From c0267088dc71e1981319e2a7b49d5f11e778b2d7 Mon Sep 17 00:00:00 2001 From: Simone Date: Thu, 18 Aug 2022 00:24:21 +0200 Subject: [PATCH] fuck take2 --- .clang-format | 28 + .gitattributes | 2 + .github/ISSUE_TEMPLATE/bug_report.md | 24 + .github/pull_request_template.md | 13 + .github/workflows/build-cmake-conan.yml | 117 + .github/workflows/build-switch.yml | 28 + .github/workflows/re3_msvc_amd64.yml | 65 + .github/workflows/re3_msvc_x86.yml | 67 + .gitignore | 363 + .gitmodules | 16 + .vscode/c_cpp_properties.json | 50 + .vscode/launch.json | 89 + .vscode/settings.json | 36 + .vscode/tasks.json | 95 + CMakeLists.txt | 100 + CODING_STYLE.md | 107 + README | 7 + autoconf/LICENSE.txt | 27 + autoconf/api.lua | 305 + autoconf/autoconf.lua | 18 + autoconf/clang.lua | 27 + autoconf/gcc.lua | 27 + autoconf/msc.lua | 62 + cmake/FindMilesSDK.cmake | 34 + cmake/FindSndFile.cmake | 67 + cmake/Findmpg123.cmake | 38 + cmake/Findopusfile.cmake | 64 + cmake/GetGitRevisionDescription.cmake | 284 + cmake/GetGitRevisionDescription.cmake.in | 43 + cmake/nx/NXFunctions.cmake | 38 + codewarrior/re3.mcp.xml | 15378 ++++++++++++++++ conanfile.py | 135 + gamefiles/TEXT/JAPANESE.gxt | Bin 0 -> 122022 bytes gamefiles/TEXT/american.gxt | Bin 0 -> 220708 bytes gamefiles/TEXT/english.gxt | Bin 0 -> 204492 bytes gamefiles/TEXT/french.gxt | Bin 0 -> 246682 bytes gamefiles/TEXT/german.gxt | Bin 0 -> 242746 bytes gamefiles/TEXT/italian.gxt | Bin 0 -> 242116 bytes gamefiles/TEXT/polish.gxt | Bin 0 -> 241748 bytes gamefiles/TEXT/russian.gxt | Bin 0 -> 222534 bytes gamefiles/TEXT/spanish.gxt | Bin 0 -> 235676 bytes gamefiles/data/PARTICLE.CFG | 363 + gamefiles/data/main_d.scm | Bin 0 -> 620154 bytes gamefiles/data/main_freeroam.scm | Bin 0 -> 1618 bytes gamefiles/gamecontrollerdb.txt | 943 + gamefiles/models/fonts_j.txd | Bin 0 -> 1052072 bytes gamefiles/models/fonts_p.txd | Bin 0 -> 1379752 bytes gamefiles/models/fonts_r.txd | Bin 0 -> 1379752 bytes gamefiles/models/frontend_ds3.txd | Bin 0 -> 590632 bytes gamefiles/models/frontend_ds4.txd | Bin 0 -> 590632 bytes gamefiles/models/frontend_nsw.txd | Bin 0 -> 2360104 bytes gamefiles/models/frontend_x360.txd | Bin 0 -> 590632 bytes gamefiles/models/frontend_xone.txd | Bin 0 -> 590632 bytes gamefiles/models/generic.txd | Bin 0 -> 1604112 bytes gamefiles/models/menu.txd | Bin 0 -> 10244648 bytes gamefiles/models/nswbtns.txd | Bin 0 -> 528424 bytes gamefiles/models/particle.txd | Bin 0 -> 414968 bytes gamefiles/models/ps3btns.txd | Bin 0 -> 528424 bytes gamefiles/models/x360btns.txd | Bin 0 -> 528424 bytes gamefiles/neo/carTweakingTable.dat | 104 + gamefiles/neo/neo.txd | Bin 0 -> 118696 bytes gamefiles/neo/rimTweakingTable.dat | 130 + gamefiles/neo/worldTweakingTable.dat | 26 + premake-vs2015.cmd | 1 + premake-vs2017.cmd | 1 + premake-vs2019.cmd | 1 + premake5.exe | Bin 0 -> 1395712 bytes premake5.lua | 477 + premake5Linux | Bin 0 -> 2035312 bytes printHash.bat | 26 + printHash.sh | 14 + res/images/logo.svg | 88 + res/images/logo_1024.png | Bin 0 -> 24295 bytes res/images/logo_256.jpg | Bin 0 -> 18096 bytes src/CMakeLists.txt | 170 + src/animation/AnimBlendAssocGroup.cpp | 174 + src/animation/AnimBlendAssocGroup.h | 20 + src/animation/AnimBlendAssociation.cpp | 205 + src/animation/AnimBlendAssociation.h | 91 + src/animation/AnimBlendClumpData.cpp | 36 + src/animation/AnimBlendClumpData.h | 57 + src/animation/AnimBlendHierarchy.cpp | 94 + src/animation/AnimBlendHierarchy.h | 33 + src/animation/AnimBlendList.h | 28 + src/animation/AnimBlendNode.cpp | 160 + src/animation/AnimBlendNode.h | 31 + src/animation/AnimBlendSequence.cpp | 200 + src/animation/AnimBlendSequence.h | 68 + src/animation/AnimManager.cpp | 941 + src/animation/AnimManager.h | 94 + src/animation/AnimationId.h | 210 + src/animation/Bones.cpp | 52 + src/animation/Bones.h | 24 + src/animation/CutsceneMgr.cpp | 424 + src/animation/CutsceneMgr.h | 51 + src/animation/FrameUpdate.cpp | 445 + src/animation/RpAnimBlend.cpp | 469 + src/animation/RpAnimBlend.h | 42 + src/audio/AudioCollision.cpp | 401 + src/audio/AudioCollision.h | 57 + src/audio/AudioLogic.cpp | 9038 +++++++++ src/audio/AudioManager.cpp | 1245 ++ src/audio/AudioManager.h | 587 + src/audio/AudioSamples.h | 3273 ++++ src/audio/AudioScriptObject.cpp | 101 + src/audio/AudioScriptObject.h | 26 + src/audio/DMAudio.cpp | 340 + src/audio/DMAudio.h | 91 + src/audio/MusicManager.cpp | 1026 ++ src/audio/MusicManager.h | 89 + src/audio/PolRadio.cpp | 805 + src/audio/PolRadio.h | 66 + src/audio/audio_enums.h | 280 + src/audio/eax/eax-util.cpp | 706 + src/audio/eax/eax-util.h | 765 + src/audio/eax/eax.h | 536 + src/audio/oal/aldlist.cpp | 291 + src/audio/oal/aldlist.h | 82 + src/audio/oal/channel.cpp | 295 + src/audio/oal/channel.h | 55 + src/audio/oal/oal_utils.cpp | 181 + src/audio/oal/oal_utils.h | 54 + src/audio/oal/stream.cpp | 1759 ++ src/audio/oal/stream.h | 192 + src/audio/sampman.h | 673 + src/audio/sampman_miles.cpp | 2490 +++ src/audio/sampman_null.cpp | 372 + src/audio/sampman_oal.cpp | 1973 ++ src/audio/soundlist.h | 303 + src/buildings/Building.cpp | 22 + src/buildings/Building.h | 21 + src/buildings/Solid.h | 12 + src/buildings/Treadable.cpp | 8 + src/buildings/Treadable.h | 17 + src/collision/ColBox.cpp | 21 + src/collision/ColBox.h | 16 + src/collision/ColLine.cpp | 9 + src/collision/ColLine.h | 14 + src/collision/ColModel.cpp | 190 + src/collision/ColModel.h | 37 + src/collision/ColPoint.cpp | 16 + src/collision/ColPoint.h | 34 + src/collision/ColSphere.cpp | 11 + src/collision/ColSphere.h | 13 + src/collision/ColTriangle.cpp | 41 + src/collision/ColTriangle.h | 68 + src/collision/Collision.cpp | 2753 +++ src/collision/Collision.h | 70 + src/collision/CompressedVector.h | 36 + src/collision/TempColModels.cpp | 297 + src/collision/TempColModels.h | 23 + src/collision/VuCollision.cpp | 282 + src/collision/VuCollision.h | 32 + src/collision/vu0Collision.dsm | 21 + src/collision/vu0Collision_1.s | 610 + src/collision/vu0Collision_2.s | 191 + src/control/AutoPilot.cpp | 128 + src/control/AutoPilot.h | 123 + src/control/Bridge.cpp | 149 + src/control/Bridge.h | 28 + src/control/CarAI.cpp | 666 + src/control/CarAI.h | 26 + src/control/CarCtrl.cpp | 2825 +++ src/control/CarCtrl.h | 143 + src/control/Curves.cpp | 31 + src/control/Curves.h | 9 + src/control/Darkel.cpp | 448 + src/control/Darkel.h | 53 + src/control/GameLogic.cpp | 320 + src/control/GameLogic.h | 13 + src/control/Garages.cpp | 2548 +++ src/control/Garages.h | 263 + src/control/NameGrid.cpp | 87 + src/control/NameGrid.h | 53 + src/control/OnscreenTimer.cpp | 159 + src/control/OnscreenTimer.h | 49 + src/control/PathFind.cpp | 1830 ++ src/control/PathFind.h | 232 + src/control/Phones.cpp | 497 + src/control/Phones.h | 77 + src/control/Pickups.cpp | 1568 ++ src/control/Pickups.h | 146 + src/control/PowerPoints.cpp | 22 + src/control/PowerPoints.h | 26 + src/control/Record.cpp | 529 + src/control/Record.h | 104 + src/control/Remote.cpp | 51 + src/control/Remote.h | 8 + src/control/Replay.cpp | 1635 ++ src/control/Replay.h | 329 + src/control/Restart.cpp | 249 + src/control/Restart.h | 36 + src/control/RoadBlocks.cpp | 197 + src/control/RoadBlocks.h | 16 + src/control/SceneEdit.cpp | 1127 ++ src/control/SceneEdit.h | 96 + src/control/Script.cpp | 3014 +++ src/control/Script.h | 630 + src/control/Script2.cpp | 1555 ++ src/control/Script3.cpp | 2362 +++ src/control/Script4.cpp | 2178 +++ src/control/Script5.cpp | 2606 +++ src/control/Script6.cpp | 1356 ++ src/control/ScriptCommands.h | 1194 ++ src/control/ScriptDebug.cpp | 1441 ++ src/control/TrafficLights.cpp | 330 + src/control/TrafficLights.h | 27 + src/core/Accident.cpp | 128 + src/core/Accident.h | 31 + src/core/AnimViewer.cpp | 422 + src/core/AnimViewer.h | 12 + src/core/Cam.cpp | 5410 ++++++ src/core/Camera.cpp | 3726 ++++ src/core/Camera.h | 653 + src/core/CdStream.cpp | 538 + src/core/CdStream.h | 48 + src/core/CdStreamPosix.cpp | 604 + src/core/Clock.cpp | 117 + src/core/Clock.h | 31 + src/core/ControllerConfig.cpp | 2884 +++ src/core/ControllerConfig.h | 226 + src/core/Crime.h | 36 + src/core/Debug.cpp | 170 + src/core/Debug.h | 45 + src/core/Directory.cpp | 74 + src/core/Directory.h | 23 + src/core/EventList.cpp | 237 + src/core/EventList.h | 65 + src/core/FileLoader.cpp | 1852 ++ src/core/FileLoader.h | 49 + src/core/FileMgr.cpp | 313 + src/core/FileMgr.h | 23 + src/core/Fire.cpp | 440 + src/core/Fire.h | 51 + src/core/FrontEndControls.cpp | 1887 ++ src/core/FrontEndControls.h | 750 + src/core/Frontend.cpp | 6802 +++++++ src/core/Frontend.h | 839 + src/core/FrontendTriggers.h | 1393 ++ src/core/Frontend_PS2.cpp | 3033 +++ src/core/Frontend_PS2.h | 245 + src/core/Game.cpp | 1464 ++ src/core/Game.h | 49 + src/core/General.h | 157 + src/core/IniFile.cpp | 28 + src/core/IniFile.h | 13 + src/core/Lists.cpp | 26 + src/core/Lists.h | 130 + src/core/MenuScreens.cpp | 453 + src/core/MenuScreensCustom.cpp | 936 + src/core/Pad.cpp | 3025 +++ src/core/Pad.h | 474 + src/core/Placeable.cpp | 66 + src/core/Placeable.h | 37 + src/core/PlayerInfo.cpp | 662 + src/core/PlayerInfo.h | 97 + src/core/Pools.cpp | 585 + src/core/Pools.h | 61 + src/core/Profile.cpp | 71 + src/core/Profile.h | 28 + src/core/Radar.cpp | 1568 ++ src/core/Radar.h | 201 + src/core/Range2D.cpp | 28 + src/core/Range2D.h | 11 + src/core/Range3D.cpp | 30 + src/core/Range3D.h | 11 + src/core/References.cpp | 126 + src/core/References.h | 20 + src/core/Stats.cpp | 420 + src/core/Stats.h | 90 + src/core/Streaming.cpp | 2831 +++ src/core/Streaming.h | 197 + src/core/SurfaceTable.cpp | 143 + src/core/SurfaceTable.h | 80 + src/core/TimeStep.cpp | 5 + src/core/TimeStep.h | 10 + src/core/Timer.cpp | 329 + src/core/Timer.h | 71 + src/core/User.cpp | 127 + src/core/User.h | 41 + src/core/Wanted.cpp | 492 + src/core/Wanted.h | 58 + src/core/World.cpp | 2162 +++ src/core/World.h | 155 + src/core/ZoneCull.cpp | 1589 ++ src/core/ZoneCull.h | 137 + src/core/Zones.cpp | 821 + src/core/Zones.h | 114 + src/core/common.h | 401 + src/core/config.h | 498 + src/core/main.cpp | 2493 +++ src/core/main.h | 76 + src/core/obrstr.cpp | 119 + src/core/obrstr.h | 9 + src/core/re3.cpp | 1233 ++ src/core/templates.h | 269 + src/core/timebars.cpp | 121 + src/core/timebars.h | 13 + src/entities/Dummy.cpp | 52 + src/entities/Dummy.h | 20 + src/entities/Entity.cpp | 804 + src/entities/Entity.h | 175 + src/entities/Physical.cpp | 2002 ++ src/entities/Physical.h | 161 + src/extras/GitSHA1.cpp.in | 2 + src/extras/GitSHA1.h | 1 + src/extras/arrow.inc | 16 + src/extras/cursor.inc | 16 + src/extras/custompipes.cpp | 543 + src/extras/custompipes.h | 145 + src/extras/custompipes_d3d9.cpp | 728 + src/extras/custompipes_gl.cpp | 742 + src/extras/debugmenu.cpp | 1312 ++ src/extras/debugmenu.h | 204 + src/extras/frontendoption.cpp | 182 + src/extras/frontendoption.h | 93 + src/extras/ini.h | 761 + src/extras/postfx.cpp | 476 + src/extras/postfx.h | 36 + src/extras/re3_inttypes.h | 216 + src/extras/screendroplets.cpp | 817 + src/extras/screendroplets.h | 78 + src/extras/shaders/colourfilterIII.frag | 24 + src/extras/shaders/colourfilterIII_PS.hlsl | 15 + src/extras/shaders/contrast.frag | 19 + src/extras/shaders/contrastPS.hlsl | 21 + src/extras/shaders/default_UV2.vert | 25 + src/extras/shaders/default_UV2_VS.hlsl | 54 + src/extras/shaders/im2d.vert | 19 + src/extras/shaders/im2d_UV2.vert | 21 + src/extras/shaders/lighting.h | 44 + src/extras/shaders/make_glsl.sh | 9 + src/extras/shaders/make_hlsl.cmd | 3 + src/extras/shaders/makeinc_glsl.sh | 6 + src/extras/shaders/makeinc_hlsl.sh | 6 + src/extras/shaders/neoGloss.frag | 26 + src/extras/shaders/neoGloss.vert | 25 + src/extras/shaders/neoGloss_PS.hlsl | 20 + src/extras/shaders/neoGloss_VS.hlsl | 35 + src/extras/shaders/neoRim.vert | 34 + src/extras/shaders/neoRimSkin.vert | 43 + src/extras/shaders/neoRimSkin_VS.hlsl | 73 + src/extras/shaders/neoRim_VS.hlsl | 61 + src/extras/shaders/neoVehicle.frag | 29 + src/extras/shaders/neoVehicle.vert | 51 + src/extras/shaders/neoVehicle_PS.hlsl | 34 + src/extras/shaders/neoVehicle_VS.hlsl | 64 + src/extras/shaders/neoWorldIII.frag | 25 + src/extras/shaders/neoWorldIII_PS.hlsl | 25 + src/extras/shaders/screenDroplet.frag | 18 + src/extras/shaders/screenDroplet_PS.hlsl | 17 + src/extras/shaders/simple.frag | 17 + src/extras/shaders/standardConstants.h | 28 + src/fakerw/fake.cpp | 1010 + src/fakerw/rpanisot.h | 6 + src/fakerw/rphanim.h | 72 + src/fakerw/rpmatfx.h | 43 + src/fakerw/rpskin.h | 26 + src/fakerw/rpworld.h | 336 + src/fakerw/rtbmp.h | 4 + src/fakerw/rtcharse.h | 14 + src/fakerw/rtpng.h | 4 + src/fakerw/rtquat.h | 15 + src/fakerw/rwcore.h | 423 + src/fakerw/rwplcore.h | 498 + src/math/Matrix.cpp | 544 + src/math/Matrix.h | 131 + src/math/Quaternion.cpp | 177 + src/math/Quaternion.h | 95 + src/math/Rect.cpp | 17 + src/math/Rect.h | 71 + src/math/Vector.cpp | 46 + src/math/Vector.h | 129 + src/math/Vector2D.h | 109 + src/math/VuVector.h | 32 + src/math/math.cpp | 118 + src/math/maths.h | 19 + src/modelinfo/BaseModelInfo.cpp | 101 + src/modelinfo/BaseModelInfo.h | 80 + src/modelinfo/ClumpModelInfo.cpp | 210 + src/modelinfo/ClumpModelInfo.h | 54 + src/modelinfo/MloModelInfo.cpp | 39 + src/modelinfo/MloModelInfo.h | 14 + src/modelinfo/ModelIndices.cpp | 34 + src/modelinfo/ModelIndices.h | 509 + src/modelinfo/ModelInfo.cpp | 254 + src/modelinfo/ModelInfo.h | 55 + src/modelinfo/PedModelInfo.cpp | 378 + src/modelinfo/PedModelInfo.h | 67 + src/modelinfo/SimpleModelInfo.cpp | 173 + src/modelinfo/SimpleModelInfo.h | 65 + src/modelinfo/TimeModelInfo.cpp | 33 + src/modelinfo/TimeModelInfo.h | 21 + src/modelinfo/VehicleModelInfo.cpp | 1121 ++ src/modelinfo/VehicleModelInfo.h | 165 + src/modelinfo/XtraCompsModelInfo.h | 13 + src/objects/CutsceneHead.cpp | 230 + src/objects/CutsceneHead.h | 28 + src/objects/CutsceneObject.cpp | 153 + src/objects/CutsceneObject.h | 33 + src/objects/DummyObject.cpp | 13 + src/objects/DummyObject.h | 14 + src/objects/Object.cpp | 419 + src/objects/Object.h | 94 + src/objects/ObjectData.cpp | 98 + src/objects/ObjectData.h | 27 + src/objects/ParticleObject.cpp | 1365 ++ src/objects/ParticleObject.h | 108 + src/objects/Projectile.cpp | 15 + src/objects/Projectile.h | 9 + src/peds/CivilianPed.cpp | 467 + src/peds/CivilianPed.h | 16 + src/peds/CopPed.cpp | 736 + src/peds/CopPed.h | 41 + src/peds/DummyPed.h | 12 + src/peds/EmergencyPed.cpp | 417 + src/peds/EmergencyPed.h | 41 + src/peds/Gangs.cpp | 78 + src/peds/Gangs.h | 44 + src/peds/Ped.cpp | 8557 +++++++++ src/peds/Ped.h | 962 + src/peds/PedAI.cpp | 5404 ++++++ src/peds/PedChat.cpp | 152 + src/peds/PedDebug.cpp | 310 + src/peds/PedFight.cpp | 3268 ++++ src/peds/PedIK.cpp | 560 + src/peds/PedIK.h | 68 + src/peds/PedPlacement.cpp | 51 + src/peds/PedPlacement.h | 8 + src/peds/PedRoutes.cpp | 79 + src/peds/PedRoutes.h | 15 + src/peds/PedType.cpp | 317 + src/peds/PedType.h | 173 + src/peds/PlayerPed.cpp | 1581 ++ src/peds/PlayerPed.h | 98 + src/peds/Population.cpp | 1174 ++ src/peds/Population.h | 89 + src/renderer/2dEffect.h | 93 + src/renderer/Antennas.cpp | 129 + src/renderer/Antennas.h | 25 + src/renderer/Clouds.cpp | 466 + src/renderer/Clouds.h | 21 + src/renderer/Console.cpp | 96 + src/renderer/Console.h | 27 + src/renderer/Coronas.cpp | 779 + src/renderer/Coronas.h | 101 + src/renderer/Credits.cpp | 518 + src/renderer/Credits.h | 15 + src/renderer/Draw.cpp | 95 + src/renderer/Draw.h | 73 + src/renderer/Fluff.cpp | 870 + src/renderer/Fluff.h | 106 + src/renderer/Font.cpp | 1628 ++ src/renderer/Font.h | 182 + src/renderer/Glass.cpp | 719 + src/renderer/Glass.h | 52 + src/renderer/Hud.cpp | 1713 ++ src/renderer/Hud.h | 81 + src/renderer/Instance.cpp | 9 + src/renderer/Instance.h | 14 + src/renderer/Lines.cpp | 74 + src/renderer/Lines.h | 7 + src/renderer/MBlur.cpp | 325 + src/renderer/MBlur.h | 17 + src/renderer/Particle.cpp | 1902 ++ src/renderer/Particle.h | 94 + src/renderer/ParticleMgr.cpp | 243 + src/renderer/ParticleMgr.h | 130 + src/renderer/ParticleType.h | 77 + src/renderer/PlayerSkin.cpp | 166 + src/renderer/PlayerSkin.h | 15 + src/renderer/PointLights.cpp | 289 + src/renderer/PointLights.h | 45 + src/renderer/RenderBuffer.cpp | 52 + src/renderer/RenderBuffer.h | 18 + src/renderer/Renderer.cpp | 1841 ++ src/renderer/Renderer.h | 119 + src/renderer/Rubbish.cpp | 436 + src/renderer/Rubbish.h | 55 + src/renderer/Shadows.cpp | 1785 ++ src/renderer/Shadows.h | 180 + src/renderer/Skidmarks.cpp | 262 + src/renderer/Skidmarks.h | 32 + src/renderer/SpecialFX.cpp | 1194 ++ src/renderer/SpecialFX.h | 224 + src/renderer/Sprite.cpp | 603 + src/renderer/Sprite.h | 28 + src/renderer/Sprite2d.cpp | 490 + src/renderer/Sprite2d.h | 53 + src/renderer/TexList.cpp | 41 + src/renderer/TexList.h | 14 + src/renderer/Timecycle.cpp | 317 + src/renderer/Timecycle.h | 152 + src/renderer/WaterCannon.cpp | 307 + src/renderer/WaterCannon.h | 39 + src/renderer/WaterLevel.cpp | 1554 ++ src/renderer/WaterLevel.h | 103 + src/renderer/Weather.cpp | 552 + src/renderer/Weather.h | 71 + src/rw/ClumpRead.cpp | 224 + src/rw/Lights.cpp | 355 + src/rw/Lights.h | 26 + src/rw/MemoryHeap.cpp | 497 + src/rw/MemoryHeap.h | 202 + src/rw/MemoryMgr.cpp | 130 + src/rw/MemoryMgr.h | 12 + src/rw/NodeName.cpp | 77 + src/rw/NodeName.h | 4 + src/rw/RwHelper.cpp | 735 + src/rw/RwHelper.h | 63 + src/rw/RwMatFX.cpp | 312 + src/rw/RwPS2AlphaTest.cpp | 247 + src/rw/TexRead.cpp | 459 + src/rw/TexturePools.cpp | 221 + src/rw/TexturePools.h | 42 + src/rw/TxdStore.cpp | 183 + src/rw/TxdStore.h | 44 + src/rw/VisibilityPlugins.cpp | 1059 ++ src/rw/VisibilityPlugins.h | 141 + src/save/Date.cpp | 91 + src/save/Date.h | 18 + src/save/GenericGameStorage.cpp | 1174 ++ src/save/GenericGameStorage.h | 63 + src/save/MemoryCard.cpp | 3084 ++++ src/save/MemoryCard.h | 197 + src/save/PCSave.cpp | 166 + src/save/PCSave.h | 40 + src/save/SaveBuf.h | 73 + src/skel/crossplatform.cpp | 425 + src/skel/crossplatform.h | 184 + src/skel/events.cpp | 831 + src/skel/events.h | 7 + src/skel/glfw/glfw.cpp | 2572 +++ src/skel/platform.h | 59 + src/skel/skeleton.cpp | 432 + src/skel/skeleton.h | 290 + src/skel/win/gta3.ico | Bin 0 -> 161654 bytes src/skel/win/resource.h | 21 + src/skel/win/win.cpp | 3432 ++++ src/skel/win/win.h | 91 + src/skel/win/win.rc | 47 + src/text/Messages.cpp | 815 + src/text/Messages.h | 69 + src/text/Pager.cpp | 184 + src/text/Pager.h | 28 + src/text/Text.cpp | 331 + src/text/Text.h | 66 + src/vehicles/Automobile.cpp | 4735 +++++ src/vehicles/Automobile.h | 204 + src/vehicles/Bike.h | 45 + src/vehicles/Boat.cpp | 952 + src/vehicles/Boat.h | 81 + src/vehicles/CarGen.cpp | 271 + src/vehicles/CarGen.h | 54 + src/vehicles/Cranes.cpp | 759 + src/vehicles/Cranes.h | 97 + src/vehicles/DamageManager.cpp | 229 + src/vehicles/DamageManager.h | 115 + src/vehicles/Door.cpp | 170 + src/vehicles/Door.h | 69 + src/vehicles/Floater.cpp | 185 + src/vehicles/Floater.h | 45 + src/vehicles/HandlingMgr.cpp | 270 + src/vehicles/HandlingMgr.h | 156 + src/vehicles/Heli.cpp | 1070 ++ src/vehicles/Heli.h | 101 + src/vehicles/Plane.cpp | 975 + src/vehicles/Plane.h | 71 + src/vehicles/Train.cpp | 738 + src/vehicles/Train.h | 86 + src/vehicles/Transmission.cpp | 138 + src/vehicles/Transmission.h | 28 + src/vehicles/Vehicle.cpp | 1385 ++ src/vehicles/Vehicle.h | 293 + src/weapons/BulletInfo.cpp | 306 + src/weapons/BulletInfo.h | 25 + src/weapons/Explosion.cpp | 465 + src/weapons/Explosion.h | 49 + src/weapons/ProjectileInfo.cpp | 342 + src/weapons/ProjectileInfo.h | 33 + src/weapons/ShotInfo.cpp | 149 + src/weapons/ShotInfo.h | 24 + src/weapons/Weapon.cpp | 2415 +++ src/weapons/Weapon.h | 78 + src/weapons/WeaponEffects.cpp | 104 + src/weapons/WeaponEffects.h | 26 + src/weapons/WeaponInfo.cpp | 189 + src/weapons/WeaponInfo.h | 52 + src/weapons/WeaponType.h | 49 + utils/gxt/american.txt | 8110 ++++++++ utils/gxt/build.bat | 8 + utils/gxt/english.txt | 7026 +++++++ utils/gxt/french.txt | 8378 +++++++++ utils/gxt/german.txt | 8193 ++++++++ utils/gxt/gxt.exe | Bin 0 -> 307200 bytes utils/gxt/italian.txt | 8205 +++++++++ utils/gxt/polish.txt | 8117 ++++++++ utils/gxt/russian.txt | 8118 ++++++++ utils/gxt/spanish.txt | 8177 ++++++++ vendor/librw/.appveyor.yml | 32 + .../.github/workflows/build-cmake-conan.yml | 60 + vendor/librw/.github/workflows/build-ps2.yml | 29 + .../librw/.github/workflows/build-switch.yml | 26 + vendor/librw/.gitignore | 10 + vendor/librw/.travis.yml | 46 + vendor/librw/ARCHITECTURE.MD | 174 + vendor/librw/CMakeLists.txt | 160 + vendor/librw/Dockerfile | 60 + vendor/librw/LICENSE | 22 + vendor/librw/README.cmake | 9 + vendor/librw/README.md | 26 + vendor/librw/TODO | 24 + vendor/librw/args.h | 24 + vendor/librw/cmake/FindSDL2.cmake | 38 + vendor/librw/cmake/librw-config.cmake.in | 22 + vendor/librw/cmake/nx/NXFunctions.cmake | 37 + vendor/librw/cmake/ps2/PS2Functions.cmake | 18 + .../cmaketoolchain/CMakeDSMCompiler.cmake.in | 16 + .../cmaketoolchain/CMakeDSMInformation.cmake | 79 + .../CMakeDetermineDSMCompiler.cmake | 87 + .../cmaketoolchain/CMakeTestDSMCompiler.cmake | 7 + .../Platform/PlayStation2.cmake | 1 + .../cmake/ps2/cmaketoolchain/conanfile.py | 24 + .../ps2/cmaketoolchain/toolchain_ps2_ee.cmake | 92 + vendor/librw/conan/playstation2 | 12 + vendor/librw/conanfile.py | 136 + vendor/librw/docker_rebuild_ps2.sh | 22 + vendor/librw/premake-vs2019.cmd | 1 + vendor/librw/premake5.exe | Bin 0 -> 1362432 bytes vendor/librw/premake5.lua | 277 + vendor/librw/rw.h | 25 + vendor/librw/skeleton/CMakeLists.txt | 80 + vendor/librw/skeleton/glfw.cpp | 262 + vendor/librw/skeleton/imgui/ImGuizmo.cpp | 2724 +++ vendor/librw/skeleton/imgui/ImGuizmo.h | 213 + vendor/librw/skeleton/imgui/LICENSE_imgui.txt | 21 + .../librw/skeleton/imgui/LICENSE_imguizmo.txt | 21 + vendor/librw/skeleton/imgui/imconfig.h | 121 + vendor/librw/skeleton/imgui/imgui.cpp | 11589 ++++++++++++ vendor/librw/skeleton/imgui/imgui.h | 2852 +++ vendor/librw/skeleton/imgui/imgui_demo.cpp | 7725 ++++++++ vendor/librw/skeleton/imgui/imgui_draw.cpp | 4152 +++++ vendor/librw/skeleton/imgui/imgui_impl_rw.cpp | 240 + vendor/librw/skeleton/imgui/imgui_impl_rw.h | 5 + vendor/librw/skeleton/imgui/imgui_internal.h | 2688 +++ vendor/librw/skeleton/imgui/imgui_tables.cpp | 4028 ++++ vendor/librw/skeleton/imgui/imgui_widgets.cpp | 8056 ++++++++ vendor/librw/skeleton/imgui/imstb_rectpack.h | 639 + vendor/librw/skeleton/imgui/imstb_textedit.h | 1447 ++ vendor/librw/skeleton/imgui/imstb_truetype.h | 4903 +++++ vendor/librw/skeleton/sdl2.cpp | 326 + vendor/librw/skeleton/skeleton.cpp | 192 + vendor/librw/skeleton/skeleton.h | 120 + vendor/librw/skeleton/win.cpp | 315 + vendor/librw/src/CMakeLists.txt | 255 + vendor/librw/src/anim.cpp | 293 + vendor/librw/src/base.cpp | 1121 ++ vendor/librw/src/base.err | 24 + vendor/librw/src/bmp.cpp | 284 + vendor/librw/src/camera.cpp | 524 + vendor/librw/src/charset.cpp | 244 + vendor/librw/src/clump.cpp | 669 + vendor/librw/src/d3d/d3d.cpp | 1087 ++ vendor/librw/src/d3d/d3d8.cpp | 678 + vendor/librw/src/d3d/d3d8matfx.cpp | 56 + vendor/librw/src/d3d/d3d8render.cpp | 65 + vendor/librw/src/d3d/d3d8skin.cpp | 56 + vendor/librw/src/d3d/d3d9.cpp | 853 + vendor/librw/src/d3d/d3d9matfx.cpp | 313 + vendor/librw/src/d3d/d3d9render.cpp | 185 + vendor/librw/src/d3d/d3d9skin.cpp | 418 + vendor/librw/src/d3d/d3ddevice.cpp | 2023 ++ vendor/librw/src/d3d/d3dimmed.cpp | 386 + vendor/librw/src/d3d/d3drender.cpp | 413 + vendor/librw/src/d3d/rwd3d.h | 411 + vendor/librw/src/d3d/rwd3d8.h | 74 + vendor/librw/src/d3d/rwd3d9.h | 112 + vendor/librw/src/d3d/rwd3dimpl.h | 81 + vendor/librw/src/d3d/rwxbox.h | 195 + vendor/librw/src/d3d/rwxboximpl.h | 11 + vendor/librw/src/d3d/shaders/default_PS.h | 72 + vendor/librw/src/d3d/shaders/default_PS.hlsl | 19 + vendor/librw/src/d3d/shaders/default_VS.hlsl | 51 + vendor/librw/src/d3d/shaders/default_all_VS.h | 491 + vendor/librw/src/d3d/shaders/default_amb_VS.h | 156 + .../src/d3d/shaders/default_amb_dir_VS.h | 272 + vendor/librw/src/d3d/shaders/default_tex_PS.h | 89 + vendor/librw/src/d3d/shaders/im2d_PS.h | 72 + vendor/librw/src/d3d/shaders/im2d_PS.hlsl | 19 + vendor/librw/src/d3d/shaders/im2d_VS.h | 107 + vendor/librw/src/d3d/shaders/im2d_VS.hlsl | 30 + vendor/librw/src/d3d/shaders/im2d_tex_PS.h | 89 + vendor/librw/src/d3d/shaders/lighting.h | 44 + vendor/librw/src/d3d/shaders/make_default.cmd | 11 + vendor/librw/src/d3d/shaders/make_matfx.cmd | 7 + vendor/librw/src/d3d/shaders/make_skin.cmd | 4 + vendor/librw/src/d3d/shaders/matfx_env_PS.h | 124 + .../librw/src/d3d/shaders/matfx_env_PS.hlsl | 42 + .../librw/src/d3d/shaders/matfx_env_VS.hlsl | 59 + .../librw/src/d3d/shaders/matfx_env_all_VS.h | 535 + .../librw/src/d3d/shaders/matfx_env_amb_VS.h | 225 + .../src/d3d/shaders/matfx_env_amb_dir_VS.h | 316 + .../librw/src/d3d/shaders/matfx_env_tex_PS.h | 141 + vendor/librw/src/d3d/shaders/skin_VS.hlsl | 63 + vendor/librw/src/d3d/shaders/skin_all_VS.h | 664 + vendor/librw/src/d3d/shaders/skin_amb_VS.h | 253 + .../librw/src/d3d/shaders/skin_amb_dir_VS.h | 503 + .../librw/src/d3d/shaders/standardConstants.h | 28 + vendor/librw/src/d3d/xbox.cpp | 1005 + vendor/librw/src/d3d/xboxmatfx.cpp | 53 + vendor/librw/src/d3d/xboxskin.cpp | 252 + vendor/librw/src/d3d/xboxvfmt.cpp | 115 + vendor/librw/src/engine.cpp | 566 + vendor/librw/src/error.cpp | 49 + vendor/librw/src/frame.cpp | 463 + vendor/librw/src/geometry.cpp | 1052 ++ vendor/librw/src/geoplg.cpp | 339 + vendor/librw/src/gl/gl3.cpp | 56 + vendor/librw/src/gl/gl3device.cpp | 2111 +++ vendor/librw/src/gl/gl3immed.cpp | 306 + vendor/librw/src/gl/gl3matfx.cpp | 258 + vendor/librw/src/gl/gl3pipe.cpp | 333 + vendor/librw/src/gl/gl3raster.cpp | 1045 ++ vendor/librw/src/gl/gl3render.cpp | 184 + vendor/librw/src/gl/gl3shader.cpp | 355 + vendor/librw/src/gl/gl3skin.cpp | 366 + vendor/librw/src/gl/glad/glad.c | 1590 ++ vendor/librw/src/gl/glad/glad.h | 2808 +++ vendor/librw/src/gl/glad/khrplatform.h | 290 + vendor/librw/src/gl/rwgl3.h | 288 + vendor/librw/src/gl/rwgl3impl.h | 76 + vendor/librw/src/gl/rwgl3plg.h | 16 + vendor/librw/src/gl/rwgl3shader.h | 69 + vendor/librw/src/gl/rwwdgl.h | 88 + vendor/librw/src/gl/shaders/Makefile | 45 + vendor/librw/src/gl/shaders/default.vert | 23 + vendor/librw/src/gl/shaders/default_vs_gl.inc | 25 + vendor/librw/src/gl/shaders/header.frag | 30 + vendor/librw/src/gl/shaders/header.vert | 128 + vendor/librw/src/gl/shaders/header_fs.inc | 32 + vendor/librw/src/gl/shaders/header_vs.inc | 130 + vendor/librw/src/gl/shaders/im2d.vert | 18 + vendor/librw/src/gl/shaders/im2d_gl.inc | 20 + vendor/librw/src/gl/shaders/im3d.vert | 16 + vendor/librw/src/gl/shaders/im3d_gl.inc | 18 + vendor/librw/src/gl/shaders/matfx_env.frag | 34 + vendor/librw/src/gl/shaders/matfx_env.vert | 31 + vendor/librw/src/gl/shaders/matfx_gl.inc | 69 + vendor/librw/src/gl/shaders/simple.frag | 15 + vendor/librw/src/gl/shaders/simple_fs_gl.inc | 17 + vendor/librw/src/gl/shaders/skin.vert | 32 + vendor/librw/src/gl/shaders/skin_gl.inc | 34 + vendor/librw/src/gl/wdgl.cpp | 875 + vendor/librw/src/hanim.cpp | 414 + vendor/librw/src/image.cpp | 1324 ++ vendor/librw/src/light.cpp | 163 + vendor/librw/src/lodepng/lodepng.cpp | 6410 +++++++ vendor/librw/src/lodepng/lodepng.h | 1945 ++ vendor/librw/src/matfx.cpp | 641 + vendor/librw/src/pipeline.cpp | 198 + vendor/librw/src/plg.cpp | 246 + vendor/librw/src/png.cpp | 151 + vendor/librw/src/prim.cpp | 70 + vendor/librw/src/ps2/pds.cpp | 203 + vendor/librw/src/ps2/ps2.cpp | 1573 ++ vendor/librw/src/ps2/ps2device.cpp | 49 + vendor/librw/src/ps2/ps2matfx.cpp | 68 + vendor/librw/src/ps2/ps2raster.cpp | 2238 +++ vendor/librw/src/ps2/ps2skin.cpp | 334 + vendor/librw/src/ps2/rwps2.h | 258 + vendor/librw/src/ps2/rwps2impl.h | 16 + vendor/librw/src/ps2/rwps2plg.h | 27 + vendor/librw/src/raster.cpp | 559 + vendor/librw/src/render.cpp | 96 + vendor/librw/src/rwanim.h | 195 + vendor/librw/src/rwbase.h | 713 + vendor/librw/src/rwcharset.h | 25 + vendor/librw/src/rwengine.h | 263 + vendor/librw/src/rwerror.h | 25 + vendor/librw/src/rwobjects.h | 904 + vendor/librw/src/rwpipeline.h | 65 + vendor/librw/src/rwplg.h | 85 + vendor/librw/src/rwplugins.h | 252 + vendor/librw/src/rwrender.h | 139 + vendor/librw/src/rwuserdata.h | 94 + vendor/librw/src/skin.cpp | 486 + vendor/librw/src/texture.cpp | 596 + vendor/librw/src/tga.cpp | 222 + vendor/librw/src/tristrip.cpp | 682 + vendor/librw/src/userdata.cpp | 364 + vendor/librw/src/uvanim.cpp | 502 + vendor/librw/src/vgafont.inc | 256 + vendor/librw/src/world.cpp | 198 + vendor/librw/tools/CMakeLists.txt | 20 + vendor/librw/tools/camera/CMakeLists.txt | 19 + vendor/librw/tools/camera/camexamp.cpp | 395 + vendor/librw/tools/camera/camexamp.h | 62 + vendor/librw/tools/camera/files/clump.dff | Bin 0 -> 57700 bytes .../tools/camera/files/clump/shinarm.png | Bin 0 -> 84781 bytes .../tools/camera/files/clump/shinbody.png | Bin 0 -> 101261 bytes .../tools/camera/files/clump/shinface.png | Bin 0 -> 81338 bytes .../tools/camera/files/clump/shinleg.png | Bin 0 -> 89939 bytes vendor/librw/tools/camera/main.cpp | 508 + vendor/librw/tools/camera/viewer.cpp | 51 + vendor/librw/tools/camera/viewer.h | 6 + vendor/librw/tools/dumprwtree/CMakeLists.txt | 16 + vendor/librw/tools/dumprwtree/dumprwtree.cpp | 145 + vendor/librw/tools/im2d/CMakeLists.txt | 23 + vendor/librw/tools/im2d/files/whiteash.png | Bin 0 -> 4313 bytes vendor/librw/tools/im2d/im2d.cpp | 134 + vendor/librw/tools/im2d/im2d.h | 62 + vendor/librw/tools/im2d/linelist.cpp | 159 + vendor/librw/tools/im2d/main.cpp | 254 + vendor/librw/tools/im2d/polyline.cpp | 132 + vendor/librw/tools/im2d/trifan.cpp | 119 + vendor/librw/tools/im2d/trilist.cpp | 166 + vendor/librw/tools/im2d/tristrip.cpp | 130 + vendor/librw/tools/im3d/CMakeLists.txt | 23 + vendor/librw/tools/im3d/files/whiteash.png | Bin 0 -> 4313 bytes vendor/librw/tools/im3d/im3d.cpp | 138 + vendor/librw/tools/im3d/im3d.h | 62 + vendor/librw/tools/im3d/linelist.cpp | 176 + vendor/librw/tools/im3d/main.cpp | 268 + vendor/librw/tools/im3d/polyline.cpp | 157 + vendor/librw/tools/im3d/trifan.cpp | 152 + vendor/librw/tools/im3d/trilist.cpp | 202 + vendor/librw/tools/im3d/tristrip.cpp | 169 + vendor/librw/tools/imguitest/CMakeLists.txt | 17 + vendor/librw/tools/imguitest/main.cpp | 175 + vendor/librw/tools/lights/CMakeLists.txt | 18 + vendor/librw/tools/lights/checker.dff | Bin 0 -> 14348 bytes vendor/librw/tools/lights/lights.cpp | 463 + vendor/librw/tools/lights/lights.h | 37 + vendor/librw/tools/lights/main.cpp | 396 + vendor/librw/tools/lights/main.h | 7 + vendor/librw/tools/playground/CMakeLists.txt | 22 + vendor/librw/tools/playground/camera.cpp | 134 + vendor/librw/tools/playground/camera.h | 26 + .../tools/playground/files/Bm437_IBM_BIOS.FON | Bin 0 -> 4064 bytes .../tools/playground/files/Bm437_IBM_BIOS.tga | Bin 0 -> 65554 bytes .../tools/playground/files/Bm437_IBM_VGA8.FON | Bin 0 -> 6112 bytes .../tools/playground/files/Bm437_IBM_VGA8.tga | Bin 0 -> 131090 bytes .../librw/tools/playground/files/foobar.tga | Bin 0 -> 65554 bytes vendor/librw/tools/playground/files/maze.tga | Bin 0 -> 49196 bytes .../librw/tools/playground/files/teapot.dff | Bin 0 -> 42796 bytes vendor/librw/tools/playground/font.cpp | 181 + vendor/librw/tools/playground/main.cpp | 641 + vendor/librw/tools/playground/ras_test.cpp | 902 + vendor/librw/tools/playground/splines.cpp | 520 + vendor/librw/tools/playground/tl_tests.cpp | 766 + vendor/librw/tools/ps2test/CMakeLists.txt | 26 + vendor/librw/tools/ps2test/gs.h | 437 + vendor/librw/tools/ps2test/main.cpp | 787 + vendor/librw/tools/ps2test/mem.h | 95 + vendor/librw/tools/ps2test/ps2.h | 23 + vendor/librw/tools/ps2test/vu/defaultpipe.dsm | 93 + vendor/librw/tools/ps2test/vu/light.vu | 94 + vendor/librw/tools/ps2test/vu/setup_persp.vu | 39 + vendor/librw/tools/ps2test/vu/skinpipe.dsm | 94 + vendor/librw/tools/ska2anm/CMakeLists.txt | 16 + vendor/librw/tools/ska2anm/ska2anm.cpp | 83 + vendor/librw/tools/subrast/CMakeLists.txt | 19 + vendor/librw/tools/subrast/files/clump.dff | Bin 0 -> 76384 bytes .../tools/subrast/files/textures/whiteash.png | Bin 0 -> 4313 bytes vendor/librw/tools/subrast/main.cpp | 354 + vendor/librw/tools/subrast/subrast.cpp | 141 + vendor/librw/tools/subrast/subrast.h | 10 + vendor/libsndfile/ChangeLog | 9764 ++++++++++ vendor/libsndfile/NEWS | 199 + vendor/libsndfile/dist/Win32/libsndfile-1.dll | Bin 0 -> 1679360 bytes vendor/libsndfile/dist/Win64/libsndfile-1.dll | Bin 0 -> 1748992 bytes vendor/libsndfile/include/sndfile.h | 857 + vendor/libsndfile/include/sndfile.hh | 446 + vendor/libsndfile/lib/Win32/libsndfile-1.def | 47 + vendor/libsndfile/lib/Win32/libsndfile-1.lib | Bin 0 -> 9948 bytes .../libsndfile/lib/Win32/pkgconfig/sndfile.pc | 12 + vendor/libsndfile/lib/Win64/libsndfile-1.def | 47 + vendor/libsndfile/lib/Win64/libsndfile-1.lib | Bin 0 -> 9760 bytes .../libsndfile/lib/Win64/pkgconfig/sndfile.pc | 12 + vendor/milessdk/include/mss.h | 141 + vendor/milessdk/lib/mss32.lib | Bin 0 -> 15570 bytes vendor/mpg123/dist/Win32/libmpg123-0.dll | Bin 0 -> 343552 bytes vendor/mpg123/dist/Win64/libmpg123-0.dll | Bin 0 -> 345088 bytes vendor/mpg123/include/fmt123.h | 159 + vendor/mpg123/include/mpg123.h | 1697 ++ vendor/mpg123/lib/Win32/libmpg123-0.lib | Bin 0 -> 34076 bytes vendor/mpg123/lib/Win64/libmpg123-0.lib | Bin 0 -> 33342 bytes vendor/ogg/.gitignore | 50 + vendor/ogg/.gitlab-ci.yml | 26 + vendor/ogg/.travis.yml | 25 + vendor/ogg/AUTHORS | 7 + vendor/ogg/CHANGES | 104 + vendor/ogg/CMakeLists.txt | 201 + vendor/ogg/COPYING | 28 + vendor/ogg/Makefile.am | 44 + vendor/ogg/README.md | 160 + vendor/ogg/appveyor.yml | 33 + vendor/ogg/autogen.sh | 13 + vendor/ogg/cmake/CheckSizes.cmake | 73 + vendor/ogg/cmake/OggConfig.cmake.in | 16 + vendor/ogg/configure.ac | 209 + vendor/ogg/doc/Makefile.am | 9 + vendor/ogg/doc/fish_xiph_org.png | Bin 0 -> 2536 bytes vendor/ogg/doc/framing.html | 429 + vendor/ogg/doc/index.html | 105 + vendor/ogg/doc/libogg/Makefile.am | 39 + vendor/ogg/doc/libogg/bitpacking.html | 103 + vendor/ogg/doc/libogg/datastructures.html | 59 + vendor/ogg/doc/libogg/decoding.html | 104 + vendor/ogg/doc/libogg/encoding.html | 76 + vendor/ogg/doc/libogg/general.html | 109 + vendor/ogg/doc/libogg/index.html | 39 + vendor/ogg/doc/libogg/ogg_iovec_t.html | 62 + vendor/ogg/doc/libogg/ogg_packet.html | 75 + vendor/ogg/doc/libogg/ogg_packet_clear.html | 64 + vendor/ogg/doc/libogg/ogg_page.html | 75 + vendor/ogg/doc/libogg/ogg_page_bos.html | 65 + .../ogg/doc/libogg/ogg_page_checksum_set.html | 62 + vendor/ogg/doc/libogg/ogg_page_continued.html | 64 + vendor/ogg/doc/libogg/ogg_page_eos.html | 65 + .../ogg/doc/libogg/ogg_page_granulepos.html | 65 + vendor/ogg/doc/libogg/ogg_page_packets.html | 75 + vendor/ogg/doc/libogg/ogg_page_pageno.html | 63 + vendor/ogg/doc/libogg/ogg_page_serialno.html | 63 + vendor/ogg/doc/libogg/ogg_page_version.html | 63 + vendor/ogg/doc/libogg/ogg_stream_check.html | 71 + vendor/ogg/doc/libogg/ogg_stream_clear.html | 61 + vendor/ogg/doc/libogg/ogg_stream_destroy.html | 71 + vendor/ogg/doc/libogg/ogg_stream_eos.html | 62 + vendor/ogg/doc/libogg/ogg_stream_flush.html | 67 + .../ogg/doc/libogg/ogg_stream_flush_fill.html | 74 + vendor/ogg/doc/libogg/ogg_stream_init.html | 66 + vendor/ogg/doc/libogg/ogg_stream_iovecin.html | 80 + .../ogg/doc/libogg/ogg_stream_packetin.html | 72 + .../ogg/doc/libogg/ogg_stream_packetout.html | 85 + .../ogg/doc/libogg/ogg_stream_packetpeek.html | 85 + vendor/ogg/doc/libogg/ogg_stream_pagein.html | 67 + vendor/ogg/doc/libogg/ogg_stream_pageout.html | 84 + .../doc/libogg/ogg_stream_pageout_fill.html | 89 + vendor/ogg/doc/libogg/ogg_stream_reset.html | 61 + .../doc/libogg/ogg_stream_reset_serialno.html | 67 + vendor/ogg/doc/libogg/ogg_stream_state.html | 122 + vendor/ogg/doc/libogg/ogg_sync_buffer.html | 67 + vendor/ogg/doc/libogg/ogg_sync_check.html | 71 + vendor/ogg/doc/libogg/ogg_sync_clear.html | 62 + vendor/ogg/doc/libogg/ogg_sync_destroy.html | 68 + vendor/ogg/doc/libogg/ogg_sync_init.html | 63 + vendor/ogg/doc/libogg/ogg_sync_pageout.html | 77 + vendor/ogg/doc/libogg/ogg_sync_pageseek.html | 68 + vendor/ogg/doc/libogg/ogg_sync_reset.html | 63 + vendor/ogg/doc/libogg/ogg_sync_state.html | 77 + vendor/ogg/doc/libogg/ogg_sync_wrote.html | 73 + vendor/ogg/doc/libogg/oggpack_adv.html | 64 + vendor/ogg/doc/libogg/oggpack_adv1.html | 62 + vendor/ogg/doc/libogg/oggpack_bits.html | 62 + vendor/ogg/doc/libogg/oggpack_buffer.html | 66 + vendor/ogg/doc/libogg/oggpack_bytes.html | 67 + vendor/ogg/doc/libogg/oggpack_get_buffer.html | 62 + vendor/ogg/doc/libogg/oggpack_look.html | 66 + vendor/ogg/doc/libogg/oggpack_look1.html | 63 + vendor/ogg/doc/libogg/oggpack_read.html | 65 + vendor/ogg/doc/libogg/oggpack_read1.html | 63 + vendor/ogg/doc/libogg/oggpack_readinit.html | 64 + vendor/ogg/doc/libogg/oggpack_reset.html | 62 + vendor/ogg/doc/libogg/oggpack_write.html | 68 + vendor/ogg/doc/libogg/oggpack_writealign.html | 65 + vendor/ogg/doc/libogg/oggpack_writecheck.html | 81 + vendor/ogg/doc/libogg/oggpack_writeclear.html | 62 + vendor/ogg/doc/libogg/oggpack_writecopy.html | 69 + vendor/ogg/doc/libogg/oggpack_writeinit.html | 62 + vendor/ogg/doc/libogg/oggpack_writetrunc.html | 65 + vendor/ogg/doc/libogg/overview.html | 44 + vendor/ogg/doc/libogg/reference.html | 98 + vendor/ogg/doc/libogg/style.css | 7 + vendor/ogg/doc/multiplex1.png | Bin 0 -> 54409 bytes vendor/ogg/doc/multiplex1.svg | 632 + vendor/ogg/doc/ogg-multiplex.html | 446 + vendor/ogg/doc/oggstream.html | 594 + vendor/ogg/doc/packets.png | Bin 0 -> 19776 bytes vendor/ogg/doc/packets.svg | 876 + vendor/ogg/doc/pages.png | Bin 0 -> 43547 bytes vendor/ogg/doc/pages.svg | 1219 ++ vendor/ogg/doc/release.txt | 29 + vendor/ogg/doc/rfc3533.txt | 843 + vendor/ogg/doc/rfc3534.txt | 339 + vendor/ogg/doc/rfc5334.txt | 787 + vendor/ogg/doc/skeleton.html | 222 + vendor/ogg/doc/stream.png | Bin 0 -> 2254 bytes vendor/ogg/doc/vorbisword2.png | Bin 0 -> 1394 bytes vendor/ogg/doc/white-ogg.png | Bin 0 -> 2652 bytes vendor/ogg/doc/white-xifish.png | Bin 0 -> 965 bytes vendor/ogg/include/Makefile.am | 3 + vendor/ogg/include/ogg/Makefile.am | 6 + vendor/ogg/include/ogg/config_types.h.in | 26 + vendor/ogg/include/ogg/ogg.h | 209 + vendor/ogg/include/ogg/os_types.h | 158 + vendor/ogg/libogg.spec.in | 109 + vendor/ogg/ogg-uninstalled.pc.in | 14 + vendor/ogg/ogg.m4 | 116 + vendor/ogg/ogg.pc.in | 14 + vendor/ogg/releases.sha2 | 40 + vendor/ogg/src/Makefile.am | 28 + vendor/ogg/src/bitwise.c | 1087 ++ vendor/ogg/src/crctable.h | 278 + vendor/ogg/src/framing.c | 2114 +++ vendor/ogg/win32/.gitignore | 105 + vendor/ogg/win32/VS2015/libogg.sln | 38 + vendor/ogg/win32/VS2015/libogg.vcxproj | 296 + .../ogg/win32/VS2015/libogg.vcxproj.filters | 41 + vendor/ogg/win32/ogg.def | 80 + vendor/openal-soft/COPYING | 437 + vendor/openal-soft/dist/Win32/OpenAL32.dll | Bin 0 -> 1838080 bytes vendor/openal-soft/dist/Win64/OpenAL32.dll | Bin 0 -> 1771008 bytes vendor/openal-soft/include/AL/al.h | 655 + vendor/openal-soft/include/AL/alc.h | 270 + vendor/openal-soft/include/AL/alext.h | 585 + vendor/openal-soft/include/AL/efx-creative.h | 3 + vendor/openal-soft/include/AL/efx-presets.h | 402 + vendor/openal-soft/include/AL/efx.h | 762 + vendor/openal-soft/libs/Win32/OpenAL32.def | 96 + vendor/openal-soft/libs/Win32/OpenAL32.lib | Bin 0 -> 70242 bytes .../openal-soft/libs/Win32/libOpenAL32.dll.a | Bin 0 -> 60290 bytes vendor/openal-soft/libs/Win64/OpenAL32.def | 96 + vendor/openal-soft/libs/Win64/OpenAL32.lib | Bin 0 -> 70616 bytes .../openal-soft/libs/Win64/libOpenAL32.dll.a | Bin 0 -> 58954 bytes vendor/openal-soft/readme.txt | 32 + vendor/opus/.appveyor.yml | 37 + vendor/opus/.gitattributes | 10 + vendor/opus/.gitignore | 90 + vendor/opus/.gitlab-ci.yml | 42 + vendor/opus/.travis.yml | 21 + vendor/opus/AUTHORS | 6 + vendor/opus/CMakeLists.txt | 574 + vendor/opus/COPYING | 44 + vendor/opus/ChangeLog | 0 vendor/opus/LICENSE_PLEASE_READ.txt | 22 + vendor/opus/Makefile.am | 359 + vendor/opus/Makefile.mips | 161 + vendor/opus/Makefile.unix | 159 + vendor/opus/NEWS | 0 vendor/opus/README | 161 + vendor/opus/README.draft | 54 + vendor/opus/autogen.sh | 14 + vendor/opus/celt/_kiss_fft_guts.h | 182 + vendor/opus/celt/arch.h | 291 + vendor/opus/celt/bands.c | 1672 ++ vendor/opus/celt/bands.h | 123 + vendor/opus/celt/celt.c | 316 + vendor/opus/celt/celt.h | 251 + vendor/opus/celt/celt_decoder.c | 1378 ++ vendor/opus/celt/celt_encoder.c | 2607 +++ vendor/opus/celt/celt_lpc.c | 296 + vendor/opus/celt/celt_lpc.h | 66 + vendor/opus/celt/cpu_support.h | 70 + vendor/opus/celt/cwrs.c | 715 + vendor/opus/celt/cwrs.h | 48 + vendor/opus/celt/dump_modes/dump_modes.c | 353 + vendor/opus/celt/dump_modes/dump_modes_arch.h | 45 + .../celt/dump_modes/dump_modes_arm_ne10.c | 152 + vendor/opus/celt/ecintrin.h | 91 + vendor/opus/celt/entcode.c | 153 + vendor/opus/celt/entcode.h | 152 + vendor/opus/celt/entdec.c | 245 + vendor/opus/celt/entdec.h | 100 + vendor/opus/celt/entenc.c | 294 + vendor/opus/celt/entenc.h | 110 + vendor/opus/celt/fixed_c5x.h | 79 + vendor/opus/celt/fixed_c6x.h | 70 + vendor/opus/celt/fixed_debug.h | 791 + vendor/opus/celt/fixed_generic.h | 178 + vendor/opus/celt/float_cast.h | 152 + vendor/opus/celt/kiss_fft.c | 604 + vendor/opus/celt/kiss_fft.h | 200 + vendor/opus/celt/laplace.c | 134 + vendor/opus/celt/laplace.h | 48 + vendor/opus/celt/mathops.c | 209 + vendor/opus/celt/mathops.h | 290 + vendor/opus/celt/mdct.c | 343 + vendor/opus/celt/mdct.h | 112 + vendor/opus/celt/mfrngcod.h | 48 + vendor/opus/celt/mips/celt_mipsr1.h | 152 + vendor/opus/celt/mips/fixed_generic_mipsr1.h | 126 + vendor/opus/celt/mips/kiss_fft_mipsr1.h | 167 + vendor/opus/celt/mips/mdct_mipsr1.h | 288 + vendor/opus/celt/mips/pitch_mipsr1.h | 161 + vendor/opus/celt/mips/vq_mipsr1.h | 116 + vendor/opus/celt/modes.c | 442 + vendor/opus/celt/modes.h | 75 + vendor/opus/celt/opus_custom_demo.c | 210 + vendor/opus/celt/os_support.h | 91 + vendor/opus/celt/pitch.c | 537 + vendor/opus/celt/pitch.h | 192 + vendor/opus/celt/quant_bands.c | 563 + vendor/opus/celt/quant_bands.h | 66 + vendor/opus/celt/rate.c | 644 + vendor/opus/celt/rate.h | 101 + vendor/opus/celt/stack_alloc.h | 184 + vendor/opus/celt/static_modes_fixed.h | 892 + .../opus/celt/static_modes_fixed_arm_ne10.h | 388 + vendor/opus/celt/static_modes_float.h | 888 + .../opus/celt/static_modes_float_arm_ne10.h | 404 + vendor/opus/celt/tests/test_unit_cwrs32.c | 161 + vendor/opus/celt/tests/test_unit_dft.c | 179 + vendor/opus/celt/tests/test_unit_entropy.c | 383 + vendor/opus/celt/tests/test_unit_laplace.c | 93 + vendor/opus/celt/tests/test_unit_mathops.c | 266 + vendor/opus/celt/tests/test_unit_mdct.c | 227 + vendor/opus/celt/tests/test_unit_rotation.c | 86 + vendor/opus/celt/tests/test_unit_types.c | 50 + vendor/opus/celt/vq.c | 442 + vendor/opus/celt/vq.h | 79 + vendor/opus/celt_headers.mk | 53 + vendor/opus/celt_sources.mk | 50 + vendor/opus/cmake/CFeatureCheck.cmake | 39 + vendor/opus/cmake/OpusBuildtype.cmake | 27 + vendor/opus/cmake/OpusConfig.cmake | 104 + vendor/opus/cmake/OpusConfig.cmake.in | 20 + vendor/opus/cmake/OpusFunctions.cmake | 215 + vendor/opus/cmake/OpusPackageVersion.cmake | 70 + vendor/opus/cmake/OpusSources.cmake | 46 + vendor/opus/cmake/config.h.cmake.in | 1 + vendor/opus/cmake/vla.c | 7 + vendor/opus/configure.ac | 946 + vendor/opus/doc/Doxyfile.in | 337 + vendor/opus/doc/Makefile.am | 45 + vendor/opus/doc/build_draft.sh | 113 + vendor/opus/doc/build_isobmff.sh | 50 + vendor/opus/doc/build_oggdraft.sh | 52 + vendor/opus/doc/customdoxygen.css | 1011 + vendor/opus/doc/draft-ietf-codec-oggopus.xml | 1873 ++ .../opus/doc/draft-ietf-codec-opus-update.xml | 513 + vendor/opus/doc/draft-ietf-codec-opus.xml | 8276 +++++++++ .../opus/doc/draft-ietf-payload-rtp-opus.xml | 960 + vendor/opus/doc/footer.html | 32 + vendor/opus/doc/header.html | 61 + vendor/opus/doc/opus_in_isobmff.css | 60 + vendor/opus/doc/opus_in_isobmff.html | 692 + vendor/opus/doc/opus_logo.svg | 157 + vendor/opus/doc/opus_update.patch | 244 + vendor/opus/doc/release.txt | 43 + vendor/opus/doc/trivial_example.c | 171 + vendor/opus/include/opus.h | 981 + vendor/opus/include/opus_custom.h | 342 + vendor/opus/include/opus_defines.h | 799 + vendor/opus/include/opus_multistream.h | 660 + vendor/opus/include/opus_projection.h | 568 + vendor/opus/include/opus_types.h | 166 + vendor/opus/m4/as-gcc-inline-assembly.m4 | 98 + vendor/opus/m4/ax_add_fortify_source.m4 | 53 + vendor/opus/m4/opus-intrinsics.m4 | 29 + vendor/opus/opus-uninstalled.pc.in | 12 + vendor/opus/opus.m4 | 117 + vendor/opus/opus.pc.in | 16 + vendor/opus/opus_headers.mk | 9 + vendor/opus/opus_sources.mk | 16 + vendor/opus/releases.sha2 | 79 + vendor/opus/scripts/dump_rnn.py | 57 + vendor/opus/scripts/rnn_train.py | 67 + vendor/opus/silk/A2NLSF.c | 267 + vendor/opus/silk/API.h | 135 + vendor/opus/silk/CNG.c | 188 + vendor/opus/silk/HP_variable_cutoff.c | 77 + vendor/opus/silk/Inlines.h | 188 + vendor/opus/silk/LPC_analysis_filter.c | 111 + vendor/opus/silk/LPC_fit.c | 81 + vendor/opus/silk/LPC_inv_pred_gain.c | 141 + vendor/opus/silk/LP_variable_cutoff.c | 135 + vendor/opus/silk/MacroCount.h | 710 + vendor/opus/silk/MacroDebug.h | 951 + vendor/opus/silk/NLSF2A.c | 141 + vendor/opus/silk/NLSF_VQ.c | 76 + vendor/opus/silk/NLSF_VQ_weights_laroia.c | 80 + vendor/opus/silk/NLSF_decode.c | 93 + vendor/opus/silk/NLSF_del_dec_quant.c | 215 + vendor/opus/silk/NLSF_encode.c | 124 + vendor/opus/silk/NLSF_stabilize.c | 142 + vendor/opus/silk/NLSF_unpack.c | 55 + vendor/opus/silk/NSQ.c | 437 + vendor/opus/silk/NSQ.h | 101 + vendor/opus/silk/NSQ_del_dec.c | 733 + vendor/opus/silk/PLC.c | 446 + vendor/opus/silk/PLC.h | 62 + vendor/opus/silk/SigProc_FIX.h | 641 + vendor/opus/silk/VAD.c | 360 + vendor/opus/silk/VQ_WMat_EC.c | 131 + vendor/opus/silk/ana_filt_bank_1.c | 74 + vendor/opus/silk/biquad_alt.c | 121 + vendor/opus/silk/bwexpander.c | 51 + vendor/opus/silk/bwexpander_32.c | 50 + vendor/opus/silk/check_control_input.c | 106 + vendor/opus/silk/code_signs.c | 115 + vendor/opus/silk/control.h | 150 + vendor/opus/silk/control_SNR.c | 113 + vendor/opus/silk/control_audio_bandwidth.c | 132 + vendor/opus/silk/control_codec.c | 423 + vendor/opus/silk/debug.c | 173 + vendor/opus/silk/debug.h | 267 + vendor/opus/silk/dec_API.c | 419 + vendor/opus/silk/decode_core.c | 237 + vendor/opus/silk/decode_frame.c | 129 + vendor/opus/silk/decode_indices.c | 151 + vendor/opus/silk/decode_parameters.c | 115 + vendor/opus/silk/decode_pitch.c | 77 + vendor/opus/silk/decode_pulses.c | 115 + vendor/opus/silk/decoder_set_fs.c | 108 + vendor/opus/silk/define.h | 235 + vendor/opus/silk/enc_API.c | 576 + vendor/opus/silk/encode_indices.c | 181 + vendor/opus/silk/encode_pulses.c | 206 + vendor/opus/silk/errors.h | 98 + .../opus/silk/fixed/LTP_analysis_filter_FIX.c | 90 + vendor/opus/silk/fixed/LTP_scale_ctrl_FIX.c | 53 + .../opus/silk/fixed/apply_sine_window_FIX.c | 101 + vendor/opus/silk/fixed/autocorr_FIX.c | 48 + vendor/opus/silk/fixed/burg_modified_FIX.c | 280 + vendor/opus/silk/fixed/corrMatrix_FIX.c | 150 + vendor/opus/silk/fixed/encode_frame_FIX.c | 448 + vendor/opus/silk/fixed/find_LPC_FIX.c | 151 + vendor/opus/silk/fixed/find_LTP_FIX.c | 99 + vendor/opus/silk/fixed/find_pitch_lags_FIX.c | 143 + vendor/opus/silk/fixed/find_pred_coefs_FIX.c | 145 + vendor/opus/silk/fixed/k2a_FIX.c | 54 + vendor/opus/silk/fixed/k2a_Q16_FIX.c | 54 + vendor/opus/silk/fixed/main_FIX.h | 244 + .../mips/noise_shape_analysis_FIX_mipsr1.h | 336 + .../silk/fixed/mips/prefilter_FIX_mipsr1.h | 184 + .../mips/warped_autocorrelation_FIX_mipsr1.h | 165 + .../silk/fixed/noise_shape_analysis_FIX.c | 407 + .../opus/silk/fixed/pitch_analysis_core_FIX.c | 721 + vendor/opus/silk/fixed/process_gains_FIX.c | 117 + .../silk/fixed/regularize_correlations_FIX.c | 47 + .../opus/silk/fixed/residual_energy16_FIX.c | 103 + vendor/opus/silk/fixed/residual_energy_FIX.c | 98 + vendor/opus/silk/fixed/schur64_FIX.c | 93 + vendor/opus/silk/fixed/schur_FIX.c | 107 + vendor/opus/silk/fixed/structs_FIX.h | 116 + vendor/opus/silk/fixed/vector_ops_FIX.c | 102 + .../silk/fixed/warped_autocorrelation_FIX.c | 92 + .../opus/silk/float/LPC_analysis_filter_FLP.c | 249 + .../opus/silk/float/LPC_inv_pred_gain_FLP.c | 73 + .../opus/silk/float/LTP_analysis_filter_FLP.c | 75 + vendor/opus/silk/float/LTP_scale_ctrl_FLP.c | 52 + vendor/opus/silk/float/SigProc_FLP.h | 197 + .../opus/silk/float/apply_sine_window_FLP.c | 81 + vendor/opus/silk/float/autocorrelation_FLP.c | 52 + vendor/opus/silk/float/burg_modified_FLP.c | 186 + vendor/opus/silk/float/bwexpander_FLP.c | 49 + vendor/opus/silk/float/corrMatrix_FLP.c | 93 + vendor/opus/silk/float/encode_frame_FLP.c | 435 + vendor/opus/silk/float/energy_FLP.c | 59 + vendor/opus/silk/float/find_LPC_FLP.c | 104 + vendor/opus/silk/float/find_LTP_FLP.c | 64 + vendor/opus/silk/float/find_pitch_lags_FLP.c | 132 + vendor/opus/silk/float/find_pred_coefs_FLP.c | 116 + vendor/opus/silk/float/inner_product_FLP.c | 59 + vendor/opus/silk/float/k2a_FLP.c | 54 + vendor/opus/silk/float/main_FLP.h | 286 + .../silk/float/noise_shape_analysis_FLP.c | 350 + .../opus/silk/float/pitch_analysis_core_FLP.c | 630 + vendor/opus/silk/float/process_gains_FLP.c | 103 + .../silk/float/regularize_correlations_FLP.c | 48 + vendor/opus/silk/float/residual_energy_FLP.c | 117 + .../opus/silk/float/scale_copy_vector_FLP.c | 57 + vendor/opus/silk/float/scale_vector_FLP.c | 56 + vendor/opus/silk/float/schur_FLP.c | 70 + vendor/opus/silk/float/sort_FLP.c | 83 + vendor/opus/silk/float/structs_FLP.h | 112 + .../silk/float/warped_autocorrelation_FLP.c | 73 + vendor/opus/silk/float/wrappers_FLP.c | 207 + vendor/opus/silk/gain_quant.c | 142 + vendor/opus/silk/init_decoder.c | 57 + vendor/opus/silk/init_encoder.c | 64 + vendor/opus/silk/inner_prod_aligned.c | 47 + vendor/opus/silk/interpolate.c | 51 + vendor/opus/silk/lin2log.c | 46 + vendor/opus/silk/log2lin.c | 58 + vendor/opus/silk/macros.h | 151 + vendor/opus/silk/main.h | 476 + vendor/opus/silk/mips/NSQ_del_dec_mipsr1.h | 410 + vendor/opus/silk/mips/macros_mipsr1.h | 92 + vendor/opus/silk/mips/sigproc_fix_mipsr1.h | 60 + vendor/opus/silk/pitch_est_defines.h | 88 + vendor/opus/silk/pitch_est_tables.c | 99 + vendor/opus/silk/process_NLSFs.c | 107 + vendor/opus/silk/quant_LTP_gains.c | 132 + vendor/opus/silk/resampler.c | 215 + vendor/opus/silk/resampler_down2.c | 74 + vendor/opus/silk/resampler_down2_3.c | 103 + vendor/opus/silk/resampler_private.h | 88 + vendor/opus/silk/resampler_private_AR2.c | 55 + vendor/opus/silk/resampler_private_IIR_FIR.c | 107 + vendor/opus/silk/resampler_private_down_FIR.c | 194 + vendor/opus/silk/resampler_private_up2_HQ.c | 113 + vendor/opus/silk/resampler_rom.c | 96 + vendor/opus/silk/resampler_rom.h | 68 + vendor/opus/silk/resampler_structs.h | 60 + vendor/opus/silk/shell_coder.c | 151 + vendor/opus/silk/sigm_Q15.c | 76 + vendor/opus/silk/sort.c | 154 + vendor/opus/silk/stereo_LR_to_MS.c | 229 + vendor/opus/silk/stereo_MS_to_LR.c | 85 + vendor/opus/silk/stereo_decode_pred.c | 73 + vendor/opus/silk/stereo_encode_pred.c | 62 + vendor/opus/silk/stereo_find_predictor.c | 79 + vendor/opus/silk/stereo_quant_pred.c | 73 + vendor/opus/silk/structs.h | 329 + vendor/opus/silk/sum_sqr_shift.c | 83 + vendor/opus/silk/table_LSF_cos.c | 70 + vendor/opus/silk/tables.h | 114 + vendor/opus/silk/tables_LTP.c | 294 + vendor/opus/silk/tables_NLSF_CB_NB_MB.c | 195 + vendor/opus/silk/tables_NLSF_CB_WB.c | 234 + vendor/opus/silk/tables_gain.c | 63 + vendor/opus/silk/tables_other.c | 124 + vendor/opus/silk/tables_pitch_lag.c | 69 + vendor/opus/silk/tables_pulses_per_block.c | 264 + .../silk/tests/test_unit_LPC_inv_pred_gain.c | 129 + vendor/opus/silk/tuning_parameters.h | 155 + vendor/opus/silk/typedef.h | 81 + vendor/opus/silk_headers.mk | 44 + vendor/opus/silk_sources.mk | 154 + vendor/opus/src/analysis.c | 983 + vendor/opus/src/analysis.h | 103 + vendor/opus/src/mapping_matrix.c | 378 + vendor/opus/src/mapping_matrix.h | 133 + vendor/opus/src/mlp.c | 144 + vendor/opus/src/mlp.h | 60 + vendor/opus/src/mlp_data.c | 672 + vendor/opus/src/opus.c | 356 + vendor/opus/src/opus_compare.c | 382 + vendor/opus/src/opus_decoder.c | 1032 ++ vendor/opus/src/opus_demo.c | 892 + vendor/opus/src/opus_encoder.c | 2769 +++ vendor/opus/src/opus_multistream.c | 92 + vendor/opus/src/opus_multistream_decoder.c | 552 + vendor/opus/src/opus_multistream_encoder.c | 1328 ++ vendor/opus/src/opus_private.h | 201 + vendor/opus/src/opus_projection_decoder.c | 258 + vendor/opus/src/opus_projection_encoder.c | 468 + vendor/opus/src/repacketizer.c | 349 + vendor/opus/src/repacketizer_demo.c | 217 + vendor/opus/src/tansig_table.h | 45 + vendor/opus/tests/opus_decode_fuzzer.c | 124 + vendor/opus/tests/opus_decode_fuzzer.options | 2 + vendor/opus/tests/opus_encode_regressions.c | 1035 ++ vendor/opus/tests/run_vectors.sh | 143 + vendor/opus/tests/test_opus_api.c | 1904 ++ vendor/opus/tests/test_opus_common.h | 85 + vendor/opus/tests/test_opus_decode.c | 463 + vendor/opus/tests/test_opus_encode.c | 703 + vendor/opus/tests/test_opus_padding.c | 93 + vendor/opus/tests/test_opus_projection.c | 394 + vendor/opus/training/rnn_dump.py | 66 + vendor/opus/training/rnn_train.py | 177 + vendor/opus/training/txt2hdf5.py | 12 + vendor/opus/update_version | 65 + vendor/opus/win32/.gitignore | 26 + vendor/opus/win32/VS2015/common.props | 82 + vendor/opus/win32/VS2015/opus.sln | 168 + vendor/opus/win32/VS2015/opus.vcxproj | 399 + vendor/opus/win32/VS2015/opus.vcxproj.filters | 585 + vendor/opus/win32/VS2015/opus_demo.vcxproj | 171 + .../win32/VS2015/opus_demo.vcxproj.filters | 22 + .../opus/win32/VS2015/test_opus_api.vcxproj | 171 + .../VS2015/test_opus_api.vcxproj.filters | 14 + .../win32/VS2015/test_opus_decode.vcxproj | 171 + .../VS2015/test_opus_decode.vcxproj.filters | 14 + .../win32/VS2015/test_opus_encode.vcxproj | 172 + .../VS2015/test_opus_encode.vcxproj.filters | 17 + vendor/opus/win32/genversion.bat | 37 + vendor/opusfile/.appveyor.yml | 58 + vendor/opusfile/.gitattributes | 10 + vendor/opusfile/.gitignore | 49 + vendor/opusfile/.gitlab-ci.yml | 37 + vendor/opusfile/.travis.yml | 27 + vendor/opusfile/AUTHORS | 5 + vendor/opusfile/Brewfile | 7 + vendor/opusfile/COPYING | 28 + vendor/opusfile/Makefile.am | 143 + vendor/opusfile/README.md | 18 + vendor/opusfile/autogen.sh | 10 + vendor/opusfile/ci/autotools.sh | 65 + vendor/opusfile/ci/unix.sh | 23 + vendor/opusfile/configure.ac | 210 + vendor/opusfile/doc/Doxyfile.in | 22 + vendor/opusfile/doc/Makefile | 35 + vendor/opusfile/doc/opus_logo.svg | 157 + vendor/opusfile/doc/release.md | 58 + vendor/opusfile/examples/opusfile_example.c | 390 + vendor/opusfile/examples/seeking_example.c | 465 + vendor/opusfile/examples/win32utf8.c | 123 + vendor/opusfile/examples/win32utf8.h | 21 + vendor/opusfile/include/opusfile.h | 2164 +++ vendor/opusfile/m4/attributes.m4 | 321 + vendor/opusfile/mingw/Dockerfile | 21 + vendor/opusfile/mingw/Makefile | 113 + vendor/opusfile/mingw/README.md | 69 + vendor/opusfile/opusfile-uninstalled.pc.in | 14 + vendor/opusfile/opusfile.pc.in | 15 + vendor/opusfile/opusurl-uninstalled.pc.in | 14 + vendor/opusfile/opusurl.pc.in | 14 + vendor/opusfile/releases.sha2 | 23 + vendor/opusfile/src/http.c | 3592 ++++ vendor/opusfile/src/info.c | 775 + vendor/opusfile/src/internal.c | 42 + vendor/opusfile/src/internal.h | 259 + vendor/opusfile/src/opusfile.c | 3341 ++++ vendor/opusfile/src/stream.c | 415 + vendor/opusfile/src/wincerts.c | 173 + vendor/opusfile/src/winerrno.h | 90 + vendor/opusfile/unix/Makefile | 243 + vendor/opusfile/update_version | 65 + vendor/opusfile/win32/.gitignore | 25 + vendor/opusfile/win32/VS2015/opusfile.sln | 62 + vendor/opusfile/win32/VS2015/opusfile.vcxproj | 258 + .../win32/VS2015/opusfile.vcxproj.filters | 45 + .../win32/VS2015/opusfile_example.vcxproj | 263 + .../VS2015/opusfile_example.vcxproj.filters | 25 + .../win32/VS2015/seeking_example.vcxproj | 255 + .../VS2015/seeking_example.vcxproj.filters | 25 + 1420 files changed, 574487 insertions(+) create mode 100644 .clang-format create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/build-cmake-conan.yml create mode 100644 .github/workflows/build-switch.yml create mode 100644 .github/workflows/re3_msvc_amd64.yml create mode 100644 .github/workflows/re3_msvc_x86.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 CODING_STYLE.md create mode 100644 README create mode 100644 autoconf/LICENSE.txt create mode 100644 autoconf/api.lua create mode 100644 autoconf/autoconf.lua create mode 100644 autoconf/clang.lua create mode 100644 autoconf/gcc.lua create mode 100644 autoconf/msc.lua create mode 100644 cmake/FindMilesSDK.cmake create mode 100644 cmake/FindSndFile.cmake create mode 100644 cmake/Findmpg123.cmake create mode 100644 cmake/Findopusfile.cmake create mode 100644 cmake/GetGitRevisionDescription.cmake create mode 100644 cmake/GetGitRevisionDescription.cmake.in create mode 100644 cmake/nx/NXFunctions.cmake create mode 100644 codewarrior/re3.mcp.xml create mode 100644 conanfile.py create mode 100644 gamefiles/TEXT/JAPANESE.gxt create mode 100644 gamefiles/TEXT/american.gxt create mode 100644 gamefiles/TEXT/english.gxt create mode 100644 gamefiles/TEXT/french.gxt create mode 100644 gamefiles/TEXT/german.gxt create mode 100644 gamefiles/TEXT/italian.gxt create mode 100755 gamefiles/TEXT/polish.gxt create mode 100644 gamefiles/TEXT/russian.gxt create mode 100644 gamefiles/TEXT/spanish.gxt create mode 100644 gamefiles/data/PARTICLE.CFG create mode 100644 gamefiles/data/main_d.scm create mode 100644 gamefiles/data/main_freeroam.scm create mode 100644 gamefiles/gamecontrollerdb.txt create mode 100644 gamefiles/models/fonts_j.txd create mode 100644 gamefiles/models/fonts_p.txd create mode 100644 gamefiles/models/fonts_r.txd create mode 100644 gamefiles/models/frontend_ds3.txd create mode 100644 gamefiles/models/frontend_ds4.txd create mode 100644 gamefiles/models/frontend_nsw.txd create mode 100644 gamefiles/models/frontend_x360.txd create mode 100644 gamefiles/models/frontend_xone.txd create mode 100644 gamefiles/models/generic.txd create mode 100644 gamefiles/models/menu.txd create mode 100644 gamefiles/models/nswbtns.txd create mode 100644 gamefiles/models/particle.txd create mode 100644 gamefiles/models/ps3btns.txd create mode 100644 gamefiles/models/x360btns.txd create mode 100644 gamefiles/neo/carTweakingTable.dat create mode 100644 gamefiles/neo/neo.txd create mode 100644 gamefiles/neo/rimTweakingTable.dat create mode 100644 gamefiles/neo/worldTweakingTable.dat create mode 100644 premake-vs2015.cmd create mode 100644 premake-vs2017.cmd create mode 100644 premake-vs2019.cmd create mode 100644 premake5.exe create mode 100644 premake5.lua create mode 100755 premake5Linux create mode 100644 printHash.bat create mode 100755 printHash.sh create mode 100644 res/images/logo.svg create mode 100644 res/images/logo_1024.png create mode 100644 res/images/logo_256.jpg create mode 100644 src/CMakeLists.txt create mode 100644 src/animation/AnimBlendAssocGroup.cpp create mode 100644 src/animation/AnimBlendAssocGroup.h create mode 100644 src/animation/AnimBlendAssociation.cpp create mode 100644 src/animation/AnimBlendAssociation.h create mode 100644 src/animation/AnimBlendClumpData.cpp create mode 100644 src/animation/AnimBlendClumpData.h create mode 100644 src/animation/AnimBlendHierarchy.cpp create mode 100644 src/animation/AnimBlendHierarchy.h create mode 100644 src/animation/AnimBlendList.h create mode 100644 src/animation/AnimBlendNode.cpp create mode 100644 src/animation/AnimBlendNode.h create mode 100644 src/animation/AnimBlendSequence.cpp create mode 100644 src/animation/AnimBlendSequence.h create mode 100644 src/animation/AnimManager.cpp create mode 100644 src/animation/AnimManager.h create mode 100644 src/animation/AnimationId.h create mode 100644 src/animation/Bones.cpp create mode 100644 src/animation/Bones.h create mode 100644 src/animation/CutsceneMgr.cpp create mode 100644 src/animation/CutsceneMgr.h create mode 100644 src/animation/FrameUpdate.cpp create mode 100644 src/animation/RpAnimBlend.cpp create mode 100644 src/animation/RpAnimBlend.h create mode 100644 src/audio/AudioCollision.cpp create mode 100644 src/audio/AudioCollision.h create mode 100644 src/audio/AudioLogic.cpp create mode 100644 src/audio/AudioManager.cpp create mode 100644 src/audio/AudioManager.h create mode 100644 src/audio/AudioSamples.h create mode 100644 src/audio/AudioScriptObject.cpp create mode 100644 src/audio/AudioScriptObject.h create mode 100644 src/audio/DMAudio.cpp create mode 100644 src/audio/DMAudio.h create mode 100644 src/audio/MusicManager.cpp create mode 100644 src/audio/MusicManager.h create mode 100644 src/audio/PolRadio.cpp create mode 100644 src/audio/PolRadio.h create mode 100644 src/audio/audio_enums.h create mode 100644 src/audio/eax/eax-util.cpp create mode 100644 src/audio/eax/eax-util.h create mode 100644 src/audio/eax/eax.h create mode 100644 src/audio/oal/aldlist.cpp create mode 100644 src/audio/oal/aldlist.h create mode 100644 src/audio/oal/channel.cpp create mode 100644 src/audio/oal/channel.h create mode 100644 src/audio/oal/oal_utils.cpp create mode 100644 src/audio/oal/oal_utils.h create mode 100644 src/audio/oal/stream.cpp create mode 100644 src/audio/oal/stream.h create mode 100644 src/audio/sampman.h create mode 100644 src/audio/sampman_miles.cpp create mode 100644 src/audio/sampman_null.cpp create mode 100644 src/audio/sampman_oal.cpp create mode 100644 src/audio/soundlist.h create mode 100644 src/buildings/Building.cpp create mode 100644 src/buildings/Building.h create mode 100644 src/buildings/Solid.h create mode 100644 src/buildings/Treadable.cpp create mode 100644 src/buildings/Treadable.h create mode 100644 src/collision/ColBox.cpp create mode 100644 src/collision/ColBox.h create mode 100644 src/collision/ColLine.cpp create mode 100644 src/collision/ColLine.h create mode 100644 src/collision/ColModel.cpp create mode 100644 src/collision/ColModel.h create mode 100644 src/collision/ColPoint.cpp create mode 100644 src/collision/ColPoint.h create mode 100644 src/collision/ColSphere.cpp create mode 100644 src/collision/ColSphere.h create mode 100644 src/collision/ColTriangle.cpp create mode 100644 src/collision/ColTriangle.h create mode 100644 src/collision/Collision.cpp create mode 100644 src/collision/Collision.h create mode 100644 src/collision/CompressedVector.h create mode 100644 src/collision/TempColModels.cpp create mode 100644 src/collision/TempColModels.h create mode 100644 src/collision/VuCollision.cpp create mode 100644 src/collision/VuCollision.h create mode 100644 src/collision/vu0Collision.dsm create mode 100644 src/collision/vu0Collision_1.s create mode 100644 src/collision/vu0Collision_2.s create mode 100644 src/control/AutoPilot.cpp create mode 100644 src/control/AutoPilot.h create mode 100644 src/control/Bridge.cpp create mode 100644 src/control/Bridge.h create mode 100644 src/control/CarAI.cpp create mode 100644 src/control/CarAI.h create mode 100644 src/control/CarCtrl.cpp create mode 100644 src/control/CarCtrl.h create mode 100644 src/control/Curves.cpp create mode 100644 src/control/Curves.h create mode 100644 src/control/Darkel.cpp create mode 100644 src/control/Darkel.h create mode 100644 src/control/GameLogic.cpp create mode 100644 src/control/GameLogic.h create mode 100644 src/control/Garages.cpp create mode 100644 src/control/Garages.h create mode 100644 src/control/NameGrid.cpp create mode 100644 src/control/NameGrid.h create mode 100644 src/control/OnscreenTimer.cpp create mode 100644 src/control/OnscreenTimer.h create mode 100644 src/control/PathFind.cpp create mode 100644 src/control/PathFind.h create mode 100644 src/control/Phones.cpp create mode 100644 src/control/Phones.h create mode 100644 src/control/Pickups.cpp create mode 100644 src/control/Pickups.h create mode 100644 src/control/PowerPoints.cpp create mode 100644 src/control/PowerPoints.h create mode 100644 src/control/Record.cpp create mode 100644 src/control/Record.h create mode 100644 src/control/Remote.cpp create mode 100644 src/control/Remote.h create mode 100644 src/control/Replay.cpp create mode 100644 src/control/Replay.h create mode 100644 src/control/Restart.cpp create mode 100644 src/control/Restart.h create mode 100644 src/control/RoadBlocks.cpp create mode 100644 src/control/RoadBlocks.h create mode 100644 src/control/SceneEdit.cpp create mode 100644 src/control/SceneEdit.h create mode 100644 src/control/Script.cpp create mode 100644 src/control/Script.h create mode 100644 src/control/Script2.cpp create mode 100644 src/control/Script3.cpp create mode 100644 src/control/Script4.cpp create mode 100644 src/control/Script5.cpp create mode 100644 src/control/Script6.cpp create mode 100644 src/control/ScriptCommands.h create mode 100644 src/control/ScriptDebug.cpp create mode 100644 src/control/TrafficLights.cpp create mode 100644 src/control/TrafficLights.h create mode 100644 src/core/Accident.cpp create mode 100644 src/core/Accident.h create mode 100644 src/core/AnimViewer.cpp create mode 100644 src/core/AnimViewer.h create mode 100644 src/core/Cam.cpp create mode 100644 src/core/Camera.cpp create mode 100644 src/core/Camera.h create mode 100644 src/core/CdStream.cpp create mode 100644 src/core/CdStream.h create mode 100644 src/core/CdStreamPosix.cpp create mode 100644 src/core/Clock.cpp create mode 100644 src/core/Clock.h create mode 100644 src/core/ControllerConfig.cpp create mode 100644 src/core/ControllerConfig.h create mode 100644 src/core/Crime.h create mode 100644 src/core/Debug.cpp create mode 100644 src/core/Debug.h create mode 100644 src/core/Directory.cpp create mode 100644 src/core/Directory.h create mode 100644 src/core/EventList.cpp create mode 100644 src/core/EventList.h create mode 100644 src/core/FileLoader.cpp create mode 100644 src/core/FileLoader.h create mode 100644 src/core/FileMgr.cpp create mode 100644 src/core/FileMgr.h create mode 100644 src/core/Fire.cpp create mode 100644 src/core/Fire.h create mode 100644 src/core/FrontEndControls.cpp create mode 100644 src/core/FrontEndControls.h create mode 100644 src/core/Frontend.cpp create mode 100644 src/core/Frontend.h create mode 100644 src/core/FrontendTriggers.h create mode 100644 src/core/Frontend_PS2.cpp create mode 100644 src/core/Frontend_PS2.h create mode 100644 src/core/Game.cpp create mode 100644 src/core/Game.h create mode 100644 src/core/General.h create mode 100644 src/core/IniFile.cpp create mode 100644 src/core/IniFile.h create mode 100644 src/core/Lists.cpp create mode 100644 src/core/Lists.h create mode 100644 src/core/MenuScreens.cpp create mode 100644 src/core/MenuScreensCustom.cpp create mode 100644 src/core/Pad.cpp create mode 100644 src/core/Pad.h create mode 100644 src/core/Placeable.cpp create mode 100644 src/core/Placeable.h create mode 100644 src/core/PlayerInfo.cpp create mode 100644 src/core/PlayerInfo.h create mode 100644 src/core/Pools.cpp create mode 100644 src/core/Pools.h create mode 100644 src/core/Profile.cpp create mode 100644 src/core/Profile.h create mode 100644 src/core/Radar.cpp create mode 100644 src/core/Radar.h create mode 100644 src/core/Range2D.cpp create mode 100644 src/core/Range2D.h create mode 100644 src/core/Range3D.cpp create mode 100644 src/core/Range3D.h create mode 100644 src/core/References.cpp create mode 100644 src/core/References.h create mode 100644 src/core/Stats.cpp create mode 100644 src/core/Stats.h create mode 100644 src/core/Streaming.cpp create mode 100644 src/core/Streaming.h create mode 100644 src/core/SurfaceTable.cpp create mode 100644 src/core/SurfaceTable.h create mode 100644 src/core/TimeStep.cpp create mode 100644 src/core/TimeStep.h create mode 100644 src/core/Timer.cpp create mode 100644 src/core/Timer.h create mode 100644 src/core/User.cpp create mode 100644 src/core/User.h create mode 100644 src/core/Wanted.cpp create mode 100644 src/core/Wanted.h create mode 100644 src/core/World.cpp create mode 100644 src/core/World.h create mode 100644 src/core/ZoneCull.cpp create mode 100644 src/core/ZoneCull.h create mode 100644 src/core/Zones.cpp create mode 100644 src/core/Zones.h create mode 100644 src/core/common.h create mode 100644 src/core/config.h create mode 100644 src/core/main.cpp create mode 100644 src/core/main.h create mode 100644 src/core/obrstr.cpp create mode 100644 src/core/obrstr.h create mode 100644 src/core/re3.cpp create mode 100644 src/core/templates.h create mode 100644 src/core/timebars.cpp create mode 100644 src/core/timebars.h create mode 100644 src/entities/Dummy.cpp create mode 100644 src/entities/Dummy.h create mode 100644 src/entities/Entity.cpp create mode 100644 src/entities/Entity.h create mode 100644 src/entities/Physical.cpp create mode 100644 src/entities/Physical.h create mode 100644 src/extras/GitSHA1.cpp.in create mode 100644 src/extras/GitSHA1.h create mode 100644 src/extras/arrow.inc create mode 100644 src/extras/cursor.inc create mode 100644 src/extras/custompipes.cpp create mode 100644 src/extras/custompipes.h create mode 100644 src/extras/custompipes_d3d9.cpp create mode 100644 src/extras/custompipes_gl.cpp create mode 100644 src/extras/debugmenu.cpp create mode 100644 src/extras/debugmenu.h create mode 100644 src/extras/frontendoption.cpp create mode 100644 src/extras/frontendoption.h create mode 100644 src/extras/ini.h create mode 100644 src/extras/postfx.cpp create mode 100644 src/extras/postfx.h create mode 100644 src/extras/re3_inttypes.h create mode 100644 src/extras/screendroplets.cpp create mode 100644 src/extras/screendroplets.h create mode 100644 src/extras/shaders/colourfilterIII.frag create mode 100644 src/extras/shaders/colourfilterIII_PS.hlsl create mode 100644 src/extras/shaders/contrast.frag create mode 100644 src/extras/shaders/contrastPS.hlsl create mode 100644 src/extras/shaders/default_UV2.vert create mode 100644 src/extras/shaders/default_UV2_VS.hlsl create mode 100644 src/extras/shaders/im2d.vert create mode 100644 src/extras/shaders/im2d_UV2.vert create mode 100644 src/extras/shaders/lighting.h create mode 100644 src/extras/shaders/make_glsl.sh create mode 100644 src/extras/shaders/make_hlsl.cmd create mode 100644 src/extras/shaders/makeinc_glsl.sh create mode 100644 src/extras/shaders/makeinc_hlsl.sh create mode 100644 src/extras/shaders/neoGloss.frag create mode 100644 src/extras/shaders/neoGloss.vert create mode 100644 src/extras/shaders/neoGloss_PS.hlsl create mode 100644 src/extras/shaders/neoGloss_VS.hlsl create mode 100644 src/extras/shaders/neoRim.vert create mode 100644 src/extras/shaders/neoRimSkin.vert create mode 100644 src/extras/shaders/neoRimSkin_VS.hlsl create mode 100644 src/extras/shaders/neoRim_VS.hlsl create mode 100644 src/extras/shaders/neoVehicle.frag create mode 100644 src/extras/shaders/neoVehicle.vert create mode 100644 src/extras/shaders/neoVehicle_PS.hlsl create mode 100644 src/extras/shaders/neoVehicle_VS.hlsl create mode 100644 src/extras/shaders/neoWorldIII.frag create mode 100644 src/extras/shaders/neoWorldIII_PS.hlsl create mode 100644 src/extras/shaders/screenDroplet.frag create mode 100644 src/extras/shaders/screenDroplet_PS.hlsl create mode 100644 src/extras/shaders/simple.frag create mode 100644 src/extras/shaders/standardConstants.h create mode 100644 src/fakerw/fake.cpp create mode 100644 src/fakerw/rpanisot.h create mode 100644 src/fakerw/rphanim.h create mode 100644 src/fakerw/rpmatfx.h create mode 100644 src/fakerw/rpskin.h create mode 100644 src/fakerw/rpworld.h create mode 100644 src/fakerw/rtbmp.h create mode 100644 src/fakerw/rtcharse.h create mode 100644 src/fakerw/rtpng.h create mode 100644 src/fakerw/rtquat.h create mode 100644 src/fakerw/rwcore.h create mode 100644 src/fakerw/rwplcore.h create mode 100644 src/math/Matrix.cpp create mode 100644 src/math/Matrix.h create mode 100644 src/math/Quaternion.cpp create mode 100644 src/math/Quaternion.h create mode 100644 src/math/Rect.cpp create mode 100644 src/math/Rect.h create mode 100644 src/math/Vector.cpp create mode 100644 src/math/Vector.h create mode 100644 src/math/Vector2D.h create mode 100644 src/math/VuVector.h create mode 100644 src/math/math.cpp create mode 100644 src/math/maths.h create mode 100644 src/modelinfo/BaseModelInfo.cpp create mode 100644 src/modelinfo/BaseModelInfo.h create mode 100644 src/modelinfo/ClumpModelInfo.cpp create mode 100644 src/modelinfo/ClumpModelInfo.h create mode 100644 src/modelinfo/MloModelInfo.cpp create mode 100644 src/modelinfo/MloModelInfo.h create mode 100644 src/modelinfo/ModelIndices.cpp create mode 100644 src/modelinfo/ModelIndices.h create mode 100644 src/modelinfo/ModelInfo.cpp create mode 100644 src/modelinfo/ModelInfo.h create mode 100644 src/modelinfo/PedModelInfo.cpp create mode 100644 src/modelinfo/PedModelInfo.h create mode 100644 src/modelinfo/SimpleModelInfo.cpp create mode 100644 src/modelinfo/SimpleModelInfo.h create mode 100644 src/modelinfo/TimeModelInfo.cpp create mode 100644 src/modelinfo/TimeModelInfo.h create mode 100644 src/modelinfo/VehicleModelInfo.cpp create mode 100644 src/modelinfo/VehicleModelInfo.h create mode 100644 src/modelinfo/XtraCompsModelInfo.h create mode 100644 src/objects/CutsceneHead.cpp create mode 100644 src/objects/CutsceneHead.h create mode 100644 src/objects/CutsceneObject.cpp create mode 100644 src/objects/CutsceneObject.h create mode 100644 src/objects/DummyObject.cpp create mode 100644 src/objects/DummyObject.h create mode 100644 src/objects/Object.cpp create mode 100644 src/objects/Object.h create mode 100644 src/objects/ObjectData.cpp create mode 100644 src/objects/ObjectData.h create mode 100644 src/objects/ParticleObject.cpp create mode 100644 src/objects/ParticleObject.h create mode 100644 src/objects/Projectile.cpp create mode 100644 src/objects/Projectile.h create mode 100644 src/peds/CivilianPed.cpp create mode 100644 src/peds/CivilianPed.h create mode 100644 src/peds/CopPed.cpp create mode 100644 src/peds/CopPed.h create mode 100644 src/peds/DummyPed.h create mode 100644 src/peds/EmergencyPed.cpp create mode 100644 src/peds/EmergencyPed.h create mode 100644 src/peds/Gangs.cpp create mode 100644 src/peds/Gangs.h create mode 100644 src/peds/Ped.cpp create mode 100644 src/peds/Ped.h create mode 100644 src/peds/PedAI.cpp create mode 100644 src/peds/PedChat.cpp create mode 100644 src/peds/PedDebug.cpp create mode 100644 src/peds/PedFight.cpp create mode 100644 src/peds/PedIK.cpp create mode 100644 src/peds/PedIK.h create mode 100644 src/peds/PedPlacement.cpp create mode 100644 src/peds/PedPlacement.h create mode 100644 src/peds/PedRoutes.cpp create mode 100644 src/peds/PedRoutes.h create mode 100644 src/peds/PedType.cpp create mode 100644 src/peds/PedType.h create mode 100644 src/peds/PlayerPed.cpp create mode 100644 src/peds/PlayerPed.h create mode 100644 src/peds/Population.cpp create mode 100644 src/peds/Population.h create mode 100644 src/renderer/2dEffect.h create mode 100644 src/renderer/Antennas.cpp create mode 100644 src/renderer/Antennas.h create mode 100644 src/renderer/Clouds.cpp create mode 100644 src/renderer/Clouds.h create mode 100644 src/renderer/Console.cpp create mode 100644 src/renderer/Console.h create mode 100644 src/renderer/Coronas.cpp create mode 100644 src/renderer/Coronas.h create mode 100644 src/renderer/Credits.cpp create mode 100644 src/renderer/Credits.h create mode 100644 src/renderer/Draw.cpp create mode 100644 src/renderer/Draw.h create mode 100644 src/renderer/Fluff.cpp create mode 100644 src/renderer/Fluff.h create mode 100644 src/renderer/Font.cpp create mode 100644 src/renderer/Font.h create mode 100644 src/renderer/Glass.cpp create mode 100644 src/renderer/Glass.h create mode 100644 src/renderer/Hud.cpp create mode 100644 src/renderer/Hud.h create mode 100644 src/renderer/Instance.cpp create mode 100644 src/renderer/Instance.h create mode 100644 src/renderer/Lines.cpp create mode 100644 src/renderer/Lines.h create mode 100644 src/renderer/MBlur.cpp create mode 100644 src/renderer/MBlur.h create mode 100644 src/renderer/Particle.cpp create mode 100644 src/renderer/Particle.h create mode 100644 src/renderer/ParticleMgr.cpp create mode 100644 src/renderer/ParticleMgr.h create mode 100644 src/renderer/ParticleType.h create mode 100644 src/renderer/PlayerSkin.cpp create mode 100644 src/renderer/PlayerSkin.h create mode 100644 src/renderer/PointLights.cpp create mode 100644 src/renderer/PointLights.h create mode 100644 src/renderer/RenderBuffer.cpp create mode 100644 src/renderer/RenderBuffer.h create mode 100644 src/renderer/Renderer.cpp create mode 100644 src/renderer/Renderer.h create mode 100644 src/renderer/Rubbish.cpp create mode 100644 src/renderer/Rubbish.h create mode 100644 src/renderer/Shadows.cpp create mode 100644 src/renderer/Shadows.h create mode 100644 src/renderer/Skidmarks.cpp create mode 100644 src/renderer/Skidmarks.h create mode 100644 src/renderer/SpecialFX.cpp create mode 100644 src/renderer/SpecialFX.h create mode 100644 src/renderer/Sprite.cpp create mode 100644 src/renderer/Sprite.h create mode 100644 src/renderer/Sprite2d.cpp create mode 100644 src/renderer/Sprite2d.h create mode 100644 src/renderer/TexList.cpp create mode 100644 src/renderer/TexList.h create mode 100644 src/renderer/Timecycle.cpp create mode 100644 src/renderer/Timecycle.h create mode 100644 src/renderer/WaterCannon.cpp create mode 100644 src/renderer/WaterCannon.h create mode 100644 src/renderer/WaterLevel.cpp create mode 100644 src/renderer/WaterLevel.h create mode 100644 src/renderer/Weather.cpp create mode 100644 src/renderer/Weather.h create mode 100644 src/rw/ClumpRead.cpp create mode 100644 src/rw/Lights.cpp create mode 100644 src/rw/Lights.h create mode 100644 src/rw/MemoryHeap.cpp create mode 100644 src/rw/MemoryHeap.h create mode 100644 src/rw/MemoryMgr.cpp create mode 100644 src/rw/MemoryMgr.h create mode 100644 src/rw/NodeName.cpp create mode 100644 src/rw/NodeName.h create mode 100644 src/rw/RwHelper.cpp create mode 100644 src/rw/RwHelper.h create mode 100644 src/rw/RwMatFX.cpp create mode 100644 src/rw/RwPS2AlphaTest.cpp create mode 100644 src/rw/TexRead.cpp create mode 100644 src/rw/TexturePools.cpp create mode 100644 src/rw/TexturePools.h create mode 100644 src/rw/TxdStore.cpp create mode 100644 src/rw/TxdStore.h create mode 100644 src/rw/VisibilityPlugins.cpp create mode 100644 src/rw/VisibilityPlugins.h create mode 100644 src/save/Date.cpp create mode 100644 src/save/Date.h create mode 100644 src/save/GenericGameStorage.cpp create mode 100644 src/save/GenericGameStorage.h create mode 100644 src/save/MemoryCard.cpp create mode 100644 src/save/MemoryCard.h create mode 100644 src/save/PCSave.cpp create mode 100644 src/save/PCSave.h create mode 100644 src/save/SaveBuf.h create mode 100644 src/skel/crossplatform.cpp create mode 100644 src/skel/crossplatform.h create mode 100644 src/skel/events.cpp create mode 100644 src/skel/events.h create mode 100644 src/skel/glfw/glfw.cpp create mode 100644 src/skel/platform.h create mode 100644 src/skel/skeleton.cpp create mode 100644 src/skel/skeleton.h create mode 100644 src/skel/win/gta3.ico create mode 100644 src/skel/win/resource.h create mode 100644 src/skel/win/win.cpp create mode 100644 src/skel/win/win.h create mode 100644 src/skel/win/win.rc create mode 100644 src/text/Messages.cpp create mode 100644 src/text/Messages.h create mode 100644 src/text/Pager.cpp create mode 100644 src/text/Pager.h create mode 100644 src/text/Text.cpp create mode 100644 src/text/Text.h create mode 100644 src/vehicles/Automobile.cpp create mode 100644 src/vehicles/Automobile.h create mode 100644 src/vehicles/Bike.h create mode 100644 src/vehicles/Boat.cpp create mode 100644 src/vehicles/Boat.h create mode 100644 src/vehicles/CarGen.cpp create mode 100644 src/vehicles/CarGen.h create mode 100644 src/vehicles/Cranes.cpp create mode 100644 src/vehicles/Cranes.h create mode 100644 src/vehicles/DamageManager.cpp create mode 100644 src/vehicles/DamageManager.h create mode 100644 src/vehicles/Door.cpp create mode 100644 src/vehicles/Door.h create mode 100644 src/vehicles/Floater.cpp create mode 100644 src/vehicles/Floater.h create mode 100644 src/vehicles/HandlingMgr.cpp create mode 100644 src/vehicles/HandlingMgr.h create mode 100644 src/vehicles/Heli.cpp create mode 100644 src/vehicles/Heli.h create mode 100644 src/vehicles/Plane.cpp create mode 100644 src/vehicles/Plane.h create mode 100644 src/vehicles/Train.cpp create mode 100644 src/vehicles/Train.h create mode 100644 src/vehicles/Transmission.cpp create mode 100644 src/vehicles/Transmission.h create mode 100644 src/vehicles/Vehicle.cpp create mode 100644 src/vehicles/Vehicle.h create mode 100644 src/weapons/BulletInfo.cpp create mode 100644 src/weapons/BulletInfo.h create mode 100644 src/weapons/Explosion.cpp create mode 100644 src/weapons/Explosion.h create mode 100644 src/weapons/ProjectileInfo.cpp create mode 100644 src/weapons/ProjectileInfo.h create mode 100644 src/weapons/ShotInfo.cpp create mode 100644 src/weapons/ShotInfo.h create mode 100644 src/weapons/Weapon.cpp create mode 100644 src/weapons/Weapon.h create mode 100644 src/weapons/WeaponEffects.cpp create mode 100644 src/weapons/WeaponEffects.h create mode 100644 src/weapons/WeaponInfo.cpp create mode 100644 src/weapons/WeaponInfo.h create mode 100644 src/weapons/WeaponType.h create mode 100644 utils/gxt/american.txt create mode 100644 utils/gxt/build.bat create mode 100644 utils/gxt/english.txt create mode 100644 utils/gxt/french.txt create mode 100644 utils/gxt/german.txt create mode 100644 utils/gxt/gxt.exe create mode 100644 utils/gxt/italian.txt create mode 100755 utils/gxt/polish.txt create mode 100644 utils/gxt/russian.txt create mode 100644 utils/gxt/spanish.txt create mode 100644 vendor/librw/.appveyor.yml create mode 100644 vendor/librw/.github/workflows/build-cmake-conan.yml create mode 100644 vendor/librw/.github/workflows/build-ps2.yml create mode 100644 vendor/librw/.github/workflows/build-switch.yml create mode 100644 vendor/librw/.gitignore create mode 100644 vendor/librw/.travis.yml create mode 100644 vendor/librw/ARCHITECTURE.MD create mode 100644 vendor/librw/CMakeLists.txt create mode 100644 vendor/librw/Dockerfile create mode 100644 vendor/librw/LICENSE create mode 100644 vendor/librw/README.cmake create mode 100644 vendor/librw/README.md create mode 100644 vendor/librw/TODO create mode 100644 vendor/librw/args.h create mode 100644 vendor/librw/cmake/FindSDL2.cmake create mode 100644 vendor/librw/cmake/librw-config.cmake.in create mode 100644 vendor/librw/cmake/nx/NXFunctions.cmake create mode 100644 vendor/librw/cmake/ps2/PS2Functions.cmake create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py create mode 100644 vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake create mode 100644 vendor/librw/conan/playstation2 create mode 100644 vendor/librw/conanfile.py create mode 100755 vendor/librw/docker_rebuild_ps2.sh create mode 100644 vendor/librw/premake-vs2019.cmd create mode 100644 vendor/librw/premake5.exe create mode 100755 vendor/librw/premake5.lua create mode 100644 vendor/librw/rw.h create mode 100644 vendor/librw/skeleton/CMakeLists.txt create mode 100644 vendor/librw/skeleton/glfw.cpp create mode 100644 vendor/librw/skeleton/imgui/ImGuizmo.cpp create mode 100644 vendor/librw/skeleton/imgui/ImGuizmo.h create mode 100644 vendor/librw/skeleton/imgui/LICENSE_imgui.txt create mode 100644 vendor/librw/skeleton/imgui/LICENSE_imguizmo.txt create mode 100644 vendor/librw/skeleton/imgui/imconfig.h create mode 100644 vendor/librw/skeleton/imgui/imgui.cpp create mode 100644 vendor/librw/skeleton/imgui/imgui.h create mode 100644 vendor/librw/skeleton/imgui/imgui_demo.cpp create mode 100644 vendor/librw/skeleton/imgui/imgui_draw.cpp create mode 100644 vendor/librw/skeleton/imgui/imgui_impl_rw.cpp create mode 100644 vendor/librw/skeleton/imgui/imgui_impl_rw.h create mode 100644 vendor/librw/skeleton/imgui/imgui_internal.h create mode 100644 vendor/librw/skeleton/imgui/imgui_tables.cpp create mode 100644 vendor/librw/skeleton/imgui/imgui_widgets.cpp create mode 100644 vendor/librw/skeleton/imgui/imstb_rectpack.h create mode 100644 vendor/librw/skeleton/imgui/imstb_textedit.h create mode 100644 vendor/librw/skeleton/imgui/imstb_truetype.h create mode 100644 vendor/librw/skeleton/sdl2.cpp create mode 100644 vendor/librw/skeleton/skeleton.cpp create mode 100644 vendor/librw/skeleton/skeleton.h create mode 100644 vendor/librw/skeleton/win.cpp create mode 100644 vendor/librw/src/CMakeLists.txt create mode 100644 vendor/librw/src/anim.cpp create mode 100644 vendor/librw/src/base.cpp create mode 100644 vendor/librw/src/base.err create mode 100644 vendor/librw/src/bmp.cpp create mode 100644 vendor/librw/src/camera.cpp create mode 100644 vendor/librw/src/charset.cpp create mode 100644 vendor/librw/src/clump.cpp create mode 100644 vendor/librw/src/d3d/d3d.cpp create mode 100644 vendor/librw/src/d3d/d3d8.cpp create mode 100644 vendor/librw/src/d3d/d3d8matfx.cpp create mode 100644 vendor/librw/src/d3d/d3d8render.cpp create mode 100644 vendor/librw/src/d3d/d3d8skin.cpp create mode 100644 vendor/librw/src/d3d/d3d9.cpp create mode 100644 vendor/librw/src/d3d/d3d9matfx.cpp create mode 100644 vendor/librw/src/d3d/d3d9render.cpp create mode 100644 vendor/librw/src/d3d/d3d9skin.cpp create mode 100644 vendor/librw/src/d3d/d3ddevice.cpp create mode 100644 vendor/librw/src/d3d/d3dimmed.cpp create mode 100644 vendor/librw/src/d3d/d3drender.cpp create mode 100644 vendor/librw/src/d3d/rwd3d.h create mode 100644 vendor/librw/src/d3d/rwd3d8.h create mode 100644 vendor/librw/src/d3d/rwd3d9.h create mode 100644 vendor/librw/src/d3d/rwd3dimpl.h create mode 100644 vendor/librw/src/d3d/rwxbox.h create mode 100644 vendor/librw/src/d3d/rwxboximpl.h create mode 100644 vendor/librw/src/d3d/shaders/default_PS.h create mode 100644 vendor/librw/src/d3d/shaders/default_PS.hlsl create mode 100644 vendor/librw/src/d3d/shaders/default_VS.hlsl create mode 100644 vendor/librw/src/d3d/shaders/default_all_VS.h create mode 100644 vendor/librw/src/d3d/shaders/default_amb_VS.h create mode 100644 vendor/librw/src/d3d/shaders/default_amb_dir_VS.h create mode 100644 vendor/librw/src/d3d/shaders/default_tex_PS.h create mode 100644 vendor/librw/src/d3d/shaders/im2d_PS.h create mode 100644 vendor/librw/src/d3d/shaders/im2d_PS.hlsl create mode 100644 vendor/librw/src/d3d/shaders/im2d_VS.h create mode 100644 vendor/librw/src/d3d/shaders/im2d_VS.hlsl create mode 100644 vendor/librw/src/d3d/shaders/im2d_tex_PS.h create mode 100644 vendor/librw/src/d3d/shaders/lighting.h create mode 100644 vendor/librw/src/d3d/shaders/make_default.cmd create mode 100644 vendor/librw/src/d3d/shaders/make_matfx.cmd create mode 100644 vendor/librw/src/d3d/shaders/make_skin.cmd create mode 100644 vendor/librw/src/d3d/shaders/matfx_env_PS.h create mode 100644 vendor/librw/src/d3d/shaders/matfx_env_PS.hlsl create mode 100644 vendor/librw/src/d3d/shaders/matfx_env_VS.hlsl create mode 100644 vendor/librw/src/d3d/shaders/matfx_env_all_VS.h create mode 100644 vendor/librw/src/d3d/shaders/matfx_env_amb_VS.h create mode 100644 vendor/librw/src/d3d/shaders/matfx_env_amb_dir_VS.h create mode 100644 vendor/librw/src/d3d/shaders/matfx_env_tex_PS.h create mode 100644 vendor/librw/src/d3d/shaders/skin_VS.hlsl create mode 100644 vendor/librw/src/d3d/shaders/skin_all_VS.h create mode 100644 vendor/librw/src/d3d/shaders/skin_amb_VS.h create mode 100644 vendor/librw/src/d3d/shaders/skin_amb_dir_VS.h create mode 100644 vendor/librw/src/d3d/shaders/standardConstants.h create mode 100644 vendor/librw/src/d3d/xbox.cpp create mode 100644 vendor/librw/src/d3d/xboxmatfx.cpp create mode 100644 vendor/librw/src/d3d/xboxskin.cpp create mode 100644 vendor/librw/src/d3d/xboxvfmt.cpp create mode 100644 vendor/librw/src/engine.cpp create mode 100644 vendor/librw/src/error.cpp create mode 100644 vendor/librw/src/frame.cpp create mode 100644 vendor/librw/src/geometry.cpp create mode 100644 vendor/librw/src/geoplg.cpp create mode 100644 vendor/librw/src/gl/gl3.cpp create mode 100644 vendor/librw/src/gl/gl3device.cpp create mode 100644 vendor/librw/src/gl/gl3immed.cpp create mode 100644 vendor/librw/src/gl/gl3matfx.cpp create mode 100644 vendor/librw/src/gl/gl3pipe.cpp create mode 100644 vendor/librw/src/gl/gl3raster.cpp create mode 100644 vendor/librw/src/gl/gl3render.cpp create mode 100644 vendor/librw/src/gl/gl3shader.cpp create mode 100644 vendor/librw/src/gl/gl3skin.cpp create mode 100644 vendor/librw/src/gl/glad/glad.c create mode 100644 vendor/librw/src/gl/glad/glad.h create mode 100644 vendor/librw/src/gl/glad/khrplatform.h create mode 100644 vendor/librw/src/gl/rwgl3.h create mode 100644 vendor/librw/src/gl/rwgl3impl.h create mode 100644 vendor/librw/src/gl/rwgl3plg.h create mode 100644 vendor/librw/src/gl/rwgl3shader.h create mode 100644 vendor/librw/src/gl/rwwdgl.h create mode 100644 vendor/librw/src/gl/shaders/Makefile create mode 100644 vendor/librw/src/gl/shaders/default.vert create mode 100644 vendor/librw/src/gl/shaders/default_vs_gl.inc create mode 100644 vendor/librw/src/gl/shaders/header.frag create mode 100644 vendor/librw/src/gl/shaders/header.vert create mode 100644 vendor/librw/src/gl/shaders/header_fs.inc create mode 100644 vendor/librw/src/gl/shaders/header_vs.inc create mode 100644 vendor/librw/src/gl/shaders/im2d.vert create mode 100644 vendor/librw/src/gl/shaders/im2d_gl.inc create mode 100644 vendor/librw/src/gl/shaders/im3d.vert create mode 100644 vendor/librw/src/gl/shaders/im3d_gl.inc create mode 100644 vendor/librw/src/gl/shaders/matfx_env.frag create mode 100644 vendor/librw/src/gl/shaders/matfx_env.vert create mode 100644 vendor/librw/src/gl/shaders/matfx_gl.inc create mode 100644 vendor/librw/src/gl/shaders/simple.frag create mode 100644 vendor/librw/src/gl/shaders/simple_fs_gl.inc create mode 100644 vendor/librw/src/gl/shaders/skin.vert create mode 100644 vendor/librw/src/gl/shaders/skin_gl.inc create mode 100644 vendor/librw/src/gl/wdgl.cpp create mode 100644 vendor/librw/src/hanim.cpp create mode 100644 vendor/librw/src/image.cpp create mode 100644 vendor/librw/src/light.cpp create mode 100644 vendor/librw/src/lodepng/lodepng.cpp create mode 100644 vendor/librw/src/lodepng/lodepng.h create mode 100644 vendor/librw/src/matfx.cpp create mode 100644 vendor/librw/src/pipeline.cpp create mode 100644 vendor/librw/src/plg.cpp create mode 100644 vendor/librw/src/png.cpp create mode 100644 vendor/librw/src/prim.cpp create mode 100644 vendor/librw/src/ps2/pds.cpp create mode 100644 vendor/librw/src/ps2/ps2.cpp create mode 100644 vendor/librw/src/ps2/ps2device.cpp create mode 100644 vendor/librw/src/ps2/ps2matfx.cpp create mode 100644 vendor/librw/src/ps2/ps2raster.cpp create mode 100644 vendor/librw/src/ps2/ps2skin.cpp create mode 100644 vendor/librw/src/ps2/rwps2.h create mode 100644 vendor/librw/src/ps2/rwps2impl.h create mode 100644 vendor/librw/src/ps2/rwps2plg.h create mode 100644 vendor/librw/src/raster.cpp create mode 100644 vendor/librw/src/render.cpp create mode 100644 vendor/librw/src/rwanim.h create mode 100644 vendor/librw/src/rwbase.h create mode 100644 vendor/librw/src/rwcharset.h create mode 100644 vendor/librw/src/rwengine.h create mode 100644 vendor/librw/src/rwerror.h create mode 100644 vendor/librw/src/rwobjects.h create mode 100644 vendor/librw/src/rwpipeline.h create mode 100644 vendor/librw/src/rwplg.h create mode 100644 vendor/librw/src/rwplugins.h create mode 100644 vendor/librw/src/rwrender.h create mode 100644 vendor/librw/src/rwuserdata.h create mode 100644 vendor/librw/src/skin.cpp create mode 100644 vendor/librw/src/texture.cpp create mode 100644 vendor/librw/src/tga.cpp create mode 100644 vendor/librw/src/tristrip.cpp create mode 100644 vendor/librw/src/userdata.cpp create mode 100644 vendor/librw/src/uvanim.cpp create mode 100644 vendor/librw/src/vgafont.inc create mode 100644 vendor/librw/src/world.cpp create mode 100644 vendor/librw/tools/CMakeLists.txt create mode 100644 vendor/librw/tools/camera/CMakeLists.txt create mode 100644 vendor/librw/tools/camera/camexamp.cpp create mode 100644 vendor/librw/tools/camera/camexamp.h create mode 100644 vendor/librw/tools/camera/files/clump.dff create mode 100644 vendor/librw/tools/camera/files/clump/shinarm.png create mode 100644 vendor/librw/tools/camera/files/clump/shinbody.png create mode 100644 vendor/librw/tools/camera/files/clump/shinface.png create mode 100644 vendor/librw/tools/camera/files/clump/shinleg.png create mode 100644 vendor/librw/tools/camera/main.cpp create mode 100644 vendor/librw/tools/camera/viewer.cpp create mode 100644 vendor/librw/tools/camera/viewer.h create mode 100644 vendor/librw/tools/dumprwtree/CMakeLists.txt create mode 100644 vendor/librw/tools/dumprwtree/dumprwtree.cpp create mode 100644 vendor/librw/tools/im2d/CMakeLists.txt create mode 100644 vendor/librw/tools/im2d/files/whiteash.png create mode 100644 vendor/librw/tools/im2d/im2d.cpp create mode 100644 vendor/librw/tools/im2d/im2d.h create mode 100644 vendor/librw/tools/im2d/linelist.cpp create mode 100644 vendor/librw/tools/im2d/main.cpp create mode 100644 vendor/librw/tools/im2d/polyline.cpp create mode 100644 vendor/librw/tools/im2d/trifan.cpp create mode 100644 vendor/librw/tools/im2d/trilist.cpp create mode 100644 vendor/librw/tools/im2d/tristrip.cpp create mode 100644 vendor/librw/tools/im3d/CMakeLists.txt create mode 100644 vendor/librw/tools/im3d/files/whiteash.png create mode 100644 vendor/librw/tools/im3d/im3d.cpp create mode 100644 vendor/librw/tools/im3d/im3d.h create mode 100644 vendor/librw/tools/im3d/linelist.cpp create mode 100644 vendor/librw/tools/im3d/main.cpp create mode 100644 vendor/librw/tools/im3d/polyline.cpp create mode 100644 vendor/librw/tools/im3d/trifan.cpp create mode 100644 vendor/librw/tools/im3d/trilist.cpp create mode 100644 vendor/librw/tools/im3d/tristrip.cpp create mode 100644 vendor/librw/tools/imguitest/CMakeLists.txt create mode 100644 vendor/librw/tools/imguitest/main.cpp create mode 100644 vendor/librw/tools/lights/CMakeLists.txt create mode 100644 vendor/librw/tools/lights/checker.dff create mode 100644 vendor/librw/tools/lights/lights.cpp create mode 100644 vendor/librw/tools/lights/lights.h create mode 100644 vendor/librw/tools/lights/main.cpp create mode 100644 vendor/librw/tools/lights/main.h create mode 100644 vendor/librw/tools/playground/CMakeLists.txt create mode 100644 vendor/librw/tools/playground/camera.cpp create mode 100644 vendor/librw/tools/playground/camera.h create mode 100644 vendor/librw/tools/playground/files/Bm437_IBM_BIOS.FON create mode 100644 vendor/librw/tools/playground/files/Bm437_IBM_BIOS.tga create mode 100644 vendor/librw/tools/playground/files/Bm437_IBM_VGA8.FON create mode 100644 vendor/librw/tools/playground/files/Bm437_IBM_VGA8.tga create mode 100644 vendor/librw/tools/playground/files/foobar.tga create mode 100644 vendor/librw/tools/playground/files/maze.tga create mode 100644 vendor/librw/tools/playground/files/teapot.dff create mode 100644 vendor/librw/tools/playground/font.cpp create mode 100644 vendor/librw/tools/playground/main.cpp create mode 100644 vendor/librw/tools/playground/ras_test.cpp create mode 100644 vendor/librw/tools/playground/splines.cpp create mode 100644 vendor/librw/tools/playground/tl_tests.cpp create mode 100644 vendor/librw/tools/ps2test/CMakeLists.txt create mode 100644 vendor/librw/tools/ps2test/gs.h create mode 100755 vendor/librw/tools/ps2test/main.cpp create mode 100644 vendor/librw/tools/ps2test/mem.h create mode 100644 vendor/librw/tools/ps2test/ps2.h create mode 100644 vendor/librw/tools/ps2test/vu/defaultpipe.dsm create mode 100644 vendor/librw/tools/ps2test/vu/light.vu create mode 100644 vendor/librw/tools/ps2test/vu/setup_persp.vu create mode 100644 vendor/librw/tools/ps2test/vu/skinpipe.dsm create mode 100644 vendor/librw/tools/ska2anm/CMakeLists.txt create mode 100644 vendor/librw/tools/ska2anm/ska2anm.cpp create mode 100644 vendor/librw/tools/subrast/CMakeLists.txt create mode 100644 vendor/librw/tools/subrast/files/clump.dff create mode 100644 vendor/librw/tools/subrast/files/textures/whiteash.png create mode 100644 vendor/librw/tools/subrast/main.cpp create mode 100644 vendor/librw/tools/subrast/subrast.cpp create mode 100644 vendor/librw/tools/subrast/subrast.h create mode 100644 vendor/libsndfile/ChangeLog create mode 100644 vendor/libsndfile/NEWS create mode 100644 vendor/libsndfile/dist/Win32/libsndfile-1.dll create mode 100644 vendor/libsndfile/dist/Win64/libsndfile-1.dll create mode 100644 vendor/libsndfile/include/sndfile.h create mode 100644 vendor/libsndfile/include/sndfile.hh create mode 100644 vendor/libsndfile/lib/Win32/libsndfile-1.def create mode 100644 vendor/libsndfile/lib/Win32/libsndfile-1.lib create mode 100644 vendor/libsndfile/lib/Win32/pkgconfig/sndfile.pc create mode 100644 vendor/libsndfile/lib/Win64/libsndfile-1.def create mode 100644 vendor/libsndfile/lib/Win64/libsndfile-1.lib create mode 100644 vendor/libsndfile/lib/Win64/pkgconfig/sndfile.pc create mode 100644 vendor/milessdk/include/mss.h create mode 100644 vendor/milessdk/lib/mss32.lib create mode 100644 vendor/mpg123/dist/Win32/libmpg123-0.dll create mode 100644 vendor/mpg123/dist/Win64/libmpg123-0.dll create mode 100644 vendor/mpg123/include/fmt123.h create mode 100644 vendor/mpg123/include/mpg123.h create mode 100644 vendor/mpg123/lib/Win32/libmpg123-0.lib create mode 100644 vendor/mpg123/lib/Win64/libmpg123-0.lib create mode 100644 vendor/ogg/.gitignore create mode 100644 vendor/ogg/.gitlab-ci.yml create mode 100644 vendor/ogg/.travis.yml create mode 100644 vendor/ogg/AUTHORS create mode 100644 vendor/ogg/CHANGES create mode 100644 vendor/ogg/CMakeLists.txt create mode 100644 vendor/ogg/COPYING create mode 100644 vendor/ogg/Makefile.am create mode 100644 vendor/ogg/README.md create mode 100644 vendor/ogg/appveyor.yml create mode 100755 vendor/ogg/autogen.sh create mode 100644 vendor/ogg/cmake/CheckSizes.cmake create mode 100644 vendor/ogg/cmake/OggConfig.cmake.in create mode 100644 vendor/ogg/configure.ac create mode 100644 vendor/ogg/doc/Makefile.am create mode 100644 vendor/ogg/doc/fish_xiph_org.png create mode 100644 vendor/ogg/doc/framing.html create mode 100644 vendor/ogg/doc/index.html create mode 100644 vendor/ogg/doc/libogg/Makefile.am create mode 100644 vendor/ogg/doc/libogg/bitpacking.html create mode 100644 vendor/ogg/doc/libogg/datastructures.html create mode 100644 vendor/ogg/doc/libogg/decoding.html create mode 100644 vendor/ogg/doc/libogg/encoding.html create mode 100644 vendor/ogg/doc/libogg/general.html create mode 100644 vendor/ogg/doc/libogg/index.html create mode 100644 vendor/ogg/doc/libogg/ogg_iovec_t.html create mode 100644 vendor/ogg/doc/libogg/ogg_packet.html create mode 100644 vendor/ogg/doc/libogg/ogg_packet_clear.html create mode 100644 vendor/ogg/doc/libogg/ogg_page.html create mode 100644 vendor/ogg/doc/libogg/ogg_page_bos.html create mode 100644 vendor/ogg/doc/libogg/ogg_page_checksum_set.html create mode 100644 vendor/ogg/doc/libogg/ogg_page_continued.html create mode 100644 vendor/ogg/doc/libogg/ogg_page_eos.html create mode 100644 vendor/ogg/doc/libogg/ogg_page_granulepos.html create mode 100644 vendor/ogg/doc/libogg/ogg_page_packets.html create mode 100644 vendor/ogg/doc/libogg/ogg_page_pageno.html create mode 100644 vendor/ogg/doc/libogg/ogg_page_serialno.html create mode 100644 vendor/ogg/doc/libogg/ogg_page_version.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_check.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_clear.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_destroy.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_eos.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_flush.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_flush_fill.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_init.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_iovecin.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_packetin.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_packetout.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_packetpeek.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_pagein.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_pageout.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_pageout_fill.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_reset.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_reset_serialno.html create mode 100644 vendor/ogg/doc/libogg/ogg_stream_state.html create mode 100644 vendor/ogg/doc/libogg/ogg_sync_buffer.html create mode 100644 vendor/ogg/doc/libogg/ogg_sync_check.html create mode 100644 vendor/ogg/doc/libogg/ogg_sync_clear.html create mode 100644 vendor/ogg/doc/libogg/ogg_sync_destroy.html create mode 100644 vendor/ogg/doc/libogg/ogg_sync_init.html create mode 100644 vendor/ogg/doc/libogg/ogg_sync_pageout.html create mode 100644 vendor/ogg/doc/libogg/ogg_sync_pageseek.html create mode 100644 vendor/ogg/doc/libogg/ogg_sync_reset.html create mode 100644 vendor/ogg/doc/libogg/ogg_sync_state.html create mode 100644 vendor/ogg/doc/libogg/ogg_sync_wrote.html create mode 100644 vendor/ogg/doc/libogg/oggpack_adv.html create mode 100644 vendor/ogg/doc/libogg/oggpack_adv1.html create mode 100644 vendor/ogg/doc/libogg/oggpack_bits.html create mode 100644 vendor/ogg/doc/libogg/oggpack_buffer.html create mode 100644 vendor/ogg/doc/libogg/oggpack_bytes.html create mode 100644 vendor/ogg/doc/libogg/oggpack_get_buffer.html create mode 100644 vendor/ogg/doc/libogg/oggpack_look.html create mode 100644 vendor/ogg/doc/libogg/oggpack_look1.html create mode 100644 vendor/ogg/doc/libogg/oggpack_read.html create mode 100644 vendor/ogg/doc/libogg/oggpack_read1.html create mode 100644 vendor/ogg/doc/libogg/oggpack_readinit.html create mode 100644 vendor/ogg/doc/libogg/oggpack_reset.html create mode 100644 vendor/ogg/doc/libogg/oggpack_write.html create mode 100644 vendor/ogg/doc/libogg/oggpack_writealign.html create mode 100644 vendor/ogg/doc/libogg/oggpack_writecheck.html create mode 100644 vendor/ogg/doc/libogg/oggpack_writeclear.html create mode 100644 vendor/ogg/doc/libogg/oggpack_writecopy.html create mode 100644 vendor/ogg/doc/libogg/oggpack_writeinit.html create mode 100644 vendor/ogg/doc/libogg/oggpack_writetrunc.html create mode 100644 vendor/ogg/doc/libogg/overview.html create mode 100644 vendor/ogg/doc/libogg/reference.html create mode 100644 vendor/ogg/doc/libogg/style.css create mode 100644 vendor/ogg/doc/multiplex1.png create mode 100644 vendor/ogg/doc/multiplex1.svg create mode 100644 vendor/ogg/doc/ogg-multiplex.html create mode 100644 vendor/ogg/doc/oggstream.html create mode 100644 vendor/ogg/doc/packets.png create mode 100644 vendor/ogg/doc/packets.svg create mode 100644 vendor/ogg/doc/pages.png create mode 100644 vendor/ogg/doc/pages.svg create mode 100644 vendor/ogg/doc/release.txt create mode 100644 vendor/ogg/doc/rfc3533.txt create mode 100644 vendor/ogg/doc/rfc3534.txt create mode 100644 vendor/ogg/doc/rfc5334.txt create mode 100755 vendor/ogg/doc/skeleton.html create mode 100644 vendor/ogg/doc/stream.png create mode 100644 vendor/ogg/doc/vorbisword2.png create mode 100644 vendor/ogg/doc/white-ogg.png create mode 100644 vendor/ogg/doc/white-xifish.png create mode 100644 vendor/ogg/include/Makefile.am create mode 100644 vendor/ogg/include/ogg/Makefile.am create mode 100644 vendor/ogg/include/ogg/config_types.h.in create mode 100644 vendor/ogg/include/ogg/ogg.h create mode 100644 vendor/ogg/include/ogg/os_types.h create mode 100644 vendor/ogg/libogg.spec.in create mode 100644 vendor/ogg/ogg-uninstalled.pc.in create mode 100644 vendor/ogg/ogg.m4 create mode 100644 vendor/ogg/ogg.pc.in create mode 100644 vendor/ogg/releases.sha2 create mode 100644 vendor/ogg/src/Makefile.am create mode 100644 vendor/ogg/src/bitwise.c create mode 100644 vendor/ogg/src/crctable.h create mode 100644 vendor/ogg/src/framing.c create mode 100644 vendor/ogg/win32/.gitignore create mode 100644 vendor/ogg/win32/VS2015/libogg.sln create mode 100644 vendor/ogg/win32/VS2015/libogg.vcxproj create mode 100644 vendor/ogg/win32/VS2015/libogg.vcxproj.filters create mode 100644 vendor/ogg/win32/ogg.def create mode 100644 vendor/openal-soft/COPYING create mode 100644 vendor/openal-soft/dist/Win32/OpenAL32.dll create mode 100644 vendor/openal-soft/dist/Win64/OpenAL32.dll create mode 100644 vendor/openal-soft/include/AL/al.h create mode 100644 vendor/openal-soft/include/AL/alc.h create mode 100644 vendor/openal-soft/include/AL/alext.h create mode 100644 vendor/openal-soft/include/AL/efx-creative.h create mode 100644 vendor/openal-soft/include/AL/efx-presets.h create mode 100644 vendor/openal-soft/include/AL/efx.h create mode 100644 vendor/openal-soft/libs/Win32/OpenAL32.def create mode 100644 vendor/openal-soft/libs/Win32/OpenAL32.lib create mode 100644 vendor/openal-soft/libs/Win32/libOpenAL32.dll.a create mode 100644 vendor/openal-soft/libs/Win64/OpenAL32.def create mode 100644 vendor/openal-soft/libs/Win64/OpenAL32.lib create mode 100644 vendor/openal-soft/libs/Win64/libOpenAL32.dll.a create mode 100644 vendor/openal-soft/readme.txt create mode 100644 vendor/opus/.appveyor.yml create mode 100644 vendor/opus/.gitattributes create mode 100644 vendor/opus/.gitignore create mode 100644 vendor/opus/.gitlab-ci.yml create mode 100644 vendor/opus/.travis.yml create mode 100644 vendor/opus/AUTHORS create mode 100644 vendor/opus/CMakeLists.txt create mode 100644 vendor/opus/COPYING create mode 100644 vendor/opus/ChangeLog create mode 100644 vendor/opus/LICENSE_PLEASE_READ.txt create mode 100644 vendor/opus/Makefile.am create mode 100644 vendor/opus/Makefile.mips create mode 100644 vendor/opus/Makefile.unix create mode 100644 vendor/opus/NEWS create mode 100644 vendor/opus/README create mode 100644 vendor/opus/README.draft create mode 100755 vendor/opus/autogen.sh create mode 100644 vendor/opus/celt/_kiss_fft_guts.h create mode 100644 vendor/opus/celt/arch.h create mode 100644 vendor/opus/celt/bands.c create mode 100644 vendor/opus/celt/bands.h create mode 100644 vendor/opus/celt/celt.c create mode 100644 vendor/opus/celt/celt.h create mode 100644 vendor/opus/celt/celt_decoder.c create mode 100644 vendor/opus/celt/celt_encoder.c create mode 100644 vendor/opus/celt/celt_lpc.c create mode 100644 vendor/opus/celt/celt_lpc.h create mode 100644 vendor/opus/celt/cpu_support.h create mode 100644 vendor/opus/celt/cwrs.c create mode 100644 vendor/opus/celt/cwrs.h create mode 100644 vendor/opus/celt/dump_modes/dump_modes.c create mode 100644 vendor/opus/celt/dump_modes/dump_modes_arch.h create mode 100644 vendor/opus/celt/dump_modes/dump_modes_arm_ne10.c create mode 100644 vendor/opus/celt/ecintrin.h create mode 100644 vendor/opus/celt/entcode.c create mode 100644 vendor/opus/celt/entcode.h create mode 100644 vendor/opus/celt/entdec.c create mode 100644 vendor/opus/celt/entdec.h create mode 100644 vendor/opus/celt/entenc.c create mode 100644 vendor/opus/celt/entenc.h create mode 100644 vendor/opus/celt/fixed_c5x.h create mode 100644 vendor/opus/celt/fixed_c6x.h create mode 100644 vendor/opus/celt/fixed_debug.h create mode 100644 vendor/opus/celt/fixed_generic.h create mode 100644 vendor/opus/celt/float_cast.h create mode 100644 vendor/opus/celt/kiss_fft.c create mode 100644 vendor/opus/celt/kiss_fft.h create mode 100644 vendor/opus/celt/laplace.c create mode 100644 vendor/opus/celt/laplace.h create mode 100644 vendor/opus/celt/mathops.c create mode 100644 vendor/opus/celt/mathops.h create mode 100644 vendor/opus/celt/mdct.c create mode 100644 vendor/opus/celt/mdct.h create mode 100644 vendor/opus/celt/mfrngcod.h create mode 100644 vendor/opus/celt/mips/celt_mipsr1.h create mode 100644 vendor/opus/celt/mips/fixed_generic_mipsr1.h create mode 100644 vendor/opus/celt/mips/kiss_fft_mipsr1.h create mode 100644 vendor/opus/celt/mips/mdct_mipsr1.h create mode 100644 vendor/opus/celt/mips/pitch_mipsr1.h create mode 100644 vendor/opus/celt/mips/vq_mipsr1.h create mode 100644 vendor/opus/celt/modes.c create mode 100644 vendor/opus/celt/modes.h create mode 100644 vendor/opus/celt/opus_custom_demo.c create mode 100644 vendor/opus/celt/os_support.h create mode 100644 vendor/opus/celt/pitch.c create mode 100644 vendor/opus/celt/pitch.h create mode 100644 vendor/opus/celt/quant_bands.c create mode 100644 vendor/opus/celt/quant_bands.h create mode 100644 vendor/opus/celt/rate.c create mode 100644 vendor/opus/celt/rate.h create mode 100644 vendor/opus/celt/stack_alloc.h create mode 100644 vendor/opus/celt/static_modes_fixed.h create mode 100644 vendor/opus/celt/static_modes_fixed_arm_ne10.h create mode 100644 vendor/opus/celt/static_modes_float.h create mode 100644 vendor/opus/celt/static_modes_float_arm_ne10.h create mode 100644 vendor/opus/celt/tests/test_unit_cwrs32.c create mode 100644 vendor/opus/celt/tests/test_unit_dft.c create mode 100644 vendor/opus/celt/tests/test_unit_entropy.c create mode 100644 vendor/opus/celt/tests/test_unit_laplace.c create mode 100644 vendor/opus/celt/tests/test_unit_mathops.c create mode 100644 vendor/opus/celt/tests/test_unit_mdct.c create mode 100644 vendor/opus/celt/tests/test_unit_rotation.c create mode 100644 vendor/opus/celt/tests/test_unit_types.c create mode 100644 vendor/opus/celt/vq.c create mode 100644 vendor/opus/celt/vq.h create mode 100644 vendor/opus/celt_headers.mk create mode 100644 vendor/opus/celt_sources.mk create mode 100644 vendor/opus/cmake/CFeatureCheck.cmake create mode 100644 vendor/opus/cmake/OpusBuildtype.cmake create mode 100644 vendor/opus/cmake/OpusConfig.cmake create mode 100644 vendor/opus/cmake/OpusConfig.cmake.in create mode 100644 vendor/opus/cmake/OpusFunctions.cmake create mode 100644 vendor/opus/cmake/OpusPackageVersion.cmake create mode 100644 vendor/opus/cmake/OpusSources.cmake create mode 100644 vendor/opus/cmake/config.h.cmake.in create mode 100644 vendor/opus/cmake/vla.c create mode 100644 vendor/opus/configure.ac create mode 100644 vendor/opus/doc/Doxyfile.in create mode 100644 vendor/opus/doc/Makefile.am create mode 100755 vendor/opus/doc/build_draft.sh create mode 100755 vendor/opus/doc/build_isobmff.sh create mode 100755 vendor/opus/doc/build_oggdraft.sh create mode 100644 vendor/opus/doc/customdoxygen.css create mode 100644 vendor/opus/doc/draft-ietf-codec-oggopus.xml create mode 100644 vendor/opus/doc/draft-ietf-codec-opus-update.xml create mode 100644 vendor/opus/doc/draft-ietf-codec-opus.xml create mode 100644 vendor/opus/doc/draft-ietf-payload-rtp-opus.xml create mode 100644 vendor/opus/doc/footer.html create mode 100644 vendor/opus/doc/header.html create mode 100644 vendor/opus/doc/opus_in_isobmff.css create mode 100644 vendor/opus/doc/opus_in_isobmff.html create mode 100644 vendor/opus/doc/opus_logo.svg create mode 100644 vendor/opus/doc/opus_update.patch create mode 100644 vendor/opus/doc/release.txt create mode 100644 vendor/opus/doc/trivial_example.c create mode 100644 vendor/opus/include/opus.h create mode 100644 vendor/opus/include/opus_custom.h create mode 100644 vendor/opus/include/opus_defines.h create mode 100644 vendor/opus/include/opus_multistream.h create mode 100644 vendor/opus/include/opus_projection.h create mode 100644 vendor/opus/include/opus_types.h create mode 100644 vendor/opus/m4/as-gcc-inline-assembly.m4 create mode 100644 vendor/opus/m4/ax_add_fortify_source.m4 create mode 100644 vendor/opus/m4/opus-intrinsics.m4 create mode 100644 vendor/opus/opus-uninstalled.pc.in create mode 100644 vendor/opus/opus.m4 create mode 100644 vendor/opus/opus.pc.in create mode 100644 vendor/opus/opus_headers.mk create mode 100644 vendor/opus/opus_sources.mk create mode 100644 vendor/opus/releases.sha2 create mode 100755 vendor/opus/scripts/dump_rnn.py create mode 100755 vendor/opus/scripts/rnn_train.py create mode 100644 vendor/opus/silk/A2NLSF.c create mode 100644 vendor/opus/silk/API.h create mode 100644 vendor/opus/silk/CNG.c create mode 100644 vendor/opus/silk/HP_variable_cutoff.c create mode 100644 vendor/opus/silk/Inlines.h create mode 100644 vendor/opus/silk/LPC_analysis_filter.c create mode 100644 vendor/opus/silk/LPC_fit.c create mode 100644 vendor/opus/silk/LPC_inv_pred_gain.c create mode 100644 vendor/opus/silk/LP_variable_cutoff.c create mode 100644 vendor/opus/silk/MacroCount.h create mode 100644 vendor/opus/silk/MacroDebug.h create mode 100644 vendor/opus/silk/NLSF2A.c create mode 100644 vendor/opus/silk/NLSF_VQ.c create mode 100644 vendor/opus/silk/NLSF_VQ_weights_laroia.c create mode 100644 vendor/opus/silk/NLSF_decode.c create mode 100644 vendor/opus/silk/NLSF_del_dec_quant.c create mode 100644 vendor/opus/silk/NLSF_encode.c create mode 100644 vendor/opus/silk/NLSF_stabilize.c create mode 100644 vendor/opus/silk/NLSF_unpack.c create mode 100644 vendor/opus/silk/NSQ.c create mode 100644 vendor/opus/silk/NSQ.h create mode 100644 vendor/opus/silk/NSQ_del_dec.c create mode 100644 vendor/opus/silk/PLC.c create mode 100644 vendor/opus/silk/PLC.h create mode 100644 vendor/opus/silk/SigProc_FIX.h create mode 100644 vendor/opus/silk/VAD.c create mode 100644 vendor/opus/silk/VQ_WMat_EC.c create mode 100644 vendor/opus/silk/ana_filt_bank_1.c create mode 100644 vendor/opus/silk/biquad_alt.c create mode 100644 vendor/opus/silk/bwexpander.c create mode 100644 vendor/opus/silk/bwexpander_32.c create mode 100644 vendor/opus/silk/check_control_input.c create mode 100644 vendor/opus/silk/code_signs.c create mode 100644 vendor/opus/silk/control.h create mode 100644 vendor/opus/silk/control_SNR.c create mode 100644 vendor/opus/silk/control_audio_bandwidth.c create mode 100644 vendor/opus/silk/control_codec.c create mode 100644 vendor/opus/silk/debug.c create mode 100644 vendor/opus/silk/debug.h create mode 100644 vendor/opus/silk/dec_API.c create mode 100644 vendor/opus/silk/decode_core.c create mode 100644 vendor/opus/silk/decode_frame.c create mode 100644 vendor/opus/silk/decode_indices.c create mode 100644 vendor/opus/silk/decode_parameters.c create mode 100644 vendor/opus/silk/decode_pitch.c create mode 100644 vendor/opus/silk/decode_pulses.c create mode 100644 vendor/opus/silk/decoder_set_fs.c create mode 100644 vendor/opus/silk/define.h create mode 100644 vendor/opus/silk/enc_API.c create mode 100644 vendor/opus/silk/encode_indices.c create mode 100644 vendor/opus/silk/encode_pulses.c create mode 100644 vendor/opus/silk/errors.h create mode 100644 vendor/opus/silk/fixed/LTP_analysis_filter_FIX.c create mode 100644 vendor/opus/silk/fixed/LTP_scale_ctrl_FIX.c create mode 100644 vendor/opus/silk/fixed/apply_sine_window_FIX.c create mode 100644 vendor/opus/silk/fixed/autocorr_FIX.c create mode 100644 vendor/opus/silk/fixed/burg_modified_FIX.c create mode 100644 vendor/opus/silk/fixed/corrMatrix_FIX.c create mode 100644 vendor/opus/silk/fixed/encode_frame_FIX.c create mode 100644 vendor/opus/silk/fixed/find_LPC_FIX.c create mode 100644 vendor/opus/silk/fixed/find_LTP_FIX.c create mode 100644 vendor/opus/silk/fixed/find_pitch_lags_FIX.c create mode 100644 vendor/opus/silk/fixed/find_pred_coefs_FIX.c create mode 100644 vendor/opus/silk/fixed/k2a_FIX.c create mode 100644 vendor/opus/silk/fixed/k2a_Q16_FIX.c create mode 100644 vendor/opus/silk/fixed/main_FIX.h create mode 100644 vendor/opus/silk/fixed/mips/noise_shape_analysis_FIX_mipsr1.h create mode 100644 vendor/opus/silk/fixed/mips/prefilter_FIX_mipsr1.h create mode 100644 vendor/opus/silk/fixed/mips/warped_autocorrelation_FIX_mipsr1.h create mode 100644 vendor/opus/silk/fixed/noise_shape_analysis_FIX.c create mode 100644 vendor/opus/silk/fixed/pitch_analysis_core_FIX.c create mode 100644 vendor/opus/silk/fixed/process_gains_FIX.c create mode 100644 vendor/opus/silk/fixed/regularize_correlations_FIX.c create mode 100644 vendor/opus/silk/fixed/residual_energy16_FIX.c create mode 100644 vendor/opus/silk/fixed/residual_energy_FIX.c create mode 100644 vendor/opus/silk/fixed/schur64_FIX.c create mode 100644 vendor/opus/silk/fixed/schur_FIX.c create mode 100644 vendor/opus/silk/fixed/structs_FIX.h create mode 100644 vendor/opus/silk/fixed/vector_ops_FIX.c create mode 100644 vendor/opus/silk/fixed/warped_autocorrelation_FIX.c create mode 100644 vendor/opus/silk/float/LPC_analysis_filter_FLP.c create mode 100644 vendor/opus/silk/float/LPC_inv_pred_gain_FLP.c create mode 100644 vendor/opus/silk/float/LTP_analysis_filter_FLP.c create mode 100644 vendor/opus/silk/float/LTP_scale_ctrl_FLP.c create mode 100644 vendor/opus/silk/float/SigProc_FLP.h create mode 100644 vendor/opus/silk/float/apply_sine_window_FLP.c create mode 100644 vendor/opus/silk/float/autocorrelation_FLP.c create mode 100644 vendor/opus/silk/float/burg_modified_FLP.c create mode 100644 vendor/opus/silk/float/bwexpander_FLP.c create mode 100644 vendor/opus/silk/float/corrMatrix_FLP.c create mode 100644 vendor/opus/silk/float/encode_frame_FLP.c create mode 100644 vendor/opus/silk/float/energy_FLP.c create mode 100644 vendor/opus/silk/float/find_LPC_FLP.c create mode 100644 vendor/opus/silk/float/find_LTP_FLP.c create mode 100644 vendor/opus/silk/float/find_pitch_lags_FLP.c create mode 100644 vendor/opus/silk/float/find_pred_coefs_FLP.c create mode 100644 vendor/opus/silk/float/inner_product_FLP.c create mode 100644 vendor/opus/silk/float/k2a_FLP.c create mode 100644 vendor/opus/silk/float/main_FLP.h create mode 100644 vendor/opus/silk/float/noise_shape_analysis_FLP.c create mode 100644 vendor/opus/silk/float/pitch_analysis_core_FLP.c create mode 100644 vendor/opus/silk/float/process_gains_FLP.c create mode 100644 vendor/opus/silk/float/regularize_correlations_FLP.c create mode 100644 vendor/opus/silk/float/residual_energy_FLP.c create mode 100644 vendor/opus/silk/float/scale_copy_vector_FLP.c create mode 100644 vendor/opus/silk/float/scale_vector_FLP.c create mode 100644 vendor/opus/silk/float/schur_FLP.c create mode 100644 vendor/opus/silk/float/sort_FLP.c create mode 100644 vendor/opus/silk/float/structs_FLP.h create mode 100644 vendor/opus/silk/float/warped_autocorrelation_FLP.c create mode 100644 vendor/opus/silk/float/wrappers_FLP.c create mode 100644 vendor/opus/silk/gain_quant.c create mode 100644 vendor/opus/silk/init_decoder.c create mode 100644 vendor/opus/silk/init_encoder.c create mode 100644 vendor/opus/silk/inner_prod_aligned.c create mode 100644 vendor/opus/silk/interpolate.c create mode 100644 vendor/opus/silk/lin2log.c create mode 100644 vendor/opus/silk/log2lin.c create mode 100644 vendor/opus/silk/macros.h create mode 100644 vendor/opus/silk/main.h create mode 100644 vendor/opus/silk/mips/NSQ_del_dec_mipsr1.h create mode 100644 vendor/opus/silk/mips/macros_mipsr1.h create mode 100644 vendor/opus/silk/mips/sigproc_fix_mipsr1.h create mode 100644 vendor/opus/silk/pitch_est_defines.h create mode 100644 vendor/opus/silk/pitch_est_tables.c create mode 100644 vendor/opus/silk/process_NLSFs.c create mode 100644 vendor/opus/silk/quant_LTP_gains.c create mode 100644 vendor/opus/silk/resampler.c create mode 100644 vendor/opus/silk/resampler_down2.c create mode 100644 vendor/opus/silk/resampler_down2_3.c create mode 100644 vendor/opus/silk/resampler_private.h create mode 100644 vendor/opus/silk/resampler_private_AR2.c create mode 100644 vendor/opus/silk/resampler_private_IIR_FIR.c create mode 100644 vendor/opus/silk/resampler_private_down_FIR.c create mode 100644 vendor/opus/silk/resampler_private_up2_HQ.c create mode 100644 vendor/opus/silk/resampler_rom.c create mode 100644 vendor/opus/silk/resampler_rom.h create mode 100644 vendor/opus/silk/resampler_structs.h create mode 100644 vendor/opus/silk/shell_coder.c create mode 100644 vendor/opus/silk/sigm_Q15.c create mode 100644 vendor/opus/silk/sort.c create mode 100644 vendor/opus/silk/stereo_LR_to_MS.c create mode 100644 vendor/opus/silk/stereo_MS_to_LR.c create mode 100644 vendor/opus/silk/stereo_decode_pred.c create mode 100644 vendor/opus/silk/stereo_encode_pred.c create mode 100644 vendor/opus/silk/stereo_find_predictor.c create mode 100644 vendor/opus/silk/stereo_quant_pred.c create mode 100644 vendor/opus/silk/structs.h create mode 100644 vendor/opus/silk/sum_sqr_shift.c create mode 100644 vendor/opus/silk/table_LSF_cos.c create mode 100644 vendor/opus/silk/tables.h create mode 100644 vendor/opus/silk/tables_LTP.c create mode 100644 vendor/opus/silk/tables_NLSF_CB_NB_MB.c create mode 100644 vendor/opus/silk/tables_NLSF_CB_WB.c create mode 100644 vendor/opus/silk/tables_gain.c create mode 100644 vendor/opus/silk/tables_other.c create mode 100644 vendor/opus/silk/tables_pitch_lag.c create mode 100644 vendor/opus/silk/tables_pulses_per_block.c create mode 100644 vendor/opus/silk/tests/test_unit_LPC_inv_pred_gain.c create mode 100644 vendor/opus/silk/tuning_parameters.h create mode 100644 vendor/opus/silk/typedef.h create mode 100644 vendor/opus/silk_headers.mk create mode 100644 vendor/opus/silk_sources.mk create mode 100644 vendor/opus/src/analysis.c create mode 100644 vendor/opus/src/analysis.h create mode 100644 vendor/opus/src/mapping_matrix.c create mode 100644 vendor/opus/src/mapping_matrix.h create mode 100644 vendor/opus/src/mlp.c create mode 100644 vendor/opus/src/mlp.h create mode 100644 vendor/opus/src/mlp_data.c create mode 100644 vendor/opus/src/opus.c create mode 100644 vendor/opus/src/opus_compare.c create mode 100644 vendor/opus/src/opus_decoder.c create mode 100644 vendor/opus/src/opus_demo.c create mode 100644 vendor/opus/src/opus_encoder.c create mode 100644 vendor/opus/src/opus_multistream.c create mode 100644 vendor/opus/src/opus_multistream_decoder.c create mode 100644 vendor/opus/src/opus_multistream_encoder.c create mode 100644 vendor/opus/src/opus_private.h create mode 100644 vendor/opus/src/opus_projection_decoder.c create mode 100644 vendor/opus/src/opus_projection_encoder.c create mode 100644 vendor/opus/src/repacketizer.c create mode 100644 vendor/opus/src/repacketizer_demo.c create mode 100644 vendor/opus/src/tansig_table.h create mode 100644 vendor/opus/tests/opus_decode_fuzzer.c create mode 100644 vendor/opus/tests/opus_decode_fuzzer.options create mode 100644 vendor/opus/tests/opus_encode_regressions.c create mode 100755 vendor/opus/tests/run_vectors.sh create mode 100644 vendor/opus/tests/test_opus_api.c create mode 100644 vendor/opus/tests/test_opus_common.h create mode 100644 vendor/opus/tests/test_opus_decode.c create mode 100644 vendor/opus/tests/test_opus_encode.c create mode 100644 vendor/opus/tests/test_opus_padding.c create mode 100644 vendor/opus/tests/test_opus_projection.c create mode 100755 vendor/opus/training/rnn_dump.py create mode 100755 vendor/opus/training/rnn_train.py create mode 100755 vendor/opus/training/txt2hdf5.py create mode 100755 vendor/opus/update_version create mode 100644 vendor/opus/win32/.gitignore create mode 100644 vendor/opus/win32/VS2015/common.props create mode 100644 vendor/opus/win32/VS2015/opus.sln create mode 100644 vendor/opus/win32/VS2015/opus.vcxproj create mode 100644 vendor/opus/win32/VS2015/opus.vcxproj.filters create mode 100644 vendor/opus/win32/VS2015/opus_demo.vcxproj create mode 100644 vendor/opus/win32/VS2015/opus_demo.vcxproj.filters create mode 100644 vendor/opus/win32/VS2015/test_opus_api.vcxproj create mode 100644 vendor/opus/win32/VS2015/test_opus_api.vcxproj.filters create mode 100644 vendor/opus/win32/VS2015/test_opus_decode.vcxproj create mode 100644 vendor/opus/win32/VS2015/test_opus_decode.vcxproj.filters create mode 100644 vendor/opus/win32/VS2015/test_opus_encode.vcxproj create mode 100644 vendor/opus/win32/VS2015/test_opus_encode.vcxproj.filters create mode 100644 vendor/opus/win32/genversion.bat create mode 100644 vendor/opusfile/.appveyor.yml create mode 100644 vendor/opusfile/.gitattributes create mode 100644 vendor/opusfile/.gitignore create mode 100644 vendor/opusfile/.gitlab-ci.yml create mode 100644 vendor/opusfile/.travis.yml create mode 100644 vendor/opusfile/AUTHORS create mode 100644 vendor/opusfile/Brewfile create mode 100644 vendor/opusfile/COPYING create mode 100644 vendor/opusfile/Makefile.am create mode 100644 vendor/opusfile/README.md create mode 100755 vendor/opusfile/autogen.sh create mode 100755 vendor/opusfile/ci/autotools.sh create mode 100755 vendor/opusfile/ci/unix.sh create mode 100644 vendor/opusfile/configure.ac create mode 100644 vendor/opusfile/doc/Doxyfile.in create mode 100644 vendor/opusfile/doc/Makefile create mode 100644 vendor/opusfile/doc/opus_logo.svg create mode 100644 vendor/opusfile/doc/release.md create mode 100644 vendor/opusfile/examples/opusfile_example.c create mode 100644 vendor/opusfile/examples/seeking_example.c create mode 100644 vendor/opusfile/examples/win32utf8.c create mode 100644 vendor/opusfile/examples/win32utf8.h create mode 100644 vendor/opusfile/include/opusfile.h create mode 100644 vendor/opusfile/m4/attributes.m4 create mode 100644 vendor/opusfile/mingw/Dockerfile create mode 100644 vendor/opusfile/mingw/Makefile create mode 100644 vendor/opusfile/mingw/README.md create mode 100644 vendor/opusfile/opusfile-uninstalled.pc.in create mode 100644 vendor/opusfile/opusfile.pc.in create mode 100644 vendor/opusfile/opusurl-uninstalled.pc.in create mode 100644 vendor/opusfile/opusurl.pc.in create mode 100644 vendor/opusfile/releases.sha2 create mode 100644 vendor/opusfile/src/http.c create mode 100644 vendor/opusfile/src/info.c create mode 100644 vendor/opusfile/src/internal.c create mode 100644 vendor/opusfile/src/internal.h create mode 100644 vendor/opusfile/src/opusfile.c create mode 100644 vendor/opusfile/src/stream.c create mode 100644 vendor/opusfile/src/wincerts.c create mode 100644 vendor/opusfile/src/winerrno.h create mode 100644 vendor/opusfile/unix/Makefile create mode 100755 vendor/opusfile/update_version create mode 100644 vendor/opusfile/win32/.gitignore create mode 100644 vendor/opusfile/win32/VS2015/opusfile.sln create mode 100644 vendor/opusfile/win32/VS2015/opusfile.vcxproj create mode 100644 vendor/opusfile/win32/VS2015/opusfile.vcxproj.filters create mode 100644 vendor/opusfile/win32/VS2015/opusfile_example.vcxproj create mode 100644 vendor/opusfile/win32/VS2015/opusfile_example.vcxproj.filters create mode 100644 vendor/opusfile/win32/VS2015/seeking_example.vcxproj create mode 100644 vendor/opusfile/win32/VS2015/seeking_example.vcxproj.filters diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7185ba3 --- /dev/null +++ b/.clang-format @@ -0,0 +1,28 @@ +--- +AllowShortBlocksOnASingleLine: 'true' +AllowShortCaseLabelsOnASingleLine: 'true' +AllowShortIfStatementsOnASingleLine: 'true' +AllowShortLoopsOnASingleLine: 'true' +AlwaysBreakAfterReturnType: TopLevel +AccessModifierOffset: -8 +BreakBeforeBraces: Linux +ColumnLimit: 160 +IndentCaseLabels: 'false' +IndentWidth: '8' +Language: Cpp +PointerAlignment: Right +SpaceAfterCStyleCast: 'false' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeCtorInitializerColon: 'true' +SpaceBeforeInheritanceColon: 'true' +SpaceBeforeParens: Never +SpaceInEmptyParentheses: 'false' +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInContainerLiterals: 'false' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +TabWidth: '8' +UseTab: ForIndentation + +... diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9261b40 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +sdk/* linguist-vendored +vendor/* linguist-vendored \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..85d1e58 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Version** +Tell us what version you're running. Find out using the debug menu (Ctrl-M, Debug -> Version Text) +If you send a screenshot just enable it beforehand. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..f458bd4 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +As long as it's not linux/cross-platform skeleton/compatibility layer, all of the code on the repo that's not behind a preprocessor condition(like FIX_BUGS) are **completely** reversed code from original binaries. + +We **don't** accept custom codes, as long as it's not wrapped via preprocessor conditions, or it's linux/cross-platform skeleton/compatibility layer. + +We accept only these kinds of PRs; + +- A new feature that exists in at least one of the GTAs (if it wasn't in III/VC then it doesn't have to be decompilation) +- Game, UI or UX bug fixes (if it's a fix to R* code, it should be behind FIX_BUGS) +- Platform-specific and/or unused code that's not been reversed yet +- Makes reversed code more understandable/accurate, as in "which code would produce this assembly". +- A new cross-platform skeleton/compatibility layer, or improvements to them +- Translation fixes, for languages R* supported/outsourced +- Code that increase maintainability diff --git a/.github/workflows/build-cmake-conan.yml b/.github/workflows/build-cmake-conan.yml new file mode 100644 index 0000000..5e8dad9 --- /dev/null +++ b/.github/workflows/build-cmake-conan.yml @@ -0,0 +1,117 @@ +name: re3 conan+cmake +on: + pull_request: + push: + release: + types: published +jobs: + build-cmake: + strategy: + matrix: + include: + - os: 'windows-latest' + platform: 'gl3' + gl3_gfxlib: 'glfw' + audio: 'openal' +# - os: 'windows-latest' +# platform: 'gl3' +# gl3_gfxlib: 'sdl2' +# audio: 'openal' + - os: 'windows-latest' + platform: 'd3d9' + audio: 'openal' +# - os: 'windows-latest' +# platform: 'd3d9' +# audio: 'miles' + - os: 'ubuntu-18.04' + platform: 'gl3' + gl3_gfxlib: 'glfw' + audio: 'openal' +# - os: 'ubuntu-18.04' +# platform: 'gl3' +# gl3_gfxlib: 'sdl2' +# audio: 'openal' + - os: 'macos-latest' + platform: 'gl3' + gl3_gfxlib: 'glfw' + audio: 'openal' +# - os: 'macos-latest' +# platform: 'gl3' +# gl3_gfxlib: 'sdl2' +# audio: 'openal' + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.platform == 'ps2' || matrix.gl3_gfxlib == 'sdl2' || matrix.audio == 'miles' }} + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - name: "Checkout Miles SDK Import Library project" + uses: actions/checkout@v2 + if: ${{ matrix.audio == 'miles' }} + with: + repository: 'withmorten/re3mss' + path: 're3mss' + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: "Use XCode 11 as default (conan-center-index does not provide XCode 12 binaries at the moment)" + if: startsWith(matrix.os, 'macos') + run: | + sudo xcode-select --switch /Applications/Xcode_11.7.app + - name: "Setup conan" + run: | + python -m pip install conan + conan config init + conan config set log.print_run_commands=True + conan config set general.revisions_enabled=1 + conan remote add bincrafters https://bincrafters.jfrog.io/artifactory/api/conan/public-conan +# conan remote add madebr_ps2dev https://api.bintray.com/conan/madebr/ps2dev + - name: "Add os=playstation2 + gcc.version=3.2 to .conan/settings.yml" + shell: python + run: | + import os, yaml + settings_path = os.path.expanduser("~/.conan/settings.yml") + yml = yaml.safe_load(open(settings_path)) + yml["os"]["playstation2"] = None + yml["compiler"]["gcc"]["version"].append("3.2") + yml["compiler"]["gcc"]["version"].sort() + yaml.safe_dump(yml, open(settings_path, "w")) + - name: "Create host profile" + shell: bash + run: | + if test "${{ matrix.platform }}" = "ps2"; then + cp vendor/librw/conan/playstation2 host_profile + else + cp ~/.conan/profiles/default host_profile + fi + - name: "Export Playstation 2 CMake toolchain conan recipe" + run: | + conan export vendor/librw/cmake/ps2/cmaketoolchain ps2dev-cmaketoolchain/master@ + - name: "Export librw conan recipe" + run: | + conan export vendor/librw librw/master@ + - name: "Export Miles SDK conan recipe" + if: ${{ matrix.audio == 'miles' }} + run: | + conan export re3mss miles-sdk/master@ + - name: "Download/build dependencies (conan install)" + run: | + conan install ${{ github.workspace }} re3/master@ -if build -o re3:audio=${{ matrix.audio }} -o librw:platform=${{ matrix.platform }} -o librw:gl3_gfxlib=${{ matrix.gl3_gfxlib || 'glfw' }} --build missing -pr:h ./host_profile -pr:b default -s re3:build_type=RelWithDebInfo -s librw:build_type=RelWithDebInfo + env: + CONAN_SYSREQUIRES_MODE: enabled + - name: "Build re3 (conan build)" + run: | + conan build ${{ github.workspace }} -if build -bf build -pf package + - name: "Package re3 (conan package)" + run: | + conan package ${{ github.workspace }} -if build -bf build -pf package + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack -C RelWithDebInfo + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "${{ matrix.os }}-${{ matrix.platform }}" + path: build/*.zip + if-no-files-found: error diff --git a/.github/workflows/build-switch.yml b/.github/workflows/build-switch.yml new file mode 100644 index 0000000..46e1d50 --- /dev/null +++ b/.github/workflows/build-switch.yml @@ -0,0 +1,28 @@ +name: re3 cmake devkitA64 (Nintendo Switch) +on: + pull_request: + push: + release: + types: published +jobs: + build-nintendo-switch: + runs-on: ubuntu-latest + container: devkitpro/devkita64:latest + steps: + - uses: actions/checkout@v2 + with: + submodules: 'true' + - name: "Build files" + run: | + /opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake -S. -Bbuild -DRE3_AUDIO=OAL -DLIBRW_PLATFORM=GL3 -DLIBRW_GL3_GFXLIB=GLFW -DRE3_WITH_OPUS=False -DRE3_VENDORED_LIBRW=True -DRE3_INSTALL=True + cmake --build build --parallel + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "switch-gl3" + path: build/*.zip + if-no-files-found: error diff --git a/.github/workflows/re3_msvc_amd64.yml b/.github/workflows/re3_msvc_amd64.yml new file mode 100644 index 0000000..014ac4f --- /dev/null +++ b/.github/workflows/re3_msvc_amd64.yml @@ -0,0 +1,65 @@ +name: re3 premake amd64 + +on: + pull_request: + push: + release: + types: published +env: + GLFW_VER: "3.3.2" + GLFW_BASE: "glfw-3.3.2.bin.WIN64" + GLFW_FILE: "glfw-3.3.2.bin.WIN64.zip" + GLFW_URL: "https://github.com/glfw/glfw/releases/download/3.3.2/glfw-3.3.2.bin.WIN64.zip" +jobs: + build: + runs-on: windows-2019 + strategy: + matrix: + platform: [win-amd64-librw_d3d9-oal, win-amd64-librw_gl3_glfw-oal] + buildtype: [Debug, Release] + steps: + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.2 + - uses: actions/checkout@v2 + with: + submodules: 'true' + - if: ${{ matrix.platform }} == "win-amd64-librw_gl3_glfw-mss" + name: Download glfw + uses: carlosperate/download-file-action@v1.0.3 + with: + file-url: ${{env.GLFW_URL}} + - if: ${{ matrix.platform }} == "win-amd64-librw_gl3_glfw-mss" + name: Unpack archives + run: | + 7z x ${{env.GLFW_FILE}} + - name: Configure build + run: | + ./premake5 vs2019 --with-librw --no-full-paths --glfwdir64=${{env.GLFW_BASE}} + - name: Build + run: | + msbuild -m build/re3.sln /property:Configuration=${{matrix.buildtype}} /property:Platform=${{matrix.platform}} + # - name: Pack artifacts + # run: | + # 7z a re3_${{matrix.buildtype}}_${{matrix.platform}}.zip ./bin/${{matrix.platform}}/${{matrix.buildtype}}/* + - name: Move binaries to gamefiles + run: | + mv ./bin/${{matrix.platform}}/${{matrix.buildtype}}/re3.exe ./gamefiles/ + mv ./bin/${{matrix.platform}}/${{matrix.buildtype}}/re3.pdb ./gamefiles/ + - name: Move dynamic dependencies to gamefiles + run: | + mv ./vendor/mpg123/dist/Win64/libmpg123-0.dll ./gamefiles/ + mv ./vendor/openal-soft/dist/Win64/OpenAL32.dll ./gamefiles/ + - name: Upload artifact to actions + uses: actions/upload-artifact@v2 + with: + name: re3_${{matrix.buildtype}}_${{matrix.platform}} + path: ./gamefiles/* +# - name: Upload artifact to Bintray +# uses: hpcsc/upload-bintray-docker-action@v1 +# with: +# repository: re3 +# package: ${{matrix.buildtype}}_${{matrix.platform}} +# version: 1.0-$(echo ${GITHUB_SHA} +# sourcePath: ./bin/${{matrix.platform}}/${{matrix.buildtype}} +# username: gtamodding +# apiKey: ${{secrets.BINTRAY_API_KEY}} diff --git a/.github/workflows/re3_msvc_x86.yml b/.github/workflows/re3_msvc_x86.yml new file mode 100644 index 0000000..87f0e43 --- /dev/null +++ b/.github/workflows/re3_msvc_x86.yml @@ -0,0 +1,67 @@ +name: re3 premake x86 + +on: + pull_request: + push: + release: + types: published +env: + GLFW_VER: "3.3.2" + GLFW_BASE: "glfw-3.3.2.bin.WIN32" + GLFW_FILE: "glfw-3.3.2.bin.WIN32.zip" + GLFW_URL: "https://github.com/glfw/glfw/releases/download/3.3.2/glfw-3.3.2.bin.WIN32.zip" +jobs: + build: + runs-on: windows-2019 + strategy: + matrix: + platform: [win-x86-librw_d3d9-mss, win-x86-librw_gl3_glfw-mss, win-x86-librw_d3d9-oal, win-x86-librw_gl3_glfw-oal] + buildtype: [Debug, Release, Vanilla] + steps: + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.2 + - uses: actions/checkout@v2 + with: + submodules: 'true' + - if: ${{ matrix.platform }} == "win-x86-librw_gl3_glfw-mss" + name: Download glfw + uses: carlosperate/download-file-action@v1.0.3 + with: + file-url: ${{env.GLFW_URL}} + - if: ${{ matrix.platform }} == "win-x86-librw_gl3_glfw-mss" + name: Unpack archives + run: | + 7z x ${{env.GLFW_FILE}} + - name: Configure build + run: | + ./premake5 vs2019 --with-librw --no-full-paths --glfwdir32=${{env.GLFW_BASE}} + - name: Build + run: | + msbuild -m build/re3.sln /property:Configuration=${{matrix.buildtype}} /property:Platform=${{matrix.platform}} + # - name: Pack artifacts + # run: | + # 7z a re3_${{matrix.buildtype}}_${{matrix.platform}}.zip ./bin/${{matrix.platform}}/${{matrix.buildtype}}/* + - name: Move binaries to gamefiles + run: | + mv ./bin/${{matrix.platform}}/${{matrix.buildtype}}/re3.exe ./gamefiles/ + mv ./bin/${{matrix.platform}}/${{matrix.buildtype}}/re3.pdb ./gamefiles/ + - if: contains(matrix.platform, 'oal') + name: Move dynamic dependencies to gamefiles + run: | + mv ./vendor/mpg123/dist/Win32/libmpg123-0.dll ./gamefiles/ + mv ./vendor/openal-soft/dist/Win32/OpenAL32.dll ./gamefiles/ + - name: Upload artifact to actions + uses: actions/upload-artifact@v2 + with: + name: re3_${{matrix.buildtype}}_${{matrix.platform}} + path: ./gamefiles/* +# - name: Upload artifact to Bintray +# uses: hpcsc/upload-bintray-docker-action@v1 +# with: +# repository: re3 +# package: ${{matrix.buildtype}}_${{matrix.platform}} +# version: 1.0-$(echo ${GITHUB_SHA} +# sourcePath: ./bin/${{matrix.platform}}/${{matrix.buildtype}} +# username: gtamodding +# apiKey: ${{secrets.BINTRAY_API_KEY}} + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..38ad5d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Bb]uild/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +vendor/glew-2.1.0/ +vendor/glfw-3.3.2.bin.WIN32/ +vendor/glfw-3.3.2.bin.WIN64/ + +sdk/ + +codewarrior/re3.mcp +codewarrior/re3_Data/ +codewarrior/Release/ +codewarrior/Debug/ + +src/extras/GitSHA1.cpp \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c9a30c9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,16 @@ +[submodule "vendor/ogg"] + path = vendor/ogg + url = https://github.com/xiph/ogg.git + branch = master +[submodule "vendor/opus"] + path = vendor/opus + url = https://github.com/xiph/opus.git + branch = master +[submodule "vendor/opusfile"] + path = vendor/opusfile + url = https://github.com/xiph/opusfile.git + branch = master +[submodule "vendor/librw"] + path = vendor/librw + url = https://github.com/aap/librw.git + branch = master diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..2ce8272 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,50 @@ +{ + "configurations": [ + { + "name": "Mac", + "includePath": ["${default}"], + "defines": [], + "macFrameworkPath": [ + "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks" + ], + "compilerPath": "/opt/local/bin/clang", + "compilerArgs": ["-g"], + "cStandard": "gnu11", + "cppStandard": "gnu++14", + "browse": { + "path": [ + "/opt/local/include", + "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include" + ] + } + }, + { + "name": "Linux", + "includePath": ["${default}"], + "defines": ["XDG_ROOT"], + "compilerPath": "/usr/bin/gcc", + "compilerArgs": ["-ggdb"], + "cStandard": "gnu11", + "cppStandard": "gnu++14" + }, + { + "name": "devkitPro aarch64 (Nintendo Switch)", + "compilerPath": "${env:DEVKITPRO}/devkitA64/bin/aarch64-none-elf-g++", + "includePath": [ + "${default}", + "${env:DEVKITPRO}/portlibs/switch/include", + "${env:DEVKITPRO}/libnx/include" + ], + "intelliSenseMode": "gcc-arm64", + "cStandard": "gnu11", + "cppStandard": "gnu++11", + "defines": [ + "__SWITCH__", + "LIBRW", + "RW_GL3", + "AUDIO_OAL" + ] + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..82ce041 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,89 @@ +{ + "configurations": [ + { + "MIMode": "gdb", + "args": [], + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "name": "(gdb) Launch (Linux Debug)", + "preLaunchTask": "Compile (Debug Linux x64)", + "program": "${workspaceFolder}/bin/linux-amd64-librw_gl3_glfw-oal/Debug/re3", + "request": "launch", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "ignoreFailures": true, + "text": "-enable-pretty-printing" + } + ], + "stopAtEntry": false, + "targetArchitecture": "x64", + "type": "cppdbg" + }, + { + "MIMode": "gdb", + "args": [], + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "name": "(gdb) Launch (Linux Release)", + "preLaunchTask": "Compile (Release Linux x64)", + "program": "${workspaceFolder}/bin/linux-amd64-librw_gl3_glfw-oal/Release/re3", + "request": "launch", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "ignoreFailures": true, + "text": "-enable-pretty-printing" + } + ], + "stopAtEntry": false, + "targetArchitecture": "x64", + "type": "cppdbg" + }, + { + "MIMode": "lldb", + "args": [], + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "name": "(lldb) Launch (macOS Debug)", + "preLaunchTask": "Compile (Debug macOS x64)", + "program": "${workspaceFolder}/bin/macosx-amd64-librw_gl3_glfw-oal/Debug/re3.app", + "request": "launch", + "setupCommands": [ + { + "description": "Enable pretty-printing for lldb", + "ignoreFailures": true, + "text": "-enable-pretty-printing" + } + ], + "stopAtEntry": false, + "targetArchitecture": "x64", + "type": "cppdbg" + }, + { + "MIMode": "lldb", + "args": [], + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "name": "(lldb) Launch (macOS Release)", + "preLaunchTask": "Compile (Release macOS x64)", + "program": "${workspaceFolder}/bin/macosx-amd64-librw_gl3_glfw-oal/Release/re3.app", + "request": "launch", + "setupCommands": [ + { + "description": "Enable pretty-printing for lldb", + "ignoreFailures": true, + "text": "-enable-pretty-printing" + } + ], + "stopAtEntry": false, + "targetArchitecture": "x64", + "type": "cppdbg" + } + ], + "version": "0.2.0" +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..98870ba --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,36 @@ +{ + "C_Cpp.default.cStandard": "gnu11", + "C_Cpp.default.cppStandard": "gnu++14", + "C_Cpp.default.includePath": [ + "src", + "src/animation", + "src/audio", + "src/audio/eax", + "src/audio/oal", + "src/buildings", + "src/collision", + "src/control", + "src/core", + "src/entities", + "src/extras", + "src/fakerw", + "src/math", + "src/modelinfo", + "src/objects", + "src/peds", + "src/renderer", + "src/rw", + "src/save/", + "src/skel/", + "src/skel/glfw", + "src/text", + "src/vehicles", + "src/weapons", + "vendor/librw" + ], + "C_Cpp.vcFormat.indent.gotoLabels": "leftmostColumn", + "C_Cpp.vcFormat.space.pointerReferenceAlignment": "right", + "cSpell.enabled": false, + "files.trimFinalNewlines": false, + "files.trimTrailingWhitespace": false +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..0f610d5 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,95 @@ +{ + "tasks": [ + { + "args": ["--with-librw", "gmake2"], + "command": "./premake5Linux", + "label": "Premake (Linux)", + "problemMatcher": "$gcc", + "type": "shell" + }, + { + "args": ["--with-librw", "gmake2"], + "command": "premake5", + "label": "Premake (macOS)", + "problemMatcher": "$gcc", + "type": "shell" + }, + { + "args": [ + "-j5", + "config=debug_linux-amd64-librw_gl3_glfw-oal", + "verbose=1" + ], + "command": "make", + "dependsOn": "Premake (Linux)", + "group": { + "isDefault": true, + "kind": "build" + }, + "label": "Compile (Debug Linux x64)", + "options": { + "cwd": "${workspaceFolder}/build" + }, + "problemMatcher": "$gcc", + "type": "shell" + }, + { + "args": [ + "-j5", + "config=release_linux-amd64-librw_gl3_glfw-oal", + "verbose=1" + ], + "command": "make", + "dependsOn": "Premake (Linux)", + "group": { + "isDefault": true, + "kind": "build" + }, + "label": "Compile (Release Linux x64)", + "options": { + "cwd": "${workspaceFolder}/build" + }, + "problemMatcher": "$gcc", + "type": "shell" + }, + { + "args": [ + "-j5", + "config=debug_macosx-amd64-librw_gl3_glfw-oal", + "verbose=1" + ], + "command": "make", + "dependsOn": "Premake (macOS)", + "group": { + "isDefault": true, + "kind": "build" + }, + "label": "Compile (Debug macOS x64)", + "options": { + "cwd": "${workspaceFolder}/build" + }, + "problemMatcher": "$gcc", + "type": "shell" + }, + { + "args": [ + "-j5", + "config=release_macosx-amd64-librw_gl3_glfw-oal", + "verbose=1" + ], + "command": "make", + "dependsOn": "Premake (macOS)", + "group": { + "isDefault": true, + "kind": "build" + }, + "label": "Compile (Release macOS x64)", + "options": { + "cwd": "${workspaceFolder}/build" + }, + "problemMatcher": "$gcc", + "type": "shell" + } + ], + "version": "2.0.0" +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fedf86a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,100 @@ +cmake_minimum_required(VERSION 3.14) + +set(EXECUTABLE re3) +set(PROJECT RE3) + +project(${EXECUTABLE} C CXX) +set(${PROJECT}_AUTHOR "${PROJECT} Team") +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +include(GetGitRevisionDescription) +get_git_head_revision(GIT_REFSPEC GIT_SHA1 "ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR") +message(STATUS "Building ${CMAKE_PROJECT_NAME} GIT SHA1: ${GIT_SHA1}") + +if(NINTENDO_SWITCH) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/nx") + include(NXFunctions) +endif() + +if(NOT COMMAND re3_platform_target) + function(re3_platform_target) + endfunction() +endif() + +if(WIN32) + set(${PROJECT}_AUDIOS "OAL" "MSS") +else() + set(${PROJECT}_AUDIOS "OAL") +endif() + +set(${PROJECT}_AUDIO "OAL" CACHE STRING "Audio") + +option(${PROJECT}_INSTALL "Enable installation of ${EXECUTABLE} + gamefiles" OFF) +option(${PROJECT}_WITH_OPUS "Build ${EXECUTABLE} with opus support" OFF) +option(${PROJECT}_WITH_LIBSNDFILE "Build ${EXECUTABLE} with libsndfile (instead of internal decoder)" OFF) + +set_property(CACHE ${PROJECT}_AUDIO PROPERTY STRINGS ${${PROJECT}_AUDIOS}) +message(STATUS "${PROJECT}_AUDIO = ${${PROJECT}_AUDIO} (choices=${${PROJECT}_AUDIOS})") +set("${PROJECT}_AUDIO_${${PROJECT}_AUDIO}" ON) +if(NOT ${PROJECT}_AUDIO IN_LIST ${PROJECT}_AUDIOS) + message(FATAL_ERROR "Illegal ${PROJECT}_AUDIO=${${PROJECT}_AUDIO}") +endif() + +option(${PROJECT}_VENDORED_LIBRW "Use vendored librw" ON) +if(${PROJECT}_VENDORED_LIBRW) + add_subdirectory(vendor/librw) +else() + find_package(librw REQUIRED) +endif() +add_subdirectory(src) + +if(${PROJECT}_INSTALL) + install(DIRECTORY gamefiles/ DESTINATION ".") + if(LIBRW_PLATFORM_NULL) + set(platform "-null") + elseif(LIBRW_PLATFORM_PS2) + set(platform "-ps2") + elseif(LIBRW_PLATFORM_GL3) + if(LIBRW_GL3_GFXLIB STREQUAL "GLFW") + set(platform "-gl3-glfw") + else() + set(platform "-gl3-sdl2") + endif() + elseif(LIBRW_PLATFORM_D3D9) + set(platform "-d3d9") + endif() + if(${PROJECT}_AUDIO_OAL) + set(audio "-oal") + elseif(${PROJECT}_AUDIO_MSS) + set(audio "-mss") + endif() + if(${PROJECT}_WITH_OPUS) + set(audio "${audio}-opus") + endif() + if(NOT LIBRW_PLATFORM_PS2) + if(WIN32) + set(os "-win") + elseif(APPLE) + set(os "-apple") + elseif(UNIX) + set(os "-linux") + elseif(NINTENDO_SWITCH) + set(os "-switch") + else() + set(compiler "-UNK") + message(WARNING "Unknown os. Created cpack package will be wrong. (override using cpack -P)") + endif() + endif() + + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}${platform}${audio}${os}${compiler}") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "GTA III reversed") + set(CPACK_PACKAGE_VENDOR "GTAModding") + # FIXME: missing license (https://github.com/GTAmodding/re3/issues/794) + # set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/LICENSE") + # set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") + set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_GENERATOR "ZIP") + include(CPack) +endif() diff --git a/CODING_STYLE.md b/CODING_STYLE.md new file mode 100644 index 0000000..b8be02b --- /dev/null +++ b/CODING_STYLE.md @@ -0,0 +1,107 @@ +# Coding style + +I started writing in [Plan 9 style](http://man.cat-v.org/plan_9/6/style), +but realize that this is not the most popular style, so I'm willing to compromise. +Try not to deviate too much so the code will look similar across the whole project. + +To give examples, these two styles (or anything in between) are fine: + +``` +type +functionname(args) +{ + if(a == b){ + s1; + s2; + }else{ + s3; + s4; + } + if(x != y) + s5; +} + +type functionname(args) +{ + if (a == b) { + s1; + s2; + } else { + s3; + s4; + } + if (x != y) + s5; +} +``` + +This one (or anything more extreme) is heavily discouraged: + +``` +type functionname ( args ) +{ + if ( a == b ) + { + s1; + s2; + } + else + { + s3; + s4; + } + if ( x != y ) + { + s5; + } +} +``` + +i.e. + +* Put the brace on the same line as control statements + +* Put the brace on the next line after function definitions and structs/classes + +* Put an `else` on the same line with the braces + +* Don't put braces around single statements + +* Put the function return type on a separate line + +* Indent with TABS + +As for the less cosmetic choices, here are some guidelines how the code should look: + +* Don't use magic numbers where the original source code would have had an enum or similar. +Even if you don't know the exact meaning it's better to call something `FOOBAR_TYPE_4` than just `4`, +since `4` will be used in other places and you can't easily see where else the enum value is used. + +* Don't just copy paste code from IDA, make it look nice + +* Use the right types. In particular: + + * don't use types like `__int16`, we have `int16` for that + + * don't use `unsigned`, we have typedefs for that + + * don't use `char` for anything but actual characters, use `int8`, `uint8` or `bool` + + * don't even think about using win32 types (`BYTE`, `WORD`, &c.) unless you're writing win32 specific code + + * declare pointers like `int *ptr;`, not `int* ptr;` + +* As for variable names, the original gta source code was not written in a uniform style, +but here are some observations: + + * many variables employ a form of hungarian notation, i.e.: + + * `m_` may be used for class member variables (mostly those that are considered private) + + * `ms_` for (mostly private) static members + + * `f` is a float, `i` or `n` is an integer, `b` is a boolean, `a` is an array + + * do *not* use `dw` for `DWORD` or so, we're not programming win32 + +* Generally, try to make the code look as if R* could have written it diff --git a/README b/README new file mode 100644 index 0000000..1909e8d --- /dev/null +++ b/README @@ -0,0 +1,7 @@ +In this repository you'll find the fully reversed source code for GTA 3 + +Building on Linux: +$ cd re3/ +$ premake5 --with-librw gmake2 +$ cd build/ +$ make config=release_linux-amd64-librw_gl3_glfw-oal diff --git a/autoconf/LICENSE.txt b/autoconf/LICENSE.txt new file mode 100644 index 0000000..eb1b172 --- /dev/null +++ b/autoconf/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2016 Blizzard Entertainment and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of Premake nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/autoconf/api.lua b/autoconf/api.lua new file mode 100644 index 0000000..064ea79 --- /dev/null +++ b/autoconf/api.lua @@ -0,0 +1,305 @@ +--- +-- Autoconfiguration. +-- Copyright (c) 2016 Blizzard Entertainment +-- Enhanced by re3 +--- +local p = premake +local autoconf = p.modules.autoconf +autoconf.cache = {} +autoconf.parameters = "" + + +--- +-- register autoconfigure api. +--- +p.api.register { + name = "autoconfigure", + scope = "config", + kind = "table" +} + +--- +-- Check for a particular include file. +-- +-- @cfg : Current config. +-- @variable : The variable to store the result, such as 'HAVE_STDINT_H'. +-- @filename : The header file to check for. +--- +function check_include(cfg, variable, filename) + local res = autoconf.cache_compile(cfg, variable, function () + p.outln('#include <' .. filename .. '>') + p.outln('int main(void) { return 0; }') + end) + + if res.value then + autoconf.set_value(cfg, variable, 1) + end +end + + +--- +-- Check for size of a particular type. +-- +-- @cfg : Current config. +-- @variable : The variable to use, such as 'SIZEOF_SIZE_T', this method will also add "'HAVE_' .. variable". +-- @type : The type to check. +-- @headers : An optional array of header files to include. +-- @defines : An optional array of defines to define. +--- +function check_type_size(cfg, variable, type, headers, defines) + check_include(cfg, 'HAVE_SYS_TYPES_H', 'sys/types.h') + check_include(cfg, 'HAVE_STDINT_H', 'stdint.h') + check_include(cfg, 'HAVE_STDDEF_H', 'stddef.h') + + local res = autoconf.cache_compile(cfg, variable .. cfg.platform, + function () + if cfg.autoconf['HAVE_SYS_TYPES_H'] then + p.outln('#include ') + end + + if cfg.autoconf['HAVE_STDINT_H'] then + p.outln('#include ') + end + + if cfg.autoconf['HAVE_STDDEF_H'] then + p.outln('#include ') + end + + autoconf.include_defines(defines) + autoconf.include_headers(headers) + p.outln("") + p.outln("#define SIZE (sizeof(" .. type .. "))") + p.outln("char info_size[] = {'I', 'N', 'F', 'O', ':', 's','i','z','e','[',") + p.outln(" ('0' + ((SIZE / 10000)%10)),") + p.outln(" ('0' + ((SIZE / 1000)%10)),") + p.outln(" ('0' + ((SIZE / 100)%10)),") + p.outln(" ('0' + ((SIZE / 10)%10)),") + p.outln(" ('0' + (SIZE %10)),") + p.outln(" ']', '\\0'};") + p.outln("") + p.outln("int main(int argc, char *argv[]) {") + p.outln(" int require = 0;") + p.outln(" require += info_size[argc];") + p.outln(" (void)argv;") + p.outln(" return require;") + p.outln("}") + end, + function (e) + -- if the compile step succeeded, we should have a binary with 'INFO:size[*****]' + -- somewhere in there. + local content = io.readfile(e.binary) + if content then + local size = string.find(content, 'INFO:size') + if size then + e.size = tonumber(string.sub(content, size+10, size+14)) + end + end + end + ) + + if res.size then + autoconf.set_value(cfg, 'HAVE_' .. variable, 1) + autoconf.set_value(cfg, variable, res.size) + end +end + + +--- +-- Check if the given struct or class has the specified member variable +-- +-- @cfg : current config. +-- @variable : variable to store the result. +-- @type : the name of the struct or class you are interested in +-- @member : the member which existence you want to check +-- @headers : an optional array of header files to include. +-- @defines : An optional array of defines to define. +--- +function check_struct_has_member(cfg, variable, type, member, headers, defines) + local res = autoconf.cache_compile(cfg, variable, function () + autoconf.include_defines(defines) + autoconf.include_headers(headers) + p.outln('int main(void) {') + p.outln(' (void)sizeof(((' .. type .. '*)0)->' .. member ..');') + p.outln(' return 0;') + p.outln('}') + end) + + if res.value then + autoconf.set_value(cfg, variable, 1) + end +end + + +--- +-- Check if a symbol exists as a function, variable, or macro +-- +-- @cfg : current config. +-- @variable : variable to store the result. +-- @symbol : The symbol to check for. +-- @headers : an optional array of header files to include. +-- @defines : An optional array of defines to define. +--- +function check_symbol_exists(cfg, variable, symbol, headers, defines) + local h = headers + local res = autoconf.cache_compile(cfg, variable, function () + autoconf.include_defines(defines) + autoconf.include_headers(headers) + p.outln('int main(int argc, char** argv) {') + p.outln(' (void)argv;') + p.outln('#ifndef ' .. symbol) + p.outln(' return ((int*)(&' .. symbol .. '))[argc];') + p.outln('#else') + p.outln(' (void)argc;') + p.outln(' return 0;') + p.outln('#endif') + p.outln('}') + end) + + if res.value then + autoconf.set_value(cfg, variable, 1) + end +end + + +--- +-- try compiling a piece of c/c++ +--- +function autoconf.try_compile(cfg, cpp) + local ts = autoconf.toolset(cfg) + if ts then + return ts.try_compile(cfg, cpp, autoconf.parameters) + else + p.warnOnce('autoconf', 'no toolset found, autoconf always failing.') + end +end + + +function autoconf.cache_compile(cfg, entry, func, post) + if not autoconf.cache[entry] then + local cpp = p.capture(func) + local res = autoconf.try_compile(cfg, cpp) + if res then + local e = { binary = res, value = true } + if post then + post(e) + end + autoconf.cache[entry] = e + else + autoconf.cache[entry] = { } + end + end + return autoconf.cache[entry] +end + + +--- +-- get the current configured toolset, or the default. +--- +function autoconf.toolset(cfg) + local ts = p.config.toolset(cfg) + if not ts then + local tools = { + -- Actually we always return nil on msc. see msc.lua + ['vs2010'] = p.tools.msc, + ['vs2012'] = p.tools.msc, + ['vs2013'] = p.tools.msc, + ['vs2015'] = p.tools.msc, + ['vs2017'] = p.tools.msc, + ['vs2019'] = p.tools.msc, + ['gmake'] = premake.tools.gcc, + ['gmake2'] = premake.tools.gcc, + ['codelite'] = premake.tools.gcc, + ['xcode4'] = premake.tools.clang, + } + ts = tools[_ACTION] + end + return ts +end + + +--- +-- store the value of the variable in the configuration +--- +function autoconf.set_value(cfg, variable, value) + cfg.autoconf[variable] = value +end + + +--- +-- write the cfg.autoconf table to the file +--- +function autoconf.writefile(cfg, filename) + if cfg.autoconf then + local file = io.open(filename, "w+") + for variable, value in pairs(cfg.autoconf) do + file:write('#define ' .. variable .. ' ' .. tostring(value) .. (_eol or '\n')) + end + file:close() + end +end + + +--- +-- Utility method to add a table of headers. +--- +function autoconf.include_headers(headers) + if headers ~= nil then + if type(headers) == "table" then + for _, v in ipairs(headers) do + p.outln('#include <' .. v .. '>') + end + else + p.outln('#include <' .. headers .. '>') + end + end +end + +function autoconf.include_defines(defines) + if defines ~= nil then + if type(defines) == "table" then + for _, v in ipairs(defines) do + p.outln('#define ' .. v) + end + else + p.outln('#define ' .. defines) + end + end +end + +--- +-- attach ourselfs to the running action. +--- +p.override(p.action, 'call', function (base, name) + local a = p.action.get(name) + + -- store the old callback. + local onBaseProject = a.onProject or a.onproject + + -- override it with our own. + a.onProject = function(prj) + -- go through each configuration, and call the setup configuration methods. + for cfg in p.project.eachconfig(prj) do + cfg.autoconf = {} + if cfg.autoconfigure then + verbosef('Running auto config steps for "%s/%s".', prj.name, cfg.name) + for file, func in pairs(cfg.autoconfigure) do + func(cfg) + + if not (file ~= "dontWrite") then + os.mkdir(cfg.objdir) + local filename = path.join(cfg.objdir, file) + autoconf.writefile(cfg, filename) + end + end + end + end + + -- then call the old onProject. + if onBaseProject then + onBaseProject(prj) + end + end + + -- now call the original action.call methods + base(name) +end) diff --git a/autoconf/autoconf.lua b/autoconf/autoconf.lua new file mode 100644 index 0000000..6c99f9d --- /dev/null +++ b/autoconf/autoconf.lua @@ -0,0 +1,18 @@ +--- +-- Autoconfiguration. +-- Copyright (c) 2016 Blizzard Entertainment +--- + local p = premake + + if not premake.modules.autoconf then + p.modules.autoconf = {} + p.modules.autoconf._VERSION = p._VERSION + + verbosef('Loading autoconf module...') + include('api.lua') + include('msc.lua') + include('clang.lua') + include('gcc.lua') + end + + return p.modules.autoconf diff --git a/autoconf/clang.lua b/autoconf/clang.lua new file mode 100644 index 0000000..fdb5f40 --- /dev/null +++ b/autoconf/clang.lua @@ -0,0 +1,27 @@ +--- +-- Autoconfiguration. +-- Copyright (c) 2016 Blizzard Entertainment +--- +local p = premake +local clang = p.tools.clang + +function clang.try_compile(cfg, text, parameters) + -- write the text to a temporary file. + local cppFile = path.join(cfg.objdir, "temp.cpp") + if not io.writefile(cppFile, text) then + return nil + end + + if parameters == nil then + parameters = "" + end + + local outFile = path.join(cfg.objdir, "temp.out") + + -- compile that text file. + if os.execute('clang "' .. cppFile .. '" ' .. parameters .. ' -o "' .. outFile ..'" &> /dev/null') then + return outFile + else + return nil + end +end diff --git a/autoconf/gcc.lua b/autoconf/gcc.lua new file mode 100644 index 0000000..3452013 --- /dev/null +++ b/autoconf/gcc.lua @@ -0,0 +1,27 @@ +--- +-- Autoconfiguration. +-- Copyright (c) 2016 Blizzard Entertainment +--- +local p = premake +local gcc = p.tools.gcc + +function gcc.try_compile(cfg, text, parameters) + -- write the text to a temporary file. + local cppFile = path.join(cfg.objdir, "temp.cpp") + if not io.writefile(cppFile, text) then + return nil + end + + if parameters == nil then + parameters = "" + end + + local outFile = path.join(cfg.objdir, "temp.out") + + -- compile that text file. + if os.execute('gcc "' .. cppFile .. '" ' .. parameters .. ' -o "' .. outFile ..'" &> /dev/null') then + return outFile + else + return nil + end +end diff --git a/autoconf/msc.lua b/autoconf/msc.lua new file mode 100644 index 0000000..b96a82e --- /dev/null +++ b/autoconf/msc.lua @@ -0,0 +1,62 @@ +--- +-- Autoconfiguration. +-- Copyright (c) 2016 Blizzard Entertainment +--- +local p = premake +local msc = p.tools.msc + +-- "parameters" is unused, matter of fact this file is unused - re3 +function msc.try_compile(cfg, text, parameters) + + return nil +--[[ + -- write the text to a temporary file. + local cppFile = path.join(cfg.objdir, "temp.cpp") + if not io.writefile(cppFile, text) then + return nil + end + + -- write out a batch file. + local batch = p.capture(function () + p.outln('@echo off') + p.outln('SET mypath=%~dp0') + p.outln('pushd %mypath%') + + local map = { + vs2010 = 'VS100COMNTOOLS', + vs2012 = 'VS110COMNTOOLS', + vs2013 = 'VS120COMNTOOLS', + vs2015 = 'VS140COMNTOOLS', + vs2017 = 'VS141COMNTOOLS', + vs2019 = 'VS142COMNTOOLS', + } + + local a = map[_ACTION] + if a then + a = path.translate(os.getenv(a), '/') + a = path.join(a, '../../VC/vcvarsall.bat') + + if cfg.platform == 'x86' then + p.outln('call "' .. a .. '" > NUL') + else + p.outln('call "' .. a .. '" amd64 > NUL') + end + + p.outln('cl.exe /nologo temp.cpp > NUL') + else + error('Unsupported Visual Studio version: ' .. _ACTION) + end + end) + + local batchFile = path.join(cfg.objdir, "compile.bat") + if not io.writefile(batchFile, batch) then + return nil + end + + if os.execute(batchFile) then + return path.join(cfg.objdir, "temp.exe") + else + return nil + end +--]] +end diff --git a/cmake/FindMilesSDK.cmake b/cmake/FindMilesSDK.cmake new file mode 100644 index 0000000..dcf4da3 --- /dev/null +++ b/cmake/FindMilesSDK.cmake @@ -0,0 +1,34 @@ +# - Find Miles SDK +# Find the Miles SDK header + import library +# +# MilesSDK_INCLUDE_DIR - Where to find mss.h +# MilesSDK_LIBRARIES - List of libraries when using MilesSDK. +# MilesSDK_FOUND - True if Miles SDK found. +# MilesSDK::MilesSDK - Imported library of Miles SDK + +find_path(MilesSDK_INCLUDE_DIR mss.h + PATHS "${MilesSDK_DIR}" + PATH_SUFFIXES include +) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_miles_sdk_libname mss64) +else() + set(_miles_sdk_libname mss32) +endif() + +find_library(MilesSDK_LIBRARIES NAMES ${_miles_sdk_libname} + PATHS "${MilesSDK_DIR}" + PATH_SUFFIXES lib +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MilesSDK DEFAULT_MSG MilesSDK_LIBRARIES MilesSDK_INCLUDE_DIR) + +if(NOT TARGET MilesSDK::MilesSDK) + add_library(MilesSDK::MilesSDK UNKNOWN IMPORTED) + set_target_properties(MilesSDK::MilesSDK PROPERTIES + IMPORTED_LOCATION "${MilesSDK_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${MilesSDK_INCLUDE_DIR}" + ) +endif() diff --git a/cmake/FindSndFile.cmake b/cmake/FindSndFile.cmake new file mode 100644 index 0000000..5381af4 --- /dev/null +++ b/cmake/FindSndFile.cmake @@ -0,0 +1,67 @@ +# Found on http://hg.kvats.net +# +# - Try to find libsndfile +# +# Once done this will define +# +# SNDFILE_FOUND - system has libsndfile +# SNDFILE_INCLUDE_DIRS - the libsndfile include directory +# SNDFILE_LIBRARIES - Link these to use libsndfile +# SNDFILE_CFLAGS - Compile options to use libsndfile +# SndFile::SndFile - Imported library of libsndfile +# +# Copyright (C) 2006 Wengo +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_search_module(PKG_SNDFILE "sndfile") +endif() + +find_path(SNDFILE_INCLUDE_DIR + NAMES + sndfile.h + HINTS + ${PKG_SNDFILE_INCLUDE_DIRS} + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + +find_library(SNDFILE_LIBRARY + NAMES + sndfile + HINTS + ${PKG_SNDFILE_LIBRARIES} + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib +) + +set(SNDFILE_CFLAGS "${PKG_SNDFILE_CFLAGS_OTHER}" CACHE STRING "CFLAGS of libsndfile") + +set(SNDFILE_INCLUDE_DIRS "${SNDFILE_INCLUDE_DIR}") +set(SNDFILE_LIBRARIES "${SNDFILE_LIBRARY}") + +if(SNDFILE_INCLUDE_DIRS AND SNDFILE_LIBRARIES) + set(SNDFILE_FOUND TRUE) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SndFile DEFAULT_MSG SNDFILE_INCLUDE_DIRS SNDFILE_LIBRARIES) + +if(NOT TARGET SndFile::SndFile) + add_library(__SndFile INTERFACE) + target_compile_options(__SndFile INTERFACE ${SNDFILE_CFLAGS}) + target_include_directories(__SndFile INTERFACE ${SNDFILE_INCLUDE_DIRS}) + target_link_libraries(__SndFile INTERFACE ${SNDFILE_LIBRARIES}) + add_library(SndFile::SndFile ALIAS __SndFile) +endif() diff --git a/cmake/Findmpg123.cmake b/cmake/Findmpg123.cmake new file mode 100644 index 0000000..aa59ad8 --- /dev/null +++ b/cmake/Findmpg123.cmake @@ -0,0 +1,38 @@ +# - Find mpg123 +# Find the native mpg123 includes and library +# +# mpg123_INCLUDE_DIR - Where to find mpg123.h +# mpg123_LIBRARIES - List of libraries when using mpg123. +# mpg123_CFLAGS - Compile options to use mpg123 +# mpg123_FOUND - True if mpg123 found. +# MPG123::libmpg123 - Imported library of libmpg123 + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_search_module(PKG_MPG123 mpg123) +endif() + +find_path(mpg123_INCLUDE_DIR mpg123.h + HINTS ${PKG_MPG123_INCLUDE_DIRS} + PATHS "${mpg123_DIR}" + PATH_SUFFIXES include +) + +find_library(mpg123_LIBRARIES NAMES mpg123 mpg123-0 libmpg123-0 + HINTS ${PKG_MPG123_LIBRARIES} + PATHS "${mpg123_DIR}" + PATH_SUFFIXES lib +) + +set(mpg123_CFLAGS "${PKG_MPG123_CFLAGS_OTHER}" CACHE STRING "CFLAGS of mpg123") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(mpg123 DEFAULT_MSG mpg123_LIBRARIES mpg123_INCLUDE_DIR) + +if(NOT TARGET MPG123::libmpg123) + add_library(__libmpg123 INTERFACE) + target_compile_options(__libmpg123 INTERFACE ${mpg123_CFLAGS}) + target_include_directories(__libmpg123 INTERFACE ${mpg123_INCLUDE_DIR}) + target_link_libraries(__libmpg123 INTERFACE ${mpg123_LIBRARIES}) + add_library(MPG123::libmpg123 ALIAS __libmpg123) +endif() diff --git a/cmake/Findopusfile.cmake b/cmake/Findopusfile.cmake new file mode 100644 index 0000000..faae764 --- /dev/null +++ b/cmake/Findopusfile.cmake @@ -0,0 +1,64 @@ +# - Try to find opusfile +# +# Once done this will define +# +# OPUSFILE_FOUND - system has opusfile +# OPUSFILE_INCLUDE_DIRS - the opusfile include directories +# OPUSFILE_LIBRARIES - Link these to use opusfile +# OPUSFILE_CFLAGS - Compile options to use opusfile +# opusfile::opusfile - Imported library of opusfile +# + +# FIXME: opusfile does not ship an official opusfile cmake script, +# rename this file/variables/target when/if it has. + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_search_module(PKG_OPUSFILE "opusfile") +endif() + +find_path(OPUSFILE_INCLUDE_DIR + NAMES + opusfile.h + PATH_SUFFIXES + opusfile + HINTS + ${PKG_OPUSFILE_INCLUDE_DIRS} + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + +find_library(OPUSFILE_LIBRARY + NAMES + opusfile + HINTS + ${PKG_OPUSFILE_LIBRARIES} + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib +) + +set(OPUSFILE_CFLAGS "${PKG_OPUSFILE_CFLAGS_OTHER}" CACHE STRING "CFLAGS of opusfile") + +set(OPUSFILE_INCLUDE_DIRS "${OPUSFILE_INCLUDE_DIR}") +set(OPUSFILE_LIBRARIES "${OPUSFILE_LIBRARY}") + +if (OPUSFILE_INCLUDE_DIRS AND OPUSFILE_LIBRARIES) +set(OPUSFILE_FOUND TRUE) +endif (OPUSFILE_INCLUDE_DIRS AND OPUSFILE_LIBRARIES) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(opusfile DEFAULT_MSG OPUSFILE_INCLUDE_DIRS OPUSFILE_LIBRARIES) + +if(NOT TARGET opusfile::opusfile) + add_library(__opusfile INTERFACE) + target_compile_options(__opusfile INTERFACE ${OPUSFILE_CFLAGS}) + target_include_directories(__opusfile INTERFACE ${OPUSFILE_INCLUDE_DIRS}) + target_link_libraries(__opusfile INTERFACE ${OPUSFILE_LIBRARIES}) + add_library(opusfile::opusfile ALIAS __opusfile) +endif() diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake new file mode 100644 index 0000000..87f691a --- /dev/null +++ b/cmake/GetGitRevisionDescription.cmake @@ -0,0 +1,284 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_describe_working_tree( [ ...]) +# +# Returns the results of git describe on the working tree (--dirty option), +# and adjusting the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# git_local_changes() +# +# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. +# Uses the return code of "git diff-index --quiet HEAD --". +# Does not regard untracked files. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2020 Ryan Pavlik +# http://academic.cleardefinition.com +# +# Copyright 2009-2013, Iowa State University. +# Copyright 2013-2020, Ryan Pavlik +# Copyright 2013-2020, Contributors +# SPDX-License-Identifier: BSL-1.0 +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +# Function _git_find_closest_git_dir finds the next closest .git directory +# that is part of any directory in the path defined by _start_dir. +# The result is returned in the parent scope variable whose name is passed +# as variable _git_dir_var. If no .git directory can be found, the +# function returns an empty string via _git_dir_var. +# +# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and +# neither foo nor bar contain a file/directory .git. This wil return +# C:/bla/.git +# +function(_git_find_closest_git_dir _start_dir _git_dir_var) + set(cur_dir "${_start_dir}") + set(git_dir "${_start_dir}/.git") + while(NOT EXISTS "${git_dir}") + # .git dir not found, search parent directories + set(git_previous_parent "${cur_dir}") + get_filename_component(cur_dir ${cur_dir} DIRECTORY) + if(cur_dir STREQUAL git_previous_parent) + # We have reached the root directory, we are not in git + set(${_git_dir_var} + "" + PARENT_SCOPE) + return() + endif() + set(git_dir "${cur_dir}/.git") + endwhile() + set(${_git_dir_var} + "${git_dir}" + PARENT_SCOPE) +endfunction() + +function(get_git_head_revision _refspecvar _hashvar) + _git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR) + + if("${ARGN}" STREQUAL "ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR") + set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR TRUE) + else() + set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR FALSE) + endif() + if(NOT "${GIT_DIR}" STREQUAL "") + file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_SOURCE_DIR}" + "${GIT_DIR}") + if("${_relative_to_source_dir}" MATCHES "[.][.]" AND NOT ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR) + # We've gone above the CMake root dir. + set(GIT_DIR "") + endif() + endif() + if("${GIT_DIR}" STREQUAL "") + set(${_refspecvar} + "GITDIR-NOTFOUND" + PARENT_SCOPE) + set(${_hashvar} + "GITDIR-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + # Check if the current source dir is a git submodule or a worktree. + # In both cases .git is a file instead of a directory. + # + if(NOT IS_DIRECTORY ${GIT_DIR}) + # The following git command will return a non empty string that + # points to the super project working tree if the current + # source dir is inside a git submodule. + # Otherwise the command will return an empty string. + # + execute_process( + COMMAND "${GIT_EXECUTABLE}" rev-parse + --show-superproject-working-tree + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT "${out}" STREQUAL "") + # If out is empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE + ${submodule}) + string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} + ABSOLUTE) + set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") + else() + # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree + file(READ ${GIT_DIR} worktree_ref) + # The .git directory contains a path to the worktree information directory + # inside the parent git repo of the worktree. + # + string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir + ${worktree_ref}) + string(STRIP ${git_worktree_dir} git_worktree_dir) + _git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR) + set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") + endif() + else() + set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") + endif() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${HEAD_SOURCE_FILE}") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} + "${HEAD_REF}" + PARENT_SCOPE) + set(${_hashvar} + "${HEAD_HASH}" + PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} + "HEAD-HASH-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process( + COMMAND "${GIT_EXECUTABLE}" describe --tags --always ${hash} ${ARGN} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_describe_working_tree _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + execute_process( + COMMAND "${GIT_EXECUTABLE}" describe --dirty ${ARGN} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_get_exact_tag _var) + git_describe(out --exact-match ${ARGN}) + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_local_changes _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} + "HEAD-HASH-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + execute_process( + COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(res EQUAL 0) + set(${_var} + "CLEAN" + PARENT_SCOPE) + else() + set(${_var} + "DIRTY" + PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in new file mode 100644 index 0000000..116efc4 --- /dev/null +++ b/cmake/GetGitRevisionDescription.cmake.in @@ -0,0 +1,43 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright 2009-2012, Iowa State University +# Copyright 2011-2015, Contributors +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# SPDX-License-Identifier: BSL-1.0 + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + else() + configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) + file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) + if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") + set(HEAD_HASH "${CMAKE_MATCH_1}") + endif() + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/cmake/nx/NXFunctions.cmake b/cmake/nx/NXFunctions.cmake new file mode 100644 index 0000000..cf3f974 --- /dev/null +++ b/cmake/nx/NXFunctions.cmake @@ -0,0 +1,38 @@ +if(NOT COMMAND nx_generate_nacp) + message(FATAL_ERROR "The `nx_generate_nacp` cmake command is not available. Please use an appropriate Nintendo Switch toolchain.") +endif() + +if(NOT COMMAND nx_create_nro) + message(FATAL_ERROR "The `nx_create_nro` cmake command is not available. Please use an appropriate Nintendo Switch toolchain.") +endif() + +set(CMAKE_EXECUTABLE_SUFFIX ".elf") + +function(re3_platform_target TARGET) + cmake_parse_arguments(RPT "INSTALL" "" "" ${ARGN}) + + get_target_property(TARGET_TYPE "${TARGET}" TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + nx_generate_nacp(${TARGET}.nacp + NAME "${TARGET}" + AUTHOR "${${PROJECT}_AUTHOR}" + VERSION "1.0.0-${GIT_SHA1}" + ) + + nx_create_nro(${TARGET} + NACP ${TARGET}.nacp + ICON "${PROJECT_SOURCE_DIR}/res/images/logo_256.jpg" + ) + + if(${PROJECT}_INSTALL AND RPT_INSTALL) + get_target_property(TARGET_OUTPUT_NAME ${TARGET} OUTPUT_NAME) + if(NOT TARGET_OUTPUT_NAME) + set(TARGET_OUTPUT_NAME "${TARGET}") + endif() + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_OUTPUT_NAME}.nro" + DESTINATION "." + ) + endif() + endif() +endfunction() diff --git a/codewarrior/re3.mcp.xml b/codewarrior/re3.mcp.xml new file mode 100644 index 0000000..9a41471 --- /dev/null +++ b/codewarrior/re3.mcp.xml @@ -0,0 +1,15378 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + + Debug + + + + UserSourceTrees + + + AlwaysSearchUserPathsfalse + InterpretDOSAndUnixPathstrue + RequireFrameworkStyleIncludesfalse + UserSearchPaths + + SearchPath + Path + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\animation + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\audio + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\buildings + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\collision + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\control + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\core + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\entities + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\math + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\modelinfo + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\objects + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\peds + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\renderer + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\rw + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\save + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\skel + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\text + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\vehicles + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\weapons + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\vendor\milessdk\lib + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\vendor\milessdk\include + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\sdk\dx8sdk\Lib + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\sdk\rwsdk\lib\d3d8\release + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\sdk\rwsdk\include\d3d8 + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\extras + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SystemSearchPaths + + SearchPath + Path..\sdk\rwsdk\include\d3d8 + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\sdk\dx8sdk\Include + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + PathWin32-x86 Support\Headers\ + PathFormatWindows + PathRootCodeWarrior + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + PathWin32-x86 Support\Libraries\ + PathFormatWindows + PathRootCodeWarrior + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + PathMSL + PathFormatWindows + PathRootCodeWarrior + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\audio\eax + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + + + MWRuntimeSettings_WorkingDirectory + MWRuntimeSettings_CommandLine + MWRuntimeSettings_HostApplication + Path + PathFormatGeneric + PathRootAbsolute + + MWRuntimeSettings_EnvVars + + + LinkerWin32 x86 Linker + PreLinker + PostLinker + TargetnameDebug + OutputDirectory + Path + PathFormatWindows + PathRootProject + + SaveEntriesUsingRelativePathsfalse + + + FileMappings + + FileTypeTEXT + FileExtension.c + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.c++ + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.cc + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.cp + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.cpp + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.def + Compiler + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.h + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMaketrue + + + FileTypeTEXT + FileExtension.p + CompilerMW Pascal x86 + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.pas + CompilerMW Pascal x86 + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.pch + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompiletrue + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.pch++ + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompiletrue + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.ppu + CompilerMW Pascal x86 + EditLanguage + Precompiletrue + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.rc + CompilerMW WinRC + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.res + CompilerWinRes Import + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileExtension.doc + Compiler + EditLanguage + Precompilefalse + Launchabletrue + ResourceFilefalse + IgnoredByMaketrue + + + FileExtension.lib + CompilerLib Import x86 + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileExtension.obj + CompilerObj Import x86 + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileExtension.res + CompilerWinRes Import + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + + + CacheModDatestrue + ActivateBrowsertrue + DumpBrowserInfofalse + CacheSubprojectstrue + UseThirdPartyDebuggerfalse + DebuggerAppPath + Path + PathFormatGeneric + PathRootAbsolute + + DebuggerCmdLineArgs + DebuggerWorkingDir + Path + PathFormatGeneric + PathRootAbsolute + + + + LogSystemMessagesfalse + AutoTargetDLLsfalse + StopAtWatchpointstrue + PauseWhileRunningfalse + PauseInterval5 + PauseUIFlags0 + AltExePath + Path + PathFormatGeneric + PathRootAbsolute + + StopAtTempBPOnLaunchtrue + CacheSymbolicstrue + TempBPFunctionNamemain + TempBPType0 + + + Enabledfalse + ConnectionName + DownloadPath + LaunchRemoteAppfalse + RemoteAppPath + + + OtherExecutables + + + CustomColor1 + Red0 + Green32767 + Blue0 + + CustomColor2 + Red0 + Green32767 + Blue0 + + CustomColor3 + Red0 + Green32767 + Blue0 + + CustomColor4 + Red0 + Green32767 + Blue0 + + + + MWCodeGen_X86_processorPentiumII + MWCodeGen_X86_alignmentbytes8 + MWCodeGen_X86_exceptionsZeroOverhead + MWCodeGen_X86_extinst_mmx0 + MWCodeGen_X86_extinst_3dnow0 + MWCodeGen_X86_use_mmx_3dnow_convention0 + MWCodeGen_X86_machinecodelisting0 + MWCodeGen_X86_intrinsics0 + MWCodeGen_X86_syminfo0 + MWCodeGen_X86_codeviewinfo1 + MWCodeGen_X86_extinst_cmov_fcomi0 + MWCodeGen_X86_extinst_sse0 + + + MWDebugger_X86_Exceptions + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + + PDisasmX86_showHeaderstrue + PDisasmX86_showSymTabtrue + PDisasmX86_showCodetrue + PDisasmX86_showSourcefalse + PDisasmX86_showHextrue + PDisasmX86_showRelocationtrue + PDisasmX86_showCommentsfalse + PDisasmX86_showDebugfalse + PDisasmX86_showExceptionsfalse + PDisasmX86_showDatatrue + PDisasmX86_showRawfalse + PDisasmX86_verbosefalse + + + MWFrontEnd_C_cplusplus1 + MWFrontEnd_C_checkprotos0 + MWFrontEnd_C_arm0 + MWFrontEnd_C_trigraphs0 + MWFrontEnd_C_onlystdkeywords0 + MWFrontEnd_C_enumsalwaysint1 + MWFrontEnd_C_mpwpointerstyle0 + MWFrontEnd_C_prefixname + MWFrontEnd_C_ansistrict0 + MWFrontEnd_C_mpwcnewline0 + MWFrontEnd_C_wchar_type1 + MWFrontEnd_C_enableexceptions1 + MWFrontEnd_C_dontreusestrings0 + MWFrontEnd_C_poolstrings1 + MWFrontEnd_C_dontinline1 + MWFrontEnd_C_useRTTI1 + MWFrontEnd_C_multibyteaware1 + MWFrontEnd_C_unsignedchars0 + MWFrontEnd_C_autoinline0 + MWFrontEnd_C_booltruefalse1 + MWFrontEnd_C_direct_to_som0 + MWFrontEnd_C_som_env_check0 + MWFrontEnd_C_alwaysinline0 + MWFrontEnd_C_inlinelevel0 + MWFrontEnd_C_ecplusplus0 + MWFrontEnd_C_objective_c0 + MWFrontEnd_C_defer_codegen0 + + + MWLinker_X86_entrypointusageDefault + MWLinker_X86_entrypoint + MWLinker_X86_subsystemWinGUI + MWLinker_X86_subsysmajorid4 + MWLinker_X86_subsysminorid0 + MWLinker_X86_usrmajorid0 + MWLinker_X86_usrminorid0 + MWLinker_X86_commandfile + MWLinker_X86_generatemap0 + MWLinker_X86_linksym0 + MWLinker_X86_linkCV1 + + + MWProject_X86_typeApplication + MWProject_X86_outfileDebug\gta3.exe + MWProject_X86_baseaddress4194304 + MWProject_X86_maxstacksize1024 + MWProject_X86_minstacksize4 + MWProject_X86_size1024 + MWProject_X86_minsize4 + MWProject_X86_importlib + + + MWWarning_C_warn_illpragma0 + MWWarning_C_warn_emptydecl0 + MWWarning_C_warn_possunwant1 + MWWarning_C_warn_unusedvar1 + MWWarning_C_warn_unusedarg0 + MWWarning_C_warn_extracomma1 + MWWarning_C_pedantic0 + MWWarning_C_warningerrors0 + MWWarning_C_warn_hidevirtual1 + MWWarning_C_warn_implicitconv0 + MWWarning_C_warn_notinlined0 + MWWarning_C_warn_structclass0 + + + MWWinRC_prefixnameResourcePrefix.h + + + GlobalOptimizer_X86__optimizationlevelLevel0 + GlobalOptimizer_X86__optforSize + + + + Name + Comdlg32.lib + MacOS + Library + + + + Name + Gdi32.lib + MacOS + Library + + + + Name + Kernel32.lib + MacOS + Library + + + + Name + User32.lib + MacOS + Library + + + + Name + MSL_All_x86_D.lib + MacOS + Unknown + Debug + + + Name + AnimationId.h + Windows + Text + + + + Name + AnimBlendAssocGroup.cpp + Windows + Text + Debug + + + Name + AnimBlendAssocGroup.h + Windows + Text + + + + Name + AnimBlendAssociation.cpp + Windows + Text + Debug + + + Name + AnimBlendAssociation.h + Windows + Text + + + + Name + AnimBlendClumpData.cpp + Windows + Text + Debug + + + Name + AnimBlendClumpData.h + Windows + Text + + + + Name + AnimBlendHierarchy.cpp + Windows + Text + Debug + + + Name + AnimBlendHierarchy.h + Windows + Text + + + + Name + AnimBlendList.h + Windows + Text + + + + Name + AnimBlendNode.cpp + Windows + Text + Debug + + + Name + AnimBlendNode.h + Windows + Text + + + + Name + AnimBlendSequence.cpp + Windows + Text + Debug + + + Name + AnimBlendSequence.h + Windows + Text + + + + Name + AnimManager.cpp + Windows + Text + Debug + + + Name + AnimManager.h + Windows + Text + + + + Name + Bones.cpp + Windows + Text + Debug + + + Name + Bones.h + Windows + Text + + + + Name + CutsceneMgr.cpp + Windows + Text + Debug + + + Name + CutsceneMgr.h + Windows + Text + + + + Name + FrameUpdate.cpp + Windows + Text + Debug + + + Name + RpAnimBlend.cpp + Windows + Text + Debug + + + Name + RpAnimBlend.h + Windows + Text + + + + Name + audio_enums.h + Windows + Text + + + + Name + AudioCollision.cpp + Windows + Text + Debug + + + Name + AudioCollision.h + Windows + Text + + + + Name + AudioLogic.cpp + Windows + Text + + + + Name + AudioManager.cpp + Windows + Text + Debug + + + Name + AudioManager.h + Windows + Text + + + + Name + AudioSamples.h + Windows + Text + + + + Name + AudioScriptObject.cpp + Windows + Text + Debug + + + Name + AudioScriptObject.h + Windows + Text + + + + Name + DMAudio.cpp + Windows + Text + Debug + + + Name + DMAudio.h + Windows + Text + + + + Name + MusicManager.cpp + Windows + Text + Debug + + + Name + MusicManager.h + Windows + Text + + + + Name + PolRadio.cpp + Windows + Text + Debug + + + Name + PolRadio.h + Windows + Text + + + + Name + sampman.h + Windows + Text + + + + Name + sampman_miles.cpp + Windows + Text + Debug + + + Name + soundlist.h + Windows + Text + + + + Name + eax.h + Windows + Text + + + + Name + eax-util.cpp + Windows + Text + Debug + + + Name + eax-util.h + Windows + Text + + + + Name + Building.cpp + Windows + Text + Debug + + + Name + Building.h + Windows + Text + + + + Name + Solid.h + Windows + Text + + + + Name + Treadable.cpp + Windows + Text + Debug + + + Name + Treadable.h + Windows + Text + + + + Name + ColBox.cpp + Windows + Text + Debug + + + Name + ColBox.h + Windows + Text + + + + Name + ColLine.cpp + Windows + Text + Debug + + + Name + ColLine.h + Windows + Text + + + + Name + Collision.cpp + Windows + Text + Debug + + + Name + Collision.h + Windows + Text + + + + Name + ColModel.cpp + Windows + Text + Debug + + + Name + ColModel.h + Windows + Text + + + + Name + ColPoint.cpp + Windows + Text + Debug + + + Name + ColPoint.h + Windows + Text + + + + Name + ColSphere.cpp + Windows + Text + Debug + + + Name + ColSphere.h + Windows + Text + + + + Name + ColTriangle.cpp + Windows + Text + Debug + + + Name + ColTriangle.h + Windows + Text + + + + Name + CompressedVector.h + Windows + Text + + + + Name + TempColModels.cpp + Windows + Text + Debug + + + Name + TempColModels.h + Windows + Text + + + + Name + VuCollision.cpp + Windows + Text + Debug + + + Name + VuCollision.h + Windows + Text + + + + Name + AutoPilot.cpp + Windows + Text + Debug + + + Name + AutoPilot.h + Windows + Text + + + + Name + Bridge.cpp + Windows + Text + Debug + + + Name + Bridge.h + Windows + Text + + + + Name + CarAI.cpp + Windows + Text + Debug + + + Name + CarAI.h + Windows + Text + + + + Name + CarCtrl.cpp + Windows + Text + Debug + + + Name + CarCtrl.h + Windows + Text + + + + Name + Curves.cpp + Windows + Text + Debug + + + Name + Curves.h + Windows + Text + + + + Name + Darkel.cpp + Windows + Text + Debug + + + Name + Darkel.h + Windows + Text + + + + Name + GameLogic.cpp + Windows + Text + Debug + + + Name + GameLogic.h + Windows + Text + + + + Name + Garages.cpp + Windows + Text + Debug + + + Name + Garages.h + Windows + Text + + + + Name + NameGrid.cpp + Windows + Text + Debug + + + Name + NameGrid.h + Windows + Text + + + + Name + OnscreenTimer.cpp + Windows + Text + Debug + + + Name + OnscreenTimer.h + Windows + Text + + + + Name + PathFind.cpp + Windows + Text + Debug + + + Name + PathFind.h + Windows + Text + + + + Name + Phones.cpp + Windows + Text + Debug + + + Name + Phones.h + Windows + Text + + + + Name + Pickups.cpp + Windows + Text + Debug + + + Name + Pickups.h + Windows + Text + + + + Name + PowerPoints.cpp + Windows + Text + Debug + + + Name + PowerPoints.h + Windows + Text + + + + Name + Record.cpp + Windows + Text + Debug + + + Name + Record.h + Windows + Text + + + + Name + Remote.cpp + Windows + Text + Debug + + + Name + Remote.h + Windows + Text + + + + Name + Replay.cpp + Windows + Text + Debug + + + Name + Replay.h + Windows + Text + + + + Name + Restart.cpp + Windows + Text + Debug + + + Name + Restart.h + Windows + Text + + + + Name + RoadBlocks.cpp + Windows + Text + Debug + + + Name + RoadBlocks.h + Windows + Text + + + + Name + SceneEdit.cpp + Windows + Text + Debug + + + Name + SceneEdit.h + Windows + Text + + + + Name + ScriptDebug.cpp + Windows + Text + Debug + + + Name + Script.cpp + Windows + Text + Debug + + + Name + Script.h + Windows + Text + + + + Name + Script2.cpp + Windows + Text + Debug + + + Name + Script3.cpp + Windows + Text + Debug + + + Name + Script4.cpp + Windows + Text + Debug + + + Name + Script5.cpp + Windows + Text + Debug + + + Name + Script6.cpp + Windows + Text + Debug + + + Name + ScriptCommands.h + Windows + Text + + + + Name + TrafficLights.cpp + Windows + Text + Debug + + + Name + TrafficLights.h + Windows + Text + + + + Name + Accident.cpp + Windows + Text + Debug + + + Name + Accident.h + Windows + Text + + + + Name + AnimViewer.cpp + Windows + Text + Debug + + + Name + AnimViewer.h + Windows + Text + + + + Name + Cam.cpp + Windows + Text + Debug + + + Name + Camera.cpp + Windows + Text + Debug + + + Name + Camera.h + Windows + Text + + + + Name + CdStream.cpp + Windows + Text + Debug + + + Name + CdStream.h + Windows + Text + + + + Name + CdStreamPosix.cpp + Windows + Text + Debug + + + Name + Clock.cpp + Windows + Text + Debug + + + Name + Clock.h + Windows + Text + + + + Name + common.h + Windows + Text + + + + Name + config.h + Windows + Text + + + + Name + ControllerConfig.cpp + Windows + Text + Debug + + + Name + ControllerConfig.h + Windows + Text + + + + Name + Crime.h + Windows + Text + + + + Name + Debug.cpp + Windows + Text + Debug + + + Name + Debug.h + Windows + Text + + + + Name + Directory.cpp + Windows + Text + Debug + + + Name + Directory.h + Windows + Text + + + + Name + EventList.cpp + Windows + Text + Debug + + + Name + EventList.h + Windows + Text + + + + Name + FileLoader.cpp + Windows + Text + Debug + + + Name + FileLoader.h + Windows + Text + + + + Name + FileMgr.cpp + Windows + Text + Debug + + + Name + FileMgr.h + Windows + Text + + + + Name + Fire.cpp + Windows + Text + Debug + + + Name + Fire.h + Windows + Text + + + + Name + Frontend.cpp + Windows + Text + Debug + + + Name + Frontend.h + Windows + Text + + + + Name + Frontend_PS2.cpp + Windows + Text + Debug + + + Name + Frontend_PS2.h + Windows + Text + + + + Name + FrontEndControls.cpp + Windows + Text + Debug + + + Name + FrontEndControls.h + Windows + Text + + + + Name + FrontendTriggers.h + Windows + Text + + + + Name + Game.cpp + Windows + Text + Debug + + + Name + Game.h + Windows + Text + + + + Name + General.h + Windows + Text + + + + Name + IniFile.cpp + Windows + Text + Debug + + + Name + IniFile.h + Windows + Text + + + + Name + Lists.cpp + Windows + Text + Debug + + + Name + Lists.h + Windows + Text + + + + Name + main.cpp + Windows + Text + Debug + + + Name + main.h + Windows + Text + + + + Name + MenuScreens.cpp + Windows + Text + Debug + + + Name + MenuScreensCustom.cpp + Windows + Text + Debug + + + Name + obrstr.cpp + Windows + Text + Debug + + + Name + obrstr.h + Windows + Text + + + + Name + Pad.cpp + Windows + Text + Debug + + + Name + Pad.h + Windows + Text + + + + Name + Placeable.cpp + Windows + Text + Debug + + + Name + Placeable.h + Windows + Text + + + + Name + PlayerInfo.cpp + Windows + Text + Debug + + + Name + PlayerInfo.h + Windows + Text + + + + Name + Pools.cpp + Windows + Text + Debug + + + Name + Pools.h + Windows + Text + + + + Name + Profile.cpp + Windows + Text + Debug + + + Name + Profile.h + Windows + Text + + + + Name + Radar.cpp + Windows + Text + Debug + + + Name + Radar.h + Windows + Text + + + + Name + Range2D.cpp + Windows + Text + Debug + + + Name + Range2D.h + Windows + Text + + + + Name + Range3D.cpp + Windows + Text + Debug + + + Name + Range3D.h + Windows + Text + + + + Name + re3.cpp + Windows + Text + Debug + + + Name + References.cpp + Windows + Text + Debug + + + Name + References.h + Windows + Text + + + + Name + Stats.cpp + Windows + Text + Debug + + + Name + Stats.h + Windows + Text + + + + Name + Streaming.cpp + Windows + Text + Debug + + + Name + Streaming.h + Windows + Text + + + + Name + SurfaceTable.cpp + Windows + Text + Debug + + + Name + SurfaceTable.h + Windows + Text + + + + Name + templates.h + Windows + Text + + + + Name + timebars.cpp + Windows + Text + Debug + + + Name + timebars.h + Windows + Text + + + + Name + Timer.cpp + Windows + Text + Debug + + + Name + Timer.h + Windows + Text + + + + Name + TimeStep.cpp + Windows + Text + Debug + + + Name + TimeStep.h + Windows + Text + + + + Name + User.cpp + Windows + Text + Debug + + + Name + User.h + Windows + Text + + + + Name + Wanted.cpp + Windows + Text + Debug + + + Name + Wanted.h + Windows + Text + + + + Name + World.cpp + Windows + Text + Debug + + + Name + World.h + Windows + Text + + + + Name + ZoneCull.cpp + Windows + Text + Debug + + + Name + ZoneCull.h + Windows + Text + + + + Name + Zones.cpp + Windows + Text + Debug + + + Name + Zones.h + Windows + Text + + + + Name + Dummy.cpp + Windows + Text + Debug + + + Name + Dummy.h + Windows + Text + + + + Name + Entity.cpp + Windows + Text + Debug + + + Name + Entity.h + Windows + Text + + + + Name + Physical.cpp + Windows + Text + Debug + + + Name + Physical.h + Windows + Text + + + + Name + math.cpp + Windows + Text + Debug + + + Name + maths.h + Windows + Text + + + + Name + Matrix.cpp + Windows + Text + Debug + + + Name + Matrix.h + Windows + Text + + + + Name + Quaternion.cpp + Windows + Text + Debug + + + Name + Quaternion.h + Windows + Text + + + + Name + Rect.cpp + Windows + Text + Debug + + + Name + Rect.h + Windows + Text + + + + Name + Vector.cpp + Windows + Text + Debug + + + Name + Vector.h + Windows + Text + + + + Name + Vector2D.h + Windows + Text + + + + Name + VuVector.h + Windows + Text + + + + Name + BaseModelInfo.cpp + Windows + Text + Debug + + + Name + BaseModelInfo.h + Windows + Text + + + + Name + ClumpModelInfo.cpp + Windows + Text + Debug + + + Name + ClumpModelInfo.h + Windows + Text + + + + Name + MloModelInfo.cpp + Windows + Text + Debug + + + Name + MloModelInfo.h + Windows + Text + + + + Name + ModelIndices.cpp + Windows + Text + Debug + + + Name + ModelIndices.h + Windows + Text + + + + Name + ModelInfo.cpp + Windows + Text + Debug + + + Name + ModelInfo.h + Windows + Text + + + + Name + PedModelInfo.cpp + Windows + Text + Debug + + + Name + PedModelInfo.h + Windows + Text + + + + Name + SimpleModelInfo.cpp + Windows + Text + Debug + + + Name + SimpleModelInfo.h + Windows + Text + + + + Name + TimeModelInfo.cpp + Windows + Text + Debug + + + Name + TimeModelInfo.h + Windows + Text + + + + Name + VehicleModelInfo.cpp + Windows + Text + Debug + + + Name + VehicleModelInfo.h + Windows + Text + + + + Name + XtraCompsModelInfo.h + Windows + Text + + + + Name + CutsceneHead.cpp + Windows + Text + Debug + + + Name + CutsceneHead.h + Windows + Text + + + + Name + CutsceneObject.cpp + Windows + Text + Debug + + + Name + CutsceneObject.h + Windows + Text + + + + Name + DummyObject.cpp + Windows + Text + Debug + + + Name + DummyObject.h + Windows + Text + + + + Name + Object.cpp + Windows + Text + Debug + + + Name + Object.h + Windows + Text + + + + Name + ObjectData.cpp + Windows + Text + Debug + + + Name + ObjectData.h + Windows + Text + + + + Name + ParticleObject.cpp + Windows + Text + Debug + + + Name + ParticleObject.h + Windows + Text + + + + Name + Projectile.cpp + Windows + Text + Debug + + + Name + Projectile.h + Windows + Text + + + + Name + CivilianPed.cpp + Windows + Text + Debug + + + Name + CivilianPed.h + Windows + Text + + + + Name + CopPed.cpp + Windows + Text + Debug + + + Name + CopPed.h + Windows + Text + + + + Name + DummyPed.h + Windows + Text + + + + Name + EmergencyPed.cpp + Windows + Text + Debug + + + Name + EmergencyPed.h + Windows + Text + + + + Name + Gangs.cpp + Windows + Text + Debug + + + Name + Gangs.h + Windows + Text + + + + Name + Ped.cpp + Windows + Text + Debug + + + Name + Ped.h + Windows + Text + + + + Name + PedAI.cpp + Windows + Text + Debug + + + Name + PedChat.cpp + Windows + Text + Debug + + + Name + PedDebug.cpp + Windows + Text + Debug + + + Name + PedFight.cpp + Windows + Text + Debug + + + Name + PedIK.cpp + Windows + Text + Debug + + + Name + PedIK.h + Windows + Text + + + + Name + PedPlacement.cpp + Windows + Text + Debug + + + Name + PedPlacement.h + Windows + Text + + + + Name + PedRoutes.cpp + Windows + Text + Debug + + + Name + PedRoutes.h + Windows + Text + + + + Name + PedType.cpp + Windows + Text + Debug + + + Name + PedType.h + Windows + Text + + + + Name + PlayerPed.cpp + Windows + Text + Debug + + + Name + PlayerPed.h + Windows + Text + + + + Name + Population.cpp + Windows + Text + Debug + + + Name + Population.h + Windows + Text + + + + Name + 2dEffect.h + Windows + Text + + + + Name + Antennas.cpp + Windows + Text + Debug + + + Name + Antennas.h + Windows + Text + + + + Name + Clouds.cpp + Windows + Text + Debug + + + Name + Clouds.h + Windows + Text + + + + Name + Console.cpp + Windows + Text + Debug + + + Name + Console.h + Windows + Text + + + + Name + Coronas.cpp + Windows + Text + Debug + + + Name + Coronas.h + Windows + Text + + + + Name + Credits.cpp + Windows + Text + Debug + + + Name + Credits.h + Windows + Text + + + + Name + Draw.cpp + Windows + Text + Debug + + + Name + Draw.h + Windows + Text + + + + Name + Fluff.cpp + Windows + Text + Debug + + + Name + Fluff.h + Windows + Text + + + + Name + Font.cpp + Windows + Text + Debug + + + Name + Font.h + Windows + Text + + + + Name + Glass.cpp + Windows + Text + Debug + + + Name + Glass.h + Windows + Text + + + + Name + Hud.cpp + Windows + Text + Debug + + + Name + Hud.h + Windows + Text + + + + Name + Instance.cpp + Windows + Text + Debug + + + Name + Instance.h + Windows + Text + + + + Name + Lines.cpp + Windows + Text + Debug + + + Name + Lines.h + Windows + Text + + + + Name + MBlur.cpp + Windows + Text + Debug + + + Name + MBlur.h + Windows + Text + + + + Name + Particle.cpp + Windows + Text + Debug + + + Name + Particle.h + Windows + Text + + + + Name + ParticleMgr.cpp + Windows + Text + Debug + + + Name + ParticleMgr.h + Windows + Text + + + + Name + ParticleType.h + Windows + Text + + + + Name + PlayerSkin.cpp + Windows + Text + Debug + + + Name + PlayerSkin.h + Windows + Text + + + + Name + PointLights.cpp + Windows + Text + Debug + + + Name + PointLights.h + Windows + Text + + + + Name + RenderBuffer.cpp + Windows + Text + Debug + + + Name + RenderBuffer.h + Windows + Text + + + + Name + Renderer.cpp + Windows + Text + Debug + + + Name + Renderer.h + Windows + Text + + + + Name + Rubbish.cpp + Windows + Text + Debug + + + Name + Rubbish.h + Windows + Text + + + + Name + Shadows.cpp + Windows + Text + Debug + + + Name + Shadows.h + Windows + Text + + + + Name + Skidmarks.cpp + Windows + Text + Debug + + + Name + Skidmarks.h + Windows + Text + + + + Name + SpecialFX.cpp + Windows + Text + Debug + + + Name + SpecialFX.h + Windows + Text + + + + Name + Sprite.cpp + Windows + Text + Debug + + + Name + Sprite.h + Windows + Text + + + + Name + Sprite2d.cpp + Windows + Text + Debug + + + Name + Sprite2d.h + Windows + Text + + + + Name + TexList.cpp + Windows + Text + Debug + + + Name + TexList.h + Windows + Text + + + + Name + Timecycle.cpp + Windows + Text + Debug + + + Name + Timecycle.h + Windows + Text + + + + Name + WaterCannon.cpp + Windows + Text + Debug + + + Name + WaterCannon.h + Windows + Text + + + + Name + WaterLevel.cpp + Windows + Text + Debug + + + Name + WaterLevel.h + Windows + Text + + + + Name + Weather.cpp + Windows + Text + Debug + + + Name + Weather.h + Windows + Text + + + + Name + ClumpRead.cpp + Windows + Text + Debug + + + Name + Lights.cpp + Windows + Text + Debug + + + Name + Lights.h + Windows + Text + + + + Name + MemoryHeap.cpp + Windows + Text + Debug + + + Name + MemoryHeap.h + Windows + Text + + + + Name + MemoryMgr.cpp + Windows + Text + Debug + + + Name + MemoryMgr.h + Windows + Text + + + + Name + NodeName.cpp + Windows + Text + Debug + + + Name + NodeName.h + Windows + Text + + + + Name + RwHelper.cpp + Windows + Text + Debug + + + Name + RwHelper.h + Windows + Text + + + + Name + RwMatFX.cpp + Windows + Text + Debug + + + Name + RwPS2AlphaTest.cpp + Windows + Text + Debug + + + Name + TexRead.cpp + Windows + Text + Debug + + + Name + TexturePools.cpp + Windows + Text + Debug + + + Name + TexturePools.h + Windows + Text + + + + Name + TxdStore.cpp + Windows + Text + Debug + + + Name + TxdStore.h + Windows + Text + + + + Name + VisibilityPlugins.cpp + Windows + Text + Debug + + + Name + VisibilityPlugins.h + Windows + Text + + + + Name + Date.cpp + Windows + Text + Debug + + + Name + Date.h + Windows + Text + + + + Name + GenericGameStorage.cpp + Windows + Text + Debug + + + Name + GenericGameStorage.h + Windows + Text + + + + Name + MemoryCard.cpp + Windows + Text + Debug + + + Name + MemoryCard.h + Windows + Text + + + + Name + PCSave.cpp + Windows + Text + Debug + + + Name + PCSave.h + Windows + Text + + + + Name + crossplatform.cpp + Windows + Text + Debug + + + Name + crossplatform.h + Windows + Text + + + + Name + events.cpp + Windows + Text + Debug + + + Name + events.h + Windows + Text + + + + Name + platform.h + Windows + Text + + + + Name + skeleton.cpp + Windows + Text + Debug + + + Name + skeleton.h + Windows + Text + + + + Name + resource.h + Windows + Text + + + + Name + win.cpp + Windows + Text + Debug + + + Name + win.h + Windows + Text + + + + Name + win.rc + Windows + Text + Debug + + + Name + Messages.cpp + Windows + Text + Debug + + + Name + Messages.h + Windows + Text + + + + Name + Pager.cpp + Windows + Text + Debug + + + Name + Pager.h + Windows + Text + + + + Name + Text.cpp + Windows + Text + Debug + + + Name + Text.h + Windows + Text + + + + Name + Automobile.cpp + Windows + Text + Debug + + + Name + Automobile.h + Windows + Text + + + + Name + Bike.h + Windows + Text + + + + Name + Boat.cpp + Windows + Text + Debug + + + Name + Boat.h + Windows + Text + + + + Name + CarGen.cpp + Windows + Text + Debug + + + Name + CarGen.h + Windows + Text + + + + Name + Cranes.cpp + Windows + Text + Debug + + + Name + Cranes.h + Windows + Text + + + + Name + DamageManager.cpp + Windows + Text + Debug + + + Name + DamageManager.h + Windows + Text + + + + Name + Door.cpp + Windows + Text + Debug + + + Name + Door.h + Windows + Text + + + + Name + Floater.cpp + Windows + Text + Debug + + + Name + Floater.h + Windows + Text + + + + Name + HandlingMgr.cpp + Windows + Text + Debug + + + Name + HandlingMgr.h + Windows + Text + + + + Name + Heli.cpp + Windows + Text + Debug + + + Name + Heli.h + Windows + Text + + + + Name + Plane.cpp + Windows + Text + Debug + + + Name + Plane.h + Windows + Text + + + + Name + Train.cpp + Windows + Text + Debug + + + Name + Train.h + Windows + Text + + + + Name + Transmission.cpp + Windows + Text + Debug + + + Name + Transmission.h + Windows + Text + + + + Name + Vehicle.cpp + Windows + Text + Debug + + + Name + Vehicle.h + Windows + Text + + + + Name + BulletInfo.cpp + Windows + Text + Debug + + + Name + BulletInfo.h + Windows + Text + + + + Name + Explosion.cpp + Windows + Text + Debug + + + Name + Explosion.h + Windows + Text + + + + Name + ProjectileInfo.cpp + Windows + Text + Debug + + + Name + ProjectileInfo.h + Windows + Text + + + + Name + ShotInfo.cpp + Windows + Text + Debug + + + Name + ShotInfo.h + Windows + Text + + + + Name + Weapon.cpp + Windows + Text + Debug + + + Name + Weapon.h + Windows + Text + + + + Name + WeaponEffects.cpp + Windows + Text + Debug + + + Name + WeaponEffects.h + Windows + Text + + + + Name + WeaponInfo.cpp + Windows + Text + Debug + + + Name + WeaponInfo.h + Windows + Text + + + + Name + WeaponType.h + Windows + Text + + + + Name + mss32.lib + Windows + Library + Debug + + + Name + d3d8.lib + Windows + Library + Debug + + + Name + ddraw.lib + Windows + Library + Debug + + + Name + dxguid.lib + Windows + Library + Debug + + + Name + strmiids.lib + Windows + Library + Debug + + + Name + dinput8.lib + Windows + Library + Debug + + + Name + winmm.lib + Windows + Library + Debug + + + Name + rwcore.lib + Windows + Library + Debug + + + Name + rpworld.lib + Windows + Library + Debug + + + Name + rpmatfx.lib + Windows + Library + Debug + + + Name + rpskin.lib + Windows + Library + Debug + + + Name + rphanim.lib + Windows + Library + Debug + + + Name + rtbmp.lib + Windows + Library + Debug + + + Name + rtquat.lib + Windows + Library + Debug + + + Name + rtcharse.lib + Windows + Library + Debug + + + Name + ole32.lib + Windows + Library + Debug + + + Name + shell32.lib + Windows + Library + Debug + + + Name + uuid.lib + Windows + Library + Debug + + + + + Name + AnimationId.h + Windows + + + Name + AnimBlendAssocGroup.cpp + Windows + + + Name + AnimBlendAssocGroup.h + Windows + + + Name + AnimBlendAssociation.cpp + Windows + + + Name + AnimBlendAssociation.h + Windows + + + Name + AnimBlendClumpData.cpp + Windows + + + Name + AnimBlendClumpData.h + Windows + + + Name + AnimBlendHierarchy.cpp + Windows + + + Name + AnimBlendHierarchy.h + Windows + + + Name + AnimBlendList.h + Windows + + + Name + AnimBlendNode.cpp + Windows + + + Name + AnimBlendNode.h + Windows + + + Name + AnimBlendSequence.cpp + Windows + + + Name + AnimBlendSequence.h + Windows + + + Name + AnimManager.cpp + Windows + + + Name + AnimManager.h + Windows + + + Name + Bones.cpp + Windows + + + Name + Bones.h + Windows + + + Name + CutsceneMgr.cpp + Windows + + + Name + CutsceneMgr.h + Windows + + + Name + FrameUpdate.cpp + Windows + + + Name + RpAnimBlend.cpp + Windows + + + Name + RpAnimBlend.h + Windows + + + Name + audio_enums.h + Windows + + + Name + AudioCollision.cpp + Windows + + + Name + AudioCollision.h + Windows + + + Name + AudioLogic.cpp + Windows + + + Name + AudioManager.cpp + Windows + + + Name + AudioManager.h + Windows + + + Name + AudioSamples.h + Windows + + + Name + AudioScriptObject.cpp + Windows + + + Name + AudioScriptObject.h + Windows + + + Name + DMAudio.cpp + Windows + + + Name + DMAudio.h + Windows + + + Name + MusicManager.cpp + Windows + + + Name + MusicManager.h + Windows + + + Name + PolRadio.cpp + Windows + + + Name + PolRadio.h + Windows + + + Name + sampman.h + Windows + + + Name + sampman_miles.cpp + Windows + + + Name + soundlist.h + Windows + + + Name + eax.h + Windows + + + Name + eax-util.cpp + Windows + + + Name + eax-util.h + Windows + + + Name + Building.cpp + Windows + + + Name + Building.h + Windows + + + Name + Solid.h + Windows + + + Name + Treadable.cpp + Windows + + + Name + Treadable.h + Windows + + + Name + ColBox.cpp + Windows + + + Name + ColBox.h + Windows + + + Name + ColLine.cpp + Windows + + + Name + ColLine.h + Windows + + + Name + Collision.cpp + Windows + + + Name + Collision.h + Windows + + + Name + ColModel.cpp + Windows + + + Name + ColModel.h + Windows + + + Name + ColPoint.cpp + Windows + + + Name + ColPoint.h + Windows + + + Name + ColSphere.cpp + Windows + + + Name + ColSphere.h + Windows + + + Name + ColTriangle.cpp + Windows + + + Name + ColTriangle.h + Windows + + + Name + CompressedVector.h + Windows + + + Name + TempColModels.cpp + Windows + + + Name + TempColModels.h + Windows + + + Name + VuCollision.cpp + Windows + + + Name + VuCollision.h + Windows + + + Name + AutoPilot.cpp + Windows + + + Name + AutoPilot.h + Windows + + + Name + Bridge.cpp + Windows + + + Name + Bridge.h + Windows + + + Name + CarAI.cpp + Windows + + + Name + CarAI.h + Windows + + + Name + CarCtrl.cpp + Windows + + + Name + CarCtrl.h + Windows + + + Name + Curves.cpp + Windows + + + Name + Curves.h + Windows + + + Name + Darkel.cpp + Windows + + + Name + Darkel.h + Windows + + + Name + GameLogic.cpp + Windows + + + Name + GameLogic.h + Windows + + + Name + Garages.cpp + Windows + + + Name + Garages.h + Windows + + + Name + NameGrid.cpp + Windows + + + Name + NameGrid.h + Windows + + + Name + OnscreenTimer.cpp + Windows + + + Name + OnscreenTimer.h + Windows + + + Name + PathFind.cpp + Windows + + + Name + PathFind.h + Windows + + + Name + Phones.cpp + Windows + + + Name + Phones.h + Windows + + + Name + Pickups.cpp + Windows + + + Name + Pickups.h + Windows + + + Name + PowerPoints.cpp + Windows + + + Name + PowerPoints.h + Windows + + + Name + Record.cpp + Windows + + + Name + Record.h + Windows + + + Name + Remote.cpp + Windows + + + Name + Remote.h + Windows + + + Name + Replay.cpp + Windows + + + Name + Replay.h + Windows + + + Name + Restart.cpp + Windows + + + Name + Restart.h + Windows + + + Name + RoadBlocks.cpp + Windows + + + Name + RoadBlocks.h + Windows + + + Name + SceneEdit.cpp + Windows + + + Name + SceneEdit.h + Windows + + + Name + ScriptDebug.cpp + Windows + + + Name + Script.cpp + Windows + + + Name + Script.h + Windows + + + Name + Script2.cpp + Windows + + + Name + Script3.cpp + Windows + + + Name + Script4.cpp + Windows + + + Name + Script5.cpp + Windows + + + Name + Script6.cpp + Windows + + + Name + ScriptCommands.h + Windows + + + Name + TrafficLights.cpp + Windows + + + Name + TrafficLights.h + Windows + + + Name + Accident.cpp + Windows + + + Name + Accident.h + Windows + + + Name + AnimViewer.cpp + Windows + + + Name + AnimViewer.h + Windows + + + Name + Cam.cpp + Windows + + + Name + Camera.cpp + Windows + + + Name + Camera.h + Windows + + + Name + CdStream.cpp + Windows + + + Name + CdStream.h + Windows + + + Name + CdStreamPosix.cpp + Windows + + + Name + Clock.cpp + Windows + + + Name + Clock.h + Windows + + + Name + common.h + Windows + + + Name + config.h + Windows + + + Name + ControllerConfig.cpp + Windows + + + Name + ControllerConfig.h + Windows + + + Name + Crime.h + Windows + + + Name + Debug.cpp + Windows + + + Name + Debug.h + Windows + + + Name + Directory.cpp + Windows + + + Name + Directory.h + Windows + + + Name + EventList.cpp + Windows + + + Name + EventList.h + Windows + + + Name + FileLoader.cpp + Windows + + + Name + FileLoader.h + Windows + + + Name + FileMgr.cpp + Windows + + + Name + FileMgr.h + Windows + + + Name + Fire.cpp + Windows + + + Name + Fire.h + Windows + + + Name + Frontend.cpp + Windows + + + Name + Frontend.h + Windows + + + Name + Frontend_PS2.cpp + Windows + + + Name + Frontend_PS2.h + Windows + + + Name + FrontEndControls.cpp + Windows + + + Name + FrontEndControls.h + Windows + + + Name + FrontendTriggers.h + Windows + + + Name + Game.cpp + Windows + + + Name + Game.h + Windows + + + Name + General.h + Windows + + + Name + IniFile.cpp + Windows + + + Name + IniFile.h + Windows + + + Name + Lists.cpp + Windows + + + Name + Lists.h + Windows + + + Name + main.cpp + Windows + + + Name + main.h + Windows + + + Name + MenuScreens.cpp + Windows + + + Name + MenuScreensCustom.cpp + Windows + + + Name + obrstr.cpp + Windows + + + Name + obrstr.h + Windows + + + Name + Pad.cpp + Windows + + + Name + Pad.h + Windows + + + Name + Placeable.cpp + Windows + + + Name + Placeable.h + Windows + + + Name + PlayerInfo.cpp + Windows + + + Name + PlayerInfo.h + Windows + + + Name + Pools.cpp + Windows + + + Name + Pools.h + Windows + + + Name + Profile.cpp + Windows + + + Name + Profile.h + Windows + + + Name + Radar.cpp + Windows + + + Name + Radar.h + Windows + + + Name + Range2D.cpp + Windows + + + Name + Range2D.h + Windows + + + Name + Range3D.cpp + Windows + + + Name + Range3D.h + Windows + + + Name + re3.cpp + Windows + + + Name + References.cpp + Windows + + + Name + References.h + Windows + + + Name + Stats.cpp + Windows + + + Name + Stats.h + Windows + + + Name + Streaming.cpp + Windows + + + Name + Streaming.h + Windows + + + Name + SurfaceTable.cpp + Windows + + + Name + SurfaceTable.h + Windows + + + Name + templates.h + Windows + + + Name + timebars.cpp + Windows + + + Name + timebars.h + Windows + + + Name + Timer.cpp + Windows + + + Name + Timer.h + Windows + + + Name + TimeStep.cpp + Windows + + + Name + TimeStep.h + Windows + + + Name + User.cpp + Windows + + + Name + User.h + Windows + + + Name + Wanted.cpp + Windows + + + Name + Wanted.h + Windows + + + Name + World.cpp + Windows + + + Name + World.h + Windows + + + Name + ZoneCull.cpp + Windows + + + Name + ZoneCull.h + Windows + + + Name + Zones.cpp + Windows + + + Name + Zones.h + Windows + + + Name + Dummy.cpp + Windows + + + Name + Dummy.h + Windows + + + Name + Entity.cpp + Windows + + + Name + Entity.h + Windows + + + Name + Physical.cpp + Windows + + + Name + Physical.h + Windows + + + Name + math.cpp + Windows + + + Name + maths.h + Windows + + + Name + Matrix.cpp + Windows + + + Name + Matrix.h + Windows + + + Name + Quaternion.cpp + Windows + + + Name + Quaternion.h + Windows + + + Name + Rect.cpp + Windows + + + Name + Rect.h + Windows + + + Name + Vector.cpp + Windows + + + Name + Vector.h + Windows + + + Name + Vector2D.h + Windows + + + Name + VuVector.h + Windows + + + Name + BaseModelInfo.cpp + Windows + + + Name + BaseModelInfo.h + Windows + + + Name + ClumpModelInfo.cpp + Windows + + + Name + ClumpModelInfo.h + Windows + + + Name + MloModelInfo.cpp + Windows + + + Name + MloModelInfo.h + Windows + + + Name + ModelIndices.cpp + Windows + + + Name + ModelIndices.h + Windows + + + Name + ModelInfo.cpp + Windows + + + Name + ModelInfo.h + Windows + + + Name + PedModelInfo.cpp + Windows + + + Name + PedModelInfo.h + Windows + + + Name + SimpleModelInfo.cpp + Windows + + + Name + SimpleModelInfo.h + Windows + + + Name + TimeModelInfo.cpp + Windows + + + Name + TimeModelInfo.h + Windows + + + Name + VehicleModelInfo.cpp + Windows + + + Name + VehicleModelInfo.h + Windows + + + Name + XtraCompsModelInfo.h + Windows + + + Name + CutsceneHead.cpp + Windows + + + Name + CutsceneHead.h + Windows + + + Name + CutsceneObject.cpp + Windows + + + Name + CutsceneObject.h + Windows + + + Name + DummyObject.cpp + Windows + + + Name + DummyObject.h + Windows + + + Name + Object.cpp + Windows + + + Name + Object.h + Windows + + + Name + ObjectData.cpp + Windows + + + Name + ObjectData.h + Windows + + + Name + ParticleObject.cpp + Windows + + + Name + ParticleObject.h + Windows + + + Name + Projectile.cpp + Windows + + + Name + Projectile.h + Windows + + + Name + CivilianPed.cpp + Windows + + + Name + CivilianPed.h + Windows + + + Name + CopPed.cpp + Windows + + + Name + CopPed.h + Windows + + + Name + DummyPed.h + Windows + + + Name + EmergencyPed.cpp + Windows + + + Name + EmergencyPed.h + Windows + + + Name + Gangs.cpp + Windows + + + Name + Gangs.h + Windows + + + Name + Ped.cpp + Windows + + + Name + Ped.h + Windows + + + Name + PedAI.cpp + Windows + + + Name + PedChat.cpp + Windows + + + Name + PedDebug.cpp + Windows + + + Name + PedFight.cpp + Windows + + + Name + PedIK.cpp + Windows + + + Name + PedIK.h + Windows + + + Name + PedPlacement.cpp + Windows + + + Name + PedPlacement.h + Windows + + + Name + PedRoutes.cpp + Windows + + + Name + PedRoutes.h + Windows + + + Name + PedType.cpp + Windows + + + Name + PedType.h + Windows + + + Name + PlayerPed.cpp + Windows + + + Name + PlayerPed.h + Windows + + + Name + Population.cpp + Windows + + + Name + Population.h + Windows + + + Name + 2dEffect.h + Windows + + + Name + Antennas.cpp + Windows + + + Name + Antennas.h + Windows + + + Name + Clouds.cpp + Windows + + + Name + Clouds.h + Windows + + + Name + Console.cpp + Windows + + + Name + Console.h + Windows + + + Name + Coronas.cpp + Windows + + + Name + Coronas.h + Windows + + + Name + Credits.cpp + Windows + + + Name + Credits.h + Windows + + + Name + Draw.cpp + Windows + + + Name + Draw.h + Windows + + + Name + Fluff.cpp + Windows + + + Name + Fluff.h + Windows + + + Name + Font.cpp + Windows + + + Name + Font.h + Windows + + + Name + Glass.cpp + Windows + + + Name + Glass.h + Windows + + + Name + Hud.cpp + Windows + + + Name + Hud.h + Windows + + + Name + Instance.cpp + Windows + + + Name + Instance.h + Windows + + + Name + Lines.cpp + Windows + + + Name + Lines.h + Windows + + + Name + MBlur.cpp + Windows + + + Name + MBlur.h + Windows + + + Name + Particle.cpp + Windows + + + Name + Particle.h + Windows + + + Name + ParticleMgr.cpp + Windows + + + Name + ParticleMgr.h + Windows + + + Name + ParticleType.h + Windows + + + Name + PlayerSkin.cpp + Windows + + + Name + PlayerSkin.h + Windows + + + Name + PointLights.cpp + Windows + + + Name + PointLights.h + Windows + + + Name + RenderBuffer.cpp + Windows + + + Name + RenderBuffer.h + Windows + + + Name + Renderer.cpp + Windows + + + Name + Renderer.h + Windows + + + Name + Rubbish.cpp + Windows + + + Name + Rubbish.h + Windows + + + Name + Shadows.cpp + Windows + + + Name + Shadows.h + Windows + + + Name + Skidmarks.cpp + Windows + + + Name + Skidmarks.h + Windows + + + Name + SpecialFX.cpp + Windows + + + Name + SpecialFX.h + Windows + + + Name + Sprite.cpp + Windows + + + Name + Sprite.h + Windows + + + Name + Sprite2d.cpp + Windows + + + Name + Sprite2d.h + Windows + + + Name + TexList.cpp + Windows + + + Name + TexList.h + Windows + + + Name + Timecycle.cpp + Windows + + + Name + Timecycle.h + Windows + + + Name + WaterCannon.cpp + Windows + + + Name + WaterCannon.h + Windows + + + Name + WaterLevel.cpp + Windows + + + Name + WaterLevel.h + Windows + + + Name + Weather.cpp + Windows + + + Name + Weather.h + Windows + + + Name + ClumpRead.cpp + Windows + + + Name + Lights.cpp + Windows + + + Name + Lights.h + Windows + + + Name + MemoryHeap.cpp + Windows + + + Name + MemoryHeap.h + Windows + + + Name + MemoryMgr.cpp + Windows + + + Name + MemoryMgr.h + Windows + + + Name + NodeName.cpp + Windows + + + Name + NodeName.h + Windows + + + Name + RwHelper.cpp + Windows + + + Name + RwHelper.h + Windows + + + Name + RwMatFX.cpp + Windows + + + Name + RwPS2AlphaTest.cpp + Windows + + + Name + TexRead.cpp + Windows + + + Name + TexturePools.cpp + Windows + + + Name + TexturePools.h + Windows + + + Name + TxdStore.cpp + Windows + + + Name + TxdStore.h + Windows + + + Name + VisibilityPlugins.cpp + Windows + + + Name + VisibilityPlugins.h + Windows + + + Name + Date.cpp + Windows + + + Name + Date.h + Windows + + + Name + GenericGameStorage.cpp + Windows + + + Name + GenericGameStorage.h + Windows + + + Name + MemoryCard.cpp + Windows + + + Name + MemoryCard.h + Windows + + + Name + PCSave.cpp + Windows + + + Name + PCSave.h + Windows + + + Name + crossplatform.cpp + Windows + + + Name + crossplatform.h + Windows + + + Name + events.cpp + Windows + + + Name + events.h + Windows + + + Name + platform.h + Windows + + + Name + skeleton.cpp + Windows + + + Name + skeleton.h + Windows + + + Name + resource.h + Windows + + + Name + win.cpp + Windows + + + Name + win.h + Windows + + + Name + win.rc + Windows + + + Name + Messages.cpp + Windows + + + Name + Messages.h + Windows + + + Name + Pager.cpp + Windows + + + Name + Pager.h + Windows + + + Name + Text.cpp + Windows + + + Name + Text.h + Windows + + + Name + Automobile.cpp + Windows + + + Name + Automobile.h + Windows + + + Name + Bike.h + Windows + + + Name + Boat.cpp + Windows + + + Name + Boat.h + Windows + + + Name + CarGen.cpp + Windows + + + Name + CarGen.h + Windows + + + Name + Cranes.cpp + Windows + + + Name + Cranes.h + Windows + + + Name + DamageManager.cpp + Windows + + + Name + DamageManager.h + Windows + + + Name + Door.cpp + Windows + + + Name + Door.h + Windows + + + Name + Floater.cpp + Windows + + + Name + Floater.h + Windows + + + Name + HandlingMgr.cpp + Windows + + + Name + HandlingMgr.h + Windows + + + Name + Heli.cpp + Windows + + + Name + Heli.h + Windows + + + Name + Plane.cpp + Windows + + + Name + Plane.h + Windows + + + Name + Train.cpp + Windows + + + Name + Train.h + Windows + + + Name + Transmission.cpp + Windows + + + Name + Transmission.h + Windows + + + Name + Vehicle.cpp + Windows + + + Name + Vehicle.h + Windows + + + Name + BulletInfo.cpp + Windows + + + Name + BulletInfo.h + Windows + + + Name + Explosion.cpp + Windows + + + Name + Explosion.h + Windows + + + Name + ProjectileInfo.cpp + Windows + + + Name + ProjectileInfo.h + Windows + + + Name + ShotInfo.cpp + Windows + + + Name + ShotInfo.h + Windows + + + Name + Weapon.cpp + Windows + + + Name + Weapon.h + Windows + + + Name + WeaponEffects.cpp + Windows + + + Name + WeaponEffects.h + Windows + + + Name + WeaponInfo.cpp + Windows + + + Name + WeaponInfo.h + Windows + + + Name + WeaponType.h + Windows + + + Name + mss32.lib + Windows + + + Name + d3d8.lib + Windows + + + Name + ddraw.lib + Windows + + + Name + dxguid.lib + Windows + + + Name + strmiids.lib + Windows + + + Name + dinput8.lib + Windows + + + Name + winmm.lib + Windows + + + Name + rwcore.lib + Windows + + + Name + rpworld.lib + Windows + + + Name + rpmatfx.lib + Windows + + + Name + rpskin.lib + Windows + + + Name + rphanim.lib + Windows + + + Name + rtbmp.lib + Windows + + + Name + rtquat.lib + Windows + + + Name + rtcharse.lib + Windows + + + Name + ole32.lib + Windows + + + Name + shell32.lib + Windows + + + Name + uuid.lib + Windows + + + Name + MSL_All_x86_D.lib + MacOS + + + Name + Comdlg32.lib + MacOS + + + Name + Gdi32.lib + MacOS + + + Name + Kernel32.lib + MacOS + + + Name + User32.lib + MacOS + + + + + Release + + + + UserSourceTrees + + + AlwaysSearchUserPathsfalse + InterpretDOSAndUnixPathstrue + RequireFrameworkStyleIncludesfalse + UserSearchPaths + + SearchPath + Path + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\animation + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\audio + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\buildings + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\collision + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\control + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\core + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\entities + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\math + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\modelinfo + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\objects + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\peds + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\renderer + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\rw + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\save + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\skel + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\text + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\vehicles + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\weapons + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\vendor\milessdk\lib + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\vendor\milessdk\include + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\sdk\dx8sdk\Lib + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\sdk\rwsdk\lib\d3d8\release + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\sdk\rwsdk\include\d3d8 + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\extras + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SystemSearchPaths + + SearchPath + Path..\sdk\rwsdk\include\d3d8 + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\sdk\dx8sdk\Include + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + PathWin32-x86 Support\Headers\ + PathFormatWindows + PathRootCodeWarrior + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + PathWin32-x86 Support\Libraries\ + PathFormatWindows + PathRootCodeWarrior + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + PathMSL + PathFormatWindows + PathRootCodeWarrior + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + SearchPath + Path..\src\audio\eax + PathFormatWindows + PathRootProject + + Recursivetrue + FrameworkPathfalse + HostFlagsAll + + + + + MWRuntimeSettings_WorkingDirectory + MWRuntimeSettings_CommandLine + MWRuntimeSettings_HostApplication + Path + PathFormatGeneric + PathRootAbsolute + + MWRuntimeSettings_EnvVars + + + LinkerWin32 x86 Linker + PreLinker + PostLinker + TargetnameRelease + OutputDirectory + Path + PathFormatWindows + PathRootProject + + SaveEntriesUsingRelativePathsfalse + + + FileMappings + + FileTypeTEXT + FileExtension.c + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.c++ + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.cc + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.cp + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.cpp + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.def + Compiler + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.h + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMaketrue + + + FileTypeTEXT + FileExtension.p + CompilerMW Pascal x86 + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.pas + CompilerMW Pascal x86 + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.pch + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompiletrue + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.pch++ + CompilerMW C/C++ x86 + EditLanguageC/C++ + Precompiletrue + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.ppu + CompilerMW Pascal x86 + EditLanguage + Precompiletrue + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.rc + CompilerMW WinRC + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileTypeTEXT + FileExtension.res + CompilerWinRes Import + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileExtension.doc + Compiler + EditLanguage + Precompilefalse + Launchabletrue + ResourceFilefalse + IgnoredByMaketrue + + + FileExtension.lib + CompilerLib Import x86 + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileExtension.obj + CompilerObj Import x86 + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + FileExtension.res + CompilerWinRes Import + EditLanguage + Precompilefalse + Launchablefalse + ResourceFilefalse + IgnoredByMakefalse + + + + + CacheModDatestrue + ActivateBrowserfalse + DumpBrowserInfofalse + CacheSubprojectstrue + UseThirdPartyDebuggerfalse + DebuggerAppPath + Path + PathFormatGeneric + PathRootAbsolute + + DebuggerCmdLineArgs + DebuggerWorkingDir + Path + PathFormatGeneric + PathRootAbsolute + + + + LogSystemMessagesfalse + AutoTargetDLLsfalse + StopAtWatchpointstrue + PauseWhileRunningfalse + PauseInterval5 + PauseUIFlags0 + AltExePath + Path + PathFormatGeneric + PathRootAbsolute + + StopAtTempBPOnLaunchtrue + CacheSymbolicstrue + TempBPFunctionNamemain + TempBPType0 + + + Enabledfalse + ConnectionName + DownloadPath + LaunchRemoteAppfalse + RemoteAppPath + + + OtherExecutables + + + CustomColor1 + Red0 + Green32767 + Blue0 + + CustomColor2 + Red0 + Green32767 + Blue0 + + CustomColor3 + Red0 + Green32767 + Blue0 + + CustomColor4 + Red0 + Green32767 + Blue0 + + + + MWCodeGen_X86_processorPentiumII + MWCodeGen_X86_alignmentbytes8 + MWCodeGen_X86_exceptionsZeroOverhead + MWCodeGen_X86_extinst_mmx0 + MWCodeGen_X86_extinst_3dnow0 + MWCodeGen_X86_use_mmx_3dnow_convention0 + MWCodeGen_X86_machinecodelisting0 + MWCodeGen_X86_intrinsics1 + MWCodeGen_X86_syminfo0 + MWCodeGen_X86_codeviewinfo1 + MWCodeGen_X86_extinst_cmov_fcomi0 + MWCodeGen_X86_extinst_sse0 + + + MWDebugger_X86_Exceptions + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + + PDisasmX86_showHeaderstrue + PDisasmX86_showSymTabtrue + PDisasmX86_showCodetrue + PDisasmX86_showSourcefalse + PDisasmX86_showHextrue + PDisasmX86_showRelocationtrue + PDisasmX86_showCommentsfalse + PDisasmX86_showDebugfalse + PDisasmX86_showExceptionsfalse + PDisasmX86_showDatatrue + PDisasmX86_showRawfalse + PDisasmX86_verbosefalse + + + MWFrontEnd_C_cplusplus1 + MWFrontEnd_C_checkprotos0 + MWFrontEnd_C_arm0 + MWFrontEnd_C_trigraphs0 + MWFrontEnd_C_onlystdkeywords0 + MWFrontEnd_C_enumsalwaysint1 + MWFrontEnd_C_mpwpointerstyle0 + MWFrontEnd_C_prefixname + MWFrontEnd_C_ansistrict0 + MWFrontEnd_C_mpwcnewline0 + MWFrontEnd_C_wchar_type1 + MWFrontEnd_C_enableexceptions1 + MWFrontEnd_C_dontreusestrings0 + MWFrontEnd_C_poolstrings1 + MWFrontEnd_C_dontinline0 + MWFrontEnd_C_useRTTI1 + MWFrontEnd_C_multibyteaware1 + MWFrontEnd_C_unsignedchars0 + MWFrontEnd_C_autoinline0 + MWFrontEnd_C_booltruefalse1 + MWFrontEnd_C_direct_to_som0 + MWFrontEnd_C_som_env_check0 + MWFrontEnd_C_alwaysinline0 + MWFrontEnd_C_inlinelevel0 + MWFrontEnd_C_ecplusplus0 + MWFrontEnd_C_objective_c0 + MWFrontEnd_C_defer_codegen0 + + + MWLinker_X86_entrypointusageDefault + MWLinker_X86_entrypoint + MWLinker_X86_subsystemWinGUI + MWLinker_X86_subsysmajorid4 + MWLinker_X86_subsysminorid0 + MWLinker_X86_usrmajorid0 + MWLinker_X86_usrminorid0 + MWLinker_X86_commandfile + MWLinker_X86_generatemap0 + MWLinker_X86_linksym0 + MWLinker_X86_linkCV1 + + + MWProject_X86_typeApplication + MWProject_X86_outfileRelease\gta3.exe + MWProject_X86_baseaddress4194304 + MWProject_X86_maxstacksize1024 + MWProject_X86_minstacksize4 + MWProject_X86_size1024 + MWProject_X86_minsize4 + MWProject_X86_importlib + + + MWWarning_C_warn_illpragma0 + MWWarning_C_warn_emptydecl0 + MWWarning_C_warn_possunwant1 + MWWarning_C_warn_unusedvar1 + MWWarning_C_warn_unusedarg0 + MWWarning_C_warn_extracomma1 + MWWarning_C_pedantic0 + MWWarning_C_warningerrors0 + MWWarning_C_warn_hidevirtual1 + MWWarning_C_warn_implicitconv0 + MWWarning_C_warn_notinlined0 + MWWarning_C_warn_structclass0 + + + MWWinRC_prefixnameResourcePrefix.h + + + GlobalOptimizer_X86__optimizationlevelLevel4 + GlobalOptimizer_X86__optforSpeed + + + + Name + MSL_All_x86.lib + MacOS + Library + + + + Name + Comdlg32.lib + MacOS + Library + + + + Name + Gdi32.lib + MacOS + Library + + + + Name + Kernel32.lib + MacOS + Library + + + + Name + User32.lib + MacOS + Library + + + + Name + AnimationId.h + Windows + Text + + + + Name + AnimBlendAssocGroup.cpp + Windows + Text + Debug + + + Name + AnimBlendAssocGroup.h + Windows + Text + + + + Name + AnimBlendAssociation.cpp + Windows + Text + Debug + + + Name + AnimBlendAssociation.h + Windows + Text + + + + Name + AnimBlendClumpData.cpp + Windows + Text + Debug + + + Name + AnimBlendClumpData.h + Windows + Text + + + + Name + AnimBlendHierarchy.cpp + Windows + Text + Debug + + + Name + AnimBlendHierarchy.h + Windows + Text + + + + Name + AnimBlendList.h + Windows + Text + + + + Name + AnimBlendNode.cpp + Windows + Text + Debug + + + Name + AnimBlendNode.h + Windows + Text + + + + Name + AnimBlendSequence.cpp + Windows + Text + Debug + + + Name + AnimBlendSequence.h + Windows + Text + + + + Name + AnimManager.cpp + Windows + Text + Debug + + + Name + AnimManager.h + Windows + Text + + + + Name + Bones.cpp + Windows + Text + Debug + + + Name + Bones.h + Windows + Text + + + + Name + CutsceneMgr.cpp + Windows + Text + Debug + + + Name + CutsceneMgr.h + Windows + Text + + + + Name + FrameUpdate.cpp + Windows + Text + Debug + + + Name + RpAnimBlend.cpp + Windows + Text + Debug + + + Name + RpAnimBlend.h + Windows + Text + + + + Name + audio_enums.h + Windows + Text + + + + Name + AudioCollision.cpp + Windows + Text + Debug + + + Name + AudioCollision.h + Windows + Text + + + + Name + AudioLogic.cpp + Windows + Text + + + + Name + AudioManager.cpp + Windows + Text + Debug + + + Name + AudioManager.h + Windows + Text + + + + Name + AudioSamples.h + Windows + Text + + + + Name + AudioScriptObject.cpp + Windows + Text + Debug + + + Name + AudioScriptObject.h + Windows + Text + + + + Name + DMAudio.cpp + Windows + Text + Debug + + + Name + DMAudio.h + Windows + Text + + + + Name + MusicManager.cpp + Windows + Text + Debug + + + Name + MusicManager.h + Windows + Text + + + + Name + PolRadio.cpp + Windows + Text + Debug + + + Name + PolRadio.h + Windows + Text + + + + Name + sampman.h + Windows + Text + + + + Name + sampman_miles.cpp + Windows + Text + Debug + + + Name + soundlist.h + Windows + Text + + + + Name + eax.h + Windows + Text + + + + Name + eax-util.cpp + Windows + Text + Debug + + + Name + eax-util.h + Windows + Text + + + + Name + Building.cpp + Windows + Text + Debug + + + Name + Building.h + Windows + Text + + + + Name + Solid.h + Windows + Text + + + + Name + Treadable.cpp + Windows + Text + Debug + + + Name + Treadable.h + Windows + Text + + + + Name + ColBox.cpp + Windows + Text + Debug + + + Name + ColBox.h + Windows + Text + + + + Name + ColLine.cpp + Windows + Text + Debug + + + Name + ColLine.h + Windows + Text + + + + Name + Collision.cpp + Windows + Text + Debug + + + Name + Collision.h + Windows + Text + + + + Name + ColModel.cpp + Windows + Text + Debug + + + Name + ColModel.h + Windows + Text + + + + Name + ColPoint.cpp + Windows + Text + Debug + + + Name + ColPoint.h + Windows + Text + + + + Name + ColSphere.cpp + Windows + Text + Debug + + + Name + ColSphere.h + Windows + Text + + + + Name + ColTriangle.cpp + Windows + Text + Debug + + + Name + ColTriangle.h + Windows + Text + + + + Name + CompressedVector.h + Windows + Text + + + + Name + TempColModels.cpp + Windows + Text + Debug + + + Name + TempColModels.h + Windows + Text + + + + Name + VuCollision.cpp + Windows + Text + Debug + + + Name + VuCollision.h + Windows + Text + + + + Name + AutoPilot.cpp + Windows + Text + Debug + + + Name + AutoPilot.h + Windows + Text + + + + Name + Bridge.cpp + Windows + Text + Debug + + + Name + Bridge.h + Windows + Text + + + + Name + CarAI.cpp + Windows + Text + Debug + + + Name + CarAI.h + Windows + Text + + + + Name + CarCtrl.cpp + Windows + Text + Debug + + + Name + CarCtrl.h + Windows + Text + + + + Name + Curves.cpp + Windows + Text + Debug + + + Name + Curves.h + Windows + Text + + + + Name + Darkel.cpp + Windows + Text + Debug + + + Name + Darkel.h + Windows + Text + + + + Name + GameLogic.cpp + Windows + Text + Debug + + + Name + GameLogic.h + Windows + Text + + + + Name + Garages.cpp + Windows + Text + Debug + + + Name + Garages.h + Windows + Text + + + + Name + NameGrid.cpp + Windows + Text + Debug + + + Name + NameGrid.h + Windows + Text + + + + Name + OnscreenTimer.cpp + Windows + Text + Debug + + + Name + OnscreenTimer.h + Windows + Text + + + + Name + PathFind.cpp + Windows + Text + Debug + + + Name + PathFind.h + Windows + Text + + + + Name + Phones.cpp + Windows + Text + Debug + + + Name + Phones.h + Windows + Text + + + + Name + Pickups.cpp + Windows + Text + Debug + + + Name + Pickups.h + Windows + Text + + + + Name + PowerPoints.cpp + Windows + Text + Debug + + + Name + PowerPoints.h + Windows + Text + + + + Name + Record.cpp + Windows + Text + Debug + + + Name + Record.h + Windows + Text + + + + Name + Remote.cpp + Windows + Text + Debug + + + Name + Remote.h + Windows + Text + + + + Name + Replay.cpp + Windows + Text + Debug + + + Name + Replay.h + Windows + Text + + + + Name + Restart.cpp + Windows + Text + Debug + + + Name + Restart.h + Windows + Text + + + + Name + RoadBlocks.cpp + Windows + Text + Debug + + + Name + RoadBlocks.h + Windows + Text + + + + Name + SceneEdit.cpp + Windows + Text + Debug + + + Name + SceneEdit.h + Windows + Text + + + + Name + ScriptDebug.cpp + Windows + Text + Debug + + + Name + Script.cpp + Windows + Text + Debug + + + Name + Script.h + Windows + Text + + + + Name + Script2.cpp + Windows + Text + Debug + + + Name + Script3.cpp + Windows + Text + Debug + + + Name + Script4.cpp + Windows + Text + Debug + + + Name + Script5.cpp + Windows + Text + Debug + + + Name + Script6.cpp + Windows + Text + Debug + + + Name + ScriptCommands.h + Windows + Text + + + + Name + TrafficLights.cpp + Windows + Text + Debug + + + Name + TrafficLights.h + Windows + Text + + + + Name + Accident.cpp + Windows + Text + Debug + + + Name + Accident.h + Windows + Text + + + + Name + AnimViewer.cpp + Windows + Text + Debug + + + Name + AnimViewer.h + Windows + Text + + + + Name + Cam.cpp + Windows + Text + Debug + + + Name + Camera.cpp + Windows + Text + Debug + + + Name + Camera.h + Windows + Text + + + + Name + CdStream.cpp + Windows + Text + Debug + + + Name + CdStream.h + Windows + Text + + + + Name + CdStreamPosix.cpp + Windows + Text + Debug + + + Name + Clock.cpp + Windows + Text + Debug + + + Name + Clock.h + Windows + Text + + + + Name + common.h + Windows + Text + + + + Name + config.h + Windows + Text + + + + Name + ControllerConfig.cpp + Windows + Text + Debug + + + Name + ControllerConfig.h + Windows + Text + + + + Name + Crime.h + Windows + Text + + + + Name + Debug.cpp + Windows + Text + Debug + + + Name + Debug.h + Windows + Text + + + + Name + Directory.cpp + Windows + Text + Debug + + + Name + Directory.h + Windows + Text + + + + Name + EventList.cpp + Windows + Text + Debug + + + Name + EventList.h + Windows + Text + + + + Name + FileLoader.cpp + Windows + Text + Debug + + + Name + FileLoader.h + Windows + Text + + + + Name + FileMgr.cpp + Windows + Text + Debug + + + Name + FileMgr.h + Windows + Text + + + + Name + Fire.cpp + Windows + Text + Debug + + + Name + Fire.h + Windows + Text + + + + Name + Frontend.cpp + Windows + Text + Debug + + + Name + Frontend.h + Windows + Text + + + + Name + Frontend_PS2.cpp + Windows + Text + Debug + + + Name + Frontend_PS2.h + Windows + Text + + + + Name + FrontEndControls.cpp + Windows + Text + Debug + + + Name + FrontEndControls.h + Windows + Text + + + + Name + FrontendTriggers.h + Windows + Text + + + + Name + Game.cpp + Windows + Text + Debug + + + Name + Game.h + Windows + Text + + + + Name + General.h + Windows + Text + + + + Name + IniFile.cpp + Windows + Text + Debug + + + Name + IniFile.h + Windows + Text + + + + Name + Lists.cpp + Windows + Text + Debug + + + Name + Lists.h + Windows + Text + + + + Name + main.cpp + Windows + Text + Debug + + + Name + main.h + Windows + Text + + + + Name + MenuScreens.cpp + Windows + Text + Debug + + + Name + MenuScreensCustom.cpp + Windows + Text + Debug + + + Name + obrstr.cpp + Windows + Text + Debug + + + Name + obrstr.h + Windows + Text + + + + Name + Pad.cpp + Windows + Text + Debug + + + Name + Pad.h + Windows + Text + + + + Name + Placeable.cpp + Windows + Text + Debug + + + Name + Placeable.h + Windows + Text + + + + Name + PlayerInfo.cpp + Windows + Text + Debug + + + Name + PlayerInfo.h + Windows + Text + + + + Name + Pools.cpp + Windows + Text + Debug + + + Name + Pools.h + Windows + Text + + + + Name + Profile.cpp + Windows + Text + Debug + + + Name + Profile.h + Windows + Text + + + + Name + Radar.cpp + Windows + Text + Debug + + + Name + Radar.h + Windows + Text + + + + Name + Range2D.cpp + Windows + Text + Debug + + + Name + Range2D.h + Windows + Text + + + + Name + Range3D.cpp + Windows + Text + Debug + + + Name + Range3D.h + Windows + Text + + + + Name + re3.cpp + Windows + Text + Debug + + + Name + References.cpp + Windows + Text + Debug + + + Name + References.h + Windows + Text + + + + Name + Stats.cpp + Windows + Text + Debug + + + Name + Stats.h + Windows + Text + + + + Name + Streaming.cpp + Windows + Text + Debug + + + Name + Streaming.h + Windows + Text + + + + Name + SurfaceTable.cpp + Windows + Text + Debug + + + Name + SurfaceTable.h + Windows + Text + + + + Name + templates.h + Windows + Text + + + + Name + timebars.cpp + Windows + Text + Debug + + + Name + timebars.h + Windows + Text + + + + Name + Timer.cpp + Windows + Text + Debug + + + Name + Timer.h + Windows + Text + + + + Name + TimeStep.cpp + Windows + Text + Debug + + + Name + TimeStep.h + Windows + Text + + + + Name + User.cpp + Windows + Text + Debug + + + Name + User.h + Windows + Text + + + + Name + Wanted.cpp + Windows + Text + Debug + + + Name + Wanted.h + Windows + Text + + + + Name + World.cpp + Windows + Text + Debug + + + Name + World.h + Windows + Text + + + + Name + ZoneCull.cpp + Windows + Text + Debug + + + Name + ZoneCull.h + Windows + Text + + + + Name + Zones.cpp + Windows + Text + Debug + + + Name + Zones.h + Windows + Text + + + + Name + Dummy.cpp + Windows + Text + Debug + + + Name + Dummy.h + Windows + Text + + + + Name + Entity.cpp + Windows + Text + Debug + + + Name + Entity.h + Windows + Text + + + + Name + Physical.cpp + Windows + Text + Debug + + + Name + Physical.h + Windows + Text + + + + Name + math.cpp + Windows + Text + Debug + + + Name + maths.h + Windows + Text + + + + Name + Matrix.cpp + Windows + Text + Debug + + + Name + Matrix.h + Windows + Text + + + + Name + Quaternion.cpp + Windows + Text + Debug + + + Name + Quaternion.h + Windows + Text + + + + Name + Rect.cpp + Windows + Text + Debug + + + Name + Rect.h + Windows + Text + + + + Name + Vector.cpp + Windows + Text + Debug + + + Name + Vector.h + Windows + Text + + + + Name + Vector2D.h + Windows + Text + + + + Name + VuVector.h + Windows + Text + + + + Name + BaseModelInfo.cpp + Windows + Text + Debug + + + Name + BaseModelInfo.h + Windows + Text + + + + Name + ClumpModelInfo.cpp + Windows + Text + Debug + + + Name + ClumpModelInfo.h + Windows + Text + + + + Name + MloModelInfo.cpp + Windows + Text + Debug + + + Name + MloModelInfo.h + Windows + Text + + + + Name + ModelIndices.cpp + Windows + Text + Debug + + + Name + ModelIndices.h + Windows + Text + + + + Name + ModelInfo.cpp + Windows + Text + Debug + + + Name + ModelInfo.h + Windows + Text + + + + Name + PedModelInfo.cpp + Windows + Text + Debug + + + Name + PedModelInfo.h + Windows + Text + + + + Name + SimpleModelInfo.cpp + Windows + Text + Debug + + + Name + SimpleModelInfo.h + Windows + Text + + + + Name + TimeModelInfo.cpp + Windows + Text + Debug + + + Name + TimeModelInfo.h + Windows + Text + + + + Name + VehicleModelInfo.cpp + Windows + Text + Debug + + + Name + VehicleModelInfo.h + Windows + Text + + + + Name + XtraCompsModelInfo.h + Windows + Text + + + + Name + CutsceneHead.cpp + Windows + Text + Debug + + + Name + CutsceneHead.h + Windows + Text + + + + Name + CutsceneObject.cpp + Windows + Text + Debug + + + Name + CutsceneObject.h + Windows + Text + + + + Name + DummyObject.cpp + Windows + Text + Debug + + + Name + DummyObject.h + Windows + Text + + + + Name + Object.cpp + Windows + Text + Debug + + + Name + Object.h + Windows + Text + + + + Name + ObjectData.cpp + Windows + Text + Debug + + + Name + ObjectData.h + Windows + Text + + + + Name + ParticleObject.cpp + Windows + Text + Debug + + + Name + ParticleObject.h + Windows + Text + + + + Name + Projectile.cpp + Windows + Text + Debug + + + Name + Projectile.h + Windows + Text + + + + Name + CivilianPed.cpp + Windows + Text + Debug + + + Name + CivilianPed.h + Windows + Text + + + + Name + CopPed.cpp + Windows + Text + Debug + + + Name + CopPed.h + Windows + Text + + + + Name + DummyPed.h + Windows + Text + + + + Name + EmergencyPed.cpp + Windows + Text + Debug + + + Name + EmergencyPed.h + Windows + Text + + + + Name + Gangs.cpp + Windows + Text + Debug + + + Name + Gangs.h + Windows + Text + + + + Name + Ped.cpp + Windows + Text + Debug + + + Name + Ped.h + Windows + Text + + + + Name + PedAI.cpp + Windows + Text + Debug + + + Name + PedChat.cpp + Windows + Text + Debug + + + Name + PedDebug.cpp + Windows + Text + Debug + + + Name + PedFight.cpp + Windows + Text + Debug + + + Name + PedIK.cpp + Windows + Text + Debug + + + Name + PedIK.h + Windows + Text + + + + Name + PedPlacement.cpp + Windows + Text + Debug + + + Name + PedPlacement.h + Windows + Text + + + + Name + PedRoutes.cpp + Windows + Text + Debug + + + Name + PedRoutes.h + Windows + Text + + + + Name + PedType.cpp + Windows + Text + Debug + + + Name + PedType.h + Windows + Text + + + + Name + PlayerPed.cpp + Windows + Text + Debug + + + Name + PlayerPed.h + Windows + Text + + + + Name + Population.cpp + Windows + Text + Debug + + + Name + Population.h + Windows + Text + + + + Name + 2dEffect.h + Windows + Text + + + + Name + Antennas.cpp + Windows + Text + Debug + + + Name + Antennas.h + Windows + Text + + + + Name + Clouds.cpp + Windows + Text + Debug + + + Name + Clouds.h + Windows + Text + + + + Name + Console.cpp + Windows + Text + Debug + + + Name + Console.h + Windows + Text + + + + Name + Coronas.cpp + Windows + Text + Debug + + + Name + Coronas.h + Windows + Text + + + + Name + Credits.cpp + Windows + Text + Debug + + + Name + Credits.h + Windows + Text + + + + Name + Draw.cpp + Windows + Text + Debug + + + Name + Draw.h + Windows + Text + + + + Name + Fluff.cpp + Windows + Text + Debug + + + Name + Fluff.h + Windows + Text + + + + Name + Font.cpp + Windows + Text + Debug + + + Name + Font.h + Windows + Text + + + + Name + Glass.cpp + Windows + Text + Debug + + + Name + Glass.h + Windows + Text + + + + Name + Hud.cpp + Windows + Text + Debug + + + Name + Hud.h + Windows + Text + + + + Name + Instance.cpp + Windows + Text + Debug + + + Name + Instance.h + Windows + Text + + + + Name + Lines.cpp + Windows + Text + Debug + + + Name + Lines.h + Windows + Text + + + + Name + MBlur.cpp + Windows + Text + Debug + + + Name + MBlur.h + Windows + Text + + + + Name + Particle.cpp + Windows + Text + Debug + + + Name + Particle.h + Windows + Text + + + + Name + ParticleMgr.cpp + Windows + Text + Debug + + + Name + ParticleMgr.h + Windows + Text + + + + Name + ParticleType.h + Windows + Text + + + + Name + PlayerSkin.cpp + Windows + Text + Debug + + + Name + PlayerSkin.h + Windows + Text + + + + Name + PointLights.cpp + Windows + Text + Debug + + + Name + PointLights.h + Windows + Text + + + + Name + RenderBuffer.cpp + Windows + Text + Debug + + + Name + RenderBuffer.h + Windows + Text + + + + Name + Renderer.cpp + Windows + Text + Debug + + + Name + Renderer.h + Windows + Text + + + + Name + Rubbish.cpp + Windows + Text + Debug + + + Name + Rubbish.h + Windows + Text + + + + Name + Shadows.cpp + Windows + Text + Debug + + + Name + Shadows.h + Windows + Text + + + + Name + Skidmarks.cpp + Windows + Text + Debug + + + Name + Skidmarks.h + Windows + Text + + + + Name + SpecialFX.cpp + Windows + Text + Debug + + + Name + SpecialFX.h + Windows + Text + + + + Name + Sprite.cpp + Windows + Text + Debug + + + Name + Sprite.h + Windows + Text + + + + Name + Sprite2d.cpp + Windows + Text + Debug + + + Name + Sprite2d.h + Windows + Text + + + + Name + TexList.cpp + Windows + Text + Debug + + + Name + TexList.h + Windows + Text + + + + Name + Timecycle.cpp + Windows + Text + Debug + + + Name + Timecycle.h + Windows + Text + + + + Name + WaterCannon.cpp + Windows + Text + Debug + + + Name + WaterCannon.h + Windows + Text + + + + Name + WaterLevel.cpp + Windows + Text + Debug + + + Name + WaterLevel.h + Windows + Text + + + + Name + Weather.cpp + Windows + Text + Debug + + + Name + Weather.h + Windows + Text + + + + Name + ClumpRead.cpp + Windows + Text + Debug + + + Name + Lights.cpp + Windows + Text + Debug + + + Name + Lights.h + Windows + Text + + + + Name + MemoryHeap.cpp + Windows + Text + Debug + + + Name + MemoryHeap.h + Windows + Text + + + + Name + MemoryMgr.cpp + Windows + Text + Debug + + + Name + MemoryMgr.h + Windows + Text + + + + Name + NodeName.cpp + Windows + Text + Debug + + + Name + NodeName.h + Windows + Text + + + + Name + RwHelper.cpp + Windows + Text + Debug + + + Name + RwHelper.h + Windows + Text + + + + Name + RwMatFX.cpp + Windows + Text + Debug + + + Name + RwPS2AlphaTest.cpp + Windows + Text + Debug + + + Name + TexRead.cpp + Windows + Text + Debug + + + Name + TexturePools.cpp + Windows + Text + Debug + + + Name + TexturePools.h + Windows + Text + + + + Name + TxdStore.cpp + Windows + Text + Debug + + + Name + TxdStore.h + Windows + Text + + + + Name + VisibilityPlugins.cpp + Windows + Text + Debug + + + Name + VisibilityPlugins.h + Windows + Text + + + + Name + Date.cpp + Windows + Text + Debug + + + Name + Date.h + Windows + Text + + + + Name + GenericGameStorage.cpp + Windows + Text + Debug + + + Name + GenericGameStorage.h + Windows + Text + + + + Name + MemoryCard.cpp + Windows + Text + Debug + + + Name + MemoryCard.h + Windows + Text + + + + Name + PCSave.cpp + Windows + Text + Debug + + + Name + PCSave.h + Windows + Text + + + + Name + crossplatform.cpp + Windows + Text + Debug + + + Name + crossplatform.h + Windows + Text + + + + Name + events.cpp + Windows + Text + Debug + + + Name + events.h + Windows + Text + + + + Name + platform.h + Windows + Text + + + + Name + skeleton.cpp + Windows + Text + Debug + + + Name + skeleton.h + Windows + Text + + + + Name + resource.h + Windows + Text + + + + Name + win.cpp + Windows + Text + Debug + + + Name + win.h + Windows + Text + + + + Name + win.rc + Windows + Text + Debug + + + Name + Messages.cpp + Windows + Text + Debug + + + Name + Messages.h + Windows + Text + + + + Name + Pager.cpp + Windows + Text + Debug + + + Name + Pager.h + Windows + Text + + + + Name + Text.cpp + Windows + Text + Debug + + + Name + Text.h + Windows + Text + + + + Name + Automobile.cpp + Windows + Text + Debug + + + Name + Automobile.h + Windows + Text + + + + Name + Bike.h + Windows + Text + + + + Name + Boat.cpp + Windows + Text + Debug + + + Name + Boat.h + Windows + Text + + + + Name + CarGen.cpp + Windows + Text + Debug + + + Name + CarGen.h + Windows + Text + + + + Name + Cranes.cpp + Windows + Text + Debug + + + Name + Cranes.h + Windows + Text + + + + Name + DamageManager.cpp + Windows + Text + Debug + + + Name + DamageManager.h + Windows + Text + + + + Name + Door.cpp + Windows + Text + Debug + + + Name + Door.h + Windows + Text + + + + Name + Floater.cpp + Windows + Text + Debug + + + Name + Floater.h + Windows + Text + + + + Name + HandlingMgr.cpp + Windows + Text + Debug + + + Name + HandlingMgr.h + Windows + Text + + + + Name + Heli.cpp + Windows + Text + Debug + + + Name + Heli.h + Windows + Text + + + + Name + Plane.cpp + Windows + Text + Debug + + + Name + Plane.h + Windows + Text + + + + Name + Train.cpp + Windows + Text + Debug + + + Name + Train.h + Windows + Text + + + + Name + Transmission.cpp + Windows + Text + Debug + + + Name + Transmission.h + Windows + Text + + + + Name + Vehicle.cpp + Windows + Text + Debug + + + Name + Vehicle.h + Windows + Text + + + + Name + BulletInfo.cpp + Windows + Text + Debug + + + Name + BulletInfo.h + Windows + Text + + + + Name + Explosion.cpp + Windows + Text + Debug + + + Name + Explosion.h + Windows + Text + + + + Name + ProjectileInfo.cpp + Windows + Text + Debug + + + Name + ProjectileInfo.h + Windows + Text + + + + Name + ShotInfo.cpp + Windows + Text + Debug + + + Name + ShotInfo.h + Windows + Text + + + + Name + Weapon.cpp + Windows + Text + Debug + + + Name + Weapon.h + Windows + Text + + + + Name + WeaponEffects.cpp + Windows + Text + Debug + + + Name + WeaponEffects.h + Windows + Text + + + + Name + WeaponInfo.cpp + Windows + Text + Debug + + + Name + WeaponInfo.h + Windows + Text + + + + Name + WeaponType.h + Windows + Text + + + + Name + mss32.lib + Windows + Library + Debug + + + Name + d3d8.lib + Windows + Library + Debug + + + Name + ddraw.lib + Windows + Library + Debug + + + Name + dxguid.lib + Windows + Library + Debug + + + Name + strmiids.lib + Windows + Library + Debug + + + Name + dinput8.lib + Windows + Library + Debug + + + Name + winmm.lib + Windows + Library + Debug + + + Name + rwcore.lib + Windows + Library + Debug + + + Name + rpworld.lib + Windows + Library + Debug + + + Name + rpmatfx.lib + Windows + Library + Debug + + + Name + rpskin.lib + Windows + Library + Debug + + + Name + rphanim.lib + Windows + Library + Debug + + + Name + rtbmp.lib + Windows + Library + Debug + + + Name + rtquat.lib + Windows + Library + Debug + + + Name + rtcharse.lib + Windows + Library + Debug + + + Name + ole32.lib + Windows + Library + Debug + + + Name + shell32.lib + Windows + Library + Debug + + + Name + uuid.lib + Windows + Library + Debug + + + + + Name + AnimationId.h + Windows + + + Name + AnimBlendAssocGroup.cpp + Windows + + + Name + AnimBlendAssocGroup.h + Windows + + + Name + AnimBlendAssociation.cpp + Windows + + + Name + AnimBlendAssociation.h + Windows + + + Name + AnimBlendClumpData.cpp + Windows + + + Name + AnimBlendClumpData.h + Windows + + + Name + AnimBlendHierarchy.cpp + Windows + + + Name + AnimBlendHierarchy.h + Windows + + + Name + AnimBlendList.h + Windows + + + Name + AnimBlendNode.cpp + Windows + + + Name + AnimBlendNode.h + Windows + + + Name + AnimBlendSequence.cpp + Windows + + + Name + AnimBlendSequence.h + Windows + + + Name + AnimManager.cpp + Windows + + + Name + AnimManager.h + Windows + + + Name + Bones.cpp + Windows + + + Name + Bones.h + Windows + + + Name + CutsceneMgr.cpp + Windows + + + Name + CutsceneMgr.h + Windows + + + Name + FrameUpdate.cpp + Windows + + + Name + RpAnimBlend.cpp + Windows + + + Name + RpAnimBlend.h + Windows + + + Name + audio_enums.h + Windows + + + Name + AudioCollision.cpp + Windows + + + Name + AudioCollision.h + Windows + + + Name + AudioLogic.cpp + Windows + + + Name + AudioManager.cpp + Windows + + + Name + AudioManager.h + Windows + + + Name + AudioSamples.h + Windows + + + Name + AudioScriptObject.cpp + Windows + + + Name + AudioScriptObject.h + Windows + + + Name + DMAudio.cpp + Windows + + + Name + DMAudio.h + Windows + + + Name + MusicManager.cpp + Windows + + + Name + MusicManager.h + Windows + + + Name + PolRadio.cpp + Windows + + + Name + PolRadio.h + Windows + + + Name + sampman.h + Windows + + + Name + sampman_miles.cpp + Windows + + + Name + soundlist.h + Windows + + + Name + eax.h + Windows + + + Name + eax-util.cpp + Windows + + + Name + eax-util.h + Windows + + + Name + Building.cpp + Windows + + + Name + Building.h + Windows + + + Name + Solid.h + Windows + + + Name + Treadable.cpp + Windows + + + Name + Treadable.h + Windows + + + Name + ColBox.cpp + Windows + + + Name + ColBox.h + Windows + + + Name + ColLine.cpp + Windows + + + Name + ColLine.h + Windows + + + Name + Collision.cpp + Windows + + + Name + Collision.h + Windows + + + Name + ColModel.cpp + Windows + + + Name + ColModel.h + Windows + + + Name + ColPoint.cpp + Windows + + + Name + ColPoint.h + Windows + + + Name + ColSphere.cpp + Windows + + + Name + ColSphere.h + Windows + + + Name + ColTriangle.cpp + Windows + + + Name + ColTriangle.h + Windows + + + Name + CompressedVector.h + Windows + + + Name + TempColModels.cpp + Windows + + + Name + TempColModels.h + Windows + + + Name + VuCollision.cpp + Windows + + + Name + VuCollision.h + Windows + + + Name + AutoPilot.cpp + Windows + + + Name + AutoPilot.h + Windows + + + Name + Bridge.cpp + Windows + + + Name + Bridge.h + Windows + + + Name + CarAI.cpp + Windows + + + Name + CarAI.h + Windows + + + Name + CarCtrl.cpp + Windows + + + Name + CarCtrl.h + Windows + + + Name + Curves.cpp + Windows + + + Name + Curves.h + Windows + + + Name + Darkel.cpp + Windows + + + Name + Darkel.h + Windows + + + Name + GameLogic.cpp + Windows + + + Name + GameLogic.h + Windows + + + Name + Garages.cpp + Windows + + + Name + Garages.h + Windows + + + Name + NameGrid.cpp + Windows + + + Name + NameGrid.h + Windows + + + Name + OnscreenTimer.cpp + Windows + + + Name + OnscreenTimer.h + Windows + + + Name + PathFind.cpp + Windows + + + Name + PathFind.h + Windows + + + Name + Phones.cpp + Windows + + + Name + Phones.h + Windows + + + Name + Pickups.cpp + Windows + + + Name + Pickups.h + Windows + + + Name + PowerPoints.cpp + Windows + + + Name + PowerPoints.h + Windows + + + Name + Record.cpp + Windows + + + Name + Record.h + Windows + + + Name + Remote.cpp + Windows + + + Name + Remote.h + Windows + + + Name + Replay.cpp + Windows + + + Name + Replay.h + Windows + + + Name + Restart.cpp + Windows + + + Name + Restart.h + Windows + + + Name + RoadBlocks.cpp + Windows + + + Name + RoadBlocks.h + Windows + + + Name + SceneEdit.cpp + Windows + + + Name + SceneEdit.h + Windows + + + Name + ScriptDebug.cpp + Windows + + + Name + Script.cpp + Windows + + + Name + Script.h + Windows + + + Name + Script2.cpp + Windows + + + Name + Script3.cpp + Windows + + + Name + Script4.cpp + Windows + + + Name + Script5.cpp + Windows + + + Name + Script6.cpp + Windows + + + Name + ScriptCommands.h + Windows + + + Name + TrafficLights.cpp + Windows + + + Name + TrafficLights.h + Windows + + + Name + Accident.cpp + Windows + + + Name + Accident.h + Windows + + + Name + AnimViewer.cpp + Windows + + + Name + AnimViewer.h + Windows + + + Name + Cam.cpp + Windows + + + Name + Camera.cpp + Windows + + + Name + Camera.h + Windows + + + Name + CdStream.cpp + Windows + + + Name + CdStream.h + Windows + + + Name + CdStreamPosix.cpp + Windows + + + Name + Clock.cpp + Windows + + + Name + Clock.h + Windows + + + Name + common.h + Windows + + + Name + config.h + Windows + + + Name + ControllerConfig.cpp + Windows + + + Name + ControllerConfig.h + Windows + + + Name + Crime.h + Windows + + + Name + Debug.cpp + Windows + + + Name + Debug.h + Windows + + + Name + Directory.cpp + Windows + + + Name + Directory.h + Windows + + + Name + EventList.cpp + Windows + + + Name + EventList.h + Windows + + + Name + FileLoader.cpp + Windows + + + Name + FileLoader.h + Windows + + + Name + FileMgr.cpp + Windows + + + Name + FileMgr.h + Windows + + + Name + Fire.cpp + Windows + + + Name + Fire.h + Windows + + + Name + Frontend.cpp + Windows + + + Name + Frontend.h + Windows + + + Name + Frontend_PS2.cpp + Windows + + + Name + Frontend_PS2.h + Windows + + + Name + FrontEndControls.cpp + Windows + + + Name + FrontEndControls.h + Windows + + + Name + FrontendTriggers.h + Windows + + + Name + Game.cpp + Windows + + + Name + Game.h + Windows + + + Name + General.h + Windows + + + Name + IniFile.cpp + Windows + + + Name + IniFile.h + Windows + + + Name + Lists.cpp + Windows + + + Name + Lists.h + Windows + + + Name + main.cpp + Windows + + + Name + main.h + Windows + + + Name + MenuScreens.cpp + Windows + + + Name + MenuScreensCustom.cpp + Windows + + + Name + obrstr.cpp + Windows + + + Name + obrstr.h + Windows + + + Name + Pad.cpp + Windows + + + Name + Pad.h + Windows + + + Name + Placeable.cpp + Windows + + + Name + Placeable.h + Windows + + + Name + PlayerInfo.cpp + Windows + + + Name + PlayerInfo.h + Windows + + + Name + Pools.cpp + Windows + + + Name + Pools.h + Windows + + + Name + Profile.cpp + Windows + + + Name + Profile.h + Windows + + + Name + Radar.cpp + Windows + + + Name + Radar.h + Windows + + + Name + Range2D.cpp + Windows + + + Name + Range2D.h + Windows + + + Name + Range3D.cpp + Windows + + + Name + Range3D.h + Windows + + + Name + re3.cpp + Windows + + + Name + References.cpp + Windows + + + Name + References.h + Windows + + + Name + Stats.cpp + Windows + + + Name + Stats.h + Windows + + + Name + Streaming.cpp + Windows + + + Name + Streaming.h + Windows + + + Name + SurfaceTable.cpp + Windows + + + Name + SurfaceTable.h + Windows + + + Name + templates.h + Windows + + + Name + timebars.cpp + Windows + + + Name + timebars.h + Windows + + + Name + Timer.cpp + Windows + + + Name + Timer.h + Windows + + + Name + TimeStep.cpp + Windows + + + Name + TimeStep.h + Windows + + + Name + User.cpp + Windows + + + Name + User.h + Windows + + + Name + Wanted.cpp + Windows + + + Name + Wanted.h + Windows + + + Name + World.cpp + Windows + + + Name + World.h + Windows + + + Name + ZoneCull.cpp + Windows + + + Name + ZoneCull.h + Windows + + + Name + Zones.cpp + Windows + + + Name + Zones.h + Windows + + + Name + Dummy.cpp + Windows + + + Name + Dummy.h + Windows + + + Name + Entity.cpp + Windows + + + Name + Entity.h + Windows + + + Name + Physical.cpp + Windows + + + Name + Physical.h + Windows + + + Name + math.cpp + Windows + + + Name + maths.h + Windows + + + Name + Matrix.cpp + Windows + + + Name + Matrix.h + Windows + + + Name + Quaternion.cpp + Windows + + + Name + Quaternion.h + Windows + + + Name + Rect.cpp + Windows + + + Name + Rect.h + Windows + + + Name + Vector.cpp + Windows + + + Name + Vector.h + Windows + + + Name + Vector2D.h + Windows + + + Name + VuVector.h + Windows + + + Name + BaseModelInfo.cpp + Windows + + + Name + BaseModelInfo.h + Windows + + + Name + ClumpModelInfo.cpp + Windows + + + Name + ClumpModelInfo.h + Windows + + + Name + MloModelInfo.cpp + Windows + + + Name + MloModelInfo.h + Windows + + + Name + ModelIndices.cpp + Windows + + + Name + ModelIndices.h + Windows + + + Name + ModelInfo.cpp + Windows + + + Name + ModelInfo.h + Windows + + + Name + PedModelInfo.cpp + Windows + + + Name + PedModelInfo.h + Windows + + + Name + SimpleModelInfo.cpp + Windows + + + Name + SimpleModelInfo.h + Windows + + + Name + TimeModelInfo.cpp + Windows + + + Name + TimeModelInfo.h + Windows + + + Name + VehicleModelInfo.cpp + Windows + + + Name + VehicleModelInfo.h + Windows + + + Name + XtraCompsModelInfo.h + Windows + + + Name + CutsceneHead.cpp + Windows + + + Name + CutsceneHead.h + Windows + + + Name + CutsceneObject.cpp + Windows + + + Name + CutsceneObject.h + Windows + + + Name + DummyObject.cpp + Windows + + + Name + DummyObject.h + Windows + + + Name + Object.cpp + Windows + + + Name + Object.h + Windows + + + Name + ObjectData.cpp + Windows + + + Name + ObjectData.h + Windows + + + Name + ParticleObject.cpp + Windows + + + Name + ParticleObject.h + Windows + + + Name + Projectile.cpp + Windows + + + Name + Projectile.h + Windows + + + Name + CivilianPed.cpp + Windows + + + Name + CivilianPed.h + Windows + + + Name + CopPed.cpp + Windows + + + Name + CopPed.h + Windows + + + Name + DummyPed.h + Windows + + + Name + EmergencyPed.cpp + Windows + + + Name + EmergencyPed.h + Windows + + + Name + Gangs.cpp + Windows + + + Name + Gangs.h + Windows + + + Name + Ped.cpp + Windows + + + Name + Ped.h + Windows + + + Name + PedAI.cpp + Windows + + + Name + PedChat.cpp + Windows + + + Name + PedDebug.cpp + Windows + + + Name + PedFight.cpp + Windows + + + Name + PedIK.cpp + Windows + + + Name + PedIK.h + Windows + + + Name + PedPlacement.cpp + Windows + + + Name + PedPlacement.h + Windows + + + Name + PedRoutes.cpp + Windows + + + Name + PedRoutes.h + Windows + + + Name + PedType.cpp + Windows + + + Name + PedType.h + Windows + + + Name + PlayerPed.cpp + Windows + + + Name + PlayerPed.h + Windows + + + Name + Population.cpp + Windows + + + Name + Population.h + Windows + + + Name + 2dEffect.h + Windows + + + Name + Antennas.cpp + Windows + + + Name + Antennas.h + Windows + + + Name + Clouds.cpp + Windows + + + Name + Clouds.h + Windows + + + Name + Console.cpp + Windows + + + Name + Console.h + Windows + + + Name + Coronas.cpp + Windows + + + Name + Coronas.h + Windows + + + Name + Credits.cpp + Windows + + + Name + Credits.h + Windows + + + Name + Draw.cpp + Windows + + + Name + Draw.h + Windows + + + Name + Fluff.cpp + Windows + + + Name + Fluff.h + Windows + + + Name + Font.cpp + Windows + + + Name + Font.h + Windows + + + Name + Glass.cpp + Windows + + + Name + Glass.h + Windows + + + Name + Hud.cpp + Windows + + + Name + Hud.h + Windows + + + Name + Instance.cpp + Windows + + + Name + Instance.h + Windows + + + Name + Lines.cpp + Windows + + + Name + Lines.h + Windows + + + Name + MBlur.cpp + Windows + + + Name + MBlur.h + Windows + + + Name + Particle.cpp + Windows + + + Name + Particle.h + Windows + + + Name + ParticleMgr.cpp + Windows + + + Name + ParticleMgr.h + Windows + + + Name + ParticleType.h + Windows + + + Name + PlayerSkin.cpp + Windows + + + Name + PlayerSkin.h + Windows + + + Name + PointLights.cpp + Windows + + + Name + PointLights.h + Windows + + + Name + RenderBuffer.cpp + Windows + + + Name + RenderBuffer.h + Windows + + + Name + Renderer.cpp + Windows + + + Name + Renderer.h + Windows + + + Name + Rubbish.cpp + Windows + + + Name + Rubbish.h + Windows + + + Name + Shadows.cpp + Windows + + + Name + Shadows.h + Windows + + + Name + Skidmarks.cpp + Windows + + + Name + Skidmarks.h + Windows + + + Name + SpecialFX.cpp + Windows + + + Name + SpecialFX.h + Windows + + + Name + Sprite.cpp + Windows + + + Name + Sprite.h + Windows + + + Name + Sprite2d.cpp + Windows + + + Name + Sprite2d.h + Windows + + + Name + TexList.cpp + Windows + + + Name + TexList.h + Windows + + + Name + Timecycle.cpp + Windows + + + Name + Timecycle.h + Windows + + + Name + WaterCannon.cpp + Windows + + + Name + WaterCannon.h + Windows + + + Name + WaterLevel.cpp + Windows + + + Name + WaterLevel.h + Windows + + + Name + Weather.cpp + Windows + + + Name + Weather.h + Windows + + + Name + ClumpRead.cpp + Windows + + + Name + Lights.cpp + Windows + + + Name + Lights.h + Windows + + + Name + MemoryHeap.cpp + Windows + + + Name + MemoryHeap.h + Windows + + + Name + MemoryMgr.cpp + Windows + + + Name + MemoryMgr.h + Windows + + + Name + NodeName.cpp + Windows + + + Name + NodeName.h + Windows + + + Name + RwHelper.cpp + Windows + + + Name + RwHelper.h + Windows + + + Name + RwMatFX.cpp + Windows + + + Name + RwPS2AlphaTest.cpp + Windows + + + Name + TexRead.cpp + Windows + + + Name + TexturePools.cpp + Windows + + + Name + TexturePools.h + Windows + + + Name + TxdStore.cpp + Windows + + + Name + TxdStore.h + Windows + + + Name + VisibilityPlugins.cpp + Windows + + + Name + VisibilityPlugins.h + Windows + + + Name + Date.cpp + Windows + + + Name + Date.h + Windows + + + Name + GenericGameStorage.cpp + Windows + + + Name + GenericGameStorage.h + Windows + + + Name + MemoryCard.cpp + Windows + + + Name + MemoryCard.h + Windows + + + Name + PCSave.cpp + Windows + + + Name + PCSave.h + Windows + + + Name + crossplatform.cpp + Windows + + + Name + crossplatform.h + Windows + + + Name + events.cpp + Windows + + + Name + events.h + Windows + + + Name + platform.h + Windows + + + Name + skeleton.cpp + Windows + + + Name + skeleton.h + Windows + + + Name + resource.h + Windows + + + Name + win.cpp + Windows + + + Name + win.h + Windows + + + Name + win.rc + Windows + + + Name + Messages.cpp + Windows + + + Name + Messages.h + Windows + + + Name + Pager.cpp + Windows + + + Name + Pager.h + Windows + + + Name + Text.cpp + Windows + + + Name + Text.h + Windows + + + Name + Automobile.cpp + Windows + + + Name + Automobile.h + Windows + + + Name + Bike.h + Windows + + + Name + Boat.cpp + Windows + + + Name + Boat.h + Windows + + + Name + CarGen.cpp + Windows + + + Name + CarGen.h + Windows + + + Name + Cranes.cpp + Windows + + + Name + Cranes.h + Windows + + + Name + DamageManager.cpp + Windows + + + Name + DamageManager.h + Windows + + + Name + Door.cpp + Windows + + + Name + Door.h + Windows + + + Name + Floater.cpp + Windows + + + Name + Floater.h + Windows + + + Name + HandlingMgr.cpp + Windows + + + Name + HandlingMgr.h + Windows + + + Name + Heli.cpp + Windows + + + Name + Heli.h + Windows + + + Name + Plane.cpp + Windows + + + Name + Plane.h + Windows + + + Name + Train.cpp + Windows + + + Name + Train.h + Windows + + + Name + Transmission.cpp + Windows + + + Name + Transmission.h + Windows + + + Name + Vehicle.cpp + Windows + + + Name + Vehicle.h + Windows + + + Name + BulletInfo.cpp + Windows + + + Name + BulletInfo.h + Windows + + + Name + Explosion.cpp + Windows + + + Name + Explosion.h + Windows + + + Name + ProjectileInfo.cpp + Windows + + + Name + ProjectileInfo.h + Windows + + + Name + ShotInfo.cpp + Windows + + + Name + ShotInfo.h + Windows + + + Name + Weapon.cpp + Windows + + + Name + Weapon.h + Windows + + + Name + WeaponEffects.cpp + Windows + + + Name + WeaponEffects.h + Windows + + + Name + WeaponInfo.cpp + Windows + + + Name + WeaponInfo.h + Windows + + + Name + WeaponType.h + Windows + + + Name + mss32.lib + Windows + + + Name + d3d8.lib + Windows + + + Name + ddraw.lib + Windows + + + Name + dxguid.lib + Windows + + + Name + strmiids.lib + Windows + + + Name + dinput8.lib + Windows + + + Name + winmm.lib + Windows + + + Name + rwcore.lib + Windows + + + Name + rpworld.lib + Windows + + + Name + rpmatfx.lib + Windows + + + Name + rpskin.lib + Windows + + + Name + rphanim.lib + Windows + + + Name + rtbmp.lib + Windows + + + Name + rtquat.lib + Windows + + + Name + rtcharse.lib + Windows + + + Name + MSL_All_x86.lib + MacOS + + + Name + Comdlg32.lib + MacOS + + + Name + Gdi32.lib + MacOS + + + Name + Kernel32.lib + MacOS + + + Name + User32.lib + MacOS + + + Name + ole32.lib + Windows + + + Name + shell32.lib + Windows + + + Name + uuid.lib + Windows + + + + + + + Debug + Release + + + + animation + + Debug + Name + AnimationId.h + Windows + + + Debug + Name + AnimBlendAssocGroup.cpp + Windows + + + Debug + Name + AnimBlendAssocGroup.h + Windows + + + Debug + Name + AnimBlendAssociation.cpp + Windows + + + Debug + Name + AnimBlendAssociation.h + Windows + + + Debug + Name + AnimBlendClumpData.cpp + Windows + + + Debug + Name + AnimBlendClumpData.h + Windows + + + Debug + Name + AnimBlendHierarchy.cpp + Windows + + + Debug + Name + AnimBlendHierarchy.h + Windows + + + Debug + Name + AnimBlendList.h + Windows + + + Debug + Name + AnimBlendNode.cpp + Windows + + + Debug + Name + AnimBlendNode.h + Windows + + + Debug + Name + AnimBlendSequence.cpp + Windows + + + Debug + Name + AnimBlendSequence.h + Windows + + + Debug + Name + AnimManager.cpp + Windows + + + Debug + Name + AnimManager.h + Windows + + + Debug + Name + Bones.cpp + Windows + + + Debug + Name + Bones.h + Windows + + + Debug + Name + CutsceneMgr.cpp + Windows + + + Debug + Name + CutsceneMgr.h + Windows + + + Debug + Name + FrameUpdate.cpp + Windows + + + Debug + Name + RpAnimBlend.cpp + Windows + + + Debug + Name + RpAnimBlend.h + Windows + + + audio + + Debug + Name + audio_enums.h + Windows + + + Debug + Name + AudioCollision.cpp + Windows + + + Debug + Name + AudioCollision.h + Windows + + + Debug + Name + AudioLogic.cpp + Windows + + + Debug + Name + AudioManager.cpp + Windows + + + Debug + Name + AudioManager.h + Windows + + + Debug + Name + AudioSamples.h + Windows + + + Debug + Name + AudioScriptObject.cpp + Windows + + + Debug + Name + AudioScriptObject.h + Windows + + + Debug + Name + DMAudio.cpp + Windows + + + Debug + Name + DMAudio.h + Windows + + + Debug + Name + MusicManager.cpp + Windows + + + Debug + Name + MusicManager.h + Windows + + + Release + Name + PolRadio.cpp + Windows + + + Release + Name + PolRadio.h + Windows + + + Debug + Name + sampman.h + Windows + + + Debug + Name + sampman_miles.cpp + Windows + + + Debug + Name + soundlist.h + Windows + + + Debug + Name + eax.h + Windows + + + Debug + Name + eax-util.cpp + Windows + + + Debug + Name + eax-util.h + Windows + + + buildings + + Debug + Name + Building.cpp + Windows + + + Debug + Name + Building.h + Windows + + + Debug + Name + Solid.h + Windows + + + Debug + Name + Treadable.cpp + Windows + + + Debug + Name + Treadable.h + Windows + + + collision + + Debug + Name + ColBox.cpp + Windows + + + Debug + Name + ColBox.h + Windows + + + Debug + Name + ColLine.cpp + Windows + + + Debug + Name + ColLine.h + Windows + + + Debug + Name + Collision.cpp + Windows + + + Debug + Name + Collision.h + Windows + + + Debug + Name + ColModel.cpp + Windows + + + Debug + Name + ColModel.h + Windows + + + Debug + Name + ColPoint.cpp + Windows + + + Debug + Name + ColPoint.h + Windows + + + Debug + Name + ColSphere.cpp + Windows + + + Debug + Name + ColSphere.h + Windows + + + Debug + Name + ColTriangle.cpp + Windows + + + Debug + Name + ColTriangle.h + Windows + + + Debug + Name + CompressedVector.h + Windows + + + Debug + Name + TempColModels.cpp + Windows + + + Debug + Name + TempColModels.h + Windows + + + Debug + Name + VuCollision.cpp + Windows + + + Debug + Name + VuCollision.h + Windows + + + control + + Debug + Name + AutoPilot.cpp + Windows + + + Debug + Name + AutoPilot.h + Windows + + + Debug + Name + Bridge.cpp + Windows + + + Debug + Name + Bridge.h + Windows + + + Debug + Name + CarAI.cpp + Windows + + + Debug + Name + CarAI.h + Windows + + + Debug + Name + CarCtrl.cpp + Windows + + + Debug + Name + CarCtrl.h + Windows + + + Debug + Name + Curves.cpp + Windows + + + Debug + Name + Curves.h + Windows + + + Debug + Name + Darkel.cpp + Windows + + + Debug + Name + Darkel.h + Windows + + + Debug + Name + GameLogic.cpp + Windows + + + Debug + Name + GameLogic.h + Windows + + + Debug + Name + Garages.cpp + Windows + + + Debug + Name + Garages.h + Windows + + + Debug + Name + NameGrid.cpp + Windows + + + Debug + Name + NameGrid.h + Windows + + + Debug + Name + OnscreenTimer.cpp + Windows + + + Debug + Name + OnscreenTimer.h + Windows + + + Debug + Name + PathFind.cpp + Windows + + + Debug + Name + PathFind.h + Windows + + + Debug + Name + Phones.cpp + Windows + + + Debug + Name + Phones.h + Windows + + + Debug + Name + Pickups.cpp + Windows + + + Debug + Name + Pickups.h + Windows + + + Debug + Name + PowerPoints.cpp + Windows + + + Debug + Name + PowerPoints.h + Windows + + + Debug + Name + Record.cpp + Windows + + + Debug + Name + Record.h + Windows + + + Debug + Name + Remote.cpp + Windows + + + Debug + Name + Remote.h + Windows + + + Debug + Name + Replay.cpp + Windows + + + Debug + Name + Replay.h + Windows + + + Debug + Name + Restart.cpp + Windows + + + Debug + Name + Restart.h + Windows + + + Debug + Name + RoadBlocks.cpp + Windows + + + Debug + Name + RoadBlocks.h + Windows + + + Debug + Name + SceneEdit.cpp + Windows + + + Debug + Name + SceneEdit.h + Windows + + + Debug + Name + ScriptDebug.cpp + Windows + + + Debug + Name + Script.cpp + Windows + + + Debug + Name + Script.h + Windows + + + Debug + Name + Script2.cpp + Windows + + + Debug + Name + Script3.cpp + Windows + + + Debug + Name + Script4.cpp + Windows + + + Debug + Name + Script5.cpp + Windows + + + Debug + Name + Script6.cpp + Windows + + + Debug + Name + ScriptCommands.h + Windows + + + Debug + Name + TrafficLights.cpp + Windows + + + Debug + Name + TrafficLights.h + Windows + + + core + + Debug + Name + Accident.cpp + Windows + + + Debug + Name + Accident.h + Windows + + + Debug + Name + AnimViewer.cpp + Windows + + + Debug + Name + AnimViewer.h + Windows + + + Debug + Name + Cam.cpp + Windows + + + Debug + Name + Camera.cpp + Windows + + + Debug + Name + Camera.h + Windows + + + Debug + Name + CdStream.cpp + Windows + + + Debug + Name + CdStream.h + Windows + + + Debug + Name + CdStreamPosix.cpp + Windows + + + Debug + Name + Clock.cpp + Windows + + + Debug + Name + Clock.h + Windows + + + Debug + Name + common.h + Windows + + + Debug + Name + config.h + Windows + + + Debug + Name + ControllerConfig.cpp + Windows + + + Debug + Name + ControllerConfig.h + Windows + + + Debug + Name + Crime.h + Windows + + + Debug + Name + Debug.cpp + Windows + + + Debug + Name + Debug.h + Windows + + + Debug + Name + Directory.cpp + Windows + + + Debug + Name + Directory.h + Windows + + + Debug + Name + EventList.cpp + Windows + + + Debug + Name + EventList.h + Windows + + + Debug + Name + FileLoader.cpp + Windows + + + Debug + Name + FileLoader.h + Windows + + + Debug + Name + FileMgr.cpp + Windows + + + Debug + Name + FileMgr.h + Windows + + + Debug + Name + Fire.cpp + Windows + + + Debug + Name + Fire.h + Windows + + + Debug + Name + Frontend.cpp + Windows + + + Debug + Name + Frontend.h + Windows + + + Debug + Name + Frontend_PS2.cpp + Windows + + + Debug + Name + Frontend_PS2.h + Windows + + + Debug + Name + FrontEndControls.cpp + Windows + + + Debug + Name + FrontEndControls.h + Windows + + + Debug + Name + FrontendTriggers.h + Windows + + + Debug + Name + Game.cpp + Windows + + + Debug + Name + Game.h + Windows + + + Debug + Name + General.h + Windows + + + Debug + Name + IniFile.cpp + Windows + + + Debug + Name + IniFile.h + Windows + + + Debug + Name + Lists.cpp + Windows + + + Debug + Name + Lists.h + Windows + + + Debug + Name + main.cpp + Windows + + + Debug + Name + main.h + Windows + + + Debug + Name + MenuScreens.cpp + Windows + + + Debug + Name + MenuScreensCustom.cpp + Windows + + + Debug + Name + obrstr.cpp + Windows + + + Debug + Name + obrstr.h + Windows + + + Debug + Name + Pad.cpp + Windows + + + Debug + Name + Pad.h + Windows + + + Debug + Name + Placeable.cpp + Windows + + + Debug + Name + Placeable.h + Windows + + + Debug + Name + PlayerInfo.cpp + Windows + + + Debug + Name + PlayerInfo.h + Windows + + + Debug + Name + Pools.cpp + Windows + + + Debug + Name + Pools.h + Windows + + + Debug + Name + Profile.cpp + Windows + + + Debug + Name + Profile.h + Windows + + + Debug + Name + Radar.cpp + Windows + + + Debug + Name + Radar.h + Windows + + + Debug + Name + Range2D.cpp + Windows + + + Debug + Name + Range2D.h + Windows + + + Debug + Name + Range3D.cpp + Windows + + + Debug + Name + Range3D.h + Windows + + + Debug + Name + re3.cpp + Windows + + + Debug + Name + References.cpp + Windows + + + Debug + Name + References.h + Windows + + + Debug + Name + Stats.cpp + Windows + + + Debug + Name + Stats.h + Windows + + + Debug + Name + Streaming.cpp + Windows + + + Debug + Name + Streaming.h + Windows + + + Debug + Name + SurfaceTable.cpp + Windows + + + Debug + Name + SurfaceTable.h + Windows + + + Debug + Name + templates.h + Windows + + + Debug + Name + timebars.cpp + Windows + + + Debug + Name + timebars.h + Windows + + + Debug + Name + Timer.cpp + Windows + + + Debug + Name + Timer.h + Windows + + + Debug + Name + TimeStep.cpp + Windows + + + Debug + Name + TimeStep.h + Windows + + + Debug + Name + User.cpp + Windows + + + Debug + Name + User.h + Windows + + + Debug + Name + Wanted.cpp + Windows + + + Debug + Name + Wanted.h + Windows + + + Debug + Name + World.cpp + Windows + + + Debug + Name + World.h + Windows + + + Debug + Name + ZoneCull.cpp + Windows + + + Debug + Name + ZoneCull.h + Windows + + + Debug + Name + Zones.cpp + Windows + + + Debug + Name + Zones.h + Windows + + + entities + + Debug + Name + Dummy.cpp + Windows + + + Debug + Name + Dummy.h + Windows + + + Debug + Name + Entity.cpp + Windows + + + Debug + Name + Entity.h + Windows + + + Debug + Name + Physical.cpp + Windows + + + Debug + Name + Physical.h + Windows + + + extras + + math + + Debug + Name + math.cpp + Windows + + + Debug + Name + maths.h + Windows + + + Debug + Name + Matrix.cpp + Windows + + + Debug + Name + Matrix.h + Windows + + + Debug + Name + Quaternion.cpp + Windows + + + Debug + Name + Quaternion.h + Windows + + + Debug + Name + Rect.cpp + Windows + + + Debug + Name + Rect.h + Windows + + + Debug + Name + Vector.cpp + Windows + + + Debug + Name + Vector.h + Windows + + + Debug + Name + Vector2D.h + Windows + + + Debug + Name + VuVector.h + Windows + + + modelinfo + + Debug + Name + BaseModelInfo.cpp + Windows + + + Debug + Name + BaseModelInfo.h + Windows + + + Debug + Name + ClumpModelInfo.cpp + Windows + + + Debug + Name + ClumpModelInfo.h + Windows + + + Debug + Name + MloModelInfo.cpp + Windows + + + Debug + Name + MloModelInfo.h + Windows + + + Debug + Name + ModelIndices.cpp + Windows + + + Debug + Name + ModelIndices.h + Windows + + + Debug + Name + ModelInfo.cpp + Windows + + + Debug + Name + ModelInfo.h + Windows + + + Debug + Name + PedModelInfo.cpp + Windows + + + Debug + Name + PedModelInfo.h + Windows + + + Debug + Name + SimpleModelInfo.cpp + Windows + + + Debug + Name + SimpleModelInfo.h + Windows + + + Debug + Name + TimeModelInfo.cpp + Windows + + + Debug + Name + TimeModelInfo.h + Windows + + + Debug + Name + VehicleModelInfo.cpp + Windows + + + Debug + Name + VehicleModelInfo.h + Windows + + + Debug + Name + XtraCompsModelInfo.h + Windows + + + objects + + Debug + Name + CutsceneHead.cpp + Windows + + + Debug + Name + CutsceneHead.h + Windows + + + Debug + Name + CutsceneObject.cpp + Windows + + + Debug + Name + CutsceneObject.h + Windows + + + Debug + Name + DummyObject.cpp + Windows + + + Debug + Name + DummyObject.h + Windows + + + Debug + Name + Object.cpp + Windows + + + Debug + Name + Object.h + Windows + + + Debug + Name + ObjectData.cpp + Windows + + + Debug + Name + ObjectData.h + Windows + + + Debug + Name + ParticleObject.cpp + Windows + + + Debug + Name + ParticleObject.h + Windows + + + Debug + Name + Projectile.cpp + Windows + + + Debug + Name + Projectile.h + Windows + + + peds + + Debug + Name + CivilianPed.cpp + Windows + + + Debug + Name + CivilianPed.h + Windows + + + Debug + Name + CopPed.cpp + Windows + + + Debug + Name + CopPed.h + Windows + + + Debug + Name + DummyPed.h + Windows + + + Debug + Name + EmergencyPed.cpp + Windows + + + Debug + Name + EmergencyPed.h + Windows + + + Debug + Name + Gangs.cpp + Windows + + + Debug + Name + Gangs.h + Windows + + + Debug + Name + Ped.cpp + Windows + + + Debug + Name + Ped.h + Windows + + + Debug + Name + PedAI.cpp + Windows + + + Debug + Name + PedChat.cpp + Windows + + + Debug + Name + PedDebug.cpp + Windows + + + Debug + Name + PedFight.cpp + Windows + + + Debug + Name + PedIK.cpp + Windows + + + Debug + Name + PedIK.h + Windows + + + Debug + Name + PedPlacement.cpp + Windows + + + Debug + Name + PedPlacement.h + Windows + + + Debug + Name + PedRoutes.cpp + Windows + + + Debug + Name + PedRoutes.h + Windows + + + Debug + Name + PedType.cpp + Windows + + + Debug + Name + PedType.h + Windows + + + Debug + Name + PlayerPed.cpp + Windows + + + Debug + Name + PlayerPed.h + Windows + + + Debug + Name + Population.cpp + Windows + + + Debug + Name + Population.h + Windows + + + renderer + + Debug + Name + 2dEffect.h + Windows + + + Debug + Name + Antennas.cpp + Windows + + + Debug + Name + Antennas.h + Windows + + + Debug + Name + Clouds.cpp + Windows + + + Debug + Name + Clouds.h + Windows + + + Debug + Name + Console.cpp + Windows + + + Debug + Name + Console.h + Windows + + + Debug + Name + Coronas.cpp + Windows + + + Debug + Name + Coronas.h + Windows + + + Debug + Name + Credits.cpp + Windows + + + Debug + Name + Credits.h + Windows + + + Debug + Name + Draw.cpp + Windows + + + Debug + Name + Draw.h + Windows + + + Debug + Name + Fluff.cpp + Windows + + + Debug + Name + Fluff.h + Windows + + + Debug + Name + Font.cpp + Windows + + + Debug + Name + Font.h + Windows + + + Debug + Name + Glass.cpp + Windows + + + Debug + Name + Glass.h + Windows + + + Debug + Name + Hud.cpp + Windows + + + Debug + Name + Hud.h + Windows + + + Debug + Name + Instance.cpp + Windows + + + Debug + Name + Instance.h + Windows + + + Debug + Name + Lines.cpp + Windows + + + Debug + Name + Lines.h + Windows + + + Debug + Name + MBlur.cpp + Windows + + + Debug + Name + MBlur.h + Windows + + + Debug + Name + Particle.cpp + Windows + + + Debug + Name + Particle.h + Windows + + + Debug + Name + ParticleMgr.cpp + Windows + + + Debug + Name + ParticleMgr.h + Windows + + + Debug + Name + ParticleType.h + Windows + + + Debug + Name + PlayerSkin.cpp + Windows + + + Debug + Name + PlayerSkin.h + Windows + + + Debug + Name + PointLights.cpp + Windows + + + Debug + Name + PointLights.h + Windows + + + Debug + Name + RenderBuffer.cpp + Windows + + + Debug + Name + RenderBuffer.h + Windows + + + Debug + Name + Renderer.cpp + Windows + + + Debug + Name + Renderer.h + Windows + + + Debug + Name + Rubbish.cpp + Windows + + + Debug + Name + Rubbish.h + Windows + + + Debug + Name + Shadows.cpp + Windows + + + Debug + Name + Shadows.h + Windows + + + Debug + Name + Skidmarks.cpp + Windows + + + Debug + Name + Skidmarks.h + Windows + + + Debug + Name + SpecialFX.cpp + Windows + + + Debug + Name + SpecialFX.h + Windows + + + Debug + Name + Sprite.cpp + Windows + + + Debug + Name + Sprite.h + Windows + + + Debug + Name + Sprite2d.cpp + Windows + + + Debug + Name + Sprite2d.h + Windows + + + Debug + Name + TexList.cpp + Windows + + + Debug + Name + TexList.h + Windows + + + Debug + Name + Timecycle.cpp + Windows + + + Debug + Name + Timecycle.h + Windows + + + Debug + Name + WaterCannon.cpp + Windows + + + Debug + Name + WaterCannon.h + Windows + + + Debug + Name + WaterLevel.cpp + Windows + + + Debug + Name + WaterLevel.h + Windows + + + Debug + Name + Weather.cpp + Windows + + + Debug + Name + Weather.h + Windows + + + rw + + Debug + Name + ClumpRead.cpp + Windows + + + Debug + Name + Lights.cpp + Windows + + + Debug + Name + Lights.h + Windows + + + Debug + Name + MemoryHeap.cpp + Windows + + + Debug + Name + MemoryHeap.h + Windows + + + Debug + Name + MemoryMgr.cpp + Windows + + + Debug + Name + MemoryMgr.h + Windows + + + Debug + Name + NodeName.cpp + Windows + + + Debug + Name + NodeName.h + Windows + + + Debug + Name + RwHelper.cpp + Windows + + + Debug + Name + RwHelper.h + Windows + + + Debug + Name + RwMatFX.cpp + Windows + + + Debug + Name + RwPS2AlphaTest.cpp + Windows + + + Debug + Name + TexRead.cpp + Windows + + + Debug + Name + TexturePools.cpp + Windows + + + Debug + Name + TexturePools.h + Windows + + + Debug + Name + TxdStore.cpp + Windows + + + Debug + Name + TxdStore.h + Windows + + + Debug + Name + VisibilityPlugins.cpp + Windows + + + Debug + Name + VisibilityPlugins.h + Windows + + + save + + Debug + Name + Date.cpp + Windows + + + Debug + Name + Date.h + Windows + + + Debug + Name + GenericGameStorage.cpp + Windows + + + Debug + Name + GenericGameStorage.h + Windows + + + Debug + Name + MemoryCard.cpp + Windows + + + Debug + Name + MemoryCard.h + Windows + + + Debug + Name + PCSave.cpp + Windows + + + Debug + Name + PCSave.h + Windows + + + skel + + Debug + Name + crossplatform.cpp + Windows + + + Debug + Name + crossplatform.h + Windows + + + Debug + Name + events.cpp + Windows + + + Debug + Name + events.h + Windows + + + Debug + Name + platform.h + Windows + + + Debug + Name + skeleton.cpp + Windows + + + Debug + Name + skeleton.h + Windows + + + Debug + Name + resource.h + Windows + + + Debug + Name + win.cpp + Windows + + + Debug + Name + win.h + Windows + + + Debug + Name + win.rc + Windows + + + text + + Debug + Name + Messages.cpp + Windows + + + Debug + Name + Messages.h + Windows + + + Debug + Name + Pager.cpp + Windows + + + Debug + Name + Pager.h + Windows + + + Debug + Name + Text.cpp + Windows + + + Debug + Name + Text.h + Windows + + + vehicles + + Debug + Name + Automobile.cpp + Windows + + + Debug + Name + Automobile.h + Windows + + + Debug + Name + Bike.h + Windows + + + Debug + Name + Boat.cpp + Windows + + + Debug + Name + Boat.h + Windows + + + Debug + Name + CarGen.cpp + Windows + + + Debug + Name + CarGen.h + Windows + + + Debug + Name + Cranes.cpp + Windows + + + Debug + Name + Cranes.h + Windows + + + Debug + Name + DamageManager.cpp + Windows + + + Debug + Name + DamageManager.h + Windows + + + Debug + Name + Door.cpp + Windows + + + Debug + Name + Door.h + Windows + + + Debug + Name + Floater.cpp + Windows + + + Debug + Name + Floater.h + Windows + + + Debug + Name + HandlingMgr.cpp + Windows + + + Debug + Name + HandlingMgr.h + Windows + + + Debug + Name + Heli.cpp + Windows + + + Debug + Name + Heli.h + Windows + + + Debug + Name + Plane.cpp + Windows + + + Debug + Name + Plane.h + Windows + + + Debug + Name + Train.cpp + Windows + + + Debug + Name + Train.h + Windows + + + Debug + Name + Transmission.cpp + Windows + + + Debug + Name + Transmission.h + Windows + + + Debug + Name + Vehicle.cpp + Windows + + + Debug + Name + Vehicle.h + Windows + + + weapons + + Debug + Name + BulletInfo.cpp + Windows + + + Debug + Name + BulletInfo.h + Windows + + + Debug + Name + Explosion.cpp + Windows + + + Debug + Name + Explosion.h + Windows + + + Debug + Name + ProjectileInfo.cpp + Windows + + + Debug + Name + ProjectileInfo.h + Windows + + + Debug + Name + ShotInfo.cpp + Windows + + + Debug + Name + ShotInfo.h + Windows + + + Debug + Name + Weapon.cpp + Windows + + + Debug + Name + Weapon.h + Windows + + + Debug + Name + WeaponEffects.cpp + Windows + + + Debug + Name + WeaponEffects.h + Windows + + + Debug + Name + WeaponInfo.cpp + Windows + + + Debug + Name + WeaponInfo.h + Windows + + + Debug + Name + WeaponType.h + Windows + + + RenderWare + + Debug + Name + rwcore.lib + Windows + + + Debug + Name + rpworld.lib + Windows + + + Debug + Name + rpmatfx.lib + Windows + + + Debug + Name + rpskin.lib + Windows + + + Debug + Name + rphanim.lib + Windows + + + Debug + Name + rtbmp.lib + Windows + + + Debug + Name + rtquat.lib + Windows + + + Debug + Name + rtcharse.lib + Windows + + + DirectX + + Debug + Name + d3d8.lib + Windows + + + Debug + Name + ddraw.lib + Windows + + + Debug + Name + dxguid.lib + Windows + + + Debug + Name + strmiids.lib + Windows + + + Debug + Name + dinput8.lib + Windows + + + Miles + + Debug + Name + mss32.lib + Windows + + + MSL ANSI Libraries + + Debug + Name + MSL_All_x86_D.lib + MacOS + + + Release + Name + MSL_All_x86.lib + MacOS + + + Win32 SDK Libraries + + Debug + Name + Gdi32.lib + MacOS + + + Debug + Name + Kernel32.lib + MacOS + + + Debug + Name + User32.lib + MacOS + + + Debug + Name + Comdlg32.lib + MacOS + + + Debug + Name + winmm.lib + Windows + + + Debug + Name + ole32.lib + Windows + + + Debug + Name + shell32.lib + Windows + + + Debug + Name + uuid.lib + Windows + + + + + diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..b6424eb --- /dev/null +++ b/conanfile.py @@ -0,0 +1,135 @@ +from conans import ConanFile, CMake, tools +from conans.errors import ConanException, ConanInvalidConfiguration +import os +import shutil +import textwrap + + +class Re3Conan(ConanFile): + name = "re3" + version = "master" + license = "???" # FIXME: https://github.com/GTAmodding/re3/issues/794 + settings = "os", "arch", "compiler", "build_type" + generators = "cmake", "cmake_find_package" + options = { + "audio": ["openal", "miles"], + "with_libsndfile": [True, False], + "with_opus": [True, False], + } + default_options = { + "audio": "openal", + "with_libsndfile": False, + "with_opus": False, + # "libsndfile:with_external_libs": False, + # "mpg123:flexible_resampling": False, + # "mpg123:network": False, + # "mpg123:icy": False, + # "mpg123:id3v2": False, + # "mpg123:ieeefloat": False, + # "mpg123:layer1": False, + # "mpg123:layer2": False, + # "mpg123:layer3": False, + # "mpg123:moreinfo": False, + # "sdl2:vulkan": False, + # "sdl2:opengl": True, + # "sdl2:sdl2main": True, + } + no_copy_source = True + + @property + def _os_is_playstation2(self): + try: + return self.settings.os == "Playstation2" + except ConanException: + return False + + def configure(self): + if self.options.audio != "openal": + self.options.with_libsndfile = False + + def requirements(self): + self.requires("librw/{}".format(self.version)) + self.requires("mpg123/1.26.4") + if self.options.audio == "openal": + self.requires("openal/1.21.0") + elif self.options.audio == "miles": + self.requires("miles-sdk/{}".format(self.version)) + if self.options.with_libsndfile: + self.requires("libsndfile/1.0.30") + if self.options.with_opus: + self.requires("opusfile/0.12") + + def export_sources(self): + for d in ("cmake", "gamefiles", "src"): + shutil.copytree(src=d, dst=os.path.join(self.export_sources_folder, d)) + self.copy("CMakeLists.txt") + + def validate(self): + if self.options["librw"].platform == "gl3" and self.options["librw"].gl3_gfxlib != "glfw": + raise ConanInvalidConfiguration("Only `glfw` is supported as gl3_gfxlib.") + #if not self.options.with_opus: + # if not self.options["libsndfile"].with_external_libs: + # raise ConanInvalidConfiguration("re3 with opus support requires a libsndfile built with external libs (=ogg/flac/opus/vorbis)") + + @property + def _re3_audio(self): + return { + "miles": "MSS", + "openal": "OAL", + }[str(self.options.audio)] + + def build(self): + if self.source_folder == self.build_folder: + raise Exception("cannot build with source_folder == build_folder") + try: + os.unlink(os.path.join(self.install_folder, "Findlibrw.cmake")) + tools.save("FindOpenAL.cmake", + textwrap.dedent( + """ + set(OPENAL_FOUND ON) + set(OPENAL_INCLUDE_DIR ${OpenAL_INCLUDE_DIRS}) + set(OPENAL_LIBRARY ${OpenAL_LIBRARIES}) + set(OPENAL_DEFINITIONS ${OpenAL_DEFINITIONS}) + """), append=True) + if self.options["librw"].platform == "gl3" and self.options["librw"].gl3_gfxlib == "glfw": + tools.save("Findglfw3.cmake", + textwrap.dedent( + """ + if(NOT TARGET glfw) + message(STATUS "Creating glfw TARGET") + add_library(glfw INTERFACE IMPORTED) + set_target_properties(glfw PROPERTIES + INTERFACE_LINK_LIBRARIES CONAN_PKG::glfw) + endif() + """), append=True) + tools.save("CMakeLists.txt", + textwrap.dedent( + """ + cmake_minimum_required(VERSION 3.0) + project(cmake_wrapper) + + include("{}/conanbuildinfo.cmake") + conan_basic_setup(TARGETS NO_OUTPUT_DIRS) + + add_subdirectory("{}" re3) + """).format(self.install_folder.replace("\\", "/"), + self.source_folder.replace("\\", "/"))) + except FileNotFoundError: + pass + cmake = CMake(self) + cmake.definitions["RE3_AUDIO"] = self._re3_audio + cmake.definitions["RE3_WITH_OPUS"] = self.options.with_opus + cmake.definitions["RE3_INSTALL"] = True + cmake.definitions["RE3_VENDORED_LIBRW"] = False + env = {} + if self._os_is_playstation2: + cmake.definitions["CMAKE_TOOLCHAIN_FILE"] = self.deps_user_info["ps2dev-cmaketoolchain"].cmake_toolchain_file + env["PS2SDK"] = self.deps_cpp_info["ps2dev-ps2sdk"].rootpath + + with tools.environment_append(env): + cmake.configure(source_folder=self.build_folder) + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() diff --git a/gamefiles/TEXT/JAPANESE.gxt b/gamefiles/TEXT/JAPANESE.gxt new file mode 100644 index 0000000000000000000000000000000000000000..d4b5438388b4c17680b769fa84ce099d8587bb3b GIT binary patch literal 122022 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⪚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 MIMHz8obBuX4wS=Wg8%>k literal 0 HcmV?d00001 diff --git a/gamefiles/TEXT/english.gxt b/gamefiles/TEXT/english.gxt new file mode 100644 index 0000000000000000000000000000000000000000..71a42d12d3e01472b0a9778d7c718ddb615a0b4e GIT binary patch literal 204492 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≧78jt0U0?|nKvID9Mk9d+|&83 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 literal 0 HcmV?d00001 diff --git a/gamefiles/TEXT/italian.gxt b/gamefiles/TEXT/italian.gxt new file mode 100644 index 0000000000000000000000000000000000000000..5a060a9a606ec4d27569a5ecff2b631078148cda GIT binary patch literal 242116 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

wKJeYUkVRRrSW?h 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 literal 0 HcmV?d00001 diff --git a/gamefiles/TEXT/polish.gxt b/gamefiles/TEXT/polish.gxt new file mode 100755 index 0000000000000000000000000000000000000000..782f656d8baf0290abd8e30e355637a966ca5da5 GIT binary patch literal 241748 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 z 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⪙^#;;&-G8|lX#b^_!=@C`v?>*1SnZ3>Tpsre{ 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 literal 0 HcmV?d00001 diff --git a/gamefiles/TEXT/russian.gxt b/gamefiles/TEXT/russian.gxt new file mode 100644 index 0000000000000000000000000000000000000000..914f5b52ada25cc7e9329d0455b7137445054270 GIT binary patch literal 222534 zcmeFaf1H&?cISUWj4_57V~lYLF|9F5h(o$>|DYkp*xk2(!R_wry|z=3T)TvXaPSx|=huYlk`D<5M_|??d*a)1hKjJ2s0&xNEb8d#O zaBhLGb8dwc`12=_s&-@PBY6|U^oI|^7okP2aoI|_si`%1~ z7fdqi=;|7-bf^sOt_+n+(i!frl!q!cyBaQak94bi93BWw*|W}L{51VLL-`mQt&EHk zcSJ^s=Od%U?2}xc&c70w&R_jqUQXvvKG~VhUlp0oABZfKt@hn6(<$B_xl4S`DPCR_ zUl+MseCqdjd5?HYuxOyoJ@gOTToFaBO+f|+fB^BDa)5E+geKRs`tv@}; zjL}x{2jJiWw!}G%*OQ4gkY}6`WX76Q(Tc>X=!wLtsNn~rjFes(87-cPObe%;=`xJ( z;>e8e_Q;Iy;vaGu#&=6(#`lBB^kv#vE<<0&Bh#1je%Q;g+seq;?Y+p@s&KZ;V5{c> zqm9PeIr!}b{|uSdV7DcaX=}z5FQ=^ok!kBl;LhPv$DqBQcx=E<`Vr&@h9zQ^$W(e; zVpaNbVpV#{RF@x9rRx)`0|yhU0}VeanR10HZ5mwF0O{Ao&OzsGkn9?k zbM42x9Q*8uj7?sMjL%=(fJ{MTcDZw~&!NPc*B74~WX6azBZ+m%GnqT=_VZkZnojv~ zXMAI6WPIbX$c#qM`7VQRj7P>dPWlNi$2UeJ;~URLmT7JF1uoMeeYPi7!%s)XCUY)y z8JXOki;PVQ)4Uv;JQi6dw|4@gv0}(iBGVk~urje4J`|Zg7N@%mSKJYqKE4r|KA!qh zE<+#Bi;VtHM@G++E^-<4T#{J*S{E6e_eMtNhR?V>IxkBs`6H1T-`Wh9=~ek1ks0fr zpZ4-TmG6p7tou0f9C1nGP2cB=*9N9nnx}z>=c4~UxHeoHs%S2&F6V-sCc`*4+vD&s z+Ni(qypH+s`;qmJohjpWjCFMks`Dl1p@DLft;<}nCo?zOBB|0b*3;2nYO*bv3-&?g zW;;)Ym&|afq<9{<+)=G+{S92{80_t~mjYKiM!NNZ2B%}J);}QQo-OX|7_9bnciZ`a zE0wOfRhu2Srkgeq37oQS%AjGuC zBct5PpF^fn#VF&E$7HO>ox|Kbl34!uT4HtPy~J{|Q!Wklj5RfD?j)AKG$xiW^d#0; zl@lu%U7lEDa#dp4``XC(@cP8kVQXUPup_aA_9m7N4<(ikPbHQPFD8}_Zzh%wM-xkj zQ#JOo$yh-;oD*49+C_<_!|cS;VNPP{aCu@~aBX7gur{%D*pyg0Y)>p5_9T`L4=0um zPexWX_F`mJV{b)PHTFT|5Zg7d=&x$*?8vIdrbq6o3|mj+?$NR>4(#)8MPwXhy5^6Z zxoJ#g>k`YCHYe6RdexaBvUekM?Hc)_t}Tpd@i>@R^}Lh0U`te2kkNH}66^CP6YJke zY9q)fQCpa~*=|g%e|r@Ef{gw>l30Owzy5@B1>ckOC-5Bg%ejBBr^yC07i^F7*ns^1 zJjrzxyDJq1-?eZbvs!Ct;BHw(ewA3ge>1VheX@djSD$1wVAB(8z{-g=VAmwpfNe@V z)MJM-584~f-L;NBd*69vNdHbz1nBDP=`ZTvTI7;!wLWnFpv-S~!2N?=3UBs!;M)8J z>etI~X~B@1vXk^#V8zDi@L-pMwc=IaTB)jf);ovt=i#A_F4nlW1D8tOT{ZjId1#=k zV&|x?uAx$GOfhXV@W5DiN7b&1j1KDp4_0gP!yV4Giu8Iqa9OTfvm1w~r>x9Rb0qLs zM~Hl7$`zs1-xj!1Y;0}pu%dHQb6bZkOgyv0u20HiciU{|LX+yOW$v`4fxC-r{WfMU*m&kcXW4?p+7Mowxyd$XF4(@z≫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 Aj3v90xCnEt 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 `*J!=9*&g%5$StGPoYgbD?Kv@ZtKk zd-^>eXP$qvcf_5u?YXKd`kwOp{B^ya3_9=J9wY2Jk84bI`~5tf>0`It-lYHi>TOni zZg2jRgWB*y>+iDD?WrZKZR+OVS)5lrVW&kkw%FPhN++X8Hlzhttu!a&ezK$sE$NQ^ zJzl3MzPnnLN7i>D(erhc?f(9n=Z}|(Ij6%N14An0vurg&== zqowSZO-?m;S~ZGRtc6bt1E}w}jVY)OolkF3_iXhe3-quxJ?bUbx~kxU^&*3Yj-&sQ0#RR_Jy zTc0 z@HuokJ3_VM`8I|=TRm|z8OgqDmK4cvsynUMCpSzI5YERHOE;yW^z3fjm3cBeueU^x zSIb8X4!1273s5reOJY+Z>h+v=a|za8Mjo=GKTF5k_x-KAD6qAxM^p^YN4Ka zG<&+~h^%NNzT=2oM>4q6a>sb`c(#78K9sLxDKvGiP?5^-9(x4VAnlx?A-@)Cc&qdJ zTC8u)fg*UhUoNi0v!_VG7JH~Y4`Y2RPuzY9chyX$idW2@73CSP`Ds0@riX%j<+UWB zzg+X>YM(8{47QVmu;yImJyL>YK%ftUAX*N)#9%;~9 z=Q^y!vg>(1`J)xoqbZwU7ms zXAk6;cZu0AkJ-yRalm81Aez$F%w>y2obYxi=c5&ong{Dk4BU?XVB} z=H;qDXnl@`Z}U@-Nn)a5RiJ!5D=3HE>wR-L4_p>$-idW&i!1ZDvs-lfe6=aYY#LEF zo|u(2`+jm}>j-kwviii&Z}~Z|$KFK>Z$tE@#Tjkxlcm4W)}>TPgw~Sw=H%uSzh101 zbTK-)ZWRwQsq~-D@}A>;W&~dM?RwrbKVL2H!^~uK^uvR6wQt;@N{D{FAhoY zSdWf1=MRrOeam{+5%MC}s4YS{aY&-sUC84f+4q+GyW0L)`cGfWB+DwUy;(|Xm;8l0 z*`btg-UUX*UwFQKGxkn{@!h?~F7Pcb zx?0;LO#<<&6=_r#N`Lof?tH>WucZ}A`A(MecKVHOmo5snFL||cxQ#3JHF<4Z%-Awb z)eE+3HdJOO^UMaiyg6CqvqjflDe~2#3c2>wowHQ=g+2axlqbh5R-$F*6RDoBaoAdN zB~fvV49L_7c`5f-KV0pS4o$F`Vp6rYiBa@4891*jTZqS9i+FI%S&d_2BVW|odHXbD z8bfu*x^U_^`LFXE-DrXidII#r<9!@zQoVblY+Q{CM`z7n;#{@sJL?ad_@h-X@I+0y z{EPL+v!S1@|F@lNq_jfli4fufjnS;!?(!}g5scnDm(ZS(cB7AZat4gx7?$z&|3#Y1 z)%S`Xmva&gLRx*mnP0XvtIo?X$nw+m_kJrLnUSgfXYA`&)Y-D#IjfP&)1kQjqmel` ztM1FzF3_5t!eWda_a7! zVO^{$S$d9(+5(HalrAgXeuCYf`OQ-}->K5eVm@5`=Y03?FFR5j?wy%dxE~&NcuCpi zdwl4HkCXedBsp!mQ3Pfb47?w9deX2h@a=s0C9kSk8u zy`uFWzOD(mI`&=u2N~tSFvx=rR?iti(SP@Z0#U=2V#Ro(T7F|awfN@1h)nkBYNh7O z15Zp!3XS0{sMc=h7+-!`PL7*DS#*_;^cD`@qMIlKJAGEK9uDoUH$SIkXAg^Gb{{6&Cb@KD}O+>u_C0yf%w2CThXEI2i zX!vheY=Ta*`K_gAPY-&!Y|y<|o^t)a9B*p(>#x@Pn@9h&GX9FBswa-;J&)~)3UIz_;Bu(W`^f9Sng!4@MZIb_`6anZ!`9sIi4B^t-b4$i0*o`e{7BXlMMg) z(%-L#YBGGhaM_cr^7N2W^|^{Hlzev~mv3?6i&x_1vV>MR+1}lrvmTMbu-fs!K3PYd zd*{2w8(yU~*PG@J<-0dNiUMD4CcDn%?YxZH9^HP=a*Ig!jd`X$B$8QvJ)4z-vDuN- z^DH}q7=Oddh|zMDsx;Yu-a%bj7DnSYtAFn|8ovlj z#n-7}Ry`NNTN%Fjjea-BjPI|}$*i$OT>kBxPvOLx7bTMoZ%-+!?|B#Ec)8kTY`EM9 z+q|i&6agXaTp!{`rv_wQcHXTJwe4r^_;Ot-ooF0hFL%U})o6QSZr`ibbw1YURi@%} zbObjaEPA?USyov-qSDvhNNMuXYT>N<&N;TN6qUkhSId6ScRX7sgiaZpPu;yXR#*-# z+k(uvQiBz*pv4pXe|Q_S%3($K&yPCatJNppu-E(Ls-ivX`o;PmdddSDo$krvW=Y_` zF1<}f*XT+5j~6w6x%v-1o)Olm0ap6S>bt88_vM_U8(vEVNapkD`kSP7Jaw(M3+P#=@cr;h^^A_$e zj;yM)dGr9wo)Yw8Nfj^mVFHb;Y|f$RV+YyuuIDVA7OlU_PGCZv+8n)Sz8Xdxv#hg* zCdcr9S;~0+8H4OR#Ue_o2i20i>z^%bc}}eh{SajCFr%fvp|MOiEhJO;t7UPD{049B zc++hwM!q5K=V3y{pDmg^16+<;cE)@1d(}PCio2JnxJ`KBRSJ+3&2|ZkAj>TD_vjYL=vod$|TYdo&e8GtT*|&KMUz@>O%z z048Xz)hpT{5B*SC6~Wa!9g7<2ilg!3-I+yt+*J?EKcI<(%|J5y`3dLuuR7tVD(IBj zAFXKYoixu@v{d2yWaS~=#QI0;n)kq|`FZE6d|$od%@segJU`PWFVMCh8g(dASYp*(Ft zL={XHQ*29{GneX6o_bECei<&U49+`bW}3I4zRi1owEXkq<(J?7*6eKs`K#7(b5*uH zJBFXF&(3mMLHHbBYIS4@x$J1Lo)q64TmDC&ZtT@cNj%ASk-*N6Ce!n~e4_u^v$$Wz zcyn0b(=|%*sB9)gr0XJ+_4LSB->Ll+od-kXXRGc$T=CGF;I~)gZMCSU8Hf73O*tXY zCF1<;nkiH_npNv%wY_??%s#JPf9RBHv7(WJo^SvY?CX5@(2O#1Vq$Qd#x*(3y>+ZY zEX`M$CC|yuvP;ne4d^-Vla#5u+s_t97RpvKkN+ z;{mTfK7QHx-(c(q#~OG%1)1f|&`O&;iqBzRyUt+A(N#qJ3ZZ%8;TrF10NJkJttqPs zu;RQldU?H0fzQ>6=_EW=gK#5)sW19--YMzc>EXc+sWn}3jCUf_yoRTTK0FZCY%IaG zso-VjJa0ZcuJf&1V%hA?>i1+x->T8BKyO~Ve9mlq(tPhId9-?bw0uU-^MAJVGqwYr0#p!cVmrp0--X*iZXG*1u*fgQutb!sQI*%6@@D@|?GS=P^hBJQXChJ@?Mt z(In%Jyjssm3Fnpi<@rJXliMD0=iXMLiczM}SgIR6J(T9w?AGynOA6laLJBnWYK`7AubO@D@8dL`JiM*{cw*{Mp2t1sLiikR z?&sxFKDeAn{=>y(l^~e_`87U!)$VWCuOgRkTO#~jKKILKF5@kh%U6@nH;?;)pbP3f zbrD8}N25rmp}5Wo^tc~%4Dj`l(~Gm+RM2_>%TwWl(+nHs-UIp0d(3o;=f&gVjuayy!{iFn~&wSqFCKU3bm=aNX`gO!YNzO`tW z^*UN)YCm1qKUlxpsYQ>vjuCNh9gULiNN-oe$d6Xfa|P(LrH^l~(Y=N5gY}zn(T_Ll z=Q-_&GCl-H^B&QEvqrb0jx!Wm{Xq-qe6W6DYybApgmVyR)9Tyns%nG#thx`uoR^gG zwB&<&t1Xl`y{c+ZO)3qk{aIz2l~ljA{&ylENstp6!vos2n0F6`J#P-`1PIRbw&TcZ zJv~GI{&ZcZLuhEXzk1I3E#Xd=wbG9Z{b>L~zQ6wPz-b(|^pE4`hwS*UpbaEznV=74>UKDU5>h8~J-l=LE{EKJdcqXjs4jZ<$>4V_>2eg6 z_<5q#T|Rf)R$h-6kL1!Cij?RXwpm84q4Inu4L!jbS-tn*-z{3NM`iDHK*PO@Yh;F^ z&_{MK@O=H>+dq9{*FQW^mc9S+y7ukk`%e~?(9@lO?$jI%!_uFwUU?G;`EZ@2_`}r_ zcX_0-xj8GN53Clac=p+0|EeAu$FfX zh5WX*D$Mfk;^>yv^u>Cxoyy@;OZxxbxdhXfW z1^VAtR_t!Bhs#E3n$Mw^s_WMor?>`sk zO=jNv{AfjZXW_ou35zeY|D;Ps50|I-%eAxC$>liCx8)f-Eq%UDrx5->1N~FAc)B>+ z&k)(Z)M@eVj*>UE1Cw`ALzz|DxgNdkH{T%UmM6=CQf-KlI+?F zhW~UQK0jd>n1}OBhELb`7pphDp&^gR`?*tNvit8Gzou0fWm%sc9*H-3w))Q#6N+{6 zKj#5dc*=5TEz$RfYYgMCKfj9~of^pc(L*L8T0X(~IxX>AX8-%^7i&4yBKG@o(b$=~ z^K(7t^xNf{`+vSZ&b0F+JYceOdcZS}+l>#8YA-x+8OvahPYPfD)sO!D3S|?WpoqR& z^EuORY{=_rQFDesM4D(^-_KUN*=anVD?tsrf4asLeXnQlYPcu{_xyh7lRcu6g-j-D z-ndgqfFbx}17`W&`Yj9GncYOuS-azC&U&(2@l`f3GGJ>W%YL#}bZL>)ize*LoN)g6 z`fa!u!}jcKZsK}=;fq81^mwj<@+Y%jJ~5CcMt^zO^Yit_=bZC{n?=u))hiDz?x9ki zGThDXt}ABr1k#c5xi;>&gj6YQ|?Cg-|qHZ6l zPGTmO#izC|;UQp)wT^A0h?h6t%|h9}pGu4CXA<%YR;=PX>UmHUj}**nzNmU|^0*V9 zSOL$ydEw7_wVr>Ff7QPX!7t-Wj9f-E)K~K)P1<39{qi4=GhnFN_d@!OXY()HG#Wf~ z7DV5Pc5WZT{9?(K2%6+H1+(*HPFtwPc}}#I;eT77tb&usrtIUDLpn<* zzk}3ba8;HNRi-&{1rH)onD%Y*i0hRlI^ES*u34OcgR}1MK544^Y&JUBk=ep{D=OCe zn_V|SZ&@-5li)u|>b|Eb-6yG8M|rQrleGf)aOwE@y3^C!s+{||S-6`$HSgzs@3_xP ze%EQMxAnVRBmbv0&flyV-eq+hl4KT_x!Ac%{FC*Y-*LqU%sH}FA!RlBDgIDJsa=%( zIp4GQ=Fa0@`Vn?5p4h$2Fa6KS)%Dq=`zj)rk|E*hbXAb@(4;&0>z3-4`TX{HX-u@5 z?~}Fj52L4>g}r|`y7;ZVm$akdKQ2Y*eX0KuXsRZzmO)lk8mt;=_#a<~(eeJG2O9se zDU#EicL3i;&j%~kJ1^Nu*P8?Rm)ERGyg}llqps-G#yofLy4|SMiJ9!s5NR>%LaBmL$j_I%0JPBA``+~}ZcN~g-n|Jq_(CA_aLIBk#j5U->uWi_J0gFRa-6-#-yC({J}jekpM9Oqc#Gg7bn7JR(s$>V zHJACr;-wVFL;vI9=HcSkT3n8KP2HQgc73mzc6Re_-tDU)^)gqV72FRFpX=Xq_WCYX z{O+FOF8A;5EAU9$st$KO`B&4r+OTzKbqcwnnjW3G`o)^(FV~sBkxZ@9rK&n)(W~oL z3O_n3K9AS6T2_g0SGVuglZ=actBGIU&NDfUF{T<(Uvv1RVgW_xYEboF5;(6%s~FY0 z7OFM>@sO3?JHIw&)Ig|JcvXw1YZjb28&-8@Eb`my7i(>ls0qX+q^Ng6EkvnUkYH7Z zxfe(UN)1V``=4a^;5ZGbovPT##JF;UU#{!cN7WU`RDA8b+{4L%r|XJZ3;H`Pa!qmB zgj1?jf#$xh(XpAv(yABXw{;$5@J_Te6_pq33XYvcqyv-%cYPw@$SR$};B!Whm_0zgqWbf9tmGaPj{7{-aexFIy$4N9!|< zR?!Q==bj;7e%1cq4-Qm6S$!bau3|{G2E&Qn8mMInaOeL`ozc!GdhX*~Evq_O>$+$s z8#9qQ4b0xst{ACees-%5(#xND9)KNobdbwu>wh}5M{5W4=LZ*Ti8MMl1g&_x+`S zl)2@JvJQ6CdFDKkQxVyJZ|&Rr<>|0{A4lW!cAWhVpx)p$l|&u}h3H9(-sG62=3k(} z9NFG4S3BAXi?pWq&7ynz>Tq)QU+IngltJVL(lsAuj%u)cs_RDFcnUrIS#cs=(hgie z)_8{AWKetSkFd0wEJ2P%9x|wGaJYGD9^z1ReFjmQ?e|AtQO4104e0Rw) zk9J-6*B-sCXR5ZtDt^MHF?{RXo&V>pO*|be)a%juR7~KFpd~vnbJyu!CRdMk;LiRe zd`%`bJH+XnrboY*JFXwCnX;s?HLJ|<7B+_>vY<(bjH9^|x`-z_daD=BnSbA*fePby zdVXYr6Pg)&q>KES5w>RTo^gP@hX*a)o02~qzv9eCUUuT58AZdY-&xemaR2$Ivkeal zjb;oUm%SA4AQe3y-_A*|IMOm6=7@2s55eo*+cW5*}ShxRfy2!J?autwAvg|t|v zwNbL82ft?<6a8sPyW~`EWj31B+sN$W&(=4q3i=^KoH};~X<00tqc@LWy!O*@CXp-}U7y=G4z{t;IPWZmTy(J63R^=8gV|)G`Pf5tTwxIBq=jqZ+rc zKiY`vp=)eQ%fVju$}45{^2>G%(`s3m);x%;YsbF$K6@Q<7*0`{Mn$4o-J&hIrh}d> zYp%HN-?7B=b0l{3Zcn@n$eFa+%`YYy{naf#(erXiQN*J+-{og?2R-Z$opB7ygP3qg z-(#<3>3$~x2+TjxOqnaIP6%Nd{ISE2#6Cg@8UIW+T3eqwDRcIfPu6VjtsH$; znXs#}s?rMC&XWGi^{@NI{_pi&ZS}=#d^dx*)4f#JZ%i7W32QxpiKNkTpT8Kp4f)qk zY4EgFI?)rx%y_dX+4&7!lfu~_z>eAZj^vWY_S<7Z*MB@B!^pbb4}1J)ywmR71KGNc zKdTKJe0AhW-NBZ`w++*jYZJxJ@eXD7heP{=V?=1PhAqD+q8MW$Q>Q131@MgueOM`} zL(s&%`Z-JFdM8lI;~+tAlItn1>=dSwmFr@gsNY=Xhki4*;(hO^v7?nNy4TVDXgnKD z8*PwypVrxhSy7&)K_pp?CmZ`8yXd6l4; z^i6lT;m6{9W*T1ejYB=pn~#6AXx{mh5w&qU83%0(d>-OoK@ia(p1`pJbb#opBLoHzdQbHJ;vMf)lN?F^w7kgt$D~O|0?^k zPQ`OO=T8?6vqznGvmUy>;|=%ydZsMBoNcKqD7cSOv;M&v9WGh&&R0k7!Dp$oVv^YKf@R&7z`GTJ<{;J=K&itE9 zH(4ZW6i!s!RjQd`JkRKn`LK_b&VwN)+nnY>9M1b8W8&@yR3%n(Z4MRZ)Q2? zP1&nfWxFWE+m-TgDjLgJ{kpSc zbPX-7r6y96A`DJ_1HWWJhNL&qX!s;Wnr^MvnGqD;EWTc@h!G;jSCOEe6B&~RuR9h& zI^SO3QD_{JiSydZyW~CE#RbmT2I{g1bI2R>qC1Z=uidtyqquVR0p;wp(7Jj!-8n`S zKsztE5hh zDz=;_5=TQ%H;?L22B0_PfYvMBMNnd9Iy4iDB)7sU`_T2?B*=>6>^fT+|o;xsizj#t3y(gb(g|ZKivFEv~PyvN9#*x#k zr=Kl4(r+^IZSI+v+E-qk7n@ujx5JUTu1JDAo`E*LSQJAFuaHDLVa})A_ipTFxXc!! zH6;9Ujc(ju96qFIO`CiW*@yb`6a4dBcF)E?@IR`C44*9f#>X(a385cUM zVO9l!Xh^Is+_U0n6h2Afi`SS5ZH?NSfZ2s8{Ql}al(O1vA;fPv6_e)9zL8eCZw;ge zTAv&gWq-0%eyKYMXO^k6?i?-d#fd*Tq?13+%E~Et2y*ea*+nF>=sh8Wg*_&3TRst< zQAGMYDrx%6U-19JP1iOO^HETjc*)l1~TuI%};QS;6SMf0xk zOV!Oq=rZ9niq^?kW|yiwqN;wnOY&k{UfOq(SI4`=83drStVp{D2k(Ow5>NBl@5nPw zcoj1DejA!e8|5iR*0X0>z|ls%-_kz`8#B*H*2eLBa`C%wW4)Ii!rY%6zmf~Ru!h;E znfdvs_ip{5aphsE;;0wX(zO4}@oJN&dcNAc%S!%SJvd{N1k#y*FR8x|581zZGp(XE zuca2qA}^n_v^_3KJXvk&?Yw(BIVV5Txd^jXy(d*TA6++h8Q|6nP4l9BNm=aRE{{l0 zd7uxDu}EFcQP$O308OK3zC=Dz=8l62i%=wzhlm}&RM1m{rI5dkJtBSi()jvhq$;g zqqV#cgWyzZltzCOFn|3bpflG6M5ED|AC zmI;%nnHt`U^=&jZen|$A*TLzzK2luNUoV+(b-A8lf3rByqgA=gggA*y2< zqjN;(7<#7hqoW1t=P5gr*S&f@_MoTC4|n-MR6boiPfllk*kh+?Baa_1E1wf8;%WHF z$KinFTtDBvOG~^F{P=vcIQJ|vPt0|)yz zOLDIv&MWBgJveWN&N>Hm@yfE#l9_5=eiMT7<}hpZcmEw*^OlYm2amozU(dVv!^Nv} z>VLHOezvNlPLuxC`n%(2&-3sEy<%JOYjkc8T?KmjrGqRA_Gdj2t><@+&}d`-&B|-t zZS-Xnez?{&FOQZeJp1&-3hmbks=^Qij;h6Ky?-=kiZE%V744 zMPGPg7jlq%W@zFg=xd_(IrkNfpRHM{u9p#s5!0S5_r9Lr^LubdN@0y>kPY{4fXQBx zbQ$RQ@^tX6HHs@$r~9-yr^X3?{Pj%M~?C>kVFf zrEl^p9L?-o@}mj%Ix>W68o(ops^MQta!c{9H6{tDv!>67i+}PD)ZeU8U#xG%^0R(s zR9y3l>_LBXoTJ9h&c^>-?-0lo9xget?8`U5Y;EL~WDW4htNDMBmFfJ7MBQt ze$S7c74KkVvJJhR7`?8nzBpt->NLa~eRpY#)Wu|6?>uwUOgE}Ed9QFb(I<+@rWg_@ z5G?M!S(lequ|3<_)>)lGyx?bZM@$SZbwsHZ_Onq_tT%u0N|FZ*0Ju~&iqVW1H zG5DhA>W-~U!an)46M5ChHVWSSR?VB&xD97pr}7~vD~@Ipo2NIA7>cud{-7Pva_lh3 zm)LbqO7o;uPjPj+?~H7SV8b+ZvuL@#@8}0hIz?m>Ad4zPxSBdcnXwAa?;ImMS$!_o z7)7$N>gzdCWMqB;>PM?MESKTANrp8WJ2Z}cJX-DYd^GpN6S};Ks0^+sl+5bQ&*5pt zJY$V7OUJx=+59BPcxsgAfIVB^pRC^Q=IW{Z&K=A)*3xgm#uVi*7eWVXgkZz;`8uR(@Z;htMH}4mx~VkjeOXuC)DOUiwOP7vi7?| z^25_BtwKINoVkCvM)WiRcNW^Q^uCOFqCmeu)`v%)>b)0H^J?vD(wRFGW<7w0vW3yL z=UarXBq56a)uK-3U_Xmo{o+ZsJKJ^T(wc8h^Ui%oz4_x`KiI#W=hB}{@8-$q`Ne_a zyds&(Fw{>v&#=)vxba>@t&N(ewqDFnWJ^ZrdmcqgRw6>pnBnCBmEt8HaD-^HEL721Ef=C@*?r?V_Z z1~5s6Z(fJ}9@JR-jYin`fa@B6M>`8QK zcP~tkp&bLGK+3lIy#inb;x)Uw&RR)aUs^dwn)lat7Bsl=d%NOg0psthF^zYYd)rwm z2R>)`;h(jrELE>ICeExF=4AQ!$}i7O9vv7GecJ6y9@)Uu?&AyH$!+XkCGh-y!r=y9 z%USa2_j@R`#o@+pHpn-z%8Bqvls_r|$){A+=cDr!AFnw{W?RVAB8;D(ZdHQn|F^w+ zX|n97?gd^SrONk47~d--Ft)F+UlG;S)eouqp{kOEm=KaNVAB?a1U6zoghmV)c))}a z0|pEn7j=KcyMFs$t26gGryi|`5t!)cI_K=2J9E9)%9Sf~_n9B=wSKK_W1{-*ZC#t? zsqIRjm+R`M)irsDbvt&pi+tzYJcX_k+3%mT`P55^(#S`a6 z^)X8HmSu=HkA8HYsoUL@2fanRlGg_{`OLG4#ZuiOGw1GX!&#VA8E4j;r^LB*N)(a3 zN=}Iop9FVtVRR^#lbrL6v6Zq%>*`jT{Nt+H)vxDJbVMq80x?q?&RdB&S-*Ai8rV57EgCIUxj2u7RIckvV1$Vj{x8F zx98kNZCpy6ncnQz`~Grb*IBh-wHjMEqLvZhvRO5wRv>dPeYOU)rACoNvF^b?a|xa% zQrCXT>M|a7WXrdtek0OxR_|9(*&f~CV|hdLL0RyH)1lnh)s(v#Yve4(OcqRfOI8of zLZew8$=%4|khnLC5+L9qat2^x)m~R_DcY{9C1Y%e8k_;unD6Obl${x_HzWjqaAjQ> zMz}KcqWxcLFXDU6UU4B(oVNNttkENh!TEzlOVe`jW|l&nF>mSI#%Vq8+K>y_ihbeL zScL*?hPb(Ftb%=H0tFKjucMQLf--po?*%m@JgqAyb-n`$JFUk+w|x$X9>hTLbhqp7_)qZxMWmx~REhT?yFvj^3~VOg@|S@9*>5gyKRL*5RS zCzCz9S6q%&5cfpuqW8Iu^X_M_LNwErgSwXY&#&wLI?FK2T-%>W@nOm6cIJ47>CMs& z9>MG>nj4)k|7%*1vP5cdj>IPW9Xb!4j%Itd*BSKEF=e8_;hE>`e>D2Ep8Rl{MV{1e z@=8+W0TKm9Lpr8RwTftwl#76)x$J)8(nQ0_4J7i!wE;&gXLJhvH{*gtZ-2Vv>5fZp zyq`1~m6Q5(MicvL1?*8}V$9y!1Dk8c-k@J>4;#`}^ZQHwmEFYcu~x*6feM`Xkvt?0 z;ZxSmy&2ZL3-?nG5&LD7wreC41?SuyDupB63hlajs;?!M_H5fD^#0LB&#Aei6<6hi zTgTFa*;~JL2MnYn)&Fq*_4d1I8w!GnS`o*i9|>M1^KfA330@w3{o^=I`R zC1H(vbPNWwenftG7k7pyU;`f%|4x_ltXW7WBJM9XvvmsnJ8Sh?(Z2Dc^LlFk*Es;* z)f%)&NgVgEJ~vm1*8L55%87v8Q&KF3}aS0dxWEX_;FM@rTY~@JSvHh=RDv0sCtqk|84!3C+DT-cKhWKzFJrB zuBh33$5#?!eHC^rILYd$?RO(@c>TK@`B_=HtF-Z;pgS$j(lfp>nacRGNN-aW9-Z-? zW8#*0T1v_j<;FJRJiVN2MB-ESi{Htw$_2zexRYql)A1njQ0}w1$-n85_3zAIAj^tE z`(zm^y}@L}#PKn^Mh5+eJyGR$U5O?2x2;b5JT|Dk5lC4%7BI5{qk>g*DP&zS|ruj8k4O~R#@p4+>BU3B@dVC%h{m$&MeLZ3M$ zCB3l*Vqn)vj#>EV<(muEUnFQmU2hl6e4>htkzGq>IyH&tYuENc@k|uJk9Ze(uerpR zh#ADHPHE{#vSp7vY-U8_Grcv!ywfZc)r?_fZpd=vzI`U%Cbto7U}a_56OYgmvMGK@ z#KHRMvmyF4J<~aJ8B!Y0t7lP>*xmcHy}#XR`iw(x8~RIbY2kQFN^AD6^@5gjnf*`* zMyboX9H%_>Vx#QBZE)iA!h!_O^Un6d>`Uk!E0;VtyWM`7m1J>P7<{-q%jxv-yY(+` zF!H|CTP0!li&JL{#~#+_t`nx|Fk7E2Z?Y_rZ;QZ$0}b}lq9BPOT=a{#gQK@YiPYP+?fUX5{o$KA(D4ro#Lnz z6yxJDy9jnWMpd=YhvwP9&WL)h^c+P)_-P%`O> z?!Xj?=e%FIXGTdv#>_r?mNy}1=7Q5KtDZx<(BF;(+(Wa>s2a~XpVBMvLOa#-wOFnzm13e!ubW9vPs2-{4oyKF98!ObbNW7kF>;XC{k77Sl?zlz zd$>2(Xo06(OWcq%LSPmS>m{7(oY1bC;Ol!QE&8IzB!C@i8N!`Jr*d7__Z3K48y=^7 zzB!a68^-9~J5WNE$O9|&ad8oZKd9ZD$pn(fhIx}JV7_Y3q8~EM0`N$?=1=Q3pV#bZ zJ<8_`$&1gNM$UA+&5^G2P5suM*zQymyju7At>-PD{2@7Gr|~zs*>_nqT*UA88zghA zhDKvuiSCgW=huqQSd^S*`%+!im%FiqB*mV^WEI_~ndCkBN2hHWHM$*bXs7}+%n|{x zdzm47rAjy?+^Rk z<7Ynv3dCY3hQWtaHzv~&+Q$OnMe;w|vWY%vQ|Gi}K8_>tTs{`!^vagap7 z*!NiZ$Vc)pYzTO^BSMzpUOf{J58uwmr0=FO$>{M|uyD04kQp#|x^<*&%}X2+6(%BW zsc>!BeKShy^fKtYe&a$1czZa7x_k9YkKIq!vSKS^sYf09kQrEl)e1YABfIFbJ~1?N z>&z-xbTuX z)t%}IKIF`OcGY|Zg}gcZpO!1&(tqOX`9z4|lImKS8y33d&{MM3q>@C4hw$AvRvk94 z)3;mI#`D^sXSCMe*3SC8RSF-aQX^?dyvCg;f zw!ro`sf;1dY!R4SBSy4tt{Ul2XSlBy#QQ|+UN4RYY8>lc!N>M=OcCti7RnekQr8sV1!K#M84pUfJQ1shA|2lxJl8C( zagVG=7iC+)8;R0Ct!Gh5-jAzKRpVb>@4CEjH9qQOs>li9?NqTROtP60Y7xcJpyJpUHGGKMHR8ZvyqLG|(M zB4-krjUDa%bPpF=ZQjWkZ7XtiHHksDoO{BqvIuaBT_j(=x2=MGv>wbE2R423sop}R z;jdIM;2rrQIhz?%e)X(cG`9zynf*Sf{?S9fc#)H2jD$y_ZAQT7=qro*on=kyV4-d9 zhgZWkC!!oRoJL3vsMpNSYV2W&n-0N5Yc}rP(mvAt58q@liV=0z0f$v*d=SDmQ!l_{ijd z&~?nNlTVC&!pY26;KXjPt2FlNiaT@;WNmG_a@D<|J?I;I)*iszBUTAb;@>krY)du! zDKastSl2O3Vpf30Z%-0p2^IQgs{e~<#T;fo+Ph#jwDI!cWdvd3K9kGV1vZ#HOlED zJ~{p^zU*0P4y{`21>Uq(Bo%GJqg%!o$u&RVkT@(Bz#Y^Re`URdj*7SnK+75tdEqP@A>GP8)=hl&N{!4xHzb+ObdOvoX^OT*#q<$Lc0d zu019!pHy31Z0TVm2G2o}xyEe!k}VNcv1aK5ERmqhv3KX;dbR-i#)1VCAG1QiJJj!Z z{-D}=7sxxyzwhoH6fNWwK3r%3Ues)QwD*QjTf`CZ#8&I!_~LnEWz{H_1wNh)Zt*g_ zGaJOO(oK33{aWuDYI?#a9xJCmqG!{|bMb>H7W>f8$sNVI)1kw&nPSc6`GUn`5LVP~-qIl%M%whSHpXY4j-4=J$;AK~XZbQK}@4s)qJqjeUAi ziD#$6QQ<@eSS7JLGE3l$T8#L~dEHF}L~pCWAAYK^v2&aa}ee6v_B#8Y-bH8N#Qo7whvOLcL@nRh5#%-p`TR(b1CjjV$HQr{06I@Q^e-Al>|MF@_a0Wd5iPQ(^K-U{Y;`<`kQQB zUpJR)4UB)z$(it2$A`%UidQoWqCXj*R)=ax*{KkwbbutkF}shqV*KFK+?qSrULJcWsJ9KwAbocI5jNeC+(!NBlB*3!r88>HI??vqkO|BtFQ-3J$XG)3%PKUsJjX`ZS0azWjeqb<^2 z^?)0ZKs)&8!o7>0hb+fZ_48~PEg(Ihp79*k=U!n;tJxu%bf^B)^}w*%N2neRV!PUc zyjp$eDIBG7WDy<7t$8ZRAORUE_7!Ba%&d6_8oaRE;_iY08GN&Tqrij0&2GlXKc0+x z#<{)Ri!Ay@62Ny>Pu^c3CkHKucM8geZBtY19)Kx6hJ$1uzwL;Lbzj?SIz^^q4UJ0{ zahb&!7B%~8-HT?PEOeulIKK8#yL^)Q6)Lea`pPasn#&t(+DtvJJsO&ATi46viA3ld zR*wol7`6w9Ph_+4YB#L2NAu*cRIgS{5%0x%;0kN7?SuF04!URWG~P#M^AxGX5#9lR z-dMh!E)wG@mV(r@=JQ|C&g_rXA6-z5Z~Wtn^9tvEPcuyIIa*h?Id)a=L%L}k*j}r# zPZk{sR-~ry(F``3WRO9!f4JalFM#J`e_Ds{)ID}L9z{=hJ3|h9KUQ{*fTqqcW@WW^w=VA_1{E4{G+#JJ83L zr>iK*w%{(gwNoM%Wm_Y`ZOrhw?KxSwOyc7WoM(wM&1ZDw6H$yMfXkiw*F7FSzecm$ zmOm`4;z@band171RcjuCGGl~#=>-J%aa{Z$`8f-b2hFbTdv!1I++~tGdxKaqyV$n9 zB%B^CcgWpw{UbqZ&gX;x*ZDBA8L!4Rub(1MEG{0Ao2H~s6k)AdQTmJ4#sk6SktK?4 z+pWuFctl=n$&3bm5*?%YFej;GG@8bub;R3z-v#OZg6UQG(Ihl`R1}G9irYK3h}Usv zHxMTBTv{E~ce9Q}xM>~BgHmYDf0`-8*S8kry1k2_ z&vT#!mC^KQme(^RGAGVU%fBv4;9}zJ*b|Yxo@R+dFTFij{_a)#(5ih3T(#jr-YCot z3raK*8G5eAW2GNf&uDy9FygJ4P(OF-FaHuAraziuj$=I1#}2XsdNw2Bf5}o><2n5F zu=ZV#mr-De)1U&ac4p=%!e;MPEB-68Bs##8c$J0+wCucDFj%^+NI|recDJj4?Ql!9 zMK3ZZHp>dH$y>`}w>c@Au$!5^W#-Ke(hm^R;rP0nVHkOft&AjihbMn>J*J=r4+_a9 zT#YdpwMCoYOOs)9-AK)w<_DPB3p5rXKB!(JwdpaGz-!(v5i-q`k%{DXTtHtu|8n)D z&-Khcvb1XkKm2>gZ008Ib;UMx%AR1{Aj`Am9M439He+3sy4;dxPLr7qxsJUulY`Z1 zJ!v)7YvA9PBz%QIz;gwlG*6D%OEp{22ASz7Dny#;$Cn1uy{ zy}D*>aKMIVf=&G2tgYcovKMlD$(kgdz~O#!a|V&Qc_O|2>iN8|i0xR9{n`S*e4V!8 zz}scHV&~g_<24Om$B}^<4&PWPz~0a&_$OZ*S=_6B;V*wq8gb8DnLLh|pPqu@cF~4~ z2~88H;z8$6$p;xi_vEJH!M(9Slp7z19}T@YF;PMHoK}&EjPST%ViS!Uy!cx7O&*3E zkv_9UIE-BDkb<5)krOcHS-eGeaTc#+POLXNVQu1m{nck{PBJ{C1hspnD@G6_(lk1f zs13zLQ0%Vx+4*uG2GPxo!N%#!sK@mr`iCB_!j%-klYJHQfe`gWUp@~OBCF8HxQPpK zQ}4MqVp@DWEqUh+W4Gq49q3y!GxLhy4CTTju#4>IFkc(5?nxNa^W>`9BOVsS+9jf- zRq=8pjiivZwSCkA9_%Nc%4Ez^6Mlk1&x!JAy>Ib=;C-|_OSbiwXc3G#^^t|8yJSer z-x?TgXvxYdLwj;qNA_UpJH3>*5ygYY=*F`Oq(>SPSp34R*i<}&UG62ji^rfs6IPPc zkOMu^1Y_b`WPi60>8OFUM3(2w7u?Bz$MKPT9HuMuo4k@mQio>IDH7aR@hYB3z8rQP zlbS8iXOrraG4l&1^s0zpV*>ye`254Z$B!Z+V^qo!`6}{R*!9|va1i!JsY~D@|lTqZ`j0ZNH z)KB6tP$w%uYso0-30`~En|^m^Pq(odRM^CXR>b15StKbEKUVybLmH9zTypKV1r<5k4c)ru!$ndPMSwUqJibgH!)Kx2$h8hD&R(>y zk@35|=vAU~lGNE=uxXhYt-O2UE}Fq=WX|wb(UVlPr9QjpO-zqYInRoWkha(ha?M-O z#&{DvNq=-J8G+0r@k%RH$eL*6KX%p#Gt7GzBY1{%ZDTdV3bXJsF#sKpq|iN<2{p)M zv^~-q??MZPZya#2ilB|fL<241kDiGNahgtLRB|79+ge`!6nNq%*%@&u3p)EO@nBX- zS_3mv4xO_;Htw*tnP#Zs|_P z5Cf~1tK~N4M^zD{hA8!Xsf0^akvb#02Y#0%Wr)DQrKiRLGYAbVa6{+vlr_ohV>NRyyYK@-Mol)<0uuyH(276wOTWZ_7 zB51DY)q9_0G%9^YkO%<5xN0ph`W2Q>WqYG*+|S8uDQ(x)0YHA*=x z=CFG0{X4hoPVdF*Dz2_iO`WN_sMI~7UDr}JM@Ic(>KMOYEwq9gN~p018C?8Ml`1-= z_AaMEk~$9aMJ<<|8Owtw>=?$ex32o65ge0c#2fNf92XB(A)-AQH!SwR8;Q64O zF5mhSJnY|0-jD11d{ATMe2A<2Wj9E5`kvp4H*jgn zjOydqDAq=GC<@G!I;sBlYkvZ%LUXbnE%Tk`_1iGMweTWUim4eU-LWsUJa#y?jXp;L zV_Cwj%VEk+%^@3fHT$mmt=Pt8JTQS13ozjg_;`(kI*Ad_Jz{>1AwVsw}&rG(D3hnoXuck=^I4fHv?Ai#XRkukoMH znh57wo5pTfJcM8Kt@l8suC(o$ed4qVuG!B{#%>oz=!{V-uvQlZ4QOKwk zLyq*FidHtS--{HjiA9|K?cgxE@oc2ac#*Guk8oQFaIvIq`yW=j^**fQYOyJ+T`f#+ z$tKR_{0XCY`*CWMU#@;VA1JbOa&0A3{TrTz60HaPfl6C)%Y)z$+D7m4W;%DXiw6W~ z=ZV&2xY4(d7ii$v@*m3KoqBGv9NCNn*{>7NW$%T1dI1Nk8IJHPWVbEXfI8Y{G&0G* zxq`+o74(rwcU#YR5GS-m>oVR98Q#Te%yu&ol6|$IzNh_1p*IR|64EEl8VyItRobzZ zp#i-L?NnKl)}|;4hhftUHro7T!3!?<6`xB6P&-}%#*z5;8%-r+22Y-Z)d`2&7w4Xy z_A9>4u6nYAw=hffd?3oMl%}&MSQT(Rt#}#N^z&~^#k&ZI_K1M&k9%{$Eqh|KH(`zu zXSu<3as}5&0uRn}IT7XmEo0b&-1>zQ->DY;PUw!4X8k7ZG(BfIZQ4J0dT&Xz9j8cx z_6PO-xPJN975Vfv@mY4-kkQn;(-{^X?a66d=yBNbxzW#=CZVjIZow~fX}$0(*10WX>#IAyuXl@Qp9sm%nO3xiY3Hn13(89pnGZY!Eo|8Q~e`9ZZt z;aPq|EBw%>J)hASzaoK&AB@$0k^MkvG5JC1Sw>ClB4UGU`)t|lwid&Sgbw;4(d>r) z{hH+kCs1R~58xiEJ-LARr*+ra=-*rZ%9+T0sc|{p`(}NY9eudm^M*(|@4ms26|B!AI`_J&6|gnoS{Bzh!%w#P{u`>vlYGK|6~hU>H-vySj9y^j69 zo4OOt;n6y(!=c1?bSAPN9=ggS<^%{-KC9=%C7EGn=HVHf{?2ke{(bbc>nsa2i1mZp zhlO)~fqjmzuIYU3-PR z?I9A^w8A?&AKv-fylAenYCJKlpVZiFbz(BtL{C2DK}cTCb&8#1Q=0P~6-AP^ITTKy z3HuGdoYR6Xon0Fz8Nrul&)cg-ge|>oS^Or2BWi{>{fd7H7kj?*%WBE$9@J>(OK#o^J{rRR1Ddv((lz9xxW#g^p9@Ka5E9yFLk@kMgSgq+; z^|IR|yXy3w*y`DeBDnG*@2o#tAZ%R`v5pz0F>>DwB{m5yPpVa-8B!k%qvb}A#E{>2 zt8JuYF@VMN;&ev)Cffe$z_ww9W+$ISD^^$8oU|TCgb}7Zqa}J5GyC9+xzUG@{Aq z8Gn%4aHewN?PV2GW$dlt_P`Er@vMrH%oEYaQ;RDFp-rfZ2_=QgbH4lDLH|8BTp&5^z)39UF{zq8;U+#US{$2IXQ}cz+KhKcAn7_pNwXFaC z(dy6t3u$$kmc+l^gzuk5OZP8$HRjGaoki?&QwYphvYIWnb3Q$v!DazZfyq7X=ct)!PeSdDTxF^Ct7O z9i44k@86%uW-Y6qhc)cENc8GO?e!PO>x}q)J^ve-7Ezlhjhw~qp=amxoI{(eM`zRd z)J^%!I#kweyeGt5`)u#$^=Ve8-q&3Fi_f*Q(z>kI$@`f9ql0L6)Bo?+o7IQ+{~}R& zSCl$tth8g>>})hl%H{;%?^L zf7p1g^P2t6nq(2X3Si_E*(bSR`5SRlY_&OdveDhry>Sxfdy5@TR+ArS`yS7kzOvST zT0N#3D49aqIxtf$EQsD9~{_Z?^EJ69B*!9S7p!@3_Bt>nr<;L5tr zD)8__zg{RLr~d9mZA@}=`kf`G zthZ#I?qBrVr+lV*L3SGzQ}G~5i7yl7{Bq$)BslL9ik4Y}3`dMa4ztpLKCP)LdsHoi zL%tnUyTfYz>-r4N-L~J)M_Tj-}keR;@`0eMrQj z{xA{TZwgnvr~Y72R=<(q@P*vYy@Teg`lMMIzvqT1HdZ6UubWl@^i1@$Dgx};w)<|^ z`_x$g^$?*<#&7E&4;J5$b=}7c=42tZqhW#F)inuMLn97sOPkf+q3<0D?WW0~a_UWb z^#$erNY~Ggu76r9HHXDBCn4C?cv$bz+pGWCKjaPQ*{7JR+1vArdd5yAYgRPYJ|}Ca zr^^mwE2zhH-A*5SvGT4yqv*>%Nbl}*)i`!m$+p-dn?4SgJtlUg+i7Kfc=MLs)?T5v z>uEbI?Kop&Tw9-;H2SqWIHxUTN3DHY)_$8B_7~ah)VQTmXFhc_eW40E81u{Z-=5&( z3;1P~P!+b{c;1u!hdGHSJ-GYZg%b8i=31z3N0ePs`p&M=+(m6W#q2tFK2BDw8&bWT zEu+EaR#6}vK>Kh$J0Q^;tm8RS;hw$AO$YFM3UA4ZRZ14Y-;??q{7B4z{p$rAsI&7c zJ8~l-*(dK@C^DfwAoHz`a=u?Z_F3aBQ~NRUw(jk_*~YYcvb0XU*FnL1R8~}v>8C5_5(9R%X)G^qGIqd>3a~^`Yl~~2 z-|e#X=#C@yI_LDPQ3*z#dptfqwNvrT{f4>N-}q{k)gT>pZGVJ))6@Oe&|MW{d~SPc zvfI^(?j(XDi<{M^+izvlnp7rk?%gJd@$fqGDKko9T<;l3d*jH*X5A8xtFrHn<==z7 zgXOoKs*g%8^NUQf1#C|&QeRiMXzwR0<(gV`(Ab~pQ|!s-9JaEiy|CXGQwQ&s)D%cG+95zgt&k zXztcEYgY5s9>FOlUw__1Yff}mw9@lSoX|(+cqqQE91{uSYwXF0cv5-(PPIR)XXp3d z@O~dGln}k)%4MTp^`6FUNK!|Xaly#$MG``@)WRh$-qmyN?5;1ozsMNrW^b}a=^N^- zYY*Y`c%cZ~#r3jMbNnh+%#Mw`!!MY0wA#`i`-#uq=jMsy1qOV9S;pS1#rpoSF}YsqMbYx9|4xk^57fKz_UbO)y&b( zu0mCtc2Yf#xxIX=_IQ-~=FGqQT_wNVJ1B~p*~C{S_Yu#SELi9fs*VgpYH{^EGMy+b z>P)^m6^HGY)1xTP_;&EZEbpaAz6AD(@-h~w_8!>G=Vq?&o*!hkNr%Hvd5e#$4>B6c z4($QkfFrsUDzhwV>00Aj%0h9FqIi5e$_KA-qhpO^Op>L^ye>SQy*xQ?8Jcrc>3g>4 z$);p1e~q2kKJiKHhv#^LK=O}^6^~8veAhf{;VKKH{<>#)xJU;A=TD0-KV4ejGPy~w z(RR9@Ij?*|a5#95&1QycL|{Fy%#K>rPlgCg=_T}uZyV1IS~Qf&D%iP@onfvI86pSx z7tTe_@FH)Pjx5AyZeko?n7#LbXmII`3$8aMLTwbJlU=!*tKmQHsKX-9_!(0;R{Q>+k0n4^0@{=!g(p%xs*JMR^W#d%;=h!diae8JMZt+ir(ytPWQgHtmz$J$o0)u z@Hew*n`_W=Q7HSj_5bhd|KHXBf2jXXIyDP(BA|KOY5g{*w4-Fq4n6nx^~^ui-J3q~ z_&J3)Bj?S1D_6~2aH=($2U=Is$7a!^k1s5LbhNX%F#e*zVLt9W5|ni!vQ^uh|D|TT zPcDq@IV+4d-=dWmtlt+Mjb$S)ujMxD?HVceo8|jT$>Ue+e^+Xw(7kGxH|2?qc`Y#& zOLSDf^KG_k{jNJ!ClQ5+WkhZ*YwKtvnemQjhP7P31-1Q+I077^g`Vw*u-MeRhp5L+ z+y%G50#ZJ3JoiE27`cc=^qrj;exo(EtH$YXuTrGf-sL(xASj+xE0`xY-+Ol3BgJ}( zL)deaj@_JTDWVv<8e0@tlbcq>9czvndV+7LCxW68=0{rb1LzPhpLqaY=ylWU%~w^*nMXlWkw@~%?=8DwTPkJqkPTdXs(4)M(MG&qXnaVuxuFDIx}#O z|69j}IA-1MM@t5#W13j8M9YcwXddYT16+8*$nYGup$iI!=aD+A6uClwBcbb_NguiC zc?!(n!iGjx;BdBF?QGhJ$nFzfYh8h`j?0iR~%w*Q|)%$Nset-0w%fSNO#bAlee^Vo6 z77$%S`$Q%pGW3|OPOr&@Tfs7sX;!V8c68Tho)1*0+gxrqQDUvH_;FP5jfk6`wTiKq3_ z(6yA}OHN-%cIAK7`2IVOE+-;7nNNL1&qQCTUPVlasH_$^@9?@7&dM8o{K-Wh-XUZ~ zLUuem?Zm{**8Oyx@K@Cu&NJk=m00u5!lXS# z<|@u^X_EW(t+V!Kta#*SK6nRLE_z&x;vxUZ_33XC8k_adsNqaO*NQ6{?^MYNFuxS$u9Wc1A$ z*4{+l;~X>8Oka^K_Aqpy+a#wYGk18g?^Vz8n+FBe^|tC+amV@B^vG-{pX-%GcbtOR zCtPNgKucrn?jfs?IW0BouUGeSnZ(By@Y7}jk>hn%qhk*G;2%W@e%E%b!>rlBBa-|;DaAJ=o2pLQm>GqU-d>;mJnvJWirV!VPKVXB`Ft6eHtI!^naHR5cIL*LlT zcZ)tfi#VyAa9hn`bwyRE%-YUR<}{=q*WJWiHyc3&8oT`YjBvWdxSz!cq0dcEL7FY) zO(@n>Nd6?b&d``X%HfNEa~|+a**`+{S<$LbdiREqZD-u6li z3n^x`W)z+!mJ4s>lyOgdgqOMJyGSkTVK?XKAEEripe3KuQR^#9Jf0}EHc7u`-`N-*zr@lX7Z;sQsQ|z26Nq()*luT_W+O@xyQ#=7rCc2?j@GB$N8jM z{A$6&EWs1oFwXhEb>8J>)EjGDSv$cwvyM%B58rTL_nP!LQ%lR#@1RQ0bHLvFkx(u< zn#3EMnCe0M%-gKN{^#<$=WMccV_gqNWU9ltmN}WHn^NF$>2`Jlbk%?F4R}<04pNaj z&ty~>%94?=o^yCRFi++EbD`6ym)9!U$kdg+Xl6O{r-PEHk?{fRZ zSvg4FEL0BPJ6Ag-`>Ip9R9?MaQaejH>9F#V^BOPPsUM6^7U(a_=_j8{zB`}W5@Dqh z=JT97eVHw0KV`2B?CvEN0+x}fUdPCaNOB4-ZPt^}{&BTNCA=J2b($OsTInR!;YsTX zYeFVB^{MUK%`%X`4`Ois zWqtE#dB)XV%_YOR|8n&k&EQKC#lHBNIIXwg=2{=Z&C7KGH?!6Vudxyz9AmplTlP-E zJEJ6{WG?16dEP!pg{4VGKF>U;Us-Ep140)oHeV_UJ*!hA{;a+S-cN!FS{@ZVv{+W` zSH%;yi`mW@*Xy;TUO{$?WlM?>nrLd*DBkI zlF8ShlDI8W9Jw~{f{1^|LkTgW>?##O1J%9`1+fo zspyb3`O-3HwyQAjOZEQL|FLMkyPxdoSdPR5%@^>lC63$);m@~McRxQXb+cuK<6!fhm}n~uJ5kONx;DT)>~Zvqn_!!#n^9p zzP!Z}-oAk^83P!c)ibJH!7NLXcRQ+|Nvns|BKt)HC;a@Q4Ju!}3lP0Trw2u4`c9wX zRYxsV>{aa4QSmY>6Z`cvs~kCu?V%NOU18Q9Xlc&)js{wNC-ue(zv)r0&R7L*yBddQ9yBTvdc7t36d##W9!4EYYzO;hK1Tog9EnJ5o}DAZH%bn7QG|cV_}=zH zMr67|i`xZfzn#TB8Bx1LXMG2)CsN6~cM(B~@KPo6PPNWzt~$GMt_O{vJ<)1!mxIBG zW>}SA0auX}y$6MOGah+VC$@(%k`b*E<@AfbBMOJ9$b=_Gc^dh&WGl1P51zlvrsvFh zqS0n(DnVII%F)Gfqn8tI_gynWHjhtp@cg~#kL>+YHS zO?}noxI9Qlk|Z_Nl_~>cx9oXPZ>XIpE1&x8b7MlGj!sZ&#IT{4x)#s`>!%C9RduyQ zijN{G=$xlDjPo2}$h_YP8;^LbKNt-mE(#AA?q1 zL+FXwep!o+KZH{#=t-Ib>V_*cds0{AOmdH{v+mKp(_J)0XOhH%&CfSgT4u~!jiGJo zM-sDyvOVh#OtAv%b5B~5j0YB)Mn3$HcB8f45?`K`)x~8n`P*}z{iPBS*ZI?oF4`eO zt*x#5$zY+F9)7$?FpC=vqJPIVHg2;bxgK4}nC>;)V1~DqZ~O`e5)Y8%ZJhU(N?g3q zH*3jqbbs^yYqT}Hy3sk@3$?>J&v}OAKB&*Oq0wREw&ee4sn$=vkv#B9 zr{_KkcmByD)vitpE&cl_LQYAI`OO1O2mQsa$S5XJ1JTfNsCvkikWd`eMn5It(aL0 z(KEP4{JX#OD2@enzo|aw#2D+N`mZwM_`+9PXU=IWhjpq-a-toti%Ru<@SHd3h|;1m z+NSb19y1nco$=|2;HaLm=O^b$kxY&sd@0I+}=bqaIk3_G=>iXeXb9Z64yX7X1 zcWNwE_eQagL=?+6m?c=9U=I@wta*LBK*&>}LY@z=Px{N6%3<|^<18Zl&0k1STSZ<|J~h=_w4f<Ge(+;flVN+WN1UG#yG`s3K^G)YE zuqE=n@s4~`^ogeOxUJ>eYX>bi z+|HbU1W$ct^+W!Pt;S=tO@CuI1Uj&;v0QQ*?^K&P_jFnT- z0!3v$dxYJ63<`|M9Y=VEP3m`x%vIx$x>e?UB%!HgT)sVik#**AM!b?$+0OIWA0^uF z-`&fa=31G_$jd}4XuUS%o0kB2v=V%zbJKT-UclBmwX4lZ^}cH-`(8zGWJxX)b-!Dq z?O%9(dMmr7F8HYK1Y5HH?~AOREVMaaMrOM}{ABT9?T7Iy`mk3S%EU}rIkI~QW=3EU zSl>ujM|fv-cRtHW1YpIG^k)8)SPU0X=)Jn`Gcyeu*z_7#&B}P`RD=Cu!SwjzueJX~ z&B^0#Ykur;W_GdAIDvEDFU#f%dqQG5f+f*-=Sm`(T~R^$+Wq|f!i$BB7UOXC>}Hp4 zTaC^ptu2=mEbNTDRou&`>LqzUbD8U@2~%_&^t+m>*Y}tj?zV-4L&M(U&ei>lT;!++ zN4vG%zP=ywav3~w7auP2N&dgL$jm2)YO^;MKiWzzT2?^(#v&`Yd{r!Lc7=xfMXAi1 zIzyMtMxydh(%)gp87UV7w_oJDvVt?9n`1+*M{L=UZMI+q@H_JvusJjI?YfpV)UIf0 z&8An;i`dKPP0#k%HZ*?9+`#-{j;lpq&&qp_{ar27bSnAX_}|$15uF?qgvZ4VBgKE` zeS%__oFTG(|Icf*tL}g8n)@;I6Cq*4Q zypJoBa9B@m#wVGp@n>0OxXJqep#885!@jB?IxC*x7Hdd0`h>^M;j>PO4YU%`S?geb zW5+!6!G%;s9{;o2lbXo*2U)jlUSqr$pjl{{QUDgf_ttz7+zkO9jg6i{}FuaReS4s8V zf>+PhpLabwpJ}i^akDumt`cpa#c}Z$FLzTWtJQewgag@!$Awka%~788pb7t;UQagq zP2HmW-_NTp2vEh7k*7BbKGFk%tQv^eWk+VTlVu-y7NnZRVMh` z1?hfan#vI?{{HQ%yb>n_&v?S^+N{}DT-GMJ9a*1bLk{bcKTL)TO~Z7!12gCI`pisWzs9i!5Z{;CMC?7hjY?|73S-`klV($(L02cX zv8Lt^G?Hd!FHERP>o+O3JYF?mHpy)d`@=YL`Fp>+wwG`npk6eA>D@s>2-M*l0u?#>T)u+=Hu(xLR-hd zgU}$Bh<9JNR}W?ePtMSpbgsE6KFQOez5OS#=&Uq(z_H9F(3M^6SUI#e&t7v7-R#1m zh3%OVe^}2-WTD!Lblcg)YOpn^-4X7TvBZ+jzLuEC|isP5h`{?FnvpDzcRp>MB<1J|+ea(zZ%zl5z3f1kp&w7p( zsmOo4+E}qL2I_6DPvAvsJbJuRc*mw2@wC3^+BfL5_=Shh>jV5WVE!-dQi`Bci)}owVN~F@#N;6`KetT-ChIRwF(uR z0Xx2cr6EVK1UcSjCGTj#Hx}2uHO0QPXtrHjtfm>paeZe6vRZtv;>fHwKdS5Tuj0l; zdnn0@Cs(BMK6xzTuN-RaBfg z#+Mg(v->8oGFp7SEZkS?PQLwleN(3iE79=%io)}b6D@vLoM)L>xcG+6I6M){@#61A zNT=0u-6rwUuBcvwE9~GT*Ww#~`ZP}%Tv&L-Rf2Bt5T0W9PBjokB z$62@3h6iVZ%*)W0beJQGuGd#@E!VVeITAb2iM8I%y+)qr)zI9#=)st%wO{=ozf#+BU^km3vFwwx{;*S=MWmivYE{EJ49dwRId6Dq-g|2`j*P|d{^ z=4*ovBGS`>WV9Ps(h3F5e$J;h{d=@{Yk_|B(!WOw(O5^fcHiD~zI)S_9f$MCByi+t zdHZ{7Z`FP+k z$~&`lY`R>6d~4pK6)%;{28$-UNDJd({1-68Fp-H{b}weInF?GIqfKeCA7 zU`{%6b_@Gz-UbGJdJc@B+b;|JsIGeR4==!$fB^Jt(@BjrVjVVZ&Wpm+75%k`J*=lw z3CQ1@W2To_h#vL9V%u<9``vBCJ2~4z1t2QH08}9Do_tRhHqvvqVE)#oju2m zZtrR(b3lF)8G|ytu4l9EH-I@NRAqO(Y?)gfe{W6Bu`89oTa#<1zsxh+Q??Xm7!1 zZRSYajpyhy3)pEs!itgePdD@3qOQy#yHY1>>@U@GXc-Jdb6^}f8o%cikiCuH*V!r5 z4@Dwzu^r?$luI_H?Zf^CJ;QBd#NKRkXhc~<3h{*%d%Y-cHkfq}nBp;b66v`z$H?0f z5PCHJco@4DYIXFbRmRHdL(Yl5jJ~9kw~ZfUePIQr%>B&jy4tyYq*=&X()NoY=wdC5 zbnRCU^4zhfv2I3UyW$nV9_=<_o{fJAT@pt}m*RgnM`YR3b90n*%rn`?7m~Zq`zL!r zs@2Yjh{XO+>ThyaGTi%%JxjcE^9V~E)tVsBGO9^3(-^DgO`S3Oko`$^oF=_r)Hz=u zGiMQ*CCe;xll*|_%xH9M4Zo`)B6w>cjxnr3&5Zx$y@V)U#-1TK{6+#p(7 zLwdK`{h&tr(?vSz;ryP}lX}8iVeQWTkCNJ)%f+6G0kTSwH=-sQ$^DjEZ?w(MRBJ`q zog4m(+Qd_Nt0_y6--#EEG|z?PyAwe~LqHYDAFIC)7unsMSCEMCVqF16(r~Ni!z0%; z~Q`GdZKFQbcO7VPIGd!!in|^gW){3>>wXzAe zU4C`lD<2`o^R9yI#v$GPZf57P*n>^V`Fy_;<@zok#{TnnohMpDykF0X+4&7E`E_deA_zYi1V7vSsFwXwP>~>nUrId|O(W`*@FV=0#d{+-8+kWXo=6CIrjAmL29a z9p-xljcZBg zLM1AN zcJ;vKYGk}ka*47zd=2>>RvyNg0e0^F>2e>B(Ipacx~v_0P7E(%+?HJOHLom?OwubK z7;Q6FM^I#iHq-maHNO+tAzhK{NO3$@T8ZFfE;|R1ne{F`F_I@mc`Z?6G;n?6Vr*Va z4OMC!A`_Vvk!f)(8n8E82~j92$;aoAWw&HWnf*`xHo)*e?>>IXxLav64H9lvwc1w?~l`RyyZ$ zX0Dbj&0#@qra}Y9X(eLT-PgZ%$@Rae>jUbha?@hJR+9g!PMEs74LgnQ+x^HWv>eCE zo>k^Smco38ooq|d+#U3bq>`-7H63=RXFw#wn$-FVDy*--e|-h!tqrLSBlY{$YQHd{ r8LW6_Sgu{Sjz`8=HD0Q9c^+}d{-6`-V^>3WYYREo1Y}0uzW)CKE>khc literal 0 HcmV?d00001 diff --git a/gamefiles/data/PARTICLE.CFG b/gamefiles/data/PARTICLE.CFG new file mode 100644 index 0000000..8bb6d30 --- /dev/null +++ b/gamefiles/data/PARTICLE.CFG @@ -0,0 +1,363 @@ +; Author: Alexander Roger +; Date: 21/12/2000 +; +; Author: Andrzej Madajczyk +; Date: 26/02/2001 +; 14/03/2001 - Alpha (opacity) support added; +; 10/05/2001 - Drag/Friction Decceleration changed to constants; +; 28/08/2001 - Initial Color Variation added; +; +; +; +; +; Note! Last line of the file MUST BE ";the end\n", otherwise you'll get parsing error(s) of the file; +; +; +; +;Particle Systems Configuration Data:: Format +; +; +;A: Particle Type Name (max 20 chars) +; +;B/C/D: Render Colouring (r,g,b) (0-255) +; +;CV: Initial Color Variation (for r,g,b only, in %) (0-100); +; (i.e. Color=(100,100,100) and CV=20, then v=random(-20,20), real_color=(100+v, 100+v, 100+v)); +; +; +; +;B2/C2/D2: Fade Destination Color (r,g,b) (0-255) +; +;FT: Color Fade Time for (B,C,D)->(B2,C2,D2), (0 for none); +; +; +; +; +;E: Default Initial Radius (float) +;F: Expansion Rate (float) +; +; +; Color "Fade-to-Black" options: +;G: Initial Intensity (0-255) +;H: Fade Time (time between fade steps in frames) +;I: Fade Amount (-255 to 255) can get brighter or dimmer +; +; "Fade Alpha" options: +;GA: Initial Intensity (0-255) +;HA: Fade Time (time between fade steps in frames) +;IA: Fade Amount +; +; "Z Rotation" options: +;GZA: Initial Angle (0-1023) +;HZA: Change Time (time between steps in frames) +;IZA: Angle Change Amount +; +;GZR: Initial Z Radius +;HZR: Change Time (time between steps in frames) +;IZR: Z Radius Change Amount +; +; +;J: Animation Speed (0=no animation)(time between steps msec) +;K: Start Animation Frame ( 0 -> ) +;L: Final Animation Frame ( H -> ) +; +; +;M: Rotation Speed (0=None,i-deg/frame) +;N: Gravitational Acceleration (0=none, float) +;O: Drag/Friction Decceleration (int: 0=none, 50=0.50, 80=0.80, 90=0.90, 95=0.95, 96=0.96, 99=0.99) +; +;P: Default Life-Span of Particle (msec) +; +;Q: Position Random Error [position += (+/-)rand(a)] +;R: Velocity Random Error [velocity += (+/-)rand(b)] +;S: Expansion Rate Error [exp_rate += (+)rand(c)] +;T: Rotation Rate Error [rot_speed = (+/-)rand(d)] +;U: Life-Span Error Shape [shape distribution, e=0->all at default, e->Inf then shape->0] (max=255!!) +;V: Trail Length Multiplier [length *= (float) multiplier] (only used if trail flag active) +; +;CR:Particle Create Range (in meters: 0=no check); if particles are created enough far away from camera, they are deleted (not added to particle system); +; +; +;Z: Flags! Guide: 1=ZCHECK_FIRST, 2=ZCHECK_STEP, 4=DRAW_OPAQUE, 8=SCREEN_TRAIL, +; 16=SPEED_TRAIL, 32=RAND_VERT_V, 64=CYCLE_ANIM, 128=DRAW_DARK, 256=VERT_TRAIL +; 1024=DRAWTOP2D, 2048=CLIPOUT2D +; 4096=ZCHECK_BUMP, 8192=ZCHECK_BUMP_FIRST +; +; +; +;default: +;GUNFLASH 255 255 255 0 0.1 0.0 255 0 128 0 0 0 0 0.0 1.0 250 0.0 0.0 0.0 0 0 1.0 0 +; +;good idea for fire-smudge? +;GUNFLASH 255 255 255 0 1.0 0.0 255 0 32 100 0 3 0 0.0 1.0 400 0.0 0.0 0.0 0 0 1.0 0 +; +;current: +;GUNFLASH 255 255 255 0 1.0 0.0 255 0 32 100 0 3 0 0.0 1.0 400 0.0 0.0 0.0 0 0 1.0 0 +; +; +;SPARK_SMALL 255 255 128 0 0.005 0.0 255 0 0 0 0 0 0 0.0 1.0 500 0.0 0.05 0.0 0 0 0.5 40 +; +; +; +; +; +; +; +; A B C D CV B2 C2 D2 FT E F G H I GA HA IA GZA HZA IZA GZR HZR IZR J K L M N O P Q R S T U V CR Z +; +SPARK 255 128 64 0 0 0 0 0 0.005 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.001 1 300 0.0 0.07 0.0 0 0 1.0 20.0 48 +SPARK_SMALL 255 255 128 0 0 0 0 0 0.005 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.001 1 500 0.0 0.05 0.0 0 0 0.6 20.0 40 +; +WHEEL_DIRT 8 24 8 0 0 0 0 0 0.05 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.002 1 1000 0.15 0.015 0.0 0 0 1.0 30.0 4 +; +; +;WHEEL_WATER 24 24 24 0 0 0 0 0 0.05 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.002 1 1000 0.15 0.015 0.0 0 0 1.0 20.0 0 +WHEEL_WATER 24 24 32 0 0 0 0 0 0.05 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.004 1 1000 0.15 0.015 0.0 0 0 1.0 20.0 1 +; +; +BLOOD 128 128 128 0 0 0 0 0 0.02 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.03 1 2000 0.3 0.05 0.0 0 0 1.0 50.0 5 +BLOOD_SMALL 255 32 32 0 0 0 0 0 0.007 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.005 1 2000 0.05 0.05 0.0 0 0 1.0 50.0 53 +;BLOOD_SPLAT 128 128 128 0 0 0 0 0 0.1 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 1 200 0.3 0.0 0.0 0 0 1.0 400.0 36 +BLOOD_SPURT 255 32 32 0 0 0 0 0 0.008 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.005 1 2000 0.0 0.01 0.0 0 0 2.0 50.0 52 +DEBRIS 64 64 64 0 0 0 0 0 0.5 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.01 95 1000 0.2 0.0 0.0 0 0 1.0 50.0 4 +DEBRIS2 64 64 64 0 0 0 0 0 0.04 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 5 0.01 99 1000 0.03 0.04 0.0 0 0 1.0 50.0 38 +WATER 64 64 128 0 0 0 0 0 0.01 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 1 2000 0.0 0.0 0.0 0 0 1.0 100.0 0 +; +; +;FLAME 255 74 30 0 0 0 0 0 0.2 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 32 0 4 0 0.0 1 100 0.05 0.0 0.0 0 0 1.0 400.0 0 +;FLAME 255 74 30 0 0 255 0 400 0.8 -0.02 255 0 10 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 -0.005 1 2000 0.02 0.01 0.01 0 0 1.0 200.0 0 +FLAME 255 74 30 0 0 0 0 0 0.8 -0.02 255 0 10 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 -0.005 1 2000 0.02 0.01 0.01 0 0 1.0 200.0 0 +; +; +; +;FIREBALL 255 74 30 0 0 0 0 0 0.1 0.04 255 1 8 255 0 0 0 0 0 0.0 0 0.0 32 0 7 0 0.0 96 1000 0.1 0.0 0.0 0 0 1.0 400.0 0 +; +;FIREBALL 255 74 30 0 0 0 0 0 0.1 0.05 255 0 6 255 0 0 0 0 0 0.0 0 0.0 1 0 7 0 -0.002 96 2000 0.1 0.02 0.02 3 0 1.0 200.0 0 +FIREBALL 255 74 30 0 0 0 0 0 0.1 0.02 255 0 6 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 -0.003 96 2000 0.1 0.03 0.014 2.5 0 1.0 200.0 0 +; +; +; +GUNFLASH 170 170 170 0 0 0 0 0 0.1 0.0 255 1 50 255 0 0 0 0 0 0.0 0 0.0 51 0 3 0 0.0 1 250 0.0 0.0 0.0 0 0 1.0 35.0 0 +GUNFLASH_NOANIM 128 128 128 0 0 0 0 0 0.1 0.0 255 1 128 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 1 25 0.0 0.0 0.0 0 0 1.0 35.0 0 +; +GUNSMOKE 64 64 64 0 0 0 0 0 0.15 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 2 0 7 0 -0.002 95 1000 0.0 0.0 0.0 0 0 1.0 60.0 0 +GUNSMOKE2 255 255 255 0 0 0 0 0 0.05 0.02 255 0 0 255 0 8 0 0 0 0.0 0 0.0 0 0 3 4 -0.001 80 1400 0.05 0.05 0.01 3 0 1.0 60.0 4 +; +; +SMOKE 32 32 32 0 0 0 0 0 0.15 0.015 255 5 25 255 0 0 0 0 0 0.0 0 0.0 32 0 4 0 -0.01 95 1000 0.05 0.05 0.01 3 0 1.0 150.0 0 +;SMOKE_SLOWMOTION 32 32 32 0 0 0 0 0 0.15 0.015 255 5 15 255 0 0 0 0 0 0.0 0 0.0 32 0 4 0 -0.003 95 1000 0.05 0.05 0.01 3 0 1.0 400.0 0 +SMOKE_SLOWMOTION 32 32 32 0 0 0 0 0 0.15 0.015 128 5 11 255 0 0 0 0 0 0.0 0 0.0 32 0 4 0 -0.003 95 3000 0.05 0.05 0.01 3 0 1.0 150.0 0 +; +; +; +;GARAGEPAINT_SPRAY 32 32 32 0 0 0 0 0 0.15 0.015 255 0 5 255 0 0 0 0 0 0.0 0 0.0 0 0 4 0 -0.001 95 2000 0.05 0.05 0.01 3 0 1.0 400.0 0 +GARAGEPAINT_SPRAY 32 32 32 0 0 0 0 0 0.15 0.015 255 0 5 255 0 0 0 0 0 0.0 0 0.0 0 0 4 0 -0.0005 95 4000 0.05 0.05 0.01 3 0 1.0 100.0 0 +SHARD 255 255 255 0 0 0 0 0 0.03 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 96 300 0.0 0.0 0.0 0 0 1.0 100.0 0 +SPLASH 64 64 128 0 0 0 0 0 0.1 0.007 255 1 10 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 1 1000 0.0 0.0 0.0 0 0 1.0 100.0 0 +;BLOOD_SPLASH 24 64 0 0 0 0 0 0 0.1 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 96 300 0.0 0.0 0.0 0 0 1.0 100.0 0 +; +; +;CARFLAME 255 74 30 0 0 0 0 0 0.5 0.04 255 2 20 255 0 0 0 0 0 0.0 0 0.0 32 0 4 0 0.0 1 1000 0.4 0.0 0.0 0 0 1.0 400.0 64 +;CARFLAME 255 74 30 0 0 0 0 0 0.8 -0.02 255 0 10 255 0 0 0 0 0 0.0 0 0.0 32 0 4 0 -0.001 1 2000 0.4 0.01 0.01 0 0 1.0 400.0 64 +;CARFLAME 255 74 30 0 0 0 0 0 0.8 -0.02 255 0 10 255 0 0 0 0 0 0.0 0 0.0 32 0 4 0 -0.001 1 2000 0.4 0.01 0.01 0 0 1.0 400.0 64 +; +CARFLAME 255 74 30 0 0 0 0 0 0.8 -0.02 255 0 10 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 -0.005 1 2000 0.02 0.01 0.01 0 0 1.0 100.0 0 +; +; +STEAM 64 64 64 0 0 0 0 0 0.5 0.05 255 1 16 255 0 0 0 0 0 0.0 0 0.0 32 0 4 0 -0.005 95 2000 0.01 0.03 0.0 0 0 1.0 85.0 0 +; +;default: +;STEAM2 255 255 255 0 0 0 0 0 0.5 0.05 255 0 0 128 2 8 0 0 0 0.0 0 0.0 32 0 4 0 -0.005 95 2000 0.01 0.03 0.0 0 0 1.0 400.0 4 +STEAM2 255 255 255 0 0 0 0 0 0.5 0.015 255 0 0 192 0 1 0 0 10 0.5 1 0.02 32 0 4 0 -0.002 95 8000 0.01 0.03 0.0 0 0 1.0 85.0 4 +; +; +;STEAM_NY 255 255 255 0 0 0 0 0 0.5 0.05 255 0 0 128 2 8 0 0 0 0.0 0 0.0 32 0 4 0 -0.005 95 2000 0.01 0.03 0.0 0 0 1.0 400.0 4 +STEAM_NY 255 255 255 0 0 0 0 0 0.5 0.05 255 0 0 96 2 8 0 0 0 0.0 0 0.0 32 0 4 0 -0.005 95 1400 0.01 0.03 0.0 0 0 1.0 85.0 4 +STEAM_NY_SLOWMOTION 255 255 255 0 0 0 0 0 0.5 0.05 255 0 0 96 2 8 0 0 0 0.0 0 0.0 32 0 4 0 -0.0015 95 1400 0.01 0.03 0.0 0 0 1.0 85.0 4 +; +; +;ENGINE_STEAM 210 210 210 0 0 0 0 0 0.5 0.05 255 0 0 192 2 16 0 0 0 0.0 0 0.0 32 0 4 0 -0.005 95 2000 0.01 0.03 0.0 0 0 1.0 250.0 4 +ENGINE_STEAM 210 210 210 0 0 0 0 0 0.5 0.05 255 0 0 192 0 10 0 0 0 0.0 0 0.0 32 0 4 1 -0.005 95 4000 0.03 0.03 0.02 0 0 1.0 85.0 4 +; +; +;RAINDROP 32 32 32 0 0 0 0 0 0.6 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.025 1 1000 0.0 0.0 0.0 0 0 1.0 15.0 1 +RAINDROP 64 64 64 0 0 0 0 0 0.4 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 3 0 0.05 1 1000 0.0 0.0 0.0 0 0 1.0 15.0 1 +RAINDROP_SMALL 16 16 16 0 0 0 0 0 0.3 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.05 1 1000 0.0 0.0 0.0 0 0 1.0 15.0 1 +RAIN_SPLASH 32 32 32 0 0 0 0 0 0.08 0.0 255 0 5 255 0 0 0 0 0 0.0 0 0.0 1 0 4 0 0.0 1 500 0.0 0.0 0.0 0 0 1.0 15.0 0 +RAIN_SPLASH_BIGGROW 128 128 128 0 0 0 0 0 0.5 0.06 255 0 2 255 0 0 0 0 0 0.0 0 0.0 2 1 4 0 0.0 1 5500 0.0 0.0 0.0 0 0 1.0 15.0 0 +RAIN_SPLASHUP 48 48 48 0 0 0 0 0 0.1 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 1 0 0.0 1 50 0.0 0.0 0.0 0 0 1.0 15.0 0 +; +WATERSPRAY 64 64 64 0 0 0 0 0 0.2 0.0 255 0 25 255 0 0 0 0 0 0.0 0 0.0 3 0 2 0 0.002 1 800 0.05 0.0 0.01 0 0 1.0 20.0 0 +; +; +; +;EXPLOSION_MEDIUM 80 80 80 0 0 0 0 0 0.6 0.04 255 5 8 255 0 0 0 0 0 0.0 0 0.0 8 0 11 0 0.0 96 15000 0.2 0.0 0.0 3 0 1.0 400.0 0 +;EXPLOSION_LARGE 80 80 80 0 0 0 0 0 1.1 0.04 255 5 8 255 0 0 0 0 0 0.0 0 0.0 8 0 11 0 0.0 96 15000 0.8 0.0 0.0 3 0 1.0 400.0 0 +;EXPLOSION_MEDIUM 80 80 80 0 0 0 0 0 0.6 0.04 255 1 4 255 0 0 0 0 0 0.0 0 0.0 1 0 11 0 0.0 96 7000 0.2 0.0 0.0 0 0 1.0 400.0 0 +;EXPLOSION_LARGE 80 80 80 0 0 0 0 0 1.1 0.04 255 1 4 255 0 0 0 0 0 0.0 0 0.0 1 0 11 0 0.0 96 7000 0.8 0.0 0.0 0 0 1.0 400.0 0 +; +;EXPLOSION_MEDIUM 80 80 80 0 0 0 0 0 0.6 0.04 255 0 3 255 0 0 0 0 0 0.0 0 0.0 1 0 11 0 -0.001 96 6000 0.2 0.0 0.0 0 0 1.0 400.0 0 +;EXPLOSION_LARGE 80 80 80 0 0 0 0 0 1.1 0.04 255 0 3 255 0 0 0 0 0 0.0 0 0.0 1 0 11 0 -0.001 96 6000 0.8 0.0 0.0 0 0 1.0 400.0 0 +EXPLOSION_MEDIUM 80 80 80 0 0 0 0 0 0.6 0.04 255 0 3 255 0 0 0 0 0 0.0 0 0.0 2 0 5 0 -0.001 96 6000 0.2 0.0 0.0 0 0 1.0 200.0 0 +EXPLOSION_LARGE 80 80 80 0 0 0 0 0 1.1 0.04 255 0 3 255 0 0 0 0 0 0.0 0 0.0 2 0 5 0 -0.001 96 6000 0.8 0.0 0.0 0 0 1.0 200.0 0 +EXPLOSION_MFAST 80 80 80 0 0 0 0 0 0.6 0.04 255 0 6 255 0 0 0 0 0 0.0 0 0.0 2 0 5 0 -0.001 96 3500 0.2 0.0 0.0 0 0 1.0 200.0 0 +EXPLOSION_LFAST 80 80 80 0 0 0 0 0 1.1 0.04 255 0 6 255 0 0 0 0 0 0.0 0 0.0 2 0 5 0 -0.001 96 3500 0.8 0.0 0.0 0 0 1.0 200.0 0 +; +; +; +; +;BOAT_SPLASH 32 64 32 0 0 0 0 0 0.2 0.2 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.01 1 2000 0.0 0.0 0.0 0 0 1.0 200.0 0 +;BOAT_THRUSTJET 24 32 24 0 0 0 0 0 0.5 0.1 255 0 0 255 0 0 0 0 0 0.0 0 0.0 250 0 4 0 0.01 50 1000 0.0 0.0 0.0 0 4 1.0 200.0 8 +;BOAT_SPLASH 16 32 32 0 0 0 0 0 0.2 0.2 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.01 1 2000 0.0 0.0 0.0 0 0 1.0 200.0 0 +;BOAT_THRUSTJET 8 24 24 0 0 0 0 0 0.5 0.1 255 0 0 255 0 0 0 0 0 0.0 0 0.0 250 0 4 0 0.01 50 1000 0.0 0.0 0.0 0 4 1.0 200.0 8 +;CAR_SPLASH 64 64 64 0 0 0 0 0 2.0 0.25 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.02 1 2000 0.0 0.0 0.0 0 0 1.0 250.0 0 +;CAR_SPLASH 64 64 64 0 0 0 0 0 2.0 0.25 255 0 0 200 0 8 0 0 0 0.0 0 0.0 0 0 0 0 0.04 1 2000 0.0 0.0 0.0 0 0 1.0 150.0 4 +;CAR_SPLASH 64 64 64 0 0 0 0 0 2.0 0.35 255 0 0 200 0 8 0 0 0 0.0 0 0.0 0 0 0 0 0.05 1 2000 0.0 0.0 0.0 0 0 1.0 150.0 4 +;CAR_SPLASH 64 64 64 0 0 0 0 0 1.0 0.25 255 0 0 180 0 5 0 0 0 0.0 0 0.0 2 1 3 0 0.05 1 1000 0.0 0.0 0.0 0 0 1.0 150.0 12 +; +; +;CAR_SPLASH 64 64 64 0 0 0 0 0 1.0 0.15 255 0 0 180 0 2 0 0 0 0.0 0 0.0 2 0 3 0 0.02 1 2000 0.0 0.0 0.0 0 0 1.0 150.0 12 +;CAR_SPLASH 48 48 64 0 0 0 0 0 1.0 0.15 96 0 0 255 0 0 0 0 0 0.0 0 0.0 6 0 2 0 0.01 1 2000 0.5 0.04 0.0 0 0 2.0 150.0 288 +;CAR_SPLASH 48 48 64 0 0 0 0 0 1.0 0.05 96 0 0 255 0 0 0 0 0 0.0 0 0.0 0 1 2 0 0.01 1 2000 0.5 0.04 0.0 0 0 2.0 150.0 288 +; A B C D CV B2 C2 D2 FT E F G H I GA HA IA GZA HZA IZA GZR HZR IZR J K L M N O P Q R S T U V CR Z +CAR_SPLASH 48 48 60 0 0 0 0 0 1.0 0.00 128 1 4 128 0 0 0 0 0 0.0 0 0.0 0 0 2 0 0.01 1 2000 0.5 0.04 0.0 0 0 1.4 150.0 272 +; +; +; +;BOAT_SPLASH 70 70 70 0 0 0 0 0 0.2 0.2 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.01 1 1000 0.0 0.0 0.0 0 0 1.0 150.0 0 +BOAT_SPLASH 64 64 64 0 0 0 0 0 0.2 0.2 255 0 2 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.01 1 1000 0.0 0.0 0.0 0 0 1.0 150.0 0 +; +; +;BOAT_THRUSTJET 90 90 90 0 0 0 0 0 1.8 0.1 255 0 0 120 0 1 0 0 0 0.0 0 0.0 0 1 4 0 0.01 50 1600 0.8 0.4 0.02 0 4 1.0 150.0 4 +BOAT_THRUSTJET 90 90 90 0 0 0 0 0 1.4 0.06 255 0 0 96 0 1 0 0 0 0.0 0 0.0 0 1 4 0 0.01 50 1600 0.8 0.4 0.02 0 4 1.0 150.0 4 +; +; +;BOAT_WAKE 255 255 255 0 0 0 0 0 2.0 0.2 255 0 0 128 0 1 0 0 0 0.0 0 0.0 0 0 0 0 0.03 50 1600 0.8 0.4 0.02 0 4 1.0 150.0 4 +BOAT_WAKE 255 255 255 0 0 0 0 0 1.5 0.45 255 0 0 192 0 2 0 0 0 0.0 0 0.0 0 0 0 0 0.0 50 1600 0.8 0.4 0.02 0 4 1.0 150.0 4 +; +; +; +; +; +; A B C D CV B2 C2 D2 FT E F G H I GA HA IA GZA HZA IZA GZR HZR IZR J K L M N O P Q R S T U V CR Z +WATER_HYDRANT 64 64 64 0 0 0 0 0 0.8 0.01 255 1 16 255 1 16 0 0 0 0.0 0 0.0 0 0 2 0 0.007 99 500 0.02 0.08 0.0 0 4 1.0 85.0 16 +WATER_CANNON 64 64 128 0 0 0 0 0 0.03 0.03 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 1 1000 0.0 0.0 0.0 0 0 1.0 85.0 0 +EXTINGUISH_STEAM 32 32 32 0 0 0 0 0 0.1 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 1 1000 0.0 0.0 0.0 0 0 1.0 85.0 0 +; +; +; +;PED_SPLASH 32 32 64 0 0 0 0 0 0.1 0.05 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 1 1000 0.0 0.0 0.0 0 0 1.0 85.0 0 +PED_SPLASH 48 48 60 0 0 0 0 0 0.1 0.06 96 0 0 255 0 0 0 0 0 0.0 0 0.0 0 1 2 0 0.01 1 2000 0.5 0.04 0.0 0 0 1.4 50.0 256 +; +; +PEDFOOT_DUST 170 166 150 0 0 0 0 0 0.01 0.015 255 0 0 63 0 4 0 0 0 0.0 0 0.0 0 0 0 0 -0.0005 1 1000 0.0 0.0 0.0 0 0 1.0 6.0 4 +; +HELI_DUST 17 15 9 0 0 0 0 0 0.2 0.1 255 1 8 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 -0.001 1 1000 0.2 0.05 0.0 0 0 1.0 85.0 0 +HELI_ATTACK 255 255 128 0 0 0 0 0 0.01 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 1 500 0.0 0.0 0.0 0 0 0.5 85.0 10 +; +; +;ENGINE_SMOKE 16 16 16 0 0 0 0 0 0.5 0.04 255 0 0 63 0 0 0 0 0 0.0 0 0.0 0 0 0 0 -0.005 95 2000 0.01 0.03 0.0 0 0 1.0 150.0 4 +;ENGINE_SMOKE2 8 8 8 0 0 0 0 0 1.0 0.2 128 2 4 63 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.001 1 1000 0.0 0.0 0.0 0 3 1.0 150.0 4 +ENGINE_SMOKE 16 16 16 0 0 0 0 0 0.5 0.04 255 0 0 52 0 2 10 0 80 0.0 0 0.0 0 0 5 2 -0.009 95 2000 0.11 0.03 0.01 1 0 1.0 85.0 4 +ENGINE_SMOKE2 9 9 9 80 0 0 0 0 1.0 0.06 128 0 1 140 0 5 10 0 80 0.0 0 0.0 0 0 0 2 0.002 1 1300 0.0 0.01 0.0 3 3 1.0 85.0 4 +; +; +CARFLAME_SMOKE 32 32 32 0 0 0 0 0 0.05 0.01 255 0 0 64 0 2 0 0 0 0.0 0 0.0 0 0 0 0 -0.008 95 2000 0.01 0.03 0.01 0 0 1.0 85.0 4 +FIREBALL_SMOKE 32 32 32 0 0 0 0 0 0.05 0.03 255 0 0 128 0 2 0 0 0 0.0 0 0.0 0 0 0 0 -0.004 95 2000 0.01 0.03 0.01 0 0 1.0 85.0 4 +; +PAINT_SMOKE 255 0 0 0 0 0 0 0 0.1 0.01 255 1 8 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 95 3000 0.0 0.005 0.0 0 0 1.0 85.0 0 +TREE_LEAVES 64 64 64 0 0 0 0 0 0.2 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 1 1000 0.0 0.0 0.0 0 0 1.0 85.0 0 +; +; +;CARCOLLISION_DUST 224 224 224 0 0 0 0 0 0.15 0.04 255 0 0 127 1 8 0 0 0 0.0 0 0.0 0 0 0 0 -0.002 90 2000 0.02 0.02 0.0 0 0 1.0 80.0 4 +CARCOLLISION_DUST 76 76 76 0 0 0 0 0 0.10 0.02 255 0 0 160 0 4 0 0 0 0.0 0 0.0 0 0 0 0 -0.0015 90 2000 0.02 0.02 0.0 0 0 1.0 30.0 4 +; +; +CAR_DEBRIS 32 32 32 0 0 0 0 0 0.5 0.0 224 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 4 0 0.010 90 1000 0.02 0.02 0.0 0 0 1.0 50.0 4 +HELI_DEBRIS 32 32 32 0 0 0 0 0 1.5 0.0 224 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 4 0 0.065 90 1500 0.02 0.02 0.0 0 0 1.0 150.0 4 +; +; +; +;EXHAUST_FUMES 80 80 80 0 0 0 0 0 0.03 0.03 255 0 0 122 0 4 0 0 0 0.0 0 0.0 2 0 4 0 -0.001 95 1500 0.01 0.03 0.0 0 0 1.0 50.0 4 +EXHAUST_FUMES 98 98 108 0 0 0 0 0 0.03 0.06 255 0 0 152 0 12 0 0 0 0.0 0 0.0 2 0 4 0 -0.002 96 1000 0.01 0.03 0.0 0 0 1.0 25.0 4 +; +; +;RUBBER 40 40 40 0 0 0 0 0 0.4 0.005 255 21 20 255 0 0 0 0 0 0.0 0 0.0 3 0 4 0 -0.0005 1 1000 0.02 0.0 0.0 0 0 1.0 400.0 4 +RUBBER_SMOKE 255 255 255 0 0 0 0 0 0.4 0.005 255 0 0 127 1 8 0 0 0 0.0 0 0.0 3 0 4 0 -0.0005 1 1000 0.02 0.0 0.0 0 0 1.0 50.0 4 +;BURNINGRUBBER_SMOKE128 128 128 0 0 0 0 0 0.35 0.06 255 0 0 192 1 6 0 0 0 0.0 0 0.0 0 0 0 0 -0.002 90 4000 0.02 0.02 0.0 0 0 1.0 400.0 4 +BURNINGRUBBER_SMOKE 128 128 128 0 0 0 0 0 0.35 0.06 255 0 0 128 0 4 0 0 0 0.0 0 0.0 0 0 0 0 -0.002 90 2000 0.02 0.02 0.0 0 0 1.0 50.0 4 +; +; +BULLETHIT_SMOKE 192 192 192 0 0 0 0 0 0.15 0.03 70 0 2 255 1 10 0 0 0 0.0 0 0.0 0 0 0 0 -0.001 90 2000 0.04 0.02 0.0 0 0 1.0 150.0 0 +; +; +GUNSHELL_FIRST 108 108 108 0 0 0 0 0 0.015 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 4 0 0.010 90 1000 0.02 0.02 0.0 0 0 1.0 0.0 12292 +GUNSHELL 108 108 108 0 0 0 0 0 0.015 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 4 0 0.010 90 1000 0.02 0.02 0.0 0 0 1.0 12.0 4100 +GUNSHELL_BUMP1 108 108 108 0 0 0 0 0 0.015 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 4 0 0.010 90 1000 0.02 0.02 0.0 0 0 1.0 8.0 4100 +GUNSHELL_BUMP2 108 108 108 0 0 0 0 0 0.015 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 0 4 0 0.010 90 400 0.02 0.02 0.0 0 0 1.0 8.0 4100 +; +; +TEST 255 64 64 0 0 0 0 0 0.2 0.025 255 1 20 255 0 0 0 0 0 0.0 0 0.0 0 0 0 0 0.0 1 3000 0.0 0.0 0.0 0 0 1.0 400.0 128 +; +; +;Particles with flag DRAWTOP2D should be placed last and VR (Visibility Range) set to 0! +; +;BIRD_FRONT 8 8 8 0 0 0 0 0 0.05 0.0 255 0 0 255 2 1 0 0 0 0.0 0 0.0 1 0 3 0 0.0 1 10000 0.0 0.0 0.0 0 0 1.0 0.0 3140 +BIRD_FRONT 8 8 8 0 0 0 0 0 1.05 0.0 255 0 0 255 2 2 0 0 0 0.0 0 0.0 1 0 3 0 0.0 1 8000 0.0 0.0 0.0 0 0 1.0 0.0 68 +; +RAINDROP_2D 32 32 32 0 0 0 0 0 0.5 0.0 255 0 0 255 0 0 0 0 0 0.0 0 0.0 0 1 0 0 0.0 1 1000 0.0 0.0 0.0 0 0 1.0 0.0 3072 +; +; +; +; +; +; +; +; +; +; +; +; +; below is just backup of above values: +; +;SPARK 255 128 64 0.005 0.0 255 0 0 0 0 0 0 0.0 1.0 300 0.0 0.07 0.0 0 0 1.0 48 +;SPARK_SMALL 255 255 128 0.005 0.0 255 0 0 0 0 0 0 0.0 1.0 500 0.0 0.05 0.0 0 0 0.6 40 +;BLOOD 128 128 128 0.02 0.0 255 0 0 0 0 0 0 0.03 1.0 2000 0.3 0.05 0.0 0 0 1.0 6 +;BLOOD_SMALL 255 32 32 0.007 0.0 255 0 0 0 0 0 0 0.005 1.0 2000 0.05 0.05 0.0 0 0 1.0 54 +;BLOOD_SPLAT 128 128 128 0.1 0.0 255 0 0 0 0 0 0 0.0 1.0 200 0.3 0.0 0.0 0 0 1.0 36 +;BLOOD_SPURT 255 32 32 0.008 0.0 255 0 0 0 0 0 0 0.005 1.0 2000 0.0 0.01 0.0 0 0 2.0 52 +;DEBRIS 64 64 64 0.5 0.0 255 0 0 0 0 0 0 0.01 0.95 1000 0.2 0.0 0.0 0 0 1.0 4 +;DEBRIS2 64 64 64 0.04 0.0 255 0 0 0 0 0 5 0.01 0.99 1000 0.03 0.04 0.0 0 0 1.0 38 +;WATER 64 64 128 0.01 0.0 255 0 0 0 0 0 0 0.0 1.0 2000 0.0 0.0 0.0 0 0 1.0 0 +;FLAME 255 74 30 0.2 0.0 255 0 0 31 0 5 0 0.0 1.0 100 0.05 0.0 0.0 0 0 1.0 0 +;FIREBALL 255 74 30 0.1 0.04 255 0 8 31 0 8 0 0.0 0.96 1000 0.1 0.0 0.0 0 0 1.0 0 +;GUNFLASH 255 255 255 0.1 0.0 255 0 50 50 0 3 0 0.0 1.0 250 0.0 0.0 0.0 0 0 1.0 0 +;GUNFLASHSTATIC 255 255 255 0.1 0.0 255 0 128 0 0 0 0 0.0 1.0 25 0.0 0.0 0.0 0 0 1.0 0 +;SMOKE 32 32 32 0.15 0.015 255 4 25 31 0 5 0 -0.01 0.95 1000 0.05 0.05 0.01 3 0 1.0 0 +;SHARD 255 255 255 0.03 0.0 255 0 0 0 0 0 0 0.0 0.96 300 0.0 0.0 0.0 0 0 1.0 0 +;SPLASH 64 64 128 0.1 0.007 255 0 10 0 0 0 0 0.0 1.0 1000 0.0 0.0 0.0 0 0 1.0 0 +;BLOOD_SPLASH 24 64 0 0.1 0.0 255 0 0 0 0 0 0 0.0 0.96 300 0.0 0.0 0.0 0 0 1.0 0 +;RUBBER 40 40 40 0.4 0.005 255 1 25 31 0 5 0 0.0 1.0 1000 0.02 0.0 0.0 0 0 1.0 0 +;CARFLAME 255 74 30 0.5 0.04 255 1 20 31 0 5 0 0.0 1.0 1000 0.4 0.0 0.0 0 0 1.0 64 +;STEAM 64 64 64 0.5 0.05 255 0 16 31 0 5 0 -0.005 0.95 2000 0.01 0.03 0.0 0 0 1.0 0 +;RAINDROP 32 32 32 0.6 0.0 255 0 0 0 0 0 0 0.1 1.0 1000 0.0 0.0 0.0 0 0 1.0 1 +;RAIN_SPLASH 32 32 32 0.08 0.0 255 0 0 1 0 4 0 0.0 1.0 1000 0.0 0.0 0.0 0 0 1.0 0 +;RAINDROP_SMALL 32 32 32 0.3 0.0 255 0 0 0 0 0 0 0.1 1.0 1000 0.0 0.0 0.0 0 0 1.0 1 +;EXPLOSION_MEDIUM 80 80 80 0.6 0.04 255 4 8 7 0 11 0 0.0 0.96 30000 0.2 0.0 0.0 3 0 1.0 0 +;EXPLOSION_LARGE 80 80 80 1.1 0.04 255 4 8 7 0 11 0 0.0 0.96 30000 0.8 0.0 0.0 3 0 1.0 0 +;BOAT_SPLASH 32 64 32 0.2 0.2 255 0 0 0 0 0 0 0.01 1.0 2000 0.0 0.0 0.0 0 0 1.0 0 +;BOAT_THRUSTJET 24 32 24 0.5 0.1 255 0 0 250 0 5 0 0.01 0.5 1000 0.0 0.0 0.0 0 4 1.0 8 +;WATER_HYDRANT 64 64 128 0.4 0.01 255 1 2 20 0 5 0 0.007 0.99 500 0.02 0.05 0.0 0 4 1.0 256 +;WATER_CANNON 64 64 128 0.03 0.03 255 0 0 0 0 0 0 0.0 1.0 1000 0.0 0.0 0.0 0 0 1.0 0 +;EXTINGUISH_STEAM 32 32 32 0.1 0.0 255 0 0 0 0 0 0 0.0 1.0 1000 0.0 0.0 0.0 0 0 1.0 0 +;PED_SPLASH 32 32 64 0.1 0.05 255 0 0 0 0 0 0 0.0 1.0 1000 0.0 0.0 0.0 0 0 1.0 0 +;HELI_DUST 17 15 9 0.2 0.1 255 0 8 0 0 0 0 -0.001 1.0 1000 0.2 0.05 0.0 0 0 1.0 0 +;HELI_ATTACK 255 255 128 0.01 0.0 255 0 0 0 0 0 0 0.0 1.0 500 0.0 0.0 0.0 0 0 0.5 10 +;ENGINE_SMOKE 16 16 16 0.5 0.04 255 0 0 0 0 0 0 -0.005 0.95 2000 0.01 0.03 0.0 0 0 1.0 4 +;ENGINE_SMOKE2 4 4 4 1.0 0.2 255 1 4 0 0 0 0 0.001 1.0 1000 0.0 0.0 0.0 0 3 1.0 4 +;PAINT_SMOKE 255 0 0 0.1 0.01 255 0 8 0 0 0 0 0.0 0.95 3000 0.0 0.005 0.0 0 0 1.0 0 +;TREE_LEAVES 64 64 64 0.2 0.0 255 0 0 0 0 0 0 0.0 1.0 1000 0.0 0.0 0.0 0 0 1.0 0 +;TEST 255 64 64 0.2 0.05 255 0 16 0 0 0 0 0.0 1.0 3000 0.0 0.0 0.0 0 0 1.0 128 +; +; +;the end diff --git a/gamefiles/data/main_d.scm b/gamefiles/data/main_d.scm new file mode 100644 index 0000000000000000000000000000000000000000..7b46ca39914259bbca8fd1d82f6af2cdeea45e53 GIT binary patch literal 620154 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 z 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

i0#~1N8jlT=YmF`fzh`(w4U5M%*O(oyN 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 Wk7i%F>g$gmNiNP9R`7UM>Hh;Q14J(X literal 0 HcmV?d00001 diff --git a/gamefiles/data/main_freeroam.scm b/gamefiles/data/main_freeroam.scm new file mode 100644 index 0000000000000000000000000000000000000000..021b5c256ee1eeb15e2fc30578a9717889f92ab5 GIT binary patch literal 1618 zcmb`HJ4{qT6hQCH&f9<=vI%}5A%++WOiTnrL7@qsHLwQIfQlhBCQ|H1$rc#U_*p-t zT3A_909Ga>BvuwA#L7r$(8dBf3+*(~&U4?)dru%(xy9a@x#!-wpLy4c8Ib?*vtn+l z8}koeFf~<=^jlZ17{}>{Tz)e?7x~0? z;IS(YiTQ{l!xqoWEEmL;1BUqlzn?o3+!dsRyH)utrhzakCi zLAC_h8srI;+}Oq+;*=NdUUYiV<;7Vqy1gh8+QNl?poEz9GCpMW!3(aAq5mu}{eh`=@SO}^>?!v3jEKA84EIP}NC)yAg!geQ7mH7>HF6?@S{Y+{B{z_?h{X-0`V{S1;XH61On`t%NU{8jioHQjFhLb}+`GINII4}CbO A?EnA( literal 0 HcmV?d00001 diff --git a/gamefiles/gamecontrollerdb.txt b/gamefiles/gamecontrollerdb.txt new file mode 100644 index 0000000..728fddc --- /dev/null +++ b/gamefiles/gamecontrollerdb.txt @@ -0,0 +1,943 @@ +# Game Controller DB for SDL in 2.0.9 format +# Source: https://github.com/gabomdq/SDL_GameControllerDB + +# Windows +03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows, +03000000c82d00002038000000000000,8bitdo,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d000011ab000000000000,8BitDo F30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00001038000000000000,8BitDo F30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000650000000000000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:a4,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00005106000000000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000310000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00002028000000000000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00008010000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00000190000000000000,8BitDo N30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00015900000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00065280000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000022000000090000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000203800000900000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000130000000000000,8BitDo SF30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000060000000000000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000061000000000000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d000021ab000000000000,8BitDo SFC30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000102800000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00003028000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000030000000000000,8BitDo SN30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000351000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00001290000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d000020ab000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00004028000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00006228000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000260000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000261000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000031000000000000,8BitDo Wireless Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001890000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00003032000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000a00500003232000000000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows, +030000008f0e00001200000000000000,Acme GA-02,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000fa190000f0ff000000000000,Acteck AGJ-3200,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000006f0e00001413000000000000,Afterglow,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000263000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001101000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001401000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001402000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001901000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001a01000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d62000001d57000000000000,Airflo PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000869800002400000000007801,Astro C40 TR,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000d6200000e557000000000000,Batarang,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows, +030000006f0e00003201000000000000,Battlefield 4 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d62000002a79000000000000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000bc2000006012000000000000,Betop 2126F,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bc2000000055000000000000,Betop BFM Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000bc2000006312000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bc2000006321000000000000,BETOP CONTROLLER,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bc2000006412000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000c01100000555000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000c01100000655000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000790000000700000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000808300000300000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +030000006b1400000055000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000006b1400000103000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +0300000066f700000500000000000000,BrutalLegendTest,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows, +03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000457500000401000000000000,Cobra,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000005e0400008e02000000000000,Controller (XBOX 360 For Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000a102000000000000,Controller (Xbox 360 Wireless Receiver for Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000ff02000000000000,Controller (Xbox One For Windows) - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000ea02000000000000,Controller (Xbox One For Windows) - Wireless,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows, +03000000a306000022f6000000000000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000451300000830000000000000,Defender Game Racer X7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000007d0400000840000000000000,Destroyer Tiltpad,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,x:b0,y:b3,platform:Windows, +03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bd12000002e0000000000000,Dual USB Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows, +030000008f0e00000910000000000000,DualShock 2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows, +030000006f0e00003001000000000000,EA SPORTS PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000b80500000410000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows, +03000000b80500000610000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows, +03000000120c0000f61c000000000000,Elite,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000008f0e00000f31000000000000,EXEQ,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000006f0e00008401000000000000,Faceoff Deluxe+ Audio Wired Controller for Nintendo Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00008001000000000000,Faceoff Wired Pro Controller for Nintendo Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000852100000201000000000000,FF-GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008500000000000000,Fighting Commander 2016 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008400000000000000,Fighting Commander 5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008700000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008800000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows, +030000000d0f00002700000000000000,FIGHTING STICK V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +78696e70757403000000000000000000,Fightstick TES,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows, +03000000790000002201000000000000,Game Controller for PC,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +0300000066f700000100000000000000,Game VIB Joystick,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows, +03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows, +03000000790000004618000000000000,GameCube Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000280400000140000000000000,GamePad Pro USB,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000ac0500003d03000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000ac0500004d04000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000ffff00000000000000000000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000c01100000140000000000000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000102000000007801,GameStop Xbox 360 Wired Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000009b2800003200000000000000,GC/N64 to USB v3.4,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows, +030000009b2800006000000000000000,GC/N64 to USB v3.6,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows, +030000008305000009a0000000000000,Genius,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000005c1a00003330000000000000,Genius MaxFire Grandias 12V,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000300f00000b01000000000000,GGE909 Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c283000000000000,Gioteck,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000f025000021c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c383000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c483000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000007d0400000540000000000000,Gravis Eliminator GamePad Pro,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00004900000000000000,Hatsune Miku Sho Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000001008000001e1000000000000,Havit HV-G60,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b0,platform:Windows, +03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows, +03000000632500002605000000000000,HJD-X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +030000000d0f00002d00000000000000,Hori Fighting Commander 3 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005f00000000000000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005e00000000000000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005400000000000000,Hori Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00000900000000000000,Hori Pad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00004d00000000000000,Hori Pad A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00009200000000000000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00001600000000007803,HORI Real Arcade Pro EX-SE (Xbox 360),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f00009c00000000000000,Hori TAC Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000c100000000000000,Horipad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006e00000000000000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006600000000000000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005500000000000000,Horipad 4 FPS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000ee00000000000000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows, +030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Windows, +03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Windows, +03000000b50700001403000000000000,Impact Black,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +030000006f0e00002401000000000000,INJUSTICE FightStick PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000ac0500002c02000000000000,IPEGA,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000491900000304000000000000,Ipega PG-9087 - Bluetooth Gamepad,+righty:+a5,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows, +030000006e0500000a20000000000000,JC-DUX60 ELECOM MMO Gamepad,a:b2,b:b3,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b14,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b15,righttrigger:b13,rightx:a3,righty:a4,start:b20,x:b0,y:b1,platform:Windows, +030000006e0500000520000000000000,JC-P301U,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows, +030000006e0500000320000000000000,JC-U3613M (DInput),a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows, +030000006e0500000720000000000000,JC-W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows, +030000007e0500000620000000000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Windows, +030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Windows, +030000007e0500000720000000000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows, +030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows, +03000000bd12000003c0000000000000,JY-P70UR,a:b1,b:b0,back:b5,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b11,righttrigger:b9,rightx:a3,righty:a2,start:b4,x:b3,y:b2,platform:Windows, +03000000242f00002d00000000000000,JYS Wireless Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000242f00008a00000000000000,JYS Wireless Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows, +03000000790000000200000000000000,King PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d040000d2ca000000000000,Logitech Cordless Precision,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows, +030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d0400001ac2000000000000,Logitech Precision Gamepad,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000006d0400000ac2000000000000,Logitech WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Windows, +03000000380700006652000000000000,Mad Catz C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000380700005032000000000000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700005082000000000000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008433000000000000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008483000000000000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000380700006252000000000000,Mad Catz Micro C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008532000000000000,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows, +03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows, +03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b0,leftshoulder:b4,leftstick:b0,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000242f00007300000000000000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows, +0300000079000000d218000000000000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000d620000010a7000000000000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,platform:Windows, +0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000790000002418000000000000,Mega Drive,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b2,start:b9,x:b3,y:b4,platform:Windows, +03000000380700006382000000000000,MLG GamePad PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000c62400002a89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c62400002b89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c62400001a89000000000000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c62400001b89000000000000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000efbe0000edfe000000000000,Monect Virtual Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +030000006b140000010c000000000000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Windows, +03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows, +030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Windows, +03000000550900001472000000000000,NVIDIA Controller v01.04,a:b11,b:b10,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b5,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b4,righttrigger:a5,rightx:a3,righty:a6,start:b3,x:b9,y:b8,platform:Windows, +030000004b120000014d000000000000,NYKO AIRFLO,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,rightshoulder:b5,rightstick:a2,righttrigger:b7,start:b9,x:b2,y:b3,platform:Windows, +03000000782300000a10000000000000,Onlive Wireless Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,platform:Windows, +03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows, +03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000901000000000000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +030000004c050000da0c000000000000,PlayStation Classic Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d62000009557000000000000,Pro Elite PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d62000009f31000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d6200000c757000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000632500002306000000000000,PS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows, +03000000e30500009605000000000000,PS to USB convert cable,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000100800000100000000000000,PS1 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +030000008f0e00007530000000000000,PS1 Controller,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000100800000300000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000250900008888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000666600006706000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Windows, +030000006b1400000303000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000009d0d00001330000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000250900000500000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows, +030000004c0500006802000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b10,lefttrigger:a3~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:a4~,rightx:a2,righty:a5,start:b8,x:b3,y:b0,platform:Windows, +03000000632500007505000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows, +030000008f0e00001431000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000003807000056a8000000000000,PS3 RF pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000100000008200000000000000,PS360+ v1.66,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004c050000e60c000000000000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,platform:Windows, +03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows, +03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows, +03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,platform:Windows, +03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000321500000204000000000000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000104000000000000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000507000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000321500000707000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000321500000011000000000000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000009000000000000,Razer Serval,+lefty:+a2,-lefty:-a1,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,leftx:a0,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000bd12000013d0000000000000,Retrolink USB SEGA Saturn Classic,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,lefttrigger:b6,rightshoulder:b2,righttrigger:b7,start:b8,x:b3,y:b4,platform:Windows, +0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows, +0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows, +030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006b140000020d000000000000,Revolution Pro Controller 2(1/2),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006b140000130d000000000000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000a306000023f6000000000000,Saitek Cyborg V.1 Game pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001201000000000000,Saitek Dual Analog Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000a30600000701000000000000,Saitek P220,a:b2,b:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,x:b0,y:b1,platform:Windows, +03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b0,y:b1,platform:Windows, +03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001001000000000000,Saitek P480 Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Windows, +03000000a30600002106000000000000,Saitek PS1000,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000a306000020f6000000000000,Saitek PS2700,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001101000000000000,Saitek Rumble Pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000730700000401000000000000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Windows, +0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows, +030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows, +030000005e0400008e02000000007801,ShanWan PS3/PC Wired GamePad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows, +03000000341a00000908000000000000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000008f0e00000800000000000000,SpeedLink Strike FX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000c01100000591000000000000,Speedlink Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000d11800000094000000000000,Stadia Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b11,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows, +03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000381000001214000000000000,SteelSeries Free,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows, +03000000110100003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,platform:Windows, +03000000790000001c18000000000000,STK-7024X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,platform:Windows, +03000000d620000011a7000000000000,Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000457500002211000000000000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000004f04000007d0000000000000,T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000004f0400000ab1000000000000,T.16000M,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b10,x:b2,y:b3,platform:Windows, +03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +030000004f04000015b3000000000000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows, +030000004f04000023b3000000000000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004f0400000ed0000000000000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows, +030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows, +03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000d62000006000000000000000,Tournament PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000005f140000c501000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000b80500000210000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000004f04000087b6000000000000,TWCS Throttle,dpdown:b8,dpleft:b9,dpright:b7,dpup:b6,leftstick:b5,lefttrigger:-a5,leftx:a0,lefty:a1,righttrigger:+a5,platform:Windows, +03000000d90400000200000000000000,TwinShock PS2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +030000006e0500001320000000000000,U4113,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000300f00000701000000000000,USB 4-Axis 12-Button Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000341a00002308000000000000,USB gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000005509000000b4000000000000,USB gamepad,a:b10,b:b11,back:b5,dpdown:b1,dpleft:b2,dpright:b3,dpup:b0,guide:b14,leftshoulder:b8,leftstick:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b7,righttrigger:a5,rightx:a2,righty:a3,start:b4,x:b12,y:b13,platform:Windows, +030000006b1400000203000000000000,USB gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000790000000a00000000000000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c183000000000000,USB gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000ff1100004133000000000000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000632500002305000000000000,USB Vibration Joystick (BM),a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000790000001a18000000000000,Venom,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000302000000000000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000702000000000000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +0300000034120000adbe000000000000,vJoy Device,a:b0,b:b1,back:b15,dpdown:b6,dpleft:b7,dpright:b8,dpup:b5,guide:b16,leftshoulder:b9,leftstick:b13,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b14,righttrigger:b12,rightx:+a3,righty:+a4,start:b4,x:b2,y:b3,platform:Windows, +030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000ff02000000007801,Xbox One Elite Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000130b000000000000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000341a00000608000000000000,Xeox,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000450c00002043000000000000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000ac0500005b05000000000000,Xiaoji Gamesir-G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000786901006e70000000000000,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000790000004f18000000000000,ZD-T Android,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000120c0000101e000000000000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, + +# Mac OS X +030000008f0e00000300000009010000,2In1 USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000650000001000000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +030000003512000012ab000001000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000022000000090000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000190000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000102800000900000000000000,8Bitdo SFC30 GamePad Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001290000001000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000260000001000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000031000001000000,8BitDo Wireless Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001890000001000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a31,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000a00500003232000009010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000c62400001a89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X, +03000000c62400001b89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000d62000002a79000000010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X, +03000000a306000022f6000001030000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000c01100000140000000010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006f0e00000102000000000000,GameStop Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000007d0400000540000001010000,Gravis Eliminator GamePad Pro,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00002d00000000100000,Hori Fighting Commander 3 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005f00000000010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005e00000000010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005f00000000000000,HORI Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005e00000000000000,HORI Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00004d00000000000000,HORI Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00009200000000010000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00006e00000000010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00006600000000010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00006600000000000000,HORIPAD FPS PLUS 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f0000ee00000000010000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,platform:Mac OS X, +03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X, +03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X, +030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Mac OS X, +030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000242f00002d00000007010000,JYS Wireless Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000019c2000005030000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000006d04000018c2000000010000,Logitech RumblePad 2 USB,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700005032000000010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700008433000000010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700008483000000010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000242f00007300000000020000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Mac OS X, +0300000079000000d218000026010000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000d620000010a7000003010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X, +03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X, +03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000005e0400002700000001010000,Microsoft SideWinder Plug & Play Game Pad,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b4,leftx:a0,lefty:a1,righttrigger:b5,x:b2,y:b3,platform:Mac OS X, +03000000d62000007162000001000000,Moga Pro 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X, +03000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c62400002b89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000632500007505000000020000,NEOGEO mini PAD Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b2,y:b3,platform:Mac OS X, +030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000d620000011a7000000020000,Nintendo Switch Core (Plus) Wired Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +030000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000550900001472000025050000,NVIDIA Controller v01.04,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Mac OS X, +030000006f0e00000901000002010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X, +030000004c050000da0c000000010000,Playstation Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000d62000006dca000000010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X, +030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X, +030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000321500000204000000010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000321500000104000000010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000321500000507000001010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000321500000011000000010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000321500000009000000020000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X, +030000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X, +0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006b140000130d000000010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000c6240000fefa000000000000,Rock Candy Gamepad for PS3,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000730700000401000000010000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Mac OS X, +03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X, +03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X, +030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +0300000000f00000f100000000000000,SNES RetroPort,a:b2,b:b3,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,rightshoulder:b7,start:b6,x:b0,y:b1,platform:Mac OS X, +030000004c050000e60c000000010000,Sony DualSense,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000d11800000094000000010000,Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X, +030000005e0400008e02000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Mac OS X, +03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X, +03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X, +03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X, +03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X, +03000000457500002211000000010000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X, +030000004f0400000ed0000000020000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X, +03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X, +030000006f0e00000302000025040000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006f0e00000702000003060000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000791d00000103000009010000,Wii Classic Controller,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X, +050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X, +030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000050b000003090000,Xbox Elite Wireless Controller Series 2,a:b0,b:b1,back:b31,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b53,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X, +030000005e040000e002000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X, +030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000120c0000101e000000010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, + +# Linux +03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00001038000000010000,8Bitdo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00005106000000010000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000310000011010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux, +05000000c82d00008010000000010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux, +03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00002038000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000190000011010000,8Bitdo NES30 Pro 8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000060000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000061000000010000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d000021ab000010010000,8BitDo SFC30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +030000003512000012ab000010010000,8Bitdo SFC30 GamePad,a:b2,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Linux, +05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00001290000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00006228000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000260000011010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +030000005e0400008e02000020010000,8BitDo Wireless Adapter (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c82d00000031000011010000,8BitDo Wireless Adapter (DInput),a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00001890000011010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux, +05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux, +030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00003901000013020000,Afterglow Prismatic Wired Controller 048-007-NA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000007c1800000006000010010000,Alienware Dual Compatible Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +05000000491900000204000021000000,Amazon Fire Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000790000003018000011010000,Arcade Fightstick F300,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux, +05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux, +03000000120c00000500000010010000,AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux, +03000000c62400001b89000011010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000c21100000791000011010000,Be1 GC101 Controller 1.03 mode,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000c31100000791000011010000,Be1 GC101 GAMEPAD 1.03 mode,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e0400008e02000003030000,Be1 GC101 Xbox 360 Controller mode,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux, +03000000ffff0000ffff000000010000,Chinese-made Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000000b0400003365000000010000,Competition Pro,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Linux, +03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux, +03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux, +03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux, +030000004f04000004b3000010010000,Dual Power 2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000008f0e00000800000010010000,Gasia Co. Ltd PS(R) Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000007d0400000540000000010000,Gravis Eliminator GamePad Pro,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +03000000280400000140000000010000,Gravis GamePad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys GamePad ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux, +030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000f0250000c383000010010000,GT VX2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux, +03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000632500002605000010010000,HJD-X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,platform:Linux, +030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f0000c100000011010000,HORI CO. LTD. HORIPAD S,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00008500000010010000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00008600000002010000,Hori Fighting Commander,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000000d0f00009200000011010000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f0000aa00000011010000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000000d0f0000d800000072056800,HORI Real Arcade Pro S,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +030000000d0f00001600000000010000,Hori Real Arcade Pro.EX-SE (Xbox 360),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux, +030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f0000ee00000011010000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Linux, +03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux, +050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000d80400008200000003000000,IMS PCU#0 Gamepad Interface,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,platform:Linux, +03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,platform:Linux, +0500000049190000020400001b010000,Ipega PG-9069 - Bluetooth Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b161,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000632500007505000011010000,Ipega PG-9099 - Bluetooth Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux, +03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000300f00000b01000010010000,Jess Tech GGE909 PC Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux, +050000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux, +030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux, +050000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux, +03000000242f00002d00000011010000,JYS Wireless Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000242f00008a00000011010000,JYS Wireless Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux, +030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d0400001ec2000019200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d0400000ac2000010010000,Logitech Inc. WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Linux, +030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux, +050000004d4f435554452d3035305800,M54-PC,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,platform:Linux, +05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux, +03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000120c00000500000000010000,Manta Dualshock 2,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +03000000790000004318000010010000,Mayflash GameCube Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +03000000242f00007300000011010000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux, +0300000079000000d218000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000d620000010a7000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +0300000025090000e803000001010000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:a5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux, +03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux, +030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000050b000003090000,Microsoft X-Box One Elite 2 pad,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e040000e302000003020000,Microsoft X-Box One Elite pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000dd02000003020000,Microsoft X-Box One pad (Firmware 2015),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +030000005e040000000b000008040000,Microsoft Xbox One Elite 2 pad - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000ea02000008040000,Microsoft Xbox One S pad - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c62400001a53000000010000,Mini PE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux, +05000000d6200000e589000001000000,Moga 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +05000000d62000007162000001000000,Moga Pro 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +03000000c62400002b89000011010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000c62400001a89000000010000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000250900006688000000010000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux, +030000006b140000010c000010010000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux, +060000007e0500000820000000000000,Nintendo Combined Joy-Cons (joycond),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +030000007e0500003703000000016800,Nintendo GameCube Controller,a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,platform:Linux, +03000000790000004618000010010000,Nintendo GameCube Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5~,righty:a2~,start:b9,x:b0,y:b3,platform:Linux, +050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +050000007e0500000920000001800000,Nintendo Switch Pro Controller (joycond),a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +030000007e0500000920000011810000,Nintendo Switch Pro Controller Wired (joycond),a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux, +05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux, +03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000550900001472000011010000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux, +05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux, +03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +19000000010000000100000001010000,odroidgo2_joypad,a:b1,b:b0,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,guide:b10,leftshoulder:b4,leftstick:b12,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b13,righttrigger:b14,start:b15,x:b2,y:b3,platform:Linux, +19000000010000000200000011000000,odroidgo2_joypad_v11,a:b1,b:b0,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b12,leftshoulder:b4,leftstick:b14,lefttrigger:b13,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b16,start:b17,x:b2,y:b3,platform:Linux, +030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux, +05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux, +03000000830500005020000010010000,Padix Co. Ltd. Rockfire PSX/USB Bridge,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b2,y:b3,platform:Linux, +03000000790000001c18000011010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006f0e0000b802000001010000,PDP AFTERGLOW Wired Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e0000b802000013020000,PDP AFTERGLOW Wired Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00008001000011010000,PDP CO. LTD. Faceoff Wired Pro Controller for Nintendo Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00003101000000010000,PDP EA Sports Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e0000c802000012010000,PDP Kingdom Hearts Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00008701000011010000,PDP Rock Candy Wired Controller for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006f0e00000901000011010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e0000a802000023020000,PDP Wired Controller for Xbox One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000006f0e00008501000011010000,PDP Wired Fight Pad Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +05000000491900000204000000000000,PG-9118,x:b76,a:b73,b:b74,y:b77,back:b83,start:b84,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b79,lefttrigger:b81,rightshoulder:b80,righttrigger:b82,leftstick:b86,rightstick:b87,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Linux, +0500000049190000030400001b010000,PG-9099,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000004c050000da0c000011010000,Playstation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c62400003a54000001010000,PowerA 1428124-01,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d62000006dca000011010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000c62400001a58000001010000,PowerA Xbox One Cabled,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000006f0e00001402000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000008f0e00000300000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +050000004c0500006802000000800000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +05000000504c415953544154494f4e00,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +060000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +03000000c01100000140000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000c405000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000cc09000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c050000e60c000011010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux, +030000009b2800003200000001010000,Raphnet Technologies GC/N64 to USB v3.4,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux, +030000009b2800006000000001010000,Raphnet Technologies GC/N64 to USB v3.6,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux, +030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux, +030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000008916000000fd000024010000,Razer Onza Tournament Edition,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000104000011010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000810000011010000,Razer Panthera Evo Arcade Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000321500000011000011010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux, +0300000081170000990a000001010000,Retronic Adapter,a:b0,leftx:a0,lefty:a1,platform:Linux, +0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux, +030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000a30600001005000000010000,Saitek P150,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b2,righttrigger:b5,x:b3,y:b4,platform:Linux, +03000000a30600000701000000010000,Saitek P220,a:b2,b:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,x:b0,y:b1,platform:Linux, +03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b0,y:b1,platform:Linux, +03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux, +03000000300f00001201000010010000,Saitek P380,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux, +03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux, +03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000a306000020f6000011010000,Saitek PS2700 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000d81d00000e00000010010000,Savior,a:b0,b:b1,back:b8,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,start:b9,x:b4,y:b5,platform:Linux, +03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,platform:Linux, +03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000bc2000000055000010010000,ShanWan PS3/PC Wired GamePad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000632500002305000010010000,ShanWan USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux, +030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d11800000094000011010000,Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800000211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux, +03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800004211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux, +03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000381000003014000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000381000003114000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000003b07000004a1000000010000,Suncom SFX Plus for USB,a:b0,b:b2,back:b7,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b5,start:b8,x:b1,y:b3,platform:Linux, +03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux, +0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux, +03000000457500002211000010010000,SZMY-POWER CO. LTD. GAMEPAD,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000008f0e00000d31000010010000,SZMY-POWER CO. LTD. GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000008f0e00001431000010010000,SZMY-POWER CO.,LTD. PS3 gamepad,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux, +030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004f0400000ed0000011010000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000b50700000399000000010000,Thrustmaster Firestorm Digital 2,a:b2,b:b4,back:b11,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b0,righttrigger:b9,start:b1,x:b3,y:b5,platform:Linux, +030000004f04000003b3000010010000,Thrustmaster Firestorm Dual Analog 2,a:b0,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b9,rightx:a2,righty:a3,x:b1,y:b3,platform:Linux, +030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux, +030000004f04000026b3000002040000,Thrustmaster Gamepad GP XID,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c6240000025b000002020000,Thrustmaster GPX Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000004f04000007d0000000010000,Thrustmaster T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000004f04000012b3000010010000,Thrustmaster vibrating gamepad,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux, +030000005e0400008e02000070050000,Torid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c01100000591000011010000,Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux, +03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux, +030000006f0e00000302000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00000702000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +03000000791d00000103000010010000,Wii Classic Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +050000000d0f0000f600000001000000,Wireless HORIPAD Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux, +030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000d102000002010000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000fd02000030110000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000050b000002090000,Xbox One Elite Series 2,a:b0,b:b1,back:b136,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e040000ea02000000000000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e040000ea02000001030000,Xbox One Wireless Controller (Model 1708),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000001050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e0400008e02000000010000,xbox360 Wireless EasySMX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000ac0500005b05000010010000,Xiaoji Gamesir-G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux, +03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux, +xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000c0160000dc27000001010000,OnyxSoft Dual JoyDivision,platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b6,leftshoulder:b4,rightshoulder:b5,dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0, + +# Android +05000000c82d000006500000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a4,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000051060000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000015900000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000065280000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000000220000000900000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000002038000009000000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000000600000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000000610000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000012900000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000062280000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000001600000ffff3f00,8BitDo SN30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000002600000ffff0f00,8BitDo SN30 Pro+,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000002028000009000000ffff3f00,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b21,b:b20,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b24,y:b23,platform:Android, +05000000c82d000018900000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000030320000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +05000000bc20000000550000ffff3f00,GameSir G3w,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +05000000d6020000e5890000dfff3f00,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android, +0500000031366332860c44aadfff0f00,GS Gamepad,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b3,y:b2,platform:Android, +64633436313965656664373634323364,Microsoft X-Box 360 pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android, +7573622067616d657061642020202020,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Android, +050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b17,y:b2,platform:Android, +37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005509000003720000cf7f3f00,NVIDIA Controller v01.01,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005509000010720000ffff3f00,NVIDIA Controller v01.03,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005509000014720000df7f3f00,NVIDIA Controller v01.04,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android, +050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +030000004c050000cc09000000006800,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000004c050000c4050000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +050000004c050000c4050000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000004c050000cc090000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +050000004c050000cc090000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +050000004c050000e60c0000fffe3f00,PS5 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +62653861643333663663383332396665,Razer Kishi,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000003215000005070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000003215000007070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000003215000000090000bf7f3f00,Razer Serval,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android, +32633532643734376632656664383733,Sony DualSense,a:b1,b:b19,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android, +61303162353165316365336436343139,Sony DualSense,a:b1,b:b19,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android, +05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android, +05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android, +050000004f0400000ed00000fffe3f00,ThrustMaster eSwap PRO Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +5477696e20555342204a6f7973746963,Twin USB Joystick,a:b22,b:b21,back:b28,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android, +30306539356238653637313730656134,Wireless HORIPAD Switch Pro Controller,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android, +050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000e00200000ffe3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b17,y:b2,platform:Android, +050000005e040000fd020000ffff3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000130b0000ffff3f00,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +65633038363832353634653836396239,Xbox Series Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e04000091020000ff073f00,Xbox Wireless Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android, +34356136633366613530316338376136,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android, +050000001727000044310000ffff3f00,XiaoMi Game Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android, + +# iOS +05000000ac0500000100000000006d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS, +05000000ac050000010000004f066d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS, +05000000ac05000001000000cf076d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS, +05000000ac05000001000000df076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +05000000ac0500000200000000006d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS, +05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS, +4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS, +4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS, +050000004c050000cc090000df070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +050000004c050000cc090000ff070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +050000004c050000cc090000ff870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,platform:iOS, +05000000ac0500000300000000006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS, +05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS, +05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS, +05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS, +050000005e040000050b0000ff070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +050000005e040000e0020000df070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +050000005e040000e0020000ff070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, diff --git a/gamefiles/models/fonts_j.txd b/gamefiles/models/fonts_j.txd new file mode 100644 index 0000000000000000000000000000000000000000..437e13f96b2fc655b02c048518ee3594c3c89a9c GIT binary patch literal 1052072 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 z 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 z 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 z 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 X0wh2JBtQc9MPP64{P*APH6H&DWV&GK literal 0 HcmV?d00001 diff --git a/gamefiles/models/fonts_p.txd b/gamefiles/models/fonts_p.txd new file mode 100644 index 0000000000000000000000000000000000000000..c05e402ea668edaf0df58b56517da73c2f50f81a GIT binary patch literal 1379752 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

dQNan{A6yN=$e2VM9*q&CkSHkF@zi2}$w;FZA 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�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

m{F3vqW9m0Z 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 literal 0 HcmV?d00001 diff --git a/gamefiles/models/fonts_r.txd b/gamefiles/models/fonts_r.txd new file mode 100644 index 0000000000000000000000000000000000000000..4b89e4492a72ebe4cbf892ea541140c17c8f8da9 GIT binary patch literal 1379752 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�ScLK6 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

A^e==1yV6n~!HQu%#(o?acV(FAvFP^Eh0 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

5Tdh7{5BMz5+0WzJ~ujoqN3dw4C|n9%1|) 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

EBHLMqI7`-Q4FQ#{8|@ADCHVc7eNlSPAc` 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≧`9C< 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 literal 0 HcmV?d00001 diff --git a/gamefiles/models/frontend_nsw.txd b/gamefiles/models/frontend_nsw.txd new file mode 100644 index 0000000000000000000000000000000000000000..1379a1a75039893c832c9b015f35fc88590ae0a2 GIT binary patch literal 2360104 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 literal 0 HcmV?d00001 diff --git a/gamefiles/models/frontend_x360.txd b/gamefiles/models/frontend_x360.txd new file mode 100644 index 0000000000000000000000000000000000000000..a57b8d131ba36f19b14c00f7918a7b291de75ced GIT binary patch literal 590632 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

?fnWcQ#NYQKy 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

)G=M|eIKyT$ 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 Mop^WukS7b}Wega7~l literal 0 HcmV?d00001 diff --git a/gamefiles/models/menu.txd b/gamefiles/models/menu.txd new file mode 100644 index 0000000000000000000000000000000000000000..f617bcf80d98d50da75c36d48150a91b3eb65082 GIT binary patch literal 10244648 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Ñ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

%#kIEDAYXwov_5bbLxBE@5 zNcneJ|HJv8Wbgmiqcj}vt&rKOziVdAQV-I-2fP8reF4s8La!4lg56jRS~>U@I_H5^ zdMH92TE48hZT&jJwx5DVop88cfBsp$xcMp7y<>Y-&b>W5@>ru{IrY>d57L?SV0Tp# z&RE3bGmk$)-^M=8kq71#)QTmt)Me*SQ}1ls>Nn~@{@;G{b#=q?CH7fsnHSRIJ>P*u z@)tG#|NIO8-%$>f*)$9UdFt7|tzMr6f&XQwQWg?9-drKGotN%iI;$lM7pg_{%~PnL zrHd9>>axX))pA-dfEl!i?nPcl^G~RtC9se9S+R7f<##3B!L-A0)FvrauFA-UJ{ zHhj*VYTb$@>c*8zNhTIs>iSj7)H*V6SWRxE!VPvC)=(bSuB0$j79yBIa6?$c zbN#YK7Oh#j&{8K)7;o2&p1y8wRHv3&IR9F;X7K{Go_KW6rrRi;RhF!-TTb~Q{xF{` z6+DaZ2K)iperz}JIhQt&`x2_ddWTkzl_#wI;X7aQcP<$mVXnC3Vud{2uxu%nbBUD( z>I>;}UhH%ud4wDMtRuR9>0&D^;y`%Vv0isWJejt$|@nXd!PYOc*Q6~oz;hTe=eE_-1d}Xr!XX3@3L|Y0QPjCNf!~}-8#I@ z^a_!zFQa$z<9Xtbo9vTyR@D7p!^^0)R1oifpf32weZXI2VcN2j2Y9RMVj^C{E=KxZ zMA)NAHLoxxxq_%4(HbJh_p`}d(D?(eZX&8n5H%^z+)>Y1jRHYx*v%79a-u^|@mdGn_RmrV4(JZ13 zMC&zz-3>&D3-Rk|a2YrcQoWk|V;+P&fG#0Iy&Ow~bp8BJCeO%+OrP_^c3vvyk=t(N}$^LYe^DX_eAKa6=hUgF?^D`VU955U(955U(955U(955U(955U(955U( z955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U( z955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U( z955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U( z955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U( z955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(955U(90BK_!g#~wSE)W^xJ+O}@3NUx6T&_Q)**X||K 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

ZIaVI?Kdia;_{vWgfki}hY#P8l) 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%0D?H!DeKJ3~k@?HZ(NnU(muC0q3)$kM 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�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

S6) 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
vL|d`giq)l{Kw~9J>?lc`l5CiS28f!7JV{z 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 z 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%0Dj8+$7;D_^5gn@Lw%qA7@MEC?`x_2 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 zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? zSb4DWpmV^=gU$`xKUjIN@}P6T%7e}g+do)&u=1dDz{-Qp4ck9hd9d=JbHK`j&JEi? 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η{yf zsr&)ie)zN7oxh^e`~j3N3ji14GjWKXN6v5e<@X-=(+39HSH|qb`EPb)vlGPIQuY!2 zmY(0=jDX1upMMxb;G3R$>Z!4_X3t}p(A^M|91Soggz+g5Ad(|{R74J z0P+n9`@`3#S*#z^!w-XPmf{JQtsdZA#vgukAJaY_%{86HdWmB%zG!>6luzcVr`&u< z|M_Bj$m)N>U*J3RyD44y{msWvm@dYD*l(i0?(@GCePF!facNIXAODm70Qe84_6Oj3 ze>5un6Uq31*?xcIuK{m+n7q@*Knl+=m-tWjqpt;uQ$3AfV%UjNk2mzRG{{*MG_9 zm%@*7kNft3886sk^)2BK+`p6h2WUL=uM>E&Aw&9(+Z*L)g$8sqt)Jukr{u4O`nLU{ znZ_52Z*M($()JfA|JlbMfBXrz$H)Jr=kI92QLdlXpGEg`#`^z0KmGUsil3X>AFw^)4`|C#1}^vh{Q67LG=7iwUB}Pcmb>Qw z>T7f~;^PwW@EbB)1-@ZDfF_!Y#^?L*&!6v3^Y@P7h4A^4z7K={WA%u?Uk8T?olh1y z&5y%;TboY@w~h+e7q4#qq=pCcSC*Nv0!v;Aof4FYfDKpQiKwgQ%~*_tDIn0cRf=|G^1sFGTSML6z-a zn|#Zihxq>fdM%~#=9Pnh|8;yl#czN7$)}$(?YGy@|3mi4RP+DIzn&cbFVFwK6ZzNB z-Mx?71ONZNCaeg>^mP0GK7Z(W>csn?@$(7d1FZjv?*I9y#A`o3fYzs<+I|kpaJwW&maZdlyweSDo_k-cv_TsxY_R6*J5A#7&@_T=Q^P6om zw}1ZmdM{Wd(Ddo@S#-F1&38OYhQm-114K`^onuEwYb62%V{XujgmWyBPP zG+&q;m{dP@T%z&TO}krTeMpd2QE6(&^e7m#r&>E&UUViK~Ii5@HJ9^ZOulViRf%vX= z6uzF~2f$C;J{nKJF+taM!(ZRZ=M#Ql{6ApY#ACbjDgGbzt>xo1UUKAEmpwm={{K|# z|7LR=npUH@>!bEr|4HmWx9uxMw?Q6cd_eR*roMi zzLR`$*TSK&uTbBc3N~$Z>lNGYw z(pYRCKpUfj9S-Mm?_rRz9{$Uq^^el;<_ppJ+5S|G)W^2I=xDJ&f{)YKC7nXqVyOR) z+{c&3av|c8FdP|=XI=_(mnlBFVRx&Dk0$$I>quOE7|lNq^A;cC@mky8^zp!_>HLhq z4`e$@_$z2VQ}URR6wj zyYu%q-}dwmMSCZ@9+%tsZip|1e7N~;2H%%je5ZIwIo?9{mCScHDSpQwD6Y1{}ONJv+x;;9|(v)Y0NJ)>g@OxzRb|>JGeo* zN7ncHK=q2~ez>8j?-!H$BmDcZeL(R^WFK5M_Q7!EPpDecPUllj#Cpt6+}i5KZwWur z+eWOur}%rox9?B*5#P_p6QzUunK!hZZ+^u7d5iB@AIHOY#N&(k?6uv`A56}7n;yQ` z!#)5T_8OjCxkCB2@wf4ZAl{DR|6zYz49CX0yD#U%Wd(}Y-_wvAG*%(y^S+1)bLRRm zB!9om$0NTNI>*w~`>%OxQ3^ZKX_p`0(C)m6>4xj}2hjD3|Bjzvhn5A*ffU-V_&@mz zB>qr-ffZj)o&#k5EO^H4BVF|BtHpE*yVB`jP(1P2tM#!xk#1-h`^|@_ zkMMEhe+aFAIUcVt&JmnM>&YR1iuE^RJz%V#;{5mi`hPomzm`}Z@CA6o<^9iKzJ$D< z=1)l4KmWVqQG0QUDLZn>`pc#L`ld^t?xOkZ={jy-6fc_YvHYg-{DC#@ebsiq;Nkn! z^Vz-rI^toY`LJ}q2(drPA5MY4d&aO9I3f0q9Q<2HH>IzfyBv*QA-@)Gj`?{x)5149 zAc1he55{l!H1coBcsk79n=t=v{Cqw1J>Yp?is-TQe;w1?K=-`*kNA7~_P2jijKAl{ z$H#G5R7&~3Q#h-f@c=qs@!#RU`wNUgQQGi<|5tu6C(Ks)F*E#FalvFmPN6!o7ifgE zF(3RP(a@LUMt@#&{UfuZh-Ui$^QDIA6t)jS%zbzd0S7h5q2GgLYt7}Cu>SIC>d##@ zG{qm_iaHlRK;!MEvuDnz#eAyWXcM2G?Z4-M%+E&lL#H@i+x=z-DQ`ZX>R;13dHw$V zZZ(?t1NK;!d-y&1_7QurV6ulR4+ z5tOojrh8oVdCcf|qx?{QNDj=lJfv_l!hgOe(WL*7`2djtasLPo9@4+3!r&AgclLp6 zU6Fo{;~Qu-#dciqW10MimnsnpT&-7Jq7B)Rf+YLXgy{Zk6tbw>G|vbjNb)q zr^}B7z7LPZ{&&QmA{LeAv)A_`z7+ET2wUA&&@>s`z6qEM7JeBl3;AIu4} zResDYKT@b}BK+sO&~bl!hxF@z3Rj$y=)&eyiDU2QK56F6w$+G&uvee z^_6n@;djY{(3X2EfGSpRgCslK%s) zamxSnS`k|FTMQoi$MhM z2CqN;)Xt;f^LhJBjQ>oh@CW7dk+k2RSNwNca;EWmrt5PpHE&dDmTct*aX{r^cI6?3 z>Td-9xm9$%??3$o3Va2h2J84Nga4n0>%O4#)9G#73_Q5ETSO0=J>*SZ3;zMx0!jM- z*;(_$x%ytpuu>Jz7qsg!#-qj4@BhxVeL(q#Vc*brIh7cX!-yWs4Gz_IzmJS|K{P)! zmm3#4NmQZ-256J1aA-=?M< zUzh&)KMxnz8hnSr_>*eWBgf@M@*&5+={VqD@gIDklEOb}Z__?mW*UFzyFRA@%u^~Y z-?rkP%ERo+LkiW;2L2~o4eGGjlfs7jYhn0yA?Pss=zqeRLf9ekgmz6%4@ZZ?_rH;} z4}Pt%uBP+Y0PyPyR(lE0?&zfmaI;=tAsould^F%|oc~}Dct`n|Zrqoct{DKW|q z<%i^eFApgktO)#uul!(6n62`om>=t~>+WY9f71AKXlRb!kQ`8XklaxHpz@&dAUUA&Ai1IXLFGZ^L2^Ll zL2^U&gUW--gXDn9gXD(l2bBkv2gw1I2gwc94=N8T50V2a50V?IA5Ianvl?TZIl?TZU)ekBUDi4waDi4wysvlGyR30P;R30QZR6nRZs60px zs60q+sD4m+P$~v9#kGA2UH#;H&j2UJg7WK4yZgxZm51x zc~E(f98h_X+)(|X@}Tk{IiT_&xuN<&!kQ`8X zklaxHpz@&dAUUA&Ai1IXLFGZ^L2^LlL2^U&gUW--gXDn9gXD(l2bBkv2gw1I2gwc9 z4=N8T50V2a50V?IA5IapFnURO;xSBAm`n8q#``-k& z1@!kK{Jkaqy9R$(eK`pJ;F(>Iy!dbK|GoH<@;A5{)f-d=RVxt{)TBFu zkav`#-&hd@o1S`V6Q4inwlOfj_=GSne13S&{4(RIns$^zXntq<+j5&w`24n9ZTDsr zrvK{G^?$~H)7GbJjz5>#g~I1-&unNKMPa(;_=cu4C`@n9tZMo?3ey`epW2f}VfwGb zk-y--X4j=M%p(D11U&Zt=b)C`^~)U#a87 zU-^EI>{-?HIttgH?XT}XjKXxO`uyW|F@>ARW?E30of0z%4ZMmA`)%X2&o_A3DJ39~Eu>AV` zm}~k>|Bl`s6n>r^y&IagpfG(Ue<;O&)Asz{RZVxI@IHF~@_X~!-uaREv;OP}%#ZN+b`;*P_{^LzTjj^p z{P@)!uxZojG@o@nbb~mbuPfT#B=}F?JJbVgGgC^_Q{zAIG4pw6rv*^LOXwf2KD+@s z<6Td*#S6M0=uy5wOfyOM%e>{?U#P>VJ)_CvnV!zh9^fulz| zn4T$o;`-Wh7rOB6TYIG~_t?s@nzdOD?^E3+I z&(s&zF-_kCxNpDrY;FVWb;W(SFL@7+5Sf!96{mw&hNeL zX)DawS&An4e0b%`m3(~`{=Yx{MMDGAJ9<|&ZMo*Y53&c+&{p#JfS}3V>F99Zv;TZ; zxpy8v1m(rox8?S&$LA;Lj>nVqD;=xFalgLVqP71Irbm1?TmIi6mD!^-bLPhQaTf*| z=v&}gd^&{#Ehv2ay0;aA|HeP<@aehCa~XOD{{D;mudCos@7&7A3C}=VJSa^^*Pr0) z{rU;t??d6^+5Vlq1r(;s)hBel^q200eIfm&wcQ(<)}nCzG``Y+!gRU!>&owM-h;x| z`~01M{I5CQ(ec?gxqUQ#6nyUNF`_TRfyKvbyT5e&qQK9Y#&a4!K=lvt{n~mjyp6wq z$#i_&`fEzvzqb3H1M6Cy{O-&>NA{i=?@Eprd^orvlReUIpO?l9mi&u<_h0z_BoA=s z-LO1l`>P(1_)qU&`rm!|_vwpo?_2*c3b$vv-$S|bT&d?P{u`um$fPuJV5a%uKFcF#sBi+DSZ9xkN@v)Ue$CEh523E{n%?&C`^~a)0sXV z<9iR^pX`nHHOp*!sDHYyrcU_BYrE5%HaGJ5(RhOxzp3x7IUa^R5A$)VZ}pNYE05%_ zI*AXGkMHP(|ESFPfo02=S$X&QU5cJ-{Ex;nIy4;*m#Q4B$h_t&7pMwXx2?jz0cFI#`}#>?b??y)rW-#a^x-tc_Ha`C>@`Q$H`_5k&# z)9Foie%%L%-@B6^4%?`|UA1I6@AB&}#(#=`>9YO~^1q??Jt#_x|x4$N#Y_Ke>D^+?h)uV(Ran2*WEVVYsuG^gE3=%q)%me*x8p+1xtJ z|J!EeiP1#o{~1oGeZdlOWBiy1|8FSXFWC%e$hn#SZ^ldI{QgI$^#4zU|8o9+TkidD zRHLxIp?GA(M_K=2DLOO$!CpE4&BXt^_`_`OZZG~XipR6_iA&Y*A3rnLmKzut=;8KK zf95*k|E}`+eto5AAOFeUD&xIK-^=IE0&iy0zdG0be}ng}7Vo3{5^y*(|LFZne>2g0 zgufZ{J({W${`E?;2M{mQc(RqBXMZ#1qvrG1m?n8zy>?~0qmf_W=+Q$=FFpUtLHWGJ z@$`Oty-&Y#upPRC>%(|R?x^5jrQm;@t|Y8G|I7-RkA&48w|}l^!v8hFQt(aZ|1-~N ztM-88MrnSeh-YFxKhZ6M26I;pGY{J+zEV6NJ#R+jTWZUlUr_i@@%4x&Yvf0q2=DQ@qkMcP z@G(8G*7lDnzf6b#JU)H|@rf-=U`zAaq&?7+xS4ge?8kY&9`q6`F1d0B-60T z%^zKSVAa!izW(|!KLX7kKyz<6Ua;KfA^-ozjidGx?8=kBBAhYW`2SrzdG*D{&`E_k7?6yJ^v47ef+oLKXbxt75~fQNAm0$q$&jeeShD7LjDx!vN6_Y zgkMshQ+^d3v;F^=#`kN=b<(YTzVClLwFh{SPTPE9r}jL%9{0EPS314;K-7La|IdFu z!hdOx%{2ZKe#(4kbidz3VSbkz|9?IGdnq4Leukd=mht_To`3#e7AG)`{_o=>t)%Zw zX`}gh2C*6LV1TAndrRT}mw^5Bo9m+ZpX9jWe|h}aelPO>z@Fx-DF0tN zojJ+$4R0$1|H(g%?wakdz0$F&>GEZ7{9ok%ZNq#&O#id}8=4PD%whddnm)f5)Bo1- z`J_Lkf1LUoNdIH;zbjvJ+;+xs5&2VPymF<{ln-f7V`HOxK8){*`Hd02&0zoEj7JQ# z^ZjT%dh?boHa|4kW3Rt{=be1Kw);ZY;lmv^{?4D@SB&?VFXH+0fd^Jze7tYVbv#}r z5nJ{d{h*_ zm=nqm;=pXn!(Qa`56Ga73(+S>Gkci6;ceyMf1*En@$H*y{-3W;XPb`j%)@_a-^?`r z6W#!hGmH-}Q2h5MH~#)+N5|tmB7Y3!(>aU*YCgZTKOgyOApd-RG2ZW6Uk={;{Kx#i z-d21>uKrVdO4$Q2SZ#YKULW!JE8X?i*I(uH{r32DDf~a>SZD^ta zM82O-(|A~b^*#9bnJAvd<)0$_e+h+;Kla))cO>Hb!XU-RX!{SQN&EglM;TK{J^;5i|6aC#L(`fQ0~XIrewo?J50lLufq%fo8&G^Z%oqE7ufV>M`SHjf z{zgZ~8{B>$&&#Dt;lGcUQG8(7VB2SC7a!}>+5Wp9kohSI|7HG3dY+{y{5(GXqrOZS zChDIqjqyEKpW;7j{>s3k`NqRr?f!)KNS9T9e!ryH6jK%2|DSmNzsvtO+xWlu!292j z@qeeg_C0QuDU@KP(=`4+BKm(5#s5iri^fx?!hh2rOpmkvkJdjN-DB~7@qu5~%6RZs z4t6{)^WoKZBfhYDuN{xT{EjL8`8z2;o*2K+(s~OrUtaU7r=>kmkNKhq-=8sifb1t3 zUznxwj7_fpO#Tz;?;w0?U%lMMqhS34C34h0jgQvS11ikU{;sbI#Y^Le9-07|*qJ2Jpn7%&$ ze+^b^mF3Gm9WBEu&mg=}|G(qNt%kqz{k)FzGyGR&}M^^2uH|G@Z>J)^h**GG7Za6S8c6hD&b?0SojmunA@JWYxBG~X@^ zTe$vg|Ei{e6KiaIA+4V$+sk;w=TDdR*N>p*_v8OEFzEPxv|hE8=Ti5l`I^uyhxmH> zJ_NhNa_P3*ZB(ClU(==Er1cA}d`$K{rSP9}0Z?HrT12-*hwbsIU|Z0KpW;7r%xoP8 zl_n0%Ha`rPXW{<}!GGU>8N~zs2dD3S{38G5SFATa3@N_MDjR&lC27p}^3SJy3g+vl z_$9J0+<0T;?-m!7qG`R=t0-I_jdzEl|K-zX`cHRB|EsCr#aETOpMSj6{Qv#le9OmP z+mLxj_zMW1vHr1rVjr*H?7;eN>47!w`L<`)E_d^@YP%7yw|SF&L;m%^FW{%d`y+eG z!Oynb?fY7z@6NO!3;b%>x?9=HSLi*opS~_z(F15#NvI_sjJaO5LCAt-nLz>$l_kvM(Py z=bXlPPb(Y>+#a%brt(iw{08i|fbjn(h2hIPbI+`J;)y3>d*;d&%I*yDj_WI;FUQA6 z`F^Zv`-0)Ba4CM;|7Vs&+u6oDtv|u;*&`|h|Kssp`~5}nU19hUe~&SKSeo(!;Cr_E z_zcD`ac=y5hhh12RpRTUC-z@a=Q%w8u?LO_}f8>8ffE8a~ z>i+(5AO9o&zli@Q{72m_T>phGnlCtD`&T=9X+E6G4;S^%dmO$~fA`oM&R=|{AN})8 zb0gPB@9U_zALUcP@;E(wye)V41Lfeo|9n0j%|EY8;zfg4uP2rtYY!aRvv0lZ;QICn z?17B5Pv#o${pa)XpX?DI|9|w*LvH;-ydRk#%dgL;ef;<-1vi!XVkvYAG!X1)V``}YY&xs|Cv5N z9?!BOO7Z$~e#HlSS2Ya}zkVn4yDfLIYtf?WGVIr6|KI1&T+`FV|Lf*|jQ?%9!>N-f z%s(N?Buyt}ir^Us+sHUE$No9O?y@%fRy`%32znWp@w%a^aQ`FY~{t-m~`?RcnJ zpWVgt)%Tw6Kzl4cl~2=p@1Y&vpRT=e6X1Vh{Db&?4aL2$?U~&pZoJ^a+f&;Els~rX z*ogtYK8kM$3!gC!yidgE10NK0j+9hU*!pe{Q~TuAjempGG#XU;yK1 z954GF^${NbP>g4n!vAdl{^sv}?|ZgCL-A?w zC)@FK>c6dRclxll`vb&tx&GM>toJVCrKo?}RmI#R|9*14_j2hoSTAlX3O}FXKlT4R z?fW+P@8TEf{nrURh5gpF>!j1?7d-!fKVEPkvIoY+_{&87d*{EW`~WgOzts3W;cY8~ zp5JE_?;z~~Z@!^#4`9CAK0Kbae@o#J%@4%wTHXCc2(q^;1NDBkv5coyqx9=-$cZ}k!3E#TkkH_}f>e|XK(KK@g@elp)j)dR~=oPOMp z*|cTzZl;YsbnBHy{(#l(w!et}d{V=r{?8x(pT7UUwj0Ck9zVKbc@0C~J|g~j_CRLW$yUohf4qR& zE9WB^d2#WJS@aKq_qIPy_LL>IMID52Mevof3 zf%lI*cvVcOB>N|c_m3jKfQv^Z{vASq2DgW3!2c-In9n~Y{?qq~@1c&)AG>mH2=n!x zhN6qc7p`0x0{zQc&{1_aH4XePTpG%^<*M$*@PAA6!1#|=4~Bkg(D8qRgY$)d=I*zy zeZu|iGyNMf6n|D}G^;FbAAdjSe?{$`jHdpTjHj#Zes+DVZlX)F7{9F&@peSF3fe#4 zr?X!De*30NmwLYG9-nUeD1Uw99@l>aequhj!;9(SKlNWb(c$O!OXEG5?q~a}KHit2 zDLzEn153|uXd(dS`Y%S~1qEv#0B-RL5GoJwXmjnI@yo~Vh0N>BI7bri@ zItE0X)Q0hBf4l(c4sHBEu|0tAA^6!JasBn(Un|D@IyZlhC%L6NS|Qs0iq8H8u=F9 zu3C1VeO@2GeVXi**2c|teIlaQi18$%gP_{xHz>Y7X%A5T+(dptnol6wx4-#UPrf1O zsDEE4{rSasPXQ{|%g-Cd|JeNvdj_3<6uuv8H=@UKU%C6yX#KH#G&k|v_ivM%N5B?r~{k_m=%GVRR>EB^J^24ON*4X|c{I{8O zXJGqJ+1#T0e&t4a65lSw2#(nz*k`TPx=3X;4MK@e%-1Q{C+4tGYkvIm^S$9 z)^{%#|NQgg`5q5*{j@%B`m&2hAp0Pl-fH6&==i{iW7hsGjraceeKKCp$p7g7!uL1% zl)m{a)8>10@q)FGcZ9F_{Ic;3{`2`X@ozTp-=IHb-cOcdd*GdfJ@5zLGPXNkwa{qA ze|~djJ6rh1chUNp6G?uPzR2aSTzM7oIOy(1`e7oP;&tTl*{16e?=SCH-;4bCm?A*! z&EwZb{rArUJAO&=)#5b63XP4Ro_^tMBEJr(L7A^v8>P-MT5z2oBRV*BEc z`M7U?+-!7=7ZxA<=kt$GoA%<}{r+pr|E!oj%nNJ((ee9)y@C9}nPtMht3f<_ zP_-tKAF3nUAH~yKJofRf6m9zN&f$c31&sF}wFWB1zm3*O`v>zw#Q3^7->qj5#jA+^ zeLdDElJR=xeMtYo-4Dv~dy2<@1fMqZhw3YeALw-P17!a$#{?HXAO4lD&ZBP}X4-%L zxyJvbwhDrq%^pzvPrhNr=lS6WfA4(vN~OtrIe z59YHWAK=wieqUz(dfJXRlYId5z`n2g-t8GV9#8h69FLFt_mjpO5FS3%DdXYmyOGY% z%9HQEr}gb&Z=T@xMf2~25i74~|3vfg#CQR%|L5ZK$-a>Bdj9<=fAYFk$DcMD|3boI zzCZB;3mVli{ox?uH?zl%FkNZ*&-#hC3*8=2{O31mwu=Ar!w>r2|NXyr_pSu`i||)= zoR9Sp%@(5kKnDFUq5rnMxq9ET&3v5Ji@|(m+dqu(Bpk4K;Ol?V_e+*6xA}b{ec#~v z|2ukLN#yUL`E$6J#g9_-4#cYsBf$x`5A_`c{0JKPea6EPcO3Zyu)sUh{~wPsWhu4?lJNtTHa?*EpM0H)|MSa_lSc>uElj#Q*wkcB2hu{h$B< z#O;gQJMQRu%r6V<_&D$v_JpfpV%m*g`|X`+I-9FX^#6Ey|8tA_3g-&L`LSy?$5S*;^~hf-{JBP7 zx&B@)#`||QI{)Y5eeZt*YSyY;>d#?07K^9#-H&FF?)QYlQ<|?W^Q}?5my0L&^{wxp zHvRdfCtZDn|1QGIw_nPo>3RQL*bhW6MM68iAK627Okhj#@i^GSyR!ZD-Px>*U-zG9 zrfJL{$N+vU{*XN;`~NiFKP&68#1CBRdFXv5>;v@2{|JEY_*L88Q04fWMLzsA96Wsd z4Eznp#QPw7L;Cwm-G8Q!$NnTM0@?ng{pI6--Q{;4m-+q8_ax&T78d@q{^0F=+XIUK z{3gs+@qd2#k-Yo#sT8V6r^oYLlcC*Po{ZiVHsY81!W`eWjrA$lGe#4Nu_!=&@A?(D zW5AM+6TV6RzK@@^-51|p3Wd(+)BG`{UvFjlvDbQ^#NXB)Abt4NQFlJYXJI(l>VN7V zV)j9j7W30^9Px$jIQ1VTP5I|gyS<+L8G-Abhh~18JpNqb`X$BJBfp`2ys+;4_Dr&V znom|Yz|TwZVRdy&PcUub(;8Zso@xB?>)Q$b$n_iz{y2UU{}7*H?K}T^rAPjP-xY03 z@q^a;Lwv~?UsK=vXl4r*_+|P$*7t2`3GZZ@J@Kgqk5Po;1Px<>4UH5RzAhX#zgG^X zYT8lEdVz(I@c;9_|Kf8#-^YKSCjWX5pNuWV18f;_{#Y6>!2InIJ|)U86z1*vKomb! zaBvmnKR~=5pMD)Q;@w66knj(*Z9mmNb$!wK;(im-wn0>XkGS6lh=;h(UQPbs?Zx&@ zuuRwoJA1Jl;otHlrSPWI@ht48B>r#6$n}(VB47M+6uzJDPbpRZt$rN*O055b_uDMj zPoVV$tg#CwQ@mq47Wid;6W$~KZhc$ z0BGuu&m>L$0HiN~9GFrr?YAe&f6(CK32A;j_-XAMvJWtP^1t%^i2g0%|H$?~m#`1W zKacs7!ampm`=IbkzCOkuix)l~Q+z(>tDv|cc+ z)29_Q0P!u)YT7^ct>@@)P1Q z3rv@qZ=m?koHE?d;_>|Q zBYF3Eq+33E>d#8U z=Spi2<%3(dRN}$@<^}}FGroKY<0bGPSp13c#qyWp;eQwS!RM2IdD*zbBf_hb-*R+> z|9C-sKIM0m@t6nlh?jst$;aoKCVRwrx2gYI9-Z~_OP8WieC!-~Zv4Fzq4Oi~d5o;i;$iJHFYr?_j?2N?{-T$pd%QyaD@| zpEJh)f5kN6AM%^5lr;ZMTg6Ao4dus7%R`d8L1d2)jE|4={=29@k22Tv=g0;0gK%Bs z|8Hn0jt6A@M(b(6G>}e@0shC%g=5<@si1nv^0jol0E>Y7J4@EAg?(TO^?y5$9y-jl zum6-@|7Xk_+7|l{?m5s1Vc>J>$zQP5#q(o+9L;}wgOAhvzAzlH`S4?Vp@)x8lK*#< zkJETH{4x_G(EEGisEgmH zd5l0fB`6r|NY;&W1{~5_XG3mdOGP3T#fa7FkNOf>xq9<) z=|>TL4|5=Wu7DW>kv)Jjs+~O$D z{(ou@y>zY*>rdQ|@Q4LW`_F@XL}Gp1cznKvpO@x?{J6T><`=B(ezSvC&Eexw{3{dK zn)ma09q%&ndp&jGmEpm@{BS-RpC8$^%Z%QK;r9xu8uG9B^tt>{6edX7B;~o{xc`cR`Gsr`H{R^;FF-y_$R)JV)WZ5 zCGCOdoIQ|33}N_O1|EZI;}5tL4n_Wf>gCQJfWH7&xb=mMeGtCO`ZUHz8zJc9<4n^# zXnteQ@BcR6pVps|@qx2K7p`9A`}xm9&yRF5)_)BAZ<6>=<1KPLzxeu7d)BR6carOm z{Qp>ihUs_n8!{(Qn5OamLctymDW9Dfj$d#zJx@ITbMr5{LYhdy}dL2 z=l9Bd$g%t_A+qL3;SUVw zTe|jCb{QqwLg@bt%KJH&%-+Q`a_TDxv%gwzGRv(wKjW50Lpq{7k3Y*D@W=$FFkzeSbX&!uN5Je~9K^;B?C} zmKTTr6OZSQ<@)aK3HkyWzaC${x_xNqpZy+vJFD(`rX~%4&e;S1l*r&VjPH}bA|mv^ zX>&mFKgmMH|GDKy3bz}Fw&uIe*LeSR-v55vzIX1=|0QDn_&U~?lhR}1xv{@Fm#6ti z18bL84~F^o!d&0AZ2#R4d_Horv9EBpa0Qi|?h1lEds?hMp0vInQ}iLtpH9_KyxO%e zR~Wt)UaIefeNeUJc5V;NkFKey6aLsg>A>JNnIzMb%XnfBfg6{%V~Vu)zGr^;bt)?fIuW z_C1cetbbwgfx{`BZjaX-e|Eh*Uf=!5gOYyiwRb3=lRe+3cl6Tw(60V+X}`X5X}`Tb zP3u{%Uhd}O%~bw_QuyBo?5Ce!P9r;27sktYKgIWvzk+GqPLBhM|4IHiBr^Mzo?ZSV zYp-CcF?b^S#Z80`j|#WAh$g%r{E5W-KI9L)lX#g=rrjeVY6Sv)liFwYLs{%9tRZvll)V>pId$;?^eN-LH{SXg^W*@9t(d$+p%6?KK!Wf-Q0(R zAMAajHuzfKwOmL}_iTTvrg8Hot6%N)rb1duFe)I&a*yA&h~kyf>GP+?3SYlk^$Ert z-0|oNrlb7CNP6<&;JTV*{u<U^zqQAXCME4|9@>a_3yZSG+rU# z3Hg?=J`o?pmev!n$qPaI?Jq?W{>$-h!v95B0Dx~XQ}}nQ*LQvR-^1dp+d1NZ;(wCS zivM%VkL2Adm@@d^$Sq`jJ{f(Ez#fJ0c&F%3dFQ`w>EC>7(bnL|5ELXk53v886UU)j^g?v{QqO- zC*l80uKz>hFK|bUn)dU?o9C6D{{O^+^a-`3BQ_bb)DDUKJS`R4Zd3bdZFKY!uv3<2K~Rn_$O%FzvDVW%ncx&O3`w+j}x9;iRg=O;|z>P-_mqT zI(-TAC+OFfJJKA8@%VPj&H;0NE~YttV&>?vNE&k(4{OVv zzyliT%;h#-Zp%SDZ+`k5MhAy;xy*Q5uBW~IC6))uR*iH-n-u@em*6B?Y2v`m&JXjJ z7w-QlG@bCDpMYq>f2JA#nWp1Y;XnENZ_np#d=t?Sf^~K?Z545q_bIM_%Sr}UFc(nw?jkUO3-=Je@4Ps=3f#&r|_?r!vDVT zt*|HTN#TW%eehKL##vrC!p+i;nm0%1hhfXMYvJe7&+Ns%sP|tQ|7koTXbpnCL36i>>>u-Ac1%6Vqy&CR9w&o`$osT&j``f9%RSkX1{&w<@ zNdG(2m%{hpPd z@t>cD@t)}v{vf=UG~tCo|Igj?8aV$6`Tt%2zYw(#?ti;pLNp$bPOpv4g>d8LXI9Ak zNqirJR(+jxf7~u_=uV~$iFGLB<4Q*yD4QQYxBUCvaLu{fe>?IYrpI0XAv%t_@fxC% z!vAFa8_M__r$bXsBrjZ_tJ&;PnmMEVAP&s5JS4fh@Te4;JRkVaoTB>^{xf~6ZRz<5 zpPmR;T*C;-|7_@&S|W9fj7jlvLt>4XBz?*1(f9;QpUYu2hvBe~@-bBZxV^nFh5>B+ zD1NW!t6%+UE_dlYS_AqOjJE{WHf_q~OpiIDqw$uZsK-a+F*N?d0f>~X=_vrXL4Vu- zxBeb#AKH?jw{0t4AFc@h`^*z89ZJ=-gCV)Xl_(xGn+t-P`>;OFStOgh99}-t|I6A# z$cG;f7h$|86+E$x{YmcYesRUN;6?nXJ^6uj`paL&A1EpO!=3U>v%R7;amS7KmCKK4 za1ht0@NX*rA@dujMP<4)y>Z2V=H|@foBN{7J(PC5z8U^X;UN_N=kHAt|08`#<0L34 z{A)q^y(oK})5ku2IDDcId=UJUuA$=NKaJMX?=aR3L$^fuzxBP_Q@D3}z-)4*p#x&ObwY82<`8REh>uyi~h zUFde#QU!dtbzV*@E@C~`1&%1y@0xH!MfUFdeyP8Sp6dIh<5R`|;c!_c zg|lyU|G(rn@k8Q2(HK9s_5jf|evVC|5cboLcit%er%(aH`O*gc4gRl$LBRD={`!+A ze|nZ_I$z2U?=QI}?4LpWAFDFIlK1saNP!*~&qp-ufl1?Er;ArN`}M>>DbKV0yo&!| zxG0MMf*JU8w)v5~+rpz#XtLDbL{Iho(s9LqFkyyD3iV*RgvD2)TVPOP6O|PHhx^>p zW^eKTGl~Cc7yyj_bblG2Me{etMSJM@OUQMDO;l3&ABP(ZHO2oGg8zOCQ`n*QfP0_i z;y3YcD*aJzeWlK)mg@LFm>w7R=FASInKQG)k7Vr&k4mA*ivJ7mz4>$Nzte$NwolAR7OV{r@BnfPahsrSz|VoUX_C!%X4-AgwRq#{Ub*5B;~b6OI4h zX#XG0owB5GwAdbS@$LTe-|95+OYt8}h>PMcbHZ$IHhv_ZZsAcWG+FU~;k`G%#S`Fv zw7%l1CgA@-I{j|9$yih0iz`E#5LbwX+&AEO6wjZ)|Frf0r|5K?&-c3?8sP`le@^Ga zAK?23!r1%&_wn(Ts4@5tr$;61Hu<3HX+5ZM%m=u1C2BCuZ*=)1-1)PG|J)wlrtn9x zJ)rRel0S<7%#GRiKf5o$+*4`Cd-`j(`H{Ta!lP1Xvf}@Zzc=C^@NIx=Ftn4pxuOaG z|8sbM-rUcvkHqaN!q<3*Jw6h%=gkNE-|NJliL$#?R3*F($o?WsL5ACmS1 z(H{rD3Z97PJAkI<6vFo%{`>8ln6~l$g#SH@YNPoR>3rV$AL#z5JaPY-rpLYd(Nt^y z+-m*+;$~v9#kGA2UH#;H&j2U zJg7WK4yZgxZm51xc~E(f98h_X+)(|X@}Tk{IiT_&xuN<&!kQ`8XklaxHpz@&dAUUA&Ai1IXLFGZ^L2^LlL2^U&gUW--gXDn9gXD(l z2bBkv2gw1I2gwc94=N8T50V2a50V?IA5Ianvl?TZI zl?TZU)ekBUDi4waDi4wysvlGyR30P;R30QZR6nRZs60pxs60q+sD4m+P$~v9#kGA2UH#;H&j2UJg7WK4yZgxZm51xc~E(f98h_X+)(|X@}Tk{ zIiT_&xuN<&!kQ`8XklaxHpz@&dAUUA&Ai1IX zLFGZ^L2^LlL2^U&gUW--gXDn9gXD(l2bBkv2gw1I2gwc94=N8T50V2a50V?IA5Ianvl?TZIl?TZU)ekBUDi4waDi4wysvlGyR30P;R30QZ zR6nRZs60pxs60q+sD4m+P$~v9#kGA2UH#;H&j2UJg7WK z4yZgxZm51xc~E(f98h_X+)(|X@}Tk{IiT_&xuN<&!kQ`8XklaxHpz@&dAUUA&Ai1IXLFGZ^L2^LlL2^U&gUW--gXDn9gXD(l2bBkv z2gw1I2gwc94=N8T50V2a50V?IA5Ianvl?TZIl?TZU z)ekBUDi4waDi4wysvlGyR30P;R30QZR6nRZs60pxs60q+sD4m+P$~v9#kGA2UH#;H&j2UJg7WK4yZgxZm51xc~E(f98h_X+)(|X@}Tk{IiT_& zxuN<&!kQ`8XklaxHpz@&dAUUA&Ai1IXLFGZ^ zL2^LlL2^U&gUW--gXDn9gXD(l2bBkv2gw1I2gwc94=N8T50V2a50V?IA5Ianvl?TZIl?TZU)ekBUDi4waDi4wysvlGyR30P;R30QZR6nRZ zs60pxs60q+sD4m+PbO~9SoLf2@Bbfp@Xt2@ literal 0 HcmV?d00001 diff --git a/gamefiles/models/nswbtns.txd b/gamefiles/models/nswbtns.txd new file mode 100644 index 0000000000000000000000000000000000000000..0b1756ee347da3be0589cc7c328e52eb52354b84 GIT binary patch literal 528424 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%0DIZM|G1g|qvgLa7r`S< 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%0AS_b1@2=LE(AZvjvBdJ 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

L7> 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%0Aa)h7R4Y(A_=Ax6ih=p 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%0AL_OmtoQ`@Y4 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 z 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�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�qZzsgI{0I{89%vHXO2C>5?~oy9=S>mMvN>?cJ;Q`oe-j zG_zAKSjZ0oCs%m_i%i$D1nQr~G#_D2wUslAbz z|3Ka?nzvj!aKOMS8*>wv?PZz3uK5+=pBjuDF)HZUwMWpWdmoOyf&=>>6rV%aSlp5M z7{vL&cvZ0!)kB94`7^mKK4mNpAK-zDxyEM3ZlO%Or52X!T#DkSw7MyBS?9VfJp+?| zu_^CvhUBNCb*yl zzP zUr`)?Vh+#$mOl~)Nc{+D+}Lqn?A^Zor^rDzQT0J>&g+~jFG}s#uH)C7+pqa;#kXtD zt7P}x_gG#qzQFRnvMv{m!*}w|i~*cem~$Ox_2A&qW5W68INvwrj_}NE4NB#9?>%v%>AMeTTT?&y{bG)&j+Y9zA-jQM@JPwlY)p zOE`CMLQv`KpV;H|U$;T@ZOoRa-%--6MGN*o&|ctEBiI`lm$?f%?)g^V5&SAw4LO*r zJ9X?>0RYX_iVxC<5gg+$V?zkiIAa&0_|=!sxsor)cc&P?^)h18$T>p35wkrm9{V35 z%ldHE^*MHx@=dOpFro0PHf`AR+xgQ<58mXwWfB8Y@%#7O#A$a({F5Ci>E5*)`{C05 z^7|t9oP-~h_)u55{+XW%zxoL5`@S_VpM1%};r9Ic3Fq$Rv8FpRVaiSSA$J?AwQ1dY z8Ma<)%^tUSkm#@e8(|lpDW^z&GS%b)TLTvAK-?beVGKxz+g~H6HK*$1EF>Rv3HcYc z@VnwE?YdE-ubMUX}I;(;Ig@xIc$dY?VM~lS3FsG&G+^7ndw#d3|p7dx$hG<_}TKA zo$@v7Se0N>-$?l12cYspx)!1KQToAnMr*pcCHP-%rJNDjhvmt#EW+q~Pmdsu!sQ>B zP2PbcXU`$_crN?dL38F3LjcX4YjLczi5D{eWa|$*8x!eMH&j1r#*7*B7uJhD0hhG} zRPMU=!I=w7(3RYUty;D$pU4B@@3F+V$EI5(MtAn z`jptMm5_Xrjh3%qf9U0wgW!!f-VA>B)1O(Kf?`y@4Sw3i>b=|Vt@cp4KecDUFEYrV zDxWhCKA|VMC0)C8S$Bx}T9G5s>VBqrDL)#g&#Kp+4?0dHPUi8)p0L;`#RMuoOtfj^ zCj8Pjhs60t?UlAELBxs2f{+YNHYfGH%t6c_-<*XhfBjwhR&$-91^2(q3QDRWFhppn!Y)@NfRb)m+pgIb|k`oZ_6nQa#22!Yp=d8xai_b!J8Pvix2D}PHedF zyO==5get!H-n;G!jyh^iFlVm(%;yBh9d|7IpkE%8lY>LN@;uNUdQU$2B(X$^*q-|E z*8beu!>{+)Yxh<3ix*J`SlhLHz^VAhIQhql_uRXEh4N2?bw7@s{-2WrxMvMcCBYl7y>9V`>)D%d-sAJ|_nzotg@s*x5RM+da0tI*N;YwR z<2Bcr?#v+Oe8!B!%-##%@h6-Rtfnrw_+iBrvhOMR9p)Z6$N28_U{~-@4TcUGv`zR8 zV~oCiZTy#Wjj>K{iTpo=U)T)`cHD4ZbXoBFtFHy?DixRgIW~$IA@-&G`OmMg*hI!3 z&K^YYKaBhhKiIXWxnl6IyZRc_DTf~(5Rb$2g5!@rp1luOSRAIs({k@(;!)Hls{Usy ze~Z79+{~woy322m9XsYL@^LLe>f(XcnxgWT?Y~R-Ig!6P`8TH3kXzICB);^Lp9XKd z{yOou>r9RwyYR0jX7Wd;{ebvPCv&I!3Hd+#(8DSCHw9ObJ3v@w5SOA@937R9;Cy1u z#9Lt(Nmu3*M=U-GgAurD*YU(P*&wiz({+^Px%K%&3JVK}KhIM?K=UK>ApP?^(D`G~ z$1Jv$jvIUM_E|?9Q6ro57IEmp?PKWNF^s80rsd>iIOdqSal0SKj}A!xVgrPG^& zpI?3@@}B0f6L+aSGZnA($RpSa#WyQ{p8J)H;upX8MM{ic0&6NCq<;g*=i7z6rdmNn;^rc(9;iHqA3gTGzfqPrUQ4yYFVd+9g)U*R5ARlrT0NnR_0nJ})Akbk-4vhx!@fSN+Fc8vcjT z{|Nri!L}Zop*Bv}{T`ccMldR{N>sU`f~y4fC6 zvdgjq9BYUraq;Kr@jnHA+xr?_A=YAI&5`uUe|6jK!IGur;7{N-{GmNtykv>Rkw^D> zG@B{>v=_5%4fjuNzXs0{iuRIU#}y2qI3A5c=Wl%q3L&3r?SB>9*~36 z_SGWaOfYQtaMMTOXFaWkwIeV8bo&2*;b$&=LSX^>S7|mzfHPidV>^2m<3UI{ z2#z`WsNm8|E(w;QXR7-y7X|%f>YegC2#f#i?2pbK>3+54VA=sl%?Am;{1T$|U-&PD zY_4+%;{}^XQtYYZ8n z9HnDJ{2${RzMl0c{C^Yp-59|00zQzB;p9_K3I0Hg@w`VLrJlN&dTaj-lj=X?pGVdI zN!WkkzYCnqt6-Ouufpf7m}2=h)~#6^RILy3qbtf$BYtL&KddLJi|9SNZ}^o{4Y2fo z;X%xNG@{sbJ{-~8%c9zSckWrYRfw>$jm%Oxn42kQ47!uW-JWg(v;_|fZmk3Z(*kM67OGyHel zZEdyXpi;k9`ihK?J?7ZpqmPQMO?2a>D)JC6u-N5>$LfT7pt(!dMOwFRqx=iPj}Nf< zR;A)^%=LGQW6Ee@neAfR#{+p5i z-FMw%b;ELyh4__oQ}bf;=f7k9UbQc>7fWbgm2V>62a?Umik$s}w>9J02b{hSA*|F51J~KJMh-Q(rZD zzv_$ni?RvY`(5_*h*`7fAI!0FQbPCY(|_OgY5Xf+n~_`au>-f^*P2+zO!yn+F+-3O zyBO?hCrgOkUwSZmM?Tz1`_L*MpZqA1J8|7ejxmRNr`|u>#Q~Nz$_Bo9#-XONOWDi+ihzUN$c-dnC*XNNwAnxCVG$dvJ?lP32SdF>S#r#@5O~j~=T% zOyYsflO@X`>GOXwzp(lE-zncHId?;wACsS${APLK;o>JMo>uypX?A>^Tm+k!Y| zrx(V9__cox52n_P9W!Pdxwf=tu&|5R!wx@PFY_$)KYpw8t^H%D0}ILHT{e>37T9EK z4?>#|Z4a`=ulu6(9n_Jj-xL&#|4X}eZI|PquYRtuYp#DV_!BzcF#JF_U&8s)0a3H2 z&6g5Cn#f1u+O|}ikUbu?fudxalHL3Gcc-$ae8FG#*kgCKb;2$Z_U}XdYasscc7YFH zqd77*2zk(?+0vmyhHjyqRxS!_pTqLkD}QG@vDNf*ydAZEn(#M-5Al1al3#o5ws!5> zf!*fzKL)Ydt{Wil(#0t+DYpLTn2GdJc#G~1U%I;h@_JC5NB_8AzrGv5@Altv^7Hup zQ5Yk7Za%!4iwg!0=oj?u(>v(L9`LNuXD4ra2D}S>fjL%>7(U{E$dlyjyX3C%?H!a= zzRJljp=h}N``^&FkHq(xJOn-Afr$AwSC2e? ze{_1~$BDAL)ygll=bn46LQ$=crZPY}{~?sD^IWi(05yP8@uz&X8NYTR*OY@mF*?LZ z$4d8L%uE}(g?Zzh;CA^Noqez|By%UWrb?`fVHf`H=mGV}<^yC)O!g7%(h05^?lIJJ zatW?sT%vKD*<*XY)5&2%m{Lw%vVZ((Fn)cdj+Z_j3_ZthOBZasK#ub=_^?v35QT*k z{zBQ&iD{OPfj+t7?Q13!7Jf76po4+~4?Hk{UqNAELDYsC4_sgCkZNK@cCg3XmiGI! zUrq3}WGvk3n|ivG^VU%Mmj|&6_|yMjt*cGAasnh>z|TtUk86MP8|N?G3q9@KvscwH z_AwfF@Yt^=78dO!?uR@YMPE-WD*9&f#EIW97xE20hODWjPl1G(%0P&)^Kc%!um z`RNI_Cw+G*ek;V&#crU@?aAJ@xs8?gK+jh$0!X<6e4Yf>_;T|2%qYy6z;C20XT8_- zwR&Hfe_L~GyFv6nbB=*7gMJ0w4?O`r3(0nP(tE8->GNBlbD=3vXGn9i^_I8RJ-+a} z^4)*jcbq{^|F_{7|8eE~KT#InS!h81fpfiDHTW2si}5+*b2`Tw<#6~v!Q)!>@?Wd7 z>1&?5`tK=^Oo~=NALZvNU(X@X9OyhqYrl6w$~XUDNcVA+ZT@dazJcXJt zmrUi;_4>Y>#pgcfK=i2mKpB2pUV%l$gH_v(|VZ=(LK;wRceLRI_qEwGX=HtF}$KOpHA z|3>{l#k;CM%-lz;1V2Zu-$A+L9nLRRF71x{c3j=Bci*a!Lx=ylaP0W)MFkUF{sCgk ziDOlsv+aY24E=M@UVYZL-)G+y&0DlAgGb_*?}zHR3{qe17m#v2G@zK=Z7-tabxh%v za$4DS`^Z%}yFKMkPA_3#sWl;uZx-|5C6MwEoX)i* z!KqNSU(u_!0#OS;iM!(X_5J8KYW-d;#{PM+qqyl^j>IJA%5M+3=amwdypp|FH-kZ# zzZP!IM|htYtqT!19rWqlha8xF$bmQ1{F-epS8KhR+jXQdsbcQG9yf06m#inOgQv*f z_VB}w*xtSx^F9Q1s#QjRAvz)tLD@lnA_V*-{Qm1{j(hOEJ0O)ovDn4rCM(@z&%IW% zKlWCw`QcLoW^mh@rQt`;etl?aFrZ)mpl7!pe+K6bya?}=!4l8t&hy-ynv$+CBJIn4|O|&UEk7ezAsk$N8&%(ld~7OpRuLv znd$k`LF9?Ic?Qerr};t6K?$$To%nck9c`YWPtQImt zOyW08kK_|WzR)lCVQh}h$u<&muef{ZL+nJ}`)bT-^yN`yz|XDq;6D)D^C88gib~lh za~1O=UxG{XLtu-}9V#!c#VqNlvWpDoz4esR z*e|1+Ii8J8nlxF$*!CmJ<9HCuISnZ3d0;F^U;p({9r$}kv6F_cjCr$4@a#m+(cA;l zbMVGx?{y$@oND<#M~@sW-+*BJ_yXsP5XTp(RViY9!u z=bn4vV;1+HQGcnQ!ylp%D|pq9-)w~HkbF|he0}fRr}qYYCY4V|m_0>wySBbYF+?%z zJo?$Lo=XpeUsOoU7`ckbuW3BcT-}S{Zz;^tml+4I7XHW$3H>)lkOUI2Fw{2JIdp70%F7JT{)@Rf@SeMRSwAZ8h1nM6>4SDtE67Mn*_MX~Lo!7j1 zPFbH`eJa(5m93$yp8oCR@A+u$0=iH@9&Yk8sUAoNWD6!k?v)8U*TH@j<2LGijB#7* za$@Co7;f@zg!t>hp04fJHGkco`}g0C9rXC+AMN_hQ~ut+|9~nGsh=wRS~qTN49JYX znQT3C2kZfsTRLdqK~*+>$r^hUi}QtZ{)4V>==+GZk7@dQS@wq#xTDXOe~k9ME85Yj zmBvhN4-<}4>cM|7xXbc#8tdw$UXXeCu8}HD<)eprqhXAJruvw z!`T1ou*293>+o6Ti+Tj{%d_w^okfhLd`?{~uHrltcNO|}G@KWPpK|}bPw(F7fccPV zz48@AU)Ng3ubCYviGiP7_Urh*?R5f=<9+w^{H~2%`aWsWA;E$L@7vyI%0ckyr{odMYud*PH@#HjNsWjXih_@%I|nG&ILhD0^f^&VKeu|HQ>=*&cvyA3)_A zP;7~T4*MLpqBiVsp zbFP1>_~n;Oo0nVMs#Tli8-RYyU&T&T)0cs^Gs)ISIe= zndBguMGlf9gHujADR|)ihloKWKY?QJEdGvtFp2rR7hRs}n3J}Bs9aN&g)2N#}y zzQt`T$A`o2^e)N=@ke&BaQyhcHf!EgKByWWYL2c$8vH-x8f|`VN$=i$HfhgT`HR?Z zGyu1MzS(&4%qV``@FRc4ldNXH-PKZW4*(@zgpvN!4rFFtSkpelA*@hPcg4e^uXbn@5j+wZj+i7S7g=GVuk z!G9yyG^d20>ELa|Y&O8=CD#x4T-86}Uu^pxyZGZOvww5JuiOjDO`|qJ@#p%^O*h^g zY#}};j-NX46nmvhk34?GbZPI<7hZVL;+od1`7C(;1^968xfa81?XAhn*9m?15d3|5 zd@lTu{L_Ar2LJE5Ci$0*9yxLc-!c4}uk_?)Ti4Ocz~Y6#UyA${r!4)~{+#(vT~I?|H2Ewx#yi{ z@%m05;DO3d-5EM$@K)9rG>5D;n5$Co%N{-_{G61JA3K(POVoZ%vb||EM?*bIYhzM* zkrBW2pK=Pna*s?ul)M9nk$-@g6zRZJbojAH9t$?BZ3u0E^#A6YZzhgK@f6BI;_{H- zrxToc_Sv>?xZ>g!8}4GFtFWn`1%D)N_hRhE;o6rPeknE{AIC3QW6^w}@ZXgN|Lej} z`@fu=$koab)r0xDu3fqZ-45sx3_57A`3R=sO?|IEk3AFFOaJ5YH~dcj=ggUHan-T` zF89E6%fYm0QE8$LfCp>WtO>sV{qMtj>7a|%lsu*5$A9m8^dVM*JH&2$NW_S*;k$3V z@p^FORX-1om_0iv7+3ID+I95@&{%aIQzRn>wUU{1~SlgQ5fwi+d%i9}0@8ge)gP&e{*-re&H69ZFCk=g~4HSO- z)N>X!%5Aiqe(%@9Pab85pSjw8z5BU3Ai0`-Nq*<<;B+CWSXlNX|7gnT3y)vXL0=x@vHx1@(AT(JOHC&tT!mu z*a(?h?g8>M@Y#*mUQdo6>;ia`@WYQo_}ycVJC43Yg~>nq+s0Sy>=N>4-1@6qgCofu zpnbXvCQSG%^7s1xNE-YvbM0gD?U#d}+(RxNPB|^y7%n^eCmz(oul|qZFFqjuW#uc# zKej4ZO}>rQ)Zw*jDh<1ILHe)uqH^8Z;HT`Xu5x(%j=$7@?DA=+pT<2dFN%I!@tykp zGWwg^mwpcVq4AUQ$S?+DJ+WXrK53fMk^bMC2LB&~pSAR*6ALWwn#nsZ^M=S?Jkb4K z|2=+=s~Pv4bo>d}=9$4Pau3WVw~+Gg+)GXZ@kUrwr`2bA{mnP5kLT-v*DrFw2A5xc zx$#PvH*&9VD__}cY@MF%+H8x92LCjCo=0zETub|IYqaX?a2hAo|A&O1ON+;i9{pAL ztOoaiO!kH!eKGutOrGAZRjghe%$a?p_19#}G(Of*dqaQB*iUl&Tx~kIR;*kRoOHs8 z4o_$YR4(OjzyJRGiR)!Q@nz+~ZMWZU{pduV2DKM)viSRf0}rfXeO9s;T?j?->-;vz z`bWbD5B?(T|ELd~Rh{>;@%U9|rV2lDvHp+hKG>9FW)3o!opkugy<~aYrjp0tamzm@ z{A$;v-9Pj= zzW=Ya>s0*bhxixK?rl`uGMLPN-earp=&|d_Tr4rwQ%o<_?~_g&er(np-!@Pew_j=B`}oQ@{iJA zUAm;q;aA)2b(f>?AAiF4g7-gINS>ol$V*vnwteBk4}()r{V&sxD36oR9cDc*O8kD! zh!G>U<&jSW-4}l8{?SkrbM&0N)ADJ_`#^qM2ai7Z3;G|-Avynsc=-D$Z@xwmjx?L)mkTX^tx@?^@^J9t{tagLr}V&>_}zTS^_MryL3FR$j^9*W2j1 zzxJIRld$tV#W_u_ZohZ?)ku^&j~`_$_8!!-e*EkjpA&l)HlYKt57^(M!r1{=58_z9 zlX6FR^R0d_8Ncd1b9n44nkSiS-j`V1nDR^2l6S3ZT;R4Lkk(5U&}Nj-cO=#}#E;#M z@WA>=k@wNh{)y*F_=)A3_$4u^%fYPvJ~l7*G^DtoEb!Ycav1of@gSD-KK_?W$Br5M zRZ4%r0?tnI10bH7fVD$sl37BSx$>jU!ov5P<9i_><{yS!ZheLxP1mBh^X zK4EG*=y{NO-Sg-ldo-Q2@kBN2ed`nhiLBMuYtH}25K!FzdUZhfbt}ZHh!fV&&G&lY zKKt%l5$8cTKOE8hhVa1O`|bH6?4qgD{yuonpek~#D!z?1Z|mniz&UH)Sz>pX4SR@R z#^3^eOSV8%+=)F6CQK;Oehjop)-OuMlC=&rv|kzFwd0$!MLch6^^hThH#KgYUvBFg zn&*$@{E_nR0deYq*&AbrUDD-L(j~5i-{2iiIwlX;L8cJ=#g zp3vmx`>LTFvuOvs%z3u@Cj$ozs3Im=eqY!-)BE`xyF=kCL}LfMzjVw*`iRf$R{!-J z_8`{2_b!&hc+jqG+ZFiXYL7WD|LD={b@UqNZf=~MPCiKQ`8A<%dc;C!II!l>HsbJ}^=A$eDr4b=Y*Cj&zypc5CFToVI`zx5F3-J%ATY z8aFN@hI%u7G|d;LV6}EK>6+>b{lA(%efn5_SmlWA)~$PR@WBULKe_(;V76ZQ2foH< zcw2k=CKMLz79S#;C;x`NY04f!l0N=H{_oyFO6D@dZ>TPu3%$<`;sJ57IVJn<(|#p6 ztF>k*|3JTHs-vApbwumdl6#NtcB7E(8mSb_8Ld&!UE`n#!d1|+3UW7 z90BC2LLY=(KKu?xTn{wHQO=E?T3ZQe+_-VBkMC@1Dt_gxP%g}`1`irsMSdCOSfQ>P zb{#dR`$I^w;cYVMos(t=j(6$&U-d;gzVd)aU*ha z?7Mo%kRe;~KicVX4=De@G~Yk@MtFw~9U5>CIl@N7=kH`6G@rv!wn9AHiJjd>PVLIJ z?RHyE47QiGe3}%e{wQ<+l=&3}q4PS3{dVkk>XLuTgQQ9aLGvK%C-M7c8(?N>^OnuZ zJ9X@^W?=vRTSko-`6cU$J10+^T+RM%3 zhP19mI~nfFY4c6if0Auh%nCW+a@HWn4G?ouIgVmE8~ALkeeUYA@OsjEz4Tl%mmhu+ z*nT(d@O?W64u|(R-~;dCWQrGdeH-^PC6ra&w0^qA>ZyspTuLKrS(l z5Wbp;az^tIgt-S;^LC$Sj@L}$Q`08-IgR-p#CXj74PM~r>6!4eyEt9}X}mfBY60nAQaQ7FpIYo)`Hk=XI}T0lzYWLt-IwkE zTp6q_L{GBMKcMuayZEAomdM8Q??3qmV8p*pf8(`}BFN0q9Oc9ssT26GD^v2bvr)17 z%c{YS>}L)rEma~eHPOY#u(8|YfD{ScY}b%XR>H(;v&S;^)9miITJ ziKi8U68|)EiT7FUswt?nC@^ezZRO=qNXq0nwL?@SJh9V!LiO8@-t=6W{rPrgT`QLN%wlvLepBGpe2~S*>S*s3 zYbTaT&(`w!U0o|@ZP*Vs9+X!6dJPyPPR z@#G*FJ8sPOp~Hu7>)of%hJ8D9SlP05>m}qK)_!&9f$>Z+&!6yqDLlB5XYEVT^pDf; zp4<;7&}KMm$Mv5F{4ZIHosO@>G-92mPWe0Wlsg!sZSCB-^BQ7EKGFW(xw+&XaBB(bt7 z_N3WA27d;`cA3s>EN41|&z|KU_?WU0vlA;L{&6L_5H~We+M#i?jU$%F|^9*4&>`j_9E$iQJfXjm#&l|y7h@U5PN393OeIld-uf6(; z`C@8sDeZG}$FFZ^9^LIV;?`QV-U=U>lfjQczh}JEoG)YbuX^_EvA$`OW}mP=wiw@v zB@})c<+>eGo7rF<=qrBy#lByg37xp4+zTJTE9Mwt#qIXkqnvfAZ8on+xtNC}KR^<8 z;{(|J(Q<8^clNo#TKq7aFRI&Dt90?=;OL{ZkD7d`9QMR9oRj0Xr3d1{B=-3pF>=_S z$@yz_!t6q><^26UWo+G09@K+>53oPuv8%3kWRG#?5|#Vb;-7Vd4~o@sB)Qw%n_D0A z@|W!Y_|i+}V0ccc3h^rAx5A)CJ2) zp}bBl8^VJ+@HYkf?;w*q-H(z34(PHTePk`p{7jN@yZOdA2H_@8uj#&gde1%koZvHJ z1)_e#^10N$S|2Q2$Q~%O&;emi;I+CG*NO1D>6(i#V!yJTox5~iL%X9qHT0dd{+6e? z^8BoM0KmBZb1r`H(k#wJ5N(ZgKBu_r0S9cLY~*&8E^2<&)}fNi8!x-B^E%>hM7#IW zOD{&^ahy-C{IcY0wE?;ge>?eTxLAV-59~L+E+mes3tAVZKJ46Y|NYlc56TonNtx9) zTmWU=fA;gweAnYYgi{~CTfF~%`)NNA%Rc~a!=5CkIBs8VFMH>YtNk6Ea{j8j? zxc0`}6nyZ}N9+wI|3uBjySkJ7j0AS?8|rv4Nj&&#`+ZrHz$Q{h#RJ+%oBzqm9(w$e zJ+v*@wVcn;mCA_4TxOYuM+@ z#~)T|&rf_UwGZ^>&Hob=7k_MfgHMKk3H)yDF2QS$U(d64(%J>_V8qA~U$7QS-c)O+ zX{&9&Q}x;E)PsC|?rxbXJHp$A*wkYB>syc|xSi}%@yD4Tp7y>w`NR{0^Upcg;;){4<`2O#_WY4Ac|!lw)dPn+Q5Nw- zbw=&*6}$=c=6Zsg>=$@gq*+Mz>-RmkycVdq(D+l=2H zudvsJ-`Q^GXDR=1#f6#QHNL0vKbF6p>WOq%{(6dcm^EuoaP~Rp2Q!J$RvY8$k&g9} zt#k1m*~Fb~cW=8guTd_!i7W@;tKe@^7Z37X1Mnl{uDlKS;WlC~*l}7in~maEqI;Jo#T`LZGX(32UBW>3>k|5UB1R-^i_=q zN7RL%&$NcOfPsX`zB9STd7ksRG1)sE%c2aStX!;^WT<)|8Jqv1{17d!MX@c8OgA{! zJr1k*;`!Xr=lZqqDb;)Ky_bBEK0>Byzh8pt8Y3JCe$7cNWPPP{4F0F8PbU8c%~DeBevIG;SfbVTng z*Zi=d!?xj%qKsQY0YLIdOghUwD|wK&=3#F4BZ zR&3R3at(O0`~#nod*D-Ik>sDe!txK4lq|A1(({S0cI`(4o*A(ZVZB3khwNZN0_D`L>^S-|ugy z{q8s6b1u>z!0H2O-|I^H$UndGN{hX9HZY54*20etFpto?XV1^EfojW@gW)-Frkg8h z%T>**u+L4CvVw7Aw@Zf-{k#Us0sPvx`;9l>v_6iFbwd0V#Gou=-)!xjU0z;pe(CB1 zB=){mUw~L$bmn{bq`Urb!|+GyLJhejzMvgY?5*_wL#Qc~S_cMlP4UUayAi8G?9AUx z{?@;5Aa=(pYyraVA%?w;g-o&1R1~_RgL|JoW__UO-$P``@A~ zHXfp%Bptc^Huj3eR=Rd{*La{D6s=mdWc}LmFKBMLQyToUxF$PM+@;F_mD61Oz14f! zV^0yjrnNiK&y^Rz@SFZCp2WpfT|lh-#EBDJd!Tk7yFX#l#Nc7}idBq;+y7ViHw7=g z_>#4g4d??z%H_sgntzZVgk%dw%@>VJga0R7vv!~PyDjPR4>X_+cJc@RYs5fX{b#Ii zvC_oeo=5Ds@+hc(=SlkqUw!R0PX5GQNe3kVH{bkIa42@9q4M|h4YGsN>0=D&*LNfM zHD@RJpPUAN7=ITl8A;4W0)L_%j>@*)bF+UAzm07zrjEWp_|HB2Oxx2~aq%AiM0DW4 zFaK|sf5YN%o&4W?{SC9D&IV>vmdKc!-xq$x`LHM2Ht=gasJ`$ODfnd%AA=F+Uo7g$&yy2&8+6!tT_28OouMTt3$OgFl8&5y=H2UoN z$l25Z^{tIp{GR%j!mi_Z>^XgU$zJ$1Hxjjf!v8Y#vF25a*n>oJVa_?|+EshWcMsSP z*ZOMp%3Zck6yjI?ck*{St@+L~$*V*D(0Dyne9biW2c0+XQS_fNFxVBVE<14PC6~b~ z*ZZ+Y z;oBK>J)o=c;D7@T&>rgXGH2#PSVr*AAZ{*h|H3-Gf#ZuWz8E}Cj-_XwekOPpQVxRG zUwb2{SpAt{R~xXIKEjhvK4tkCq<{5c*XQ`d23_{wKj_k>OVGJ<=b$V8or-}|9hihK z9DN|oi3tBwP&)j@^n1(bn;ZTfJ$u-1h!4y+vrkXqF5~xYnze)20@*~xO-tTJ%0Cd# zK~TlM;g1oMd_;UKlui3nh=6<9DBI60is`b=&QJ8;V1IK&rz9%@0 zu|xfG_s@0XtD!@OfE(XV{^xgO(vJpKzk zsvmnqPgVate&p=!pN^kZtg+l7pOSY>`y79U-mhPWy{F#0`~$K9pR-T)-N-JqXWp*V zbCjN_%vNr@S2l2(_Q2jwW%vH+ze&L_JMddbYw(K-$nPAE`y%7f1fR3wLx}%P^?yVB zn(Nct*Ofo}8RI?1gR?|Ou1wP0;(^-!Rpe?q_Sj<$OWn^**u{7s1({py zBYv#30W~8>4Bu+)pOgO$U=`*l$@fC--=dMUl{`MJ^Z(F%3gW=e~RK~ z97I3p^dJ6+{!fwFCe=^1h3fx2{p8b${5xTtIgh?#w)Q{4qY%5|{piQ4FMH5|12>uN zbA5pGqS%`>k$+{be3-${-tT?-Y_#=PH~umFiQmpb4?_Gi(Eq6XUESXhobjX6EQf~m z>9|+zbrF8J*OGI@a?w%$)ds5nd&70tx&D6^?O#$E9d^U7zFfEN-PU@1U_Ua7zhx_y z40CfQg#0P#xNnCFeMj<_+^Gv$)_w5?-U~nTJu{{YzZ?Jf`mbC`XPka|E&R%ivpG}*C-w=PVTsY1SCiFZBYu!4jb<^rS_SjQ?%ZzWV|2s5_KRiExBjdjq z`x&|K0}hOL$zJn?%ck)P}{TL;lxh~|WrEM984xzwJy*o21RM?N+C zb=sf0Z$4NhfK)$U|F~3(PUoWJU)-W)tEKcAcTg@j2bSPz{ki~l@j&hWGfzKjd4?*; zGo;)rn*Tff3}^owew(YbwqVYjxxxH*7BDyTX|NJn1uZT6gj_?)!`ToWbib*7?}Q13 zJDN3ZfuFnZGq)SdJu3}v&kNT4tdDGUfV`EPvCZKeV1xK>_*HL|Uv1`0=6W>u8_xUs zd>dvbC09s(FRBBUdw_g=GY^|-xd#rPIm^~ave&;vIl*7kyLX=|@+sIl5bI$Y|Lju- zel#NX2z;{n|F&)0u4F7E9|z~h(-0ltUbSm++acd3`L`HW$VwUPpa(O_Q=xu)eZQafb8I&Am&m^F z2I4QM^Vl@nw%nY?bhZ2J5}}XTJ${;uF7M52vNTDaHeWoZ7LQ>yFx!KkxQx$8XoK8?J<2M=x~pWa)H1#Y?J7s2@#TtH5hQ)~_R zdnX)kITp_&Z|1GH-Wn`;Z$a=W`C2Z#@Iv~%6T?1yqE0)W)qXD>3qK>@(&U}w9a$A( z*P6%s(Cz|OFS%P-F39;gr?Q#F8aEFbG-L~P(QIK#89Wa%@}@2}~Ya+M|~{NBP-(-K$rx^``f3-LoV=ujyg+ z;`Y2T{AMCra;i8t;{kqtOWEsXd)Ob4J&cwm%7Zj~aCYo2#_yGsK?iNgZ`wrjd<)V0Vq&#!;k;hohd6(PAL)x7y!t_gpJQ>G zHoL7Lb~4OMZEZr72mZV^evL)o*HLqivOMGJ-!sOoarAkO<~=MABiCOhcdF(rv$yvN z>?1}D+uFQUOYN7t5X2v_?)eNrTF0?@8D}_p|E~ys@!%*(wm|l<7(drfh8@|z1@11{V%L4bZ>&kkAISWYi@A>L!EU>?UCAEi7JHAJG)BsdUu6{b5dYEG zfmN~tehy6QBItb;?_YcEHTp}_tlnE&pM*Wa2YxH8fAUB9ddScrTkw|`_F|KJuEk9Z zYY28Vdh<gI_#QnAYWx#!uEaYSFy;^5MgVZ*@N2Ru?=E{84h#xg%>o@zFvD?)}X@ zw!Z28Uv*qhzS;9HINx%GX3~AL@$ge)3$;V&(@y5QtD51T#okE5z7W4R+51AsRbm4~ zsp~m<{M^aC@%nFX!#|37y$~{=Fpk<^v8*@wPQax3LXSIvIfC8F8}i^7=CqV|RQ6Bv z0jdjO(m)H^fJ7nvG`#os9A+HB-DnC5_ z4Dt_IV0Gb8-n;|(`cSfayLQV*j2yX@a#`HH`3Yek(0h^Z^CBwpo2EGYD2$PejU!gOZ_{I>kdlAmF@d%@Ozemy)*O} z?|pq>U$VTiP8~b08Z~pRG9QU{J<&Qm&|b^dqXeaB?4H9$9S z3(umq4!sfXp+kml-E%MIaSXS!$0i5o-K%JDYi^;%60iP)m!&7@r2*c<|RMb+acKnYa{tR zi#c!Ftnt#l_S&n0JR6&a3>xy6abw2qAn!mm{nM{m|NCb4teM~3`K#N$IfQwF3Fyc~ zaubXnU$A4u@Dba{QBk!|`@L4>HwkkFC+eGTi*MvPb&X|r9d|_0;|%cE{4xH4op3eQ z6S|jos*{QXvsgB+NoFdu=HFIQ<~6ary!CmF8&~BvYNUKgRZW^U*^u8jzY^V9MW3bI z)~Q@R8R?aa-*kF{SZ}CL+Lp9&%au4Jx51i$Z5HFN_Kt~bRZQezE(sv;86y$Ob`3Jc6 z4E#KT-)=+B0j4o{?E;>Le#htI(Bsm+>Mn!&+i} z|G>YC-qo&o?#jPM`Mh?0JDVg#^7HHnjezDr=R;ciz6*K)k{{56koF_c{GdKR0+QWT z-=Z<3e+}b{FMssdfZt#b5XPd^%zD0quTr{uw6;|M-2u&qK87^!uXc42B>xwU7g9;r zb&qSW-FWk5z(q6)iN*d)iF3E=)ChN39v zm7bIS6)mej_jmgD8IJjlHJtD-r*i&Xt)qU!UYxkOy&=4P3{t=8BgW;-1Bmj9>7$jl zYum28YnQGSgV|4V?C68H78Mk3pIBJ5gYn4@`32$Ewte*Iga3E%kfED;_3rc8-uvuR z&YVIi{Zz*<*RPlVv~plQ4~>F+)4^#&Ug(2vPvJu&`j2+qKFW+g4&D7icW%E6La%+F zJEnPwk~Xc|lrs;!j(wN@mo?14F;9vg$aL}#DE5YY|MEMrHF&qLk=C0SvqFleoK!u& zVBB8@3>;9kSNpwIkW-Gl12)FfbHoSH{gkB}$4HS<-teJ&MQ>3+CNEuz5Z&51e*dwK z8D6dU?j;cO`r3P)7$@S?$dORS9@G`Xh7SF+#^B5cD^}Cb%Ye zD3q=C6jT1kp5`B_{Fgm?^l*NiHmBy+V#7Hbi?N1(ns2gm@`irdp<{+9De0P6lSCNT(hqawiau1Zp=%5!yLue!|)Gl+pg^jgoYj{ zp2G40yunfBQ#sSTxNM(fz|S}02Br2d=*mRC{}yD}k@-Smu`4K#Tbq=OoU9~kk2!iY z9gv?aV75~$0I0f zZ3N;-h(BHZ*GF;ux;ObXmbdevclrHyiES-8=s@-co;rGG=q+f0s7bS?rR()D ze7BB!n5R6X2HUu;Wy|JjALwgo&v*9o%dvbZ9O}m(*M;zIz9BZ<&GiqlbsyxC&y-w3 z#HCDg*hBo3YnQRtfvG_}K^CfmHKWmkW=)%_|1A6;fZ<_|wc5sX^*J!sJ2&EzQ3Jf6xeIunYhbofSB(viEo z7ax7V4fw9)lyvIQv6428oEtvx!nfAh2FW)Yet8|xoINokmB>wfXVnGu(YkV;{7Q#i z*Bn=F=ljY(P|}KhBovQ8UyQmg%1+<2Df4%HkJSOnS2J_N`W z>P?1v}+EbR-Y90lfogRf?MOpv5Y**xFIT^fh{eV_LG zNC&h|EZ<}850Ias{_DEtX*3U0It-r<@JjaZU-EB=F3KlnT5#We4+M+L$}Glw1#w0z z$u*!oYem|-cR72ad0Mt?dGJa3vfyL#4?OhnBjjc|DjXL(9grS0O#d7Yh7KG41?{2M zu(T$KUCT)qdmvrdACkQ&Zce^P;$!fy!S_7mLBsINw{#l&Q$B9-M)I$S=2}u*g>nqU z2|s(qns^`eP3-IW%4@HpBjjOXZosfN9KZM_f0t_f#ctUgKl6@pE!)ED26I`6TwfJ)9gQv#sub?*#eW{x0N^*OjVZ z*^1@CsVAS}=E-(V{>B5%%dn3Cc~SE;R@NMI2PjqkntxYYQ96A1@c#|tuad?r+0;Li zJ^1Bg|Iot^$MH+{ssq{&^vGGWOrFLkVyoqMarDv02A?ctugp*fKIdN1)j$8a#p*}q zQyQd;Dl@UCf7@-hHmlHo;n(Z_}=NkRySlO5{2P=0dvTu01T^s*8i@A6CCqn$} z^*fdLR_$r7oJ8Wop~}_q`s?P8W3kt=8TjV>k~~!SWq`TiWiffthEAjJPF(VOuzCF7 zPr)yH@Qmh%)#zjX2!e#ZmxLa{n8 zKL0%Nhc5P1vaiAh+kfm<#9*6IJ1Y4FQ`OMU*8 z#4RZ9C>UQbJ{ZGX?O5jLq@#|Xb+tqO*~$7ZA3Nb!{FUKnk7~6KRz4lUWw}};e=y4q ziWIAV5qVl05`Rg&O~9Nx`3AJ74Dk;5aH>CX@aR!r(hhzijF8r*P=`24{+icZjL*fY zNt25HK6uEWplf$xxw~}-w^mx0Hb{wVo7lKp%5 z5!wH>N&5dOZQY}f&kG)V;)&piC!YwOdFJWhkAHkQSc>g)Hp0n%O~vZql1ndTZV;Zw z@n`10_(^VF+fPZc=bBg78iV3JBsbQXs+l)m0b$@%lz);#uC#hTjo7vNpPAtQc=9i>Hs^0Pnz0&f*C>L;aWhA4(&C)&E8L`S~lSPMQ2QISkE5(tP2=xUu@nl8pZm z_GgdcmrY+wUackQ`ZD@}%1NgBux9OA>-V`lCC*kZU$HE>fH<9o^!wBfSu8uT+=}m1 zouJGXe^1N+`B!q~^CtZAKS|d5A6f05vxA=p@8Fj_n{l&~W8Jnt1#?_hw6PY0+^=81Du6P#Ve&r@ ztWo?r*FFzgQ(QWJ?3gdjPdpxbTt|FGl5GmxsSy7>!>^pHk}3UJ`v1c3atxv?+^0UE z?E7IeW(MysSm5@G*S*x0B}GE9|;g zvC8L?tK9Xyc3u9oecM~KXvG*`Azue#6)F0ps{J50FX664yga zhJPA4hUU?S!_J3w+VC$b369X7=E5AIDU+uL4?g%%uz5=Y{~Fqf%P+rTm+{jVGuy}B zs9R#W+9NYp>tm9CI=^kjE@=Ey(zb28m9&97@x#!XLO2Gt`ILCQN?=a3KkB2IOu;W7 z58+qaZ}`>TBhwP_&(iqk5OT3%BRnbQ_|7};3by>uX6lLh0_XsGbIUEi+y(ttIl_1q zbibqNzQ+EJe46^tMMMI68CfNM+Y#~@Bhmd-v7J%ji=eSgT7wbk-aMlwl{6oTt4mi zo0=WAi<-vmY7cj3i+j8(KKZ~%7R2MEh|H9zY)vIkjXf?TaM4Ce|{+ON3&IU*arJvIO ze*ODxV1EX5Ub46RYe#v!sYmel1HW`&VN0KXLHciX!0CB(ted_La38uNyQ#7Bq1Za* zpH*&I<(t*fl`SGCY@fcPa?fULIE(R-@)f9_97-H|cDm>5q0@amgM8^b+O^%i9NeMq zTkeTwskr_7M%vscC-x#dV68t^tbK)fw&Bl+)#T!zoj9t@Hpj1H^d7@WpA_Esy6^3v zp9|60*q*0)N8doa%BmME)KN8AodE_wS|$S(im+7JhUP*`0aj zSH& zU*df59W~uIKQYCE053B+{Cu86{H9wwe_#2#@%bPJdD?nl>rBWeZQnq8tNmSn^!*~+kC%{$JK?~f9B)wz^>DWIN}44vw;4(&GDN_W$ z?bn+qFZJB)AoW+dR)2f%eQu4z);%^y);m}SVeLa}AujLG#^Bm(uHicqlkG-*x)(l= zwfgPsNwXZiS3gH{z4$7|GOc+f>4RGw1u-x1ci|xX@9Eg7<9=(fMJ^XR?LmYG!pwWi zSDJd=>aW&t=FFKBl$MqnR^=ej+^!vUPM#s1iw_Ildym{Rhc(RRhuCR%cXr;ta|O=d z!Y%xwF5pkXDRAj11wS|C{Fksp;=w}t1na2>+IxjI!L6@F_z=+rUmrsqpdM&G_4@0s zxBPO9QR++#mv>Q3gpROdZ*&2@%tef{{w5|7`r zC%mMXLfOK1VOlZsi|jQ;oN>Y)q{=+`y_HYx*ktPm6fZ2c9LuKj$lKQakiYc*-FHIS zdtI+@zSUtTrgiF0e61^U8>x@4HB5`~yPczA;3)PkA0B~B4x z*16MuYlpM%2kVHIhXL8y9Bg=$4}UQG$XP)-a{)^kAJ3w1pxg_x8{rz8;~6m|h2smh z_3hKQvUQu*J{H^AcNf3?0MFhZ%KTDE^-95-)qD5Ny8fay#HQsCJ8k_0i$nMTy)4$a zt5vHO%lGTlY5jnH{kD!AHhg>GxPqM%CrqrKR5a=9qKQS{Tz%!0-&}j`bwR;|q8btV zMEs5YQnvT++iwf&)oa;jayf095<>W0SmpkK5^EuI)BXcl0tC(LWekHFeFTc?y)+08-rz(8Zs<7c}`RSDq_#(0R z=8t7{Typ;dq!>7_s+{uL6>M32$MO#-7P40E0iS<>+yi`;PwZHJp5hJh63@x)3ub&t zVzQ6&w-G5vjB*gDol?I{GK`XP5D1@-?Q%9v_qp%OXK4Ub=lGS+jTemBa*XmgQa)l0 zcZo;(xs0#6`NyZR=1_0SG!r@(x(T`$dK7vZdJcLaB%MD6XB4 zn+K^iPd3&5HEo&~YuT#VKf65t$mePN+ZIyWaX54~bPc5TM7H>T=mAK1X&Eo)UIm@T z`BaE@EN3?;978uiS2IxUq+Y81|*il$ewDW`$PTpBSj_$GJ$88@veE6Sx_368z{l5FIY~HeE8GUc9gM>V= zI5?LND<|g`u50{Le?e>A;aHc7UpcINXI@`Pd@XUt<;Zot%h?mU8bZfo3mLZ|H|-x7 zD{0xHWm)I__g^(+$dD}sg#|mbCo4Y9->|my4Zfz9hv3o+FA0_}TZtbw``l=)!Ns~T zMxDsM-UT}b4H>+(<9?kgnl*2>i2k|wpm_U_DUtY~{M6@gYyrhzDs6I!^*+2}hxGWB zf55lJ`mxmCh(Ewi)OcE)^V<%0Jl}-Y?#mBkoLoe13C7_zPboisKdzR~sr=y%Wlidh zSJ@kayr*~Ed8ds(Co4y|eBQaA`8vf8D3+{xT*3IS`u6Ypd7IsKV;{#{huy{A%cjtW zjSY$yIa+X=cjSVOLKJ>xhwzeJ(??JgzrjrZryZAn1<6;%WR6Uf7q*;OwN1>?Gv+4m zg65~gITz+@Ep~~c&7EmY@uG8o5>%4=cpZ6<%gdJsbBVv1gs-UidTGAW=Bz_r=y#Zh zP|P`NqMKW`Y_W_2YfTMXYI|BSzKfkjndsp}UWPFORHwX510t6_Ydrq&Hg|LK-_E?% z7|{F_I2Jd-hV}2)U;Bbvo_H|2xp{ZgI)ddqgclsqacCOz46nWXDtjKeJkaa$Gye7M zcZ5E3rUOZQ;I|d?Yrm@_{xn|^`@as`vV^kZQzRZ}Y!~}AWoikfd4bCGAe@v=!LKjW zgTD*6$VXT8TXv~r_uY3dXU>55M2kZgR?~B2C3!pYamt!2XI@cj3+Llgw3hv^mG{;N zKNI;R&Sf4(KGV{H_}XLmJ*z+bR)i1LBSwt;bDK7;r3=C@eIU=j`4+bpp2RBz8{#)h zmx5n!J^sY^;%9qv_A=NN!&HKgcB%G-W?fDBEqzRC0&|8~J#x|q)A6%-`K3QvzF&{s z^54RPU;N^h$i5)S^N`+t#Q8vdP@N!`!FI(-VjtlFc2Voi>R09D(4hMl=!D(Y*HuZd z^x5NY%CF3W3_JKs*Vu+@(u}#hcoio2@-+{e^YM5W4_}@@H0}PW)m` z_)?R21;pHl@wJqF^};=&eN3+36Fif=5;){v@yaW&n17M#ejIy(L z@gURhy6fVB@(5A%s);5^i|LeZ{?ppz7 z&0`6__)ruVb_gn6{pa3z{nu^Kz8n<4ZrJzQd+(LX!5hIY8ztROr=O8)k|VLG_*cC4 z>TB%nL>wOSR=#iB=gH3TO$&Ljku{0yn46!3Kc4o*bb3(VGsFYSms?X*RPhm=k-KbM?~_uMl9W4Y2pz#mDkLc!X2+HA?7yiK=pz6n{Bkq zn$yG<%KwHu8VT_lz_i%K7hW7@P*v>v^tVcf4j!_JXGq6P?_JvzX`|}kgY;hY__x3N zE&ins1Xr@B^;u_~6`XY9iTI%(7kuyd6N2yKdws#V=La|4a8vN)6Hf(Yi_3x=Zn)9e zLBpRNpT!5`!6AqIy*s0@c`XAMXJ!v2C z?C`6NkU!aU_BS;->D+v-eODhMT-{f&nJG`zzd+>UU_r zRl3RaVl^??U)4LNmV@YYW~1aqYQ$2Nio0 za81%{6|bpv>(|LQU}zW8W~zxVUtX&D{jMhAUM$E1x(_lg53pJId)$DX1e-Btx)*rR zh8678f4*tTbHsl(zT3#U{$H@BLhm(zc~)3o%5$zWNL|M~BwUlZNW72dE8lxp-TyE5 zV;&ybLsEv+KJS5l)=#Q2hxC1u2iW(x2E>0+8I<7K8^n)&LAMMYI^+}TowkoW08F*V z&+ivKhqaaNnp&Oz60EU;LUBEm;RRQQ@}YXT!Y4W_mszW%vZnq>TA$0j?7D_ zjXhxf5%y6z5F}+@8u6o#PWHu(;%6T<>y71&G44GY-@g?6z)k$te?gvE?ZEm5VkiCT zxnHI5WBn1=AKrtxHkVxb9jrgYob=1Sr>-1(CBE~0H}+!q6Kp`Q!=#)(f*t(q5%voJ zKj-!5NIU*M)?S`{c2#l1252WspDgo0-2MYB8(3c~Kk=lK@a@NQG3OZiH~8b@j+cd% z0Ylb#L;VBO?%kF0{d25w)_A0ji*FpGUA%PZGI^GJP>`4pe)%82lzB%v+JjkSBlDIH zOS^zRP|sBwgfOcAJ8(_v3SG;(?&bay7JF^%-^I@J7oLAXuPMWN8tRnzCz$WQbB?;K zL&yAU=JBahXTbI~cH(D#c=_cQQGdX9utop1w*i^IggQuBSsB*fu|6zw8Lo=KKhNN= z=-a0^zJILoqfZv~k$64F*@TOq_g}!8dip%c*TBQ|$P*^s4Bu$hhk`fObNJrB;5#7F zK4Kfm;D70*-{QXg4tW0fQ}ecTeS&-ENqtz4!Q36ePk*?W_|aV6GH}2E%q!$vxlwBH z(800?0r`!?8@-kn{vS@z_~}zEeuPt}PL(!cX#enmOO`A~`-!+MW!DAgtPdMLOyT=~ zcu(&?!1@&S)c6eFhhpCVZD2OYEdzM|Ea)|i6TLBX$dE&9vvu#$U3KVyZ^@rhs_+dK zsi#HpMt_I+O_>$dKi8h$JmD71K|4$H2fed?%yl};m+LjSx?g~OgYT=`Zo3_IA!5$0 zd-B}lH1J&7L3KFg6m?q9p0Fz|1MKm92y+Fu0weMN92CXRzRlM#Ubc0_@L^T_K22G- zGT9^6?i0(k{87Bo-%jz6prD zzjWDh^EyaEjq%K`M%Q^BGLaTz}90a_XPsvQhm1 zjw_om4hj4ttGH$YYnBzp>|`$s%(E5y*JVNGJZO2fbvDsI)*T9dVCFtN{Khx)xa>27 z{vYWJ4EqD|jcizZ zj{V25=NRdow|A;P?|xr>L|LE@l>MNT)4$?-ku3K_mU)yxOqcdTe)awXth2I=9aBAg z_z3Plz&>*7pWm2q_0PHh=Ob*woWa+J4IP5<%E)Ecl;CJr4M3?^uJ% z{?B_+*Sq+l@1RZiUF<`3vFt}Mb@EiT>+N^6P0%**{0{6xape_P!vE!0_m;Fnd$ImP z&wYh0e2ns+bwA>t4vN-)CgTFXmHYa@0jD2;u37gd8NJtn2k73#zZY#C;^7)x)_-sM z!4K3Z`hScC5gX@ZQ{Jo5R!r?bq}L$ddHWsEXMXj6;*ZG}vj0G@UcFKOkv@FlXI(Ff z{{~!P{rB~rJ-X|?_ni7~u6%FPb@k8jo)@r>TsrC>DeZgN^+OZPYSpOw{&P|EpAB8?o^b`6O?Xbr?>OZ>Q$JGDp zivRiNp96kv{~tpC??L|)Cf;K5C$Rt*?1gJHxYyEdyBCSU|Ku-zAvTghw;6F;eZcp# z|Dvj-S##_^AoYLZ9}t7T6!!wu*Y z+GX&6AZ_6nU;M4Q5Nmeoil2M$U@xV8n0o}@C-~X^kLw5K=-6{IUmP}YYR66((?3_Q zHd@qhH4zY_ddZ;3L1^!|tMtBJSX0{>UDF8NR6#z|k8?hD$3v5=3jU*T3Y z7UKjge_i?Jd(Jfvty;I*rN@QE|FNDEE&Ie5Bm?gz=6{rv6~*}OIefHPd(F0Be)z3? zsDI+;9+W%T=l9OrLfngB{7p9^|EJ^Uv+N^XxpFo70AI&`T-dV!dsJ@1nDF&C-dHF6 ztoNWU_gS(4V?na_q>Kws0Q#u!)A$oU#{9TP55J|7sOZ(J=U(8Eu|-?=$tHIzBWwRG z*I0l54$AuPqO9kda_%cI<~&_b(Q%n_75HtR@A(&AAZrybLj9Nh#phw2Hsu39R;zqK z-E++-@xZR(`(=(m>rD6yWC6A>WBq)mX-Ua;%s*mVFsut?!|&b`SrC&U@~_K#_Ggkb z9%2&0Z(g%x-{U)SO|aJvKlfiKZq$T+5BQ<`0P|xq*nTJz&+h~C;MxfG1yuCx2^)|x zc3T(9uN<(lu=&CMc0+y7=-@Li@GyyK^FUyS0?c1MRiFzvbq%`>;g*hB&$&6@&>ZO8qG0>dvx#izI-cK&l_f& z?*ffIZ~U}^h9IuxWj@$gScq?roOvepB$7R;IJeQ&VJB~44V zV{Ba38S8!m(R*gRu8xV_^k?hwk5s!hKi{9KBfthZzFCF-`n{MFY}+>)zvm4!{HFp7 zEG8t#q2neB??5aUq&?jNKH(oQ*9Q0xbBzN+ z&Xv(VGG+T3KkUDM-+rposhw13;KzJY*(aBEVAgq4b=?@hh_)=+6o>lt>$g|dqtm}@ zT~Hs~GpZLT!vRD`fj{zUJ0!9Q*SHpgX>aD(62@3}4mbn-2AFrl{J=7Tb!VQFSSKY7 z89W5|J7TN?V-&zYWatpnPSE{;A_K8=+B(V@^x+UU`r!AqY~5-HW+$>An?BhR=l97R z#|WReY>feTqBpbrwznjL_&;20Fb+hUPnq^*AnUqt z8aW6=4xCs}d4%C>_Vvsm=f=+k(Kk{CtaG5P|@PB`UWMXYkU!D()b>HXITvj= z=tIoP{s^=eI^BzK58nARuI~bIkMtB?BTLAW*SQzey&&!{L(cS(iAQ@~o+67l$&)Y+ zf-V#6DS);67!yhn*7jrFi6QPikn{?L5?&IEsPGSM%-}wx!~F-CPf6&1LB9&g_qiV; z_Z7&#|A5{@iO+38w2M*JoSO_14Y=O{`3cuGa(yHCw7inv)cylF=WkGeeH0K^-o&mD zKA!c+9v{{o$Tv|(AiNPY8T2S<5ok4N9q5-1@%IYQlb~6k>p^`{E;j&+9J@E&C*?Hk0)x-IK)zXkK(M#%bq{6TJ_2d|myMW~O+xxJPtjK&fFo9!Op z_FqPwa|70<{!uU&B+8TR+HWZs_N|?Xf4b$v#O+h}2a_K^7|dQ)70i43a4>&ubufRO zp|#bCd26Z@b5|b@W-YG@e)`10VB(Ym$;-#@N%R}M!*ASp3kC(OEFcG2U-uHy9RspO zNIZApRSVMd3b}mV{>&DJ@76rQYupF#d${vk;C==Cy$nV+_(cuM!Sh?m@%QZwrY||< z0i!qnnIqo3bw~Vp>s2uSnQDKYkU|(zfb#(N{dwyQt*`dyu2aEW$Yt^q2b1G&-jnFm zZCg;l@o}*U&J`i81P?txM3Q#6(A*pO;I9#x8-eGe-Qz!kdiDlXxS@A^7rn6Pk3s(- z+mkUZn;HrAMJLb33lkij2-ljUkRmviJ#`~)$;QAF2iF4++I7v+RL%wy)41iW-M2>SLn zz^XR*O`B~F#@@0wn6vt@KX)x{oc1obHJ@oRsDaB+;I&jZ$Da_txlqq9jK!bDk)kgf>v;Jcc+dZ%->Pjz z;s^Kd_vfuSLLVjh8@5jT#11@1yaj1`1K%mzw>q`B)9|J1+ts~3Cw`Cz|DkQ+*=le0 z>Z;@=SAQ536Pji`%g!u7;mvO+K`l}EMcE4H6t^Q399za^udzi;-=ufbw`1PnS%EFa_ z;|pE<8gJ@d!E1D$b{^UZo<-h6Ia2M-S#!AX((iv56cj)PwqEI@A43EH7#dX^e%=c6 zzsApp@4>sMcR=?IiYxqU?$}T4@N-h0gKyfqu~G0L=S~>Mx4o7De<&lyCC^&hiIIuU z15$o!{{tU1YgJXjh;u4Y4@0>?8Th{kbq1F3h_ip<5A}~9_}LLJ@SD-7$6{}+Z(p{@ zoAb0_*ZE!NXJTevWch9Kq}XO?rxx$Snt%O_e%IAI&vjfTUc4uH%(WS7R}$AVuqV`? zLK)zFu>;6p^1mHQmUZ6_ecF7m99Y8oav_mYyAk5113VB=6iV4D=WbOb^y##2*0?>f0p?_|It?Tt`VfSo*Zcdyzy3(8d z^kL}6lx-f4*G>^@uF(F}iNnN4{Ll&HFn!6v#3`q41uw`4Vgpwb zpd7du#h=6kOdObpE#Jv24~m;?@$Z=PnKxHt%5pBPtV`n`e9#7xrw>$U6DZWhfZK#P z=QXr{m3RH!20wKN8^(KL|C9;kIBl`=+MNR2d_T}}hz$7~_#2g|;I1J6_?#{KX^^cb*>zNG6# zG{2IL(Vt11zB`!kfJzoO)Vd@7kf=}CK<`1&9rCZEf5wUSu<{qer;YoO!k*9?KV<-& zyA+K0jzWD0a!c7E?JreU@LcnyZD8raei z2qsQDAiAgD)A)_tQ+6vl4ATQob5^P3z~Ktx7aF_h(ddr&A)DZbQxK2Vz1II)}YVWR+;tIAR09&OA*TGFk<5 z)`UL5>OQ#nK@}7rkBH8R-N7FODsjO$<;}%=v=yW$_+89$Zsb7xPy&$6I6f}IjUrC9oy&|if;#;zE@2fF_Pc8Yd}uFr>d7R8^YOYk*!wMq^irF|al zz_kJ3_nNg-{(XSlG|sf zWaFmdvr_mW3;M+b=;CYSUFb*!sLup`d0s1FX`AV zKa_$0vu6$$3?Ea8xY1uE?z> zO}wiLMw|y50M@7t&<~WBsbK07g2cydx;|O9LeK|HwE6)&jEPhu8 zsGCiitrE?e8$6JM)hFeVEL8q=cR@G*u7dj?Q;Ak>#m>Q(ejd-Tya@Q*xZ5C4ljo7H z*dxVi5Wbl<>rlzB`~zr@L_lWkjOf|6h0PL3(UcN*)z^ zvvRe#!%(Li`UXB{d4vrl&%F?J8B>mje!%Fp|1i;gaLEl)UVt4f1IYvWJkhO>@}@4* zzRt#L!YKY!KCrStNd2Q+P5$(;gI-}h_MdZp#J_=}WxxXJ-b)m04kpd|Z{nB!N{h3W z_(cZP&%Fy&vJG@=@&FRm{)`wKHdetu|8LZ1JB!YxejwP5?hEUw;F?LH4#M(|_=O*X zQ~a+vXPW$NZG*lA?S$maB~@O7MsFGXt0MR*3$Dp7_X>(Cf(IY_gn1wRJ5D|~c4g(^ zhBhtBletIVMn8}||8j+IT!{S>KX?$5I&tGh@Q1JuT%-Qw9&I1z$GD=h$1hSJdG3#I!}9Y5Y3Rg?ZoDb`2j8@}Od)ajnZXcr}zarPZ*1i+@cFe(p)I(JQE5k$mv6&!96s_F~c& z8*uTM^UV0cAJV4}5c@?KeE)|kDAes^t!D?jl>_i6`VFbI?p^%RI$2`jdQ~`WQMFgx z7<+Sy{yApm>Yu+@CZf+Q`C9VMnFqjg8h(){Fxb%Q+TyprQ=!$5;}fPYRzcTZM&I;< zDPKrCkbJ#GYNdb|qbHQRxa z_*!u7o%=8*7vg8!X=RU*gT-#c6c2HE2ELR&`^q0EKbcI~yw$r2gYJFd^VwIHj@$m$ zX~g5#&se$9{)DOn)PD+o{P024xtRA^$B)~8;VKA(WA2+#|Jxm~-Gi}L!46(%%a(_1 zVXO?O2gJcXBJZyG=+9~q`hDu1B>n*PB>()YvgZ3#{H_dK-cr{set+S!)ddqC_$&HT z>|E9v&^0ZvMk;HM3s3=AsJ z|Fzlc)@OTSHu3=L!4CdxI>RAKdrI ze<$ltZkri2K*O72;1--x6NKry)D75==Ya$ko~`@yzS!4bzTGitfeX$;@5c} zJZXP`zORJEZ}N;AzvY3~c<$b_<)QI;xW{||-7kFh3;&|4--B;A*!`a)y=d9TD&kT=@Fh3F5ctNv5E z5#A{4DT_Y!+n%%?#?CU2y8lOQ=s45%UNh7}7=qgs;Ax{*uYQF;{>L99PSgi$_(J*x z@eSEzAo`d52%b;}_Dj*9C)iWk8e<9JP*qb>|;-b(qc zPE;P+$Nq$=+Q*o8bK^HM(6Ts!G4X1D>Y`7)&Rw^o?-_l4qWh)5&3ui+U}WGDesa*? zIe7O@{Gkl~;3bpg!MNM^L5EfbhEL;jv`o@vU@;&}>#xHYaDYC|#8k8g*(RJ0o0tK= zfU&c1Tu)@CeTk8?r02>*=PSxq=!o|)#;NA4IOq>MXD5cTft`B}1sKQoOGDfM16xbS zPNX=DfI_k067mb?tZNx;@R~KRNZdRb<$-P+x?`Az7n>LKn2G2l>3 zPw!wBc}3p0*u(s1o+o~3pUzu#$iLue{y3;@><4bwkrf zK%eOFW6ncc!0$9Uh^{P-T%i+hXcOTz^eqzm>}3ao3&&Ra^$LMm)*EaBcFzB6>R<>@ z3z-85&>`lDEcp54d7$6n)mIR1@S3--@W%dVpEnD21K5BWFQDC9O6fa=QRI*ff3|zZ zzA$dgK2@aoFw&a*ggzWYNEgOk(JRAM7jMK*DPqy;C}E^RB*qpEqN9mCQlY zeAdJp%0Oh~=8rHhr{yEcz+#p>gtq*G^@qJ_i$C?hcf)S4q{UX)Ez5U~Yx6s7>kyV& zW+im-n;V6MVXvDIV==OzfsY^_xosYg3tHgf0zCA4hN-4n$0U!1K7nh>YfP2_E_l9+$b6xW3UVD%|XK@BfxJ?zVlw9e5&Lhj6(+xKK@B?)vX_RoqGd!68#gRbJpqM>rqej{hl}n z8%C`-kstU?5L%nOs}FYc@V`sczpej>{>>v2Q5czxAuVi$U(*oRA0RaP9SyeeqAXZ$2vIgDORzQub=WWbxobX8On&N<#Q2}= z^#@`Mp>(@9>cUFzno0W-x6k<``QQ@=(LVb; zId$>piHDv#l(_HF&oHlie{jXkd%PiM@ABGp*zOg|H>3nJSircQ&o%g*+dl^JhVxnR z8$9M_Ql7(~tm|LoAtx@LW+EG*4YBYKqB{d~{Z=7lf5S#x*@*dU2q6c~zezxUMFrcu z;s!gsMvZo%uk~Hbt=@(HChV(`fS!RfK%Jd?O)&Ij-9Fo{^tD%jiqiSP6&kM)p0Xh> zr9Wpylu&>-ke_`4iZ0Ac;K)NZgB{P(r;AK@yV)fxkZZxB~MI zG0x8K2%}Dd?|1=|rX=!j0wi^G`mpevwoS~mcj(&R23vd){gKyU|A8i{eGss|QT7}_ zZ#6<6X+p+^u-5?iBafDNPlOR8lQi7se!T))CicM)w*v zYNVw?r=0%JUjHUMM!JCfP2qvg7!@?+Le|@c>vOX3kLJ7{&Vv$s%yJ@5j^=>JX1v4k zuLGN9!;dDiZ{Gbz0(nWGA)aDA=}yp-AdK(HUO%P}R`)@hYrMy2_$=SyyZ=8P%*xk* z{tsw3s2UW-o9j2<;k%53aWT&0{b1X4E{J+0Rf5uRwr<^8_C4eGbX?+IXuKDF-o#z{hmUSDQAa+k5ml^FY6Tqg3C%BSQ?huf}0-_b z<6@kQn`zke5HfA@@b@@IBhgnt<;|O)xb?Kt27f{xEuD56=8!oU_-(6vkF{&pDtxyy ze4laIco{M+rpdGo59H-o9juPr3o0)tsJEH6>GEdJyLSDDkbiuKR!yHiJzw~W__6PJ z_&(#Z@wy??CJ*EV^kWct`znw6h@Aeoyh)SRTYB~!`Zt#!_qT%_$-O*eAHw__0T5_&(!`OC#=@BLnjMW%vW}b@|x5%Mbi>$p65B z1M7spWy_Wl2jg<%&UPNj3-pOcbBxBz;_LFUdCM1`4YA{WHFD(0I^k!XknfWir;R^X zNM0SENDOs3=tEv%`I61g$MNEpefajW>}_4!{7?Ju-Me>)KT{bPmp-qNXHzz^j#3wY zDD}Eb<$I=dsVCG~L%!+zh@~$02M-<`#=|&kV*})myaPM^A+glvVBK2kb(wI+T_=C7 zTAdv7T)%#O+3UQn__;4~adB}NALFjA44ismZQ2XcmWkGB<1pK6y?c*H!w&g{-(Mtt zwtMnDmMmE!HpBP&_U#+uXPlG))3WL23W`i&lb%hNVNp>-;qBB@ zd#mo<2jSV_X)QV>7b9;56Ub4l9Ce93BPCI@^~!HnI`RlJmkx-!IsGn zHpKn^$`)6fHf>bbu3aU6vwe0faORT+d0`uayphLj7z{>YjUFR#A$-^BmihU3!kUjf z_zZa_?-^`_Sfj^1xFGmhcG2JDo7VCE9I?3lNx(_0(PJep2>!1VcB>m=viJ}ZYxF3` z1$FYtCx319Zn4{t*oYBCtkGj1F36aKeg5kfvMol8pua@Ho9n1E^!wWCpV%zUO!)0L z}#9~sA(=6zza{mH-? zSN^?fWTS=;AO5wI0Wn#82%iT(_4*&RfCn8ZvONQNDu)4Laujg87-4U0$-C z15-8bzXu}5Oo#M;aonI+uVJc7m(yh&m;H>%WPG0=A`%|(?MKD&ilfzMuJ_rlcgMwS z`fgZQ*g&;tagy-PxdZGUb;d%n^?$|of7-rK&ck8fBKyD`a`LVV?b;h*r;E%hmtQcsVbpUAwL|`ef;A*-u$pjG3ONPmAiD z{lH8+IycDTb;DZpe-l5)2xN|j8%MTt=9Lpp=$MulDFgbZOqlY$&Nw1vm!!osv+?BK ztL69yWxzht^4g4lF+Gk&;JuJKckX=D_!);AC)04TWkM`gkGS(T(Ycu~1M|bAcaWwKBaG3ueo&rV#$R!df&yLtIUtsJbA3kd@o|mbx@{~7iu&AnQg=;~8@H1^Q$B+Ga$724wl>tMRjTzTJ zxcIsEb`)Q(-|jq@D1O#yoq8R4ATNK9gZWJAi3p{PwC%YQnRRe2pz=Y6of!1e`slKE4#P4t;m_JIK>qkjMeyPEeSS-E*#qVIFtM>7RGsdE1GPaUYMk!B*PAekArY zv8>ny3he;rF1BpvU*K6{XB>xD|U*|9LNACOymFotf^L`TwVRp6=IszkAO)?>XnL z^;Kmghr>B>e*j0JQ2KZP{d*Ao5C0xPs7C+W%aNOWv zzuDQteTAKuE7kv@Kc&9bTRB@fg9Z#5z~ON?5oZqksGz@p;y?ZHkIaKZM-J{yrN4j0 zO?xRDsoal$`TYN@`S=(7k{gAGj;!9xN$nLJIx^$ORCJOA}Q;qT_j_2lrv>au98jx^eCsMlVAGs<$u~A z{d!@?)|1JNeQ|B<=WDC_<23uN$z8(zUSTHSKh2)I7`*RT-k1a4VJ40^`r=&eH{-|sUR$d> zY!OciJL7w@Ay^P*;-Gc1ODw3LGf0Enz<1CJQ6;{DNg5&`e~^Y9_xIRe|64mL`VnpN zmxGpard$etDC-nRp@-5y2E9rudRcvWp`y$3LOU(J-_F0cANu3{d)_A-0&FuV&cs)E z$1-##uzpUlI?#ytp6nI!i`5r#PxcD<#GcYYAy|aPocJ@OdWJ>PKBRiIQ_fUK(NCG` zAxF7L#)n+uFM}Q+^a9}rWu5fD@B{MU@5R60pDfQC8w2{{?H2(3)g1x0EY9SI#F_Ex z)g8e?#<#;(KvCv2bcu!FM`^&Ptp=x8p%4QVzm>&&KC74ih6=fqm5US#Ir>r9`H-WN zDVH&S=w(TvM@6qd#`1!A!wwa_^sK&o(53j({F(Om%LBx{VSRul=Ao0-etyWm>JC3! z!~y(&&ztD8_%?I}3&FFC7SukzLdHu#JUJZf?C|#=Bafa)|HU6N)-RLfz#lr91&r@K^c%XMX6fhd<=9f60r#@PmqORu=34f7qp>o1O(bRdo9DKqoz& z=EwiqI>F|-hV|>=&uWVMe&jo$^HK*b0$Yqr0$b!~$8YiOCE~F6@4>sDe?R_(A83F; zuO8(C?9e4Yz%B}V7VK1^f52`an-_JowEk!wVBbPKX?}NerF{46i@Tp+{hV4JRkOH* zKjI#2D-`d7!HcyJWzMWSeJ>npMzGsI)SxFmB%5pg+2ZA+TSw1|GR#HzW(`v(m)P7z#DcE z>cdV#UD&NM4E$&OsFj|c-p}i=`JulZ*15IByOs89#M2M^6Cl=UAp8%6J!0WMKJNe4 zZ_q23_2mKh!Y&}}1j240cnlo|Km2X;Kz}>*13%2GKC=l>i3E%Y3A@W}yx&ECKFbEcOZfH)Wu zzkdw>jcflk|3m-N{1*s?;=eb)za|g-r7{`YCn3K7n*X8yF8_6PhYkIs@yEPD)8Bf` zBYu7xtA9WLSyS9G@7Vqpf3ZT*%i_W3|HJd(H$76IT%jm}9a+R5^PfK9@8>__tpXl2 z{b~KJ{QJiT)(xUpjdjG&ucI343d&e#z#ID>Tlxp`Paw1nhCKqI_#d7J&?A+}S=^^@up0;-T3R~F ze=G0)cK!K+{0R;SXu!TQz^{Sjfo*_a^^G|->hQyFc~Ff!5C(TO^x5~@c=$W`;V*eB zil9eUkg4-q+*Ppu%}Uo(LEN*_byZ+zx~>k(`-1dzoqituy#6S{f0hC38-B}!lUNUI zX*}TEVH?mzqRe&}NM3&m_-OvjqPIzO=Xd_EiBe3`uO{G?E1{+_o~ zR-gyH1S+rt{9#uX<@;}OSBBjQ=~_B}l<&W7-|s&*A2v27Y)z#n!|_?6gvmzJj8Z}-3D^It8)pN+{Ux3Yav^;Qn_fj9IEgBwmF z4nX815aW_B>^+G*dqU$8%rAX!U`K7lHrA2*6^=KzmWGb_M(jsLhM^>Z^ zxv~NsW#|DyFA#RX4{V>KqQrrnY1+z4|5cvN8G3XK^i>H^vl7UP;TpW2|+(WkaG|0TCMNx8&CDO;^wl? z1-}2*;d+?KAdR6zhwe$UtWj@l5Cs_1xuvvMvLehRbY^goe%vIIXKiX~hW=i*2|Ixb zGyTtjo-ylh0}Gwpc#z9q=2(dG72_ZBfo_XUmqWkYt5u5UcPoz11Hbw^cY0x;t-*-7 zHH5)tQ{JPjBfnS(`y0Eyb4Ph!_Js4``?#qn75-6N`X+c1m3OvDgRoy|E*u+^G{=rx zu}0h+ug>Gtmrfsy=Zh?)anMuve(t`+nHt=xs_QI1?a_}|9PC6J*uDKNp~cc^_JcIi zQ<`*)mk!c+5)hEgT`W-@GJR?Y;-94^Y>X#69$TArONftDcSwV zMVBkc&l=pI&XNxFUwc}k1NEh`m9x=aZsYF(IgK&?MroELY2v(dIImioTj!=yKJ4kr zk4ZJTF~>N3M~;IQhg15@n&p4=D~p99@LKI;gZ?oQd=toDajdfl<*Ui2-Snw4$D7YL ztfBfx`>3Cm>%#Xy7bD6WLs-5$SrA^I)*vfK)I;Eli;@f#j;Wd-8bQ{~HVSY*fD zA}+TXq!FTO*}f_hc0Z4N6|AYn|sE9cCvbl}Kw6>Y#N-eOyzuT5-3s>vjcD+3N5cV1eoKL7HYr#EO{pP0y1Li{Hb zXH=k_4_{D-aXZS$bdnumWUZ+->K&sGZ-)JM<5j9q_CEM|DDb>d;xUP;2KV!#<9|Xq zz3kanz;;7E%Y%yOeJSzOuQYrJ^NYN0;AGgFIlh4IPc*nT8_%o<{_4yMn=Yk9{Xy}iI)T&#Yvtv!Rg*hp*9H&vHXY5YfMPHBN%(#p;LBI?)a zZSIEtE3$+o^tJ%U#%8=6fMRs1|4{-h`RdsB2Hxn=*;?TAP|NUeX{RbM!ZS!e{5g)K^In z$cLyk)%{?VEsUo_aLD@i_T>Gjlwbl`FT2sJKGrw>CU!Cb+ zjq<}X&!JNn)46{&-y8i++of#2UuV2}2;`(^j!pm%hZs{M@YeX^c$)Fj5Y^;dzb8`{ z57MyA+rArq$r_&(1$~dVZ1zEW{@}4{z$3@`S&*Zl{q5RW@`rWX4%n7IqLu1_L$~Z z4#&*2N`Nw7ST+>)-gY=N8^~XMW#Py6Mq1B1V~!#Jb9Zgoi1$3^Weg8XrE{)9)~NC5 zS7L1|hQ6{XHd0`=?G84MitK|GC_jIx9ciSc!KKf^9;4ir70&YXSee5jlpnf(Jy=5M z_>nTLlRVCZ%&d0Q`)oR53cEde%8J|aS&BTnVu zZ_0njPqTE;nyGb4e#g>|+tt-;HI@8Om-Y0li%H_lrk$-a^f!N8Qp)^gx7q@@%2~P^ zc+ltDBd|lZ+CqT1^>&|U{@MN_hVos5+Z1(|%|9upeX8)>EK8>fc9aLNn-W6zQ_w#g>cCe1@a zfo~$_5vf7={z0^URG4lSquuapAMEE86)# zEisBG+{C|U%U!I&HRbc!K9O&h!s5I3^CNWo!sEoQd0K~dg2%okuw%mX5=#l;{^zT! zQ1*$4Fh|}ladPy9zQ8$d`jD^vU^<&O0$%5&qMQ=lO!F|0^Q=;819{pvP7X%?FaNmy zBlP>+Iq?|e1%@6nL3xR^)D-2hPkqmT4}YK0VXXI8UZtmS7l)|21iZP*rSX4HF{&tx z*8gjfr3$q7u&jzi`^%8QEa6(|Qj2*`n4E9=Gfuzx_EshS<(Ht$*o zlvkSzb|C(5&keNGq7}sD{+?vagVV$ICOT7nQRJUfpx-P~V2buZ&FZd2Duee z64`v$)3rMS`d&5*#{(nMJ!<0#dlZ^%Jj|YQ{0QpH#G3CV)bA7jvtOWWU*%l^Ip6r+ zo}D<8?&UtTf4bOuduCe{3OWhDQ z4_0gpbd*qiS;Y=t*n9Qd%IJ9NFE9<}1KoU$n!(4xbh0VN`}1wP2Zqs3?nz}{7xr&E zrzmrg|GTd?cA(u6Gm|f&C(D<=Q$)zW7-a+W42o2PU3{tH7M{;>XtzK-?sz{nLqEZo zSGK5k{#uvW>oBCz0PQaobJxNy&&ri|U{ARgpZO_HZ*C2qU-$5pNvNOEj=8M9PR-tl z>G9S~FkVXMDI-3~<8e+Mw7XbC?VX>TutNKiUyY?A!YkI!EWeHmJlKBhshZ1W)R#Sr zx&m}dNMZBWSiY&DF|9nsr)t(9?oT?BIXM3u+2~U0ytI~={F7%OcqFY`|2;58W5+@C z_g;No7D8xx%#HDk`O^;hrFS=N8bP+ zX=`P(c~Y&8$w@Iw&c}GM+$^quzxkj1_yc%+_T6e5;Oa%A2TxsG%RBr$We&!>Sy!XE z^HRr|74^;(|61PSrMH)hNUrHavmmtRN30qZPx^yO`A*QscNx|Uj5%Y=@+#IM_EQO! zFPp!720Nb$@}@)oH+N%#P!14kvhh)VE#)}m{P5U@#YHVBMH6z`Lt0CK=hjVT=VRMz zL)d)hTQI2s^~>%(D2Du?q(7|$N)L3Nf}FR9>e+ttaY^SI5!Jhvcu&Q1@vUaI?#gmr zZV91sxNa1iC-{NKwZKQZ+h?6K#r@R8iScQcgEY3RZwbbI{_ga&#yfN_T70!pgnlM@ zR?d*;eeBmr5uwo%p#}8%23;DD=ZPoI+(UU>jkyqL`|gz?aAfnIUOeZ0QdEVqfz*TT zb8f78We<7gg)7hDd48aK0?MUkr8Ga%emJnlqlVfy#)ix@x?PRH2$xo(mBAzHE=EVy-2e* zH}-uzISM;56$0<8>9O(a;2p@;%h}c?tD%2~N^c?b)^}_xj3;bf87)V9*UR72 zIfcjhp;9VG{$>}+WUzlhdl%bBW?V90<K|A210Vf&jQ_9$=ZGib z8?1fsTB~^Iu`1;5MZ7!4816|y z#DOVEwEs)xmg@-Ad?@MAbCimH6_C3(ix}B|Cy=o;g36;Gzzhw7K7Nv?F zJdaFs9|q(btfFkAdF$HkCbS!sUdi@{rTX?{|GM>tfWyXw$S;-~VKPl+375GG& zu3m)oFGYhUmBsY_?CYLFEn5FK2j^_TK44myn*jRS&O`@`2>th!vh#kVc8LS>;q!_Z z2Ry$yB_;yct`)_`OYD%ybrLGeTGYybGsPuIz}rDLmfV;_=h`4CALS_p-#1Sp`=1Gf zfw1#R`k^M+f6O2|9Oc;~t)5~&9GZ5!nY)m}$vqs2E6dbZve>cf<>|DwH=z7@fwsS7+#ec((s}YHNw} zz{$pRZ_ERMuQQ*coy`FU(-4y5JT8X$?MKbK`XVYj?67fw{Gcp;bPefqZ1z>49IcRe z;Q3`WtMU-StRv19u*+b9Z3OJ!MMe>sceN!ugZS z(QbQ-*<EdNfz^g!yE?Pg+77LuD<;#Urh=<15N9uqP^p4?;r`GgYUApHPlYK z$p03~MXkQ(D4%xsXZia6M5kvlm4)rnY2b5eY?~?MYAPRXuA%tuvC1icKH=teQ?x7g z@~DKLtSue}qJC3pA_wgfdT%vCZjM&56!n6mE^OQz8S|~}$ga;EBG~?S5`XI_Qy0gp zUpkex9p}iC0UMtr(kC49C+c+nIZ2~b_G6-m`X6zKVCUx55vx9dyrNj&{XiT2atG+S z@2zNtokDy0CDb3O;4hF67L4tR5fKWO9`(cXa#;}FYjHSrcZbQ)ZjGa?6*%57x)aY! z-X*Z{FkMkkdBWj*h_Y!bCUkC(+l+SgMh#juRL+qZu=pp+KPtg<4{OUi$p1MrkSmmy`BX7+b=b+ekHp$N1#6WF5eMY!4EZxr{}$8&)Gbhlj`~q@fvENWh$ZPxi<0$ z@F}RY+zhK8um_!NYs>BRim)sW;=b z-Ns@g#{c{GZ?$m#T)S~?!yU`Snbw<|==`5Fr|QnqduVTT#GK`gsg>^)l%pPe%ksdn zPQF+~c0G1AO@#etDmE!#*Ils<(`PKTQA7K|{wCM(JW=qV4Y<&H&LrRtqZOB6r$dEQ zjB-|~J-e@OHOv08rt>@d<)BJpXk?jB^c@ z%koQB;CZva`M!keBZ5tBpx^hIkdJoOHtp;@Ae4?``vR##joI_elFy!dMattLM;t6) z0(~W>wcnz@noUFw(Ba~$bkqyVzWomS0AEGiUAFG85ZYiI_%;pmhg}8FZGvFOhn7{- z?Wp}@qfgnr|3N!{BlJI3vO{3JbP+dRODhfjK6x_N9p|32I_j~@Yd3{9~_yC{d#Mkv2k=$64Ai+1Bzz(B#! zVAS({MzxCw4_obWKzUTo<)y#_wOw?d$>TWkgN$K+m0AV!bI^tPY+krlc3KnTJf*{G zHtrEW?iig0oi?m1z_{|ZQLy|!mM}gS^|Ij5F~EvfOFSfmLnpo+0W@|lD23gZ zeR|IWujyZ6d8U1N59zeHS8Q@Q0aXb@!_vlcW9s+>iGCury3UVI4MmM7+ous6D()EjY@s5hG0a38?S9I%j4L$q#tCU?lYT%J}RYVx9e5Tc2C{wSNW;`I*e^D^Ox3~jRUSuOpfAvID)YDY@S`sv zzueT_9sG^5Mnz!XH&07<`_#pnS^{5JrGNMS0k!M@Z3OGz{!PG9`h-3Qv)k<8>1?;* z8*fi94^OK6qkjYX-a1~$S=jgc2M|o(!&A`TKkMht z*w^_@%FD!k%=`N*Bd|X_F3V5>>NGj|K+nWq_k0dKvd^#<`&WhRx&V5$HPl9-zSibt z0MIkxDxL3noX*&Vnb_BfL**5NNUovp{jso1>E+YK~uR8-C7jLI| zL;va98dlJgV598_o?YA`Q^>bU?Pm8-*;^(0(EqTkjh#Q8w?r}QTwEFqJ6&y(W5I9J zk-To`yFP|X_mVu$tS^psqU^Nqh&M37a+jlsu#7t)Nx)s@@D z&}-KwErlOScfOl~vad7$KI~{Vh%7|BdlR0wp}xJO^H4mMJA6FY`F-P_<71(B?v}-c zKvjb>wqI{|oEe02;;Yc3XurUIo)mWV9-MIv^0E|GCvlG|yv8q`L~?)A9fM@!=oB;hziVirD>`+g2-fFHxX! zc$S3p8%~oGe;y~}^g?>SXA!s4UM|M_rry$Yb{}WCz@-ZQlnn`_{G@xZeo$_U7Hk9j4D zr}8YlyUT!UpIOX6|2lmI>7$>I*(lM59YsnDI$2z#f${Lm%dmIl=+706d{IMaGFf*E z`lr{9qR&alKdD=6pzphjW7vIWjO3_ZF+G=SmsOx$?3@&R@X>u-T7>c#+c(6M{HWDR z{CS*)-c3`%XGpYjYmK@{)n!rBe8hhZeL#(NBMYZigZG2#20X_GN-1*>@zAeEY{_P%_%=C6%ZJG(Y4bm0k z;qSILE_2Z@d%y2KX7{th*@*W~XLg-{-%gYruR(e6#n|KEm-Dn^8tUD0o~OkVn$$=8 zqnx@=i|pWVrseFO0e|r>Dp8&h&(gIc0ZAKB@^p0JZg zx1yiFG-*5hp7QRDJ@}oloKXrtBpV!G4t@K7;nO<6fhx8qKpUS$?a@0}M~7qR=;%z=1MpU1hD z@`Un|F)x51~tfA(UZ7r^W$yG7u+K32f|;q<_p-MhRv_hKA)9`UrP6??ZlWV)JooymbTkpRqmt6`t>Z(dG&}T{i7!?4edrGAhc;EAGW_l~~CpXdDGoI{vo;RL-t~0pXN)EnfbCVCE zKD_rOyGJ|e!=w1~IIeGRN8%|5NEf~r=~KV19QjvODtn7@D;0av{6p{iHNC8cf227r zO7Me~fhVntv_A^=K8kiF0zE(QRo>hofu7c|kE`JCFW(i`qg}Ak5?|Pn`__tm9+d5L zzzue$Xcx3$9Pi`H6yWoyrKlY8r}CRak!LGEbO$1?13Qt7w#ky-uaZwtYN8ulzG5o}`j4Jf zV)t*A*?PUuBX3ow&mC$1Fr(27=-2}djQw^iM1%#|UVb5jdI1N*f%Q?3J%Mk0npJ>jr`PDiZk=+&&w=$J zx-Gar{8%Axf!`ZkZ07-^z0xv}2bq@N(7e2eJG`Ju8*$IODeM`fLHmfPw)e0%uu5?e z&%LaoTjL35j*EN(JtlXyXnbscV~)nq0a_p1hnd8!4U8`)xuw^ZS4!x)V8oLN@cYV0 zd7=ZYubREC>ELt5Dy9l@yWhW%L61sZUYaJgOUtjRs3Ci|n3b?~IK8q|0)H=ca#;g; zjc4M%0G|laha8mEoHb~j<#D?5t>R%%_0Q=;;D7mu=yb>x&o)d!JfAN(!DLvIP@VSe*? zdHo0Eqi563ub}T+qYX=4QDLk{I3~{O3S7i-(HiAk$=$)@T zvL0orxTg#HzjqwY_TBbz9!)4GCO7^H+~u43J>q3_cisxvF+SgFD&#f>JPL&WGtJu8 zVBU2qZyXH1HsYOGn3swQ!YE&9pQwL(DE##5Y%t|1?H^v~M#7ISXRO#hA~kmRw-|?< zpYH7V*gkaT(A@)65dV9!Z`r<1aJ2VF*k8zV(}f>L7e<|i+&hK!Ly-@*HU;-!hj-MA zyu_Iys#by>6!%5kwIyqVVP};~E4@`t?{}nF<^qczDvIF$Rm!8-{l9S4_lKc>#M&ky z_&S~XC=9rAyLBGo)m~{ye&ca2pDc@p{RKA$&4&F=yNlu>KX;Yl3*ZEu_Ghq5oGT5$ zcrz{H(>h_tZ8OvEnM8U+j3qr1lG`1&faZT{w>Etw^w#+9kA*(<=Pr%YDF0JZPMre( zoU~u|!0(Zrm&tD0Z?$&Oy05{F8PiU3Y28X~$YAx2S-!x$4DlxT;c>cLjPHk7b+ziD zzD2Fq9rlHHd$Rlf&Ar_dp-<(`_v685c7BEiuu(2F7g7CC{V+$Aua1t(2k*2z53^bd z&mKi*0rXz-xI+0%`Ey~a!6XfOPpYo`A@aRTEG9ebxEtG3Rp5uc-7=adDNp&O^nDZB z4=mz$pkLV2OR3-$D4N4QXZ`W(?FO*ttK9C%_};@d4UGXG+dD7a;JP*p?H^<;2u6Q? zckEci`%Zd0A9^(1%eo-vX^Q1WBU)#}oq9JRUP+=>cHTUkQ7{m^qkH7+qO2Q)zwU zi$CE*Z|8!G0>ouwi(hXokMgM3VsQ=G6?;o18}=FeaybQdY}U?q0qWLH%)mHGvAk6tqSL^| z+1Wb4w>#t6dF0GH#WC=%kQH2jzs}5!wgk^K^D4IfPwB9w`Igond)JlVdAaaL3(jXo zSvK8R2bZfXtAt%?$y&XkG%pAg=C+ViJ^oY?+E-LA9E)*S5nOByz0EUn6;1TJ0H-vz zZNvPx`TDnMh)a`WXa@Qn2v*Q~!s9G3=_VZZZ@Hb-g}83FmNF9KX7l@FKFM@VtqXh>4x;Dg-F^2prR`1I zpvSYOjh**5=G!}=JZ{__T90U*_q;@JpVIrC(os>U-*k1f7=99F$LFBlH|erwEswsx znpVQb@A#kg+=ae_gQepz9+RRk-T~fK>a3VVdGnKB;3e?(JT9FM|4Y1QOhehycOF}B z9w<$udl1@pKYS*}JUBc3T<6F34q5{TZr|2q*{~>8c_H!OaXze4nT|Zncwbb4e3oVI zmLtCJZdcKKL+ka{E-&Ehcj<@plz8KP=*|HyFm&fkv&~D;RhFcQX}}$gZ3cuNLT=9MUWYPVexddnX>} z=At|EA%FXefH!wh z=c2wob%r+lk~iCu_9eJ?*mbLh>PH;cw*rrI30t25Lr3@Jy?;x!7=+x$JTeTA~P5HnYSgq zK|=C=EF6@AJS<)Ij`EAXkJ_?gJL+Ft8es+W&96NH`3bv=R^oZCvfVD=)81=#@UL^! zSN6~o&acXY9{I|r-@)!({@yO=cOg!9KIYp}v7Q?I&22xnv6jc<%O`Kc@7e+xK z*jdtaL4mUCdC!moU5BM4pbm2l-EJ-dzK~mnn)G zU}s3C4(zC(9C-%gEcQl)0DOn!znBlXX~Xgrz>lp9n4L}Y-jjYFC)H0+0X`ewPjmoJ zy$QB#zrB0p0R!ke{o)E+kKMK0N+559M-kmu@i_12Id_3CzbQo%@7s8rRw-(EcHAbd zwuy+7Z|SG>JFIjMdB*S|YP=M>tXEr=cuJ0}kiJq7Q)6djdGBv;4QAZO}g`u(1H`2A`g z$H%_q4)R3F_C1~dI2?(#NPzZ@9~RKMPxsQ}Drp~1?@uKMH$Yxt;m$h9m-ZItBd;^w z1Teei-c@4j@`Zg768POL+lDKQ1p$UfRPG!7|%U1#`;cG!_pIrljF*LznO!Y{{$n5x1b zVh2eG${8VBRG~M9?@#YX^Eg)$4Wi&zMeC?;?0;*TT;yOY&dA@5{XU?p;Uz(lQQqEzItsdnh{B zbhpeyd{ep&q|mcT=5B#;*W8}T&V7g7*19`VS--2L2=X_2`ln)FV58{aqraBr{M+!$ z$)k-d&Jk^jN8ly0Y$^ukel)!l?H0Lr+QOdWZs9v&&oJi(+JDe`b2O9kRf8K}Y9I&i zL(OJ1?=Rw-RH*SVzQjH{QsbqR2mBQR>`Q{)%X?vOy2|D%*u`&uLH7f64)PEF6?&w0 z0n=&-Js~O74*{r<*`tApH4Mtw6C^g5{(|V)m4O#+! zyvT7GjJVWKSzwENs3{SUUeY%|vjg%+S=G3L-{WZY=a3uMQ?IUHPva%-0L0aKdG{{x-{U`m=D$VUk8NB6P&TWKxPtw_W7o>d&^u$-fg<3;1|=8p zoO)aD*GY)?j{2ZMWXIr_*4>jxk5_m(odfASS6TQC^69~OzpwE;sCUKOS{{AB>A~%r z(BE3~$RF5w=_^`|X+OWq<0NpvTdpDeT(P9@d!fhX{v3*W7s2}Nkh@z}QHt~Ydq*(jHVz&W1HXu>9sLo9IN>%r zACRAJT?&?vp7GYh=Y#iGhr1U+Z@ziZB={q6aNjvG*mGW1F_p7^D$GipDN?P_Phj@6 z=ep2%r}=(q&}8f{>NLB*K^};`KM2u&gv#+Hx4wZi|U=}*M+vjx(!`Q=Xx(Iwto>h<;B)eyPV|nbk9%g&8e_b#M!MWp6<jeCHM%qYzPAsEp}))US*L_>O2>m!c%E$B%Jv}( zl}=|OpW2Jw-o*H$j1>TTrcJ4XUmEEL(D7WaGn~qFul{ksR@nRV2CF(2xAPO-;z?e} zZXY?y^Dq5t5@X0|>o_0pV%`fuLpRKgxD#|}DA(0p`fTRiIb z3=f=#_>PhLvvbGX7=9u6m-y#=iG0^fmt{hK%;k%}Orm!06@D&|^V9iD>>N>0^kO>7 zA=5_bhzNK6oZ<0bd;egT{_O9~{`k)@{U`4q9J`m(342VXzh1%nrp1Fk?uFm#dXvW2 z5FdSqVHOzo&Y7o|~n_bWufeIKmIbIO9<(`>FOy1I!wMwz36k&7uSNJzeqpqF#Ouu zl%i#%B~hI`OR+zcKJhYc-RBTGQ=-~4^<5y!zOEN4c930xX+L;C-?J@YR}rrTPtUW@ zS&kHiu7_OHPx$Qq=e=?TyYF#$Dyl+#O4XX7z&6{1Qs^yB^^X9`?*-K(-?Sz-()lYy zb>-Ww>&MLZ(rFEj+`D>twu*DsytviyyKH193q7G1qXdL*j3&Zjb=Z{WNQ z`+%p)-w5!%hDW|qx_63KH~(PKh5GxM@4mx)aIY&^0{&wxXE#9Z#_xXG4V;ygYX&S7 zx_^hfGmkGD1m1!w7xsSf>?sl@=$$ob;C%Su)}HD-e19;jwEda`$uA60F@_#LMejT0 z!}|sgntv8?H;l-;jPbs|+LiVf8eDJl)F=t*8RgsB34I@i9=8PwZ0@K5b(#eBcdC31c6-Ca_A382$Iq<7(l9|YN<4aB#!FSK@DGjh=#N_>|h|~7o z84Od3)_0+QLzB&Z$Y1~N#Yd3))4ju6Q1+fd{YlUh8!~6#4Jx?Ggw6=Pu^fK;DVZ zru&Nsvp-HSMtOtI#3{hhj%n|K`y9R5IiRH3kL`oZ9>%e8a_gr+W5gp}Z_g$0&^h%J z@wDTnhR^!~dcV4#GTwpsuK%WI9?EkHb}Io-t67gmo_d&dZ86dksV)%&()z5yebRFI zHS`U?J(2nQ`}!(5$}dKTM*-!U>L(?HQ?5JHI;p|+shf2QeA3G*v?0&fd2%KEt#ip> zD)jJ_lnfjQ@6Ag61b%<+IouWgvOT<2g#P9ywov?OUwMA6lZfzVH#g>o;8G;M*os#G$;~XY?!Z_L0t^bylQ0=Tg`(#DBhqf9hS=-no3_n+N?O ze_baQ(r$D_Gc8Q z(|jAES}`=D2>io~?-yX)?a$FJ!1qXHzQe61(I**KT~&u;{t*hi)ii0n2=3{2hu%}- z$73MZA}4n}@To<_SP`M=psH+?9W6~*zW>=Jg7TB{p~bfX^~Wk@nZO+f6U-2Yk>SVb z{A38ReuX;x+{NdEKo9-iM9K6!54yApVeN8(X$5w^W z4p^x4~AC!#(9D;#D!?ni1+8j<2#}EtKp`u_&jpC*V3=!iGTh^eC|j*_Mc|o!-*e*G|zqD0@H ziE{{tp02K66D5QmsX75D%L_8tx-%enC+!0lai?p^$06?b<&&EswhB?0I2XL-zo7+}Db*mlBfKwpZZ=yl4}80rK6elnj6g_f5Khny0=_ zMc&s~y&VgjHeLG*%zp!J^B&N52(R+_9w3)#)#d?qAL65A~}84(vp` zo28{AFpp}!Rno!dJI{CRasjU!`Cr|GJ?Y{$4dkP<;$1#?HxE>+#q;rB7prIzTHg$) zM*cht5mkdv(g~{$$h|#cIE(ko4drb9iMA_MM*ZM5x?#ZO2RBEcU&57%`@#Rxs|9lz zZ&qI!S(m8;8;GQBK+mzZ84|*VL_W)h{d1*b!Cxn>PJ57sM0M!Qy8GaFx_M6~qmJzUSPUi-4u^*-?m3 zYT@Bauv0qY-3ahswI#d-`7dboItxG8m&lUhY5Y%bldOWCfb{E*z@lGLrU57Ke#!&R zFdu&n_Upf!|2g=a8Df{^{Kwy&3s<hy3pSwvL41wlUgR|9p*v!SI*MfkkeZXY(r?_d;%-bt2pMx<e+vQ08l>r!4Eauzq_J3sCPa3u zmr(ghVGz5Id7Cw1ImWL-L+t_he11WchkTkeT>p0H%vxTEwCgSSyDt5_1NvUg>!$k| z%FDG|_d?(8Sz9ZiZ_k?;Pw25+72E_przRBS!XLUmHlL%+-(1#$&z&^&^))x`rT0VW z4|Wt=ST&>HQQo{~$ip$px0Jv?vss7MH=2j_9?gY41HwQ40(Q)@OtuC8U5!gdz@850 zhp|`>U!`a}52Ai8t7}g}UaUgf3i_=AlS5H14${sN5qj}orlb7D*qAg4;YF#>FpPhL z1f#3Sy8!W{myR^grz)!ECK7+g{K~t=H1FN7Diy+>ty{vl(Eq_KaxUzQ|H)$|_%FLL zF&y&sy`-g(zjOO*XXHgeWqLfyj&W^~sV05%-|zE3VR~;I>_|yj`wZhqtF`#~SNh z=Sic4AfqO z|K`;11HZ93`G*njk5txujQm%bd1(mB%{{SnA5)|5rI^m_wd{S;f;dhpce#nY4|hGy z#^X1uDldvies0xjKI->md$99}ez&Q8S>2a{k%Zz+NY~4E?b#WH% z!;O=af0j^v-0gGpp0OQQr@q1!{4?eb(}VoP!KF0+*YYBNdF60}J^`1?8gC_%Kbp!V zKSAGCyScAxdGxtqh}SNBE+y_!D3DJL<$QLoky=FLib($EF{LfgA2z-&YxQ3I_oK4t zzaQF{|9E~i)yM(k=jEub64<>aSU($b^Jgze2mi?vhAajDt!`&CVAuJHCZ8e?6~7*D zg*}RU<@K;TIdBhsK2PsQ&i1GVFTdCqY=8IQ`m@c@KgVs`x4>V^$FTiok%gf;3v6mM*RUBe$Y97dcRM4opM zx9dGG*!b_+aDEl^9II_$<1SacX)WxDbP>H`^FNLM)qDSS{BM@~v+=k>kVE&DJkF5& z4SSG}JBx-FVH|cU42C$8-W4@d;_!Qhf_6m@^0H-KN`ZscAdRm*c0I-TU+*ctiMW*4 z1(+hfwpEMYL2sw9Cl2;xWOwfYCZz6aM1BfWr^sP<$mf`M7twQzaXChqk1IwmV4u6~x_MR? z@d)jD91p(oui=0xjNHe~)&B9$C*}|BQ4Gx=)m-ex)TZ2S1nWEsXE&+xzOl ze_qFv!lXGh>H&?-bT1a7y76pB2kj12Qx0Ua^WOr$jD?W<)%2-8z^K=2+kp0#iUjbq z6Poc+4&5gj9|)pM2C4TZgiS}}!)|E2H|jTqk*LYoiJ|IG<$7VNq( zaq41}Ur%Wi0T0)lD}>!6Prl({{Xg7RP3w^cx5c-T-N!xE>3RUYFAp9O0{N!N`oJ^A zhy5jlP4~kL5&yIW!&4B?@mH=)fF6#Zz1orFYd@V5gLOAq+;s`#j^}o5t^+;y3sCrL zlHLx1U&kQwyYzkEe^2_;C0oK7->A#%o~el+!S;cLyDmvZr0;&I1*@-{Rd5P+So-#| z{YX{Tz8%Qht)I@`hWSXVy=gS$M>l`=0jQ^4xE6Yq_lx!+A0B!%<>B|7eYePk7$?)C zmsx{<$-yNT5m)u$0b>}+XJg8J&v54rGn=rs3s@Ymuf z@4z^Cb}gyFapqOkusL^J;D_As92@wh!0*K@+ykwD)iwxuU~4L7pHsCpnTg^_ZhQVO z72yBpGyHublAm{>K@EBwhWSPVFFwzli~2RmVmp+x{2h~lUg_uf62bw6bLifW_J4y* z=yN9e{f?K0OTqt_+_mgJD&SSuW$-Ur=E~NGwTpw;fB))ryVZ8Yp=N`!E%=*OwxmGr z=WC_Rza^DB+5DHI-D@bK`blO+^U-cWbK)iBKgVJ88O$GF=+0w$IG%{(%=;hpVoN9wUzBMKH;TFyP(TrZZ6FpPx-D$!mP_ff z5F`}^1w<@bKrf^PRHOw|ZVOZ_x|D7d>HNky{qEy=|Gvlj@Mj!zU2BbV)EslnSc_|j zvrqQbwBC^?7(3kB-9-BTs)=ung57WBviV_2geINia^tsnp3BDfrQ<)|fIr`!VKdNu zVGX?;qRO`o{6MQe=&^ZNs7oZpImLs&+FMYysDS05n_D*1Je2z5#&Md5(|1c=9`uI( zX~W*qu>0%DO4A^hQr!wc=q;S{ViJD$bZck*FRAt;%|~cGvO0Z?OZ-fWx9tVrMaT9% zMO=1_1+{?M1o)rC?`mTMD?t-i|3g`n_P+-=6=J=qrS_Q{>^$BtP=)+@-%R`*aM#m+ zWB_jBYrEsHi*sXODB@GoEzN_Z;ZZ#_TnG6N_=l8mi0|l0N# ze8iH~2RDA?*1>NDy4!!i|97qoQ`Qvb(dg=y2mbO|2i}1?|E=^L^o#JY6lhaM=`1c$ zi5F89@!Z>G*Mh+(!{_3N5%G`waE#^^d4i3OwNf5b4|cT~lp^2i8~U*L*Q`}N3jIw~ zvW_Bt>aIU#aqMuZ@jm$58nv7P^}k(Yz#)3udKZn$B!^n22JG=DQ&xAFPubQA{v*Gh zcz|{&Yioo4ve*!Wcx?+kMqHJ6)nC06f3ve`-BE)ouN)JpkyKQ z#~ahY+o=D4I5a0g&IczNmgD*B_8z?8d+CGRNJ|NG(fur?B=WS;5Ut=%HP~dQ%8@54z|I2O%L0hUFX+4z7-{#O}54=_l`*nDJ z;jy??OpnO0E1;G&y`!Mw%HCQ^gr|}HG4TK4{K5aq_Ya`I(o);kkgF)vsvI~BBR z9lH00!r@27%iJ>bYrVPgmRMJcpRGvq(LBK!E&7Kr?w@gqd4&9zTIRfA!WT*Wrhp(e}^4xf9UR2pZb?fyG1hbJt3=@12)9h5jm6+t!Bo zmA;omdCrZ0!nK*S{zvoPx|L3thmU?Us7Acs(ODe;f8SZ#Z3?{V?Z=ey+?YnkSMVDT zt>*{zTW-tho0Rhk{b;|^bYSZ+%QG^H;D2?|=d2D@wR+9^W82FBDd=C|85;qpdG45suJ+gzn zKikw!1-|1|J2vkv(sO3(FL^c}qw)O1ZC2*s%XOM`9#rd9i5aMo^)4CEL~D&EF46G- z`&hJ1;w|@qS}oAJ0NOXJFS?HStdIAqMqI}&n@M?qhc zWYdM~4_=#<<32CDz1`S94REmU0?s#aQMT^>cdO0Z2(rU$?_wR`gqgX&2lbOFOn~2S zM31gyaS}Q47xd4KUL7k&_HKC}8wb7JvZ|NC@6!%+QasRkgxO7oXjeEo%E3;zoHRc| zZuR>a9pL*h;%XRZK2Foo->|!Ag=7)51Ifc zwob1VI4{UyR64blS{12b#B!iERWhcW)dY7{w{L@dydxO?%+x^QS zD!yFpFZg}l>%LytA#c#D1GokYU+#r|%}WLSG2Zv@NMqyf;{j_4)Pa*XKK~BAHh z*v&d-RbU=HU!m(t{fWNwp{CmfDqf*64r;sEMGbUsKrgF9ckIz%e&;Gb@Wl0z5N&7B z*3eh94nylkwNn!$J=pnhl{4fR2&p#3{ogbb z8=?QULnln3m*wWxX7C+7efKE*<7jTp>Ys(0m-cb!dPsPw8uZrj(aD4!;>N?Y?#bbI z&fPd$jBsMP`-EWMP>)NJh=am|!EC=X?8}@2;N8v9Gyq=eKrILQ+gbh$VR7JC)Ck=^K9mEeOnH_1cUR4lM3@Hwv?QW9-(7qrf@vWHRgT zI|^xCR}k{APP$7S@L!7OTR{JYh>NMvUxF|0BJ5h5@%c6KO!KM;dJm^g(4a=U z8+f*rqx4;GqE343oK(A0H?8+k{$6Ct#ZNNRB{rlbHYA(7vXxU{|^?Ak{3El z(Dw8`n}qw`y{e#5jmD)>wG=L0_v2nngx;YWAITY!9a0ZuMZy2WiYcy;>-v(3!=Qf% z?X>|lJ3d+mf0){jNY;@Y26e|DfPbg|J+>a=;I%~o?F_pNGw?r9+`#%>N=U?7_&-HG zi$*tkzuF?Q5PWu|OsDaU>fCdktj_OyA?F1A<%#w8jOeN;uOJzji1;5I-z$fD+qEZ$<-w$OZar)3rfOiEYP{|A3(u>VpV#2N4jki# zfUlrU`--+-8UmX)0>SJ!Ec>8-L8=1%H`vVuw(eKLk8%V85--EA6`@q zlmW-g&x_?JHNM~9g}pd)jI67~yI{VYC+t3Ls>c9uo@fX~?jY*^_D4MYpDTLcoiW|H zyjQUc{xGN=w88x)R|3ai@6Ks94qWK_R!aalel8k+afs@jbXknHbM!r}|L#BkpZib6 zFCWaq@5W|l<|BT@k`gaWTtMfVs$?plSFWt|bKs?TXHnfp|4*vdo7RQu1h<7Xc|*P% zQ~p^BI}O=~Wg;Fe3d+eJbUt0QLJ)Q3%JF^Xh=YvBv8+C^j?-rSG(uW08@T!2;#XmB zzRuYv5J&QL7uSRD=h=e?G2SomnQIGtll7m6F%RvI?o5K+N4gVTaQ|K%O-+V(^u%58 zoBYATiSae}umaV%t;6imYpbh)eRu3X{a1g)Md)sKHZG2}b<_AQ%u}>6J`3@u9cAu` zy0(6D%vIE3g})5+;fLXQ>hs{A{9&F1w6(o`sQz{1U;5+jDbznX)#jz}hl=NUiU&H^ zZ{ZvZIrbm<&gu-u>^%W!XWe(00?H#QO6M?@c#mf^2*BR)({r4`x2@!*O%&;ESlPtt zjmf#$xwtOh60d>!K<1y-HNYLX*>VQ)rkt<)2K>Cm>UV(-_}P|&mfWbA!66!%qNxVG zCo}#3=lqGj>TOmp#PtT3gAcFRd&(zn{5?a3LGVMR*jstne=OT+wh`Ioq3Eo7w4LH* zO7MH#q)Y=)2YuDk7>{(GZKZW*CEndh=91`7b1to=s*%3`KjY#VCYPNJn-7F27p*~? ze^-Pq?iY|8dJBCd!sBwl@8*YO*=D6W!7|rhY`j@0k@U`puFD=9kAZ)V9O8b)`LId@ z{b9)6?!p}bzu5PQ(?B_$IxO$)@SB&#p>|^6pSoUb|9vv;KmO1DQ`%xJW!&%CDLWBz z1V4(;KwK@#$SH@trPY_eK-^~?(fN!v-$uP(Sa&_GH);oYir)kdz;8|ir>;Q%`^Dpj z>qwp(NzN7E*D?~T5B>*oj+lU^a`O&?-WDyj12x@gy#jV>&d8+oYd8MM)2003-(wG? zDGsRadpG{pm}vD6t|0Pm)rcCi$LqenRk%O#>ZyP_!qGJQ{eObq$iK(|-=<=-si1zJ zeJVLb{lAv@70~$;Svr3Jiv34ik5iCk^ODr6@qEPV%ZXl_vH#OEUPb+k_WKjuP0$am z&o^8^TPJbt1IQ)Q`Sy`9*`qDoNfCatEa>FNIs9?++`G^_?)|D%^h<;CPSzhcb{=>N zc|ISGRtFs})S^5=?=M&ju=4|n3G;ejKbb+r?dadqvj#Ue(SMX18ux9%KAevCCSl;3 zeHgLdL2}%w%D;wjK%+`lPn5>L!CHSdu5MU&kOTg^{3Q*~6)*d$O_>JfZi1LVh*upImx;Gt*!EQy%<(;MBjlko#=W zmv%1k*X*)ygTZ_>Dk^lbB_#c(5zli)?Qux&bbwrR{Hq}*J z{ue97ViDJwOJeD}2h`uIzsCXJ`TA=v`oWM&w=eWhUN(Om_VUgwwTA!lo|X-vj;xMx z`JI<>3sb^{Ohp}Yui=Y zf%$)SYdw9Js7|nGAc^ub?SJd*s=!_YcMq8Wue5xq891-&CdGlC?9$l*`sa|77^u!Q zakGD@-^2gdheZ6N{%I_o%HnIGdRqjfDp}Ks! z^gUe9efnY%`r|HFXUc!{|23LiY5q#@$ylDqLtIDP)KmdYYA+7r5WawfoEqeaz99Y` zw2oWr5C1G)Bqs^|`I1E+z}_djTpEoCr}2H&H0(FIH@u~NxJe@YfflsxKS@Mm!5p$j zgkXqMvmNyRcKpgs;Og5P*X9ym%a*;j!C&uHJ)7smX;=iKeX{lE8L$7}|LFg^q(82= zD2L7gec&;Z{7>JH6C0=hN8`r-D_*q4i1h5-*h2d)b%H*x?7Z=Rgq@vF_Tu`Q6?Spx z|8C)ennon|KyD>Fe->C_G620!4Tp7eiSNB3;~wzu)(LP0y*w_?{P9J`_j3g4t*-Ti z3;nnMGd~LU^^{y*hj=@#A3^(`^}_O*Joh|S)Bmq6^X1^X?FRFU#!&}UI{1ylUQg6B z6OjLH_Ssv4|I~+>uQ)_^nfPdxfKort^?I=qqAAvpXo6T`aU2gi|k zR*&6`G;N0*_Z5YhJS`Wp!y(6|1(AcOiGITXQh#f8w9~udB=Q|Ax66SpAoJO_<`5zV~FbC>nZXiX}#(f4|x&Aq{`eIG0TG zjy%EoE6+xtf6~m;S&+-m&@m11f5K0i>UTrlQ4DqsOsAe!a273-D2P*i~S=lp)4wL!`Xzz-J7;! zTM>8H&UP8VPr)*EpYgnw>hNdaYFAb?f`5&Pvp9$7BNGi>wEcp8j{Nuh0nXu}|KobO z(LPB~MTOnsu-{Z}jwJMm)E?V{b?)(V5~(-`?OO9y4gEkRr#m`|W{X}^o=pL_>X#Q(D3crV}x zJriX4R_gCVY`w$DJ}nK`7k~JVFXCR7&L7|$k|v!$VE1o6s|D)nYTv;nDwQ^QxL(+h z_rX*_9r0qM|Dg6IOn%g4_zF+3LyMUex zJR1f&^Q$<`*Oho1<@F3flNL{yig@0{H^#>QZ%(Osz_(xQBZs=fSxvql<4uj4F1`Os z`#!F!dQBTS{DrQ4G|y1t4K#be)*TM?M*IW*8h(6!przq*@1UoP>(?B#Kia9uK!5#z z1o_amxtnqp?R`hxdC;!)`Ns=(E$VHO5TpFpe`&*f#9L7SFROnQpM=x;7}fo2^AF(v zN!2)y=%cNC;@KHIH*Q!-@`&(W&$rg&62CU-Vjm7swGE%y{LK2;@Bagmq9>FF`$(*7 zUk3k2zDs254m00yso50u&1eW7n^5CvsPj~AVLELXH=^ze0bP|96U%fT2_YI&wrp9gA3I1o7dX0ge z=J#cG4_y9?)d^7xqFV4=@l^4h=>I=`f@Od!nJAPC{V(mZ-;a5q`nW_I@W)E#AA|jW zIzFXyIdtx|XPow1gn3MT9y|p9>yo3JK(EZx7W^{+?e`JQCKl~=4Fv#*r#OMkhjB7>F*OmdVAm8!`{LdHvll+}0Xmh@t#_2r4 z{Yee>u)~&0OFBnK=g-G{rZiH&z6T;=XakJDYHy_GDW-{9Y6puL`-tGTmq$lFQ#= zwLlN^K$(j7v=37!*s3UDgSuldZs;2N{SLjFe$-#Te#}aS{ud3J9HEDFz@b*qp;iuC z=USTk`7QANc(PI$H1&F}2I4f*Ln|BhmJ89Q^)?Y+&*5x#-X&vZXg2h$nfXE+aqw%c zybSb}ORs#0e7mLcp*-y06LIzGtJNI-uA1`Gz&q}(-Nz;VJx;!Az)=y9JqEqXO6KaI zf2gUIv$#t+t9AgtmrYiS#_!(~MA-Obq){z^`;yicEQdZd`LhQgM}~_}1ZZ>f-J_U~ zEvp)J0e&1$+85B?pFfvl9~gp?7T8-*-?48hk2g0G>}u^)yf?UwzsS zqxs^G;WpS$yVM~FI4=9AQaw!d&$)h8;HCVzkoA8poqk*V9vqS42db82#Om=^tq$2} zujmw~E=}*#H;nH<92TG3n1ndkwCX;c`=|YHJ{w0P!hIMiU|>e|*dbLl4aoN;xsK{~ zvV)lgjhlwNfhxlm(5pSnA|8CN_C9d~{kvf&i-SBNS$(u!hSJzPpxJP97I01Y8U}$I z^lLNIQ%uT@>U=JLOn+7;@a;5nDK6@T!=?7vM-lEovttzSt~gA~g8%J4#zuqh-fm0Q z@01%t#L*5JJYWYJYU1e%duSdy%f|cMuVySh8~5&F@uDvoDT}zAziBE}9kl}~{r248d`yVG|&%I7Dby*kN`>NR1W3Es14|A^|K6&HPA_d322So|`YRxye{j;OAFw76JM>|M>#Y1+h!7ftnn;>-c|t z{$LIM|HA+7_Ybs6{aAl{vM6~Na(Rnx(8Ri{XF^+&DCvJ$?pizS8$R#RFenf2qP?iM zTz_V1ApiGtW*xw~PybmLci5>>ONsUYXrJ2XTMFV}<&SYz*Iz$)Ne2Aa4LP#@w(o3P z1KRpya`ax5Fi&K!lsWE~t9PL92pjTtC&V{_pG(QTmB960d3g)su5iQbZNR?~*U*Ky zb6NMT4dZc*Ro6-2wqM&P4EqNQTi(I@pwIelviXnxjw&|JzdY>5-v5_V>D&PPpuJ}_ zK+VPMSe)|D5Ua&JXm}`g7T%|o>5Nr`9GB}dJS+r^z1fwxmxDU+puu1#^!fM2 zwlCoOsVwvkhv?T6{yJ!1%d}kuEZUpqCVWBMwS**0$8#brNr}L}<}qP1?CaXv zCWCR#xwqsp>|mf?U3o$A5&u$-L?XdX`A0Sf%^82n?>9ZcMa zCT6{)^Vn36s}I+q9jVsE{=cEcz6(ZmbUnLjR5FkJp}fT41^A^DDUJh2pe?~1`wp&Y z^X*`VgTEfI^KS+!YE_7fmJc%H@O%0q=`h$|=0lVKYH_gSDg3pezLq5c!IwY$PV=0jpnRJsDqUMsCtV2 zXSKen7x`{>btlzFhP<`I6F7+f6RXc(1pe^_F6O{{cB$Yjm#CKZ)kx5;f=pI#`dKy5 z{E+JKJyS1%&U)=|4e~r*7|RF!H|H!41FpKd$UMlAaAMVE3H1y@|xd)-cP7a_Q= z+<@vdH~zRkpG`&HywjQW0dnjqIbi_3960G2;Li!LuK@nx(VOZVqH?$9uskdrbnZRm zm2PNC0`*)yft|Y%DH7X*ek55w#PUGL_cH;&y;7SpgnjV-@n558&pINR3VClmP;h`8 zY7N{O5yZ#BEr7-S%Rp5Ycg45WdX*^tHO}s3b#PMu+eFC0x&8PXG9f3^V()iBh=WSk)g?YA|o&kM-j_R#m z1FCnp{0~aA*#5{+MN&8N&xhm3%+Y@|BaE7Xqih;I#37oo*IyRYC{61&sCLPX$@o1r z(T3G&!Fsl;Xcw15ms7d>w?B6)ZVr_lNzz2_$vqyhO7y+-wn zsGmN5I?UETqLc2Zzz-ciZ-0P3XEl4DaEWiu{=HOJ@bC!d{QmBbnpG6re^HNp9gh2E zFJHU^{X~96%rrdz)<=2}xN=@18OZm}T6!meFTQj|I>w!|=N`nsA2E+l#Ul=MyozbP zM~Rm^^mjV)KuMcARjG8I#h{0+w^t3~3jQGzuT$s_UMBkO9*XKov51)F8t*8jSIX|#^5PEOiJjC`L z;$-$yy*)`p@72#XTpt^WE5`fyt^<88z@MNxasmCo_JAMtD_TDkGHr%_y+)>7$llpLOZ%BVKMG&co>%te=M0M)&~2|d^^=2cnVc*G~RLf zGlm|z;rgRpduBraxRoE%fZMh`sSt8h**dJKBmX~;`HSWQ!aPm-1A5?Zq5I{L9fQNB3kd`@&`z}%104yO|$(u;LVb)&M~HT^OF8V^aBgi zBc+H7=lva8sAIYxT0DgQ&YoV~(66F7!kI(-Z%XV^0aXe*Vg|e*{YRsqNjaKSuhM(( z&wR;#^nbQ4OUnRXJ5|mP`byn;J_tK}TQN%UNblc!pCx}x5?T27$>s2ity#qr?2q<3 zI`zW-Ctt2itRvja+;$i6_dAlz=G`^n0rT-b{n4$@Uf}u_J3BW1HcYiRhxWQ36A6vJ zWBzl3C;XY!HI4&{rcghO5L~%Ce>?Jk(C`ViA2?bw(hs|gbcSjn9)1=Dw!nYZ zXG+E$hGdFfTbA7W}y}jnDxeojuTh^wC_{;6wl#2I4 zdSVYBfghZzdTlTcE5yh3BYs>@g*pMZxYD@-I8Fu*cB16}?0_P9Gvx@uZ5$3&kF+ji zrzeNs8|Jmyafv>UpZ^8^l9!a)3w(hBR~qMOUo-Wd8}Q3=V*kLpQ~F@l*z1j=BGu=1 z80?^ak$z{_3G@%^b0;YObNEYKW)))o+1}~&7jSmpwX6h=mC)StpdMzSX`rdH)7-G` zc70l;KK%Y=UE8oI$?cfX;EDKs@W+D(&^J3QYckfghPy7a`G!J{4dpvJkJfQn2-ixR|R{pbf9 zqz|)kY2U`SNs~kjdBbh{6ruk{v0gU+i^#rW#gok(?Yo}m1MybR#$YpJ$WNiAixn9_i zx8Xus68z?=n9vG8X8KHFd9Y)G*e2xHJ)Kz;cW(T3E$t7m{|o@7l7x2 z_x}FArKGQ%6!c9zv^)U*@7USbhqzy!aX%dWpkbR`FB^ZWOv@*cywxPu{Ks~*N2=nc!~P#XDk!2~me`%@hrgzWR9>zZrg0X@yf1TT02N5gpm{x=KTlMsG$#A!=x0dcymwXq zbIN~o-tQYH0>5uqYLXATJt3=C%)hFi~I8^^pef%IH4D!e+O#1*i*4B)$@BYMn3ZZx-eWThXK@<9Y zDNj;ed_~&^`cEl{TLM4EY>T0F1QA}2iP6yr;@6xTtq*Sq0EU}c64;&0q{56yF_9(EgPg}q%5{F24GTh^meefXsYA}Q2%5v>lZx~9 z!F@xslNzD7Uy3Z%w_N_2+cVqI?~+nt*!)BKygBYP%?3(*E?-rF4#t{?B6S zTb6$&hU?Qh46U!#C(<}W>yQSHZ{X);PlKs0q4fvlIw50{tJ`3J#s6lBk}HtosD|Fz z2*TGKzVRJ6_f_?)>j*!k+HbxY#lQKsZ7lw^G*s0f&*vsBTin;z7!?b71RT5SP{(D- zZHR|`bHuYUVSnpY)6XORhiXP;jS0Vgy@fugW_#}t>=ia6${Tr0t#MQV`d>H0|8=2# zz~jS=|6tD{X6KD&J>$Ur@|Up|ei!c$iASE0e|G)_^q2ge9{_&B1yf?9$vN$Bkud;Cx;;K)h14%I+^Clrkdu^d!)!)rH*iO3!e6IiGOXE1*K&nxn5Nd@GvUX!B_Kc-FTh{ih9d9OcMz4akCOAPor zo1!UC(RV+Ottp2b7VVm}en)lM`qOiOe~Y(P7<^(>Vw^yq=(sR>GCugLBHr|_@-KwF zXMAd8b`iehGYR&(X_l}H`qzyrW&!`uL=B>D{4bo|Ekm4LeDJ{^aqzrG>oeN@TSk@f zed@T6t+YN*^R-k5`#SR9iN*u1SjRtd!hEVB;SJ88#PaR}0rg`1Ka;I*G}LhY-hMT< zF2!fIgVlj`rSdk=P^)J8ZHJ^gtaRe!SQa{qONY^=#Oqebj-~9k=eD`U*Ka>`c<3zu0!o zdM@!jY;=>^|HSo0c>X2fU=4_s%0uZ0tF7{*czQ+wZD??|K`Z0pJ*g-nWFl zcDfBJpjTF0J7h@I?_?44`@P;!GdH@=!vFX8Um9|JqQTGii<&$7cS4i2o6PPj8jT>ktLx8a>wYnFS|fmaF3h(bxQDE4**G82FY5=pEKE5d z069G7m2Ja)4OzweBS`*+B%Te(fAO2+5)jvReAB27p!d#>w9tGbPq2JOY$w*8gfB8jRUKfE-m2J+~~ePp;lTK^3O|AT7BZK0+jrY2U=rh!t8L~;}h#IbNYsa;ID44 zzUjz62~(R=fS=YMN9WGzeZS29Tu)r?jHqY*`((xWr?)Ackyg}mw6i*ai z+VAyy4DlzCQ9TIzbd5S6fc`Dz&s4a?_xP@|a_~2MFkk^1SslpY%~{8;2fxpJ z9efLT=09)vGyFxH6QRGK#kv)U@2;kmLCE_isp8)-->B(4I1Yc$mUg1`d3rBTw&o_{ zVf1<;t^bh!qp}oXN8OOW**P@zj*+Jv!ZF?JN4=BuzoE*;KjYPZ9YWeV(`WQUPo7ve;E$H>F-QI_>yqw8+_&>tnnV8+j^jCyGiRD`CF-8B-D913q(^hx zH|u&~+K1CuVe{VbQ;xI_EzGm?T}KY&n3$-Z0379nNb(c?e-pE!WIUH`D@Fa7-XBc# zoB};sKi5fO{3||qE+2U({i5@qz#XWsh=W{FLQTAo%k=56FUD)P+1Kvlx%A+QP2k&Z zood4+oXGg$C_~b-w7`7~xat+}jzbR1AhQ%)FMIT_AFkh+Ins^iikDki?I3*`q!kok zSO4PW7)6uY}gsyU@Sw@>@yRH9;ZxKfo;y-e`^Wr?rEb zwEjisA8udTfc`h$KXMlKxG?lX1^Ne=7n}g?JpQp9{8Wlu{)ArHhXO@G1#IooI7Dx~ z%=7zyIDhcJ^8EwI)7`8Q13JO9@gSFIUhPI9*zecIzq#niozD8~-f&T5v8)&qq?@vjtQ$W6z%(A}# zJe4gDX`pX8rOoh9)OU9lw-+b6KY`sjU#7Ie@4G5?`+&bv+BrI3OW&RPCz1If@8W7& zkE8D(ILz3C>usrZFO0|!PHBb5M9F`(%^`o+Q~dW^SiOy)et4ui#CDH@<}CBPTt{->_dZ8;J)QT8mrujGcadHZwdsAUM{8FBx9;hhfGDC% zk2Lrh(}-Dlah&9(|G!}RUV0MAA7{B`ENV4}f3&NR*}=%z|10ob*EIftyx27MmGz70 zrEA?I2v2N@hC6V1@6K$%xF@|{bUE-={r%|y_W2B)<@$NIarhI*(z@{-HZzUMJ*9jWiO-n_)rS(c8{6CcLP4i>=-u}_n zRNUWXJWlgplA~LVgY%O;GLCWZ@3CFt*P(Zszlk~gaVQDZF>tmhh=HM^Hpdl z`;OABj-h<;+2Apg#(!FOyWca4b-&l+`t<*?CyDg6@1*rcnvbRbb4X0$0`J}PF;k#_ zOs(}@HW22VGD=o+gH4Y@O#?pP}(P<^}r>2 z*mz@7c5VcD>_PV02#mYgB|>962#-hl-)XRS^Q9xMn8yiK^^W5Fi_M-I1B{D7eOKfm zcZKA-Q9OS`XWR?8oQJLlVdoLMoxPyEbA8%?d!wyY26F1HVd@m-gN0yMr4SCScsHsp(b3x!@etu`t~4Ykt)n`A6r`$L2cX zn->4f9{MIflsOLEYo=$WqW!q;0P7Fo#jE1h(C)*SIoe# z)WX0S__u6?yMSM)JlqGnv~@QvKzo(ogQK{w-Cg}Xo|BZ=l!Sb8F(a!09%!56^wc`;X?uJ ze|n5_8~Q6>XqZzkti(H$FJm_NNGbN#Vx39PZgfBV-q+qJkA5)JwEhm_Vze)B9Cm5C z>O^w8@&6?kPwO`v{;`ecOX1IVKAwBHmE|){a7y1j3=eE zeoJ+Czg`vOu8US|!oF)##!JdGB++113i`WG+;lh0!(B^y)R6ydZG4I$U#Ye$?Zeam z!)_jC>+j<++9}W{Ysw()E75w^c+>{q9lpA56^H1FS#~TQ#D$ls}$l)jFibgfGeY*Q3CzbKN;FX|BlqN&Y%uy=9_`*aw~H#?DhTb301^Tl8M(} z&@1Z2-Z#L#`1R6Bw2j82*nB%|QMn|pOLC8v;(EHH28)lUKAvpk(1K52_T=h59Ep{ zlq~EXomFXWM89Xw_(l63^!`C(eqhr^nwMx8s38u@Z+y82zODveQ=zA+YEnO_@(sTr z&}aqQNYKZA>sdSuXoZ=e%@Hf#BS!tMOsKyGez>({I>kGkKWp};_gu&hZ?u;o?xS7w zbU{m;GmoJTvnvWMf*hl^ns$)id%A8I{24ndZvcF^UiGSl-U;in-OygN?j)NRZqVBj z0y|kqq`wB`hQ62qT)q`=sP4@ZRQa%p>WxVvC)V!SfVx92Q)CbF&xXjcO^AmfonvmW z!!fV9RHxAQk2+&0kJ0~aGl}~F{Y_OnHv!M$QS^BZ(bVBPEFYYi_xpXgx!y%X_&v1Y ztpaFb!(~4_=ddZ~F67vl*_D8Ot=TL8VCyhkZTDG-_l)tyHPBbt%ec~m@~=k*)+DARchHS=eXBXaA{bD-mbCQm?*mk5Q| zk=(ML_>a#_uttBA7+>)V{uqrHdISB9s$)7qw@ZIkMZ9Wj7r4O=)sM#N zAxG0<*FMC7{K8ZN@UJl5&TvO>e7g?2_^a4DKrYFjxn8i>i?(||@%(t?>lyI>hc1B} z;28$Ru6j-QD;3%#;QzRK3zsIM=h|%Saeb_?#1?Yw?=ATY^P`aQin-9E@=P;}zvQc# zTk8lXv`SGf?g-6aIp*8AB+rNZF}5Dky0A1Ca>$fzeu?}SvPn!4a$K9b`5N?=n~{+Q z+&~v|=iO8fjvg*ihy6ErJiP_`G>%y6!(T3W@!ojvd(B;o2hjiIkp3_D{psnM`4PlF zY2-{U?C|GDZzzy9`dMqC3(H3xcPH>d|1i08 zN(-`9)o%=d9K=T*+o<)&>pP4fC+oPOsxISc6>;gHCgytLu z?Ox^Si+;E?^uieWapa54EH>V$*(zecIai~XgLo($a8ZH&6P=yy5OJSj!~amm?K z4!K*yk4}O=515?&CQ5Q>IJLUK{uL{`E(7;L{i(B{LSEU^VE3%c>v|xselGs+f+25| z$M5ex+>bu{33g4`{I(GOb^Vpp2|HB&Atr?Do0^VwLLP(ON_{bkzpmxyb>RONr&FvS zb9Zj_0#5s}z{Wblmt8#S2YsIH-nJF%4qHRiuHg45M~{_=Q$^iKt9oJjf5sh)sNU!D zraDAiub{OnY+3IUwlFY&67C%aeLBap#Qj8 z>Tc-oUZY+FoMO$2`Jj(`;%9P+R*Xbdpx+0%%vVEyUFA|u`z%yH7mR%Ym5vBehJCm2 zX43vI?Z0;Xn25H8#i~sBKW)=hT93~YJTa0;^B*q%ggd6wK^s-Hj4}TxO&^K}-qwG_ zui*byhUW$dcpahh2d1qY#9c>!$~^r3a%Sf&v}6BV)C>Q|hKqHs~%@+)vZi=L`fm6nk)ubq zvi0VKV}T1GhtJ|UY#!PYx>F2(m^gZ8J?a=QsoZSfx5-4B!yiM}v;F{X1K(s-A*))6FEl zA!kkIb1~?@WU#OadFN%L+$P{9zMTFTI9kVIn}DmVk#ZezDS7RQ7jW0i?mh~tI;uc* zC!IgIzhV{WL+%Vy@b&OCn1=fIL*-Ep;$J8wOx~RG|L$+w*g4=zfi>*CxMZhgs_2Jt zLtQ7Kf4Wye6XIF&<$9_EY2DXXel7g}eo;j^-aqp5Ezbq6vao3$>~H6+n~1n`&Nrs@ zFE{>m&nq3!k3+iJeS!ORc<3Aakdbs;2YP>2j*5oe^E+nVh5rSv$In84F+RQP3GAMf zyG{Xq+>qf-`+IqUQ*%FmM?X(G+(GqNo?xJURy6W~WzGPtzt;)=C_EYhIfX6bnt;nW zfBqTt7v6VEo=Z4S!#B|SIMw}OLbU%rNo4D`t+Xyf<9@?gHOO&qRdos4iAI}a;ip*X z2odySAMwap!0XgkkbxZ0?#<7je`dBzA^P7hsr|J6Mc)DNY`cZuwF+EP;K$q*rU8)S z@cQ1fu%~`dP9@_X_{c$jKUfFXwy+fV~5)C*;-( z)B4-|G$F4e9DdG=7i>Lzj*gNz!EYPaq!_|=AzAN(-^61g3u-(9a*t)CW)+tf|84?SWxj`-iSxz-eRcR1d0AGjMN@6ftBeP2)@C4ucfbDpvO zuk|Q61iv?G-0%b~mK%Eo|NBde$sqoQ)eU`7U&p4OUkUtNdC6|rJ$n7YIAhX(mFOd? z&q(itBUOn1(r+1rPvfA0=o{Go`SZKF5kzy+<_HF=Q z$&LbM-?6v-)zFh?q-_b}ZpmYb8p!qKg`_{~vBS1wRmhuJe|hoX`K0saU%~(UziZk4 z!-Fk<7uAt|vLW*V_9#aPK6`d_D}IlDrK83rnlWFh1^$rj-OKdn-v6i2i1P39X>xx-Ibz5XI8&8gng&T z%(ldOtj2sbJ=9xc(QCLy)ZY^;71E)1U3$zZJkQY%t^i-|!F-nIx$$=>&dAR~S~@GX ziSRyqR>Sh1^^`@YA;-<;l)rKRh3dq#Nz{KD`I?$A{zzpj^uyj)QfsteZ=rD&1K7j0 zNbM`G|1h{Z3>sZ-U5EJ3x*jb9xn(s^jzXUH2L^HAtE&_D4mgi)r5^*mJo)w{F46hM ze>1yRT+FwJ{^bS<)K5A5b=g@|$8-4SPUY2kP4)G`G#yvsFBrjj!{&X3dI>Zh(s$N+ zJ88Xv%ilN7-Hv!Ie|obExT7js#lRKTza0a-+M|Oepf{@Aw}1}Hr)q(6Oiau{?-o`| zfv#u`qWv!Vf8F8lTVQXclIT!l(od-^whR81xOOQDd8Z^fF%SKb)1Mm$s_*!LjgtY_ z`_7;rdLC`IhaHoYE5~5>4MUpD&T-34hk-Ne)ga4<(ofRWk#AbE7xoewZ}wMJ)^C?i z%6~SAgC0m7F#=uJ7Iq)<6sF}yA&%c~ zilO-~?Q^b?dzWYeQma0yVl{$IPebk9b@Bc?S|bf{yh#{ zOa$HyKeG#{``wOD6F@syOR^c%VwWn@|5awZ5bS5yZA0U_5^rHn+OD>XrP zn^g?qy4I_zMo?j!Lh>S=yOEOc#&e6KC!|7ug^c_h#P|M-UK22Hbyn#&fE_qTrT2k` z`>YT}{1T|4Ho-^ewbvry$AedL|cVGfAWj4JlMJQ;32gCc_TveMfzT<%x6|V zPFKv1gdGd)S5V%i_w2PkYQyhB-QT}J@Ampo4V)t$u9?0a*W1eNN8zWEa&1XGXJ9bI z^qp1nj~DnK6H-{hAv!Q~W-8kK$894(jgP0U!}XgNb#HKq&e#&x2)X+eZcae{xyI*B z^PW1vl9w;Vn#e-|x2zlyZ|NNC56C~ehwFHOn__sz61eO0753vf^<8l^-xcBA_voV{ z;=6^@y%PK#II%46dfc4R3H&v@#n18k>?{Qz=s#O-GywEQ;NR=uPgjGjbncnUA5z=t z0XjC9Yl{3os$wtzy+^xT*06f4ETISSGB1>ut%IxD#Hm6LZO!Xpz-h5Mco%-3e>U+1 z+ODqmj&q3CWR@_0gwB6e13z6J%1uQ7ACps52fq}3ftk?v-j8PX9!P`p>|V&>?ev5C z8+~tIY001{^_MA2S8Rg*v8Q9*5jQ!bJ9a^j1pmTv#C3mtQ!8+fq(_MYcV^V@|3{v^ zSb7AwOH*?AAn!Kwk8yZ@=%=7Ga2uM$(^wrYJxu|)yDMj$fZle^9W?Lc@-O-3`48p= z!vVt!AN1wuOr8e)BZ4-2L*HIs zchZ~c;h^Ds=yhyD%Y5V?{q21;4pSW%)F~51bbVh}1?1|1g^f-$rr#4wwt~RZGm?xtR9W*?3l@=w#w8+>3FVEc4q+aM-%zdfY+Mj zP5qGa-_VTJ7;nl()8kPGYK-@@`n0bx&=PhK&^Ues{&U$7O!GbZ4$pTLO{wCyw!OEfIko(wO=P|T%4_2^w zQ=Dw)df53`QIiqID_8%T_gH7BN_y#7M|!{UjGmfDe2Ti{3t{J0T^~07|3}S_{Xc3I zEwxh^2UXK-**VOuX4l!d#KZp{u)_UD#fz7~ewrzU<-qR`>N)|rhJt1>yDA2?>EQQ> zwgyA2_x7e)Ct6o@%_nA#;7V?s%Q8qNRZIVP0xKaBYgRPJzyy8r=n4ZL2R=p~uFjo-@J!kD*gk4bXdA%d4tjr+$xg z-a2|dD9XtO&l%-8S>S%Lq4`yy2@fI@fFF8n<~g*}wEEcf3$2eS4~XzOjaQTX=)J&m zr}e-u;#KVz(3*$8Ot3NijApiV-tbKVr)miwz z7P+a^%(PCEu1X7`O^TvrG%eHax*_D2>{OC1l%gcGAqsWx*h4A%)-Y|fO@)vp*Dkc# z0H{ zZ&MaX_k9y89sw^|A3*Kab2`ITs0gVaS3dk8>~876?+1P9cXllQc_ue83UobJggF5= z>vlW`TsF~XHt2a6?(9JS_hWL`g0FtG!G6%m85~{!`07(iE%-!Tg~G=2EP6IZa(-v z`9s@*@y*R`L|m12{2_sSg*UuNZv~F6nHjr3COfkr26Bwd`7!~1Y4Q}upnt*ai*oS$ zr?%Wm0paV<^|1X$vzbdeHAg$P5yNn#XZGs;7iKa>HKJ-UJ>$+zFDeMVULo==p4LLBaZLfh(c`)xS6IN%??mdnsSIBL-}*v}eAym3@DM?@(jkYmR;mozoqyYQ2XM2+ z{B+O@^N3;PBK?~zp6my&wF#-d(B;w3;w{3!9=$0na&oB>&O-oQG!Z z0bJ5EP=I#(Gcwq?tGv76Bk*^CThs z{$2i;eCS;k;Ig!Z>^;TQ)CvB%GGhXrvvZ}=-T3d&|7>ESCFs2~2xj_?GdOAjy?I6o z=Fr!C{qDnPC;He{CM4KszPkhXFKOnj!1aSPgTYVneKO0F#`8ZXL$AAW(;`rRck;I$ zwBNHjn#~_~zm|moH~np3F64DQ^WXc<(~dfh2kxHsY8C2j-VFQ+xFb26twWv@!)RTj z^(}4MR{@oalny7O{$A+8$Iz=f&5w<5N%#nP(0OrwWDC~W$|}oF$fqW>w1(c?vBPEP zH#N2RRP$V@b6T1=6d2tJi7XXK;3LwYmI&%b=w!~KX&8*k8WzWKstv^TC1W#OE+E6Cmz z^4)F|rvdhwZd-tI(`MIgXrHF)w_8APfq|hGa8YH9KA=I#RJLB9wwq`J`f(roSi(_nqx7@c&Ry#wo1t6Rc%8qZMerYnRVPc}4Rbl9TRpb%@zK-CpVT9&|ge z>M{MaJ$dE88~S!Ly`MNYI|~Ts9g;-AK6(}A9;h!hk5C2dk2t#pus8ZwMcCnBRel=U zmu`IbT}1Mw8XK)+c9`AhfpYJCFFyjWYl~v@)xxZiV<1nFsogA$+d+@d3ZS?52=4^o zA+;*z@8HJW_Cmtd`T9b$lV?0oA^&KAh2wc|<(=q_rL{dN6HOb5y zFD6*4E*=NGRbn#|e2?td=`J7`{5qD+1AA?Z*}eQLvwmj&(Qn)u3c9M%D&^3(O|_sF z`meo}EDL%!dgpb5-k%$M7XtV88K}Vg=lLpn0CFYP=C+|Vp;1DXJDq^}M}{iWF0F2L}(Sfwa2lEdecY>hwu_0boT`63)>jwjVW-&DW}I(cjS}!% zQT2%Jlg#&-I)m@gk_$9XaizqYe@%s)S5xP)@o9NExE*|g`6t6s{;IRD?^qd-Ht^z*12gl->9S$0X0P5eGya(mg zdw+37zi}@k9U8b?slK*SBjgjV)!h`wT&brAyjy(eoK(_2>;_zTBv~1DTAq+X~k%^m-$<=yzn^Wtk=)hgd9ghY}tI{JaIak@0`m`Ckm-PEn1n`$HPNf9qldU zwLjtisL8U+X#S<=Wg=}jsU%Cp~)5K zles8n4`8wTEC-ZJGFFBFzrDPq61ab}#b@9-i`LZwHr{^yvWUv3_omyR{`i~a7a&ik z;6uNV>RZ+-vN*YFll%?sLlhG)z#d_oxH*u=L$Wdr?LS$JXL;8j>zi2|dNnR67f|~u zbHP#ggU>TO3cShFla14v0i6%v_h}M+WAGPPtiAz$Po)wMhzTF&uS^8KT>Xu~~p(2MaMM*qE^4yy?W9=dmOJN&J`o8Jxnc_$|DfN$A7r4)2c`4!s%ds;)8 zyvfOYiYi*KKhKCp`TJ0fa@aMq`S5Dk`%zE!UFf^m;ox1!AqmEJEGS6dN53SSfL*}pb?-BU1QVtD*}DDiilGh4 zOEw1Q0j~VII|E@oku1Ebw53dlwtoiyi zfF}n+^@~Uznb2qbF-s{vY9&X}FJJoc6Zk8yDEkHSzM&yUmiIaSc>PXD^&=g3GJV~y zju3**rfstrOy7B_4)|RcgTFv$+Ko>!(7V0nH_Ah3Ute`^0{A`icjsUnc6Jy%fgPg5 zN7b`=CMmB3eC;$}>7ZS_cForYF75jR(mRo#D9Q-{>8rV3uUj62J0FPWpCapgWE5y7s>1#I88Y}v1$VOQhq z)92u4V=ukU=)bqPhzaM-q@Merg`TpG=zoOjK@xS-Y20I_V4!XS&S4==>=7KO=;6Vc+KZE~- z9DgzF9iK9D3HS|P>}!SI-W?;95%2RKbMv5YE;s%e{H{|P+byR0hdDxONAtjy%wW*b zd*Ye_Xf#E__MdrKJa#Uf{Z}^+{K})74TS`!+$?8)8>i#*9OZVy$)SLcfB!ZIQ2uI| z66iEHYBWQhwu+)U^t+Z~$;Pq&vQcc_yynnufO;P9zjKz;*UtJtf1dGfcK#5))3Z%L z{hSQ$Z2^>TKidjB9bB<%BkWLZt~?;7{@n#i9b2~2dlij&yP@}`{hcho%hm|3#duJ! z=`qE*PjQS+DDVO8rm?Wc;0b^76P;W3v?PF@LEdi)q@)KeX$2_TL3P zuHDu>13fK5JqkhZuLpnq#rRE`aT)z%k~BO4Rg*0~0%n!R^UzOvQ3Q*x=?3XZDEHFr zUI6=acnESG*L%bE0eq+Zdr?0a7F7+n+o6XK$T#k8gFekp zQ`r9d#mp#qlvjOsu7e$7e{?Pfo?vOh^a{PITnPLc-_8N_c|SEC1^j6@?>6A!avrny zW0@TG-No1Yv)a%;v1z-Tkf5&j8eF?l$-fonuDtMlN9=0Xc3DE1t(dcH3 z_eZruEdPkVb&>9W(0g+WqMid9=RU7MyHKlVCqeJQ`M?A~lj%eCfaaI^`yfx)5gwZd zF8(w{AGo2f=`RARH*lWD<}IJ#NN3o+)uwqrppONg%_E&^wWA@=%+MGX=edT1NkZyZ zRX2gzx!cM2IOy#**PjJ`r8YBIo~h@5n2oRZ{r`OjY@xYRFZjJ~=0$*hW9iODv>y_# ziv_IAufGcTB<6Dg;NZT zf1ahtZUtR0zOg_+{WpaP20-VXaW#GYhxFwA$>Kn72k$J(RSe35&8YmbaV(8GvJ+2N z2hfhE^$AdaX2M({!C;wL!N4m<3yT2v3{1N$CO*|p=3CLj{6)ox<`iT`Mzn)c?K0dcT0^#9ZC95>R8>f8VXpGT4;``9~aeeg;0mb>S67 zgcn5Te<~vXs+`=u3;4wWw;JT5rsdyC;rI6*Q!<3q-oD4`6#7}t%rOF=$JNf$z(2(B z_uqh*mg@eBdc%x}7?l5XV8IrYZ}~~KUqIys&Uy-{4_@nb1JG(-NHbv3&d|RAM_=4~ z1^jjP*0=-j+xp-8*WJG)MS)(U-hRr$DDU~}D6`X}DHh7Wt#)7f2kjg6ohpFy^m!h@ zJ!L}Kxj@n`Q#N0i>egDtkY3gNAr0tNmaFd$zve{T+>Z8ciea>FQd|b6QawGte9xKk zHwBLM7Ggl zuU$y}EQ}-WgN}KV3L7{2@eyo45*^}Ei1Koks3Gug^bK}_JbC?oERKH4EMohe^l(QO zCl`Jk?}i*siRyl!&->{in}4V1PYFT4^5AwB54`mTEYC34%j1Ktv7bm4_1=pQW&jQh z-aLfze|JVy00z7pGzRoj%xCLSv+esqA+?L^uWm*At98N0&4}M$E{Ax)E2<5ceRIsFoiBH~;1 z>3z1CU`Jaw+h2t~v}WttgCTOH_*-Bri(|HPrZZNR7S9&QFSxwn0?faGw| zFJp2=&-uv-a*2|7Ie;>4W90zl4P9Re37+f@Jq*5fRRumMe^U39)&-gmIwf>2M*kCK zsKr1YzPqLb^)>k+x{$+Y@3~(Arx+b#>rMIo(slvWn>+E?e#n_Oot*>J9t!>i{d}!1 z^MnLH+NJgbP8gwSCZhO~GyHK0{F@yD=zNC$7hBsi{A4xl7kU4k@8u{5k3;!O!F(;y zsWh4~4Um8P9-Gfp}JI%iUeksn% zhA2O}BKwq(VA}#dTc0K;@D)*BR$)8?e9x73_8=ekddn+EyN{kNEN+bUPwfW%Gb_B_ z0iGV=!Q$_)3BQB_|Cjd<5BH3e#LZl4(80ONpIj%eDj^zymIwTWi0F*Jh>|s z@!_Bz?hg6-1zr|__h(7^L8m#_IB~QB>FI2ITtM{x2n%O+Kklcwowcv!n?dfaKNYM6 zbmIMN0%)+(KLPDS1HR}9304k{Dn@y^`8nqAtgPV2DA)BpT>w4@?q-Tm-#)&L+2N%A zw>mMEcNu;9fbt_xdQJ%lzOyi51iotA7i^q_`TC!R9n$~0L*s(Zt2&ceybg5DnSgc~p;?xo zKehDs6!bTbh`s>GJ1AW)AUO5KA`cK`+* zyFuOPXq12S_A5X?pJ@*4Ih3EGYmb3n`_hD$;6Ie3$^$%lznk)Inx9q(58(e!uOtt4 zF8@Aq#|^BHv*Lq&@qf71E*Clj;z;WnS89ZK>`d&Zt=tl2AQx|8I?FSHUV7#NzpEe4z86teEcpb!u`lCJ z;r@yL7pD}&XX4ATd1z;peC8VDb@-S{`&XKWC4FoiojuKft$U$c0;WTbx-i9Z)Mw|< zCOaaJEu;HfG|x#s)IhG*mDy`hUfI^h&g7yC>nx|W1`er)as=zYYovsZDEaMI_{iFPQjjeXt#x}FDq zNd>5c=Wav#=xg6h-q%%(me);r#X;Ylz5;B<=HAa)4|W=Tj6=+*|${# z-YlZbc!=k;9rvEv|BwkWCm0%G2V`{0Xwq|1vw3{z zZCztb^@2&hDT~3N{E4fGB2f_0sUczftlcYHp5gI{teHYr2#(mZ4nK?O%v9#_X1?8$L_`b ztGt&PvlJ;VMh+#Dzvv!pWfhb2fXlu%&`n7j{0w>CMt@}M$d@nQt6+yi-Rnaj&!&!) zeAxY7e@qg_Yh9&89`+kf|Bc3*0%t%yhvEnMhd+%Yy8m%=tv%whw_3*&_%*i`2124Y z_YS={h@k!K#C3od=PzaUZ-{cL1|GV7X#@0*k6qA)`w9n_%$W<^@UYqv=$(_V!|nkW z2RczcN$)3V9hr>suDjtg<`J}%>}7HG^^~e5{FF29r33U0-=+Bxu+r>3-Cw8s|3|Fb z0CR8NV|jvoT*67n*Ro1}1N3dq#^(puzYG$x}tB(6r5q2|{+c69} z@y1`|Fs=e5wsvUOc1q0N7hn`vGzfcTyPp!mzBy@6JQ2qq_tchy&ql}I|JLbL`AG2N z&RDz;<3T-bixl)$3NCB|RL(o1g#NirL;awqv?xEPh{^*xzY!!qyxY7E?}^uQ9;y{n zyQzLflou;-5?itsU>KUZ_v*cnrkm9eQyPU=sy?@W@cnaYCwkq1MQk>gW&urk*|Ll@i?0L|S z+UlzjSE9vVKenu=b!RQ#1pM3sVgq4s!(e%5K=-mcY`#hB`QZjTT3kCxcBT7LU3xCi z(|h8GVBjyG9XtYj{;eAv_5gpfXE-1F*Q=-`L5{|Iyz6K`es)+*j><)yRBjnUp>(;#~e$>l@^J^yCLv2&I=&*Xe6928UiVt&d^&`|T~uLC+DGajrUdn!w-XPO;fDAr>qYg z0bOks587YQd#aU|#zOzlTnUX!1ka?yUwc z$r1FJ-djNT0Lm}yfV|yeFD1mSzIJm4){o1pVy~n9K||vN=--xoG!T3{K3~iNY+cmY z3|ObVh~{aUw|ZW9LcdMLUQ9pNftowu-_4&$c^*Ait@pbS_SE!~RRy2sj_eEIbH=)E zP?vD$-uGn~r}OrxErz}q^ON#HzohBvWZ<_VU5kM`OuP^b$lG&!65s}@{94%Uh`zQA z{Bmo0{zcH^uV`fXn7>@TJjTfx&B!j;M=YDq=Gnk$F4mA&$GeH13lK~i_{{eLJ}$8N zD&(?%Zu1O!-C2M23izBi{m=?HM*kR_kJf~n%@-2B-o~p2tFaaM2N-vK`Rn&0UIcXvEF06h9hT@>IY$*~!*gRLM*QB3volh33=52e|MM}z)A z6ga&L=CioIq_+Gb;?PG!sTux1oih18{1TkBkmRKI-41#k1l=cN@3=yq;YQz2 zz-_+U843tCY~)7+{@Y-vCnT6`+NlY8k8?7dpudY}C=c_#)r_)e@cr@T{wdhG7^4CT}R^JX0Xl-3*b2$$Gw?*_jEZ?9xRkK-q_<-vC_Dn<_W?fbTV3;1~- zFJ$tp9Wh4acATB_JLzNW@xlc5zcJ+C5B=^JhEtwPeAa~*6&cH{&qUGl&91G>COFYzDYXhbr|xvHmhF;EaiDG zMtyX#@*u|Nn?LW#N(BNlEDOqy2i<&5#MR`C^m zca*-@l=L_V{%X2xQ3yQ+S=+9H{=PqSsFwbhjLF-M_5VPOwGHYsqU0w+zeips72q2u zyd8vcx7Ej(AAgu^iGZG_#ceeI(0sf1O*`y7d|SO6dGLNyg7g>g@#i+NjTI zko$yjAGqPxchH?+QF|Z$s2I~BgdEqm7J38nk5%sijLf?=rh!ZPT6vrV9j&}!wjL~u zxlkY`d5R+(XkS6=obkkQ;5WD+MTxV4;!4s@^AkOHJ*V3l_73>wY=d@viTzn3vgesM zuj(L&N;7|oi0JHGzlr5Fn#~1s5a&auPYpq?mY|ZA(A$Bo6{Fv>mo9;r|Mm^EiyF9Q zoVP`73ZNUJ|Lr%}OYK{}8{U7!=d1G|U*NW7|A2pWWD?UqWjmkpbDBR~Y$y+3&uP$Y zD-{!6k1xZa&^LSkj#c1O->OgJjw>Zyy?!C=bi%~$6=1;eb93M)xvT=3Kk2;rR%j&7 zwVY>5B)E6`PLJ0OzT5IRQi#j9k8Fy7j}4u{{txW_*#8l9{HwOsV?IdpGGlqnaXVLb zPBm@7f%0-XN58AxEhKs7UY|J%{gZ5KX#F7Q5cmrE?8uAq0KQP~!E?x)>v!QH>~P|1 zxjEYB_Dt+T+-rDM^RWMEs_tU*^m5T{b?Co8*ZMiyoA&KC1HYji=R}4% zsi56?uQPP7k?sw)+PD8sc6F}SB7TBN=ih61pnsRez6WAzzpcq-9PlU|&NA3P=8t7@ zGMb|4!;hSu%*h_@&b0^8?uhJXns;e^oN2uPdW*yp*!Y(>6D~#^2%NS51Wd~n{Q`O1 z7ym|jtmoX`rm+U?Q*83D!hi8G1FFzFmp_;fy1S;;+T(sZzkD{ECqu(N@X(L5`D_+o zva8w&^e?&HR|kB)>pV7Jdz9x6z+Q(Palef2tGI;91Adl#{1x=~*=G&I@4Z&88o>XZ z*-r19p!XrycDvwSNv{O&f2HS6F8E%6pE`W6cj7smf8X4!g&ou$-a3uvL&e<qaKtpa@MZF=ng6_*#@lWs%77OF>eiYfUUueI#o-=RsE^`sV$)a*k^sk#EVfn?irltbaZ#?+EmYr`)Okc2Z zFner>n96gxdHcYpR!J=dcD|lmX@YnvQ(SEdeM`S+vpnPJmFQsDKhvOOEy_nts=5rj zYgxNig3h94H<%s0v&OJr}L-$bz6{Flk`kVj_XnJ?fcH|By4 z?AsAGo%SEzlOCR|;Xv=o2hMa(>OE6#v#kX6LD3z-@PF6cI(FW3PrjU; zXD>;3Hz*)}EAPGi3H+shJ#i21I#dtt1)s1S{zAacUwt(pN1fs__8mHz_hW{UPpGD< z(72}O?1#eXxm52-x-ZHoFQD&VgboJaxqlmLx5p?~UhRAXu)gYq&OECBZnDn`@+Um6 zjE4VCw0+Km-dm4!`h(w(ea$)0|H+4UVPcZUvZ|4d$Bj|e&S>}Z)Ng9=TY~$Td7$I| z@fVus=>MSAQEZ*kic$ECdsz}mZ!y{p)u%?H-vL`KHR#FPQ_%!``Vo75z|BgNBQXCL zsc+3f`)=K6I**`ppo8R>C2Upmo11Itw8_F4rdG?u0D3-d*76|bys9V z-sz*|E~DMex?Al#<5yh40iZ1l>h}eZDB4^x@$b$PxP0ij9xw7OF5lrhlosT4prRU>mUzpOl4eid}oztX9 zc#!?JA6OqoK6JCkcyZkRvK(@{M4r}1KC@MNoh#%R?AX~VASfU9#}nW)n?lPV*DSYe zmM5DwpRh!{oUH0qg?{(nubLw!y(}KhO=#fKxrmo)KjcZ%O!h{*`zV~2Y!vABbdFnkKe`YKXIYeM)X^7@mPT( zom&j*MD~DgM}*-<@b#ADv;5ya$Mh5YtaZYI>diPZ-|9jT7w>IT$&P|amIFG*uv4;o z;5NvU*=Wr6U7whWm(rgd!jl~b^9{*vpV@c*qJ>ukYqpR>a&*vH-6!w+^}D0_wST^eWQ z5yL{_V=>$22l%a+z91a_E)99Ci~iN)JDB~0EDgQk_bXd<>0HlyQmFR+mGelSA1y)s z$UjTFC63THQ+LM@o{#4p<+FAJaT_zx?qi#0ei6+Z2dr!~Am`q8<4!={;b*P_;+yt! zjQ~(a`Wmyx&KhR{^p&`o()z=dvPij1{v!V_UO57GEPt%E1aNdjyZrtfx7#GC_HZ{mMwg(Rlfp6hZzjFiEjPqbkKg|#H-jC2&af<=(0<$CsA_R1K4f z58dxB3qAq5hUM)+qX|#wI7e|!-+kiO2jlqw-q4p?$U8zgVIS%vI~SIs|A4i73+U=5 z`>qEx^U%{55ZvVdfaU}G|CVR^4dsDh;k78&ExhIgzk5ft_P}pXrADWK-l`mjXP9ri zj10w?SDtiE@W;4YXtg^Je4i&iSb(_6SCm@_`8zXZD8HcZ+KHDKfzSAwxMjf0Lfl3| z?~PW&Uf>%a*%uEyBdGOn^bhe>ncKiM;}{1?3Sid>H=GSH&ih}zNW(nfb=Noz^Rk?Z zb`_{#zRW51OT=J&2FjSkqY(W6HP{jJJmROS)=fSdA;;FJBieF*2ieB-@zE<$;d znlK!3Sz5n^)+Z^cg-UtMU!ATFlrPhI{=wb_{I69z)Bb~=>rf4pf!sUS?q~C1PWvHW z)Xyn#zY6+>r9q@O?X#^%ivfK%{YvL5^nI<_QIY6ZnUEO(m{cOI*+6k#cQLaT?Nqeo z*?R2O@|op3Ar`YU(C&~)%~>Jo_k2`=DeT*2Q#}RoxF;&j6nc-**S&&z>)VdOu-m!l zfwhoFaK-i_YyVFh-S>o@-Act&KJY!9<>~KAmodLf{uxEv(bpAhVo)0zUeTQF+LklSX5ll)eu%dmM8E4gs^B2&6vTD5=;>V`+ z-!9nQeojq2Iw9>7Jm#+@@{n0O0_4XFX*_qX72Kx<%zYxNnp}Nm#A4KCn zcI*EC_5A}^b6Z;nXIm?K^QFsHZPx!^T|yWs#eS!xPDza(F?xg)mvXs1f=P;s(a~Gz zrl4Tm)^(6;?wVus;13U{x$TfU$lhNG_UB6qXg^2if=P1hJbkFG=_1Hd4a7n`3| zlb5i0P`LJ{jhM<4o&`m~zdj!N5s)Wphc?Z}3Y<#2afyKQhEF{}`Kf31+Mu%{F1r}^ ze{*H;1;Aft@4gPGnb;={c;MWhl#kPW<#oRvU%0`HBP}f>&EChkb?a6U^$${BVTg7C zvTFC>=WdY$?Z@bRLQ|&=e7u!hX#CMV3dxu=z~j#h$Kicd4ld56*q1puo4msKf2)<3 zhV@8wC+{i?2sT4EQmxgP88 zm`8a3o0NR;y`D2l6VN+%l{DJbTk2*R)LLbW^15k`?EgnC&81RY6c1KE$B zU+^#}!8wYew6x+vI=_7QaNR@HThHsI{UKLsw~iK@cdI0ijx=z&Qayw1WDojZ?`;>e zhmEO37I??2sUM)PcDpR?v*~~C;vY0mQ(orkYXbfLG)a8{deQmM3IG>0Cu?Iq(QIz9 zLj70K3_Iw($T?O5y?VnvWZ`#ysl*m>GgMVB0DlAFVjaYpo}~RI_{{x!(iZ*3jB*PU z6a1X&`we1Y*)B@!7j7*yHXj>_-(i^`8u{V;*z7Gq-O1K{HM?{owy~ z2iDbzNWQ7t$HhU;aZR_Zz$b8^_XX(5MLPMwzUc+`69I#cO=`xvA8nZX74^rbO(1{M zdkAbF+yU;A_rwAEwOn$N2nkwFSjztAY?r^X1m)`X_H6t|s$IGZJ=}BJW}xb$ zX+Ky!k=9QI&fh$*cIdC|WkmN&X#Q}y*oXD@h4CY{4u>C0_Jp4ONylhjqIp}=OZgd{ zb4`=5@o!_6Tn&A!TH|j)zMY-9uYj-CR<^>tCrtsc_#pY6e&GB5nD!#nrvzBW!ESTi z0;}hdK3*vwOwd1TKHlp?dHj=C&tQjq7fA&CRXZdnY~Yf;GD9jMceST_H{i7sh82E6>9U7nTJkA*Bs&!Sv$>?u+SC9^QvtQg}e%l)9k_Y^$xrZVT`)~eklEFO8a>FuFKahQ!%v{tBrSq zzx7WltN%{rgQ-S5&~>kDNdVtoOH&c>`E%*=OW=WbjnnN@woqh`?lzsZ)y58c~X*pLl5o;cSGhzZa1lnBwj;N&WL?-c$2 zH*?}J>@2%-2icp>N0d_7{xad0KS#pPe|PPZ1`IoO`y1rxlFTwdJCUFx59MO<)~P7B z`L?tXau!6lv+-r5^Pn1bP?r055aX`+e1$sXGugN}8T?f_`PO3US0FkYAfkTZSJqD~ zqVn$I#yOyq`MG2V`0QP%d@-6?9)14IQ3G^#wMKVzi5PpF{oVo?l;79pv+uZwrSXYLAbR2OhsVlj4*1lLpVb z0At^}HCxg6S5)*N{|hE=PzKF%A|LFkmFn8 z)4$MumQ>au*#A`RfH%fvuUKq@`i}nZ2T*=_L|!fEx%MZ%N8Io|yqz%ri{@#_Laux( z+rwglvqB|a(EsM*cj_n~S6RjEcsn`l2jqP?3z zx-af2c9+q2sJK$C!|^mv(mWfJ5(K@rs>bC(?{4~8(fVh(`G5d+sd;rsMnvtzSt44O zXuY`F?g2Y1J-$QhA^nfgW!VCLK0RG@&P>mD=M=1l9Tbl}C@mtoozc6PJjOGf$^P!H(N0>A-U*FFpnRHPnMCPoeXTvdGm;ug8(Jk0d`C^Y=rq zv3|?Dz~|Vw`bdnAr|;trpk0iTlmzXxVr%q}f9g4T(77kQr=Uk)g8IA~RTeL?cN%j* zf0tvv70Ts3+pk#fq5A?piqckF>AilI>nQ%|e_W~IPk`5B^4YpFH$au*e?8|9)2>v= zvG?1lFJh|KQav>q^IdJes1NOLeJ)%HJDaY4YXUoL`Bkw3`qe9?{RH^q*l+B7r{Ln` zm%z^^KVxw_6x$Gj_UG2lr~Mw~xea*>P~SB2A>B_UKT0iMg1jd(xcemha6RgD73^g% ziGKxoUQ1$(z}Kv=Xb|t$AMSj?;@HLe#-E_muD$gl=s4EPpTzujl5_bN*x{E5lbZjT z|5&cuED#a@$e+JyB0hY(&VGQsQ@1JhqrI~F5e?Y=MBFf)Z(_gCC!Efi!c!JNjtiDq zH2=~6O5F$PUJ(7?oj4rtP5j}`fiD>nmw@l1kiKNd(<|{_gZil-wicrQnybH91Nz7F zY5k-3fG%9I5_-))|KEEcvo;L8fgJ=LMmyO2FC!y^Jm?_>0Qxz1{rOo$FjUT+_|yBF z3qNW>FP=weG59|`-KYuq#&|!ae24DqwJFehvg!YN_n{YPpP6*6PMYGc(Y=a|*AAzM zZ}TWV%ZJ{vaX+=JJ0ATz9>thI@3YQk8$fquccTUHEX6aFU(x#fI4>CVw2rOs1b)FT z{w(Y&EXnr+TzzAh#yjO(?m>RgzwqY|?Pxb&)~5sVCulx+fby#l0W)9&S|jME@0qn|q*- zZH93m&ZYg6Uv$F{B`(Q)+^=(MFAKstP-&WI0eN0me5imP;@Gux|AziY+Oc~X^xWju z$j%|$XJniM9-N#}B%*e^+d^H@{{0)lW9XN^Y)=sM_@sS_=5;!cOR}bW26Ud~VVny) zi2E@P1e0o}J(vN1UsZj&7V@~=&0hdI_d7GmZ|gZb&ehO-PWS$NeeZ)`A6J|1tI`J0JATCvrveh_7dyD$9>e-Er@L&J_j^vGw9u;GBHm^K*CV zhzPDrp3?$7PDwS52R>=D66I^OKRI{eJM`Ed+Vz#Kf0O`i*~*n#_Ymtp<&~ZDuE1|m zdzaP0u4Pkh@StZ+uWg8!=+vs8qIH+{leI6x8ElQUgg-RWw;QA0{Znip>{XpJ`R^ja z^Xz5M1M0`7odg^kBZ&pf%r9g6pJ!jI&47=~Yq|$Jq%;=?!44xo=oE^nTyh%o89hhy zrhN_M*qf`_1UuB!W#>Y^0PR01zo+~6F6*SwUikG3eP@8~6IBk4fS!@J+len%>d$4O z8nlZXys3q9LnGN6_zr<$w6qVtFSu^QI`Tiw!ySo6uv5gL#rJ`Sx|)$3be>!j^d9=k zsg&=B99-ve%?)zgYpbh zZMM#4wlk~-}FY5Py> zAFcm_4b1=dlU-^2(Y>d&de+c8ia&?W`Dxz%n#}Cnwk2*0;xm4QRRH4slgKR!a_O{R zWAWgT@vRbkR<%82@w{p6;v>LyrYN!bZq>d*wl7Y8?QsC*##Ih}u)}+6-BduI*1xx+ z+>kER0M6~5Xasrui(Gz&-bOc_AK)C+$GO%Ecu$FS6W|lkP$T?vw=IqSccA_Ehg){A zYxB3hFu=OyvTS@$Ufh@s{a2r-7qycAeg5zMe=Ey>>HPDRwhs8Fi*#%;k0obB`NNK; zpD(F_j%wbShtRvR7|*p+UageJLA$8z0#)?$?N4??xv1TN=3Bb&mY;qBdI|UMV|vaw z5ZMhp;ct~|s6VmagW;nO>3$Rup3*?Gk>1@FV;V=iJD_B z?4kT8+ULLh4;=u(E;oetD{!7`uWnAyKheGTHSr#xySAk79_ZP$j1(6UbXWaA^AMej z4I59ucfx7;~p z2i!6HEt@BzT7BQ4|GiKXn)m4cH!bBUiliUkDQY~*72dR58cp{9QE*HPe)ou~S_!>0 z)q08%m&c;U*P@*`_YKig;8+e$E)WwwkkqRUJb8ce1L%41$;M{vd!?nL|J(l_!1}Mi ziCFDEC?@zyx=IOgx87zw8((Lea-M+hhJ6|IT>{#tC+A5(_hP+-@=2~#;M0{Oe3>d1Frcr*t^0!rvnXZhnUTe7h;>(7(3U&kb^Fzb|3Wmwj7O{ucE7+rGVk zo+>w=Wud-H&NvS6>e%Twgv7T<>KuK?guXAV=;MR&zjgCE0s7^w$+3VOU0-L6M?3D> z$LzU|cul2h)L)QF@PZtlcs6Pn2U*T#^xXy8PbZbo|6_FD-RlX9_m5i3e}n&<`%TrL z=b70nK4Sg%h}+K2I}eS*_hq?K-LG6({>Pbg<}>K`YuGb7W38fwVAm}Z33;>_?|66I^XnQABx{d(~^^b23KAP6wUv4PFs>gxt6?&ZUL(c~|^evl5{L>QpG7r;!%fxuC1pUJ;Q|^FX`p@4wVF&;6XlL+uJ!w!OAbWgV zop~Ad=(9{Z4Esck9dilr_$P}(#Bp(A|9%n49eYN43jSX`UHcF1Yv{X*?`n-f=acr( zMbP)3WPO3zxgeJK(K&Zg%pNg8$A*I`XxDaqtT*&H*X~I9AkF87ZYsd@+RJnQoBwhC zBb;<#9riz5snJgo-9h)oxi*R`IzRcNX%qA_t_kQB6aM;c5Y3OYe!P4=ANEgkPh$78 z?%27MBOaQ9MQJEE9*Ep0Bz^2t-%N&HPqyv16cDVsG=C}RXP-RWf%4G%9(sU*XPXj) z1cy~#rJ~;VT#Xs%^oz9Sh=^a&@$b(-zw1T55OPgec6_*jYsNXC`ikZ&T9>Vr=v;{I zoxgc6fgSwp^tS*OKe$Bl(mrBe;sEHZaS#Rpelb`%xQM>PIAP)hra!4~3B8W4ypaz3 ztCbC_LasSVK6NN>at;{@xf1-2QGC)puuup3zk{AHa^lfEMB_y#vj=hES(~WRz@>S@ zYhV}nTq;QJLw&8+_3C*9CA{K6(8+PW?Fo2z%HbH;)p$zOSn!eXYwn{w!gN&1;iYuG z)$Kukr1y?Yu)Ynx=e^!i9MN+(>B%&HDBmEN@+faWC!j-}&DLIsirgSC9WJNBiev&laLR*Cb^a^N~(Q zc`xF9w!=|3@bwYp#G?F+z>D@bv_7VGQ$5{#sICfvKR!q+g+rfbHf~PP-_9+-3HDs> z`&|GS>3g6Aay+=7Hy(UC5@yYSpCXg@MWepzTdNY}P|jNr55K>xE+sm2-zQoy8}x#L zf&?hfUtDkua(mu7m#L`0egRS5@AIJ|nM>wnqQ*@VGYMZ|>ZA130j`%HNu<$JOQv~E%UkWp+2yQdHQoe4XXW`Bvoy8k|y-wM2FTN3T_=w6CX zrw`ObJpBFg7U-Eg?%R9FlQhsn@l5X>u*|0MM)OMTLCQPHpBhtdL(YN=YHa;4Hm#$0 zqI1$v<4@3YcI{tm9#Hx7Q3d32wt9R5<;!gLuyc`BhovXLUiOdc3t;!IW4*@Uuh6UB z3p(jqoMhJJBz{I^`7XlGfY6 z=FKzd1vcP&YntULtF4s(Qv%G^Kf$_n!bu}IbA__U#MH0+Lf3cnU##5E?)%iP4NL() zgW+Y&?}}}AX}p_p`0odop`Gf&d30V!<6vk58$XT9BiKCdd57-`eru+i)2#q{Z&Ttq zcJ7s+IfeZnvigGRav_zM`6))@ytVgaQYq+lB^S&>`!`Gew1<8My=^5p2dZtJFa-N+ zn6}YAfa1cXhV~znAB}Fm4F4oJ=kk#+MStlhyU~8f?H6X3rrg(SfJa-JuzhU3Fy`ll z8x%Nn{5=8BZ)~6q5c>0f+gOEm-OtV48n|Yhz!b$U;7Ompy@MPJJBQ?$zf)={4$U~W z`Tu=4^pJt%FWAk;ukb(vjRRF@hY%6rE$P({;MeM*H_6av;~pcLb?7;ue-@5`-fQb0 zvHLz!^Sxc*U$=q0AkYaA+^a==>D4sTxT8FGLfta(FV#|31s-B;tvZizkxu93PGuRgGLu*fY1F-e=*r9TBxpydqy#{YMby}`LkoYMw=6zRQI_pp!?+69QxkpJcXAg zyV>_J-K}qxLyn(Me(3_g-@XQ|!T!v@>I&JL^3L|R=S2jAT@L+$d}qn{4ebB$PU*N6 zoDKB-j72Jo%xN8Yr}yC`;^NXn-oKEq>C4(c_2~#CPJUF_Y+PYCi_RUPN45k(f7aKE_7%nJBJ$N z(RZiB)7xcdi~^M2>OBGc4ki{Hg#Op_pY8)cx8dv_zqx*6V^`Dn$@EUJJV=sfyiiPj>WWQJ1f6M*hC6VdTUJqfCHSnd(ICC&DVWqh z!#?^BTha78$MRa{QU6+D)h*aFSLkpDaXr2MA%mr-qAQ^PyDw^9_^#TB z5n~r2&pEmOoeAtS(dRnN8(gU_pKiKeK=-)A3+TC8?@4ouk~oOJvSR%N@Jqd@KAW>) zoJH(EDQ5hC0wspZhKka=m=G zZVbjnq>85r;;kSoaojv=ueeri9O7<(^J5YG&1+P*h*_$;NcMhZIQYL%uw!~l4!zd~ z{ypt?ic9fy%fY^0^#8E`&H?cI%U-+}`i3tH+y!__?|dfoJXor{o3o*To2N3C?z3~H z-hcnFAN7d|O^P@-7yll`=6#iHISt5hp;hYv^o#mh(+oMJe>d?0-_AJ)mO{SPoWM@t z37^hIApT``n7P1ipE`>VLY`A5pYFmAu6wO55RaXGiHD*0u`DGgK#jP8Q276Jkd7bP zrB~kl1^zr5-#7%A@-a~e{l-OT3Ab#Wr(jyX*9r108?T^`c>J??^xvq@*3YK%81G4L z;r~6qV^Gh^>pxX?zSl2&mZgc_aEPfq5lcriY&mjOZ?|z z+zys%_apu?QaWWJho&Tn?%9u4n4TAT407W-9uM_MYA!J^_~Q zwfz7(akj_mpf{&|?pN5iB*TFVd1fws=r5x3kqTeh;4cM1_h;B^a6}CI?$U&N`7Pi( zRN}|JgX+1&u?~8Ak9r;my|W6uZiBD;;n&WGsoXIw=#nn|U+k9Hc@pz?e^2Kg@bwhE z^n_mAp>b@S+x8aj#r)^z=a+=}U(ZBeAN4NfDzxvU`wRt&33!f7alXn^=ph#(q4!*f zr}wqrr0y=tn*pO{9&LvI7wCVdC=pKjS=DPZ{IPHN%xma( ze3k1?=-+7_yAyuUO0J^)Al+kn)M<_R;F(Vzs-D;cMVI>5n4e+acYC!Y{44bSt>2G* zhwu0N?*oXxS}#6*my5nbKJK#{_}$OVxrguZRe0_v`RP9VTL)Ta=>Fu>yhFX6-^RbWi}SpFZ#3zB+LW(3w`_#pXGYy{ z0(9|>C%@4Bmesy(u*2HQfX}e|*aOSYpuf{r|JR_~u92}9eAb-1r2=?$eU3f!ukJ1V z0Kfbtm2(^Ma{7FbA?BT0$$>i1anEe-#{A-Fc&ie2PncG-9s1wBb+B>~ojW8Z#rwhk zdV25J{HM{9b{+DZ3sLQa+|LxPn_yS<;82?PX5zSGYi^hJ#-Mlm$b#)O_Y{)GIupw(4^@|;WYZfO6*`u8KS z+rUA7Gx)u_{$LmMANK5Pf&UZqylMWS{}0a$T*mVXehLc8<~0B9*VEI(JTc^HREu^^ z4`bAzzmAvR9O&WoViEaOJU!@HryTg2wmRv8?+{06CyT>w=h-oY8~-2D-aM}6?2jMM zUdS@zCQKBPB+VEhCWaA%LFK;hZM&tb8$}A;_JyozvfmOCHpR`rhoIbBazc*Lu z@|ATq>esEP)C>(*Pl5lp;}?)$r1(&~fV>CY+p|9@fE{@+OsIxD?Th+ozN^%nw$Jte z`unLes{#J*`vbY!&_~MGz0u&Op(X7&_`Mxw^*iJb^0D0m`T1MsQXE9<_6GZI@N@iJ zf;r$E(}O?K(4k#UqXb{>fX;6dxw2 z=Om@Ld#KLxem}Cep*r_VEF;0Ef03N#n^;W`_x0@D%)c(E7kc30ENP!#TjhTvrdmM{ zz5iOu(Elm=YJ(8}3OxDD{=XcmPeA#+jFEZZW7^1YipTRbr{>hiK$p8a(i`w!UdVFL zm8;f}e;~VC_#efiGj%$TUTj7Awa@QqUZ(%4Z13HP`%9GxJwBLErflz(LI3+MkNpnz zmUV4UJ^1U^yd$e5IBfaGB+yOUGh+qp+541#0^+WH8`GwP-l?}OH2y0!{Rewo#yjs{ z{+n|j|HBvTHLwi&>gh`LMEi~ymA~P?uJtuK!|#7|4|r2au*G%BRmc~oyI3}x=0AO- zvB?n z$6*Z%Jdq#&^ErxB=zl?jgI}7_eB}AR{Ai4$pEn1QJ<|Oyqj$FG_h(l>y@K2|g@((K z{%cfICdNl$i9s#;@9S#o$)Fq9G^hsh4!ZBV12A-S@(Jjnv18tQz=#|F-G{#ke_4JX zbb|~Ih-Q-f!CUyWzenF^uXJSV?F%O+xwC#fT~F&^ZIy+-lMkW))dr=^i2q4PwWEOV ziGCR+JV)zSM^$<2n2F#=y``JQ|I43sF9!XA&@1tfbK!r%Q$hdh@-x2y8m;Wyi2T{J zFPnq!xv?u>VE*5k`{4`vqv>$LP4H7L_8$XzeHubz0b{+=FT*cSZRvi8@`X|LA^47R z-5mXq_$ zpDG%_f6w-!D9HV)arR)yH{aawwwPd1=B@4cUUkr*P@M!5I?wI3{7eDK0Ng2#qzs-H>rq7l*Aq)Ym1 ze7cnW_pwq_S9cGK|F-^D9}fH%e^6_d(D>Nm_hT>8yB>=~_#fOd(@xqS5|8_J%*(yd zr}?tm6kp?e72C~_&(Qd0YzV(KscXHcFR=Gepx<4!S9VttzNbMi?*o6BPEIiVKy<=} zM9^P%wkxM@#eUUMYGE_5k162yGWnmF{(S-cko9AVKK$0p7Wc2{hZA#;F9MxmZc`!< zZ)_Q*G8z7EVe5?bkXxhj`gN3V8h!W!_^|spnZp;1}}4v<`R` z)W`VH*^mpEem$S8oc4(hKV|FglAZw-*w3qsuh)QYwtI?80_ofS!=ggi{kx)~e%OUv z`CS*%yn9Gg3iPKu_>^>!t=$xF6*kaQ&Pg4 z4*x{=>nQ+$exin7;Nkm>lO9D!V225{6{PPx&80HYSU{uw4a^TO(a(MeemWD@Zh{|4 zx)$F9dq_NA6$n41d|;l6@jq>|m{!Sjf51KU6ZEQ8n%WOK>FetBz|V{eL&Wg6%93;~ z$p4pdj1c~-M?;c`_Dd&8+ObYqPJaLnd7rsl9t?f^v@Z8YdTvkp9N5FmH?Lp8Uvva? zSEAp<16$bn#$wgWLBL5)V$b<;Mq!*9L?`o_3lrrxa|WbBvyRcTp})zZ@#w8slxs$GJ10N!!1yn$|D*za zR+e*gE<^ElZnHP&w5$(i``?YJWkvXYHq0RT3g&-)L16&oKXBRqawWCf;iXsz`X-Tq z>F~#C^BZFkXD#AMg^>Gr{U&wjAyq}k3H>fwemf2Qa?G`E7of6e+E?^nfmd?yP{Q*h zJm)d`q4`db59FV!JogXy`PK0swOFF(*}OR#e2yBPLi?;C;|`iA^`OT^x^6dNPg%3N zO+j~Y#1E#4cFk2v&pgV+lE^qsk}2jlK`NMIdnAKH3ke z)SO?JMf;CKbw+K@p#6_n&HFcY%|?HCto5LMRGR<2owh?h%gSyx-#t(M_WnWfxEm|L zU*nynY@X7dt@vdo)jyY#bOZCac85NV`=L6n#|+r~U$w#TA?Ppq(s&i}AKEXXc`#4& z;mPy&!DsI37i_=vXhlr`=>1vg`U3IJm7DXvhuphA|eY9OL5U zfVUsgJ$}BG=>Kb}RyV-cwd)sb;74qix}HaWwCr#_0Xp};3_SuE)96j}6WwE|xw5~K z%I7UP^%(WjM&&4UsXt9iQchvM8K*!0JoNu!So~bj=`c=mJq&tYXMRRHrY@P@!;01X zZrQrupuh8**Pcf|n%av?Vej*wxMzW$!IVFi0FL%pU{q@oGH%omeTrXnR0Uq&*8hH) zYPx8b9C&O8{8sy&cxH#;7E{?e!y&w$_T8(r?Kh^kgZ{JRo@(^N^T-GLkbaWN4~D(A zIPR+h|DQ^ni-B**e=GAK{{yY}JLE*)d%qXkZ;&sYcm@6MJhqv%Mf*R?ODf?vQ|jxD zAy4z0IU}&o6Vv2PaYdzOL*Le$hzGmBrn0yvWm5|C6U(NA?Lz(GzwG-N@_(s$nTmeg zUKjlq@&<%7u=~h<*^5~JM|n8zM7*XmDAWM^zXg0dPt;SX&)*+Q?VRHVS7077%(~YO zyDI!?fidW}Ew9315MpFlEw;Jkqc0+3hU;-)q9_ z1>GExhB4d!GgN;M`!DqjC`EpG$ATL4$GhqBqeBUgaio&XD@zRf93WTpn31C(Z%whZ z72`mn=;{Rh^~WoQAbmmfMHEl=-&NAx4f%ufw|7?(AHSN9nGU>r9X`YW`d8ZKL+&lR zFI`5u(D@!owL=8-7ni0y&a@>eYr^DT9&|K_}DJw=^E|W>xwFcD%mE@}?WYB<2_QS5dvLYxXQ5}0z{3UL=ekOIBk}_SwpoMTl4X}%A^$Ai z(!sFj=lhHnN(kQ2dvzcCZe<%d5$w?Tis}I9eTkFCv3;Mhqnw~8Iu=JB;v{Q0SQlR>{pq|Ww(HGh5T0liBUR|8=;AA{N>K~K=Sc4i*+=OX&w7tqT; z@w+Qv`{_LTe?*FZ^pk1*L;HUL1s&j{N9$Sz_{dn;*9<^Uc(~VJ(2nwXEnYz=kMVg)^C7*9^q^!X#&baLZzp02pReGc6YO!~ zuc^$=dM;)R!2gGzO36cc(eU>i_`hKs$JPOge}`y8{@B!gBVd1j#!h{SamM8fI+4Dg zd%hmyExGvPP1M`rvCj+bcZx53g#7y-pUr_iyZHUX?sqMmAN&L4|1q^eO@AGo((3*32{Yk$A1?F}gML@8$qmrg9a`aobltD!Ul9M? zzc{#eDAAp2*Zr=N@O^ld$U$2`s06aGy9C!`9i5bt9g5Kw}MlIN1e8OGQH`!Zc$PdUr68Q8o^23JB`h@Y| z@UZ*|QLiNW!$;N` zBJLRec;6l9^NW7ga?ojQFFFD^!ZPzT`roi&4)X)sMb=rU7wb202lMCM53$c6M{%?E zb>Nr0s``_`#Bc8{?`o<4;s5sj!T;_12W!{mk^LoxtYQBXXzZ%?3@fV$kcdPCKl-zf-!MJ#Fu=XO-(b9T4r*%}_UfxM@FRgdV4u!$5 zsz&V<0*=}F6RkIA>P+gn{SowXFAk(ydiLQSA>wY(!$7V}L(K*#noz=H(>Aa#!`|+6%ze`Ad+jog+ zu;Y{bUT4^aFr)c7_|ecxVEclS&n?ODk6$0P7E}`5S8twWqkhWg`B%_>ZOOG<$Tg;^ znBpb6ckm)rA9RMCnqwAEa82fe(MWe{E4O1DUhA!U13Gm(L#D&-x8yD!33^TkyU)u> z?n$Yg&mez8#P^AaW7I?U55f2ou>E)XUQaDirIPA9m{z1=AMzKq+l$bTk5Yf?10PF^ zhtoY2n)gh22JpMT$?bMy|82l1Fai7&cNcs>y_0_=6`p5NI3y?3{s7h~Ly`s2L>{bw6_V>|k@?M|-@e*bsv(<1?SM-9hf z{=K3$i2N^|zw!&2{a2M|6~Qiz@7$ORdo(dM>4x9UTNp<3AB`6ygD}|P=@$*N!Oz^w z+I;9Cu5*76`0{`JnfU|DxcRTax4?)075sR=5}b#8>jGZA0Y8&0YB!-Dv_CI>54}j6 zF1RE=Q+EJg(0KoY(DNnv7#8O?jyrG}bX%|Le}Mg8QB!CAAHXkQ_Rklt7y zqRR5IFQ?y=qX)S=^qPG^=SR!r#(3gqpXpaI*1_?Ol@;KpZfnZF=#Sv%QW~e!|LK`Y z;6M1!hJPV{>Byi6^haHCU_JD|&u`6G(CO5Uya?FeUsQ^C=jRQh#$mo2P@lRD?fZKc zHUY2l(Ey8cpPb^2fu7yBg?IrTJ8+Wb|4PkIIq@v+KQI5s2lC&alUxLP5$;{~kbmdw zdHJxj{(|eSnD3?s^$w`gy|=^zp$FLhOTf4Dm%~XlhS1MJ{d+m2<#A#s>SqTH>$u-i3bT2iLuDMZYIW8)#hSX?lHV>%n+@>z=a* z{oyobTLGZrp?3`Cqv>JKe2@-ozo&+A_p&8)6!bN4WDGm^wC$Kr@ok>wgV3ZC@VC7} z2a4}$-52UQGlBZwdrM3V=q>Rx^njjZ0qy;mx6Iu#UqSx$jWO>b_s#$9x(&Hr{x9bV zV9m7Cf1&@?)JCb{{)s2eztGE-UBNwow{t!(Mf<0@{d(w!eC;kX@R50BKC|zOB|B4L ze-T;XXTitU{AaO;iLcT-rGDUVEMHn3PxX83%ld|r{JYLgA6H3o`u)4C5&dl-mOsJw zhYI>*PWY#Zx+lg!4?{a~m85sY(_ADs!8Y4}xA+c84L_jnuQx#{99g|57gKHfGnX z#JYX#PmF`q9ZNiPsr{Z$@(|GT^SMCs(z)x8*Zv0G zvUG=Mu95N^Y`!96<4*8!aTd{rU7nPH5pncQx7i4d-nrdOs$nR5p zy`;n-p5X2Obm_Y(U44fxVF~qX(LwRoafKdHt<)&pl+^lIFV~Fz)h9roDh3s(!ru8Tc!* zL(4!f#;Cd({2W`ldMe;6+Y~9*6BV9;BIwVnZgUIx54ay+UP<`V{Y%TBhZir*@4yb; z>Z#7c{zIZpGQ|@VfA`w4`1|yfj!xKR>Q&=d@EzT3O8dxkPJ6)kCC1$k%_FoSf9&3x zb)Y+PddNEPHRZ4$+Xp)xq*sdmU+#GOIokcYOtlj7IBaTL3_jEYR^EiXMa^~;kI}n4 zLWLF5PgP7DP_7?()d=G^d)OF1!~x;W8FS1i&JVX6Oy}8^nxp4OG5=FKto|SH(S6+i zE$rZBRc0Fce@&#zbzK@i*0nxj;Ctn5)e1dVH{N0U%O@XP7y>;MKmS1m^M4rKKZN{k z1qDISOYPl3G(O2sBri9H-%c5s`xSUQmqbQF{@na05uhucVUdcs&qDlx{8E*6X3fuZ zoxKDxH!4SvG1W~_nV)7bc-9rVK1eWiT^ znveS9MnGS;!iWEZaa?xII0<^LJgZOt`$+d4de>fp9t>uOzQnl88g+@zJ%{R)Say`d z?rJwD)40pi{4YOP0Dp3(+k7tQ?bT~z>ySIO?w=s%Yxz1G$RDmYY82!TDzz&`zJ^)x z7x2ZKGK0SFq5n}yJF*n^Z)g-od@$hf+%>e1Ia8-J@O(S?%zHJR z=6{+WyDBWe|GDeGcS8>!99#nPh@Uk3cYlMQt))pHa8F!W*HVana?#680~`{X!s0iB z+_h1xZrz;CPR8HK|C+4b@RP;X*a{EABIk2x7eiO}Dq z>;>#RG2W2=n3d?n88;hZ{231Pr6c`p*L=GFFjHsz?Ax{A2A zQv5ko$3*2c#a%OX1bs2RusgMe%ztaC|Io@&AY$8evWkCovS6-&q~bBrt?KQe<%&$BmbdjE}g&7IGoq7I+OUAv_z%_`qvxQ z#{+ghdp;6!+y7R71^hht%i$C5@rgDGnSMr@200eiELHE1=4#+|O-{7L= zq5ple-&}ZXLU@Pl4~6{~c=CI}*M^Wt2k;SfU3UrE$K8(&haC15ZksR;hQ4fNk250?xJ~_ z*4Y~kXucr-crxxT+SiV4wSXLZ%zMv+-pT(iuzt^({ekU6U%#4YtG|x!pJgOc97q4d zSSnBWE$#WHH?4XL35a&b8kyIf$@L+;|My37^;(MY4Qks3BRuDK>tj?e7O*M zAGi?^fcekWvM>_k>&7av4Ep{%so^8&4m7Sc!F-uvn396{x0l8L^d8W%gC|jMy#+U@403B+mNpM`PYquX-p8_4hN-Fw}c>V3Jl^DgEw zv5)H%oMY_ji8&Wb@^u+T=s{lhvz^_bv+1W?rthfx-PB)nFa0lViaYTBVn5q=biAkG z1$_5E#^nHiu~7``|K*GRWq#Oh$E76jQ_}TH0e#)j_-KIs-{Yj61byy$TbBuXO6P0u zA-AAr>@`5=099k`zv+h#Nyhruj{oic%|Y3i5#VE6(Xj;RuRG&0&2n@Pq(*-Z^c>xH zq!aSi>Q&qZKMgZ;sNZ$u7I!CF#bemq<10r zM^F1{;43_0<|RPawq9NAH>gh9*$VzQcG{}zlK)hVU&7Aa`zAgQ2Hp0oE1BTqNajl# z_f^`x0}FmZy5q567wFpg=Fol-y+_>B#o}yV!!Qx>ANT%~a_N6y`TOPoPP*hgAOG8~ zW{@}|9`XzL<*>`O36H}t9`7^B)1GiGj0hThL|HEjL!HeY`eJAb6}S2Obe0c8K+r+U;E zEwl?vucv!6^#2=lbzxsGB{WeT<3Fg)(+>I97R-MIK6hOmv=(@p z<_uMUzOmu;6wsBmPBa1R*!(UU^5-6tP~4}h?^nN8iu9X=mkFp}9=vWQ_@D5_xgUJY zlb`t)c+YQ(pUn8#>~s(F-{Os3*06`$8Ke3!PFjqsJ1~D4=*Q%uz4k;o#esCs#b~?? z`b~)modP;_&Lh@?j~LtZ5r+vLsyjIZ_I52c=~w9c$q9V#Li-81IR&6+IOfiA!0O(7 zx`&{xGTk8l2kNlbe6D8>Ka*Y2%{hLNP)sW}5UTq8HH%gy98FHVv9n}ZAY5N8KmL{-8=Ue%*XkoL1<{A zGtK{PZ2@He6c?@k{vO(;O=@EO(Y7>&ojQ?GM`MCZ|H|=KQrU(3Fi3MJ~-4DX3X``0rhwEn8p{P&`j0qAv3G_(gCS(}&z`F98PvGKU^*MT6| zzuj~zf6UvK;?}<)$Hn5C4AX*gqOa%>GfjkpV5gC z6n{FZ#&BUW=y6Kfks9!G^Q^iN#+&<*;A@DRd7bT2q}9J@XCEehvVR@D5qu?O)jvQw zde`nO@Z&7*9{`?mhf;6!?<=(sSMbwXXy5|6pM?XR(8J>9Dk1Ek=g<^3e}&zfS_Jtw z9MPkBCr@+C(oEV1qVM14Y###rKAYPw0{T5zPWq5UkZ9J znyeg{{8g*j`O}-7C)mEjGVOhOk4<7V4b=7zLHu7BP{77Xbf#-A+NB-;S_u1(`!hoi ze(Z40a_0Y%)fM}}$Ck_gvh(&dNe|nX`SLdF9rQ7;tdI6zX}@mp+F0=a`}PYLb!q0| zM?1KopUWF>?t}g>?ASC5F#hw5^0n*ee@xgu7|jQ&qiMZN_w;*4>S7+dmAfq0h2nHh zDC9v-7Zq<72i{+>V-(Umo;OAT21UBILf`L-JLvr0QMEOqgv}?>mvVd{{{y|MF3?;5 z7YCv1N~T<+Zux(e!h~v z-8_!(2nO2wDPGIdTzoY+0P|=6#l1}ad$of)L9f@n`!_k!Yuvto>0v>KMK!N;SFj3i#vR%GfzmdrCxIJf+J*+Ix`i68o^! zne^V?+fDsX??L^7|GA=j1QRypKpyWKCr*RjnuzV=poi45w+>kUhmKN9#P=7ye7jzh z2WT(c20P6g@#sXAwxj9~S@+0}DULTSdyIaa*D!t>=uQ0ULH#jPC#CHQ{r}xe9W94% z>r>gq_g_#?{O^Mv*suG_6M2x|)%;vM4RM zdDZCudD(Ml9;CRVZ^&}cZ@-@T9&|(9IXytp=HQO*hVMY9XupdC=zbpiWhmedr%Ybm3cA;FMW4<8Wd4P6%D>RJrULvZ zZUioeAFYXAWCZ$SOu|QluCBXQuRcW>oB9U6B7OSF@9!`#^`Gr#<8bV)61H#sY}_v& z!N2XVeG?(Sa($i?`orWy(mBxC7P5FO_^SKxyXDc zxq*)Rsi~s2R^9*Gr>R*_cI~;bD)PC$R#(hz4O*rVh zUBj7UoDa{~BaElCG-=}sz_04Q6rT;%Ihhj`1b*ZF^J(8CR`a9zC5j^*RgdQ3eQEms zptfW$6yO2G<>lD_?=>+ zs(@ducO;9$R%+k6j(ocv1%-zR3aU$K-ADV4dp~Ulydk?G!ua1(mwy*>kK5ZZ1modi zwbBj#FEn(J0nUE{_-)Mo_eXVoM*rN<9mM7>qcP>Q-le$zYuW|qxB4^hXQbbDU%L)H z?AX636ZAEYuJQ!D?^x^&J3O~#ES+EHY5sZN@;&UJGs`Lf>zlUYvooOI!tHl=gU-=? zv5)geABiKaY9arUjAS|6_wS5b1izDd{0Yskm6|^r+;fF|fBcym3>apcyBx5sWH|Zd zp*q%^x^FmBy-$%HsS+A@{r-39-6mSUz4@&xmhiSla?YT)zUY^)@bl7J@zMV$_Ye4v zdmMhkKQFMC5B`sC9(;TF;Q!3bSFAA09uDpSN!m|nDS*WydMHij4Kl45xq)! zfCy>H@2S~nSL1tv>ErFT9X#A~+tm8Hhv9WRcvVD-k?$y`zIw-tL;H=7!_7d<{VE=KHzM(qCzj=_4Im#pXQcslg`R8UMt=ulH787)u zwbl#xj%;?>jr{H*#wP(CwhG#iFOUg8BR#PtX#!xm-$y&h-#75(E%^PqBPOSkAQ#-V z4du}y3t!M#VJ5H;Q+k=OjE#pd9hW)C-!#na37~ZepXDpcM3d3ps9p31_^t)EvvFr~ zs8ND^Jq=z8-sQ0le$M2)RBXE(c&$yO+Z+i`^#s=<;9F`^XoxHJ}!6Fm|FE{@Ew zK>N+BhBJL~Te-)`cjsE8kU!OYElb-4RbN1wzujB`7^fS-#(&Y{9g9$3sYtOx{x#=G zHjb!gs^%C=a*Fz-@8nc|DRFxt(q$g~(MYea7TtheqTf3*ITY2c$ARa7E641zUZQXY zz7-B}D*!D{xpDA&qei8KuBCU_K6!j%_W$9I4f9iyQ<9aaXR<2rXW)I-*OLZ(c`rl) z(Cy*cQ_;T)saYrL+3z#iAg1zk55Hrm7xz5T7wsJ02R0#H8x$Uh@*rNlp(EAbD~i~L zcAObE0`Tt%er$Z(x<4&K{V4CWS;&`6Fy0M%h8f{999ZZoSEqYa=3h1=cF#yn47BLC1FHNDv=&s;>hj;hXQ`L0v4)K;xVmANnO1E)9 zdBn_htUgy(%|Q-tQQ|VR4^nJyloOm5%n4D?ewaCM1x0s~5StN=)??+^I`| zuHwYAz!xR6Do1&}QQAt-(>^Se0w&4d7|JQ%i>}R~yw7^i4bVBm1rGzXsFqa&+O%&j zI7Tz-kRhRiX48DKMeN~$`qkn8SOY3_3)y(S^f->G@LHYD(lG%B^DbDCA4uL*D!itR(ne%Ekj-|sm zk(kN_o~G5nzuSM62+%<&Ed*V+iQGlNZ52v;=qa~o^Df{!8I#1ulTxV>hji)eIgCGB z7x#}y3m-UVgC6H-NxRI{FJ@+^0ePI8VMUx_XlbIx7_1_`W#=%=6##V8En4% zUa`+CkN8MjQ!hk0UowgH_h;9M0;I*sky`;pOZ6(C2i~mOH1J()<d~B8p_K8|G9+vQrpeU&zYZ%dIo$N*CqKVf3T*E&1YP^RuSx-Gn2A@ z4!av?hIXd4E@`Nj@%D~2=qo%$?Rf;Bt=P%N*Y(YgOb*A*_lJY7)*%J6+licp2JW94 z(}2MLP?`JK`iTioroFHLP$U$+1{4&{IwU7pW^5z@bm2P>0DoW@Hz1+;Nw_fCf z`Z&-RwO25^;PR^mP`{z#+OAm2m-P-zLOW?8=NUrz4jGEo;797rNyU`@EzYqI^-Q(y z=^>tQP$+mv+wU!8_QBb5I~}Qh<3XqhoDBG zKzi`tSsTDVS87)bJXO_?_El1PAV$eYdA#6tB+BQhua89eqv^b6ZDTOtuS}HhjM|n%O4>GRezb72xwpE#O9F|QcI>srBvYv`Tb=I-Z9H6ZJuFh zBL06QDB#=pcZqnbMEP081{SYuu~;dcN&0S&HfH^k#zp&po@tsLqi<8)p1;@%+tK*jEm5Q2Ip{oc^; zZZm!o(r)#K<6tjJQ&+~H&^vn!f{|^$IGym^;w#^p&|0Ad6GwgIM|GOcU_6E`Uox94h2^%-+)i z8^)oWOLG_u{Y5;DQ$lb3%Tw8W>gy53#{X-+{YKQIXIxK#uCvApvtyPsbq1+rn_~TE z3)Ujrz!)I1A%*+CQ8Ji_h%7Doo8s!^%~T(A3B`zxkH!Jg#ODmRhA*2%(V+?rPB7j7TxUu2!Q8+8Bvu4%lS(qZ=%v^z+4)6`-Q{u4A-?ye<2qmVwAqTFB+-YKQHtyf@& zdOWAjVC1Lq5)wiP$}8QkBP}fLj}Q|q)39dxEo%>d13KG(6RoW^q5o?Q9TtxDugxAC zf7Hw0ZdCy2DR38pZ>gJbBGN)(@vIPn!Ho~sRB3Cg#DxpT$SM6i)SKDE+A$o{ll27c zdX#r=%_sioJKu`~#eg0BEOWGv-W<(A9zM4!4Qc&*oCxV^YlokZRwxP?EK{r9g8W~E z+*vW@M>=^Y%2+->X+eq+H zs5^c)@LiG#DsgTl5*_jeAL$>Y72t2l`Vb}Pn+XII&(Y4#*}VPl z^tK}ZX;gcmoZ#|yzfYi-v&@IlQIjI9NEA$!0#TW z&j!47ekh9*D|)`oC+1JM?PAJTlx;b-g5=@Gd3ZvuweJN?A7z)e8CMcMQ=vDlZ zF0=pY2y50p-KAuQnDVdKPPGL*Ea9S1zkce$!${wWb1OlbXKW*Yeq;_x_Wv|J!D8$(m_5E3VpxUt zuj)6m{#>$v!i=n4Mdj4QQ`-=?C8mUp!uwA_K6^b;-?~AsP)yL|fDcIPnK5&64Cl6xE72mn3)@cUp<-I6u|k# z$IQ{5XE9@BE%o=e{Wh|TBF-3m1UOAQi*k|Sg?`lUJle(L>IUBLEPe{=I=2XPMQ&cD zz|TFEngb3BHCv7P1<_m)@Mr|y_!sa0=#14@8%-9s=l_lWuWI=8qQ2N%#^N3e&YtZ9 z7FAaXfcKhL4)engzbcxM-YYCv47{gZ97^Gjr^N)b^(ogT^+x`t5$R0trd%vre_AM_ zWxzkpf^!yAxkP<{l}9b9v654ItZkGJpv0r_5%|=({*M;wKX)>d0pFn_-;sGl*QV`! z7WS>UN5M26=$=vgE*031daKVa;2mFG8-ey=+g@>KFX1+4?OQ4S<(kHP-=Ncz1d~@LA1{q0=}zM{B8x z!T$Z4z+$N!^~NoeAzu;Y$M}|UQEVLBZs1mdAB9dFzfkUJ;|O!>1^Gtw9y}l@?{HL{{vrUPqo;%zfzyZ zL%wI3EEsgSEB4I4S()7$=SurOf2oCry3qdTmaeW(VyZXQ_B%FDo;BByBAwVLTnjnc z1Fhc!Po*=LgYoXBkgB76`5}L+vDeAR>YuEJs= z`1^|!F#fc-LLvB?pm{}sa*L9VK;UT~pnKy4n+iQ{gT7;=y*0|qtPA%7$|B0xzSkj@ z!$nov^nI0kU?=iFa;N z``f!@SG|H-P=56Bqf=tS=WDj!40vtIj5Poka_zU^H12fsW5< z=~U!{|7wKqjb8-^xf9Q`=Pkd%WUqzFk!?V^gIRkH_;CJd{{+-) zk37TrT_Y}jCi2%hnpp!zsmJ{Q{0dIC9PLt)Cn}H@v@c=t)H=~l5z<0_qz&lQ3N{y_ zp7eq>+lNo%WevcOi}>HhQTtG5CCZE1<22BY<2u_I9l^Kryeh8KYc$Q!$%ZKu|Lds! z*R|bMPW3OG?MN0AjBv2-1&o)Cm!Q2!=)m-DEsfg=yk6nCLcmL$n+$lXzth;y>b2h6 zh4g#dRlX=UE|#4G6!rN^kiSAxgUx4Bo`w?X-(RIMyO6Z+Fh)Il+pi3tXJXh&;A{3f z;wz^7B*~!!)Ymw?g3WJN1+r~OA2@409QAtJ>?6@$kykSVF#4nYB(&R5=F9w1KgUT? ze>Z3T2v9uakYMgsy4RDEl7jzVblu}4M!RiQw#-gAMJV$FidlajV)(dzHvaN`e^|ui zEbEAXzmswme3YLuneT>qQOmyF8+4~0idRQIx0>rh+G?k@lOxgPpEb?~eeSdw+m9A` zR)2>yZ{>GPKVQES$uO>jeSJK@qCWZ}807*18;8c-z8vtLzmXpU=$7Wm##gzvg%;92 z&TlUP+AG{F!~~@_hvLzG+OP{8(uEPcIHU_W1^@r_{(+l|&;Q@s2ee_S!hZWy_NfdV zG<1*(>5i>G)zs-baPhcLd-iMx-&R?dT*2QV8w)?cy8=#t{M2%R2>d^9?_&BGA3xjz z`6YoV%+5*#9NpufIJ|GI8|sy>brGOF_h=jIPbVu8vls3?w*vWR3k0lOL&%o^PfnZ* z8^06dzWJNQ^~xQNRL_6yO%|8f3an}TYpdv12QmGde=gY}r*=_ker%kzhq|r+o=@gs z^Fd!2`I*`4!rekfSF&aN56Bl;2DA0ZD++9|o>v>IM&ECV$NBj9u=r8Lm)fA7O!jSm z=&a1-G5F=h+p_qb#5#*|rFavwGhglz<0r!Alo0t1mZuCt2)-3@(kg9j8uu@ikVDb_ z1Jf_3ux8`dn-h`MlYS=4*m$#u&@w~4Gdwfe=haquF6G#GTJVc~4d}13tjjJ;_{Fb`juHS3Q7nT)l2?vri~x)U9(6sWgiWd?(rIoTzoIdjPd$mQZ7VDqX- zR6GmyPYE}&a(QK)bWH z+gN{Fn#s~oPvEm*7~rGRn~$RWfwf;cppD;P8_?wionqs_`?Zp-FHcQg{hgS~Cl>8? zK)wF@R&zkvn(~i;#nv|)90`hc>^OvaidPD@-hLkErwjRbJHDNdInL)g)CwVb_83v4tLz*}E^d8Wgda99)lV?89k0HI; z`YPkY+PbgWk?O4;6|n{NrI!sYkUq<0gd%-lwWU4y-ezITXCYs<;*|yR-Lw?U z4r;AsMo0@xon;`oS8%TB@x{sm^&Pp2a@4b%9nbu{P-0;w zr~DTj&QY~h&RDuPqu%efvR$w@Ma$X&;P*MJD+F|n+05iImI~LP-kq;Ke*p@D^~z9w zYDy&Y7XohPA@IrdS}?yf(n@BAdI^b-3_w38g7+Ee!ev*P-bU93Gd=75hOjWyr zb*_*39v_sabom9KzGK3kwSXGhB4^MWkXG|h{xr?M0Qsxj1#ba0qf0pCpLLWmXfLp5 z`&_%Rr}`OPudMXET($d3B|0v?B*o4E|ysEoKSB8r%=kNyxS}?1^E6kmaw>& zt6eW}q;f;gK?>xjJ4zTG4K1HY)K}h2+65?>E%}OaY&+MuE4c|X)+3RZyI=O4174EdY7 z&6fa1I`O~A38o2UY##Ibyt`IR>2T|4?wD6{+_sNzTrm zMSXIr-_ScX7IJ5Km!1P2g=Zk!Zy0vp&kpqFi}+E1!s^|8jC+?J$y2mbe`qiW^hB1j z29zr{PhfgWwX$a8$Ru4MLp_c&ya2o_Puvh7{lDk!6-Z}(Nn>_mn#yH^A73k}6#3QO zqVs?q9rr(DJw;&$vwvMqo!O;z;T0AS{kDP6>VGek=AypyWL6f}VoS?MMsS8{UJjq6*nN)Jxh?ng%$`u<0J) zy==Qb#RMnB`7wWQZ7X2$l8eZdtrsE!g;K1~pb{~y{1 zPEKU=Z?;NA1JZdB4%MLB6!?t!qcmx-C)!KPZ!y0Vqwc`sugR(3{?A6*U(V)r`xpEq z$Z5~-umK*kl0Y`E*u1ejhy3CBpNo*cZH+GnxbUEajh9Of_IJ^r3dOT+fW{-6YXM)l zC3~Qj>wo+B zqXOhdDV9m(1Vw#EguwsVyG#l>ySD3X0DhhZpV_saxiA80v++NU2cGD%(+rQU(mD|N zPEuVC`BkC{291{qypXmJ44nfgb4a%WG%cyV2e@5rCDU&szlhDxN?XTT;8|tOnW3Ch zm@>c2=i)W+eTKL{o2{F-Uhg-DUIRVKyji_MlMp%8myF(Kk8=(U(ems(D(}cSaSiv> z!~J@jkzaju%^c{lEI4%u+ADJAeTQ}*xD7`fiLR|su~tm!D^Z<{Z_c=g13zcYxSs$m z%p4N|w_N3hp`Nz`cPfOS!}%BM0egJAjRCoC(H_*BI+QdnO=%q5jGw(B-1> z{RG7kifv**VRsXozc!dnWb?R8x|+qoGIK>d@HpCXbf1;pzdB$@-@j_p{|}Ux!Vip% zUi1}m^>tiajs0!mWYbaj4#IEYQ+mH6WSrDk;f=J1I5`>q$l(~KePRYCjYP-sOK83 z!Qw}rgA?JP2tH?qmf8~OLN%;OFanuZYf11*zy}hVE^8I2uzgeN0*!}n~e6R4T;YkXct=sz z^c%%rqe?WP9kH7mU}1bULmkEt-2<1-if zk><+TxJzl z(x(3d%I~A^S?T*TNy~G<{;$VEApf94R`ma6^zLjhNAI)E)R}+4zZCgPKm7OxcHne0 z{toPffBx1x?2rGlHT5C%n)6@rFLI(UPcEGcd52|ZHvrzAc|HXFUOcT~3gitO{q_jP z-M#EI`o4$0Bh0Fs0enFXb3+n{?+VwR576&G+fF)%r+3HS)Xtqr`Ut+Wcr^4f@c%S* z;%a&2R2 zK@o}u)uqC9EzQ`kQrRx3`0)_@bk?}65OJGUTet)N=cHba2mcopbwh}o2pz3H@QmQRS4Mno@%QdaBYS5J zvFy+nWYgt%Bh*J95y>aPPVUVoJ8(R=sm%lNuzyfI4V+dJ*Jjuu;E$>_@P8EQk%RoF zk*q6+`v3>5tgK+KmQIs7$omtSA2pG`Y%hMd96Y>AKhA)EEnklZe9H!lb>N?Ac$2EA#D|k$n3Z?*;Q|`s~+?yn^1+1}`Uo%gQGE^dRq*4VOCo*7m%FoomlBkm1OKnl>?=c@G_8C_qc}2F zO^z)Auf!HJs;lf)ZCW$*3giAq@39>4?YTxl_?>H#+v`kHCrmQ{ak4*Dd|);mzu?9x z%H!0>H*fMLc+1xt=K?o>|F9bDv2Y-74*Y+2mLeB*Eqp)LD1J|B`0EailZjqJ^^a^u zox+1+jH@UR8Kdupmzp3EdRVRJmBSBO5myRfhti*xr9-c6FEeYQcbKI8C61eFZlyd% z=MN(#WOoPo8*59>L!a)O8Pew%zmz{e2mU<`339-lCk(0tuIx<671aGQV|tE(-{;H< z4sd1WKX?Z{P7AM%VtcwVT?=_^vh57L4^8#&=X+nle!(ZkrR&Jem^fLC8)aMO2i%$4 zeb3!7#&lc;_~*VVZ$|$+Mb5qO zv{mXs`NB*~O(I77;tNPg&EJEJ{OMk&e;P&|y#zK#-RG~e# z9sBLNQ2+mMYkCUeVMwq|0{&q)_SnLojQ2^D@6=_sKdmFb(0RI>sU`G?^@!4e|I2l! z=y$4r4w>J9c_1u}s0080XZecAbF)^@rMmBK)zyu%`+z6>*IHlTSr&|oAYB*t&~xER z)osHLORU&{Zhv^9UT z89&PB&Yk-!{H7*$89GMermFNuqy9D4!Lk$hQ~8Z*;9nMw@84=`Xjfm1Mm=`=S}Uqs zroN4A4#iEf>WM&=ap)R|-H15U99YH$ zt{HQeH{x0}$5a{o`_0X1-K2iZZ$qn~N4~!u^`WJ{P@^4Ezsp|E@&$j1`+Jr)@%ek= zFBW)q?M#0P=``u)0z6-XRtE4i|NLvx#Aa~rhbSVinXMbh1n=4Z zp7O={&zUCcX>6YqM`j`p_5CBVV6RACuM7A)_DX!f$JU<5!nlE4pQFJ46sFh>Iiwvj z2|0X{EzMV#`dya7|0mn#OZyIFZWhG=SBWz$M*LaybX5ay=(Mvg@M@Odro2SoL-Dk| zfc@S!zkY~*_boZ0q1}X+JeuQ#@%aiePa(C0=~BE{haNuWzkZ}rIPOca_+PGn;*1*I zU@!Ih$lnoF%Oel6G#BXrj~i&b82cA5Rjk7C zY6cu9*z;&mt}J+dwz#zbaz^}SA#fyn1wzC_pv}~vGx5)01<-vhHe=!TV0ycP{OWl2 z`y|4V7=3xa`rL}i4B$HQvgo}-HbXZ|pZc26yqx+H{{ubOy`g8?fU!IL&zqH4fczJk z{X!qSyXvLWbJh?1 z#yqczwCTFpTP%>^dLyBoX$HLL@^4y@Z>u7s>HTr)UvTU0EF6D)TFU@_)$PyM0p8ST zAg!;|UuMtpILM!jJNh9zH9u5gzXYcXZiu%Od7eAQ6(-bHB$K?bbEpV*&|mSP6a19| zR@_Ja2NjlV74!$^Z(fz2L*TAV1La{v2-4U zXPb4UY~!jpzC6F8RLn(NZ4n0 zh8g7#dM+90t^!=SkKKhj^j(?bLdzQ9_7>dfMBa(?7X)IyRc&stmdCn$sDZ8r=sUfe vjx@qvfp1<@{j-Doc&fD(`dZB#(Jck9&^z^u9Oydm&~M63|9t=8pMU=kL?Htw literal 0 HcmV?d00001 diff --git a/gamefiles/neo/rimTweakingTable.dat b/gamefiles/neo/rimTweakingTable.dat new file mode 100644 index 0000000..058d55e --- /dev/null +++ b/gamefiles/neo/rimTweakingTable.dat @@ -0,0 +1,130 @@ +# Ramp Start Table +# SUNNY CLOUDY RAINY, FOGGY +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # Midnight +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 1am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 2am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 3am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 4am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 5am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 6am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 7am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 8am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 9am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 10am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 11am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # Midday +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 1pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 2pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 3pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 4pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 5pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 6pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 7pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 8pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 9pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 10pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 11pm +# Ramp End Table +# SUNNY CLOUDY RAINY, FOGGY +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # Midnight +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 1am +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 2am +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 3am +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 4am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 5am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 6am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 7am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 8am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 9am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 10am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 11am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # Midday +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 1pm +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 2pm +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 3pm +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 4pm +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 5pm +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 6pm +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 7pm +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 8pm +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 9pm +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 10pm +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 11pm +# Offset Table +# SUNNY CLOUDY RAINY, FOGGY +0 0 0 0 # Midnight +0 0 0 0 # 1am +0 0 0 0 # 2am +0 0 0 0 # 3am +0 0 0 0 # 4am +0 0 0 0 # 5am +0 0 0 0 # 6am +0 0 0 0 # 7am +0 0 0 0 # 8am +0 0 0 0 # 9am +0 0 0 0 # 10am +0 0 0 0 # 11am +0 0 0 0 # Midday +0 0 0 0 # 1pm +0 0 0 0 # 2pm +0 0 0 0 # 3pm +0 0 0 0 # 4pm +0 0 0 0 # 5pm +0 0 0 0 # 6pm +0 0 0 0 # 7pm +0 0 0 0 # 8pm +0 0 0 0 # 9pm +0 0 0 0 # 10pm +0 0 0 0 # 11pm +# Scale Table +# SUNNY CLOUDY RAINY, FOGGY +1.5 1.5 1.0 1.0 # Midnight +1.5 1.5 1.0 1.0 # 1am +1.5 1.5 1.0 1.0 # 2am +1.5 1.5 1.5 1.5 # 3am +2.0 2.0 2.0 2.0 # 4am +2.0 2.0 2.0 2.0 # 5am +2.0 2.0 2.0 2.0 # 6am +2.5 2.5 2.0 2.0 # 7am +2.5 2.5 2.0 2.0 # 8am +2.5 2.5 2.0 2.0 # 9am +2.5 2.5 2.0 2.0 # 10am +2.5 2.5 2.0 2.0 # 11am +2.5 2.5 2.0 2.0 # Midday +2.5 2.5 2.0 2.0 # 1pm +2.5 2.5 2.0 2.0 # 2pm +2.5 2.5 2.0 2.0 # 3pm +2.5 2.5 2.0 2.0 # 4pm +2.0 2.0 2.0 2.0 # 5pm +2.0 2.0 2.0 2.0 # 6pm +2.0 2.0 2.0 2.0 # 7pm +1.5 1.5 1.5 1.5 # 8pm +1.5 1.5 1.0 1.0 # 9pm +1.5 1.5 1.0 1.0 # 10pm +1.5 1.5 1.0 1.0 # 11pm +# Scaling Table +# SUNNY CLOUDY RAINY, FOGGY +0.2 0.2 0.1 0.1 # Midnight +0.2 0.2 0.1 0.1 # 1am +0.7 0.7 0.2 0.2 # 6am +0.7 0.7 0.2 0.2 # 3am +0.7 0.7 0.2 0.2 # 4am +2.0 2.0 0.3 0.3 # 5am +3.0 3.0 0.3 0.3 # 6am +4.0 4.0 0.3 0.3 # 7am +5.0 5.0 0.3 0.3 # 8am +6.0 6.0 1.3 1.3 # 9am +6.0 6.0 2.0 2.0 # 10am +6.0 6.0 2.0 2.0 # 11am +6.0 6.0 2.0 2.0 # Midday +6.0 6.0 2.0 2.0 # 1pm +6.0 6.0 1.3 1.3 # 6pm +5.0 5.0 0.3 0.3 # 3pm +4.0 4.0 0.3 0.3 # 4pm +3.0 3.0 0.3 0.3 # 5pm +2.0 2.0 0.3 0.3 # 6pm +0.7 0.7 0.2 0.2 # 7pm +0.7 0.7 0.2 0.2 # 8pm +0.7 0.7 0.2 0.2 # 9pm +0.2 0.2 0.1 0.1 # 10pm +0.2 0.2 0.1 0.1 # 11pm diff --git a/gamefiles/neo/worldTweakingTable.dat b/gamefiles/neo/worldTweakingTable.dat new file mode 100644 index 0000000..b054fa3 --- /dev/null +++ b/gamefiles/neo/worldTweakingTable.dat @@ -0,0 +1,26 @@ +# LM blend Table +# SUNNY CLOUDY RAINY FOGGY +0.700000 0.700000 0.700000 0.550000 # Midnight +0.700000 0.700000 0.700000 0.550000 # 1am +0.700000 0.700000 0.700000 0.550000 # 2am +0.700000 0.700000 0.700000 0.550000 # 3am +0.700000 0.700000 0.700000 0.550000 # 4am +0.750000 0.750000 0.700000 0.600000 # 5am +0.800000 0.800000 0.750000 0.600000 # 6am +0.850000 0.850000 0.800000 0.650000 # 7am +0.900000 0.900000 0.800000 0.700000 # 8am +0.950000 0.900000 0.800000 0.700000 # 9am +1.000000 0.900000 0.800000 0.700000 # 10am +1.000000 0.900000 0.800000 0.700000 # 11am +1.000000 0.900000 0.800000 0.700000 # Midday +1.000000 0.900000 0.800000 0.700000 # 1pm +1.000000 0.900000 0.800000 0.700000 # 2pm +0.950000 0.900000 0.800000 0.700000 # 3pm +0.900000 0.900000 0.800000 0.700000 # 4pm +0.850000 0.850000 0.800000 0.650000 # 5pm +0.800000 0.800000 0.750000 0.600000 # 6pm +0.750000 0.750000 0.700000 0.600000 # 7pm +0.700000 0.700000 0.700000 0.550000 # 8pm +0.700000 0.700000 0.700000 0.550000 # 9pm +0.700000 0.700000 0.700000 0.550000 # 10pm +0.700000 0.700000 0.700000 0.550000 # 11pm diff --git a/premake-vs2015.cmd b/premake-vs2015.cmd new file mode 100644 index 0000000..fc1bd29 --- /dev/null +++ b/premake-vs2015.cmd @@ -0,0 +1 @@ +premake5 vs2015 --with-librw diff --git a/premake-vs2017.cmd b/premake-vs2017.cmd new file mode 100644 index 0000000..f3562da --- /dev/null +++ b/premake-vs2017.cmd @@ -0,0 +1 @@ +premake5 vs2017 --with-librw diff --git a/premake-vs2019.cmd b/premake-vs2019.cmd new file mode 100644 index 0000000..9971831 --- /dev/null +++ b/premake-vs2019.cmd @@ -0,0 +1 @@ +premake5 vs2019 --with-librw diff --git a/premake5.exe b/premake5.exe new file mode 100644 index 0000000000000000000000000000000000000000..a8483721467dc3d67ca25ac14a6ff811c0bb92c7 GIT binary patch literal 1395712 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ⅆ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

i(6op*zHAGF~FjChrxzWQ^fE$%qU4>7{%ynK5&- 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 z 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 z»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%0D|K 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

@oEb4AvsdOSoFa&@emYgLE2n z{6vE=x?r$9-lWW0sw(zI)gPoBIAhlw&U`EGU{1(=u)X#&?!ti z0y5>X2+32-7@Y`iB)r_I*Qj>HO-2i;oYWs)j0#B1w)R%s<>Fz9FE339UOF1ku3%K7|AndOc?~} z9(GLwDgP$;G=33g7Yr3Dc~LQuhzw3%nKZ`~#wVE6d@DV|M?iBo8YM7NcOlijUS2Ya zHtM3Z$7S?R*1x$mg4AF+NI;TEG^^Hv{##qo{7EXRI~e&x;!16_96+(buTIlc!;u8U zeWZOFdZcFax*srH7`zH*T3R7!O530Cf$awj?7toSO`eW1_-#coBDjGE!TYt8A#dVQ zPF?9F$yk|z!IU^;BRo)%lNvX4TpdYjsopE`Q5?FQHU?XMpi3y0`ZjVOx|sA>++|b~ ze-v&lA*M#=Tbvc6j3s0W2i4*c47P+~t=uJP`4D|cRIC=$(y@e7`;o84!Y)XXJZi~& zYKDjzqa1JYX%xM~@z<1)iZC5cWJXno`cP}F8c!q5qC_*}Dshnul8BYGbg_|dL+Zg* zoYc3NGPa6of;cTkfMYIA_X!D0lR35;R4_?e*+G}!OrcV6my%ywbCQR2ngoM$iz=&1 z%F84@tnhJJOaAUVR|Z7z-P2LH{K`%NI1yk;#nX z=zf=)LOP33U30Uv+O4dNJR&{9_T%lkdy8V;X|AhNxRG4cbEo7F@d}%91c~An#XpnS-KFpgGTKZ0psMUku z#Nc@GPfw&v7VNW_;!I&%FiU(2*WxyyKT?N~i<+F!pN-(3#Z<*7xY;H!vYJqipjJ=7 zRMdbHq`j1K2lQ8&%EA|t5kq=BC=Ov+3$Xe6qr>66$oK~1cO|-foVA3UL8C~e^oS(@MkSDnOfz@)p(MW8)LO5O%fY;=nGK1^uBI9LzY@0gW!nCQf!Dk=o;MepvBjFx3WVS0|#v9u;XSdm|?mTiX< zB+}SOIY@|t`_k9WQXwQE6-d&$}sYXTCH zot6m214jOl@IHSl3P33+_~{SQE4qXs4f=3qb6uvI^D(~V*Ukt>W|bV#G^eGirej{` z{ACN4ADVvj(IdhmI!8oC%o%SOmrvgHxr!aTkZU}_px z4+B!%bm-1W4>Q*D0z^QMy#X^%Wwzwa&>0P6=x(4~jHo>Q!E5nq&OVyTi|{MFTju)V zHl7e8U+~5lsHO!X`?FFMR(VK0!y>w3O6|oSiPhCf{_-8|jE38BhC@W-2<|W)rM?i) zwx$-kN1~Ss{@DXZ3&}Hf?^V)d{2pWBC;bk zQkBriZ2sntR9z}2brwlgI2ZdXXtcx9bH|IQCUBck8C7K#s->Cd=oE}SfX14rA2*Q&CrCeF|UTopp$VFTbEnxK7CkwJV69- z>2ox=k*Nxy+YozA-;|1wj`i2NAaC129TP1`QFmh#)Rxb1t7PX#QyLRqnrSpjx*pI* zBvWJ<64H9dE~z7rh~!qHeJKlkDHJdTlGGCodXgaDYM!ohmZo)-a3pcyHe#ozDcZDo zvnJFQMo_5NY6`|(utzxP4G@+bb;PS(7U^*p!NAtjsJON~Un9KKsTG13MUm``kip4w zOQJ@(fq4g=rA+f3o_Tjd;bv8b5VKdD(50cM;TMjoDL*_t!91@Xp43wLQH>zJ2;nVR4~=KLPxMCFMaz zTOM8~ZLP}{IZtH(4?=*{3Ow6)sG}+`;~+aqS|g%3Q2EgYI%%C|EUuDdFRqWDd)yv= zCH${~F|Vj=pEczty-=gfyq0l#trVcNW{V#L<3 zMQVmWW2r(Nts+CQq`9=Vz0THORf9fYDyV|eM-i4KY+Honl!=@!dB&^S+R*_~IEy3A zPY9#l)WwuO6l4X2_);detIA)eF_dWY+dCQ*c}o3SVf3n6@%9!)r%c^jLrvt7&}$-6 zZY#TqY2&g}iH${i!Y6B{yqHB^32+sc?3u5~Yg<%7xR{v;*>an;kbsg42rHby4rKlb zxa5FHi{SGo0Y0#!f6H<85 zRfQB^Rf`T5}8@+k*+5rvU#OYuWAUc4XPk@qjK94Bz^XN0FvxHx7L8naRL7L#irWeSdC3T0#HJOH@ zPemx`Gxf-tu=J(+V*#r)^d$A2CyHefp5{v+a0G**Oc?M%pBzYpVLsC5bfUsp>HoE) zkS(!wI$e^Gc0!iYi&|=hNoXgq6cJ3VIZ1TQqiMB(DuurY9&RLYt%DXJZX`<@BRvG* zMPF%D<5m)vo>J7}2|3z+n!S$1GvkGfD>_lwiYcvTQoe>tEJHBt)CzpbQ`KD;-m-ba z7Fidt+GoOLNYxZW8l}ILNNB!Aqen}rF!fP2y=`vw{OAGZTbNL5vn&NUBd3m3NQlK# zabSW7(LvENJ5mCKQZ_ZWXbq2tABqGv=&ni@jx)H6de{??giVPSgEVCnX%qabZ%LZC z$&?+Zy1cTosI2AL`w$GX zPhT-p(^Kxof7@OC@l+Pp&lbvNY~54ifLg>Y>u2L=*QDI!xl85iSST{% zShC%eRBu|%-<}3Z#zzPlY$=8=>I?cpG}IK3WXnF z%R|i6>e0R}6O0>Nun46a6NaK^MoKGKjjWxZ7YNe|p`K#tPUugq@u<3Z@mN@DjUihE zGQdTpX#n*yx2L@{^S?vdrS%N9D5|Vplf2i@q?ld}6w?SKLg3s6FnvW%97sLj@KNRq zumE#P0lu;%n;rD5z1VK#)U=2q+LA>Eod@RH+Lm1#1;3~n)Jis6gzD->*}e$v$iypK zCUZvoQ8{>v4PEmn*RvCEZ$&i}BrPQUjYQy95lu_B>J{ac)uu?kc2GnVsN|rS&#%(! z*=q8m(i9q$wPQLCwP$~g64-P{5}8)bZRnE-T}@DUo5EImize9es%puzs;boMAr`-0 z|AdNUcN7C*T(BqMeST_Jl2Af!cDA7i=3a&Qnnx-UKQ2yh~!=F?adI3hS&@uT$FmFnDRBZn&O$E1fFQwnU{@3*2*bZn^eL`jkevT z)T)25dxQC+=24LFV$B>u2k*cx~v6aM~%mnTX8mx@&fQG%_tn9dpNL&B`G8?XsGd*R%5iuwdP&}{z`CsG_+$7Dytr=HpUEI)UhOyXctNr2|N(0XEuyD(2cllX@rjB9=AsPK?-hFTNH&Dn2kzn zE1EDeofomR86q~Kg5aZC4N}E|jWS5NSZ#Rm+>K;0H`Z*|#tIob;7DxT1nH=Xyd7K~ z7Hyg(Miyg?&^yLVUNvTRfo4XHoPm-;@~hPSRpH9Yi0XVE(!VYvGn+(vxB+QT2$H6^ zY7Vb%1(XLInr$3{@9Fcc#VR52Hz zyGu|R_m)Wu!T{nChQTmvdzIBy6@h}P=#k!ZM-XKteep115@wcZ29+9b9|1B1-H>L{ zgwx0@2mBPMt*tQJM&aCclmIsr58mo$!`^N?JEjVawzStW+^cPx-co^99JRy{G7+}D z0FH#W0$-9(h(Lh)O5VHIyjEC#aX$KMX}>Dp+(ApqQPoGYdyuCr1$g5rwIU7!vYc;J z>L_OheO!ovq}8|P3Fg}lNch=4ZU-ruEE+jO(Goj2IMoOHX5KFJ5$F&(PR}n@+A_J5 zP9vz5f+G*qGpWtAomu{lfHpLw>LQ~5TGv31i#Q{^=qX!Qh{w4^>H-PUa8 zGw^2hZRZ@Q#aUvqQy;~R~>n5{^+Nc`G)GVDoEz=#L zE1v+P`num8H0wD%9KlRM6D%=Bog{8-X^n6KNhSTH#NFJzqIle$vR5ZnKS=npJu%^k z%&KQSDdlR7*2#?4aZ1@R?s9IS4Bb7mE4`tdv}VclPDTy;?MH~AapKHlkb#^<&{6^=F$Fn_ zui@RzUp$l2OT_cHj9C2r#HH#ij)tkG+Q$UY`Rk#My!04PN#$Vi<-m1D`iM>hx`N6D3mQ>H06P#00H7CEiK<(Ypelzk5gELCX3QNjnR)J>~Ufe2X&t&8>_L}12qRL=( zd3liNrAQjuy&%7=th_o{QdU*1T)Vhe%NQ zE0mqvK#Jy2+`0LsUfl|dG|p@co1ZVSigHfmFEDBO+$)O?snON3e$-=TWqx51MX4?- zs8&OV@hd9pIiHfUg7P^PrGyU_fEtvlCHbYage`~9p9@1YMv{J2v`k4s39*86isqD8 z%KjmZ+Y-=KS(aZKEUK*3cL`W`Rr}zq{4#T|N2+^8eq}y4nH2HTxoS6qEgFAzQJHnC zs;Yrrf(7}sUUg*+X!Y8(qzAv??7{+DEEIIwRN=t8~+3 z8xp2Rn!vqeP6fMI%gYqG+j5F~MKHgx5bZ%#kr0tCC+@S0DrfK~CGCp*AophEC+kVL zstGmhf26EcH5H(BLojAuTpHp*WOjLYN`uqL-rZkQXp*_;x>i*yo~W2ec^7QhG^+C}XBPQ5s^?w;va4nn%`&r;YEmQn0wDtUy}FmZPRD>4(Zx2;QR^ zkC6l`%gcrAOj-3%kn9FY#-;Pq{$52)yb=m;xGH>3)ug1XM)LrlUzzRbl9sslG{$Im zi5~0{-p8Ah1@w4#Nw5^^Eafj8$14|oth9>FuR1GR&k{OTS<%dLn18kHMmqo`Hb9f78_ zQ4}EA)umNTbD0H+T!p})HK`fVtokgqYQd)ACFr2lr7xqh*Z_`hhU-FPCxyAT#gmRj zo{`xkVi$s7B2cxAl?aw$*v-kBRC9fn)HR*sQl_Btof$}H-jbAy!=fdXqR13{j9TFq znNN~!Zj|#Ytg=S4*qGsqGa{V{4>DAPh@hm|7_AdIjw7G!;HZpdir{J`Vv)VsKGP(+ zt59T2fGruIgQIJbuI%@yh_T#V+sbt)xpqk>y=6>5)k-H(iFg&9apnGuyZoGmBdY8~G={ zq~Cb%9ZmX_u#diq?aZt#EK_;aG4qe-N|xyA|wZdMVq)aSd#n9s#}*yQ?a=ui$S4;BckluQfrI4)>3rU z^Ypeea#42;NMEtM$t>WmB?)Ngj7|uEwg}oCt}fD$5s@% zLXOy#6fc;?DkDU|=&!br8|6tNFgCzop!QEHp26N=X+}d)GDbtZDKsjBr5?8_IqNYp zdaLm@%ok4jB#)DV6wOiOSQNNIAdLzA^+?s_Z3&WnO~{ z{$=wQRBmgpduvTRruIp(3tkqfRGqCZd*AfLzM7|5{j)57u-!nEdTLqMThq0xLvpqf zK~x`-QC%{wmg+>e$;u6RH`6(k#LIDBG`BW9cohXd$Mc=^(PQB)1G0s?V5&2R_QRy( zsIg4wnX~?5$4$!0;on%kqzV7wT8g@F(y%{4wAnJ}4EmtyvFJ@L{I{0h3-YDWD)1Q8YbWm{&TUbb)9zP+43MIB@c$ z2{J25;<}__>45C)`P{hqT}DEBh(DS2PXt zMQ=lCRX8cH2tSF54aPL~3EXKZ?h}liaFVXjP?vpYY|~00eIuJoCp0ou_b}oy#%@#) zBGpI|K}a^X$h1rrs;e?pEo|qKwkcEhiLhx$^+SCIn}J=%7|C0o0hEpkRu-m@o-lD? z@&XintF68hH8*RK9CdW2NWY(|KDlq6IDprFGORVUOG~P@tPqLLWGTfI1-d!r@7R9n zD^_xxm4iTbP>{k({kYepjm@3XccvI3GTR;$ZwS#pma*qU+63C3&Xp5#;4O3t1g#{4XKx|h~?Wd0oFmK+iKcO^^@A*uExwRd-7Bcx`BOrca*?w8bf&+T-v)! z)X@()&>wI1NYO!aVM)>ObRV~e$ucg=ola)4LN<{r49f8A4IuN__{9*d*uEa*I@s*N zW@9rA_oc|~DjlI^(1+9{v8Q=pz@KJT$gz8OuZ? zq}SVZOiiWUSq+CZ{7j)I_MfwBD&dTn1!F2FGY;vqfFLR_r+rnX1NNshQ3TC-%9rwMKpD9JGF80Q9SdFGu!#8aaZrhMRB^&Q z)Xc$b_6L9LS~If3@IX<7hGIwJGR|EEWaCXMn^3X_sJ0zJ8pTRmXM$_Jp7NWQUs=Yw zL9U(t!Xy$QCU~X@Lbj?GFeNearAR$mPGlTYCvgPFLMTDw9Ml&)CFiG_fTrk_)Vy0p zH96+lU4?h_a=143NC9Tr`zFKnf^ zGt2D8g1G7b%Z1A2esJVl(VvQBF2`f-QD4zr179zHQLB- zsAwcpiJS~{$i^tOPA0y;oz&So zP%WGhwNa?kB1Z799i`@B+3cEhl07gH-9pl^|J&`_jzrDeGMV7kWh@m(iU_GTWwe!2 zNG)Yn$(|7~%0wA06wRiN%#jN!IGo~IzmwEyiDVE)GG;>`JMr}9o8g>EP-!F5A=$A- zcUhoa?IwqfOBQk{R^eXXL6qIXwHlNmbys_#hykKDkb((M9h;NGSfQ@$k=WjDL46q6 zh%tg`(}~a;F&~xKR6}aGg*XVjXs~oMtL88~!Ne9iK!%a|3@91yaEnIHIPPCi^E-Ec znPW013VZuVGvK15$j|O|uA_1yJV`ogPbVl~i!nXKa26?^LONOGs+ontO%f2i*A8~s!y}XFL_h+uJ|k}#D5=uOa|NCp$Gls#5FlDB0a?|euQxFI zDM^o%B`o~|UX{V2@&KxOS=~~Mg*OXi_iZjdg5zvau_HrR<%X7Cs^oZ?3?xkLIkIZB zOzf>=hoWp-6!oCd0ZF%IcI5Zr-ipjz^vCgsXb5h-BTKAbIUbQhR)c!!dBB8g=OJ1W#gBN!BE5fonR#hu0 z&gNY~rO>2WOZ2YHfvee<)yP{9@@+6nEz$$?*-91(1oCC8Q++g0g?1ckK=zeMXlt5} z0)1p$y&56jTl^KO?ZyFfjDvJc_M{I7;wKC;ptF-=4%CR1K_{?~H4cTSEX+#NP|=4RmCu!q4_4pv6VX zE@a19ZdoS}G36H7B%m54J(30Ullgh=an&aXxb-_?( z6{ZYlLhL%8*d;~O2l~WmZeRvC%Y^9=um&wJT47ORT4+z&SLr%5P3kTn2RcOhGo&iy z-KCpvO+Qp}1#Pdo2u-K*!9XskXZMFKx70UTX7Nw0h3p-s8n~-^V(M{P>Z^O5RwQ9l z!UYQ@S_`WBh@>TR1dTuExlE$>V4}N-Z-t!2Dh~NYXvPR{eRNU z#!<{1(N&!_u|m9zCyIr6OFGP6Er;p}Z+c9K>LFCO1sTXcuWgK7l+xmcURYH3Mu%<7 zmut$I8yKBET7voH0Hrn1lQhp=G;=a2(iUngRl}#IC&-mqqq?|AlvLg!bl*s9XDkYr zq6`PCmCDrf3uGKq2F-`GUy{cBVFNjtstDQYuZrf;VoErWB1PSba9$9|&EQLMs_DY4 zG_Ue=+aBBkGO}gn3&)odk&a^HQZ3y&UH_)+bK)Qan=U%C5F^nssH@Io-k@;IYb{~+ z@+Z$h3%cbFBsI10c5gx3oRKZnXAO!v*)wz)FSN=J9cE!;W1%`NDz~nyk&uCv|4tE; zHBK%uZ&5S{uVHzlOeH%Q$cR%|URGrIGBY<{H<2ZvR7DVR75_$r)5@0a0W5#j{4`hTQ@Od& z3n+N#&R4q!OU0E)xF$>5OA+Z-ys z;_Uod&`o$B)k1P=RZY~ECcMlAnz*$!tmF|1FEpFtuf$~WVa0TUkS!Nchmrh+*XeCx zN=K;k_cBL6s1i$M-Q=5`6PV}$Z?PaX2^J~k{FAzeUHKRs$r2mlrP@J(Sm4vsk)a4Y zY@8ER*rHHOg(=eW=dD!@JqKlhdWo!&8ec6K$O$cd$-8Lzklx0~Nknx~4~uc?OkXrG zL85tWVY3c!2_;-RqF12lo2u}Z`FAO=?5+{i3HQ;FbMgr)1`g$%$a zC;4e=lRqsaOUiQRt(=o1U7gbcY%vQgWMc}u!^Lqb3NZETr!9xfeUtMf^m_>7*3akRjR@9H zPhHh+bGh3g!bVyOl#G!qH;!B24au{@s=vwdlw3NZv#>Pk;Y?V7Iv5DOXuYTx5Lw9< z(C?O7M%f#o4&|x%y5DxoQm-M9r>7&ZN1kmbWwC9`vHW>h1%M&`iGi6_Rhjt}C4waN zs-};RB>BxmRvccYIa@7BdR-l{S9*bss+T=r3$Th&n$FQ1 z%f!YvHPW|csdV(F8$l$@q(mJjII>+7sEx5DqB2LO(i=unGmxc!=!CnyDUjNgBnyNb zu|?UzT0h%{Wd&E|AWJT4q@tmuq=xJjb3lks(pVO|rMI@MLXS0^SI}!=l*ANI?h9p2 zRvzz?@{uLG4k@Erxz#xwYS7qUk7t1adEVkHLF%9zoiv(nR75{AI$4g z`R(=Sep;Mz-dQPYUdo*ZVb)y6F8rUosV;kEJQavihNQ;@hn^c&cGbzXp^drT>8d1O zvI|*8Oy?M9HEj+D!j3Uv76bbNF;LTN_w^2NdvDH?qNGFp9xOM z8@|X1Cp~GCH7ObTtIA4HxHi_KH{&HM)H~6(u$CBE6^;hc0S8TfQm1&EeyAZ?rq=Ge zuNdKFFko!f7u-QL$?!05)-3UdXY!RA5++gSEglOcCgXX+i{bD7%a6Ey+vql#LUEIf z-s6>ws~kn(_|3SAxT#|@9u<$(Ez_Iv%sp9b4wi#!9Ah&rswYw+RqGQ+Y6$X%B<|;c z&&mL%TUq%z>dQ;W3$Fvos_U{v(bRA!#D*Ww)X@j_tpH) zjv9YG+dWA~t*yx31n;&o_HlDgD$HrAW==;nJ9V6y3OF@R9M?Jk-h;iW%bBWxQ=ww; zA{lX;IbCI4mJ%mIXbvQ$;2JuVX@$R8RkQidnWTiz4B)OVis?b zZK*Z8B^7q}?HHkai%}1osCEM7Rhs@~-&VFLV^i}jttcBq$3f&5iZbiZ$$YG|SE9vJ z7KQA&u>vi~r4}$HZ5ekZQJY4R0n2KAE`*}}(;Lm+40XOGnYXUGbbd90|3)$>sn{+< z_xF%8*fX}ARMI|87A{c*3n8IUIAHjl2v83 zu~QIcI`me??V<2GuhjNurBv=kBOkScLT?7i6@rjeBiUVJCR2P4@bMB;S~(8jO~K7= zIX#p+S>}+PfckrecW;Xk8q}zrjAQ9Uy$s=@M-_NWM3h2@NgOLW+EOw-5K*!WD$-VU zf@GX7xYdZHCpW4CddYCMsghN*D3vDf9`5()aeOy=^y7eg)#*{_b7bPdF=xAa;?JvV zcoBCQlzpL-N@uh)bHS6P zNFSwH6@%kfZ8Q0@LFGt>?EgmX5SNoBYK=45?h+ji%{c|zn-#yw(Ca(Gp^39xULNS4 zJ_6rwrfpPOGRanF{`B&Zf5Tw19D463bezKWXdAmX(mUv%+NSL0m%Ez$vh2}$*x3H#Gpa0r;@FPJ;d_IqkjsLg64WG$XJe~2~$oj8{3N- z+1VV51nHv`#M}2FM-{oXWoCX!nV;5vCwyg5!CZZvj%3rl9(uh(DJPQj;ZMH{-(#6u znl`n}T^eAEys=9-c4bH95p9hEU)j2ZQ{+#;-yO%gly$4)nT~^esusPKa$K&K@ae8n zXdrA3E5@76sz0+7`;<4-XO0b~;;4r>U5e``)+D|8N`drcJ7~{s8AD!kny z==P;;CoXKdp__$j8C;ubG`plYRvMErR`_ ztbl@^!@iRY@w43ml2&JKrS0TgD0Hp%6m|9OuB)*dZa@Kx$XWIB(lqBnn%Kdatu0wc zw6F`^x*gHN6H^XuVmMiC`rTI1US}&ti<_K(!B4rF@2pPcU5pVoz=-6 zZ!dw~%c##p%U#`S52N%0(oIiH(QhMj?7{9`$jmfr;HKW}CARxlIU^$*ASLn?H6Ne4 zFK?og&cFdeG7PerU2|)Sbw)YRqTX^$W-@Sq&c((`$pd{c!iDHOx}_`o7-S~`Sz^jc zz1+88&g!Y7*y@mwELP$#d1P`*RKn_SiIKU5AJ_iK)j#awhW4C7=H0yi#T;G)J9tN9 z6Q&ZdF!ctdU?R~H%MHxoZl5aIwl=>)aq0FgvO3^aNuZ!MMI;Agm8@pID$N8de zF}w;n-V2G^P2J);HJy@IRk5m~q!HD(b;p-a@^1|{DLHJm(ooc@zoPe$-JSxk9Tbu}`#5GfHd2qffBO#4s9*RS$YIIq6@s#vi5w=a3A<6pEwKM9T zleUXQTfIiq9!PIzjmYjJ`>j+1(qODMn1OpEU631Z(};ceF{-&xg;)R zGSx|v$xdr zjA|+2t>=-L8nlz^F{G!uCWG%|nRGQK$(%OkRx^wyKo}?Kg z5~VB_CiCeroV1QgKN&8CR#K9*bdu8UJ%rfGHuuHU#VFKM5wDKFn~zb*aArlw(UCco zT@*9JhT}Q-8rE~`+Q8u1w6V;C*%&6oL9-@XG-d0>*sO6`lP7TRLQW2UCn$wtBoShd zXR7;n(#4(gi){^=a8FS@`ol~lytqZf73$LJ+0kWP{Kw1f4o-MnOT2dMf#a7FI5ob` zI~w+KshX89drgz&IMCz~Y0wvA8_^>fX6*Qh6AsEkE2{OPt%*>PCOOE-U*2RmIg05t zMaIpeT9sA#DZzV6cY+Q-acjd*l<k=3ol3^{P@XRnZD2l;U|vm0gYYCGcJq7!FShsjmrX4O_7PXb9{CJ ze+YRX=@yhZ4xUiv1gFaTo`_yS?#L-KFW=cP2b!>x7_+V0WJh-D>NE~Qra;}(#_mSu z-@<>jyF$BDp0o?fobnpaU(pG73pI)yVd6-&?e`@4MsVYNQ30#))_sCX$CO{Cp{{=^ zgSUHeC#ZDH=qg1rX--hIW=>y2z=yEyMLk_?{S|G^pO}-w^)E2s617?-ZyWp45EMF* zZgCG(aa$76c8>Zl6IrWEJB`hvA4yy}!Pl5Xm&tpQstc1AsfNRr4OuhFZ*0>QllqP$ z{$%`kwu`aB^HTA}?;!kYWxqQ@DQ$wHLp|Mw|JVaHLxLpY~Ie1^M#4@MiEf;&T&k>)3YWdGSjoh=|AWe$FWea{>X$W zA-YEWm$0(oJiXlutfKj25KSp33F>efOg1X2`7z(HfPiMC>=Bk*^%_}JiM6S#M;t7| zBYXjn4HKb^IQlj*d5TYXb%wmxC+3mHlozCxdwXq=M_907Z&1O+R1Zvx-FjE)cIB)d zTTJw~DWN$>38hh1{Y-}O5sb~%djQ4HeBwz3WsB)}1=3+Xs|}#0 zK{JYGmXrnLBIyz?z-G6sa@BLBA}-(P<^%sjmn-em<;QLI@~dzUx~%f5;&;z6_D$~4 z%);vb4DUZ(c{}?VGiUk_Yr?y)zqVA{Gdi@{a;!F|pQX$df6U5v)Ng||3$%UHvBq3w z{2w#+&DwOF?f6)1EyfIB?gk76HtXN9*2GDEE7V_1ad({6uCW_qlPQPe^se~VwQ{F2-cx(=S{tlqr&Sd)=w)TrMRtH!I}%2oF^t-Ggb-F2%zz3Q~A z2R^yLHSX?HNAAi0%Uu)RE&t;2vM(M#$2H;I{aQOaJ8wA2z2su~o$gBB{B(X~tTWtX z@VUX5gPgAp`svphStm|+oYx;7SKDuU6HdqTO1VjmXO6RF{FtCCyeM0!zb(J5mo5MQ z-TMB1J#9Pa@^4tRllmQJ@O`bj`xR;YAH8a~#x=V(u9>@P!c%jv{a~-BrfK(|rk(Kg zGfg=`+rI=)i+2y*bvBwaYfh&h-tQ(_w#_BzR%rd8U% z8~s>(8*|fUo$jX1G`adtn%ZZz388Jt_SE*AJZt`X`~lIKyUd$zO~+AmLix0rJLRtr zUGn{&Q?0qN-%EG24LZ&}^qM+Q`GmXTpI_`hujX3!sQ0Ag?-Eabwm!et9{qH1{_1*v zb*y$6gdt_j$$K_3ua|%gsKf1O!tbb^SFh9gVh&Qh@vIrvB)|&dNV?kWyu4y?;VbU^ zl{@b<_N#qpH~rJ;CjVM@ZQA-+Gu4-Jn)bD(*FMLlsi2!Y^T+2ed~(Ps6Q@r-Zd9cI zdHq(`{BhzDE1pST{qn>M)^Bsf&+hjZ5B|J$>db->4?*drye|^@1zY^-H=m;Mevy+dlO1zBUbQ_C|{ySF;xE+YE9xm6kXw<{0?_v%-LqC;09v zKS)>`elhZcrZYdEl6)>&Eq^hmE5k|FClqd0b35(sIPB3m z-{FS;v2^0+!?150_VfK8+~MY2_{*@e-LXIN?p5!PxYO;wX3f6+_Io4GseQd^`^cT{ z>GiiAvg?!Ncl6NEq-*YUKl${5vmV-GeV!9~a7FmTJKcdf+sz7I@oAnj=*<_;-fyM5 zsAjK#yW`V&&g9c-Rz+62M;~*{H&?s=$a5xsvGZ5gtaSfyz+d{U{}<^E-EClC<4Si_ z&a~TR-11zWbN$F!mFai6Hw{~TT*>|`^PIzTpZ-(yE_d4SXV2XE*-!GEMB_ORTzi)r zeJou2ha+FebJnf9e$&Rg+}88{w*J`_kLEeW*YB9I|K0AP-(3>#_1?>Q&d4vQSdu{$DPl){vLPzib0_WSD% z_qyv|+ViWgdz0Qh@jW+wa&MnS`nWOGQ-u&BT53YJV&w1^Mi#lTW zxf`zhbmLA_zshqeKS_V=#{1k~>h^hK`sQo%oYCja81&hFZs?Y0-t2hjvpi?!zy5Xj z==R9sQ?PM&koar>m*bibSTTJ{MytS5Z#4%bwF ze!pAXcGe@iU4LVq)34v(Z#>`ux9s>>?eYrR|J=*}>NGy!-u^Qa()pwp>a*8^n;&o& zP5=7Vl%{inxXnL&ALhP=?&^q?E7 z?*C8s9O!GO2S=~HobW|wzxi$aA$MK%jTvVg0KT7mr)J@854o?Mvd61^E~USW{P>#3zIw<#qJ8*; z`~FP%cis4GX3oRzYrEa>{Z(HzU-hFf5J05mV+UA~% zj#~dY^w;~yj;|kfgY$0q@S;u6{aONv5k8?bk{1k@8UZPt7|FW-UBb3^6yn{)lQe6oOKHH zR^6v^^XS#?s5>ej|LEdZ@|>q|S_yxJX7RWQUI@OGXv z>#tv4`o(JZ(9Zg`bsN9Sb2jx^oSnJG?f3l;Cx0`7_I>A+Nl!Mdai8kwbKK1Xp|6jQ zKQVOc8aKG_hJw|%x zrQz4QagVw8UbWxdX>;LM?@TyqMB*{G=B%fGtRF!Ad%yRe4_7|sUVQI99e1pxd8C$~z`L?*8+nU$Um3P5vvsES|OWaks4hx{W7(Lw~t%_$hne z`?&i-?;}>-{oOZt&d*CTe)|4#_qL0cf4S?vv}f$g1J_M{!gWq@hd=!APkBz>pPyW_ z>T7d*!-k> z-JUtuo_oQXJm-qFYj2zWlzZinfgg|Ei~Qdo|J~!qJ>~xKhOE%;HKafMj-m~ZKIL9k z^5+{){uci9(z}08>-Dtz<7-d#zxRWe@|@L2Tsf-fX}5gHuXo+N=2z%%`&Z_j__RCk zqMWy*qp0thXSSdI)YI;-SDpXyfa<67oEx9}%TxWHasTncnrqxsexQACncx55XWZd^ zE6OJw^mCr`Yxb%`PJ71v_msUpoqrAWIe5|gSFC-;otqoYef8^q;eW_Kz8m0cS3b!w~^P;o&)B0++XKb(|dkdMEtqs?Z>{e)*W7T;bSZ2li!ZDJ7os`?iSpcdv4$Np#Rl_j($J% zcX!6EdAHrZ-+%I)kB@rh@~i*u9x(s28*64VJ~h2DFuL*Y?zEad>rU+pJ#K7XG;ZYc zZl5d9bV}YN{THuT+c);SJM)*<2CaOJ{;}bm=8b=Q-VLmK>-mf0wC_PH7e4pJ^X{kp zPdoa(I;9!J)V9^|HH9=xyyIjcECwT`~p9?Z<~b|zUJO>@yO#gz6t$J8Q%M{ zb+5U78~%9tc~6qwu}|myw9o7ADS!DUckre7-*U&$MC5gMhpTt{V*Kske~sI}-;J-k zM_oF+>A@NB>%3_z?*HfO?j_re9(vI@+V_P|H=Q)@4L3aJ@5`EJ?l{$%`{N6VjyK%N zv-iIDwEf^my{-WF={T-${7xnL-ef3-JK@-j&vbqR)(;ZlS(5Ki(Keydk zqe!pO{Vtll&i!S^ftL<1ro8Pr9e1~{b5EXg@#hzuP5a)oWO2qr>)dnJZ(jMu&h)Pd zf1J8%oAvJOmeaP~X3e%!otgVyy1Zn)yWb+9V;LdDxmx*z<`9eBhUd%x|TUwzx2mn?%HRK9!shUnYwoJE;myck6u*zbmI z7T)rees;_E@U!MrU3 z@E_lCdzajD&!X!o@1LG5U$^!h_n*(b)w%b{)PLoOefHbsUH7#3vCB_+gZ_5@sK7;w z-gU1({^E}so`Bw0jr?`eb?>^%7B#HieaAHV$AbD^U%l&Y_fFMI;T_PR+ZPoc20!}X*)`6o8{Fdf ztiR_kKz`VB->cW%y21U+s2PJ=?in`K8CChoAs=mUzu2_$s7Jp38u_+#)ulP_xnG}q zZ_SH)F+L8CyjOPAd+z-Yz7~FUEcL1TaGU)R3vpwJV#x+v$DxjJ*du zv#An#Yh1YDUyI&%_g=f-6PLyYO?5i5S4RH!zWc}zd)-%X0_D3To5)U3yk+~^*#`1F7DS_l4r?6+q1V;kM$pL!*I_(aP4Q}ZiyYYUVZo@H&R&n&V5sMru`f5y>91^ z-P&QJ+eZ8od~}|YKfU2&_uqfohd%mj zeCqDt=ifPd9sQ}>_r>yee!d>P(Jy!3HTsQD-KJwo`o4S==^y(;v|{3C?g2j?b7;wq z*xx_ouD_i3nfvd#{&>y$?Uvyo0AXQKMKe8*bw%W2r$QCV^=Z2KDP!VmUeNib| zm1{{QNm`IKrL@yZZueZ;OJ&Oz*`>m@XU%WUxx@Q?dw<{iN1s0PIWzOj%roaa=bV{C zTV`^{Pmu6=G<|P1>d%4C$hCgK@on#OD~!J^1#MVi{!%;s|@+zKKoX z?5x!_-VgP6nR@PyV@)~G(&r&bpU z9!Q_8a*KVB&PP74@?Y%bLHnU%`Lowi`)v+9_4v+%fvI{2Wp*KdW~fbeoZbrDp)9Wf zFC7cwN9gV~J6qwKPJeR!3N7?JzyX_?j)oZ9B78T{ z^(eW42W}lv#)1efgIC;MSI>iNA90oRIv)5dMV%Ro(A2xCdt)sR!bWGWO0PlXNh@ZE zBV1|sQO32J2l5jiZMAK8PT6sgIqa@P-F7X9ez@@|p*mWw+j$ z_KF9oB8t@}FL|Idceljs7d+_jH=1gh%>!9w#9mjgIY2OiXve09!Sg#1GK`KZly zJn)fTuF^ga`6Kz}rjHd5j2*-Cd_|$^$8fGh2fV zQGbm2vY<$h2hp=<1Ssk7z^?SX%oDen}eco6#-T$Lqw@Ui2y#&{7lub^P6=qMhvd}v+YGS~t=vkpB> z|J?$!6eMRY=~&nT)~?wv`=JGX5iaK6 zOj}@b!;1LJIxS#tCH~n^xdpUrI^q&#TVSVC=C>arE%0=n;l?rj&0z0pO^ETDp?9`W zPu4a&M)c22aZwAYOiTP>En!)*^P3RSyX4t(lcHb7W zW^iizzEE&#Gfc1~3%n;bgK&Tho%TCXmj_*SmnOGmn!)^3`@VfdV78 zC~$2P_*N!1K3RnFnVo-6ny|*JzaXs0(%0bFFA%QLKX+^8F9=_^W}mC! zFPOb-&sB4=U*LJg-p_uHPz}y zXp6l0Chtxo1eYB(+j*c7l$3^MH!>T6Fkdy(QoIo~jz3r3ockR%Y3uA@xE z@T5QKwwzxroE1ZRH(P9v-fN5|!K|^~yC6chB&oGq)N}&Q!81 zT~`fv8}HAZ(^m!Di#Z3ZBdXy2!ujTDT2;W&mJ#;ZRS9Wn0Xp(0zJX`=^_WAKDxgm= zBZeJQ4!6$u7QDUq74+0N-yH+X;Mephscer@aBw#1S4H}Vty`&{TZF-`s^df?2&H0ruPn_Wp}C-oq7QqTpyn5 zy8Hyt(jRI`-pKE-$KxaE|Nnnl!N}8dyg|l3Qty!t`}%n5Jq1xULEDneKfV?Ex5*2L z+&xc@zP^o--t(5yxf4A-M)Xx`ap!-IzD|lHd@&HYkWcA}XYzeU^v@S}1YhxeNN%`! z?|RMA61x0my)7g9A=$otTW52~Io%>N*G~IJ>8htB*D;3c_dfJ$^_GZN4AMU#YSFcFvY>xR!=~af^vvB!Uup~Cz zPj6uynf9F3_F!fcrL)qi_K)QESv7Byj~IuvJ!AZNM|d+`e#1)dk^K5SclH}4UV}3I z*UobjTIllA+)rRV7rtz9l~PfKPR0 zp!%8JM^EDV|7RTtrT^Ct=ZAmEPvZLFU+O2Z{p7#cPvZ8&zqFsk{RjWjeP{Ex)`3;*K3B=&#!7yl>m{DFU&KO~-i@GtX^ z#Pb*aW&V=?HQ4_{TK~>Z9Z$`F>|d>a*H7ux{2$T(vY*na`9Grn)qc8sH2+8Rzxt2T zsrk=Fh<>eq_dl(p`9GrnHGZ^?=KqNP*Z9*qn*X@|s=xh**3tYQ(f{&4N~h*OF2Cw; z|D|+l{*UDU%m3-}(fl9D|JVGX%SZDc>$!i=KRQ2}|0DWe^Ow@8`H$=8Qv4sk;rG1% zN&4&j@pt~e&p&_H4}YD%D81?*_QPN2KRW+E+W+_YlhXg$fAH7&moEPw{r~s*n=bz! z;|G78|7ran;}3sbKWP0Q{sVtqe`x(5{s(_uzi9m*{tJIy|0uocAN~)2T|X)PpY!MM z>o2AMbN>B({ie(R$NU9!{D1ux`v^*rNA5D*SqCgH>k))p_m&yU@2t9S0 z;qzhluhmxt2)F68Eq^y%X57lyYILGdfH?Ewy3O3(A&jC>kCSsmMiCcMQfddoE-_}J zKiztG<|v~5yGqow`7B0-Z_TE8J4O*kx5YiHe+MwCzo~S^#g8H$N{rbXV1Al0TCnc= z@%&N5liR6_eHI5Y6rLJM{1Fi(7WNdZomFv`F*I)D12Kjmp>uRXPU!S2jQ2NZo8Ryf zBu=k8y-z3q2BSNn-t_AYLBey9?u`Wr#~Es-vWu>N6C{Kq;xuj3Qy4l1zo$ry6Cz~q zWV<(Sux99=TJJ8lM2I*fc%`g5XBQ)vk@Yw!Scpiye>n3~_)U7q|aGuMi>n{Z>}0crwF(X`J&kQ(+>kcyNO7jPr~#?Z;;GcMB8ZD->(0H-s~E zRxGFxy)8`4;BK7OV0?kGV9H&E-QR@?iO|;QAlXDlkkjncV;UlaNcleEL~ArdL(M5* zriTdO<&-G7_)Ih-hmL2%omNCTEi4i;X zc5Gp63+I)qJ{?0mjIJ~j-*cXEV_nPHak-LQM76T=t7FhW4uUEtC7Ch?e7dMmINxFqrdpt={i2mWca!Ztl)`)%@BHGIKa5K-AI5e%4kw6VjKX{vtuPFfH_~i}bd(4M19`j-J#(WqTVm^$jm=9wQeV>VzA!5w^3sW#3#sJKRaSi6f zXomSP%3?l@O?X}TFuuop7_VbKj6s+WqX*{0XodMOs$xEj1m+`1dn^8jk(R5&2d5ZW z5Znzh!7Y2=2n6%Xm+~X1k%|?3E}#_r0a(#bUVG)C!YESu8K4iusqOvBc@Q|Wruh0ddA|_Ta>|q$dz_J z)s5C+OcQ$cYqdEl@A8M=XL4Yda#)vD3$jOb$KeyYpJ0;Mi39%XGc5?imR%)JKZA9@ z`ozyF==jtlKZKHV!Dh_1h6{$s-lz}v6gTI=>FuIp2t|GL-i+7puagha12>NzzCRw7 zSIw^Zkq_BQXJp41qV_MybXL4k0D(a%k9WUB?`>UTvq!Hlgf}1O1TJ<%^)aQ-D=B?} zuvp2YzSJ2OL~FuJrQ9!2u&yukHdEVz=vkyO;dl{DY&+dEeeYBYLadyaz$gap{uRH! zs+u5suEEJ4=skP8^_JfIsJ(`#LJFcvVC8gW(aqhcf7}J93N0)Jn;oI4?C;3_tz@Tq zTPdh8+Z9w#nW6spdV3(c3}mjJaEon}LF**seIsqY!dJ95&~!T*j|tlGAAf!Y$Cwje zcZHz$Qev4!Pb119d*_Lvgg@v#AL~W9lvxE7jkR(4s%MJEyISa0VFe6MYSnivF-GMb z^vL)A2JDp=n&)Mp_nfH)JI6{_LP>{t+<{Me$o|W*Z|_vX&Nouhl9uDpc)d#w(5!;c z>Zm0<_0%kga^@PTz3BNHq!QjWsJ~{;_`Zy+f`StTv8NuR_nM0g;(v)(!}rmaI$O%n zd(Fc10u!fdcu|m=q8Nqz<=W_HPvWX!YtpsjE?cId{@lH#vY{Go1!nj^b3yOr*qoA7 z!x}iUdBHA`0hFKdPC5QS4IGM@vCm`~>L10RULCRqnw}P{pBaMQ8;)6Ox>B?jOj|vi z8)GQ{y_mvUR142d?+l!0Dp?RY^%cyBT6pO4^yk;}s6SRr+Htw07A%!h+&;cR<3|K{ zex6(hLAp{IMZ3{Dl5*zf)f?*I?t9t7FO8@_G)z4juhzlFpx@_jE=BF%c~Bv@u@2PD z40E|@sQoWvv^;g|;oYh3t!krC{XL4s=XTaZ{4TS+`$y1wf1z2c9d6e{--b=yhdt1H z%?TQ}0$S>UV_P<6<|tI&U+dZT>^^|c1HK>3f#ZU^d}JNPh~osN zD&{y&V7jpG9^K5~cS1oS?eYA?4JU;hjE$Q_Oo zPVhNvJw^sTZa7ZpR#T>^8+%0_0orlvk_(%w1tl?kxSy_B!qUYw6JuH75 z4|wpAI~*ryU|pDx%vvcQ{Vi%jZldtk1%zgwq8u$8iGpJ^#ATz0XJH zMH~kl!RhXp&*vjcAM@cjp`U-9XW}@4^$znieEpBc+@FuEZ8&{6PMCxFaGW5CkHc{S zvkG75hvS4te9pvi0{0~5`!KGRjj!jm7(eronaM})4a_fK-5=vF zjH|J}fREf6n5$!52ICN(lg)f&e&r+U9ZpZj9LEXFi}*MkCouP6j^hNDBhEJ$=QG6m zaGW5G_5U|cpgfKG<*)A?42Iv`rM3g{==wk$Hqvak1YhNyH%h@g#3Ld-CS8Fdv%=yv z5sML5NfgMu4tu`N@?IB>j%Tge@Nmf;5HEWFJi>n=;wG%qKU1N8^7IRli&D(bcS2s-*#vR6K)+NU4#F7OkyPms*((zQYPFV)(xKZE&-%~N|H z&PUu#yzpQyjB=F~>uy8WgYXBMpJel({@J{-ipNp^w$>gPeJ2mD9upcZ9f{(B)co*d z>wH*S_@mJ$2I&sx@2>os50=>>>RqO29Sd=RJmE^=VzVE^@ z$0B&SWwzSX5Hz0Bya?@@BA69^OiE)Zibse%zY?!v_`0jW=zfT%1+hnG$7!Mj_Lsg7 zO&3PjfA?gU*1D8TC{h*KyDdI%aw%+i-CbvyiN-^kb9;44DHJ_eFLC)K#Xq>^ zo0LJ(8NE^;7c?I3@4}{Y${;YOCfw)%YHzNk_NE12!CfU@eBoo%KIwx}$8x`d>SsyC zY$~3S-u2AgwH*HVf4KDg5gI?kbEn5umqX3OwZd2LPDi@BYV?*05b|lfrlKmb;TKPRuz1e_;hei=p58v zpB=_!RYCGHnP1Dsqw!#+X^9C`gG;_>$h)hke;XH8p%1>EAF_E)7$%3* zT`ncN1}%uSq?58>;+5UVIi_uw(_G@(!3p@AyLQ{r=2|t2FC5K?JT% z=q@B}@7d>jcFaWit*5vL7LrDXj4!*!R#=w4M`Fe3nldU$@vPdeI!UFY#`>T|T+=(&Ink2X#^Y z@SV)kJkpqZ!?M^GwJ&vP!HjKrO8LFm~OU|vAd%JfV1NDd3v0HAR$*g6sRQD@bp#HNEKNkFn z3$jl-&UsF_dyneS&n7L_I%L-_FhusvSrNKEi_Dw+@+emFkbG#QDhIRyeDOL zDmit<{U1||kiW6cc82Y`OS0GQcVy@w|0K2+b9Ub%HHv+|ZdRnmGazg5;0@Av`Th0~ zb5o={sTufPBR_h+I&W)*{EcOnY!`5av|nE7COH}TE1}vlV{#n1xc!m-Qx(de4jUb{ ziX_jc3fi|3^AVSp%-?X1gxaN>GaAu+Mmzdc^iGib>c_g6+M)W0gPt$N50Dx^J~}_! zK=tqc_v7CmFo*c}0n9%b2Qdy{?8n%Lu@_?x#@`sbF?M0>#Mptc9b+5DR*Y!E zt$U;qt#_oc2_stPNb{c9IT9-&8T8~I$E=IH-k>+SU zB8_N0B8@p1(RxIhzsL9v<6DexFuul!)*;fzzr={vA<{e>V-_C?Y5w?g$Kv}$j7*FK zMy?drF)}d{7`c*I$H>G;VC0U$Iz}c&0wZ@c)-f_M5*WD>SjWi3NMPiOV;v(CBY}}C zhINcgj08rmDAqADF%lTLB3Q@B#7JP|3S%836C;6NKtj zbreUO>z=>?FrPDKCHozUhY0tp=E?IQ_`ddfLKDSP1oy+@9rjS98_!npK=B)4IPP7P z1L%s~)oVJ8;#op^<0#+dz_Gu%Lc0>h&nTc==H?7d@*a_=T2XvN>{<8B$`wKi`~Gy! z(?XmpIZJB|_;psE_^@OarB{6tcZ15i^E`~#&PBZE*Yj?7P?UZ8WwW{+;#GYKTRmW@ zarpQJ?&$d%kr;QTYy)gEj(ocQj}hXng~5w9fiWk59JLI^pG4G^)11vPx7%dW88;LU zqX&?&hFf7ZJJ5LBrlp9d4n9rV2HzBo_9hBb_9li^%I|<6*43a-SM?F+K9xPO6WDdV zIVV@6c%H~Te`5%6e5iK=B??Sl_d24;)q%@H`?()$e^z zXQ~%Sd+K#9|AXQ=^tnLyYA-l<;piq6L8`wBZ^mBU3yDXM&E8x(4{?WzZ_D=qnOFHm zD~u{Xw^B>e8}zhVF9(!U^{1wJzxIauO?i)&gfXe(m%sJ%fpvRew`l}*e^#Y zl%;$KUX6PEawegGxIANK$sw3Be(Dj6j|&i2z4YkZVR*JtZ@x;F72<{2X7)!QKd$}f zJaMZ03)vhYUpT-qIcfBr@~=l17jb-n>{%!6qEC%~6r<|EQ7}{tkDqd!>i@*2hm4Ma zuiDG|{f?ABX^2kzbqt<$>^QYql(H{q8t0lH#EyUF5$HtuUwDM+bQb8Iv3L8x&_$ea zxyYLZUqo`w92`yghm>(z9t#e*es-+9YikoO|Ln`%?{4+h7IJ_o^-U&5XX)O!1IAL%b}(=sHGiie^$XL;=-w;cQZ#{BqH#7oRG6!wv!m&}B( z`piRo>gD?xdr6=D2Flg*^bq$xb8YQjvSHEb7yJE}BF>hH64*;#eS5ih^qxtGH)@3> zdy&?}akUx!RC_ve4%>N=yT2Dyd>5mRFBII~yoVG@8QY)IPI2E4E6*V=`Su=nIu$=w zsoTuhLoOY<{du|x#akz?d$6074;W*|xi`@|8` zm7B>waZl{z2B~;d_3+`mw$2qT)rKtRSYCk=7BX91P`pIDFQ@T^P?K<*H=c7xf zXHoU1icBb4Lq0z(d^IPD9-l6gO{>Y16`417Sfcw$LREcXrz?1Pub@Xwkptt z^!!s|dfbk>pJ1AAl3htEzF#_+m^7U#PigxVCo<-dUG&aDs=V;I+YDEb{wuvk-}0gC zNlfw}myzGc`Fb|aruvi1bY9{}g75rn&PHlH-OW!vTT0G+^vSfOgBlOkW|xvhWYXc# z5-W3Rd=sUVy6wn4rfQm}dnx~SpI#(4pDdW&CGlbyZi=}H=0<#E8uF2AfVn>A)A-2Je0Ooy|_wteG;UlXX^DfLg_{ePMBexZE9_G!M zH}R49#Yff;%o{Op;3Ko1kK9_!YcQ|kBdd~+#5c^#G5?BrDIb|7eB>5k{sr>_KC<%p zNaSJu8S_tkYK5|JuGC7#DG0)^9@r;kGbj+V( z{uuK|eB?ghBQp*2`jEE%^O&E*JRI|@e81x+@sWvlj&Qae5d|$H+Xx z=d4hypT;^yW(d|#;dG45lQ{hZAGyI;$H+X6^&qSVVjUwZ0H^!obc{?EpR@e1ehljv znMZNDFHXnEJi_O!!&pCrb&Sk|SU-UE{d{C%WclE9Z$1(jnfv&hwHNDNSjWiRgVT58 zbd1bhe9rR3`cAB4WbVNFcASopxecdp-n7J zj&(PzV`Q$w`dXZhk+}w^ujV6n71l8_U9s+h^_5u1$a2Q%PBLe$H-jD=d2}IUyOB(%tctY$NEA(GBL93aQXs15*UZC|9{5?!<6WfPVu)5X z>VAhv6d>MO8k9fTxw3(}KcaY^<@T?qZ+eJQ_cH_`NEBN}zgcIuXcBe5LZk{26_!^& zPTdxyMcw}pTLoFwmRlMs5*)PY{Ck9mI?J*KU7fss8%i$`Ccaw=4r)D6?4|BkiG!lV zPfLS2FXKWZsrx@-fjH4@>3HZzrq4aevM@Ykn!s0g#oa#>Ktm20!9 z`yV1*l-q78-alG~b7V2a32|npWw7*<%OCow`z69%g4<>35&paKf)aKAN<@t&ep@Dq zd2jrYPU{+ytRBn4!RlF`N|sW3jug>nX~kAul75-iL#0{$mf725wE_p}`s8J~1C~`n ztd9m)sry%S$ISd=DeS)U{N6`&|A~#`4p~kK<2CNwM?Ie)l*(hmxkLKs3{hwk9Qca zDhe}}s%+GZ7N?HCG=VD$wI}zscJ`xv@aP6?5=#tp-!Is*K9R2P^<<_v+_ruetDHlR z?_MQB0={dw_G9n5i|PI{)k>6v2a=i9!VUECh1yIh`24GId{7+iKZQEPSkQK87501I zMcMal8h0#g?lR3z+d#Lke;iX9yjBY4?K(o;UlWz%!=-_@#acCf8}Z%LU{-~yw zD+gD$XGjFBq4^$lLwWGEGhe0NO^??Ljc|Ebmod3vz0E?ZJSDAGd1%eH)YCSi?X}Wo zDuA5-?D%O*X#c%2HBkXrMu+b{W75aV=@R4MbW~c~p9l1OIX=yO92_|BWD2JQsPYE% zxZ}Wv_i(K2AKJg?7)Xx?jXaO607911In(!yhb=l&SNpo4^>=9g8Rd?LJC<9VdwY~9 zUTkbQ0pyl`v-;RW=hro5O#lyvb3$`_0;%@Cno%_Y{H5-0x)FrluM@;(bLK?wU410r zm@eI)QnL~#!k^@c4W$|MczVxnoe1;S7S#-%qQ_60;V=nuj(BGJOriU`+9Guluq5`c z`OKxuy8_aaA^Ar^L)k7Ny8O9&Cd0rH^ZQf2(DpP~aVNvz%RU> zRe1M0%THchauQrXHv5%QtFO zg>bcJ8P#$$Z!uP#0_XPPiKQ2-GXpE~9kx5*>#4_G#qt!gkT zhdH(1jLz>5s_M|G9UEDlLf03{T%Zo7CgXxgNs6;R%{{0NK1+`33WZVr&yu%JQ3oB) zUUS#I)blddTALDeP!bF^u(zP+_uhGg28i5^d%u@O`3H+@Yp4Mx7Dg?BtLXEy>;iWU z*y?*)?4<=gUR&(KHK6)u`OPIARDTfULaqiJH{FytI-8#FRrZYpIof|CUDLHdFlftLZCT1bLT+`V7D&q5w5zS7{a-qN?0B-}va!y! z_ZrWU{wcq59BE$eaojV5dR|8?E+EH|>113?wcJ#Six-|9N4`=NSbi;wdf!RBD0CY~ zvc)Z5PAa00_x&<`9QiC|Oz)?Siz(f-Xh?zFlAR`3Jdt|dMEorJtUwy4DtVQ~8c{sH zI6;B*T0iNH!%b_7JC^KLATx+hEt+#?QG8S>D3G$>-A)_cru74*Dhj0O7EQD7$#i{M zWjuLuaYXE#9VeO8@dah1JUR20(a_u=D~kJlJu6Qdz4RGp^+BHEI^}NifZz6xXlxlOrF=&0cGu76jRiX5379z1p0JXJ~`Q_Yhl z^?y1a^L10AcuF-XOa4-_I{MOWF2${D&dQSZQ_539U1@vDYusc>`LQ#K+hpfcx_9k# zS+d%?FYJT{U0+Gvg=2h&X`{#VU zpA6~fB)zWNo32lzVW|u$BllF~MNm7{9}gN-WXKns=E`k3bbjV{o-}EHYi5h@9Am2d zobRMG$;_HMC^VPuPv^$7(&WYKMP`%6)BcdwxLcb1DDv}8;tG1aX8izZQp=~IFD=c0 z%1{0nD@_U>vmMwsn?63ckQ__CmlAIlzOj(fL%+CJe!dx8S}93R@ij1&|J+OIx@8HHWYv?P zqX8#rUHL0WlJnKJdrDZM^$X~QL3!mEGFZ1V>t4iCiVu|gjUf#sRg-2X(7driWek}R zbJy7Z6n%W|H*z%T=>9UlM3}b!QKj2xQsIVb;pkO1RQ^j1s%$>(l);BurkKp(QpUi=KZ~zY>H=uRPh_$~-!ML=y;;vnQQfb}yMe zpD>#%g~;--1dXx6)bnhjso776l(LVg7?r0@@ii^z>p%Xvw~k!&pxZx+S1Cv)wrkA0 zyPx)->pVX}((cEBndg?#$J@552$J)A+_pc^)1mU$w34I9d(*ZzS(VZK@7v}!iX82} zL%7bL_HWttApufTd+&`gR+Yh9A z7>>L`yq@;MQB-+vd4Bzz^sN_*KRVI=lhR7|ah#%)UcZf^=SNW6P%px_|F<4s~W0rLODKCTbPsiMjs*AS>7St9w-Aiq(8-w}tj+=% z+POs^#3>|HEqYCVD;?4h+?BG$%fYGk!z2@9ltJ zHRn_E_<6VE>FZl!-%ur|*ge@os*=85B=!%Lb3XLT%(L*7pvq$n43%{D>q)c{^F43hDWD$=Q}(mMdxm_wKgas`A72CB~bsLVbXuo zVq7ac{g{4PXMGG~J-v^)71oNaULF|k#Xj!0t5Lza6?VNlFj4VpIGew(xfM1&^0`*C z?+oKxcZJT5J*}X{NUNzl7s}@Eb4K624|-tF30}_#?0Eg&GQJgNtT&o)aKSme@3|Eu z)|W3dxE{s$J%8`Ch@4jNT3R!%Tsa)?gKmXoM_y@Ge=d_syd=7#&Tf1U}Q*&EUZJH|%lsY{<@^Y>A=LETonBa2ptF$&iR z1yyCXfoQkEjNs+bZ2rFLHehT_B6bPHGn7gP)Et`I;PlfYqoOL$vHAO~+u=gcLVuq( zXBd~tkDjwrX@}Xg7;y!L+KA@ zw8`@ThX01H>dK&YI4TLDvlm@v^Y>-9gOlvt`;D@}jPTO3T)*^oxH@TOQMtriHh-UX zI~30Sx?=vQaE9}A%bWL`+96Q=%e=Agz3{&64yX$-%HQ4@%rGihG_Yq%2lxt22>Tuz zg7KBJ6oDv;){e z(Tp<}pBpCb1{|3wG9&{=EVGZfF}@B8k6%=lIr@v;zxBI}OO zyuJ?T=ymQ@&y8g3()++W;b4TJ;{(k`P;h&tp}Tk*!9&EFT^37=U!p~Eo? z7%K^-=DFKDVSmWRJo^i=c%OJDw0eX;xtg_)@vWe^RrhKqICJtkJU&F@edC=_W7Tv& z()uVvvSKj$L3t;b9NfqJ!z}jLh_&ypId;M2`et72)-!DWK69j>R}#?6jAjhJi7b>o(*@s^F0Zb> z7=ZVkBYnQD@FL?&jHkA)AzJUcASrm8|73dx-iO`=Ya3XGz1L$H;^q4@s)f5@{x|0s ztD5ud^(Zv4c$_~nqwI!+1~WN_TLbF_+;?>UH)uu|KtUi;Wy0rvT0{&N;vx+z0dtO z6nGk~bkzuF!`g34x3PYM-ndZ`>6@b%C+L0ezhQc`P|E>@AhvOqp+IxiZ}_r!T;3wt z1iTOaHyk^p@NCrDJq+1NmMgS{dO$bQ(<9h7f-Ohyi|>JAV}(McMRDxAZ`5rk&F_K! zk8`gE6`f=^)BEIm;8}NJOwa2CcJ$_u(l;SJF#EA@NA`tiyl=h-wCsbO+kRYPgxWkF zXZ*PbZq@!UUiW1Wo4=2~2X;o*CduU-XS^OdH+r>vFQ{v}7#u2!W7pIB>U$yc*kk`U zZd=%6m%aU9v#J+nwI*NkwcpQnp!eDLLhkX&zN;t1v7d>)-_w@Z3&stf&OP6Ej?Le9 z-wT{}uT)!KUk1A8p17g07j`XbIQU-UAY&7~55EtLzbLM?b3M;C(O=jUW7r3`&B~9i zF}%cBOYh6?gX+0wzV4bH$98#WC>MXI4_XoY4`L>IaQF)}pR&G3)H>sp3gEXy<*w(>3#kEF#FDmV@q9p*b7Cj7^WuogMrQpse*xcHh-UgKLo|= zAI`N1V2CRhug-4ohrO{o;Qe4co4@aW04f$ZoZ2Mq&p4bri?ZJC@ykN>c2^^g%dqJkIOLf;cw+y8(kBV!pa~RC53$D)8O7TGk*m zRyLo`Qa;D#e@9>tj30Y$Bh+IV?vDgIrW6c9_Jr1hE6XAnZuEBr24S`Rgry&o&#`r` zbjjOl{eesJhyAsmM6&tc8TbQdb8fOu#RoD17rp6sJp2c$&y0Dm_9cQ*Mt^tU51gMG zJ6=6Dp53-3On<@WKM;CX=26G_ST_GV1b-m)$+h6lH*t)}XFJTNX%2yPm?W8eRm1P}ku|N5l$PIhj*M6Xl*e`LZUyTPeeD**p}sxSX@T-8wpDGN_e z;Qt+~tpCw#eWuMbD_aSQ&#p{z6S zMEIx+W7yu(wdYf6DgsCB7`FRry!I+l(_16yeVg>HmkZn&;iGIPo#F|`K~J(?QR#vA z=(_9na8ptw^tl*YJAN(!@zS%8YfqkqpNaJnp@klWbmBS?s{7x7cL&nRtx%(2)AA*RPS7w3l2JZXQR*+M6dlJ*W_ze*!BXji2kIsc#kJ_|BQaEu;F&Vw1}{$BX;o9(Q3Lq9nQ`J zIV0(gvz%{f3cMTPYkj3GOoU&7K}>T@DGKm?Uw(IUXwQOr&8$nM&FH&@nWI*Dt>J*G zo>}lyu`+0$VPkJqkOAkIcJ|L@N`d3D`SF{&$FP4e>3jsY0Db>gyG%Oz0ZgkDG~t}e z!{0|LgDvu*Po|&VGt$52)?=b3cE1|2W7uASdcmBrZNq<;3@t8gyk;}!NyW%<33s=e zU)%c?-Ye!>WDM4W%v7c85B8SA=)3crd(rnJZ$FgFy&75!^1Y+Uh_*T~{Jwm#JKEn) zgr5eJ=sFOao?fGCTnIB#%v`I{zM~k$*J=F^^FcUxJ}-V#H8h!uO?*138h&a%`DpJP zHqyVf&{N#*4MXu7!!q&W`T_ zBjQ$t`IL6>Eu3#7;M)aCezD3j=i4E?_o;6B`g&+P8msy$W%#>(t?-(?XKo4FH~(M8 zBC=}n?Kc|h#F)eBYj0Tou(&jOgs*D9aiPY1Ec3&95Bs3rE|4vWTa|iChB<+F;ho&o z2eplQey9J)G5c18{W0A)2xrzyZW(iB9Mfe_#$wqjf>~ixso@qok@;!YQS{+RL1w^d zoxsHdip-+$!if_@g_#ah>vRe0pU|KhCX?=Y822y1OrK{`t5sh}4%;=1n_Vw$a=kQV z#6JG{S8If;n|}}_pcy*!P4HJCL`_G?ecrf&vez1 zagaHsncMM9g8Y$CryjcMHff-?Twg)5+tTsExG4|zUIF%E&CJII)ts<7yt zcqF{h*9Gr?1hq@yD){WA(QL2tWE|sq8U%`s#TJFta_!as2WxoI4rjoh|k(@^R9+H|KwF zEgvXdK9|kf15&HEnJLlCZw!MAjWFtyFn(!P4dM{$h~{)~KGJx{aCd$n`(f z4{TGXpAXX}4_!J?Y1L5lKlXkS9m+VUs6JwEfR|%(RqNOhd)bRdA3C?boAcT>B#1jH ziR_!PF|Dbf+R`{>lI~BhSl~tH4^FCh$GQLP3~VqtME-o8U|6K^$kI!wf%7bI2Si>^ zydZNgj`MKK8p|WOOG!)1vzL9I2U}iy%DI-NYykxKv(6{GSES2%1=8dQ{rrTscV~;r ziPp{kYp-*1)KQmNlSb?nC_fpOEhIW(?=)TSeG7$~IVa`?xyb3pqII!bowTkNTNbsM zv~;5Dz{#lclv$@pPWt1*TeiRVl2>@6&IO!JvHYyVE}I=8=Iig4}>=QKZ3@xIz& zOK#ZgcawZ{$P$K5z2EcI4AAn=6uA~IS#6(Wk#L%Ro=MxgZRbasL4p6ZH_~2VlE4m` z5qk~yTj`VY1xD@_R$o1a0l^Ru&i*U3@Oi|C(v+F<+U$HVc|fBbjjhedeH{*_R_>c}?Z?0^elLZj;R`Z~3k# zU)YGd7cIGH*>kJvn(b>3h<$K&$JJVIj=0dG@@2gy6>3o2ZC8u z?U9p8`i1WhV8_ag*xOf<(eTjkf9<`lc7JWLPA6yBzIn-Pk)uh!N9?_vC9~I3@g>Ld z%dU%^wTH>QQa&w{!(Ui-Sr?v@`+W&^DsMIu-*ukje}1<^qZ(SrTj==#J-D$PdJwhw zP3n4>*0y71$SMy``H#fR&?SJhZ)`yWJ^?Tac?cF2x4yO;JpO#IL@YHNe{@-K0G~zeIy2rVymk&J8 zSZ+}eVE-R?ZvtP_)wU1s$RMT)Vk{c-%%0~VA%+ly#86{G4x*W4Am$iqrl`4wP;IFx zR86G`rBn@}=Bh@`bEulD-?jHSCr;8H&-47==lkCO|2sdoefC;=uf6sf?seVQeXo6`3^)2gcjyPhjeam3`XRr*?(~C0|K``%jsDdg`qyxye|3lc zHQeZ5-JyRCH~QCb=->SMy3@ao^)0`?Zq_&5vA!8@);Ha;z8P-TH{G$m8E)1$!?C{Q z*Vou?E3JS#J29EC;`n<%ZwMa_~F( z?dy);aqwsP^>xFaWjXk>EI0gFmV-aba>JixIry_IH~d+agFnk}Uw8bOgP+W=uN!_c z%fU}(x#1_X9QU{&K>#10v&oV-ElMz&c9>sJmM#Q%)kF$@Zds^Z@Y2j6_a=y-R3hr9dd7>?_W+DU0?X=^4MOBS1-8HWAu zVLP6O2iu?On|Gd8sU!Xr7jg3RPU?gIFGWjeD{*4RW;}Fn(v9tDSqRO9;*kS zDGwOw_wLs1*5xx}w?EM3K-L%5z&mY(kC?P*@!}ths@8qES@v?@^pCxUZ{F9Q z@2nT%PVAqt&LcH&+KsX2x)@m*UT1wjSZ@sLuq$ERmI=nJ1LObNlXc9n&wK0l8=dph z++UOBthb|lNBcY9%Q@b19qp9s$T$1P{5*4{-ATEnEe-cx;usA)b#)!9D8 zPDtd)V#J4#&f&q~+q%TPmpHIO$l?-PnWl^S_Atv1mU~6@N=&lFMSWmv(mx@|R`8@> zOnk2-TdXZAIX{DIni$>Bs)9FORCc_Sd2HPxB9wqii2R@!(KN-DmQp*;a<-dwXp1}S zX=xb!#fWU&+cYLRDkZ8mXBL({z8K+UUPF@o2jEW*?025G=p0eIdy63RnnQ)s%)}!F z(|VZKoHn0n zo!3q>pVLw6KN$&;5pWVYoo*Qt0%e8S!SK=eNLNc>}7vTXF1XsGp!BMT*d}# zrukm=CnlNY+3)4eG$PNRAfecrI^K=^)1OF0vUQGT$iVqcG+xtw$;sCBe&vY_Ge0Rg zsef&fGSf1UHU)#uk!ID&On3dfcFg;8j-z>gA+Z&44`P0t`A1yxMI16)Sf>Q#}6USdaN#V-IjH3W2njTAc6`!S zwX$%Kd5YUC{pQT`z!y`}&qNS4y+5vu=}OGTcQY*mN7s*;=6t?uJ!>p#Mbv?qA7{R6 zI~+saNk_hrVZW~S*$8W$BXJCcm>*MjnjdE!%{g&)h5dS`j&{u}fwG*>b8T0%jCa?& zuaBAE{cyqjt&Vnamgn_j!TiGIM-|FHROtT3$KHPb_J5HtohW#J;quFzE118#(DO^3 zDVXotU*`L#{!}o(@cW%S`S$$n=ii=R^yjzd&o5N}!u9*)V!`_h=Xd($?fH{070fUE zesg~A4_zhHjh=kvV^=DX@W^ZqjN1@jBve=N3Oe&P3j9aAvB@csRh3g#ES|3+HD z{2hhH|CHo{`Gw1GJN)hW#YYv)FFe0Sj4PO5xP5%57R)dF{12zSJwIxe`+R_L$CvrsoZWBFFZ-7J-#yiucn@NJob@l_bZ$~%%@;};rAa`x?q0c`~UQNd;Zo6 z1@jB9AH8c7%->&V{xqy#Fu!p9jJgH$UE9Bk(T3n5DDtn-BSS1)VPXfbUkl<1tAX&J zE3PQ~p4B_Le~x+o(GCUk3*X-;s$hQM_ZS`j_Iy2|V1D6w{xr>=Z`S2Ue0PrL!usg; z=r?&U#=QAnxW3b86?~p+yPD59H??4X;rll%d3*l;%!2u@w#Iog4l{Ag)VyNt2N#B5k$Hy@^Qcg?s<$^B8$K zkeVo;hO~l2K%yZjkln~z4VeoW1-S|_KUE#a+SmLE7J?rSBf3K3A!8u3AQ_NM$Uev! z$W6#ANZITDL~RHQ350Zj^neV4jDjqJWJ0nbXCRLuFHo1Okg~Yn{{hbL`xCj4Q;@@u z9gx+K1(0;eU`QAw7(zkufn0?=fs8>JRdGKJX#sz6vsTSz2i1Y|a3H6#;q4ssRp2!bDVBZ@<+LK;JKNLNTdND^c=WI1FbBnNU6 z@(NPvu0K&1qC!F;eISXDbjU2omym6cLy!xQ+YkcnQxZ}M(h#CTIzakB%+CV!PbOp^ z#Qc=SD2Bb0-U?#{=5jjh%5-mt52PZbI0R;6dS~Qgbf%Ywl!Ji!NKb&IKwxyH_e1_d z97{urLd>#FTh{#4BU&W1NU}wx*jlH?L=!|ptc{_YM#shy(~ESr4eXd`i*IKe-ZCwS zSY)1eO0^{o?-~`GYBQ6Fo@QE$*o0&oE+U4TXD#DX;~aO4a-Lf^1QB!dO9+V{kbsL& zLQrtmps*0fBMC2}wJoJpOsuV|Eh#xBA>O<$JZ6L~h$u}U>CMw|CwQK}T^mGnZP__I zq+WU})D&AX zQpbC=iiwYI6_b=~^UbrLlgQ(#kNYa*@!!y z?GuJt=`GW`5xejnaX5Ev>3U`lm^Vhm#My|WLoc#TI8~MI_{3MBFm# z{3eSS`sTgNi#kNbnVsTgraD?FygzCa-#m4|09%qdj_`Y1R?pkpFamFt|N2C`mYqAa z40VpeK%#{$DaCvl^B6NAraz_!N;RoO{LDn>_roF_m&!!9@Su={{wc8pF`pQKJEG9= zG1k)-5Cd%~`KR5&>AueLFXD}dC#H0^^O??qPuf#k+u}Q8lqAIAFBIBSg8C0h#W;0b zhqpZKyfURtRPvy(sHCVk;-Eb(*p{4(Cy4X*H1j0bBaIY zfZmLX4vp!T6qRI6ePV-$*&2=%VokxBf;(=z^m!1$nC4F_SG}g7B$rNxAcCVA@R60Di*q+P$o((>lllLa){!d7!46I z{Xb}tkQ$%Dv-rt_H>1d9Kpu2`U$pOc-q1Dn2^o#2Cd9XoO8$U2Xr{&t81AB)aBY8& zmT8ITua@ycW0Df$V z2lf=S=M>M7WXGINNNNS`oSI|{!-PiZ#5m*y+xn#r9B4~&rfu@>5vgIkDM-VwH>xR`j%G^a8nPI+}s ziR}_U3~Gspa=EKeKL^D}hbP9wTeT!Iydz?hgJNS7`V+(|^E;|+EkDGk<{6s8ezwv) zv-H*XuDx%nETS1kXMbqnu!I=vz59E0j*3afisxuqjCU7(-6AC^*4}YRM2e-g%~vG| z%4~t)q?n<$h~bG)P9f&GnPoP!xzM1Gnq#KgSA;NUu6Z$0%PY)g`@mFc(P1$-pI$&K zMvMqgwDmXjlBIPmEgB2mY*(le2`*|9E42C4)Y#atsFXntE!pvJJ&cAJl4^_ZKb(lf zc#g4_5Oet$Zr<&>dRXoA%uJ22#U+|$qP?1U1g9p(T3XWaIyazcOibbEN?h}YBgH;K z@Z)gSbDU=zUG)q4)2xFt&)>dQT8(; zQ&SRCQ(A;!4fMpYOSBJ^cGh=mFc7uOtD!Azb})eZ-?3|vxHl=wfCJ(Zqmpd)nT~6= zdWN=WAC;JYKF}=1>Kv=Lam8%RT8zAyXnQK*Zc-cOBtah5ktk61rI%vzuF( z6U_l%h+m!K#vJs-=z@sEs_iUiJ6iVvrx9DtS_GMWy4WnUW6SpDcgZUMZ%P?&w%!_R zf7jRDi1Ow;yJU2YN{Wf97KLuNzSUE0(KKs>7wuR*Pz3Uio{qDTSJO=(A;d=T%K6e&o z_LKADyEBBCvMYqBw9b8Br9X-w0{@60THq#kbv4U#zNfS7bfS21*z5Tp^Zlo}j0v+W zlbZ_*e^a0Qi~oZ!VZr~y`-yPke^$bOP}zUDG>i!TUw!@}jI(o)N{=ImMG)71bp8Gh zRsSy~`MLer9}wX7^IxjK|8g-={rX4S1`He&Gx&qpxcG#`As{4DhYm{{KEhtHpyn-t zTefQ5CZuh<(DofVhIQ&39?_+1x9;y-u_6DY`FbMcBR^orZsQ=4{ zgGUiB3wEsbL_FH~;p1yKTJ-Z@?O#@YWdq2JKX@EWZZ~u>b zZBGhx9KZfIuMcuuzYW(PUv+g=72?{eo2z=dFMrHR>yA#|_<`9AeJ%tLeJ>2Y@b?g< zaX+2Al=~c3?-+Z{oPH)}!KsU!-|WW`@E?ym)QYH*+lm-~<1F}F_pd`t!HW<}=LUWq zNaVZ|1V?@lky$E;2)qzTL_*RbnUEYv*u_AC{2AXNk>>-6Jc!>fNQY!2FAtKA@98*Z zLIQscBxXbMAOX37Lp&pPNNMV^?|^c`&~}ip zPPoqej$?F&J&X2jkqzc zD4ZyWw=Lm?lsBKyl+ZUP|M0Ek^AIbwsS(t*UsQBoo0AL*Oiqao3?y9h-g!br@8{yd#`QA(Ds~BAytC(qf2M!iJLLi2+0ct_rrUAqkcs2?@fY z4rYyFiMqJA1+IxhoM#lSACB5Y;ml^0-4@@IksFWJ3`1HH@qzV>WSpB4dw*Pmm}YqOJo4)njPzvWm~~D>evI`L^PvUHvBH>9*WxbscE!C( zxckjL94u4Pk|wB=jTnk@G6)^_gyL)<>J)3eWg_0seBwaV*{qZK<^|INi26uvfcK$M zB8mT-Sl`UE0K{AW_u8X5Q400z=%|m`b7n1_eP^E8dm#~5ylGeSO>Kzzwf7HgwW--+ z33%^RtEFB0@1NRIwA!-ZJ%wBJpV~7R?d5^d8-=GiYx!n`;V*V#Y~t@`BG()l$#|Ej z0{vRdE7MCg(&AzRhT7esT0N>MSuX%YZ9+6$3R=A`5v`ht^#YRdYjV+1v0&b`dc$qW z^$fjKaWyJA*%sF?c6a~^h)>q)r6$G8$^8c*!ali4TulF@gye()DG0TXlcSR3nhvGv z1%OqJ82|^8!^2^J8j1=CP*ajplT%Eh&rw>agG#=Yle?6 zIt=7E7|emT`kochqAJ~%W1*FCV^*7y~TD<{L zvB|c20nPHuQJcAcirTC|oz!OeFNfRJX3loQ33})Mw;w2cbH(!@yHVd#$LMZMZ)OIw zp1I1@XPdEAxd5&&jObZVDU4BsBsk_uRT88$mc2Ya5)zVw&ZS-0CJ^h(J$cQtZnaZ8S{sJ5- zkk`q(pgK|=uijGsRNuwl>EN{?+9)kuo2X6EW@xjudD9Mp2GoJaG@n%P}8`(D8Fm56@llzj(;*N6>d>=lVpTy7Rm+)Eq z0o3FcPY6YY5yI!fUqW|rv)El)AbldOl-8hCJIizBCm4x~mF-G#^<8zKdP%K@w{M`4 znxN@gOD$9j*LrAC+8`}K+owI#cs*KA(kB|H%zCDSkHsO7OeZIjQ^*|*ZBttDh|Hsou=?Y9I^im@GCA*e;k6*_h z=Y53k!jHl`;!*LjNJ>5Bvda5Pocdb*MVq8A)xX#8>Sc_oMooh;nj7tmF!X=Ak%iw2 z#9wMgT{N;EIUY|wNj@cuP`*@Usy;q$M!+08r>9a_#yop-I!^`MB>?ZnP7Gs zTbm2vx^Olwm79u|yvpTq9r!uCC@d5OX|Hrjny4&N&MV8*gK9}_vbI9oq@BR{i_RM=n0DYjIjkf$#$DZD7`#dd2)utL#v#4d%PU=VM4)rJ17+U9h%ugSNXNE8nnO_-y zmSQ8>o$O6^5NG2*G2 z>NdmLshVTgnWM-L$+_e=7#X?bL-dy?Rh_Cs9i^@^B%ZO6>%?E@7Ymz(!beIo^`^?0 z<24Tv0XTe!arq1MU3+E}o6b&Tr?4~F+3Y-a5xbPlVC!=A_$K@sJUc;HBaD_CDJRwQ z&})C8=c{YAG+O&X%h8(XwBAwgs;B6~^;7z7{V%-|MrD9e+o*3eHb^7Vh&Bcpu|}ei z0!=syV|1c1#h78tHtPX2V`9MPWE9nb?n1}XJLqDJAI9Qu%pIl@TZc8+5Vkk_F?*c- zoqYjK@5lLbmAI;08!nU!<0823TrbXxZ^ViHR9GkcF0_}Y$t9Jh%1Grm zrIOl0ouW=RJ~s9lgrA40>WPLFLj_aE>G}L;!dc~^(p{acE!5KWiTV_MhCW-Lr!O>4 z7*DVkd4NHN{XlL&iw02lsAm+#lxF?ev+PCGh!$ia82#N>)TPH#5xJAnQ+ckGQ9Efp zwdYzH{h&F~Ez;QR&n~Y6>-jnoZ54rm*qcY)%(OD98-+u=?av;f!)wxvM-? zil`M;Qe{<9RiTrE)i!FV8m305-PK-dq#CUbQe)LbHAPLsYLkvNZi+fXoefR8NL{LC zsH@dA>UuR(-Ky?TcdOaze)W)g1iz|&N1q#a{j|T? zB5Em>L9M3NP#&x=tFd8h57t?d*>4y50m2-?S6l!soF_*r%hgQn8;#K?=_m9W7&*O+ zt;P;xw~=k^xAdzAu?ThYCTmdwJ&;X;Zut9nUCpjx*Rz@IA8c=K4Y!`lmJ<9RAy!Bf4q;8IAnIa2@x1s2;S8EmYYI+mhrVrD*U~~|(?PFsux%MCQ`&sHTRfneOM0zwmm7YV_ zQ0uGfwM=cRwnN*kz0$hrtg!^Y8|VSM0Wl(EB$-NXBsWrX=tcBt`WfxZl*C+^&V0cv zVt27e*`L`GTwP8^Z%pTk^PI3id?^-_-jS+Hq|{34p!8DCsFSr>+9s?5-y6q`yG9CeYj-q5*Ntt;`fS2#4BQbg@ab=r47_p8=DOx$HTIT$C6E`C@P5F zLpNjM*u&gqtR78yj-Mkg(l_fxj8Z^VeK0=mn`0x_!&>i(k^y8ODPxAMfX-v+Tl6~U z=tgW5H-hu$yYnCOU-Lil_xKQ@tMH+)NZ2Lp6`l(|qA13RDdJW!Tih=mLY=&&npiKk zN++bNk|?*7W91?8B>7z>Lg|mua7vk?eyna$n`zylb+_u>jOoUA29f7s;h_Fl*Mq4L zDuOyo6`{+~!SpD461^Vp@tU5(c(I$<0B$h%A@47=5Vi_WgqGq}k&r~GpEObWN(z)q zsGHEHt@OkC6+I6tk}uwLgQX{nSmn#W7W2uq^a;8oTJ;Efi50ncZWKoPIqo4>ns3a< z0PlQ)^{}-ND)hukR8(vumXQ=`xRfUa$d#2a<*;&6IjZ*5j%&YZpX*2TOS%_yVi6)8 zO<9qwN^%&>7toFd^#QdBD@S#D9T39%%t_`ihR5n~lJnqu^VNik;!N=e@r3w7tRU5w z#$)cBlH%p@@-nQkYvr%x9r7Oei2PEnq|izmrJu4vIj!7R{MApiPI{Vt5D2oF(cV~Q ze1lO+1QfA+bR_9bm7%Ip;nauJ0;&eRg6@s}AI0uwe_<?YjvrclqgkEZfezaN{@!lEkcaKJ3S{ODH}D08h{zql9>a1`iOatt;?Q+ z_ReLmV*S3$=CP02XY4DM;Ji3*KA3OER}pK7Wt8elBSpo0=!(8iR8o{QWt5VxOjMi} zOHTg$hyi}xrq`d|duT zel53AmMS@Dmvul?pJ|*Pq>s*Dffnibo$0V5u;U40B+y)2>Pu=Q{R0s1P38|+C4N|i zDzR1B0Jb(}-ct5cwx&P{fkHd0t0*h4H=kNZ{>3d@P^Hr|@a~DA*Yrd0#;n76^w0MvM{1i>;*_ilA16P4SC% zL%XLxHE~O#eeBhzO3>x$Aa)G9nX7?yd?W86^g|zSgPmAYN&r${BeC-PatbWQlkzM1 z9cZZK$`<9Qa!c{T3YntLS2tpH`&BIi#2yQ4WtH}`_D~DeJL}{0A26fJ8nuj;&>S(w z1Y-+k?RmRyOt(M30ojHePVOadkgv&7lt_)Gc2K!g1K4J9^l{AIm(V7k!pg|9*8Qg# zEy>&ztTO( zV&vlKugtkUy9i<0I0WfM`jQ&?G3Qo#($T+J!gb-PP)$r1KNJ^_mjD5tU7G^|f`^>&z{jg4u>SH@}MhFQ;yBBohM zJRy@|kH4Ur!TS1&enp2cV}aHrHkmyE+hHtPMB(=eCB@3XLUmx}MJNNHb4seyfUZt! z3iOR`3^VY%`I$v58?^zsocxu1MK+>TdOAIqzDU1jJlU>T$+xgSvIaK~7C=>gAFm1> zFm^BD{rtp%;+N3agQW}7I=Po}Ol_-mfo*ya7$!jPt25msRg<`&=Z*O6c90 z^gKF(>CW_m#S_g8Vy4TpWkKn#3|E#YKP%UjdsyKJwYci9R)$@=L|vtRtv1$5=~=K4 z+GBkxV&S-=8E<`uY(0lB`Av)LeQJJCn7e?Qgh!Tp$opQ6M2s57dLfq=f2WdW7Cx z@1;k=DjuZA>fM3hP=KGOg$WZ$59$Hk8gsi6@J1_sH2;JjCu|naiEE_w(1%;49aulI zr6&>#{Glp=N-)-pP^?xF=$l?jB>15e?N==a7REVPnz{N_oe1!>bnGKA0|8W!qYdui zO&ihI=sfx{^!+QmZw_;o+0AByC%VPw@v&keR{J#AhK;3Zaz|~fwo#*Wg7mcf6~9oE zV1sfR5okX@4ckW(%8LlSv-*7|?6a$ubrnPpriU_>pgo(3#gunU{2pn)#)m%1{|v4n zkN<;z#d`|ALV2OOZ~|Bp*CcvczrisTi0iJH2fO~6_zDPsNcXfbcRVoP80KRx7k2ke z?k?9-7$~F)iO_lWu{9AZs~^T@CDpD|5Cm%Jjs$4uW8`jX4fwW9W-GHJpYb_@_dms) zV=h8}4dQ}@Hoyg8LIjwFUeFoQf*lE`2x-C`ak}DaLtInKYgM)Su=2WSy})`TY16g2 z+H!3T*pDADlYfPEL+B;-3OcC^dMk{8M4;jc`bYXYeT)9BehB)bi1Ch5$&i3?JuRDy zCIvEt{Foljyn-%^VJibM%#}0b^Kyu?1-2!lwbS+jw-TA2);iRVTt+UZQ`x56KE9DK z4?1nBm?5qf*NE%IOlW`|;_iIhmLs0Rc)BR&0#n@-U0c^n@|8+Tev-dbNveuD*hcCi z@fc%mv3{&qb}5Gx9~FI*?P>jH$L~ySwyLl|U$1Z1i5z=>l>+P7l-|NUQ9Ym?S87DA zeSExR$^l{G-aJqEItZc`{TCa~RS@14CW+0Za4A>1Cw(k`ChrI4x~yCSTlYZu1J>bR zilusUB3_52mR*wDQ`!V55Sx zPFioxrX^~_wF%I93$>+K=hkc6w7uH*U}DZ{m$f^Zr(P5Dvb%m={{t*bGG?Ne`FdDm z-lZB)Rp}aZJ-P|a&=PIXE$Mc2C%PNhwElDqoj|A3Bk6JUWO^q3F})ZF>@(oM&GdF) z*zf2cuo9l8f2J?fH|Trx@APx}FWQ?a!IWVtGS!$`OarDF!!ZgI#I$DGGvPpueHk0r z%puG$W;7VWsmw>reC88oC2X1v;COa2SYX(x3W8cBTYSU2*^2yJ>?bw`f?@nc?LUc-IyaemJ?m9VJG(x z$whO6xL7WcO98h!3fOcaP{9mX^#{3QV2XbMYxR(O2~=4c%vCL51%VF&Uh2a41y)an zCY%g*?GyepeiOfw|BgS(pXD#}cc44|;)@C8gla-Pp{c+LDlCS!LMP1IzQO<@PDl|( z0@1Aim+3@sr@(t%6mo^DV8QPSdHEc>m*^{&6#Ybhv65I-3=nIJ^~J^_DYCG9R54Ht zhJ|3){@ul1Vx$-?4ievJ$>(AxtW_JJpYKT(<*ITmIUKnDwEQdZ!|(E+a#5v>@~%=x zX{Ja@3#C0Q$0+3kB^9V*s`9b2O!-3Dtb79&{x~?pD_C8hDjsSHwY>VC+CXJg4eLlJ zwWn%R6Ra696XSm+knq=Pmihz6|1atd^`ZI_UV+kDWiU=nfb4>xBf4mPwHPf~8?DU; z1G!Dx1D$k4I|DA`Hdu_;ny+3~Z;TbGFIcHGeU$FX>MR0xnt_#d4Q6;IEVv!|Zt#Em zp|6hUuItN9toV8QV|WN&f$R1%e2tQZpWzRER@H^wVhzzyjX)#VXai&!W<(g~%IBW* zng?966#VvTV-5K8Oc$2p5HMj5*yMA@MPS3L#?5^D9VpSyi+=X~?LUlQt#}h;16?-%j`h zj$-ER1GCVLyTkS5bNJFieIW{r%wzc(FgBrhDZWZc#ZU29Dk)W!AC<*m#n#MR+JQ6n&%+tnD|Ysxl+bkiV77D4Uch*w7?! z!*=~S>@+XS_A3E~OeQK4TWLso-t;^CS87d?vq@-@)(Zv%w%A;*X$~r{KM~ z$mjA``J4P*thA5$`O*bhResmj0I#BMfnL`7F&=gErDW&Ib5sUfYB%E;~I;qoG2dd_?0iA zXYXlW8oR)=>^FWyubnfW8RljE4wL~V*U9m(ptr(S9||pZ2G)NywiO!%T)Pcy=55xK ztHD*^$G|>Y0&H+a_(NzeUckClTB;#^Cy!DVDl3&E$_+4+WmN^M!+foQz8w5&b7QWt zF@LP!xshI$eK?Lh3cLMX@Ww27&G)IfV6BUSgOKRH;FDGX3;8gmn0Y_~D!UAN@Chq$ zv$zcI0=HFoPy7z6XIZ(1+(_;sQ%X1x*EFRs=8~sg3Tt~^W0lFy0Ka%y9AzN+fP9B) ziDz5~w)6qZae=7^zNaH_`(W_Mi`i>z7<>rBxw1S9+h`GAQ&5zL_B(makyURaJClpfXv>fY!LH{HaucmgoR~Ogh%VHdO)(P*>fu*q+!h8C%f(7uB;XpXrt`W({& z)6eQ{Fi-b@!^7H$vUqY2`2%^HyiDFBpIbb{yVOX`sl(Jc>Jsc7AG#ve%*ph5`XT)e zQx`Mo10bvWOgF4Mb1|}}f|IMscZ64B9>(l>{vrI4JUm}3U|(Ms1hJzSB_&A@rD8IV z_uVD$g%x{SZU;tTH|+n0YIEqo$?8_D`_Ey)>Uh%$+9%o}=(Kve0Yp6!%-0Wkark>W z8Ux`YTMzs7q1h)HUPNQWmQ*KsvOhTz-llB4fj9h%ZK>(hr(pkYQsq(ecra04(LVwe z`@y^T8`j!Hb^{pF8{8sT>KXiMF#Z4DTzdvwO9);-wj~8W!C$B(R22e*+Rz-01rlBu z5vx@oIC1w_^FLuG{((;V7<_Miz`x!mIg_&V2M(sG-;HS4%Tf7 zEaTbIJZX`%RLYQ6gJW>DM+$P?xnKluN_WAeJ%%m)N+M)0*;g(p`^o-tC3!0tuidb# z_rouHM9z^<$>-#Ya;|(;zA4|8-D%VSrM6OEX{?Y+LCa?=d{n!YY-K;t=@E>NQ~$`r z=D4u=0YK?F+6AqcUQT~cFJ;ulJn3!>fd&65c=XeDt~Ap=Z%nRK0=x48yp_wyZRBy- zG#*qHctk?Mm3>A1LKR{9fG2y!Jm89m<)tPP4Xbs8^dVa3fHVg_GN}0S#$f{ON9f%BjJ1TmiPx& zxAIb5ApJwq4T*xs=@Ya~2QX8kVX>Z6O2AI5qecP2)YByGxxNIw@WQN9t{1Ty2L{&A zDzH7rU{!Vj-})={2UxHgv_Q898vlZxfmQk-lMDZL8LS=+SPqPO7jWqV*dgpN@KDvc zfm|4nyTgBP(Bb*xARqmGxsi~Ua4hL3IwoedAIFH;+l z3Rl1h>(Kf4;OlS2^#cq29GbxuVNQhq)ztS9(qw6&)JzuTAh|6tWG~q!$IGdhDG%jP zrKZ|U-3oLSf4i0U(-4C8Q`Ht=;Kt&2I+z|xHR`J zSC3=R*X_7&+&JKaZQPF>!F%z(d`aGq_vb4CD|81^m9MU%Q`h7)L`$Lz1i`kj0-c_HeUYei7mL6zXZsTD~&(oG^pKBX{ zThD5jv=Tt9@9FhmwT0WR5_9Nbb2Y_;jI z%?{FU7_A#vlZJ6);3I0xGl;%;!}0!WP1@!kEP>T;v9_;B<s z&FtR*Z!4y=1pE>0$Svr}7tk!t=>b48Yv3b3M4!VL3}P~vY1~?F4;ROefDQE}ET`Mx zYg!3kfZx6WJk=lm#kJx=@h8z!t|-eGdu`y)S%-1|94wz^Vf(kC^*qae2K-HWTiDJO ze^)}?0?69XDvkffk()6f>&VhnRq7-95*ILdQ1x@idn%*h~XInM!!9H_4e{e zd7=EHd=35YiCYaJM^Z{mkQkb`e!C#Pn zkzQ01_}$;>SB#G6iwm45KbBt$tukC(0d(3y>JM~!My?58$PTPYe<{iEHlM{fXEYI( z*-WsoOZ1I8JaduW7GL~=oC1&BQE+l4;JKf}UEyAHmEg_Wf~cHa=u{bO)ECgFFNHtF zweT(%lQpcCWtDvxGXbihZqT3Wia90{y)BOHIAXJ^0MF!t6%K`;U_aQlX2SQFlQn@Z zRk5$sNRCmOs@-5+{BGWt?rp{9lqVZP_dg^j!}q>`+Kd_WGxZXTXazbDtcDL`>i!hw zLs&-hn6=;}a+q3(JGjfXoVKh@>Mnf&mT;V1)F;vE2kIbPgMbX za)=3M6M&t);Zu-cos8yJ0BQAxE&rD=Ra_#jf;QiVv0OqL3|)Fm@`4_nC$GnxdoFJP zTJ8bQe-^Cfo2s7{qIE~C(zl4rsiXI?`Z-gNv15P?dwckiGI@Y3N2S0^&tPVy(50-NrpbaL1)!PFk^rLK`6=o?dVBK$5No_TFLqBkYf9V~7VON0X^R|57{mI4TPiO}Q zb&)Z*YlESm#ZXw|mV&Jr$JIe($3%V#KLeH@DSQAOd{lfWO41go0-ltJ_zn^r!eh0B zwnRIph3gBTRYHvI#?NM(XWQp)II!_P(uWE}{L>8TJbJz!-5F6mdoeO!&@~a!GYtN} zT6pKh+;3b*%&axyNyJ?>lxNAA81YY(@6~v1jJ6Z(=T+^2R#~s5^STC&odEyRG_an( zfG_BdD6B8weaCZiysempa9HYx$Uop!@}*Qf`6n>(B+y0&x;s25ljw!?HpG54!mOUh z90n8i3mCB1j4yb_=4@wnK3d>7JDe{M9!Q6kIz$)`J9P zSofa%2s-Fpr4RhQuatJ^m9gq#ST#G;-H1VHh$y<+@No9mKLn3|Mek%JAQEVualx!t zo?Ykrli}n{>N6^fx`CL_Vsu@2Y4$S5nexD7O%dlwz{(=|PVh5Mg4ME%FCvr`Y63?N zg0C|JNb-qL2mJneu^D`=nLw*QAx`BIPqTf^xIzc20V(hxXSMsYj>D{53Jaxz7$gqBiu=8IUo46-KL@D#mNZp9h}a5V z>4P}qZ5ZdjAxg6@JSXF{^I#84qJ^T+SJO>T9=ItV;wlae$ry43IUT6lK+MW|YA={O zlGYFdHyTXXr_c%q=|RkJ^wY@%gas|B@uRS-u}~zP23^TNU*HeE=d|C+X8M3(xDn>NoY8h*e7m zBexLtCRo5oAImO@MbC~UCm~j63HdGb#VatiUWmahN%>)|uLNB95>dY)i2CNhnMSaE z5F3@mPC|6eexUZYh+K}vTDO#6i71H6V3J<)I=tWE@Yv16j9iPDh#Q!fuY_V^6Hyae zi<4n3F9p)QAl?=qfLp5y7KB1XOa@|du465zCU=sf5XZCz{KPT&XY_6*unx1}|JsPC zoNJiVRn^9bp;(HD!Q<)^)kmwKb=IP_H0^6>o=d=_ueF(oh;;g1Z|d_9nFn;0=tKB> z5JY95V1sN4tlkN;zBhQNG;$^)pqC*2cpF(Ay&Mi+;SlwZ>P}AphF=Cg@;jjT)4&|p zG51y=axas;gUG$tY*9pa)MQrSS@po9|RiyX8S+taTg7Zm`hC**F1*f}4+6Z6sH_|@o2ShNQ z#kz0}y7&+2PoU1Sun?-r^<)ZGg4tSwVau0J&Vo+BOk+@#>x=MO@w#n1z$&K z2pu~D{O>IE=n3JP;31X}%Zs%Tk=Rk}DGo!Qeui~yi+By5;^J5>Lcz^$0fWwipY5xR zQ+@$ATts~b9z;&W?_xhFUYkbaceLwP}M9yFL>>!%yMG z-2f~95-jXd@O@lFGwkjC;MG>7 zWl==Z*$~5)Drc!8n0G{5WcXM<0q9w>0;yuoFC=?{dEU%c<9xBQOyn0L3hz692yDtt z`TpAq=p`pNcT(ZahiBT$SqSU?bFwqF9-KpYu+}Tt&)Bt?-<#R5*_~_yZZr2a#>O7Z z^8?%u(As%me%^RzCW52#v22j9nPW^KX4z!m+8l1C_yutEe#FOI#C&-H#=S0l6n&r# z$4MU{K5LEit#nfQSsEyh170Yn1ZolRw#J*7Ajijw!^k5~Vk}po)9K~(TKGa=051hF zG$UG`&sjim=YS`#Gru#b>;b$~4!j4E>tn^yCtx4UCGr|s0#WJSGzX+MlAgx;fJx&K3Eu%eL)0zL z2m9v0iC`^w4v$6$MCgUUb5|6)eE@J-NA*3;&Ycvs;+cn28gRiv<~DN!{+fQUHqP=k zjKnkW+O)%3Ukx#(acGg6@={>AZ{flFRND;HD?{HAenqWzEk<^PrW;4iLpz+J|D=mD z8Z&@N1jeWhzHpDU2*|lUwCD~nm~9ZVd=4@9gRqz68LWUQ@WNF>eE1q@5+b0e72g<$ z9TQ)W-gH}f6ng`j>^-R-R@lYTJg`+c;8RN|vZ{kAZ-reW9n{WhSH$S_QHQF1w0>BX z25a$HnTCSvAA>kN(4OnF=8_2hTcXkASL?Xh9TK<(j{8FI?e5akThAu$F;dXHkn1v&VfII`Oa~H9= zzLFVfIR)#-Y9R3Mu;1VpU;_`?$I>m0pffy_a!NhRSK1tN*aj5374Zkh;1`uO3!6t4 zwc;d}lco6z{666z!!thwmvC$YBVb!TbOe zS{~eMIP|F(R=}UQG+?PMi1i~xA9#V^6Fm^)d>Ctd8D)*qNc~tlpk2{C;3>82)AXX2 zJysoaw+HqHJj96WN?oL$ZF#8-0-tdTSj+?Q2p15w!&>^q-WNVtA&0<2v>m)~BQRAw?3+Zyp%58G ziSBqcimVIm`2qB2GML$su$3kNH%_M~NYkV*u&ZJdp1EE6*^1a|h#f?ufV<8h`mY-N z!eha&-3Q`}f_;%@+%{J#=#-+w4jdXI2Bjw5*kOSULCG87r8`Eyq>C_35J@Y;@*avP z+WD})SHmm6i$92{zTdzrZ2_b7EB0VX@E)v$4?qG_(GqLL2jIPS!rDC|pG8FHO)vnj zOtcMS`p&N{xpH>Lhmf+=mZ}VwS`Ie1|Cst-Jxz7{9ZdxL?7p zG={%$5O&t>6-ZGQXNlW@uz!|R) zVLm&rs1Ss z`zJsd=MjJLj4uPL($u1T!KaUbc3vkm!|n)Qb1Mfi2>jkpK%?!j$FV==TYKpEC3uGt zTs5u}P|G<)Z2LE7;I;M=iV5!^I;<9aXQH49 z&4tde@q1x+!VqC7qPM3bayJ9yfzfmQ@ z23KU-GN%zax&U+d3F2+afh7?6D2%1Cn9IlcGkgV%^t$jj^+){7B;lxV0re#c=Ud550Nl!sMFfhq3?dn#Rgs61DT zYd7>qh;~!pCI1c_HWBD+>6&X~dF%z)LGMK*>t%RJo?_;Cb0y$)C=b6w4R{`!0O_V9 zX7(1w;h)g0W%w$v;24aX4wl!!zJGBw?0|1DGLQ1V@I7G#YykiG65jOYVmr|$#*6d8 z4rW*}v4^o9T@oLOPhsP&!U(7-N2q<(0nj!h)k)YNG#3o~M))HtA;vTYYvow@Ax6QX zS*mYZnT#j`+3ze-o& z`?@1NfSo}p&9slLJw(11#`ul=nOX-%sW+km# zDlG%j9*#J+hlqOJqW6JCMI`!KnBX_6FSJ&9J^-zvK{pTJ_o5YAV0XiG@UYjwjn)yP z5VbKyOq9pU3xLymA`+a}0)YiaT3B}@qRstbyETIEe<31%j_QZaK27(v*7a2KBWe+K zgf7ec!hOfDghlvTC?Zt@@>l}9psH-ZS2hPM%?jxFL-5&~kx$R%j)+j$q%23&oew-W zwXq{;v%U*ak7gXvF)+Rt;oEoymL|{`Z0?htZGToHvO8A2!`S0f6%2M9*tWCqq*Xv{ z4g=&EiU{+P7#H1P`6O{4avx(p?Bj=tqcI=mSk}N+?DsLPnRFnGQ&LH}A^aJU;QlAc zdz5|ZRq(sm?UUhaVYB++cSFfOh}<6!OYC!a84rW+yAMm$plZW+c#D0>mg3&ydU9oj z%0dmH0d@}Dkyop6+BJDullRR^WB|&7{=TdpHC%n&#$xVoK zUk)DaCDju4{-@B^FXksk?M6h1QxG?=*L*VQx73b^ovuqkrEQ@zAIY=XT%$?R_S0DBDm zSOoj(+h8T@f+(rd!f3EmM}_0!X^{sOZ7+QwjYUnHfsy}4o`X4F6FbCL!K?TSbaOGQ zT?NDe+{X^#a=^YDFz>%Gf9@lo7;zVe`(SCFkT1a06rp?&Y4#3Pma2d_f$CIEsxH+K zQD-!E6w8zjp0O2tt{te(z>7VoK2$%v$zb>v9#KuPuXhFgCH8ezgx_i$_Om@@YT_Lh zq4vMQZybPM1!&GCAbN5ye*zH!G;rWj*deFU@&sZQvtZZX0lF@VRc^BMF`|aPkSfUm za($VV0}*vURsISQhCd;Czb#NzAFxzEz>oZ=8UQx780BOA6ZjrV zB8D#ldqB+npohQ-79)<}T~ENqx=gMIM|lXlo*n|Nl%y$oC_SEjME8MJ6VD7~wlVwQ z^DYXmX$T_b26GF!Pq|H4?K@#Naunh@hwvZ4!hOzrV@B1%eCh=LZ9Ag7ZoymR1>{`} z7D8o7kPP6*4cJY6R5~xUmOFx(8Hb$`OXYOzEZPar*HO%y>cBM`;+VqJcy$CKgBOEw zy@y?;(-HA_9o$=S=$#;VQTOVx#%Syk``Vmaf%qFGIFunPgOBNteLKU*UF03I4Y;)h z@SxU(&KZulz`F3SJVaDMHSDl$#ZF~E<4(fUcLTdJZvy#vK^xRVq(c;N0SXX-(C5Z?#GITDd08^xXCk75IOhjvNd zOSMtg-iS7s0t^%Z|7$uTGnOi!0qJmP-w^D&Iu8Hk6)+|h;K7`Ty}z3gZBW*zf(V+4 zm@RvZ116Gz@n|0>UC06CXJ9c;Sdn5+$QUY>8VygyOGMkXpp)nk7%|)F>lV^p3!dTt z_Ql-C{<30RQ|x^l3-4zutnFRF@ht}?{1G_tCHBI;kC=Kz zaULAiL#d6NB#(fP_Y2^-eR489yX%#&fq`FQr*S=azZC3nj)E050c-qxcw>WM1JA{7 zqifoq*lW)s7Iv>5Z;S;?yWM=3MEs2!94dgH4aWYiA=ufKg}9h@h!~qoy#t?U6yg>M zM2B8xo+BE&CTn9qfxVD}c;>ZWa1NmT?*qRULu_)gFc!Pg{Sa9^Oq>co)G1iQ8Z^)Q zl1)m+zWWW>dH*f;>D0nrIt3Q*N^`G7I&=?kbT&A^Q@{ro9sB<8B5Kv#vth=p1|Y(u zK7N5iWCEEsOc+LNBs@WhOd698jW`=oq8ZE@>~`3J-QtJftvv_*coSUWGq6;?*cn#| zvE%g-H!fl?Y8&iB?T)yaL2M#MQ#$s!&xZcVuvo_(h=n|ab>$p(1Kz|=z-PcFzKH*> zWN9W6QQv{kuVGk~BN6GH2tAdK2=Ce4BJc=nfHil3(=l7-9CmHogn#%MJXF4j9H|7v zQs2U$f#4FtfaW5BMH8_zE*+lC*@)N4fIl_E+NZr5eB64(-5dhjO3W^1?L#OK|C>>a zc#QUP7k@yKEcWZEVA_MRi!GE4BO`z`di}%hJu@<4#=rgR6wci)d03qUQeB6tm8#7>KuYtEavrwd+ z8JFxXiY_dF|4=k+9CJUjGxlO){vOUn&;}V`SJq%gWrB~f$KAP)xg)H-&m~pYeK_^O zo|FI9PW?9jW|TPkIF;3cGd_-b(NCN_!4bE*1A|tZ#t0d>( zm&t>-!WUkO0Q9Q}tQSW0q6T3lPlpF@5&ZL+*k@+0kh!oBo>5-3A6=EMPqT=k2!%&G z8oS*`(NnO?E(80XchLJ0b#)Qe*<;M=l88dD&#;)!p_t9ln9HLuljmU`ug5Ijk2!o1 zGx#y)Z%NGF+L*g;W)A+o7uwB> zEQyHws%X8&m}x3zStwet7cALW?7SL(P?C5&wMzzwdGpt$7zS$BghO zi59Jj+0qy@MMbNI!Y=HE7{pk#>?r&q#0>lz#8R~Gdc^SW#xA}iXyJ?4y>}P;*qS@qcF!+k{M+NyLhZ6$nHGfpmtKp&3Nx%?bnpfk3Q? zKtv!a5Qr5BLpsYiucg{52$5*WYsUh)oUW^4J&_-2Oyr^%f1Ft z_gViu+_;!6gw^)_22x+@oEO<1~KFp6uhp z$>Z%W5|^v=+RpIZG^uZ&Gc}|~Utn-}0W+P@oBV{o`ZM~OI}`&k95s9Nb&_vVRD5$} zMJ`^63i|6Bb3qzZltus5#TU`X6EUJ9JXKZwoXC4CInDf~=_&qMcpq$ZS1HbM4!m>0 zx(a8xMxN8ao!sU;cd0J)sa}pa(^DMzGhBNM&h?Vq$D#vgbGB1pRt_zQ%lWP_hpEOS zrUqxcjTWNIL`6ob?5K21-cvN+f~t| zkDpBo`Ids(rbpo%CE@h@=n3=i`V(qDW#(K}$!|}O>eR7(a+s)CAd2hfYU^c$cjfoT zh-hv0@?k#4*D>OdSuO=8xyT%s3R7H8nc-5W+SbI~(1y9aN5LhGV2~fGVrIh37SVWL z!{`<$N$=2-nzymHarC4%iqa(h%rxD-Jk<^tZB3b)tViGW43%kvsa-8x@Es~VJtm(C z!?-|UIMyg~g-%N1$U^77+~ef5G?v)ZNhF%2k8>JNT(mF}Q9O0^z9Vz>6?Z$w;-mV2 zi%L9HA8}Ak6@6#w&AlPlUn&Ay>Y0+BTuM<`?!Mr#+xp)cjM-Lh7XwG)AjuwBVw`Q`aE;1qb5iOnKO=9Xw@sWMqEj(m`23?Yi#O`q`WZb>f!H;aAyZ3Z90XNzk$x`qx{Zgm5{{44#tW3cWh-=*<(%>`JJdb61ea}&4N z@{c%J`jL}0Le=%}^kn+E7S+yk>aXI64xTXXDLw5ODu_pCd2G%5?9eAjJ#)yER`UqF zUvS5K3)d6sjCsWNG&P$q`0k=-c#83lC!(oX-^LLZ;(Y+`A8zEVNvwQ|n_&IsxxkO( zyy@&}ZLNehv&OsupWW)>XBpyGH^HuqYBP2Cu1rLdo8E*!4Pj0rn{2PK|E}3^(v+gE}sv$bCi8@Bbxn|>96VKWqPPG!V5PW)VV;t0m zT1J6RsL$ORbDPXx4qh^ks(U~cY>w~C0na_1_7QL{=lEnDR1O}srGT2u9B+X`oUfvI z@QLSRqPHPhJ49)Zs$@V6o)d8$re}D>(}3tXSC_HHnz+QoHavJr#KUDpFO~0iodUeJhDgxi*3w-UAL2R@8+3>%%pL@lxbn|vTgF5tud zZMeBO-GyU4y?4?@E#MPWV%eG&&S2E;&j20JT}Emoq2JMp59T zCy`0K=Bvuw60%DS1D3kzi88tNh6*m**zBynx7b89cZQxSz{hZ|Lf<&j+Dtk?bJ|c} z!8I)Ip5-R8y69{k7}0i)v6(^Ui{QkK>vHt-x{D(2Nc26N=NgN+EL*EFhK5UPY`Gh} zS;4t@%DjS%pFW*u>f+L5)&gz@SKt!qxNhjyxn&OP-RmdH;aV@cV znoRGKem{}9nazx%r@;~2Q_0)8QN@aY8hVDov0|>0E3(jYsC~MI4j}pEHr@aedFs+Bl;_iwl`tAg8CO-Gei0W8CIU-aVPA zS)klaBo0h5S|R2XMXtf^^lDmVSq6`jisc@9oO^KKS+li)<7Vp3nvwXx_?cXIBI9&ouLzeb7i zpa6>skG@5<9l1GT&TOSYa7@LOpsJm7zAPHa=^I#Vb*VL?S?p<#U@@m(b5^FFM9is9 z>IEwrcbUP(H!&6swCr)Qj~;hhfB8&%hdq}*6qwlR*RCmZ;ij2~TO`EiZ!EBK*Xy#A zOVl&4l=O8@k%({NpDDAO(Zw>9GoHmY=qomM z-!2y;FM01gyKd%IJ(*o&+orXbcsge8JuQ0SFDWuMG#gtK61q548ta*BN=&V{bUG#ElWQp}e zIfEh+3>C)yjuuN@EoK-?`;NP;FS74r-nFiq%W;m&>dJApwk2~1Epc`CGPD<2cJ)xq zbLF@)iO8Xrv1^+7I%%$+ep^wNVRn?WpM@l}r08i)!`Ym_TX#eKNFyt;zF5yzVxj1} zvm|P^$Tm#RDc`S!$Y7Mw_3%%f{Cm3`DH^38GeZc;@lPoK z0{*erFyR>hY5~E0J;K#03iS-J)jH&^rF|{Q1%1#2Mh!Z1`Go* z84hJ6AevssKskbrAQ!M0 zAjlFpUIxeqtOBeCtOGFFK;=d#Hv_f+wgYwo_5heDq1;Qy`=BfY90VK&90eQ)oCKT# zr~pigsAR{d;kcMyUx4x=;3~bo2IWn_Ex>KST>z7NRI=kobgZWG36xI(FX%PX`LDbN z{w=-t4$6;!FMzLr?*O)s`T_U__zht4mzDbP4`2uo#E6cKp){f6T2Pt-%m8%&Y~NxD zs0*+L)B~`sUwwchzzNU*AO<)CTmfzX4}ce-3BU)?6wn;d0?-n`q!pB{0qp?o0UZHM zIzuS|bfMRb3xwmK|GWqW=X|8TwGnC-Xv0r~?50fqvG1EK)Y zfEYk5U<4o@AjoJqP6DJ5H8Q1l$!xt=ryvC-|%_|U>9HyKnd6j*biWG0Lp`aqxAY1 zl*j4#B$O&Z5#Ti7EZ{ug0^lOx5}*`t6>tq8$c_IT--hEmfO6vQ{^vcW{}5h3`49IT zj$Z;^0bUdT2FeeB&w#Ih?|>fwCcmKkO~>r@A2|N^pV#`Zku(4>F`|+k*Meg+fH}Yt zP#3_&ib{M1IS#Mu5oZgfJ-`9r2yg<30Zd$}bceDLzysg~@CGpPq0$#hKR^pWOF(Nt z8$dfi2S6u4XFvcT5FkjI#F;O4TuAb0K@}E0!9Op0Lg$90FyCLjsr{tOd@Uylxcu;daZzR8ej%s7ICwo z%mQQs<^tvc7629kasjZ(s?eTsYEOI z|9;>O01nc7hp0RP?=c)d0Xzj%{D*%@$CXgN0=x!%AdcDm2*;o3brqC9|MT8&IR5*e*G&H( zyw)>>IUm5pfXW(B8q;wtDovrR4X6VU0W1JaETOal*Z^z+b^v=oeE<_DD8&G0fD6DC z;09<2@C0}P8UwrmK7gix=72VU_J9t6&H#Tv0H7-%5YP<}0$>tKWlt!3(J|wD|L3?b z9QOkZ0t^KV12B<684VZ#NB|@PMgf>4K`94}0gMHt0ww^MOr&x$m5iGL$7z76fN8`} zhjKO`8!!(rAFu$B1IPm`0x($&;~)w z>;vov6ao$c4grn;jsln*hw>yq1vm{j3pfWj4=4d#29yHI05<@)0Of#t0734<@dLm^ zfEw@=Pyu)es01*14dq)peh1|zz!$(bKo#HzfXPoN{{sH8*K~45A4&s24M0tR5x^K= z0;mNr1(*XwfVu!C)==62>H%zt!*?Jz;k7-$0pJ7>0|asX&#^lkdjh-wjfrmpr8mF_ z;0tI9Xa;BrXai^u=m_WxU?PFC3m}kQGcE{@g8?Cc?tmVEP(T=ppRcqk|ShiC6ihSzC;bbtbo378I; z0hk5&uVlmPIe@u<`G5rgCJUj=1uO6paFaYde1&+Vc>+e)n zLHP^t8}JA455P-Jn6Cl)00V#^hHz|5uT7vd1(*Tq07L)_0250p*|8NI+W>3n7PqaSYU-A9DNu!e=cJw1Kd3$C3gkgtHs_zu$&x~&0 zxSNNK{iHv&8=3iR$rzGd^(rR7{9JzHdzJQ16(-M;#!mLHn^(9la9Y{d%;FOX=T9|| zr+aDQFS~k-UE(?aYTTpZov+;fr5oSU^Lbd9-7-tsdy=x_$oJ-lXE}+kcKDgp*kZ|1 z6PLO@v)hafGMRWjvu4{*a~({sZ~S|sc4b!ju@R>p?QJTm^Khl+%i}TDJyJ5B*tdOe zaAerm_x_-N_Eh$9)rL=tAD=&Z>PB&t-lrw6S|y~Jp9(*FZ;@p!*T_E!j|(Rhzv+0_ zZd`G64e80MX0q@j_Ku$l^xwCZ9S?eAH8r+$QpCewCYQVi54sVW*u~~Ys_Vxi8Pm6| zzvS{|*R$@Wf16m!?e9Ooan`ZVM5CZuqo4h@cw0RE&BN)nrE$3h!&bFWz8Ut~Q}Q5t z?Zpd?%})llzT#|_G2CrXcyQOJU)wJK>;L4vOV)(v)4J}Lc5D=CRIh&g?T~njm+-p$ z$+yzDhn4?~zjetRIp|$22S3x!dN!wOt$ez9&Kr1tqs7OpN8cy62wy&1ay{qh>)9I* zXE$l?7i6?0=#$5&M`QA9?{!`F^pw*h*BATu?;4WwZDYF;m*zMhpBCPs@`--OzRjl8 z4ffeSJ=p7Yqtfub%L?=je%`WeT49}SWYyUu`CZPqIcrSo@4m4(EBW9sqg?w6r-Wy} z`=l;z_G-XPtX~ zTAJjMdpuKIy2yU+7dY;|eMCl$j#pm<1m1c(uWK#;ueYvDXg{^}zaQ}4k=Kje%R(zJ z>}$XO+oxjR-;vVwUlu|?Z8SE^wr$$D*7zSEnzed+e&wBI9gX%$JRe@m%TK#_{ESho@o-gQmh|`Y1#7^E z-FMpE+v4Ts84j=eeL7I~#V*%!jsARIb|YvkWyebb@uqz?{lcsFpoc|uH$3e$?8YXV2yf7#^1 zrqNt0abaQpfk6{eu6ygl@#kNIM>Q)w6|k-6yS>|k%a0e9pY?KE{LBC0$^z@zQ@;*! z%>Q(M$%`#-AD(V!{eErk*!#9NEqV@YaARkWXR5MkH4@gh2@U_~vHIqsh8^P-?W;O% z{5k!?G;^c90S2v{UM~JJE`F53mw!#{&m0%ox2y=6mlT;F_PK4|zn8t+*crOS-$^z0 z`dHU`N0Zpv?UtGLUACd}_PTpr55{IZ*?LJ`!ZpW$=vdt{aeUh5A^N6;kZ{-r)f@o!%ClC^;SRY-|EH2Gj4AVObX6g z(e(NL`JdxrQuY}Rua|#y>$~#UZ*@*fHyWCaH2Lf8ZOvUf(YtmF^X-y*BUVqe9Y6HZ zjhYkdbRIuiIblpE>5Vr-9|Vd-OE$a=YaM*@Sl3aH&Qz`CTy;q?X&{N&m8bT?^%Yo^Ed zvNnwrkZZ|pRKFKQc^?O>iv^wa!dqGLysnSbE_HB0N2W%;tTp4!q_x)9SBmHm2MMR3) z|9WR`z8b=!6Z*|+S|GQst3t=Z3g9D?gbzwsTSacZNU(v8`XQ#rfggkI;mV$_@HFu_5yp!H=pAVyjLp6oolvr z_RPdC*H*QhRA1R_=B5iD!^Rvd8rq=kr6G&{Z5uz!uk_3EvsZ(j^e;)Q@==aAl}PeU z8)jQ|p7Ypd#F58srXCs@KQwNN)%ir5hVQ1%nDV`eL*9v@okBt$jy@l_R8=v>-_+lx z=D@)IPu|ZMu~|C)NzWk{XC9qj&v=>hwi&xcYd1HRH>wyqpy1blPSetqT;8s`T+>UH zML!hbj}zwI_z<7sUeBj~KSSm9!P2r5MkOu>Z#;S`|2Qdek5lh>bFheu~ug+%YUp5vR->gSOAZh!i^CVv|{ zS>I@CL38ESeMK?HcQtPK^~o{UiuZfW9Ith@v;Lzxx8p@`s}PCxyQ;!Yt4H4G-qa`9 z?6gzkeHEXBL?1r+T`*m8vi+Ld(S`{JEv^+TnWZAP4K_4RVEmTpg; zxIX>RtXA5_MMZzlN{X{5kE;1-v8Fs_fI}(h|G6~A>dfuf+D%UUJ9KLF)x2U!&*y`E z4(ynnp7G>yPOzx3qo4(%oG-`m4>A*e#=eDf*)7P0E9Pb+qpNvqpu*$LU+byJbx~liuR&-#UBq<}O($T_`zccWaZ`$Cb6L!@sze z?)`hzVoc7b!mZJ_`d41*Qc}Y-Qg6YTxbQWSM&-jA5A*-AJ?`D)Gkc!qKX`k2ny6@h z(VS_2)=zj8{%+EO&*$q?%oV@KcR`^Rv-$ELJi-%a%QUGC_;?;JKR7F}Jo*gVWyJk;q& z%WI#zK8wpu4q84k&nnUA_&mQeQ+kZwRKICgo5XAD?dPl<=s)Yt@v4y_gU476N{$ADEo%^)JrRO#C+o?TIUi(=&PS8;HHP2Jqw z%Aa8`$FBLkVpXDhyB~Ge8J!#}=~rWQ%?O+AFE_a+B+S0nEb9Kd^U>b#t_`>!X1saS z8ck}(lPxZ*ekaBcT6JR5gp|<{k$*G|qeecBIy%(1&FfcPP7NO(c_}H_t;5&}*Jn%X zZAfK-fqv&a*6KlV63YcAS$o^x*X{a|_c$?}zZ8dUr-jR;g9kkG&oQZVrkUt^9W>cgRxrkALUX{m3;dvR=JqLE_@a z(VzPN3$AlJHaN=bb@tj`m3xB?Hl+ORJ=fp!&4oIhql{x_WoA$A=ekF=Y1PRmx5ABP zyVaNNyxJneyhmj7@z1=hrr+r9@Nais>idvGrgrtpdaR92kl%LoekfCGrrfwOHD;u7 z-^=R(&wl&fFsS?_pSyP3;3wz&_U4}3Vre2B zuy9ffgH;PoAG-DAix21D3+-8dg#XP2W8bG$xq6LWSu)mU z>8E8~z7{9XdU+ylVTEm;v{9t#<|gnNR_W|x2%7>bjXN; zFz=4zCr_CDJ;~x)$;K`7_6CP`Gu&Fg$*K%4{z|9lyAGQ!=U&S_Hy!fA;jYWuWsfSK z>jh*j3DCQo@~HI4uN!kLH;mbOqsf+-^|CchT34p#iJ#|Z$9HHJc`bVXIKN>IDP3Fb z^K?Jo|K6LNgw;R)PEJY;`r9}4-jpX__n4PY-M)O&-h;ibHg-x1*}cHC{Cm@5A8tiF z&CD)!)w8MnrB~OMAI|OSIJxVD-Q!lp8Mj}0@>hPJFPHttyFcERJ|pV-p5*l=zYQl} zPO9*1IDglN#`|0K&Y5$kaHOjB!tU2&9j@%K(bp&~dOcoyX12b~!Bg*tzFIt7uhy#j zrO(!I2gh%p=@;F*{IN1B%{6Z3_g@cQej4BBmgww)gOv;0J&9Z{zv(bxkwxztL66-H zPOp*K7*rHm4t#kyIpX_DRpqkYa)E-eXhg6-S1}l%6U}aw~Xsa zGa5!O`@PTL+>x|oyS%if_dT0+`}6bD9sPDP>$$bteye)=@l*Ery{j*I$DiE0@a76h zpEa{iMl9W>?)zfOo?F99j~=w#fBW5T?_ndN+MMW9+rIAosA;o`-Mhqi-!mH@8#~fY zwxRIag~=HubNz2N3AoT^%(IH_*0qm38W8X~bl1Nhe{6jY?$+;EnmFmnv{!Eq#8~w% zTK(esQNJ~j&t-!G%L=DYcm0@e_4#(s^9h_sm#OC+92)K};9CSP8;J%8-8 z-;Z0g6;H72ZiLo0pELN*+^{BBR(aNP&pT7*dUs>5e$S0sJI4Mjum4EXzVy4%@H^M6 zgCiZv7oS=fxqi>QfMNXtV4qjnJ)qv+6Qvh73~!r!<=2-{Ll)Hg8+KZ9e_vbYM+P;z zi~xO;9zIXphb)L2|1l|ligg!#>%{VpsWwNuHIhVZmBahPOM(ryb{jdiu)sJOxyqMAl z;{JJUfK4k+z5UL!-wocl;nwv{-!;~YF}<` z#qIgpd??FejT$E)TL#*n?DpY8p}*@K>zwO?l3@>n6{y6j%pG`6{o@`}cD8(B7d=?|e<8oi4bpG@fgm zs{Y^=_*e0@+3-M-q3iXE(xNGU8;maMz5h?vHRC213+7(9V^zoHVy7i0O+UCr-CN)G zRz|N!vZ+tZ z=kFcM66}`bA858>K#kJW^WTTt?M&M_sLw607Wo_YpRX@#kuu4sZ}DwshaDfhXSEW~ zAGZ7Ek%@^-H$I%zv*{P_8E04L{Ha~z*T&Fn+d_}^749NQ{e_!HFG}3Kx!YL7k=?H} z)OdB-EsGm5I5*LL?)R|gcc)#SX1xFI$R+JZE9W=rI_KZ>li~Nhv(|PRoA|tod)=Y+ zmxrg!_d5S~=fJ$ZH&%J29PD`~WaiY8xU8iimj2O8?>9b>cQ1Rw?xEw&KUlVPZ05bC za@mV^!;|AH*MqLn=Jk&GzGo#TIx2gZR{5NHvS!rCDeks*C0yw}$Iio+o=9$8Ja@zG z?M3Y)wk){5+uQPc#IR*MG>?ljYc)HcaPg|S=yQ(#@`c3~3-s@Je$DN%=gy~`n5V1% z?KSm$bn5HAp#I&f#;r25QDk?2A~#)9cE;)c%m-F?V;erPde?RO=r_`2htLCDw~9VR z-ZPh*tUfUFMCF?1gJ=Ah=hZtGMnAeZbDe!MuT4TxP`hT;oYc1Fj0>>X-eSY+PRBJgDI4 zpOArd^2|Cf=zOU0{0YaVxNMSlIB0pSfyuf~htq41ZL?_BlC+ZJYqO0zx{vOEt?sE0 zjaOaBs8ieCGTP=^*wrS_8g}0OVoylwhOJvG$`S**7iN6z*YVkq9vZnpz5Uy^_DSv6 zUUvL;kz{t))j59d%5O7wUm5U1edkBUbH`?9%$Mw$bZo$%+J5D8-&{FWd|JPOxoLKC z-7ki{hTd~L8`x~?vp~bqSdBbk0y?!n@)b~V5>JqDrL&tqaJ$f#&nZLnk-Qu~|`uC3- z=8tE{|L^~6;kP%-|4Z|~X=&sA;rC;0d{S@iJo}wrn||wd+W8_E?L50iOPihkI{atX zacI-;s6+pR4*Q944T&~8?00N!{7fBo-gs))Z=yrr6#7K0rCChh^solP*4mvh;S)Defvb+!A? zuGiBRw?;bbUuvXX|4u*cd~Y3gLUizhbog`5LA(7?I@)VcOS?Y122Wdi*XXGC%xLZU z^L6NF=xA5tj@s==b@XpH9Xz{UR-6CqI#q2vyIxTn|44_Q{yN523mx`7bi|)sQ>?At zw>s(_t3&^$4*v~w_`|M+)K)LM?nfKXuC>y}v+HNH@i995WY>;q(|7aH&L``zKV64? zw;tN<+^MIXFVW%8GadE^>foR2h;yXXP2OaHt zrh{kKwQKXIkq-Z1x!}Zr>QY-rKl8I;hXYc4}?-3n#*fsmw?Az*y54+Don|^@~`>jT4*S`k0J!-SVt~b@j-_m7Yho9{3 zIBj;i>+rw3j`*L_;SakHLYw^r9s2AVGi~~rI`U+q4*$z^)H_9o|LodxZS}Hi#ubM1VImv)|AbE~c14m$i~ z*FS30|E5D{1% z(9w@Sb@($yM|{$C*kRYdYpa)CtEr852++qG^DA`N->1XRo6s*V)t!Xb?4BKMekQ>NLL0wDN4@NtX>Iz0b;PsfaP9iFboAGU zQ0@AS`F*K5CubWWkRbklxIWsM>6>%vF?i=VyN`{@$ri}V2EjxD!p^cCA(o_0SO>p(hzyYf;v3U@`K5HSGeYs6Tt<)@C6g3KD3j` zbh0m-g?5DduCBU~o&9hjCI~F|yg2qjUK)<=wZMWi^+jI2%ZS%=BYzt8MZSP;fU+h# z95jc;N4yGr0)f*Yyj&YW`oqwlZ{pJJs^L`&&PZ|KtHxJ58(3w`u8gO@5H|g z*WOe7bJ#`>WDxOb?r2Be8SBj;J0JM{3B*B#aa%+B&Yh{=D6CgZ`hL(%)?QU#i~zeI zmdRT1m+=Z;^#3)618&wpUULQ;^c2I&4I=w~Z_!UbvVVyG{ssPs&_0EMGjBrnS7Lip zv0AP>zi&6^a>SU=LB!v-VfkjwspxqOB1-hejnIy=2093*3wZfN^{S~K2N3_BpO?%z zNhr5ay5cI_lG~<8H7U-DLe1L z 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 I4SK%S4ah9Ho!O%(th>$1RD1L|&`U-d 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≧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%0D75|n>`$O{ai~-Dj#rD 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

RN-nAod!oV(Q0ltv(;hU7t<* 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π0xu-~pVM)~!RJ@xw7Ly-*tspv#3F}S`X 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 Ftq`8;%?NW;I09ed|nZsBEW*qvI1x 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 literal 0 HcmV?d00001 diff --git a/printHash.bat b/printHash.bat new file mode 100644 index 0000000..ef1cd9d --- /dev/null +++ b/printHash.bat @@ -0,0 +1,26 @@ +@echo off + +REM creates version.h with HEAD commit hash +REM params: $1=full path to output file (usually points version.h) + +setlocal enableextensions enabledelayedexpansion + +cd /d "%~dp0" + +break> %1 + + %1 + +where git +if "%errorlevel%" == "0" ( goto :havegit ) else ( goto :writeending ) + +:havegit +for /f %%v in ('git rev-parse --short HEAD') do set version=%%v +> %1 + +:writeending + +echo ^" >> %1 +echo const char* g_GIT_SHA1 = GIT_SHA1; >> %1 + +EXIT /B \ No newline at end of file diff --git a/printHash.sh b/printHash.sh new file mode 100755 index 0000000..213d935 --- /dev/null +++ b/printHash.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env sh +if [ -z "${1}" ] + then + printf "%s\n" "Input the path to the file for writing the commit hash to." + else + printf "%s" "#define GIT_SHA1 \"" > $1 + + if (command -v "git" >/dev/null) then + git rev-parse --short HEAD | tr -d '\n' >> $1 + fi + + printf "%s\n" "\"" >> $1 + printf "%s\n" "const char* g_GIT_SHA1 = GIT_SHA1;" >> $1 +fi diff --git a/res/images/logo.svg b/res/images/logo.svg new file mode 100644 index 0000000..9db8447 --- /dev/null +++ b/res/images/logo.svg @@ -0,0 +1,88 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/res/images/logo_1024.png b/res/images/logo_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..50ae869051b2b808a05f999863c722cfff527a9c GIT binary patch literal 24295 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 N^`5SNF6*2UngBw-Pj3JK literal 0 HcmV?d00001 diff --git a/res/images/logo_256.jpg b/res/images/logo_256.jpg new file mode 100644 index 0000000000000000000000000000000000000000..595d2c3b8d03cbaf72a213c73f84fa02f2b157cc GIT binary patch literal 18096 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 n0L%=}BjvI1_J5yy|65!A@6i0O?)NvR`` + $ +) + +target_compile_definitions(${EXECUTABLE} + PRIVATE + $,DEBUG,NDEBUG> + LIBRW + CMAKE_NO_AUTOLINK +) + +if(LIBRW_PLATFORM_D3D9) + target_compile_definitions(${EXECUTABLE} + PUBLIC + USE_D3D9 + ) +endif() + +target_compile_definitions(${EXECUTABLE} PRIVATE CMAKE_BUILD) +target_compile_definitions(${EXECUTABLE} PRIVATE USE_OUR_VERSIONING) + +if(${PROJECT}_AUDIO STREQUAL "OAL") + find_package(OpenAL REQUIRED) + if(TARGET OpenAL::OpenAL) + target_link_libraries(${EXECUTABLE} PRIVATE OpenAL::OpenAL) + else() + target_include_directories(${EXECUTABLE} PRIVATE ${OPENAL_INCLUDE_DIR}) + target_link_libraries(${EXECUTABLE} PRIVATE ${OPENAL_LIBRARY}) + target_compile_definitions(${EXECUTABLE} PRIVATE ${OPENAL_DEFINITIONS}) + endif() + target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_OAL) +elseif(${PROJECT}_AUDIO STREQUAL "MSS") + find_package(MilesSDK REQUIRED) + target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_MSS) + target_link_libraries(${EXECUTABLE} PRIVATE MilesSDK::MilesSDK) +endif() + +find_package(mpg123 REQUIRED) +target_link_libraries(${EXECUTABLE} PRIVATE + MPG123::libmpg123 +) +if(${PROJECT}_WITH_OPUS) + find_package(opusfile REQUIRED) + target_link_libraries(${EXECUTABLE} PRIVATE + opusfile::opusfile + ) + target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_OPUS) +endif() +if(${PROJECT}_WITH_LIBSNDFILE) + find_package(SndFile REQUIRED) + target_link_libraries(${EXECUTABLE} PRIVATE + SndFile::SndFile + ) + target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_OAL_USE_SNDFILE) +endif() + +target_compile_definitions(${EXECUTABLE} PRIVATE ) + +option(${PROJECT}_WITH_SANITIZERS "Use UB sanitizers (better crash log)" OFF) +option(${PROJECT}_WITH_ASAN "Use Address sanitizer (better crash log)" OFF) + +if(${PROJECT}_WITH_SANITIZERS) + target_compile_options(${EXECUTABLE} PUBLIC + -fsanitize=undefined,float-divide-by-zero,integer,implicit-conversion,implicit-integer-truncation,implicit-integer-arithmetic-value-change,local-bounds,nullability + -g3 -fno-omit-frame-pointer) + target_link_options(${EXECUTABLE} PUBLIC -fsanitize=undefined,float-divide-by-zero,integer,implicit-conversion,implicit-integer-truncation,implicit-integer-arithmetic-value-change,local-bounds,nullability) +endif() + +if(${PROJECT}_WITH_ASAN) + target_compile_options(${EXECUTABLE} PUBLIC -fsanitize=address -g3 -fno-omit-frame-pointer) + target_link_options(${EXECUTABLE} PUBLIC -fsanitize=address) +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + target_compile_options(${EXECUTABLE} + PRIVATE + "-Wall" + ) + if (NOT LIBRW_PLATFORM_PS2) + target_compile_options(${EXECUTABLE} + PRIVATE + -Wextra + -Wdouble-promotion + -Wpedantic + ) + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(${EXECUTABLE} + PUBLIC + /Zc:sizedDealloc- + ) +endif() + +if(NINTENDO_SWITCH) + set(${PROJECT}_C_CXX_EXTENSIONS ON) +else() + set(${PROJECT}_C_CXX_EXTENSIONS OFF) +endif() + +if(LIBRW_PLATFORM_GL3 AND LIBRW_GL3_GFXLIB STREQUAL "GLFW") + include(CheckSymbolExists) + + set(CMAKE_REQUIRED_LIBRARIES glfw) + set(CMAKE_REQUIRED_DEFINITIONS -DGLFW_EXPOSE_NATIVE_X11) + check_symbol_exists(glfwGetX11Display "GLFW/glfw3.h;GLFW/glfw3native.h" GLFW_HAS_X11) + unset(CMAKE_REQUIRED_DEFINITIONS) + unset(CMAKE_REQUIRED_LIBRARIES) + + if (GLFW_HAS_X11) + find_package(X11 REQUIRED) + target_link_libraries(${EXECUTABLE} PRIVATE X11::X11) + target_compile_definitions(${EXECUTABLE} PRIVATE GET_KEYBOARD_INPUT_FROM_X11) + endif (GLFW_HAS_X11) +endif() + +set_target_properties(${EXECUTABLE} + PROPERTIES + C_STANDARD 11 + C_EXTENSIONS ${${PROJECT}_C_CXX_EXTENSIONS} + C_STANDARD_REQUIRED ON + CXX_STANDARD 11 + CXX_EXTENSIONS ${${PROJECT}_C_CXX_EXTENSIONS} + CXX_STANDARD_REQUIRED ON +) + +if(${PROJECT}_INSTALL) + install( + TARGETS ${EXECUTABLE} + EXPORT ${EXECUTABLE}-targets + RUNTIME DESTINATION "." + ) + if(MSVC) + install(FILES $ DESTINATION "." OPTIONAL) + endif() +endif() + +re3_platform_target(${EXECUTABLE} INSTALL) diff --git a/src/animation/AnimBlendAssocGroup.cpp b/src/animation/AnimBlendAssocGroup.cpp new file mode 100644 index 0000000..295d6be --- /dev/null +++ b/src/animation/AnimBlendAssocGroup.cpp @@ -0,0 +1,174 @@ +#include "common.h" + +#if defined _WIN32 && !defined __MINGW32__ +#if defined __MWERKS__ +#include +#else +#include "ctype.h" +#endif +#else +#include +#endif + +#include "General.h" +#include "RwHelper.h" +#include "ModelInfo.h" +#include "AnimManager.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "AnimBlendAssocGroup.h" + +CAnimBlendAssocGroup::CAnimBlendAssocGroup(void) +{ + assocList = nil; + numAssociations = 0; +} + +CAnimBlendAssocGroup::~CAnimBlendAssocGroup(void) +{ + DestroyAssociations(); +} + +void +CAnimBlendAssocGroup::DestroyAssociations(void) +{ + if(assocList){ + delete[] assocList; + assocList = nil; + numAssociations = 0; + } +} + +CAnimBlendAssociation* +CAnimBlendAssocGroup::GetAnimation(uint32 id) +{ + return &assocList[id]; +} + +CAnimBlendAssociation* +CAnimBlendAssocGroup::GetAnimation(const char *name) +{ + int i; + for(i = 0; i < numAssociations; i++) + if(!CGeneral::faststricmp(assocList[i].hierarchy->name, name)) + return &assocList[i]; + return nil; +} + + +CAnimBlendAssociation* +CAnimBlendAssocGroup::CopyAnimation(uint32 id) +{ + CAnimBlendAssociation *anim = GetAnimation(id); + if(anim == nil) + return nil; + CAnimManager::UncompressAnimation(anim->hierarchy); + return new CAnimBlendAssociation(*anim); +} + +CAnimBlendAssociation* +CAnimBlendAssocGroup::CopyAnimation(const char *name) +{ + CAnimBlendAssociation *anim = GetAnimation(name); + if(anim == nil) + return nil; + CAnimManager::UncompressAnimation(anim->hierarchy); + return new CAnimBlendAssociation(*anim); +} + +bool +strcmpIgnoringDigits(const char *s1, const char *s2) +{ + char c1, c2; + + for(;;){ + c1 = *s1; + c2 = *s2; + if(c1) s1++; + if(c2) s2++; + if(c1 == '\0' && c2 == '\0') return true; +#ifndef ASCII_STRCMP + if(iswdigit(c1) && iswdigit(c2)) +#else + if(__ascii_iswdigit(c1) && __ascii_iswdigit(c2)) +#endif + continue; +#ifndef ASCII_STRCMP + c1 = toupper(c1); + c2 = toupper(c2); +#else + c1 = __ascii_toupper(c1); + c2 = __ascii_toupper(c2); +#endif + + if(c1 != c2) + return false; + } +} + +CBaseModelInfo* +GetModelFromName(const char *name) +{ + int i; + CBaseModelInfo *mi; + + for(i = 0; i < MODELINFOSIZE; i++){ + mi = CModelInfo::GetModelInfo(i); + if(mi && mi->GetRwObject() && RwObjectGetType(mi->GetRwObject()) == rpCLUMP && + strcmpIgnoringDigits(mi->GetModelName(), name)) + return mi; + } + return nil; +} + +void +CAnimBlendAssocGroup::CreateAssociations(const char *name) +{ + int i; + CAnimBlock *animBlock; + + if(assocList) + DestroyAssociations(); + + animBlock = CAnimManager::GetAnimationBlock(name); + assocList = new CAnimBlendAssociation[animBlock->numAnims]; + numAssociations = 0; + + for(i = 0; i < animBlock->numAnims; i++){ + CAnimBlendHierarchy *anim = CAnimManager::GetAnimation(animBlock->firstIndex + i); + CBaseModelInfo *model = GetModelFromName(anim->name); + assert(model); + printf("Associated anim %s with model %s\n", anim->name, model->GetModelName()); + RpClump *clump = (RpClump*)model->CreateInstance(); +#ifdef PED_SKIN + if(IsClumpSkinned(clump)) + RpClumpForAllAtomics(clump, AtomicRemoveAnimFromSkinCB, nil); +#endif + RpAnimBlendClumpInit(clump); + assocList[i].Init(clump, anim); + RpClumpDestroy(clump); + assocList[i].animId = i; + } + numAssociations = animBlock->numAnims; +} + +// Create associations from hierarchies for a given clump +void +CAnimBlendAssocGroup::CreateAssociations(const char *blockName, RpClump *clump, const char **animNames, int numAssocs) +{ + int i; + CAnimBlock *animBlock; + + if(assocList) + DestroyAssociations(); + + animBlock = CAnimManager::GetAnimationBlock(blockName); + assocList = new CAnimBlendAssociation[numAssocs]; + + numAssociations = 0; + for(i = 0; i < numAssocs; i++){ + assocList[i].Init(clump, CAnimManager::GetAnimation(animNames[i], animBlock)); + assocList[i].animId = i; + } + numAssociations = numAssocs; +} diff --git a/src/animation/AnimBlendAssocGroup.h b/src/animation/AnimBlendAssocGroup.h new file mode 100644 index 0000000..aa58b0d --- /dev/null +++ b/src/animation/AnimBlendAssocGroup.h @@ -0,0 +1,20 @@ +#pragma once + +class CAnimBlendAssociation; + +class CAnimBlendAssocGroup +{ +public: + CAnimBlendAssociation *assocList; + int32 numAssociations; + + CAnimBlendAssocGroup(void); + ~CAnimBlendAssocGroup(void); + void DestroyAssociations(void); + CAnimBlendAssociation *GetAnimation(uint32 id); + CAnimBlendAssociation *GetAnimation(const char *name); + CAnimBlendAssociation *CopyAnimation(uint32 id); + CAnimBlendAssociation *CopyAnimation(const char *name); + void CreateAssociations(const char *name); + void CreateAssociations(const char *blockName, RpClump *clump, const char **animNames, int numAssocs); +}; diff --git a/src/animation/AnimBlendAssociation.cpp b/src/animation/AnimBlendAssociation.cpp new file mode 100644 index 0000000..b03571b --- /dev/null +++ b/src/animation/AnimBlendAssociation.cpp @@ -0,0 +1,205 @@ +#include "common.h" + +#include "AnimBlendHierarchy.h" +#include "AnimBlendClumpData.h" +#include "RpAnimBlend.h" +#include "AnimManager.h" +#include "AnimBlendAssociation.h" +#include "MemoryMgr.h" + +CAnimBlendAssociation::CAnimBlendAssociation(void) +{ + nodes = nil; + blendAmount = 1.0f; + blendDelta = 0.0f; + currentTime = 0.0f; + speed = 1.0f; + timeStep = 0.0f; + animId = -1; + flags = 0; + callbackType = CB_NONE; + link.Init(); +} + +CAnimBlendAssociation::CAnimBlendAssociation(CAnimBlendAssociation &other) +{ + nodes = nil; + blendAmount = 1.0f; + blendDelta = 0.0f; + currentTime = 0.0f; + speed = 1.0f; + timeStep = 0.0f; + callbackType = CB_NONE; + link.Init(); + Init(other); +} + +CAnimBlendAssociation::~CAnimBlendAssociation(void) +{ + FreeAnimBlendNodeArray(); + link.Remove(); +} + + +void +CAnimBlendAssociation::AllocateAnimBlendNodeArray(int n) +{ + int i; + + nodes = (CAnimBlendNode*)RwMallocAlign(n*sizeof(CAnimBlendNode), 64); + for(i = 0; i < n; i++) + nodes[i].Init(); +} + +void +CAnimBlendAssociation::FreeAnimBlendNodeArray(void) +{ + assert(nodes != nil); + RwFreeAlign(nodes); +} + +void +CAnimBlendAssociation::Init(RpClump *clump, CAnimBlendHierarchy *hier) +{ + int i; + AnimBlendFrameData *frame; + + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + numNodes = clumpData->numFrames; + AllocateAnimBlendNodeArray(numNodes); + for(i = 0; i < numNodes; i++) + nodes[i].association = this; + hierarchy = hier; + + // Init every node from a sequence and a Clump frame + // NB: This is where the order of nodes is defined + for(i = 0; i < hier->numSequences; i++){ + CAnimBlendSequence *seq = &hier->sequences[i]; + frame = RpAnimBlendClumpFindFrame(clump, seq->name); + if(frame && seq->numFrames > 0) + nodes[frame - clumpData->frames].sequence = seq; + } +} + +void +CAnimBlendAssociation::Init(CAnimBlendAssociation &assoc) +{ + int i; + + hierarchy = assoc.hierarchy; + numNodes = assoc.numNodes; + flags = assoc.flags; + animId = assoc.animId; + AllocateAnimBlendNodeArray(numNodes); + for(i = 0; i < numNodes; i++){ + nodes[i] = assoc.nodes[i]; + nodes[i].association = this; + } +} + +void +CAnimBlendAssociation::SetBlend(float amount, float delta) +{ + blendAmount = amount; + blendDelta = delta; +} + +void +CAnimBlendAssociation::SetFinishCallback(void (*cb)(CAnimBlendAssociation*, void*), void *arg) +{ + callbackType = CB_FINISH; + callback = cb; + callbackArg = arg; +} + +void +CAnimBlendAssociation::SetDeleteCallback(void (*cb)(CAnimBlendAssociation*, void*), void *arg) +{ + callbackType = CB_DELETE; + callback = cb; + callbackArg = arg; +} + +void +CAnimBlendAssociation::SetCurrentTime(float time) +{ + int i; + + for(currentTime = time; currentTime >= hierarchy->totalLength; currentTime -= hierarchy->totalLength) + if(!IsRepeating()) + return; + CAnimManager::UncompressAnimation(hierarchy); + for(i = 0; i < numNodes; i++) + if(nodes[i].sequence) + nodes[i].FindKeyFrame(currentTime); +} + +void +CAnimBlendAssociation::SyncAnimation(CAnimBlendAssociation *other) +{ + SetCurrentTime(other->currentTime/other->hierarchy->totalLength * hierarchy->totalLength); +} + +void +CAnimBlendAssociation::Start(float time) +{ + flags |= ASSOC_RUNNING; + SetCurrentTime(time); +} + +bool +CAnimBlendAssociation::UpdateTime(float timeDelta, float relSpeed) +{ + if(!IsRunning()) + return true; + + timeStep = (flags & ASSOC_MOVEMENT ? relSpeed*hierarchy->totalLength : speed) * timeDelta; + currentTime += timeStep; + + if(currentTime >= hierarchy->totalLength){ + // Ran past end + + if(IsRepeating()) + currentTime -= hierarchy->totalLength; + else{ + currentTime = hierarchy->totalLength; + flags &= ~ASSOC_RUNNING; + if(flags & ASSOC_FADEOUTWHENDONE){ + flags |= ASSOC_DELETEFADEDOUT; + blendDelta = -4.0f; + } + if(callbackType == CB_FINISH){ + callbackType = CB_NONE; + callback(this, callbackArg); + } + } + } + return true; +} + +// return whether we still exist after this function +bool +CAnimBlendAssociation::UpdateBlend(float timeDelta) +{ + blendAmount += blendDelta * timeDelta; + + if(blendAmount <= 0.0f && blendDelta < 0.0f){ + // We're faded out and are not fading in + blendAmount = 0.0f; + blendDelta = Max(0.0f, blendDelta); + if(flags & ASSOC_DELETEFADEDOUT){ + if(callbackType == CB_FINISH || callbackType == CB_DELETE) + callback(this, callbackArg); + delete this; + return false; + } + } + + if(blendAmount > 1.0f){ + // Maximally faded in, clamp values + blendAmount = 1.0f; + blendDelta = Min(0.0f, blendDelta); + } + + return true; +} diff --git a/src/animation/AnimBlendAssociation.h b/src/animation/AnimBlendAssociation.h new file mode 100644 index 0000000..45720b6 --- /dev/null +++ b/src/animation/AnimBlendAssociation.h @@ -0,0 +1,91 @@ +#pragma once + +#include "AnimBlendList.h" +#include "AnimBlendNode.h" +#include "AnimBlendHierarchy.h" + +enum { + ASSOC_RUNNING = 1, + ASSOC_REPEAT = 2, + ASSOC_DELETEFADEDOUT = 4, + ASSOC_FADEOUTWHENDONE = 8, + ASSOC_PARTIAL = 0x10, + ASSOC_MOVEMENT = 0x20, // ??? + ASSOC_HAS_TRANSLATION = 0x40, + ASSOC_WALK = 0x80, // for CPed::PlayFootSteps(void) + ASSOC_IDLE = 0x100, // only used by xpress scratch, see CPed::Chat(void) + ASSOC_NOWALK = 0x200, // see CPed::PlayFootSteps(void) + ASSOC_BLOCK = 0x400, // unused in assoc description, blocks other anims from being played + ASSOC_FRONTAL = 0x800, // anims that we fall to front + ASSOC_HAS_X_TRANSLATION = 0x1000, // for 2d velocity extraction +}; + +// Anim hierarchy associated with a clump +// Holds the interpolated state of all nodes. +// Also used as template for other clumps. +class CAnimBlendAssociation +{ +public: + enum { + // callbackType + CB_NONE, + CB_FINISH, + CB_DELETE + }; + + CAnimBlendLink link; + + int32 numNodes; // taken from CAnimBlendClumpData::numFrames + // NB: Order of these depends on order of nodes in Clump this was built from + CAnimBlendNode *nodes; + CAnimBlendHierarchy *hierarchy; + float blendAmount; + float blendDelta; // how much blendAmount changes over time + float currentTime; + float speed; + float timeStep; + int32 animId; + int32 flags; + int32 callbackType; + void (*callback)(CAnimBlendAssociation*, void*); + void *callbackArg; + + bool IsRunning(void) { return !!(flags & ASSOC_RUNNING); } + bool IsRepeating(void) { return !!(flags & ASSOC_REPEAT); } + bool IsPartial(void) { return !!(flags & ASSOC_PARTIAL); } + bool IsMovement(void) { return !!(flags & ASSOC_MOVEMENT); } + bool HasTranslation(void) { return !!(flags & ASSOC_HAS_TRANSLATION); } + bool HasXTranslation(void) { return !!(flags & ASSOC_HAS_X_TRANSLATION); } + + float GetBlendAmount(float weight) { return IsPartial() ? blendAmount : blendAmount*weight; } + CAnimBlendNode *GetNode(int i) { return &nodes[i]; } + + CAnimBlendAssociation(void); + CAnimBlendAssociation(CAnimBlendAssociation &other); +#ifndef FIX_BUGS + virtual +#endif + ~CAnimBlendAssociation(void); + void AllocateAnimBlendNodeArray(int n); + void FreeAnimBlendNodeArray(void); + void Init(RpClump *clump, CAnimBlendHierarchy *hier); + void Init(CAnimBlendAssociation &assoc); + void SetBlend(float amount, float delta); + void SetFinishCallback(void (*callback)(CAnimBlendAssociation*, void*), void *arg); + void SetDeleteCallback(void (*callback)(CAnimBlendAssociation*, void*), void *arg); + void SetCurrentTime(float time); + void SyncAnimation(CAnimBlendAssociation *other); + void Start(float time); + bool UpdateTime(float timeDelta, float relSpeed); + bool UpdateBlend(float timeDelta); + + void SetRun(void) { flags |= ASSOC_RUNNING; } + + inline float GetTimeLeft() { return hierarchy->totalLength - currentTime; } + + static CAnimBlendAssociation *FromLink(CAnimBlendLink *l) { + return (CAnimBlendAssociation*)((uint8*)l - offsetof(CAnimBlendAssociation, link)); + } +}; + +VALIDATE_SIZE(CAnimBlendAssociation, 0x40); diff --git a/src/animation/AnimBlendClumpData.cpp b/src/animation/AnimBlendClumpData.cpp new file mode 100644 index 0000000..b333a44 --- /dev/null +++ b/src/animation/AnimBlendClumpData.cpp @@ -0,0 +1,36 @@ +#include "common.h" + +#include "AnimBlendClumpData.h" +#include "MemoryMgr.h" + +CAnimBlendClumpData::CAnimBlendClumpData(void) +{ + numFrames = 0; + velocity2d = nil; + frames = nil; + link.Init(); +} + +CAnimBlendClumpData::~CAnimBlendClumpData(void) +{ + link.Remove(); + if(frames) + RwFreeAlign(frames); +} + +void +CAnimBlendClumpData::SetNumberOfFrames(int n) +{ + if(frames) + RwFreeAlign(frames); + numFrames = n; + frames = (AnimBlendFrameData*)RwMallocAlign(numFrames * sizeof(AnimBlendFrameData), 64); +} + +void +CAnimBlendClumpData::ForAllFrames(void (*cb)(AnimBlendFrameData*, void*), void *arg) +{ + int i; + for(i = 0; i < numFrames; i++) + cb(&frames[i], arg); +} diff --git a/src/animation/AnimBlendClumpData.h b/src/animation/AnimBlendClumpData.h new file mode 100644 index 0000000..acfd006 --- /dev/null +++ b/src/animation/AnimBlendClumpData.h @@ -0,0 +1,57 @@ +#pragma once + +#include "AnimBlendList.h" + + +struct AnimBlendFrameData +{ + enum { + IGNORE_ROTATION = 2, + IGNORE_TRANSLATION = 4, + VELOCITY_EXTRACTION = 8, + VELOCITY_EXTRACTION_3D = 0x10, + }; + + uint8 flag; + RwV3d resetPos; +#ifdef PED_SKIN + union { + RwFrame *frame; + RpHAnimStdInterpFrame *hanimFrame; + }; + int32 nodeID; +#else + RwFrame *frame; +#endif +}; +#ifndef PED_SKIN +VALIDATE_SIZE(AnimBlendFrameData, 0x14); +#endif + + +class CAnimBlendClumpData +{ +public: + CAnimBlendLink link; + int32 numFrames; +#ifdef PED_SKIN + int32 modelNumber; // doesn't seem to be used +#endif + union { + CVector2D *velocity2d; + CVector *velocity3d; + }; + // order of frames is determined by RW hierarchy + AnimBlendFrameData *frames; + + CAnimBlendClumpData(void); + ~CAnimBlendClumpData(void); + void SetNumberOfFrames(int n); +#ifdef PED_SKIN + void SetNumberOfBones(int n) { SetNumberOfFrames(n); } +#endif + void ForAllFrames(void (*cb)(AnimBlendFrameData*, void*), void *arg); +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CAnimBlendClumpData, 0x14); +#endif diff --git a/src/animation/AnimBlendHierarchy.cpp b/src/animation/AnimBlendHierarchy.cpp new file mode 100644 index 0000000..ea66999 --- /dev/null +++ b/src/animation/AnimBlendHierarchy.cpp @@ -0,0 +1,94 @@ +#include "common.h" + +#include "AnimBlendSequence.h" +#include "AnimBlendHierarchy.h" + +CAnimBlendHierarchy::CAnimBlendHierarchy(void) +{ + sequences = nil; + numSequences = 0; + compressed = 0; + totalLength = 0.0f; + linkPtr = nil; +} + +void +CAnimBlendHierarchy::Shutdown(void) +{ + RemoveAnimSequences(); + compressed = 0; + linkPtr = nil; +} + +void +CAnimBlendHierarchy::SetName(char *name) +{ + strncpy(this->name, name, 24); +} + +void +CAnimBlendHierarchy::CalcTotalTime(void) +{ + int i, j; + totalLength = 0.0f; + + for(i = 0; i < numSequences; i++){ + float seqTime = 0.0f; + for(j = 0; j < sequences[i].numFrames; j++) + seqTime += sequences[i].GetKeyFrame(j)->deltaTime; + totalLength = Max(totalLength, seqTime); + } +} + +void +CAnimBlendHierarchy::RemoveQuaternionFlips(void) +{ + int i; + + for(i = 0; i < numSequences; i++) + sequences[i].RemoveQuaternionFlips(); +} + +void +CAnimBlendHierarchy::RemoveAnimSequences(void) +{ + delete[] sequences; + numSequences = 0; +} + +void +CAnimBlendHierarchy::Uncompress(void) +{ +#ifdef ANIM_COMPRESSION + int i; + assert(compressed); + for(i = 0; i < numSequences; i++) + sequences[i].Uncompress(); +#endif + if(totalLength == 0.0f) + CalcTotalTime(); + compressed = 0; +} + +void +CAnimBlendHierarchy::RemoveUncompressedData(void) +{ +#ifdef ANIM_COMPRESSION + int i; + assert(!compressed); + for(i = 0; i < numSequences; i++) + sequences[i].RemoveUncompressedData(); +#endif + compressed = 1; +} + +#ifdef USE_CUSTOM_ALLOCATOR +void +CAnimBlendHierarchy::MoveMemory(bool onlyone) +{ + int i; + for(i = 0; i < numSequences; i++) + if(sequences[i].MoveMemory() && onlyone) + return; +} +#endif diff --git a/src/animation/AnimBlendHierarchy.h b/src/animation/AnimBlendHierarchy.h new file mode 100644 index 0000000..40d2731 --- /dev/null +++ b/src/animation/AnimBlendHierarchy.h @@ -0,0 +1,33 @@ +#pragma once + +#include "templates.h" + +#ifdef MoveMemory +#undef MoveMemory // windows shit +#endif + +class CAnimBlendSequence; + +// A collection of sequences +class CAnimBlendHierarchy +{ +public: + char name[24]; + CAnimBlendSequence *sequences; + int16 numSequences; + int16 compressed; + float totalLength; + CLink *linkPtr; + + CAnimBlendHierarchy(void); + void Shutdown(void); + void SetName(char *name); + void CalcTotalTime(void); + void RemoveQuaternionFlips(void); + void RemoveAnimSequences(void); + void Uncompress(void); + void RemoveUncompressedData(void); + void MoveMemory(bool onlyone = false); +}; + +VALIDATE_SIZE(CAnimBlendHierarchy, 0x28); \ No newline at end of file diff --git a/src/animation/AnimBlendList.h b/src/animation/AnimBlendList.h new file mode 100644 index 0000000..018b598 --- /dev/null +++ b/src/animation/AnimBlendList.h @@ -0,0 +1,28 @@ +#pragma once + +// name made up +class CAnimBlendLink +{ +public: + CAnimBlendLink *next; + CAnimBlendLink *prev; + + void Init(void){ + next = nil; + prev = nil; + } + void Prepend(CAnimBlendLink *link){ + if(next) + next->prev = link; + link->next = next; + link->prev = this; + next = link; + } + void Remove(void){ + if(prev) + prev->next = next; + if(next) + next->prev = prev; + Init(); + } +}; diff --git a/src/animation/AnimBlendNode.cpp b/src/animation/AnimBlendNode.cpp new file mode 100644 index 0000000..df6cd1d --- /dev/null +++ b/src/animation/AnimBlendNode.cpp @@ -0,0 +1,160 @@ +#include "common.h" + +#include "AnimBlendAssociation.h" +#include "AnimBlendNode.h" + +void +CAnimBlendNode::Init(void) +{ + frameA = -1; + frameB = -1; + remainingTime = 0.0f; + sequence = nil; + association = nil; +} + +bool +CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight) +{ + bool looped = false; + + trans = CVector(0.0f, 0.0f, 0.0f); + rot = CQuaternion(0.0f, 0.0f, 0.0f, 0.0f); + + if(association->IsRunning()){ + remainingTime -= association->timeStep; + if(remainingTime <= 0.0f) + looped = NextKeyFrame(); + } + + float blend = association->GetBlendAmount(weight); + if(blend > 0.0f){ + KeyFrameTrans *kfA = (KeyFrameTrans*)sequence->GetKeyFrame(frameA); + KeyFrameTrans *kfB = (KeyFrameTrans*)sequence->GetKeyFrame(frameB); + float t = kfA->deltaTime == 0.0f ? 0.0f : (kfA->deltaTime - remainingTime)/kfA->deltaTime; + if(sequence->type & CAnimBlendSequence::KF_TRANS){ + trans = kfB->translation + t*(kfA->translation - kfB->translation); + trans *= blend; + } + if(sequence->type & CAnimBlendSequence::KF_ROT){ + rot.Slerp(kfB->rotation, kfA->rotation, theta, invSin, t); + rot *= blend; + } + } + + return looped; +} + +bool +CAnimBlendNode::NextKeyFrame(void) +{ + bool looped; + + if(sequence->numFrames <= 1) + return false; + + looped = false; + frameB = frameA; + + // Advance as long as we have to + while(remainingTime <= 0.0f){ + frameA++; + + if(frameA >= sequence->numFrames){ + // reached end of animation + if(!association->IsRepeating()){ + frameA--; + remainingTime = 0.0f; + return false; + } + looped = true; + frameA = 0; + } + + remainingTime += sequence->GetKeyFrame(frameA)->deltaTime; + } + + frameB = frameA - 1; + if(frameB < 0) + frameB += sequence->numFrames; + + CalcDeltas(); + return looped; +} + +// Set animation to time t +bool +CAnimBlendNode::FindKeyFrame(float t) +{ + if(sequence->numFrames < 1) + return false; + + frameA = 0; + frameB = frameA; + + if(sequence->numFrames >= 2){ + frameA++; + + // advance until t is between frameB and frameA + while(t > sequence->GetKeyFrame(frameA)->deltaTime){ + t -= sequence->GetKeyFrame(frameA)->deltaTime; + frameB = frameA++; + if(frameA >= sequence->numFrames){ + // reached end of animation + if(!association->IsRepeating()) + return false; + frameA = 0; + frameB = 0; + } + } + + remainingTime = sequence->GetKeyFrame(frameA)->deltaTime - t; + } + + CalcDeltas(); + return true; +} + +void +CAnimBlendNode::CalcDeltas(void) +{ + if((sequence->type & CAnimBlendSequence::KF_ROT) == 0) + return; + KeyFrame *kfA = sequence->GetKeyFrame(frameA); + KeyFrame *kfB = sequence->GetKeyFrame(frameB); + float cos = DotProduct(kfA->rotation, kfB->rotation); + if(cos > 1.0f) + cos = 1.0f; + theta = Acos(cos); + invSin = theta == 0.0f ? 0.0f : 1.0f/Sin(theta); +} + +void +CAnimBlendNode::GetCurrentTranslation(CVector &trans, float weight) +{ + trans = CVector(0.0f, 0.0f, 0.0f); + + float blend = association->GetBlendAmount(weight); + if(blend > 0.0f){ + KeyFrameTrans *kfA = (KeyFrameTrans*)sequence->GetKeyFrame(frameA); + KeyFrameTrans *kfB = (KeyFrameTrans*)sequence->GetKeyFrame(frameB); + float t = (kfA->deltaTime - remainingTime)/kfA->deltaTime; + if(sequence->type & CAnimBlendSequence::KF_TRANS){ + trans = kfB->translation + t*(kfA->translation - kfB->translation); + trans *= blend; + } + } +} + +void +CAnimBlendNode::GetEndTranslation(CVector &trans, float weight) +{ + trans = CVector(0.0f, 0.0f, 0.0f); + + float blend = association->GetBlendAmount(weight); + if(blend > 0.0f){ + KeyFrameTrans *kf = (KeyFrameTrans*)sequence->GetKeyFrame(sequence->numFrames-1); + if(sequence->type & CAnimBlendSequence::KF_TRANS) + trans = kf->translation * blend; + } +} diff --git a/src/animation/AnimBlendNode.h b/src/animation/AnimBlendNode.h new file mode 100644 index 0000000..89924d6 --- /dev/null +++ b/src/animation/AnimBlendNode.h @@ -0,0 +1,31 @@ +#pragma once + +#include "AnimBlendSequence.h" + +class CAnimBlendAssociation; + +// The interpolated state between two key frames in a sequence +class CAnimBlendNode +{ +public: + // for slerp + float theta; // angle between quaternions + float invSin; // 1/Sin(theta) + // indices into array in sequence + int32 frameA; // next key frame + int32 frameB; // previous key frame + float remainingTime; // time until frames have to advance + CAnimBlendSequence *sequence; + CAnimBlendAssociation *association; + + void Init(void); + bool Update(CVector &trans, CQuaternion &rot, float weight); + bool NextKeyFrame(void); + bool FindKeyFrame(float t); + void CalcDeltas(void); + void GetCurrentTranslation(CVector &trans, float weight); + void GetEndTranslation(CVector &trans, float weight); +}; + + +VALIDATE_SIZE(CAnimBlendNode, 0x1C); diff --git a/src/animation/AnimBlendSequence.cpp b/src/animation/AnimBlendSequence.cpp new file mode 100644 index 0000000..2ae150c --- /dev/null +++ b/src/animation/AnimBlendSequence.cpp @@ -0,0 +1,200 @@ +#include "common.h" + +#include "AnimBlendSequence.h" +#include "MemoryHeap.h" + +CAnimBlendSequence::CAnimBlendSequence(void) +{ + type = 0; + numFrames = 0; + keyFrames = nil; + keyFramesCompressed = nil; +#ifdef PED_SKIN + boneTag = -1; +#endif +} + +CAnimBlendSequence::~CAnimBlendSequence(void) +{ + if(keyFrames) + RwFree(keyFrames); + if(keyFramesCompressed) + RwFree(keyFramesCompressed); +} + +void +CAnimBlendSequence::SetName(char *name) +{ + strncpy(this->name, name, 24); +} + +void +CAnimBlendSequence::SetNumFrames(int numFrames, bool translation) +{ + int sz; + + if(translation){ + sz = sizeof(KeyFrameTrans); + type |= KF_ROT | KF_TRANS; + }else{ + sz = sizeof(KeyFrame); + type |= KF_ROT; + } + keyFrames = RwMalloc(sz * numFrames); + this->numFrames = numFrames; +} + +void +CAnimBlendSequence::RemoveQuaternionFlips(void) +{ + int i; + CQuaternion last; + KeyFrame *frame; + + if(numFrames < 2) + return; + + frame = GetKeyFrame(0); + last = frame->rotation; + for(i = 1; i < numFrames; i++){ + frame = GetKeyFrame(i); + if(DotProduct(last, frame->rotation) < 0.0f) + frame->rotation = -frame->rotation; + last = frame->rotation; + } +} + +void +CAnimBlendSequence::Uncompress(void) +{ + int i; + + if(numFrames == 0) + return; + + PUSH_MEMID(MEMID_ANIMATION); + + float rotScale = 1.0f/4096.0f; + float timeScale = 1.0f/60.0f; + float transScale = 1.0f/128.0f; + if(type & KF_TRANS){ + void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameTrans)); + KeyFrameTransCompressed *ckf = (KeyFrameTransCompressed*)keyFramesCompressed; + KeyFrameTrans *kf = (KeyFrameTrans*)newKfs; + for(i = 0; i < numFrames; i++){ + kf->rotation.x = ckf->rot[0]*rotScale; + kf->rotation.y = ckf->rot[1]*rotScale; + kf->rotation.z = ckf->rot[2]*rotScale; + kf->rotation.w = ckf->rot[3]*rotScale; + kf->deltaTime = ckf->deltaTime*timeScale; + kf->translation.x = ckf->trans[0]*transScale; + kf->translation.y = ckf->trans[1]*transScale; + kf->translation.z = ckf->trans[2]*transScale; + kf++; + ckf++; + } + keyFrames = newKfs; + }else{ + void *newKfs = RwMalloc(numFrames * sizeof(KeyFrame)); + KeyFrameCompressed *ckf = (KeyFrameCompressed*)keyFramesCompressed; + KeyFrame *kf = (KeyFrame*)newKfs; + for(i = 0; i < numFrames; i++){ + kf->rotation.x = ckf->rot[0]*rotScale; + kf->rotation.y = ckf->rot[1]*rotScale; + kf->rotation.z = ckf->rot[2]*rotScale; + kf->rotation.w = ckf->rot[3]*rotScale; + kf->deltaTime = ckf->deltaTime*timeScale; + kf++; + ckf++; + } + keyFrames = newKfs; + } + REGISTER_MEMPTR(&keyFrames); + + RwFree(keyFramesCompressed); + keyFramesCompressed = nil; + + POP_MEMID(); +} + +void +CAnimBlendSequence::CompressKeyframes(void) +{ + int i; + + if(numFrames == 0) + return; + + PUSH_MEMID(MEMID_ANIMATION); + + float rotScale = 4096.0f; + float timeScale = 60.0f; + float transScale = 128.0f; + if(type & KF_TRANS){ + void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameTransCompressed)); + KeyFrameTransCompressed *ckf = (KeyFrameTransCompressed*)newKfs; + KeyFrameTrans *kf = (KeyFrameTrans*)keyFrames; + for(i = 0; i < numFrames; i++){ + ckf->rot[0] = kf->rotation.x*rotScale; + ckf->rot[1] = kf->rotation.y*rotScale; + ckf->rot[2] = kf->rotation.z*rotScale; + ckf->rot[3] = kf->rotation.w*rotScale; + ckf->deltaTime = kf->deltaTime*timeScale + 0.5f; + ckf->trans[0] = kf->translation.x*transScale; + ckf->trans[1] = kf->translation.y*transScale; + ckf->trans[2] = kf->translation.z*transScale; + kf++; + ckf++; + } + keyFramesCompressed = newKfs; + }else{ + void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameCompressed)); + KeyFrameCompressed *ckf = (KeyFrameCompressed*)newKfs; + KeyFrame *kf = (KeyFrame*)keyFrames; + for(i = 0; i < numFrames; i++){ + ckf->rot[0] = kf->rotation.x*rotScale; + ckf->rot[1] = kf->rotation.y*rotScale; + ckf->rot[2] = kf->rotation.z*rotScale; + ckf->rot[3] = kf->rotation.w*rotScale; + ckf->deltaTime = kf->deltaTime*timeScale + 0.5f; + kf++; + ckf++; + } + keyFramesCompressed = newKfs; + } + REGISTER_MEMPTR(&keyFramesCompressed); + + POP_MEMID(); +} + +void +CAnimBlendSequence::RemoveUncompressedData(void) +{ + if(numFrames == 0) + return; + CompressKeyframes(); + RwFree(keyFrames); + keyFrames = nil; +} + +#ifdef USE_CUSTOM_ALLOCATOR +bool +CAnimBlendSequence::MoveMemory(void) +{ + if(keyFrames){ + void *newaddr = gMainHeap.MoveMemory(keyFrames); + if(newaddr != keyFrames){ + keyFrames = newaddr; + return true; + } + }else if(keyFramesCompressed){ + void *newaddr = gMainHeap.MoveMemory(keyFramesCompressed); + if(newaddr != keyFramesCompressed){ + keyFramesCompressed = newaddr; + return true; + } + } + return false; +} +#endif + diff --git a/src/animation/AnimBlendSequence.h b/src/animation/AnimBlendSequence.h new file mode 100644 index 0000000..c6e70f2 --- /dev/null +++ b/src/animation/AnimBlendSequence.h @@ -0,0 +1,68 @@ +#pragma once + +#include "Quaternion.h" + +#ifdef MoveMemory +#undef MoveMemory // windows shit +#endif + +// TODO: put them somewhere else? +struct KeyFrame { + CQuaternion rotation; + float deltaTime; // relative to previous key frame +}; + +struct KeyFrameTrans : KeyFrame { + CVector translation; +}; + +struct KeyFrameCompressed { + int16 rot[4]; // 4096 + int16 deltaTime; // 60 +}; + +struct KeyFrameTransCompressed : KeyFrameCompressed { + int16 trans[3]; // 128 +}; + + +// The sequence of key frames of one animated node +class CAnimBlendSequence +{ +public: + enum { + KF_ROT = 1, + KF_TRANS = 2 + }; + int32 type; + char name[24]; + int32 numFrames; +#ifdef PED_SKIN + int16 boneTag; +#endif + void *keyFrames; + void *keyFramesCompressed; + + CAnimBlendSequence(void); + virtual ~CAnimBlendSequence(void); + void SetName(char *name); + void SetNumFrames(int numFrames, bool translation); + void RemoveQuaternionFlips(void); + KeyFrame *GetKeyFrame(int n) { + return type & KF_TRANS ? + &((KeyFrameTrans*)keyFrames)[n] : + &((KeyFrame*)keyFrames)[n]; + } + bool HasTranslation(void) { return !!(type & KF_TRANS); } + void Uncompress(void); + void CompressKeyframes(void); + void RemoveUncompressedData(void); + bool MoveMemory(void); + +#ifdef PED_SKIN + void SetBoneTag(int tag) { boneTag = tag; } +#endif +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CAnimBlendSequence, 0x2C); +#endif diff --git a/src/animation/AnimManager.cpp b/src/animation/AnimManager.cpp new file mode 100644 index 0000000..c66997c --- /dev/null +++ b/src/animation/AnimManager.cpp @@ -0,0 +1,941 @@ +#include "common.h" + +#include "General.h" +#include "RwHelper.h" +#include "ModelInfo.h" +#include "ModelIndices.h" +#include "FileMgr.h" +#include "RpAnimBlend.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendAssociation.h" +#include "AnimBlendAssocGroup.h" +#include "AnimManager.h" + +CAnimBlock CAnimManager::ms_aAnimBlocks[NUMANIMBLOCKS]; +CAnimBlendHierarchy CAnimManager::ms_aAnimations[NUMANIMATIONS]; +int32 CAnimManager::ms_numAnimBlocks; +int32 CAnimManager::ms_numAnimations; +CAnimBlendAssocGroup *CAnimManager::ms_aAnimAssocGroups; +CLinkList CAnimManager::ms_animCache; + +AnimAssocDesc aStdAnimDescs[] = { + { ANIM_STD_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK }, + { ANIM_STD_RUN, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK }, + { ANIM_STD_RUNFAST, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK }, + { ANIM_STD_IDLE, ASSOC_REPEAT }, + { ANIM_STD_STARTWALK, ASSOC_HAS_TRANSLATION }, + { ANIM_STD_RUNSTOP1, ASSOC_DELETEFADEDOUT | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_RUNSTOP2, ASSOC_DELETEFADEDOUT | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_IDLE_CAM, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_IDLE_HBHB, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_IDLE_TIRED, ASSOC_REPEAT }, + { ANIM_STD_IDLE_BIGGUN, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_CHAT, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_HAILTAXI, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_KO_FRONT, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_KO_LEFT, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_KO_BACK, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_KO_RIGHT, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_KO_SHOT_FACE, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_KO_SHOT_STOMACH, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_KO_SHOT_ARM_L, ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_KO_SHOT_ARM_R, ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_KO_SHOT_LEG_L, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_KO_SHOT_LEG_R, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_SPINFORWARD_LEFT, ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_SPINFORWARD_RIGHT, ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_HIGHIMPACT_FRONT, ASSOC_PARTIAL }, + { ANIM_STD_HIGHIMPACT_LEFT, ASSOC_PARTIAL }, + { ANIM_STD_HIGHIMPACT_BACK, ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_HIGHIMPACT_RIGHT, ASSOC_PARTIAL }, + { ANIM_STD_HITBYGUN_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_HITBYGUN_LEFT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_HITBYGUN_BACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_HITBYGUN_RIGHT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_HIT_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_LEFT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_HIT_BACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_RIGHT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_HIT_FLOOR, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, +#if GTA_VERSION <= GTA3_PS2_160 + { ANIM_STD_HIT_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, +#endif + { ANIM_STD_HIT_BODYBLOW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_CHEST, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_HEAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_WALK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_WALL, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_FLOOR_FRONT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_HIT_BEHIND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_KICKGROUND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_BAT_H, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_BAT_V, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_HGUN_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_WEAPON_AK_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_PUMP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_SNIPER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_THROW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_THROW_UNDER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_START_THROW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_DETONATE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_HGUN_RELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_AK_RELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, +#ifdef PC_PLAYER_CONTROLS + // maybe wrong define, but unused anyway + { ANIM_FPS_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_BAT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_UZI, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_PUMP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_AK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_M16, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_ROCKET, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, +#endif + { ANIM_STD_FIGHT_IDLE, ASSOC_REPEAT }, + { ANIM_STD_FIGHT_2IDLE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_SHUFFLE_F, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_FIGHT_BODYBLOW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_HEAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_KICK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_KNEE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_LHOOK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_ROUNDHOUSE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_FIGHT_LONGKICK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_PARTIAL_PUNCH, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_JACKEDCAR_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_JACKEDCAR_LO_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_JACKEDCAR_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_JACKEDCAR_LO_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_QUICKJACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_QUICKJACKED, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_ALIGN_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_ALIGNHI_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_OPEN_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CARDOOR_LOCKED_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_PULL_OUT_PED_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_PULL_OUT_PED_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_GET_IN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_GET_IN_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_GETOUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_GETOUT_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_ALIGN_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_ALIGNHI_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_OPEN_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CARDOOR_LOCKED_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_PULL_OUT_PED_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_PULL_OUT_PED_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_GET_IN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_GET_IN_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_SHUFFLE_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_SHUFFLE_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_SIT, ASSOC_DELETEFADEDOUT }, + { ANIM_STD_CAR_SIT_LO, ASSOC_DELETEFADEDOUT }, + { ANIM_STD_CAR_SIT_P, ASSOC_DELETEFADEDOUT }, + { ANIM_STD_CAR_SIT_P_LO, ASSOC_DELETEFADEDOUT }, + { ANIM_STD_CAR_DRIVE_LEFT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_DRIVE_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_DRIVE_LEFT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_DRIVE_RIGHT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_DRIVEBY_LEFT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_DRIVEBY_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_LOOKBEHIND, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_BOAT_DRIVE, ASSOC_DELETEFADEDOUT }, + { ANIM_STD_GETOUT_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_GETOUT_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_HOOKERTALK, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_COACH_OPEN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_COACH_OPEN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_COACH_GET_IN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_COACH_GET_IN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_COACH_GET_OUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_TRAIN_GETIN, ASSOC_PARTIAL }, + { ANIM_STD_TRAIN_GETOUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CRAWLOUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CRAWLOUT_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_OPEN_DOOR_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_GET_IN_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_GET_OUT_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_OPEN_DOOR_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_GET_IN_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_GET_OUT_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_GET_UP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_GET_UP_LEFT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_GET_UP_RIGHT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_GET_UP_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_JUMP_LAUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_JUMP_GLIDE, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_JUMP_LAND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_FALL, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_FALL_GLIDE, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_FALL_LAND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_FALL_COLLAPSE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_EVADE_STEP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_EVADE_DIVE, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_XPRESS_SCRATCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_IDLE }, + { ANIM_STD_ROADCROSS, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_TURN180, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_ARREST, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_DROWN, ASSOC_PARTIAL }, + { ANIM_MEDIC_CPR, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_DUCK_DOWN, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_DUCK_LOW, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_RBLOCK_SHOOT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_THROW_UNDER2, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_HANDSUP, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HANDSCOWER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_PARTIAL_FUCKU, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_PHONE_IN, ASSOC_PARTIAL }, + { ANIM_STD_PHONE_OUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_PHONE_TALK, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, +}; +#ifdef PC_PLAYER_CONTROLS +AnimAssocDesc aStdAnimDescsSide[] = { + { ANIM_STD_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK | ASSOC_HAS_X_TRANSLATION }, + { ANIM_STD_RUN, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK | ASSOC_HAS_X_TRANSLATION }, + { ANIM_STD_RUNFAST, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK | ASSOC_HAS_X_TRANSLATION }, + { ANIM_STD_IDLE, ASSOC_REPEAT }, + { ANIM_STD_STARTWALK, ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION }, +}; +#endif +char const *aStdAnimations[] = { + "walk_civi", + "run_civi", + "sprint_panic", + "idle_stance", + "walk_start", + "run_stop", + "run_stopR", + "idle_cam", + "idle_hbhb", + "idle_tired", + "idle_armed", + "idle_chat", + "idle_taxi", + "KO_shot_front", + "KO_shot_front", + "KO_shot_front", + "KO_shot_front", + "KO_shot_face", + "KO_shot_stom", + "KO_shot_arml", + "KO_shot_armR", + "KO_shot_legl", + "KO_shot_legR", + "KD_left", + "KD_right", + "KO_skid_front", + "KO_spin_R", + "KO_skid_back", + "KO_spin_L", + "SHOT_partial", + "SHOT_leftP", + "SHOT_partial", + "SHOT_rightP", + "HIT_front", + "HIT_L", + "HIT_back", + "HIT_R", + "FLOOR_hit", +#if GTA_VERSION <= GTA3_PS2_160 + "HIT_body", +#endif + "HIT_bodyblow", + "HIT_chest", + "HIT_head", + "HIT_walk", + "HIT_wall", + "FLOOR_hit_f", + "HIT_behind", + "punchR", + "KICK_floor", + "WEAPON_bat_h", + "WEAPON_bat_v", + "WEAPON_hgun_body", + "WEAPON_AK_body", + "WEAPON_pump", + "WEAPON_sniper", + "WEAPON_throw", + "WEAPON_throwu", + "WEAPON_start_throw", + "bomber", + "WEAPON_hgun_rload", + "WEAPON_AK_rload", +#ifdef PC_PLAYER_CONTROLS + // maybe wrong define, but unused anyway + "FPS_PUNCH", + "FPS_BAT", + "FPS_UZI", + "FPS_PUMP", + "FPS_AK", + "FPS_M16", + "FPS_ROCKET", +#endif + "FIGHTIDLE", + "FIGHT2IDLE", + "FIGHTsh_F", + "FIGHTbodyblow", + "FIGHThead", + "FIGHTkick", + "FIGHTknee", + "FIGHTLhook", + "FIGHTpunch", + "FIGHTrndhse", + "FIGHTlngkck", + "FIGHTppunch", + "car_jackedRHS", + "car_LjackedRHS", + "car_jackedLHS", + "car_LjackedLHS", + "CAR_Qjack", + "CAR_Qjacked", + "CAR_align_LHS", + "CAR_alignHI_LHS", + "CAR_open_LHS", + "CAR_doorlocked_LHS", + "CAR_pullout_LHS", + "CAR_pulloutL_LHS", + "CAR_getin_LHS", + "CAR_getinL_LHS", + "CAR_closedoor_LHS", + "CAR_closedoorL_LHS", + "CAR_rolldoor", + "CAR_rolldoorLO", + "CAR_getout_LHS", + "CAR_getoutL_LHS", + "CAR_close_LHS", + "CAR_align_RHS", + "CAR_alignHI_RHS", + "CAR_open_RHS", + "CAR_doorlocked_RHS", + "CAR_pullout_RHS", + "CAR_pulloutL_RHS", + "CAR_getin_RHS", + "CAR_getinL_RHS", + "CAR_closedoor_RHS", + "CAR_closedoorL_RHS", + "CAR_shuffle_RHS", + "CAR_Lshuffle_RHS", + "CAR_sit", + "CAR_Lsit", + "CAR_sitp", + "CAR_sitpLO", + "DRIVE_L", + "Drive_R", + "Drive_LO_l", + "Drive_LO_R", + "Driveby_L", + "Driveby_R", + "CAR_LB", + "DRIVE_BOAT", + "CAR_getout_RHS", + "CAR_getoutL_RHS", + "CAR_close_RHS", + "car_hookertalk", + "COACH_opnL", + "COACH_opnR", + "COACH_inL", + "COACH_inR", + "COACH_outL", + "TRAIN_getin", + "TRAIN_getout", + "CAR_crawloutRHS", + "CAR_crawloutRHS", + "VAN_openL", + "VAN_getinL", + "VAN_closeL", + "VAN_getoutL", + "VAN_open", + "VAN_getin", + "VAN_close", + "VAN_getout", + "Getup", + "Getup", + "Getup", + "Getup_front", + "JUMP_launch", + "JUMP_glide", + "JUMP_land", + "FALL_fall", + "FALL_glide", + "FALL_land", + "FALL_collapse", + "EV_step", + "EV_dive", + "XPRESSscratch", + "roadcross", + "TURN_180", + "ARRESTgun", + "DROWN", + "CPR", + "DUCK_down", + "DUCK_low", + "RBLOCK_Cshoot", + "WEAPON_throwu", + "handsup", + "handsCOWER", + "FUCKU", + "PHONE_in", + "PHONE_out", + "PHONE_talk", +}; +char const *aPlayerAnimations[] = { + "walk_player", + "run_player", + "SPRINT_civi", + "IDLE_STANCE", + "walk_start", +}; +char const *aPlayerWithRocketAnimations[] = { + "walk_rocket", + "run_rocket", + "run_rocket", + "idle_rocket", + "walk_start_rocket", +}; +char const *aPlayer1ArmedAnimations[] = { + "walk_player", + "run_1armed", + "SPRINT_civi", + "IDLE_STANCE", + "walk_start", +}; +char const *aPlayer2ArmedAnimations[] = { + "walk_player", + "run_armed", + "run_armed", + "idle_stance", + "walk_start", +}; +char const *aPlayerBBBatAnimations[] = { + "walk_player", + "run_player", + "run_player", + "IDLE_STANCE", + "walk_start", +}; +char const *aShuffleAnimations[] = { + "WALK_shuffle", + "RUN_civi", + "SPRINT_civi", + "IDLE_STANCE", +}; +char const *aOldAnimations[] = { + "walk_old", + "run_civi", + "sprint_civi", + "idle_stance", +}; +char const *aGang1Animations[] = { + "walk_gang1", + "run_gang1", + "sprint_civi", + "idle_stance", +}; +char const *aGang2Animations[] = { + "walk_gang2", + "run_gang1", + "sprint_civi", + "idle_stance", +}; +char const *aFatAnimations[] = { + "walk_fat", + "run_civi", + "woman_runpanic", + "idle_stance", +}; +char const *aOldFatAnimations[] = { + "walk_fatold", + "run_fatold", + "woman_runpanic", + "idle_stance", +}; +char const *aStdWomanAnimations[] = { + "woman_walknorm", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const *aWomanShopAnimations[] = { + "woman_walkshop", + "woman_run", + "woman_run", + "woman_idlestance", +}; +char const *aBusyWomanAnimations[] = { + "woman_walkbusy", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const *aSexyWomanAnimations[] = { + "woman_walksexy", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const *aOldWomanAnimations[] = { + "woman_walkold", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const *aFatWomanAnimations[] = { + "walk_fat", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const *aPanicChunkyAnimations[] = { + "run_fatold", + "woman_runpanic", + "woman_runpanic", + "idle_stance", +}; +#ifdef PC_PLAYER_CONTROLS +char const *aPlayerStrafeBackAnimations[] = { + "walk_player_back", + "run_player_back", + "run_player_back", + "IDLE_STANCE", + "walk_start_back", +}; +char const *aPlayerStrafeLeftAnimations[] = { + "walk_player_left", + "run_left", + "run_left", + "IDLE_STANCE", + "walk_start_left", +}; +char const *aPlayerStrafeRightAnimations[] = { + "walk_player_right", + "run_right", + "run_right", + "IDLE_STANCE", + "walk_start_right", +}; +char const *aRocketStrafeBackAnimations[] = { + "walk_rocket_back", + "run_rocket_back", + "run_rocket_back", + "idle_rocket", + "walkst_rocket_back", +}; +char const *aRocketStrafeLeftAnimations[] = { + "walk_rocket_left", + "run_rocket_left", + "run_rocket_left", + "idle_rocket", + "walkst_rocket_left", +}; +char const *aRocketStrafeRightAnimations[] = { + "walk_rocket_right", + "run_rocket_right", + "run_rocket_right", + "idle_rocket", + "walkst_rocket_right", +}; +#endif + +#define awc(a) ARRAY_SIZE(a), a +const AnimAssocDefinition CAnimManager::ms_aAnimAssocDefinitions[NUM_ANIM_ASSOC_GROUPS] = { + { "man", "ped", MI_COP, awc(aStdAnimations), aStdAnimDescs }, + { "player", "ped", MI_COP, awc(aPlayerAnimations), aStdAnimDescs }, + { "playerrocket", "ped", MI_COP, awc(aPlayerWithRocketAnimations), aStdAnimDescs }, + { "player1armed", "ped", MI_COP, awc(aPlayer1ArmedAnimations), aStdAnimDescs }, + { "player2armed", "ped", MI_COP, awc(aPlayer2ArmedAnimations), aStdAnimDescs }, + { "playerBBBat", "ped", MI_COP, awc(aPlayerBBBatAnimations), aStdAnimDescs }, + { "shuffle", "ped", MI_COP, awc(aShuffleAnimations), aStdAnimDescs }, + { "oldman", "ped", MI_COP, awc(aOldAnimations), aStdAnimDescs }, + { "gang1", "ped", MI_COP, awc(aGang1Animations), aStdAnimDescs }, + { "gang2", "ped", MI_COP, awc(aGang2Animations), aStdAnimDescs }, + { "fatman", "ped", MI_COP, awc(aFatAnimations), aStdAnimDescs }, + { "oldfatman", "ped", MI_COP, awc(aOldFatAnimations), aStdAnimDescs }, + { "woman", "ped", MI_COP, awc(aStdWomanAnimations), aStdAnimDescs }, + { "shopping", "ped", MI_COP, awc(aWomanShopAnimations), aStdAnimDescs }, + { "busywoman", "ped", MI_COP, awc(aBusyWomanAnimations), aStdAnimDescs }, + { "sexywoman", "ped", MI_COP, awc(aSexyWomanAnimations), aStdAnimDescs }, + { "oldwoman", "ped", MI_COP, awc(aOldWomanAnimations), aStdAnimDescs }, + { "fatwoman", "ped", MI_COP, awc(aFatWomanAnimations), aStdAnimDescs }, + { "panicchunky", "ped", MI_COP, awc(aPanicChunkyAnimations), aStdAnimDescs }, +#ifdef PC_PLAYER_CONTROLS + { "playerback", "ped", MI_COP, awc(aPlayerStrafeBackAnimations), aStdAnimDescs }, + { "playerleft", "ped", MI_COP, awc(aPlayerStrafeLeftAnimations), aStdAnimDescsSide }, + { "playerright", "ped", MI_COP, awc(aPlayerStrafeRightAnimations), aStdAnimDescsSide }, + { "rocketback", "ped", MI_COP, awc(aRocketStrafeBackAnimations), aStdAnimDescs }, + { "rocketleft", "ped", MI_COP, awc(aRocketStrafeLeftAnimations), aStdAnimDescsSide }, + { "rocketright", "ped", MI_COP, awc(aRocketStrafeRightAnimations), aStdAnimDescsSide }, +#endif +}; +#undef awc + +void +CAnimManager::Initialise(void) +{ + ms_numAnimations = 0; + ms_numAnimBlocks = 0; + ms_animCache.Init(25); + +// dumpanimdata(); +} + +void +CAnimManager::Shutdown(void) +{ + int i; + + ms_animCache.Shutdown(); + + for(i = 0; i < ms_numAnimations; i++) + ms_aAnimations[i].Shutdown(); + + delete[] ms_aAnimAssocGroups; +} + +void +CAnimManager::UncompressAnimation(CAnimBlendHierarchy *hier) +{ + if(!hier->compressed){ + if(hier->linkPtr){ + hier->linkPtr->Remove(); + ms_animCache.head.Insert(hier->linkPtr); + } + }else{ + CLink *link = ms_animCache.Insert(hier); + if(link == nil){ + ms_animCache.tail.prev->item->RemoveUncompressedData(); + ms_animCache.Remove(ms_animCache.tail.prev); + link = ms_animCache.Insert(hier); + } + hier->linkPtr = link; + hier->Uncompress(); + } +} + +CAnimBlock* +CAnimManager::GetAnimationBlock(const char *name) +{ + int i; + + for(i = 0; i < ms_numAnimBlocks; i++) + if(strcasecmp(ms_aAnimBlocks[i].name, name) == 0) + return &ms_aAnimBlocks[i]; + return nil; +} + +CAnimBlendHierarchy* +CAnimManager::GetAnimation(const char *name, CAnimBlock *animBlock) +{ + int i; + CAnimBlendHierarchy *hier = &ms_aAnimations[animBlock->firstIndex]; + + for(i = 0; i < animBlock->numAnims; i++){ + if(!CGeneral::faststricmp(hier->name, name)) + return hier; + hier++; + } + return nil; +} + +const char* +CAnimManager::GetAnimGroupName(AssocGroupId groupId) +{ + return ms_aAnimAssocDefinitions[groupId].name; +} + +CAnimBlendAssociation* +CAnimManager::CreateAnimAssociation(AssocGroupId groupId, AnimationId animId) +{ + return ms_aAnimAssocGroups[groupId].CopyAnimation(animId); +} + +CAnimBlendAssociation* +CAnimManager::GetAnimAssociation(AssocGroupId groupId, AnimationId animId) +{ + return ms_aAnimAssocGroups[groupId].GetAnimation(animId); +} + +CAnimBlendAssociation* +CAnimManager::GetAnimAssociation(AssocGroupId groupId, const char *name) +{ + return ms_aAnimAssocGroups[groupId].GetAnimation(name); +} + +CAnimBlendAssociation* +CAnimManager::AddAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId) +{ + CAnimBlendAssociation *anim = CreateAnimAssociation(groupId, animId); + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + if(anim->IsMovement()){ + CAnimBlendAssociation *syncanim = nil; + CAnimBlendLink *link; + for(link = clumpData->link.next; link; link = link->next){ + syncanim = CAnimBlendAssociation::FromLink(link); + if(syncanim->IsMovement()) + break; + } + if(link){ + anim->SyncAnimation(syncanim); + anim->flags |= ASSOC_RUNNING; + }else + anim->Start(0.0f); + }else + anim->Start(0.0f); + + clumpData->link.Prepend(&anim->link); + return anim; +} + +CAnimBlendAssociation* +CAnimManager::AddAnimationAndSync(RpClump *clump, CAnimBlendAssociation *syncanim, AssocGroupId groupId, AnimationId animId) +{ + CAnimBlendAssociation *anim = CreateAnimAssociation(groupId, animId); + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + if (anim->IsMovement() && syncanim){ + anim->SyncAnimation(syncanim); + anim->flags |= ASSOC_RUNNING; + }else + anim->Start(0.0f); + + clumpData->link.Prepend(&anim->link); + return anim; +} + +CAnimBlendAssociation* +CAnimManager::BlendAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId, float delta) +{ + int removePrevAnim = 0; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + CAnimBlendAssociation *anim = GetAnimAssociation(groupId, animId); + bool isMovement = anim->IsMovement(); + bool isPartial = anim->IsPartial(); + CAnimBlendLink *link; + CAnimBlendAssociation *found = nil, *movementAnim = nil; + for(link = clumpData->link.next; link; link = link->next){ + anim = CAnimBlendAssociation::FromLink(link); + if(isMovement && anim->IsMovement()) + movementAnim = anim; + if(anim->animId == animId) + found = anim; + else{ + if(isPartial == anim->IsPartial()){ + if(anim->blendAmount > 0.0f){ + float blendDelta = -delta*anim->blendAmount; + if(blendDelta < anim->blendDelta || !isPartial) + anim->blendDelta = blendDelta; + }else{ + anim->blendDelta = -1.0f; + } + anim->flags |= ASSOC_DELETEFADEDOUT; + removePrevAnim = 1; + } + } + } + if(found){ + found->blendDelta = (1.0f - found->blendAmount)*delta; + if(!found->IsRunning() && found->currentTime == found->hierarchy->totalLength) + found->Start(0.0f); + }else{ + found = AddAnimationAndSync(clump, movementAnim, groupId, animId); + if(!removePrevAnim && !isPartial){ + found->blendAmount = 1.0f; + return found; + } + found->blendAmount = 0.0f; + found->blendDelta = delta; + } + UncompressAnimation(found->hierarchy); + return found; +} + +void +CAnimManager::LoadAnimFiles(void) +{ + int i, j; + + LoadAnimFile("ANIM\\PED.IFP"); + + // Create all assoc groups + ms_aAnimAssocGroups = new CAnimBlendAssocGroup[NUM_ANIM_ASSOC_GROUPS]; + for(i = 0; i < NUM_ANIM_ASSOC_GROUPS; i++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(ms_aAnimAssocDefinitions[i].modelIndex); + RpClump *clump = (RpClump*)mi->CreateInstance(); + RpAnimBlendClumpInit(clump); + CAnimBlendAssocGroup *group = &ms_aAnimAssocGroups[i]; + const AnimAssocDefinition *def = &ms_aAnimAssocDefinitions[i]; + group->CreateAssociations(def->blockName, clump, def->animNames, def->numAnims); + for(j = 0; j < group->numAssociations; j++) + group->GetAnimation(j)->flags |= def->animDescs[j].flags; +#ifdef PED_SKIN + // forgot on xbox/android + if(IsClumpSkinned(clump)) + RpClumpForAllAtomics(clump, AtomicRemoveAnimFromSkinCB, nil); +#endif + RpClumpDestroy(clump); + } +} + +void +CAnimManager::LoadAnimFile(const char *filename) +{ + int fd; + fd = CFileMgr::OpenFile(filename, "rb"); + assert(fd > 0); + LoadAnimFile(fd, true); + CFileMgr::CloseFile(fd); +} + +void +CAnimManager::LoadAnimFile(int fd, bool compress) +{ + #define ROUNDSIZE(x) if((x) & 3) (x) += 4 - ((x)&3) + struct IfpHeader { + char ident[4]; + uint32 size; + }; + IfpHeader anpk, info, name, dgan, cpan, anim; + int numANPK; + char buf[256]; + int i, j, k, l; + float *fbuf = (float*)buf; + + CFileMgr::Read(fd, (char*)&anpk, sizeof(IfpHeader)); + if(!CGeneral::faststrncmp(anpk.ident, "ANLF", 4)) { + ROUNDSIZE(anpk.size); + CFileMgr::Read(fd, buf, anpk.size); + numANPK = *(int*)buf; + } else if(!CGeneral::faststrncmp(anpk.ident, "ANPK", 4)) { + CFileMgr::Seek(fd, -8, 1); + numANPK = 1; + } + + for(i = 0; i < numANPK; i++){ + // block name + CFileMgr::Read(fd, (char*)&anpk, sizeof(IfpHeader)); + ROUNDSIZE(anpk.size); + CFileMgr::Read(fd, (char*)&info, sizeof(IfpHeader)); + ROUNDSIZE(info.size); + CFileMgr::Read(fd, buf, info.size); + CAnimBlock *animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++]; + strncpy(animBlock->name, buf+4, 24); + animBlock->numAnims = *(int*)buf; + + animBlock->firstIndex = ms_numAnimations; + + for(j = 0; j < animBlock->numAnims; j++){ + CAnimBlendHierarchy *hier = &ms_aAnimations[ms_numAnimations++]; + + // animation name + CFileMgr::Read(fd, (char*)&name, sizeof(IfpHeader)); + ROUNDSIZE(name.size); + CFileMgr::Read(fd, buf, name.size); + hier->SetName(buf); + + // DG info has number of nodes/sequences + CFileMgr::Read(fd, (char*)&dgan, sizeof(IfpHeader)); + ROUNDSIZE(dgan.size); + CFileMgr::Read(fd, (char*)&info, sizeof(IfpHeader)); + ROUNDSIZE(info.size); + CFileMgr::Read(fd, buf, info.size); + hier->numSequences = *(int*)buf; + hier->sequences = new CAnimBlendSequence[hier->numSequences]; + + CAnimBlendSequence *seq = hier->sequences; + for(k = 0; k < hier->numSequences; k++, seq++){ + // Each node has a name and key frames + CFileMgr::Read(fd, (char*)&cpan, sizeof(IfpHeader)); + ROUNDSIZE(dgan.size); + CFileMgr::Read(fd, (char*)&anim, sizeof(IfpHeader)); + ROUNDSIZE(anim.size); + CFileMgr::Read(fd, buf, anim.size); + int numFrames = *(int*)(buf+28); + seq->SetName(buf); +#ifdef PED_SKIN + if(anim.size == 44) + seq->SetBoneTag(*(int*)(buf+40)); +#endif + if(numFrames == 0) + continue; + + bool hasScale = false; + bool hasTranslation = false; + CFileMgr::Read(fd, (char*)&info, sizeof(info)); + if(!CGeneral::faststrncmp(info.ident, "KRTS", 4)) { + hasScale = true; + seq->SetNumFrames(numFrames, true); + }else if(!CGeneral::faststrncmp(info.ident, "KRT0", 4)) { + hasTranslation = true; + seq->SetNumFrames(numFrames, true); + }else if(!CGeneral::faststrncmp(info.ident, "KR00", 4)){ + seq->SetNumFrames(numFrames, false); + } + + for(l = 0; l < numFrames; l++){ + if(hasScale){ + CFileMgr::Read(fd, buf, 0x2C); + CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); + rot.Invert(); + CVector trans(fbuf[4], fbuf[5], fbuf[6]); + + KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(l); + kf->rotation = rot; + kf->translation = trans; + // scaling ignored + kf->deltaTime = fbuf[10]; // absolute time here + }else if(hasTranslation){ + CFileMgr::Read(fd, buf, 0x20); + CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); + rot.Invert(); + CVector trans(fbuf[4], fbuf[5], fbuf[6]); + + KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(l); + kf->rotation = rot; + kf->translation = trans; + kf->deltaTime = fbuf[7]; // absolute time here + }else{ + CFileMgr::Read(fd, buf, 0x14); + CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); + rot.Invert(); + + KeyFrame *kf = (KeyFrame*)seq->GetKeyFrame(l); + kf->rotation = rot; + kf->deltaTime = fbuf[4]; // absolute time here + } + } + + // convert absolute time to deltas + for(l = seq->numFrames-1; l > 0; l--){ + KeyFrame *kf1 = seq->GetKeyFrame(l); + KeyFrame *kf2 = seq->GetKeyFrame(l-1); + kf1->deltaTime -= kf2->deltaTime; + } + } + + hier->RemoveQuaternionFlips(); + if(compress) + hier->RemoveUncompressedData(); + else + hier->CalcTotalTime(); + } + } +} + +void +CAnimManager::RemoveLastAnimFile(void) +{ + int i; + ms_numAnimBlocks--; + ms_numAnimations = ms_aAnimBlocks[ms_numAnimBlocks].firstIndex; + for(i = 0; i < ms_aAnimBlocks[ms_numAnimBlocks].numAnims; i++) + ms_aAnimations[ms_aAnimBlocks[ms_numAnimBlocks].firstIndex + i].RemoveAnimSequences(); +} diff --git a/src/animation/AnimManager.h b/src/animation/AnimManager.h new file mode 100644 index 0000000..92192c7 --- /dev/null +++ b/src/animation/AnimManager.h @@ -0,0 +1,94 @@ +#pragma once + +#include "AnimBlendHierarchy.h" +#include "AnimationId.h" + +enum AssocGroupId +{ + ASSOCGRP_STD, + ASSOCGRP_PLAYER, + ASSOCGRP_PLAYERROCKET, + ASSOCGRP_PLAYER1ARMED, + ASSOCGRP_PLAYER2ARMED, + ASSOCGRP_PLAYERBBBAT, + ASSOCGRP_SHUFFLE, + ASSOCGRP_OLD, + ASSOCGRP_GANG1, + ASSOCGRP_GANG2, + ASSOCGRP_FAT, + ASSOCGRP_OLDFAT, + ASSOCGRP_WOMAN, + ASSOCGRP_WOMANSHOP, + ASSOCGRP_BUSYWOMAN, + ASSOCGRP_SEXYWOMAN, + ASSOCGRP_OLDWOMAN, + ASSOCGRP_FATWOMAN, + ASSOCGRP_PANICCHUNKY, +#ifdef PC_PLAYER_CONTROLS + ASSOCGRP_PLAYERBACK, + ASSOCGRP_PLAYERLEFT, + ASSOCGRP_PLAYERRIGHT, + ASSOCGRP_ROCKETBACK, + ASSOCGRP_ROCKETLEFT, + ASSOCGRP_ROCKETRIGHT, +#endif + + NUM_ANIM_ASSOC_GROUPS +}; + +class CAnimBlendAssociation; +class CAnimBlendAssocGroup; + +// A block of hierarchies +struct CAnimBlock +{ + char name[24]; + int32 firstIndex; + int32 numAnims; +}; + +struct AnimAssocDesc +{ + int32 animId; + int32 flags; +}; + +struct AnimAssocDefinition +{ + char const *name; + char const *blockName; + int32 modelIndex; + int32 numAnims; + char const **animNames; + AnimAssocDesc *animDescs; +}; + +class CAnimManager +{ + static const AnimAssocDefinition ms_aAnimAssocDefinitions[NUM_ANIM_ASSOC_GROUPS]; + static CAnimBlock ms_aAnimBlocks[NUMANIMBLOCKS]; + static CAnimBlendHierarchy ms_aAnimations[NUMANIMATIONS]; + static int32 ms_numAnimBlocks; + static int32 ms_numAnimations; + static CAnimBlendAssocGroup *ms_aAnimAssocGroups; + static CLinkList ms_animCache; +public: + + static void Initialise(void); + static void Shutdown(void); + static void UncompressAnimation(CAnimBlendHierarchy *anim); + static CAnimBlock *GetAnimationBlock(const char *name); + static CAnimBlendHierarchy *GetAnimation(const char *name, CAnimBlock *animBlock); + static CAnimBlendHierarchy *GetAnimation(int32 n) { return &ms_aAnimations[n]; } + static const char *GetAnimGroupName(AssocGroupId groupId); + static CAnimBlendAssociation *CreateAnimAssociation(AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *GetAnimAssociation(AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *GetAnimAssociation(AssocGroupId groupId, const char *name); + static CAnimBlendAssociation *AddAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *AddAnimationAndSync(RpClump *clump, CAnimBlendAssociation *syncanim, AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *BlendAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId, float delta); + static void LoadAnimFiles(void); + static void LoadAnimFile(const char *filename); + static void LoadAnimFile(int fd, bool compress); + static void RemoveLastAnimFile(void); +}; diff --git a/src/animation/AnimationId.h b/src/animation/AnimationId.h new file mode 100644 index 0000000..baf6eb3 --- /dev/null +++ b/src/animation/AnimationId.h @@ -0,0 +1,210 @@ +#pragma once + +enum AnimationId +{ + ANIM_STD_WALK, + ANIM_STD_RUN, + ANIM_STD_RUNFAST, + ANIM_STD_IDLE, + ANIM_STD_STARTWALK, + ANIM_STD_RUNSTOP1, + ANIM_STD_RUNSTOP2, + ANIM_STD_IDLE_CAM, + ANIM_STD_IDLE_HBHB, + ANIM_STD_IDLE_TIRED, + ANIM_STD_IDLE_BIGGUN, + ANIM_STD_CHAT, + ANIM_STD_HAILTAXI, + ANIM_STD_KO_FRONT, + ANIM_STD_KO_LEFT, + ANIM_STD_KO_BACK, + ANIM_STD_KO_RIGHT, + ANIM_STD_KO_SHOT_FACE, + ANIM_STD_KO_SHOT_STOMACH, + ANIM_STD_KO_SHOT_ARM_L, + ANIM_STD_KO_SHOT_ARM_R, + ANIM_STD_KO_SHOT_LEG_L, + ANIM_STD_KO_SHOT_LEG_R, + ANIM_STD_SPINFORWARD_LEFT, + ANIM_STD_SPINFORWARD_RIGHT, + ANIM_STD_HIGHIMPACT_FRONT, + ANIM_STD_HIGHIMPACT_LEFT, + ANIM_STD_HIGHIMPACT_BACK, + ANIM_STD_HIGHIMPACT_RIGHT, + ANIM_STD_HITBYGUN_FRONT, + ANIM_STD_HITBYGUN_LEFT, + ANIM_STD_HITBYGUN_BACK, + ANIM_STD_HITBYGUN_RIGHT, + ANIM_STD_HIT_FRONT, + ANIM_STD_HIT_LEFT, + ANIM_STD_HIT_BACK, + ANIM_STD_HIT_RIGHT, + ANIM_STD_HIT_FLOOR, + + /* names made up */ +#if GTA_VERSION <= GTA3_PS2_160 + ANIM_STD_HIT_BODY, +#endif + ANIM_STD_HIT_BODYBLOW, + ANIM_STD_HIT_CHEST, + ANIM_STD_HIT_HEAD, + ANIM_STD_HIT_WALK, + /**/ + + ANIM_STD_HIT_WALL, + ANIM_STD_HIT_FLOOR_FRONT, + ANIM_STD_HIT_BEHIND, + ANIM_STD_PUNCH, + ANIM_STD_KICKGROUND, + + /* names made up */ + ANIM_STD_WEAPON_BAT_H, + ANIM_STD_WEAPON_BAT_V, + ANIM_STD_WEAPON_HGUN_BODY, + ANIM_STD_WEAPON_AK_BODY, + ANIM_STD_WEAPON_PUMP, + ANIM_STD_WEAPON_SNIPER, + ANIM_STD_WEAPON_THROW, + /**/ + + ANIM_STD_THROW_UNDER, + + /* names made up */ + ANIM_STD_START_THROW, + /**/ + + ANIM_STD_DETONATE, + + /* names made up */ + ANIM_STD_HGUN_RELOAD, + ANIM_STD_AK_RELOAD, +#ifdef PC_PLAYER_CONTROLS + // maybe wrong define, but unused anyway + ANIM_FPS_PUNCH, + ANIM_FPS_BAT, + ANIM_FPS_UZI, + ANIM_FPS_PUMP, + ANIM_FPS_AK, + ANIM_FPS_M16, + ANIM_FPS_ROCKET, +#endif + /**/ + + ANIM_STD_FIGHT_IDLE, + ANIM_STD_FIGHT_2IDLE, + ANIM_STD_FIGHT_SHUFFLE_F, + + /* names made up */ + ANIM_STD_FIGHT_BODYBLOW, + ANIM_STD_FIGHT_HEAD, + ANIM_STD_FIGHT_KICK, + ANIM_STD_FIGHT_KNEE, + ANIM_STD_FIGHT_LHOOK, + ANIM_STD_FIGHT_PUNCH, + ANIM_STD_FIGHT_ROUNDHOUSE, + ANIM_STD_FIGHT_LONGKICK, + /**/ + + ANIM_STD_PARTIAL_PUNCH, + ANIM_STD_JACKEDCAR_RHS, + ANIM_STD_JACKEDCAR_LO_RHS, + ANIM_STD_JACKEDCAR_LHS, + ANIM_STD_JACKEDCAR_LO_LHS, + ANIM_STD_QUICKJACK, + ANIM_STD_QUICKJACKED, + ANIM_STD_CAR_ALIGN_DOOR_LHS, + ANIM_STD_CAR_ALIGNHI_DOOR_LHS, + ANIM_STD_CAR_OPEN_DOOR_LHS, + ANIM_STD_CARDOOR_LOCKED_LHS, + ANIM_STD_CAR_PULL_OUT_PED_LHS, + ANIM_STD_CAR_PULL_OUT_PED_LO_LHS, + ANIM_STD_CAR_GET_IN_LHS, + ANIM_STD_CAR_GET_IN_LO_LHS, + ANIM_STD_CAR_CLOSE_DOOR_LHS, + ANIM_STD_CAR_CLOSE_DOOR_LO_LHS, + ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, + ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS, + ANIM_STD_GETOUT_LHS, + ANIM_STD_GETOUT_LO_LHS, + ANIM_STD_CAR_CLOSE_LHS, + ANIM_STD_CAR_ALIGN_DOOR_RHS, + ANIM_STD_CAR_ALIGNHI_DOOR_RHS, + ANIM_STD_CAR_OPEN_DOOR_RHS, + ANIM_STD_CARDOOR_LOCKED_RHS, + ANIM_STD_CAR_PULL_OUT_PED_RHS, + ANIM_STD_CAR_PULL_OUT_PED_LO_RHS, + ANIM_STD_CAR_GET_IN_RHS, + ANIM_STD_CAR_GET_IN_LO_RHS, + ANIM_STD_CAR_CLOSE_DOOR_RHS, + ANIM_STD_CAR_CLOSE_DOOR_LO_RHS, + ANIM_STD_CAR_SHUFFLE_RHS, + ANIM_STD_CAR_SHUFFLE_LO_RHS, + ANIM_STD_CAR_SIT, + ANIM_STD_CAR_SIT_LO, + ANIM_STD_CAR_SIT_P, + ANIM_STD_CAR_SIT_P_LO, + ANIM_STD_CAR_DRIVE_LEFT, + ANIM_STD_CAR_DRIVE_RIGHT, + ANIM_STD_CAR_DRIVE_LEFT_LO, + ANIM_STD_CAR_DRIVE_RIGHT_LO, + ANIM_STD_CAR_DRIVEBY_LEFT, + ANIM_STD_CAR_DRIVEBY_RIGHT, + ANIM_STD_CAR_LOOKBEHIND, + ANIM_STD_BOAT_DRIVE, + ANIM_STD_GETOUT_RHS, + ANIM_STD_GETOUT_LO_RHS, + ANIM_STD_CAR_CLOSE_RHS, + ANIM_STD_CAR_HOOKERTALK, + ANIM_STD_COACH_OPEN_LHS, + ANIM_STD_COACH_OPEN_RHS, + ANIM_STD_COACH_GET_IN_LHS, + ANIM_STD_COACH_GET_IN_RHS, + ANIM_STD_COACH_GET_OUT_LHS, + ANIM_STD_TRAIN_GETIN, + ANIM_STD_TRAIN_GETOUT, + ANIM_STD_CRAWLOUT_LHS, + ANIM_STD_CRAWLOUT_RHS, + ANIM_STD_VAN_OPEN_DOOR_REAR_LHS, + ANIM_STD_VAN_GET_IN_REAR_LHS, + ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS, + ANIM_STD_VAN_GET_OUT_REAR_LHS, + ANIM_STD_VAN_OPEN_DOOR_REAR_RHS, + ANIM_STD_VAN_GET_IN_REAR_RHS, + ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS, + ANIM_STD_VAN_GET_OUT_REAR_RHS, + ANIM_STD_GET_UP, + ANIM_STD_GET_UP_LEFT, + ANIM_STD_GET_UP_RIGHT, + ANIM_STD_GET_UP_FRONT, + ANIM_STD_JUMP_LAUNCH, + ANIM_STD_JUMP_GLIDE, + ANIM_STD_JUMP_LAND, + ANIM_STD_FALL, + ANIM_STD_FALL_GLIDE, + ANIM_STD_FALL_LAND, + ANIM_STD_FALL_COLLAPSE, + ANIM_STD_EVADE_STEP, + ANIM_STD_EVADE_DIVE, + ANIM_STD_XPRESS_SCRATCH, + ANIM_STD_ROADCROSS, + ANIM_STD_TURN180, + ANIM_STD_ARREST, + ANIM_STD_DROWN, + ANIM_MEDIC_CPR, + ANIM_STD_DUCK_DOWN, + ANIM_STD_DUCK_LOW, + ANIM_STD_RBLOCK_SHOOT, + + /* names made up */ + ANIM_STD_THROW_UNDER2, + /**/ + + ANIM_STD_HANDSUP, + ANIM_STD_HANDSCOWER, + ANIM_STD_PARTIAL_FUCKU, + ANIM_STD_PHONE_IN, + ANIM_STD_PHONE_OUT, + ANIM_STD_PHONE_TALK, + + ANIM_STD_NUM +}; \ No newline at end of file diff --git a/src/animation/Bones.cpp b/src/animation/Bones.cpp new file mode 100644 index 0000000..1608449 --- /dev/null +++ b/src/animation/Bones.cpp @@ -0,0 +1,52 @@ +#include "common.h" +#include "PedModelInfo.h" +#include "Bones.h" + +#ifdef PED_SKIN + +int +ConvertPedNode2BoneTag(int node) +{ + switch(node){ + case PED_TORSO: return BONE_waist; + case PED_MID: return BONE_torso; // this is what Xbox/Mobile use + // return BONE_mid; // this is what PS2/PC use + case PED_HEAD: return BONE_head; + case PED_UPPERARML: return BONE_upperarml; + case PED_UPPERARMR: return BONE_upperarmr; + case PED_HANDL: return BONE_Lhand; + case PED_HANDR: return BONE_Rhand; + case PED_UPPERLEGL: return BONE_upperlegl; + case PED_UPPERLEGR: return BONE_upperlegr; + case PED_FOOTL: return BONE_footl; + case PED_FOOTR: return BONE_footr; + case PED_LOWERLEGR: return BONE_lowerlegl; + } + return -1; +} + +const char* +ConvertBoneTag2BoneName(int tag) +{ + switch(tag){ + case BONE_waist: return "Swaist"; + case BONE_upperlegr: return "Supperlegr"; + case BONE_lowerlegr: return "Slowerlegr"; + case BONE_footr: return "Sfootr"; + case BONE_upperlegl: return "Supperlegl"; + case BONE_lowerlegl: return "Slowerlegl"; + case BONE_footl: return "Sfootl"; + case BONE_mid: return "Smid"; + case BONE_torso: return "Storso"; + case BONE_head: return "Shead"; + case BONE_upperarmr: return "Supperarmr"; + case BONE_lowerarmr: return "Slowerarmr"; + case BONE_Rhand: return "SRhand"; + case BONE_upperarml: return "Supperarml"; + case BONE_lowerarml: return "Slowerarml"; + case BONE_Lhand: return "SLhand"; + } + return nil; +} + +#endif diff --git a/src/animation/Bones.h b/src/animation/Bones.h new file mode 100644 index 0000000..38d91ba --- /dev/null +++ b/src/animation/Bones.h @@ -0,0 +1,24 @@ +#pragma once + +enum BoneTag +{ + BONE_waist, + BONE_upperlegr, + BONE_lowerlegr, + BONE_footr, + BONE_upperlegl, + BONE_lowerlegl, + BONE_footl, + BONE_mid, + BONE_torso, + BONE_head, + BONE_upperarmr, + BONE_lowerarmr, + BONE_Rhand, + BONE_upperarml, + BONE_lowerarml, + BONE_Lhand, +}; + +int ConvertPedNode2BoneTag(int node); +const char *ConvertBoneTag2BoneName(int tag); diff --git a/src/animation/CutsceneMgr.cpp b/src/animation/CutsceneMgr.cpp new file mode 100644 index 0000000..83c4dbc --- /dev/null +++ b/src/animation/CutsceneMgr.cpp @@ -0,0 +1,424 @@ +#include "common.h" + +#include "General.h" +#include "CutsceneMgr.h" +#include "Directory.h" +#include "Camera.h" +#include "Streaming.h" +#include "FileMgr.h" +#include "main.h" +#include "AnimManager.h" +#include "AnimBlendAssociation.h" +#include "AnimBlendAssocGroup.h" +#include "AnimBlendClumpData.h" +#include "Pad.h" +#include "DMAudio.h" +#include "World.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "CutsceneHead.h" +#include "RpAnimBlend.h" +#include "ModelIndices.h" +#include "TempColModels.h" + +const struct { + const char *szTrackName; + int iTrackId; +} musicNameIdAssoc[] = { + { "JB", STREAMED_SOUND_NEWS_INTRO }, + { "BET", STREAMED_SOUND_BANK_INTRO }, + { "L1_LG", STREAMED_SOUND_CUTSCENE_LUIGI1_LG }, + { "L2_DSB", STREAMED_SOUND_CUTSCENE_LUIGI2_DSB }, + { "L3_DM", STREAMED_SOUND_CUTSCENE_LUIGI3_DM }, + { "L4_PAP", STREAMED_SOUND_CUTSCENE_LUIGI4_PAP }, + { "L5_TFB", STREAMED_SOUND_CUTSCENE_LUIGI5_TFB }, + { "J0_DM2", STREAMED_SOUND_CUTSCENE_JOEY0_DM2 }, + { "J1_LFL", STREAMED_SOUND_CUTSCENE_JOEY1_LFL }, + { "J2_KCL", STREAMED_SOUND_CUTSCENE_JOEY2_KCL }, + { "J3_VH", STREAMED_SOUND_CUTSCENE_JOEY3_VH }, + { "J4_ETH", STREAMED_SOUND_CUTSCENE_JOEY4_ETH }, + { "J5_DST", STREAMED_SOUND_CUTSCENE_JOEY5_DST }, + { "J6_TBJ", STREAMED_SOUND_CUTSCENE_JOEY6_TBJ }, + { "T1_TOL", STREAMED_SOUND_CUTSCENE_TONI1_TOL }, + { "T2_TPU", STREAMED_SOUND_CUTSCENE_TONI2_TPU }, + { "T3_MAS", STREAMED_SOUND_CUTSCENE_TONI3_MAS }, + { "T4_TAT", STREAMED_SOUND_CUTSCENE_TONI4_TAT }, + { "T5_BF", STREAMED_SOUND_CUTSCENE_TONI5_BF }, + { "S0_MAS", STREAMED_SOUND_CUTSCENE_SAL0_MAS }, + { "S1_PF", STREAMED_SOUND_CUTSCENE_SAL1_PF }, + { "S2_CTG", STREAMED_SOUND_CUTSCENE_SAL2_CTG }, + { "S3_RTC", STREAMED_SOUND_CUTSCENE_SAL3_RTC }, + { "S5_LRQ", STREAMED_SOUND_CUTSCENE_SAL5_LRQ }, + { "S4_BDBA", STREAMED_SOUND_CUTSCENE_SAL4_BDBA }, + { "S4_BDBB", STREAMED_SOUND_CUTSCENE_SAL4_BDBB }, + { "S2_CTG2", STREAMED_SOUND_CUTSCENE_SAL2_CTG2 }, + { "S4_BDBD", STREAMED_SOUND_CUTSCENE_SAL4_BDBD }, + { "S5_LRQB", STREAMED_SOUND_CUTSCENE_SAL5_LRQB }, + { "S5_LRQC", STREAMED_SOUND_CUTSCENE_SAL5_LRQC }, + { "A1_SS0", STREAMED_SOUND_CUTSCENE_ASUKA_1_SSO }, + { "A2_PP", STREAMED_SOUND_CUTSCENE_ASUKA_2_PP }, + { "A3_SS", STREAMED_SOUND_CUTSCENE_ASUKA_3_SS }, + { "A4_PDR", STREAMED_SOUND_CUTSCENE_ASUKA_4_PDR }, + { "A5_K2FT", STREAMED_SOUND_CUTSCENE_ASUKA_5_K2FT}, + { "K1_KBO", STREAMED_SOUND_CUTSCENE_KENJI1_KBO }, + { "K2_GIS", STREAMED_SOUND_CUTSCENE_KENJI2_GIS }, + { "K3_DS", STREAMED_SOUND_CUTSCENE_KENJI3_DS }, + { "K4_SHI", STREAMED_SOUND_CUTSCENE_KENJI4_SHI }, + { "K5_SD", STREAMED_SOUND_CUTSCENE_KENJI5_SD }, + { "R0_PDR2", STREAMED_SOUND_CUTSCENE_RAY0_PDR2 }, + { "R1_SW", STREAMED_SOUND_CUTSCENE_RAY1_SW }, + { "R2_AP", STREAMED_SOUND_CUTSCENE_RAY2_AP }, + { "R3_ED", STREAMED_SOUND_CUTSCENE_RAY3_ED }, + { "R4_GF", STREAMED_SOUND_CUTSCENE_RAY4_GF }, + { "R5_PB", STREAMED_SOUND_CUTSCENE_RAY5_PB }, + { "R6_MM", STREAMED_SOUND_CUTSCENE_RAY6_MM }, + { "D1_STOG", STREAMED_SOUND_CUTSCENE_DONALD1_STOG }, + { "D2_KK", STREAMED_SOUND_CUTSCENE_DONALD2_KK }, + { "D3_ADO", STREAMED_SOUND_CUTSCENE_DONALD3_ADO }, + { "D5_ES", STREAMED_SOUND_CUTSCENE_DONALD5_ES }, + { "D7_MLD", STREAMED_SOUND_CUTSCENE_DONALD7_MLD }, + { "D4_GTA", STREAMED_SOUND_CUTSCENE_DONALD4_GTA }, + { "D4_GTA2", STREAMED_SOUND_CUTSCENE_DONALD4_GTA2 }, + { "D6_STS", STREAMED_SOUND_CUTSCENE_DONALD6_STS }, + { "A6_BAIT", STREAMED_SOUND_CUTSCENE_ASUKA6_BAIT }, + { "A7_ETG", STREAMED_SOUND_CUTSCENE_ASUKA7_ETG }, + { "A8_PS", STREAMED_SOUND_CUTSCENE_ASUKA8_PS }, + { "A9_ASD", STREAMED_SOUND_CUTSCENE_ASUKA9_ASD }, + { "K4_SHI2", STREAMED_SOUND_CUTSCENE_KENJI4_SHI2 }, + { "C1_TEX", STREAMED_SOUND_CUTSCENE_CATALINA1_TEX }, + { "EL_PH1", STREAMED_SOUND_CUTSCENE_ELBURRO1_PH1 }, + { "EL_PH2", STREAMED_SOUND_CUTSCENE_ELBURRO2_PH2 }, + { "EL_PH3", STREAMED_SOUND_CUTSCENE_ELBURRO3_PH3 }, + { "EL_PH4", STREAMED_SOUND_CUTSCENE_ELBURRO4_PH4 }, + { "YD_PH1", STREAMED_SOUND_CUTSCENE_YARDIE_PH1 }, + { "YD_PH2", STREAMED_SOUND_CUTSCENE_YARDIE_PH2 }, + { "YD_PH3", STREAMED_SOUND_CUTSCENE_YARDIE_PH3 }, + { "YD_PH4", STREAMED_SOUND_CUTSCENE_YARDIE_PH4 }, + { "HD_PH1", STREAMED_SOUND_CUTSCENE_HOODS_PH1 }, + { "HD_PH2", STREAMED_SOUND_CUTSCENE_HOODS_PH2 }, + { "HD_PH3", STREAMED_SOUND_CUTSCENE_HOODS_PH3 }, + { "HD_PH4", STREAMED_SOUND_CUTSCENE_HOODS_PH4 }, + { "HD_PH5", STREAMED_SOUND_CUTSCENE_HOODS_PH5 }, + { "MT_PH1", STREAMED_SOUND_CUTSCENE_MARTY_PH1 }, + { "MT_PH2", STREAMED_SOUND_CUTSCENE_MARTY_PH2 }, + { "MT_PH3", STREAMED_SOUND_CUTSCENE_MARTY_PH3 }, + { "MT_PH4", STREAMED_SOUND_CUTSCENE_MARTY_PH4 }, + { NULL, 0 } +}; + +int +FindCutsceneAudioTrackId(const char *szCutsceneName) +{ + for (int i = 0; musicNameIdAssoc[i].szTrackName; i++) { + if (!CGeneral::faststricmp(musicNameIdAssoc[i].szTrackName, szCutsceneName)) + return musicNameIdAssoc[i].iTrackId; + } + return -1; +} + +bool CCutsceneMgr::ms_running; +bool CCutsceneMgr::ms_cutsceneProcessing; +CDirectory *CCutsceneMgr::ms_pCutsceneDir; +CCutsceneObject *CCutsceneMgr::ms_pCutsceneObjects[NUMCUTSCENEOBJECTS]; +int32 CCutsceneMgr::ms_numCutsceneObjs; +bool CCutsceneMgr::ms_loaded; +bool CCutsceneMgr::ms_animLoaded; +bool CCutsceneMgr::ms_useLodMultiplier; +char CCutsceneMgr::ms_cutsceneName[CUTSCENENAMESIZE]; +CAnimBlendAssocGroup CCutsceneMgr::ms_cutsceneAssociations; +CVector CCutsceneMgr::ms_cutsceneOffset; +float CCutsceneMgr::ms_cutsceneTimer; +uint32 CCutsceneMgr::ms_cutsceneLoadStatus; + +RpAtomic * +CalculateBoundingSphereRadiusCB(RpAtomic *atomic, void *data) +{ + float radius = RpAtomicGetBoundingSphere(atomic)->radius; + RwV3d center = RpAtomicGetBoundingSphere(atomic)->center; + + for (RwFrame *frame = RpAtomicGetFrame(atomic); RwFrameGetParent(frame); frame = RwFrameGetParent(frame)) + RwV3dTransformPoints(¢er, ¢er, 1, RwFrameGetMatrix(frame)); + + float size = RwV3dLength(¢er) + radius; + if (size > *(float *)data) + *(float *)data = size; + return atomic; +} + +void +CCutsceneMgr::Initialise(void) +{ + ms_numCutsceneObjs = 0; + ms_loaded = false; + ms_running = false; + ms_animLoaded = false; + ms_cutsceneProcessing = false; + ms_useLodMultiplier = false; + + ms_pCutsceneDir = new CDirectory(CUTSCENEDIRSIZE); + ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR"); +} + +void +CCutsceneMgr::Shutdown(void) +{ + delete ms_pCutsceneDir; +} + +void +CCutsceneMgr::LoadCutsceneData(const char *szCutsceneName) +{ + int file; + uint32 size; + uint32 offset; + CPlayerPed *pPlayerPed; + + ms_cutsceneProcessing = true; + if (!strcasecmp(szCutsceneName, "jb")) + ms_useLodMultiplier = true; + CTimer::Stop(); + + ms_pCutsceneDir->numEntries = 0; + ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR"); + + CStreaming::RemoveUnusedModelsInLoadedList(); + CGame::DrasticTidyUpMemory(true); + + strcpy(ms_cutsceneName, szCutsceneName); + file = CFileMgr::OpenFile("ANIM\\CUTS.IMG", "rb"); + + // Load animations + sprintf(gString, "%s.IFP", szCutsceneName); + if (ms_pCutsceneDir->FindItem(gString, offset, size)) { + CStreaming::MakeSpaceFor(size << 11); + CStreaming::ImGonnaUseStreamingMemory(); + CFileMgr::Seek(file, offset << 11, SEEK_SET); + CAnimManager::LoadAnimFile(file, false); + ms_cutsceneAssociations.CreateAssociations(szCutsceneName); + CStreaming::IHaveUsedStreamingMemory(); + ms_animLoaded = true; + } else { + ms_animLoaded = false; + } + + // Load camera data + sprintf(gString, "%s.DAT", szCutsceneName); + if (ms_pCutsceneDir->FindItem(gString, offset, size)) { + CFileMgr::Seek(file, offset << 11, SEEK_SET); + TheCamera.LoadPathSplines(file); + } + + CFileMgr::CloseFile(file); + + if (CGeneral::faststricmp(ms_cutsceneName, "end")) { + DMAudio.ChangeMusicMode(MUSICMODE_CUTSCENE); + int trackId = FindCutsceneAudioTrackId(szCutsceneName); + if (trackId != -1) { + printf("Start preload audio %s\n", szCutsceneName); + DMAudio.PreloadCutSceneMusic(trackId); + printf("End preload audio %s\n", szCutsceneName); + } + } + + ms_cutsceneTimer = 0.0f; + ms_loaded = true; + ms_cutsceneOffset = CVector(0.0f, 0.0f, 0.0f); + + pPlayerPed = FindPlayerPed(); + CTimer::Update(); + + pPlayerPed->m_pWanted->ClearQdCrimes(); + pPlayerPed->bIsVisible = false; + pPlayerPed->m_fCurrentStamina = pPlayerPed->m_fMaxStamina; + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_CUTSCENE); + CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(true); +} + +void +CCutsceneMgr::SetHeadAnim(const char *animName, CObject *pObject) +{ + CCutsceneHead *pCutsceneHead = (CCutsceneHead*)pObject; + char szAnim[CUTSCENENAMESIZE * 2]; + + sprintf(szAnim, "%s_%s", ms_cutsceneName, animName); + pCutsceneHead->PlayAnimation(szAnim); +} + +void +CCutsceneMgr::FinishCutscene() +{ + CCutsceneMgr::ms_cutsceneTimer = TheCamera.GetCutSceneFinishTime() * 0.001f; + TheCamera.FinishCutscene(); + + FindPlayerPed()->bIsVisible = true; + CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(false); +} + +void +CCutsceneMgr::SetupCutsceneToStart(void) +{ + TheCamera.SetCamCutSceneOffSet(ms_cutsceneOffset); + TheCamera.TakeControlWithSpline(JUMP_CUT); + TheCamera.SetWideScreenOn(); + + ms_cutsceneOffset.z++; + + for (int i = ms_numCutsceneObjs - 1; i >= 0; i--) { + assert(RwObjectGetType(ms_pCutsceneObjects[i]->m_rwObject) == rpCLUMP); + if (CAnimBlendAssociation *pAnimBlendAssoc = RpAnimBlendClumpGetFirstAssociation((RpClump*)ms_pCutsceneObjects[i]->m_rwObject)) { + assert(pAnimBlendAssoc->hierarchy->sequences[0].HasTranslation()); + ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + ((KeyFrameTrans*)pAnimBlendAssoc->hierarchy->sequences[0].GetKeyFrame(0))->translation); + CWorld::Add(ms_pCutsceneObjects[i]); + pAnimBlendAssoc->SetRun(); + } else { + ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset); + } + } + + CTimer::Update(); + CTimer::Update(); + ms_running = true; + ms_cutsceneTimer = 0.0f; +} + +void +CCutsceneMgr::SetCutsceneAnim(const char *animName, CObject *pObject) +{ + CAnimBlendAssociation *pNewAnim; + CAnimBlendClumpData *pAnimBlendClumpData; + + assert(RwObjectGetType(pObject->m_rwObject) == rpCLUMP); + RpAnimBlendClumpRemoveAllAssociations((RpClump*)pObject->m_rwObject); + + pNewAnim = ms_cutsceneAssociations.CopyAnimation(animName); + pNewAnim->SetCurrentTime(0.0f); + pNewAnim->flags |= ASSOC_HAS_TRANSLATION; + pNewAnim->flags &= ~ASSOC_RUNNING; + + pAnimBlendClumpData = *RPANIMBLENDCLUMPDATA(pObject->m_rwObject); + pAnimBlendClumpData->link.Prepend(&pNewAnim->link); +} + +CCutsceneHead * +CCutsceneMgr::AddCutsceneHead(CObject *pObject, int modelId) +{ + CCutsceneHead *pHead = new CCutsceneHead(pObject); + pHead->SetModelIndex(modelId); + CWorld::Add(pHead); + ms_pCutsceneObjects[ms_numCutsceneObjs++] = pHead; + return pHead; +} + +CCutsceneObject * +CCutsceneMgr::CreateCutsceneObject(int modelId) +{ + CBaseModelInfo *pModelInfo; + CColModel *pColModel; + float radius; + RpClump *clump; + CCutsceneObject *pCutsceneObject; + + if (modelId >= MI_CUTOBJ01 && modelId <= MI_CUTOBJ05) { + pModelInfo = CModelInfo::GetModelInfo(modelId); + pColModel = &CTempColModels::ms_colModelCutObj[modelId - MI_CUTOBJ01]; + radius = 0.0f; + + pModelInfo->SetColModel(pColModel); + clump = (RpClump*)pModelInfo->GetRwObject(); + assert(RwObjectGetType((RwObject*)clump) == rpCLUMP); + RpClumpForAllAtomics(clump, CalculateBoundingSphereRadiusCB, &radius); + + pColModel->boundingSphere.radius = radius; + pColModel->boundingBox.min = CVector(-radius, -radius, -radius); + pColModel->boundingBox.max = CVector(radius, radius, radius); + } + + pCutsceneObject = new CCutsceneObject(); + pCutsceneObject->SetModelIndex(modelId); + ms_pCutsceneObjects[ms_numCutsceneObjs++] = pCutsceneObject; + return pCutsceneObject; +} + +void +CCutsceneMgr::DeleteCutsceneData(void) +{ + if (!ms_loaded) return; + + ms_cutsceneProcessing = false; + ms_useLodMultiplier = false; + + for (--ms_numCutsceneObjs; ms_numCutsceneObjs >= 0; ms_numCutsceneObjs--) { + CWorld::Remove(ms_pCutsceneObjects[ms_numCutsceneObjs]); + ms_pCutsceneObjects[ms_numCutsceneObjs]->DeleteRwObject(); + delete ms_pCutsceneObjects[ms_numCutsceneObjs]; + ms_pCutsceneObjects[ms_numCutsceneObjs] = nil; + } + ms_numCutsceneObjs = 0; + + if (ms_animLoaded) + CAnimManager::RemoveLastAnimFile(); + + ms_animLoaded = false; + TheCamera.RestoreWithJumpCut(); + TheCamera.SetWideScreenOff(); + ms_running = false; + ms_loaded = false; + + FindPlayerPed()->bIsVisible = true; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_CUTSCENE); + CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(false); + + if (CGeneral::faststricmp(ms_cutsceneName, "end")) { + DMAudio.StopCutSceneMusic(); + if (CGeneral::faststricmp(ms_cutsceneName, "bet")) + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + } + CTimer::Stop(); + CGame::DrasticTidyUpMemory(TheCamera.GetScreenFadeStatus() == FADE_2); + CTimer::Update(); +} + +void +CCutsceneMgr::Update(void) +{ + enum { + CUTSCENE_LOADING_0 = 0, + CUTSCENE_LOADING_AUDIO, + CUTSCENE_LOADING_2, + CUTSCENE_LOADING_3, + CUTSCENE_LOADING_4 + }; + + switch (ms_cutsceneLoadStatus) { + case CUTSCENE_LOADING_AUDIO: + SetupCutsceneToStart(); + if (CGeneral::faststricmp(ms_cutsceneName, "end")) + DMAudio.PlayPreloadedCutSceneMusic(); + ms_cutsceneLoadStatus++; + break; + case CUTSCENE_LOADING_2: + case CUTSCENE_LOADING_3: + ms_cutsceneLoadStatus++; + break; + case CUTSCENE_LOADING_4: + ms_cutsceneLoadStatus = CUTSCENE_LOADING_0; + break; + default: + break; + } + + if (!ms_running) return; + + ms_cutsceneTimer += CTimer::GetTimeStepNonClippedInSeconds(); + if (CGeneral::faststricmp(ms_cutsceneName, "end") && TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FLYBY && ms_cutsceneLoadStatus == CUTSCENE_LOADING_0) { + if (CPad::GetPad(0)->GetCrossJustDown() + || (CGame::playingIntro && CPad::GetPad(0)->GetStartJustDown()) + || CPad::GetPad(0)->GetLeftMouseJustDown() + || CPad::GetPad(0)->GetEnterJustDown() + || CPad::GetPad(0)->GetCharJustDown(' ')) + FinishCutscene(); + } +} + +bool CCutsceneMgr::HasCutsceneFinished(void) { return TheCamera.GetPositionAlongSpline() == 1.0f; } + diff --git a/src/animation/CutsceneMgr.h b/src/animation/CutsceneMgr.h new file mode 100644 index 0000000..bfdcdb5 --- /dev/null +++ b/src/animation/CutsceneMgr.h @@ -0,0 +1,51 @@ +#pragma once +#include "CutsceneObject.h" + +#define CUTSCENENAMESIZE 8 + +class CDirectory; +class CAnimBlendAssocGroup; +class CCutsceneHead; + +class CCutsceneMgr +{ + static bool ms_running; + static CCutsceneObject *ms_pCutsceneObjects[NUMCUTSCENEOBJECTS]; + + static int32 ms_numCutsceneObjs; + static bool ms_loaded; + static bool ms_animLoaded; + static bool ms_useLodMultiplier; + + static char ms_cutsceneName[CUTSCENENAMESIZE]; + static CAnimBlendAssocGroup ms_cutsceneAssociations; + static CVector ms_cutsceneOffset; + static float ms_cutsceneTimer; + static bool ms_cutsceneProcessing; +public: + static CDirectory *ms_pCutsceneDir; + static uint32 ms_cutsceneLoadStatus; + + static void StartCutsceneProcessing() { ms_cutsceneProcessing = true; } + static bool IsRunning(void) { return ms_running; } + static bool HasLoaded(void) { return ms_loaded; } + static bool IsCutsceneProcessing(void) { return ms_cutsceneProcessing; } + static bool UseLodMultiplier(void) { return ms_useLodMultiplier; } + static CCutsceneObject* GetCutsceneObject(int id) { return ms_pCutsceneObjects[id]; } + static int GetCutsceneTimeInMilleseconds(void) { return 1000.0f * ms_cutsceneTimer; } + static char *GetCutsceneName(void) { return ms_cutsceneName; } + static void SetCutsceneOffset(const CVector& vec) { ms_cutsceneOffset = vec; } + static bool HasCutsceneFinished(void); + + static void Initialise(void); + static void Shutdown(void); + static void LoadCutsceneData(const char *szCutsceneName); + static void FinishCutscene(void); + static void SetHeadAnim(const char *animName, CObject *pObject); + static void SetupCutsceneToStart(void); + static void SetCutsceneAnim(const char *animName, CObject *pObject); + static CCutsceneHead *AddCutsceneHead(CObject *pObject, int modelId); + static CCutsceneObject *CreateCutsceneObject(int modelId); + static void DeleteCutsceneData(void); + static void Update(void); +}; diff --git a/src/animation/FrameUpdate.cpp b/src/animation/FrameUpdate.cpp new file mode 100644 index 0000000..c7d347b --- /dev/null +++ b/src/animation/FrameUpdate.cpp @@ -0,0 +1,445 @@ +#include "common.h" + +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendAssociation.h" +#include "RpAnimBlend.h" + +CAnimBlendClumpData *gpAnimBlendClump; + +// PS2 names without "NonSkinned" +void FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackWithVelocityExtractionNonSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackWith3dVelocityExtractionNonSkinned(AnimBlendFrameData *frame, void *arg); + +void FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackWithVelocityExtractionSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackWith3dVelocityExtractionSkinned(AnimBlendFrameData *frame, void *arg); + + +void +FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + RwMatrix *mat = RwFrameGetMatrix(frame->frame); + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION && + gpAnimBlendClump->velocity2d){ + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION_3D) + FrameUpdateCallBackWith3dVelocityExtractionNonSkinned(frame, arg); + else + FrameUpdateCallBackWithVelocityExtractionNonSkinned(frame, arg); + return; + } + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + (*node)->Update(vec, q, 1.0f-totalBlendAmount); + if((*node)->sequence->HasTranslation()) + pos += vec; +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + RwMatrixSetIdentity(mat); + rot.Normalise(); + rot.Get(mat); + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + mat->pos.x = pos.x; + mat->pos.y = pos.y; + mat->pos.z = pos.z; + mat->pos.x += frame->resetPos.x; + mat->pos.y += frame->resetPos.y; + mat->pos.z += frame->resetPos.z; + } + RwMatrixUpdate(mat); +} + +void +FrameUpdateCallBackWithVelocityExtractionNonSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + float transx = 0.0f, transy = 0.0f; + float curx = 0.0f, cury = 0.0f; + float endx = 0.0f, endy = 0.0f; + bool looped = false; + RwMatrix *mat = RwFrameGetMatrix(frame->frame); + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->sequence->HasTranslation()){ + if((*node)->association->HasTranslation()){ + (*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount); + cury += vec.y; + if((*node)->association->HasXTranslation()) + curx += vec.x; + } + } + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount); +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + if((*node)->sequence->HasTranslation()){ + pos += vec; + if((*node)->association->HasTranslation()){ + transy += vec.y; + if((*node)->association->HasXTranslation()) + transx += vec.x; + looped |= nodelooped; + if(nodelooped){ + (*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount); + endy += vec.y; + if((*node)->association->HasXTranslation()) + endx += vec.x; + } + } + } + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + RwMatrixSetIdentity(mat); + rot.Normalise(); + rot.Get(mat); + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + gpAnimBlendClump->velocity2d->x = transx - curx; + gpAnimBlendClump->velocity2d->y = transy - cury; + if(looped){ + gpAnimBlendClump->velocity2d->x += endx; + gpAnimBlendClump->velocity2d->y += endy; + } + mat->pos.x = pos.x - transx; + mat->pos.y = pos.y - transy; + mat->pos.z = pos.z; + if(mat->pos.z >= -0.8f) { + if(mat->pos.z < -0.4f) + mat->pos.z += (2.5f * mat->pos.z + 2.0f) * frame->resetPos.z; + else + mat->pos.z += frame->resetPos.z; + } + mat->pos.x += frame->resetPos.x; + mat->pos.y += frame->resetPos.y; + } + RwMatrixUpdate(mat); +} + +// original code uses do loops? +void +FrameUpdateCallBackWith3dVelocityExtractionNonSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + CVector trans(0.0f, 0.0f, 0.0f); + CVector cur(0.0f, 0.0f, 0.0f); + CVector end(0.0f, 0.0f, 0.0f); + bool looped = false; + RwMatrix *mat = RwFrameGetMatrix(frame->frame); + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->sequence->HasTranslation()){ + if((*node)->association->HasTranslation()){ + (*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount); + cur += vec; + } + } + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount); +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + if((*node)->sequence->HasTranslation()){ + pos += vec; + if((*node)->association->HasTranslation()){ + trans += vec; + looped |= nodelooped; + if(nodelooped){ + (*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount); + end += vec; + } + } + } + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + RwMatrixSetIdentity(mat); + rot.Normalise(); + rot.Get(mat); + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + *gpAnimBlendClump->velocity3d = trans - cur; + if(looped) + *gpAnimBlendClump->velocity3d += end; + mat->pos.x = (pos - trans).x + frame->resetPos.x; + mat->pos.y = (pos - trans).y + frame->resetPos.y; + mat->pos.z = (pos - trans).z + frame->resetPos.z; + } + RwMatrixUpdate(mat); +} + +#ifdef PED_SKIN + +void +FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + RpHAnimStdInterpFrame *xform = frame->hanimFrame; + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION && + gpAnimBlendClump->velocity2d){ + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION_3D) + FrameUpdateCallBackWith3dVelocityExtractionSkinned(frame, arg); + else + FrameUpdateCallBackWithVelocityExtractionSkinned(frame, arg); + return; + } + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + (*node)->Update(vec, q, 1.0f-totalBlendAmount); + if((*node)->sequence->HasTranslation()) + pos += vec; +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + rot.Normalise(); + xform->q.imag.x = rot.x; + xform->q.imag.y = rot.y; + xform->q.imag.z = rot.z; + xform->q.real = rot.w; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + xform->t.x = pos.x; + xform->t.y = pos.y; + xform->t.z = pos.z; + xform->t.x += frame->resetPos.x; + xform->t.y += frame->resetPos.y; + xform->t.z += frame->resetPos.z; + } +} + +void +FrameUpdateCallBackWithVelocityExtractionSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + float transx = 0.0f, transy = 0.0f; + float curx = 0.0f, cury = 0.0f; + float endx = 0.0f, endy = 0.0f; + bool looped = false; + RpHAnimStdInterpFrame *xform = frame->hanimFrame; + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->sequence->HasTranslation()){ + if((*node)->association->HasTranslation()){ + (*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount); + cury += vec.y; + if((*node)->association->HasXTranslation()) + curx += vec.x; + } + } + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount); +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + if((*node)->sequence->HasTranslation()){ + pos += vec; + if((*node)->association->HasTranslation()){ + transy += vec.y; + if((*node)->association->HasXTranslation()) + transx += vec.x; + looped |= nodelooped; + if(nodelooped){ + (*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount); + endy += vec.y; + if((*node)->association->HasXTranslation()) + endx += vec.x; + } + } + } + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + rot.Normalise(); + xform->q.imag.x = rot.x; + xform->q.imag.y = rot.y; + xform->q.imag.z = rot.z; + xform->q.real = rot.w; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + gpAnimBlendClump->velocity2d->x = transx - curx; + gpAnimBlendClump->velocity2d->y = transy - cury; + if(looped){ + gpAnimBlendClump->velocity2d->x += endx; + gpAnimBlendClump->velocity2d->y += endy; + } + xform->t.x = pos.x - transx; + xform->t.y = pos.y - transy; + xform->t.z = pos.z; + if(xform->t.z >= -0.8f) { + if(xform->t.z < -0.4f) + xform->t.z += (2.5f * xform->t.z + 2.0f) * frame->resetPos.z; + else + xform->t.z += frame->resetPos.z; + } + xform->t.x += frame->resetPos.x; + xform->t.y += frame->resetPos.y; + } +} + +void +FrameUpdateCallBackWith3dVelocityExtractionSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + CVector trans(0.0f, 0.0f, 0.0f); + CVector cur(0.0f, 0.0f, 0.0f); + CVector end(0.0f, 0.0f, 0.0f); + bool looped = false; + RpHAnimStdInterpFrame *xform = frame->hanimFrame; + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->sequence->HasTranslation()){ + if((*node)->association->HasTranslation()){ + (*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount); + cur += vec; + } + } + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount); +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + if((*node)->sequence->HasTranslation()){ + pos += vec; + if((*node)->association->HasTranslation()){ + trans += vec; + looped |= nodelooped; + if(nodelooped){ + (*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount); + end += vec; + } + } + } + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + rot.Normalise(); + xform->q.imag.x = rot.x; + xform->q.imag.y = rot.y; + xform->q.imag.z = rot.z; + xform->q.real = rot.w; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + *gpAnimBlendClump->velocity3d = trans - cur; + if(looped) + *gpAnimBlendClump->velocity3d += end; + xform->t.x = (pos - trans).x + frame->resetPos.x; + xform->t.y = (pos - trans).y + frame->resetPos.y; + xform->t.z = (pos - trans).z + frame->resetPos.z; + } +} + +#endif diff --git a/src/animation/RpAnimBlend.cpp b/src/animation/RpAnimBlend.cpp new file mode 100644 index 0000000..e430e52 --- /dev/null +++ b/src/animation/RpAnimBlend.cpp @@ -0,0 +1,469 @@ +#include "common.h" + +#include "RwHelper.h" +#include "General.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "Bones.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendHierarchy.h" +#include "AnimBlendAssociation.h" +#include "AnimManager.h" +#include "RpAnimBlend.h" +#ifdef PED_SKIN +#include "PedModelInfo.h" +#endif + +RwInt32 ClumpOffset; + +enum +{ + ID_RPANIMBLEND = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0xFD), +}; + +void* +AnimBlendClumpCreate(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + *RWPLUGINOFFSET(CAnimBlendClumpData*, object, offsetInObject) = nil; + return object; +} + +void* +AnimBlendClumpDestroy(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + CAnimBlendClumpData *data; + data = *RPANIMBLENDCLUMPDATA(object); + if(data){ + RpAnimBlendClumpRemoveAllAssociations((RpClump*)object); + delete data; + *RPANIMBLENDCLUMPDATA(object) = nil; + } + return object; +} + +void *AnimBlendClumpCopy(void *dstObject, const void *srcObject, RwInt32 offsetInObject, RwInt32 sizeInObject) { return nil; } + +bool +RpAnimBlendPluginAttach(void) +{ + ClumpOffset = RpClumpRegisterPlugin(sizeof(CAnimBlendClumpData*), ID_RPANIMBLEND, + AnimBlendClumpCreate, AnimBlendClumpDestroy, AnimBlendClumpCopy); + return ClumpOffset >= 0; +} + +CAnimBlendAssociation* +RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc) +{ + if(assoc->link.next) + return CAnimBlendAssociation::FromLink(assoc->link.next); + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc, uint32 mask) +{ + CAnimBlendLink *link; + for(link = assoc->link.next; link; link = link->next){ + assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->flags & mask) + return assoc; + } + return nil; +} + +void +RpAnimBlendAllocateData(RpClump *clump) +{ + *RPANIMBLENDCLUMPDATA(clump) = new CAnimBlendClumpData; +} + + +void +RpAnimBlendClumpSetBlendDeltas(RpClump *clump, uint32 mask, float delta) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(mask == 0 || (assoc->flags & mask)) + assoc->blendDelta = delta; + } +} + +void +RpAnimBlendClumpRemoveAllAssociations(RpClump *clump) +{ + RpAnimBlendClumpRemoveAssociations(clump, 0); +} + +void +RpAnimBlendClumpRemoveAssociations(RpClump *clump, uint32 mask) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + CAnimBlendLink *next; + for(CAnimBlendLink *link = clumpData->link.next; link; link = next){ + next = link->next; + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(mask == 0 || (assoc->flags & mask)) + if(assoc) + delete assoc; + } +} + +RwFrame* +FrameForAllChildrenCountCallBack(RwFrame *frame, void *data) +{ + int *numFrames = (int*)data; + (*numFrames)++; + RwFrameForAllChildren(frame, FrameForAllChildrenCountCallBack, data); + return frame; +} + +RwFrame* +FrameForAllChildrenFillFrameArrayCallBack(RwFrame *frame, void *data) +{ + AnimBlendFrameData **frames = (AnimBlendFrameData**)data; + (*frames)->frame = frame; + (*frames)++; + RwFrameForAllChildren(frame, FrameForAllChildrenFillFrameArrayCallBack, frames); + return frame; +} + +// FrameInitCallBack on PS2 +void +FrameInitCBnonskin(AnimBlendFrameData *frameData, void*) +{ + frameData->flag = 0; + frameData->resetPos = *RwMatrixGetPos(RwFrameGetMatrix(frameData->frame)); +} + +void +FrameInitCBskin(AnimBlendFrameData *frameData, void*) +{ + frameData->flag = 0; +} + +#ifdef PED_SKIN +void +RpAnimBlendClumpInitSkinned(RpClump *clump) +{ + int i; + RwV3d boneTab[64]; + CAnimBlendClumpData *clumpData; + RpAtomic *atomic; + RpSkin *skin; + RpHAnimHierarchy *hier; + int numBones; + + RpAnimBlendAllocateData(clump); + clumpData = *RPANIMBLENDCLUMPDATA(clump); + atomic = IsClumpSkinned(clump); + assert(atomic); + skin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic)); + assert(skin); + numBones = RpSkinGetNumBones(skin); + clumpData->SetNumberOfBones(numBones); + hier = GetAnimHierarchyFromSkinClump(clump); + assert(hier); + memset(boneTab, 0, sizeof(boneTab)); + SkinGetBonePositionsToTable(clump, boneTab); + + AnimBlendFrameData *frames = clumpData->frames; + for(i = 0; i < numBones; i++){ + frames[i].nodeID = HIERNODEID(hier, i); + frames[i].resetPos = boneTab[i]; + frames[i].hanimFrame = (RpHAnimStdInterpFrame*)rpHANIMHIERARCHYGETINTERPFRAME(hier, i); + } + clumpData->ForAllFrames(FrameInitCBskin, nil); + clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION; +} +#endif + +void +RpAnimBlendClumpInitNotSkinned(RpClump *clump) +{ + int numFrames = 0; + CAnimBlendClumpData *clumpData; + RwFrame *root; + AnimBlendFrameData *frames; + + RpAnimBlendAllocateData(clump); + clumpData = *RPANIMBLENDCLUMPDATA(clump); + root = RpClumpGetFrame(clump); + RwFrameForAllChildren(root, FrameForAllChildrenCountCallBack, &numFrames); + clumpData->SetNumberOfFrames(numFrames); + frames = clumpData->frames; + RwFrameForAllChildren(root, FrameForAllChildrenFillFrameArrayCallBack, &frames); + clumpData->ForAllFrames(FrameInitCBnonskin, nil); + clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION; +} + +void +RpAnimBlendClumpInit(RpClump *clump) +{ +#ifdef PED_SKIN + if(IsClumpSkinned(clump)) + RpAnimBlendClumpInitSkinned(clump); + else +#endif + RpAnimBlendClumpInitNotSkinned(clump); +} + +bool +RpAnimBlendClumpIsInitialized(RpClump *clump) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + return clumpData && clumpData->numFrames != 0; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetAssociation(RpClump *clump, uint32 id) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->animId == id) + return assoc; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainAssociation(RpClump *clump, CAnimBlendAssociation **assocRet, float *blendRet) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + CAnimBlendAssociation *mainAssoc = nil; + CAnimBlendAssociation *secondAssoc = nil; + float mainBlend = 0.0f; + float secondBlend = 0.0f; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(assoc->IsPartial()) + continue; + + if(assoc->blendAmount > mainBlend){ + secondBlend = mainBlend; + mainBlend = assoc->blendAmount; + + secondAssoc = mainAssoc; + mainAssoc = assoc; + }else if(assoc->blendAmount > secondBlend){ + secondBlend = assoc->blendAmount; + secondAssoc = assoc; + } + } + if(assocRet) *assocRet = secondAssoc; + if(blendRet) *blendRet = secondBlend; + return mainAssoc; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainPartialAssociation(RpClump *clump) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + CAnimBlendAssociation *mainAssoc = nil; + float mainBlend = 0.0f; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(!assoc->IsPartial()) + continue; + + if(assoc->blendAmount > mainBlend){ + mainBlend = assoc->blendAmount; + mainAssoc = assoc; + } + } + return mainAssoc; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainAssociation_N(RpClump *clump, int n) +{ + int i; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + i = 0; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(assoc->IsPartial()) + continue; + + if(i == n) + return assoc; + i++; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainPartialAssociation_N(RpClump *clump, int n) +{ + int i; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + i = 0; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(!assoc->IsPartial()) + continue; + + if(i == n) + return assoc; + i++; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetFirstAssociation(RpClump *clump, uint32 mask) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->flags & mask) + return assoc; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetFirstAssociation(RpClump *clump) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + if(!RpAnimBlendClumpIsInitialized(clump)) + return nil; + if(clumpData->link.next) + return CAnimBlendAssociation::FromLink(clumpData->link.next); + return nil; +} + +// FillFrameArrayCallBack on PS2 +void +FillFrameArrayCBnonskin(AnimBlendFrameData *frame, void *arg) +{ + AnimBlendFrameData **frames = (AnimBlendFrameData**)arg; + frames[CVisibilityPlugins::GetFrameHierarchyId(frame->frame)] = frame; +} + +#ifdef PED_SKIN +void +RpAnimBlendClumpFillFrameArraySkin(RpClump *clump, AnimBlendFrameData **frames) +{ + int i; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump); + for(i = PED_MID; i < PED_NODE_MAX; i++) + frames[i] = &clumpData->frames[RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(i))]; +} +#endif + +void +RpAnimBlendClumpFillFrameArray(RpClump *clump, AnimBlendFrameData **frames) +{ +#ifdef PED_SKIN + if(IsClumpSkinned(clump)) + RpAnimBlendClumpFillFrameArraySkin(clump, frames); + else +#endif + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FillFrameArrayCBnonskin, frames); +} + +AnimBlendFrameData *pFrameDataFound; + +// FrameFindCallBack on PS2 +void +FrameFindByNameCBnonskin(AnimBlendFrameData *frame, void *arg) +{ + char *nodename = GetFrameNodeName(frame->frame); + if(!CGeneral::faststricmp(nodename, (char*)arg)) + pFrameDataFound = frame; +} + +#ifdef PED_SKIN +void +FrameFindByNameCBskin(AnimBlendFrameData *frame, void *arg) +{ + const char *name = ConvertBoneTag2BoneName(frame->nodeID); + if(name && CGeneral::faststricmp(name, (char*)arg) == 0) + pFrameDataFound = frame; +} +#endif + +AnimBlendFrameData* +RpAnimBlendClumpFindFrame(RpClump *clump, const char *name) +{ + pFrameDataFound = nil; +#ifdef PED_SKIN + if(IsClumpSkinned(clump)) + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBskin, (void*)name); + else +#endif + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBnonskin, (void*)name); + return pFrameDataFound; +} + +void +RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta) +{ + int i; + AnimBlendFrameUpdateData updateData; + float totalLength = 0.0f; + float totalBlend = 0.0f; + CAnimBlendLink *link, *next; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + gpAnimBlendClump = clumpData; + + if(clumpData->link.next == nil) + return; + + // Update blend and get node array + i = 0; + updateData.foobar = 0; + for(link = clumpData->link.next; link; link = next){ + next = link->next; + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->UpdateBlend(timeDelta)){ + CAnimManager::UncompressAnimation(assoc->hierarchy); + updateData.nodes[i++] = assoc->GetNode(0); + if(assoc->flags & ASSOC_MOVEMENT){ + totalLength += assoc->hierarchy->totalLength/assoc->speed * assoc->blendAmount; + totalBlend += assoc->blendAmount; + }else + updateData.foobar = 1; + } + } + updateData.nodes[i] = nil; + +#ifdef PED_SKIN + if(IsClumpSkinned(clump)) + clumpData->ForAllFrames(FrameUpdateCallBackSkinned, &updateData); + else +#endif + clumpData->ForAllFrames(FrameUpdateCallBackNonSkinned, &updateData); + + for(link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + float relSpeed = totalLength == 0.0f ? 1.0f : totalBlend/totalLength; + assoc->UpdateTime(timeDelta, relSpeed); + } + RwFrameUpdateObjects(RpClumpGetFrame(clump)); +} diff --git a/src/animation/RpAnimBlend.h b/src/animation/RpAnimBlend.h new file mode 100644 index 0000000..838c881 --- /dev/null +++ b/src/animation/RpAnimBlend.h @@ -0,0 +1,42 @@ +#pragma once + +class CAnimBlendNode; +class CAnimBlendAssociation; +class CAnimBlendClumpData; +struct AnimBlendFrameData; + +struct AnimBlendFrameUpdateData +{ + int foobar; // TODO: figure out what this actually means + CAnimBlendNode *nodes[16]; +}; + +extern RwInt32 ClumpOffset; +#define RPANIMBLENDCLUMPDATA(o) (RWPLUGINOFFSET(CAnimBlendClumpData*, o, ClumpOffset)) + +bool RpAnimBlendPluginAttach(void); +CAnimBlendAssociation *RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc); +CAnimBlendAssociation *RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc, uint32 mask); +void RpAnimBlendAllocateData(RpClump *clump); + +void RpAnimBlendClumpSetBlendDeltas(RpClump *clump, uint32 mask, float delta); +void RpAnimBlendClumpRemoveAllAssociations(RpClump *clump); +void RpAnimBlendClumpRemoveAssociations(RpClump *clump, uint32 mask); +void RpAnimBlendClumpInit(RpClump *clump); +bool RpAnimBlendClumpIsInitialized(RpClump *clump); +void RpAnimBlendClumpFillFrameArray(RpClump* clump, AnimBlendFrameData** frames); +AnimBlendFrameData *RpAnimBlendClumpFindFrame(RpClump *clump, const char *name); +void FillFrameArrayCallBack(AnimBlendFrameData *frame, void *arg); +CAnimBlendAssociation *RpAnimBlendClumpGetAssociation(RpClump *clump, uint32 id); +CAnimBlendAssociation *RpAnimBlendClumpGetMainAssociation(RpClump *clump, CAnimBlendAssociation **assocRet, float *blendRet); +CAnimBlendAssociation *RpAnimBlendClumpGetMainPartialAssociation(RpClump *clump); +CAnimBlendAssociation *RpAnimBlendClumpGetMainAssociation_N(RpClump *clump, int n); +CAnimBlendAssociation *RpAnimBlendClumpGetMainPartialAssociation_N(RpClump *clump, int n); +CAnimBlendAssociation *RpAnimBlendClumpGetFirstAssociation(RpClump *clump, uint32 mask); +CAnimBlendAssociation *RpAnimBlendClumpGetFirstAssociation(RpClump *clump); +void RpAnimBlendClumpUpdateAnimations(RpClump* clump, float timeDelta); + + +extern CAnimBlendClumpData *gpAnimBlendClump; +void FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg); diff --git a/src/audio/AudioCollision.cpp b/src/audio/AudioCollision.cpp new file mode 100644 index 0000000..d7f2f5a --- /dev/null +++ b/src/audio/AudioCollision.cpp @@ -0,0 +1,401 @@ +#include "common.h" + +#include "DMAudio.h" +#include "Entity.h" +#include "AudioCollision.h" +#include "AudioManager.h" +#include "AudioSamples.h" +#include "SurfaceTable.h" +#include "sampman.h" + +void +cAudioManager::ReportCollision(CEntity *entity1, CEntity *entity2, uint8 surface1, uint8 surface2, float collisionPower, + float velocity) +{ + float distSquared; + CVector v1; + CVector v2; + + if(!m_bIsInitialised || m_nCollisionEntity < 0 || m_bIsPaused || + (velocity < 0.0016f && collisionPower < 0.01f)) + return; + + if(entity1->IsBuilding()) { + v1 = v2 = entity2->GetPosition(); + } else if(entity2->IsBuilding()) { + v1 = v2 = entity1->GetPosition(); + } else { + v1 = entity1->GetPosition(); + v2 = entity2->GetPosition(); + } + CVector pos = (v1 + v2) * 0.5f; + distSquared = GetDistanceSquared(pos); + if(distSquared < SQR(COLLISION_MAX_DIST)) { + m_sCollisionManager.m_sQueue.m_pEntity1 = entity1; + m_sCollisionManager.m_sQueue.m_pEntity2 = entity2; + m_sCollisionManager.m_sQueue.m_bSurface1 = surface1; + m_sCollisionManager.m_sQueue.m_bSurface2 = surface2; + m_sCollisionManager.m_sQueue.m_fIntensity1 = collisionPower; + m_sCollisionManager.m_sQueue.m_fIntensity2 = velocity; + m_sCollisionManager.m_sQueue.m_vecPosition = pos; + m_sCollisionManager.m_sQueue.m_fDistance = distSquared; + m_sCollisionManager.AddCollisionToRequestedQueue(); + } +} + +void +cAudioCollisionManager::AddCollisionToRequestedQueue() +{ + uint32 collisionsIndex; + uint32 i; + + + if (m_bCollisionsInQueue < NUMAUDIOCOLLISIONS) + collisionsIndex = m_bCollisionsInQueue++; + else { + collisionsIndex = m_bIndicesTable[NUMAUDIOCOLLISIONS - 1]; + if (m_sQueue.m_fDistance >= m_asCollisions1[collisionsIndex].m_fDistance) return; + } + + m_asCollisions1[collisionsIndex] = m_sQueue; + + i = 0; + if(collisionsIndex) { + while(m_asCollisions1[m_bIndicesTable[i]].m_fDistance <= m_asCollisions1[collisionsIndex].m_fDistance) { + if(++i >= collisionsIndex) { + m_bIndicesTable[i] = collisionsIndex; + return; + } + } + memmove(&m_bIndicesTable[i + 1], &m_bIndicesTable[i], NUMAUDIOCOLLISIONS - 1 - i); + } + m_bIndicesTable[i] = collisionsIndex; +} + +void +cAudioManager::ServiceCollisions() +{ + int i, j; + bool8 abRepeatedCollision1[NUMAUDIOCOLLISIONS]; + bool8 abRepeatedCollision2[NUMAUDIOCOLLISIONS]; + + m_sQueueSample.m_nEntityIndex = m_nCollisionEntity; + + for (int i = 0; i < NUMAUDIOCOLLISIONS; i++) + abRepeatedCollision1[i] = abRepeatedCollision2[i] = FALSE; + + for (i = 0; i < m_sCollisionManager.m_bCollisionsInQueue; i++) { + for (j = 0; j < NUMAUDIOCOLLISIONS; j++) { + int index = m_sCollisionManager.m_bIndicesTable[i]; + if ((m_sCollisionManager.m_asCollisions1[index].m_pEntity1 == m_sCollisionManager.m_asCollisions2[j].m_pEntity1) + && (m_sCollisionManager.m_asCollisions1[index].m_pEntity2 == m_sCollisionManager.m_asCollisions2[j].m_pEntity2) + && (m_sCollisionManager.m_asCollisions1[index].m_bSurface1 == m_sCollisionManager.m_asCollisions2[j].m_bSurface1) + && (m_sCollisionManager.m_asCollisions1[index].m_bSurface2 == m_sCollisionManager.m_asCollisions2[j].m_bSurface2) + ) { + abRepeatedCollision1[index] = TRUE; + abRepeatedCollision2[j] = TRUE; + m_sCollisionManager.m_asCollisions1[index].m_nBaseVolume = ++m_sCollisionManager.m_asCollisions2[j].m_nBaseVolume; + SetUpLoopingCollisionSound(m_sCollisionManager.m_asCollisions1[index], j); + break; + } + } + } + + for (i = 0; i < NUMAUDIOCOLLISIONS; i++) { + if (!abRepeatedCollision2[i]) { + m_sCollisionManager.m_asCollisions2[i].m_pEntity1 = nil; + m_sCollisionManager.m_asCollisions2[i].m_pEntity2 = nil; + m_sCollisionManager.m_asCollisions2[i].m_bSurface1 = SURFACE_DEFAULT; + m_sCollisionManager.m_asCollisions2[i].m_bSurface2 = SURFACE_DEFAULT; + m_sCollisionManager.m_asCollisions2[i].m_fIntensity2 = 0.0f; + m_sCollisionManager.m_asCollisions2[i].m_fIntensity1 = 0.0f; + m_sCollisionManager.m_asCollisions2[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f); + m_sCollisionManager.m_asCollisions2[i].m_fDistance = 0.0f; + } + } + + for (i = 0; i < m_sCollisionManager.m_bCollisionsInQueue; i++) { + int index = m_sCollisionManager.m_bIndicesTable[i]; + if (!abRepeatedCollision1[index]) { + for (j = 0; j < NUMAUDIOCOLLISIONS; j++) { + if (!abRepeatedCollision2[j]) { + m_sCollisionManager.m_asCollisions2[j].m_nBaseVolume = 1; + m_sCollisionManager.m_asCollisions2[j].m_pEntity1 = m_sCollisionManager.m_asCollisions1[index].m_pEntity1; + m_sCollisionManager.m_asCollisions2[j].m_pEntity2 = m_sCollisionManager.m_asCollisions1[index].m_pEntity2; + m_sCollisionManager.m_asCollisions2[j].m_bSurface1 = m_sCollisionManager.m_asCollisions1[index].m_bSurface1; + m_sCollisionManager.m_asCollisions2[j].m_bSurface2 = m_sCollisionManager.m_asCollisions1[index].m_bSurface2; + break; + } + } + SetUpOneShotCollisionSound(m_sCollisionManager.m_asCollisions1[index]); + SetUpLoopingCollisionSound(m_sCollisionManager.m_asCollisions1[index], j); + } + } + + for (int i = 0; i < NUMAUDIOCOLLISIONS; i++) + m_sCollisionManager.m_bIndicesTable[i] = NUMAUDIOCOLLISIONS; + m_sCollisionManager.m_bCollisionsInQueue = 0; +} + +static const uint32 gOneShotCol[] = {SFX_COL_TARMAC_1, + SFX_COL_TARMAC_1, + SFX_COL_GRASS_1, + SFX_COL_GRAVEL_1, + SFX_COL_MUD_1, + SFX_COL_TARMAC_1, + SFX_COL_CAR_1, + SFX_COL_GRASS_1, + SFX_COL_SCAFFOLD_POLE_1, + SFX_COL_GARAGE_DOOR_1, + SFX_COL_CAR_PANEL_1, + SFX_COL_THICK_METAL_PLATE_1, + SFX_COL_SCAFFOLD_POLE_1, + SFX_COL_LAMP_POST_1, + SFX_COL_HYDRANT_1, + SFX_COL_HYDRANT_1, + SFX_COL_METAL_CHAIN_FENCE_1, + SFX_COL_PED_1, + SFX_COL_SAND_1, + SFX_SPLASH_1, + SFX_COL_WOOD_CRATES_1, + SFX_COL_WOOD_BENCH_1, + SFX_COL_WOOD_SOLID_1, + SFX_COL_GRASS_1, + SFX_COL_GRASS_1, + SFX_COL_VEG_1, + SFX_COL_TARMAC_1, + SFX_COL_CONTAINER_1, + SFX_COL_NEWS_VENDOR_1, + SFX_TYRE_BUMP, + SFX_COL_CARDBOARD_1, + SFX_COL_TARMAC_1, + SFX_COL_GATE}; + +void +cAudioManager::SetUpOneShotCollisionSound(const cAudioCollision &col) +{ + uint16 s1; + uint16 s2; + + uint32 emittingVol; + float ratio; + + static uint16 counter = 28; + + for(int32 i = 0; i < 2; i++) { + if(i) { + s1 = col.m_bSurface2; + s2 = col.m_bSurface1; + } else { + s1 = col.m_bSurface1; + s2 = col.m_bSurface2; + } + ratio = GetCollisionOneShotRatio(s1, col.m_fIntensity1); + if(s1 == SURFACE_CAR && s2 == SURFACE_PED) ratio /= 4.0f; + if(s1 == SURFACE_CAR && ratio < 0.6f) { + s1 = SURFACE_CAR_PANEL; + ratio = Min(1.f, 2.f * ratio); + } + emittingVol = 40 * ratio; + if(emittingVol) { + m_sQueueSample.m_fDistance = Sqrt(col.m_fDistance); + m_sQueueSample.m_nVolume = + ComputeVolume(emittingVol, COLLISION_MAX_DIST, m_sQueueSample.m_fDistance); + if(m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = gOneShotCol[s1]; + switch(m_sQueueSample.m_nSampleIndex) { + case SFX_COL_TARMAC_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[3] % 5; + break; + case SFX_COL_CAR_PANEL_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[0] % 6; + break; + case SFX_COL_LAMP_POST_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[1] % 2; + break; + case SFX_COL_METAL_CHAIN_FENCE_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[3] % 4; + break; + case SFX_COL_PED_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[4] % 5; + break; + case SFX_COL_WOOD_CRATES_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[4] % 4; + break; + case SFX_COL_WOOD_BENCH_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[1] % 4; + break; + case SFX_COL_VEG_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[2] % 5; + break; + case SFX_COL_NEWS_VENDOR_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[2] % 3; + break; + case SFX_COL_CAR_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[1] % 5; + break; + case SFX_COL_CARDBOARD_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[3] % 2; + break; + default: break; + } + switch(s1) { + case SURFACE_GLASS: m_sQueueSample.m_nFrequency = 13500; break; + case SURFACE_GIRDER: m_sQueueSample.m_nFrequency = 8819; break; + case SURFACE_WATER: + m_sQueueSample.m_nFrequency = + 2 * SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + break; + case SURFACE_RUBBER: m_sQueueSample.m_nFrequency = 6000; break; + case SURFACE_PLASTIC: m_sQueueSample.m_nFrequency = 8000; break; + default: + m_sQueueSample.m_nFrequency = + SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + break; + } + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency / 16); + m_sQueueSample.m_nCounter = counter++; + if(counter >= 255) counter = 28; + m_sQueueSample.m_vecPos = col.m_vecPosition; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 11; + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(emittingVol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +void +cAudioManager::SetUpLoopingCollisionSound(const cAudioCollision &col, uint8 counter) +{ + if(col.m_fIntensity2 > 0.0016f) { + uint8 emittingVol = SetLoopingCollisionRequestedSfxFreqAndGetVol(col); + if(emittingVol) { + m_sQueueSample.m_fDistance = Sqrt(col.m_fDistance); + m_sQueueSample.m_nVolume = + ComputeVolume(emittingVol, COLLISION_MAX_DIST, m_sQueueSample.m_fDistance); + if(m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = counter; + m_sQueueSample.m_vecPos = col.m_vecPosition; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(emittingVol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +uint32 +cAudioManager::SetLoopingCollisionRequestedSfxFreqAndGetVol(const cAudioCollision &audioCollision) +{ + uint8 surface1 = audioCollision.m_bSurface1; + uint8 surface2 = audioCollision.m_bSurface2; + int32 vol; + float ratio; + + if(surface1 == SURFACE_GRASS || surface2 == SURFACE_GRASS || surface1 == SURFACE_HEDGE || + surface2 == SURFACE_HEDGE) { + ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f); + m_sQueueSample.m_nSampleIndex = SFX_RAIN; + m_sQueueSample.m_nFrequency = 13000.f * ratio + 35000; + vol = 50.f * ratio; + } else if(surface1 == SURFACE_WATER || surface2 == SURFACE_WATER) { + ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f); + m_sQueueSample.m_nSampleIndex = SFX_BOAT_WATER_LOOP; + m_sQueueSample.m_nFrequency = 6050.f * ratio + 16000; + vol = 30.f * ratio; + } else if(surface1 == SURFACE_GRAVEL || surface2 == SURFACE_GRAVEL || surface1 == SURFACE_MUD_DRY || + surface2 == SURFACE_MUD_DRY || surface1 == SURFACE_SAND || surface2 == SURFACE_SAND) { + ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f); + m_sQueueSample.m_nSampleIndex = SFX_GRAVEL_SKID; + m_sQueueSample.m_nFrequency = 6000.f * ratio + 10000; + vol = 50.f * ratio; + } else if(surface1 == SURFACE_PED || surface2 == SURFACE_PED) { + return 0; + } else { + ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f); + m_sQueueSample.m_nSampleIndex = SFX_SCRAPE_CAR_1; + m_sQueueSample.m_nFrequency = 10000.f * ratio + 10000; + vol = 40.f * ratio; + } + if(audioCollision.m_nBaseVolume < 2) vol = audioCollision.m_nBaseVolume * vol / 2; + return vol; +} + +float +cAudioManager::GetCollisionOneShotRatio(uint32 a, float b) +{ + switch(a) { + case SURFACE_DEFAULT: + case SURFACE_TARMAC: + case SURFACE_PAVEMENT: + case SURFACE_STEEP_CLIFF: + case SURFACE_TRANSPARENT_STONE: return GetCollisionRatio(b, 10.f, 60.f, 50.f); + case SURFACE_GRASS: + case SURFACE_CARDBOARDBOX: + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: return GetCollisionRatio(b, 0.f, 2.f, 2.f); + case SURFACE_CAR: return GetCollisionRatio(b, 6.f, 50.f, 44.f); + case SURFACE_GLASS: + case SURFACE_METAL_CHAIN_FENCE: return GetCollisionRatio(b, 0.1f, 10.f, 9.9f); + case SURFACE_TRANSPARENT_CLOTH: + case SURFACE_THICK_METAL_PLATE: return GetCollisionRatio(b, 30.f, 130.f, 100.f); + case SURFACE_GARAGE_DOOR: return GetCollisionRatio(b, 20.f, 100.f, 80.f); + case SURFACE_CAR_PANEL: return GetCollisionRatio(b, 0.f, 4.f, 4.f); + case SURFACE_SCAFFOLD_POLE: + case SURFACE_METAL_GATE: + case SURFACE_LAMP_POST: return GetCollisionRatio(b, 1.f, 10.f, 9.f); + case SURFACE_FIRE_HYDRANT: return GetCollisionRatio(b, 1.f, 15.f, 14.f); + case SURFACE_GIRDER: return GetCollisionRatio(b, 8.f, 50.f, 42.f); + case SURFACE_PED: return GetCollisionRatio(b, 0.f, 20.f, 20.f); + case SURFACE_SAND: + case SURFACE_WATER: + case SURFACE_RUBBER: + case SURFACE_WHEELBASE: return GetCollisionRatio(b, 0.f, 10.f, 10.f); + case SURFACE_WOOD_CRATES: return GetCollisionRatio(b, 1.f, 4.f, 3.f); + case SURFACE_WOOD_BENCH: return GetCollisionRatio(b, 0.1f, 5.f, 4.9f); + case SURFACE_WOOD_SOLID: return GetCollisionRatio(b, 0.1f, 40.f, 39.9f); + case SURFACE_PLASTIC: return GetCollisionRatio(b, 0.1f, 4.f, 3.9f); + case SURFACE_HEDGE: return GetCollisionRatio(b, 0.f, 0.5f, 0.5f); + case SURFACE_CONTAINER: return GetCollisionRatio(b, 4.f, 40.f, 36.f); + case SURFACE_NEWS_VENDOR: return GetCollisionRatio(b, 0.f, 5.f, 5.f); + default: break; + } + + return 0.f; +} + +float +cAudioManager::GetCollisionLoopingRatio(uint32 a, uint32 b, float c) +{ + return GetCollisionRatio(c, 0.0f, 0.02f, 0.02f); +} + +float +cAudioManager::GetCollisionRatio(float a, float b, float c, float d) +{ + float e; + e = a; + if(a <= b) return 0.0f; + if(c <= a) e = c; + return (e - b) / d; +} diff --git a/src/audio/AudioCollision.h b/src/audio/AudioCollision.h new file mode 100644 index 0000000..a201d50 --- /dev/null +++ b/src/audio/AudioCollision.h @@ -0,0 +1,57 @@ +#pragma once + +#define NUMAUDIOCOLLISIONS 10 + +class CEntity; + +class cAudioCollision +{ +public: + CEntity *m_pEntity1; + CEntity *m_pEntity2; + uint8 m_bSurface1; + uint8 m_bSurface2; + float m_fIntensity1; + float m_fIntensity2; + CVector m_vecPosition; + float m_fDistance; + int32 m_nBaseVolume; + + cAudioCollision() { Reset(); } + + void Reset() + { + m_pEntity1 = nil; + m_pEntity2 = nil; + m_bSurface1 = 0; + m_bSurface2 = 0; + m_fIntensity1 = m_fIntensity2 = 0.0f; + m_vecPosition = CVector(0.0f, 0.0f, 0.0f); + m_fDistance = 0.0f; + } +}; + +VALIDATE_SIZE(cAudioCollision, 40); + +class cAudioCollisionManager +{ +public: + cAudioCollision m_asCollisions1[NUMAUDIOCOLLISIONS]; + cAudioCollision m_asCollisions2[NUMAUDIOCOLLISIONS]; + uint8 m_bIndicesTable[NUMAUDIOCOLLISIONS]; + uint8 m_bCollisionsInQueue; + cAudioCollision m_sQueue; + + cAudioCollisionManager() + { + m_sQueue.Reset(); + + for(int i = 0; i < NUMAUDIOCOLLISIONS; i++) + m_bIndicesTable[i] = NUMAUDIOCOLLISIONS; + + m_bCollisionsInQueue = 0; + } + void AddCollisionToRequestedQueue(); +}; + +VALIDATE_SIZE(cAudioCollisionManager, 852); diff --git a/src/audio/AudioLogic.cpp b/src/audio/AudioLogic.cpp new file mode 100644 index 0000000..2860230 --- /dev/null +++ b/src/audio/AudioLogic.cpp @@ -0,0 +1,9038 @@ +#include "common.h" + +#include "AudioManager.h" +#include "audio_enums.h" + +#include "Automobile.h" +#include "Boat.h" +#include "Bridge.h" +#include "Camera.h" +#include "Cranes.h" +#include "DMAudio.h" +#include "Entity.h" +#include "Explosion.h" +#include "Fire.h" +#include "Garages.h" +#include "General.h" +#include "HandlingMgr.h" +#include "Heli.h" +#include "ModelIndices.h" +#include "MusicManager.h" +#include "Pad.h" +#include "ParticleObject.h" +#include "Ped.h" +#include "Physical.h" +#include "Placeable.h" +#include "Plane.h" +#include "PlayerPed.h" +#include "Pools.h" +#include "Projectile.h" +#include "ProjectileInfo.h" +#include "Replay.h" +#include "Stats.h" +#include "SurfaceTable.h" +#include "Train.h" +#include "Transmission.h" +#include "Vehicle.h" +#include "WaterCannon.h" +#include "Weather.h" +#include "ZoneCull.h" +#include "sampman.h" + +#ifndef GTA_PS2 +#define CHANNEL_PLAYER_VEHICLE_ENGINE m_nActiveSamples +#endif + +enum eVehicleModel { + LANDSTAL, + IDAHO, + STINGER, + LINERUN, + PEREN, + SENTINEL, + PATRIOT, + FIRETRUK, + TRASH, + STRETCH, + MANANA, + INFERNUS, + BLISTA, + PONY, + MULE, + CHEETAH, + AMBULAN, + FBICAR, + MOONBEAM, + ESPERANT, + TAXI, + KURUMA, + BOBCAT, + MRWHOOP, + BFINJECT, + CORPSE, + POLICE, + ENFORCER, + SECURICA, + BANSHEE, + PREDATOR, + BUS, + RHINO, + BARRACKS, + TRAIN, + CHOPPER, + DODO, + COACH, + CABBIE, + STALLION, + RUMPO, + RCBANDIT, + BELLYUP, + MRWONGS, + MAFIA, + YARDIE, + YAKUZA, + DIABLOS, + COLUMB, + HOODS, + AIRTRAIN, + DEADDODO, + SPEEDER, + REEFER, + PANLANT, + FLATBED, + YANKEE, + ESCAPE, + BORGNINE, + TOYZ, + GHOST, + CAR151, + CAR152, + CAR153, + CAR154, + CAR155, + CAR156, + CAR157, + CAR158, + CAR159, + MAX_CARS +}; + +enum +{ + OLD_DOOR = 0, + NEW_DOOR, + TRUCK_DOOR, + BUS_DOOR, +}; + + +struct tVehicleSampleData { + eSfxSample m_nAccelerationSampleIndex; + uint8 m_nBank; + eSfxSample m_nHornSample; + int32 m_nHornFrequency; + uint8 m_nSirenOrAlarmSample; + int32 m_nSirenOrAlarmFrequency; + uint8 m_bDoorType; +}; + +Const static tVehicleSampleData aVehicleSettings[MAX_CARS] = { + {SFX_CAR_REV_2, SFX_BANK_PATHFINDER, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 11487, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_8, SFX_BANK_COBRA, SFX_CAR_HORN_PORSCHE, 11025, SFX_CAR_ALARM_1, 10928, NEW_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 12893, SFX_CAR_ALARM_1, 8941, OLD_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_BMW328, 10706, SFX_CAR_ALARM_1, 11922, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_ALARM_1, 7948, TRUCK_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 29711, SFX_POLICE_SIREN_SLOW, 11556, TRUCK_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 31478, SFX_CAR_ALARM_1, 8941, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_BMW328, 9538, SFX_CAR_ALARM_1, 12220, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10842, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_PORSCHE, SFX_CAR_HORN_BMW328, 12017, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PATHFINDER, SFX_CAR_HORN_JEEP, 22295, SFX_CAR_ALARM_1, 12200, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS2, 18000, SFX_CAR_ALARM_1, 13400, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS, 18286, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_PORSCHE, SFX_CAR_HORN_PORSCHE, 11025, SFX_CAR_ALARM_1, 13600, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 22295, SFX_AMBULANCE_SIREN_SLOW, 8795, TRUCK_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_PORSCHE, 9271, SFX_POLICE_SIREN_SLOW, 16168, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 12170, SFX_CAR_ALARM_1, 8000, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_BUS2, 12345, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PATHFINDER, SFX_CAR_HORN_BMW328, 10796, SFX_CAR_ALARM_1, 8543, NEW_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_PORSCHE, 9271, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PATHFINDER, SFX_CAR_HORN_PICKUP, 10924, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_PICKUP, 11025, SFX_ICE_CREAM_TUNE, 11025, OLD_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_HOTROD, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_ALARM_1, 10000, OLD_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_BMW328, 10706, SFX_POLICE_SIREN_SLOW, 13596, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS, 17260, SFX_POLICE_SIREN_SLOW, 13000, TRUCK_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_PICKUP, 8670, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_8, SFX_BANK_COBRA, SFX_CAR_HORN_PORSCHE, 10400, SFX_CAR_ALARM_1, 10123, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 26513, SFX_POLICE_SIREN_SLOW, 13596, OLD_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_BUS2, 11652, SFX_CAR_ALARM_1, 10554, BUS_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_ALARM_1, 8000, TRUCK_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 28043, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_ALARM_1, 9935, BUS_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CESNA_IDLE, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_BUS, 16291, SFX_CAR_ALARM_1, 7500, BUS_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10842, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10233, SFX_CAR_ALARM_1, 8935, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_PICKUP, 8670, SFX_CAR_ALARM_1, 8935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_PICKUP, 2000, SFX_CAR_ALARM_1, 17000, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_BMW328, 9003, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PATHFINDER, SFX_CAR_HORN_PORSCHE, 12375, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_BUS2, 15554, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_HOTROD, SFX_CAR_HORN_BUS2, 13857, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_HOTROD, SFX_CAR_HORN_PICKUP, 10924, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 20143, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9000, OLD_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 28043, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS, 18286, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10842, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS2, 18000, SFX_CAR_ALARM_1, 13400, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}}; + + +uint32 gPornNextTime; +uint32 gSawMillNextTime; +uint32 gShopNextTime; +uint32 gAirportNextTime; +uint32 gCinemaNextTime; +uint32 gDocksNextTime; +uint32 gHomeNextTime; +uint32 gCellNextTime; +uint32 gNextCryTime; + +void +cAudioManager::PreInitialiseGameSpecificSetup() +{ + BankStartOffset[SFX_BANK_0] = SAMPLEBANK_START; +#ifdef GTA_PS2 + BankStartOffset[SFX_BANK_PACARD] = SFX_CAR_ACCEL_1; + BankStartOffset[SFX_BANK_PATHFINDER] = SFX_CAR_ACCEL_2; + BankStartOffset[SFX_BANK_PORSCHE] = SFX_CAR_ACCEL_3; + BankStartOffset[SFX_BANK_SPIDER] = SFX_CAR_ACCEL_4; + BankStartOffset[SFX_BANK_MERC] = SFX_CAR_ACCEL_5; + BankStartOffset[SFX_BANK_TRUCK] = SFX_CAR_ACCEL_6; + BankStartOffset[SFX_BANK_HOTROD] = SFX_CAR_ACCEL_7; + BankStartOffset[SFX_BANK_COBRA] = SFX_CAR_ACCEL_8; + BankStartOffset[SFX_BANK_NONE] = SFX_CAR_ACCEL_9; + BankStartOffset[SFX_BANK_FRONT_END_MENU] = SFX_PAGE_CHANGE_AND_BACK_LEFT; + BankStartOffset[SFX_BANK_TRAIN] = SFX_TRAIN_STATION_AMBIENCE_LOOP; + BankStartOffset[SFX_BANK_BUILDING_CLUB_1] = SFX_CLUB_1; + BankStartOffset[SFX_BANK_BUILDING_CLUB_2] = SFX_CLUB_2; + BankStartOffset[SFX_BANK_BUILDING_CLUB_3] = SFX_CLUB_3; + BankStartOffset[SFX_BANK_BUILDING_CLUB_4] = SFX_CLUB_4; + BankStartOffset[SFX_BANK_BUILDING_CLUB_5] = SFX_CLUB_5; + BankStartOffset[SFX_BANK_BUILDING_CLUB_6] = SFX_CLUB_6; + BankStartOffset[SFX_BANK_BUILDING_CLUB_7] = SFX_CLUB_7; + BankStartOffset[SFX_BANK_BUILDING_CLUB_8] = SFX_CLUB_8; + BankStartOffset[SFX_BANK_BUILDING_CLUB_9] = SFX_CLUB_9; + BankStartOffset[SFX_BANK_BUILDING_CLUB_10] = SFX_CLUB_10; + BankStartOffset[SFX_BANK_BUILDING_CLUB_11] = SFX_CLUB_11; + BankStartOffset[SFX_BANK_BUILDING_CLUB_12] = SFX_CLUB_12; + BankStartOffset[SFX_BANK_BUILDING_CLUB_RAGGA] = SFX_CLUB_RAGGA; + BankStartOffset[SFX_BANK_BUILDING_STRIP_CLUB_1] = SFX_STRIP_CLUB_1; + BankStartOffset[SFX_BANK_BUILDING_STRIP_CLUB_2] = SFX_STRIP_CLUB_2; + BankStartOffset[SFX_BANK_BUILDING_WORKSHOP] = SFX_WORKSHOP_1; + BankStartOffset[SFX_BANK_BUILDING_PIANO_BAR] = SFX_PIANO_BAR_1; + BankStartOffset[SFX_BANK_BUILDING_SAWMILL] = SFX_SAWMILL_LOOP; + BankStartOffset[SFX_BANK_BUILDING_DOG_FOOD_FACTORY] = SFX_DOG_FOOD_FACTORY; + BankStartOffset[SFX_BANK_BUILDING_LAUNDERETTE] = SFX_LAUNDERETTE_LOOP; + BankStartOffset[SFX_BANK_BUILDING_RESTAURANT_CHINATOWN] = SFX_RESTAURANT_CHINATOWN; + BankStartOffset[SFX_BANK_BUILDING_RESTAURANT_ITALY] = SFX_RESTAURANT_ITALY; + BankStartOffset[SFX_BANK_BUILDING_RESTAURANT_GENERIC_1] = SFX_RESTAURANT_GENERIC_1; + BankStartOffset[SFX_BANK_BUILDING_RESTAURANT_GENERIC_2] = SFX_RESTAURANT_GENERIC_2; + BankStartOffset[SFX_BANK_BUILDING_AIRPORT] = SFX_AIRPORT_ANNOUNCEMENT_1; + BankStartOffset[SFX_BANK_BUILDING_SHOP] = SFX_SHOP_LOOP; + BankStartOffset[SFX_BANK_BUILDING_CINEMA] = SFX_CINEMA_BASS_1; + BankStartOffset[SFX_BANK_BUILDING_DOCKS] = SFX_DOCKS_FOGHORN; + BankStartOffset[SFX_BANK_BUILDING_HOME] = SFX_HOME_1; + BankStartOffset[SFX_BANK_BUILDING_PORN_1] = SFX_PORN_1_LOOP; + BankStartOffset[SFX_BANK_BUILDING_PORN_2] = SFX_PORN_2_LOOP; + BankStartOffset[SFX_BANK_BUILDING_PORN_3] = SFX_PORN_3_LOOP; + BankStartOffset[SFX_BANK_BUILDING_POLICE_BALL] = SFX_POLICE_BALL_1; + BankStartOffset[SFX_BANK_BUILDING_BANK_ALARM] = SFX_BANK_ALARM_1; + BankStartOffset[SFX_BANK_BUILDING_RAVE_INDUSTRIAL] = SFX_RAVE_INDUSTRIAL; + BankStartOffset[SFX_BANK_BUILDING_RAVE_COMMERCIAL] = SFX_RAVE_COMMERCIAL; + BankStartOffset[SFX_BANK_BUILDING_RAVE_SUBURBAN] = SFX_RAVE_SUBURBAN; + BankStartOffset[SFX_BANK_BUILDING_RAVE_COMMERCIAL_2] = SFX_RAVE_COMMERCIAL_2; + BankStartOffset[SFX_BANK_BUILDING_39] = SFX_CLUB_1_1; + BankStartOffset[SFX_BANK_BUILDING_40] = SFX_CLUB_1_2; + BankStartOffset[SFX_BANK_BUILDING_41] = SFX_CLUB_1_3; + BankStartOffset[SFX_BANK_BUILDING_42] = SFX_CLUB_1_4; + BankStartOffset[SFX_BANK_BUILDING_43] = SFX_CLUB_1_5; + BankStartOffset[SFX_BANK_BUILDING_44] = SFX_CLUB_1_6; + BankStartOffset[SFX_BANK_BUILDING_45] = SFX_CLUB_1_7; + BankStartOffset[SFX_BANK_BUILDING_46] = SFX_CLUB_1_8; + BankStartOffset[SFX_BANK_BUILDING_47] = SFX_CLUB_1_9; + BankStartOffset[SFX_BANK_GENERIC_EXTRA] = SFX_EXPLOSION_1; +#endif // GTA_PS2 + BankStartOffset[SFX_BANK_PED_COMMENTS] = SAMPLEBANK_PED_START; +} + +void +cAudioManager::PostInitialiseGameSpecificSetup() +{ + m_nFireAudioEntity = CreateEntity(AUDIOTYPE_FIRE, &gFireManager); + if (m_nFireAudioEntity >= 0) + SetEntityStatus(m_nFireAudioEntity, TRUE); + + m_nCollisionEntity = CreateEntity(AUDIOTYPE_COLLISION, (void *)1); + if (m_nCollisionEntity >= 0) + SetEntityStatus(m_nCollisionEntity, TRUE); + + m_nFrontEndEntity = CreateEntity(AUDIOTYPE_FRONTEND, (void *)1); + if (m_nFrontEndEntity >= 0) + SetEntityStatus(m_nFrontEndEntity, TRUE); + + m_nProjectileEntity = CreateEntity(AUDIOTYPE_PROJECTILE, (void *)1); + if (m_nProjectileEntity >= 0) + SetEntityStatus(m_nProjectileEntity, TRUE); + + m_nWaterCannonEntity = CreateEntity(AUDIOTYPE_WATERCANNON, (void *)1); + if (m_nWaterCannonEntity >= 0) + SetEntityStatus(m_nWaterCannonEntity, TRUE); + + m_nPoliceChannelEntity = CreateEntity(AUDIOTYPE_POLICERADIO, (void *)1); + if (m_nPoliceChannelEntity >= 0) + SetEntityStatus(m_nPoliceChannelEntity, TRUE); + + m_nBridgeEntity = CreateEntity(AUDIOTYPE_BRIDGE, (void *)1); + if (m_nBridgeEntity >= 0) + SetEntityStatus(m_nBridgeEntity, TRUE); + + m_nMissionAudioSampleIndex = NO_SAMPLE; + m_nMissionAudioLoadingStatus = LOADING_STATUS_NOT_LOADED; + m_nMissionAudioPlayStatus = PLAY_STATUS_STOPPED; + m_bIsMissionAudioPlaying = FALSE; + m_bIsMissionAudioAllowedToPlay = FALSE; + m_bIsMissionAudio2D = TRUE; + m_nMissionAudioFramesToPlay = 0; + ResetAudioLogicTimers(CTimer::GetTimeInMilliseconds()); +} + +void +cAudioManager::PreTerminateGameSpecificShutdown() +{ + if (m_nBridgeEntity >= 0) { + DestroyEntity(m_nBridgeEntity); + m_nBridgeEntity = AEHANDLE_NONE; + } + if (m_nPoliceChannelEntity >= 0) { + DestroyEntity(m_nPoliceChannelEntity); + m_nPoliceChannelEntity = AEHANDLE_NONE; + } + if (m_nWaterCannonEntity >= 0) { + DestroyEntity(m_nWaterCannonEntity); + m_nWaterCannonEntity = AEHANDLE_NONE; + } + if (m_nFireAudioEntity >= 0) { + DestroyEntity(m_nFireAudioEntity); + m_nFireAudioEntity = AEHANDLE_NONE; + } + if (m_nCollisionEntity >= 0) { + DestroyEntity(m_nCollisionEntity); + m_nCollisionEntity = AEHANDLE_NONE; + } + if (m_nFrontEndEntity >= 0) { + DestroyEntity(m_nFrontEndEntity); + m_nFrontEndEntity = AEHANDLE_NONE; + } + if (m_nProjectileEntity >= 0) { + DestroyEntity(m_nProjectileEntity); + m_nProjectileEntity = AEHANDLE_NONE; + } +} + +void +cAudioManager::PostTerminateGameSpecificShutdown() +{ + ; +} + +void +cAudioManager::ResetAudioLogicTimers(uint32 timer) +{ + gPornNextTime = timer; + gNextCryTime = timer; + gSawMillNextTime = timer; + gCellNextTime = timer; + gShopNextTime = timer; + gHomeNextTime = timer; + gAirportNextTime = timer; + gDocksNextTime = timer; + gCinemaNextTime = timer; + for (uint32 i = 0; i < m_nAudioEntitiesCount; i++) { + if (m_asAudioEntities[m_aAudioEntityOrderList[i]].m_nType == AUDIOTYPE_PHYSICAL) { + CPed *ped = (CPed *)m_asAudioEntities[m_aAudioEntityOrderList[i]].m_pEntity; + if (ped->IsPed()) { + ped->m_lastSoundStart = timer; + ped->m_soundStart = timer + m_anRandomTable[0] % 3000; + } + } + } + ClearMissionAudio(); + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); +} + +void +cAudioManager::ProcessReverb() +{ +#ifdef EXTERNAL_3D_SOUND + if (SampleManager.UpdateReverb() && m_bDynamicAcousticModelingStatus) { +#ifndef GTA_PS2 + for (uint32 i = 0; i < +#ifdef FIX_BUGS + NUM_CHANNELS_GENERIC +#else + NUM_CHANNELS_GENERIC+1 +#endif + ; + i++) { + if (m_asActiveSamples[i].m_bReverb) + SampleManager.SetChannelReverbFlag(i, TRUE); + } +#endif + } +#else + static uint8 OldVolL = 0; + static uint8 OldVolR = 0; + + uint8 VolL = Min(40, 3 * (20 - TheCamera.SoundDistLeft)) + 20; + uint8 VolR = Min(40, 3 * (20 - TheCamera.SoundDistRight)) + 20; + + uint8 VolUp = 5 * (20 - TheCamera.SoundDistUp); + + VolL = Min(MAX_VOLUME, VolL + VolUp); + VolR = Min(MAX_VOLUME, VolR + VolUp); + + if (OldVolL != VolL || OldVolR != VolR) { + SampleManager.UpdateReverb(VolL, VolR, 100, 15, 80); + OldVolL = VolL; + OldVolR = VolR; + } +#endif +} + +float +cAudioManager::GetDistanceSquared(const CVector &v) +{ + const CVector &c = TheCamera.GetPosition(); + return sq(v.x - c.x) + sq(v.y - c.y) + sq((v.z - c.z) * 0.2f); +} + +void +cAudioManager::CalculateDistance(bool8 &distCalculated, float dist) +{ + if (!distCalculated) { + m_sQueueSample.m_fDistance = Sqrt(dist); + distCalculated = TRUE; + } +} + +void +cAudioManager::ProcessSpecial() +{ + if (m_bIsPaused) { + if (!m_bWasPaused) { + MusicManager.ChangeMusicMode(MUSICMODE_FRONTEND); +#ifdef GTA_PS2 + if (SampleManager.IsSampleBankLoaded(SFX_BANK_FRONT_END_MENU) == LOADING_STATUS_NOT_LOADED) + SampleManager.LoadSampleBank(SFX_BANK_FRONT_END_MENU); +#else + SampleManager.SetEffectsFadeVolume(MAX_VOLUME); + SampleManager.SetMusicFadeVolume(MAX_VOLUME); +#endif + } +#ifdef GTA_PS2 + else { + int8 isBankLoaded = SampleManager.IsSampleBankLoaded(SFX_BANK_FRONT_END_MENU); + if (isBankLoaded != -1 && isBankLoaded == LOADING_STATUS_NOT_LOADED) // what a useless -1 check + SampleManager.LoadSampleBank(SFX_BANK_FRONT_END_MENU); + } +#endif + } else { + if (m_bWasPaused) { + MusicManager.StopFrontEndTrack(); + MusicManager.ChangeMusicMode(MUSICMODE_GAME); + } + CPlayerPed *playerPed = FindPlayerPed(); + if (playerPed) { + if(!playerPed->EnteringCar() && !playerPed->bInVehicle) + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); +#ifdef GTA_PS2 + else { + int8 isBankLoaded = SampleManager.IsSampleBankLoaded(aVehicleSettings[playerPed->m_pMyVehicle->GetModelIndex() - MI_FIRST_VEHICLE].m_nBank); + if (isBankLoaded != -1 && isBankLoaded == LOADING_STATUS_NOT_LOADED) { // again, useless -1 check + if (playerPed->m_pMyVehicle->GetType() == ENTITY_TYPE_VEHICLE // no shit, what else could it be? + && playerPed->m_pMyVehicle->IsCar()) + SampleManager.LoadSampleBank(aVehicleSettings[playerPed->m_pMyVehicle->GetModelIndex() - MI_FIRST_VEHICLE].m_nBank); + } + } +#endif + } + } +} + +void +cAudioManager::ProcessEntity(int32 id) +{ + if (m_asAudioEntities[id].m_bStatus) { + m_sQueueSample.m_nEntityIndex = id; + switch (m_asAudioEntities[id].m_nType) { + case AUDIOTYPE_PHYSICAL: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessPhysical(id); + } + break; + case AUDIOTYPE_EXPLOSION: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessExplosions(id); + } + break; + case AUDIOTYPE_FIRE: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessFires(id); + } + break; + case AUDIOTYPE_WEATHER: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessWeather(id); + } + break; + case AUDIOTYPE_CRANE: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessCrane(); + } + break; + case AUDIOTYPE_SCRIPTOBJECT: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessScriptObject(id); + } + break; + case AUDIOTYPE_BRIDGE: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessBridge(); + } + break; + case AUDIOTYPE_FRONTEND: + m_sQueueSample.m_bReverb = FALSE; + ProcessFrontEnd(); + break; + case AUDIOTYPE_PROJECTILE: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessProjectiles(); + } + break; + case AUDIOTYPE_GARAGE: + if (!m_bIsPaused) + ProcessGarages(); + break; + case AUDIOTYPE_FIREHYDRANT: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessFireHydrant(); + } + break; + case AUDIOTYPE_WATERCANNON: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessWaterCannon(id); + } + break; + default: + return; + } + } +} + +void +cAudioManager::ProcessPhysical(int32 id) +{ + CPhysical *entity = (CPhysical *)m_asAudioEntities[id].m_pEntity; + if (entity) { + switch (entity->GetType()) { + case ENTITY_TYPE_VEHICLE: + ProcessVehicle((CVehicle *)entity); + break; + case ENTITY_TYPE_PED: + ProcessPed(entity); + break; + default: + return; + } + } +} + +enum +{ + RAIN_ON_VEHICLE_MAX_DIST = 22, + RAIN_ON_VEHICLE_VOLUME = 30, + + REVERSE_GEAR_MAX_DIST = 30, + REVERSE_GEAR_VOLUME = 24, + + MODEL_CAR_ENGINE_MAX_DIST = 30, + MODEL_CAR_ENGINE_VOLUME = 90, + + VEHICLE_ROAD_NOISE_MAX_DIST = 95, + VEHICLE_ROAD_NOISE_VOLUME = 30, + + WET_ROAD_NOISE_MAX_DIST = 30, + WET_ROAD_NOISE_VOLUME = 23, + + VEHICLE_ENGINE_MAX_DIST = 50, + VEHICLE_ENGINE_BASE_VOLUME = 80, + VEHICLE_ENGINE_FULL_VOLUME = 120, + + CESNA_IDLE_MAX_DIST = 200, + CESNA_REV_MAX_DIST = 90, + CESNA_VOLUME = 80, + + PLAYER_VEHICLE_ENGINE_VOLUME = 85, + + VEHICLE_SKIDDING_MAX_DIST = 40, + VEHICLE_SKIDDING_VOLUME = 50, + + VEHICLE_HORN_MAX_DIST = 40, + VEHICLE_HORN_VOLUME = 80, + + VEHICLE_SIREN_MAX_DIST = 110, + VEHICLE_SIREN_VOLUME = 80, + + VEHICLE_REVERSE_WARNING_MAX_DIST = 50, + VEHICLE_REVERSE_WARNING_VOLUME = 60, + + VEHICLE_DOORS_MAX_DIST = 40, + VEHICLE_DOORS_VOLUME = 100, + + AIR_BRAKES_MAX_DIST = 30, + AIR_BRAKES_VOLUME = 70, + + ENGINE_DAMAGE_MAX_DIST = 40, + ENGINE_DAMAGE_VOLUME = 6, + ENGINE_DAMAGE_ON_FIRE_VOLUME = 60, + + CAR_BOMB_TICK_MAX_DIST = 40, + CAR_BOMB_TICK_VOLUME = 60, + + VEHICLE_ONE_SHOT_DOOR_MAX_DIST = 50, + VEHICLE_ONE_SHOT_DOOR_OPEN_VOLUME = 122, + VEHICLE_ONE_SHOT_DOOR_CLOSE_VOLUME = 117, + + VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_MAX_DIST = 30, + VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_VOLUME = 60, + + VEHICLE_ONE_SHOT_CAR_JUMP_MAX_DIST = 35, + VEHICLE_ONE_SHOT_CAR_JUMP_VOLUME = 80, + + VEHICLE_ONE_SHOT_CAR_ENGINE_START_MAX_DIST = 40, + VEHICLE_ONE_SHOT_CAR_ENGINE_START_VOLUME = 60, + + VEHICLE_ONE_SHOT_CAR_LIGHT_BREAK_VOLUME = 30, + + VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST = 35, + VEHICLE_ONE_SHOT_CAR_HYDRAULIC_VOLUME = 55, + + VEHICLE_ONE_SHOT_CAR_SPLASH_MAX_DIST = 40, + VEHICLE_ONE_SHOT_CAR_SPLASH_VOLUME = 55, + + VEHICLE_ONE_SHOT_BOAT_SLOWDOWN_MAX_DIST = 50, + + VEHICLE_ONE_SHOT_TRAIN_DOOR_MAX_DIST = 35, + VEHICLE_ONE_SHOT_TRAIN_DOOR_VOLUME = 70, + + VEHICLE_ONE_SHOT_CAR_TANK_TURRET_MAX_DIST = 40, + VEHICLE_ONE_SHOT_CAR_TANK_TURRET_VOLUME = 90, + + VEHICLE_ONE_SHOT_CAR_BOMB_TICK_MAX_DIST = 30, + VEHICLE_ONE_SHOT_CAR_BOMB_TICK_VOLUME = CAR_BOMB_TICK_VOLUME, + + VEHICLE_ONE_SHOT_PLANE_ON_GROUND_MAX_DIST = 180, + VEHICLE_ONE_SHOT_PLANE_ON_GROUND_VOLUME = 75, + + VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_MAX_DIST = 120, + VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_VOLUME = 65, + + VEHICLE_ONE_SHOT_WEAPON_HIT_VEHICLE_MAX_DIST = 40, + VEHICLE_ONE_SHOT_WEAPON_HIT_VEHICLE_VOLUME = 90, + + VEHICLE_ONE_SHOT_BOMB_ARMED_MAX_DIST = 50, + VEHICLE_ONE_SHOT_BOMB_ARMED_VOLUME = 50, + + VEHICLE_ONE_SHOT_WATER_FALL_MAX_DIST = 40, + VEHICLE_ONE_SHOT_WATER_FALL_VOLUME = 90, + + VEHICLE_ONE_SHOT_SPLATTER_MAX_DIST = 40, + VEHICLE_ONE_SHOT_SPLATTER_VOLUME = 55, + + VEHICLE_ONE_SHOT_CAR_PED_COLLISION_MAX_DIST = 40, + + TRAIN_NOISE_FAR_MAX_DIST = 300, + TRAIN_NOISE_NEAR_MAX_DIST = 70, + TRAIN_NOISE_VOLUME = 75, + + BOAT_ENGINE_MAX_DIST = 50, + BOAT_ENGINE_REEFER_IDLE_VOLUME = 50, + BOAT_ENGINE_REEFER_ACCEL_MIN_VOLUME = 15, + BOAT_ENGINE_REEFER_ACCEL_VOLUME_MULT = 100, + + BOAT_ENGINE_LOW_ACCEL_VOLUME = 45, + BOAT_ENGINE_HIGH_ACCEL_MIN_VOLUME = 15, + BOAT_ENGINE_HIGH_ACCEL_VOLUME_MULT = 105, + + BOAT_MOVING_OVER_WATER_MAX_DIST = 50, + BOAT_MOVING_OVER_WATER_VOLUME = 30, + + JUMBO_MAX_DIST = 440, + JUMBO_RUMBLE_SOUND_MAX_DIST = 240, + JUMBO_ENGINE_SOUND_MAX_DIST = 180, + JUMBO_WHINE_SOUND_MAX_DIST = 170, + + PED_HEADPHONES_MAX_DIST = 7, + PED_HEADPHONES_VOLUME = 42, + PED_HEADPHONES_IN_CAR_VOLUME = 10, + + PED_ONE_SHOT_STEP_MAX_DIST = 20, + PED_ONE_SHOT_STEP_VOLUME = 45, + + PED_ONE_SHOT_FALL_MAX_DIST = 30, + PED_ONE_SHOT_FALL_VOLUME = 80, + + PED_ONE_SHOT_PUNCH_MAX_DIST = 30, + PED_ONE_SHOT_PUNCH_VOLUME = 100, + + PED_ONE_SHOT_WEAPON_COLT45_MAX_DIST = 50, + PED_ONE_SHOT_WEAPON_COLT45_VOLUME = 90, + + PED_ONE_SHOT_WEAPON_UZI_MAX_DIST = 80, + PED_ONE_SHOT_WEAPON_UZI_VOLUME = 70, + + PED_ONE_SHOT_WEAPON_SHOTGUN_MAX_DIST = 60, + PED_ONE_SHOT_WEAPON_SHOTGUN_VOLUME = 100, + + PED_ONE_SHOT_WEAPON_AK47_MAX_DIST = 80, + PED_ONE_SHOT_WEAPON_AK47_VOLUME = 70, + + PED_ONE_SHOT_WEAPON_M16_MAX_DIST = 80, + PED_ONE_SHOT_WEAPON_M16_VOLUME = 70, + + PED_ONE_SHOT_WEAPON_SNIPERRIFLE_MAX_DIST = 60, + PED_ONE_SHOT_WEAPON_SNIPERRIFLE_VOLUME = 110, + + PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_MAX_DIST = 90, + PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_VOLUME = 80, + + PED_ONE_SHOT_WEAPON_FLAMETHROWER_MAX_DIST = 60, + PED_ONE_SHOT_WEAPON_FLAMETHROWER_VOLUME = 90, + + PED_ONE_SHOT_WEAPON_RELOAD_MAX_DIST = 30, + PED_ONE_SHOT_WEAPON_RELOAD_VOLUME = 75, + + PED_ONE_SHOT_WEAPON_BULLET_ECHO_MAX_DIST = 80, + PED_ONE_SHOT_WEAPON_BULLET_ECHO_VOLUME = 40, + + PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_MAX_DIST = 60, + PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_VOLUME = 70, + + PED_ONE_SHOT_WEAPON_HIT_PED_MAX_DIST = 30, + PED_ONE_SHOT_WEAPON_HIT_PED_VOLUME = 90, + + PED_ONE_SHOT_SPLASH_MAX_DIST = 40, + PED_ONE_SHOT_SPLASH_PED_VOLUME = 70, + + PED_COMMENT_MAX_DIST = 50, + PED_COMMENT_POLICE_HELI_MAX_DIST = 400, + + EXPLOSION_DEFAULT_MAX_DIST = 400, + EXPLOSION_MOLOTOV_MAX_DIST = 200, + EXPLOSION_MINE_MAX_DIST = 300, + + FIRE_DEFAULT_MAX_DIST = 50, + FIRE_DEFAULT_VOLUME = 80, + FIRE_BUILDING_MAX_DIST = 50, + FIRE_BUILDING_VOLUME = 100, + FIRE_PED_MAX_DIST = 25, + FIRE_PED_VOLUME = 60, + + WATER_CANNON_MAX_DIST = 30, + WATER_CANNON_VOLUME = 50, + + SCRIPT_OBJECT_GATE_MAX_DIST = 40, + + SCRIPT_OBJECT_BULLET_HIT_GROUND_MAX_DIST = 50, + SCRIPT_OBJECT_BULLET_HIT_GROUND_VOLUME = 90, + + SCRIPT_OBJECT_TRAIN_ANNOUNCEMENT_MAX_DIST = 80, + SCRIPT_OBJECT_TRAIN_ANNOUNCEMENT_VOLUME = MAX_VOLUME, + + SCRIPT_OBJECT_PAYPHONE_RINGING_MAX_DIST = 80, + SCRIPT_OBJECT_PAYPHONE_RINGING_VOLUME = 80, + + SCRIPT_OBJECT_GLASS_BREAK_MAX_DIST = 60, + SCRIPT_OBJECT_GLASS_BREAK_LONG_VOLUME = 70, + SCRIPT_OBJECT_GLASS_BREAK_SHORT_VOLUME = 60, + + SCRIPT_OBJECT_GLASS_LIGHT_BREAK_MAX_DIST = 55, + SCRIPT_OBJECT_GLASS_LIGHT_BREAK_VOLUME = 25, + + SCRIPT_OBJECT_BOX_DESTROYED_MAX_DIST = 60, + SCRIPT_OBJECT_BOX_DESTROYED_VOLUME = 80, + + SCRIPT_OBJECT_METAL_COLLISION_VOLUME = 70, + SCRIPT_OBJECT_TIRE_COLLISION_VOLUME = 60, + + SCRIPT_OBJECT_GUNSHELL_MAX_DIST = 20, + SCRIPT_OBJECT_GUNSHELL_VOLUME = 30, + + SCRIPT_OBJECT_SHORT_MAX_DIST = 30, + SCRIPT_OBJECT_LONG_MAX_DIST = 80, + SCRIPT_OBJECT_DEFAULT_VOLUME = MAX_VOLUME, + SCRIPT_OBJECT_RESAURANT_VOLUME = 110, + SCRIPT_OBJECT_BANK_ALARM_VOLUME = 90, + + PORN_CINEMA_SHORT_MAX_DIST = 20, + PORN_CINEMA_LONG_MAX_DIST = SCRIPT_OBJECT_LONG_MAX_DIST, + PORN_CINEMA_VOLUME = SCRIPT_OBJECT_DEFAULT_VOLUME, + PORN_CINEMA_MOAN_VOLUME = 90, + + WORK_SHOP_MAX_DIST = 20, + WORK_SHOP_VOLUME = 30, + + SAWMILL_VOLUME = 30, + SAWMILL_CUT_WOOD_VOLUME = 70, + + LAUNDERETTE_VOLUME = 45, + LAUNDERETTE_SONG_VOLUME = 110, + + SHOP_VOLUME = 30, + SHOP_TILL_VOLUME = 70, + + AIRPORT_VOLUME = 110, + + CINEMA_VOLUME = 30, + DOCKS_VOLUME = 40, + HOME_VOLUME = 40, + POLICE_CELL_BEATING_VOLUME = 55, + + FRONTEND_VOLUME = 110, + + CRANE_MAX_DIST = 80, + CRANE_VOLUME = 100, + + PROJECTILE_ROCKET_MAX_DIST = 90, + PROJECTILE_ROCKET_VOLUME = MAX_VOLUME, + + PROJECTILE_MOLOTOV_MAX_DIST = 30, + PROJECTILE_MOLOTOV_VOLUME = 50, + + GARAGES_MAX_DIST = 80, + GARAGES_VOLUME = 90, + GARAGES_DOOR_VOLUME = 60, + + FIRE_HYDRANT_MAX_DIST = 35, + FIRE_HYDRANT_VOLUME = 40, + + BRIDGE_MOTOR_MAX_DIST = 400, + BRIDGE_MOTOR_VOLUME = MAX_VOLUME, + BRIDGE_MAX_DIST = BRIDGE_MOTOR_MAX_DIST + 50, + + BRIDGE_WARNING_VOLUME = 100, + + MISSION_AUDIO_MAX_DIST = 50, +#ifdef GTA_PS2 + MISSION_AUDIO_VOLUME = MAX_VOLUME, +#else + MISSION_AUDIO_VOLUME = 80, +#endif +}; + +#pragma region VEHICLE AUDIO +bool8 bPlayerJustEnteredCar; + +Const static bool8 HornPattern[8][44] = { + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, + FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, + TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, + TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE}, +}; + +void +cAudioManager::ProcessVehicle(CVehicle *veh) +{ + cVehicleParams params; + + m_sQueueSample.m_vecPos = veh->GetPosition(); + params.m_bDistanceCalculated = FALSE; + params.m_pVehicle = veh; + params.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + params.m_pTransmission = veh->pHandling != nil ? &veh->pHandling->Transmission : nil; + params.m_nIndex = veh->GetModelIndex() - MI_FIRST_VEHICLE; + if (params.m_pVehicle->GetStatus() == STATUS_SIMPLE) + params.m_fVelocityChange = params.m_pVehicle->AutoPilot.m_fMaxTrafficSpeed * 0.02f; + else + params.m_fVelocityChange = DotProduct(params.m_pVehicle->m_vecMoveSpeed, params.m_pVehicle->GetForward()); + switch (params.m_pVehicle->m_vehType) { + case VEHICLE_TYPE_CAR: + UpdateGasPedalAudio((CAutomobile *)veh); + if (params.m_nIndex == RCBANDIT) { + ProcessModelCarEngine(params); + ProcessVehicleOneShots(params); + ((CAutomobile *)veh)->m_fVelocityChangeForAudio = params.m_fVelocityChange; + break; + } + if (params.m_nIndex == DODO) { + if (!ProcessVehicleRoadNoise(params)) { + ProcessVehicleOneShots(params); + ((CAutomobile *)veh)->m_fVelocityChangeForAudio = params.m_fVelocityChange; + break; + } + if (CWeather::WetRoads > 0.0f) + ProcessWetRoadNoise(params); + ProcessVehicleSkidding(params); + } else { + if (!ProcessVehicleRoadNoise(params)) { + ProcessVehicleOneShots(params); + ((CAutomobile *)veh)->m_fVelocityChangeForAudio = params.m_fVelocityChange; + break; + } + ProcessReverseGear(params); + if (CWeather::WetRoads > 0.0f) + ProcessWetRoadNoise(params); + ProcessVehicleSkidding(params); + ProcessVehicleHorn(params); + ProcessVehicleSirenOrAlarm(params); + if (UsesReverseWarning(params.m_nIndex)) + ProcessVehicleReverseWarning(params); + if (HasAirBrakes(params.m_nIndex)) + ProcessAirBrakes(params); + } + ProcessCarBombTick(params); + ProcessVehicleEngine(params); + ProcessEngineDamage(params); + ProcessVehicleDoors(params); + + ProcessVehicleOneShots(params); + ((CAutomobile *)veh)->m_fVelocityChangeForAudio = params.m_fVelocityChange; + break; + case VEHICLE_TYPE_BOAT: + ProcessBoatEngine(params); + ProcessBoatMovingOverWater(params); + ProcessVehicleOneShots(params); + break; + case VEHICLE_TYPE_TRAIN: + ProcessTrainNoise(params); + ProcessVehicleOneShots(params); + break; + case VEHICLE_TYPE_HELI: + ProcessHelicopter(params); + ProcessVehicleOneShots(params); + break; + case VEHICLE_TYPE_PLANE: + ProcessPlane(params); + ProcessVehicleOneShots(params); + break; + default: + break; + } + ProcessRainOnVehicle(params); +} + +void +cAudioManager::ProcessRainOnVehicle(cVehicleParams& params) +{ + if (params.m_fDistance < SQR(RAIN_ON_VEHICLE_MAX_DIST) && CWeather::Rain > 0.01f && (!CCullZones::CamNoRain() || !CCullZones::PlayerNoRain())) { + CVehicle *veh = params.m_pVehicle; + veh->m_bRainAudioCounter++; + if (veh->m_bRainAudioCounter >= 2) { + veh->m_bRainAudioCounter = 0; + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + uint8 Vol = RAIN_ON_VEHICLE_VOLUME * CWeather::Rain; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, RAIN_ON_VEHICLE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = veh->m_bRainSamplesCounter++; + if (veh->m_bRainSamplesCounter > 4) + veh->m_bRainSamplesCounter = 68; + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[1] & 3) + SFX_CAR_RAIN_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 9; + m_sQueueSample.m_nFrequency = m_anRandomTable[1] % 4000 + 28000; + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = RAIN_ON_VEHICLE_MAX_DIST; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +bool8 +cAudioManager::ProcessReverseGear(cVehicleParams& params) +{ + CVehicle *veh; + CAutomobile *automobile; + uint8 Vol; + float modificator; + + if (params.m_fDistance < SQR(REVERSE_GEAR_MAX_DIST)) { + veh = params.m_pVehicle; + if (veh->bEngineOn && (veh->m_fGasPedal < 0.0f || veh->m_nCurrentGear == 0)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + automobile = (CAutomobile *)params.m_pVehicle; + if (automobile->m_nWheelsOnGround > 0) + modificator = params.m_fVelocityChange / params.m_pTransmission->fMaxReverseVelocity; + else { + if (automobile->m_nDriveWheelsOnGround > 0) + automobile->m_fGasPedalAudio *= 0.4f; + modificator = automobile->m_fGasPedalAudio; + } + modificator = ABS(modificator); + Vol = (REVERSE_GEAR_VOLUME * modificator); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, REVERSE_GEAR_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (params.m_pVehicle->m_fGasPedal < 0.0f) { + m_sQueueSample.m_nCounter = 61; + m_sQueueSample.m_nSampleIndex = SFX_REVERSE_GEAR; + } else { + m_sQueueSample.m_nCounter = 62; + m_sQueueSample.m_nSampleIndex = SFX_REVERSE_GEAR_2; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFrequency = (6000 * modificator) + 7000; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = REVERSE_GEAR_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +void +cAudioManager::ProcessModelCarEngine(cVehicleParams& params) +{ + CAutomobile *automobile; + float allowedVelocity; + uint8 Vol; + float velocityChange; + + if (params.m_fDistance < SQR(MODEL_CAR_ENGINE_MAX_DIST)) { + automobile = (CAutomobile *)params.m_pVehicle; + if (automobile->bEngineOn) { + if (automobile->m_nWheelsOnGround > 0) + velocityChange = Abs(params.m_fVelocityChange); + else { + if (automobile->m_nDriveWheelsOnGround > 0) + automobile->m_fGasPedalAudio *= 0.4f; + velocityChange = automobile->m_fGasPedalAudio * params.m_pTransmission->fMaxVelocity; + } + if (velocityChange > 0.001f) { + allowedVelocity = 0.5f * params.m_pTransmission->fMaxVelocity; + if (velocityChange < allowedVelocity) + Vol = (MODEL_CAR_ENGINE_VOLUME * velocityChange / allowedVelocity); + else + Vol = MODEL_CAR_ENGINE_VOLUME; + if (Vol > 0) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, MODEL_CAR_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 2; + m_sQueueSample.m_nSampleIndex = SFX_REMOTE_CONTROLLED_CAR; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = (11025 * velocityChange / params.m_pTransmission->fMaxVelocity + 11025); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = MODEL_CAR_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + } + } +} + +bool8 +cAudioManager::ProcessVehicleRoadNoise(cVehicleParams& params) +{ + uint8 Vol; + uint32 freq; + float multiplier; + int sampleFreq; + float velocity; + + if (params.m_fDistance < SQR(VEHICLE_ROAD_NOISE_MAX_DIST)) { + if ((params.m_pTransmission != nil) && (((CAutomobile*)params.m_pVehicle)->m_nDriveWheelsOnGround > 0)) { + velocity = Abs(params.m_fVelocityChange); + if (velocity > 0.0f) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + Vol = VEHICLE_ROAD_NOISE_VOLUME * Min(1.0f, velocity / (0.5f * params.m_pTransmission->fMaxVelocity)); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_ROAD_NOISE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + if (params.m_pVehicle->m_nSurfaceTouched == SURFACE_WATER) { + m_sQueueSample.m_nSampleIndex = SFX_BOAT_WATER_LOOP; + freq = 6050 * Vol / VEHICLE_ROAD_NOISE_VOLUME + 16000; + } else { + m_sQueueSample.m_nSampleIndex = SFX_ROAD_NOISE; + multiplier = (m_sQueueSample.m_fDistance / VEHICLE_ROAD_NOISE_MAX_DIST) * 0.5f; + sampleFreq = SampleManager.GetSampleBaseFrequency(SFX_ROAD_NOISE); + freq = (sampleFreq * multiplier) + ((3 * sampleFreq) >> 2); + } + m_sQueueSample.m_nFrequency = freq; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ROAD_NOISE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessWetRoadNoise(cVehicleParams& params) +{ + float relativeVelocity; + uint8 Vol; + float multiplier; + int freq; + float velChange; + + if (params.m_fDistance < SQR(WET_ROAD_NOISE_MAX_DIST)) { + if ((params.m_pTransmission != nil) && (((CAutomobile*)params.m_pVehicle)->m_nDriveWheelsOnGround > 0)) { + velChange = Abs(params.m_fVelocityChange); + if (velChange > 0.0f) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + relativeVelocity = Min(1.0f, velChange / (0.5f * params.m_pTransmission->fMaxVelocity)); + Vol = WET_ROAD_NOISE_VOLUME * relativeVelocity * CWeather::WetRoads; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, WET_ROAD_NOISE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_nSampleIndex = SFX_ROAD_NOISE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + multiplier = (m_sQueueSample.m_fDistance / WET_ROAD_NOISE_MAX_DIST) * 0.5f; + freq = SampleManager.GetSampleBaseFrequency(SFX_ROAD_NOISE); + m_sQueueSample.m_nFrequency = freq + freq * multiplier; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = WET_ROAD_NOISE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessVehicleEngine(cVehicleParams& params) +{ + CAutomobile *automobile; + float relativeGearChange; +#ifdef FIX_BUGS + uint32 freq = 0; // uninitialized variable +#else + uint32 freq; +#endif + uint8 Vol; + cTransmission *transmission; + uint8 currentGear; + float modificator; + float traction = 0.0f; + + if (params.m_fDistance < SQR(VEHICLE_ENGINE_MAX_DIST)) { + if (FindPlayerVehicle() == params.m_pVehicle && params.m_pVehicle->GetStatus() == STATUS_WRECKED) { + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + return TRUE; + } + if (params.m_pVehicle->bEngineOn) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + automobile = (CAutomobile *)params.m_pVehicle; + if (params.m_nIndex == DODO) + ProcessCesna(params); + else if (FindPlayerVehicle() == automobile) + ProcessPlayersVehicleEngine(params, automobile); + else { + transmission = params.m_pTransmission; + if (transmission != nil) { + currentGear = params.m_pVehicle->m_nCurrentGear; + if (automobile->m_nWheelsOnGround > 0) { + if (automobile->bIsHandbrakeOn) { + if (params.m_fVelocityChange == 0.0f) + traction = 0.9f; + } else if (params.m_pVehicle->GetStatus() == STATUS_SIMPLE) { + traction = 0.0f; + } else { + switch (transmission->nDriveType) { + case '4': + for (uint8 i = 0; i < ARRAY_SIZE(automobile->m_aWheelState); i++) { + if (automobile->m_aWheelState[i] == WHEEL_STATE_SPINNING) + traction += 0.05f; + } + break; + case 'F': + if (automobile->m_aWheelState[CARWHEEL_FRONT_LEFT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + if (automobile->m_aWheelState[CARWHEEL_FRONT_RIGHT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + break; + case 'R': + if (automobile->m_aWheelState[CARWHEEL_REAR_LEFT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + if (automobile->m_aWheelState[CARWHEEL_REAR_RIGHT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + break; + default: + break; + } + } + if (transmission->fMaxVelocity > 0.0f) { + if (currentGear > 0) { + relativeGearChange = + Min(1.0f, (params.m_fVelocityChange - transmission->Gears[currentGear].fShiftDownVelocity) / transmission->fMaxVelocity * 2.5f); + if (traction == 0.0f && automobile->GetStatus() != STATUS_SIMPLE && + params.m_fVelocityChange < transmission->Gears[1].fShiftUpVelocity) { + traction = 0.7f; + } + modificator = traction * automobile->m_fGasPedalAudio * 0.95f + (1.0f - traction) * relativeGearChange; + } else + modificator = + Min(1.0f, 1.0f - Abs((params.m_fVelocityChange - transmission->Gears[0].fShiftDownVelocity) / transmission->fMaxReverseVelocity)); + } + else + modificator = 0.0f; + } else { + if (automobile->m_nDriveWheelsOnGround > 0) + automobile->m_fGasPedalAudio *= 0.4f; + modificator = automobile->m_fGasPedalAudio; + } + if (currentGear == 0 && automobile->m_nWheelsOnGround > 0) + freq = 13000 * modificator + 14000; + else + freq = 1200 * currentGear + 18000 * modificator + 14000; + if (modificator < 0.75f) + Vol = modificator / 0.75f * (VEHICLE_ENGINE_FULL_VOLUME-VEHICLE_ENGINE_BASE_VOLUME) + VEHICLE_ENGINE_BASE_VOLUME; + else + Vol = VEHICLE_ENGINE_FULL_VOLUME; + } else { + modificator = 0.0f; + Vol = VEHICLE_ENGINE_BASE_VOLUME; + } + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (automobile->GetStatus() == STATUS_SIMPLE) { + if (modificator < 0.02f) { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nBank - CAR_SFX_BANKS_OFFSET + SFX_CAR_IDLE_1; + freq = modificator * 10000 + 22050; + m_sQueueSample.m_nCounter = 52; + } else { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nAccelerationSampleIndex; + m_sQueueSample.m_nCounter = 2; + } + } else { + if (automobile->m_fGasPedal < 0.05f) { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nBank - CAR_SFX_BANKS_OFFSET + SFX_CAR_IDLE_1; + freq = modificator * 10000 + 22050; + m_sQueueSample.m_nCounter = 52; + } else { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nAccelerationSampleIndex; + m_sQueueSample.m_nCounter = 2; + } + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFrequency = freq + 100 * m_sQueueSample.m_nEntityIndex % 1000; + if (m_sQueueSample.m_nSampleIndex == SFX_CAR_IDLE_6 || m_sQueueSample.m_nSampleIndex == SFX_CAR_REV_6) + m_sQueueSample.m_nFrequency >>= 1; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 8; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +void +cAudioManager::UpdateGasPedalAudio(CAutomobile *automobile) +{ + float gasPedal = Abs(automobile->m_fGasPedal); + float gasPedalAudio = automobile->m_fGasPedalAudio; + + if (gasPedalAudio < gasPedal) + automobile->m_fGasPedalAudio = Min(gasPedalAudio + 0.09f, gasPedal); + else + automobile->m_fGasPedalAudio = Max(gasPedalAudio - 0.07f, gasPedal); +} + +void +cAudioManager::PlayerJustGotInCar() +{ + if (m_bIsInitialised) + bPlayerJustEnteredCar = TRUE; +} + +void +cAudioManager::PlayerJustLeftCar(void) +{ + // UNUSED: This is a perfectly empty function. +} + +void +cAudioManager::AddPlayerCarSample(uint8 Vol, uint32 freq, uint32 sample, uint8 bank, uint8 counter, bool8 bLooping) +{ + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = counter; + m_sQueueSample.m_nSampleIndex = sample; +#ifdef GTA_PS2 + m_sQueueSample.m_nBankIndex = bank; +#else + m_sQueueSample.m_nBankIndex = SFX_BANK_0; +#endif // GTA_PS2 + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nFrequency = freq; + if (bLooping) { + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_nFramesToPlay = 8; + } else + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } +} + +void +cAudioManager::ProcessCesna(cVehicleParams& params) +{ + static uint8 nAccel = 0; + +#ifdef THIS_IS_STUPID + ((CAutomobile *)params.m_pVehicle)->Damage.GetEngineStatus(); +#endif + + if (FindPlayerVehicle() == params.m_pVehicle) { + if (params.m_nIndex == DODO) { + if (Pads[0].GetAccelerate() > 0) { + if (nAccel < 60) + nAccel++; + } else + if (nAccel > 0) + nAccel--; + AddPlayerCarSample(PLAYER_VEHICLE_ENGINE_VOLUME * (60 - nAccel) / 60 + 20, 8500 * nAccel / 60 + 17000, SFX_CESNA_IDLE, SFX_BANK_0, 52, TRUE); + AddPlayerCarSample(PLAYER_VEHICLE_ENGINE_VOLUME * nAccel / 60 + 20, 8500 * nAccel / 60 + 17000, SFX_CESNA_REV, SFX_BANK_0, 2, TRUE); + } + } else if (params.m_nIndex == DODO) { + AddPlayerCarSample(PLAYER_VEHICLE_ENGINE_VOLUME + 20, 17000, SFX_CESNA_IDLE, SFX_BANK_0, 52, TRUE); + } else if (params.m_fDistance < SQR(CESNA_IDLE_MAX_DIST)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(CESNA_VOLUME, CESNA_IDLE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 52; + m_sQueueSample.m_nSampleIndex = SFX_CESNA_IDLE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nFrequency = 12500; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_nFramesToPlay = 8; + SET_EMITTING_VOLUME(CESNA_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 8.0f; + m_sQueueSample.m_MaxDistance = CESNA_IDLE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + if (params.m_fDistance < SQR(CESNA_REV_MAX_DIST)) { + m_sQueueSample.m_nVolume = ComputeVolume(CESNA_VOLUME, CESNA_REV_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 2; + m_sQueueSample.m_nSampleIndex = SFX_CESNA_REV; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nFrequency = 25000; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_nFramesToPlay = 4; + SET_EMITTING_VOLUME(CESNA_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 8.0f; + m_sQueueSample.m_MaxDistance = CESNA_REV_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +void +cAudioManager::ProcessPlayersVehicleEngine(cVehicleParams& params, CAutomobile *automobile) +{ + static int32 GearFreqAdj[] = {6000, 6000, 3400, 1200, 0, -1000}; + + cTransmission *transmission; + float relativeVelocityChange; + float accelerationMultipler; + uint8 wheelInUseCounter; + float time; + int baseFreq; + uint8 vol; + int32 freq; + + int freqModifier; + uint32 soundOffset; + uint8 engineSoundType; + int16 accelerateState; + bool8 channelUsed; + uint8 currentGear; + float gasPedalAudio; + CVector pos; + bool8 slowingDown; + + static int16 LastAccel = 0; + static int16 LastBrake = 0; + static uint8 CurrentPretendGear = 1; + static bool8 bLostTractionLastFrame = FALSE; + static bool8 bHandbrakeOnLastFrame = FALSE; + static uint32 nCruising = 0; + static bool8 bAccelSampleStopped = TRUE; + + bool8 lostTraction = FALSE; + bool8 processedAccelSampleStopped = FALSE; + if (bPlayerJustEnteredCar) { + bAccelSampleStopped = TRUE; + bPlayerJustEnteredCar = FALSE; + nCruising = 0; + LastAccel = 0; + bLostTractionLastFrame = FALSE; + LastBrake = 0; + bHandbrakeOnLastFrame = FALSE; + CurrentPretendGear = 1; + } +#ifdef FIX_BUGS // fix acceleration sound on exiting the vehicle + if (CReplay::IsPlayingBack() || FindPlayerPed()->GetPedState() == PED_EXIT_CAR) +#else + if (CReplay::IsPlayingBack()) +#endif + accelerateState = 255 * Clamp(automobile->m_fGasPedal, 0.0f, 1.0f); + else + accelerateState = Pads[0].GetAccelerate(); + + slowingDown = params.m_fVelocityChange < -0.001f; + channelUsed = SampleManager.GetChannelUsedFlag(CHANNEL_PLAYER_VEHICLE_ENGINE); + transmission = params.m_pTransmission; + relativeVelocityChange = 2.0f * params.m_fVelocityChange / transmission->fMaxVelocity; + + accelerationMultipler = Clamp(relativeVelocityChange, 0.0f, 1.0f); + gasPedalAudio = accelerationMultipler; + currentGear = params.m_pVehicle->m_nCurrentGear; + + switch (transmission->nDriveType) + { + case '4': + wheelInUseCounter = 0; + for (uint8 i = 0; i < ARRAY_SIZE(automobile->m_aWheelState); i++) { + if (automobile->m_aWheelState[i] != WHEEL_STATE_NORMAL) + wheelInUseCounter++; + } + if (wheelInUseCounter > 2) + lostTraction = TRUE; + break; + case 'F': + if ((automobile->m_aWheelState[CARWHEEL_FRONT_LEFT] != WHEEL_STATE_NORMAL || automobile->m_aWheelState[CARWHEEL_FRONT_RIGHT] != WHEEL_STATE_NORMAL) && + (automobile->m_aWheelState[CARWHEEL_REAR_LEFT] != WHEEL_STATE_NORMAL || automobile->m_aWheelState[CARWHEEL_REAR_RIGHT] != WHEEL_STATE_NORMAL)) + lostTraction = TRUE; + break; + case 'R': + if ((automobile->m_aWheelState[CARWHEEL_REAR_LEFT] != WHEEL_STATE_NORMAL) || (automobile->m_aWheelState[CARWHEEL_REAR_RIGHT] != WHEEL_STATE_NORMAL)) + lostTraction = TRUE; + break; + default: + break; + } + + if (params.m_fVelocityChange != 0.0f) { + time = params.m_pVehicle->m_vecMoveSpeed.z / params.m_fVelocityChange; + if (time > 0.0f) + freqModifier = -(Min(0.2f, time) * 3000 * 5); + else + freqModifier = -(Max(-0.2f, time) * 3000 * 5); + if (slowingDown) + freqModifier = -freqModifier; + } else + freqModifier = 0; + + engineSoundType = aVehicleSettings[params.m_nIndex].m_nBank; + soundOffset = 3 * (engineSoundType - CAR_SFX_BANKS_OFFSET); + if (accelerateState > 0) { + if (nCruising > 0) { +PlayCruising: + bAccelSampleStopped = TRUE; + if (accelerateState < 150 || automobile->m_nWheelsOnGround == 0 || automobile->bIsHandbrakeOn || lostTraction || + currentGear < params.m_pTransmission->nNumberOfGears - 1) { + nCruising = 0; + } else { + if (accelerateState < 220 || params.m_fVelocityChange + 0.001f < automobile->m_fVelocityChangeForAudio) { + if (nCruising > 3) + nCruising--; + } else + if (nCruising < 800) + nCruising++; + freq = 27 * nCruising + freqModifier + 22050; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + AddPlayerCarSample(PLAYER_VEHICLE_ENGINE_VOLUME, freq, (soundOffset + SFX_CAR_AFTER_ACCEL_1), engineSoundType, 64, TRUE); + } + } else { + if (accelerateState < 150 || automobile->m_nWheelsOnGround == 0 || automobile->bIsHandbrakeOn || lostTraction || + currentGear < 2 && params.m_fVelocityChange - automobile->m_fVelocityChangeForAudio < 0.01f) { // here could be used abs + if (automobile->m_nWheelsOnGround == 0 || automobile->bIsHandbrakeOn || lostTraction) { + if (automobile->m_nWheelsOnGround == 0 && automobile->m_nDriveWheelsOnGround > 0 || + (automobile->bIsHandbrakeOn && !bHandbrakeOnLastFrame || lostTraction && !bLostTractionLastFrame) && automobile->m_nWheelsOnGround > 0) + automobile->m_fGasPedalAudio *= 0.6f; + freqModifier = 0; + baseFreq = (15000 * automobile->m_fGasPedalAudio) + 14000; + vol = (25 * automobile->m_fGasPedalAudio) + 60; + } else { + baseFreq = (8000 * accelerationMultipler) + 16000; + vol = (25 * accelerationMultipler) + 60; + automobile->m_fGasPedalAudio = accelerationMultipler; + } + freq = freqModifier + baseFreq; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + if (channelUsed) { + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + bAccelSampleStopped = TRUE; + } + AddPlayerCarSample(vol, freq, (engineSoundType - CAR_SFX_BANKS_OFFSET + SFX_CAR_REV_1), SFX_BANK_0, 2, TRUE); + } else { + TranslateEntity(&m_sQueueSample.m_vecPos, &pos); +#ifndef EXTERNAL_3D_SOUND + m_sQueueSample.m_nPan = ComputePan(m_sQueueSample.m_fDistance, &pos); +#endif + if (bAccelSampleStopped) { + if (CurrentPretendGear != 1 || currentGear != 2) + CurrentPretendGear = Max(1, currentGear - 1); + processedAccelSampleStopped = TRUE; + bAccelSampleStopped = FALSE; + } + + if (!channelUsed) { + if (!processedAccelSampleStopped) { + if (CurrentPretendGear < params.m_pTransmission->nNumberOfGears - 1) + CurrentPretendGear++; + else { + nCruising = 1; + goto PlayCruising; + } + } + +#ifdef GTA_PS2 + SampleManager.InitialiseChannel(CHANNEL_PLAYER_VEHICLE_ENGINE, soundOffset + SFX_CAR_ACCEL_1, SFX_BANK_0); +#else + if (!SampleManager.InitialiseChannel(CHANNEL_PLAYER_VEHICLE_ENGINE, soundOffset + SFX_CAR_ACCEL_1, SFX_BANK_0)) + return; +#endif + SampleManager.SetChannelLoopCount(CHANNEL_PLAYER_VEHICLE_ENGINE, 1); +#ifndef GTA_PS2 + SampleManager.SetChannelLoopPoints(CHANNEL_PLAYER_VEHICLE_ENGINE, 0, -1); +#endif + } + +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetChannelEmittingVolume(CHANNEL_PLAYER_VEHICLE_ENGINE, PLAYER_VEHICLE_ENGINE_VOLUME); + SampleManager.SetChannel3DPosition(CHANNEL_PLAYER_VEHICLE_ENGINE, pos.x, pos.y, pos.z); + SampleManager.SetChannel3DDistances(CHANNEL_PLAYER_VEHICLE_ENGINE, VEHICLE_ENGINE_MAX_DIST, VEHICLE_ENGINE_MAX_DIST / 4.0f); +#else + + SampleManager.SetChannelVolume(CHANNEL_PLAYER_VEHICLE_ENGINE, ComputeVolume(PLAYER_VEHICLE_ENGINE_VOLUME, VEHICLE_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance)); + SampleManager.SetChannelPan(CHANNEL_PLAYER_VEHICLE_ENGINE, m_sQueueSample.m_nPan); +#endif + freq = GearFreqAdj[CurrentPretendGear] + freqModifier + 22050; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(CHANNEL_PLAYER_VEHICLE_ENGINE, freq * CTimer::GetTimeScale()); +#else + SampleManager.SetChannelFrequency(CHANNEL_PLAYER_VEHICLE_ENGINE, freq); +#endif + if (!channelUsed) { +#if GTA_VERSION >= GTA3_PC_10 + SampleManager.SetChannelReverbFlag(CHANNEL_PLAYER_VEHICLE_ENGINE, m_bDynamicAcousticModelingStatus != FALSE); +#else + SampleManager.SetChannelReverbFlag(CHANNEL_PLAYER_VEHICLE_ENGINE, TRUE); +#endif + SampleManager.StartChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + } + } + } + } else { + if (slowingDown) { + if (channelUsed) { + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + bAccelSampleStopped = TRUE; + } + if (automobile->m_nWheelsOnGround == 0 || automobile->bIsHandbrakeOn || lostTraction) + gasPedalAudio = automobile->m_fGasPedalAudio; + else + gasPedalAudio = Min(1.0f, params.m_fVelocityChange / params.m_pTransmission->fMaxReverseVelocity); + + gasPedalAudio = Max(0.0f, gasPedalAudio); + automobile->m_fGasPedalAudio = gasPedalAudio; + } else if (LastAccel > 0) { + if (channelUsed) { + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + bAccelSampleStopped = TRUE; + } + nCruising = 0; + if (automobile->m_nWheelsOnGround == 0 || automobile->bIsHandbrakeOn || lostTraction || + params.m_fVelocityChange < 0.01f && automobile->m_fGasPedalAudio > 0.2f) { + automobile->m_fGasPedalAudio *= 0.6f; + gasPedalAudio = automobile->m_fGasPedalAudio; + } + if (gasPedalAudio > 0.05f) { + freq = (5000 * (gasPedalAudio - 0.05f) / 0.95f) + 19000; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + AddPlayerCarSample((25 * (gasPedalAudio - 0.05f) / 0.95f) + 40, freq, (soundOffset + SFX_CAR_FINGER_OFF_ACCEL_1), engineSoundType, 63, + FALSE); + } + } + freq = (10000 * gasPedalAudio) + 22050; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + AddPlayerCarSample(110 - (40 * gasPedalAudio), freq, (engineSoundType - CAR_SFX_BANKS_OFFSET + SFX_CAR_IDLE_1), SFX_BANK_0, 52, TRUE); + + CurrentPretendGear = Max(1, currentGear); + } + LastAccel = accelerateState; + + bHandbrakeOnLastFrame = !!automobile->bIsHandbrakeOn; + bLostTractionLastFrame = lostTraction; +} + +bool8 +cAudioManager::ProcessVehicleSkidding(cVehicleParams& params) +{ + CAutomobile *automobile; + cTransmission *transmission; + uint8 Vol; + float newSkidVal = 0.0f; + float skidVal = 0.0f; + + if (params.m_fDistance < SQR(VEHICLE_SKIDDING_MAX_DIST)) { + automobile = (CAutomobile *)params.m_pVehicle; + if (automobile->m_nWheelsOnGround > 0) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + for (uint8 i = 0; i < ARRAY_SIZE(automobile->m_aWheelState); i++) { + if (automobile->m_aWheelState[i] == WHEEL_STATE_NORMAL || automobile->Damage.GetWheelStatus(i) == WHEEL_STATUS_MISSING) + continue; + transmission = params.m_pTransmission; + switch (transmission->nDriveType) { + case '4': + newSkidVal = GetVehicleDriveWheelSkidValue(i, automobile, transmission, params.m_fVelocityChange); + break; + case 'F': + if (i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT) + newSkidVal = GetVehicleDriveWheelSkidValue(i, automobile, transmission, params.m_fVelocityChange); + else + newSkidVal = GetVehicleNonDriveWheelSkidValue(i, automobile, transmission, params.m_fVelocityChange); + break; + case 'R': + if (i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT) + newSkidVal = GetVehicleDriveWheelSkidValue(i, automobile, transmission, params.m_fVelocityChange); + else + newSkidVal = GetVehicleNonDriveWheelSkidValue(i, automobile, transmission, params.m_fVelocityChange); + break; + default: + break; + } + skidVal = Max(skidVal, newSkidVal); + } + + if (skidVal > 0.0f) { + Vol = VEHICLE_SKIDDING_VOLUME * skidVal; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_SKIDDING_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 3; + switch (params.m_pVehicle->m_nSurfaceTouched) { + case SURFACE_GRASS: + case SURFACE_HEDGE: + m_sQueueSample.m_nSampleIndex = SFX_RAIN; + Vol >>= 2; + m_sQueueSample.m_nFrequency = 13000 * skidVal + 35000; + m_sQueueSample.m_nVolume >>= 2; + if (m_sQueueSample.m_nVolume == 0) + return TRUE; + break; + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: + case SURFACE_SAND: + case SURFACE_WATER: + m_sQueueSample.m_nSampleIndex = SFX_GRAVEL_SKID; + m_sQueueSample.m_nFrequency = 6000 * skidVal + 10000; + break; + + default: + m_sQueueSample.m_nSampleIndex = SFX_SKID; + m_sQueueSample.m_nFrequency = 5000 * skidVal + 11000; + break; + } + + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 8; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_SKIDDING_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +float +cAudioManager::GetVehicleDriveWheelSkidValue(uint8 wheel, CAutomobile *automobile, cTransmission *transmission, float velocityChange) +{ + float relativeVelChange = 0.0f; + float gasPedalAudio = automobile->m_fGasPedalAudio; + float velChange; + float relativeVel; + + switch (automobile->m_aWheelState[wheel]) + { + case WHEEL_STATE_SPINNING: + if (gasPedalAudio > 0.4f) + relativeVelChange = (gasPedalAudio - 0.4f) * (5.0f / 3.0f) * 0.75f; + break; + case WHEEL_STATE_SKIDDING: + relativeVelChange = Min(1.0f, Abs(velocityChange) / transmission->fMaxVelocity); + break; + case WHEEL_STATE_FIXED: + relativeVel = gasPedalAudio; + if (relativeVel > 0.4f) + relativeVel = (gasPedalAudio - 0.4f) * (5.0f / 3.0f); + + velChange = Abs(velocityChange); + if (velChange > 0.04f) + relativeVelChange = Min(1.0f, velChange / transmission->fMaxVelocity); + if (relativeVel > relativeVelChange) + relativeVelChange = relativeVel; + + break; + default: + break; + } + + return Max(relativeVelChange, Min(1.0f, Abs(automobile->m_vecTurnSpeed.z) * 20.0f)); +} + +float +cAudioManager::GetVehicleNonDriveWheelSkidValue(uint8 wheel, CAutomobile *automobile, cTransmission *transmission, float velocityChange) +{ + float relativeVelChange = 0.0f; + + if (automobile->m_aWheelState[wheel] == WHEEL_STATE_SKIDDING) + relativeVelChange = Min(1.0f, Abs(velocityChange) / transmission->fMaxVelocity); + + return Max(relativeVelChange, Min(1.0f, Abs(automobile->m_vecTurnSpeed.z) * 20.0f)); +} + +bool8 +cAudioManager::ProcessVehicleHorn(cVehicleParams& params) +{ + if (params.m_fDistance < SQR(VEHICLE_HORN_MAX_DIST)) { + if (params.m_pVehicle->m_bSirenOrAlarm && UsesSirenSwitching(params.m_nIndex)) + return TRUE; + + if (params.m_pVehicle->GetModelIndex() == MI_MRWHOOP) + return TRUE; + + if (params.m_pVehicle->m_nCarHornTimer > 0) { + if (params.m_pVehicle->GetStatus() != STATUS_PLAYER) { + params.m_pVehicle->m_nCarHornTimer = Min(44, params.m_pVehicle->m_nCarHornTimer); + if (params.m_pVehicle->m_nCarHornTimer == 44) + params.m_pVehicle->m_nCarHornPattern = (m_FrameCounter + m_sQueueSample.m_nEntityIndex) & 7; + if (!HornPattern[params.m_pVehicle->m_nCarHornPattern][44 - params.m_pVehicle->m_nCarHornTimer]) + return TRUE; + } + + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(VEHICLE_HORN_VOLUME, VEHICLE_HORN_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 4; + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nHornSample; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFrequency = aVehicleSettings[params.m_nIndex].m_nHornFrequency; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(VEHICLE_HORN_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 5.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_HORN_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::UsesSiren(uint32 model) +{ + switch (model) { + case FIRETRUK: + case AMBULAN: + case FBICAR: + case POLICE: + case ENFORCER: + case PREDATOR: + return TRUE; + default: + return FALSE; + } +} + +bool8 +cAudioManager::UsesSirenSwitching(uint32 model) +{ + switch (model) { + case AMBULAN: + case POLICE: + case ENFORCER: + case PREDATOR: + return TRUE; + default: + return FALSE; + } +} + +bool8 +cAudioManager::ProcessVehicleSirenOrAlarm(cVehicleParams& params) +{ + if (params.m_fDistance < SQR(VEHICLE_SIREN_MAX_DIST)) { + CVehicle *veh = params.m_pVehicle; + if (veh->m_bSirenOrAlarm || veh->IsAlarmOn()) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(VEHICLE_SIREN_VOLUME, VEHICLE_SIREN_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 5; + if (UsesSiren(params.m_nIndex)) { + if (params.m_pVehicle->GetStatus() == STATUS_ABANDONED) + return TRUE; + if (veh->m_nCarHornTimer > 0 && params.m_nIndex != FIRETRUK) { + m_sQueueSample.m_nSampleIndex = SFX_SIREN_FAST; + if (params.m_nIndex == FBICAR) + m_sQueueSample.m_nFrequency = 16113; + else + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SIREN_FAST); + m_sQueueSample.m_nCounter = 60; + } else { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nSirenOrAlarmSample; + m_sQueueSample.m_nFrequency = aVehicleSettings[params.m_nIndex].m_nSirenOrAlarmFrequency; + } + } else { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nSirenOrAlarmSample; + m_sQueueSample.m_nFrequency = aVehicleSettings[params.m_nIndex].m_nSirenOrAlarmFrequency; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(VEHICLE_SIREN_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 7.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_SIREN_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::UsesReverseWarning(uint32 model) +{ + return model == LINERUN || model == FIRETRUK || model == TRASH || model == BUS || model == COACH; +} + +bool8 +cAudioManager::ProcessVehicleReverseWarning(cVehicleParams& params) +{ + CVehicle *veh = params.m_pVehicle; + + if (params.m_fDistance < SQR(VEHICLE_REVERSE_WARNING_MAX_DIST)) { + if (veh->bEngineOn && veh->m_fGasPedal < 0.0f) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(VEHICLE_REVERSE_WARNING_VOLUME, VEHICLE_REVERSE_WARNING_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 12; + m_sQueueSample.m_nSampleIndex = SFX_REVERSE_WARNING; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFrequency = (100 * m_sQueueSample.m_nEntityIndex % 1024) + SampleManager.GetSampleBaseFrequency(SFX_REVERSE_WARNING); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(VEHICLE_REVERSE_WARNING_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_REVERSE_WARNING_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessVehicleDoors(cVehicleParams& params) +{ + CAutomobile *automobile; + int8 doorState; + uint8 Vol; + float velocity; + + if (params.m_fDistance < SQR(VEHICLE_DOORS_MAX_DIST)) { + automobile = (CAutomobile *)params.m_pVehicle; + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + for (uint8 i = 0; i < ARRAY_SIZE(automobile->Doors); i++) { + if (automobile->Damage.GetDoorStatus(i) == DOOR_STATUS_SWINGING) { + doorState = automobile->Doors[i].m_nDoorState; + if (doorState == DOORST_OPEN || doorState == DOORST_CLOSED) { + velocity = Min(0.3f, Abs(automobile->Doors[i].m_fAngVel)); + if (velocity > 0.0035f) { + Vol = (VEHICLE_DOORS_VOLUME * velocity / 0.3f); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_DOORS_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i + 6; + m_sQueueSample.m_nSampleIndex = m_anRandomTable[1] % 6 + SFX_COL_CAR_PANEL_1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex) + RandomDisplacement(1000); + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 10; + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_fSpeedMultiplier = 1.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_DOORS_MAX_DIST; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(TRUE); + AddSampleToRequestedQueue(); + } + } + } + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessAirBrakes(cVehicleParams& params) +{ + CAutomobile *automobile; + uint8 Vol; + + if (params.m_fDistance < SQR(AIR_BRAKES_MAX_DIST)) { + automobile = (CAutomobile *)params.m_pVehicle; + if (automobile->bEngineOn && (automobile->m_fVelocityChangeForAudio >= 0.025f && params.m_fVelocityChange < 0.025f || + automobile->m_fVelocityChangeForAudio <= -0.025f && params.m_fVelocityChange > 0.025f)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + Vol = m_anRandomTable[0] % 10 + AIR_BRAKES_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, AIR_BRAKES_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 13; + m_sQueueSample.m_nSampleIndex = SFX_AIR_BRAKES; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_AIR_BRAKES); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 10; + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = AIR_BRAKES_MAX_DIST; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::HasAirBrakes(uint32 model) +{ + return model == LINERUN || model == FIRETRUK || model == TRASH || model == BUS || model == COACH; +} + +bool8 +cAudioManager::ProcessEngineDamage(cVehicleParams& params) +{ + CAutomobile *veh; + uint8 engineStatus; + uint8 Vol; + + if (params.m_fDistance < SQR(ENGINE_DAMAGE_MAX_DIST)) { + veh = (CAutomobile *)params.m_pVehicle; + if (veh->bEngineOn) { + engineStatus = veh->Damage.GetEngineStatus(); + if (engineStatus > 250 || engineStatus < 100) + return TRUE; + if (engineStatus >= 225) { + Vol = ENGINE_DAMAGE_ON_FIRE_VOLUME; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE); + } else { + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_TAXI; + Vol = ENGINE_DAMAGE_VOLUME; + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_nFrequency = 40000; + } + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, ENGINE_DAMAGE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 28; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = ENGINE_DAMAGE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessCarBombTick(cVehicleParams& params) +{ + CAutomobile *automobile; + + if (params.m_fDistance < SQR(CAR_BOMB_TICK_MAX_DIST)) { + automobile = (CAutomobile *)params.m_pVehicle; + if (automobile->bEngineOn && automobile->m_bombType == CARBOMB_TIMEDACTIVE) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(CAR_BOMB_TICK_VOLUME, CAR_BOMB_TICK_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 35; + m_sQueueSample.m_nSampleIndex = SFX_COUNTDOWN; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_COUNTDOWN); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(CAR_BOMB_TICK_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = CAR_BOMB_TICK_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +void +cAudioManager::ProcessVehicleOneShots(cVehicleParams& params) +{ + int16 event; + uint8 Vol; + float eventRelVol; + float eventVol; + bool8 bLoop; + float maxDist; + + static uint8 WaveIndex = 41; + static uint8 GunIndex = 53; + + for (uint16 i = 0; i < m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents; i++) { + bLoop = FALSE; + SET_SOUND_REFLECTION(FALSE); + event = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]; + switch (event) { + case SOUND_CAR_WINDSHIELD_CRACK: + maxDist = SQR(VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_MAX_DIST); + m_sQueueSample.m_nSampleIndex = SFX_GLASS_CRACK; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 68; + Vol = m_anRandomTable[1] % 30 + VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_GLASS_CRACK); + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_MAX_DIST; + break; + case SOUND_CAR_DOOR_OPEN_BONNET: + case SOUND_CAR_DOOR_OPEN_BUMPER: + case SOUND_CAR_DOOR_OPEN_FRONT_LEFT: + case SOUND_CAR_DOOR_OPEN_FRONT_RIGHT: + case SOUND_CAR_DOOR_OPEN_BACK_LEFT: + case SOUND_CAR_DOOR_OPEN_BACK_RIGHT: + maxDist = SQR(VEHICLE_ONE_SHOT_DOOR_MAX_DIST); + Vol = m_anRandomTable[1] % (MAX_VOLUME - VEHICLE_ONE_SHOT_DOOR_CLOSE_VOLUME) + VEHICLE_ONE_SHOT_DOOR_CLOSE_VOLUME; + switch (aVehicleSettings[params.m_nIndex].m_bDoorType) { + case OLD_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_OLD_CAR_DOOR_OPEN; + break; + case NEW_DOOR: + default: + m_sQueueSample.m_nSampleIndex = SFX_NEW_CAR_DOOR_OPEN; + break; + case TRUCK_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_TRUCK_DOOR_OPEN; + break; + case BUS_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_AIR_BRAKES; + break; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; +#ifdef THIS_IS_STUPID + m_sQueueSample.m_nCounter = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i] + 10; +#else + m_sQueueSample.m_nCounter = event + 10; +#endif + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_DOOR_MAX_DIST; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_CAR_DOOR_CLOSE_BONNET: + case SOUND_CAR_DOOR_CLOSE_BUMPER: + case SOUND_CAR_DOOR_CLOSE_FRONT_LEFT: + case SOUND_CAR_DOOR_CLOSE_FRONT_RIGHT: + case SOUND_CAR_DOOR_CLOSE_BACK_LEFT: + case SOUND_CAR_DOOR_CLOSE_BACK_RIGHT: + maxDist = SQR(VEHICLE_ONE_SHOT_DOOR_MAX_DIST); + Vol = m_anRandomTable[2] % (MAX_VOLUME - VEHICLE_ONE_SHOT_DOOR_OPEN_VOLUME) + VEHICLE_ONE_SHOT_DOOR_OPEN_VOLUME; + switch (aVehicleSettings[params.m_nIndex].m_bDoorType) { + case OLD_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_OLD_CAR_DOOR_CLOSE; + break; + case NEW_DOOR: + default: + m_sQueueSample.m_nSampleIndex = SFX_NEW_CAR_DOOR_CLOSE; + break; + case TRUCK_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_TRUCK_DOOR_CLOSE; + break; + case BUS_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_AIR_BRAKES; + break; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; +#ifdef THIS_IS_STUPID + m_sQueueSample.m_nCounter = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i] + 22; +#else + m_sQueueSample.m_nCounter = event + 22; +#endif + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_DOOR_MAX_DIST; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_CAR_ENGINE_START: + Vol = VEHICLE_ONE_SHOT_CAR_ENGINE_START_VOLUME; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_ENGINE_START_MAX_DIST); + m_sQueueSample.m_nSampleIndex = SFX_CAR_STARTER; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 33; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_STARTER); + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_ENGINE_START_MAX_DIST; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_WEAPON_HIT_VEHICLE: + m_sQueueSample.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % ARRAY_SIZE(m_anRandomTable)] % 6 + SFX_BULLET_CAR_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 34; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WEAPON_HIT_VEHICLE_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_WEAPON_HIT_VEHICLE_MAX_DIST); + Vol = m_anRandomTable[3] % 20 + VEHICLE_ONE_SHOT_WEAPON_HIT_VEHICLE_VOLUME; + break; + case SOUND_BOMB_TIMED_ACTIVATED: + case SOUND_55: + case SOUND_BOMB_ONIGNITION_ACTIVATED: + case SOUND_BOMB_TICK: + m_sQueueSample.m_nSampleIndex = SFX_ARM_BOMB; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 36; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_ARM_BOMB); + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_BOMB_ARMED_MAX_DIST; + SET_SOUND_REFLECTION(TRUE); + Vol = VEHICLE_ONE_SHOT_BOMB_ARMED_VOLUME; + maxDist = SQR(VEHICLE_ONE_SHOT_BOMB_ARMED_MAX_DIST); + break; + case SOUND_CAR_LIGHT_BREAK: + m_sQueueSample.m_nSampleIndex = SFX_GLASS_SHARD_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 37; + m_sQueueSample.m_nFrequency = 9 * SampleManager.GetSampleBaseFrequency(SFX_GLASS_SHARD_1) / 10; + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 3); + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_MAX_DIST); + Vol = m_anRandomTable[4] % 10 + VEHICLE_ONE_SHOT_CAR_LIGHT_BREAK_VOLUME; + break; + case SOUND_PLANE_ON_GROUND: + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_LAND_WHEELS; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 81; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_JUMBO_LAND_WHEELS); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_PLANE_ON_GROUND_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_PLANE_ON_GROUND_MAX_DIST); + Vol = m_anRandomTable[4] % 25 + VEHICLE_ONE_SHOT_PLANE_ON_GROUND_VOLUME; + break; + case SOUND_CAR_JERK: + m_sQueueSample.m_nSampleIndex = SFX_SHAG_SUSPENSION; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 87; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SHAG_SUSPENSION); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 3); + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST); + Vol = m_anRandomTable[1] % 15 + VEHICLE_ONE_SHOT_CAR_HYDRAULIC_VOLUME; + break; + case SOUND_CAR_HYDRAULIC_1: + case SOUND_CAR_HYDRAULIC_2: + if (event == SOUND_CAR_HYDRAULIC_1) + m_sQueueSample.m_nFrequency = 15600; + else + m_sQueueSample.m_nFrequency = 13118; + m_sQueueSample.m_nSampleIndex = SFX_SUSPENSION_FAST_MOVE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 51; + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 3); + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST); + Vol = m_anRandomTable[0] % 15 + VEHICLE_ONE_SHOT_CAR_HYDRAULIC_VOLUME; + break; + case SOUND_CAR_HYDRAULIC_3: + m_sQueueSample.m_nSampleIndex = SFX_SUSPENSION_SLOW_MOVE_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 86; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SUSPENSION_SLOW_MOVE_LOOP); + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST; + m_sQueueSample.m_nFramesToPlay = 7; + bLoop = TRUE; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST); + Vol = m_anRandomTable[0] % 15 + VEHICLE_ONE_SHOT_CAR_HYDRAULIC_VOLUME; + break; + case SOUND_WATER_FALL: + m_sQueueSample.m_nSampleIndex = SFX_SPLASH_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 15; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 16000; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WATER_FALL_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_WATER_FALL_MAX_DIST); + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[4] % 20 + VEHICLE_ONE_SHOT_WATER_FALL_VOLUME; + break; + case SOUND_CAR_BOMB_TICK: + m_sQueueSample.m_nSampleIndex = SFX_BOMB_BEEP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 80; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_BOMB_BEEP); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_BOMB_TICK_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_BOMB_TICK_MAX_DIST); + SET_SOUND_REFLECTION(TRUE); + Vol = VEHICLE_ONE_SHOT_CAR_BOMB_TICK_VOLUME; + break; + case SOUND_CAR_SPLASH: + eventVol = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + if (eventVol <= 300) + continue; + if (eventVol > 1200) + m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] = 1200; + eventRelVol = (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] - 300) / (1200 - 300); + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[0] % 2) + SFX_BOAT_SPLASH_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = WaveIndex++; + if (WaveIndex > 46) + WaveIndex = 41; + m_sQueueSample.m_nFrequency = (7000 * eventRelVol) + 6000; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_SPLASH_MAX_DIST; + Vol = (VEHICLE_ONE_SHOT_CAR_SPLASH_VOLUME * eventRelVol); + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_SPLASH_MAX_DIST); + break; + case SOUND_BOAT_SLOWDOWN: + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_THUMB_OFF; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 47; + m_sQueueSample.m_nFrequency = RandomDisplacement(600) + SampleManager.GetSampleBaseFrequency(SFX_POLICE_BOAT_THUMB_OFF); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_BOAT_SLOWDOWN_MAX_DIST; + Vol = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + maxDist = SQR(VEHICLE_ONE_SHOT_BOAT_SLOWDOWN_MAX_DIST); + break; + case SOUND_CAR_JUMP: + { + static uint8 iWheelIndex = 82; + Vol = Max(VEHICLE_ONE_SHOT_CAR_JUMP_VOLUME, 2 * (100 * m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i])); + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_JUMP_MAX_DIST); + m_sQueueSample.m_nSampleIndex = SFX_TYRE_BUMP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iWheelIndex++; + if (iWheelIndex > 85) + iWheelIndex = 82; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_TYRE_BUMP); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + if (params.m_nIndex == RCBANDIT) { + m_sQueueSample.m_nFrequency <<= 1; + Vol >>= 1; + } + m_sQueueSample.m_nPriority = 6; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_JUMP_MAX_DIST; + break; + } + case SOUND_WEAPON_SHOT_FIRED: + Vol = m_anRandomTable[2]; + maxDist = SQR(VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_MAX_DIST); + m_sQueueSample.m_nSampleIndex = SFX_UZI_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = GunIndex++; + Vol = Vol % 15 + VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_VOLUME; + if (GunIndex > 58) + GunIndex = 53; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_UZI_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_MAX_DIST; + break; + case SOUND_TRAIN_DOOR_CLOSE: + case SOUND_TRAIN_DOOR_OPEN: + m_sQueueSample.m_nSampleIndex = SFX_AIR_BRAKES; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 59; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 11025; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 5.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_TRAIN_DOOR_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_TRAIN_DOOR_MAX_DIST); + Vol = m_anRandomTable[1] % 20 + VEHICLE_ONE_SHOT_TRAIN_DOOR_VOLUME; + break; + case SOUND_SPLATTER: + { + static uint8 CrunchOffset = 0; + m_sQueueSample.m_nSampleIndex = CrunchOffset + SFX_PED_CRUNCH_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 48; + m_sQueueSample.m_nFrequency = RandomDisplacement(600) + SampleManager.GetSampleBaseFrequency(SFX_PED_CRUNCH_1); + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_SPLATTER_MAX_DIST; + CrunchOffset++; + maxDist = SQR(VEHICLE_ONE_SHOT_SPLATTER_MAX_DIST); + Vol = m_anRandomTable[4] % 20 + VEHICLE_ONE_SHOT_SPLATTER_VOLUME; + CrunchOffset %= 2; + SET_SOUND_REFLECTION(TRUE); + break; + } + case SOUND_CAR_PED_COLLISION: + eventVol = Min(20.0f, m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]); + Vol = (eventVol / 20 * MAX_VOLUME); + if (Vol == 0) + continue; + + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[2] % 4) + SFX_FIGHT_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 50; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex) >> 1; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_PED_COLLISION_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_PED_COLLISION_MAX_DIST); + break; + case SOUND_PED_HELI_PLAYER_FOUND: + { + cPedParams pedParams; + pedParams.m_bDistanceCalculated = params.m_bDistanceCalculated; + pedParams.m_fDistance = params.m_fDistance; + SetupPedComments(pedParams, SOUND_PED_HELI_PLAYER_FOUND); + continue; + } + case SOUND_PED_BODYCAST_HIT: + { + cPedParams pedParams; + pedParams.m_bDistanceCalculated = params.m_bDistanceCalculated; + pedParams.m_fDistance = params.m_fDistance; + SetupPedComments(pedParams, SOUND_PED_BODYCAST_HIT); + continue; + } + case SOUND_CAR_TANK_TURRET_ROTATE: + eventVol = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + if (eventVol > 96.0f / 2500.0f) + eventVol = 96.0f / 2500.0f; + m_sQueueSample.m_nSampleIndex = SFX_TANK_TURRET; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 79; + m_sQueueSample.m_nFrequency = (3000 * eventVol / (96.0f / 2500.0f)) + 9000; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_TANK_TURRET_MAX_DIST; + Vol = (37 * eventVol / (96.0f / 2500.0f)) + VEHICLE_ONE_SHOT_CAR_TANK_TURRET_VOLUME; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_TANK_TURRET_MAX_DIST); + bLoop = TRUE; + break; + default: + continue; + } + if (params.m_fDistance < maxDist) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (bLoop) { + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + } else { + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + } + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bReverb = TRUE; + m_sQueueSample.m_bIs2D = FALSE; + AddSampleToRequestedQueue(); + } + } + } +} + +bool8 +cAudioManager::ProcessTrainNoise(cVehicleParams& params) +{ + CTrain *train; + uint8 Vol; + float speedMultipler; + + if (params.m_fDistance < SQR(TRAIN_NOISE_FAR_MAX_DIST)){ + if (params.m_fVelocityChange > 0.0f) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + train = (CTrain *)params.m_pVehicle; + speedMultipler = Min(1.0f, train->m_fSpeed * 250.0f / 51.0f); + Vol = (TRAIN_NOISE_VOLUME * speedMultipler); + if (train->m_fWagonPosition == 0.0f) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, TRAIN_NOISE_FAR_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 32; + m_sQueueSample.m_nSampleIndex = SFX_TRAIN_FAR; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_TRAIN_FAR); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = TRAIN_NOISE_FAR_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + if (params.m_fDistance < SQR(TRAIN_NOISE_NEAR_MAX_DIST)) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, TRAIN_NOISE_NEAR_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 33; + m_sQueueSample.m_nSampleIndex = SFX_TRAIN_NEAR; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_TRAIN_NEAR) + 100 * m_sQueueSample.m_nEntityIndex % 987; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = TRAIN_NOISE_NEAR_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessBoatEngine(cVehicleParams& params) +{ + CBoat *boat; + float padRelativeAccerate; + float gasPedal; + float padAccelerate; + uint8 Vol; + float oneShotVol; + + static uint16 LastAccel = 0; + static uint8 LastVol = 0; + + if (params.m_fDistance < SQR(BOAT_ENGINE_MAX_DIST)) { + boat = (CBoat *)params.m_pVehicle; + if (params.m_nIndex == REEFER) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(BOAT_ENGINE_REEFER_IDLE_VOLUME, BOAT_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 39; + m_sQueueSample.m_nSampleIndex = SFX_FISHING_BOAT_IDLE; + m_sQueueSample.m_nFrequency = 10386; + m_sQueueSample.m_nFrequency += (m_sQueueSample.m_nEntityIndex << 16) % 1000; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(BOAT_ENGINE_REEFER_IDLE_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BOAT_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 7; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + if (FindPlayerVehicle() == params.m_pVehicle) { + padAccelerate = Max(Pads[0].GetAccelerate(), Pads[0].GetBrake()); + padRelativeAccerate = padAccelerate / 255.0f; + Vol = (BOAT_ENGINE_REEFER_ACCEL_VOLUME_MULT * padRelativeAccerate) + BOAT_ENGINE_REEFER_ACCEL_MIN_VOLUME; + m_sQueueSample.m_nFrequency = (3000 * padRelativeAccerate) + 6000; + if (!boat->bPropellerInWater) + m_sQueueSample.m_nFrequency = 11 * m_sQueueSample.m_nFrequency / 10; + } else { + gasPedal = Abs(boat->m_fGasPedal); + if (gasPedal > 0.0f) { + Vol = (BOAT_ENGINE_REEFER_ACCEL_VOLUME_MULT * gasPedal) + BOAT_ENGINE_REEFER_ACCEL_MIN_VOLUME; + m_sQueueSample.m_nFrequency = (3000 * gasPedal) + 6000; + if (!boat->bPropellerInWater) + m_sQueueSample.m_nFrequency = 11 * m_sQueueSample.m_nFrequency / 10; + } else { + m_sQueueSample.m_nFrequency = 6000; + Vol = BOAT_ENGINE_REEFER_ACCEL_MIN_VOLUME; + } + } + m_sQueueSample.m_nVolume = ComputeVolume(Vol, BOAT_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 40; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_ACCEL; + m_sQueueSample.m_nFrequency += (m_sQueueSample.m_nEntityIndex << 16) % 1000; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BOAT_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 7; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } else { + if (FindPlayerVehicle() == params.m_pVehicle) { + padAccelerate = Max(Pads[0].GetAccelerate(), Pads[0].GetBrake()); + if (padAccelerate <= 20) { + Vol = BOAT_ENGINE_LOW_ACCEL_VOLUME - BOAT_ENGINE_LOW_ACCEL_VOLUME * padAccelerate / 40; + m_sQueueSample.m_nFrequency = 100 * padAccelerate + 11025; + m_sQueueSample.m_nCounter = 39; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_IDLE; + if (LastAccel > 20) { + oneShotVol = LastVol; + PlayOneShot(m_sQueueSample.m_nEntityIndex, SOUND_BOAT_SLOWDOWN, oneShotVol); + } + } else { + Vol = BOAT_ENGINE_HIGH_ACCEL_VOLUME_MULT * padAccelerate / 255 + BOAT_ENGINE_HIGH_ACCEL_MIN_VOLUME; + m_sQueueSample.m_nFrequency = 4000 * padAccelerate / 255 + 8000; + if (!boat->m_bIsAnchored) + m_sQueueSample.m_nFrequency = 11 * m_sQueueSample.m_nFrequency / 10; + m_sQueueSample.m_nCounter = 40; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_ACCEL; + } + LastVol = Vol; + LastAccel = padAccelerate; + } else { + gasPedal = Abs(boat->m_fGasPedal); + if (gasPedal > 0.0f) { + Vol = (BOAT_ENGINE_HIGH_ACCEL_VOLUME_MULT * gasPedal) + BOAT_ENGINE_HIGH_ACCEL_MIN_VOLUME; + m_sQueueSample.m_nFrequency = (4000 * gasPedal) + 8000; + if (!boat->m_bIsAnchored) + m_sQueueSample.m_nFrequency = 11 * m_sQueueSample.m_nFrequency / 10; + m_sQueueSample.m_nCounter = 40; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_ACCEL; + } else { + m_sQueueSample.m_nFrequency = 11025; + Vol = BOAT_ENGINE_LOW_ACCEL_VOLUME; + m_sQueueSample.m_nCounter = 39; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_IDLE; + } + } + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, BOAT_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nFrequency += (m_sQueueSample.m_nEntityIndex << 16) % 1000; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BOAT_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 7; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessBoatMovingOverWater(cVehicleParams& params) +{ + float velocityChange; + uint8 Vol; + float multiplier; + + if (params.m_fDistance < SQR(BOAT_MOVING_OVER_WATER_MAX_DIST)) { + velocityChange = Abs(params.m_fVelocityChange); + if (velocityChange > 0.0005f && ((CBoat*)params.m_pVehicle)->bBoatInWater) { + velocityChange = Min(0.75f, velocityChange); + multiplier = (velocityChange - 0.0005f) / (1499.0f / 2000.0f); + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + Vol = (BOAT_MOVING_OVER_WATER_VOLUME * multiplier); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, BOAT_MOVING_OVER_WATER_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 38; + m_sQueueSample.m_nSampleIndex = SFX_BOAT_WATER_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFrequency = (6050 * multiplier) + 16000; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BOAT_MOVING_OVER_WATER_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +struct tHelicopterSampleData { + float m_fMaxDistance; + float m_fBaseDistance; + uint8 m_bBaseVolume; +}; +static Const tHelicopterSampleData gHeliSfxRanges[3] = {{400, 380, 100}, {100, 70, MAX_VOLUME}, {60, 30, MAX_VOLUME}}; + +bool8 +cAudioManager::ProcessHelicopter(cVehicleParams& params) +{ + CHeli *heli; + float MaxDist; + float dist; + float baseDist; + uint8 Vol; + + if (params.m_fDistance < SQR(gHeliSfxRanges[0].m_fMaxDistance)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + heli = (CHeli *)params.m_pVehicle; + for (uint32 i = 0; i < ARRAY_SIZE(gHeliSfxRanges); i++) { + MaxDist = gHeliSfxRanges[i].m_fMaxDistance; + dist = m_sQueueSample.m_fDistance; + if (dist < MaxDist) { + baseDist = gHeliSfxRanges[i].m_fBaseDistance; + if (dist < baseDist) + Vol = gHeliSfxRanges[i].m_bBaseVolume; + else + Vol = (gHeliSfxRanges[i].m_bBaseVolume * ((MaxDist - dist) / (MaxDist - baseDist))); + } else + return TRUE; + + m_sQueueSample.m_nVolume = ComputeVolume(Vol, gHeliSfxRanges[i].m_fMaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i + 65; + m_sQueueSample.m_nSampleIndex = i + SFX_HELI_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nFrequency = 1200 * heli->m_nHeliId + SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = gHeliSfxRanges[i].m_fMaxDistance; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +void +cAudioManager::ProcessPlane(cVehicleParams& params) +{ + switch (params.m_nIndex) { + case AIRTRAIN: + ProcessJumbo(params); + break; + case DEADDODO: + ProcessCesna(params); + break; + default: + debug("Plane Model Id is %d\n, ", params.m_pVehicle->GetModelIndex()); + break; + } +} + +#pragma region JUMBO +uint8 gJumboVolOffsetPercentage; + +void +DoJumboVolOffset() +{ + if (!(AudioManager.m_FrameCounter % (AudioManager.m_anRandomTable[0] % 6 + 3))) + gJumboVolOffsetPercentage = AudioManager.m_anRandomTable[1] % 60; +} + +void +cAudioManager::ProcessJumbo(cVehicleParams& params) +{ + CPlane *plane; + float position; + + if (params.m_fDistance < SQR(JUMBO_MAX_DIST)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + plane = (CPlane *)params.m_pVehicle; + DoJumboVolOffset(); + position = PlanePathPosition[plane->m_nPlaneId]; + if (position > TakeOffPoint) { + if (300.0f + TakeOffPoint < position) { + if (LandingPoint - 350.0f < position) { + if (position > LandingPoint) { + if (plane->m_fSpeed > 0.103344f) + ProcessJumboDecel(plane); + else + ProcessJumboTaxi(); + } + else + ProcessJumboLanding(plane); + } + else + ProcessJumboFlying(); + } else + ProcessJumboTakeOff(plane); + } else { + if (plane->m_fSpeed > 0.103344f) + ProcessJumboAccel(plane); + else + ProcessJumboTaxi(); + } + } +} + +void +cAudioManager::ProcessJumboTaxi() +{ + if (SetupJumboFlySound(20)) { + if (SetupJumboTaxiSound(75)) + SetupJumboWhineSound(18, 29500); + } +} + +void +cAudioManager::ProcessJumboAccel(CPlane *plane) +{ + uint32 engineFreq; + uint8 vol; + float modificator; + float freqMult; + + if (SetupJumboFlySound(20)) { + modificator = Min(1.0f, (plane->m_fSpeed - 0.103344f) * 1.6760077f); + if (SetupJumboRumbleSound(MAX_VOLUME * modificator) && SetupJumboTaxiSound((1.0f - modificator) * 75)) { + if (modificator >= 0.2f) { + freqMult = 1; + engineFreq = 22050; + vol = MAX_VOLUME; + } else { + freqMult = modificator * 5; + vol = freqMult * MAX_VOLUME; + engineFreq = freqMult * 6050 + 16000; + } + SetupJumboEngineSound(vol, engineFreq); + SetupJumboWhineSound(18, 14600 * freqMult + 29500); + } + } +} + +void +cAudioManager::ProcessJumboTakeOff(CPlane *plane) +{ + float modificator = (PlanePathPosition[plane->m_nPlaneId] - TakeOffPoint) / 300; + if (SetupJumboFlySound((107 * modificator) + 20) && SetupJumboRumbleSound(MAX_VOLUME * (1.0f - modificator))) { + if (SetupJumboEngineSound(MAX_VOLUME, 22050)) + SetupJumboWhineSound(18 * (1.0f - modificator), 44100); + } +} + +void +cAudioManager::ProcessJumboFlying() +{ + if (SetupJumboFlySound(MAX_VOLUME)) + SetupJumboEngineSound(63, 22050); +} + +void +cAudioManager::ProcessJumboLanding(CPlane *plane) +{ + float modificator = (LandingPoint - PlanePathPosition[plane->m_nPlaneId]) / 350; + if (SetupJumboFlySound(107 * modificator + 20)) { + if (SetupJumboTaxiSound(75 * (1.0f - modificator))) { + SetupJumboEngineSound(MAX_VOLUME, 22050); + SetupJumboWhineSound(18 * (1.0f - modificator), 14600 * modificator + 29500); + } + } +} + +void +cAudioManager::ProcessJumboDecel(CPlane *plane) +{ + if (SetupJumboFlySound(20) && SetupJumboTaxiSound(75)) { + float modificator = Min(1.0f, (plane->m_fSpeed - 0.103344f) * 1.6760077f); + SetupJumboEngineSound(MAX_VOLUME * modificator, 6050 * modificator + 16000); + SetupJumboWhineSound(18, 29500); + } +} + +bool8 +cAudioManager::SetupJumboTaxiSound(uint8 vol) +{ + if (m_sQueueSample.m_fDistance < JUMBO_ENGINE_SOUND_MAX_DIST) { + uint8 Vol = (vol >> 1) + ((vol >> 1) * m_sQueueSample.m_fDistance / JUMBO_ENGINE_SOUND_MAX_DIST); + + if (m_sQueueSample.m_fDistance / JUMBO_ENGINE_SOUND_MAX_DIST < 0.7f) + Vol -= Vol * gJumboVolOffsetPercentage / 100; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, JUMBO_ENGINE_SOUND_MAX_DIST, m_sQueueSample.m_fDistance); + + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_TAXI; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = GetJumboTaxiFreq(); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = JUMBO_ENGINE_SOUND_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::SetupJumboWhineSound(uint8 Vol, uint32 freq) +{ + if (m_sQueueSample.m_fDistance < JUMBO_WHINE_SOUND_MAX_DIST) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, JUMBO_WHINE_SOUND_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 2; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_WHINE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = freq; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = JUMBO_WHINE_SOUND_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::SetupJumboEngineSound(uint8 Vol, uint32 freq) +{ + if (m_sQueueSample.m_fDistance < JUMBO_ENGINE_SOUND_MAX_DIST) { + uint8 FinalVol = Vol - gJumboVolOffsetPercentage / 100; + m_sQueueSample.m_nVolume = ComputeVolume(FinalVol, JUMBO_ENGINE_SOUND_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 3; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_ENGINE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = freq; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(FinalVol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = JUMBO_ENGINE_SOUND_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::SetupJumboFlySound(uint8 Vol) +{ + if (m_sQueueSample.m_fDistance < JUMBO_MAX_DIST) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, JUMBO_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_DIST_FLY; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_JUMBO_DIST_FLY); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = JUMBO_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::SetupJumboRumbleSound(uint8 Vol) +{ + if (m_sQueueSample.m_fDistance < JUMBO_RUMBLE_SOUND_MAX_DIST) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, JUMBO_RUMBLE_SOUND_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 5; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_RUMBLE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_JUMBO_RUMBLE); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = JUMBO_RUMBLE_SOUND_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 12; + m_sQueueSample.m_nPan = 0; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + m_sQueueSample.m_nCounter = 6; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_RUMBLE; + m_sQueueSample.m_nFrequency += 200; + m_sQueueSample.m_nPan = 127; + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +int32 +cAudioManager::GetJumboTaxiFreq() +{ + return (1.0f / 180 * 10950 * m_sQueueSample.m_fDistance) + 22050; +} + +#pragma endregion Some jumbo crap + +#pragma endregion All the vehicle audio code + +#pragma region PED AUDIO +void +cAudioManager::ProcessPed(CPhysical *ped) +{ + cPedParams params; + + m_sQueueSample.m_vecPos = ped->GetPosition(); + + params.m_bDistanceCalculated = FALSE; + params.m_pPed = (CPed *)ped; + params.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (ped->GetModelIndex() == MI_FATMALE02) + ProcessPedHeadphones(params); + ProcessPedOneShots(params); +} + +void +cAudioManager::ProcessPedHeadphones(cPedParams ¶ms) +{ + CPed *ped; + CAutomobile *veh; + uint8 Vol; + + if (params.m_fDistance < SQR(PED_HEADPHONES_MAX_DIST)) { + ped = params.m_pPed; + if (ped->bBodyPartJustCameOff && ped->m_bodyPartBleeding == PED_HEAD) + return; + + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + if (ped->bInVehicle && ped->m_nPedState == PED_DRIVING) { + Vol = PED_HEADPHONES_IN_CAR_VOLUME; + veh = (CAutomobile *)ped->m_pMyVehicle; + if (veh && veh->IsCar()) { + for (int32 i = DOOR_FRONT_LEFT; i < ARRAY_SIZE(veh->Doors); i++) { + if (!veh->IsDoorClosed((eDoors)i) || veh->IsDoorMissing((eDoors)i)) { + Vol = PED_HEADPHONES_VOLUME; + break; + } + } + } + } else + Vol = PED_HEADPHONES_VOLUME; + + m_sQueueSample.m_nVolume = ComputeVolume(Vol, PED_HEADPHONES_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 64; + m_sQueueSample.m_nSampleIndex = SFX_HEADPHONES; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_HEADPHONES); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = PED_HEADPHONES_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessPedOneShots(cPedParams ¶ms) +{ + uint8 Vol; + uint32 sampleIndex; + + CPed *ped = params.m_pPed; + + bool8 narrowSoundRange; + int16 sound; + bool8 stereo; + CWeapon *weapon; +#ifdef FIX_BUGS + float maxDist = 0.0f; // uninitialized variable +#else + float maxDist; +#endif + + static uint8 iSound = 21; + + weapon = params.m_pPed->GetWeapon(); + for (uint32 i = 0; i < m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents; i++) { + stereo = FALSE; + narrowSoundRange = FALSE; + SET_SOUND_REFLECTION(FALSE); + sound = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]; + switch (sound) { + case SOUND_FALL_LAND: + case SOUND_FALL_COLLAPSE: + if (ped->bIsInTheAir) + continue; + maxDist = SQR(PED_ONE_SHOT_FALL_MAX_DIST); + Vol = m_anRandomTable[3] % 20 + PED_ONE_SHOT_FALL_VOLUME; + if (ped->m_nSurfaceTouched == SURFACE_WATER) + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[3] % 4) + SFX_FOOTSTEP_WATER_1; + else if (sound == SOUND_FALL_LAND) + m_sQueueSample.m_nSampleIndex = SFX_BODY_LAND; + else + m_sQueueSample.m_nSampleIndex = SFX_BODY_LAND_AND_FALL; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency / 17); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_FALL_MAX_DIST; + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_STEP_START: + case SOUND_STEP_END: + if (params.m_pPed->bIsInTheAir) + continue; + Vol = m_anRandomTable[3] % 15 + PED_ONE_SHOT_STEP_VOLUME; + if (FindPlayerPed() != m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_pEntity) + Vol >>= 1; + maxDist = SQR(PED_ONE_SHOT_STEP_MAX_DIST); + switch (params.m_pPed->m_nSurfaceTouched) { + case SURFACE_GRASS: + sampleIndex = m_anRandomTable[1] % 5 + SFX_FOOTSTEP_GRASS_1; + break; + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: + sampleIndex = m_anRandomTable[4] % 5 + SFX_FOOTSTEP_GRAVEL_1; + break; + case SURFACE_CAR: + case SURFACE_GARAGE_DOOR: + case SURFACE_CAR_PANEL: + case SURFACE_THICK_METAL_PLATE: + case SURFACE_SCAFFOLD_POLE: + case SURFACE_LAMP_POST: + case SURFACE_FIRE_HYDRANT: + case SURFACE_GIRDER: + case SURFACE_METAL_CHAIN_FENCE: + case SURFACE_CONTAINER: + case SURFACE_NEWS_VENDOR: + sampleIndex = m_anRandomTable[0] % 5 + SFX_FOOTSTEP_METAL_1; + break; + case SURFACE_SAND: + sampleIndex = (m_anRandomTable[4] & 3) + SFX_FOOTSTEP_SAND_1; + break; + case SURFACE_WATER: + sampleIndex = (m_anRandomTable[3] & 3) + SFX_FOOTSTEP_WATER_1; + break; + case SURFACE_WOOD_CRATES: + case SURFACE_WOOD_BENCH: + case SURFACE_WOOD_SOLID: + sampleIndex = m_anRandomTable[2] % 5 + SFX_FOOTSTEP_WOOD_1; + break; + case SURFACE_HEDGE: + sampleIndex = m_anRandomTable[2] % 5 + SFX_COL_VEG_1; + break; + default: + sampleIndex = m_anRandomTable[2] % 5 + SFX_FOOTSTEP_CONCRETE_1; + break; + } + m_sQueueSample.m_nSampleIndex = sampleIndex; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i] - SOUND_STEP_START + 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency / 17); + switch (params.m_pPed->m_nMoveState) { + case PEDMOVE_WALK: + Vol >>= 2; + m_sQueueSample.m_nFrequency = 9 * m_sQueueSample.m_nFrequency / 10; + break; + case PEDMOVE_RUN: + Vol >>= 1; + m_sQueueSample.m_nFrequency = 11 * m_sQueueSample.m_nFrequency / 10; + break; + case PEDMOVE_SPRINT: + m_sQueueSample.m_nFrequency = 12 * m_sQueueSample.m_nFrequency / 10; + break; + default: + break; + } + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_STEP_MAX_DIST; + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_WEAPON_AK47_BULLET_ECHO: + case SOUND_WEAPON_UZI_BULLET_ECHO: + case SOUND_WEAPON_M16_BULLET_ECHO: + m_sQueueSample.m_nSampleIndex = SFX_UZI_END_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_UZI_END_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_BULLET_ECHO_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_BULLET_ECHO_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[4] % 10 + PED_ONE_SHOT_WEAPON_BULLET_ECHO_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case SOUND_WEAPON_FLAMETHROWER_FIRE: + m_sQueueSample.m_nSampleIndex = SFX_FLAMETHROWER_START_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_FLAMETHROWER_START_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_VOLUME; + SET_EMITTING_VOLUME(PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + break; + case SOUND_WEAPON_SHOT_FIRED: + weapon = ped->GetWeapon(); + switch (weapon->m_eWeaponType) { + case WEAPONTYPE_COLT45: + m_sQueueSample.m_nSampleIndex = SFX_COLT45_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_COLT45_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_COLT45_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_COLT45_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[1] % 10 + PED_ONE_SHOT_WEAPON_COLT45_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case WEAPONTYPE_ROCKETLAUNCHER: + m_sQueueSample.m_nSampleIndex = SFX_ROCKET_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_ROCKET_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[0] % 20 + PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case WEAPONTYPE_FLAMETHROWER: + m_sQueueSample.m_nSampleIndex = SFX_FLAMETHROWER_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 9; + Vol = PED_ONE_SHOT_WEAPON_FLAMETHROWER_VOLUME; + m_sQueueSample.m_nFrequency = (10 * m_sQueueSample.m_nEntityIndex % 2048) + SampleManager.GetSampleBaseFrequency(SFX_FLAMETHROWER_LEFT); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_FLAMETHROWER_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_FLAMETHROWER_MAX_DIST); + m_sQueueSample.m_nLoopCount = 0; + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_EMITTING_VOLUME(PED_ONE_SHOT_WEAPON_FLAMETHROWER_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 6; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case WEAPONTYPE_AK47: + m_sQueueSample.m_nSampleIndex = SFX_AK47_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_AK47_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_AK47_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_AK47_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[1] % 15 + PED_ONE_SHOT_WEAPON_AK47_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + break; + case WEAPONTYPE_UZI: + m_sQueueSample.m_nSampleIndex = SFX_UZI_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_UZI_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_UZI_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_UZI_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[3] % 15 + PED_ONE_SHOT_WEAPON_UZI_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + break; + case WEAPONTYPE_SNIPERRIFLE: + m_sQueueSample.m_nSampleIndex = SFX_SNIPER_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SNIPER_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_SNIPERRIFLE_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_SNIPERRIFLE_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[4] % 10 + PED_ONE_SHOT_WEAPON_SNIPERRIFLE_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case WEAPONTYPE_SHOTGUN: + m_sQueueSample.m_nSampleIndex = SFX_SHOTGUN_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SHOTGUN_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_SHOTGUN_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_SHOTGUN_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[2] % 10 + PED_ONE_SHOT_WEAPON_SHOTGUN_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case WEAPONTYPE_M16: + m_sQueueSample.m_nSampleIndex = SFX_M16_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_M16_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_M16_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_M16_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[4] % 15 + PED_ONE_SHOT_WEAPON_M16_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + break; + default: + continue; + } + + break; + case SOUND_WEAPON_RELOAD: + weapon = &ped->m_weapons[ped->m_currentWeapon]; + switch (weapon->m_eWeaponType) { + case WEAPONTYPE_COLT45: + m_sQueueSample.m_nSampleIndex = SFX_PISTOL_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PISTOL_RELOAD) + RandomDisplacement(300); + break; + case WEAPONTYPE_UZI: + m_sQueueSample.m_nSampleIndex = SFX_M16_RELOAD; + m_sQueueSample.m_nFrequency = 39243; + break; + case WEAPONTYPE_AK47: + m_sQueueSample.m_nSampleIndex = SFX_AK47_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_AK47_RELOAD); + break; + case WEAPONTYPE_M16: + m_sQueueSample.m_nSampleIndex = SFX_M16_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_M16_RELOAD); + break; + case WEAPONTYPE_SHOTGUN: + m_sQueueSample.m_nSampleIndex = SFX_AK47_RELOAD; + m_sQueueSample.m_nFrequency = 30290; + break; + case WEAPONTYPE_ROCKETLAUNCHER: + m_sQueueSample.m_nSampleIndex = SFX_ROCKET_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_ROCKET_RELOAD); + break; + case WEAPONTYPE_SNIPERRIFLE: + m_sQueueSample.m_nSampleIndex = SFX_RIFLE_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RIFLE_RELOAD); + break; + default: + continue; + } + Vol = PED_ONE_SHOT_WEAPON_RELOAD_VOLUME; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency += RandomDisplacement(300); + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_RELOAD_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_RELOAD_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + SET_EMITTING_VOLUME(PED_ONE_SHOT_WEAPON_RELOAD_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_WEAPON_HIT_PED: + m_sQueueSample.m_nSampleIndex = SFX_BULLET_PED; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_BULLET_PED); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 3); + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_HIT_PED_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_HIT_PED_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[0] % 20 + PED_ONE_SHOT_WEAPON_HIT_PED_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + break; + case SOUND_WEAPON_BAT_ATTACK: + m_sQueueSample.m_nSampleIndex = SFX_BAT_HIT_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = RandomDisplacement(2000) + 22000; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_PUNCH_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_PUNCH_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[2] % 20 + PED_ONE_SHOT_PUNCH_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case SOUND_FIGHT_PUNCH_33: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_1; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_KICK_34: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_1; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_HEADBUTT_35: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_1; + m_sQueueSample.m_nFrequency = 20000; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_36: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_2; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_37: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_2; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_CLOSE_PUNCH_38: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_2; + m_sQueueSample.m_nFrequency = 20000; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_39: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_4; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_OR_KICK_BELOW_40: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_4; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_41: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_4; + m_sQueueSample.m_nFrequency = 20000; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_FROM_BEHIND_42: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_5; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_KNEE_OR_KICK_43: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_5; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_KICK_44: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_5; + m_sQueueSample.m_nFrequency = 20000; + AddFightSound: + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound; + narrowSoundRange = TRUE; + iSound++; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_PUNCH_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_PUNCH_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[3] % 26 + PED_ONE_SHOT_PUNCH_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_SPLASH: + m_sQueueSample.m_nSampleIndex = SFX_SPLASH_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = RandomDisplacement(1400) + 20000; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_SPLASH_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_SPLASH_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[2] % 30 + PED_ONE_SHOT_SPLASH_PED_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + default: + SetupPedComments(params, sound); + continue; + } + + if (narrowSoundRange && iSound > 60) + iSound = 21; + if (params.m_fDistance < maxDist) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (stereo) { + if (m_sQueueSample.m_fDistance < 0.2f * m_sQueueSample.m_MaxDistance) { + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nPan = 0; + } else + stereo = FALSE; + } + m_sQueueSample.m_bReverb = TRUE; + AddSampleToRequestedQueue(); + if (stereo) { + m_sQueueSample.m_nPan = 127; + m_sQueueSample.m_nSampleIndex++; + if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i] == SOUND_WEAPON_SHOT_FIRED && + weapon->m_eWeaponType == WEAPONTYPE_FLAMETHROWER) + m_sQueueSample.m_nCounter++; + else { + m_sQueueSample.m_nCounter = iSound++; + if (iSound > 60) + iSound = 21; + } + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::SetupPedComments(cPedParams ¶ms, uint16 sound) +{ + CPed *ped = params.m_pPed; + uint8 Vol; + float maxDist; + tPedComment pedComment; + + if (ped != nil) { + switch (sound) { + case SOUND_AMMUNATION_WELCOME_1: + pedComment.m_nSampleIndex = SFX_AMMU_D; + break; + case SOUND_AMMUNATION_WELCOME_2: + pedComment.m_nSampleIndex = SFX_AMMU_E; + break; + case SOUND_AMMUNATION_WELCOME_3: + pedComment.m_nSampleIndex = SFX_AMMU_F; + break; + default: + pedComment.m_nSampleIndex = GetPedCommentSfx(ped, sound); + if (pedComment.m_nSampleIndex == NO_SAMPLE) + return; + break; + } + + maxDist = PED_COMMENT_MAX_DIST; + } else { + switch (sound) { +#ifdef GTA_PS2 + case SOUND_PAGER: + maxDist = PED_COMMENT_MAX_DIST; + pedComment.m_nSampleIndex = SFX_PAGER; + break; +#endif + case SOUND_PED_HELI_PLAYER_FOUND: + maxDist = PED_COMMENT_POLICE_HELI_MAX_DIST; + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 29 + SFX_POLICE_HELI_1; + break; + case SOUND_PED_BODYCAST_HIT: + if (CTimer::GetTimeInMilliseconds() <= gNextCryTime) + return; + maxDist = PED_COMMENT_MAX_DIST; + gNextCryTime = CTimer::GetTimeInMilliseconds() + 500; + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 4 + SFX_PLASTER_BLOKE_1; + break; + case SOUND_INJURED_PED_MALE_OUCH: + case SOUND_INJURED_PED_MALE_PRISON: + maxDist = PED_COMMENT_MAX_DIST; + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 15 + SFX_GENERIC_MALE_GRUNT_1; + break; + case SOUND_INJURED_PED_FEMALE: + maxDist = PED_COMMENT_MAX_DIST; + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 11 + SFX_GENERIC_FEMALE_GRUNT_1; + break; + default: + return; + } + } + + if (params.m_fDistance < SQR(maxDist)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + if (sound != SOUND_PAGER) { + switch (sound) { + case SOUND_AMMUNATION_WELCOME_1: + case SOUND_AMMUNATION_WELCOME_2: + case SOUND_AMMUNATION_WELCOME_3: + Vol = PED_COMMENT_VOLUME; + break; + default: + if (CWorld::GetIsLineOfSightClear(TheCamera.GetPosition(), m_sQueueSample.m_vecPos, true, false, false, false, false, false)) + Vol = PED_COMMENT_VOLUME; + else + Vol = PED_COMMENT_VOLUME_BEHIND_WALL; + break; + } + m_sQueueSample.m_nVolume = ComputeVolume(Vol, maxDist, m_sQueueSample.m_fDistance); + pedComment.m_nLoadingTimeout = 10; + if (m_sQueueSample.m_nVolume > 0) { + pedComment.m_nEntityIndex = m_sQueueSample.m_nEntityIndex; + pedComment.m_vecPos = m_sQueueSample.m_vecPos; + pedComment.m_fDistance = m_sQueueSample.m_fDistance; + pedComment.m_nVolume = m_sQueueSample.m_nVolume; +#if defined(EXTERNAL_3D_SOUND) && defined(FIX_BUGS) + pedComment.m_nEmittingVolume = Vol; +#endif + m_sPedComments.Add(&pedComment); + } + } +#ifdef GTA_PS2 + else { + m_sQueueSample.m_nVolume = MAX_VOLUME; + pedComment.m_nLoadingTimeout = 40; + } +#endif + } +} + +int32 +cAudioManager::GetPedCommentSfx(CPed *ped, uint16 sound) +{ + if (ped->IsPlayer()) + return GetPlayerTalkSfx(sound); + + switch (ped->GetModelIndex()) { + case MI_COP: + return GetCopTalkSfx(sound); + case MI_SWAT: + return GetSwatTalkSfx(sound); + case MI_FBI: + return GetFBITalkSfx(sound); + case MI_ARMY: + return GetArmyTalkSfx(sound); + case MI_MEDIC: + return GetMedicTalkSfx(sound); + case MI_FIREMAN: + return GetFiremanTalkSfx(sound); + case MI_MALE01: + return GetNormalMaleTalkSfx(sound); + case MI_TAXI_D: + return GetAsianTaxiDriverTalkSfx(sound); + case MI_PIMP: + return GetPimpTalkSfx(sound); + case MI_GANG01: + case MI_GANG02: + return GetMafiaTalkSfx(sound); + case MI_GANG03: + case MI_GANG04: + return GetTriadTalkSfx(sound); + case MI_GANG05: + case MI_GANG06: + return GetDiabloTalkSfx(sound); + case MI_GANG07: + case MI_GANG08: + return GetYakuzaTalkSfx(sound); + case MI_GANG09: + case MI_GANG10: + return GetYardieTalkSfx(sound); + case MI_GANG11: + case MI_GANG12: + return GetColumbianTalkSfx(sound); + case MI_GANG13: + case MI_GANG14: + return GetHoodTalkSfx(sound); + case MI_CRIMINAL01: + return GetBlackCriminalTalkSfx(sound); + case MI_CRIMINAL02: + return GetWhiteCriminalTalkSfx(sound); + case MI_SPECIAL01: + case MI_SPECIAL02: + case MI_SPECIAL03: + case MI_SPECIAL04: + return GetSpecialCharacterTalkSfx(ped->GetModelIndex(), sound); + case MI_MALE02: + return GetCasualMaleOldTalkSfx(sound); + case MI_MALE03: + case MI_P_MAN1: + case MI_P_MAN2: + return GetBlackProjectMaleTalkSfx(sound, ped->GetModelIndex()); + case MI_FATMALE01: + return GetWhiteFatMaleTalkSfx(sound); + case MI_FATMALE02: + return GetBlackFatMaleTalkSfx(sound); + case MI_FEMALE01: + return GetBlackCasualFemaleTalkSfx(sound); + case MI_FEMALE02: + case MI_CAS_WOM: + return GetWhiteCasualFemaleTalkSfx(sound); + case MI_FEMALE03: + return GetFemaleNo3TalkSfx(sound); + case MI_FATFEMALE01: + return GetBlackFatFemaleTalkSfx(sound); + case MI_FATFEMALE02: + return GetWhiteFatFemaleTalkSfx(sound); + case MI_PROSTITUTE: + return GetBlackFemaleProstituteTalkSfx(sound); + case MI_PROSTITUTE2: + return GetWhiteFemaleProstituteTalkSfx(sound); + case MI_P_WOM1: + return GetBlackProjectFemaleOldTalkSfx(sound); + case MI_P_WOM2: + return GetBlackProjectFemaleYoungTalkSfx(sound); + case MI_CT_MAN1: + return GetChinatownMaleOldTalkSfx(sound); + case MI_CT_MAN2: + return GetChinatownMaleYoungTalkSfx(sound); + case MI_CT_WOM1: + return GetChinatownFemaleOldTalkSfx(sound); + case MI_CT_WOM2: + return GetChinatownFemaleYoungTalkSfx(sound); + case MI_LI_MAN1: + case MI_LI_MAN2: + return GetLittleItalyMaleTalkSfx(sound); + case MI_LI_WOM1: + return GetLittleItalyFemaleOldTalkSfx(sound); + case MI_LI_WOM2: + return GetLittleItalyFemaleYoungTalkSfx(sound); + case MI_DOCKER1: + return GetWhiteDockerMaleTalkSfx(sound); + case MI_DOCKER2: + return GetBlackDockerMaleTalkSfx(sound); + case MI_SCUM_MAN: + return GetScumMaleTalkSfx(sound); + case MI_SCUM_WOM: + return GetScumFemaleTalkSfx(sound); + case MI_WORKER1: + return GetWhiteWorkerMaleTalkSfx(sound); + case MI_WORKER2: + return GetBlackWorkerMaleTalkSfx(sound); + case MI_B_MAN1: + case MI_B_MAN3: + return GetBusinessMaleYoungTalkSfx(sound, ped->GetModelIndex()); + case MI_B_MAN2: + return GetBusinessMaleOldTalkSfx(sound); + case MI_B_WOM1: + case MI_B_WOM2: + return GetWhiteBusinessFemaleTalkSfx(sound, ped->GetModelIndex()); + case MI_B_WOM3: + return GetBlackBusinessFemaleTalkSfx(sound); + case MI_MOD_MAN: + return GetSupermodelMaleTalkSfx(sound); + case MI_MOD_WOM: + return GetSupermodelFemaleTalkSfx(sound); + case MI_ST_MAN: + return GetStewardMaleTalkSfx(sound); + case MI_ST_WOM: + return GetStewardFemaleTalkSfx(sound); + case MI_FAN_MAN1: + case MI_FAN_MAN2: + return GetFanMaleTalkSfx(sound, ped->GetModelIndex()); + case MI_FAN_WOM: + return GetFanFemaleTalkSfx(sound); + case MI_HOS_MAN: + return GetHospitalMaleTalkSfx(sound); + case MI_HOS_WOM: + return GetHospitalFemaleTalkSfx(sound); + case MI_CONST1: + return GetWhiteConstructionWorkerTalkSfx(sound); + case MI_CONST2: + return GetBlackConstructionWorkerTalkSfx(sound); + case MI_SHOPPER1: + case MI_SHOPPER2: + case MI_SHOPPER3: + return GetShopperFemaleTalkSfx(sound, ped->GetModelIndex()); + case MI_STUD_MAN: + return GetStudentMaleTalkSfx(sound); + case MI_STUD_WOM: + return GetStudentFemaleTalkSfx(sound); + case MI_CAS_MAN: + return GetCasualMaleYoungTalkSfx(sound); + default: + return GetGenericMaleTalkSfx(sound); + } +} + +void +cAudioManager::GetPhrase(uint32 &phrase, uint32 &prevPhrase, uint32 sample, uint32 maxOffset) +{ + phrase = sample + m_anRandomTable[m_sQueueSample.m_nEntityIndex & 3] % maxOffset; + + // check if the same sfx like last time, if yes, then try use next one, + // if exceeded range, then choose first available sample + if (phrase == prevPhrase && ++phrase >= sample + maxOffset) + phrase = sample; + prevPhrase = phrase; +} + +#pragma region PED_COMMENTS + +uint32 +cAudioManager::GetPlayerTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_DAMAGE: + GetPhrase(sfx, lastSfx, SFX_CLAUDE_HIGH_DAMAGE_GRUNT_1, 11); + break; + case SOUND_PED_HIT: + GetPhrase(sfx, lastSfx, SFX_CLAUDE_LOW_DAMAGE_GRUNT_1, 10); + break; + case SOUND_PED_LAND: + GetPhrase(sfx, lastSfx, SFX_CLAUDE_HIT_GROUND_GRUNT_1, 6); + break; + default: + sfx = NO_SAMPLE; + break; + } + return sfx; +} + +uint32 +cAudioManager::GetCopTalkSfx(uint16 sound) +{ + uint32 sfx; + PedState pedState; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ARREST_COP: + GetPhrase(sfx, lastSfx, SFX_COP_VOICE_1_ARREST_1, 6); + break; + case SOUND_PED_PURSUIT_COP: + pedState = FindPlayerPed()->m_nPedState; + if (pedState == PED_ARRESTED || pedState == PED_DEAD || pedState == PED_DIE) + return NO_SAMPLE; + GetPhrase(sfx, lastSfx, SFX_COP_VOICE_1_CHASE_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + return (SFX_COP_VOICE_2_ARREST_1 - SFX_COP_VOICE_1_ARREST_1) * (m_sQueueSample.m_nEntityIndex % 5) + sfx; +} + +uint32 +cAudioManager::GetSwatTalkSfx(uint16 sound) +{ + uint32 sfx; + PedState pedState; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ARREST_SWAT: + GetPhrase(sfx, lastSfx, SFX_SWAT_VOICE_1_CHASE_1, 6); + break; + case SOUND_PED_PURSUIT_SWAT: + pedState = FindPlayerPed()->m_nPedState; + if (pedState == PED_ARRESTED || pedState == PED_DEAD || pedState == PED_DIE) + return NO_SAMPLE; + GetPhrase(sfx, lastSfx, SFX_SWAT_VOICE_1_CHASE_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + return (SFX_SWAT_VOICE_2_CHASE_1 - SFX_SWAT_VOICE_1_CHASE_1) * (m_sQueueSample.m_nEntityIndex % 4) + sfx; +} + +uint32 +cAudioManager::GetFBITalkSfx(uint16 sound) +{ + uint32 sfx; + PedState pedState; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ARREST_FBI: + GetPhrase(sfx, lastSfx, SFX_FBI_VOICE_1_CHASE_1, 6); + break; + case SOUND_PED_PURSUIT_FBI: + pedState = FindPlayerPed()->m_nPedState; + if (pedState == PED_ARRESTED || pedState == PED_DEAD || pedState == PED_DIE) + return NO_SAMPLE; + GetPhrase(sfx, lastSfx, SFX_FBI_VOICE_1_CHASE_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + return (SFX_FBI_VOICE_2_CHASE_1 - SFX_FBI_VOICE_1_CHASE_1) * (m_sQueueSample.m_nEntityIndex % 3) + sfx; +} + +uint32 +cAudioManager::GetArmyTalkSfx(uint16 sound) +{ + uint32 sfx; + PedState pedState; + static uint32 lastSfx = NO_SAMPLE; + + switch(sound) { + case SOUND_PED_PURSUIT_ARMY: + pedState = FindPlayerPed()->m_nPedState; + if(pedState == PED_ARRESTED || pedState == PED_DEAD || pedState == PED_DIE) return NO_SAMPLE; + GetPhrase(sfx, lastSfx, SFX_ARMY_VOICE_1_CHASE_1, 15); + break; + default: return GetGenericMaleTalkSfx(sound); + } + + return (SFX_ARMY_VOICE_2_CHASE_1 - SFX_ARMY_VOICE_1_CHASE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetMedicTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_MEDIC_VOICE_1_GUN_PANIC_1, 5); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_MEDIC_VOICE_1_CARJACKED_1, 5); + break; + case SOUND_PED_HEALING: + GetPhrase(sfx, lastSfx, SFX_MEDIC_VOICE_1_AT_VICTIM_1, 12); + break; + case SOUND_PED_LEAVE_VEHICLE: + GetPhrase(sfx, lastSfx, SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_1, 9); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_MEDIC_VOICE_2_GUN_PANIC_1 - SFX_MEDIC_VOICE_1_GUN_PANIC_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetFiremanTalkSfx(uint16 sound) +{ + return GetGenericMaleTalkSfx(sound); +} + +uint32 +cAudioManager::GetBusinessMaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBusinessMaleYoungTalkSfx(uint16 sound, uint32 model) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + if (model == MI_B_MAN3) + sfx += (SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_1 - SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_1); + return sfx; +} + +uint32 +cAudioManager::GetMafiaTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 3) + sfx; +} + +uint32 +cAudioManager::GetTriadTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_GUN_COOL_1, 3); + break; + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetDiabloTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_GUN_COOL_1, 4); + break; + case SOUND_PED_HANDS_COWER: + sound = SOUND_PED_FLEE_SPRINT; + return GetGenericMaleTalkSfx(sound); + break; + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_EYING_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_DIABLO_MALE_VOICE_2_CHAT_1 - SFX_DIABLO_MALE_VOICE_1_CHAT_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetYakuzaTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetYardieTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + sfx = SFX_YARDIE_MALE_VOICE_1_GUN_COOL_1; + break; + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + sfx = SFX_YARDIE_MALE_VOICE_1_CARJACKED_1; + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_FIGHT_1, 6); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_EYING_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetColumbianTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_EYING_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetHoodTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_GUN_COOL_1, 5); + break; + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_FIGHT_1, 6); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_EYING_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_CHAT_1, 6); + break; + + default: + return GetGenericMaleTalkSfx(sound); + break; + } + return (SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetBlackCriminalTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_BLACK_CRIMINAL_VOICE_1_GUN_COOL_1, 4); + break; + case SOUND_PED_CAR_JACKING: + sfx = SFX_BLACK_CRIMINAL_VOICE_1_CARJACKING_1; + break; + case SOUND_PED_MUGGING: + GetPhrase(sfx, lastSfx, SFX_BLACK_CRIMINAL_VOICE_1_MUGGING_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_CRIMINAL_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + break; + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteCriminalTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_WHITE_CRIMINAL_VOICE_1_GUN_COOL_1, 3); + break; + case SOUND_PED_CAR_JACKING: + sfx = SFX_WHITE_CRIMINAL_VOICE_1_CARJACKING_1; + break; + case SOUND_PED_MUGGING: + GetPhrase(sfx, lastSfx, SFX_WHITE_CRIMINAL_VOICE_1_MUGGING_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_CRIMINAL_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_CRIMINAL_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_CRIMINAL_VOICE_1_DRIVER_ABUSE_1, 4); + break; + default: + return GetGenericMaleTalkSfx(sound); + break; + } + return sfx; +} + +uint32 +cAudioManager::GetCasualMaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_CARJACKED_1, 3); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_MUGGED_1, 4); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_EYING_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetCasualMaleYoungTalkSfx(uint16 sound) +{ + return GetGenericMaleTalkSfx(sound); +} + +uint32 +cAudioManager::GetBlackCasualFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_GUN_PANIC_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_MUGGED_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_RUN_FROM_FIGHT_1, 2); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteCasualFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_GUN_PANIC_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + sfx = SFX_WHITE_CASUAL_FEMALE_VOICE_1_MUGGED_1; + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_DODGE_1, 3); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, 2); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_1, 8); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetFemaleNo3TalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_GUN_PANIC_1, 5); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_CARJACKED_1, 3); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_MUGGED_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_RUN_FROM_FIGHT_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteBusinessFemaleTalkSfx(uint16 sound, uint32 model) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + + if (model == MI_B_WOM2) + sfx += (SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_1 - SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_1); + return sfx; +} + +uint32 +cAudioManager::GetBlackFatFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteFatMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch(sound) { + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_CARJACKED_1, 3); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_MUGGED_1, 3); break; + case SOUND_PED_EVADE: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_DODGE_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_1, 9); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_LOST_1, 2); break; + case SOUND_PED_CHAT: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_CHAT_1, 9); break; + default: return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackFatMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_CARJACKED_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_MUGGED_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_DODGE_1, 7); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_WAIT_DOUBLEBACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_LOST_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteFatFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_1, 8); + break; + case SOUND_PED_WAIT_DOUBLEBACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_LOST_1, 2); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackFemaleProstituteTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_GUN_COOL_1, 4); + break; + case SOUND_PED_ROBBED: + sfx = SFX_BLACK_PROSTITUTE_VOICE_1_MUGGED_1; + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_DODGE_1, 3); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_SOLICIT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_1, 8); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return (SFX_BLACK_PROSTITUTE_VOICE_2_CHAT_1 - SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetWhiteFemaleProstituteTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_DODGE_1, 3); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_SOLICIT: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_1, 8); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return (SFX_WHITE_PROSTITUTE_VOICE_2_CHAT_1 - SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetBlackProjectMaleTalkSfx(uint16 sound, uint32 model) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_GUN_COOL_1, 3); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_CARJACKED_1, 2); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_MUGGED_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_1, 6); break; + case SOUND_PED_EVADE: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_1, 5); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_1, 7); break; + case SOUND_PED_CHAT_SEXY: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_EYING_1, 3); break; + case SOUND_PED_CHAT: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_1, 6); break; + default: return GetGenericMaleTalkSfx(sound); + } + + if (model == MI_P_MAN2) + sfx += (SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_1); + return sfx; +} + +uint32 +cAudioManager::GetBlackProjectFemaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_1, 6); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_1, 10); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_1, 10); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackProjectFemaleYoungTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_CAR_JACKED: + sfx = SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CARJACKED_1; + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetChinatownMaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetChinatownMaleYoungTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_GUN_PANIC_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_1, 6); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetChinatownFemaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_OLD_FEMALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_OLD_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_EVENT: + sfx = SFX_CHINATOWN_OLD_FEMALE_VOICE_1_SHOCKED_1; + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetChinatownFemaleYoungTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetLittleItalyMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetLittleItalyFemaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetLittleItalyFemaleYoungTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_1, 7); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteDockerMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_GUN_PANIC_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_FIGHT_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackDockerMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetScumMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_GUN_PANIC_1, 5); + break; + case SOUND_PED_ROBBED: + sfx = SFX_SCUM_MALE_VOICE_1_MUGGED_1; + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_FIGHT_1, 10); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_WAIT_DOUBLEBACK: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_LOST_1, 3); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_EYING_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_CHAT_1, 9); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetScumFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_DODGE_1, 8); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_CHAT_1, 13); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteWorkerMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_FIGHT_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_EYING_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackWorkerMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_FIGHT_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_DODGE_1, 3); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackBusinessFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_1, 5); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CARAJACKED_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_MUGGED_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSupermodelMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSupermodelFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_MUGGED_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_SHOCKED_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetStewardMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_STEWARD_MALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_STEWARD_MALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_STEWARD_MALE_VOICE_1_DODGE_1, 3); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_STEWARD_MALE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetStewardFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_STEWARD_FEMALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_STEWARD_FEMALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_STEWARD_FEMALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return (SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_1 - SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetFanMaleTalkSfx(uint16 sound, uint32 model) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_MALE_VOICE_1_FIGHT_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_MALE_VOICE_1_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_MALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + if (model == MI_FAN_MAN2) + sfx += (SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_1); + return sfx; +} + +uint32 +cAudioManager::GetFanFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ROBBED: + sfx = SFX_FOOTBALL_FEMALE_VOICE_1_MUGGED_1; + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_FEMALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_FEMALE_VOICE_1_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return (SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_1 - SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetHospitalMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_MALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_MALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHospitalFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteConstructionWorkerTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + sfx = SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CARJACKED_1; + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackConstructionWorkerTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_EYING_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetShopperFemaleTalkSfx(uint16 sound, uint32 model) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + + if (model == MI_SHOPPER2) { + sfx += (SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_1 - SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_1); + } else if (model == MI_SHOPPER3) { + sfx += (SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_1 - SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_1); + } + return sfx; +} + +uint32 +cAudioManager::GetStudentMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_GUN_PANIC_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_SHOCKED_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetStudentFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSpecialCharacterTalkSfx(uint32 modelIndex, uint16 sound) +{ + char *modelName = CModelInfo::GetModelInfo(modelIndex)->GetModelName(); + if (!CGeneral::faststricmp(modelName, "eight") || !CGeneral::faststricmp(modelName, "eight2")) + return GetEightBallTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "frankie")) + return GetSalvatoreTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "misty")) + return GetMistyTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "ojg") || !CGeneral::faststricmp(modelName, "ojg_p")) + return GetOldJapTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "cat")) + return GetCatalinaTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "bomber")) + return GetBomberTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "s_guard")) + return GetSecurityGuardTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "chunky")) + return GetChunkyTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "asuka")) + return GetGenericFemaleTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "maria")) + return GetGenericFemaleTalkSfx(sound); + return GetGenericMaleTalkSfx(sound); +} + +uint32 +cAudioManager::GetEightBallTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_8BALL_GUN_COOL_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_8BALL_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_8BALL_FIGHT_1, 6); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_8BALL_DODGE_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSalvatoreTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_SALVATORE_GUN_COOL_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_SALVATORE_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_SALVATORE_FIGHT_1, 6); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_SALVATORE_DODGE_1, 3); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetMistyTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_MISTY_GUN_COOL_1, 5); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_MISTY_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_MISTY_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_MISTY_DODGE_1, 5); + break; + case SOUND_PED_TAXI_CALL: + GetPhrase(sfx, lastSfx, SFX_MISTY_HERE_1, 4); + break; + default: + return GetGenericFemaleTalkSfx(sound); + break; + } + return sfx; +} + +uint32 +cAudioManager::GetOldJapTalkSfx(uint16 sound) +{ + return GetGenericMaleTalkSfx(sound); +} + +uint32 +cAudioManager::GetCatalinaTalkSfx(uint16 sound) +{ + return GetGenericFemaleTalkSfx(sound); +} + +uint32 +cAudioManager::GetBomberTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) + { + case SOUND_PED_BOMBER: + GetPhrase(sfx, lastSfx, SFX_BOMBERMAN_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSecurityGuardTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_SECURITY_GUARD_VOICE_1_GUN_COOL_1, 2); + break; + case SOUND_PED_HANDS_COWER: + sfx = SFX_SECURITY_GUARD_VOICE_1_GUN_PANIC_1; + break; + case SOUND_PED_CAR_JACKED: + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_SECURITY_GUARD_VOICE_1_FIGHT_1, 2); + break; + case SOUND_PED_FLEE_RUN: +#ifdef FIX_BUGS + sfx = SFX_SECURITY_GUARD_VOICE_1_RUN_FROM_FIGHT_1; +#else + GetPhrase(sfx, lastSfx, SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_1, 12); +#endif + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetChunkyTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) + { + case SOUND_PED_DEATH: + return SFX_CHUNKY_DEATH; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_CHUNKY_RUN_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + return sfx; +} + +uint32 +cAudioManager::GetAsianTaxiDriverTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_1, 7); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + return (SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_1 - SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetPimpTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_PIMP_GUN_COOL_1, 7); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_PIMP_CARJACKED_1, 4); + break; + case SOUND_PED_DEFEND: + GetPhrase(sfx, lastSfx, SFX_PIMP_FIGHT_1, 9); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_PIMP_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_PIMP_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_PIMP_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_PIMP_CHAT_1, 17); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetNormalMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_GUN_PANIC_1, 7); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_CARJACKED_1, 7); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_DODGE_1, 9); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_RUN_FROM_FIGHT_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_DRIVER_ABUSE_1, 12); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_EYING_1, 8); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_SHOCKED_1, 10); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_CHAT_1, 25); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetGenericMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_DEATH: + GetPhrase(sfx, lastSfx, SFX_GENERIC_MALE_DEATH_1, 8); + break; + case SOUND_PED_BULLET_HIT: + case SOUND_PED_DEFEND: + GetPhrase(sfx, lastSfx, SFX_GENERIC_MALE_GRUNT_1, 15); + break; + case SOUND_PED_BURNING: + GetPhrase(sfx, lastSfx, SFX_GENERIC_MALE_FIRE_1, 8); + break; + case SOUND_PED_FLEE_SPRINT: + GetPhrase(sfx, lastSfx, SFX_GENERIC_MALE_PANIC_1, 6); + break; + default: + return NO_SAMPLE; + } + return sfx; +} + +uint32 +cAudioManager::GetGenericFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_DEATH: + GetPhrase(sfx, lastSfx, SFX_GENERIC_FEMALE_DEATH_1, 10); + break; + case SOUND_PED_BULLET_HIT: + case SOUND_PED_DEFEND: + GetPhrase(sfx, lastSfx, SFX_GENERIC_FEMALE_GRUNT_1, 11); + break; + case SOUND_PED_BURNING: + GetPhrase(sfx, lastSfx, SFX_GENERIC_FEMALE_FIRE_1, 9); + break; + case SOUND_PED_FLEE_SPRINT: + GetPhrase(sfx, lastSfx, SFX_GENERIC_FEMALE_PANIC_1, 8); + break; + default: + return NO_SAMPLE; + } + return sfx; +} + +void +cPedComments::Add(tPedComment *com) +{ + uint8 index; + + // copypasted priority check from cAudioManager::AddSampleToRequestedQueue + + if (m_nPedCommentCount[m_nActiveQueue] >= NUM_PED_COMMENTS_SLOTS) { + index = m_aPedCommentOrderList[m_nActiveQueue][NUM_PED_COMMENTS_SLOTS - 1]; + if (m_aPedCommentQueue[m_nActiveQueue][index].m_nVolume > com->m_nVolume) + return; + } else + index = m_nPedCommentCount[m_nActiveQueue]++; + + m_aPedCommentQueue[m_nActiveQueue][index] = *com; + + // this bit is basically copypasted cAudioManager::AddDetailsToRequestedOrderList + uint8 i = 0; + if (index != 0) { + for (i = 0; i < index; i++) { + if (m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][i]].m_nVolume < m_aPedCommentQueue[m_nActiveQueue][index].m_nVolume) + break; + } + + if (i < index) + memmove(&m_aPedCommentOrderList[m_nActiveQueue][i + 1], &m_aPedCommentOrderList[m_nActiveQueue][i], NUM_PED_COMMENTS_SLOTS - 1 - i); + } + + m_aPedCommentOrderList[m_nActiveQueue][i] = index; +} + +void +cPedComments::Process() +{ + uint32 sampleIndex; + uint8 queue; + + if (AudioManager.m_bIsPaused) return; + + if (m_nPedCommentCount[m_nActiveQueue]) { + sampleIndex = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nSampleIndex; + switch (SampleManager.IsPedCommentLoaded(sampleIndex)) + { + case LOADING_STATUS_NOT_LOADED: + SampleManager.LoadPedComment(sampleIndex); +#ifdef GTA_PS2 // on PC ped comment is loaded at once + break; +#endif + case LOADING_STATUS_LOADED: + AudioManager.m_sQueueSample.m_nEntityIndex = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nEntityIndex; + AudioManager.m_sQueueSample.m_nCounter = 0; + AudioManager.m_sQueueSample.m_nSampleIndex = sampleIndex; + AudioManager.m_sQueueSample.m_nBankIndex = SFX_BANK_PED_COMMENTS; + AudioManager.m_sQueueSample.m_nPriority = 3; + AudioManager.m_sQueueSample.m_nVolume = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nVolume; + AudioManager.m_sQueueSample.m_fDistance = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_fDistance; + AudioManager.m_sQueueSample.m_nLoopCount = 1; +#ifndef GTA_PS2 + AudioManager.m_sQueueSample.m_nLoopStart = 0; + AudioManager.m_sQueueSample.m_nLoopEnd = -1; +#endif // !GTA_PS2 +#ifdef EXTERNAL_3D_SOUND + #ifdef FIX_BUGS + AudioManager.m_sQueueSample.m_nEmittingVolume = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nEmittingVolume; + #else + AudioManager.m_sQueueSample.m_nEmittingVolume = MAX_VOLUME; + #endif // FIX_BUGS +#endif // EXTERNAL_3D_SOUND +#ifdef ATTACH_RELEASING_SOUNDS_TO_ENTITIES + // let's disable doppler because if sounds funny as the sound moves + // originally position of ped comment doesn't change so this has no effect anyway + AudioManager.m_sQueueSample.m_fSpeedMultiplier = 0.0f; +#else + AudioManager.m_sQueueSample.m_fSpeedMultiplier = 3.0f; +#endif + switch (sampleIndex) { + case SFX_POLICE_HELI_1: + case SFX_POLICE_HELI_2: + case SFX_POLICE_HELI_3: +#ifdef FIX_BUGS + case SFX_POLICE_HELI_4: + case SFX_POLICE_HELI_5: + case SFX_POLICE_HELI_6: + case SFX_POLICE_HELI_7: + case SFX_POLICE_HELI_8: + case SFX_POLICE_HELI_9: + case SFX_POLICE_HELI_10: + case SFX_POLICE_HELI_11: + case SFX_POLICE_HELI_12: + case SFX_POLICE_HELI_13: + case SFX_POLICE_HELI_14: + case SFX_POLICE_HELI_15: + case SFX_POLICE_HELI_16: + case SFX_POLICE_HELI_17: + case SFX_POLICE_HELI_18: + case SFX_POLICE_HELI_19: + case SFX_POLICE_HELI_20: + case SFX_POLICE_HELI_21: + case SFX_POLICE_HELI_22: + case SFX_POLICE_HELI_23: + case SFX_POLICE_HELI_24: + case SFX_POLICE_HELI_25: + case SFX_POLICE_HELI_26: + case SFX_POLICE_HELI_27: + case SFX_POLICE_HELI_28: + case SFX_POLICE_HELI_29: +#endif + AudioManager.m_sQueueSample.m_MaxDistance = PED_COMMENT_POLICE_HELI_MAX_DIST; + break; + default: + AudioManager.m_sQueueSample.m_MaxDistance = PED_COMMENT_MAX_DIST; + break; + } + AudioManager.m_sQueueSample.m_bStatic = TRUE; + AudioManager.m_sQueueSample.m_vecPos = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_vecPos; + + if (sampleIndex >= SFX_AMMU_D && sampleIndex <= SFX_AMMU_F) { + AudioManager.m_sQueueSample.m_bReverb = FALSE; +#ifdef AUDIO_REFLECTIONS + AudioManager.m_sQueueSample.m_bReflections = FALSE; +#endif +#ifdef FIX_BUGS + } + else if (sampleIndex >= SFX_POLICE_HELI_1 && sampleIndex <= SFX_POLICE_HELI_29) { + AudioManager.m_sQueueSample.m_bReverb = TRUE; +#ifdef AUDIO_REFLECTIONS + AudioManager.m_sQueueSample.m_bReflections = FALSE; +#endif // AUDIO_REFLECTIONS +#endif // FIX_BUGS + } + else { + AudioManager.m_sQueueSample.m_bReverb = TRUE; +#ifdef AUDIO_REFLECTIONS + AudioManager.m_sQueueSample.m_bReflections = TRUE; +#endif + } + + AudioManager.m_sQueueSample.m_bIs2D = FALSE; + AudioManager.m_sQueueSample.m_nFrequency = + SampleManager.GetSampleBaseFrequency(AudioManager.m_sQueueSample.m_nSampleIndex) + AudioManager.RandomDisplacement(750); +#ifndef USE_TIME_SCALE_FOR_AUDIO + if (CTimer::GetIsSlowMotionActive()) + AudioManager.m_sQueueSample.m_nFrequency >>= 1; +#endif + m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nLoadingTimeout = -1; + AudioManager.AddSampleToRequestedQueue(); + break; + case LOADING_STATUS_LOADING: break; + default: break; + } + } + + // Switch queue + if (m_nActiveQueue == 0) { + queue = 0; + m_nActiveQueue = 1; + } else { + queue = 1; + m_nActiveQueue = 0; + } + for (uint8 i = 0; i < m_nPedCommentCount[queue]; i++) { + if (m_aPedCommentQueue[queue][m_aPedCommentOrderList[queue][i]].m_nLoadingTimeout > 0) { + m_aPedCommentQueue[queue][m_aPedCommentOrderList[queue][i]].m_nLoadingTimeout--; + Add(&m_aPedCommentQueue[queue][m_aPedCommentOrderList[queue][i]]); + } + } + + // clear queue + for (uint8 i = 0; i < NUM_PED_COMMENTS_SLOTS; i++) + m_aPedCommentOrderList[queue][i] = NUM_PED_COMMENTS_SLOTS; + m_nPedCommentCount[queue] = 0; +} + +#pragma endregion + +#pragma endregion All the ped audio code + +void +cAudioManager::ProcessExplosions(int32 id) +{ + uint8 type; + float distSquared; + + for (uint8 i = 0; i < ARRAY_SIZE(gaExplosion); i++) { + if (CExplosion::GetExplosionActiveCounter(i) == 1) { + CExplosion::ResetExplosionActiveCounter(i); + type = CExplosion::GetExplosionType(i); + switch (type) { + case EXPLOSION_GRENADE: + case EXPLOSION_ROCKET: + case EXPLOSION_BARREL: + case EXPLOSION_TANK_GRENADE: + m_sQueueSample.m_MaxDistance = EXPLOSION_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_2; +#ifdef GTA_PS2 + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 19000; +#else + m_sQueueSample.m_nFrequency = RandomDisplacement(2000) + 38000; +#endif + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + break; + case EXPLOSION_MINE: + case EXPLOSION_HELI_BOMB: + m_sQueueSample.m_MaxDistance = EXPLOSION_MINE_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_ROCKET_LEFT; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 12347; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + break; + case EXPLOSION_MOLOTOV: + m_sQueueSample.m_MaxDistance = EXPLOSION_MOLOTOV_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_3; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 19000; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + break; + default: + m_sQueueSample.m_MaxDistance = EXPLOSION_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_1; +#ifdef GTA_PS2 + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 19000; +#else + m_sQueueSample.m_nFrequency = RandomDisplacement(2000) + 38000; +#endif + if (type == EXPLOSION_HELI) + m_sQueueSample.m_nFrequency = 8 * m_sQueueSample.m_nFrequency / 10; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_GENERIC_EXTRA; + break; + } + m_sQueueSample.m_vecPos = *CExplosion::GetExplosionPosition(i); + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(m_sQueueSample.m_MaxDistance)) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(MAX_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = TRUE; + SET_EMITTING_VOLUME(MAX_VOLUME); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(TRUE); + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::ProcessFires(int32 id) +{ + CEntity *entity; + uint8 Vol; + float distSquared; + + for (uint8 i = 0; i < NUM_FIRES; i++) { + if (gFireManager.m_aFires[i].m_bIsOngoing && gFireManager.m_aFires[i].m_bAudioSet) { + entity = gFireManager.m_aFires[i].m_pEntity; + if (entity) { + switch (entity->GetType()) { + case ENTITY_TYPE_BUILDING: + m_sQueueSample.m_MaxDistance = FIRE_BUILDING_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + Vol = FIRE_BUILDING_VOLUME; + m_sQueueSample.m_nFrequency = 8 * SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE) / 10; + m_sQueueSample.m_nFrequency += i * (m_sQueueSample.m_nFrequency >> 6); + m_sQueueSample.m_nPriority = 6; + break; + case ENTITY_TYPE_PED: + m_sQueueSample.m_MaxDistance = FIRE_PED_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_PED_ON_FIRE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PED_ON_FIRE); + Vol = FIRE_PED_VOLUME; + m_sQueueSample.m_nFrequency += i * (m_sQueueSample.m_nFrequency >> 6); + m_sQueueSample.m_nPriority = 10; + break; + default: + m_sQueueSample.m_MaxDistance = FIRE_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE); + m_sQueueSample.m_nFrequency += i * (m_sQueueSample.m_nFrequency >> 6); + Vol = FIRE_DEFAULT_VOLUME; + m_sQueueSample.m_nPriority = 8; + } + } else { + m_sQueueSample.m_MaxDistance = FIRE_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE); + m_sQueueSample.m_nFrequency += i * (m_sQueueSample.m_nFrequency >> 6); + Vol = FIRE_DEFAULT_VOLUME; + m_sQueueSample.m_nPriority = 8; + } + m_sQueueSample.m_vecPos = gFireManager.m_aFires[i].m_vecPos; + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(m_sQueueSample.m_MaxDistance)) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_nFramesToPlay = 10; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::ProcessWaterCannon(int32 id) +{ + for (uint32 i = 0; i < NUM_WATERCANNONS; i++) { + if (CWaterCannons::aCannons[i].m_nId) { + m_sQueueSample.m_vecPos = CWaterCannons::aCannons[0].m_avecPos[CWaterCannons::aCannons[i].m_nCur]; + float distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(WATER_CANNON_MAX_DIST)) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); +#ifdef FIX_BUGS + m_sQueueSample.m_nVolume = ComputeVolume(WATER_CANNON_VOLUME, WATER_CANNON_MAX_DIST, m_sQueueSample.m_fDistance); +#else + m_sQueueSample.m_nVolume = ComputeVolume(WATER_CANNON_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); +#endif + if (m_sQueueSample.m_nVolume > 0) { +#ifdef FIX_BUGS + m_sQueueSample.m_MaxDistance = WATER_CANNON_MAX_DIST; +#else + m_sQueueSample.m_MaxDistance = SQR(WATER_CANNON_MAX_DIST); +#endif + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_TAXI; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 15591; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_nFramesToPlay = 8; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + SET_EMITTING_VOLUME(WATER_CANNON_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + } +} + +#pragma region SCRIPT_OBJECTS +void +cAudioManager::ProcessScriptObject(int32 id) +{ + cAudioScriptObject *entity = (cAudioScriptObject *)m_asAudioEntities[id].m_pEntity; + if (entity != nil) { + m_sQueueSample.m_vecPos = entity->Posn; + if (m_asAudioEntities[id].m_AudioEvents == 1) + ProcessOneShotScriptObject(m_asAudioEntities[id].m_awAudioEvent[0]); + else + ProcessLoopingScriptObject(entity->AudioId); + } +} + +void +cAudioManager::ProcessOneShotScriptObject(uint8 sound) +{ + CPlayerPed *playerPed; + uint8 Vol; + float distSquared; + + static uint8 iSound = 0; + + switch (sound) { + case SCRIPT_SOUND_BOX_DESTROYED_1: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_BOX_DESTROYED_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_WOODEN_BOX_SMASH; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = RandomDisplacement(1500) + 18600; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[2] % 20 + SCRIPT_OBJECT_BOX_DESTROYED_VOLUME; + break; + case SCRIPT_SOUND_BOX_DESTROYED_2: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_BOX_DESTROYED_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_CARDBOARD_BOX_SMASH; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = RandomDisplacement(1500) + 18600; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[2] % 20 + SCRIPT_OBJECT_BOX_DESTROYED_VOLUME; + break; + case SCRIPT_SOUND_METAL_COLLISION: + m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST; + m_sQueueSample.m_nSampleIndex = m_anRandomTable[3] % 5 + SFX_COL_CAR_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[2] % 30 + SCRIPT_OBJECT_METAL_COLLISION_VOLUME; + break; + case SCRIPT_SOUND_TIRE_COLLISION: + m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_TYRE_BUMP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[2] % 30 + SCRIPT_OBJECT_TIRE_COLLISION_VOLUME; + break; + case SCRIPT_SOUND_GUNSHELL_DROP: + playerPed = FindPlayerPed(); + if (playerPed) { + switch (playerPed->m_nSurfaceTouched) { + case SURFACE_GRASS: + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: + case SURFACE_TRANSPARENT_CLOTH: + case SURFACE_PED: + case SURFACE_SAND: + case SURFACE_RUBBER: + case SURFACE_HEDGE: + m_sQueueSample.m_nSampleIndex = SFX_BULLET_SHELL_HIT_GROUND_2; + m_sQueueSample.m_nFrequency = RandomDisplacement(500) + 11000; + m_sQueueSample.m_nPriority = 18; + break; + case SURFACE_WATER: + return; + default: + m_sQueueSample.m_nSampleIndex = SFX_BULLET_SHELL_HIT_GROUND_1; + m_sQueueSample.m_nFrequency = RandomDisplacement(750) + 18000; + m_sQueueSample.m_nPriority = 15; + break; + } + } else { + m_sQueueSample.m_nSampleIndex = SFX_BULLET_SHELL_HIT_GROUND_1; + m_sQueueSample.m_nFrequency = RandomDisplacement(750) + 18000; + m_sQueueSample.m_nPriority = 15; + } + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GUNSHELL_MAX_DIST; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + Vol = m_anRandomTable[2] % 20 + SCRIPT_OBJECT_GUNSHELL_VOLUME; + break; + case SCRIPT_SOUND_GUNSHELL_DROP_SOFT: + m_sQueueSample.m_nSampleIndex = SFX_BULLET_SHELL_HIT_GROUND_2; + m_sQueueSample.m_nFrequency = RandomDisplacement(500) + 11000; + m_sQueueSample.m_nPriority = 18; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GUNSHELL_MAX_DIST; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + Vol = m_anRandomTable[2] % 20 + SCRIPT_OBJECT_GUNSHELL_VOLUME; + break; + case SCRIPT_SOUND_BULLET_HIT_GROUND_1: + case SCRIPT_SOUND_BULLET_HIT_GROUND_2: + case SCRIPT_SOUND_BULLET_HIT_GROUND_3: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_BULLET_HIT_GROUND_MAX_DIST; + m_sQueueSample.m_nSampleIndex = m_anRandomTable[iSound % 5] % 3 + SFX_BULLET_WALL_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 9; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + Vol = m_anRandomTable[2] % 20 + SCRIPT_OBJECT_BULLET_HIT_GROUND_VOLUME; + break; + case SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_1: + case SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_2: + if (SampleManager.IsSampleBankLoaded(SFX_BANK_TRAIN) != LOADING_STATUS_LOADED) + return; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_TRAIN_ANNOUNCEMENT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_TRAIN_STATION_ANNOUNCE; + m_sQueueSample.m_nBankIndex = SFX_BANK_TRAIN; + Vol = SCRIPT_OBJECT_TRAIN_ANNOUNCEMENT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_TRAIN_STATION_ANNOUNCE); + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_PAYPHONE_RINGING: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_PAYPHONE_RINGING_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_PHONE_RING; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_PAYPHONE_RINGING_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PHONE_RING); + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(FALSE); + break; + case SCRIPT_SOUND_GLASS_BREAK_L: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GLASS_BREAK_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_GLASS_SMASH; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_GLASS_BREAK_LONG_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_GLASS_SMASH); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_GLASS_BREAK_S: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GLASS_BREAK_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_GLASS_SMASH; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_GLASS_BREAK_SHORT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_GLASS_SMASH); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_GLASS_CRACK: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GLASS_BREAK_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_GLASS_CRACK; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_GLASS_BREAK_LONG_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_GLASS_CRACK); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + break; + case SCRIPT_SOUND_GLASS_LIGHT_BREAK: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GLASS_LIGHT_BREAK_MAX_DIST; + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[4] & 3) + SFX_GLASS_SHARD_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = RandomDisplacement(2000) + 19000; + m_sQueueSample.m_nPriority = 9; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + Vol = RandomDisplacement(11) + SCRIPT_OBJECT_GLASS_LIGHT_BREAK_VOLUME; + break; + case SCRIPT_SOUND_INJURED_PED_MALE_OUCH_S: + case SCRIPT_SOUND_INJURED_PED_MALE_OUCH_L: + { + cPedParams pedParams; + pedParams.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + SetupPedComments(pedParams, SOUND_INJURED_PED_MALE_OUCH); + return; + } + case SCRIPT_SOUND_INJURED_PED_FEMALE_OUCH_S: + case SCRIPT_SOUND_INJURED_PED_FEMALE_OUCH_L: + { + cPedParams pedParams; + pedParams.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + SetupPedComments(pedParams, SOUND_INJURED_PED_FEMALE); + return; + } + case SCRIPT_SOUND_GATE_START_CLUNK: + case SCRIPT_SOUND_GATE_STOP_CLUNK: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GATE_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_GATE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + if (sound == SCRIPT_SOUND_GATE_START_CLUNK) + m_sQueueSample.m_nFrequency = 10600; + else + m_sQueueSample.m_nFrequency = 9000; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = RandomDisplacement(10) + 50; + break; + default: + return; + } + + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(m_sQueueSample.m_MaxDistance)) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessLoopingScriptObject(uint8 sound) +{ + uint8 Vol; + float distSquared; + float maxDistSquared; + bool8 bLoadBank = FALSE; + + switch (sound) { + case SCRIPT_SOUND_PORN_CINEMA_1_S: + case SCRIPT_SOUND_PORN_CINEMA_1_L: + case SCRIPT_SOUND_PORN_CINEMA_2_S: + case SCRIPT_SOUND_PORN_CINEMA_2_L: + case SCRIPT_SOUND_PORN_CINEMA_3_S: + case SCRIPT_SOUND_PORN_CINEMA_3_L: + case SCRIPT_SOUND_MISTY_SEX_S: + case SCRIPT_SOUND_MISTY_SEX_L: + ProcessPornCinema(sound); + return; + case SCRIPT_SOUND_WORK_SHOP_LOOP_S: + case SCRIPT_SOUND_WORK_SHOP_LOOP_L: + ProcessWorkShopScriptObject(sound); + return; + case SCRIPT_SOUND_SAWMILL_LOOP_S: + case SCRIPT_SOUND_SAWMILL_LOOP_L: + ProcessSawMillScriptObject(sound); + return; + case SCRIPT_SOUND_LAUNDERETTE_LOOP_S: + case SCRIPT_SOUND_LAUNDERETTE_LOOP_L: + ProcessLaunderetteScriptObject(sound); + return; + case SCRIPT_SOUND_SHOP_LOOP_S: + case SCRIPT_SOUND_SHOP_LOOP_L: + ProcessShopScriptObject(sound); + return; + case SCRIPT_SOUND_AIRPORT_LOOP_S: + case SCRIPT_SOUND_AIRPORT_LOOP_L: + ProcessAirportScriptObject(sound); + return; + case SCRIPT_SOUND_CINEMA_LOOP_S: + case SCRIPT_SOUND_CINEMA_LOOP_L: + ProcessCinemaScriptObject(sound); + return; + case SCRIPT_SOUND_DOCKS_LOOP_S: + case SCRIPT_SOUND_DOCKS_LOOP_L: + ProcessDocksScriptObject(sound); + return; + case SCRIPT_SOUND_HOME_LOOP_S: + case SCRIPT_SOUND_HOME_LOOP_L: + ProcessHomeScriptObject(sound); + return; + case SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_S: + case SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_L: + ProcessPoliceCellBeatingScriptObject(sound); + return; + case SCRIPT_SOUND_BANK_ALARM_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto BankAlarm; + case SCRIPT_SOUND_BANK_ALARM_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +BankAlarm: + m_sQueueSample.m_nSampleIndex = SFX_BANK_ALARM_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_BANK_ALARM; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_BANK_ALARM_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_BANK_ALARM_1); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_POLICE_BALL_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto PoliceBall; + case SCRIPT_SOUND_POLICE_BALL_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +PoliceBall: + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BALL_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_POLICE_BALL; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_POLICE_BALL_1); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_1_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party1; + case SCRIPT_SOUND_PARTY_1_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party1: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_1; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_1); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_2_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party2; + case SCRIPT_SOUND_PARTY_2_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party2: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_2; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_2); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_3_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party3; + case SCRIPT_SOUND_PARTY_3_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party3: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_3; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_3; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_3); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_4_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party4; + case SCRIPT_SOUND_PARTY_4_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party4: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_4; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_4; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_5_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party5; + case SCRIPT_SOUND_PARTY_5_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party5: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_5; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_5; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_6_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party6; + case SCRIPT_SOUND_PARTY_6_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party6: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_6; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_6; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_6); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_7_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party7; + case SCRIPT_SOUND_PARTY_7_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party7: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_7; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_7; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_7); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_8_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party8; + case SCRIPT_SOUND_PARTY_8_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party8: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_8; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_8; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_8); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_9_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party9; + case SCRIPT_SOUND_PARTY_9_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party9: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_9; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_9; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_9); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_10_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party10; + case SCRIPT_SOUND_PARTY_10_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party10: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_10; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_10; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_10); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_11_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party11; + case SCRIPT_SOUND_PARTY_11_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party11: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_11; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_11; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_11); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_12_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party12; + case SCRIPT_SOUND_PARTY_12_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party12: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_12; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_12; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_12); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_13_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party13; + case SCRIPT_SOUND_PARTY_13_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party13: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_RAGGA; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_RAGGA; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_RAGGA); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_STRIP_CLUB_LOOP_1_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto StripClub1; + case SCRIPT_SOUND_STRIP_CLUB_LOOP_1_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +StripClub1: + m_sQueueSample.m_nSampleIndex = SFX_STRIP_CLUB_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_STRIP_CLUB_1; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_STRIP_CLUB_1); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_STRIP_CLUB_LOOP_2_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto StripClub2; + case SCRIPT_SOUND_STRIP_CLUB_LOOP_2_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +StripClub2: + m_sQueueSample.m_nSampleIndex = SFX_STRIP_CLUB_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_STRIP_CLUB_2; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_STRIP_CLUB_2); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_1_LOOP: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party1; // BUG? Shouldn't this be Frankie piano? + case SCRIPT_SOUND_FRANKIE_PIANO: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_PIANO_BAR_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PIANO_BAR; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PIANO_BAR_1); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_DOG_FOOD_FACTORY_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto DogFoodFactory; + case SCRIPT_SOUND_DOG_FOOD_FACTORY_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +DogFoodFactory: + m_sQueueSample.m_nSampleIndex = SFX_DOG_FOOD_FACTORY; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_DOG_FOOD_FACTORY; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_RESAURANT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_DOG_FOOD_FACTORY); + m_sQueueSample.m_nPriority = 6; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_CHINATOWN_RESTAURANT_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto ChinatownRestaurant; + case SCRIPT_SOUND_CHINATOWN_RESTAURANT_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +ChinatownRestaurant: + m_sQueueSample.m_nSampleIndex = SFX_RESTAURANT_CHINATOWN; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RESTAURANT_CHINATOWN; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_RESAURANT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RESTAURANT_CHINATOWN); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_CIPRIANI_RESAURANT_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto CiprianiRestaurant; + case SCRIPT_SOUND_CIPRIANI_RESAURANT_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +CiprianiRestaurant: + m_sQueueSample.m_nSampleIndex = SFX_RESTAURANT_ITALY; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RESTAURANT_ITALY; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_RESAURANT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RESTAURANT_ITALY); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_47_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto GenericRestaurant1; + case SCRIPT_SOUND_46_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +GenericRestaurant1: + m_sQueueSample.m_nSampleIndex = SFX_RESTAURANT_GENERIC_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RESTAURANT_GENERIC_1; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_RESAURANT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RESTAURANT_GENERIC_1); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_MARCO_BISTRO_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto GenericRestaurant2; + case SCRIPT_SOUND_MARCO_BISTRO_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +GenericRestaurant2: + m_sQueueSample.m_nSampleIndex = SFX_RESTAURANT_GENERIC_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RESTAURANT_GENERIC_2; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_RESAURANT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RESTAURANT_GENERIC_2); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_RAVE_LOOP_INDUSTRIAL_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto RaveLoop; + case SCRIPT_SOUND_RAVE_LOOP_INDUSTRIAL_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +RaveLoop: + m_sQueueSample.m_nSampleIndex = SFX_RAVE_INDUSTRIAL; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RAVE_INDUSTRIAL; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RAVE_INDUSTRIAL); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_RAVE_1_LOOP_L: + case SCRIPT_SOUND_RAVE_2_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Rave1; + case SCRIPT_SOUND_RAVE_1_LOOP_S: + case SCRIPT_SOUND_RAVE_2_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Rave1: + m_sQueueSample.m_nSampleIndex = SFX_RAVE_COMMERCIAL; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RAVE_COMMERCIAL; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_RAVE_3_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Rave3; + case SCRIPT_SOUND_RAVE_3_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Rave3: + m_sQueueSample.m_nSampleIndex = SFX_RAVE_SUBURBAN; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RAVE_SUBURBAN; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RAVE_SUBURBAN); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PRETEND_FIRE_LOOP: + maxDistSquared = SQR(FIRE_DEFAULT_MAX_DIST); + m_sQueueSample.m_MaxDistance = FIRE_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = FIRE_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE); + m_sQueueSample.m_nPriority = 8; + m_sQueueSample.m_nFramesToPlay = 10; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + default: + return; + } + + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { +#ifdef GTA_PS2 + if (bLoadBank && !LoadBankIfNecessary(m_sQueueSample.m_nBankIndex)) + return; +#endif + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = TRUE; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessPornCinema(uint8 sound) +{ + eSfxSample sample; + uint32 time; + int32 rand; + float distSquared; + float maxDistSquared; + + switch (sound) { + case SCRIPT_SOUND_PORN_CINEMA_1_S: + case SCRIPT_SOUND_MISTY_SEX_S: + m_sQueueSample.m_nSampleIndex = SFX_PORN_1_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_1; + maxDistSquared = SQR(PORN_CINEMA_SHORT_MAX_DIST); + sample = SFX_PORN_1_GROAN_1; + m_sQueueSample.m_MaxDistance = PORN_CINEMA_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_PORN_CINEMA_1_L: + case SCRIPT_SOUND_MISTY_SEX_L: + m_sQueueSample.m_nSampleIndex = SFX_PORN_1_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_1; + maxDistSquared = SQR(PORN_CINEMA_LONG_MAX_DIST); + sample = SFX_PORN_1_GROAN_1; + m_sQueueSample.m_MaxDistance = PORN_CINEMA_LONG_MAX_DIST; + break; + case SCRIPT_SOUND_PORN_CINEMA_2_S: + m_sQueueSample.m_nSampleIndex = SFX_PORN_2_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_2; + maxDistSquared = SQR(PORN_CINEMA_SHORT_MAX_DIST); + sample = SFX_PORN_2_GROAN_1; + m_sQueueSample.m_MaxDistance = PORN_CINEMA_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_PORN_CINEMA_2_L: + m_sQueueSample.m_nSampleIndex = SFX_PORN_2_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_2; + maxDistSquared = SQR(PORN_CINEMA_LONG_MAX_DIST); + sample = SFX_PORN_2_GROAN_1; + m_sQueueSample.m_MaxDistance = PORN_CINEMA_LONG_MAX_DIST; + break; + case SCRIPT_SOUND_PORN_CINEMA_3_S: + m_sQueueSample.m_nSampleIndex = SFX_PORN_3_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_3; + maxDistSquared = SQR(PORN_CINEMA_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = PORN_CINEMA_SHORT_MAX_DIST; + sample = SFX_PORN_3_GROAN_1; + break; + case SCRIPT_SOUND_PORN_CINEMA_3_L: + m_sQueueSample.m_nSampleIndex = SFX_PORN_3_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_3; + maxDistSquared = SQR(PORN_CINEMA_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = PORN_CINEMA_LONG_MAX_DIST; + sample = SFX_PORN_3_GROAN_1; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(m_sQueueSample.m_nBankIndex)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + if (sound != SCRIPT_SOUND_MISTY_SEX_S && sound != SCRIPT_SOUND_MISTY_SEX_L) { + m_sQueueSample.m_nVolume = ComputeVolume(PORN_CINEMA_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(PORN_CINEMA_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + + time = CTimer::GetTimeInMilliseconds(); + if (time > gPornNextTime) { + m_sQueueSample.m_nVolume = ComputeVolume(PORN_CINEMA_MOAN_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + rand = m_anRandomTable[1] & 1; + m_sQueueSample.m_nSampleIndex = rand + sample; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nCounter = rand + 1; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 6; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; +#ifdef FIX_BUGS + SET_EMITTING_VOLUME(PORN_CINEMA_MOAN_VOLUME); +#endif + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gPornNextTime = time + 2000 + m_anRandomTable[3] % 6000; + } + } + } +} + +void +cAudioManager::ProcessWorkShopScriptObject(uint8 sound) +{ + float distSquared; + float maxDistSquared; + + switch (sound) { + case SCRIPT_SOUND_WORK_SHOP_LOOP_S: + case SCRIPT_SOUND_WORK_SHOP_LOOP_L: + maxDistSquared = SQR(WORK_SHOP_MAX_DIST); + m_sQueueSample.m_MaxDistance = WORK_SHOP_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_WORKSHOP)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(WORK_SHOP_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_WORKSHOP_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_WORKSHOP; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_WORKSHOP_1); + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(WORK_SHOP_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessSawMillScriptObject(uint8 sound) +{ + uint32 time; + float distSquared; + float maxDistSquared; + + switch (sound) { + case SCRIPT_SOUND_SAWMILL_LOOP_S: + case SCRIPT_SOUND_SAWMILL_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_SAWMILL)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(SAWMILL_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_SAWMILL_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_SAWMILL; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SAWMILL_LOOP); + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(SAWMILL_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + time = CTimer::GetTimeInMilliseconds(); + if (time > gSawMillNextTime) { + m_sQueueSample.m_nVolume = ComputeVolume(SAWMILL_CUT_WOOD_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_SAWMILL_CUT_WOOD; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_SAWMILL; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; +#ifdef FIX_BUGS + SET_EMITTING_VOLUME(SAWMILL_CUT_WOOD_VOLUME); +#endif + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gSawMillNextTime = time + 2000 + m_anRandomTable[3] % 4000; + } + } + } +} + +void +cAudioManager::ProcessLaunderetteScriptObject(uint8 sound) +{ + float maxDistSquared; + + switch (sound) { + case SCRIPT_SOUND_LAUNDERETTE_LOOP_S: + case SCRIPT_SOUND_LAUNDERETTE_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + float distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_LAUNDERETTE)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(LAUNDERETTE_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_LAUNDERETTE_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_LAUNDERETTE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_LAUNDERETTE_LOOP); + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(LAUNDERETTE_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + m_sQueueSample.m_nVolume = ComputeVolume(LAUNDERETTE_SONG_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_LAUNDERETTE_SONG_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_LAUNDERETTE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_LAUNDERETTE_SONG_LOOP); + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(LAUNDERETTE_SONG_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessShopScriptObject(uint8 sound) +{ + uint32 time; + int32 rand; + float distSquared; + float maxDistSquared; + + switch (sound) { + case SCRIPT_SOUND_SHOP_LOOP_S: + case SCRIPT_SOUND_SHOP_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_SHOP)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(SHOP_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_SHOP_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_SHOP; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SHOP_LOOP); + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(SHOP_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + time = CTimer::GetTimeInMilliseconds(); + if (time > gShopNextTime) { + m_sQueueSample.m_nVolume = ComputeVolume(SHOP_TILL_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + rand = m_anRandomTable[1] & 1; + m_sQueueSample.m_nSampleIndex = rand + SFX_SHOP_TILL_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_SHOP; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nCounter = rand + 1; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(SHOP_TILL_VOLUME); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gShopNextTime = time + 3000 + m_anRandomTable[3] % 7000; + } + } + } +} + +void +cAudioManager::ProcessAirportScriptObject(uint8 sound) +{ + float maxDistSquared; + static uint8 iSound = 0; + + uint32 time = CTimer::GetTimeInMilliseconds(); + if (time > gAirportNextTime) { + switch (sound) { + case SCRIPT_SOUND_AIRPORT_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_AIRPORT_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + float distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_AIRPORT)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(AIRPORT_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[1] & 3) + SFX_AIRPORT_ANNOUNCEMENT_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_AIRPORT; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(AIRPORT_VOLUME); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gAirportNextTime = time + 10000 + m_anRandomTable[3] % 20000; + } + } + } +} + +void +cAudioManager::ProcessCinemaScriptObject(uint8 sound) +{ + float maxDistSquared; + uint8 Vol; + + static uint8 iSound = 0; + + uint32 time = CTimer::GetTimeInMilliseconds(); + if (time > gCinemaNextTime) { + switch (sound) { + case SCRIPT_SOUND_CINEMA_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_CINEMA_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + float distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_CINEMA)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + Vol = m_anRandomTable[0] % 90 + CINEMA_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = iSound % 3 + SFX_CINEMA_BASS_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CINEMA; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gCinemaNextTime = time + 1000 + m_anRandomTable[3] % 4000; + } + } + } +} + +void +cAudioManager::ProcessDocksScriptObject(uint8 sound) +{ + uint32 time; + uint8 Vol; + float distSquared; + float maxDistSquared; + + static uint8 iSound = 0; + + time = CTimer::GetTimeInMilliseconds(); + if (time > gDocksNextTime) { + switch (sound) { + case SCRIPT_SOUND_DOCKS_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_DOCKS_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_DOCKS)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + Vol = m_anRandomTable[0] % 60 + DOCKS_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_DOCKS_FOGHORN; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_DOCKS; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_DOCKS_FOGHORN); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 3); + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gDocksNextTime = time + 10000 + m_anRandomTable[3] % 40000; + } + } + } +} +void +cAudioManager::ProcessHomeScriptObject(uint8 sound) +{ + uint32 time; + uint8 Vol; + float dist; + float maxDistSquared; + + static uint8 iSound = 0; + + time = CTimer::GetTimeInMilliseconds(); + if (time > gHomeNextTime) { + switch (sound) { + case SCRIPT_SOUND_HOME_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_HOME_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + dist = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (dist < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_HOME)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(dist); + Vol = m_anRandomTable[0] % 30 + HOME_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = m_anRandomTable[0] % 5 + SFX_HOME_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_HOME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(TRUE); + AddSampleToRequestedQueue(); + gHomeNextTime = time + 1000 + m_anRandomTable[3] % 4000; + } + } + } +} +void +cAudioManager::ProcessPoliceCellBeatingScriptObject(uint8 sound) +{ + uint32 time = CTimer::GetTimeInMilliseconds(); + int32 sampleIndex; + uint8 Vol; + float distSquared; + float maxDistSquared; + + static uint8 iSound = 0; + + if (time > gCellNextTime) { + switch (sound) { + case SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + if (m_FrameCounter & 1) + sampleIndex = (m_anRandomTable[1] & 3) + SFX_FIGHT_1; + else + sampleIndex = (m_anRandomTable[3] & 1) + SFX_BAT_HIT_LEFT; + m_sQueueSample.m_nSampleIndex = sampleIndex; + Vol = m_anRandomTable[0] % 50 + POLICE_CELL_BEATING_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + cPedParams params; + params.m_bDistanceCalculated = TRUE; + params.m_fDistance = distSquared; + SetupPedComments(params, SOUND_INJURED_PED_MALE_PRISON); + } + gCellNextTime = time + 500 + m_anRandomTable[3] % 1500; + } + } +} +#pragma endregion All the code for script object audio on the map + +void +cAudioManager::ProcessWeather(int32 id) +{ + uint8 Vol; + static uint8 iSound = 0; + + if (m_asAudioEntities[id].m_AudioEvents > 0 && m_asAudioEntities[id].m_awAudioEvent[0] == SOUND_LIGHTNING) { + if (m_asAudioEntities[id].m_afVolume[0] < 10) { + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = RandomDisplacement(500) + 4000; + Vol = (m_asAudioEntities[id].m_afVolume[0] * 10.0f * 0.1f); + Vol += 35; + } else { + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_GENERIC_EXTRA; + m_sQueueSample.m_nFrequency = RandomDisplacement(500) + 4000; + Vol = ((m_asAudioEntities[id].m_afVolume[0] - 10.0f) * 10.0f * 0.1f); + Vol += 40; + } + m_sQueueSample.m_nVolume = Vol; + if (TheCamera.SoundDistUp < 20.0f) + m_sQueueSample.m_nVolume >>= 1; + if (iSound == 4) + iSound = 0; + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nPan = (m_anRandomTable[2] % 16) + 55; + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + SET_EMITTING_VOLUME(m_sQueueSample.m_nVolume); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + if (CWeather::Rain > 0.0f && (!CCullZones::CamNoRain() || !CCullZones::PlayerNoRain())) { + m_sQueueSample.m_nSampleIndex = SFX_RAIN; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RAIN); + m_sQueueSample.m_nVolume = (uint8)(25.0f * CWeather::Rain); + m_sQueueSample.m_nCounter = 4; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nPan = 63; + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 30; + m_sQueueSample.m_bReverb = FALSE; + SET_EMITTING_VOLUME(m_sQueueSample.m_nVolume); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } +} + +void +cAudioManager::ProcessFrontEnd() +{ + bool8 stereo; + bool8 processedPickup; + bool8 processedMission; + bool8 frontendBank; + int16 sample; + + static uint8 iSound = 0; + static uint32 cPickupNextFrame = 0; + static uint32 cPartMisComNextFrame = 0; + + for (uint32 i = 0; i < m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents; i++) { + processedPickup = FALSE; + stereo = FALSE; + processedMission = FALSE; + frontendBank = FALSE; + switch (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]) { + case SOUND_FRONTEND_RADIO_TURN_OFF: + case SOUND_FRONTEND_RADIO_CHANGE: + m_sQueueSample.m_nSampleIndex = SFX_RADIO_CLICK; + break; + case SOUND_HUD: + m_sQueueSample.m_nSampleIndex = SFX_INFO; + break; + case SOUND_FRONTEND_MENU_STARTING: + m_sQueueSample.m_nSampleIndex = SFX_START_BUTTON_LEFT; + stereo = TRUE; + break; + case SOUND_FRONTEND_MENU_NEW_PAGE: + m_sQueueSample.m_nSampleIndex = SFX_PAGE_CHANGE_AND_BACK_LEFT; + stereo = TRUE; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_MENU_NAVIGATION: + m_sQueueSample.m_nSampleIndex = SFX_HIGHLIGHT_LEFT; + stereo = TRUE; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_MENU_SETTING_CHANGE: + m_sQueueSample.m_nSampleIndex = SFX_SELECT_LEFT; + stereo = TRUE; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_MENU_BACK: + m_sQueueSample.m_nSampleIndex = SFX_SUB_MENU_BACK_LEFT; + stereo = TRUE; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_STEREO: + m_sQueueSample.m_nSampleIndex = SFX_STEREO_LEFT; + stereo = TRUE; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_MONO: + m_sQueueSample.m_nSampleIndex = SFX_MONO; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_AUDIO_TEST: + m_sQueueSample.m_nSampleIndex = m_anRandomTable[0] % 3 + SFX_NOISE_BURST_1; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_FAIL: + m_sQueueSample.m_nSampleIndex = SFX_ERROR_LEFT; + frontendBank = TRUE; + stereo = TRUE; + break; + case SOUND_RACE_START_GO: + m_sQueueSample.m_nSampleIndex = SFX_PART_MISSION_COMPLETE; + break; + case SOUND_PART_MISSION_COMPLETE: + m_sQueueSample.m_nSampleIndex = SFX_PART_MISSION_COMPLETE; + processedMission = TRUE; + break; + case SOUND_RACE_START_3: + case SOUND_RACE_START_2: + case SOUND_RACE_START_1: + case SOUND_CLOCK_TICK: + m_sQueueSample.m_nSampleIndex = SFX_TIMER_BEEP; + break; + case SOUND_PAGER: +#ifdef GTA_PS2 + { + cPedParams pedParams; + pedParams.m_bDistanceCalculated = TRUE; + SetupPedComments(pedParams, SOUND_PAGER); + continue; + } +#else + m_sQueueSample.m_nSampleIndex = SFX_PAGER; + break; +#endif + case SOUND_WEAPON_SNIPER_SHOT_NO_ZOOM: + m_sQueueSample.m_nSampleIndex = SFX_ERROR_FIRE_RIFLE; + break; + case SOUND_WEAPON_ROCKET_SHOT_NO_ZOOM: + m_sQueueSample.m_nSampleIndex = SFX_ERROR_FIRE_ROCKET_LAUNCHER; + break; + case SOUND_PICKUP_WEAPON_BOUGHT: + case SOUND_PICKUP_WEAPON: + m_sQueueSample.m_nSampleIndex = SFX_PICKUP_1_LEFT; + processedPickup = TRUE; + stereo = TRUE; + break; + case SOUND_PICKUP_BONUS: + case SOUND_PICKUP_MONEY: + case SOUND_PICKUP_HIDDEN_PACKAGE: + case SOUND_PICKUP_PACMAN_PILL: + case SOUND_PICKUP_PACMAN_PACKAGE: + case SOUND_PICKUP_FLOAT_PACKAGE: + m_sQueueSample.m_nSampleIndex = SFX_PICKUP_3_LEFT; + processedPickup = TRUE; + stereo = TRUE; + break; + case SOUND_PICKUP_ERROR: + m_sQueueSample.m_nSampleIndex = SFX_PICKUP_ERROR_LEFT; + processedPickup = TRUE; + stereo = TRUE; + break; + case SOUND_GARAGE_NO_MONEY: + case SOUND_GARAGE_BAD_VEHICLE: + case SOUND_GARAGE_BOMB_ALREADY_SET: + m_sQueueSample.m_nSampleIndex = SFX_PICKUP_ERROR_LEFT; + stereo = TRUE; + break; + case SOUND_GARAGE_OPENING: + case SOUND_GARAGE_BOMB1_SET: + case SOUND_GARAGE_BOMB2_SET: + case SOUND_GARAGE_BOMB3_SET: + case SOUND_41: + case SOUND_GARAGE_VEHICLE_DECLINED: + case SOUND_GARAGE_VEHICLE_ACCEPTED: + case SOUND_PICKUP_HEALTH: + case SOUND_4B: + case SOUND_PICKUP_ADRENALINE: + case SOUND_PICKUP_ARMOUR: + case SOUND_EVIDENCE_PICKUP: + case SOUND_UNLOAD_GOLD: + m_sQueueSample.m_nSampleIndex = SFX_PICKUP_2_LEFT; + processedPickup = TRUE; + stereo = TRUE; + break; + default: + continue; + } + + if (processedPickup) { + if (m_FrameCounter <= cPickupNextFrame) + continue; + cPickupNextFrame = m_FrameCounter + 5; + } else if (processedMission) { + if (m_FrameCounter <= cPartMisComNextFrame) + continue; + cPartMisComNextFrame = m_FrameCounter + 5; + } + + sample = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]; + if (sample == SFX_RAIN) + m_sQueueSample.m_nFrequency = 28509; + else if (sample == SFX_PICKUP_1_LEFT) { + if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] == 1.0f) + m_sQueueSample.m_nFrequency = 48000; + else + m_sQueueSample.m_nFrequency = 32000; + } else + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nVolume = FRONTEND_VOLUME; + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nBankIndex = frontendBank ? SFX_BANK_FRONT_END_MENU : SFX_BANK_0; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_bIs2D = TRUE; + SET_EMITTING_VOLUME(m_sQueueSample.m_nVolume); + RESET_LOOP_OFFSETS + if (stereo) + m_sQueueSample.m_nPan = m_anRandomTable[0] & 31; + else + m_sQueueSample.m_nPan = 63; + m_sQueueSample.m_bReverb = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + if (stereo) { + m_sQueueSample.m_nSampleIndex++; + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nPan = 127 - m_sQueueSample.m_nPan; + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessCrane() +{ + CCrane *crane = (CCrane *)m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_pEntity; + float distSquared; + bool8 distCalculated = FALSE; + + if (crane) { + if (crane->m_nCraneStatus == CCrane::ACTIVATED) { + if (crane->m_nCraneState != CCrane::IDLE) { + m_sQueueSample.m_vecPos = crane->m_pCraneEntity->GetPosition(); + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(CRANE_MAX_DIST)) { + CalculateDistance(distCalculated, distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(CRANE_VOLUME, CRANE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_CRANE_MAGNET; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFrequency = 6000; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(CRANE_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = CRANE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents > 0) { + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_nSampleIndex = SFX_COL_CAR_2; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_COL_CAR_2); + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(TRUE); + AddSampleToRequestedQueue(); + } + } + } + } + } +} + +void +cAudioManager::ProcessProjectiles() +{ + uint8 Vol; + + for (uint8 i = 0; i < NUM_PROJECTILES; i++) { + if (CProjectileInfo::GetProjectileInfo(i)->m_bInUse) { + switch (CProjectileInfo::GetProjectileInfo(i)->m_eWeaponType) { + case WEAPONTYPE_ROCKETLAUNCHER: + Vol = PROJECTILE_ROCKET_VOLUME; + m_sQueueSample.m_MaxDistance = PROJECTILE_ROCKET_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_ROCKET_FLY; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_ROCKET_FLY); + m_sQueueSample.m_nPriority = 3; + break; + case WEAPONTYPE_MOLOTOV: + Vol = PROJECTILE_MOLOTOV_VOLUME; + m_sQueueSample.m_MaxDistance = PROJECTILE_MOLOTOV_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_PED_ON_FIRE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 32 * SampleManager.GetSampleBaseFrequency(SFX_PED_ON_FIRE) / 25; + m_sQueueSample.m_nPriority = 7; + break; + default: + continue; + } + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_vecPos = CProjectileInfo::ms_apProjectile[i]->GetPosition(); + float distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(m_sQueueSample.m_MaxDistance)) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::ProcessGarages() +{ + CEntity *entity; + uint8 state; + uint32 sampleIndex; + uint8 j; + float distSquared; + bool8 distCalculated; + + static uint8 iSound = 32; + +#ifdef FIX_BUGS + for (uint32 i = 0; i < CGarages::NumGarages; i++) { +#else + for (uint8 i = 0; i < CGarages::NumGarages; i++) { +#endif + if (CGarages::aGarages[i].m_eGarageType == GARAGE_NONE) + continue; + entity = CGarages::aGarages[i].m_pDoor1; + if (entity == nil) + continue; + m_sQueueSample.m_vecPos = entity->GetPosition(); + distCalculated = FALSE; + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(GARAGES_MAX_DIST)) { + state = CGarages::aGarages[i].m_eGarageState; + if (state == GS_OPENING || state == GS_CLOSING || state == GS_AFTERDROPOFF) { + CalculateDistance(distCalculated, distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(GARAGES_VOLUME, GARAGES_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (CGarages::aGarages[i].m_eGarageType == GARAGE_CRUSHER) { + if (CGarages::aGarages[i].m_eGarageState == GS_AFTERDROPOFF) { + if (m_FrameCounter & 1) { + if (m_anRandomTable[1] & 1) + sampleIndex = m_anRandomTable[2] % 5 + SFX_COL_CAR_1; + else + sampleIndex = m_anRandomTable[2] % 6 + SFX_COL_CAR_PANEL_1; + m_sQueueSample.m_nSampleIndex = sampleIndex; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex) >> 1; + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nCounter = iSound++; + if (iSound < 32) + iSound = 32; + } else + goto CheckGarageEvents; // premature exit to go straight to the for loop + } else { + m_sQueueSample.m_nSampleIndex = SFX_FISHING_BOAT_IDLE; + m_sQueueSample.m_nFrequency = 6543; + + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bStatic = FALSE; + } + } else { + m_sQueueSample.m_nSampleIndex = SFX_GARAGE_DOOR_LOOP; + m_sQueueSample.m_nFrequency = 13961; + + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bStatic = FALSE; + } + + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + SET_EMITTING_VOLUME(GARAGES_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = GARAGES_MAX_DIST; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +CheckGarageEvents: + for (j = 0; j < m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents; j++) { + switch (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[j]) { + case SOUND_GARAGE_DOOR_CLOSED: + case SOUND_GARAGE_DOOR_OPENED: + if (distSquared < SQR(GARAGES_MAX_DIST)) { + CalculateDistance(distCalculated, distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(GARAGES_DOOR_VOLUME, GARAGES_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (CGarages::aGarages[i].m_eGarageType == GARAGE_CRUSHER) { + m_sQueueSample.m_nSampleIndex = SFX_COL_CAR_PANEL_2; + m_sQueueSample.m_nFrequency = 6735; + } else if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[j] == SOUND_GARAGE_DOOR_OPENED) { + m_sQueueSample.m_nSampleIndex = SFX_COL_CAR_PANEL_2; + m_sQueueSample.m_nFrequency = 22000; + } else { + m_sQueueSample.m_nSampleIndex = SFX_COL_GARAGE_DOOR_1; + m_sQueueSample.m_nFrequency = 18000; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nPriority = 4; + SET_EMITTING_VOLUME(GARAGES_DOOR_VOLUME); + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = GARAGES_MAX_DIST; + m_sQueueSample.m_bReverb = TRUE; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + m_sQueueSample.m_nCounter = iSound++; + if (iSound < 32) + iSound = 32; + SET_SOUND_REFLECTION(TRUE); + AddSampleToRequestedQueue(); + } + } + break; + default: + break; + } + } + } +} + +void +cAudioManager::ProcessFireHydrant() +{ + float distSquared; + bool8 distCalculated = FALSE; + + m_sQueueSample.m_vecPos = ((CParticleObject*)m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_pEntity)->GetPosition(); + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(FIRE_HYDRANT_MAX_DIST)) { + CalculateDistance(distCalculated, distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(FIRE_HYDRANT_VOLUME, FIRE_HYDRANT_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_TAXI; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 4; + m_sQueueSample.m_nFrequency = 15591; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(FIRE_HYDRANT_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = FIRE_HYDRANT_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +#pragma region BRIDGE +void +cAudioManager::ProcessBridge() +{ + float dist; + bool8 distCalculated = FALSE; + + if (CBridge::pLiftRoad) { + m_sQueueSample.m_vecPos = CBridge::pLiftRoad->GetPosition(); + dist = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (dist < SQR(BRIDGE_MAX_DIST)) { + CalculateDistance(distCalculated, dist); + switch (CBridge::State) { + case STATE_BRIDGE_LOCKED: + case STATE_LIFT_PART_IS_UP: + case STATE_LIFT_PART_ABOUT_TO_MOVE_UP: + ProcessBridgeWarning(); + break; + case STATE_LIFT_PART_MOVING_DOWN: + case STATE_LIFT_PART_MOVING_UP: + ProcessBridgeWarning(); + ProcessBridgeMotor(); + break; + default: + break; + } + ProcessBridgeOneShots(); + } + } +} + +void +cAudioManager::ProcessBridgeWarning() +{ + if (!CStats::CommercialPassed) + return; + + if (m_sQueueSample.m_fDistance < BRIDGE_MAX_DIST) { + m_sQueueSample.m_nVolume = ComputeVolume(BRIDGE_WARNING_VOLUME, BRIDGE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_BRIDGE_OPEN_WARNING; + m_sQueueSample.m_nBankIndex = SFX_BANK_GENERIC_EXTRA; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_BRIDGE_OPEN_WARNING); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(BRIDGE_WARNING_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BRIDGE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 8; + m_sQueueSample.m_bReverb = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessBridgeMotor() +{ + if (m_sQueueSample.m_fDistance < BRIDGE_MOTOR_MAX_DIST) { + m_sQueueSample.m_nVolume = ComputeVolume(BRIDGE_MOTOR_VOLUME, BRIDGE_MOTOR_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_nSampleIndex = SFX_FISHING_BOAT_IDLE; // todo check sfx name + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = 5500; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(BRIDGE_MOTOR_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BRIDGE_MOTOR_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = FALSE; + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessBridgeOneShots() +{ + float maxDist; + + if (CBridge::State == STATE_LIFT_PART_IS_UP && CBridge::OldState == STATE_LIFT_PART_MOVING_UP) { + maxDist = BRIDGE_MOTOR_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_CONTAINER_1; + } else if (CBridge::State == STATE_LIFT_PART_IS_DOWN && CBridge::OldState == STATE_LIFT_PART_MOVING_DOWN) { + maxDist = BRIDGE_MOTOR_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_CONTAINER_1; + } else if (CBridge::State == STATE_LIFT_PART_MOVING_UP && CBridge::OldState == STATE_LIFT_PART_ABOUT_TO_MOVE_UP) { + maxDist = BRIDGE_MOTOR_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_CONTAINER_1; + } else if (CBridge::State == STATE_LIFT_PART_MOVING_DOWN && CBridge::OldState == STATE_LIFT_PART_IS_UP) { + maxDist = BRIDGE_MOTOR_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_CONTAINER_1; + } else return; + + if (m_sQueueSample.m_fDistance < maxDist) { + m_sQueueSample.m_nVolume = ComputeVolume(BRIDGE_MOTOR_VOLUME, maxDist, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 2; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(BRIDGE_MOTOR_VOLUME); + RESET_LOOP_OFFSETS + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = maxDist; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} +#pragma endregion + +#pragma region MISSION_AUDIO +bool8 g_bMissionAudioLoadFailed; + +struct MissionAudioData { + const char *m_pName; + uint32 m_nId; +}; + +Const MissionAudioData MissionAudioNameSfxAssoc[] = { + {"lib_a1", SFX_MISSION_LIB_A1}, {"lib_a2", SFX_MISSION_LIB_A2}, {"lib_a", SFX_MISSION_LIB_A}, + {"lib_b", SFX_MISSION_LIB_B}, {"lib_c", SFX_MISSION_LIB_C}, {"lib_d", SFX_MISSION_LIB_D}, + {"l2_a", SFX_MISSION_L2_A}, {"j4t_1", SFX_MISSION_J4T_1}, {"j4t_2", SFX_MISSION_J4T_2}, + {"j4t_3", SFX_MISSION_J4T_3}, {"j4t_4", SFX_MISSION_J4T_4}, {"j4_a", SFX_MISSION_J4_A}, + {"j4_b", SFX_MISSION_J4_B}, {"j4_c", SFX_MISSION_J4_C}, {"j4_d", SFX_MISSION_J4_D}, + {"j4_e", SFX_MISSION_J4_E}, {"j4_f", SFX_MISSION_J4_F}, {"j6_1", SFX_MISSION_J6_1}, + {"j6_a", SFX_MISSION_J6_A}, {"j6_b", SFX_MISSION_J6_B}, {"j6_c", SFX_MISSION_J6_C}, + {"j6_d", SFX_MISSION_J6_D}, {"t4_a", SFX_MISSION_T4_A}, {"s1_a", SFX_MISSION_S1_A}, + {"s1_a1", SFX_MISSION_S1_A1}, {"s1_b", SFX_MISSION_S1_B}, {"s1_c", SFX_MISSION_S1_C}, + {"s1_c1", SFX_MISSION_S1_C1}, {"s1_d", SFX_MISSION_S1_D}, {"s1_e", SFX_MISSION_S1_E}, + {"s1_f", SFX_MISSION_S1_F}, {"s1_g", SFX_MISSION_S1_G}, {"s1_h", SFX_MISSION_S1_H}, + {"s1_i", SFX_MISSION_S1_I}, {"s1_j", SFX_MISSION_S1_J}, {"s1_k", SFX_MISSION_S1_K}, + {"s1_l", SFX_MISSION_S1_L}, {"s3_a", SFX_MISSION_S3_A}, {"s3_b", SFX_MISSION_S3_B}, + {"el3_a", SFX_MISSION_EL3_A}, {"mf1_a", SFX_MISSION_MF1_A}, {"mf2_a", SFX_MISSION_MF2_A}, + {"mf3_a", SFX_MISSION_MF3_A}, {"mf3_b", SFX_MISSION_MF3_B}, {"mf3_b1", SFX_MISSION_MF3_B1}, + {"mf3_c", SFX_MISSION_MF3_C}, {"mf4_a", SFX_MISSION_MF4_A}, {"mf4_b", SFX_MISSION_MF4_B}, + {"mf4_c", SFX_MISSION_MF4_C}, {"a1_a", SFX_MISSION_A1_A}, {"a3_a", SFX_MISSION_A3_A}, + {"a5_a", SFX_MISSION_A5_A}, {"a4_a", SFX_MISSION_A4_A}, {"a4_b", SFX_MISSION_A4_B}, + {"a4_c", SFX_MISSION_A4_C}, {"a4_d", SFX_MISSION_A4_D}, {"k1_a", SFX_MISSION_K1_A}, + {"k3_a", SFX_MISSION_K3_A}, {"r1_a", SFX_MISSION_R1_A}, {"r2_a", SFX_MISSION_R2_A}, + {"r2_b", SFX_MISSION_R2_B}, {"r2_c", SFX_MISSION_R2_C}, {"r2_d", SFX_MISSION_R2_D}, + {"r2_e", SFX_MISSION_R2_E}, {"r2_f", SFX_MISSION_R2_F}, {"r2_g", SFX_MISSION_R2_G}, + {"r2_h", SFX_MISSION_R2_H}, {"r5_a", SFX_MISSION_R5_A}, {"r6_a", SFX_MISSION_R6_A}, + {"r6_a1", SFX_MISSION_R6_A1}, {"r6_b", SFX_MISSION_R6_B}, {"lo2_a", SFX_MISSION_LO2_A}, + {"lo6_a", SFX_MISSION_LO6_A}, {"yd2_a", SFX_MISSION_YD2_A}, {"yd2_b", SFX_MISSION_YD2_B}, + {"yd2_c", SFX_MISSION_YD2_C}, {"yd2_c1", SFX_MISSION_YD2_C1}, {"yd2_d", SFX_MISSION_YD2_D}, + {"yd2_e", SFX_MISSION_YD2_E}, {"yd2_f", SFX_MISSION_YD2_F}, {"yd2_g", SFX_MISSION_YD2_G}, + {"yd2_h", SFX_MISSION_YD2_H}, {"yd2_ass", SFX_MISSION_YD2_ASS}, {"yd2_ok", SFX_MISSION_YD2_OK}, + {"h5_a", SFX_MISSION_H5_A}, {"h5_b", SFX_MISSION_H5_B}, {"h5_c", SFX_MISSION_H5_C}, + {"ammu_a", SFX_MISSION_AMMU_A}, {"ammu_b", SFX_MISSION_AMMU_B}, {"ammu_c", SFX_MISSION_AMMU_C}, +#if GTA_VERSION < GTA3_PC_10 + {"ammu_d", SFX_AMMU_D }, {"ammu_e", SFX_AMMU_E}, {"ammu_f", SFX_AMMU_F}, + {"ammu_g", SFX_MISSION_AMMU_A}, {"ammu_h", SFX_MISSION_AMMU_A}, {"ammu_i", SFX_MISSION_AMMU_A}, + {"ammu_j", SFX_MISSION_AMMU_A}, {"ammu_k", SFX_MISSION_AMMU_A}, +#endif + {"door_1", SFX_MISSION_DOOR_1}, {"door_2", SFX_MISSION_DOOR_2}, {"door_3", SFX_MISSION_DOOR_3}, + {"door_4", SFX_MISSION_DOOR_4}, {"door_5", SFX_MISSION_DOOR_5}, {"door_6", SFX_MISSION_DOOR_6}, + {"t3_a", SFX_MISSION_T3_A}, {"t3_b", SFX_MISSION_T3_B}, {"t3_c", SFX_MISSION_T3_C}, + {"k1_b", SFX_MISSION_K1_B}, {"c_1", SFX_MISSION_CAT1}, {nil, 0}}; + +uint32 +FindMissionAudioSfx(const char *name) +{ + for (uint32 i = 0; MissionAudioNameSfxAssoc[i].m_pName != nil; i++) { + if (!CGeneral::faststricmp(MissionAudioNameSfxAssoc[i].m_pName, name)) + return MissionAudioNameSfxAssoc[i].m_nId; + } + debug("Can't find mission audio %s", name); + return NO_SAMPLE; +} + +bool8 +cAudioManager::MissionScriptAudioUsesPoliceChannel(uint32 soundMission) +{ + switch (soundMission) { + case SFX_MISSION_J6_D: + case SFX_MISSION_T4_A: + case SFX_MISSION_S1_H: + case SFX_MISSION_S3_B: + case SFX_MISSION_EL3_A: + case SFX_MISSION_A3_A: + case SFX_MISSION_A5_A: + case SFX_MISSION_K1_A: + case SFX_MISSION_R1_A: + case SFX_MISSION_R5_A: + case SFX_MISSION_LO2_A: + case SFX_MISSION_LO6_A: + return TRUE; + default: + return FALSE; + } +} + +void +cAudioManager::PreloadMissionAudio(Const char *name) +{ + if (m_bIsInitialised) { + uint32 missionAudioSfx = FindMissionAudioSfx(name); + if (missionAudioSfx != NO_SAMPLE) { + m_nMissionAudioSampleIndex = missionAudioSfx; + m_nMissionAudioLoadingStatus = LOADING_STATUS_NOT_LOADED; + m_nMissionAudioPlayStatus = PLAY_STATUS_STOPPED; + m_bIsMissionAudioPlaying = FALSE; +#ifdef GTA_PS2 + m_nMissionAudioFramesToPlay = m_nTimeSpent * SampleManager.GetSampleLength(missionAudioSfx) / SampleManager.GetSampleBaseFrequency(missionAudioSfx); + m_nMissionAudioFramesToPlay = 11 * m_nMissionAudioFramesToPlay / 10; +#else + m_nMissionAudioFramesToPlay = m_nTimeSpent * SampleManager.GetStreamedFileLength(missionAudioSfx) / 1000; + m_nMissionAudioFramesToPlay *= 4; +#endif + m_bIsMissionAudioAllowedToPlay = FALSE; + m_bIsMissionAudio2D = TRUE; + g_bMissionAudioLoadFailed = FALSE; + } + } +} + +uint8 +cAudioManager::GetMissionAudioLoadingStatus() +{ + if (m_bIsInitialised) + return m_nMissionAudioLoadingStatus; + + return LOADING_STATUS_LOADED; +} + +void +cAudioManager::SetMissionAudioLocation(float x, float y, float z) +{ + if (m_bIsInitialised) { + m_bIsMissionAudio2D = FALSE; + m_vecMissionAudioPosition = CVector(x, y, z); + } +} + +void +cAudioManager::PlayLoadedMissionAudio() +{ + if (m_bIsInitialised && m_nMissionAudioSampleIndex != NO_SAMPLE && m_nMissionAudioLoadingStatus == LOADING_STATUS_LOADED && + m_nMissionAudioPlayStatus == PLAY_STATUS_STOPPED) + m_bIsMissionAudioAllowedToPlay = TRUE; +} + +bool8 +cAudioManager::IsMissionAudioSampleFinished() +{ + if (m_bIsInitialised) + return m_nMissionAudioPlayStatus == PLAY_STATUS_FINISHED; + + static uint32 cPretendFrame = 1; + + return (cPretendFrame++ & 63) == 0; +} + +void +cAudioManager::ClearMissionAudio() +{ + if (m_bIsInitialised) { + m_nMissionAudioSampleIndex = NO_SAMPLE; + m_nMissionAudioLoadingStatus = LOADING_STATUS_NOT_LOADED; + m_nMissionAudioPlayStatus = PLAY_STATUS_STOPPED; + m_bIsMissionAudioPlaying = FALSE; + m_bIsMissionAudioAllowedToPlay = FALSE; + m_bIsMissionAudio2D = TRUE; + m_nMissionAudioFramesToPlay = 0; + } +} + +void +cAudioManager::ProcessMissionAudio() +{ + float dist; + uint8 Vol; + uint8 pan; + float distSquared; + CVector vec; + + static uint8 nCheckPlayingDelay = 0; + static uint8 nFramesUntilFailedLoad = 0; + static uint8 nFramesForPretendPlaying = 0; + + if (m_bIsInitialised && m_nMissionAudioSampleIndex != NO_SAMPLE) { + switch (m_nMissionAudioLoadingStatus) { + case LOADING_STATUS_NOT_LOADED: +#ifdef GTA_PS2 + SampleManager.LoadPedComment(m_nMissionAudioSampleIndex); + m_nMissionAudioLoadingStatus = LOADING_STATUS_LOADING; +#else + SampleManager.PreloadStreamedFile(m_nMissionAudioSampleIndex, 1); + m_nMissionAudioLoadingStatus = LOADING_STATUS_LOADED; +#endif + nFramesUntilFailedLoad = 0; + break; + case LOADING_STATUS_LOADING: +#ifdef GTA_PS2 + if (SampleManager.IsPedCommentLoaded(m_nMissionAudioSampleIndex) == TRUE) + m_nMissionAudioLoadingStatus = LOADING_STATUS_LOADED; + // fallthrough + else +#endif + if (++nFramesUntilFailedLoad >= 90) { + nFramesForPretendPlaying = 0; + g_bMissionAudioLoadFailed = TRUE; + nFramesUntilFailedLoad = 0; + m_nMissionAudioLoadingStatus = LOADING_STATUS_LOADED; + return; + } +#ifdef GTA_PS2 + else { + // try loading again + SampleManager.LoadPedComment(m_nMissionAudioSampleIndex); + return; + } +#endif + case LOADING_STATUS_LOADED: + if (!m_bIsMissionAudioAllowedToPlay) + break; + if (g_bMissionAudioLoadFailed) { + if (m_bTimerJustReset) { + ClearMissionAudio(); +#ifdef GTA_PS2 + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); // why? + SampleManager.StopChannel(CHANNEL_MISSION_AUDIO); +#else + SampleManager.StopStreamedFile(1); +#endif + nFramesForPretendPlaying = 0; + nCheckPlayingDelay = 0; + nFramesUntilFailedLoad = 0; + } else if (!m_bIsPaused) { + if (++nFramesForPretendPlaying >= 120) { + m_nMissionAudioPlayStatus = PLAY_STATUS_FINISHED; + m_nMissionAudioSampleIndex = NO_SAMPLE; + } else + m_nMissionAudioPlayStatus = PLAY_STATUS_PLAYING; + } + break; + } + switch (m_nMissionAudioPlayStatus) { + case PLAY_STATUS_STOPPED: + if (MissionScriptAudioUsesPoliceChannel(m_nMissionAudioSampleIndex)) + SetMissionScriptPoliceAudio(m_nMissionAudioSampleIndex); + else { +#ifdef GTA_PS2 + SampleManager.InitialiseChannel(CHANNEL_MISSION_AUDIO, m_nMissionAudioSampleIndex, SFX_BANK_PED_COMMENTS); + if (m_bIsPaused) + SampleManager.SetChannelFrequency(CHANNEL_MISSION_AUDIO, 0); + else + SampleManager.SetChannelFrequency(CHANNEL_MISSION_AUDIO, SampleManager.GetSampleBaseFrequency(m_nMissionAudioSampleIndex)); +#else + if (m_bIsPaused) + SampleManager.PauseStream(TRUE, 1); +#endif + if (m_bIsMissionAudio2D) { +#ifdef GTA_PS2 + SampleManager.SetChannelVolume(CHANNEL_MISSION_AUDIO, MISSION_AUDIO_VOLUME); + SampleManager.SetChannelPan(CHANNEL_MISSION_AUDIO, 63); + SampleManager.SetChannelReverbFlag(CHANNEL_MISSION_AUDIO, FALSE); +#else + SampleManager.SetStreamedVolumeAndPan(MISSION_AUDIO_VOLUME, 63, TRUE, 1); +#endif + } else { + distSquared = GetDistanceSquared(m_vecMissionAudioPosition); + if (distSquared < SQR(MISSION_AUDIO_MAX_DIST)) { + dist = Sqrt(distSquared); + Vol = ComputeVolume(MISSION_AUDIO_VOLUME, MISSION_AUDIO_MAX_DIST, dist); + TranslateEntity(&m_vecMissionAudioPosition, &vec); + pan = ComputePan(MISSION_AUDIO_MAX_DIST, &vec); + } else { + Vol = 0; + pan = 63; + } +#ifdef GTA_PS2 + SampleManager.SetChannelVolume(CHANNEL_MISSION_AUDIO, Vol); + SampleManager.SetChannelPan(CHANNEL_MISSION_AUDIO, pan); + SampleManager.SetChannelReverbFlag(CHANNEL_MISSION_AUDIO, TRUE); +#else + SampleManager.SetStreamedVolumeAndPan(Vol, pan, TRUE, 1); +#endif + } +#ifdef GTA_PS2 + SampleManager.StartChannel(CHANNEL_MISSION_AUDIO); +#else + SampleManager.StartPreloadedStreamedFile(1); +#endif + } + m_nMissionAudioPlayStatus = PLAY_STATUS_PLAYING; + nCheckPlayingDelay = 30; + break; + case PLAY_STATUS_PLAYING: + if (m_bTimerJustReset) { + ClearMissionAudio(); +#ifdef GTA_PS2 + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); // why? + SampleManager.StopChannel(CHANNEL_MISSION_AUDIO); +#else + SampleManager.StopStreamedFile(1); +#endif + return; + } + if (MissionScriptAudioUsesPoliceChannel(m_nMissionAudioSampleIndex)) { + if (!m_bIsPaused) { + if (nCheckPlayingDelay > 0) { + nCheckPlayingDelay--; + } else if (GetMissionScriptPoliceAudioPlayingStatus() == PLAY_STATUS_FINISHED || m_nMissionAudioFramesToPlay-- == 0) { + m_nMissionAudioPlayStatus = PLAY_STATUS_FINISHED; + m_nMissionAudioSampleIndex = NO_SAMPLE; +#ifdef GTA_PS2 + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); +#else + SampleManager.StopStreamedFile(1); +#endif + m_nMissionAudioFramesToPlay = 0; + } + } + } else if (m_bIsMissionAudioPlaying) { +#ifdef GTA_PS2 + if (!SampleManager.GetChannelUsedFlag(CHANNEL_MISSION_AUDIO) && !m_bIsPaused && !m_bWasPaused) { +#else + if (!SampleManager.IsStreamPlaying(1) && !m_bIsPaused && !m_bWasPaused) { +#endif + m_nMissionAudioPlayStatus = PLAY_STATUS_FINISHED; + m_nMissionAudioSampleIndex = NO_SAMPLE; +#ifdef GTA_PS2 + SampleManager.StopChannel(CHANNEL_MISSION_AUDIO); +#else + SampleManager.StopStreamedFile(1); +#endif + m_nMissionAudioFramesToPlay = 0; + } else { +#ifdef GTA_PS2 + if (m_bIsPaused) + SampleManager.SetChannelFrequency(CHANNEL_MISSION_AUDIO, 0); + else + SampleManager.SetChannelFrequency(CHANNEL_MISSION_AUDIO, SampleManager.GetSampleBaseFrequency(m_nMissionAudioSampleIndex)); +#else + if (m_bIsPaused) + SampleManager.PauseStream(TRUE, 1); + else + SampleManager.PauseStream(FALSE, 1); +#endif + } + } else { + if (m_bIsPaused) + break; + if (nCheckPlayingDelay-- > 0) { +#ifdef GTA_PS2 + if (!SampleManager.GetChannelUsedFlag(CHANNEL_MISSION_AUDIO)) +#else + if (!SampleManager.IsStreamPlaying(1)) +#endif + break; + nCheckPlayingDelay = 0; + } + m_bIsMissionAudioPlaying = TRUE; + } + break; + default: + break; + } + break; + default: + return; + } + } +} +#pragma endregion All the mission audio stuff diff --git a/src/audio/AudioManager.cpp b/src/audio/AudioManager.cpp new file mode 100644 index 0000000..5d83269 --- /dev/null +++ b/src/audio/AudioManager.cpp @@ -0,0 +1,1245 @@ +#include "common.h" + +#include "AudioManager.h" +#include "audio_enums.h" + +#include "AudioScriptObject.h" +#include "MusicManager.h" +#include "Timer.h" +#include "DMAudio.h" +#include "sampman.h" +#include "Camera.h" +#include "World.h" +#include "Entity.h" + +cAudioManager AudioManager; + +#define SPEED_OF_SOUND 343.f +#ifdef GTA_PS2 +#define TIME_SPENT 40 +#else +#define TIME_SPENT 50 +#endif + +cAudioManager::cAudioManager() +{ + m_bIsInitialised = FALSE; + m_bIsSurround = TRUE; + m_fSpeedOfSound = SPEED_OF_SOUND / TIME_SPENT; + m_nTimeSpent = TIME_SPENT; + m_nActiveSamples = NUM_CHANNELS_GENERIC; + m_nActiveQueue = 1; + ClearRequestedQueue(); + m_nActiveQueue = 0; + ClearRequestedQueue(); + ClearActiveSamples(); + GenerateIntegerRandomNumberTable(); + m_bDoubleVolume = FALSE; +#ifdef AUDIO_REFLECTIONS + m_bDynamicAcousticModelingStatus = TRUE; +#endif + + for (uint32 i = 0; i < NUM_AUDIOENTITIES; i++) { + m_asAudioEntities[i].m_bIsUsed = FALSE; + m_aAudioEntityOrderList[i] = NUM_AUDIOENTITIES; + } + m_nAudioEntitiesCount = 0; + m_FrameCounter = 0; + m_bReduceReleasingPriority = FALSE; + m_bTimerJustReset = FALSE; + m_nTimer = 0; +} + +cAudioManager::~cAudioManager() +{ + if (m_bIsInitialised) + Terminate(); +} + +void +cAudioManager::Initialise() +{ + if (!m_bIsInitialised) { + PreInitialiseGameSpecificSetup(); + m_bIsInitialised = SampleManager.Initialise(); + if (m_bIsInitialised) { +#ifdef EXTERNAL_3D_SOUND + m_nActiveSamples = SampleManager.GetMaximumSupportedChannels(); + if (m_nActiveSamples <= 1) { + Terminate(); + } else { + m_nActiveSamples--; +#else + { + m_nActiveSamples = NUM_CHANNELS_GENERIC; +#endif + PostInitialiseGameSpecificSetup(); + InitialisePoliceRadioZones(); + InitialisePoliceRadio(); + MusicManager.Initialise(); + } + } + } +} + +void +cAudioManager::Terminate() +{ + if (m_bIsInitialised) { + MusicManager.Terminate(); + + for (uint32 i = 0; i < NUM_AUDIOENTITIES; i++) { + m_asAudioEntities[i].m_bIsUsed = FALSE; + m_aAudioEntityOrderList[i] = NUM_AUDIOENTITIES; + } + + m_nAudioEntitiesCount = 0; + m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal = 0; + PreTerminateGameSpecificShutdown(); + + for (uint32 i = 0; i < MAX_SFX_BANKS; i++) { + if (SampleManager.IsSampleBankLoaded(i)) + SampleManager.UnloadSampleBank(i); + } + + SampleManager.Terminate(); + + m_bIsInitialised = FALSE; + PostTerminateGameSpecificShutdown(); + } +} + +void +cAudioManager::Service() +{ + GenerateIntegerRandomNumberTable(); + if (m_bTimerJustReset) { + ResetAudioLogicTimers(m_nTimer); + MusicManager.ResetTimers(m_nTimer); + m_bTimerJustReset = FALSE; + } + if (m_bIsInitialised) { + m_bWasPaused = m_bIsPaused; + m_bIsPaused = CTimer::GetIsUserPaused(); +#ifdef AUDIO_REFLECTIONS + UpdateReflections(); +#endif + ServiceSoundEffects(); + MusicManager.Service(); + } +} + +int32 +cAudioManager::CreateEntity(eAudioType type, void *entity) +{ + if (!m_bIsInitialised) + return AEHANDLE_ERROR_NOAUDIOSYS; + if (!entity) + return AEHANDLE_ERROR_NOENTITY; + if (type >= TOTAL_AUDIO_TYPES) + return AEHANDLE_ERROR_BADAUDIOTYPE; + +#ifdef FIX_BUGS + // since sound could still play after entity deletion let's make sure we don't override one that is in use + // find all the free entity IDs that are being used by queued samples + int32 stillUsedEntities[NUM_CHANNELS_GENERIC * NUM_SOUND_QUEUES]; + uint32 stillUsedEntitiesCount = 0; + + for (uint8 i = 0; i < NUM_SOUND_QUEUES; i++) + for (uint8 j = 0; j < m_nRequestedCount[i]; j++) { + tSound &sound = m_aRequestedQueue[i][m_aRequestedOrderList[i][j]]; + if (sound.m_nEntityIndex < 0) continue; + if (!m_asAudioEntities[sound.m_nEntityIndex].m_bIsUsed) { + bool found = false; + for (uint8 k = 0; k < stillUsedEntitiesCount; k++) { + if (stillUsedEntities[k] == sound.m_nEntityIndex) { + found = true; + break; + } + } + if (!found) + stillUsedEntities[stillUsedEntitiesCount++] = sound.m_nEntityIndex; + } + } +#endif + + for (uint32 i = 0; i < NUM_AUDIOENTITIES; i++) { + if (!m_asAudioEntities[i].m_bIsUsed) { +#ifdef FIX_BUGS + // skip if ID is still used by queued sample + bool skip = false; + for (uint8 j = 0; j < stillUsedEntitiesCount; j++) { + if (stillUsedEntities[j] == i) { + //debug("audio entity %i still used, skipping\n", i); + skip = true; + break; + } + } + if (skip) + continue; +#endif + + m_asAudioEntities[i].m_bIsUsed = TRUE; + m_asAudioEntities[i].m_bStatus = FALSE; + m_asAudioEntities[i].m_nType = type; + m_asAudioEntities[i].m_pEntity = entity; + m_asAudioEntities[i].m_awAudioEvent[0] = SOUND_NO_SOUND; + m_asAudioEntities[i].m_awAudioEvent[1] = SOUND_NO_SOUND; + m_asAudioEntities[i].m_awAudioEvent[2] = SOUND_NO_SOUND; + m_asAudioEntities[i].m_awAudioEvent[3] = SOUND_NO_SOUND; + m_asAudioEntities[i].m_AudioEvents = 0; + m_aAudioEntityOrderList[m_nAudioEntitiesCount++] = i; + return i; + } + } + return AEHANDLE_ERROR_NOFREESLOT; +} + +void +cAudioManager::DestroyEntity(int32 id) +{ + if (m_bIsInitialised && id >= 0 && id < NUM_AUDIOENTITIES && m_asAudioEntities[id].m_bIsUsed) { + m_asAudioEntities[id].m_bIsUsed = FALSE; + for (uint32 i = 0; i < m_nAudioEntitiesCount; i++) { + if (id == m_aAudioEntityOrderList[i]) { + if (i < NUM_AUDIOENTITIES - 1) + memmove(&m_aAudioEntityOrderList[i], &m_aAudioEntityOrderList[i + 1], sizeof(uint32) * (m_nAudioEntitiesCount - (i + 1))); + m_aAudioEntityOrderList[--m_nAudioEntitiesCount] = NUM_AUDIOENTITIES; + return; + } + } + } +} + +bool8 +cAudioManager::GetEntityStatus(int32 id) +{ + if (m_bIsInitialised && id >= 0 && id < NUM_AUDIOENTITIES && m_asAudioEntities[id].m_bIsUsed) + return m_asAudioEntities[id].m_bStatus; + return FALSE; +} + +void +cAudioManager::SetEntityStatus(int32 id, bool8 status) +{ + if (m_bIsInitialised && id >= 0 && id < NUM_AUDIOENTITIES && m_asAudioEntities[id].m_bIsUsed) + m_asAudioEntities[id].m_bStatus = status; +} + +void * +cAudioManager::GetEntityPointer(int32 id) +{ + if (m_bIsInitialised && id >= 0 && id < NUM_AUDIOENTITIES && m_asAudioEntities[id].m_bIsUsed) + return m_asAudioEntities[id].m_pEntity; + return NULL; +} + +static Const uint8 OneShotPriority[] = { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 5, 5, 3, 5, 2, 2, 1, 1, 3, 1, 3, 3, 1, 1, 1, 4, 4, 3, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 3, 2, 2, 2, 2, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 3, 1, 1, 1, 9, + 2, 2, 0, 0, 0, 0, 3, 3, 5, 1, 1, 1, 1, 3, 4, 7, 6, 6, 6, 6, 1, 3, 4, 3, 4, 2, 1, 3, 5, 4, 6, 6, 1, 3, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + +void +cAudioManager::PlayOneShot(int32 index, uint16 sound, float vol) +{ + if (m_bIsInitialised) { + if (index >= 0 && index < NUM_AUDIOENTITIES) { + tAudioEntity &entity = m_asAudioEntities[index]; + if (entity.m_bIsUsed) { + if (sound < SOUND_TOTAL_SOUNDS) { + if (entity.m_nType == AUDIOTYPE_SCRIPTOBJECT) { + if (m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal < ARRAY_SIZE(m_sAudioScriptObjectManager.m_anScriptObjectEntityIndices)) { + entity.m_awAudioEvent[0] = sound; + entity.m_AudioEvents = 1; + m_sAudioScriptObjectManager.m_anScriptObjectEntityIndices[m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal++] = index; + } + } else { + int32 i = 0; + while (TRUE) { + if (i >= entity.m_AudioEvents) { + if (entity.m_AudioEvents < NUM_AUDIOENTITY_EVENTS) { + entity.m_awAudioEvent[i] = sound; + entity.m_afVolume[i] = vol; + entity.m_AudioEvents++; + } + return; + } + if (OneShotPriority[entity.m_awAudioEvent[i]] > OneShotPriority[sound]) + break; + i++; + } + if (i < NUM_AUDIOENTITY_EVENTS - 1) { + memmove(&entity.m_awAudioEvent[i + 1], &entity.m_awAudioEvent[i], (NUM_AUDIOENTITY_EVENTS - 1 - i) * sizeof(int16)); + memmove(&entity.m_afVolume[i + 1], &entity.m_afVolume[i], (NUM_AUDIOENTITY_EVENTS - 1 - i) * sizeof(float)); + } + entity.m_awAudioEvent[i] = sound; + entity.m_afVolume[i] = vol; + if (entity.m_AudioEvents < NUM_AUDIOENTITY_EVENTS) + entity.m_AudioEvents++; + } + } + } + } + } +} + +void +cAudioManager::SetEffectsMasterVolume(uint8 volume) +{ + SampleManager.SetEffectsMasterVolume(volume); +} + +void +cAudioManager::SetMusicMasterVolume(uint8 volume) +{ + SampleManager.SetMusicMasterVolume(volume); +} + +void +cAudioManager::SetEffectsFadeVol(uint8 volume) +{ + SampleManager.SetEffectsFadeVolume(volume); +} + +void +cAudioManager::SetMusicFadeVol(uint8 volume) +{ + SampleManager.SetMusicFadeVolume(volume); +} + +void +cAudioManager::SetMonoMode(bool8 mono) +{ + SampleManager.SetMonoMode(mono); +} + +void +cAudioManager::ResetTimers(uint32 time) +{ + if (m_bIsInitialised) { + m_bTimerJustReset = TRUE; + m_nTimer = time; + ClearRequestedQueue(); + if (m_nActiveQueue) { + m_nActiveQueue = 0; + ClearRequestedQueue(); + m_nActiveQueue = 1; + } else { + m_nActiveQueue = 1; + ClearRequestedQueue(); + m_nActiveQueue = 0; + } + ClearActiveSamples(); + ClearMissionAudio(); + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); + SampleManager.SetEffectsFadeVolume(0); + SampleManager.SetMusicFadeVolume(0); + MusicManager.ResetMusicAfterReload(); +#ifdef AUDIO_OAL + SampleManager.Service(); +#endif + } +} + +void +cAudioManager::DestroyAllGameCreatedEntities() +{ + cAudioScriptObject *entity; + + if (m_bIsInitialised) { + for (uint32 i = 0; i < NUM_AUDIOENTITIES; i++) { + if (m_asAudioEntities[i].m_bIsUsed) { + switch (m_asAudioEntities[i].m_nType) { + case AUDIOTYPE_PHYSICAL: + case AUDIOTYPE_EXPLOSION: + case AUDIOTYPE_WEATHER: + case AUDIOTYPE_CRANE: + case AUDIOTYPE_GARAGE: + case AUDIOTYPE_FIREHYDRANT: + DestroyEntity(i); + break; + case AUDIOTYPE_SCRIPTOBJECT: + entity = (cAudioScriptObject *)m_asAudioEntities[i].m_pEntity; + if (entity) { + delete entity; + m_asAudioEntities[i].m_pEntity = nil; + } + DestroyEntity(i); + break; + default: + break; + } + } + } + m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal = 0; + } +} + +#ifdef GTA_PC + +uint8 +cAudioManager::GetNum3DProvidersAvailable() +{ +#ifdef EXTERNAL_3D_SOUND + if (m_bIsInitialised) + return SampleManager.GetNum3DProvidersAvailable(); +#endif + return 0; +} + +char * +cAudioManager::Get3DProviderName(uint8 id) +{ +#ifndef EXTERNAL_3D_SOUND + return nil; +#else + if (!m_bIsInitialised) + return nil; +#ifdef AUDIO_OAL + id = Clamp(id, 0, SampleManager.GetNum3DProvidersAvailable() - 1); +#else + // We don't want that either since it will crash the game, but skipping for now + if (id >= SampleManager.GetNum3DProvidersAvailable()) + return nil; +#endif + return SampleManager.Get3DProviderName(id); +#endif +} + +int8 +cAudioManager::GetCurrent3DProviderIndex() +{ +#ifdef EXTERNAL_3D_SOUND + if (m_bIsInitialised) + return SampleManager.GetCurrent3DProviderIndex(); +#endif + + return -1; +} + +int8 +cAudioManager::SetCurrent3DProvider(uint8 which) +{ +#ifndef EXTERNAL_3D_SOUND + return -1; +#else + if (!m_bIsInitialised) + return -1; + for (uint8 i = 0; i < m_nActiveSamples + 1; i++) + SampleManager.StopChannel(i); + ClearRequestedQueue(); + if (m_nActiveQueue == 0) + m_nActiveQueue = 1; + else + m_nActiveQueue = 0; + ClearRequestedQueue(); + ClearActiveSamples(); + int8 current = SampleManager.SetCurrent3DProvider(which); + if (current > 0) { +#ifdef EXTERNAL_3D_SOUND + m_nActiveSamples = SampleManager.GetMaximumSupportedChannels(); + if (m_nActiveSamples > 1) + m_nActiveSamples--; +#endif + } + return current; +#endif +} + +void +cAudioManager::SetSpeakerConfig(int32 conf) +{ +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetSpeakerConfig(conf); +#endif +} + +bool8 +cAudioManager::IsMP3RadioChannelAvailable() +{ + if (m_bIsInitialised) + return SampleManager.IsMP3RadioChannelAvailable(); + + return FALSE; +} + +void +cAudioManager::ReleaseDigitalHandle() +{ + if (m_bIsInitialised) + SampleManager.ReleaseDigitalHandle(); +} + +void +cAudioManager::ReacquireDigitalHandle() +{ + if (m_bIsInitialised) + SampleManager.ReacquireDigitalHandle(); +} + +#ifdef AUDIO_REFLECTIONS +void +cAudioManager::SetDynamicAcousticModelingStatus(bool8 status) +{ + m_bDynamicAcousticModelingStatus = status; +} +#endif + +bool8 +cAudioManager::CheckForAnAudioFileOnCD() +{ + return SampleManager.CheckForAnAudioFileOnCD(); +} + +char +cAudioManager::GetCDAudioDriveLetter() +{ + if (m_bIsInitialised) + return SampleManager.GetCDAudioDriveLetter(); + + return '\0'; +} + +bool8 +cAudioManager::IsAudioInitialised() +{ + return m_bIsInitialised; +} + +#endif // GTA_PC + +void +cAudioManager::ServiceSoundEffects() +{ +#ifdef FIX_BUGS + if(CTimer::GetLogicalFramesPassed() != 0) +#endif + m_bReduceReleasingPriority = (m_FrameCounter++ % 5) == 0; + if (m_bIsPaused && !m_bWasPaused) { + for (int32 i = 0; i < NUM_CHANNELS; i++) + SampleManager.StopChannel(i); + + ClearRequestedQueue(); + if (m_nActiveQueue) { + m_nActiveQueue = 0; + ClearRequestedQueue(); + m_nActiveQueue = 1; + } else { + m_nActiveQueue = 1; + ClearRequestedQueue(); + m_nActiveQueue = 0; + } + ClearActiveSamples(); + } + m_nActiveQueue = m_nActiveQueue == 1 ? 0 : 1; + ProcessReverb(); + ProcessSpecial(); + ClearRequestedQueue(); + InterrogateAudioEntities(); + m_sPedComments.Process(); + ServicePoliceRadio(); + ServiceCollisions(); + AddReleasingSounds(); + ProcessMissionAudio(); +#ifdef EXTERNAL_3D_SOUND + AdjustSamplesVolume(); +#endif + ProcessActiveQueues(); +#ifdef AUDIO_OAL + SampleManager.Service(); +#endif + for (int32 i = 0; i < m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal; i++) { + cAudioScriptObject *object = (cAudioScriptObject *)m_asAudioEntities[m_sAudioScriptObjectManager.m_anScriptObjectEntityIndices[i]].m_pEntity; + delete object; + m_asAudioEntities[m_sAudioScriptObjectManager.m_anScriptObjectEntityIndices[i]].m_pEntity = nil; + DestroyEntity(m_sAudioScriptObjectManager.m_anScriptObjectEntityIndices[i]); + } + m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal = 0; +} + +uint32 +cAudioManager::FL(float f) +{ + return SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex) * f; +} + +uint8 +cAudioManager::ComputeVolume(uint8 emittingVolume, float maxDistance, float distance) +{ + float minDistance; + if (maxDistance <= 0.0f) + return 0; + minDistance = maxDistance / 5.0f; + if (minDistance <= distance) + emittingVolume = sq((maxDistance - minDistance - (distance - minDistance)) / (maxDistance - minDistance)) * emittingVolume; + return emittingVolume; +} + +void +cAudioManager::TranslateEntity(Const CVector *in, CVector *out) +{ + *out = MultiplyInverse(TheCamera.GetMatrix(), *in); +} + +int32 +cAudioManager::ComputePan(float dist, CVector *vec) +{ + Const static uint8 PanTable[64] = {0, 3, 8, 12, 16, 19, 22, 24, 26, 28, 30, 31, 33, 34, 36, 37, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49, 49, 50, 51, 52, 53, 53, + 54, 55, 55, 56, 56, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63}; + + int32 index = vec->x / (dist / 64.0f); + index = Min(63, ABS(index)); + + if (vec->x > 0.f) + return Max(20, 63 - (int8)PanTable[index]); + return Min(107, PanTable[index] + 63); +} + +uint32 +cAudioManager::ComputeDopplerEffectedFrequency(uint32 oldFreq, float position1, float position2, float speedMultiplier) +{ + uint32 newFreq = oldFreq; + if (!TheCamera.Get_Just_Switched_Status() && speedMultiplier != 0.0f) { + float dist = position2 - position1; + if (dist != 0.0f) { + float speedOfSource = (dist / m_nTimeSpent) * speedMultiplier; + if (m_fSpeedOfSound > Abs(speedOfSource)) { + speedOfSource = Clamp2(speedOfSource, 0.0f, 1.5f); + newFreq = (oldFreq * m_fSpeedOfSound) / (speedOfSource + m_fSpeedOfSound); + } + } + } + return newFreq; +} + +int32 +cAudioManager::RandomDisplacement(uint32 seed) +{ + int32 value; + + static bool8 bPos = TRUE; + static uint32 Adjustment = 0; + + if (seed == 0) + return 0; + + value = m_anRandomTable[(Adjustment + seed) % 5] % seed; + Adjustment += value; + + if (value % 2) + bPos = !bPos; + + if (!bPos) + value = -value; + return value; +} + +void +cAudioManager::InterrogateAudioEntities() +{ + for (uint32 i = 0; i < m_nAudioEntitiesCount; i++) { + ProcessEntity(m_aAudioEntityOrderList[i]); + m_asAudioEntities[m_aAudioEntityOrderList[i]].m_AudioEvents = 0; + } +} + +void +cAudioManager::AddSampleToRequestedQueue() +{ + uint32 finalPriority; + uint8 sampleIndex; +#ifdef AUDIO_REFLECTIONS + bool8 bReflections; +#endif + + if (m_sQueueSample.m_nSampleIndex < TOTAL_AUDIO_SAMPLES) { + finalPriority = m_sQueueSample.m_nPriority * (MAX_VOLUME - m_sQueueSample.m_nVolume); + sampleIndex = m_nRequestedCount[m_nActiveQueue]; + if (sampleIndex >= m_nActiveSamples) { + sampleIndex = m_aRequestedOrderList[m_nActiveQueue][m_nActiveSamples - 1]; + if (m_aRequestedQueue[m_nActiveQueue][sampleIndex].m_nFinalPriority <= finalPriority) + return; + } else + m_nRequestedCount[m_nActiveQueue]++; +#if GTA_VERSION < GTA3_PC_10 + if (m_sQueueSample.m_bStatic) { + if (m_sQueueSample.m_nLoopCount > 0) + m_sQueueSample.unk = m_nTimeSpent * SampleManager.GetSampleLength(m_sQueueSample.m_nSampleIndex) / m_sQueueSample.m_nFrequency; + else + m_sQueueSample.unk = -3; + } +#endif + m_sQueueSample.m_nFinalPriority = finalPriority; + m_sQueueSample.m_bIsPlayingFinished = FALSE; +#ifdef AUDIO_REFLECTIONS + if (m_sQueueSample.m_bIs2D) { + m_sQueueSample.m_bReflections = FALSE; + m_sQueueSample.m_nReflectionDelay = 0; + } + if (m_bDynamicAcousticModelingStatus && m_sQueueSample.m_nLoopCount > 0) { + bReflections = m_sQueueSample.m_bReflections; + } else { + bReflections = FALSE; + m_sQueueSample.m_nReflectionDelay = 0; + } + m_sQueueSample.m_bReflections = FALSE; + + if (!m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReverb = FALSE; +#endif + + m_aRequestedQueue[m_nActiveQueue][sampleIndex] = m_sQueueSample; + + AddDetailsToRequestedOrderList(sampleIndex); +#ifdef AUDIO_REFLECTIONS + if (bReflections) + AddReflectionsToRequestedQueue(); +#endif + } +} + +void +cAudioManager::AddDetailsToRequestedOrderList(uint8 sample) +{ + uint32 i = 0; + if (sample != 0) { + for (; i < sample; i++) { + if (m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][i]].m_nFinalPriority > + m_aRequestedQueue[m_nActiveQueue][sample].m_nFinalPriority) + break; + } + if (i < sample) + memmove(&m_aRequestedOrderList[m_nActiveQueue][i + 1], &m_aRequestedOrderList[m_nActiveQueue][i], m_nActiveSamples - i - 1); + } + m_aRequestedOrderList[m_nActiveQueue][i] = sample; +} + +#ifdef AUDIO_REFLECTIONS +void +cAudioManager::AddReflectionsToRequestedQueue() +{ + float reflectionDistance; + int32 noise; + uint32 oldCounter = m_sQueueSample.m_nCounter; + uint8 emittingVolume = (m_sQueueSample.m_nVolume >> 1) + (m_sQueueSample.m_nVolume >> 3); + + for (uint32 i = 0; i < ARRAY_SIZE(m_afReflectionsDistances); i++) { + reflectionDistance = m_afReflectionsDistances[i]; + if (reflectionDistance > 0.0f && reflectionDistance < 100.0f && reflectionDistance < m_sQueueSample.m_MaxDistance) { + m_sQueueSample.m_nReflectionDelay = (reflectionDistance * 500.0f / 1029.0f); + if (m_sQueueSample.m_nReflectionDelay > 5) { + m_sQueueSample.m_fDistance = m_afReflectionsDistances[i]; + SET_EMITTING_VOLUME(emittingVolume); + m_sQueueSample.m_nVolume = ComputeVolume(emittingVolume, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > emittingVolume >> 4) { + m_sQueueSample.m_nCounter = oldCounter + ((i + 1) << 8); + if (m_sQueueSample.m_nLoopCount > 0) { + noise = RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + if (noise > 0) + m_sQueueSample.m_nFrequency -= noise; + else + m_sQueueSample.m_nFrequency += noise; + } + m_sQueueSample.m_nPriority += 20; + m_sQueueSample.m_vecPos = m_avecReflectionsPos[i]; + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::UpdateReflections() +{ + CVector camPos; + CColPoint colpoint; + CEntity *ent; + + if (m_FrameCounter % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[0] = camPos; + m_avecReflectionsPos[0].y += 50.0f; + if (CWorld::ProcessLineOfSight(camPos, m_avecReflectionsPos[0], colpoint, ent, true, false, false, true, false, true, true)) + m_afReflectionsDistances[0] = Distance(camPos, colpoint.point); + else + m_afReflectionsDistances[0] = 50.0f; + } else if ((m_FrameCounter + 1) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[1] = camPos; + m_avecReflectionsPos[1].y -= 50.0f; + if (CWorld::ProcessLineOfSight(camPos, m_avecReflectionsPos[1], colpoint, ent, true, false, false, true, false, true, true)) + m_afReflectionsDistances[1] = Distance(camPos, colpoint.point); + else + m_afReflectionsDistances[1] = 50.0f; + } else if ((m_FrameCounter + 2) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[2] = camPos; + m_avecReflectionsPos[2].x -= 50.0f; + if (CWorld::ProcessLineOfSight(camPos, m_avecReflectionsPos[2], colpoint, ent, true, false, false, true, false, true, true)) + m_afReflectionsDistances[2] = Distance(camPos, colpoint.point); + else + m_afReflectionsDistances[2] = 50.0f; + } else if ((m_FrameCounter + 3) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[3] = camPos; + m_avecReflectionsPos[3].x += 50.0f; + if (CWorld::ProcessLineOfSight(camPos, m_avecReflectionsPos[3], colpoint, ent, true, false, false, true, false, true, true)) + m_afReflectionsDistances[3] = Distance(camPos, colpoint.point); + else + m_afReflectionsDistances[3] = 50.0f; + } else if ((m_FrameCounter + 4) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[4] = camPos; + m_avecReflectionsPos[4].z += 50.0f; + if (CWorld::ProcessVerticalLine(camPos, m_avecReflectionsPos[4].z, colpoint, ent, true, false, false, false, true, false, nil)) + m_afReflectionsDistances[4] = colpoint.point.z - camPos.z; + else + m_afReflectionsDistances[4] = 50.0f; + } +} +#endif // AUDIO_REFLECTIONS + +void +cAudioManager::AddReleasingSounds() +{ + // in case someone would want to increase it +#ifdef FIX_BUGS + bool8 toProcess[NUM_CHANNELS_GENERIC]; +#else + bool8 toProcess[44]; +#endif + + uint8 queue = m_nActiveQueue == 0 ? 1 : 0; + + for (uint8 i = 0; i < m_nRequestedCount[queue]; i++) { + tSound &sample = m_aRequestedQueue[queue][m_aRequestedOrderList[queue][i]]; + if (sample.m_bIsPlayingFinished) + continue; + + toProcess[i] = FALSE; + for (uint8 j = 0; j < m_nRequestedCount[m_nActiveQueue]; j++) { + if (sample.m_nEntityIndex == m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][j]].m_nEntityIndex && + sample.m_nCounter == m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][j]].m_nCounter) { + toProcess[i] = TRUE; + break; + } + } + if (!toProcess[i]) { +#ifdef AUDIO_REFLECTIONS + if (sample.m_nCounter <= 255 || sample.m_nReflectionDelay == 0) // check if not delayed reflection +#endif + { +#ifdef ATTACH_RELEASING_SOUNDS_TO_ENTITIES + if (sample.m_nCounter <= 255 && !sample.m_bIs2D) { // check if not reflection and is a 3D sound + CEntity* entity = (CEntity*)GetEntityPointer(sample.m_nEntityIndex); + if (entity && m_asAudioEntities[sample.m_nEntityIndex].m_nType == AUDIOTYPE_PHYSICAL) { + sample.m_vecPos = entity->GetPosition(); + float oldDistance = sample.m_fDistance; + sample.m_fDistance = Sqrt(GetDistanceSquared(sample.m_vecPos)); + if (sample.m_nSampleIndex >= SAMPLEBANK_PED_START && sample.m_nSampleIndex <= SAMPLEBANK_PED_END) { // check if it's ped comment + uint8 vol; + if (CWorld::GetIsLineOfSightClear(TheCamera.GetPosition(), sample.m_vecPos, true, false, false, false, false, false)) + vol = PED_COMMENT_VOLUME; + else + vol = PED_COMMENT_VOLUME_BEHIND_WALL; +#ifdef EXTERNAL_3D_SOUND + sample.m_nEmittingVolume = vol; +#endif + sample.m_nVolume = ComputeVolume(vol, sample.m_MaxDistance, sample.m_fDistance); + } else { + // calculate new volume with changed distance + float volumeDiff = sq((sample.m_MaxDistance - sample.m_fDistance) / (sample.m_MaxDistance - oldDistance)); + if (volumeDiff > 0.0f) { + uint8 newVolume = volumeDiff * sample.m_nVolume; + if (sample.m_nVolumeChange > 0) + sample.m_nVolumeChange = volumeDiff * sample.m_nVolumeChange; +#if defined(FIX_BUGS) && defined(EXTERNAL_3D_SOUND) + if (sample.m_nEmittingVolumeChange > 0) + sample.m_nEmittingVolumeChange = volumeDiff * sample.m_nEmittingVolumeChange; +#endif + sample.m_nVolume = Min(MAX_VOLUME, newVolume); + } + } + if (sample.m_nVolume == 0) + sample.m_nFramesToPlay = 0; + } + } +#endif +#ifdef FIX_BUGS + // fixing emitting volume not being lowered and high fps bugs + if (sample.m_nFramesToPlay <= 0) + continue; + if (sample.m_nLoopCount == 0) { + if (sample.m_nVolumeChange == -1) { + sample.m_nVolumeChange = sample.m_nVolume / sample.m_nFramesToPlay; + if (sample.m_nVolumeChange <= 0) + sample.m_nVolumeChange = 1; +#ifdef EXTERNAL_3D_SOUND + sample.m_nEmittingVolumeChange = sample.m_nEmittingVolume / sample.m_nFramesToPlay; + if (sample.m_nEmittingVolumeChange <= 0) + sample.m_nEmittingVolumeChange = 1; +#endif + } + if (sample.m_nVolume <= sample.m_nVolumeChange * CTimer::GetTimeStepFix()) { + sample.m_nFramesToPlay = 0; + continue; + } + sample.m_nVolume -= sample.m_nVolumeChange * CTimer::GetTimeStepFix(); +#ifdef EXTERNAL_3D_SOUND + if (sample.m_nEmittingVolume <= sample.m_nEmittingVolumeChange * CTimer::GetTimeStepFix()) { + sample.m_nFramesToPlay = 0; + continue; + } + sample.m_nEmittingVolume -= sample.m_nEmittingVolumeChange * CTimer::GetTimeStepFix(); +#endif + } + sample.m_nFramesToPlay -= CTimer::GetTimeStepFix(); + if (sample.m_nFramesToPlay < 0) + sample.m_nFramesToPlay = 0; +#else + if (sample.m_nFramesToPlay == 0) + continue; + if (sample.m_nLoopCount == 0) { + if (sample.m_nVolumeChange == -1) { + sample.m_nVolumeChange = sample.m_nVolume / sample.m_nFramesToPlay; + if (sample.m_nVolumeChange <= 0) + sample.m_nVolumeChange = 1; + } + if (sample.m_nVolume <= sample.m_nVolumeChange) { + sample.m_nFramesToPlay = 0; + continue; + } + sample.m_nVolume -= sample.m_nVolumeChange; + } + sample.m_nFramesToPlay--; +#endif + if (m_bReduceReleasingPriority) { + if (sample.m_nPriority < 20) + sample.m_nPriority++; + } + sample.m_bStatic = FALSE; + } + memcpy(&m_sQueueSample, &sample, sizeof(tSound)); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessActiveQueues() +{ + bool8 flag; + float position2; + float position1; + + uint32 samplesPerFrame; + uint32 samplesToPlay; + +#ifdef EXTERNAL_3D_SOUND + float x; + float usedX; + float usedY; + float usedZ; +#endif + + uint8 vol; + uint8 emittingVol; + CVector position; + +#ifdef EXTERNAL_3D_SOUND + #define WORKING_VOLUME_FIELD m_nEmittingVolume +#else + #define WORKING_VOLUME_FIELD m_nVolume +#endif + +#ifdef USE_TIME_SCALE_FOR_AUDIO + float timeScale = m_bIsPaused ? 1.0f : CTimer::GetTimeScale(); +#endif + + for (uint8 i = 0; i < m_nActiveSamples; i++) { + m_aRequestedQueue[m_nActiveQueue][i].m_bIsBeingPlayed = FALSE; + m_asActiveSamples[i].m_bIsBeingPlayed = FALSE; + } + + for (uint8 i = 0; i < m_nRequestedCount[m_nActiveQueue]; i++) { + tSound &sample = m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][i]]; + if (sample.m_nSampleIndex != NO_SAMPLE) { + for (uint8 j = 0; j < m_nActiveSamples; j++) { + if (sample.m_nEntityIndex == m_asActiveSamples[j].m_nEntityIndex && sample.m_nCounter == m_asActiveSamples[j].m_nCounter && + sample.m_nSampleIndex == m_asActiveSamples[j].m_nSampleIndex) { + if (sample.m_nLoopCount > 0) { +#if GTA_VERSION >= GTA3_PC_10 + if (m_FrameCounter & 1) + flag = !!(j & 1); + else + flag = !(j & 1); + if (flag && !SampleManager.GetChannelUsedFlag(j)) { +#else + if (m_asActiveSamples[j].unk != 0) + m_asActiveSamples[j].unk--; + else if (SampleManager.GetChannelUsedFlag(j)) + m_asActiveSamples[j].unk = m_nTimeSpent * SampleManager.GetSampleLength(m_asActiveSamples[j].m_nSampleIndex) / m_asActiveSamples[j].m_nFrequency; + else { +#endif + sample.m_bIsPlayingFinished = TRUE; + m_asActiveSamples[j].m_bIsPlayingFinished = TRUE; + m_asActiveSamples[j].m_nSampleIndex = NO_SAMPLE; + m_asActiveSamples[j].m_nEntityIndex = AEHANDLE_NONE; + continue; + } + } + sample.m_bIsBeingPlayed = TRUE; + m_asActiveSamples[j].m_bIsBeingPlayed = TRUE; + sample.m_nVolumeChange = -1; + if (!sample.m_bStatic) { + if (sample.m_bIs2D) { + emittingVol = m_bDoubleVolume ? 2 * Min(63, sample.WORKING_VOLUME_FIELD) : sample.WORKING_VOLUME_FIELD; +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(j, sample.m_nFrequency * timeScale); +#else + SampleManager.SetChannelFrequency(j, sample.m_nFrequency); +#endif +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetChannelEmittingVolume(j, emittingVol); +#else + SampleManager.SetChannelPan(j, sample.m_nPan); + SampleManager.SetChannelVolume(j, sample.m_nVolume); +#endif + } else { + position2 = sample.m_fDistance; + position1 = m_asActiveSamples[j].m_fDistance; + m_asActiveSamples[j].m_fDistance = sample.m_fDistance; + sample.m_nFrequency = ComputeDopplerEffectedFrequency(sample.m_nFrequency, position1, position2, sample.m_fSpeedMultiplier); + if (sample.m_nFrequency != m_asActiveSamples[j].m_nFrequency) { + uint32 freq = Clamp2((int32)sample.m_nFrequency, (int32)m_asActiveSamples[j].m_nFrequency, 6000); + m_asActiveSamples[j].m_nFrequency = freq; +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(j, freq * timeScale); +#else + SampleManager.SetChannelFrequency(j, freq); +#endif + } + +#ifdef EXTERNAL_3D_SOUND + if (sample.m_nEmittingVolume != m_asActiveSamples[j].m_nEmittingVolume) { + vol = Clamp2((int8)sample.m_nEmittingVolume, (int8)m_asActiveSamples[j].m_nEmittingVolume, 10); + SampleManager.SetChannelEmittingVolume(j, m_bDoubleVolume ? 2 * Min(63, vol) : vol); + m_asActiveSamples[j].m_nEmittingVolume = vol; + } +#else + if (sample.m_nVolume != m_asActiveSamples[j].m_nVolume) { + vol = Clamp2((int8)sample.m_nVolume, (int8)m_asActiveSamples[j].m_nVolume, 10); + m_asActiveSamples[j].m_nVolume = vol; + } + SampleManager.SetChannelVolume(j, m_bDoubleVolume ? 2 * Min(63, m_asActiveSamples[j].m_nVolume) : m_asActiveSamples[j].m_nVolume); +#endif + TranslateEntity(&sample.m_vecPos, &position); +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetChannel3DPosition(j, position.x, position.y, position.z); + SampleManager.SetChannel3DDistances(j, sample.m_MaxDistance, 0.25f * sample.m_MaxDistance); +#else + sample.m_nPan = ComputePan(sample.m_fDistance, &position); + SampleManager.SetChannelPan(j, sample.m_nPan); +#endif + } + SampleManager.SetChannelReverbFlag(j, sample.m_bReverb); + break; + } + sample.m_bIsBeingPlayed = FALSE; + m_asActiveSamples[j].m_bIsBeingPlayed = FALSE; + } + } + } + } + for (uint8 i = 0; i < m_nActiveSamples; i++) { + if (m_asActiveSamples[i].m_nSampleIndex != NO_SAMPLE && !m_asActiveSamples[i].m_bIsBeingPlayed) { + SampleManager.StopChannel(i); + m_asActiveSamples[i].m_nSampleIndex = NO_SAMPLE; + m_asActiveSamples[i].m_nEntityIndex = AEHANDLE_NONE; + } + } + for (uint8 i = 0; i < m_nRequestedCount[m_nActiveQueue]; i++) { + tSound &sample = m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][i]]; + if (!sample.m_bIsBeingPlayed && !sample.m_bIsPlayingFinished && m_asAudioEntities[sample.m_nEntityIndex].m_bIsUsed && sample.m_nSampleIndex < NO_SAMPLE) { +#ifdef AUDIO_REFLECTIONS + if (sample.m_nCounter > 255 && sample.m_nLoopCount > 0 && sample.m_nReflectionDelay > 0) { // check if reflection + sample.m_nReflectionDelay--; + sample.m_nFramesToPlay = 1; + } else +#endif + { + for (uint8 j = 0; j < m_nActiveSamples; j++) { + if (!m_asActiveSamples[j].m_bIsBeingPlayed) { + if (sample.m_nLoopCount > 0) { + samplesPerFrame = sample.m_nFrequency / m_nTimeSpent; + samplesToPlay = sample.m_nLoopCount * SampleManager.GetSampleLength(sample.m_nSampleIndex); + if (samplesPerFrame == 0) + continue; + sample.m_nFramesToPlay = samplesToPlay / samplesPerFrame + 1; + } + memcpy(&m_asActiveSamples[j], &sample, sizeof(tSound)); + if (!m_asActiveSamples[j].m_bIs2D) { + TranslateEntity(&m_asActiveSamples[j].m_vecPos, &position); +#ifndef EXTERNAL_3D_SOUND + m_asActiveSamples[j].m_nPan = ComputePan(m_asActiveSamples[j].m_fDistance, &position); +#endif + } + emittingVol = m_bDoubleVolume ? 2 * Min(63, m_asActiveSamples[j].WORKING_VOLUME_FIELD) : m_asActiveSamples[j].WORKING_VOLUME_FIELD; +#ifdef GTA_PS2 + { + SampleManager.InitialiseChannel(j, m_asActiveSamples[j].m_nSampleIndex, m_asActiveSamples[j].m_nBankIndex); +#else + if (SampleManager.InitialiseChannel(j, m_asActiveSamples[j].m_nSampleIndex, m_asActiveSamples[j].m_nBankIndex)) { +#endif +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(j, m_asActiveSamples[j].m_nFrequency * timeScale); +#else + SampleManager.SetChannelFrequency(j, m_asActiveSamples[j].m_nFrequency); +#endif +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetChannelEmittingVolume(j, emittingVol); +#else + SampleManager.SetChannelVolume(j, emittingVol); + SampleManager.SetChannelPan(j, m_asActiveSamples[j].m_nPan); +#endif +#ifndef GTA_PS2 + SampleManager.SetChannelLoopPoints(j, m_asActiveSamples[j].m_nLoopStart, m_asActiveSamples[j].m_nLoopEnd); +#endif + SampleManager.SetChannelLoopCount(j, m_asActiveSamples[j].m_nLoopCount); + SampleManager.SetChannelReverbFlag(j, m_asActiveSamples[j].m_bReverb); +#ifdef EXTERNAL_3D_SOUND + if (m_asActiveSamples[j].m_bIs2D) { + uint8 offset = m_asActiveSamples[j].m_nPan; + if (offset == 63) + x = 0.f; + else if (offset >= 63) + x = (offset - 63) * 1000.0f / 63; + else + x = -(63 - offset) * 1000.0f / 63; + usedX = x; + usedY = 0.0f; + usedZ = 0.0f; + m_asActiveSamples[j].m_MaxDistance = 100000.0f; + } else { + usedX = position.x; + usedY = position.y; + usedZ = position.z; + } + SampleManager.SetChannel3DPosition(j, usedX, usedY, usedZ); + SampleManager.SetChannel3DDistances(j, m_asActiveSamples[j].m_MaxDistance, 0.25f * m_asActiveSamples[j].m_MaxDistance); +#endif + SampleManager.StartChannel(j); + } + m_asActiveSamples[j].m_bIsBeingPlayed = TRUE; + sample.m_bIsBeingPlayed = TRUE; + sample.m_nVolumeChange = -1; + break; + } + } + } + } + } + +#ifdef USE_TIME_SCALE_FOR_AUDIO + for (uint8 i = 0; i < m_nActiveSamples; i++) { + if (m_asActiveSamples[i].m_nSampleIndex != NO_SAMPLE && m_asActiveSamples[i].m_bIsBeingPlayed) + SampleManager.SetChannelFrequency(i, m_asActiveSamples[i].m_nFrequency * timeScale); + } +#endif + + #undef WORKING_VOLUME_FIELD +} + +void +cAudioManager::ClearRequestedQueue() +{ + for (uint8 i = 0; i < m_nActiveSamples; i++) + m_aRequestedOrderList[m_nActiveQueue][i] = m_nActiveSamples; + m_nRequestedCount[m_nActiveQueue] = 0; +} + +void +cAudioManager::ClearActiveSamples() +{ + for (uint8 i = 0; i < m_nActiveSamples; i++) { + m_asActiveSamples[i].m_nEntityIndex = AEHANDLE_NONE; + m_asActiveSamples[i].m_nCounter = 0; + m_asActiveSamples[i].m_nSampleIndex = NO_SAMPLE; + m_asActiveSamples[i].m_nBankIndex = INVALID_SFX_BANK; + m_asActiveSamples[i].m_bIs2D = FALSE; + m_asActiveSamples[i].m_nPriority = 5; + m_asActiveSamples[i].m_nFrequency = 0; + m_asActiveSamples[i].m_nVolume = 0; +#ifdef EXTERNAL_3D_SOUND + m_asActiveSamples[i].m_nEmittingVolume = 0; +#endif + m_asActiveSamples[i].m_fDistance = 0.0f; + m_asActiveSamples[i].m_bIsBeingPlayed = FALSE; + m_asActiveSamples[i].m_bIsPlayingFinished = FALSE; + m_asActiveSamples[i].m_nLoopCount = 1; +#ifndef GTA_PS2 + m_asActiveSamples[i].m_nLoopStart = 0; + m_asActiveSamples[i].m_nLoopEnd = -1; +#endif + m_asActiveSamples[i].m_fSpeedMultiplier = 0.0f; + m_asActiveSamples[i].m_MaxDistance = 200.0f; + m_asActiveSamples[i].m_nPan = 63; + m_asActiveSamples[i].m_bStatic = FALSE; +#if GTA_VERSION < GTA3_PC_10 + m_asActiveSamples[i].unk = -3; +#endif + m_asActiveSamples[i].m_nFinalPriority = 0; + m_asActiveSamples[i].m_nFramesToPlay = 0; + m_asActiveSamples[i].m_nVolumeChange = -1; + m_asActiveSamples[i].m_vecPos = CVector(0.0f, 0.0f, 0.0f); + m_asActiveSamples[i].m_bReverb = FALSE; +#ifdef AUDIO_REFLECTIONS + m_asActiveSamples[i].m_nReflectionDelay = 0; + m_asActiveSamples[i].m_bReflections = FALSE; +#endif + } +} + +void +cAudioManager::GenerateIntegerRandomNumberTable() +{ + for (uint32 i = 0; i < ARRAY_SIZE(m_anRandomTable); i++) + m_anRandomTable[i] = myrand(); +} + +#ifdef GTA_PS2 +bool8 +cAudioManager::LoadBankIfNecessary(uint8 bank) +{ + if(!SampleManager.IsSampleBankLoaded(bank)) + return SampleManager.LoadSampleBank(bank); + return FALSE; +} +#endif + +#ifdef EXTERNAL_3D_SOUND +void +cAudioManager::AdjustSamplesVolume() +{ + for (uint8 i = 0; i < m_nRequestedCount[m_nActiveQueue]; i++) { + tSound *pSample = &m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][i]]; + + if (!pSample->m_bIs2D) + pSample->m_nEmittingVolume = ComputeEmittingVolume(pSample->m_nEmittingVolume, pSample->m_MaxDistance, pSample->m_fDistance); + } +} + +uint8 +cAudioManager::ComputeEmittingVolume(uint8 emittingVolume, float maxDistance, float distance) +{ + float minDistance = maxDistance / 4.0f; + float diffDistance = maxDistance - minDistance; + if (distance > diffDistance) + return (minDistance - (distance - diffDistance)) * (float)emittingVolume / minDistance; + return emittingVolume; +} +#endif diff --git a/src/audio/AudioManager.h b/src/audio/AudioManager.h new file mode 100644 index 0000000..7983987 --- /dev/null +++ b/src/audio/AudioManager.h @@ -0,0 +1,587 @@ +#pragma once + +#include "audio_enums.h" +#include "AudioCollision.h" +#include "PolRadio.h" + +class tSound +{ +public: + int32 m_nEntityIndex; // audio entity index +#if GTA_VERSION >= GTA3_PC_10 + uint32 m_nCounter; // I'm not sure what this is but it looks like a virtual counter to determine the same sound in queue + // Values higher than 255 are used by reflections +#else + uint8 m_nCounter; +#endif + uint32 m_nSampleIndex; // An index of sample from AudioSamples.h + uint8 m_nBankIndex; // A sound bank index. IDK what's the point of it here since samples are hardcoded anyway + bool8 m_bIs2D; // If TRUE then sound is played in 2D space (such as frontend or police radio) + uint32 m_nPriority; // The multiplier for the sound priority (see m_nFinalPriority below). Lesser value means higher priority + uint32 m_nFrequency; // Sound frequency, plain and simple + uint8 m_nVolume; // Sound volume (0..127), only used as an actual volume without EXTERNAL_3D_SOUND (see m_nEmittingVolume) + float m_fDistance; // Distance to camera (useless if m_bIs2D == TRUE) + uint32 m_nLoopCount; // 0 - always loop, 1 - don't loop, other values never seen +#ifndef GTA_PS2 + // Loop offsets + uint32 m_nLoopStart; + int32 m_nLoopEnd; +#endif +#ifdef EXTERNAL_3D_SOUND + uint8 m_nEmittingVolume; // The volume in 3D space, provided to 3D audio engine +#endif + float m_fSpeedMultiplier; // Used for doppler effect. 0.0f - unaffected by doppler +#if GTA_VERSION >= GTA3_PC_10 + float m_MaxDistance; // The maximum distance at which sound could be heard. Minimum distance = MaxDistance / 5 or MaxDistance / 4 in case of emitting volume (useless if m_bIs2D == TRUE) +#else + uint32 m_MaxDistance; +#endif + bool8 m_bStatic; // If TRUE then sound parameters cannot be changed during playback (frequency, position, etc.) + CVector m_vecPos; // Position of sound in 3D space. Unused if m_bIs2D == TRUE + bool8 m_bReverb; // Toggles reverb effect +#ifdef AUDIO_REFLECTIONS + uint8 m_nReflectionDelay; // Number of frames before reflection could be played. This is calculated internally by AudioManager and shouldn't be set by queued sample + bool8 m_bReflections; // Add sound reflections +#endif + uint8 m_nPan; // Sound panning (0-127). Controls the volume of the playback coming from left and right speaker. Calculated internally unless m_bIs2D==TRUE. + // 0 = L 100% R 0% + // 63 = L 100% R 100% + // 127 = L 0% R 100% +#ifndef FIX_BUGS + uint32 m_nFramesToPlay; // Number of frames the sound would be played (if it stops being queued). + // This one is being set by queued sample for looping sounds, otherwise calculated inside AudioManager +#else + float m_nFramesToPlay; // Made into float for high fps fix +#endif + + // all fields below are internal to AudioManager calculations and aren't set by queued sample + bool8 m_bIsBeingPlayed; // Set to TRUE when the sound was added or changed on current frame to avoid it being overwritten + bool8 m_bIsPlayingFinished; // Not sure about the name. Set to TRUE when sampman channel becomes free +#if GTA_VERSION < GTA3_PC_10 + int32 unk; // (inherited from GTA 2) Only on PS2, used by static non-looped sounds (AFAIK) + // Looks like it's keeping a number of frames left to play with the purpose of setting m_bIsPlayingFinished=TRUE once value reaches 0 + // Default value is -3 for whatever reason +#endif + uint32 m_nFinalPriority; // Actual value used to compare priority, calculated using volume and m_nPriority. Lesser value means higher priority + int8 m_nVolumeChange; // How much m_nVolume should reduce per each frame. +#if defined(FIX_BUGS) && defined(EXTERNAL_3D_SOUND) + int8 m_nEmittingVolumeChange; // same as above but for m_nEmittingVolume +#endif +}; + +VALIDATE_SIZE(tSound, 92); + +class CPhysical; +class CAutomobile; + +class tAudioEntity +{ +public: + eAudioType m_nType; + void *m_pEntity; + bool8 m_bIsUsed; + bool8 m_bStatus; + int16 m_awAudioEvent[NUM_AUDIOENTITY_EVENTS]; + float m_afVolume[NUM_AUDIOENTITY_EVENTS]; + uint8 m_AudioEvents; +}; + +VALIDATE_SIZE(tAudioEntity, 40); + +class tPedComment +{ +public: + uint32 m_nSampleIndex; + int32 m_nEntityIndex; + CVector m_vecPos; + float m_fDistance; + uint8 m_nVolume; + int8 m_nLoadingTimeout; // how many iterations we gonna wait until dropping the sample if it's still not loaded (only useful on PS2) +#if defined(EXTERNAL_3D_SOUND) && defined(FIX_BUGS) + uint8 m_nEmittingVolume; +#endif +}; + +VALIDATE_SIZE(tPedComment, 28); + +class cPedComments +{ +public: + tPedComment m_aPedCommentQueue[NUM_SOUND_QUEUES][NUM_PED_COMMENTS_SLOTS]; + uint8 m_aPedCommentOrderList[NUM_SOUND_QUEUES][NUM_PED_COMMENTS_SLOTS]; + uint8 m_nPedCommentCount[NUM_SOUND_QUEUES]; + uint8 m_nActiveQueue; + + cPedComments() + { + for (int i = 0; i < NUM_PED_COMMENTS_SLOTS; i++) + for (int j = 0; j < NUM_SOUND_QUEUES; j++) { + m_aPedCommentQueue[j][i].m_nLoadingTimeout = -1; + m_aPedCommentOrderList[j][i] = NUM_PED_COMMENTS_SLOTS; + } + + for (int i = 0; i < NUM_SOUND_QUEUES; i++) + m_nPedCommentCount[i] = 0; + m_nActiveQueue = 0; + } + void Add(tPedComment *com); + void Process(); +}; + +VALIDATE_SIZE(cPedComments, 1164); + +// name made up +class cAudioScriptObjectManager +{ +public: + int32 m_anScriptObjectEntityIndices[NUM_SCRIPT_MAX_ENTITIES]; + int32 m_nScriptObjectEntityTotal; + + cAudioScriptObjectManager() { m_nScriptObjectEntityTotal = 0; } + ~cAudioScriptObjectManager() { m_nScriptObjectEntityTotal = 0; } +}; + + +class cTransmission; +class CEntity; +class CPlane; +class CVehicle; +class CPed; + +class cPedParams +{ +public: + bool8 m_bDistanceCalculated; + float m_fDistance; + CPed *m_pPed; + + cPedParams() + { + m_bDistanceCalculated = false; + m_fDistance = 0.0f; + m_pPed = nil; + } +}; + +class cVehicleParams +{ +public: + bool8 m_bDistanceCalculated; + float m_fDistance; + CVehicle *m_pVehicle; + cTransmission *m_pTransmission; + uint32 m_nIndex; + float m_fVelocityChange; + + cVehicleParams() + { + m_bDistanceCalculated = false; + m_fDistance = 0.0f; + m_pVehicle = nil; + m_pTransmission = nil; + m_nIndex = 0; + m_fVelocityChange = 0.0f; + } +}; + +VALIDATE_SIZE(cVehicleParams, 0x18); + +enum { + /* + REFLECTION_YMAX = 0, top + REFLECTION_YMIN = 1, bottom + REFLECTION_XMIN = 2, left + REFLECTION_XMAX = 3, right + REFLECTION_ZMAX = 4, + */ + + REFLECTION_TOP = 0, + REFLECTION_BOTTOM, + REFLECTION_LEFT, + REFLECTION_RIGHT, + REFLECTION_UP, + MAX_REFLECTIONS, +}; + +enum PLAY_STATUS { PLAY_STATUS_STOPPED = 0, PLAY_STATUS_PLAYING, PLAY_STATUS_FINISHED }; +enum LOADING_STATUS { LOADING_STATUS_NOT_LOADED = 0, LOADING_STATUS_LOADED, LOADING_STATUS_LOADING }; + +class cAudioManager +{ +public: + bool8 m_bIsInitialised; + bool8 m_bIsSurround; // unused until VC + bool8 m_bReduceReleasingPriority; + uint8 m_nActiveSamples; + bool8 m_bDoubleVolume; // unused +#if GTA_VERSION >= GTA3_PC_10 + bool8 m_bDynamicAcousticModelingStatus; +#endif + float m_fSpeedOfSound; + bool8 m_bTimerJustReset; + uint32 m_nTimer; + tSound m_sQueueSample; + uint8 m_nActiveQueue; + tSound m_aRequestedQueue[NUM_SOUND_QUEUES][NUM_CHANNELS_GENERIC]; + uint8 m_aRequestedOrderList[NUM_SOUND_QUEUES][NUM_CHANNELS_GENERIC]; + uint8 m_nRequestedCount[NUM_SOUND_QUEUES]; + tSound m_asActiveSamples[NUM_CHANNELS_GENERIC]; + tAudioEntity m_asAudioEntities[NUM_AUDIOENTITIES]; + uint32 m_aAudioEntityOrderList[NUM_AUDIOENTITIES]; + uint32 m_nAudioEntitiesCount; +#ifdef AUDIO_REFLECTIONS + CVector m_avecReflectionsPos[MAX_REFLECTIONS]; + float m_afReflectionsDistances[MAX_REFLECTIONS]; +#endif + cAudioScriptObjectManager m_sAudioScriptObjectManager; + cPedComments m_sPedComments; + int32 m_nFireAudioEntity; + int32 m_nWaterCannonEntity; + int32 m_nPoliceChannelEntity; + cPoliceRadioQueue m_sPoliceRadioQueue; + cAMCrime m_aCrimes[10]; + int32 m_nFrontEndEntity; + int32 m_nCollisionEntity; + cAudioCollisionManager m_sCollisionManager; + int32 m_nProjectileEntity; + int32 m_nBridgeEntity; + + // Mission audio stuff + CVector m_vecMissionAudioPosition; + bool8 m_bIsMissionAudio2D; + uint32 m_nMissionAudioSampleIndex; + uint8 m_nMissionAudioLoadingStatus; + uint8 m_nMissionAudioPlayStatus; + bool8 m_bIsMissionAudioPlaying; + int32 m_nMissionAudioFramesToPlay; // possibly unsigned + bool8 m_bIsMissionAudioAllowedToPlay; + + int32 m_anRandomTable[5]; + uint8 m_nTimeSpent; + bool8 m_bIsPaused; + bool8 m_bWasPaused; + uint32 m_FrameCounter; + + cAudioManager(); + ~cAudioManager(); + + void Initialise(); + void Terminate(); + void Service(); + int32 CreateEntity(eAudioType type, void *entity); + void DestroyEntity(int32 id); + bool8 GetEntityStatus(int32 id); + void SetEntityStatus(int32 id, bool8 status); + void *GetEntityPointer(int32 id); + void PlayOneShot(int32 index, uint16 sound, float vol); + void SetEffectsMasterVolume(uint8 volume); + void SetMusicMasterVolume(uint8 volume); + void SetEffectsFadeVol(uint8 volume); + void SetMusicFadeVol(uint8 volume); + void SetMonoMode(bool8 mono); + void ResetTimers(uint32 time); + void DestroyAllGameCreatedEntities(); + +#ifdef GTA_PC + uint8 GetNum3DProvidersAvailable(); + char *Get3DProviderName(uint8 id); + int8 GetCurrent3DProviderIndex(); + int8 SetCurrent3DProvider(uint8 which); + void SetSpeakerConfig(int32 conf); + bool8 IsMP3RadioChannelAvailable(); + void ReleaseDigitalHandle(); + void ReacquireDigitalHandle(); +#ifdef AUDIO_REFLECTIONS + void SetDynamicAcousticModelingStatus(bool8 status); +#endif + bool8 CheckForAnAudioFileOnCD(); + char GetCDAudioDriveLetter(); + bool8 IsAudioInitialised(); +#endif + + void ServiceSoundEffects(); + uint32 FL(float f); // not used + uint8 ComputeVolume(uint8 emittingVolume, float maxDistance, float distance); + void TranslateEntity(Const CVector *v1, CVector *v2); + int32 ComputePan(float, CVector *); + uint32 ComputeDopplerEffectedFrequency(uint32 oldFreq, float position1, float position2, float speedMultiplier); // inlined on PS2 + int32 RandomDisplacement(uint32 seed); + void InterrogateAudioEntities(); // inlined on PS2 + void AddSampleToRequestedQueue(); + void AddDetailsToRequestedOrderList(uint8 sample); // inlined on PS2 +#ifdef AUDIO_REFLECTIONS + void AddReflectionsToRequestedQueue(); + void UpdateReflections(); +#endif + void AddReleasingSounds(); + void ProcessActiveQueues(); + void ClearRequestedQueue(); // inlined on PS2 + void ClearActiveSamples(); + void GenerateIntegerRandomNumberTable(); // inlined on PS2 +#ifdef GTA_PS2 + bool8 LoadBankIfNecessary(uint8 bank); // this is used only on PS2 but technically not a platform code +#endif + +#ifdef EXTERNAL_3D_SOUND // actually must have been && AUDIO_MSS as well + void AdjustSamplesVolume(); + uint8 ComputeEmittingVolume(uint8 emittingVolume, float maxDistance, float distance); +#endif + + // audio logic + void PreInitialiseGameSpecificSetup(); + void PostInitialiseGameSpecificSetup(); + void PreTerminateGameSpecificShutdown(); + void PostTerminateGameSpecificShutdown(); + void ResetAudioLogicTimers(uint32 timer); + void ProcessReverb(); + float GetDistanceSquared(const CVector &v); + void CalculateDistance(bool8 &condition, float dist); + void ProcessSpecial(); + void ProcessEntity(int32 id); + void ProcessPhysical(int32 id); + + // vehicles + void ProcessVehicle(CVehicle *vehicle); + void ProcessRainOnVehicle(cVehicleParams ¶ms); + bool8 ProcessReverseGear(cVehicleParams ¶ms); + void ProcessModelCarEngine(cVehicleParams ¶ms); + bool8 ProcessVehicleRoadNoise(cVehicleParams ¶ms); + bool8 ProcessWetRoadNoise(cVehicleParams ¶ms); + bool8 ProcessVehicleEngine(cVehicleParams ¶ms); + void UpdateGasPedalAudio(CAutomobile *automobile); // inlined on PS2 + void PlayerJustGotInCar(); + void PlayerJustLeftCar(); + void AddPlayerCarSample(uint8 emittingVolume, uint32 freq, uint32 sample, uint8 bank, uint8 counter, bool8 notLooping); + void ProcessCesna(cVehicleParams ¶ms); + void ProcessPlayersVehicleEngine(cVehicleParams ¶ms, CAutomobile *automobile); + bool8 ProcessVehicleSkidding(cVehicleParams ¶ms); + float GetVehicleDriveWheelSkidValue(uint8 wheel, CAutomobile *automobile, cTransmission *transmission, float velocityChange); + float GetVehicleNonDriveWheelSkidValue(uint8 wheel, CAutomobile *automobile, cTransmission *transmission, float velocityChange); // inlined on PS2 + bool8 ProcessVehicleHorn(cVehicleParams ¶ms); + bool8 UsesSiren(uint32 model); // inlined on PS2 + bool8 UsesSirenSwitching(uint32 model); // inlined on PS2 + bool8 ProcessVehicleSirenOrAlarm(cVehicleParams ¶ms); + bool8 UsesReverseWarning(uint32 model); // inlined on PS2 + bool8 ProcessVehicleReverseWarning(cVehicleParams ¶ms); + bool8 ProcessVehicleDoors(cVehicleParams ¶ms); + bool8 ProcessAirBrakes(cVehicleParams ¶ms); + bool8 HasAirBrakes(uint32 model); // inlined on PS2 + bool8 ProcessEngineDamage(cVehicleParams ¶ms); + bool8 ProcessCarBombTick(cVehicleParams ¶ms); + void ProcessVehicleOneShots(cVehicleParams ¶ms); + bool8 ProcessTrainNoise(cVehicleParams ¶ms); + bool8 ProcessBoatEngine(cVehicleParams ¶ms); + bool8 ProcessBoatMovingOverWater(cVehicleParams ¶ms); + bool8 ProcessHelicopter(cVehicleParams ¶ms); + void ProcessPlane(cVehicleParams ¶ms); // inlined on PS2 + void ProcessJumbo(cVehicleParams ¶ms); + void ProcessJumboTaxi(); // inlined on PS2 + void ProcessJumboAccel(CPlane *plane); + void ProcessJumboTakeOff(CPlane *plane); // inlined on PS2 + void ProcessJumboFlying(); // inlined on PS2 + void ProcessJumboLanding(CPlane *plane); // inlined on PS2 + void ProcessJumboDecel(CPlane *plane); // inlined on PS2 + bool8 SetupJumboTaxiSound(uint8 vol); + bool8 SetupJumboWhineSound(uint8 emittingVol, uint32 freq); + bool8 SetupJumboEngineSound(uint8 vol, uint32 freq); + bool8 SetupJumboFlySound(uint8 emittingVol); + bool8 SetupJumboRumbleSound(uint8 emittingVol); + int32 GetJumboTaxiFreq(); // inlined on PS2 + + // peds + void ProcessPed(CPhysical *ped); // inlined on PS2 + void ProcessPedHeadphones(cPedParams ¶ms); + void ProcessPedOneShots(cPedParams ¶ms); + + // ped comments + void SetupPedComments(cPedParams ¶ms, uint16 sound); + int32 GetPedCommentSfx(CPed *ped, uint16 sound); + void GetPhrase(uint32 &phrase, uint32 &prevPhrase, uint32 sample, uint32 maxOffset); // inlined on PS2 + uint32 GetPlayerTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetCopTalkSfx(uint16 sound); + uint32 GetSwatTalkSfx(uint16 sound); + uint32 GetFBITalkSfx(uint16 sound); + uint32 GetArmyTalkSfx(uint16 sound); + uint32 GetMedicTalkSfx(uint16 sound); + uint32 GetFiremanTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetBusinessMaleOldTalkSfx(uint16 sound); + uint32 GetBusinessMaleYoungTalkSfx(uint16 sound, uint32 model); + uint32 GetMafiaTalkSfx(uint16 sound); + uint32 GetTriadTalkSfx(uint16 sound); + uint32 GetDiabloTalkSfx(uint16 sound); + uint32 GetYakuzaTalkSfx(uint16 sound); + uint32 GetYardieTalkSfx(uint16 sound); + uint32 GetColumbianTalkSfx(uint16 sound); + uint32 GetHoodTalkSfx(uint16 sound); + uint32 GetBlackCriminalTalkSfx(uint16 sound); + uint32 GetWhiteCriminalTalkSfx(uint16 sound); + uint32 GetCasualMaleOldTalkSfx(uint16 sound); + uint32 GetCasualMaleYoungTalkSfx(uint16 sound); + uint32 GetBlackCasualFemaleTalkSfx(uint16 sound); + uint32 GetWhiteCasualFemaleTalkSfx(uint16 sound); + uint32 GetFemaleNo3TalkSfx(uint16 sound); + uint32 GetWhiteBusinessFemaleTalkSfx(uint16 sound, uint32 model); + uint32 GetBlackFatFemaleTalkSfx(uint16 sound); + uint32 GetWhiteFatMaleTalkSfx(uint16 sound); + uint32 GetBlackFatMaleTalkSfx(uint16 sound); + uint32 GetWhiteFatFemaleTalkSfx(uint16 sound); + uint32 GetBlackFemaleProstituteTalkSfx(uint16 sound); + uint32 GetWhiteFemaleProstituteTalkSfx(uint16 sound); + uint32 GetBlackProjectMaleTalkSfx(uint16 sound, uint32 model); + uint32 GetBlackProjectFemaleOldTalkSfx(uint16 sound); + uint32 GetBlackProjectFemaleYoungTalkSfx(uint16 sound); + uint32 GetChinatownMaleOldTalkSfx(uint16 sound); + uint32 GetChinatownMaleYoungTalkSfx(uint16 sound); + uint32 GetChinatownFemaleOldTalkSfx(uint16 sound); + uint32 GetChinatownFemaleYoungTalkSfx(uint16 sound); + uint32 GetLittleItalyMaleTalkSfx(uint16 sound); + uint32 GetLittleItalyFemaleOldTalkSfx(uint16 sound); + uint32 GetLittleItalyFemaleYoungTalkSfx(uint16 sound); + uint32 GetWhiteDockerMaleTalkSfx(uint16 sound); + uint32 GetBlackDockerMaleTalkSfx(uint16 sound); + uint32 GetScumMaleTalkSfx(uint16 sound); + uint32 GetScumFemaleTalkSfx(uint16 sound); + uint32 GetWhiteWorkerMaleTalkSfx(uint16 sound); + uint32 GetBlackWorkerMaleTalkSfx(uint16 sound); + uint32 GetBlackBusinessFemaleTalkSfx(uint16 sound); + uint32 GetSupermodelMaleTalkSfx(uint16 sound); + uint32 GetSupermodelFemaleTalkSfx(uint16 sound); + uint32 GetStewardMaleTalkSfx(uint16 sound); + uint32 GetStewardFemaleTalkSfx(uint16 sound); + uint32 GetFanMaleTalkSfx(uint16 sound, uint32 model); + uint32 GetFanFemaleTalkSfx(uint16 sound); + uint32 GetHospitalMaleTalkSfx(uint16 sound); + uint32 GetHospitalFemaleTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetWhiteConstructionWorkerTalkSfx(uint16 sound); + uint32 GetBlackConstructionWorkerTalkSfx(uint16 sound); + uint32 GetShopperFemaleTalkSfx(uint16 sound, uint32 model); + uint32 GetStudentMaleTalkSfx(uint16 sound); + uint32 GetStudentFemaleTalkSfx(uint16 sound); + + uint32 GetSpecialCharacterTalkSfx(uint32 modelIndex, uint16 sound); + uint32 GetEightBallTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetSalvatoreTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetMistyTalkSfx(uint16 sound); + uint32 GetOldJapTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetCatalinaTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetBomberTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetSecurityGuardTalkSfx(uint16 sound); + uint32 GetChunkyTalkSfx(uint16 sound); // inlined on PS2 + + uint32 GetAsianTaxiDriverTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetPimpTalkSfx(uint16 sound); + uint32 GetNormalMaleTalkSfx(uint16 sound); + uint32 GetGenericMaleTalkSfx(uint16 sound); + uint32 GetGenericFemaleTalkSfx(uint16 sound); + + // particles + void ProcessExplosions(int32 id); + void ProcessFires(int32 id); + void ProcessWaterCannon(int32 id); + + // script objects + void ProcessScriptObject(int32 id); // inlined on PS2 + void ProcessOneShotScriptObject(uint8 sound); + void ProcessLoopingScriptObject(uint8 sound); + void ProcessPornCinema(uint8 sound); + void ProcessWorkShopScriptObject(uint8 sound); + void ProcessSawMillScriptObject(uint8 sound); + void ProcessLaunderetteScriptObject(uint8 sound); + void ProcessShopScriptObject(uint8 sound); + void ProcessAirportScriptObject(uint8 sound); + void ProcessCinemaScriptObject(uint8 sound); + void ProcessDocksScriptObject(uint8 sound); + void ProcessHomeScriptObject(uint8 sound); + void ProcessPoliceCellBeatingScriptObject(uint8 sound); + + // misc + void ProcessWeather(int32 id); + void ProcessFrontEnd(); + void ProcessCrane(); + void ProcessProjectiles(); + void ProcessGarages(); + void ProcessFireHydrant(); + + // bridge + void ProcessBridge(); // inlined on PS2 + void ProcessBridgeWarning(); + void ProcessBridgeMotor(); + void ProcessBridgeOneShots(); + + // mission audio + bool8 MissionScriptAudioUsesPoliceChannel(uint32 soundMission); + void PreloadMissionAudio(Const char *name); + uint8 GetMissionAudioLoadingStatus(); + void SetMissionAudioLocation(float x, float y, float z); + void PlayLoadedMissionAudio(); + bool8 IsMissionAudioSampleFinished(); + bool8 IsMissionAudioSamplePlaying() { return m_nMissionAudioPlayStatus == PLAY_STATUS_PLAYING; } + bool8 ShouldDuckMissionAudio() { return IsMissionAudioSamplePlaying(); } + void ClearMissionAudio(); + void ProcessMissionAudio(); + + // police radio + void InitialisePoliceRadioZones(); + void InitialisePoliceRadio(); + void ResetPoliceRadio(); + void SetMissionScriptPoliceAudio(uint32 sfx); + int8 GetMissionScriptPoliceAudioPlayingStatus(); + void DoPoliceRadioCrackle(); + void ServicePoliceRadio(); + void ServicePoliceRadioChannel(uint8 wantedLevel); + bool8 SetupCrimeReport(); + void SetupSuspectLastSeenReport(); + void ReportCrime(eCrimeType crime, const CVector &pos); + void PlaySuspectLastSeen(float x, float y, float z); + void AgeCrimes(); // inlined on PS2 + + // collision stuff + void ReportCollision(CEntity *entity1, CEntity *entity2, uint8 surface1, uint8 surface2, float collisionPower, float intensity2); + void ServiceCollisions(); + void SetUpOneShotCollisionSound(const cAudioCollision &col); + void SetUpLoopingCollisionSound(const cAudioCollision &col, uint8 counter); + uint32 SetLoopingCollisionRequestedSfxFreqAndGetVol(const cAudioCollision &audioCollision); + float GetCollisionOneShotRatio(uint32 a, float b); + float GetCollisionLoopingRatio(uint32 a, uint32 b, float c); // not used + float GetCollisionRatio(float a, float b, float c, float d); // inlined on PS2 +}; + +/* + Manual loop points are not on PS2 so let's have these macros to avoid massive ifndefs. + Setting these manually was pointless anyway since they never change from sdt values. + What were they thinking? +*/ +#ifndef GTA_PS2 +#define RESET_LOOP_OFFSETS \ + m_sQueueSample.m_nLoopStart = 0; \ + m_sQueueSample.m_nLoopEnd = -1; +#define SET_LOOP_OFFSETS(sample) \ + m_sQueueSample.m_nLoopStart = SampleManager.GetSampleLoopStartOffset(sample); \ + m_sQueueSample.m_nLoopEnd = SampleManager.GetSampleLoopEndOffset(sample); +#else +#define RESET_LOOP_OFFSETS +#define SET_LOOP_OFFSETS(sample) +#endif +#ifdef EXTERNAL_3D_SOUND +#define SET_EMITTING_VOLUME(vol) m_sQueueSample.m_nEmittingVolume = vol +#else +#define SET_EMITTING_VOLUME(vol) +#endif +#ifdef AUDIO_REFLECTIONS +#define SET_SOUND_REFLECTION(b) m_sQueueSample.m_bReflections = b +#else +#define SET_SOUND_REFLECTION(b) +#endif + +#if defined(AUDIO_MSS) && !defined(PS2_AUDIO_CHANNELS) +static_assert(sizeof(cAudioManager) == 19220, "cAudioManager: error"); +#endif + +extern cAudioManager AudioManager; + +enum +{ + PED_COMMENT_VOLUME = 127, + PED_COMMENT_VOLUME_BEHIND_WALL = 31, + COLLISION_MAX_DIST = 60, +}; diff --git a/src/audio/AudioSamples.h b/src/audio/AudioSamples.h new file mode 100644 index 0000000..1e7ba68 --- /dev/null +++ b/src/audio/AudioSamples.h @@ -0,0 +1,3273 @@ +#pragma once + +#include "common.h" + +enum eSfxSample +{ + SFX_CAR_HORN_JEEP = 0, + SFX_CAR_HORN_BMW328, + SFX_CAR_HORN_BUS, + SFX_CAR_HORN_BUS2, + SFX_CAR_HORN_56CHEV, + SFX_CAR_HORN_PICKUP, + SFX_CAR_HORN_PORSCHE, + SFX_CAR_HORN_TRUCK, + SFX_OLD_CAR_DOOR_OPEN, + SFX_OLD_CAR_DOOR_CLOSE, + SFX_NEW_CAR_DOOR_OPEN, + SFX_NEW_CAR_DOOR_CLOSE, + SFX_TRUCK_DOOR_OPEN, + SFX_TRUCK_DOOR_CLOSE, + SFX_REMOTE_CONTROLLED_CAR, + SFX_REVERSE_GEAR, + SFX_REVERSE_GEAR_2, + SFX_CAR_STARTER, + SFX_ROAD_NOISE, + SFX_SKID, + SFX_GRAVEL_SKID, + SFX_POLICE_SIREN_SLOW, + SFX_SIREN_FAST, + SFX_AMBULANCE_SIREN_SLOW, + SFX_REVERSE_WARNING, + SFX_ICE_CREAM_TUNE, + SFX_CAR_ALARM_1, + SFX_AIR_BRAKES, + SFX_SQUEAKY_BRAKES, + SFX_TYRE_BUMP, + SFX_TRAIN_FAR, + SFX_TRAIN_NEAR, + SFX_FOOTSTEP_CONCRETE_1, + SFX_FOOTSTEP_CONCRETE_2, + SFX_FOOTSTEP_CONCRETE_3, + SFX_FOOTSTEP_CONCRETE_4, + SFX_FOOTSTEP_CONCRETE_5, + SFX_FOOTSTEP_GRASS_1, + SFX_FOOTSTEP_GRASS_2, + SFX_FOOTSTEP_GRASS_3, + SFX_FOOTSTEP_GRASS_4, + SFX_FOOTSTEP_GRASS_5, + SFX_FOOTSTEP_GRAVEL_1, + SFX_FOOTSTEP_GRAVEL_2, + SFX_FOOTSTEP_GRAVEL_3, + SFX_FOOTSTEP_GRAVEL_4, + SFX_FOOTSTEP_GRAVEL_5, + SFX_FOOTSTEP_WOOD_1, + SFX_FOOTSTEP_WOOD_2, + SFX_FOOTSTEP_WOOD_3, + SFX_FOOTSTEP_WOOD_4, + SFX_FOOTSTEP_WOOD_5, + SFX_FOOTSTEP_METAL_1, + SFX_FOOTSTEP_METAL_2, + SFX_FOOTSTEP_METAL_3, + SFX_FOOTSTEP_METAL_4, + SFX_FOOTSTEP_METAL_5, + SFX_FOOTSTEP_WATER_1, + SFX_FOOTSTEP_WATER_2, + SFX_FOOTSTEP_WATER_3, + SFX_FOOTSTEP_WATER_4, + SFX_FOOTSTEP_SAND_1, + SFX_FOOTSTEP_SAND_2, + SFX_FOOTSTEP_SAND_3, + SFX_FOOTSTEP_SAND_4, + SFX_EXPLOSION_2, + SFX_EXPLOSION_3, + SFX_COLT45_LEFT, + SFX_COLT45_RIGHT, + SFX_M16_LEFT, + SFX_M16_RIGHT, + SFX_AK47_LEFT, + SFX_AK47_RIGHT, + SFX_UZI_LEFT, + SFX_UZI_RIGHT, + SFX_UZI_END_LEFT, + SFX_UZI_END_RIGHT, + SFX_SNIPER_LEFT, + SFX_SNIPER_RIGHT, + SFX_ROCKET_LEFT, + SFX_ROCKET_RIGHT, + SFX_ROCKET_FLY, + SFX_FLAMETHROWER_LEFT, + SFX_FLAMETHROWER_RIGHT, + SFX_FLAMETHROWER_START_LEFT, + SFX_FLAMETHROWER_START_RIGHT, + SFX_SHOTGUN_LEFT, + SFX_SHOTGUN_RIGHT, + SFX_PISTOL_RELOAD, + SFX_AK47_RELOAD, + SFX_M16_RELOAD, + SFX_ROCKET_RELOAD, + SFX_RIFLE_RELOAD, + SFX_COL_TARMAC_1, + SFX_COL_TARMAC_2, + SFX_COL_TARMAC_3, + SFX_COL_TARMAC_4, + SFX_COL_TARMAC_5, + SFX_COL_GRASS_1, + SFX_COL_GRAVEL_1, + SFX_COL_MUD_1, + SFX_COL_GARAGE_DOOR_1, + SFX_COL_CAR_PANEL_1, + SFX_COL_CAR_PANEL_2, + SFX_COL_CAR_PANEL_3, + SFX_COL_CAR_PANEL_4, + SFX_COL_CAR_PANEL_5, + SFX_COL_CAR_PANEL_6, + SFX_COL_THICK_METAL_PLATE_1, + SFX_COL_SCAFFOLD_POLE_1, + SFX_COL_LAMP_POST_1, + SFX_COL_HYDRANT_1, + SFX_COL_METAL_CHAIN_FENCE_1, + SFX_COL_METAL_CHAIN_FENCE_2, + SFX_COL_METAL_CHAIN_FENCE_3, + SFX_COL_METAL_CHAIN_FENCE_4, + SFX_COL_PED_1, + SFX_COL_PED_2, + SFX_COL_PED_3, + SFX_COL_PED_4, + SFX_COL_PED_5, + SFX_COL_SAND_1, + SFX_COL_WOOD_CRATES_1, + SFX_COL_WOOD_CRATES_2, + SFX_COL_WOOD_CRATES_3, + SFX_COL_WOOD_CRATES_4, + SFX_COL_WOOD_BENCH_1, + SFX_COL_WOOD_BENCH_2, + SFX_COL_WOOD_BENCH_3, + SFX_COL_WOOD_BENCH_4, + SFX_COL_WOOD_SOLID_1, + SFX_COL_VEG_1, + SFX_COL_VEG_2, + SFX_COL_VEG_3, + SFX_COL_VEG_4, + SFX_COL_VEG_5, + SFX_COL_CONTAINER_1, + SFX_COL_NEWS_VENDOR_1, + SFX_COL_NEWS_VENDOR_2, + SFX_COL_NEWS_VENDOR_3, + SFX_COL_CAR_1, + SFX_COL_CAR_2, + SFX_COL_CAR_3, + SFX_COL_CAR_4, + SFX_COL_CAR_5, + SFX_COL_CARDBOARD_1, + SFX_COL_CARDBOARD_2, + SFX_COL_GATE, + SFX_SCRAPE_CAR_1, + SFX_CRATE_SMASH, + SFX_GLASS_CRACK, + SFX_GLASS_SMASH, + SFX_GLASS_SHARD_1, + SFX_GLASS_SHARD_2, + SFX_GLASS_SHARD_3, + SFX_GLASS_SHARD_4, + SFX_PED_ON_FIRE, + SFX_CAR_ON_FIRE, + SFX_RAIN, + SFX_PICKUP_1_LEFT, + SFX_PICKUP_1_RIGHT, + SFX_PICKUP_2_LEFT, + SFX_PICKUP_2_RIGHT, + SFX_PICKUP_3_LEFT, + SFX_PICKUP_3_RIGHT, + SFX_PICKUP_ERROR_LEFT, + SFX_PICKUP_ERROR_RIGHT, + SFX_BULLET_SHELL_HIT_GROUND_1, + SFX_BULLET_SHELL_HIT_GROUND_2, + SFX_BULLET_PED, + SFX_BULLET_CAR_1, + SFX_BULLET_CAR_2, + SFX_BULLET_CAR_3, + SFX_BULLET_CAR_4, + SFX_BULLET_CAR_5, + SFX_BULLET_CAR_6, + SFX_BULLET_WALL_1, + SFX_BULLET_WALL_2, + SFX_BULLET_WALL_3, + SFX_BAT_HIT_LEFT, + SFX_BAT_HIT_RIGHT, + SFX_FIGHT_1, + SFX_FIGHT_2, + SFX_FIGHT_4, + SFX_FIGHT_5, + SFX_GARAGE_DOOR_LOOP, + SFX_COUNTDOWN, + SFX_ARM_BOMB, + SFX_POLICE_RADIO_CRACKLE, + SFX_WEVE_GOT, + SFX_THERES, + SFX_RESPOND_TO, + SFX_A_10_1, + SFX_A_10_2, + SFX_CRIME_1, + SFX_CRIME_2, + SFX_CRIME_3, + SFX_CRIME_4, + SFX_CRIME_5, + SFX_CRIME_6, + SFX_CRIME_7, + SFX_CRIME_8, + SFX_CRIME_9, + SFX_CRIME_10, + SFX_CRIME_11, + SFX_CRIME_12, + SFX_IN, + SFX_NORTH, + SFX_EAST, + SFX_SOUTH, + SFX_WEST, + SFX_CENTRAL, + SFX_POLICE_RADIO_MESSAGE_NOISE_1, + SFX_POLICE_RADIO_MESSAGE_NOISE_2, + SFX_POLICE_RADIO_MESSAGE_NOISE_3, + SFX_POLICE_RADIO_LIBERTY_CITY, + SFX_POLICE_RADIO_PORTLAND, + SFX_POLICE_RADIO_STAUNTON_ISLAND, + SFX_POLICE_RADIO_SHORESIDE_VALE, + SFX_POLICE_RADIO_ROCKFORD, + SFX_POLICE_RADIO_FORT_STAUNTON, + SFX_POLICE_RADIO_ASPATRIA, + SFX_POLICE_RADIO_TORRINGTON, + SFX_POLICE_RADIO_BEDFORD_POINT, + SFX_POLICE_RADIO_NEWPORT, + SFX_POLICE_RADIO_BELLEVILLE_PARK, + SFX_POLICE_RADIO_LIBERTY_CAMPUS, + SFX_POLICE_RADIO_COCHRANE_DAM, + SFX_POLICE_RADIO_PIKE_CREEK, + SFX_POLICE_RADIO_CEDAR_GROVE, + SFX_POLICE_RADIO_WICHITA_GARDENS, + SFX_POLICE_RADIO_FRANCIS_INTERNATIONAL_AIRPORT, + SFX_POLICE_RADIO_CALLAHAN_POINT, + SFX_POLICE_RADIO_ATLANTIC_QUAYS, + SFX_POLICE_RADIO_PORTLAND_HARBOUR, + SFX_POLICE_RADIO_TRENTON, + SFX_POLICE_RADIO_CHINATOWN, + SFX_POLICE_RADIO_RED_LIGHT_DISTRICT, + SFX_POLICE_RADIO_HEPBURN_HEIGHTS, + SFX_POLICE_RADIO_SAINT_MARKS, + SFX_POLICE_RADIO_HARWOOD, + SFX_POLICE_RADIO_PORTLAND_BEACH, + SFX_POLICE_RADIO_PORTLAND_STRAIGHTS, // shouldn't be used anymore + SFX_POLICE_RADIO_SUSPECT, + SFX_POLICE_RADIO_LAST_SEEN, + SFX_POLICE_RADIO_ON_FOOT, + SFX_POLICE_RADIO_IN_A, + SFX_POLICE_RADIO_IN_AN, + SFX_POLICE_RADIO_BLACK, + SFX_POLICE_RADIO_WHITE, + SFX_POLICE_RADIO_BLUE, + SFX_POLICE_RADIO_RED, + SFX_POLICE_RADIO_PURPLE, + SFX_POLICE_RADIO_YELLOW, + SFX_POLICE_RADIO_GREY, + SFX_POLICE_RADIO_ORANGE, + SFX_POLICE_RADIO_GREEN, + SFX_POLICE_RADIO_SILVER, + SFX_POLICE_RADIO_DARK, + SFX_POLICE_RADIO_LIGHT, + SFX_POLICE_RADIO_BRIGHT, + SFX_POLICE_RADIO_AMBULANCE, + SFX_POLICE_RADIO_VAN, + SFX_POLICE_RADIO_TRUCK, + SFX_POLICE_RADIO_SALOON, + SFX_POLICE_RADIO_SPORTS_CAR, + SFX_POLICE_RADIO_BUGGY, + SFX_POLICE_RADIO_TAXI, + SFX_POLICE_RADIO_CRUISER, + SFX_POLICE_RADIO_BUS, + SFX_POLICE_RADIO_2_DOOR, + SFX_POLICE_RADIO_FIRE_TRUCK, + SFX_POLICE_RADIO_BOAT, + SFX_POLICE_RADIO_PICKUP, + SFX_POLICE_RADIO_ICE_CREAM_VAN, + SFX_POLICE_RADIO_LIMO, + SFX_POLICE_RADIO_POLICE_CAR, + SFX_POLICE_RADIO_CONVERTIBLE, + SFX_POLICE_RADIO_SUBWAY_CAR, + SFX_POLICE_RADIO_TANK, + SFX_HELI_1, + SFX_HELI_2, + SFX_HELI_3, + SFX_PHONE_RING, + SFX_CAR_REV_1, + SFX_CAR_REV_2, + SFX_CAR_REV_3, + SFX_CAR_REV_4, + SFX_CAR_REV_5, + SFX_CAR_REV_6, + SFX_CAR_REV_7, + SFX_CAR_REV_8, + SFX_CAR_REV_9, + SFX_CAR_REV_10, + SFX_CAR_IDLE_1, + SFX_CAR_IDLE_2, + SFX_CAR_IDLE_3, + SFX_CAR_IDLE_4, + SFX_CAR_IDLE_5, + SFX_CAR_IDLE_6, + SFX_CAR_IDLE_7, + SFX_CAR_IDLE_8, + SFX_CAR_IDLE_9, + SFX_CAR_IDLE_10, + SFX_JUMBO_DIST_FLY, + SFX_JUMBO_TAXI, + SFX_JUMBO_WHINE, + SFX_JUMBO_ENGINE, + SFX_JUMBO_RUMBLE, + SFX_JUMBO_LAND_WHEELS, + SFX_POLICE_BOAT_IDLE, + SFX_POLICE_BOAT_ACCEL, + SFX_POLICE_BOAT_THUMB_OFF, + SFX_BOAT_WATER_LOOP, + SFX_BOAT_SPLASH_1, + SFX_BOAT_SPLASH_2, + SFX_FISHING_BOAT_IDLE, + SFX_CESNA_IDLE, + SFX_CESNA_REV, + SFX_CAR_RAIN_1, + SFX_CAR_RAIN_2, + SFX_CAR_RAIN_3, + SFX_CAR_RAIN_4, + SFX_SPLASH_1, + SFX_PED_CRUNCH_1, + SFX_PED_CRUNCH_2, + SFX_HEADPHONES, + SFX_WOODEN_BOX_SMASH, + SFX_CARDBOARD_BOX_SMASH, + SFX_ERROR_FIRE_ROCKET_LAUNCHER, + SFX_ERROR_FIRE_RIFLE, + SFX_TANK_TURRET, + SFX_CRANE_MAGNET, + SFX_BODY_LAND_AND_FALL, + SFX_BODY_LAND, + SFX_BOMB_BEEP, + SFX_TIMER_BEEP, + SFX_PART_MISSION_COMPLETE, + SFX_START_BUTTON_LEFT, + SFX_START_BUTTON_RIGHT, + SFX_SUSPENSION_FAST_MOVE, + SFX_SUSPENSION_SLOW_MOVE_LOOP, + SFX_SHAG_SUSPENSION, + SFX_RADIO_CLICK, + SFX_INFO, + + // bank 1 + SFX_CAR_ACCEL_1, + SFX_CAR_AFTER_ACCEL_1, + SFX_CAR_FINGER_OFF_ACCEL_1, + + // bank 2 + SFX_CAR_ACCEL_2, + SFX_CAR_AFTER_ACCEL_2, + SFX_CAR_FINGER_OFF_ACCEL_2, + + // bank 3 + SFX_CAR_ACCEL_3, + SFX_CAR_AFTER_ACCEL_3, + SFX_CAR_FINGER_OFF_ACCEL_3, + + // bank 4 + SFX_CAR_ACCEL_4, + SFX_CAR_AFTER_ACCEL_4, + SFX_CAR_FINGER_OFF_ACCEL_4, + + // bank 5 + SFX_CAR_ACCEL_5, + SFX_CAR_AFTER_ACCEL_5, + SFX_CAR_FINGER_OFF_ACCEL_5, + + // bank 6 + SFX_CAR_ACCEL_6, + SFX_CAR_AFTER_ACCEL_6, + SFX_CAR_FINGER_OFF_ACCEL_6, + + // bank 7 + SFX_CAR_ACCEL_7, + SFX_CAR_AFTER_ACCEL_7, + SFX_CAR_FINGER_OFF_ACCEL_7, + + // bank 8 + SFX_CAR_ACCEL_8, + SFX_CAR_AFTER_ACCEL_8, + SFX_CAR_FINGER_OFF_ACCEL_8, + + // bank 9 + SFX_CAR_ACCEL_9, + SFX_CAR_AFTER_ACCEL_9, + SFX_CAR_FINGER_OFF_ACCEL_9, + + // bank 10 + SFX_PAGE_CHANGE_AND_BACK_LEFT, + SFX_PAGE_CHANGE_AND_BACK_RIGHT, + SFX_HIGHLIGHT_LEFT, + SFX_HIGHLIGHT_RIGHT, + SFX_SELECT_LEFT, + SFX_SELECT_RIGHT, + SFX_SUB_MENU_BACK_LEFT, + SFX_SUB_MENU_BACK_RIGHT, + SFX_STEREO_LEFT, + SFX_STEREO_RIGHT, + SFX_MONO, + SFX_NOISE_BURST_1, + SFX_NOISE_BURST_2, + SFX_NOISE_BURST_3, + SFX_ERROR_LEFT, + SFX_ERROR_RIGHT, + + // bank 11 + SFX_TRAIN_STATION_AMBIENCE_LOOP, + SFX_TRAIN_STATION_ANNOUNCE, + + // bank 12 + SFX_CLUB_1, + + // bank 13 + SFX_CLUB_2, + + // bank 14 + SFX_CLUB_3, + + // bank 15 + SFX_CLUB_4, + + // bank 16 + SFX_CLUB_5, + + // bank 17 + SFX_CLUB_6, + + // bank 18 + SFX_CLUB_7, + + // bank 19 + SFX_CLUB_8, + + // bank 20 + SFX_CLUB_9, + + // bank 21 + SFX_CLUB_10, + + // bank 22 + SFX_CLUB_11, + + // bank 23 + SFX_CLUB_12, + + // bank 24 + SFX_CLUB_RAGGA, + + // bank 25 + SFX_STRIP_CLUB_1, + + // bank 26 + SFX_STRIP_CLUB_2, + + // bank 27 + SFX_WORKSHOP_1, + + // bank 28 + SFX_PIANO_BAR_1, + + // bank 29 + SFX_SAWMILL_LOOP, + SFX_SAWMILL_CUT_WOOD, + + // bank 30 + SFX_DOG_FOOD_FACTORY, + + // bank 31 + SFX_LAUNDERETTE_LOOP, + SFX_LAUNDERETTE_SONG_LOOP, + + // bank 32 + SFX_RESTAURANT_CHINATOWN, + + // bank 33 + SFX_RESTAURANT_ITALY, + + // bank 34 + SFX_RESTAURANT_GENERIC_1, + + // bank 35 + SFX_RESTAURANT_GENERIC_2, + + // bank 36 + SFX_AIRPORT_ANNOUNCEMENT_1, + SFX_AIRPORT_ANNOUNCEMENT_2, + SFX_AIRPORT_ANNOUNCEMENT_3, + SFX_AIRPORT_ANNOUNCEMENT_4, + + // bank 37 + SFX_SHOP_LOOP, + SFX_SHOP_TILL_1, + SFX_SHOP_TILL_2, + + // bank 38 + SFX_CINEMA_BASS_1, + SFX_CINEMA_BASS_2, + SFX_CINEMA_BASS_3, + + // bank 39 + SFX_DOCKS_FOGHORN, + + // bank 40 + SFX_HOME_1, + SFX_HOME_2, + SFX_HOME_3, + SFX_HOME_4, + SFX_HOME_5, + + // bank 41 + SFX_PORN_1_LOOP, + SFX_PORN_1_GROAN_1, + SFX_PORN_1_GROAN_2, + + // bank 42 + SFX_PORN_2_LOOP, + SFX_PORN_2_GROAN_1, + SFX_PORN_2_GROAN_2, + + // bank 43 + SFX_PORN_3_LOOP, + SFX_PORN_3_GROAN_1, + SFX_PORN_3_GROAN_2, + + // bank 44 + SFX_POLICE_BALL_1, + + // bank 45 + SFX_BANK_ALARM_1, + + // bank 46 + SFX_RAVE_INDUSTRIAL, + + // bank 47 + SFX_RAVE_COMMERCIAL, + + // bank 48 + SFX_RAVE_SUBURBAN, + + // bank 49 + SFX_RAVE_COMMERCIAL_2, + + // unused banks 50-58 + SFX_CLUB_1_1, + SFX_CLUB_1_2, + SFX_CLUB_1_3, + SFX_CLUB_1_4, + SFX_CLUB_1_5, + SFX_CLUB_1_6, + SFX_CLUB_1_7, + SFX_CLUB_1_8, + SFX_CLUB_1_9, + + // bank 59 + SFX_EXPLOSION_1, + SFX_BRIDGE_OPEN_WARNING, +#ifndef GTA_PS2 + SFX_PAGER, // used to be ped comment on PS2 +#endif + + SFX_COP_VOICE_1_ARREST_1, + SFX_COP_VOICE_1_ARREST_2, + SFX_COP_VOICE_1_ARREST_3, + SFX_COP_VOICE_1_ARREST_4, + SFX_COP_VOICE_1_ARREST_5, + SFX_COP_VOICE_1_ARREST_6, + SFX_COP_VOICE_1_CHASE_1, + SFX_COP_VOICE_1_CHASE_2, + SFX_COP_VOICE_1_CHASE_3, + SFX_COP_VOICE_1_CHASE_4, + SFX_COP_VOICE_1_CHASE_5, + SFX_COP_VOICE_1_CHASE_6, + SFX_COP_VOICE_1_CHASE_7, + SFX_COP_VOICE_2_ARREST_1, + SFX_COP_VOICE_2_ARREST_2, + SFX_COP_VOICE_2_ARREST_3, + SFX_COP_VOICE_2_ARREST_4, + SFX_COP_VOICE_2_ARREST_5, + SFX_COP_VOICE_2_ARREST_6, + SFX_COP_VOICE_2_CHASE_1, + SFX_COP_VOICE_2_CHASE_2, + SFX_COP_VOICE_2_CHASE_3, + SFX_COP_VOICE_2_CHASE_4, + SFX_COP_VOICE_2_CHASE_5, + SFX_COP_VOICE_2_CHASE_6, + SFX_COP_VOICE_2_CHASE_7, + SFX_COP_VOICE_3_ARREST_1, + SFX_COP_VOICE_3_ARREST_2, + SFX_COP_VOICE_3_ARREST_3, + SFX_COP_VOICE_3_ARREST_4, + SFX_COP_VOICE_3_ARREST_5, + SFX_COP_VOICE_3_ARREST_6, + SFX_COP_VOICE_3_CHASE_1, + SFX_COP_VOICE_3_CHASE_2, + SFX_COP_VOICE_3_CHASE_3, + SFX_COP_VOICE_3_CHASE_4, + SFX_COP_VOICE_3_CHASE_5, + SFX_COP_VOICE_3_CHASE_6, + SFX_COP_VOICE_3_CHASE_7, + SFX_COP_VOICE_4_ARREST_1, + SFX_COP_VOICE_4_ARREST_2, + SFX_COP_VOICE_4_ARREST_3, + SFX_COP_VOICE_4_ARREST_4, + SFX_COP_VOICE_4_ARREST_5, + SFX_COP_VOICE_4_ARREST_6, + SFX_COP_VOICE_4_CHASE_1, + SFX_COP_VOICE_4_CHASE_2, + SFX_COP_VOICE_4_CHASE_3, + SFX_COP_VOICE_4_CHASE_4, + SFX_COP_VOICE_4_CHASE_5, + SFX_COP_VOICE_4_CHASE_6, + SFX_COP_VOICE_4_CHASE_7, + SFX_COP_VOICE_5_ARREST_1, + SFX_COP_VOICE_5_ARREST_2, + SFX_COP_VOICE_5_ARREST_3, + SFX_COP_VOICE_5_ARREST_4, + SFX_COP_VOICE_5_ARREST_5, + SFX_COP_VOICE_5_ARREST_6, + SFX_COP_VOICE_5_CHASE_1, + SFX_COP_VOICE_5_CHASE_2, + SFX_COP_VOICE_5_CHASE_3, + SFX_COP_VOICE_5_CHASE_4, + SFX_COP_VOICE_5_CHASE_5, + SFX_COP_VOICE_5_CHASE_6, + SFX_COP_VOICE_5_CHASE_7, + SFX_SWAT_VOICE_1_CHASE_1, + SFX_SWAT_VOICE_1_CHASE_2, + SFX_SWAT_VOICE_1_CHASE_3, + SFX_SWAT_VOICE_1_CHASE_4, + SFX_SWAT_VOICE_1_CHASE_5, + SFX_SWAT_VOICE_1_CHASE_6, + SFX_SWAT_VOICE_2_CHASE_1, + SFX_SWAT_VOICE_2_CHASE_2, + SFX_SWAT_VOICE_2_CHASE_3, + SFX_SWAT_VOICE_2_CHASE_4, + SFX_SWAT_VOICE_2_CHASE_5, + SFX_SWAT_VOICE_2_CHASE_6, + SFX_SWAT_VOICE_3_CHASE_1, + SFX_SWAT_VOICE_3_CHASE_2, + SFX_SWAT_VOICE_3_CHASE_3, + SFX_SWAT_VOICE_3_CHASE_4, + SFX_SWAT_VOICE_3_CHASE_5, + SFX_SWAT_VOICE_3_CHASE_6, + SFX_SWAT_VOICE_4_CHASE_1, + SFX_SWAT_VOICE_4_CHASE_2, + SFX_SWAT_VOICE_4_CHASE_3, + SFX_SWAT_VOICE_4_CHASE_4, + SFX_SWAT_VOICE_4_CHASE_5, + SFX_SWAT_VOICE_4_CHASE_6, + SFX_FBI_VOICE_1_CHASE_1, + SFX_FBI_VOICE_1_CHASE_2, + SFX_FBI_VOICE_1_CHASE_3, + SFX_FBI_VOICE_1_CHASE_4, + SFX_FBI_VOICE_1_CHASE_5, + SFX_FBI_VOICE_1_CHASE_6, + SFX_FBI_VOICE_2_CHASE_1, + SFX_FBI_VOICE_2_CHASE_2, + SFX_FBI_VOICE_2_CHASE_3, + SFX_FBI_VOICE_2_CHASE_4, + SFX_FBI_VOICE_2_CHASE_5, + SFX_FBI_VOICE_2_CHASE_6, + SFX_FBI_VOICE_3_CHASE_1, + SFX_FBI_VOICE_3_CHASE_2, + SFX_FBI_VOICE_3_CHASE_3, + SFX_FBI_VOICE_3_CHASE_4, + SFX_FBI_VOICE_3_CHASE_5, + SFX_FBI_VOICE_3_CHASE_6, + SFX_POLICE_HELI_1, + SFX_POLICE_HELI_2, + SFX_POLICE_HELI_3, + SFX_POLICE_HELI_4, + SFX_POLICE_HELI_5, + SFX_POLICE_HELI_6, + SFX_POLICE_HELI_7, + SFX_POLICE_HELI_8, + SFX_POLICE_HELI_9, + SFX_POLICE_HELI_10, + SFX_POLICE_HELI_11, + SFX_POLICE_HELI_12, + SFX_POLICE_HELI_13, + SFX_POLICE_HELI_14, + SFX_POLICE_HELI_15, + SFX_POLICE_HELI_16, + SFX_POLICE_HELI_17, + SFX_POLICE_HELI_18, + SFX_POLICE_HELI_19, + SFX_POLICE_HELI_20, + SFX_POLICE_HELI_21, + SFX_POLICE_HELI_22, + SFX_POLICE_HELI_23, + SFX_POLICE_HELI_24, + SFX_POLICE_HELI_25, + SFX_POLICE_HELI_26, + SFX_POLICE_HELI_27, + SFX_POLICE_HELI_28, + SFX_POLICE_HELI_29, +#ifdef GTA_PS2 + SFX_MISSION_CAT1, +#endif + SFX_CHUNKY_DEATH, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_DOCKER_VOICE_1_CHAT_1, + SFX_BLACK_DOCKER_VOICE_1_CHAT_2, + SFX_BLACK_DOCKER_VOICE_1_CHAT_3, + SFX_BLACK_DOCKER_VOICE_1_CHAT_4, + SFX_BLACK_DOCKER_VOICE_1_CHAT_5, + SFX_BLACK_DOCKER_VOICE_1_DODGE_1, + SFX_BLACK_DOCKER_VOICE_1_DODGE_2, + SFX_BLACK_DOCKER_VOICE_1_DODGE_3, + SFX_BLACK_DOCKER_VOICE_1_DODGE_4, + SFX_BLACK_DOCKER_VOICE_1_DODGE_5, + SFX_BLACK_DOCKER_VOICE_1_EYING_1, + SFX_BLACK_DOCKER_VOICE_1_EYING_2, + SFX_BLACK_DOCKER_VOICE_1_EYING_3, + SFX_BLACK_DOCKER_VOICE_1_FIGHT_1, + SFX_BLACK_DOCKER_VOICE_1_FIGHT_2, + SFX_BLACK_DOCKER_VOICE_1_FIGHT_3, + SFX_BLACK_DOCKER_VOICE_1_FIGHT_4, + SFX_BLACK_DOCKER_VOICE_1_FIGHT_5, + SFX_BLACK_DOCKER_VOICE_1_GUN_PANIC_1, + SFX_BLACK_DOCKER_VOICE_1_GUN_PANIC_2, + SFX_BLACK_DOCKER_VOICE_1_GUN_PANIC_3, + SFX_ARMY_VOICE_1_CHASE_1, + SFX_ARMY_VOICE_1_CHASE_2, + SFX_ARMY_VOICE_1_CHASE_3, + SFX_ARMY_VOICE_1_CHASE_4, + SFX_ARMY_VOICE_1_CHASE_5, + SFX_ARMY_VOICE_1_CHASE_6, + SFX_ARMY_VOICE_1_CHASE_7, + SFX_ARMY_VOICE_1_CHASE_8, + SFX_ARMY_VOICE_1_CHASE_9, + SFX_ARMY_VOICE_1_CHASE_10, + SFX_ARMY_VOICE_1_CHASE_11, + SFX_ARMY_VOICE_1_CHASE_12, + SFX_ARMY_VOICE_1_CHASE_13, + SFX_ARMY_VOICE_1_CHASE_14, + SFX_ARMY_VOICE_1_CHASE_15, + SFX_ARMY_VOICE_2_CHASE_1, + SFX_ARMY_VOICE_2_CHASE_2, + SFX_ARMY_VOICE_2_CHASE_3, + SFX_ARMY_VOICE_2_CHASE_4, + SFX_ARMY_VOICE_2_CHASE_5, + SFX_ARMY_VOICE_2_CHASE_6, + SFX_ARMY_VOICE_2_CHASE_7, + SFX_ARMY_VOICE_2_CHASE_8, + SFX_ARMY_VOICE_2_CHASE_9, + SFX_ARMY_VOICE_2_CHASE_10, + SFX_ARMY_VOICE_2_CHASE_11, + SFX_ARMY_VOICE_2_CHASE_12, + SFX_ARMY_VOICE_2_CHASE_13, + SFX_ARMY_VOICE_2_CHASE_14, + SFX_ARMY_VOICE_2_CHASE_15, +#ifdef GTA_PS2 + SFX_PAGER, +#endif + SFX_CLAUDE_LOW_DAMAGE_GRUNT_1, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_2, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_3, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_4, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_5, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_6, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_7, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_8, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_9, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_10, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_1, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_2, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_3, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_4, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_5, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_6, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_7, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_8, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_9, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_10, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_11, + SFX_CLAUDE_HIT_GROUND_GRUNT_1, + SFX_CLAUDE_HIT_GROUND_GRUNT_2, + SFX_CLAUDE_HIT_GROUND_GRUNT_3, + SFX_CLAUDE_HIT_GROUND_GRUNT_4, + SFX_CLAUDE_HIT_GROUND_GRUNT_5, + SFX_CLAUDE_HIT_GROUND_GRUNT_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_7, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_3, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_4, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_5, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_7, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_8, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_9, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_10, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_3, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_4, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_5, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_7, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_8, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_9, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_10, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_3, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_4, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_5, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_MUGGED_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_MUGGED_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_3, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_4, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_5, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_SHOCKED_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_SHOCKED_2, + SFX_CHUNKY_RUN_1, + SFX_CHUNKY_RUN_2, + SFX_CHUNKY_RUN_3, + SFX_CHUNKY_RUN_4, + SFX_CHUNKY_RUN_5, + SFX_PIMP_DRIVER_ABUSE_1, + SFX_PIMP_DRIVER_ABUSE_2, + SFX_PIMP_DRIVER_ABUSE_3, + SFX_PIMP_DRIVER_ABUSE_4, + SFX_PIMP_DRIVER_ABUSE_5, + SFX_PIMP_CHAT_1, + SFX_PIMP_CHAT_2, + SFX_PIMP_CHAT_3, + SFX_PIMP_CHAT_4, + SFX_PIMP_CHAT_5, + SFX_PIMP_CHAT_6, + SFX_PIMP_CHAT_7, + SFX_PIMP_CHAT_8, + SFX_PIMP_CHAT_9, + SFX_PIMP_CHAT_10, + SFX_PIMP_CHAT_11, + SFX_PIMP_CHAT_12, + SFX_PIMP_CHAT_13, + SFX_PIMP_CHAT_14, + SFX_PIMP_CHAT_15, + SFX_PIMP_CHAT_16, + SFX_PIMP_CHAT_17, + SFX_PIMP_DODGE_1, + SFX_PIMP_DODGE_2, + SFX_PIMP_DODGE_3, + SFX_PIMP_DODGE_4, + SFX_PIMP_DODGE_5, + SFX_PIMP_DODGE_6, + SFX_PIMP_FIGHT_1, + SFX_PIMP_FIGHT_2, + SFX_PIMP_FIGHT_3, + SFX_PIMP_FIGHT_4, + SFX_PIMP_FIGHT_5, + SFX_PIMP_FIGHT_6, + SFX_PIMP_FIGHT_7, + SFX_PIMP_FIGHT_8, + SFX_PIMP_FIGHT_9, + SFX_PIMP_GUN_COOL_1, + SFX_PIMP_GUN_COOL_2, + SFX_PIMP_GUN_COOL_3, + SFX_PIMP_GUN_COOL_4, + SFX_PIMP_GUN_COOL_5, + SFX_PIMP_GUN_COOL_6, + SFX_PIMP_GUN_COOL_7, + SFX_PIMP_CARJACKED_1, + SFX_PIMP_CARJACKED_2, + SFX_PIMP_CARJACKED_3, + SFX_PIMP_CARJACKED_4, + SFX_PIMP_SHOCKED_1, + SFX_PIMP_SHOCKED_2, + SFX_NORMAL_MALE_DRIVER_ABUSE_1, + SFX_NORMAL_MALE_DRIVER_ABUSE_2, + SFX_NORMAL_MALE_DRIVER_ABUSE_3, + SFX_NORMAL_MALE_DRIVER_ABUSE_4, + SFX_NORMAL_MALE_DRIVER_ABUSE_5, + SFX_NORMAL_MALE_DRIVER_ABUSE_6, + SFX_NORMAL_MALE_DRIVER_ABUSE_7, + SFX_NORMAL_MALE_DRIVER_ABUSE_8, + SFX_NORMAL_MALE_DRIVER_ABUSE_9, + SFX_NORMAL_MALE_DRIVER_ABUSE_10, + SFX_NORMAL_MALE_DRIVER_ABUSE_11, + SFX_NORMAL_MALE_DRIVER_ABUSE_12, + SFX_NORMAL_MALE_CHAT_1, + SFX_NORMAL_MALE_CHAT_2, + SFX_NORMAL_MALE_CHAT_3, + SFX_NORMAL_MALE_CHAT_4, + SFX_NORMAL_MALE_CHAT_5, + SFX_NORMAL_MALE_CHAT_6, + SFX_NORMAL_MALE_CHAT_7, + SFX_NORMAL_MALE_CHAT_8, + SFX_NORMAL_MALE_CHAT_9, + SFX_NORMAL_MALE_CHAT_10, + SFX_NORMAL_MALE_CHAT_11, + SFX_NORMAL_MALE_CHAT_12, + SFX_NORMAL_MALE_CHAT_13, + SFX_NORMAL_MALE_CHAT_14, + SFX_NORMAL_MALE_CHAT_15, + SFX_NORMAL_MALE_CHAT_16, + SFX_NORMAL_MALE_CHAT_17, + SFX_NORMAL_MALE_CHAT_18, + SFX_NORMAL_MALE_CHAT_19, + SFX_NORMAL_MALE_CHAT_20, + SFX_NORMAL_MALE_CHAT_21, + SFX_NORMAL_MALE_CHAT_22, + SFX_NORMAL_MALE_CHAT_23, + SFX_NORMAL_MALE_CHAT_24, + SFX_NORMAL_MALE_CHAT_25, + SFX_NORMAL_MALE_DODGE_1, + SFX_NORMAL_MALE_DODGE_2, + SFX_NORMAL_MALE_DODGE_3, + SFX_NORMAL_MALE_DODGE_4, + SFX_NORMAL_MALE_DODGE_5, + SFX_NORMAL_MALE_DODGE_6, + SFX_NORMAL_MALE_DODGE_7, + SFX_NORMAL_MALE_DODGE_8, + SFX_NORMAL_MALE_DODGE_9, + SFX_NORMAL_MALE_EYING_1, + SFX_NORMAL_MALE_EYING_2, + SFX_NORMAL_MALE_EYING_3, + SFX_NORMAL_MALE_EYING_4, + SFX_NORMAL_MALE_EYING_5, + SFX_NORMAL_MALE_EYING_6, + SFX_NORMAL_MALE_EYING_7, + SFX_NORMAL_MALE_EYING_8, + SFX_NORMAL_MALE_GUN_PANIC_1, + SFX_NORMAL_MALE_GUN_PANIC_2, + SFX_NORMAL_MALE_GUN_PANIC_3, + SFX_NORMAL_MALE_GUN_PANIC_4, + SFX_NORMAL_MALE_GUN_PANIC_5, + SFX_NORMAL_MALE_GUN_PANIC_6, + SFX_NORMAL_MALE_GUN_PANIC_7, + SFX_NORMAL_MALE_CARJACKED_1, + SFX_NORMAL_MALE_CARJACKED_2, + SFX_NORMAL_MALE_CARJACKED_3, + SFX_NORMAL_MALE_CARJACKED_4, + SFX_NORMAL_MALE_CARJACKED_5, + SFX_NORMAL_MALE_CARJACKED_6, + SFX_NORMAL_MALE_CARJACKED_7, + SFX_NORMAL_MALE_RUN_FROM_FIGHT_1, + SFX_NORMAL_MALE_RUN_FROM_FIGHT_2, + SFX_NORMAL_MALE_RUN_FROM_FIGHT_3, + SFX_NORMAL_MALE_RUN_FROM_FIGHT_4, + SFX_NORMAL_MALE_RUN_FROM_FIGHT_5, + SFX_NORMAL_MALE_SHOCKED_1, + SFX_NORMAL_MALE_SHOCKED_2, + SFX_NORMAL_MALE_SHOCKED_3, + SFX_NORMAL_MALE_SHOCKED_4, + SFX_NORMAL_MALE_SHOCKED_5, + SFX_NORMAL_MALE_SHOCKED_6, + SFX_NORMAL_MALE_SHOCKED_7, + SFX_NORMAL_MALE_SHOCKED_8, + SFX_NORMAL_MALE_SHOCKED_9, + SFX_NORMAL_MALE_SHOCKED_10, + SFX_BOMBERMAN_1, + SFX_BOMBERMAN_2, + SFX_BOMBERMAN_3, + SFX_BOMBERMAN_4, + SFX_BOMBERMAN_5, + SFX_BOMBERMAN_6, + SFX_BOMBERMAN_7, + SFX_8BALL_DODGE_1, + SFX_8BALL_DODGE_2, + SFX_8BALL_DODGE_3, + SFX_8BALL_DODGE_4, + SFX_8BALL_DODGE_5, + SFX_8BALL_DODGE_6, + SFX_8BALL_DODGE_7, + SFX_8BALL_FIGHT_1, + SFX_8BALL_FIGHT_2, + SFX_8BALL_FIGHT_3, + SFX_8BALL_FIGHT_4, + SFX_8BALL_FIGHT_5, + SFX_8BALL_FIGHT_6, + SFX_8BALL_GUN_COOL_1, + SFX_8BALL_GUN_COOL_2, + SFX_8BALL_MUGGED_1, + SFX_8BALL_MUGGED_2, + SFX_SALVATORE_DODGE_1, + SFX_SALVATORE_DODGE_2, + SFX_SALVATORE_DODGE_3, + SFX_SALVATORE_FIGHT_1, + SFX_SALVATORE_FIGHT_2, + SFX_SALVATORE_FIGHT_3, + SFX_SALVATORE_FIGHT_4, + SFX_SALVATORE_FIGHT_5, + SFX_SALVATORE_FIGHT_6, + SFX_SALVATORE_GUN_COOL_1, + SFX_SALVATORE_GUN_COOL_2, + SFX_SALVATORE_GUN_COOL_3, + SFX_SALVATORE_GUN_COOL_4, + SFX_SALVATORE_MUGGED_1, + SFX_SALVATORE_MUGGED_2, + SFX_MISTY_DODGE_1, + SFX_MISTY_DODGE_2, + SFX_MISTY_DODGE_3, + SFX_MISTY_DODGE_4, + SFX_MISTY_DODGE_5, + SFX_MISTY_FIGHT_1, + SFX_MISTY_FIGHT_2, + SFX_MISTY_FIGHT_3, + SFX_MISTY_FIGHT_4, + SFX_MISTY_GUN_COOL_1, + SFX_MISTY_GUN_COOL_2, + SFX_MISTY_GUN_COOL_3, + SFX_MISTY_GUN_COOL_4, + SFX_MISTY_GUN_COOL_5, + SFX_MISTY_HERE_1, + SFX_MISTY_HERE_2, + SFX_MISTY_HERE_3, + SFX_MISTY_HERE_4, + SFX_MISTY_MUGGED_1, + SFX_MISTY_MUGGED_2, + SFX_MEDIC_VOICE_1_GUN_PANIC_1, + SFX_MEDIC_VOICE_1_GUN_PANIC_2, + SFX_MEDIC_VOICE_1_GUN_PANIC_3, + SFX_MEDIC_VOICE_1_GUN_PANIC_4, + SFX_MEDIC_VOICE_1_GUN_PANIC_5, + SFX_MEDIC_VOICE_1_CARJACKED_1, + SFX_MEDIC_VOICE_1_CARJACKED_2, + SFX_MEDIC_VOICE_1_CARJACKED_3, + SFX_MEDIC_VOICE_1_CARJACKED_4, + SFX_MEDIC_VOICE_1_CARJACKED_5, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_1, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_2, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_3, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_4, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_5, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_6, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_1, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_2, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_3, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_4, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_5, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_6, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_7, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_8, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_9, + SFX_MEDIC_VOICE_1_AT_VICTIM_1, + SFX_MEDIC_VOICE_1_AT_VICTIM_2, + SFX_MEDIC_VOICE_1_AT_VICTIM_3, + SFX_MEDIC_VOICE_1_AT_VICTIM_4, + SFX_MEDIC_VOICE_1_AT_VICTIM_5, + SFX_MEDIC_VOICE_1_AT_VICTIM_6, + SFX_MEDIC_VOICE_1_AT_VICTIM_7, + SFX_MEDIC_VOICE_1_AT_VICTIM_8, + SFX_MEDIC_VOICE_1_AT_VICTIM_9, + SFX_MEDIC_VOICE_1_AT_VICTIM_10, + SFX_MEDIC_VOICE_1_AT_VICTIM_11, + SFX_MEDIC_VOICE_1_AT_VICTIM_12, + SFX_MEDIC_VOICE_2_GUN_PANIC_1, + SFX_MEDIC_VOICE_2_GUN_PANIC_2, + SFX_MEDIC_VOICE_2_GUN_PANIC_3, + SFX_MEDIC_VOICE_2_GUN_PANIC_4, + SFX_MEDIC_VOICE_2_GUN_PANIC_5, + SFX_MEDIC_VOICE_2_CARJACKED_1, + SFX_MEDIC_VOICE_2_CARJACKED_2, + SFX_MEDIC_VOICE_2_CARJACKED_3, + SFX_MEDIC_VOICE_2_CARJACKED_4, + SFX_MEDIC_VOICE_2_CARJACKED_5, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_1, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_2, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_3, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_4, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_5, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_6, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_1, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_2, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_3, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_4, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_5, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_6, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_7, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_8, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_9, + SFX_MEDIC_VOICE_2_AT_VICTIM_1, + SFX_MEDIC_VOICE_2_AT_VICTIM_2, + SFX_MEDIC_VOICE_2_AT_VICTIM_3, + SFX_MEDIC_VOICE_2_AT_VICTIM_4, + SFX_MEDIC_VOICE_2_AT_VICTIM_5, + SFX_MEDIC_VOICE_2_AT_VICTIM_6, + SFX_MEDIC_VOICE_2_AT_VICTIM_7, + SFX_MEDIC_VOICE_2_AT_VICTIM_8, + SFX_MEDIC_VOICE_2_AT_VICTIM_9, + SFX_MEDIC_VOICE_2_AT_VICTIM_10, + SFX_MEDIC_VOICE_2_AT_VICTIM_11, + SFX_MEDIC_VOICE_2_AT_VICTIM_12, + SFX_PLASTER_BLOKE_1, + SFX_PLASTER_BLOKE_2, + SFX_PLASTER_BLOKE_3, + SFX_PLASTER_BLOKE_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CHAT_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CHAT_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CHAT_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CHAT_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_5, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_EYING_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_EYING_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_EYING_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_EYING_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_5, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_GUN_PANIC_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_GUN_PANIC_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_GUN_PANIC_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CARJACKED_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CARJACKED_2, + SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_1, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_2, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_3, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_4, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_5, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_6, + SFX_FOOTBALL_FEMALE_VOICE_1_DODGE_1, + SFX_FOOTBALL_FEMALE_VOICE_1_DODGE_2, + SFX_FOOTBALL_FEMALE_VOICE_1_DODGE_3, + SFX_FOOTBALL_FEMALE_VOICE_1_DODGE_4, + SFX_FOOTBALL_FEMALE_VOICE_1_MUGGED_1, + SFX_FOOTBALL_FEMALE_VOICE_1_SHOCKED_1, + SFX_FOOTBALL_FEMALE_VOICE_1_SHOCKED_2, + SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_1, + SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_2, + SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_3, + SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_4, + SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_5, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_1, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_2, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_3, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_4, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_5, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_6, + SFX_FOOTBALL_FEMALE_VOICE_2_DODGE_1, + SFX_FOOTBALL_FEMALE_VOICE_2_DODGE_2, + SFX_FOOTBALL_FEMALE_VOICE_2_DODGE_3, + SFX_FOOTBALL_FEMALE_VOICE_2_DODGE_4, + SFX_FOOTBALL_FEMALE_VOICE_2_MUGGED_1, + SFX_FOOTBALL_FEMALE_VOICE_2_SHOCKED_1, + SFX_FOOTBALL_FEMALE_VOICE_2_SHOCKED_2, + SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_1, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_2, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_3, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_4, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_5, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_6, + SFX_FOOTBALL_MALE_VOICE_1_DODGE_1, + SFX_FOOTBALL_MALE_VOICE_1_DODGE_2, + SFX_FOOTBALL_MALE_VOICE_1_DODGE_3, + SFX_FOOTBALL_MALE_VOICE_1_DODGE_4, + SFX_FOOTBALL_MALE_VOICE_1_FIGHT_1, + SFX_FOOTBALL_MALE_VOICE_1_FIGHT_2, + SFX_FOOTBALL_MALE_VOICE_1_FIGHT_3, + SFX_FOOTBALL_MALE_VOICE_1_SHOCKED_1, + SFX_FOOTBALL_MALE_VOICE_1_SHOCKED_2, + SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_1, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_2, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_3, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_4, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_5, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_6, + SFX_FOOTBALL_MALE_VOICE_2_DODGE_1, + SFX_FOOTBALL_MALE_VOICE_2_DODGE_2, + SFX_FOOTBALL_MALE_VOICE_2_DODGE_3, + SFX_FOOTBALL_MALE_VOICE_2_DODGE_4, + SFX_FOOTBALL_MALE_VOICE_2_FIGHT_1, + SFX_FOOTBALL_MALE_VOICE_2_FIGHT_2, + SFX_FOOTBALL_MALE_VOICE_2_FIGHT_3, + SFX_FOOTBALL_MALE_VOICE_2_SHOCKED_1, + SFX_FOOTBALL_MALE_VOICE_2_SHOCKED_2, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_MODEL_FEMALE_VOICE_1_CHAT_1, + SFX_MODEL_FEMALE_VOICE_1_CHAT_2, + SFX_MODEL_FEMALE_VOICE_1_CHAT_3, + SFX_MODEL_FEMALE_VOICE_1_CHAT_4, + SFX_MODEL_FEMALE_VOICE_1_CHAT_5, + SFX_MODEL_FEMALE_VOICE_1_CHAT_6, + SFX_MODEL_FEMALE_VOICE_1_CHAT_7, + SFX_MODEL_FEMALE_VOICE_1_CHAT_8, + SFX_MODEL_FEMALE_VOICE_1_DODGE_1, + SFX_MODEL_FEMALE_VOICE_1_DODGE_2, + SFX_MODEL_FEMALE_VOICE_1_DODGE_3, + SFX_MODEL_FEMALE_VOICE_1_DODGE_4, + SFX_MODEL_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_MODEL_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_MODEL_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_MODEL_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_MODEL_FEMALE_VOICE_1_MUGGED_1, + SFX_MODEL_FEMALE_VOICE_1_MUGGED_2, + SFX_MODEL_FEMALE_VOICE_1_MUGGED_3, + SFX_MODEL_FEMALE_VOICE_1_SHOCKED_1, + SFX_MODEL_FEMALE_VOICE_1_SHOCKED_2, + SFX_MODEL_FEMALE_VOICE_1_SHOCKED_3, + SFX_MODEL_FEMALE_VOICE_1_SHOCKED_4, + SFX_MODEL_FEMALE_VOICE_1_SHOCKED_5, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_MODEL_MALE_VOICE_1_CHAT_1, + SFX_MODEL_MALE_VOICE_1_CHAT_2, + SFX_MODEL_MALE_VOICE_1_CHAT_3, + SFX_MODEL_MALE_VOICE_1_CHAT_4, + SFX_MODEL_MALE_VOICE_1_CHAT_5, + SFX_MODEL_MALE_VOICE_1_CHAT_6, + SFX_MODEL_MALE_VOICE_1_DODGE_1, + SFX_MODEL_MALE_VOICE_1_DODGE_2, + SFX_MODEL_MALE_VOICE_1_DODGE_3, + SFX_MODEL_MALE_VOICE_1_DODGE_4, + SFX_MODEL_MALE_VOICE_1_DODGE_5, + SFX_MODEL_MALE_VOICE_1_DODGE_6, + SFX_MODEL_MALE_VOICE_1_EYING_1, + SFX_MODEL_MALE_VOICE_1_EYING_2, + SFX_MODEL_MALE_VOICE_1_EYING_3, + SFX_MODEL_MALE_VOICE_1_FIGHT_1, + SFX_MODEL_MALE_VOICE_1_FIGHT_2, + SFX_MODEL_MALE_VOICE_1_FIGHT_3, + SFX_MODEL_MALE_VOICE_1_FIGHT_4, + SFX_MODEL_MALE_VOICE_1_FIGHT_5, + SFX_MODEL_MALE_VOICE_1_CARJACKED_1, + SFX_MODEL_MALE_VOICE_1_CARJACKED_2, + SFX_MODEL_MALE_VOICE_1_MUGGED_1, + SFX_MODEL_MALE_VOICE_1_MUGGED_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_3, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_4, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_5, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_6, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_3, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_4, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_5, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_6, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_3, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_4, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_5, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_EYING_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_EYING_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_EYING_3, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_3, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_4, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_5, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_6, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_GUN_PANIC_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_GUN_PANIC_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CARJACKED_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CARJACKED_2, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_SCUM_MALE_VOICE_1_CHAT_1, + SFX_SCUM_MALE_VOICE_1_CHAT_2, + SFX_SCUM_MALE_VOICE_1_CHAT_3, + SFX_SCUM_MALE_VOICE_1_CHAT_4, + SFX_SCUM_MALE_VOICE_1_CHAT_5, + SFX_SCUM_MALE_VOICE_1_CHAT_6, + SFX_SCUM_MALE_VOICE_1_CHAT_7, + SFX_SCUM_MALE_VOICE_1_CHAT_8, + SFX_SCUM_MALE_VOICE_1_CHAT_9, + SFX_SCUM_MALE_VOICE_1_DODGE_1, + SFX_SCUM_MALE_VOICE_1_DODGE_2, + SFX_SCUM_MALE_VOICE_1_DODGE_3, + SFX_SCUM_MALE_VOICE_1_DODGE_4, + SFX_SCUM_MALE_VOICE_1_DODGE_5, + SFX_SCUM_MALE_VOICE_1_EYING_1, + SFX_SCUM_MALE_VOICE_1_EYING_2, + SFX_SCUM_MALE_VOICE_1_EYING_3, + SFX_SCUM_MALE_VOICE_1_EYING_4, + SFX_SCUM_MALE_VOICE_1_EYING_5, + SFX_SCUM_MALE_VOICE_1_FIGHT_1, + SFX_SCUM_MALE_VOICE_1_FIGHT_2, + SFX_SCUM_MALE_VOICE_1_FIGHT_3, + SFX_SCUM_MALE_VOICE_1_FIGHT_4, + SFX_SCUM_MALE_VOICE_1_FIGHT_5, + SFX_SCUM_MALE_VOICE_1_FIGHT_6, + SFX_SCUM_MALE_VOICE_1_FIGHT_7, + SFX_SCUM_MALE_VOICE_1_FIGHT_8, + SFX_SCUM_MALE_VOICE_1_FIGHT_9, + SFX_SCUM_MALE_VOICE_1_FIGHT_10, + SFX_SCUM_MALE_VOICE_1_GUN_PANIC_1, + SFX_SCUM_MALE_VOICE_1_GUN_PANIC_2, + SFX_SCUM_MALE_VOICE_1_GUN_PANIC_3, + SFX_SCUM_MALE_VOICE_1_GUN_PANIC_4, + SFX_SCUM_MALE_VOICE_1_GUN_PANIC_5, + SFX_SCUM_MALE_VOICE_1_LOST_1, + SFX_SCUM_MALE_VOICE_1_LOST_2, + SFX_SCUM_MALE_VOICE_1_LOST_3, + SFX_SCUM_MALE_VOICE_1_MUGGED_1, + SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_SCUM_FEMALE_VOICE_1_CHAT_1, + SFX_SCUM_FEMALE_VOICE_1_CHAT_2, + SFX_SCUM_FEMALE_VOICE_1_CHAT_3, + SFX_SCUM_FEMALE_VOICE_1_CHAT_4, + SFX_SCUM_FEMALE_VOICE_1_CHAT_5, + SFX_SCUM_FEMALE_VOICE_1_CHAT_6, + SFX_SCUM_FEMALE_VOICE_1_CHAT_7, + SFX_SCUM_FEMALE_VOICE_1_CHAT_8, + SFX_SCUM_FEMALE_VOICE_1_CHAT_9, + SFX_SCUM_FEMALE_VOICE_1_CHAT_10, + SFX_SCUM_FEMALE_VOICE_1_CHAT_11, + SFX_SCUM_FEMALE_VOICE_1_CHAT_12, + SFX_SCUM_FEMALE_VOICE_1_CHAT_13, + SFX_SCUM_FEMALE_VOICE_1_DODGE_1, + SFX_SCUM_FEMALE_VOICE_1_DODGE_2, + SFX_SCUM_FEMALE_VOICE_1_DODGE_3, + SFX_SCUM_FEMALE_VOICE_1_DODGE_4, + SFX_SCUM_FEMALE_VOICE_1_DODGE_5, + SFX_SCUM_FEMALE_VOICE_1_DODGE_6, + SFX_SCUM_FEMALE_VOICE_1_DODGE_7, + SFX_SCUM_FEMALE_VOICE_1_DODGE_8, + SFX_SCUM_FEMALE_VOICE_1_FIGHT_1, + SFX_SCUM_FEMALE_VOICE_1_FIGHT_2, + SFX_SCUM_FEMALE_VOICE_1_FIGHT_3, + SFX_SCUM_FEMALE_VOICE_1_FIGHT_4, + SFX_SCUM_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_SCUM_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_SCUM_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_SCUM_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_SCUM_FEMALE_VOICE_1_MUGGED_1, + SFX_SCUM_FEMALE_VOICE_1_MUGGED_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_3, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_4, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_5, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_6, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_7, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_3, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_4, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_5, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_GUN_PANIC_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_GUN_PANIC_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_GUN_PANIC_3, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_GUN_PANIC_4, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CARJACKED_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_MUGGED_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_MUGGED_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_3, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_4, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_6, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_6, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DODGE_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DODGE_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DODGE_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DODGE_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_FIGHT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_FIGHT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_FIGHT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_FIGHT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_GUN_PANIC_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_GUN_PANIC_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_GUN_PANIC_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CARJACKED_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CARJACKED_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_MUGGED_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_MUGGED_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_6, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_6, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DODGE_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DODGE_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DODGE_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DODGE_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_FIGHT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_FIGHT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_FIGHT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_FIGHT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_GUN_PANIC_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_GUN_PANIC_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_GUN_PANIC_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CARJACKED_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CARJACKED_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_MUGGED_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_MUGGED_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_RUN_FROM_FIGHT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_RUN_FROM_FIGHT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_RUN_FROM_FIGHT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_RUN_FROM_FIGHT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_RUN_FROM_FIGHT_5, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_3, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_4, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_5, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_6, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_7, + SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_3, + SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_4, + SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_5, + SFX_BLACK_FAT_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_BLACK_FAT_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_BLACK_FAT_FEMALE_VOICE_1_CARJACKED_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_CARJACKED_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_MUGGED_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_MUGGED_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_3, + SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_4, + SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_5, + SFX_WHITE_DOCKER_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_DOCKER_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_DOCKER_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_2, + SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_3, + SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_4, + SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_5, + SFX_WHITE_DOCKER_MALE_VOICE_1_DODGE_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_DODGE_2, + SFX_WHITE_DOCKER_MALE_VOICE_1_DODGE_3, + SFX_WHITE_DOCKER_MALE_VOICE_1_DODGE_4, + SFX_WHITE_DOCKER_MALE_VOICE_1_EYING_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_EYING_2, + SFX_WHITE_DOCKER_MALE_VOICE_1_EYING_3, + SFX_WHITE_DOCKER_MALE_VOICE_1_FIGHT_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_FIGHT_2, + SFX_WHITE_DOCKER_MALE_VOICE_1_FIGHT_3, + SFX_WHITE_DOCKER_MALE_VOICE_1_GUN_PANIC_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_GUN_PANIC_2, + SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_HOSPITAL_MALE_VOICE_1_CHAT_1, + SFX_HOSPITAL_MALE_VOICE_1_CHAT_2, + SFX_HOSPITAL_MALE_VOICE_1_CHAT_3, + SFX_HOSPITAL_MALE_VOICE_1_CHAT_4, + SFX_HOSPITAL_MALE_VOICE_1_CHAT_5, + SFX_HOSPITAL_MALE_VOICE_1_DODGE_1, + SFX_HOSPITAL_MALE_VOICE_1_DODGE_2, + SFX_HOSPITAL_MALE_VOICE_1_DODGE_3, + SFX_HOSPITAL_MALE_VOICE_1_DODGE_4, + SFX_HOSPITAL_MALE_VOICE_1_FIGHT_1, + SFX_HOSPITAL_MALE_VOICE_1_FIGHT_2, + SFX_HOSPITAL_MALE_VOICE_1_FIGHT_3, + SFX_HOSPITAL_MALE_VOICE_1_FIGHT_4, + SFX_HOSPITAL_MALE_VOICE_1_GUN_PANIC_1, + SFX_HOSPITAL_MALE_VOICE_1_GUN_PANIC_2, + SFX_HOSPITAL_MALE_VOICE_1_GUN_PANIC_3, + SFX_HOSPITAL_MALE_VOICE_1_GUN_PANIC_4, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_1, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_2, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_3, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_4, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_5, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_6, + SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_1, + SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_2, + SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_3, + SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_4, + SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_5, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_1, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_2, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_3, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_4, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_5, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_6, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_7, + SFX_FEMALE_1_VOICE_1_CHAT_1, + SFX_FEMALE_1_VOICE_1_CHAT_2, + SFX_FEMALE_1_VOICE_1_CHAT_3, + SFX_FEMALE_1_VOICE_1_CHAT_4, + SFX_FEMALE_1_VOICE_1_CHAT_5, + SFX_FEMALE_1_VOICE_1_CHAT_6, + SFX_FEMALE_1_VOICE_1_CHAT_7, + SFX_FEMALE_1_VOICE_1_CHAT_8, + SFX_FEMALE_1_VOICE_1_DODGE_1, + SFX_FEMALE_1_VOICE_1_DODGE_2, + SFX_FEMALE_1_VOICE_1_DODGE_3, + SFX_FEMALE_1_VOICE_1_DODGE_4, + SFX_FEMALE_1_VOICE_1_DODGE_5, + SFX_FEMALE_1_VOICE_1_DODGE_6, + SFX_FEMALE_1_VOICE_1_GUN_PANIC_1, + SFX_FEMALE_1_VOICE_1_GUN_PANIC_2, + SFX_FEMALE_1_VOICE_1_CARJACKED_1, + SFX_FEMALE_1_VOICE_1_CARJACKED_2, + SFX_FEMALE_1_VOICE_1_MUGGED_1, + SFX_FEMALE_1_VOICE_1_MUGGED_2, + SFX_FEMALE_1_VOICE_1_MUGGED_3, + SFX_FEMALE_1_VOICE_1_RUN_FROM_FIGHT_1, + SFX_FEMALE_1_VOICE_1_RUN_FROM_FIGHT_2, + SFX_FEMALE_1_VOICE_1_SHOCKED_1, + SFX_FEMALE_1_VOICE_1_SHOCKED_2, + SFX_FEMALE_1_VOICE_1_SHOCKED_3, + SFX_FEMALE_1_VOICE_1_SHOCKED_4, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_1, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_2, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_3, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_4, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_5, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_6, + SFX_FEMALE_3_VOICE_1_CHAT_1, + SFX_FEMALE_3_VOICE_1_CHAT_2, + SFX_FEMALE_3_VOICE_1_CHAT_3, + SFX_FEMALE_3_VOICE_1_CHAT_4, + SFX_FEMALE_3_VOICE_1_CHAT_5, + SFX_FEMALE_3_VOICE_1_DODGE_1, + SFX_FEMALE_3_VOICE_1_DODGE_2, + SFX_FEMALE_3_VOICE_1_DODGE_3, + SFX_FEMALE_3_VOICE_1_DODGE_4, + SFX_FEMALE_3_VOICE_1_DODGE_5, + SFX_FEMALE_3_VOICE_1_DODGE_6, + SFX_FEMALE_3_VOICE_1_GUN_PANIC_1, + SFX_FEMALE_3_VOICE_1_GUN_PANIC_2, + SFX_FEMALE_3_VOICE_1_GUN_PANIC_3, + SFX_FEMALE_3_VOICE_1_GUN_PANIC_4, + SFX_FEMALE_3_VOICE_1_GUN_PANIC_5, + SFX_FEMALE_3_VOICE_1_CARJACKED_1, + SFX_FEMALE_3_VOICE_1_CARJACKED_2, + SFX_FEMALE_3_VOICE_1_CARJACKED_3, + SFX_FEMALE_3_VOICE_1_MUGGED_1, + SFX_FEMALE_3_VOICE_1_MUGGED_2, + SFX_FEMALE_3_VOICE_1_MUGGED_3, + SFX_FEMALE_3_VOICE_1_RUN_FROM_FIGHT_1, + SFX_FEMALE_3_VOICE_1_RUN_FROM_FIGHT_2, + SFX_FEMALE_3_VOICE_1_RUN_FROM_FIGHT_3, + SFX_FEMALE_3_VOICE_1_RUN_FROM_FIGHT_4, + SFX_FEMALE_3_VOICE_1_SHOCKED_1, + SFX_FEMALE_3_VOICE_1_SHOCKED_2, + SFX_FEMALE_3_VOICE_1_SHOCKED_3, + SFX_FEMALE_3_VOICE_1_SHOCKED_4, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_2, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_3, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_4, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_5, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_6, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_7, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_1, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_2, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_3, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_4, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_5, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_6, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_7, + SFX_CASUAL_MALE_OLD_VOICE_1_DODGE_1, + SFX_CASUAL_MALE_OLD_VOICE_1_DODGE_2, + SFX_CASUAL_MALE_OLD_VOICE_1_DODGE_3, + SFX_CASUAL_MALE_OLD_VOICE_1_DODGE_4, + SFX_CASUAL_MALE_OLD_VOICE_1_EYING_1, + SFX_CASUAL_MALE_OLD_VOICE_1_EYING_2, + SFX_CASUAL_MALE_OLD_VOICE_1_EYING_3, + SFX_CASUAL_MALE_OLD_VOICE_1_EYING_4, + SFX_CASUAL_MALE_OLD_VOICE_1_EYING_5, + SFX_CASUAL_MALE_OLD_VOICE_1_FIGHT_1, + SFX_CASUAL_MALE_OLD_VOICE_1_FIGHT_2, + SFX_CASUAL_MALE_OLD_VOICE_1_FIGHT_3, + SFX_CASUAL_MALE_OLD_VOICE_1_FIGHT_4, + SFX_CASUAL_MALE_OLD_VOICE_1_CARJACKED_1, + SFX_CASUAL_MALE_OLD_VOICE_1_CARJACKED_2, + SFX_CASUAL_MALE_OLD_VOICE_1_CARJACKED_3, + SFX_CASUAL_MALE_OLD_VOICE_1_MUGGED_1, + SFX_CASUAL_MALE_OLD_VOICE_1_MUGGED_2, + SFX_CASUAL_MALE_OLD_VOICE_1_MUGGED_3, + SFX_CASUAL_MALE_OLD_VOICE_1_MUGGED_4, + SFX_STUDENT_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_STUDENT_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_STUDENT_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_STUDENT_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_STUDENT_MALE_VOICE_1_CHAT_1, + SFX_STUDENT_MALE_VOICE_1_CHAT_2, + SFX_STUDENT_MALE_VOICE_1_CHAT_3, + SFX_STUDENT_MALE_VOICE_1_CHAT_4, + SFX_STUDENT_MALE_VOICE_1_CHAT_5, + SFX_STUDENT_MALE_VOICE_1_DODGE_1, + SFX_STUDENT_MALE_VOICE_1_DODGE_2, + SFX_STUDENT_MALE_VOICE_1_DODGE_3, + SFX_STUDENT_MALE_VOICE_1_DODGE_4, + SFX_STUDENT_MALE_VOICE_1_FIGHT_1, + SFX_STUDENT_MALE_VOICE_1_FIGHT_2, + SFX_STUDENT_MALE_VOICE_1_FIGHT_3, + SFX_STUDENT_MALE_VOICE_1_FIGHT_4, + SFX_STUDENT_MALE_VOICE_1_GUN_PANIC_1, + SFX_STUDENT_MALE_VOICE_1_GUN_PANIC_2, + SFX_STUDENT_MALE_VOICE_1_MUGGED_1, + SFX_STUDENT_MALE_VOICE_1_MUGGED_2, + SFX_STUDENT_MALE_VOICE_1_SHOCKED_1, + SFX_STUDENT_MALE_VOICE_1_SHOCKED_2, + SFX_STUDENT_MALE_VOICE_1_SHOCKED_3, + SFX_STUDENT_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_STUDENT_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_STUDENT_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_STUDENT_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_STUDENT_FEMALE_VOICE_1_CHAT_1, + SFX_STUDENT_FEMALE_VOICE_1_CHAT_2, + SFX_STUDENT_FEMALE_VOICE_1_CHAT_3, + SFX_STUDENT_FEMALE_VOICE_1_CHAT_4, + SFX_STUDENT_FEMALE_VOICE_1_DODGE_1, + SFX_STUDENT_FEMALE_VOICE_1_DODGE_2, + SFX_STUDENT_FEMALE_VOICE_1_DODGE_3, + SFX_STUDENT_FEMALE_VOICE_1_DODGE_4, + SFX_STUDENT_FEMALE_VOICE_1_FIGHT_1, + SFX_STUDENT_FEMALE_VOICE_1_FIGHT_2, + SFX_STUDENT_FEMALE_VOICE_1_FIGHT_3, + SFX_STUDENT_FEMALE_VOICE_1_FIGHT_4, + SFX_STUDENT_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_STUDENT_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_STUDENT_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_STUDENT_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_STUDENT_FEMALE_VOICE_1_MUGGED_1, + SFX_STUDENT_FEMALE_VOICE_1_MUGGED_2, + SFX_STUDENT_FEMALE_VOICE_1_SHOCKED_1, + SFX_STUDENT_FEMALE_VOICE_1_SHOCKED_2, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_7, + SFX_HOOD_MALE_VOICE_1_CHAT_1, + SFX_HOOD_MALE_VOICE_1_CHAT_2, + SFX_HOOD_MALE_VOICE_1_CHAT_3, + SFX_HOOD_MALE_VOICE_1_CHAT_4, + SFX_HOOD_MALE_VOICE_1_CHAT_5, + SFX_HOOD_MALE_VOICE_1_CHAT_6, + SFX_HOOD_MALE_VOICE_1_DODGE_1, + SFX_HOOD_MALE_VOICE_1_DODGE_2, + SFX_HOOD_MALE_VOICE_1_DODGE_3, + SFX_HOOD_MALE_VOICE_1_DODGE_4, + SFX_HOOD_MALE_VOICE_1_DODGE_5, + SFX_HOOD_MALE_VOICE_1_EYING_1, + SFX_HOOD_MALE_VOICE_1_EYING_2, + SFX_HOOD_MALE_VOICE_1_FIGHT_1, + SFX_HOOD_MALE_VOICE_1_FIGHT_2, + SFX_HOOD_MALE_VOICE_1_FIGHT_3, + SFX_HOOD_MALE_VOICE_1_FIGHT_4, + SFX_HOOD_MALE_VOICE_1_FIGHT_5, + SFX_HOOD_MALE_VOICE_1_FIGHT_6, + SFX_HOOD_MALE_VOICE_1_GUN_COOL_1, + SFX_HOOD_MALE_VOICE_1_GUN_COOL_2, + SFX_HOOD_MALE_VOICE_1_GUN_COOL_3, + SFX_HOOD_MALE_VOICE_1_GUN_COOL_4, + SFX_HOOD_MALE_VOICE_1_GUN_COOL_5, + SFX_HOOD_MALE_VOICE_1_CARJACKED_1, + SFX_HOOD_MALE_VOICE_1_CARJACKED_2, + SFX_HOOD_MALE_VOICE_1_CARJACKING_1, + SFX_HOOD_MALE_VOICE_1_CARJACKING_2, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_7, + SFX_HOOD_MALE_VOICE_2_CHAT_1, + SFX_HOOD_MALE_VOICE_2_CHAT_2, + SFX_HOOD_MALE_VOICE_2_CHAT_3, + SFX_HOOD_MALE_VOICE_2_CHAT_4, + SFX_HOOD_MALE_VOICE_2_CHAT_5, + SFX_HOOD_MALE_VOICE_2_CHAT_6, + SFX_HOOD_MALE_VOICE_2_DODGE_1, + SFX_HOOD_MALE_VOICE_2_DODGE_2, + SFX_HOOD_MALE_VOICE_2_DODGE_3, + SFX_HOOD_MALE_VOICE_2_DODGE_4, + SFX_HOOD_MALE_VOICE_2_DODGE_5, + SFX_HOOD_MALE_VOICE_2_EYING_1, + SFX_HOOD_MALE_VOICE_2_EYING_2, + SFX_HOOD_MALE_VOICE_2_FIGHT_1, + SFX_HOOD_MALE_VOICE_2_FIGHT_2, + SFX_HOOD_MALE_VOICE_2_FIGHT_3, + SFX_HOOD_MALE_VOICE_2_FIGHT_4, + SFX_HOOD_MALE_VOICE_2_FIGHT_5, + SFX_HOOD_MALE_VOICE_2_FIGHT_6, + SFX_HOOD_MALE_VOICE_2_GUN_COOL_1, + SFX_HOOD_MALE_VOICE_2_GUN_COOL_2, + SFX_HOOD_MALE_VOICE_2_GUN_COOL_3, + SFX_HOOD_MALE_VOICE_2_GUN_COOL_4, + SFX_HOOD_MALE_VOICE_2_GUN_COOL_5, + SFX_HOOD_MALE_VOICE_2_CARJACKED_1, + SFX_HOOD_MALE_VOICE_2_CARJACKED_2, + SFX_HOOD_MALE_VOICE_2_CARJACKING_1, + SFX_HOOD_MALE_VOICE_2_CARJACKING_2, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_YARDIE_MALE_VOICE_1_CHAT_1, + SFX_YARDIE_MALE_VOICE_1_CHAT_2, + SFX_YARDIE_MALE_VOICE_1_CHAT_3, + SFX_YARDIE_MALE_VOICE_1_CHAT_4, + SFX_YARDIE_MALE_VOICE_1_CHAT_5, + SFX_YARDIE_MALE_VOICE_1_CHAT_6, + SFX_YARDIE_MALE_VOICE_1_CHAT_7, + SFX_YARDIE_MALE_VOICE_1_CHAT_8, + SFX_YARDIE_MALE_VOICE_1_DODGE_1, + SFX_YARDIE_MALE_VOICE_1_DODGE_2, + SFX_YARDIE_MALE_VOICE_1_DODGE_3, + SFX_YARDIE_MALE_VOICE_1_DODGE_4, + SFX_YARDIE_MALE_VOICE_1_DODGE_5, + SFX_YARDIE_MALE_VOICE_1_EYING_1, + SFX_YARDIE_MALE_VOICE_1_EYING_2, + SFX_YARDIE_MALE_VOICE_1_FIGHT_1, + SFX_YARDIE_MALE_VOICE_1_FIGHT_2, + SFX_YARDIE_MALE_VOICE_1_FIGHT_3, + SFX_YARDIE_MALE_VOICE_1_FIGHT_4, + SFX_YARDIE_MALE_VOICE_1_FIGHT_5, + SFX_YARDIE_MALE_VOICE_1_FIGHT_6, + SFX_YARDIE_MALE_VOICE_1_GUN_COOL_1, + SFX_YARDIE_MALE_VOICE_1_CARJACKED_1, + SFX_YARDIE_MALE_VOICE_1_CARJACKING_1, + SFX_YARDIE_MALE_VOICE_1_CARJACKING_2, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_YARDIE_MALE_VOICE_2_CHAT_1, + SFX_YARDIE_MALE_VOICE_2_CHAT_2, + SFX_YARDIE_MALE_VOICE_2_CHAT_3, + SFX_YARDIE_MALE_VOICE_2_CHAT_4, + SFX_YARDIE_MALE_VOICE_2_CHAT_5, + SFX_YARDIE_MALE_VOICE_2_CHAT_6, + SFX_YARDIE_MALE_VOICE_2_CHAT_7, + SFX_YARDIE_MALE_VOICE_2_CHAT_8, + SFX_YARDIE_MALE_VOICE_2_DODGE_1, + SFX_YARDIE_MALE_VOICE_2_DODGE_2, + SFX_YARDIE_MALE_VOICE_2_DODGE_3, + SFX_YARDIE_MALE_VOICE_2_DODGE_4, + SFX_YARDIE_MALE_VOICE_2_DODGE_5, + SFX_YARDIE_MALE_VOICE_2_EYING_1, + SFX_YARDIE_MALE_VOICE_2_EYING_2, + SFX_YARDIE_MALE_VOICE_2_FIGHT_1, + SFX_YARDIE_MALE_VOICE_2_FIGHT_2, + SFX_YARDIE_MALE_VOICE_2_FIGHT_3, + SFX_YARDIE_MALE_VOICE_2_FIGHT_4, + SFX_YARDIE_MALE_VOICE_2_FIGHT_5, + SFX_YARDIE_MALE_VOICE_2_FIGHT_6, + SFX_YARDIE_MALE_VOICE_2_GUN_COOL_1, + SFX_YARDIE_MALE_VOICE_2_CARJACKED_1, + SFX_YARDIE_MALE_VOICE_2_CARJACKING_1, + SFX_YARDIE_MALE_VOICE_2_CARJACKING_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_5, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_6, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_7, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_5, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_6, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_5, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CARAJACKED_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CARAJACKED_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CARAJACKED_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CARAJACKED_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_MUGGED_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_MUGGED_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_MUGGED_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_5, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_6, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_SHOCKED_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_SHOCKED_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_SHOCKED_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_SHOCKED_4, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_1, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_2, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_3, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_4, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_5, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_6, + SFX_WHITE_WORKER_MALE_VOICE_1_DODGE_1, + SFX_WHITE_WORKER_MALE_VOICE_1_DODGE_2, + SFX_WHITE_WORKER_MALE_VOICE_1_DODGE_3, + SFX_WHITE_WORKER_MALE_VOICE_1_DODGE_4, + SFX_WHITE_WORKER_MALE_VOICE_1_EYING_1, + SFX_WHITE_WORKER_MALE_VOICE_1_EYING_2, + SFX_WHITE_WORKER_MALE_VOICE_1_FIGHT_1, + SFX_WHITE_WORKER_MALE_VOICE_1_FIGHT_2, + SFX_WHITE_WORKER_MALE_VOICE_1_FIGHT_3, + SFX_WHITE_WORKER_MALE_VOICE_1_GUN_PANIC_1, + SFX_WHITE_WORKER_MALE_VOICE_1_GUN_PANIC_2, + SFX_WHITE_WORKER_MALE_VOICE_1_GUN_PANIC_3, + SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_STEWARD_MALE_VOICE_1_CHAT_1, + SFX_STEWARD_MALE_VOICE_1_CHAT_2, + SFX_STEWARD_MALE_VOICE_1_CHAT_3, + SFX_STEWARD_MALE_VOICE_1_CHAT_4, + SFX_STEWARD_MALE_VOICE_1_DODGE_1, + SFX_STEWARD_MALE_VOICE_1_DODGE_2, + SFX_STEWARD_MALE_VOICE_1_DODGE_3, + SFX_STEWARD_MALE_VOICE_1_FIGHT_1, + SFX_STEWARD_MALE_VOICE_1_FIGHT_2, + SFX_STEWARD_MALE_VOICE_1_FIGHT_3, + SFX_STEWARD_MALE_VOICE_1_FIGHT_4, + SFX_STEWARD_MALE_VOICE_1_GUN_PANIC_1, + SFX_STEWARD_MALE_VOICE_1_GUN_PANIC_2, + SFX_STEWARD_MALE_VOICE_1_GUN_PANIC_3, + SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_STEWARD_FEMALE_VOICE_1_CHAT_1, + SFX_STEWARD_FEMALE_VOICE_1_CHAT_2, + SFX_STEWARD_FEMALE_VOICE_1_CHAT_3, + SFX_STEWARD_FEMALE_VOICE_1_CHAT_4, + SFX_STEWARD_FEMALE_VOICE_1_CHAT_5, + SFX_STEWARD_FEMALE_VOICE_1_DODGE_1, + SFX_STEWARD_FEMALE_VOICE_1_DODGE_2, + SFX_STEWARD_FEMALE_VOICE_1_DODGE_3, + SFX_STEWARD_FEMALE_VOICE_1_DODGE_4, + SFX_STEWARD_FEMALE_VOICE_1_DODGE_5, + SFX_STEWARD_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_STEWARD_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_STEWARD_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_1, + SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_2, + SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_3, + SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_4, + SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_5, + SFX_STEWARD_FEMALE_VOICE_2_CHAT_1, + SFX_STEWARD_FEMALE_VOICE_2_CHAT_2, + SFX_STEWARD_FEMALE_VOICE_2_CHAT_3, + SFX_STEWARD_FEMALE_VOICE_2_CHAT_4, + SFX_STEWARD_FEMALE_VOICE_2_CHAT_5, + SFX_STEWARD_FEMALE_VOICE_2_DODGE_1, + SFX_STEWARD_FEMALE_VOICE_2_DODGE_2, + SFX_STEWARD_FEMALE_VOICE_2_DODGE_3, + SFX_STEWARD_FEMALE_VOICE_2_DODGE_4, + SFX_STEWARD_FEMALE_VOICE_2_DODGE_5, + SFX_STEWARD_FEMALE_VOICE_2_GUN_PANIC_1, + SFX_STEWARD_FEMALE_VOICE_2_GUN_PANIC_2, + SFX_STEWARD_FEMALE_VOICE_2_GUN_PANIC_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_4, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_5, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_6, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_4, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_5, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_6, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_7, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_4, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_5, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_6, + SFX_CHINATOWN_MALE_OLD_VOICE_1_EYING_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_EYING_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_EYING_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_4, + SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_5, + SFX_CHINATOWN_MALE_OLD_VOICE_1_GUN_PANIC_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_GUN_PANIC_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_GUN_PANIC_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CARJACKED_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CARJACKED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_6, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_7, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_6, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CARJACKED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CARJACKED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_MUGGED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_MUGGED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_SHOCKED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_SHOCKED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_SHOCKED_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_SHOCKED_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_6, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_7, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_6, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_GUN_PANIC_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_GUN_PANIC_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_GUN_PANIC_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_GUN_PANIC_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CARJACKED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CARJACKED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_MUGGED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_MUGGED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_RUN_FROM_FIGHT_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_RUN_FROM_FIGHT_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_RUN_FROM_FIGHT_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_RUN_FROM_FIGHT_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_SHOCKED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_SHOCKED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_SHOCKED_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_SHOCKED_4, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_1, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_2, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_3, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_4, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_5, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_6, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_7, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_8, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_1, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_2, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_3, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_4, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_5, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_6, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_7, + SFX_BLACK_FAT_MALE_VOICE_1_CARJACKED_1, + SFX_BLACK_FAT_MALE_VOICE_1_CARJACKED_2, + SFX_BLACK_FAT_MALE_VOICE_1_CARJACKED_3, + SFX_BLACK_FAT_MALE_VOICE_1_CARJACKED_4, + SFX_BLACK_FAT_MALE_VOICE_1_LOST_1, + SFX_BLACK_FAT_MALE_VOICE_1_LOST_2, + SFX_BLACK_FAT_MALE_VOICE_1_LOST_3, + SFX_BLACK_FAT_MALE_VOICE_1_MUGGED_1, + SFX_BLACK_FAT_MALE_VOICE_1_MUGGED_2, + SFX_BLACK_FAT_MALE_VOICE_1_MUGGED_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_7, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_4, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_5, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_6, + SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_4, + SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_5, + SFX_BLACK_PROJECT_MALE_VOICE_1_EYING_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_EYING_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_EYING_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_4, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_5, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_6, + SFX_BLACK_PROJECT_MALE_VOICE_1_GUN_COOL_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_GUN_COOL_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_GUN_COOL_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_CARJACKED_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_CARJACKED_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_MUGGED_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_MUGGED_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_7, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_4, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_5, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_6, + SFX_BLACK_PROJECT_MALE_VOICE_2_DODGE_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_DODGE_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_DODGE_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_DODGE_4, + SFX_BLACK_PROJECT_MALE_VOICE_2_DODGE_5, + SFX_BLACK_PROJECT_MALE_VOICE_2_EYING_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_EYING_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_EYING_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_4, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_5, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_6, + SFX_BLACK_PROJECT_MALE_VOICE_2_GUN_COOL_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_GUN_COOL_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_GUN_COOL_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_CARJACKED_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_CARJACKED_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_MUGGED_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_MUGGED_2, + SFX_BLACK_WORKER_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_WORKER_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_WORKER_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_WORKER_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_WORKER_MALE_VOICE_1_CHAT_1, + SFX_BLACK_WORKER_MALE_VOICE_1_CHAT_2, + SFX_BLACK_WORKER_MALE_VOICE_1_CHAT_3, + SFX_BLACK_WORKER_MALE_VOICE_1_CHAT_4, + SFX_BLACK_WORKER_MALE_VOICE_1_DODGE_1, + SFX_BLACK_WORKER_MALE_VOICE_1_DODGE_2, + SFX_BLACK_WORKER_MALE_VOICE_1_DODGE_3, + SFX_BLACK_WORKER_MALE_VOICE_1_EYING_1, + SFX_BLACK_WORKER_MALE_VOICE_1_EYING_2, + SFX_BLACK_WORKER_MALE_VOICE_1_EYING_3, + SFX_BLACK_WORKER_MALE_VOICE_1_FIGHT_1, + SFX_BLACK_WORKER_MALE_VOICE_1_FIGHT_2, + SFX_BLACK_WORKER_MALE_VOICE_1_FIGHT_3, + SFX_BLACK_WORKER_MALE_VOICE_1_GUN_PANIC_1, + SFX_BLACK_WORKER_MALE_VOICE_1_GUN_PANIC_2, + SFX_BLACK_WORKER_MALE_VOICE_1_GUN_PANIC_3, + SFX_BLACK_WORKER_MALE_VOICE_1_GUN_PANIC_4, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_1, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_2, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_3, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_4, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_5, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_6, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_7, + SFX_SHOPPER_VOICE_1_CHAT_1, + SFX_SHOPPER_VOICE_1_CHAT_2, + SFX_SHOPPER_VOICE_1_CHAT_3, + SFX_SHOPPER_VOICE_1_CHAT_4, + SFX_SHOPPER_VOICE_1_CHAT_5, + SFX_SHOPPER_VOICE_1_CHAT_6, + SFX_SHOPPER_VOICE_1_CHAT_7, + SFX_SHOPPER_VOICE_1_DODGE_1, + SFX_SHOPPER_VOICE_1_DODGE_2, + SFX_SHOPPER_VOICE_1_DODGE_3, + SFX_SHOPPER_VOICE_1_DODGE_4, + SFX_SHOPPER_VOICE_1_DODGE_5, + SFX_SHOPPER_VOICE_1_DODGE_6, + SFX_SHOPPER_VOICE_1_CARJACKED_1, + SFX_SHOPPER_VOICE_1_CARJACKED_2, + SFX_SHOPPER_VOICE_1_MUGGED_1, + SFX_SHOPPER_VOICE_1_MUGGED_2, + SFX_SHOPPER_VOICE_1_SHOCKED_1, + SFX_SHOPPER_VOICE_1_SHOCKED_2, + SFX_SHOPPER_VOICE_1_SHOCKED_3, + SFX_SHOPPER_VOICE_1_SHOCKED_4, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_1, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_2, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_3, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_4, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_5, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_6, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_7, + SFX_SHOPPER_VOICE_2_CHAT_1, + SFX_SHOPPER_VOICE_2_CHAT_2, + SFX_SHOPPER_VOICE_2_CHAT_3, + SFX_SHOPPER_VOICE_2_CHAT_4, + SFX_SHOPPER_VOICE_2_CHAT_5, + SFX_SHOPPER_VOICE_2_CHAT_6, + SFX_SHOPPER_VOICE_2_CHAT_7, + SFX_SHOPPER_VOICE_2_DODGE_1, + SFX_SHOPPER_VOICE_2_DODGE_2, + SFX_SHOPPER_VOICE_2_DODGE_3, + SFX_SHOPPER_VOICE_2_DODGE_4, + SFX_SHOPPER_VOICE_2_DODGE_5, + SFX_SHOPPER_VOICE_2_DODGE_6, + SFX_SHOPPER_VOICE_2_CARJACKED_1, + SFX_SHOPPER_VOICE_2_CARJACKED_2, + SFX_SHOPPER_VOICE_2_MUGGED_1, + SFX_SHOPPER_VOICE_2_MUGGED_2, + SFX_SHOPPER_VOICE_2_SHOCKED_1, + SFX_SHOPPER_VOICE_2_SHOCKED_2, + SFX_SHOPPER_VOICE_2_SHOCKED_3, + SFX_SHOPPER_VOICE_2_SHOCKED_4, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_1, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_2, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_3, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_4, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_5, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_6, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_7, + SFX_SHOPPER_VOICE_3_CHAT_1, + SFX_SHOPPER_VOICE_3_CHAT_2, + SFX_SHOPPER_VOICE_3_CHAT_3, + SFX_SHOPPER_VOICE_3_CHAT_4, + SFX_SHOPPER_VOICE_3_CHAT_5, + SFX_SHOPPER_VOICE_3_CHAT_6, + SFX_SHOPPER_VOICE_3_CHAT_7, + SFX_SHOPPER_VOICE_3_DODGE_1, + SFX_SHOPPER_VOICE_3_DODGE_2, + SFX_SHOPPER_VOICE_3_DODGE_3, + SFX_SHOPPER_VOICE_3_DODGE_4, + SFX_SHOPPER_VOICE_3_DODGE_5, + SFX_SHOPPER_VOICE_3_DODGE_6, + SFX_SHOPPER_VOICE_3_CARJACKED_1, + SFX_SHOPPER_VOICE_3_CARJACKED_2, + SFX_SHOPPER_VOICE_3_MUGGED_1, + SFX_SHOPPER_VOICE_3_MUGGED_2, + SFX_SHOPPER_VOICE_3_SHOCKED_1, + SFX_SHOPPER_VOICE_3_SHOCKED_2, + SFX_SHOPPER_VOICE_3_SHOCKED_3, + SFX_SHOPPER_VOICE_3_SHOCKED_4, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_COLUMBIAN_MALE_VOICE_1_CHAT_1, + SFX_COLUMBIAN_MALE_VOICE_1_CHAT_2, + SFX_COLUMBIAN_MALE_VOICE_1_CHAT_3, + SFX_COLUMBIAN_MALE_VOICE_1_CHAT_4, + SFX_COLUMBIAN_MALE_VOICE_1_CHAT_5, + SFX_COLUMBIAN_MALE_VOICE_1_DODGE_1, + SFX_COLUMBIAN_MALE_VOICE_1_DODGE_2, + SFX_COLUMBIAN_MALE_VOICE_1_DODGE_3, + SFX_COLUMBIAN_MALE_VOICE_1_DODGE_4, + SFX_COLUMBIAN_MALE_VOICE_1_DODGE_5, + SFX_COLUMBIAN_MALE_VOICE_1_EYING_1, + SFX_COLUMBIAN_MALE_VOICE_1_EYING_2, + SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_1, + SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_2, + SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_3, + SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_4, + SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_5, + SFX_COLUMBIAN_MALE_VOICE_1_CARJACKED_1, + SFX_COLUMBIAN_MALE_VOICE_1_CARJACKED_2, + SFX_COLUMBIAN_MALE_VOICE_1_CARJACKING_1, + SFX_COLUMBIAN_MALE_VOICE_1_CARJACKING_2, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_COLUMBIAN_MALE_VOICE_2_CHAT_1, + SFX_COLUMBIAN_MALE_VOICE_2_CHAT_2, + SFX_COLUMBIAN_MALE_VOICE_2_CHAT_3, + SFX_COLUMBIAN_MALE_VOICE_2_CHAT_4, + SFX_COLUMBIAN_MALE_VOICE_2_CHAT_5, + SFX_COLUMBIAN_MALE_VOICE_2_DODGE_1, + SFX_COLUMBIAN_MALE_VOICE_2_DODGE_2, + SFX_COLUMBIAN_MALE_VOICE_2_DODGE_3, + SFX_COLUMBIAN_MALE_VOICE_2_DODGE_4, + SFX_COLUMBIAN_MALE_VOICE_2_DODGE_5, + SFX_COLUMBIAN_MALE_VOICE_2_EYING_1, + SFX_COLUMBIAN_MALE_VOICE_2_EYING_2, + SFX_COLUMBIAN_MALE_VOICE_2_FIGHT_1, + SFX_COLUMBIAN_MALE_VOICE_2_FIGHT_2, + SFX_COLUMBIAN_MALE_VOICE_2_FIGHT_3, + SFX_COLUMBIAN_MALE_VOICE_2_FIGHT_4, + SFX_COLUMBIAN_MALE_VOICE_2_FIGHT_5, + SFX_COLUMBIAN_MALE_VOICE_2_CARJACKED_1, + SFX_COLUMBIAN_MALE_VOICE_2_CARJACKED_2, + SFX_COLUMBIAN_MALE_VOICE_2_CARJACKING_1, + SFX_COLUMBIAN_MALE_VOICE_2_CARJACKING_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_3, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_4, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_5, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_6, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_7, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_3, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_4, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_5, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_6, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CARJACKED_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CARJACKED_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_MUGGED_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_MUGGED_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_SHOCKED_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_SHOCKED_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_SHOCKED_3, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_SHOCKED_4, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_1, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_2, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_3, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_4, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_5, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_6, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_1, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_2, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_3, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_4, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_5, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_MUGGED_1, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_MUGGED_2, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_SHOCKED_1, + SFX_GENERIC_FEMALE_DEATH_1, + SFX_GENERIC_FEMALE_DEATH_2, + SFX_GENERIC_FEMALE_DEATH_3, + SFX_GENERIC_FEMALE_DEATH_4, + SFX_GENERIC_FEMALE_DEATH_5, + SFX_GENERIC_FEMALE_DEATH_6, + SFX_GENERIC_FEMALE_DEATH_7, + SFX_GENERIC_FEMALE_DEATH_8, + SFX_GENERIC_FEMALE_DEATH_9, + SFX_GENERIC_FEMALE_DEATH_10, + SFX_GENERIC_FEMALE_FIRE_1, + SFX_GENERIC_FEMALE_FIRE_2, + SFX_GENERIC_FEMALE_FIRE_3, + SFX_GENERIC_FEMALE_FIRE_4, + SFX_GENERIC_FEMALE_FIRE_5, + SFX_GENERIC_FEMALE_FIRE_6, + SFX_GENERIC_FEMALE_FIRE_7, + SFX_GENERIC_FEMALE_FIRE_8, + SFX_GENERIC_FEMALE_FIRE_9, + SFX_GENERIC_FEMALE_GRUNT_1, + SFX_GENERIC_FEMALE_GRUNT_2, + SFX_GENERIC_FEMALE_GRUNT_3, + SFX_GENERIC_FEMALE_GRUNT_4, + SFX_GENERIC_FEMALE_GRUNT_5, + SFX_GENERIC_FEMALE_GRUNT_6, + SFX_GENERIC_FEMALE_GRUNT_7, + SFX_GENERIC_FEMALE_GRUNT_8, + SFX_GENERIC_FEMALE_GRUNT_9, + SFX_GENERIC_FEMALE_GRUNT_10, + SFX_GENERIC_FEMALE_GRUNT_11, + SFX_GENERIC_FEMALE_PANIC_1, + SFX_GENERIC_FEMALE_PANIC_2, + SFX_GENERIC_FEMALE_PANIC_3, + SFX_GENERIC_FEMALE_PANIC_4, + SFX_GENERIC_FEMALE_PANIC_5, + SFX_GENERIC_FEMALE_PANIC_6, + SFX_GENERIC_FEMALE_PANIC_7, + SFX_GENERIC_FEMALE_PANIC_8, + SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_1, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_2, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_3, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_4, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_5, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_6, + SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_1, + SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_2, + SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_3, + SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_4, + SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_5, + SFX_BLACK_CRIMINAL_VOICE_1_GUN_COOL_1, + SFX_BLACK_CRIMINAL_VOICE_1_GUN_COOL_2, + SFX_BLACK_CRIMINAL_VOICE_1_GUN_COOL_3, + SFX_BLACK_CRIMINAL_VOICE_1_GUN_COOL_4, + SFX_BLACK_CRIMINAL_VOICE_1_CARJACKING_1, + SFX_BLACK_CRIMINAL_VOICE_1_MUGGING_1, + SFX_BLACK_CRIMINAL_VOICE_1_MUGGING_2, + SFX_WHITE_CRIMINAL_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_CRIMINAL_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_CRIMINAL_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_CRIMINAL_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_CRIMINAL_VOICE_1_DODGE_1, + SFX_WHITE_CRIMINAL_VOICE_1_DODGE_2, + SFX_WHITE_CRIMINAL_VOICE_1_DODGE_3, + SFX_WHITE_CRIMINAL_VOICE_1_DODGE_4, + SFX_WHITE_CRIMINAL_VOICE_1_DODGE_5, + SFX_WHITE_CRIMINAL_VOICE_1_FIGHT_1, + SFX_WHITE_CRIMINAL_VOICE_1_FIGHT_2, + SFX_WHITE_CRIMINAL_VOICE_1_FIGHT_3, + SFX_WHITE_CRIMINAL_VOICE_1_FIGHT_4, + SFX_WHITE_CRIMINAL_VOICE_1_GUN_COOL_1, + SFX_WHITE_CRIMINAL_VOICE_1_GUN_COOL_2, + SFX_WHITE_CRIMINAL_VOICE_1_GUN_COOL_3, + SFX_WHITE_CRIMINAL_VOICE_1_CARJACKING_1, + SFX_WHITE_CRIMINAL_VOICE_1_MUGGING_1, + SFX_WHITE_CRIMINAL_VOICE_1_MUGGING_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_4, + SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_5, + SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_4, + SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_5, + SFX_BUSINESS_MALE_OLD_VOICE_1_DODGE_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_DODGE_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_DODGE_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_DODGE_4, + SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_4, + SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_5, + SFX_BUSINESS_MALE_OLD_VOICE_1_GUN_PANIC_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_GUN_PANIC_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_GUN_PANIC_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_CARJACKED_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_CARJACKED_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_MUGGED_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_MUGGED_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_4, + SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_5, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_7, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_3, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_4, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_5, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_6, + SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_3, + SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_4, + SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_5, + SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_3, + SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_4, + SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_5, + SFX_LITTLE_ITALY_MALE_VOICE_1_GUN_PANIC_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_GUN_PANIC_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_GUN_PANIC_3, + SFX_LITTLE_ITALY_MALE_VOICE_1_CARJACKED_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_CARJACKED_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_MUGGED_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_MUGGED_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_7, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_3, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_4, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_5, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_6, + SFX_LITTLE_ITALY_MALE_VOICE_2_DODGE_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_DODGE_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_DODGE_3, + SFX_LITTLE_ITALY_MALE_VOICE_2_DODGE_4, + SFX_LITTLE_ITALY_MALE_VOICE_2_DODGE_5, + SFX_LITTLE_ITALY_MALE_VOICE_2_FIGHT_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_FIGHT_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_FIGHT_3, + SFX_LITTLE_ITALY_MALE_VOICE_2_FIGHT_4, + SFX_LITTLE_ITALY_MALE_VOICE_2_FIGHT_5, + SFX_LITTLE_ITALY_MALE_VOICE_2_GUN_PANIC_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_GUN_PANIC_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_GUN_PANIC_3, + SFX_LITTLE_ITALY_MALE_VOICE_2_CARJACKED_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_CARJACKED_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_MUGGED_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_MUGGED_2, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_7, + SFX_TRIAD_MALE_VOICE_1_CHAT_1, + SFX_TRIAD_MALE_VOICE_1_CHAT_2, + SFX_TRIAD_MALE_VOICE_1_CHAT_3, + SFX_TRIAD_MALE_VOICE_1_CHAT_4, + SFX_TRIAD_MALE_VOICE_1_CHAT_5, + SFX_TRIAD_MALE_VOICE_1_CHAT_6, + SFX_TRIAD_MALE_VOICE_1_CHAT_7, + SFX_TRIAD_MALE_VOICE_1_CHAT_8, + SFX_TRIAD_MALE_VOICE_1_DODGE_1, + SFX_TRIAD_MALE_VOICE_1_DODGE_2, + SFX_TRIAD_MALE_VOICE_1_DODGE_3, + SFX_TRIAD_MALE_VOICE_1_DODGE_4, + SFX_TRIAD_MALE_VOICE_1_EYING_1, + SFX_TRIAD_MALE_VOICE_1_EYING_2, + SFX_TRIAD_MALE_VOICE_1_EYING_3, + SFX_TRIAD_MALE_VOICE_1_FIGHT_1, + SFX_TRIAD_MALE_VOICE_1_FIGHT_2, + SFX_TRIAD_MALE_VOICE_1_FIGHT_3, + SFX_TRIAD_MALE_VOICE_1_FIGHT_4, + SFX_TRIAD_MALE_VOICE_1_FIGHT_5, + SFX_TRIAD_MALE_VOICE_1_GUN_COOL_1, + SFX_TRIAD_MALE_VOICE_1_GUN_COOL_2, + SFX_TRIAD_MALE_VOICE_1_GUN_COOL_3, + SFX_TRIAD_MALE_VOICE_1_CARJACKED_1, + SFX_TRIAD_MALE_VOICE_1_CARJACKED_2, + SFX_TRIAD_MALE_VOICE_1_CARJACKING_1, + SFX_TRIAD_MALE_VOICE_1_CARJACKING_2, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_MAFIA_MALE_VOICE_1_CHAT_1, + SFX_MAFIA_MALE_VOICE_1_CHAT_2, + SFX_MAFIA_MALE_VOICE_1_CHAT_3, + SFX_MAFIA_MALE_VOICE_1_CHAT_4, + SFX_MAFIA_MALE_VOICE_1_CHAT_5, + SFX_MAFIA_MALE_VOICE_1_CHAT_6, + SFX_MAFIA_MALE_VOICE_1_CHAT_7, + SFX_MAFIA_MALE_VOICE_1_DODGE_1, + SFX_MAFIA_MALE_VOICE_1_DODGE_2, + SFX_MAFIA_MALE_VOICE_1_DODGE_3, + SFX_MAFIA_MALE_VOICE_1_DODGE_4, + SFX_MAFIA_MALE_VOICE_1_DODGE_5, + SFX_MAFIA_MALE_VOICE_1_EYING_1, + SFX_MAFIA_MALE_VOICE_1_EYING_2, + SFX_MAFIA_MALE_VOICE_1_EYING_3, + SFX_MAFIA_MALE_VOICE_1_FIGHT_1, + SFX_MAFIA_MALE_VOICE_1_FIGHT_2, + SFX_MAFIA_MALE_VOICE_1_FIGHT_3, + SFX_MAFIA_MALE_VOICE_1_FIGHT_4, + SFX_MAFIA_MALE_VOICE_1_FIGHT_5, + SFX_MAFIA_MALE_VOICE_1_CARJACKED_1, + SFX_MAFIA_MALE_VOICE_1_CARJACKED_2, + SFX_MAFIA_MALE_VOICE_1_CARJACKING_1, + SFX_MAFIA_MALE_VOICE_1_CARJACKING_2, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_MAFIA_MALE_VOICE_2_CHAT_1, + SFX_MAFIA_MALE_VOICE_2_CHAT_2, + SFX_MAFIA_MALE_VOICE_2_CHAT_3, + SFX_MAFIA_MALE_VOICE_2_CHAT_4, + SFX_MAFIA_MALE_VOICE_2_CHAT_5, + SFX_MAFIA_MALE_VOICE_2_CHAT_6, + SFX_MAFIA_MALE_VOICE_2_CHAT_7, + SFX_MAFIA_MALE_VOICE_2_DODGE_1, + SFX_MAFIA_MALE_VOICE_2_DODGE_2, + SFX_MAFIA_MALE_VOICE_2_DODGE_3, + SFX_MAFIA_MALE_VOICE_2_DODGE_4, + SFX_MAFIA_MALE_VOICE_2_DODGE_5, + SFX_MAFIA_MALE_VOICE_2_EYING_1, + SFX_MAFIA_MALE_VOICE_2_EYING_2, + SFX_MAFIA_MALE_VOICE_2_EYING_3, + SFX_MAFIA_MALE_VOICE_2_FIGHT_1, + SFX_MAFIA_MALE_VOICE_2_FIGHT_2, + SFX_MAFIA_MALE_VOICE_2_FIGHT_3, + SFX_MAFIA_MALE_VOICE_2_FIGHT_4, + SFX_MAFIA_MALE_VOICE_2_FIGHT_5, + SFX_MAFIA_MALE_VOICE_2_CARJACKED_1, + SFX_MAFIA_MALE_VOICE_2_CARJACKED_2, + SFX_MAFIA_MALE_VOICE_2_CARJACKING_1, + SFX_MAFIA_MALE_VOICE_2_CARJACKING_2, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_1, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_2, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_3, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_4, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_5, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_6, + SFX_MAFIA_MALE_VOICE_3_CHAT_1, + SFX_MAFIA_MALE_VOICE_3_CHAT_2, + SFX_MAFIA_MALE_VOICE_3_CHAT_3, + SFX_MAFIA_MALE_VOICE_3_CHAT_4, + SFX_MAFIA_MALE_VOICE_3_CHAT_5, + SFX_MAFIA_MALE_VOICE_3_CHAT_6, + SFX_MAFIA_MALE_VOICE_3_CHAT_7, + SFX_MAFIA_MALE_VOICE_3_DODGE_1, + SFX_MAFIA_MALE_VOICE_3_DODGE_2, + SFX_MAFIA_MALE_VOICE_3_DODGE_3, + SFX_MAFIA_MALE_VOICE_3_DODGE_4, + SFX_MAFIA_MALE_VOICE_3_DODGE_5, + SFX_MAFIA_MALE_VOICE_3_EYING_1, + SFX_MAFIA_MALE_VOICE_3_EYING_2, + SFX_MAFIA_MALE_VOICE_3_EYING_3, + SFX_MAFIA_MALE_VOICE_3_FIGHT_1, + SFX_MAFIA_MALE_VOICE_3_FIGHT_2, + SFX_MAFIA_MALE_VOICE_3_FIGHT_3, + SFX_MAFIA_MALE_VOICE_3_FIGHT_4, + SFX_MAFIA_MALE_VOICE_3_FIGHT_5, + SFX_MAFIA_MALE_VOICE_3_CARJACKED_1, + SFX_MAFIA_MALE_VOICE_3_CARJACKED_2, + SFX_MAFIA_MALE_VOICE_3_CARJACKING_1, + SFX_MAFIA_MALE_VOICE_3_CARJACKING_2, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_YAKUZA_MALE_VOICE_1_CHAT_1, + SFX_YAKUZA_MALE_VOICE_1_CHAT_2, + SFX_YAKUZA_MALE_VOICE_1_CHAT_3, + SFX_YAKUZA_MALE_VOICE_1_CHAT_4, + SFX_YAKUZA_MALE_VOICE_1_CHAT_5, + SFX_YAKUZA_MALE_VOICE_1_DODGE_1, + SFX_YAKUZA_MALE_VOICE_1_DODGE_2, + SFX_YAKUZA_MALE_VOICE_1_DODGE_3, + SFX_YAKUZA_MALE_VOICE_1_DODGE_4, + SFX_YAKUZA_MALE_VOICE_1_FIGHT_1, + SFX_YAKUZA_MALE_VOICE_1_FIGHT_2, + SFX_YAKUZA_MALE_VOICE_1_FIGHT_3, + SFX_YAKUZA_MALE_VOICE_1_FIGHT_4, + SFX_YAKUZA_MALE_VOICE_1_FIGHT_5, + SFX_YAKUZA_MALE_VOICE_1_CARJACKED_1, + SFX_YAKUZA_MALE_VOICE_1_CARJACKED_2, + SFX_YAKUZA_MALE_VOICE_1_CARJACKING_1, + SFX_YAKUZA_MALE_VOICE_1_CARJACKING_2, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_YAKUZA_MALE_VOICE_2_CHAT_1, + SFX_YAKUZA_MALE_VOICE_2_CHAT_2, + SFX_YAKUZA_MALE_VOICE_2_CHAT_3, + SFX_YAKUZA_MALE_VOICE_2_CHAT_4, + SFX_YAKUZA_MALE_VOICE_2_CHAT_5, + SFX_YAKUZA_MALE_VOICE_2_DODGE_1, + SFX_YAKUZA_MALE_VOICE_2_DODGE_2, + SFX_YAKUZA_MALE_VOICE_2_DODGE_3, + SFX_YAKUZA_MALE_VOICE_2_DODGE_4, + SFX_YAKUZA_MALE_VOICE_2_FIGHT_1, + SFX_YAKUZA_MALE_VOICE_2_FIGHT_2, + SFX_YAKUZA_MALE_VOICE_2_FIGHT_3, + SFX_YAKUZA_MALE_VOICE_2_FIGHT_4, + SFX_YAKUZA_MALE_VOICE_2_FIGHT_5, + SFX_YAKUZA_MALE_VOICE_2_CARJACKED_1, + SFX_YAKUZA_MALE_VOICE_2_CARJACKED_2, + SFX_YAKUZA_MALE_VOICE_2_CARJACKING_1, + SFX_YAKUZA_MALE_VOICE_2_CARJACKING_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_4, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_5, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_6, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_7, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_4, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_5, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_EYING_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_EYING_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_EYING_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_4, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_5, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_GUN_PANIC_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_GUN_PANIC_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_GUN_PANIC_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CARJACKED_1, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_1, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_2, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_3, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_4, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_5, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_6, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_1, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_2, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_3, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_4, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_5, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_6, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_7, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_1, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_2, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_3, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_4, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_5, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_6, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_1, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_2, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_3, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_4, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_5, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_6, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_7, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_1, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_2, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_3, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_4, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_5, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_6, + SFX_SECURITY_GUARD_VOICE_1_FIGHT_1, + SFX_SECURITY_GUARD_VOICE_1_FIGHT_2, + SFX_SECURITY_GUARD_VOICE_1_GUN_COOL_1, + SFX_SECURITY_GUARD_VOICE_1_GUN_COOL_2, + SFX_SECURITY_GUARD_VOICE_1_GUN_PANIC_1, + SFX_SECURITY_GUARD_VOICE_1_RUN_FROM_FIGHT_1, + SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_1, + SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_2, + SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_3, + SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_4, + SFX_BLACK_PROSTITUTE_VOICE_1_DODGE_1, + SFX_BLACK_PROSTITUTE_VOICE_1_DODGE_2, + SFX_BLACK_PROSTITUTE_VOICE_1_DODGE_3, + SFX_BLACK_PROSTITUTE_VOICE_1_MUGGED_1, + SFX_BLACK_PROSTITUTE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_PROSTITUTE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_PROSTITUTE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_PROSTITUTE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_PROSTITUTE_VOICE_1_FIGHT_1, + SFX_BLACK_PROSTITUTE_VOICE_1_FIGHT_2, + SFX_BLACK_PROSTITUTE_VOICE_1_FIGHT_3, + SFX_BLACK_PROSTITUTE_VOICE_1_FIGHT_4, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_1, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_2, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_3, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_4, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_5, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_6, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_7, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_8, + SFX_BLACK_PROSTITUTE_VOICE_1_GUN_COOL_1, + SFX_BLACK_PROSTITUTE_VOICE_1_GUN_COOL_2, + SFX_BLACK_PROSTITUTE_VOICE_1_GUN_COOL_3, + SFX_BLACK_PROSTITUTE_VOICE_1_GUN_COOL_4, + SFX_BLACK_PROSTITUTE_VOICE_2_CHAT_1, + SFX_BLACK_PROSTITUTE_VOICE_2_CHAT_2, + SFX_BLACK_PROSTITUTE_VOICE_2_CHAT_3, + SFX_BLACK_PROSTITUTE_VOICE_2_CHAT_4, + SFX_BLACK_PROSTITUTE_VOICE_2_DODGE_1, + SFX_BLACK_PROSTITUTE_VOICE_2_DODGE_2, + SFX_BLACK_PROSTITUTE_VOICE_2_DODGE_3, + SFX_BLACK_PROSTITUTE_VOICE_2_MUGGED_1, + SFX_BLACK_PROSTITUTE_VOICE_2_DRIVER_ABUSE_1, + SFX_BLACK_PROSTITUTE_VOICE_2_DRIVER_ABUSE_2, + SFX_BLACK_PROSTITUTE_VOICE_2_DRIVER_ABUSE_3, + SFX_BLACK_PROSTITUTE_VOICE_2_DRIVER_ABUSE_4, + SFX_BLACK_PROSTITUTE_VOICE_2_FIGHT_1, + SFX_BLACK_PROSTITUTE_VOICE_2_FIGHT_2, + SFX_BLACK_PROSTITUTE_VOICE_2_FIGHT_3, + SFX_BLACK_PROSTITUTE_VOICE_2_FIGHT_4, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_1, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_2, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_3, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_4, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_5, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_6, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_7, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_8, + SFX_BLACK_PROSTITUTE_VOICE_2_GUN_COOL_1, + SFX_BLACK_PROSTITUTE_VOICE_2_GUN_COOL_2, + SFX_BLACK_PROSTITUTE_VOICE_2_GUN_COOL_3, + SFX_BLACK_PROSTITUTE_VOICE_2_GUN_COOL_4, + SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_1, + SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_2, + SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_3, + SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_4, + SFX_WHITE_PROSTITUTE_VOICE_1_DODGE_1, + SFX_WHITE_PROSTITUTE_VOICE_1_DODGE_2, + SFX_WHITE_PROSTITUTE_VOICE_1_DODGE_3, + SFX_WHITE_PROSTITUTE_VOICE_1_MUGGED_1, + SFX_WHITE_PROSTITUTE_VOICE_1_MUGGED_2, + SFX_WHITE_PROSTITUTE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_PROSTITUTE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_PROSTITUTE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_PROSTITUTE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_PROSTITUTE_VOICE_1_FIGHT_1, + SFX_WHITE_PROSTITUTE_VOICE_1_FIGHT_2, + SFX_WHITE_PROSTITUTE_VOICE_1_FIGHT_3, + SFX_WHITE_PROSTITUTE_VOICE_1_FIGHT_4, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_1, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_2, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_3, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_4, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_5, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_6, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_7, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_8, + SFX_WHITE_PROSTITUTE_VOICE_2_CHAT_1, + SFX_WHITE_PROSTITUTE_VOICE_2_CHAT_2, + SFX_WHITE_PROSTITUTE_VOICE_2_CHAT_3, + SFX_WHITE_PROSTITUTE_VOICE_2_CHAT_4, + SFX_WHITE_PROSTITUTE_VOICE_2_DODGE_1, + SFX_WHITE_PROSTITUTE_VOICE_2_DODGE_2, + SFX_WHITE_PROSTITUTE_VOICE_2_DODGE_3, + SFX_WHITE_PROSTITUTE_VOICE_2_MUGGED_1, + SFX_WHITE_PROSTITUTE_VOICE_2_MUGGED_2, + SFX_WHITE_PROSTITUTE_VOICE_2_DRIVER_ABUSE_1, + SFX_WHITE_PROSTITUTE_VOICE_2_DRIVER_ABUSE_2, + SFX_WHITE_PROSTITUTE_VOICE_2_DRIVER_ABUSE_3, + SFX_WHITE_PROSTITUTE_VOICE_2_DRIVER_ABUSE_4, + SFX_WHITE_PROSTITUTE_VOICE_2_FIGHT_1, + SFX_WHITE_PROSTITUTE_VOICE_2_FIGHT_2, + SFX_WHITE_PROSTITUTE_VOICE_2_FIGHT_3, + SFX_WHITE_PROSTITUTE_VOICE_2_FIGHT_4, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_1, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_2, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_3, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_4, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_5, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_6, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_7, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_8, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_3, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_4, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_5, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_6, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_3, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_4, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_5, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_6, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_7, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CARJACKED_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CARJACKED_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_MUGGED_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_MUGGED_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_SHOCKED_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_SHOCKED_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_SHOCKED_3, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_SHOCKED_4, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_3, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_4, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_5, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_6, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_7, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_3, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_4, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_5, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_6, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CARJACKED_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CARJACKED_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_MUGGED_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_MUGGED_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_SHOCKED_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_SHOCKED_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_SHOCKED_3, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_SHOCKED_4, + SFX_GENERIC_MALE_DEATH_1, + SFX_GENERIC_MALE_DEATH_2, + SFX_GENERIC_MALE_DEATH_3, + SFX_GENERIC_MALE_DEATH_4, + SFX_GENERIC_MALE_DEATH_5, + SFX_GENERIC_MALE_DEATH_6, + SFX_GENERIC_MALE_DEATH_7, + SFX_GENERIC_MALE_DEATH_8, + SFX_GENERIC_MALE_FIRE_1, + SFX_GENERIC_MALE_FIRE_2, + SFX_GENERIC_MALE_FIRE_3, + SFX_GENERIC_MALE_FIRE_4, + SFX_GENERIC_MALE_FIRE_5, + SFX_GENERIC_MALE_FIRE_6, + SFX_GENERIC_MALE_FIRE_7, + SFX_GENERIC_MALE_FIRE_8, + SFX_GENERIC_MALE_GRUNT_1, + SFX_GENERIC_MALE_GRUNT_2, + SFX_GENERIC_MALE_GRUNT_3, + SFX_GENERIC_MALE_GRUNT_4, + SFX_GENERIC_MALE_GRUNT_5, + SFX_GENERIC_MALE_GRUNT_6, + SFX_GENERIC_MALE_GRUNT_7, + SFX_GENERIC_MALE_GRUNT_8, + SFX_GENERIC_MALE_GRUNT_9, + SFX_GENERIC_MALE_GRUNT_10, + SFX_GENERIC_MALE_GRUNT_11, + SFX_GENERIC_MALE_GRUNT_12, + SFX_GENERIC_MALE_GRUNT_13, + SFX_GENERIC_MALE_GRUNT_14, + SFX_GENERIC_MALE_GRUNT_15, + SFX_GENERIC_MALE_PANIC_1, + SFX_GENERIC_MALE_PANIC_2, + SFX_GENERIC_MALE_PANIC_3, + SFX_GENERIC_MALE_PANIC_4, + SFX_GENERIC_MALE_PANIC_5, + SFX_GENERIC_MALE_PANIC_6, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_1, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_2, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_3, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_4, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_5, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_6, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_7, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_8, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_9, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_1, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_2, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_3, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_4, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_5, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_6, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_7, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_8, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_9, + SFX_WHITE_FAT_MALE_VOICE_1_CARJACKED_1, + SFX_WHITE_FAT_MALE_VOICE_1_CARJACKED_2, + SFX_WHITE_FAT_MALE_VOICE_1_CARJACKED_3, + SFX_WHITE_FAT_MALE_VOICE_1_MUGGED_1, + SFX_WHITE_FAT_MALE_VOICE_1_MUGGED_2, + SFX_WHITE_FAT_MALE_VOICE_1_MUGGED_3, + SFX_WHITE_FAT_MALE_VOICE_1_LOST_1, + SFX_WHITE_FAT_MALE_VOICE_1_LOST_2, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_7, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_8, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_9, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_3, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_4, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_5, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_6, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_7, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_8, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_3, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_4, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_5, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_6, + SFX_WHITE_FAT_FEMALE_VOICE_1_CARJACKED_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_CARJACKED_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_MUGGED_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_MUGGED_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_LOST_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_LOST_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_8, + SFX_WHITE_FAT_FEMALE_VOICE_1_SHOCKED_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_SHOCKED_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_SHOCKED_3, + SFX_WHITE_FAT_FEMALE_VOICE_1_SHOCKED_4, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CHAT_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CHAT_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CHAT_3, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CHAT_4, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DODGE_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DODGE_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DODGE_3, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CARJACKED_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CARJACKED_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_MUGGED_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_8, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_SHOCKED_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_SHOCKED_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_RUN_FROM_FIGHT_2, + SFX_DIABLO_MALE_VOICE_1_CHAT_1, + SFX_DIABLO_MALE_VOICE_1_CHAT_2, + SFX_DIABLO_MALE_VOICE_1_CHAT_3, + SFX_DIABLO_MALE_VOICE_1_CHAT_4, + SFX_DIABLO_MALE_VOICE_1_CHAT_5, + SFX_DIABLO_MALE_VOICE_1_DODGE_1, + SFX_DIABLO_MALE_VOICE_1_DODGE_2, + SFX_DIABLO_MALE_VOICE_1_DODGE_3, + SFX_DIABLO_MALE_VOICE_1_DODGE_4, + SFX_DIABLO_MALE_VOICE_1_CARJACKED_1, + SFX_DIABLO_MALE_VOICE_1_CARJACKED_2, + SFX_DIABLO_MALE_VOICE_1_CARJACKING_1, + SFX_DIABLO_MALE_VOICE_1_CARJACKING_2, + SFX_DIABLO_MALE_VOICE_1_FIGHT_1, + SFX_DIABLO_MALE_VOICE_1_FIGHT_2, + SFX_DIABLO_MALE_VOICE_1_FIGHT_3, + SFX_DIABLO_MALE_VOICE_1_FIGHT_4, + SFX_DIABLO_MALE_VOICE_1_EYING_1, + SFX_DIABLO_MALE_VOICE_1_EYING_2, + SFX_DIABLO_MALE_VOICE_1_EYING_3, + SFX_DIABLO_MALE_VOICE_1_EYING_4, + SFX_DIABLO_MALE_VOICE_1_GUN_COOL_1, + SFX_DIABLO_MALE_VOICE_1_GUN_COOL_2, + SFX_DIABLO_MALE_VOICE_1_GUN_COOL_3, + SFX_DIABLO_MALE_VOICE_1_GUN_COOL_4, + SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_DIABLO_MALE_VOICE_2_CHAT_1, + SFX_DIABLO_MALE_VOICE_2_CHAT_2, + SFX_DIABLO_MALE_VOICE_2_CHAT_3, + SFX_DIABLO_MALE_VOICE_2_CHAT_4, + SFX_DIABLO_MALE_VOICE_2_CHAT_5, + SFX_DIABLO_MALE_VOICE_2_DODGE_1, + SFX_DIABLO_MALE_VOICE_2_DODGE_2, + SFX_DIABLO_MALE_VOICE_2_DODGE_3, + SFX_DIABLO_MALE_VOICE_2_DODGE_4, + SFX_DIABLO_MALE_VOICE_2_CARJACKED_1, + SFX_DIABLO_MALE_VOICE_2_CARJACKED_2, + SFX_DIABLO_MALE_VOICE_2_CARJACKING_1, + SFX_DIABLO_MALE_VOICE_2_CARJACKING_2, + SFX_DIABLO_MALE_VOICE_2_FIGHT_1, + SFX_DIABLO_MALE_VOICE_2_FIGHT_2, + SFX_DIABLO_MALE_VOICE_2_FIGHT_3, + SFX_DIABLO_MALE_VOICE_2_FIGHT_4, + SFX_DIABLO_MALE_VOICE_2_EYING_1, + SFX_DIABLO_MALE_VOICE_2_EYING_2, + SFX_DIABLO_MALE_VOICE_2_EYING_3, + SFX_DIABLO_MALE_VOICE_2_EYING_4, + SFX_DIABLO_MALE_VOICE_2_GUN_COOL_1, + SFX_DIABLO_MALE_VOICE_2_GUN_COOL_2, + SFX_DIABLO_MALE_VOICE_2_GUN_COOL_3, + SFX_DIABLO_MALE_VOICE_2_GUN_COOL_4, + SFX_DIABLO_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_DIABLO_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_DIABLO_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_DIABLO_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_DIABLO_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_AMMU_D, + SFX_AMMU_E, + SFX_AMMU_F, + +#ifdef GTA_PS2 + SFX_MISSION_LIB_A1, + SFX_MISSION_LIB_A2, + SFX_MISSION_LIB_A, + SFX_MISSION_LIB_B, + SFX_MISSION_LIB_C, + SFX_MISSION_LIB_D, + SFX_MISSION_L2_A, + SFX_MISSION_J4T_1, + SFX_MISSION_J4T_2, + SFX_MISSION_J4T_3, + SFX_MISSION_J4T_4, + SFX_MISSION_J4_A, + SFX_MISSION_J4_B, + SFX_MISSION_J4_C, + SFX_MISSION_J4_D, + SFX_MISSION_J4_E, + SFX_MISSION_J4_F, + SFX_MISSION_J6_1, + SFX_MISSION_J6_A, + SFX_MISSION_J6_B, + SFX_MISSION_J6_C, + SFX_MISSION_J6_D, + SFX_MISSION_T4_A, + SFX_MISSION_S1_A, + SFX_MISSION_S1_A1, + SFX_MISSION_S1_B, + SFX_MISSION_S1_C, + SFX_MISSION_S1_C1, + SFX_MISSION_S1_D, + SFX_MISSION_S1_E, + SFX_MISSION_S1_F, + SFX_MISSION_S1_G, + SFX_MISSION_S1_H, + SFX_MISSION_S1_I, + SFX_MISSION_S1_J, + SFX_MISSION_S1_K, + SFX_MISSION_S1_L, + SFX_MISSION_S3_A, + SFX_MISSION_S3_B, + SFX_MISSION_EL3_A, + SFX_MISSION_MF1_A, + SFX_MISSION_MF2_A, + SFX_MISSION_MF3_A, + SFX_MISSION_MF3_B, + SFX_MISSION_MF3_B1, + SFX_MISSION_MF3_C, + SFX_MISSION_MF4_A, + SFX_MISSION_MF4_B, + SFX_MISSION_MF4_C, + SFX_MISSION_A1_A, + SFX_MISSION_A3_A, + SFX_MISSION_A5_A, + SFX_MISSION_A4_A, + SFX_MISSION_A4_B, + SFX_MISSION_A4_C, + SFX_MISSION_A4_D, + SFX_MISSION_K1_A, + SFX_MISSION_K3_A, + SFX_MISSION_R1_A, + SFX_MISSION_R2_A, + SFX_MISSION_R2_B, + SFX_MISSION_R2_C, + SFX_MISSION_R2_D, + SFX_MISSION_R2_E, + SFX_MISSION_R2_F, + SFX_MISSION_R2_G, + SFX_MISSION_R2_H, + SFX_MISSION_R5_A, + SFX_MISSION_R6_A, + SFX_MISSION_R6_A1, + SFX_MISSION_R6_B, + SFX_MISSION_LO2_A, + SFX_MISSION_LO6_A, + SFX_MISSION_YD2_A, + SFX_MISSION_YD2_B, + SFX_MISSION_YD2_C, + SFX_MISSION_YD2_C1, + SFX_MISSION_YD2_D, + SFX_MISSION_YD2_E, + SFX_MISSION_YD2_F, + SFX_MISSION_YD2_G, + SFX_MISSION_YD2_H, + SFX_MISSION_YD2_ASS, + SFX_MISSION_YD2_OK, + SFX_MISSION_H5_A, + SFX_MISSION_H5_B, + SFX_MISSION_H5_C, + SFX_MISSION_DOOR_1, + SFX_MISSION_DOOR_2, + SFX_MISSION_DOOR_3, + SFX_MISSION_DOOR_4, + SFX_MISSION_DOOR_5, + SFX_MISSION_DOOR_6, + SFX_MISSION_T3_A, + SFX_MISSION_T3_B, + SFX_MISSION_T3_C, + SFX_MISSION_K1_B, + SFX_MISSION_AMMU_A, + SFX_MISSION_AMMU_B, + SFX_MISSION_AMMU_C, +#endif + TOTAL_AUDIO_SAMPLES, + NO_SAMPLE, + + // shorthands + SAMPLEBANK_START = SFX_CAR_HORN_JEEP, +#ifdef GTA_PS2 + SAMPLEBANK_END = SFX_INFO, + SAMPLEBANK_MAX = SFX_INFO + 1, +#else + SAMPLEBANK_END = SFX_PAGER, + SAMPLEBANK_MAX = SFX_PAGER + 1, +#endif + SAMPLEBANK_PED_START = SFX_COP_VOICE_1_ARREST_1, +#ifdef GTA_PS2 + SAMPLEBANK_PED_END = SFX_MISSION_AMMU_C, + SAMPLEBANK_PED_MAX = SFX_MISSION_AMMU_C + 1, +#else + SAMPLEBANK_PED_END = SFX_AMMU_F, + SAMPLEBANK_PED_MAX = SFX_AMMU_F + 1, +#endif +}; diff --git a/src/audio/AudioScriptObject.cpp b/src/audio/AudioScriptObject.cpp new file mode 100644 index 0000000..03efdea --- /dev/null +++ b/src/audio/AudioScriptObject.cpp @@ -0,0 +1,101 @@ +#include "common.h" + +#include "AudioScriptObject.h" +#include "Pools.h" +#include "DMAudio.h" +#include "SaveBuf.h" + +cAudioScriptObject::cAudioScriptObject() +{ + Reset(); +}; + +cAudioScriptObject::~cAudioScriptObject() +{ + Reset(); +}; + +void +cAudioScriptObject::Reset() +{ + AudioId = SCRIPT_SOUND_INVALID; + Posn = CVector(0.0f, 0.0f, 0.0f); + AudioEntity = AEHANDLE_NONE; +} + +void * +cAudioScriptObject::operator new(size_t sz) throw() +{ + return CPools::GetAudioScriptObjectPool()->New(); +} + +void * +cAudioScriptObject::operator new(size_t sz, int handle) throw() +{ + return CPools::GetAudioScriptObjectPool()->New(handle); +} + +void +cAudioScriptObject::operator delete(void *p, size_t sz) throw() +{ + CPools::GetAudioScriptObjectPool()->Delete((cAudioScriptObject *)p); +} + +void +cAudioScriptObject::operator delete(void *p, int handle) throw() +{ + CPools::GetAudioScriptObjectPool()->Delete((cAudioScriptObject *)p); +} + +void +cAudioScriptObject::LoadAllAudioScriptObjects(uint8 *buf, uint32 size) +{ + INITSAVEBUF + + CheckSaveHeader(buf, 'A', 'U', 'D', '\0', size - SAVE_HEADER_SIZE); + + int32 pool_size; + ReadSaveBuf(&pool_size, buf); + for (int32 i = 0; i < pool_size; i++) { + int32 handle; + ReadSaveBuf(&handle, buf); + cAudioScriptObject *p = new(handle) cAudioScriptObject; + assert(p != nil); + ReadSaveBuf(p, buf); + p->AudioEntity = DMAudio.CreateLoopingScriptObject(p); + } + + VALIDATESAVEBUF(size); +} + +void +cAudioScriptObject::SaveAllAudioScriptObjects(uint8 *buf, uint32 *size) +{ + INITSAVEBUF + + int32 pool_size = CPools::GetAudioScriptObjectPool()->GetNoOfUsedSpaces(); + *size = SAVE_HEADER_SIZE + sizeof(int32) + pool_size * (sizeof(cAudioScriptObject) + sizeof(int32)); + WriteSaveHeader(buf, 'A', 'U', 'D', '\0', *size - SAVE_HEADER_SIZE); + WriteSaveBuf(buf, pool_size); + + int32 i = CPools::GetAudioScriptObjectPool()->GetSize(); + while (i--) { + cAudioScriptObject *p = CPools::GetAudioScriptObjectPool()->GetSlot(i); + if (p != nil) { + WriteSaveBuf(buf, CPools::GetAudioScriptObjectPool()->GetIndex(p)); + WriteSaveBuf(buf, *p); + } + } + + VALIDATESAVEBUF(*size); +} + +void +PlayOneShotScriptObject(uint8 id, CVector const &pos) +{ + cAudioScriptObject *audioScriptObject = new cAudioScriptObject(); + audioScriptObject->Posn = pos; + audioScriptObject->AudioId = id; + audioScriptObject->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(audioScriptObject); +} diff --git a/src/audio/AudioScriptObject.h b/src/audio/AudioScriptObject.h new file mode 100644 index 0000000..b9a7e61 --- /dev/null +++ b/src/audio/AudioScriptObject.h @@ -0,0 +1,26 @@ +#pragma once + +class cAudioScriptObject +{ +public: + int16 AudioId; + CVector Posn; + int32 AudioEntity; + + cAudioScriptObject(); + ~cAudioScriptObject(); + + void Reset(); /// ok + + static void* operator new(size_t) throw(); + static void* operator new(size_t, int) throw(); + static void operator delete(void*, size_t) throw(); + static void operator delete(void*, int) throw(); + + static void LoadAllAudioScriptObjects(uint8 *buf, uint32 size); + static void SaveAllAudioScriptObjects(uint8 *buf, uint32 *size); +}; + +VALIDATE_SIZE(cAudioScriptObject, 20); + +extern void PlayOneShotScriptObject(uint8 id, CVector const &pos); \ No newline at end of file diff --git a/src/audio/DMAudio.cpp b/src/audio/DMAudio.cpp new file mode 100644 index 0000000..d88bfdd --- /dev/null +++ b/src/audio/DMAudio.cpp @@ -0,0 +1,340 @@ +#include "common.h" + +#include "DMAudio.h" +#include "MusicManager.h" +#include "AudioManager.h" +#include "AudioScriptObject.h" +#include "sampman.h" + +cDMAudio DMAudio; + +void +cDMAudio::Initialise(void) +{ + AudioManager.Initialise(); +} + +void +cDMAudio::Terminate(void) +{ + AudioManager.Terminate(); +} + +void +cDMAudio::Service(void) +{ + AudioManager.Service(); +} + +int32 +cDMAudio::CreateEntity(eAudioType type, void *UID) +{ + return AudioManager.CreateEntity(type, (CPhysical *)UID); +} + +void +cDMAudio::DestroyEntity(int32 audioEntity) +{ + AudioManager.DestroyEntity(audioEntity); +} + +bool8 +cDMAudio::GetEntityStatus(int32 audioEntity) +{ + return AudioManager.GetEntityStatus(audioEntity); +} + +void +cDMAudio::SetEntityStatus(int32 audioEntity, bool8 status) +{ + AudioManager.SetEntityStatus(audioEntity, status); +} + +void +cDMAudio::PlayOneShot(int32 audioEntity, uint16 oneShot, float volume) +{ + AudioManager.PlayOneShot(audioEntity, oneShot, volume); +} + +void +cDMAudio::DestroyAllGameCreatedEntities(void) +{ + AudioManager.DestroyAllGameCreatedEntities(); +} + +void +cDMAudio::SetMonoMode(bool8 mono) +{ + AudioManager.SetMonoMode(mono); +} + +void +cDMAudio::SetEffectsMasterVolume(uint8 volume) +{ + uint8 vol = volume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + AudioManager.SetEffectsMasterVolume(vol); +} + +void +cDMAudio::SetMusicMasterVolume(uint8 volume) +{ + uint8 vol = volume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + AudioManager.SetMusicMasterVolume(vol); +} + +void +cDMAudio::SetEffectsFadeVol(uint8 volume) +{ + uint8 vol = volume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + AudioManager.SetEffectsFadeVol(vol); +} + +void +cDMAudio::SetMusicFadeVol(uint8 volume) +{ + uint8 vol = volume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + AudioManager.SetMusicFadeVol(vol); +} + +uint8 +cDMAudio::GetNum3DProvidersAvailable(void) +{ + return AudioManager.GetNum3DProvidersAvailable(); +} + +char * +cDMAudio::Get3DProviderName(uint8 id) +{ + return AudioManager.Get3DProviderName(id); +} + +int8 +cDMAudio::GetCurrent3DProviderIndex(void) +{ + return AudioManager.GetCurrent3DProviderIndex(); +} + +int8 +cDMAudio::SetCurrent3DProvider(uint8 which) +{ + return AudioManager.SetCurrent3DProvider(which); +} + +void +cDMAudio::SetSpeakerConfig(int32 config) +{ + AudioManager.SetSpeakerConfig(config); +} + +bool8 +cDMAudio::IsMP3RadioChannelAvailable(void) +{ + return AudioManager.IsMP3RadioChannelAvailable(); +} + +void +cDMAudio::ReleaseDigitalHandle(void) +{ + AudioManager.ReleaseDigitalHandle(); +} + +void +cDMAudio::ReacquireDigitalHandle(void) +{ + AudioManager.ReacquireDigitalHandle(); +} + +void +cDMAudio::SetDynamicAcousticModelingStatus(bool8 status) +{ +#ifdef AUDIO_REFLECTIONS + AudioManager.SetDynamicAcousticModelingStatus(status); +#endif +} + +bool8 +cDMAudio::CheckForAnAudioFileOnCD(void) +{ + return AudioManager.CheckForAnAudioFileOnCD(); +} + +char +cDMAudio::GetCDAudioDriveLetter(void) +{ + return AudioManager.GetCDAudioDriveLetter(); +} + +bool8 +cDMAudio::IsAudioInitialised(void) +{ + return AudioManager.IsAudioInitialised(); +} + +void +cDMAudio::ResetPoliceRadio() +{ + AudioManager.ResetPoliceRadio(); +} + +void +cDMAudio::ReportCrime(eCrimeType crime, const CVector &pos) +{ + AudioManager.ReportCrime(crime, pos); +} + +int32 +cDMAudio::CreateLoopingScriptObject(cAudioScriptObject *scriptObject) +{ + int32 audioEntity = AudioManager.CreateEntity(AUDIOTYPE_SCRIPTOBJECT, scriptObject); + + if ( AEHANDLE_IS_OK(audioEntity) ) + AudioManager.SetEntityStatus(audioEntity, TRUE); + + return audioEntity; +} + +void +cDMAudio::DestroyLoopingScriptObject(int32 audioEntity) +{ + AudioManager.DestroyEntity(audioEntity); +} + +void +cDMAudio::CreateOneShotScriptObject(cAudioScriptObject *scriptObject) +{ + int32 audioEntity = AudioManager.CreateEntity(AUDIOTYPE_SCRIPTOBJECT, scriptObject); + + if ( AEHANDLE_IS_OK(audioEntity) ) + { + AudioManager.SetEntityStatus(audioEntity, TRUE); + AudioManager.PlayOneShot(audioEntity, scriptObject->AudioId, 0.0f); + } +} + +void +cDMAudio::PlaySuspectLastSeen(float x, float y, float z) +{ + AudioManager.PlaySuspectLastSeen(x, y, z); +} + +void +cDMAudio::ReportCollision(CEntity *entityA, CEntity *entityB, uint8 surfaceTypeA, uint8 surfaceTypeB, float collisionPower, float velocity) +{ + AudioManager.ReportCollision(entityA, entityB, surfaceTypeA, surfaceTypeB, collisionPower, velocity); +} + +void +cDMAudio::PlayFrontEndSound(uint16 frontend, uint32 volume) +{ + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, frontend, (float)volume); +} + +void +cDMAudio::PlayRadioAnnouncement(uint8 announcement) +{ + MusicManager.PlayAnnouncement(announcement); +} + +void +cDMAudio::PlayFrontEndTrack(uint8 track, bool8 frontendFlag) +{ + MusicManager.PlayFrontEndTrack(track, frontendFlag); +} + +void +cDMAudio::StopFrontEndTrack(void) +{ + MusicManager.StopFrontEndTrack(); +} + +void +cDMAudio::ResetTimers(uint32 time) +{ + AudioManager.ResetTimers(time); +} + +void +cDMAudio::ChangeMusicMode(uint8 mode) +{ + MusicManager.ChangeMusicMode(mode); +} + +void +cDMAudio::PreloadCutSceneMusic(uint8 track) +{ + MusicManager.PreloadCutSceneMusic(track); +} + +void +cDMAudio::PlayPreloadedCutSceneMusic(void) +{ + MusicManager.PlayPreloadedCutSceneMusic(); +} + +void +cDMAudio::StopCutSceneMusic(void) +{ + MusicManager.StopCutSceneMusic(); +} + +void +cDMAudio::PreloadMissionAudio(Const char *missionAudio) +{ + AudioManager.PreloadMissionAudio(missionAudio); +} + +uint8 +cDMAudio::GetMissionAudioLoadingStatus(void) +{ + return AudioManager.GetMissionAudioLoadingStatus(); +} + +void +cDMAudio::SetMissionAudioLocation(float x, float y, float z) +{ + AudioManager.SetMissionAudioLocation(x, y, z); +} + +void +cDMAudio::PlayLoadedMissionAudio(void) +{ + AudioManager.PlayLoadedMissionAudio(); +} + +bool8 +cDMAudio::IsMissionAudioSampleFinished(void) +{ + return AudioManager.IsMissionAudioSampleFinished(); +} + +void +cDMAudio::ClearMissionAudio(void) +{ + AudioManager.ClearMissionAudio(); +} + +uint8 +cDMAudio::GetRadioInCar(void) +{ + return MusicManager.GetRadioInCar(); +} + +void +cDMAudio::SetRadioInCar(uint32 radio) +{ + MusicManager.SetRadioInCar(radio); +} + +void +cDMAudio::SetRadioChannel(uint8 radio, int32 pos) +{ + MusicManager.SetRadioChannelByScript(radio, pos); +} diff --git a/src/audio/DMAudio.h b/src/audio/DMAudio.h new file mode 100644 index 0000000..9f42727 --- /dev/null +++ b/src/audio/DMAudio.h @@ -0,0 +1,91 @@ +#pragma once + +#include "audio_enums.h" +#include "soundlist.h" +#include "Crime.h" + +#define AEHANDLE_IS_FAILED(h) ((h)<0) +#define AEHANDLE_IS_OK(h) ((h)>=0) + +class cAudioScriptObject; +class CEntity; + +class cDMAudio +{ +public: + ~cDMAudio() + { } + + void Initialise(void); + void Terminate(void); + void Service(void); + + int32 CreateEntity(eAudioType type, void *UID); + void DestroyEntity(int32 audioEntity); + bool8 GetEntityStatus(int32 audioEntity); + void SetEntityStatus(int32 audioEntity, bool8 status); + void PlayOneShot(int32 audioEntity, uint16 oneShot, float volume); + void DestroyAllGameCreatedEntities(void); + + void SetMonoMode(bool8 mono); + void SetEffectsMasterVolume(uint8 volume); + void SetMusicMasterVolume(uint8 volume); + void SetEffectsFadeVol(uint8 volume); + void SetMusicFadeVol(uint8 volume); + + uint8 GetNum3DProvidersAvailable(void); + char *Get3DProviderName(uint8 id); + + int8 GetCurrent3DProviderIndex(void); + int8 SetCurrent3DProvider(uint8 which); + + void SetSpeakerConfig(int32 config); + + bool8 IsMP3RadioChannelAvailable(void); + + void ReleaseDigitalHandle(void); + void ReacquireDigitalHandle(void); + + void SetDynamicAcousticModelingStatus(bool8 status); + + bool8 CheckForAnAudioFileOnCD(void); + + char GetCDAudioDriveLetter(void); + bool8 IsAudioInitialised(void); + + void ResetPoliceRadio(); + void ReportCrime(eCrimeType crime, CVector const &pos); + + int32 CreateLoopingScriptObject(cAudioScriptObject *scriptObject); + void DestroyLoopingScriptObject(int32 audioEntity); + void CreateOneShotScriptObject(cAudioScriptObject *scriptObject); + + void PlaySuspectLastSeen(float x, float y, float z); + + void ReportCollision(CEntity *entityA, CEntity *entityB, uint8 surfaceTypeA, uint8 surfaceTypeB, float collisionPower, float velocity); + + void PlayFrontEndSound(uint16 frontend, uint32 volume); + void PlayRadioAnnouncement(uint8 announcement); + void PlayFrontEndTrack(uint8 track, bool8 frontendFlag); + void StopFrontEndTrack(void); + + void ResetTimers(uint32 time); + + void ChangeMusicMode(uint8 mode); + + void PreloadCutSceneMusic(uint8 track); + void PlayPreloadedCutSceneMusic(void); + void StopCutSceneMusic(void); + + void PreloadMissionAudio(Const char *missionAudio); + uint8 GetMissionAudioLoadingStatus(void); + void SetMissionAudioLocation(float x, float y, float z); + void PlayLoadedMissionAudio(void); + bool8 IsMissionAudioSampleFinished(void); + void ClearMissionAudio(void); + + uint8 GetRadioInCar(void); + void SetRadioInCar(uint32 radio); + void SetRadioChannel(uint8 radio, int32 pos); +}; +extern cDMAudio DMAudio; diff --git a/src/audio/MusicManager.cpp b/src/audio/MusicManager.cpp new file mode 100644 index 0000000..3f4ae48 --- /dev/null +++ b/src/audio/MusicManager.cpp @@ -0,0 +1,1026 @@ +#include "common.h" +#include +#include "soundlist.h" +#include "MusicManager.h" +#include "AudioManager.h" +#include "ControllerConfig.h" +#include "Camera.h" +#include "Font.h" +#include "Hud.h" +#include "ModelIndices.h" +#include "Replay.h" +#include "Pad.h" +#include "Text.h" +#include "Timer.h" +#include "World.h" +#include "sampman.h" + +#if !defined FIX_BUGS && (defined RADIO_SCROLL_TO_PREV_STATION || defined RADIO_OFF_TEXT) +static_assert(false, "RADIO_SCROLL_TO_PREV_STATION and RADIO_OFF_TEXT won't work correctly without FIX_BUGS"); +#endif + +cMusicManager MusicManager; +int32 gNumRetunePresses; +int32 gRetuneCounter; +bool8 bHasStarted; + +cMusicManager::cMusicManager() +{ + m_bIsInitialised = FALSE; + m_bDisabled = FALSE; + m_nMusicMode = MUSICMODE_DISABLED; + m_nNextTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + m_bFrontendTrackFinished = FALSE; + m_bPlayInFrontend = FALSE; + m_bSetNextStation = FALSE; + m_nAnnouncement = NO_TRACK; + m_bPreviousPlayerInCar = FALSE; + m_bPlayerInCar = FALSE; + m_bAnnouncementInProgress = FALSE; + m_bVerifyAmbienceTrackStartedToPlay = FALSE; + bHasStarted = FALSE; +} + +bool8 +cMusicManager::PlayerInCar() +{ + if(!FindPlayerVehicle()) + return FALSE; + + int32 State = FindPlayerPed()->m_nPedState; + + if(State == PED_DRAG_FROM_CAR || State == PED_EXIT_CAR || State == PED_ARRESTED) + return FALSE; + + if (!FindPlayerVehicle()) + return TRUE; + + if (FindPlayerVehicle()->GetStatus() == STATUS_WRECKED) + return FALSE; + + switch (FindPlayerVehicle()->GetModelIndex()) { + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_MRWHOOP: + case MI_PREDATOR: + case MI_TRAIN: + case MI_SPEEDER: + case MI_REEFER: + case MI_GHOST: return FALSE; + default: return TRUE; + } +} + +void +cMusicManager::DisplayRadioStationName() +{ + int8 pRetune; + int8 gStreamedSound; + int8 gRetuneCounter; + static wchar *pCurrentStation = nil; + static uint8 cDisplay = 0; + + if(!CTimer::GetIsPaused() && !TheCamera.m_WideScreenOn && PlayerInCar() && + !CReplay::IsPlayingBack()) { + if(m_bPlayerInCar && !m_bPreviousPlayerInCar) + pCurrentStation = nil; + +#ifdef FIX_BUGS + const int curRadio = GetCarTuning(); +#else + const int curRadio = m_nNextTrack; +#endif + +#ifdef RADIO_SCROLL_TO_PREV_STATION + if(gNumRetunePresses < 0) { + gStreamedSound = curRadio; + + gRetuneCounter = gNumRetunePresses; + pRetune = gStreamedSound; + + while(gRetuneCounter < 0) { + if(pRetune == HEAD_RADIO) { + pRetune = RADIO_OFF; + } else if(pRetune == RADIO_OFF || pRetune == NUM_RADIOS) { + pRetune = SampleManager.IsMP3RadioChannelAvailable() ? USERTRACK : USERTRACK - 1; + } else + pRetune--; + + ++gRetuneCounter; + } + } else +#endif + if(SampleManager.IsMP3RadioChannelAvailable()) { + gStreamedSound = curRadio; + + if(gStreamedSound == STREAMED_SOUND_CITY_AMBIENT || + gStreamedSound == STREAMED_SOUND_WATER_AMBIENT) { // which means OFF + gStreamedSound = NUM_RADIOS; + } else if(gStreamedSound > STREAMED_SOUND_RADIO_MP3_PLAYER) + return; + + pRetune = gNumRetunePresses + gStreamedSound; + +#ifdef FIX_BUGS + while(pRetune > NUM_RADIOS) + pRetune -= (NUM_RADIOS + 1); +#endif + if(pRetune == NUM_RADIOS) { + pRetune = RADIO_OFF; + } +#ifndef FIX_BUGS + else if(pRetune > NUM_RADIOS) { + pRetune = pRetune - (NUM_RADIOS + 1); + } +#endif + } else { + gStreamedSound = curRadio; + pRetune = gNumRetunePresses + gStreamedSound; + + if(pRetune >= USERTRACK) { + gRetuneCounter = gNumRetunePresses; + pRetune = curRadio; + + if(gStreamedSound == STREAMED_SOUND_WATER_AMBIENT) + pRetune = STREAMED_SOUND_CITY_AMBIENT; // which is RADIO_OFF + + while(gRetuneCounter) { + if(pRetune == RADIO_OFF) { + pRetune = HEAD_RADIO; + } else if(pRetune < USERTRACK) { + pRetune = pRetune + 1; + } + if(pRetune == USERTRACK) pRetune = RADIO_OFF; + + --gRetuneCounter; + } + } + } + + wchar *string; + + switch(pRetune) { + case HEAD_RADIO: string = TheText.Get("FEA_FM0"); break; + case DOUBLE_CLEF: string = TheText.Get("FEA_FM1"); break; + case JAH_RADIO: string = TheText.Get("FEA_FM2"); break; + case RISE_FM: string = TheText.Get("FEA_FM3"); break; + case LIPS_106: string = TheText.Get("FEA_FM4"); break; + case GAME_FM: string = TheText.Get("FEA_FM5"); break; + case MSX_FM: string = TheText.Get("FEA_FM6"); break; + case FLASHBACK: string = TheText.Get("FEA_FM7"); break; + case CHATTERBOX: string = TheText.Get("FEA_FM8"); break; + case USERTRACK: + if (!SampleManager.IsMP3RadioChannelAvailable()) + return; + string = TheText.Get("FEA_FM9"); break; +#ifdef RADIO_OFF_TEXT + case RADIO_OFF: { + extern wchar WideErrorString[]; + + string = TheText.Get("FEA_FMN"); + if(string == WideErrorString) { + pCurrentStation = nil; + return; + } + break; + } +#endif + default: return; + }; + + if(pCurrentStation != string || + m_nNextTrack == STREAMED_SOUND_RADIO_MP3_PLAYER && m_nPlayingTrack != STREAMED_SOUND_RADIO_MP3_PLAYER) { + pCurrentStation = string; + cDisplay = 60; + } else { + if(cDisplay == 0) return; +#ifdef FIX_BUGS + cDisplay -= CTimer::GetLogicalFramesPassed(); +#else + cDisplay--; +#endif + } + + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetCentreOn(); + // Reminder: Game doesn't have "scaling" at all, it just stretches, and it's team's decision here to not let centered text occupy all the screen. + // Disable ASPECT_RATIO_SCALE and it'll go back to default behaviour; stretching. + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(22.0f) + SCREEN_SCALE_Y(2.0f), pCurrentStation); +#else + CFont::PrintString(SCREEN_WIDTH / 2 + 2.0f, SCREEN_SCALE_Y(22.0f) + 2.0f, pCurrentStation); +#endif + + if(gNumRetunePresses) + CFont::SetColor(CRGBA(102, 133, 143, 255)); + else + CFont::SetColor(CRGBA(147, 196, 211, 255)); + + CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_SCALE_Y(22.0f), pCurrentStation); + CFont::DrawFonts(); + } +} + +bool8 +cMusicManager::Initialise() +{ + int pos; + + if (!IsInitialised()) { + time_t timevalue = time(0); + if (timevalue == -1) { + pos = AudioManager.m_anRandomTable[0]; + } else { + tm *pTm = localtime(&timevalue); + if (pTm->tm_sec == 0) + pTm->tm_sec = AudioManager.m_anRandomTable[0]; + if (pTm->tm_min == 0) + pTm->tm_min = AudioManager.m_anRandomTable[1]; + if (pTm->tm_hour == 0) + pTm->tm_hour = AudioManager.m_anRandomTable[2]; + if (pTm->tm_mday == 0) + pTm->tm_mday = AudioManager.m_anRandomTable[3]; + if (pTm->tm_mon == 0) + pTm->tm_mon = AudioManager.m_anRandomTable[4]; + if (pTm->tm_year == 0) + pTm->tm_year = AudioManager.m_anRandomTable[3]; + if (pTm->tm_wday == 0) + pTm->tm_wday = AudioManager.m_anRandomTable[2]; + pos = pTm->tm_yday + * pTm->tm_wday + * pTm->tm_year + * pTm->tm_mon + * pTm->tm_mday + * pTm->tm_hour * pTm->tm_hour + * pTm->tm_min * pTm->tm_min + * pTm->tm_sec * pTm->tm_sec * pTm->tm_sec * pTm->tm_sec; + } + + for (int i = 0; i < TOTAL_STREAMED_SOUNDS; i++) { + m_aTracks[i].m_nLength = SampleManager.GetStreamedFileLength(i); + m_aTracks[i].m_nPosition = pos * AudioManager.m_anRandomTable[i % 5] % m_aTracks[i].m_nLength; + m_aTracks[i].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + + m_bResetTimers = FALSE; + m_nResetTime = 0; + m_nTimer = m_nLastTrackServiceTime = CTimer::GetTimeInMillisecondsPauseMode(); + m_bDoTrackService = FALSE; + m_bIgnoreTimeDelay = FALSE; + m_bRadioSetByScript = FALSE; + m_nRadioStationScript = HEAD_RADIO; + m_nRadioPosition = -1; + m_nRadioInCar = NO_TRACK; + gNumRetunePresses = 0; + gRetuneCounter = 0; + m_bIsInitialised = TRUE; + } + return m_bIsInitialised; +} + +void +cMusicManager::Terminate() +{ + if (!IsInitialised()) return; + + if (SampleManager.IsStreamPlaying()) { + SampleManager.StopStreamedFile(); + m_nNextTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + } + m_bIsInitialised = FALSE; +} + +void +cMusicManager::ChangeMusicMode(uint8 mode) +{ + if (!IsInitialised()) return; + + uint8 mode2; + switch (mode) + { + case MUSICMODE_FRONTEND: + mode2 = MUSICMODE_FRONTEND; +#ifdef PAUSE_RADIO_IN_FRONTEND + // rewind those streams we weren't listening right now + for (uint32 i = STREAMED_SOUND_RADIO_HEAD; i < STREAMED_SOUND_CUTSCENE_LUIGI1_LG; i++) { + m_aTracks[i].m_nPosition = GetTrackStartPos(i); + m_aTracks[i].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } +#endif + break; + case MUSICMODE_GAME: mode2 = MUSICMODE_GAME; break; + case MUSICMODE_CUTSCENE: mode2 = MUSICMODE_CUTSCENE; break; + case MUSICMODE_DISABLE: mode2 = MUSICMODE_DISABLED; break; + default: return; + } + + if (mode2 != m_nMusicMode || mode == MUSICMODE_FRONTEND && mode2 == MUSICMODE_FRONTEND) { + switch (mode) + { + case MUSICMODE_FRONTEND: + case MUSICMODE_GAME: + case MUSICMODE_CUTSCENE: + case MUSICMODE_DISABLED: + if (SampleManager.IsStreamPlaying()) { + if (m_nNextTrack < TOTAL_STREAMED_SOUNDS) { + m_aTracks[m_nNextTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + SampleManager.StopStreamedFile(); + } + m_nNextTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + m_bFrontendTrackFinished = FALSE; + m_bPlayInFrontend = FALSE; + m_bSetNextStation = FALSE; + m_bPreviousPlayerInCar = FALSE; + m_bPlayerInCar = FALSE; + m_bAnnouncementInProgress = FALSE; + m_nTimer = m_nLastTrackServiceTime = CTimer::GetTimeInMillisecondsPauseMode(); + m_bDoTrackService = FALSE; + m_bIgnoreTimeDelay = TRUE; + m_bVerifyAmbienceTrackStartedToPlay = FALSE; + m_nMusicMode = mode2; + break; + default: return; + } + } +} + +uint8 +cMusicManager::GetRadioInCar(void) +{ + if (!m_bIsInitialised) return HEAD_RADIO; + if (PlayerInCar()) { + CVehicle *veh = FindPlayerVehicle(); + if (veh != nil){ + if (UsesPoliceRadio(veh)) { + if (m_nRadioInCar == NO_TRACK || (CReplay::IsPlayingBack() && !AudioManager.m_bIsPaused)) + return POLICE_RADIO; + return m_nRadioInCar; + } else return veh->m_nRadioStation; + } + } + + if (m_nRadioInCar == NO_TRACK || (CReplay::IsPlayingBack() && !AudioManager.m_bIsPaused)) + return RADIO_OFF; + return m_nRadioInCar; +} + +void +cMusicManager::SetRadioInCar(uint32 station) +{ + if (m_bIsInitialised) { + if (!PlayerInCar()) { + m_nRadioInCar = station; + return; + } + CVehicle *veh = FindPlayerVehicle(); + if (veh == nil) return; + if (UsesPoliceRadio(veh)) + m_nRadioInCar = station; + else + veh->m_nRadioStation = station; + } +} + +void +cMusicManager::SetRadioChannelByScript(uint8 station, int32 pos) +{ + if (m_bIsInitialised && station < RADIO_OFF) { + m_bRadioSetByScript = TRUE; + m_nRadioStationScript = station; + m_nRadioPosition = pos == -1 ? -1 : pos % m_aTracks[station].m_nLength; + } +} + + +void +cMusicManager::ResetMusicAfterReload() +{ + m_bRadioSetByScript = FALSE; + m_nRadioStationScript = 0; + m_nRadioPosition = -1; + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + m_bSetNextStation = FALSE; + gRetuneCounter = 0; + gNumRetunePresses = 0; +} + + +void +cMusicManager::ResetTimers(uint32 time) +{ + m_bResetTimers = TRUE; + m_nResetTime = time; +} + +void +cMusicManager::Service() +{ + if (m_bResetTimers) { + m_bResetTimers = FALSE; + m_nLastTrackServiceTime = m_nResetTime; + } + + if (!m_bIsInitialised || m_bDisabled) return; + + if (m_nMusicMode == MUSICMODE_CUTSCENE) { + SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, TRUE); + return; + } + + m_nTimer = CTimer::GetTimeInMillisecondsPauseMode(); + if (m_nTimer > (m_nLastTrackServiceTime + 2000) || m_bIgnoreTimeDelay) { + m_bIgnoreTimeDelay = FALSE; + m_bDoTrackService = TRUE; + m_nLastTrackServiceTime = m_nTimer; + } else m_bDoTrackService = FALSE; + + if (m_nNextTrack == NO_TRACK && SampleManager.IsStreamPlaying()) + SampleManager.StopStreamedFile(); + else switch (m_nMusicMode) { + case MUSICMODE_FRONTEND: ServiceFrontEndMode(); break; + case MUSICMODE_GAME: ServiceGameMode(); break; + } +} + +void +cMusicManager::ServiceFrontEndMode() +{ +#ifdef PAUSE_RADIO_IN_FRONTEND + // pause radio + for (uint32 i = STREAMED_SOUND_RADIO_HEAD; i < STREAMED_SOUND_CUTSCENE_LUIGI1_LG; i++) + m_aTracks[i].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); +#endif + + if (m_nNextTrack < TOTAL_STREAMED_SOUNDS) { + if (m_bFrontendTrackFinished) { + if (!SampleManager.IsStreamPlaying()) { + switch (m_nNextTrack) + { + case STREAMED_SOUND_MISSION_COMPLETED: + if (!AudioManager.m_bIsPaused) + ChangeMusicMode(MUSICMODE_GAME); + break; + case STREAMED_SOUND_GAME_COMPLETED: + ChangeMusicMode(MUSICMODE_GAME); + break; + default: + break; + } + m_nNextTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + } + } else if (bHasStarted) { + if (!SampleManager.IsStreamPlaying()) + SampleManager.StartStreamedFile(m_nNextTrack, 0); + } else { + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + if (!SampleManager.StartStreamedFile(m_nNextTrack, m_nNextTrack < NUM_RADIOS ? GetTrackStartPos(m_nNextTrack) : 0)) + return; + SampleManager.SetStreamedVolumeAndPan(100, 63, FALSE); + if (m_bPlayInFrontend) bHasStarted = TRUE; + else m_bFrontendTrackFinished = TRUE; + } + } + if (SampleManager.IsStreamPlaying()) + SampleManager.SetStreamedVolumeAndPan((CPad::GetPad(0)->bDisplayNoControllerMessage || CPad::GetPad(0)->bObsoleteControllerMessage) ? 0 : 100, 63, FALSE); +} + +void +cMusicManager::ServiceGameMode() +{ + bool8 bRadioOff = FALSE; + static int8 nFramesSinceCutsceneEnded = -1; + uint8 volume; + + m_bPreviousPlayerInCar = m_bPlayerInCar; + m_bPlayerInCar = PlayerInCar(); + m_nPlayingTrack = m_nNextTrack; + if (m_bPlayerInCar) { + if (FindPlayerPed() != nil + && !FindPlayerPed()->DyingOrDead() + && !CReplay::IsPlayingBack() + && FindPlayerVehicle() != nil + && !UsesPoliceRadio(FindPlayerVehicle())) { + + if (CPad::GetPad(0)->ChangeStationJustDown()) { + gRetuneCounter = 30; + gNumRetunePresses++; + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_CHANGE, 1.0f); + // This needs loop, and this is not the right place. Now done elsewhere. +#ifndef FIX_BUGS + if (SampleManager.IsMP3RadioChannelAvailable()) { + if (gNumRetunePresses > RADIO_OFF) + gNumRetunePresses -= RADIO_OFF; + } +#endif + } +#ifdef RADIO_SCROLL_TO_PREV_STATION + else if(!CPad::GetPad(0)->ArePlayerControlsDisabled() && (CPad::GetPad(0)->GetMouseWheelDownJustDown() || CPad::GetPad(0)->GetMouseWheelUpJustDown())) { + int scrollNext = ControlsManager.GetControllerKeyAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, MOUSE); + int scrollPrev = scrollNext == rsMOUSEWHEELUPBUTTON ? rsMOUSEWHEELDOWNBUTTON : scrollNext == rsMOUSEWHEELDOWNBUTTON ? rsMOUSEWHEELUPBUTTON : -1; + + if (scrollPrev != -1 && !ControlsManager.IsAnyVehicleActionAssignedToMouseKey(scrollPrev)) { + gRetuneCounter = 30; + gNumRetunePresses--; + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_CHANGE, 1.0f); + } + } +#endif + } + } else { + nFramesSinceCutsceneEnded = -1; + } + + if (AudioManager.m_bWasPaused) + m_bPreviousPlayerInCar = FALSE; + if (!m_bPlayerInCar) { + if (m_bPreviousPlayerInCar) { + if (m_nNextTrack != STREAMED_SOUND_RADIO_POLICE) + m_nRadioInCar = m_nNextTrack; + } + ServiceAmbience(); + return; + } + + if (m_bPreviousPlayerInCar) { + if (m_nAnnouncement < TOTAL_STREAMED_SOUNDS + && (m_nNextTrack < RADIO_OFF || m_bAnnouncementInProgress) + && ServiceAnnouncement()) + { + if (m_bAnnouncementInProgress) { + m_bSetNextStation = FALSE; + return; + } + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = GetCarTuning(); + } + if (SampleManager.IsMP3RadioChannelAvailable() + && m_nNextTrack != STREAMED_SOUND_RADIO_MP3_PLAYER + && ControlsManager.GetIsKeyboardKeyJustDown(rsF9)) + { + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = STREAMED_SOUND_RADIO_MP3_PLAYER; + if (FindPlayerVehicle() != nil) + FindPlayerVehicle()->m_nRadioStation = STREAMED_SOUND_RADIO_MP3_PLAYER; + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_CHANGE, 1.0f); + gRetuneCounter = 0; + gNumRetunePresses = 0; + m_bSetNextStation = FALSE; + } + // Because when you switch radio back and forth, gNumRetunePresses will be 0 but gRetuneCounter won't. +#ifdef RADIO_SCROLL_TO_PREV_STATION + if (gRetuneCounter != 0) { + if (gRetuneCounter > 1) gRetuneCounter--; + else if (gRetuneCounter == 1) gRetuneCounter = -1; + else if (gRetuneCounter == -1) { + m_bSetNextStation = TRUE; + gRetuneCounter = 0; + } + } +#else + if (gNumRetunePresses) { + if (gRetuneCounter != 0) gRetuneCounter--; + else m_bSetNextStation = TRUE; + } +#endif + if (gRetuneCounter) + AudioManager.DoPoliceRadioCrackle(); + if (m_bSetNextStation) { + m_bSetNextStation = FALSE; + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = GetNextCarTuning(); + if (m_nNextTrack == STREAMED_SOUND_CITY_AMBIENT || m_nNextTrack == STREAMED_SOUND_WATER_AMBIENT) + bRadioOff = TRUE; + + if (m_nPlayingTrack == STREAMED_SOUND_CITY_AMBIENT || m_nPlayingTrack == STREAMED_SOUND_WATER_AMBIENT) + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_CHANGE, 0.0f); + } + if (m_nNextTrack < RADIO_OFF) { + if (ChangeRadioChannel()) { + ServiceTrack(); + } else { + m_bPlayerInCar = FALSE; + if (FindPlayerVehicle()) + FindPlayerVehicle()->m_nRadioStation = m_nNextTrack; + m_nNextTrack = NO_TRACK; + } + if (CTimer::GetIsSlowMotionActive()) { + if (TheCamera.pTargetEntity != nil) { + float DistToTargetSq = (TheCamera.pTargetEntity->GetPosition() - TheCamera.GetPosition()).MagnitudeSqr(); + if (DistToTargetSq >= SQR(55.0f)) { + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + } else if (DistToTargetSq >= SQR(10.0f)) { + volume = ((45.0f - (Sqrt(DistToTargetSq) - 10.0f)) / 45.0f * 100.0f); + uint8 pan; + if (AudioManager.ShouldDuckMissionAudio()) + volume /= 4; + if (volume > 0) { + CVector panVec; + AudioManager.TranslateEntity(&TheCamera.pTargetEntity->GetPosition(), &panVec); + pan = AudioManager.ComputePan(55.0f, &panVec); + } else { + pan = 0; + } + if (gRetuneCounter) + volume /= 4; + SampleManager.SetStreamedVolumeAndPan(volume, pan, FALSE); + } else if (AudioManager.ShouldDuckMissionAudio()) { + SampleManager.SetStreamedVolumeAndPan(25, 63, FALSE); + } else if (gRetuneCounter) { + SampleManager.SetStreamedVolumeAndPan(25, 63, FALSE); + } else { + SampleManager.SetStreamedVolumeAndPan(100, 63, FALSE); + } + } + } else if (AudioManager.ShouldDuckMissionAudio()) { + SampleManager.SetStreamedVolumeAndPan(25, 63, FALSE); + nFramesSinceCutsceneEnded = 0; + } else { + if (nFramesSinceCutsceneEnded == -1) { + volume = 100; + } else if (nFramesSinceCutsceneEnded < 20) { + nFramesSinceCutsceneEnded++; + volume = 25; + } else if (nFramesSinceCutsceneEnded < 40) { + volume = 3 * (nFramesSinceCutsceneEnded - 20) + 25; + nFramesSinceCutsceneEnded++; + } else { + nFramesSinceCutsceneEnded = -1; + volume = 100; + } + if (gRetuneCounter != 0) + volume /= 4; + SampleManager.SetStreamedVolumeAndPan(volume, 63, FALSE); + } + return; + } + if (bRadioOff) { + m_nNextTrack = m_nPlayingTrack; + if (FindPlayerVehicle() != nil) + FindPlayerVehicle()->m_nRadioStation = RADIO_OFF; + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_TURN_OFF, 0.0f); + } + ServiceAmbience(); + return; + } + if (m_bRadioSetByScript) { + if (UsesPoliceRadio(FindPlayerVehicle())) { + m_nNextTrack = STREAMED_SOUND_RADIO_POLICE; + } else { + m_nNextTrack = m_nRadioStationScript; + if (FindPlayerVehicle()->m_nRadioStation == m_nNextTrack) { + m_nPlayingTrack = NO_TRACK; + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + SampleManager.StopStreamedFile(); + } + if (m_nRadioPosition != -1) { + m_aTracks[m_nNextTrack].m_nPosition = m_nRadioPosition; + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + } + } else { + m_nNextTrack = GetCarTuning(); + } + if (m_nNextTrack >= RADIO_OFF) { + ServiceAmbience(); + return; + } + if (ChangeRadioChannel()) { + if (m_bRadioSetByScript) { + m_bRadioSetByScript = FALSE; + FindPlayerVehicle()->m_nRadioStation = m_nNextTrack; + } + } else { + m_bPlayerInCar = FALSE; + m_nNextTrack = NO_TRACK; + } +} + +void +cMusicManager::StopFrontEndTrack() +{ + if (IsInitialised() && !m_bDisabled && m_nMusicMode == MUSICMODE_FRONTEND && m_nNextTrack != NO_TRACK) { + m_aTracks[m_nNextTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + SampleManager.StopStreamedFile(); + m_nPlayingTrack = NO_TRACK; + m_nNextTrack = NO_TRACK; + } +} + +void +cMusicManager::PlayAnnouncement(uint8 announcement) +{ + if (IsInitialised() && !m_bDisabled && !m_bAnnouncementInProgress) + m_nAnnouncement = announcement; +} + +void +cMusicManager::PlayFrontEndTrack(uint8 track, bool8 bPlayInFrontend) +{ + if (IsInitialised() && !m_bDisabled && track < TOTAL_STREAMED_SOUNDS) { + if (m_nMusicMode == MUSICMODE_GAME) { + if (m_nNextTrack != NO_TRACK) { + if (m_bAnnouncementInProgress) { + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + } + m_aTracks[m_nNextTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + SampleManager.StopStreamedFile(); + } else if (m_nMusicMode == MUSICMODE_FRONTEND) { + if (m_nNextTrack != NO_TRACK) { + m_aTracks[m_nNextTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + SampleManager.StopStreamedFile(); + } + + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = track; + m_bPlayInFrontend = bPlayInFrontend; + m_bFrontendTrackFinished = FALSE; + m_bDoTrackService = TRUE; + bHasStarted = FALSE; + if (m_nNextTrack < NUM_RADIOS) { + gRetuneCounter = 0; + gNumRetunePresses = 0; + } + } +} + +void +cMusicManager::PreloadCutSceneMusic(uint8 track) +{ + if (IsInitialised() && !m_bDisabled && track < TOTAL_STREAMED_SOUNDS && m_nMusicMode == MUSICMODE_CUTSCENE) { + AudioManager.ResetPoliceRadio(); + while (SampleManager.IsStreamPlaying()) + SampleManager.StopStreamedFile(); + SampleManager.PreloadStreamedFile(track); + SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, TRUE); + m_nNextTrack = track; + } +} + +void +cMusicManager::PlayPreloadedCutSceneMusic(void) +{ + if (IsInitialised() && !m_bDisabled && m_nMusicMode == MUSICMODE_CUTSCENE) + SampleManager.StartPreloadedStreamedFile(); +} + +void +cMusicManager::StopCutSceneMusic(void) +{ + if (IsInitialised() && !m_bDisabled && m_nMusicMode == MUSICMODE_CUTSCENE) { + SampleManager.StopStreamedFile(); + m_nNextTrack = NO_TRACK; + } +} + +uint32 +cMusicManager::GetTrackStartPos(uint8 track) +{ + uint32 pos = m_aTracks[track].m_nPosition; + if (CTimer::GetTimeInMillisecondsPauseMode() > m_aTracks[track].m_nLastPosCheckTimer) + pos += Min(CTimer::GetTimeInMillisecondsPauseMode() - m_aTracks[track].m_nLastPosCheckTimer, 90000); + else + m_aTracks[track].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + + if (pos > m_aTracks[track].m_nLength) + pos %= m_aTracks[track].m_nLength; + return pos; +} + + +bool8 +cMusicManager::UsesPoliceRadio(CVehicle *veh) +{ + switch (veh->GetModelIndex()) + { + case MI_FBICAR: + case MI_POLICE: + case MI_ENFORCER: + case MI_PREDATOR: + case MI_RHINO: + case MI_BARRACKS: + return TRUE; + } + return FALSE; +} + +void +cMusicManager::ServiceAmbience() +{ + uint8 volume; + + if (m_bAnnouncementInProgress) { + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + } + if (m_nNextTrack < RADIO_OFF) { + if (SampleManager.IsStreamPlaying()) { + m_aTracks[m_nNextTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + SampleManager.StopStreamedFile(); + m_nNextTrack = NO_TRACK; + return; + } + m_nNextTrack = RADIO_OFF; + } + if (CWorld::Players[CWorld::PlayerInFocus].m_WBState != WBSTATE_PLAYING && !SampleManager.IsStreamPlaying()) { + m_nNextTrack = NO_TRACK; + return; + } + + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = TheCamera.DistanceToWater <= 45.0f ? STREAMED_SOUND_WATER_AMBIENT : STREAMED_SOUND_CITY_AMBIENT; + + if (m_nNextTrack == m_nPlayingTrack) { + ComputeAmbienceVol(FALSE, volume); + SampleManager.SetStreamedVolumeAndPan(volume, 63, TRUE); + if (m_bVerifyAmbienceTrackStartedToPlay) { + if (SampleManager.IsStreamPlaying()) + m_bVerifyAmbienceTrackStartedToPlay = FALSE; + } else ServiceTrack(); + } else { + if (m_nPlayingTrack < TOTAL_STREAMED_SOUNDS) { + m_aTracks[m_nPlayingTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nPlayingTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + SampleManager.StopStreamedFile(); + } + uint32 pos = GetTrackStartPos(m_nNextTrack); + SampleManager.SetStreamedVolumeAndPan(0, 63, TRUE); + if (SampleManager.StartStreamedFile(m_nNextTrack, pos)) { + ComputeAmbienceVol(TRUE, volume); + SampleManager.SetStreamedVolumeAndPan(volume, 63, TRUE); + m_bVerifyAmbienceTrackStartedToPlay = TRUE; + } else + m_nNextTrack = NO_TRACK; + } +} + +void +cMusicManager::ComputeAmbienceVol(bool8 reset, uint8 &outVolume) +{ + static float fVol = 0.0f; + + if (reset) + fVol = 0.0f; + else if (fVol < 60.0f) + fVol += 1.0f; + + if (TheCamera.DistanceToWater > 70.0f) + outVolume = fVol; + else if (TheCamera.DistanceToWater > 45.0f) + outVolume = (TheCamera.DistanceToWater - 45.0f) / 25.0f * fVol; + else if (TheCamera.DistanceToWater > 20.0f) + outVolume = (45.0f - TheCamera.DistanceToWater) / 25.0f * fVol; + else + outVolume = fVol; +} + +void +cMusicManager::ServiceTrack() +{ + if (m_bDoTrackService) { + if (!SampleManager.IsStreamPlaying()) + SampleManager.StartStreamedFile(m_nNextTrack, 0); + } +} + +bool8 +cMusicManager::ServiceAnnouncement() +{ + static int8 cCheck = 0; + if (m_bAnnouncementInProgress) { + if (!SampleManager.IsStreamPlaying()) { + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + } + return TRUE; + } + + if (++cCheck >= 30) { + cCheck = 0; + int pos = SampleManager.GetStreamedFilePosition(); + if (SampleManager.IsStreamPlaying()) { + if (m_nNextTrack != NO_TRACK) { + m_aTracks[m_nNextTrack].m_nPosition = pos; + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + SampleManager.StopStreamedFile(); + } + } + + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + if (SampleManager.StartStreamedFile(m_nAnnouncement, 0)) { + SampleManager.SetStreamedVolumeAndPan(AudioManager.ShouldDuckMissionAudio() ? 25 : 100, 63, FALSE); + m_bAnnouncementInProgress = TRUE; + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = m_nAnnouncement; + return TRUE; + } + + if (cCheck != 0) cCheck--; + else cCheck = 30; + return FALSE; + } + + return FALSE; +} + +uint8 +cMusicManager::GetCarTuning() +{ + CVehicle *veh = FindPlayerVehicle(); + if (veh == nil) return RADIO_OFF; + if (UsesPoliceRadio(veh)) return POLICE_RADIO; + if (veh->m_nRadioStation == USERTRACK && !SampleManager.IsMP3RadioChannelAvailable()) + veh->m_nRadioStation = AudioManager.m_anRandomTable[2] % USERTRACK; + return veh->m_nRadioStation; +} + +uint8 +cMusicManager::GetNextCarTuning() +{ + CVehicle *veh = FindPlayerVehicle(); + if (veh == nil) return RADIO_OFF; + if (UsesPoliceRadio(veh)) return POLICE_RADIO; + if (gNumRetunePresses != 0) { +#ifdef RADIO_SCROLL_TO_PREV_STATION + if (gNumRetunePresses < 0) { + while (gNumRetunePresses < 0) { + if(veh->m_nRadioStation == HEAD_RADIO) { + veh->m_nRadioStation = RADIO_OFF; + } else if(veh->m_nRadioStation == RADIO_OFF || veh->m_nRadioStation == NUM_RADIOS) { + veh->m_nRadioStation = SampleManager.IsMP3RadioChannelAvailable() ? USERTRACK : USERTRACK - 1; + } else + veh->m_nRadioStation--; + + ++gNumRetunePresses; + } + } else +#endif + if (SampleManager.IsMP3RadioChannelAvailable()) { + if (veh->m_nRadioStation == RADIO_OFF) + veh->m_nRadioStation = NUM_RADIOS; + veh->m_nRadioStation += gNumRetunePresses; +#ifdef FIX_BUGS + while (veh->m_nRadioStation > NUM_RADIOS) + veh->m_nRadioStation -= (NUM_RADIOS + 1); +#endif + if (veh->m_nRadioStation == NUM_RADIOS) + veh->m_nRadioStation = RADIO_OFF; +#ifndef FIX_BUGS + else if (veh->m_nRadioStation > NUM_RADIOS) + veh->m_nRadioStation -= (NUM_RADIOS + 1); +#endif + } else if (gNumRetunePresses + veh->m_nRadioStation >= USERTRACK) { + while (gNumRetunePresses) { + if (veh->m_nRadioStation == RADIO_OFF) + veh->m_nRadioStation = HEAD_RADIO; + else if (veh->m_nRadioStation < USERTRACK) + ++veh->m_nRadioStation; + + if (veh->m_nRadioStation == USERTRACK) + veh->m_nRadioStation = RADIO_OFF; + --gNumRetunePresses; + } + } else + veh->m_nRadioStation += gNumRetunePresses; + gNumRetunePresses = 0; + } + return veh->m_nRadioStation; +} + +bool8 +cMusicManager::ChangeRadioChannel() +{ + if (m_nNextTrack != m_nPlayingTrack) { + if (m_nPlayingTrack < TOTAL_STREAMED_SOUNDS) { + m_aTracks[m_nPlayingTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nPlayingTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + SampleManager.StopStreamedFile(); + } + if (SampleManager.IsStreamPlaying()) + return FALSE; + if (!SampleManager.StartStreamedFile(m_nNextTrack, GetTrackStartPos(m_nNextTrack))) + return FALSE; + SampleManager.SetStreamedVolumeAndPan(AudioManager.ShouldDuckMissionAudio() ? 25 : 100, 63, FALSE); + } + return TRUE; +} diff --git a/src/audio/MusicManager.h b/src/audio/MusicManager.h new file mode 100644 index 0000000..49c91cf --- /dev/null +++ b/src/audio/MusicManager.h @@ -0,0 +1,89 @@ +#pragma once + +#include "audio_enums.h" + +class tStreamedSample +{ +public: + uint32 m_nLength; + uint32 m_nPosition; + uint32 m_nLastPosCheckTimer; +}; + +class CVehicle; + +class cMusicManager +{ +public: + bool8 m_bIsInitialised; + bool8 m_bDisabled; + uint8 m_nMusicMode; + uint8 m_nNextTrack; + uint8 m_nPlayingTrack; + bool8 m_bFrontendTrackFinished; + bool8 m_bPlayInFrontend; + bool8 m_bSetNextStation; + uint8 m_nAnnouncement; + bool8 m_bPreviousPlayerInCar; + bool8 m_bPlayerInCar; + bool8 m_bAnnouncementInProgress; + tStreamedSample m_aTracks[TOTAL_STREAMED_SOUNDS]; + bool8 m_bResetTimers; + uint32 m_nResetTime; + uint32 m_nLastTrackServiceTime; + uint32 m_nTimer; + bool8 m_bDoTrackService; + bool8 m_bIgnoreTimeDelay; + bool8 m_bVerifyAmbienceTrackStartedToPlay; + bool8 m_bRadioSetByScript; + uint8 m_nRadioStationScript; + int32 m_nRadioPosition; + uint8 m_nRadioInCar; + +public: + cMusicManager(); + bool8 IsInitialised() { return m_bIsInitialised; } + uint32 GetMusicMode() { return m_nMusicMode; } + uint8 GetNextTrack() { return m_nNextTrack; } + + bool8 Initialise(); + void Terminate(); + + void ChangeMusicMode(uint8 mode); + void StopFrontEndTrack(); + + bool8 PlayerInCar(); + void DisplayRadioStationName(); + + void PlayAnnouncement(uint8); + void PlayFrontEndTrack(uint8, bool8); + void PreloadCutSceneMusic(uint8); + void PlayPreloadedCutSceneMusic(void); + void StopCutSceneMusic(void); + uint8 GetRadioInCar(void); + void SetRadioInCar(uint32); + void SetRadioChannelByScript(uint8, int32); + + void ResetMusicAfterReload(); + + void ResetTimers(uint32); + void Service(); + void ServiceFrontEndMode(); + void ServiceGameMode(); + void ServiceAmbience(); + void ServiceTrack(); + + bool8 UsesPoliceRadio(CVehicle *veh); + uint32 GetTrackStartPos(uint8); + + void ComputeAmbienceVol(bool8 reset, uint8& outVolume); + bool8 ServiceAnnouncement(); + + uint8 GetCarTuning(); + uint8 GetNextCarTuning(); + bool8 ChangeRadioChannel(); +}; + +VALIDATE_SIZE(cMusicManager, 0x95C); + +extern cMusicManager MusicManager; diff --git a/src/audio/PolRadio.cpp b/src/audio/PolRadio.cpp new file mode 100644 index 0000000..f2c14ec --- /dev/null +++ b/src/audio/PolRadio.cpp @@ -0,0 +1,805 @@ +#include "common.h" + +#include "DMAudio.h" + +#include "AudioManager.h" + +#include "AudioSamples.h" +#include "MusicManager.h" +#include "PlayerPed.h" +#include "PolRadio.h" +#include "Replay.h" +#include "Vehicle.h" +#include "World.h" +#include "Zones.h" +#include "sampman.h" +#include "Wanted.h" + +struct tPoliceRadioZone { + char m_aName[8]; + uint32 m_nSampleIndex; + int32 field_12; +}; + +tPoliceRadioZone ZoneSfx[NUMAUDIOZONES]; +char SubZo2Label[8]; +char SubZo3Label[8]; + +uint32 g_nMissionAudioSfx = TOTAL_AUDIO_SAMPLES; +int8 g_nMissionAudioPlayingStatus = PLAY_STATUS_FINISHED; +bool8 gSpecialSuspectLastSeenReport; +uint32 gMinTimeToNextReport[NUM_CRIME_TYPES]; + +void +cAudioManager::InitialisePoliceRadioZones() +{ + for (int32 i = 0; i < NUMAUDIOZONES; i++) + memset(ZoneSfx[i].m_aName, 0, 8); + +#define SETZONESFX(i, name, sample) \ + strcpy(ZoneSfx[i].m_aName, name); \ + ZoneSfx[i].m_nSampleIndex = sample; + + SETZONESFX(0, "HOSPI_2", SFX_POLICE_RADIO_ROCKFORD); + SETZONESFX(1, "CONSTRU", SFX_POLICE_RADIO_FORT_STAUNTON); + SETZONESFX(2, "STADIUM", SFX_POLICE_RADIO_ASPATRIA); + SETZONESFX(3, "YAKUSA", SFX_POLICE_RADIO_TORRINGTON); + SETZONESFX(4, "SHOPING", SFX_POLICE_RADIO_BEDFORD_POINT); + SETZONESFX(5, "COM_EAS", SFX_POLICE_RADIO_NEWPORT); + SETZONESFX(6, "PARK", SFX_POLICE_RADIO_BELLEVILLE_PARK); + SETZONESFX(7, "UNIVERS", SFX_POLICE_RADIO_LIBERTY_CAMPUS); + SETZONESFX(8, "BIG_DAM", SFX_POLICE_RADIO_COCHRANE_DAM); + SETZONESFX(9, "SUB_IND", SFX_POLICE_RADIO_PIKE_CREEK); + SETZONESFX(10, "SWANKS", SFX_POLICE_RADIO_CEDAR_GROVE); + SETZONESFX(11, "PROJECT", SFX_POLICE_RADIO_WICHITA_GARDENS); + SETZONESFX(12, "AIRPORT", SFX_POLICE_RADIO_FRANCIS_INTERNATIONAL_AIRPORT); + SETZONESFX(13, "PORT_W", SFX_POLICE_RADIO_CALLAHAN_POINT); + SETZONESFX(14, "PORT_S", SFX_POLICE_RADIO_ATLANTIC_QUAYS); + SETZONESFX(15, "PORT_E", SFX_POLICE_RADIO_PORTLAND_HARBOUR); + SETZONESFX(16, "PORT_I", SFX_POLICE_RADIO_TRENTON); + SETZONESFX(17, "CHINA", SFX_POLICE_RADIO_CHINATOWN); + SETZONESFX(18, "REDLIGH", SFX_POLICE_RADIO_RED_LIGHT_DISTRICT); + SETZONESFX(19, "TOWERS", SFX_POLICE_RADIO_HEPBURN_HEIGHTS); + SETZONESFX(20, "LITTLEI", SFX_POLICE_RADIO_SAINT_MARKS); + SETZONESFX(21, "HARWOOD", SFX_POLICE_RADIO_HARWOOD); + SETZONESFX(22, "EASTBAY", SFX_POLICE_RADIO_PORTLAND_BEACH); + SETZONESFX(23, "S_VIEW", SFX_POLICE_RADIO_PORTLAND_STRAIGHTS); + SETZONESFX(24, "CITYZON", SFX_POLICE_RADIO_LIBERTY_CITY); + SETZONESFX(25, "IND_ZON", SFX_POLICE_RADIO_PORTLAND); + SETZONESFX(26, "COM_ZON", SFX_POLICE_RADIO_STAUNTON_ISLAND); + SETZONESFX(27, "SUB_ZON", SFX_POLICE_RADIO_SHORESIDE_VALE); + SETZONESFX(28, "SUB_ZO2", SFX_POLICE_RADIO_SHORESIDE_VALE); + SETZONESFX(29, "SUB_ZO3", SFX_POLICE_RADIO_SHORESIDE_VALE); + SETZONESFX(30, "A", SFX_POLICE_RADIO_ROCKFORD); + SETZONESFX(31, "A", SFX_POLICE_RADIO_ROCKFORD); + SETZONESFX(32, "A", SFX_POLICE_RADIO_ROCKFORD); + SETZONESFX(33, "A", SFX_POLICE_RADIO_ROCKFORD); + SETZONESFX(34, "A", SFX_POLICE_RADIO_ROCKFORD); + +#undef SETZONESFX + + strcpy(SubZo2Label, "SUB_ZO2"); + strcpy(SubZo3Label, "SUB_ZO3"); +} + +void +cAudioManager::InitialisePoliceRadio() +{ + m_sPoliceRadioQueue.Reset(); + for (int32 i = 0; i < ARRAY_SIZE(m_aCrimes); i++) + m_aCrimes[i].type = CRIME_NONE; + + SampleManager.SetChannelReverbFlag(CHANNEL_POLICE_RADIO, FALSE); + gSpecialSuspectLastSeenReport = FALSE; + for (int32 i = 0; i < ARRAY_SIZE(gMinTimeToNextReport); i++) + gMinTimeToNextReport[i] = m_FrameCounter; +} + +void +cAudioManager::ResetPoliceRadio() +{ + if (!m_bIsInitialised) return; + if (SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) SampleManager.StopChannel(CHANNEL_POLICE_RADIO); + InitialisePoliceRadio(); +} + +void +cAudioManager::SetMissionScriptPoliceAudio(uint32 sfx) +{ + if (!m_bIsInitialised) return; + if (g_nMissionAudioPlayingStatus != PLAY_STATUS_PLAYING) { + g_nMissionAudioPlayingStatus = PLAY_STATUS_STOPPED; + g_nMissionAudioSfx = sfx; + } +} + +int8 +cAudioManager::GetMissionScriptPoliceAudioPlayingStatus() +{ + return g_nMissionAudioPlayingStatus; +} + +void +cAudioManager::DoPoliceRadioCrackle() +{ + m_sQueueSample.m_nEntityIndex = m_nPoliceChannelEntity; + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_RADIO_CRACKLE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nPriority = 10; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_POLICE_RADIO_CRACKLE); + m_sQueueSample.m_nVolume = m_anRandomTable[2] % 20 + 15; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(m_sQueueSample.m_nVolume); + SET_LOOP_OFFSETS(SFX_POLICE_RADIO_CRACKLE) + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = FALSE; + m_sQueueSample.m_nPan = 63; + m_sQueueSample.m_nFramesToPlay = 3; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); +} + +void +cAudioManager::ServicePoliceRadio() +{ + int32 wantedLevel = 0; // uninitialized variable + static uint32 nLastSeen = 300; + + if(!m_bIsInitialised) return; + + if(!m_bIsPaused) { + bool8 crimeReport = SetupCrimeReport(); +#ifdef FIX_BUGS // Crash at 0x5fe6ef + if(CReplay::IsPlayingBack() || !FindPlayerPed() || !FindPlayerPed()->m_pWanted) + return; +#endif + wantedLevel = FindPlayerPed()->m_pWanted->GetWantedLevel(); + if (!crimeReport) { + if (wantedLevel != 0) { + if (nLastSeen != 0) { +#ifdef FIX_BUGS + nLastSeen -= CTimer::GetLogicalFramesPassed(); +#else + nLastSeen--; +#endif + } else { + nLastSeen = m_anRandomTable[1] % 1000 + 2000; + SetupSuspectLastSeenReport(); + } + } + } + } + ServicePoliceRadioChannel(wantedLevel); +} + +void +cAudioManager::ServicePoliceRadioChannel(uint8 wantedLevel) +{ + bool8 processed = FALSE; + uint32 sample; + uint32 freq; + + static int cWait = 0; + static bool8 bChannelOpen = FALSE; + static uint8 bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_STOPPED; + static uint32 PoliceChannelFreq = 5500; + + if (!m_bIsInitialised) return; + + if (m_bIsPaused) { +#ifdef GTA_PS2 + if (SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, 0); +#else + if (SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) SampleManager.StopChannel(CHANNEL_POLICE_RADIO); + if (g_nMissionAudioSfx != TOTAL_AUDIO_SAMPLES && bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_PLAYING && + SampleManager.IsStreamPlaying(1)) + SampleManager.PauseStream(TRUE, 1); +#endif + } else { +#ifdef GTA_PS2 + if (m_bWasPaused) + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, PoliceChannelFreq); +#else + if (m_bWasPaused && g_nMissionAudioSfx != TOTAL_AUDIO_SAMPLES && + bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_PLAYING) + SampleManager.PauseStream(FALSE, 1); +#endif + if (m_sPoliceRadioQueue.m_nSamplesInQueue == 0) bChannelOpen = FALSE; + if (cWait) { +#ifdef FIX_BUGS + cWait -= CTimer::GetLogicalFramesPassed(); +#else + --cWait; +#endif + return; + } + if (g_nMissionAudioSfx != TOTAL_AUDIO_SAMPLES && !bChannelOpen) { + if (g_nMissionAudioPlayingStatus != PLAY_STATUS_STOPPED) { + if (g_nMissionAudioPlayingStatus == PLAY_STATUS_PLAYING && bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_STOPPED && +#ifdef GTA_PS2 + SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) { +#else + SampleManager.IsStreamPlaying(1)) { +#endif + bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_PLAYING; + } + if (bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_PLAYING) { +#ifdef GTA_PS2 + if (SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) { +#else + if (SampleManager.IsStreamPlaying(1)) { +#endif + DoPoliceRadioCrackle(); + } else { + bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_FINISHED; + g_nMissionAudioPlayingStatus = PLAY_STATUS_FINISHED; + g_nMissionAudioSfx = TOTAL_AUDIO_SAMPLES; + cWait = 30; + } + return; + } + } else if (!SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) { +#ifdef GTA_PS2 + SampleManager.InitialiseChannel(CHANNEL_POLICE_RADIO, g_nMissionAudioSfx, SFX_BANK_PED_COMMENTS); + PoliceChannelFreq = SampleManager.GetSampleBaseFrequency(g_nMissionAudioSfx); + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, PoliceChannelFreq); + SampleManager.SetChannelVolume(CHANNEL_POLICE_RADIO, MAX_VOLUME); + SampleManager.SetChannelPan(CHANNEL_POLICE_RADIO, 63); + SampleManager.StartChannel(CHANNEL_POLICE_RADIO); +#else + SampleManager.PreloadStreamedFile(g_nMissionAudioSfx, 1); + SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, TRUE, 1); + SampleManager.StartPreloadedStreamedFile(1); +#endif + g_nMissionAudioPlayingStatus = PLAY_STATUS_PLAYING; + bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_STOPPED; + return; + } + } + if (bChannelOpen) DoPoliceRadioCrackle(); + if ((g_nMissionAudioSfx == TOTAL_AUDIO_SAMPLES || g_nMissionAudioPlayingStatus != PLAY_STATUS_PLAYING) && + !SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO) && m_sPoliceRadioQueue.m_nSamplesInQueue != 0) { + sample = m_sPoliceRadioQueue.Remove(); + if (wantedLevel == 0) { + if (gSpecialSuspectLastSeenReport) { + gSpecialSuspectLastSeenReport = FALSE; + } else if (((sample >= SFX_POLICE_RADIO_MESSAGE_NOISE_1) && (sample <= SFX_POLICE_RADIO_MESSAGE_NOISE_3)) || sample == TOTAL_AUDIO_SAMPLES) { + bChannelOpen = FALSE; + processed = TRUE; + } + } + if (sample == TOTAL_AUDIO_SAMPLES) { + if (!processed) cWait = 30; + } else { + SampleManager.InitialiseChannel(CHANNEL_POLICE_RADIO, sample, SFX_BANK_0); + switch (sample) { + case SFX_POLICE_RADIO_MESSAGE_NOISE_1: + case SFX_POLICE_RADIO_MESSAGE_NOISE_2: + case SFX_POLICE_RADIO_MESSAGE_NOISE_3: + freq = m_anRandomTable[4] % 2000 + 10025; + bChannelOpen = bChannelOpen == FALSE; + break; + default: freq = SampleManager.GetSampleBaseFrequency(sample); break; + } + PoliceChannelFreq = freq; +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, freq * CTimer::GetTimeScale()); +#else + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, freq); +#endif + SampleManager.SetChannelVolume(CHANNEL_POLICE_RADIO, 100); + SampleManager.SetChannelPan(CHANNEL_POLICE_RADIO, 63); + SampleManager.SetChannelLoopCount(CHANNEL_POLICE_RADIO, 1); +#ifndef GTA_PS2 + SampleManager.SetChannelLoopPoints(CHANNEL_POLICE_RADIO, 0, -1); +#endif + SampleManager.StartChannel(CHANNEL_POLICE_RADIO); + } + if (processed) ResetPoliceRadio(); + } + } +} + +bool8 +cAudioManager::SetupCrimeReport() +{ + int16 audioZoneId; + CZone *zone; + float rangeX; + float rangeY; + float halfX; + float halfY; + float quarterX; + float quarterY; + int i; + uint32 sampleIndex; + bool8 processed = FALSE; + + if (MusicManager.m_nMusicMode == MUSICMODE_CUTSCENE) return FALSE; + + if (POLICE_RADIO_QUEUE_MAX_SAMPLES - m_sPoliceRadioQueue.m_nSamplesInQueue <= 9) { + AgeCrimes(); + return TRUE; + } + + for (i = 0; i < ARRAY_SIZE(m_aCrimes); i++) { + if (m_aCrimes[i].type != CRIME_NONE) + break; + } + + if (i == ARRAY_SIZE(m_aCrimes)) return FALSE; + audioZoneId = CTheZones::FindAudioZone(&m_aCrimes[i].position); + if (audioZoneId >= 0 && audioZoneId < NUMAUDIOZONES) { + zone = CTheZones::GetAudioZone(audioZoneId); + for (int j = 0; j < NUMAUDIOZONES; j++) { + if (strcmp(zone->name, ZoneSfx[j].m_aName) == 0) { + sampleIndex = ZoneSfx[j].m_nSampleIndex; + m_sPoliceRadioQueue.Add(m_anRandomTable[4] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(m_anRandomTable[0] % 3 + SFX_WEVE_GOT); + m_sPoliceRadioQueue.Add(m_anRandomTable[1] % 2 + SFX_A_10_1); + switch (m_aCrimes[i].type) { + case CRIME_PED_BURNED: m_aCrimes[i].type = CRIME_HIT_PED; break; + case CRIME_COP_BURNED: m_aCrimes[i].type = CRIME_HIT_COP; break; + case CRIME_VEHICLE_BURNED: m_aCrimes[i].type = CRIME_STEAL_CAR; break; + case CRIME_DESTROYED_CESSNA: m_aCrimes[i].type = CRIME_SHOOT_HELI; break; + default: break; + } + m_sPoliceRadioQueue.Add(m_aCrimes[i].type + SFX_CRIME_1 - 1); + m_sPoliceRadioQueue.Add(SFX_IN); + if (sampleIndex == SFX_POLICE_RADIO_SHORESIDE_VALE && + (strcmp(zone->name, SubZo2Label) == 0 || strcmp(zone->name, SubZo3Label) == 0)) { + m_sPoliceRadioQueue.Add(SFX_NORTH); + m_sPoliceRadioQueue.Add(SFX_EAST); + } else { + rangeX = zone->maxx - zone->minx; + rangeY = zone->maxy - zone->miny; + halfX = 0.5f * rangeX + zone->minx; + halfY = 0.5f * rangeY + zone->miny; + quarterX = 0.25f * rangeX; + quarterY = 0.25f * rangeY; + + if (m_aCrimes[i].position.y > halfY + quarterY) { + m_sPoliceRadioQueue.Add(SFX_NORTH); + processed = TRUE; + } else if (m_aCrimes[i].position.y < halfY - quarterY) { + m_sPoliceRadioQueue.Add(SFX_SOUTH); + processed = TRUE; + } + + if (m_aCrimes[i].position.x > halfX + quarterX) + m_sPoliceRadioQueue.Add(SFX_EAST); + else if (m_aCrimes[i].position.x < halfX - quarterX) + m_sPoliceRadioQueue.Add(SFX_WEST); + else if (!processed) + m_sPoliceRadioQueue.Add(SFX_CENTRAL); + + m_sPoliceRadioQueue.Add(sampleIndex); + m_sPoliceRadioQueue.Add(m_anRandomTable[2] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(TOTAL_AUDIO_SAMPLES); + } + break; + } + } + } + m_aCrimes[i].type = CRIME_NONE; + AgeCrimes(); + return TRUE; +} + +Const uint32 gCarColourTable[][3] = { + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLACK, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_WHITE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_BRIGHT, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_BLUE, SFX_POLICE_RADIO_GREY}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_PURPLE, SFX_POLICE_RADIO_BLUE}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, SFX_POLICE_RADIO_GREY}, +#else + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, +#else + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, +#endif + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES} +}; + +void +cAudioManager::SetupSuspectLastSeenReport() +{ + CVehicle *veh; + uint8 color1; + uint32 main_color; + uint32 sample; + + uint32 color_pre_modifier; + uint32 color_post_modifier; + + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) { + veh = FindPlayerVehicle(); + if (veh != nil) { + if (POLICE_RADIO_QUEUE_MAX_SAMPLES - m_sPoliceRadioQueue.m_nSamplesInQueue > 9) { + color1 = veh->m_currentColour1; + if (color1 >= ARRAY_SIZE(gCarColourTable)) { + debug("\n *** UNKNOWN CAR COLOUR %d *** ", color1); + } else { + main_color = gCarColourTable[color1][1]; + color_pre_modifier = gCarColourTable[color1][0]; + color_post_modifier = gCarColourTable[color1][2]; + switch (veh->GetModelIndex()) { +#ifdef FIX_BUGS + case MI_COLUMB: + main_color = SFX_POLICE_RADIO_BLUE; + color_pre_modifier = color_post_modifier = TOTAL_AUDIO_SAMPLES; +#endif + case MI_LANDSTAL: + case MI_BLISTA: sample = SFX_POLICE_RADIO_CRUISER; break; +#ifdef FIX_BUGS + case MI_YARDIE: + color_pre_modifier = TOTAL_AUDIO_SAMPLES; + main_color = SFX_POLICE_RADIO_RED; + color_post_modifier = SFX_POLICE_RADIO_YELLOW; + sample = SFX_POLICE_RADIO_CONVERTIBLE; break; + case MI_DIABLOS: + main_color = SFX_POLICE_RADIO_BLACK; +#endif + case MI_IDAHO: + case MI_STALLION: sample = SFX_POLICE_RADIO_CONVERTIBLE; break; +#ifdef FIX_BUGS + case MI_YAKUZA: + color_pre_modifier = TOTAL_AUDIO_SAMPLES; + main_color = SFX_POLICE_RADIO_SILVER; + color_post_modifier = SFX_POLICE_RADIO_RED; +#endif + case MI_STINGER: + case MI_INFERNUS: + case MI_CHEETAH: + case MI_BANSHEE: sample = SFX_POLICE_RADIO_SPORTS_CAR; break; +#ifdef FIX_BUGS + case MI_MAFIA: + color_pre_modifier = color_post_modifier = TOTAL_AUDIO_SAMPLES; + main_color = SFX_POLICE_RADIO_GREY; + case MI_KURUMA: +#endif + case MI_PEREN: + case MI_SENTINEL: + case MI_FBICAR: sample = SFX_POLICE_RADIO_SALOON; break; + case MI_PATRIOT: + case MI_BOBCAT: sample = SFX_POLICE_RADIO_PICKUP; break; + case MI_FIRETRUCK: sample = SFX_POLICE_RADIO_FIRE_TRUCK; break; +#ifdef FIX_BUGS + case MI_LINERUN: + case MI_FLATBED: +#endif + case MI_TRASH: + case MI_BARRACKS: sample = SFX_POLICE_RADIO_TRUCK; break; + case MI_STRETCH: sample = SFX_POLICE_RADIO_LIMO; break; +#ifdef FIX_BUGS + case MI_CORPSE: +#endif + case MI_MANANA: + case MI_ESPERANT: sample = SFX_POLICE_RADIO_2_DOOR; break; +#ifdef FIX_BUGS + case MI_HOODS: + color_pre_modifier = TOTAL_AUDIO_SAMPLES; + main_color = SFX_POLICE_RADIO_BLUE; + color_post_modifier = SFX_POLICE_RADIO_GREEN; + case MI_BELLYUP: + case MI_YANKEE: + case MI_TOYZ: + case MI_MRWONGS: + case MI_PANLANT: +#endif + case MI_PONY: + case MI_MULE: + case MI_MOONBEAM: + case MI_ENFORCER: + case MI_SECURICA: + case MI_RUMPO: sample = SFX_POLICE_RADIO_VAN; break; + case MI_AMBULAN: sample = SFX_POLICE_RADIO_AMBULANCE; break; + case MI_TAXI: + case MI_CABBIE: + case MI_BORGNINE: sample = SFX_POLICE_RADIO_TAXI; break; + case MI_MRWHOOP: + sample = SFX_POLICE_RADIO_ICE_CREAM_VAN; + break; + case MI_BFINJECT: sample = SFX_POLICE_RADIO_BUGGY; break; + case MI_POLICE: sample = SFX_POLICE_RADIO_POLICE_CAR; break; +#ifdef FIX_BUGS + case MI_SPEEDER: + case MI_REEFER: + case MI_GHOST: +#endif + case MI_PREDATOR: sample = SFX_POLICE_RADIO_BOAT; break; + case MI_BUS: + case MI_COACH: sample = SFX_POLICE_RADIO_BUS; break; + case MI_RHINO: + sample = SFX_POLICE_RADIO_TANK; + main_color = TOTAL_AUDIO_SAMPLES; + color_post_modifier = TOTAL_AUDIO_SAMPLES; + break; + case MI_TRAIN: + sample = SFX_POLICE_RADIO_SUBWAY_CAR; + main_color = TOTAL_AUDIO_SAMPLES; + color_post_modifier = TOTAL_AUDIO_SAMPLES; + + break; + default: + debug("\n *** UNKNOWN CAR MODEL INDEX %d *** ", veh->GetModelIndex()); + return; + } + m_sPoliceRadioQueue.Add(m_anRandomTable[4] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_SUSPECT); + if (m_anRandomTable[3] % 2) + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_LAST_SEEN); +#ifdef FIX_BUGS + if (main_color == SFX_POLICE_RADIO_ORANGE && color_pre_modifier == TOTAL_AUDIO_SAMPLES) +#else + if (main_color == SFX_POLICE_RADIO_ORANGE) +#endif + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_IN_AN); + else + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_IN_A); + if (color_pre_modifier != TOTAL_AUDIO_SAMPLES) + m_sPoliceRadioQueue.Add(color_pre_modifier); + if (main_color != TOTAL_AUDIO_SAMPLES) + m_sPoliceRadioQueue.Add(main_color); + if (color_post_modifier != TOTAL_AUDIO_SAMPLES) + m_sPoliceRadioQueue.Add(color_post_modifier); + m_sPoliceRadioQueue.Add(sample); + m_sPoliceRadioQueue.Add(m_anRandomTable[0] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(TOTAL_AUDIO_SAMPLES); + } + } + } else if (POLICE_RADIO_QUEUE_MAX_SAMPLES - m_sPoliceRadioQueue.m_nSamplesInQueue > 4) { + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_SUSPECT); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_ON_FOOT); + m_sPoliceRadioQueue.Add(m_anRandomTable[0] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(TOTAL_AUDIO_SAMPLES); + } + } +} + +void +cAudioManager::ReportCrime(eCrimeType type, const CVector &pos) +{ + int32 lastCrime = ARRAY_SIZE(m_aCrimes); + if (m_bIsInitialised && MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE && FindPlayerPed()->m_pWanted->GetWantedLevel() > 0 && + (type > CRIME_NONE || type < NUM_CRIME_TYPES) && m_FrameCounter >= gMinTimeToNextReport[type]) { + for (int32 i = 0; i < ARRAY_SIZE(m_aCrimes); i++) { + if (m_aCrimes[i].type != CRIME_NONE) { + if (m_aCrimes[i].type == type) { + m_aCrimes[i].position = pos; + m_aCrimes[i].timer = 0; + return; + } + } else { + lastCrime = i; + } + } + + if (lastCrime < ARRAY_SIZE(m_aCrimes)) { + m_aCrimes[lastCrime].type = type; + m_aCrimes[lastCrime].position = pos; + m_aCrimes[lastCrime].timer = 0; + gMinTimeToNextReport[type] = m_FrameCounter + 500; + } + } +} + +void +cAudioManager::PlaySuspectLastSeen(float x, float y, float z) +{ + int16 audioZone; + CZone *zone; + float rangeX; + float rangeY; + float halfX; + float halfY; + float quarterX; + float quarterY; + uint32 sample; + bool8 processed = FALSE; + CVector vec = CVector(x, y, z); + + if (!m_bIsInitialised) return; + + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE && POLICE_RADIO_QUEUE_MAX_SAMPLES - m_sPoliceRadioQueue.m_nSamplesInQueue > 9) { + audioZone = CTheZones::FindAudioZone(&vec); + if (audioZone >= 0 && audioZone < NUMAUDIOZONES) { + zone = CTheZones::GetAudioZone(audioZone); + for (int i = 0; i < NUMAUDIOZONES; i++) { + if (strcmp(zone->name, ZoneSfx[i].m_aName) == 0) { + sample = ZoneSfx[i].m_nSampleIndex; + m_sPoliceRadioQueue.Add(m_anRandomTable[4] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_SUSPECT); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_LAST_SEEN); + m_sPoliceRadioQueue.Add(SFX_IN); + if (sample == SFX_POLICE_RADIO_SHORESIDE_VALE && + (strcmp(zone->name, SubZo2Label) == 0 || + strcmp(zone->name, SubZo3Label) == 0)) { + m_sPoliceRadioQueue.Add(SFX_NORTH); + m_sPoliceRadioQueue.Add(SFX_EAST); + } else { + rangeX = zone->maxx - zone->minx; + rangeY = zone->maxy - zone->miny; + halfX = 0.5f * rangeX + zone->minx; + halfY = 0.5f * rangeY + zone->miny; + quarterX = 0.25f * rangeX; + quarterY = 0.25f * rangeY; + + if (vec.y > halfY + quarterY) { + m_sPoliceRadioQueue.Add(SFX_NORTH); + processed = TRUE; + } else if (vec.y < halfY - quarterY) { + m_sPoliceRadioQueue.Add(SFX_SOUTH); + processed = TRUE; + } + + if (vec.x > halfX + quarterX) + m_sPoliceRadioQueue.Add(SFX_EAST); + else if (vec.x < halfX - quarterX) + m_sPoliceRadioQueue.Add(SFX_WEST); + else if (!processed) + m_sPoliceRadioQueue.Add(SFX_CENTRAL); + } + m_sPoliceRadioQueue.Add(sample); + m_sPoliceRadioQueue.Add(m_anRandomTable[2] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(TOTAL_AUDIO_SAMPLES); + gSpecialSuspectLastSeenReport = TRUE; + break; + } + } + } + } +} + +void +cAudioManager::AgeCrimes() +{ + for (uint8 i = 0; i < ARRAY_SIZE(m_aCrimes); i++) { + if (m_aCrimes[i].type != CRIME_NONE) { + if (++m_aCrimes[i].timer > 1500) m_aCrimes[i].type = CRIME_NONE; + } + } +} diff --git a/src/audio/PolRadio.h b/src/audio/PolRadio.h new file mode 100644 index 0000000..c7b0bcc --- /dev/null +++ b/src/audio/PolRadio.h @@ -0,0 +1,66 @@ +#pragma once + +#include "Crime.h" +#include "AudioSamples.h" + +struct cAMCrime { + int32 type; + CVector position; + uint16 timer; + + cAMCrime() + { + type = CRIME_NONE; + position = CVector(0.0f, 0.0f, 0.0f); + timer = 0; + } +}; + +VALIDATE_SIZE(cAMCrime, 20); + +#define POLICE_RADIO_QUEUE_MAX_SAMPLES 60 + +class cPoliceRadioQueue +{ +public: + uint32 m_aSamples[POLICE_RADIO_QUEUE_MAX_SAMPLES]; + uint8 m_nSamplesInQueue; + uint8 m_nAddOffset; + uint8 m_nRemoveOffset; + + cPoliceRadioQueue() + { + Reset(); + } + + void Reset() + { + m_nAddOffset = 0; + m_nRemoveOffset = 0; + m_nSamplesInQueue = 0; + } + + bool8 Add(uint32 sample) + { + if (m_nSamplesInQueue != POLICE_RADIO_QUEUE_MAX_SAMPLES) { + m_aSamples[m_nAddOffset] = sample; + m_nSamplesInQueue++; + m_nAddOffset = (m_nAddOffset + 1) % POLICE_RADIO_QUEUE_MAX_SAMPLES; + return TRUE; + } + return FALSE; + } + + uint32 Remove() + { + if (m_nSamplesInQueue != 0) { + uint32 sample = m_aSamples[m_nRemoveOffset]; + m_nSamplesInQueue--; + m_nRemoveOffset = (m_nRemoveOffset + 1) % POLICE_RADIO_QUEUE_MAX_SAMPLES; + return sample; + } + return TOTAL_AUDIO_SAMPLES; + } +}; + +VALIDATE_SIZE(cPoliceRadioQueue, 244); diff --git a/src/audio/audio_enums.h b/src/audio/audio_enums.h new file mode 100644 index 0000000..b8fc611 --- /dev/null +++ b/src/audio/audio_enums.h @@ -0,0 +1,280 @@ +#pragma once + +enum eRadioStation +{ + HEAD_RADIO, + DOUBLE_CLEF, + JAH_RADIO, + RISE_FM, + LIPS_106, + GAME_FM, + MSX_FM, + FLASHBACK, + CHATTERBOX, + USERTRACK, + POLICE_RADIO = 10, + NUM_RADIOS = 10, + RADIO_OFF = 11, +}; + +enum eMusicMode +{ + MUSICMODE_FRONTEND = 0, + MUSICMODE_GAME, + MUSICMODE_CUTSCENE, + MUSICMODE_DISABLE, + MUSICMODE_DISABLED, +}; + +enum eStreamedSounds +{ + STREAMED_SOUND_RADIO_HEAD, + STREAMED_SOUND_RADIO_CLASSIC, + STREAMED_SOUND_RADIO_KJAH, + STREAMED_SOUND_RADIO_RISE, + STREAMED_SOUND_RADIO_LIPS, + STREAMED_SOUND_RADIO_GAME, + STREAMED_SOUND_RADIO_MSX, + STREAMED_SOUND_RADIO_FLASH, + STREAMED_SOUND_RADIO_CHAT, + STREAMED_SOUND_RADIO_MP3_PLAYER, + STREAMED_SOUND_RADIO_POLICE, + STREAMED_SOUND_CITY_AMBIENT, + STREAMED_SOUND_WATER_AMBIENT, + STREAMED_SOUND_ANNOUNCE_COMMERCIAL_OPEN, + STREAMED_SOUND_ANNOUNCE_SUBURBAN_OPEN, + STREAMED_SOUND_NEWS_INTRO, + STREAMED_SOUND_BANK_INTRO, + STREAMED_SOUND_CUTSCENE_LUIGI1_LG, + STREAMED_SOUND_CUTSCENE_LUIGI2_DSB, + STREAMED_SOUND_CUTSCENE_LUIGI3_DM, + STREAMED_SOUND_CUTSCENE_LUIGI4_PAP, + STREAMED_SOUND_CUTSCENE_LUIGI5_TFB, + STREAMED_SOUND_CUTSCENE_JOEY0_DM2, + STREAMED_SOUND_CUTSCENE_JOEY1_LFL, + STREAMED_SOUND_CUTSCENE_JOEY2_KCL, + STREAMED_SOUND_CUTSCENE_JOEY3_VH, + STREAMED_SOUND_CUTSCENE_JOEY4_ETH, + STREAMED_SOUND_CUTSCENE_JOEY5_DST, + STREAMED_SOUND_CUTSCENE_JOEY6_TBJ, + STREAMED_SOUND_CUTSCENE_TONI1_TOL, + STREAMED_SOUND_CUTSCENE_TONI2_TPU, + STREAMED_SOUND_CUTSCENE_TONI3_MAS, + STREAMED_SOUND_CUTSCENE_TONI4_TAT, + STREAMED_SOUND_CUTSCENE_TONI5_BF, + STREAMED_SOUND_CUTSCENE_SAL0_MAS, + STREAMED_SOUND_CUTSCENE_SAL1_PF, + STREAMED_SOUND_CUTSCENE_SAL2_CTG, + STREAMED_SOUND_CUTSCENE_SAL3_RTC, + STREAMED_SOUND_CUTSCENE_SAL5_LRQ, + STREAMED_SOUND_CUTSCENE_SAL4_BDBA, + STREAMED_SOUND_CUTSCENE_SAL4_BDBB, + STREAMED_SOUND_CUTSCENE_SAL2_CTG2, + STREAMED_SOUND_CUTSCENE_SAL4_BDBD, + STREAMED_SOUND_CUTSCENE_SAL5_LRQB, + STREAMED_SOUND_CUTSCENE_SAL5_LRQC, + STREAMED_SOUND_CUTSCENE_ASUKA_1_SSO, + STREAMED_SOUND_CUTSCENE_ASUKA_2_PP, + STREAMED_SOUND_CUTSCENE_ASUKA_3_SS, + STREAMED_SOUND_CUTSCENE_ASUKA_4_PDR, + STREAMED_SOUND_CUTSCENE_ASUKA_5_K2FT, + STREAMED_SOUND_CUTSCENE_KENJI1_KBO, + STREAMED_SOUND_CUTSCENE_KENJI2_GIS, + STREAMED_SOUND_CUTSCENE_KENJI3_DS, + STREAMED_SOUND_CUTSCENE_KENJI4_SHI, + STREAMED_SOUND_CUTSCENE_KENJI5_SD, + STREAMED_SOUND_CUTSCENE_RAY0_PDR2, + STREAMED_SOUND_CUTSCENE_RAY1_SW, + STREAMED_SOUND_CUTSCENE_RAY2_AP, + STREAMED_SOUND_CUTSCENE_RAY3_ED, + STREAMED_SOUND_CUTSCENE_RAY4_GF, + STREAMED_SOUND_CUTSCENE_RAY5_PB, + STREAMED_SOUND_CUTSCENE_RAY6_MM, + STREAMED_SOUND_CUTSCENE_DONALD1_STOG, + STREAMED_SOUND_CUTSCENE_DONALD2_KK, + STREAMED_SOUND_CUTSCENE_DONALD3_ADO, + STREAMED_SOUND_CUTSCENE_DONALD5_ES, + STREAMED_SOUND_CUTSCENE_DONALD7_MLD, + STREAMED_SOUND_CUTSCENE_DONALD4_GTA, + STREAMED_SOUND_CUTSCENE_DONALD4_GTA2, + STREAMED_SOUND_CUTSCENE_DONALD6_STS, + STREAMED_SOUND_CUTSCENE_ASUKA6_BAIT, + STREAMED_SOUND_CUTSCENE_ASUKA7_ETG, + STREAMED_SOUND_CUTSCENE_ASUKA8_PS, + STREAMED_SOUND_CUTSCENE_ASUKA9_ASD, + STREAMED_SOUND_CUTSCENE_KENJI4_SHI2, + STREAMED_SOUND_CUTSCENE_CATALINA1_TEX, + STREAMED_SOUND_CUTSCENE_ELBURRO1_PH1, + STREAMED_SOUND_CUTSCENE_ELBURRO2_PH2, + STREAMED_SOUND_CUTSCENE_ELBURRO3_PH3, + STREAMED_SOUND_CUTSCENE_ELBURRO4_PH4, + STREAMED_SOUND_CUTSCENE_YARDIE_PH1, + STREAMED_SOUND_CUTSCENE_YARDIE_PH2, + STREAMED_SOUND_CUTSCENE_YARDIE_PH3, + STREAMED_SOUND_CUTSCENE_YARDIE_PH4, + STREAMED_SOUND_CUTSCENE_HOODS_PH1, + STREAMED_SOUND_CUTSCENE_HOODS_PH2, + STREAMED_SOUND_CUTSCENE_HOODS_PH3, + STREAMED_SOUND_CUTSCENE_HOODS_PH4, + STREAMED_SOUND_CUTSCENE_HOODS_PH5, + STREAMED_SOUND_CUTSCENE_MARTY_PH1, + STREAMED_SOUND_CUTSCENE_MARTY_PH2, + STREAMED_SOUND_CUTSCENE_MARTY_PH3, + STREAMED_SOUND_CUTSCENE_MARTY_PH4, + STREAMED_SOUND_MISSION_COMPLETED, + STREAMED_SOUND_GAME_COMPLETED, +#ifndef GTA_PS2 + SFX_MISSION_LIB_A1, + SFX_MISSION_LIB_A2, + SFX_MISSION_LIB_A, + SFX_MISSION_LIB_B, + SFX_MISSION_LIB_C, + SFX_MISSION_LIB_D, + SFX_MISSION_L2_A, + SFX_MISSION_J4T_1, + SFX_MISSION_J4T_2, + SFX_MISSION_J4T_3, + SFX_MISSION_J4T_4, + SFX_MISSION_J4_A, + SFX_MISSION_J4_B, + SFX_MISSION_J4_C, + SFX_MISSION_J4_D, + SFX_MISSION_J4_E, + SFX_MISSION_J4_F, + SFX_MISSION_J6_1, + SFX_MISSION_J6_A, + SFX_MISSION_J6_B, + SFX_MISSION_J6_C, + SFX_MISSION_J6_D, + SFX_MISSION_T4_A, + SFX_MISSION_S1_A, + SFX_MISSION_S1_A1, + SFX_MISSION_S1_B, + SFX_MISSION_S1_C, + SFX_MISSION_S1_C1, + SFX_MISSION_S1_D, + SFX_MISSION_S1_E, + SFX_MISSION_S1_F, + SFX_MISSION_S1_G, + SFX_MISSION_S1_H, + SFX_MISSION_S1_I, + SFX_MISSION_S1_J, + SFX_MISSION_S1_K, + SFX_MISSION_S1_L, + SFX_MISSION_S3_A, + SFX_MISSION_S3_B, + SFX_MISSION_EL3_A, + SFX_MISSION_MF1_A, + SFX_MISSION_MF2_A, + SFX_MISSION_MF3_A, + SFX_MISSION_MF3_B, + SFX_MISSION_MF3_B1, + SFX_MISSION_MF3_C, + SFX_MISSION_MF4_A, + SFX_MISSION_MF4_B, + SFX_MISSION_MF4_C, + SFX_MISSION_A1_A, + SFX_MISSION_A3_A, + SFX_MISSION_A5_A, + SFX_MISSION_A4_A, + SFX_MISSION_A4_B, + SFX_MISSION_A4_C, + SFX_MISSION_A4_D, + SFX_MISSION_K1_A, + SFX_MISSION_K3_A, + SFX_MISSION_R1_A, + SFX_MISSION_R2_A, + SFX_MISSION_R2_B, + SFX_MISSION_R2_C, + SFX_MISSION_R2_D, + SFX_MISSION_R2_E, + SFX_MISSION_R2_F, + SFX_MISSION_R2_G, + SFX_MISSION_R2_H, + SFX_MISSION_R5_A, + SFX_MISSION_R6_A, + SFX_MISSION_R6_A1, + SFX_MISSION_R6_B, + SFX_MISSION_LO2_A, + SFX_MISSION_LO6_A, + SFX_MISSION_YD2_A, + SFX_MISSION_YD2_B, + SFX_MISSION_YD2_C, + SFX_MISSION_YD2_C1, + SFX_MISSION_YD2_D, + SFX_MISSION_YD2_E, + SFX_MISSION_YD2_F, + SFX_MISSION_YD2_G, + SFX_MISSION_YD2_H, + SFX_MISSION_YD2_ASS, + SFX_MISSION_YD2_OK, + SFX_MISSION_H5_A, + SFX_MISSION_H5_B, + SFX_MISSION_H5_C, + SFX_MISSION_AMMU_A, + SFX_MISSION_AMMU_B, + SFX_MISSION_AMMU_C, + SFX_MISSION_DOOR_1, + SFX_MISSION_DOOR_2, + SFX_MISSION_DOOR_3, + SFX_MISSION_DOOR_4, + SFX_MISSION_DOOR_5, + SFX_MISSION_DOOR_6, + SFX_MISSION_T3_A, + SFX_MISSION_T3_B, + SFX_MISSION_T3_C, + SFX_MISSION_K1_B, + SFX_MISSION_CAT1, +#endif + TOTAL_STREAMED_SOUNDS, + NO_TRACK, +}; + +enum AudioEntityHandle { + AEHANDLE_NONE = -5, + AEHANDLE_ERROR_NOAUDIOSYS = -4, + AEHANDLE_ERROR_NOFREESLOT = -3, + AEHANDLE_ERROR_NOENTITY = -2, + AEHANDLE_ERROR_BADAUDIOTYPE = -1, +}; + +enum eAudioType +{ + AUDIOTYPE_PHYSICAL = 0, + AUDIOTYPE_EXPLOSION, + AUDIOTYPE_FIRE, + AUDIOTYPE_WEATHER, + AUDIOTYPE_CRANE, + AUDIOTYPE_SCRIPTOBJECT, + AUDIOTYPE_BRIDGE, + AUDIOTYPE_COLLISION, + AUDIOTYPE_FRONTEND, + AUDIOTYPE_PROJECTILE, + AUDIOTYPE_GARAGE, + AUDIOTYPE_FIREHYDRANT, + AUDIOTYPE_WATERCANNON, + AUDIOTYPE_POLICERADIO, + TOTAL_AUDIO_TYPES, +}; + +#ifdef GTA_PS2 +enum +{ + NUM_CHANNELS_GENERIC = 43, + CHANNEL_POLICE_RADIO = NUM_CHANNELS_GENERIC, + CHANNEL_MISSION_AUDIO, + CHANNEL_PLAYER_VEHICLE_ENGINE, + NUM_CHANNELS +}; +#else +enum +{ +#ifdef PS2_AUDIO_CHANNELS + NUM_CHANNELS_GENERIC = 43, +#else + NUM_CHANNELS_GENERIC = 27, +#endif + CHANNEL_POLICE_RADIO, + NUM_CHANNELS +}; +#endif diff --git a/src/audio/eax/eax-util.cpp b/src/audio/eax/eax-util.cpp new file mode 100644 index 0000000..42eef73 --- /dev/null +++ b/src/audio/eax/eax-util.cpp @@ -0,0 +1,706 @@ +/***********************************************************************************************\ +* * +* EAX-UTIL.CPP - utilities for EAX 3.0 * +* Function declaration for EAX Morphing * +* String names of the all the presets defined in eax-util.h * +* Arrays grouping together all the EAX presets in a scenario * +* * +************************************************************************************************/ + +#include "eax-util.h" +#include + +// Function prototypes used by EAX3ListenerInterpolate +void Clamp(EAXVECTOR *eaxVector); +bool CheckEAX3LP(LPEAXLISTENERPROPERTIES lpEAX3LP); + + +/***********************************************************************************************\ +* +* Definition of the EAXMorph function - EAX3ListenerInterpolate +* +\***********************************************************************************************/ + +/* + EAX3ListenerInterpolate + lpStart - Initial EAX 3 Listener parameters + lpFinish - Final EAX 3 Listener parameters + flRatio - Ratio Destination : Source (0.0 == Source, 1.0 == Destination) + lpResult - Interpolated EAX 3 Listener parameters + bCheckValues - Check EAX 3.0 parameters are in range, default = false (no checking) +*/ +bool EAX3ListenerInterpolate(LPEAXLISTENERPROPERTIES lpStart, LPEAXLISTENERPROPERTIES lpFinish, + float flRatio, LPEAXLISTENERPROPERTIES lpResult, bool bCheckValues) +{ + EAXVECTOR StartVector, FinalVector; + + float flInvRatio; + + if (bCheckValues) + { + if (!CheckEAX3LP(lpStart)) + return false; + + if (!CheckEAX3LP(lpFinish)) + return false; + } + + if (flRatio >= 1.0f) + { + memcpy(lpResult, lpFinish, sizeof(EAXLISTENERPROPERTIES)); + return true; + } + else if (flRatio <= 0.0f) + { + memcpy(lpResult, lpStart, sizeof(EAXLISTENERPROPERTIES)); + return true; + } + + flInvRatio = (1.0f - flRatio); + + // Environment + lpResult->ulEnvironment = 26; // (UNDEFINED environment) + + // Environment Size + if (lpStart->flEnvironmentSize == lpFinish->flEnvironmentSize) + lpResult->flEnvironmentSize = lpStart->flEnvironmentSize; + else + lpResult->flEnvironmentSize = (float)exp( (log(lpStart->flEnvironmentSize) * flInvRatio) + (log(lpFinish->flEnvironmentSize) * flRatio) ); + + // Environment Diffusion + if (lpStart->flEnvironmentDiffusion == lpFinish->flEnvironmentDiffusion) + lpResult->flEnvironmentDiffusion = lpStart->flEnvironmentDiffusion; + else + lpResult->flEnvironmentDiffusion = (lpStart->flEnvironmentDiffusion * flInvRatio) + (lpFinish->flEnvironmentDiffusion * flRatio); + + // Room + if (lpStart->lRoom == lpFinish->lRoom) + lpResult->lRoom = lpStart->lRoom; + else + lpResult->lRoom = (int)( ((float)lpStart->lRoom * flInvRatio) + ((float)lpFinish->lRoom * flRatio) ); + + // Room HF + if (lpStart->lRoomHF == lpFinish->lRoomHF) + lpResult->lRoomHF = lpStart->lRoomHF; + else + lpResult->lRoomHF = (int)( ((float)lpStart->lRoomHF * flInvRatio) + ((float)lpFinish->lRoomHF * flRatio) ); + + // Room LF + if (lpStart->lRoomLF == lpFinish->lRoomLF) + lpResult->lRoomLF = lpStart->lRoomLF; + else + lpResult->lRoomLF = (int)( ((float)lpStart->lRoomLF * flInvRatio) + ((float)lpFinish->lRoomLF * flRatio) ); + + // Decay Time + if (lpStart->flDecayTime == lpFinish->flDecayTime) + lpResult->flDecayTime = lpStart->flDecayTime; + else + lpResult->flDecayTime = (float)exp( (log(lpStart->flDecayTime) * flInvRatio) + (log(lpFinish->flDecayTime) * flRatio) ); + + // Decay HF Ratio + if (lpStart->flDecayHFRatio == lpFinish->flDecayHFRatio) + lpResult->flDecayHFRatio = lpStart->flDecayHFRatio; + else + lpResult->flDecayHFRatio = (float)exp( (log(lpStart->flDecayHFRatio) * flInvRatio) + (log(lpFinish->flDecayHFRatio) * flRatio) ); + + // Decay LF Ratio + if (lpStart->flDecayLFRatio == lpFinish->flDecayLFRatio) + lpResult->flDecayLFRatio = lpStart->flDecayLFRatio; + else + lpResult->flDecayLFRatio = (float)exp( (log(lpStart->flDecayLFRatio) * flInvRatio) + (log(lpFinish->flDecayLFRatio) * flRatio) ); + + // Reflections + if (lpStart->lReflections == lpFinish->lReflections) + lpResult->lReflections = lpStart->lReflections; + else + lpResult->lReflections = (int)( ((float)lpStart->lReflections * flInvRatio) + ((float)lpFinish->lReflections * flRatio) ); + + // Reflections Delay + if (lpStart->flReflectionsDelay == lpFinish->flReflectionsDelay) + lpResult->flReflectionsDelay = lpStart->flReflectionsDelay; + else + lpResult->flReflectionsDelay = (float)exp( (log(lpStart->flReflectionsDelay+0.0001f) * flInvRatio) + (log(lpFinish->flReflectionsDelay+0.0001f) * flRatio) ); + + // Reflections Pan + + // To interpolate the vector correctly we need to ensure that both the initial and final vectors vectors are clamped to a length of 1.0f + StartVector = lpStart->vReflectionsPan; + FinalVector = lpFinish->vReflectionsPan; + + Clamp(&StartVector); + Clamp(&FinalVector); + + if (lpStart->vReflectionsPan.x == lpFinish->vReflectionsPan.x) + lpResult->vReflectionsPan.x = lpStart->vReflectionsPan.x; + else + lpResult->vReflectionsPan.x = FinalVector.x + (flInvRatio * (StartVector.x - FinalVector.x)); + + if (lpStart->vReflectionsPan.y == lpFinish->vReflectionsPan.y) + lpResult->vReflectionsPan.y = lpStart->vReflectionsPan.y; + else + lpResult->vReflectionsPan.y = FinalVector.y + (flInvRatio * (StartVector.y - FinalVector.y)); + + if (lpStart->vReflectionsPan.z == lpFinish->vReflectionsPan.z) + lpResult->vReflectionsPan.z = lpStart->vReflectionsPan.z; + else + lpResult->vReflectionsPan.z = FinalVector.z + (flInvRatio * (StartVector.z - FinalVector.z)); + + // Reverb + if (lpStart->lReverb == lpFinish->lReverb) + lpResult->lReverb = lpStart->lReverb; + else + lpResult->lReverb = (int)( ((float)lpStart->lReverb * flInvRatio) + ((float)lpFinish->lReverb * flRatio) ); + + // Reverb Delay + if (lpStart->flReverbDelay == lpFinish->flReverbDelay) + lpResult->flReverbDelay = lpStart->flReverbDelay; + else + lpResult->flReverbDelay = (float)exp( (log(lpStart->flReverbDelay+0.0001f) * flInvRatio) + (log(lpFinish->flReverbDelay+0.0001f) * flRatio) ); + + // Reverb Pan + + // To interpolate the vector correctly we need to ensure that both the initial and final vectors are clamped to a length of 1.0f + StartVector = lpStart->vReverbPan; + FinalVector = lpFinish->vReverbPan; + + Clamp(&StartVector); + Clamp(&FinalVector); + + if (lpStart->vReverbPan.x == lpFinish->vReverbPan.x) + lpResult->vReverbPan.x = lpStart->vReverbPan.x; + else + lpResult->vReverbPan.x = FinalVector.x + (flInvRatio * (StartVector.x - FinalVector.x)); + + if (lpStart->vReverbPan.y == lpFinish->vReverbPan.y) + lpResult->vReverbPan.y = lpStart->vReverbPan.y; + else + lpResult->vReverbPan.y = FinalVector.y + (flInvRatio * (StartVector.y - FinalVector.y)); + + if (lpStart->vReverbPan.z == lpFinish->vReverbPan.z) + lpResult->vReverbPan.z = lpStart->vReverbPan.z; + else + lpResult->vReverbPan.z = FinalVector.z + (flInvRatio * (StartVector.z - FinalVector.z)); + + // Echo Time + if (lpStart->flEchoTime == lpFinish->flEchoTime) + lpResult->flEchoTime = lpStart->flEchoTime; + else + lpResult->flEchoTime = (float)exp( (log(lpStart->flEchoTime) * flInvRatio) + (log(lpFinish->flEchoTime) * flRatio) ); + + // Echo Depth + if (lpStart->flEchoDepth == lpFinish->flEchoDepth) + lpResult->flEchoDepth = lpStart->flEchoDepth; + else + lpResult->flEchoDepth = (lpStart->flEchoDepth * flInvRatio) + (lpFinish->flEchoDepth * flRatio); + + // Modulation Time + if (lpStart->flModulationTime == lpFinish->flModulationTime) + lpResult->flModulationTime = lpStart->flModulationTime; + else + lpResult->flModulationTime = (float)exp( (log(lpStart->flModulationTime) * flInvRatio) + (log(lpFinish->flModulationTime) * flRatio) ); + + // Modulation Depth + if (lpStart->flModulationDepth == lpFinish->flModulationDepth) + lpResult->flModulationDepth = lpStart->flModulationDepth; + else + lpResult->flModulationDepth = (lpStart->flModulationDepth * flInvRatio) + (lpFinish->flModulationDepth * flRatio); + + // Air Absorption HF + if (lpStart->flAirAbsorptionHF == lpFinish->flAirAbsorptionHF) + lpResult->flAirAbsorptionHF = lpStart->flAirAbsorptionHF; + else + lpResult->flAirAbsorptionHF = (lpStart->flAirAbsorptionHF * flInvRatio) + (lpFinish->flAirAbsorptionHF * flRatio); + + // HF Reference + if (lpStart->flHFReference == lpFinish->flHFReference) + lpResult->flHFReference = lpStart->flHFReference; + else + lpResult->flHFReference = (float)exp( (log(lpStart->flHFReference) * flInvRatio) + (log(lpFinish->flHFReference) * flRatio) ); + + // LF Reference + if (lpStart->flLFReference == lpFinish->flLFReference) + lpResult->flLFReference = lpStart->flLFReference; + else + lpResult->flLFReference = (float)exp( (log(lpStart->flLFReference) * flInvRatio) + (log(lpFinish->flLFReference) * flRatio) ); + + // Room Rolloff Factor + if (lpStart->flRoomRolloffFactor == lpFinish->flRoomRolloffFactor) + lpResult->flRoomRolloffFactor = lpStart->flRoomRolloffFactor; + else + lpResult->flRoomRolloffFactor = (lpStart->flRoomRolloffFactor * flInvRatio) + (lpFinish->flRoomRolloffFactor * flRatio); + + // Flags + lpResult->ulFlags = (lpStart->ulFlags & lpFinish->ulFlags); + + // Clamp Delays + if (lpResult->flReflectionsDelay > EAXLISTENER_MAXREFLECTIONSDELAY) + lpResult->flReflectionsDelay = EAXLISTENER_MAXREFLECTIONSDELAY; + + if (lpResult->flReverbDelay > EAXLISTENER_MAXREVERBDELAY) + lpResult->flReverbDelay = EAXLISTENER_MAXREVERBDELAY; + + return true; +} + + +/* + CheckEAX3LP + Checks that the parameters in the EAX 3 Listener Properties structure are in-range +*/ +bool CheckEAX3LP(LPEAXLISTENERPROPERTIES lpEAX3LP) +{ + if ( (lpEAX3LP->lRoom < EAXLISTENER_MINROOM) || (lpEAX3LP->lRoom > EAXLISTENER_MAXROOM) ) + return false; + + if ( (lpEAX3LP->lRoomHF < EAXLISTENER_MINROOMHF) || (lpEAX3LP->lRoomHF > EAXLISTENER_MAXROOMHF) ) + return false; + + if ( (lpEAX3LP->lRoomLF < EAXLISTENER_MINROOMLF) || (lpEAX3LP->lRoomLF > EAXLISTENER_MAXROOMLF) ) + return false; + + if ( (lpEAX3LP->ulEnvironment < EAXLISTENER_MINENVIRONMENT) || (lpEAX3LP->ulEnvironment > EAXLISTENER_MAXENVIRONMENT) ) + return false; + + if ( (lpEAX3LP->flEnvironmentSize < EAXLISTENER_MINENVIRONMENTSIZE) || (lpEAX3LP->flEnvironmentSize > EAXLISTENER_MAXENVIRONMENTSIZE) ) + return false; + + if ( (lpEAX3LP->flEnvironmentDiffusion < EAXLISTENER_MINENVIRONMENTDIFFUSION) || (lpEAX3LP->flEnvironmentDiffusion > EAXLISTENER_MAXENVIRONMENTDIFFUSION) ) + return false; + + if ( (lpEAX3LP->flDecayTime < EAXLISTENER_MINDECAYTIME) || (lpEAX3LP->flDecayTime > EAXLISTENER_MAXDECAYTIME) ) + return false; + + if ( (lpEAX3LP->flDecayHFRatio < EAXLISTENER_MINDECAYHFRATIO) || (lpEAX3LP->flDecayHFRatio > EAXLISTENER_MAXDECAYHFRATIO) ) + return false; + + if ( (lpEAX3LP->flDecayLFRatio < EAXLISTENER_MINDECAYLFRATIO) || (lpEAX3LP->flDecayLFRatio > EAXLISTENER_MAXDECAYLFRATIO) ) + return false; + + if ( (lpEAX3LP->lReflections < EAXLISTENER_MINREFLECTIONS) || (lpEAX3LP->lReflections > EAXLISTENER_MAXREFLECTIONS) ) + return false; + + if ( (lpEAX3LP->flReflectionsDelay < EAXLISTENER_MINREFLECTIONSDELAY) || (lpEAX3LP->flReflectionsDelay > EAXLISTENER_MAXREFLECTIONSDELAY) ) + return false; + + if ( (lpEAX3LP->lReverb < EAXLISTENER_MINREVERB) || (lpEAX3LP->lReverb > EAXLISTENER_MAXREVERB) ) + return false; + + if ( (lpEAX3LP->flReverbDelay < EAXLISTENER_MINREVERBDELAY) || (lpEAX3LP->flReverbDelay > EAXLISTENER_MAXREVERBDELAY) ) + return false; + + if ( (lpEAX3LP->flEchoTime < EAXLISTENER_MINECHOTIME) || (lpEAX3LP->flEchoTime > EAXLISTENER_MAXECHOTIME) ) + return false; + + if ( (lpEAX3LP->flEchoDepth < EAXLISTENER_MINECHODEPTH) || (lpEAX3LP->flEchoDepth > EAXLISTENER_MAXECHODEPTH) ) + return false; + + if ( (lpEAX3LP->flModulationTime < EAXLISTENER_MINMODULATIONTIME) || (lpEAX3LP->flModulationTime > EAXLISTENER_MAXMODULATIONTIME) ) + return false; + + if ( (lpEAX3LP->flModulationDepth < EAXLISTENER_MINMODULATIONDEPTH) || (lpEAX3LP->flModulationDepth > EAXLISTENER_MAXMODULATIONDEPTH) ) + return false; + + if ( (lpEAX3LP->flAirAbsorptionHF < EAXLISTENER_MINAIRABSORPTIONHF) || (lpEAX3LP->flAirAbsorptionHF > EAXLISTENER_MAXAIRABSORPTIONHF) ) + return false; + + if ( (lpEAX3LP->flHFReference < EAXLISTENER_MINHFREFERENCE) || (lpEAX3LP->flHFReference > EAXLISTENER_MAXHFREFERENCE) ) + return false; + + if ( (lpEAX3LP->flLFReference < EAXLISTENER_MINLFREFERENCE) || (lpEAX3LP->flLFReference > EAXLISTENER_MAXLFREFERENCE) ) + return false; + + if ( (lpEAX3LP->flRoomRolloffFactor < EAXLISTENER_MINROOMROLLOFFFACTOR) || (lpEAX3LP->flRoomRolloffFactor > EAXLISTENER_MAXROOMROLLOFFFACTOR) ) + return false; + + if (lpEAX3LP->ulFlags & EAXLISTENERFLAGS_RESERVED) + return false; + + return true; +} + +/* + Clamp + Clamps the length of the vector to 1.0f +*/ +void Clamp(EAXVECTOR *eaxVector) +{ + float flMagnitude; + float flInvMagnitude; + + flMagnitude = (float)sqrt((eaxVector->x*eaxVector->x) + (eaxVector->y*eaxVector->y) + (eaxVector->z*eaxVector->z)); + + if (flMagnitude <= 1.0f) + return; + + flInvMagnitude = 1.0f / flMagnitude; + + eaxVector->x *= flInvMagnitude; + eaxVector->y *= flInvMagnitude; + eaxVector->z *= flInvMagnitude; +} + + +/***********************************************************************************************\ +* +* To assist those developers wishing to add EAX effects to their level editors, each of the + +* List of string names of the various EAX 3.0 presets defined in eax-util.h +* Arrays to group together presets of the same scenario +* +\***********************************************************************************************/ + + +////////////////////////////////////////////////////// +// Array of scenario names // +////////////////////////////////////////////////////// + +const char* EAX30_SCENARIO_NAMES[] = +{ + "Castle", + "Factory", + "IcePalace", + "SpaceStation", + "WoodenShip", + "Sports", + "Prefab", + "Domes and Pipes", + "Outdoors", + "Mood", + "Driving", + "City", + "Miscellaneous", + "Original" +}; + +////////////////////////////////////////////////////// +// Array of standardised location names // +////////////////////////////////////////////////////// + +const char* EAX30_LOCATION_NAMES[] = +{ + "Hall", + "Large Room", + "Medium Room", + "Small Room", + "Cupboard", + "Alcove", + "Long Passage", + "Short Passage", + "Courtyard" +}; + +////////////////////////////////////////////////////// +// Standardised Location effects can be accessed // +// from a matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_STANDARD_PRESETS[EAX30_NUM_STANDARD_SCENARIOS][EAX30_NUM_LOCATIONS]= +{ + {EAX30_PRESET_CASTLE_HALL, EAX30_PRESET_CASTLE_LARGEROOM, EAX30_PRESET_CASTLE_MEDIUMROOM, EAX30_PRESET_CASTLE_SMALLROOM, EAX30_PRESET_CASTLE_CUPBOARD, EAX30_PRESET_CASTLE_ALCOVE, EAX30_PRESET_CASTLE_LONGPASSAGE, EAX30_PRESET_CASTLE_SHORTPASSAGE, EAX30_PRESET_CASTLE_COURTYARD}, + {EAX30_PRESET_FACTORY_HALL, EAX30_PRESET_FACTORY_LARGEROOM, EAX30_PRESET_FACTORY_MEDIUMROOM, EAX30_PRESET_FACTORY_SMALLROOM, EAX30_PRESET_FACTORY_CUPBOARD, EAX30_PRESET_FACTORY_ALCOVE, EAX30_PRESET_FACTORY_LONGPASSAGE, EAX30_PRESET_FACTORY_SHORTPASSAGE, EAX30_PRESET_FACTORY_COURTYARD}, + {EAX30_PRESET_ICEPALACE_HALL, EAX30_PRESET_ICEPALACE_LARGEROOM, EAX30_PRESET_ICEPALACE_MEDIUMROOM, EAX30_PRESET_ICEPALACE_SMALLROOM, EAX30_PRESET_ICEPALACE_CUPBOARD, EAX30_PRESET_ICEPALACE_ALCOVE, EAX30_PRESET_ICEPALACE_LONGPASSAGE, EAX30_PRESET_ICEPALACE_SHORTPASSAGE, EAX30_PRESET_ICEPALACE_COURTYARD}, + {EAX30_PRESET_SPACESTATION_HALL,EAX30_PRESET_SPACESTATION_LARGEROOM,EAX30_PRESET_SPACESTATION_MEDIUMROOM, EAX30_PRESET_SPACESTATION_SMALLROOM,EAX30_PRESET_SPACESTATION_CUPBOARD, EAX30_PRESET_SPACESTATION_ALCOVE, EAX30_PRESET_SPACESTATION_LONGPASSAGE, EAX30_PRESET_SPACESTATION_SHORTPASSAGE, EAX30_PRESET_SPACESTATION_HALL}, + {EAX30_PRESET_WOODEN_HALL, EAX30_PRESET_WOODEN_LARGEROOM, EAX30_PRESET_WOODEN_MEDIUMROOM, EAX30_PRESET_WOODEN_SMALLROOM, EAX30_PRESET_WOODEN_CUPBOARD, EAX30_PRESET_WOODEN_ALCOVE, EAX30_PRESET_WOODEN_LONGPASSAGE, EAX30_PRESET_WOODEN_SHORTPASSAGE, EAX30_PRESET_WOODEN_COURTYARD}, +}; + + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of original environment names // +////////////////////////////////////////////////////// + +const char* EAX30_ORIGINAL_PRESET_NAMES[] = +{ + "Generic", + "Padded Cell", + "Room", + "Bathroom", + "Living Room", + "Stone Room", + "Auditorium", + "Concert Hall", + "Cave", + "Arena", + "Hangar", + "Carpetted Hallway", + "Hallway", + "Stone Corridor", + "Alley", + "Forest", + "City", + "Mountains", + "Quarry", + "Plain", + "Parking Lot", + "Sewer Pipe", + "Underwater", + "Drugged", + "Dizzy", + "Psychotic" +}; + +////////////////////////////////////////////////////// +// Sports effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_ORIGINAL_PRESETS[] = +{ + EAX30_PRESET_GENERIC, + EAX30_PRESET_PADDEDCELL, + EAX30_PRESET_ROOM, + EAX30_PRESET_BATHROOM, + EAX30_PRESET_LIVINGROOM, + EAX30_PRESET_STONEROOM, + EAX30_PRESET_AUDITORIUM, + EAX30_PRESET_CONCERTHALL, + EAX30_PRESET_CAVE, + EAX30_PRESET_ARENA, + EAX30_PRESET_HANGAR, + EAX30_PRESET_CARPETTEDHALLWAY, + EAX30_PRESET_HALLWAY, + EAX30_PRESET_STONECORRIDOR, + EAX30_PRESET_ALLEY, + EAX30_PRESET_FOREST, + EAX30_PRESET_CITY, + EAX30_PRESET_MOUNTAINS, + EAX30_PRESET_QUARRY, + EAX30_PRESET_PLAIN, + EAX30_PRESET_PARKINGLOT, + EAX30_PRESET_SEWERPIPE, + EAX30_PRESET_UNDERWATER, + EAX30_PRESET_DRUGGED, + EAX30_PRESET_DIZZY, + EAX30_PRESET_PSYCHOTIC +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of sport environment names // +////////////////////////////////////////////////////// + +const char* EAX30_SPORTS_PRESET_NAMES[] = +{ + "Empty Stadium", + "Full Stadium", + "Stadium Tannoy", + "Squash Court", + "Small Swimming Pool", + "Large Swimming Pool", + "Gymnasium" +}; + +////////////////////////////////////////////////////// +// Sports effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_SPORTS_PRESETS[] = +{ + EAX30_PRESET_SPORT_EMPTYSTADIUM, + EAX30_PRESET_SPORT_FULLSTADIUM, + EAX30_PRESET_SPORT_STADIUMTANNOY, + EAX30_PRESET_SPORT_SQUASHCOURT, + EAX30_PRESET_SPORT_SMALLSWIMMINGPOOL, + EAX30_PRESET_SPORT_LARGESWIMMINGPOOL, + EAX30_PRESET_SPORT_GYMNASIUM +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of prefab environment names // +////////////////////////////////////////////////////// + +const char* EAX30_PREFAB_PRESET_NAMES[] = +{ + "Workshop", + "School Room", + "Practise Room", + "Outhouse", + "Caravan" +}; + +////////////////////////////////////////////////////// +// Prefab effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_PREFAB_PRESETS[] = +{ + EAX30_PRESET_PREFAB_WORKSHOP, + EAX30_PRESET_PREFAB_SCHOOLROOM, + EAX30_PRESET_PREFAB_PRACTISEROOM, + EAX30_PRESET_PREFAB_OUTHOUSE, + EAX30_PRESET_PREFAB_CARAVAN +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of Domes & Pipes environment names // +////////////////////////////////////////////////////// + +const char* EAX30_DOMESNPIPES_PRESET_NAMES[] = +{ + "Domed Tomb", + "Saint Paul's Dome", + "Small Pipe", + "Long Thin Pipe", + "Large Pipe", + "Resonant Pipe" +}; + +////////////////////////////////////////////////////// +// Domes & Pipes effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_DOMESNPIPES_PRESETS[] = +{ + EAX30_PRESET_DOME_TOMB, + EAX30_PRESET_DOME_SAINTPAULS, + EAX30_PRESET_PIPE_SMALL, + EAX30_PRESET_PIPE_LONGTHIN, + EAX30_PRESET_PIPE_LARGE, + EAX30_PRESET_PIPE_RESONANT +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of Outdoors environment names // +////////////////////////////////////////////////////// + +const char* EAX30_OUTDOORS_PRESET_NAMES[] = +{ + "Backyard", + "Rolling Plains", + "Deep Canyon", + "Creek", + "Valley" +}; + +////////////////////////////////////////////////////// +// Outdoors effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_OUTDOORS_PRESETS[] = +{ + EAX30_PRESET_OUTDOORS_BACKYARD, + EAX30_PRESET_OUTDOORS_ROLLINGPLAINS, + EAX30_PRESET_OUTDOORS_DEEPCANYON, + EAX30_PRESET_OUTDOORS_CREEK, + EAX30_PRESET_OUTDOORS_VALLEY +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of Mood environment names // +////////////////////////////////////////////////////// + +const char* EAX30_MOOD_PRESET_NAMES[] = +{ + "Heaven", + "Hell", + "Memory" +}; + +////////////////////////////////////////////////////// +// Mood effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_MOOD_PRESETS[] = +{ + EAX30_PRESET_MOOD_HEAVEN, + EAX30_PRESET_MOOD_HELL, + EAX30_PRESET_MOOD_MEMORY +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of driving environment names // +////////////////////////////////////////////////////// + +const char* EAX30_DRIVING_PRESET_NAMES[] = +{ + "Race Commentator", + "Pit Garage", + "In-car (Stripped out racer)", + "In-car (Sportscar)", + "In-car (Luxury)", + "Full Grandstand", + "Empty Grandstand", + "Tunnel" +}; + +////////////////////////////////////////////////////// +// Driving effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_DRIVING_PRESETS[] = +{ + EAX30_PRESET_DRIVING_COMMENTATOR, + EAX30_PRESET_DRIVING_PITGARAGE, + EAX30_PRESET_DRIVING_INCAR_RACER, + EAX30_PRESET_DRIVING_INCAR_SPORTS, + EAX30_PRESET_DRIVING_INCAR_LUXURY, + EAX30_PRESET_DRIVING_FULLGRANDSTAND, + EAX30_PRESET_DRIVING_EMPTYGRANDSTAND, + EAX30_PRESET_DRIVING_TUNNEL +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of City environment names // +////////////////////////////////////////////////////// + +const char* EAX30_CITY_PRESET_NAMES[] = +{ + "City Streets", + "Subway", + "Museum", + "Library", + "Underpass", + "Abandoned City" +}; + +////////////////////////////////////////////////////// +// City effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_CITY_PRESETS[] = +{ + EAX30_PRESET_CITY_STREETS, + EAX30_PRESET_CITY_SUBWAY, + EAX30_PRESET_CITY_MUSEUM, + EAX30_PRESET_CITY_LIBRARY, + EAX30_PRESET_CITY_UNDERPASS, + EAX30_PRESET_CITY_ABANDONED +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of Misc environment names // +////////////////////////////////////////////////////// + +const char* EAX30_MISC_PRESET_NAMES[] = +{ + "Dusty Box Room", + "Chapel", + "Small Water Room" +}; + +////////////////////////////////////////////////////// +// Misc effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_MISC_PRESETS[] = +{ + EAX30_PRESET_DUSTYROOM, + EAX30_PRESET_CHAPEL, + EAX30_PRESET_SMALLWATERROOM +}; + diff --git a/src/audio/eax/eax-util.h b/src/audio/eax/eax-util.h new file mode 100644 index 0000000..441f011 --- /dev/null +++ b/src/audio/eax/eax-util.h @@ -0,0 +1,765 @@ +/*******************************************************************\ +* * +* EAX-UTIL.H - utilities for Environmental Audio Extensions v. 3.0 * +* Definitions of the Original 26 EAX Presets * +* Definitions for some new EAX Presets * +* Definitions of some Material Presets * +* Function declaration for EAX Morphing * +* * +\*******************************************************************/ + +#ifndef EAXUTIL_INCLUDED +#define EAXUTIL_INCLUDED + +#include + +/*********************************************************************************************** +* Function : EAX3ListenerInterpolate +* Params : lpStart - Initial EAX 3 Listener parameters +* : lpFinish - Final EAX 3 Listener parameters +* : flRatio - Ratio Destination : Source (0.0 == Source, 1.0 == Destination) +* : lpResult - Interpolated EAX 3 Listener parameters +* : bCheckValues - Check EAX 3.0 parameters are in range, + - default == false (no checking) +************************************************************************************************/ +bool EAX3ListenerInterpolate(EAXLISTENERPROPERTIES *lpStartEAX3LP, EAXLISTENERPROPERTIES *lpFinishEAX3LP, + float flRatio, EAXLISTENERPROPERTIES *lpResultEAX3LP, bool bCheckValues = false); + + +/***********************************************************************************************\ +* +* Legacy environment presets for use with DSPROPERTY_EAXLISTENER_ALLPARAMETERS. +* Each array conforms to the DSPROPSETID_EAX30_ListenerProperties structure defined in EAX.H. +* +************************************************************************************************/ + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_GENERIC \ + {0, 7.5f, 1.000f, -1000, -100, 0, 1.49f, 0.83f, 1.00f, -2602, 0.007f, 0.00f,0.00f,0.00f, 200, 0.011f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_PADDEDCELL \ + {1, 1.4f, 1.000f, -1000, -6000, 0, 0.17f, 0.10f, 1.00f, -1204, 0.001f, 0.00f,0.00f,0.00f, 207, 0.002f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_ROOM \ + {2, 1.9f, 1.000f, -1000, -454, 0, 0.40f, 0.83f, 1.00f, -1646, 0.002f, 0.00f,0.00f,0.00f, 53, 0.003f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_BATHROOM \ + {3, 1.4f, 1.000f, -1000, -1200, 0, 1.49f, 0.54f, 1.00f, -370, 0.007f, 0.00f,0.00f,0.00f, 1030, 0.011f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_LIVINGROOM \ + {4, 2.5f, 1.000f, -1000, -6000, 0, 0.50f, 0.10f, 1.00f, -1376, 0.003f, 0.00f,0.00f,0.00f, -1104, 0.004f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_STONEROOM \ + {5, 11.6f, 1.000f, -1000, -300, 0, 2.31f, 0.64f, 1.00f, -711, 0.012f, 0.00f,0.00f,0.00f, 83, 0.017f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_AUDITORIUM \ + {6, 21.6f, 1.000f, -1000, -476, 0, 4.32f, 0.59f, 1.00f, -789, 0.020f, 0.00f,0.00f,0.00f, -289, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_CONCERTHALL \ + {7, 19.6f, 1.000f, -1000, -500, 0, 3.92f, 0.70f, 1.00f, -1230, 0.020f, 0.00f,0.00f,0.00f, -02, 0.029f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_CAVE \ + {8, 14.6f, 1.000f, -1000, 0, 0, 2.91f, 1.30f, 1.00f, -602, 0.015f, 0.00f,0.00f,0.00f, -302, 0.022f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_ARENA \ + {9, 36.2f, 1.000f, -1000, -698, 0, 7.24f, 0.33f, 1.00f, -1166, 0.020f, 0.00f,0.00f,0.00f, 16, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_HANGAR \ + {10, 50.3f, 1.000f, -1000, -1000, 0, 10.05f, 0.23f, 1.00f, -602, 0.020f, 0.00f,0.00f,0.00f, 198, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_CARPETTEDHALLWAY \ + {11, 1.9f, 1.000f, -1000, -4000, 0, 0.30f, 0.10f, 1.00f, -1831, 0.002f, 0.00f,0.00f,0.00f, -1630, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_HALLWAY \ + {12, 1.8f, 1.000f, -1000, -300, 0, 1.49f, 0.59f, 1.00f, -1219, 0.007f, 0.00f,0.00f,0.00f, 441, 0.011f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_STONECORRIDOR \ + {13, 13.5f, 1.000f, -1000, -237, 0, 2.70f, 0.79f, 1.00f, -1214, 0.013f, 0.00f,0.00f,0.00f, 395, 0.020f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_ALLEY \ + {14, 7.5f, 0.300f, -1000, -270, 0, 1.49f, 0.86f, 1.00f, -1204, 0.007f, 0.00f,0.00f,0.00f, -4, 0.011f, 0.00f,0.00f,0.00f, 0.125f, 0.950f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_FOREST \ + {15, 38.0f, 0.300f, -1000, -3300, 0, 1.49f, 0.54f, 1.00f, -2560, 0.162f, 0.00f,0.00f,0.00f, -229, 0.088f, 0.00f,0.00f,0.00f, 0.125f, 1.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_CITY \ + {16, 7.5f, 0.500f, -1000, -800, 0, 1.49f, 0.67f, 1.00f, -2273, 0.007f, 0.00f,0.00f,0.00f, -1691, 0.011f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_MOUNTAINS \ + {17, 100.0f, 0.270f, -1000, -2500, 0, 1.49f, 0.21f, 1.00f, -2780, 0.300f, 0.00f,0.00f,0.00f, -1434, 0.100f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_QUARRY \ + {18, 17.5f, 1.000f, -1000, -1000, 0, 1.49f, 0.83f, 1.00f, -10000, 0.061f, 0.00f,0.00f,0.00f, 500, 0.025f, 0.00f,0.00f,0.00f, 0.125f, 0.700f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_PLAIN \ + {19, 42.5f, 0.210f, -1000, -2000, 0, 1.49f, 0.50f, 1.00f, -2466, 0.179f, 0.00f,0.00f,0.00f, -1926, 0.100f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_PARKINGLOT \ + {20, 8.3f, 1.000f, -1000, 0, 0, 1.65f, 1.50f, 1.00f, -1363, 0.008f, 0.00f,0.00f,0.00f, -1153, 0.012f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_SEWERPIPE \ + {21, 1.7f, 0.800f, -1000, -1000, 0, 2.81f, 0.14f, 1.00f, 429, 0.014f, 0.00f,0.00f,0.00f, 1023, 0.021f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_UNDERWATER \ + {22, 1.8f, 1.000f, -1000, -4000, 0, 1.49f, 0.10f, 1.00f, -449, 0.007f, 0.00f,0.00f,0.00f, 1700, 0.011f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 1.180f, 0.348f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_DRUGGED \ + {23, 1.9f, 0.500f, -1000, 0, 0, 8.39f, 1.39f, 1.00f, -115, 0.002f, 0.00f,0.00f,0.00f, 985, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 1.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_DIZZY \ + {24, 1.8f, 0.600f, -1000, -400, 0, 17.23f, 0.56f, 1.00f, -1713, 0.020f, 0.00f,0.00f,0.00f, -613, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.810f, 0.310f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_PSYCHOTIC \ + {25, 1.0f, 0.500f, -1000, -151, 0, 7.56f, 0.91f, 1.00f, -626, 0.020f, 0.00f,0.00f,0.00f, 774, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 4.000f, 1.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } + + +/***********************************************************************************************\ +* +* New environment presets for use with DSPROPERTY_EAXLISTENER_ALLPARAMETERS. +* Each array conforms to the DSPROPSETID_EAX30_ListenerProperties structure defined in EAX.H. +* +************************************************************************************************/ + +// STANDARDISED-LOCATION SCENARIOS + +// CASTLE PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_CASTLE_SMALLROOM \ + { 26, 8.3f, 0.890f, -1100, -800, -2000, 1.22f, 0.83f, 0.31f, -100, 0.022f, 0.00f,0.00f,0.00f, 0, 0.011f, 0.00f,0.00f,0.00f, 0.138f, 0.080f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_SHORTPASSAGE \ + { 26, 8.3f, 0.890f, -1000, -1000, -2000, 2.32f, 0.83f, 0.31f, -100, 0.007f, 0.00f,0.00f,0.00f, -500, 0.023f, 0.00f,0.00f,0.00f, 0.138f, 0.080f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_MEDIUMROOM \ + { 26, 8.3f, 0.930f, -1000, -1100, -2000, 2.04f, 0.83f, 0.46f, -300, 0.022f, 0.00f,0.00f,0.00f, -200, 0.011f, 0.00f,0.00f,0.00f, 0.155f, 0.030f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_LONGPASSAGE \ + { 26, 8.3f, 0.890f, -1000, -800, -2000, 3.42f, 0.83f, 0.31f, -200, 0.007f, 0.00f,0.00f,0.00f, -600, 0.023f, 0.00f,0.00f,0.00f, 0.138f, 0.080f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_LARGEROOM \ + { 26, 8.3f, 0.820f, -1000, -1100, -1800, 2.53f, 0.83f, 0.50f, -900, 0.034f, 0.00f,0.00f,0.00f, -400, 0.016f, 0.00f,0.00f,0.00f, 0.185f, 0.070f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_HALL \ + { 26, 8.3f, 0.810f, -1000, -1100, -1500, 3.14f, 0.79f, 0.62f, -1300, 0.056f, 0.00f,0.00f,0.00f, -500, 0.024f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_CUPBOARD \ + { 26, 8.3f, 0.890f, -1000, -1100, -2000, 0.67f, 0.87f, 0.31f, 300, 0.010f, 0.00f,0.00f,0.00f, 300, 0.007f, 0.00f,0.00f,0.00f, 0.138f, 0.080f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_COURTYARD \ + { 26, 8.3f, 0.420f, -1100, -700, -900, 2.13f, 0.61f, 0.23f, -2300, 0.112f, 0.00f,0.00f,0.00f, -1500, 0.036f, 0.00f,0.00f,0.00f, 0.250f, 0.370f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_CASTLE_ALCOVE \ + { 26, 8.3f, 0.890f, -1000, -600, -2000, 1.64f, 0.87f, 0.31f, -100, 0.007f, 0.00f,0.00f,0.00f, -500, 0.034f, 0.00f,0.00f,0.00f, 0.138f, 0.080f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } + + +// FACTORY PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_FACTORY_ALCOVE \ + { 26, 1.8f, 0.590f, -1200, -200, -600, 3.14f, 0.65f, 1.31f, 300, 0.010f, 0.00f,0.00f,0.00f, -1200, 0.038f, 0.00f,0.00f,0.00f, 0.114f, 0.100f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_SHORTPASSAGE \ + { 26, 1.8f, 0.640f, -1200, -200, -600, 2.53f, 0.65f, 1.31f, 0, 0.010f, 0.00f,0.00f,0.00f, -600, 0.038f, 0.00f,0.00f,0.00f, 0.135f, 0.230f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_MEDIUMROOM \ + { 26, 1.9f, 0.820f, -1200, -200, -600, 2.76f, 0.65f, 1.31f, -1100, 0.022f, 0.00f,0.00f,0.00f, -400, 0.023f, 0.00f,0.00f,0.00f, 0.174f, 0.070f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_LONGPASSAGE \ + { 26, 1.8f, 0.640f, -1200, -200, -600, 4.06f, 0.65f, 1.31f, 0, 0.020f, 0.00f,0.00f,0.00f, -900, 0.037f, 0.00f,0.00f,0.00f, 0.135f, 0.230f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_LARGEROOM \ + { 26, 1.9f, 0.750f, -1200, -300, -400, 4.24f, 0.51f, 1.31f, -1500, 0.039f, 0.00f,0.00f,0.00f, -600, 0.023f, 0.00f,0.00f,0.00f, 0.231f, 0.070f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_HALL \ + { 26, 1.9f, 0.750f, -1000, -300, -400, 7.43f, 0.51f, 1.31f, -2400, 0.073f, 0.00f,0.00f,0.00f, -500, 0.027f, 0.00f,0.00f,0.00f, 0.250f, 0.070f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_CUPBOARD \ + { 26, 1.7f, 0.630f, -1200, -200, -600, 0.49f, 0.65f, 1.31f, 200, 0.010f, 0.00f,0.00f,0.00f, 200, 0.032f, 0.00f,0.00f,0.00f, 0.107f, 0.070f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_COURTYARD \ + { 26, 1.7f, 0.570f, -1000, -1000, -400, 2.32f, 0.29f, 0.56f, -2400, 0.090f, 0.00f,0.00f,0.00f, -2000, 0.039f, 0.00f,0.00f,0.00f, 0.250f, 0.290f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_SMALLROOM \ + { 26, 1.8f, 0.820f, -1200, -200, -600, 1.72f, 0.65f, 1.31f, -300, 0.010f, 0.00f,0.00f,0.00f, -200, 0.024f, 0.00f,0.00f,0.00f, 0.119f, 0.070f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } + +// ICE PALACE PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_ICEPALACE_ALCOVE \ + { 26, 2.7f, 0.840f, -1000, -500, -1100, 2.76f, 1.46f, 0.28f, 100, 0.010f, 0.00f,0.00f,0.00f, -1200, 0.030f, 0.00f,0.00f,0.00f, 0.161f, 0.090f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_SHORTPASSAGE \ + { 26, 2.7f, 0.750f, -1000, -500, -1100, 1.79f, 1.46f, 0.28f, -600, 0.010f, 0.00f,0.00f,0.00f, -700, 0.019f, 0.00f,0.00f,0.00f, 0.177f, 0.090f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_MEDIUMROOM \ + { 26, 2.7f, 0.870f, -1000, -500, -700, 2.22f, 1.53f, 0.32f, -800, 0.039f, 0.00f,0.00f,0.00f, -1200, 0.027f, 0.00f,0.00f,0.00f, 0.186f, 0.120f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_LONGPASSAGE \ + { 26, 2.7f, 0.770f, -1000, -500, -800, 3.01f, 1.46f, 0.28f, -200, 0.012f, 0.00f,0.00f,0.00f, -800, 0.025f, 0.00f,0.00f,0.00f, 0.186f, 0.040f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_LARGEROOM \ + { 26, 2.9f, 0.810f, -1000, -500, -700, 3.14f, 1.53f, 0.32f, -1200, 0.039f, 0.00f,0.00f,0.00f, -1300, 0.027f, 0.00f,0.00f,0.00f, 0.214f, 0.110f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_HALL \ + { 26, 2.9f, 0.760f, -1000, -700, -500, 5.49f, 1.53f, 0.38f, -1900, 0.054f, 0.00f,0.00f,0.00f, -1400, 0.052f, 0.00f,0.00f,0.00f, 0.226f, 0.110f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_CUPBOARD \ + { 26, 2.7f, 0.830f, -1000, -600, -1300, 0.76f, 1.53f, 0.26f, 100, 0.012f, 0.00f,0.00f,0.00f, 100, 0.016f, 0.00f,0.00f,0.00f, 0.143f, 0.080f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_COURTYARD \ + { 26, 2.9f, 0.590f, -1000, -1100, -1000, 2.04f, 1.20f, 0.38f, -2000, 0.073f, 0.00f,0.00f,0.00f, -2200, 0.043f, 0.00f,0.00f,0.00f, 0.235f, 0.480f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_SMALLROOM \ + { 26, 2.7f, 0.840f, -1000, -500, -1100, 1.51f, 1.53f, 0.27f, -100, 0.010f, 0.00f,0.00f,0.00f, -900, 0.011f, 0.00f,0.00f,0.00f, 0.164f, 0.140f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } + +// SPACE STATION PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_SPACESTATION_ALCOVE \ + { 26, 1.5f, 0.780f, -1100, -300, -100, 1.16f, 0.81f, 0.55f, 300, 0.007f, 0.00f,0.00f,0.00f, -500, 0.018f, 0.00f,0.00f,0.00f, 0.192f, 0.210f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_MEDIUMROOM \ + { 26, 1.5f, 0.750f, -1000, -400, -100, 3.01f, 0.50f, 0.55f, -1000, 0.034f, 0.00f,0.00f,0.00f, -700, 0.035f, 0.00f,0.00f,0.00f, 0.209f, 0.310f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_SHORTPASSAGE \ + { 26, 1.5f, 0.870f, -1000, -400, -100, 3.57f, 0.50f, 0.55f, 0, 0.012f, 0.00f,0.00f,0.00f, -600, 0.016f, 0.00f,0.00f,0.00f, 0.172f, 0.200f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_LONGPASSAGE \ + { 26, 1.9f, 0.820f, -1000, -400, -100, 4.62f, 0.62f, 0.55f, 0, 0.012f, 0.00f,0.00f,0.00f, -800, 0.031f, 0.00f,0.00f,0.00f, 0.250f, 0.230f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_LARGEROOM \ + { 26, 1.8f, 0.810f, -1000, -400, -100, 3.89f, 0.38f, 0.61f, -1200, 0.056f, 0.00f,0.00f,0.00f, -800, 0.035f, 0.00f,0.00f,0.00f, 0.233f, 0.280f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_HALL \ + { 26, 1.9f, 0.870f, -1000, -400, -100, 7.11f, 0.38f, 0.61f, -1500, 0.100f, 0.00f,0.00f,0.00f, -1000, 0.047f, 0.00f,0.00f,0.00f, 0.250f, 0.250f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_CUPBOARD \ + { 26, 1.4f, 0.560f, -1000, -300, -100, 0.79f, 0.81f, 0.55f, 200, 0.007f, 0.00f,0.00f,0.00f, 400, 0.018f, 0.00f,0.00f,0.00f, 0.181f, 0.310f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_SMALLROOM \ + { 26, 1.5f, 0.700f, -1000, -300, -100, 1.72f, 0.82f, 0.55f, -400, 0.007f, 0.00f,0.00f,0.00f, -500, 0.013f, 0.00f,0.00f,0.00f, 0.188f, 0.260f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } + +// WOODEN GALLEON PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_WOODEN_ALCOVE \ + { 26, 7.5f, 1.000f, -1100, -1800, -1000, 1.22f, 0.62f, 0.91f, -100, 0.012f, 0.00f,0.00f,0.00f, -600, 0.024f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_SHORTPASSAGE \ + { 26, 7.5f, 1.000f, -1100, -1800, -1000, 1.45f, 0.50f, 0.87f, -300, 0.012f, 0.00f,0.00f,0.00f, -700, 0.024f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_MEDIUMROOM \ + { 26, 7.5f, 1.000f, -1200, -2000, -1100, 1.07f, 0.42f, 0.82f, -300, 0.039f, 0.00f,0.00f,0.00f, -400, 0.029f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_LONGPASSAGE \ + { 26, 7.5f, 1.000f, -1100, -2000, -1000, 1.79f, 0.40f, 0.79f, -200, 0.020f, 0.00f,0.00f,0.00f, -1000, 0.036f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_LARGEROOM \ + { 26, 7.5f, 1.000f, -1200, -2100, -1100, 1.45f, 0.33f, 0.82f, -300, 0.056f, 0.00f,0.00f,0.00f, -500, 0.049f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_HALL \ + { 26, 7.5f, 1.000f, -1200, -2200, -1100, 1.95f, 0.30f, 0.82f, -300, 0.068f, 0.00f,0.00f,0.00f, -500, 0.063f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_CUPBOARD \ + { 26, 7.5f, 1.000f, -1000, -1700, -1000, 0.56f, 0.46f, 0.91f, -100, 0.012f, 0.00f,0.00f,0.00f, -100, 0.028f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_SMALLROOM \ + { 26, 7.5f, 1.000f, -1200, -1900, -1000, 0.79f, 0.32f, 0.87f, -200, 0.032f, 0.00f,0.00f,0.00f, -300, 0.029f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_COURTYARD \ + { 26, 7.5f, 0.650f, -1700, -2200, -1000, 1.79f, 0.35f, 0.79f, -700, 0.063f, 0.00f,0.00f,0.00f, -2300, 0.032f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } + + +// OTHER SCENARIOS + +// SPORTS PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_SPORT_EMPTYSTADIUM \ + { 26, 7.2f, 1.000f, -1300, -700, -200, 6.26f, 0.51f, 1.10f, -2400, 0.183f, 0.00f,0.00f,0.00f, -1100, 0.038f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_SPORT_SQUASHCOURT \ + { 26, 7.5f, 0.750f, -1100, -1000, -200, 2.22f, 0.91f, 1.16f, -700, 0.007f, 0.00f,0.00f,0.00f, -300, 0.011f, 0.00f,0.00f,0.00f, 0.126f, 0.190f, 0.250f, 0.000f, -0.0f, 7176.9f, 211.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPORT_SMALLSWIMMINGPOOL \ + { 26, 36.2f, 0.700f, -1400, -200, -100, 2.76f, 1.25f, 1.14f, -400, 0.020f, 0.00f,0.00f,0.00f, -300, 0.030f, 0.00f,0.00f,0.00f, 0.179f, 0.150f, 0.895f, 0.190f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x0 } +#define EAX30_PRESET_SPORT_LARGESWIMMINGPOOL\ + { 26, 36.2f, 0.820f, -1200, -200, 0, 5.49f, 1.31f, 1.14f, -700, 0.039f, 0.00f,0.00f,0.00f, -800, 0.049f, 0.00f,0.00f,0.00f, 0.222f, 0.550f, 1.159f, 0.210f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x0 } +#define EAX30_PRESET_SPORT_GYMNASIUM \ + { 26, 7.5f, 0.810f, -1200, -700, -100, 3.14f, 1.06f, 1.35f, -800, 0.029f, 0.00f,0.00f,0.00f, -700, 0.045f, 0.00f,0.00f,0.00f, 0.146f, 0.140f, 0.250f, 0.000f, -0.0f, 7176.9f, 211.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPORT_FULLSTADIUM \ + { 26, 7.2f, 1.000f, -1300, -2300, -200, 5.25f, 0.17f, 0.80f, -2000, 0.188f, 0.00f,0.00f,0.00f, -1300, 0.038f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_SPORT_STADIUMTANNOY \ + { 26, 3.0f, 0.780f, -900, -500, -600, 2.53f, 0.88f, 0.68f, -1100, 0.230f, 0.00f,0.00f,0.00f, -600, 0.063f, 0.00f,0.00f,0.00f, 0.250f, 0.200f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } + +// PREFAB PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_PREFAB_WORKSHOP \ + { 26, 1.9f, 1.000f, -1000, -1700, -800, 0.76f, 1.00f, 1.00f, 0, 0.012f, 0.00f,0.00f,0.00f, -200, 0.012f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x0 } +#define EAX30_PRESET_PREFAB_SCHOOLROOM \ + { 26, 1.86f, 0.690f, -1100, -400, -600, 0.98f, 0.45f, 0.18f, 300, 0.017f, 0.00f,0.00f,0.00f, 0, 0.015f, 0.00f,0.00f,0.00f, 0.095f, 0.140f, 0.250f, 0.000f, -0.0f, 7176.9f, 211.2f, 0.00f, 0x20 } +#define EAX30_PRESET_PREFAB_PRACTISEROOM \ + { 26, 1.86f, 0.870f, -1000, -800, -600, 1.12f, 0.56f, 0.18f, 200, 0.010f, 0.00f,0.00f,0.00f, -200, 0.011f, 0.00f,0.00f,0.00f, 0.095f, 0.140f, 0.250f, 0.000f, -0.0f, 7176.9f, 211.2f, 0.00f, 0x20 } +#define EAX30_PRESET_PREFAB_OUTHOUSE \ + { 26, 80.3f, 0.820f, -1100, -1900, -1600, 1.38f, 0.38f, 0.35f, -100, 0.024f, 0.00f,0.00f,-0.00f, -800, 0.044f, 0.00f,0.00f,0.00f, 0.121f, 0.170f, 0.250f, 0.000f, -0.0f, 2854.4f, 107.5f, 0.00f, 0x0 } +#define EAX30_PRESET_PREFAB_CARAVAN \ + { 26, 8.3f, 1.000f, -1000, -2100, -1800, 0.43f, 1.50f, 1.00f, 0, 0.012f, 0.00f,0.00f,0.00f, 400, 0.012f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } + // for US developers, a caravan is the same as a trailer =o) + + +// DOME AND PIPE PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_DOME_TOMB \ + { 26, 51.8f, 0.790f, -1000, -900, -1300, 4.18f, 0.21f, 0.10f, -825, 0.030f, 0.00f,0.00f,0.00f, -125, 0.022f, 0.00f,0.00f,0.00f, 0.177f, 0.190f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x0 } +#define EAX30_PRESET_PIPE_SMALL \ + { 26, 50.3f, 1.000f, -1000, -900, -1300, 5.04f, 0.10f, 0.10f, -600, 0.032f, 0.00f,0.00f,0.00f, 400, 0.015f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x3f } +#define EAX30_PRESET_DOME_SAINTPAULS \ + { 26, 50.3f, 0.870f, -1000, -900, -1300, 10.48f, 0.19f, 0.10f, -1500, 0.090f, 0.00f,0.00f,0.00f, -500, 0.042f, 0.00f,0.00f,0.00f, 0.250f, 0.120f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x3f } +#define EAX30_PRESET_PIPE_LONGTHIN \ + { 26, 1.6f, 0.910f, -1200, -700, -1100, 9.21f, 0.18f, 0.10f, -300, 0.010f, 0.00f,0.00f,0.00f, -1000, 0.022f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x0 } +#define EAX30_PRESET_PIPE_LARGE \ + { 26, 50.3f, 1.000f, -1000, -900, -1300, 8.45f, 0.10f, 0.10f, -800, 0.046f, 0.00f,0.00f,0.00f, 0, 0.032f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x3f } +#define EAX30_PRESET_PIPE_RESONANT \ + { 26, 1.3f, 0.910f, -1200, -700, -1100, 6.81f, 0.18f, 0.10f, -300, 0.010f, 0.00f,0.00f,0.00f, -700, 0.022f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x0 } + +// OUTDOORS PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_OUTDOORS_BACKYARD \ + { 26, 80.3f, 0.450f, -1100, -1200, -600, 1.12f, 0.34f, 0.46f, -1100, 0.049f, 0.00f,0.00f,-0.00f, -1300, 0.023f, 0.00f,0.00f,0.00f, 0.218f, 0.340f, 0.250f, 0.000f, -5.0f, 4399.1f, 242.9f, 0.00f, 0x0 } +#define EAX30_PRESET_OUTDOORS_ROLLINGPLAINS \ + { 26, 80.3f, 0.000f, -1100, -3900, -400, 2.13f, 0.21f, 0.46f, -2000, 0.300f, 0.00f,0.00f,-0.00f, -1500, 0.019f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.250f, 0.000f, -5.0f, 4399.1f, 242.9f, 0.00f, 0x0 } +#define EAX30_PRESET_OUTDOORS_DEEPCANYON \ + { 26, 80.3f, 0.740f, -1100, -1500, -400, 3.89f, 0.21f, 0.46f, -2000, 0.193f, 0.00f,0.00f,-0.00f, -1100, 0.019f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.250f, 0.000f, -5.0f, 4399.1f, 242.9f, 0.00f, 0x0 } +#define EAX30_PRESET_OUTDOORS_CREEK \ + { 26, 80.3f, 0.350f, -1100, -1500, -600, 2.13f, 0.21f, 0.46f, -1700, 0.115f, 0.00f,0.00f,-0.00f, -1100, 0.031f, 0.00f,0.00f,0.00f, 0.218f, 0.340f, 0.250f, 0.000f, -5.0f, 4399.1f, 242.9f, 0.00f, 0x0 } +#define EAX30_PRESET_OUTDOORS_VALLEY \ + { 26, 80.3f, 0.280f, -1100, -3100, -1600, 2.88f, 0.26f, 0.35f, -3200, 0.163f, 0.00f,0.00f,-0.00f, -1000, 0.100f, 0.00f,0.00f,0.00f, 0.250f, 0.340f, 0.250f, 0.000f, -0.0f, 2854.4f, 107.5f, 0.00f, 0x0 } + + +// MOOD PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_MOOD_HEAVEN \ + { 26, 19.6f, 0.940f, -1000, -200, -700, 5.04f, 1.12f, 0.56f, -1230, 0.020f, 0.00f,0.00f,0.00f, -200, 0.029f, 0.00f,0.00f,0.00f, 0.250f, 0.080f, 2.742f, 0.050f, -2.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_MOOD_HELL \ + { 26, 100.0f, 0.570f, -1000, -900, -700, 3.57f, 0.49f, 2.00f, -10000, 0.020f, 0.00f,0.00f,0.00f, 100, 0.030f, 0.00f,0.00f,0.00f, 0.110f, 0.040f, 2.109f, 0.520f, -5.0f, 5000.0f, 139.5f, 0.00f, 0x40 } +#define EAX30_PRESET_MOOD_MEMORY \ + { 26, 8.0f, 0.850f, -1000, -400, -900, 4.06f, 0.82f, 0.56f, -2800, 0.000f, 0.00f,0.00f,0.00f, -500, 0.000f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.474f, 0.450f, -2.0f, 5000.0f, 250.0f, 0.00f, 0x0 } + +// DRIVING SIMULATION PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_DRIVING_COMMENTATOR \ + { 26, 3.0f, 0.000f, -900, -500, -600, 2.42f, 0.88f, 0.68f, -1400, 0.093f, 0.00f,0.00f,0.00f, -1200, 0.017f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_DRIVING_PITGARAGE \ + { 26, 1.9f, 0.590f, -1400, -300, -500, 1.72f, 0.93f, 0.87f, -500, 0.000f, 0.00f,0.00f,0.00f, 0, 0.016f, 0.00f,0.00f,0.00f, 0.250f, 0.110f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x0 } +#define EAX30_PRESET_DRIVING_INCAR_RACER \ + { 26, 1.1f, 0.800f, -700, 0, -200, 0.17f, 2.00f, 0.41f, 500, 0.007f, 0.00f,0.00f,0.00f, -500, 0.015f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -0.0f, 10268.2f, 251.0f, 0.00f, 0x20 } +#define EAX30_PRESET_DRIVING_INCAR_SPORTS \ + { 26, 1.1f, 0.800f, -900, -400, 0, 0.17f, 0.75f, 0.41f, 0, 0.010f, 0.00f,0.00f,0.00f, -600, 0.000f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -0.0f, 10268.2f, 251.0f, 0.00f, 0x20 } +#define EAX30_PRESET_DRIVING_INCAR_LUXURY \ + { 26, 1.6f, 1.000f, -800, -2000, -600, 0.13f, 0.41f, 0.46f, -200, 0.010f, 0.00f,0.00f,0.00f, 300, 0.010f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -0.0f, 10268.2f, 251.0f, 0.00f, 0x20 } +#define EAX30_PRESET_DRIVING_FULLGRANDSTAND \ + { 26, 8.3f, 1.000f, -1100, -1100, -400, 3.01f, 1.37f, 1.28f, -900, 0.090f, 0.00f,0.00f,0.00f, -1700, 0.049f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 10420.2f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_DRIVING_EMPTYGRANDSTAND \ + { 26, 8.3f, 1.000f, -700, 0, -200, 4.62f, 1.75f, 1.40f, -1363, 0.090f, 0.00f,0.00f,0.00f, -1900, 0.049f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 10420.2f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_DRIVING_TUNNEL \ + { 26, 3.1f, 0.810f, -900, -800, -100, 3.42f, 0.94f, 1.31f, -300, 0.051f, 0.00f,0.00f,0.00f, -500, 0.047f, 0.00f,0.00f,0.00f, 0.214f, 0.050f, 0.250f, 0.000f, -0.0f, 5000.0f, 155.3f, 0.00f, 0x20 } + +// CITY PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_CITY_STREETS \ + { 26, 3.0f, 0.780f, -1100, -300, -100, 1.79f, 1.12f, 0.91f, -1700, 0.046f, 0.00f,0.00f,0.00f, -2800, 0.028f, 0.00f,0.00f,0.00f, 0.250f, 0.200f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_CITY_SUBWAY \ + { 26, 3.0f, 0.740f, -1100, -300, -100, 3.01f, 1.23f, 0.91f, -700, 0.046f, 0.00f,0.00f,0.00f, -1000, 0.028f, 0.00f,0.00f,0.00f, 0.125f, 0.210f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_CITY_MUSEUM \ + { 26, 80.3f, 0.820f, -1100, -1500, -1500, 3.28f, 1.40f, 0.57f, -1600, 0.039f, 0.00f,0.00f,-0.00f, -600, 0.034f, 0.00f,0.00f,0.00f, 0.130f, 0.170f, 0.250f, 0.000f, -0.0f, 2854.4f, 107.5f, 0.00f, 0x0 } +#define EAX30_PRESET_CITY_LIBRARY \ + { 26, 80.3f, 0.820f, -1100, -1100, -2100, 2.76f, 0.89f, 0.41f, -1100, 0.029f, 0.00f,0.00f,-0.00f, -500, 0.020f, 0.00f,0.00f,0.00f, 0.130f, 0.170f, 0.250f, 0.000f, -0.0f, 2854.4f, 107.5f, 0.00f, 0x0 } +#define EAX30_PRESET_CITY_UNDERPASS \ + { 26, 3.0f, 0.820f, -1500, -700, -100, 3.57f, 1.12f, 0.91f, -1500, 0.059f, 0.00f,0.00f,0.00f, -1100, 0.037f, 0.00f,0.00f,0.00f, 0.250f, 0.140f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_CITY_ABANDONED \ + { 26, 3.0f, 0.690f, -1100, -200, -100, 3.28f, 1.17f, 0.91f, -1400, 0.044f, 0.00f,0.00f,0.00f, -2400, 0.024f, 0.00f,0.00f,0.00f, 0.250f, 0.200f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } + +// MISC ROOMS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_DUSTYROOM \ + { 26, 1.8f, 0.560f, -1100, -200, -300, 1.79f, 0.38f, 0.21f, -600, 0.002f, 0.00f,0.00f,0.00f, 200, 0.006f, 0.00f,0.00f,0.00f, 0.202f, 0.050f, 0.250f, 0.000f, -3.0f, 13046.0f, 163.3f, 0.00f, 0x20 } +#define EAX30_PRESET_CHAPEL \ + { 26, 19.6f, 0.840f, -1000, -500, 0, 4.62f, 0.64f, 1.23f, -700, 0.032f, 0.00f,0.00f,0.00f, -800, 0.049f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.110f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_SMALLWATERROOM \ + { 26, 36.2f, 0.700f, -1200, -698, 0, 1.51f, 1.25f, 1.14f, -100, 0.020f, 0.00f,0.00f,0.00f, 200, 0.030f, 0.00f,0.00f,0.00f, 0.179f, 0.150f, 0.895f, 0.190f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x0 } + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Effect Scenarios enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + EAX30_SCENARIO_CASTLE = 0, + EAX30_SCENARIO_FACTORY, + EAX30_SCENARIO_ICEPALACE, + EAX30_SCENARIO_SPACESTATION, + EAX30_SCENARIO_WOODGALLEON, + EAX30_SCENARIO_SPORTS, + EAX30_SCENARIO_PREFAB, + EAX30_SCENARIO_DOMESNPIPES, + EAX30_SCENARIO_OUTDOORS, + EAX30_SCENARIO_MOOD, + EAX30_SCENARIO_DRIVING, + EAX30_SCENARIO_CITY, + EAX30_SCENARIO_MISC, + EAX30_SCENARIO_ORIGINAL +} +EAX30_SCENARIO; + +////////////////////////////////////////////////////// +// Number of Effect Scenarios // +////////////////////////////////////////////////////// + +#define EAX30_NUM_SCENARIOS 14 + +////////////////////////////////////////////////////// +// Number of Effect Scenarios with standardised // +// locations // +////////////////////////////////////////////////////// + +#define EAX30_NUM_STANDARD_SCENARIOS 5 + +////////////////////////////////////////////////////// +// Array of scenario names // +////////////////////////////////////////////////////// + +extern const char* EAX30_SCENARIO_NAMES[]; + +////////////////////////////////////////////////////// +// Standardised Locations enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + EAX30_LOCATION_HALL = 0, + EAX30_LOCATION_LARGEROOM, + EAX30_LOCATION_MEDIUMROOM, + EAX30_LOCATION_SMALLROOM, + EAX30_LOCATION_CUPBOARD, + EAX30_LOCATION_ALCOVE, + EAX30_LOCATION_LONGPASSAGE, + EAX30_LOCATION_SHORTPASSAGE, + EAX30_LOCATION_COURTYARD +} +EAX30_LOCATION; + +////////////////////////////////////////////////////// +// Number of Standardised Locations // +////////////////////////////////////////////////////// + +#define EAX30_NUM_LOCATIONS 9 + +////////////////////////////////////////////////////// +// Array of standardised location names // +////////////////////////////////////////////////////// + +extern const char* EAX30_LOCATION_NAMES[]; + +////////////////////////////////////////////////////// +// Number of effects in each scenario // +////////////////////////////////////////////////////// + +#define EAX30_NUM_ORIGINAL_PRESETS 26 +#define EAX30_NUM_CASTLE_PRESETS EAX30_NUM_LOCATIONS +#define EAX30_NUM_FACTORY_PRESETS EAX30_NUM_LOCATIONS +#define EAX30_NUM_ICEPALACE_PRESETS EAX30_NUM_LOCATIONS +#define EAX30_NUM_SPACESTATION_PRESETS EAX30_NUM_LOCATIONS +#define EAX30_NUM_WOODGALLEON_PRESETS EAX30_NUM_LOCATIONS +#define EAX30_NUM_SPORTS_PRESETS 7 +#define EAX30_NUM_PREFAB_PRESETS 5 +#define EAX30_NUM_DOMESNPIPES_PRESETS 6 +#define EAX30_NUM_OUTDOORS_PRESETS 5 +#define EAX30_NUM_MOOD_PRESETS 3 +#define EAX30_NUM_DRIVING_PRESETS 8 +#define EAX30_NUM_CITY_PRESETS 6 +#define EAX30_NUM_MISC_PRESETS 3 + +////////////////////////////////////////////////////// +// Standardised Location effects can be accessed // +// from a matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_STANDARD_PRESETS[EAX30_NUM_STANDARD_SCENARIOS][EAX30_NUM_LOCATIONS]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Original Preset effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + ORIGINAL_GENERIC = 0, + ORIGINAL_PADDEDCELL, + ORIGINAL_ROOM, + ORIGINAL_BATHROOM, + ORIGINAL_LIVINGROOM, + ORIGINAL_STONEROOM, + ORIGINAL_AUDITORIUM, + ORIGINAL_CONCERTHALL, + ORIGINAL_CAVE, + ORIGINAL_ARENA, + ORIGINAL_HANGAR, + ORIGINAL_CARPETTEDHALLWAY, + ORIGINAL_HALLWAY, + ORIGINAL_STONECORRIDOR, + ORIGINAL_ALLEY, + ORIGINAL_FOREST, + ORIGINAL_CITY, + ORIGINAL_MOUNTAINS, + ORIGINAL_QUARRY, + ORIGINAL_PLAIN, + ORIGINAL_PARKINGLOT, + ORIGINAL_SEWERPIPE, + ORIGINAL_UNDERWATER, + ORIGINAL_DRUGGED, + ORIGINAL_DIZZY, + ORIGINAL_PSYCHOTIC +} +EAX30_ORIGINAL_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of original environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_ORIGINAL_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Original effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_ORIGINAL_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Sports scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + SPORT_EMPTYSTADIUM=0, + SPORT_FULLSTADIUM, + SPORT_STADIUMTANNOY, + SPORT_SQUASHCOURT, + SPORT_SMALLSWIMMINGPOOL, + SPORT_LARGESWIMMINGPOOL, + SPORT_GYMNASIUM +} +EAX30_SPORTS_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of sport environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_SPORTS_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Sports effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_SPORTS_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Prefab scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + PREFAB_WORKSHOP, + PREFAB_SCHOOLROOM, + PREFAB_PRACTISEROOM, + PREFAB_OUTHOUSE, + PREFAB_CARAVAN +} +EAX30_PREFAB_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of prefab environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_PREFAB_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Prefab effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_PREFAB_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Domes & Pipes effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + DOME_TOMB, + DOME_SAINTPAULS, + PIPE_SMALL, + PIPE_LONGTHIN, + PIPE_LARGE, + PIPE_RESONANT +} +EAX30_DOMESNPIPES_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of Domes & Pipes environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_DOMESNPIPES_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Domes & Pipes effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_DOMESNPIPES_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Outdoors scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + OUTDOORS_BACKYARD, + OUTDOORS_ROLLINGPLAINS, + OUTDOORS_DEEPCANYON, + OUTDOORS_CREEK, + OUTDOORS_VALLEY +} +EAX30_OUTDOORS_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of Outdoors environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_OUTDOORS_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Outdoors effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_OUTDOORS_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Mood scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + MOOD_HEAVEN, + MOOD_HELL, + MOOD_MEMORY +} +EAX30_MOOD_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of Mood environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_MOOD_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Mood effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_MOOD_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Driving scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + DRIVING_COMMENTATOR, + DRIVING_PITGARAGE, + DRIVING_INCAR_RACER, + DRIVING_INCAR_SPORTS, + DRIVING_INCAR_LUXURY, + DRIVING_FULLGRANDSTAND, + DRIVING_EMPTYGRANDSTAND, + DRIVING_TUNNEL +} +EAX30_DRIVING_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of driving environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_DRIVING_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Driving effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_DRIVING_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// City scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + CITY_STREETS, + CITY_SUBWAY, + CITY_MUSEUM, + CITY_LIBRARY, + CITY_UNDERPASS, + CITY_ABANDONED +} +EAX30_CITY_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of City environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_CITY_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// City effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_CITY_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Misc scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + DUSTYROOM, + CHAPEL, + SMALLWATERROOM +} +EAX30_MISC_PRESET_ENUMS; + + +////////////////////////////////////////////////////// +// Array of Misc environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_MISC_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Misc effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_MISC_PRESETS[]; + + +/***********************************************************************************************\ +* +* Material transmission presets +* +* Three values in this order :- +* +* 1. Occlusion (or Obstruction) +* 2. Occlusion LF Ratio (or Obstruction LF Ratio) +* 3. Occlusion Room Ratio +* +************************************************************************************************/ + + +// Single window material preset +#define EAX_MATERIAL_SINGLEWINDOW (-2800) +#define EAX_MATERIAL_SINGLEWINDOWLF 0.71f +#define EAX_MATERIAL_SINGLEWINDOWROOMRATIO 0.43f + +// Double window material preset +#define EAX_MATERIAL_DOUBLEWINDOW (-5000) +#define EAX_MATERIAL_DOUBLEWINDOWLF 0.40f +#define EAX_MATERIAL_DOUBLEWINDOWROOMRATIO 0.24f + +// Thin door material preset +#define EAX_MATERIAL_THINDOOR (-1800) +#define EAX_MATERIAL_THINDOORLF 0.66f +#define EAX_MATERIAL_THINDOORROOMRATIO 0.66f + +// Thick door material preset +#define EAX_MATERIAL_THICKDOOR (-4400) +#define EAX_MATERIAL_THICKDOORLF 0.64f +#define EAX_MATERIAL_THICKDOORROOMRATIO 0.27f + +// Wood wall material preset +#define EAX_MATERIAL_WOODWALL (-4000) +#define EAX_MATERIAL_WOODWALLLF 0.50f +#define EAX_MATERIAL_WOODWALLROOMRATIO 0.30f + +// Brick wall material preset +#define EAX_MATERIAL_BRICKWALL (-5000) +#define EAX_MATERIAL_BRICKWALLLF 0.60f +#define EAX_MATERIAL_BRICKWALLROOMRATIO 0.24f + +// Stone wall material preset +#define EAX_MATERIAL_STONEWALL (-6000) +#define EAX_MATERIAL_STONEWALLLF 0.68f +#define EAX_MATERIAL_STONEWALLROOMRATIO 0.20f + +// Curtain material preset +#define EAX_MATERIAL_CURTAIN (-1200) +#define EAX_MATERIAL_CURTAINLF 0.15f +#define EAX_MATERIAL_CURTAINROOMRATIO 1.00f + + +#endif // EAXUTIL_INCLUDED diff --git a/src/audio/eax/eax.h b/src/audio/eax/eax.h new file mode 100644 index 0000000..b221093 --- /dev/null +++ b/src/audio/eax/eax.h @@ -0,0 +1,536 @@ +/*******************************************************************\ +* * +* EAX.H - Environmental Audio Extensions version 3.0 * +* for OpenAL and DirectSound3D * +* * +********************************************************************/ + +#ifndef EAX_H_INCLUDED +#define EAX_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#ifndef AUDIO_OAL + #include + + /* + * EAX Wrapper Interface (using Direct X 7) {4FF53B81-1CE0-11d3-AAB8-00A0C95949D5} + */ + DEFINE_GUID(CLSID_EAXDirectSound, + 0x4ff53b81, + 0x1ce0, + 0x11d3, + 0xaa, 0xb8, 0x0, 0xa0, 0xc9, 0x59, 0x49, 0xd5); + + /* + * EAX Wrapper Interface (using Direct X 8) {CA503B60-B176-11d4-A094-D0C0BF3A560C} + */ + DEFINE_GUID(CLSID_EAXDirectSound8, + 0xca503b60, + 0xb176, + 0x11d4, + 0xa0, 0x94, 0xd0, 0xc0, 0xbf, 0x3a, 0x56, 0xc); + + + +#ifdef DIRECTSOUND_VERSION +#if DIRECTSOUND_VERSION >= 0x0800 + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate8(GUID*, LPDIRECTSOUND8*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE8)(GUID*, LPDIRECTSOUND8*, IUnknown FAR*); +#endif +#endif + + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate(GUID*, LPDIRECTSOUND*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE)(GUID*, LPDIRECTSOUND*, IUnknown FAR*); + + __declspec(dllimport) void CDECL GetCurrentVersion(LPDWORD major, LPDWORD minor); + typedef void (CDECL *LPGETCURRENTVERSION)(LPDWORD major, LPDWORD minor); + + +#else // AUDIO_OAL + #include + #include + + #ifndef GUID_DEFINED + #define GUID_DEFINED + typedef struct _GUID + { + unsigned long Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; + } GUID; + #endif // !GUID_DEFINED + + #ifndef DEFINE_GUID + #ifndef INITGUID + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID /*FAR*/ name + #else + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } + #endif // INITGUID + #endif // DEFINE_GUID + + + /* + * EAX OpenAL Extension + */ + typedef ALenum (*EAXSet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); + typedef ALenum (*EAXGet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); +#endif + +#pragma pack(push, 4) + +/* + * EAX 3.0 listener property set {A8FA6880-B476-11d3-BDB9-00C0F02DDF87} + */ +DEFINE_GUID(DSPROPSETID_EAX30_ListenerProperties, + 0xa8fa6882, + 0xb476, + 0x11d3, + 0xbd, 0xb9, 0x00, 0xc0, 0xf0, 0x2d, 0xdf, 0x87); + +// For compatibility with future EAX versions: +#define DSPROPSETID_EAX_ListenerProperties DSPROPSETID_EAX30_ListenerProperties + +typedef enum +{ + DSPROPERTY_EAXLISTENER_NONE, + DSPROPERTY_EAXLISTENER_ALLPARAMETERS, + DSPROPERTY_EAXLISTENER_ENVIRONMENT, + DSPROPERTY_EAXLISTENER_ENVIRONMENTSIZE, + DSPROPERTY_EAXLISTENER_ENVIRONMENTDIFFUSION, + DSPROPERTY_EAXLISTENER_ROOM, + DSPROPERTY_EAXLISTENER_ROOMHF, + DSPROPERTY_EAXLISTENER_ROOMLF, + DSPROPERTY_EAXLISTENER_DECAYTIME, + DSPROPERTY_EAXLISTENER_DECAYHFRATIO, + DSPROPERTY_EAXLISTENER_DECAYLFRATIO, + DSPROPERTY_EAXLISTENER_REFLECTIONS, + DSPROPERTY_EAXLISTENER_REFLECTIONSDELAY, + DSPROPERTY_EAXLISTENER_REFLECTIONSPAN, + DSPROPERTY_EAXLISTENER_REVERB, + DSPROPERTY_EAXLISTENER_REVERBDELAY, + DSPROPERTY_EAXLISTENER_REVERBPAN, + DSPROPERTY_EAXLISTENER_ECHOTIME, + DSPROPERTY_EAXLISTENER_ECHODEPTH, + DSPROPERTY_EAXLISTENER_MODULATIONTIME, + DSPROPERTY_EAXLISTENER_MODULATIONDEPTH, + DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF, + DSPROPERTY_EAXLISTENER_HFREFERENCE, + DSPROPERTY_EAXLISTENER_LFREFERENCE, + DSPROPERTY_EAXLISTENER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAXLISTENER_FLAGS +} DSPROPERTY_EAX_LISTENERPROPERTY; + +// OR these flags with property id +#define DSPROPERTY_EAXLISTENER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAXLISTENER_DEFERRED 0x80000000 // changes take effect later +#define DSPROPERTY_EAXLISTENER_COMMITDEFERREDSETTINGS (DSPROPERTY_EAXLISTENER_NONE | \ + DSPROPERTY_EAXLISTENER_IMMEDIATE) + +typedef struct _EAXVECTOR { + float x; + float y; + float z; +} EAXVECTOR; + +// Use this structure for DSPROPERTY_EAXLISTENER_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all times and delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myListener.lRoom = -1000; +// myListener.lRoomHF = -100; +// ... +// myListener.dwFlags = myFlags /* see EAXLISTENERFLAGS below */ ; +// instead of: +// myListener = { -1000, -100, ... , 0x00000009 }; +// If you want to save and load presets in binary form, you +// should define your own structure to insure future compatibility. +// +typedef struct _EAXLISTENERPROPERTIES +{ + unsigned long ulEnvironment; // sets all listener properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + long lRoom; // room effect level (at mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lRoomLF; // relative room effect level at low frequencies + float flDecayTime; // reverberation decay time at mid frequencies + float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio + float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + EAXVECTOR vReflectionsPan; // early reflections panning vector + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + EAXVECTOR vReverbPan; // late reverberation panning vector + float flEchoTime; // echo time + float flEchoDepth; // echo depth + float flModulationTime; // modulation time + float flModulationDepth; // modulation depth + float flAirAbsorptionHF; // change in level per meter at high frequencies + float flHFReference; // reference high frequency + float flLFReference; // reference low frequency + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + unsigned long ulFlags; // modifies the behavior of properties +} EAXLISTENERPROPERTIES, *LPEAXLISTENERPROPERTIES; + +// used by DSPROPERTY_EAXLISTENER_ENVIRONMENT +enum +{ + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX_ENVIRONMENT_UNDEFINED, + + EAX_ENVIRONMENT_COUNT +}; + +// Used by DSPROPERTY_EAXLISTENER_FLAGS +// +// Note: The number and order of flags may change in future EAX versions. +// It is recommended to use the flag defines as follows: +// myFlags = EAXLISTENERFLAGS_DECAYTIMESCALE | EAXLISTENERFLAGS_REVERBSCALE; +// instead of: +// myFlags = 0x00000009; +// +// These flags determine what properties are affected by environment size. +#define EAXLISTENERFLAGS_DECAYTIMESCALE 0x00000001 // reverberation decay time +#define EAXLISTENERFLAGS_REFLECTIONSSCALE 0x00000002 // reflection level +#define EAXLISTENERFLAGS_REFLECTIONSDELAYSCALE 0x00000004 // initial reflection delay time +#define EAXLISTENERFLAGS_REVERBSCALE 0x00000008 // reflections level +#define EAXLISTENERFLAGS_REVERBDELAYSCALE 0x00000010 // late reverberation delay time +#define EAXLISTENERFLAGS_ECHOTIMESCALE 0x00000040 // echo time +#define EAXLISTENERFLAGS_MODULATIONTIMESCALE 0x00000080 // modulation time + +// This flag limits high-frequency decay time according to air absorption. +#define EAXLISTENERFLAGS_DECAYHFLIMIT 0x00000020 + +#define EAXLISTENERFLAGS_RESERVED 0xFFFFFF00 // reserved future use + +// Property ranges and defaults: + +#define EAXLISTENER_MINENVIRONMENT 0 +#define EAXLISTENER_MAXENVIRONMENT (EAX_ENVIRONMENT_COUNT-1) +#define EAXLISTENER_DEFAULTENVIRONMENT EAX_ENVIRONMENT_GENERIC + +#define EAXLISTENER_MINENVIRONMENTSIZE 1.0f +#define EAXLISTENER_MAXENVIRONMENTSIZE 100.0f +#define EAXLISTENER_DEFAULTENVIRONMENTSIZE 7.5f + +#define EAXLISTENER_MINENVIRONMENTDIFFUSION 0.0f +#define EAXLISTENER_MAXENVIRONMENTDIFFUSION 1.0f +#define EAXLISTENER_DEFAULTENVIRONMENTDIFFUSION 1.0f + +#define EAXLISTENER_MINROOM (-10000) +#define EAXLISTENER_MAXROOM 0 +#define EAXLISTENER_DEFAULTROOM (-1000) + +#define EAXLISTENER_MINROOMHF (-10000) +#define EAXLISTENER_MAXROOMHF 0 +#define EAXLISTENER_DEFAULTROOMHF (-100) + +#define EAXLISTENER_MINROOMLF (-10000) +#define EAXLISTENER_MAXROOMLF 0 +#define EAXLISTENER_DEFAULTROOMLF 0 + +#define EAXLISTENER_MINDECAYTIME 0.1f +#define EAXLISTENER_MAXDECAYTIME 20.0f +#define EAXLISTENER_DEFAULTDECAYTIME 1.49f + +#define EAXLISTENER_MINDECAYHFRATIO 0.1f +#define EAXLISTENER_MAXDECAYHFRATIO 2.0f +#define EAXLISTENER_DEFAULTDECAYHFRATIO 0.83f + +#define EAXLISTENER_MINDECAYLFRATIO 0.1f +#define EAXLISTENER_MAXDECAYLFRATIO 2.0f +#define EAXLISTENER_DEFAULTDECAYLFRATIO 1.00f + +#define EAXLISTENER_MINREFLECTIONS (-10000) +#define EAXLISTENER_MAXREFLECTIONS 1000 +#define EAXLISTENER_DEFAULTREFLECTIONS (-2602) + +#define EAXLISTENER_MINREFLECTIONSDELAY 0.0f +#define EAXLISTENER_MAXREFLECTIONSDELAY 0.3f +#define EAXLISTENER_DEFAULTREFLECTIONSDELAY 0.007f + +#define EAXLISTENER_MINREVERB (-10000) +#define EAXLISTENER_MAXREVERB 2000 +#define EAXLISTENER_DEFAULTREVERB 200 + +#define EAXLISTENER_MINREVERBDELAY 0.0f +#define EAXLISTENER_MAXREVERBDELAY 0.1f +#define EAXLISTENER_DEFAULTREVERBDELAY 0.011f + +#define EAXLISTENER_MINECHOTIME 0.075f +#define EAXLISTENER_MAXECHOTIME 0.25f +#define EAXLISTENER_DEFAULTECHOTIME 0.25f + +#define EAXLISTENER_MINECHODEPTH 0.0f +#define EAXLISTENER_MAXECHODEPTH 1.0f +#define EAXLISTENER_DEFAULTECHODEPTH 0.0f + +#define EAXLISTENER_MINMODULATIONTIME 0.04f +#define EAXLISTENER_MAXMODULATIONTIME 4.0f +#define EAXLISTENER_DEFAULTMODULATIONTIME 0.25f + +#define EAXLISTENER_MINMODULATIONDEPTH 0.0f +#define EAXLISTENER_MAXMODULATIONDEPTH 1.0f +#define EAXLISTENER_DEFAULTMODULATIONDEPTH 0.0f + +#define EAXLISTENER_MINAIRABSORPTIONHF (-100.0f) +#define EAXLISTENER_MAXAIRABSORPTIONHF 0.0f +#define EAXLISTENER_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAXLISTENER_MINHFREFERENCE 1000.0f +#define EAXLISTENER_MAXHFREFERENCE 20000.0f +#define EAXLISTENER_DEFAULTHFREFERENCE 5000.0f + +#define EAXLISTENER_MINLFREFERENCE 20.0f +#define EAXLISTENER_MAXLFREFERENCE 1000.0f +#define EAXLISTENER_DEFAULTLFREFERENCE 250.0f + +#define EAXLISTENER_MINROOMROLLOFFFACTOR 0.0f +#define EAXLISTENER_MAXROOMROLLOFFFACTOR 10.0f +#define EAXLISTENER_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXLISTENER_DEFAULTFLAGS (EAXLISTENERFLAGS_DECAYTIMESCALE | \ + EAXLISTENERFLAGS_REFLECTIONSSCALE | \ + EAXLISTENERFLAGS_REFLECTIONSDELAYSCALE | \ + EAXLISTENERFLAGS_REVERBSCALE | \ + EAXLISTENERFLAGS_REVERBDELAYSCALE | \ + EAXLISTENERFLAGS_DECAYHFLIMIT) + + + +/* +* EAX 3.0 buffer property set {A8FA6881-B476-11d3-BDB9-00C0F02DDF87} +*/ +DEFINE_GUID(DSPROPSETID_EAX30_BufferProperties, + 0xa8fa6881, + 0xb476, + 0x11d3, + 0xbd, 0xb9, 0x0, 0xc0, 0xf0, 0x2d, 0xdf, 0x87); + +// For compatibility with future EAX versions: +#define DSPROPSETID_EAX_BufferProperties DSPROPSETID_EAX30_BufferProperties +#define DSPROPSETID_EAX_SourceProperties DSPROPSETID_EAX30_BufferProperties + +typedef enum +{ + DSPROPERTY_EAXBUFFER_NONE, + DSPROPERTY_EAXBUFFER_ALLPARAMETERS, + DSPROPERTY_EAXBUFFER_OBSTRUCTIONPARAMETERS, + DSPROPERTY_EAXBUFFER_OCCLUSIONPARAMETERS, + DSPROPERTY_EAXBUFFER_EXCLUSIONPARAMETERS, + DSPROPERTY_EAXBUFFER_DIRECT, + DSPROPERTY_EAXBUFFER_DIRECTHF, + DSPROPERTY_EAXBUFFER_ROOM, + DSPROPERTY_EAXBUFFER_ROOMHF, + DSPROPERTY_EAXBUFFER_OBSTRUCTION, + DSPROPERTY_EAXBUFFER_OBSTRUCTIONLFRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSION, + DSPROPERTY_EAXBUFFER_OCCLUSIONLFRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSIONROOMRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSIONDIRECTRATIO, + DSPROPERTY_EAXBUFFER_EXCLUSION, + DSPROPERTY_EAXBUFFER_EXCLUSIONLFRATIO, + DSPROPERTY_EAXBUFFER_OUTSIDEVOLUMEHF, + DSPROPERTY_EAXBUFFER_DOPPLERFACTOR, + DSPROPERTY_EAXBUFFER_ROLLOFFFACTOR, + DSPROPERTY_EAXBUFFER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAXBUFFER_AIRABSORPTIONFACTOR, + DSPROPERTY_EAXBUFFER_FLAGS +} DSPROPERTY_EAX_BUFFERPROPERTY; + +// OR these flags with property id +#define DSPROPERTY_EAXBUFFER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAXBUFFER_DEFERRED 0x80000000 // changes take effect later +#define DSPROPERTY_EAXBUFFER_COMMITDEFERREDSETTINGS (DSPROPERTY_EAXBUFFER_NONE | \ + DSPROPERTY_EAXBUFFER_IMMEDIATE) + +// Use this structure for DSPROPERTY_EAXBUFFER_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myBuffer.lDirect = 0; +// myBuffer.lDirectHF = -200; +// ... +// myBuffer.dwFlags = myFlags /* see EAXBUFFERFLAGS below */ ; +// instead of: +// myBuffer = { 0, -200, ... , 0x00000003 }; +// +typedef struct _EAXBUFFERPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // relative occlusion control for room effect + float flOcclusionDirectRatio; // relative occlusion control for direct path + long lExclusion; // main exlusion control (attenuation at high frequencies) + float flExclusionLFRatio; // exclusion low-frequency level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flDopplerFactor; // like DS3D flDopplerFactor but per source + float flRolloffFactor; // like DS3D flRolloffFactor but per source + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF + unsigned long ulFlags; // modifies the behavior of properties +} EAXBUFFERPROPERTIES, *LPEAXBUFFERPROPERTIES; + +// Use this structure for DSPROPERTY_EAXBUFFER_OBSTRUCTION, +typedef struct _EAXOBSTRUCTIONPROPERTIES +{ + long lObstruction; + float flObstructionLFRatio; +} EAXOBSTRUCTIONPROPERTIES, *LPEAXOBSTRUCTIONPROPERTIES; + +// Use this structure for DSPROPERTY_EAXBUFFER_OCCLUSION +typedef struct _EAXOCCLUSIONPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +} EAXOCCLUSIONPROPERTIES, *LPEAXOCCLUSIONPROPERTIES; + +// Use this structure for DSPROPERTY_EAXBUFFER_EXCLUSION +typedef struct _EAXEXCLUSIONPROPERTIES +{ + long lExclusion; + float flExclusionLFRatio; +} EAXEXCLUSIONPROPERTIES, *LPEAXEXCLUSIONPROPERTIES; + +// Used by DSPROPERTY_EAXBUFFER_FLAGS +// TRUE: value is computed automatically - property is an offset +// FALSE: value is used directly +// +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXBUFFERFLAGS_DIRECTHFAUTO | EAXBUFFERFLAGS_ROOMAUTO; +// instead of: +// myFlags = 0x00000003; +// +#define EAXBUFFERFLAGS_DIRECTHFAUTO 0x00000001 // affects DSPROPERTY_EAXBUFFER_DIRECTHF +#define EAXBUFFERFLAGS_ROOMAUTO 0x00000002 // affects DSPROPERTY_EAXBUFFER_ROOM +#define EAXBUFFERFLAGS_ROOMHFAUTO 0x00000004 // affects DSPROPERTY_EAXBUFFER_ROOMHF + +#define EAXBUFFERFLAGS_RESERVED 0xFFFFFFF8 // reserved future use + +// Property ranges and defaults: + +#define EAXBUFFER_MINDIRECT (-10000) +#define EAXBUFFER_MAXDIRECT 1000 +#define EAXBUFFER_DEFAULTDIRECT 0 + +#define EAXBUFFER_MINDIRECTHF (-10000) +#define EAXBUFFER_MAXDIRECTHF 0 +#define EAXBUFFER_DEFAULTDIRECTHF 0 + +#define EAXBUFFER_MINROOM (-10000) +#define EAXBUFFER_MAXROOM 1000 +#define EAXBUFFER_DEFAULTROOM 0 + +#define EAXBUFFER_MINROOMHF (-10000) +#define EAXBUFFER_MAXROOMHF 0 +#define EAXBUFFER_DEFAULTROOMHF 0 + +#define EAXBUFFER_MINOBSTRUCTION (-10000) +#define EAXBUFFER_MAXOBSTRUCTION 0 +#define EAXBUFFER_DEFAULTOBSTRUCTION 0 + +#define EAXBUFFER_MINOBSTRUCTIONLFRATIO 0.0f +#define EAXBUFFER_MAXOBSTRUCTIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTOBSTRUCTIONLFRATIO 0.0f + +#define EAXBUFFER_MINOCCLUSION (-10000) +#define EAXBUFFER_MAXOCCLUSION 0 +#define EAXBUFFER_DEFAULTOCCLUSION 0 + +#define EAXBUFFER_MINOCCLUSIONLFRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTOCCLUSIONLFRATIO 0.25f + +#define EAXBUFFER_MINOCCLUSIONROOMRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONROOMRATIO 10.0f +#define EAXBUFFER_DEFAULTOCCLUSIONROOMRATIO 1.5f + +#define EAXBUFFER_MINOCCLUSIONDIRECTRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONDIRECTRATIO 10.0f +#define EAXBUFFER_DEFAULTOCCLUSIONDIRECTRATIO 1.0f + +#define EAXBUFFER_MINEXCLUSION (-10000) +#define EAXBUFFER_MAXEXCLUSION 0 +#define EAXBUFFER_DEFAULTEXCLUSION 0 + +#define EAXBUFFER_MINEXCLUSIONLFRATIO 0.0f +#define EAXBUFFER_MAXEXCLUSIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTEXCLUSIONLFRATIO 1.0f + +#define EAXBUFFER_MINOUTSIDEVOLUMEHF (-10000) +#define EAXBUFFER_MAXOUTSIDEVOLUMEHF 0 +#define EAXBUFFER_DEFAULTOUTSIDEVOLUMEHF 0 + +#define EAXBUFFER_MINDOPPLERFACTOR 0.0f +#define EAXBUFFER_MAXDOPPLERFACTOR 10.f +#define EAXBUFFER_DEFAULTDOPPLERFACTOR 0.0f + +#define EAXBUFFER_MINROLLOFFFACTOR 0.0f +#define EAXBUFFER_MAXROLLOFFFACTOR 10.f +#define EAXBUFFER_DEFAULTROLLOFFFACTOR 0.0f + +#define EAXBUFFER_MINROOMROLLOFFFACTOR 0.0f +#define EAXBUFFER_MAXROOMROLLOFFFACTOR 10.f +#define EAXBUFFER_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXBUFFER_MINAIRABSORPTIONFACTOR 0.0f +#define EAXBUFFER_MAXAIRABSORPTIONFACTOR 10.0f +#define EAXBUFFER_DEFAULTAIRABSORPTIONFACTOR 1.0f + +#define EAXBUFFER_DEFAULTFLAGS (EAXBUFFERFLAGS_DIRECTHFAUTO | \ + EAXBUFFERFLAGS_ROOMAUTO | \ + EAXBUFFERFLAGS_ROOMHFAUTO ) + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif diff --git a/src/audio/oal/aldlist.cpp b/src/audio/oal/aldlist.cpp new file mode 100644 index 0000000..6024adf --- /dev/null +++ b/src/audio/oal/aldlist.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2006, Creative Labs Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions and + * the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the distribution. + * * Neither the name of Creative Labs Inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "aldlist.h" + +#ifdef AUDIO_OAL +/* + * Init call + */ +ALDeviceList::ALDeviceList() +{ + char *devices; + int index; + const char *defaultDeviceName; + const char *actualDeviceName; + + // DeviceInfo vector stores, for each enumerated device, it's device name, selection status, spec version #, and extension support + nNumOfDevices = 0; + + defaultDeviceIndex = 0; + + if (alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT")) { + devices = (char *)alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); + defaultDeviceName = (char *)alcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); + + index = 0; + // go through device list (each device terminated with a single NULL, list terminated with double NULL) + while (*devices != '\0') { + if (strcmp(defaultDeviceName, devices) == 0) { + defaultDeviceIndex = index; + } + ALCdevice *device = alcOpenDevice(devices); + if (device) { + ALCcontext *context = alcCreateContext(device, NULL); + if (context) { + alcMakeContextCurrent(context); + // if new actual device name isn't already in the list, then add it... + actualDeviceName = alcGetString(device, ALC_ALL_DEVICES_SPECIFIER); + if ((actualDeviceName != NULL) && (strlen(actualDeviceName) > 0)) { + ALDEVICEINFO &ALDeviceInfo = aDeviceInfo[nNumOfDevices++]; + ALDeviceInfo.bSelected = true; + ALDeviceInfo.SetName(actualDeviceName); + alcGetIntegerv(device, ALC_MAJOR_VERSION, sizeof(int), &ALDeviceInfo.iMajorVersion); + alcGetIntegerv(device, ALC_MINOR_VERSION, sizeof(int), &ALDeviceInfo.iMinorVersion); + + // Check for ALC Extensions + if (alcIsExtensionPresent(device, "ALC_EXT_CAPTURE") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EXT_CAPTURE; + if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EXT_EFX; + + // Check for AL Extensions + if (alIsExtensionPresent("AL_EXT_OFFSET") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EXT_OFFSET; + + if (alIsExtensionPresent("AL_EXT_LINEAR_DISTANCE") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EXT_LINEAR_DISTANCE; + if (alIsExtensionPresent("AL_EXT_EXPONENT_DISTANCE") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EXT_EXPONENT_DISTANCE; + + if (alIsExtensionPresent("EAX2.0") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EAX2; + if (alIsExtensionPresent("EAX3.0") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EAX3; + if (alIsExtensionPresent("EAX4.0") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EAX4; + if (alIsExtensionPresent("EAX5.0") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EAX5; + + if (alIsExtensionPresent("EAX-RAM") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EAX_RAM; + + // Get Source Count + ALDeviceInfo.uiSourceCount = GetMaxNumSources(); + } + alcMakeContextCurrent(NULL); + alcDestroyContext(context); + } + alcCloseDevice(device); + } + devices += strlen(devices) + 1; + index += 1; + } + } + + ResetFilters(); +} + +/* + * Exit call + */ +ALDeviceList::~ALDeviceList() +{ +} + +/* + * Returns the number of devices in the complete device list + */ +unsigned int ALDeviceList::GetNumDevices() +{ + return nNumOfDevices; +} + +/* + * Returns the device name at an index in the complete device list + */ +const char * ALDeviceList::GetDeviceName(unsigned int index) +{ + if (index < GetNumDevices()) + return aDeviceInfo[index].strDeviceName; + else + return NULL; +} + +/* + * Returns the major and minor version numbers for a device at a specified index in the complete list + */ +void ALDeviceList::GetDeviceVersion(unsigned int index, int *major, int *minor) +{ + if (index < GetNumDevices()) { + if (major) + *major = aDeviceInfo[index].iMajorVersion; + if (minor) + *minor = aDeviceInfo[index].iMinorVersion; + } + return; +} + +/* + * Returns the maximum number of Sources that can be generate on the given device + */ +unsigned int ALDeviceList::GetMaxNumSources(unsigned int index) +{ + if (index < GetNumDevices()) + return aDeviceInfo[index].uiSourceCount; + else + return 0; +} + +/* + * Checks if the extension is supported on the given device + */ +bool ALDeviceList::IsExtensionSupported(int index, unsigned short ext) +{ + return !!(aDeviceInfo[index].Extensions & ext); +} + +/* + * returns the index of the default device in the complete device list + */ +int ALDeviceList::GetDefaultDevice() +{ + return defaultDeviceIndex; +} + +/* + * Deselects devices which don't have the specified minimum version + */ +void ALDeviceList::FilterDevicesMinVer(int major, int minor) +{ + int dMajor, dMinor; + for (unsigned int i = 0; i < nNumOfDevices; i++) { + GetDeviceVersion(i, &dMajor, &dMinor); + if ((dMajor < major) || ((dMajor == major) && (dMinor < minor))) { + aDeviceInfo[i].bSelected = false; + } + } +} + +/* + * Deselects devices which don't have the specified maximum version + */ +void ALDeviceList::FilterDevicesMaxVer(int major, int minor) +{ + int dMajor, dMinor; + for (unsigned int i = 0; i < nNumOfDevices; i++) { + GetDeviceVersion(i, &dMajor, &dMinor); + if ((dMajor > major) || ((dMajor == major) && (dMinor > minor))) { + aDeviceInfo[i].bSelected = false; + } + } +} + +/* + * Deselects device which don't support the given extension name + */ +void +ALDeviceList::FilterDevicesExtension(unsigned short ext) +{ + for (unsigned int i = 0; i < nNumOfDevices; i++) { + if (!IsExtensionSupported(i, ext)) + aDeviceInfo[i].bSelected = false; + } +} + +/* + * Resets all filtering, such that all devices are in the list + */ +void ALDeviceList::ResetFilters() +{ + for (unsigned int i = 0; i < GetNumDevices(); i++) { + aDeviceInfo[i].bSelected = true; + } + filterIndex = 0; +} + +/* + * Gets index of first filtered device + */ +int ALDeviceList::GetFirstFilteredDevice() +{ + unsigned int i; + + for (i = 0; i < GetNumDevices(); i++) { + if (aDeviceInfo[i].bSelected == true) { + break; + } + } + filterIndex = i + 1; + return i; +} + +/* + * Gets index of next filtered device + */ +int ALDeviceList::GetNextFilteredDevice() +{ + unsigned int i; + + for (i = filterIndex; i < GetNumDevices(); i++) { + if (aDeviceInfo[i].bSelected == true) { + break; + } + } + filterIndex = i + 1; + return i; +} + +/* + * Internal function to detemine max number of Sources that can be generated + */ +unsigned int ALDeviceList::GetMaxNumSources() +{ + ALuint uiSources[256]; + unsigned int iSourceCount = 0; + + // Clear AL Error Code + alGetError(); + + // Generate up to 256 Sources, checking for any errors + for (iSourceCount = 0; iSourceCount < 256; iSourceCount++) + { + alGenSources(1, &uiSources[iSourceCount]); + if (alGetError() != AL_NO_ERROR) + break; + } + + // Release the Sources + alDeleteSources(iSourceCount, uiSources); + if (alGetError() != AL_NO_ERROR) + { + for (unsigned int i = 0; i < 256; i++) + { + alDeleteSources(1, &uiSources[i]); + } + } + + return iSourceCount; +} +#endif diff --git a/src/audio/oal/aldlist.h b/src/audio/oal/aldlist.h new file mode 100644 index 0000000..3ed12d8 --- /dev/null +++ b/src/audio/oal/aldlist.h @@ -0,0 +1,82 @@ +#ifndef ALDEVICELIST_H +#define ALDEVICELIST_H + +#include "oal_utils.h" + +#ifdef AUDIO_OAL +#pragma warning(disable: 4786) //disable warning "identifier was truncated to '255' characters in the browser information" + +enum +{ + ADEXT_EXT_CAPTURE = (1 << 0), + ADEXT_EXT_EFX = (1 << 1), + ADEXT_EXT_OFFSET = (1 << 2), + ADEXT_EXT_LINEAR_DISTANCE = (1 << 3), + ADEXT_EXT_EXPONENT_DISTANCE = (1 << 4), + ADEXT_EAX2 = (1 << 5), + ADEXT_EAX3 = (1 << 6), + ADEXT_EAX4 = (1 << 7), + ADEXT_EAX5 = (1 << 8), + ADEXT_EAX_RAM = (1 << 9), +}; + +struct ALDEVICEINFO { + char *strDeviceName; + int iMajorVersion; + int iMinorVersion; + unsigned int uiSourceCount; + unsigned short Extensions; + bool bSelected; + + ALDEVICEINFO() : iMajorVersion(0), iMinorVersion(0), uiSourceCount(0), bSelected(false) + { + strDeviceName = NULL; + Extensions = 0; + } + + ~ALDEVICEINFO() + { + delete[] strDeviceName; + strDeviceName = NULL; + } + + void SetName(const char *name) + { + if(strDeviceName) delete[] strDeviceName; + strDeviceName = new char[strlen(name) + 1]; + strcpy(strDeviceName, name); + } +}; + +typedef ALDEVICEINFO *LPALDEVICEINFO; + +class ALDeviceList +{ +private: + ALDEVICEINFO aDeviceInfo[64]; + unsigned int nNumOfDevices; + int defaultDeviceIndex; + int filterIndex; + +public: + ALDeviceList (); + ~ALDeviceList (); + unsigned int GetNumDevices(); + const char *GetDeviceName(unsigned int index); + void GetDeviceVersion(unsigned int index, int *major, int *minor); + unsigned int GetMaxNumSources(unsigned int index); + bool IsExtensionSupported(int index, unsigned short ext); + int GetDefaultDevice(); + void FilterDevicesMinVer(int major, int minor); + void FilterDevicesMaxVer(int major, int minor); + void FilterDevicesExtension(unsigned short ext); + void ResetFilters(); + int GetFirstFilteredDevice(); + int GetNextFilteredDevice(); + +private: + unsigned int GetMaxNumSources(); +}; +#endif + +#endif // ALDEVICELIST_H diff --git a/src/audio/oal/channel.cpp b/src/audio/oal/channel.cpp new file mode 100644 index 0000000..04e7e52 --- /dev/null +++ b/src/audio/oal/channel.cpp @@ -0,0 +1,295 @@ +#include "common.h" + +#ifdef AUDIO_OAL +#include "channel.h" +#include "sampman.h" + +#ifndef _WIN32 +#include +#endif + +extern bool IsFXSupported(); + +ALuint alSources[NUM_CHANNELS]; +ALuint alFilters[NUM_CHANNELS]; +ALuint alBuffers[NUM_CHANNELS]; +bool bChannelsCreated = false; + +int32 CChannel::channelsThatNeedService = 0; + +uint8 tempStereoBuffer[PED_BLOCKSIZE * 2]; + +void +CChannel::InitChannels() +{ + alGenSources(NUM_CHANNELS, alSources); + alGenBuffers(NUM_CHANNELS, alBuffers); + if (IsFXSupported()) + alGenFilters(NUM_CHANNELS, alFilters); + bChannelsCreated = true; +} + +void +CChannel::DestroyChannels() +{ + if (bChannelsCreated) + { + alDeleteSources(NUM_CHANNELS, alSources); + memset(alSources, 0, sizeof(alSources)); + alDeleteBuffers(NUM_CHANNELS, alBuffers); + memset(alBuffers, 0, sizeof(alBuffers)); + if (IsFXSupported()) + { + alDeleteFilters(NUM_CHANNELS, alFilters); + memset(alFilters, 0, sizeof(alFilters)); + } + bChannelsCreated = false; + } +} + + +CChannel::CChannel() +{ + Data = nil; + DataSize = 0; + bIs2D = false; + SetDefault(); +} + +void CChannel::SetDefault() +{ + Pitch = 1.0f; + Gain = 1.0f; + Mix = 0.0f; + + Position[0] = 0.0f; Position[1] = 0.0f; Position[2] = 0.0f; + Distances[0] = 0.0f; Distances[1] = FLT_MAX; + + LoopCount = 1; + LastProcessedOffset = UINT32_MAX; + LoopPoints[0] = 0; LoopPoints[1] = -1; + + Frequency = MAX_FREQ; +} + +void CChannel::Reset() +{ + // Here is safe because ctor don't call this + if (LoopCount > 1) + channelsThatNeedService--; + + ClearBuffer(); + SetDefault(); +} + +void CChannel::Init(uint32 _id, bool Is2D) +{ + id = _id; + if ( HasSource() ) + { + alSourcei(alSources[id], AL_SOURCE_RELATIVE, AL_TRUE); + if ( IsFXSupported() ) + alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); + + if ( Is2D ) + { + bIs2D = true; + alSource3f(alSources[id], AL_POSITION, 0.0f, 0.0f, 0.0f); + alSourcef(alSources[id], AL_GAIN, 1.0f); + } + } +} + +void CChannel::Term() +{ + Stop(); + if ( HasSource() ) + { + if ( IsFXSupported() ) + { + alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); + } + } +} + +void CChannel::Start() +{ + if ( !HasSource() ) return; + if ( !Data ) return; + + if ( bIs2D ) + { + // convert mono data to stereo + int16 *monoData = (int16*)Data; + int16 *stereoData = (int16*)tempStereoBuffer; + for (size_t i = 0; i < DataSize / 2; i++) + { + *(stereoData++) = *monoData; + *(stereoData++) = *(monoData++); + } + alBufferData(alBuffers[id], AL_FORMAT_STEREO16, tempStereoBuffer, DataSize * 2, Frequency); + } + else + alBufferData(alBuffers[id], AL_FORMAT_MONO16, Data, DataSize, Frequency); + if ( LoopPoints[0] != 0 && LoopPoints[0] != -1 ) + alBufferiv(alBuffers[id], AL_LOOP_POINTS_SOFT, LoopPoints); + alSourcei(alSources[id], AL_BUFFER, alBuffers[id]); + alSourcePlay(alSources[id]); +} + +void CChannel::Stop() +{ + if ( HasSource() ) + alSourceStop(alSources[id]); + + Reset(); +} + +bool CChannel::HasSource() +{ + return alSources[id] != AL_NONE; +} + +bool CChannel::IsUsed() +{ + if ( HasSource() ) + { + ALint sourceState; + alGetSourcei(alSources[id], AL_SOURCE_STATE, &sourceState); + return sourceState == AL_PLAYING; + } + return false; +} + +void CChannel::SetPitch(float pitch) +{ + if ( !HasSource() ) return; + alSourcef(alSources[id], AL_PITCH, pitch); +} + +void CChannel::SetGain(float gain) +{ + if ( !HasSource() ) return; + alSourcef(alSources[id], AL_GAIN, gain); +} + +void CChannel::SetVolume(int32 vol) +{ + SetGain(ALfloat(vol) / MAX_VOLUME); +} + +void CChannel::SetSampleData(void *_data, size_t _DataSize, int32 freq) +{ + Data = _data; + DataSize = _DataSize; + Frequency = freq; +} + +void CChannel::SetCurrentFreq(uint32 freq) +{ + SetPitch(ALfloat(freq) / Frequency); +} + +void CChannel::SetLoopCount(int32 count) +{ + if ( !HasSource() ) return; + + // 0: loop indefinitely, 1: play one time, 2: play two times etc... + // only > 1 needs manual processing + + if (LoopCount > 1 && count < 2) + channelsThatNeedService--; + else if (LoopCount < 2 && count > 1) + channelsThatNeedService++; + + alSourcei(alSources[id], AL_LOOPING, count == 1 ? AL_FALSE : AL_TRUE); + LoopCount = count; +} + +bool CChannel::Update() +{ + if (!HasSource()) return false; + if (LoopCount < 2) return false; + + ALint state; + alGetSourcei(alSources[id], AL_SOURCE_STATE, &state); + if (state == AL_STOPPED) { + debug("Looping channels(%d in this case) shouldn't report AL_STOPPED, but nvm\n", id); + SetLoopCount(1); + return true; + } + + assert(channelsThatNeedService > 0 && "Ref counting is broken"); + + ALint offset; + alGetSourcei(alSources[id], AL_SAMPLE_OFFSET, &offset); + + // Rewound + if (offset < LastProcessedOffset) { + LoopCount--; + if (LoopCount == 1) { + // Playing last tune... + channelsThatNeedService--; + alSourcei(alSources[id], AL_LOOPING, AL_FALSE); + } + } + LastProcessedOffset = offset; + return true; +} + +void CChannel::SetLoopPoints(ALint start, ALint end) +{ + LoopPoints[0] = start; + LoopPoints[1] = end; +} + +void CChannel::SetPosition(float x, float y, float z) +{ + if ( !HasSource() ) return; + alSource3f(alSources[id], AL_POSITION, x, y, z); +} + +void CChannel::SetDistances(float max, float min) +{ + if ( !HasSource() ) return; + alSourcef (alSources[id], AL_MAX_DISTANCE, max); + alSourcef (alSources[id], AL_REFERENCE_DISTANCE, min); + alSourcef (alSources[id], AL_MAX_GAIN, 1.0f); + alSourcef (alSources[id], AL_ROLLOFF_FACTOR, 1.0f); +} + +void CChannel::SetPan(int32 pan) +{ + SetPosition((pan-63)/64.0f, 0.0f, Sqrt(1.0f-SQR((pan-63)/64.0f))); +} + +void CChannel::ClearBuffer() +{ + if ( !HasSource() ) return; + alSourcei(alSources[id], AL_LOOPING, AL_FALSE); + alSourcei(alSources[id], AL_BUFFER, AL_NONE); + Data = nil; + DataSize = 0; +} + +void CChannel::SetReverbMix(ALuint slot, float mix) +{ + if ( !IsFXSupported() ) return; + if ( !HasSource() ) return; + if ( alFilters[id] == AL_FILTER_NULL ) return; + + Mix = mix; + EAX3_SetReverbMix(alFilters[id], mix); + alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, slot, 0, alFilters[id]); +} + +void CChannel::UpdateReverb(ALuint slot) +{ + if ( !IsFXSupported() ) return; + if ( !HasSource() ) return; + if ( alFilters[id] == AL_FILTER_NULL ) return; + EAX3_SetReverbMix(alFilters[id], Mix); + alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, slot, 0, alFilters[id]); +} + +#endif diff --git a/src/audio/oal/channel.h b/src/audio/oal/channel.h new file mode 100644 index 0000000..872646c --- /dev/null +++ b/src/audio/oal/channel.h @@ -0,0 +1,55 @@ +#pragma once + +#ifdef AUDIO_OAL +#include "oal/oal_utils.h" +#include +#include +#include + + +class CChannel +{ + uint32 id; + float Pitch, Gain; + float Mix; + void *Data; + size_t DataSize; + int32 Frequency; + float Position[3]; + float Distances[2]; + int32 LoopCount; + ALint LoopPoints[2]; + ALint LastProcessedOffset; + bool bIs2D; +public: + static int32 channelsThatNeedService; + + static void InitChannels(); + static void DestroyChannels(); + + CChannel(); + void SetDefault(); + void Reset(); + void Init(uint32 _id, bool Is2D = false); + void Term(); + void Start(); + void Stop(); + bool HasSource(); + bool IsUsed(); + void SetPitch(float pitch); + void SetGain(float gain); + void SetVolume(int32 vol); + void SetSampleData(void *_data, size_t _DataSize, int32 freq); + void SetCurrentFreq(uint32 freq); + void SetLoopCount(int32 count); + void SetLoopPoints(ALint start, ALint end); + void SetPosition(float x, float y, float z); + void SetDistances(float max, float min); + void SetPan(int32 pan); + void ClearBuffer(); + void SetReverbMix(ALuint slot, float mix); + void UpdateReverb(ALuint slot); + bool Update(); +}; + +#endif \ No newline at end of file diff --git a/src/audio/oal/oal_utils.cpp b/src/audio/oal/oal_utils.cpp new file mode 100644 index 0000000..e4cb0b7 --- /dev/null +++ b/src/audio/oal/oal_utils.cpp @@ -0,0 +1,181 @@ +#include "common.h" +#include "oal_utils.h" + +#ifdef AUDIO_OAL + +/* + * When linking to a static openal-soft library, + * the extension function inside the openal library conflict with the variables here. + * Therefore declare these re3 owned symbols in a private namespace. + */ + +namespace re3_openal { + +LPALGENEFFECTS alGenEffects; +LPALDELETEEFFECTS alDeleteEffects; +LPALISEFFECT alIsEffect; +LPALEFFECTI alEffecti; +LPALEFFECTIV alEffectiv; +LPALEFFECTF alEffectf; +LPALEFFECTFV alEffectfv; +LPALGETEFFECTI alGetEffecti; +LPALGETEFFECTIV alGetEffectiv; +LPALGETEFFECTF alGetEffectf; +LPALGETEFFECTFV alGetEffectfv; +LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; +LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; +LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; +LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; +LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; +LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; +LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; +LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; +LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; +LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; +LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; +LPALGENFILTERS alGenFilters; +LPALDELETEFILTERS alDeleteFilters; +LPALISFILTER alIsFilter; +LPALFILTERI alFilteri; +LPALFILTERIV alFilteriv; +LPALFILTERF alFilterf; +LPALFILTERFV alFilterfv; +LPALGETFILTERI alGetFilteri; +LPALGETFILTERIV alGetFilteriv; +LPALGETFILTERF alGetFilterf; +LPALGETFILTERFV alGetFilterfv; + +} + +using namespace re3_openal; + +void EFXInit() +{ + /* Define a macro to help load the function pointers. */ +#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x)) + LOAD_PROC(LPALGENEFFECTS, alGenEffects); + LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); + LOAD_PROC(LPALISEFFECT, alIsEffect); + LOAD_PROC(LPALEFFECTI, alEffecti); + LOAD_PROC(LPALEFFECTIV, alEffectiv); + LOAD_PROC(LPALEFFECTF, alEffectf); + LOAD_PROC(LPALEFFECTFV, alEffectfv); + LOAD_PROC(LPALGETEFFECTI, alGetEffecti); + LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); + LOAD_PROC(LPALGETEFFECTF, alGetEffectf); + LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); + + LOAD_PROC(LPALGENFILTERS, alGenFilters); + LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters); + LOAD_PROC(LPALISFILTER, alIsFilter); + LOAD_PROC(LPALFILTERI, alFilteri); + LOAD_PROC(LPALFILTERIV, alFilteriv); + LOAD_PROC(LPALFILTERF, alFilterf); + LOAD_PROC(LPALFILTERFV, alFilterfv); + LOAD_PROC(LPALGETFILTERI, alGetFilteri); + LOAD_PROC(LPALGETFILTERIV, alGetFilteriv); + LOAD_PROC(LPALGETFILTERF, alGetFilterf); + LOAD_PROC(LPALGETFILTERFV, alGetFilterfv); + + LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); + LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); + LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); +#undef LOAD_PROC +} + +void SetEffectsLevel(ALuint uiFilter, float level) +{ + alFilteri(uiFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); + alFilterf(uiFilter, AL_LOWPASS_GAIN, 1.0f); + alFilterf(uiFilter, AL_LOWPASS_GAINHF, level); +} + +static inline float gain_to_mB(float gain) +{ + return (gain > 1e-5f) ? (float)(log10f(gain) * 2000.0f) : -10000l; +} + +static inline float mB_to_gain(float millibels) +{ + return (millibels > -10000.0f) ? powf(10.0f, millibels/2000.0f) : 0.0f; +} + +static inline float clampF(float val, float minval, float maxval) +{ + if(val >= maxval) return maxval; + if(val <= minval) return minval; + return val; +} + +void EAX3_Set(ALuint effect, const EAXLISTENERPROPERTIES *props) +{ + alEffecti (effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + alEffectf (effect, AL_EAXREVERB_DENSITY, clampF(powf(props->flEnvironmentSize, 3.0f) / 16.0f, 0.0f, 1.0f)); + alEffectf (effect, AL_EAXREVERB_DIFFUSION, props->flEnvironmentDiffusion); + alEffectf (effect, AL_EAXREVERB_GAIN, mB_to_gain((float)props->lRoom)); + alEffectf (effect, AL_EAXREVERB_GAINHF, mB_to_gain((float)props->lRoomHF)); + alEffectf (effect, AL_EAXREVERB_GAINLF, mB_to_gain((float)props->lRoomLF)); + alEffectf (effect, AL_EAXREVERB_DECAY_TIME, props->flDecayTime); + alEffectf (effect, AL_EAXREVERB_DECAY_HFRATIO, props->flDecayHFRatio); + alEffectf (effect, AL_EAXREVERB_DECAY_LFRATIO, props->flDecayLFRatio); + alEffectf (effect, AL_EAXREVERB_REFLECTIONS_GAIN, clampF(mB_to_gain((float)props->lReflections), AL_EAXREVERB_MIN_REFLECTIONS_GAIN, AL_EAXREVERB_MAX_REFLECTIONS_GAIN)); + alEffectf (effect, AL_EAXREVERB_REFLECTIONS_DELAY, props->flReflectionsDelay); + alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, &props->vReflectionsPan.x); + alEffectf (effect, AL_EAXREVERB_LATE_REVERB_GAIN, clampF(mB_to_gain((float)props->lReverb), AL_EAXREVERB_MIN_LATE_REVERB_GAIN, AL_EAXREVERB_MAX_LATE_REVERB_GAIN)); + alEffectf (effect, AL_EAXREVERB_LATE_REVERB_DELAY, props->flReverbDelay); + alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, &props->vReverbPan.x); + alEffectf (effect, AL_EAXREVERB_ECHO_TIME, props->flEchoTime); + alEffectf (effect, AL_EAXREVERB_ECHO_DEPTH, props->flEchoDepth); + alEffectf (effect, AL_EAXREVERB_MODULATION_TIME, props->flModulationTime); + alEffectf (effect, AL_EAXREVERB_MODULATION_DEPTH, props->flModulationDepth); + alEffectf (effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, clampF(mB_to_gain(props->flAirAbsorptionHF), AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF, AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)); + alEffectf (effect, AL_EAXREVERB_HFREFERENCE, props->flHFReference); + alEffectf (effect, AL_EAXREVERB_LFREFERENCE, props->flLFReference); + alEffectf (effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, props->flRoomRolloffFactor); + alEffecti (effect, AL_EAXREVERB_DECAY_HFLIMIT, (props->ulFlags&EAXLISTENERFLAGS_DECAYHFLIMIT) ? AL_TRUE : AL_FALSE); +} + +void EFX_Set(ALuint effect, const EAXLISTENERPROPERTIES *props) +{ + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); + + alEffectf(effect, AL_REVERB_DENSITY, clampF(powf(props->flEnvironmentSize, 3.0f) / 16.0f, 0.0f, 1.0f)); + alEffectf(effect, AL_REVERB_DIFFUSION, props->flEnvironmentDiffusion); + alEffectf(effect, AL_REVERB_GAIN, mB_to_gain((float)props->lRoom)); + alEffectf(effect, AL_REVERB_GAINHF, mB_to_gain((float)props->lRoomHF)); + alEffectf(effect, AL_REVERB_DECAY_TIME, props->flDecayTime); + alEffectf(effect, AL_REVERB_DECAY_HFRATIO, props->flDecayHFRatio); + alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, clampF(mB_to_gain((float)props->lReflections), AL_EAXREVERB_MIN_REFLECTIONS_GAIN, AL_EAXREVERB_MAX_REFLECTIONS_GAIN)); + alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, props->flReflectionsDelay); + alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, clampF(mB_to_gain((float)props->lReverb), AL_EAXREVERB_MIN_LATE_REVERB_GAIN, AL_EAXREVERB_MAX_LATE_REVERB_GAIN)); + alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, props->flReverbDelay); + alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, clampF(mB_to_gain(props->flAirAbsorptionHF), AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF, AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)); + alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, props->flRoomRolloffFactor); + alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, (props->ulFlags&EAXLISTENERFLAGS_DECAYHFLIMIT) ? AL_TRUE : AL_FALSE); +} + +void EAX3_SetReverbMix(ALuint filter, float mix) +{ + //long vol=(long)linear_to_dB(mix); + //DSPROPERTY_EAXBUFFER_ROOMHF, + //DSPROPERTY_EAXBUFFER_ROOM, + //DSPROPERTY_EAXBUFFER_REVERBMIX, + + long mbvol = gain_to_mB(mix); + float mb = mbvol; + float mbhf = mbvol; + + alFilteri(filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); + alFilterf(filter, AL_LOWPASS_GAIN, mB_to_gain(Min(mb, 0.0f))); + alFilterf(filter, AL_LOWPASS_GAINHF, mB_to_gain(mbhf)); +} + +#endif \ No newline at end of file diff --git a/src/audio/oal/oal_utils.h b/src/audio/oal/oal_utils.h new file mode 100644 index 0000000..f0fa090 --- /dev/null +++ b/src/audio/oal/oal_utils.h @@ -0,0 +1,54 @@ +#pragma once + +#ifdef AUDIO_OAL +#include "eax.h" +#include "AL/efx.h" + + +void EFXInit(); +void EAX3_Set(ALuint effect, const EAXLISTENERPROPERTIES *props); +void EFX_Set(ALuint effect, const EAXLISTENERPROPERTIES *props); +void EAX3_SetReverbMix(ALuint filter, float mix); +void SetEffectsLevel(ALuint uiFilter, float level); + +namespace re3_openal { + +extern LPALGENEFFECTS alGenEffects; +extern LPALDELETEEFFECTS alDeleteEffects; +extern LPALISEFFECT alIsEffect; +extern LPALEFFECTI alEffecti; +extern LPALEFFECTIV alEffectiv; +extern LPALEFFECTF alEffectf; +extern LPALEFFECTFV alEffectfv; +extern LPALGETEFFECTI alGetEffecti; +extern LPALGETEFFECTIV alGetEffectiv; +extern LPALGETEFFECTF alGetEffectf; +extern LPALGETEFFECTFV alGetEffectfv; +extern LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; +extern LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; +extern LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; +extern LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; +extern LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; +extern LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; +extern LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; +extern LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; +extern LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; +extern LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; +extern LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; +extern LPALGENFILTERS alGenFilters; +extern LPALDELETEFILTERS alDeleteFilters; +extern LPALISFILTER alIsFilter; +extern LPALFILTERI alFilteri; +extern LPALFILTERIV alFilteriv; +extern LPALFILTERF alFilterf; +extern LPALFILTERFV alFilterfv; +extern LPALGETFILTERI alGetFilteri; +extern LPALGETFILTERIV alGetFilteriv; +extern LPALGETFILTERF alGetFilterf; +extern LPALGETFILTERFV alGetFilterfv; + +} + +using namespace re3_openal; + +#endif diff --git a/src/audio/oal/stream.cpp b/src/audio/oal/stream.cpp new file mode 100644 index 0000000..6afe8e3 --- /dev/null +++ b/src/audio/oal/stream.cpp @@ -0,0 +1,1759 @@ +#include "common.h" + +#ifdef AUDIO_OAL + +#if defined _MSC_VER && !defined CMAKE_NO_AUTOLINK +#ifdef AUDIO_OAL_USE_SNDFILE +#pragma comment( lib, "libsndfile-1.lib" ) +#endif +#ifdef AUDIO_OAL_USE_MPG123 +#pragma comment( lib, "libmpg123-0.lib" ) +#endif +#endif +#ifdef AUDIO_OAL_USE_SNDFILE +#include +#endif +#ifdef AUDIO_OAL_USE_MPG123 +#include +#endif +#ifdef AUDIO_OAL_USE_OPUS +#include +#endif + +#include +#include + +#ifdef MULTITHREADED_AUDIO +#include +#include +#include +#include +#include "MusicManager.h" +#include "stream.h" + +std::thread gAudioThread; +std::mutex gAudioThreadQueueMutex; +std::condition_variable gAudioThreadCv; +bool gAudioThreadTerm = false; +std::queue gStreamsToProcess; // values are not unique, we will handle that ourself +std::queue> gStreamsToClose; +#else +#include "stream.h" +#endif + +#include "sampman.h" + +#ifndef _WIN32 +#include "crossplatform.h" +#endif + +/* +As we ran onto an issue of having different volume levels for mono streams +and stereo streams we are now handling all the stereo panning ourselves. +Each stream now has two sources - one panned to the left and one to the right, +and uses two separate buffers to store data for each individual channel. +For that we also have to reshuffle all decoded PCM stereo data from LRLRLRLR to +LLLLRRRR (handled by CSortStereoBuffer). +*/ + +class CSortStereoBuffer +{ + uint16* PcmBuf; + size_t BufSize; +//#ifdef MULTITHREADED_AUDIO +// std::mutex Mutex; +//#endif + +public: + CSortStereoBuffer() : PcmBuf(nil), BufSize(0) {} + ~CSortStereoBuffer() + { + if (PcmBuf) + free(PcmBuf); + } + + uint16* GetBuffer(size_t size) + { + if (size == 0) return nil; + if (!PcmBuf) + { + BufSize = size; + PcmBuf = (uint16*)malloc(BufSize); + } + else if (BufSize < size) + { + BufSize = size; + PcmBuf = (uint16*)realloc(PcmBuf, size); + } + return PcmBuf; + } + + void SortStereo(void* buf, size_t size) + { +//#ifdef MULTITHREADED_AUDIO +// std::lock_guard lock(Mutex); +//#endif + uint16* InBuf = (uint16*)buf; + uint16* OutBuf = GetBuffer(size); + + if (!OutBuf) return; + + size_t rightStart = size / 4; + for (size_t i = 0; i < size / 4; i++) + { + OutBuf[i] = InBuf[i*2]; + OutBuf[i+rightStart] = InBuf[i*2+1]; + } + + memcpy(InBuf, OutBuf, size); + } + +}; + +CSortStereoBuffer SortStereoBuffer; + +class CImaADPCMDecoder +{ + const uint16 StepTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, + 32767 + }; + + int16 Sample, StepIndex; + +public: + CImaADPCMDecoder() + { + Init(0, 0); + } + + void Init(int16 _Sample, int16 _StepIndex) + { + Sample = _Sample; + StepIndex = _StepIndex; + } + + void Decode(uint8 *inbuf, int16 *_outbuf, size_t size) + { + int16* outbuf = _outbuf; + for (size_t i = 0; i < size; i++) + { + *(outbuf++) = DecodeSample(inbuf[i] & 0xF); + *(outbuf++) = DecodeSample(inbuf[i] >> 4); + } + } + + int16 DecodeSample(uint8 adpcm) + { + uint16 step = StepTable[StepIndex]; + + if (adpcm & 4) + StepIndex += ((adpcm & 3) + 1) * 2; + else + StepIndex--; + + StepIndex = Clamp(StepIndex, 0, 88); + + int delta = step >> 3; + if (adpcm & 1) delta += step >> 2; + if (adpcm & 2) delta += step >> 1; + if (adpcm & 4) delta += step; + if (adpcm & 8) delta = -delta; + + int newSample = Sample + delta; + Sample = Clamp(newSample, -32768, 32767); + return Sample; + } +}; + +class CWavFile : public IDecoder +{ + enum + { + WAVEFMT_PCM = 1, + WAVEFMT_IMA_ADPCM = 0x11, + WAVEFMT_XBOX_ADPCM = 0x69, + }; + + struct tDataHeader + { + uint32 ID; + uint32 Size; + }; + + struct tFormatHeader + { + uint16 AudioFormat; + uint16 NumChannels; + uint32 SampleRate; + uint32 ByteRate; + uint16 BlockAlign; + uint16 BitsPerSample; + uint16 extra[2]; // adpcm only + + tFormatHeader() { memset(this, 0, sizeof(*this)); } + }; + + FILE *m_pFile; + bool m_bIsOpen; + + tFormatHeader m_FormatHeader; + + uint32 m_DataStartOffset; // TODO: 64 bit? + uint32 m_nSampleCount; + uint32 m_nSamplesPerBlock; + + // ADPCM things + uint8 *m_pAdpcmBuffer; + int16 **m_ppPcmBuffers; + CImaADPCMDecoder *m_pAdpcmDecoders; + + void Close() + { + if (m_pFile) { + fclose(m_pFile); + m_pFile = nil; + } + delete[] m_pAdpcmBuffer; + delete[] m_ppPcmBuffers; + delete[] m_pAdpcmDecoders; + } + + uint32 GetCurrentSample() const + { + // TODO: 64 bit? + uint32 FilePos = ftell(m_pFile); + if (FilePos <= m_DataStartOffset) + return 0; + return (FilePos - m_DataStartOffset) / m_FormatHeader.BlockAlign * m_nSamplesPerBlock; + } + +public: + CWavFile(const char* path) : m_bIsOpen(false), m_DataStartOffset(0), m_nSampleCount(0), m_nSamplesPerBlock(0), m_pAdpcmBuffer(nil), m_ppPcmBuffers(nil), m_pAdpcmDecoders(nil) + { + m_pFile = fopen(path, "rb"); + if (!m_pFile) return; + +#define CLOSE_ON_ERROR(op)\ + if (op) { \ + Close(); \ + return; \ + } + + tDataHeader DataHeader; + + CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0); + CLOSE_ON_ERROR(DataHeader.ID != 'FFIR'); + + // TODO? validate filesizes + + int WAVE; + CLOSE_ON_ERROR(fread(&WAVE, 4, 1, m_pFile) == 0); + CLOSE_ON_ERROR(WAVE != 'EVAW') + CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0); + CLOSE_ON_ERROR(DataHeader.ID != ' tmf'); + + CLOSE_ON_ERROR(fread(&m_FormatHeader, Min(DataHeader.Size, sizeof(tFormatHeader)), 1, m_pFile) == 0); + CLOSE_ON_ERROR(DataHeader.Size > sizeof(tFormatHeader)); + + switch (m_FormatHeader.AudioFormat) + { + case WAVEFMT_XBOX_ADPCM: + m_FormatHeader.AudioFormat = WAVEFMT_IMA_ADPCM; + case WAVEFMT_IMA_ADPCM: + m_nSamplesPerBlock = (m_FormatHeader.BlockAlign / m_FormatHeader.NumChannels - 4) * 2 + 1; + m_pAdpcmBuffer = new uint8[m_FormatHeader.BlockAlign]; + m_ppPcmBuffers = new int16*[m_FormatHeader.NumChannels]; + m_pAdpcmDecoders = new CImaADPCMDecoder[m_FormatHeader.NumChannels]; + break; + case WAVEFMT_PCM: + m_nSamplesPerBlock = 1; + if (m_FormatHeader.BitsPerSample != 16) + { + debug("Unsupported PCM (%d bits), only signed 16-bit is supported (%s)\n", m_FormatHeader.BitsPerSample, path); + Close(); + return; + } + break; + default: + debug("Unsupported wav format 0x%x (%s)\n", m_FormatHeader.AudioFormat, path); + Close(); + return; + } + + while (true) { + CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0); + if (DataHeader.ID == 'atad') + break; + fseek(m_pFile, DataHeader.Size, SEEK_CUR); + // TODO? validate data size + // maybe check if there no extreme custom headers that might break this + } + + m_DataStartOffset = ftell(m_pFile); + m_nSampleCount = DataHeader.Size / m_FormatHeader.BlockAlign * m_nSamplesPerBlock; + + m_bIsOpen = true; +#undef CLOSE_ON_ERROR + } + + void FileOpen() + { + } + + ~CWavFile() + { + Close(); + } + + bool IsOpened() + { + return m_bIsOpen; + } + + + uint32 GetSampleSize() + { + return sizeof(uint16); + } + + uint32 GetSampleCount() + { + return m_nSampleCount; + } + + uint32 GetSampleRate() + { + return m_FormatHeader.SampleRate; + } + + uint32 GetChannels() + { + return m_FormatHeader.NumChannels; + } + + void Seek(uint32 milliseconds) + { + if (!IsOpened()) return; + fseek(m_pFile, m_DataStartOffset + ms2samples(milliseconds) / m_nSamplesPerBlock * m_FormatHeader.BlockAlign, SEEK_SET); + } + + uint32 Tell() + { + if (!IsOpened()) return 0; + return samples2ms(GetCurrentSample()); + } + +#define SAMPLES_IN_LINE (8) + + uint32 Decode(void* buffer) + { + if (!IsOpened()) return 0; + + if (m_FormatHeader.AudioFormat == WAVEFMT_PCM) + { + // just read the file and sort the samples + uint32 size = fread(buffer, 1, GetBufferSize(), m_pFile); + if (m_FormatHeader.NumChannels == 2) + SortStereoBuffer.SortStereo(buffer, size); + return size; + } + else if (m_FormatHeader.AudioFormat == WAVEFMT_IMA_ADPCM) + { + // trim the buffer size if we're at the end of our file + uint32 nMaxSamples = GetBufferSamples() / m_FormatHeader.NumChannels; + uint32 nSamplesLeft = m_nSampleCount - GetCurrentSample(); + nMaxSamples = Min(nMaxSamples, nSamplesLeft); + + // align sample count to our block + nMaxSamples = nMaxSamples / m_nSamplesPerBlock * m_nSamplesPerBlock; + + // count the size of output buffer + uint32 OutBufSizePerChannel = nMaxSamples * GetSampleSize(); + uint32 OutBufSize = OutBufSizePerChannel * m_FormatHeader.NumChannels; + + // calculate the pointers to individual channel buffers + for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++) + m_ppPcmBuffers[i] = (int16*)((int8*)buffer + OutBufSizePerChannel * i); + + uint32 samplesRead = 0; + while (samplesRead < nMaxSamples) + { + // read the file + uint8 *pAdpcmBuf = m_pAdpcmBuffer; + if (fread(m_pAdpcmBuffer, 1, m_FormatHeader.BlockAlign, m_pFile) == 0) + return 0; + + // get the first sample in adpcm block and initialise the decoder(s) + for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++) + { + int16 Sample = *(int16*)pAdpcmBuf; + pAdpcmBuf += sizeof(int16); + int16 Step = *(int16*)pAdpcmBuf; + pAdpcmBuf += sizeof(int16); + m_pAdpcmDecoders[i].Init(Sample, Step); + *(m_ppPcmBuffers[i]) = Sample; + m_ppPcmBuffers[i]++; + } + samplesRead++; + + // decode the rest of the block + for (uint32 s = 1; s < m_nSamplesPerBlock; s += SAMPLES_IN_LINE) + { + for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++) + { + m_pAdpcmDecoders[i].Decode(pAdpcmBuf, m_ppPcmBuffers[i], SAMPLES_IN_LINE / 2); + pAdpcmBuf += SAMPLES_IN_LINE / 2; + m_ppPcmBuffers[i] += SAMPLES_IN_LINE; + } + samplesRead += SAMPLES_IN_LINE; + } + } + return OutBufSize; + } + return 0; + } +}; + +#ifdef AUDIO_OAL_USE_SNDFILE +class CSndFile : public IDecoder +{ + SNDFILE *m_pfSound; + SF_INFO m_soundInfo; +public: + CSndFile(const char *path) : + m_pfSound(nil) + { + memset(&m_soundInfo, 0, sizeof(m_soundInfo)); + m_pfSound = sf_open(path, SFM_READ, &m_soundInfo); + } + + void FileOpen() + { + } + + ~CSndFile() + { + if ( m_pfSound ) + { + sf_close(m_pfSound); + m_pfSound = nil; + } + } + + bool IsOpened() + { + return m_pfSound != nil; + } + + uint32 GetSampleSize() + { + return sizeof(uint16); + } + + uint32 GetSampleCount() + { + return m_soundInfo.frames; + } + + uint32 GetSampleRate() + { + return m_soundInfo.samplerate; + } + + uint32 GetChannels() + { + return m_soundInfo.channels; + } + + void Seek(uint32 milliseconds) + { + if ( !IsOpened() ) return; + sf_seek(m_pfSound, ms2samples(milliseconds), SF_SEEK_SET); + } + + uint32 Tell() + { + if ( !IsOpened() ) return 0; + return samples2ms(sf_seek(m_pfSound, 0, SF_SEEK_CUR)); + } + + uint32 Decode(void *buffer) + { + if ( !IsOpened() ) return 0; + + size_t size = sf_read_short(m_pfSound, (short*)buffer, GetBufferSamples()) * GetSampleSize(); + if (GetChannels()==2) + SortStereoBuffer.SortStereo(buffer, size); + return size; + } +}; +#endif + +#ifdef AUDIO_OAL_USE_MPG123 + +class CMP3File : public IDecoder +{ +protected: + mpg123_handle *m_pMH; + bool m_bOpened; + uint32 m_nRate; + uint32 m_nChannels; + const char* m_pPath; + bool m_bFileNotOpenedYet; +public: + CMP3File(const char *path) : + m_pMH(nil), + m_bOpened(false), + m_nRate(0), + m_nChannels(0), + m_pPath(path), + m_bFileNotOpenedYet(false) + { + m_pMH = mpg123_new(nil, nil); + if ( m_pMH ) + { + mpg123_param(m_pMH, MPG123_FLAGS, MPG123_SEEKBUFFER | MPG123_GAPLESS, 0.0); + + m_bOpened = true; + m_bFileNotOpenedYet = true; + // It's possible to move this to audioFileOpsThread(), but effect isn't noticable + probably not compatible with our current cutscene audio handling +#if 1 + FileOpen(); +#endif + } + } + + void FileOpen() + { + if(!m_bFileNotOpenedYet) return; + + long rate = 0; + int channels = 0; + int encoding = 0; + m_bOpened = mpg123_open(m_pMH, m_pPath) == MPG123_OK + && mpg123_getformat(m_pMH, &rate, &channels, &encoding) == MPG123_OK; + + m_nRate = rate; + m_nChannels = channels; + + if(IsOpened()) { + mpg123_format_none(m_pMH); + mpg123_format(m_pMH, rate, channels, encoding); + } + m_bFileNotOpenedYet = false; + } + + ~CMP3File() + { + if ( m_pMH ) + { + mpg123_close(m_pMH); + mpg123_delete(m_pMH); + m_pMH = nil; + } + } + + bool IsOpened() + { + return m_bOpened; + } + + uint32 GetSampleSize() + { + return sizeof(uint16); + } + + uint32 GetSampleCount() + { + if ( !IsOpened() || m_bFileNotOpenedYet ) return 0; + return mpg123_length(m_pMH); + } + + uint32 GetSampleRate() + { + return m_nRate; + } + + uint32 GetChannels() + { + return m_nChannels; + } + + void Seek(uint32 milliseconds) + { + if ( !IsOpened() || m_bFileNotOpenedYet ) return; + mpg123_seek(m_pMH, ms2samples(milliseconds), SEEK_SET); + } + + uint32 Tell() + { + if ( !IsOpened() || m_bFileNotOpenedYet ) return 0; + return samples2ms(mpg123_tell(m_pMH)); + } + + uint32 Decode(void *buffer) + { + if ( !IsOpened() || m_bFileNotOpenedYet ) return 0; + + size_t size; + int err = mpg123_read(m_pMH, (unsigned char *)buffer, GetBufferSize(), &size); +#if defined(__LP64__) || defined(_WIN64) + assert("We can't handle audio files more then 2 GB yet :shrug:" && (size < UINT32_MAX)); +#endif + if (err != MPG123_OK && err != MPG123_DONE) return 0; + if (GetChannels() == 2) + SortStereoBuffer.SortStereo(buffer, size); + return (uint32)size; + } +}; + +#endif +#define VAG_LINE_SIZE (0x10) +#define VAG_SAMPLES_IN_LINE (28) + +class CVagDecoder +{ + const double f[5][2] = { { 0.0, 0.0 }, + { 60.0 / 64.0, 0.0 }, + { 115.0 / 64.0, -52.0 / 64.0 }, + { 98.0 / 64.0, -55.0 / 64.0 }, + { 122.0 / 64.0, -60.0 / 64.0 } }; + + double s_1; + double s_2; +public: + CVagDecoder() + { + ResetState(); + } + + void ResetState() + { + s_1 = s_2 = 0.0; + } + + static short quantize(double sample) + { + int a = int(sample + 0.5); + return short(Clamp(a, -32768, 32767)); + } + + void Decode(void* _inbuf, int16* _outbuf, size_t size) + { + uint8* inbuf = (uint8*)_inbuf; + int16* outbuf = _outbuf; + size &= ~(VAG_LINE_SIZE - 1); + + while (size > 0) { + double samples[VAG_SAMPLES_IN_LINE]; + + int predict_nr, shift_factor, flags; + predict_nr = *(inbuf++); + shift_factor = predict_nr & 0xf; + predict_nr >>= 4; + flags = *(inbuf++); + if (flags == 7) // TODO: ignore? + break; + for (int i = 0; i < VAG_SAMPLES_IN_LINE; i += 2) { + int d = *(inbuf++); + int16 s = int16((d & 0xf) << 12); + samples[i] = (double)(s >> shift_factor); + s = int16((d & 0xf0) << 8); + samples[i + 1] = (double)(s >> shift_factor); + } + + for (int i = 0; i < VAG_SAMPLES_IN_LINE; i++) { + samples[i] = samples[i] + s_1 * f[predict_nr][0] + s_2 * f[predict_nr][1]; + s_2 = s_1; + s_1 = samples[i]; + *(outbuf++) = quantize(samples[i] + 0.5); + } + size -= VAG_LINE_SIZE; + } + } +}; + +#define VB_BLOCK_SIZE (0x2000) +#define NUM_VAG_LINES_IN_BLOCK (VB_BLOCK_SIZE / VAG_LINE_SIZE) +#define NUM_VAG_SAMPLES_IN_BLOCK (NUM_VAG_LINES_IN_BLOCK * VAG_SAMPLES_IN_LINE) + +class CVbFile : public IDecoder +{ + FILE *m_pFile; + CVagDecoder *m_pVagDecoders; + + size_t m_FileSize; + size_t m_nNumberOfBlocks; + + uint32 m_nSampleRate; + uint8 m_nChannels; + bool m_bBlockRead; + uint16 m_LineInBlock; + size_t m_CurrentBlock; + + uint8 **m_ppVagBuffers; // buffers that cache actual ADPCM file data + int16 **m_ppPcmBuffers; + + void ReadBlock(int32 block = -1) + { + // just read next block if -1 + if (block != -1) + fseek(m_pFile, block * m_nChannels * VB_BLOCK_SIZE, SEEK_SET); + + for (int i = 0; i < m_nChannels; i++) + fread(m_ppVagBuffers[i], VB_BLOCK_SIZE, 1, m_pFile); + m_bBlockRead = true; + } + +public: + CVbFile(const char* path, uint32 nSampleRate = 32000, uint8 nChannels = 2) : m_nSampleRate(nSampleRate), m_nChannels(nChannels), m_pVagDecoders(nil), m_ppVagBuffers(nil), m_ppPcmBuffers(nil), + m_FileSize(0), m_nNumberOfBlocks(0), m_bBlockRead(false), m_LineInBlock(0), m_CurrentBlock(0) + { + m_pFile = fopen(path, "rb"); + if (!m_pFile) return; + + fseek(m_pFile, 0, SEEK_END); + m_FileSize = ftell(m_pFile); + fseek(m_pFile, 0, SEEK_SET); + + m_nNumberOfBlocks = m_FileSize / (nChannels * VB_BLOCK_SIZE); + m_pVagDecoders = new CVagDecoder[nChannels]; + m_ppVagBuffers = new uint8*[nChannels]; + m_ppPcmBuffers = new int16*[nChannels]; + for (uint8 i = 0; i < nChannels; i++) + m_ppVagBuffers[i] = new uint8[VB_BLOCK_SIZE]; + } + + void FileOpen() + { + } + + ~CVbFile() + { + if (m_pFile) + { + fclose(m_pFile); + + delete[] m_pVagDecoders; + for (int i = 0; i < m_nChannels; i++) + delete[] m_ppVagBuffers[i]; + delete[] m_ppVagBuffers; + delete[] m_ppPcmBuffers; + } + } + + bool IsOpened() + { + return m_pFile != nil; + } + + uint32 GetSampleSize() + { + return sizeof(uint16); + } + + uint32 GetSampleCount() + { + if (!IsOpened()) return 0; + return m_nNumberOfBlocks * NUM_VAG_LINES_IN_BLOCK * VAG_SAMPLES_IN_LINE; + } + + uint32 GetSampleRate() + { + return m_nSampleRate; + } + + uint32 GetChannels() + { + return m_nChannels; + } + + void Seek(uint32 milliseconds) + { + if (!IsOpened()) return; + uint32 samples = ms2samples(milliseconds); + + // find the block of our sample + uint32 block = samples / NUM_VAG_SAMPLES_IN_BLOCK; + if (block > m_nNumberOfBlocks) + { + samples = 0; + block = 0; + } + if (block != m_CurrentBlock) + m_bBlockRead = false; + + // find a line of our sample within our block + uint32 remainingSamples = samples - block * NUM_VAG_SAMPLES_IN_BLOCK; + uint32 newLine = remainingSamples / VAG_SAMPLES_IN_LINE / VAG_LINE_SIZE; + + if (m_CurrentBlock != block || m_LineInBlock != newLine) + { + m_CurrentBlock = block; + m_LineInBlock = newLine; + for (uint32 i = 0; i < GetChannels(); i++) + m_pVagDecoders[i].ResetState(); + } + + } + + uint32 Tell() + { + if (!IsOpened()) return 0; + uint32 pos = (m_CurrentBlock * NUM_VAG_LINES_IN_BLOCK + m_LineInBlock) * VAG_SAMPLES_IN_LINE; + return samples2ms(pos); + } + + uint32 Decode(void* buffer) + { + if (!IsOpened()) return 0; + + if (m_CurrentBlock >= m_nNumberOfBlocks) return 0; + + // cache current ADPCM block + if (!m_bBlockRead) + ReadBlock(m_CurrentBlock); + + // trim the buffer size if we're at the end of our file + int numberOfRequiredLines = GetBufferSamples() / m_nChannels / VAG_SAMPLES_IN_LINE; + int numberOfRemainingLines = (m_nNumberOfBlocks - m_CurrentBlock) * NUM_VAG_LINES_IN_BLOCK - m_LineInBlock; + int bufSizePerChannel = Min(numberOfRequiredLines, numberOfRemainingLines) * VAG_SAMPLES_IN_LINE * GetSampleSize(); + + // calculate the pointers to individual channel buffers + for (uint32 i = 0; i < m_nChannels; i++) + m_ppPcmBuffers[i] = (int16*)((int8*)buffer + bufSizePerChannel * i); + + int size = 0; + while (size < bufSizePerChannel) + { + // decode the VAG lines + for (uint32 i = 0; i < m_nChannels; i++) + { + m_pVagDecoders[i].Decode(m_ppVagBuffers[i] + m_LineInBlock * VAG_LINE_SIZE, m_ppPcmBuffers[i], VAG_LINE_SIZE); + m_ppPcmBuffers[i] += VAG_SAMPLES_IN_LINE; + } + size += VAG_SAMPLES_IN_LINE * GetSampleSize(); + m_LineInBlock++; + + // block is over, read the next block + if (m_LineInBlock >= NUM_VAG_LINES_IN_BLOCK) + { + m_CurrentBlock++; + if (m_CurrentBlock >= m_nNumberOfBlocks) // end of file + break; + m_LineInBlock = 0; + ReadBlock(); + } + } + + return bufSizePerChannel * m_nChannels; + } +}; +#ifdef AUDIO_OAL_USE_OPUS +class COpusFile : public IDecoder +{ + OggOpusFile *m_FileH; + bool m_bOpened; + uint32 m_nRate; + uint32 m_nChannels; +public: + COpusFile(const char *path) : m_FileH(nil), + m_bOpened(false), + m_nRate(0), + m_nChannels(0) + { + int ret; + m_FileH = op_open_file(path, &ret); + + if (m_FileH) { + m_nChannels = op_head(m_FileH, 0)->channel_count; + m_nRate = 48000; + const OpusTags *tags = op_tags(m_FileH, 0); + for (int i = 0; i < tags->comments; i++) { + if (strncmp(tags->user_comments[i], "SAMPLERATE", sizeof("SAMPLERATE")-1) == 0) + { + sscanf(tags->user_comments[i], "SAMPLERATE=%i", &m_nRate); + break; + } + } + + m_bOpened = true; + } + } + + void FileOpen() + { + } + + ~COpusFile() + { + if (m_FileH) + { + op_free(m_FileH); + m_FileH = nil; + } + } + + bool IsOpened() + { + return m_bOpened; + } + + uint32 GetSampleSize() + { + return sizeof(uint16); + } + + uint32 GetSampleCount() + { + if ( !IsOpened() ) return 0; + return op_pcm_total(m_FileH, 0); + } + + uint32 GetSampleRate() + { + return m_nRate; + } + + uint32 GetChannels() + { + return m_nChannels; + } + + void Seek(uint32 milliseconds) + { + if ( !IsOpened() ) return; + op_pcm_seek(m_FileH, ms2samples(milliseconds) / GetChannels()); + } + + uint32 Tell() + { + if ( !IsOpened() ) return 0; + return samples2ms(op_pcm_tell(m_FileH) * GetChannels()); + } + + uint32 Decode(void *buffer) + { + if ( !IsOpened() ) return 0; + + int size = op_read(m_FileH, (opus_int16 *)buffer, GetBufferSamples(), NULL); + + if (size < 0) + return 0; + + if (GetChannels() == 2) + SortStereoBuffer.SortStereo(buffer, size * m_nChannels * GetSampleSize()); + + return size * m_nChannels * GetSampleSize(); + } +}; +#endif + + +// For multi-thread: Someone always acquire stream's mutex before entering here +void +CStream::BuffersShouldBeFilled() +{ +#ifdef MULTITHREADED_AUDIO + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) { + std::queue> tempQueue; + for(int i = 0; i < NUM_STREAMBUFFERS / 2; i++) { + tempQueue.push(std::pair(m_alBuffers[i * 2], m_alBuffers[i * 2 + 1])); + } + m_fillBuffers.swap(tempQueue); + + FlagAsToBeProcessed(); + + m_bActive = true; // to allow Update() to queue the filled buffers & play + return; + } + std::queue>().swap(m_fillBuffers); +#endif + if ( FillBuffers() != 0 ) + { + SetPlay(true); + } +} + +// returns whether it's queued (not on multi-thread) +bool +CStream::BufferShouldBeFilledAndQueued(std::pair* bufs) +{ +#ifdef MULTITHREADED_AUDIO + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) + m_fillBuffers.push(*bufs); + else +#endif + { + ALuint alBuffers[2] = {(*bufs).first, (*bufs).second}; // left - right + if (FillBuffer(alBuffers)) { + alSourceQueueBuffers(m_pAlSources[0], 1, &alBuffers[0]); + alSourceQueueBuffers(m_pAlSources[1], 1, &alBuffers[1]); + return true; + } + } + return false; +} + +#ifdef MULTITHREADED_AUDIO +void +CStream::FlagAsToBeProcessed(bool close) +{ + if (!close && MusicManager.m_nMusicMode == MUSICMODE_CUTSCENE) + return; + + gAudioThreadQueueMutex.lock(); + if (close) + gStreamsToClose.push(std::pair(m_pSoundFile ? m_pSoundFile : nil, m_pBuffer ? m_pBuffer : nil)); + else + gStreamsToProcess.push(this); + + gAudioThreadQueueMutex.unlock(); + + gAudioThreadCv.notify_one(); +} + +void audioFileOpsThread() +{ + do + { + CStream *stream; + { + // Just a semaphore + std::unique_lock queueMutex(gAudioThreadQueueMutex); + gAudioThreadCv.wait(queueMutex, [] { return gStreamsToProcess.size() > 0 || gStreamsToClose.size() > 0 || gAudioThreadTerm; }); + if (gAudioThreadTerm) + return; + + if (!gStreamsToClose.empty()) { + auto streamToClose = gStreamsToClose.front(); + gStreamsToClose.pop(); + if (streamToClose.first) { // pSoundFile + delete streamToClose.first; + } + + if (streamToClose.second) { // pBuffer + free(streamToClose.second); + } + } + + if (!gStreamsToProcess.empty()) { + stream = gStreamsToProcess.front(); + gStreamsToProcess.pop(); + } else + continue; + } + + std::unique_lock lock(stream->m_mutex); + + std::pair buffers, *lastBufAddr; + bool insertBufsAfterCheck = false; + + do { + if (!stream->IsOpened()) { + break; + } + + if (stream->m_bReset) + break; + + // We gave up this idea for now + /* + stream->m_pSoundFile->FileOpen(); + + // Deffered allocation, do it now + if (stream->m_pBuffer == nil) { + stream->m_pBuffer = malloc(stream->m_pSoundFile->GetBufferSize()); + ASSERT(stream->m_pBuffer != nil); + } + */ + + if (stream->m_bDoSeek) { + stream->m_bDoSeek = false; + int pos = stream->m_SeekPos; + lock.unlock(); + stream->m_pSoundFile->Seek(pos); + lock.lock(); + + continue; // let's do the checks again, make sure we didn't miss anything while Seeking + } + + if (insertBufsAfterCheck) { + stream->m_queueBuffers.push(buffers); + insertBufsAfterCheck = false; + } + + if (!stream->m_fillBuffers.empty()) { + lastBufAddr = &stream->m_fillBuffers.front(); + buffers = *lastBufAddr; + lock.unlock(); + + ALuint alBuffers[2] = {buffers.first, buffers.second}; // left - right + bool filled = stream->FillBuffer(alBuffers); + + lock.lock(); + + // Make sure queue isn't touched after we released mutex + if (!stream->m_fillBuffers.empty() && lastBufAddr == &stream->m_fillBuffers.front()) { + stream->m_fillBuffers.pop(); + if (filled) + insertBufsAfterCheck = true; // Also make sure stream's properties aren't changed. So make one more pass, and push it to m_queueBuffers only if it pass checks again. + } + } else + break; + + } while (true); + + } while(true); +} +#endif + +void CStream::Initialise() +{ +#ifdef AUDIO_OAL_USE_MPG123 + mpg123_init(); +#endif +#ifdef MULTITHREADED_AUDIO + gAudioThread = std::thread(audioFileOpsThread); +#endif +} + +void CStream::Terminate() +{ +#ifdef AUDIO_OAL_USE_MPG123 + mpg123_exit(); +#endif +#ifdef MULTITHREADED_AUDIO + gAudioThreadQueueMutex.lock(); + gAudioThreadTerm = true; + gAudioThreadQueueMutex.unlock(); + + gAudioThreadCv.notify_one(); + gAudioThread.join(); +#endif +} + +CStream::CStream(ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS]) : + m_pAlSources(sources), + m_alBuffers(buffers), + m_pBuffer(nil), + m_bPaused(false), + m_bActive(false), +#ifdef MULTITHREADED_AUDIO + m_bIExist(false), + m_bDoSeek(false), + m_SeekPos(0), +#endif + m_pSoundFile(nil), + m_bReset(false), + m_nVolume(0), + m_nPan(0), + m_nPosBeforeReset(0), + m_nLoopCount(1) + +{ +} + +bool CStream::Open(const char* filename, uint32 overrideSampleRate) +{ + if (IsOpened()) return false; + +#ifdef MULTITHREADED_AUDIO + std::unique_lock lock(m_mutex); + + m_bDoSeek = false; + m_SeekPos = 0; +#endif + + m_bPaused = false; + m_bActive = false; + m_bReset = false; + m_nVolume = 0; + m_nPan = 0; + m_nPosBeforeReset = 0; + m_nLoopCount = 1; + +// Be case-insensitive on linux (from https://github.com/OneSadCookie/fcaseopen/) +#if !defined(_WIN32) + char *real = casepath(filename); + if (real) { + strcpy(m_aFilename, real); + free(real); + } else { +#else + { +#endif + strcpy(m_aFilename, filename); + } + + DEV("Stream %s\n", m_aFilename); + + if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".wav")], ".wav")) +#ifdef AUDIO_OAL_USE_SNDFILE + m_pSoundFile = new CSndFile(m_aFilename); +#else + m_pSoundFile = new CWavFile(m_aFilename); +#endif +#ifdef AUDIO_OAL_USE_MPG123 + else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".mp3")], ".mp3")) + m_pSoundFile = new CMP3File(m_aFilename); +#endif + else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".vb")], ".VB")) + m_pSoundFile = new CVbFile(m_aFilename, overrideSampleRate); +#ifdef AUDIO_OAL_USE_OPUS + else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".opus")], ".opus")) + m_pSoundFile = new COpusFile(m_aFilename); +#endif + else + m_pSoundFile = nil; + + if ( m_pSoundFile && m_pSoundFile->IsOpened() ) + { + uint32 bufSize = m_pSoundFile->GetBufferSize(); + if(bufSize != 0) { // Otherwise it's deferred + m_pBuffer = malloc(bufSize); + ASSERT(m_pBuffer != nil); + + DEV("AvgSamplesPerSec: %d\n", m_pSoundFile->GetAvgSamplesPerSec()); + DEV("SampleCount: %d\n", m_pSoundFile->GetSampleCount()); + DEV("SampleRate: %d\n", m_pSoundFile->GetSampleRate()); + DEV("Channels: %d\n", m_pSoundFile->GetChannels()); + DEV("Buffer Samples: %d\n", m_pSoundFile->GetBufferSamples()); + DEV("Buffer sec: %f\n", (float(m_pSoundFile->GetBufferSamples()) / float(m_pSoundFile->GetChannels())/ float(m_pSoundFile->GetSampleRate()))); + DEV("Length MS: %02d:%02d\n", (m_pSoundFile->GetLength() / 1000) / 60, (m_pSoundFile->GetLength() / 1000) % 60); + } +#ifdef MULTITHREADED_AUDIO + m_bIExist = true; +#endif + return true; + } + return false; +} + +CStream::~CStream() +{ + assert(!IsOpened()); +} + +void CStream::Close() +{ + if(!IsOpened()) return; + +#ifdef MULTITHREADED_AUDIO + { + std::lock_guard lock(m_mutex); + + Stop(); + ClearBuffers(); + m_bIExist = false; + std::queue>().swap(m_fillBuffers); + tsQueue>().swapNts(m_queueBuffers); // TSness not required, mutex is acquired + } + + FlagAsToBeProcessed(true); +#else + + Stop(); + ClearBuffers(); + + if ( m_pSoundFile ) + { + delete m_pSoundFile; + m_pSoundFile = nil; + } + + if ( m_pBuffer ) + { + free(m_pBuffer); + m_pBuffer = nil; + } +#endif +} + +bool CStream::HasSource() +{ + return (m_pAlSources[0] != AL_NONE) && (m_pAlSources[1] != AL_NONE); +} + +// m_bIExist only written in main thread, thus mutex is not needed on main thread +bool CStream::IsOpened() +{ +#ifdef MULTITHREADED_AUDIO + return m_bIExist; +#else + return m_pSoundFile && m_pSoundFile->IsOpened(); +#endif +} + +bool CStream::IsPlaying() +{ + if ( !HasSource() || !IsOpened() ) return false; + + if ( !m_bPaused ) + { + ALint sourceState[2]; + alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState[0]); + alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState[1]); + if (sourceState[0] == AL_PLAYING || sourceState[1] == AL_PLAYING) + return true; + +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + // Streams are designed in such a way that m_fillBuffers and m_queueBuffers will be *always* filled if audio is playing, and mutex is acquired + if (!m_fillBuffers.empty() || !m_queueBuffers.emptyNts()) + return true; +#endif + } + + return false; +} + +void CStream::Pause() +{ + if ( !HasSource() ) return; + ALint sourceState = AL_PAUSED; + alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_PAUSED) + alSourcePause(m_pAlSources[0]); + alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_PAUSED) + alSourcePause(m_pAlSources[1]); +} + +void CStream::SetPause(bool bPause) +{ + if ( !HasSource() ) return; + if ( bPause ) + { + Pause(); + m_bPaused = true; + } + else + { + if (m_bPaused) + SetPlay(true); + m_bPaused = false; + } +} + +void CStream::SetPitch(float pitch) +{ + if ( !HasSource() ) return; + alSourcef(m_pAlSources[0], AL_PITCH, pitch); + alSourcef(m_pAlSources[1], AL_PITCH, pitch); +} + +void CStream::SetGain(float gain) +{ + if ( !HasSource() ) return; + alSourcef(m_pAlSources[0], AL_GAIN, gain); + alSourcef(m_pAlSources[1], AL_GAIN, gain); +} + +void CStream::SetPosition(int i, float x, float y, float z) +{ + if ( !HasSource() ) return; + alSource3f(m_pAlSources[i], AL_POSITION, x, y, z); +} + +void CStream::SetVolume(uint32 nVol) +{ + m_nVolume = nVol; + SetGain(ALfloat(nVol) / MAX_VOLUME); +} + +void CStream::SetPan(uint8 nPan) +{ + m_nPan = Clamp((int8)nPan - 63, 0, 63); + SetPosition(0, (m_nPan - 63) / 64.0f, 0.0f, Sqrt(1.0f - SQR((m_nPan - 63) / 64.0f))); + + m_nPan = Clamp((int8)nPan + 64, 64, 127); + SetPosition(1, (m_nPan - 63) / 64.0f, 0.0f, Sqrt(1.0f - SQR((m_nPan - 63) / 64.0f))); + + m_nPan = nPan; +} + +// Should only be called if source is stopped +void CStream::SetPosMS(uint32 nPos) +{ + if ( !IsOpened() ) return; + +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + std::queue>().swap(m_fillBuffers); + tsQueue>().swapNts(m_queueBuffers); // TSness not required, second thread always access it when stream mutex acquired + + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) { + m_bDoSeek = true; + m_SeekPos = nPos; + } else +#endif + { + m_pSoundFile->Seek(nPos); + } + ClearBuffers(); + + // adding to gStreamsToProcess not needed, someone always calls Start() / BuffersShouldBeFilled() after SetPosMS +} + +uint32 CStream::GetPosMS() +{ + if ( !HasSource() ) return 0; + if ( !IsOpened() ) return 0; + + // Deferred init causes division by zero + if (m_pSoundFile->GetChannels() == 0) + return 0; + + ALint offset; + //alGetSourcei(m_alSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(m_pAlSources[0], AL_BYTE_OFFSET, &offset); + + //std::lock_guard lock(m_mutex); + + return m_pSoundFile->Tell() + - m_pSoundFile->samples2ms(m_pSoundFile->GetBufferSamples() * (NUM_STREAMBUFFERS/2-1)) / m_pSoundFile->GetChannels() + + m_pSoundFile->samples2ms(offset/m_pSoundFile->GetSampleSize()) / m_pSoundFile->GetChannels(); +} + +uint32 CStream::GetLengthMS() +{ + if ( !IsOpened() ) return 0; + return m_pSoundFile->GetLength(); +} + +bool CStream::FillBuffer(ALuint *alBuffer) +{ +#ifndef MULTITHREADED_AUDIO + if ( !HasSource() ) + return false; + if ( !IsOpened() ) + return false; + if ( !(alBuffer[0] != AL_NONE && alIsBuffer(alBuffer[0])) ) + return false; + if ( !(alBuffer[1] != AL_NONE && alIsBuffer(alBuffer[1])) ) + return false; +#endif + + uint32 size = m_pSoundFile->Decode(m_pBuffer); + if( size == 0 ) + return false; + + uint32 channelSize = size / m_pSoundFile->GetChannels(); + + alBufferData(alBuffer[0], AL_FORMAT_MONO16, m_pBuffer, channelSize, m_pSoundFile->GetSampleRate()); + // TODO: use just one buffer if we play mono + if (m_pSoundFile->GetChannels() == 1) + alBufferData(alBuffer[1], AL_FORMAT_MONO16, m_pBuffer, channelSize, m_pSoundFile->GetSampleRate()); + else + alBufferData(alBuffer[1], AL_FORMAT_MONO16, (uint8*)m_pBuffer + channelSize, channelSize, m_pSoundFile->GetSampleRate()); + return true; +} + +#ifdef MULTITHREADED_AUDIO +bool CStream::QueueBuffers() +{ + bool buffersQueued = false; + std::pair buffers; + while (m_queueBuffers.peekPop(&buffers)) // beware: m_queueBuffers is tsQueue + { + ALuint leftBuf = buffers.first; + ALuint rightBuf = buffers.second; + + alSourceQueueBuffers(m_pAlSources[0], 1, &leftBuf); + alSourceQueueBuffers(m_pAlSources[1], 1, &rightBuf); + + buffersQueued = true; + } + return buffersQueued; +} +#endif + +// Only used in single-threaded audio or cutscene audio +int32 CStream::FillBuffers() +{ + int32 i = 0; + for ( i = 0; i < NUM_STREAMBUFFERS/2; i++ ) + { + if ( !FillBuffer(&m_alBuffers[i*2]) ) + break; + alSourceQueueBuffers(m_pAlSources[0], 1, &m_alBuffers[i*2]); + alSourceQueueBuffers(m_pAlSources[1], 1, &m_alBuffers[i*2+1]); + } + + return i; +} + +void CStream::ClearBuffers() +{ + if ( !HasSource() ) return; + + ALint buffersQueued[2]; + alGetSourcei(m_pAlSources[0], AL_BUFFERS_QUEUED, &buffersQueued[0]); + alGetSourcei(m_pAlSources[1], AL_BUFFERS_QUEUED, &buffersQueued[1]); + + ALuint value; + while (buffersQueued[0]--) + alSourceUnqueueBuffers(m_pAlSources[0], 1, &value); + while (buffersQueued[1]--) + alSourceUnqueueBuffers(m_pAlSources[1], 1, &value); +} + +bool CStream::Setup(bool imSureQueueIsEmpty, bool lock) +{ + if ( IsOpened() ) + { +#ifdef MULTITHREADED_AUDIO + if (lock) + m_mutex.lock(); +#endif + + if (!imSureQueueIsEmpty) { + Stop(); + ClearBuffers(); + } +#ifdef MULTITHREADED_AUDIO + if (MusicManager.m_nMusicMode == MUSICMODE_CUTSCENE) { + m_pSoundFile->Seek(0); + } else { + m_bDoSeek = true; + m_SeekPos = 0; + } + + if (lock) + m_mutex.unlock(); +#else + m_pSoundFile->Seek(0); +#endif + + //SetPosition(0.0f, 0.0f, 0.0f); + SetPitch(1.0f); + //SetPan(m_nPan); + //SetVolume(100); + } + + return IsOpened(); +} + +void CStream::SetLoopCount(int32 count) +{ + if ( !HasSource() ) return; + + m_nLoopCount = count; +} + +void CStream::SetPlay(bool state) +{ + if ( !HasSource() ) return; + if ( state ) + { + ALint sourceState = AL_PLAYING; + alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_PLAYING ) + alSourcePlay(m_pAlSources[0]); + + sourceState = AL_PLAYING; + alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_PLAYING) + alSourcePlay(m_pAlSources[1]); + + m_bActive = true; + } + else + { + ALint sourceState = AL_STOPPED; + alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_STOPPED) + alSourceStop(m_pAlSources[0]); + + sourceState = AL_STOPPED; + alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_STOPPED) + alSourceStop(m_pAlSources[1]); + + m_bActive = false; + } +} + +void CStream::Start() +{ + if ( !HasSource() ) return; + +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + tsQueue>().swapNts(m_queueBuffers); // TSness not required, second thread always access it when stream mutex acquired +#endif + BuffersShouldBeFilled(); +} + +void CStream::Stop() +{ + if ( !HasSource() ) return; + SetPlay(false); +} + +void CStream::Update() +{ + if ( !IsOpened() ) + return; + + if ( !HasSource() ) + return; + + if ( m_bReset ) + return; + + if ( !m_bPaused ) + { + + bool buffersQueuedAndStarted = false; + bool buffersQueuedButNotStarted = false; +#ifdef MULTITHREADED_AUDIO + // Put it in here because we need totalBuffers after queueing to decide when to loop audio + if (m_bActive) + { + buffersQueuedAndStarted = QueueBuffers(); + if(buffersQueuedAndStarted) { + SetPlay(true); + } + } +#endif + + ALint totalBuffers[2] = {0, 0}; + ALint buffersProcessed[2] = {0, 0}; + + // Relying a lot on left buffer states in here + + do + { + //alSourcef(m_pAlSources[0], AL_ROLLOFF_FACTOR, 0.0f); + alGetSourcei(m_pAlSources[0], AL_BUFFERS_QUEUED, &totalBuffers[0]); + alGetSourcei(m_pAlSources[0], AL_BUFFERS_PROCESSED, &buffersProcessed[0]); + //alSourcef(m_pAlSources[1], AL_ROLLOFF_FACTOR, 0.0f); + alGetSourcei(m_pAlSources[1], AL_BUFFERS_QUEUED, &totalBuffers[1]); + alGetSourcei(m_pAlSources[1], AL_BUFFERS_PROCESSED, &buffersProcessed[1]); + } while (buffersProcessed[0] != buffersProcessed[1]); + + assert(buffersProcessed[0] == buffersProcessed[1]); + + // Correcting OpenAL concepts here: + // AL_BUFFERS_QUEUED = Number of *all* buffers in queue, including processed, processing and pending + // AL_BUFFERS_PROCESSED = Index of the buffer being processing right now. Buffers coming after that(have greater index) are pending buffers. + // which means: totalBuffers[0] - buffersProcessed[0] = pending buffers + + // We should wait queue to be cleared to loop track, because position calculation relies on queue. + if (m_nLoopCount != 1 && m_bActive && totalBuffers[0] == 0) + { +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + if (m_fillBuffers.empty() && m_queueBuffers.emptyNts()) // we already acquired stream mutex, which is enough for second thread. thus Nts variant +#endif + { + Setup(true, false); + BuffersShouldBeFilled(); // will also call SetPlay(true) + if (m_nLoopCount != 0) + m_nLoopCount--; + } + } + else + { + static std::queue> tempFillBuffer; + + while ( buffersProcessed[0]-- ) + { + ALuint buffer[2]; + + alSourceUnqueueBuffers(m_pAlSources[0], 1, &buffer[0]); + alSourceUnqueueBuffers(m_pAlSources[1], 1, &buffer[1]); + + if (m_bActive) + { + tempFillBuffer.push(std::pair(buffer[0], buffer[1])); + } + } + + if (m_bActive && buffersProcessed[1]) + { +#ifdef MULTITHREADED_AUDIO + m_mutex.lock(); +#endif + while (!tempFillBuffer.empty()) { + auto elem = tempFillBuffer.front(); + tempFillBuffer.pop(); + buffersQueuedButNotStarted = BufferShouldBeFilledAndQueued(&elem); + } +#ifdef MULTITHREADED_AUDIO + m_mutex.unlock(); + FlagAsToBeProcessed(); +#endif + + } + } + + // Source may be starved to audio and stopped itself + if (m_bActive && !buffersQueuedAndStarted && (buffersQueuedButNotStarted || (totalBuffers[1] - buffersProcessed[1] != 0))) + SetPlay(true); + } +} + +void CStream::ProviderInit() +{ + if ( m_bReset ) + { + if ( Setup(true, false) ) // lock not needed, thread can't process streams with m_bReset set + { + SetPan(m_nPan); + SetVolume(m_nVolume); + SetLoopCount(m_nLoopCount); + SetPosMS(m_nPosBeforeReset); +#ifdef MULTITHREADED_AUDIO + std::unique_lock lock(m_mutex); +#endif + if(m_bActive) + BuffersShouldBeFilled(); + + if (m_bPaused) + Pause(); + + m_bReset = false; + + } else { +#ifdef MULTITHREADED_AUDIO + std::unique_lock lock(m_mutex); +#endif + m_bReset = false; + } + } +} + +void CStream::ProviderTerm() +{ +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + // unlike Close() we will reuse this stream, so clearing queues are important. + std::queue>().swap(m_fillBuffers); + tsQueue>().swapNts(m_queueBuffers); // stream mutex is already acquired, thus Nts variant +#endif + m_bReset = true; + m_nPosBeforeReset = GetPosMS(); + + Stop(); + ClearBuffers(); +} + +#endif diff --git a/src/audio/oal/stream.h b/src/audio/oal/stream.h new file mode 100644 index 0000000..f045692 --- /dev/null +++ b/src/audio/oal/stream.h @@ -0,0 +1,192 @@ +#pragma once + +#ifdef AUDIO_OAL +#include + +#define NUM_STREAMBUFFERS 8 + +class IDecoder +{ +public: + virtual ~IDecoder() { } + + virtual bool IsOpened() = 0; + virtual void FileOpen() = 0; + + virtual uint32 GetSampleSize() = 0; + virtual uint32 GetSampleCount() = 0; + virtual uint32 GetSampleRate() = 0; + virtual uint32 GetChannels() = 0; + + uint32 GetAvgSamplesPerSec() + { + return GetChannels() * GetSampleRate(); + } + + uint32 ms2samples(uint32 ms) + { + return float(ms) / 1000.0f * float(GetSampleRate()); + } + + uint32 samples2ms(uint32 sm) + { + return float(sm) * 1000.0f / float(GetSampleRate()); + } + + uint32 GetBufferSamples() + { + //return (GetAvgSamplesPerSec() >> 2) - (GetSampleCount() % GetChannels()); + return (GetAvgSamplesPerSec() / 4); // 250ms + } + + uint32 GetBufferSize() + { + return GetBufferSamples() * GetSampleSize(); + } + + virtual void Seek(uint32 milliseconds) = 0; + virtual uint32 Tell() = 0; + + uint32 GetLength() + { + FileOpen(); // abort deferred init, we need length now - game has to cache audio file sizes + return float(GetSampleCount()) * 1000.0f / float(GetSampleRate()); + } + + virtual uint32 Decode(void *buffer) = 0; +}; +#ifdef MULTITHREADED_AUDIO +template class tsQueue +{ +public: + tsQueue() : count(0) { } + + void push(const T &value) + { + std::lock_guard lock(m_mutex); + m_queue.push(value); + count++; + } + + bool peekPop(T *retVal) + { + std::lock_guard lock(m_mutex); + if (count == 0) + return false; + + *retVal = m_queue.front(); + m_queue.pop(); + count--; + return true; + } + + void swapNts(tsQueue &replaceWith) + { + m_queue.swap(replaceWith.m_queue); + replaceWith.count = count; + } + + /* + void swapTs(tsQueue &replaceWith) + { + std::lock_guard lock(m_mutex); + std::lock_guard lock2(replaceWith.m_mutex); + swapNts(replaceWith); + } + */ + + bool emptyNts() + { + return count == 0; + } + + /* + bool emptyTs() + { + std::lock_guard lock(m_mutex); + return emptyNts(); + } + */ + + std::queue m_queue; + int count; + mutable std::mutex m_mutex; +}; +#endif +class CStream +{ + char m_aFilename[128]; + ALuint *m_pAlSources; + ALuint (&m_alBuffers)[NUM_STREAMBUFFERS]; + + bool m_bPaused; + bool m_bActive; + +public: +#ifdef MULTITHREADED_AUDIO + std::mutex m_mutex; + std::queue> m_fillBuffers; // left and right buffer + tsQueue> m_queueBuffers; +// std::condition_variable m_closeCv; + bool m_bDoSeek; + uint32 m_SeekPos; + bool m_bIExist; +#endif + + void *m_pBuffer; + + bool m_bReset; + uint32 m_nVolume; + uint8 m_nPan; + uint32 m_nPosBeforeReset; + int32 m_nLoopCount; + + IDecoder *m_pSoundFile; + + void BuffersShouldBeFilled(); // all + bool BufferShouldBeFilledAndQueued(std::pair*); // two (left-right) +#ifdef MULTITHREADED_AUDIO + void FlagAsToBeProcessed(bool close = false); + bool QueueBuffers(); +#endif + + bool HasSource(); + void SetPosition(int i, float x, float y, float z); + void SetPitch(float pitch); + void SetGain(float gain); + void Pause(); + void SetPlay(bool state); + + bool FillBuffer(ALuint *alBuffer); + int32 FillBuffers(); + void ClearBuffers(); +//public: + static void Initialise(); + static void Terminate(); + + CStream(ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS]); + ~CStream(); + void Delete(); + bool Open(const char *filename, uint32 overrideSampleRate = 32000); + void Close(); + + bool IsOpened(); + bool IsPlaying(); + void SetPause (bool bPause); + void SetVolume(uint32 nVol); + void SetPan (uint8 nPan); + void SetPosMS (uint32 nPos); + uint32 GetPosMS(); + uint32 GetLengthMS(); + + bool Setup(bool imSureQueueIsEmpty = false, bool lock = true); + void Start(); + void Stop(); + void Update(void); + void SetLoopCount(int32); + + void ProviderInit(); + void ProviderTerm(); +}; + +#endif diff --git a/src/audio/sampman.h b/src/audio/sampman.h new file mode 100644 index 0000000..ad14e2b --- /dev/null +++ b/src/audio/sampman.h @@ -0,0 +1,673 @@ +#pragma once +#include "AudioSamples.h" +#include "audio_enums.h" + +#define MAX_VOLUME 127 +#define MAX_FREQ DIGITALRATE + +struct tSample { + uint32 nOffset; + uint32 nSize; + uint32 nFrequency; + uint32 nLoopStart; + int32 nLoopEnd; +}; + +#ifdef GTA_PS2 +#define PS2BANK(e) e +#else +#define PS2BANK(e) e = SFX_BANK_0 +#endif // GTA_PS2 + + +enum +{ + SFX_BANK_0, + + CAR_SFX_BANKS_OFFSET, + SFX_BANK_PACARD = CAR_SFX_BANKS_OFFSET, + SFX_BANK_PATHFINDER, + SFX_BANK_PORSCHE, + SFX_BANK_SPIDER, + SFX_BANK_MERC, + SFX_BANK_TRUCK, + SFX_BANK_HOTROD, + SFX_BANK_COBRA, + SFX_BANK_NONE, + + PS2BANK(SFX_BANK_FRONT_END_MENU), + + PS2BANK(SFX_BANK_TRAIN), + + PS2BANK(SFX_BANK_BUILDING_CLUB_1), + PS2BANK(SFX_BANK_BUILDING_CLUB_2), + PS2BANK(SFX_BANK_BUILDING_CLUB_3), + PS2BANK(SFX_BANK_BUILDING_CLUB_4), + PS2BANK(SFX_BANK_BUILDING_CLUB_5), + PS2BANK(SFX_BANK_BUILDING_CLUB_6), + PS2BANK(SFX_BANK_BUILDING_CLUB_7), + PS2BANK(SFX_BANK_BUILDING_CLUB_8), + PS2BANK(SFX_BANK_BUILDING_CLUB_9), + PS2BANK(SFX_BANK_BUILDING_CLUB_10), + PS2BANK(SFX_BANK_BUILDING_CLUB_11), + PS2BANK(SFX_BANK_BUILDING_CLUB_12), + PS2BANK(SFX_BANK_BUILDING_CLUB_RAGGA), + PS2BANK(SFX_BANK_BUILDING_STRIP_CLUB_1), + PS2BANK(SFX_BANK_BUILDING_STRIP_CLUB_2), + PS2BANK(SFX_BANK_BUILDING_WORKSHOP), + PS2BANK(SFX_BANK_BUILDING_PIANO_BAR), + PS2BANK(SFX_BANK_BUILDING_SAWMILL), + PS2BANK(SFX_BANK_BUILDING_DOG_FOOD_FACTORY), + PS2BANK(SFX_BANK_BUILDING_LAUNDERETTE), + PS2BANK(SFX_BANK_BUILDING_RESTAURANT_CHINATOWN), + PS2BANK(SFX_BANK_BUILDING_RESTAURANT_ITALY), + PS2BANK(SFX_BANK_BUILDING_RESTAURANT_GENERIC_1), + PS2BANK(SFX_BANK_BUILDING_RESTAURANT_GENERIC_2), + PS2BANK(SFX_BANK_BUILDING_AIRPORT), + PS2BANK(SFX_BANK_BUILDING_SHOP), + PS2BANK(SFX_BANK_BUILDING_CINEMA), + PS2BANK(SFX_BANK_BUILDING_DOCKS), + PS2BANK(SFX_BANK_BUILDING_HOME), + PS2BANK(SFX_BANK_BUILDING_PORN_1), + PS2BANK(SFX_BANK_BUILDING_PORN_2), + PS2BANK(SFX_BANK_BUILDING_PORN_3), + PS2BANK(SFX_BANK_BUILDING_POLICE_BALL), + PS2BANK(SFX_BANK_BUILDING_BANK_ALARM), + PS2BANK(SFX_BANK_BUILDING_RAVE_INDUSTRIAL), + PS2BANK(SFX_BANK_BUILDING_RAVE_COMMERCIAL), + PS2BANK(SFX_BANK_BUILDING_RAVE_SUBURBAN), + PS2BANK(SFX_BANK_BUILDING_RAVE_COMMERCIAL_2), + + PS2BANK(SFX_BANK_BUILDING_39), + PS2BANK(SFX_BANK_BUILDING_40), + PS2BANK(SFX_BANK_BUILDING_41), + PS2BANK(SFX_BANK_BUILDING_42), + PS2BANK(SFX_BANK_BUILDING_43), + PS2BANK(SFX_BANK_BUILDING_44), + PS2BANK(SFX_BANK_BUILDING_45), + PS2BANK(SFX_BANK_BUILDING_46), + PS2BANK(SFX_BANK_BUILDING_47), + + PS2BANK(SFX_BANK_GENERIC_EXTRA), + + SFX_BANK_PED_COMMENTS, + MAX_SFX_BANKS, + INVALID_SFX_BANK +}; + +#define MAX_PEDSFX 7 +#define PED_BLOCKSIZE 79000 + +#define MAXPROVIDERS 64 + +#ifdef EXTERNAL_3D_SOUND +#define MAXCHANNELS (NUM_CHANNELS_GENERIC+1) +#define MAXCHANNELS_SURROUND (MAXCHANNELS-4) +#define MAX2DCHANNELS 1 +#else +#define MAXCHANNELS 0 +#define MAXCHANNELS_SURROUND 0 +#define MAX2DCHANNELS NUM_CHANNELS +#endif + +#define MAX_STREAMS 2 + +#define DIGITALRATE 32000 +#define DIGITALBITS 16 +#define DIGITALCHANNELS 2 + +#ifdef FIX_BUGS +#define MAX_DIGITAL_MIXER_CHANNELS (MAXCHANNELS+MAX_STREAMS*2+MAX2DCHANNELS) +#else +#define MAX_DIGITAL_MIXER_CHANNELS (MAXCHANNELS+MAX_STREAMS*2) +#endif + +static_assert( NUM_CHANNELS == MAXCHANNELS + MAX2DCHANNELS, "The number of channels doesn't match with an enum" ); + +class cSampleManager +{ + uint8 m_nEffectsVolume; + uint8 m_nMusicVolume; + uint8 m_nEffectsFadeVolume; + uint8 m_nMusicFadeVolume; + bool8 m_nMonoMode; + char unk; + char m_szCDRomRootPath[80]; + bool8 m_bInitialised; + uint8 m_nNumberOfProviders; + char *m_aAudioProviders[MAXPROVIDERS]; + tSample m_aSamples[TOTAL_AUDIO_SAMPLES]; + +public: + + + + cSampleManager(void); + ~cSampleManager(void); + +#ifdef EXTERNAL_3D_SOUND + void SetSpeakerConfig(int32 nConfig); + uint32 GetMaximumSupportedChannels(void); + + uint32 GetNum3DProvidersAvailable(void); + void SetNum3DProvidersAvailable(uint32 num); + + char *Get3DProviderName(uint8 id); + void Set3DProviderName(uint8 id, char *name); + + int8 GetCurrent3DProviderIndex(void); + int8 SetCurrent3DProvider(uint8 which); +#endif + + bool8 IsMP3RadioChannelAvailable(void); + + void ReleaseDigitalHandle (void); + void ReacquireDigitalHandle(void); + + bool8 Initialise(void); + void Terminate (void); + + bool8 CheckForAnAudioFileOnCD(void); + char GetCDAudioDriveLetter (void); + + void UpdateEffectsVolume(void); + + void SetEffectsMasterVolume(uint8 nVolume); + void SetMusicMasterVolume (uint8 nVolume); + void SetEffectsFadeVolume (uint8 nVolume); + void SetMusicFadeVolume (uint8 nVolume); + void SetMonoMode (bool8 nMode); + + bool8 LoadSampleBank (uint8 nBank); + void UnloadSampleBank (uint8 nBank); + int8 IsSampleBankLoaded(uint8 nBank); + + uint8 IsPedCommentLoaded(uint32 nComment); + bool8 LoadPedComment (uint32 nComment); + int32 GetBankContainingSound(uint32 offset); + + int32 _GetPedCommentSlot(uint32 nComment); + + uint32 GetSampleBaseFrequency (uint32 nSample); + uint32 GetSampleLoopStartOffset(uint32 nSample); + int32 GetSampleLoopEndOffset (uint32 nSample); + uint32 GetSampleLength (uint32 nSample); + + bool8 UpdateReverb(void); + + void SetChannelReverbFlag (uint32 nChannel, bool8 nReverbFlag); + bool8 InitialiseChannel (uint32 nChannel, uint32 nSfx, uint8 nBank); +#ifdef EXTERNAL_3D_SOUND + void SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume); + void SetChannel3DPosition (uint32 nChannel, float fX, float fY, float fZ); + void SetChannel3DDistances (uint32 nChannel, float fMax, float fMin); +#endif + void SetChannelVolume (uint32 nChannel, uint32 nVolume); + void SetChannelPan (uint32 nChannel, uint32 nPan); + void SetChannelFrequency (uint32 nChannel, uint32 nFreq); + void SetChannelLoopPoints (uint32 nChannel, uint32 nLoopStart, int32 nLoopEnd); + void SetChannelLoopCount (uint32 nChannel, uint32 nLoopCount); + bool8 GetChannelUsedFlag (uint32 nChannel); + void StartChannel (uint32 nChannel); + void StopChannel (uint32 nChannel); + + void PreloadStreamedFile (uint8 nFile, uint8 nStream = 0); + void PauseStream (bool8 nPauseFlag, uint8 nStream = 0); + void StartPreloadedStreamedFile (uint8 nStream = 0); + bool8 StartStreamedFile (uint8 nFile, uint32 nPos, uint8 nStream = 0); + void StopStreamedFile (uint8 nStream = 0); + int32 GetStreamedFilePosition (uint8 nStream = 0); + void SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, bool8 nEffectFlag, uint8 nStream = 0); + int32 GetStreamedFileLength (uint8 nStream = 0); + bool8 IsStreamPlaying (uint8 nStream = 0); +#ifdef AUDIO_OAL + void Service(void); +#endif + bool8 InitialiseSampleBanks(void); +}; + +extern cSampleManager SampleManager; +extern uint32 BankStartOffset[MAX_SFX_BANKS]; + +#ifdef AUDIO_OAL +extern int defaultProvider; +#endif + +#if defined(OPUS_AUDIO_PATHS) +static char StreamedNameTable[][25] = { + "AUDIO\\HEAD.OPUS", "AUDIO\\CLASS.OPUS", "AUDIO\\KJAH.OPUS", "AUDIO\\RISE.OPUS", "AUDIO\\LIPS.OPUS", "AUDIO\\GAME.OPUS", + "AUDIO\\MSX.OPUS", "AUDIO\\FLASH.OPUS", "AUDIO\\CHAT.OPUS", "AUDIO\\HEAD.OPUS", "AUDIO\\POLICE.OPUS", "AUDIO\\CITY.OPUS", + "AUDIO\\WATER.OPUS", "AUDIO\\COMOPEN.OPUS", "AUDIO\\SUBOPEN.OPUS", "AUDIO\\JB.OPUS", "AUDIO\\BET.OPUS", "AUDIO\\L1_LG.OPUS", + "AUDIO\\L2_DSB.OPUS", "AUDIO\\L3_DM.OPUS", "AUDIO\\L4_PAP.OPUS", "AUDIO\\L5_TFB.OPUS", "AUDIO\\J0_DM2.OPUS", "AUDIO\\J1_LFL.OPUS", + "AUDIO\\J2_KCL.OPUS", "AUDIO\\J3_VH.OPUS", "AUDIO\\J4_ETH.OPUS", "AUDIO\\J5_DST.OPUS", "AUDIO\\J6_TBJ.OPUS", "AUDIO\\T1_TOL.OPUS", + "AUDIO\\T2_TPU.OPUS", "AUDIO\\T3_MAS.OPUS", "AUDIO\\T4_TAT.OPUS", "AUDIO\\T5_BF.OPUS", "AUDIO\\S0_MAS.OPUS", "AUDIO\\S1_PF.OPUS", + "AUDIO\\S2_CTG.OPUS", "AUDIO\\S3_RTC.OPUS", "AUDIO\\S5_LRQ.OPUS", "AUDIO\\S4_BDBA.OPUS", "AUDIO\\S4_BDBB.OPUS", "AUDIO\\S2_CTG2.OPUS", + "AUDIO\\S4_BDBD.OPUS", "AUDIO\\S5_LRQB.OPUS", "AUDIO\\S5_LRQC.OPUS", "AUDIO\\A1_SSO.OPUS", "AUDIO\\A2_PP.OPUS", "AUDIO\\A3_SS.OPUS", + "AUDIO\\A4_PDR.OPUS", "AUDIO\\A5_K2FT.OPUS", "AUDIO\\K1_KBO.OPUS", "AUDIO\\K2_GIS.OPUS", "AUDIO\\K3_DS.OPUS", "AUDIO\\K4_SHI.OPUS", + "AUDIO\\K5_SD.OPUS", "AUDIO\\R0_PDR2.OPUS", "AUDIO\\R1_SW.OPUS", "AUDIO\\R2_AP.OPUS", "AUDIO\\R3_ED.OPUS", "AUDIO\\R4_GF.OPUS", + "AUDIO\\R5_PB.OPUS", "AUDIO\\R6_MM.OPUS", "AUDIO\\D1_STOG.OPUS", "AUDIO\\D2_KK.OPUS", "AUDIO\\D3_ADO.OPUS", "AUDIO\\D5_ES.OPUS", + "AUDIO\\D7_MLD.OPUS", "AUDIO\\D4_GTA.OPUS", "AUDIO\\D4_GTA2.OPUS", "AUDIO\\D6_STS.OPUS", "AUDIO\\A6_BAIT.OPUS", "AUDIO\\A7_ETG.OPUS", + "AUDIO\\A8_PS.OPUS", "AUDIO\\A9_ASD.OPUS", "AUDIO\\K4_SHI2.OPUS", "AUDIO\\C1_TEX.OPUS", "AUDIO\\EL_PH1.OPUS", "AUDIO\\EL_PH2.OPUS", + "AUDIO\\EL_PH3.OPUS", "AUDIO\\EL_PH4.OPUS", "AUDIO\\YD_PH1.OPUS", "AUDIO\\YD_PH2.OPUS", "AUDIO\\YD_PH3.OPUS", "AUDIO\\YD_PH4.OPUS", + "AUDIO\\HD_PH1.OPUS", "AUDIO\\HD_PH2.OPUS", "AUDIO\\HD_PH3.OPUS", "AUDIO\\HD_PH4.OPUS", "AUDIO\\HD_PH5.OPUS", "AUDIO\\MT_PH1.OPUS", + "AUDIO\\MT_PH2.OPUS", "AUDIO\\MT_PH3.OPUS", "AUDIO\\MT_PH4.OPUS", "AUDIO\\MISCOM.OPUS", "AUDIO\\END.OPUS", "AUDIO\\lib_a1.OPUS", + "AUDIO\\lib_a2.OPUS", "AUDIO\\lib_a.OPUS", "AUDIO\\lib_b.OPUS", "AUDIO\\lib_c.OPUS", "AUDIO\\lib_d.OPUS", "AUDIO\\l2_a.OPUS", + "AUDIO\\j4t_1.OPUS", "AUDIO\\j4t_2.OPUS", "AUDIO\\j4t_3.OPUS", "AUDIO\\j4t_4.OPUS", "AUDIO\\j4_a.OPUS", "AUDIO\\j4_b.OPUS", + "AUDIO\\j4_c.OPUS", "AUDIO\\j4_d.OPUS", "AUDIO\\j4_e.OPUS", "AUDIO\\j4_f.OPUS", "AUDIO\\j6_1.OPUS", "AUDIO\\j6_a.OPUS", + "AUDIO\\j6_b.OPUS", "AUDIO\\j6_c.OPUS", "AUDIO\\j6_d.OPUS", "AUDIO\\t4_a.OPUS", "AUDIO\\s1_a.OPUS", "AUDIO\\s1_a1.OPUS", + "AUDIO\\s1_b.OPUS", "AUDIO\\s1_c.OPUS", "AUDIO\\s1_c1.OPUS", "AUDIO\\s1_d.OPUS", "AUDIO\\s1_e.OPUS", "AUDIO\\s1_f.OPUS", + "AUDIO\\s1_g.OPUS", "AUDIO\\s1_h.OPUS", "AUDIO\\s1_i.OPUS", "AUDIO\\s1_j.OPUS", "AUDIO\\s1_k.OPUS", "AUDIO\\s1_l.OPUS", + "AUDIO\\s3_a.OPUS", "AUDIO\\s3_b.OPUS", "AUDIO\\el3_a.OPUS", "AUDIO\\mf1_a.OPUS", "AUDIO\\mf2_a.OPUS", "AUDIO\\mf3_a.OPUS", + "AUDIO\\mf3_b.OPUS", "AUDIO\\mf3_b1.OPUS", "AUDIO\\mf3_c.OPUS", "AUDIO\\mf4_a.OPUS", "AUDIO\\mf4_b.OPUS", "AUDIO\\mf4_c.OPUS", + "AUDIO\\a1_a.OPUS", "AUDIO\\a3_a.OPUS", "AUDIO\\a5_a.OPUS", "AUDIO\\a4_a.OPUS", "AUDIO\\a4_b.OPUS", "AUDIO\\a4_c.OPUS", + "AUDIO\\a4_d.OPUS", "AUDIO\\k1_a.OPUS", "AUDIO\\k3_a.OPUS", "AUDIO\\r1_a.OPUS", "AUDIO\\r2_a.OPUS", "AUDIO\\r2_b.OPUS", + "AUDIO\\r2_c.OPUS", "AUDIO\\r2_d.OPUS", "AUDIO\\r2_e.OPUS", "AUDIO\\r2_f.OPUS", "AUDIO\\r2_g.OPUS", "AUDIO\\r2_h.OPUS", + "AUDIO\\r5_a.OPUS", "AUDIO\\r6_a.OPUS", "AUDIO\\r6_a1.OPUS", "AUDIO\\r6_b.OPUS", "AUDIO\\lo2_a.OPUS", "AUDIO\\lo6_a.OPUS", + "AUDIO\\yd2_a.OPUS", "AUDIO\\yd2_b.OPUS", "AUDIO\\yd2_c.OPUS", "AUDIO\\yd2_c1.OPUS", "AUDIO\\yd2_d.OPUS", "AUDIO\\yd2_e.OPUS", + "AUDIO\\yd2_f.OPUS", "AUDIO\\yd2_g.OPUS", "AUDIO\\yd2_h.OPUS", "AUDIO\\yd2_ass.OPUS", "AUDIO\\yd2_ok.OPUS", "AUDIO\\h5_a.OPUS", + "AUDIO\\h5_b.OPUS", "AUDIO\\h5_c.OPUS", "AUDIO\\ammu_a.OPUS", "AUDIO\\ammu_b.OPUS", "AUDIO\\ammu_c.OPUS", "AUDIO\\door_1.OPUS", + "AUDIO\\door_2.OPUS", "AUDIO\\door_3.OPUS", "AUDIO\\door_4.OPUS", "AUDIO\\door_5.OPUS", "AUDIO\\door_6.OPUS", "AUDIO\\t3_a.OPUS", + "AUDIO\\t3_b.OPUS", "AUDIO\\t3_c.OPUS", "AUDIO\\k1_b.OPUS", "AUDIO\\cat1.OPUS"}; +#else +#ifdef PS2_AUDIO_PATHS +static char PS2StreamedNameTable[][25]= +{ + "AUDIO\\MUSIC\\HEAD.VB", + "AUDIO\\MUSIC\\CLASS.VB", + "AUDIO\\MUSIC\\KJAH.VB", + "AUDIO\\MUSIC\\RISE.VB", + "AUDIO\\MUSIC\\LIPS.VB", + "AUDIO\\MUSIC\\GAME.VB", + "AUDIO\\MUSIC\\MSX.VB", + "AUDIO\\MUSIC\\FLASH.VB", + "AUDIO\\MUSIC\\CHAT.VB", + "AUDIO\\MUSIC\\HEAD.VB", + "AUDIO\\MUSIC\\POLICE.VB", + "AUDIO\\MUSIC\\CITY.VB", + "AUDIO\\MUSIC\\WATER.VB", + "AUDIO\\MUSIC\\COMOPEN.VB", + "AUDIO\\MUSIC\\SUBOPEN.VB", + "AUDIO\\OTHER\\JB.VB", + "AUDIO\\OTHER\\BET.VB", + "AUDIO\\LUIGI\\L1_LG.VB", + "AUDIO\\LUIGI\\L2_DSB.VB", + "AUDIO\\LUIGI\\L3_DM.VB", + "AUDIO\\LUIGI\\L4_PAP.VB", + "AUDIO\\LUIGI\\L5_TFB.VB", + "AUDIO\\JOEY\\J0_DM2.VB", + "AUDIO\\JOEY\\J1_LFL.VB", + "AUDIO\\JOEY\\J2_KCL.VB", + "AUDIO\\JOEY\\J3_VH.VB", + "AUDIO\\JOEY\\J4_ETH.VB", + "AUDIO\\JOEY\\J5_DST.VB", + "AUDIO\\JOEY\\J6_TBJ.VB", + "AUDIO\\TONI\\T1_TOL.VB", + "AUDIO\\TONI\\T2_TPU.VB", + "AUDIO\\TONI\\T3_MAS.VB", + "AUDIO\\TONI\\T4_TAT.VB", + "AUDIO\\TONI\\T5_BF.VB", + "AUDIO\\SAL\\S0_MAS.VB", + "AUDIO\\SAL\\S1_PF.VB", + "AUDIO\\SAL\\S2_CTG.VB", + "AUDIO\\SAL\\S3_RTC.VB", + "AUDIO\\SAL\\S5_LRQ.VB", + "AUDIO\\EBALL\\S4_BDBA.VB", + "AUDIO\\EBALL\\S4_BDBB.VB", + "AUDIO\\SAL\\S2_CTG2.VB", + "AUDIO\\SAL\\S4_BDBD.VB", + "AUDIO\\SAL\\S5_LRQB.VB", + "AUDIO\\SAL\\S5_LRQC.VB", + "AUDIO\\ASUKA\\A1_SSO.VB", + "AUDIO\\ASUKA\\A2_PP.VB", + "AUDIO\\ASUKA\\A3_SS.VB", + "AUDIO\\ASUKA\\A4_PDR.VB", + "AUDIO\\ASUKA\\A5_K2FT.VB", + "AUDIO\\KENJI\\K1_KBO.VB", + "AUDIO\\KENJI\\K2_GIS.VB", + "AUDIO\\KENJI\\K3_DS.VB", + "AUDIO\\KENJI\\K4_SHI.VB", + "AUDIO\\KENJI\\K5_SD.VB", + "AUDIO\\RAY\\R0_PDR2.VB", + "AUDIO\\RAY\\R1_SW.VB", + "AUDIO\\RAY\\R2_AP.VB", + "AUDIO\\RAY\\R3_ED.VB", + "AUDIO\\RAY\\R4_GF.VB", + "AUDIO\\RAY\\R5_PB.VB", + "AUDIO\\RAY\\R6_MM.VB", + "AUDIO\\LOVE\\D1_STOG.VB", + "AUDIO\\LOVE\\D2_KK.VB", + "AUDIO\\LOVE\\D3_ADO.VB", + "AUDIO\\LOVE\\D5_ES.VB", + "AUDIO\\LOVE\\D7_MLD.VB", + "AUDIO\\LOVE\\D4_GTA.VB", + "AUDIO\\LOVE\\D4_GTA2.VB", + "AUDIO\\LOVE\\D6_STS.VB", + "AUDIO\\ASUKA\\A6_BAIT.VB", + "AUDIO\\ASUKA\\A7_ETG.VB", + "AUDIO\\ASUKA\\A8_PS.VB", + "AUDIO\\ASUKA\\A9_ASD.VB", + "AUDIO\\SHOP\\K4_SHI2.VB", + "AUDIO\\OTHER\\C1_TEX.VB", + "AUDIO\\PHONE\\EL_PH1.VB", + "AUDIO\\PHONE\\EL_PH2.VB", + "AUDIO\\PHONE\\EL_PH3.VB", + "AUDIO\\PHONE\\EL_PH4.VB", + "AUDIO\\PHONE\\YD_PH1.VB", + "AUDIO\\PHONE\\YD_PH2.VB", + "AUDIO\\PHONE\\YD_PH3.VB", + "AUDIO\\PHONE\\YD_PH4.VB", + "AUDIO\\PHONE\\HD_PH1.VB", + "AUDIO\\PHONE\\HD_PH2.VB", + "AUDIO\\PHONE\\HD_PH3.VB", + "AUDIO\\PHONE\\HD_PH4.VB", + "AUDIO\\PHONE\\HD_PH5.VB", + "AUDIO\\PHONE\\MT_PH1.VB", + "AUDIO\\PHONE\\MT_PH2.VB", + "AUDIO\\PHONE\\MT_PH3.VB", + "AUDIO\\PHONE\\MT_PH4.VB", + "AUDIO\\MUSIC\\MISCOM.VB", + "AUDIO\\MUSIC\\END.VB", + "AUDIO\\lib_a1.WAV", + "AUDIO\\lib_a2.WAV", + "AUDIO\\lib_a.WAV", + "AUDIO\\lib_b.WAV", + "AUDIO\\lib_c.WAV", + "AUDIO\\lib_d.WAV", + "AUDIO\\l2_a.WAV", + "AUDIO\\j4t_1.WAV", + "AUDIO\\j4t_2.WAV", + "AUDIO\\j4t_3.WAV", + "AUDIO\\j4t_4.WAV", + "AUDIO\\j4_a.WAV", + "AUDIO\\j4_b.WAV", + "AUDIO\\j4_c.WAV", + "AUDIO\\j4_d.WAV", + "AUDIO\\j4_e.WAV", + "AUDIO\\j4_f.WAV", + "AUDIO\\j6_1.WAV", + "AUDIO\\j6_a.WAV", + "AUDIO\\j6_b.WAV", + "AUDIO\\j6_c.WAV", + "AUDIO\\j6_d.WAV", + "AUDIO\\t4_a.WAV", + "AUDIO\\s1_a.WAV", + "AUDIO\\s1_a1.WAV", + "AUDIO\\s1_b.WAV", + "AUDIO\\s1_c.WAV", + "AUDIO\\s1_c1.WAV", + "AUDIO\\s1_d.WAV", + "AUDIO\\s1_e.WAV", + "AUDIO\\s1_f.WAV", + "AUDIO\\s1_g.WAV", + "AUDIO\\s1_h.WAV", + "AUDIO\\s1_i.WAV", + "AUDIO\\s1_j.WAV", + "AUDIO\\s1_k.WAV", + "AUDIO\\s1_l.WAV", + "AUDIO\\s3_a.WAV", + "AUDIO\\s3_b.WAV", + "AUDIO\\el3_a.WAV", + "AUDIO\\mf1_a.WAV", + "AUDIO\\mf2_a.WAV", + "AUDIO\\mf3_a.WAV", + "AUDIO\\mf3_b.WAV", + "AUDIO\\mf3_b1.WAV", + "AUDIO\\mf3_c.WAV", + "AUDIO\\mf4_a.WAV", + "AUDIO\\mf4_b.WAV", + "AUDIO\\mf4_c.WAV", + "AUDIO\\a1_a.WAV", + "AUDIO\\a3_a.WAV", + "AUDIO\\a5_a.WAV", + "AUDIO\\a4_a.WAV", + "AUDIO\\a4_b.WAV", + "AUDIO\\a4_c.WAV", + "AUDIO\\a4_d.WAV", + "AUDIO\\k1_a.WAV", + "AUDIO\\k3_a.WAV", + "AUDIO\\r1_a.WAV", + "AUDIO\\r2_a.WAV", + "AUDIO\\r2_b.WAV", + "AUDIO\\r2_c.WAV", + "AUDIO\\r2_d.WAV", + "AUDIO\\r2_e.WAV", + "AUDIO\\r2_f.WAV", + "AUDIO\\r2_g.WAV", + "AUDIO\\r2_h.WAV", + "AUDIO\\r5_a.WAV", + "AUDIO\\r6_a.WAV", + "AUDIO\\r6_a1.WAV", + "AUDIO\\r6_b.WAV", + "AUDIO\\lo2_a.WAV", + "AUDIO\\lo6_a.WAV", + "AUDIO\\yd2_a.WAV", + "AUDIO\\yd2_b.WAV", + "AUDIO\\yd2_c.WAV", + "AUDIO\\yd2_c1.WAV", + "AUDIO\\yd2_d.WAV", + "AUDIO\\yd2_e.WAV", + "AUDIO\\yd2_f.WAV", + "AUDIO\\yd2_g.WAV", + "AUDIO\\yd2_h.WAV", + "AUDIO\\yd2_ass.WAV", + "AUDIO\\yd2_ok.WAV", + "AUDIO\\h5_a.WAV", + "AUDIO\\h5_b.WAV", + "AUDIO\\h5_c.WAV", + "AUDIO\\ammu_a.WAV", + "AUDIO\\ammu_b.WAV", + "AUDIO\\ammu_c.WAV", + "AUDIO\\door_1.WAV", + "AUDIO\\door_2.WAV", + "AUDIO\\door_3.WAV", + "AUDIO\\door_4.WAV", + "AUDIO\\door_5.WAV", + "AUDIO\\door_6.WAV", + "AUDIO\\t3_a.WAV", + "AUDIO\\t3_b.WAV", + "AUDIO\\t3_c.WAV", + "AUDIO\\k1_b.WAV", + "AUDIO\\cat1.WAV" +}; +#endif + +static char StreamedNameTable[][25] = +{ + "AUDIO\\HEAD.WAV", + "AUDIO\\CLASS.WAV", + "AUDIO\\KJAH.WAV", + "AUDIO\\RISE.WAV", + "AUDIO\\LIPS.WAV", + "AUDIO\\GAME.WAV", + "AUDIO\\MSX.WAV", + "AUDIO\\FLASH.WAV", + "AUDIO\\CHAT.WAV", + "AUDIO\\HEAD.WAV", + "AUDIO\\POLICE.WAV", + "AUDIO\\CITY.WAV", + "AUDIO\\WATER.WAV", + "AUDIO\\COMOPEN.WAV", + "AUDIO\\SUBOPEN.WAV", + "AUDIO\\JB.MP3", + "AUDIO\\BET.MP3", + "AUDIO\\L1_LG.MP3", + "AUDIO\\L2_DSB.MP3", + "AUDIO\\L3_DM.MP3", + "AUDIO\\L4_PAP.MP3", + "AUDIO\\L5_TFB.MP3", + "AUDIO\\J0_DM2.MP3", + "AUDIO\\J1_LFL.MP3", + "AUDIO\\J2_KCL.MP3", + "AUDIO\\J3_VH.MP3", + "AUDIO\\J4_ETH.MP3", + "AUDIO\\J5_DST.MP3", + "AUDIO\\J6_TBJ.MP3", + "AUDIO\\T1_TOL.MP3", + "AUDIO\\T2_TPU.MP3", + "AUDIO\\T3_MAS.MP3", + "AUDIO\\T4_TAT.MP3", + "AUDIO\\T5_BF.MP3", + "AUDIO\\S0_MAS.MP3", + "AUDIO\\S1_PF.MP3", + "AUDIO\\S2_CTG.MP3", + "AUDIO\\S3_RTC.MP3", + "AUDIO\\S5_LRQ.MP3", + "AUDIO\\S4_BDBA.MP3", + "AUDIO\\S4_BDBB.MP3", + "AUDIO\\S2_CTG2.MP3", + "AUDIO\\S4_BDBD.MP3", + "AUDIO\\S5_LRQB.MP3", + "AUDIO\\S5_LRQC.MP3", + "AUDIO\\A1_SSO.WAV", + "AUDIO\\A2_PP.WAV", + "AUDIO\\A3_SS.WAV", + "AUDIO\\A4_PDR.WAV", + "AUDIO\\A5_K2FT.WAV", + "AUDIO\\K1_KBO.MP3", + "AUDIO\\K2_GIS.MP3", + "AUDIO\\K3_DS.MP3", + "AUDIO\\K4_SHI.MP3", + "AUDIO\\K5_SD.MP3", + "AUDIO\\R0_PDR2.MP3", + "AUDIO\\R1_SW.MP3", + "AUDIO\\R2_AP.MP3", + "AUDIO\\R3_ED.MP3", + "AUDIO\\R4_GF.MP3", + "AUDIO\\R5_PB.MP3", + "AUDIO\\R6_MM.MP3", + "AUDIO\\D1_STOG.MP3", + "AUDIO\\D2_KK.MP3", + "AUDIO\\D3_ADO.MP3", + "AUDIO\\D5_ES.MP3", + "AUDIO\\D7_MLD.MP3", + "AUDIO\\D4_GTA.MP3", + "AUDIO\\D4_GTA2.MP3", + "AUDIO\\D6_STS.MP3", + "AUDIO\\A6_BAIT.WAV", + "AUDIO\\A7_ETG.WAV", + "AUDIO\\A8_PS.WAV", + "AUDIO\\A9_ASD.WAV", + "AUDIO\\K4_SHI2.MP3", + "AUDIO\\C1_TEX.MP3", + "AUDIO\\EL_PH1.MP3", + "AUDIO\\EL_PH2.MP3", + "AUDIO\\EL_PH3.MP3", + "AUDIO\\EL_PH4.MP3", + "AUDIO\\YD_PH1.MP3", + "AUDIO\\YD_PH2.MP3", + "AUDIO\\YD_PH3.MP3", + "AUDIO\\YD_PH4.MP3", + "AUDIO\\HD_PH1.MP3", + "AUDIO\\HD_PH2.MP3", + "AUDIO\\HD_PH3.MP3", + "AUDIO\\HD_PH4.MP3", + "AUDIO\\HD_PH5.MP3", + "AUDIO\\MT_PH1.MP3", + "AUDIO\\MT_PH2.MP3", + "AUDIO\\MT_PH3.MP3", + "AUDIO\\MT_PH4.MP3", + "AUDIO\\MISCOM.WAV", + "AUDIO\\END.MP3", + "AUDIO\\lib_a1.WAV", + "AUDIO\\lib_a2.WAV", + "AUDIO\\lib_a.WAV", + "AUDIO\\lib_b.WAV", + "AUDIO\\lib_c.WAV", + "AUDIO\\lib_d.WAV", + "AUDIO\\l2_a.WAV", + "AUDIO\\j4t_1.WAV", + "AUDIO\\j4t_2.WAV", + "AUDIO\\j4t_3.WAV", + "AUDIO\\j4t_4.WAV", + "AUDIO\\j4_a.WAV", + "AUDIO\\j4_b.WAV", + "AUDIO\\j4_c.WAV", + "AUDIO\\j4_d.WAV", + "AUDIO\\j4_e.WAV", + "AUDIO\\j4_f.WAV", + "AUDIO\\j6_1.WAV", + "AUDIO\\j6_a.WAV", + "AUDIO\\j6_b.WAV", + "AUDIO\\j6_c.WAV", + "AUDIO\\j6_d.WAV", + "AUDIO\\t4_a.WAV", + "AUDIO\\s1_a.WAV", + "AUDIO\\s1_a1.WAV", + "AUDIO\\s1_b.WAV", + "AUDIO\\s1_c.WAV", + "AUDIO\\s1_c1.WAV", + "AUDIO\\s1_d.WAV", + "AUDIO\\s1_e.WAV", + "AUDIO\\s1_f.WAV", + "AUDIO\\s1_g.WAV", + "AUDIO\\s1_h.WAV", + "AUDIO\\s1_i.WAV", + "AUDIO\\s1_j.WAV", + "AUDIO\\s1_k.WAV", + "AUDIO\\s1_l.WAV", + "AUDIO\\s3_a.WAV", + "AUDIO\\s3_b.WAV", + "AUDIO\\el3_a.WAV", + "AUDIO\\mf1_a.WAV", + "AUDIO\\mf2_a.WAV", + "AUDIO\\mf3_a.WAV", + "AUDIO\\mf3_b.WAV", + "AUDIO\\mf3_b1.WAV", + "AUDIO\\mf3_c.WAV", + "AUDIO\\mf4_a.WAV", + "AUDIO\\mf4_b.WAV", + "AUDIO\\mf4_c.WAV", + "AUDIO\\a1_a.WAV", + "AUDIO\\a3_a.WAV", + "AUDIO\\a5_a.WAV", + "AUDIO\\a4_a.WAV", + "AUDIO\\a4_b.WAV", + "AUDIO\\a4_c.WAV", + "AUDIO\\a4_d.WAV", + "AUDIO\\k1_a.WAV", + "AUDIO\\k3_a.WAV", + "AUDIO\\r1_a.WAV", + "AUDIO\\r2_a.WAV", + "AUDIO\\r2_b.WAV", + "AUDIO\\r2_c.WAV", + "AUDIO\\r2_d.WAV", + "AUDIO\\r2_e.WAV", + "AUDIO\\r2_f.WAV", + "AUDIO\\r2_g.WAV", + "AUDIO\\r2_h.WAV", + "AUDIO\\r5_a.WAV", + "AUDIO\\r6_a.WAV", + "AUDIO\\r6_a1.WAV", + "AUDIO\\r6_b.WAV", + "AUDIO\\lo2_a.WAV", + "AUDIO\\lo6_a.WAV", + "AUDIO\\yd2_a.WAV", + "AUDIO\\yd2_b.WAV", + "AUDIO\\yd2_c.WAV", + "AUDIO\\yd2_c1.WAV", + "AUDIO\\yd2_d.WAV", + "AUDIO\\yd2_e.WAV", + "AUDIO\\yd2_f.WAV", + "AUDIO\\yd2_g.WAV", + "AUDIO\\yd2_h.WAV", + "AUDIO\\yd2_ass.WAV", + "AUDIO\\yd2_ok.WAV", + "AUDIO\\h5_a.WAV", + "AUDIO\\h5_b.WAV", + "AUDIO\\h5_c.WAV", + "AUDIO\\ammu_a.WAV", + "AUDIO\\ammu_b.WAV", + "AUDIO\\ammu_c.WAV", + "AUDIO\\door_1.WAV", + "AUDIO\\door_2.WAV", + "AUDIO\\door_3.WAV", + "AUDIO\\door_4.WAV", + "AUDIO\\door_5.WAV", + "AUDIO\\door_6.WAV", + "AUDIO\\t3_a.WAV", + "AUDIO\\t3_b.WAV", + "AUDIO\\t3_c.WAV", + "AUDIO\\k1_b.WAV", + "AUDIO\\cat1.WAV" +}; +#endif \ No newline at end of file diff --git a/src/audio/sampman_miles.cpp b/src/audio/sampman_miles.cpp new file mode 100644 index 0000000..a4309b6 --- /dev/null +++ b/src/audio/sampman_miles.cpp @@ -0,0 +1,2490 @@ +#define WITHWINDOWS +#include "common.h" + +#ifdef AUDIO_MSS +#include +#include + +#include + +#include "eax.h" +#include "eax-util.h" +#include "mss.h" + +#include "sampman.h" +#include "AudioManager.h" +#include "MusicManager.h" +#include "Frontend.h" +#include "Timer.h" + + +#pragma comment( lib, "mss32.lib" ) + +cSampleManager SampleManager; +uint32 BankStartOffset[MAX_SFX_BANKS]; +/////////////////////////////////////////////////////////////// + +char SampleBankDescFilename[] = "AUDIO\\SFX.SDT"; +char SampleBankDataFilename[] = "AUDIO\\SFX.RAW"; + +FILE *fpSampleDescHandle; +FILE *fpSampleDataHandle; +int8 gBankLoaded [MAX_SFX_BANKS]; +int32 nSampleBankDiscStartOffset [MAX_SFX_BANKS]; +int32 nSampleBankSize [MAX_SFX_BANKS]; +int32 nSampleBankMemoryStartAddress[MAX_SFX_BANKS]; +int32 _nSampleDataEndOffset; + +int32 nPedSlotSfx [MAX_PEDSFX]; +int32 nPedSlotSfxAddr[MAX_PEDSFX]; +uint8 nCurrentPedSlot; + +uint8 nChannelVolume[MAXCHANNELS+MAX2DCHANNELS]; + +uint32 nStreamLength[TOTAL_STREAMED_SOUNDS]; + +/////////////////////////////////////////////////////////////// +struct tMP3Entry +{ + char aFilename[MAX_PATH]; + + uint32 nTrackLength; + uint32 nTrackStreamPos; + + tMP3Entry *pNext; + char *pLinkPath; +}; + +uint32 nNumMP3s; +tMP3Entry *_pMP3List; +char _mp3DirectoryPath[MAX_PATH]; +HSTREAM mp3Stream [MAX_STREAMS]; +int8 nStreamPan [MAX_STREAMS]; +int8 nStreamVolume[MAX_STREAMS]; +uint32 _CurMP3Index; +int32 _CurMP3Pos; +bool8 _bIsMp3Active; + +#if GTA_VERSION >= GTA3_PC_11 || defined(NO_CDCHECK) +bool8 _bUseHDDAudio; +char _aHDDPath[MAX_PATH]; +#endif +/////////////////////////////////////////////////////////////// + + +bool8 _bSampmanInitialised = FALSE; +#ifdef EXTERNAL_3D_SOUND +// +// Miscellaneous globals / defines + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS + +EAXLISTENERPROPERTIES StartEAX3 = + {26, 1.7f, 0.8f, -1000, -1000, -100, 4.42f, 0.14f, 1.00f, 429, 0.014f, 0.00f,0.00f,0.00f, 1023, 0.021f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2727.1f, 250.0f, 0.00f, 0x3f }; + +EAXLISTENERPROPERTIES FinishEAX3 = + {26, 100.0f, 1.0f, 0, -1000, -2200, 20.0f, 1.39f, 1.00f, 1000, 0.069f, 0.00f,0.00f,0.00f, 400, 0.100f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 3.982f, 0.000f, -18.0f, 3530.8f, 417.9f, 6.70f, 0x3f }; + +EAXLISTENERPROPERTIES EAX3Params; + +S32 prevprovider=-1; +S32 curprovider=-1; +S32 usingEAX=0; +S32 usingEAX3=0; +HPROVIDER opened_provider=0; +H3DSAMPLE opened_samples[MAXCHANNELS] = {0}; +#endif +HSAMPLE opened_2dsamples[MAX2DCHANNELS] = {0}; +HDIGDRIVER DIG; +#ifdef EXTERNAL_3D_SOUND +S32 speaker_type=0; +U32 _maxSamples; +float _fPrevEaxRatioDestination; +bool8 _usingMilesFast2D; +float _fEffectsLevel; + + +struct +{ + HPROVIDER id; + char name[80]; +}providers[MAXPROVIDERS]; + +typedef struct provider_stuff +{ + char* name; + HPROVIDER id; +} provider_stuff; + + +static int __cdecl comp(const provider_stuff*s1,const provider_stuff*s2) +{ + return( _stricmp(s1->name,s2->name) ); +} + +static void +add_providers() +{ + provider_stuff pi[MAXPROVIDERS]; + U32 n,i,j; + + SampleManager.SetNum3DProvidersAvailable(0); + + HPROENUM next = HPROENUM_FIRST; + + n=0; + while (AIL_enumerate_3D_providers(&next, &pi[n].id, &pi[n].name) && (n MAXCHANNELS ) + _maxSamples = MAXCHANNELS; + + SampleManager.SetSpeakerConfig(speaker_type); + + //obtain a 3D sample handles + for ( U32 i = 0; i < _maxSamples; ++i ) + { + opened_samples[i] = AIL_allocate_3D_sample_handle(opened_provider); + if ( opened_samples[i] != NULL ) + AIL_set_3D_sample_effects_level(opened_samples[i], 0.0f); + } + + return TRUE; + } + } + + return FALSE; +} +#endif + +cSampleManager::cSampleManager(void) : + m_nNumberOfProviders(0) +{ + ; +} + +cSampleManager::~cSampleManager(void) +{ + +} + +#ifdef EXTERNAL_3D_SOUND +void +cSampleManager::SetSpeakerConfig(int32 which) +{ + switch ( which ) + { + case 1: + speaker_type=AIL_3D_2_SPEAKER; + break; + + case 2: + speaker_type=AIL_3D_HEADPHONE; + break; + + case 3: + speaker_type=AIL_3D_4_SPEAKER; + break; + + default: + return; + break; + } + + if (opened_provider) + AIL_set_3D_speaker_type(opened_provider, speaker_type); +} + +uint32 +cSampleManager::GetMaximumSupportedChannels(void) +{ + if ( _maxSamples > MAXCHANNELS ) + return MAXCHANNELS; + + return _maxSamples; +} + +uint32 cSampleManager::GetNum3DProvidersAvailable() +{ + return m_nNumberOfProviders; +} + +void cSampleManager::SetNum3DProvidersAvailable(uint32 num) +{ + m_nNumberOfProviders = num; +} + +char *cSampleManager::Get3DProviderName(uint8 id) +{ + return m_aAudioProviders[id]; +} + +void cSampleManager::Set3DProviderName(uint8 id, char *name) +{ + m_aAudioProviders[id] = name; +} + +int8 +cSampleManager::GetCurrent3DProviderIndex(void) +{ + return curprovider; +} + +int8 +cSampleManager::SetCurrent3DProvider(uint8 nProvider) +{ + S32 savedprovider = curprovider; + + if ( nProvider < m_nNumberOfProviders ) + { + if ( set_new_provider(nProvider) ) + return curprovider; + else if ( savedprovider != -1 && savedprovider < m_nNumberOfProviders && set_new_provider(savedprovider) ) + return curprovider; + else + return -1; + } + else + return curprovider; +} +#endif + +static bool8 +_ResolveLink(char const *path, char *out) +{ + IShellLink* psl; + WIN32_FIND_DATA fd; + char filepath[MAX_PATH]; + + CoInitialize(NULL); + + if (SUCCEEDED( CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl ) )) + { + IPersistFile *ppf; + + if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf))) + { + WCHAR wpath[MAX_PATH]; + + MultiByteToWideChar(CP_ACP, 0, path, -1, wpath, MAX_PATH); + + if (SUCCEEDED(ppf->Load(wpath, STGM_READ))) + { + /* Resolve the link */ + if (SUCCEEDED(psl->Resolve(NULL, SLR_ANY_MATCH|SLR_NO_UI|SLR_NOSEARCH))) + { + strcpy(filepath, path); + + if (SUCCEEDED(psl->GetPath(filepath, MAX_PATH, &fd, SLGP_UNCPRIORITY))) + { + OutputDebugString(fd.cFileName); + + strcpy(out, filepath); + // FIX: Release the objects. Taken from SA. +#ifdef FIX_BUGS + ppf->Release(); + psl->Release(); +#endif + return TRUE; + } + } + } + + ppf->Release(); + } + psl->Release(); + } + + return FALSE; +} + +static void +_FindMP3s(void) +{ + tMP3Entry *pList; + bool8 bShortcut; + bool8 bInitFirstEntry; + HANDLE hFind; + char path[MAX_PATH]; + char filepath[MAX_PATH*2]; + S32 total_ms; + WIN32_FIND_DATA fd; + + + if ( GetCurrentDirectory(MAX_PATH, _mp3DirectoryPath) == 0 ) + { + GetLastError(); + return; + } + + OutputDebugString("Finding MP3s..."); + strcpy(path, _mp3DirectoryPath); + strcat(path, "\\MP3\\"); + + strcpy(_mp3DirectoryPath, path); + OutputDebugString(_mp3DirectoryPath); + + strcat(path, "*"); + + hFind = FindFirstFile(path, &fd); + + if ( hFind == INVALID_HANDLE_VALUE ) + { + GetLastError(); + return; + } + + strcpy(filepath, _mp3DirectoryPath); + strcat(filepath, fd.cFileName); + + int32 filepathlen = strlen(filepath); + + if ( filepathlen <= 0) + { + FindClose(hFind); + return; + } + + FILE *f = fopen("MP3\\MP3Report.txt", "w"); + + if ( f ) + { + fprintf(f, "MP3 Report File\n\n"); + fprintf(f, "\"%s\"", fd.cFileName); + } + + + if ( filepathlen > 4 ) + { + if ( !strcmp(&filepath[filepathlen - 4], ".lnk") ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - shortcut to \"%s\"", filepath); + } + else + { + if ( f ) fprintf(f, " - couldn't resolve shortcut"); + } + + bShortcut = TRUE; + } + else + bShortcut = FALSE; + } + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + if ( mp3Stream[0] ) + { + AIL_stream_ms_position(mp3Stream[0], &total_ms, NULL); + + AIL_close_stream(mp3Stream[0]); + mp3Stream[0] = NULL; + + OutputDebugString(fd.cFileName); + + _pMP3List = new tMP3Entry; + + if ( _pMP3List == NULL ) + { + FindClose(hFind); + + if ( f ) + fclose(f); + + return; + } + + nNumMP3s = 1; + + strcpy(_pMP3List->aFilename, fd.cFileName); + + _pMP3List->nTrackLength = total_ms; + + _pMP3List->pNext = NULL; + + pList = _pMP3List; + + if ( bShortcut ) + { + _pMP3List->pLinkPath = new char[MAX_PATH*2]; + strcpy(_pMP3List->pLinkPath, filepath); + } + else + { + _pMP3List->pLinkPath = NULL; + } + + if ( f ) fprintf(f, " - OK\n"); + + bInitFirstEntry = FALSE; + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - not an MP3 or supported MP3 type\n"); + + bInitFirstEntry = TRUE; + } + + while ( TRUE ) + { + if ( !FindNextFile(hFind, &fd) ) + break; + + if ( bInitFirstEntry ) + { + strcpy(filepath, _mp3DirectoryPath); + strcat(filepath, fd.cFileName); + + int32 filepathlen = strlen(filepath); + + if ( f ) fprintf(f, "\"%s\"", fd.cFileName); + + if ( filepathlen > 0 ) + { + if ( filepathlen > 4 ) + { + if ( !strcmp(&filepath[filepathlen - 4], ".lnk") ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - shortcut to \"%s\"", filepath); + } + else + { + if ( f ) fprintf(f, " - couldn't resolve shortcut"); + } + + bShortcut = TRUE; + } + else + { + bShortcut = FALSE; + + if ( filepathlen > MAX_PATH ) + { + if ( f ) fprintf(f, " - Filename and path too long - %s - IGNORED)\n", filepath); + + continue; + } + } + } + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + if ( mp3Stream[0] ) + { + AIL_stream_ms_position(mp3Stream[0], &total_ms, NULL); + + AIL_close_stream(mp3Stream[0]); + mp3Stream[0] = NULL; + + OutputDebugString(fd.cFileName); + + _pMP3List = new tMP3Entry; + + if ( _pMP3List == NULL) + break; + + nNumMP3s = 1; + + strcpy(_pMP3List->aFilename, fd.cFileName); + + _pMP3List->nTrackLength = total_ms; + _pMP3List->pNext = NULL; + + if ( bShortcut ) + { + _pMP3List->pLinkPath = new char [MAX_PATH*2]; + strcpy(_pMP3List->pLinkPath, filepath); + } + else + { + _pMP3List->pLinkPath = NULL; + } + + pList = _pMP3List; + + if ( f ) fprintf(f, " - OK\n"); + + bInitFirstEntry = FALSE; + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - not an MP3 or supported MP3 type\n"); + } + } + } + else + { + strcpy(filepath, _mp3DirectoryPath); + strcat(filepath, fd.cFileName); + + int32 filepathlen = strlen(filepath); + + if ( filepathlen > 0 ) + { + if ( f ) fprintf(f, "\"%s\"", fd.cFileName); + + if ( filepathlen > 4 ) + { + if ( !strcmp(&filepath[filepathlen - 4], ".lnk") ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - shortcut to \"%s\"", filepath); + } + else + { + if ( f ) fprintf(f, " - couldn't resolve shortcut"); + } + + bShortcut = TRUE; + } + else + { + bShortcut = FALSE; + } + } + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + if ( mp3Stream[0] ) + { + AIL_stream_ms_position(mp3Stream[0], &total_ms, NULL); + + AIL_close_stream(mp3Stream[0]); + mp3Stream[0] = NULL; + + pList->pNext = new tMP3Entry; + + tMP3Entry *e = pList->pNext; + + if ( e == NULL ) + break; + + pList = pList->pNext; + + strcpy(e->aFilename, fd.cFileName); + e->nTrackLength = total_ms; + e->pNext = NULL; + + if ( bShortcut ) + { + e->pLinkPath = new char [MAX_PATH*2]; + strcpy(e->pLinkPath, filepath); + } + else + { + e->pLinkPath = NULL; + } + + nNumMP3s++; + + OutputDebugString(fd.cFileName); + + if ( f ) fprintf(f, " - OK\n"); + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - not an MP3 or supported MP3 type\n"); + } + } + } + } + + if ( f ) + { + fprintf(f, "\nTOTAL SUPPORTED MP3s: %d\n", nNumMP3s); + fclose(f); + } + + FindClose(hFind); +} + +static void +_DeleteMP3Entries(void) +{ + tMP3Entry *e = _pMP3List; + + while ( e != NULL ) + { + tMP3Entry *next = e->pNext; + + if ( next == NULL ) + next = NULL; + + if ( e->pLinkPath != NULL ) + { +#ifndef FIX_BUGS + delete e->pLinkPath; // BUG: should be delete [] +#else + delete[] e->pLinkPath; +#endif + e->pLinkPath = NULL; + } + + delete e; + + if ( next ) + e = next; + else + e = NULL; + + nNumMP3s--; + } + + + if ( nNumMP3s != 0 ) + { + OutputDebugString("Not all MP3 entries were deleted"); + nNumMP3s = 0; + } + + _pMP3List = NULL; +} + +static tMP3Entry * +_GetMP3EntryByIndex(uint32 idx) +{ + uint32 n = ( idx < nNumMP3s ) ? idx : 0; + + if ( _pMP3List != NULL ) + { + tMP3Entry *e = _pMP3List; + + for ( uint32 i = 0; i < n; i++ ) + e = e->pNext; + + return e; + + } + + return NULL; +} + +static inline bool8 +_GetMP3PosFromStreamPos(uint32 *pPosition, tMP3Entry **pEntry) +{ + _CurMP3Index = 0; + + for ( *pEntry = _pMP3List; *pEntry != NULL; *pEntry = (*pEntry)->pNext ) + { + if ( *pPosition >= (*pEntry)->nTrackStreamPos + && *pPosition < (*pEntry)->nTrackLength + (*pEntry)->nTrackStreamPos ) + { + *pPosition -= (*pEntry)->nTrackStreamPos; + _CurMP3Pos = *pPosition; + + return TRUE; + } + + _CurMP3Index++; + } + + *pPosition = 0; + *pEntry = _pMP3List; + _CurMP3Pos = 0; + _CurMP3Index = 0; + + return FALSE; +} + +bool8 +cSampleManager::IsMP3RadioChannelAvailable(void) +{ + return nNumMP3s != 0; +} + +void +cSampleManager::ReleaseDigitalHandle(void) +{ + if ( DIG ) + { +#ifdef EXTERNAL_3D_SOUND + prevprovider = curprovider; + release_existing(); + curprovider = -1; +#endif + AIL_digital_handle_release(DIG); + } +} + +void +cSampleManager::ReacquireDigitalHandle(void) +{ + if ( DIG ) + { + AIL_digital_handle_reacquire(DIG); +#ifdef EXTERNAL_3D_SOUND + if ( prevprovider != -1 ) + set_new_provider(prevprovider); +#endif + } +} + +bool8 +cSampleManager::Initialise(void) +{ + TRACE("start"); + + if ( _bSampmanInitialised ) + return TRUE; + + { + for ( int32 i = 0; i < TOTAL_AUDIO_SAMPLES; i++ ) + { + m_aSamples[i].nOffset = 0; + m_aSamples[i].nSize = 0; + m_aSamples[i].nFrequency = 22050; + m_aSamples[i].nLoopStart = 0; + m_aSamples[i].nLoopEnd = -1; + } + + m_nEffectsVolume = MAX_VOLUME; + m_nMusicVolume = MAX_VOLUME; + m_nEffectsFadeVolume = MAX_VOLUME; + m_nMusicFadeVolume = MAX_VOLUME; + + m_nMonoMode = 0; + } + +#ifdef EXTERNAL_3D_SOUND + // miles + TRACE("MILES"); + { + curprovider = -1; + prevprovider = -1; + + _usingMilesFast2D = FALSE; + usingEAX=0; + usingEAX3=0; + + _fEffectsLevel = 0.0f; + + _maxSamples = 0; + + opened_provider = NULL; + DIG = NULL; + + for ( int32 i = 0; i < MAXCHANNELS; i++ ) + opened_samples[i] = NULL; + } +#endif + + // banks + TRACE("banks"); + { + fpSampleDescHandle = NULL; + fpSampleDataHandle = NULL; + + _nSampleDataEndOffset = 0; + + for ( int32 i = 0; i < MAX_SFX_BANKS; i++ ) + { + gBankLoaded[i] = LOADING_STATUS_NOT_LOADED; + nSampleBankDiscStartOffset[i] = 0; + nSampleBankSize[i] = 0; + nSampleBankMemoryStartAddress[i] = 0; + } + } + + // pedsfx + TRACE("pedsfx"); + { + for ( int32 i = 0; i < MAX_PEDSFX; i++ ) + { + nPedSlotSfx[i] = NO_SAMPLE; + nPedSlotSfxAddr[i] = 0; + } + + nCurrentPedSlot = 0; + } + + // channel volume + TRACE("vol"); + { + for ( int32 i = 0; i < MAXCHANNELS+MAX2DCHANNELS; i++ ) + nChannelVolume[i] = 0; + } + + TRACE("mss"); + { + AIL_set_redist_directory( "mss" ); + + AIL_startup(); + + AIL_set_preference(DIG_MIXER_CHANNELS, MAX_DIGITAL_MIXER_CHANNELS); + + DIG = AIL_open_digital_driver(DIGITALRATE, DIGITALBITS, DIGITALCHANNELS, 0); + if ( DIG == NULL ) + { + OutputDebugString(AIL_last_error()); + Terminate(); + return FALSE; + } + +#ifdef EXTERNAL_3D_SOUND + add_providers(); +#endif + + if ( !InitialiseSampleBanks() ) + { + Terminate(); + return FALSE; + } + + nSampleBankMemoryStartAddress[SFX_BANK_0] = (int32)AIL_mem_alloc_lock(nSampleBankSize[SFX_BANK_0]); + if ( !nSampleBankMemoryStartAddress[SFX_BANK_0] ) + { + Terminate(); + return FALSE; + } + + nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] = (int32)AIL_mem_alloc_lock(PED_BLOCKSIZE*MAX_PEDSFX); + + } + +#ifdef AUDIO_CACHE + TRACE("cache"); + FILE *cacheFile = fopen("audio\\sound.cache", "rb"); + if (cacheFile) { + fread(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); + fclose(cacheFile); + m_bInitialised = TRUE; + }else { +#endif + TRACE("cdrom"); + + S32 tatalms; + char filepath[MAX_PATH]; + + { + m_bInitialised = FALSE; + + while (TRUE) + { + int32 drive = 'C'; + + do + { + char latter[2]; + + latter[0] = drive; + latter[1] = '\0'; + + strcpy(m_szCDRomRootPath, latter); + strcat(m_szCDRomRootPath, ":\\"); + + if ( GetDriveType(m_szCDRomRootPath) == DRIVE_CDROM ) + { + FILE *f; +#ifdef PS2_AUDIO_PATHS + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, PS2StreamedNameTable[0]); + f = fopen(filepath, "rb"); + + if ( !f ) +#endif + { + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, StreamedNameTable[0]); + + f = fopen(filepath, "rb"); + } + if ( f ) + { + fclose(f); + + bool8 bFileNotFound = FALSE; + + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + { +#ifdef PS2_AUDIO_PATHS + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, PS2StreamedNameTable[i]); + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + if ( !mp3Stream[0] ) +#endif + { + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, StreamedNameTable[i]); + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + } + + if ( mp3Stream[0] ) + { + AIL_stream_ms_position(mp3Stream[0], &tatalms, NULL); + + AIL_close_stream(mp3Stream[0]); + mp3Stream[0] = NULL; + + nStreamLength[i] = tatalms; + } + else + { + bFileNotFound = TRUE; + break; + } + } + + if ( !bFileNotFound ) + { + m_bInitialised = TRUE; + break; + } + else + { + m_bInitialised = FALSE; + continue; + } + } + } + + } while ( ++drive <= 'Z' ); + + if ( !m_bInitialised ) + { +#if GTA_VERSION < GTA3_PC_STEAM && !defined(NO_CDCHECK) + FrontEndMenuManager.WaitForUserCD(); + if ( FrontEndMenuManager.m_bQuitGameNoCD ) + { + Terminate(); + return FALSE; + } + continue; +#else + m_bInitialised = TRUE; +#endif + } + + break; + } + } + +#if GTA_VERSION >= GTA3_PC_11 || defined(NO_CDCHECK) + // hddaudio + /** + Option for user to play audio files directly from hard disk. + Copy the contents of the PLAY discs Audio directory into your installed Grand Theft Auto III Audio directory. + Grand Theft Auto III still requires the presence of the PLAY disc when started. + This may give better performance on some machines (though worse on others). + **/ + TRACE("hddaudio 1.1 patch"); + { + int32 streamLength[TOTAL_STREAMED_SOUNDS]; + + bool8 bFileNotFound = FALSE; + char rootpath[MAX_PATH]; + + strcpy(_aHDDPath, m_szCDRomRootPath); + rootpath[0] = '\0'; + + FILE *f; + +#ifdef PS2_AUDIO_PATHS + f = fopen(PS2StreamedNameTable[0], "rb"); + if (!f) +#endif + + f = fopen(StreamedNameTable[0], "rb"); + + if ( f ) + { + fclose(f); + + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + { +#ifdef PS2_AUDIO_PATHS + strcpy(filepath, rootpath); + strcat(filepath, PS2StreamedNameTable[i]); + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + if ( !mp3Stream[0] ) +#endif + { + strcpy(filepath, rootpath); + strcat(filepath, StreamedNameTable[i]); + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + } + + if ( mp3Stream[0] ) + { + AIL_stream_ms_position(mp3Stream[0], &tatalms, NULL); + + AIL_close_stream(mp3Stream[0]); + mp3Stream[0] = NULL; + + streamLength[i] = tatalms; + } + else + { + bFileNotFound = TRUE; + break; + } + } + + } + else + bFileNotFound = TRUE; + + if ( !bFileNotFound ) + { + strcpy(m_szCDRomRootPath, rootpath); + + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + nStreamLength[i] = streamLength[i]; + + _bUseHDDAudio = TRUE; + } + else + _bUseHDDAudio = FALSE; + } +#endif +#ifdef AUDIO_CACHE + cacheFile = fopen("audio\\sound.cache", "wb"); + fwrite(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); + fclose(cacheFile); + } +#endif + + TRACE("stream"); + { + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + mp3Stream [i] = NULL; + nStreamPan [i] = 63; + nStreamVolume[i] = 100; + } + } + + for ( int32 i = 0; i < MAX2DCHANNELS; i++ ) + { + opened_2dsamples[i] = AIL_allocate_sample_handle(DIG); + if ( opened_2dsamples[i] ) + { + AIL_init_sample(opened_2dsamples[i]); + AIL_set_sample_type(opened_2dsamples[i], DIG_F_MONO_16, DIG_PCM_SIGN); + } + } + + TRACE("providerset"); + { + _bSampmanInitialised = TRUE; + +#ifdef EXTERNAL_3D_SOUND + U32 n = 0; + + while ( n < m_nNumberOfProviders ) + { + if ( !strcmp(providers[n].name, "Miles Fast 2D Positional Audio") ) + { + set_new_provider(n); + break; + } + n++; + } + + if ( n == m_nNumberOfProviders ) + { + Terminate(); + return FALSE; + } +#endif + } + + TRACE("bank"); + + LoadSampleBank(SFX_BANK_0); + + // mp3 + TRACE("mp3"); + { + nNumMP3s = 0; + + _pMP3List = NULL; + + _FindMP3s(); + + if ( nNumMP3s != 0 ) + { + nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] = 0; + + for ( tMP3Entry *e = _pMP3List; e != NULL; e = e->pNext ) + { + e->nTrackStreamPos = nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER]; + nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] += e->nTrackLength; + } + + time_t t = time(NULL); + tm *localtm; + bool8 bUseRandomTable; + + if ( t == -1 ) + bUseRandomTable = TRUE; + else + { + bUseRandomTable = FALSE; + localtm = localtime(&t); + } + + int32 randval; + if ( bUseRandomTable ) + randval = AudioManager.m_anRandomTable[1]; + else + randval = localtm->tm_sec * localtm->tm_min; + + _CurMP3Index = randval % nNumMP3s; + + tMP3Entry *randmp3 = _pMP3List; + for ( int32 i = randval % nNumMP3s; i > 0; --i) + randmp3 = randmp3->pNext; + + if ( bUseRandomTable ) + _CurMP3Pos = AudioManager.m_anRandomTable[0] % randmp3->nTrackLength; + else + { + if ( localtm->tm_sec > 0 ) + { + int32 s = localtm->tm_sec; + _CurMP3Pos = s*s*s*s*s*s*s*s % randmp3->nTrackLength; + } + else + _CurMP3Pos = AudioManager.m_anRandomTable[0] % randmp3->nTrackLength; + } + } + else + _CurMP3Pos = 0; + + _bIsMp3Active = FALSE; + } + + TRACE("end"); + + return TRUE; +} + +void +cSampleManager::Terminate(void) +{ + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + if ( mp3Stream[i] ) + { + AIL_pause_stream(mp3Stream[i], 1); + AIL_close_stream(mp3Stream[i]); + mp3Stream[i] = NULL; + } + } + + for ( int32 i = 0; i < MAX2DCHANNELS; i++ ) + { + if ( opened_2dsamples[i] ) + { + AIL_release_sample_handle(opened_2dsamples[i]); + opened_2dsamples[i] = NULL; + } + } + +#ifdef EXTERNAL_3D_SOUND + release_existing(); +#endif + + _DeleteMP3Entries(); + + if ( nSampleBankMemoryStartAddress[SFX_BANK_0] != 0 ) + { + AIL_mem_free_lock((void *)nSampleBankMemoryStartAddress[SFX_BANK_0]); + nSampleBankMemoryStartAddress[SFX_BANK_0] = 0; + } + + if ( nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] != 0 ) + { + AIL_mem_free_lock((void *)nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS]); + nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] = 0; + } + + if ( DIG ) + { + AIL_close_digital_driver(DIG); + DIG = NULL; + } + + AIL_shutdown(); + + _bSampmanInitialised = FALSE; +} + +bool8 +cSampleManager::CheckForAnAudioFileOnCD(void) +{ +#if GTA_VERSION < GTA3_PC_STEAM && !defined(NO_CDCHECK) + char filepath[MAX_PATH]; + FILE *f; + +#ifdef PS2_AUDIO_PATHS +#if GTA_VERSION >= GTA3_PC_11 + if(_bUseHDDAudio) + strcpy(filepath, _aHDDPath); + else + strcpy(filepath, m_szCDRomRootPath); +#else + strcpy(filepath, m_szCDRomRootPath); +#endif // #if GTA_VERSION >= GTA3_PC_11 + + strcat(filepath, PS2StreamedNameTable[AudioManager.m_anRandomTable[1] % TOTAL_STREAMED_SOUNDS]); + + f = fopen(filepath, "rb"); + if ( !f ) +#endif // PS2_AUDIO_PATHS + { +#if GTA_VERSION >= GTA3_PC_11 + if (_bUseHDDAudio) + strcpy(filepath, _aHDDPath); + else + strcpy(filepath, m_szCDRomRootPath); +#else + strcpy(filepath, m_szCDRomRootPath); +#endif // #if GTA_VERSION >= GTA3_PC_11 + + strcat(filepath, StreamedNameTable[AudioManager.m_anRandomTable[1] % TOTAL_STREAMED_SOUNDS]); + + f = fopen(filepath, "rb"); + } + if ( f ) + { + fclose(f); + + return TRUE; + } + + return FALSE; + +#else + return TRUE; +#endif // #if GTA_VERSION < GTA3_PC_STEAM && !defined(NO_CDCHECK) +} + +char +cSampleManager::GetCDAudioDriveLetter(void) +{ +#if GTA_VERSION >= GTA3_PC_11 || defined(NO_CDCHECK) + if (_bUseHDDAudio) + { + if ( strlen(_aHDDPath) != 0 ) + return _aHDDPath[0]; + else + return '\0'; + } + else + { + if ( strlen(m_szCDRomRootPath) != 0 ) + return m_szCDRomRootPath[0]; + else + return '\0'; + } +#else + if ( strlen(m_szCDRomRootPath) != 0 ) + return m_szCDRomRootPath[0]; + else + return '\0'; +#endif +} + +void +cSampleManager::UpdateEffectsVolume(void) //[Y], cSampleManager::UpdateSoundBuffers ? +{ + if ( _bSampmanInitialised ) + { + for ( int32 i = 0; i < MAXCHANNELS+MAX2DCHANNELS; i++ ) + { +#ifdef EXTERNAL_3D_SOUND + if ( i < MAXCHANNELS ) + { + if ( opened_samples[i] && GetChannelUsedFlag(i) ) + { + if ( nChannelVolume[i] ) + { + AIL_set_3D_sample_volume(opened_samples[i], + m_nEffectsFadeVolume * nChannelVolume[i] * m_nEffectsVolume >> 14); + } + } + } + else +#endif + { + if ( opened_2dsamples[i - MAXCHANNELS] ) + { + if ( GetChannelUsedFlag(i - MAXCHANNELS) ) + { + if ( nChannelVolume[i - MAXCHANNELS] ) + { + AIL_set_sample_volume(opened_2dsamples[i - MAXCHANNELS], + m_nEffectsFadeVolume * nChannelVolume[i - MAXCHANNELS] * m_nEffectsVolume >> 14); + } + } + } + } + } + } +} + +void +cSampleManager::SetEffectsMasterVolume(uint8 nVolume) +{ + m_nEffectsVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicMasterVolume(uint8 nVolume) +{ + m_nMusicVolume = nVolume; +} + +void +cSampleManager::SetEffectsFadeVolume(uint8 nVolume) +{ + m_nEffectsFadeVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicFadeVolume(uint8 nVolume) +{ + m_nMusicFadeVolume = nVolume; +} + +void +cSampleManager::SetMonoMode(bool8 nMode) +{ + m_nMonoMode = nMode; +} + +bool8 +cSampleManager::LoadSampleBank(uint8 nBank) +{ + if ( CTimer::GetIsCodePaused() ) + return FALSE; + + if ( MusicManager.IsInitialised() + && MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && nBank != SFX_BANK_0 ) + { + return FALSE; + } + + if ( fseek(fpSampleDataHandle, nSampleBankDiscStartOffset[nBank], SEEK_SET) != 0 ) + return FALSE; + + if ( fread((void *)nSampleBankMemoryStartAddress[nBank], 1, nSampleBankSize[nBank],fpSampleDataHandle) != nSampleBankSize[nBank] ) + return FALSE; + + gBankLoaded[nBank] = LOADING_STATUS_LOADED; + + return TRUE; +} + +void +cSampleManager::UnloadSampleBank(uint8 nBank) +{ + gBankLoaded[nBank] = LOADING_STATUS_NOT_LOADED; +} + +int8 +cSampleManager::IsSampleBankLoaded(uint8 nBank) +{ + return gBankLoaded[nBank]; +} + +uint8 +cSampleManager::IsPedCommentLoaded(uint32 nComment) +{ + int8 slot; + + for ( int32 i = 0; i < _TODOCONST(3); i++ ) + { + slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nComment == nPedSlotSfx[slot] ) + return LOADING_STATUS_LOADED; + } + + return LOADING_STATUS_NOT_LOADED; +} + +int32 +cSampleManager::_GetPedCommentSlot(uint32 nComment) +{ + int8 slot; + + for ( int32 i = 0; i < _TODOCONST(3); i++ ) + { + slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nComment == nPedSlotSfx[slot] ) + return slot; + } + + return -1; +} + +bool8 +cSampleManager::LoadPedComment(uint32 nComment) +{ + if ( CTimer::GetIsCodePaused() ) + return FALSE; + + // no talking peds during cutsenes or the game end + if ( MusicManager.IsInitialised() ) + { + switch ( MusicManager.GetMusicMode() ) + { + case MUSICMODE_CUTSCENE: + { + return FALSE; + + break; + } + + case MUSICMODE_FRONTEND: + { + if ( MusicManager.GetNextTrack() == STREAMED_SOUND_GAME_COMPLETED ) + return FALSE; + + break; + } + } + } + + if ( fseek(fpSampleDataHandle, m_aSamples[nComment].nOffset, SEEK_SET) != 0 ) + return FALSE; + + if ( fread((void *)(nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] + PED_BLOCKSIZE*nCurrentPedSlot), 1, m_aSamples[nComment].nSize, fpSampleDataHandle) != m_aSamples[nComment].nSize ) + return FALSE; + + nPedSlotSfxAddr[nCurrentPedSlot] = nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] + PED_BLOCKSIZE*nCurrentPedSlot; + nPedSlotSfx [nCurrentPedSlot] = nComment; + + if ( ++nCurrentPedSlot >= MAX_PEDSFX ) + nCurrentPedSlot = 0; + + return TRUE; +} + +int32 +cSampleManager::GetBankContainingSound(uint32 offset) +{ + if ( offset >= BankStartOffset[SFX_BANK_PED_COMMENTS] ) + return SFX_BANK_PED_COMMENTS; + + if ( offset >= BankStartOffset[SFX_BANK_0] ) + return SFX_BANK_0; + + return INVALID_SFX_BANK; +} + +uint32 +cSampleManager::GetSampleBaseFrequency(uint32 nSample) +{ + return m_aSamples[nSample].nFrequency; +} + +uint32 +cSampleManager::GetSampleLoopStartOffset(uint32 nSample) +{ + return m_aSamples[nSample].nLoopStart; +} + +int32 +cSampleManager::GetSampleLoopEndOffset(uint32 nSample) +{ + return m_aSamples[nSample].nLoopEnd; +} + +uint32 +cSampleManager::GetSampleLength(uint32 nSample) +{ + return m_aSamples[nSample].nSize >> 1; +} + +bool8 +cSampleManager::UpdateReverb(void) +{ +#ifdef EXTERNAL_3D_SOUND + if ( !usingEAX ) + return FALSE; + + if ( AudioManager.m_FrameCounter & 15 ) + return FALSE; + +#ifdef AUDIO_REFLECTIONS + float y = AudioManager.m_afReflectionsDistances[REFLECTION_TOP] + AudioManager.m_afReflectionsDistances[REFLECTION_BOTTOM]; + float x = AudioManager.m_afReflectionsDistances[REFLECTION_LEFT] + AudioManager.m_afReflectionsDistances[REFLECTION_RIGHT]; + float z = AudioManager.m_afReflectionsDistances[REFLECTION_UP]; +#else + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; +#endif + + float normy = norm(y, 5.0f, 40.0f); + float normx = norm(x, 5.0f, 40.0f); + float normz = norm(z, 5.0f, 40.0f); + + float fRatio; + + if ( normy == 0.0f ) + { + if ( normx == 0.0f ) + { + if ( normz == 0.0f ) + fRatio = 0.3f; + else + fRatio = 0.5f; + } + else + { + fRatio = 0.3f; + } + } + else + { + if ( normx == 0.0f ) + { + if ( normz == 0.0f ) + fRatio = 0.3f; + else + fRatio = 0.5f; + } + else + { + if ( normz == 0.0f ) + fRatio = 0.3f; + else + fRatio = (normy+normx+normz) / 3.0f; + } + } + + fRatio = Clamp(fRatio, usingEAX3==1 ? 0.0f : 0.30f, 1.0f); + + if ( fRatio == _fPrevEaxRatioDestination ) + return FALSE; + + if ( usingEAX3 ) + { + if ( EAX3ListenerInterpolate(&StartEAX3, &FinishEAX3, fRatio, &EAX3Params, false) ) + { + AIL_set_3D_provider_preference(opened_provider, "EAX all parameters", &EAX3Params); + _fEffectsLevel = 1.0f - fRatio * 0.5f; + } + } + else + { + if ( _usingMilesFast2D ) + _fEffectsLevel = (1.0f - fRatio) * 0.4f; + else + _fEffectsLevel = (1.0f - fRatio) * 0.7f; + } + + _fPrevEaxRatioDestination = fRatio; + + return TRUE; +#endif + return FALSE; +} + +void +cSampleManager::SetChannelReverbFlag(uint32 nChannel, bool8 nReverbFlag) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( usingEAX ) + { + if ( nReverbFlag != FALSE ) + { + if ( !b2d ) + AIL_set_3D_sample_effects_level(opened_samples[nChannel], _fEffectsLevel); + } + else + { + if ( !b2d ) + AIL_set_3D_sample_effects_level(opened_samples[nChannel], 0.0f); + } + } +#endif +} + +bool8 +cSampleManager::InitialiseChannel(uint32 nChannel, uint32 nSfx, uint8 nBank) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } +#endif + + int32 addr; + + if ( nSfx < SAMPLEBANK_MAX ) + { + if ( !IsSampleBankLoaded(nBank) ) + return FALSE; + + addr = nSampleBankMemoryStartAddress[nBank] + m_aSamples[nSfx].nOffset - m_aSamples[BankStartOffset[nBank]].nOffset; + } + else + { + int32 i; + for ( i = 0; i < _TODOCONST(3); i++ ) + { + int32 slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nSfx == nPedSlotSfx[slot] ) + { + addr = nPedSlotSfxAddr[slot]; + break; + } + } + + if (i == _TODOCONST(3)) + return FALSE; + } + +#ifdef EXTERNAL_3D_SOUND + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + { + AIL_set_sample_address(opened_2dsamples[nChannel - MAXCHANNELS], (void *)addr, m_aSamples[nSfx].nSize); + return TRUE; + } + else + return FALSE; +#ifdef EXTERNAL_3D_SOUND + } + else + { + AILSOUNDINFO info; + + info.format = WAVE_FORMAT_PCM; + info.data_ptr = (void *)addr; + info.channels = 1; + info.data_len = m_aSamples[nSfx].nSize; + info.rate = m_aSamples[nSfx].nFrequency; + info.bits = 16; + + if ( AIL_set_3D_sample_info(opened_samples[nChannel], &info) == 0 ) + { + OutputDebugString(AIL_last_error()); + return FALSE; + } + + return TRUE; + } +#endif +} + +#ifdef EXTERNAL_3D_SOUND +void +cSampleManager::SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume) +{ + uint32 vol = nVolume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + nChannelVolume[nChannel] = vol; + + // increase the volume for JB.MP3 and S4_BDBD.MP3 + if ( MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && MusicManager.GetNextTrack() != STREAMED_SOUND_NEWS_INTRO + && MusicManager.GetNextTrack() != STREAMED_SOUND_CUTSCENE_SAL4_BDBD ) + { + nChannelVolume[nChannel] >>= 2; + } + + if ( opened_samples[nChannel] ) + AIL_set_3D_sample_volume(opened_samples[nChannel], m_nEffectsFadeVolume*nChannelVolume[nChannel]*m_nEffectsVolume >> 14); + +} + +void +cSampleManager::SetChannel3DPosition(uint32 nChannel, float fX, float fY, float fZ) +{ + if ( opened_samples[nChannel] ) + AIL_set_3D_position(opened_samples[nChannel], -fX, fY, fZ); +} + +void +cSampleManager::SetChannel3DDistances(uint32 nChannel, float fMax, float fMin) +{ + if ( opened_samples[nChannel] ) + AIL_set_3D_sample_distances(opened_samples[nChannel], fMax, fMin); +} +#endif + +void +cSampleManager::SetChannelVolume(uint32 nChannel, uint32 nVolume) +{ + uint32 vol = nVolume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + +#ifdef EXTERNAL_3D_SOUND + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { +#endif + nChannelVolume[nChannel] = vol; + + // increase the volume for JB.MP3 and S4_BDBD.MP3 + if ( MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && MusicManager.GetNextTrack() != STREAMED_SOUND_NEWS_INTRO + && MusicManager.GetNextTrack() != STREAMED_SOUND_CUTSCENE_SAL4_BDBD ) + { + nChannelVolume[nChannel] >>= 2; + } + + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + { + AIL_set_sample_volume(opened_2dsamples[nChannel - MAXCHANNELS], + m_nEffectsFadeVolume*vol*m_nEffectsVolume >> 14); + } + +#ifdef EXTERNAL_3D_SOUND + break; + } + } +#endif +} + +void +cSampleManager::SetChannelPan(uint32 nChannel, uint32 nPan) +{ +#ifdef EXTERNAL_3D_SOUND + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { +#endif +#if !defined(FIX_BUGS) && defined(EXTERNAL_3D_SOUND) + if ( opened_samples[nChannel - MAXCHANNELS] ) // BUG +#else + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) +#endif + AIL_set_sample_pan(opened_2dsamples[nChannel - MAXCHANNELS], nPan); + +#ifdef EXTERNAL_3D_SOUND + break; + } + } +#endif +} + +void +cSampleManager::SetChannelFrequency(uint32 nChannel, uint32 nFreq) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + AIL_set_sample_playback_rate(opened_2dsamples[nChannel - MAXCHANNELS], nFreq); +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + AIL_set_3D_sample_playback_rate(opened_samples[nChannel], nFreq); + } +#endif +} + +void +cSampleManager::SetChannelLoopPoints(uint32 nChannel, uint32 nLoopStart, int32 nLoopEnd) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + AIL_set_sample_loop_block(opened_2dsamples[nChannel - MAXCHANNELS], nLoopStart, nLoopEnd); +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + AIL_set_3D_sample_loop_block(opened_samples[nChannel], nLoopStart, nLoopEnd); + } +#endif +} + +void +cSampleManager::SetChannelLoopCount(uint32 nChannel, uint32 nLoopCount) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + AIL_set_sample_loop_count(opened_2dsamples[nChannel - MAXCHANNELS], nLoopCount); +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + AIL_set_3D_sample_loop_count(opened_samples[nChannel], nLoopCount); + } +#endif +} + +bool8 +cSampleManager::GetChannelUsedFlag(uint32 nChannel) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + return AIL_sample_status(opened_2dsamples[nChannel - MAXCHANNELS]) == SMP_PLAYING; + else + return FALSE; +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + return AIL_3D_sample_status(opened_samples[nChannel]) == SMP_PLAYING; + else + return FALSE; + } +#endif + +} + +void +cSampleManager::StartChannel(uint32 nChannel) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + AIL_start_sample(opened_2dsamples[nChannel - MAXCHANNELS]); +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + AIL_start_3D_sample(opened_samples[nChannel]); + } +#endif +} + +void +cSampleManager::StopChannel(uint32 nChannel) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + AIL_end_sample(opened_2dsamples[nChannel - MAXCHANNELS]); +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + { + if ( AIL_3D_sample_status(opened_samples[nChannel]) == SMP_PLAYING ) + AIL_end_3D_sample(opened_samples[nChannel]); + } + } +#endif +} + +void +cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream) +{ + if ( m_bInitialised ) + { + if ( nFile < TOTAL_STREAMED_SOUNDS ) + { + if ( mp3Stream[nStream] ) + { + AIL_pause_stream(mp3Stream[nStream], 1); + AIL_close_stream(mp3Stream[nStream]); + } + + char filepath[MAX_PATH]; +#ifdef PS2_AUDIO_PATHS + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filepath, 0); + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filepath, 0); + } + + if ( mp3Stream[nStream] ) + { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_service_stream(mp3Stream[nStream], 1); + } + else + OutputDebugString(AIL_last_error()); + } + } +} + +void +cSampleManager::PauseStream(bool8 nPauseFlag, uint8 nStream) +{ + if ( m_bInitialised ) + { + if ( mp3Stream[nStream] ) + AIL_pause_stream(mp3Stream[nStream], nPauseFlag != FALSE); + } +} + +void +cSampleManager::StartPreloadedStreamedFile(uint8 nStream) +{ + if ( m_bInitialised ) + { + if ( mp3Stream[nStream] ) + AIL_start_stream(mp3Stream[nStream]); + } +} + +bool8 +cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) +{ + uint32 i = 0; + uint32 position = nPos; + char filename[MAX_PATH]; + + if ( !m_bInitialised || nFile >= TOTAL_STREAMED_SOUNDS ) + return FALSE; + + if ( mp3Stream[nStream] ) + { + AIL_pause_stream(mp3Stream[nStream], 1); + AIL_close_stream(mp3Stream[nStream]); + } + if ( nFile == STREAMED_SOUND_RADIO_MP3_PLAYER ) + { + do + { + // Just switched to MP3 player + if ( !_bIsMp3Active && i == 0 ) + { + if ( nPos > nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] ) + position = 0; + tMP3Entry *e = _pMP3List; + + // Try to continue from previous song, if already started + if(!_GetMP3PosFromStreamPos(&position, &e) && !e) { + nFile = 0; +#ifdef PS2_AUDIO_PATHS + strcpy(filename, m_szCDRomRootPath); + strcat(filename, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filename, m_szCDRomRootPath); + strcat(filename, StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + } + if ( mp3Stream[nStream] ) + { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_set_stream_ms_position(mp3Stream[nStream], position); + AIL_pause_stream(mp3Stream[nStream], 0); + return TRUE; + } + return FALSE; + + } else { + if ( e->pLinkPath != NULL ) + mp3Stream[nStream] = AIL_open_stream(DIG, e->pLinkPath, 0); + else { + strcpy(filename, _mp3DirectoryPath); + strcat(filename, e->aFilename); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + } + + if ( mp3Stream[nStream] ) { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_set_stream_ms_position(mp3Stream[nStream], position); + AIL_pause_stream(mp3Stream[nStream], 0); + + _bIsMp3Active = TRUE; + + return TRUE; + } + // fall through, start playing from another song + } + } else { + if(++_CurMP3Index >= nNumMP3s) _CurMP3Index = 0; + + _CurMP3Pos = 0; + + tMP3Entry *mp3 = _GetMP3EntryByIndex(_CurMP3Index); + if ( !mp3 ) + { + mp3 = _pMP3List; + if ( !_pMP3List ) + { + nFile = 0; + _bIsMp3Active = FALSE; +#ifdef PS2_AUDIO_PATHS + strcpy(filename, m_szCDRomRootPath); + strcat(filename, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filename, m_szCDRomRootPath); + strcat(filename, StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + } + if ( mp3Stream[nStream] ) + { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_set_stream_ms_position(mp3Stream[nStream], position); + AIL_pause_stream(mp3Stream[nStream], 0); + return TRUE; + } + return FALSE; + } + } + if(mp3->pLinkPath != NULL) + mp3Stream[nStream] = AIL_open_stream(DIG, mp3->pLinkPath, 0); + else { + strcpy(filename, _mp3DirectoryPath); + strcat(filename, mp3->aFilename); + + mp3Stream[nStream] = + AIL_open_stream(DIG, filename, 0); + } + + if(mp3Stream[nStream]) { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_set_stream_ms_position(mp3Stream[nStream], 0); + AIL_pause_stream(mp3Stream[nStream], 0); +#ifdef FIX_BUGS + _bIsMp3Active = TRUE; +#endif + return TRUE; + } + + } + _bIsMp3Active = FALSE; + } + while ( ++i < nNumMP3s ); + position = 0; + nFile = 0; + } +#ifdef PS2_AUDIO_PATHS + strcpy(filename, m_szCDRomRootPath); + strcat(filename, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filename, m_szCDRomRootPath); + strcat(filename, StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + } + if ( mp3Stream[nStream] ) + { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_set_stream_ms_position(mp3Stream[nStream], position); + AIL_pause_stream(mp3Stream[nStream], 0); + return TRUE; + } + return FALSE; +} + +void +cSampleManager::StopStreamedFile(uint8 nStream) +{ + if ( m_bInitialised ) + { + if ( mp3Stream[nStream] ) + { + AIL_pause_stream(mp3Stream[nStream], 1); + + AIL_close_stream(mp3Stream[nStream]); + mp3Stream[nStream] = NULL; + + if ( nStream == 0 ) + _bIsMp3Active = FALSE; + } + } +} + +int32 +cSampleManager::GetStreamedFilePosition(uint8 nStream) +{ + S32 currentms; + + if ( m_bInitialised ) + { + if ( mp3Stream[nStream] ) + { + if ( _bIsMp3Active ) + { + tMP3Entry *mp3 = _GetMP3EntryByIndex(_CurMP3Index); + + if ( mp3 != NULL ) + { + AIL_stream_ms_position(mp3Stream[nStream], NULL, ¤tms); + return currentms + mp3->nTrackStreamPos; + } + else + return 0; + } + else + { + AIL_stream_ms_position(mp3Stream[nStream], NULL, ¤tms); + return currentms; + } + } + } + + return 0; +} + +void +cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, bool8 nEffectFlag, uint8 nStream) +{ + uint8 vol = nVolume; + + if ( m_bInitialised ) + { + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + nStreamVolume[nStream] = vol; + nStreamPan[nStream] = nPan; + + if ( mp3Stream[nStream] ) + { + if ( nEffectFlag ) + AIL_set_stream_volume(mp3Stream[nStream], m_nEffectsFadeVolume*vol*m_nEffectsVolume >> 14); + else + AIL_set_stream_volume(mp3Stream[nStream], m_nMusicFadeVolume*vol*m_nMusicVolume >> 14); + + AIL_set_stream_pan(mp3Stream[nStream], nPan); + } + } +} + +int32 +cSampleManager::GetStreamedFileLength(uint8 nStream) +{ + if ( m_bInitialised ) + return nStreamLength[nStream]; + + return 0; +} + +bool8 +cSampleManager::IsStreamPlaying(uint8 nStream) +{ + if ( m_bInitialised ) + { + if ( mp3Stream[nStream] ) + { + if ( AIL_stream_status(mp3Stream[nStream]) == SMP_PLAYING ) + return TRUE; + else + return FALSE; + } + } + + return FALSE; +} + +bool8 +cSampleManager::InitialiseSampleBanks(void) +{ + int32 nBank = SFX_BANK_0; + + fpSampleDescHandle = fopen(SampleBankDescFilename, "rb"); + if ( fpSampleDescHandle == NULL ) + return FALSE; + + fpSampleDataHandle = fopen(SampleBankDataFilename, "rb"); + if ( fpSampleDataHandle == NULL ) + { + fclose(fpSampleDescHandle); + fpSampleDescHandle = NULL; + + return FALSE; + } + + fseek(fpSampleDataHandle, 0, SEEK_END); + _nSampleDataEndOffset = ftell(fpSampleDataHandle); + rewind(fpSampleDataHandle); + + fread(m_aSamples, sizeof(tSample), TOTAL_AUDIO_SAMPLES, fpSampleDescHandle); + + fclose(fpSampleDescHandle); + fpSampleDescHandle = NULL; + + for ( int32 i = 0; i < TOTAL_AUDIO_SAMPLES; i++ ) + { +#ifdef FIX_BUGS + if (nBank >= MAX_SFX_BANKS) break; +#endif + if ( BankStartOffset[nBank] == BankStartOffset[SFX_BANK_0] + i ) + { + nSampleBankDiscStartOffset[nBank] = m_aSamples[i].nOffset; + nBank++; + } + } + + nSampleBankSize[SFX_BANK_0] = nSampleBankDiscStartOffset[SFX_BANK_PED_COMMENTS] - nSampleBankDiscStartOffset[SFX_BANK_0]; + nSampleBankSize[SFX_BANK_PED_COMMENTS] = _nSampleDataEndOffset - nSampleBankDiscStartOffset[SFX_BANK_PED_COMMENTS]; + + return TRUE; +} + +#endif diff --git a/src/audio/sampman_null.cpp b/src/audio/sampman_null.cpp new file mode 100644 index 0000000..d607836 --- /dev/null +++ b/src/audio/sampman_null.cpp @@ -0,0 +1,372 @@ +#include "common.h" +#if !defined(AUDIO_OAL) && !defined(AUDIO_MSS) +#include "sampman.h" +#include "AudioManager.h" + +cSampleManager SampleManager; +bool8 _bSampmanInitialised = FALSE; + +uint32 BankStartOffset[MAX_SFX_BANKS]; +uint32 nNumMP3s; + +cSampleManager::cSampleManager(void) +{ + ; +} + +cSampleManager::~cSampleManager(void) +{ + +} + +#ifdef EXTERNAL_3D_SOUND +void cSampleManager::SetSpeakerConfig(int32 nConfig) +{ + +} + +uint32 cSampleManager::GetMaximumSupportedChannels(void) +{ + return MAXCHANNELS; +} + +uint32 cSampleManager::GetNum3DProvidersAvailable() +{ + return 1; +} + +void cSampleManager::SetNum3DProvidersAvailable(uint32 num) +{ + +} + +char *cSampleManager::Get3DProviderName(uint8 id) +{ + static char name[64] = "NULL"; + return name; +} + +void cSampleManager::Set3DProviderName(uint8 id, char *name) +{ + +} + +int8 cSampleManager::GetCurrent3DProviderIndex(void) +{ + return 0; +} + +int8 cSampleManager::SetCurrent3DProvider(uint8 nProvider) +{ + return 0; +} +#endif + +bool8 +cSampleManager::IsMP3RadioChannelAvailable(void) +{ + return nNumMP3s != 0; +} + + +void cSampleManager::ReleaseDigitalHandle(void) +{ +} + +void cSampleManager::ReacquireDigitalHandle(void) +{ +} + +bool8 +cSampleManager::Initialise(void) +{ + return TRUE; +} + +void +cSampleManager::Terminate(void) +{ + +} + +bool8 cSampleManager::CheckForAnAudioFileOnCD(void) +{ + return TRUE; +} + +char cSampleManager::GetCDAudioDriveLetter(void) +{ + return '\0'; +} + +void +cSampleManager::UpdateEffectsVolume(void) +{ + +} + +void +cSampleManager::SetEffectsMasterVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetMusicMasterVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetEffectsFadeVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetMusicFadeVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetMonoMode(uint8 nMode) +{ +} + +bool8 +cSampleManager::LoadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); + return FALSE; +} + +void +cSampleManager::UnloadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); +} + +int8 +cSampleManager::IsSampleBankLoaded(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); + + return LOADING_STATUS_NOT_LOADED; +} + +uint8 +cSampleManager::IsPedCommentLoaded(uint32 nComment) +{ + ASSERT( nComment < TOTAL_AUDIO_SAMPLES ); + + return LOADING_STATUS_NOT_LOADED; +} + + +int32 +cSampleManager::_GetPedCommentSlot(uint32 nComment) +{ + return -1; +} + +bool8 +cSampleManager::LoadPedComment(uint32 nComment) +{ + ASSERT( nComment < TOTAL_AUDIO_SAMPLES ); + return FALSE; +} + +int32 +cSampleManager::GetBankContainingSound(uint32 offset) +{ + return INVALID_SFX_BANK; +} + +uint32 +cSampleManager::GetSampleBaseFrequency(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return 0; +} + +uint32 +cSampleManager::GetSampleLoopStartOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return 0; +} + +int32 +cSampleManager::GetSampleLoopEndOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return 0; +} + +uint32 +cSampleManager::GetSampleLength(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return 0; +} + +bool8 cSampleManager::UpdateReverb(void) +{ + return FALSE; +} + +void +cSampleManager::SetChannelReverbFlag(uint32 nChannel, bool8 nReverbFlag) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +bool8 +cSampleManager::InitialiseChannel(uint32 nChannel, uint32 nSfx, uint8 nBank) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + return FALSE; +} + +#ifdef EXTERNAL_3D_SOUND +void +cSampleManager::SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume) +{ + ASSERT( nChannel < MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannel3DPosition(uint32 nChannel, float fX, float fY, float fZ) +{ + ASSERT( nChannel < MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannel3DDistances(uint32 nChannel, float fMax, float fMin) +{ + ASSERT( nChannel < MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} +#endif + +void +cSampleManager::SetChannelVolume(uint32 nChannel, uint32 nVolume) +{ + ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannelPan(uint32 nChannel, uint32 nPan) +{ + ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannelFrequency(uint32 nChannel, uint32 nFreq) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannelLoopPoints(uint32 nChannel, uint32 nLoopStart, int32 nLoopEnd) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannelLoopCount(uint32 nChannel, uint32 nLoopCount) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +bool8 +cSampleManager::GetChannelUsedFlag(uint32 nChannel) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + + return FALSE; +} + +void +cSampleManager::StartChannel(uint32 nChannel) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::StopChannel(uint32 nChannel) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); +} + +void +cSampleManager::PauseStream(bool8 nPauseFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); +} + +void +cSampleManager::StartPreloadedStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); +} + +bool8 +cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + return FALSE; +} + +void +cSampleManager::StopStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); +} + +int32 +cSampleManager::GetStreamedFilePosition(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + return 0; +} + +void +cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, uint8 nEffectFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); +} + +int32 +cSampleManager::GetStreamedFileLength(uint8 nStream) +{ + ASSERT( nStream < TOTAL_STREAMED_SOUNDS ); + + return 1; +} + +bool8 +cSampleManager::IsStreamPlaying(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + return FALSE; +} + +bool8 +cSampleManager::InitialiseSampleBanks(void) +{ + + return TRUE; +} + +#endif diff --git a/src/audio/sampman_oal.cpp b/src/audio/sampman_oal.cpp new file mode 100644 index 0000000..d59b86e --- /dev/null +++ b/src/audio/sampman_oal.cpp @@ -0,0 +1,1973 @@ +//#define JUICY_OAL + +#ifdef AUDIO_OAL +#include + +#include "eax.h" +#include "eax-util.h" + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include + +// for user MP3s +#include +#include +#include +#else +#define _getcwd getcwd +#endif + +#if defined _MSC_VER && !defined CMAKE_NO_AUTOLINK +#pragma comment( lib, "OpenAL32.lib" ) +#endif + +#include "common.h" +#include "crossplatform.h" + +#include "sampman.h" + +#include "oal/oal_utils.h" +#include "oal/aldlist.h" +#include "oal/channel.h" + +#include +#ifdef MULTITHREADED_AUDIO +#include +#include +#include +#endif +#include "oal/stream.h" + +#include "AudioManager.h" +#include "MusicManager.h" +#include "Frontend.h" +#include "Timer.h" +#ifdef AUDIO_OAL_USE_OPUS +#include +#endif + +//TODO: fix eax3 reverb + +cSampleManager SampleManager; +bool8 _bSampmanInitialised = FALSE; + +uint32 BankStartOffset[MAX_SFX_BANKS]; + +int prevprovider=-1; +int curprovider=-1; +int usingEAX=0; +int usingEAX3=0; +//int speaker_type=0; +ALCdevice *ALDevice = NULL; +ALCcontext *ALContext = NULL; +unsigned int _maxSamples; +float _fPrevEaxRatioDestination; +bool _effectsSupported = false; +bool _usingEFX; +float _fEffectsLevel; +ALuint ALEffect = AL_EFFECT_NULL; +ALuint ALEffectSlot = AL_EFFECTSLOT_NULL; +struct +{ + const char *id; + char name[256]; + int sources; + bool bSupportsFx; +}providers[MAXPROVIDERS]; + +int defaultProvider; + + +char SampleBankDescFilename[] = "audio/sfx.SDT"; +char SampleBankDataFilename[] = "audio/sfx.RAW"; + +FILE *fpSampleDescHandle; +#ifdef OPUS_SFX +OggOpusFile *fpSampleDataHandle; +#else +FILE *fpSampleDataHandle; +#endif +int8 gBankLoaded [MAX_SFX_BANKS]; +int32 nSampleBankDiscStartOffset [MAX_SFX_BANKS]; +int32 nSampleBankSize [MAX_SFX_BANKS]; +uintptr nSampleBankMemoryStartAddress[MAX_SFX_BANKS]; +int32 _nSampleDataEndOffset; + +int32 nPedSlotSfx [MAX_PEDSFX]; +int32 nPedSlotSfxAddr[MAX_PEDSFX]; +uint8 nCurrentPedSlot; + +CChannel aChannel[NUM_CHANNELS]; +uint8 nChannelVolume[NUM_CHANNELS]; + +uint32 nStreamLength[TOTAL_STREAMED_SOUNDS]; +ALuint ALStreamSources[MAX_STREAMS][2]; +ALuint ALStreamBuffers[MAX_STREAMS][NUM_STREAMBUFFERS]; + +struct tMP3Entry +{ + char aFilename[MAX_PATH]; + + uint32 nTrackLength; + uint32 nTrackStreamPos; + + tMP3Entry* pNext; + char* pLinkPath; +}; + +uint32 nNumMP3s; +tMP3Entry* _pMP3List; +char _mp3DirectoryPath[MAX_PATH]; +CStream *aStream[MAX_STREAMS]; +uint8 nStreamPan [MAX_STREAMS]; +uint8 nStreamVolume[MAX_STREAMS]; +uint32 _CurMP3Index; +int32 _CurMP3Pos; +bool8 _bIsMp3Active; +/////////////////////////////////////////////////////////////// +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +EAXLISTENERPROPERTIES StartEAX3 = + {26, 1.7f, 0.8f, -1000, -1000, -100, 4.42f, 0.14f, 1.00f, 429, 0.014f, 0.00f,0.00f,0.00f, 1023, 0.021f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2727.1f, 250.0f, 0.00f, 0x3f }; + +EAXLISTENERPROPERTIES FinishEAX3 = + {26, 100.0f, 1.0f, 0, -1000, -2200, 20.0f, 1.39f, 1.00f, 1000, 0.069f, 0.00f,0.00f,0.00f, 400, 0.100f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 3.982f, 0.000f, -18.0f, 3530.8f, 417.9f, 6.70f, 0x3f }; + +EAXLISTENERPROPERTIES EAX3Params; + + +bool IsFXSupported() +{ + return _effectsSupported; // usingEAX || usingEAX3 || _usingEFX; +} + +void EAX_SetAll(const EAXLISTENERPROPERTIES *allparameters) +{ + if ( usingEAX || usingEAX3 ) + EAX3_Set(ALEffect, allparameters); + else + EFX_Set(ALEffect, allparameters); +} + +static void +add_providers() +{ + SampleManager.SetNum3DProvidersAvailable(0); + + static ALDeviceList DeviceList; + ALDeviceList *pDeviceList = &DeviceList; + + if ((pDeviceList) && (pDeviceList->GetNumDevices())) + { + const int devNumber = Min(pDeviceList->GetNumDevices(), MAXPROVIDERS); + int n = 0; + + //for (int i = 0; i < devNumber; i++) + int i = pDeviceList->GetDefaultDevice(); + { + if ( n < MAXPROVIDERS ) + { + providers[n].id = pDeviceList->GetDeviceName(i); + strcpy(providers[n].name, "OPENAL SOFT"); + providers[n].sources = pDeviceList->GetMaxNumSources(i); + SampleManager.Set3DProviderName(n, providers[n].name); + n++; + } + + if ( alGetEnumValue("AL_EFFECT_EAXREVERB") != 0 + || pDeviceList->IsExtensionSupported(i, ADEXT_EAX2) + || pDeviceList->IsExtensionSupported(i, ADEXT_EAX3) + || pDeviceList->IsExtensionSupported(i, ADEXT_EAX4) + || pDeviceList->IsExtensionSupported(i, ADEXT_EAX5) ) + { + providers[n - 1].bSupportsFx = true; + if ( n < MAXPROVIDERS ) + { + providers[n].id = pDeviceList->GetDeviceName(i); + strcpy(providers[n].name, "OPENAL SOFT EAX"); + providers[n].sources = pDeviceList->GetMaxNumSources(i); + providers[n].bSupportsFx = true; + SampleManager.Set3DProviderName(n, providers[n].name); + n++; + } + + if ( n < MAXPROVIDERS ) + { + providers[n].id = pDeviceList->GetDeviceName(i); + strcpy(providers[n].name, "OPENAL SOFT EAX3"); + providers[n].sources = pDeviceList->GetMaxNumSources(i); + providers[n].bSupportsFx = true; + SampleManager.Set3DProviderName(n, providers[n].name); + n++; + } + } + } + SampleManager.SetNum3DProvidersAvailable(n); + + for(int j=n;jGetDefaultDevice(); + //if ( defaultProvider > MAXPROVIDERS ) + defaultProvider = 0; + } +} + +static void +release_existing() +{ + if ( IsFXSupported() ) + { + if ( alIsEffect(ALEffect) ) + { + alEffecti(ALEffect, AL_EFFECT_TYPE, AL_EFFECT_NULL); + } + + if (alIsAuxiliaryEffectSlot(ALEffectSlot)) + { + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, AL_EFFECT_NULL); + } + } + + DEV("release_existing()\n"); +} + +static bool8 +set_new_provider(int index) +{ + if ( curprovider == index ) + return TRUE; + + curprovider = index; + + release_existing(); + + if ( curprovider != -1 ) + { + DEV("set_new_provider()\n"); + + usingEAX = 0; + usingEAX3 = 0; + _usingEFX = false; + + if ( !strcmp(&providers[index].name[strlen(providers[index].name) - strlen(" EAX3")], " EAX3") + && alcIsExtensionPresent(ALDevice, (ALCchar*)ALC_EXT_EFX_NAME) ) + { + + usingEAX = 1; + usingEAX3 = 1; + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, ALEffect); + EAX_SetAll(&FinishEAX3); + + DEV("EAX3\n"); + } + else if ( alcIsExtensionPresent(ALDevice, (ALCchar*)ALC_EXT_EFX_NAME) ) + { + + if ( !strcmp(&providers[index].name[strlen(providers[index].name) - strlen(" EAX")], " EAX")) + { + usingEAX = 1; + DEV("EAX1\n"); + } + else + { + _usingEFX = true; + DEV("EFX\n"); + } + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, ALEffect); + EAX_SetAll(&EAX30_ORIGINAL_PRESETS[EAX_ENVIRONMENT_CAVE]); + } + + //SampleManager.SetSpeakerConfig(speaker_type); + + if ( IsFXSupported() ) + { + for ( int32 i = 0; i < MAXCHANNELS; i++ ) + aChannel[i].SetReverbMix(ALEffectSlot, 0.0f); + } + + return TRUE; + } + + return FALSE; +} + +static bool8 +IsThisTrackAt16KHz(uint32 track) +{ + return track == STREAMED_SOUND_RADIO_CHAT; +} + +cSampleManager::cSampleManager(void) +{ + ; +} + +cSampleManager::~cSampleManager(void) +{ + +} + +void cSampleManager::SetSpeakerConfig(int32 nConfig) +{ + +} + +uint32 cSampleManager::GetMaximumSupportedChannels(void) +{ + if ( _maxSamples > MAXCHANNELS ) + return MAXCHANNELS; + + return _maxSamples; +} + +uint32 cSampleManager::GetNum3DProvidersAvailable() +{ + return m_nNumberOfProviders; +} + +void cSampleManager::SetNum3DProvidersAvailable(uint32 num) +{ + m_nNumberOfProviders = num; +} + +char *cSampleManager::Get3DProviderName(uint8 id) +{ + return m_aAudioProviders[id]; +} + +void cSampleManager::Set3DProviderName(uint8 id, char *name) +{ + m_aAudioProviders[id] = name; +} + +int8 cSampleManager::GetCurrent3DProviderIndex(void) +{ + return curprovider; +} + +int8 cSampleManager::SetCurrent3DProvider(uint8 nProvider) +{ + int savedprovider = curprovider; + + nProvider = Clamp(nProvider, 0, m_nNumberOfProviders - 1); + + if ( set_new_provider(nProvider) ) + return curprovider; + else if ( savedprovider != -1 && savedprovider < m_nNumberOfProviders && set_new_provider(savedprovider) ) + return curprovider; + else + return curprovider; +} + +static bool8 +_ResolveLink(char const *path, char *out) +{ +#ifdef _WIN32 + size_t len = strlen(path); + if (len < 4 || strcmp(&path[len - 4], ".lnk") != 0) + return FALSE; + + IShellLink* psl; + WIN32_FIND_DATA fd; + char filepath[MAX_PATH]; + + CoInitialize(NULL); + + if (SUCCEEDED( CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl ) )) + { + IPersistFile *ppf; + + if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf))) + { + WCHAR wpath[MAX_PATH]; + + MultiByteToWideChar(CP_ACP, 0, path, -1, wpath, MAX_PATH); + + if (SUCCEEDED(ppf->Load(wpath, STGM_READ))) + { + /* Resolve the link */ + if (SUCCEEDED(psl->Resolve(NULL, SLR_ANY_MATCH|SLR_NO_UI|SLR_NOSEARCH))) + { + strcpy(filepath, path); + + if (SUCCEEDED(psl->GetPath(filepath, MAX_PATH, &fd, SLGP_UNCPRIORITY))) + { + OutputDebugString(fd.cFileName); + + strcpy(out, filepath); + // FIX: Release the objects. Taken from SA. +#ifdef FIX_BUGS + ppf->Release(); + psl->Release(); +#endif + return TRUE; + } + } + } + + ppf->Release(); + } + psl->Release(); + } + + return FALSE; +#else + struct stat sb; + + if (lstat(path, &sb) == -1) { + perror("lstat: "); + return FALSE; + } + + if (S_ISLNK(sb.st_mode)) { + char* linkname = (char*)alloca(sb.st_size + 1); + if (linkname == NULL) { + fprintf(stderr, "insufficient memory\n"); + return FALSE; + } + + if (readlink(path, linkname, sb.st_size + 1) < 0) { + perror("readlink: "); + return FALSE; + } + linkname[sb.st_size] = '\0'; + strcpy(out, linkname); + return TRUE; + } else { + return FALSE; + } +#endif +} + +static void +_FindMP3s(void) +{ + tMP3Entry *pList; + bool8 bShortcut; + bool8 bInitFirstEntry; + HANDLE hFind; + char path[MAX_PATH]; + int total_ms; + WIN32_FIND_DATA fd; + char filepath[MAX_PATH + sizeof(fd.cFileName)]; + + if (getcwd(_mp3DirectoryPath, MAX_PATH) == NULL) { + perror("getcwd: "); + return; + } + + if (strlen(_mp3DirectoryPath) + 1 > MAX_PATH - 10) { + // This is not gonna end well + printf("MP3 folder path is too long, no place left for file names. MP3 finding aborted.\n"); + return; + } + + OutputDebugString("Finding MP3s..."); + strcpy(path, _mp3DirectoryPath); + strcat(path, "\\MP3\\"); + +#if !defined(_WIN32) + char *actualPath = casepath(path); + if (actualPath) { + strcpy(path, actualPath); + free(actualPath); + } +#endif + + strcpy(_mp3DirectoryPath, path); + OutputDebugString(_mp3DirectoryPath); + + strcat(path, "*"); + + hFind = FindFirstFile(path, &fd); + + if ( hFind == INVALID_HANDLE_VALUE ) + { + return; + } + + bShortcut = FALSE; + bInitFirstEntry = TRUE; + + do + { + strcpy(filepath, _mp3DirectoryPath); + strcat(filepath, fd.cFileName); + + if (!strcmp(fd.cFileName, ".") || !strcmp(fd.cFileName, "..")) + continue; + + size_t filepathlen = strlen(filepath); + + if ( bInitFirstEntry ) + { + if (filepathlen > 0) + { + if (_ResolveLink(filepath, filepath)) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + bShortcut = TRUE; + } + else + { + bShortcut = FALSE; + if (filepathlen > MAX_PATH) { + continue; + } + } + if (aStream[0] && aStream[0]->Open(filepath)) + { + total_ms = aStream[0]->GetLengthMS(); + aStream[0]->Close(); + + OutputDebugString(fd.cFileName); + + _pMP3List = new tMP3Entry; + + if (_pMP3List == NULL) + break; + + nNumMP3s = 1; + + strcpy(_pMP3List->aFilename, fd.cFileName); + + _pMP3List->nTrackLength = total_ms; + _pMP3List->pNext = NULL; + + if (bShortcut) + { + _pMP3List->pLinkPath = new char[MAX_PATH + sizeof(fd.cFileName)]; + strcpy(_pMP3List->pLinkPath, filepath); + } + else + { + _pMP3List->pLinkPath = NULL; + } + + pList = _pMP3List; + + bInitFirstEntry = FALSE; + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + OutputDebugString(filepath); + } + } + else + break; + } + else + { + if ( filepathlen > 0 ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + bShortcut = TRUE; + } + else + bShortcut = FALSE; + + if (aStream[0] && aStream[0]->Open(filepath)) + { + total_ms = aStream[0]->GetLengthMS(); + aStream[0]->Close(); + + OutputDebugString(fd.cFileName); + + pList->pNext = new tMP3Entry; + + tMP3Entry *e = pList->pNext; + + if ( e == NULL ) + break; + + pList = pList->pNext; + + strcpy(e->aFilename, fd.cFileName); + e->nTrackLength = total_ms; + e->pNext = NULL; + + if ( bShortcut ) + { + e->pLinkPath = new char [MAX_PATH + sizeof(fd.cFileName)]; + strcpy(e->pLinkPath, filepath); + } + else + { + e->pLinkPath = NULL; + } + + nNumMP3s++; + + OutputDebugString(fd.cFileName); + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + OutputDebugString(filepath); + } + } + } + } while (FindNextFile(hFind, &fd)); + + FindClose(hFind); +} + +static void +_DeleteMP3Entries(void) +{ + tMP3Entry *e = _pMP3List; + + while ( e != NULL ) + { + tMP3Entry *next = e->pNext; + + if ( next == NULL ) + next = NULL; + + if ( e->pLinkPath != NULL ) + { +#ifndef FIX_BUGS + delete e->pLinkPath; // BUG: should be delete [] +#else + delete[] e->pLinkPath; +#endif + e->pLinkPath = NULL; + } + + delete e; + + if ( next ) + e = next; + else + e = NULL; + + nNumMP3s--; + } + + + if ( nNumMP3s != 0 ) + { + OutputDebugString("Not all MP3 entries were deleted"); + nNumMP3s = 0; + } + + _pMP3List = NULL; +} + +static tMP3Entry * +_GetMP3EntryByIndex(uint32 idx) +{ + uint32 n = ( idx < nNumMP3s ) ? idx : 0; + + if ( _pMP3List != NULL ) + { + tMP3Entry *e = _pMP3List; + + for ( uint32 i = 0; i < n; i++ ) + e = e->pNext; + + return e; + + } + + return NULL; +} + +static inline bool8 +_GetMP3PosFromStreamPos(uint32 *pPosition, tMP3Entry **pEntry) +{ + _CurMP3Index = 0; + + for ( *pEntry = _pMP3List; *pEntry != NULL; *pEntry = (*pEntry)->pNext ) + { + if ( *pPosition >= (*pEntry)->nTrackStreamPos + && *pPosition < (*pEntry)->nTrackLength + (*pEntry)->nTrackStreamPos ) + { + *pPosition -= (*pEntry)->nTrackStreamPos; + _CurMP3Pos = *pPosition; + + return TRUE; + } + + _CurMP3Index++; + } + + *pPosition = 0; + *pEntry = _pMP3List; + _CurMP3Pos = 0; + _CurMP3Index = 0; + + return FALSE; +} + +bool8 +cSampleManager::IsMP3RadioChannelAvailable(void) +{ + return nNumMP3s != 0; +} + + +void cSampleManager::ReleaseDigitalHandle(void) +{ + // TODO? alcSuspendContext +} + +void cSampleManager::ReacquireDigitalHandle(void) +{ + // TODO? alcProcessContext +} + +bool8 +cSampleManager::Initialise(void) +{ + if ( _bSampmanInitialised ) + return TRUE; + + EFXInit(); + + for(int i = 0; i < MAX_STREAMS; i++) + aStream[i] = new CStream(ALStreamSources[i], ALStreamBuffers[i]); + + CStream::Initialise(); + + { + for ( int32 i = 0; i < TOTAL_AUDIO_SAMPLES; i++ ) + { + m_aSamples[i].nOffset = 0; + m_aSamples[i].nSize = 0; + m_aSamples[i].nFrequency = 22050; + m_aSamples[i].nLoopStart = 0; + m_aSamples[i].nLoopEnd = -1; + } + + m_nEffectsVolume = MAX_VOLUME; + m_nMusicVolume = MAX_VOLUME; + m_nEffectsFadeVolume = MAX_VOLUME; + m_nMusicFadeVolume = MAX_VOLUME; + + m_nMonoMode = 0; + } + + { + curprovider = -1; + prevprovider = -1; + + _usingEFX = false; + usingEAX =0; + usingEAX3=0; + + _fEffectsLevel = 0.0f; + + _maxSamples = 0; + + ALDevice = NULL; + ALContext = NULL; + } + + { + fpSampleDescHandle = NULL; + fpSampleDataHandle = NULL; + + for ( int32 i = 0; i < MAX_SFX_BANKS; i++ ) + { + gBankLoaded[i] = LOADING_STATUS_NOT_LOADED; + nSampleBankDiscStartOffset[i] = 0; + nSampleBankSize[i] = 0; + nSampleBankMemoryStartAddress[i] = 0; + } + } + + { + for ( int32 i = 0; i < MAX_PEDSFX; i++ ) + { + nPedSlotSfx[i] = NO_SAMPLE; + nPedSlotSfxAddr[i] = 0; + } + + nCurrentPedSlot = 0; + } + + { + for ( int32 i = 0; i < NUM_CHANNELS; i++ ) + nChannelVolume[i] = 0; + } + + add_providers(); + + { + int index = 0; + _maxSamples = Min(MAXCHANNELS, providers[index].sources); + + ALCint attr[] = {ALC_FREQUENCY,MAX_FREQ, + ALC_MONO_SOURCES, MAX_DIGITAL_MIXER_CHANNELS - MAX2DCHANNELS, + ALC_STEREO_SOURCES, MAX2DCHANNELS, + 0, + }; + + ALDevice = alcOpenDevice(providers[index].id); + ASSERT(ALDevice != NULL); + + ALContext = alcCreateContext(ALDevice, attr); + ASSERT(ALContext != NULL); + + alcMakeContextCurrent(ALContext); + + const char* ext=(const char*)alGetString(AL_EXTENSIONS); + ASSERT(strstr(ext,"AL_SOFT_loop_points")!=NULL); + if ( strstr(ext,"AL_SOFT_loop_points")==NULL ) + { + Terminate(); + return FALSE; + } + + alListenerf (AL_GAIN, 1.0f); + alListener3f(AL_POSITION, 0.0f, 0.0f, 0.0f); + alListener3f(AL_VELOCITY, 0.0f, 0.0f, 0.0f); + ALfloat orientation[6] = { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; + alListenerfv(AL_ORIENTATION, orientation); + + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); + + if ( alcIsExtensionPresent(ALDevice, (ALCchar*)ALC_EXT_EFX_NAME) ) + { + _effectsSupported = providers[index].bSupportsFx; + alGenAuxiliaryEffectSlots(1, &ALEffectSlot); + alGenEffects(1, &ALEffect); + } + + alGenSources(MAX_STREAMS*2, ALStreamSources[0]); + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + alGenBuffers(NUM_STREAMBUFFERS, ALStreamBuffers[i]); + alSourcei(ALStreamSources[i][0], AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(ALStreamSources[i][0], AL_POSITION, 0.0f, 0.0f, 0.0f); + alSourcef(ALStreamSources[i][0], AL_GAIN, 1.0f); + alSourcei(ALStreamSources[i][1], AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(ALStreamSources[i][1], AL_POSITION, 0.0f, 0.0f, 0.0f); + alSourcef(ALStreamSources[i][1], AL_GAIN, 1.0f); + } + + CChannel::InitChannels(); + + for ( int32 i = 0; i < MAXCHANNELS; i++ ) + aChannel[i].Init(i); + for ( int32 i = 0; i < MAX2DCHANNELS; i++ ) + aChannel[MAXCHANNELS+i].Init(MAXCHANNELS+i, true); + + if ( IsFXSupported() ) + { + /**/ + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, ALEffect); + /**/ + + for ( int32 i = 0; i < MAXCHANNELS; i++ ) + aChannel[i].SetReverbMix(ALEffectSlot, 0.0f); + } + } + + { + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + nStreamLength[i] = 0; + } + +#ifdef AUDIO_CACHE + FILE *cacheFile = fcaseopen("audio\\sound.cache", "rb"); + if (cacheFile) { + debug("Loadind audio cache (If game crashes around here, then your cache is corrupted, remove audio/sound.cache)\n"); + fread(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); + fclose(cacheFile); + } else + { + debug("Cannot load audio cache\n"); +#endif + + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + { + if(aStream[0] && ( +#ifdef PS2_AUDIO_PATHS + aStream[0]->Open(PS2StreamedNameTable[i], IsThisTrackAt16KHz(i) ? 16000 : 32000) || +#endif + aStream[0]->Open(StreamedNameTable[i], IsThisTrackAt16KHz(i) ? 16000 : 32000))) + { + uint32 tatalms = aStream[0]->GetLengthMS(); + aStream[0]->Close(); + nStreamLength[i] = tatalms; + } else + USERERROR("Can't open '%s'\n", StreamedNameTable[i]); + } +#ifdef AUDIO_CACHE + cacheFile = fcaseopen("audio\\sound.cache", "wb"); + if(cacheFile) { + debug("Saving audio cache\n"); + fwrite(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); + fclose(cacheFile); + } else { + debug("Cannot save audio cache\n"); + } + } +#endif + + { + if ( !InitialiseSampleBanks() ) + { + Terminate(); + return FALSE; + } + + nSampleBankMemoryStartAddress[SFX_BANK_0] = (uintptr)malloc(nSampleBankSize[SFX_BANK_0]); + ASSERT(nSampleBankMemoryStartAddress[SFX_BANK_0] != 0); + + if ( nSampleBankMemoryStartAddress[SFX_BANK_0] == 0 ) + { + Terminate(); + return FALSE; + } + + nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] = (uintptr)malloc(PED_BLOCKSIZE*MAX_PEDSFX); + ASSERT(nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] != 0); + + LoadSampleBank(SFX_BANK_0); + } + + { + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + aStream[i]->Close(); + + nStreamVolume[i] = 100; + nStreamPan[i] = 63; + } + } + + { + _bSampmanInitialised = TRUE; + + if ( defaultProvider >= 0 && defaultProvider < m_nNumberOfProviders ) + { + set_new_provider(defaultProvider); + } + else + { + Terminate(); + return FALSE; + } + } + + { + nNumMP3s = 0; + + _pMP3List = NULL; + + _FindMP3s(); + + if ( nNumMP3s != 0 ) + { + nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] = 0; + + for ( tMP3Entry *e = _pMP3List; e != NULL; e = e->pNext ) + { + e->nTrackStreamPos = nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER]; + nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] += e->nTrackLength; + } + + time_t t = time(NULL); + tm *localtm; + bool8 bUseRandomTable; + + if ( t == -1 ) + bUseRandomTable = TRUE; + else + { + bUseRandomTable = FALSE; + localtm = localtime(&t); + } + + int32 randval; + if ( bUseRandomTable ) + randval = AudioManager.m_anRandomTable[1]; + else + randval = localtm->tm_sec * localtm->tm_min; + + _CurMP3Index = randval % nNumMP3s; + + tMP3Entry *randmp3 = _pMP3List; + for ( int32 i = randval % nNumMP3s; i > 0; --i) + randmp3 = randmp3->pNext; + + if ( bUseRandomTable ) + _CurMP3Pos = AudioManager.m_anRandomTable[0] % randmp3->nTrackLength; + else + { + if ( localtm->tm_sec > 0 ) + { + int32 s = localtm->tm_sec; + _CurMP3Pos = s*s*s*s*s*s*s*s % randmp3->nTrackLength; + } + else + _CurMP3Pos = AudioManager.m_anRandomTable[0] % randmp3->nTrackLength; + } + } + else + _CurMP3Pos = 0; + + _bIsMp3Active = FALSE; + } + + return TRUE; +} + +void +cSampleManager::Terminate(void) +{ + for (int32 i = 0; i < MAX_STREAMS; i++) + aStream[i]->Close(); + + for ( int32 i = 0; i < NUM_CHANNELS; i++ ) + aChannel[i].Term(); + + if ( IsFXSupported() ) + { + if ( alIsEffect(ALEffect) ) + { + alEffecti(ALEffect, AL_EFFECT_TYPE, AL_EFFECT_NULL); + alDeleteEffects(1, &ALEffect); + ALEffect = AL_EFFECT_NULL; + } + + if (alIsAuxiliaryEffectSlot(ALEffectSlot)) + { + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, AL_EFFECT_NULL); + + alDeleteAuxiliaryEffectSlots(1, &ALEffectSlot); + ALEffectSlot = AL_EFFECTSLOT_NULL; + } + } + + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + alDeleteBuffers(NUM_STREAMBUFFERS, ALStreamBuffers[i]); + } + alDeleteSources(MAX_STREAMS*2, ALStreamSources[0]); + + CChannel::DestroyChannels(); + + if ( ALContext ) + { + alcMakeContextCurrent(NULL); + alcSuspendContext(ALContext); + alcDestroyContext(ALContext); + } + if ( ALDevice ) + alcCloseDevice(ALDevice); + + ALDevice = NULL; + ALContext = NULL; + + _fPrevEaxRatioDestination = 0.0f; + _usingEFX = false; + _fEffectsLevel = 0.0f; + + _DeleteMP3Entries(); + + CStream::Terminate(); + + for(int32 i = 0; i < MAX_STREAMS; i++) + delete aStream[i]; + + if ( nSampleBankMemoryStartAddress[SFX_BANK_0] != 0 ) + { + free((void *)nSampleBankMemoryStartAddress[SFX_BANK_0]); + nSampleBankMemoryStartAddress[SFX_BANK_0] = 0; + } + + if ( nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] != 0 ) + { + free((void *)nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS]); + nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] = 0; + } + + _bSampmanInitialised = FALSE; +} + +bool8 cSampleManager::CheckForAnAudioFileOnCD(void) +{ + return TRUE; +} + +char cSampleManager::GetCDAudioDriveLetter(void) +{ + return '\0'; +} + +void +cSampleManager::UpdateEffectsVolume(void) +{ + if ( _bSampmanInitialised ) + { + for ( int32 i = 0; i < NUM_CHANNELS; i++ ) + { + if ( GetChannelUsedFlag(i) ) + { + if ( nChannelVolume[i] != 0 ) + aChannel[i].SetVolume(m_nEffectsFadeVolume*nChannelVolume[i]*m_nEffectsVolume >> 14); + } + } + } +} + +void +cSampleManager::SetEffectsMasterVolume(uint8 nVolume) +{ + m_nEffectsVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicMasterVolume(uint8 nVolume) +{ + m_nMusicVolume = nVolume; +} + +void +cSampleManager::SetEffectsFadeVolume(uint8 nVolume) +{ + m_nEffectsFadeVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicFadeVolume(uint8 nVolume) +{ + m_nMusicFadeVolume = nVolume; +} + +void +cSampleManager::SetMonoMode(uint8 nMode) +{ + m_nMonoMode = nMode; +} + +bool8 +cSampleManager::LoadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS); + + if ( CTimer::GetIsCodePaused() ) + return FALSE; + + if ( MusicManager.IsInitialised() + && MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && nBank != SFX_BANK_0 ) + { + return FALSE; + } + +#ifdef OPUS_SFX + int samplesRead = 0; + int samplesSize = nSampleBankSize[nBank] / 2; + op_pcm_seek(fpSampleDataHandle, 0); + while (samplesSize > 0) { + int size = op_read(fpSampleDataHandle, (opus_int16 *)(nSampleBankMemoryStartAddress[nBank] + samplesRead), samplesSize, NULL); + if (size <= 0) { + // huh? + //assert(0); + break; + } + samplesRead += size*2; + samplesSize -= size; + } +#else + if ( fseek(fpSampleDataHandle, nSampleBankDiscStartOffset[nBank], SEEK_SET) != 0 ) + return FALSE; + + if ( fread((void *)nSampleBankMemoryStartAddress[nBank], 1, nSampleBankSize[nBank], fpSampleDataHandle) != nSampleBankSize[nBank] ) + return FALSE; +#endif + gBankLoaded[nBank] = LOADING_STATUS_LOADED; + + return TRUE; +} + +void +cSampleManager::UnloadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS); + + gBankLoaded[nBank] = LOADING_STATUS_NOT_LOADED; +} + +int8 +cSampleManager::IsSampleBankLoaded(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS); + + return gBankLoaded[nBank]; +} + +uint8 +cSampleManager::IsPedCommentLoaded(uint32 nComment) +{ + ASSERT( nComment < TOTAL_AUDIO_SAMPLES ); + + for ( int32 i = 0; i < _TODOCONST(3); i++ ) + { +#ifdef FIX_BUGS + int8 slot = (int8)nCurrentPedSlot - i - 1; + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#else + uint8 slot = nCurrentPedSlot - i - 1; +#endif + if ( nComment == nPedSlotSfx[slot] ) + return LOADING_STATUS_LOADED; + } + + return LOADING_STATUS_NOT_LOADED; +} + + +int32 +cSampleManager::_GetPedCommentSlot(uint32 nComment) +{ + for (int32 i = 0; i < _TODOCONST(3); i++) + { +#ifdef FIX_BUGS + int8 slot = (int8)nCurrentPedSlot - i - 1; + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#else + uint8 slot = nCurrentPedSlot - i - 1; +#endif + if (nComment == nPedSlotSfx[slot]) + return slot; + } + + return -1; +} + +bool8 +cSampleManager::LoadPedComment(uint32 nComment) +{ + ASSERT( nComment < TOTAL_AUDIO_SAMPLES ); + + if ( CTimer::GetIsCodePaused() ) + return FALSE; + + // no talking peds during cutsenes or the game end + if ( MusicManager.IsInitialised() ) + { + switch ( MusicManager.GetMusicMode() ) + { + case MUSICMODE_CUTSCENE: + { + return FALSE; + + break; + } + + case MUSICMODE_FRONTEND: + { + if ( MusicManager.GetNextTrack() == STREAMED_SOUND_GAME_COMPLETED ) + return FALSE; + + break; + } + } + } + +#ifdef OPUS_SFX + int samplesRead = 0; + int samplesSize = m_aSamples[nComment].nSize / 2; + op_pcm_seek(fpSampleDataHandle, m_aSamples[nComment].nOffset / 2); + while (samplesSize > 0) { + int size = op_read(fpSampleDataHandle, (opus_int16 *)(nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] + PED_BLOCKSIZE * nCurrentPedSlot + samplesRead), + samplesSize, NULL); + if (size <= 0) { + return FALSE; + } + samplesRead += size * 2; + samplesSize -= size; + } +#else + if ( fseek(fpSampleDataHandle, m_aSamples[nComment].nOffset, SEEK_SET) != 0 ) + return FALSE; + + if ( fread((void *)(nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] + PED_BLOCKSIZE*nCurrentPedSlot), 1, m_aSamples[nComment].nSize, fpSampleDataHandle) != m_aSamples[nComment].nSize ) + return FALSE; + +#endif + nPedSlotSfx[nCurrentPedSlot] = nComment; + + if ( ++nCurrentPedSlot >= MAX_PEDSFX ) + nCurrentPedSlot = 0; + + return TRUE; +} + +int32 +cSampleManager::GetBankContainingSound(uint32 offset) +{ + if ( offset >= BankStartOffset[SFX_BANK_PED_COMMENTS] ) + return SFX_BANK_PED_COMMENTS; + + if ( offset >= BankStartOffset[SFX_BANK_0] ) + return SFX_BANK_0; + + return INVALID_SFX_BANK; +} + +uint32 +cSampleManager::GetSampleBaseFrequency(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nFrequency; +} + +uint32 +cSampleManager::GetSampleLoopStartOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nLoopStart; +} + +int32 +cSampleManager::GetSampleLoopEndOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nLoopEnd; +} + +uint32 +cSampleManager::GetSampleLength(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nSize / sizeof(uint16); +} + +bool8 cSampleManager::UpdateReverb(void) +{ + if ( !usingEAX && !_usingEFX ) + return FALSE; + + if ( AudioManager.m_FrameCounter & 15 ) + return FALSE; + +#ifdef AUDIO_REFLECTIONS + float y = AudioManager.m_afReflectionsDistances[REFLECTION_TOP] + AudioManager.m_afReflectionsDistances[REFLECTION_BOTTOM]; + float x = AudioManager.m_afReflectionsDistances[REFLECTION_LEFT] + AudioManager.m_afReflectionsDistances[REFLECTION_RIGHT]; + float z = AudioManager.m_afReflectionsDistances[REFLECTION_UP]; +#else + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; +#endif + + float normy = norm(y, 5.0f, 40.0f); + float normx = norm(x, 5.0f, 40.0f); + float normz = norm(z, 5.0f, 40.0f); + + #define ZR(v, a, b) (((v)==0)?(a):(b)) + #define CALCRATIO(x,y,z,min,max,val) (ZR(y, ZR(x, ZR(z, min, max), min), ZR(x, ZR(z, min, max), ZR(z, min, val)))) + + float fRatio = CALCRATIO(normx, normy, normz, 0.3f, 0.5f, (normy+normx+normz)/3.0f); + + #undef CALCRATIO + #undef ZR + + fRatio = Clamp(fRatio, usingEAX3==1 ? 0.0f : 0.30f, 1.0f); + + if ( fRatio == _fPrevEaxRatioDestination ) + return FALSE; + +#ifdef JUICY_OAL + if ( usingEAX3 || _usingEFX ) +#else + if ( usingEAX3 ) +#endif + { + if ( EAX3ListenerInterpolate(&StartEAX3, &FinishEAX3, fRatio, &EAX3Params, false) ) + { + EAX_SetAll(&EAX3Params); + + /* + if ( IsFXSupported() ) + { + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, ALEffect); + + for ( int32 i = 0; i < MAXCHANNELS; i++ ) + aChannel[i].UpdateReverb(ALEffectSlot); + } + */ + + _fEffectsLevel = 1.0f - fRatio * 0.5f; + } + } + else + { + if ( _usingEFX ) + _fEffectsLevel = (1.0f - fRatio) * 0.4f; + else + _fEffectsLevel = (1.0f - fRatio) * 0.7f; + } + + _fPrevEaxRatioDestination = fRatio; + + return TRUE; +} + +void +cSampleManager::SetChannelReverbFlag(uint32 nChannel, bool8 nReverbFlag) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + if ( usingEAX || _usingEFX ) + { + if ( IsFXSupported() ) + { + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, ALEffect); + + if ( nReverbFlag != FALSE ) + aChannel[nChannel].SetReverbMix(ALEffectSlot, _fEffectsLevel); + else + aChannel[nChannel].SetReverbMix(ALEffectSlot, 0.0f); + } + } +} + +bool8 +cSampleManager::InitialiseChannel(uint32 nChannel, uint32 nSfx, uint8 nBank) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + uintptr addr; + + if ( nSfx < SAMPLEBANK_MAX ) + { + if ( !IsSampleBankLoaded(nBank) ) + return FALSE; + + addr = nSampleBankMemoryStartAddress[nBank] + m_aSamples[nSfx].nOffset - m_aSamples[BankStartOffset[nBank]].nOffset; + } + else + { + int32 i; + for ( i = 0; i < _TODOCONST(3); i++ ) + { + int32 slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nSfx == nPedSlotSfx[slot] ) + { + addr = (nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] + PED_BLOCKSIZE * slot); + break; + } + } + + if (i == _TODOCONST(3)) + return FALSE; + } + + if ( GetChannelUsedFlag(nChannel) ) + { + TRACE("Stopping channel %d - really!!!", nChannel); + StopChannel(nChannel); + } + + aChannel[nChannel].Reset(); + if ( aChannel[nChannel].HasSource() ) + { + aChannel[nChannel].SetSampleData ((void*)addr, m_aSamples[nSfx].nSize, m_aSamples[nSfx].nFrequency); + aChannel[nChannel].SetLoopPoints (0, -1); + aChannel[nChannel].SetPitch (1.0f); + return TRUE; + } + + return FALSE; +} + +void +cSampleManager::SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume) +{ + ASSERT( nChannel < MAXCHANNELS ); + + uint32 vol = nVolume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + nChannelVolume[nChannel] = vol; + + // reduce channel volume when JB.MP3 or S4_BDBD.MP3 playing + if ( MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && MusicManager.GetNextTrack() != STREAMED_SOUND_NEWS_INTRO + && MusicManager.GetNextTrack() != STREAMED_SOUND_CUTSCENE_SAL4_BDBD ) + { + nChannelVolume[nChannel] = vol / 4; + } + + // no idea, does this one looks like a bug or it's SetChannelVolume ? + aChannel[nChannel].SetVolume(m_nEffectsFadeVolume*nChannelVolume[nChannel]*m_nEffectsVolume >> 14); +} + +void +cSampleManager::SetChannel3DPosition(uint32 nChannel, float fX, float fY, float fZ) +{ + ASSERT( nChannel < MAXCHANNELS ); + + aChannel[nChannel].SetPosition(-fX, fY, fZ); +} + +void +cSampleManager::SetChannel3DDistances(uint32 nChannel, float fMax, float fMin) +{ + ASSERT( nChannel < MAXCHANNELS ); + aChannel[nChannel].SetDistances(fMax, fMin); +} + +void +cSampleManager::SetChannelVolume(uint32 nChannel, uint32 nVolume) +{ + ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < NUM_CHANNELS ); + + if ( nChannel == CHANNEL_POLICE_RADIO ) + { + uint32 vol = nVolume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + nChannelVolume[nChannel] = vol; + + // reduce the volume for JB.MP3 and S4_BDBD.MP3 + if ( MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && MusicManager.GetNextTrack() != STREAMED_SOUND_NEWS_INTRO + && MusicManager.GetNextTrack() != STREAMED_SOUND_CUTSCENE_SAL4_BDBD ) + { + nChannelVolume[nChannel] = vol / 4; + } + + aChannel[nChannel].SetVolume(m_nEffectsFadeVolume*vol*m_nEffectsVolume >> 14); + } +} + +void +cSampleManager::SetChannelPan(uint32 nChannel, uint32 nPan) +{ + ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < NUM_CHANNELS ); + + if ( nChannel == CHANNEL_POLICE_RADIO ) + { + aChannel[nChannel].SetPan(nPan); + } +} + +void +cSampleManager::SetChannelFrequency(uint32 nChannel, uint32 nFreq) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + aChannel[nChannel].SetCurrentFreq(nFreq); +} + +void +cSampleManager::SetChannelLoopPoints(uint32 nChannel, uint32 nLoopStart, int32 nLoopEnd) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + aChannel[nChannel].SetLoopPoints(nLoopStart / (DIGITALBITS / 8), nLoopEnd / (DIGITALBITS / 8)); +} + +void +cSampleManager::SetChannelLoopCount(uint32 nChannel, uint32 nLoopCount) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + aChannel[nChannel].SetLoopCount(nLoopCount); +} + +bool8 +cSampleManager::GetChannelUsedFlag(uint32 nChannel) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + return aChannel[nChannel].IsUsed(); +} + +void +cSampleManager::StartChannel(uint32 nChannel) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + aChannel[nChannel].Start(); +} + +void +cSampleManager::StopChannel(uint32 nChannel) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + aChannel[nChannel].Stop(); +} + +void +cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + if ( nFile < TOTAL_STREAMED_SOUNDS ) + { + CStream *stream = aStream[nStream]; + + stream->Close(); + +#ifdef PS2_AUDIO_PATHS + if(!stream->Open(PS2StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000)) +#endif + stream->Open(StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + if ( !stream->Setup() ) + { + stream->Close(); + } + } +} + +void +cSampleManager::PauseStream(bool8 nPauseFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + stream->SetPause(nPauseFlag != FALSE); + } +} + +void +cSampleManager::StartPreloadedStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + stream->Start(); + } +} + +bool8 +cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) +{ + uint32 i = 0; + uint32 position = nPos; + char filename[MAX_PATH]; + + if ( nFile >= TOTAL_STREAMED_SOUNDS ) + return FALSE; + + aStream[nStream]->Close(); + + if ( nFile == STREAMED_SOUND_RADIO_MP3_PLAYER ) + { + do + { + // Switched to MP3 player just now + if ( !_bIsMp3Active && i == 0 ) + { + if ( nPos > nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] ) + position = 0; + tMP3Entry *e = _pMP3List; + + // Try to continue from previous song, if already started + if(!_GetMP3PosFromStreamPos(&position, &e) && !e) { + nFile = 0; + CStream *stream = aStream[nStream]; +#ifdef PS2_AUDIO_PATHS + if(!stream->Open(PS2StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000)) +#endif + stream->Open(StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + if ( stream->Setup() ) { + if (position != 0) + stream->SetPosMS(position); + + stream->Start(); + + return TRUE; + } else { + stream->Close(); + } + return FALSE; + + } else { + if (e->pLinkPath != NULL) + aStream[nStream]->Open(e->pLinkPath, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + else { + strcpy(filename, _mp3DirectoryPath); + strcat(filename, e->aFilename); + + aStream[nStream]->Open(filename); + } + + if (aStream[nStream]->Setup()) { + if (position != 0) + aStream[nStream]->SetPosMS(position); + + aStream[nStream]->Start(); + + _bIsMp3Active = TRUE; + return TRUE; + } else { + aStream[nStream]->Close(); + } + // fall through, start playing from another song + } + } else { + if(++_CurMP3Index >= nNumMP3s) _CurMP3Index = 0; + + _CurMP3Pos = 0; + + tMP3Entry *mp3 = _GetMP3EntryByIndex(_CurMP3Index); + if ( !mp3 ) + { + mp3 = _pMP3List; + if ( !_pMP3List ) + { + nFile = 0; + _bIsMp3Active = FALSE; + CStream *stream = aStream[nStream]; +#ifdef PS2_AUDIO_PATHS + if(!stream->Open(PS2StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000)) +#endif + stream->Open(StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + + if (stream->Setup()) { + if (position != 0) + stream->SetPosMS(position); + + stream->Start(); + + return TRUE; + } else { + stream->Close(); + } + return FALSE; + } + } + if (mp3->pLinkPath != NULL) + aStream[nStream]->Open(mp3->pLinkPath, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + else { + strcpy(filename, _mp3DirectoryPath); + strcat(filename, mp3->aFilename); + aStream[nStream]->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + } + + if (aStream[nStream]->Setup()) { + aStream[nStream]->Start(); +#ifdef FIX_BUGS + _bIsMp3Active = TRUE; +#endif + return TRUE; + } else { + aStream[nStream]->Close(); + } + + } + _bIsMp3Active = FALSE; + } + while ( ++i < nNumMP3s ); + position = 0; + nFile = 0; + } + CStream *stream = aStream[nStream]; +#ifdef PS2_AUDIO_PATHS + if(!stream->Open(PS2StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000)) +#endif + stream->Open(StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + + if ( stream->Setup() ) { + if (position != 0) + stream->SetPosMS(position); + + stream->Start(); + + return TRUE; + } else { + stream->Close(); + } + return FALSE; +} + +void +cSampleManager::StopStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + CStream *stream = aStream[nStream]; + + stream->Close(); + + if ( nStream == 0 ) + _bIsMp3Active = FALSE; +} + +int32 +cSampleManager::GetStreamedFilePosition(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + if ( _bIsMp3Active ) + { + tMP3Entry *mp3 = _GetMP3EntryByIndex(_CurMP3Index); + + if ( mp3 != NULL ) + { + return stream->GetPosMS() + mp3->nTrackStreamPos; + } + else + return 0; + } + else + { + return stream->GetPosMS(); + } + } + + return 0; +} + +void +cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, uint8 nEffectFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + if ( nVolume > MAX_VOLUME ) + nVolume = MAX_VOLUME; + + if ( nPan > MAX_VOLUME ) + nPan = MAX_VOLUME; + + nStreamVolume[nStream] = nVolume; + nStreamPan [nStream] = nPan; + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + if ( nEffectFlag ) + stream->SetVolume(m_nEffectsFadeVolume*nVolume*m_nEffectsVolume >> 14); + else + stream->SetVolume(m_nMusicFadeVolume*nVolume*m_nMusicVolume >> 14); + + stream->SetPan(nPan); + } +} + +int32 +cSampleManager::GetStreamedFileLength(uint8 nStream) +{ + ASSERT( nStream < TOTAL_STREAMED_SOUNDS ); + + return nStreamLength[nStream]; +} + +bool8 +cSampleManager::IsStreamPlaying(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + if ( stream->IsPlaying() ) + return TRUE; + } + + return FALSE; +} + +void +cSampleManager::Service(void) +{ + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + CStream *stream = aStream[i]; + + if ( stream->IsOpened() ) + stream->Update(); + } + int refCount = CChannel::channelsThatNeedService; + for ( int32 i = 0; refCount && i < NUM_CHANNELS; i++ ) + { + if ( aChannel[i].Update() ) + refCount--; + } +} + +bool8 +cSampleManager::InitialiseSampleBanks(void) +{ + int32 nBank = SFX_BANK_0; + + fpSampleDescHandle = fcaseopen(SampleBankDescFilename, "rb"); + if ( fpSampleDescHandle == NULL ) + return FALSE; +#ifndef OPUS_SFX + fpSampleDataHandle = fcaseopen(SampleBankDataFilename, "rb"); + if ( fpSampleDataHandle == NULL ) + { + fclose(fpSampleDescHandle); + fpSampleDescHandle = NULL; + + return FALSE; + } + + fseek(fpSampleDataHandle, 0, SEEK_END); + int32 _nSampleDataEndOffset = ftell(fpSampleDataHandle); + rewind(fpSampleDataHandle); +#else + int e; + fpSampleDataHandle = op_open_file(SampleBankDataFilename, &e); +#endif + fread(m_aSamples, sizeof(tSample), TOTAL_AUDIO_SAMPLES, fpSampleDescHandle); +#ifdef OPUS_SFX + int32 _nSampleDataEndOffset = m_aSamples[TOTAL_AUDIO_SAMPLES - 1].nOffset + m_aSamples[TOTAL_AUDIO_SAMPLES - 1].nSize; +#endif + fclose(fpSampleDescHandle); + fpSampleDescHandle = NULL; + + for ( int32 i = 0; i < TOTAL_AUDIO_SAMPLES; i++ ) + { +#ifdef FIX_BUGS + if (nBank >= MAX_SFX_BANKS) break; +#endif + if ( BankStartOffset[nBank] == BankStartOffset[SFX_BANK_0] + i ) + { + nSampleBankDiscStartOffset[nBank] = m_aSamples[i].nOffset; + nBank++; + } + } + + nSampleBankSize[SFX_BANK_0] = nSampleBankDiscStartOffset[SFX_BANK_PED_COMMENTS] - nSampleBankDiscStartOffset[SFX_BANK_0]; + nSampleBankSize[SFX_BANK_PED_COMMENTS] = _nSampleDataEndOffset - nSampleBankDiscStartOffset[SFX_BANK_PED_COMMENTS]; + + return TRUE; +} +#endif diff --git a/src/audio/soundlist.h b/src/audio/soundlist.h new file mode 100644 index 0000000..5f3f752 --- /dev/null +++ b/src/audio/soundlist.h @@ -0,0 +1,303 @@ +#pragma once + +enum eSound +{ + SOUND_CAR_DOOR_CLOSE_BONNET = 0, + SOUND_CAR_DOOR_CLOSE_BUMPER, + SOUND_CAR_DOOR_CLOSE_FRONT_LEFT, + SOUND_CAR_DOOR_CLOSE_FRONT_RIGHT, + SOUND_CAR_DOOR_CLOSE_BACK_LEFT, + SOUND_CAR_DOOR_CLOSE_BACK_RIGHT, + SOUND_CAR_DOOR_OPEN_BONNET, + SOUND_CAR_DOOR_OPEN_BUMPER, + SOUND_CAR_DOOR_OPEN_FRONT_LEFT, + SOUND_CAR_DOOR_OPEN_FRONT_RIGHT, + SOUND_CAR_DOOR_OPEN_BACK_LEFT, + SOUND_CAR_DOOR_OPEN_BACK_RIGHT, + SOUND_CAR_WINDSHIELD_CRACK, + SOUND_CAR_JUMP, + SOUND_E, + SOUND_F, + SOUND_CAR_ENGINE_START, + SOUND_CAR_LIGHT_BREAK, + SOUND_CAR_HYDRAULIC_1, + SOUND_CAR_HYDRAULIC_2, + SOUND_CAR_HYDRAULIC_3, + SOUND_CAR_JERK, + SOUND_CAR_SPLASH, + SOUND_BOAT_SLOWDOWN, + SOUND_TRAIN_DOOR_CLOSE, + SOUND_TRAIN_DOOR_OPEN, + SOUND_CAR_TANK_TURRET_ROTATE, + SOUND_CAR_BOMB_TICK, + SOUND_PLANE_ON_GROUND, + SOUND_STEP_START, + SOUND_STEP_END, + SOUND_FALL_LAND, + SOUND_FALL_COLLAPSE, + SOUND_FIGHT_PUNCH_33, + SOUND_FIGHT_KICK_34, + SOUND_FIGHT_HEADBUTT_35, + SOUND_FIGHT_PUNCH_36, + SOUND_FIGHT_PUNCH_37, + SOUND_FIGHT_CLOSE_PUNCH_38, + SOUND_FIGHT_PUNCH_39, + SOUND_FIGHT_PUNCH_OR_KICK_BELOW_40, + SOUND_FIGHT_PUNCH_41, + SOUND_FIGHT_PUNCH_FROM_BEHIND_42, + SOUND_FIGHT_KNEE_OR_KICK_43, + SOUND_FIGHT_KICK_44, + SOUND_2D, + SOUND_WEAPON_BAT_ATTACK, + SOUND_WEAPON_SHOT_FIRED, + SOUND_WEAPON_RELOAD, + SOUND_WEAPON_AK47_BULLET_ECHO, + SOUND_WEAPON_UZI_BULLET_ECHO, + SOUND_WEAPON_M16_BULLET_ECHO, + SOUND_WEAPON_FLAMETHROWER_FIRE, + SOUND_WEAPON_SNIPER_SHOT_NO_ZOOM, + SOUND_WEAPON_ROCKET_SHOT_NO_ZOOM, + SOUND_WEAPON_HIT_PED, + SOUND_WEAPON_HIT_VEHICLE, + SOUND_GARAGE_NO_MONEY, + SOUND_GARAGE_BAD_VEHICLE, + SOUND_GARAGE_OPENING, + SOUND_GARAGE_BOMB_ALREADY_SET, + SOUND_GARAGE_BOMB1_SET, + SOUND_GARAGE_BOMB2_SET, + SOUND_GARAGE_BOMB3_SET, + SOUND_40, + SOUND_41, + SOUND_GARAGE_VEHICLE_DECLINED, + SOUND_GARAGE_VEHICLE_ACCEPTED, + SOUND_GARAGE_DOOR_CLOSED, + SOUND_GARAGE_DOOR_OPENED, + SOUND_CRANE_PICKUP, + SOUND_PICKUP_WEAPON_BOUGHT, + SOUND_PICKUP_WEAPON, + SOUND_PICKUP_HEALTH, + SOUND_PICKUP_ERROR, + SOUND_4B, + SOUND_PICKUP_ADRENALINE, + SOUND_PICKUP_ARMOUR, + SOUND_PICKUP_BONUS, + SOUND_PICKUP_MONEY, + SOUND_PICKUP_HIDDEN_PACKAGE, + SOUND_PICKUP_PACMAN_PILL, + SOUND_PICKUP_PACMAN_PACKAGE, + SOUND_PICKUP_FLOAT_PACKAGE, + SOUND_BOMB_TIMED_ACTIVATED, + SOUND_55, + SOUND_BOMB_ONIGNITION_ACTIVATED, + SOUND_BOMB_TICK, + SOUND_RAMPAGE_START, + SOUND_RAMPAGE_ONGOING, + SOUND_RAMPAGE_PASSED, + SOUND_RAMPAGE_FAILED, + SOUND_RAMPAGE_KILL, + SOUND_RAMPAGE_CAR_BLOWN, + SOUND_EVIDENCE_PICKUP, + SOUND_UNLOAD_GOLD, + SOUND_PAGER, + SOUND_PED_DEATH, + SOUND_PED_DAMAGE, + SOUND_PED_HIT, + SOUND_PED_LAND, + SOUND_PED_BULLET_HIT, + SOUND_PED_BOMBER, + SOUND_PED_BURNING, + SOUND_PED_ARREST_FBI, + SOUND_PED_ARREST_SWAT, + SOUND_PED_ARREST_COP, + SOUND_PED_HELI_PLAYER_FOUND, + SOUND_PED_HANDS_UP, + SOUND_PED_HANDS_COWER, + SOUND_PED_FLEE_SPRINT, + SOUND_PED_CAR_JACKING, + SOUND_PED_MUGGING, + SOUND_PED_CAR_JACKED, + SOUND_PED_ROBBED, + SOUND_PED_TAXI_WAIT, + SOUND_PED_ATTACK, + SOUND_PED_DEFEND, + SOUND_PED_PURSUIT_ARMY, + SOUND_PED_PURSUIT_FBI, + SOUND_PED_PURSUIT_SWAT, + SOUND_PED_PURSUIT_COP, + SOUND_PED_HEALING, + SOUND_PED_7B, + SOUND_PED_LEAVE_VEHICLE, + SOUND_PED_EVADE, + SOUND_PED_FLEE_RUN, + SOUND_PED_ANNOYED_DRIVER, + SOUND_PED_SOLICIT, + SOUND_PED_EXTINGUISHING_FIRE, + SOUND_PED_WAIT_DOUBLEBACK, + SOUND_PED_CHAT_SEXY, + SOUND_PED_CHAT_EVENT, + SOUND_PED_CHAT, + SOUND_PED_BODYCAST_HIT, + SOUND_PED_TAXI_CALL, + SOUND_INJURED_PED_MALE_OUCH, + SOUND_INJURED_PED_FEMALE, + SOUND_INJURED_PED_MALE_PRISON, + SOUND_RACE_START_3, + SOUND_RACE_START_2, + SOUND_RACE_START_1, + SOUND_RACE_START_GO, + SOUND_SPLASH, + SOUND_WATER_FALL, + SOUND_SPLATTER, + SOUND_CAR_PED_COLLISION, + SOUND_CLOCK_TICK, + SOUND_PART_MISSION_COMPLETE, + SOUND_FRONTEND_MENU_STARTING, + SOUND_FRONTEND_MENU_NEW_PAGE, + SOUND_FRONTEND_MENU_NAVIGATION, + SOUND_FRONTEND_MENU_SETTING_CHANGE, + SOUND_FRONTEND_MENU_BACK, + SOUND_FRONTEND_STEREO, + SOUND_FRONTEND_MONO, + SOUND_FRONTEND_AUDIO_TEST, + SOUND_FRONTEND_FAIL, + SOUND_FRONTEND_RADIO_TURN_OFF, + SOUND_FRONTEND_RADIO_CHANGE, + SOUND_HUD, + SOUND_AMMUNATION_WELCOME_1, + SOUND_AMMUNATION_WELCOME_2, + SOUND_AMMUNATION_WELCOME_3, + SOUND_LIGHTNING, + SOUND_A5, + SOUND_TOTAL_SOUNDS, + SOUND_NO_SOUND, +}; + + +enum eScriptSounds { + SCRIPT_SOUND_0 = 0, + SCRIPT_SOUND_1, + SCRIPT_SOUND_2, + SCRIPT_SOUND_3, + SCRIPT_SOUND_PARTY_1_LOOP_S, + SCRIPT_SOUND_PARTY_1_LOOP_L, + SCRIPT_SOUND_PARTY_2_LOOP_S, + SCRIPT_SOUND_PARTY_2_LOOP_L, + SCRIPT_SOUND_PARTY_3_LOOP_S, + SCRIPT_SOUND_PARTY_3_LOOP_L, + SCRIPT_SOUND_PARTY_4_LOOP_S, + SCRIPT_SOUND_PARTY_4_LOOP_L, + SCRIPT_SOUND_PARTY_5_LOOP_S, + SCRIPT_SOUND_PARTY_5_LOOP_L, + SCRIPT_SOUND_PARTY_6_LOOP_S, + SCRIPT_SOUND_PARTY_6_LOOP_L, + SCRIPT_SOUND_PARTY_7_LOOP_S, + SCRIPT_SOUND_PARTY_7_LOOP_L, + SCRIPT_SOUND_PARTY_8_LOOP_S, + SCRIPT_SOUND_PARTY_8_LOOP_L, + SCRIPT_SOUND_PARTY_9_LOOP_S, + SCRIPT_SOUND_PARTY_9_LOOP_L, + SCRIPT_SOUND_PARTY_10_LOOP_S, + SCRIPT_SOUND_PARTY_10_LOOP_L, + SCRIPT_SOUND_PARTY_11_LOOP_S, + SCRIPT_SOUND_PARTY_11_LOOP_L, + SCRIPT_SOUND_PARTY_12_LOOP_S, + SCRIPT_SOUND_PARTY_12_LOOP_L, + SCRIPT_SOUND_PARTY_13_LOOP_S, + SCRIPT_SOUND_PARTY_13_LOOP_L, + SCRIPT_SOUND_STRIP_CLUB_LOOP_1_S, + SCRIPT_SOUND_STRIP_CLUB_LOOP_1_L, + SCRIPT_SOUND_STRIP_CLUB_LOOP_2_S, + SCRIPT_SOUND_STRIP_CLUB_LOOP_2_L, + SCRIPT_SOUND_WORK_SHOP_LOOP_S, + SCRIPT_SOUND_WORK_SHOP_LOOP_L, + SCRIPT_SOUND_SAWMILL_LOOP_S, + SCRIPT_SOUND_SAWMILL_LOOP_L, + SCRIPT_SOUND_DOG_FOOD_FACTORY_S, + SCRIPT_SOUND_DOG_FOOD_FACTORY_L, + SCRIPT_SOUND_LAUNDERETTE_LOOP_S, + SCRIPT_SOUND_LAUNDERETTE_LOOP_L, + SCRIPT_SOUND_CHINATOWN_RESTAURANT_S, + SCRIPT_SOUND_CHINATOWN_RESTAURANT_L, + SCRIPT_SOUND_CIPRIANI_RESAURANT_S, + SCRIPT_SOUND_CIPRIANI_RESAURANT_L, + SCRIPT_SOUND_46_S, + SCRIPT_SOUND_47_L, + SCRIPT_SOUND_MARCO_BISTRO_S, + SCRIPT_SOUND_MARCO_BISTRO_L, + SCRIPT_SOUND_AIRPORT_LOOP_S, + SCRIPT_SOUND_AIRPORT_LOOP_L, + SCRIPT_SOUND_SHOP_LOOP_S, + SCRIPT_SOUND_SHOP_LOOP_L, + SCRIPT_SOUND_CINEMA_LOOP_S, + SCRIPT_SOUND_CINEMA_LOOP_L, + SCRIPT_SOUND_DOCKS_LOOP_S, + SCRIPT_SOUND_DOCKS_LOOP_L, + SCRIPT_SOUND_HOME_LOOP_S, + SCRIPT_SOUND_HOME_LOOP_L, + SCRIPT_SOUND_FRANKIE_PIANO, + SCRIPT_SOUND_PARTY_1_LOOP, + SCRIPT_SOUND_PORN_CINEMA_1_S, + SCRIPT_SOUND_PORN_CINEMA_1_L, + SCRIPT_SOUND_PORN_CINEMA_2_S, + SCRIPT_SOUND_PORN_CINEMA_2_L, + SCRIPT_SOUND_PORN_CINEMA_3_S, + SCRIPT_SOUND_PORN_CINEMA_3_L, + SCRIPT_SOUND_BANK_ALARM_LOOP_S, + SCRIPT_SOUND_BANK_ALARM_LOOP_L, + SCRIPT_SOUND_POLICE_BALL_LOOP_S, + SCRIPT_SOUND_POLICE_BALL_LOOP_L, + SCRIPT_SOUND_RAVE_LOOP_INDUSTRIAL_S, + SCRIPT_SOUND_RAVE_LOOP_INDUSTRIAL_L, + SCRIPT_SOUND_74, + SCRIPT_SOUND_75, + SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_S, + SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_L, + SCRIPT_SOUND_INJURED_PED_MALE_OUCH_S, + SCRIPT_SOUND_INJURED_PED_MALE_OUCH_L, + SCRIPT_SOUND_INJURED_PED_FEMALE_OUCH_S, + SCRIPT_SOUND_INJURED_PED_FEMALE_OUCH_L, + SCRIPT_SOUND_EVIDENCE_PICKUP, + SCRIPT_SOUND_UNLOAD_GOLD, + SCRIPT_SOUND_RAVE_1_LOOP_S, + SCRIPT_SOUND_RAVE_1_LOOP_L, + SCRIPT_SOUND_RAVE_2_LOOP_S, + SCRIPT_SOUND_RAVE_2_LOOP_L, + SCRIPT_SOUND_RAVE_3_LOOP_S, + SCRIPT_SOUND_RAVE_3_LOOP_L, + SCRIPT_SOUND_MISTY_SEX_S, + SCRIPT_SOUND_MISTY_SEX_L, + SCRIPT_SOUND_GATE_START_CLUNK, + SCRIPT_SOUND_GATE_STOP_CLUNK, + SCRIPT_SOUND_PART_MISSION_COMPLETE, + SCRIPT_SOUND_CHUNKY_RUN_SHOUT, + SCRIPT_SOUND_SECURITY_GUARD_AWAY_SHOUT, + SCRIPT_SOUND_RACE_START_3, + SCRIPT_SOUND_RACE_START_2, + SCRIPT_SOUND_RACE_START_1, + SCRIPT_SOUND_RACE_START_GO, + SCRIPT_SOUND_SWAT_PED_SHOUT, + SCRIPT_SOUND_PRETEND_FIRE_LOOP, + SCRIPT_SOUND_AMMUNATION_CHAT_1, + SCRIPT_SOUND_AMMUNATION_CHAT_2, + SCRIPT_SOUND_AMMUNATION_CHAT_3, + SCRIPT_SOUND_BULLET_HIT_GROUND_1, + SCRIPT_SOUND_BULLET_HIT_GROUND_2, + SCRIPT_SOUND_BULLET_HIT_GROUND_3, + SCRIPT_SOUND_BULLET_HIT_WATER, // no sound + SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_1, + SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_2, + SCRIPT_SOUND_PAYPHONE_RINGING, + SCRIPT_SOUND_113, + SCRIPT_SOUND_GLASS_BREAK_L, + SCRIPT_SOUND_GLASS_BREAK_S, + SCRIPT_SOUND_GLASS_CRACK, + SCRIPT_SOUND_GLASS_LIGHT_BREAK, + SCRIPT_SOUND_BOX_DESTROYED_1, + SCRIPT_SOUND_BOX_DESTROYED_2, + SCRIPT_SOUND_METAL_COLLISION, + SCRIPT_SOUND_TIRE_COLLISION, + SCRIPT_SOUND_GUNSHELL_DROP, + SCRIPT_SOUND_GUNSHELL_DROP_SOFT, + SCRIPT_SOUND_TOTAL, + SCRIPT_SOUND_INVALID, +}; diff --git a/src/buildings/Building.cpp b/src/buildings/Building.cpp new file mode 100644 index 0000000..e4475ae --- /dev/null +++ b/src/buildings/Building.cpp @@ -0,0 +1,22 @@ +#include "common.h" + +#include "Building.h" +#include "Streaming.h" +#include "Pools.h" + +void *CBuilding::operator new(size_t sz) throw() { return CPools::GetBuildingPool()->New(); } +void CBuilding::operator delete(void *p, size_t sz) throw() { CPools::GetBuildingPool()->Delete((CBuilding*)p); } + +void +CBuilding::ReplaceWithNewModel(int32 id) +{ + DeleteRwObject(); + + if (CModelInfo::GetModelInfo(m_modelIndex)->GetNumRefs() == 0) + CStreaming::RemoveModel(m_modelIndex); + m_modelIndex = id; + + if(bIsBIGBuilding) + if(m_level == LEVEL_GENERIC || m_level == CGame::currLevel) + CStreaming::RequestModel(id, STREAMFLAGS_DONT_REMOVE); +} diff --git a/src/buildings/Building.h b/src/buildings/Building.h new file mode 100644 index 0000000..94e66c8 --- /dev/null +++ b/src/buildings/Building.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Entity.h" + +class CBuilding : public CEntity +{ +public: + CBuilding(void) { + m_type = ENTITY_TYPE_BUILDING; + bUsesCollision = true; + } + static void *operator new(size_t) throw(); + static void operator delete(void*, size_t) throw(); + + void ReplaceWithNewModel(int32 id); + + virtual bool GetIsATreadable(void) { return false; } +}; + +VALIDATE_SIZE(CBuilding, 0x64); + diff --git a/src/buildings/Solid.h b/src/buildings/Solid.h new file mode 100644 index 0000000..4ca800c --- /dev/null +++ b/src/buildings/Solid.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Entity.h" + +class CSolid : public CEntity +{ +public: + CSolid(void) { + m_type = ENTITY_TYPE_BUILDING; + bUsesCollision = true; + } +}; \ No newline at end of file diff --git a/src/buildings/Treadable.cpp b/src/buildings/Treadable.cpp new file mode 100644 index 0000000..d84603a --- /dev/null +++ b/src/buildings/Treadable.cpp @@ -0,0 +1,8 @@ +#include "common.h" + +#include "rpworld.h" +#include "Treadable.h" +#include "Pools.h" + +void *CTreadable::operator new(size_t sz) throw() { return CPools::GetTreadablePool()->New(); } +void CTreadable::operator delete(void *p, size_t sz) throw() { CPools::GetTreadablePool()->Delete((CTreadable*)p); } diff --git a/src/buildings/Treadable.h b/src/buildings/Treadable.h new file mode 100644 index 0000000..9e89596 --- /dev/null +++ b/src/buildings/Treadable.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Building.h" + +class CTreadable : public CBuilding +{ +public: + static void *operator new(size_t) throw(); + static void operator delete(void*, size_t) throw(); + + int16 m_nodeIndices[2][12]; // first car, then ped + + bool GetIsATreadable(void) { return true; } +}; + +VALIDATE_SIZE(CTreadable, 0x94); + diff --git a/src/collision/ColBox.cpp b/src/collision/ColBox.cpp new file mode 100644 index 0000000..53cba88 --- /dev/null +++ b/src/collision/ColBox.cpp @@ -0,0 +1,21 @@ +#include "common.h" +#include "ColBox.h" + +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; +} + +CColBox& +CColBox::operator=(const CColBox& other) +{ + min = other.min; + max = other.max; + surface = other.surface; + piece = other.piece; + return *this; +} \ No newline at end of file diff --git a/src/collision/ColBox.h b/src/collision/ColBox.h new file mode 100644 index 0000000..ac2cd67 --- /dev/null +++ b/src/collision/ColBox.h @@ -0,0 +1,16 @@ +#pragma once + +#include "SurfaceTable.h" + +struct CColBox +{ + CVector min; + CVector max; + uint8 surface; + uint8 piece; + + void Set(const CVector &min, const CVector &max, uint8 surf = SURFACE_DEFAULT, uint8 piece = 0); + CVector GetSize(void) { return max - min; } + + CColBox& operator=(const CColBox &other); +}; \ No newline at end of file diff --git a/src/collision/ColLine.cpp b/src/collision/ColLine.cpp new file mode 100644 index 0000000..c624744 --- /dev/null +++ b/src/collision/ColLine.cpp @@ -0,0 +1,9 @@ +#include "common.h" +#include "ColLine.h" + +void +CColLine::Set(const CVector &p0, const CVector &p1) +{ + this->p0 = p0; + this->p1 = p1; +} \ No newline at end of file diff --git a/src/collision/ColLine.h b/src/collision/ColLine.h new file mode 100644 index 0000000..21587a0 --- /dev/null +++ b/src/collision/ColLine.h @@ -0,0 +1,14 @@ +#pragma once + +struct CColLine +{ + // NB: this has to be compatible with two CVuVectors + 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); +}; \ No newline at end of file diff --git a/src/collision/ColModel.cpp b/src/collision/ColModel.cpp new file mode 100644 index 0000000..fb90e7d --- /dev/null +++ b/src/collision/ColModel.cpp @@ -0,0 +1,190 @@ +#include "common.h" +#include "ColModel.h" +#include "Game.h" +#include "MemoryHeap.h" + +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) +{ + PUSH_MEMID(MEMID_COLLISION); + + // HACK: allocate space for one more element to stuff the link pointer into + trianglePlanes = (CColTrianglePlane*)RwMalloc(sizeof(CColTrianglePlane) * (numTriangles+1)); + REGISTER_MEMPTR(&trianglePlanes); + for(int i = 0; i < numTriangles; i++) + trianglePlanes[i].Set(vertices, triangles[i]); + + POP_MEMID(); +} + +void +CColModel::RemoveTrianglePlanes(void) +{ + RwFree(trianglePlanes); + trianglePlanes = nil; +} + +void +CColModel::SetLinkPtr(CLink *lptr) +{ + assert(trianglePlanes); + *(CLink**)ALIGNPTR(&trianglePlanes[numTriangles]) = lptr; +} + +CLink* +CColModel::GetLinkPtr(void) +{ + assert(trianglePlanes); + return *(CLink**)ALIGNPTR(&trianglePlanes[numTriangles]); +} + +void +CColModel::GetTrianglePoint(CVector &v, int i) const +{ + v = vertices[i].Get(); +} + +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) + numVerts = other.triangles[i].a; + if(other.triangles[i].b > numVerts) + numVerts = other.triangles[i].b; + if(other.triangles[i].c > numVerts) + numVerts = other.triangles[i].c; + } + numVerts++; + if(vertices) + RwFree(vertices); + if(numVerts){ + vertices = (CompressedVector*)RwMalloc(numVerts*sizeof(CompressedVector)); + 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; +} diff --git a/src/collision/ColModel.h b/src/collision/ColModel.h new file mode 100644 index 0000000..7dcdfa4 --- /dev/null +++ b/src/collision/ColModel.h @@ -0,0 +1,37 @@ +#pragma once + +#include "templates.h" +#include "ColBox.h" +#include "ColSphere.h" +#include "ColLine.h" +#include "ColPoint.h" +#include "ColTriangle.h" + +struct CColModel +{ + CColSphere boundingSphere; + CColBox boundingBox; + int16 numSpheres; + int16 numLines; + int16 numBoxes; + int16 numTriangles; + int32 level; + bool ownsCollisionVolumes; // missing on PS2 + CColSphere *spheres; + CColLine *lines; + CColBox *boxes; + CompressedVector *vertices; + CColTriangle *triangles; + CColTrianglePlane *trianglePlanes; + + CColModel(void); + ~CColModel(void); + void RemoveCollisionVolumes(void); + void CalculateTrianglePlanes(void); + void RemoveTrianglePlanes(void); + CLink *GetLinkPtr(void); + void SetLinkPtr(CLink*); + void GetTrianglePoint(CVector &v, int i) const; + + CColModel& operator=(const CColModel& other); +}; \ No newline at end of file diff --git a/src/collision/ColPoint.cpp b/src/collision/ColPoint.cpp new file mode 100644 index 0000000..fbf9e8c --- /dev/null +++ b/src/collision/ColPoint.cpp @@ -0,0 +1,16 @@ +#include "common.h" +#include "ColPoint.h" + +CColPoint& +CColPoint::operator=(const CColPoint &other) +{ + point = other.point; + normal = other.normal; + surfaceA = other.surfaceA; + pieceA = other.pieceA; + surfaceB = other.surfaceB; + pieceB = other.pieceB; + + // no depth? + return *this; +} diff --git a/src/collision/ColPoint.h b/src/collision/ColPoint.h new file mode 100644 index 0000000..a15b234 --- /dev/null +++ b/src/collision/ColPoint.h @@ -0,0 +1,34 @@ +#pragma once + +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; + + const CVector &GetNormal() { return normal; } + float GetDepth() { return depth; } + void Set(float depth, uint8 surfA, uint8 pieceA, uint8 surfB, uint8 pieceB) { + this->depth = depth; + this->surfaceA = surfA; + this->pieceA = pieceA; + this->surfaceB = surfB; + this->pieceB = pieceB; + } + void Set(uint8 surfA, uint8 pieceA, uint8 surfB, uint8 pieceB) { + this->surfaceA = surfA; + this->pieceA = pieceA; + this->surfaceB = surfB; + this->pieceB = pieceB; + } + + CColPoint &operator=(const CColPoint &other); +}; + diff --git a/src/collision/ColSphere.cpp b/src/collision/ColSphere.cpp new file mode 100644 index 0000000..9aac01e --- /dev/null +++ b/src/collision/ColSphere.cpp @@ -0,0 +1,11 @@ +#include "common.h" +#include "ColSphere.h" + +void +CColSphere::Set(float radius, const CVector ¢er, uint8 surf, uint8 piece) +{ + this->radius = radius; + this->center = center; + this->surface = surf; + this->piece = piece; +} \ No newline at end of file diff --git a/src/collision/ColSphere.h b/src/collision/ColSphere.h new file mode 100644 index 0000000..70e2976 --- /dev/null +++ b/src/collision/ColSphere.h @@ -0,0 +1,13 @@ +#pragma once + +#include "SurfaceTable.h" + +struct CColSphere +{ + // NB: this has to be compatible with a CVuVector + CVector center; + float radius; + uint8 surface; + uint8 piece; + void Set(float radius, const CVector ¢er, uint8 surf = SURFACE_DEFAULT, uint8 piece = 0); +}; \ No newline at end of file diff --git a/src/collision/ColTriangle.cpp b/src/collision/ColTriangle.cpp new file mode 100644 index 0000000..9120fcf --- /dev/null +++ b/src/collision/ColTriangle.cpp @@ -0,0 +1,41 @@ +#include "common.h" +#include "ColTriangle.h" + +void +CColTriangle::Set(const CompressedVector *, int a, int b, int c, uint8 surf, uint8 piece) +{ + this->a = a; + this->b = b; + this->c = c; + this->surface = surf; +} + +#ifdef VU_COLLISION +void +CColTrianglePlane::Set(const CVector &va, const CVector &vb, const CVector &vc) +{ + CVector norm = CrossProduct(vc-va, vb-va); + norm.Normalise(); + float d = DotProduct(norm, va); + normal.x = norm.x*4096.0f; + normal.y = norm.y*4096.0f; + normal.z = norm.z*4096.0f; + dist = d*128.0f; +} +#else +void +CColTrianglePlane::Set(const CVector &va, const CVector &vb, const CVector &vc) +{ + normal = CrossProduct(vc-va, vb-va); + normal.Normalise(); + dist = DotProduct(normal, va); + CVector an(Abs(normal.x), Abs(normal.y), Abs(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; +} +#endif \ No newline at end of file diff --git a/src/collision/ColTriangle.h b/src/collision/ColTriangle.h new file mode 100644 index 0000000..9e918e3 --- /dev/null +++ b/src/collision/ColTriangle.h @@ -0,0 +1,68 @@ +#pragma once + +#include "CompressedVector.h" + +enum Direction { + DIR_X_POS, + DIR_X_NEG, + DIR_Y_POS, + DIR_Y_NEG, + DIR_Z_POS, + DIR_Z_NEG, +}; + +struct CColTriangle +{ + uint16 a; + uint16 b; + uint16 c; + uint8 surface; + + void Set(const CompressedVector *v, int a, int b, int c, uint8 surf, uint8 piece); +}; + +struct CColTrianglePlane +{ +#ifdef VU_COLLISION + CompressedVector normal; + int16 dist; + + void Set(const CVector &va, const CVector &vb, const CVector &vc); + void Set(const CompressedVector *v, CColTriangle &tri) { Set(v[tri.a].Get(), v[tri.b].Get(), v[tri.c].Get()); } + void GetNormal(CVector &n) const { n.x = normal.x/4096.0f; n.y = normal.y/4096.0f; n.z = normal.z/4096.0f; } + float CalcPoint(const CVector &v) const { CVector n; GetNormal(n); return DotProduct(n, v) - dist/128.0f; }; +#ifdef GTA_PS2 + void Unpack(uint128 &qword) const { + __asm__ volatile ( + "lh $8, 0(%1)\n" + "lh $9, 2(%1)\n" + "lh $10, 4(%1)\n" + "lh $11, 6(%1)\n" + "pextlw $10, $8\n" + "pextlw $11, $9\n" + "pextlw $2, $11, $10\n" + "sq $2, %0\n" + : "=m" (qword) + : "r" (this) + : "$8", "$9", "$10", "$11", "$2" + ); + } +#else + void Unpack(int32 *qword) const { + qword[0] = normal.x; + qword[1] = normal.y; + qword[2] = normal.z; + qword[3] = dist; + } +#endif +#else + CVector normal; + float dist; + uint8 dir; + + void Set(const CVector &va, const CVector &vb, const CVector &vc); + void Set(const CompressedVector *v, CColTriangle &tri) { Set(v[tri.a].Get(), v[tri.b].Get(), v[tri.c].Get()); } + void GetNormal(CVector &n) const { n = normal; } + float CalcPoint(const CVector &v) const { return DotProduct(normal, v) - dist; }; +#endif +}; \ No newline at end of file diff --git a/src/collision/Collision.cpp b/src/collision/Collision.cpp new file mode 100644 index 0000000..832d773 --- /dev/null +++ b/src/collision/Collision.cpp @@ -0,0 +1,2753 @@ +#include "common.h" + +#include "VuVector.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 "Lines.h" +#include "Collision.h" +#include "Frontend.h" + +#ifdef VU_COLLISION +#include "VuCollision.h" + +inline int +GetVUresult(void) +{ +#ifdef GTA_PS2 + int ret; + __asm__ volatile ( + "cfc2.i %0,vi01\n" // .i important! wait for VU0 to finish + : "=r" (ret) + ); + return ret; +#else + return vi01; +#endif +} + +inline int +GetVUresult(CVuVector &point, CVuVector &normal, float &dist) +{ +#ifdef GTA_PS2 + int ret; + __asm__ volatile ( + "cfc2.i %0,vi01\n" // .i important! wait for VU0 to finish + "sqc2 vf01,(%1)\n" + "sqc2 vf02,(%2)\n" + "qmfc2 $12,vf03\n" + "sw $12,(%3)\n" + : "=r" (ret) + : "r" (&point), "r" (&normal), "r" (&dist) + : "$12" + ); + return ret; +#else + point = vf01; + normal = vf02; + dist = vf03.x; + return vi01; +#endif +} + +#endif + +eLevelName CCollision::ms_collisionInMemory; +CLinkList CCollision::ms_colModelCache; + +void +CCollision::Init(void) +{ + ms_colModelCache.Init(NUMCOLCACHELINKS); + ms_collisionInMemory = LEVEL_GENERIC; +} + +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_GENERIC){ + 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_GENERIC && 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::GetColModel(e->GetModelIndex())->level; + if(level != LEVEL_GENERIC) + return (eLevelName)level; + } + return LEVEL_GENERIC; +} + +// 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_GENERIC) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + if(level == LEVEL_GENERIC) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_OBJECTS]); + if(level == LEVEL_GENERIC) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_OBJECTS_OVERLAP]); + if(level == LEVEL_GENERIC) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_DUMMIES]); + if(level == LEVEL_GENERIC) + 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_GENERIC; + + playerCoors = FindPlayerCoors(); + sx = CWorld::GetSectorIndexX(playerCoors.x); + sy = CWorld::GetSectorIndexY(playerCoors.y); + multipleLevels = false; + + veh = FindPlayerVehicle(); + if(veh && veh->IsTrain()){ + if(((CTrain*)veh)->m_nDoorState != TRAIN_DOOR_OPEN) + 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_GENERIC){ + if(level == LEVEL_GENERIC) + 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_GENERIC) + break; + } + } + + if (level == CGame::currLevel || forceChange) { +#ifdef FIX_BUGS + CTimer::Suspend(); +#else + CTimer::Stop(); +#endif + ISLAND_LOADING_IS(LOW) + { + DMAudio.SetEffectsFadeVol(0); + CPad::StopPadsShaking(); + LoadCollisionScreen(CGame::currLevel); + DMAudio.Service(); + } + + CPopulation::DealWithZoneChange(ms_collisionInMemory, CGame::currLevel, false); + + ISLAND_LOADING_ISNT(HIGH) + { + CStreaming::RemoveIslandsNotUsed(LEVEL_INDUSTRIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_COMMERCIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN); + } + ISLAND_LOADING_IS(LOW) + { + 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(); + ISLAND_LOADING_IS(LOW) + { + if (CGame::currLevel != LEVEL_GENERIC) + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + CStreaming::RequestBigBuildings(CGame::currLevel); + } +#ifdef NO_ISLAND_LOADING + else if (CMenuManager::m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_MEDIUM) + CStreaming::RequestIslands(CGame::currLevel); +#endif + CStreaming::LoadAllRequestedModels(true); + + ISLAND_LOADING_IS(LOW) + { + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); + + CGame::TidyUpMemory(true, true); + } +#ifdef FIX_BUGS + CTimer::Resume(); +#else + CTimer::Update(); +#endif + ISLAND_LOADING_IS(LOW) + DMAudio.SetEffectsFadeVol(127); + } +} + +#ifdef NO_ISLAND_LOADING +bool CCollision::bAlreadyLoaded = false; +#endif +void +CCollision::SortOutCollisionAfterLoad(void) +{ + if(ms_collisionInMemory == CGame::currLevel) + return; + ISLAND_LOADING_IS(LOW) + CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel); + + if (CGame::currLevel != LEVEL_GENERIC) { +#ifdef NO_ISLAND_LOADING + if (CMenuManager::m_PrefsIslandLoading != CMenuManager::ISLAND_LOADING_LOW) { + if (bAlreadyLoaded) { + ms_collisionInMemory = CGame::currLevel; + return; + } + bAlreadyLoaded = true; + CFileLoader::LoadCollisionFromDatFile(LEVEL_INDUSTRIAL); + CFileLoader::LoadCollisionFromDatFile(LEVEL_COMMERCIAL); + CFileLoader::LoadCollisionFromDatFile(LEVEL_SUBURBAN); + } else +#endif + 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 Const 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 CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane) +{ +#ifdef VU_COLLISION + // not used in favour of optimized loops + VuTriangle vutri; + verts[tri.a].Unpack(vutri.v0); + verts[tri.b].Unpack(vutri.v1); + verts[tri.c].Unpack(vutri.v2); + plane.Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(*(CVuVector*)&line.p0, *(CVuVector*)&line.p1, vutri); + + if(GetVUresult()) + return true; + return false; +#else + 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; + + float p0dist = DotProduct(line.p1 - line.p0, normal); + +#ifdef FIX_BUGS + // line lines in the plane, assume no collision + if (p0dist == 0.0f) + return false; +#endif + + // intersection parameter on line + t = -plane.CalcPoint(line.p0) / p0dist; + // find point of intersection + CVector p = line.p0 + (line.p1-line.p0)*t; + + const CVector &va = verts[tri.a].Get(); + const CVector &vb = verts[tri.b].Get(); + const CVector &vc = verts[tri.c].Get(); + 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; +#endif +} + +// 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 CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane) +{ +#ifdef VU_COLLISION + // not used in favour of optimized loops + VuTriangle vutri; + verts[tri.a].Unpack(vutri.v0); + verts[tri.b].Unpack(vutri.v1); + verts[tri.c].Unpack(vutri.v2); + plane.Unpack(vutri.plane); + + SphereToTriangleCollisionCompressed(*(CVuVector*)&sphere, vutri); + + if(GetVUresult()) + return true; + return false; +#else + // If sphere and plane don't intersect, no collision + float planedist = plane.CalcPoint(sphere.center); + if(Abs(planedist) > sphere.radius) + return false; + + const CVector &va = verts[tri.a].Get(); + const CVector &vb = verts[tri.b].Get(); + const CVector &vc = verts[tri.c].Get(); + + // calculate two orthogonal basis vectors for the triangle + CVector vec2 = vb - va; + float len = vec2.Magnitude(); + vec2 = vec2 * (1.0f/len); + CVector normal; + plane.GetNormal(normal); + 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; + switch(testcase){ + case 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); + break; + case 2: + // closest to an edge + // looks like original game as DistToLine manually inlined + 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); + break; + case 3: + // center is in triangle + dist = Abs(planedist); + break; + default: + assert(0); + } + + return dist < sphere.radius; +#endif +} + +bool +CCollision::TestLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSeeThrough) +{ +#ifdef VU_COLLISION + CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CVuVector newline[2]; + TransformPoints(newline, 2, matTransform, &line.p0, sizeof(CColLine)/2); + + // If we don't intersect with the bounding box, no chance on the rest + if(!TestLineBox(*(CColLine*)newline, model.boundingBox)) + return false; + + for(i = 0; i < model.numSpheres; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.spheres[i].surface)) continue; + if(TestLineSphere(*(CColLine*)newline, model.spheres[i])) + return true; + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + if(TestLineBox(*(CColLine*)newline, model.boxes[i])) + return true; + } + + CalculateTrianglePlanes(&model); + int lastTest = -1; + VuTriangle vutri; + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lastTest = i; + break; + } +#ifdef FIX_BUGS + // no need to check first again + i++; +#endif + for(; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + if(GetVUresult()) + return true; + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lastTest = i; + + } + if(lastTest != -1 && GetVUresult()) + return true; + + return false; +#else + 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 && IsSeeThrough(model.spheres[i].surface)) continue; + if(TestLineSphere(newline, model.spheres[i])) + return true; + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + if(TestLineBox(newline, model.boxes[i])) + return true; + } + + CalculateTrianglePlanes(&model); + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + if(TestLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i])) + return true; + } + + return false; +#endif +} + + +// +// 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 depth = s1.radius - d; // sphere overlap + if(d < 0.0f) d = 0.0f; // clamp to zero, i.e. if s1's center is inside s2 + // no collision if sphere is not close enough + if(d*d < mindistsq && d < s1.radius){ + dist.Normalise(); + point.point = s1.center - dist*d; + point.normal = dist; +#ifndef VU_COLLISION + point.surfaceA = s1.surface; + point.pieceA = s1.piece; + point.surfaceB = s2.surface; + point.pieceB = s2.piece; +#endif + point.depth = depth; + mindistsq = d*d; // collision radius + return true; + } + return false; +} + +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; +#ifndef VU_COLLISION + point.surfaceA = sph.surface; + point.pieceA = sph.piece; + point.surfaceB = box.surface; + point.pieceB = box.piece; +#endif + + // 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); +#ifndef VU_COLLISION + point.surfaceA = sph.surface; + point.pieceA = sph.piece; + point.surfaceB = box.surface; + point.pieceB = box.piece; +#endif + 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; +#ifndef VU_COLLISION + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = box.surface; + point.pieceB = box.piece; +#endif + 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(); +#ifndef VU_COLLISION + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = sphere.surface; + point.pieceB = sphere.piece; +#endif + mindist = t; + return true; +} + +bool +CCollision::ProcessVerticalLineTriangle(const CColLine &line, + const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindist, CStoredCollPoly *poly) +{ +#ifdef VU_COLLISION + // not used in favour of optimized loops + bool res = ProcessLineTriangle(line, verts, tri, plane, point, mindist); + if(res && poly){ + poly->verts[0] = verts[tri.a].Get(); + poly->verts[1] = verts[tri.b].Get(); + poly->verts[2] = verts[tri.c].Get(); + poly->valid = true; + } + return res; +#else + float t; + CVector normal; + + const CVector &p0 = line.p0; + const CVector &va = verts[tri.a].Get(); + const CVector &vb = verts[tri.b].Get(); + const CVector &vc = verts[tri.c].Get(); + + // 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; + if(t >= mindist) 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; +#endif +} + +bool +CCollision::IsStoredPolyStillValidVerticalLine(const CVector &pos, float z, CColPoint &point, CStoredCollPoly *poly) +{ +#ifdef VU_COLLISION + if(!poly->valid) + return false; + + CVuVector p0 = pos; + CVuVector p1 = pos; + p1.z = z; + + CVector v01 = poly->verts[1] - poly->verts[0]; + CVector v02 = poly->verts[2] - poly->verts[0]; + CVuVector plane = CrossProduct(v02, v01); + plane.Normalise(); + plane.w = DotProduct(plane, poly->verts[0]); + + LineToTriangleCollision(p0, p1, poly->verts[0], poly->verts[1], poly->verts[2], plane); + + CVuVector pnt; + float dist; + if(!GetVUresult(pnt, plane, dist)) +#ifdef FIX_BUGS + // perhaps not needed but be safe + return poly->valid = false; +#else + return false; +#endif + point.point = pnt; + return true; +#else + float t; + + if(!poly->valid) + return false; + + // maybe inlined? + CColTrianglePlane plane; + plane.Set(poly->verts[0], poly->verts[1], poly->verts[2]); + + const CVector &va = poly->verts[0]; + const CVector &vb = poly->verts[1]; + const CVector &vc = poly->verts[2]; + CVector p0 = pos; + CVector p1(pos.x, pos.y, z); + + // The rest is pretty much CCollision::ProcessLineTriangle + + // if points are on the same side, no collision + if(plane.CalcPoint(p0) * plane.CalcPoint(p1) > 0.0f) + return poly->valid = false; + + // intersection parameter on line + CVector normal; + plane.GetNormal(normal); + t = -plane.CalcPoint(p0) / DotProduct(p1 - p0, normal); + // find point of intersection + CVector p = p0 + (p1-p0)*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 poly->valid = false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return poly->valid = false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return poly->valid = false; + point.point = p; + return poly->valid = true; +#endif +} + +bool +CCollision::ProcessLineTriangle(const CColLine &line, + const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindist) +{ +#ifdef VU_COLLISION + // not used in favour of optimized loops + VuTriangle vutri; + verts[tri.a].Unpack(vutri.v0); + verts[tri.b].Unpack(vutri.v1); + verts[tri.c].Unpack(vutri.v2); + plane.Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(*(CVuVector*)&line.p0, *(CVuVector*)&line.p1, vutri); + + CVuVector pnt, normal; + float dist; + if(GetVUresult(pnt, normal, dist)){ + if(dist < mindist){ + point.point = pnt; + point.normal = normal; + mindist = dist; + return true; + } + } + return false; +#else + 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; + + float p0dist = DotProduct(line.p1 - line.p0, normal); + +#ifdef FIX_BUGS + // line lines in the plane, assume no collision + if (p0dist == 0.0f) + return false; +#endif + + // intersection parameter on line + t = -plane.CalcPoint(line.p0) / p0dist; + + // 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].Get(); + const CVector &vb = verts[tri.b].Get(); + const CVector &vc = verts[tri.c].Get(); + 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; + if(t >= mindist) 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; +#endif +} + +bool +CCollision::ProcessSphereTriangle(const CColSphere &sphere, + const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindistsq) +{ +#ifdef VU_COLLISION + // not used in favour of optimized loops + VuTriangle vutri; + verts[tri.a].Unpack(vutri.v0); + verts[tri.b].Unpack(vutri.v1); + verts[tri.c].Unpack(vutri.v2); + plane.Unpack(vutri.plane); + + SphereToTriangleCollisionCompressed(*(CVuVector*)&sphere, vutri); + + CVuVector pnt, normal; + float dist; + if(GetVUresult(pnt, normal, dist) && dist*dist < mindistsq){ + float depth = sphere.radius - dist; + if(depth > point.depth){ + point.point = pnt; + point.normal = normal; + point.depth = depth; + mindistsq = dist*dist; + return true; + } + } + return false; +#else + // If sphere and plane don't intersect, no collision + float planedist = plane.CalcPoint(sphere.center); + float distsq = planedist*planedist; + if(Abs(planedist) > sphere.radius || distsq > mindistsq) + return false; + + const CVector &va = verts[tri.a].Get(); + const CVector &vb = verts[tri.b].Get(); + const CVector &vc = verts[tri.c].Get(); + + // 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; + switch(testcase){ + case 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(); + break; + case 2: + // closest to an edge + // looks like original game as DistToLine manually inlined + 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); + break; + case 3: + // center is in triangle + dist = Abs(planedist); + p = sphere.center - normal*planedist; + break; + default: + assert(0); + } + + if(dist >= sphere.radius || dist*dist >= mindistsq) + return false; + + point.point = p; + point.normal = sphere.center - p; + point.normal.Normalise(); +#ifndef VU_COLLISION + point.surfaceA = sphere.surface; + point.pieceA = sphere.piece; + point.surfaceB = tri.surface; + point.pieceB = 0; +#endif + point.depth = sphere.radius - dist; + mindistsq = dist*dist; + return true; +#endif +} + +bool +CCollision::ProcessLineOfSight(const CColLine &line, + const CMatrix &matrix, CColModel &model, + CColPoint &point, float &mindist, bool ignoreSeeThrough) +{ +#ifdef VU_COLLISION + CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CVuVector newline[2]; + TransformPoints(newline, 2, matTransform, &line.p0, sizeof(CColLine)/2); + + if(mindist < 1.0f) + newline[1] = newline[0] + (newline[1] - newline[0])*mindist; + + // If we don't intersect with the bounding box, no chance on the rest + if(!TestLineBox(*(CColLine*)newline, model.boundingBox)) + return false; + + float coldist = 1.0f; + for(i = 0; i < model.numSpheres; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.spheres[i].surface)) continue; + if(ProcessLineSphere(*(CColLine*)newline, model.spheres[i], point, coldist)) + point.Set(0, 0, model.spheres[i].surface, model.spheres[i].piece); + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + if(ProcessLineBox(*(CColLine*)newline, model.boxes[i], point, coldist)) + point.Set(0, 0, model.boxes[i].surface, model.boxes[i].piece); + } + + CalculateTrianglePlanes(&model); + VuTriangle vutri; + CColTriangle *lasttri = nil; + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lasttri = tri; + break; + } +#ifdef FIX_BUGS + // no need to check first again + i++; +#endif + CVuVector pnt, normal; + float dist; + for(; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + if(GetVUresult(pnt, normal, dist)) + if(dist < coldist){ + point.point = pnt; + point.normal = normal; + point.Set(0, 0, lasttri->surface, 0); + coldist = dist; + } + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lasttri = tri; + } + if(lasttri && GetVUresult(pnt, normal, dist)) + if(dist < coldist){ + point.point = pnt; + point.normal = normal; + point.Set(0, 0, lasttri->surface, 0); + coldist = dist; + } + + + if(coldist < 1.0f){ + point.point = matrix * point.point; + point.normal = Multiply3x3(matrix, point.normal); + mindist *= coldist; + return true; + } + return false; +#else + 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 && IsSeeThrough(model.spheres[i].surface)) continue; + ProcessLineSphere(newline, model.spheres[i], point, coldist); + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + ProcessLineBox(newline, model.boxes[i], point, coldist); + } + + CalculateTrianglePlanes(&model); + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + 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; +#endif +} + +bool +CCollision::ProcessVerticalLine(const CColLine &line, + const CMatrix &matrix, CColModel &model, + CColPoint &point, float &mindist, bool ignoreSeeThrough, CStoredCollPoly *poly) +{ +#ifdef VU_COLLISION + static CStoredCollPoly TempStoredPoly; + CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CVuVector newline[2]; + TransformPoints(newline, 2, matTransform, &line.p0, sizeof(CColLine)/2); + + if(mindist < 1.0f) + newline[1] = newline[0] + (newline[1] - newline[0])*mindist; + + if(!TestLineBox(*(CColLine*)newline, model.boundingBox)) + return false; + + float coldist = 1.0f; + for(i = 0; i < model.numSpheres; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.spheres[i].surface)) continue; + if(ProcessLineSphere(*(CColLine*)newline, model.spheres[i], point, coldist)) + point.Set(0, 0, model.spheres[i].surface, model.spheres[i].piece); + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + if(ProcessLineBox(*(CColLine*)newline, model.boxes[i], point, coldist)) + point.Set(0, 0, model.boxes[i].surface, model.boxes[i].piece); + } + + CalculateTrianglePlanes(&model); + TempStoredPoly.valid = false; + if(model.numTriangles){ + bool registeredCol; + CColTriangle *lasttri = nil; + VuTriangle vutri; + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lasttri = tri; + break; + } +#ifdef FIX_BUGS + // no need to check first again + i++; +#endif + CVuVector pnt, normal; + float dist; + for(; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + if(GetVUresult(pnt, normal, dist)){ + if(dist < coldist){ + point.point = pnt; + point.normal = normal; + point.Set(0, 0, lasttri->surface, 0); + coldist = dist; + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol){ + TempStoredPoly.verts[0] = model.vertices[lasttri->a].Get(); + TempStoredPoly.verts[1] = model.vertices[lasttri->b].Get(); + TempStoredPoly.verts[2] = model.vertices[lasttri->c].Get(); + TempStoredPoly.valid = true; + } + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lasttri = tri; + } + if(lasttri && GetVUresult(pnt, normal, dist)){ + if(dist < coldist){ + point.point = pnt; + point.normal = normal; + point.Set(0, 0, lasttri->surface, 0); + coldist = dist; + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol){ + TempStoredPoly.verts[0] = model.vertices[lasttri->a].Get(); + TempStoredPoly.verts[1] = model.vertices[lasttri->b].Get(); + TempStoredPoly.verts[2] = model.vertices[lasttri->c].Get(); + TempStoredPoly.valid = true; + } + } + + if(coldist < 1.0f){ + point.point = matrix * point.point; + point.normal = Multiply3x3(matrix, point.normal); + if(TempStoredPoly.valid && poly){ + *poly = TempStoredPoly; + poly->verts[0] = matrix * CVector(poly->verts[0]); + poly->verts[1] = matrix * CVector(poly->verts[1]); + poly->verts[2] = matrix * CVector(poly->verts[2]); + } + mindist *= coldist; + return true; + } + return false; +#else + 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 && IsSeeThrough(model.spheres[i].surface)) continue; + ProcessLineSphere(newline, model.spheres[i], point, coldist); + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + ProcessLineBox(newline, model.boxes[i], point, coldist); + } + + CalculateTrianglePlanes(&model); + TempStoredPoly.valid = false; + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + 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(TempStoredPoly.valid && poly){ + *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; +#endif +} + +enum { + MAXNUMSPHERES = 128, + MAXNUMBOXES = 32, + MAXNUMLINES = 16, + MAXNUMTRIS = 600 +}; + +#ifdef VU_COLLISION +#ifdef GTA_PS2 +#define SPR(off) ((uint8*)(0x70000000 + (off))) +#else +static uint8 fakeSPR[16*1024]; +#define SPR(off) ((uint8*)(fakeSPR + (off))) +#endif +#endif + +// 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: only vehicles can have col models with lines, exactly 4, one for each wheel +int32 +CCollision::ProcessColModels(const CMatrix &matrixA, CColModel &modelA, + const CMatrix &matrixB, CColModel &modelB, + CColPoint *spherepoints, CColPoint *linepoints, float *linedists) +{ +#ifdef VU_COLLISION + CVuVector *aSpheresA = (CVuVector*)SPR(0x0000); + CVuVector *aSpheresB = (CVuVector*)SPR(0x0800); + CVuVector *aLinesA = (CVuVector*)SPR(0x1000); + int32 *aSphereIndicesA = (int32*)SPR(0x1200); + int32 *aSphereIndicesB = (int32*)SPR(0x1400); + int32 *aBoxIndicesB = (int32*)SPR(0x1600); + int32 *aTriangleIndicesB = (int32*)SPR(0x1680); + bool *aCollided = (bool*)SPR(0x1FE0); + CMatrix &matAB = *(CMatrix*)SPR(0x1FF0); + CMatrix &matBA = *(CMatrix*)SPR(0x2040); + int i, j, k; + + // From model A space to model B space + Invert(matrixB, matAB); + matAB *= matrixA; + + CVuVector bsphereAB; // bounding sphere of A in B space + TransformPoint(bsphereAB, matAB, modelA.boundingSphere.center); // inlined + bsphereAB.w = modelA.boundingSphere.radius; + if(!TestSphereBox(*(CColSphere*)&bsphereAB, modelB.boundingBox)) + return 0; + + // transform modelA's spheres and lines to B space + TransformPoints(aSpheresA, modelA.numSpheres, matAB, &modelA.spheres->center, sizeof(CColSphere)); + for(i = 0; i < modelA.numSpheres; i++) + aSpheresA[i].w = modelA.spheres[i].radius; + TransformPoints(aLinesA, modelA.numLines*2, matAB, &modelA.lines->p0, sizeof(CColLine)/2); + + // Test them against model B's bounding volumes + int numSpheresA = 0; + for(i = 0; i < modelA.numSpheres; i++) + if(TestSphereBox(*(CColSphere*)&aSpheresA[i], modelB.boundingBox)) + aSphereIndicesA[numSpheresA++] = i; + // No collision + if(numSpheresA == 0 && modelA.numLines == 0) + return 0; + + + // B to A space + Invert(matrixA, matBA); + matBA *= matrixB; + + // transform modelB's spheres to A space + TransformPoints(aSpheresB, modelB.numSpheres, matBA, &modelB.spheres->center, sizeof(CColSphere)); + for(i = 0; i < modelB.numSpheres; i++) + aSpheresB[i].w = modelB.spheres[i].radius; + + // Check model B against A's bounding volumes + int numSpheresB = 0; + int numBoxesB = 0; + int numTrianglesB = 0; + for(i = 0; i < modelB.numSpheres; i++) + if(TestSphereBox(*(CColSphere*)&aSpheresB[i], modelA.boundingBox)) + aSphereIndicesB[numSpheresB++] = i; + for(i = 0; i < modelB.numBoxes; i++) + if(TestSphereBox(*(CColSphere*)&bsphereAB, modelB.boxes[i])) + aBoxIndicesB[numBoxesB++] = i; + CalculateTrianglePlanes(&modelB); + if(modelB.numTriangles){ + VuTriangle vutri; + // process the first triangle + CColTriangle *tri = &modelB.triangles[0]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[0].Unpack(vutri.plane); + + SphereToTriangleCollisionCompressed(bsphereAB, vutri); + + for(i = 1; i < modelB.numTriangles; i++){ + // set up the next triangle while VU0 is running + tri = &modelB.triangles[i]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[i].Unpack(vutri.plane); + + // check previous result + if(GetVUresult()) + aTriangleIndicesB[numTrianglesB++] = i-1; + + // kick off this one + SphereToTriangleCollisionCompressed(bsphereAB, vutri); + } + + // check last result + if(GetVUresult()) + aTriangleIndicesB[numTrianglesB++] = i-1; + } + // 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; + spherepoints[numCollisions].depth = -1.0f; + for(i = 0; i < numSpheresA; i++){ + float coldist = 1.0e24f; + bool hasCollided = false; + CColSphere *sphA = &modelA.spheres[aSphereIndicesA[i]]; + CVuVector *vusphA = &aSpheresA[aSphereIndicesA[i]]; + + for(j = 0; j < numSpheresB; j++) + // This actually looks like something was inlined here + if(ProcessSphereSphere(*(CColSphere*)vusphA, modelB.spheres[aSphereIndicesB[j]], + spherepoints[numCollisions], coldist)){ + spherepoints[numCollisions].Set( + sphA->surface, sphA->piece, + modelB.spheres[aSphereIndicesB[j]].surface, modelB.spheres[aSphereIndicesB[j]].piece); + hasCollided = true; + } + for(j = 0; j < numBoxesB; j++) + if(ProcessSphereBox(*(CColSphere*)vusphA, modelB.boxes[aBoxIndicesB[j]], + spherepoints[numCollisions], coldist)){ + spherepoints[numCollisions].Set( + sphA->surface, sphA->piece, + modelB.boxes[aBoxIndicesB[j]].surface, modelB.boxes[aBoxIndicesB[j]].piece); + hasCollided = true; + } + if(numTrianglesB){ + CVuVector point, normal; + float depth; + bool registeredCol; + CColTriangle *lasttri; + + VuTriangle vutri; + // process the first triangle + k = aTriangleIndicesB[0]; + CColTriangle *tri = &modelB.triangles[k]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[k].Unpack(vutri.plane); + + SphereToTriangleCollisionCompressed(*vusphA, vutri); + lasttri = tri; + + for(j = 1; j < numTrianglesB; j++){ + k = aTriangleIndicesB[j]; + // set up the next triangle while VU0 is running + tri = &modelB.triangles[k]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[k].Unpack(vutri.plane); + + // check previous result + // TODO: this looks inlined but spherepoints[numCollisions] does not... + if(GetVUresult(point, normal, depth)){ + depth = sphA->radius - depth; + if(depth > spherepoints[numCollisions].depth){ + spherepoints[numCollisions].point = point; + spherepoints[numCollisions].normal = normal; + spherepoints[numCollisions].Set(depth, + sphA->surface, sphA->piece, lasttri->surface, 0); + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol) + hasCollided = true; + + // kick off this one + SphereToTriangleCollisionCompressed(*vusphA, vutri); + lasttri = tri; + } + + // check last result + // TODO: this looks inlined but spherepoints[numCollisions] does not... + if(GetVUresult(point, normal, depth)){ + depth = sphA->radius - depth; + if(depth > spherepoints[numCollisions].depth){ + spherepoints[numCollisions].point = point; + spherepoints[numCollisions].normal = normal; + spherepoints[numCollisions].Set(depth, + sphA->surface, sphA->piece, lasttri->surface, 0); + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol) + hasCollided = true; + } + + if(hasCollided){ + numCollisions++; + if(numCollisions == MAX_COLLISION_POINTS) + break; + spherepoints[numCollisions].depth = -1.0f; + } + } + for(i = 0; i < numCollisions; i++){ + // TODO: both VU0 macros + 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 < modelA.numLines; i++){ + aCollided[i] = false; + CVuVector *lineA = &aLinesA[i*2]; + + for(j = 0; j < numSpheresB; j++) + if(ProcessLineSphere(*(CColLine*)lineA, modelB.spheres[aSphereIndicesB[j]], + linepoints[i], linedists[i])){ + linepoints[i].Set(0, 0, +#ifdef FIX_BUGS + modelB.spheres[aSphereIndicesB[j]].surface, modelB.spheres[aSphereIndicesB[j]].piece); +#else + modelB.spheres[j].surface, modelB.spheres[j].piece); +#endif + aCollided[i] = true; + } + for(j = 0; j < numBoxesB; j++) + if(ProcessLineBox(*(CColLine*)lineA, modelB.boxes[aBoxIndicesB[j]], + linepoints[i], linedists[i])){ + linepoints[i].Set(0, 0, + modelB.boxes[aBoxIndicesB[j]].surface, modelB.boxes[aBoxIndicesB[j]].piece); + aCollided[i] = true; + } + if(numTrianglesB){ + CVuVector point, normal; + float dist; + bool registeredCol; + CColTriangle *lasttri; + + VuTriangle vutri; + // process the first triangle + k = aTriangleIndicesB[0]; + CColTriangle *tri = &modelB.triangles[k]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[k].Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(lineA[0], lineA[1], vutri); + lasttri = tri; + + for(j = 1; j < numTrianglesB; j++){ + k = aTriangleIndicesB[j]; + // set up the next triangle while VU0 is running + CColTriangle *tri = &modelB.triangles[k]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[k].Unpack(vutri.plane); + + // check previous result + // TODO: this again somewhat looks inlined + if(GetVUresult(point, normal, dist)){ + if(dist < linedists[i]){ + linepoints[i].point = point; + linepoints[i].normal = normal; + linedists[i] = dist; + linepoints[i].Set(0, 0, lasttri->surface, 0); + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol) + aCollided[i] = true; + + // kick of this one + LineToTriangleCollisionCompressed(lineA[0], lineA[1], vutri); + lasttri = tri; + } + + // check last result + if(GetVUresult(point, normal, dist)){ + if(dist < linedists[i]){ + linepoints[i].point = point; + linepoints[i].normal = normal; + linedists[i] = dist; + linepoints[i].Set(0, 0, lasttri->surface, 0); + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol) + aCollided[i] = true; + } + + if(aCollided[i]){ + // TODO: both VU0 macros + linepoints[i].point = matrixB * linepoints[i].point; + linepoints[i].normal = Multiply3x3(matrixB, linepoints[i].normal); + } + } + + return numCollisions; // sphere collisions +#else + 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); + matAB *= matrixA; + + CColSphere bsphereAB; // bounding sphere of A in B space + bsphereAB.radius = modelA.boundingSphere.radius; + bsphereAB.center = matAB * modelA.boundingSphere.center; + if(!TestSphereBox(bsphereAB, modelB.boundingBox)) + return 0; + // B to A space + matBA = Invert(matrixA, matBA); + 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.radius = modelB.spheres[i].radius; + s.center = 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 +#endif +} + + +// +// 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 + float distSqr = (*point - *l0).MagnitudeSqr() - dot * dot / lensq; + if(distSqr <= 0.f) return 0.f; + return Sqrt(distSqr); +} + +// 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) +{ + assert(model); + if(model->numTriangles == 0) + return; + + CLink *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{ + 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) +{ + int i; + CVector min, max; + CVector verts[8]; + CVector c; + float r; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + min = colModel.boundingBox.min; + max = colModel.boundingBox.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); + + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[1].x, verts[1].y, verts[1].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[1].x, verts[1].y, verts[1].z, + verts[3].x, verts[3].y, verts[3].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[3].x, verts[3].y, verts[3].z, + verts[2].x, verts[2].y, verts[2].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[2].x, verts[2].y, verts[2].z, + verts[0].x, verts[0].y, verts[0].z, + 0xFF0000FF, 0xFF0000FF); + + CLines::RenderLineWithClipping( + verts[4].x, verts[4].y, verts[4].z, + verts[5].x, verts[5].y, verts[5].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[5].x, verts[5].y, verts[5].z, + verts[7].x, verts[7].y, verts[7].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[7].x, verts[7].y, verts[7].z, + verts[6].x, verts[6].y, verts[6].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[6].x, verts[6].y, verts[6].z, + verts[4].x, verts[4].y, verts[4].z, + 0xFF0000FF, 0xFF0000FF); + + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[4].x, verts[4].y, verts[4].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[1].x, verts[1].y, verts[1].z, + verts[5].x, verts[5].y, verts[5].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[2].x, verts[2].y, verts[2].z, + verts[6].x, verts[6].y, verts[6].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[3].x, verts[3].y, verts[3].z, + verts[7].x, verts[7].y, verts[7].z, + 0xFF0000FF, 0xFF0000FF); + + for(i = 0; i < colModel.numSpheres; i++){ + c = mat * colModel.spheres[i].center; + r = colModel.spheres[i].radius; + + CLines::RenderLineWithClipping( + c.x, c.y, c.z-r, + c.x-r, c.y-r, c.z, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x, c.y, c.z-r, + c.x-r, c.y+r, c.z, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x, c.y, c.z-r, + c.x+r, c.y-r, c.z, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x, c.y, c.z-r, + c.x+r, c.y+r, c.z, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x-r, c.y-r, c.z, + c.x, c.y, c.z+r, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x-r, c.y+r, c.z, + c.x, c.y, c.z+r, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x+r, c.y-r, c.z, + c.x, c.y, c.z+r, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x+r, c.y+r, c.z, + c.x, c.y, c.z+r, + 0xFF00FFFF, 0xFF00FFFF); + } + + for(i = 0; i < colModel.numLines; i++){ + verts[0] = colModel.lines[i].p0; + verts[1] = colModel.lines[i].p1; + + verts[0] = mat * verts[0]; + verts[1] = mat * verts[1]; + + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[1].x, verts[1].y, verts[1].z, + 0x00FFFFFF, 0x00FFFFFF); + } + + 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); + + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[1].x, verts[1].y, verts[1].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[1].x, verts[1].y, verts[1].z, + verts[3].x, verts[3].y, verts[3].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[3].x, verts[3].y, verts[3].z, + verts[2].x, verts[2].y, verts[2].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[2].x, verts[2].y, verts[2].z, + verts[0].x, verts[0].y, verts[0].z, + 0xFFFFFFFF, 0xFFFFFFFF); + + CLines::RenderLineWithClipping( + verts[4].x, verts[4].y, verts[4].z, + verts[5].x, verts[5].y, verts[5].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[5].x, verts[5].y, verts[5].z, + verts[7].x, verts[7].y, verts[7].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[7].x, verts[7].y, verts[7].z, + verts[6].x, verts[6].y, verts[6].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[6].x, verts[6].y, verts[6].z, + verts[4].x, verts[4].y, verts[4].z, + 0xFFFFFFFF, 0xFFFFFFFF); + + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[4].x, verts[4].y, verts[4].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[1].x, verts[1].y, verts[1].z, + verts[5].x, verts[5].y, verts[5].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[2].x, verts[2].y, verts[2].z, + verts[6].x, verts[6].y, verts[6].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[3].x, verts[3].y, verts[3].z, + verts[7].x, verts[7].y, verts[7].z, + 0xFFFFFFFF, 0xFFFFFFFF); + } + + 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]; + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[1].x, verts[1].y, verts[1].z, + 0x00FF00FF, 0x00FF00FF); + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[2].x, verts[2].y, verts[2].z, + 0x00FF00FF, 0x00FF00FF); + CLines::RenderLineWithClipping( + verts[1].x, verts[1].y, verts[1].z, + verts[2].x, verts[2].y, verts[2].z, + 0x00FF00FF, 0x00FF00FF); + } + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); +} + +void +CCollision::DrawColModel_Coloured(const CMatrix &mat, const CColModel &colModel, int32 id) +{ + int i; + int s; + float f; + CVector verts[8]; + CVector min, max; + int r, g, b; + RwImVertexIndex *iptr; + RwIm3DVertex *vptr; + + RenderBuffer::ClearRenderBuffer(); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + for(i = 0; i < colModel.numTriangles; i++){ + colModel.GetTrianglePoint(verts[0], colModel.triangles[i].a); + colModel.GetTrianglePoint(verts[1], colModel.triangles[i].b); + colModel.GetTrianglePoint(verts[2], colModel.triangles[i].c); + verts[0] = mat * verts[0]; + verts[1] = mat * verts[1]; + verts[2] = mat * verts[2]; + + // game doesn't do this + 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; + } + + if(s == SURFACE_TRANSPARENT_CLOTH || s == SURFACE_METAL_CHAIN_FENCE || + s == SURFACE_TRANSPARENT_STONE || s == SURFACE_SCAFFOLD_POLE) + if(CTimer::GetFrameCounter() & 1){ + r = 0; + g = 0; + b = 0; + } + + if(s > SURFACE_METAL_GATE){ + 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; + } + + if(s == SURFACE_TRANSPARENT_CLOTH || s == SURFACE_METAL_CHAIN_FENCE || + s == SURFACE_TRANSPARENT_STONE || s == SURFACE_SCAFFOLD_POLE) + if(CTimer::GetFrameCounter() & 1){ + r = 0; + g = 0; + b = 0; + } + + 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); +} diff --git a/src/collision/Collision.h b/src/collision/Collision.h new file mode 100644 index 0000000..f4270bc --- /dev/null +++ b/src/collision/Collision.h @@ -0,0 +1,70 @@ +#pragma once + +#include "ColModel.h" +#include "Game.h" // for eLevelName +#ifdef VU_COLLISION +#include "VuVector.h" +#endif + +struct CStoredCollPoly +{ +#ifdef VU_COLLISION + CVuVector verts[3]; +#else + CVector verts[3]; +#endif + bool valid; +}; + +// If you spawn many tanks at once, you will see that collisions of two entity exceeds 32. +#if defined(FIX_BUGS) && !defined(SQUEEZE_PERFORMANCE) +#define MAX_COLLISION_POINTS 64 +#else +#define MAX_COLLISION_POINTS 32 +#endif + +class CCollision +{ +public: + static eLevelName ms_collisionInMemory; + static CLinkList ms_colModelCache; +#ifdef NO_ISLAND_LOADING + static bool bAlreadyLoaded; +#endif + + 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 CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane); + static bool TestLineSphere(const CColLine &line, const CColSphere &sph); + static bool TestSphereTriangle(const CColSphere &sphere, const CompressedVector *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 CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindist, CStoredCollPoly *poly); + static bool ProcessLineTriangle(const CColLine &line , const CompressedVector *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 CompressedVector *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 &matrixA, CColModel &modelA, const CMatrix &matrixB, CColModel &modelB, CColPoint *spherepoints, CColPoint *linepoints, float *linedists); + static bool IsStoredPolyStillValidVerticalLine(const CVector &pos, float z, CColPoint &point, CStoredCollPoly *poly); + + 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/collision/CompressedVector.h b/src/collision/CompressedVector.h new file mode 100644 index 0000000..d54e49b --- /dev/null +++ b/src/collision/CompressedVector.h @@ -0,0 +1,36 @@ +#pragma once + +struct CompressedVector +{ +#ifdef COMPRESSED_COL_VECTORS + int16 x, y, z; + CVector Get(void) const { return CVector(x, y, z)/128.0f; }; + void Set(float x, float y, float z) { this->x = x*128.0f; this->y = y*128.0f; this->z = z*128.0f; }; +#ifdef GTA_PS2 + void Unpack(uint128 &qword) const { + __asm__ volatile ( + "lh $8, 0(%1)\n" + "lh $9, 2(%1)\n" + "lh $10, 4(%1)\n" + "pextlw $10, $8\n" + "pextlw $2, $9, $10\n" + "sq $2, %0\n" + : "=m" (qword) + : "r" (this) + : "$8", "$9", "$10", "$2" + ); + } +#else + void Unpack(int32 *qword) const { + qword[0] = x; + qword[1] = y; + qword[2] = z; + qword[3] = 0; // junk + } +#endif +#else + float x, y, z; + CVector Get(void) const { return CVector(x, y, z); }; + void Set(float x, float y, float z) { this->x = x; this->y = y; this->z = z; }; +#endif +}; \ No newline at end of file diff --git a/src/collision/TempColModels.cpp b/src/collision/TempColModels.cpp new file mode 100644 index 0000000..494c148 --- /dev/null +++ b/src/collision/TempColModels.cpp @@ -0,0 +1,297 @@ +#include "common.h" + +#include "TempColModels.h" +#include "Game.h" + +CColModel CTempColModels::ms_colModelPed1; +CColModel CTempColModels::ms_colModelPed2; +CColModel CTempColModels::ms_colModelBBox; +CColModel CTempColModels::ms_colModelBumper1; +CColModel CTempColModels::ms_colModelWheel1; +CColModel CTempColModels::ms_colModelPanel1; +CColModel CTempColModels::ms_colModelBodyPart2; +CColModel CTempColModels::ms_colModelBodyPart1; +CColModel CTempColModels::ms_colModelCutObj[5]; +CColModel CTempColModels::ms_colModelPedGroundHit; +CColModel CTempColModels::ms_colModelBoot1; +CColModel CTempColModels::ms_colModelDoor1; +CColModel CTempColModels::ms_colModelBonnet1; + + +CColSphere s_aPedSpheres[3]; +CColSphere s_aPed2Spheres[3]; +CColSphere s_aPedGSpheres[4]; +#ifdef FIX_BUGS +CColSphere s_aDoorSpheres[3]; +#else +CColSphere s_aDoorSpheres[4]; +#endif +CColSphere s_aBumperSpheres[4]; +CColSphere s_aPanelSpheres[4]; +CColSphere s_aBonnetSpheres[4]; +CColSphere s_aBootSpheres[4]; +CColSphere s_aWheelSpheres[2]; +CColSphere s_aBodyPartSpheres1[2]; +CColSphere s_aBodyPartSpheres2[2]; + +void +CTempColModels::Initialise(void) +{ +#define SET_COLMODEL_SPHERES(colmodel, sphrs)\ + colmodel.numSpheres = ARRAY_SIZE(sphrs);\ + colmodel.spheres = sphrs;\ + colmodel.level = LEVEL_GENERIC;\ + colmodel.ownsCollisionVolumes = false; + + int i; + + ms_colModelBBox.boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelBBox.boundingBox.Set(CVector(-2.0f, -2.0f, -2.0f), CVector(2.0f, 2.0f, 2.0f)); + ms_colModelBBox.level = LEVEL_GENERIC; + + for (i = 0; i < ARRAY_SIZE(ms_colModelCutObj); i++) { + ms_colModelCutObj[i].boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelCutObj[i].boundingBox.Set(CVector(-2.0f, -2.0f, -2.0f), CVector(2.0f, 2.0f, 2.0f)); + ms_colModelCutObj[i].level = LEVEL_GENERIC; + } + + // Ped Spheres + + for (i = 0; i < ARRAY_SIZE(s_aPedSpheres); i++) + s_aPedSpheres[i].radius = 0.35f; + + s_aPedSpheres[0].center = CVector(0.0f, 0.0f, -0.25f); + s_aPedSpheres[1].center = CVector(0.0f, 0.0f, 0.15f); + s_aPedSpheres[2].center = CVector(0.0f, 0.0f, 0.55f); + +#ifdef FIX_BUGS + for (i = 0; i < ARRAY_SIZE(s_aPedSpheres); i++) { +#else + for (i = 0; i < ARRAY_SIZE(s_aPedGSpheres); i++) { +#endif + s_aPedSpheres[i].surface = SURFACE_PED; + s_aPedSpheres[i].piece = 0; + } + + ms_colModelPed1.boundingSphere.Set(1.25f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelPed1.boundingBox.Set(CVector(-0.35f, -0.35f, -1.0f), CVector(0.35f, 0.35f, 0.9f)); + SET_COLMODEL_SPHERES(ms_colModelPed1, s_aPedSpheres); + + // Ped 2 Spheres + + s_aPed2Spheres[0].radius = 0.3f; + s_aPed2Spheres[1].radius = 0.4f; + s_aPed2Spheres[2].radius = 0.3f; + + s_aPed2Spheres[0].center = CVector(0.0f, 0.35f, -0.9f); + s_aPed2Spheres[1].center = CVector(0.0f, 0.0f, -0.9f); + s_aPed2Spheres[2].center = CVector(0.0f, -0.35f, -0.9f); + + for (i = 0; i < ARRAY_SIZE(s_aPed2Spheres); i++) { + s_aPed2Spheres[i].surface = SURFACE_PED; + s_aPed2Spheres[i].piece = 0; + } + + ms_colModelPed2.boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelPed2.boundingBox.Set(CVector(-0.7f, -0.7f, -1.2f), CVector(0.7f, 0.7f, 0.0f)); + + SET_COLMODEL_SPHERES(ms_colModelPed2, s_aPed2Spheres); + + // Ped ground collision + + s_aPedGSpheres[0].radius = 0.35f; + s_aPedGSpheres[1].radius = 0.35f; + s_aPedGSpheres[2].radius = 0.35f; + s_aPedGSpheres[3].radius = 0.3f; + + s_aPedGSpheres[0].center = CVector(0.0f, -0.4f, -0.9f); + s_aPedGSpheres[1].center = CVector(0.0f, -0.1f, -0.9f); + s_aPedGSpheres[2].center = CVector(0.0f, 0.25f, -0.9f); + s_aPedGSpheres[3].center = CVector(0.0f, 0.65f, -0.9f); + + s_aPedGSpheres[0].surface = SURFACE_PED; + s_aPedGSpheres[1].surface = SURFACE_PED; + s_aPedGSpheres[2].surface = SURFACE_PED; + s_aPedGSpheres[3].surface = SURFACE_PED; + s_aPedGSpheres[0].piece = 4; + s_aPedGSpheres[1].piece = 1; + s_aPedGSpheres[2].piece = 0; + s_aPedGSpheres[3].piece = 6; + + ms_colModelPedGroundHit.boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelPedGroundHit.boundingBox.Set(CVector(-0.4f, -1.0f, -1.25f), CVector(0.4f, 1.2f, -0.5f)); + + SET_COLMODEL_SPHERES(ms_colModelPedGroundHit, s_aPedGSpheres); + + // Door Spheres + + s_aDoorSpheres[0].radius = 0.15f; + s_aDoorSpheres[1].radius = 0.15f; + s_aDoorSpheres[2].radius = 0.25f; + + s_aDoorSpheres[0].center = CVector(0.0f, -0.25f, -0.35f); + s_aDoorSpheres[1].center = CVector(0.0f, -0.95f, -0.35f); + s_aDoorSpheres[2].center = CVector(0.0f, -0.6f, 0.25f); + +#ifdef FIX_BUGS + for (i = 0; i < ARRAY_SIZE(s_aDoorSpheres); i++) { +#else + for (i = 0; i < ARRAY_SIZE(s_aPed2Spheres); i++) { +#endif + s_aDoorSpheres[i].surface = SURFACE_CAR_PANEL; + s_aDoorSpheres[i].piece = 0; + } + + ms_colModelDoor1.boundingSphere.Set(1.5f, CVector(0.0f, -0.6f, 0.0f)); + ms_colModelDoor1.boundingBox.Set(CVector(-0.3f, 0.0f, -0.6f), CVector(0.3f, -1.2f, 0.6f)); + + SET_COLMODEL_SPHERES(ms_colModelDoor1, s_aDoorSpheres); + + // Bumper Spheres + + for (i = 0; i < ARRAY_SIZE(s_aBumperSpheres); i++) + s_aBumperSpheres[i].radius = 0.15f; + + s_aBumperSpheres[0].center = CVector(0.85f, -0.05f, 0.0f); + s_aBumperSpheres[1].center = CVector(0.4f, 0.05f, 0.0f); + s_aBumperSpheres[2].center = CVector(-0.4f, 0.05f, 0.0f); + s_aBumperSpheres[3].center = CVector(-0.85f, -0.05f, 0.0f); + + for (i = 0; i < ARRAY_SIZE(s_aBumperSpheres); i++) { + s_aBumperSpheres[i].surface = SURFACE_CAR_PANEL; + s_aBumperSpheres[i].piece = 0; + } + + ms_colModelBumper1.boundingSphere.Set(2.2f, CVector(0.0f, -0.6f, 0.0f)); + ms_colModelBumper1.boundingBox.Set(CVector(-1.2f, -0.3f, -0.2f), CVector(1.2f, 0.3f, 0.2f)); + + SET_COLMODEL_SPHERES(ms_colModelBumper1, s_aBumperSpheres); + + // Panel Spheres + + for (i = 0; i < ARRAY_SIZE(s_aPanelSpheres); i++) + s_aPanelSpheres[i].radius = 0.15f; + + s_aPanelSpheres[0].center = CVector(0.15f, 0.45f, 0.0f); + s_aPanelSpheres[1].center = CVector(0.15f, -0.45f, 0.0f); + s_aPanelSpheres[2].center = CVector(-0.15f, -0.45f, 0.0f); + s_aPanelSpheres[3].center = CVector(-0.15f, 0.45f, 0.0f); + + for (i = 0; i < ARRAY_SIZE(s_aPanelSpheres); i++) { + s_aPanelSpheres[i].surface = SURFACE_CAR_PANEL; + s_aPanelSpheres[i].piece = 0; + } + + ms_colModelPanel1.boundingSphere.Set(1.4f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelPanel1.boundingBox.Set(CVector(-0.3f, -0.6f, -0.15f), CVector(0.3f, 0.6f, 0.15f)); + + SET_COLMODEL_SPHERES(ms_colModelPanel1, s_aPanelSpheres); + + // Bonnet Spheres + + for (i = 0; i < ARRAY_SIZE(s_aBonnetSpheres); i++) + s_aBonnetSpheres[i].radius = 0.2f; + + s_aBonnetSpheres[0].center = CVector(-0.4f, 0.1f, 0.0f); + s_aBonnetSpheres[1].center = CVector(-0.4f, 0.9f, 0.0f); + s_aBonnetSpheres[2].center = CVector(0.4f, 0.1f, 0.0f); + s_aBonnetSpheres[3].center = CVector(0.4f, 0.9f, 0.0f); + + for (i = 0; i < ARRAY_SIZE(s_aBonnetSpheres); i++) { + s_aBonnetSpheres[i].surface = SURFACE_CAR_PANEL; + s_aBonnetSpheres[i].piece = 0; + } + + ms_colModelBonnet1.boundingSphere.Set(1.7f, CVector(0.0f, 0.5f, 0.0f)); + ms_colModelBonnet1.boundingBox.Set(CVector(-0.7f, -0.2f, -0.3f), CVector(0.7f, 1.2f, 0.3f)); + + SET_COLMODEL_SPHERES(ms_colModelBonnet1, s_aBonnetSpheres); + + // Boot Spheres + + for (i = 0; i < ARRAY_SIZE(s_aBootSpheres); i++) + s_aBootSpheres[i].radius = 0.2f; + + s_aBootSpheres[0].center = CVector(-0.4f, -0.1f, 0.0f); + s_aBootSpheres[1].center = CVector(-0.4f, -0.6f, 0.0f); + s_aBootSpheres[2].center = CVector(0.4f, -0.1f, 0.0f); + s_aBootSpheres[3].center = CVector(0.4f, -0.6f, 0.0f); + + for (i = 0; i < ARRAY_SIZE(s_aBootSpheres); i++) { + s_aBootSpheres[i].surface = SURFACE_CAR_PANEL; + s_aBootSpheres[i].piece = 0; + } + + ms_colModelBoot1.boundingSphere.Set(1.4f, CVector(0.0f, -0.4f, 0.0f)); + ms_colModelBoot1.boundingBox.Set(CVector(-0.7f, -0.9f, -0.3f), CVector(0.7f, 0.2f, 0.3f)); + + SET_COLMODEL_SPHERES(ms_colModelBoot1, s_aBootSpheres); + + // Wheel Spheres + + s_aWheelSpheres[0].radius = 0.35f; + s_aWheelSpheres[1].radius = 0.35f; + + s_aWheelSpheres[0].center = CVector(-0.3f, 0.0f, 0.0f); + s_aWheelSpheres[1].center = CVector(0.3f, 0.0f, 0.0f); + +#ifdef FIX_BUGS + for (i = 0; i < ARRAY_SIZE(s_aWheelSpheres); i++) { +#else + for (i = 0; i < ARRAY_SIZE(s_aBootSpheres); i++) { +#endif + s_aWheelSpheres[i].surface = SURFACE_WHEELBASE; + s_aWheelSpheres[i].piece = 0; + } + + ms_colModelWheel1.boundingSphere.Set(1.4f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelWheel1.boundingBox.Set(CVector(-0.7f, -0.4f, -0.4f), CVector(0.7f, 0.4f, 0.4f)); + + SET_COLMODEL_SPHERES(ms_colModelWheel1, s_aWheelSpheres); + + // Body Part Spheres 1 + + s_aBodyPartSpheres1[0].radius = 0.2f; + s_aBodyPartSpheres1[1].radius = 0.2f; + + s_aBodyPartSpheres1[0].center = CVector(0.0f, 0.0f, 0.0f); + s_aBodyPartSpheres1[1].center = CVector(0.8f, 0.0f, 0.0f); + +#ifdef FIX_BUGS + for (i = 0; i < ARRAY_SIZE(s_aBodyPartSpheres1); i++) { +#else + for (i = 0; i < ARRAY_SIZE(s_aBootSpheres); i++) { +#endif + s_aBodyPartSpheres1[i].surface = SURFACE_PED; + s_aBodyPartSpheres1[i].piece = 0; + } + + ms_colModelBodyPart1.boundingSphere.Set(0.7f, CVector(0.4f, 0.0f, 0.0f)); + ms_colModelBodyPart1.boundingBox.Set(CVector(-0.3f, -0.3f, -0.3f), CVector(1.1f, 0.3f, 0.3f)); + + SET_COLMODEL_SPHERES(ms_colModelBodyPart1, s_aBodyPartSpheres1); + + // Body Part Spheres 2 + + s_aBodyPartSpheres2[0].radius = 0.15f; + s_aBodyPartSpheres2[1].radius = 0.15f; + + s_aBodyPartSpheres2[0].center = CVector(0.0f, 0.0f, 0.0f); + s_aBodyPartSpheres2[1].center = CVector(0.5f, 0.0f, 0.0f); + +#ifdef FIX_BUGS + for (i = 0; i < ARRAY_SIZE(s_aBodyPartSpheres2); i++) { +#else + for (i = 0; i < ARRAY_SIZE(s_aBootSpheres); i++) { +#endif + s_aBodyPartSpheres2[i].surface = SURFACE_PED; + s_aBodyPartSpheres2[i].piece = 0; + } + + ms_colModelBodyPart2.boundingSphere.Set(0.5f, CVector(0.25f, 0.0f, 0.0f)); + ms_colModelBodyPart2.boundingBox.Set(CVector(-0.2f, -0.2f, -0.2f), CVector(0.7f, 0.2f, 0.2f)); + + SET_COLMODEL_SPHERES(ms_colModelBodyPart2, s_aBodyPartSpheres2); + +#undef SET_COLMODEL_SPHERES +} diff --git a/src/collision/TempColModels.h b/src/collision/TempColModels.h new file mode 100644 index 0000000..057728a --- /dev/null +++ b/src/collision/TempColModels.h @@ -0,0 +1,23 @@ +#pragma once + +#include "ColModel.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[5]; + static CColModel ms_colModelPedGroundHit; + static CColModel ms_colModelBoot1; + static CColModel ms_colModelDoor1; + static CColModel ms_colModelBonnet1; + + static void Initialise(void); +}; diff --git a/src/collision/VuCollision.cpp b/src/collision/VuCollision.cpp new file mode 100644 index 0000000..8828d2e --- /dev/null +++ b/src/collision/VuCollision.cpp @@ -0,0 +1,282 @@ +#include "common.h" +#ifdef VU_COLLISION +#include "VuVector.h" +#include "VuCollision.h" + +#ifndef GTA_PS2 +int16 vi01; +CVuVector vf01; +CVuVector vf02; +CVuVector vf03; + +CVuVector +DistanceBetweenSphereAndLine(const CVuVector ¢er, const CVuVector &p0, const CVuVector &line) +{ + // center VF12 + // p0 VF14 + // line VF15 + CVuVector ret; // VF16 + CVuVector p1 = p0+line; + CVuVector dist0 = center - p0; // VF20 + CVuVector dist1 = center - p1; // VF25 + float lenSq = line.MagnitudeSqr(); // VF21 + float distSq0 = dist0.MagnitudeSqr(); // VF22 + float distSq1 = dist1.MagnitudeSqr(); + float dot = DotProduct(dist0, line); // VF23 + if(dot < 0.0f){ + // not above line, closest to p0 + ret = p0; + ret.w = distSq0; + return ret; + } + float t = dot/lenSq; // param of nearest point on infinite line + if(t > 1.0f){ + // not above line, closest to p1 + ret = p1; + ret.w = distSq1; + return ret; + } + // closest to line + ret = p0 + line*t; + ret.w = (ret - center).MagnitudeSqr(); + return ret; +} +inline int SignFlags(const CVector &v) +{ + int f = 0; + if(v.x < 0.0f) f |= 1; + if(v.y < 0.0f) f |= 2; + if(v.z < 0.0f) f |= 4; + return f; +} +#endif + +extern "C" void +LineToTriangleCollision(const CVuVector &p0, const CVuVector &p1, + const CVuVector &v0, const CVuVector &v1, const CVuVector &v2, + const CVuVector &plane) +{ +#ifdef GTA_PS2 + __asm__ volatile ( + ".set noreorder\n" + "lqc2 vf12, 0x0(%0)\n" + "lqc2 vf13, 0x0(%1)\n" + "lqc2 vf14, 0x0(%2)\n" + "lqc2 vf15, 0x0(%3)\n" + "lqc2 vf16, 0x0(%4)\n" + "lqc2 vf17, 0x0(%5)\n" + "vcallms Vu0LineToTriangleCollisionStart\n" + ".set reorder\n" + : + : "r" (&p0), "r" (&p1), "r" (&v0), "r" (&v1), "r" (&v2), "r" (&plane) + ); +#else + float dot0 = DotProduct(plane, p0); + float dot1 = DotProduct(plane, p1); + float dist0 = plane.w - dot0; + float dist1 = plane.w - dot1; + + // if points are on the same side, no collision + if(dist0 * dist1 > 0.0f){ + vi01 = 0; + return; + } + + CVuVector diff = p1 - p0; + float t = dist0/(dot1 - dot0); + CVuVector p = p0 + diff*t; + p.w = 0.0f; + vf01 = p; + vf03.x = t; + + // Check if point is inside + CVector cross1 = CrossProduct(p-v0, v1-v0); + CVector cross2 = CrossProduct(p-v1, v2-v1); + CVector cross3 = CrossProduct(p-v2, v0-v2); + // Only check relevant directions + int flagmask = 0; + if(Abs(plane.x) > 0.5f) flagmask |= 1; + if(Abs(plane.y) > 0.5f) flagmask |= 2; + if(Abs(plane.z) > 0.5f) flagmask |= 4; + int flags1 = SignFlags(cross1) & flagmask; + int flags2 = SignFlags(cross2) & flagmask; + int flags3 = SignFlags(cross3) & flagmask; + // inside if on the same side of all edges + if(flags1 != flags2 || flags1 != flags3){ + vi01 = 0; + return; + } + vi01 = 1; + vf02 = plane; + return; +#endif +} + +extern "C" void +LineToTriangleCollisionCompressed(const CVuVector &p0, const CVuVector &p1, VuTriangle &tri) +{ +#ifdef GTA_PS2 + __asm__ volatile ( + ".set noreorder\n" + "lqc2 vf12, 0x0(%0)\n" + "lqc2 vf13, 0x0(%1)\n" + "lqc2 vf14, 0x0(%2)\n" + "lqc2 vf15, 0x10(%2)\n" + "lqc2 vf16, 0x20(%2)\n" + "lqc2 vf17, 0x30(%2)\n" + "vcallms Vu0LineToTriangleCollisionCompressedStart\n" + ".set reorder\n" + : + : "r" (&p0), "r" (&p1), "r" (&tri) + ); +#else + CVuVector v0, v1, v2, plane; + v0.x = tri.v0[0]/128.0f; + v0.y = tri.v0[1]/128.0f; + v0.z = tri.v0[2]/128.0f; + v0.w = tri.v0[3]/128.0f; + v1.x = tri.v1[0]/128.0f; + v1.y = tri.v1[1]/128.0f; + v1.z = tri.v1[2]/128.0f; + v1.w = tri.v1[3]/128.0f; + v2.x = tri.v2[0]/128.0f; + v2.y = tri.v2[1]/128.0f; + v2.z = tri.v2[2]/128.0f; + v2.w = tri.v2[3]/128.0f; + plane.x = tri.plane[0]/4096.0f; + plane.y = tri.plane[1]/4096.0f; + plane.z = tri.plane[2]/4096.0f; + plane.w = tri.plane[3]/128.0f; + LineToTriangleCollision(p0, p1, v0, v1, v2, plane); +#endif +} + +extern "C" void +SphereToTriangleCollision(const CVuVector &sph, + const CVuVector &v0, const CVuVector &v1, const CVuVector &v2, + const CVuVector &plane) +{ +#ifdef GTA_PS2 + __asm__ volatile ( + ".set noreorder\n" + "lqc2 vf12, 0x0(%0)\n" + "lqc2 vf14, 0x0(%1)\n" + "lqc2 vf15, 0x0(%2)\n" + "lqc2 vf16, 0x0(%3)\n" + "lqc2 vf17, 0x0(%4)\n" + "vcallms Vu0SphereToTriangleCollisionStart\n" + ".set reorder\n" + : + : "r" (&sph), "r" (&v0), "r" (&v1), "r" (&v2), "r" (&plane) + ); +#else + float planedist = DotProduct(plane, sph) - plane.w; // VF02 + if(Abs(planedist) > sph.w){ + vi01 = 0; + return; + } + // point on plane + CVuVector p = sph - planedist*plane; + p.w = 0.0f; + vf01 = p; + planedist = Abs(planedist); + // edges + CVuVector v01 = v1 - v0; + CVuVector v12 = v2 - v1; + CVuVector v20 = v0 - v2; + // VU code calculates normal again for some weird reason... + // Check sides of point + CVector cross1 = CrossProduct(p-v0, v01); + CVector cross2 = CrossProduct(p-v1, v12); + CVector cross3 = CrossProduct(p-v2, v20); + // Only check relevant directions + int flagmask = 0; + if(Abs(plane.x) > 0.1f) flagmask |= 1; + if(Abs(plane.y) > 0.1f) flagmask |= 2; + if(Abs(plane.z) > 0.1f) flagmask |= 4; + int nflags = SignFlags(plane) & flagmask; + int flags1 = SignFlags(cross1) & flagmask; + int flags2 = SignFlags(cross2) & flagmask; + int flags3 = SignFlags(cross3) & flagmask; + int testcase = 0; + CVuVector closest(0.0f, 0.0f, 0.0f); // VF04 + if(flags1 == nflags){ + closest += v2; + testcase++; + } + if(flags2 == nflags){ + closest += v0; + testcase++; + } + if(flags3 == nflags){ + closest += v1; + testcase++; + } + if(testcase == 3){ + // inside triangle - dist to plane already checked + vf02 = plane; + vf02.w = vf03.x = planedist; + vi01 = 1; + }else if(testcase == 1){ + // outside two sides - closest to point opposide inside edge + vf01 = closest; + vf02 = sph - closest; + float distSq = vf02.MagnitudeSqr(); + vi01 = sph.w*sph.w > distSq; + vf03.x = Sqrt(distSq); + vf02 *= 1.0f/vf03.x; + }else{ + // inside two sides - closest to third edge + if(flags1 != nflags) + closest = DistanceBetweenSphereAndLine(sph, v0, v01); + else if(flags2 != nflags) + closest = DistanceBetweenSphereAndLine(sph, v1, v12); + else + closest = DistanceBetweenSphereAndLine(sph, v2, v20); + vi01 = sph.w*sph.w > closest.w; + vf01 = closest; + vf02 = sph - closest; + vf03.x = Sqrt(closest.w); + vf02 *= 1.0f/vf03.x; + } +#endif +} + +extern "C" void +SphereToTriangleCollisionCompressed(const CVuVector &sph, VuTriangle &tri) +{ +#ifdef GTA_PS2 + __asm__ volatile ( + ".set noreorder\n" + "lqc2 vf12, 0x0(%0)\n" + "lqc2 vf14, 0x0(%1)\n" + "lqc2 vf15, 0x10(%1)\n" + "lqc2 vf16, 0x20(%1)\n" + "lqc2 vf17, 0x30(%1)\n" + "vcallms Vu0SphereToTriangleCollisionCompressedStart\n" + ".set reorder\n" + : + : "r" (&sph), "r" (&tri) + ); +#else + CVuVector v0, v1, v2, plane; + v0.x = tri.v0[0]/128.0f; + v0.y = tri.v0[1]/128.0f; + v0.z = tri.v0[2]/128.0f; + v0.w = tri.v0[3]/128.0f; + v1.x = tri.v1[0]/128.0f; + v1.y = tri.v1[1]/128.0f; + v1.z = tri.v1[2]/128.0f; + v1.w = tri.v1[3]/128.0f; + v2.x = tri.v2[0]/128.0f; + v2.y = tri.v2[1]/128.0f; + v2.z = tri.v2[2]/128.0f; + v2.w = tri.v2[3]/128.0f; + plane.x = tri.plane[0]/4096.0f; + plane.y = tri.plane[1]/4096.0f; + plane.z = tri.plane[2]/4096.0f; + plane.w = tri.plane[3]/128.0f; + SphereToTriangleCollision(sph, v0, v1, v2, plane); +#endif +} +#endif \ No newline at end of file diff --git a/src/collision/VuCollision.h b/src/collision/VuCollision.h new file mode 100644 index 0000000..29ca4cb --- /dev/null +++ b/src/collision/VuCollision.h @@ -0,0 +1,32 @@ +#pragma once + + +struct VuTriangle +{ + // Compressed int16 but unpacked +#ifdef GTA_PS2 + uint128 v0; + uint128 v1; + uint128 v2; + uint128 plane; +#else + int32 v0[4]; + int32 v1[4]; + int32 v2[4]; + int32 plane[4]; +#endif +}; + +#ifndef GTA_PS2 +extern int16 vi01; +extern CVuVector vf01; +extern CVuVector vf02; +extern CVuVector vf03; +#endif + +extern "C" { +void LineToTriangleCollision(const CVuVector &p0, const CVuVector &p1, const CVuVector &v0, const CVuVector &v1, const CVuVector &v2, const CVuVector &plane); +void LineToTriangleCollisionCompressed(const CVuVector &p0, const CVuVector &p1, VuTriangle &tri); +void SphereToTriangleCollision(const CVuVector &sph, const CVuVector &v0, const CVuVector &v1, const CVuVector &v2, const CVuVector &plane); +void SphereToTriangleCollisionCompressed(const CVuVector &sph, VuTriangle &tri); +} diff --git a/src/collision/vu0Collision.dsm b/src/collision/vu0Collision.dsm new file mode 100644 index 0000000..657c8b8 --- /dev/null +++ b/src/collision/vu0Collision.dsm @@ -0,0 +1,21 @@ +.align 4 +.global Vu0CollisionDmaTag +Vu0CollisionDmaTag: +DMAcnt * +MPG 0, * +.vu +.include "vu0Collision_1.s" +.EndMPG +.EndDmaData +DMAend + +.global Vu0Collision2DmaTag +Vu0Collision2DmaTag: +DMAcnt * +MPG 0, * +.vu +.include "vu0Collision_2.s" +.EndMPG +.EndDmaData +DMAend +.end diff --git a/src/collision/vu0Collision_1.s b/src/collision/vu0Collision_1.s new file mode 100644 index 0000000..055c864 --- /dev/null +++ b/src/collision/vu0Collision_1.s @@ -0,0 +1,610 @@ +QuitAndFail: + NOP[E] IADDIU VI01, VI00, 0 + NOP NOP + + +QuitAndSucceed: + NOP[E] IADDIU VI01, VI00, 1 + NOP NOP + + +; 20 -- unused +; VF12, VF13 xyz: sphere centers +; VF14, VF15 x: sphere radii +; out: +; VI01: set when collision +; VF01: supposed to be intersection point? +; VF02: normal (pointing towards s1, not normalized) +.globl Vu0SphereToSphereCollision +Vu0SphereToSphereCollision: + SUB.xyz VF02, VF13, VF12 NOP ; dist of centers + ADD.x VF04, VF14, VF15 NOP ; s = sum of radii + MUL.xyzw VF03, VF02, VF02 NOP ; + MUL.x VF04, VF04, VF04 DIV Q, VF14x, VF04x ; square s + NOP NOP ; + NOP NOP ; + MULAx.w ACC, VF00, VF03 NOP ; + MADDAy.w ACC, VF00, VF03 NOP ; + MADDz.w VF03, VF00, VF03 NOP ; d = DistSq of centers + NOP NOP ; + MULAw.xyz ACC, VF12, VF00 NOP ; + MADDq.xyz VF01, VF02, Q NOP ; intersection, but wrong + CLIPw.xyz VF04, VF03 NOP ; compare s and d + SUB.xyz VF02, VF00, VF02 NOP ; compute normal + NOP NOP ; + NOP NOP ; + NOP FCAND VI01, 0x3 ; 0x2 cannot be set here + NOP[E] NOP ; + NOP NOP ; + + +; B8 -- unused +; VF12: +; VF13: radius +; VF14: +; VF15: box dimensions (?) +.globl Vu0SphereToAABBCollision +Vu0SphereToAABBCollision: + SUB.xyz VF03, VF12, VF14 LOI 0.5 + MULi.xyz VF15, VF15, I NOP + MUL.x VF13, VF13, VF13 NOP + SUB.xyz VF04, VF03, VF15 NOP + ADD.xyz VF05, VF03, VF15 MR32.xyzw VF16, VF15 + CLIPw.xyz VF03, VF16 MR32.xyzw VF17, VF16 + MUL.xyz VF04, VF04, VF04 NOP + MUL.xyz VF05, VF05, VF05 NOP + CLIPw.xyz VF03, VF17 MR32.xyzw VF16, VF17 + NOP FCAND VI01, 0x1 + MINI.xyz VF04, VF04, VF05 MFIR.x VF09, VI01 + NOP NOP + CLIPw.xyz VF03, VF16 FCAND VI01, 0x4 + NOP MFIR.y VF09, VI01 + NOP NOP + MULAx.w ACC, VF00, VF00 NOP + ADD.xyz VF01, VF00, VF03 FCAND VI01, 0x10 + NOP MFIR.z VF09, VI01 + NOP LOI 2 + NOP FCAND VI01, 0x30 + SUBAw.xyz ACC, VF00, VF00 IADD VI04, VI00, VI01 + ITOF0.xyz VF09, VF09 FCAND VI01, 0x300 + NOP IADD VI03, VI00, VI01 + NOP FCAND VI01, 0x3000 + NOP IADD VI02, VI00, VI01 + MADDi.xyzw VF09, VF09, I NOP + NOP IBEQ VI04, VI00, IgnoreZValue + NOP NOP + MADDAz.w ACC, VF00, VF04 NOP + MUL.z VF01, VF09, VF15 NOP +IgnoreZValue: + NOP IBEQ VI03, VI00, IgnoreYValue + NOP NOP + MADDAy.w ACC, VF00, VF04 NOP + MUL.y VF01, VF09, VF15 NOP +IgnoreYValue: + NOP IBEQ VI02, VI00, IgnoreXValue + NOP NOP + MADDAx.w ACC, VF00, VF04 NOP + MUL.x VF01, VF09, VF15 NOP +IgnoreXValue: + MADDx.w VF06, VF00, VF00 NOP + SUB.xyz VF02, VF03, VF01 NOP + ADD.xyz VF01, VF01, VF14 NOP + MULx.w VF01, VF00, VF00 NOP + CLIPw.xyz VF13, VF06 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x1 +QuitMicrocode: + NOP[E] NOP + NOP NOP + + +; 240 +.globl Vu0LineToSphereCollision +Vu0LineToSphereCollision: + SUB.xyzw VF01, VF13, VF12 NOP + SUB.xyzw VF02, VF14, VF12 NOP + MUL.xyz VF03, VF01, VF02 NOP + MUL.xyz VF04, VF01, VF01 NOP + MUL.x VF15, VF15, VF15 NOP + MUL.xyz VF02, VF02, VF02 NOP + MULAx.w ACC, VF00, VF03 NOP + MADDAy.w ACC, VF00, VF03 NOP + MADDz.w VF03, VF00, VF03 NOP + MULAx.w ACC, VF00, VF04 NOP + MADDAy.w ACC, VF00, VF04 NOP + MADDz.w VF01, VF00, VF04 NOP + MULAx.w ACC, VF00, VF02 NOP + MADDAy.w ACC, VF00, VF02 NOP + MADDz.w VF02, VF00, VF02 NOP + MULA.w ACC, VF03, VF03 NOP + MADDAx.w ACC, VF01, VF15 NOP + MSUB.w VF05, VF01, VF02 NOP + NOP NOP + NOP NOP + NOP IADDIU VI02, VI00, 0x10 + NOP FMAND VI01, VI02 + NOP IBNE VI01, VI00, QuitAndFail + NOP NOP + CLIPw.xyz VF15, VF02 SQRT Q, VF05w + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x1 + NOP IBNE VI00, VI01, LineStartInsideSphere + NOP NOP + SUBq.w VF05, VF03, Q NOP + SUB.w VF05, VF05, VF01 DIV Q, VF05w, VF01w + NOP FMAND VI01, VI02 + NOP IBNE VI01, VI00, QuitAndFail + NOP NOP + NOP FMAND VI01, VI02 + NOP IBEQ VI01, VI00, QuitAndFail + NOP NOP + ADDA.xyz ACC, VF12, VF00 NOP + MADDq.xyz VF01, VF01, Q NOP + MULx.w VF01, VF00, VF00 NOP + SUB.xyz VF02, VF01, VF14 NOP + NOP[E] NOP + NOP NOP +LineStartInsideSphere: + NOP MOVE.xyzw VF01, VF12 + NOP[E] IADDIU VI01, VI00, 0x1 + NOP NOP + + +; 3C0 +.globl Vu0LineToAABBCollision +Vu0LineToAABBCollision: + SUB.xyzw VF08, VF13, VF12 LOI 0.5 + MULi.xyz VF15, VF15, I IADDIU VI08, VI00, 0x0 + SUB.xyzw VF12, VF12, VF14 NOP + SUB.xyzw VF13, VF13, VF14 NOP + NOP DIV Q, VF00w, VF08x + NOP MR32.xyzw VF03, VF15 + SUB.xyz VF06, VF15, VF12 NOP + ADD.xyz VF07, VF15, VF12 NOP + NOP NOP + CLIPw.xyz VF12, VF03 MR32.xyzw VF04, VF03 + NOP NOP + ADDq.x VF09, VF00, Q DIV Q, VF00w, VF08y + NOP NOP + CLIPw.xyz VF12, VF04 MR32.xyzw VF05, VF04 + SUB.xyz VF07, VF00, VF07 IADDIU VI06, VI00, 0xCC + NOP IADDIU VI07, VI00, 0x30 + NOP NOP + CLIPw.xyz VF12, VF05 FCGET VI02 + NOP IAND VI02, VI02, VI06 + ADDq.y VF09, VF00, Q DIV Q, VF00w, VF08z + SUB.xyz VF10, VF00, VF10 NOP + CLIPw.xyz VF13, VF03 FCGET VI03 + CLIPw.xyz VF13, VF04 IAND VI03, VI03, VI07 + CLIPw.xyz VF13, VF05 FCAND VI01, 0x3330 + NOP IBEQ VI01, VI00, StartPointInsideAABB + NOP NOP + ADDq.z VF09, VF00, Q FCGET VI04 + NOP FCGET VI05 + NOP IAND VI04, VI04, VI06 + NOP IAND VI05, VI05, VI07 + MULx.xyz VF17, VF08, VF09 NOP + MULy.xyz VF18, VF08, VF09 IADDIU VI07, VI00, 0x80 + MULz.xyz VF19, VF08, VF09 IAND VI06, VI02, VI07 + MUL.w VF10, VF00, VF00 IAND VI07, VI04, VI07 + NOP NOP + NOP IBEQ VI06, VI07, CheckMaxXSide + NOP NOP + MULAx.xyz ACC, VF17, VF07 NOP + MADDw.xyz VF16, VF12, VF00 NOP + MUL.x VF10, VF07, VF09 NOP + CLIPw.xyz VF16, VF04 NOP + CLIPw.xyz VF16, VF05 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x330 + NOP IBNE VI01, VI00, CheckMaxXSide + NOP NOP + MULx.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.yz VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + SUBw.x VF02, VF00, VF00 NOP +CheckMaxXSide: + MULAx.xyz ACC, VF17, VF06 IADDIU VI07, VI00, 0x40 + MADDw.xyz VF16, VF12, VF00 IAND VI06, VI02, VI07 + MUL.x VF10, VF06, VF09 IAND VI07, VI04, VI07 + NOP NOP + NOP IBEQ VI06, VI07, CheckMinYSide + NOP NOP + CLIPw.xyz VF16, VF04 NOP + CLIPw.xyz VF16, VF05 NOP + CLIPw.xyz VF10, VF10 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0xCC03 + NOP IBNE VI01, VI00, CheckMinYSide + NOP NOP + MULx.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.yz VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + ADDw.x VF02, VF00, VF00 NOP +CheckMinYSide: + MULAy.xyz ACC, VF18, VF07 IADDIU VI07, VI00, 0x8 + MADDw.xyz VF16, VF12, VF00 IAND VI06, VI02, VI07 + MUL.y VF10, VF07, VF09 IAND VI07, VI04, VI07 + NOP NOP + NOP IBEQ VI06, VI07, CheckMaxYSide + NOP NOP + CLIPw.xyz VF16, VF03 NOP + CLIPw.xyz VF16, VF05 NOP + CLIPw.xyz VF10, VF10 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3C0C + NOP IBNE VI01, VI00, CheckMaxYSide + NOP NOP + MULy.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.xz VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + SUBw.y VF02, VF00, VF00 NOP +CheckMaxYSide: + MULAy.xyz ACC, VF18, VF06 IADDIU VI07, VI00, 0x4 + MADDw.xyz VF16, VF12, VF00 IAND VI06, VI02, VI07 + MUL.y VF10, VF06, VF09 IAND VI07, VI04, VI07 + NOP NOP + NOP IBEQ VI06, VI07, CheckMinZSide + NOP NOP + CLIPw.xyz VF16, VF03 NOP + CLIPw.xyz VF16, VF05 NOP + CLIPw.xyz VF10, VF10 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3C0C + NOP IBNE VI01, VI00, CheckMinZSide + NOP NOP + MULy.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.xz VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + ADDw.y VF02, VF00, VF00 NOP +CheckMinZSide: + MULAz.xyz ACC, VF19, VF07 IADDIU VI07, VI00, 0x20 + MADDw.xyz VF16, VF12, VF00 IAND VI06, VI03, VI07 + MUL.z VF10, VF07, VF09 IAND VI07, VI05, VI07 + NOP NOP + NOP IBEQ VI06, VI07, CheckMaxZSide + NOP NOP + CLIPw.xyz VF16, VF03 NOP + CLIPw.xyz VF16, VF04 NOP + CLIPw.xyz VF10, VF10 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3330 + NOP IBNE VI01, VI00, CheckMaxZSide + NOP NOP + MULz.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.xy VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + SUBw.z VF02, VF00, VF00 NOP +CheckMaxZSide: + MULAz.xyz ACC, VF19, VF06 IADDIU VI07, VI00, 0x10 + MADDw.xyz VF16, VF12, VF00 IAND VI06, VI03, VI07 + MUL.z VF10, VF06, VF09 IAND VI07, VI05, VI07 + NOP NOP + NOP IBEQ VI06, VI07, DoneAllChecks + NOP NOP + CLIPw.xyz VF16, VF03 NOP + CLIPw.xyz VF16, VF04 NOP + CLIPw.xyz VF10, VF10 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3330 + NOP IBNE VI01, VI00, DoneAllChecks + NOP NOP + MULz.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.xy VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + ADDw.z VF02, VF00, VF00 NOP +DoneAllChecks: + ADD.xyz VF01, VF01, VF14 IADD VI01, VI00, VI08 + NOP[E] NOP + NOP NOP +StartPointInsideAABB: + ADD.xyz VF01, VF12, VF14 WAITQ + NOP IADDIU VI01, VI00, 0x1 + NOP[E] NOP + NOP NOP + + +; 860 +.globl Vu0LineToTriangleCollisionCompressedStart +Vu0LineToTriangleCollisionCompressedStart: + ITOF0.xyzw VF17, VF17 LOI 0.000244140625 ; 1.0/4096.0 + ITOF0.xyzw VF14, VF14 NOP + ITOF0.xyzw VF15, VF15 NOP + ITOF0.xyzw VF16, VF16 NOP + MULi.xyz VF17, VF17, I LOI 0.0078125 ; 1.0/128.0 + MULi.w VF17, VF17, I NOP + MULi.xyzw VF14, VF14, I NOP + MULi.xyzw VF15, VF15, I NOP + MULi.xyzw VF16, VF16, I NOP +; fall through + +; 8A8 +; VF12: point0 +; VF13: point1 +; VF14-16: verts +; VF17: plane +; out: +; VF01: intersection point +; VF02: triangle normal +; VF03 x: intersection parameter +.globl Vu0LineToTriangleCollisionStart +Vu0LineToTriangleCollisionStart: + MUL.xyz VF10, VF17, VF12 LOI 0.5 + MUL.xyz VF11, VF17, VF13 NOP + SUB.xyz VF02, VF13, VF12 NOP ; line dist + ADD.xyz VF17, VF17, VF00 NOP + MULi.w VF03, VF00, I NOP + MULAx.w ACC, VF00, VF10 NOP + MADDAy.w ACC, VF00, VF10 IADDIU VI06, VI00, 0xE0 + MADDz.w VF10, VF00, VF10 FMAND VI05, VI06 ; -- normal sign flags, unused + MULAx.w ACC, VF00, VF11 NOP + MADDAy.w ACC, VF00, VF11 NOP + MADDz.w VF11, VF00, VF11 NOP + SUB.w VF09, VF17, VF10 NOP ; plane-pos 0 + CLIPw.xyz VF17, VF03 NOP ; compare normal against 0.5 to figure out which in which dimension to compare + NOP IADDIU VI02, VI00, 0x10 ; Sw flag + SUBA.w ACC, VF17, VF11 NOP ; plane-pos 1 + SUB.w VF08, VF11, VF10 FMAND VI01, VI02 + NOP NOP + NOP NOP + NOP FMAND VI02, VI02 + NOP IBEQ VI01, VI02, QuitAndFail ; if on same side, no collision + NOP NOP + NOP DIV Q, VF09w, VF08w ; parameter of intersection + NOP FCAND VI01, 0x3 ; check x direction + NOP IADDIU VI02, VI01, 0x7F + NOP IADDIU VI06, VI00, 0x80 + NOP IAND VI02, VI02, VI06 ; Sx flag + NOP FCAND VI01, 0xC ; check y direction + NOP IADDIU VI03, VI01, 0x3F + MULAw.xyz ACC, VF12, VF00 IADDIU VI06, VI00, 0x40 + MADDq.xyz VF01, VF02, Q IAND VI03, VI03, VI06 ; point of intersection -- Sy flag + MULx.w VF01, VF00, VF00 FCAND VI01, 0x30 ; -- check z direction + ADDq.x VF03, VF00, Q IADDIU VI04, VI01, 0x1F ; output parameter + SUB.xyz VF05, VF15, VF14 IADDIU VI06, VI00, 0x20 ; edge vectors + SUB.xyz VF08, VF01, VF14 IAND VI04, VI04, VI06 ; edge vectors -- Sz flag + SUB.xyz VF06, VF16, VF15 IADD VI06, VI02, VI03 ; edge vectors + SUB.xyz VF09, VF01, VF15 IADD VI06, VI06, VI04 ; edge vectors -- combine flags + SUB.xyz VF07, VF14, VF16 NOP ; edge vectors + SUB.xyz VF10, VF01, VF16 NOP ; edge vectors + OPMULA.xyz ACC, VF08, VF05 NOP + OPMSUB.xyz VF18, VF05, VF08 NOP ; cross1 + OPMULA.xyz ACC, VF09, VF06 NOP + OPMSUB.xyz VF19, VF06, VF09 NOP ; cross2 + OPMULA.xyz ACC, VF10, VF07 NOP + OPMSUB.xyz VF20, VF07, VF10 FMAND VI02, VI06 ; cross3 + NOP NOP + NOP FMAND VI03, VI06 + NOP NOP + NOP FMAND VI04, VI06 + NOP NOP + NOP IBNE VI03, VI02, QuitAndFail ; point has to lie on the same side of all edges (i.e. inside) + NOP NOP + NOP IBNE VI04, VI02, QuitAndFail + NOP NOP + MULw.xyz VF02, VF17, VF00 IADDIU VI01, VI00, 0x1 ; success + NOP[E] NOP + NOP NOP + + +; A68 +; VF12: center +; VF14: line origin +; VF15: line vector to other point +; out: VF16 xyz: nearest point on line; w: distance to that point +DistanceBetweenSphereAndLine: + SUB.xyz VF20, VF12, VF14 NOP + MUL.xyz VF21, VF15, VF15 NOP + ADDA.xyz ACC, VF14, VF15 NOP + MSUBw.xyz VF25, VF12, VF00 NOP ; VF25 = VF12 - (VF14+VF15) + MUL.xyz VF22, VF20, VF20 NOP + MUL.xyz VF23, VF20, VF15 NOP + MULAx.w ACC, VF00, VF21 NOP + MADDAy.w ACC, VF00, VF21 NOP + MADDz.w VF21, VF00, VF21 NOP ; MagSq VF15 (line length) + MULAx.w ACC, VF00, VF23 NOP + MADDAy.w ACC, VF00, VF23 NOP + MADDz.w VF23, VF00, VF23 NOP ; dot(VF12-VF14, VF15) + MULAx.w ACC, VF00, VF22 NOP + MADDAy.w ACC, VF00, VF22 NOP + MADDz.w VF22, VF00, VF22 IADDIU VI08, VI00, 0x10 ; MagSq VF12-VF14 -- Sw bit + MUL.xyz VF25, VF25, VF25 FMAND VI08, VI08 + NOP DIV Q, VF23w, VF21w + NOP IBNE VI00, VI08, NegativeRatio + NOP NOP + ADDA.xyz ACC, VF00, VF14 NOP + MADDq.xyz VF16, VF15, Q WAITQ ; nearest point on infinte line + ADDq.x VF24, VF00, Q NOP ; ratio + NOP NOP + NOP NOP + SUB.xyz VF26, VF16, VF12 NOP + CLIPw.xyz VF24, VF00 NOP ; compare ratio to 1.0 + NOP NOP + NOP NOP + MUL.xyz VF26, VF26, VF26 NOP + NOP FCAND VI01, 0x1 + NOP IBNE VI00, VI01, RatioGreaterThanOne + NOP NOP + MULAx.w ACC, VF00, VF26 NOP + MADDAy.w ACC, VF00, VF26 NOP + MADDz.w VF16, VF00, VF26 NOP ; distance + NOP JR VI15 + NOP NOP +NegativeRatio: + ADD.xyz VF16, VF00, VF14 NOP ; return line origin + MUL.w VF16, VF00, VF22 NOP ; and DistSq to it + NOP JR VI15 + NOP NOP +RatioGreaterThanOne: + MULAx.w ACC, VF00, VF25 NOP + MADDAy.w ACC, VF00, VF25 NOP + MADDz.w VF16, VF00, VF25 NOP + ADD.xyz VF16, VF14, VF15 NOP ; return toerh line point + NOP JR VI15 + NOP NOP + + +; BE0 +.globl Vu0SphereToTriangleCollisionCompressedStart +Vu0SphereToTriangleCollisionCompressedStart: + ITOF0.xyzw VF17, VF17 LOI 0.000244140625 ; 1.0/4096.0 + ITOF0.xyzw VF14, VF14 NOP + ITOF0.xyzw VF15, VF15 NOP + ITOF0.xyzw VF16, VF16 NOP + MULi.xyz VF17, VF17, I LOI 0.0078125 ; 1.0/128.0 + MULi.w VF17, VF17, I NOP + MULi.xyzw VF14, VF14, I NOP + MULi.xyzw VF15, VF15, I NOP + MULi.xyzw VF16, VF16, I NOP +; fall through + +; C28 +; VF12: sphere +; VF14-16: verts +; VF17: plane +; out: +; VF01: intersection point +; VF02: triangle normal +; VF03 x: intersection parameter +.globl Vu0SphereToTriangleCollisionStart +Vu0SphereToTriangleCollisionStart: + MUL.xyz VF02, VF12, VF17 LOI 0.1 + ADD.xyz VF17, VF17, VF00 NOP + ADDw.x VF13, VF00, VF12 NOP + NOP NOP + MULAx.w ACC, VF00, VF02 IADDIU VI06, VI00, 0xE0 + MADDAy.w ACC, VF00, VF02 FMAND VI05, VI06 ; normal sign flags + MADDAz.w ACC, VF00, VF02 NOP + MSUB.w VF02, VF00, VF17 NOP ; center plane pos + MULi.w VF03, VF00, I MOVE.xyzw VF04, VF03 + NOP NOP + NOP NOP + CLIPw.xyz VF13, VF02 NOP ; compare dist and radius + CLIPw.xyz VF17, VF03 NOP + MULAw.xyz ACC, VF12, VF00 IADDIU VI07, VI00, 0x0 ; -- clear test case + MSUBw.xyz VF01, VF17, VF02 NOP + MULx.w VF01, VF00, VF00 FCAND VI01, 0x3 ; projected center on plane + ABS.w VF02, VF02 IBEQ VI00, VI01, QuitAndFail ; no intersection + NOP NOP + NOP FCAND VI01, 0x3 ; -- check x direction + SUB.xyz VF02, VF12, VF01 IADDIU VI02, VI01, 0x7F + NOP IADDIU VI06, VI00, 0x80 + SUB.xyz VF05, VF15, VF14 IAND VI02, VI02, VI06 + SUB.xyz VF08, VF01, VF14 FCAND VI01, 0xC ; -- check y direction + SUB.xyz VF06, VF16, VF15 IADDIU VI03, VI01, 0x3F + SUB.xyz VF09, VF01, VF15 IADDIU VI06, VI00, 0x40 + SUB.xyz VF07, VF14, VF16 IAND VI03, VI03, VI06 + SUB.xyz VF10, VF01, VF16 FCAND VI01, 0x30 ; -- check z direction + MUL.xyz VF03, VF02, VF02 IADDIU VI04, VI01, 0x1F + OPMULA.xyz ACC, VF08, VF05 IADDIU VI06, VI00, 0x20 + OPMSUB.xyz VF18, VF05, VF08 IAND VI04, VI04, VI06 + OPMULA.xyz ACC, VF09, VF06 NOP + OPMSUB.xyz VF19, VF06, VF09 IADD VI06, VI02, VI03 + OPMULA.xyz ACC, VF10, VF07 IADD VI06, VI06, VI04 ; -- combine flags + OPMSUB.xyz VF20, VF07, VF10 FMAND VI02, VI06 ; -- cross 1 flags + MULAx.w ACC, VF00, VF03 IAND VI05, VI05, VI06 + MADDAy.w ACC, VF00, VF03 FMAND VI03, VI06 ; -- cross 2 flags + MADDz.w VF03, VF00, VF03 IADDIU VI08, VI00, 0x3 + NOP FMAND VI04, VI06 ; -- cross 3 flags + NOP NOP + NOP IBNE VI02, VI05, CheckSide2 + NOP RSQRT Q, VF00w, VF03w + ADD.xyz VF04, VF00, VF16 IADDIU VI07, VI07, 0x1 ; inside side 1 +CheckSide2: + NOP IBNE VI03, VI05, CheckSide3 + NOP NOP + ADD.xyz VF04, VF00, VF14 IADDIU VI07, VI07, 0x1 ; inside side 2 +CheckSide3: + NOP IBNE VI04, VI05, FinishCheckingSides + NOP NOP + ADD.xyz VF04, VF00, VF15 IADDIU VI07, VI07, 0x1 ; inside side 3 + NOP NOP + NOP IBEQ VI07, VI08, TotallyInsideTriangle + NOP NOP +FinishCheckingSides: + MUL.x VF13, VF13, VF13 IADDIU VI08, VI00, 0x2 + MULq.xyz VF02, VF02, Q WAITQ + NOP IBNE VI07, VI08, IntersectionOutsideTwoSides + NOP NOP + NOP IBEQ VI02, VI05, CheckDistanceSide2 + NOP NOP + NOP MOVE.xyzw VF15, VF05 + NOP BAL VI15, DistanceBetweenSphereAndLine + NOP NOP + NOP B ProcessLineResult + NOP NOP +CheckDistanceSide2: + NOP IBEQ VI03, VI05, CheckDistanceSide3 + NOP NOP + NOP MOVE.xyzw VF14, VF15 + NOP MOVE.xyzw VF15, VF06 + NOP BAL VI15, DistanceBetweenSphereAndLine + NOP NOP + NOP B ProcessLineResult + NOP NOP +CheckDistanceSide3: + NOP MOVE.xyzw VF14, VF16 + NOP MOVE.xyzw VF15, VF07 + NOP BAL VI15, DistanceBetweenSphereAndLine + NOP NOP + NOP B ProcessLineResult + NOP NOP +IntersectionOutsideTwoSides: + SUB.xyz VF05, VF04, VF12 NOP + ADD.xyz VF01, VF00, VF04 NOP ; col point + SUB.xyz VF02, VF12, VF04 NOP + NOP NOP + MUL.xyz VF05, VF05, VF05 NOP + NOP NOP + NOP NOP + NOP NOP + MULAx.w ACC, VF00, VF05 NOP + MADDAy.w ACC, VF00, VF05 NOP + MADDz.w VF05, VF00, VF05 NOP ; distSq to vertex + NOP NOP + NOP NOP + NOP NOP + CLIPw.xyz VF13, VF05 SQRT Q, VF05w ; compare radiusSq and distSq + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x1 + ADDq.x VF03, VF00, Q WAITQ ; dist to vertex + NOP IBEQ VI00, VI01, QuitAndFail ; too far + NOP NOP + NOP NOP + NOP DIV Q, VF00w, VF03x + MULq.xyz VF02, VF02, Q WAITQ ; col normal + NOP[E] NOP + NOP NOP +TotallyInsideTriangle: + ADDw.x VF03, VF00, VF02 WAITQ + MULq.xyz VF02, VF02, Q NOP + NOP[E] IADDIU VI01, VI00, 0x1 + NOP NOP +ProcessLineResult: + CLIPw.xyz VF13, VF16 SQRT Q, VF16w + ADD.xyz VF01, VF00, VF16 NOP + SUB.xyz VF02, VF12, VF16 NOP + NOP NOP + NOP FCAND VI01, 0x1 + ADDq.x VF03, VF00, Q WAITQ + NOP IBEQ VI00, VI01, QuitAndFail + NOP NOP + NOP NOP + NOP DIV Q, VF00w, VF03x + MULq.xyz VF02, VF02, Q WAITQ + NOP[E] NOP + NOP NOP + +EndOfMicrocode: diff --git a/src/collision/vu0Collision_2.s b/src/collision/vu0Collision_2.s new file mode 100644 index 0000000..716c29a --- /dev/null +++ b/src/collision/vu0Collision_2.s @@ -0,0 +1,191 @@ +QuitAndFail2: + NOP[E] IADDIU VI01, VI00, 0x0 + NOP NOP + + +QuitAndSucceed2: + NOP[E] IADDIU VI01, VI00, 0x1 + NOP NOP + + +; 20 +GetBBVertices: + MULw.xy VF02, VF01, VF00 NOP + MUL.z VF02, VF01, VF11 NOP + MULw.xz VF03, VF01, VF00 NOP + MUL.y VF03, VF01, VF11 NOP + MULw.x VF04, VF01, VF00 NOP + MUL.yz VF04, VF01, VF11 NOP + NOP JR VI15 + NOP NOP + + +; 60 +Vu0OBBToOBBCollision: + SUBw.xyz VF11, VF00, VF00 LOI 0.5 + MULi.xyz VF12, VF12, I NOP + MULi.xyz VF13, VF13, I NOP + NOP NOP + NOP NOP + NOP MOVE.xyz VF01, VF12 + NOP BAL VI15, GetBBVertices + NOP NOP + MULAx.xyz ACC, VF14, VF01 NOP + MADDAy.xyz ACC, VF15, VF01 NOP + MADDz.xyz VF01, VF16, VF01 NOP + MULAx.xyz ACC, VF14, VF02 NOP + MADDAy.xyz ACC, VF15, VF02 NOP + MADDz.xyz VF02, VF16, VF02 NOP + MULAx.xyz ACC, VF14, VF03 NOP + MADDAy.xyz ACC, VF15, VF03 NOP + MADDz.xyz VF03, VF16, VF03 NOP + MULAx.xyz ACC, VF14, VF04 NOP + MADDAy.xyz ACC, VF15, VF04 NOP + MADDz.xyz VF04, VF16, VF04 NOP + ABS.xyz VF05, VF01 NOP + ABS.xyz VF06, VF02 NOP + ABS.xyz VF07, VF03 NOP + ABS.xyz VF08, VF04 NOP + NOP NOP + MAX.xyz VF05, VF05, VF06 NOP + NOP NOP + MAX.xyz VF07, VF07, VF08 NOP + NOP NOP + NOP NOP + NOP NOP + MAX.xyz VF05, VF05, VF07 NOP + NOP NOP + NOP NOP + NOP NOP + ADD.xyz VF09, VF05, VF13 NOP + NOP NOP + NOP NOP + NOP NOP + MULx.w VF05, VF00, VF09 NOP + MULy.w VF06, VF00, VF09 NOP + MULz.w VF07, VF00, VF09 NOP + CLIPw.xyz VF17, VF05 NOP + CLIPw.xyz VF17, VF06 NOP + CLIPw.xyz VF17, VF07 MOVE.xyz VF01, VF13 + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3330 + NOP IBNE VI01, VI00, QuitAndFail2 + NOP NOP + NOP BAL VI15, GetBBVertices + NOP NOP + MULAx.xyz ACC, VF18, VF01 NOP + MADDAy.xyz ACC, VF19, VF01 NOP + MADDz.xyz VF01, VF20, VF01 NOP + MULAx.xyz ACC, VF18, VF02 NOP + MADDAy.xyz ACC, VF19, VF02 NOP + MADDz.xyz VF02, VF20, VF02 NOP + MULAx.xyz ACC, VF18, VF03 NOP + MADDAy.xyz ACC, VF19, VF03 NOP + MADDz.xyz VF03, VF20, VF03 NOP + MULAx.xyz ACC, VF18, VF04 NOP + MADDAy.xyz ACC, VF19, VF04 NOP + MADDz.xyz VF04, VF20, VF04 NOP + ABS.xyz VF05, VF01 NOP + ABS.xyz VF06, VF02 NOP + ABS.xyz VF07, VF03 NOP + ABS.xyz VF08, VF04 NOP + NOP NOP + MAX.xyz VF05, VF05, VF06 NOP + NOP NOP + MAX.xyz VF07, VF07, VF08 NOP + NOP NOP + NOP NOP + NOP NOP + MAX.xyz VF05, VF05, VF07 NOP + NOP NOP + NOP NOP + NOP NOP + ADD.xyz VF09, VF05, VF12 NOP + NOP NOP + NOP NOP + NOP NOP + MULx.w VF05, VF00, VF09 NOP + MULy.w VF06, VF00, VF09 NOP + MULz.w VF07, VF00, VF09 NOP + CLIPw.xyz VF21, VF05 NOP + CLIPw.xyz VF21, VF06 NOP + CLIPw.xyz VF21, VF07 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3330 + NOP IBNE VI01, VI00, QuitAndFail2 + NOP NOP + SUB.xyz VF06, VF02, VF01 NOP + SUB.xyz VF07, VF03, VF01 NOP + ADD.xyz VF08, VF04, VF01 NOP + ADD.x VF09, VF00, VF12 NOP + ADD.yz VF09, VF00, VF00 NOP + ADD.y VF10, VF00, VF12 NOP + ADD.xz VF10, VF00, VF00 NOP + ADD.z VF11, VF00, VF12 IADDI VI04, VI00, 0x0 + ADD.xy VF11, VF00, VF00 IADD VI02, VI00, VI00 + OPMULA.xyz ACC, VF06, VF09 NOP + OPMSUB.xyz VF01, VF09, VF06 NOP + OPMULA.xyz ACC, VF06, VF10 NOP + OPMSUB.xyz VF02, VF10, VF06 NOP + OPMULA.xyz ACC, VF06, VF11 NOP + OPMSUB.xyz VF03, VF11, VF06 SQI.xyzw VF01, (VI02++) + OPMULA.xyz ACC, VF07, VF09 NOP + OPMSUB.xyz VF01, VF09, VF07 SQI.xyzw VF02, (VI02++) + OPMULA.xyz ACC, VF07, VF10 NOP + OPMSUB.xyz VF02, VF10, VF07 SQI.xyzw VF03, (VI02++) + OPMULA.xyz ACC, VF07, VF11 NOP + OPMSUB.xyz VF03, VF11, VF07 SQI.xyzw VF01, (VI02++) + OPMULA.xyz ACC, VF08, VF09 NOP + OPMSUB.xyz VF01, VF09, VF08 SQI.xyzw VF02, (VI02++) + OPMULA.xyz ACC, VF08, VF10 NOP + OPMSUB.xyz VF02, VF10, VF08 SQI.xyzw VF03, (VI02++) + OPMULA.xyz ACC, VF08, VF11 LOI 0.5 + OPMSUB.xyz VF01, VF11, VF08 SQI.xyzw VF01, (VI02++) + MULi.xyz VF06, VF06, I NOP + MULi.xyz VF07, VF07, I SQI.xyzw VF02, (VI02++) + MULi.xyz VF08, VF08, I NOP + MUL.xyz VF02, VF21, VF01 NOP + MUL.xyz VF03, VF12, VF01 NOP + MUL.xyz VF09, VF06, VF01 NOP + MUL.xyz VF10, VF07, VF01 NOP + MUL.xyz VF11, VF08, VF01 NOP + ABS.xyz VF03, VF03 NOP + ADDy.x VF05, VF09, VF09 NOP + ADDx.y VF05, VF10, VF10 NOP + ADDx.z VF05, VF11, VF11 NOP + NOP NOP +EdgePairLoop: + ADDz.x VF05, VF05, VF09 NOP + ADDz.y VF05, VF05, VF10 NOP + ADDy.z VF05, VF05, VF11 NOP + MULAx.w ACC, VF00, VF02 IADD VI03, VI02, VI00 + MADDAy.w ACC, VF00, VF02 LQD.xyzw VF01, (--VI02) + MADDz.w VF02, VF00, VF02 NOP + ABS.xyz VF05, VF05 NOP + MULAx.w ACC, VF00, VF03 NOP + MADDAy.w ACC, VF00, VF03 NOP + MADDAz.w ACC, VF00, VF03 NOP + MADDAx.w ACC, VF00, VF05 NOP + MADDAy.w ACC, VF00, VF05 NOP + MADDz.w VF03, VF00, VF05 NOP + ADDw.x VF04, VF00, VF02 NOP + MUL.xyz VF02, VF21, VF01 NOP + MUL.xyz VF03, VF12, VF01 NOP + MUL.xyz VF09, VF06, VF01 NOP + CLIPw.xyz VF04, VF03 NOP + MUL.xyz VF10, VF07, VF01 NOP + MUL.xyz VF11, VF08, VF01 NOP + ABS.xyz VF03, VF03 NOP + ADDy.x VF05, VF09, VF09 FCAND VI01, 0x3 + ADDx.y VF05, VF10, VF10 IBNE VI01, VI00, QuitAndFail2 + ADDx.z VF05, VF11, VF11 NOP + NOP IBNE VI03, VI00, EdgePairLoop + NOP NOP + NOP[E] IADDIU VI01, VI00, 0x1 + NOP NOP + +EndOfMicrocode2: diff --git a/src/control/AutoPilot.cpp b/src/control/AutoPilot.cpp new file mode 100644 index 0000000..5af4071 --- /dev/null +++ b/src/control/AutoPilot.cpp @@ -0,0 +1,128 @@ +#include "common.h" + +#include "AutoPilot.h" + +#include "CarCtrl.h" +#include "Curves.h" +#include "PathFind.h" +#include "SaveBuf.h" + +void CAutoPilot::ModifySpeed(float speed) +{ + m_fMaxTrafficSpeed = Max(0.01f, speed); + float positionBetweenNodes = (float)(CTimer::GetTimeInMilliseconds() - m_nTimeEnteredCurve) / m_nTimeToSpendOnCurrentCurve; + CCarPathLink* pCurrentLink = &ThePaths.m_carPathLinks[m_nCurrentPathNodeInfo]; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[m_nNextPathNodeInfo]; + float currentPathLinkForwardX = m_nCurrentDirection * ThePaths.m_carPathLinks[m_nCurrentPathNodeInfo].GetDirX(); + float currentPathLinkForwardY = m_nCurrentDirection * ThePaths.m_carPathLinks[m_nCurrentPathNodeInfo].GetDirY(); + float nextPathLinkForwardX = m_nNextDirection * ThePaths.m_carPathLinks[m_nNextPathNodeInfo].GetDirX(); + float nextPathLinkForwardY = m_nNextDirection * ThePaths.m_carPathLinks[m_nNextPathNodeInfo].GetDirY(); + CVector positionOnCurrentLinkIncludingLane( + pCurrentLink->GetX() + ((m_nCurrentLane + 0.5f) * LANE_WIDTH) * currentPathLinkForwardY, + pCurrentLink->GetY() - ((m_nCurrentLane + 0.5f) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((m_nNextLane + 0.5f) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((m_nNextLane + 0.5f) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + currentPathLinkForwardX, currentPathLinkForwardY, + nextPathLinkForwardX, nextPathLinkForwardY + ) * (1000.0f / m_fMaxTrafficSpeed); +#ifdef FIX_BUGS + /* Casting timer to float is very unwanted, and in this case even causes crashes. */ + m_nTimeEnteredCurve = CTimer::GetTimeInMilliseconds() - + (uint32)(positionBetweenNodes * m_nTimeToSpendOnCurrentCurve); +#else + m_nTimeEnteredCurve = CTimer::GetTimeInMilliseconds() - positionBetweenNodes * m_nTimeToSpendOnCurrentCurve; +#endif +} + +void CAutoPilot::RemoveOnePathNode() +{ + --m_nPathFindNodesCount; + for (int i = 0; i < m_nPathFindNodesCount; i++) + m_aPathFindNodesInfo[i] = m_aPathFindNodesInfo[i + 1]; +} + +#ifdef COMPATIBLE_SAVES +void CAutoPilot::Save(uint8*& buf) +{ + WriteSaveBuf(buf, m_nCurrentRouteNode); + WriteSaveBuf(buf, m_nNextRouteNode); + WriteSaveBuf(buf, m_nPrevRouteNode); + WriteSaveBuf(buf, m_nTimeEnteredCurve); + WriteSaveBuf(buf, m_nTimeToSpendOnCurrentCurve); + WriteSaveBuf(buf, m_nCurrentPathNodeInfo); + WriteSaveBuf(buf, m_nNextPathNodeInfo); + WriteSaveBuf(buf, m_nPreviousPathNodeInfo); + WriteSaveBuf(buf, m_nAntiReverseTimer); + WriteSaveBuf(buf, m_nTimeToStartMission); + WriteSaveBuf(buf, m_nPreviousDirection); + WriteSaveBuf(buf, m_nCurrentDirection); + WriteSaveBuf(buf, m_nNextDirection); + WriteSaveBuf(buf, m_nCurrentLane); + WriteSaveBuf(buf, m_nNextLane); + WriteSaveBuf(buf, m_nDrivingStyle); + WriteSaveBuf(buf, m_nCarMission); + WriteSaveBuf(buf, m_nTempAction); + WriteSaveBuf(buf, m_nTimeTempAction); + WriteSaveBuf(buf, m_fMaxTrafficSpeed); + WriteSaveBuf(buf, m_nCruiseSpeed); + uint8 flags = 0; + if (m_bSlowedDownBecauseOfCars) flags |= BIT(0); + if (m_bSlowedDownBecauseOfPeds) flags |= BIT(1); + if (m_bStayInCurrentLevel) flags |= BIT(2); + if (m_bStayInFastLane) flags |= BIT(3); + if (m_bIgnorePathfinding) flags |= BIT(4); + WriteSaveBuf(buf, flags); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, m_vecDestinationCoors.x); + WriteSaveBuf(buf, m_vecDestinationCoors.y); + WriteSaveBuf(buf, m_vecDestinationCoors.z); + ZeroSaveBuf(buf, 32); + WriteSaveBuf(buf, m_nPathFindNodesCount); + ZeroSaveBuf(buf, 6); +} + +void CAutoPilot::Load(uint8*& buf) +{ + ReadSaveBuf(&m_nCurrentRouteNode, buf); + ReadSaveBuf(&m_nNextRouteNode, buf); + ReadSaveBuf(&m_nPrevRouteNode, buf); + ReadSaveBuf(&m_nTimeEnteredCurve, buf); + ReadSaveBuf(&m_nTimeToSpendOnCurrentCurve, buf); + ReadSaveBuf(&m_nCurrentPathNodeInfo, buf); + ReadSaveBuf(&m_nNextPathNodeInfo, buf); + ReadSaveBuf(&m_nPreviousPathNodeInfo, buf); + ReadSaveBuf(&m_nAntiReverseTimer, buf); + ReadSaveBuf(&m_nTimeToStartMission, buf); + ReadSaveBuf(&m_nPreviousDirection, buf); + ReadSaveBuf(&m_nCurrentDirection, buf); + ReadSaveBuf(&m_nNextDirection, buf); + ReadSaveBuf(&m_nCurrentLane, buf); + ReadSaveBuf(&m_nNextLane, buf); + ReadSaveBuf(&m_nDrivingStyle, buf); + ReadSaveBuf(&m_nCarMission, buf); + ReadSaveBuf(&m_nTempAction, buf); + ReadSaveBuf(&m_nTimeTempAction, buf); + ReadSaveBuf(&m_fMaxTrafficSpeed, buf); + ReadSaveBuf(&m_nCruiseSpeed, buf); + uint8 flags; + ReadSaveBuf(&flags, buf); + m_bSlowedDownBecauseOfCars = !!(flags & BIT(0)); + m_bSlowedDownBecauseOfPeds = !!(flags & BIT(1)); + m_bStayInCurrentLevel = !!(flags & BIT(2)); + m_bStayInFastLane = !!(flags & BIT(3)); + m_bIgnorePathfinding = !!(flags & BIT(4)); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&m_vecDestinationCoors.x, buf); + ReadSaveBuf(&m_vecDestinationCoors.y, buf); + ReadSaveBuf(&m_vecDestinationCoors.z, buf); + SkipSaveBuf(buf, 32); + ReadSaveBuf(&m_nPathFindNodesCount, buf); + SkipSaveBuf(buf, 6); +} +#endif \ No newline at end of file diff --git a/src/control/AutoPilot.h b/src/control/AutoPilot.h new file mode 100644 index 0000000..c7707ed --- /dev/null +++ b/src/control/AutoPilot.h @@ -0,0 +1,123 @@ +#pragma once +#include "Timer.h" + +class CVehicle; +struct CPathNode; + +enum eCarMission +{ + MISSION_NONE, + MISSION_CRUISE, + MISSION_RAMPLAYER_FARAWAY, + MISSION_RAMPLAYER_CLOSE, + MISSION_BLOCKPLAYER_FARAWAY, + MISSION_BLOCKPLAYER_CLOSE, + MISSION_BLOCKPLAYER_HANDBRAKESTOP, + MISSION_WAITFORDELETION, + MISSION_GOTOCOORDS, + MISSION_GOTOCOORDS_STRAIGHT, + MISSION_EMERGENCYVEHICLE_STOP, + MISSION_STOP_FOREVER, + MISSION_GOTOCOORDS_ACCURATE, + MISSION_GOTO_COORDS_STRAIGHT_ACCURATE, + MISSION_GOTOCOORDS_ASTHECROWSWIMS, + MISSION_RAMCAR_FARAWAY, + MISSION_RAMCAR_CLOSE, + MISSION_BLOCKCAR_FARAWAY, + MISSION_BLOCKCAR_CLOSE, + MISSION_BLOCKCAR_HANDBRAKESTOP, +}; + +enum eCarTempAction +{ + TEMPACT_NONE, + TEMPACT_WAIT, + TEMPACT_REVERSE, + TEMPACT_HANDBRAKETURNLEFT, + TEMPACT_HANDBRAKETURNRIGHT, + TEMPACT_HANDBRAKESTRAIGHT, + TEMPACT_TURNLEFT, + TEMPACT_TURNRIGHT, + TEMPACT_GOFORWARD, + TEMPACT_SWERVELEFT, + TEMPACT_SWERVERIGHT +}; + +enum eCarDrivingStyle +{ + DRIVINGSTYLE_STOP_FOR_CARS, + DRIVINGSTYLE_SLOW_DOWN_FOR_CARS, + DRIVINGSTYLE_AVOID_CARS, + DRIVINGSTYLE_PLOUGH_THROUGH, + DRIVINGSTYLE_STOP_FOR_CARS_IGNORE_LIGHTS +}; + +class CAutoPilot { +public: + int32 m_nCurrentRouteNode; + int32 m_nNextRouteNode; + int32 m_nPrevRouteNode; + int32 m_nTimeEnteredCurve; + int32 m_nTimeToSpendOnCurrentCurve; + uint32 m_nCurrentPathNodeInfo; + uint32 m_nNextPathNodeInfo; + uint32 m_nPreviousPathNodeInfo; + uint32 m_nAntiReverseTimer; + uint32 m_nTimeToStartMission; + int8 m_nPreviousDirection; + int8 m_nCurrentDirection; + int8 m_nNextDirection; + int8 m_nCurrentLane; + int8 m_nNextLane; + uint8 m_nDrivingStyle; + uint8 m_nCarMission; + uint8 m_nTempAction; + uint32 m_nTimeTempAction; + float m_fMaxTrafficSpeed; + uint8 m_nCruiseSpeed; + uint8 m_bSlowedDownBecauseOfCars : 1; + uint8 m_bSlowedDownBecauseOfPeds : 1; + uint8 m_bStayInCurrentLevel : 1; + uint8 m_bStayInFastLane : 1; + uint8 m_bIgnorePathfinding : 1; + CVector m_vecDestinationCoors; + CPathNode *m_aPathFindNodesInfo[NUM_PATH_NODES_IN_AUTOPILOT]; + int16 m_nPathFindNodesCount; + CVehicle *m_pTargetCar; + + CAutoPilot(void) { + m_nPrevRouteNode = 0; + m_nNextRouteNode = m_nPrevRouteNode; + m_nCurrentRouteNode = m_nNextRouteNode; + m_nTimeEnteredCurve = 0; + m_nTimeToSpendOnCurrentCurve = 1000; + m_nPreviousPathNodeInfo = 0; + m_nNextPathNodeInfo = m_nPreviousPathNodeInfo; + m_nCurrentPathNodeInfo = m_nNextPathNodeInfo; + m_nNextDirection = 1; + m_nCurrentDirection = m_nNextDirection; + m_nCurrentLane = m_nNextLane = 0; + m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + m_nCarMission = MISSION_NONE; + m_nTempAction = TEMPACT_NONE; + m_nCruiseSpeed = 10; + m_fMaxTrafficSpeed = 10.0f; + m_bSlowedDownBecauseOfPeds = false; + m_bSlowedDownBecauseOfCars = false; + m_nPathFindNodesCount = 0; + m_pTargetCar = 0; + m_nTimeToStartMission = CTimer::GetTimeInMilliseconds(); + m_nAntiReverseTimer = m_nTimeToStartMission; + m_bStayInFastLane = false; + } + + void ModifySpeed(float); + void RemoveOnePathNode(); +#ifdef COMPATIBLE_SAVES + void Save(uint8*& buf); + void Load(uint8*& buf); +#endif + +}; + +VALIDATE_SIZE(CAutoPilot, 0x70); diff --git a/src/control/Bridge.cpp b/src/control/Bridge.cpp new file mode 100644 index 0000000..e873062 --- /dev/null +++ b/src/control/Bridge.cpp @@ -0,0 +1,149 @@ +#include "common.h" + +#include "Bridge.h" +#include "Pools.h" +#include "ModelIndices.h" +#include "PathFind.h" +#include "Stats.h" + +CEntity *CBridge::pLiftRoad; +CEntity *CBridge::pLiftPart; +CEntity *CBridge::pWeight; + +int CBridge::State; +int CBridge::OldState; + +float CBridge::DefaultZLiftPart; +float CBridge::DefaultZLiftRoad; +float CBridge::DefaultZLiftWeight; + +float CBridge::OldLift; + +uint32 CBridge::TimeOfBridgeBecomingOperational; + +void CBridge::Init() +{ + FindBridgeEntities(); + OldLift = -1.0f; + if (pLiftPart && pWeight) + { + DefaultZLiftPart = pLiftPart->GetPosition().z; + DefaultZLiftWeight = pWeight->GetPosition().z; + + if (pLiftRoad) + DefaultZLiftRoad = pLiftRoad->GetPosition().z; + + ThePaths.SetLinksBridgeLights(-330.0, -230.0, -700.0, -588.0, true); + } +} + +void CBridge::Update() +{ + if (!pLiftPart || !pWeight) + return; + + OldState = State; + + float liftHeight; + + // Set bridge height and state + if (CStats::CommercialPassed) + { + if (TimeOfBridgeBecomingOperational == 0) + TimeOfBridgeBecomingOperational = CTimer::GetTimeInMilliseconds(); + + // Time remaining for bridge to become operational + // uint16, so after about a minute it overflows to 0 and the cycle repeats + uint16 timeElapsed = CTimer::GetTimeInMilliseconds() - TimeOfBridgeBecomingOperational; + + // Calculate lift part height and bridge state + if (timeElapsed < 10000) + { + State = STATE_LIFT_PART_MOVING_DOWN; + liftHeight = 25.0f - timeElapsed / 10000.0f * 25.0f; + } + else if (timeElapsed < 40000) + { + liftHeight = 0.0f; + State = STATE_LIFT_PART_IS_DOWN; + } + else if (timeElapsed < 50000) + { + liftHeight = 0.0f; + State = STATE_LIFT_PART_ABOUT_TO_MOVE_UP; + } + else if (timeElapsed < 60000) + { + State = STATE_LIFT_PART_MOVING_UP; + liftHeight = (timeElapsed - 50000) / 10000.0f * 25.0f; + } + else + { + liftHeight = 25.0f; + State = STATE_LIFT_PART_IS_UP; + } + } + else + { + liftHeight = 25.0f; + TimeOfBridgeBecomingOperational = 0; + State = STATE_BRIDGE_LOCKED; + } + + // Move bridge part + if (liftHeight != OldLift) + { + pLiftPart->GetMatrix().GetPosition().z = DefaultZLiftPart + liftHeight; + pLiftPart->GetMatrix().UpdateRW(); + pLiftPart->UpdateRwFrame(); + if (pLiftRoad) + { + pLiftRoad->GetMatrix().GetPosition().z = DefaultZLiftRoad + liftHeight; + pLiftRoad->GetMatrix().UpdateRW(); + pLiftRoad->UpdateRwFrame(); + } + pWeight->GetMatrix().GetPosition().z = DefaultZLiftWeight - liftHeight; + pWeight->GetMatrix().UpdateRW(); + pWeight->UpdateRwFrame(); + + OldLift = liftHeight; + } + + if (State == STATE_LIFT_PART_ABOUT_TO_MOVE_UP && OldState == STATE_LIFT_PART_IS_DOWN) + ThePaths.SetLinksBridgeLights(-330.0, -230.0, -700.0, -588.0, true); + else if (State == STATE_LIFT_PART_IS_DOWN && OldState == STATE_LIFT_PART_MOVING_DOWN) + ThePaths.SetLinksBridgeLights(-330.0, -230.0, -700.0, -588.0, false); +} + +bool CBridge::ShouldLightsBeFlashing() +{ + return State != STATE_LIFT_PART_IS_DOWN; +} + +void CBridge::FindBridgeEntities() +{ + pWeight = nil; + pLiftRoad = nil; + pLiftPart = nil; + + for (int i = CPools::GetBuildingPool()->GetSize()-1; i >= 0; i--) { + CBuilding* entry = CPools::GetBuildingPool()->GetSlot(i); + if (entry) + { + if (entry->GetModelIndex() == MI_BRIDGELIFT) + pLiftPart = entry; + else if (entry->GetModelIndex() == MI_BRIDGEROADSEGMENT) + pLiftRoad = entry; + else if (entry->GetModelIndex() == MI_BRIDGEWEIGHT) + pWeight = entry; + } + } +} + +bool CBridge::ThisIsABridgeObjectMovingUp(int index) +{ + if (index != MI_BRIDGEROADSEGMENT && index != MI_BRIDGELIFT) + return false; + + return State == STATE_LIFT_PART_ABOUT_TO_MOVE_UP || State == STATE_LIFT_PART_MOVING_UP; +} diff --git a/src/control/Bridge.h b/src/control/Bridge.h new file mode 100644 index 0000000..c570262 --- /dev/null +++ b/src/control/Bridge.h @@ -0,0 +1,28 @@ +#pragma once + +class CEntity; + +enum bridgeStates { + STATE_BRIDGE_LOCKED, + STATE_LIFT_PART_IS_UP, + STATE_LIFT_PART_MOVING_DOWN, + STATE_LIFT_PART_IS_DOWN, + STATE_LIFT_PART_ABOUT_TO_MOVE_UP, + STATE_LIFT_PART_MOVING_UP +}; + +class CBridge +{ +public: + static CEntity *pLiftRoad, *pLiftPart, *pWeight; + static int State, OldState; + static float DefaultZLiftPart, DefaultZLiftRoad, DefaultZLiftWeight; + static float OldLift; + static uint32 TimeOfBridgeBecomingOperational; + + static void Init(); + static void Update(); + static bool ShouldLightsBeFlashing(); + static void FindBridgeEntities(); + static bool ThisIsABridgeObjectMovingUp(int); +}; diff --git a/src/control/CarAI.cpp b/src/control/CarAI.cpp new file mode 100644 index 0000000..ffde7ab --- /dev/null +++ b/src/control/CarAI.cpp @@ -0,0 +1,666 @@ +#include "common.h" + +#include "CarAI.h" + +#include "Accident.h" +#include "AutoPilot.h" +#include "CarCtrl.h" +#include "General.h" +#include "HandlingMgr.h" +#include "ModelIndices.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "Fire.h" +#include "Pools.h" +#include "Timer.h" +#include "TrafficLights.h" +#include "Vehicle.h" +#include "World.h" +#include "ZoneCull.h" + +#define DISTANCE_TO_SWITCH_DISTANCE_GOTO 20.0f + +float CCarAI::FindSwitchDistanceClose(CVehicle* pVehicle) +{ + return 30.0f; +} + +float CCarAI::FindSwitchDistanceFarNormalVehicle(CVehicle* pVehicle) +{ + return FindSwitchDistanceClose(pVehicle) + 5.0f; +} + +float CCarAI::FindSwitchDistanceFar(CVehicle* pVehicle) +{ + if (pVehicle->bIsLawEnforcer) + return 50.0f; + return FindSwitchDistanceFarNormalVehicle(pVehicle); +} + +void CCarAI::UpdateCarAI(CVehicle* pVehicle) +{ + if (pVehicle->bIsLawEnforcer){ + if (pVehicle->AutoPilot.m_nCarMission == MISSION_BLOCKCAR_FARAWAY || + pVehicle->AutoPilot.m_nCarMission == MISSION_RAMPLAYER_FARAWAY || + pVehicle->AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_CLOSE || + pVehicle->AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE) + pVehicle->AutoPilot.m_nCruiseSpeed = FindPoliceCarSpeedForWantedLevel(pVehicle); + } + switch (pVehicle->GetStatus()){ + case STATUS_PLAYER: + case STATUS_PLAYER_PLAYBACKFROMBUFFER: + case STATUS_TRAIN_MOVING: + case STATUS_TRAIN_NOT_MOVING: + case STATUS_HELI: + case STATUS_PLANE: + case STATUS_PLAYER_REMOTE: + case STATUS_PLAYER_DISABLED: + break; + case STATUS_SIMPLE: + case STATUS_PHYSICS: + switch (pVehicle->AutoPilot.m_nCarMission) { + case MISSION_RAMPLAYER_FARAWAY: + if (FindSwitchDistanceClose(pVehicle) > (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() || + pVehicle->AutoPilot.m_bIgnorePathfinding) { + pVehicle->AutoPilot.m_nCarMission = MISSION_RAMPLAYER_CLOSE; + if (pVehicle->UsesSiren(pVehicle->GetModelIndex())) + pVehicle->m_bSirenOrAlarm = true; + } + if (FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || pVehicle->bIsLawEnforcer && + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())) { + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->m_bSirenOrAlarm = false; + if (CCullZones::NoPolice()) + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + case MISSION_RAMPLAYER_CLOSE: + if (FindSwitchDistanceFar(pVehicle) >= (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() || + pVehicle->AutoPilot.m_bIgnorePathfinding) { + if (FindPlayerVehicle()) { + if (pVehicle->GetHasCollidedWith(FindPlayerVehicle())) { + if (pVehicle->AutoPilot.m_nTempAction != TEMPACT_TURNLEFT && pVehicle->AutoPilot.m_nTempAction != TEMPACT_TURNRIGHT) { + if (FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f) { + pVehicle->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 800; + } + else { + pVehicle->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 50; + } + } + } + } + if (FindPlayerVehicle() && FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f) +#ifdef FIX_BUGS + pVehicle->m_nTimeBlocked += CTimer::GetTimeStepInMilliseconds(); +#else + pVehicle->m_nTimeBlocked += 1000.0f / 60.0f * CTimer::GetTimeStep(); +#endif + else + pVehicle->m_nTimeBlocked = 0; + if (!FindPlayerVehicle() || FindPlayerVehicle()->IsUpsideDown() || + FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f && pVehicle->m_nTimeBlocked > TIME_COPS_WAIT_TO_EXIT_AFTER_STOPPING) { + if (pVehicle->bIsLawEnforcer && + (pVehicle->GetModelIndex() != MI_RHINO || pVehicle->m_randomSeed > 10000) && + (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() < 10.0f) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + if (FindPlayerPed()->m_pWanted->GetWantedLevel() <= 1) + pVehicle->m_bSirenOrAlarm = false; + } + } + } + else if (!CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, FindPlayerCoors(), true)){ + pVehicle->AutoPilot.m_nCarMission = MISSION_RAMPLAYER_FARAWAY; + pVehicle->m_bSirenOrAlarm = false; + pVehicle->m_nCarHornTimer = 0; + } + if (FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || pVehicle->bIsLawEnforcer && + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())){ + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->m_bSirenOrAlarm = false; + if (CCullZones::NoPolice()) + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + + else if (pVehicle->bIsLawEnforcer) + MellowOutChaseSpeed(pVehicle); + break; + case MISSION_BLOCKPLAYER_FARAWAY: + if (FindSwitchDistanceClose(pVehicle) > (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() || + pVehicle->AutoPilot.m_bIgnorePathfinding) { + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKPLAYER_CLOSE; + if (pVehicle->UsesSiren(pVehicle->GetModelIndex())) + pVehicle->m_bSirenOrAlarm = true; + } + if (FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || pVehicle->bIsLawEnforcer && + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())) { + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->m_bSirenOrAlarm = false; + if (CCullZones::NoPolice()) + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + case MISSION_BLOCKPLAYER_CLOSE: + if (FindSwitchDistanceFar(pVehicle) >= (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() || + pVehicle->AutoPilot.m_bIgnorePathfinding) { + if (FindPlayerVehicle() && FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f) +#ifdef FIX_BUGS + pVehicle->m_nTimeBlocked += CTimer::GetTimeStepInMilliseconds(); +#else + pVehicle->m_nTimeBlocked += 1000.0f / 60.0f * CTimer::GetTimeStep(); +#endif + else + pVehicle->m_nTimeBlocked = 0; + if (!FindPlayerVehicle() || FindPlayerVehicle()->IsUpsideDown() || + FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f && pVehicle->m_nTimeBlocked > TIME_COPS_WAIT_TO_EXIT_AFTER_STOPPING) { + if (pVehicle->bIsLawEnforcer && + (pVehicle->GetModelIndex() != MI_RHINO || pVehicle->m_randomSeed > 10000) && + (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() < 10.0f) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + if (FindPlayerPed()->m_pWanted->GetWantedLevel() <= 1) + pVehicle->m_bSirenOrAlarm = false; + } + } + }else if (!CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, FindPlayerCoors(), true)) { + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKPLAYER_FARAWAY; + pVehicle->m_bSirenOrAlarm = false; + pVehicle->m_nCarHornTimer = 0; + } + if (FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || pVehicle->bIsLawEnforcer && + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())) { + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->m_bSirenOrAlarm = false; + if (CCullZones::NoPolice()) + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + if (pVehicle->bIsLawEnforcer) + MellowOutChaseSpeed(pVehicle); + break; + case MISSION_GOTOCOORDS: + if ((pVehicle->AutoPilot.m_vecDestinationCoors - pVehicle->GetPosition()).Magnitude2D() < DISTANCE_TO_SWITCH_DISTANCE_GOTO || + pVehicle->AutoPilot.m_bIgnorePathfinding) + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_STRAIGHT; + break; + case MISSION_GOTOCOORDS_STRAIGHT: + { + float distance = (pVehicle->AutoPilot.m_vecDestinationCoors - pVehicle->GetPosition()).Magnitude2D(); + if ((pVehicle->bIsAmbulanceOnDuty || pVehicle->bIsFireTruckOnDuty) && distance < 20.0f) + pVehicle->AutoPilot.m_nCarMission = MISSION_EMERGENCYVEHICLE_STOP; + if (distance < 5.0f){ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + } + else if (distance > FindSwitchDistanceFarNormalVehicle(pVehicle) && !pVehicle->AutoPilot.m_bIgnorePathfinding && (CTimer::GetFrameCounter() & 7) == 0){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nCarMission = (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, pVehicle->AutoPilot.m_vecDestinationCoors, true)) ? + MISSION_GOTOCOORDS_STRAIGHT : MISSION_GOTOCOORDS; + } + break; + } + case MISSION_EMERGENCYVEHICLE_STOP: + if (pVehicle->GetMoveSpeed().Magnitude2D() < 0.01f){ + if (pVehicle->bIsAmbulanceOnDuty){ + float distance = 30.0f; + if (gAccidentManager.FindNearestAccident(pVehicle->AutoPilot.m_vecDestinationCoors, &distance)){ + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_STOP_FOREVER; + }else{ + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->m_bSirenOrAlarm = false; + pVehicle->AutoPilot.m_nCruiseSpeed = 17; + if (pVehicle->bIsAmbulanceOnDuty){ + pVehicle->bIsAmbulanceOnDuty = false; + --CCarCtrl::NumAmbulancesOnDuty; + } + } + } + if (pVehicle->bIsFireTruckOnDuty) { + float distance = 30.0f; + if (gFireManager.FindNearestFire(pVehicle->AutoPilot.m_vecDestinationCoors, &distance)) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_STOP_FOREVER; + } + else { + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->m_bSirenOrAlarm = false; + pVehicle->AutoPilot.m_nCruiseSpeed = 17; + if (pVehicle->bIsFireTruckOnDuty) { + pVehicle->bIsFireTruckOnDuty = false; + --CCarCtrl::NumFiretrucksOnDuty; + } + } + } + } + break; + case MISSION_GOTOCOORDS_ACCURATE: + if ((pVehicle->AutoPilot.m_vecDestinationCoors - pVehicle->GetPosition()).Magnitude2D() < 20.0f || + pVehicle->AutoPilot.m_bIgnorePathfinding) + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTO_COORDS_STRAIGHT_ACCURATE; + break; + case MISSION_GOTO_COORDS_STRAIGHT_ACCURATE: + { + float distance = (pVehicle->AutoPilot.m_vecDestinationCoors - pVehicle->GetPosition()).Magnitude2D(); + if (distance < 1.0f) { + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + } + else if (distance > FindSwitchDistanceFarNormalVehicle(pVehicle) && !pVehicle->AutoPilot.m_bIgnorePathfinding && (CTimer::GetFrameCounter() & 7) == 0) { + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nCarMission = (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, pVehicle->AutoPilot.m_vecDestinationCoors, true)) ? + MISSION_GOTO_COORDS_STRAIGHT_ACCURATE : MISSION_GOTOCOORDS_ACCURATE; + } + break; + } + case MISSION_RAMCAR_FARAWAY: + if (pVehicle->AutoPilot.m_pTargetCar){ + if ((pVehicle->GetPosition() - pVehicle->AutoPilot.m_pTargetCar->GetPosition()).Magnitude2D() < FindSwitchDistanceClose(pVehicle) || + pVehicle->AutoPilot.m_bIgnorePathfinding) + pVehicle->AutoPilot.m_nCarMission = MISSION_RAMCAR_CLOSE; + }else{ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + case MISSION_RAMCAR_CLOSE: + if (pVehicle->AutoPilot.m_pTargetCar){ + if +#ifdef FIX_BUGS + (FindPlayerVehicle() == pVehicle->AutoPilot.m_pTargetCar && +#endif + (FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || pVehicle->bIsLawEnforcer && + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())) +#ifdef FIX_BUGS + ) +#endif + { + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->m_bSirenOrAlarm = false; + if (CCullZones::NoPolice()) + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + if ((pVehicle->AutoPilot.m_pTargetCar->GetPosition() - pVehicle->GetPosition()).Magnitude2D() <= FindSwitchDistanceFar(pVehicle) || + pVehicle->AutoPilot.m_bIgnorePathfinding){ + if (pVehicle->GetHasCollidedWith(pVehicle->AutoPilot.m_pTargetCar)){ + if (pVehicle->GetMoveSpeed().Magnitude() < 0.04f){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 800; + } + } + }else{ + pVehicle->AutoPilot.m_nCarMission = MISSION_RAMCAR_FARAWAY; + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + } + }else{ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + case MISSION_BLOCKCAR_FARAWAY: + if (pVehicle->AutoPilot.m_pTargetCar){ + if ((pVehicle->AutoPilot.m_pTargetCar->GetPosition() - pVehicle->GetPosition()).Magnitude2D() < FindSwitchDistanceClose(pVehicle) || + pVehicle->AutoPilot.m_bIgnorePathfinding){ + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKCAR_CLOSE; + if (pVehicle->UsesSiren(pVehicle->GetModelIndex())) + pVehicle->m_bSirenOrAlarm = true; + } + }else{ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + case MISSION_BLOCKCAR_CLOSE: + if (pVehicle->AutoPilot.m_pTargetCar){ + if ((pVehicle->AutoPilot.m_pTargetCar->GetPosition() - pVehicle->GetPosition()).Magnitude2D() > FindSwitchDistanceFar(pVehicle) && + !pVehicle->AutoPilot.m_bIgnorePathfinding){ + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKCAR_FARAWAY; + pVehicle->m_bSirenOrAlarm = false; + pVehicle->m_nCarHornTimer = 0; + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + } + }else{ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + default: + if (pVehicle->bIsLawEnforcer && FindPlayerPed()->m_pWanted->GetWantedLevel() > 0 && !CCullZones::NoPolice()){ + if (ABS(FindPlayerCoors().x - pVehicle->GetPosition().x) > 10.0f || + ABS(FindPlayerCoors().y - pVehicle->GetPosition().y) > 10.0f){ + pVehicle->AutoPilot.m_nCruiseSpeed = FindPoliceCarSpeedForWantedLevel(pVehicle); + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->AutoPilot.m_nCarMission = + FindPoliceCarMissionForWantedLevel(); + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + }else if (pVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE){ + pVehicle->SetStatus(STATUS_PHYSICS); + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + if (FindPlayerPed()->m_pWanted->GetWantedLevel() <= 1) + pVehicle->m_bSirenOrAlarm = false; + } + } + break; + } + break; + case STATUS_ABANDONED: + case STATUS_WRECKED: + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + break; + } + float flatSpeed = pVehicle->GetMoveSpeed().MagnitudeSqr2D(); + if (flatSpeed > SQR(0.018f)){ + pVehicle->AutoPilot.m_nTimeToStartMission = CTimer::GetTimeInMilliseconds(); + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + } + if (pVehicle->GetStatus() == STATUS_PHYSICS && pVehicle->AutoPilot.m_nTempAction == TEMPACT_NONE){ + if (pVehicle->AutoPilot.m_nCarMission != MISSION_NONE){ + if (pVehicle->AutoPilot.m_nCarMission != MISSION_STOP_FOREVER && + pVehicle->AutoPilot.m_nCruiseSpeed != 0 && + (pVehicle->VehicleCreatedBy != RANDOM_VEHICLE || pVehicle->AutoPilot.m_nCarMission != MISSION_CRUISE)){ + if (pVehicle->AutoPilot.m_nDrivingStyle != DRIVINGSTYLE_STOP_FOR_CARS + ) { + if (CTimer::GetTimeInMilliseconds() - pVehicle->m_nLastTimeCollided > 500) + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + if (flatSpeed < SQR(0.018f) && CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nAntiReverseTimer > 2000){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + if (pVehicle->AutoPilot.m_nCarMission != MISSION_NONE && + pVehicle->AutoPilot.m_nCarMission != MISSION_CRUISE || pVehicle->VehicleCreatedBy == MISSION_VEHICLE) + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 1500; + else + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 750; + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + if (pVehicle->VehicleCreatedBy == RANDOM_VEHICLE) + pVehicle->AutoPilot.m_nDrivingStyle = Max(DRIVINGSTYLE_AVOID_CARS, pVehicle->AutoPilot.m_nDrivingStyle); + pVehicle->PlayCarHorn(); + } + } + } + } + } + if ((pVehicle->m_randomSeed & 7) == 0){ + if (CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeToStartMission > 30000 && + CTimer::GetPreviousTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeToStartMission <= 30000 && + pVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE && + !CTrafficLights::ShouldCarStopForBridge(pVehicle)){ + pVehicle->SetStatus(STATUS_PHYSICS); + CCarCtrl::SwitchVehicleToRealPhysics(pVehicle); + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 400; + } + } + if (pVehicle->GetUp().z < -0.7f){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 1000; + } + if (pVehicle->AutoPilot.m_nTempAction == TEMPACT_NONE){ + switch (pVehicle->AutoPilot.m_nCarMission){ + case MISSION_RAMPLAYER_FARAWAY: + case MISSION_RAMPLAYER_CLOSE: + case MISSION_BLOCKPLAYER_FARAWAY: + case MISSION_BLOCKPLAYER_CLOSE: + if (FindPlayerVehicle() && FindPlayerSpeed().Magnitude() > pVehicle->GetMoveSpeed().Magnitude()){ + if (FindPlayerSpeed().Magnitude() > 0.1f){ + if (DotProduct2D(FindPlayerVehicle()->GetForward(), pVehicle->GetForward()) > 0.0f){ + CVector2D dist = pVehicle->GetPosition() - FindPlayerCoors(); + CVector2D speed = FindPlayerSpeed(); + if (0.5f * dist.Magnitude() * speed.Magnitude() < DotProduct2D(dist, speed)){ + if ((FindPlayerCoors() - pVehicle->GetPosition()).Magnitude() > 12.0f){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 500; + } + } + } + } + } + break; + default: break; + } + } + if (pVehicle->pDriver && pVehicle->pDriver->m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS){ + if ((pVehicle->GetPosition() - FindPlayerCoors()).Magnitude() < 15.0f){ + if (!FindPlayerVehicle() || pVehicle->GetHasCollidedWith(FindPlayerVehicle())){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 3000; + } + } + } + if (pVehicle->m_bSirenOrAlarm){ + if ((uint8)(pVehicle->m_randomSeed ^ CGeneral::GetRandomNumber()) == 0xAD) + pVehicle->m_nCarHornTimer = 45; + } +} + +void CCarAI::CarHasReasonToStop(CVehicle* pVehicle) +{ + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); +} + +float CCarAI::GetCarToGoToCoors(CVehicle* pVehicle, CVector* pTarget) +{ + if (pVehicle->AutoPilot.m_nCarMission != MISSION_GOTOCOORDS && pVehicle->AutoPilot.m_nCarMission != MISSION_GOTOCOORDS_STRAIGHT){ + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nCruiseSpeed = 20; + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->AutoPilot.m_nCarMission = (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, *pTarget, false)) ? + MISSION_GOTOCOORDS_STRAIGHT : MISSION_GOTOCOORDS; + }else if (Abs(pTarget->x - pVehicle->AutoPilot.m_vecDestinationCoors.x) > 2.0f || + Abs(pTarget->y - pVehicle->AutoPilot.m_vecDestinationCoors.y) > 2.0f){ + pVehicle->AutoPilot.m_vecDestinationCoors = *pTarget; + } + return (pVehicle->GetPosition() - *pTarget).Magnitude2D(); +} + +void CCarAI::AddPoliceCarOccupants(CVehicle* pVehicle) +{ + if (pVehicle->bOccupantsHaveBeenGenerated) + return; + pVehicle->bOccupantsHaveBeenGenerated = true; + switch (pVehicle->GetModelIndex()){ + case MI_FBICAR: + case MI_ENFORCER: + pVehicle->SetUpDriver(); + for (int i = 0; i < 3; i++) + pVehicle->SetupPassenger(i); + return; + case MI_POLICE: + case MI_RHINO: + case MI_BARRACKS: + pVehicle->SetUpDriver(); + if (FindPlayerPed()->m_pWanted->GetWantedLevel() > 1) + pVehicle->SetupPassenger(0); + return; + default: + return; + } +} + +void CCarAI::AddAmbulanceOccupants(CVehicle* pVehicle) +{ + pVehicle->SetUpDriver(); + pVehicle->SetupPassenger(1); +} + +void CCarAI::AddFiretruckOccupants(CVehicle* pVehicle) +{ + pVehicle->SetUpDriver(); + pVehicle->SetupPassenger(0); +} + +void CCarAI::TellOccupantsToLeaveCar(CVehicle* pVehicle) +{ + if (pVehicle->pDriver){ + pVehicle->pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + switch (pVehicle->GetModelIndex()) { + case MI_FIRETRUCK: + case MI_FBICAR: + case MI_ENFORCER: + case MI_BARRACKS: + case MI_RHINO: + case MI_POLICE: + break; + case MI_AMBULAN: + pVehicle->pDriver->Say(SOUND_PED_LEAVE_VEHICLE); + break; + } + } + int timer = 100; + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++){ + if (pVehicle->pPassengers[i]) { + pVehicle->pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + } + } +} + +void CCarAI::TellCarToRamOtherCar(CVehicle* pVehicle, CVehicle* pTarget) +{ + pVehicle->AutoPilot.m_pTargetCar = pTarget; + pTarget->RegisterReference((CEntity**)&pVehicle->AutoPilot.m_pTargetCar); + pVehicle->AutoPilot.m_nCarMission = MISSION_RAMCAR_FARAWAY; + pVehicle->bEngineOn = true; + pVehicle->AutoPilot.m_nCruiseSpeed = Max(6, pVehicle->AutoPilot.m_nCruiseSpeed); +} + +void CCarAI::TellCarToBlockOtherCar(CVehicle* pVehicle, CVehicle* pTarget) +{ + pVehicle->AutoPilot.m_pTargetCar = pTarget; + pTarget->RegisterReference((CEntity**)&pVehicle->AutoPilot.m_pTargetCar); + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKCAR_FARAWAY; + pVehicle->bEngineOn = true; + pVehicle->AutoPilot.m_nCruiseSpeed = Max(6, pVehicle->AutoPilot.m_nCruiseSpeed); +} + +uint8 CCarAI::FindPoliceCarMissionForWantedLevel() +{ + switch (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel()){ + case 0: + case 1: return MISSION_BLOCKPLAYER_FARAWAY; + case 2: return (CGeneral::GetRandomNumber() & 3) >= 3 ? MISSION_RAMPLAYER_FARAWAY : MISSION_BLOCKPLAYER_FARAWAY; + case 3: return (CGeneral::GetRandomNumber() & 3) >= 2 ? MISSION_RAMPLAYER_FARAWAY : MISSION_BLOCKPLAYER_FARAWAY; + case 4: + case 5: + case 6: return (CGeneral::GetRandomNumber() & 3) >= 1 ? MISSION_RAMPLAYER_FARAWAY : MISSION_BLOCKPLAYER_FARAWAY; + default: return MISSION_BLOCKPLAYER_FARAWAY; + } +} + +int32 CCarAI::FindPoliceCarSpeedForWantedLevel(CVehicle* pVehicle) +{ + switch (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel()) { + case 0: return CGeneral::GetRandomNumberInRange(12, 16); + case 1: return 25; + case 2: return 34; + case 3: return GAME_SPEED_TO_CARAI_SPEED * pVehicle->pHandling->Transmission.fMaxVelocity * 0.9f; + case 4: return GAME_SPEED_TO_CARAI_SPEED * pVehicle->pHandling->Transmission.fMaxVelocity * 1.2f; + case 5: return GAME_SPEED_TO_CARAI_SPEED * pVehicle->pHandling->Transmission.fMaxVelocity * 1.25f; + case 6: return GAME_SPEED_TO_CARAI_SPEED * pVehicle->pHandling->Transmission.fMaxVelocity * 1.3f; + default: return 0; + } +} + +void CCarAI::MellowOutChaseSpeed(CVehicle* pVehicle) +{ + if (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel() == 1){ + float distanceToPlayer = (pVehicle->GetPosition() - FindPlayerCoors()).Magnitude(); + if (FindPlayerVehicle()){ + if (distanceToPlayer < 10.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 15; + else if (distanceToPlayer < 20.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 22; + else + pVehicle->AutoPilot.m_nCruiseSpeed = 25; + }else{ + if (distanceToPlayer < 20.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 5; + else if (distanceToPlayer < 40.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 13; + else + pVehicle->AutoPilot.m_nCruiseSpeed = 25; + } + }else if (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel() == 2){ + float distanceToPlayer = (pVehicle->GetPosition() - FindPlayerCoors()).Magnitude(); + if (FindPlayerVehicle()) { + if (distanceToPlayer < 10.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 27; + else if (distanceToPlayer < 20.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 30; + else + pVehicle->AutoPilot.m_nCruiseSpeed = 34; + } + else { + if (distanceToPlayer < 20.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 5; + else if (distanceToPlayer < 40.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 18; + else + pVehicle->AutoPilot.m_nCruiseSpeed = 34; + } + } +} + +void CCarAI::MakeWayForCarWithSiren(CVehicle *pVehicle) +{ + float flatSpeed = pVehicle->GetMoveSpeed().Magnitude2D(); + if (flatSpeed < 0.1f) + return; + CVector2D forward = pVehicle->GetMoveSpeed() / flatSpeed; + float projection = flatSpeed * 45 + 20; + int i = CPools::GetVehiclePool()->GetSize(); + while (--i >= 0) { + CVehicle* vehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!vehicle) + continue; + if (!vehicle->IsCar() && !vehicle->IsBike()) + continue; + if (vehicle->GetStatus() != STATUS_SIMPLE && vehicle->GetStatus() != STATUS_PHYSICS) + continue; + if (vehicle->VehicleCreatedBy != RANDOM_VEHICLE) + continue; + if (vehicle->bIsLawEnforcer || vehicle->bIsAmbulanceOnDuty || vehicle->bIsFireTruckOnDuty) + continue; + if (vehicle == pVehicle) + continue; + if (Abs(pVehicle->GetPosition().z - vehicle->GetPosition().z) >= 5.0f) + continue; + CVector2D distance = vehicle->GetPosition() - pVehicle->GetPosition(); + if (distance.Magnitude() >= projection) + continue; + if (vehicle->GetMoveSpeed().Magnitude2D() <= 0.05f) + continue; + float correlation = DotProduct2D(forward, distance) / distance.Magnitude(); + if (correlation <= 0.0f) + continue; + if (correlation > 0.8f && DotProduct2D(forward, vehicle->GetForward()) > 0.7f){ + if (vehicle->AutoPilot.m_nTempAction != TEMPACT_SWERVELEFT && vehicle->AutoPilot.m_nTempAction != TEMPACT_SWERVERIGHT){ + vehicle->AutoPilot.m_nTempAction = (distance.x * forward.y - distance.y * forward.x > 0.0f) ? + TEMPACT_SWERVELEFT : TEMPACT_SWERVERIGHT; + vehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2000; + } + vehicle->SetStatus(STATUS_PHYSICS); + }else{ + if (DotProduct2D(vehicle->GetMoveSpeed(), distance) < 0.0f && vehicle->AutoPilot.m_nTempAction != TEMPACT_WAIT){ + vehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + vehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2000; + } + } + } +} diff --git a/src/control/CarAI.h b/src/control/CarAI.h new file mode 100644 index 0000000..9b731ad --- /dev/null +++ b/src/control/CarAI.h @@ -0,0 +1,26 @@ +#pragma once + +#include "AutoPilot.h" + +class CVehicle; + +class CCarAI +{ +public: + static float FindSwitchDistanceClose(CVehicle*); + static float FindSwitchDistanceFarNormalVehicle(CVehicle*); + static float FindSwitchDistanceFar(CVehicle*); + static void UpdateCarAI(CVehicle*); + static void CarHasReasonToStop(CVehicle*); + static float GetCarToGoToCoors(CVehicle*, CVector*); + static void AddPoliceCarOccupants(CVehicle*); + static void AddAmbulanceOccupants(CVehicle*); + static void AddFiretruckOccupants(CVehicle*); + static void TellOccupantsToLeaveCar(CVehicle*); + static void TellCarToRamOtherCar(CVehicle*, CVehicle*); + static void TellCarToBlockOtherCar(CVehicle*, CVehicle*); + static uint8 FindPoliceCarMissionForWantedLevel(); + static int32 FindPoliceCarSpeedForWantedLevel(CVehicle*); + static void MellowOutChaseSpeed(CVehicle*); + static void MakeWayForCarWithSiren(CVehicle *veh); +}; diff --git a/src/control/CarCtrl.cpp b/src/control/CarCtrl.cpp new file mode 100644 index 0000000..2085681 --- /dev/null +++ b/src/control/CarCtrl.cpp @@ -0,0 +1,2825 @@ +#include "common.h" + +#include "CarCtrl.h" + +#include "Accident.h" +#include "Automobile.h" +#include "Camera.h" +#include "CarAI.h" +#include "CarGen.h" +#include "Cranes.h" +#include "Curves.h" +#include "CutsceneMgr.h" +#include "Gangs.h" +#include "Garages.h" +#include "General.h" +#include "IniFile.h" +#include "ModelIndices.h" +#include "PathFind.h" +#include "Ped.h" +#include "PlayerInfo.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Pools.h" +#include "Renderer.h" +#include "RoadBlocks.h" +#include "Timer.h" +#include "TrafficLights.h" +#include "Streaming.h" +#include "VisibilityPlugins.h" +#include "Vehicle.h" +#include "Fire.h" +#include "World.h" +#include "Zones.h" + +#define DISTANCE_TO_SPAWN_ROADBLOCK_PEDS 51.0f +#define DISTANCE_TO_SCAN_FOR_DANGER 11.0f +#define SAFE_DISTANCE_TO_PED 3.0f +#define INFINITE_Z 1000000000.0f + +#define VEHICLE_HEIGHT_DIFF_TO_CONSIDER_WEAVING 4.0f +#define PED_HEIGHT_DIFF_TO_CONSIDER_WEAVING 4.0f +#define OBJECT_HEIGHT_DIFF_TO_CONSIDER_WEAVING 8.0f +#define WIDTH_COEF_TO_WEAVE_SAFELY 1.2f +#define OBJECT_WIDTH_TO_WEAVE 0.3f +#define PED_WIDTH_TO_WEAVE 0.8f + +#define PATH_DIRECTION_NONE 0 +#define PATH_DIRECTION_STRAIGHT 1 +#define PATH_DIRECTION_RIGHT 2 +#define PATH_DIRECTION_LEFT 4 + +#define ATTEMPTS_TO_FIND_NEXT_NODE 15 + +#define DISTANCE_TO_SWITCH_FROM_BLOCK_TO_STOP 5.0f +#define DISTANCE_TO_SWITCH_FROM_STOP_TO_BLOCK 10.0f +#define MAX_SPEED_TO_ACCOUNT_IN_INTERCEPTING 0.13f +#define DISTANCE_TO_NEXT_NODE_TO_CONSIDER_SLOWING_DOWN 40.0f +#define MAX_ANGLE_TO_STEER_AT_HIGH_SPEED 0.2f +#define MIN_SPEED_TO_START_LIMITING_STEER 0.45f +#define DISTANCE_TO_NEXT_NODE_TO_SELECT_NEW 5.0f +#define DISTANCE_TO_FACING_NEXT_NODE_TO_SELECT_NEW 8.0f +#define DEFAULT_MAX_STEER_ANGLE 0.5f +#define MIN_LOWERING_SPEED_COEFFICIENT 0.4f +#define MAX_ANGLE_FOR_SPEED_LIMITING 1.2f +#define MIN_ANGLE_FOR_SPEED_LIMITING 0.4f +#define MIN_ANGLE_FOR_SPEED_LIMITING_BETWEEN_NODES 0.1f +#define MIN_ANGLE_TO_APPLY_HANDBRAKE 0.7f +#define MIN_SPEED_TO_APPLY_HANDBRAKE 0.3f + +int CCarCtrl::NumLawEnforcerCars; +int CCarCtrl::NumAmbulancesOnDuty; +int CCarCtrl::NumFiretrucksOnDuty; +bool CCarCtrl::bCarsGeneratedAroundCamera; +float CCarCtrl::CarDensityMultiplier = 1.0f; +int32 CCarCtrl::NumMissionCars; +int32 CCarCtrl::NumRandomCars; +int32 CCarCtrl::NumParkedCars; +int32 CCarCtrl::NumPermanentCars; +int8 CCarCtrl::CountDownToCarsAtStart; +int32 CCarCtrl::MaxNumberOfCarsInUse = DEFAULT_MAX_NUMBER_OF_CARS; +uint32 CCarCtrl::LastTimeLawEnforcerCreated; +uint32 CCarCtrl::LastTimeFireTruckCreated; +uint32 CCarCtrl::LastTimeAmbulanceCreated; +int32 CCarCtrl::TotalNumOfCarsOfRating[TOTAL_CUSTOM_CLASSES]; +int32 CCarCtrl::NextCarOfRating[TOTAL_CUSTOM_CLASSES]; +int32 CCarCtrl::CarArrays[TOTAL_CUSTOM_CLASSES][MAX_CAR_MODELS_IN_ARRAY]; +CVehicle* apCarsToKeep[MAX_CARS_TO_KEEP]; +uint32 aCarsToKeepTime[MAX_CARS_TO_KEEP]; + +void +CCarCtrl::GenerateRandomCars() +{ + if (CCutsceneMgr::IsRunning()) + return; + if (NumRandomCars < 30){ + if (CountDownToCarsAtStart == 0){ + GenerateOneRandomCar(); + } + else if (--CountDownToCarsAtStart == 0) { + for (int i = 0; i < 50; i++) + GenerateOneRandomCar(); + CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter = 20; + } + } + /* Approximately once per 4 seconds. */ + if ((CTimer::GetTimeInMilliseconds() & 0xFFFFF000) != (CTimer::GetPreviousTimeInMilliseconds() & 0xFFFFF000)) + GenerateEmergencyServicesCar(); +} + +void +CCarCtrl::GenerateOneRandomCar() +{ + static int32 unk = 0; + CPlayerInfo* pPlayer = &CWorld::Players[CWorld::PlayerInFocus]; + CVector vecTargetPos = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + CVector2D vecPlayerSpeed = FindPlayerSpeed(); + CZoneInfo zone; + CTheZones::GetZoneInfoForTimeOfDay(&vecTargetPos, &zone); + pPlayer->m_nTrafficMultiplier = pPlayer->m_fRoadDensity * zone.carDensity; + if (NumRandomCars >= pPlayer->m_nTrafficMultiplier * CarDensityMultiplier * CIniFile::CarNumberMultiplier) + return; + if (NumFiretrucksOnDuty + NumAmbulancesOnDuty + NumParkedCars + NumMissionCars + NumLawEnforcerCars + NumRandomCars >= MaxNumberOfCarsInUse) + return; + CWanted* pWanted = pPlayer->m_pPed->m_pWanted; + int carClass; + int carModel; + if (pWanted->GetWantedLevel() > 1 && NumLawEnforcerCars < pWanted->m_MaximumLawEnforcerVehicles && + pWanted->m_CurrentCops < pWanted->m_MaxCops && ( + pWanted->GetWantedLevel() > 3 || + pWanted->GetWantedLevel() > 2 && CTimer::GetTimeInMilliseconds() > LastTimeLawEnforcerCreated + 5000 || + pWanted->GetWantedLevel() > 1 && CTimer::GetTimeInMilliseconds() > LastTimeLawEnforcerCreated + 8000)) { + /* Last pWanted->GetWantedLevel() > 1 is unnecessary but I added it for better readability. */ + /* Wouldn't be surprised it was there originally but was optimized out. */ + carClass = COPS; + carModel = ChoosePoliceCarModel(); + }else{ + carModel = ChooseModel(&zone, &vecTargetPos, &carClass); + if (carClass == COPS && pWanted->GetWantedLevel() >= 1) + /* All cop spawns with wanted level are handled by condition above. */ + /* In particular it means that cop cars never spawn if player has wanted level of 1. */ + return; + } + float frontX, frontY; + float preferredDistance, angleLimit; + bool invertAngleLimitTest; + CVector spawnPosition; + int32 curNodeId, nextNodeId; + float positionBetweenNodes; + bool testForCollision; + CVehicle* pPlayerVehicle = FindPlayerVehicle(); + CVector2D vecPlayerVehicleSpeed; + float fPlayerVehicleSpeed; + if (pPlayerVehicle) { + vecPlayerVehicleSpeed = FindPlayerVehicle()->GetMoveSpeed(); + fPlayerVehicleSpeed = vecPlayerVehicleSpeed.Magnitude(); + } + if (TheCamera.GetForward().z < -0.9f){ + /* Player uses topdown camera. */ + /* Spawn essentially anywhere. */ + frontX = frontY = 0.707f; /* 45 degrees */ + angleLimit = -1.0f; + invertAngleLimitTest = true; + preferredDistance = 40.0f; + /* BUG: testForCollision not initialized in original game. */ + testForCollision = false; + }else if (!pPlayerVehicle){ + /* Player is not in vehicle. */ + testForCollision = true; + frontX = TheCamera.CamFrontXNorm; + frontY = TheCamera.CamFrontYNorm; + switch (CTimer::GetFrameCounter() & 1) { + case 0: + /* Spawn a vehicle relatively far away from player. */ + /* Forward to his current direction (camera direction). */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 1: + /* Spawn a vehicle close to player to his side. */ + /* Kinda not within camera angle. */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = false; + preferredDistance = 40.0f; + break; + } + }else if (fPlayerVehicleSpeed > 0.4f){ /* 72 km/h */ + /* Player is moving fast in vehicle */ + /* Prefer spawning vehicles very far away from him. */ + frontX = vecPlayerVehicleSpeed.x / fPlayerVehicleSpeed; + frontY = vecPlayerVehicleSpeed.y / fPlayerVehicleSpeed; + testForCollision = false; + switch (CTimer::GetFrameCounter() & 3) { + case 0: + case 1: + /* Spawn a vehicle in a very narrow gap in front of a player */ + angleLimit = 0.85f; /* approx 30 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 2: + /* Spawn a vehicle relatively far away from player. */ + /* Forward to his current direction (camera direction). */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 3: + /* Spawn a vehicle close to player to his side. */ + /* Kinda not within camera angle. */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = false; + preferredDistance = 40.0f; + break; + } + }else if (fPlayerVehicleSpeed > 0.1f){ /* 18 km/h */ + /* Player is moving moderately fast in vehicle */ + /* Spawn more vehicles to player's side. */ + frontX = vecPlayerVehicleSpeed.x / fPlayerVehicleSpeed; + frontY = vecPlayerVehicleSpeed.y / fPlayerVehicleSpeed; + testForCollision = false; + switch (CTimer::GetFrameCounter() & 3) { + case 0: + /* Spawn a vehicle in a very narrow gap in front of a player */ + angleLimit = 0.85f; /* approx 30 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 1: + /* Spawn a vehicle relatively far away from player. */ + /* Forward to his current direction (camera direction). */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 2: + case 3: + /* Spawn a vehicle close to player to his side. */ + /* Kinda not within camera angle. */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = false; + preferredDistance = 40.0f; + break; + } + }else{ + /* Player is in vehicle but moving very slow. */ + /* Then use camera direction instead of vehicle direction. */ + testForCollision = true; + frontX = TheCamera.CamFrontXNorm; + frontY = TheCamera.CamFrontYNorm; + switch (CTimer::GetFrameCounter() & 1) { + case 0: + /* Spawn a vehicle relatively far away from player. */ + /* Forward to his current direction (camera direction). */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 1: + /* Spawn a vehicle close to player to his side. */ + /* Kinda not within camera angle. */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = false; + preferredDistance = 40.0f; + break; + } + } + if (!ThePaths.NewGenerateCarCreationCoors(vecTargetPos.x, vecTargetPos.y, frontX, frontY, + preferredDistance, angleLimit, invertAngleLimitTest, &spawnPosition, &curNodeId, &nextNodeId, + &positionBetweenNodes, carClass == COPS && pWanted->GetWantedLevel() >= 1)) + return; + int16 colliding; + CWorld::FindObjectsKindaColliding(spawnPosition, 10.0f, true, &colliding, 2, nil, false, true, true, false, false); + if (colliding) + /* If something is already present in spawn position, do not create vehicle*/ + return; + if (!ThePaths.TestCoorsCloseness(vecTargetPos, false, spawnPosition)) + /* Testing if spawn position can reach target position via valid path. */ + return; + int16 idInNode = 0; + CPathNode* pCurNode = &ThePaths.m_pathNodes[curNodeId]; + CPathNode* pNextNode = &ThePaths.m_pathNodes[nextNodeId]; + while (idInNode < pCurNode->numLinks && + ThePaths.ConnectedNode(idInNode + pCurNode->firstLink) != nextNodeId) + idInNode++; + int16 connectionId = ThePaths.m_carPathConnections[idInNode + pCurNode->firstLink]; + CCarPathLink* pPathLink = &ThePaths.m_carPathLinks[connectionId]; + int16 lanesOnCurrentRoad = pPathLink->pathNodeIndex == nextNodeId ? pPathLink->numLeftLanes : pPathLink->numRightLanes; + CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(carModel); + if (lanesOnCurrentRoad == 0 || pModelInfo->m_vehicleType == VEHICLE_TYPE_BIKE) + /* Not spawning vehicle if road is one way and intended direction is opposide to that way. */ + /* Also not spawning bikes but they don't exist in final game. */ + return; + CAutomobile* pVehicle = new CAutomobile(carModel, RANDOM_VEHICLE); + pVehicle->AutoPilot.m_nPrevRouteNode = 0; + pVehicle->AutoPilot.m_nCurrentRouteNode = curNodeId; + pVehicle->AutoPilot.m_nNextRouteNode = nextNodeId; + switch (carClass) { + case POOR: + case RICH: + case EXEC: + case WORKER: + case SPECIAL: + case BIG: + case TAXI: + case MAFIA: + case TRIAD: + case DIABLO: + case YAKUZA: + case YARDIE: + case COLOMB: + case NINES: + case GANG8: + case GANG9: + { + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(9, 14); + if (carClass == EXEC) + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(12, 18); + else if (carClass == POOR || carClass == SPECIAL) + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(7, 10); + CVehicleModelInfo* pVehicleInfo = pVehicle->GetModelInfo(); + if (pVehicleInfo->GetColModel()->boundingBox.max.y - pVehicle->GetModelInfo()->GetColModel()->boundingBox.min.y > 10.0f || carClass == BIG) { + pVehicle->AutoPilot.m_nCruiseSpeed *= 3; + pVehicle->AutoPilot.m_nCruiseSpeed /= 4; + } + pVehicle->AutoPilot.m_fMaxTrafficSpeed = pVehicle->AutoPilot.m_nCruiseSpeed; + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + break; + } + case COPS: + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + if (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel() != 0){ + pVehicle->AutoPilot.m_nCruiseSpeed = CCarAI::FindPoliceCarSpeedForWantedLevel(pVehicle); + pVehicle->AutoPilot.m_fMaxTrafficSpeed = pVehicle->AutoPilot.m_nCruiseSpeed / 2; + pVehicle->AutoPilot.m_nCarMission = CCarAI::FindPoliceCarMissionForWantedLevel(); + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + }else{ + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(12, 16); + pVehicle->AutoPilot.m_fMaxTrafficSpeed = pVehicle->AutoPilot.m_nCruiseSpeed; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + } + if (carModel == MI_FBICAR){ + pVehicle->m_currentColour1 = 0; + pVehicle->m_currentColour2 = 0; + /* FBI cars are gray in carcols, but we want them black if they going after player. */ + } + default: + break; + } + if (pVehicle && pVehicle->GetModelIndex() == MI_MRWHOOP) + pVehicle->m_bSirenOrAlarm = true; + pVehicle->AutoPilot.m_nNextPathNodeInfo = connectionId; + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane = CGeneral::GetRandomNumber() % lanesOnCurrentRoad; + CColBox* boundingBox = &CModelInfo::GetColModel(pVehicle->GetModelIndex())->boundingBox; + float carLength = 1.0f + (boundingBox->max.y - boundingBox->min.y) / 2; + float distanceBetweenNodes = (pCurNode->GetPosition() - pNextNode->GetPosition()).Magnitude2D(); + /* If car is so long that it doesn't fit between two car nodes, place it directly in the middle. */ + /* Otherwise put it at least in a way that full vehicle length fits between two nodes. */ + if (distanceBetweenNodes / 2 < carLength) + positionBetweenNodes = 0.5f; + else + positionBetweenNodes = Min(1.0f - carLength / distanceBetweenNodes, Max(carLength / distanceBetweenNodes, positionBetweenNodes)); + pVehicle->AutoPilot.m_nNextDirection = (curNodeId >= nextNodeId) ? 1 : -1; + if (pCurNode->numLinks == 1){ + /* Do not create vehicle if there is nowhere to go. */ + delete pVehicle; + return; + } + int16 nextConnection = pVehicle->AutoPilot.m_nNextPathNodeInfo; + int16 newLink; + while (nextConnection == pVehicle->AutoPilot.m_nNextPathNodeInfo){ + newLink = CGeneral::GetRandomNumber() % pCurNode->numLinks; + nextConnection = ThePaths.m_carPathConnections[newLink + pCurNode->firstLink]; + } + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = nextConnection; + pVehicle->AutoPilot.m_nCurrentDirection = (ThePaths.ConnectedNode(newLink + pCurNode->firstLink) >= curNodeId) ? 1 : -1; + CVector2D vecBetweenNodes = pNextNode->GetPosition() - pCurNode->GetPosition(); + float forwardX, forwardY; + float distBetweenNodes = vecBetweenNodes.Magnitude(); + if (distanceBetweenNodes == 0.0f){ + forwardX = 1.0f; + forwardY = 0.0f; + }else{ + forwardX = vecBetweenNodes.x / distBetweenNodes; + forwardY = vecBetweenNodes.y / distBetweenNodes; + } + /* I think the following might be some form of SetRotateZOnly. */ + /* Setting up direction between two car nodes. */ + pVehicle->GetForward() = CVector(forwardX, forwardY, 0.0f); + pVehicle->GetRight() = CVector(forwardY, -forwardX, 0.0f); + pVehicle->GetUp() = CVector(0.0f, 0.0f, 1.0f); + + float currentPathLinkForwardX = pVehicle->AutoPilot.m_nCurrentDirection * ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo].GetDirX(); + float currentPathLinkForwardY = pVehicle->AutoPilot.m_nCurrentDirection * ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo].GetDirY(); + float nextPathLinkForwardX = pVehicle->AutoPilot.m_nNextDirection * ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo].GetDirX(); + float nextPathLinkForwardY = pVehicle->AutoPilot.m_nNextDirection * ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo].GetDirY(); + +#ifdef FIX_BUGS + CCarPathLink* pCurrentLink; + CCarPathLink* pNextLink; + CVector positionOnCurrentLinkIncludingLane; + CVector positionOnNextLinkIncludingLane; + float directionCurrentLinkX; + float directionCurrentLinkY; + float directionNextLinkX; + float directionNextLinkY; + if (positionBetweenNodes < 0.5f) { + pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + positionOnCurrentLinkIncludingLane = CVector( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + positionOnNextLinkIncludingLane = CVector( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + directionCurrentLinkX = pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + directionCurrentLinkY = pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + /* We want to make a path between two links that may not have the same forward directions a curve. */ + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + directionCurrentLinkX, directionCurrentLinkY, + directionNextLinkX, directionNextLinkY + ) * (1000.0f / pVehicle->AutoPilot.m_fMaxTrafficSpeed); + pVehicle->AutoPilot.m_nTimeEnteredCurve = CTimer::GetTimeInMilliseconds() - + (uint32)((0.5f + positionBetweenNodes) * pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve); + } + else { + PickNextNodeRandomly(pVehicle); + pVehicle->AutoPilot.m_nTimeEnteredCurve = CTimer::GetTimeInMilliseconds() - + (uint32)((positionBetweenNodes - 0.5f) * pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve); + + pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + positionOnCurrentLinkIncludingLane = CVector( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + positionOnNextLinkIncludingLane = CVector( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + directionCurrentLinkX = pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + directionCurrentLinkY = pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + } +#else + + CCarPathLink* pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + CVector positionOnCurrentLinkIncludingLane( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + float directionCurrentLinkX = pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionCurrentLinkY = pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + /* We want to make a path between two links that may not have the same forward directions a curve. */ + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + directionCurrentLinkX, directionCurrentLinkY, + directionNextLinkX, directionNextLinkY + ) * (1000.0f / pVehicle->AutoPilot.m_fMaxTrafficSpeed); + pVehicle->AutoPilot.m_nTimeEnteredCurve = CTimer::GetTimeInMilliseconds() - + (0.5f + positionBetweenNodes) * pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve; +#endif + CVector directionCurrentLink(directionCurrentLinkX, directionCurrentLinkY, 0.0f); + CVector directionNextLink(directionNextLinkX, directionNextLinkY, 0.0f); + CVector positionIncludingCurve; + CVector directionIncludingCurve; + CCurves::CalcCurvePoint( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + &directionCurrentLink, + &directionNextLink, + GetPositionAlongCurrentCurve(pVehicle), + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve, + &positionIncludingCurve, + &directionIncludingCurve + ); + CVector vectorBetweenNodes = pCurNode->GetPosition() - pNextNode->GetPosition(); + CVector finalPosition = positionIncludingCurve + vectorBetweenNodes * 2.0f / vectorBetweenNodes.Magnitude(); + finalPosition.z = positionBetweenNodes * pNextNode->GetZ() + + (1.0f - positionBetweenNodes) * pCurNode->GetZ(); + float groundZ = INFINITE_Z; + CColPoint colPoint; + CEntity* pEntity; + if (CWorld::ProcessVerticalLine(finalPosition, 1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil)) + groundZ = colPoint.point.z; + if (CWorld::ProcessVerticalLine(finalPosition, -1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil)){ + if (ABS(colPoint.point.z - finalPosition.z) < ABS(groundZ - finalPosition.z)) + groundZ = colPoint.point.z; + } + if (groundZ == INFINITE_Z || ABS(groundZ - finalPosition.z) > 7.0f) { + /* Failed to find ground or too far from expected position. */ + delete pVehicle; + return; + } + finalPosition.z = groundZ + pVehicle->GetHeightAboveRoad(); + pVehicle->SetPosition(finalPosition); + pVehicle->SetMoveSpeed(directionIncludingCurve / GAME_SPEED_TO_CARAI_SPEED); + CVector2D speedDifferenceWithTarget = (CVector2D)pVehicle->GetMoveSpeed() - vecPlayerSpeed; + CVector2D distanceToTarget = positionIncludingCurve - vecTargetPos; + switch (carClass) { + case POOR: + case RICH: + case EXEC: + case WORKER: + case SPECIAL: + case BIG: + case TAXI: + case MAFIA: + case TRIAD: + case DIABLO: + case YAKUZA: + case YARDIE: + case COLOMB: + case NINES: + case GANG8: + case GANG9: + pVehicle->SetStatus(STATUS_SIMPLE); + break; + case COPS: + pVehicle->SetStatus((pVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE) ? STATUS_SIMPLE : STATUS_PHYSICS); + pVehicle->ChangeLawEnforcerState(1); + break; + default: + break; + } + CVisibilityPlugins::SetClumpAlpha(pVehicle->GetClump(), 0); + if (!pVehicle->GetIsOnScreen()){ + if ((vecTargetPos - pVehicle->GetPosition()).Magnitude2D() > 50.0f) { + /* Too far away cars that are not visible aren't needed. */ + delete pVehicle; + return; + } + }else if((vecTargetPos - pVehicle->GetPosition()).Magnitude2D() > TheCamera.GenerationDistMultiplier * 130.0f || + (vecTargetPos - pVehicle->GetPosition()).Magnitude2D() < TheCamera.GenerationDistMultiplier * 110.0f){ + delete pVehicle; + return; + }else if((TheCamera.GetPosition() - pVehicle->GetPosition()).Magnitude2D() < 90.0f * TheCamera.GenerationDistMultiplier){ + delete pVehicle; + return; + } + CVehicleModelInfo* pVehicleModel = pVehicle->GetModelInfo(); + float radiusToTest = pVehicleModel->GetColModel()->boundingSphere.radius; + if (testForCollision){ + CWorld::FindObjectsKindaColliding(pVehicle->GetPosition(), radiusToTest + 20.0f, true, &colliding, 2, nil, false, true, false, false, false); + if (colliding){ + delete pVehicle; + return; + } + } + CWorld::FindObjectsKindaColliding(pVehicle->GetPosition(), radiusToTest, true, &colliding, 2, nil, false, true, false, false, false); + if (colliding){ + delete pVehicle; + return; + } + if (speedDifferenceWithTarget.x * distanceToTarget.x + + speedDifferenceWithTarget.y * distanceToTarget.y >= 0.0f){ + delete pVehicle; + return; + } + pVehicleModel->AvoidSameVehicleColour(&pVehicle->m_currentColour1, &pVehicle->m_currentColour2); + CWorld::Add(pVehicle); + if (carClass == COPS) + CCarAI::AddPoliceCarOccupants(pVehicle); + else + pVehicle->SetUpDriver(); + if ((CGeneral::GetRandomNumber() & 0x3F) == 0){ /* 1/64 probability */ + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle->AutoPilot.m_nCruiseSpeed += 10; + } + if (carClass == COPS) + LastTimeLawEnforcerCreated = CTimer::GetTimeInMilliseconds(); +} + +int32 +CCarCtrl::ChooseModel(CZoneInfo* pZone, CVector* pPos, int* pClass) { + int32 model = -1; + while (model == -1 || !CStreaming::HasModelLoaded(model)){ + int rnd = CGeneral::GetRandomNumberInRange(0, 1000); + if (rnd < pZone->carThreshold[0]) + model = CCarCtrl::ChooseCarModel((*pClass = POOR)); + else if (rnd < pZone->carThreshold[1]) + model = CCarCtrl::ChooseCarModel((*pClass = RICH)); + else if (rnd < pZone->carThreshold[2]) + model = CCarCtrl::ChooseCarModel((*pClass = EXEC)); + else if (rnd < pZone->carThreshold[3]) + model = CCarCtrl::ChooseCarModel((*pClass = WORKER)); + else if (rnd < pZone->carThreshold[4]) + model = CCarCtrl::ChooseCarModel((*pClass = SPECIAL)); + else if (rnd < pZone->carThreshold[5]) + model = CCarCtrl::ChooseCarModel((*pClass = BIG)); + else if (rnd < pZone->copThreshold) + *pClass = COPS, model = CCarCtrl::ChoosePoliceCarModel(); + else if (rnd < pZone->gangThreshold[0]) + model = CCarCtrl::ChooseGangCarModel((*pClass = MAFIA) - MAFIA); + else if (rnd < pZone->gangThreshold[1]) + model = CCarCtrl::ChooseGangCarModel((*pClass = TRIAD) - MAFIA); + else if (rnd < pZone->gangThreshold[2]) + model = CCarCtrl::ChooseGangCarModel((*pClass = DIABLO) - MAFIA); + else if (rnd < pZone->gangThreshold[3]) + model = CCarCtrl::ChooseGangCarModel((*pClass = YAKUZA) - MAFIA); + else if (rnd < pZone->gangThreshold[4]) + model = CCarCtrl::ChooseGangCarModel((*pClass = YARDIE) - MAFIA); + else if (rnd < pZone->gangThreshold[5]) + model = CCarCtrl::ChooseGangCarModel((*pClass = COLOMB) - MAFIA); + else if (rnd < pZone->gangThreshold[6]) + model = CCarCtrl::ChooseGangCarModel((*pClass = NINES) - MAFIA); + else if (rnd < pZone->gangThreshold[7]) + model = CCarCtrl::ChooseGangCarModel((*pClass = GANG8) - MAFIA); + else if (rnd < pZone->gangThreshold[8]) + model = CCarCtrl::ChooseGangCarModel((*pClass = GANG9) - MAFIA); + else + model = CCarCtrl::ChooseCarModel((*pClass = TAXI)); + } + return model; +} + +int32 +CCarCtrl::ChooseCarModel(int32 vehclass) +{ + int32 model = -1; + switch (vehclass) { + case POOR: + case RICH: + case EXEC: + case WORKER: + case SPECIAL: + case BIG: + case TAXI: + { + if (TotalNumOfCarsOfRating[vehclass] == 0) + debug("ChooseCarModel : No cars of type %d have been declared\n", vehclass); + model = CarArrays[vehclass][NextCarOfRating[vehclass]]; + int32 total = TotalNumOfCarsOfRating[vehclass]; + NextCarOfRating[vehclass] += CGeneral::GetRandomNumberInRange(1, total); + while (NextCarOfRating[vehclass] >= total) + NextCarOfRating[vehclass] -= total; + //NextCarOfRating[vehclass] %= total; + TotalNumOfCarsOfRating[vehclass] = total; /* why... */ + } + default: + break; + } + return model; +} + +int32 +CCarCtrl::ChoosePoliceCarModel(void) +{ + if (FindPlayerPed()->m_pWanted->AreSwatRequired() && + CStreaming::HasModelLoaded(MI_ENFORCER) && + CStreaming::HasModelLoaded(MI_POLICE)) + return ((CGeneral::GetRandomNumber() & 0xF) == 0) ? MI_ENFORCER : MI_POLICE; + if (FindPlayerPed()->m_pWanted->AreFbiRequired() && + CStreaming::HasModelLoaded(MI_FBICAR) && + CStreaming::HasModelLoaded(MI_FBI)) + return MI_FBICAR; + if (FindPlayerPed()->m_pWanted->AreArmyRequired() && + CStreaming::HasModelLoaded(MI_RHINO) && + CStreaming::HasModelLoaded(MI_BARRACKS) && + CStreaming::HasModelLoaded(MI_ARMY)) + return CGeneral::GetRandomTrueFalse() ? MI_BARRACKS : MI_RHINO; + return MI_POLICE; +} + +int32 +CCarCtrl::ChooseGangCarModel(int32 gang) +{ + if (CStreaming::HasModelLoaded(MI_GANG01 + 2 * gang) && + CStreaming::HasModelLoaded(MI_GANG02 + 2 * gang)) + return CGangs::GetGangVehicleModel(gang); + return -1; +} + +void +CCarCtrl::AddToCarArray(int32 id, int32 vehclass) +{ + CarArrays[vehclass][TotalNumOfCarsOfRating[vehclass]++] = id; +} + +void +CCarCtrl::RemoveDistantCars() +{ + for (int i = CPools::GetVehiclePool()->GetSize()-1; i >= 0; i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + PossiblyRemoveVehicle(pVehicle); + if (pVehicle->bCreateRoadBlockPeds){ + if ((pVehicle->GetPosition() - FindPlayerCentreOfWorld(CWorld::PlayerInFocus)).Magnitude2D() < DISTANCE_TO_SPAWN_ROADBLOCK_PEDS) { + CRoadBlocks::GenerateRoadBlockCopsForCar(pVehicle, pVehicle->m_nRoadblockType, pVehicle->m_nRoadblockNode); + pVehicle->bCreateRoadBlockPeds = false; + } + } + } +} + +void +CCarCtrl::PossiblyRemoveVehicle(CVehicle* pVehicle) +{ +#ifdef FIX_BUGS + if (pVehicle->bIsLocked) + return; +#endif + CVector vecPlayerPos = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + /* BUG: this variable is initialized only in if-block below but can be used outside of it. */ + if (!IsThisVehicleInteresting(pVehicle) && !pVehicle->bIsLocked && + pVehicle->CanBeDeleted() && !CCranes::IsThisCarBeingTargettedByAnyCrane(pVehicle)){ + if (pVehicle->bFadeOut && CVisibilityPlugins::GetClumpAlpha(pVehicle->GetClump()) == 0){ + CWorld::Remove(pVehicle); + delete pVehicle; + return; + } + float distanceToPlayer = (pVehicle->GetPosition() - vecPlayerPos).Magnitude2D(); + float threshold = 50.0f; +#ifndef EXTENDED_OFFSCREEN_DESPAWN_RANGE + if (pVehicle->GetIsOnScreen() || + TheCamera.Cams[TheCamera.ActiveCam].LookingLeft || + TheCamera.Cams[TheCamera.ActiveCam].LookingRight || + TheCamera.Cams[TheCamera.ActiveCam].LookingBehind || + TheCamera.GetLookDirection() == 0 || + pVehicle->VehicleCreatedBy == PARKED_VEHICLE || + pVehicle->GetModelIndex() == MI_AMBULAN || + pVehicle->GetModelIndex() == MI_FIRETRUCK || + pVehicle->bIsLawEnforcer || + pVehicle->bIsCarParkVehicle + ) +#endif + { + threshold = 130.0f * TheCamera.GenerationDistMultiplier; + } + if (pVehicle->bExtendedRange) + threshold *= 1.5f; + if (distanceToPlayer > threshold && !CGarages::IsPointWithinHideOutGarage(pVehicle->GetPosition())){ + if (pVehicle->GetIsOnScreen() && CRenderer::IsEntityCullZoneVisible(pVehicle)) { + pVehicle->bFadeOut = true; + }else{ + CWorld::Remove(pVehicle); + delete pVehicle; + } + return; + } + } + if ((pVehicle->GetStatus() == STATUS_SIMPLE || pVehicle->GetStatus() == STATUS_PHYSICS && pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_STOP_FOR_CARS) && + CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeToStartMission > 5000 && + !pVehicle->GetIsOnScreen() && + (pVehicle->GetPosition() - vecPlayerPos).Magnitude2D() > 25.0f && + !IsThisVehicleInteresting(pVehicle) && + !pVehicle->bIsLocked && + pVehicle->CanBeDeleted() && + !CTrafficLights::ShouldCarStopForLight(pVehicle, true) && + !CTrafficLights::ShouldCarStopForBridge(pVehicle) && + !CGarages::IsPointWithinHideOutGarage(pVehicle->GetPosition())){ + CWorld::Remove(pVehicle); + delete pVehicle; + return; + } + if (pVehicle->GetStatus() == STATUS_WRECKED) { + if (pVehicle->m_nTimeOfDeath != 0) { + if (CTimer::GetTimeInMilliseconds() > pVehicle->m_nTimeOfDeath + 60000 && + !(pVehicle->GetIsOnScreen() && CRenderer::IsEntityCullZoneVisible(pVehicle))) { + if ((pVehicle->GetPosition() - vecPlayerPos).MagnitudeSqr() > SQR(7.5f)) { + if (!CGarages::IsPointWithinHideOutGarage(pVehicle->GetPosition())) { + CWorld::Remove(pVehicle); + delete pVehicle; + } + } + } + } + } +} + +int32 +CCarCtrl::CountCarsOfType(int32 mi) +{ + int32 total = 0; + for (int i = CPools::GetVehiclePool()->GetSize()-1; i >= 0; i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (pVehicle->GetModelIndex() == mi) + total++; + } + return total; +} + +void +CCarCtrl::UpdateCarOnRails(CVehicle* pVehicle) +{ + if (pVehicle->AutoPilot.m_nTempAction == TEMPACT_WAIT){ + pVehicle->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pVehicle->AutoPilot.ModifySpeed(0.0f); + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTempAction){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + pVehicle->AutoPilot.m_nTimeToStartMission = CTimer::GetTimeInMilliseconds(); + } + return; + } + SlowCarOnRailsDownForTrafficAndLights(pVehicle); + if (pVehicle->AutoPilot.m_nTimeEnteredCurve + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve <= CTimer::GetTimeInMilliseconds()) + PickNextNodeAccordingStrategy(pVehicle); + if (pVehicle->GetStatus() == STATUS_PHYSICS) + return; + CCarPathLink* pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + float currentPathLinkForwardX = pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + float currentPathLinkForwardY = pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + float nextPathLinkForwardX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float nextPathLinkForwardY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + CVector positionOnCurrentLinkIncludingLane( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + CVector directionCurrentLink(currentPathLinkForwardX, currentPathLinkForwardY, 0.0f); + CVector directionNextLink(nextPathLinkForwardX, nextPathLinkForwardY, 0.0f); + CVector positionIncludingCurve; + CVector directionIncludingCurve; + CCurves::CalcCurvePoint( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + &directionCurrentLink, + &directionNextLink, + GetPositionAlongCurrentCurve(pVehicle), + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve, + &positionIncludingCurve, + &directionIncludingCurve + ); + positionIncludingCurve.z = 15.0f; + DragCarToPoint(pVehicle, &positionIncludingCurve); + pVehicle->SetMoveSpeed(directionIncludingCurve / GAME_SPEED_TO_CARAI_SPEED); +} + +float +CCarCtrl::FindMaximumSpeedForThisCarInTraffic(CVehicle* pVehicle) +{ + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_AVOID_CARS || + pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_PLOUGH_THROUGH) + return pVehicle->AutoPilot.m_nCruiseSpeed; + float left = pVehicle->GetPosition().x - DISTANCE_TO_SCAN_FOR_DANGER; + float right = pVehicle->GetPosition().x + DISTANCE_TO_SCAN_FOR_DANGER; + float top = pVehicle->GetPosition().y - DISTANCE_TO_SCAN_FOR_DANGER; + float bottom = pVehicle->GetPosition().y + DISTANCE_TO_SCAN_FOR_DANGER; + int xstart = Max(0, CWorld::GetSectorIndexX(left)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(right)); + int ystart = Max(0, CWorld::GetSectorIndexY(top)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(bottom)); + assert(xstart <= xend); + assert(ystart <= yend); + + float maxSpeed = pVehicle->AutoPilot.m_nCruiseSpeed; + + CWorld::AdvanceCurrentScanCode(); + + for (int y = ystart; y <= yend; y++){ + for (int x = xstart; x <= xend; x++){ + CSector* s = CWorld::GetSector(x, y); + SlowCarDownForCarsSectorList(s->m_lists[ENTITYLIST_VEHICLES], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + SlowCarDownForCarsSectorList(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + SlowCarDownForPedsSectorList(s->m_lists[ENTITYLIST_PEDS], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + SlowCarDownForPedsSectorList(s->m_lists[ENTITYLIST_PEDS_OVERLAP], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + } + } + pVehicle->bWarnedPeds = true; + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_STOP_FOR_CARS) + return maxSpeed; + return (maxSpeed + pVehicle->AutoPilot.m_nCruiseSpeed) / 2; +} + +void +CCarCtrl::ScanForPedDanger(CVehicle* pVehicle) +{ + bool storedSlowDownFlag = pVehicle->AutoPilot.m_bSlowedDownBecauseOfPeds; + float left = pVehicle->GetPosition().x - DISTANCE_TO_SCAN_FOR_DANGER; + float right = pVehicle->GetPosition().x + DISTANCE_TO_SCAN_FOR_DANGER; + float top = pVehicle->GetPosition().y - DISTANCE_TO_SCAN_FOR_DANGER; + float bottom = pVehicle->GetPosition().y + DISTANCE_TO_SCAN_FOR_DANGER; + int xstart = Max(0, CWorld::GetSectorIndexX(left)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(right)); + int ystart = Max(0, CWorld::GetSectorIndexY(top)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(bottom)); + assert(xstart <= xend); + assert(ystart <= yend); + + float maxSpeed = pVehicle->AutoPilot.m_nCruiseSpeed; + + CWorld::AdvanceCurrentScanCode(); + + for (int y = ystart; y <= yend; y++) { + for (int x = xstart; x <= xend; x++) { + CSector* s = CWorld::GetSector(x, y); + SlowCarDownForPedsSectorList(s->m_lists[ENTITYLIST_PEDS], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + SlowCarDownForPedsSectorList(s->m_lists[ENTITYLIST_PEDS_OVERLAP], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + } + } + pVehicle->bWarnedPeds = true; + pVehicle->AutoPilot.m_bSlowedDownBecauseOfPeds = storedSlowDownFlag; +} + +void +CCarCtrl::SlowCarOnRailsDownForTrafficAndLights(CVehicle* pVehicle) +{ + float maxSpeed; + if (CTrafficLights::ShouldCarStopForLight(pVehicle, false) || CTrafficLights::ShouldCarStopForBridge(pVehicle)){ + CCarAI::CarHasReasonToStop(pVehicle); + maxSpeed = 0.0f; + }else{ + maxSpeed = FindMaximumSpeedForThisCarInTraffic(pVehicle); + } + float curSpeed = pVehicle->AutoPilot.m_fMaxTrafficSpeed; + if (maxSpeed >= curSpeed){ + if (maxSpeed > curSpeed) + pVehicle->AutoPilot.ModifySpeed(Min(maxSpeed, curSpeed + 0.05f * CTimer::GetTimeStep())); + }else if (curSpeed != 0.0f) { + if (curSpeed < 0.1f) + pVehicle->AutoPilot.ModifySpeed(0.0f); + else + pVehicle->AutoPilot.ModifySpeed(Max(maxSpeed, curSpeed - 0.5f * CTimer::GetTimeStep())); + } +} + +void CCarCtrl::SlowCarDownForPedsSectorList(CPtrList& lst, CVehicle* pVehicle, float x_inf, float y_inf, float x_sup, float y_sup, float* pSpeed, float curSpeed) +{ + float frontOffset = pVehicle->GetModelInfo()->GetColModel()->boundingBox.max.y; + float frontSafe = frontOffset + SAFE_DISTANCE_TO_PED; + for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next){ + CPed* pPed = (CPed*)pNode->item; + if (pPed->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + if (!pPed->bUsesCollision) + continue; + pPed->m_scanCode = CWorld::GetCurrentScanCode(); + CVector vecPedPos = pPed->GetPosition(); + if (vecPedPos.x < x_inf || vecPedPos.x > x_sup) + continue; + if (vecPedPos.y < y_inf || vecPedPos.y > y_sup) + continue; + if (ABS(vecPedPos.z - pVehicle->GetPosition().z) >= 4.0f) + continue; + CVector vecToPed = vecPedPos - pVehicle->GetPosition(); + float dotDirection = DotProduct(pVehicle->GetForward(), vecToPed); + float dotVelocity = DotProduct(pVehicle->GetForward(), pVehicle->GetMoveSpeed()); + if (dotDirection <= frontOffset) /* If already run him over, don't care */ + continue; + float distanceUntilHit = dotDirection - frontOffset; + float movementTowardsPedPerSecond = GAME_SPEED_TO_METERS_PER_SECOND * dotVelocity; + if (4 * movementTowardsPedPerSecond <= distanceUntilHit) + /* If car isn't projected to hit a ped in 4 seconds, don't care */ + continue; + float sidewaysDistance = ABS(DotProduct(pVehicle->GetRight(), vecToPed)); + float sideLength = pVehicle->GetModelInfo()->GetColModel()->boundingBox.max.x; + if (pVehicle->m_vehType == VEHICLE_TYPE_BIKE) + sideLength *= 1.6f; + if (sideLength + 0.5f < sidewaysDistance) + /* If car is far enough taking side into account, don't care */ + continue; + if (pPed->IsPed()){ /* ...how can it not be? */ + if (pPed->GetPedState() != PED_STEP_AWAY && pPed->GetPedState() != PED_DIVE_AWAY){ + if (distanceUntilHit < movementTowardsPedPerSecond){ + /* Very close. Time to evade. */ + if (pVehicle->GetModelIndex() == MI_RCBANDIT){ + if (dotVelocity * GAME_SPEED_TO_METERS_PER_SECOND / 2 > distanceUntilHit) + pPed->SetEvasiveStep(pVehicle, 0); + } + else if (dotVelocity > 0.3f) { + if (sideLength + 0.1f < sidewaysDistance) + pPed->SetEvasiveStep(pVehicle, 0); + else + pPed->SetEvasiveDive(pVehicle, 0); + } + else if (dotVelocity > 0.1f) { + if (sideLength - 0.5f < sidewaysDistance) + pPed->SetEvasiveStep(pVehicle, 0); + else + pPed->SetEvasiveDive(pVehicle, 0); + } + }else{ + /* Relatively safe but annoying. */ + if (pVehicle->GetStatus() == STATUS_PLAYER && + pPed->GetPedState() != PED_FLEE_ENTITY && + pPed->CharCreatedBy == RANDOM_CHAR){ + float angleCarToPed = CGeneral::GetRadianAngleBetweenPoints( + pVehicle->GetPosition().x, pVehicle->GetPosition().y, + pPed->GetPosition().x, pPed->GetPosition().y + ); + angleCarToPed = CGeneral::LimitRadianAngle(angleCarToPed); + pPed->m_headingRate = CGeneral::LimitRadianAngle(pPed->m_headingRate); + float visibilityAngle = ABS(angleCarToPed - pPed->m_headingRate); + if (visibilityAngle > PI) + visibilityAngle = TWOPI - visibilityAngle; + if (visibilityAngle < HALFPI || pVehicle->m_nCarHornTimer){ + /* if ped sees the danger or if car horn is on */ + pPed->SetFlee(pVehicle, 2000); + pPed->bUsePedNodeSeek = false; + pPed->SetMoveState(PEDMOVE_RUN); + } + }else{ + CPlayerPed* pPlayerPed = (CPlayerPed*)pPed; + if (pPlayerPed->IsPlayer() && dotDirection < frontSafe && + pPlayerPed->IsPedInControl() && + pPlayerPed->m_fMoveSpeed < 1.0f && !pPlayerPed->bIsLooking && + CTimer::GetTimeInMilliseconds() > pPlayerPed->m_lookTimer) { + pPlayerPed->AnnoyPlayerPed(false); + pPlayerPed->SetLookFlag(pVehicle, true); + pPlayerPed->SetLookTimer(1500); + if (pPlayerPed->GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED || + pPlayerPed->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT || + pPlayerPed->GetWeapon()->m_eWeaponType == WEAPONTYPE_COLT45 || + pPlayerPed->GetWeapon()->m_eWeaponType == WEAPONTYPE_UZI) { + pPlayerPed->bShakeFist = true; + } + } + } + } + } + } + /* Ped stuff done. Now vehicle stuff. */ + if (distanceUntilHit < 10.0f){ + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_STOP_FOR_CARS || + pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_SLOW_DOWN_FOR_CARS){ + *pSpeed = Min(*pSpeed, ABS(distanceUntilHit - 1.0f) * 0.1f * curSpeed); + pVehicle->AutoPilot.m_bSlowedDownBecauseOfPeds = true; + if (distanceUntilHit < 2.0f){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 3000; + } + } + } + } +} + +void CCarCtrl::SlowCarDownForCarsSectorList(CPtrList& lst, CVehicle* pVehicle, float x_inf, float y_inf, float x_sup, float y_sup, float* pSpeed, float curSpeed) +{ + for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next){ + CVehicle* pTestVehicle = (CVehicle*)pNode->item; + if (pVehicle == pTestVehicle) + continue; + if (pTestVehicle->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + if (!pTestVehicle->bUsesCollision) + continue; + pTestVehicle->m_scanCode = CWorld::GetCurrentScanCode(); + CVector boundCenter = pTestVehicle->GetBoundCentre(); + if (boundCenter.x < x_inf || boundCenter.x > x_sup) + continue; + if (boundCenter.y < y_inf || boundCenter.y > y_sup) + continue; + if (Abs(boundCenter.z - pVehicle->GetPosition().z) < 5.0f) + SlowCarDownForOtherCar(pTestVehicle, pVehicle, pSpeed, curSpeed); + } +} + +void CCarCtrl::SlowCarDownForOtherCar(CEntity* pOtherEntity, CVehicle* pVehicle, float* pSpeed, float curSpeed) +{ + CVector forwardA = pVehicle->GetForward(); + ((CVector2D)forwardA).NormaliseSafe(); + if (DotProduct2D(pOtherEntity->GetPosition() - pVehicle->GetPosition(), forwardA) < 0.0f) + return; + CVector forwardB = pOtherEntity->GetForward(); + ((CVector2D)forwardB).NormaliseSafe(); + forwardA.z = forwardB.z = 0.0f; + CVehicle* pOtherVehicle = (CVehicle*)pOtherEntity; + /* why is the argument CEntity if it's always CVehicle anyway and is casted? */ + float speedOtherX = GAME_SPEED_TO_CARAI_SPEED * pOtherVehicle->GetMoveSpeed().x; + float speedOtherY = GAME_SPEED_TO_CARAI_SPEED * pOtherVehicle->GetMoveSpeed().y; + float projectionX = speedOtherX - forwardA.x * curSpeed; + float projectionY = speedOtherY - forwardA.y * curSpeed; + float proximityA = TestCollisionBetween2MovingRects(pOtherVehicle, pVehicle, projectionX, projectionY, &forwardA, &forwardB, 0); + float proximityB = TestCollisionBetween2MovingRects(pVehicle, pOtherVehicle, -projectionX, -projectionY, &forwardB, &forwardA, 1); + float minProximity = Min(proximityA, proximityB); + if (minProximity >= 0.0f && minProximity < 1.0f){ + minProximity = Max(0.0f, (minProximity - 0.2f) * 1.25f); + pVehicle->AutoPilot.m_bSlowedDownBecauseOfCars = true; + *pSpeed = Min(*pSpeed, minProximity * curSpeed); + } + if (minProximity >= 0.0f && minProximity < 0.5f && pOtherEntity->IsVehicle() && + CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeToStartMission > 15000 && + CTimer::GetTimeInMilliseconds() - pOtherVehicle->AutoPilot.m_nTimeToStartMission > 15000){ + /* If cars are standing for 15 seconds, annoy one of them and make avoid cars. */ + if (pOtherEntity != FindPlayerVehicle() && + DotProduct2D(pVehicle->GetForward(), pOtherVehicle->GetForward()) < -0.5f && + pVehicle < pOtherVehicle){ /* that comparasion though... */ + *pSpeed = Max(curSpeed / 5, *pSpeed); + if (pVehicle->GetStatus() == STATUS_SIMPLE){ + pVehicle->SetStatus(STATUS_PHYSICS); + SwitchVehicleToRealPhysics(pVehicle); + } + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 1000; + } + } +} + +float CCarCtrl::TestCollisionBetween2MovingRects(CVehicle* pVehicleA, CVehicle* pVehicleB, float projectionX, float projectionY, CVector* pForwardA, CVector* pForwardB, uint8 id) +{ + CVector2D vecBToA = pVehicleA->GetPosition() - pVehicleB->GetPosition(); + float lenB = pVehicleB->GetModelInfo()->GetColModel()->boundingBox.max.y; + float widthB = pVehicleB->GetModelInfo()->GetColModel()->boundingBox.max.x; + float backLenB = -pVehicleB->GetModelInfo()->GetColModel()->boundingBox.min.y; + float lenA = pVehicleA->GetModelInfo()->GetColModel()->boundingBox.max.y; + float widthA = pVehicleA->GetModelInfo()->GetColModel()->boundingBox.max.x; + float backLenA = -pVehicleA->GetModelInfo()->GetColModel()->boundingBox.min.y; + float proximity = 1.0f; + float fullWidthB = 2.0f * widthB; + float fullLenB = lenB + backLenB; + for (int i = 0; i < 4; i++){ + float testedOffsetX; + float testedOffsetY; + switch (i) { + case 0: /* Front right corner */ + testedOffsetX = vecBToA.x + widthA * pForwardB->y + lenA * pForwardB->x; + testedOffsetY = vecBToA.y + lenA * pForwardB->y - widthA * pForwardB->x; + break; + case 1: /* Front left corner */ + testedOffsetX = vecBToA.x + -widthA * pForwardB->x + lenA * pForwardB->x; + testedOffsetY = vecBToA.y + lenA * pForwardB->y + widthA * pForwardB->x; + break; + case 2: /* Rear right corner */ + testedOffsetX = vecBToA.x + widthA * pForwardB->y - backLenA * pForwardB->x; + testedOffsetY = vecBToA.y - backLenA * pForwardB->y - widthA * pForwardB->x; + break; + case 3: /* Rear left corner */ + testedOffsetX = vecBToA.x - widthA * pForwardB->y - backLenA * pForwardB->x; + testedOffsetY = vecBToA.y - backLenA * pForwardB->y + widthA * pForwardB->x; + break; + default: + break; + } + /* Testing width collision */ + float baseWidthProximity = 0.0f; + float fullWidthProximity = 1.0f; + float widthDistance = testedOffsetX * pForwardA->y - testedOffsetY * pForwardA->x; + float widthProjection = projectionX * pForwardA->y - projectionY * pForwardA->x; + if (widthDistance > widthB){ + if (widthProjection < 0.0f){ + float proximityWidth = -(widthDistance - widthB) / widthProjection; + if (proximityWidth < 1.0f){ + baseWidthProximity = proximityWidth; + fullWidthProximity = Min(1.0f, proximityWidth - fullWidthB / widthProjection); + }else{ + baseWidthProximity = 1.0f; + } + }else{ + baseWidthProximity = 1.0f; + fullWidthProximity = 1.0f; + } + }else if (widthDistance < -widthB){ + if (widthProjection > 0.0f) { + float proximityWidth = -(widthDistance + widthB) / widthProjection; + if (proximityWidth < 1.0f) { + baseWidthProximity = proximityWidth; + fullWidthProximity = Min(1.0f, proximityWidth + fullWidthB / widthProjection); + } + else { + baseWidthProximity = 1.0f; + } + } + else { + baseWidthProximity = 1.0f; + fullWidthProximity = 1.0f; + } + }else if (widthProjection > 0.0f){ + fullWidthProximity = (widthB - widthDistance) / widthProjection; + }else if (widthProjection < 0.0f){ + fullWidthProximity = -(widthB + widthDistance) / widthProjection; + } + /* Testing length collision */ + float baseLengthProximity = 0.0f; + float fullLengthProximity = 1.0f; + float lenDistance = testedOffsetX * pForwardA->x + testedOffsetY * pForwardA->y; + float lenProjection = projectionX * pForwardA->x + projectionY * pForwardA->y; + if (lenDistance > lenB) { + if (lenProjection < 0.0f) { + float proximityLength = -(lenDistance - lenB) / lenProjection; + if (proximityLength < 1.0f) { + baseLengthProximity = proximityLength; + fullLengthProximity = Min(1.0f, proximityLength - fullLenB / lenProjection); + } + else { + baseLengthProximity = 1.0f; + } + } + else { + baseLengthProximity = 1.0f; + fullLengthProximity = 1.0f; + } + } + else if (lenDistance < -backLenB) { + if (lenProjection > 0.0f) { + float proximityLength = -(lenDistance + backLenB) / lenProjection; + if (proximityLength < 1.0f) { + baseLengthProximity = proximityLength; + fullLengthProximity = Min(1.0f, proximityLength + fullLenB / lenProjection); + } + else { + baseLengthProximity = 1.0f; + } + } + else { + baseLengthProximity = 1.0f; + fullLengthProximity = 1.0f; + } + } + else if (lenProjection > 0.0f) { + fullLengthProximity = (lenB - lenDistance) / lenProjection; + } + else if (lenProjection < 0.0f) { + fullLengthProximity = -(backLenB + lenDistance) / lenProjection; + } + float baseProximity = Max(baseWidthProximity, baseLengthProximity); + if (baseProximity < fullWidthProximity && baseProximity < fullLengthProximity) + proximity = Min(proximity, baseProximity); + } + return proximity; +} + +float CCarCtrl::FindAngleToWeaveThroughTraffic(CVehicle* pVehicle, CPhysical* pTarget, float angleToTarget, float angleForward) +{ + float distanceToTest = Min(2.0f, pVehicle->GetMoveSpeed().Magnitude2D() * 2.5f + 1.0f) * 12.0f; + float left = pVehicle->GetPosition().x - distanceToTest; + float right = pVehicle->GetPosition().x + distanceToTest; + float top = pVehicle->GetPosition().y - distanceToTest; + float bottom = pVehicle->GetPosition().y + distanceToTest; + int xstart = Max(0, CWorld::GetSectorIndexX(left)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(right)); + int ystart = Max(0, CWorld::GetSectorIndexY(top)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(bottom)); + assert(xstart <= xend); + assert(ystart <= yend); + + float angleToWeaveLeft = angleToTarget; + float angleToWeaveRight = angleToTarget; + + CWorld::AdvanceCurrentScanCode(); + + float angleToWeaveLeftLastIteration = -9999.9f; + float angleToWeaveRightLastIteration = -9999.9f; + + while (angleToWeaveLeft != angleToWeaveLeftLastIteration || + angleToWeaveRight != angleToWeaveRightLastIteration){ + angleToWeaveLeftLastIteration = angleToWeaveLeft; + angleToWeaveRightLastIteration = angleToWeaveRight; + for (int y = ystart; y <= yend; y++) { + for (int x = xstart; x <= xend; x++) { + CSector* s = CWorld::GetSector(x, y); + WeaveThroughCarsSectorList(s->m_lists[ENTITYLIST_VEHICLES], pVehicle, pTarget, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + WeaveThroughCarsSectorList(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP], pVehicle, pTarget, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + WeaveThroughPedsSectorList(s->m_lists[ENTITYLIST_PEDS], pVehicle, pTarget, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + WeaveThroughPedsSectorList(s->m_lists[ENTITYLIST_PEDS_OVERLAP], pVehicle, pTarget, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + WeaveThroughObjectsSectorList(s->m_lists[ENTITYLIST_OBJECTS], pVehicle, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + WeaveThroughObjectsSectorList(s->m_lists[ENTITYLIST_OBJECTS_OVERLAP], pVehicle, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + } + } + } + float angleDiffFromActualToTarget = LimitRadianAngle(angleForward - angleToTarget); + float angleToBisectActualToTarget = LimitRadianAngle(angleToTarget + angleDiffFromActualToTarget / 2); + float angleDiffLeft = LimitRadianAngle(angleToWeaveLeft - angleToBisectActualToTarget); + angleDiffLeft = ABS(angleDiffLeft); + float angleDiffRight = LimitRadianAngle(angleToWeaveRight - angleToBisectActualToTarget); + angleDiffRight = ABS(angleDiffRight); + if (angleDiffLeft > HALFPI && angleDiffRight > HALFPI) + return angleToBisectActualToTarget; + if (ABS(angleDiffLeft - angleDiffRight) < 0.08f) + return angleToWeaveRight; + return angleDiffLeft < angleDiffRight ? angleToWeaveLeft : angleToWeaveRight; +} + +void CCarCtrl::WeaveThroughCarsSectorList(CPtrList& lst, CVehicle* pVehicle, CPhysical* pTarget, float x_inf, float y_inf, float x_sup, float y_sup, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next) { + CVehicle* pTestVehicle = (CVehicle*)pNode->item; + if (pTestVehicle->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + if (!pTestVehicle->bUsesCollision) + continue; + if (pTestVehicle == pTarget) + continue; + pTestVehicle->m_scanCode = CWorld::GetCurrentScanCode(); + if (pTestVehicle->GetBoundCentre().x < x_inf || pTestVehicle->GetBoundCentre().x > x_sup) + continue; + if (pTestVehicle->GetBoundCentre().y < y_inf || pTestVehicle->GetBoundCentre().y > y_sup) + continue; + if (Abs(pTestVehicle->GetPosition().z - pVehicle->GetPosition().z) >= VEHICLE_HEIGHT_DIFF_TO_CONSIDER_WEAVING) + continue; + if (pTestVehicle != pVehicle) + WeaveForOtherCar(pTestVehicle, pVehicle, pAngleToWeaveLeft, pAngleToWeaveRight); + } +} + +void CCarCtrl::WeaveForOtherCar(CEntity* pOtherEntity, CVehicle* pVehicle, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + if (pVehicle->AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE && pOtherEntity == FindPlayerVehicle()) + return; + if (pVehicle->AutoPilot.m_nCarMission == MISSION_RAMCAR_CLOSE && pOtherEntity == pVehicle->AutoPilot.m_pTargetCar) + return; + CVehicle* pOtherCar = (CVehicle*)pOtherEntity; + CVector2D vecDiff = pOtherCar->GetPosition() - pVehicle->GetPosition(); + float angleBetweenVehicles = CGeneral::GetATanOfXY(vecDiff.x, vecDiff.y); + float distance = vecDiff.Magnitude(); + if (distance < 1.0f) + return; + if (DotProduct2D(pVehicle->GetMoveSpeed() - pOtherCar->GetMoveSpeed(), vecDiff) * 110.0f - + pOtherCar->GetModelInfo()->GetColModel()->boundingSphere.radius - + pVehicle->GetModelInfo()->GetColModel()->boundingSphere.radius < distance) + return; + CVector2D forward = pVehicle->GetForward(); + forward.NormaliseSafe(); + float forwardAngle = CGeneral::GetATanOfXY(forward.x, forward.y); + float angleDiff = angleBetweenVehicles - forwardAngle; + float lenProjection = ABS(pOtherCar->GetColModel()->boundingBox.max.y * Sin(angleDiff)); + float widthProjection = ABS(pOtherCar->GetColModel()->boundingBox.max.x * Cos(angleDiff)); + float lengthToEvade = (2 * (lenProjection + widthProjection) + WIDTH_COEF_TO_WEAVE_SAFELY * 2 * pVehicle->GetColModel()->boundingBox.max.x) / distance; + float diffToLeftAngle = LimitRadianAngle(angleBetweenVehicles - *pAngleToWeaveLeft); + diffToLeftAngle = ABS(diffToLeftAngle); + float angleToWeave = lengthToEvade / 2; + if (diffToLeftAngle < angleToWeave){ + *pAngleToWeaveLeft = angleBetweenVehicles - angleToWeave; + while (*pAngleToWeaveLeft < -PI) + *pAngleToWeaveLeft += TWOPI; + } + float diffToRightAngle = LimitRadianAngle(angleBetweenVehicles - *pAngleToWeaveRight); + diffToRightAngle = ABS(diffToRightAngle); + if (diffToRightAngle < angleToWeave){ + *pAngleToWeaveRight = angleBetweenVehicles + angleToWeave; + while (*pAngleToWeaveRight > PI) + *pAngleToWeaveRight -= TWOPI; + } +} + +void CCarCtrl::WeaveThroughPedsSectorList(CPtrList& lst, CVehicle* pVehicle, CPhysical* pTarget, float x_inf, float y_inf, float x_sup, float y_sup, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next) { + CPed* pPed = (CPed*)pNode->item; + if (pPed->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + if (!pPed->bUsesCollision) + continue; + if (pPed == pTarget) + continue; + pPed->m_scanCode = CWorld::GetCurrentScanCode(); + if (pPed->GetPosition().x < x_inf || pPed->GetPosition().x > x_sup) + continue; + if (pPed->GetPosition().y < y_inf || pPed->GetPosition().y > y_sup) + continue; + if (Abs(pPed->GetPosition().z - pVehicle->GetPosition().z) >= PED_HEIGHT_DIFF_TO_CONSIDER_WEAVING) + continue; + if (pPed->m_pCurSurface != pVehicle) + WeaveForPed(pPed, pVehicle, pAngleToWeaveLeft, pAngleToWeaveRight); + } + +} +void CCarCtrl::WeaveForPed(CEntity* pOtherEntity, CVehicle* pVehicle, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + if (pVehicle->AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE && pOtherEntity == FindPlayerPed()) + return; + CPed* pPed = (CPed*)pOtherEntity; + CVector2D vecDiff = pPed->GetPosition() - pVehicle->GetPosition(); + float angleBetweenVehicleAndPed = CGeneral::GetATanOfXY(vecDiff.x, vecDiff.y); + float distance = vecDiff.Magnitude(); + float lengthToEvade = (WIDTH_COEF_TO_WEAVE_SAFELY * 2 * pVehicle->GetColModel()->boundingBox.max.x + PED_WIDTH_TO_WEAVE) / distance; + float diffToLeftAngle = LimitRadianAngle(angleBetweenVehicleAndPed - *pAngleToWeaveLeft); + diffToLeftAngle = ABS(diffToLeftAngle); + float angleToWeave = lengthToEvade / 2; + if (diffToLeftAngle < angleToWeave) { + *pAngleToWeaveLeft = angleBetweenVehicleAndPed - angleToWeave; + while (*pAngleToWeaveLeft < -PI) + *pAngleToWeaveLeft += TWOPI; + } + float diffToRightAngle = LimitRadianAngle(angleBetweenVehicleAndPed - *pAngleToWeaveRight); + diffToRightAngle = ABS(diffToRightAngle); + if (diffToRightAngle < angleToWeave) { + *pAngleToWeaveRight = angleBetweenVehicleAndPed + angleToWeave; + while (*pAngleToWeaveRight > PI) + *pAngleToWeaveRight -= TWOPI; + } +} + +void CCarCtrl::WeaveThroughObjectsSectorList(CPtrList& lst, CVehicle* pVehicle, float x_inf, float y_inf, float x_sup, float y_sup, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next) { + CObject* pObject = (CObject*)pNode->item; + if (pObject->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + if (!pObject->bUsesCollision) + continue; + pObject->m_scanCode = CWorld::GetCurrentScanCode(); + if (pObject->GetPosition().x < x_inf || pObject->GetPosition().x > x_sup) + continue; + if (pObject->GetPosition().y < y_inf || pObject->GetPosition().y > y_sup) + continue; + if (Abs(pObject->GetPosition().z - pVehicle->GetPosition().z) >= OBJECT_HEIGHT_DIFF_TO_CONSIDER_WEAVING) + continue; + if (pObject->GetUp().z > 0.9f) + WeaveForObject(pObject, pVehicle, pAngleToWeaveLeft, pAngleToWeaveRight); + } +} + +void CCarCtrl::WeaveForObject(CEntity* pOtherEntity, CVehicle* pVehicle, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + float rightCoef; + float forwardCoef; + if (pOtherEntity->GetModelIndex() == MI_TRAFFICLIGHTS){ + rightCoef = 2.957f; + forwardCoef = 0.147f; + }else if (pOtherEntity->GetModelIndex() == MI_SINGLESTREETLIGHTS1){ + rightCoef = 0.744f; + forwardCoef = 0.0f; + }else if (pOtherEntity->GetModelIndex() == MI_SINGLESTREETLIGHTS2){ + rightCoef = 0.043f; + forwardCoef = 0.0f; + }else if (pOtherEntity->GetModelIndex() == MI_SINGLESTREETLIGHTS3){ + rightCoef = 1.143f; + forwardCoef = 0.145f; + }else if (pOtherEntity->GetModelIndex() == MI_DOUBLESTREETLIGHTS){ + rightCoef = 0.0f; + forwardCoef = -0.048f; + }else if (IsTreeModel(pOtherEntity->GetModelIndex())){ + rightCoef = 0.0f; + forwardCoef = 0.0f; + }else if (pOtherEntity->GetModelIndex() == MI_STREETLAMP1 || pOtherEntity->GetModelIndex() == MI_STREETLAMP2){ + rightCoef = 0.0f; + forwardCoef = 0.0f; + }else + return; + CObject* pObject = (CObject*)pOtherEntity; + CVector2D vecDiff = pObject->GetPosition() + + rightCoef * pObject->GetRight() + + forwardCoef * pObject->GetForward() - + pVehicle->GetPosition(); + float angleBetweenVehicleAndObject = CGeneral::GetATanOfXY(vecDiff.x, vecDiff.y); + float distance = vecDiff.Magnitude(); + float lengthToEvade = (WIDTH_COEF_TO_WEAVE_SAFELY * 2 * pVehicle->GetColModel()->boundingBox.max.x + OBJECT_WIDTH_TO_WEAVE) / distance; + float diffToLeftAngle = LimitRadianAngle(angleBetweenVehicleAndObject - *pAngleToWeaveLeft); + diffToLeftAngle = ABS(diffToLeftAngle); + float angleToWeave = lengthToEvade / 2; + if (diffToLeftAngle < angleToWeave) { + *pAngleToWeaveLeft = angleBetweenVehicleAndObject - angleToWeave; + while (*pAngleToWeaveLeft < -PI) + *pAngleToWeaveLeft += TWOPI; + } + float diffToRightAngle = LimitRadianAngle(angleBetweenVehicleAndObject - *pAngleToWeaveRight); + diffToRightAngle = ABS(diffToRightAngle); + if (diffToRightAngle < angleToWeave) { + *pAngleToWeaveRight = angleBetweenVehicleAndObject + angleToWeave; + while (*pAngleToWeaveRight > PI) + *pAngleToWeaveRight -= TWOPI; + } +} + +bool CCarCtrl::PickNextNodeAccordingStrategy(CVehicle* pVehicle) +{ + switch (pVehicle->AutoPilot.m_nCarMission){ + case MISSION_RAMPLAYER_FARAWAY: + case MISSION_BLOCKPLAYER_FARAWAY: + PickNextNodeToChaseCar(pVehicle, + FindPlayerCoors().x, + FindPlayerCoors().y, +#ifdef FIX_PATHFIND_BUG + FindPlayerCoors().z, +#endif + FindPlayerVehicle()); + return false; + case MISSION_GOTOCOORDS: + case MISSION_GOTOCOORDS_ACCURATE: + return PickNextNodeToFollowPath(pVehicle); + case MISSION_RAMCAR_FARAWAY: + case MISSION_BLOCKCAR_FARAWAY: + PickNextNodeToChaseCar(pVehicle, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().x, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().y, +#ifdef FIX_PATHFIND_BUG + pVehicle->AutoPilot.m_pTargetCar->GetPosition().z, +#endif + pVehicle->AutoPilot.m_pTargetCar); + return false; + default: + PickNextNodeRandomly(pVehicle); + return false; + } +} + +void CCarCtrl::PickNextNodeRandomly(CVehicle* pVehicle) +{ + int32 prevNode = pVehicle->AutoPilot.m_nCurrentRouteNode; + int32 curNode = pVehicle->AutoPilot.m_nNextRouteNode; + uint8 totalLinks = ThePaths.m_pathNodes[curNode].numLinks; + CCarPathLink* pCurLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; +#ifdef FIX_BUGS + uint8 lanesOnCurrentPath = pCurLink->pathNodeIndex == curNode ? + pCurLink->numLeftLanes : pCurLink->numRightLanes; +#else + uint8 lanesOnCurrentPath = pCurLink->pathNodeIndex == curNode ? + pCurLink->numRightLanes : pCurLink->numLeftLanes; +#endif + uint8 allowedDirections = PATH_DIRECTION_NONE; + uint8 nextLane = pVehicle->AutoPilot.m_nNextLane; + if (nextLane == 0) + /* We are always allowed to turn left from leftmost lane */ + allowedDirections |= PATH_DIRECTION_LEFT; + if (nextLane == lanesOnCurrentPath - 1) + /* We are always allowed to turn right from rightmost lane */ + allowedDirections |= PATH_DIRECTION_RIGHT; + if (lanesOnCurrentPath < 3 || allowedDirections == PATH_DIRECTION_NONE) + /* We are always allowed to go straight on one/two-laned road */ + /* or if we are in one of middle lanes of the road */ + allowedDirections |= PATH_DIRECTION_STRAIGHT; + int attempt; + pVehicle->AutoPilot.m_nPrevRouteNode = pVehicle->AutoPilot.m_nCurrentRouteNode; + pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_nNextRouteNode; + CPathNode* pPrevPathNode = &ThePaths.m_pathNodes[prevNode]; + CPathNode* pCurPathNode = &ThePaths.m_pathNodes[curNode]; + int16 nextLink; + CCarPathLink* pNextLink; + CPathNode* pNextPathNode; + bool goingAgainstOneWayRoad; + uint8 direction; + for(attempt = 0; attempt < ATTEMPTS_TO_FIND_NEXT_NODE; attempt++){ + if (attempt != 0){ + if (pVehicle->AutoPilot.m_nNextRouteNode != prevNode){ + if (direction & allowedDirections){ + pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + if ((!pNextPathNode->bDeadEnd || pPrevPathNode->bDeadEnd) && + (!pNextPathNode->bDisabled || pPrevPathNode->bDisabled) && + (!pNextPathNode->bBetweenLevels || pPrevPathNode->bBetweenLevels || !pVehicle->AutoPilot.m_bStayInCurrentLevel) && + !goingAgainstOneWayRoad) + break; + } + } + } + nextLink = CGeneral::GetRandomNumber() % totalLinks; + pVehicle->AutoPilot.m_nNextRouteNode = ThePaths.ConnectedNode(nextLink + pCurPathNode->firstLink); + direction = FindPathDirection(prevNode, curNode, pVehicle->AutoPilot.m_nNextRouteNode); + pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurPathNode->firstLink]]; + goingAgainstOneWayRoad = pNextLink->pathNodeIndex == curNode ? pNextLink->numRightLanes == 0 : pNextLink->numLeftLanes == 0; + } + if (attempt >= ATTEMPTS_TO_FIND_NEXT_NODE) { + /* If we failed 15 times, then remove dead end and current lane limitations */ + for (attempt = 0; attempt < ATTEMPTS_TO_FIND_NEXT_NODE; attempt++) { + if (attempt != 0) { + if (pVehicle->AutoPilot.m_nNextRouteNode != prevNode) { + pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + if ((!pNextPathNode->bDisabled || pPrevPathNode->bDisabled) && + (!pNextPathNode->bBetweenLevels || pPrevPathNode->bBetweenLevels || !pVehicle->AutoPilot.m_bStayInCurrentLevel) && + !goingAgainstOneWayRoad) + break; + } + } + nextLink = CGeneral::GetRandomNumber() % totalLinks; + pVehicle->AutoPilot.m_nNextRouteNode = ThePaths.ConnectedNode(nextLink + pCurPathNode->firstLink); + pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurPathNode->firstLink]]; + goingAgainstOneWayRoad = pNextLink->pathNodeIndex == curNode ? pNextLink->numRightLanes == 0 : pNextLink->numLeftLanes == 0; + } + } + if (attempt >= ATTEMPTS_TO_FIND_NEXT_NODE) { + /* If we failed again, remove no U-turn limitation and remove randomness */ + for (nextLink = 0; nextLink < totalLinks; nextLink++) { + pVehicle->AutoPilot.m_nNextRouteNode = ThePaths.ConnectedNode(nextLink + pCurPathNode->firstLink); + pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurPathNode->firstLink]]; + goingAgainstOneWayRoad = pNextLink->pathNodeIndex == curNode ? pNextLink->numRightLanes == 0 : pNextLink->numLeftLanes == 0; + if (!goingAgainstOneWayRoad) { + pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + if ((!pNextPathNode->bDisabled || pPrevPathNode->bDisabled) && + (!pNextPathNode->bBetweenLevels || pPrevPathNode->bBetweenLevels || !pVehicle->AutoPilot.m_bStayInCurrentLevel)) + /* Nice way to exit loop but this will fail because this is used for indexing! */ + nextLink = 1000; + } + } + if (nextLink < 999) + /* If everything else failed, turn vehicle around */ + pVehicle->AutoPilot.m_nNextRouteNode = prevNode; + } + pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurPathNode->firstLink]]; + if (prevNode == pVehicle->AutoPilot.m_nNextRouteNode){ + /* We can no longer shift vehicle without physics if we have to turn it around. */ + pVehicle->SetStatus(STATUS_PHYSICS); + SwitchVehicleToRealPhysics(pVehicle); + } + pVehicle->AutoPilot.m_nTimeEnteredCurve += pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve; + pVehicle->AutoPilot.m_nPreviousPathNodeInfo = pVehicle->AutoPilot.m_nCurrentPathNodeInfo; + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = pVehicle->AutoPilot.m_nNextPathNodeInfo; + pVehicle->AutoPilot.m_nPreviousDirection = pVehicle->AutoPilot.m_nCurrentDirection; + pVehicle->AutoPilot.m_nCurrentDirection = pVehicle->AutoPilot.m_nNextDirection; + pVehicle->AutoPilot.m_nCurrentLane = pVehicle->AutoPilot.m_nNextLane; + pVehicle->AutoPilot.m_nNextPathNodeInfo = ThePaths.m_carPathConnections[nextLink + pCurPathNode->firstLink]; + int8 lanesOnNextNode; + if (curNode >= pVehicle->AutoPilot.m_nNextRouteNode){ + pVehicle->AutoPilot.m_nNextDirection = 1; + lanesOnNextNode = pNextLink->numLeftLanes; + }else{ + pVehicle->AutoPilot.m_nNextDirection = -1; + lanesOnNextNode = pNextLink->numRightLanes; + } + float currentPathLinkForwardX = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirX(); + float nextPathLinkForwardX = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirX(); +#ifdef FIX_BUGS + float currentPathLinkForwardY = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirY(); + float nextPathLinkForwardY = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirY(); +#endif + if (lanesOnNextNode >= 0){ + if ((CGeneral::GetRandomNumber() & 0x600) == 0){ + /* 25% chance vehicle will try to switch lane */ + CVector2D dist = pNextPathNode->GetPosition() - pCurPathNode->GetPosition(); + if (dist.MagnitudeSqr() >= SQR(14.0f)){ + if (CGeneral::GetRandomTrueFalse()) + pVehicle->AutoPilot.m_nNextLane += 1; + else + pVehicle->AutoPilot.m_nNextLane -= 1; + } + } + pVehicle->AutoPilot.m_nNextLane = Min(lanesOnNextNode - 1, pVehicle->AutoPilot.m_nNextLane); + pVehicle->AutoPilot.m_nNextLane = Max(0, pVehicle->AutoPilot.m_nNextLane); + }else{ + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane; + } + if (pVehicle->AutoPilot.m_bStayInFastLane) + pVehicle->AutoPilot.m_nNextLane = 0; + CVector positionOnCurrentLinkIncludingLane( + pCurLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) +#ifdef FIX_BUGS + * currentPathLinkForwardY +#endif + ,pCurLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) +#ifdef FIX_BUGS + * nextPathLinkForwardY +#endif + ,pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + float directionCurrentLinkX = pCurLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionCurrentLinkY = pCurLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + /* We want to make a path between two links that may not have the same forward directions a curve. */ + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + directionCurrentLinkX, directionCurrentLinkY, + directionNextLinkX, directionNextLinkY + ) * (1000.0f / pVehicle->AutoPilot.m_fMaxTrafficSpeed); + if (pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve < 10) + /* Oh hey there Obbe */ + printf("fout\n"); + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = Max(10, pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve); +} + +uint8 CCarCtrl::FindPathDirection(int32 prevNode, int32 curNode, int32 nextNode) +{ + CVector2D prevToCur = ThePaths.m_pathNodes[curNode].GetPosition() - ThePaths.m_pathNodes[prevNode].GetPosition(); + CVector2D curToNext = ThePaths.m_pathNodes[nextNode].GetPosition() - ThePaths.m_pathNodes[curNode].GetPosition(); + float distPrevToCur = prevToCur.Magnitude(); + if (distPrevToCur == 0.0f) + return PATH_DIRECTION_NONE; + /* We are trying to determine angle between prevToCur and curToNext. */ + /* To find it, we consider a to be an angle between y axis and prevToCur */ + /* and b to be an angle between x axis and curToNext */ + /* Then the angle we are looking for is (pi/2 + a + b). */ + float sin_a = prevToCur.x / distPrevToCur; + float cos_a = prevToCur.y / distPrevToCur; + float distCurToNext = curToNext.Magnitude(); + if (distCurToNext == 0.0f) + return PATH_DIRECTION_NONE; + float sin_b = curToNext.y / distCurToNext; + float cos_b = curToNext.x / distCurToNext; + /* sin(a) * sin(b) - cos(a) * cos(b) = -cos(a+b) = sin(pi/2+a+b) */ + float sin_direction = sin_a * sin_b - cos_a * cos_b; + if (sin_direction > 0.77f) /* Roughly between -50 and -130 degrees */ + return PATH_DIRECTION_LEFT; + if (sin_direction < -0.77f) /* Roughly between 50 and 130 degrees */ + return PATH_DIRECTION_RIGHT; + return PATH_DIRECTION_STRAIGHT; +} + +#ifdef FIX_PATHFIND_BUG +void CCarCtrl::PickNextNodeToChaseCar(CVehicle* pVehicle, float targetX, float targetY, float targetZ, CVehicle* pTarget) +#else +void CCarCtrl::PickNextNodeToChaseCar(CVehicle* pVehicle, float targetX, float targetY, CVehicle* pTarget) +#endif +{ + int prevNode = pVehicle->AutoPilot.m_nCurrentRouteNode; + int curNode = pVehicle->AutoPilot.m_nNextRouteNode; + CPathNode* pPrevNode = &ThePaths.m_pathNodes[prevNode]; + CPathNode* pCurNode = &ThePaths.m_pathNodes[curNode]; + CPathNode* pTargetNode; + int16 numNodes; + float distanceToTargetNode; + if (pTarget && pTarget->m_pCurGroundEntity && + pTarget->m_pCurGroundEntity->IsBuilding() && + ((CBuilding*)pTarget->m_pCurGroundEntity)->GetIsATreadable() && + ((CTreadable*)pTarget->m_pCurGroundEntity)->m_nodeIndices[0][0] >= 0){ + CTreadable* pCurrentMapObject = (CTreadable*)pTarget->m_pCurGroundEntity; + int closestNode = -1; + float minDist = 100000.0f; + for (int i = 0; i < 12; i++){ + int node = pCurrentMapObject->m_nodeIndices[0][i]; + if (node < 0) + break; + float dist = (ThePaths.m_pathNodes[node].GetPosition() - pTarget->GetPosition()).Magnitude(); + if (dist < minDist){ + minDist = dist; + closestNode = node; + } + } + ThePaths.DoPathSearch(0, pCurNode->GetPosition(), curNode, +#ifdef FIX_PATHFIND_BUG + CVector(targetX, targetY, targetZ), +#else + CVector(targetX, targetY, 0.0f), +#endif + &pTargetNode, &numNodes, 1, pVehicle, &distanceToTargetNode, 999999.9f, closestNode); + }else + { + + ThePaths.DoPathSearch(0, pCurNode->GetPosition(), curNode, +#ifdef FIX_PATHFIND_BUG + CVector(targetX, targetY, targetZ), +#else + CVector(targetX, targetY, 0.0f), +#endif + &pTargetNode, &numNodes, 1, pVehicle, &distanceToTargetNode, 999999.9f, -1); + } + + int newNextNode; + int nextLink; + if (numNodes != 1 || pTargetNode == pCurNode){ + float currentAngle = CGeneral::GetATanOfXY(targetX - pVehicle->GetPosition().x, targetY - pVehicle->GetPosition().y); + nextLink = 0; + float lowestAngleChange = 10.0f; + int numLinks = pCurNode->numLinks; + newNextNode = 0; + for (int i = 0; i < numLinks; i++){ + int conNode = ThePaths.ConnectedNode(i + pCurNode->firstLink); + if (conNode == prevNode && i > 1) + continue; + CPathNode* pTestNode = &ThePaths.m_pathNodes[conNode]; + float angle = CGeneral::GetATanOfXY(pTestNode->GetX() - pCurNode->GetX(), pTestNode->GetY() - pCurNode->GetY()); + angle = LimitRadianAngle(angle - currentAngle); + angle = ABS(angle); + if (angle < lowestAngleChange){ + lowestAngleChange = angle; + newNextNode = conNode; + nextLink = i; + } + } + }else{ + nextLink = 0; + newNextNode = pTargetNode - ThePaths.m_pathNodes; + for (int i = pCurNode->firstLink; ThePaths.ConnectedNode(i) != newNextNode; i++, nextLink++) + ; + } + CPathNode* pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurNode->firstLink]]; + CCarPathLink* pCurLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + pVehicle->AutoPilot.m_nPrevRouteNode = pVehicle->AutoPilot.m_nCurrentRouteNode; + pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_nNextRouteNode; + pVehicle->AutoPilot.m_nNextRouteNode = newNextNode; + pVehicle->AutoPilot.m_nTimeEnteredCurve += pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve; + pVehicle->AutoPilot.m_nPreviousPathNodeInfo = pVehicle->AutoPilot.m_nCurrentPathNodeInfo; + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = pVehicle->AutoPilot.m_nNextPathNodeInfo; + pVehicle->AutoPilot.m_nPreviousDirection = pVehicle->AutoPilot.m_nCurrentDirection; + pVehicle->AutoPilot.m_nCurrentDirection = pVehicle->AutoPilot.m_nNextDirection; + pVehicle->AutoPilot.m_nCurrentLane = pVehicle->AutoPilot.m_nNextLane; + pVehicle->AutoPilot.m_nNextPathNodeInfo = ThePaths.m_carPathConnections[nextLink + pCurNode->firstLink]; + int8 lanesOnNextNode; + if (curNode >= pVehicle->AutoPilot.m_nNextRouteNode) { + pVehicle->AutoPilot.m_nNextDirection = 1; + lanesOnNextNode = pNextLink->numLeftLanes; + } + else { + pVehicle->AutoPilot.m_nNextDirection = -1; + lanesOnNextNode = pNextLink->numRightLanes; + } + float currentPathLinkForwardX = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirX(); + float currentPathLinkForwardY = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirY(); + float nextPathLinkForwardX = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirX(); + float nextPathLinkForwardY = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirY(); + if (lanesOnNextNode >= 0) { + CVector2D dist = pNextPathNode->GetPosition() - pCurNode->GetPosition(); + if (dist.MagnitudeSqr() >= SQR(7.0f)){ + /* 25% chance vehicle will try to switch lane */ + /* No lane switching if following car from far away */ + /* ...although it's always one of those. */ + if ((CGeneral::GetRandomNumber() & 0x600) == 0 && + pVehicle->AutoPilot.m_nCarMission != MISSION_RAMPLAYER_FARAWAY && + pVehicle->AutoPilot.m_nCarMission != MISSION_BLOCKPLAYER_FARAWAY && + pVehicle->AutoPilot.m_nCarMission != MISSION_RAMCAR_FARAWAY && + pVehicle->AutoPilot.m_nCarMission != MISSION_BLOCKCAR_FARAWAY){ + if (CGeneral::GetRandomTrueFalse()) + pVehicle->AutoPilot.m_nNextLane += 1; + else + pVehicle->AutoPilot.m_nNextLane -= 1; + } + } + pVehicle->AutoPilot.m_nNextLane = Min(lanesOnNextNode - 1, pVehicle->AutoPilot.m_nNextLane); + pVehicle->AutoPilot.m_nNextLane = Max(0, pVehicle->AutoPilot.m_nNextLane); + } + else { + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane; + } + if (pVehicle->AutoPilot.m_bStayInFastLane) + pVehicle->AutoPilot.m_nNextLane = 0; + CVector positionOnCurrentLinkIncludingLane( + pCurLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + float directionCurrentLinkX = pCurLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionCurrentLinkY = pCurLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + /* We want to make a path between two links that may not have the same forward directions a curve. */ + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + directionCurrentLinkX, directionCurrentLinkY, + directionNextLinkX, directionNextLinkY + ) * (1000.0f / pVehicle->AutoPilot.m_fMaxTrafficSpeed); + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = Max(10, pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve); +} + +bool CCarCtrl::PickNextNodeToFollowPath(CVehicle* pVehicle) +{ + int curNode = pVehicle->AutoPilot.m_nNextRouteNode; + CPathNode* pCurNode = &ThePaths.m_pathNodes[curNode]; + if (pVehicle->AutoPilot.m_nPathFindNodesCount == 0){ + ThePaths.DoPathSearch(0, pVehicle->GetPosition(), curNode, + pVehicle->AutoPilot.m_vecDestinationCoors, pVehicle->AutoPilot.m_aPathFindNodesInfo, + &pVehicle->AutoPilot.m_nPathFindNodesCount, NUM_PATH_NODES_IN_AUTOPILOT, + pVehicle, nil, 999999.9f, -1); + if (pVehicle->AutoPilot.m_nPathFindNodesCount < 1) + return true; + } + CPathNode* pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + CCarPathLink* pCurLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + pVehicle->AutoPilot.m_nPrevRouteNode = pVehicle->AutoPilot.m_nCurrentRouteNode; + pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_nNextRouteNode; + pVehicle->AutoPilot.m_nNextRouteNode = pVehicle->AutoPilot.m_aPathFindNodesInfo[0] - ThePaths.m_pathNodes; + pVehicle->AutoPilot.RemoveOnePathNode(); + pVehicle->AutoPilot.m_nTimeEnteredCurve += pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve; + pVehicle->AutoPilot.m_nPreviousPathNodeInfo = pVehicle->AutoPilot.m_nCurrentPathNodeInfo; + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = pVehicle->AutoPilot.m_nNextPathNodeInfo; + pVehicle->AutoPilot.m_nPreviousDirection = pVehicle->AutoPilot.m_nCurrentDirection; + pVehicle->AutoPilot.m_nCurrentDirection = pVehicle->AutoPilot.m_nNextDirection; + pVehicle->AutoPilot.m_nCurrentLane = pVehicle->AutoPilot.m_nNextLane; + int nextLink = 0; + for (int i = pCurNode->firstLink; ThePaths.ConnectedNode(i) != pVehicle->AutoPilot.m_nNextRouteNode; i++, nextLink++) + ; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurNode->firstLink]]; + pVehicle->AutoPilot.m_nNextPathNodeInfo = ThePaths.m_carPathConnections[nextLink + pCurNode->firstLink]; + int8 lanesOnNextNode; + if (curNode >= pVehicle->AutoPilot.m_nNextRouteNode) { + pVehicle->AutoPilot.m_nNextDirection = 1; + lanesOnNextNode = pNextLink->numLeftLanes; + } + else { + pVehicle->AutoPilot.m_nNextDirection = -1; + lanesOnNextNode = pNextLink->numRightLanes; + } + float currentPathLinkForwardX = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirX(); + float currentPathLinkForwardY = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirY(); + float nextPathLinkForwardX = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirX(); + float nextPathLinkForwardY = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirY(); + if (lanesOnNextNode >= 0) { + CVector2D dist = pNextPathNode->GetPosition() - pCurNode->GetPosition(); + if (dist.MagnitudeSqr() >= SQR(7.0f) && (CGeneral::GetRandomNumber() & 0x600) == 0) { + if (CGeneral::GetRandomTrueFalse()) + pVehicle->AutoPilot.m_nNextLane += 1; + else + pVehicle->AutoPilot.m_nNextLane -= 1; + } + pVehicle->AutoPilot.m_nNextLane = Min(lanesOnNextNode - 1, pVehicle->AutoPilot.m_nNextLane); + pVehicle->AutoPilot.m_nNextLane = Max(0, pVehicle->AutoPilot.m_nNextLane); + } + else { + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane; + } + if (pVehicle->AutoPilot.m_bStayInFastLane) + pVehicle->AutoPilot.m_nNextLane = 0; + CVector positionOnCurrentLinkIncludingLane( + pCurLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + float directionCurrentLinkX = pCurLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionCurrentLinkY = pCurLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + /* We want to make a path between two links that may not have the same forward directions a curve. */ + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + directionCurrentLinkX, directionCurrentLinkY, + directionNextLinkX, directionNextLinkY + ) * (1000.0f / pVehicle->AutoPilot.m_fMaxTrafficSpeed); + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = Max(10, pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve); + return false; +} + +void CCarCtrl::Init(void) +{ + NumRandomCars = 0; + NumLawEnforcerCars = 0; + NumMissionCars = 0; + NumParkedCars = 0; + NumPermanentCars = 0; + NumAmbulancesOnDuty = 0; + NumFiretrucksOnDuty = 0; + LastTimeFireTruckCreated = 0; + LastTimeAmbulanceCreated = 0; +#ifdef FIX_BUGS + LastTimeLawEnforcerCreated = 0; +#endif + bCarsGeneratedAroundCamera = false; + CountDownToCarsAtStart = 2; + CarDensityMultiplier = 1.0f; + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) + apCarsToKeep[i] = nil; + for (int i = 0; i < TOTAL_CUSTOM_CLASSES; i++){ + for (int j = 0; j < MAX_CAR_MODELS_IN_ARRAY; j++) + CarArrays[i][j] = 0; + NextCarOfRating[i] = 0; + TotalNumOfCarsOfRating[i] = 0; + } +} + +void CCarCtrl::ReInit(void) +{ + NumRandomCars = 0; + NumLawEnforcerCars = 0; + NumMissionCars = 0; + NumParkedCars = 0; + NumPermanentCars = 0; + NumAmbulancesOnDuty = 0; + NumFiretrucksOnDuty = 0; +#ifdef FIX_BUGS + LastTimeFireTruckCreated = 0; + LastTimeAmbulanceCreated = 0; + LastTimeLawEnforcerCreated = 0; +#endif + CountDownToCarsAtStart = 2; + CarDensityMultiplier = 1.0f; + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) + apCarsToKeep[i] = nil; + for (int i = 0; i < TOTAL_CUSTOM_CLASSES; i++) + NextCarOfRating[i] = 0; +} + +void CCarCtrl::DragCarToPoint(CVehicle* pVehicle, CVector* pPoint) +{ + CVector2D posBehind = (CVector2D)pVehicle->GetPosition() - 3 * pVehicle->GetForward() / 2; + CVector2D posTarget = *pPoint; + CVector2D direction = posBehind - posTarget; + CVector2D midPos = posTarget + direction * 3 / direction.Magnitude(); + float actualAheadZ; + float actualBehindZ; + CColPoint point; + CEntity* pRoadObject; + if (CCollision::IsStoredPolyStillValidVerticalLine(CVector(posTarget.x, posTarget.y, pVehicle->GetPosition().z - 3.0f), + pVehicle->GetPosition().z - 3.0f, point, &pVehicle->m_aCollPolys[0])){ + actualAheadZ = point.point.z; + }else if (CWorld::ProcessVerticalLine(CVector(posTarget.x, posTarget.y, pVehicle->GetPosition().z + 1.5f), + pVehicle->GetPosition().z - 2.0f, point, + pRoadObject, true, false, false, false, false, false, &pVehicle->m_aCollPolys[0])){ + actualAheadZ = point.point.z; + pVehicle->m_pCurGroundEntity = pRoadObject; + if (ThisRoadObjectCouldMove(pRoadObject->GetModelIndex())) + pVehicle->m_aCollPolys[0].valid = false; + }else if (CWorld::ProcessVerticalLine(CVector(posTarget.x, posTarget.y, pVehicle->GetPosition().z + 3.0f), + pVehicle->GetPosition().z - 3.0f, point, + pRoadObject, true, false, false, false, false, false, &pVehicle->m_aCollPolys[0])) { + actualAheadZ = point.point.z; + pVehicle->m_pCurGroundEntity = pRoadObject; + if (ThisRoadObjectCouldMove(pRoadObject->GetModelIndex())) + pVehicle->m_aCollPolys[0].valid = false; + }else{ + actualAheadZ = pVehicle->m_fMapObjectHeightAhead; + } + pVehicle->m_fMapObjectHeightAhead = actualAheadZ; + if (CCollision::IsStoredPolyStillValidVerticalLine(CVector(midPos.x, midPos.y, pVehicle->GetPosition().z - 3.0f), + pVehicle->GetPosition().z - 3.0f, point, &pVehicle->m_aCollPolys[1])){ + actualBehindZ = point.point.z; + }else if (CWorld::ProcessVerticalLine(CVector(midPos.x, midPos.y, pVehicle->GetPosition().z + 1.5f), + pVehicle->GetPosition().z - 2.0f, point, + pRoadObject, true, false, false, false, false, false, &pVehicle->m_aCollPolys[1])){ + actualBehindZ = point.point.z; + pVehicle->m_pCurGroundEntity = pRoadObject; + if (ThisRoadObjectCouldMove(pRoadObject->GetModelIndex())) + pVehicle->m_aCollPolys[1].valid = false; + }else if (CWorld::ProcessVerticalLine(CVector(midPos.x, midPos.y, pVehicle->GetPosition().z + 3.0f), + pVehicle->GetPosition().z - 3.0f, point, + pRoadObject, true, false, false, false, false, false, &pVehicle->m_aCollPolys[1])){ + actualBehindZ = point.point.z; + pVehicle->m_pCurGroundEntity = pRoadObject; + if (ThisRoadObjectCouldMove(pRoadObject->GetModelIndex())) + pVehicle->m_aCollPolys[1].valid = false; + }else{ + actualBehindZ = pVehicle->m_fMapObjectHeightBehind; + } + pVehicle->m_fMapObjectHeightBehind = actualBehindZ; + float angleZ = Atan2((actualAheadZ - actualBehindZ) / 3, 1.0f); + float cosZ = Cos(angleZ); + float sinZ = Sin(angleZ); + pVehicle->GetRight() = CVector(posTarget.y - midPos.y, -(posTarget.x - midPos.x), 0.0f) / 3; + pVehicle->GetForward() = CVector(-cosZ * pVehicle->GetRight().y, cosZ * pVehicle->GetRight().x, sinZ); + pVehicle->GetUp() = CrossProduct(pVehicle->GetRight(), pVehicle->GetForward()); + pVehicle->SetPosition((CVector(midPos.x, midPos.y, actualBehindZ) + CVector(posTarget.x, posTarget.y, actualAheadZ)) / 2); + pVehicle->GetMatrix().GetPosition().z += pVehicle->GetHeightAboveRoad(); +} + +float CCarCtrl::FindSpeedMultiplier(float angleChange, float minAngle, float maxAngle, float coef) +{ + float angle = Abs(LimitRadianAngle(angleChange)); + float n = angle - minAngle; + n = Max(0.0f, n); + float d = maxAngle - minAngle; + float mult = 1.0f - n / d * (1.0f - coef); + if (n > d) + return coef; + return mult; +} + +void CCarCtrl::SteerAICarWithPhysics(CVehicle* pVehicle) +{ + float swerve; + float accel; + float brake; + bool handbrake; + switch (pVehicle->AutoPilot.m_nTempAction){ + case TEMPACT_WAIT: + swerve = 0.0f; + accel = 0.0f; + brake = 0.2f; + handbrake = false; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds(); + } + break; + case TEMPACT_REVERSE: + SteerAICarWithPhysics_OnlyMission(pVehicle, &swerve, &accel, &brake, &handbrake); + handbrake = false; + swerve = -swerve; + if (DotProduct(pVehicle->GetMoveSpeed(), pVehicle->GetForward()) > 0.04f){ + accel = 0.0f; + brake = 0.5f; + }else{ + accel = -0.5f; + brake = 0.0f; + } + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_HANDBRAKETURNLEFT: + swerve = -1.0f; // It seems like this should be swerve = 1.0f (fixed in VC) + accel = 0.0f; + brake = 0.0f; + handbrake = true; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_HANDBRAKETURNRIGHT: + swerve = 1.0f; // It seems like this should be swerve = -1.0f (fixed in VC) + accel = 0.0f; + brake = 0.0f; + handbrake = true; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_HANDBRAKESTRAIGHT: + swerve = 0.0f; + accel = 0.0f; + brake = 0.0f; + handbrake = true; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_TURNLEFT: + swerve = 1.0f; + accel = 1.0f; + brake = 0.0f; + handbrake = false; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_TURNRIGHT: + swerve = -1.0f; + accel = 1.0f; + brake = 0.0f; + handbrake = false; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_GOFORWARD: + swerve = 0.0f; + accel = 0.5f; + brake = 0.0f; + handbrake = false; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_SWERVELEFT: + case TEMPACT_SWERVERIGHT: + swerve = (pVehicle->AutoPilot.m_nTempAction == TEMPACT_SWERVERIGHT) ? 0.15f : -0.15f; + accel = 0.0f; + brake = 0.001f; + handbrake = false; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction - 1000) + swerve = -swerve; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + default: + SteerAICarWithPhysics_OnlyMission(pVehicle, &swerve, &accel, &brake, &handbrake); + break; + } + pVehicle->m_fSteerAngle = swerve; + pVehicle->bIsHandbrakeOn = handbrake; + pVehicle->m_fGasPedal = accel; + pVehicle->m_fBrakePedal = brake; +} + +void CCarCtrl::SteerAICarWithPhysics_OnlyMission(CVehicle* pVehicle, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + switch (pVehicle->AutoPilot.m_nCarMission) { + case MISSION_NONE: + *pSwerve = 0.0f; + *pAccel = 0.0f; + *pBrake = 0.5f; + *pHandbrake = true; + return; + case MISSION_CRUISE: + case MISSION_RAMPLAYER_FARAWAY: + case MISSION_BLOCKPLAYER_FARAWAY: + case MISSION_GOTOCOORDS: + case MISSION_GOTOCOORDS_ACCURATE: + case MISSION_RAMCAR_FARAWAY: + case MISSION_BLOCKCAR_FARAWAY: + SteerAICarWithPhysicsFollowPath(pVehicle, pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_RAMPLAYER_CLOSE: + { + CVector2D targetPos = FindPlayerCoors(); + if (FindPlayerVehicle()){ + if (pVehicle->m_randomSeed & 1 && DotProduct(FindPlayerVehicle()->GetForward(), pVehicle->GetForward()) > 0.5f){ + float targetWidth = FindPlayerVehicle()->GetColModel()->boundingBox.max.x; + float ownWidth = pVehicle->GetColModel()->boundingBox.max.x; + if (pVehicle->m_randomSeed & 2){ + targetPos += (targetWidth + ownWidth - 0.2f) * FindPlayerVehicle()->GetRight(); + }else{ + targetPos -= (targetWidth + ownWidth - 0.2f) * FindPlayerVehicle()->GetRight(); + } + float targetSpeed = FindPlayerVehicle()->GetMoveSpeed().Magnitude(); + float distanceToTarget = ((CVector2D)pVehicle->GetPosition() - targetPos).Magnitude(); + if (12.0f * targetSpeed + 2.0f > distanceToTarget && pVehicle->AutoPilot.m_nTempAction == TEMPACT_NONE){ + pVehicle->AutoPilot.m_nTempAction = (pVehicle->m_randomSeed & 2) ? TEMPACT_TURNLEFT : TEMPACT_TURNRIGHT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 250; + } + }else{ + targetPos += FindPlayerVehicle()->GetRight() / 160 * ((pVehicle->m_randomSeed & 0xFF) - 128); + } + } + SteerAICarWithPhysicsHeadingForTarget(pVehicle, FindPlayerVehicle(), targetPos.x, targetPos.y, pSwerve, pAccel, pBrake, pHandbrake); + return; + } + case MISSION_BLOCKPLAYER_CLOSE: + SteerAICarWithPhysicsTryingToBlockTarget(pVehicle, FindPlayerCoors().x, FindPlayerCoors().y, + FindPlayerSpeed().x, FindPlayerSpeed().y, pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_BLOCKPLAYER_HANDBRAKESTOP: + SteerAICarWithPhysicsTryingToBlockTarget_Stop(pVehicle, FindPlayerCoors().x, FindPlayerCoors().y, + FindPlayerSpeed().x, FindPlayerSpeed().y, pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_GOTOCOORDS_STRAIGHT: + case MISSION_GOTO_COORDS_STRAIGHT_ACCURATE: + SteerAICarWithPhysicsHeadingForTarget(pVehicle, nil, + pVehicle->AutoPilot.m_vecDestinationCoors.x, pVehicle->AutoPilot.m_vecDestinationCoors.y, + pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_EMERGENCYVEHICLE_STOP: + case MISSION_STOP_FOREVER: + *pSwerve = 0.0f; + *pAccel = 0.0f; + *pHandbrake = true; + *pBrake = 0.5f; + return; + case MISSION_RAMCAR_CLOSE: + SteerAICarWithPhysicsHeadingForTarget(pVehicle, pVehicle->AutoPilot.m_pTargetCar, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().x, pVehicle->AutoPilot.m_pTargetCar->GetPosition().y, + pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_BLOCKCAR_CLOSE: + SteerAICarWithPhysicsTryingToBlockTarget(pVehicle, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().x, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().y, + pVehicle->AutoPilot.m_pTargetCar->GetMoveSpeed().x, + pVehicle->AutoPilot.m_pTargetCar->GetMoveSpeed().y, + pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_BLOCKCAR_HANDBRAKESTOP: + SteerAICarWithPhysicsTryingToBlockTarget_Stop(pVehicle, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().x, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().y, + pVehicle->AutoPilot.m_pTargetCar->GetMoveSpeed().x, + pVehicle->AutoPilot.m_pTargetCar->GetMoveSpeed().y, + pSwerve, pAccel, pBrake, pHandbrake); + return; + default: + return; + } +} + +void CCarCtrl::SteerAIBoatWithPhysics(CBoat* pBoat) +{ + if (pBoat->AutoPilot.m_nCarMission == MISSION_GOTOCOORDS_ASTHECROWSWIMS){ + SteerAIBoatWithPhysicsHeadingForTarget(pBoat, + pBoat->AutoPilot.m_vecDestinationCoors.x, pBoat->AutoPilot.m_vecDestinationCoors.y, + &pBoat->m_fSteeringLeftRight, &pBoat->m_fAccelerate, &pBoat->m_fBrake); + }else if (pBoat->AutoPilot.m_nCarMission == MISSION_NONE){ + pBoat->m_fSteeringLeftRight = 0.0f; + pBoat->m_fAccelerate = 0.0f; + pBoat->m_fBrake = 0.0f; + } + pBoat->m_fSteerAngle = pBoat->m_fSteeringLeftRight; + pBoat->m_fGasPedal = pBoat->m_fAccelerate; + pBoat->m_fBrakePedal = pBoat->m_fBrake; + pBoat->bIsHandbrakeOn = false; +} + +float CCarCtrl::FindMaxSteerAngle(CVehicle* pVehicle) +{ + return pVehicle->GetModelIndex() == MI_ENFORCER ? 0.7f : DEFAULT_MAX_STEER_ANGLE; +} + +void CCarCtrl::SteerAICarWithPhysicsFollowPath(CVehicle* pVehicle, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + CVector2D forward = pVehicle->GetForward(); + forward.NormaliseSafe(); + CCarPathLink* pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + CVector2D currentPathLinkForward(pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection, + pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection); + float nextPathLinkForwardX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float nextPathLinkForwardY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + CVector2D positionOnCurrentLinkIncludingLane( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.y, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.x); + CVector2D positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX); + CVector2D distanceToNextNode = (CVector2D)pVehicle->GetPosition() - positionOnCurrentLinkIncludingLane; + float scalarDistanceToNextNode = distanceToNextNode.Magnitude(); + CVector2D distanceBetweenNodes = positionOnNextLinkIncludingLane - positionOnCurrentLinkIncludingLane; + float dp = DotProduct2D(distanceBetweenNodes, distanceToNextNode); + if (scalarDistanceToNextNode < DISTANCE_TO_NEXT_NODE_TO_SELECT_NEW || + dp > 0.0f && scalarDistanceToNextNode < DISTANCE_TO_FACING_NEXT_NODE_TO_SELECT_NEW || + dp / (scalarDistanceToNextNode * distanceBetweenNodes.Magnitude()) > 0.7f || + pVehicle->AutoPilot.m_nNextPathNodeInfo == pVehicle->AutoPilot.m_nCurrentPathNodeInfo){ + if (PickNextNodeAccordingStrategy(pVehicle)) { + switch (pVehicle->AutoPilot.m_nCarMission){ + case MISSION_GOTOCOORDS: + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_STRAIGHT; + *pSwerve = 0.0f; + *pAccel = 0.0f; + *pBrake = 0.0f; + *pHandbrake = false; + return; + case MISSION_GOTOCOORDS_ACCURATE: + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTO_COORDS_STRAIGHT_ACCURATE; + *pSwerve = 0.0f; + *pAccel = 0.0f; + *pBrake = 0.0f; + *pHandbrake = false; + return; + default: break; + } + } + pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + scalarDistanceToNextNode = CVector2D( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.y - pVehicle->GetPosition().x, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.x - pVehicle->GetPosition().y).Magnitude(); + pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + currentPathLinkForward.x = pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + currentPathLinkForward.y = pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + nextPathLinkForwardX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + nextPathLinkForwardY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + } + positionOnCurrentLinkIncludingLane.x = pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.y; + positionOnCurrentLinkIncludingLane.y = pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.x; + CVector2D projectedPosition = positionOnCurrentLinkIncludingLane - currentPathLinkForward * scalarDistanceToNextNode * 0.4f; + if (scalarDistanceToNextNode > DISTANCE_TO_NEXT_NODE_TO_CONSIDER_SLOWING_DOWN){ + projectedPosition.x = positionOnCurrentLinkIncludingLane.x; + projectedPosition.y = positionOnCurrentLinkIncludingLane.y; + } + CVector2D distanceToProjectedPosition = projectedPosition - pVehicle->GetPosition(); + float angleCurrentLink = CGeneral::GetATanOfXY(distanceToProjectedPosition.x, distanceToProjectedPosition.y); + float angleForward = CGeneral::GetATanOfXY(forward.x, forward.y); + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_AVOID_CARS) + angleCurrentLink = FindAngleToWeaveThroughTraffic(pVehicle, nil, angleCurrentLink, angleForward); + float steerAngle = LimitRadianAngle(angleCurrentLink - angleForward); + float maxAngle = FindMaxSteerAngle(pVehicle); + steerAngle = Min(maxAngle, Max(-maxAngle, steerAngle)); + if (pVehicle->GetMoveSpeed().Magnitude() > MIN_SPEED_TO_START_LIMITING_STEER) + steerAngle = Min(MAX_ANGLE_TO_STEER_AT_HIGH_SPEED, Max(-MAX_ANGLE_TO_STEER_AT_HIGH_SPEED, steerAngle)); + float currentForwardSpeed = DotProduct(pVehicle->GetMoveSpeed(), pVehicle->GetForward()) * GAME_SPEED_TO_CARAI_SPEED; + float speedStyleMultiplier; + switch (pVehicle->AutoPilot.m_nDrivingStyle) { + case DRIVINGSTYLE_STOP_FOR_CARS: + case DRIVINGSTYLE_SLOW_DOWN_FOR_CARS: + speedStyleMultiplier = FindMaximumSpeedForThisCarInTraffic(pVehicle); +#ifdef FIX_BUGS + if (pVehicle->AutoPilot.m_nCruiseSpeed != 0) +#endif + speedStyleMultiplier /= pVehicle->AutoPilot.m_nCruiseSpeed; + break; + default: + speedStyleMultiplier = 1.0f; + break; + } + switch (pVehicle->AutoPilot.m_nDrivingStyle) { + case DRIVINGSTYLE_STOP_FOR_CARS: + case DRIVINGSTYLE_SLOW_DOWN_FOR_CARS: + if (CTrafficLights::ShouldCarStopForLight(pVehicle, false)){ + CCarAI::CarHasReasonToStop(pVehicle); + speedStyleMultiplier = 0.0f; + } + break; + default: + break; + } + if (CTrafficLights::ShouldCarStopForBridge(pVehicle)){ + CCarAI::CarHasReasonToStop(pVehicle); + speedStyleMultiplier = 0.0f; + } + CVector2D trajectory(pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.y, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.x); + trajectory -= pVehicle->GetPosition(); + float speedAngleMultiplier = FindSpeedMultiplier( + CGeneral::GetATanOfXY(trajectory.x, trajectory.y) - angleForward, + MIN_ANGLE_FOR_SPEED_LIMITING, MAX_ANGLE_FOR_SPEED_LIMITING, MIN_LOWERING_SPEED_COEFFICIENT); + float tmpWideMultiplier = FindSpeedMultiplier( + CGeneral::GetATanOfXY(currentPathLinkForward.x, currentPathLinkForward.y) - + CGeneral::GetATanOfXY(nextPathLinkForwardX, nextPathLinkForwardY), + MIN_ANGLE_FOR_SPEED_LIMITING_BETWEEN_NODES, MAX_ANGLE_FOR_SPEED_LIMITING, MIN_LOWERING_SPEED_COEFFICIENT); + float speedNodesMultiplier; + if (scalarDistanceToNextNode > DISTANCE_TO_NEXT_NODE_TO_CONSIDER_SLOWING_DOWN || pVehicle->AutoPilot.m_nCruiseSpeed < 12) + speedNodesMultiplier = 1.0f; + else + speedNodesMultiplier = 1.0f - + (1.0f - scalarDistanceToNextNode / DISTANCE_TO_NEXT_NODE_TO_CONSIDER_SLOWING_DOWN) * + (1.0f - tmpWideMultiplier); + float speedMultiplier = Min(speedStyleMultiplier, Min(speedAngleMultiplier, speedNodesMultiplier)); + float speed = pVehicle->AutoPilot.m_nCruiseSpeed * speedMultiplier; + float speedDifference = speed - currentForwardSpeed; + if (speed < 0.05f && speedDifference < 0.03f){ + *pBrake = 1.0f; + *pAccel = 0.0f; + }else if (speedDifference <= 0.0f){ + *pBrake = Min(0.5f, -speedDifference * 0.05f); + *pAccel = 0.0f; + }else if (currentForwardSpeed < 2.0f){ + *pBrake = 0.0f; + *pAccel = Min(1.0f, speedDifference * 0.25f); + }else{ + *pBrake = 0.0f; + *pAccel = Min(1.0f, speedDifference * 0.125f); + } + *pSwerve = steerAngle; + *pHandbrake = false; +} + +void CCarCtrl::SteerAICarWithPhysicsHeadingForTarget(CVehicle* pVehicle, CPhysical* pTarget, float targetX, float targetY, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + *pHandbrake = false; + CVector2D forward = pVehicle->GetForward(); + forward.NormaliseSafe(); + float angleToTarget = CGeneral::GetATanOfXY(targetX - pVehicle->GetPosition().x, targetY - pVehicle->GetPosition().y); + float angleForward = CGeneral::GetATanOfXY(forward.x, forward.y); + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_AVOID_CARS) + angleToTarget = FindAngleToWeaveThroughTraffic(pVehicle, pTarget, angleToTarget, angleForward); + float steerAngle = LimitRadianAngle(angleToTarget - angleForward); + if (pVehicle->GetMoveSpeed().Magnitude() > MIN_SPEED_TO_APPLY_HANDBRAKE) + if (ABS(steerAngle) > MIN_ANGLE_TO_APPLY_HANDBRAKE) + *pHandbrake = true; + float maxAngle = FindMaxSteerAngle(pVehicle); + steerAngle = Min(maxAngle, Max(-maxAngle, steerAngle)); + float speedMultiplier = FindSpeedMultiplier(CGeneral::GetATanOfXY(targetX - pVehicle->GetPosition().x, targetY - pVehicle->GetPosition().y) - angleForward, + MIN_ANGLE_FOR_SPEED_LIMITING, MAX_ANGLE_FOR_SPEED_LIMITING, MIN_LOWERING_SPEED_COEFFICIENT); + float speedTarget = pVehicle->AutoPilot.m_nCruiseSpeed * speedMultiplier; + float currentSpeed = pVehicle->GetMoveSpeed().Magnitude() * GAME_SPEED_TO_CARAI_SPEED; + float speedDiff = speedTarget - currentSpeed; + if (speedDiff <= 0.0f){ + *pAccel = 0.0f; + *pBrake = Min(0.5f, -speedDiff * 0.05f); + }else if (currentSpeed < 25.0f){ + *pAccel = Min(1.0f, speedDiff * 0.1f); + *pBrake = 0.0f; + }else{ + *pAccel = 1.0f; + *pBrake = 0.0f; + } + *pSwerve = steerAngle; +} + +void CCarCtrl::SteerAICarWithPhysicsTryingToBlockTarget(CVehicle* pVehicle, float targetX, float targetY, float targetSpeedX, float targetSpeedY, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + CVector2D targetPos(targetX, targetY); + CVector2D offset(targetSpeedX, targetSpeedY); + float trajectoryLen = offset.Magnitude(); + if (trajectoryLen > MAX_SPEED_TO_ACCOUNT_IN_INTERCEPTING) + offset *= MAX_SPEED_TO_ACCOUNT_IN_INTERCEPTING / trajectoryLen; + targetPos += offset * GAME_SPEED_TO_CARAI_SPEED; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + SteerAICarWithPhysicsHeadingForTarget(pVehicle, nil, targetPos.x, targetPos.y, pSwerve, pAccel, pBrake, pHandbrake); + if ((targetPos - pVehicle->GetPosition()).MagnitudeSqr() < SQR(DISTANCE_TO_SWITCH_FROM_BLOCK_TO_STOP)) + pVehicle->AutoPilot.m_nCarMission = (pVehicle->AutoPilot.m_nCarMission == MISSION_BLOCKCAR_CLOSE) ? + MISSION_BLOCKCAR_HANDBRAKESTOP : MISSION_BLOCKPLAYER_HANDBRAKESTOP; +} +void CCarCtrl::SteerAICarWithPhysicsTryingToBlockTarget_Stop(CVehicle* pVehicle, float targetX, float targetY, float targetSpeedX, float targetSpeedY, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + *pSwerve = 0.0f; + *pAccel = 0.0f; + *pBrake = 1.0f; + *pHandbrake = true; + float distanceToTargetSqr = (CVector2D(targetX, targetY) - pVehicle->GetPosition()).MagnitudeSqr(); + if (distanceToTargetSqr > SQR(DISTANCE_TO_SWITCH_FROM_STOP_TO_BLOCK)){ + pVehicle->AutoPilot.m_nCarMission = (pVehicle->AutoPilot.m_nCarMission == MISSION_BLOCKCAR_HANDBRAKESTOP) ? + MISSION_BLOCKCAR_CLOSE : MISSION_BLOCKPLAYER_CLOSE; + return; + } + if (pVehicle->AutoPilot.m_nCarMission == MISSION_BLOCKCAR_HANDBRAKESTOP){ + if (((CVector2D)pVehicle->GetMoveSpeed()).MagnitudeSqr() < SQR(0.01f) && + CVector2D(targetSpeedX, targetSpeedY).MagnitudeSqr() < SQR(0.02f) && + pVehicle->bIsLawEnforcer){ + CCarAI::TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + }else{ + if (FindPlayerVehicle() && FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f) +#ifdef FIX_BUGS + pVehicle->m_nTimeBlocked += CTimer::GetTimeStepInMilliseconds(); +#else + pVehicle->m_nTimeBlocked += 1000.0f / 60.0f * CTimer::GetTimeStep(); // very doubtful constant +#endif + else + pVehicle->m_nTimeBlocked = 0; + if (FindPlayerVehicle() == nil || FindPlayerVehicle()->IsUpsideDown() || + FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f && + pVehicle->m_nTimeBlocked > TIME_COPS_WAIT_TO_EXIT_AFTER_STOPPING){ + if (pVehicle->bIsLawEnforcer && distanceToTargetSqr < SQR(DISTANCE_TO_SWITCH_FROM_STOP_TO_BLOCK)){ + CCarAI::TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + } + } +} + +void CCarCtrl::SteerAIBoatWithPhysicsHeadingForTarget(CBoat* pBoat, float targetX, float targetY, float* pSwerve, float* pAccel, float* pBrake) +{ + CVector2D forward(pBoat->GetForward()); + forward.NormaliseSafe(); + CVector2D distanceToTarget = CVector2D(targetX, targetY) - pBoat->GetPosition(); + float angleToTarget = CGeneral::GetATanOfXY(distanceToTarget.x, distanceToTarget.y); + float angleForward = CGeneral::GetATanOfXY(forward.x, forward.y); + float angleDiff = LimitRadianAngle(angleToTarget - angleForward); + angleDiff = Min(DEFAULT_MAX_STEER_ANGLE, Max(-DEFAULT_MAX_STEER_ANGLE, angleDiff)); + float currentSpeed = pBoat->GetMoveSpeed().Magnitude2D(); // +0.0f for some reason + float speedDiff = pBoat->AutoPilot.m_nCruiseSpeed - currentSpeed * 60.0f; + if (speedDiff > 0.0f){ + float accRemaining = speedDiff / pBoat->AutoPilot.m_nCruiseSpeed; + *pAccel = (accRemaining > 0.25f) ? 1.0f : 1.0f - (0.25f - accRemaining) * 4.0f; + }else + *pAccel = (speedDiff < -5.0f) ? -0.2f : -0.1f; + *pBrake = 0.0f; + *pSwerve = angleDiff; +} + +void +CCarCtrl::RegisterVehicleOfInterest(CVehicle* pVehicle) +{ + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + if (apCarsToKeep[i] == pVehicle) { + aCarsToKeepTime[i] = CTimer::GetTimeInMilliseconds(); + return; + } + } + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + if (!apCarsToKeep[i]) { + apCarsToKeep[i] = pVehicle; + aCarsToKeepTime[i] = CTimer::GetTimeInMilliseconds(); + return; + } + } + uint32 oldestCarWeKeepTime = UINT32_MAX; + int oldestCarWeKeepIndex = 0; + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + if (apCarsToKeep[i] && aCarsToKeepTime[i] < oldestCarWeKeepTime) { + oldestCarWeKeepTime = aCarsToKeepTime[i]; + oldestCarWeKeepIndex = i; + } + } + apCarsToKeep[oldestCarWeKeepIndex] = pVehicle; + aCarsToKeepTime[oldestCarWeKeepIndex] = CTimer::GetTimeInMilliseconds(); +} + +bool +CCarCtrl::IsThisVehicleInteresting(CVehicle* pVehicle) +{ + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + if (apCarsToKeep[i] == pVehicle) + return true; + } + return false; +} + +void CCarCtrl::RemoveFromInterestingVehicleList(CVehicle* pVehicle) +{ + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + if (apCarsToKeep[i] == pVehicle) + apCarsToKeep[i] = nil; + } +} + +void CCarCtrl::ClearInterestingVehicleList() +{ + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + apCarsToKeep[i] = nil; + } +} + +void CCarCtrl::SwitchVehicleToRealPhysics(CVehicle* pVehicle) +{ + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds(); +} + +void CCarCtrl::JoinCarWithRoadSystem(CVehicle* pVehicle) +{ + pVehicle->AutoPilot.m_nPrevRouteNode = pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_nNextRouteNode = 0; + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = pVehicle->AutoPilot.m_nPreviousPathNodeInfo = pVehicle->AutoPilot.m_nNextPathNodeInfo = 0; + int nodeId = ThePaths.FindNodeClosestToCoorsFavourDirection(pVehicle->GetPosition(), 0, pVehicle->GetForward().x, pVehicle->GetForward().y); + CPathNode* pNode = &ThePaths.m_pathNodes[nodeId]; + int prevNodeId = -1; + float minDistance = 999999.9f; + for (int i = 0; i < pNode->numLinks; i++){ + int candidateId = ThePaths.ConnectedNode(i + pNode->firstLink); + CPathNode* pCandidateNode = &ThePaths.m_pathNodes[candidateId]; + float distance = (pCandidateNode->GetPosition() - pNode->GetPosition()).Magnitude2D(); + if (distance < minDistance){ + minDistance = distance; + prevNodeId = candidateId; + } + } + if (prevNodeId < 0) + return; + CVector2D forward = pVehicle->GetForward(); + CPathNode* pPrevNode = &ThePaths.m_pathNodes[prevNodeId]; + if (forward.x == 0.0f && forward.y == 0.0f) + forward.x = 1.0f; + if (DotProduct2D(pNode->GetPosition() - pPrevNode->GetPosition(), forward) < 0.0f){ + int tmp; + tmp = prevNodeId; + prevNodeId = nodeId; + nodeId = tmp; + } + pVehicle->AutoPilot.m_nPrevRouteNode = 0; + pVehicle->AutoPilot.m_nCurrentRouteNode = prevNodeId; + pVehicle->AutoPilot.m_nNextRouteNode = nodeId; + pVehicle->AutoPilot.m_nPathFindNodesCount = 0; + FindLinksToGoWithTheseNodes(pVehicle); + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane = 0; +} + +bool CCarCtrl::JoinCarWithRoadSystemGotoCoors(CVehicle* pVehicle, CVector vecTarget, bool isProperNow) +{ + pVehicle->AutoPilot.m_vecDestinationCoors = vecTarget; + ThePaths.DoPathSearch(0, pVehicle->GetPosition(), -1, vecTarget, pVehicle->AutoPilot.m_aPathFindNodesInfo, + &pVehicle->AutoPilot.m_nPathFindNodesCount, NUM_PATH_NODES_IN_AUTOPILOT, pVehicle, nil, 999999.9f, -1); + ThePaths.RemoveBadStartNode(pVehicle->GetPosition(), + pVehicle->AutoPilot.m_aPathFindNodesInfo, &pVehicle->AutoPilot.m_nPathFindNodesCount); + if (pVehicle->AutoPilot.m_nPathFindNodesCount < 2){ + pVehicle->AutoPilot.m_nPrevRouteNode = pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_nNextRouteNode = 0; + return true; + } + pVehicle->AutoPilot.m_nPrevRouteNode = 0; + pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_aPathFindNodesInfo[0] - ThePaths.m_pathNodes; + pVehicle->AutoPilot.RemoveOnePathNode(); + pVehicle->AutoPilot.m_nNextRouteNode = pVehicle->AutoPilot.m_aPathFindNodesInfo[0] - ThePaths.m_pathNodes; + pVehicle->AutoPilot.RemoveOnePathNode(); + FindLinksToGoWithTheseNodes(pVehicle); + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane = 0; + return false; +} + +void CCarCtrl::FindLinksToGoWithTheseNodes(CVehicle* pVehicle) +{ + int nextLink; + CPathNode* pCurNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nCurrentRouteNode]; + for (nextLink = 0; nextLink < 12; nextLink++) + if (ThePaths.ConnectedNode(nextLink + pCurNode->firstLink) == pVehicle->AutoPilot.m_nNextRouteNode) + break; + pVehicle->AutoPilot.m_nNextPathNodeInfo = ThePaths.m_carPathConnections[nextLink + pCurNode->firstLink]; + pVehicle->AutoPilot.m_nNextDirection = (pVehicle->AutoPilot.m_nCurrentRouteNode >= pVehicle->AutoPilot.m_nNextRouteNode) ? 1 : -1; + int curLink; + int curConnection; + if (pCurNode->numLinks == 1) { + curLink = 0; + curConnection = ThePaths.m_carPathConnections[pCurNode->firstLink]; + }else{ + curConnection = pVehicle->AutoPilot.m_nNextPathNodeInfo; + while (curConnection == pVehicle->AutoPilot.m_nNextPathNodeInfo){ + curLink = CGeneral::GetRandomNumber() % pCurNode->numLinks; + curConnection = ThePaths.m_carPathConnections[curLink + pCurNode->firstLink]; + } + } + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = curConnection; + pVehicle->AutoPilot.m_nCurrentDirection = (ThePaths.ConnectedNode(curLink + pCurNode->firstLink) >= pVehicle->AutoPilot.m_nCurrentRouteNode) ? 1 : -1; +} + +void CCarCtrl::GenerateEmergencyServicesCar(void) +{ + if (FindPlayerPed()->m_pWanted->GetWantedLevel() > 3) + return; + if (NumFiretrucksOnDuty + NumAmbulancesOnDuty + NumParkedCars + NumMissionCars + + NumLawEnforcerCars + NumRandomCars > MaxNumberOfCarsInUse) + return; + if (NumAmbulancesOnDuty == 0){ + if (gAccidentManager.CountActiveAccidents() < 2){ + if (CStreaming::HasModelLoaded(MI_AMBULAN)) + CStreaming::SetModelIsDeletable(MI_MEDIC); + }else{ + float distance = 30.0f; + CAccident* pNearestAccident = gAccidentManager.FindNearestAccident(FindPlayerCoors(), &distance); + if (pNearestAccident){ + if (CountCarsOfType(MI_AMBULAN) < 2 && CTimer::GetTimeInMilliseconds() > LastTimeAmbulanceCreated + 30000){ + CStreaming::RequestModel(MI_AMBULAN, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestModel(MI_MEDIC, STREAMFLAGS_DONT_REMOVE); + if (CStreaming::HasModelLoaded(MI_AMBULAN) && CStreaming::HasModelLoaded(MI_MEDIC)){ + if (GenerateOneEmergencyServicesCar(MI_AMBULAN, pNearestAccident->m_pVictim->GetPosition())) + LastTimeAmbulanceCreated = CTimer::GetTimeInMilliseconds(); + } + } + } + } + } + if (NumFiretrucksOnDuty == 0){ + if (gFireManager.GetTotalActiveFires() < 3){ + if (CStreaming::HasModelLoaded(MI_FIRETRUCK)) + CStreaming::SetModelIsDeletable(MI_FIREMAN); + }else{ + float distance = 30.0f; + CFire* pNearestFire = gFireManager.FindNearestFire(FindPlayerCoors(), &distance); + if (pNearestFire) { + if (CountCarsOfType(MI_FIRETRUCK) < 2 && CTimer::GetTimeInMilliseconds() > LastTimeFireTruckCreated + 35000){ + CStreaming::RequestModel(MI_FIRETRUCK, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestModel(MI_FIREMAN, STREAMFLAGS_DONT_REMOVE); + if (CStreaming::HasModelLoaded(MI_FIRETRUCK) && CStreaming::HasModelLoaded(MI_FIREMAN)){ + if (GenerateOneEmergencyServicesCar(MI_FIRETRUCK, pNearestFire->m_vecPos)) + LastTimeFireTruckCreated = CTimer::GetTimeInMilliseconds(); + } + } + } + } + } +} + +bool CCarCtrl::GenerateOneEmergencyServicesCar(uint32 mi, CVector vecPos) +{ + CVector pPlayerPos = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + bool created = false; + int attempts = 0; + CVector spawnPos; + int curNode, nextNode; + float posBetweenNodes; + while (!created && attempts < 5){ + if (ThePaths.NewGenerateCarCreationCoors(pPlayerPos.x, pPlayerPos.y, 0.707f, 0.707f, + 120.0f, -1.0f, true, &spawnPos, &curNode, &nextNode, &posBetweenNodes, false)){ + int16 colliding[2]; + CWorld::FindObjectsKindaColliding(spawnPos, 10.0f, true, colliding, 2, nil, false, true, true, false, false); + if (colliding[0] == 0) + created = true; + } + attempts += 1; + } + if (attempts >= 5) + return false; + CAutomobile* pVehicle = new CAutomobile(mi, RANDOM_VEHICLE); + pVehicle->AutoPilot.m_vecDestinationCoors = vecPos; + pVehicle->SetPosition(spawnPos); + pVehicle->AutoPilot.m_nCarMission = (JoinCarWithRoadSystemGotoCoors(pVehicle, vecPos, false)) ? MISSION_GOTOCOORDS_STRAIGHT : MISSION_GOTOCOORDS; + pVehicle->AutoPilot.m_fMaxTrafficSpeed = pVehicle->AutoPilot.m_nCruiseSpeed = 25; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + CVector2D direction = vecPos - spawnPos; + direction.NormaliseSafe(); + pVehicle->GetForward() = CVector(direction.x, direction.y, 0.0f); + pVehicle->GetRight() = CVector(direction.y, -direction.x, 0.0f); + pVehicle->GetUp() = CVector(0.0f, 0.0f, 1.0f); + spawnPos.z = posBetweenNodes * ThePaths.m_pathNodes[curNode].GetZ() + (1.0f - posBetweenNodes) * ThePaths.m_pathNodes[nextNode].GetZ(); + float groundZ = INFINITE_Z; + CColPoint colPoint; + CEntity* pEntity; + if (CWorld::ProcessVerticalLine(spawnPos, 1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil)) + groundZ = colPoint.point.z; + if (CWorld::ProcessVerticalLine(spawnPos, -1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil)) { + if (ABS(colPoint.point.z - spawnPos.z) < ABS(groundZ - spawnPos.z)) + groundZ = colPoint.point.z; + } + if (groundZ == INFINITE_Z) { + delete pVehicle; + return false; + } + spawnPos.z = groundZ + pVehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + pVehicle->SetPosition(spawnPos); + pVehicle->SetMoveSpeed(CVector(0.0f, 0.0f, 0.0f)); + pVehicle->SetStatus(STATUS_PHYSICS); + switch (mi){ + case MI_FIRETRUCK: + pVehicle->bIsFireTruckOnDuty = true; + ++NumFiretrucksOnDuty; + CCarAI::AddFiretruckOccupants(pVehicle); + break; + case MI_AMBULAN: + pVehicle->bIsAmbulanceOnDuty = true; + ++NumAmbulancesOnDuty; + CCarAI::AddAmbulanceOccupants(pVehicle); + break; + } + pVehicle->m_bSirenOrAlarm = true; + CWorld::Add(pVehicle); + printf("CREATED EMERGENCY VEHICLE\n"); + return true; +} + +void CCarCtrl::UpdateCarCount(CVehicle* pVehicle, bool remove) +{ + if (remove){ + switch (pVehicle->VehicleCreatedBy){ + case RANDOM_VEHICLE: + if (pVehicle->bIsLawEnforcer) + --NumLawEnforcerCars; + --NumRandomCars; + return; + case MISSION_VEHICLE: + --NumMissionCars; + return; + case PARKED_VEHICLE: + --NumParkedCars; + return; + case PERMANENT_VEHICLE: + --NumPermanentCars;; + return; + } + } + else{ + switch (pVehicle->VehicleCreatedBy){ + case RANDOM_VEHICLE: + if (pVehicle->bIsLawEnforcer) + ++NumLawEnforcerCars; + ++NumRandomCars; + return; + case MISSION_VEHICLE: + ++NumMissionCars; + return; + case PARKED_VEHICLE: + ++NumParkedCars; + return; + case PERMANENT_VEHICLE: + ++NumPermanentCars;; + return; + } + } +} + +bool CCarCtrl::ThisRoadObjectCouldMove(int16 mi) +{ + return mi == MI_BRIDGELIFT || mi == MI_BRIDGEROADSEGMENT; +} + +bool CCarCtrl::MapCouldMoveInThisArea(float x, float y) +{ + // bridge moves up and down + return x > -342.0f && x < -219.0f && + y > -677.0f && y < -580.0f; +} diff --git a/src/control/CarCtrl.h b/src/control/CarCtrl.h new file mode 100644 index 0000000..457224f --- /dev/null +++ b/src/control/CarCtrl.h @@ -0,0 +1,143 @@ +#pragma once +#include "PathFind.h" +#include "Boat.h" +#include "Vehicle.h" + +#define GAME_SPEED_TO_METERS_PER_SECOND 50.0f +#define METERS_PER_SECOND_TO_GAME_SPEED (1.0f / GAME_SPEED_TO_METERS_PER_SECOND) +#define GAME_SPEED_TO_CARAI_SPEED 60.0f +#define TIME_COPS_WAIT_TO_EXIT_AFTER_STOPPING 2500 + +class CZoneInfo; + +enum{ + MAX_CARS_TO_KEEP = 2, + MAX_CAR_MODELS_IN_ARRAY = 256, +}; + +#define LANE_WIDTH 5.0f + +#ifdef FIX_BUGS +#define FIX_PATHFIND_BUG +#endif + +class CCarCtrl +{ +public: + enum eCarClass { + POOR = 0, + RICH, + EXEC, + WORKER, + SPECIAL, + BIG, + TAXI, + TOTAL_CUSTOM_CLASSES, + MAFIA, + TRIAD, + DIABLO, + YAKUZA, + YARDIE, + COLOMB, + NINES, + GANG8, + GANG9, + COPS + }; + + static void SwitchVehicleToRealPhysics(CVehicle*); + static void AddToCarArray(int32 id, int32 vehclass); + static void UpdateCarCount(CVehicle*, bool); + static int32 ChooseCarModel(int32 vehclass); + static bool JoinCarWithRoadSystemGotoCoors(CVehicle*, CVector, bool); + static void JoinCarWithRoadSystem(CVehicle*); + static void UpdateCarOnRails(CVehicle*); + static bool MapCouldMoveInThisArea(float x, float y); + static void ScanForPedDanger(CVehicle *veh); + static void RemoveFromInterestingVehicleList(CVehicle*); + static void GenerateRandomCars(void); + static void GenerateOneRandomCar(void); + static void GenerateEmergencyServicesCar(void); + static int32 ChooseModel(CZoneInfo*, CVector*, int*); + static int32 ChoosePoliceCarModel(void); + static int32 ChooseGangCarModel(int32 gang); + static void RemoveDistantCars(void); + static void PossiblyRemoveVehicle(CVehicle*); + static bool IsThisVehicleInteresting(CVehicle*); + static void RegisterVehicleOfInterest(CVehicle*); + static int32 CountCarsOfType(int32 mi); + static void SlowCarOnRailsDownForTrafficAndLights(CVehicle*); + static bool PickNextNodeAccordingStrategy(CVehicle*); + static void DragCarToPoint(CVehicle*, CVector*); + static float FindMaximumSpeedForThisCarInTraffic(CVehicle*); + static void SlowCarDownForCarsSectorList(CPtrList&, CVehicle*, float, float, float, float, float*, float); + static void SlowCarDownForPedsSectorList(CPtrList&, CVehicle*, float, float, float, float, float*, float); + static void SlowCarDownForOtherCar(CEntity*, CVehicle*, float*, float); + static float TestCollisionBetween2MovingRects(CVehicle*, CVehicle*, float, float, CVector*, CVector*, uint8); + static float FindAngleToWeaveThroughTraffic(CVehicle*, CPhysical*, float, float); + static void WeaveThroughCarsSectorList(CPtrList&, CVehicle*, CPhysical*, float, float, float, float, float*, float*); + static void WeaveForOtherCar(CEntity*, CVehicle*, float*, float*); + static void WeaveThroughPedsSectorList(CPtrList&, CVehicle*, CPhysical*, float, float, float, float, float*, float*); + static void WeaveForPed(CEntity*, CVehicle*, float*, float*); + static void WeaveThroughObjectsSectorList(CPtrList&, CVehicle*, float, float, float, float, float*, float*); + static void WeaveForObject(CEntity*, CVehicle*, float*, float*); +#ifdef FIX_PATHFIND_BUG + static void PickNextNodeToChaseCar(CVehicle*, float, float, float, CVehicle*); +#else + static void PickNextNodeToChaseCar(CVehicle*, float, float, CVehicle*); +#endif + static bool PickNextNodeToFollowPath(CVehicle*); + static void PickNextNodeRandomly(CVehicle*); + static uint8 FindPathDirection(int32, int32, int32); + static void Init(void); + static void ReInit(void); + static float FindSpeedMultiplier(float, float, float, float); + static void SteerAICarWithPhysics(CVehicle*); + static void SteerAICarWithPhysics_OnlyMission(CVehicle*, float*, float*, float*, bool*); + static void SteerAIBoatWithPhysics(CBoat*); + static float FindMaxSteerAngle(CVehicle*); + static void SteerAICarWithPhysicsFollowPath(CVehicle*, float*, float*, float*, bool*); + static void SteerAICarWithPhysicsHeadingForTarget(CVehicle*, CPhysical*, float, float, float*, float*, float*, bool*); + static void SteerAICarWithPhysicsTryingToBlockTarget(CVehicle*, float, float, float, float, float*, float*, float*, bool*); + static void SteerAICarWithPhysicsTryingToBlockTarget_Stop(CVehicle*, float, float, float, float, float*, float*, float*, bool*); + static void SteerAIBoatWithPhysicsHeadingForTarget(CBoat*, float, float, float*, float*, float*); + static bool ThisRoadObjectCouldMove(int16); + static void ClearInterestingVehicleList(); + static void FindLinksToGoWithTheseNodes(CVehicle*); + static bool GenerateOneEmergencyServicesCar(uint32, CVector); + + static float GetPositionAlongCurrentCurve(CVehicle* pVehicle) + { + uint32 timeInCurve = CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeEnteredCurve; + return (float)timeInCurve / pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve; + } + + static float LimitRadianAngle(float angle) + { + while (angle < -PI) + angle += TWOPI; + while (angle > PI) + angle -= TWOPI; + return angle; + } + + static int32 NumLawEnforcerCars; + static int32 NumAmbulancesOnDuty; + static int32 NumFiretrucksOnDuty; + static int32 NumRandomCars; + static int32 NumMissionCars; + static int32 NumParkedCars; + static int32 NumPermanentCars; + static bool bCarsGeneratedAroundCamera; + static float CarDensityMultiplier; + static int8 CountDownToCarsAtStart; + static int32 MaxNumberOfCarsInUse; + static uint32 LastTimeLawEnforcerCreated; + static uint32 LastTimeFireTruckCreated; + static uint32 LastTimeAmbulanceCreated; + static int32 TotalNumOfCarsOfRating[TOTAL_CUSTOM_CLASSES]; + static int32 NextCarOfRating[TOTAL_CUSTOM_CLASSES]; + static int32 CarArrays[TOTAL_CUSTOM_CLASSES][MAX_CAR_MODELS_IN_ARRAY]; +}; + +extern CVehicle* apCarsToKeep[MAX_CARS_TO_KEEP]; \ No newline at end of file diff --git a/src/control/Curves.cpp b/src/control/Curves.cpp new file mode 100644 index 0000000..0a01a7a --- /dev/null +++ b/src/control/Curves.cpp @@ -0,0 +1,31 @@ +#include "common.h" + +#include "Curves.h" + +float CCurves::CalcSpeedScaleFactor(CVector* pPoint1, CVector* pPoint2, float dir1X, float dir1Y, float dir2X, float dir2Y) +{ + CVector2D dir1(dir1X, dir1Y); + CVector2D dir2(dir2X, dir2Y); + float distance = (*pPoint1 - *pPoint2).Magnitude2D(); + float dp = DotProduct2D(dir1, dir2); + if (dp > 0.9f) + return distance + Abs((pPoint1->x * dir1Y - pPoint1->y * dir1X) - (pPoint2->x * dir1Y - pPoint2->y * dir1X)); + else + return ((1.0f - dp) * 0.2f + 1.0f) * distance; +} + +void CCurves::CalcCurvePoint(CVector* pPos1, CVector* pPos2, CVector* pDir1, CVector* pDir2, float between, int32 timeOnCurve, CVector* pOutPos, CVector* pOutDir) +{ + float actualFactor = CalcSpeedScaleFactor(pPos1, pPos2, pDir1->x, pDir1->y, pDir2->x, pDir2->y); + CVector2D dir1 = *pDir1 * actualFactor; + CVector2D dir2 = *pDir2 * actualFactor; + float curveCoef = 0.5f - 0.5f * Cos(3.1415f * between); + *pOutPos = CVector( + (pPos1->x + between * dir1.x) * (1.0f - curveCoef) + (pPos2->x - (1 - between) * dir2.x) * curveCoef, + (pPos1->y + between * dir1.y) * (1.0f - curveCoef) + (pPos2->y - (1 - between) * dir2.y) * curveCoef, + 0.0f); + *pOutDir = CVector( + (dir1.x * (1.0f - curveCoef) + dir2.x * curveCoef) / (timeOnCurve * 0.001f), + (dir1.y * (1.0f - curveCoef) + dir2.y * curveCoef) / (timeOnCurve * 0.001f), + 0.0f); +} \ No newline at end of file diff --git a/src/control/Curves.h b/src/control/Curves.h new file mode 100644 index 0000000..5d4e05a --- /dev/null +++ b/src/control/Curves.h @@ -0,0 +1,9 @@ +#pragma once +class CVector; + +class CCurves +{ +public: + static float CalcSpeedScaleFactor(CVector*, CVector*, float, float, float, float); + static void CalcCurvePoint(CVector*, CVector*, CVector*, CVector*, float, int32, CVector*, CVector*); +}; diff --git a/src/control/Darkel.cpp b/src/control/Darkel.cpp new file mode 100644 index 0000000..9f6809d --- /dev/null +++ b/src/control/Darkel.cpp @@ -0,0 +1,448 @@ +#include "common.h" + +#include "main.h" +#include "Darkel.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Timer.h" +#include "DMAudio.h" +#include "Population.h" +#include "Weapon.h" +#include "World.h" +#include "Stats.h" +#include "Font.h" +#include "Text.h" +#include "Vehicle.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif + +#define FRENZY_ANY_PED -1 +#define FRENZY_ANY_CAR -2 + +int32 CDarkel::TimeLimit; +int32 CDarkel::PreviousTime; +int32 CDarkel::TimeOfFrenzyStart; +int32 CDarkel::WeaponType; +int32 CDarkel::AmmoInterruptedWeapon; +int32 CDarkel::KillsNeeded; +int8 CDarkel::InterruptedWeapon; + +/* + * bStandardSoundAndMessages is a completely beta thing, + * makes game handle sounds & messages instead of SCM (just like in GTA2) + * but it's never been used in the game. Has unused sliding text when frenzy completed etc. + */ +bool CDarkel::bStandardSoundAndMessages; +bool CDarkel::bNeedHeadShot; +bool CDarkel::bProperKillFrenzy; +uint16 CDarkel::Status; +uint16 CDarkel::RegisteredKills[NUM_DEFAULT_MODELS]; +int32 CDarkel::ModelToKill; +int32 CDarkel::ModelToKill2; +int32 CDarkel::ModelToKill3; +int32 CDarkel::ModelToKill4; +wchar *CDarkel::pStartMessage; + +uint8 +CDarkel::CalcFade(uint32 time, uint32 start, uint32 end) +{ + if (time >= start && time <= end) { + if (time >= start + 500) { + if (time <= end - 500) + return 255; + else + return 255 * (end - time) / 500; + } else + return 255 * (time - start) / 500; + } else + return 0; +} + +// Screen positions taken from VC +void +CDarkel::DrawMessages() +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + switch (Status) { + case KILLFRENZY_ONGOING: + { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 30)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 30); +#endif + CFont::SetCentreOn(); + CFont::SetPropOn(); + uint32 timePassedSinceStart = CTimer::GetTimeInMilliseconds() - CDarkel::TimeOfFrenzyStart; + if (CDarkel::bStandardSoundAndMessages) { + if (timePassedSinceStart >= 3000 && timePassedSinceStart < 11000) { +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.3f), SCREEN_SCALE_Y(1.3f)); +#else + CFont::SetScale(1.3f, 1.3f); +#endif + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 128, CalcFade(timePassedSinceStart, 3000, 11000))); + CFont::SetFontStyle(FONT_BANK); + if (pStartMessage) { + CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, pStartMessage); + } + } + } else { + if (timePassedSinceStart < 8000) { +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.3f), SCREEN_SCALE_Y(1.3f)); +#else + CFont::SetScale(1.3f, 1.3f); +#endif + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 128, CalcFade(timePassedSinceStart, 0, 8000))); + CFont::SetFontStyle(FONT_BANK); + if (pStartMessage) { + CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, pStartMessage); + } + } + } +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(0.75f), SCREEN_SCALE_Y(1.5f)); +#else + CFont::SetScale(0.75f, 1.5f); +#endif + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + if (CDarkel::TimeLimit >= 0) { + uint32 timeLeft = CDarkel::TimeLimit - (CTimer::GetTimeInMilliseconds() - CDarkel::TimeOfFrenzyStart); + sprintf(gString, "%d:%02d", timeLeft / 60000, timeLeft % 60000 / 1000); + AsciiToUnicode(gString, gUString); + if (timeLeft > 4000 || CTimer::GetFrameCounter() & 1) { + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#if defined(PS2_HUD) || defined(FIX_BUGS) + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f - 1.0f), SCREEN_SCALE_Y(108.0f + 1.0f), gUString); + #else + CFont::PrintString(SCREEN_WIDTH-(34.0f - 1.0f), 108.0f + 1.0f, gUString); + #endif +#else + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f + 1.0f), SCREEN_SCALE_Y(108.0f + 1.0f), gUString); +#endif + CFont::SetColor(CRGBA(150, 100, 255, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f), SCREEN_SCALE_Y(108.0f), gUString); + } + } + sprintf(gString, "%d", (CDarkel::KillsNeeded >= 0 ? CDarkel::KillsNeeded : 0)); + AsciiToUnicode(gString, gUString); + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS +#define DARKEL_COUNTER_HEIGHT 143.0f +#else +#define DARKEL_COUNTER_HEIGHT 128.0f +#endif + +#if defined(PS2_HUD) || defined(FIX_BUGS) + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f - 1.0f), SCREEN_SCALE_Y(DARKEL_COUNTER_HEIGHT + 1.0f), gUString); + #else + CFont::PrintString(SCREEN_WIDTH-(34.0f - 1.0f), DARKEL_COUNTER_HEIGHT + 1.0f, gUString); + #endif +#else + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f + 1.0f), SCREEN_SCALE_Y(DARKEL_COUNTER_HEIGHT + 1.0f), gUString); +#endif + CFont::SetColor(CRGBA(255, 128, 128, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f), SCREEN_SCALE_Y(DARKEL_COUNTER_HEIGHT), gUString); +#undef DARKEL_COUNTER_HEIGHT + break; + } + case KILLFRENZY_PASSED: + { + if (CDarkel::bStandardSoundAndMessages) { + uint32 timePassedSinceStart = CTimer::GetTimeInMilliseconds() - CDarkel::TimeOfFrenzyStart; + if (CTimer::GetTimeInMilliseconds() - CDarkel::TimeOfFrenzyStart < 5000) { + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 20); +#endif + CFont::SetCentreOn(); +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.5f)); +#else + CFont::SetScale(1.5f, 1.5f); +#endif + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(128, 255, 128, CalcFade(timePassedSinceStart, 0, 5000))); + CFont::SetFontStyle(FONT_BANK); +#ifdef FIX_BUGS + int y = SCREEN_HEIGHT / 2 + SCREEN_SCALE_Y(25.0f - timePassedSinceStart * 0.01f); +#else + int y = (SCREEN_HEIGHT / 2 + 25) - (timePassedSinceStart * 0.01f); +#endif + CFont::PrintString(SCREEN_WIDTH / 2, y, TheText.Get("KF_3")); + } + } + break; + } + default: + break; + } +} + +void +CDarkel::Init() +{ + Status = KILLFRENZY_NONE; +} + +uint16 +CDarkel::QueryModelsKilledByPlayer(int32 modelId) +{ + return RegisteredKills[modelId]; +} + + +bool +CDarkel::FrenzyOnGoing() +{ + return Status == KILLFRENZY_ONGOING; +} + + +uint16 +CDarkel::ReadStatus() +{ + return Status; +} + +void +CDarkel::RegisterCarBlownUpByPlayer(CVehicle *vehicle) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + if (FrenzyOnGoing()) { + int32 model = vehicle->GetModelIndex(); + if (ModelToKill == FRENZY_ANY_CAR || ModelToKill == model || ModelToKill2 == model || ModelToKill3 == model || ModelToKill4 == model) { + KillsNeeded--; + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_CAR_BLOWN, 0); + } + } + RegisteredKills[vehicle->GetModelIndex()]++; + CStats::CarsExploded++; +} + +void +CDarkel::RegisterKillByPlayer(CPed *victim, eWeaponType weapon, bool headshot) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + if (FrenzyOnGoing() && (weapon == WeaponType + || weapon == WEAPONTYPE_EXPLOSION + || weapon == WEAPONTYPE_UZI_DRIVEBY && WeaponType == WEAPONTYPE_UZI + || weapon == WEAPONTYPE_RAMMEDBYCAR && WeaponType == WEAPONTYPE_RUNOVERBYCAR + || weapon == WEAPONTYPE_RUNOVERBYCAR && WeaponType == WEAPONTYPE_RAMMEDBYCAR + || weapon == WEAPONTYPE_FLAMETHROWER && WeaponType == WEAPONTYPE_MOLOTOV)) { + int32 model = victim->GetModelIndex(); + if (ModelToKill == FRENZY_ANY_PED || ModelToKill == model || ModelToKill2 == model || ModelToKill3 == model || ModelToKill4 == model) { + if (!bNeedHeadShot || headshot) { + KillsNeeded--; + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_KILL, 0); + } + } + } + CStats::PeopleKilledByPlayer++; + RegisteredKills[victim->GetModelIndex()]++; + CStats::PedsKilledOfThisType[victim->bChrisCriminal ? PEDTYPE_CRIMINAL : victim->m_nPedType]++; + if (headshot) + CStats::HeadsPopped++; + CStats::KillsSinceLastCheckpoint++; +} + +void +CDarkel::RegisterKillNotByPlayer(CPed* victim, eWeaponType weapontype) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + CStats::PeopleKilledByOthers++; +} + +void +CDarkel::ResetModelsKilledByPlayer() +{ + for (int i = 0; i < NUM_DEFAULT_MODELS; i++) + RegisteredKills[i] = 0; +} + +void +CDarkel::ResetOnPlayerDeath() +{ + if (Status != KILLFRENZY_ONGOING) + return; + + CPopulation::m_AllRandomPedsThisType = -1; + Status = KILLFRENZY_FAILED; + TimeOfFrenzyStart = CTimer::GetTimeInMilliseconds(); + + eWeaponType fixedWeapon; + if (WeaponType == WEAPONTYPE_UZI_DRIVEBY) + fixedWeapon = WEAPONTYPE_UZI; + else + fixedWeapon = (eWeaponType)WeaponType; + + CPlayerPed *player = FindPlayerPed(); + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS) { + player->m_nSelectedWepSlot = InterruptedWeapon; + player->GetWeapon(player->GetWeaponSlot(fixedWeapon)).m_nAmmoTotal = CDarkel::AmmoInterruptedWeapon; + } + + if (FindPlayerVehicle()) { + player->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(player->GetWeapon()->m_eWeaponType)->m_nModelId); + player->m_currentWeapon = player->m_nSelectedWepSlot; + player->MakeChangesForNewWeapon(player->m_currentWeapon); + } +} + +void +CDarkel::StartFrenzy(eWeaponType weaponType, int32 time, uint16 kill, int32 modelId0, wchar *text, int32 modelId2, int32 modelId3, int32 modelId4, bool standardSound, bool needHeadShot) +{ + eWeaponType fixedWeapon; + if (weaponType == WEAPONTYPE_UZI_DRIVEBY) + fixedWeapon = WEAPONTYPE_UZI; + else + fixedWeapon = weaponType; + + WeaponType = weaponType; + Status = KILLFRENZY_ONGOING; + KillsNeeded = kill; + ModelToKill = modelId0; + ModelToKill2 = modelId2; + ModelToKill3 = modelId3; + ModelToKill4 = modelId4; + pStartMessage = text; + + if (text == TheText.Get("PAGE_00")) { + CDarkel::bProperKillFrenzy = true; + CDarkel::pStartMessage = nil; + } else + bProperKillFrenzy = false; + + bStandardSoundAndMessages = standardSound; + bNeedHeadShot = needHeadShot; + TimeOfFrenzyStart = CTimer::GetTimeInMilliseconds(); + TimeLimit = time; + PreviousTime = time / 1000; + + CPlayerPed *player = FindPlayerPed(); + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS) { + InterruptedWeapon = player->m_currentWeapon; + player->GiveWeapon(fixedWeapon, 0); + AmmoInterruptedWeapon = player->GetWeapon(player->GetWeaponSlot(fixedWeapon)).m_nAmmoTotal; + player->GiveWeapon(fixedWeapon, 30000); + player->m_nSelectedWepSlot = player->GetWeaponSlot(fixedWeapon); + player->MakeChangesForNewWeapon(player->m_nSelectedWepSlot); + + if (FindPlayerVehicle()) { + player->m_currentWeapon = player->m_nSelectedWepSlot; + player->GetWeapon()->m_nAmmoInClip = Min(player->GetWeapon()->m_nAmmoTotal, CWeaponInfo::GetWeaponInfo(player->GetWeapon()->m_eWeaponType)->m_nAmountofAmmunition); + player->ClearWeaponTarget(); + } + } + if (CDarkel::bStandardSoundAndMessages) + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_START, 0); +} + +void +CDarkel::Update() +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + + if (Status != KILLFRENZY_ONGOING) + return; + + int32 FrameTime = TimeLimit - (CTimer::GetTimeInMilliseconds() - TimeOfFrenzyStart); + if (FrameTime > 0 || TimeLimit < 0) { + + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_ONGOING, FrameTime); + + int32 PrevTime = FrameTime / 1000; + + if (PrevTime != PreviousTime) { + if (PreviousTime < 12) + DMAudio.PlayFrontEndSound(SOUND_CLOCK_TICK, PrevTime); + PreviousTime = PrevTime; + } + + } else { + CPopulation::m_AllRandomPedsThisType = -1; + Status = KILLFRENZY_FAILED; + TimeOfFrenzyStart = CTimer::GetTimeInMilliseconds(); + + eWeaponType fixedWeapon; + if (WeaponType == WEAPONTYPE_UZI_DRIVEBY) + fixedWeapon = WEAPONTYPE_UZI; + else + fixedWeapon = (eWeaponType)WeaponType; + + CPlayerPed *player = FindPlayerPed(); + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS) { + player->m_nSelectedWepSlot = InterruptedWeapon; + player->GetWeapon(player->GetWeaponSlot(fixedWeapon)).m_nAmmoTotal = CDarkel::AmmoInterruptedWeapon; + } + + if (FindPlayerVehicle()) { + player->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(player->GetWeapon()->m_eWeaponType)->m_nModelId); + player->m_currentWeapon = player->m_nSelectedWepSlot; + player->MakeChangesForNewWeapon(player->m_currentWeapon); + } + + if (bStandardSoundAndMessages) + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_FAILED, 0); + } + + if (KillsNeeded <= 0) { + CPopulation::m_AllRandomPedsThisType = -1; + Status = KILLFRENZY_PASSED; + + if (bProperKillFrenzy) + CStats::AnotherKillFrenzyPassed(); + + TimeOfFrenzyStart = CTimer::GetTimeInMilliseconds(); + + FindPlayerPed()->m_pWanted->SetWantedLevel(0); + + eWeaponType fixedWeapon; + if (WeaponType == WEAPONTYPE_UZI_DRIVEBY) + fixedWeapon = WEAPONTYPE_UZI; + else + fixedWeapon = (eWeaponType)WeaponType; + + CPlayerPed* player = FindPlayerPed(); + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS) { + player->m_nSelectedWepSlot = InterruptedWeapon; + player->GetWeapon(player->GetWeaponSlot(fixedWeapon)).m_nAmmoTotal = CDarkel::AmmoInterruptedWeapon; + } + + if (FindPlayerVehicle()) { + player->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(player->GetWeapon()->m_eWeaponType)->m_nModelId); + player->m_currentWeapon = player->m_nSelectedWepSlot; + player->MakeChangesForNewWeapon(player->m_currentWeapon); + } + + if (bStandardSoundAndMessages) + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_PASSED, 0); + } +} diff --git a/src/control/Darkel.h b/src/control/Darkel.h new file mode 100644 index 0000000..0f5c232 --- /dev/null +++ b/src/control/Darkel.h @@ -0,0 +1,53 @@ +#pragma once + +#include "ModelIndices.h" +#include "WeaponType.h" + +class CVehicle; +class CPed; + +enum +{ + KILLFRENZY_NONE, + KILLFRENZY_ONGOING, + KILLFRENZY_PASSED, + KILLFRENZY_FAILED, +}; + +class CDarkel +{ +private: + static int32 TimeLimit; + static int32 PreviousTime; + static int32 TimeOfFrenzyStart; + static int32 WeaponType; + static int32 AmmoInterruptedWeapon; + static int32 KillsNeeded; + static int8 InterruptedWeapon; + static bool bStandardSoundAndMessages; + static bool bNeedHeadShot; + static bool bProperKillFrenzy; + static uint16 Status; + static uint16 RegisteredKills[NUM_DEFAULT_MODELS]; + static int32 ModelToKill; + static int32 ModelToKill2; + static int32 ModelToKill3; + static int32 ModelToKill4; + static wchar *pStartMessage; + +public: + static uint8 CalcFade(uint32 time, uint32 min, uint32 max); + static void DrawMessages(void); + static bool FrenzyOnGoing(); + static void Init(); + static uint16 QueryModelsKilledByPlayer(int32 modelId); + static uint16 ReadStatus(); + static void RegisterCarBlownUpByPlayer(CVehicle *vehicle); + static void RegisterKillByPlayer(CPed *victim, eWeaponType weapontype, bool headshot = false); + static void RegisterKillNotByPlayer(CPed* victim, eWeaponType weapontype); + static void ResetModelsKilledByPlayer(); + static void ResetOnPlayerDeath(); + static void StartFrenzy(eWeaponType weaponType, int32 time, uint16 kill, int32 modelId0, wchar *text, int32 modelId2, int32 modelId3, int32 modelId4, bool standardSound, bool needHeadShot); + static void Update(); + +}; diff --git a/src/control/GameLogic.cpp b/src/control/GameLogic.cpp new file mode 100644 index 0000000..19e0f83 --- /dev/null +++ b/src/control/GameLogic.cpp @@ -0,0 +1,320 @@ +#include "common.h" + +#include "GameLogic.h" +#include "Clock.h" +#include "Stats.h" +#include "Pickups.h" +#include "Timer.h" +#include "Streaming.h" +#include "CutsceneMgr.h" +#include "World.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Camera.h" +#include "Messages.h" +#include "CarCtrl.h" +#include "Restart.h" +#include "Pad.h" +#include "References.h" +#include "Fire.h" +#include "Script.h" +#include "Garages.h" +#include "screendroplets.h" + +uint8 CGameLogic::ActivePlayers; + +void +CGameLogic::InitAtStartOfGame() +{ + ActivePlayers = 1; +} + +void +CGameLogic::PassTime(uint32 time) +{ + int32 minutes, hours, days; + + minutes = time + CClock::GetMinutes(); + hours = CClock::GetHours(); + + for (; minutes >= 60; minutes -= 60) + hours++; + + if (hours > 23) { + days = CStats::DaysPassed; + for (; hours >= 24; hours -= 24) + days++; + CStats::DaysPassed = days; + } + + CClock::SetGameClock(hours, minutes); + CPickups::PassTime(time * 1000); +} + +void +CGameLogic::SortOutStreamingAndMemory(const CVector &pos) +{ + CTimer::Stop(); + CStreaming::FlushRequestList(); + CStreaming::DeleteRwObjectsAfterDeath(pos); + CStreaming::RemoveUnusedModelsInLoadedList(); + CGame::DrasticTidyUpMemory(true); + CStreaming::LoadScene(pos); + CTimer::Update(); +} + +void +CGameLogic::Update() +{ + CVector vecRestartPos; + float fRestartFloat; + + if (CCutsceneMgr::IsCutsceneProcessing()) return; + + CPlayerInfo &pPlayerInfo = CWorld::Players[CWorld::PlayerInFocus]; + switch (pPlayerInfo.m_WBState) { + case WBSTATE_PLAYING: + if (pPlayerInfo.m_pPed->m_nPedState == PED_DEAD) { + pPlayerInfo.m_pPed->ClearAdrenaline(); + pPlayerInfo.KillPlayer(); + } + if (pPlayerInfo.m_pPed->m_nPedState == PED_ARRESTED) { + pPlayerInfo.m_pPed->ClearAdrenaline(); + pPlayerInfo.ArrestPlayer(); + } + break; + case WBSTATE_WASTED: +#ifdef MISSION_REPLAY + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > AddExtraDeathDelay() + 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= AddExtraDeathDelay() + 0x800)) { +#else + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= 0x800)) { +#endif + TheCamera.SetFadeColour(200, 200, 200); + TheCamera.Fade(2.0f, FADE_OUT); + } + +#ifdef MISSION_REPLAY + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= AddExtraDeathDelay() + 0x1000) { +#else + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= 0x1000) { +#endif + pPlayerInfo.m_WBState = WBSTATE_PLAYING; + if (pPlayerInfo.m_bGetOutOfHospitalFree) { + pPlayerInfo.m_bGetOutOfHospitalFree = false; + } else { + pPlayerInfo.m_nMoney = Max(0, pPlayerInfo.m_nMoney - 1000); + pPlayerInfo.m_pPed->ClearWeapons(); + } + + if (pPlayerInfo.m_pPed->bInVehicle) { + CVehicle *pVehicle = pPlayerInfo.m_pPed->m_pMyVehicle; + if (pVehicle != nil) { + if (pVehicle->pDriver == pPlayerInfo.m_pPed) { + pVehicle->pDriver = nil; + if (pVehicle->GetStatus() != STATUS_WRECKED) + pVehicle->SetStatus(STATUS_ABANDONED); + } else + pVehicle->RemovePassenger(pPlayerInfo.m_pPed); + } + } + CEventList::Initialise(); +#ifdef SCREEN_DROPLETS + ScreenDroplets::Initialise(); +#endif + CMessages::ClearMessages(); + CCarCtrl::ClearInterestingVehicleList(); + CWorld::ClearExcitingStuffFromArea(pPlayerInfo.GetPos(), 4000.0f, 1); + CRestart::FindClosestHospitalRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat); + CRestart::OverrideHospitalLevel = LEVEL_GENERIC; + CRestart::OverridePoliceStationLevel = LEVEL_GENERIC; + PassTime(720); + RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat); + SortOutStreamingAndMemory(pPlayerInfo.GetPos()); + TheCamera.m_fCamShakeForce = 0.0f; + TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE); + CPad::GetPad(0)->StopShaking(0); + CReferences::RemoveReferencesToPlayer(); + CCarCtrl::CountDownToCarsAtStart = 2; + CPad::GetPad(CWorld::PlayerInFocus)->DisablePlayerControls = PLAYERCONTROL_ENABLED; + if (CRestart::bFadeInAfterNextDeath) { + TheCamera.SetFadeColour(200, 200, 200); + TheCamera.Fade(4.0f, FADE_IN); + } else CRestart::bFadeInAfterNextDeath = true; + } + break; + case WBSTATE_BUSTED: +#ifdef MISSION_REPLAY + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > AddExtraDeathDelay() + 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= AddExtraDeathDelay() + 0x800)) { +#else + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= 0x800)) { +#endif + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(2.0f, FADE_OUT); + } +#ifdef MISSION_REPLAY + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= AddExtraDeathDelay() + 0x1000) { +#else + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= 0x1000) { +#endif + pPlayerInfo.m_WBState = WBSTATE_PLAYING; + int takeMoney; + + switch (pPlayerInfo.m_pPed->m_pWanted->GetWantedLevel()) { + case 0: + case 1: + takeMoney = 100; + break; + case 2: + takeMoney = 200; + break; + case 3: + takeMoney = 400; + break; + case 4: + takeMoney = 600; + break; + case 5: + takeMoney = 900; + break; + case 6: + takeMoney = 1500; + break; + } + if (pPlayerInfo.m_bGetOutOfJailFree) { + pPlayerInfo.m_bGetOutOfJailFree = false; + } else { + pPlayerInfo.m_nMoney = Max(0, pPlayerInfo.m_nMoney - takeMoney); + pPlayerInfo.m_pPed->ClearWeapons(); + } + + if (pPlayerInfo.m_pPed->bInVehicle) { + CVehicle *pVehicle = pPlayerInfo.m_pPed->m_pMyVehicle; + if (pVehicle != nil) { + if (pVehicle->pDriver == pPlayerInfo.m_pPed) { + pVehicle->pDriver = nil; + if (pVehicle->GetStatus() != STATUS_WRECKED) + pVehicle->SetStatus(STATUS_ABANDONED); + } + else + pVehicle->RemovePassenger(pPlayerInfo.m_pPed); + } + } + CEventList::Initialise(); +#ifdef SCREEN_DROPLETS + ScreenDroplets::Initialise(); +#endif + CMessages::ClearMessages(); + CCarCtrl::ClearInterestingVehicleList(); + CWorld::ClearExcitingStuffFromArea(pPlayerInfo.GetPos(), 4000.0f, 1); + CRestart::FindClosestPoliceRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat); + CRestart::OverrideHospitalLevel = LEVEL_GENERIC; + CRestart::OverridePoliceStationLevel = LEVEL_GENERIC; + PassTime(720); + RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat); + pPlayerInfo.m_pPed->ClearWeapons(); + SortOutStreamingAndMemory(pPlayerInfo.GetPos()); + TheCamera.m_fCamShakeForce = 0.0f; + TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE); + CPad::GetPad(0)->StopShaking(0); + CReferences::RemoveReferencesToPlayer(); + CCarCtrl::CountDownToCarsAtStart = 2; + CPad::GetPad(CWorld::PlayerInFocus)->DisablePlayerControls = PLAYERCONTROL_ENABLED; + if (CRestart::bFadeInAfterNextArrest) { + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(4.0f, FADE_IN); + } else CRestart::bFadeInAfterNextArrest = true; + } + break; + case WBSTATE_FAILED_CRITICAL_MISSION: +#ifdef MISSION_REPLAY + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > AddExtraDeathDelay() + 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= AddExtraDeathDelay() + 0x800)) { +#else + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= 0x800)) { +#endif + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(2.0f, FADE_OUT); + } +#ifdef MISSION_REPLAY + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= AddExtraDeathDelay() + 0x1000) { +#else + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= 0x1000) { +#endif + pPlayerInfo.m_WBState = WBSTATE_PLAYING; + if (pPlayerInfo.m_pPed->bInVehicle) { + CVehicle *pVehicle = pPlayerInfo.m_pPed->m_pMyVehicle; + if (pVehicle != nil) { + if (pVehicle->pDriver == pPlayerInfo.m_pPed) { + pVehicle->pDriver = nil; + if (pVehicle->GetStatus() != STATUS_WRECKED) + pVehicle->SetStatus(STATUS_ABANDONED); + } else + pVehicle->RemovePassenger(pPlayerInfo.m_pPed); + } + } + CEventList::Initialise(); +#ifdef SCREEN_DROPLETS + ScreenDroplets::Initialise(); +#endif + CMessages::ClearMessages(); + CCarCtrl::ClearInterestingVehicleList(); + CWorld::ClearExcitingStuffFromArea(pPlayerInfo.GetPos(), 4000.0f, 1); + CRestart::FindClosestPoliceRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat); + CRestart::OverridePoliceStationLevel = LEVEL_GENERIC; + CRestart::OverrideHospitalLevel = LEVEL_GENERIC; + RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat); + SortOutStreamingAndMemory(pPlayerInfo.GetPos()); + TheCamera.m_fCamShakeForce = 0.0f; + TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE); + CPad::GetPad(0)->StopShaking(0); + CReferences::RemoveReferencesToPlayer(); + CCarCtrl::CountDownToCarsAtStart = 2; + CPad::GetPad(CWorld::PlayerInFocus)->DisablePlayerControls = PLAYERCONTROL_ENABLED; + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(4.0f, FADE_IN); + } + break; + case 4: + return; + } +} + +void +CGameLogic::RestorePlayerStuffDuringResurrection(CPlayerPed *pPlayerPed, CVector pos, float angle) +{ + pPlayerPed->m_fHealth = 100.0f; + pPlayerPed->m_fArmour = 0.0f; + pPlayerPed->bIsVisible = true; + pPlayerPed->m_bloodyFootprintCountOrDeathTime = 0; + pPlayerPed->bDoBloodyFootprints = false; + pPlayerPed->ClearAdrenaline(); + pPlayerPed->m_fCurrentStamina = pPlayerPed->m_fMaxStamina; + if (pPlayerPed->m_pFire) + pPlayerPed->m_pFire->Extinguish(); + pPlayerPed->bInVehicle = false; + pPlayerPed->m_pMyVehicle = nil; + pPlayerPed->m_pVehicleAnim = nil; + pPlayerPed->m_pWanted->Reset(); + pPlayerPed->RestartNonPartialAnims(); + pPlayerPed->GetPlayerInfoForThisPlayerPed()->MakePlayerSafe(false); + pPlayerPed->bRemoveFromWorld = false; + pPlayerPed->ClearWeaponTarget(); + pPlayerPed->SetInitialState(); + CCarCtrl::ClearInterestingVehicleList(); + + pos.z += 1.0f; + pPlayerPed->Teleport(pos); + pPlayerPed->SetMoveSpeed(CVector(0.0f, 0.0f, 0.0f)); + + pPlayerPed->m_fRotationCur = DEGTORAD(angle); + pPlayerPed->m_fRotationDest = pPlayerPed->m_fRotationCur; + pPlayerPed->SetHeading(pPlayerPed->m_fRotationCur); + CTheScripts::ClearSpaceForMissionEntity(pos, pPlayerPed); + CWorld::ClearExcitingStuffFromArea(pos, 4000.0, 1); + pPlayerPed->RestoreHeadingRate(); + TheCamera.SetCameraDirectlyInFrontForFollowPed_CamOnAString(); + CReferences::RemoveReferencesToPlayer(); + CGarages::PlayerArrestedOrDied(); + CStats::CheckPointReachedUnsuccessfully(); + CWorld::Remove(pPlayerPed); + CWorld::Add(pPlayerPed); +} diff --git a/src/control/GameLogic.h b/src/control/GameLogic.h new file mode 100644 index 0000000..43e244a --- /dev/null +++ b/src/control/GameLogic.h @@ -0,0 +1,13 @@ +#pragma once + +class CGameLogic +{ +public: + static void InitAtStartOfGame(); + static void PassTime(uint32 time); + static void SortOutStreamingAndMemory(const CVector &pos); + static void Update(); + static void RestorePlayerStuffDuringResurrection(class CPlayerPed *pPlayerPed, CVector pos, float angle); + + static uint8 ActivePlayers; +}; \ No newline at end of file diff --git a/src/control/Garages.cpp b/src/control/Garages.cpp new file mode 100644 index 0000000..bb919ea --- /dev/null +++ b/src/control/Garages.cpp @@ -0,0 +1,2548 @@ +#include "common.h" + +#include "Garages.h" +#include "main.h" + +#ifdef FIX_BUGS +#include "Boat.h" +#endif +#include "DMAudio.h" +#include "General.h" +#include "Font.h" +#include "HandlingMgr.h" +#include "Hud.h" +#include "Messages.h" +#include "ModelIndices.h" +#include "Pad.h" +#include "Particle.h" +#include "PlayerPed.h" +#include "Replay.h" +#include "Stats.h" +#include "Streaming.h" +#include "Text.h" +#include "Timer.h" +#include "Vehicle.h" +#include "Wanted.h" +#include "World.h" +#include "SaveBuf.h" + +#define ROTATED_DOOR_OPEN_SPEED (0.015f) +#define ROTATED_DOOR_CLOSE_SPEED (0.02f) +#define DEFAULT_DOOR_OPEN_SPEED (0.035f) +#define DEFAULT_DOOR_CLOSE_SPEED (0.04f) +#define CRUSHER_CRANE_SPEED (0.005f) + +// Prices +#define BOMB_PRICE (1000) +#define RESPRAY_PRICE (1000) + +// Distances +#define DISTANCE_TO_CALL_OFF_CHASE (10.0f) +#define DISTANCE_FOR_MRWHOOP_HACK (4.0f) +#define DISTANCE_TO_ACTIVATE_GARAGE (8.0f) +#define DISTANCE_TO_ACTIVATE_KEEPCAR_GARAGE (17.0f) +#define DISTANCE_TO_CLOSE_MISSION_GARAGE (30.0f) +#define DISTANCE_TO_CLOSE_COLLECTSPECIFICCARS_GARAGE (25.0f) +#define DISTANCE_TO_CLOSE_COLLECTCARS_GARAGE (40.0f) +#define DISTANCE_TO_CLOSE_HIDEOUT_GARAGE_ON_FOOT (2.2f) +#define DISTANCE_TO_CLOSE_HIDEOUT_GARAGE_IN_CAR (15.0f) +#define DISTANCE_TO_FORCE_CLOSE_HIDEOUT_GARAGE (70.0f) +#define DISTANCE_TO_OPEN_HIDEOUT_GARAGE_ON_FOOT (1.7f) +#define DISTANCE_TO_OPEN_HIDEOUT_GARAGE_IN_CAR (10.0f) +#define DISTANCE_TO_SHOW_HIDEOUT_MESSAGE (5.0f) + +#define DISTANCE_TO_CONSIDER_DOOR_FOR_GARAGE (20.0f) + +// Time +#define TIME_TO_RESPRAY (2000) +#define TIME_TO_SETUP_BOMB (2000) +#define TIME_TO_CRUSH_CAR (3000) +#define TIME_TO_PROCESS_KEEPCAR_GARAGE (2000) + +// Respray stuff +#define FREE_RESPRAY_HEALTH_THRESHOLD (970.0f) +#define NUM_PARTICLES_IN_RESPRAY (200) +#define RESPRAY_CENTERING_COEFFICIENT (0.75f) + +// Bomb stuff +#define KGS_OF_EXPLOSIVES_IN_BOMB (10) + +// Collect specific cars stuff +#define REWARD_FOR_FIRST_POLICE_CAR (5000) +#define REWARD_FOR_FIRST_BANK_VAN (5000) +#define MAX_POLICE_CARS_TO_COLLECT (10) +#define MAX_BANK_VANS_TO_COLLECT (10) + +// Collect cars stuff +#define MAX_SPEED_TO_SHOW_COLLECTED_MESSAGE (0.03f) +#define IMPORT_REWARD (1000) +#define IMPORT_ALLCARS_REWARD (200000) + +// Crusher stuff +#define CRUSHER_VEHICLE_TEST_SPAN (8) +#define CRUSHER_MIN_REWARD (25) +#define CRUSHER_MAX_REWARD (125) +#define CRUSHER_REWARD_COEFFICIENT (1.0f/500000) + +// Hideout stuff +#define MAX_STORED_CARS_IN_INDUSTRIAL (1) +#define MAX_STORED_CARS_IN_COMMERCIAL (NUM_GARAGE_STORED_CARS) +#define MAX_STORED_CARS_IN_SUBURBAN (NUM_GARAGE_STORED_CARS) +#define LIMIT_CARS_IN_INDUSTRIAL (1) +#define LIMIT_CARS_IN_COMMERCIAL (2) +#define LIMIT_CARS_IN_SUBURBAN (3) +#define HIDEOUT_DOOR_SPEED_COEFFICIENT (1.7f) +#define TIME_BETWEEN_HIDEOUT_MESSAGES (18000) + +// Camera stuff +#define MARGIN_FOR_CAMERA_COLLECTCARS (1.3f) +#define MARGIN_FOR_CAMERA_DEFAULT (4.0f) + +const int32 gaCarsToCollectInCraigsGarages[TOTAL_COLLECTCARS_GARAGES][TOTAL_COLLECTCARS_CARS] = +{ + { MI_SECURICA, MI_MOONBEAM, MI_COACH, MI_FLATBED, MI_LINERUN, MI_TRASH, MI_PATRIOT, MI_MRWHOOP, MI_BLISTA, MI_MULE, MI_YANKEE, MI_BOBCAT, MI_DODO, MI_BUS, MI_RUMPO, MI_PONY }, + { MI_SENTINEL, MI_CHEETAH, MI_BANSHEE, MI_IDAHO, MI_INFERNUS, MI_TAXI, MI_KURUMA, MI_STRETCH, MI_PEREN, MI_STINGER, MI_MANANA, MI_LANDSTAL, MI_STALLION, MI_BFINJECT, MI_CABBIE, MI_ESPERANT }, + { MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_CHEETAH, MI_TAXI, MI_ESPERANT, MI_SENTINEL, MI_IDAHO } +}; + +const int32 gaCarsToCollectIn60Seconds[] = { MI_CHEETAH, MI_TAXI, MI_ESPERANT, MI_SENTINEL, MI_IDAHO }; + +int32 CGarages::BankVansCollected; +bool CGarages::BombsAreFree; +bool CGarages::RespraysAreFree; +int32 CGarages::CarsCollected; +int32 CGarages::CarTypesCollected[TOTAL_COLLECTCARS_GARAGES]; +int32 CGarages::CrushedCarId; +uint32 CGarages::LastTimeHelpMessage; +int32 CGarages::MessageNumberInString; +char CGarages::MessageIDString[MESSAGE_LENGTH]; +int32 CGarages::MessageNumberInString2; +uint32 CGarages::MessageStartTime; +uint32 CGarages::MessageEndTime; +uint32 CGarages::NumGarages; +bool CGarages::PlayerInGarage; +int32 CGarages::PoliceCarsCollected; +CStoredCar CGarages::aCarsInSafeHouse1[NUM_GARAGE_STORED_CARS]; +CStoredCar CGarages::aCarsInSafeHouse2[NUM_GARAGE_STORED_CARS]; +CStoredCar CGarages::aCarsInSafeHouse3[NUM_GARAGE_STORED_CARS]; +int32 hGarages = AEHANDLE_NONE; +CGarage CGarages::aGarages[NUM_GARAGES]; +bool CGarages::bCamShouldBeOutisde; + +void CGarages::Init(void) +{ + CrushedCarId = -1; + NumGarages = 0; + MessageEndTime = 0; + MessageStartTime = 0; + PlayerInGarage = false; + BombsAreFree = false; +#ifdef FIX_BUGS + RespraysAreFree = false; +#endif + CarsCollected = 0; + BankVansCollected = 0; + PoliceCarsCollected = 0; + for (int i = 0; i < TOTAL_COLLECTCARS_GARAGES; i++) + CarTypesCollected[i] = 0; + LastTimeHelpMessage = 0; + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) + aCarsInSafeHouse1[i].Init(); + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) + aCarsInSafeHouse2[i].Init(); + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) + aCarsInSafeHouse3[i].Init(); + hGarages = DMAudio.CreateEntity(AUDIOTYPE_GARAGE, (void*)1); + if (hGarages >= 0) + DMAudio.SetEntityStatus(hGarages, TRUE); + AddOne( + CVector(CRUSHER_GARAGE_X1, CRUSHER_GARAGE_Y1, CRUSHER_GARAGE_Z1), + CVector(CRUSHER_GARAGE_X2, CRUSHER_GARAGE_Y2, CRUSHER_GARAGE_Z2), + GARAGE_CRUSHER, 0); +} + +#ifndef PS2 +void CGarages::Shutdown(void) +{ + NumGarages = 0; + if (hGarages < 0) + return; + DMAudio.DestroyEntity(hGarages); + hGarages = AEHANDLE_NONE; +} +#endif + +void CGarages::Update(void) +{ + static int GarageToBeTidied = 0; + if (CReplay::IsPlayingBack()) + return; + bCamShouldBeOutisde = false; + TheCamera.pToGarageWeAreIn = nil; + TheCamera.pToGarageWeAreInForHackAvoidFirstPerson = nil; + for (int i = 0; i < NUM_GARAGES; i++) { + if (aGarages[i].IsUsed()) + aGarages[i].Update(); + } + if ((CTimer::GetFrameCounter() & 0xF) != 0xC) + return; + if (++GarageToBeTidied >= NUM_GARAGES) + GarageToBeTidied = 0; + if (!aGarages[GarageToBeTidied].IsUsed()) + return; + if (!aGarages[GarageToBeTidied].IsFar()) + aGarages[GarageToBeTidied].TidyUpGarageClose(); + else + aGarages[GarageToBeTidied].TidyUpGarage(); +} + +int16 CGarages::AddOne(CVector p1, CVector p2, uint8 type, int32 targetId) +{ + if (NumGarages >= NUM_GARAGES) { + assert(0); + return NumGarages++; + } + CGarage* pGarage = &aGarages[NumGarages]; + pGarage->m_fX1 = Min(p1.x, p2.x); + pGarage->m_fX2 = Max(p1.x, p2.x); + pGarage->m_fY1 = Min(p1.y, p2.y); + pGarage->m_fY2 = Max(p1.y, p2.y); + pGarage->m_fZ1 = Min(p1.z, p2.z); + pGarage->m_fZ2 = Max(p1.z, p2.z); + pGarage->m_pDoor1 = nil; + pGarage->m_pDoor2 = nil; + pGarage->m_fDoor1Z = p1.z; + pGarage->m_fDoor2Z = p1.z; + pGarage->m_eGarageType = type; + pGarage->m_bRecreateDoorOnNextRefresh = false; + pGarage->m_bRotatedDoor = false; + pGarage->m_bCameraFollowsPlayer = false; + pGarage->RefreshDoorPointers(true); + if (pGarage->m_pDoor1) { + pGarage->m_fDoor1Z = pGarage->m_pDoor1->GetPosition().z; + pGarage->m_fDoor1X = pGarage->m_pDoor1->GetPosition().x; + pGarage->m_fDoor1Y = pGarage->m_pDoor1->GetPosition().y; + } + if (pGarage->m_pDoor2) { + pGarage->m_fDoor2Z = pGarage->m_pDoor2->GetPosition().z; + pGarage->m_fDoor2X = pGarage->m_pDoor2->GetPosition().x; + pGarage->m_fDoor2Y = pGarage->m_pDoor2->GetPosition().y; + } + pGarage->m_fDoorHeight = pGarage->m_pDoor1 ? FindDoorHeightForMI(pGarage->m_pDoor1->GetModelIndex()) : 4.0f; + pGarage->m_fDoorPos = 0.0f; + pGarage->m_eGarageState = GS_FULLYCLOSED; + pGarage->m_nTimeToStartAction = 0; + pGarage->field_2 = false; + pGarage->m_nTargetModelIndex = targetId; + pGarage->field_96 = nil; + pGarage->m_bCollectedCarsState = 0; + pGarage->m_bDeactivated = false; + pGarage->m_bResprayHappened = false; + switch (type) { + case GARAGE_MISSION: + case GARAGE_COLLECTORSITEMS: + case GARAGE_COLLECTSPECIFICCARS: + case GARAGE_COLLECTCARS_1: + case GARAGE_COLLECTCARS_2: + case GARAGE_COLLECTCARS_3: + case GARAGE_FORCARTOCOMEOUTOF: + case GARAGE_60SECONDS: + case GARAGE_MISSION_KEEPCAR: + case GARAGE_FOR_SCRIPT_TO_OPEN: + case GARAGE_HIDEOUT_ONE: + case GARAGE_HIDEOUT_TWO: + case GARAGE_HIDEOUT_THREE: + case GARAGE_FOR_SCRIPT_TO_OPEN_AND_CLOSE: + case GARAGE_KEEPS_OPENING_FOR_SPECIFIC_CAR: + case GARAGE_MISSION_KEEPCAR_REMAINCLOSED: + pGarage->m_eGarageState = GS_FULLYCLOSED; + pGarage->m_fDoorPos = 0.0f; + break; + case GARAGE_BOMBSHOP1: + case GARAGE_BOMBSHOP2: + case GARAGE_BOMBSHOP3: + case GARAGE_RESPRAY: + pGarage->m_eGarageState = GS_OPENED; + pGarage->m_fDoorPos = pGarage->m_fDoorHeight; + break; + case GARAGE_CRUSHER: + pGarage->m_eGarageState = GS_OPENED; + pGarage->m_fDoorPos = HALFPI; + break; + default: + assert(false); + } + if (type == GARAGE_CRUSHER) + pGarage->UpdateCrusherAngle(); + else + pGarage->UpdateDoorsHeight(); + return NumGarages++; +} + +void CGarages::ChangeGarageType(int16 garage, uint8 type, int32 mi) +{ + CGarage* pGarage = &aGarages[garage]; + pGarage->m_eGarageType = type; + pGarage->m_nTargetModelIndex = mi; + pGarage->m_eGarageState = GS_FULLYCLOSED; +} + +void CGarage::Update() +{ + if (m_eGarageType != GARAGE_CRUSHER) { + switch (m_eGarageState) { + case GS_FULLYCLOSED: + case GS_OPENED: + case GS_CLOSING: + case GS_OPENING: + case GS_OPENEDCONTAINSCAR: + case GS_CLOSEDCONTAINSCAR: + if (FindPlayerPed() && !m_bCameraFollowsPlayer) { + CVehicle* pVehicle = FindPlayerVehicle(); + if (IsEntityEntirelyInside3D(FindPlayerPed(), 0.25f)) { + TheCamera.pToGarageWeAreIn = this; + CGarages::bCamShouldBeOutisde = true; + } + if (pVehicle) { + if (!IsEntityEntirelyOutside(pVehicle, 0.0f)) + TheCamera.pToGarageWeAreInForHackAvoidFirstPerson = this; + if (pVehicle->GetModelIndex() == MI_MRWHOOP) { + if (pVehicle->IsWithinArea( + m_fX1 - DISTANCE_FOR_MRWHOOP_HACK, + m_fY1 + DISTANCE_FOR_MRWHOOP_HACK, + m_fX2 - DISTANCE_FOR_MRWHOOP_HACK, + m_fY2 + DISTANCE_FOR_MRWHOOP_HACK)) { + TheCamera.pToGarageWeAreIn = this; + CGarages::bCamShouldBeOutisde = true; + } + } + } + } + break; + default: + break; + } + } + if (m_bDeactivated && m_eGarageState == GS_FULLYCLOSED) + return; + switch (m_eGarageType) { + case GARAGE_RESPRAY: + switch (m_eGarageState) { + case GS_OPENED: + if (IsStaticPlayerCarEntirelyInside() && !IsAnyOtherCarTouchingGarage(FindPlayerVehicle())) { + if (CGarages::IsCarSprayable(FindPlayerVehicle())) { + if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney >= RESPRAY_PRICE || CGarages::RespraysAreFree) { + m_eGarageState = GS_CLOSING; + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + } + else { + CGarages::TriggerMessage("GA_3", -1, 4000, -1); // No more freebies. $1000 to respray! + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayFrontEndSound(SOUND_GARAGE_NO_MONEY, 1); + } + } + else { + CGarages::TriggerMessage("GA_1", -1, 4000, -1); // Whoa! I don't touch nothing THAT hot! + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayFrontEndSound(SOUND_GARAGE_BAD_VEHICLE, 1); + } + } + if (FindPlayerVehicle()) { + if (CalcDistToGarageRectangleSquared(FindPlayerVehicle()->GetPosition().x, FindPlayerVehicle()->GetPosition().y) < SQR(DISTANCE_TO_ACTIVATE_GARAGE)) + CWorld::CallOffChaseForArea( + m_fX1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fY1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fX2 + DISTANCE_TO_CALL_OFF_CHASE, + m_fY2 + DISTANCE_TO_CALL_OFF_CHASE); + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + m_nTimeToStartAction = CTimer::GetTimeInMilliseconds() + TIME_TO_RESPRAY; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + CStats::CheckPointReachedSuccessfully(); + } + UpdateDoorsHeight(); +#ifdef FIX_BUGS + if (FindPlayerVehicle() && FindPlayerVehicle()->IsCar()) +#else + if (FindPlayerVehicle()) +#endif + ((CAutomobile*)(FindPlayerVehicle()))->m_fFireBlowUpTimer = 0.0f; + CWorld::CallOffChaseForArea( + m_fX1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fY1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fX2 + DISTANCE_TO_CALL_OFF_CHASE, + m_fY2 + DISTANCE_TO_CALL_OFF_CHASE); + break; + case GS_FULLYCLOSED: + if (CTimer::GetTimeInMilliseconds() > m_nTimeToStartAction) { + m_eGarageState = GS_OPENING; + DMAudio.PlayFrontEndSound(SOUND_GARAGE_OPENING, 1); + bool bTakeMoney = false; + if (FindPlayerPed()->m_pWanted->GetWantedLevel() != 0) + bTakeMoney = true; + FindPlayerPed()->m_pWanted->Reset(); + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; +#ifdef FIX_BUGS + bool bChangedColour = false; +#else + bool bChangedColour; +#endif + if (FindPlayerVehicle() && FindPlayerVehicle()->IsCar()) { + if (FindPlayerVehicle()->m_fHealth < FREE_RESPRAY_HEALTH_THRESHOLD) + bTakeMoney = true; + FindPlayerVehicle()->m_fHealth = 1000.0f; + ((CAutomobile*)(FindPlayerVehicle()))->m_fFireBlowUpTimer = 0.0f; + ((CAutomobile*)(FindPlayerVehicle()))->Fix(); + if (FindPlayerVehicle()->GetUp().z < 0.0f) { + FindPlayerVehicle()->GetUp() = -FindPlayerVehicle()->GetUp(); + FindPlayerVehicle()->GetRight() = -FindPlayerVehicle()->GetRight(); + } + bChangedColour = false; + if (!((CAutomobile*)(FindPlayerVehicle()))->bFixedColour) { + uint8 colour1, colour2; + uint16 attempt; + FindPlayerVehicle()->GetModelInfo()->ChooseVehicleColour(colour1, colour2); + for (attempt = 0; attempt < 10; attempt++) { + if (colour1 != FindPlayerVehicle()->m_currentColour1 || colour2 != FindPlayerVehicle()->m_currentColour2) + break; + FindPlayerVehicle()->GetModelInfo()->ChooseVehicleColour(colour1, colour2); + } + bChangedColour = (attempt < 10); + FindPlayerVehicle()->m_currentColour1 = colour1; + FindPlayerVehicle()->m_currentColour2 = colour2; + if (bChangedColour) { + for (int i = 0; i < NUM_PARTICLES_IN_RESPRAY; i++) { + CVector pos; +#ifdef FIX_BUGS + pos.x = CGeneral::GetRandomNumberInRange(m_fX1 + 0.5f, m_fX2 - 0.5f); + pos.y = CGeneral::GetRandomNumberInRange(m_fY1 + 0.5f, m_fY2 - 0.5f); + pos.z = CGeneral::GetRandomNumberInRange(m_fDoor1Z - 3.0f, m_fDoor1Z + 1.0f); +#else + // wtf is this + pos.x = m_fX1 + 0.5f + (uint8)(CGeneral::GetRandomNumber()) / 256.0f * (m_fX2 - m_fX1 - 1.0f); + pos.y = m_fY1 + 0.5f + (uint8)(CGeneral::GetRandomNumber()) / 256.0f * (m_fY2 - m_fY1 - 1.0f); + pos.z = m_fDoor1Z - 3.0f + (uint8)(CGeneral::GetRandomNumber()) / 256.0f * 4.0f; +#endif + CParticle::AddParticle(PARTICLE_GARAGEPAINT_SPRAY, pos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, CVehicleModelInfo::ms_vehicleColourTable[colour1]); + } + } + } + CenterCarInGarage(FindPlayerVehicle()); + } + if (bTakeMoney) { + if (!CGarages::RespraysAreFree) + CWorld::Players[CWorld::PlayerInFocus].m_nMoney = Max(0, CWorld::Players[CWorld::PlayerInFocus].m_nMoney - RESPRAY_PRICE); + CGarages::TriggerMessage("GA_2", -1, 4000, -1); // New engine and paint job. The cops won't recognize you! + } + else if (bChangedColour) { + if (CGeneral::GetRandomTrueFalse()) + CGarages::TriggerMessage("GA_15", -1, 4000, -1); // Hope you like the new color. + else + CGarages::TriggerMessage("GA_16", -1, 4000, -1); // Respray is complementary. + } + m_bResprayHappened = true; + } + CWorld::CallOffChaseForArea( + m_fX1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fY1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fX2 + DISTANCE_TO_CALL_OFF_CHASE, + m_fY2 + DISTANCE_TO_CALL_OFF_CHASE); + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_OPENEDCONTAINSCAR: + if (IsPlayerOutsideGarage()) + m_eGarageState = GS_OPENED; + break; + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_BOMBSHOP1: + case GARAGE_BOMBSHOP2: + case GARAGE_BOMBSHOP3: + switch (m_eGarageState) { + case GS_OPENED: + if (IsStaticPlayerCarEntirelyInside() && !IsAnyOtherCarTouchingGarage(FindPlayerVehicle())) { +#ifdef FIX_BUGS // FindPlayerVehicle() can never be NULL here because IsStaticPlayerCarEntirelyInside() is true, and there is no IsCar() check + if (FindPlayerVehicle()->IsCar() && ((CAutomobile*)FindPlayerVehicle())->m_bombType) { +#else + if (!FindPlayerVehicle() || ((CAutomobile*)FindPlayerVehicle())->m_bombType) { +#endif + CGarages::TriggerMessage("GA_5", -1, 4000, -1); //"Your car is already fitted with a bomb" + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayFrontEndSound(SOUND_GARAGE_BOMB_ALREADY_SET, 1); + break; + } + if (!CGarages::BombsAreFree && CWorld::Players[CWorld::PlayerInFocus].m_nMoney < BOMB_PRICE) { + CGarages::TriggerMessage("GA_4", -1, 4000, -1); // "Car bombs are $1000 each" - weird that the price is hardcoded in message + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayFrontEndSound(SOUND_GARAGE_NO_MONEY, 1); + break; + } + m_eGarageState = GS_CLOSING; + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + m_nTimeToStartAction = CTimer::GetTimeInMilliseconds() + TIME_TO_SETUP_BOMB; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (CTimer::GetTimeInMilliseconds() > m_nTimeToStartAction) { + switch (m_eGarageType) { + case GARAGE_BOMBSHOP1: DMAudio.PlayFrontEndSound(SOUND_GARAGE_BOMB1_SET, 1); break; + case GARAGE_BOMBSHOP2: DMAudio.PlayFrontEndSound(SOUND_GARAGE_BOMB2_SET, 1); break; + case GARAGE_BOMBSHOP3: DMAudio.PlayFrontEndSound(SOUND_GARAGE_BOMB3_SET, 1); break; + default: break; + } + m_eGarageState = GS_OPENING; + if (!CGarages::BombsAreFree) + CWorld::Players[CWorld::PlayerInFocus].m_nMoney = Max(0, CWorld::Players[CWorld::PlayerInFocus].m_nMoney - BOMB_PRICE); + if (FindPlayerVehicle() && FindPlayerVehicle()->IsCar()) { + ((CAutomobile*)(FindPlayerVehicle()))->m_bombType = CGarages::GetBombTypeForGarageType(m_eGarageType); + ((CAutomobile*)(FindPlayerVehicle()))->m_pBombRigger = FindPlayerPed(); + if (m_eGarageType == GARAGE_BOMBSHOP3) + CGarages::GivePlayerDetonator(); + CStats::KgsOfExplosivesUsed += KGS_OF_EXPLOSIVES_IN_BOMB; + } +#ifdef DETECT_PAD_INPUT_SWITCH + int16 Mode = CPad::IsAffectedByController ? CPad::GetPad(0)->Mode : 0; +#else + int16 Mode = CPad::GetPad(0)->Mode; +#endif + switch (m_eGarageType) { + case GARAGE_BOMBSHOP1: + switch (Mode) { + case 0: + case 1: + case 2: + CHud::SetHelpMessage(TheText.Get("GA_6"), false); // Arm with ~h~~k~~PED_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + break; + case 3: + CHud::SetHelpMessage(TheText.Get("GA_6B"), false); // Arm with ~h~~k~~PED_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + break; + } + break; + case GARAGE_BOMBSHOP2: + switch (Mode) { + case 0: + case 1: + case 2: + CHud::SetHelpMessage(TheText.Get("GA_7"), false); // Park it, prime it by pressing the ~h~~k~~PED_FIREWEAPON~ button~w~ and LEG IT! + break; + case 3: + CHud::SetHelpMessage(TheText.Get("GA_7B"), false); // Park it, prime it by pressing the ~h~~k~~PED_FIREWEAPON~ button~w~ and LEG IT! + break; + } + break; + case GARAGE_BOMBSHOP3: + CHud::SetHelpMessage(TheText.Get("GA_8"), false); // Use the detonator to activate the bomb. + break; + default: break; + } + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + } + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_OPENEDCONTAINSCAR: + if (IsPlayerOutsideGarage()) + m_eGarageState = GS_OPENED; + break; + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_MISSION: + switch (m_eGarageState) { + case GS_OPENED: + if (((CVector2D)FindPlayerCoors() - CVector2D(GetGarageCenterX(), GetGarageCenterY())).MagnitudeSqr() > SQR(DISTANCE_TO_CLOSE_MISSION_GARAGE)) { + if ((CTimer::GetFrameCounter() & 0x1F) == 0 && !IsAnyOtherCarTouchingGarage(nil)) { + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = true; + } + } + else if (!FindPlayerVehicle() && m_pTarget && IsEntityEntirelyInside3D(m_pTarget, 0.0f) && + !IsAnyOtherCarTouchingGarage(m_pTarget) && IsEntityEntirelyOutside(FindPlayerPed(), 2.0f) && + !IsAnyOtherCarTouchingGarage(m_pTarget)) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = false; + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + if (m_bClosingWithoutTargetCar) + m_eGarageState = GS_FULLYCLOSED; + else { + if (m_pTarget) { + m_eGarageState = GS_CLOSEDCONTAINSCAR; + DestroyVehicleAndDriverAndPassengers(m_pTarget); + m_pTarget = nil; + } + else { + m_eGarageState = GS_FULLYCLOSED; + } + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + } + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (FindPlayerVehicle() == m_pTarget && m_pTarget) { + if (CalcDistToGarageRectangleSquared( + FindPlayerVehicle()->GetPosition().x, + FindPlayerVehicle()->GetPosition().y) < SQR(DISTANCE_TO_ACTIVATE_GARAGE)) + m_eGarageState = GS_OPENING; + } + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_COLLECTSPECIFICCARS: + switch (m_eGarageState) { + case GS_OPENED: + if (FindPlayerVehicle() && m_nTargetModelIndex == FindPlayerVehicle()->GetModelIndex()) { + m_pTarget = FindPlayerVehicle(); + m_pTarget->RegisterReference((CEntity**)&m_pTarget); + } + if (!FindPlayerVehicle()) { + if (m_pTarget && IsEntityEntirelyInside3D(m_pTarget, 0.0f) && !IsAnyOtherCarTouchingGarage(m_pTarget)) { + if (IsEntityEntirelyOutside(FindPlayerPed(), 2.0f)) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + m_eGarageState = GS_CLOSING; + } + } + else if (Abs(FindPlayerCoors().x - GetGarageCenterX()) > DISTANCE_TO_CLOSE_COLLECTSPECIFICCARS_GARAGE || + Abs(FindPlayerCoors().y - GetGarageCenterY()) > DISTANCE_TO_CLOSE_COLLECTSPECIFICCARS_GARAGE) { + m_eGarageState = GS_CLOSING; + m_pTarget = nil; + } + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + if (m_pTarget) { + DestroyVehicleAndDriverAndPassengers(m_pTarget); + m_pTarget = nil; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + int16 reward; + switch (m_nTargetModelIndex) { + case MI_POLICE: + reward = REWARD_FOR_FIRST_POLICE_CAR * (MAX_POLICE_CARS_TO_COLLECT - CGarages::PoliceCarsCollected++) / MAX_POLICE_CARS_TO_COLLECT; + break; + case MI_SECURICA: + reward = REWARD_FOR_FIRST_BANK_VAN * (MAX_BANK_VANS_TO_COLLECT - CGarages::BankVansCollected++) / MAX_BANK_VANS_TO_COLLECT; + break; +#ifdef FIX_BUGS // not possible though + default: + reward = 0; + break; +#endif + } + if (reward > 0) { + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += reward; + CGarages::TriggerMessage("GA_10", reward, 4000, -1); // Nice one. Here's your $~1~ + DMAudio.PlayFrontEndSound(SOUND_GARAGE_VEHICLE_ACCEPTED, 1); + } + else { + CGarages::TriggerMessage("GA_11", -1, 4000, -1); // We got these wheels already. It's worthless to us! + DMAudio.PlayFrontEndSound(SOUND_GARAGE_VEHICLE_DECLINED, 1); + } + } + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (FindPlayerVehicle() && m_nTargetModelIndex == FindPlayerVehicle()->GetModelIndex()) { + if (CalcDistToGarageRectangleSquared(FindPlayerVehicle()->GetPosition().x, FindPlayerVehicle()->GetPosition().y) < SQR(DISTANCE_TO_ACTIVATE_GARAGE)) + m_eGarageState = GS_OPENING; + } + break; + case GS_OPENING: + if (FindPlayerVehicle() && m_nTargetModelIndex == FindPlayerVehicle()->GetModelIndex()) { + m_pTarget = FindPlayerVehicle(); + m_pTarget->RegisterReference((CEntity**)&m_pTarget); + } + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_COLLECTCARS_1: + case GARAGE_COLLECTCARS_2: + case GARAGE_COLLECTCARS_3: + switch (m_eGarageState) { + case GS_OPENED: + if (FindPlayerVehicle() && DoesCraigNeedThisCar(FindPlayerVehicle()->GetModelIndex())) { + m_pTarget = FindPlayerVehicle(); + m_pTarget->RegisterReference((CEntity**)&m_pTarget); + } + if (Abs(FindPlayerCoors().x - GetGarageCenterX()) > DISTANCE_TO_CLOSE_COLLECTCARS_GARAGE || + Abs(FindPlayerCoors().y - GetGarageCenterY()) > DISTANCE_TO_CLOSE_COLLECTCARS_GARAGE) { + m_eGarageState = GS_CLOSING; + m_pTarget = nil; + break; + } + if (m_pTarget && !FindPlayerVehicle() && IsEntityEntirelyInside3D(m_pTarget, 0.0f) && + !IsAnyOtherCarTouchingGarage(m_pTarget) && IsEntityEntirelyOutside(FindPlayerPed(), 2.0f)) { +#ifdef FIX_BUGS + if (!m_pTarget->IsCar() || + ((CAutomobile*)(m_pTarget))->Damage.GetEngineStatus() <= ENGINE_STATUS_ON_FIRE && + ((CAutomobile*)(m_pTarget))->m_fFireBlowUpTimer == 0.0f) { +#else + if (((CAutomobile*)(m_pTarget))->Damage.GetEngineStatus() <= ENGINE_STATUS_ON_FIRE && + ((CAutomobile*)(m_pTarget))->m_fFireBlowUpTimer == 0.0f) { +#endif + if (m_pTarget->GetStatus() != STATUS_WRECKED) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + m_eGarageState = GS_CLOSING; + TheCamera.SetCameraDirectlyBehindForFollowPed_CamOnAString(); + } + } + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + if (m_pTarget) { + MarkThisCarAsCollectedForCraig(m_pTarget->GetModelIndex()); + DestroyVehicleAndDriverAndPassengers(m_pTarget); + m_pTarget = nil; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + } + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (FindPlayerVehicle() && + CalcSmallestDistToGarageDoorSquared( + FindPlayerVehicle()->GetPosition().x, + FindPlayerVehicle()->GetPosition().y + ) < SQR(DISTANCE_TO_ACTIVATE_GARAGE)) { + if (DoesCraigNeedThisCar(FindPlayerVehicle()->GetModelIndex())) { + if (FindPlayerVehicle()->VehicleCreatedBy == MISSION_VEHICLE) + CGarages::TriggerMessage("GA_1A", -1, 5000, -1); // Come back when you're not so busy... + else + m_eGarageState = GS_OPENING; + } + else { + if (HasCraigCollectedThisCar(FindPlayerVehicle()->GetModelIndex())) + CGarages::TriggerMessage("GA_20", -1, 5000, -1); // We got more of these than we can shift. Sorry man, no deal. + else if (FindPlayerSpeed().Magnitude() < MAX_SPEED_TO_SHOW_COLLECTED_MESSAGE) + CGarages::TriggerMessage("GA_19", -1, 5000, -1); // We're not interested in that model. + } + } + m_pTarget = nil; + break; + case GS_OPENING: + if (FindPlayerVehicle() && DoesCraigNeedThisCar(FindPlayerVehicle()->GetModelIndex())) { + m_pTarget = FindPlayerVehicle(); + m_pTarget->RegisterReference((CEntity**)&m_pTarget); + } + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_FORCARTOCOMEOUTOF: + switch (m_eGarageState) { + case GS_OPENED: + if (IsGarageEmpty()) + m_eGarageState = GS_CLOSING; + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + } + if (!IsGarageEmpty()) + m_eGarageState = GS_OPENING; + break; + case GS_FULLYCLOSED: + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_CRUSHER: + switch (m_eGarageState) { + case GS_OPENED: + { + int i = CPools::GetVehiclePool()->GetSize() * (CTimer::GetFrameCounter() % CRUSHER_VEHICLE_TEST_SPAN) / CRUSHER_VEHICLE_TEST_SPAN; + int end = CPools::GetVehiclePool()->GetSize() * (CTimer::GetFrameCounter() % CRUSHER_VEHICLE_TEST_SPAN + 1) / CRUSHER_VEHICLE_TEST_SPAN; + for (; i < end; i++) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (pVehicle->IsCar() && IsEntityEntirelyInside3D(pVehicle, 0.0f)) { + m_eGarageState = GS_CLOSING; + m_pTarget = pVehicle; + m_pTarget->RegisterReference((CEntity**)&m_pTarget); + } + } + break; + } + case GS_CLOSING: + if (m_pTarget) { + m_fDoorPos = Max(0.0f, m_fDoorPos - CRUSHER_CRANE_SPEED * CTimer::GetTimeStep()); + if (m_fDoorPos < TWOPI / 5) { + m_pTarget->bUsesCollision = false; + m_pTarget->bAffectedByGravity = false; + m_pTarget->SetMoveSpeed(0.0f, 0.0f, 0.0f); + } + else { + m_pTarget->SetMoveSpeed(m_pTarget->GetMoveSpeed() * Pow(0.8f, CTimer::GetTimeStep())); + } + if (m_fDoorPos == 0.0f) { + CGarages::CrushedCarId = CPools::GetVehiclePool()->GetIndex(m_pTarget); + float reward = Min(CRUSHER_MAX_REWARD, CRUSHER_MIN_REWARD + m_pTarget->pHandling->nMonetaryValue * m_pTarget->m_fHealth * CRUSHER_REWARD_COEFFICIENT); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += reward; + DestroyVehicleAndDriverAndPassengers(m_pTarget); + ++CStats::CarsCrushed; + m_pTarget = nil; + m_eGarageState = GS_AFTERDROPOFF; + m_nTimeToStartAction = CTimer::GetTimeInMilliseconds() + TIME_TO_CRUSH_CAR; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + } + } + else + m_eGarageState = GS_OPENING; + UpdateCrusherAngle(); + break; + case GS_AFTERDROPOFF: + if (CTimer::GetTimeInMilliseconds() <= m_nTimeToStartAction) { + UpdateCrusherShake((myrand() & 0xFF - 128) * 0.0002f, (myrand() & 0xFF - 128) * 0.0002f); + } + else { + UpdateCrusherShake(0.0f, 0.0f); + m_eGarageState = GS_OPENING; + } + break; + case GS_OPENING: + m_fDoorPos = Min(HALFPI, m_fDoorPos + CTimer::GetTimeStep() * CRUSHER_CRANE_SPEED); + if (m_fDoorPos == HALFPI) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateCrusherAngle(); + break; + //case GS_FULLYCLOSED: + //case GS_CLOSEDCONTAINSCAR: + //case GS_OPENEDCONTAINSCAR: + default: + break; + } + if (!FindPlayerVehicle() && (CTimer::GetFrameCounter() & 0x1F) == 0x17 && IsEntityEntirelyInside(FindPlayerPed())) + FindPlayerPed()->InflictDamage(nil, WEAPONTYPE_RAMMEDBYCAR, 300.0f, PEDPIECE_TORSO, 0); + break; + case GARAGE_MISSION_KEEPCAR: + case GARAGE_MISSION_KEEPCAR_REMAINCLOSED: + switch (m_eGarageState) { + case GS_OPENED: + if (((CVector2D)FindPlayerCoors() - CVector2D(GetGarageCenterX(), GetGarageCenterY())).MagnitudeSqr() > SQR(DISTANCE_TO_CLOSE_MISSION_GARAGE) && + !IsAnyOtherCarTouchingGarage(nil)) { + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = true; + } + else if (m_pTarget && m_pTarget == FindPlayerVehicle() && IsStaticPlayerCarEntirelyInside() && !IsAnyCarBlockingDoor()) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = false; + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + if (m_bClosingWithoutTargetCar) + m_eGarageState = GS_FULLYCLOSED; + else { + if (m_pTarget) { + m_eGarageState = GS_CLOSEDCONTAINSCAR; + m_nTimeToStartAction = CTimer::GetTimeInMilliseconds() + TIME_TO_PROCESS_KEEPCAR_GARAGE; + m_pTarget = nil; + } + else + m_eGarageState = GS_FULLYCLOSED; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + } + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (FindPlayerVehicle() == m_pTarget && m_pTarget && + CalcDistToGarageRectangleSquared( + FindPlayerVehicle()->GetPosition().x, + FindPlayerVehicle()->GetPosition().y + ) < SQR(DISTANCE_TO_ACTIVATE_KEEPCAR_GARAGE)) + m_eGarageState = GS_OPENING; + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_CLOSEDCONTAINSCAR: + if (m_eGarageType == GARAGE_MISSION_KEEPCAR && CTimer::GetTimeInMilliseconds() > m_nTimeToStartAction) + m_eGarageState = GS_OPENING; + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_FOR_SCRIPT_TO_OPEN: + switch (m_eGarageState) { + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENED: + //case GS_CLOSING: + //case GS_FULLYCLOSED: + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_FOR_SCRIPT_TO_OPEN_AND_CLOSE: + switch (m_eGarageState) { + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENED: + //case GS_FULLYCLOSED: + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_HIDEOUT_ONE: + case GARAGE_HIDEOUT_TWO: + case GARAGE_HIDEOUT_THREE: + switch (m_eGarageState) { + case GS_OPENED: + { + float distance = CalcDistToGarageRectangleSquared(FindPlayerCoors().x, FindPlayerCoors().y); + // Close car doors either if player is far, or if he is in vehicle and garage is full, + // or if player is very very far so that we can remove whatever is blocking garage door without him noticing + if ((distance > SQR(DISTANCE_TO_CLOSE_HIDEOUT_GARAGE_IN_CAR) || + !FindPlayerVehicle() && distance > SQR(DISTANCE_TO_CLOSE_HIDEOUT_GARAGE_ON_FOOT)) && + !IsAnyCarBlockingDoor()) + m_eGarageState = GS_CLOSING; + else if (FindPlayerVehicle() && + CountCarsWithCenterPointWithinGarage(FindPlayerVehicle()) >= + CGarages::FindMaxNumStoredCarsForGarage(m_eGarageType)) { + m_eGarageState = GS_CLOSING; + } + else if (distance > SQR(DISTANCE_TO_FORCE_CLOSE_HIDEOUT_GARAGE)) { + m_eGarageState = GS_CLOSING; + RemoveCarsBlockingDoorNotInside(); + } + break; + } + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - HIDEOUT_DOOR_SPEED_COEFFICIENT * (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (!IsPlayerOutsideGarage()) + m_eGarageState = GS_OPENING; + else if (m_fDoorPos == 0.0f) { + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + m_eGarageState = GS_FULLYCLOSED; + switch (m_eGarageType) { + case GARAGE_HIDEOUT_ONE: StoreAndRemoveCarsForThisHideout(CGarages::aCarsInSafeHouse1, MAX_STORED_CARS_IN_INDUSTRIAL); break; + case GARAGE_HIDEOUT_TWO: StoreAndRemoveCarsForThisHideout(CGarages::aCarsInSafeHouse2, MAX_STORED_CARS_IN_COMMERCIAL); break; + case GARAGE_HIDEOUT_THREE: StoreAndRemoveCarsForThisHideout(CGarages::aCarsInSafeHouse3, MAX_STORED_CARS_IN_SUBURBAN); break; + default: break; + } + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + { + float distance = CalcDistToGarageRectangleSquared(FindPlayerCoors().x, FindPlayerCoors().y); + if (distance < SQR(DISTANCE_TO_OPEN_HIDEOUT_GARAGE_ON_FOOT) || + distance < SQR(DISTANCE_TO_OPEN_HIDEOUT_GARAGE_IN_CAR) && FindPlayerVehicle()) { + if (FindPlayerVehicle() && CGarages::CountCarsInHideoutGarage(m_eGarageType) >= CGarages::FindMaxNumStoredCarsForGarage(m_eGarageType)) { + if (m_pDoor1) { + if (((CVector2D)FindPlayerVehicle()->GetPosition() - (CVector2D)m_pDoor1->GetPosition()).MagnitudeSqr() < SQR(DISTANCE_TO_SHOW_HIDEOUT_MESSAGE) && + CTimer::GetTimeInMilliseconds() - CGarages::LastTimeHelpMessage > TIME_BETWEEN_HIDEOUT_MESSAGES) { + CHud::SetHelpMessage(TheText.Get("GA_21"), false); // You cannot store any more cars in this garage. + CGarages::LastTimeHelpMessage = CTimer::GetTimeInMilliseconds(); + } + } + } + else { +#ifdef FIX_BUGS + bool bCreatedAllCars = false; +#else + bool bCreatedAllCars; +#endif + switch (m_eGarageType) { + case GARAGE_HIDEOUT_ONE: bCreatedAllCars = RestoreCarsForThisHideout(CGarages::aCarsInSafeHouse1); break; + case GARAGE_HIDEOUT_TWO: bCreatedAllCars = RestoreCarsForThisHideout(CGarages::aCarsInSafeHouse2); break; + case GARAGE_HIDEOUT_THREE: bCreatedAllCars = RestoreCarsForThisHideout(CGarages::aCarsInSafeHouse3); break; + default: break; + } + if (bCreatedAllCars) + m_eGarageState = GS_OPENING; + } + } + break; + } + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + HIDEOUT_DOOR_SPEED_COEFFICIENT * (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_KEEPS_OPENING_FOR_SPECIFIC_CAR: + switch (m_eGarageState) { + case GS_OPENED: + if (((CVector2D)FindPlayerCoors() - CVector2D(GetGarageCenterX(), GetGarageCenterY())).MagnitudeSqr() > SQR(DISTANCE_TO_CLOSE_MISSION_GARAGE)) { + if (m_pTarget && IsEntityEntirelyOutside(m_pTarget, 0.0f) && !IsAnyOtherCarTouchingGarage(nil)) { + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = true; + } + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (FindPlayerVehicle() == m_pTarget && m_pTarget && + CalcDistToGarageRectangleSquared( + FindPlayerVehicle()->GetPosition().x, + FindPlayerVehicle()->GetPosition().y + ) < SQR(DISTANCE_TO_ACTIVATE_GARAGE)) + m_eGarageState = GS_OPENING; + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + //case GARAGE_COLLECTORSITEMS: + //case GARAGE_60SECONDS: + default: + break; + } +} + +bool CGarage::IsStaticPlayerCarEntirelyInside() +{ + if (!FindPlayerVehicle()) + return false; + if (!FindPlayerVehicle()->IsCar()) + return false; + if (FindPlayerPed()->GetPedState() != PED_DRIVING) + return false; + if (FindPlayerPed()->m_objective == OBJECTIVE_LEAVE_CAR) + return false; + CVehicle* pVehicle = FindPlayerVehicle(); + if (pVehicle->GetPosition().x < m_fX1 || pVehicle->GetPosition().x > m_fX2 || + pVehicle->GetPosition().y < m_fY1 || pVehicle->GetPosition().y > m_fY2) + return false; + if (Abs(pVehicle->GetSpeed().x) > 0.01f || + Abs(pVehicle->GetSpeed().y) > 0.01f || + Abs(pVehicle->GetSpeed().z) > 0.01f) + return false; + if (pVehicle->GetSpeed().MagnitudeSqr() > SQR(0.01f)) + return false; + return IsEntityEntirelyInside3D(pVehicle, 0.0f); +} + +bool CGarage::IsEntityEntirelyInside(CEntity * pEntity) +{ + if (pEntity->GetPosition().x < m_fX1 || pEntity->GetPosition().x > m_fX2 || + pEntity->GetPosition().y < m_fY1 || pEntity->GetPosition().y > m_fY2) + return false; + CColModel* pColModel = pEntity->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pEntity->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x - radius < m_fX1 || pos.x + radius > m_fX2 || + pos.y - radius < m_fY1 || pos.y + radius > m_fY2) + return false; + } + return true; +} + +bool CGarage::IsEntityEntirelyInside3D(CEntity * pEntity, float fMargin) +{ + if (pEntity->GetPosition().x < m_fX1 - fMargin || pEntity->GetPosition().x > m_fX2 + fMargin || + pEntity->GetPosition().y < m_fY1 - fMargin || pEntity->GetPosition().y > m_fY2 + fMargin || + pEntity->GetPosition().z < m_fZ1 - fMargin || pEntity->GetPosition().z > m_fZ2 + fMargin) + return false; + CColModel* pColModel = pEntity->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pEntity->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius < m_fX1 - fMargin || pos.x - radius > m_fX2 + fMargin || + pos.y + radius < m_fY1 - fMargin || pos.y - radius > m_fY2 + fMargin || + pos.z + radius < m_fZ1 - fMargin || pos.z - radius > m_fZ2 + fMargin) + return false; + } + return true; +} + +bool CGarage::IsEntityEntirelyOutside(CEntity * pEntity, float fMargin) +{ + if (pEntity->GetPosition().x > m_fX1 - fMargin && pEntity->GetPosition().x < m_fX2 + fMargin && + pEntity->GetPosition().y > m_fY1 - fMargin && pEntity->GetPosition().y < m_fY2 + fMargin) + return false; + CColModel* pColModel = pEntity->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pEntity->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius > m_fX1 - fMargin && pos.x - radius < m_fX2 + fMargin && + pos.y + radius > m_fY1 - fMargin && pos.y - radius < m_fY2 + fMargin) + return false; + } + return true; +} + +bool CGarage::IsGarageEmpty() +{ + int16 num; + CWorld::FindObjectsIntersectingCube(CVector(m_fX1, m_fY1, m_fZ1), CVector(m_fX2, m_fY2, m_fZ2), &num, 2, nil, false, true, true, false, false); + return num == 0; +} + +bool CGarage::IsPlayerOutsideGarage() +{ + if (FindPlayerVehicle()) + return IsEntityEntirelyOutside(FindPlayerVehicle(), 0.0f); + return IsEntityEntirelyOutside(FindPlayerPed(), 0.0f); +} + +bool CGarage::IsEntityTouching3D(CEntity * pEntity) +{ + float radius = pEntity->GetBoundRadius(); + if (m_fX1 - radius > pEntity->GetPosition().x || m_fX2 + radius < pEntity->GetPosition().x || + m_fY1 - radius > pEntity->GetPosition().y || m_fY2 + radius < pEntity->GetPosition().y || + m_fZ1 - radius > pEntity->GetPosition().z || m_fZ2 + radius < pEntity->GetPosition().z) + return false; + CColModel* pColModel = pEntity->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pEntity->GetMatrix() * pColModel->spheres[i].center; + radius = pColModel->spheres[i].radius; + if (pos.x + radius > m_fX1 && pos.x - radius < m_fX2 && + pos.y + radius > m_fY1 && pos.y - radius < m_fY2 && + pos.z + radius > m_fZ1 && pos.z - radius < m_fZ2) + return true; + } + return false; +} + +bool CGarage::EntityHasASphereWayOutsideGarage(CEntity * pEntity, float fMargin) +{ + CColModel* pColModel = pEntity->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pEntity->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius + fMargin < m_fX1 || pos.x - radius - fMargin > m_fX2 || + pos.y + radius + fMargin < m_fY1 || pos.y - radius - fMargin > m_fY2 || + pos.z + radius + fMargin < m_fZ1 || pos.z - radius - fMargin > m_fZ2) + return true; + } + return false; +} + +bool CGarage::IsAnyOtherCarTouchingGarage(CVehicle * pException) +{ + uint32 i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle || pVehicle == pException) + continue; + if (!IsEntityTouching3D(pVehicle)) + continue; + CColModel* pColModel = pVehicle->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pVehicle->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius > m_fX1 && pos.x - radius < m_fX2 && + pos.y + radius > m_fY1 && pos.y - radius < m_fY2 && + pos.z + radius > m_fZ1 && pos.z - radius < m_fZ2) + return true; + } + } + return false; +} + +bool CGarage::IsAnyOtherPedTouchingGarage(CPed * pException) +{ + uint32 i = CPools::GetPedPool()->GetSize(); + while (i--) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed || pPed == pException) + continue; + if (!IsEntityTouching3D(pPed)) + continue; + CColModel* pColModel = pException->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pPed->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius > m_fX1 && pos.x - radius < m_fX2 && + pos.y + radius > m_fY1 && pos.y - radius < m_fY2 && + pos.z + radius > m_fZ1 && pos.z - radius < m_fZ2) + return true; + } + } + return false; +} + +bool CGarage::IsAnyCarBlockingDoor() +{ + uint32 i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (!IsEntityTouching3D(pVehicle)) + continue; + CColModel* pColModel = pVehicle->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pVehicle->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius < m_fX1 || pos.x - radius > m_fX2 || + pos.y + radius < m_fY1 || pos.y - radius > m_fY2 || + pos.z + radius < m_fZ1 || pos.z - radius > m_fZ2) + return true; + } + } + return false; +} + +int32 CGarage::CountCarsWithCenterPointWithinGarage(CEntity * pException) +{ + int32 total = 0; + uint32 i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle || pVehicle == pException) + continue; + if (pVehicle->GetPosition().x > m_fX1 && pVehicle->GetPosition().x < m_fX2 && + pVehicle->GetPosition().y > m_fY1 && pVehicle->GetPosition().y < m_fY2 && + pVehicle->GetPosition().z > m_fZ1 && pVehicle->GetPosition().z < m_fZ2) + total++; + } + return total; +} + +void CGarage::RemoveCarsBlockingDoorNotInside() +{ + uint32 i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (!IsEntityTouching3D(pVehicle)) + continue; + if (pVehicle->GetPosition().x < m_fX1 || pVehicle->GetPosition().x > m_fX2 || + pVehicle->GetPosition().y < m_fY1 || pVehicle->GetPosition().y > m_fY2 || + pVehicle->GetPosition().z < m_fZ1 || pVehicle->GetPosition().z > m_fZ2) { + if (!pVehicle->bIsLocked && pVehicle->CanBeDeleted()) { + CWorld::Remove(pVehicle); + delete pVehicle; +#ifndef FIX_BUGS + return; // makes no sense +#endif + } + } + } +} + +void CGarages::PrintMessages() +{ + if (CTimer::GetTimeInMilliseconds() > MessageStartTime && CTimer::GetTimeInMilliseconds() < MessageEndTime) { +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.5f)); +#else + CFont::SetScale(1.2f, 1.5f); +#endif + CFont::SetPropOn(); + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 50)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 50); +#endif + CFont::SetCentreOn(); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + +#if defined(PS2_HUD) || defined (FIX_BUGS) + float y_offset = SCREEN_HEIGHT / 3; // THIS is PS2 calculation +#else + float y_offset = SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(84.0f); // This is PC and results in text being written over some HUD elements +#endif + + if (MessageNumberInString2 >= 0) { + CMessages::InsertNumberInString(TheText.Get(MessageIDString), MessageNumberInString, MessageNumberInString2, -1, -1, -1, -1, gUString); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X(2.0f), y_offset - SCREEN_SCALE_Y(40.0f) + SCREEN_SCALE_Y(2.0f), gUString); +#else + CFont::PrintString(SCREEN_WIDTH / 2 + 2.0f, y_offset - 40.0f + 2.0f, gUString); +#endif + CFont::SetColor(CRGBA(89, 115, 150, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2, y_offset - SCREEN_SCALE_Y(40.0f), gUString); +#else + CFont::PrintString(SCREEN_WIDTH / 2, y_offset - 40.0f, gUString); +#endif + } + else if (MessageNumberInString >= 0) { + CMessages::InsertNumberInString(TheText.Get(MessageIDString), MessageNumberInString, -1, -1, -1, -1, -1, gUString); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X(2.0f), y_offset - SCREEN_SCALE_Y(40.0f) + SCREEN_SCALE_Y(2.0f), gUString); +#else + CFont::PrintString(SCREEN_WIDTH / 2 + 2.0f, y_offset - 40.0f + 2.0f, gUString); +#endif + + CFont::SetColor(CRGBA(89, 115, 150, 255)); + +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2, y_offset - SCREEN_SCALE_Y(40.0f), gUString); +#else + CFont::PrintString(SCREEN_WIDTH / 2, y_offset - 40.0f, gUString); +#endif + } + else { +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2 - SCREEN_SCALE_X(2.0f), y_offset - SCREEN_SCALE_Y(2.0f), TheText.Get(MessageIDString)); +#else + CFont::PrintString(SCREEN_WIDTH / 2 - 2.0f, y_offset - 2.0f, TheText.Get(MessageIDString)); +#endif + CFont::SetColor(CRGBA(89, 115, 150, 255)); + CFont::PrintString(SCREEN_WIDTH / 2, y_offset, TheText.Get(MessageIDString)); + } + } +} + +bool CGarages::IsCarSprayable(CVehicle * pVehicle) +{ + switch (pVehicle->GetModelIndex()) { + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_POLICE: + case MI_ENFORCER: + case MI_BUS: + case MI_RHINO: + case MI_BARRACKS: + case MI_DODO: + case MI_COACH: + return false; + default: + break; + } + return true; +} + +void CGarage::UpdateDoorsHeight() +{ + RefreshDoorPointers(false); + if (m_pDoor1) { + m_pDoor1->GetMatrix().GetPosition().z = m_fDoorPos + m_fDoor1Z; + if (m_bRotatedDoor) + BuildRotatedDoorMatrix(m_pDoor1, m_fDoorPos / m_fDoorHeight); + m_pDoor1->GetMatrix().UpdateRW(); + m_pDoor1->UpdateRwFrame(); + } + if (m_pDoor2) { + m_pDoor2->GetMatrix().GetPosition().z = m_fDoorPos + m_fDoor2Z; + if (m_bRotatedDoor) + BuildRotatedDoorMatrix(m_pDoor2, m_fDoorPos / m_fDoorHeight); + m_pDoor2->GetMatrix().UpdateRW(); + m_pDoor2->UpdateRwFrame(); + } +} + +void CGarage::BuildRotatedDoorMatrix(CEntity * pDoor, float fPosition) +{ + float fAngle = -fPosition * HALFPI; + CVector up(-Sin(fAngle) * pDoor->GetForward().y, Sin(fAngle) * pDoor->GetForward().x, Cos(fAngle)); + pDoor->GetRight() = CrossProduct(up, pDoor->GetForward()); + pDoor->GetUp() = up; +} + +void CGarage::UpdateCrusherAngle() +{ + RefreshDoorPointers(false); + m_pDoor2->GetMatrix().SetRotateXOnly(TWOPI - m_fDoorPos); + m_pDoor2->GetMatrix().UpdateRW(); + m_pDoor2->UpdateRwFrame(); +} + +void CGarage::UpdateCrusherShake(float X, float Y) +{ + RefreshDoorPointers(false); + m_pDoor1->GetMatrix().GetPosition().x += X; + m_pDoor1->GetMatrix().GetPosition().y += Y; + m_pDoor1->GetMatrix().UpdateRW(); + m_pDoor1->UpdateRwFrame(); + m_pDoor1->GetMatrix().GetPosition().x -= X; + m_pDoor1->GetMatrix().GetPosition().y -= Y; + m_pDoor2->GetMatrix().GetPosition().x += X; + m_pDoor2->GetMatrix().GetPosition().y += Y; + m_pDoor2->GetMatrix().UpdateRW(); + m_pDoor2->UpdateRwFrame(); + m_pDoor2->GetMatrix().GetPosition().x -= X; + m_pDoor2->GetMatrix().GetPosition().y -= Y; +} + +void CGarage::RefreshDoorPointers(bool bCreate) +{ + bool bNeedToFindDoorEntities = bCreate || m_bRecreateDoorOnNextRefresh; + m_bRecreateDoorOnNextRefresh = false; + if (m_pDoor1) { + if (m_bDoor1IsDummy) { + if (CPools::GetDummyPool()->GetIsFree(CPools::GetDummyPool()->GetJustIndex_NoFreeAssert((CDummy*)m_pDoor1))) + bNeedToFindDoorEntities = true; + else { + if (m_bDoor1PoolIndex != (CPools::GetDummyPool()->GetIndex((CDummy*)m_pDoor1) & 0x7F)) + bNeedToFindDoorEntities = true; + if (!CGarages::IsModelIndexADoor(m_pDoor1->GetModelIndex())) + bNeedToFindDoorEntities = true; + } + } + else { + if (CPools::GetObjectPool()->GetIsFree(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert((CObject*)m_pDoor1))) + bNeedToFindDoorEntities = true; + else { + if (m_bDoor1PoolIndex != (CPools::GetObjectPool()->GetIndex((CObject*)m_pDoor1) & 0x7F)) + bNeedToFindDoorEntities = true; + if (!CGarages::IsModelIndexADoor(m_pDoor1->GetModelIndex())) + bNeedToFindDoorEntities = true; + } + } + } + if (m_pDoor2) { + if (m_bDoor2IsDummy) { + if (CPools::GetDummyPool()->GetIsFree(CPools::GetDummyPool()->GetJustIndex_NoFreeAssert((CDummy*)m_pDoor2))) + bNeedToFindDoorEntities = true; + else { + if (m_bDoor2PoolIndex != (CPools::GetDummyPool()->GetIndex((CDummy*)m_pDoor2) & 0x7F)) + bNeedToFindDoorEntities = true; + if (!CGarages::IsModelIndexADoor(m_pDoor2->GetModelIndex())) + bNeedToFindDoorEntities = true; + } + } + else { + if (CPools::GetObjectPool()->GetIsFree(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert((CObject*)m_pDoor2))) + bNeedToFindDoorEntities = true; + else { + if (m_bDoor2PoolIndex != (CPools::GetObjectPool()->GetIndex((CObject*)m_pDoor2) & 0x7F)) + bNeedToFindDoorEntities = true; + if (!CGarages::IsModelIndexADoor(m_pDoor2->GetModelIndex())) + bNeedToFindDoorEntities = true; + } + } + } + if (bNeedToFindDoorEntities) + FindDoorsEntities(); +} + +void CGarages::TriggerMessage(const char* text, int16 num1, uint16 time, int16 num2) +{ + if (strcmp(text, MessageIDString) == 0 && + CTimer::GetTimeInMilliseconds() >= MessageStartTime && + CTimer::GetTimeInMilliseconds() <= MessageEndTime) { + if (CTimer::GetTimeInMilliseconds() - MessageStartTime <= 500) + return; + MessageStartTime = CTimer::GetTimeInMilliseconds() - 500; + MessageEndTime = CTimer::GetTimeInMilliseconds() - 500 + time; + } + else { + strcpy(MessageIDString, text); + MessageStartTime = CTimer::GetTimeInMilliseconds(); + MessageEndTime = CTimer::GetTimeInMilliseconds() + time; + } + MessageNumberInString = num1; + MessageNumberInString2 = num2; +} + +void CGarages::SetTargetCarForMissonGarage(int16 garage, CVehicle * pVehicle) +{ + assert(garage >= 0 && garage < NUM_GARAGES); + if (pVehicle) { + aGarages[garage].m_pTarget = pVehicle; + if (aGarages[garage].m_eGarageState == GS_CLOSEDCONTAINSCAR) + aGarages[garage].m_eGarageState = GS_FULLYCLOSED; + } + else + aGarages[garage].m_pTarget = nil; +} + +bool CGarages::HasCarBeenDroppedOffYet(int16 garage) +{ + return aGarages[garage].m_eGarageState == GS_CLOSEDCONTAINSCAR; +} + +void CGarages::DeActivateGarage(int16 garage) +{ + aGarages[garage].m_bDeactivated = true; +} + +void CGarages::ActivateGarage(int16 garage) +{ + aGarages[garage].m_bDeactivated = false; + if (aGarages[garage].m_eGarageType == GARAGE_FORCARTOCOMEOUTOF && aGarages[garage].m_eGarageState == GS_FULLYCLOSED) + aGarages[garage].m_eGarageState = GS_OPENING; +} + +int32 CGarages::QueryCarsCollected(int16 garage) +{ + return 0; +} + +bool CGarages::HasImportExportGarageCollectedThisCar(int16 garage, int8 car) +{ + return CarTypesCollected[GetCarsCollectedIndexForGarageType(aGarages[garage].m_eGarageType)] & (BIT(car)); +} + +bool CGarages::IsGarageOpen(int16 garage) +{ + return aGarages[garage].IsOpen(); +} + +bool CGarages::IsGarageClosed(int16 garage) +{ + return aGarages[garage].IsClosed(); +} + +bool CGarages::HasThisCarBeenCollected(int16 garage, uint8 id) +{ + return aGarages[garage].m_bCollectedCarsState & BIT(id); +} + +bool CGarage::DoesCraigNeedThisCar(int32 mi) +{ + if (mi == MI_CORPSE) + mi = MI_MANANA; + int ct = CGarages::GetCarsCollectedIndexForGarageType(m_eGarageType); + for (int i = 0; i < TOTAL_COLLECTCARS_CARS; i++) { + if (mi == gaCarsToCollectInCraigsGarages[ct][i]) + return (CGarages::CarTypesCollected[ct] & BIT(i)) == 0; + } + return false; +} + +bool CGarage::HasCraigCollectedThisCar(int32 mi) +{ + if (mi == MI_CORPSE) + mi = MI_MANANA; + int ct = CGarages::GetCarsCollectedIndexForGarageType(m_eGarageType); + for (int i = 0; i < TOTAL_COLLECTCARS_CARS; i++) { + if (mi == gaCarsToCollectInCraigsGarages[ct][i]) + return CGarages::CarTypesCollected[ct] & BIT(i); + } + return false; +} + +bool CGarage::MarkThisCarAsCollectedForCraig(int32 mi) +{ + if (mi == MI_CORPSE) + mi = MI_MANANA; + int ct = CGarages::GetCarsCollectedIndexForGarageType(m_eGarageType); + int index; + for (index = 0; index < TOTAL_COLLECTCARS_CARS; index++) { + if (mi == gaCarsToCollectInCraigsGarages[ct][index]) + break; + } + if (index >= TOTAL_COLLECTCARS_CARS) + return false; + CGarages::CarTypesCollected[ct] |= BIT(index); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += IMPORT_REWARD; + for (int i = 0; i < TOTAL_COLLECTCARS_CARS; i++) { + if ((CGarages::CarTypesCollected[ct] & BIT(i)) == 0) { + CGarages::TriggerMessage("GA_13", -1, 5000, -1); // Delivered like a pro. Complete the list and there'll be a bonus for you. + return false; + } + } + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += IMPORT_ALLCARS_REWARD; + CGarages::TriggerMessage("GA_14", -1, 5000, -1); // All the cars. NICE! Here's a little something. + return true; +} + +void CGarage::OpenThisGarage() +{ + if (m_eGarageState == GS_FULLYCLOSED || m_eGarageState == GS_CLOSING || m_eGarageState == GS_CLOSEDCONTAINSCAR) + m_eGarageState = GS_OPENING; +} + +void CGarage::CloseThisGarage() +{ + if (m_eGarageState == GS_OPENED || m_eGarageState == GS_OPENING) + m_eGarageState = GS_CLOSING; +} + +float CGarage::CalcDistToGarageRectangleSquared(float X, float Y) +{ + float distX, distY; + if (X < m_fX1) + distX = m_fX1 - X; + else if (X > m_fX2) + distX = X - m_fX2; + else + distX = 0.0f; + if (Y < m_fY1) + distY = m_fY1 - Y; + else if (Y > m_fY2) + distY = Y - m_fY2; + else + distY = 0.0f; + return SQR(distX) + SQR(distY); +} + +float CGarage::CalcSmallestDistToGarageDoorSquared(float X, float Y) +{ + float dist1 = 10000000.0f; + float dist2 = 10000000.0f; + if (m_pDoor1) + dist1 = SQR(m_fDoor1X - X) + SQR(m_fDoor1Y - Y); + if (m_pDoor2) + dist2 = SQR(m_fDoor2X - X) + SQR(m_fDoor2Y - Y); + return Min(dist1, dist2); +} + +void CGarage::FindDoorsEntities() +{ + m_pDoor1 = nil; + m_pDoor2 = nil; + int xstart = Max(0, CWorld::GetSectorIndexX(m_fX1)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(m_fX2)); + int ystart = Max(0, CWorld::GetSectorIndexY(m_fY1)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(m_fY2)); + assert(xstart <= xend); + assert(ystart <= yend); + + CWorld::AdvanceCurrentScanCode(); + + for (int y = ystart; y <= yend; y++) { + for (int x = xstart; x <= xend; x++) { + CSector* s = CWorld::GetSector(x, y); + FindDoorsEntitiesSectorList(s->m_lists[ENTITYLIST_OBJECTS], false); + FindDoorsEntitiesSectorList(s->m_lists[ENTITYLIST_OBJECTS_OVERLAP], false); + FindDoorsEntitiesSectorList(s->m_lists[ENTITYLIST_DUMMIES], true); + FindDoorsEntitiesSectorList(s->m_lists[ENTITYLIST_DUMMIES_OVERLAP], true); + } + } + if (!m_pDoor1 || !m_pDoor2) + return; + if (m_pDoor1->GetModelIndex() == MI_CRUSHERBODY || m_pDoor1->GetModelIndex() == MI_CRUSHERLID) + return; + CVector2D vecDoor1ToGarage(m_pDoor1->GetPosition().x - GetGarageCenterX(), m_pDoor1->GetPosition().y - GetGarageCenterY()); + CVector2D vecDoor2ToGarage(m_pDoor2->GetPosition().x - GetGarageCenterX(), m_pDoor2->GetPosition().y - GetGarageCenterY()); + if (DotProduct2D(vecDoor1ToGarage, vecDoor2ToGarage) > 0.0f) { + if (vecDoor1ToGarage.MagnitudeSqr() >= vecDoor2ToGarage.MagnitudeSqr()) { + m_pDoor1 = m_pDoor2; + m_bDoor1IsDummy = m_bDoor2IsDummy; + } + m_pDoor2 = nil; + m_bDoor2IsDummy = false; + } +} + +void CGarage::FindDoorsEntitiesSectorList(CPtrList& list, bool dummy) +{ + CPtrNode* node; + for (node = list.first; node; node = node->next) { + CEntity* pEntity = (CEntity*)node->item; + if (pEntity->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + pEntity->m_scanCode = CWorld::GetCurrentScanCode(); + if (!pEntity || !CGarages::IsModelIndexADoor(pEntity->GetModelIndex())) + continue; + if (Abs(pEntity->GetPosition().x - GetGarageCenterX()) >= DISTANCE_TO_CONSIDER_DOOR_FOR_GARAGE) + continue; + if (Abs(pEntity->GetPosition().y - GetGarageCenterY()) >= DISTANCE_TO_CONSIDER_DOOR_FOR_GARAGE) + continue; + if (pEntity->GetModelIndex() == MI_CRUSHERBODY) { + m_pDoor1 = pEntity; + m_bDoor1IsDummy = dummy; + // very odd pool operations, they could have used GetJustIndex + if (dummy) + m_bDoor1PoolIndex = (CPools::GetDummyPool()->GetIndex((CDummy*)pEntity)) & 0x7F; + else + m_bDoor1PoolIndex = (CPools::GetObjectPool()->GetIndex((CObject*)pEntity)) & 0x7F; + continue; + } + if (pEntity->GetModelIndex() == MI_CRUSHERLID) { + m_pDoor2 = pEntity; + m_bDoor2IsDummy = dummy; + if (dummy) + m_bDoor2PoolIndex = (CPools::GetDummyPool()->GetIndex((CDummy*)pEntity)) & 0x7F; + else + m_bDoor2PoolIndex = (CPools::GetObjectPool()->GetIndex((CObject*)pEntity)) & 0x7F; + continue; + } + if (!m_pDoor1) { + m_pDoor1 = pEntity; + m_bDoor1IsDummy = dummy; + if (dummy) + m_bDoor1PoolIndex = (CPools::GetDummyPool()->GetIndex((CDummy*)pEntity)) & 0x7F; + else + m_bDoor1PoolIndex = (CPools::GetObjectPool()->GetIndex((CObject*)pEntity)) & 0x7F; + continue; + } + else { + m_pDoor2 = pEntity; + m_bDoor2IsDummy = dummy; + if (dummy) + m_bDoor2PoolIndex = (CPools::GetDummyPool()->GetIndex((CDummy*)pEntity)) & 0x7F; + else + m_bDoor2PoolIndex = (CPools::GetObjectPool()->GetIndex((CObject*)pEntity)) & 0x7F; + } + } +} + +bool CGarages::HasResprayHappened(int16 garage) +{ + bool result = aGarages[garage].m_bResprayHappened; + aGarages[garage].m_bResprayHappened = false; + return result; +} + +void CGarages::SetGarageDoorToRotate(int16 garage) +{ + if (aGarages[garage].m_bRotatedDoor) + return; + aGarages[garage].m_bRotatedDoor = true; + aGarages[garage].m_fDoorHeight /= 2.0f; + aGarages[garage].m_fDoorHeight -= 0.1f; +} + +void CGarages::SetLeaveCameraForThisGarage(int16 garage) +{ + aGarages[garage].m_bCameraFollowsPlayer = true; +} + +bool CGarages::IsThisCarWithinGarageArea(int16 garage, CEntity * pCar) +{ + return aGarages[garage].IsEntityEntirelyInside3D(pCar, 0.0f); +} + +bool CGarages::HasCarBeenCrushed(int32 handle) +{ + return CrushedCarId == handle; +} + +void CStoredCar::StoreCar(CVehicle* pVehicle) +{ + m_nModelIndex = pVehicle->GetModelIndex(); + m_vecPos = pVehicle->GetPosition(); + m_vecAngle = pVehicle->GetForward(); + m_nPrimaryColor = pVehicle->m_currentColour1; + m_nSecondaryColor = pVehicle->m_currentColour2; + m_nRadioStation = pVehicle->m_nRadioStation; + m_nVariationA = pVehicle->m_aExtras[0]; + m_nVariationB = pVehicle->m_aExtras[1]; + m_nFlags = 0; + if (pVehicle->bBulletProof) m_nFlags |= FLAG_BULLETPROOF; + if (pVehicle->bFireProof) m_nFlags |= FLAG_FIREPROOF; + if (pVehicle->bExplosionProof) m_nFlags |= FLAG_EXPLOSIONPROOF; + if (pVehicle->bCollisionProof) m_nFlags |= FLAG_COLLISIONPROOF; + if (pVehicle->bMeleeProof) m_nFlags |= FLAG_MELEEPROOF; + if (pVehicle->IsCar()) + m_nCarBombType = ((CAutomobile*)pVehicle)->m_bombType; +} + +CVehicle* CStoredCar::RestoreCar() +{ + CStreaming::RequestModel(m_nModelIndex, STREAMFLAGS_DEPENDENCY); + if (!CStreaming::HasModelLoaded(m_nModelIndex)) + return nil; +#ifdef FIX_BUGS + CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(m_nModelIndex); + assert(pModelInfo); + if (pModelInfo->m_numComps != 0) +#endif + { + CVehicleModelInfo::SetComponentsToUse(m_nVariationA, m_nVariationB); + } +#ifdef FIX_BUGS + CVehicle* pVehicle; + if (CModelInfo::IsBoatModel(m_nModelIndex)) + pVehicle = new CBoat(m_nModelIndex, RANDOM_VEHICLE); + else + pVehicle = new CAutomobile(m_nModelIndex, RANDOM_VEHICLE); +#else + CVehicle* pVehicle = new CAutomobile(m_nModelIndex, RANDOM_VEHICLE); +#endif + pVehicle->SetPosition(m_vecPos); + pVehicle->SetStatus(STATUS_ABANDONED); + pVehicle->GetForward() = m_vecAngle; + pVehicle->GetRight() = CVector(m_vecAngle.y, -m_vecAngle.x, 0.0f); + pVehicle->GetUp() = CVector(0.0f, 0.0f, 1.0f); + pVehicle->pDriver = nil; + pVehicle->m_currentColour1 = m_nPrimaryColor; + pVehicle->m_currentColour2 = m_nSecondaryColor; + pVehicle->m_nRadioStation = m_nRadioStation; + pVehicle->bFreebies = false; +#ifdef FIX_BUGS + if (pVehicle->IsCar()) +#endif + { + ((CAutomobile*)pVehicle)->m_bombType = m_nCarBombType; +#ifdef FIX_BUGS + if (m_nCarBombType != CARBOMB_NONE) + ((CAutomobile*)pVehicle)->m_pBombRigger = FindPlayerPed(); +#endif + } + pVehicle->bHasBeenOwnedByPlayer = true; + pVehicle->m_nDoorLock = CARLOCK_UNLOCKED; + if (m_nFlags & FLAG_BULLETPROOF) pVehicle->bBulletProof = true; + if (m_nFlags & FLAG_FIREPROOF) pVehicle->bFireProof = true; + if (m_nFlags & FLAG_EXPLOSIONPROOF) pVehicle->bExplosionProof = true; + if (m_nFlags & FLAG_COLLISIONPROOF) pVehicle->bCollisionProof = true; + if (m_nFlags & FLAG_MELEEPROOF) pVehicle->bMeleeProof = true; + return pVehicle; +} + +void CGarage::StoreAndRemoveCarsForThisHideout(CStoredCar* aCars, int32 nMax) +{ + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) + aCars[i].Clear(); + int i = CPools::GetVehiclePool()->GetSize(); + int index = 0; + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (pVehicle->GetPosition().x > m_fX1 && pVehicle->GetPosition().x < m_fX2 && + pVehicle->GetPosition().y > m_fY1 && pVehicle->GetPosition().y < m_fY2 && + pVehicle->GetPosition().z > m_fZ1 && pVehicle->GetPosition().z < m_fZ2) { + if (pVehicle->VehicleCreatedBy != MISSION_VEHICLE) { + if (index < Max(NUM_GARAGE_STORED_CARS, nMax) && !EntityHasASphereWayOutsideGarage(pVehicle, 1.0f)) + aCars[index++].StoreCar(pVehicle); + CWorld::Players[CWorld::PlayerInFocus].CancelPlayerEnteringCars(pVehicle); + CWorld::Remove(pVehicle); + delete pVehicle; + } + } + } + // why? + for (i = index; i < NUM_GARAGE_STORED_CARS; i++) + aCars[i].Clear(); +} + +bool CGarage::RestoreCarsForThisHideout(CStoredCar* aCars) +{ + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) { + if (aCars[i].HasCar()) { + CVehicle* pVehicle = aCars[i].RestoreCar(); + if (pVehicle) { + CWorld::Add(pVehicle); + aCars[i].Clear(); + } + } + } + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) { + if (aCars[i].HasCar()) + return false; + } + return true; +} + +bool CGarages::IsPointInAGarageCameraZone(CVector point) +{ + for (int i = 0; i < NUM_GARAGES; i++) { + switch (aGarages[i].m_eGarageType) { + case GARAGE_NONE: + break; + case GARAGE_COLLECTCARS_1: + case GARAGE_COLLECTCARS_2: + case GARAGE_COLLECTCARS_3: + if (aGarages[i].m_fX1 - MARGIN_FOR_CAMERA_COLLECTCARS <= point.x && + aGarages[i].m_fX2 + MARGIN_FOR_CAMERA_COLLECTCARS >= point.x && + aGarages[i].m_fY1 - MARGIN_FOR_CAMERA_COLLECTCARS <= point.y && + aGarages[i].m_fY2 + MARGIN_FOR_CAMERA_COLLECTCARS >= point.y) + return true; + break; + default: + if (aGarages[i].m_fX1 - MARGIN_FOR_CAMERA_DEFAULT <= point.x && + aGarages[i].m_fX2 + MARGIN_FOR_CAMERA_DEFAULT >= point.x && + aGarages[i].m_fY1 - MARGIN_FOR_CAMERA_DEFAULT <= point.y && + aGarages[i].m_fY2 + MARGIN_FOR_CAMERA_DEFAULT >= point.y) + return true; + break; + } + } + return false; +} + +bool CGarages::CameraShouldBeOutside() +{ + return bCamShouldBeOutisde; +} + +void CGarages::GivePlayerDetonator() +{ + FindPlayerPed()->GiveWeapon(WEAPONTYPE_DETONATOR, 1); + FindPlayerPed()->GetWeapon(FindPlayerPed()->GetWeaponSlot(WEAPONTYPE_DETONATOR)).m_eWeaponState = WEAPONSTATE_READY; +} + +float CGarages::FindDoorHeightForMI(int32 mi) +{ + return CModelInfo::GetColModel(mi)->boundingBox.max.z - CModelInfo::GetColModel(mi)->boundingBox.min.z - 0.1f; +} + +void CGarage::TidyUpGarage() +{ + uint32 i = CPools::GetVehiclePool()->GetSize(); +#ifdef FIX_BUGS + while (i--) { +#else + while (--i) { +#endif + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle || !pVehicle->IsCar()) + continue; + if (pVehicle->GetPosition().x > m_fX1 && pVehicle->GetPosition().x < m_fX2 && + pVehicle->GetPosition().y > m_fY1 && pVehicle->GetPosition().y < m_fY2 && + pVehicle->GetPosition().z > m_fZ1 && pVehicle->GetPosition().z < m_fZ2) { + if (pVehicle->GetStatus() == STATUS_WRECKED || pVehicle->GetUp().z < 0.5f) { + CWorld::Remove(pVehicle); + delete pVehicle; + } + } + } +} + +void CGarage::TidyUpGarageClose() +{ + uint32 i = CPools::GetVehiclePool()->GetSize(); +#ifdef FIX_BUGS + while (i--) { +#else + while (--i) { +#endif + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle || !pVehicle->IsCar()) + continue; + if (!pVehicle->IsCar() || pVehicle->GetStatus() != STATUS_WRECKED || !IsEntityTouching3D(pVehicle)) + continue; + bool bRemove = false; + if (m_eGarageState != GS_FULLYCLOSED) { + CColModel* pColModel = pVehicle->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pVehicle->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius < m_fX1 || pos.x - radius > m_fX2 || + pos.y + radius < m_fY1 || pos.y - radius > m_fY2 || + pos.z + radius < m_fZ1 || pos.z - radius > m_fZ2) { + bRemove = true; + } + } + } + else + bRemove = true; + if (bRemove) { + // no MISSION_VEHICLE check??? + CWorld::Remove(pVehicle); + delete pVehicle; + } + } +} + +void CGarages::PlayerArrestedOrDied() +{ + static int GarageToBeTidied = 0; // lol + for (int i = 0; i < NUM_GARAGES; i++) { + if (aGarages[i].m_eGarageType != GARAGE_NONE) + aGarages[i].PlayerArrestedOrDied(); + } + MessageEndTime = 0; + MessageStartTime = 0; +} + +void CGarage::PlayerArrestedOrDied() +{ + switch (m_eGarageType) { + case GARAGE_MISSION: + case GARAGE_COLLECTORSITEMS: + case GARAGE_COLLECTSPECIFICCARS: + case GARAGE_COLLECTCARS_1: + case GARAGE_COLLECTCARS_2: + case GARAGE_COLLECTCARS_3: + case GARAGE_FORCARTOCOMEOUTOF: + case GARAGE_60SECONDS: + case GARAGE_MISSION_KEEPCAR: + case GARAGE_FOR_SCRIPT_TO_OPEN: + case GARAGE_HIDEOUT_ONE: + case GARAGE_HIDEOUT_TWO: + case GARAGE_HIDEOUT_THREE: + case GARAGE_FOR_SCRIPT_TO_OPEN_AND_CLOSE: + case GARAGE_KEEPS_OPENING_FOR_SPECIFIC_CAR: + case GARAGE_MISSION_KEEPCAR_REMAINCLOSED: + switch (m_eGarageState) { + case GS_OPENED: + case GS_CLOSING: + case GS_OPENING: + m_eGarageState = GS_CLOSING; + break; + default: + break; + } + break; + case GARAGE_BOMBSHOP1: + case GARAGE_BOMBSHOP2: + case GARAGE_BOMBSHOP3: + case GARAGE_RESPRAY: + case GARAGE_CRUSHER: + switch (m_eGarageState) { + case GS_FULLYCLOSED: + case GS_CLOSING: + case GS_OPENING: + m_eGarageState = GS_OPENING; + break; + default: + break; + } + break; + default: + break; + } +} + +void CGarage::CenterCarInGarage(CVehicle* pVehicle) +{ + if (IsAnyOtherCarTouchingGarage(FindPlayerVehicle())) + return; + if (IsAnyOtherPedTouchingGarage(FindPlayerPed())) + return; + CVector pos = pVehicle->GetPosition(); + float garageX = GetGarageCenterX(); + float garageY = GetGarageCenterY(); + float offsetX = garageX - pos.x; + float offsetY = garageY - pos.y; + float offsetZ = pos.z - pos.z; + float distance = CVector(offsetX, offsetY, offsetZ).Magnitude(); + if (distance < RESPRAY_CENTERING_COEFFICIENT) { + pVehicle->GetMatrix().GetPosition().x = GetGarageCenterX(); + pVehicle->GetMatrix().GetPosition().y = GetGarageCenterY(); + } + else { + pVehicle->GetMatrix().GetPosition().x += offsetX * RESPRAY_CENTERING_COEFFICIENT / distance; + pVehicle->GetMatrix().GetPosition().y += offsetY * RESPRAY_CENTERING_COEFFICIENT / distance; + } + if (!IsEntityEntirelyInside3D(pVehicle, 0.1f)) + pVehicle->SetPosition(pos); +} + +void CGarages::CloseHideOutGaragesBeforeSave() +{ + for (int i = 0; i < NUM_GARAGES; i++) { + if (aGarages[i].m_eGarageType != GARAGE_HIDEOUT_ONE && + aGarages[i].m_eGarageType != GARAGE_HIDEOUT_TWO && + aGarages[i].m_eGarageType != GARAGE_HIDEOUT_THREE) + continue; + if (aGarages[i].m_eGarageState != GS_FULLYCLOSED && + (aGarages[i].m_eGarageType != GARAGE_HIDEOUT_ONE || !aGarages[i].IsAnyCarBlockingDoor())) { + aGarages[i].m_eGarageState = GS_FULLYCLOSED; + switch (aGarages[i].m_eGarageType) { + case GARAGE_HIDEOUT_ONE: + aGarages[i].StoreAndRemoveCarsForThisHideout(aCarsInSafeHouse1, NUM_GARAGE_STORED_CARS); + aGarages[i].RemoveCarsBlockingDoorNotInside(); + break; + case GARAGE_HIDEOUT_TWO: + aGarages[i].StoreAndRemoveCarsForThisHideout(aCarsInSafeHouse2, NUM_GARAGE_STORED_CARS); + aGarages[i].RemoveCarsBlockingDoorNotInside(); + break; + case GARAGE_HIDEOUT_THREE: + aGarages[i].StoreAndRemoveCarsForThisHideout(aCarsInSafeHouse3, NUM_GARAGE_STORED_CARS); + aGarages[i].RemoveCarsBlockingDoorNotInside(); + break; + default: + break; + } + } + aGarages[i].m_fDoorPos = 0.0f; + aGarages[i].UpdateDoorsHeight(); + } +} + +int32 CGarages::CountCarsInHideoutGarage(uint8 type) +{ + int32 total = 0; + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) { + switch (type) { + case GARAGE_HIDEOUT_ONE: + total += (aCarsInSafeHouse1[i].HasCar()); + break; + case GARAGE_HIDEOUT_TWO: + total += (aCarsInSafeHouse2[i].HasCar()); + break; + case GARAGE_HIDEOUT_THREE: + total += (aCarsInSafeHouse3[i].HasCar()); + break; + default: break; + } + } + return total; +} + +int32 CGarages::FindMaxNumStoredCarsForGarage(uint8 type) +{ + switch (type) { + case GARAGE_HIDEOUT_ONE: + return LIMIT_CARS_IN_INDUSTRIAL; + case GARAGE_HIDEOUT_TWO: + return LIMIT_CARS_IN_COMMERCIAL; + case GARAGE_HIDEOUT_THREE: + return LIMIT_CARS_IN_SUBURBAN; + default: break; + } + return 0; +} + +bool CGarages::IsPointWithinHideOutGarage(Const CVector& point) +{ + for (int i = 0; i < NUM_GARAGES; i++) { + switch (aGarages[i].m_eGarageType) { + case GARAGE_HIDEOUT_ONE: + case GARAGE_HIDEOUT_TWO: + case GARAGE_HIDEOUT_THREE: + if (point.x > aGarages[i].m_fX1 && point.x < aGarages[i].m_fX2 && + point.y > aGarages[i].m_fY1 && point.y < aGarages[i].m_fY2 && + point.z > aGarages[i].m_fZ1 && point.z < aGarages[i].m_fZ2) + return true; + default: break; + } + } + return false; +} + +bool CGarages::IsPointWithinAnyGarage(Const CVector& point) +{ + for (int i = 0; i < NUM_GARAGES; i++) { + switch (aGarages[i].m_eGarageType) { + case GARAGE_NONE: + continue; + default: + if (point.x > aGarages[i].m_fX1 && point.x < aGarages[i].m_fX2 && + point.y > aGarages[i].m_fY1 && point.y < aGarages[i].m_fY2 && + point.z > aGarages[i].m_fZ1 && point.z < aGarages[i].m_fZ2) + return true; + } + } + return false; +} + +void CGarages::SetAllDoorsBackToOriginalHeight() +{ + for (int i = 0; i < NUM_GARAGES; i++) { + switch (aGarages[i].m_eGarageType) { + case GARAGE_NONE: + continue; + default: + aGarages[i].RefreshDoorPointers(true); + if (aGarages[i].m_pDoor1) { + aGarages[i].m_pDoor1->GetMatrix().GetPosition().z = aGarages[i].m_fDoor1Z; + if (aGarages[i].m_pDoor1->IsObject()) + ((CObject*)aGarages[i].m_pDoor1)->m_objectMatrix.GetPosition().z = aGarages[i].m_fDoor1Z; + if (aGarages[i].m_bRotatedDoor) + aGarages[i].BuildRotatedDoorMatrix(aGarages[i].m_pDoor1, 0.0f); + aGarages[i].m_pDoor1->GetMatrix().UpdateRW(); + aGarages[i].m_pDoor1->UpdateRwFrame(); + } + if (aGarages[i].m_pDoor2) { + aGarages[i].m_pDoor2->GetMatrix().GetPosition().z = aGarages[i].m_fDoor2Z; + if (aGarages[i].m_pDoor2->IsObject()) + ((CObject*)aGarages[i].m_pDoor2)->m_objectMatrix.GetPosition().z = aGarages[i].m_fDoor2Z; + if (aGarages[i].m_bRotatedDoor) + aGarages[i].BuildRotatedDoorMatrix(aGarages[i].m_pDoor2, 0.0f); + aGarages[i].m_pDoor2->GetMatrix().UpdateRW(); + aGarages[i].m_pDoor2->UpdateRwFrame(); + } + } + } +} + +void CGarages::Save(uint8 * buf, uint32 * size) +{ +#ifdef FIX_GARAGE_SIZE + INITSAVEBUF + *size = (6 * sizeof(uint32) + TOTAL_COLLECTCARS_GARAGES * sizeof(*CarTypesCollected) + sizeof(uint32) + 3 * NUM_GARAGE_STORED_CARS * sizeof(CStoredCar) + NUM_GARAGES * sizeof(CGarage)); +#else + * size = 5484; +#endif +#if !defined THIS_IS_STUPID && !defined FIX_GARAGE_SIZE && defined COMPATIBLE_SAVES + memset(buf + 5240, 0, *size - 5240); // garbage data is written otherwise +#endif + CloseHideOutGaragesBeforeSave(); + WriteSaveBuf(buf, NumGarages); + WriteSaveBuf(buf, (uint32)BombsAreFree); + WriteSaveBuf(buf, (uint32)RespraysAreFree); + WriteSaveBuf(buf, CarsCollected); + WriteSaveBuf(buf, BankVansCollected); + WriteSaveBuf(buf, PoliceCarsCollected); + for (int i = 0; i < TOTAL_COLLECTCARS_GARAGES; i++) + WriteSaveBuf(buf, CarTypesCollected[i]); + WriteSaveBuf(buf, LastTimeHelpMessage); + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) { + WriteSaveBuf(buf, aCarsInSafeHouse1[i]); + WriteSaveBuf(buf, aCarsInSafeHouse2[i]); + WriteSaveBuf(buf, aCarsInSafeHouse3[i]); + } + for (int i = 0; i < NUM_GARAGES; i++) { +#ifdef COMPATIBLE_SAVES + WriteSaveBuf(buf, aGarages[i].m_eGarageType); + WriteSaveBuf(buf, aGarages[i].m_eGarageState); + WriteSaveBuf(buf, aGarages[i].field_2); + WriteSaveBuf(buf, aGarages[i].m_bClosingWithoutTargetCar); + WriteSaveBuf(buf, aGarages[i].m_bDeactivated); + WriteSaveBuf(buf, aGarages[i].m_bResprayHappened); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, aGarages[i].m_nTargetModelIndex); + ZeroSaveBuf(buf, 4 + 4); + WriteSaveBuf(buf, aGarages[i].m_bDoor1PoolIndex); + WriteSaveBuf(buf, aGarages[i].m_bDoor2PoolIndex); + WriteSaveBuf(buf, aGarages[i].m_bDoor1IsDummy); + WriteSaveBuf(buf, aGarages[i].m_bDoor2IsDummy); + WriteSaveBuf(buf, aGarages[i].m_bRecreateDoorOnNextRefresh); + WriteSaveBuf(buf, aGarages[i].m_bRotatedDoor); + WriteSaveBuf(buf, aGarages[i].m_bCameraFollowsPlayer); + ZeroSaveBuf(buf, 1); + WriteSaveBuf(buf, aGarages[i].m_fX1); + WriteSaveBuf(buf, aGarages[i].m_fX2); + WriteSaveBuf(buf, aGarages[i].m_fY1); + WriteSaveBuf(buf, aGarages[i].m_fY2); + WriteSaveBuf(buf, aGarages[i].m_fZ1); + WriteSaveBuf(buf, aGarages[i].m_fZ2); + WriteSaveBuf(buf, aGarages[i].m_fDoorPos); + WriteSaveBuf(buf, aGarages[i].m_fDoorHeight); + WriteSaveBuf(buf, aGarages[i].m_fDoor1X); + WriteSaveBuf(buf, aGarages[i].m_fDoor1Y); + WriteSaveBuf(buf, aGarages[i].m_fDoor2X); + WriteSaveBuf(buf, aGarages[i].m_fDoor2Y); + WriteSaveBuf(buf, aGarages[i].m_fDoor1Z); + WriteSaveBuf(buf, aGarages[i].m_fDoor2Z); + WriteSaveBuf(buf, aGarages[i].m_nTimeToStartAction); + WriteSaveBuf(buf, aGarages[i].m_bCollectedCarsState); + ZeroSaveBuf(buf, 3 + 4 + 4); + ZeroSaveBuf(buf, sizeof(aGarages[i].m_sStoredCar)); +#else + WriteSaveBuf(buf, aGarages[i]); +#endif + } +#ifdef FIX_GARAGE_SIZE + VALIDATESAVEBUF(*size); +#endif +} + +const CStoredCar &CStoredCar::operator=(const CStoredCar & other) +{ + m_nModelIndex = other.m_nModelIndex; + m_vecPos = other.m_vecPos; + m_vecAngle = other.m_vecAngle; + m_nFlags = other.m_nFlags; + m_nPrimaryColor = other.m_nPrimaryColor; + m_nSecondaryColor = other.m_nSecondaryColor; + m_nRadioStation = other.m_nRadioStation; + m_nVariationA = other.m_nVariationA; + m_nVariationB = other.m_nVariationB; + m_nCarBombType = other.m_nCarBombType; + return *this; +} + +void CGarages::Load(uint8* buf, uint32 size) +{ +#ifdef FIX_GARAGE_SIZE + INITSAVEBUF + assert(size == (6 * sizeof(uint32) + TOTAL_COLLECTCARS_GARAGES * sizeof(*CarTypesCollected) + sizeof(uint32) + 3 * NUM_GARAGE_STORED_CARS * sizeof(CStoredCar) + NUM_GARAGES * sizeof(CGarage))); +#else + assert(size == 5484); +#endif + CloseHideOutGaragesBeforeSave(); + ReadSaveBuf(&NumGarages, buf); + int32 tempInt; + ReadSaveBuf(&tempInt, buf); + BombsAreFree = tempInt ? true : false; + ReadSaveBuf(&tempInt, buf); + RespraysAreFree = tempInt ? true : false; + ReadSaveBuf(&CarsCollected, buf); + ReadSaveBuf(&BankVansCollected, buf); + ReadSaveBuf(&PoliceCarsCollected, buf); + for (int i = 0; i < TOTAL_COLLECTCARS_GARAGES; i++) + ReadSaveBuf(&CarTypesCollected[i], buf); + ReadSaveBuf(&LastTimeHelpMessage, buf); + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) { + ReadSaveBuf(&aCarsInSafeHouse1[i], buf); + ReadSaveBuf(&aCarsInSafeHouse2[i], buf); + ReadSaveBuf(&aCarsInSafeHouse3[i], buf); + } + for (int i = 0; i < NUM_GARAGES; i++) { +#ifdef COMPATIBLE_SAVES + ReadSaveBuf(&aGarages[i].m_eGarageType, buf); + ReadSaveBuf(&aGarages[i].m_eGarageState, buf); + ReadSaveBuf(&aGarages[i].field_2, buf); + ReadSaveBuf(&aGarages[i].m_bClosingWithoutTargetCar, buf); + ReadSaveBuf(&aGarages[i].m_bDeactivated, buf); + ReadSaveBuf(&aGarages[i].m_bResprayHappened, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&aGarages[i].m_nTargetModelIndex, buf); + SkipSaveBuf(buf, 4 + 4); + ReadSaveBuf(&aGarages[i].m_bDoor1PoolIndex, buf); + ReadSaveBuf(&aGarages[i].m_bDoor2PoolIndex, buf); + ReadSaveBuf(&aGarages[i].m_bDoor1IsDummy, buf); + ReadSaveBuf(&aGarages[i].m_bDoor2IsDummy, buf); + ReadSaveBuf(&aGarages[i].m_bRecreateDoorOnNextRefresh, buf); + ReadSaveBuf(&aGarages[i].m_bRotatedDoor, buf); + ReadSaveBuf(&aGarages[i].m_bCameraFollowsPlayer, buf); + SkipSaveBuf(buf, 1); + ReadSaveBuf(&aGarages[i].m_fX1, buf); + ReadSaveBuf(&aGarages[i].m_fX2, buf); + ReadSaveBuf(&aGarages[i].m_fY1, buf); + ReadSaveBuf(&aGarages[i].m_fY2, buf); + ReadSaveBuf(&aGarages[i].m_fZ1, buf); + ReadSaveBuf(&aGarages[i].m_fZ2, buf); + ReadSaveBuf(&aGarages[i].m_fDoorPos, buf); + ReadSaveBuf(&aGarages[i].m_fDoorHeight, buf); + ReadSaveBuf(&aGarages[i].m_fDoor1X, buf); + ReadSaveBuf(&aGarages[i].m_fDoor1Y, buf); + ReadSaveBuf(&aGarages[i].m_fDoor2X, buf); + ReadSaveBuf(&aGarages[i].m_fDoor2Y, buf); + ReadSaveBuf(&aGarages[i].m_fDoor1Z, buf); + ReadSaveBuf(&aGarages[i].m_fDoor2Z, buf); + ReadSaveBuf(&aGarages[i].m_nTimeToStartAction, buf); + ReadSaveBuf(&aGarages[i].m_bCollectedCarsState, buf); + SkipSaveBuf(buf, 3 + 4 + 4); + SkipSaveBuf(buf, sizeof(aGarages[i].m_sStoredCar)); +#else + ReadSaveBuf(&aGarages[i], buf); +#endif + aGarages[i].m_pDoor1 = nil; + aGarages[i].m_pDoor2 = nil; + aGarages[i].m_pTarget = nil; + aGarages[i].field_96 = nil; + aGarages[i].m_bRecreateDoorOnNextRefresh = true; + aGarages[i].RefreshDoorPointers(true); + if (aGarages[i].m_eGarageType == GARAGE_CRUSHER) + aGarages[i].UpdateCrusherAngle(); + else + aGarages[i].UpdateDoorsHeight(); + } +#ifdef FIX_GARAGE_SIZE + VALIDATESAVEBUF(size); +#endif + + MessageEndTime = 0; + bCamShouldBeOutisde = false; + MessageStartTime = 0; +} + +bool +CGarages::IsModelIndexADoor(uint32 id) +{ + return id == MI_GARAGEDOOR1 || + id == MI_GARAGEDOOR2 || + id == MI_GARAGEDOOR3 || + id == MI_GARAGEDOOR4 || + id == MI_GARAGEDOOR5 || + id == MI_GARAGEDOOR6 || + id == MI_GARAGEDOOR7 || + id == MI_GARAGEDOOR9 || + id == MI_GARAGEDOOR10 || + id == MI_GARAGEDOOR11 || + id == MI_GARAGEDOOR12 || + id == MI_GARAGEDOOR13 || + id == MI_GARAGEDOOR14 || + id == MI_GARAGEDOOR15 || + id == MI_GARAGEDOOR16 || + id == MI_GARAGEDOOR17 || + id == MI_GARAGEDOOR18 || + id == MI_GARAGEDOOR19 || + id == MI_GARAGEDOOR20 || + id == MI_GARAGEDOOR21 || + id == MI_GARAGEDOOR22 || + id == MI_GARAGEDOOR23 || + id == MI_GARAGEDOOR24 || + id == MI_GARAGEDOOR25 || + id == MI_GARAGEDOOR26 || + id == MI_GARAGEDOOR27 || + id == MI_GARAGEDOOR28 || + id == MI_GARAGEDOOR29 || + id == MI_GARAGEDOOR30 || + id == MI_GARAGEDOOR31 || + id == MI_GARAGEDOOR32 || + id == MI_CRUSHERBODY || + id == MI_CRUSHERLID; +} + +void CGarages::StopCarFromBlowingUp(CAutomobile* pCar) +{ + pCar->m_fFireBlowUpTimer = 0.0f; + pCar->m_fHealth = Max(pCar->m_fHealth, 300.0f); + pCar->Damage.SetEngineStatus(Max(pCar->Damage.GetEngineStatus(), 275)); +} + +bool CGarage::Does60SecondsNeedThisCarAtAll(int mi) +{ + for (int i = 0; i < ARRAY_SIZE(gaCarsToCollectIn60Seconds); i++) { + if (gaCarsToCollectIn60Seconds[i] == mi) + return true; + } + return false; +} + +bool CGarage::Does60SecondsNeedThisCar(int mi) +{ + for (int i = 0; i < ARRAY_SIZE(gaCarsToCollectIn60Seconds); i++) { + if (gaCarsToCollectIn60Seconds[i] == mi) + return m_bCollectedCarsState & BIT(i); + } + return false; +} + +void CGarage::MarkThisCarAsCollectedFor60Seconds(int mi) +{ + for (int i = 0; i < ARRAY_SIZE(gaCarsToCollectIn60Seconds); i++) { + if (gaCarsToCollectIn60Seconds[i] == mi) + m_bCollectedCarsState |= BIT(i); + } +} + +bool CGarage::IsPlayerEntirelyInsideGarage() +{ + return IsEntityEntirelyInside3D(FindPlayerVehicle() ? (CEntity*)FindPlayerVehicle() : (CEntity*)FindPlayerPed(), 0.0f); +} diff --git a/src/control/Garages.h b/src/control/Garages.h new file mode 100644 index 0000000..8a9fd1b --- /dev/null +++ b/src/control/Garages.h @@ -0,0 +1,263 @@ +#pragma once +#include "audio_enums.h" +#include "Camera.h" +#include "config.h" +#include "Lists.h" + +class CVehicle; + +enum eGarageState +{ + GS_FULLYCLOSED, + GS_OPENED, + GS_CLOSING, + GS_OPENING, + GS_OPENEDCONTAINSCAR, + GS_CLOSEDCONTAINSCAR, + GS_AFTERDROPOFF, +}; + +enum eGarageType +{ + GARAGE_NONE, + GARAGE_MISSION, + GARAGE_BOMBSHOP1, + GARAGE_BOMBSHOP2, + GARAGE_BOMBSHOP3, + GARAGE_RESPRAY, + GARAGE_COLLECTORSITEMS, + GARAGE_COLLECTSPECIFICCARS, + GARAGE_COLLECTCARS_1, + GARAGE_COLLECTCARS_2, + GARAGE_COLLECTCARS_3, + GARAGE_FORCARTOCOMEOUTOF, + GARAGE_60SECONDS, + GARAGE_CRUSHER, + GARAGE_MISSION_KEEPCAR, + GARAGE_FOR_SCRIPT_TO_OPEN, + GARAGE_HIDEOUT_ONE, + GARAGE_HIDEOUT_TWO, + GARAGE_HIDEOUT_THREE, + GARAGE_FOR_SCRIPT_TO_OPEN_AND_CLOSE, + GARAGE_KEEPS_OPENING_FOR_SPECIFIC_CAR, + GARAGE_MISSION_KEEPCAR_REMAINCLOSED, +}; + +enum +{ + TOTAL_COLLECTCARS_GARAGES = GARAGE_COLLECTCARS_3 - GARAGE_COLLECTCARS_1 + 1, + TOTAL_COLLECTCARS_CARS = 16 +}; + +class CStoredCar +{ + enum { + FLAG_BULLETPROOF = 0x1, + FLAG_FIREPROOF = 0x2, + FLAG_EXPLOSIONPROOF = 0x4, + FLAG_COLLISIONPROOF = 0x8, + FLAG_MELEEPROOF = 0x10, + }; + int32 m_nModelIndex; + CVector m_vecPos; + CVector m_vecAngle; + int32 m_nFlags; + int8 m_nPrimaryColor; + int8 m_nSecondaryColor; + int8 m_nRadioStation; + int8 m_nVariationA; + int8 m_nVariationB; + int8 m_nCarBombType; +public: + void Init() { m_nModelIndex = 0; } + void Clear() { m_nModelIndex = 0; } + bool HasCar() { return m_nModelIndex != 0; } + const CStoredCar &operator=(const CStoredCar& other); + void StoreCar(CVehicle*); + CVehicle* RestoreCar(); +}; + +VALIDATE_SIZE(CStoredCar, 0x28); + +#define SWITCH_GARAGE_DISTANCE_CLOSE 40.0f + +#define CRUSHER_GARAGE_X1 (1135.5f) +#define CRUSHER_GARAGE_Y1 (57.0f) +#define CRUSHER_GARAGE_Z1 (-1.0f) +#define CRUSHER_GARAGE_X2 (1149.5f) +#define CRUSHER_GARAGE_Y2 (63.7f) +#define CRUSHER_GARAGE_Z2 (3.5f) + +class CGarage +{ +public: + uint8 m_eGarageType; + uint8 m_eGarageState; + bool field_2; // unused + bool m_bClosingWithoutTargetCar; + bool m_bDeactivated; + bool m_bResprayHappened; + int32 m_nTargetModelIndex; + CEntity *m_pDoor1; + CEntity *m_pDoor2; + uint8 m_bDoor1PoolIndex; + uint8 m_bDoor2PoolIndex; + bool m_bDoor1IsDummy; + bool m_bDoor2IsDummy; + bool m_bRecreateDoorOnNextRefresh; + bool m_bRotatedDoor; + bool m_bCameraFollowsPlayer; + float m_fX1; + float m_fX2; + float m_fY1; + float m_fY2; + float m_fZ1; + float m_fZ2; + float m_fDoorPos; + float m_fDoorHeight; + float m_fDoor1X; + float m_fDoor1Y; + float m_fDoor2X; + float m_fDoor2Y; + float m_fDoor1Z; + float m_fDoor2Z; + uint32 m_nTimeToStartAction; + uint8 m_bCollectedCarsState; + CVehicle *m_pTarget; + void* field_96; // unused + CStoredCar m_sStoredCar; // not needed + + void OpenThisGarage(); + void CloseThisGarage(); + bool IsOpen() { return m_eGarageState == GS_OPENED || m_eGarageState == GS_OPENEDCONTAINSCAR; } + bool IsClosed() { return m_eGarageState == GS_FULLYCLOSED; } + bool IsUsed() { return m_eGarageType != GARAGE_NONE; } + void Update(); + float GetGarageCenterX() { return (m_fX1 + m_fX2) / 2; } + float GetGarageCenterY() { return (m_fY1 + m_fY2) / 2; } + bool IsFar() + { +#ifdef FIX_BUGS + return Abs(TheCamera.GetPosition().x - GetGarageCenterX()) > SWITCH_GARAGE_DISTANCE_CLOSE || + Abs(TheCamera.GetPosition().y - GetGarageCenterY()) > SWITCH_GARAGE_DISTANCE_CLOSE; +#else + return Abs(TheCamera.GetPosition().x - m_fX1) > SWITCH_GARAGE_DISTANCE_CLOSE || + Abs(TheCamera.GetPosition().y - m_fY1) > SWITCH_GARAGE_DISTANCE_CLOSE; +#endif + } + void TidyUpGarageClose(); + void TidyUpGarage(); + void RefreshDoorPointers(bool); + void UpdateCrusherAngle(); + void UpdateDoorsHeight(); + bool IsEntityEntirelyInside3D(CEntity*, float); + bool IsEntityEntirelyOutside(CEntity*, float); + bool IsEntityEntirelyInside(CEntity*); + float CalcDistToGarageRectangleSquared(float, float); + float CalcSmallestDistToGarageDoorSquared(float, float); + bool IsAnyOtherCarTouchingGarage(CVehicle* pException); + bool IsStaticPlayerCarEntirelyInside(); + bool IsPlayerOutsideGarage(); + bool IsAnyCarBlockingDoor(); + void CenterCarInGarage(CVehicle*); + bool DoesCraigNeedThisCar(int32); + bool MarkThisCarAsCollectedForCraig(int32); + bool HasCraigCollectedThisCar(int32); + bool IsGarageEmpty(); + void UpdateCrusherShake(float, float); + int32 CountCarsWithCenterPointWithinGarage(CEntity* pException); + void RemoveCarsBlockingDoorNotInside(); + void StoreAndRemoveCarsForThisHideout(CStoredCar*, int32); + bool RestoreCarsForThisHideout(CStoredCar*); + bool IsEntityTouching3D(CEntity*); + bool EntityHasASphereWayOutsideGarage(CEntity*, float); + bool IsAnyOtherPedTouchingGarage(CPed* pException); + void BuildRotatedDoorMatrix(CEntity*, float); + void FindDoorsEntities(); + void FindDoorsEntitiesSectorList(CPtrList&, bool); + void PlayerArrestedOrDied(); + bool Does60SecondsNeedThisCarAtAll(int mi); + bool Does60SecondsNeedThisCar(int mi); + void MarkThisCarAsCollectedFor60Seconds(int mi); + bool IsPlayerEntirelyInsideGarage(); + +}; + +VALIDATE_SIZE(CGarage, 140); + +class CGarages +{ + enum { + MESSAGE_LENGTH = 8 + }; +public: + static int32 BankVansCollected; + static bool BombsAreFree; + static bool RespraysAreFree; + static int32 CarsCollected; + static int32 CarTypesCollected[TOTAL_COLLECTCARS_GARAGES]; + static int32 CrushedCarId; + static uint32 LastTimeHelpMessage; + static int32 MessageNumberInString; + static char MessageIDString[MESSAGE_LENGTH]; + static int32 MessageNumberInString2; + static uint32 MessageStartTime; + static uint32 MessageEndTime; + static uint32 NumGarages; + static bool PlayerInGarage; + static int32 PoliceCarsCollected; + static CGarage aGarages[NUM_GARAGES]; + static CStoredCar aCarsInSafeHouse1[NUM_GARAGE_STORED_CARS]; + static CStoredCar aCarsInSafeHouse2[NUM_GARAGE_STORED_CARS]; + static CStoredCar aCarsInSafeHouse3[NUM_GARAGE_STORED_CARS]; + static bool bCamShouldBeOutisde; + + static void Init(void); +#ifndef PS2 + static void Shutdown(void); +#endif + static void Update(void); + + static int16 AddOne(CVector pos1, CVector pos2, uint8 type, int32 targetId); + static void ChangeGarageType(int16, uint8, int32); + static void PrintMessages(void); + static void TriggerMessage(const char* text, int16, uint16 time, int16); + static void SetTargetCarForMissonGarage(int16, CVehicle*); + static bool HasCarBeenDroppedOffYet(int16); + static void DeActivateGarage(int16); + static void ActivateGarage(int16); + static int32 QueryCarsCollected(int16); + static bool HasImportExportGarageCollectedThisCar(int16, int8); + static bool IsGarageOpen(int16); + static bool IsGarageClosed(int16); + static bool HasThisCarBeenCollected(int16, uint8); + static void OpenGarage(int16 garage) { aGarages[garage].OpenThisGarage(); } + static void CloseGarage(int16 garage) { aGarages[garage].CloseThisGarage(); } + static bool HasResprayHappened(int16); + static void SetGarageDoorToRotate(int16); + static void SetLeaveCameraForThisGarage(int16); + static bool IsThisCarWithinGarageArea(int16, CEntity*); + static bool HasCarBeenCrushed(int32); + static bool IsPointInAGarageCameraZone(CVector); + static bool CameraShouldBeOutside(void); + static void GivePlayerDetonator(void); + static void PlayerArrestedOrDied(void); + static bool IsPointWithinHideOutGarage(Const CVector&); + static bool IsPointWithinAnyGarage(Const CVector&); + static void SetAllDoorsBackToOriginalHeight(void); + static void Save(uint8* buf, uint32* size); + static void Load(uint8* buf, uint32 size); + static bool IsModelIndexADoor(uint32 id); + static void SetFreeBombs(bool bValue) { BombsAreFree = bValue; } + static void SetFreeResprays(bool bValue) { RespraysAreFree = bValue; } + static void StopCarFromBlowingUp(CAutomobile*); + + static bool IsCarSprayable(CVehicle*); + static float FindDoorHeightForMI(int32); + static void CloseHideOutGaragesBeforeSave(void); + static int32 CountCarsInHideoutGarage(uint8); + static int32 FindMaxNumStoredCarsForGarage(uint8); + static int32 GetBombTypeForGarageType(uint8 type) { return type - GARAGE_BOMBSHOP1 + 1; } + static int32 GetCarsCollectedIndexForGarageType(uint8 type) { return type - GARAGE_COLLECTCARS_1; } + +}; diff --git a/src/control/NameGrid.cpp b/src/control/NameGrid.cpp new file mode 100644 index 0000000..204e8b9 --- /dev/null +++ b/src/control/NameGrid.cpp @@ -0,0 +1,87 @@ +#include "common.h" +#include "NameGrid.h" + +// TODO: reverse mobile code + +CPlayerName::CPlayerName() +{ + // TODO +} + +void +CPlayerName::DisplayName(int) +{ + // TODO +} + +CRow::CRow() +{ + // TODO +} + +void +CRow::SetLetter(int, wchar *) +{ + // TODO +} + +CGrid::CGrid() +{ + // TODO +} + +void +CGrid::ProcessAnyLeftJustDown() +{ + unk_int2--; +} + +void +CGrid::ProcessAnyRightJustDown() +{ + unk_int2++; +} + +void +CGrid::ProcessAnyUpJustDown() +{ + unk_int1--; +} + +void +CGrid::ProcessAnyDownJustDown() +{ + unk_int1++; +} + +void +CGrid::AllDoneMakePlayerName() +{ + // TODO +} + +void +CGrid::ProcessDPadCrossJustDown() +{ + // TODO +} + +void +CGrid::DisplayGrid() +{ + // TODO +} + +void +CGrid::ProcessControllerInput() +{ + // TODO +} + +void +CGrid::Process() +{ + ProcessControllerInput(); + DisplayGrid(); + playerName.DisplayName(2 * playerName.unk_4c); +} \ No newline at end of file diff --git a/src/control/NameGrid.h b/src/control/NameGrid.h new file mode 100644 index 0000000..d52cec7 --- /dev/null +++ b/src/control/NameGrid.h @@ -0,0 +1,53 @@ +#pragma once + +// TODO: reverse mobile code + +class CPlayerName +{ + friend class CGrid; + + float x; + float y; + wchar unk_8[34]; + int unk_4c; +public: + CPlayerName(); + void DisplayName(int); +}; + +class CRow +{ + friend class CGrid; + + int unk_0; + int unk_4; + wchar unk_8[20]; + int unk_30; +public: + CRow(); + void SetLetter(int, wchar *); +}; + +class CGrid +{ + CRow rows[5]; + int unk_int1; + int unk_int2; + int unk_int3; + float unk_float1; + float unk_float2; + CPlayerName playerName; + char unk2[4]; + char unk3[4]; +public: + CGrid(); + void ProcessAnyLeftJustDown(); + void ProcessAnyRightJustDown(); + void ProcessAnyUpJustDown(); + void ProcessAnyDownJustDown(); + void AllDoneMakePlayerName(); + void ProcessDPadCrossJustDown(); + void DisplayGrid(); + void ProcessControllerInput(); + void Process(); +}; \ No newline at end of file diff --git a/src/control/OnscreenTimer.cpp b/src/control/OnscreenTimer.cpp new file mode 100644 index 0000000..08c68cb --- /dev/null +++ b/src/control/OnscreenTimer.cpp @@ -0,0 +1,159 @@ +#include "common.h" + + +#include "DMAudio.h" +#include "Hud.h" +#include "Replay.h" +#include "Timer.h" +#include "Script.h" +#include "OnscreenTimer.h" + +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 = COUNTER_DISPLAY_NUMBER; + m_sEntries[i].m_bTimerProcessed = false; + m_sEntries[i].m_bCounterProcessed = false; + } +} + +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 = COUNTER_DISPLAY_NUMBER; + m_sEntries[i].m_bCounterProcessed = false; + } + } +} + +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 = false; + } +} + +void +COnscreenTimer::AddCounter(uint32 offset, uint16 type, char* text) +{ + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) + if(m_sEntries[i].m_nCounterOffset == 0) { + 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; + break; + } +} + +void +COnscreenTimer::AddClock(uint32 offset, char* text) +{ + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) + if(m_sEntries[i].m_nTimerOffset == 0) { + m_sEntries[i].m_nTimerOffset = offset; + if (text) + strncpy(m_sEntries[i].m_aTimerText, text, 10); + else + m_sEntries[i].m_aTimerText[0] = '\0'; + break; + } +} + +void +COnscreenTimerEntry::Process() +{ + if(m_nTimerOffset == 0) + return; + + int32* timerPtr = CTheScripts::GetPointerToScriptVariable(m_nTimerOffset); + int32 oldTime = *timerPtr; + int32 newTime = oldTime - int32(CTimer::GetTimeStepInMilliseconds()); + if(newTime < 0) { + *timerPtr = 0; + m_bTimerProcessed = false; + m_nTimerOffset = 0; + m_aTimerText[0] = '\0'; + } else { + *timerPtr = newTime; + int32 oldTimeSeconds = oldTime / 1000; + if(oldTimeSeconds < 12 && newTime / 1000 != oldTimeSeconds) { + DMAudio.PlayFrontEndSound(SOUND_CLOCK_TICK, 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; + ProcessForDisplayClock(); + } + + if(m_nCounterOffset != 0) { + m_bCounterProcessed = true; + ProcessForDisplayCounter(); + } + return true; +} + +void +COnscreenTimerEntry::ProcessForDisplayClock() +{ + uint32 time = *CTheScripts::GetPointerToScriptVariable(m_nTimerOffset); + sprintf(m_bTimerBuffer, "%02d:%02d", time / 1000 / 60, + time / 1000 % 60); +} + +void +COnscreenTimerEntry::ProcessForDisplayCounter() +{ + uint32 counter = *CTheScripts::GetPointerToScriptVariable(m_nCounterOffset); + sprintf(m_bCounterBuffer, "%d", counter); +} diff --git a/src/control/OnscreenTimer.h b/src/control/OnscreenTimer.h new file mode 100644 index 0000000..3ef7764 --- /dev/null +++ b/src/control/OnscreenTimer.h @@ -0,0 +1,49 @@ +#pragma once + +enum +{ + COUNTER_DISPLAY_NUMBER, + COUNTER_DISPLAY_BAR, +}; + +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(); + + void ProcessForDisplayClock(); + void ProcessForDisplayCounter(); +}; + +VALIDATE_SIZE(COnscreenTimerEntry, 0x74); + +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); +}; + +VALIDATE_SIZE(COnscreenTimer, 0x78); diff --git a/src/control/PathFind.cpp b/src/control/PathFind.cpp new file mode 100644 index 0000000..c640782 --- /dev/null +++ b/src/control/PathFind.cpp @@ -0,0 +1,1830 @@ +#include "common.h" + +#include "General.h" +#include "FileMgr.h" // only needed for empty function +#include "Camera.h" +#include "Vehicle.h" +#include "World.h" +#include "Lines.h" // for debug +#include "PathFind.h" + +bool gbShowPedPaths; +bool gbShowCarPaths; +bool gbShowCarPathsLinks; + +CPathFind ThePaths; + +#define MAX_DIST INT16_MAX-1 +#define MIN_PED_ROUTE_DISTANCE 23.8f + + +#define NUMTEMPNODES 4000 +#define NUMDETACHED_CARS 100 +#define NUMDETACHED_PEDS 50 + + +// object flags: +// 1 UseInRoadBlock +// 2 east/west road(?) + +CPathInfoForObject *InfoForTileCars; +CPathInfoForObject *InfoForTilePeds; + +// unused +CTempDetachedNode *DetachedNodesCars; +CTempDetachedNode *DetachedNodesPeds; + +bool +CPedPath::CalcPedRoute(int8 pathType, CVector position, CVector destination, CVector *pointPoses, int16 *pointsFound, int16 maxPoints) +{ + *pointsFound = 0; + CVector vecDistance = destination - position; + if (Abs(vecDistance.x) > MIN_PED_ROUTE_DISTANCE || Abs(vecDistance.y) > MIN_PED_ROUTE_DISTANCE || Abs(vecDistance.z) > MIN_PED_ROUTE_DISTANCE) + return false; + CVector vecPos = (position + destination) * 0.5f; + CVector vecSectorStartPos (vecPos.x - 14.0f, vecPos.y - 14.0f, vecPos.z); + CVector2D vecSectorEndPos (vecPos.x + 28.0f, vecPos.x + 28.0f); + const int16 nodeStartX = (position.x - vecSectorStartPos.x) / 0.7f; + const int16 nodeStartY = (position.y - vecSectorStartPos.y) / 0.7f; + const int16 nodeEndX = (destination.x - vecSectorStartPos.x) / 0.7f; + const int16 nodeEndY = (destination.y - vecSectorStartPos.y) / 0.7f; + if (nodeStartX == nodeEndX && nodeStartY == nodeEndY) + return false; + CPedPathNode pathNodes[40][40]; + CPedPathNode pathNodesList[416]; + for (int32 x = 0; x < 40; x++) { + for (int32 y = 0; y < 40; y++) { + pathNodes[x][y].bBlockade = false; + pathNodes[x][y].id = INT16_MAX; + pathNodes[x][y].nodeIdX = x; + pathNodes[x][y].nodeIdY = y; + } + } + CWorld::AdvanceCurrentScanCode(); + if (pathType != ROUTE_NO_BLOCKADE) { + const int32 nStartX = Max(CWorld::GetSectorIndexX(vecSectorStartPos.x), 0); + const int32 nStartY = Max(CWorld::GetSectorIndexY(vecSectorStartPos.y), 0); + const int32 nEndX = Min(CWorld::GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(CWorld::GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y - 1); + for (int32 y = nStartY; y <= nEndY; y++) { + for (int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = CWorld::GetSector(x, y); + AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_VEHICLES], pathNodes, &vecSectorStartPos); + AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], pathNodes, &vecSectorStartPos); + AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_OBJECTS], pathNodes, &vecSectorStartPos); + AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], pathNodes, &vecSectorStartPos); + } + } + } + for (int32 i = 0; i < 416; i++) { + pathNodesList[i].prev = nil; + pathNodesList[i].next = nil; + } + CPedPathNode *pStartPathNode = &pathNodes[nodeStartX][nodeStartY]; + CPedPathNode *pEndPathNode = &pathNodes[nodeEndX][nodeEndY]; + pEndPathNode->bBlockade = false; + pEndPathNode->id = 0; + pEndPathNode->prev = nil; + pEndPathNode->next = pathNodesList; + pathNodesList[0].prev = pEndPathNode; + int32 pathNodeIndex = 0; + CPedPathNode *pPreviousNode = nil; + for (; pathNodeIndex < 414; pathNodeIndex++) + { + pPreviousNode = pathNodesList[pathNodeIndex].prev; + while (pPreviousNode && pPreviousNode != pStartPathNode) { + const uint8 nodeIdX = pPreviousNode->nodeIdX; + const uint8 nodeIdY = pPreviousNode->nodeIdY; + if (nodeIdX > 0) { + AddNodeToPathList(&pathNodes[nodeIdX - 1][nodeIdY], pathNodeIndex + 5, pathNodesList); + if (nodeIdY > 0) + AddNodeToPathList(&pathNodes[nodeIdX - 1][nodeIdY - 1], pathNodeIndex + 7, pathNodesList); + if (nodeIdY < 39) + AddNodeToPathList(&pathNodes[nodeIdX - 1][nodeIdY + 1], pathNodeIndex + 7, pathNodesList); + } + if (nodeIdX < 39) { + AddNodeToPathList(&pathNodes[nodeIdX + 1][nodeIdY], pathNodeIndex + 5, pathNodesList); + if (nodeIdY > 0) + AddNodeToPathList(&pathNodes[nodeIdX + 1][nodeIdY - 1], pathNodeIndex + 7, pathNodesList); + if (nodeIdY < 39) + AddNodeToPathList(&pathNodes[nodeIdX + 1][nodeIdY + 1], pathNodeIndex + 7, pathNodesList); + } + if (nodeIdY > 0) + AddNodeToPathList(&pathNodes[nodeIdX][nodeIdY - 1], pathNodeIndex + 5, pathNodesList); + if (nodeIdY < 39) + AddNodeToPathList(&pathNodes[nodeIdX][nodeIdY + 1], pathNodeIndex + 5, pathNodesList); + pPreviousNode = pPreviousNode->prev; + if (!pPreviousNode) + break; + } + + if (pPreviousNode && pPreviousNode == pStartPathNode) + break; + } + if (pathNodeIndex == 414) + return false; + CPedPathNode *pPathNode = pStartPathNode; + for (*pointsFound = 0; pPathNode != pEndPathNode && *pointsFound < maxPoints; ++ *pointsFound) { + const uint8 nodeIdX = pPathNode->nodeIdX; + const uint8 nodeIdY = pPathNode->nodeIdY; + if (nodeIdX > 0 && pathNodes[nodeIdX - 1][nodeIdY].id + 5 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX - 1][nodeIdY]; + else if (nodeIdX > 39 && pathNodes[nodeIdX + 1][nodeIdY].id + 5 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX + 1][nodeIdY]; + else if (nodeIdY > 0 && pathNodes[nodeIdX][nodeIdY - 1].id + 5 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX][nodeIdY - 1]; + else if (nodeIdY > 39 && pathNodes[nodeIdX][nodeIdY + 1].id + 5 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX][nodeIdY + 1]; + else if (nodeIdX > 0 && nodeIdY > 0 && pathNodes[nodeIdX - 1][nodeIdY - 1].id + 7 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX - 1][nodeIdY - 1]; + else if (nodeIdX > 0 && nodeIdY < 39 && pathNodes[nodeIdX - 1][nodeIdY + 1].id + 7 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX - 1][nodeIdY + 1]; + else if (nodeIdX < 39 && nodeIdY > 0 && pathNodes[nodeIdX + 1][nodeIdY - 1].id + 7 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX + 1][nodeIdY - 1]; + else if (nodeIdX < 39 && nodeIdY < 39 && pathNodes[nodeIdX + 1][nodeIdY + 1].id + 7 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX + 1][nodeIdY + 1]; + pointPoses[*pointsFound] = vecSectorStartPos; + pointPoses[*pointsFound].x += pPathNode->nodeIdX * 0.7f; + pointPoses[*pointsFound].y += pPathNode->nodeIdY * 0.7f; + } + return true; +} + + +void +CPedPath::AddNodeToPathList(CPedPathNode *pNodeToAdd, int16 id, CPedPathNode *pNodeList) +{ + if (!pNodeToAdd->bBlockade && id < pNodeToAdd->id) { + if (pNodeToAdd->id != INT16_MAX) + RemoveNodeFromList(pNodeToAdd); + AddNodeToList(pNodeToAdd, id, pNodeList); + } +} + +void +CPedPath::RemoveNodeFromList(CPedPathNode *pNode) +{ + pNode->next->prev = pNode->prev; + if (pNode->prev) + pNode->prev->next = pNode->next; +} + +void +CPedPath::AddNodeToList(CPedPathNode *pNode, int16 index, CPedPathNode *pList) +{ + pNode->prev = pList[index].prev; + pNode->next = &pList[index]; + if (pList[index].prev) + pList[index].prev->next = pNode; + pList[index].prev = pNode; + pNode->id = index; +} + +void +CPedPath::AddBlockadeSectorList(CPtrList& list, CPedPathNode(*pathNodes)[40], CVector *pPosition) +{ + CPtrNode* listNode = list.first; + while (listNode) { + CEntity* pEntity = (CEntity*)listNode->item; + if (pEntity->m_scanCode != CWorld::GetCurrentScanCode() && pEntity->bUsesCollision) { + pEntity->m_scanCode = CWorld::GetCurrentScanCode(); + AddBlockade(pEntity, pathNodes, pPosition); + } + listNode = listNode->next; + } +} + +void +CPedPath::AddBlockade(CEntity *pEntity, CPedPathNode(*pathNodes)[40], CVector *pPosition) +{ + const CColBox& boundingBox = pEntity->GetColModel()->boundingBox; + const float fBoundMaxY = boundingBox.max.y + 0.3f; + const float fBoundMinY = boundingBox.min.y - 0.3f; + const float fBoundMaxX = boundingBox.max.x + 0.3f; + const float fDistanceX = pPosition->x - pEntity->GetMatrix().GetPosition().x; + const float fDistanceY = pPosition->y - pEntity->GetMatrix().GetPosition().y; + const float fBoundRadius = pEntity->GetBoundRadius(); + CVector vecBoundCentre; + pEntity->GetBoundCentre(vecBoundCentre); + if (vecBoundCentre.x + fBoundRadius >= pPosition->x && + vecBoundCentre.y + fBoundRadius >= pPosition->y && + vecBoundCentre.x - fBoundRadius <= pPosition->x + 28.0f && + vecBoundCentre.y - fBoundRadius <= pPosition->y + 28.0f) { + for (int16 x = 0; x < 40; x++) { + const float pointX = x * 0.7f + fDistanceX; + for (int16 y = 0; y < 40; y++) { + if (!pathNodes[x][y].bBlockade) { + const float pointY = y * 0.7f + fDistanceY; + CVector2D point(pointX, pointY); + if (fBoundMaxX > Abs(DotProduct2D(point, pEntity->GetMatrix().GetRight()))) { + float fDotProduct = DotProduct2D(point, pEntity->GetMatrix().GetForward()); + if (fBoundMaxY > fDotProduct && fBoundMinY < fDotProduct) + pathNodes[x][y].bBlockade = true; + } + } + } + } + } +} + +void +CPathFind::Init(void) +{ + int i; + + m_numPathNodes = 0; + m_numMapObjects = 0; + m_numConnections = 0; + m_numCarPathLinks = 0; + unk = 0; + + for(i = 0; i < NUM_PATHNODES; i++) + m_pathNodes[i].distance = MAX_DIST; +} + +void +CPathFind::AllocatePathFindInfoMem(int16 numPathGroups) +{ + delete[] InfoForTileCars; + InfoForTileCars = nil; + delete[] InfoForTilePeds; + InfoForTilePeds = nil; + + // NB: MIAMI doesn't use numPathGroups here but hardcodes 4500 + InfoForTileCars = new CPathInfoForObject[12*numPathGroups]; + memset(InfoForTileCars, 0, 12*numPathGroups*sizeof(CPathInfoForObject)); + InfoForTilePeds = new CPathInfoForObject[12*numPathGroups]; + memset(InfoForTilePeds, 0, 12*numPathGroups*sizeof(CPathInfoForObject)); + + // unused + delete[] DetachedNodesCars; + DetachedNodesCars = nil; + delete[] DetachedNodesPeds; + DetachedNodesPeds = nil; + DetachedNodesCars = new CTempDetachedNode[NUMDETACHED_CARS]; + memset(DetachedNodesCars, 0, NUMDETACHED_CARS*sizeof(CTempDetachedNode)); + DetachedNodesPeds = new CTempDetachedNode[NUMDETACHED_PEDS]; + memset(DetachedNodesPeds, 0, NUMDETACHED_PEDS*sizeof(CTempDetachedNode)); +} + +void +CPathFind::RegisterMapObject(CTreadable *mapObject) +{ + m_mapObjects[m_numMapObjects++] = mapObject; +} + +void +CPathFind::StoreNodeInfoPed(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, int16 width, bool crossing) +{ + int i, j; + + i = id*12 + node; + InfoForTilePeds[i].type = type; + InfoForTilePeds[i].next = next; + InfoForTilePeds[i].x = x; + InfoForTilePeds[i].y = y; + InfoForTilePeds[i].z = z; + InfoForTilePeds[i].numLeftLanes = 0; + InfoForTilePeds[i].numRightLanes = 0; + InfoForTilePeds[i].crossing = crossing; + + if(type) + for(i = 0; i < node; i++){ + j = id*12 + i; + if(x == InfoForTilePeds[j].x && y == InfoForTilePeds[j].y){ + printf("^^^^^^^^^^^^^ AARON IS TOO CHICKEN TO EAT MEAT!\n"); + printf("Several ped nodes on one road segment have identical coordinates (%d==%d && %d==%d)\n", + x, InfoForTilePeds[j].x, y, InfoForTilePeds[j].y); + printf("Modelindex of cullprit: %d\n\n", id); + } + } +} + +void +CPathFind::StoreNodeInfoCar(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, int16 width, int8 numLeft, int8 numRight) +{ + int i, j; + + i = id*12 + node; + InfoForTileCars[i].type = type; + InfoForTileCars[i].next = next; + InfoForTileCars[i].x = x; + InfoForTileCars[i].y = y; + InfoForTileCars[i].z = z; + InfoForTileCars[i].numLeftLanes = numLeft; + InfoForTileCars[i].numRightLanes = numRight; + + + if(type) + for(i = 0; i < node; i++){ + j = id*12 + i; + if(x == InfoForTileCars[j].x && y == InfoForTileCars[j].y){ + printf("^^^^^^^^^^^^^ AARON IS TOO CHICKEN TO EAT MEAT!\n"); + printf("Several car nodes on one road segment have identical coordinates (%d==%d && %d==%d)\n", + x, InfoForTileCars[j].x, y, InfoForTileCars[j].y); + printf("Modelindex of cullprit: %d\n\n", id); + } + } +} + +void +CPathFind::CalcNodeCoors(int16 x, int16 y, int16 z, int id, CVector *out) +{ + CVector pos; + pos.x = x / 16.0f; + pos.y = y / 16.0f; + pos.z = z / 16.0f; + *out = m_mapObjects[id]->GetMatrix() * pos; +} + +bool +CPathFind::LoadPathFindData(void) +{ + CFileMgr::SetDir(""); + return false; +} + +void +CPathFind::PreparePathData(void) +{ + int i, j, k; + int numExtern, numIntern, numLanes; + float maxX, maxY; + CTempNode *tempNodes; + + printf("PreparePathData\n"); + if(!CPathFind::LoadPathFindData() && // empty + InfoForTileCars && InfoForTilePeds && + DetachedNodesCars && DetachedNodesPeds + ){ + tempNodes = new CTempNode[NUMTEMPNODES]; + + m_numConnections = 0; + + for(i = 0; i < PATHNODESIZE; i++) + m_pathNodes[i].unkBits = 0; + + for(i = 0; i < PATHNODESIZE; i++){ + numExtern = 0; + numIntern = 0; + for(j = 0; j < 12; j++){ + if(InfoForTileCars[i*12 + j].type == NodeTypeExtern) + numExtern++; + if(InfoForTileCars[i*12 + j].type == NodeTypeIntern) + numIntern++; + } + if(numIntern > 1 && numExtern != 2) + printf("ILLEGAL BLOCK. MORE THAN 1 INTERNALS AND NOT 2 EXTERNALS (Modelindex:%d)\n", i); + } + + for(i = 0; i < PATHNODESIZE; i++) + for(j = 0; j < 12; j++) + if(InfoForTileCars[i*12 + j].type == NodeTypeExtern){ + // MIAMI has MI:%d here but no argument for it + if(InfoForTileCars[i*12 + j].numLeftLanes < 0) + printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i); + if(InfoForTileCars[i*12 + j].numRightLanes < 0) + printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i); + if(InfoForTileCars[i*12 + j].numLeftLanes + InfoForTileCars[i*12 + j].numRightLanes <= 0) + printf("ILLEGAL BLOCK. NO LANES IN NODE (Obj:%d)\n", i); + } + + m_numPathNodes = 0; + PreparePathDataForType(PATH_CAR, tempNodes, InfoForTileCars, 1.0f, DetachedNodesCars, NUMDETACHED_CARS); + m_numCarPathNodes = m_numPathNodes; + PreparePathDataForType(PATH_PED, tempNodes, InfoForTilePeds, 1.0f, DetachedNodesPeds, NUMDETACHED_PEDS); + m_numPedPathNodes = m_numPathNodes - m_numCarPathNodes; + + // TODO: figure out what exactly is going on here + // Some roads seem to get a west/east flag + for(i = 0; i < m_numMapObjects; i++){ + numExtern = 0; + numIntern = 0; + numLanes = 0; + maxX = 0.0f; + maxY = 0.0f; + for(j = 0; j < 12; j++){ + k = m_mapObjects[i]->GetModelIndex()*12 + j; + if(InfoForTileCars[k].type == NodeTypeExtern){ + numExtern++; + numLanes = Max(numLanes, InfoForTileCars[k].numLeftLanes + InfoForTileCars[k].numRightLanes); + maxX = Max(maxX, Abs(InfoForTileCars[k].x)); + maxY = Max(maxY, Abs(InfoForTileCars[k].y)); + }else if(InfoForTileCars[k].type == NodeTypeIntern) + numIntern++; + } + + if(numIntern == 1 && numExtern == 2){ + if(numLanes < 4){ + if((i & 7) == 4){ // 1/8 probability + m_objectFlags[i] |= UseInRoadBlock; + if(maxX > maxY) + m_objectFlags[i] |= ObjectEastWest; + else + m_objectFlags[i] &= ~ObjectEastWest; + } + }else{ + m_objectFlags[i] |= UseInRoadBlock; + if(maxX > maxY) + m_objectFlags[i] |= ObjectEastWest; + else + m_objectFlags[i] &= ~ObjectEastWest; + } + } + } + + delete[] tempNodes; + + CountFloodFillGroups(PATH_CAR); + CountFloodFillGroups(PATH_PED); + + delete[] InfoForTileCars; + InfoForTileCars = nil; + delete[] InfoForTilePeds; + InfoForTilePeds = nil; + + delete[] DetachedNodesCars; + DetachedNodesCars = nil; + delete[] DetachedNodesPeds; + DetachedNodesPeds = nil; + } + printf("Done with PreparePathData\n"); +} + +/* String together connected nodes in a list by a flood fill algorithm */ +void +CPathFind::CountFloodFillGroups(uint8 type) +{ + int start, end; + int i, l; + uint16 n; + CPathNode *node, *prev; + + switch(type){ + case PATH_CAR: + start = 0; + end = m_numCarPathNodes; + break; + case PATH_PED: + start = m_numCarPathNodes; + end = start + m_numPedPathNodes; + break; + } + + for(i = start; i < end; i++) + m_pathNodes[i].group = 0; + + n = 0; + for(;;){ + n++; + if(n > 1500){ + for(i = start; m_pathNodes[i].group && i < end; i++); + printf("NumNodes:%d Accounted for:%d\n", end - start, i - start); + } + + // Look for unvisited node + for(i = start; m_pathNodes[i].group && i < end; i++); + if(i == end) + break; + + node = &m_pathNodes[i]; + node->SetNext(nil); + node->group = n; + + if(node->numLinks == 0){ + if(type == PATH_CAR) + printf("Single car node: %f %f %f (%d)\n", + node->GetX(), node->GetY(), node->GetZ(), m_mapObjects[node->objectIndex]->GetModelIndex()); + else + printf("Single ped node: %f %f %f\n", + node->GetX(), node->GetY(), node->GetZ()); + } + + while(node){ + prev = node; + node = node->GetNext(); + for(i = 0; i < prev->numLinks; i++){ + l = ConnectedNode(prev->firstLink + i); + if(m_pathNodes[l].group == 0){ + m_pathNodes[l].group = n; + if(m_pathNodes[l].group == 0) + m_pathNodes[l].group = INT8_MIN; + m_pathNodes[l].SetNext(node); + node = &m_pathNodes[l]; + } + } + } + } + + m_numGroups[type] = n-1; + printf("GraphType:%d. FloodFill groups:%d\n", type, n); +} + +int32 TempListLength; + +void +CPathFind::PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo, + float maxdist, CTempDetachedNode *detachednodes, int numDetached) +{ + static CVector CoorsXFormed; + int i, j, k, l; + int l1, l2; + int start; + float posx, posy; + float dx, dy, mag; + float nearestDist; + int nearestId; + int next; + int oldNumPathNodes, oldNumLinks; + float dist; + int iseg, jseg; + int istart, jstart; + int done, cont; + int tileStart; + +#ifndef MASTER + for (i = 0; i < m_numMapObjects-1; i++) + for (j = i+1; j < m_numMapObjects; j++) { + CTreadable *obj1 = m_mapObjects[i]; + CTreadable *obj2 = m_mapObjects[j]; + if (obj1->GetModelIndex() == obj2->GetModelIndex() && + obj1->GetPosition().x == obj2->GetPosition().x && obj1->GetPosition().y == obj2->GetPosition().y && obj1->GetPosition().z == obj2->GetPosition().z && + obj1->GetRight().x == obj2->GetRight().x && obj1->GetForward().x == obj2->GetForward().x && obj1->GetUp().x == obj2->GetUp().x && + obj1->GetRight().y == obj2->GetRight().y && obj1->GetForward().y == obj2->GetForward().y && obj1->GetUp().y == obj2->GetUp().y && + obj1->GetRight().z == obj2->GetRight().z && obj1->GetForward().z == obj2->GetForward().z && obj1->GetUp().z == obj2->GetUp().z) { + printf("THIS IS VERY BAD INDEED. FIX IMMEDIATELY!!!\n"); + printf("Double road objects at the following coors: %f %f %f\n", obj1->GetPosition().x, obj1->GetPosition().y, obj1->GetPosition().z); + } + } +#endif // !MASTER + + oldNumPathNodes = m_numPathNodes; + oldNumLinks = m_numConnections; + +#define OBJECTINDEX(n) (m_pathNodes[(n)].objectIndex) + // Initialize map objects + for(i = 0; i < m_numMapObjects; i++) + for(j = 0; j < 12; j++) + m_mapObjects[i]->m_nodeIndices[type][j] = -1; + + // Calculate internal nodes, store them and connect them to defining object + for(i = 0; i < m_numMapObjects; i++){ + tileStart = m_numPathNodes; + start = 12 * m_mapObjects[i]->GetModelIndex(); + for(j = 0; j < 12; j++){ + if(objectpathinfo[start + j].type == NodeTypeIntern){ + CalcNodeCoors( + objectpathinfo[start + j].x, + objectpathinfo[start + j].y, + objectpathinfo[start + j].z, + i, + &CoorsXFormed); + m_pathNodes[m_numPathNodes].SetPosition(CoorsXFormed); + OBJECTINDEX(m_numPathNodes) = i; + m_pathNodes[m_numPathNodes].unkBits = 1; + m_mapObjects[i]->m_nodeIndices[type][j] = m_numPathNodes; + m_numPathNodes++; + } + } + } + + + // Insert external nodes into TempList + TempListLength = 0; + for(i = 0; i < m_numMapObjects; i++){ + start = 12 * m_mapObjects[i]->GetModelIndex(); + for(j = 0; j < 12; j++){ + if(objectpathinfo[start + j].type != NodeTypeExtern) + continue; + CalcNodeCoors( + objectpathinfo[start + j].x, + objectpathinfo[start + j].y, + objectpathinfo[start + j].z, + i, + &CoorsXFormed); + + // find closest unconnected node + nearestId = -1; + nearestDist = maxdist; + for(k = 0; k < TempListLength; k++){ + if(tempnodes[k].linkState != 1) + continue; + dx = tempnodes[k].pos.x - CoorsXFormed.x; + if(Abs(dx) < nearestDist){ + dy = tempnodes[k].pos.y - CoorsXFormed.y; + if(Abs(dy) < nearestDist){ + nearestDist = Max(Abs(dx), Abs(dy)); + nearestId = k; + } + } + } + + if(nearestId < 0){ + // None found, add this one to temp list + tempnodes[TempListLength].pos = CoorsXFormed; + next = objectpathinfo[start + j].next; + if(next < 0){ + // no link from this node, find link to this node + next = 0; + for(k = start; j != objectpathinfo[k].next; k++) + next++; + } + // link to connecting internal node + tempnodes[TempListLength].link1 = m_mapObjects[i]->m_nodeIndices[type][next]; + if(type == PATH_CAR){ + tempnodes[TempListLength].numLeftLanes = objectpathinfo[start + j].numLeftLanes; + tempnodes[TempListLength].numRightLanes = objectpathinfo[start + j].numRightLanes; + } + tempnodes[TempListLength++].linkState = 1; + }else{ + // Found nearest, connect it to our neighbour + next = objectpathinfo[start + j].next; + if(next < 0){ + // no link from this node, find link to this node + next = 0; + for(k = start; j != objectpathinfo[k].next; k++) + next++; + } + tempnodes[nearestId].link2 = m_mapObjects[i]->m_nodeIndices[type][next]; + tempnodes[nearestId].linkState = 2; + + // collapse this node with nearest we found + dx = m_pathNodes[tempnodes[nearestId].link1].GetX() - m_pathNodes[tempnodes[nearestId].link2].GetX(); + dy = m_pathNodes[tempnodes[nearestId].link1].GetY() - m_pathNodes[tempnodes[nearestId].link2].GetY(); + tempnodes[nearestId].pos = (tempnodes[nearestId].pos + CoorsXFormed)*0.5f; + mag = Sqrt(dx*dx + dy*dy); + tempnodes[nearestId].dirX = dx/mag; + tempnodes[nearestId].dirY = dy/mag; + // do something when number of lanes doesn't agree + if(type == PATH_CAR) + if(tempnodes[nearestId].numLeftLanes != 0 && tempnodes[nearestId].numRightLanes != 0 && + (objectpathinfo[start + j].numLeftLanes == 0 || objectpathinfo[start + j].numRightLanes == 0)){ + // why switch left and right here? + tempnodes[nearestId].numLeftLanes = objectpathinfo[start + j].numRightLanes; + tempnodes[nearestId].numRightLanes = objectpathinfo[start + j].numLeftLanes; + } + } + } + } + + // Loop through previously added internal nodes and link them + for(i = oldNumPathNodes; i < m_numPathNodes; i++){ + // Init link + m_pathNodes[i].numLinks = 0; + m_pathNodes[i].firstLink = m_numConnections; + + // See if node connects to external nodes + for(j = 0; j < TempListLength; j++){ + if(tempnodes[j].linkState != 2) + continue; + + // Add link to other side of the external + // NB this clears the flags in MIAMI + if(tempnodes[j].link1 == i) + m_connections[m_numConnections] = tempnodes[j].link2; + else if(tempnodes[j].link2 == i) + m_connections[m_numConnections] = tempnodes[j].link1; + else + continue; + + dist = (m_pathNodes[i].GetPosition() - m_pathNodes[ConnectedNode(m_numConnections)].GetPosition()).Magnitude(); + m_distances[m_numConnections] = dist; + m_connectionFlags[m_numConnections].flags = 0; + + if(type == PATH_CAR){ + // IMPROVE: use a goto here + // Find existing car path link + for(k = 0; k < m_numCarPathLinks; k++){ + if(m_carPathLinks[k].dir.x == tempnodes[j].dirX && + m_carPathLinks[k].dir.y == tempnodes[j].dirY && + m_carPathLinks[k].pos.x == tempnodes[j].pos.x && + m_carPathLinks[k].pos.y == tempnodes[j].pos.y){ + m_carPathConnections[m_numConnections] = k; + k = m_numCarPathLinks; + } + } + // k is m_numCarPathLinks+1 if we found one + if(k == m_numCarPathLinks){ + m_carPathLinks[m_numCarPathLinks].dir.x = tempnodes[j].dirX; + m_carPathLinks[m_numCarPathLinks].dir.y = tempnodes[j].dirY; + m_carPathLinks[m_numCarPathLinks].pos.x = tempnodes[j].pos.x; + m_carPathLinks[m_numCarPathLinks].pos.y = tempnodes[j].pos.y; + m_carPathLinks[m_numCarPathLinks].pathNodeIndex = i; + m_carPathLinks[m_numCarPathLinks].numLeftLanes = tempnodes[j].numLeftLanes; + m_carPathLinks[m_numCarPathLinks].numRightLanes = tempnodes[j].numRightLanes; + m_carPathLinks[m_numCarPathLinks].trafficLightType = 0; + assert(m_numCarPathLinks <= NUM_CARPATHLINKS); + m_carPathConnections[m_numConnections] = m_numCarPathLinks++; + } + } + + m_pathNodes[i].numLinks++; + m_numConnections++; + } + + + // Find i inside path segment + iseg = 0; + for(j = Max(oldNumPathNodes, i-12); j < i; j++) + if(OBJECTINDEX(j) == OBJECTINDEX(i)) + iseg++; + + istart = 12 * m_mapObjects[m_pathNodes[i].objectIndex]->GetModelIndex(); + // Add links to other internal nodes + for(j = Max(oldNumPathNodes, i-12); j < Min(m_numPathNodes, i+12); j++){ + if(OBJECTINDEX(i) != OBJECTINDEX(j) || i == j) + continue; + // N.B.: in every path segment, the externals have to be at the end + jseg = j-i + iseg; + + jstart = 12 * m_mapObjects[m_pathNodes[j].objectIndex]->GetModelIndex(); + if(objectpathinfo[istart + iseg].next == jseg || + objectpathinfo[jstart + jseg].next == iseg){ + // Found a link between i and jConnectionSetCrossesRoad + // NB this clears the flags in MIAMI + m_connections[m_numConnections] = j; + dist = (m_pathNodes[i].GetPosition() - m_pathNodes[j].GetPosition()).Magnitude(); + m_distances[m_numConnections] = dist; + + if(type == PATH_CAR){ + posx = (m_pathNodes[i].GetX() + m_pathNodes[j].GetX())*0.5f; + posy = (m_pathNodes[i].GetY() + m_pathNodes[j].GetY())*0.5f; + dx = m_pathNodes[j].GetX() - m_pathNodes[i].GetX(); + dy = m_pathNodes[j].GetY() - m_pathNodes[i].GetY(); + mag = Sqrt(dx*dx + dy*dy); + dx /= mag; + dy /= mag; + if(i < j){ + dx = -dx; + dy = -dy; + } + // IMPROVE: use a goto here + // Find existing car path link + for(k = 0; k < m_numCarPathLinks; k++){ + if(m_carPathLinks[k].dir.x == dx && + m_carPathLinks[k].dir.y == dy && + m_carPathLinks[k].pos.x == posx && + m_carPathLinks[k].pos.y == posy){ + m_carPathConnections[m_numConnections] = k; + k = m_numCarPathLinks; + } + } + // k is m_numCarPathLinks+1 if we found one + if(k == m_numCarPathLinks){ + m_carPathLinks[m_numCarPathLinks].dir.x = dx; + m_carPathLinks[m_numCarPathLinks].dir.y = dy; + m_carPathLinks[m_numCarPathLinks].pos.x = posx; + m_carPathLinks[m_numCarPathLinks].pos.y = posy; + m_carPathLinks[m_numCarPathLinks].pathNodeIndex = i; + m_carPathLinks[m_numCarPathLinks].numLeftLanes = -1; + m_carPathLinks[m_numCarPathLinks].numRightLanes = -1; + m_carPathLinks[m_numCarPathLinks].trafficLightType = 0; + assert(m_numCarPathLinks <= NUM_CARPATHLINKS); + m_carPathConnections[m_numConnections] = m_numCarPathLinks++; + } + }else{ + // Crosses road + if(objectpathinfo[istart + iseg].next == jseg && objectpathinfo[istart + iseg].crossing || + objectpathinfo[jstart + jseg].next == iseg && objectpathinfo[jstart + jseg].crossing) + m_connectionFlags[m_numConnections].bCrossesRoad = true; + else + m_connectionFlags[m_numConnections].bCrossesRoad = false; + } + + m_pathNodes[i].numLinks++; + m_numConnections++; + } + } + } + + if(type == PATH_CAR){ + done = 0; + // Set number of lanes for all nodes somehow + // very strange code + for(k = 0; !done && k < 10; k++){ + done = 1; + for(i = 0; i < m_numPathNodes; i++){ + if(m_pathNodes[i].numLinks != 2) + continue; + l1 = m_carPathConnections[m_pathNodes[i].firstLink]; + l2 = m_carPathConnections[m_pathNodes[i].firstLink+1]; + + if(m_carPathLinks[l1].numLeftLanes == -1 && + m_carPathLinks[l2].numLeftLanes != -1){ + done = 0; + if(m_carPathLinks[l2].pathNodeIndex == i){ + // why switch left and right here? + m_carPathLinks[l1].numLeftLanes = m_carPathLinks[l2].numRightLanes; + m_carPathLinks[l1].numRightLanes = m_carPathLinks[l2].numLeftLanes; + }else{ + m_carPathLinks[l1].numLeftLanes = m_carPathLinks[l2].numLeftLanes; + m_carPathLinks[l1].numRightLanes = m_carPathLinks[l2].numRightLanes; + } + m_carPathLinks[l1].pathNodeIndex = i; + }else if(m_carPathLinks[l1].numLeftLanes != -1 && + m_carPathLinks[l2].numLeftLanes == -1){ + done = 0; + if(m_carPathLinks[l1].pathNodeIndex == i){ + // why switch left and right here? + m_carPathLinks[l2].numLeftLanes = m_carPathLinks[l1].numRightLanes; + m_carPathLinks[l2].numRightLanes = m_carPathLinks[l1].numLeftLanes; + }else{ + m_carPathLinks[l2].numLeftLanes = m_carPathLinks[l1].numLeftLanes; + m_carPathLinks[l2].numRightLanes = m_carPathLinks[l1].numRightLanes; + } + m_carPathLinks[l2].pathNodeIndex = i; + }else if(m_carPathLinks[l1].numLeftLanes == -1 && + m_carPathLinks[l2].numLeftLanes == -1) + done = 0; + } + } + + // Fall back to default values for number of lanes + for(i = 0; i < m_numPathNodes; i++) + for(j = 0; j < m_pathNodes[i].numLinks; j++){ + k = m_carPathConnections[m_pathNodes[i].firstLink + j]; + if(m_carPathLinks[k].numLeftLanes < 0) + m_carPathLinks[k].numLeftLanes = 1; + if(m_carPathLinks[k].numRightLanes < 0) + m_carPathLinks[k].numRightLanes = 1; + } + } + + // Set flags for car nodes + if(type == PATH_CAR){ + do{ + cont = 0; + for(i = 0; i < m_numPathNodes; i++){ + m_pathNodes[i].bDisabled = false; + m_pathNodes[i].bBetweenLevels = false; + // See if node is a dead end, if so, we're not done yet + if(!m_pathNodes[i].bDeadEnd){ + k = 0; + for(j = 0; j < m_pathNodes[i].numLinks; j++) + if(!m_pathNodes[ConnectedNode(m_pathNodes[i].firstLink + j)].bDeadEnd) + k++; + if(k < 2){ + m_pathNodes[i].bDeadEnd = true; + cont = 1; + } + } + } + }while(cont); + } + + // Remove isolated ped nodes + if(type == PATH_PED) + for(i = oldNumPathNodes; i < m_numPathNodes; i++){ + if(m_pathNodes[i].numLinks != 0) + continue; + + // Remove node + for(j = i; j < m_numPathNodes-1; j++) + m_pathNodes[j] = m_pathNodes[j+1]; + + // Fix links + for(j = oldNumLinks; j < m_numConnections; j++){ + int node = ConnectedNode(j); + if(node >= i) + m_connections[j] = node-1; + } + + // Also in treadables + for(j = 0; j < m_numMapObjects; j++) + for(k = 0; k < 12; k++){ + if(m_mapObjects[j]->m_nodeIndices[PATH_PED][k] == i){ + // remove this one + for(l = k; l < 12-1; l++) + m_mapObjects[j]->m_nodeIndices[PATH_PED][l] = m_mapObjects[j]->m_nodeIndices[PATH_PED][l+1]; + m_mapObjects[j]->m_nodeIndices[PATH_PED][11] = -1; + }else if(m_mapObjects[j]->m_nodeIndices[PATH_PED][k] > i) + m_mapObjects[j]->m_nodeIndices[PATH_PED][k]--; + } + + i--; + m_numPathNodes--; + } +} + +float +CPathFind::CalcRoadDensity(float x, float y) +{ + int i, j; + float density = 0.0f; + + for(i = 0; i < m_numCarPathNodes; i++){ + if(Abs(m_pathNodes[i].GetX() - x) < 80.0f && + Abs(m_pathNodes[i].GetY() - y) < 80.0f && + m_pathNodes[i].numLinks > 0){ + for(j = 0; j < m_pathNodes[i].numLinks; j++){ + int next = ConnectedNode(m_pathNodes[i].firstLink + j); + float dist = (m_pathNodes[i].GetPosition() - m_pathNodes[next].GetPosition()).Magnitude2D(); + next = m_carPathConnections[m_pathNodes[i].firstLink + j]; + density += m_carPathLinks[next].numLeftLanes * dist; + density += m_carPathLinks[next].numRightLanes * dist; + + if(m_carPathLinks[next].numLeftLanes < 0) + printf("Link from object %d to %d (MIs)\n", + m_mapObjects[m_pathNodes[i].objectIndex]->GetModelIndex(), + m_mapObjects[m_pathNodes[ConnectedNode(m_pathNodes[i].firstLink + j)].objectIndex]->GetModelIndex()); + if(m_carPathLinks[next].numRightLanes < 0) + printf("Link from object %d to %d (MIs)\n", + m_mapObjects[m_pathNodes[i].objectIndex]->GetModelIndex(), + m_mapObjects[m_pathNodes[ConnectedNode(m_pathNodes[i].firstLink + j)].objectIndex]->GetModelIndex()); + } + } + } + return density/2500.0f; +} + +bool +CPathFind::TestForPedTrafficLight(CPathNode *n1, CPathNode *n2) +{ + int i; + for(i = 0; i < n1->numLinks; i++) + if(&m_pathNodes[ConnectedNode(n1->firstLink + i)] == n2) + return ConnectionHasTrafficLight(n1->firstLink + i); + return false; +} + +bool +CPathFind::TestCrossesRoad(CPathNode *n1, CPathNode *n2) +{ + int i; + for(i = 0; i < n1->numLinks; i++) + if(&m_pathNodes[ConnectedNode(n1->firstLink + i)] == n2) + return ConnectionCrossesRoad(n1->firstLink + i); + return false; +} + +void +CPathFind::AddNodeToList(CPathNode *node, int32 listId) +{ + int i = listId & 0x1FF; + node->SetNext(m_searchNodes[i].GetNext()); + node->SetPrev(&m_searchNodes[i]); + if(m_searchNodes[i].GetNext()) + m_searchNodes[i].GetNext()->SetPrev(node); + m_searchNodes[i].SetNext(node); + node->distance = listId; +} + +void +CPathFind::RemoveNodeFromList(CPathNode *node) +{ + node->GetPrev()->SetNext(node->GetNext()); + if(node->GetNext()) + node->GetNext()->SetPrev(node->GetPrev()); +} + +void +CPathFind::RemoveBadStartNode(CVector pos, CPathNode **nodes, int16 *n) +{ + int i; + if(*n < 2) + return; + if(DotProduct2D(nodes[1]->GetPosition() - pos, nodes[0]->GetPosition() - pos) < 0.0f){ + (*n)--; + for(i = 0; i < *n; i++) + nodes[i] = nodes[i+1]; + } +} + +void +CPathFind::SetLinksBridgeLights(float x1, float x2, float y1, float y2, bool enable) +{ + int i; + for(i = 0; i < m_numCarPathLinks; i++){ + CVector2D pos = m_carPathLinks[i].GetPosition(); + if(x1 < pos.x && pos.x < x2 && + y1 < pos.y && pos.y < y2) + m_carPathLinks[i].bBridgeLights = enable; + } +} + +void +CPathFind::SwitchOffNodeAndNeighbours(int32 nodeId, bool disable) +{ + int i, next; + + m_pathNodes[nodeId].bDisabled = disable; + if(m_pathNodes[nodeId].numLinks < 3) + for(i = 0; i < m_pathNodes[nodeId].numLinks; i++){ + next = ConnectedNode(m_pathNodes[nodeId].firstLink + i); + if(m_pathNodes[next].bDisabled != disable && + m_pathNodes[next].numLinks < 3) + SwitchOffNodeAndNeighbours(next, disable); + } +} + +void +CPathFind::SwitchRoadsOffInArea(float x1, float x2, float y1, float y2, float z1, float z2, bool disable) +{ + int i; + + for(i = 0; i < m_numCarPathNodes; i++){ + CVector pos = m_pathNodes[i].GetPosition(); + if(x1 <= pos.x && pos.x <= x2 && + y1 <= pos.y && pos.y <= y2 && + z1 <= pos.z && pos.z <= z2 && + disable != m_pathNodes[i].bDisabled) + SwitchOffNodeAndNeighbours(i, disable); + } +} + +void +CPathFind::SwitchPedRoadsOffInArea(float x1, float x2, float y1, float y2, float z1, float z2, bool disable) +{ + int i; + + for(i = m_numCarPathNodes; i < m_numPathNodes; i++){ + CVector pos = m_pathNodes[i].GetPosition(); + if(x1 <= pos.x && pos.x <= x2 && + y1 <= pos.y && pos.y <= y2 && + z1 <= pos.z && pos.z <= z2 && + disable != m_pathNodes[i].bDisabled) + SwitchOffNodeAndNeighbours(i, disable); + } +} + +void +CPathFind::SwitchRoadsInAngledArea(float x1, float y1, float z1, float x2, float y2, float z2, float length, uint8 type, uint8 mode) +{ + int i; + int firstNode, lastNode; + + // this is NOT PATH_CAR + if(type != 0){ + firstNode = 0; + lastNode = m_numCarPathNodes; + }else{ + firstNode = m_numCarPathNodes; + lastNode = m_numPathNodes; + } + + if(z1 > z2){ + float tmp = z2; + z2 = z1; + z1 = tmp; + } + + // angle of vector from p2 to p1 + float angle = CGeneral::GetRadianAngleBetweenPoints(x1, y1, x2, y2) + HALFPI; + while(angle < 0.0f) angle += TWOPI; + while(angle > TWOPI) angle -= TWOPI; + // vector from p1 to p2 + CVector2D v12(x2 - x1, y2 - y1); + float len12 = v12.Magnitude(); + v12 /= len12; + + // vector from p2 to new point p3 + CVector2D v23(Sin(angle)*length, -(Cos(angle)*length)); + v23 /= v23.Magnitude(); // obivously just 'length' but whatever + + bool disable = mode == SWITCH_OFF; + for(i = firstNode; i < lastNode; i++){ + CVector pos = m_pathNodes[i].GetPosition(); + if(pos.z < z1 || pos.z > z2) + continue; + CVector2D d(pos.x - x1, pos.y - y1); + float dot = DotProduct2D(d, v12); + if(dot < 0.0f || dot > len12) + continue; + dot = DotProduct2D(d, v23); + if(dot < 0.0f || dot > length) + continue; + if(m_pathNodes[i].bDisabled != disable) + SwitchOffNodeAndNeighbours(i, disable); + } +} + +void +CPathFind::MarkRoadsBetweenLevelsNodeAndNeighbours(int32 nodeId) +{ + int i, next; + + m_pathNodes[nodeId].bBetweenLevels = true; + if(m_pathNodes[nodeId].numLinks < 3) + for(i = 0; i < m_pathNodes[nodeId].numLinks; i++){ + next = ConnectedNode(m_pathNodes[nodeId].firstLink + i); + if(!m_pathNodes[next].bBetweenLevels && + m_pathNodes[next].numLinks < 3) + MarkRoadsBetweenLevelsNodeAndNeighbours(next); + } +} + +void +CPathFind::MarkRoadsBetweenLevelsInArea(float x1, float x2, float y1, float y2, float z1, float z2) +{ + int i; + + for(i = 0; i < m_numPathNodes; i++){ + CVector pos = m_pathNodes[i].GetPosition(); + if(x1 < pos.x && pos.x < x2 && + y1 < pos.y && pos.y < y2 && + z1 < pos.z && pos.z < z2) + MarkRoadsBetweenLevelsNodeAndNeighbours(i); + } +} + +void +CPathFind::PedMarkRoadsBetweenLevelsInArea(float x1, float x2, float y1, float y2, float z1, float z2) +{ + int i; + + for(i = m_numCarPathNodes; i < m_numPathNodes; i++){ + CVector pos = m_pathNodes[i].GetPosition(); + if(x1 < pos.x && pos.x < x2 && + y1 < pos.y && pos.y < y2 && + z1 < pos.z && pos.z < z2) + MarkRoadsBetweenLevelsNodeAndNeighbours(i); + } +} + +int32 +CPathFind::FindNodeClosestToCoors(CVector coors, uint8 type, float distLimit, bool ignoreDisabled, bool ignoreBetweenLevels) +{ + int i; + int firstNode, lastNode; + float dist; + float closestDist = 10000.0f; + int closestNode = 0; + + switch(type){ + case PATH_CAR: + firstNode = 0; + lastNode = m_numCarPathNodes; + break; + case PATH_PED: + firstNode = m_numCarPathNodes; + lastNode = m_numPathNodes; + break; + } + + for(i = firstNode; i < lastNode; i++){ + if(ignoreDisabled && m_pathNodes[i].bDisabled) continue; + if(ignoreBetweenLevels && m_pathNodes[i].bBetweenLevels) continue; + switch(m_pathNodes[i].unkBits){ + case 1: + case 2: + dist = Abs(m_pathNodes[i].GetX() - coors.x) + + Abs(m_pathNodes[i].GetY() - coors.y) + + 3.0f*Abs(m_pathNodes[i].GetZ() - coors.z); + if(dist < closestDist){ + closestDist = dist; + closestNode = i; + } + break; + } + } + return closestDist < distLimit ? closestNode : -1; +} + +int32 +CPathFind::FindNodeClosestToCoorsFavourDirection(CVector coors, uint8 type, float dirX, float dirY) +{ + int i; + int firstNode, lastNode; + float dist, dX, dY; + NormalizeXY(dirX, dirY); + float closestDist = 10000.0f; + int closestNode = 0; + + switch(type){ + case PATH_CAR: + firstNode = 0; + lastNode = m_numCarPathNodes; + break; + case PATH_PED: + firstNode = m_numCarPathNodes; + lastNode = m_numPathNodes; + break; + } + + for(i = firstNode; i < lastNode; i++){ + switch(m_pathNodes[i].unkBits){ + case 1: + case 2: + dX = m_pathNodes[i].GetX() - coors.x; + dY = m_pathNodes[i].GetY() - coors.y; + dist = Abs(dX) + Abs(dY) + + 3.0f*Abs(m_pathNodes[i].GetZ() - coors.z); + if(dist < closestDist){ + NormalizeXY(dX, dY); + dist -= (dX*dirX + dY*dirY - 1.0f)*20.0f; + if(dist < closestDist){ + closestDist = dist; + closestNode = i; + } + } + break; + } + } + return closestNode; +} + +float +CPathFind::FindNodeOrientationForCarPlacement(int32 nodeId) +{ + if(m_pathNodes[nodeId].numLinks == 0) + return 0.0f; + CVector dir = m_pathNodes[ConnectedNode(m_pathNodes[nodeId].firstLink)].GetPosition() - m_pathNodes[nodeId].GetPosition(); + dir.z = 0.0f; + dir.Normalise(); + return RADTODEG(dir.Heading()); +} + +float +CPathFind::FindNodeOrientationForCarPlacementFacingDestination(int32 nodeId, float x, float y, bool towards) +{ + int i; + + CVector targetDir(x - m_pathNodes[nodeId].GetX(), y - m_pathNodes[nodeId].GetY(), 0.0f); + targetDir.Normalise(); + CVector dir; + + if(m_pathNodes[nodeId].numLinks == 0) + return 0.0f; + + int bestNode = ConnectedNode(m_pathNodes[nodeId].firstLink); +#ifdef FIX_BUGS + float bestDot = towards ? -2.0f : 2.0f; +#else + int bestDot = towards ? -2 : 2; // why int? +#endif + + for(i = 0; i < m_pathNodes[nodeId].numLinks; i++){ + dir = m_pathNodes[ConnectedNode(m_pathNodes[nodeId].firstLink + i)].GetPosition() - m_pathNodes[nodeId].GetPosition(); + dir.z = 0.0f; + dir.Normalise(); + float angle = DotProduct2D(dir, targetDir); + if(towards){ + if(angle > bestDot){ + bestDot = angle; + bestNode = ConnectedNode(m_pathNodes[nodeId].firstLink + i); + } + }else{ + if(angle < bestDot){ + bestDot = angle; + bestNode = ConnectedNode(m_pathNodes[nodeId].firstLink + i); + } + } + } + + dir = m_pathNodes[bestNode].GetPosition() - m_pathNodes[nodeId].GetPosition(); + dir.z = 0.0f; + dir.Normalise(); + return RADTODEG(dir.Heading()); +} + +bool +CPathFind::NewGenerateCarCreationCoors(float x, float y, float dirX, float dirY, float spawnDist, float angleLimit, bool forward, CVector *pPosition, int32 *pNode1, int32 *pNode2, float *pPositionBetweenNodes, bool ignoreDisabled) +{ + int i, j; + int node1, node2; + float dist1, dist2, d1, d2; + + if(m_numCarPathNodes == 0) + return false; + + for(i = 0; i < 500; i++){ + node1 = (CGeneral::GetRandomNumber()>>3) % m_numCarPathNodes; + if(m_pathNodes[node1].bDisabled && !ignoreDisabled) + continue; + dist1 = Distance2D(m_pathNodes[node1].GetPosition(), x, y); + if(dist1 < spawnDist + 60.0f){ + d1 = dist1 - spawnDist; + for(j = 0; j < m_pathNodes[node1].numLinks; j++){ + node2 = ConnectedNode(m_pathNodes[node1].firstLink + j); + if(m_pathNodes[node2].bDisabled && !ignoreDisabled) + continue; + dist2 = Distance2D(m_pathNodes[node2].GetPosition(), x, y); + d2 = dist2 - spawnDist; + if(d1*d2 < 0.0f){ + // nodes are on different sides of spawn distance + float f2 = Abs(d1)/(Abs(d1) + Abs(d2)); + float f1 = 1.0f - f2; + *pPositionBetweenNodes = f2; + CVector pos = m_pathNodes[node1].GetPosition()*f1 + m_pathNodes[node2].GetPosition()*f2; + CVector2D dist2d(pos.x - x, pos.y - y); + dist2d.Normalise(); // done manually in the game + float dot = DotProduct2D(dist2d, CVector2D(dirX, dirY)); + if(forward){ + if(dot > angleLimit){ + *pNode1 = node1; + *pNode2 = node2; + *pPosition = pos; + return true; + } + }else{ + if(dot <= angleLimit){ + *pNode1 = node1; + *pNode2 = node2; + *pPosition = pos; + return true; + } + } + } + } + } + } + return false; +} + +bool +CPathFind::GeneratePedCreationCoors(float x, float y, float minDist, float maxDist, float minDistOffScreen, float maxDistOffScreen, CVector *pPosition, int32 *pNode1, int32 *pNode2, float *pPositionBetweenNodes, CMatrix *camMatrix) +{ + int i; + int node1, node2; + + if(m_numPedPathNodes == 0) + return false; + + for(i = 0; i < 400; i++){ + node1 = m_numCarPathNodes + CGeneral::GetRandomNumber() % m_numPedPathNodes; + if(DistanceSqr2D(m_pathNodes[node1].GetPosition(), x, y) < sq(maxDist+30.0f)){ + if(m_pathNodes[node1].numLinks == 0) + continue; + int link = m_pathNodes[node1].firstLink + CGeneral::GetRandomNumber() % m_pathNodes[node1].numLinks; + if(ConnectionCrossesRoad(link)) + continue; + node2 = ConnectedNode(link); + if(m_pathNodes[node1].bDisabled || m_pathNodes[node2].bDisabled) + continue; + + float f2 = (CGeneral::GetRandomNumber()&0xFF)/256.0f; + float f1 = 1.0f - f2; + *pPositionBetweenNodes = f2; + CVector pos = m_pathNodes[node1].GetPosition()*f1 + m_pathNodes[node2].GetPosition()*f2; + if(Distance2D(pos, x, y) < maxDist+20.0f){ + pos.x += ((CGeneral::GetRandomNumber()&0xFF)-128)*0.01f; + pos.y += ((CGeneral::GetRandomNumber()&0xFF)-128)*0.01f; + float dist = Distance2D(pos, x, y); + + bool visible; + if(camMatrix) + visible = TheCamera.IsSphereVisible(pos, 2.0f, camMatrix); + else + visible = TheCamera.IsSphereVisible(pos, 2.0f); + if(!visible){ + minDist = minDistOffScreen; + maxDist = maxDistOffScreen; + } + if(minDist < dist && dist < maxDist){ + *pNode1 = node1; + *pNode2 = node2; + *pPosition = pos; + + bool found; + float groundZ = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z+2.0f, &found); + if(!found) + return false; + if(Abs(groundZ - pos.z) > 3.0f) + return false; + pPosition->z = groundZ; + return true; + } + } + } + } + return false; +} + +CTreadable* +CPathFind::FindRoadObjectClosestToCoors(CVector coors, uint8 type) +{ + int i, j, k; + int node1, node2; + CTreadable *closestMapObj = nil; + float closestDist = 10000.0f; + + for(i = 0; i < m_numMapObjects; i++){ + CTreadable *mapObj = m_mapObjects[i]; + if(mapObj->m_nodeIndices[type][0] < 0) + continue; + CVector vDist = mapObj->GetPosition() - coors; + float fDist = Abs(vDist.x) + Abs(vDist.y) + Abs(vDist.z); + if(fDist < 200.0f || fDist < closestDist) + for(j = 0; j < 12; j++){ + node1 = mapObj->m_nodeIndices[type][j]; + if(node1 < 0) + break; + // FIX: game uses ThePaths here explicitly + for(k = 0; k < m_pathNodes[node1].numLinks; k++){ + node2 = ConnectedNode(m_pathNodes[node1].firstLink + k); + float lineDist = CCollision::DistToLine(&m_pathNodes[node1].GetPosition(), &m_pathNodes[node2].GetPosition(), &coors); + if(lineDist < closestDist){ + closestDist = lineDist; + if((coors - m_pathNodes[node1].GetPosition()).MagnitudeSqr() < (coors - m_pathNodes[node2].GetPosition()).MagnitudeSqr()) + closestMapObj = m_mapObjects[m_pathNodes[node1].objectIndex]; + else + closestMapObj = m_mapObjects[m_pathNodes[node2].objectIndex]; + } + } + } + } + return closestMapObj; +} + +void +CPathFind::FindNextNodeWandering(uint8 type, CVector coors, CPathNode **lastNode, CPathNode **nextNode, uint8 curDir, uint8 *nextDir) +{ + int i; + CPathNode *node; + + if(lastNode == nil || (node = *lastNode) == nil || (coors - (*lastNode)->GetPosition()).MagnitudeSqr() > 7.0f){ + // need to find the node we're coming from + node = nil; + CTreadable *obj = FindRoadObjectClosestToCoors(coors, type); + float nodeDist = 1000000000.0f; + for(i = 0; i < 12; i++){ + if(obj->m_nodeIndices[type][i] < 0) + break; + float dist = (coors - m_pathNodes[obj->m_nodeIndices[type][i]].GetPosition()).MagnitudeSqr(); + if(dist < nodeDist){ + nodeDist = dist; + node = &m_pathNodes[obj->m_nodeIndices[type][i]]; + } + } + } + + CVector2D vCurDir(Sin(curDir*PI/4.0f), Cos(curDir * PI / 4.0f)); + *nextNode = 0; + float bestDot = -999999.0f; + for(i = 0; i < node->numLinks; i++){ + int next = ConnectedNode(node->firstLink+i); + if(!node->bDisabled && m_pathNodes[next].bDisabled) + continue; + CVector pedCoors = coors; + pedCoors.z += 1.0f; + CVector nodeCoors = m_pathNodes[next].GetPosition(); + nodeCoors.z += 1.0f; + if(!CWorld::GetIsLineOfSightClear(pedCoors, nodeCoors, true, false, false, false, false, false)) + continue; + CVector2D nodeDir = m_pathNodes[next].GetPosition() - node->GetPosition(); + nodeDir.Normalise(); + float dot = DotProduct2D(nodeDir, vCurDir); + if(dot >= bestDot){ + *nextNode = &m_pathNodes[next]; + bestDot = dot; + + // direction is 0, 2, 4, 6 for north, east, south, west + // this could be done simpler... + if(nodeDir.x < 0.0f){ + if(2.0f*Abs(nodeDir.y) < -nodeDir.x) + *nextDir = 6; // west + else if(-2.0f*nodeDir.x < nodeDir.y) + *nextDir = 0; // north + else if(2.0f*nodeDir.x > nodeDir.y) + *nextDir = 4; // south + else if(nodeDir.y > 0.0f) + *nextDir = 7; // north west + else + *nextDir = 5; // south west` + }else{ + if(2.0f*Abs(nodeDir.y) < nodeDir.x) + *nextDir = 2; // east + else if(2.0f*nodeDir.x < nodeDir.y) + *nextDir = 0; // north + else if(-2.0f*nodeDir.x > nodeDir.y) + *nextDir = 4; // south + else if(nodeDir.y > 0.0f) + *nextDir = 1; // north east + else + *nextDir = 3; // south east` + } + } + } + if(*nextNode == nil){ + *nextDir = 0; + *nextNode = node; + } +} + +static CPathNode *apNodesToBeCleared[4995]; + +void +CPathFind::DoPathSearch(uint8 type, CVector start, int32 startNodeId, CVector target, CPathNode **nodes, int16 *pNumNodes, int16 maxNumNodes, CVehicle *vehicle, float *pDist, float distLimit, int32 targetNodeId) +{ + int i, j; + + // Find target + if(targetNodeId < 0) + targetNodeId = FindNodeClosestToCoors(target, type, distLimit); + if(targetNodeId < 0) { + *pNumNodes = 0; + if(pDist) *pDist = 100000.0f; + return; + } + + // Find start + int numPathsToTry; + CTreadable *startObj; + if(startNodeId < 0){ + if(vehicle == nil || (startObj = vehicle->m_treadable[type]) == nil) + startObj = FindRoadObjectClosestToCoors(start, type); + numPathsToTry = 0; + for(i = 0; i < 12; i++){ + if(startObj->m_nodeIndices[type][i] < 0) + break; + if(m_pathNodes[startObj->m_nodeIndices[type][i]].group == m_pathNodes[targetNodeId].group) + numPathsToTry++; + } + }else{ + numPathsToTry = 1; + startObj = m_mapObjects[m_pathNodes[startNodeId].objectIndex]; + } + if(numPathsToTry == 0) { + *pNumNodes = 0; + if(pDist) *pDist = 100000.0f; + return; + } + + if(startNodeId < 0){ + // why only check node 0? + if(m_pathNodes[startObj->m_nodeIndices[type][0]].group != + m_pathNodes[targetNodeId].group) { + *pNumNodes = 0; + if(pDist) *pDist = 100000.0f; + return; + } + }else{ + if(m_pathNodes[startNodeId].group != m_pathNodes[targetNodeId].group) { + *pNumNodes = 0; + if(pDist) *pDist = 100000.0f; + return; + } + } + + for(i = 0; i < ARRAY_SIZE(m_searchNodes); i++) + m_searchNodes[i].SetNext(nil); + AddNodeToList(&m_pathNodes[targetNodeId], 0); + int numNodesToBeCleared = 0; + apNodesToBeCleared[numNodesToBeCleared++] = &m_pathNodes[targetNodeId]; + + // Dijkstra's algorithm + // Find distances + int numPathsFound = 0; + if(startNodeId < 0 && m_mapObjects[m_pathNodes[targetNodeId].objectIndex] == startObj) + numPathsFound++; + for(i = 0; numPathsFound < numPathsToTry; i = (i+1) & 0x1FF){ + CPathNode *node; + for(node = m_searchNodes[i].GetNext(); node; node = node->GetNext()){ + if(m_mapObjects[node->objectIndex] == startObj && + (startNodeId < 0 || node == &m_pathNodes[startNodeId])) + numPathsFound++; + + for(j = 0; j < node->numLinks; j++){ + int next = ConnectedNode(node->firstLink + j); + int dist = node->distance + m_distances[node->firstLink + j]; + if(dist < m_pathNodes[next].distance){ + if(m_pathNodes[next].distance != MAX_DIST) + RemoveNodeFromList(&m_pathNodes[next]); + if(m_pathNodes[next].distance == MAX_DIST) + apNodesToBeCleared[numNodesToBeCleared++] = &m_pathNodes[next]; + AddNodeToList(&m_pathNodes[next], dist); + } + } + + RemoveNodeFromList(node); + } + } + + // Find out whence to start tracing back + CPathNode *curNode; + if(startNodeId < 0){ + int minDist = MAX_DIST; + *pNumNodes = 1; + for(i = 0; i < 12; i++){ + if(startObj->m_nodeIndices[type][i] < 0) + break; + int dist = (m_pathNodes[startObj->m_nodeIndices[type][i]].GetPosition() - start).Magnitude(); + if(m_pathNodes[startObj->m_nodeIndices[type][i]].distance + dist < minDist){ + minDist = m_pathNodes[startObj->m_nodeIndices[type][i]].distance + dist; + curNode = &m_pathNodes[startObj->m_nodeIndices[type][i]]; + } + } + if(maxNumNodes == 0){ + *pNumNodes = 0; + }else{ + nodes[0] = curNode; + *pNumNodes = 1; + } + if(pDist) + *pDist = minDist; + }else + { + curNode = &m_pathNodes[startNodeId]; + *pNumNodes = 0; + if(pDist) + *pDist = m_pathNodes[startNodeId].distance; + } + + // Trace back to target and update list of nodes + while(*pNumNodes < maxNumNodes && curNode != &m_pathNodes[targetNodeId]) + for(i = 0; i < curNode->numLinks; i++){ + int next = ConnectedNode(curNode->firstLink + i); + if(curNode->distance - m_distances[curNode->firstLink + i] == m_pathNodes[next].distance){ + curNode = &m_pathNodes[next]; + nodes[(*pNumNodes)++] = curNode; + i = 29030; // could have used a break... + } + } + + for(i = 0; i < numNodesToBeCleared; i++) + apNodesToBeCleared[i]->distance = MAX_DIST; + return; +} + +static CPathNode *pNodeList[32]; +static int16 DummyResult; +static int16 DummyResult2; + +bool +CPathFind::TestCoorsCloseness(CVector target, uint8 type, CVector start) +{ + float dist; + + if(type == PATH_CAR) + DoPathSearch(type, start, -1, target, pNodeList, &DummyResult, 32, nil, &dist, 999999.88f, -1); + else + DoPathSearch(type, start, -1, target, nil, &DummyResult2, 0, nil, &dist, 50.0f, -1); +#ifdef FIX_BUGS + // dist has GenerationDistMultiplier as a factor, so our reference dist should have it too + if(type == PATH_CAR) + return dist < 160.0f*TheCamera.GenerationDistMultiplier; + else + return dist < 100.0f*TheCamera.GenerationDistMultiplier; +#else + if(type == PATH_CAR) + return dist < 160.0f; + else + return dist < 100.0f; +#endif +} + +void +CPathFind::Save(uint8 *buf, uint32 *size) +{ + int i; + int n = m_numPathNodes/8 + 1; + + *size = 2*n; + + for(i = 0; i < m_numPathNodes; i++) + if(m_pathNodes[i].bDisabled) + buf[i/8] |= 1 << i%8; + else + buf[i/8] &= ~(1 << i%8); + + for(i = 0; i < m_numPathNodes; i++) + if(m_pathNodes[i].bBetweenLevels) + buf[i/8 + n] |= 1 << i%8; + else + buf[i/8 + n] &= ~(1 << i%8); +} + +void +CPathFind::Load(uint8 *buf, uint32 size) +{ + int i; + int n = m_numPathNodes/8 + 1; + + for(i = 0; i < m_numPathNodes; i++) + if(buf[i/8] & (1 << i%8)) + m_pathNodes[i].bDisabled = true; + else + m_pathNodes[i].bDisabled = false; + + for(i = 0; i < m_numPathNodes; i++) + if(buf[i/8 + n] & (1 << i%8)) + m_pathNodes[i].bBetweenLevels = true; + else + m_pathNodes[i].bBetweenLevels = false; +} + +void +CPathFind::DisplayPathData(void) +{ + // Not the function from mobile but my own! + + int i, j, k; + // Draw 50 units around camera + CVector pos = TheCamera.GetPosition(); + const float maxDist = 50.0f; + + // Render car path nodes + if(gbShowCarPaths) + for(i = 0; i < m_numCarPathNodes; i++){ + if((m_pathNodes[i].GetPosition() - pos).MagnitudeSqr() > SQR(maxDist)) + continue; + + CVector n1 = m_pathNodes[i].GetPosition(); + n1.z += 0.3f; + + // Draw node itself + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n1.x, n1.y, n1.z + 1.0f, + 0xFFFFFFFF, 0xFFFFFFFF); + + for(j = 0; j < m_pathNodes[i].numLinks; j++){ + k = ConnectedNode(m_pathNodes[i].firstLink + j); + CVector n2 = m_pathNodes[k].GetPosition(); + n2.z += 0.3f; + // Draw links to neighbours + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n2.x, n2.y, n2.z, + 0xFFFFFFFF, 0xFFFFFFFF); + } + } + + // Render car path nodes + if(gbShowCarPathsLinks) + for(i = 0; i < m_numCarPathLinks; i++){ + CVector2D n1_2d = m_carPathLinks[i].GetPosition(); + if((n1_2d - pos).MagnitudeSqr() > SQR(maxDist)) + continue; + + int ni = m_carPathLinks[i].pathNodeIndex; + CVector pn1 = m_pathNodes[ni].GetPosition(); + pn1.z += 0.3f; + CVector n1(n1_2d.x, n1_2d.y, pn1.z); + n1.z += 0.3f; + + // Draw car node itself + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n1.x, n1.y, n1.z + 1.0f, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z + 0.5f, + n1.x+m_carPathLinks[i].GetDirX(), n1.y+m_carPathLinks[i].GetDirY(), n1.z + 0.5f, + 0xFFFFFFFF, 0xFFFFFFFF); + + // Draw connection to car path node + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + pn1.x, pn1.y, pn1.z, + 0xFF0000FF, 0xFFFFFFFF); + + // traffic light type + uint32 col = 0xFF; + if((m_carPathLinks[i].trafficLightType&0x7F) == 1) + col += 0xFF000000; + if((m_carPathLinks[i].trafficLightType&0x7F) == 2) + col += 0x00FF0000; + if(m_carPathLinks[i].trafficLightType & 0x80) + col += 0x0000FF00; + CLines::RenderLineWithClipping(n1.x+0.2f, n1.y, n1.z, + n1.x+0.2f, n1.y, n1.z + 1.0f, + col, col); + + for(j = 0; j < m_pathNodes[ni].numLinks; j++){ + k = m_carPathConnections[m_pathNodes[ni].firstLink + j]; + CVector2D n2_2d = m_carPathLinks[k].GetPosition(); + int nk = m_carPathLinks[k].pathNodeIndex; + CVector pn2 = m_pathNodes[nk].GetPosition(); + pn2.z += 0.3f; + CVector n2(n2_2d.x, n2_2d.y, pn2.z); + n2.z += 0.3f; + + // Draw links to neighbours + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n2.x, n2.y, n2.z, + 0xFF00FFFF, 0xFF00FFFF); + } + } + + // Render ped path nodes + if(gbShowPedPaths) + for(i = m_numCarPathNodes; i < m_numPathNodes; i++){ + if((m_pathNodes[i].GetPosition() - pos).MagnitudeSqr() > SQR(maxDist)) + continue; + + CVector n1 = m_pathNodes[i].GetPosition(); + n1.z += 0.3f; + + // Draw node itself + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n1.x, n1.y, n1.z + 1.0f, + 0xFFFFFFFF, 0xFFFFFFFF); + + for(j = 0; j < m_pathNodes[i].numLinks; j++){ + k = ConnectedNode(m_pathNodes[i].firstLink + j); + CVector n2 = m_pathNodes[k].GetPosition(); + n2.z += 0.3f; + // Draw links to neighbours + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n2.x, n2.y, n2.z, + 0xFFFFFFFF, 0xFFFFFFFF); + + // Draw connection flags + CVector mid = (n1+n2)/2.0f; + uint32 col = 0xFF; + if(ConnectionCrossesRoad(m_pathNodes[i].firstLink + j)) + col += 0x00FF0000; + if(ConnectionHasTrafficLight(m_pathNodes[i].firstLink + j)) + col += 0xFF000000; + CLines::RenderLineWithClipping(mid.x, mid.y, mid.z, + mid.x, mid.y, mid.z + 1.0f, + col, col); + } + } +} diff --git a/src/control/PathFind.h b/src/control/PathFind.h new file mode 100644 index 0000000..bbfdf7b --- /dev/null +++ b/src/control/PathFind.h @@ -0,0 +1,232 @@ +#pragma once + +#include "Treadable.h" + +class CVehicle; +class CPtrList; + +enum +{ + NodeTypeExtern = 1, + NodeTypeIntern = 2, + + UseInRoadBlock = 1, + ObjectEastWest = 2, +}; + +enum +{ + PATH_CAR = 0, + PATH_PED = 1, +}; + +enum +{ + SWITCH_OFF = 0, + SWITCH_ON = 1, +}; + +enum +{ + ROUTE_ADD_BLOCKADE = 0, + ROUTE_NO_BLOCKADE = 1 +}; + +struct CPedPathNode +{ + bool bBlockade; + uint8 nodeIdX; + uint8 nodeIdY; + int16 id; + CPedPathNode* prev; + CPedPathNode* next; +}; + +VALIDATE_SIZE(CPedPathNode, 0x10); + +class CPedPath { +public: + static bool CalcPedRoute(int8 pathType, CVector position, CVector destination, CVector *pointPoses, int16 *pointsFound, int16 maxPoints); + static void AddNodeToPathList(CPedPathNode *pNodeToAdd, int16 id, CPedPathNode *pNodeList); + static void RemoveNodeFromList(CPedPathNode *pNode); + static void AddNodeToList(CPedPathNode *pNode, int16 index, CPedPathNode *pList); + static void AddBlockade(CEntity *pEntity, CPedPathNode(*pathNodes)[40], CVector *pPosition); + static void AddBlockadeSectorList(CPtrList& list, CPedPathNode(*pathNodes)[40], CVector *pPosition); +}; + +struct CPathNode +{ + CVector pos; + CPathNode *prev; + CPathNode *next; + int16 distance; // in path search + int16 objectIndex; + int16 firstLink; + uint8 numLinks; + + uint8 unkBits : 2; + uint8 bDeadEnd : 1; + uint8 bDisabled : 1; + uint8 bBetweenLevels : 1; + + int8 group; + + CVector &GetPosition(void) { return pos; } + void SetPosition(const CVector &p) { pos = p; } + float GetX(void) { return pos.x; } + float GetY(void) { return pos.y; } + float GetZ(void) { return pos.z; } + + CPathNode *GetPrev(void) { return prev; } + CPathNode *GetNext(void) { return next; } + void SetPrev(CPathNode *node) { prev = node; } + void SetNext(CPathNode *node) { next = node; } +}; + +union CConnectionFlags +{ + uint8 flags; + struct { + uint8 bCrossesRoad : 1; + uint8 bTrafficLight : 1; + }; +}; + +struct CCarPathLink +{ + CVector2D pos; + CVector2D dir; + int16 pathNodeIndex; + int8 numLeftLanes; + int8 numRightLanes; + uint8 trafficLightType; + + uint8 bBridgeLights : 1; + // more? + + CVector2D &GetPosition(void) { return pos; } + CVector2D &GetDirection(void) { return dir; } + float GetX(void) { return pos.x; } + float GetY(void) { return pos.y; } + float GetDirX(void) { return dir.x; } + float GetDirY(void) { return dir.y; } + + float OneWayLaneOffset() + { + if (numLeftLanes == 0) + return 0.5f - 0.5f * numRightLanes; + if (numRightLanes == 0) + return 0.5f - 0.5f * numLeftLanes; + return 0.5f; + } +}; + +// This is what we're reading from the files, only temporary +struct CPathInfoForObject +{ + int16 x; + int16 y; + int16 z; + int8 type; + int8 next; + int8 numLeftLanes; + int8 numRightLanes; + uint8 crossing : 1; +}; +extern CPathInfoForObject *InfoForTileCars; +extern CPathInfoForObject *InfoForTilePeds; + +struct CTempNode +{ + CVector pos; + float dirX; + float dirY; + int16 link1; + int16 link2; + int8 numLeftLanes; + int8 numRightLanes; + int8 linkState; +}; + +struct CTempDetachedNode // unused +{ + uint8 foo[20]; +}; + +class CPathFind +{ +public: + CPathNode m_pathNodes[NUM_PATHNODES]; + CCarPathLink m_carPathLinks[NUM_CARPATHLINKS]; + CTreadable *m_mapObjects[NUM_MAPOBJECTS]; + uint8 m_objectFlags[NUM_MAPOBJECTS]; + int16 m_connections[NUM_PATHCONNECTIONS]; + int16 m_distances[NUM_PATHCONNECTIONS]; + CConnectionFlags m_connectionFlags[NUM_PATHCONNECTIONS]; + int16 m_carPathConnections[NUM_PATHCONNECTIONS]; + + int32 m_numPathNodes; + int32 m_numCarPathNodes; + int32 m_numPedPathNodes; + int16 m_numMapObjects; + int16 m_numConnections; + int32 m_numCarPathLinks; + int32 unk; + uint8 m_numGroups[2]; + CPathNode m_searchNodes[512]; + + void Init(void); + void AllocatePathFindInfoMem(int16 numPathGroups); + void RegisterMapObject(CTreadable *mapObject); + void StoreNodeInfoPed(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, int16 width, bool crossing); + void StoreNodeInfoCar(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, int16 width, int8 numLeft, int8 numRight); + void CalcNodeCoors(int16 x, int16 y, int16 z, int32 id, CVector *out); + bool LoadPathFindData(void); + void PreparePathData(void); + void CountFloodFillGroups(uint8 type); + void PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo, + float maxdist, CTempDetachedNode *detachednodes, int32 numDetached); + + bool IsPathObject(int id) { return id < PATHNODESIZE && (InfoForTileCars[id*12].type != 0 || InfoForTilePeds[id*12].type != 0); } + + float CalcRoadDensity(float x, float y); + bool TestForPedTrafficLight(CPathNode *n1, CPathNode *n2); + bool TestCrossesRoad(CPathNode *n1, CPathNode *n2); + void AddNodeToList(CPathNode *node, int32 listId); + void RemoveNodeFromList(CPathNode *node); + void RemoveBadStartNode(CVector pos, CPathNode **nodes, int16 *n); + void SetLinksBridgeLights(float, float, float, float, bool); + void SwitchOffNodeAndNeighbours(int32 nodeId, bool disable); + void SwitchRoadsOffInArea(float x1, float x2, float y1, float y2, float z1, float z2, bool disable); + void SwitchPedRoadsOffInArea(float x1, float x2, float y1, float y2, float z1, float z2, bool disable); + void SwitchRoadsInAngledArea(float x1, float y1, float z1, float x2, float y2, float z2, float length, uint8 type, uint8 enable); + void MarkRoadsBetweenLevelsNodeAndNeighbours(int32 nodeId); + void MarkRoadsBetweenLevelsInArea(float x1, float x2, float y1, float y2, float z1, float z2); + void PedMarkRoadsBetweenLevelsInArea(float x1, float x2, float y1, float y2, float z1, float z2); + int32 FindNodeClosestToCoors(CVector coors, uint8 type, float distLimit, bool ignoreDisabled = false, bool ignoreBetweenLevels = false); + int32 FindNodeClosestToCoorsFavourDirection(CVector coors, uint8 type, float dirX, float dirY); + float FindNodeOrientationForCarPlacement(int32 nodeId); + float FindNodeOrientationForCarPlacementFacingDestination(int32 nodeId, float x, float y, bool towards); + bool NewGenerateCarCreationCoors(float x, float y, float dirX, float dirY, float spawnDist, float angleLimit, bool forward, CVector *pPosition, int32 *pNode1, int32 *pNode2, float *pPositionBetweenNodes, bool ignoreDisabled = false); + bool GeneratePedCreationCoors(float x, float y, float minDist, float maxDist, float minDistOffScreen, float maxDistOffScreen, CVector *pPosition, int32 *pNode1, int32 *pNode2, float *pPositionBetweenNodes, CMatrix *camMatrix); + CTreadable *FindRoadObjectClosestToCoors(CVector coors, uint8 type); + void FindNextNodeWandering(uint8, CVector, CPathNode**, CPathNode**, uint8, uint8*); + void DoPathSearch(uint8 type, CVector start, int32 startNodeId, CVector target, CPathNode **nodes, int16 *numNodes, int16 maxNumNodes, CVehicle *vehicle, float *dist, float distLimit, int32 forcedTargetNode); + bool TestCoorsCloseness(CVector target, uint8 type, CVector start); + void Save(uint8 *buf, uint32 *size); + void Load(uint8 *buf, uint32 size); + uint16 ConnectedNode(int id) { return m_connections[id]; } + bool ConnectionCrossesRoad(int id) { return m_connectionFlags[id].bCrossesRoad; } + bool ConnectionHasTrafficLight(int id) { return m_connectionFlags[id].bTrafficLight; } + void ConnectionSetTrafficLight(int id) { m_connectionFlags[id].bTrafficLight = true; } + + void DisplayPathData(void); +}; + +VALIDATE_SIZE(CPathFind, 0x49bf4); + +extern CPathFind ThePaths; + +extern bool gbShowPedPaths; +extern bool gbShowCarPaths; +extern bool gbShowCarPathsLinks; diff --git a/src/control/Phones.cpp b/src/control/Phones.cpp new file mode 100644 index 0000000..7632cfa --- /dev/null +++ b/src/control/Phones.cpp @@ -0,0 +1,497 @@ +#include "common.h" + +#include "Phones.h" +#include "Pools.h" +#include "ModelIndices.h" +#include "Ped.h" +#include "Pad.h" +#include "Messages.h" +#include "Camera.h" +#include "World.h" +#include "General.h" +#include "AudioScriptObject.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "soundlist.h" +#include "SaveBuf.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif + +#ifdef COMPATIBLE_SAVES +#define PHONEINFO_SAVE_SIZE 0xA30 +#else +#define PHONEINFO_SAVE_SIZE sizeof(CPhoneInfo) +#endif + +CPhoneInfo gPhoneInfo; + +bool CPhoneInfo::bDisplayingPhoneMessage; // is phone picked up +uint32 CPhoneInfo::PhoneEnableControlsTimer; +CPhone *CPhoneInfo::pPhoneDisplayingMessages; +bool CPhoneInfo::bPickingUpPhone; +CPed *CPhoneInfo::pCallBackPed; // ped who picking up the phone (reset after pickup cb) + +/* + Entering phonebooth cutscene, showing messages and triggering these things + by checking coordinates happens in here - blue mission marker is cosmetic. + + Repeated message means after the script set the messages for a particular phone, + player can pick the phone again with the same messages appearing, + after 60 seconds of last phone pick-up. +*/ + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +CPed* crimeReporters[NUMPHONES] = {}; +bool +isPhoneAvailable(int m_phoneId) +{ + return crimeReporters[m_phoneId] == nil || !crimeReporters[m_phoneId]->IsPointerValid() || crimeReporters[m_phoneId]->m_objective > OBJECTIVE_WAIT_ON_FOOT || + (crimeReporters[m_phoneId]->m_nPedState != PED_MAKE_CALL && crimeReporters[m_phoneId]->m_nPedState != PED_FACE_PHONE && crimeReporters[m_phoneId]->m_nPedState != PED_SEEK_POS); +} +#endif + +void +CPhoneInfo::Update(void) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + CPlayerPed *player = FindPlayerPed(); + CPlayerInfo *playerInfo = &CWorld::Players[CWorld::PlayerInFocus]; + if (bDisplayingPhoneMessage && CTimer::GetTimeInMilliseconds() > PhoneEnableControlsTimer) { + playerInfo->MakePlayerSafe(false); + TheCamera.SetWideScreenOff(); + pPhoneDisplayingMessages = nil; + bDisplayingPhoneMessage = false; + CAnimBlendAssociation *talkAssoc = RpAnimBlendClumpGetAssociation(player->GetClump(), ANIM_STD_PHONE_TALK); + if (talkAssoc && talkAssoc->blendAmount > 0.5f) { + CAnimBlendAssociation *endAssoc = CAnimManager::BlendAnimation(player->GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_OUT, 8.0f); + endAssoc->flags &= ~ASSOC_DELETEFADEDOUT; + endAssoc->SetFinishCallback(PhonePutDownCB, player); + } else { + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_PHONE); + if (player->m_nPedState == PED_MAKE_CALL) + player->SetPedState(PED_IDLE); + } + } + bool notInCar; + CVector playerPos; + if (FindPlayerVehicle()) { + notInCar = false; + playerPos = FindPlayerVehicle()->GetPosition(); + } else { + notInCar = true; + playerPos = player->GetPosition(); + } + bool phoneRings = false; + bool scratchTheCabinet; + for(int phoneId = 0; phoneId < m_nScriptPhonesMax; phoneId++) { + if (m_aPhones[phoneId].m_visibleToCam) { + switch (m_aPhones[phoneId].m_nState) { + case PHONE_STATE_ONETIME_MESSAGE_SET: + case PHONE_STATE_REPEATED_MESSAGE_SET: + case PHONE_STATE_REPEATED_MESSAGE_SHOWN_ONCE: + if (bPickingUpPhone) { + scratchTheCabinet = false; + phoneRings = false; + } else { + scratchTheCabinet = (CTimer::GetTimeInMilliseconds() / 1880) % 2 == 1; + phoneRings = (CTimer::GetPreviousTimeInMilliseconds() / 1880) % 2 == 1; + } + if (scratchTheCabinet) { + m_aPhones[phoneId].m_pEntity->GetUp().z = (CGeneral::GetRandomNumber() % 1024) / 16000.0f + 1.0f; + if (!phoneRings) + PlayOneShotScriptObject(SCRIPT_SOUND_PAYPHONE_RINGING, m_aPhones[phoneId].m_pEntity->GetPosition()); + } else { + m_aPhones[phoneId].m_pEntity->GetUp().z = 1.0f; + } + m_aPhones[phoneId].m_pEntity->GetMatrix().UpdateRW(); + m_aPhones[phoneId].m_pEntity->UpdateRwFrame(); + if (notInCar && !bPickingUpPhone && player->IsPedInControl()) { + CVector2D distToPhone = playerPos - m_aPhones[phoneId].m_vecPos; + if (Abs(distToPhone.x) < 1.0f && Abs(distToPhone.y) < 1.0f) { + if (DotProduct2D(distToPhone, m_aPhones[phoneId].m_pEntity->GetForward()) / distToPhone.Magnitude() < -0.85f) { + CVector2D distToPhoneObj = playerPos - m_aPhones[phoneId].m_pEntity->GetPosition(); + float angleToFace = CGeneral::GetATanOfXY(distToPhoneObj.x, distToPhoneObj.y) + HALFPI; + if (angleToFace > TWOPI) + angleToFace = angleToFace - TWOPI; + player->m_fRotationCur = angleToFace; + player->m_fRotationDest = angleToFace; + player->SetHeading(angleToFace); + player->SetPedState(PED_MAKE_CALL); + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_PHONE); + TheCamera.SetWideScreenOn(); + playerInfo->MakePlayerSafe(true); + CAnimBlendAssociation *phonePickAssoc = CAnimManager::BlendAnimation(player->GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_IN, 4.0f); + phonePickAssoc->SetFinishCallback(PhonePickUpCB, &m_aPhones[phoneId]); + bPickingUpPhone = true; + pCallBackPed = player; + } + } + } + break; + case PHONE_STATE_REPEATED_MESSAGE_STARTED: + if (CTimer::GetTimeInMilliseconds() - m_aPhones[phoneId].m_repeatedMessagePickupStart > 60000) + m_aPhones[phoneId].m_nState = PHONE_STATE_REPEATED_MESSAGE_SHOWN_ONCE; + break; + case PHONE_STATE_9: + scratchTheCabinet = (CTimer::GetTimeInMilliseconds() / 1880) % 2 == 1; + phoneRings = (CTimer::GetPreviousTimeInMilliseconds() / 1880) % 2 == 1; + if (scratchTheCabinet) { + m_aPhones[phoneId].m_pEntity->GetUp().z = (CGeneral::GetRandomNumber() % 1024) / 16000.0f + 1.0f; + if (!phoneRings) + PlayOneShotScriptObject(SCRIPT_SOUND_PAYPHONE_RINGING, m_aPhones[phoneId].m_pEntity->GetPosition()); + } else { + m_aPhones[phoneId].m_pEntity->GetUp().z = 1.0f; + } + m_aPhones[phoneId].m_pEntity->GetMatrix().UpdateRW(); + m_aPhones[phoneId].m_pEntity->UpdateRwFrame(); + break; + default: + break; + } + if (CVector2D(TheCamera.GetPosition() - m_aPhones[phoneId].m_vecPos).MagnitudeSqr() > sq(100.0f)) + m_aPhones[phoneId].m_visibleToCam = false; + } else if (!((CTimer::GetFrameCounter() + m_aPhones[phoneId].m_pEntity->m_randomSeed) % 16)) { + if (CVector2D(TheCamera.GetPosition() - m_aPhones[phoneId].m_vecPos).MagnitudeSqr() < sq(60.0f)) + m_aPhones[phoneId].m_visibleToCam = true; + } + } +} + +int +CPhoneInfo::FindNearestFreePhone(CVector *pos) +{ + int nearestPhoneId = -1; + float nearestPhoneDist = 60.0f; + + for (int phoneId = 0; phoneId < m_nMax; phoneId++) { + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + if (isPhoneAvailable(phoneId)) +#else + if (gPhoneInfo.m_aPhones[phoneId].m_nState == PHONE_STATE_FREE) +#endif + { + float phoneDist = (m_aPhones[phoneId].m_vecPos - *pos).Magnitude2D(); + + if (phoneDist < nearestPhoneDist) { + nearestPhoneDist = phoneDist; + nearestPhoneId = phoneId; + } + } + } + return nearestPhoneId; +} + +bool +CPhoneInfo::PhoneAtThisPosition(CVector pos) +{ + for (int phoneId = 0; phoneId < m_nMax; phoneId++) { + if (pos.x == m_aPhones[phoneId].m_vecPos.x && pos.y == m_aPhones[phoneId].m_vecPos.y) + return true; + } + return false; +} + +bool +CPhoneInfo::HasMessageBeenDisplayed(int phoneId) +{ + if (bDisplayingPhoneMessage) + return false; + + int state = m_aPhones[phoneId].m_nState; + + return state == PHONE_STATE_REPEATED_MESSAGE_SHOWN_ONCE || + state == PHONE_STATE_ONETIME_MESSAGE_STARTED || + state == PHONE_STATE_REPEATED_MESSAGE_STARTED; +} + +bool +CPhoneInfo::IsMessageBeingDisplayed(int phoneId) +{ + return pPhoneDisplayingMessages == &m_aPhones[phoneId]; +} + +#ifdef COMPATIBLE_SAVES +static inline void +LoadPhone(CPhone &phone, uint8 *&buf) +{ + ReadSaveBuf(&phone.m_vecPos, buf); + SkipSaveBuf(buf, 6 * 4); + ReadSaveBuf(&phone.m_repeatedMessagePickupStart, buf); + uint32 tmp; + ReadSaveBuf(&tmp, buf); + phone.m_pEntity = (CEntity*)(uintptr)tmp; + ReadSaveBuf(&phone.m_nState, buf); + ReadSaveBuf(&phone.m_visibleToCam, buf); + SkipSaveBuf(buf, 3); +} +#endif + +void +CPhoneInfo::Load(uint8 *buf, uint32 size) +{ +INITSAVEBUF + int32 max, scriptPhonesMax; + ReadSaveBuf(&max, buf); + ReadSaveBuf(&scriptPhonesMax, buf); + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + m_nMax = Min(NUMPHONES, max); + m_nScriptPhonesMax = 0; + + bool ignoreOtherPhones = false; + + // We can do it without touching saves. We'll only load script phones, others are already loaded in Initialise + for (int i = 0; i < 50; i++) { + CPhone phoneToLoad; +#ifdef COMPATIBLE_SAVES + phoneToLoad.m_apMessages[0]=phoneToLoad.m_apMessages[1]=phoneToLoad.m_apMessages[2]=phoneToLoad.m_apMessages[3]=phoneToLoad.m_apMessages[4]=phoneToLoad.m_apMessages[5] = nil; + LoadPhone(phoneToLoad, buf); +#else + ReadSaveBuf(&phoneToLoad, buf); +#endif + + if (ignoreOtherPhones) + continue; + + if (i < scriptPhonesMax) { + if (i >= m_nMax) { + assert(0 && "Number of phones used by script exceeds the NUMPHONES or the stored phones in save file. Ignoring some phones"); + ignoreOtherPhones = true; + continue; + } + SwapPhone(phoneToLoad.m_vecPos.x, phoneToLoad.m_vecPos.y, i); + + m_aPhones[i] = phoneToLoad; + // It's saved as building pool index in save file, convert it to true entity + if (m_aPhones[i].m_pEntity) { + m_aPhones[i].m_pEntity = CPools::GetBuildingPool()->GetSlot((uintptr)m_aPhones[i].m_pEntity - 1); + } + } else + ignoreOtherPhones = true; + } +#else + m_nMax = max; + m_nScriptPhonesMax = scriptPhonesMax; + + for (int i = 0; i < NUMPHONES; i++) { +#ifdef COMPATIBLE_SAVES + LoadPhone(m_aPhones[i], buf); +#else + ReadSaveBuf(&m_aPhones[i], buf); +#endif + // It's saved as building pool index in save file, convert it to true entity + if (m_aPhones[i].m_pEntity) { + m_aPhones[i].m_pEntity = CPools::GetBuildingPool()->GetSlot((uintptr)m_aPhones[i].m_pEntity - 1); + } + } +#endif +VALIDATESAVEBUF(size) +} + +void +CPhoneInfo::SetPhoneMessage_JustOnce(int phoneId, wchar *msg1, wchar *msg2, wchar *msg3, wchar *msg4, wchar *msg5, wchar *msg6) +{ + // If there is at least one message, it should be msg1. + if (msg1) { + m_aPhones[phoneId].m_apMessages[0] = msg1; + m_aPhones[phoneId].m_apMessages[1] = msg2; + m_aPhones[phoneId].m_apMessages[2] = msg3; + m_aPhones[phoneId].m_apMessages[3] = msg4; + m_aPhones[phoneId].m_apMessages[4] = msg5; + m_aPhones[phoneId].m_apMessages[5] = msg6; + m_aPhones[phoneId].m_nState = PHONE_STATE_ONETIME_MESSAGE_SET; + } else { + m_aPhones[phoneId].m_nState = PHONE_STATE_MESSAGE_REMOVED; + } +} + +void +CPhoneInfo::SetPhoneMessage_Repeatedly(int phoneId, wchar *msg1, wchar *msg2, wchar *msg3, wchar *msg4, wchar *msg5, wchar *msg6) +{ + // If there is at least one message, it should be msg1. + if (msg1) { + m_aPhones[phoneId].m_apMessages[0] = msg1; + m_aPhones[phoneId].m_apMessages[1] = msg2; + m_aPhones[phoneId].m_apMessages[2] = msg3; + m_aPhones[phoneId].m_apMessages[3] = msg4; + m_aPhones[phoneId].m_apMessages[4] = msg5; + m_aPhones[phoneId].m_apMessages[5] = msg6; + m_aPhones[phoneId].m_nState = PHONE_STATE_REPEATED_MESSAGE_SET; + } else { + m_aPhones[phoneId].m_nState = PHONE_STATE_MESSAGE_REMOVED; + } +} + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +void +CPhoneInfo::SwapPhone(float xPos, float yPos, int into) +{ + // "into" should be in 0 - m_nScriptPhonesMax range + int nearestPhoneId = -1; + CVector pos(xPos, yPos, 0.0f); + float nearestPhoneDist = 1.0f; + + for (int phoneId = m_nScriptPhonesMax; phoneId < m_nMax; phoneId++) { + float phoneDistance = (m_aPhones[phoneId].m_vecPos - pos).Magnitude2D(); + if (phoneDistance < nearestPhoneDist) { + nearestPhoneDist = phoneDistance; + nearestPhoneId = phoneId; + } + } + m_aPhones[nearestPhoneId].m_nState = PHONE_STATE_MESSAGE_REMOVED; + + CPhone oldPhone = m_aPhones[into]; + m_aPhones[into] = m_aPhones[nearestPhoneId]; + m_aPhones[nearestPhoneId] = oldPhone; + m_nScriptPhonesMax++; +} +#endif + +int +CPhoneInfo::GrabPhone(float xPos, float yPos) +{ + // "Grab" doesn't mean picking up the phone, it means allocating some particular phone to + // whoever called the 024A opcode first with the position parameters closest to phone. + // Same phone won't be available on next run of this function. + + int nearestPhoneId = -1; + CVector pos(xPos, yPos, 0.0f); + float nearestPhoneDist = 100.0f; + + for (int phoneId = m_nScriptPhonesMax; phoneId < m_nMax; phoneId++) { + float phoneDistance = (m_aPhones[phoneId].m_vecPos - pos).Magnitude2D(); + if (phoneDistance < nearestPhoneDist) { + nearestPhoneDist = phoneDistance; + nearestPhoneId = phoneId; + } + } + m_aPhones[nearestPhoneId].m_nState = PHONE_STATE_MESSAGE_REMOVED; + + CPhone oldFirstPhone = m_aPhones[m_nScriptPhonesMax]; + m_aPhones[m_nScriptPhonesMax] = m_aPhones[nearestPhoneId]; + m_aPhones[nearestPhoneId] = oldFirstPhone; + m_nScriptPhonesMax++; + return m_nScriptPhonesMax - 1; +} + +void +CPhoneInfo::Initialise(void) +{ + CBuildingPool *pool = CPools::GetBuildingPool(); + pCallBackPed = nil; + bDisplayingPhoneMessage = false; + bPickingUpPhone = false; + pPhoneDisplayingMessages = nil; + m_nMax = 0; + m_nScriptPhonesMax = 0; + for (int i = pool->GetSize() - 1; i >= 0; i--) { + CBuilding *building = pool->GetSlot(i); + if (building) { + if (building->GetModelIndex() == MI_PHONEBOOTH1) { + assert(m_nMax < ARRAY_SIZE(m_aPhones) && "NUMPHONES should be increased"); + CPhone *maxPhone = &m_aPhones[m_nMax]; + maxPhone->m_nState = PHONE_STATE_FREE; + maxPhone->m_vecPos = building->GetPosition(); + maxPhone->m_pEntity = building; + m_nMax++; + } + } + } +} + +void +CPhoneInfo::Save(uint8 *buf, uint32 *size) +{ + *size = PHONEINFO_SAVE_SIZE; +INITSAVEBUF + WriteSaveBuf(buf, m_nMax); + WriteSaveBuf(buf, m_nScriptPhonesMax); +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + for (int phoneId = 0; phoneId < 50; phoneId++) { // We can do it without touching saves +#else + for (int phoneId = 0; phoneId < NUMPHONES; phoneId++) { +#endif +#ifdef COMPATIBLE_SAVES + WriteSaveBuf(buf, m_aPhones[phoneId].m_vecPos); + ZeroSaveBuf(buf, 6 * 4); + WriteSaveBuf(buf, m_aPhones[phoneId].m_repeatedMessagePickupStart); + // Convert entity pointer to building pool index while saving + int32 tmp = m_aPhones[phoneId].m_pEntity ? CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert((CBuilding*)m_aPhones[phoneId].m_pEntity) + 1 : 0; + WriteSaveBuf(buf, tmp); + WriteSaveBuf(buf, m_aPhones[phoneId].m_nState); + WriteSaveBuf(buf, m_aPhones[phoneId].m_visibleToCam); + ZeroSaveBuf(buf, 3); +#else + CPhone* phone = WriteSaveBuf(buf, m_aPhones[phoneId]); + + // Convert entity pointer to building pool index while saving + if (phone->m_pEntity) { + phone->m_pEntity = (CEntity*) (CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert((CBuilding*)phone->m_pEntity) + 1); + } +#endif + } +VALIDATESAVEBUF(*size) +} + +void +CPhoneInfo::Shutdown(void) +{ + m_nMax = 0; + m_nScriptPhonesMax = 0; +} + +void +PhonePutDownCB(CAnimBlendAssociation *assoc, void *arg) +{ + assoc->flags |= ASSOC_DELETEFADEDOUT; + assoc->blendDelta = -1000.0f; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_PHONE); + CPed *ped = (CPed*)arg; + + if (assoc->blendAmount > 0.5f) + ped->bUpdateAnimHeading = true; + + if (ped->m_nPedState == PED_MAKE_CALL) + ped->SetPedState(PED_IDLE); +} + +void +PhonePickUpCB(CAnimBlendAssociation *assoc, void *arg) +{ + CPhone *phone = (CPhone*)arg; + int messagesDisplayTime = 0; + + for(int i=0; i < 6; i++) { + wchar *msg = phone->m_apMessages[i]; + if (msg) { + CMessages::AddMessage(msg, 3000, 0); + messagesDisplayTime += 3000; + } + } + + CPhoneInfo::bPickingUpPhone = false; + CPhoneInfo::bDisplayingPhoneMessage = true; + CPhoneInfo::pPhoneDisplayingMessages = phone; + CPhoneInfo::PhoneEnableControlsTimer = CTimer::GetTimeInMilliseconds() + messagesDisplayTime; + + if (phone->m_nState == PHONE_STATE_ONETIME_MESSAGE_SET) { + phone->m_nState = PHONE_STATE_ONETIME_MESSAGE_STARTED; + } else { + phone->m_nState = PHONE_STATE_REPEATED_MESSAGE_STARTED; + phone->m_repeatedMessagePickupStart = CTimer::GetTimeInMilliseconds(); + } + + CPed *ped = CPhoneInfo::pCallBackPed; + ped->m_nMoveState = PEDMOVE_STILL; + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE, 8.0f); + + if (assoc->blendAmount > 0.5f && ped) + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_TALK, 8.0f); + + CPhoneInfo::pCallBackPed = nil; +} diff --git a/src/control/Phones.h b/src/control/Phones.h new file mode 100644 index 0000000..02c9a92 --- /dev/null +++ b/src/control/Phones.h @@ -0,0 +1,77 @@ +#pragma once + +#include "Physical.h" + +class CPed; +class CAnimBlendAssociation; + +enum PhoneState { + PHONE_STATE_FREE, + PHONE_STATE_REPORTING_CRIME, // CCivilianPed::ProcessControl sets it but unused + PHONE_STATE_2, + PHONE_STATE_MESSAGE_REMOVED, + PHONE_STATE_ONETIME_MESSAGE_SET, + PHONE_STATE_REPEATED_MESSAGE_SET, + PHONE_STATE_REPEATED_MESSAGE_SHOWN_ONCE, + PHONE_STATE_ONETIME_MESSAGE_STARTED, + PHONE_STATE_REPEATED_MESSAGE_STARTED, + PHONE_STATE_9 // just rings, picking being handled via script. most of the time game uses this +}; + +class CPhone +{ +public: + CVector m_vecPos; + wchar *m_apMessages[6]; + uint32 m_repeatedMessagePickupStart; + CEntity *m_pEntity; // stored as building pool index in save files + PhoneState m_nState; + bool m_visibleToCam; + + CPhone() { } + ~CPhone() { } +}; + +VALIDATE_SIZE(CPhone, 0x34); + +class CPhoneInfo { +public: + static bool bDisplayingPhoneMessage; + static uint32 PhoneEnableControlsTimer; + static CPhone *pPhoneDisplayingMessages; + static bool bPickingUpPhone; + static CPed *pCallBackPed; + + int32 m_nMax; + int32 m_nScriptPhonesMax; + CPhone m_aPhones[NUMPHONES]; + + CPhoneInfo() { } + ~CPhoneInfo() { } + + int FindNearestFreePhone(CVector*); + bool PhoneAtThisPosition(CVector); + bool HasMessageBeenDisplayed(int); + bool IsMessageBeingDisplayed(int); + void Load(uint8 *buf, uint32 size); + void Save(uint8 *buf, uint32 *size); + void SetPhoneMessage_JustOnce(int phoneId, wchar *msg1, wchar *msg2, wchar *msg3, wchar *msg4, wchar *msg5, wchar *msg6); + void SetPhoneMessage_Repeatedly(int phoneId, wchar *msg1, wchar *msg2, wchar *msg3, wchar *msg4, wchar *msg5, wchar *msg6); + int GrabPhone(float, float); + void Initialise(void); + void Shutdown(void); + void Update(void); +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + void SwapPhone(float xPos, float yPos, int into); +#endif +}; + +extern CPhoneInfo gPhoneInfo; + +void PhonePutDownCB(CAnimBlendAssociation *assoc, void *arg); +void PhonePickUpCB(CAnimBlendAssociation *assoc, void *arg); + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +extern CPed *crimeReporters[NUMPHONES]; +bool isPhoneAvailable(int); +#endif diff --git a/src/control/Pickups.cpp b/src/control/Pickups.cpp new file mode 100644 index 0000000..8d3472e --- /dev/null +++ b/src/control/Pickups.cpp @@ -0,0 +1,1568 @@ +#include "common.h" + +#include "main.h" + +#include "Camera.h" +#include "Coronas.h" +#include "Darkel.h" +#include "Entity.h" +#include "Explosion.h" +#include "Font.h" +#include "Garages.h" +#include "General.h" +#include "ModelIndices.h" +#include "Object.h" +#include "Pad.h" +#include "Pickups.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "Fire.h" +#include "PointLights.h" +#include "Pools.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif +#include "SaveBuf.h" +#include "Script.h" +#include "Shadows.h" +#include "SpecialFX.h" +#include "Sprite.h" +#include "Timer.h" +#include "WaterLevel.h" +#include "World.h" + +#ifdef COMPATIBLE_SAVES +#define PICKUPS_SAVE_SIZE 0x24C0 +#else +#define PICKUPS_SAVE_SIZE sizeof(aPickUps) +#endif + +CPickup CPickups::aPickUps[NUMPICKUPS]; +int16 CPickups::NumMessages; +int32 CPickups::aPickUpsCollected[NUMCOLLECTEDPICKUPS]; +int16 CPickups::CollectedPickUpIndex; + +// unused +bool CPickups::bPickUpcamActivated; +CVehicle *CPickups::pPlayerVehicle; +CVector CPickups::StaticCamCoors; +uint32 CPickups::StaticCamStartTime; + +tPickupMessage CPickups::aMessages[NUMPICKUPMESSAGES]; + +// 20 ?! Some Miami leftover? (Originally at 0x5ED8D4) +uint16 AmmoForWeapon[20] = { 0, 1, 45, 125, 25, 150, 300, 25, 5, 250, 5, 5, 0, 500, 0, 100, 0, 0, 0, 0 }; +uint16 AmmoForWeapon_OnStreet[20] = { 0, 1, 9, 25, 5, 30, 60, 5, 1, 50, 1, 1, 0, 200, 0, 100, 0, 0, 0, 0 }; +uint16 CostOfWeapon[20] = { 0, 10, 250, 800, 1500, 3000, 5000, 10000, 25000, 25000, 2000, 2000, 0, 50000, 0, 3000, 0, 0, 0, 0 }; + +uint8 aWeaponReds[] = { 255, 0, 128, 255, 255, 0, 255, 0, 128, 128, 255, 255, 128, 0, 255, 0 }; +uint8 aWeaponGreens[] = { 0, 255, 128, 255, 0, 255, 128, 255, 0, 255, 255, 0, 255, 0, 255, 0 }; +uint8 aWeaponBlues[] = { 0, 0, 255, 0, 255, 255, 0, 128, 255, 0, 255, 0, 128, 255, 0, 0 }; +float aWeaponScale[] = { 1.0f, 2.0f, 1.5f, 1.0f, 1.0f, 1.5f, 1.0f, 2.0f, 1.0f, 2.0f, 2.5f, 1.0f, 1.0f, 1.0f, 1.0f }; + + +inline void +CPickup::Remove() +{ + CWorld::Remove(m_pObject); + delete m_pObject; + + m_bRemoved = true; + m_pObject = nil; + m_eType = PICKUP_NONE; +} + +CObject * +CPickup::GiveUsAPickUpObject(int32 handle) +{ + CObject *object; + + if (handle >= 0) { + CPools::MakeSureSlotInObjectPoolIsEmpty(handle); + object = new (handle) CObject(m_eModelIndex, false); + } else + object = new CObject(m_eModelIndex, false); + + if (object == nil) return nil; + object->ObjectCreatedBy = MISSION_OBJECT; + object->SetPosition(m_vecPos); + object->SetOrientation(0.0f, 0.0f, -HALFPI); + object->GetMatrix().UpdateRW(); + object->UpdateRwFrame(); + + object->bAffectedByGravity = false; + object->bExplosionProof = true; + object->bUsesCollision = false; + object->bIsPickup = true; + + object->m_nBonusValue = m_eModelIndex == MI_PICKUP_BONUS ? m_nQuantity : 0; + + switch (m_eType) + { + case PICKUP_IN_SHOP: + object->bPickupObjWithMessage = true; + object->bOutOfStock = false; + break; + case PICKUP_ON_STREET: + case PICKUP_ONCE: + case PICKUP_ONCE_TIMEOUT: + case PICKUP_COLLECTABLE1: + case PICKUP_MONEY: + case PICKUP_MINE_INACTIVE: + case PICKUP_MINE_ARMED: + case PICKUP_NAUTICAL_MINE_INACTIVE: + case PICKUP_NAUTICAL_MINE_ARMED: + case PICKUP_FLOATINGPACKAGE: + case PICKUP_ON_STREET_SLOW: + object->bPickupObjWithMessage = false; + object->bOutOfStock = false; + break; + case PICKUP_IN_SHOP_OUT_OF_STOCK: + object->bPickupObjWithMessage = false; + object->bOutOfStock = true; + object->bRenderScorched = true; + break; + case PICKUP_FLOATINGPACKAGE_FLOATING: + default: + break; + } + return object; +} + +bool +CPickup::CanBePickedUp(CPlayerPed *player) +{ + bool cannotBePickedUp = + (m_pObject->GetModelIndex() == MI_PICKUP_BODYARMOUR && player->m_fArmour > 99.5f) + || (m_pObject->GetModelIndex() == MI_PICKUP_HEALTH && player->m_fHealth > 99.5f) + || (m_pObject->GetModelIndex() == MI_PICKUP_BRIBE && player->m_pWanted->GetWantedLevel() == 0) + || (m_pObject->GetModelIndex() == MI_PICKUP_KILLFRENZY && (CTheScripts::IsPlayerOnAMission() || CDarkel::FrenzyOnGoing() || !CGame::nastyGame)); + return !cannotBePickedUp; +} + +bool +CPickup::Update(CPlayerPed *player, CVehicle *vehicle, int playerId) +{ + float waterLevel; + bool result = false; + + if (m_bRemoved) { + if (CTimer::GetTimeInMilliseconds() > m_nTimer) { + // respawn pickup if we're far enough + float dist = (FindPlayerCoors().x - m_vecPos.x) * (FindPlayerCoors().x - m_vecPos.x) + (FindPlayerCoors().y - m_vecPos.y) * (FindPlayerCoors().y - m_vecPos.y); + if (dist > 100.0f || m_eType == PICKUP_IN_SHOP && dist > 2.4f) { + m_pObject = GiveUsAPickUpObject(-1); + if (m_pObject) { + CWorld::Add(m_pObject); + m_bRemoved = false; + } + } + } + return false; + } + + if (!m_pObject) return false; + + if (!IsMine()) { + // let's check if we touched the pickup + bool isPickupTouched = false; + if (m_pObject->GetModelIndex() == MI_PICKUP_BRIBE) { + if (vehicle != nil) { + if (vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) + isPickupTouched = true; + } + else { + if (Abs(player->GetPosition().z - m_pObject->GetPosition().z) < 2.0f) { + if ((player->GetPosition().x - m_pObject->GetPosition().x) * (player->GetPosition().x - m_pObject->GetPosition().x) + + (player->GetPosition().y - m_pObject->GetPosition().y) * (player->GetPosition().y - m_pObject->GetPosition().y) < 1.8f) + isPickupTouched = true; + } + } + } else if (m_pObject->GetModelIndex() == MI_PICKUP_CAMERA) { + if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) { + isPickupTouched = true; + } + } else if (vehicle == nil) { + if (Abs(player->GetPosition().z - m_pObject->GetPosition().z) < 2.0f) { + if ((player->GetPosition().x - m_pObject->GetPosition().x) * (player->GetPosition().x - m_pObject->GetPosition().x) + + (player->GetPosition().y - m_pObject->GetPosition().y) * (player->GetPosition().y - m_pObject->GetPosition().y) < 1.8f) + isPickupTouched = true; + } + } + + // if we didn't then we've got nothing to do + if (isPickupTouched && CanBePickedUp(player)) { + CPad::GetPad(0)->StartShake(120, 100); + switch (m_eType) + { + case PICKUP_IN_SHOP: + if (CWorld::Players[playerId].m_nMoney < CostOfWeapon[CPickups::WeaponForModel(m_pObject->GetModelIndex())]) { + CGarages::TriggerMessage("PU_MONY", -1, 6000, -1); + } else { + CWorld::Players[playerId].m_nMoney -= CostOfWeapon[CPickups::WeaponForModel(m_pObject->GetModelIndex())]; + if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) { + player->GiveWeapon(CPickups::WeaponForModel(m_pObject->GetModelIndex()), AmmoForWeapon[CPickups::WeaponForModel(m_pObject->GetModelIndex())]); + player->m_nSelectedWepSlot = player->GetWeaponSlot(CPickups::WeaponForModel(m_pObject->GetModelIndex())); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON_BOUGHT, m_pObject->GetModelIndex() - MI_GRENADE); + } + result = true; + CWorld::Remove(m_pObject); + delete m_pObject; + m_pObject = nil; + m_nTimer = CTimer::GetTimeInMilliseconds() + 5000; + m_bRemoved = true; + } + break; + case PICKUP_ON_STREET: + case PICKUP_ON_STREET_SLOW: + if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) { + if (CPickups::WeaponForModel(m_pObject->GetModelIndex())) { + player->GiveWeapon(CPickups::WeaponForModel(m_pObject->GetModelIndex()), m_nQuantity != 0 ? m_nQuantity : AmmoForWeapon_OnStreet[CPickups::WeaponForModel(m_pObject->GetModelIndex())]); + if (player->m_nSelectedWepSlot == player->GetWeaponSlot(WEAPONTYPE_UNARMED)) { + player->m_nSelectedWepSlot = player->GetWeaponSlot(CPickups::WeaponForModel(m_pObject->GetModelIndex())); + } + DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON, m_pObject->GetModelIndex() - MI_GRENADE); + } else if (m_pObject->GetModelIndex() == MI_PICKUP_CAMERA && vehicle != nil) { + DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0); + CPickups::bPickUpcamActivated = true; + CPickups::pPlayerVehicle = FindPlayerVehicle(); + CPickups::StaticCamCoors = m_pObject->GetPosition(); + CPickups::StaticCamStartTime = CTimer::GetTimeInMilliseconds(); + } + } + if (m_eType == PICKUP_ON_STREET) { + m_nTimer = CTimer::GetTimeInMilliseconds() + 30000; + } else if (m_eType == PICKUP_ON_STREET_SLOW) { + if (MI_PICKUP_BRIBE == m_pObject->GetModelIndex()) + m_nTimer = CTimer::GetTimeInMilliseconds() + 300000; + else + m_nTimer = CTimer::GetTimeInMilliseconds() + 720000; + } + + result = true; + CWorld::Remove(m_pObject); + delete m_pObject; + m_pObject = nil; + m_bRemoved = true; + break; + case PICKUP_ONCE: + case PICKUP_ONCE_TIMEOUT: + if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) { + if (CPickups::WeaponForModel(m_pObject->GetModelIndex())) { + player->GiveWeapon(CPickups::WeaponForModel(m_pObject->GetModelIndex()), m_nQuantity != 0 ? m_nQuantity : AmmoForWeapon[CPickups::WeaponForModel(m_pObject->GetModelIndex())]); + if (player->m_nSelectedWepSlot == player->GetWeaponSlot(WEAPONTYPE_UNARMED)) + player->m_nSelectedWepSlot = player->GetWeaponSlot(CPickups::WeaponForModel(m_pObject->GetModelIndex())); + } + DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON, m_pObject->GetModelIndex() - MI_GRENADE); + } + result = true; + Remove(); + break; + case PICKUP_COLLECTABLE1: + CWorld::Players[playerId].m_nCollectedPackages++; + CWorld::Players[playerId].m_nMoney += 1000; + + if (CWorld::Players[playerId].m_nCollectedPackages == CWorld::Players[playerId].m_nTotalPackages) { + printf("All collectables have been picked up\n"); + CGarages::TriggerMessage("CO_ALL", -1, 5000, -1); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 1000000; + } else + CGarages::TriggerMessage("CO_ONE", CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages, 5000, CWorld::Players[CWorld::PlayerInFocus].m_nTotalPackages); + + result = true; + Remove(); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_HIDDEN_PACKAGE, 0); + break; + case PICKUP_MONEY: + CWorld::Players[playerId].m_nMoney += m_nQuantity; + sprintf(gString, "$%d", m_nQuantity); +#ifdef MONEY_MESSAGES + CMoneyMessages::RegisterOne(m_vecPos + CVector(0.0f, 0.0f, 1.0f), gString, 0, 255, 0, 0.5f, 0.5f); +#endif + result = true; + Remove(); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_MONEY, 0); + break; + default: + break; + } + } + } else { + switch (m_eType) + { + case PICKUP_MINE_INACTIVE: + if (vehicle != nil && !vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) { + m_eType = PICKUP_MINE_ARMED; + m_nTimer = CTimer::GetTimeInMilliseconds() + 10000; + } + break; + case PICKUP_NAUTICAL_MINE_INACTIVE: + { + if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false)) + m_pObject->GetMatrix().GetPosition().z = waterLevel + 0.6f; + + m_pObject->GetMatrix().UpdateRW(); + m_pObject->UpdateRwFrame(); + + bool touched = false; + for (int32 i = CPools::GetVehiclePool()->GetSize()-1; i >= 0; i--) { + CVehicle *vehicle = CPools::GetVehiclePool()->GetSlot(i); + if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 1.5f)) { + touched = true; +#ifdef FIX_BUGS + break; +#endif + } + } + + if (!touched) { + m_eType = PICKUP_NAUTICAL_MINE_ARMED; + m_nTimer = CTimer::GetTimeInMilliseconds() + 10000; + } + break; + } + case PICKUP_NAUTICAL_MINE_ARMED: + if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false)) + m_pObject->GetMatrix().GetPosition().z = waterLevel + 0.6f; + + m_pObject->GetMatrix().UpdateRW(); + m_pObject->UpdateRwFrame(); + // no break here + case PICKUP_MINE_ARMED: + { + bool explode = false; + if (CTimer::GetTimeInMilliseconds() > m_nTimer) + explode = true; +#ifdef FIX_BUGS + else// added else here since vehicle lookup is useless +#endif + { + for (int32 i = CPools::GetVehiclePool()->GetSize()-1; i >= 0; i--) { + CVehicle *vehicle = CPools::GetVehiclePool()->GetSlot(i); + if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 1.5f)) { + explode = true; +#ifdef FIX_BUGS + break; +#endif + } + } + } + if (explode) { + CExplosion::AddExplosion(nil, nil, EXPLOSION_MINE, m_pObject->GetPosition(), 0); + Remove(); + } + break; + } + case PICKUP_FLOATINGPACKAGE: + m_pObject->m_vecMoveSpeed.z -= 0.01f * CTimer::GetTimeStep(); + m_pObject->GetMatrix().GetPosition() += m_pObject->GetMoveSpeed() * CTimer::GetTimeStep(); + + m_pObject->GetMatrix().UpdateRW(); + m_pObject->UpdateRwFrame(); + if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false) && waterLevel >= m_pObject->GetPosition().z) + m_eType = PICKUP_FLOATINGPACKAGE_FLOATING; + break; + case PICKUP_FLOATINGPACKAGE_FLOATING: + if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false)) + m_pObject->GetMatrix().GetPosition().z = waterLevel; + + m_pObject->GetMatrix().UpdateRW(); + m_pObject->UpdateRwFrame(); + if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) { + Remove(); + result = true; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_FLOAT_PACKAGE, 0); + } + break; + default: break; + } + } + if (!m_bRemoved && (m_eType == PICKUP_ONCE_TIMEOUT || m_eType == PICKUP_MONEY) && CTimer::GetTimeInMilliseconds() > m_nTimer) + Remove(); + return result; +} + +void +CPickups::Init(void) +{ + NumMessages = 0; + for (int i = 0; i < NUMPICKUPS; i++) { + aPickUps[i].m_eType = PICKUP_NONE; + aPickUps[i].m_nIndex = 1; + aPickUps[i].m_pObject = nil; + } + + for (int i = 0; i < NUMCOLLECTEDPICKUPS; i++) + aPickUpsCollected[i] = 0; + + CollectedPickUpIndex = 0; +} + +bool +CPickups::IsPickUpPickedUp(int32 pickupId) +{ + for (int i = 0; i < NUMCOLLECTEDPICKUPS; i++) { + if (pickupId == aPickUpsCollected[i]) { + aPickUpsCollected[i] = 0; + return true; + } + } + return false; +} + +void +CPickups::PassTime(uint32 time) +{ + for (int i = 0; i < NUMPICKUPS; i++) { + if (aPickUps[i].m_eType != PICKUP_NONE) { + if (aPickUps[i].m_nTimer <= time) + aPickUps[i].m_nTimer = 0; + else + aPickUps[i].m_nTimer -= time; + } + } +} + +int32 +CPickups::GetActualPickupIndex(int32 index) +{ + if (index == -1) return -1; + + // doesn't look nice + if ((uint16)((index & 0xFFFF0000) >> 16) != aPickUps[(uint16)index].m_nIndex) return -1; + return (uint16)index; +} + +bool +CPickups::GivePlayerGoodiesWithPickUpMI(int16 modelIndex, int playerIndex) +{ + CPlayerPed *player; + + if (playerIndex <= 0) player = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + else player = CWorld::Players[playerIndex].m_pPed; + + if (modelIndex == MI_PICKUP_ADRENALINE) { + player->m_bAdrenalineActive = true; + player->m_nAdrenalineTime = CTimer::GetTimeInMilliseconds() + 20000; + player->m_fCurrentStamina = player->m_fMaxStamina; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_ADRENALINE, 0); + return true; + } else if (modelIndex == MI_PICKUP_BODYARMOUR) { + player->m_fArmour = 100.0f; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_ARMOUR, 0); + return true; + } else if (modelIndex == MI_PICKUP_INFO) { + DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0); + return true; + } else if (modelIndex == MI_PICKUP_HEALTH) { + player->m_fHealth = 100.0f; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_HEALTH, 0); + return true; + } else if (modelIndex == MI_PICKUP_BONUS) { + DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0); + return true; + } else if (modelIndex == MI_PICKUP_BRIBE) { + int32 level = FindPlayerPed()->m_pWanted->GetWantedLevel() - 1; + if (level < 0) level = 0; + player->SetWantedLevel(level); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0); + return true; + } else if (modelIndex == MI_PICKUP_KILLFRENZY) { + DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0); + return true; + } + return false; +} + +void +CPickups::RemoveAllFloatingPickups() +{ + for (int i = 0; i < NUMPICKUPS; i++) { + if (aPickUps[i].m_eType == PICKUP_FLOATINGPACKAGE || aPickUps[i].m_eType == PICKUP_FLOATINGPACKAGE_FLOATING) { + if (aPickUps[i].m_pObject) { + CWorld::Remove(aPickUps[i].m_pObject); + delete aPickUps[i].m_pObject; + aPickUps[i].m_pObject = nil; + } + } + } +} + +void +CPickups::RemovePickUp(int32 pickupIndex) +{ + int32 index = CPickups::GetActualPickupIndex(pickupIndex); + if (index == -1) return; + + if (aPickUps[index].m_pObject) { + CWorld::Remove(aPickUps[index].m_pObject); + delete aPickUps[index].m_pObject; + aPickUps[index].m_pObject = nil; + } + aPickUps[index].m_eType = PICKUP_NONE; + aPickUps[index].m_bRemoved = true; +} + +int32 +CPickups::GenerateNewOne(CVector pos, uint32 modelIndex, uint8 type, uint32 quantity) +{ + bool bFreeFound = false; + int32 slot = 0; + + if (type == PICKUP_FLOATINGPACKAGE || type == PICKUP_NAUTICAL_MINE_INACTIVE) { + for (slot = NUMPICKUPS-1; slot >= 0; slot--) { + if (aPickUps[slot].m_eType == PICKUP_NONE) { + bFreeFound = true; + break; + } + } + } else { + for (slot = 0; slot < NUMGENERALPICKUPS; slot++) { + if (aPickUps[slot].m_eType == PICKUP_NONE) { + bFreeFound = true; + break; + } + } + } + + if (!bFreeFound) { + for (slot = 0; slot < NUMGENERALPICKUPS; slot++) { + if (aPickUps[slot].m_eType == PICKUP_MONEY) break; + } + + if (slot >= NUMGENERALPICKUPS) { + for (slot = 0; slot < NUMGENERALPICKUPS; slot++) { + if (aPickUps[slot].m_eType == PICKUP_ONCE_TIMEOUT) break; + } + + if (slot >= NUMGENERALPICKUPS) return -1; + } + } + + if (slot >= NUMPICKUPS) return -1; + + aPickUps[slot].m_eType = type; + aPickUps[slot].m_bRemoved = false; + aPickUps[slot].m_nQuantity = quantity; + if (type == PICKUP_ONCE_TIMEOUT) + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 20000; + else if (type == PICKUP_MONEY) + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 30000; + else if (type == PICKUP_MINE_INACTIVE || type == PICKUP_MINE_ARMED) { + aPickUps[slot].m_eType = PICKUP_MINE_INACTIVE; + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 1500; + } else if (type == PICKUP_NAUTICAL_MINE_INACTIVE || type == PICKUP_NAUTICAL_MINE_ARMED) { + aPickUps[slot].m_eType = PICKUP_NAUTICAL_MINE_INACTIVE; + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 1500; + } + aPickUps[slot].m_eModelIndex = modelIndex; + aPickUps[slot].m_vecPos = pos; + aPickUps[slot].m_pObject = aPickUps[slot].GiveUsAPickUpObject(-1); + if (aPickUps[slot].m_pObject) + CWorld::Add(aPickUps[slot].m_pObject); + return GetNewUniquePickupIndex(slot); +} + +int32 +CPickups::GenerateNewOne_WeaponType(CVector pos, eWeaponType weaponType, uint8 type, uint32 quantity) +{ + return GenerateNewOne(pos, ModelForWeapon(weaponType), type, quantity); +} + +int32 +CPickups::GetNewUniquePickupIndex(int32 slot) +{ + if (aPickUps[slot].m_nIndex >= 0xFFFE) + aPickUps[slot].m_nIndex = 1; + else + aPickUps[slot].m_nIndex++; + return slot | (aPickUps[slot].m_nIndex << 16); +} + +int32 +CPickups::ModelForWeapon(eWeaponType weaponType) +{ + switch (weaponType) + { + case WEAPONTYPE_BASEBALLBAT: return MI_BASEBALL_BAT; + case WEAPONTYPE_COLT45: return MI_COLT; + case WEAPONTYPE_UZI: return MI_UZI; + case WEAPONTYPE_SHOTGUN: return MI_SHOTGUN; + case WEAPONTYPE_AK47: return MI_AK47; + case WEAPONTYPE_M16: return MI_M16; + case WEAPONTYPE_SNIPERRIFLE: return MI_SNIPER; + case WEAPONTYPE_ROCKETLAUNCHER: return MI_ROCKETLAUNCHER; + case WEAPONTYPE_FLAMETHROWER: return MI_FLAMETHROWER; + case WEAPONTYPE_MOLOTOV: return MI_MOLOTOV; + case WEAPONTYPE_GRENADE: return MI_GRENADE; + default: break; + } + return 0; +} + +eWeaponType +CPickups::WeaponForModel(int32 model) +{ + if (model == MI_PICKUP_BODYARMOUR) return WEAPONTYPE_ARMOUR; + switch (model) + { + case MI_GRENADE: return WEAPONTYPE_GRENADE; + case MI_AK47: return WEAPONTYPE_AK47; + case MI_BASEBALL_BAT: return WEAPONTYPE_BASEBALLBAT; + case MI_COLT: return WEAPONTYPE_COLT45; + case MI_MOLOTOV: return WEAPONTYPE_MOLOTOV; + case MI_ROCKETLAUNCHER: return WEAPONTYPE_ROCKETLAUNCHER; + case MI_SHOTGUN: return WEAPONTYPE_SHOTGUN; + case MI_SNIPER: return WEAPONTYPE_SNIPERRIFLE; + case MI_UZI: return WEAPONTYPE_UZI; + case MI_MISSILE: return WEAPONTYPE_UNARMED; + case MI_M16: return WEAPONTYPE_M16; + case MI_FLAMETHROWER: return WEAPONTYPE_FLAMETHROWER; + } + return WEAPONTYPE_UNARMED; +} + +int32 +CPickups::FindColourIndexForWeaponMI(int32 model) +{ + return WeaponForModel(model) - 1; +} + +void +CPickups::AddToCollectedPickupsArray(int32 index) +{ + aPickUpsCollected[CollectedPickUpIndex++] = index | (aPickUps[index].m_nIndex << 16); + if (CollectedPickUpIndex >= NUMCOLLECTEDPICKUPS) + CollectedPickUpIndex = 0; +} + +void +CPickups::Update() +{ +#ifdef FIX_BUGS // RIP speedrunning (solution from SA) + if (CReplay::IsPlayingBack()) + return; +#endif +#ifdef CAMERA_PICKUP + if ( bPickUpcamActivated ) // taken from PS2 + { + float dist = Distance2D(StaticCamCoors, FindPlayerCoors()); + float mult; + if ( dist < 10.0f ) + mult = 1.0f - (dist / 10.0f ); + else + mult = 0.0f; + + CVector pos = StaticCamCoors; + pos.z += (pPlayerVehicle->GetColModel()->boundingBox.GetSize().z + 2.0f) * mult; + + if ( (CTimer::GetTimeInMilliseconds() - StaticCamStartTime) > 750 ) + { + TheCamera.SetCamPositionForFixedMode(pos, CVector(0.0f, 0.0f, 0.0f)); + TheCamera.TakeControl(FindPlayerVehicle(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_SCRIPT); + } + + if ( FindPlayerVehicle() != pPlayerVehicle || Distance(StaticCamCoors, FindPlayerCoors()) > 40.0f + || ((CTimer::GetTimeInMilliseconds() - StaticCamStartTime) > 60000) ) + { + TheCamera.RestoreWithJumpCut(); + bPickUpcamActivated = false; + } + } +#endif +#define PICKUPS_FRAME_SPAN (6) +#ifdef FIX_BUGS + for (uint32 i = NUMGENERALPICKUPS * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN) / PICKUPS_FRAME_SPAN; i < NUMGENERALPICKUPS * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN + 1) / PICKUPS_FRAME_SPAN; i++) { +#else // BUG: this code can only reach 318 out of 320 pickups + for (uint32 i = NUMGENERALPICKUPS / PICKUPS_FRAME_SPAN * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN); i < NUMGENERALPICKUPS / PICKUPS_FRAME_SPAN * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN + 1); i++) { +#endif + if (aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].Update(FindPlayerPed(), FindPlayerVehicle(), CWorld::PlayerInFocus)) { + AddToCollectedPickupsArray(i); + } + } +#undef PICKUPS_FRAME_SPAN + for (uint32 i = NUMGENERALPICKUPS; i < NUMPICKUPS; i++) { + if (aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].Update(FindPlayerPed(), FindPlayerVehicle(), CWorld::PlayerInFocus)) { + AddToCollectedPickupsArray(i); + } + } +} + +void +CPickups::DoPickUpEffects(CEntity *entity) +{ + if (entity->GetModelIndex() == MI_PICKUP_KILLFRENZY) + entity->bDoNotRender = CTheScripts::IsPlayerOnAMission() || CDarkel::FrenzyOnGoing() || !CGame::nastyGame; + + if (!entity->bDoNotRender) { + float modifiedSin = 0.3f * (Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x7FF) * DEGTORAD(360.0f / 0x800)) + 1.0f); + + + int16 colorId; + + if (entity->GetModelIndex() == MI_PICKUP_ADRENALINE || entity->GetModelIndex() == MI_PICKUP_CAMERA) + colorId = 11; + else if (entity->GetModelIndex() == MI_PICKUP_BODYARMOUR || entity->GetModelIndex() == MI_PICKUP_BRIBE) + colorId = 12; + else if (entity->GetModelIndex() == MI_PICKUP_INFO || entity->GetModelIndex() == MI_PICKUP_KILLFRENZY) + colorId = 13; + else if (entity->GetModelIndex() == MI_PICKUP_HEALTH || entity->GetModelIndex() == MI_PICKUP_BONUS) + colorId = 14; + else + colorId = FindColourIndexForWeaponMI(entity->GetModelIndex()); + + assert(colorId >= 0); + + const CVector &pos = entity->GetPosition(); + + float colorModifier = ((CGeneral::GetRandomNumber() & 0x1F) * 0.015f + 1.0f) * modifiedSin * 0.15f; + CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, + aWeaponReds[colorId] * colorModifier, aWeaponGreens[colorId] * colorModifier, aWeaponBlues[colorId] * colorModifier, 4.0f, + 1.0f, 40.0f, false, 0.0f); + + float radius = (CGeneral::GetRandomNumber() & 0xF) * 0.1f + 3.0f; + CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f), radius, aWeaponReds[colorId] * modifiedSin / 256.0f, aWeaponGreens[colorId] * modifiedSin / 256.0f, aWeaponBlues[colorId] * modifiedSin / 256.0f, CPointLights::FOG_NONE, true); + float size = (CGeneral::GetRandomNumber() & 0xF) * 0.0005f + 0.6f; + CCoronas::RegisterCorona( (uintptr)entity, + aWeaponReds[colorId] * modifiedSin / 2.0f, aWeaponGreens[colorId] * modifiedSin / 2.0f, aWeaponBlues[colorId] * modifiedSin / 2.0f, + 255, + pos, + size, 65.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + + CObject *object = (CObject*)entity; + if (object->bPickupObjWithMessage || object->bOutOfStock || object->m_nBonusValue) { + float dist = Distance2D(pos, TheCamera.GetPosition()); + const float MAXDIST = 12.0f; + + if (dist < MAXDIST && NumMessages < NUMPICKUPMESSAGES) { + RwV3d vecOut; + float fDistX, fDistY; + if (CSprite::CalcScreenCoors(entity->GetPosition() + CVector(0.0f, 0.0f, 0.7f), &vecOut, &fDistX, &fDistY, true)) { + aMessages[NumMessages].m_pos.x = vecOut.x; + aMessages[NumMessages].m_pos.y = vecOut.y; + aMessages[NumMessages].m_dist.x = fDistX; + aMessages[NumMessages].m_dist.y = fDistY; + aMessages[NumMessages].m_weaponType = WeaponForModel(entity->GetModelIndex()); + aMessages[NumMessages].m_color.red = aWeaponReds[colorId]; + aMessages[NumMessages].m_color.green = aWeaponGreens[colorId]; + aMessages[NumMessages].m_color.blue = aWeaponBlues[colorId]; + aMessages[NumMessages].m_color.alpha = (1.0f - dist / MAXDIST) * 128.0f; + aMessages[NumMessages].m_bOutOfStock = object->bOutOfStock; + aMessages[NumMessages].m_quantity = object->m_nBonusValue; + NumMessages++; + } + } + } + + float angle = (float)(CTimer::GetTimeInMilliseconds() & 0x7FF) * DEGTORAD(360.0f / 0x800); + float c = Cos(angle) * aWeaponScale[colorId]; + float s = Sin(angle) * aWeaponScale[colorId]; + + // we know from SA they were setting each field manually like this + entity->GetMatrix().rx = c; + entity->GetMatrix().ry = s; + entity->GetMatrix().rz = 0.0f; + entity->GetMatrix().fx = -s; + entity->GetMatrix().fy = c; + entity->GetMatrix().fz = 0.0f; + entity->GetMatrix().ux = 0.0f; + entity->GetMatrix().uy = 0.0f; + entity->GetMatrix().uz = aWeaponScale[colorId]; + } +} + +void +CPickups::DoMineEffects(CEntity *entity) +{ + const CVector &pos = entity->GetPosition(); + float dist = Distance(pos, TheCamera.GetPosition()); + const float MAXDIST = 20.0f; + + if (dist < MAXDIST) { + float s = Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x1FF) * DEGTORAD(360.0f / 0x200)); + + int32 red = (MAXDIST - dist) * (0.5f * s + 0.5f) / MAXDIST * 64.0f; + CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, red, 0, 0, 4.0f, 1.0f, 40.0f, + false, 0.0f); + CCoronas::RegisterCorona((uintptr)entity, red, 0, 0, 255, pos, 0.6f, 60.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } + + entity->GetMatrix().SetRotateZOnly((float)(CTimer::GetTimeInMilliseconds() & 0x3FF) * DEGTORAD(360.0f / 0x400)); +} + +void +CPickups::DoMoneyEffects(CEntity *entity) +{ + const CVector &pos = entity->GetPosition(); + float dist = Distance(pos, TheCamera.GetPosition()); + const float MAXDIST = 20.0f; + + if (dist < MAXDIST) { + float s = Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x3FF) * DEGTORAD(360.0f / 0x400)); + + int32 green = (MAXDIST - dist) * (0.2f * s + 0.3f) / MAXDIST * 64.0f; + CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, 0, green, 0, 4.0f, 1.0f, + 40.0f, false, 0.0f); + CCoronas::RegisterCorona((uintptr)entity, 0, green, 0, 255, pos, 0.4f, 40.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } + + entity->GetMatrix().SetRotateZOnly((float)(CTimer::GetTimeInMilliseconds() & 0x7FF) * DEGTORAD(360.0f / 0x800)); +} + +void +CPickups::DoCollectableEffects(CEntity *entity) +{ + const CVector &pos = entity->GetPosition(); + float dist = Distance(pos, TheCamera.GetPosition()); + const float MAXDIST = 14.0f; + + if (dist < MAXDIST) { + float s = Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x7FF) * DEGTORAD(360.0f / 0x800)); + + int32 color = (MAXDIST - dist) * (0.5f * s + 0.5f) / MAXDIST * 255.0f; + CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, color, color, color, 4.0f, + 1.0f, 40.0f, false, 0.0f); + CCoronas::RegisterCorona((uintptr)entity, color, color, color, 255, pos, 0.6f, 40.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } + + entity->GetMatrix().SetRotateZOnly((float)(CTimer::GetTimeInMilliseconds() & 0xFFF) * DEGTORAD(360.0f / 0x1000)); +} + +void +CPickups::RenderPickUpText() +{ + wchar *strToPrint; + for (int32 i = 0; i < NumMessages; i++) { + if (aMessages[i].m_quantity <= 39) { + switch (aMessages[i].m_quantity) // could use some enum maybe + { + case 0: + if (aMessages[i].m_weaponType == WEAPONTYPE_TOTALWEAPONS) { // unreachable code? + // what is this?? + sprintf(gString, "%d/%d", CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages, 2903); + } else { + if (aMessages[i].m_bOutOfStock) + strToPrint = TheText.Get("STOCK"); + else { + sprintf(gString, "$%d", CostOfWeapon[aMessages[i].m_weaponType]); + AsciiToUnicode(gString, gUString); + strToPrint = gUString; + } + } + break; + case 1: + strToPrint = TheText.Get("SECURI"); + break; + case 2: + strToPrint = TheText.Get("MOONBM"); + break; + case 3: + strToPrint = TheText.Get("COACH"); + break; + case 4: + strToPrint = TheText.Get("FLATBED"); + break; + case 5: + strToPrint = TheText.Get("LINERUN"); + break; + case 6: + strToPrint = TheText.Get("TRASHM"); + break; + case 7: + strToPrint = TheText.Get("PATRIOT"); + break; + case 8: + strToPrint = TheText.Get("WHOOPEE"); + break; + case 9: + strToPrint = TheText.Get("BLISTA"); + break; + case 10: + strToPrint = TheText.Get("MULE"); + break; + case 11: + strToPrint = TheText.Get("YANKEE"); + break; + case 12: + strToPrint = TheText.Get("BOBCAT"); + break; + case 13: + strToPrint = TheText.Get("DODO"); + break; + case 14: + strToPrint = TheText.Get("BUS"); + break; + case 15: + strToPrint = TheText.Get("RUMPO"); + break; + case 16: + strToPrint = TheText.Get("PONY"); + break; + case 17: + strToPrint = TheText.Get("SENTINL"); + break; + case 18: + strToPrint = TheText.Get("CHEETAH"); + break; + case 19: + strToPrint = TheText.Get("BANSHEE"); + break; + case 20: + strToPrint = TheText.Get("IDAHO"); + break; + case 21: + strToPrint = TheText.Get("INFERNS"); + break; + case 22: + strToPrint = TheText.Get("TAXI"); + break; + case 23: + strToPrint = TheText.Get("KURUMA"); + break; + case 24: + strToPrint = TheText.Get("STRETCH"); + break; + case 25: + strToPrint = TheText.Get("PEREN"); + break; + case 26: + strToPrint = TheText.Get("STINGER"); + break; + case 27: + strToPrint = TheText.Get("MANANA"); + break; + case 28: + strToPrint = TheText.Get("LANDSTK"); + break; + case 29: + strToPrint = TheText.Get("STALION"); + break; + case 30: + strToPrint = TheText.Get("BFINJC"); + break; + case 31: + strToPrint = TheText.Get("CABBIE"); + break; + case 32: + strToPrint = TheText.Get("ESPERAN"); + break; + case 33: + strToPrint = TheText.Get("FIRETRK"); + break; + case 34: + strToPrint = TheText.Get("AMBULAN"); + break; + case 35: + strToPrint = TheText.Get("ENFORCR"); + break; + case 36: + strToPrint = TheText.Get("FBICAR"); + break; + case 37: + strToPrint = TheText.Get("RHINO"); + break; + case 38: + strToPrint = TheText.Get("BARRCKS"); + break; + case 39: + strToPrint = TheText.Get("POLICAR"); + break; + default: + break; + } + } + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + + const float MAX_SCALE = 1.0f; + + float fScaleY = aMessages[i].m_dist.y / 100.0f; + if (fScaleY > MAX_SCALE) fScaleY = MAX_SCALE; + + float fScaleX = aMessages[i].m_dist.x / 100.0f; + if (fScaleX > MAX_SCALE) fScaleX = MAX_SCALE; + +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(fScaleX), SCREEN_SCALE_Y(fScaleY)); +#else + CFont::SetScale(fScaleX, fScaleY); +#endif + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_WIDTH); + CFont::SetJustifyOff(); + + CFont::SetColor(CRGBA(aMessages[i].m_color.red, aMessages[i].m_color.green, aMessages[i].m_color.blue, aMessages[i].m_color.alpha)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::PrintString(aMessages[i].m_pos.x, aMessages[i].m_pos.y, strToPrint); + } + NumMessages = 0; +} + +void +CPickups::Load(uint8 *buf, uint32 size) +{ +INITSAVEBUF + + for (int32 i = 0; i < NUMPICKUPS; i++) { +#ifdef COMPATIBLE_SAVES + ReadSaveBuf(&aPickUps[i].m_eType, buf); + ReadSaveBuf(&aPickUps[i].m_bRemoved, buf); + ReadSaveBuf(&aPickUps[i].m_nQuantity, buf); + int32 tmp; + ReadSaveBuf(&tmp, buf); + aPickUps[i].m_pObject = aPickUps[i].m_eType != PICKUP_NONE && tmp != 0 ? CPools::GetObjectPool()->GetSlot(tmp - 1) : nil; + ReadSaveBuf(&aPickUps[i].m_nTimer, buf); + ReadSaveBuf(&aPickUps[i].m_eModelIndex, buf); + ReadSaveBuf(&aPickUps[i].m_nIndex, buf); + ReadSaveBuf(&aPickUps[i].m_vecPos, buf); +#else + ReadSaveBuf(&aPickUps[i], buf); + + if (aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].m_pObject != nil) + aPickUps[i].m_pObject = CPools::GetObjectPool()->GetSlot((uintptr)aPickUps[i].m_pObject - 1); +#endif + } + + ReadSaveBuf(&CollectedPickUpIndex, buf); + SkipSaveBuf(buf, 2); + NumMessages = 0; + + for (uint16 i = 0; i < NUMCOLLECTEDPICKUPS; i++) + ReadSaveBuf(&aPickUpsCollected[i], buf); + +VALIDATESAVEBUF(size) +} + +void +CPickups::Save(uint8 *buf, uint32 *size) +{ + *size = PICKUPS_SAVE_SIZE + sizeof(uint16) + sizeof(uint16) + sizeof(aPickUpsCollected); + +INITSAVEBUF + + for (int32 i = 0; i < NUMPICKUPS; i++) { +#ifdef COMPATIBLE_SAVES + WriteSaveBuf(buf, aPickUps[i].m_eType); + WriteSaveBuf(buf, aPickUps[i].m_bRemoved); + WriteSaveBuf(buf, aPickUps[i].m_nQuantity); + int32 tmp = aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].m_pObject != nil ? CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(aPickUps[i].m_pObject) + 1 : 0; + WriteSaveBuf(buf, tmp); + WriteSaveBuf(buf, aPickUps[i].m_nTimer); + WriteSaveBuf(buf, aPickUps[i].m_eModelIndex); + WriteSaveBuf(buf, aPickUps[i].m_nIndex); + WriteSaveBuf(buf, aPickUps[i].m_vecPos); +#else + CPickup *buf_pickup = WriteSaveBuf(buf, aPickUps[i]); + if (buf_pickup->m_eType != PICKUP_NONE && buf_pickup->m_pObject != nil) + buf_pickup->m_pObject = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(buf_pickup->m_pObject) + 1); +#endif + } + + WriteSaveBuf(buf, CollectedPickUpIndex); + WriteSaveBuf(buf, (uint16)0); // possibly was NumMessages + + for (uint16 i = 0; i < NUMCOLLECTEDPICKUPS; i++) + WriteSaveBuf(buf, aPickUpsCollected[i]); + +VALIDATESAVEBUF(*size) +} + +void +CPacManPickup::Update() +{ + if (FindPlayerVehicle() == nil) return; + + CVehicle *veh = FindPlayerVehicle(); + + if (DistanceSqr2D(FindPlayerVehicle()->GetPosition(), m_vecPosn.x, m_vecPosn.y) < 100.0f && veh->IsSphereTouchingVehicle(m_vecPosn.x, m_vecPosn.y, m_vecPosn.z, 1.5f)) { + switch (m_eType) + { + case PACMAN_SCRAMBLE: + { + veh->m_nPacManPickupsCarried++; + veh->m_vecMoveSpeed *= 0.65f; + float massMult = (veh->m_fMass + 250.0f) / veh->m_fMass; + veh->m_fMass *= massMult; + veh->m_fTurnMass *= massMult; + veh->m_fForceMultiplier *= massMult; + FindPlayerPed()->m_pWanted->m_nChaos += 10; + FindPlayerPed()->m_pWanted->UpdateWantedLevel(); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_PACMAN_PACKAGE, 0); + break; + } + case PACMAN_RACE: + CPacManPickups::PillsEatenInRace++; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_PACMAN_PILL, 0); + break; + default: + break; + } + m_eType = PACMAN_NONE; + if (m_pObject != nil) { + CWorld::Remove(m_pObject); + delete m_pObject; + m_pObject = nil; + } + } +} + +int32 CollectGameState; +int16 ThingsToCollect; + +CPacManPickup CPacManPickups::aPMPickUps[NUMPACMANPICKUPS]; +CVector CPacManPickups::LastPickUpCoors; +int32 CPacManPickups::PillsEatenInRace; +bool CPacManPickups::bPMActive; + +void +CPacManPickups::Init() +{ + for (int i = 0; i < NUMPACMANPICKUPS; i++) + aPMPickUps[i].m_eType = PACMAN_NONE; + bPMActive = false; +} + +void +CPacManPickups::Update() +{ + if (FindPlayerVehicle()) { + float dist = Distance(FindPlayerCoors(), CVector(1072.0f, -948.0f, 14.5f)); + switch (CollectGameState) { + case 1: + if (dist < 10.0f) { + ThingsToCollect -= FindPlayerVehicle()->m_nPacManPickupsCarried; + FindPlayerVehicle()->m_nPacManPickupsCarried = 0; + FindPlayerVehicle()->m_fMass /= FindPlayerVehicle()->m_fForceMultiplier; + FindPlayerVehicle()->m_fTurnMass /= FindPlayerVehicle()->m_fForceMultiplier; + FindPlayerVehicle()->m_fForceMultiplier = 1.0f; + } + if (ThingsToCollect <= 0) { + CollectGameState = 2; + ClearPMPickUps(); + } + break; + case 2: + if (dist > 11.0f) + CollectGameState = 0; + break; + case 20: + if (Distance(FindPlayerCoors(), LastPickUpCoors) > 30.0f) { + LastPickUpCoors = FindPlayerCoors(); + printf("%f, %f, %f,\n", LastPickUpCoors.x, LastPickUpCoors.y, LastPickUpCoors.z); + } + break; + default: + break; + } + } + if (bPMActive) { +#define PACMANPICKUPS_FRAME_SPAN (4) + for (uint32 i = (CTimer::GetFrameCounter() % PACMANPICKUPS_FRAME_SPAN) * (NUMPACMANPICKUPS / PACMANPICKUPS_FRAME_SPAN); i < ((CTimer::GetFrameCounter() % PACMANPICKUPS_FRAME_SPAN) + 1) * (NUMPACMANPICKUPS / PACMANPICKUPS_FRAME_SPAN); i++) { + if (aPMPickUps[i].m_eType != PACMAN_NONE) + aPMPickUps[i].Update(); + } +#undef PACMANPICKUPS_FRAME_SPAN + } +} + +void +CPacManPickups::GeneratePMPickUps(CVector pos, float scrambleMult, int16 count, uint8 type) +{ + int i = 0; + while (count > 0) { + while (aPMPickUps[i].m_eType != PACMAN_NONE) + i++; + + bool bPickupCreated = false; + while (!bPickupCreated) { + CVector newPos = pos; + CColPoint colPoint; + CEntity *pRoad; + uint16 nRand = CGeneral::GetRandomNumber(); + newPos.x += ((nRand & 0xFF) - 128) * scrambleMult / 128.0f; + newPos.y += (((nRand >> 8) & 0xFF) - 128) * scrambleMult / 128.0f; + newPos.z = 1000.0f; + if (CWorld::ProcessVerticalLine(newPos, -1000.0f, colPoint, pRoad, true, false, false, false, true, false, nil) && pRoad->IsBuilding() && ((CBuilding*)pRoad)->GetIsATreadable()) { + newPos.z = 0.7f + colPoint.point.z; + aPMPickUps[i].m_eType = type; + aPMPickUps[i].m_vecPosn = newPos; + CObject *obj = new CObject(MI_BULLION, true); + if (obj != nil) { + obj->ObjectCreatedBy = MISSION_OBJECT; + obj->SetPosition(aPMPickUps[i].m_vecPosn); + obj->SetOrientation(0.0f, 0.0f, -HALFPI); + obj->GetMatrix().UpdateRW(); + obj->UpdateRwFrame(); + + obj->bAffectedByGravity = false; + obj->bExplosionProof = true; + obj->bUsesCollision = false; + obj->bIsPickup = false; + CWorld::Add(obj); + } + aPMPickUps[i].m_pObject = obj; + bPickupCreated = true; + } + } + count--; + } + bPMActive = true; +} + +// diablo porn mission pickups +static const CVector aRacePoints1[] = { + CVector(913.62219f, -155.13692f, 4.9699469f), + CVector(913.92401f, -124.12943f, 4.9692569f), + CVector(913.27899f, -93.524231f, 7.4325991f), + CVector(912.60852f, -63.15905f, 7.4533591f), + CVector(934.22144f, -42.049122f, 7.4511471f), + CVector(958.88092f, -23.863735f, 7.4652338f), + CVector(978.50812f, -0.78458798f, 5.13515f), + CVector(1009.4175f, -2.1041219f, 2.4461579f), + CVector(1040.6313f, -2.0793829f, 2.293175f), + CVector(1070.7863f, -2.084095f, 2.2789791f), + CVector(1100.5773f, -8.468729f, 5.3248072f), + CVector(1119.9341f, -31.738031f, 7.1913071f), + CVector(1122.1664f, -62.762737f, 7.4703908f), + CVector(1122.814f, -93.650566f, 8.5577497f), + CVector(1125.8253f, -124.26616f, 9.9803305f), + CVector(1153.8727f, -135.47169f, 14.150617f), + CVector(1184.0831f, -135.82845f, 14.973998f), + CVector(1192.0432f, -164.57816f, 19.18627f), + CVector(1192.7761f, -194.28871f, 24.799675f), + CVector(1215.1527f, -215.0714f, 25.74975f), + CVector(1245.79f, -215.39304f, 28.70726f), + CVector(1276.2477f, -216.39485f, 33.71236f), + CVector(1306.5535f, -216.71007f, 39.711472f), + CVector(1335.0244f, -224.59329f, 46.474979f), + CVector(1355.4879f, -246.27664f, 49.934841f), + CVector(1362.6003f, -276.47064f, 49.96265f), + CVector(1363.027f, -307.30847f, 49.969173f), + CVector(1365.343f, -338.08609f, 49.967789f), + CVector(1367.5957f, -368.01105f, 50.092304f), + CVector(1368.2749f, -398.38049f, 50.061268f), + CVector(1366.9034f, -429.98483f, 50.057545f), + CVector(1356.8534f, -459.09259f, 50.035545f), + CVector(1335.5819f, -481.13544f, 47.217903f), + CVector(1306.7552f, -491.07443f, 40.202629f), + CVector(1275.5978f, -491.33194f, 33.969223f), + CVector(1244.702f, -491.46451f, 29.111021f), + CVector(1213.2222f, -491.8754f, 25.771168f), + CVector(1182.7729f, -492.19995f, 24.749964f), + CVector(1152.6874f, -491.42221f, 21.70038f), + CVector(1121.5352f, -491.94604f, 20.075182f), + CVector(1090.7056f, -492.63751f, 17.585758f), + CVector(1059.6008f, -491.65762f, 14.848632f), + CVector(1029.113f, -489.66031f, 14.918498f), + CVector(998.20679f, -486.78107f, 14.945688f), + CVector(968.00555f, -484.91266f, 15.001229f), + CVector(937.74939f, -492.09015f, 14.958629f), + CVector(927.17352f, -520.97736f, 14.972308f), + CVector(929.29749f, -552.08643f, 14.978855f), + CVector(950.69525f, -574.47778f, 14.972788f), + CVector(974.02826f, -593.56024f, 14.966445f), + CVector(989.04779f, -620.12854f, 14.951016f), + CVector(1014.1639f, -637.3905f, 14.966736f), + CVector(1017.5961f, -667.3736f, 14.956415f), + CVector(1041.9735f, -685.94391f, 15.003841f), + CVector(1043.3064f, -716.11298f, 14.974236f), + CVector(1043.5337f, -746.63855f, 14.96919f), + CVector(1044.142f, -776.93823f, 14.965424f), + CVector(1044.2657f, -807.29395f, 14.97171f), + CVector(1017.0797f, -820.1076f, 14.975431f), + CVector(986.23865f, -820.37103f, 14.972883f), + CVector(956.10065f, -820.23291f, 14.981133f), + CVector(925.86914f, -820.19049f, 14.976553f), + CVector(897.69702f, -831.08734f, 14.962709f), + CVector(868.06586f, -835.99237f, 14.970685f), + CVector(836.93054f, -836.84387f, 14.965049f), + CVector(811.63586f, -853.7915f, 15.067576f), + CVector(811.46344f, -884.27368f, 12.247812f), + CVector(811.60651f, -914.70959f, 9.2393751f), + CVector(811.10425f, -945.16272f, 5.817255f), + CVector(816.54584f, -975.64587f, 4.998558f), + CVector(828.2951f, -1003.3685f, 5.0471172f), + CVector(852.28839f, -1021.5963f, 4.9371028f), + CVector(882.50067f, -1025.4459f, 5.14077f), + CVector(912.84821f, -1026.7874f, 8.3415451f), + CVector(943.68274f, -1026.6914f, 11.341879f), + CVector(974.4129f, -1027.3682f, 14.410345f), + CVector(1004.1079f, -1036.0778f, 14.92961f), + CVector(1030.1144f, -1051.1224f, 14.850387f), + CVector(1058.7585f, -1060.342f, 14.821624f), + CVector(1087.7797f, -1068.3263f, 14.800561f), + CVector(1099.8807f, -1095.656f, 11.877907f), + CVector(1130.0005f, -1101.994f, 11.853914f), + CVector(1160.3809f, -1101.6355f, 11.854824f), + CVector(1191.8524f, -1102.1577f, 11.853843f), + CVector(1223.3307f, -1102.7448f, 11.852233f), + CVector(1253.564f, -1098.1045f, 11.853944f), + CVector(1262.0203f, -1069.1785f, 14.8147f), + CVector(1290.9998f, -1059.1882f, 14.816016f), + CVector(1316.246f, -1041.0635f, 14.81109f), + CVector(1331.7539f, -1013.835f, 14.81207f), + CVector(1334.0579f, -983.55402f, 14.827253f), + CVector(1323.2429f, -954.23083f, 14.954678f), + CVector(1302.7495f, -932.21216f, 14.962917f), + CVector(1317.418f, -905.89325f, 14.967506f), + CVector(1337.9503f, -883.5025f, 14.969675f), + CVector(1352.6929f, -855.96954f, 14.967854f), + CVector(1357.2388f, -826.26971f, 14.97295f), + CVector(1384.8668f, -812.47693f, 12.907736f), + CVector(1410.8983f, -795.39056f, 12.052228f), + CVector(1433.901f, -775.55811f, 11.96265f), + CVector(1443.8615f, -746.92511f, 11.976114f), + CVector(1457.7015f, -720.00903f, 11.971177f), + CVector(1481.5685f, -701.30237f, 11.977908f), + CVector(1511.4004f, -696.83295f, 11.972709f), + CVector(1542.1796f, -695.61676f, 11.970441f), + CVector(1570.3301f, -684.6239f, 11.969202f), + CVector(0.0f, 0.0f, 0.0f), +}; + +void +CPacManPickups::GeneratePMPickUpsForRace(int32 race) +{ + const CVector *pPos = nil; + int i = 0; + + if (race == 0) pPos = aRacePoints1; // there's only one available + assert(pPos != nil); + + while (!pPos->IsZero()) { + while (aPMPickUps[i].m_eType != PACMAN_NONE) + i++; + + aPMPickUps[i].m_eType = PACMAN_RACE; + aPMPickUps[i].m_vecPosn = *(pPos++); + if (race == 0) { + CObject* obj = new CObject(MI_DONKEYMAG, true); + if (obj != nil) { + obj->ObjectCreatedBy = MISSION_OBJECT; + + obj->SetPosition(aPMPickUps[i].m_vecPosn); + obj->SetOrientation(0.0f, 0.0f, -HALFPI); + obj->GetMatrix().UpdateRW(); + obj->UpdateRwFrame(); + + obj->bAffectedByGravity = false; + obj->bExplosionProof = true; + obj->bUsesCollision = false; + obj->bIsPickup = false; + + CWorld::Add(obj); + } + aPMPickUps[i].m_pObject = obj; + } else + aPMPickUps[i].m_pObject = nil; + } + bPMActive = true; +} + +void +CPacManPickups::GenerateOnePMPickUp(CVector pos) +{ + bPMActive = true; + aPMPickUps[0].m_eType = PACMAN_RACE; + aPMPickUps[0].m_vecPosn = pos; +} + +void +CPacManPickups::Render() +{ + if (!bPMActive) return; + + PUSH_RENDERGROUP("CPacManPickups::Render"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[6])); + + RwV3d pos; + float w, h; + + for (int i = 0; i < NUMPACMANPICKUPS; i++) { + switch (aPMPickUps[i].m_eType) + { + case PACMAN_SCRAMBLE: + case PACMAN_RACE: + if (CSprite::CalcScreenCoors(aPMPickUps[i].m_vecPosn, &pos, &w, &h, true) && pos.z < 100.0f) { + if (aPMPickUps[i].m_pObject != nil) { + aPMPickUps[i].m_pObject->GetMatrix().SetRotateZOnly((CTimer::GetTimeInMilliseconds() % 1024) * TWOPI / 1024.0f); + aPMPickUps[i].m_pObject->GetMatrix().UpdateRW(); + aPMPickUps[i].m_pObject->UpdateRwFrame(); + } + float fsin = Sin((CTimer::GetTimeInMilliseconds() % 1024) * 6.28f / 1024.0f); // yes, it is 6.28f when it was TWOPI just now... + CSprite::RenderOneXLUSprite(pos.x, pos.y, pos.z, 0.8f * w * fsin, 0.8f * h, 100, 50, 5, 255, 1.0f / pos.z, 255); + } + break; + default: + break; + } + } + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, FALSE); + + POP_RENDERGROUP(); +} + +void +CPacManPickups::ClearPMPickUps() +{ + bPMActive = false; + + for (int i = 0; i < NUMPACMANPICKUPS; i++) { + if (aPMPickUps[i].m_pObject != nil) { + CWorld::Remove(aPMPickUps[i].m_pObject); + delete aPMPickUps[i].m_pObject; + aPMPickUps[i].m_pObject = nil; + } + aPMPickUps[i].m_eType = PACMAN_NONE; + } +} + +void +CPacManPickups::StartPacManRace(int32 race) +{ + GeneratePMPickUpsForRace(race); + PillsEatenInRace = 0; +} + +void +CPacManPickups::StartPacManRecord() +{ + CollectGameState = 20; + LastPickUpCoors = FindPlayerCoors(); +} + +uint32 +CPacManPickups::QueryPowerPillsEatenInRace() +{ + return PillsEatenInRace; +} + +void +CPacManPickups::ResetPowerPillsEatenInRace() +{ + PillsEatenInRace = 0; +} + +void +CPacManPickups::CleanUpPacManStuff() +{ + ClearPMPickUps(); +} + +void +CPacManPickups::StartPacManScramble(CVector pos, float scrambleMult, int16 count) +{ + GeneratePMPickUps(pos, scrambleMult, count, PACMAN_SCRAMBLE); +} + +uint32 +CPacManPickups::QueryPowerPillsCarriedByPlayer() +{ + if (FindPlayerVehicle()) + return FindPlayerVehicle()->m_nPacManPickupsCarried; + return 0; +} + +void +CPacManPickups::ResetPowerPillsCarriedByPlayer() +{ + if (FindPlayerVehicle() != nil) { + FindPlayerVehicle()->m_nPacManPickupsCarried = 0; + FindPlayerVehicle()->m_fMass /= FindPlayerVehicle()->m_fForceMultiplier; + FindPlayerVehicle()->m_fTurnMass /= FindPlayerVehicle()->m_fForceMultiplier; + FindPlayerVehicle()->m_fForceMultiplier = 1.0f; + } +} + +void +CPed::CreateDeadPedMoney(void) +{ + if (!CGame::nastyGame) + return; + + int mi = GetModelIndex(); + + if ((mi >= MI_COP && mi <= MI_FIREMAN) || CharCreatedBy == MISSION_CHAR || bInVehicle) + return; + + int money = CGeneral::GetRandomNumber() % 60; + if (money < 10) + return; + + if (money == 43) + money = 700; + + int pickupCount = money / 40 + 1; + int moneyPerPickup = money / pickupCount; + + for(int i = 0; i < pickupCount; i++) { + // (CGeneral::GetRandomNumber() % 256) * PI / 128 gives a float up to something TWOPI-ish. + float pickupX = 1.5f * Sin((CGeneral::GetRandomNumber() % 256) * PI / 128) + GetPosition().x; + float pickupY = 1.5f * Cos((CGeneral::GetRandomNumber() % 256) * PI / 128) + GetPosition().y; + bool found = false; + float groundZ = CWorld::FindGroundZFor3DCoord(pickupX, pickupY, GetPosition().z, &found) + 0.5f; + if (found) { + CPickups::GenerateNewOne(CVector(pickupX, pickupY, groundZ), MI_MONEY, PICKUP_MONEY, moneyPerPickup + (CGeneral::GetRandomNumber() & 7)); + } + } +} + +void +CPed::CreateDeadPedWeaponPickups(void) +{ + bool found = false; + float angleToPed; + CVector pickupPos; + + if (bInVehicle) + return; + + for(int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++) { + + eWeaponType weapon = GetWeapon(i).m_eWeaponType; + int weaponAmmo = GetWeapon(i).m_nAmmoTotal; + if (weapon == WEAPONTYPE_UNARMED || weapon == WEAPONTYPE_DETONATOR || weaponAmmo == 0) + continue; + + angleToPed = i * 1.75f; + pickupPos = GetPosition(); + pickupPos.x += 1.5f * Sin(angleToPed); + pickupPos.y += 1.5f * Cos(angleToPed); + pickupPos.z = CWorld::FindGroundZFor3DCoord(pickupPos.x, pickupPos.y, pickupPos.z, &found) + 0.5f; + + CVector pedPos = GetPosition(); + pedPos.z += 0.3f; + + CVector pedToPickup = pickupPos - pedPos; + float distance = pedToPickup.Magnitude(); + + // outer edge of pickup + distance = (distance + 0.3f) / distance; + CVector pickupPos2 = pedPos; + pickupPos2 += distance * pedToPickup; + + // pickup must be on ground and line to its edge must be clear + if (!found || CWorld::GetIsLineOfSightClear(pickupPos2, pedPos, true, false, false, false, false, false, false)) { + // otherwise try another position (but disregard second check apparently) + angleToPed += 3.14f; + pickupPos = GetPosition(); + pickupPos.x += 1.5f * Sin(angleToPed); + pickupPos.y += 1.5f * Cos(angleToPed); + pickupPos.z = CWorld::FindGroundZFor3DCoord(pickupPos.x, pickupPos.y, pickupPos.z, &found) + 0.5f; + } + if (found) + CPickups::GenerateNewOne_WeaponType(pickupPos, weapon, PICKUP_ONCE_TIMEOUT, Min(weaponAmmo, AmmoForWeapon_OnStreet[weapon])); + } + ClearWeapons(); +} \ No newline at end of file diff --git a/src/control/Pickups.h b/src/control/Pickups.h new file mode 100644 index 0000000..4e1c764 --- /dev/null +++ b/src/control/Pickups.h @@ -0,0 +1,146 @@ +#pragma once +#include "Weapon.h" + +enum ePickupType +{ + PICKUP_NONE = 0, + PICKUP_IN_SHOP, + PICKUP_ON_STREET, + PICKUP_ONCE, + PICKUP_ONCE_TIMEOUT, + PICKUP_COLLECTABLE1, + PICKUP_IN_SHOP_OUT_OF_STOCK, + PICKUP_MONEY, + PICKUP_MINE_INACTIVE, + PICKUP_MINE_ARMED, + PICKUP_NAUTICAL_MINE_INACTIVE, + PICKUP_NAUTICAL_MINE_ARMED, + PICKUP_FLOATINGPACKAGE, + PICKUP_FLOATINGPACKAGE_FLOATING, + PICKUP_ON_STREET_SLOW, + PICKUP_NUMOFTYPES +}; + +class CEntity; +class CObject; +class CVehicle; +class CPlayerPed; + +class CPickup +{ +public: + uint8 m_eType; + bool m_bRemoved; + uint16 m_nQuantity; + CObject *m_pObject; + uint32 m_nTimer; + int16 m_eModelIndex; + uint16 m_nIndex; + CVector m_vecPos; + + CObject *GiveUsAPickUpObject(int32 handle); + bool Update(CPlayerPed *player, CVehicle *vehicle, int playerId); +private: + inline bool IsMine() { return m_eType >= PICKUP_MINE_INACTIVE && m_eType <= PICKUP_FLOATINGPACKAGE_FLOATING; } + inline bool CanBePickedUp(CPlayerPed *player); + inline void Remove(); +}; + +VALIDATE_SIZE(CPickup, 0x1C); + +struct tPickupMessage +{ + CVector2D m_pos; + eWeaponType m_weaponType; + CVector2D m_dist; + CRGBA m_color; + uint8 m_bOutOfStock : 1; + uint8 m_quantity; +}; + +class CPickups +{ + static int32 aPickUpsCollected[NUMCOLLECTEDPICKUPS]; + static int16 CollectedPickUpIndex; + static int16 NumMessages; + static tPickupMessage aMessages[NUMPICKUPMESSAGES]; +public: + static void Init(); + static void Update(); + static void RenderPickUpText(); + static void DoCollectableEffects(CEntity *ent); + static void DoMoneyEffects(CEntity *ent); + static void DoMineEffects(CEntity *ent); + static void DoPickUpEffects(CEntity *ent); + static int32 GenerateNewOne(CVector pos, uint32 modelIndex, uint8 type, uint32 quantity); + static int32 GenerateNewOne_WeaponType(CVector pos, eWeaponType weaponType, uint8 type, uint32 quantity); + static void RemovePickUp(int32 pickupIndex); + static void RemoveAllFloatingPickups(); + static void AddToCollectedPickupsArray(int32 index); + static bool IsPickUpPickedUp(int32 pickupId); + static int32 ModelForWeapon(eWeaponType weaponType); + static enum eWeaponType WeaponForModel(int32 model); + static int32 FindColourIndexForWeaponMI(int32 model); + static int32 GetActualPickupIndex(int32 index); + static int32 GetNewUniquePickupIndex(int32 slot); + static void PassTime(uint32 time); + static bool GivePlayerGoodiesWithPickUpMI(int16 modelIndex, int playerIndex); + static void Load(uint8 *buf, uint32 size); + static void Save(uint8 *buf, uint32 *size); + + static CPickup aPickUps[NUMPICKUPS]; + + // unused + static bool bPickUpcamActivated; + static CVehicle *pPlayerVehicle; + static CVector StaticCamCoors; + static uint32 StaticCamStartTime; +}; + +extern uint16 AmmoForWeapon[20]; +extern uint16 AmmoForWeapon_OnStreet[20]; +extern uint16 CostOfWeapon[20]; + +enum ePacmanPickupType +{ + PACMAN_NONE, + PACMAN_SCRAMBLE, + PACMAN_RACE, +}; + +class CPacManPickup +{ +public: + CVector m_vecPosn; + CObject *m_pObject; + uint8 m_eType; + + void Update(); +}; + +class CPacManPickups +{ + friend class CPacManPickup; + + static CPacManPickup aPMPickUps[NUMPACMANPICKUPS]; + static CVector LastPickUpCoors; + static int PillsEatenInRace; + static bool bPMActive; +public: + static void Init(void); + static void Update(void); + static void GeneratePMPickUps(CVector, float, int16, uint8); + static void GeneratePMPickUpsForRace(int32); + static void GenerateOnePMPickUp(CVector); + static void Render(void); + static void StartPacManRace(int32); + static void StartPacManRecord(void); + static uint32 QueryPowerPillsEatenInRace(void); + static void ResetPowerPillsEatenInRace(void); + static void ClearPMPickUps(void); + static void CleanUpPacManStuff(void); + static void StartPacManScramble(CVector, float, int16); + static uint32 QueryPowerPillsCarriedByPlayer(void); + static void ResetPowerPillsCarriedByPlayer(void); + +}; diff --git a/src/control/PowerPoints.cpp b/src/control/PowerPoints.cpp new file mode 100644 index 0000000..9a74e8d --- /dev/null +++ b/src/control/PowerPoints.cpp @@ -0,0 +1,22 @@ +#include "common.h" +#include "PowerPoints.h" + +// Some cut beta feature + +void CPowerPoint::Update() +{} + +void CPowerPoints::Init() +{} + +void CPowerPoints::Update() +{} + +void CPowerPoints::GenerateNewOne(float, float, float, float, float, float, uint8) +{} + +void CPowerPoints::Save(uint8**, uint32*) +{} + +void CPowerPoints::Load(uint8*, uint32) +{} \ No newline at end of file diff --git a/src/control/PowerPoints.h b/src/control/PowerPoints.h new file mode 100644 index 0000000..ee3750c --- /dev/null +++ b/src/control/PowerPoints.h @@ -0,0 +1,26 @@ +#pragma once + +enum +{ + POWERPOINT_NONE = 0, + POWERPOINT_HEALTH, + POWERPOINT_HIDEOUT_INDUSTRIAL, + POWERPOINT_HIDEOUT_COMMERCIAL, + POWERPOINT_HIDEOUT_SUBURBAN +}; + +class CPowerPoint +{ +public: + void Update(); +}; + +class CPowerPoints +{ +public: + static void Init(); + static void Update(); + static void GenerateNewOne(float, float, float, float, float, float, uint8); + static void Save(uint8**, uint32*); + static void Load(uint8*, uint32); +}; \ No newline at end of file diff --git a/src/control/Record.cpp b/src/control/Record.cpp new file mode 100644 index 0000000..7f636ec --- /dev/null +++ b/src/control/Record.cpp @@ -0,0 +1,529 @@ +#include "common.h" + +#include "Record.h" + +#include "FileMgr.h" +#include "Pad.h" +#include "Pools.h" +#include "Streaming.h" +#include "Timer.h" +#include "VehicleModelInfo.h" +#include "World.h" +#include "Frontend.h" + +uint16 CRecordDataForGame::RecordingState; +uint8* CRecordDataForGame::pDataBuffer; +uint8* CRecordDataForGame::pDataBufferPointer; +int CRecordDataForGame::FId; +tGameBuffer CRecordDataForGame::pDataBufferForFrame; + +#define MEMORY_FOR_GAME_RECORD (150000) + +void CRecordDataForGame::Init(void) +{ + RecordingState = STATE_NONE; + delete[] pDataBuffer; + pDataBufferPointer = nil; + pDataBuffer = nil; +#ifndef GTA_PS2 // this stuff is not present on PS2 + FId = CFileMgr::OpenFile("playback.dat", "r"); + if (FId <= 0) { + if ((FId = CFileMgr::OpenFile("record.dat", "r")) <= 0) + RecordingState = STATE_NONE; + else { + CFileMgr::CloseFile(FId); + FId = CFileMgr::OpenFileForWriting("record.dat"); + RecordingState = STATE_RECORD; + } + } + else { + RecordingState = STATE_PLAYBACK; + } + if (RecordingState == STATE_PLAYBACK) { + pDataBufferPointer = new uint8[MEMORY_FOR_GAME_RECORD]; + pDataBuffer = pDataBufferPointer; + pDataBuffer[CFileMgr::Read(FId, (char*)pDataBufferPointer, MEMORY_FOR_GAME_RECORD) + 8] = (uint8)-1; + CFileMgr::CloseFile(FId); + } +#else + RecordingState = STATE_NONE; // second time to make sure +#endif +} + +void CRecordDataForGame::SaveOrRetrieveDataForThisFrame(void) +{ + switch (RecordingState) { + case STATE_RECORD: + { + pDataBufferForFrame.m_fTimeStep = CTimer::GetTimeStep(); + pDataBufferForFrame.m_nTimeInMilliseconds = CTimer::GetTimeInMilliseconds(); + pDataBufferForFrame.m_nSizeOfPads[0] = 0; + pDataBufferForFrame.m_nSizeOfPads[1] = 0; + pDataBufferForFrame.m_nChecksum = CalcGameChecksum(); + uint8* pController1 = PackCurrentPadValues(pDataBufferForFrame.m_ControllerBuffer, &CPad::GetPad(0)->OldState, &CPad::GetPad(0)->NewState); + pDataBufferForFrame.m_nSizeOfPads[0] = (pController1 - pDataBufferForFrame.m_ControllerBuffer) / 2; + uint8* pController2 = PackCurrentPadValues(pController1, &CPad::GetPad(1)->OldState, &CPad::GetPad(1)->NewState); + pDataBufferForFrame.m_nSizeOfPads[1] = (pController2 - pController1) / 2; + uint8* pEndPtr = pController2; + if ((pDataBufferForFrame.m_nSizeOfPads[0] + pDataBufferForFrame.m_nSizeOfPads[1]) & 1) + pEndPtr += 2; + CFileMgr::Write(FId, (char*)&pDataBufferForFrame, pEndPtr - (uint8*)&pDataBufferForFrame); + break; + } + case STATE_PLAYBACK: + if (pDataBufferPointer[8] == (uint8)-1) + CPad::GetPad(0)->NewState.Clear(); + else { + tGameBuffer* pData = (tGameBuffer*)pDataBufferPointer; + CTimer::SetTimeInMilliseconds(pData->m_nTimeInMilliseconds); + CTimer::SetTimeStep(pData->m_fTimeStep); + uint8 size1 = pData->m_nSizeOfPads[0]; + uint8 size2 = pData->m_nSizeOfPads[1]; + pDataBufferPointer = (uint8*)&pData->m_ControllerBuffer; + pDataBufferPointer = UnPackCurrentPadValues(pDataBufferPointer, size1, &CPad::GetPad(0)->NewState); + pDataBufferPointer = UnPackCurrentPadValues(pDataBufferPointer, size2, &CPad::GetPad(1)->NewState); + if ((size1 + size2) & 1) + pDataBufferPointer += 2; + if (pData->m_nChecksum != CalcGameChecksum()) + printf("Playback out of sync\n"); + } + } +} + +#define PROCESS_BUTTON_STATE_STORE(buf, os, ns, field, id) \ + do { \ + if (os->field != ns->field){ \ + *buf++ = id; \ + *buf++ = ns->field; \ + } \ + } while (0); + +uint8* CRecordDataForGame::PackCurrentPadValues(uint8* buf, CControllerState* os, CControllerState* ns) +{ + PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftStickX, 0); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftStickY, 1); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightStickX, 2); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightStickY, 3); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftShoulder1, 4); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftShoulder2, 5); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightShoulder1, 6); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightShoulder2, 7); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadUp, 8); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadDown, 9); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadLeft, 10); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadRight, 11); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Start, 12); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Select, 13); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Square, 14); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Triangle, 15); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Cross, 16); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Circle, 17); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftShock, 18); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightShock, 19); + return buf; +} +#undef PROCESS_BUTTON_STATE_STORE + +#define PROCESS_BUTTON_STATE_RESTORE(buf, state, field, id) case id: state->field = *buf++; break; + +uint8* CRecordDataForGame::UnPackCurrentPadValues(uint8* buf, uint8 total, CControllerState* state) +{ + for (uint8 i = 0; i < total; i++) { + switch (*buf++) { + PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftStickX, 0); + PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftStickY, 1); + PROCESS_BUTTON_STATE_RESTORE(buf, state, RightStickX, 2); + PROCESS_BUTTON_STATE_RESTORE(buf, state, RightStickY, 3); + PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftShoulder1, 4); + PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftShoulder2, 5); + PROCESS_BUTTON_STATE_RESTORE(buf, state, RightShoulder1, 6); + PROCESS_BUTTON_STATE_RESTORE(buf, state, RightShoulder2, 7); + PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadUp, 8); + PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadDown, 9); + PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadLeft, 10); + PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadRight, 11); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Start, 12); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Select, 13); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Square, 14); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Triangle, 15); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Cross, 16); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Circle, 17); + PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftShock, 18); + PROCESS_BUTTON_STATE_RESTORE(buf, state, RightShock, 19); + } + } + return buf; +} + +#undef PROCESS_BUTTON_STATE_RESTORE + +uint16 CRecordDataForGame::CalcGameChecksum(void) +{ + uint32 checksum = 0; + int i = CPools::GetPedPool()->GetSize(); + while (i--) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + checksum ^= pPed->GetModelIndex() ^ *(uint32*)&pPed->GetPosition().z ^ *(uint32*)&pPed->GetPosition().y ^ *(uint32*)&pPed->GetPosition().x; + } + i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + checksum ^= pVehicle->GetModelIndex() ^ *(uint32*)&pVehicle->GetPosition().z ^ *(uint32*)&pVehicle->GetPosition().y ^ *(uint32*)&pVehicle->GetPosition().x; + } + return checksum ^ checksum >> 16; +} + +uint8 CRecordDataForChase::Status; +int CRecordDataForChase::PositionChanges; +uint8 CRecordDataForChase::CurrentCar; +CAutomobile* CRecordDataForChase::pChaseCars[NUM_CHASE_CARS]; +uint32 CRecordDataForChase::AnimStartTime; +float CRecordDataForChase::AnimTime; +CCarStateEachFrame* CRecordDataForChase::pBaseMemForCar[NUM_CHASE_CARS]; +float CRecordDataForChase::TimeMultiplier; +int CRecordDataForChase::FId2; + +#define CHASE_SCENE_LENGTH_IN_SECONDS (80) +#define CHASE_SCENE_FRAMES_PER_SECOND (15) // skipping every second frame +#define CHASE_SCENE_FRAMES_IN_RECORDING (CHASE_SCENE_LENGTH_IN_SECONDS * CHASE_SCENE_FRAMES_PER_SECOND) +#define CHASE_SCENE_LENGTH_IN_FRAMES (CHASE_SCENE_FRAMES_IN_RECORDING * 2) + +void CRecordDataForChase::Init(void) +{ + Status = STATE_NONE; + PositionChanges = 0; + CurrentCar = 0; + for (int i = 0; i < NUM_CHASE_CARS; i++) + pChaseCars[i] = nil; + AnimStartTime = 0; +} + +void CRecordDataForChase::SaveOrRetrieveDataForThisFrame(void) +{ + switch (Status) { + case STATE_NONE: + return; + case STATE_RECORD: + { + if ((CTimer::GetFrameCounter() & 1) == 0) + StoreInfoForCar(pChaseCars[CurrentCar], &pBaseMemForCar[CurrentCar][CTimer::GetFrameCounter() / 2]); + if (CTimer::GetFrameCounter() < CHASE_SCENE_LENGTH_IN_FRAMES * 2) + return; + CFileMgr::SetDir("data\\paths"); + sprintf(gString, "chase%d.dat", CurrentCar); + int fid = CFileMgr::OpenFileForWriting(gString); + uint32 fs = CHASE_SCENE_LENGTH_IN_FRAMES * sizeof(CCarStateEachFrame); + printf("FileSize:%d\n", fs); + CFileMgr::Write(fid, (char*)pBaseMemForCar[CurrentCar], fs); + CFileMgr::CloseFile(fid); + CFileMgr::SetDir(""); + sprintf(gString, "car%d.max", CurrentCar); + int fid2 = CFileMgr::OpenFileForWriting(gString); + for (int i = 0; i < CHASE_SCENE_FRAMES_IN_RECORDING; i++) { + // WTF? Was it ever used? +#ifdef FIX_BUGS + CCarStateEachFrame* pState = pBaseMemForCar[CurrentCar]; +#else + CCarStateEachFrame* pState = (CCarStateEachFrame*)pChaseCars[CurrentCar]; +#endif + CVector right = CVector(pState->rightX, pState->rightY, pState->rightZ) / INT8_MAX; + CVector forward = CVector(pState->forwardX, pState->forwardY, pState->forwardZ) / INT8_MAX; + CVector up = CrossProduct(right, forward); + sprintf(gString, "%f %f %f\n", pState->pos.x, pState->pos.y, pState->pos.z); + CFileMgr::Write(fid2, gString, strlen(gString) - 1); + sprintf(gString, "%f %f %f\n", right.x, right.y, right.z); + CFileMgr::Write(fid2, gString, strlen(gString) - 1); + sprintf(gString, "%f %f %f\n", forward.x, forward.y, forward.z); + CFileMgr::Write(fid2, gString, strlen(gString) - 1); + sprintf(gString, "%f %f %f\n", up.x, up.y, up.z); + CFileMgr::Write(fid2, gString, strlen(gString) - 1); + } + CFileMgr::CloseFile(fid2); + } + case STATE_PLAYBACK: + case STATE_PLAYBACK_BEFORE_RECORDING: + case STATE_PLAYBACK_INIT: + break; + } +} + +struct tCoors { + CVector pos; + float angle; +}; + +// I guess developer was filling this with actual data before running the game +tCoors NewCoorsForRecordedCars[7]; + +void CRecordDataForChase::SaveOrRetrieveCarPositions(void) +{ + switch (Status) { + case STATE_NONE: + return; + case STATE_RECORD: + case STATE_PLAYBACK_BEFORE_RECORDING: + for (int i = 0; i < NUM_CHASE_CARS; i++) { + if (i != CurrentCar && CTimer::GetFrameCounter()) { + RestoreInfoForCar(pChaseCars[i], &pBaseMemForCar[i][CTimer::GetFrameCounter() / 2], false); + pChaseCars[i]->GetMatrix().UpdateRW(); + pChaseCars[i]->UpdateRwFrame(); + } + } + if (Status == STATE_PLAYBACK_BEFORE_RECORDING && CTimer::GetFrameCounter()) { + RestoreInfoForCar(pChaseCars[CurrentCar], &pBaseMemForCar[CurrentCar][CTimer::GetFrameCounter() / 2], false); + pChaseCars[CurrentCar]->GetMatrix().UpdateRW(); + pChaseCars[CurrentCar]->UpdateRwFrame(); + } + if (CPad::GetPad(0)->GetLeftShockJustDown() && CPad::GetPad(0)->GetRightShockJustDown()) { + if (!CPad::GetPad(0)->GetRightShockJustDown()) { + pChaseCars[CurrentCar]->SetPosition(NewCoorsForRecordedCars[PositionChanges].pos); + pChaseCars[CurrentCar]->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pChaseCars[CurrentCar]->GetMatrix().SetRotateZOnly(DEGTORAD(NewCoorsForRecordedCars[PositionChanges].angle)); + ++PositionChanges; + } + if (Status == STATE_PLAYBACK_BEFORE_RECORDING) { + Status = STATE_RECORD; + pChaseCars[CurrentCar]->SetStatus(STATUS_PLAYER); + } + } + break; + case STATE_PLAYBACK_INIT: + Status = STATE_PLAYBACK; + break; + case STATE_PLAYBACK: + { + TimeMultiplier += CTimer::GetTimeStepNonClippedInSeconds(); + float EndOfFrameTime = CHASE_SCENE_FRAMES_PER_SECOND * Min(CHASE_SCENE_LENGTH_IN_SECONDS, TimeMultiplier); + for (int i = 0; i < NUM_CHASE_CARS; i++) { + if (!pBaseMemForCar[i]) + continue; + if (!pChaseCars[i]) + continue; + if (EndOfFrameTime < CHASE_SCENE_FRAMES_IN_RECORDING - 1) { + int FlooredEOFTime = EndOfFrameTime; + RestoreInfoForCar(pChaseCars[i], &pBaseMemForCar[i][FlooredEOFTime], false); + CMatrix tmp; + float dp = EndOfFrameTime - FlooredEOFTime; + RestoreInfoForMatrix(tmp, &pBaseMemForCar[i][FlooredEOFTime + 1]); + pChaseCars[i]->GetRight() += (tmp.GetRight() - pChaseCars[i]->GetRight()) * dp; + pChaseCars[i]->GetForward() += (tmp.GetForward() - pChaseCars[i]->GetForward()) * dp; + pChaseCars[i]->GetUp() += (tmp.GetUp() - pChaseCars[i]->GetUp()) * dp; + pChaseCars[i]->GetMatrix().GetPosition() += (tmp.GetPosition() - pChaseCars[i]->GetPosition()) * dp; + } + else{ + RestoreInfoForCar(pChaseCars[i], &pBaseMemForCar[i][CHASE_SCENE_FRAMES_IN_RECORDING - 1], true); + if (i == 0) + pChaseCars[i]->GetMatrix().GetPosition().z += 0.2f; + } + pChaseCars[i]->GetMatrix().UpdateRW(); + pChaseCars[i]->UpdateRwFrame(); + pChaseCars[i]->RemoveAndAdd(); + } + break; + } + } +} + +void CRecordDataForChase::StoreInfoForCar(CAutomobile* pCar, CCarStateEachFrame* pState) +{ + pState->rightX = INT8_MAX * pCar->GetRight().x; + pState->rightY = INT8_MAX * pCar->GetRight().y; + pState->rightZ = INT8_MAX * pCar->GetRight().z; + pState->forwardX = INT8_MAX * pCar->GetForward().x; + pState->forwardY = INT8_MAX * pCar->GetForward().y; + pState->forwardZ = INT8_MAX * pCar->GetForward().z; + pState->pos = pCar->GetPosition(); + pState->velX = 0.5f * INT16_MAX * pCar->GetMoveSpeed().x; + pState->velY = 0.5f * INT16_MAX * pCar->GetMoveSpeed().y; + pState->velZ = 0.5f * INT16_MAX * pCar->GetMoveSpeed().z; + pState->wheel = 20 * pCar->m_fSteerAngle; + pState->gas = 100 * pCar->m_fGasPedal; + pState->brake = 100 * pCar->m_fBrakePedal; + pState->handbrake = pCar->bIsHandbrakeOn; +} + +void CRecordDataForChase::RestoreInfoForMatrix(CMatrix& matrix, CCarStateEachFrame* pState) +{ + matrix.GetRight() = CVector(pState->rightX, pState->rightY, pState->rightZ) / INT8_MAX; + matrix.GetForward() = CVector(pState->forwardX, pState->forwardY, pState->forwardZ) / INT8_MAX; + matrix.GetUp() = CrossProduct(matrix.GetRight(), matrix.GetForward()); + matrix.GetPosition() = pState->pos; +} + +void CRecordDataForChase::RestoreInfoForCar(CAutomobile* pCar, CCarStateEachFrame* pState, bool stop) +{ + CVector oldPos = pCar->GetPosition(); + RestoreInfoForMatrix(pCar->GetMatrix(), pState); + pCar->SetMoveSpeed(CVector(pState->velX, pState->velY, pState->velZ) / INT16_MAX / 0.5f); + pCar->SetTurnSpeed(0.0f, 0.0f, 0.0f); + pCar->m_fSteerAngle = pState->wheel / 20.0f; + pCar->m_fGasPedal = pState->gas / 100.0f; + pCar->m_fBrakePedal = pState->brake / 100.0f; + pCar->bIsHandbrakeOn = pState->handbrake; + if ((oldPos - pCar->GetPosition()).Magnitude() > 15.0f) { + if (pCar == pChaseCars[14]) { + pCar->m_currentColour1 = 58; + pCar->m_currentColour2 = 1; + } + else + pCar->GetModelInfo()->ChooseVehicleColour(pCar->m_currentColour1, pCar->m_currentColour2); + } + pCar->m_fHealth = Min(pCar->m_fHealth, 500.0f); + if (stop) { + pCar->m_fGasPedal = 0.0f; + pCar->m_fBrakePedal = 0.0f; + pCar->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pCar->bIsHandbrakeOn = false; + } +} + +void CRecordDataForChase::ProcessControlCars(void) +{ + if (Status != STATE_PLAYBACK) + return; + for (int i = 0; i < NUM_CHASE_CARS; i++) { + if (pChaseCars[i]) + pChaseCars[i]->ProcessControl(); + } +} + +bool CRecordDataForChase::ShouldThisPadBeLeftAlone(uint8 pad) +{ + // may be wrong + if (Status == STATE_PLAYBACK_INIT) // this is useless but ps2 def checks if it's STATE_PLAYBACK_INIT + return false; + + if (Status == STATE_RECORD) + return pad != 0; + + return false; +} + +void CRecordDataForChase::GiveUsACar(int32 mi, CVector pos, float angle, CAutomobile** ppCar, uint8 colour1, uint8 colour2) +{ + CStreaming::RequestModel(mi, STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + if (!CStreaming::HasModelLoaded(mi)) + return; + CAutomobile* pCar = new CAutomobile(mi, MISSION_VEHICLE); + pCar->SetPosition(pos); + pCar->SetStatus(STATUS_PLAYER_PLAYBACKFROMBUFFER); + pCar->GetMatrix().SetRotateZOnly(DEGTORAD(angle)); + pCar->pDriver = nil; + pCar->m_currentColour1 = colour1; + pCar->m_currentColour2 = colour2; + CWorld::Add(pCar); + *ppCar = pCar; +} + +void RemoveUnusedCollision(void) +{ + static const char* dontDeleteArray[] = { + "rd_SrRoad2A50", "rd_SrRoad2A20", "rd_CrossRda1w22", "rd_CrossRda1rw22", + "road_broadway02", "road_broadway01", "com_21way5", "com_21way50", + "cm1waycrosscom", "com_21way20", "com_21way10", "road_broadway04", + "com_rvroads52", "com_roadsrv", "com_roadkb23", "com_roadkb22" + }; + for (int i = 0; i < ARRAY_SIZE(dontDeleteArray); i++) + CModelInfo::GetModelInfo(dontDeleteArray[i], nil)->GetColModel()->level = LEVEL_GENERIC; + CModelInfo::RemoveColModelsFromOtherLevels(LEVEL_GENERIC); + for (int i = 0; i < ARRAY_SIZE(dontDeleteArray); i++) + CModelInfo::GetModelInfo(dontDeleteArray[i], nil)->GetColModel()->level = LEVEL_COMMERCIAL; +} + +void CRecordDataForChase::StartChaseScene(float startTime) +{ + char filename[28]; + SetUpCarsForChaseScene(); + Status = STATE_PLAYBACK; + AnimTime = startTime; + AnimStartTime = CTimer::GetTimeInMilliseconds(); +#ifdef NO_ISLAND_LOADING + if (CMenuManager::m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_LOW) +#endif + RemoveUnusedCollision(); + CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN); + CGame::TidyUpMemory(true, true); + CStreaming::ImGonnaUseStreamingMemory(); + CFileMgr::SetDir("data\\paths"); + for (int i = 0; i < NUM_CHASE_CARS; i++) { + if (!pChaseCars[i]) { + pBaseMemForCar[i] = nil; + continue; + } + sprintf(filename, "chase%d.dat", i); + FId2 = CFileMgr::OpenFile(filename, "rb"); + if (FId2 <= 0) { + pBaseMemForCar[i] = nil; + continue; + } + pBaseMemForCar[i] = new CCarStateEachFrame[CHASE_SCENE_FRAMES_IN_RECORDING]; + for (int j = 0; j < CHASE_SCENE_FRAMES_IN_RECORDING; j++) { + CFileMgr::Read(FId2, (char*)&pBaseMemForCar[i][j], sizeof(CCarStateEachFrame)); + CFileMgr::Seek(FId2, sizeof(CCarStateEachFrame), 1); + } + CFileMgr::CloseFile(FId2); + } + CFileMgr::SetDir(""); + CStreaming::IHaveUsedStreamingMemory(); + TimeMultiplier = 0.0f; +} + +void CRecordDataForChase::CleanUpChaseScene(void) +{ + if (Status != STATE_PLAYBACK_INIT && Status != STATE_PLAYBACK) + return; + Status = STATE_NONE; + CleanUpCarsForChaseScene(); + for (int i = 0; i < NUM_CHASE_CARS; i++) { + if (pBaseMemForCar[i]) { + delete[] pBaseMemForCar[i]; + pBaseMemForCar[i] = nil; + } + } +} + +void CRecordDataForChase::SetUpCarsForChaseScene(void) +{ + GiveUsACar(MI_POLICE, CVector(273.54221f, -1167.1907f, 24.880601f), 63.0f, &pChaseCars[0], 2, 1); + GiveUsACar(MI_ENFORCER, CVector(231.1783f, -1388.8322f, 25.978201f), 90.0f, &pChaseCars[1], 2, 1); + GiveUsACar(MI_TAXI, CVector(184.3156f, -1473.251f, 25.978201f), 0.0f, &pChaseCars[4], 6, 6); + GiveUsACar(MI_CHEETAH, CVector(173.8868f, -1377.6514f, 25.978201f), 0.0f, &pChaseCars[6], 4, 5); + GiveUsACar(MI_STINGER, CVector(102.5946f, -943.93628f, 25.9781f), 270.0f, &pChaseCars[7], 53, 53); + GiveUsACar(MI_CHEETAH, CVector(-177.7157f, -862.18652f, 25.978201f), 155.0f, &pChaseCars[10], 41, 1); + GiveUsACar(MI_STINGER, CVector(-170.56979f, -889.02362f, 25.978201f), 154.0f, &pChaseCars[11], 10, 10); + GiveUsACar(MI_KURUMA, CVector(402.60809f, -917.49628f, 37.381001f), 90.0f, &pChaseCars[14], 34, 1); + GiveUsACar(MI_TAXI, CVector(-33.496201f, -938.4563f, 25.9781f), 266.0f, &pChaseCars[16], 6, 6); + GiveUsACar(MI_KURUMA, CVector(49.363098f, -987.60498f, 25.9781f), 0.0f, &pChaseCars[18], 51, 1); + GiveUsACar(MI_TAXI, CVector(179.0049f, -1154.6686f, 25.9781f), 0.0f, &pChaseCars[19], 6, 76); + GiveUsACar(MI_RUMPO, CVector(-28.9762f, -1031.3367f, 25.990601f), 242.0f, &pChaseCars[2], 1, 75); + GiveUsACar(MI_PATRIOT, CVector(114.1564f, -796.69379f, 24.978201f), 180.0f, &pChaseCars[3], 0, 0); +} + +void CRecordDataForChase::CleanUpCarsForChaseScene(void) +{ + for (int i = 0; i < NUM_CHASE_CARS; i++) + RemoveCarFromChase(i); +} + +void CRecordDataForChase::RemoveCarFromChase(int32 i) +{ + if (!pChaseCars[i]) + return; + CWorld::Remove(pChaseCars[i]); + delete pChaseCars[i]; + pChaseCars[i] = nil; +} + +CVehicle* CRecordDataForChase::TurnChaseCarIntoScriptCar(int32 i) +{ + CVehicle* pVehicle = pChaseCars[i]; + pChaseCars[i] = nil; + pVehicle->SetStatus(STATUS_PHYSICS); + return pVehicle; +} + diff --git a/src/control/Record.h b/src/control/Record.h new file mode 100644 index 0000000..6a94c40 --- /dev/null +++ b/src/control/Record.h @@ -0,0 +1,104 @@ +#pragma once + +class CAutomobile; +class CVehicle; +class CControllerState; + +class CCarStateEachFrame +{ +public: + int16 velX; + int16 velY; + int16 velZ; + int8 rightX; + int8 rightY; + int8 rightZ; + int8 forwardX; + int8 forwardY; + int8 forwardZ; + int8 wheel; + int8 gas; + int8 brake; + bool handbrake; + CVector pos; +}; + +extern char gString[256]; + +class CRecordDataForChase +{ + enum { + NUM_CHASE_CARS = 20 + }; + enum { + STATE_NONE = 0, + STATE_RECORD = 1, + STATE_PLAYBACK_INIT = 2, + STATE_PLAYBACK = 3, + STATE_PLAYBACK_BEFORE_RECORDING = 4 + }; + static uint8 Status; + static int PositionChanges; + static uint8 CurrentCar; + static CAutomobile*pChaseCars[NUM_CHASE_CARS]; + static float AnimTime; + static uint32 AnimStartTime; + static CCarStateEachFrame* pBaseMemForCar[NUM_CHASE_CARS]; + static float TimeMultiplier; + static int FId2; +public: + + static bool IsRecording(void) { return Status == STATE_RECORD; } + + static void Init(void); + static void SaveOrRetrieveDataForThisFrame(void); + static void SaveOrRetrieveCarPositions(void); + static void StoreInfoForCar(CAutomobile*, CCarStateEachFrame*); + static void RestoreInfoForMatrix(CMatrix&, CCarStateEachFrame*); + static void RestoreInfoForCar(CAutomobile*, CCarStateEachFrame*, bool); + static void ProcessControlCars(void); + static bool ShouldThisPadBeLeftAlone(uint8 pad); + static void GiveUsACar(int32, CVector, float, CAutomobile**, uint8, uint8); + static void StartChaseScene(float); + static void CleanUpChaseScene(void); + static void SetUpCarsForChaseScene(void); + static void CleanUpCarsForChaseScene(void); + static void RemoveCarFromChase(int32); + static CVehicle* TurnChaseCarIntoScriptCar(int32); + +}; + +struct tGameBuffer +{ + float m_fTimeStep; + uint32 m_nTimeInMilliseconds; + uint8 m_nSizeOfPads[2]; + uint16 m_nChecksum; + uint8 m_ControllerBuffer[116]; +}; + +class CRecordDataForGame +{ + enum { + STATE_NONE = 0, + STATE_RECORD = 1, + STATE_PLAYBACK = 2, + }; + static uint16 RecordingState; + static uint8* pDataBuffer; + static uint8* pDataBufferPointer; + static int FId; + static tGameBuffer pDataBufferForFrame; + +public: + static bool IsRecording() { return RecordingState == STATE_RECORD; } + static bool IsPlayingBack() { return RecordingState == STATE_PLAYBACK; } + + static void SaveOrRetrieveDataForThisFrame(void); + static void Init(void); + +private: + static uint16 CalcGameChecksum(void); + static uint8* PackCurrentPadValues(uint8*, CControllerState*, CControllerState*); + static uint8* UnPackCurrentPadValues(uint8*, uint8, CControllerState*); +}; diff --git a/src/control/Remote.cpp b/src/control/Remote.cpp new file mode 100644 index 0000000..904e902 --- /dev/null +++ b/src/control/Remote.cpp @@ -0,0 +1,51 @@ +#include "common.h" + +#include "Automobile.h" +#include "CarCtrl.h" +#include "Camera.h" +#include "Remote.h" +#include "Timer.h" +#include "World.h" +#include "PlayerInfo.h" +#include "Vehicle.h" + +void +CRemote::GivePlayerRemoteControlledCar(float x, float y, float z, float rot, uint16 model) +{ + CAutomobile *car = new CAutomobile(model, MISSION_VEHICLE); + bool found; + + z = car->GetDistanceFromCentreOfMassToBaseOfModel() + CWorld::FindGroundZFor3DCoord(x, y, z + 2.0f, &found); + + car->GetMatrix().SetRotateZOnly(rot); + car->SetPosition(x, y, z); + car->SetStatus(STATUS_PLAYER_REMOTE); + car->bIsLocked = true; + + CCarCtrl::JoinCarWithRoadSystem(car); + car->AutoPilot.m_nCarMission = MISSION_NONE; + car->AutoPilot.m_nTempAction = TEMPACT_NONE; + car->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + car->AutoPilot.m_nCruiseSpeed = car->AutoPilot.m_fMaxTrafficSpeed = 9.0f; + car->AutoPilot.m_nNextLane = car->AutoPilot.m_nCurrentLane = 0; + car->bEngineOn = true; + CWorld::Add(car); + if (FindPlayerVehicle() != nil) + FindPlayerVehicle()->SetStatus(STATUS_PLAYER_DISABLED); + + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle = car; + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->RegisterReference((CEntity**)&CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle); + TheCamera.TakeControl(car, CCam::MODE_BEHINDCAR, INTERPOLATION, CAMCONTROL_SCRIPT); +} + +void +CRemote::TakeRemoteControlledCarFromPlayer(void) +{ + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->VehicleCreatedBy = RANDOM_VEHICLE; + CCarCtrl::NumMissionCars--; + CCarCtrl::NumRandomCars++; + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->bIsLocked = false; + CWorld::Players[CWorld::PlayerInFocus].m_nTimeLostRemoteCar = CTimer::GetTimeInMilliseconds(); + CWorld::Players[CWorld::PlayerInFocus].m_bInRemoteMode = true; + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->bRemoveFromWorld = true; +} diff --git a/src/control/Remote.h b/src/control/Remote.h new file mode 100644 index 0000000..5e47458 --- /dev/null +++ b/src/control/Remote.h @@ -0,0 +1,8 @@ +#pragma once + +class CRemote +{ +public: + static void GivePlayerRemoteControlledCar(float, float, float, float, uint16); + static void TakeRemoteControlledCarFromPlayer(void); +}; diff --git a/src/control/Replay.cpp b/src/control/Replay.cpp new file mode 100644 index 0000000..b9b5530 --- /dev/null +++ b/src/control/Replay.cpp @@ -0,0 +1,1635 @@ +#include "common.h" +#ifdef GTA_REPLAY +#include "AnimBlendAssociation.h" +#include "Boat.h" +#include "SpecialFX.h" +#include "CarCtrl.h" +#include "CivilianPed.h" +#include "Wanted.h" +#include "Clock.h" +#include "DMAudio.h" +#include "Draw.h" +#include "FileMgr.h" +#ifdef FIX_BUGS +#include "Fire.h" +#include "Garages.h" +#endif +#include "Heli.h" +#include "main.h" +#include "Matrix.h" +#include "ModelIndices.h" +#include "ModelInfo.h" +#include "Object.h" +#include "Pad.h" +#include "Phones.h" +#include "Pickups.h" +#include "Plane.h" +#include "Pools.h" +#include "Population.h" +#ifdef FIX_BUGS +#include "Projectile.h" +#include "ProjectileInfo.h" +#endif +#include "Replay.h" +#include "References.h" +#include "Pools.h" +#include "RpAnimBlend.h" +#include "RwHelper.h" +#include "CutsceneMgr.h" +#include "Skidmarks.h" +#include "Streaming.h" +#include "Timer.h" +#include "Train.h" +#include "Weather.h" +#include "Zones.h" +#include "Font.h" +#include "Text.h" +#include "Camera.h" +#include "Radar.h" + +uint8 CReplay::Mode; +CAddressInReplayBuffer CReplay::Record; +CAddressInReplayBuffer CReplay::Playback; +uint8 *CReplay::pBuf0; +CAutomobile *CReplay::pBuf1; +uint8 *CReplay::pBuf2; +CPlayerPed *CReplay::pBuf3; +uint8 *CReplay::pBuf4; +CCutsceneHead *CReplay::pBuf5; +uint8 *CReplay::pBuf6; +CPtrNode *CReplay::pBuf7; +uint8 *CReplay::pBuf8; +CEntryInfoNode *CReplay::pBuf9; +uint8 *CReplay::pBuf10; +CDummyPed *CReplay::pBuf11; +uint8 *CReplay::pRadarBlips; +uint8 *CReplay::pStoredCam; +uint8 *CReplay::pWorld1; +CReference *CReplay::pEmptyReferences; +CStoredDetailedAnimationState *CReplay::pPedAnims; +uint8 *CReplay::pPickups; +uint8 *CReplay::pReferences; +uint8 CReplay::BufferStatus[NUM_REPLAYBUFFERS]; +uint8 CReplay::Buffers[NUM_REPLAYBUFFERS][REPLAYBUFFERSIZE]; +bool CReplay::bPlayingBackFromFile; +bool CReplay::bReplayEnabled = true; +uint32 CReplay::SlowMotion; +uint32 CReplay::FramesActiveLookAroundCam; +bool CReplay::bDoLoadSceneWhenDone; +CPtrNode* CReplay::WorldPtrList; +CPtrNode* CReplay::BigBuildingPtrList; +CWanted CReplay::PlayerWanted; +CPlayerInfo CReplay::PlayerInfo; +uint32 CReplay::Time1; +uint32 CReplay::Time2; +uint32 CReplay::Time3; +uint32 CReplay::Time4; +uint32 CReplay::Frame; +uint8 CReplay::ClockHours; +uint8 CReplay::ClockMinutes; +uint16 CReplay::OldWeatherType; +uint16 CReplay::NewWeatherType; +float CReplay::WeatherInterpolationValue; +float CReplay::TimeStepNonClipped; +float CReplay::TimeStep; +float CReplay::TimeScale; +float CReplay::CameraFixedX; +float CReplay::CameraFixedY; +float CReplay::CameraFixedZ; +int32 CReplay::OldRadioStation; +int8 CReplay::CameraMode; +bool CReplay::bAllowLookAroundCam; +float CReplay::LoadSceneX; +float CReplay::LoadSceneY; +float CReplay::LoadSceneZ; +float CReplay::CameraFocusX; +float CReplay::CameraFocusY; +float CReplay::CameraFocusZ; +bool CReplay::bPlayerInRCBuggy; +float CReplay::fDistanceLookAroundCam; +float CReplay::fBetaAngleLookAroundCam; +float CReplay::fAlphaAngleLookAroundCam; +#ifdef FIX_BUGS +uint8* CReplay::pGarages; +CFire* CReplay::FireArray; +uint32 CReplay::NumOfFires; +uint8* CReplay::paProjectileInfo; +uint8* CReplay::paProjectiles; +int CReplay::nHandleOfPlayerPed[NUMPLAYERS]; +#endif + +static void(*CBArray[])(CAnimBlendAssociation*, void*) = +{ + nil, &CPed::PedGetupCB, &CPed::PedStaggerCB, &CPed::PedEvadeCB, &CPed::FinishDieAnimCB, + &CPed::FinishedWaitCB, &CPed::FinishLaunchCB, &CPed::FinishHitHeadCB, &CPed::PedAnimGetInCB, &CPed::PedAnimDoorOpenCB, + &CPed::PedAnimPullPedOutCB, &CPed::PedAnimDoorCloseCB, &CPed::PedSetInCarCB, &CPed::PedSetOutCarCB, &CPed::PedAnimAlignCB, + &CPed::PedSetDraggedOutCarCB, &CPed::PedAnimStepOutCarCB, &CPed::PedSetInTrainCB, &CPed::PedSetOutTrainCB, &CPed::FinishedAttackCB, + &CPed::FinishFightMoveCB, &PhonePutDownCB, &PhonePickUpCB, &CPed::PedAnimDoorCloseRollingCB, &CPed::FinishJumpCB, + &CPed::PedLandCB, &FinishFuckUCB, &CPed::RestoreHeadingRateCB, &CPed::PedSetQuickDraggedOutCarPositionCB, &CPed::PedSetDraggedOutCarPositionCB +}; + +static uint8 FindCBFunctionID(void(*f)(CAnimBlendAssociation*, void*)) +{ + for (int i = 0; i < sizeof(CBArray) / sizeof(*CBArray); i++){ + if (CBArray[i] == f) + return i; + } + + return 0; +} + +static void(*FindCBFunction(uint8 id))(CAnimBlendAssociation*, void*) +{ + return CBArray[id]; +} + +static void ApplyPanelDamageToCar(uint32 panels, CAutomobile* vehicle, bool flying) +{ + if(vehicle->Damage.GetPanelStatus(VEHPANEL_FRONT_LEFT) != CDamageManager::GetPanelStatus(panels, VEHPANEL_FRONT_LEFT)){ + vehicle->Damage.SetPanelStatus(VEHPANEL_FRONT_LEFT, CDamageManager::GetPanelStatus(panels, VEHPANEL_FRONT_LEFT)); + vehicle->SetPanelDamage(CAR_WING_LF, VEHPANEL_FRONT_LEFT, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHPANEL_FRONT_RIGHT) != CDamageManager::GetPanelStatus(panels, VEHPANEL_FRONT_RIGHT)){ + vehicle->Damage.SetPanelStatus(VEHPANEL_FRONT_RIGHT, CDamageManager::GetPanelStatus(panels, VEHPANEL_FRONT_RIGHT)); + vehicle->SetPanelDamage(CAR_WING_RF, VEHPANEL_FRONT_RIGHT, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHPANEL_REAR_LEFT) != CDamageManager::GetPanelStatus(panels, VEHPANEL_REAR_LEFT)){ + vehicle->Damage.SetPanelStatus(VEHPANEL_REAR_LEFT, CDamageManager::GetPanelStatus(panels, VEHPANEL_REAR_LEFT)); + vehicle->SetPanelDamage(CAR_WING_LR, VEHPANEL_REAR_LEFT, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHPANEL_REAR_RIGHT) != CDamageManager::GetPanelStatus(panels, VEHPANEL_REAR_RIGHT)){ + vehicle->Damage.SetPanelStatus(VEHPANEL_REAR_RIGHT, CDamageManager::GetPanelStatus(panels, VEHPANEL_REAR_RIGHT)); + vehicle->SetPanelDamage(CAR_WING_RR, VEHPANEL_REAR_RIGHT, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHPANEL_WINDSCREEN) != CDamageManager::GetPanelStatus(panels, VEHPANEL_WINDSCREEN)){ + vehicle->Damage.SetPanelStatus(VEHPANEL_WINDSCREEN, CDamageManager::GetPanelStatus(panels, VEHPANEL_WINDSCREEN)); + vehicle->SetPanelDamage(CAR_WINDSCREEN, VEHPANEL_WINDSCREEN, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHBUMPER_FRONT) != CDamageManager::GetPanelStatus(panels, VEHBUMPER_FRONT)){ + vehicle->Damage.SetPanelStatus(VEHBUMPER_FRONT, CDamageManager::GetPanelStatus(panels, VEHBUMPER_FRONT)); + vehicle->SetPanelDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHBUMPER_REAR) != CDamageManager::GetPanelStatus(panels, VEHBUMPER_REAR)){ + vehicle->Damage.SetPanelStatus(VEHBUMPER_REAR, CDamageManager::GetPanelStatus(panels, VEHBUMPER_REAR)); + vehicle->SetPanelDamage(CAR_BUMP_REAR, VEHBUMPER_REAR, flying); + } +} + +void PrintElementsInPtrList(void) +{ + for (CPtrNode* node = CWorld::GetBigBuildingList(LEVEL_GENERIC).first; node; node = node->next) { + /* Most likely debug print was present here */ + } +} + +void CReplay::Init(void) +{ + pBuf0 = nil; + pBuf1 = nil; + pBuf2 = nil; + pBuf3 = nil; + pBuf4 = nil; + pBuf5 = nil; + pBuf6 = nil; + pBuf7 = nil; + pBuf8 = nil; + pBuf9 = nil; + pBuf10 = nil; + pBuf11 = nil; + pRadarBlips = nil; + pStoredCam = nil; + pWorld1 = nil; + pEmptyReferences = nil; + pPedAnims = nil; + pPickups = nil; + pReferences = nil; + Mode = MODE_RECORD; + Playback.m_nOffset = 0; + Playback.m_pBase = nil; + Playback.m_bSlot = 0; + Record.m_nOffset = 0; + Record.m_pBase = nil; + Record.m_bSlot = 0; + for (int i = 0; i < NUM_REPLAYBUFFERS; i++) + BufferStatus[i] = REPLAYBUFFER_UNUSED; + Record.m_bSlot = 0; + Record.m_pBase = Buffers[0]; + BufferStatus[0] = REPLAYBUFFER_RECORD; + Buffers[0][Record.m_nOffset] = REPLAYPACKET_END; + bPlayingBackFromFile = false; + bReplayEnabled = true; + SlowMotion = 1; + FramesActiveLookAroundCam = 0; + bDoLoadSceneWhenDone = false; +} + +void CReplay::DisableReplays(void) +{ + bReplayEnabled = false; +} + +void CReplay::EnableReplays(void) +{ + bReplayEnabled = true; +} + +void PlayReplayFromHD(void); +void CReplay::Update(void) +{ + if (CCutsceneMgr::IsCutsceneProcessing() || CTimer::GetIsPaused()) + return; + switch (Mode){ + case MODE_RECORD: + RecordThisFrame(); + break; + case MODE_PLAYBACK: + PlaybackThisFrame(); + break; + } + if (CDraw::FadeValue || !bReplayEnabled) + return; + if (Mode == MODE_PLAYBACK){ + if (CPad::GetPad(0)->GetFJustDown(0)) + FinishPlayback(); + } + else if (Mode == MODE_RECORD){ + if (CPad::GetPad(0)->GetFJustDown(0)) + TriggerPlayback(REPLAYCAMMODE_ASSTORED, 0.0f, 0.0f, 0.0f, false); + if (CPad::GetPad(0)->GetFJustDown(1)) + SaveReplayToHD(); + if (CPad::GetPad(0)->GetFJustDown(2)) + PlayReplayFromHD(); +#ifdef USE_BETA_REPLAY_MODE + if (CPad::GetPad(0)->GetFJustDown(3)) + TriggerPlaybackLastCoupleOfSeconds(5000, REPLAYCAMMODE_TOPDOWN, 0.0f, 0.0f, 0.0f, 4); +#endif + } +} + +void CReplay::RecordThisFrame(void) +{ +#ifdef FIX_REPLAY_BUGS + uint32 memory_required = sizeof(tGeneralPacket) + sizeof(tClockPacket) + sizeof(tWeatherPacket) + sizeof(tTimerPacket); + CVehiclePool* vehiclesT = CPools::GetVehiclePool(); + for (int i = 0; i < vehiclesT->GetSize(); i++) { + CVehicle* v = vehiclesT->GetSlot(i); + if (v && v->m_rwObject && v->GetModelIndex() != MI_AIRTRAIN && v->GetModelIndex() != MI_TRAIN) + memory_required += sizeof(tVehicleUpdatePacket); + } + CPedPool* pedsT = CPools::GetPedPool(); + for (int i = 0; i < pedsT->GetSize(); i++) { + CPed* p = pedsT->GetSlot(i); + if (!p || !p->m_rwObject) + continue; + if (!p->bHasAlreadyBeenRecorded) { + memory_required += sizeof(tPedHeaderPacket); + } + memory_required += sizeof(tPedUpdatePacket); + } + for (uint8 i = 0; i < NUMBULLETTRACES; i++) { + if (!CBulletTraces::aTraces[i].m_bInUse) + continue; + memory_required += sizeof(tBulletTracePacket); + } + memory_required += sizeof(tEndOfFramePacket) + 1; // 1 for Record.m_pBase[Record.m_nOffset] = REPLAYPACKET_END; + if (Record.m_nOffset + memory_required > REPLAYBUFFERSIZE) { + Record.m_pBase[Record.m_nOffset] = REPLAYPACKET_END; + BufferStatus[Record.m_bSlot] = REPLAYBUFFER_PLAYBACK; + Record.m_bSlot = (Record.m_bSlot + 1) % NUM_REPLAYBUFFERS; + BufferStatus[Record.m_bSlot] = REPLAYBUFFER_RECORD; + Record.m_pBase = Buffers[Record.m_bSlot]; + Record.m_nOffset = 0; + *Record.m_pBase = REPLAYPACKET_END; + MarkEverythingAsNew(); + } +#endif + tGeneralPacket* general = (tGeneralPacket*)&Record.m_pBase[Record.m_nOffset]; + general->type = REPLAYPACKET_GENERAL; + general->camera_pos.CopyOnlyMatrix(TheCamera.GetMatrix()); + general->player_pos = FindPlayerCoors(); + general->in_rcvehicle = CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle ? true : false; + Record.m_nOffset += sizeof(*general); + tClockPacket* clock = (tClockPacket*)&Record.m_pBase[Record.m_nOffset]; + clock->type = REPLAYPACKET_CLOCK; + clock->hours = CClock::GetHours(); + clock->minutes = CClock::GetMinutes(); + Record.m_nOffset += sizeof(*clock); + tWeatherPacket* weather = (tWeatherPacket*)&Record.m_pBase[Record.m_nOffset]; + weather->type = REPLAYPACKET_WEATHER; + weather->old_weather = CWeather::OldWeatherType; + weather->new_weather = CWeather::NewWeatherType; + weather->interpolation = CWeather::InterpolationValue; + Record.m_nOffset += sizeof(*weather); + tTimerPacket* timer = (tTimerPacket*)&Record.m_pBase[Record.m_nOffset]; + timer->type = REPLAYPACKET_TIMER; + timer->timer = CTimer::GetTimeInMilliseconds(); + Record.m_nOffset += sizeof(*timer); + CVehiclePool* vehicles = CPools::GetVehiclePool(); + for (int i = 0; i < vehicles->GetSize(); i++){ + CVehicle* v = vehicles->GetSlot(i); + if (v && v->m_rwObject && v->GetModelIndex() != MI_AIRTRAIN && v->GetModelIndex() != MI_TRAIN) + StoreCarUpdate(v, i); + } + CPedPool* peds = CPools::GetPedPool(); + for (int i = 0; i < peds->GetSize(); i++) { + CPed* p = peds->GetSlot(i); + if (!p || !p->m_rwObject) + continue; + if (!p->bHasAlreadyBeenRecorded){ + tPedHeaderPacket* ph = (tPedHeaderPacket*)&Record.m_pBase[Record.m_nOffset]; + ph->type = REPLAYPACKET_PED_HEADER; + ph->index = i; + ph->mi = p->GetModelIndex(); + ph->pedtype = p->m_nPedType; + Record.m_nOffset += sizeof(*ph); + p->bHasAlreadyBeenRecorded = true; + } + StorePedUpdate(p, i); + } + for (uint8 i = 0; i < NUMBULLETTRACES; i++){ + if (!CBulletTraces::aTraces[i].m_bInUse) + continue; + tBulletTracePacket* bt = (tBulletTracePacket*)&Record.m_pBase[Record.m_nOffset]; + bt->type = REPLAYPACKET_BULLET_TRACES; + bt->index = i; + bt->frames = CBulletTraces::aTraces[i].m_framesInUse; + bt->lifetime = CBulletTraces::aTraces[i].m_lifeTime; + bt->inf = CBulletTraces::aTraces[i].m_vecCurrentPos; + bt->sup = CBulletTraces::aTraces[i].m_vecTargetPos; + Record.m_nOffset += sizeof(*bt); + } + tEndOfFramePacket* eof = (tEndOfFramePacket*)&Record.m_pBase[Record.m_nOffset]; + eof->type = REPLAYPACKET_ENDOFFRAME; + Record.m_nOffset += sizeof(*eof); + Record.m_pBase[Record.m_nOffset] = REPLAYPACKET_END; +#ifndef FIX_REPLAY_BUGS + if (Record.m_nOffset <= REPLAYBUFFERSIZE - 3000){ + /* Unsafe assumption which can cause buffer overflow + * if size of next frame exceeds 3000 bytes. + * Most notably it causes various timecyc errors. */ + return; + } + BufferStatus[Record.m_bSlot] = REPLAYBUFFER_PLAYBACK; + Record.m_bSlot = (Record.m_bSlot + 1) % NUM_REPLAYBUFFERS; + BufferStatus[Record.m_bSlot] = REPLAYBUFFER_RECORD; + Record.m_pBase = Buffers[Record.m_bSlot]; + Record.m_nOffset = 0; + *Record.m_pBase = REPLAYPACKET_END; + MarkEverythingAsNew(); +#endif +} + +void CReplay::StorePedUpdate(CPed *ped, int id) +{ + tPedUpdatePacket* pp = (tPedUpdatePacket*)&Record.m_pBase[Record.m_nOffset]; + pp->type = REPLAYPACKET_PED_UPDATE; + pp->index = id; + pp->heading = 128.0f / PI * ped->m_fRotationCur; + pp->matrix.CompressFromFullMatrix(ped->GetMatrix()); + pp->assoc_group_id = ped->m_animGroup; + /* Would be more sane to use GetJustIndex(ped->m_pMyVehicle) in following assignment */ + if (ped->InVehicle()) + pp->vehicle_index = (CPools::GetVehiclePool()->GetIndex(ped->m_pMyVehicle) >> 8) + 1; + else + pp->vehicle_index = 0; + pp->weapon_model = ped->m_wepModelID; + StorePedAnimation(ped, &pp->anim_state); + Record.m_nOffset += sizeof(tPedUpdatePacket); +} + +void CReplay::StorePedAnimation(CPed *ped, CStoredAnimationState *state) +{ + CAnimBlendAssociation* second; + float blend_amount; + CAnimBlendAssociation* main = RpAnimBlendClumpGetMainAssociation((RpClump*)ped->m_rwObject, &second, &blend_amount); + if (main){ + state->animId = main->animId; + state->time = 255.0f / 4.0f * Clamp(main->currentTime, 0.0f, 4.0f); + state->speed = 255.0f / 3.0f * Clamp(main->speed, 0.0f, 3.0f); + }else{ + state->animId = 3; + state->time = 0; + state->speed = 85; + } + if (second) { + state->secAnimId = second->animId; + state->secTime = 255.0f / 4.0f * Clamp(second->currentTime, 0.0f, 4.0f); + state->secSpeed = 255.0f / 3.0f * Clamp(second->speed, 0.0f, 3.0f); + state->blendAmount = 255.0f / 2.0f * Clamp(blend_amount, 0.0f, 2.0f); + }else{ + state->secAnimId = 0; + state->secTime = 0; + state->secSpeed = 0; + state->blendAmount = 0; + } + CAnimBlendAssociation* partial = RpAnimBlendClumpGetMainPartialAssociation((RpClump*)ped->m_rwObject); + if (partial) { + state->partAnimId = partial->animId; + state->partAnimTime = 255.0f / 4.0f * Clamp(partial->currentTime, 0.0f, 4.0f); + state->partAnimSpeed = 255.0f / 3.0f * Clamp(partial->speed, 0.0f, 3.0f); + state->partBlendAmount = 255.0f / 2.0f * Clamp(partial->blendAmount, 0.0f, 2.0f); + }else{ + state->partAnimId = 0; + state->partAnimTime = 0; + state->partAnimSpeed = 0; + state->partBlendAmount = 0; + } +} + +void CReplay::StoreDetailedPedAnimation(CPed *ped, CStoredDetailedAnimationState *state) +{ + for (int i = 0; i < NUM_MAIN_ANIMS_IN_REPLAY; i++){ + CAnimBlendAssociation* assoc = RpAnimBlendClumpGetMainAssociation_N((RpClump*)ped->m_rwObject, i); + if (assoc){ + state->aAnimId[i] = assoc->animId; + state->aCurTime[i] = 255.0f / 4.0f * Clamp(assoc->currentTime, 0.0f, 4.0f); + state->aSpeed[i] = 255.0f / 3.0f * Clamp(assoc->speed, 0.0f, 3.0f); + state->aBlendAmount[i] = 255.0f / 2.0f * Clamp(assoc->blendAmount, 0.0f, 2.0f); +#ifdef FIX_REPLAY_BUGS + state->aBlendDelta[i] = 127.0f / 32.0f * Clamp(assoc->blendDelta, -16.0f, 16.0f); +#endif + state->aFlags[i] = assoc->flags; + if (assoc->callbackType == CAnimBlendAssociation::CB_FINISH || assoc->callbackType == CAnimBlendAssociation::CB_DELETE) { + state->aFunctionCallbackID[i] = FindCBFunctionID(assoc->callback); + if (assoc->callbackType == CAnimBlendAssociation::CB_FINISH) + state->aFunctionCallbackID[i] |= 0x80; + }else{ + state->aFunctionCallbackID[i] = 0; + } + }else{ + state->aAnimId[i] = ANIM_STD_NUM; + state->aCurTime[i] = 0; + state->aSpeed[i] = 85; + state->aFunctionCallbackID[i] = 0; + state->aFlags[i] = 0; + } + } + for (int i = 0; i < NUM_PARTIAL_ANIMS_IN_REPLAY; i++) { + CAnimBlendAssociation* assoc = RpAnimBlendClumpGetMainPartialAssociation_N((RpClump*)ped->m_rwObject, i); + if (assoc) { + state->aAnimId2[i] = assoc->animId; + state->aCurTime2[i] = 255.0f / 4.0f * Clamp(assoc->currentTime, 0.0f, 4.0f); + state->aSpeed2[i] = 255.0f / 3.0f * Clamp(assoc->speed, 0.0f, 3.0f); + state->aBlendAmount2[i] = 255.0f / 2.0f * Clamp(assoc->blendAmount, 0.0f, 2.0f); +#ifdef FIX_REPLAY_BUGS + state->aBlendDelta2[i] = 127.0f / 16.0f * Clamp(assoc->blendDelta, -16.0f, 16.0f); +#endif + state->aFlags2[i] = assoc->flags; + if (assoc->callbackType == CAnimBlendAssociation::CB_FINISH || assoc->callbackType == CAnimBlendAssociation::CB_DELETE) { + state->aFunctionCallbackID2[i] = FindCBFunctionID(assoc->callback); + if (assoc->callbackType == CAnimBlendAssociation::CB_FINISH) + state->aFunctionCallbackID2[i] |= 0x80; + }else{ + state->aFunctionCallbackID2[i] = 0; + } + } + else { + state->aAnimId2[i] = ANIM_STD_NUM; + state->aCurTime2[i] = 0; + state->aSpeed2[i] = 85; + state->aFunctionCallbackID2[i] = 0; + state->aFlags2[i] = 0; + } + } +} + +void CReplay::ProcessPedUpdate(CPed *ped, float interpolation, CAddressInReplayBuffer *buffer) +{ + tPedUpdatePacket *pp = (tPedUpdatePacket*)&buffer->m_pBase[buffer->m_nOffset]; + if (!ped){ + printf("Replay:Ped wasn't there\n"); + buffer->m_nOffset += sizeof(tPedUpdatePacket); + return; + } + ped->m_fRotationCur = pp->heading * PI / 128.0f; + ped->m_fRotationDest = pp->heading * PI / 128.0f; + CMatrix ped_matrix; + pp->matrix.DecompressIntoFullMatrix(ped_matrix); + ped->GetMatrix() = ped->GetMatrix() * CMatrix(1.0f - interpolation); + ped->GetMatrix().GetPosition() *= (1.0f - interpolation); + ped->GetMatrix() += CMatrix(interpolation) * ped_matrix; + if (pp->vehicle_index) { + ped->m_pMyVehicle = CPools::GetVehiclePool()->GetSlot(pp->vehicle_index - 1); + ped->bInVehicle = pp->vehicle_index; + } + else { + ped->m_pMyVehicle = nil; + ped->bInVehicle = false; + } + if (pp->assoc_group_id != ped->m_animGroup) { + ped->m_animGroup = (AssocGroupId)pp->assoc_group_id; + if (ped == FindPlayerPed()) + ((CPlayerPed*)ped)->ReApplyMoveAnims(); + } + RetrievePedAnimation(ped, &pp->anim_state); + ped->RemoveWeaponModel(-1); + if (pp->weapon_model != (uint8)-1) + ped->AddWeaponModel(pp->weapon_model); + CWorld::Remove(ped); + CWorld::Add(ped); + buffer->m_nOffset += sizeof(tPedUpdatePacket); +} + +void CReplay::RetrievePedAnimation(CPed *ped, CStoredAnimationState *state) +{ + CAnimBlendAssociation* anim1 = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, + (state->animId > 3) ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->animId, 100.0f); + anim1->SetCurrentTime(state->time * 4.0f / 255.0f); + anim1->speed = state->speed * 3.0f / 255.0f; + anim1->SetBlend(1.0f, 1.0f); + anim1->callbackType = CAnimBlendAssociation::CB_NONE; + if (state->blendAmount && state->secAnimId){ + float time = state->secTime * 4.0f / 255.0f; + float speed = state->secSpeed * 3.0f / 255.0f; + float blend = state->blendAmount * 2.0f / 255.0f; + CAnimBlendAssociation* anim2 = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, + (state->secAnimId > 3) ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->secAnimId, 100.0f); + anim2->SetCurrentTime(time); + anim2->speed = speed; + anim2->SetBlend(blend, 1.0f); + anim2->callbackType = CAnimBlendAssociation::CB_NONE; + } + RpAnimBlendClumpRemoveAssociations((RpClump*)ped->m_rwObject, 0x10); + if (state->partAnimId){ + float time = state->partAnimTime * 4.0f / 255.0f; + float speed = state->partAnimSpeed * 3.0f / 255.0f; + float blend = state->partBlendAmount * 2.0f / 255.0f; + if (blend > 0.0f && state->partAnimId != ANIM_STD_IDLE){ + CAnimBlendAssociation* anim3 = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, ASSOCGRP_STD, (AnimationId)state->partAnimId, 1000.0f); + anim3->SetCurrentTime(time); + anim3->speed = speed; + anim3->SetBlend(blend, 0.0f); + } + } +} + +void CReplay::RetrieveDetailedPedAnimation(CPed *ped, CStoredDetailedAnimationState *state) +{ +#ifdef FIX_REPLAY_BUGS + CAnimBlendAssociation* assoc; + for (int i = 0; ((assoc = RpAnimBlendClumpGetMainAssociation_N(ped->GetClump(), i))); i++) + assoc->SetBlend(0.0f, -1.0f); + for (int i = 0; ((assoc = RpAnimBlendClumpGetMainPartialAssociation_N(ped->GetClump(), i))); i++) + assoc->SetBlend(0.0f, -1.0f); +#endif + for (int i = 0; i < NUM_MAIN_ANIMS_IN_REPLAY; i++) { + if (state->aAnimId[i] == ANIM_STD_NUM) + continue; +#ifdef FIX_REPLAY_BUGS + CAnimBlendAssociation* anim = CAnimManager::AddAnimation(ped->GetClump(), + state->aAnimId[i] > 3 ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->aAnimId[i]); +#else + CAnimBlendAssociation* anim = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, + state->aAnimId[i] > 3 ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->aAnimId[i], 100.0f); +#endif + anim->SetCurrentTime(state->aCurTime[i] * 4.0f / 255.0f); + anim->speed = state->aSpeed[i] * 3.0f / 255.0f; +#ifdef FIX_REPLAY_BUGS + anim->SetBlend(state->aBlendAmount[i] * 2.0f / 255.0f, state->aBlendDelta[i] * 16.0f / 127.0f); +#else + anim->SetBlend(state->aBlendAmount[i], 1.0f); +#endif + anim->flags = state->aFlags[i]; + uint8 callback = state->aFunctionCallbackID[i]; + if (!callback) + anim->callbackType = CAnimBlendAssociation::CB_NONE; + else if (callback & 0x80) + anim->SetFinishCallback(FindCBFunction(callback & 0x7F), ped); + else + anim->SetDeleteCallback(FindCBFunction(callback & 0x7F), ped); + } + for (int i = 0; i < NUM_PARTIAL_ANIMS_IN_REPLAY; i++) { + if (state->aAnimId2[i] == ANIM_STD_NUM) + continue; +#ifdef FIX_REPLAY_BUGS + CAnimBlendAssociation* anim = CAnimManager::AddAnimation(ped->GetClump(), + state->aAnimId2[i] > 3 ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->aAnimId2[i]); +#else + CAnimBlendAssociation* anim = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, + state->aAnimId2[i] > 3 ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->aAnimId2[i], 100.0f); +#endif + anim->SetCurrentTime(state->aCurTime2[i] * 4.0f / 255.0f); + anim->speed = state->aSpeed2[i] * 3.0f / 255.0f; +#ifdef FIX_REPLAY_BUGS + anim->SetBlend(state->aBlendAmount2[i] * 2.0f / 255.0f, state->aBlendDelta2[i] * 16.0f / 127.0f); +#else + anim->SetBlend(state->aBlendAmount2[i], 1.0f); +#endif + anim->flags = state->aFlags2[i]; + uint8 callback = state->aFunctionCallbackID2[i]; + if (!callback) + anim->callbackType = CAnimBlendAssociation::CB_NONE; + else if (callback & 0x80) + anim->SetFinishCallback(FindCBFunction(callback & 0x7F), ped); + else + anim->SetDeleteCallback(FindCBFunction(callback & 0x7F), ped); + } +} + +void CReplay::PlaybackThisFrame(void) +{ + static int FrameSloMo = 0; + CAddressInReplayBuffer buf = Playback; + if (PlayBackThisFrameInterpolation(&buf, 1.0f, nil)){ + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); + return; + } + if (FrameSloMo){ + CAddressInReplayBuffer buf_sm = buf; + if (PlayBackThisFrameInterpolation(&buf_sm, FrameSloMo * 1.0f / SlowMotion, nil)){ + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); + return; + } + } + FrameSloMo = (FrameSloMo + 1) % SlowMotion; + if (FrameSloMo == 0) + Playback = buf; + ProcessLookAroundCam(); + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); +} + +// next two functions are only found in mobile version +// most likely they were optimized out for being unused + +void CReplay::TriggerPlaybackLastCoupleOfSeconds(uint32 start, uint8 cam_mode, float cam_x, float cam_y, float cam_z, uint32 slomo) +{ + if (Mode != MODE_RECORD) + return; + TriggerPlayback(cam_mode, cam_x, cam_y, cam_z, true); + SlowMotion = slomo; + bAllowLookAroundCam = false; + if (!FastForwardToTime(CTimer::GetTimeInMilliseconds() - start)) + Mode = MODE_RECORD; +} + +bool CReplay::FastForwardToTime(uint32 start) +{ + uint32 timer = 0; + while (start > timer) + if (PlayBackThisFrameInterpolation(&Playback, 1.0f, &timer)) + return false; + return true; +} + +void CReplay::StoreCarUpdate(CVehicle *vehicle, int id) +{ + tVehicleUpdatePacket* vp = (tVehicleUpdatePacket*)&Record.m_pBase[Record.m_nOffset]; + vp->type = REPLAYPACKET_VEHICLE; + vp->index = id; + vp->matrix.CompressFromFullMatrix(vehicle->GetMatrix()); + vp->health = vehicle->m_fHealth / 4.0f; /* Not anticipated that health can be > 1000. */ + vp->acceleration = vehicle->m_fGasPedal * 100.0f; + vp->panels = vehicle->IsCar() ? ((CAutomobile*)vehicle)->Damage.m_panelStatus : 0; + vp->velocityX = 8000.0f * Max(-4.0f, Min(4.0f, vehicle->GetMoveSpeed().x)); /* 8000!? */ + vp->velocityY = 8000.0f * Max(-4.0f, Min(4.0f, vehicle->GetMoveSpeed().y)); + vp->velocityZ = 8000.0f * Max(-4.0f, Min(4.0f, vehicle->GetMoveSpeed().z)); + vp->mi = vehicle->GetModelIndex(); + vp->primary_color = vehicle->m_currentColour1; + vp->secondary_color = vehicle->m_currentColour2; + if (vehicle->GetModelIndex() == MI_RHINO) + vp->car_gun = 128.0f / PI * ((CAutomobile*)vehicle)->m_fCarGunLR; + else + vp->wheel_state = 50.0f * vehicle->m_fSteerAngle; + if (vehicle->IsCar()){ + CAutomobile* car = (CAutomobile*)vehicle; + for (int i = 0; i < 4; i++){ + vp->wheel_susp_dist[i] = 50.0f * car->m_aSuspensionSpringRatio[i]; + vp->wheel_rotation[i] = 128.0f / PI * car->m_aWheelRotation[i]; + } + vp->door_angles[0] = 127.0f / PI * car->Doors[2].m_fAngle; + vp->door_angles[1] = 127.0f / PI * car->Doors[3].m_fAngle; + vp->door_status = 0; + for (int i = 0; i < 6; i++){ + if (car->Damage.GetDoorStatus(i) == DOOR_STATUS_MISSING) + vp->door_status |= BIT(i); + } + } + Record.m_nOffset += sizeof(tVehicleUpdatePacket); +} + +void CReplay::ProcessCarUpdate(CVehicle *vehicle, float interpolation, CAddressInReplayBuffer *buffer) +{ + tVehicleUpdatePacket* vp = (tVehicleUpdatePacket*)&buffer->m_pBase[buffer->m_nOffset]; + if (!vehicle){ + printf("Replay:Car wasn't there"); + return; + } + CMatrix vehicle_matrix; + vp->matrix.DecompressIntoFullMatrix(vehicle_matrix); + vehicle->GetMatrix() = vehicle->GetMatrix() * CMatrix(1.0f - interpolation); + vehicle->GetMatrix().GetPosition() *= (1.0f - interpolation); + vehicle->GetMatrix() += CMatrix(interpolation) * vehicle_matrix; + vehicle->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + vehicle->m_fHealth = 4 * vp->health; + vehicle->m_fGasPedal = vp->acceleration / 100.0f; + if (vehicle->IsCar()) + ApplyPanelDamageToCar(vp->panels, (CAutomobile*)vehicle, true); + vehicle->m_vecMoveSpeed = CVector(vp->velocityX / 8000.0f, vp->velocityY / 8000.0f, vp->velocityZ / 8000.0f); + if (vehicle->GetModelIndex() == MI_RHINO) { + ((CAutomobile*)vehicle)->m_fCarGunLR = vp->car_gun * PI / 128.0f; + vehicle->m_fSteerAngle = 0.0f; + }else{ + vehicle->m_fSteerAngle = vp->wheel_state / 50.0f; + } + if (vehicle->IsCar()) { + CAutomobile* car = (CAutomobile*)vehicle; + for (int i = 0; i < 4; i++) { + car->m_aSuspensionSpringRatio[i] = vp->wheel_susp_dist[i] / 50.0f; + car->m_aWheelRotation[i] = vp->wheel_rotation[i] * PI / 128.0f; + } + car->Doors[DOOR_FRONT_LEFT].m_fAngle = car->Doors[DOOR_FRONT_LEFT].m_fPrevAngle = vp->door_angles[0] * PI / 127.0f; + car->Doors[DOOR_FRONT_RIGHT].m_fAngle = car->Doors[DOOR_FRONT_RIGHT].m_fPrevAngle = vp->door_angles[1] * PI / 127.0f; + if (vp->door_angles[0]) + car->Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_SWINGING); + if (vp->door_angles[1]) + car->Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_SWINGING); + if (vp->door_status & 1 && car->Damage.GetDoorStatus(DOOR_BONNET) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_BONNET, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_BONNET, DOOR_BONNET, true); + } + if (vp->door_status & 2 && car->Damage.GetDoorStatus(DOOR_BOOT) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_BOOT, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_BOOT, DOOR_BOOT, true); + } + if (vp->door_status & 4 && car->Damage.GetDoorStatus(DOOR_FRONT_LEFT) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT, true); + } + if (vp->door_status & 8 && car->Damage.GetDoorStatus(DOOR_FRONT_RIGHT) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT, true); + } + if (vp->door_status & 0x10 && car->Damage.GetDoorStatus(DOOR_REAR_LEFT) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_REAR_LEFT, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT, true); + } + if (vp->door_status & 0x20 && car->Damage.GetDoorStatus(DOOR_REAR_RIGHT) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_REAR_RIGHT, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT, true); + } + } + vehicle->bEngineOn = true; + if (vehicle->IsCar()) + ((CAutomobile*)vehicle)->m_nDriveWheelsOnGround = 4; + CWorld::Remove(vehicle); + CWorld::Add(vehicle); + if (vehicle->IsBoat()) + ((CBoat*)vehicle)->m_bIsAnchored = false; +} + +bool CReplay::PlayBackThisFrameInterpolation(CAddressInReplayBuffer *buffer, float interpolation, uint32 *pTimer){ + /* Mistake. Not even sure what this is even doing here... + * PlayerWanted is a backup to restore at the end of replay. + * Setting current wanted pointer to it makes it useless. + * Causes picking up bribes in replays reducing wanted level bug. + * Obviously fact of picking them up is a bug on its own, + * but it doesn't cancel this one. + */ +#ifndef FIX_REPLAY_BUGS + FindPlayerPed()->m_pWanted = &PlayerWanted; +#endif + + CBulletTraces::Init(); + float split = 1.0f - interpolation; + int ped_min_index = 0; /* Optimization due to peds and vehicles placed in buffer sequentially. */ + int vehicle_min_index = 0; /* So next ped can't have pool index less than current. */ + for(;;){ + uint8* ptr = buffer->m_pBase; + uint32 offset = buffer->m_nOffset; + uint8 type = ptr[offset]; + if (type == REPLAYPACKET_ENDOFFRAME) + break; + switch (type) { + case REPLAYPACKET_END: + { + int slot = buffer->m_bSlot; + if (BufferStatus[slot] == REPLAYBUFFER_RECORD) { + FinishPlayback(); + return true; + } + buffer->m_bSlot = (slot + 1) % NUM_REPLAYBUFFERS; + buffer->m_nOffset = 0; + buffer->m_pBase = Buffers[buffer->m_bSlot]; + ped_min_index = 0; + vehicle_min_index = 0; + break; + } + case REPLAYPACKET_VEHICLE: + { + tVehicleUpdatePacket* vp = (tVehicleUpdatePacket*)&ptr[offset]; + for (int i = vehicle_min_index; i < vp->index; i++) { + CVehicle* v = CPools::GetVehiclePool()->GetSlot(i); + if (!v) + continue; + /* Removing vehicles not present in this frame. */ + CWorld::Remove(v); + delete v; + } + vehicle_min_index = vp->index + 1; + CVehicle* v = CPools::GetVehiclePool()->GetSlot(vp->index); + CVehicle* new_v; + if (!v) { + int mi = vp->mi; + if (CStreaming::ms_aInfoForModel[mi].m_loadState != 1) { + CStreaming::RequestModel(mi, 0); + } + else { + if (mi == MI_DEADDODO || mi == MI_AIRTRAIN) { + new_v = new(vp->index << 8) CPlane(mi, 2); + } + else if (mi == MI_TRAIN) { + new_v = new(vp->index << 8) CTrain(mi, 2); + } + else if (mi == MI_CHOPPER || mi == MI_ESCAPE) { + new_v = new(vp->index << 8) CHeli(mi, 2); + } + else if (CModelInfo::IsBoatModel(mi)){ + new_v = new(vp->index << 8) CBoat(mi, 2); + } + else{ + new_v = new(vp->index << 8) CAutomobile(mi, 2); + } + new_v->SetStatus(STATUS_PLAYER_PLAYBACKFROMBUFFER); + vp->matrix.DecompressIntoFullMatrix(new_v->GetMatrix()); + new_v->m_currentColour1 = vp->primary_color; + new_v->m_currentColour2 = vp->secondary_color; + CWorld::Add(new_v); + } + } + ProcessCarUpdate(CPools::GetVehiclePool()->GetSlot(vp->index), interpolation, buffer); + buffer->m_nOffset += sizeof(tVehicleUpdatePacket); + break; + } + case REPLAYPACKET_PED_HEADER: + { + tPedHeaderPacket* ph = (tPedHeaderPacket*)&ptr[offset]; + if (!CPools::GetPedPool()->GetSlot(ph->index)) { + if (CStreaming::ms_aInfoForModel[ph->mi].m_loadState != 1) { + CStreaming::RequestModel(ph->mi, 0); + } + else { + CPed* new_p = new(ph->index << 8) CCivilianPed((ePedType)ph->pedtype, ph->mi); + new_p->SetStatus(STATUS_PLAYER_PLAYBACKFROMBUFFER); + new_p->GetMatrix().SetUnity(); + CWorld::Add(new_p); + } + } + buffer->m_nOffset += sizeof(tPedHeaderPacket); + break; + } + case REPLAYPACKET_PED_UPDATE: + { + tPedUpdatePacket* pu = (tPedUpdatePacket*)&ptr[offset]; + for (int i = ped_min_index; i < pu->index; i++) { + CPed* p = CPools::GetPedPool()->GetSlot(i); + if (!p) + continue; + /* Removing peds not present in this frame. */ + CWorld::Remove(p); + delete p; + } + ped_min_index = pu->index + 1; + ProcessPedUpdate(CPools::GetPedPool()->GetSlot(pu->index), interpolation, buffer); + break; + } + case REPLAYPACKET_GENERAL: + { + tGeneralPacket* pg = (tGeneralPacket*)&ptr[offset]; + TheCamera.GetMatrix() = TheCamera.GetMatrix() * CMatrix(split); + TheCamera.GetMatrix().GetPosition() *= split; + TheCamera.GetMatrix() += CMatrix(interpolation) * pg->camera_pos; + RwMatrix* pm = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + pm->pos = TheCamera.GetPosition(); + pm->at = TheCamera.GetForward(); + pm->up = TheCamera.GetUp(); + pm->right = TheCamera.GetRight(); + CameraFocusX = split * CameraFocusX + interpolation * pg->player_pos.x; + CameraFocusY = split * CameraFocusY + interpolation * pg->player_pos.y; + CameraFocusZ = split * CameraFocusZ + interpolation * pg->player_pos.z; + bPlayerInRCBuggy = pg->in_rcvehicle; + buffer->m_nOffset += sizeof(tGeneralPacket); + break; + } + case REPLAYPACKET_CLOCK: + { + tClockPacket* pc = (tClockPacket*)&ptr[offset]; + CClock::SetGameClock(pc->hours, pc->minutes); + buffer->m_nOffset += sizeof(tClockPacket); + break; + } + case REPLAYPACKET_WEATHER: + { + tWeatherPacket* pw = (tWeatherPacket*)&ptr[offset]; + CWeather::OldWeatherType = pw->old_weather; + CWeather::NewWeatherType = pw->new_weather; + CWeather::InterpolationValue = pw->interpolation; + buffer->m_nOffset += sizeof(tWeatherPacket); + break; + } + case REPLAYPACKET_ENDOFFRAME: + { + /* Not supposed to be here. */ + assert(false); + buffer->m_nOffset++; + break; + } + case REPLAYPACKET_TIMER: + { + tTimerPacket* pt = (tTimerPacket*)&ptr[offset]; + if (pTimer) + *pTimer = pt->timer; + CTimer::SetTimeInMilliseconds(pt->timer); + buffer->m_nOffset += sizeof(tTimerPacket); + break; + } + case REPLAYPACKET_BULLET_TRACES: + { + tBulletTracePacket* pb = (tBulletTracePacket*)&ptr[offset]; + CBulletTraces::aTraces[pb->index].m_bInUse = true; + CBulletTraces::aTraces[pb->index].m_framesInUse = pb->frames; + CBulletTraces::aTraces[pb->index].m_lifeTime = pb->lifetime; + CBulletTraces::aTraces[pb->index].m_vecCurrentPos = pb->inf; + CBulletTraces::aTraces[pb->index].m_vecTargetPos = pb->sup; + buffer->m_nOffset += sizeof(tBulletTracePacket); + } + default: + break; + } + } + buffer->m_nOffset += 4; + for (int i = vehicle_min_index; i < CPools::GetVehiclePool()->GetSize(); i++) { + CVehicle* v = CPools::GetVehiclePool()->GetSlot(i); + if (!v) + continue; + /* Removing vehicles not present in this frame. */ + CWorld::Remove(v); + delete v; + } + for (int i = ped_min_index; i < CPools::GetPedPool()->GetSize(); i++) { + CPed* p = CPools::GetPedPool()->GetSlot(i); + if (!p) + continue; + /* Removing peds not present in this frame. */ + CWorld::Remove(p); + delete p; + } + ProcessReplayCamera(); + return false; +} + +void CReplay::FinishPlayback(void) +{ + if (Mode != MODE_PLAYBACK) + return; + EmptyAllPools(); + RestoreStuffFromMem(); + Mode = MODE_RECORD; + if (bDoLoadSceneWhenDone){ + CVector v_ls(LoadSceneX, LoadSceneY, LoadSceneZ); + CGame::currLevel = CTheZones::GetLevelFromPosition(&v_ls); + CCollision::SortOutCollisionAfterLoad(); + CStreaming::LoadScene(v_ls); + } + bDoLoadSceneWhenDone = false; + if (bPlayingBackFromFile){ + Init(); + MarkEverythingAsNew(); + } + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); +} + +void CReplay::EmptyReplayBuffer(void) +{ + if (Mode == MODE_PLAYBACK) + return; + Record.m_nOffset = 0; + for (int i = 0; i < NUM_REPLAYBUFFERS; i++){ + BufferStatus[i] = REPLAYBUFFER_UNUSED; + } + Record.m_bSlot = 0; + Record.m_pBase = Buffers[0]; + BufferStatus[0] = REPLAYBUFFER_RECORD; + Record.m_pBase[Record.m_nOffset] = 0; + MarkEverythingAsNew(); +} + +void CReplay::ProcessReplayCamera(void) +{ + switch (CameraMode) { + case REPLAYCAMMODE_TOPDOWN: + { + TheCamera.SetPosition(CameraFocusX, CameraFocusY, CameraFocusZ + 15.0f); + TheCamera.GetForward() = CVector(0.0f, 0.0f, -1.0f); + TheCamera.GetUp() = CVector(0.0f, 1.0f, 0.0f); + TheCamera.GetRight() = CVector(1.0f, 0.0f, 0.0f); + RwMatrix* pm = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + pm->pos = TheCamera.GetPosition(); + pm->at = TheCamera.GetForward(); + pm->up = TheCamera.GetUp(); + pm->right = TheCamera.GetRight(); + break; + } + case REPLAYCAMMODE_FIXED: + { + TheCamera.GetMatrix().GetPosition() = CVector(CameraFixedX, CameraFixedY, CameraFixedZ); + CVector forward(CameraFocusX - CameraFixedX, CameraFocusY - CameraFixedY, CameraFocusZ - CameraFixedZ); + forward.Normalise(); + CVector right = CrossProduct(CVector(0.0f, 0.0f, 1.0f), forward); + right.Normalise(); + CVector up = CrossProduct(forward, right); + up.Normalise(); + TheCamera.GetMatrix().GetForward() = forward; + TheCamera.GetMatrix().GetUp() = up; + TheCamera.GetMatrix().GetRight() = right; + RwMatrix* pm = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + pm->pos = TheCamera.GetMatrix().GetPosition(); + pm->at = TheCamera.GetMatrix().GetForward(); + pm->up = TheCamera.GetMatrix().GetUp(); + pm->right = TheCamera.GetMatrix().GetRight(); + break; + } + default: + break; + } + TheCamera.m_vecGameCamPos = TheCamera.GetMatrix().GetPosition(); + TheCamera.CalculateDerivedValues(); + RwMatrixUpdate(RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera))); + RwFrameUpdateObjects(RwCameraGetFrame(TheCamera.m_pRwCamera)); +} + +void CReplay::TriggerPlayback(uint8 cam_mode, float cam_x, float cam_y, float cam_z, bool load_scene) +{ + if (Mode != MODE_RECORD) + return; + CameraFixedX = cam_x; + CameraFixedY = cam_y; + CameraFixedZ = cam_z; + Mode = MODE_PLAYBACK; + FramesActiveLookAroundCam = 0; + CameraMode = cam_mode; + bAllowLookAroundCam = true; + bPlayingBackFromFile = false; + OldRadioStation = DMAudio.GetRadioInCar(); + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + int current; + for (current = 0; current < NUM_REPLAYBUFFERS; current++) + if (BufferStatus[current] == REPLAYBUFFER_RECORD) + break; + int first; + for (first = (current + 1) % NUM_REPLAYBUFFERS; ; first = (first + 1) % NUM_REPLAYBUFFERS) + if (BufferStatus[first] == REPLAYBUFFER_RECORD || BufferStatus[first] == REPLAYBUFFER_PLAYBACK) + break; + Playback.m_bSlot = first; + Playback.m_nOffset = 0; + Playback.m_pBase = Buffers[first]; + CObject::DeleteAllTempObjectsInArea(CVector(0.0f, 0.0f, 0.0f), 1000000.0f); + StoreStuffInMem(); + EmptyPedsAndVehiclePools(); + SlowMotion = 1; + CSkidmarks::Clear(); + StreamAllNecessaryCarsAndPeds(); + if (load_scene) + bDoLoadSceneWhenDone = false; + else{ + bDoLoadSceneWhenDone = true; + LoadSceneX = TheCamera.GetPosition().x; + LoadSceneY = TheCamera.GetPosition().y; + LoadSceneZ = TheCamera.GetPosition().z; + CVector ff_coord; + FindFirstFocusCoordinate(&ff_coord); + CGame::currLevel = CTheZones::GetLevelFromPosition(&ff_coord); + CCollision::SortOutCollisionAfterLoad(); + CStreaming::LoadScene(ff_coord); + } + if (cam_mode == REPLAYCAMMODE_ASSTORED) + TheCamera.CarZoomIndicator = CAM_ZOOM_CINEMATIC; +} + +void CReplay::StoreStuffInMem(void) +{ +#ifdef FIX_BUGS + for (int i = 0; i < NUMPLAYERS; i++) + nHandleOfPlayerPed[i] = CPools::GetPedPool()->GetIndex(CWorld::Players[i].m_pPed); +#endif + CPools::GetVehiclePool()->Store(pBuf0, pBuf1); + CPools::GetPedPool()->Store(pBuf2, pBuf3); + CPools::GetObjectPool()->Store(pBuf4, pBuf5); + CPools::GetPtrNodePool()->Store(pBuf6, pBuf7); + CPools::GetEntryInfoNodePool()->Store(pBuf8, pBuf9); + CPools::GetDummyPool()->Store(pBuf10, pBuf11); + pWorld1 = new uint8[sizeof(CSector) * NUMSECTORS_X * NUMSECTORS_Y]; + memcpy(pWorld1, CWorld::GetSector(0, 0), NUMSECTORS_X * NUMSECTORS_Y * sizeof(CSector)); + WorldPtrList = CWorld::GetMovingEntityList().first; // why + BigBuildingPtrList = CWorld::GetBigBuildingList(LEVEL_GENERIC).first; + pPickups = new uint8[sizeof(CPickup) * NUMPICKUPS]; + memcpy(pPickups, CPickups::aPickUps, NUMPICKUPS * sizeof(CPickup)); + pReferences = new uint8[(sizeof(CReference) * NUMREFERENCES)]; + memcpy(pReferences, CReferences::aRefs, NUMREFERENCES * sizeof(CReference)); + pEmptyReferences = CReferences::pEmptyList; + pStoredCam = new uint8[sizeof(CCamera)]; + memcpy(pStoredCam, &TheCamera, sizeof(CCamera)); + pRadarBlips = new uint8[sizeof(sRadarTrace) * NUMRADARBLIPS]; + memcpy(pRadarBlips, CRadar::ms_RadarTrace, NUMRADARBLIPS * sizeof(sRadarTrace)); + PlayerWanted = *FindPlayerPed()->m_pWanted; + PlayerInfo = CWorld::Players[0]; + Time1 = CTimer::GetTimeInMilliseconds(); + Time2 = CTimer::GetTimeInMillisecondsNonClipped(); + Time3 = CTimer::GetPreviousTimeInMilliseconds(); + Time4 = CTimer::GetTimeInMillisecondsPauseMode(); + Frame = CTimer::GetFrameCounter(); + ClockHours = CClock::GetHours(); + ClockMinutes = CClock::GetMinutes(); + OldWeatherType = CWeather::OldWeatherType; + NewWeatherType = CWeather::NewWeatherType; + WeatherInterpolationValue = CWeather::InterpolationValue; + TimeStepNonClipped = CTimer::GetTimeStepNonClipped(); + TimeStep = CTimer::GetTimeStep(); + TimeScale = CTimer::GetTimeScale(); + int size = CPools::GetPedPool()->GetSize(); + pPedAnims = new CStoredDetailedAnimationState[size]; + for (int i = 0; i < size; i++) { + CPed* ped = CPools::GetPedPool()->GetSlot(i); + if (ped) + StoreDetailedPedAnimation(ped, &pPedAnims[i]); + } +#ifdef FIX_BUGS + pGarages = new uint8[sizeof(CGarages::aGarages)]; + memcpy(pGarages, CGarages::aGarages, sizeof(CGarages::aGarages)); + FireArray = new CFire[NUM_FIRES]; + memcpy(FireArray, gFireManager.m_aFires, sizeof(gFireManager.m_aFires)); + NumOfFires = gFireManager.m_nTotalFires; + paProjectileInfo = new uint8[sizeof(gaProjectileInfo)]; + memcpy(paProjectileInfo, gaProjectileInfo, sizeof(gaProjectileInfo)); + paProjectiles = new uint8[sizeof(CProjectileInfo::ms_apProjectile)]; + memcpy(paProjectiles, CProjectileInfo::ms_apProjectile, sizeof(CProjectileInfo::ms_apProjectile)); +#endif +} + +void CReplay::RestoreStuffFromMem(void) +{ + CPools::GetVehiclePool()->CopyBack(pBuf0, pBuf1); + CPools::GetPedPool()->CopyBack(pBuf2, pBuf3); + CPools::GetObjectPool()->CopyBack(pBuf4, pBuf5); + CPools::GetPtrNodePool()->CopyBack(pBuf6, pBuf7); + CPools::GetEntryInfoNodePool()->CopyBack(pBuf8, pBuf9); + CPools::GetDummyPool()->CopyBack(pBuf10, pBuf11); + memcpy(CWorld::GetSector(0, 0), pWorld1, sizeof(CSector) * NUMSECTORS_X * NUMSECTORS_Y); + delete[] pWorld1; + pWorld1 = nil; + CWorld::GetMovingEntityList().first = WorldPtrList; + CWorld::GetBigBuildingList(LEVEL_GENERIC).first = BigBuildingPtrList; + memcpy(CPickups::aPickUps, pPickups, sizeof(CPickup) * NUMPICKUPS); + delete[] pPickups; + pPickups = nil; + memcpy(CReferences::aRefs, pReferences, sizeof(CReference) * NUMREFERENCES); + delete[] pReferences; + pReferences = nil; + CReferences::pEmptyList = pEmptyReferences; + pEmptyReferences = nil; + memcpy(&TheCamera, pStoredCam, sizeof(CCamera)); + delete[] pStoredCam; + pStoredCam = nil; + memcpy(CRadar::ms_RadarTrace, pRadarBlips, sizeof(sRadarTrace) * NUMRADARBLIPS); + delete[] pRadarBlips; + pRadarBlips = nil; +#ifdef FIX_BUGS + for (int i = 0; i < NUMPLAYERS; i++) { + CPlayerPed* pPlayerPed = (CPlayerPed*)CPools::GetPedPool()->GetAt(nHandleOfPlayerPed[i]); + assert(pPlayerPed); + CWorld::Players[i].m_pPed = pPlayerPed; + pPlayerPed->RegisterReference((CEntity**)&CWorld::Players[i].m_pPed); + } +#endif + FindPlayerPed()->m_pWanted = new CWanted(PlayerWanted); + CWorld::Players[0] = PlayerInfo; + int i = CPools::GetPedPool()->GetSize(); + while (--i >= 0) { + CPed* ped = CPools::GetPedPool()->GetSlot(i); + if (!ped) + continue; + int mi = ped->GetModelIndex(); + CStreaming::RequestModel(mi, 0); + CStreaming::LoadAllRequestedModels(false); + ped->m_rwObject = nil; + ped->m_modelIndex = -1; + ped->SetModelIndex(mi); + ped->m_pVehicleAnim = nil; + ped->m_audioEntityId = DMAudio.CreateEntity(AUDIOTYPE_PHYSICAL, ped); + DMAudio.SetEntityStatus(ped->m_audioEntityId, TRUE); + CPopulation::UpdatePedCount((ePedType)ped->m_nPedType, false); + if (ped->m_wepModelID >= 0) + ped->AddWeaponModel(ped->m_wepModelID); + } + i = CPools::GetVehiclePool()->GetSize(); + while (--i >= 0) { + CVehicle* vehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!vehicle) + continue; + int mi = vehicle->GetModelIndex(); + CStreaming::RequestModel(mi, 0); + CStreaming::LoadAllRequestedModels(false); + vehicle->m_rwObject = nil; + vehicle->m_modelIndex = -1; + vehicle->SetModelIndex(mi); + if (mi == MI_DODO){ + CAutomobile* dodo = (CAutomobile*)vehicle; + RpAtomicSetFlags((RpAtomic*)GetFirstObject(dodo->m_aCarNodes[CAR_WHEEL_LF]), 0); + CMatrix tmp1; + tmp1.Attach(RwFrameGetMatrix(dodo->m_aCarNodes[CAR_WHEEL_RF]), false); + CMatrix tmp2(RwFrameGetMatrix(dodo->m_aCarNodes[CAR_WHEEL_LF]), false); + tmp1.GetPosition() += CVector(tmp2.GetPosition().x + 0.1f, 0.0f, tmp2.GetPosition().z); + tmp1.UpdateRW(); + } + if (vehicle->IsCar()){ + CAutomobile* car = (CAutomobile*)vehicle; + int32 panels = car->Damage.m_panelStatus; + car->Damage.m_panelStatus = 0; + ApplyPanelDamageToCar(panels, car, true); + car->SetDoorDamage(CAR_BONNET, DOOR_BONNET, true); + car->SetDoorDamage(CAR_BOOT, DOOR_BOOT, true); + car->SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT, true); + car->SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT, true); + car->SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT, true); + car->SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT, true); + } + vehicle->m_audioEntityId = DMAudio.CreateEntity(AUDIOTYPE_PHYSICAL, vehicle); + DMAudio.SetEntityStatus(vehicle->m_audioEntityId, TRUE); + CCarCtrl::UpdateCarCount(vehicle, false); + if ((mi == MI_AIRTRAIN || mi == MI_DEADDODO) && vehicle->m_rwObject){ + CVehicleModelInfo* info = (CVehicleModelInfo*)CModelInfo::GetModelInfo(mi); + if (RwObjectGetType(vehicle->m_rwObject) == rpATOMIC){ + vehicle->GetMatrix().Detach(); + if (vehicle->m_rwObject){ + if (RwObjectGetType(vehicle->m_rwObject) == rpATOMIC){ + RwFrame* frame = RpAtomicGetFrame((RpAtomic*)vehicle->m_rwObject); + RpAtomicDestroy((RpAtomic*)vehicle->m_rwObject); + RwFrameDestroy(frame); + } + vehicle->m_rwObject = nil; + } + }else{ + vehicle->DeleteRwObject(); + int model_id = info->m_wheelId; + if (model_id != -1){ + if ((vehicle->m_rwObject = CModelInfo::GetModelInfo(model_id)->CreateInstance())){ + vehicle->GetMatrix().AttachRW(RwFrameGetMatrix(RpClumpGetFrame((RpClump*)vehicle->m_rwObject)), false); + } + } + } + } + } + PrintElementsInPtrList(); + i = CPools::GetObjectPool()->GetSize(); + while (--i >= 0) { + CObject* object = CPools::GetObjectPool()->GetSlot(i); + if (!object) + continue; + int mi = object->GetModelIndex(); + CStreaming::RequestModel(mi, 0); + CStreaming::LoadAllRequestedModels(false); + object->m_rwObject = nil; + object->m_modelIndex = -1; + object->SetModelIndex(mi); + object->GetMatrix().m_attachment = nil; + if (RwObjectGetType(object->m_rwObject) == rpATOMIC) + object->GetMatrix().AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic*)object->m_rwObject)), false); + } + i = CPools::GetDummyPool()->GetSize(); + while (--i >= 0) { + CDummy* dummy = CPools::GetDummyPool()->GetSlot(i); + if (!dummy) + continue; + int mi = dummy->GetModelIndex(); + CStreaming::RequestModel(mi, 0); + CStreaming::LoadAllRequestedModels(false); + dummy->m_rwObject = nil; + dummy->m_modelIndex = -1; + dummy->SetModelIndex(mi); + dummy->GetMatrix().m_attachment = nil; + if (RwObjectGetType(dummy->m_rwObject) == rpATOMIC) + dummy->GetMatrix().AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic*)dummy->m_rwObject)), false); + } + CTimer::SetTimeInMilliseconds(Time1); + CTimer::SetTimeInMillisecondsNonClipped(Time2); + CTimer::SetPreviousTimeInMilliseconds(Time3); + CTimer::SetTimeInMillisecondsPauseMode(Time4); + CTimer::SetTimeScale(TimeScale); + CTimer::SetFrameCounter(Frame); + CTimer::SetTimeStep(TimeStep); + CTimer::SetTimeStepNonClipped(TimeStepNonClipped); + CClock::SetGameClock(ClockHours, ClockMinutes); + CWeather::OldWeatherType = OldWeatherType; + CWeather::NewWeatherType = NewWeatherType; + CWeather::InterpolationValue = WeatherInterpolationValue; + for (int i = 0; i < CPools::GetPedPool()->GetSize(); i++) { + CPed* ped = CPools::GetPedPool()->GetSlot(i); + if (!ped) + continue; + RetrieveDetailedPedAnimation(ped, &pPedAnims[i]); + } + delete[] pPedAnims; + pPedAnims = nil; +#ifdef FIX_BUGS + memcpy(CGarages::aGarages, pGarages, sizeof(CGarages::aGarages)); + delete[] pGarages; + pGarages = nil; + memcpy(gFireManager.m_aFires, FireArray, sizeof(gFireManager.m_aFires)); + delete[] FireArray; + FireArray = nil; + gFireManager.m_nTotalFires = NumOfFires; + memcpy(gaProjectileInfo, paProjectileInfo, sizeof(gaProjectileInfo)); + delete[] paProjectileInfo; + paProjectileInfo = nil; + memcpy(CProjectileInfo::ms_apProjectile, paProjectiles, sizeof(CProjectileInfo::ms_apProjectile)); + delete[] paProjectiles; + paProjectiles = nil; + //CExplosion::ClearAllExplosions(); not in III +#endif + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.SetRadioInCar(OldRadioStation); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); +} + +void CReplay::EmptyPedsAndVehiclePools(void) +{ + int i = CPools::GetVehiclePool()->GetSize(); + while (--i >= 0) { + CVehicle* v = CPools::GetVehiclePool()->GetSlot(i); + if (!v) + continue; + CWorld::Remove(v); + delete v; + } + i = CPools::GetPedPool()->GetSize(); + while (--i >= 0) { + CPed* p = CPools::GetPedPool()->GetSlot(i); + if (!p) + continue; + CWorld::Remove(p); + delete p; + } +} + +void CReplay::EmptyAllPools(void) +{ + EmptyPedsAndVehiclePools(); + int i = CPools::GetObjectPool()->GetSize(); + while (--i >= 0) { + CObject* o = CPools::GetObjectPool()->GetSlot(i); + if (!o) + continue; + CWorld::Remove(o); + delete o; + } + i = CPools::GetDummyPool()->GetSize(); + while (--i >= 0) { + CDummy* d = CPools::GetDummyPool()->GetSlot(i); + if (!d) + continue; + CWorld::Remove(d); + delete d; + } +} + +void CReplay::MarkEverythingAsNew(void) +{ + int i = CPools::GetVehiclePool()->GetSize(); + while (--i >= 0) { + CVehicle* v = CPools::GetVehiclePool()->GetSlot(i); + if (!v) + continue; + v->bHasAlreadyBeenRecorded = false; + } + i = CPools::GetPedPool()->GetSize(); + while (--i >= 0) { + CPed* p = CPools::GetPedPool()->GetSlot(i); + if (!p) + continue; + p->bHasAlreadyBeenRecorded = false; + } +} + +void CReplay::SaveReplayToHD(void) +{ + CFileMgr::SetDirMyDocuments(); + int fw = CFileMgr::OpenFileForWriting("replay.rep"); +#ifdef FIX_REPLAY_BUGS + if (fw == 0) { +#else + if (fw < 0){ // BUG? +#endif + printf("Couldn't open replay.rep for writing"); + CFileMgr::SetDir(""); + return; + } + CFileMgr::Write(fw, "gta3_7f", sizeof("gta3_7f")); + int current; + for (current = 0; current < NUM_REPLAYBUFFERS; current++) + if (BufferStatus[current] == REPLAYBUFFER_RECORD) + break; + int first; + for (first = (current + 1) % NUM_REPLAYBUFFERS; ; first = (first + 1) % NUM_REPLAYBUFFERS) + if (BufferStatus[first] == REPLAYBUFFER_RECORD || BufferStatus[first] == REPLAYBUFFER_PLAYBACK) + break; + for(int i = first; ; i = (i + 1) % NUM_REPLAYBUFFERS){ + CFileMgr::Write(fw, (char*)Buffers[i], sizeof(Buffers[i])); + if (BufferStatus[i] == REPLAYBUFFER_RECORD) + break; + } + CFileMgr::CloseFile(fw); + CFileMgr::SetDir(""); +} + +void CReplay::PlayReplayFromHD(void) +{ + CFileMgr::SetDirMyDocuments(); + int fr = CFileMgr::OpenFile("replay.rep", "rb"); + if (fr == 0) { + printf("Couldn't open replay.rep for reading"); +#ifdef FIX_REPLAY_BUGS + CFileMgr::SetDir(""); +#endif + return; + } + CFileMgr::Read(fr, gString, 8); + if (strncmp(gString, "gta3_7f", sizeof("gta3_7f"))){ + CFileMgr::CloseFile(fr); + printf("Wrong file type for replay"); + CFileMgr::SetDir(""); + return; + } + int slot; + for (slot = 0; CFileMgr::Read(fr, (char*)Buffers[slot], sizeof(Buffers[slot])); slot++) + BufferStatus[slot] = REPLAYBUFFER_PLAYBACK; + BufferStatus[slot - 1] = REPLAYBUFFER_RECORD; + while (slot < NUM_REPLAYBUFFERS) + BufferStatus[slot++] = REPLAYBUFFER_UNUSED; + CFileMgr::CloseFile(fr); + CFileMgr::SetDir(""); + TriggerPlayback(REPLAYCAMMODE_ASSTORED, 0.0f, 0.0f, 0.0f, false); + bPlayingBackFromFile = true; + bAllowLookAroundCam = true; + StreamAllNecessaryCarsAndPeds(); +} + +void CReplay::StreamAllNecessaryCarsAndPeds(void) +{ + for (int slot = 0; slot < NUM_REPLAYBUFFERS; slot++) { + if (BufferStatus[slot] == REPLAYBUFFER_UNUSED) + continue; + for (size_t offset = 0; Buffers[slot][offset] != REPLAYPACKET_END; offset += FindSizeOfPacket(Buffers[slot][offset])) { + switch (Buffers[slot][offset]) { + case REPLAYPACKET_VEHICLE: + CStreaming::RequestModel(((tVehicleUpdatePacket*)&Buffers[slot][offset])->mi, 0); + break; + case REPLAYPACKET_PED_HEADER: + CStreaming::RequestModel(((tPedHeaderPacket*)&Buffers[slot][offset])->mi, 0); + break; + default: + break; + } + } + } + CStreaming::LoadAllRequestedModels(false); +} + +void CReplay::FindFirstFocusCoordinate(CVector *coord) +{ + *coord = CVector(0.0f, 0.0f, 0.0f); + for (int slot = 0; slot < NUM_REPLAYBUFFERS; slot++) { + if (BufferStatus[slot] == REPLAYBUFFER_UNUSED) + continue; + for (size_t offset = 0; Buffers[slot][offset] != REPLAYPACKET_END; offset += FindSizeOfPacket(Buffers[slot][offset])) { + if (Buffers[slot][offset] == REPLAYPACKET_GENERAL) { + *coord = ((tGeneralPacket*)&Buffers[slot][offset])->player_pos; + return; + } + } + } +} + +bool CReplay::ShouldStandardCameraBeProcessed(void) +{ + if (Mode != MODE_PLAYBACK) + return true; + if (FramesActiveLookAroundCam || bPlayerInRCBuggy) + return false; + return FindPlayerVehicle() != nil; +} + +void CReplay::ProcessLookAroundCam(void) +{ + if (!bAllowLookAroundCam) + return; + float x_moved = CPad::NewMouseControllerState.x / 200.0f; + float y_moved = CPad::NewMouseControllerState.y / 200.0f; + if (x_moved > 0.01f || y_moved > 0.01f) { + if (FramesActiveLookAroundCam == 0) + fDistanceLookAroundCam = 9.0f; + FramesActiveLookAroundCam = 60; + } + if (bPlayerInRCBuggy) + FramesActiveLookAroundCam = 0; + if (!FramesActiveLookAroundCam) + return; + --FramesActiveLookAroundCam; + fBetaAngleLookAroundCam += x_moved; + if (CPad::NewMouseControllerState.LMB && CPad::NewMouseControllerState.RMB) + fDistanceLookAroundCam = Max(3.0f, Min(15.0f, fDistanceLookAroundCam + 2.0f * y_moved)); + else + fAlphaAngleLookAroundCam = Max(0.1f, Min(1.5f, fAlphaAngleLookAroundCam + y_moved)); + CVector camera_pt( + fDistanceLookAroundCam * Sin(fBetaAngleLookAroundCam) * Cos(fAlphaAngleLookAroundCam), + fDistanceLookAroundCam * Cos(fBetaAngleLookAroundCam) * Cos(fAlphaAngleLookAroundCam), + fDistanceLookAroundCam * Sin(fAlphaAngleLookAroundCam) + ); + CVector focus = CVector(CameraFocusX, CameraFocusY, CameraFocusZ); + camera_pt += focus; + CColPoint cp; + CEntity* pe = nil; + if (CWorld::ProcessLineOfSight(focus, camera_pt, cp, pe, true, false, false, false, false, true, true)){ + camera_pt = cp.point; + CVector direction = focus - cp.point; + direction.Normalise(); + camera_pt += direction / 4.0f; + } + CVector forward = focus - camera_pt; + forward.Normalise(); + CVector right = CrossProduct(CVector(0.0f, 0.0f, 1.0f), forward); + right.Normalise(); + CVector up = CrossProduct(forward, right); + up.Normalise(); + TheCamera.GetForward() = forward; + TheCamera.GetUp() = up; + TheCamera.GetRight() = right; + TheCamera.SetPosition(camera_pt); + RwMatrix* pm = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + pm->pos = TheCamera.GetPosition(); + pm->at = TheCamera.GetForward(); + pm->up = TheCamera.GetUp(); + pm->right = TheCamera.GetRight(); + TheCamera.CalculateDerivedValues(); + RwMatrixUpdate(RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera))); + RwFrameUpdateObjects(RwCameraGetFrame(TheCamera.m_pRwCamera)); +} + +size_t CReplay::FindSizeOfPacket(uint8 type) +{ + switch (type) { + case REPLAYPACKET_END: return 4; + case REPLAYPACKET_VEHICLE: return sizeof(tVehicleUpdatePacket); + case REPLAYPACKET_PED_HEADER: return sizeof(tPedHeaderPacket); + case REPLAYPACKET_PED_UPDATE: return sizeof(tPedUpdatePacket); + case REPLAYPACKET_GENERAL: return sizeof(tGeneralPacket); + case REPLAYPACKET_CLOCK: return sizeof(tClockPacket); + case REPLAYPACKET_WEATHER: return sizeof(tWeatherPacket); + case REPLAYPACKET_ENDOFFRAME: return 4; + case REPLAYPACKET_TIMER: return sizeof(tTimerPacket); + case REPLAYPACKET_BULLET_TRACES:return sizeof(tBulletTracePacket); + default: assert(false); break; + } + return 0; +} + +void CReplay::Display() +{ + static int TimeCount = 0; + if (Mode == MODE_RECORD) + return; + TimeCount = (TimeCount + 1) % UINT16_MAX; + if ((TimeCount & 0x20) == 0) + return; + + CFont::SetScale(SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.5f)); + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-20)); +#else + CFont::SetCentreSize(SCREEN_WIDTH-20); +#endif + CFont::SetCentreOff(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(255, 255, 200, 200)); + CFont::SetFontStyle(FONT_BANK); + if (Mode == MODE_PLAYBACK) + CFont::PrintString(SCREEN_WIDTH/15, SCREEN_HEIGHT/10, TheText.Get("REPLAY")); +} +#endif diff --git a/src/control/Replay.h b/src/control/Replay.h new file mode 100644 index 0000000..68da9cc --- /dev/null +++ b/src/control/Replay.h @@ -0,0 +1,329 @@ +#pragma once + +#include "Pools.h" +#include "World.h" + +#ifdef FIX_BUGS +#ifndef DONT_FIX_REPLAY_BUGS +#define FIX_REPLAY_BUGS +#endif +#endif + +class CVehicle; +struct CReference; + +struct CAddressInReplayBuffer +{ + uint32 m_nOffset; + uint8 *m_pBase; + uint8 m_bSlot; +}; + +struct CStoredAnimationState +{ + uint8 animId; + uint8 time; + uint8 speed; + uint8 secAnimId; + uint8 secTime; + uint8 secSpeed; + uint8 blendAmount; + uint8 partAnimId; + uint8 partAnimTime; + uint8 partAnimSpeed; + uint8 partBlendAmount; +}; + +enum { + NUM_MAIN_ANIMS_IN_REPLAY = 3, + NUM_PARTIAL_ANIMS_IN_REPLAY = 6 +}; + +struct CStoredDetailedAnimationState +{ + uint8 aAnimId[NUM_MAIN_ANIMS_IN_REPLAY]; + uint8 aCurTime[NUM_MAIN_ANIMS_IN_REPLAY]; + uint8 aSpeed[NUM_MAIN_ANIMS_IN_REPLAY]; + uint8 aBlendAmount[NUM_MAIN_ANIMS_IN_REPLAY]; +#ifdef FIX_REPLAY_BUGS + int8 aBlendDelta[NUM_MAIN_ANIMS_IN_REPLAY]; +#endif + uint8 aFunctionCallbackID[NUM_MAIN_ANIMS_IN_REPLAY]; + uint16 aFlags[NUM_MAIN_ANIMS_IN_REPLAY]; + uint8 aAnimId2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint8 aCurTime2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint8 aSpeed2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint8 aBlendAmount2[NUM_PARTIAL_ANIMS_IN_REPLAY]; +#ifdef FIX_REPLAY_BUGS + int8 aBlendDelta2[NUM_PARTIAL_ANIMS_IN_REPLAY]; +#endif + uint8 aFunctionCallbackID2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint16 aFlags2[NUM_PARTIAL_ANIMS_IN_REPLAY]; +}; + +#ifdef GTA_REPLAY +#define REPLAY_STUB +#else +#define REPLAY_STUB {} +#endif + +class CReplay +{ + enum { + MODE_RECORD = 0, + MODE_PLAYBACK = 1 + }; + + enum { + REPLAYCAMMODE_ASSTORED = 0, + REPLAYCAMMODE_TOPDOWN = 1, + REPLAYCAMMODE_FIXED = 2 + }; + + enum { + REPLAYPACKET_END = 0, + REPLAYPACKET_VEHICLE = 1, + REPLAYPACKET_PED_HEADER = 2, + REPLAYPACKET_PED_UPDATE = 3, + REPLAYPACKET_GENERAL = 4, + REPLAYPACKET_CLOCK = 5, + REPLAYPACKET_WEATHER = 6, + REPLAYPACKET_ENDOFFRAME = 7, + REPLAYPACKET_TIMER = 8, + REPLAYPACKET_BULLET_TRACES = 9 + }; + + enum { + REPLAYBUFFER_UNUSED = 0, + REPLAYBUFFER_PLAYBACK = 1, + REPLAYBUFFER_RECORD = 2 + }; + + enum { + NUM_REPLAYBUFFERS = 8, + REPLAYBUFFERSIZE = 100000 + }; + + + struct tGeneralPacket + { + uint8 type; + bool in_rcvehicle; + CMatrix camera_pos; + CVector player_pos; + }; + + VALIDATE_SIZE(tGeneralPacket, 88); + + struct tClockPacket + { + uint8 type; + uint8 hours; + uint8 minutes; + private: + uint8 __align; + }; + VALIDATE_SIZE(tClockPacket, 4); + + struct tWeatherPacket + { + uint8 type; + uint8 old_weather; + uint8 new_weather; + float interpolation; + }; + VALIDATE_SIZE(tWeatherPacket, 8); + + struct tTimerPacket + { + uint8 type; + uint32 timer; + }; + VALIDATE_SIZE(tTimerPacket, 8); + + struct tPedHeaderPacket + { + uint8 type; + uint8 index; + uint16 mi; + uint8 pedtype; + private: + uint8 __align[3]; + }; + VALIDATE_SIZE(tPedHeaderPacket, 8); + + struct tBulletTracePacket + { + uint8 type; + uint8 frames; + uint8 lifetime; + uint8 index; + CVector inf; + CVector sup; + }; + VALIDATE_SIZE(tBulletTracePacket, 28); + + struct tEndOfFramePacket + { + uint8 type; + private: + uint8 __align[3]; + }; + VALIDATE_SIZE(tEndOfFramePacket, 4); + + struct tPedUpdatePacket + { + uint8 type; + uint8 index; + int8 heading; + int8 vehicle_index; + CStoredAnimationState anim_state; + CCompressedMatrixNotAligned matrix; + int8 assoc_group_id; + uint8 weapon_model; + }; + VALIDATE_SIZE(tPedUpdatePacket, 40); + + struct tVehicleUpdatePacket + { + uint8 type; + uint8 index; + uint8 health; + uint8 acceleration; + CCompressedMatrixNotAligned matrix; + int8 door_angles[2]; + uint16 mi; + uint32 panels; + int8 velocityX; + int8 velocityY; + int8 velocityZ; + union { + int8 car_gun; + int8 wheel_state; + }; + uint8 wheel_susp_dist[4]; + uint8 wheel_rotation[4]; + uint8 door_status; + uint8 primary_color; + uint8 secondary_color; + }; + VALIDATE_SIZE(tVehicleUpdatePacket, 48); + +private: + static uint8 Mode; + static CAddressInReplayBuffer Record; + static CAddressInReplayBuffer Playback; + static uint8* pBuf0; + static CAutomobile* pBuf1; + static uint8* pBuf2; + static CPlayerPed* pBuf3; + static uint8* pBuf4; + static CCutsceneHead* pBuf5; + static uint8* pBuf6; + static CPtrNode* pBuf7; + static uint8* pBuf8; + static CEntryInfoNode* pBuf9; + static uint8* pBuf10; + static CDummyPed* pBuf11; + static uint8* pRadarBlips; + static uint8* pStoredCam; + static uint8* pWorld1; + static CReference* pEmptyReferences; + static CStoredDetailedAnimationState* pPedAnims; + static uint8* pPickups; + static uint8* pReferences; + static uint8 BufferStatus[NUM_REPLAYBUFFERS]; + static uint8 Buffers[NUM_REPLAYBUFFERS][REPLAYBUFFERSIZE]; + static bool bPlayingBackFromFile; + static bool bReplayEnabled; + static uint32 SlowMotion; + static uint32 FramesActiveLookAroundCam; + static bool bDoLoadSceneWhenDone; + static CPtrNode* WorldPtrList; + static CPtrNode* BigBuildingPtrList; + static CWanted PlayerWanted; + static CPlayerInfo PlayerInfo; + static uint32 Time1; + static uint32 Time2; + static uint32 Time3; + static uint32 Time4; + static uint32 Frame; + static uint8 ClockHours; + static uint8 ClockMinutes; + static uint16 OldWeatherType; + static uint16 NewWeatherType; + static float WeatherInterpolationValue; + static float TimeStepNonClipped; + static float TimeStep; + static float TimeScale; + static float CameraFixedX; + static float CameraFixedY; + static float CameraFixedZ; + static int32 OldRadioStation; + static int8 CameraMode; + static bool bAllowLookAroundCam; + static float LoadSceneX; + static float LoadSceneY; + static float LoadSceneZ; + static float CameraFocusX; + static float CameraFocusY; + static float CameraFocusZ; + static bool bPlayerInRCBuggy; + static float fDistanceLookAroundCam; + static float fAlphaAngleLookAroundCam; + static float fBetaAngleLookAroundCam; +#ifdef FIX_BUGS + static uint8* pGarages; + static CFire* FireArray; + static uint32 NumOfFires; + static uint8* paProjectileInfo; + static uint8* paProjectiles; + static int nHandleOfPlayerPed[NUMPLAYERS]; +#endif + +public: + static void Init(void) REPLAY_STUB; + static void DisableReplays(void) REPLAY_STUB; + static void EnableReplays(void) REPLAY_STUB; + static void Update(void) REPLAY_STUB; + static void FinishPlayback(void) REPLAY_STUB; + static void EmptyReplayBuffer(void) REPLAY_STUB; + static void Display(void) REPLAY_STUB; + static void TriggerPlayback(uint8 cam_mode, float cam_x, float cam_y, float cam_z, bool load_scene) REPLAY_STUB; + static void StreamAllNecessaryCarsAndPeds(void) REPLAY_STUB; + +#ifndef GTA_REPLAY + static bool ShouldStandardCameraBeProcessed(void) { return true; } + static bool IsPlayingBack() { return false; } + static bool IsPlayingBackFromFile() { return false; } +#else + static bool ShouldStandardCameraBeProcessed(void); + static bool IsPlayingBack() { return Mode == MODE_PLAYBACK; } + static bool IsPlayingBackFromFile() { return bPlayingBackFromFile; } +private: + static void RecordThisFrame(void); + static void StorePedUpdate(CPed *ped, int id); + static void StorePedAnimation(CPed *ped, CStoredAnimationState *state); + static void StoreDetailedPedAnimation(CPed *ped, CStoredDetailedAnimationState *state); + static void ProcessPedUpdate(CPed *ped, float interpolation, CAddressInReplayBuffer *buffer); + static void RetrievePedAnimation(CPed *ped, CStoredAnimationState *state); + static void RetrieveDetailedPedAnimation(CPed *ped, CStoredDetailedAnimationState *state); + static void PlaybackThisFrame(void); + static void TriggerPlaybackLastCoupleOfSeconds(uint32, uint8, float, float, float, uint32); + static bool FastForwardToTime(uint32); + static void StoreCarUpdate(CVehicle *vehicle, int id); + static void ProcessCarUpdate(CVehicle *vehicle, float interpolation, CAddressInReplayBuffer *buffer); + static bool PlayBackThisFrameInterpolation(CAddressInReplayBuffer *buffer, float interpolation, uint32 *pTimer); + static void ProcessReplayCamera(void); + static void StoreStuffInMem(void); + static void RestoreStuffFromMem(void); + static void EmptyPedsAndVehiclePools(void); + static void EmptyAllPools(void); + static void MarkEverythingAsNew(void); + static void SaveReplayToHD(void); + static void PlayReplayFromHD(void); // out of class in III PC and later because of SecuROM + static void FindFirstFocusCoordinate(CVector *coord); + static void ProcessLookAroundCam(void); + static size_t FindSizeOfPacket(uint8); +#endif +}; diff --git a/src/control/Restart.cpp b/src/control/Restart.cpp new file mode 100644 index 0000000..2f5e3d4 --- /dev/null +++ b/src/control/Restart.cpp @@ -0,0 +1,249 @@ +#include "common.h" + +#include "Restart.h" +#include "SaveBuf.h" +#include "Zones.h" +#include "PathFind.h" + +uint8 CRestart::OverrideHospitalLevel; +uint8 CRestart::OverridePoliceStationLevel; +bool CRestart::bFadeInAfterNextArrest; +bool CRestart::bFadeInAfterNextDeath; + +bool CRestart::bOverrideRestart; +CVector CRestart::OverridePosition; +float CRestart::OverrideHeading; + +CVector CRestart::HospitalRestartPoints[NUM_RESTART_POINTS]; +float CRestart::HospitalRestartHeadings[NUM_RESTART_POINTS]; +uint16 CRestart::NumberOfHospitalRestarts; + +CVector CRestart::PoliceRestartPoints[NUM_RESTART_POINTS]; +float CRestart::PoliceRestartHeadings[NUM_RESTART_POINTS]; +uint16 CRestart::NumberOfPoliceRestarts; + +void +CRestart::Initialise() +{ + OverridePoliceStationLevel = LEVEL_GENERIC; + OverrideHospitalLevel = LEVEL_GENERIC; + bFadeInAfterNextArrest = true; + bFadeInAfterNextDeath = true; + OverrideHeading = 0.0f; + OverridePosition = CVector(0.0f, 0.0f, 0.0f); + bOverrideRestart = false; + NumberOfPoliceRestarts = 0; + NumberOfHospitalRestarts = 0; + + for (int i = 0; i < NUM_RESTART_POINTS; i++) { + HospitalRestartPoints[i] = CVector(0.0f, 0.0f, 0.0f); + HospitalRestartHeadings[i] = 0.0f; + PoliceRestartPoints[i] = CVector(0.0f, 0.0f, 0.0f); + PoliceRestartHeadings[i] = 0.0f; + } +} + +void +CRestart::AddHospitalRestartPoint(const CVector &pos, float heading) +{ + HospitalRestartPoints[NumberOfHospitalRestarts] = pos; + HospitalRestartHeadings[NumberOfHospitalRestarts++] = heading; +} + +void +CRestart::AddPoliceRestartPoint(const CVector &pos, float heading) +{ + PoliceRestartPoints[NumberOfPoliceRestarts] = pos; + PoliceRestartHeadings[NumberOfPoliceRestarts++] = heading; +} + +void +CRestart::OverrideNextRestart(const CVector &pos, float heading) +{ + bOverrideRestart = true; + OverridePosition = pos; + OverrideHeading = heading; +} + +void +CRestart::CancelOverrideRestart() +{ + bOverrideRestart = false; +} + +void +CRestart::FindClosestHospitalRestartPoint(const CVector &pos, CVector *outPos, float *outHeading) +{ + if (bOverrideRestart) { + *outPos = OverridePosition; + *outHeading = OverrideHeading; + CancelOverrideRestart(); + return; + } + + eLevelName curlevel = CTheZones::FindZoneForPoint(pos); + float fMinDist = SQR(4000.0f); + int closestPoint = NUM_RESTART_POINTS; + + // find closest point on this level + for (int i = 0; i < NumberOfHospitalRestarts; i++) { + if (CTheZones::FindZoneForPoint(HospitalRestartPoints[i]) == (OverrideHospitalLevel != LEVEL_GENERIC ? OverrideHospitalLevel : curlevel)) { + float dist = (pos - HospitalRestartPoints[i]).MagnitudeSqr(); + if (fMinDist >= dist) { + fMinDist = dist; + closestPoint = i; + } + } + } + + // if we didn't find anything, find closest point on any level + if (closestPoint == NUM_RESTART_POINTS) { + for (int i = 0; i < NumberOfHospitalRestarts; i++) { + float dist = (pos - HospitalRestartPoints[i]).MagnitudeSqr(); + if (fMinDist >= dist) { + fMinDist = dist; + closestPoint = i; + } + } + } + + // if we still didn't find anything, find closest path node + if (closestPoint == NUM_RESTART_POINTS) { + *outPos = ThePaths.m_pathNodes[ThePaths.FindNodeClosestToCoors(pos, PATH_PED, 999999.9f)].GetPosition(); + *outHeading = 0.0f; + printf("Couldn't find a hospital restart zone near the player %f %f %f->%f %f %f\n", pos.x, pos.y, pos.z, outPos->x, outPos->y, outPos->z); + } else { + *outPos = HospitalRestartPoints[closestPoint]; + *outHeading = HospitalRestartHeadings[closestPoint]; + } +} + +void +CRestart::FindClosestPoliceRestartPoint(const CVector &pos, CVector *outPos, float *outHeading) +{ + if (bOverrideRestart) { + *outPos = OverridePosition; + *outHeading = OverrideHeading; + CancelOverrideRestart(); + return; + } + + eLevelName curlevel = CTheZones::FindZoneForPoint(pos); + float fMinDist = SQR(4000.0f); + int closestPoint = NUM_RESTART_POINTS; + + // find closest point on this level + for (int i = 0; i < NumberOfPoliceRestarts; i++) { + if (CTheZones::FindZoneForPoint(PoliceRestartPoints[i]) == (OverridePoliceStationLevel != LEVEL_GENERIC ? OverridePoliceStationLevel : curlevel)) { + float dist = (pos - PoliceRestartPoints[i]).MagnitudeSqr(); + if (fMinDist >= dist) { + fMinDist = dist; + closestPoint = i; + } + } + } + + // if we didn't find anything, find closest point on any level + if (closestPoint == NUM_RESTART_POINTS) { + for (int i = 0; i < NumberOfPoliceRestarts; i++) { + float dist = (pos - PoliceRestartPoints[i]).MagnitudeSqr(); + if (fMinDist >= dist) { + fMinDist = dist; + closestPoint = i; + } + } + } + + // if we still didn't find anything, find closest path node + if (closestPoint == NUM_RESTART_POINTS) { + printf("Couldn't find a police restart zone near the player\n"); + *outPos = ThePaths.m_pathNodes[ThePaths.FindNodeClosestToCoors(pos, PATH_PED, 999999.9f)].GetPosition(); + *outHeading = 0.0f; + } else { + *outPos = PoliceRestartPoints[closestPoint]; + *outHeading = PoliceRestartHeadings[closestPoint]; + } +} + +void +CRestart::LoadAllRestartPoints(uint8 *buf, uint32 size) +{ + Initialise(); + +INITSAVEBUF + CheckSaveHeader(buf, 'R','S','T','\0', size - SAVE_HEADER_SIZE); + + for (int i = 0; i < NUM_RESTART_POINTS; i++) { + ReadSaveBuf(&HospitalRestartPoints[i], buf); + ReadSaveBuf(&HospitalRestartHeadings[i], buf); + } + + for (int i = 0; i < NUM_RESTART_POINTS; i++) { + ReadSaveBuf(&PoliceRestartPoints[i], buf); + ReadSaveBuf(&PoliceRestartHeadings[i], buf); + } + + ReadSaveBuf(&NumberOfHospitalRestarts, buf); + ReadSaveBuf(&NumberOfPoliceRestarts, buf); + ReadSaveBuf(&bOverrideRestart, buf); + + // skip something unused + SkipSaveBuf(buf, 3); + + ReadSaveBuf(&OverridePosition, buf); + ReadSaveBuf(&OverrideHeading, buf); + ReadSaveBuf(&bFadeInAfterNextDeath, buf); + ReadSaveBuf(&bFadeInAfterNextArrest, buf); + ReadSaveBuf(&OverrideHospitalLevel, buf); + ReadSaveBuf(&OverridePoliceStationLevel, buf); +VALIDATESAVEBUF(size); +} + +void +CRestart::SaveAllRestartPoints(uint8 *buf, uint32 *size) +{ + *size = SAVE_HEADER_SIZE + + sizeof(HospitalRestartPoints) + + sizeof(HospitalRestartHeadings) + + sizeof(PoliceRestartPoints) + + sizeof(PoliceRestartHeadings) + + sizeof(NumberOfHospitalRestarts) + + sizeof(NumberOfPoliceRestarts) + + sizeof(bOverrideRestart) + + sizeof(uint8) + + sizeof(uint16) + + sizeof(OverridePosition) + + sizeof(OverrideHeading) + + sizeof(bFadeInAfterNextDeath) + + sizeof(bFadeInAfterNextArrest) + + sizeof(OverrideHospitalLevel) + + sizeof(OverridePoliceStationLevel); // == 292 + +INITSAVEBUF + WriteSaveHeader(buf, 'R','S','T','\0', *size - SAVE_HEADER_SIZE); + + for (int i = 0; i < NUM_RESTART_POINTS; i++) { + WriteSaveBuf(buf, HospitalRestartPoints[i]); + WriteSaveBuf(buf, HospitalRestartHeadings[i]); + } + + for (int i = 0; i < NUM_RESTART_POINTS; i++) { + WriteSaveBuf(buf, PoliceRestartPoints[i]); + WriteSaveBuf(buf, PoliceRestartHeadings[i]); + } + + WriteSaveBuf(buf, NumberOfHospitalRestarts); + WriteSaveBuf(buf, NumberOfPoliceRestarts); + WriteSaveBuf(buf, bOverrideRestart); + + WriteSaveBuf(buf, (uint8)0); + WriteSaveBuf(buf, (uint16)0); + + WriteSaveBuf(buf, OverridePosition); + WriteSaveBuf(buf, OverrideHeading); + WriteSaveBuf(buf, bFadeInAfterNextDeath); + WriteSaveBuf(buf, bFadeInAfterNextArrest); + WriteSaveBuf(buf, OverrideHospitalLevel); + WriteSaveBuf(buf, OverridePoliceStationLevel); +VALIDATESAVEBUF(*size); +} diff --git a/src/control/Restart.h b/src/control/Restart.h new file mode 100644 index 0000000..5d84c72 --- /dev/null +++ b/src/control/Restart.h @@ -0,0 +1,36 @@ +#pragma once + +#define NUM_RESTART_POINTS 8 + +class CRestart +{ +public: + static void AddPoliceRestartPoint(const CVector&, float); + static void AddHospitalRestartPoint(const CVector&, float); + static void OverrideNextRestart(const CVector&, float); + + static void FindClosestHospitalRestartPoint(const CVector &, CVector *, float *); + static void FindClosestPoliceRestartPoint(const CVector &, CVector *, float *); + static void Initialise(); + static void CancelOverrideRestart(); + + static void LoadAllRestartPoints(uint8 *buf, uint32 size); + static void SaveAllRestartPoints(uint8 *buf, uint32 *size); + + static uint8 OverrideHospitalLevel; + static uint8 OverridePoliceStationLevel; + static bool bFadeInAfterNextArrest; + static bool bFadeInAfterNextDeath; + + static bool bOverrideRestart; + static CVector OverridePosition; + static float OverrideHeading; + + static CVector HospitalRestartPoints[NUM_RESTART_POINTS]; + static float HospitalRestartHeadings[NUM_RESTART_POINTS]; + static uint16 NumberOfHospitalRestarts; + + static CVector PoliceRestartPoints[NUM_RESTART_POINTS]; + static float PoliceRestartHeadings[NUM_RESTART_POINTS]; + static uint16 NumberOfPoliceRestarts; +}; diff --git a/src/control/RoadBlocks.cpp b/src/control/RoadBlocks.cpp new file mode 100644 index 0000000..4c3307c --- /dev/null +++ b/src/control/RoadBlocks.cpp @@ -0,0 +1,197 @@ +#include "common.h" + +#include "RoadBlocks.h" +#include "PathFind.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "World.h" +#include "PedPlacement.h" +#include "Automobile.h" +#include "CopPed.h" +#include "VisibilityPlugins.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "General.h" + +#define ROADBLOCKDIST (80.0f) + +int16 CRoadBlocks::NumRoadBlocks; +int16 CRoadBlocks::RoadBlockObjects[NUMROADBLOCKS]; +bool CRoadBlocks::InOrOut[NUMROADBLOCKS]; + +void +CRoadBlocks::Init(void) +{ + int i; + NumRoadBlocks = 0; + for (i = 0; i < ThePaths.m_numMapObjects; i++) { + if (ThePaths.m_objectFlags[i] & UseInRoadBlock) { + if (NumRoadBlocks < NUMROADBLOCKS) { + InOrOut[NumRoadBlocks] = true; + RoadBlockObjects[NumRoadBlocks] = i; + NumRoadBlocks++; + } else { +#ifndef MASTER + printf("Not enough room for the potential roadblocks\n"); +#endif + // FIX: Don't iterate loop after NUMROADBLOCKS + return; + } + } + } +} + +void +CRoadBlocks::GenerateRoadBlockCopsForCar(CVehicle* pVehicle, int32 roadBlockType, int16 roadBlockNode) +{ + static const CVector vecRoadBlockOffets[6] = { CVector(-1.5, 1.8f, 0.0f), CVector(-1.5f, -1.8f, 0.0f), CVector(1.5f, 1.8f, 0.0f), + CVector(1.5f, -1.8f, 0.0f), CVector(-1.5f, 0.0f, 0.0f), CVector(1.5, 0.0, 0.0) }; + CEntity* pEntityToAttack = (CEntity*)FindPlayerVehicle(); + if (!pEntityToAttack) + pEntityToAttack = (CEntity*)FindPlayerPed(); + CColModel* pPoliceColModel = CModelInfo::GetColModel(MI_POLICE); + float fRadius = pVehicle->GetBoundRadius() / pPoliceColModel->boundingSphere.radius; + for (int32 i = 0; i < 2; i++) { + const int32 roadBlockIndex = i + 2 * roadBlockType; + CVector posForZ = pVehicle->GetMatrix() * (fRadius * vecRoadBlockOffets[roadBlockIndex]); + int32 modelInfoId = MI_COP; + eCopType copType = COP_STREET; + switch (pVehicle->GetModelIndex()) + { + case MI_FBICAR: + modelInfoId = MI_FBI; + copType = COP_FBI; + break; + case MI_ENFORCER: + modelInfoId = MI_SWAT; + copType = COP_SWAT; + break; + case MI_BARRACKS: + modelInfoId = MI_ARMY; + copType = COP_ARMY; + break; + } + if (!CStreaming::HasModelLoaded(modelInfoId)) + copType = COP_STREET; + CCopPed* pCopPed = new CCopPed(copType); + if (copType == COP_STREET) + pCopPed->SetCurrentWeapon(WEAPONTYPE_COLT45); + CPedPlacement::FindZCoorForPed(&posForZ); + pCopPed->SetPosition(posForZ); + pCopPed->SetOrientation(0.0f, 0.0f, -HALFPI); + pCopPed->m_bIsDisabledCop = true; + pCopPed->SetIdle(); + pCopPed->bKindaStayInSamePlace = true; + pCopPed->bNotAllowedToDuck = false; + pCopPed->m_nRoadblockNode = roadBlockNode; + pCopPed->bCrouchWhenShooting = roadBlockType != 2; + if (pEntityToAttack) { + pCopPed->SetWeaponLockOnTarget(pEntityToAttack); + pCopPed->SetAttack(pEntityToAttack); + } + pCopPed->m_pMyVehicle = pVehicle; + pVehicle->RegisterReference((CEntity**)&pCopPed->m_pMyVehicle); + pCopPed->bCullExtraFarAway = true; + CVisibilityPlugins::SetClumpAlpha(pCopPed->GetClump(), 0); + CWorld::Add(pCopPed); + } +} + +void +CRoadBlocks::GenerateRoadBlocks(void) +{ +#ifdef SQUEEZE_PERFORMANCE + if (FindPlayerPed()->m_pWanted->m_RoadblockDensity == 0) + return; +#endif + CMatrix offsetMatrix; + uint32 frame = CTimer::GetFrameCounter() & 0xF; + int16 nRoadblockNode = (int16)(NUMROADBLOCKS * frame) / 16; + const int16 maxRoadBlocks = (int16)(NUMROADBLOCKS * (frame + 1)) / 16; + for (; nRoadblockNode < Min(NumRoadBlocks, maxRoadBlocks); nRoadblockNode++) { + CTreadable *mapObject = ThePaths.m_mapObjects[RoadBlockObjects[nRoadblockNode]]; + CVector2D vecDistance = FindPlayerCoors() - mapObject->GetPosition(); + if (vecDistance.x > -ROADBLOCKDIST && vecDistance.x < ROADBLOCKDIST && + vecDistance.y > -ROADBLOCKDIST && vecDistance.y < ROADBLOCKDIST && + vecDistance.Magnitude() < ROADBLOCKDIST) { + if (!InOrOut[nRoadblockNode]) { + InOrOut[nRoadblockNode] = true; + if (FindPlayerVehicle() && (CGeneral::GetRandomNumber() & 0x7F) < FindPlayerPed()->m_pWanted->m_RoadblockDensity) { + CWanted *pPlayerWanted = FindPlayerPed()->m_pWanted; + float fMapObjectRadius = 2.0f * mapObject->GetColModel()->boundingBox.max.x; + int32 vehicleId = MI_POLICE; + if (pPlayerWanted->AreArmyRequired()) + vehicleId = MI_BARRACKS; + else if (pPlayerWanted->AreFbiRequired()) + vehicleId = MI_FBICAR; + else if (pPlayerWanted->AreSwatRequired()) + vehicleId = MI_ENFORCER; + if (!CStreaming::HasModelLoaded(vehicleId)) + vehicleId = MI_POLICE; + CColModel *pVehicleColModel = CModelInfo::GetColModel(vehicleId); + float fModelRadius = 2.0f * pVehicleColModel->boundingSphere.radius + 0.25f; + int16 radius = (int16)(fMapObjectRadius / fModelRadius); + if (radius >= 6) + continue; + CVector2D vecDistanceToCamera = TheCamera.GetPosition() - mapObject->GetPosition(); + float fDotProduct = DotProduct2D(vecDistanceToCamera, mapObject->GetForward()); + float fOffset = 0.5f * fModelRadius * (float)(radius - 1); + for (int16 i = 0; i < radius; i++) { + uint8 nRoadblockType = fDotProduct < 0.0f; + if (CGeneral::GetRandomNumber() & 1) { + offsetMatrix.SetRotateZ(((CGeneral::GetRandomNumber() & 0xFF) - 128.0f) * 0.003f + HALFPI); + } + else { + nRoadblockType = !nRoadblockType; + offsetMatrix.SetRotateZ(((CGeneral::GetRandomNumber() & 0xFF) - 128.0f) * 0.003f - HALFPI); + } + if (ThePaths.m_objectFlags[RoadBlockObjects[nRoadblockNode]] & ObjectEastWest) + offsetMatrix.GetPosition() = CVector(0.0f, i * fModelRadius - fOffset, 0.6f); + else + offsetMatrix.GetPosition() = CVector(i * fModelRadius - fOffset, 0.0f, 0.6f); + CMatrix vehicleMatrix = mapObject->GetMatrix() * offsetMatrix; + float fModelRadius = CModelInfo::GetColModel(vehicleId)->boundingSphere.radius - 0.25f; + int16 colliding = 0; + CWorld::FindObjectsKindaColliding(vehicleMatrix.GetPosition(), fModelRadius, 0, &colliding, 2, nil, false, true, true, false, false); + if (!colliding) { + CAutomobile *pVehicle = new CAutomobile(vehicleId, RANDOM_VEHICLE); + pVehicle->SetStatus(STATUS_ABANDONED); + // pVehicle->GetHeightAboveRoad(); // called but return value is ignored? + vehicleMatrix.GetPosition().z += fModelRadius - 0.6f; + pVehicle->SetMatrix(vehicleMatrix); + pVehicle->PlaceOnRoadProperly(); + pVehicle->SetIsStatic(false); + pVehicle->GetMatrix().UpdateRW(); + pVehicle->m_nDoorLock = CARLOCK_UNLOCKED; + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->bIsLocked = false; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nCurrentLane = 0; + pVehicle->AutoPilot.m_nNextLane = 0; + pVehicle->AutoPilot.m_fMaxTrafficSpeed = 0.0f; + pVehicle->AutoPilot.m_nCruiseSpeed = 0.0f; + pVehicle->bExtendedRange = true; + if (pVehicle->UsesSiren(pVehicle->GetModelIndex()) && CGeneral::GetRandomNumber() & 1) + pVehicle->m_bSirenOrAlarm = true; + if (pVehicle->GetUp().z > 0.94f) { + CVisibilityPlugins::SetClumpAlpha(pVehicle->GetClump(), 0); + CWorld::Add(pVehicle); + pVehicle->bCreateRoadBlockPeds = true; + pVehicle->m_nRoadblockType = nRoadblockType; + pVehicle->m_nRoadblockNode = nRoadblockNode; + } + else { + delete pVehicle; + } + } + } + } + } + } else { + InOrOut[nRoadblockNode] = false; + } + } +} diff --git a/src/control/RoadBlocks.h b/src/control/RoadBlocks.h new file mode 100644 index 0000000..0f0c188 --- /dev/null +++ b/src/control/RoadBlocks.h @@ -0,0 +1,16 @@ +#pragma once +#include "common.h" + +class CVehicle; + +class CRoadBlocks +{ +public: + static int16 NumRoadBlocks; + static int16 RoadBlockObjects[NUMROADBLOCKS]; + static bool InOrOut[NUMROADBLOCKS]; + + static void Init(void); + static void GenerateRoadBlockCopsForCar(CVehicle* pVehicle, int32 roadBlockType, int16 roadBlockNode); + static void GenerateRoadBlocks(void); +}; diff --git a/src/control/SceneEdit.cpp b/src/control/SceneEdit.cpp new file mode 100644 index 0000000..42dadee --- /dev/null +++ b/src/control/SceneEdit.cpp @@ -0,0 +1,1127 @@ +#include "common.h" + +#include "SceneEdit.h" +#ifdef GTA_SCENE_EDIT +#include "Automobile.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "CivilianPed.h" +#include "FileMgr.h" +#include "Font.h" +#include "ModelIndices.h" +#include "ModelInfo.h" +#include "Pad.h" +#include "Ped.h" +#include "Population.h" +#include "Text.h" +#include "Timecycle.h" +#include "Streaming.h" +#include "Vehicle.h" +#include "WeaponInfo.h" +#include "World.h" + +bool CSceneEdit::m_bEditOn; +int32 CSceneEdit::m_bCameraFollowActor; +bool CSceneEdit::m_bRecording; +CVector CSceneEdit::m_vecCurrentPosition; +CVector CSceneEdit::m_vecCamHeading; +CVector CSceneEdit::m_vecGotoPosition; +int32 CSceneEdit::m_nVehicle; +int32 CSceneEdit::m_nVehicle2; +int32 CSceneEdit::m_nActor; +int32 CSceneEdit::m_nActor2; +int32 CSceneEdit::m_nVehiclemodelId; +int32 CSceneEdit::m_nPedmodelId; +int16 CSceneEdit::m_nCurrentMovieCommand; +int16 CSceneEdit::m_nNumActors; +int16 CSceneEdit::m_nNumMovieCommands; +int16 CSceneEdit::m_nCurrentCommand; +int16 CSceneEdit::m_nCurrentVehicle; +int16 CSceneEdit::m_nCurrentActor; +int16 CSceneEdit::m_nWeaponType; +bool CSceneEdit::m_bCommandActive; +bool CSceneEdit::m_bActorSelected; +bool CSceneEdit::m_bActor2Selected; +bool CSceneEdit::m_bVehicleSelected; +int16 CSceneEdit::m_nNumVehicles; +CPed* CSceneEdit::pActors[NUM_ACTORS_IN_MOVIE]; +CVehicle* CSceneEdit::pVehicles[NUM_VEHICLES_IN_MOVIE]; +bool CSceneEdit::m_bDrawGotoArrow; +CMovieCommand CSceneEdit::Movie[NUM_COMMANDS_IN_MOVIE]; + +#define SHADOW_OFFSET (2.0f) +#define ACTION_MESSAGE_X_RIGHT (60.0f) +#define ACTION_MESSAGE_Y (8.0f) +#define SELECTED_MESSAGE_X_RIGHT (60.0f) +#define SELECTED_MESSAGE_Y (248.0f) +#define COMMAND_NAME_X_RIGHT (60.0f) +#define COMMAND_NAME_Y (38.0f) +#define COMMAND_NAME_HEIGHT (16.0f) + +#define NUM_COMMANDS_TO_DRAW (9) + +static const char* pCommandStrings[] = { + "do-nothing", "New Actor", "Move Actor", "Select Actor", "Delete Actor", + "New Vehicle", "Move Vehicle", "Select Vehicle", "Delete Vehicle", "Give Weapon", + "Goto", "Goto (wait)", "Get In Car", "Get Out Car", "Kill", + "Flee", "Wait", "Position Camera", "Set Camera Target", "Select Camera Mode", + "Save Movie", "Load Movie", "Play Movie", "END" +}; + +#ifdef CHECK_STRUCT_SIZES +static_assert(ARRAY_SIZE(pCommandStrings) == CSceneEdit::MOVIE_TOTAL_COMMANDS, "Scene edit: not all commands have names"); +#endif + +static int32 NextValidModelId(int32 mi, int32 step) +{ + int32 result = -1; + int32 i = mi; + while (result == -1) { + i += step; + if (i < 0 || i > MODELINFOSIZE) { + step = -step; + continue; + } + CBaseModelInfo* pInfo = CModelInfo::GetModelInfo(i); + CVehicleModelInfo* pVehicleInfo = (CVehicleModelInfo*)pInfo; + if (!pInfo) + continue; + if (pInfo->GetModelType() == MITYPE_PED +#ifdef FIX_BUGS + && !(i >= MI_SPECIAL01 && i <= MI_SPECIAL04) +#endif + || pInfo->GetModelType() == MITYPE_VEHICLE && +#ifdef FIX_BUGS + (pVehicleInfo->m_vehicleType == VEHICLE_TYPE_CAR || pVehicleInfo->m_vehicleType == VEHICLE_TYPE_BOAT)) +#else // && and || priority failure it seems, also crashes on special models + pVehicleInfo->m_vehicleType == VEHICLE_TYPE_CAR || pVehicleInfo->m_vehicleType == VEHICLE_TYPE_BOAT) +#endif + result = i; + } + return result; +} + +void CSceneEdit::LoadMovie(void) +{ + ReInitialise(); + CFileMgr::SetDir("DATA"); + int fid = CFileMgr::OpenFile("movie.dat", "r"); +#ifdef FIX_BUGS + if (fid >= 0) +#endif + { + CFileMgr::Read(fid, (char*)&Movie, sizeof(Movie)); + CFileMgr::Read(fid, (char*)&m_nNumMovieCommands, sizeof(m_nNumMovieCommands)); + CFileMgr::CloseFile(fid); + } + CFileMgr::SetDir(""); + m_bCommandActive = false; +} + +void CSceneEdit::SaveMovie(void) +{ + CFileMgr::SetDir("DATA"); + int fid = CFileMgr::OpenFileForWriting("movie.dat"); + if (fid >= 0) { + CFileMgr::Write(fid, (char*)&Movie, sizeof(Movie)); + CFileMgr::Write(fid, (char*)&m_nNumMovieCommands, sizeof(m_nNumMovieCommands)); + CFileMgr::CloseFile(fid); + } + CFileMgr::SetDir(""); + m_bCommandActive = false; +} + +void CSceneEdit::Initialise(void) +{ + m_nActor = -1; + m_nActor2 = -1; + m_nVehicle = -1; + m_nVehicle2 = -1; + m_nCurrentCommand = MOVIE_NEW_ACTOR; + m_nVehiclemodelId = MI_INFERNUS; + m_nPedmodelId = MI_MALE01; + m_nNumVehicles = 0; + m_nNumActors = 0; + m_nNumMovieCommands = 0; + m_bCommandActive = false; + m_bRecording = true; + m_bEditOn = false; + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) + pActors[i] = nil; + for (int i = 0; i < NUM_VEHICLES_IN_MOVIE; i++) + pVehicles[i] = nil; + m_vecCamHeading = TheCamera.Cams[TheCamera.ActiveCam].Front; + m_vecGotoPosition = CVector(0.0f, 0.0f, 0.0f); + m_bCameraFollowActor = false; + TheCamera.Cams[TheCamera.ActiveCam].ResetStatics = true; + m_bDrawGotoArrow = false; +} + +void CSceneEdit::InitPlayback(void) +{ + m_nVehiclemodelId = MI_INFERNUS; + m_nPedmodelId = MI_MALE01; + m_bCommandActive = false; + m_nNumActors = 0; + m_nNumVehicles = 0; + m_nActor = -1; + m_nActor2 = -1; + m_nVehicle = -1; + m_nVehicle2 = -1; + TheCamera.Cams[TheCamera.ActiveCam].ResetStatics = true; + m_vecCamHeading = TheCamera.Cams[TheCamera.ActiveCam].Front; + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i]) { + CPopulation::RemovePed(pActors[i]); + pActors[i] = nil; + } + } + m_nCurrentActor = 0; + for (int i = 0; i < NUM_VEHICLES_IN_MOVIE; i++) { + if (pVehicles[i]) { + CWorld::Remove(pVehicles[i]); + delete pVehicles[i]; + pVehicles[i] = nil; + } + } + m_nCurrentVehicle = 0; + m_vecGotoPosition = CVector(0.0f, 0.0f, 0.0f); + m_nCurrentMovieCommand = MOVIE_DO_NOTHING; + m_bDrawGotoArrow = false; +} + +void CSceneEdit::ReInitialise(void) +{ + m_nVehiclemodelId = MI_INFERNUS; + m_nPedmodelId = MI_MALE01; + m_nCurrentCommand = MOVIE_NEW_ACTOR; + m_bEditOn = true; + m_bRecording = true; + m_bCommandActive = false; +#ifdef FIX_BUGS + m_bCameraFollowActor = false; + TheCamera.Cams[TheCamera.ActiveCam].ResetStatics = true; // not enough... +#endif + m_nActor = -1; + m_nActor2 = -1; + m_nVehicle = -1; + m_nVehicle2 = -1; + m_nNumMovieCommands = 0; + m_nCurrentMovieCommand = MOVIE_DO_NOTHING; + m_nNumActors = 0; + m_nNumVehicles = 0; + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i]) { + CPopulation::RemovePed(pActors[i]); + pActors[i] = nil; + } + } + for (int i = 0; i < NUM_VEHICLES_IN_MOVIE; i++) { + if (pVehicles[i]) { + CWorld::Remove(pVehicles[i]); + delete pVehicles[i]; + pVehicles[i] = nil; + } + } + for (int i = 0; i < NUM_COMMANDS_IN_MOVIE; i++) { + Movie[i].m_nCommandId = MOVIE_DO_NOTHING; + Movie[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f); + Movie[i].m_vecCamera = CVector(0.0f, 0.0f, 0.0f); + Movie[i].m_nActorId = -1; + Movie[i].m_nActor2Id = -1; + Movie[i].m_nVehicleId = -1; + Movie[i].m_nModelIndex = 0; + } + m_vecGotoPosition = CVector(0.0f, 0.0f, 0.0f); + m_bDrawGotoArrow = false; +} + +void CSceneEdit::Update(void) +{ + if (!m_bEditOn) + return; + if (m_bRecording) + ProcessCommand(); + else { + if (m_bCameraFollowActor && m_nActor != -1) { + if (pActors[m_nActor]->bInVehicle) + TheCamera.TakeControl(pActors[m_nActor]->m_pMyVehicle, CCam::MODE_BEHINDCAR, JUMP_CUT, CAMCONTROL_SCRIPT); + else + TheCamera.TakeControl(pActors[m_nActor], CCam::MODE_FOLLOWPED, JUMP_CUT, CAMCONTROL_SCRIPT); + } + PlayBack(); + } +} + +void CSceneEdit::Draw(void) +{ + char str[200]; + wchar wstr[200]; + if (TheCamera.m_WideScreenOn) + return; +#ifndef FIX_BUGS + CFont::SetPropOff(); +#endif + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetCentreOn(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); +#ifdef FIX_BUGS + CFont::SetFontStyle(FONT_BANK); + CFont::SetPropOn(); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetDropShadowPosition(1); +#else + CFont::SetFontStyle(FONT_HEADING); + CFont::SetPropOff(); +#endif + sprintf(str, "Action"); + AsciiToUnicode(str, wstr); + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(ACTION_MESSAGE_X_RIGHT - SHADOW_OFFSET), SCREEN_SCALE_Y(ACTION_MESSAGE_Y + SHADOW_OFFSET), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-ACTION_MESSAGE_X_RIGHT) + SHADOW_OFFSET, SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-ACTION_MESSAGE_Y) + SHADOW_OFFSET, wstr); +#endif + CFont::SetColor(CRGBA(193, 164, 120, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(ACTION_MESSAGE_X_RIGHT), SCREEN_SCALE_Y(ACTION_MESSAGE_Y), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-ACTION_MESSAGE_X_RIGHT), SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-ACTION_MESSAGE_Y), wstr); +#endif + sprintf(str, "Selected"); + AsciiToUnicode(str, wstr); + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(SELECTED_MESSAGE_X_RIGHT - SHADOW_OFFSET), SCREEN_SCALE_Y(SELECTED_MESSAGE_Y + SHADOW_OFFSET), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-SELECTED_MESSAGE_X_RIGHT) + SHADOW_OFFSET, SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-SELECTED_MESSAGE_Y) + SHADOW_OFFSET, wstr); +#endif + CFont::SetColor(CRGBA(193, 164, 120, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(SELECTED_MESSAGE_X_RIGHT), SCREEN_SCALE_Y(SELECTED_MESSAGE_Y), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-SELECTED_MESSAGE_X_RIGHT), SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-SELECTED_MESSAGE_Y), wstr); +#endif + CFont::SetCentreOff(); +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(0.7f), SCREEN_SCALE_Y(0.7f)); +#else + CFont::SetScale(0.7f, 0.7f); +#endif +#ifdef FIX_BUGS + CFont::SetFontStyle(FONT_BANK); +#else + CFont::SetFontStyle(FONT_HEADING); +#endif + CFont::SetColor(CRGBA(0, 0, 0, 255)); + for (int i = 0; i < NUM_COMMANDS_TO_DRAW; i++) { + int16 nCommandDrawn = m_nCurrentCommand + i - NUM_COMMANDS_TO_DRAW / 2; + if (nCommandDrawn >= MOVIE_TOTAL_COMMANDS) + nCommandDrawn -= (MOVIE_TOTAL_COMMANDS - 1); + if (nCommandDrawn <= MOVIE_DO_NOTHING) + nCommandDrawn += (MOVIE_TOTAL_COMMANDS - 1); + sprintf(str, "%s", pCommandStrings[nCommandDrawn]); + AsciiToUnicode(str, wstr); + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(COMMAND_NAME_X_RIGHT - SHADOW_OFFSET), SCREEN_SCALE_Y(COMMAND_NAME_Y + SHADOW_OFFSET + i * COMMAND_NAME_HEIGHT), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-COMMAND_NAME_X_RIGHT) + SHADOW_OFFSET, SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-COMMAND_NAME_Y) + SHADOW_OFFSET + i * COMMAND_NAME_HEIGHT, wstr); +#endif + if (nCommandDrawn == m_nCurrentCommand) + CFont::SetColor(CRGBA(156, 91, 40, 255)); + else + CFont::SetColor(CRGBA(193, 164, 120, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(COMMAND_NAME_X_RIGHT), SCREEN_SCALE_Y(COMMAND_NAME_Y + i * COMMAND_NAME_HEIGHT), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-COMMAND_NAME_X_RIGHT), SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-COMMAND_NAME_Y) + i * COMMAND_NAME_HEIGHT, wstr); +#endif + } +} + +void CSceneEdit::ProcessCommand(void) +{ + if (!m_bCommandActive) { + ClearForNewCommand(); + if (CPad::GetPad(1)->GetDPadUpJustDown()) { + if (--m_nCurrentCommand == MOVIE_DO_NOTHING) + m_nCurrentCommand = MOVIE_END; + } + if (CPad::GetPad(1)->GetDPadDownJustDown()) { + if (++m_nCurrentCommand == MOVIE_TOTAL_COMMANDS) + m_nCurrentCommand = MOVIE_NEW_ACTOR; + } + if (CPad::GetPad(1)->GetTriangleJustDown()) { + if (m_nCurrentCommand != MOVIE_DO_NOTHING) + m_bCommandActive = true; + } + return; + } + switch (m_nCurrentCommand) { + case MOVIE_DO_NOTHING: + m_bCommandActive = false; + break; + case MOVIE_NEW_ACTOR: + if (m_nActor == -1) { + if (m_nNumActors == NUM_ACTORS_IN_MOVIE) + break; + if (!CStreaming::HasModelLoaded(m_nPedmodelId)) { + CStreaming::RequestModel(m_nPedmodelId, 0); +#ifdef FIX_BUGS + CStreaming::LoadAllRequestedModels(false); // otherwise gets stuck :( +#endif + break; + } + CPed* pPed = new CCivilianPed(PEDTYPE_SPECIAL, m_nPedmodelId); + pPed->CharCreatedBy = MISSION_CHAR; + pPed->SetPosition(m_vecCurrentPosition); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(pPed); + pPed->bUsesCollision = false; + pPed->bAffectedByGravity = false; + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i] == nil) { + m_nActor = i; + pActors[i] = pPed; + break; + } + } + } + else { + pActors[m_nActor]->SetPosition(m_vecCurrentPosition); + pActors[m_nActor]->SetOrientation(0.0f, 0.0f, 0.0f); + int32 mi = m_nPedmodelId; + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) + mi = NextValidModelId(m_nPedmodelId, -1); + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()) + mi = NextValidModelId(m_nPedmodelId, 1); + if (mi == m_nPedmodelId) { + if (CPad::GetPad(1)->GetTriangleJustDown()) { + pActors[m_nActor]->bUsesCollision = true; + pActors[m_nActor]->bAffectedByGravity = true; + ++m_nNumActors; + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_NEW_ACTOR; + Movie[m_nNumMovieCommands].m_vecPosition = m_vecCurrentPosition; + Movie[m_nNumMovieCommands].m_nModelIndex = m_nPedmodelId; + Movie[m_nNumMovieCommands++].m_nActorId = m_nActor; + m_nActor = -1; + m_bCommandActive = false; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + CWorld::Remove(pActors[m_nActor]); + delete pActors[m_nActor]; + pActors[m_nActor] = nil; + m_nActor = -1; + m_bCommandActive = false; + } + } + else { + m_nPedmodelId = mi; + if (pActors[m_nActor]) { + CWorld::Remove(pActors[m_nActor]); + delete pActors[m_nActor]; + } + pActors[m_nActor] = nil; + m_nActor = -1; + } + } + break; + case MOVIE_MOVE_ACTOR: + SelectActor(); + if (m_bCommandActive) + break; + pActors[m_nActor]->SetPosition(m_vecCurrentPosition); + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bCommandActive = false; +#ifndef FIX_BUGS // why? it crashes, also makes no sense + pActors[m_nActor] = nil; +#endif + SelectActor(); + } + break; + case MOVIE_SELECT_ACTOR: + SelectActor(); + break; + case MOVIE_DELETE_ACTOR: + SelectActor(); + if (m_bActorSelected) { + CPopulation::RemovePed(pActors[m_nActor]); + m_nCurrentActor = 0; + --m_nNumActors; +#ifdef FIX_BUGS + pActors[m_nActor] = nil; + m_nActor = -1; +#else + m_nActor = -1; + pActors[m_nActor] = nil; +#endif + SelectActor(); + m_bCommandActive = false; + } + else if (CPad::GetPad(1)->GetCircleJustDown()) { + m_nActor = -1; + m_bCommandActive = false; + } + break; + case MOVIE_NEW_VEHICLE: + if (m_nVehicle == -1) { + if (m_nNumVehicles == NUM_VEHICLES_IN_MOVIE) + break; + if (!CStreaming::HasModelLoaded(m_nVehiclemodelId)) { + CStreaming::RequestModel(m_nVehiclemodelId, 0); +#ifdef FIX_BUGS + CStreaming::LoadAllRequestedModels(false); // otherwise gets stuck :( +#endif + break; + } + CVehicle* pVehicle = new CAutomobile(m_nVehiclemodelId, MISSION_VEHICLE); + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->SetPosition(m_vecCurrentPosition); + pVehicle->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(pVehicle); + pVehicle->bUsesCollision = false; + pVehicle->bAffectedByGravity = false; + for (int i = 0; i < NUM_VEHICLES_IN_MOVIE; i++) { + if (pVehicles[i] == nil) { + m_nVehicle = i; + pVehicles[i] = pVehicle; + break; + } + } + } + else { + pVehicles[m_nVehicle]->SetPosition(m_vecCurrentPosition); + pVehicles[m_nVehicle]->SetOrientation(0.0f, 0.0f, 0.0f); + int32 mi = m_nVehiclemodelId; + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) + mi = NextValidModelId(m_nVehiclemodelId, -1); + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()) + mi = NextValidModelId(m_nVehiclemodelId, 1); + if (mi == m_nVehiclemodelId) { + if (CPad::GetPad(1)->GetTriangleJustDown()) { + pVehicles[m_nVehicle]->bUsesCollision = true; + pVehicles[m_nVehicle]->bAffectedByGravity = true; + ++m_nNumVehicles; + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_NEW_VEHICLE; + Movie[m_nNumMovieCommands].m_vecPosition = m_vecCurrentPosition; + Movie[m_nNumMovieCommands].m_nModelIndex = m_nVehiclemodelId; + Movie[m_nNumMovieCommands++].m_nVehicleId = m_nVehicle; + m_nVehicle = -1; + m_bCommandActive = false; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + CWorld::Remove(pVehicles[m_nVehicle]); + delete pVehicles[m_nVehicle]; + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + m_bCommandActive = false; + } + } + else { + m_nVehiclemodelId = mi; + if (pVehicles[m_nVehicle]) { + CWorld::Remove(pVehicles[m_nVehicle]); + delete pVehicles[m_nVehicle]; + } + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + } + } + break; + case MOVIE_MOVE_VEHICLE: + SelectVehicle(); + if (m_bCommandActive) + break; + pVehicles[m_nVehicle]->SetPosition(m_vecCurrentPosition); + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bCommandActive = false; +#ifndef FIX_BUGS // again, why? works wrong + pVehicles[m_nVehicle] = nil; +#endif + m_nVehicle = -1; + } + break; + case MOVIE_SELECT_VEHICLE: + SelectVehicle(); + break; + case MOVIE_DELETE_VEHICLE: + SelectVehicle(); + if (m_bVehicleSelected) { + CWorld::Remove(pVehicles[m_nVehicle]); + delete pVehicles[m_nVehicle]; + m_nCurrentVehicle = 0; + --m_nNumVehicles; + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + SelectVehicle(); + m_bCommandActive = false; + } + else if (CPad::GetPad(1)->GetCircleJustDown()) { + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + m_bCommandActive = false; + } + break; + case MOVIE_GIVE_WEAPON: + if (m_bActorSelected) { + if (SelectWeapon()) { + m_bCommandActive = false; + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_GIVE_WEAPON; + Movie[m_nNumMovieCommands].m_nActorId = m_nActor; + Movie[m_nNumMovieCommands++].m_nModelIndex = m_nWeaponType; + } + } + else { + SelectActor(); + m_bCommandActive = true; + } + break; + case MOVIE_GOTO: + case MOVIE_GOTO_WAIT: + if (!m_bActorSelected) { + m_bDrawGotoArrow = true; + SelectActor(); + if (m_nActor == -1) + m_bCommandActive = true; + } + else { + m_vecGotoPosition = m_vecCurrentPosition; + if (CPad::GetPad(1)->GetTriangleJustDown()) { + if (pActors[m_nActor]->bInVehicle) { + if (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pActors[m_nActor]->m_pMyVehicle, m_vecGotoPosition, false)) + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_STRAIGHT; + else + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS; + pActors[m_nActor]->m_pMyVehicle->SetStatus(STATUS_PHYSICS); + pActors[m_nActor]->m_pMyVehicle->bEngineOn = true; + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCruiseSpeed = Max(16, pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCruiseSpeed); + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + TheCamera.TakeControl(pActors[m_nActor]->m_pMyVehicle, CCam::MODE_BEHINDCAR, JUMP_CUT, CAMCONTROL_SCRIPT); + } + else { + pActors[m_nActor]->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, m_vecGotoPosition); + TheCamera.TakeControl(pActors[m_nActor], CCam::MODE_FOLLOWPED, JUMP_CUT, CAMCONTROL_SCRIPT); + } + m_bDrawGotoArrow = false; + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_GOTO; + Movie[m_nNumMovieCommands].m_nActorId = m_nActor; + Movie[m_nNumMovieCommands++].m_vecPosition = m_vecGotoPosition; + } + if (!m_bDrawGotoArrow) { + if (pActors[m_nActor]->bInVehicle && pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_NONE || + !pActors[m_nActor]->bInVehicle && pActors[m_nActor]->m_objective == OBJECTIVE_NONE) { + if (pActors[m_nActor]) // if there is something that requires this check the least, it's this one + m_vecCamHeading = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].Source; + m_bCommandActive = false; + TheCamera.Cams[TheCamera.ActiveCam].Mode = CCam::MODE_FIGHT_CAM_RUNABOUT; + m_vecCurrentPosition = pActors[m_nActor]->GetPosition(); + pActors[m_nActor]->SetObjective(OBJECTIVE_NONE); + if (pActors[m_nActor]->bInVehicle) + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pActors[m_nActor] = nil; + m_nActor = -1; + m_bCommandActive = false; + } + } + break; + case MOVIE_GET_IN_CAR: + if (m_bActorSelected) + SelectVehicle(); + else { + SelectActor(); + if (m_nActor != -1) + m_bCommandActive = true; + } + if (m_bVehicleSelected) { + pActors[m_nActor]->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicles[m_nVehicle]); + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_GET_IN_CAR; + Movie[m_nNumMovieCommands].m_nActorId = m_nActor; + Movie[m_nNumMovieCommands++].m_nVehicleId = m_nVehicle; + m_nVehicle = -1; + m_bCommandActive = false; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + pActors[m_nActor] = nil; + m_nActor = -1; + m_bCommandActive = false; + } + break; + case MOVIE_GET_OUT_CAR: + SelectActor(); + if (m_bActorSelected) { + if (pActors[m_nActor]->bInVehicle) { + pActors[m_nActor]->SetObjective(OBJECTIVE_LEAVE_CAR); + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_GET_OUT_CAR; + Movie[m_nNumMovieCommands++].m_nActorId = m_nActor; + } + m_nActor = -1; + m_bCommandActive = false; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + pActors[m_nActor] = nil; + m_nActor = -1; + m_bCommandActive = false; + } + break; + case MOVIE_KILL: + if (!m_bActorSelected) { + SelectActor(); + m_bCommandActive = true; + } + else if (!m_bActor2Selected) { + SelectActor2(); + if (m_bActorSelected && m_bActor2Selected && m_nActor != -1 && m_nActor2 != -1 && m_nActor != m_nActor2) { + pActors[m_nActor]->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, pActors[m_nActor2]); + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_KILL; + Movie[m_nNumMovieCommands].m_nActorId = m_nActor; + Movie[m_nNumMovieCommands++].m_nActor2Id = m_nActor2; + m_bCommandActive = false; + } + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pActors[m_nActor] = nil; + m_nActor = -1; + pActors[m_nActor2] = nil; + m_nActor2 = -1; + m_bCommandActive = false; + } + break; + case MOVIE_FLEE: + if (!m_bActorSelected) { + SelectActor(); + m_bCommandActive = true; + } + else if (!m_bActor2Selected) { + SelectActor2(); + if (m_bActorSelected && m_bActor2Selected && m_nActor != -1 && m_nActor2 != -1 && m_nActor != m_nActor2) { + pActors[m_nActor]->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pActors[m_nActor2]); + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_FLEE; + Movie[m_nNumMovieCommands].m_nActorId = m_nActor; + Movie[m_nNumMovieCommands++].m_nActor2Id = m_nActor2; + m_bCommandActive = false; + } + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pActors[m_nActor] = nil; + m_nActor = -1; + pActors[m_nActor2] = nil; + m_nActor2 = -1; + m_bCommandActive = false; + } + break; + case MOVIE_WAIT: + SelectActor(); + if (m_bActorSelected) { + pActors[m_nActor]->SetObjective(OBJECTIVE_WAIT_ON_FOOT); + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_WAIT; + Movie[m_nNumMovieCommands++].m_nActorId = m_nActor; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pActors[m_nActor] = nil; + m_nActor = -1; + m_bCommandActive = false; + } + break; + case MOVIE_POSITION_CAMERA: + if (CPad::GetPad(1)->GetTriangleJustDown()) { + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_POSITION_CAMERA; + Movie[m_nNumMovieCommands].m_vecPosition = TheCamera.Cams[TheCamera.ActiveCam].Source; + Movie[m_nNumMovieCommands++].m_vecCamera = m_vecCamHeading; + m_bCommandActive = false; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + m_bCommandActive = false; + } + break; + case MOVIE_SET_CAMERA_TARGET: + if (!m_bActorSelected) { + SelectActor(); + m_bCommandActive = true; + } + else { + TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity = pActors[m_nActor]; + if (CPad::GetPad(1)->GetTriangleJustDown()) { + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_SET_CAMERA_TARGET; + Movie[m_nNumMovieCommands++].m_nActorId = m_nActor; + m_bCommandActive = false; + } + } + break; + case MOVIE_SELECT_CAMERA_MODE: + m_bCommandActive = false; + break; + case MOVIE_SAVE_MOVIE: + SaveMovie(); + break; + case MOVIE_LOAD_MOVIE: + LoadMovie(); + break; + case MOVIE_PLAY_MOVIE: + InitPlayback(); + LoadMovie(); + m_bRecording = false; + break; + case MOVIE_END: + m_bRecording = false; + break; + default: + assert(0); + } +} + +void CSceneEdit::PlayBack(void) +{ + m_nCurrentCommand = Movie[m_nCurrentMovieCommand].m_nCommandId; + if (m_nCurrentMovieCommand >= m_nNumMovieCommands) { + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_nCurrentCommand = MOVIE_DO_NOTHING; + m_bRecording = true; + ReInitialise(); + } + return; + } + switch (m_nCurrentCommand) { + case MOVIE_DO_NOTHING: + case MOVIE_MOVE_ACTOR: + case MOVIE_SELECT_ACTOR: + case MOVIE_DELETE_ACTOR: + case MOVIE_MOVE_VEHICLE: + case MOVIE_SELECT_VEHICLE: + case MOVIE_DELETE_VEHICLE: + break; + case MOVIE_NEW_ACTOR: + { + m_nPedmodelId = Movie[m_nCurrentMovieCommand].m_nModelIndex; + m_vecCurrentPosition = Movie[m_nCurrentMovieCommand].m_vecPosition; + if (!CStreaming::HasModelLoaded(m_nPedmodelId)) { + CStreaming::RequestModel(m_nPedmodelId, 0); +#ifdef FIX_BUGS + CStreaming::LoadAllRequestedModels(false); // otherwise gets stuck :( +#endif + break; + } + CPed* pPed = new CCivilianPed(PEDTYPE_SPECIAL, m_nPedmodelId); + pPed->CharCreatedBy = MISSION_CHAR; + CWorld::Add(pPed); + pPed->SetPosition(m_vecCurrentPosition); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i] == nil) { + m_nActor = i; + pActors[i] = pPed; + break; + } + } + m_nNumActors++; + m_nCurrentMovieCommand++; + break; + } + case MOVIE_NEW_VEHICLE: + { + m_nVehiclemodelId = Movie[m_nCurrentMovieCommand].m_nModelIndex; + m_vecCurrentPosition = Movie[m_nCurrentMovieCommand].m_vecPosition; + if (!CStreaming::HasModelLoaded(m_nVehiclemodelId)) { + CStreaming::RequestModel(m_nVehiclemodelId, 0); +#ifdef FIX_BUGS + CStreaming::LoadAllRequestedModels(false); // otherwise gets stuck :( +#endif + break; + } + CVehicle* pVehicle = new CAutomobile(m_nVehiclemodelId, MISSION_VEHICLE); + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->SetPosition(m_vecCurrentPosition); + pVehicle->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(pVehicle); + for (int i = 0; i < NUM_VEHICLES_IN_MOVIE; i++) { + if (pVehicles[i] == nil) { + m_nVehicle = i; + pVehicles[i] = pVehicle; + break; + } + } + m_nNumVehicles++; + m_nCurrentMovieCommand++; + break; + } + case MOVIE_GIVE_WEAPON: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + m_nWeaponType = Movie[m_nCurrentMovieCommand].m_nModelIndex; + pActors[m_nActor]->GiveWeapon((eWeaponType)m_nWeaponType, 1000); + pActors[m_nActor]->AddWeaponModel(CWeaponInfo::GetWeaponInfo(pActors[m_nActor]->GetWeapon()->m_eWeaponType)->m_nModelId); + pActors[m_nActor]->SetCurrentWeapon(m_nWeaponType); + m_nCurrentMovieCommand++; + break; + case MOVIE_GOTO: + case MOVIE_GOTO_WAIT: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + m_vecGotoPosition = Movie[m_nCurrentMovieCommand].m_vecPosition; + if (pActors[m_nActor]->bInVehicle) { + if (pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission != MISSION_GOTOCOORDS && + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission != MISSION_GOTOCOORDS_STRAIGHT) { + if ((pActors[m_nActor]->m_pMyVehicle->GetPosition() - m_vecGotoPosition).Magnitude() < 5.0f) { + if (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pActors[m_nActor]->m_pMyVehicle, m_vecGotoPosition, false)) + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_STRAIGHT; + else + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS; + pActors[m_nActor]->m_pMyVehicle->SetStatus(STATUS_PHYSICS); + pActors[m_nActor]->m_pMyVehicle->bEngineOn = true; + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCruiseSpeed = Max(16, pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCruiseSpeed); + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + if (m_nCurrentCommand != MOVIE_GOTO_WAIT) + ++m_nCurrentMovieCommand; + } + else + ++m_nCurrentMovieCommand; + } + } + else { + if (pActors[m_nActor]->m_objective != OBJECTIVE_GOTO_AREA_ON_FOOT) { + pActors[m_nActor]->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, m_vecGotoPosition); + ++m_nCurrentMovieCommand; + } + } + break; + case MOVIE_GET_IN_CAR: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + if (!pActors[m_nActor]->bInVehicle){ + m_nVehicle = Movie[m_nCurrentMovieCommand].m_nVehicleId; + pActors[m_nActor]->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicles[m_nVehicle]); + } + else + ++m_nCurrentMovieCommand; + break; + case MOVIE_GET_OUT_CAR: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + if (pActors[m_nActor]->bInVehicle) + pActors[m_nActor]->SetObjective(OBJECTIVE_LEAVE_CAR); + else + ++m_nCurrentMovieCommand; + break; + case MOVIE_KILL: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + m_nActor2 = Movie[m_nCurrentMovieCommand].m_nActor2Id; + pActors[m_nActor]->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, pActors[m_nActor2]); + if (pActors[m_nActor2]->GetPedState() == PED_DEAD) + ++m_nCurrentMovieCommand; + break; + case MOVIE_FLEE: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + m_nActor2 = Movie[m_nCurrentMovieCommand].m_nActor2Id; + pActors[m_nActor]->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pActors[m_nActor2]); + ++m_nCurrentMovieCommand; + break; + case MOVIE_WAIT: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + pActors[m_nActor]->SetObjective(OBJECTIVE_WAIT_ON_FOOT); + ++m_nCurrentMovieCommand; + break; + case MOVIE_POSITION_CAMERA: + TheCamera.Cams[TheCamera.ActiveCam].Source = Movie[m_nCurrentMovieCommand].m_vecPosition; + m_vecCamHeading = Movie[m_nCurrentMovieCommand].m_vecCamera; + TheCamera.Cams[TheCamera.ActiveCam].Front = m_vecCamHeading; + ++m_nCurrentMovieCommand; + break; + case MOVIE_SET_CAMERA_TARGET: + m_bCameraFollowActor = true; + TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity = pActors[Movie[m_nNumMovieCommands].m_nActorId]; + TheCamera.pTargetEntity = pActors[Movie[m_nNumMovieCommands].m_nActorId]; + TheCamera.m_bLookingAtPlayer = false; + ++m_nCurrentMovieCommand; + break; + case MOVIE_SELECT_CAMERA_MODE: + m_bCommandActive = false; // this is wrong + break; + } +} + +void CSceneEdit::ClearForNewCommand(void) +{ + m_nActor = -1; + m_nActor2 = -1; + m_nVehicle = -1; + m_bActorSelected = false; + m_bActor2Selected = false; + m_bVehicleSelected = false; + m_bDrawGotoArrow = false; +} +void CSceneEdit::SelectActor(void) +{ + m_bActorSelected = false; + if (m_nActor != -1) { + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) { + CPed* pPed; + do { + if (--m_nActor < 0) + m_nActor = NUM_ACTORS_IN_MOVIE - 1; + pPed = pActors[m_nActor]; + } while (pPed == nil); + TheCamera.Cams[TheCamera.ActiveCam].Source = pPed->GetPosition() - m_vecCamHeading; + } + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()) { + CPed* pPed; + do { + if (++m_nActor == NUM_ACTORS_IN_MOVIE) + m_nActor = 0; + pPed = pActors[m_nActor]; + } while (pPed == nil); + TheCamera.Cams[TheCamera.ActiveCam].Source = pPed->GetPosition() - m_vecCamHeading; + } + m_vecCurrentPosition = pActors[m_nActor]->GetPosition(); + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bActorSelected = true; + m_bCommandActive = false; + } + else if (CPad::GetPad(1)->GetCircleJustDown()) { + m_nActor = -1; + } + } + else if (m_nNumActors != 0) { + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i] != nil) { + m_nActor = i; + break; + } + } + TheCamera.Cams[TheCamera.ActiveCam].Source = pActors[m_nActor]->GetPosition() - m_vecCamHeading; + if (m_nNumActors == 1) { + m_bActorSelected = true; + m_bCommandActive = false; + } + } + else { + m_bCommandActive = false; + } +} + +void CSceneEdit::SelectActor2(void) +{ + m_bActor2Selected = false; + if (m_nNumActors <= 1) { + m_bCommandActive = false; + return; + } + if (m_nActor2 != -1) { + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) { + CPed* pPed; + do { + if (--m_nActor2 < 0) + m_nActor2 = NUM_ACTORS_IN_MOVIE - 1; + pPed = pActors[m_nActor2]; + } while (pPed == nil || pPed == pActors[m_nActor]); + TheCamera.Cams[TheCamera.ActiveCam].Source = pPed->GetPosition() - m_vecCamHeading; + } + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()) { + CPed* pPed; + do { + if (++m_nActor2 == NUM_ACTORS_IN_MOVIE) + m_nActor2 = 0; + pPed = pActors[m_nActor2]; + } while (pPed == nil || pPed == pActors[m_nActor]); + TheCamera.Cams[TheCamera.ActiveCam].Source = pPed->GetPosition() - m_vecCamHeading; + } + m_vecCurrentPosition = pActors[m_nActor2]->GetPosition(); + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bActor2Selected = true; + m_bCommandActive = false; + } + else if (CPad::GetPad(1)->GetCircleJustDown()) { + m_nActor2 = -1; + } + } + else { + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i] != nil && pActors[m_nActor] != pActors[i] ) { + m_nActor2 = i; + break; + } + } + TheCamera.Cams[TheCamera.ActiveCam].Source = pActors[m_nActor2]->GetPosition() - m_vecCamHeading; + } +} + +void CSceneEdit::SelectVehicle(void) +{ + m_bVehicleSelected = false; + if (m_nVehicle != -1) { + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) { + CVehicle* pVehicle; + do { + if (--m_nVehicle < 0) + m_nVehicle = NUM_VEHICLES_IN_MOVIE - 1; + pVehicle = pVehicles[m_nVehicle]; + } while (pVehicle == nil); + } + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()) { + CVehicle* pVehicle; + do { + if (++m_nVehicle == NUM_VEHICLES_IN_MOVIE) + m_nVehicle = 0; + pVehicle = pVehicles[m_nVehicle]; + } while (pVehicle == nil); + } + m_vecCurrentPosition = pVehicles[m_nVehicle]->GetPosition(); + TheCamera.Cams[TheCamera.ActiveCam].Source = pVehicles[m_nVehicle]->GetPosition() - m_vecCamHeading; + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bVehicleSelected = true; + m_bCommandActive = false; + } + else if (CPad::GetPad(1)->GetCircleJustDown()) { + m_nVehicle = -1; + } + } + else if (m_nNumVehicles != 0) { + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pVehicles[i] != nil) { + m_nVehicle = i; + break; + } + } + } +} + +bool CSceneEdit::SelectWeapon(void) +{ + if (m_nWeaponType == WEAPONTYPE_UNARMED) { + m_nWeaponType = WEAPONTYPE_COLT45; + return false; + } + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) { + if (++m_nWeaponType >= WEAPONTYPE_DETONATOR) + m_nWeaponType = WEAPONTYPE_BASEBALLBAT; + pActors[m_nActor]->ClearWeapons(); + pActors[m_nActor]->GiveWeapon((eWeaponType)m_nWeaponType, 1000); + pActors[m_nActor]->AddWeaponModel(CWeaponInfo::GetWeaponInfo(pActors[m_nActor]->GetWeapon()->m_eWeaponType)->m_nModelId); + pActors[m_nActor]->SetCurrentWeapon(m_nWeaponType); + } + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()){ + if (--m_nWeaponType <= WEAPONTYPE_UNARMED) + m_nWeaponType = WEAPONTYPE_GRENADE; + pActors[m_nActor]->ClearWeapons(); + pActors[m_nActor]->GiveWeapon((eWeaponType)m_nWeaponType, 1000); + pActors[m_nActor]->AddWeaponModel(CWeaponInfo::GetWeaponInfo(pActors[m_nActor]->GetWeapon()->m_eWeaponType)->m_nModelId); + pActors[m_nActor]->SetCurrentWeapon(m_nWeaponType); + } + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bCommandActive = false; + return true; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pActors[m_nActor]->ClearWeapons(); + m_nWeaponType = WEAPONTYPE_UNARMED; + m_bCommandActive = false; + return false; + } + return false; +} +#endif diff --git a/src/control/SceneEdit.h b/src/control/SceneEdit.h new file mode 100644 index 0000000..7c8fb98 --- /dev/null +++ b/src/control/SceneEdit.h @@ -0,0 +1,96 @@ +#pragma once +#ifdef GTA_SCENE_EDIT +class CPed; +class CVehicle; + +struct CMovieCommand +{ + int32 m_nCommandId; + CVector m_vecPosition; + CVector m_vecCamera; + int16 m_nActorId; + int16 m_nActor2Id; + int16 m_nVehicleId; + int16 m_nModelIndex; +}; + +class CSceneEdit +{ +public: + enum { + MOVIE_DO_NOTHING = 0, + MOVIE_NEW_ACTOR, + MOVIE_MOVE_ACTOR, + MOVIE_SELECT_ACTOR, + MOVIE_DELETE_ACTOR, + MOVIE_NEW_VEHICLE, + MOVIE_MOVE_VEHICLE, + MOVIE_SELECT_VEHICLE, + MOVIE_DELETE_VEHICLE, + MOVIE_GIVE_WEAPON, + MOVIE_GOTO, + MOVIE_GOTO_WAIT, + MOVIE_GET_IN_CAR, + MOVIE_GET_OUT_CAR, + MOVIE_KILL, + MOVIE_FLEE, + MOVIE_WAIT, + MOVIE_POSITION_CAMERA, + MOVIE_SET_CAMERA_TARGET, + MOVIE_SELECT_CAMERA_MODE, + MOVIE_SAVE_MOVIE, + MOVIE_LOAD_MOVIE, + MOVIE_PLAY_MOVIE, + MOVIE_END, + MOVIE_TOTAL_COMMANDS + }; + enum { + NUM_ACTORS_IN_MOVIE = 5, + NUM_VEHICLES_IN_MOVIE = 5, + NUM_COMMANDS_IN_MOVIE = 20 + }; + static int32 m_bCameraFollowActor; + static CVector m_vecCurrentPosition; + static CVector m_vecCamHeading; + static CVector m_vecGotoPosition; + static int32 m_nVehicle; + static int32 m_nVehicle2; + static int32 m_nActor; + static int32 m_nActor2; + static int32 m_nVehiclemodelId; + static int32 m_nPedmodelId; + static int16 m_nCurrentMovieCommand; + static int16 m_nCurrentCommand; + static int16 m_nCurrentVehicle; + static int16 m_nCurrentActor; + static bool m_bEditOn; + static bool m_bRecording; + static bool m_bCommandActive; + static bool m_bActorSelected; + static bool m_bActor2Selected; + static bool m_bVehicleSelected; + static int16 m_nNumActors; + static int16 m_nNumVehicles; + static int16 m_nNumMovieCommands; + static int16 m_nWeaponType; + static CPed* pActors[NUM_ACTORS_IN_MOVIE]; + static CVehicle* pVehicles[NUM_VEHICLES_IN_MOVIE]; + static bool m_bDrawGotoArrow; + static CMovieCommand Movie[NUM_COMMANDS_IN_MOVIE]; + + static void LoadMovie(void); + static void SaveMovie(void); + static void Initialise(void); + static void InitPlayback(void); + static void ReInitialise(void); + static void Update(void); + static void Draw(void); + static void ProcessCommand(void); + static void PlayBack(void); + static void ClearForNewCommand(void); + static void SelectActor(void); + static void SelectActor2(void); + static void SelectVehicle(void); + static bool SelectWeapon(void); +}; +#endif diff --git a/src/control/Script.cpp b/src/control/Script.cpp new file mode 100644 index 0000000..f1c8b34 --- /dev/null +++ b/src/control/Script.cpp @@ -0,0 +1,3014 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "AnimBlendAssociation.h" +#include "AudioManager.h" +#include "Boat.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "CivilianPed.h" +#include "Clock.h" +#include "CopPed.h" +#include "Debug.h" +#include "DMAudio.h" +#include "EmergencyPed.h" +#include "FileMgr.h" +#include "Frontend.h" +#include "General.h" +#ifdef MISSION_REPLAY +#include "GenericGameStorage.h" +#endif +#include "HandlingMgr.h" +#include "Heli.h" +#include "Hud.h" +#include "Lines.h" +#include "Messages.h" +#include "Pad.h" +#include "Pickups.h" +#include "Pools.h" +#include "Population.h" +#include "Remote.h" +#include "Replay.h" +#include "Stats.h" +#include "Streaming.h" +#include "User.h" +#include "Wanted.h" +#include "Weather.h" +#include "Zones.h" + +uint8 CTheScripts::ScriptSpace[SIZE_SCRIPT_SPACE]; +CRunningScript CTheScripts::ScriptsArray[MAX_NUM_SCRIPTS]; +int32 CTheScripts::BaseBriefIdForContact[MAX_NUM_CONTACTS]; +int32 CTheScripts::OnAMissionForContactFlag[MAX_NUM_CONTACTS]; +intro_text_line CTheScripts::IntroTextLines[MAX_NUM_INTRO_TEXT_LINES]; +intro_script_rectangle CTheScripts::IntroRectangles[MAX_NUM_INTRO_RECTANGLES]; +CSprite2d CTheScripts::ScriptSprites[MAX_NUM_SCRIPT_SRPITES]; +script_sphere_struct CTheScripts::ScriptSphereArray[MAX_NUM_SCRIPT_SPHERES]; +tCollectiveData CTheScripts::CollectiveArray[MAX_NUM_COLLECTIVES]; +tUsedObject CTheScripts::UsedObjectArray[MAX_NUM_USED_OBJECTS]; +int32 CTheScripts::MultiScriptArray[MAX_NUM_MISSION_SCRIPTS]; +tBuildingSwap CTheScripts::BuildingSwapArray[MAX_NUM_BUILDING_SWAPS]; +CEntity* CTheScripts::InvisibilitySettingArray[MAX_NUM_INVISIBILITY_SETTINGS]; +CStoredLine CTheScripts::aStoredLines[MAX_NUM_STORED_LINES]; +bool CTheScripts::DbgFlag; +uint32 CTheScripts::OnAMissionFlag; +int32 CTheScripts::StoreVehicleIndex; +bool CTheScripts::StoreVehicleWasRandom; +CRunningScript *CTheScripts::pIdleScripts; +CRunningScript *CTheScripts::pActiveScripts; +int32 CTheScripts::NextFreeCollectiveIndex; +int32 CTheScripts::LastRandomPedId; +uint16 CTheScripts::NumberOfUsedObjects; +bool CTheScripts::bAlreadyRunningAMissionScript; +bool CTheScripts::bUsingAMultiScriptFile; +uint16 CTheScripts::NumberOfMissionScripts; +uint32 CTheScripts::LargestMissionScriptSize; +uint32 CTheScripts::MainScriptSize; +uint8 CTheScripts::FailCurrentMission; +uint8 CTheScripts::CountdownToMakePlayerUnsafe; +uint8 CTheScripts::DelayMakingPlayerUnsafeThisTime; +uint16 CTheScripts::NumScriptDebugLines; +uint16 CTheScripts::NumberOfIntroRectanglesThisFrame; +uint16 CTheScripts::NumberOfIntroTextLinesThisFrame; +uint8 CTheScripts::UseTextCommands; +CMissionCleanup CTheScripts::MissionCleanUp; +CUpsideDownCarCheck CTheScripts::UpsideDownCars; +CStuckCarCheck CTheScripts::StuckCars; +uint16 CTheScripts::CommandsExecuted; +uint16 CTheScripts::ScriptsUpdated; +int32 ScriptParams[32]; + +#ifdef MISSION_REPLAY + +static const char* nonMissionScripts[] = { + "copcar", + "ambulan", + "taxi", + "firetru", + "rampage", + "t4x4_1", + "t4x4_2", + "t4x4_3", + "rc1", + "rc2", + "rc3", + "rc4", + "hj", + "usj", + "mayhem" +}; + +int AllowMissionReplay; +uint32 NextMissionDelay; +uint32 MissionStartTime; +uint32 WaitForMissionActivate; +uint32 WaitForSave; +float oldTargetX; +float oldTargetY; +int missionRetryScriptIndex; +bool doingMissionRetry; + +#endif + +const uint32 CRunningScript::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 136; +#else + sizeof(CRunningScript); +#endif + +CMissionCleanup::CMissionCleanup() +{ + Init(); +} + +void CMissionCleanup::Init() +{ + m_nCount = 0; + for (int i = 0; i < MAX_CLEANUP; i++){ + m_sEntities[i].type = CLEANUP_UNUSED; + m_sEntities[i].id = 0; + } +} + +cleanup_entity_struct* CMissionCleanup::FindFree() +{ + for (int i = 0; i < MAX_CLEANUP; i++){ + if (m_sEntities[i].type == CLEANUP_UNUSED) + return &m_sEntities[i]; + } + script_assert(0); + return nil; +} + +void CMissionCleanup::AddEntityToList(int32 id, uint8 type) +{ + cleanup_entity_struct* pNew = FindFree(); + if (!pNew) + return; + pNew->id = id; + pNew->type = type; + m_nCount++; +} + +void CMissionCleanup::RemoveEntityFromList(int32 id, uint8 type) +{ + for (int i = 0; i < MAX_CLEANUP; i++){ + if (m_sEntities[i].type == type && m_sEntities[i].id == id){ + m_sEntities[i].id = 0; + m_sEntities[i].type = CLEANUP_UNUSED; + m_nCount--; + } + } +} + +void CMissionCleanup::Process() +{ + CPopulation::m_AllRandomPedsThisType = -1; + CPopulation::PedDensityMultiplier = 1.0f; + CCarCtrl::CarDensityMultiplier = 1.0f; + FindPlayerPed()->m_pWanted->m_fCrimeSensitivity = 1.0f; + TheCamera.Restore(); + TheCamera.SetWideScreenOff(); + DMAudio.ClearMissionAudio(); + CWeather::ReleaseWeather(); + for (int i = 0; i < NUM_OF_SPECIAL_CHARS; i++) + CStreaming::SetMissionDoesntRequireSpecialChar(i); + for (int i = 0; i < NUM_OF_CUTSCENE_OBJECTS; i++) + CStreaming::SetMissionDoesntRequireModel(MI_CUTOBJ01 + i); + CStreaming::ms_disableStreaming = false; + CHud::m_ItemToFlash = -1; + CHud::SetHelpMessage(nil, false); + CUserDisplay::OnscnTimer.m_bDisabled = false; + CWorld::Players[0].m_pPed->m_pWanted->m_bIgnoredByCops = false; + CWorld::Players[0].m_pPed->m_pWanted->m_bIgnoredByEveryone = false; + CWorld::Players[0].MakePlayerSafe(false); + CTheScripts::StoreVehicleIndex = -1; + CTheScripts::StoreVehicleWasRandom = true; + CTheScripts::UpsideDownCars.Init(); + CTheScripts::StuckCars.Init(); + for (int i = 0; i < MAX_CLEANUP; i++){ + if (m_sEntities[i].type == CLEANUP_UNUSED) + continue; + switch (m_sEntities[i].type) { + case CLEANUP_CAR: + { + CVehicle* v = CPools::GetVehiclePool()->GetAt(m_sEntities[i].id); + if (v) + CTheScripts::CleanUpThisVehicle(v); + break; + } + case CLEANUP_CHAR: + { + CPed* p = CPools::GetPedPool()->GetAt(m_sEntities[i].id); + if (p) + CTheScripts::CleanUpThisPed(p); + break; + } + case CLEANUP_OBJECT: + { + CObject* o = CPools::GetObjectPool()->GetAt(m_sEntities[i].id); + if (o) + CTheScripts::CleanUpThisObject(o); + break; + } + default: + break; + } + m_sEntities[i].id = 0; + m_sEntities[i].type = CLEANUP_UNUSED; + m_nCount--; + } +} + +/* NB: CUpsideDownCarCheck is not used by actual script at all + * It has a weird usage: AreAnyCarsUpsideDown would fail any mission + * just like death or arrest. */ + +void CUpsideDownCarCheck::Init() +{ + for (int i = 0; i < MAX_UPSIDEDOWN_CAR_CHECKS; i++){ + m_sCars[i].m_nVehicleIndex = -1; + m_sCars[i].m_nUpsideDownTimer = 0; + } +} + +bool CUpsideDownCarCheck::IsCarUpsideDown(int32 id) +{ + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(id); + return IsCarUpsideDown(pVehicle); +} + +bool CUpsideDownCarCheck::IsCarUpsideDown(CVehicle* pVehicle) +{ + assert(pVehicle); + return pVehicle->GetUp().z <= UPSIDEDOWN_UP_THRESHOLD && + pVehicle->GetMoveSpeed().Magnitude() < UPSIDEDOWN_MOVE_SPEED_THRESHOLD && + pVehicle->GetTurnSpeed().Magnitude() < UPSIDEDOWN_TURN_SPEED_THRESHOLD; +} + +void CUpsideDownCarCheck::UpdateTimers() +{ + uint32 timeStep = CTimer::GetTimeStepInMilliseconds(); + for (int i = 0; i < MAX_UPSIDEDOWN_CAR_CHECKS; i++){ + CVehicle* v = CPools::GetVehiclePool()->GetAt(m_sCars[i].m_nVehicleIndex); + if (v){ + if (IsCarUpsideDown(m_sCars[i].m_nVehicleIndex)) + m_sCars[i].m_nUpsideDownTimer += timeStep; + else + m_sCars[i].m_nUpsideDownTimer = 0; + }else{ + m_sCars[i].m_nVehicleIndex = -1; + m_sCars[i].m_nUpsideDownTimer = 0; + } + } +} + +bool CUpsideDownCarCheck::AreAnyCarsUpsideDown() +{ + for (int i = 0; i < MAX_UPSIDEDOWN_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex >= 0 && m_sCars[i].m_nUpsideDownTimer > UPSIDEDOWN_TIMER_THRESHOLD) + return true; + } + return false; +} + +void CUpsideDownCarCheck::AddCarToCheck(int32 id) +{ + uint16 index = 0; + while (index < MAX_UPSIDEDOWN_CAR_CHECKS && m_sCars[index].m_nVehicleIndex >= 0) + index++; +#ifdef FIX_BUGS + if (index >= MAX_UPSIDEDOWN_CAR_CHECKS) + return; +#endif + m_sCars[index].m_nVehicleIndex = id; + m_sCars[index].m_nUpsideDownTimer = 0; +} + +void CUpsideDownCarCheck::RemoveCarFromCheck(int32 id) +{ + for (int i = 0; i < MAX_UPSIDEDOWN_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex == id){ + m_sCars[i].m_nVehicleIndex = -1; + m_sCars[i].m_nUpsideDownTimer = 0; + } + } +} + +bool CUpsideDownCarCheck::HasCarBeenUpsideDownForAWhile(int32 id) +{ + for (int i = 0; i < MAX_UPSIDEDOWN_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex == id) + return m_sCars[i].m_nUpsideDownTimer > UPSIDEDOWN_TIMER_THRESHOLD; + } + return false; +} + +void stuck_car_data::Reset() +{ + m_nVehicleIndex = -1; + m_vecPos = CVector(-5000.0f, -5000.0f, -5000.0f); + m_nLastCheck = -1; + m_fRadius = 0.0f; + m_nStuckTime = 0; + m_bStuck = false; +} + +void CStuckCarCheck::Init() +{ + for (int i = 0; i < MAX_STUCK_CAR_CHECKS; i++) { + m_sCars[i].Reset(); + } +} + +void CStuckCarCheck::Process() +{ + uint32 timer = CTimer::GetTimeInMilliseconds(); + for (int i = 0; i < MAX_STUCK_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex < 0) + continue; + if (timer <= m_sCars[i].m_nStuckTime + m_sCars[i].m_nLastCheck) + continue; + CVehicle* pv = CPools::GetVehiclePool()->GetAt(m_sCars[i].m_nVehicleIndex); + if (!pv){ + m_sCars[i].Reset(); + continue; + } + float distance = (pv->GetPosition() - m_sCars[i].m_vecPos).Magnitude(); + m_sCars[i].m_bStuck = distance < m_sCars[i].m_fRadius; + m_sCars[i].m_vecPos = pv->GetPosition(); + m_sCars[i].m_nLastCheck = timer; + } +} + +void CStuckCarCheck::AddCarToCheck(int32 id, float radius, uint32 time) +{ + CVehicle* pv = CPools::GetVehiclePool()->GetAt(id); + if (!pv) + return; + int index = 0; + while (index < MAX_STUCK_CAR_CHECKS && m_sCars[index].m_nVehicleIndex >= 0) + index++; +#ifdef FIX_BUGS + if (index >= MAX_STUCK_CAR_CHECKS) + return; +#endif + m_sCars[index].m_nVehicleIndex = id; + m_sCars[index].m_vecPos = pv->GetPosition(); + m_sCars[index].m_nLastCheck = CTimer::GetTimeInMilliseconds(); + m_sCars[index].m_fRadius = radius; + m_sCars[index].m_nStuckTime = time; + m_sCars[index].m_bStuck = false; +} + +void CStuckCarCheck::RemoveCarFromCheck(int32 id) +{ + for (int i = 0; i < MAX_STUCK_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex == id){ + m_sCars[i].Reset(); + } + } +} + +bool CStuckCarCheck::HasCarBeenStuckForAWhile(int32 id) +{ + for (int i = 0; i < MAX_STUCK_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex == id) + return m_sCars[i].m_bStuck; + } + return false; +} + +void CRunningScript::CollectParameters(uint32* pIp, int16 total) +{ + for (int16 i = 0; i < total; i++){ + float tmp; + uint16 varIndex; + switch (CTheScripts::Read1ByteFromScript(pIp)) + { + case ARGUMENT_INT32: + ScriptParams[i] = CTheScripts::Read4BytesFromScript(pIp); + break; + case ARGUMENT_GLOBALVAR: + varIndex = CTheScripts::Read2BytesFromScript(pIp); + script_assert(varIndex >= 8 && varIndex < CTheScripts::GetSizeOfVariableSpace()); + ScriptParams[i] = *((int32*)&CTheScripts::ScriptSpace[varIndex]); + break; + case ARGUMENT_LOCALVAR: + varIndex = CTheScripts::Read2BytesFromScript(pIp); + script_assert(varIndex >= 0 && varIndex < ARRAY_SIZE(m_anLocalVariables)); + ScriptParams[i] = m_anLocalVariables[varIndex]; + break; + case ARGUMENT_INT8: + ScriptParams[i] = CTheScripts::Read1ByteFromScript(pIp); + break; + case ARGUMENT_INT16: + ScriptParams[i] = CTheScripts::Read2BytesFromScript(pIp); + break; + case ARGUMENT_FLOAT: + tmp = CTheScripts::ReadFloatFromScript(pIp); + ScriptParams[i] = *(int32*)&tmp; + break; + default: + script_assert(0); + break; + } + } +} + +int32 CRunningScript::CollectNextParameterWithoutIncreasingPC(uint32 ip) +{ + uint32* pIp = &ip; + float tmp; + switch (CTheScripts::Read1ByteFromScript(pIp)) + { + case ARGUMENT_INT32: + return CTheScripts::Read4BytesFromScript(pIp); + case ARGUMENT_GLOBALVAR: + return *((int32*)&CTheScripts::ScriptSpace[(uint16)CTheScripts::Read2BytesFromScript(pIp)]); + case ARGUMENT_LOCALVAR: + return m_anLocalVariables[CTheScripts::Read2BytesFromScript(pIp)]; + case ARGUMENT_INT8: + return CTheScripts::Read1ByteFromScript(pIp); + case ARGUMENT_INT16: + return CTheScripts::Read2BytesFromScript(pIp); + case ARGUMENT_FLOAT: + tmp = CTheScripts::ReadFloatFromScript(pIp); + return *(int32*)&tmp; + default: + script_assert(0); + } + return -1; +} + +void CRunningScript::StoreParameters(uint32* pIp, int16 number) +{ + for (int16 i = 0; i < number; i++){ + switch (CTheScripts::Read1ByteFromScript(pIp)) { + case ARGUMENT_GLOBALVAR: + *(int32*)&CTheScripts::ScriptSpace[(uint16)CTheScripts::Read2BytesFromScript(pIp)] = ScriptParams[i]; + break; + case ARGUMENT_LOCALVAR: + m_anLocalVariables[CTheScripts::Read2BytesFromScript(pIp)] = ScriptParams[i]; + break; + default: + script_assert(0); + } + } +} + +int32 *CRunningScript::GetPointerToScriptVariable(uint32* pIp, int16 type) +{ + switch (CTheScripts::Read1ByteFromScript(pIp)) + { + case ARGUMENT_GLOBALVAR: + script_assert(type == VAR_GLOBAL); + return (int32*)&CTheScripts::ScriptSpace[(uint16)CTheScripts::Read2BytesFromScript(pIp)]; + case ARGUMENT_LOCALVAR: + script_assert(type == VAR_LOCAL); + return &m_anLocalVariables[CTheScripts::Read2BytesFromScript(pIp)]; + default: + script_assert(0); + } + return nil; +} + +void CRunningScript::Init() +{ + strcpy(m_abScriptName, "noname"); + next = prev = nil; + SetIP(0); + for (int i = 0; i < MAX_STACK_DEPTH; i++) + m_anStack[i] = 0; + m_nStackPointer = 0; + m_nWakeTime = 0; + m_bCondResult = false; + m_bIsMissionScript = false; + m_bSkipWakeTime = false; + for (int i = 0; i < NUM_LOCAL_VARS + NUM_TIMERS; i++) + m_anLocalVariables[i] = 0; + m_nAndOrState = 0; + m_bNotFlag = false; + m_bDeatharrestEnabled = true; + m_bDeatharrestExecuted = false; + m_bMissionFlag = false; +} + +#ifdef USE_DEBUG_SCRIPT_LOADER +int CTheScripts::ScriptToLoad = 0; + +int CTheScripts::OpenScript() +{ + CFileMgr::ChangeDir("\\"); + switch (ScriptToLoad) { + case 0: return CFileMgr::OpenFile("data\\main.scm", "rb"); + case 1: return CFileMgr::OpenFile("data\\main_freeroam.scm", "rb"); + case 2: return CFileMgr::OpenFile("data\\main_d.scm", "rb"); + } + return CFileMgr::OpenFile("data\\main.scm", "rb"); +} +#endif + +void CTheScripts::Init() +{ + for (int i = 0; i < SIZE_SCRIPT_SPACE; i++) + ScriptSpace[i] = 0; + pActiveScripts = pIdleScripts = nil; + for (int i = 0; i < MAX_NUM_SCRIPTS; i++){ + ScriptsArray[i].Init(); + ScriptsArray[i].AddScriptToList(&pIdleScripts); + } + MissionCleanUp.Init(); + UpsideDownCars.Init(); + StuckCars.Init(); +#ifdef USE_DEBUG_SCRIPT_LOADER + // glfwGetKey doesn't work because of CGame::Initialise is blocking + CPad::UpdatePads(); + if(CPad::GetPad(0)->GetChar('G')) ScriptToLoad = 0; + if(CPad::GetPad(0)->GetChar('R')) ScriptToLoad = 1; + if(CPad::GetPad(0)->GetChar('D')) ScriptToLoad = 2; + + int mainf = OpenScript(); +#else + CFileMgr::SetDir("data"); + int mainf = CFileMgr::OpenFile("main.scm", "rb"); +#endif + CFileMgr::Read(mainf, (char*)ScriptSpace, SIZE_MAIN_SCRIPT); + CFileMgr::CloseFile(mainf); + CFileMgr::SetDir(""); + StoreVehicleIndex = -1; + StoreVehicleWasRandom = true; + OnAMissionFlag = 0; + for (int i = 0; i < MAX_NUM_CONTACTS; i++){ + BaseBriefIdForContact[i] = 0; + OnAMissionForContactFlag[i] = 0; + } + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++){ + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + NextFreeCollectiveIndex = 0; + LastRandomPedId = -1; + for (int i = 0; i < MAX_NUM_USED_OBJECTS; i++){ + memset(&UsedObjectArray[i].name, 0, sizeof(UsedObjectArray[i].name)); + UsedObjectArray[i].index = 0; + } + NumberOfUsedObjects = 0; + ReadObjectNamesFromScript(); + UpdateObjectIndices(); + bAlreadyRunningAMissionScript = false; + bUsingAMultiScriptFile = true; + for (int i = 0; i < MAX_NUM_MISSION_SCRIPTS; i++) + MultiScriptArray[i] = 0; + NumberOfMissionScripts = 0; + LargestMissionScriptSize = 0; + MainScriptSize = 0; + ReadMultiScriptFileOffsetsFromScript(); + FailCurrentMission = 0; + CountdownToMakePlayerUnsafe = 0; + DbgFlag = false; + DelayMakingPlayerUnsafeThisTime = 1; + NumScriptDebugLines = 0; + for (int i = 0; i < MAX_NUM_SCRIPT_SPHERES; i++){ + ScriptSphereArray[i].m_bInUse = false; + ScriptSphereArray[i].m_Index = 1; + ScriptSphereArray[i].m_Id = 0; + ScriptSphereArray[i].m_vecCenter = CVector(0.0f, 0.0f, 0.0f); + ScriptSphereArray[i].m_fRadius = 0.0f; + } + for (int i = 0; i < MAX_NUM_INTRO_TEXT_LINES; i++){ + IntroTextLines[i].Reset(); + } + NumberOfIntroTextLinesThisFrame = 0; + UseTextCommands = 0; + for (int i = 0; i < MAX_NUM_INTRO_RECTANGLES; i++){ + IntroRectangles[i].m_bIsUsed = false; + IntroRectangles[i].m_bBeforeFade = false; + IntroRectangles[i].m_nTextureId = -1; + IntroRectangles[i].m_sRect = CRect(0.0f, 0.0f, 0.0f, 0.0f); + IntroRectangles[i].m_sColor = CRGBA(255, 255, 255, 255); + } + NumberOfIntroRectanglesThisFrame = 0; + for (int i = 0; i < MAX_NUM_BUILDING_SWAPS; i++){ + BuildingSwapArray[i].m_pBuilding = nil; + BuildingSwapArray[i].m_nNewModel = -1; + BuildingSwapArray[i].m_nOldModel = -1; + } + for (int i = 0; i < MAX_NUM_INVISIBILITY_SETTINGS; i++) + InvisibilitySettingArray[i] = nil; + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogAfterScriptInitializing(); +#endif +} + +void CRunningScript::RemoveScriptFromList(CRunningScript** ppScript) +{ + if (prev) + prev->next = next; + else + *ppScript = next; + if (next) + next->prev = prev; +} + +void CRunningScript::AddScriptToList(CRunningScript** ppScript) +{ + next = *ppScript; + prev = nil; + if (*ppScript) + (*ppScript)->prev = this; + *ppScript = this; +} + +CRunningScript* CTheScripts::StartNewScript(uint32 ip) +{ + CRunningScript* pNew = pIdleScripts; + script_assert(pNew); + pNew->RemoveScriptFromList(&pIdleScripts); + pNew->Init(); + pNew->SetIP(ip); + pNew->AddScriptToList(&pActiveScripts); + return pNew; +} + +void CTheScripts::Process() +{ + if (CReplay::IsPlayingBack()) + return; + CommandsExecuted = 0; + ScriptsUpdated = 0; + float timeStep = CTimer::GetTimeStepInMilliseconds(); + UpsideDownCars.UpdateTimers(); + StuckCars.Process(); + DrawScriptSpheres(); + if (FailCurrentMission) + --FailCurrentMission; + if (CountdownToMakePlayerUnsafe){ + if (--CountdownToMakePlayerUnsafe == 0) + CWorld::Players[0].MakePlayerSafe(false); + } + if (UseTextCommands){ + for (int i = 0; i < MAX_NUM_INTRO_TEXT_LINES; i++) + IntroTextLines[i].Reset(); + NumberOfIntroTextLinesThisFrame = 0; + for (int i = 0; i < MAX_NUM_INTRO_RECTANGLES; i++){ + IntroRectangles[i].m_bIsUsed = false; + IntroRectangles[i].m_bBeforeFade = false; + } + NumberOfIntroRectanglesThisFrame = 0; + if (UseTextCommands == 1) + UseTextCommands = 0; + } + +#ifdef MISSION_REPLAY + static uint32 TimeToWaitTill; + switch (AllowMissionReplay) { + case MISSION_RETRY_STAGE_START_PROCESSING: + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_DELAY; + TimeToWaitTill = CTimer::GetTimeInMilliseconds() + (AddExtraDeathDelay() > 1000 ? 4000 : 2500); + break; + case MISSION_RETRY_STAGE_WAIT_FOR_DELAY: + if (TimeToWaitTill < CTimer::GetTimeInMilliseconds()) + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_MENU; + break; + case MISSION_RETRY_STAGE_WAIT_FOR_MENU: + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_USER; + RetryMission(MISSION_RETRY_TYPE_SUGGEST_TO_PLAYER); + break; + case MISSION_RETRY_STAGE_START_RESTARTING: + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART; + TimeToWaitTill = CTimer::GetTimeInMilliseconds() + 500; + break; + case MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART: + if (TimeToWaitTill < CTimer::GetTimeInMilliseconds()) { + AllowMissionReplay = MISSION_RETRY_STAGE_NORMAL; + return; + } + break; + } + if (WaitForMissionActivate) { + if (WaitForMissionActivate > CTimer::GetTimeInMilliseconds()) + return; + WaitForMissionActivate = 0; + WaitForSave = CTimer::GetTimeInMilliseconds() + 3000; + } + if (WaitForSave && WaitForSave > CTimer::GetTimeInMilliseconds()) + WaitForSave = 0; +#endif + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogBeforeScriptProcessing(); +#endif + + CRunningScript* script = pActiveScripts; + while (script != nil){ + CRunningScript* next = script->GetNext(); + ++ScriptsUpdated; + script->UpdateTimers(timeStep); + script->Process(); + script = next; + } + DbgFlag = false; + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogAfterScriptProcessing(); +#endif +} + +CRunningScript* CTheScripts::StartTestScript() +{ + return StartNewScript(0); +} + +bool CTheScripts::IsPlayerOnAMission() +{ + return OnAMissionFlag && *(int32*)&ScriptSpace[OnAMissionFlag] == 1; +} + +void CRunningScript::Process() +{ +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogOnStartProcessing(); +#endif + if (m_bIsMissionScript) + DoDeatharrestCheck(); + if (m_bMissionFlag && CTheScripts::FailCurrentMission == 1 && m_nStackPointer == 1) + SetIP(m_anStack[--m_nStackPointer]); + if (CTimer::GetTimeInMilliseconds() >= m_nWakeTime){ + while (!ProcessOneCommand()) + ; + return; + } + if (!m_bSkipWakeTime) + return; + if (!CPad::GetPad(0)->GetCrossJustDown()) + return; + m_nWakeTime = 0; + for (int i = 0; i < NUMBIGMESSAGES; i++){ + if (CMessages::BIGMessages[i].m_Stack[0].m_pText != nil) + CMessages::BIGMessages[i].m_Stack[0].m_nStartTime = 0; + } + if (CMessages::BriefMessages[0].m_pText != nil) + CMessages::BriefMessages[0].m_nStartTime = 0; +} + +int8 CRunningScript::ProcessOneCommand() +{ + int8 retval = -1; + ++CTheScripts::CommandsExecuted; + int32 command = (uint16)CTheScripts::Read2BytesFromScript(&m_nIp); + m_bNotFlag = (command & 0x8000); + command &= 0x7FFF; +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogBeforeProcessingCommand(command); +#endif + if (command < 100) + retval = ProcessCommands0To99(command); + else if (command < 200) + retval = ProcessCommands100To199(command); + else if (command < 300) + retval = ProcessCommands200To299(command); + else if (command < 400) + retval = ProcessCommands300To399(command); + else if (command < 500) + retval = ProcessCommands400To499(command); + else if (command < 600) + retval = ProcessCommands500To599(command); + else if (command < 700) + retval = ProcessCommands600To699(command); + else if (command < 800) + retval = ProcessCommands700To799(command); + else if (command < 900) + retval = ProcessCommands800To899(command); + else if (command < 1000) + retval = ProcessCommands900To999(command); +#if GTA_VERSION <= GTA3_PS2_160 + else if (command < 1200) + retval = ProcessCommands1000To1099(command); +#else + else if (command < 1100) + retval = ProcessCommands1000To1099(command); + else if (command < 1200) + retval = ProcessCommands1100To1199(command); +#endif +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogAfterProcessingCommand(command); +#elif defined USE_BASIC_SCRIPT_DEBUG_OUTPUT + if (m_bMissionFlag) { + char tmp[128]; + sprintf(tmp, "Comm %d Cmp %d", command, m_bCondResult); + CDebug::DebugAddText(tmp); + } +#endif + return retval; +} + +int8 CRunningScript::ProcessCommands0To99(int32 command) +{ + float *fScriptVar1; + int *nScriptVar1; + switch (command) { + case COMMAND_NOP: + return 0; + case COMMAND_WAIT: + CollectParameters(&m_nIp, 1); + m_nWakeTime = CTimer::GetTimeInMilliseconds() + ScriptParams[0]; + m_bSkipWakeTime = false; + return 1; + case COMMAND_GOTO: + CollectParameters(&m_nIp, 1); + SetIP(ScriptParams[0] >= 0 ? ScriptParams[0] : SIZE_MAIN_SCRIPT - ScriptParams[0]); + /* Known issue: GOTO to 0. It might have been "better" to use > instead of >= */ + /* simply because it never makes sense to jump to start of the script */ + /* but jumping to start of a custom mission is an issue for simple mission-like scripts */ + /* However, it's not an issue for actual mission scripts, because they follow a structure */ + /* and never start with a loop. */ + return 0; + case COMMAND_SHAKE_CAM: + CollectParameters(&m_nIp, 1); + CamShakeNoPos(&TheCamera, ScriptParams[0] / 1000.0f); + return 0; + case COMMAND_SET_VAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr = ScriptParams[0]; + return 0; + } + case COMMAND_SET_VAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr = *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SET_LVAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr = ScriptParams[0]; + return 0; + } + case COMMAND_SET_LVAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr = *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_ADD_VAL_TO_INT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr += ScriptParams[0]; + return 0; + } + case COMMAND_ADD_VAL_TO_FLOAT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr += *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_ADD_VAL_TO_INT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr += ScriptParams[0]; + return 0; + } + case COMMAND_ADD_VAL_TO_FLOAT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr += *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SUB_VAL_FROM_INT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr -= ScriptParams[0]; + return 0; + } + case COMMAND_SUB_VAL_FROM_FLOAT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr -= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SUB_VAL_FROM_INT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr -= ScriptParams[0]; + return 0; + } + case COMMAND_SUB_VAL_FROM_FLOAT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr -= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_MULT_INT_VAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr *= ScriptParams[0]; + return 0; + } + case COMMAND_MULT_FLOAT_VAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr *= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_MULT_INT_LVAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr *= ScriptParams[0]; + return 0; + } + case COMMAND_MULT_FLOAT_LVAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr *= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_DIV_INT_VAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr /= ScriptParams[0]; + return 0; + } + case COMMAND_DIV_FLOAT_VAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr /= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_DIV_INT_LVAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr /= ScriptParams[0]; + return 0; + } + case COMMAND_DIV_FLOAT_LVAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr /= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_THAN_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr > ScriptParams[0]); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_THAN_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr > ScriptParams[0]); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_THAN_INT_VAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(ScriptParams[0] > *ptr); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_THAN_INT_LVAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(ScriptParams[0] > *ptr); + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_THAN_INT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*ptr1 > *ptr2); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_THAN_INT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*ptr1 > *ptr2); + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_THAN_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 > *ptr2); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_THAN_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 > *ptr2); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_THAN_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr > *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_THAN_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr > *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_VAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)&ScriptParams[0] > *(float*)ptr); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_LVAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)&ScriptParams[0] > *(float*)ptr); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)ptr1 > *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)ptr1 > *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 > *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 > *(float*)ptr2); + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr >= ScriptParams[0]); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr >= ScriptParams[0]); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_VAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(ScriptParams[0] >= *ptr); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_LVAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(ScriptParams[0] >= *ptr); + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*ptr1 >= *ptr2); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*ptr1 >= *ptr2); + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 >= *ptr2); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 >= *ptr2); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr >= *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr >= *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_VAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)&ScriptParams[0] >= *(float*)ptr); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_LVAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)&ScriptParams[0] >= *(float*)ptr); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)ptr1 >= *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)ptr1 >= *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 >= *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 >= *(float*)ptr2); + return 0; + } + case COMMAND_IS_INT_VAR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr == ScriptParams[0]); + return 0; + } + case COMMAND_IS_INT_LVAR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr == ScriptParams[0]); + return 0; + } + case COMMAND_IS_INT_VAR_EQUAL_TO_INT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*ptr1 == *ptr2); + return 0; + } + case COMMAND_IS_INT_VAR_EQUAL_TO_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 == *ptr2); + return 0; + } + case COMMAND_IS_INT_LVAR_EQUAL_TO_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 == *ptr2); + return 0; + } + /* Following commands are not implemented, and go to default case + case COMMAND_IS_INT_VAR_NOT_EQUAL_TO_NUMBER: + case COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_NUMBER: + case COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_VAR: + case COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_INT_LVAR: + case COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_LVAR: + */ + case COMMAND_IS_FLOAT_VAR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr == *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr == *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)ptr1 == *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 == *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_EQUAL_TO_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 == *(float*)ptr2); + return 0; + } + /* Following commands are not implemented, and go to default case + case COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_NUMBER: + case COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_NUMBER: + case COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_VAR: + case COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_FLOAT_LVAR: + case COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_LVAR: + */ + case COMMAND_GOTO_IF_TRUE: + CollectParameters(&m_nIp, 1); + if (m_bCondResult) + SetIP(ScriptParams[0] >= 0 ? ScriptParams[0] : SIZE_MAIN_SCRIPT - ScriptParams[0]); + /* Check COMMAND_GOTO note. */ + return 0; + case COMMAND_GOTO_IF_FALSE: + CollectParameters(&m_nIp, 1); + if (!m_bCondResult) + SetIP(ScriptParams[0] >= 0 ? ScriptParams[0] : SIZE_MAIN_SCRIPT - ScriptParams[0]); + /* Check COMMAND_GOTO note. */ + return 0; + case COMMAND_TERMINATE_THIS_SCRIPT: + if (m_bMissionFlag) + CTheScripts::bAlreadyRunningAMissionScript = false; + RemoveScriptFromList(&CTheScripts::pActiveScripts); + AddScriptToList(&CTheScripts::pIdleScripts); +#ifdef MISSION_REPLAY + if (m_bMissionFlag) { + CPlayerInfo* pPlayerInfo = &CWorld::Players[CWorld::PlayerInFocus]; +#if 0 // makeing autosave is pointless and is a bit buggy + if (pPlayerInfo->m_pPed->GetPedState() != PED_DEAD && pPlayerInfo->m_WBState == WBSTATE_PLAYING && !m_bDeatharrestExecuted) + SaveGameForPause(SAVE_TYPE_QUICKSAVE); +#endif + oldTargetX = oldTargetY = 0.0f; + if (AllowMissionReplay == MISSION_RETRY_STAGE_WAIT_FOR_SCRIPT_TO_TERMINATE) + AllowMissionReplay = MISSION_RETRY_STAGE_START_PROCESSING; + // I am fairly sure they forgot to set return value here + } +#endif + return 1; + case COMMAND_START_NEW_SCRIPT: + { + CollectParameters(&m_nIp, 1); + script_assert(ScriptParams[0] >= 0); + CRunningScript* pNew = CTheScripts::StartNewScript(ScriptParams[0]); + int8 type = CTheScripts::Read1ByteFromScript(&m_nIp); + float tmp; + for (int i = 0; type != ARGUMENT_END; type = CTheScripts::Read1ByteFromScript(&m_nIp), i++) { + switch (type) { + case ARGUMENT_INT32: + pNew->m_anLocalVariables[i] = CTheScripts::Read4BytesFromScript(&m_nIp); + break; + case ARGUMENT_GLOBALVAR: + pNew->m_anLocalVariables[i] = *(int32*)&CTheScripts::ScriptSpace[(uint16)CTheScripts::Read2BytesFromScript(&m_nIp)]; + break; + case ARGUMENT_LOCALVAR: + pNew->m_anLocalVariables[i] = m_anLocalVariables[CTheScripts::Read2BytesFromScript(&m_nIp)]; + break; + case ARGUMENT_INT8: + pNew->m_anLocalVariables[i] = CTheScripts::Read1ByteFromScript(&m_nIp); + break; + case ARGUMENT_INT16: + pNew->m_anLocalVariables[i] = CTheScripts::Read2BytesFromScript(&m_nIp); + break; + case ARGUMENT_FLOAT: + tmp = CTheScripts::ReadFloatFromScript(&m_nIp); + pNew->m_anLocalVariables[i] = *(int32*)&tmp; + break; + default: + break; + } + } + return 0; + } + case COMMAND_GOSUB: + CollectParameters(&m_nIp, 1); + script_assert(m_nStackPointer < MAX_STACK_DEPTH); + m_anStack[m_nStackPointer++] = m_nIp; + SetIP(ScriptParams[0] >= 0 ? ScriptParams[0] : SIZE_MAIN_SCRIPT - ScriptParams[0]); + return 0; + case COMMAND_RETURN: + script_assert(m_nStackPointer > 0); /* No more SSU */ + SetIP(m_anStack[--m_nStackPointer]); + return 0; + case COMMAND_LINE: + CollectParameters(&m_nIp, 6); + /* Something must have been here */ + return 0; + case COMMAND_CREATE_PLAYER: + { + CollectParameters(&m_nIp, 4); + int32 index = ScriptParams[0]; + script_assert(index < 1); /* Constant? Also no more double player glitch */ + printf("&&&&&&&&&&&&&Creating player: %d\n", index); + if (!CStreaming::HasModelLoaded(MI_PLAYER)) { + CStreaming::RequestSpecialModel(MI_PLAYER, "player", STREAMFLAGS_DONT_REMOVE | STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + } + CPlayerPed::SetupPlayerPed(index); + CWorld::Players[index].m_pPed->CharCreatedBy = MISSION_CHAR; + CPlayerPed::DeactivatePlayerPed(index); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += CWorld::Players[index].m_pPed->GetDistanceFromCentreOfMassToBaseOfModel(); + CWorld::Players[index].m_pPed->SetPosition(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, CWorld::Players[index].m_pPed); + CPlayerPed::ReactivatePlayerPed(index); + ScriptParams[0] = index; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_PLAYER_COORDINATES: + { + CVector pos; + CollectParameters(&m_nIp, 1); + if (CWorld::Players[ScriptParams[0]].m_pPed->bInVehicle) + pos = CWorld::Players[ScriptParams[0]].m_pPed->m_pMyVehicle->GetPosition(); + else + pos = CWorld::Players[ScriptParams[0]].m_pPed->GetPosition(); + *(CVector*)&ScriptParams[0] = pos; + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_SET_PLAYER_COORDINATES: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[1]; + int index = ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CPlayerPed* ped = CWorld::Players[index].m_pPed; + if (!ped->bInVehicle) { + pos.z += ped->GetDistanceFromCentreOfMassToBaseOfModel(); + ped->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, ped); + return 0; + } + pos.z += ped->m_pMyVehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + if (ped->m_pMyVehicle->IsBoat()) + ped->m_pMyVehicle->Teleport(pos); + else + ped->m_pMyVehicle->Teleport(pos); + /* I'll keep this condition here but obviously it is absolutely pointless */ + /* It's clearly present in disassembly so it had to be in original code */ + CTheScripts::ClearSpaceForMissionEntity(pos, ped->m_pMyVehicle); + return 0; + } + case COMMAND_IS_PLAYER_IN_AREA_2D: + { + CollectParameters(&m_nIp, 6); + CPlayerPed* ped = CWorld::Players[ScriptParams[0]].m_pPed; + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + if (!ped->bInVehicle) + UpdateCompareFlag(ped->IsWithinArea(x1, y1, x2, y2)); + else + UpdateCompareFlag(ped->m_pMyVehicle->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_PLAYER_IN_AREA_3D: + { + CollectParameters(&m_nIp, 8); + CPlayerPed* ped = CWorld::Players[ScriptParams[0]].m_pPed; + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float z1 = *(float*)&ScriptParams[3]; + float x2 = *(float*)&ScriptParams[4]; + float y2 = *(float*)&ScriptParams[5]; + float z2 = *(float*)&ScriptParams[6]; + if (ped->bInVehicle) + UpdateCompareFlag(ped->m_pMyVehicle->IsWithinArea(x1, y1, z1, x2, y2, z2)); + else + UpdateCompareFlag(ped->IsWithinArea(x1, y1, z1, x2, y2, z2)); + if (ScriptParams[7]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, (z1 + z2) / 2); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(x1, y1, z1, x2, y2, z2); + return 0; + } + case COMMAND_ADD_INT_VAR_TO_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 += *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_ADD_INT_LVAR_TO_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 += *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_ADD_INT_VAR_TO_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 += *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_ADD_INT_LVAR_TO_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 += *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_ADD_FLOAT_VAR_TO_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 += *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 += *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_ADD_FLOAT_VAR_TO_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 += *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 += *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SUB_INT_VAR_FROM_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 -= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_SUB_INT_LVAR_FROM_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 -= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 -= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 -= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + default: + script_assert(0); + break; + } + return -1; +} + +int8 CRunningScript::ProcessCommands100To199(int32 command) +{ + float *fScriptVar1; + int *nScriptVar1; + switch (command) { + case COMMAND_SUB_INT_LVAR_FROM_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 -= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SUB_INT_VAR_FROM_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 -= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 -= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 -= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_MULT_INT_VAR_BY_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 *= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_MULT_INT_VAR_BY_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 *= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_MULT_INT_LVAR_BY_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 *= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_MULT_INT_LVAR_BY_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 *= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_MULT_FLOAT_VAR_BY_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 *= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_MULT_FLOAT_VAR_BY_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 *= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 *= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 *= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_DIV_INT_VAR_BY_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 /= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_DIV_INT_VAR_BY_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 /= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_DIV_INT_LVAR_BY_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 /= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_DIV_INT_LVAR_BY_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 /= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_DIV_FLOAT_VAR_BY_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 /= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_DIV_FLOAT_VAR_BY_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 /= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 /= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 /= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_ADD_TIMED_VAL_TO_FLOAT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr += CTimer::GetTimeStep() * *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_ADD_TIMED_VAL_TO_FLOAT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr += CTimer::GetTimeStep() * *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 += CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; +#ifdef FIX_BUGS + case COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_LVAR: +#else + case COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_VAR: +#endif + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 += CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; +#ifdef FIX_BUGS + case COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_VAR: +#else + case COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_LVAR: +#endif + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 += CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 += CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SUB_TIMED_VAL_FROM_FLOAT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr -= CTimer::GetTimeStep() * *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SUB_TIMED_VAL_FROM_FLOAT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr -= CTimer::GetTimeStep() * *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 -= CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; +#ifdef FIX_BUGS // in SA it was fixed by reversing their order in enum + case COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_LVAR: +#else + case COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_VAR: +#endif + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 -= CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; +#ifdef FIX_BUGS + case COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_VAR: +#else + case COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_LVAR: +#endif + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 -= CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 -= CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SET_VAR_INT_TO_VAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_SET_VAR_INT_TO_LVAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_SET_LVAR_INT_TO_VAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_SET_LVAR_INT_TO_LVAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_SET_VAR_FLOAT_TO_VAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_SET_VAR_FLOAT_TO_LVAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_SET_LVAR_FLOAT_TO_VAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_SET_LVAR_FLOAT_TO_LVAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_CSET_VAR_INT_TO_VAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_CSET_VAR_INT_TO_LVAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_CSET_LVAR_INT_TO_VAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_CSET_LVAR_INT_TO_LVAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_CSET_VAR_FLOAT_TO_VAR_INT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_CSET_VAR_FLOAT_TO_LVAR_INT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_CSET_LVAR_FLOAT_TO_VAR_INT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_CSET_LVAR_FLOAT_TO_LVAR_INT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_ABS_VAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = ABS(*ptr); + return 0; + } + case COMMAND_ABS_LVAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = ABS(*ptr); + return 0; + } + case COMMAND_ABS_VAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = ABS(*ptr); + return 0; + } + case COMMAND_ABS_LVAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = ABS(*ptr); + return 0; + } + case COMMAND_GENERATE_RANDOM_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CGeneral::GetRandomNumber(); + CGeneral::GetRandomNumber(); + CGeneral::GetRandomNumber(); /* To make it EXTRA random! */ +#ifdef FIX_BUGS + *ptr = CGeneral::GetRandomNumberInRange(0.0f, 1.0f); +#else + *ptr = CGeneral::GetRandomNumber() / 65536.0f; + /* Between 0 and 0.5 on PC (oh well...), never used in original script. */ +#endif + + return 0; + } + case COMMAND_GENERATE_RANDOM_INT: + *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL) = CGeneral::GetRandomNumber(); + return 0; + case COMMAND_CREATE_CHAR: + { + CollectParameters(&m_nIp, 5); + switch (ScriptParams[1]) { + case MI_COP: + if (ScriptParams[0] == PEDTYPE_COP) + ScriptParams[1] = COP_STREET; + break; + case MI_SWAT: + if (ScriptParams[0] == PEDTYPE_COP) + ScriptParams[1] = COP_SWAT; + break; + case MI_FBI: + if (ScriptParams[0] == PEDTYPE_COP) + ScriptParams[1] = COP_FBI; + break; + case MI_ARMY: + if (ScriptParams[0] == PEDTYPE_COP) + ScriptParams[1] = COP_ARMY; + break; + case MI_MEDIC: + if (ScriptParams[0] == PEDTYPE_EMERGENCY) + ScriptParams[1] = PEDTYPE_EMERGENCY; + break; + case MI_FIREMAN: + if (ScriptParams[0] == PEDTYPE_FIREMAN) + ScriptParams[1] = PEDTYPE_FIREMAN; + break; + default: + break; + } + CPed* ped; + if (ScriptParams[0] == PEDTYPE_COP) + ped = new CCopPed((eCopType)ScriptParams[1]); + else if (ScriptParams[0] == PEDTYPE_EMERGENCY || ScriptParams[0] == PEDTYPE_FIREMAN) + ped = new CEmergencyPed(ScriptParams[1]); + else + ped = new CCivilianPed((ePedType)ScriptParams[0], ScriptParams[1]); + ped->CharCreatedBy = MISSION_CHAR; + ped->bRespondsToThreats = false; + ped->bAllowMedicsToReviveMe = false; + CVector pos = *(CVector*)&ScriptParams[2]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += 1.0f; + ped->SetPosition(pos); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CTheScripts::ClearSpaceForMissionEntity(pos, ped); + CWorld::Add(ped); + ped->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pos); + CPopulation::ms_nTotalMissionPeds++; + ScriptParams[0] = CPools::GetPedPool()->GetIndex(ped); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_DELETE_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + if (ped) { + if (ped->InVehicle()) { + if (ped->m_pMyVehicle->pDriver == ped) { + ped->m_pMyVehicle->RemoveDriver(); + ped->m_pMyVehicle->SetStatus(STATUS_ABANDONED); + if (ped->m_pMyVehicle->m_nDoorLock == CARLOCK_LOCKED_INITIALLY) + ped->m_pMyVehicle->m_nDoorLock = CARLOCK_UNLOCKED; + if (ped->m_nPedType == PEDTYPE_COP && ped->m_pMyVehicle->IsLawEnforcementVehicle()) + ped->m_pMyVehicle->ChangeLawEnforcerState(0); + } + else { + ped->m_pMyVehicle->RemovePassenger(ped); + } + } + CWorld::RemoveReferencesToDeletedObject(ped); + delete ped; + --CPopulation::ms_nTotalMissionPeds; + } + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_CHAR_WANDER_DIR: + { + CollectParameters(&m_nIp, 2); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + ped->ClearAll(); + int8 path = ScriptParams[1]; + if (ScriptParams[1] < 0 || ScriptParams[1] > 7) + // Max number GetRandomNumberInRange returns is max-1 +#ifdef FIX_BUGS + path = CGeneral::GetRandomNumberInRange(0, 8); +#else + path = CGeneral::GetRandomNumberInRange(0, 7); +#endif + ped->SetWanderPath(path); + return 0; + } + /* Not implemented. + case COMMAND_CHAR_WANDER_RANGE: + */ + case COMMAND_CHAR_FOLLOW_PATH: + { + CollectParameters(&m_nIp, 4); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + ped->ClearAll(); + ped->SetFollowPath(pos); + return 0; + } + case COMMAND_CHAR_SET_IDLE: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + ped->bScriptObjectiveCompleted = false; + ped->SetObjective(OBJECTIVE_WAIT_ON_FOOT); + return 0; + } + case COMMAND_GET_CHAR_COORDINATES: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVehicle* vehicle; + CVector pos; + /* Seems a bit clumsy but I'll leave original flow */ + if (ped->bInVehicle) + vehicle = ped->m_pMyVehicle; + else + vehicle = nil; + if (vehicle) + pos = vehicle->GetPosition(); + else + pos = ped->GetPosition(); + *(CVector*)&ScriptParams[0] = pos; + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_SET_CHAR_COORDINATES: + { + CollectParameters(&m_nIp, 4); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVehicle* vehicle; + if (ped->bInVehicle) + vehicle = ped->m_pMyVehicle; + else + vehicle = nil; + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + /* The following block was once again written + * by someone not familiar with virtual functions. + * It doesn't require any ifs at all. + * To keep as close to original as possible, I'll keep it. + * Maybe there was more commented out/debug + * stuff, but I doubt it. + */ + if (!vehicle) { + pos.z += ped->GetDistanceFromCentreOfMassToBaseOfModel(); + ped->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, ped); + } + else if (vehicle->IsBoat()) { + pos.z += vehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + vehicle->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, vehicle); + } + else { + pos.z += vehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + vehicle->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, vehicle); + } + /* Short version of this command. + * + * CollectParameters(&m_nIp, 4); + * CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + * script_assert(ped); + * CEntity* entityToMove = ped->bInVehicle ? ped->m_pMyVehicle : ped; + * CVector pos = *(CVector*)&ScriptParams[1]; + * if (pos.z <= MAP_Z_LOW_LIMIT) + * pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + * pos.z += entityToMove->GetDistanceFromCentreOfMassToBaseOfModel(); + * entityToMove->Teleport(pos); + * CTheScripts::ClearSpaceForMissionEntity(pos, entityToMove); + * + */ + return 0; + } + case COMMAND_IS_CHAR_STILL_ALIVE: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(ped && ped->GetPedState() != PED_DEAD && ped->GetPedState() != PED_DIE); + return 0; + } + case COMMAND_IS_CHAR_IN_AREA_2D: + { + CollectParameters(&m_nIp, 6); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVehicle* vehicle; + if (ped->bInVehicle) + vehicle = ped->m_pMyVehicle; + else + vehicle = nil; + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + if (vehicle) + UpdateCompareFlag(ped->m_pMyVehicle->IsWithinArea(x1, y1, x2, y2)); + else + UpdateCompareFlag(ped->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_CHAR_IN_AREA_3D: + { + CollectParameters(&m_nIp, 8); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVehicle* vehicle; + if (ped->bInVehicle) + vehicle = ped->m_pMyVehicle; + else + vehicle = nil; + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float z1 = *(float*)&ScriptParams[3]; + float x2 = *(float*)&ScriptParams[4]; + float y2 = *(float*)&ScriptParams[5]; + float z2 = *(float*)&ScriptParams[6]; + if (vehicle) + UpdateCompareFlag(ped->m_pMyVehicle->IsWithinArea(x1, y1, z1, x2, y2, z2)); + else + UpdateCompareFlag(ped->IsWithinArea(x1, y1, z1, x2, y2, z2)); + if (ScriptParams[7]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, (z1 + z2) / 2); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(x1, y1, z1, x2, y2, z2); + return 0; + } + case COMMAND_CREATE_CAR: + { + CollectParameters(&m_nIp, 4); + int32 handle; + if (CModelInfo::IsBoatModel(ScriptParams[0])) { + CBoat* boat = new CBoat(ScriptParams[0], MISSION_VEHICLE); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += boat->GetDistanceFromCentreOfMassToBaseOfModel(); + boat->SetPosition(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, boat); + boat->SetStatus(STATUS_ABANDONED); + boat->bIsLocked = true; + boat->AutoPilot.m_nCarMission = MISSION_NONE; + boat->AutoPilot.m_nTempAction = TEMPACT_NONE; /* Animation ID? */ + boat->AutoPilot.m_nCruiseSpeed = boat->AutoPilot.m_fMaxTrafficSpeed = 20.0f; + CWorld::Add(boat); + handle = CPools::GetVehiclePool()->GetIndex(boat); + } + else { + CVehicle* car; + if (!CModelInfo::IsBikeModel(ScriptParams[0])) + car = new CAutomobile(ScriptParams[0], MISSION_VEHICLE); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += car->GetDistanceFromCentreOfMassToBaseOfModel(); + car->SetPosition(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, car); + car->SetStatus(STATUS_ABANDONED); + car->bIsLocked = true; + CCarCtrl::JoinCarWithRoadSystem(car); + car->AutoPilot.m_nCarMission = MISSION_NONE; + car->AutoPilot.m_nTempAction = TEMPACT_NONE; /* Animation ID? */ + car->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + car->AutoPilot.m_nCruiseSpeed = car->AutoPilot.m_fMaxTrafficSpeed = 9.0f; + car->AutoPilot.m_nCurrentLane = car->AutoPilot.m_nNextLane = 0; + car->bEngineOn = false; + car->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pos); + car->bHasBeenOwnedByPlayer = true; + CWorld::Add(car); + handle = CPools::GetVehiclePool()->GetIndex(car); + } + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(handle, CLEANUP_CAR); + return 0; + } + case COMMAND_DELETE_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + if (car) { + CWorld::Remove(car); + CWorld::RemoveReferencesToDeletedObject(car); + delete car; + } + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CAR); + return 0; + } + case COMMAND_CAR_GOTO_COORDINATES: + { + CollectParameters(&m_nIp, 4); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += car->GetDistanceFromCentreOfMassToBaseOfModel(); + if (CCarCtrl::JoinCarWithRoadSystemGotoCoors(car, pos, false)) + car->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_STRAIGHT; + else + car->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS; + car->SetStatus(STATUS_PHYSICS); + car->bEngineOn = true; + car->AutoPilot.m_nCruiseSpeed = Max(6, car->AutoPilot.m_nCruiseSpeed); + car->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + return 0; + } + case COMMAND_CAR_WANDER_RANDOMLY: + { + CollectParameters(&m_nIp, 1); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + CCarCtrl::JoinCarWithRoadSystem(car); + car->AutoPilot.m_nCarMission = MISSION_CRUISE; + car->bEngineOn = true; + car->AutoPilot.m_nCruiseSpeed = Max(6, car->AutoPilot.m_nCruiseSpeed); + car->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + return 0; + } + case COMMAND_CAR_SET_IDLE: + { + CollectParameters(&m_nIp, 1); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + car->AutoPilot.m_nCarMission = MISSION_NONE; + return 0; + } + case COMMAND_GET_CAR_COORDINATES: + { + CollectParameters(&m_nIp, 1); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + *(CVector*)&ScriptParams[0] = car->GetPosition(); + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_SET_CAR_COORDINATES: + { + CollectParameters(&m_nIp, 4); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += car->GetDistanceFromCentreOfMassToBaseOfModel(); + car->SetIsStatic(false); + /* Again weird usage of virtual functions. */ + if (car->IsBoat()) { + car->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, car); + } + else { + car->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, car); + /* May the following be inlined CCarCtrl function? */ + switch (car->AutoPilot.m_nCarMission) { + case MISSION_CRUISE: + CCarCtrl::JoinCarWithRoadSystem(car); + break; + case MISSION_RAMPLAYER_FARAWAY: + case MISSION_RAMPLAYER_CLOSE: + case MISSION_BLOCKPLAYER_FARAWAY: + case MISSION_BLOCKPLAYER_CLOSE: + case MISSION_BLOCKPLAYER_HANDBRAKESTOP: + CCarCtrl::JoinCarWithRoadSystemGotoCoors(car, FindPlayerCoors(), false); + break; + case MISSION_GOTOCOORDS: + case MISSION_GOTOCOORDS_STRAIGHT: + CCarCtrl::JoinCarWithRoadSystemGotoCoors(car, car->AutoPilot.m_vecDestinationCoors, false); + break; + case MISSION_GOTOCOORDS_ACCURATE: + case MISSION_GOTO_COORDS_STRAIGHT_ACCURATE: + CCarCtrl::JoinCarWithRoadSystemGotoCoors(car, car->AutoPilot.m_vecDestinationCoors, false); + break; + case MISSION_RAMCAR_FARAWAY: + case MISSION_RAMCAR_CLOSE: + case MISSION_BLOCKCAR_FARAWAY: + case MISSION_BLOCKCAR_CLOSE: + case MISSION_BLOCKCAR_HANDBRAKESTOP: + CCarCtrl::JoinCarWithRoadSystemGotoCoors(car, car->AutoPilot.m_pTargetCar->GetPosition(), false); + break; + default: + break; + } + } + return 0; + } + case COMMAND_IS_CAR_STILL_ALIVE: + { + CollectParameters(&m_nIp, 1); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(car && car->GetStatus() != STATUS_WRECKED && (car->IsBoat() || !car->bIsInWater)); + return 0; + } + case COMMAND_SET_CAR_CRUISE_SPEED: + { + CollectParameters(&m_nIp, 2); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); +#if defined MISSION_REPLAY && defined SIMPLIER_MISSIONS + car->AutoPilot.m_nCruiseSpeed = *(float*)&ScriptParams[1]; + if (missionRetryScriptIndex == 40 && car->GetModelIndex() == MI_CHEETAH) // Turismo + car->AutoPilot.m_nCruiseSpeed = 8 * car->AutoPilot.m_nCruiseSpeed / 10; + car->AutoPilot.m_nCruiseSpeed = Min(car->AutoPilot.m_nCruiseSpeed, 60.0f * car->pHandling->Transmission.fMaxCruiseVelocity); +#else + car->AutoPilot.m_nCruiseSpeed = Min(*(float*)&ScriptParams[1], 60.0f * car->pHandling->Transmission.fMaxCruiseVelocity); +#endif + return 0; + } + case COMMAND_SET_CAR_DRIVING_STYLE: + { + CollectParameters(&m_nIp, 2); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + car->AutoPilot.m_nDrivingStyle = (uint8)ScriptParams[1]; + return 0; + } + case COMMAND_SET_CAR_MISSION: + { + CollectParameters(&m_nIp, 2); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + car->AutoPilot.m_nCarMission = (uint8)ScriptParams[1]; + car->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + car->bEngineOn = true; + return 0; + } + case COMMAND_IS_CAR_IN_AREA_2D: + { + CollectParameters(&m_nIp, 6); + CVehicle* vehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(vehicle); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + UpdateCompareFlag(vehicle->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_CAR_IN_AREA_3D: + { + CollectParameters(&m_nIp, 8); + CVehicle* vehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(vehicle); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float z1 = *(float*)&ScriptParams[3]; + float x2 = *(float*)&ScriptParams[4]; + float y2 = *(float*)&ScriptParams[5]; + float z2 = *(float*)&ScriptParams[6]; + UpdateCompareFlag(vehicle->IsWithinArea(x1, y1, z1, x2, y2, z2)); + if (ScriptParams[7]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, (z1 + z2) / 2); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(x1, y1, z1, x2, y2, z2); + return 0; + } + case COMMAND_SPECIAL_0: + case COMMAND_SPECIAL_1: + case COMMAND_SPECIAL_2: + case COMMAND_SPECIAL_3: + case COMMAND_SPECIAL_4: + case COMMAND_SPECIAL_5: + case COMMAND_SPECIAL_6: + case COMMAND_SPECIAL_7: + script_assert(0); + return 0; + case COMMAND_PRINT_BIG: + { + wchar* key = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); +#ifdef MISSION_REPLAY + if (strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "M_FAIL") == 0 && CanAllowMissionReplay()) + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_SCRIPT_TO_TERMINATE; +#endif + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + CMessages::AddBigMessage(key, ScriptParams[0], ScriptParams[1] - 1); + return 0; + } + case COMMAND_PRINT: + { + wchar* key = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + CMessages::AddMessage(key, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_PRINT_NOW: + { + wchar* key = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + CMessages::AddMessageJumpQ(key, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_PRINT_SOON: + { + wchar* key = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + CMessages::AddMessageSoon(key, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_CLEAR_PRINTS: + CMessages::ClearMessages(); + return 0; + case COMMAND_GET_TIME_OF_DAY: + ScriptParams[0] = CClock::GetHours(); + ScriptParams[1] = CClock::GetMinutes(); + StoreParameters(&m_nIp, 2); + return 0; + case COMMAND_SET_TIME_OF_DAY: + CollectParameters(&m_nIp, 2); + CClock::SetGameClock(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_GET_MINUTES_TO_TIME_OF_DAY: + CollectParameters(&m_nIp, 2); + ScriptParams[0] = CClock::GetGameClockMinutesUntil(ScriptParams[0], ScriptParams[1]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_IS_POINT_ON_SCREEN: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= -100) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + UpdateCompareFlag(TheCamera.IsSphereVisible(pos, *(float*)&ScriptParams[3])); + return 0; + } + case COMMAND_DEBUG_ON: + CTheScripts::DbgFlag = true; + return 0; + case COMMAND_DEBUG_OFF: + CTheScripts::DbgFlag = false; + return 0; + case COMMAND_RETURN_TRUE: + UpdateCompareFlag(true); + return 0; + case COMMAND_RETURN_FALSE: + UpdateCompareFlag(false); + return 0; + /* Special command only used by compiler. + case COMMAND_VAR_INT: + */ + default: + script_assert(0); + break; + } + return -1; +} + +int8 CRunningScript::ProcessCommands200To299(int32 command) +{ + switch (command) { + /* Special commands. + case COMMAND_VAR_FLOAT: + case COMMAND_LVAR_INT: + case COMMAND_LVAR_FLOAT: + case COMMAND_LBRACKET: + case COMMAND_RBRACKET: + case COMMAND_REPEAT: + case COMMAND_ENDREPEAT: + case COMMAND_IF: + case COMMAND_IFNOT: + case COMMAND_ELSE: + case COMMAND_ENDIF: + case COMMAND_WHILE: + case COMMAND_WHILENOT: + case COMMAND_ENDWHILE: + */ + case COMMAND_ANDOR: + CollectParameters(&m_nIp, 1); + m_nAndOrState = ScriptParams[0]; + if (m_nAndOrState == ANDOR_NONE){ + m_bCondResult = false; // pointless + }else if (m_nAndOrState >= ANDS_1 && m_nAndOrState <= ANDS_8){ + m_bCondResult = true; + m_nAndOrState++; + }else if (m_nAndOrState >= ORS_1 && m_nAndOrState <= ORS_8){ + m_bCondResult = false; + m_nAndOrState++; + }else{ + script_assert(0 && "COMMAND_ANDOR: invalid ANDOR state"); + } + return 0; + case COMMAND_LAUNCH_MISSION: + { + CollectParameters(&m_nIp, 1); + CRunningScript* pNew = CTheScripts::StartNewScript(ScriptParams[0]); + pNew->m_bIsMissionScript = true; + return 0; + } + case COMMAND_MISSION_HAS_FINISHED: + { + if (!m_bIsMissionScript) + return 0; + if (strcmp(m_abScriptName, "love3") == 0) /* A Drop in the Ocean */ + CPickups::RemoveAllFloatingPickups(); + CTheScripts::MissionCleanUp.Process(); + return 0; + } + case COMMAND_STORE_CAR_CHAR_IS_IN: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVehicle* pCurrent = nil; + if (ped->bInVehicle) { + pCurrent = ped->m_pMyVehicle; + } + script_assert(pCurrent); // GetIndex(0) doesn't look good + int handle = CPools::GetVehiclePool()->GetIndex(pCurrent); + if (handle != CTheScripts::StoreVehicleIndex && m_bIsMissionScript){ + CVehicle* pOld = CPools::GetVehiclePool()->GetAt(CTheScripts::StoreVehicleIndex); + if (pOld){ + CCarCtrl::RemoveFromInterestingVehicleList(pOld); + if (pOld->VehicleCreatedBy == MISSION_VEHICLE && CTheScripts::StoreVehicleWasRandom){ + pOld->VehicleCreatedBy = RANDOM_VEHICLE; + pOld->bIsLocked = false; + CCarCtrl::NumRandomCars++; + CCarCtrl::NumMissionCars--; + CTheScripts::MissionCleanUp.RemoveEntityFromList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + } + } + + CTheScripts::StoreVehicleIndex = handle; + switch (pCurrent->VehicleCreatedBy){ + case RANDOM_VEHICLE: + pCurrent->VehicleCreatedBy = MISSION_VEHICLE; + CCarCtrl::NumMissionCars++; + CCarCtrl::NumRandomCars--; + CTheScripts::StoreVehicleWasRandom = true; + CTheScripts::MissionCleanUp.AddEntityToList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + break; + case PARKED_VEHICLE: + pCurrent->VehicleCreatedBy = MISSION_VEHICLE; + CCarCtrl::NumMissionCars++; + CCarCtrl::NumParkedCars--; + CTheScripts::StoreVehicleWasRandom = true; + CTheScripts::MissionCleanUp.AddEntityToList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + break; + case MISSION_VEHICLE: + case PERMANENT_VEHICLE: + CTheScripts::StoreVehicleWasRandom = false; + break; + default: + break; + } + } + ScriptParams[0] = CTheScripts::StoreVehicleIndex; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_STORE_CAR_PLAYER_IS_IN: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(ped); + if (!ped->bInVehicle) + return 0; // No value written to output variable + CVehicle* pCurrent = ped->m_pMyVehicle; + script_assert(pCurrent); // Here pCurrent shouldn't be NULL anyway + int handle = CPools::GetVehiclePool()->GetIndex(pCurrent); + if (handle != CTheScripts::StoreVehicleIndex && m_bIsMissionScript) { + CVehicle* pOld = CPools::GetVehiclePool()->GetAt(CTheScripts::StoreVehicleIndex); + if (pOld){ + CCarCtrl::RemoveFromInterestingVehicleList(pOld); + if (pOld->VehicleCreatedBy == MISSION_VEHICLE && CTheScripts::StoreVehicleWasRandom){ + pOld->VehicleCreatedBy = RANDOM_VEHICLE; + pOld->bIsLocked = false; + CCarCtrl::NumRandomCars++; + CCarCtrl::NumMissionCars--; + CTheScripts::MissionCleanUp.RemoveEntityFromList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + } + } + + CTheScripts::StoreVehicleIndex = handle; + switch (pCurrent->VehicleCreatedBy) { + case RANDOM_VEHICLE: + pCurrent->VehicleCreatedBy = MISSION_VEHICLE; + CCarCtrl::NumMissionCars++; + CCarCtrl::NumRandomCars--; + CTheScripts::StoreVehicleWasRandom = true; + CTheScripts::MissionCleanUp.AddEntityToList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + break; + case PARKED_VEHICLE: + pCurrent->VehicleCreatedBy = MISSION_VEHICLE; + CCarCtrl::NumMissionCars++; + CCarCtrl::NumParkedCars--; + CTheScripts::StoreVehicleWasRandom = true; + CTheScripts::MissionCleanUp.AddEntityToList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + break; + case MISSION_VEHICLE: + case PERMANENT_VEHICLE: + CTheScripts::StoreVehicleWasRandom = false; + break; + default: + break; + } + } + ScriptParams[0] = CTheScripts::StoreVehicleIndex; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_CHAR_IN_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CVehicle* pCheckedVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CVehicle* pActualVehicle = pPed->bInVehicle ? pPed->m_pMyVehicle : nil; + UpdateCompareFlag(pActualVehicle && pActualVehicle == pCheckedVehicle); + return 0; + } + case COMMAND_IS_PLAYER_IN_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pCheckedVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle == pCheckedVehicle); + return 0; + } + case COMMAND_IS_CHAR_IN_MODEL: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CVehicle* pActualVehicle = pPed->bInVehicle ? pPed->m_pMyVehicle : nil; + UpdateCompareFlag(pActualVehicle && pActualVehicle->GetModelIndex() == ScriptParams[1]); + return 0; + } + case COMMAND_IS_PLAYER_IN_MODEL: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->GetModelIndex() == ScriptParams[1]); + return 0; + } + case COMMAND_IS_CHAR_IN_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(pPed->bInVehicle); + return 0; + } + case COMMAND_IS_PLAYER_IN_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + UpdateCompareFlag(pPed->bInVehicle); + return 0; + } + case COMMAND_IS_BUTTON_PRESSED: + { + CollectParameters(&m_nIp, 2); + bool value = GetPadState(ScriptParams[0], ScriptParams[1]) != 0; + if (CGame::playingIntro && ScriptParams[0] == 0 && ScriptParams[1] == 12) { + if (CPad::GetPad(0)->GetLeftMouseJustDown() || + CPad::GetPad(0)->GetEnterJustDown() || + CPad::GetPad(0)->GetCharJustDown(' ')) + value = true; + } + UpdateCompareFlag(value); + return 0; + } + case COMMAND_GET_PAD_STATE: + { + CollectParameters(&m_nIp, 1); + ScriptParams[0] = GetPadState(ScriptParams[0], ScriptParams[1]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_LOCATE_PLAYER_ANY_MEANS_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_2D: + LocatePlayerCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_2D: + LocatePlayerCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CHAR_ANY_MEANS_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_2D: + LocateCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_CHAR_2D: + LocateCharCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D: + LocatePlayerCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_3D: + LocatePlayerCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D: + LocateCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_CHAR_3D: + LocateCharCharCommand(command, &m_nIp); + return 0; + case COMMAND_CREATE_OBJECT: + { + CollectParameters(&m_nIp, 4); + int mi = ScriptParams[0] >= 0 ? ScriptParams[0] : CTheScripts::UsedObjectArray[-ScriptParams[0]].index; + CObject* pObj = new CObject(mi, false); + pObj->ObjectCreatedBy = MISSION_OBJECT; + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += pObj->GetDistanceFromCentreOfMassToBaseOfModel(); + pObj->SetPosition(pos); + pObj->SetOrientation(0.0f, 0.0f, 0.0f); + pObj->GetMatrix().UpdateRW(); + pObj->UpdateRwFrame(); + CTheScripts::ClearSpaceForMissionEntity(pos, pObj); + CWorld::Add(pObj); + ScriptParams[0] = CPools::GetObjectPool()->GetIndex(pObj); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_OBJECT); + return 0; + } + case COMMAND_DELETE_OBJECT: + { + CollectParameters(&m_nIp, 1); + CObject* pObj = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + if (pObj){ + CWorld::Remove(pObj); + CWorld::RemoveReferencesToDeletedObject(pObj); + delete pObj; + } + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_OBJECT); + return 0; + } + case COMMAND_ADD_SCORE: + CollectParameters(&m_nIp, 2); + CWorld::Players[ScriptParams[0]].m_nMoney += ScriptParams[1]; + return 0; + case COMMAND_IS_SCORE_GREATER: + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_nMoney > ScriptParams[1]); + return 0; + case COMMAND_STORE_SCORE: + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CWorld::Players[ScriptParams[0]].m_nMoney; + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_GIVE_REMOTE_CONTROLLED_CAR_TO_PLAYER: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRemote::GivePlayerRemoteControlledCar(pos.x, pos.y, pos.z, DEGTORAD(*(float*)&ScriptParams[4]), MI_RCBANDIT); + return 0; + } + case COMMAND_ALTER_WANTED_LEVEL: + CollectParameters(&m_nIp, 2); + CWorld::Players[ScriptParams[0]].m_pPed->SetWantedLevel(ScriptParams[1]); + return 0; + case COMMAND_ALTER_WANTED_LEVEL_NO_DROP: + CollectParameters(&m_nIp, 2); + CWorld::Players[ScriptParams[0]].m_pPed->SetWantedLevelNoDrop(ScriptParams[1]); + return 0; + case COMMAND_IS_WANTED_LEVEL_GREATER: + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_pPed->m_pWanted->GetWantedLevel() > ScriptParams[1]); + return 0; + case COMMAND_CLEAR_WANTED_LEVEL: + CollectParameters(&m_nIp, 1); + CWorld::Players[ScriptParams[0]].m_pPed->SetWantedLevel(0); + return 0; + case COMMAND_SET_DEATHARREST_STATE: + CollectParameters(&m_nIp, 1); + m_bDeatharrestEnabled = (ScriptParams[0] == 1); + return 0; + case COMMAND_HAS_DEATHARREST_BEEN_EXECUTED: + UpdateCompareFlag(m_bDeatharrestExecuted); + return 0; + case COMMAND_ADD_AMMO_TO_PLAYER: + { + CollectParameters(&m_nIp, 3); + CWorld::Players[ScriptParams[0]].m_pPed->GrantAmmo((eWeaponType)ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_ADD_AMMO_TO_CHAR: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->GrantAmmo((eWeaponType)ScriptParams[1], ScriptParams[2]); + return 0; + } + /* Not implemented + case COMMAND_ADD_AMMO_TO_CAR: + case COMMAND_IS_PLAYER_STILL_ALIVE: + */ + case COMMAND_IS_PLAYER_DEAD: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_WBState == WBSTATE_WASTED); + return 0; + case COMMAND_IS_CHAR_DEAD: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(!pPed || pPed->GetPedState() == PED_DIE || pPed->GetPedState() == PED_DEAD); + return 0; + } + case COMMAND_IS_CAR_DEAD: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(!pVehicle || pVehicle->GetStatus() == STATUS_WRECKED || !pVehicle->IsBoat() && pVehicle->bIsInWater); + return 0; + } + case COMMAND_SET_CHAR_THREAT_SEARCH: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_fearFlags |= ScriptParams[1]; + return 0; + } + /* Not implemented. + case COMMAND_SET_CHAR_THREAT_REACTION: + */ + case COMMAND_SET_CHAR_OBJ_NO_OBJ: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->ClearObjective(); + return 0; + } + /* Not implemented. + case COMMAND_ORDER_DRIVER_OUT_OF_CAR: + case COMMAND_ORDER_CHAR_TO_DRIVE_CAR: + case COMMAND_ADD_PATROL_POINT: + case COMMAND_IS_PLAYER_IN_GANGZONE: + */ + case COMMAND_IS_PLAYER_IN_ZONE: + { + CollectParameters(&m_nIp, 1); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + int zoneToCheck = CTheZones::FindZoneByLabelAndReturnIndex(label); + if (zoneToCheck != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; /* why only if zone != -1? */ + CVector pos = pPlayer->GetPos(); + CZone* pZone = CTheZones::GetZone(zoneToCheck); + UpdateCompareFlag(CTheZones::PointLiesWithinZone(&pos, pZone)); + return 0; + } + case COMMAND_IS_PLAYER_PRESSING_HORN: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_pPed->GetPedState() == PED_DRIVING && + CPad::GetPad(ScriptParams[0])->GetHorn()); + /* Is it correct that same parameter is used both as index of Players */ + /* and as ID of pad? Pratically this parameter is always 0 anyway of course. */ + return 0; + case COMMAND_HAS_CHAR_SPOTTED_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->OurPedCanSeeThisOne(CWorld::Players[ScriptParams[1]].m_pPed)); + return 0; + } + /* Not implemented. + case COMMAND_ORDER_CHAR_TO_BACKDOOR: + case COMMAND_ADD_CHAR_TO_GANG: + */ + case COMMAND_IS_CHAR_OBJECTIVE_PASSED: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bScriptObjectiveCompleted); + return 0; + } + /* Not implemented. + case COMMAND_SET_CHAR_DRIVE_AGGRESSION: + case COMMAND_SET_CHAR_MAX_DRIVESPEED: + */ + case COMMAND_CREATE_CHAR_INSIDE_CAR: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + switch (ScriptParams[2]) { + case MI_COP: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_STREET; + break; + case MI_SWAT: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_SWAT; + break; + case MI_FBI: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_FBI; + break; + case MI_ARMY: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_ARMY; + break; + case MI_MEDIC: + if (ScriptParams[1] == PEDTYPE_EMERGENCY) + ScriptParams[2] = PEDTYPE_EMERGENCY; + break; + case MI_FIREMAN: + if (ScriptParams[1] == PEDTYPE_FIREMAN) + ScriptParams[2] = PEDTYPE_FIREMAN; + break; + default: + break; + } + CPed* pPed; + if (ScriptParams[1] == PEDTYPE_COP) + pPed = new CCopPed((eCopType)ScriptParams[2]); + else if (ScriptParams[1] == PEDTYPE_EMERGENCY || ScriptParams[1] == PEDTYPE_FIREMAN) + pPed = new CEmergencyPed(ScriptParams[2]); + else + pPed = new CCivilianPed((ePedType)ScriptParams[1], ScriptParams[2]); + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + pPed->bAllowMedicsToReviveMe = false; + pPed->SetPosition(pVehicle->GetPosition()); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + pPed->SetPedState(PED_DRIVING); + CPopulation::ms_nTotalMissionPeds++; + script_assert(!pVehicle->pDriver); + pVehicle->pDriver = pPed; + pVehicle->pDriver->RegisterReference((CEntity**)&pVehicle->pDriver); + pPed->m_pMyVehicle = pVehicle; + pPed->m_pMyVehicle->RegisterReference((CEntity**)&pPed->m_pMyVehicle); + pPed->bInVehicle = true; + pVehicle->SetStatus(STATUS_PHYSICS); + if (!pVehicle->IsBoat()) + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->bEngineOn = true; + pPed->bUsesCollision = false; +#ifdef FIX_BUGS + AnimationId anim = pVehicle->GetDriverAnim(); +#else + AnimationId anim = pVehicle->bLowVehicle ? ANIM_STD_CAR_SIT_LO : ANIM_STD_CAR_SIT; +#endif + pPed->m_pVehicleAnim = CAnimManager::BlendAnimation(pPed->GetClump(), ASSOCGRP_STD, anim, 100.0f); + pPed->StopNonPartialAnims(); + pPed->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pPed->GetPosition()); + CWorld::Add(pPed); + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pPed); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_WARP_PLAYER_FROM_CAR_TO_COORD: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[1]; + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + if (pPlayer->m_pPed->bInVehicle){ + script_assert(pPlayer->m_pPed->m_pMyVehicle); + if (pPlayer->m_pPed->m_pMyVehicle->bIsBus) + pPlayer->m_pPed->bRenderPedInCar = true; + if (pPlayer->m_pPed->m_pMyVehicle->pDriver == pPlayer->m_pPed){ + pPlayer->m_pPed->m_pMyVehicle->RemoveDriver(); + pPlayer->m_pPed->m_pMyVehicle->SetStatus(STATUS_ABANDONED); + pPlayer->m_pPed->m_pMyVehicle->bEngineOn = false; + pPlayer->m_pPed->m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + }else{ + pPlayer->m_pPed->m_pMyVehicle->RemovePassenger(pPlayer->m_pPed); + } + } + pPlayer->m_pPed->bInVehicle = false; + pPlayer->m_pPed->m_pMyVehicle = nil; + pPlayer->m_pPed->SetPedState(PED_IDLE); + pPlayer->m_pPed->bUsesCollision = true; + pPlayer->m_pPed->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pPlayer->m_pPed->AddWeaponModel(CWeaponInfo::GetWeaponInfo(pPlayer->m_pPed->GetWeapon()->m_eWeaponType)->m_nModelId); + pPlayer->m_pPed->RemoveInCarAnims(); + if (pPlayer->m_pPed->m_pVehicleAnim) + pPlayer->m_pPed->m_pVehicleAnim->blendDelta = -1000.0f; + pPlayer->m_pPed->m_pVehicleAnim = nil; + pPlayer->m_pPed->SetMoveState(PEDMOVE_NONE); + CAnimManager::BlendAnimation(pPlayer->m_pPed->GetClump(), pPlayer->m_pPed->m_animGroup, ANIM_STD_IDLE, 100.0f); + pPlayer->m_pPed->RestartNonPartialAnims(); + AudioManager.PlayerJustLeftCar(); + pos.z += pPlayer->m_pPed->GetDistanceFromCentreOfMassToBaseOfModel(); + pPlayer->m_pPed->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, pPlayer->m_pPed); + return 0; + } + /* Not implemented. + case COMMAND_MAKE_CHAR_DO_NOTHING: + */ + default: + script_assert(0); + break; + } + return -1; +} + +#ifdef MISSION_REPLAY + +bool CRunningScript::CanAllowMissionReplay() +{ + if (AllowMissionReplay != MISSION_RETRY_STAGE_NORMAL) + return false; + if (CStats::LastMissionPassedName[0] == '\0') + return false; + for (int i = 0; i < ARRAY_SIZE(nonMissionScripts); i++) { + if (strcmp(m_abScriptName, nonMissionScripts[i]) == 0) + return false; + } + return true; +} + +uint32 AddExtraDeathDelay() +{ + if (missionRetryScriptIndex == 63) + return 7000; + if (missionRetryScriptIndex == 64) + return 4000; + return 1000; +} + +void RetryMission(int type, int unk) +{ + if (type == MISSION_RETRY_TYPE_SUGGEST_TO_PLAYER) { + doingMissionRetry = true; + FrontEndMenuManager.m_nCurrScreen = MENUPAGE_MISSION_RETRY; + FrontEndMenuManager.RequestFrontEndStartUp(); + } + else if (type == MISSION_RETRY_TYPE_BEGIN_RESTARTING) { + doingMissionRetry = false; + AllowMissionReplay = MISSION_RETRY_STAGE_START_RESTARTING; + CTheScripts::MissionCleanUp.Process(); + } +} + +#endif diff --git a/src/control/Script.h b/src/control/Script.h new file mode 100644 index 0000000..d14abe9 --- /dev/null +++ b/src/control/Script.h @@ -0,0 +1,630 @@ +#pragma once +#include "Font.h" +#include "PedType.h" +#include "Text.h" +#include "Sprite2d.h" + +class CEntity; +class CBuilding; +class CVehicle; +class CPed; +class CObject; +class CPlayerInfo; + +class CRunningScript; + +extern int32 ScriptParams[32]; + +void FlushLog(); +#define script_assert(_Expression) FlushLog(); assert(_Expression); + +#define PICKUP_PLACEMENT_OFFSET (0.5f) +#define PED_FIND_Z_OFFSET (5.0f) + +#define UPSIDEDOWN_UP_THRESHOLD (-0.97f) +#define UPSIDEDOWN_MOVE_SPEED_THRESHOLD (0.01f) +#define UPSIDEDOWN_TURN_SPEED_THRESHOLD (0.02f) +#define UPSIDEDOWN_TIMER_THRESHOLD (1000) + +#define SPHERE_MARKER_R (0) +#define SPHERE_MARKER_G (128) +#define SPHERE_MARKER_B (255) +#define SPHERE_MARKER_A (128) +#define SPHERE_MARKER_PULSE_PERIOD (2048) +#define SPHERE_MARKER_PULSE_FRACTION (0.1f) + +#ifdef USE_PRECISE_MEASUREMENT_CONVERTION +#define MILES_IN_METER (0.000621371192f) +#define METERS_IN_FOOT (0.3048f) +#define FEET_IN_METER (3.28084f) +#else +#define MILES_IN_METER (1 / 1670.f) +#define METERS_IN_FOOT (0.3f) +#define FEET_IN_METER (3.33f) +#endif + +#define KEY_LENGTH_IN_SCRIPT (8) + +#if GTA_VERSION <= GTA3_PS2_160 +#define GTA_SCRIPT_COLLECTIVE +#endif + +struct intro_script_rectangle +{ + bool m_bIsUsed; + bool m_bBeforeFade; + int16 m_nTextureId; + CRect m_sRect; + CRGBA m_sColor; + + intro_script_rectangle() { } + ~intro_script_rectangle() { } +}; + +VALIDATE_SIZE(intro_script_rectangle, 0x18); + +enum { + SCRIPT_TEXT_MAX_LENGTH = 500 +}; + +struct intro_text_line +{ + float m_fScaleX; + float m_fScaleY; + CRGBA m_sColor; + bool m_bJustify; + bool m_bCentered; + bool m_bBackground; + bool m_bBackgroundOnly; + float m_fWrapX; + float m_fCenterSize; + CRGBA m_sBackgroundColor; + bool m_bTextProportional; + bool m_bTextBeforeFade; + bool m_bRightJustify; + int32 m_nFont; + float m_fAtX; + float m_fAtY; + wchar m_Text[SCRIPT_TEXT_MAX_LENGTH]; + + intro_text_line() { } + ~intro_text_line() { } + + void Reset() + { + m_fScaleX = 0.48f; + m_fScaleY = 1.12f; + m_sColor = CRGBA(225, 225, 225, 255); + m_bJustify = false; + m_bRightJustify = false; + m_bCentered = false; + m_bBackground = false; + m_bBackgroundOnly = false; + m_fWrapX = 182.0f; + m_fCenterSize = DEFAULT_SCREEN_WIDTH; + m_sBackgroundColor = CRGBA(128, 128, 128, 128); + m_bTextProportional = true; + m_bTextBeforeFade = false; + m_nFont = FONT_HEADING; + m_fAtX = 0.0f; + m_fAtY = 0.0f; + memset(&m_Text, 0, sizeof(m_Text)); + } +}; + +VALIDATE_SIZE(intro_text_line, 0x414); + +struct script_sphere_struct +{ + bool m_bInUse; + uint16 m_Index; + uint32 m_Id; + CVector m_vecCenter; + float m_fRadius; + + script_sphere_struct() { } +}; + +struct CStoredLine +{ + CVector vecInf; + CVector vecSup; + uint32 color1; + uint32 color2; +}; + +enum { + CLEANUP_UNUSED = 0, + CLEANUP_CAR, + CLEANUP_CHAR, + CLEANUP_OBJECT +}; + +struct cleanup_entity_struct +{ + uint8 type; + int32 id; +}; + +enum { + MAX_CLEANUP = 50, + MAX_UPSIDEDOWN_CAR_CHECKS = 6, + MAX_STUCK_CAR_CHECKS = 6 +}; + +class CMissionCleanup +{ +public: + cleanup_entity_struct m_sEntities[MAX_CLEANUP]; + uint8 m_nCount; + + CMissionCleanup(); + + void Init(); + cleanup_entity_struct* FindFree(); + void AddEntityToList(int32, uint8); + void RemoveEntityFromList(int32, uint8); + void Process(); +}; + +struct upsidedown_car_data +{ + int32 m_nVehicleIndex; + uint32 m_nUpsideDownTimer; +}; + +class CUpsideDownCarCheck +{ + upsidedown_car_data m_sCars[MAX_UPSIDEDOWN_CAR_CHECKS]; + +public: + void Init(); + bool IsCarUpsideDown(int32); + bool IsCarUpsideDown(CVehicle*); + void UpdateTimers(); + bool AreAnyCarsUpsideDown(); + void AddCarToCheck(int32); + void RemoveCarFromCheck(int32); + bool HasCarBeenUpsideDownForAWhile(int32); +}; + +struct stuck_car_data +{ + int32 m_nVehicleIndex; + CVector m_vecPos; + int32 m_nLastCheck; + float m_fRadius; + uint32 m_nStuckTime; + bool m_bStuck; + + stuck_car_data() { } + void Reset(); +}; + +class CStuckCarCheck +{ + stuck_car_data m_sCars[MAX_STUCK_CAR_CHECKS]; + +public: + void Init(); + void Process(); + void AddCarToCheck(int32, float, uint32); + void RemoveCarFromCheck(int32); + bool HasCarBeenStuckForAWhile(int32); +}; + +enum { + ARGUMENT_END = 0, + ARGUMENT_INT32, + ARGUMENT_GLOBALVAR, + ARGUMENT_LOCALVAR, + ARGUMENT_INT8, + ARGUMENT_INT16, + ARGUMENT_FLOAT +}; + +struct tCollectiveData +{ + int32 colIndex; + int32 pedIndex; +}; + +enum { + USED_OBJECT_NAME_LENGTH = 24 +}; + +struct tUsedObject +{ + char name[USED_OBJECT_NAME_LENGTH]; + int32 index; +}; + +struct tBuildingSwap +{ + CBuilding* m_pBuilding; + int32 m_nNewModel; + int32 m_nOldModel; +}; + + +enum { +#if GTA_VERSION > GTA3_PS2_160 + MAX_STACK_DEPTH = 6, +#else + MAX_STACK_DEPTH = 4, +#endif + NUM_LOCAL_VARS = 16, + NUM_TIMERS = 2 +}; + +class CRunningScript +{ + enum { + ANDOR_NONE = 0, + ANDS_1 = 1, + ANDS_2, + ANDS_3, + ANDS_4, + ANDS_5, + ANDS_6, + ANDS_7, + ANDS_8, + ORS_1 = 21, + ORS_2, + ORS_3, + ORS_4, + ORS_5, + ORS_6, + ORS_7, + ORS_8 + }; + +public: + CRunningScript* next; + CRunningScript* prev; + char m_abScriptName[8]; + uint32 m_nIp; + uint32 m_anStack[MAX_STACK_DEPTH]; + uint16 m_nStackPointer; + int32 m_anLocalVariables[NUM_LOCAL_VARS + NUM_TIMERS]; + bool m_bCondResult; + bool m_bIsMissionScript; + bool m_bSkipWakeTime; + uint32 m_nWakeTime; + uint16 m_nAndOrState; + bool m_bNotFlag; + bool m_bDeatharrestEnabled; + bool m_bDeatharrestExecuted; + bool m_bMissionFlag; + +public: + void SetIP(uint32 ip) { m_nIp = ip; } + CRunningScript* GetNext() const { return next; } + + void Save(uint8*& buf); + void Load(uint8*& buf); + + void UpdateTimers(float timeStep) { + m_anLocalVariables[NUM_LOCAL_VARS] += timeStep; + m_anLocalVariables[NUM_LOCAL_VARS + 1] += timeStep; + } + + void Init(); + void Process(); + + void RemoveScriptFromList(CRunningScript**); + void AddScriptToList(CRunningScript**); + + static const uint32 nSaveStructSize; + + void CollectParameters(uint32*, int16); + int32 CollectNextParameterWithoutIncreasingPC(uint32); + int32* GetPointerToScriptVariable(uint32*, int16); + void StoreParameters(uint32*, int16); + + int8 ProcessOneCommand(); + void DoDeatharrestCheck(); + void UpdateCompareFlag(bool); + int16 GetPadState(uint16, uint16); + + int8 ProcessCommands0To99(int32); + int8 ProcessCommands100To199(int32); + int8 ProcessCommands200To299(int32); + int8 ProcessCommands300To399(int32); + int8 ProcessCommands400To499(int32); + int8 ProcessCommands500To599(int32); + int8 ProcessCommands600To699(int32); + int8 ProcessCommands700To799(int32); + int8 ProcessCommands800To899(int32); + int8 ProcessCommands900To999(int32); + int8 ProcessCommands1000To1099(int32); +#if GTA_VERSION > GTA3_PS2_160 + int8 ProcessCommands1100To1199(int32); +#endif + void LocatePlayerCommand(int32, uint32*); + void LocatePlayerCharCommand(int32, uint32*); + void LocatePlayerCarCommand(int32, uint32*); + void LocateCharCommand(int32, uint32*); + void LocateCharCharCommand(int32, uint32*); + void LocateCharCarCommand(int32, uint32*); +#if GTA_VERSION > GTA3_PS2_160 + void LocateCharObjectCommand(int32, uint32*); +#endif + void LocateCarCommand(int32, uint32*); +#if GTA_VERSION > GTA3_PS2_160 + void LocateSniperBulletCommand(int32, uint32*); +#endif + void PlayerInAreaCheckCommand(int32, uint32*); + void PlayerInAngledAreaCheckCommand(int32, uint32*); + void CharInAreaCheckCommand(int32, uint32*); + void CarInAreaCheckCommand(int32, uint32*); + +#ifdef GTA_SCRIPT_COLLECTIVE + void LocateCollectiveCommand(int32, uint32*); + void LocateCollectiveCharCommand(int32, uint32*); + void LocateCollectiveCarCommand(int32, uint32*); + void LocateCollectivePlayerCommand(int32, uint32*); + void CollectiveInAreaCheckCommand(int32, uint32*); +#endif + +#ifdef MISSION_REPLAY + bool CanAllowMissionReplay(); +#endif + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + int CollectParameterForDebug(char* buf, bool& var); + void GetStoredParameterForDebug(char* buf); + void LogOnStartProcessing(); + void LogBeforeProcessingCommand(int32 command); + void LogAfterProcessingCommand(int32 command); + + static char commandInfo[]; + static uint32 storedIp; + +#endif + + float LimitAngleOnCircle(float angle) { return angle < 0.0f ? angle + 360.0f : angle; } + + bool ThisIsAValidRandomPed(uint32 pedtype) { + switch (pedtype) { + case PEDTYPE_CIVMALE: + case PEDTYPE_CIVFEMALE: + case PEDTYPE_GANG1: + case PEDTYPE_GANG2: + case PEDTYPE_GANG3: + case PEDTYPE_GANG4: + case PEDTYPE_GANG5: + case PEDTYPE_GANG6: + case PEDTYPE_GANG7: + case PEDTYPE_GANG8: + case PEDTYPE_GANG9: + case PEDTYPE_CRIMINAL: + case PEDTYPE_PROSTITUTE: + return true; + default: + return false; + } + } +}; + + +enum { + VAR_LOCAL = 1, + VAR_GLOBAL = 2, +}; + +enum { + SIZE_MAIN_SCRIPT = 128 * 1024, + SIZE_MISSION_SCRIPT = 32 * 1024, + SIZE_SCRIPT_SPACE = SIZE_MAIN_SCRIPT + SIZE_MISSION_SCRIPT +}; + +enum { + MAX_NUM_SCRIPTS = 128, + MAX_NUM_CONTACTS = 16, + MAX_NUM_INTRO_TEXT_LINES = 2, + MAX_NUM_INTRO_RECTANGLES = 16, + MAX_NUM_SCRIPT_SRPITES = 16, + MAX_NUM_SCRIPT_SPHERES = 16, + MAX_NUM_COLLECTIVES = 32, + MAX_NUM_USED_OBJECTS = 200, + MAX_NUM_MISSION_SCRIPTS = 120, + MAX_NUM_BUILDING_SWAPS = 25, + MAX_NUM_INVISIBILITY_SETTINGS = 20, + MAX_NUM_STORED_LINES = 1024 +}; + +class CTheScripts +{ +public: + static uint8 ScriptSpace[SIZE_SCRIPT_SPACE]; + static CRunningScript ScriptsArray[MAX_NUM_SCRIPTS]; + static int32 BaseBriefIdForContact[MAX_NUM_CONTACTS]; + static int32 OnAMissionForContactFlag[MAX_NUM_CONTACTS]; + static intro_text_line IntroTextLines[MAX_NUM_INTRO_TEXT_LINES]; + static intro_script_rectangle IntroRectangles[MAX_NUM_INTRO_RECTANGLES]; + static CSprite2d ScriptSprites[MAX_NUM_SCRIPT_SRPITES]; + static script_sphere_struct ScriptSphereArray[MAX_NUM_SCRIPT_SPHERES]; + static tCollectiveData CollectiveArray[MAX_NUM_COLLECTIVES]; + static tUsedObject UsedObjectArray[MAX_NUM_USED_OBJECTS]; + static int32 MultiScriptArray[MAX_NUM_MISSION_SCRIPTS]; + static tBuildingSwap BuildingSwapArray[MAX_NUM_BUILDING_SWAPS]; + static CEntity* InvisibilitySettingArray[MAX_NUM_INVISIBILITY_SETTINGS]; + static CStoredLine aStoredLines[MAX_NUM_STORED_LINES]; + static bool DbgFlag; + static uint32 OnAMissionFlag; + static CMissionCleanup MissionCleanUp; + static CStuckCarCheck StuckCars; + static CUpsideDownCarCheck UpsideDownCars; + static int32 StoreVehicleIndex; + static bool StoreVehicleWasRandom; + static CRunningScript *pIdleScripts; + static CRunningScript *pActiveScripts; + static int32 NextFreeCollectiveIndex; + static int32 LastRandomPedId; + static uint16 NumberOfUsedObjects; + static bool bAlreadyRunningAMissionScript; + static bool bUsingAMultiScriptFile; + static uint16 NumberOfMissionScripts; + static uint32 LargestMissionScriptSize; + static uint32 MainScriptSize; + static uint8 FailCurrentMission; + static uint8 CountdownToMakePlayerUnsafe; + static uint8 DelayMakingPlayerUnsafeThisTime; + static uint16 NumScriptDebugLines; + static uint16 NumberOfIntroRectanglesThisFrame; + static uint16 NumberOfIntroTextLinesThisFrame; + static uint8 UseTextCommands; + static uint16 CommandsExecuted; + static uint16 ScriptsUpdated; + + static void Init(); + static void Process(); + + static CRunningScript* StartTestScript(); + static bool IsPlayerOnAMission(); + static void ClearSpaceForMissionEntity(const CVector&, CEntity*); + + static void UndoBuildingSwaps(); + static void UndoEntityInvisibilitySettings(); + + static void ScriptDebugLine3D(float x1, float y1, float z1, float x2, float y2, float z2, uint32 col, uint32 col2); + static void RenderTheScriptDebugLines(); + + static void SaveAllScripts(uint8*, uint32*); + static void LoadAllScripts(uint8*, uint32); + + static bool IsDebugOn() { return DbgFlag; }; + static void InvertDebugFlag() { DbgFlag = !DbgFlag; } + + static int32* GetPointerToScriptVariable(int32 offset) { assert(offset >= 8 && offset < CTheScripts::GetSizeOfVariableSpace()); return (int32*)&ScriptSpace[offset]; } + + static void ResetCountdownToMakePlayerUnsafe() { CountdownToMakePlayerUnsafe = 0; } + static bool IsCountdownToMakePlayerUnsafeOn() { return CountdownToMakePlayerUnsafe != 0; } + + static int32 Read4BytesFromScript(uint32* pIp) { + int32 retval = ScriptSpace[*pIp + 3] << 24 | ScriptSpace[*pIp + 2] << 16 | ScriptSpace[*pIp + 1] << 8 | ScriptSpace[*pIp]; + *pIp += 4; + return retval; + } + static int16 Read2BytesFromScript(uint32* pIp) { + int16 retval = ScriptSpace[*pIp + 1] << 8 | ScriptSpace[*pIp]; + *pIp += 2; + return retval; + } + static int8 Read1ByteFromScript(uint32* pIp) { + int8 retval = ScriptSpace[*pIp]; + *pIp += 1; + return retval; + } + static float ReadFloatFromScript(uint32* pIp) { + return Read2BytesFromScript(pIp) / 16.0f; + } + static void ReadTextLabelFromScript(uint32* pIp, char* buf) { + strncpy(buf, (const char*)&CTheScripts::ScriptSpace[*pIp], KEY_LENGTH_IN_SCRIPT); + } + static wchar* GetTextByKeyFromScript(uint32* pIp) { + wchar* text = TheText.Get((const char*)&CTheScripts::ScriptSpace[*pIp]); + *pIp += KEY_LENGTH_IN_SCRIPT; + return text; + } + static int32 GetSizeOfVariableSpace() + { + uint32 tmp = 3; + return Read4BytesFromScript(&tmp); + } + + static CRunningScript* StartNewScript(uint32); + + static void CleanUpThisVehicle(CVehicle*); + static void CleanUpThisPed(CPed*); + static void CleanUpThisObject(CObject*); + + static bool IsPedStopped(CPed*); + static bool IsPlayerStopped(CPlayerInfo*); + static bool IsVehicleStopped(CVehicle*); + + static void PrintListSizes(); + static void ReadObjectNamesFromScript(); + static void UpdateObjectIndices(); + static void ReadMultiScriptFileOffsetsFromScript(); + static void DrawScriptSpheres(); + static void HighlightImportantArea(uint32, float, float, float, float, float); + static void HighlightImportantAngledArea(uint32, float, float, float, float, float, float, float, float, float); + static void DrawDebugSquare(float, float, float, float); + static void DrawDebugAngledSquare(float, float, float, float, float, float, float, float); + static void DrawDebugCube(float, float, float, float, float, float); + static void DrawDebugAngledCube(float, float, float, float, float, float, float, float, float, float); + + static void AddToInvisibilitySwapArray(CEntity*, bool); + static void AddToBuildingSwapArray(CBuilding*, int32, int32); + + static int32 GetActualScriptSphereIndex(int32 index); + static int32 AddScriptSphere(int32 id, CVector pos, float radius); + static int32 GetNewUniqueScriptSphereIndex(int32 index); + static void RemoveScriptSphere(int32 index); + +#ifdef GTA_SCRIPT_COLLECTIVE + static void AdvanceCollectiveIndex() + { + if (NextFreeCollectiveIndex == INT32_MAX) + NextFreeCollectiveIndex = 0; + else + NextFreeCollectiveIndex++; + } + + static int AddPedsInVehicleToCollective(int); + static int AddPedsInAreaToCollective(float, float, float, float); + static int FindFreeSlotInCollectiveArray(); + static void SetObjectiveForAllPedsInCollective(int, eObjective, int16, int16); + static void SetObjectiveForAllPedsInCollective(int, eObjective, CVector, float); + static void SetObjectiveForAllPedsInCollective(int, eObjective, CVector); + static void SetObjectiveForAllPedsInCollective(int, eObjective, void*); + static void SetObjectiveForAllPedsInCollective(int, eObjective); +#endif + +#ifdef MISSION_SWITCHER +public: + static void SwitchToMission(int32 mission); +#endif + +#ifdef USE_DEBUG_SCRIPT_LOADER + static int ScriptToLoad; + static int OpenScript(); +#endif + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + static void LogAfterScriptInitializing(); + static void LogBeforeScriptProcessing(); + static void LogAfterScriptProcessing(); +#endif +}; + +#ifdef MISSION_REPLAY +extern int AllowMissionReplay; +extern uint32 WaitForMissionActivate; +extern uint32 WaitForSave; +extern uint32 MissionStartTime; +extern int missionRetryScriptIndex; +extern bool doingMissionRetry; + +uint32 AddExtraDeathDelay(); +void RetryMission(int, int unk = 0); + +enum { + MISSION_RETRY_TYPE_SUGGEST_TO_PLAYER = 0, + MISSION_RETRY_TYPE_1, + MISSION_RETRY_TYPE_BEGIN_RESTARTING +}; + +enum { + MISSION_RETRY_STAGE_NORMAL = 0, + MISSION_RETRY_STAGE_WAIT_FOR_SCRIPT_TO_TERMINATE, + MISSION_RETRY_STAGE_START_PROCESSING, + MISSION_RETRY_STAGE_WAIT_FOR_DELAY, + MISSION_RETRY_STAGE_WAIT_FOR_MENU, + MISSION_RETRY_STAGE_WAIT_FOR_USER, + MISSION_RETRY_STAGE_START_RESTARTING, + MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART, +}; +#endif diff --git a/src/control/Script2.cpp b/src/control/Script2.cpp new file mode 100644 index 0000000..d3ab2af --- /dev/null +++ b/src/control/Script2.cpp @@ -0,0 +1,1555 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "Camera.h" +#include "CarCtrl.h" +#include "CarGen.h" +#include "CivilianPed.h" +#include "CopPed.h" +#include "Cranes.h" +#include "DMAudio.h" +#include "EmergencyPed.h" +#include "Garages.h" +#include "General.h" +#include "Messages.h" +#include "Pad.h" +#include "PedRoutes.h" +#include "Pools.h" +#include "Population.h" +#include "Radar.h" +#include "Restart.h" +#include "Shadows.h" +#include "User.h" +#include "Wanted.h" +#include "WaterLevel.h" +#include "Weather.h" +#include "World.h" +#include "Zones.h" + +int8 CRunningScript::ProcessCommands300To399(int32 command) +{ + switch (command) { + /* Not implemented. + case COMMAND_SET_CHAR_INVINCIBLE: + case COMMAND_SET_PLAYER_INVINCIBLE: + case COMMAND_SET_CHAR_GRAPHIC_TYPE: + case COMMAND_SET_PLAYER_GRAPHIC_TYPE: + */ + case COMMAND_HAS_PLAYER_BEEN_ARRESTED: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_WBState == WBSTATE_BUSTED); + return 0; + /* Not implemented. + case COMMAND_STOP_CHAR_DRIVING: + case COMMAND_KILL_CHAR: + case COMMAND_SET_FAVOURITE_CAR_MODEL_FOR_CHAR: + case COMMAND_SET_CHAR_OCCUPATION: + */ + case COMMAND_CHANGE_CAR_LOCK: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->m_nDoorLock = (eCarLock)ScriptParams[1]; + return 0; + } + case COMMAND_SHAKE_CAM_WITH_POINT: + CollectParameters(&m_nIp, 4); + TheCamera.CamShake(ScriptParams[0] / 1000.0f, + *(float*)&ScriptParams[1], + *(float*)&ScriptParams[2], + *(float*)&ScriptParams[3]); + return 0; + case COMMAND_IS_CAR_MODEL: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->GetModelIndex() == ScriptParams[1]); + return 0; + } + /* Not implemented. + case COMMAND_IS_CAR_REMAP: + case COMMAND_HAS_CAR_JUST_SUNK: + case COMMAND_SET_CAR_NO_COLLIDE: + */ + case COMMAND_IS_CAR_DEAD_IN_AREA_2D: + { + CollectParameters(&m_nIp, 6); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + UpdateCompareFlag(pVehicle->GetStatus() == STATUS_WRECKED && + pVehicle->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_CAR_DEAD_IN_AREA_3D: + { + CollectParameters(&m_nIp, 8); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float z1 = *(float*)&ScriptParams[3]; + float x2 = *(float*)&ScriptParams[4]; + float y2 = *(float*)&ScriptParams[5]; + float z2 = *(float*)&ScriptParams[6]; + UpdateCompareFlag(pVehicle->GetStatus() == STATUS_WRECKED && + pVehicle->IsWithinArea(x1, y1, z1, x2, y2, z2)); + if (ScriptParams[7]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, (z1 + z2) / 2); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(x1, y1, z1, x2, y2, z2); + return 0; + } + /* Not implemented. + case COMMAND_IS_TRAILER_ATTACHED: + case COMMAND_IS_CAR_ON_TRAILER: + case COMMAND_HAS_CAR_GOT_WEAPON: + case COMMAND_PARK: + case COMMAND_HAS_PARK_FINISHED: + case COMMAND_KILL_ALL_PASSENGERS: + case COMMAND_SET_CAR_BULLETPROOF: + case COMMAND_SET_CAR_FLAMEPROOF: + case COMMAND_SET_CAR_ROCKETPROOF: + case COMMAND_IS_CARBOMB_ACTIVE: + case COMMAND_GIVE_CAR_ALARM: + case COMMAND_PUT_CAR_ON_TRAILER: + */ + case COMMAND_IS_CAR_CRUSHED: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CGarages::HasCarBeenCrushed(ScriptParams[0])); + return 0; + /* Not implemented. + case COMMAND_CREATE_GANG_CAR: + */ + case COMMAND_CREATE_CAR_GENERATOR: + CollectParameters(&m_nIp, 12); + ScriptParams[0] = CTheCarGenerators::CreateCarGenerator( + *(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], *(float*)&ScriptParams[3], + ScriptParams[4], ScriptParams[5], ScriptParams[6], ScriptParams[7], + ScriptParams[8], ScriptParams[9], ScriptParams[10], ScriptParams[11]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_SWITCH_CAR_GENERATOR: + { + CollectParameters(&m_nIp, 2); + CCarGenerator* pCarGen = &CTheCarGenerators::CarGeneratorArray[ScriptParams[0]]; + if (ScriptParams[1] == 0){ + pCarGen->SwitchOff(); + }else if (ScriptParams[1] <= 100){ + pCarGen->SwitchOn(); + pCarGen->SetUsesRemaining(ScriptParams[1]); + }else{ + pCarGen->SwitchOn(); + } + return 0; + } + case COMMAND_ADD_PAGER_MESSAGE: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 3); + CUserDisplay::Pager.AddMessage(text, ScriptParams[0], ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_DISPLAY_ONSCREEN_TIMER: + { + script_assert(CTheScripts::ScriptSpace[m_nIp] == ARGUMENT_GLOBALVAR); + m_nIp++; + CUserDisplay::OnscnTimer.AddClock((uint16)CTheScripts::Read2BytesFromScript(&m_nIp), nil); + return 0; + } + case COMMAND_CLEAR_ONSCREEN_TIMER: + { + script_assert(CTheScripts::ScriptSpace[m_nIp] == ARGUMENT_GLOBALVAR); + m_nIp++; + CUserDisplay::OnscnTimer.ClearClock((uint16)CTheScripts::Read2BytesFromScript(&m_nIp)); + return 0; + } + case COMMAND_DISPLAY_ONSCREEN_COUNTER: + { + script_assert(CTheScripts::ScriptSpace[m_nIp] == ARGUMENT_GLOBALVAR); + m_nIp++; + uint16 counter = CTheScripts::Read2BytesFromScript(&m_nIp); + CollectParameters(&m_nIp, 1); + CUserDisplay::OnscnTimer.AddCounter(counter, ScriptParams[0], nil); + return 0; + } + case COMMAND_CLEAR_ONSCREEN_COUNTER: + { + script_assert(CTheScripts::ScriptSpace[m_nIp] == ARGUMENT_GLOBALVAR); + m_nIp++; + CUserDisplay::OnscnTimer.ClearCounter((uint16)CTheScripts::Read2BytesFromScript(&m_nIp)); + return 0; + } + case COMMAND_SET_ZONE_CAR_INFO: + { + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 16); + int zone = CTheZones::FindZoneByLabelAndReturnIndex(label); + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + CTheZones::SetZoneCarInfo(zone, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], + ScriptParams[4], ScriptParams[5], ScriptParams[6], ScriptParams[7], ScriptParams[8], 0, 0, + ScriptParams[9], ScriptParams[10], ScriptParams[11], ScriptParams[12], + ScriptParams[13], ScriptParams[14], ScriptParams[15]); + return 0; + } + /* Not implemented. + case COMMAND_IS_CHAR_IN_GANG_ZONE: + */ + case COMMAND_IS_CHAR_IN_ZONE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + int zone = CTheZones::FindZoneByLabelAndReturnIndex(label); + if (zone != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + UpdateCompareFlag(CTheZones::PointLiesWithinZone(&pos, CTheZones::GetZone(zone))); + return 0; + } + case COMMAND_SET_CAR_DENSITY: + { + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + int16 zone = CTheZones::FindZoneByLabelAndReturnIndex(label); + m_nIp += 8; + CollectParameters(&m_nIp, 2); + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + CTheZones::SetCarDensity(zone, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_SET_PED_DENSITY: + { + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + int16 zone = CTheZones::FindZoneByLabelAndReturnIndex(label); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + CTheZones::SetPedDensity(zone, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_POINT_CAMERA_AT_PLAYER: + { + CollectParameters(&m_nIp, 3); + // ScriptParams[0] is unused. + TheCamera.TakeControl(nil, ScriptParams[1], ScriptParams[2], CAMCONTROL_SCRIPT); + return 0; + } + case COMMAND_POINT_CAMERA_AT_CAR: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + TheCamera.TakeControl(pVehicle, ScriptParams[1], ScriptParams[2], CAMCONTROL_SCRIPT); + return 0; + } + case COMMAND_POINT_CAMERA_AT_CHAR: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + TheCamera.TakeControl(pPed, ScriptParams[1], ScriptParams[2], CAMCONTROL_SCRIPT); + return 0; + } + case COMMAND_RESTORE_CAMERA: + TheCamera.Restore(); + return 0; + case COMMAND_SHAKE_PAD: + CPad::GetPad(ScriptParams[0])->StartShake(ScriptParams[1], ScriptParams[2]); + return 0; + case COMMAND_SET_ZONE_PED_INFO: + { + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 10); + int16 zone = CTheZones::FindZoneByLabelAndReturnIndex(label); + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + CTheZones::SetZonePedInfo(zone, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], + ScriptParams[4], ScriptParams[5], ScriptParams[6], ScriptParams[7], ScriptParams[8], 0, 0, ScriptParams[9]); + return 0; + } + case COMMAND_SET_TIME_SCALE: + CollectParameters(&m_nIp, 1); + CTimer::SetTimeScale(*(float*)&ScriptParams[0]); + return 0; + case COMMAND_IS_CAR_IN_AIR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle && pVehicle->IsCar()); + CAutomobile* pCar = (CAutomobile*)pVehicle; + UpdateCompareFlag(pCar->GetAllWheelsOffGround()); + return 0; + } + case COMMAND_SET_FIXED_CAMERA_POSITION: + { + CollectParameters(&m_nIp, 6); + TheCamera.SetCamPositionForFixedMode( + CVector(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2]), + CVector(*(float*)&ScriptParams[3], *(float*)&ScriptParams[4], *(float*)&ScriptParams[5])); + return 0; + } + case COMMAND_POINT_CAMERA_AT_POINT: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + TheCamera.TakeControlNoEntity(pos, ScriptParams[3], CAMCONTROL_SCRIPT); + return 0; + } + case COMMAND_ADD_BLIP_FOR_CAR_OLD: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CRadar::SetEntityBlip(BLIP_CAR, ScriptParams[0], ScriptParams[1], (eBlipDisplay)ScriptParams[2]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_CHAR_OLD: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CRadar::SetEntityBlip(BLIP_CHAR, ScriptParams[0], ScriptParams[1], (eBlipDisplay)ScriptParams[2]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_OBJECT_OLD: + { + CollectParameters(&m_nIp, 3); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CRadar::SetEntityBlip(BLIP_OBJECT, ScriptParams[0], ScriptParams[1], (eBlipDisplay)ScriptParams[2]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_REMOVE_BLIP: + CollectParameters(&m_nIp, 1); + CRadar::ClearBlip(ScriptParams[0]); + return 0; + case COMMAND_CHANGE_BLIP_COLOUR: + CollectParameters(&m_nIp, 2); + CRadar::ChangeBlipColour(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_DIM_BLIP: + CollectParameters(&m_nIp, 2); + CRadar::ChangeBlipBrightness(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_ADD_BLIP_FOR_COORD_OLD: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + // Useless call + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CRadar::SetCoordBlip(BLIP_COORD, pos, ScriptParams[3], (eBlipDisplay)ScriptParams[4]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CHANGE_BLIP_SCALE: + CollectParameters(&m_nIp, 2); + CRadar::ChangeBlipScale(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_SET_FADING_COLOUR: + CollectParameters(&m_nIp, 3); + TheCamera.SetFadeColour(ScriptParams[0], ScriptParams[1], ScriptParams[2]); + return 0; + case COMMAND_DO_FADE: + CollectParameters(&m_nIp, 2); + TheCamera.Fade(ScriptParams[0] / 1000.0f, ScriptParams[1]); + return 0; + case COMMAND_GET_FADING_STATUS: + UpdateCompareFlag(TheCamera.GetFading()); + return 0; + case COMMAND_ADD_HOSPITAL_RESTART: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + float angle = *(float*)&ScriptParams[3]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRestart::AddHospitalRestartPoint(pos, angle); + return 0; + } + case COMMAND_ADD_POLICE_RESTART: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + float angle = *(float*)&ScriptParams[3]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRestart::AddPoliceRestartPoint(pos, angle); + return 0; + } + case COMMAND_OVERRIDE_NEXT_RESTART: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + float angle = *(float*)&ScriptParams[3]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRestart::OverrideNextRestart(pos, angle); + return 0; + } + case COMMAND_DRAW_SHADOW: + { + CollectParameters(&m_nIp, 10); + CVector pos = *(CVector*)&ScriptParams[1]; + float angle = *(float*)&ScriptParams[4]; + float length = *(float*)&ScriptParams[5]; + float x, y; + if (angle != 0.0f){ + y = Cos(angle) * length; + x = Sin(angle) * length; + }else{ + y = length; + x = 0.0f; + } + float frontX = -x; + float frontY = y; + float sideX = y; + float sideY = x; + /* Not very nicely named intermediate variables. */ + CShadows::StoreShadowToBeRendered(ScriptParams[0], &pos, frontX, frontY, sideX, sideY, + ScriptParams[6], ScriptParams[7], ScriptParams[8], ScriptParams[9]); + return 0; + } + case COMMAND_GET_PLAYER_HEADING: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + float angle = pPed->bInVehicle ? pPed->m_pMyVehicle->GetForward().Heading() : pPed->GetForward().Heading(); + *(float*)&ScriptParams[0] = CGeneral::LimitAngle(RADTODEG(angle)); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_PLAYER_HEADING: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + if (pPed->bInVehicle){ + // Is script_assertion required? + return 0; + } + pPed->m_fRotationDest = pPed->m_fRotationCur = DEGTORAD(*(float*)&ScriptParams[1]); + pPed->SetHeading(DEGTORAD(*(float*)&ScriptParams[1])); + return 0; + } + case COMMAND_GET_CHAR_HEADING: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float angle = pPed->bInVehicle ? pPed->m_pMyVehicle->GetForward().Heading() : pPed->GetForward().Heading(); + *(float*)&ScriptParams[0] = CGeneral::LimitAngle(RADTODEG(angle)); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_HEADING: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (pPed->bInVehicle) { + // Is script_assertion required? + return 0; + } + pPed->m_fRotationDest = pPed->m_fRotationCur = DEGTORAD(*(float*)&ScriptParams[1]); + pPed->SetHeading(DEGTORAD(*(float*)&ScriptParams[1])); + return 0; + } + case COMMAND_GET_CAR_HEADING: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + float angle = pVehicle->GetForward().Heading(); + *(float*)&ScriptParams[0] = CGeneral::LimitAngle(RADTODEG(angle)); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CAR_HEADING: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->SetHeading(DEGTORAD(*(float*)&ScriptParams[1])); + return 0; + } + case COMMAND_GET_OBJECT_HEADING: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + float angle = pObject->GetForward().Heading(); + *(float*)&ScriptParams[0] = CGeneral::LimitAngle(RADTODEG(angle)); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_OBJECT_HEADING: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CWorld::Remove(pObject); + pObject->SetHeading(DEGTORAD(*(float*)&ScriptParams[1])); + pObject->GetMatrix().UpdateRW(); + pObject->UpdateRwFrame(); + CWorld::Add(pObject); + return 0; + } + case COMMAND_IS_PLAYER_TOUCHING_OBJECT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + script_assert(pObject); + CPhysical* pEntityToTest = pPed->bInVehicle ? (CPhysical*)pPed->m_pMyVehicle : pPed; + UpdateCompareFlag(pEntityToTest->GetHasCollidedWith(pObject)); + return 0; + } + case COMMAND_IS_CHAR_TOUCHING_OBJECT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + script_assert(pObject); + CPhysical* pEntityToTest = pPed->bInVehicle ? (CPhysical*)pPed->m_pMyVehicle : pPed; + UpdateCompareFlag(pEntityToTest->GetHasCollidedWith(pObject)); + return 0; + } + case COMMAND_SET_PLAYER_AMMO: + { + CollectParameters(&m_nIp, 3); + CWorld::Players[0].m_pPed->SetAmmo((eWeaponType)ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_SET_CHAR_AMMO: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + pPed->SetAmmo((eWeaponType)ScriptParams[1], ScriptParams[2]); + return 0; + } + /* Not implemented. + case COMMAND_SET_CAR_AMMO: + case COMMAND_LOAD_CAMERA_SPLINE: + case COMMAND_MOVE_CAMERA_ALONG_SPLINE: + case COMMAND_GET_CAMERA_POSITION_ALONG_SPLINE: + */ + case COMMAND_DECLARE_MISSION_FLAG: + CTheScripts::OnAMissionFlag = (uint16)CTheScripts::Read2BytesFromScript(&++m_nIp); + return 0; + case COMMAND_DECLARE_MISSION_FLAG_FOR_CONTACT: + CollectParameters(&m_nIp, 1); + CTheScripts::OnAMissionForContactFlag[ScriptParams[0]] = (uint16)CTheScripts::Read2BytesFromScript(&++m_nIp); + return 0; + case COMMAND_DECLARE_BASE_BRIEF_ID_FOR_CONTACT: + CollectParameters(&m_nIp, 2); + CTheScripts::BaseBriefIdForContact[ScriptParams[0]] = ScriptParams[1]; + return 0; + case COMMAND_IS_PLAYER_HEALTH_GREATER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + UpdateCompareFlag(pPed->m_fHealth > ScriptParams[1]); + return 0; + } + case COMMAND_IS_CHAR_HEALTH_GREATER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->m_fHealth > ScriptParams[1]); + return 0; + } + case COMMAND_IS_CAR_HEALTH_GREATER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->m_fHealth > ScriptParams[1]); + return 0; + } + case COMMAND_ADD_BLIP_FOR_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetEntityBlip(BLIP_CAR, ScriptParams[0], 0, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetEntityBlip(BLIP_CHAR, ScriptParams[0], 1, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_OBJECT: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetEntityBlip(BLIP_OBJECT, ScriptParams[0], 6, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_CONTACT_POINT: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + // Useless call + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetCoordBlip(BLIP_CONTACT_POINT, pos, 2, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_COORD: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + // Useless call + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetCoordBlip(BLIP_COORD, pos, 5, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CHANGE_BLIP_DISPLAY: + CollectParameters(&m_nIp, 2); + CRadar::ChangeBlipDisplay(ScriptParams[0], (eBlipDisplay)ScriptParams[1]); + return 0; + case COMMAND_ADD_ONE_OFF_SOUND: + { + CollectParameters(&m_nIp, 4); + switch (ScriptParams[3]) { + case SCRIPT_SOUND_EVIDENCE_PICKUP: + DMAudio.PlayFrontEndSound(SOUND_EVIDENCE_PICKUP, 0); + return 0; + case SCRIPT_SOUND_UNLOAD_GOLD: + DMAudio.PlayFrontEndSound(SOUND_UNLOAD_GOLD, 0); + return 0; + case SCRIPT_SOUND_PART_MISSION_COMPLETE: + DMAudio.PlayFrontEndSound(SOUND_PART_MISSION_COMPLETE, 0); + return 0; + case SCRIPT_SOUND_RACE_START_3: + DMAudio.PlayFrontEndSound(SOUND_RACE_START_3, 0); + return 0; + case SCRIPT_SOUND_RACE_START_2: + DMAudio.PlayFrontEndSound(SOUND_RACE_START_2, 0); + return 0; + case SCRIPT_SOUND_RACE_START_1: + DMAudio.PlayFrontEndSound(SOUND_RACE_START_1, 0); + return 0; + case SCRIPT_SOUND_RACE_START_GO: + DMAudio.PlayFrontEndSound(SOUND_RACE_START_GO, 0); + return 0; + default: + break; + } +#ifdef FIX_BUGS + /* BUG: if audio is not initialized, this object will not be freed. */ + if (!DMAudio.IsAudioInitialised()) + return 0; +#endif + cAudioScriptObject* obj = new cAudioScriptObject(); + obj->Posn = *(CVector*)&ScriptParams[0]; + obj->AudioId = ScriptParams[3]; + obj->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(obj); + return 0; + } + case COMMAND_ADD_CONTINUOUS_SOUND: + { + CollectParameters(&m_nIp, 4); + cAudioScriptObject* obj = new cAudioScriptObject(); + obj->Posn = *(CVector*)&ScriptParams[0]; + obj->AudioId = ScriptParams[3]; + obj->AudioEntity = DMAudio.CreateLoopingScriptObject(obj); + ScriptParams[0] = CPools::GetAudioScriptObjectPool()->GetIndex(obj); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_REMOVE_SOUND: + { + CollectParameters(&m_nIp, 1); + cAudioScriptObject* obj = CPools::GetAudioScriptObjectPool()->GetAt(ScriptParams[0]); + if (!obj){ + debug("REMOVE_SOUND - Sound doesn't exist\n"); + return 0; + } + DMAudio.DestroyLoopingScriptObject(obj->AudioEntity); + delete obj; + return 0; + } + case COMMAND_IS_CAR_STUCK_ON_ROOF: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(CTheScripts::UpsideDownCars.HasCarBeenUpsideDownForAWhile(ScriptParams[0])); + return 0; + } + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands400To499(int32 command) +{ + switch (command) { + case COMMAND_ADD_UPSIDEDOWN_CAR_CHECK: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CTheScripts::UpsideDownCars.AddCarToCheck(ScriptParams[0]); + return 0; + } + case COMMAND_REMOVE_UPSIDEDOWN_CAR_CHECK: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CTheScripts::UpsideDownCars.RemoveCarFromCheck(ScriptParams[0]); + return 0; + } + case COMMAND_SET_CHAR_OBJ_WAIT_ON_FOOT: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_WAIT_ON_FOOT); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_ON_FOOT_TILL_SAFE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GUARD_SPOT: + { + CollectParameters(&m_nIp, 4); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GUARD_SPOT, pos); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GUARD_AREA: + { + CollectParameters(&m_nIp, 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float infX = *(float*)&ScriptParams[1]; + float infY = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + if (infX > supX){ + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GUARD_SPOT, pos, radius); + return 0; + } + case COMMAND_SET_CHAR_OBJ_WAIT_IN_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_WAIT_IN_CAR); + return 0; + } + case COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_IN_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_IN_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D: + PlayerInAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_IS_CHAR_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_CHAR_IN_AREA_IN_CAR_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_2D: + case COMMAND_IS_CHAR_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_IN_AREA_IN_CAR_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D: + CharInAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_IS_CAR_STOPPED_IN_AREA_2D: + case COMMAND_IS_CAR_STOPPED_IN_AREA_3D: + CarInAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CAR_2D: + case COMMAND_LOCATE_STOPPED_CAR_2D: + case COMMAND_LOCATE_CAR_3D: + case COMMAND_LOCATE_STOPPED_CAR_3D: + LocateCarCommand(command, &m_nIp); + return 0; + case COMMAND_GIVE_WEAPON_TO_PLAYER: + { + CollectParameters(&m_nIp, 3); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->m_nSelectedWepSlot = pPed->GiveWeapon((eWeaponType)ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_GIVE_WEAPON_TO_CHAR: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->SetCurrentWeapon(pPed->GiveWeapon((eWeaponType)ScriptParams[1], ScriptParams[2])); + if (pPed->bInVehicle) + pPed->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType)->m_nModelId); + return 0; + } + /* Not implemented */ + //case COMMAND_GIVE_WEAPON_TO_CAR: + case COMMAND_SET_PLAYER_CONTROL: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + if (ScriptParams[1]){ + if (CGame::playingIntro || CTheScripts::DelayMakingPlayerUnsafeThisTime){ + CTheScripts::CountdownToMakePlayerUnsafe = 50; + if (CTheScripts::DelayMakingPlayerUnsafeThisTime) + CTheScripts::DelayMakingPlayerUnsafeThisTime--; + }else{ + pPlayer->MakePlayerSafe(false); + } + }else{ + pPlayer->MakePlayerSafe(true); + if (strcmp(m_abScriptName, "camera") == 0){ + pPlayer->m_pPed->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pPlayer->m_pPed->SetTurnSpeed(0.0f, 0.0f, 0.0f); + CAnimManager::BlendAnimation((RpClump*)pPlayer->m_pPed->m_rwObject, pPlayer->m_pPed->m_animGroup, ANIM_STD_IDLE, 1000.0f); + } + } + return 0; + } + case COMMAND_FORCE_WEATHER: + CollectParameters(&m_nIp, 1); + CWeather::ForceWeather(ScriptParams[0]); + return 0; + case COMMAND_FORCE_WEATHER_NOW: + CollectParameters(&m_nIp, 1); + CWeather::ForceWeatherNow(ScriptParams[0]); + return 0; + case COMMAND_RELEASE_WEATHER: + CWeather::ReleaseWeather(); + return 0; + case COMMAND_SET_CURRENT_PLAYER_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + for (int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++){ + if (pPed->m_weapons[i].m_eWeaponType == ScriptParams[1]) + pPed->m_nSelectedWepSlot = i; + } + return 0; + } + case COMMAND_SET_CURRENT_CHAR_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + for (int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++) { + if (pPed->m_weapons[i].m_eWeaponType == ScriptParams[1]) + pPed->SetCurrentWeapon(i); + } + return 0; + } + /* Not implemented */ + //case COMMAND_SET_CURRENT_CAR_WEAPON: + case COMMAND_GET_OBJECT_COORDINATES: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + *(CVector*)&ScriptParams[0] = pObject->GetPosition(); + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_SET_OBJECT_COORDINATES: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pObject->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, pObject); + return 0; + } + case COMMAND_GET_GAME_TIMER: + ScriptParams[0] = CTimer::GetTimeInMilliseconds(); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_TURN_CHAR_TO_FACE_COORD: + { + CollectParameters(&m_nIp, 4); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle; + CVector pos; + if (pPed->bInVehicle) + pVehicle = pPed->m_pMyVehicle; + else + pVehicle = nil; + if (pVehicle) + pos = pVehicle->GetPosition(); + else + pos = pPed->GetPosition(); + float heading = CGeneral::GetATanOfXY(pos.x - *(float*)&ScriptParams[1], pos.y - *(float*)&ScriptParams[2]); + heading += HALFPI; + if (heading > TWOPI) + heading -= TWOPI; + if (!pVehicle){ + pPed->m_fRotationCur = heading; + pPed->m_fRotationDest = heading; + pPed->SetHeading(heading); + } + return 0; + } + case COMMAND_TURN_PLAYER_TO_FACE_COORD: + { + CollectParameters(&m_nIp, 4); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CVehicle* pVehicle; + CVector pos; + if (pPed->bInVehicle) + pVehicle = pPed->m_pMyVehicle; + else + pVehicle = nil; + if (pVehicle) + pos = pVehicle->GetPosition(); + else + pos = pPed->GetPosition(); + float heading = CGeneral::GetATanOfXY(pos.x - *(float*)&ScriptParams[1], pos.y - *(float*)&ScriptParams[2]); + heading += HALFPI; + if (heading > TWOPI) + heading -= TWOPI; + if (!pVehicle) { + pPed->m_fRotationCur = heading; + pPed->m_fRotationDest = heading; + pPed->SetHeading(heading); + } + return 0; + } + case COMMAND_STORE_WANTED_LEVEL: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + ScriptParams[0] = pPed->m_pWanted->GetWantedLevel(); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_CAR_STOPPED: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(CTheScripts::IsVehicleStopped(pVehicle)); + return 0; + } + case COMMAND_MARK_CHAR_AS_NO_LONGER_NEEDED: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CTheScripts::CleanUpThisPed(pPed); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_MARK_CAR_AS_NO_LONGER_NEEDED: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + CTheScripts::CleanUpThisVehicle(pVehicle); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CAR); + return 0; + } + case COMMAND_MARK_OBJECT_AS_NO_LONGER_NEEDED: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + CTheScripts::CleanUpThisObject(pObject); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_OBJECT); + return 0; + } + case COMMAND_DONT_REMOVE_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_DONT_REMOVE_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CAR); + return 0; + } + case COMMAND_DONT_REMOVE_OBJECT: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_OBJECT); + return 0; + } + case COMMAND_CREATE_CHAR_AS_PASSENGER: + { + CollectParameters(&m_nIp, 4); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + switch (ScriptParams[2]) { + case MI_COP: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_STREET; + break; + case MI_SWAT: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_SWAT; + break; + case MI_FBI: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_FBI; + break; + case MI_ARMY: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_ARMY; + break; + case MI_MEDIC: + if (ScriptParams[1] == PEDTYPE_EMERGENCY) + ScriptParams[2] = PEDTYPE_EMERGENCY; + break; + case MI_FIREMAN: + if (ScriptParams[1] == PEDTYPE_FIREMAN) + ScriptParams[2] = PEDTYPE_FIREMAN; + break; + default: + break; + } + CPed* pPed; + if (ScriptParams[1] == PEDTYPE_COP) + pPed = new CCopPed((eCopType)ScriptParams[2]); + else if (ScriptParams[1] == PEDTYPE_EMERGENCY || ScriptParams[1] == PEDTYPE_FIREMAN) + pPed = new CEmergencyPed(ScriptParams[2]); + else + pPed = new CCivilianPed((ePedType)ScriptParams[1], ScriptParams[2]); + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + pPed->bAllowMedicsToReviveMe = false; + pPed->SetPosition(pVehicle->GetPosition()); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + pPed->SetPedState(PED_DRIVING); + CPopulation::ms_nTotalMissionPeds++; + if (ScriptParams[3] >= 0) + pVehicle->AddPassenger(pPed, ScriptParams[3]); + else + pVehicle->AddPassenger(pPed); + pPed->m_pMyVehicle = pVehicle; + pPed->m_pMyVehicle->RegisterReference((CEntity**)&pPed->m_pMyVehicle); + pPed->bInVehicle = true; + pPed->SetPedState(PED_DRIVING); + pVehicle->SetStatus(STATUS_PHYSICS); + pPed->bUsesCollision = false; +#ifdef FIX_BUGS + AnimationId anim = pVehicle->GetDriverAnim(); +#else + AnimationId anim = pVehicle->bLowVehicle ? ANIM_STD_CAR_SIT_LO : ANIM_STD_CAR_SIT; +#endif + pPed->m_pVehicleAnim = CAnimManager::BlendAnimation(pPed->GetClump(), ASSOCGRP_STD, anim, 100.0f); + pPed->StopNonPartialAnims(); + pPed->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pPed->GetPosition()); + CWorld::Add(pPed); + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pPed); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_SET_CHAR_OBJ_KILL_CHAR_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_KILL_CHAR_ANY_MEANS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_KILL_CHAR_ANY_MEANS, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ANY_MEANS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_KILL_CHAR_ANY_MEANS, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GOTO_CHAR_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GOTO_PLAYER_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_LEAVE_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + return 0; + } + case COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_PASSENGER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, pVehicle); + return 0; + } + case COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_DRIVER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicle); + return 0; + } + /* Not implemented. + case COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_IN_CAR: + case COMMAND_SET_CHAR_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE: + case COMMAND_SET_CHAR_OBJ_DESTROY_OBJECT: + */ + case COMMAND_SET_CHAR_OBJ_DESTROY_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_DESTROY_CAR, pVehicle); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GOTO_AREA_ON_FOOT: + { + CollectParameters(&m_nIp, 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float infX = *(float*)&ScriptParams[1]; + float infY = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, pos, radius); + return 0; + } + /* Not implemented. + case COMMAND_SET_CHAR_OBJ_GOTO_AREA_IN_CAR: + case COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET: + case COMMAND_SET_CHAR_OBJ_GUARD_ATTACK: + */ + case COMMAND_SET_CHAR_AS_LEADER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->SetObjective(OBJECTIVE_SET_LEADER, pTarget); + return 0; + } + case COMMAND_SET_PLAYER_AS_LEADER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->SetObjective(OBJECTIVE_SET_LEADER, pTarget); + return 0; + } + case COMMAND_LEAVE_GROUP: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->ClearLeader(); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FOLLOW_ROUTE: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FOLLOW_ROUTE, ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_ADD_ROUTE_POINT: + { + CollectParameters(&m_nIp, 4); + CRouteNode::AddRoutePoint(ScriptParams[0], *(CVector*)&ScriptParams[1]); + return 0; + } + case COMMAND_PRINT_WITH_NUMBER_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 3); + CMessages::AddBigMessageWithNumber(text, ScriptParams[1], ScriptParams[2] - 1, ScriptParams[0], -1, -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_NUMBER: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 3); + CMessages::AddMessageWithNumber(text, ScriptParams[1], ScriptParams[2], ScriptParams[0], -1, -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_NUMBER_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 3); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[1], ScriptParams[2], ScriptParams[0], -1, -1, -1, -1, -1); + return 0; + } + /* Not implemented. + case COMMAND_PRINT_WITH_NUMBER_SOON: + */ + case COMMAND_SWITCH_ROADS_ON: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX){ + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY){ + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ){ + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.SwitchRoadsOffInArea(infX, supX, infY, supY, infZ, supZ, false); + return 0; + } + case COMMAND_SWITCH_ROADS_OFF: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.SwitchRoadsOffInArea(infX, supX, infY, supY, infZ, supZ, true); + return 0; + } + case COMMAND_GET_NUMBER_OF_PASSENGERS: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = pVehicle->m_nNumPassengers; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_MAXIMUM_NUMBER_OF_PASSENGERS: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = pVehicle->m_nNumMaxPassengers; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CAR_DENSITY_MULTIPLIER: + { + CollectParameters(&m_nIp, 1); + CCarCtrl::CarDensityMultiplier = *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SET_CAR_HEAVY: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bIsHeavy = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_CLEAR_CHAR_THREAT_SEARCH: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_fearFlags = 0; + return 0; + } + case COMMAND_ACTIVATE_CRANE: + { + CollectParameters(&m_nIp, 10); + float infX = *(float*)&ScriptParams[2]; + float infY = *(float*)&ScriptParams[3]; + float supX = *(float*)&ScriptParams[4]; + float supY = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[4]; + supX = *(float*)&ScriptParams[2]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[5]; + supY = *(float*)&ScriptParams[3]; + } + CCranes::ActivateCrane(infX, supX, infY, supY, + *(float*)&ScriptParams[6], *(float*)&ScriptParams[7], *(float*)&ScriptParams[8], + DEGTORAD(*(float*)&ScriptParams[9]), false, false, + *(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + return 0; + } + case COMMAND_DEACTIVATE_CRANE: + { + CollectParameters(&m_nIp, 2); + CCranes::DeActivateCrane(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + return 0; + } + case COMMAND_SET_MAX_WANTED_LEVEL: + { + CollectParameters(&m_nIp, 1); + CWanted::SetMaximumWantedLevel(ScriptParams[0]); + return 0; + } + /* Debug commands? + case COMMAND_SAVE_VAR_INT: + case COMMAND_SAVE_VAR_FLOAT: + */ + case COMMAND_IS_CAR_IN_AIR_PROPER: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); +#ifdef FIX_BUGS + // don't wanna get stuck in unique stunt jump cam forever + bool usj_with_dodo = strcmp(m_abScriptName, "usj") == 0 && pVehicle->GetModelIndex() == MI_DODO; + UpdateCompareFlag(pVehicle->m_nCollisionRecords == 0 && !usj_with_dodo); +#else + UpdateCompareFlag(pVehicle->m_nCollisionRecords == 0); +#endif + return 0; + } + default: + script_assert(0); + } + return -1; +} diff --git a/src/control/Script3.cpp b/src/control/Script3.cpp new file mode 100644 index 0000000..ac88347 --- /dev/null +++ b/src/control/Script3.cpp @@ -0,0 +1,2362 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "Boat.h" +#include "CarCtrl.h" +#include "Clock.h" +#include "Coronas.h" +#include "Cranes.h" +#include "CutsceneMgr.h" +#include "Darkel.h" +#include "Explosion.h" +#include "Fire.h" +#include "General.h" +#include "Garages.h" +#include "Heli.h" +#include "Messages.h" +#include "Pad.h" +#include "ParticleObject.h" +#include "Phones.h" +#include "Pickups.h" +#include "PointLights.h" +#include "Population.h" +#include "Pools.h" +#include "ProjectileInfo.h" +#include "Radar.h" +#include "Restart.h" +#include "Stats.h" +#include "Streaming.h" +#include "User.h" +#include "WaterLevel.h" +#include "Weather.h" +#include "Zones.h" +#include "Wanted.h" + +int8 CRunningScript::ProcessCommands500To599(int32 command) +{ + switch (command) { + case COMMAND_IS_CAR_UPSIDEDOWN: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->GetUp().z <= -0.97f); + return 0; + } + case COMMAND_GET_PLAYER_CHAR: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pPed); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CANCEL_OVERRIDE_RESTART: + CRestart::CancelOverrideRestart(); + return 0; + case COMMAND_SET_POLICE_IGNORE_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + if (ScriptParams[1]) { + pPed->m_pWanted->m_bIgnoredByCops = true; + CWorld::StopAllLawEnforcersInTheirTracks(); + } + else { + pPed->m_pWanted->m_bIgnoredByCops = false; + } + return 0; + } + case COMMAND_ADD_PAGER_MESSAGE_WITH_NUMBER: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 4); + CUserDisplay::Pager.AddMessageWithNumber(text, ScriptParams[0], -1, -1, -1, -1, -1, + ScriptParams[1], ScriptParams[2], ScriptParams[3]); + return 0; + } + case COMMAND_START_KILL_FRENZY: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CDarkel::StartFrenzy((eWeaponType)ScriptParams[0], ScriptParams[1], ScriptParams[2], + ScriptParams[3], text, ScriptParams[4], ScriptParams[5], + ScriptParams[6], ScriptParams[7] != 0, false); + return 0; + } + case COMMAND_READ_KILL_FRENZY_STATUS: + { + ScriptParams[0] = CDarkel::ReadStatus(); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SQRT: + { + CollectParameters(&m_nIp, 1); + *(float*)&ScriptParams[0] = Sqrt(*(float*)&ScriptParams[0]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CAR_2D: + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CAR_3D: + LocatePlayerCarCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_CAR_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CAR_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_CAR_3D: + LocateCharCarCommand(command, &m_nIp); + return 0; + case COMMAND_GENERATE_RANDOM_FLOAT_IN_RANGE: + CollectParameters(&m_nIp, 2); + *(float*)&ScriptParams[0] = CGeneral::GetRandomNumberInRange(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_GENERATE_RANDOM_INT_IN_RANGE: + CollectParameters(&m_nIp, 2); + ScriptParams[0] = CGeneral::GetRandomNumberInRange(ScriptParams[0], ScriptParams[1]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_LOCK_CAR_DOORS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->m_nDoorLock = (eCarLock)ScriptParams[1]; + return 0; + } + case COMMAND_EXPLODE_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->BlowUpCar(nil); + return 0; + } + case COMMAND_ADD_EXPLOSION: + CollectParameters(&m_nIp, 4); + CExplosion::AddExplosion(nil, nil, (eExplosionType)ScriptParams[3], *(CVector*)&ScriptParams[0], 0); + return 0; + + case COMMAND_IS_CAR_UPRIGHT: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->GetUp().z >= 0.0f); + return 0; + } + case COMMAND_TURN_CHAR_TO_FACE_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CVehicle* pVehicle = pSourcePed->bInVehicle ? pSourcePed->m_pMyVehicle : nil; + CVector2D sourcePos = pSourcePed->bInVehicle ? pVehicle->GetPosition() : pSourcePed->GetPosition(); + CVector2D targetPos = pTargetPed->bInVehicle ? pTargetPed->m_pMyVehicle->GetPosition() : pTargetPed->GetPosition(); + float angle = CGeneral::GetATanOfXY(sourcePos.x - targetPos.x, sourcePos.y - targetPos.y) + HALFPI; + if (angle > TWOPI) + angle -= TWOPI; + if (!pVehicle) { + pSourcePed->m_fRotationCur = angle; + pSourcePed->m_fRotationDest = angle; + pSourcePed->SetHeading(angle); + } + return 0; + } + case COMMAND_TURN_CHAR_TO_FACE_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pTargetPed = CWorld::Players[ScriptParams[1]].m_pPed; + CVehicle* pVehicle = pSourcePed->bInVehicle ? pSourcePed->m_pMyVehicle : nil; + CVector2D sourcePos = pSourcePed->bInVehicle ? pVehicle->GetPosition() : pSourcePed->GetPosition(); + CVector2D targetPos = pTargetPed->bInVehicle ? pTargetPed->m_pMyVehicle->GetPosition() : pTargetPed->GetPosition(); + float angle = CGeneral::GetATanOfXY(sourcePos.x - targetPos.x, sourcePos.y - targetPos.y) + HALFPI; + if (angle > TWOPI) + angle -= TWOPI; + if (!pVehicle) { + pSourcePed->m_fRotationCur = angle; + pSourcePed->m_fRotationDest = angle; + pSourcePed->SetHeading(angle); + } + return 0; + } + case COMMAND_TURN_PLAYER_TO_FACE_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CWorld::Players[ScriptParams[0]].m_pPed; + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CVehicle* pVehicle = pSourcePed->bInVehicle ? pSourcePed->m_pMyVehicle : nil; + CVector2D sourcePos = pSourcePed->bInVehicle ? pVehicle->GetPosition() : pSourcePed->GetPosition(); + CVector2D targetPos = pTargetPed->bInVehicle ? pTargetPed->m_pMyVehicle->GetPosition() : pTargetPed->GetPosition(); + float angle = CGeneral::GetATanOfXY(sourcePos.x - targetPos.x, sourcePos.y - targetPos.y) + HALFPI; + if (angle > TWOPI) + angle -= TWOPI; + if (!pVehicle) { + pSourcePed->m_fRotationCur = angle; + pSourcePed->m_fRotationDest = angle; + pSourcePed->SetHeading(angle); + } + return 0; + } + case COMMAND_SET_CHAR_OBJ_GOTO_COORD_ON_FOOT: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector target; + target.x = *(float*)&ScriptParams[1]; + target.y = *(float*)&ScriptParams[2]; + target.z = CWorld::FindGroundZForCoord(target.x, target.y); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, target); + return 0; + } + /* Not implemented*/ + //case COMMAND_SET_CHAR_OBJ_GOTO_COORD_IN_CAR: + case COMMAND_CREATE_PICKUP: + { + CollectParameters(&m_nIp, 5); + int16 model = ScriptParams[0]; + if (model < 0) + model = CTheScripts::UsedObjectArray[-model].index; + CVector pos = *(CVector*)&ScriptParams[2]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GetActualPickupIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CPickups::GenerateNewOne(pos, model, ScriptParams[1], 0); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_HAS_PICKUP_BEEN_COLLECTED: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CPickups::IsPickUpPickedUp(ScriptParams[0]) != 0); + return 0; + case COMMAND_REMOVE_PICKUP: + CollectParameters(&m_nIp, 1); + CPickups::RemovePickUp(ScriptParams[0]); + return 0; + case COMMAND_SET_TAXI_LIGHTS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + ((CAutomobile*)pVehicle)->SetTaxiLight(ScriptParams[1] != 0); + return 0; + } + case COMMAND_PRINT_BIG_Q: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 2); + CMessages::AddBigMessageQ(text, ScriptParams[0], ScriptParams[1] - 1); + return 0; + } + case COMMAND_PRINT_WITH_NUMBER_BIG_Q: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 3); + CMessages::AddBigMessageWithNumberQ(text, ScriptParams[1], ScriptParams[2] - 1, + ScriptParams[0], -1, -1, -1, -1, -1); + return 0; + } + case COMMAND_SET_GARAGE: + { + CollectParameters(&m_nIp, 7); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ScriptParams[0] = CGarages::AddOne(CVector(infX, infY, infZ), CVector(supX, supY, supZ), ScriptParams[6], 0); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_GARAGE_WITH_CAR_MODEL: + { + CollectParameters(&m_nIp, 8); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ScriptParams[0] = CGarages::AddOne(CVector(infX, infY, infZ), CVector(supX, supY, supZ), ScriptParams[6], ScriptParams[7]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_TARGET_CAR_FOR_MISSION_GARAGE: + { + CollectParameters(&m_nIp, 2); + CVehicle* pTarget; + if (ScriptParams[1] >= 0) { + pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + } + else { + pTarget = nil; + } + CGarages::SetTargetCarForMissonGarage(ScriptParams[0], pTarget); + return 0; + } + case COMMAND_IS_CAR_IN_MISSION_GARAGE: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CGarages::HasCarBeenDroppedOffYet(ScriptParams[0])); + return 0; + case COMMAND_SET_FREE_BOMBS: + CollectParameters(&m_nIp, 1); + CGarages::SetFreeBombs(ScriptParams[0] != 0); + return 0; +#if GTA_VERSION <= GTA3_PS2_160 + case COMMAND_SET_POWERPOINT: + { + CollectParameters(&m_nIp, 7); + float f1 = *(float*)&ScriptParams[0]; + float f2 = *(float*)&ScriptParams[1]; + float f3 = *(float*)&ScriptParams[2]; + float f4 = *(float*)&ScriptParams[3]; + float f5 = *(float*)&ScriptParams[4]; + float f6 = *(float*)&ScriptParams[5]; + float temp; + + if (f1 > f4) { + temp = f1; + f1 = f4; + f4 = temp; + } + + if (f2 > f5) { + temp = f2; + f2 = f5; + f5 = temp; + } + + if (f3 > f6) { + temp = f3; + f3 = f6; + f6 = temp; + } + + CPowerPoints::GenerateNewOne(f1, f2, f3, f4, f5, f6, *(uint8*)&ScriptParams[6]); + + return 0; + } +#endif // GTA_VERSION <= GTA3_PS2_160 + case COMMAND_SET_ALL_TAXI_LIGHTS: + CollectParameters(&m_nIp, 1); + CAutomobile::SetAllTaxiLights(ScriptParams[0] != 0); + return 0; + case COMMAND_IS_CAR_ARMED_WITH_ANY_BOMB: + { + CollectParameters(&m_nIp, 1); + CAutomobile* pCar = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pCar); + script_assert(pCar->m_vehType == VEHICLE_TYPE_CAR); + UpdateCompareFlag(pCar->m_bombType != 0); //TODO: enum + return 0; + } + case COMMAND_APPLY_BRAKES_TO_PLAYERS_CAR: + CollectParameters(&m_nIp, 2); + CPad::GetPad(ScriptParams[0])->bApplyBrakes = (ScriptParams[1] != 0); + return 0; + case COMMAND_SET_PLAYER_HEALTH: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->m_fHealth = ScriptParams[1]; + return 0; + } + case COMMAND_SET_CHAR_HEALTH: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) { + pPed->m_fHealth = ScriptParams[1]; + } + else if (pPed->bInVehicle) { + pPed->SetDead(); + if (!pPed->IsPlayer()) + pPed->FlagToDestroyWhenNextProcessed(); + } + else { + pPed->SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } + return 0; + } + case COMMAND_SET_CAR_HEALTH: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->m_fHealth = ScriptParams[1]; + return 0; + } + case COMMAND_GET_PLAYER_HEALTH: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + ScriptParams[0] = pPed->m_fHealth; // correct cast float to int + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_CHAR_HEALTH: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + ScriptParams[0] = pPed->m_fHealth; // correct cast float to int + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_CAR_HEALTH: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = pVehicle->m_fHealth; // correct cast float to int + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_CAR_ARMED_WITH_BOMB: + { + CollectParameters(&m_nIp, 2); + CAutomobile* pCar = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pCar); + script_assert(pCar->m_vehType == VEHICLE_TYPE_CAR); + UpdateCompareFlag(pCar->m_bombType == ScriptParams[1]); //TODO: enum + return 0; + } + case COMMAND_CHANGE_CAR_COLOUR: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + if (ScriptParams[1] >= 256 || ScriptParams[2] >= 256) + debug("CHANGE_CAR_COLOUR - Colours must be less than %d", 256); + pVehicle->m_currentColour1 = ScriptParams[1]; + pVehicle->m_currentColour2 = ScriptParams[2]; + return 0; + } + case COMMAND_SWITCH_PED_ROADS_ON: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.SwitchPedRoadsOffInArea(infX, supX, infY, supY, infZ, supZ, false); + return 0; + } + case COMMAND_SWITCH_PED_ROADS_OFF: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.SwitchPedRoadsOffInArea(infX, supX, infY, supY, infZ, supZ, true); + return 0; + } + case COMMAND_CHAR_LOOK_AT_CHAR_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pSourcePed); + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTargetPed); + pSourcePed->SetLookFlag(pTargetPed, true); + pSourcePed->SetLookTimer(60000); + return 0; + } + case COMMAND_CHAR_LOOK_AT_PLAYER_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pSourcePed); + CPed* pTargetPed = CWorld::Players[ScriptParams[1]].m_pPed; + script_assert(pTargetPed); + pSourcePed->SetLookFlag(pTargetPed, true); + pSourcePed->SetLookTimer(60000); + return 0; + } + case COMMAND_PLAYER_LOOK_AT_CHAR_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pSourcePed); + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTargetPed); + pSourcePed->SetLookFlag(pTargetPed, true); + pSourcePed->SetLookTimer(60000); + return 0; + } + case COMMAND_STOP_CHAR_LOOKING: + { + CollectParameters(&m_nIp, 1); + CPed* pSourcePed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pSourcePed); + pSourcePed->ClearLookFlag(); + pSourcePed->bKeepTryingToLook = false; + if (pSourcePed->GetPedState() == PED_LOOK_HEADING || pSourcePed->GetPedState() == PED_LOOK_ENTITY) + pSourcePed->RestorePreviousState(); + return 0; + } + case COMMAND_STOP_PLAYER_LOOKING: + { + CollectParameters(&m_nIp, 1); + CPed* pSourcePed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pSourcePed); + pSourcePed->ClearLookFlag(); + pSourcePed->bKeepTryingToLook = false; + if (pSourcePed->GetPedState() == PED_LOOK_HEADING || pSourcePed->GetPedState() == PED_LOOK_ENTITY) + pSourcePed->RestorePreviousState(); + return 0; + } + case COMMAND_SWITCH_HELICOPTER: + CollectParameters(&m_nIp, 1); + CHeli::ActivateHeli(ScriptParams[0] != 0); + return 0; + + //case COMMAND_SET_GANG_ATTITUDE: + //case COMMAND_SET_GANG_GANG_ATTITUDE: + //case COMMAND_SET_GANG_PLAYER_ATTITUDE: + //case COMMAND_SET_GANG_PED_MODELS: + case COMMAND_SET_GANG_CAR_MODEL: + CollectParameters(&m_nIp, 2); + CGangs::SetGangVehicleModel(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_SET_GANG_WEAPONS: + CollectParameters(&m_nIp, 3); + CGangs::SetGangWeapons(ScriptParams[0], (eWeaponType)ScriptParams[1], (eWeaponType)ScriptParams[2]); + return 0; + case COMMAND_SET_CHAR_OBJ_RUN_TO_AREA: + { + CollectParameters(&m_nIp, 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float infX = *(float*)&ScriptParams[1]; + float infY = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_RUN_TO_AREA, pos, radius); + return 0; + } + case COMMAND_SET_CHAR_OBJ_RUN_TO_COORD: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector pos; + pos.x = *(float*)&ScriptParams[1]; + pos.y = *(float*)&ScriptParams[2]; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_RUN_TO_AREA, pos); + return 0; + } + case COMMAND_IS_PLAYER_TOUCHING_OBJECT_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + bool isTouching = false; + if (pPed->bInVehicle) + isTouching = false; + else if (pPed->GetHasCollidedWith(pObject)) + isTouching = true; + UpdateCompareFlag(isTouching); + return 0; + } + case COMMAND_IS_CHAR_TOUCHING_OBJECT_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + bool isTouching = false; + if (pPed->InVehicle()) + isTouching = false; + else if (pPed->GetHasCollidedWith(pObject)) + isTouching = true; + UpdateCompareFlag(isTouching); + return 0; + } + case COMMAND_LOAD_SPECIAL_CHARACTER: + { + CollectParameters(&m_nIp, 1); + char name[16]; + strncpy(name, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + name[i] = tolower(name[i]); + CStreaming::RequestSpecialChar(ScriptParams[0] - 1, name, STREAMFLAGS_DEPENDENCY | STREAMFLAGS_SCRIPTOWNED); + m_nIp += KEY_LENGTH_IN_SCRIPT; + return 0; + } + case COMMAND_HAS_SPECIAL_CHARACTER_LOADED: + { + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CStreaming::HasSpecialCharLoaded(ScriptParams[0] - 1)); + return 0; + } + case COMMAND_FLASH_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bHasBlip = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_FLASH_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bHasBlip = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_FLASH_OBJECT: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->bHasBlip = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_IS_PLAYER_IN_REMOTE_MODE: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].IsPlayerInRemoteMode()); + return 0; + case COMMAND_ARM_CAR_WITH_BOMB: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + ((CAutomobile*)pVehicle)->m_bombType = ScriptParams[1]; + ((CAutomobile*)pVehicle)->m_pBombRigger = FindPlayerPed(); + return 0; + } + case COMMAND_SET_CHAR_PERSONALITY: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->SetPedStats((ePedStats)ScriptParams[1]); + return 0; + } + case COMMAND_SET_CUTSCENE_OFFSET: + CollectParameters(&m_nIp, 3); + CCutsceneMgr::SetCutsceneOffset(*(CVector*)&ScriptParams[0]); + return 0; + case COMMAND_SET_ANIM_GROUP_FOR_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_animGroup = (AssocGroupId)ScriptParams[1]; + return 0; + } + case COMMAND_SET_ANIM_GROUP_FOR_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->m_animGroup = (AssocGroupId)ScriptParams[1]; + return 0; + } + case COMMAND_REQUEST_MODEL: + { + CollectParameters(&m_nIp, 1); + int model = ScriptParams[0]; + if (model < 0) + model = CTheScripts::UsedObjectArray[-model].index; + CStreaming::RequestModel(model, STREAMFLAGS_DEPENDENCY | STREAMFLAGS_NOFADE | STREAMFLAGS_SCRIPTOWNED); + return 0; + } + case COMMAND_HAS_MODEL_LOADED: + { + CollectParameters(&m_nIp, 1); + int model = ScriptParams[0]; + if (model < 0) + model = CTheScripts::UsedObjectArray[-model].index; + UpdateCompareFlag(CStreaming::HasModelLoaded(model)); + return 0; + } + case COMMAND_MARK_MODEL_AS_NO_LONGER_NEEDED: + { + CollectParameters(&m_nIp, 1); + int model = ScriptParams[0]; + if (model < 0) + model = CTheScripts::UsedObjectArray[-model].index; + CStreaming::SetMissionDoesntRequireModel(model); + return 0; + } + case COMMAND_GRAB_PHONE: + { + CollectParameters(&m_nIp, 2); + ScriptParams[0] = gPhoneInfo.GrabPhone(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_REPEATED_PHONE_MESSAGE: + { + CollectParameters(&m_nIp, 1); + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text, nil, nil, nil, nil, nil); + return 0; + } + case COMMAND_SET_PHONE_MESSAGE: + { + CollectParameters(&m_nIp, 1); + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text, nil, nil, nil, nil, nil); + return 0; + } + case COMMAND_HAS_PHONE_DISPLAYED_MESSAGE: + { + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(gPhoneInfo.HasMessageBeenDisplayed(ScriptParams[0])); + return 0; + } + case COMMAND_TURN_PHONE_OFF: + { + CollectParameters(&m_nIp, 1); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], nil, nil, nil, nil, nil, nil); + return 0; + } + case COMMAND_DRAW_CORONA: + { + CollectParameters(&m_nIp, 9); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CCoronas::RegisterCorona((uintptr)this + m_nIp, ScriptParams[6], ScriptParams[7], ScriptParams[8], + 255, pos, *(float*)&ScriptParams[3], 150.0f, ScriptParams[4], ScriptParams[5], 1, 0, 0, 0.0f); + return 0; + } + case COMMAND_DRAW_LIGHT: + { + CollectParameters(&m_nIp, 6); + CVector pos = *(CVector*)&ScriptParams[0]; + CVector unused(0.0f, 0.0f, 0.0f); + CPointLights::AddLight(0, *(CVector*)&ScriptParams[0], CVector(0.0f, 0.0f, 0.0f), 12.0f, + ScriptParams[3] / 255.0f, ScriptParams[4] / 255.0f, ScriptParams[5] / 255.0f, 0, true); + return 0; + } + case COMMAND_STORE_WEATHER: + CWeather::StoreWeatherState(); + return 0; + case COMMAND_RESTORE_WEATHER: + CWeather::RestoreWeatherState(); + return 0; + case COMMAND_STORE_CLOCK: + CClock::StoreClock(); + return 0; + case COMMAND_RESTORE_CLOCK: + CClock::RestoreClock(); + return 0; + case COMMAND_RESTART_CRITICAL_MISSION: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRestart::OverrideNextRestart(pos, *(float*)&ScriptParams[3]); + if (CWorld::Players[CWorld::PlayerInFocus].m_WBState != WBSTATE_PLAYING) + printf("RESTART_CRITICAL_MISSION - Player state is not PLAYING\n"); + CWorld::Players[CWorld::PlayerInFocus].PlayerFailedCriticalMission(); + return 0; + } + case COMMAND_IS_PLAYER_PLAYING: + { + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_WBState == WBSTATE_PLAYING); + return 0; + } +#ifdef GTA_SCRIPT_COLLECTIVE + case COMMAND_SET_COLL_OBJ_NO_OBJ: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_NONE); + return 0; +#endif + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands600To699(int32 command) +{ + switch (command){ +#ifdef GTA_SCRIPT_COLLECTIVE + case COMMAND_SET_COLL_OBJ_WAIT_ON_FOOT: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_WAIT_ON_FOOT); + return 0; + case COMMAND_SET_COLL_OBJ_FLEE_ON_FOOT_TILL_SAFE: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + return 0; + case COMMAND_SET_COLL_OBJ_GUARD_SPOT: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GUARD_AREA, pos); + return 0; + } + case COMMAND_SET_COLL_OBJ_GUARD_AREA: + { + CollectParameters(&m_nIp, 5); + float infX = *(float*)&ScriptParams[1]; + float supX = *(float*)&ScriptParams[3]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + float infY = *(float*)&ScriptParams[2]; + float supY = *(float*)&ScriptParams[4]; + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GUARD_AREA, pos, radius); + return 0; + } + case COMMAND_SET_COLL_OBJ_WAIT_IN_CAR: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_WAIT_IN_CAR); + return 0; + case COMMAND_SET_COLL_OBJ_KILL_CHAR_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_KILL_CHAR_ON_FOOT, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_KILL_PLAYER_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[1]].m_pPed; + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_KILL_CHAR_ON_FOOT, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_KILL_CHAR_ANY_MEANS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_KILL_CHAR_ANY_MEANS, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_KILL_PLAYER_ANY_MEANS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[1]].m_pPed; + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_KILL_CHAR_ANY_MEANS, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[1]].m_pPed; + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[1]].m_pPed; + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_GOTO_CHAR_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GOTO_CHAR_ON_FOOT, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_GOTO_PLAYER_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[1]].m_pPed; + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GOTO_CHAR_ON_FOOT, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_LEAVE_CAR: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_LEAVE_CAR); + return 0; + case COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_PASSENGER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_ENTER_CAR_AS_PASSENGER, pVehicle); + return 0; + } + case COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_DRIVER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicle); + return 0; + } + /* + case COMMAND_SET_COLL_OBJ_FOLLOW_CAR_IN_CAR: + case COMMAND_SET_COLL_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE: + case COMMAND_SET_COLL_OBJ_DESTROY_OBJECT: + */ + case COMMAND_SET_COLL_OBJ_DESTROY_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_DESTROY_CAR, pVehicle); + return 0; + } + case COMMAND_SET_COLL_OBJ_GOTO_AREA_ON_FOOT: + { + CollectParameters(&m_nIp, 5); + float infX = *(float*)&ScriptParams[1]; + float supX = *(float*)&ScriptParams[3]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + float infY = *(float*)&ScriptParams[2]; + float supY = *(float*)&ScriptParams[4]; + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GOTO_AREA_ON_FOOT, pos, radius); + return 0; + } + /* + case COMMAND_SET_COLL_OBJ_GOTO_AREA_IN_CAR: + case COMMAND_SET_COLL_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET: + case COMMAND_SET_COLL_OBJ_GUARD_ATTACK: + */ + case COMMAND_SET_COLL_OBJ_FOLLOW_ROUTE: + CollectParameters(&m_nIp, 3); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FOLLOW_ROUTE, ScriptParams[1], ScriptParams[2]); + return 0; + case COMMAND_SET_COLL_OBJ_GOTO_COORD_ON_FOOT: + { + CollectParameters(&m_nIp, 3); + CVector pos(*(float*)&ScriptParams[1], *(float*)&ScriptParams[2], CWorld::FindGroundZForCoord(*(float*)&ScriptParams[1], *(float*)&ScriptParams[2])); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GOTO_AREA_ON_FOOT, pos); + return 0; + } + //case COMMAND_SET_COLL_OBJ_GOTO_COORD_IN_CAR: + case COMMAND_SET_COLL_OBJ_RUN_TO_AREA: + { + CollectParameters(&m_nIp, 5); + float infX = *(float*)&ScriptParams[1]; + float supX = *(float*)&ScriptParams[3]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + float infY = *(float*)&ScriptParams[2]; + float supY = *(float*)&ScriptParams[4]; + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_RUN_TO_AREA, pos, radius); + return 0; + } + case COMMAND_SET_COLL_OBJ_RUN_TO_COORD: + { + CollectParameters(&m_nIp, 3); + CVector pos(*(float*)&ScriptParams[1], *(float*)&ScriptParams[2], CWorld::FindGroundZForCoord(*(float*)&ScriptParams[1], *(float*)&ScriptParams[2])); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_RUN_TO_AREA, pos); + return 0; + } + case COMMAND_ADD_PEDS_IN_AREA_TO_COLL: + { + CollectParameters(&m_nIp, 3); + float X = *(float*)&ScriptParams[0]; + float Y = *(float*)&ScriptParams[1]; + float Z = CWorld::FindGroundZForCoord(X, Y); + float radius = *(float*)&ScriptParams[2]; + ScriptParams[0] = CTheScripts::AddPedsInAreaToCollective(X, Y, Z, radius); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_PEDS_IN_VEHICLE_TO_COLL: + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CTheScripts::AddPedsInVehicleToCollective(ScriptParams[0]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_CLEAR_COLL: + CollectParameters(&m_nIp, 1); + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CTheScripts::CollectiveArray[i].colIndex == ScriptParams[0]) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + } + } + return 0; + case COMMAND_IS_COLL_IN_CARS: + { + CollectParameters(&m_nIp, 1); + bool result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + } + else { + result = false; + break; + } + } + UpdateCompareFlag(result); + return 0; + } + case COMMAND_LOCATE_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_COLL_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D: + LocateCollectiveCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_COLL_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_COLL_IN_CAR_CHAR_2D: + LocateCollectiveCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_COLL_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_COLL_IN_CAR_CAR_2D: + LocateCollectiveCarCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_COLL_ANY_MEANS_PLAYER_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_PLAYER_2D: + case COMMAND_LOCATE_COLL_IN_CAR_PLAYER_2D: + LocateCollectivePlayerCommand(command, &m_nIp); + return 0; + case COMMAND_IS_COLL_IN_AREA_2D: + case COMMAND_IS_COLL_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_IN_AREA_IN_CAR_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D: + CollectiveInAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_GET_NUMBER_OF_PEDS_IN_COLL: + { + CollectParameters(&m_nIp, 1); + int total = 0; + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + } + else { + total++; + } + } + ScriptParams[0] = total; + StoreParameters(&m_nIp, 1); + return 0; + } +#endif + case COMMAND_SET_CHAR_HEED_THREATS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bRespondsToThreats = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_PLAYER_HEED_THREATS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->bRespondsToThreats = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_GET_CONTROLLER_MODE: +#if defined(GTA_PC) && !defined(DETECT_PAD_INPUT_SWITCH) + ScriptParams[0] = 0; +#else + ScriptParams[0] = CPad::IsAffectedByController ? CPad::GetPad(0)->Mode : 0; +#endif + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_SET_CAN_RESPRAY_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + ((CAutomobile*)pVehicle)->bFixedColour = (ScriptParams[1] == 0); + return 0; + } + case COMMAND_IS_TAXI: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + int mi = pVehicle->GetModelIndex(); + UpdateCompareFlag(mi == MI_TAXI || mi == MI_CABBIE || mi == MI_BORGNINE); + return 0; + } + case COMMAND_UNLOAD_SPECIAL_CHARACTER: + CollectParameters(&m_nIp, 1); + CStreaming::SetMissionDoesntRequireSpecialChar(ScriptParams[0] - 1); + return 0; + case COMMAND_RESET_NUM_OF_MODELS_KILLED_BY_PLAYER: + CDarkel::ResetModelsKilledByPlayer(); + return 0; + case COMMAND_GET_NUM_OF_MODELS_KILLED_BY_PLAYER: + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CDarkel::QueryModelsKilledByPlayer(ScriptParams[0]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_ACTIVATE_GARAGE: + CollectParameters(&m_nIp, 1); + CGarages::ActivateGarage(ScriptParams[0]); + return 0; + case COMMAND_SWITCH_TAXI_TIMER: + { + CollectParameters(&m_nIp, 1); + if (ScriptParams[0] != 0){ + CWorld::Players[CWorld::PlayerInFocus].m_nUnusedTaxiTimer = CTimer::GetTimeInMilliseconds(); + CWorld::Players[CWorld::PlayerInFocus].m_bUnusedTaxiThing = true; + }else{ + CWorld::Players[CWorld::PlayerInFocus].m_bUnusedTaxiThing = false; + } + return 0; + } + case COMMAND_CREATE_OBJECT_NO_OFFSET: + { + CollectParameters(&m_nIp, 4); + int mi = ScriptParams[0] >= 0 ? ScriptParams[0] : CTheScripts::UsedObjectArray[-ScriptParams[0]].index; + CObject* pObj = new CObject(mi, false); + pObj->ObjectCreatedBy = MISSION_OBJECT; + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pObj->SetPosition(pos); + pObj->SetOrientation(0.0f, 0.0f, 0.0f); + pObj->GetMatrix().UpdateRW(); + pObj->UpdateRwFrame(); + CTheScripts::ClearSpaceForMissionEntity(pos, pObj); + CWorld::Add(pObj); + ScriptParams[0] = CPools::GetObjectPool()->GetIndex(pObj); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_OBJECT); + return 0; + } + case COMMAND_IS_BOAT: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->m_vehType == VEHICLE_TYPE_BOAT); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GOTO_AREA_ANY_MEANS: + { + CollectParameters(&m_nIp, 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float infX = *(float*)&ScriptParams[1]; + float infY = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_AREA_ANY_MEANS, pos, radius); + return 0; + } +#ifdef GTA_SCRIPT_COLLECTIVE + case COMMAND_SET_COLL_OBJ_GOTO_AREA_ANY_MEANS: + { + CollectParameters(&m_nIp, 5); + float infX = *(float*)&ScriptParams[1]; + float supX = *(float*)&ScriptParams[3]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + float infY = *(float*)&ScriptParams[2]; + float supY = *(float*)&ScriptParams[4]; + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GOTO_AREA_ANY_MEANS, pos, radius); + return 0; + } +#endif + case COMMAND_IS_PLAYER_STOPPED: + { + CollectParameters(&m_nIp, 1); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + UpdateCompareFlag(CTheScripts::IsPlayerStopped(pPlayer)); + return 0; + } + case COMMAND_IS_CHAR_STOPPED: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(CTheScripts::IsPedStopped(pPed)); + return 0; + } + case COMMAND_MESSAGE_WAIT: + CollectParameters(&m_nIp, 2); + m_nWakeTime = CTimer::GetTimeInMilliseconds() + ScriptParams[0]; + if (ScriptParams[1] != 0) + m_bSkipWakeTime = true; + return 1; + case COMMAND_ADD_PARTICLE_EFFECT: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CParticleObject::AddObject(ScriptParams[0], pos, ScriptParams[4] != 0); + return 0; + } + case COMMAND_SWITCH_WIDESCREEN: + CollectParameters(&m_nIp, 1); + if (ScriptParams[0] != 0) + TheCamera.SetWideScreenOn(); + else + TheCamera.SetWideScreenOff(); + return 0; + case COMMAND_ADD_SPRITE_BLIP_FOR_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int id = CRadar::SetEntityBlip(BLIP_CAR, ScriptParams[0], 0, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[1]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_SPRITE_BLIP_FOR_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int id = CRadar::SetEntityBlip(BLIP_CHAR, ScriptParams[0], 1, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[1]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_SPRITE_BLIP_FOR_OBJECT: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int id = CRadar::SetEntityBlip(BLIP_OBJECT, ScriptParams[0], 6, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[1]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_SPRITE_BLIP_FOR_CONTACT_POINT: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int id = CRadar::SetCoordBlip(BLIP_CONTACT_POINT, pos, 2, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[3]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_SPRITE_BLIP_FOR_COORD: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int id = CRadar::SetCoordBlip(BLIP_COORD, pos, 5, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[3]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_ONLY_DAMAGED_BY_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bOnlyDamagedByPlayer = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CAR_ONLY_DAMAGED_BY_PLAYER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bOnlyDamagedByPlayer = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CHAR_PROOFS: + { + CollectParameters(&m_nIp, 6); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bBulletProof = (ScriptParams[1] != 0); + pPed->bFireProof = (ScriptParams[2] != 0); + pPed->bExplosionProof = (ScriptParams[3] != 0); + pPed->bCollisionProof = (ScriptParams[4] != 0); + pPed->bMeleeProof = (ScriptParams[5] != 0); + return 0; + } + case COMMAND_SET_CAR_PROOFS: + { + CollectParameters(&m_nIp, 6); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bBulletProof = (ScriptParams[1] != 0); + pVehicle->bFireProof = (ScriptParams[2] != 0); + pVehicle->bExplosionProof = (ScriptParams[3] != 0); + pVehicle->bCollisionProof = (ScriptParams[4] != 0); + pVehicle->bMeleeProof = (ScriptParams[5] != 0); + return 0; + } + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D: + PlayerInAngledAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_DEACTIVATE_GARAGE: + CollectParameters(&m_nIp, 1); + CGarages::DeActivateGarage(ScriptParams[0]); + return 0; + case COMMAND_GET_NUMBER_OF_CARS_COLLECTED_BY_GARAGE: + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CGarages::QueryCarsCollected(ScriptParams[0]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_HAS_CAR_BEEN_TAKEN_TO_GARAGE: + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(CGarages::HasThisCarBeenCollected(ScriptParams[0], ScriptParams[1] - 1)); + return 0; + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands700To799(int32 command) +{ + switch (command){ + case COMMAND_SET_SWAT_REQUIRED: + CollectParameters(&m_nIp, 1); + FindPlayerPed()->m_pWanted->m_bSwatRequired = (ScriptParams[0] != 0); + return 0; + case COMMAND_SET_FBI_REQUIRED: + CollectParameters(&m_nIp, 1); + FindPlayerPed()->m_pWanted->m_bFbiRequired = (ScriptParams[0] != 0); + return 0; + case COMMAND_SET_ARMY_REQUIRED: + CollectParameters(&m_nIp, 1); + FindPlayerPed()->m_pWanted->m_bArmyRequired = (ScriptParams[0] != 0); + return 0; + case COMMAND_IS_CAR_IN_WATER: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(pVehicle && pVehicle->bIsInWater); + return 0; + } + case COMMAND_GET_CLOSEST_CHAR_NODE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CPathNode* pNode = &ThePaths.m_pathNodes[ThePaths.FindNodeClosestToCoors(pos, 1, 999999.9f)]; + *(CVector*)&ScriptParams[0] = pNode->GetPosition(); + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_GET_CLOSEST_CAR_NODE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CPathNode* pNode = &ThePaths.m_pathNodes[ThePaths.FindNodeClosestToCoors(pos, 0, 999999.9f)]; + *(CVector*)&ScriptParams[0] = pNode->GetPosition(); + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_CAR_GOTO_COORDINATES_ACCURATE: + { + CollectParameters(&m_nIp, 4); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += pVehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + if (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, pos, false)) + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTO_COORDS_STRAIGHT_ACCURATE; + else + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_ACCURATE; + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->bEngineOn = true; + pVehicle->AutoPilot.m_nCruiseSpeed = Max(6, pVehicle->AutoPilot.m_nCruiseSpeed); + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + return 0; + } + case COMMAND_START_PACMAN_RACE: + CollectParameters(&m_nIp, 1); + CPacManPickups::StartPacManRace(ScriptParams[0]); + return 0; + case COMMAND_START_PACMAN_RECORD: + CPacManPickups::StartPacManRecord(); + return 0; + case COMMAND_GET_NUMBER_OF_POWER_PILLS_EATEN: + ScriptParams[0] = CPacManPickups::QueryPowerPillsEatenInRace(); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_CLEAR_PACMAN: + CPacManPickups::CleanUpPacManStuff(); + return 0; + case COMMAND_START_PACMAN_SCRAMBLE: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CPacManPickups::StartPacManScramble(pos, *(float*)&ScriptParams[3], ScriptParams[4]); + return 0; + } + case COMMAND_GET_NUMBER_OF_POWER_PILLS_CARRIED: + ScriptParams[0] = CPacManPickups::QueryPowerPillsCarriedByPlayer(); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_CARRIED: + CPacManPickups::ResetPowerPillsCarriedByPlayer(); + return 0; + case COMMAND_IS_CAR_ON_SCREEN: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(TheCamera.IsSphereVisible(pVehicle->GetBoundCentre(), pVehicle->GetBoundRadius())); + return 0; + } + case COMMAND_IS_CHAR_ON_SCREEN: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(TheCamera.IsSphereVisible(pPed->GetBoundCentre(), pPed->GetBoundRadius())); + return 0; + } + case COMMAND_IS_OBJECT_ON_SCREEN: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + UpdateCompareFlag(TheCamera.IsSphereVisible(pObject->GetBoundCentre(), pObject->GetBoundRadius())); + return 0; + } + case COMMAND_GOSUB_FILE: + { + CollectParameters(&m_nIp, 2); + script_assert(m_nStackPointer < MAX_STACK_DEPTH); + m_anStack[m_nStackPointer++] = m_nIp; + SetIP(ScriptParams[0]); + // ScriptParams[1] == filename + return 0; + } + case COMMAND_GET_GROUND_Z_FOR_3D_COORD: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + bool success; + *(float*)&ScriptParams[0] = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &success); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_START_SCRIPT_FIRE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + ScriptParams[0] = gFireManager.StartScriptFire(pos, nil, 0.8f, 1); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_SCRIPT_FIRE_EXTINGUISHED: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(gFireManager.IsScriptFireExtinguish(ScriptParams[0])); + return 0; + case COMMAND_REMOVE_SCRIPT_FIRE: + CollectParameters(&m_nIp, 1); + gFireManager.RemoveScriptFire(ScriptParams[0]); + return 0; + case COMMAND_SET_COMEDY_CONTROLS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bComedyControls = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_BOAT_GOTO_COORDS: + { + CollectParameters(&m_nIp, 4); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_BOAT); + CBoat* pBoat = (CBoat*)pVehicle; + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &pos.z, false); + pBoat->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_ASTHECROWSWIMS; + pBoat->AutoPilot.m_vecDestinationCoors = pos; + pBoat->SetStatus(STATUS_PHYSICS); + pBoat->bEngineOn = true; + pBoat->AutoPilot.m_nCruiseSpeed = Max(6, pBoat->AutoPilot.m_nCruiseSpeed); + pBoat->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + return 0; + } + case COMMAND_BOAT_STOP: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_BOAT); + CBoat* pBoat = (CBoat*)pVehicle; + pBoat->AutoPilot.m_nCarMission = MISSION_NONE; + pBoat->SetStatus(STATUS_PHYSICS); + pBoat->bEngineOn = false; + pBoat->AutoPilot.m_nCruiseSpeed = 0; + return 0; + } + case COMMAND_IS_PLAYER_SHOOTING_IN_AREA: + { + CollectParameters(&m_nIp, 6); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + UpdateCompareFlag(pPed->bIsShooting && pPed->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_CHAR_SHOOTING_IN_AREA: + { + CollectParameters(&m_nIp, 6); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + UpdateCompareFlag(pPed->bIsShooting && pPed->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_CURRENT_PLAYER_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(ScriptParams[1] == pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType); + return 0; + } + case COMMAND_IS_CURRENT_CHAR_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(ScriptParams[1] == pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType); + return 0; + } + case COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_EATEN: + CPacManPickups::ResetPowerPillsEatenInRace(); + return 0; + case COMMAND_ADD_POWER_PILL: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CPacManPickups::GenerateOnePMPickUp(pos); + return 0; + } + case COMMAND_SET_BOAT_CRUISE_SPEED: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_BOAT); + CBoat* pBoat = (CBoat*)pVehicle; + pBoat->AutoPilot.m_nCruiseSpeed = *(float*)&ScriptParams[1]; + return 0; + } + case COMMAND_GET_RANDOM_CHAR_IN_AREA: + { + CollectParameters(&m_nIp, 4); + int ped_handle = -1; + CVector pos = FindPlayerCoors(); + float x1 = *(float*)&ScriptParams[0]; + float y1 = *(float*)&ScriptParams[1]; + float x2 = *(float*)&ScriptParams[2]; + float y2 = *(float*)&ScriptParams[3]; + int i = CPools::GetPedPool()->GetSize(); + while (--i && ped_handle == -1){ + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (CTheScripts::LastRandomPedId == CPools::GetPedPool()->GetIndex(pPed)) + continue; + if (pPed->CharCreatedBy != RANDOM_CHAR) + continue; + if (!pPed->IsPedInControl()) + continue; + if (pPed->bRemoveFromWorld) + continue; + if (pPed->bFadeOut) + continue; + if (pPed->GetModelIndex() == MI_SCUM_WOM || pPed->GetModelIndex() == MI_SCUM_MAN) + continue; + if (!ThisIsAValidRandomPed(pPed->m_nPedType)) + continue; + if (pPed->bIsLeader || pPed->m_leader) + continue; + if (!pPed->IsWithinArea(x1, y1, x2, y2)) + continue; + if (pos.z - PED_FIND_Z_OFFSET > pPed->GetPosition().z) + continue; + if (pos.z + PED_FIND_Z_OFFSET < pPed->GetPosition().z) + continue; + ped_handle = CPools::GetPedPool()->GetIndex(pPed); + CTheScripts::LastRandomPedId = ped_handle; + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + ++CPopulation::ms_nTotalMissionPeds; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ped_handle, CLEANUP_CHAR); + } + ScriptParams[0] = ped_handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_RANDOM_CHAR_IN_ZONE: + { + char zone[KEY_LENGTH_IN_SCRIPT]; + strncpy(zone, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + int nZone = CTheZones::FindZoneByLabelAndReturnIndex(zone); + if (nZone != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetZone(nZone); + int ped_handle = -1; + CVector pos = FindPlayerCoors(); + int i = CPools::GetPedPool()->GetSize(); + while (--i && ped_handle == -1) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (CTheScripts::LastRandomPedId == CPools::GetPedPool()->GetIndex(pPed)) + continue; + if (pPed->CharCreatedBy != RANDOM_CHAR) + continue; + if (!pPed->IsPedInControl()) + continue; + if (pPed->bRemoveFromWorld) + continue; + if (pPed->bFadeOut) + continue; + if (pPed->GetModelIndex() == MI_SCUM_WOM || pPed->GetModelIndex() == MI_SCUM_MAN) + continue; + if (!ThisIsAValidRandomPed(pPed->m_nPedType)) + continue; + if (pPed->bIsLeader || pPed->m_leader) + continue; + if (!CTheZones::PointLiesWithinZone(&pPed->GetPosition(), pZone)) + continue; + if (pos.z - PED_FIND_Z_OFFSET > pPed->GetPosition().z) + continue; + if (pos.z + PED_FIND_Z_OFFSET < pPed->GetPosition().z) + continue; + ped_handle = CPools::GetPedPool()->GetIndex(pPed); + CTheScripts::LastRandomPedId = ped_handle; + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + ++CPopulation::ms_nTotalMissionPeds; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ped_handle, CLEANUP_CHAR); + } + ScriptParams[0] = ped_handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_PLAYER_IN_TAXI: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->IsTaxi()); + return 0; + } + case COMMAND_IS_PLAYER_SHOOTING: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bIsShooting); + return 0; + } + case COMMAND_IS_CHAR_SHOOTING: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bIsShooting); + return 0; + } + case COMMAND_CREATE_MONEY_PICKUP: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GetActualPickupIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CPickups::GenerateNewOne(pos, MI_MONEY, PICKUP_MONEY, ScriptParams[3]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_ACCURACY: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_wepAccuracy = ScriptParams[1]; + return 0; + } + case COMMAND_GET_CAR_SPEED: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + *(float*)&ScriptParams[0] = pVehicle->GetSpeed().Magnitude() * GAME_SPEED_TO_METERS_PER_SECOND; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_LOAD_CUTSCENE: + { + char name[KEY_LENGTH_IN_SCRIPT]; + strncpy(name, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CCutsceneMgr::LoadCutsceneData(name); + return 0; + } + case COMMAND_CREATE_CUTSCENE_OBJECT: + { + CollectParameters(&m_nIp, 1); + CCutsceneObject* pCutObj = CCutsceneMgr::CreateCutsceneObject(ScriptParams[0]); + ScriptParams[0] = CPools::GetObjectPool()->GetIndex(pCutObj); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CUTSCENE_ANIM: + { + CollectParameters(&m_nIp, 1); + char name[KEY_LENGTH_IN_SCRIPT]; + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + strncpy(name, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CCutsceneMgr::SetCutsceneAnim(name, pObject); + return 0; + } + case COMMAND_START_CUTSCENE: + CCutsceneMgr::ms_cutsceneLoadStatus = 1; + return 0; + case COMMAND_GET_CUTSCENE_TIME: + ScriptParams[0] = CCutsceneMgr::GetCutsceneTimeInMilleseconds(); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_HAS_CUTSCENE_FINISHED: + UpdateCompareFlag(CCutsceneMgr::HasCutsceneFinished()); + return 0; + case COMMAND_CLEAR_CUTSCENE: + CCutsceneMgr::DeleteCutsceneData(); + return 0; + case COMMAND_RESTORE_CAMERA_JUMPCUT: + TheCamera.RestoreWithJumpCut(); + return 0; + case COMMAND_CREATE_COLLECTABLE1: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GenerateNewOne(pos, MI_COLLECTABLE1, PICKUP_COLLECTABLE1, 0); + return 0; + } + case COMMAND_SET_COLLECTABLE1_TOTAL: + CollectParameters(&m_nIp, 1); + CWorld::Players[CWorld::PlayerInFocus].m_nTotalPackages = ScriptParams[0]; + return 0; + case COMMAND_IS_PROJECTILE_IN_AREA: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + UpdateCompareFlag(CProjectileInfo::IsProjectileInRange(infX, supX, infY, supY, infZ, supZ, false)); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + return 0; + } + case COMMAND_DESTROY_PROJECTILES_IN_AREA: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + UpdateCompareFlag(CProjectileInfo::IsProjectileInRange(infX, supX, infY, supY, infZ, supZ, true)); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + return 0; + } + case COMMAND_DROP_MINE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GenerateNewOne(pos, MI_CARMINE, PICKUP_MINE_INACTIVE, 0); + return 0; + } + case COMMAND_DROP_NAUTICAL_MINE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GenerateNewOne(pos, MI_NAUTICALMINE, PICKUP_MINE_INACTIVE, 0); + return 0; + } + case COMMAND_IS_CHAR_MODEL: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(ScriptParams[1] == pPed->GetModelIndex()); + return 0; + } + case COMMAND_LOAD_SPECIAL_MODEL: + { + CollectParameters(&m_nIp, 1); + char name[KEY_LENGTH_IN_SCRIPT]; + strncpy(name, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + name[i] = tolower(name[i]); + CStreaming::RequestSpecialModel(ScriptParams[0], name, STREAMFLAGS_DEPENDENCY | STREAMFLAGS_SCRIPTOWNED); + m_nIp += KEY_LENGTH_IN_SCRIPT; + return 0; + } + case COMMAND_CREATE_CUTSCENE_HEAD: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CCutsceneHead* pCutHead = CCutsceneMgr::AddCutsceneHead(pObject, ScriptParams[1]); + ScriptParams[0] = CPools::GetObjectPool()->GetIndex(pCutHead); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CUTSCENE_HEAD_ANIM: + { + CollectParameters(&m_nIp, 1); + CObject* pCutHead = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pCutHead); + char name[KEY_LENGTH_IN_SCRIPT]; + strncpy(name, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CTimer::Stop(); + CCutsceneMgr::SetHeadAnim(name, pCutHead); + CTimer::Update(); + return 0; + } + case COMMAND_SIN: + CollectParameters(&m_nIp, 1); + *(float*)&ScriptParams[0] = Sin(DEGTORAD(*(float*)&ScriptParams[0])); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_COS: + CollectParameters(&m_nIp, 1); + *(float*)&ScriptParams[0] = Cos(DEGTORAD(*(float*)&ScriptParams[0])); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_GET_CAR_FORWARD_X: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + float forwardX = pVehicle->GetForward().x / pVehicle->GetForward().Magnitude2D(); + *(float*)&ScriptParams[0] = forwardX; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_CAR_FORWARD_Y: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + float forwardY = pVehicle->GetForward().y / pVehicle->GetForward().Magnitude2D(); + *(float*)&ScriptParams[0] = forwardY; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CHANGE_GARAGE_TYPE: + CollectParameters(&m_nIp, 2); + CGarages::ChangeGarageType(ScriptParams[0], ScriptParams[1], 0); + return 0; + case COMMAND_ACTIVATE_CRUSHER_CRANE: + { + CollectParameters(&m_nIp, 10); + float infX = *(float*)&ScriptParams[2]; + float infY = *(float*)&ScriptParams[3]; + float supX = *(float*)&ScriptParams[4]; + float supY = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[4]; + supX = *(float*)&ScriptParams[2]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[5]; + supY = *(float*)&ScriptParams[3]; + } + CCranes::ActivateCrane(infX, supX, infY, supY, + *(float*)&ScriptParams[6], *(float*)&ScriptParams[7], *(float*)&ScriptParams[8], + DEGTORAD(*(float*)&ScriptParams[9]), true, false, + *(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + return 0; + } + case COMMAND_PRINT_WITH_2_NUMBERS: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 4); + CMessages::AddMessageWithNumber(text, ScriptParams[2], ScriptParams[3], ScriptParams[0], ScriptParams[1], -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_2_NUMBERS_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 4); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[2], ScriptParams[3], ScriptParams[0], ScriptParams[1], -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_2_NUMBERS_SOON: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 4); + CMessages::AddMessageSoonWithNumber(text, ScriptParams[2], ScriptParams[3], ScriptParams[0], ScriptParams[1], -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_3_NUMBERS: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 5); + CMessages::AddMessageWithNumber(text, ScriptParams[3], ScriptParams[4], ScriptParams[0], ScriptParams[1], ScriptParams[2], -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_3_NUMBERS_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 5); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[3], ScriptParams[4], ScriptParams[0], ScriptParams[1], ScriptParams[2], -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_3_NUMBERS_SOON: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 5); + CMessages::AddMessageSoonWithNumber(text, ScriptParams[3], ScriptParams[4], ScriptParams[0], ScriptParams[1], ScriptParams[2], -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_4_NUMBERS: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 6); + CMessages::AddMessageWithNumber(text, ScriptParams[4], ScriptParams[5], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_4_NUMBERS_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 6); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[4], ScriptParams[5], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_4_NUMBERS_SOON: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 6); + CMessages::AddMessageSoonWithNumber(text, ScriptParams[4], ScriptParams[5], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_5_NUMBERS: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 7); + CMessages::AddMessageWithNumber(text, ScriptParams[5], ScriptParams[6], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], -1); + return 0; + } + case COMMAND_PRINT_WITH_5_NUMBERS_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 7); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[5], ScriptParams[6], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], -1); + return 0; + } + case COMMAND_PRINT_WITH_5_NUMBERS_SOON: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 7); + CMessages::AddMessageSoonWithNumber(text, ScriptParams[5], ScriptParams[6], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], -1); + return 0; + } + case COMMAND_PRINT_WITH_6_NUMBERS: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CMessages::AddMessageWithNumber(text, ScriptParams[6], ScriptParams[7], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], ScriptParams[5]); + return 0; + } + case COMMAND_PRINT_WITH_6_NUMBERS_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[6], ScriptParams[7], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], ScriptParams[5]); + return 0; + } + case COMMAND_PRINT_WITH_6_NUMBERS_SOON: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CMessages::AddMessageSoonWithNumber(text, ScriptParams[6], ScriptParams[7], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], ScriptParams[5]); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FOLLOW_CHAR_IN_FORMATION: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FOLLOW_CHAR_IN_FORMATION, pTargetPed); + pPed->SetFormation((eFormation)ScriptParams[2]); + return 0; + } + case COMMAND_PLAYER_MADE_PROGRESS: + CollectParameters(&m_nIp, 1); + CStats::ProgressMade += ScriptParams[0]; + return 0; + case COMMAND_SET_PROGRESS_TOTAL: + CollectParameters(&m_nIp, 1); + CStats::TotalProgressInGame = ScriptParams[0]; + return 0; + case COMMAND_REGISTER_JUMP_DISTANCE: + CollectParameters(&m_nIp, 1); + CStats::MaximumJumpDistance = Max(CStats::MaximumJumpDistance, *(float*)&ScriptParams[0]); + return 0; + case COMMAND_REGISTER_JUMP_HEIGHT: + CollectParameters(&m_nIp, 1); + CStats::MaximumJumpHeight = Max(CStats::MaximumJumpHeight, *(float*)&ScriptParams[0]); + return 0; + case COMMAND_REGISTER_JUMP_FLIPS: + CollectParameters(&m_nIp, 1); + CStats::MaximumJumpFlips = Max(CStats::MaximumJumpFlips, ScriptParams[0]); + return 0; + case COMMAND_REGISTER_JUMP_SPINS: + CollectParameters(&m_nIp, 1); + CStats::MaximumJumpSpins = Max(CStats::MaximumJumpSpins, ScriptParams[0]); + return 0; + case COMMAND_REGISTER_JUMP_STUNT: + CollectParameters(&m_nIp, 1); + CStats::BestStuntJump = Max(CStats::BestStuntJump, ScriptParams[0]); + return 0; + case COMMAND_REGISTER_UNIQUE_JUMP_FOUND: + ++CStats::NumberOfUniqueJumpsFound; + return 0; + case COMMAND_SET_UNIQUE_JUMPS_TOTAL: + CollectParameters(&m_nIp, 1); + CStats::TotalNumberOfUniqueJumps = ScriptParams[0]; + return 0; + case COMMAND_REGISTER_PASSENGER_DROPPED_OFF_TAXI: + ++CStats::PassengersDroppedOffWithTaxi; + return 0; + case COMMAND_REGISTER_MONEY_MADE_TAXI: + CollectParameters(&m_nIp, 1); + CStats::MoneyMadeWithTaxi += ScriptParams[0]; + return 0; + case COMMAND_REGISTER_MISSION_GIVEN: + ++CStats::MissionsGiven; + return 0; + case COMMAND_REGISTER_MISSION_PASSED: + { + char name[KEY_LENGTH_IN_SCRIPT]; + strncpy(name, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + strncpy(CStats::LastMissionPassedName, name, KEY_LENGTH_IN_SCRIPT); + ++CStats::MissionsPassed; + CStats::CheckPointReachedSuccessfully(); + return 0; + } + case COMMAND_SET_CHAR_RUNNING: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bIsRunning = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_REMOVE_ALL_SCRIPT_FIRES: + gFireManager.RemoveAllScriptFires(); + return 0; + case COMMAND_IS_FIRST_CAR_COLOUR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->m_currentColour1 == ScriptParams[1]); + return 0; + } + case COMMAND_IS_SECOND_CAR_COLOUR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->m_currentColour2 == ScriptParams[1]); + return 0; + } + case COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + if (!pPed) + printf("HAS_CHAR_BEEN_DAMAGED_BY_WEAPON - Character doesn't exist\n"); + UpdateCompareFlag(pPed && pPed->m_lastWepDam == ScriptParams[1]); + return 0; + } + case COMMAND_HAS_CAR_BEEN_DAMAGED_BY_WEAPON: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + if (!pVehicle) + printf("HAS_CAR_BEEN_DAMAGED_BY_WEAPON - Vehicle doesn't exist\n"); + UpdateCompareFlag(pVehicle && pVehicle->m_nLastWeaponDamage == ScriptParams[1]); + return 0; + } + case COMMAND_IS_CHAR_IN_CHARS_GROUP: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pLeader = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pPed); + script_assert(pLeader); + UpdateCompareFlag(pPed->m_leader == pLeader); + return 0; + } + default: + script_assert(0); + } + return -1; +} diff --git a/src/control/Script4.cpp b/src/control/Script4.cpp new file mode 100644 index 0000000..4e798be --- /dev/null +++ b/src/control/Script4.cpp @@ -0,0 +1,2178 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "AnimBlendAssociation.h" +#include "BulletInfo.h" +#include "CarAI.h" +#include "CarCtrl.h" +#include "CivilianPed.h" +#include "Cranes.h" +#include "DMAudio.h" +#include "Darkel.h" +#include "Explosion.h" +#include "Fire.h" +#include "Frontend.h" +#include "Garages.h" +#include "General.h" +#include "Heli.h" +#include "Hud.h" +#include "Messages.h" +#include "ParticleObject.h" +#include "PedRoutes.h" +#include "Phones.h" +#include "Pickups.h" +#include "Plane.h" +#include "Pools.h" +#include "Population.h" +#include "Radar.h" +#include "Record.h" +#include "RpAnimBlend.h" +#include "Rubbish.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "Streaming.h" +#include "TxdStore.h" +#include "User.h" +#include "WaterLevel.h" +#include "World.h" +#include "Zones.h" +#include "Wanted.h" + +int8 CRunningScript::ProcessCommands800To899(int32 command) +{ + CMatrix tmp_matrix; + switch (command) { + case COMMAND_IS_CHAR_IN_PLAYERS_GROUP: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pLeader = CWorld::Players[ScriptParams[1]].m_pPed; + script_assert(pPed); + script_assert(pLeader); + UpdateCompareFlag(pPed->m_leader == pLeader); + return 0; + } + case COMMAND_EXPLODE_CHAR_HEAD: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (pPed->m_nPedState == PED_DRIVING) { + pPed->SetDead(); + if (!pPed->IsPlayer()) + pPed->FlagToDestroyWhenNextProcessed(); + } + else if (CGame::nastyGame && pPed->IsPedInControl()) { + pPed->ApplyHeadShot(WEAPONTYPE_SNIPERRIFLE, pPed->GetNodePosition(PED_HEAD), true); + } + else { + pPed->SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } + return 0; + } + case COMMAND_EXPLODE_PLAYER_HEAD: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + if (CGame::nastyGame) { + pPed->ApplyHeadShot(WEAPONTYPE_SNIPERRIFLE, pPed->GetNodePosition(PED_HEAD), true); + } + else { + pPed->SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } + return 0; + } + case COMMAND_ANCHOR_BOAT: + { + CollectParameters(&m_nIp, 2); + CBoat* pBoat = (CBoat*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pBoat && pBoat->m_vehType == VEHICLE_TYPE_BOAT); + pBoat->m_bIsAnchored = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_ZONE_GROUP: + { + char zone[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, zone); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + int zone_id = CTheZones::FindZoneByLabelAndReturnIndex(zone); + if (zone_id < 0) { + printf("Couldn't find zone - %s\n", zone); + return 0; + } + CTheZones::SetPedGroup(zone_id, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_START_CAR_FIRE: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = gFireManager.StartScriptFire(pVehicle->GetPosition(), pVehicle, 0.8f, 1); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_START_CHAR_FIRE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + ScriptParams[0] = gFireManager.StartScriptFire(pPed->GetPosition(), pPed, 0.8f, 1); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_AREA: + { + CollectParameters(&m_nIp, 5); + int handle = -1; + uint32 i = CPools::GetVehiclePool()->GetSize(); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float supX = *(float*)&ScriptParams[2]; + float supY = *(float*)&ScriptParams[3]; + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (ScriptParams[4] != pVehicle->GetModelIndex() && ScriptParams[4] >= 0) + continue; + if (pVehicle->VehicleCreatedBy != RANDOM_VEHICLE) + continue; + if (!pVehicle->IsWithinArea(infX, infY, supX, supY)) + continue; + handle = CPools::GetVehiclePool()->GetIndex(pVehicle); + pVehicle->VehicleCreatedBy = MISSION_VEHICLE; + ++CCarCtrl::NumMissionCars; + --CCarCtrl::NumRandomCars; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(handle, CLEANUP_CAR); + } + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_ZONE: + { + char zone[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, zone); + int zone_id = CTheZones::FindZoneByLabelAndReturnIndex(zone); + if (zone_id != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetZone(zone_id); + CollectParameters(&m_nIp, 1); + int handle = -1; + uint32 i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (ScriptParams[0] != pVehicle->GetModelIndex() && ScriptParams[0] >= 0) + continue; + if (pVehicle->VehicleCreatedBy != RANDOM_VEHICLE) + continue; + if (!CTheZones::PointLiesWithinZone(&pVehicle->GetPosition(), pZone)) + continue; + handle = CPools::GetVehiclePool()->GetIndex(pVehicle); + pVehicle->VehicleCreatedBy = MISSION_VEHICLE; + ++CCarCtrl::NumMissionCars; + --CCarCtrl::NumRandomCars; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(handle, CLEANUP_CAR); + } + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_HAS_RESPRAY_HAPPENED: + { + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CGarages::HasResprayHappened(ScriptParams[0])); + return 0; + } + case COMMAND_SET_CAMERA_ZOOM: + { + CollectParameters(&m_nIp, 1); + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FOLLOWPED) + TheCamera.SetZoomValueFollowPedScript(ScriptParams[0]); + else if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_CAM_ON_A_STRING) + TheCamera.SetZoomValueCamStringScript(ScriptParams[0]); + return 0; + } + case COMMAND_CREATE_PICKUP_WITH_AMMO: + { + CollectParameters(&m_nIp, 6); + int16 model = ScriptParams[0]; + if (model < 0) + model = CTheScripts::UsedObjectArray[-model].index; + CVector pos = *(CVector*)&ScriptParams[3]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GetActualPickupIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CPickups::GenerateNewOne(pos, model, ScriptParams[1], ScriptParams[2]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CAR_RAM_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVehicle* pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CCarAI::TellCarToRamOtherCar(pVehicle, pTarget); + return 0; + } + case COMMAND_SET_CAR_BLOCK_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVehicle* pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CCarAI::TellCarToBlockOtherCar(pVehicle, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_CATCH_TRAIN: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_CATCH_TRAIN); + return 0; + } +#ifdef GTA_SCRIPT_COLLECTIVE + case COMMAND_SET_COLL_OBJ_CATCH_TRAIN: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_CATCH_TRAIN); + return 0; +#endif + case COMMAND_SET_PLAYER_NEVER_GETS_TIRED: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + pPlayer->m_bInfiniteSprint = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_PLAYER_FAST_RELOAD: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + pPlayer->m_bFastReload = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CHAR_BLEEDING: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bPedIsBleeding = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CAR_FUNNY_SUSPENSION: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + // no action + return 0; + } + case COMMAND_SET_CAR_BIG_WHEELS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + CAutomobile* pCar = (CAutomobile*)pVehicle; + pCar->bBigWheels = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_FREE_RESPRAYS: + CollectParameters(&m_nIp, 1); + CGarages::SetFreeResprays(ScriptParams[0] != 0); + return 0; + case COMMAND_SET_PLAYER_VISIBLE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->bIsVisible = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CHAR_VISIBLE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bIsVisible = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CAR_VISIBLE: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bIsVisible = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_IS_AREA_OCCUPIED: + { + CollectParameters(&m_nIp, 11); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + int16 total; + CWorld::FindObjectsIntersectingCube(CVector(infX, infY, infZ), CVector(supX, supY, supZ), &total, 2, nil, + !!ScriptParams[6], !!ScriptParams[7], !!ScriptParams[8], !!ScriptParams[9], !!ScriptParams[10]); + UpdateCompareFlag(total > 0); + return 0; + } + case COMMAND_START_DRUG_RUN: + CPlane::CreateIncomingCesna(); + return 0; + case COMMAND_HAS_DRUG_RUN_BEEN_COMPLETED: + UpdateCompareFlag(CPlane::HasCesnaLanded()); + return 0; + case COMMAND_HAS_DRUG_PLANE_BEEN_SHOT_DOWN: + UpdateCompareFlag(CPlane::HasCesnaBeenDestroyed()); + return 0; + case COMMAND_SAVE_PLAYER_FROM_FIRES: + CollectParameters(&m_nIp, 1); + gFireManager.ExtinguishPoint(CWorld::Players[ScriptParams[0]].GetPos(), 3.0f); + return 0; + case COMMAND_DISPLAY_TEXT: + { + CollectParameters(&m_nIp, 2); + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtX = *(float*)&ScriptParams[0]; + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtY = *(float*)&ScriptParams[1]; + uint16 len = CMessages::GetWideStringLength(text); + for (uint16 i = 0; i < len; i++) + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_Text[i] = text[i]; + for (uint16 i = len; i < SCRIPT_TEXT_MAX_LENGTH; i++) + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_Text[i] = 0; + ++CTheScripts::NumberOfIntroTextLinesThisFrame; + return 0; + } + case COMMAND_SET_TEXT_SCALE: + { + CollectParameters(&m_nIp, 2); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fScaleX = *(float*)&ScriptParams[0]; + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fScaleY = *(float*)&ScriptParams[1]; + return 0; + } + case COMMAND_SET_TEXT_COLOUR: + { + CollectParameters(&m_nIp, 4); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_sColor = + CRGBA(ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3]); + return 0; + } + case COMMAND_SET_TEXT_JUSTIFY: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bJustify = (ScriptParams[0] != 0); + return 0; + } + case COMMAND_SET_TEXT_CENTRE: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bCentered = (ScriptParams[0] != 0); + return 0; + } + case COMMAND_SET_TEXT_WRAPX: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fWrapX = *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SET_TEXT_CENTRE_SIZE: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fCenterSize = *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SET_TEXT_BACKGROUND: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bBackground = (ScriptParams[0] != 0); + return 0; + } + case COMMAND_SET_TEXT_BACKGROUND_COLOUR: + { + CollectParameters(&m_nIp, 4); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_sBackgroundColor = + CRGBA(ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3]); + return 0; + } + case COMMAND_SET_TEXT_BACKGROUND_ONLY_TEXT: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bBackgroundOnly = (ScriptParams[0] != 0); + return 0; + } + case COMMAND_SET_TEXT_PROPORTIONAL: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bTextProportional = (ScriptParams[0] != 0); + return 0; + } + case COMMAND_SET_TEXT_FONT: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_nFont = ScriptParams[0]; + return 0; + } + case COMMAND_INDUSTRIAL_PASSED: + CStats::IndustrialPassed = true; + DMAudio.PlayRadioAnnouncement(STREAMED_SOUND_ANNOUNCE_COMMERCIAL_OPEN); + return 0; + case COMMAND_COMMERCIAL_PASSED: + CStats::CommercialPassed = true; + DMAudio.PlayRadioAnnouncement(STREAMED_SOUND_ANNOUNCE_SUBURBAN_OPEN); + return 0; + case COMMAND_SUBURBAN_PASSED: + CStats::SuburbanPassed = true; + return 0; + case COMMAND_ROTATE_OBJECT: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + float heading = LimitAngleOnCircle( + RADTODEG(Atan2(-pObject->GetForward().x, pObject->GetForward().y))); + float headingTarget = *(float*)&ScriptParams[1]; +#ifdef FIX_BUGS + float rotateBy = *(float*)&ScriptParams[2] * CTimer::GetTimeStepFix(); +#else + float rotateBy = *(float*)&ScriptParams[2]; +#endif + if (headingTarget == heading) { // using direct comparasion here is fine + UpdateCompareFlag(true); + return 0; + } + float angleClockwise = LimitAngleOnCircle(headingTarget - heading); + float angleCounterclockwise = LimitAngleOnCircle(heading - headingTarget); + float newHeading; + if (angleClockwise < angleCounterclockwise) + newHeading = rotateBy < angleClockwise ? heading + rotateBy : headingTarget; + else + newHeading = rotateBy < angleCounterclockwise ? heading - rotateBy : headingTarget; + bool obstacleInPath = false; + if (ScriptParams[3]) { + CVector pos = pObject->GetPosition(); + tmp_matrix.SetRotateZ(DEGTORAD(newHeading)); + tmp_matrix.GetPosition() += pos; + CColModel* pColModel = pObject->GetColModel(); + CVector cp1 = tmp_matrix * pColModel->boundingBox.min; + CVector cp2 = tmp_matrix * CVector(pColModel->boundingBox.max.x, pColModel->boundingBox.min.y, pColModel->boundingBox.min.z); + CVector cp3 = tmp_matrix * CVector(pColModel->boundingBox.min.x, pColModel->boundingBox.max.y, pColModel->boundingBox.min.z); + CVector cp4 = tmp_matrix * CVector(pColModel->boundingBox.min.x, pColModel->boundingBox.min.y, pColModel->boundingBox.max.z); + int16 collisions; + CWorld::FindObjectsIntersectingAngledCollisionBox(pColModel->boundingBox, tmp_matrix, pos, + Min(cp1.x, Min(cp2.x, Min(cp3.x, cp4.x))), + Min(cp1.y, Min(cp2.y, Min(cp3.y, cp4.y))), + Max(cp1.x, Max(cp2.x, Max(cp3.x, cp4.x))), + Max(cp1.y, Max(cp2.y, Max(cp3.y, cp4.y))), + &collisions, 2, nil, false, true, true, false, false); + if (collisions > 0) + obstacleInPath = true; + } + if (obstacleInPath) { + UpdateCompareFlag(true); + return 0; + } + pObject->SetHeading(DEGTORAD(newHeading)); + pObject->GetMatrix().UpdateRW(); + pObject->UpdateRwFrame(); + UpdateCompareFlag(newHeading == headingTarget); // using direct comparasion here is fine + return 0; + } + case COMMAND_SLIDE_OBJECT: + { + CollectParameters(&m_nIp, 8); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CVector pos = pObject->GetPosition(); + CVector posTarget = *(CVector*)&ScriptParams[1]; +#ifdef FIX_BUGS + CVector slideBy = *(CVector*)&ScriptParams[4] * CTimer::GetTimeStepFix(); +#else + CVector slideBy = *(CVector*)&ScriptParams[4]; +#endif + if (posTarget == pos) { // using direct comparasion here is fine + UpdateCompareFlag(true); + return 0; + } + CVector posDiff = pos - posTarget; + CVector newPosition; + if (posDiff.x < 0) + newPosition.x = -posDiff.x < slideBy.x ? posTarget.x : pos.x + slideBy.x; + else + newPosition.x = posDiff.x < slideBy.x ? posTarget.x : pos.x - slideBy.x; + if (posDiff.y < 0) + newPosition.y = -posDiff.y < slideBy.y ? posTarget.y : pos.y + slideBy.y; + else + newPosition.y = posDiff.y < slideBy.y ? posTarget.y : pos.y - slideBy.y; + if (posDiff.z < 0) + newPosition.z = -posDiff.z < slideBy.z ? posTarget.z : pos.z + slideBy.z; + else + newPosition.z = posDiff.z < slideBy.z ? posTarget.z : pos.z - slideBy.z; + bool obstacleInPath = false; + if (ScriptParams[7]) { + tmp_matrix = pObject->GetMatrix(); + tmp_matrix.GetPosition() = newPosition; + CColModel* pColModel = pObject->GetColModel(); + CVector cp1 = tmp_matrix * pColModel->boundingBox.min; + CVector cp2 = tmp_matrix * CVector(pColModel->boundingBox.max.x, pColModel->boundingBox.min.y, pColModel->boundingBox.min.z); + CVector cp3 = tmp_matrix * CVector(pColModel->boundingBox.min.x, pColModel->boundingBox.max.y, pColModel->boundingBox.min.z); + CVector cp4 = tmp_matrix * CVector(pColModel->boundingBox.min.x, pColModel->boundingBox.min.y, pColModel->boundingBox.max.z); + int16 collisions; + CWorld::FindObjectsIntersectingAngledCollisionBox(pColModel->boundingBox, tmp_matrix, newPosition, + Min(cp1.x, Min(cp2.x, Min(cp3.x, cp4.x))), + Min(cp1.y, Min(cp2.y, Min(cp3.y, cp4.y))), + Max(cp1.x, Max(cp2.x, Max(cp3.x, cp4.x))), + Max(cp1.y, Max(cp2.y, Max(cp3.y, cp4.y))), + &collisions, 2, nil, false, true, true, false, false); + if (collisions > 0) + obstacleInPath = true; + } + if (obstacleInPath) { + UpdateCompareFlag(true); + return 0; + } + pObject->Teleport(newPosition); + UpdateCompareFlag(newPosition == posTarget); // using direct comparasion here is fine + return 0; + } + case COMMAND_REMOVE_CHAR_ELEGANTLY: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + if (pPed && pPed->CharCreatedBy == MISSION_CHAR){ + CWorld::RemoveReferencesToDeletedObject(pPed); + if (pPed->bInVehicle){ + if (pPed->m_pMyVehicle){ + if (pPed == pPed->m_pMyVehicle->pDriver){ + pPed->m_pMyVehicle->RemoveDriver(); + pPed->m_pMyVehicle->SetStatus(STATUS_ABANDONED); + if (pPed->m_pMyVehicle->m_nDoorLock == CARLOCK_LOCKED_INITIALLY) + pPed->m_pMyVehicle->m_nDoorLock = CARLOCK_UNLOCKED; + if (pPed->m_nPedType == PEDTYPE_COP && pPed->m_pMyVehicle->IsLawEnforcementVehicle()) + pPed->m_pMyVehicle->ChangeLawEnforcerState(0); + }else{ + pPed->m_pMyVehicle->RemovePassenger(pPed); + } + } + delete pPed; + --CPopulation::ms_nTotalMissionPeds; + }else{ + pPed->CharCreatedBy = RANDOM_CHAR; + pPed->bRespondsToThreats = true; + pPed->bScriptObjectiveCompleted = false; + pPed->ClearLeader(); + --CPopulation::ms_nTotalMissionPeds; + pPed->bFadeOut = true; + } + } + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_SET_CHAR_STAY_IN_SAME_PLACE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bKindaStayInSamePlace = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_IS_NASTY_GAME: + UpdateCompareFlag(CGame::nastyGame); + return 0; + case COMMAND_UNDRESS_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + char name[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, name); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + name[i] = tolower(name[i]); + int mi = pPed->GetModelIndex(); + pPed->DeleteRwObject(); + if (pPed->IsPlayer()) + mi = 0; + CStreaming::RequestSpecialModel(mi, name, STREAMFLAGS_DEPENDENCY | STREAMFLAGS_SCRIPTOWNED); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CWorld::Remove(pPed); + return 0; + } + case COMMAND_DRESS_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + int mi = pPed->GetModelIndex(); + pPed->m_modelIndex = -1; + pPed->SetModelIndex(mi); + CWorld::Add(pPed); + return 0; + } + case COMMAND_START_CHASE_SCENE: + CollectParameters(&m_nIp, 1); + CTimer::Suspend(); + CStreaming::DeleteAllRwObjects(); + CRecordDataForChase::StartChaseScene(*(float*)&ScriptParams[0]); + CTimer::Resume(); + return 0; + case COMMAND_STOP_CHASE_SCENE: + CRecordDataForChase::CleanUpChaseScene(); + return 0; + case COMMAND_IS_EXPLOSION_IN_AREA: + { + CollectParameters(&m_nIp, 7); + float infX = *(float*)&ScriptParams[1]; + float infY = *(float*)&ScriptParams[2]; + float infZ = *(float*)&ScriptParams[3]; + float supX = *(float*)&ScriptParams[4]; + float supY = *(float*)&ScriptParams[5]; + float supZ = *(float*)&ScriptParams[6]; + if (infX > supX) { + infX = *(float*)&ScriptParams[4]; + supX = *(float*)&ScriptParams[1]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[5]; + supY = *(float*)&ScriptParams[2]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + UpdateCompareFlag(CExplosion::TestForExplosionInArea((eExplosionType)ScriptParams[0], + infX, supX, infY, supY, infZ, supZ)); + return 0; + } + case COMMAND_IS_EXPLOSION_IN_ZONE: + { + CollectParameters(&m_nIp, 1); + char zone[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, zone); + int zone_id = CTheZones::FindZoneByLabelAndReturnIndex(zone); + if (zone_id != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetZone(zone_id); + UpdateCompareFlag(CExplosion::TestForExplosionInArea((eExplosionType)ScriptParams[0], + pZone->minx, pZone->maxx, pZone->miny, pZone->maxy, pZone->minz, pZone->maxz)); + return 0; + } + case COMMAND_START_DRUG_DROP_OFF: + CPlane::CreateDropOffCesna(); + return 0; + case COMMAND_HAS_DROP_OFF_PLANE_BEEN_SHOT_DOWN: + UpdateCompareFlag(CPlane::HasDropOffCesnaBeenShotDown()); + return 0; + case COMMAND_FIND_DROP_OFF_PLANE_COORDINATES: + { + CVector pos = CPlane::FindDropOffCesnaCoordinates(); + *(CVector*)&ScriptParams[0] = pos; + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_CREATE_FLOATING_PACKAGE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + ScriptParams[0] = CPickups::GenerateNewOne(pos, MI_FLOATPACKAGE1, PICKUP_FLOATINGPACKAGE, 0); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_PLACE_OBJECT_RELATIVE_TO_CAR: + { + CollectParameters(&m_nIp, 5); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + CVector offset = *(CVector*)&ScriptParams[2]; + CPhysical::PlacePhysicalRelativeToOtherPhysical(pVehicle, pObject, offset); + return 0; + } + case COMMAND_MAKE_OBJECT_TARGETTABLE: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CPlayerPed* pPlayerPed = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + script_assert(pPlayerPed); + pPlayerPed->MakeObjectTargettable(ScriptParams[0]); + return 0; + } + case COMMAND_ADD_ARMOUR_TO_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPlayerPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPlayerPed); + pPlayerPed->m_fArmour = Clamp(pPlayerPed->m_fArmour + ScriptParams[1], 0.0f, 100.0f); + return 0; + } + case COMMAND_ADD_ARMOUR_TO_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_fArmour = Clamp(pPed->m_fArmour + ScriptParams[1], 0.0f, 100.0f); + return 0; + } + case COMMAND_OPEN_GARAGE: + { + CollectParameters(&m_nIp, 1); + CGarages::OpenGarage(ScriptParams[0]); + return 0; + } + case COMMAND_CLOSE_GARAGE: + { + CollectParameters(&m_nIp, 1); + CGarages::CloseGarage(ScriptParams[0]); + return 0; + } + case COMMAND_WARP_CHAR_FROM_CAR_TO_COORD: + { + CollectParameters(&m_nIp, 4); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + if (pPed->bInVehicle){ + if (pPed->m_pMyVehicle->bIsBus) + pPed->bRenderPedInCar = true; + if (pPed->m_pMyVehicle->pDriver == pPed){ + pPed->m_pMyVehicle->RemoveDriver(); + pPed->m_pMyVehicle->SetStatus(STATUS_ABANDONED); + pPed->m_pMyVehicle->bEngineOn = false; + pPed->m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + }else{ + pPed->m_pMyVehicle->RemovePassenger(pPed); + } + pPed->m_pMyVehicle->SetMoveSpeed(0.0f, 0.0f, -0.00001f); + pPed->m_pMyVehicle->SetTurnSpeed(0.0f, 0.0f, 0.0f); + } + pPed->bInVehicle = false; + pPed->m_pMyVehicle = nil; + pPed->SetPedState(PED_IDLE); + pPed->m_nLastPedState = PED_NONE; + pPed->bUsesCollision = true; + pPed->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pPed->AddWeaponModel(CWeaponInfo::GetWeaponInfo(pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType)->m_nModelId); + pPed->RemoveInCarAnims(); + if (pPed->m_pVehicleAnim) + pPed->m_pVehicleAnim->blendDelta = -1000.0f; + pPed->m_pVehicleAnim = nil; + pPed->RestartNonPartialAnims(); + pPed->SetMoveState(PEDMOVE_NONE); + CAnimManager::BlendAnimation(pPed->GetClump(), pPed->m_animGroup, ANIM_STD_IDLE, 100.0f); + pos.z += pPed->GetDistanceFromCentreOfMassToBaseOfModel(); + pPed->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, pPed); + return 0; + } + case COMMAND_SET_VISIBILITY_OF_CLOSEST_OBJECT_OF_TYPE: + { + CollectParameters(&m_nIp, 6); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float range = *(float*)&ScriptParams[3]; + int mi = ScriptParams[4] < 0 ? CTheScripts::UsedObjectArray[-ScriptParams[4]].index : ScriptParams[4]; + int16 total; + CEntity* apEntities[16]; + CWorld::FindObjectsOfTypeInRange(mi, pos, range, true, &total, 16, apEntities, true, false, false, true, true); + if (total == 0) + CWorld::FindObjectsOfTypeInRangeSectorList(mi, CWorld::GetBigBuildingList(LEVEL_GENERIC), pos, range, true, &total, 16, apEntities); + if (total == 0) + CWorld::FindObjectsOfTypeInRangeSectorList(mi, CWorld::GetBigBuildingList(CTheZones::FindZoneForPoint(pos)), pos, range, true, &total, 16, apEntities); + CEntity* pClosestEntity = nil; + float min_dist = 2.0f * range; + for (int i = 0; i < total; i++) { + float dist = (apEntities[i]->GetPosition() - pos).Magnitude(); + if (dist < min_dist) { + min_dist = dist; + pClosestEntity = apEntities[i]; + } + } + if (pClosestEntity) { + pClosestEntity->bIsVisible = (ScriptParams[5] != 0); + CTheScripts::AddToInvisibilitySwapArray(pClosestEntity, ScriptParams[5] != 0); + } + return 0; + } + case COMMAND_HAS_CHAR_SPOTTED_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + UpdateCompareFlag(pPed->OurPedCanSeeThisOne(pTarget)); + return 0; + } + case COMMAND_SET_CHAR_OBJ_HAIL_TAXI: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_HAIL_TAXI); + return 0; + } + case COMMAND_HAS_OBJECT_BEEN_DAMAGED: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + UpdateCompareFlag(pObject->bRenderDamaged || !pObject->bIsVisible); + return 0; + } + case COMMAND_START_KILL_FRENZY_HEADSHOT: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CDarkel::StartFrenzy((eWeaponType)ScriptParams[0], ScriptParams[1], ScriptParams[2], + ScriptParams[3], text, ScriptParams[4], ScriptParams[5], + ScriptParams[6], ScriptParams[7] != 0, true); + return 0; + } + case COMMAND_ACTIVATE_MILITARY_CRANE: + { + CollectParameters(&m_nIp, 10); + float infX = *(float*)&ScriptParams[2]; + float infY = *(float*)&ScriptParams[3]; + float supX = *(float*)&ScriptParams[4]; + float supY = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[4]; + supX = *(float*)&ScriptParams[2]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[5]; + supY = *(float*)&ScriptParams[3]; + } + CCranes::ActivateCrane(infX, supX, infY, supY, + *(float*)&ScriptParams[6], *(float*)&ScriptParams[7], *(float*)&ScriptParams[8], + DEGTORAD(*(float*)&ScriptParams[9]), false, true, + *(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + return 0; + } + case COMMAND_WARP_PLAYER_INTO_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicle); + pPed->WarpPedIntoCar(pVehicle); + return 0; + } + case COMMAND_WARP_CHAR_INTO_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicle); + pPed->WarpPedIntoCar(pVehicle); + return 0; + } + //case COMMAND_SWITCH_CAR_RADIO: + //case COMMAND_SET_AUDIO_STREAM: + case COMMAND_PRINT_WITH_2_NUMBERS_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 4); + CMessages::AddBigMessageWithNumber(text, ScriptParams[2], ScriptParams[3] - 1, ScriptParams[0], ScriptParams[1], -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_3_NUMBERS_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 5); + CMessages::AddBigMessageWithNumber(text, ScriptParams[3], ScriptParams[4] - 1, ScriptParams[0], ScriptParams[1], ScriptParams[2], -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_4_NUMBERS_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 6); + CMessages::AddBigMessageWithNumber(text, ScriptParams[4], ScriptParams[5] - 1, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_5_NUMBERS_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 7); + CMessages::AddBigMessageWithNumber(text, ScriptParams[5], ScriptParams[6] - 1, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], -1); + return 0; + } + case COMMAND_PRINT_WITH_6_NUMBERS_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CMessages::AddBigMessageWithNumber(text, ScriptParams[6], ScriptParams[7] - 1, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], ScriptParams[5]); + return 0; + } + case COMMAND_SET_CHAR_WAIT_STATE: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->SetWaitState((eWaitState)ScriptParams[1], ScriptParams[2] >= 0 ? &ScriptParams[2] : nil); + return 0; + } + case COMMAND_SET_CAMERA_BEHIND_PLAYER: + TheCamera.SetCameraDirectlyBehindForFollowPed_CamOnAString(); + return 0; + case COMMAND_SET_MOTION_BLUR: + CollectParameters(&m_nIp, 1); + TheCamera.SetMotionBlur(0, 0, 0, 0, ScriptParams[0]); + return 0; + case COMMAND_PRINT_STRING_IN_STRING: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* string = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 2); + CMessages::AddMessageWithString(text, ScriptParams[0], ScriptParams[1], string); + return 0; + } + case COMMAND_CREATE_RANDOM_CHAR: + { + CollectParameters(&m_nIp, 3); + CZoneInfo zoneinfo; + CTheZones::GetZoneInfoForTimeOfDay(&CWorld::Players[CWorld::PlayerInFocus].GetPos(), &zoneinfo); + int mi; + ePedType pedtype = PEDTYPE_COP; + int attempt = 0; + while (pedtype != PEDTYPE_CIVMALE && pedtype != PEDTYPE_CIVFEMALE && attempt < 5) { + mi = CPopulation::ChooseCivilianOccupation(zoneinfo.pedGroup); + if (CModelInfo::GetModelInfo(mi)->GetRwObject()) + pedtype = ((CPedModelInfo*)(CModelInfo::GetModelInfo(mi)))->m_pedType; + attempt++; + } + if (!CModelInfo::GetModelInfo(mi)->GetRwObject()) { + mi = MI_MALE01; + pedtype = ((CPedModelInfo*)(CModelInfo::GetModelInfo(mi)))->m_pedType; + } + CPed* ped = new CCivilianPed(pedtype, mi); + ped->CharCreatedBy = MISSION_CHAR; + ped->bRespondsToThreats = false; + ped->bAllowMedicsToReviveMe = false; + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += 1.0f; + ped->SetPosition(pos); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CTheScripts::ClearSpaceForMissionEntity(pos, ped); + CWorld::Add(ped); + ped->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pos); + CPopulation::ms_nTotalMissionPeds++; + ScriptParams[0] = CPools::GetPedPool()->GetIndex(ped); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_SET_CHAR_OBJ_STEAL_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_STEAL_ANY_CAR); + return 0; + } + case COMMAND_SET_2_REPEATED_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text1, text2, nil, nil, nil, nil); + return 0; + } + case COMMAND_SET_2_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text1, text2, nil, nil, nil, nil); + return 0; + } + case COMMAND_SET_3_REPEATED_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text1, text2, text3, nil, nil, nil); + return 0; + } + case COMMAND_SET_3_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text1, text2, text3, nil, nil, nil); + return 0; + } + case COMMAND_SET_4_REPEATED_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text1, text2, text3, text4, nil, nil); + return 0; + } + case COMMAND_SET_4_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text1, text2, text3, text4, nil, nil); + return 0; + } + case COMMAND_IS_SNIPER_BULLET_IN_AREA: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + UpdateCompareFlag(CBulletInfo::TestForSniperBullet(infX, supX, infY, supY, infZ, supZ)); + return 0; + } + case COMMAND_GIVE_PLAYER_DETONATOR: + CGarages::GivePlayerDetonator(); + return 0; +#ifdef GTA_SCRIPT_COLLECTIVE + case COMMAND_SET_COLL_OBJ_STEAL_ANY_CAR: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_STEAL_ANY_CAR); + return 0; +#endif + case COMMAND_SET_OBJECT_VELOCITY: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->SetMoveSpeed(*(CVector*)&ScriptParams[1] * METERS_PER_SECOND_TO_GAME_SPEED); + return 0; + } + case COMMAND_SET_OBJECT_COLLISION: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->bUsesCollision = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_IS_ICECREAM_JINGLE_ON: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + // Adding this check to correspond to command name. + // All original game scripts always assume that the vehicle is actually Mr. Whoopee, + // but maybe there are mods that use it as "is alarm activated"? + script_assert(pVehicle->GetModelIndex() == MI_MRWHOOP); + UpdateCompareFlag(pVehicle->m_bSirenOrAlarm); + return 0; + } + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands900To999(int32 command) +{ + char str[52]; + char onscreen_str[KEY_LENGTH_IN_SCRIPT]; + switch (command) { + case COMMAND_PRINT_STRING_IN_STRING_NOW: + { + wchar* source = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* pstr = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 2); + CMessages::AddMessageJumpQWithString(source, ScriptParams[0], ScriptParams[1], pstr); + return 0; + } + //case COMMAND_PRINT_STRING_IN_STRING_SOON: + case COMMAND_SET_5_REPEATED_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text5 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text1, text2, text3, text4, text5, nil); + return 0; + } + case COMMAND_SET_5_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text5 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text1, text2, text3, text4, text5, nil); + return 0; + } + case COMMAND_SET_6_REPEATED_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text5 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text6 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text1, text2, text3, text4, text5, text6); + return 0; + } + case COMMAND_SET_6_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text5 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text6 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text1, text2, text3, text4, text5, text6); + return 0; + } + case COMMAND_IS_POINT_OBSCURED_BY_A_MISSION_ENTITY: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0] - *(float*)&ScriptParams[3]; + float supX = *(float*)&ScriptParams[0] + *(float*)&ScriptParams[3]; + float infY = *(float*)&ScriptParams[1] - *(float*)&ScriptParams[4]; + float supY = *(float*)&ScriptParams[1] + *(float*)&ScriptParams[4]; + float infZ = *(float*)&ScriptParams[2] - *(float*)&ScriptParams[5]; + float supZ = *(float*)&ScriptParams[2] + *(float*)&ScriptParams[5]; + if (infX > supX) { + float tmp = infX; + infX = supX; + supX = tmp; + } + if (infY > supY) { + float tmp = infY; + infY = supY; + supY = tmp; + } + if (infZ > supZ) { + float tmp = infZ; + infZ = supZ; + supZ = tmp; + } + int16 total; + CWorld::FindMissionEntitiesIntersectingCube(CVector(infX, infY, infZ), CVector(supX, supY, supZ), &total, 2, nil, true, true, true); + UpdateCompareFlag(total > 0); + return 0; + } + case COMMAND_LOAD_ALL_MODELS_NOW: + CTimer::Stop(); + CStreaming::LoadAllRequestedModels(false); + CTimer::Update(); + return 0; + case COMMAND_ADD_TO_OBJECT_VELOCITY: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->SetMoveSpeed(pObject->GetMoveSpeed() + METERS_PER_SECOND_TO_GAME_SPEED * *(CVector*)&ScriptParams[1]); + return 0; + } + case COMMAND_DRAW_SPRITE: + { + CollectParameters(&m_nIp, 9); + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_bIsUsed = true; + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_nTextureId = ScriptParams[0] - 1; + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_sRect = CRect( + *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], *(float*)&ScriptParams[1] + *(float*)&ScriptParams[3], *(float*)&ScriptParams[2] + *(float*)&ScriptParams[4]); + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_sColor = CRGBA(ScriptParams[5], ScriptParams[6], ScriptParams[7], ScriptParams[8]); + CTheScripts::NumberOfIntroRectanglesThisFrame++; + return 0; + } + case COMMAND_DRAW_RECT: + { + CollectParameters(&m_nIp, 8); + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_bIsUsed = true; + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_nTextureId = -1; + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_sRect = CRect( + *(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[0] + *(float*)&ScriptParams[2], *(float*)&ScriptParams[1] + *(float*)&ScriptParams[3]); + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_sColor = CRGBA(ScriptParams[4], ScriptParams[5], ScriptParams[6], ScriptParams[7]); + CTheScripts::NumberOfIntroRectanglesThisFrame++; + return 0; + } + case COMMAND_LOAD_SPRITE: + { + CollectParameters(&m_nIp, 1); + strncpy(str, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + str[i] = tolower(str[i]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + int slot = CTxdStore::FindTxdSlot("script"); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(slot); + CTheScripts::ScriptSprites[ScriptParams[0] - 1].SetTexture(str); + CTxdStore::PopCurrentTxd(); + return 0; + } + case COMMAND_LOAD_TEXTURE_DICTIONARY: + { + strcpy(str, "models\\"); + strncpy(str + sizeof("models\\"), (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + strcat(str, ".txd"); + m_nIp += KEY_LENGTH_IN_SCRIPT; + int slot = CTxdStore::FindTxdSlot("script"); + if (slot == -1) + slot = CTxdStore::AddTxdSlot("script"); + CTxdStore::LoadTxd(slot, str); + CTxdStore::AddRef(slot); + return 0; + } + case COMMAND_REMOVE_TEXTURE_DICTIONARY: + { + for (int i = 0; i < ARRAY_SIZE(CTheScripts::ScriptSprites); i++) + CTheScripts::ScriptSprites[i].Delete(); + CTxdStore::RemoveTxd(CTxdStore::FindTxdSlot("script")); + return 0; + } + case COMMAND_SET_OBJECT_DYNAMIC: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + if (ScriptParams[1]) { + if (pObject->bIsStatic) { + pObject->SetIsStatic(false); + pObject->AddToMovingList(); + } + } + else { + if (!pObject->bIsStatic) { + pObject->SetIsStatic(true); + pObject->RemoveFromMovingList(); + } + } + return 0; + } + case COMMAND_SET_CHAR_ANIM_SPEED: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CAnimBlendAssociation* pAssoc = RpAnimBlendClumpGetFirstAssociation(pPed->GetClump()); + if (pAssoc) + pAssoc->speed = *(float*)&ScriptParams[1]; + return 0; + } + case COMMAND_PLAY_MISSION_PASSED_TUNE: + { + CollectParameters(&m_nIp, 1); + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.PlayFrontEndTrack(ScriptParams[0] + STREAMED_SOUND_MISSION_COMPLETED - 1, FALSE); + return 0; + } + case COMMAND_CLEAR_AREA: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CWorld::ClearExcitingStuffFromArea(pos, *(float*)&ScriptParams[3], ScriptParams[4]); + return 0; + } + case COMMAND_FREEZE_ONSCREEN_TIMER: + CollectParameters(&m_nIp, 1); + CUserDisplay::OnscnTimer.m_bDisabled = ScriptParams[0] != 0; + return 0; + case COMMAND_SWITCH_CAR_SIREN: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->m_bSirenOrAlarm = ScriptParams[1] != 0; + return 0; + } + case COMMAND_SWITCH_PED_ROADS_ON_ANGLED: + { + CollectParameters(&m_nIp, 7); + ThePaths.SwitchRoadsInAngledArea(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], + *(float*)&ScriptParams[3], *(float*)&ScriptParams[4], *(float*)&ScriptParams[5], *(float*)&ScriptParams[6], 0, 1); + return 0; + } + case COMMAND_SWITCH_PED_ROADS_OFF_ANGLED: + CollectParameters(&m_nIp, 7); + ThePaths.SwitchRoadsInAngledArea(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], + *(float*)&ScriptParams[3], *(float*)&ScriptParams[4], *(float*)&ScriptParams[5], *(float*)&ScriptParams[6], 0, 0); + return 0; + case COMMAND_SWITCH_ROADS_ON_ANGLED: + CollectParameters(&m_nIp, 7); + ThePaths.SwitchRoadsInAngledArea(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], + *(float*)&ScriptParams[3], *(float*)&ScriptParams[4], *(float*)&ScriptParams[5], *(float*)&ScriptParams[6], 1, 1); + return 0; + case COMMAND_SWITCH_ROADS_OFF_ANGLED: + CollectParameters(&m_nIp, 7); + ThePaths.SwitchRoadsInAngledArea(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], + *(float*)&ScriptParams[3], *(float*)&ScriptParams[4], *(float*)&ScriptParams[5], *(float*)&ScriptParams[6], 1, 0); + return 0; + case COMMAND_SET_CAR_WATERTIGHT: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + CAutomobile* pCar = (CAutomobile*)pVehicle; + pCar->bWaterTight = ScriptParams[1] != 0; + return 0; + } + case COMMAND_ADD_MOVING_PARTICLE_EFFECT: + { + CollectParameters(&m_nIp, 12); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float size = Max(0.0f, *(float*)&ScriptParams[7]); + eParticleObjectType type = (eParticleObjectType)ScriptParams[0]; + RwRGBA color; + if (type == POBJECT_SMOKE_TRAIL){ + color.alpha = -1; + color.red = ScriptParams[8]; + color.green = ScriptParams[9]; + color.blue = ScriptParams[10]; + }else{ + color.alpha = color.red = color.blue = color.green = 0; + } + CVector target = *(CVector*)&ScriptParams[4]; + CParticleObject::AddObject(type, pos, target, size, ScriptParams[11], color, 1); + return 0; + } + case COMMAND_SET_CHAR_CANT_BE_DRAGGED_OUT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bDontDragMeOutCar = ScriptParams[1] != 0; + return 0; + } + case COMMAND_TURN_CAR_TO_FACE_COORD: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + const CVector& pos = pVehicle->GetPosition(); + float heading = CGeneral::GetATanOfXY(pos.x - *(float*)&ScriptParams[1], pos.y - *(float*)&ScriptParams[2]) + HALFPI; + if (heading > TWOPI) + heading -= TWOPI; + pVehicle->SetHeading(heading); + return 0; + } + case COMMAND_IS_CRANE_LIFTING_CAR: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[2]); + UpdateCompareFlag(CCranes::IsThisCarPickedUp(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], pVehicle)); + return 0; + } + case COMMAND_DRAW_SPHERE: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + C3dMarkers::PlaceMarkerSet((uintptr)this + m_nIp, MARKERTYPE_CYLINDER, pos, *(float*)&ScriptParams[3], + SPHERE_MARKER_R, SPHERE_MARKER_G, SPHERE_MARKER_B, SPHERE_MARKER_A, + SPHERE_MARKER_PULSE_PERIOD, SPHERE_MARKER_PULSE_FRACTION, 0); + return 0; + } + case COMMAND_SET_CAR_STATUS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->SetStatus(ScriptParams[1]); + return 0; + } + case COMMAND_IS_CHAR_MALE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->m_nPedType != PEDTYPE_CIVFEMALE && pPed->m_nPedType != PEDTYPE_PROSTITUTE); + return 0; + } + case COMMAND_SCRIPT_NAME: + { + strncpy(str, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + str[i] = tolower(str[i]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + strncpy(m_abScriptName, str, KEY_LENGTH_IN_SCRIPT); + return 0; + } + case COMMAND_CHANGE_GARAGE_TYPE_WITH_CAR_MODEL: + { + CollectParameters(&m_nIp, 3); + CGarages::ChangeGarageType(ScriptParams[0], ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_FIND_DRUG_PLANE_COORDINATES: + *(CVector*)&ScriptParams[0] = CPlane::FindDrugPlaneCoordinates(); + StoreParameters(&m_nIp, 3); + return 0; + case COMMAND_SAVE_INT_TO_DEBUG_FILE: + // TODO: implement something here + CollectParameters(&m_nIp, 1); + return 0; + case COMMAND_SAVE_FLOAT_TO_DEBUG_FILE: + CollectParameters(&m_nIp, 1); + return 0; + case COMMAND_SAVE_NEWLINE_TO_DEBUG_FILE: + return 0; + case COMMAND_POLICE_RADIO_MESSAGE: + CollectParameters(&m_nIp, 3); + DMAudio.PlaySuspectLastSeen(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2]); + return 0; + case COMMAND_SET_CAR_STRONG: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bTakeLessDamage = ScriptParams[1] != 0; + return 0; + } + case COMMAND_REMOVE_ROUTE: + CollectParameters(&m_nIp, 1); + CRouteNode::RemoveRoute(ScriptParams[0]); + return 0; + case COMMAND_SWITCH_RUBBISH: + CollectParameters(&m_nIp, 1); + CRubbish::SetVisibility(ScriptParams[0] != 0);; + return 0; + case COMMAND_REMOVE_PARTICLE_EFFECTS_IN_AREA: + { + CollectParameters(&m_nIp, 6); + float x1 = *(float*)&ScriptParams[0]; + float y1 = *(float*)&ScriptParams[1]; + float z1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + float z2 = *(float*)&ScriptParams[5]; + CParticleObject* tmp = CParticleObject::pCloseListHead; + while (tmp) { + CParticleObject* next = tmp->m_pNext; + if (tmp->IsWithinArea(x1, y1, z1, x2, y2, z2)) + tmp->RemoveObject(); + tmp = next; + } + tmp = CParticleObject::pFarListHead; + while (tmp) { + CParticleObject* next = tmp->m_pNext; + if (tmp->IsWithinArea(x1, y1, z1, x2, y2, z2)) + tmp->RemoveObject(); + tmp = next; + } + return 0; + } + case COMMAND_SWITCH_STREAMING: + CollectParameters(&m_nIp, 1); + CStreaming::ms_disableStreaming = ScriptParams[0] == 0; + return 0; + case COMMAND_IS_GARAGE_OPEN: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CGarages::IsGarageOpen(ScriptParams[0])); + return 0; + case COMMAND_IS_GARAGE_CLOSED: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CGarages::IsGarageClosed(ScriptParams[0])); + return 0; + case COMMAND_START_CATALINA_HELI: + CHeli::StartCatalinaFlyBy(); + return 0; + case COMMAND_CATALINA_HELI_TAKE_OFF: + CHeli::CatalinaTakeOff(); + return 0; + case COMMAND_REMOVE_CATALINA_HELI: + CHeli::RemoveCatalinaHeli(); + return 0; + case COMMAND_HAS_CATALINA_HELI_BEEN_SHOT_DOWN: + UpdateCompareFlag(CHeli::HasCatalinaBeenShotDown()); + return 0; + case COMMAND_SWAP_NEAREST_BUILDING_MODEL: + { + CollectParameters(&m_nIp, 6); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = *(float*)&ScriptParams[3]; + int mi1 = ScriptParams[4] >= 0 ? ScriptParams[4] : CTheScripts::UsedObjectArray[-ScriptParams[4]].index; + int mi2 = ScriptParams[5] >= 0 ? ScriptParams[5] : CTheScripts::UsedObjectArray[-ScriptParams[5]].index; + int16 total; + CEntity* apEntities[16]; + CWorld::FindObjectsOfTypeInRange(mi1, pos, radius, true, &total, 16, apEntities, true, false, false, false, false); + if (total == 0) + CWorld::FindObjectsOfTypeInRangeSectorList(mi1, CWorld::GetBigBuildingList(LEVEL_GENERIC), pos, radius, true, &total, 16, apEntities); + if (total == 0) + CWorld::FindObjectsOfTypeInRangeSectorList(mi1, CWorld::GetBigBuildingList(CTheZones::FindZoneForPoint(pos)), pos, radius, true, &total, 16, apEntities); + CEntity* pClosestEntity = nil; + float min_dist = 2.0f * radius; + for (int i = 0; i < total; i++) { + float dist = (apEntities[i]->GetPosition() - pos).Magnitude(); + if (dist < min_dist) { + min_dist = dist; + pClosestEntity = apEntities[i]; + } + } + if (!pClosestEntity) { + printf("Failed to find building\n"); + return 0; + } + CBuilding* pReplacedBuilding = ((CBuilding*)pClosestEntity); + pReplacedBuilding->ReplaceWithNewModel(mi2); + CTheScripts::AddToBuildingSwapArray(pReplacedBuilding, mi1, mi2); + return 0; + } + case COMMAND_SWITCH_WORLD_PROCESSING: + CollectParameters(&m_nIp, 1); + CWorld::bProcessCutsceneOnly = ScriptParams[0] == 0; + return 0; + case COMMAND_REMOVE_ALL_PLAYER_WEAPONS: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->ClearWeapons(); + return 0; + } + case COMMAND_GRAB_CATALINA_HELI: + { + CHeli* pHeli = CHeli::FindPointerToCatalinasHeli(); + ScriptParams[0] = pHeli ? CPools::GetVehiclePool()->GetIndex(pHeli) : -1; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CLEAR_AREA_OF_CARS: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + CWorld::ClearCarsFromArea(infX, infY, infZ, supX, supY, supZ); + return 0; + } + case COMMAND_SET_ROTATING_GARAGE_DOOR: + CollectParameters(&m_nIp, 1); + CGarages::SetGarageDoorToRotate(ScriptParams[0]); + return 0; + case COMMAND_ADD_SPHERE: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = *(float*)&ScriptParams[3]; + CTheScripts::GetActualScriptSphereIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CTheScripts::AddScriptSphere((uintptr)this + m_nIp, pos, radius); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_REMOVE_SPHERE: + CollectParameters(&m_nIp, 1); + CTheScripts::RemoveScriptSphere(ScriptParams[0]); + return 0; + case COMMAND_CATALINA_HELI_FLY_AWAY: + CHeli::MakeCatalinaHeliFlyAway(); + return 0; + case COMMAND_SET_EVERYONE_IGNORE_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + if (ScriptParams[1]) { + pPed->m_pWanted->m_bIgnoredByEveryone = true; + CWorld::StopAllLawEnforcersInTheirTracks(); + } + else { + pPed->m_pWanted->m_bIgnoredByEveryone = false; + } + return 0; + } + case COMMAND_STORE_CAR_CHAR_IS_IN_NO_SAVE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = pPed->bInVehicle ? pPed->m_pMyVehicle : nil; + ScriptParams[0] = CPools::GetVehiclePool()->GetIndex(pVehicle); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_STORE_CAR_PLAYER_IS_IN_NO_SAVE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CVehicle* pVehicle = pPed->bInVehicle ? pPed->m_pMyVehicle : nil; + ScriptParams[0] = CPools::GetVehiclePool()->GetIndex(pVehicle); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_PHONE_DISPLAYING_MESSAGE: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(gPhoneInfo.IsMessageBeingDisplayed(ScriptParams[0])); + return 0; + case COMMAND_DISPLAY_ONSCREEN_TIMER_WITH_STRING: + { + script_assert(CTheScripts::ScriptSpace[m_nIp++] == ARGUMENT_GLOBALVAR); + uint16 var = CTheScripts::Read2BytesFromScript(&m_nIp); + wchar* text = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); // ??? + strncpy(onscreen_str, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CUserDisplay::OnscnTimer.AddClock(var, onscreen_str); + return 0; + } + case COMMAND_DISPLAY_ONSCREEN_COUNTER_WITH_STRING: + { + script_assert(CTheScripts::ScriptSpace[m_nIp++] == ARGUMENT_GLOBALVAR); + uint16 var = CTheScripts::Read2BytesFromScript(&m_nIp); + CollectParameters(&m_nIp, 1); + wchar* text = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); // ??? + strncpy(onscreen_str, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CUserDisplay::OnscnTimer.AddCounter(var, ScriptParams[0], onscreen_str); + return 0; + } + case COMMAND_CREATE_RANDOM_CAR_FOR_CAR_PARK: + { + CollectParameters(&m_nIp, 4); + if (CCarCtrl::NumRandomCars >= 30) + return 0; + int attempts; + int model = -1; + int index = CGeneral::GetRandomNumberInRange(0, 50); + for (attempts = 0; attempts < 50; attempts++) { + if (model != -1) + break; + model = CStreaming::ms_vehiclesLoaded[index]; + if (model == -1) + continue; + // desperatly want to believe this was inlined :| + CBaseModelInfo* pInfo = CModelInfo::GetModelInfo(model); + script_assert(pInfo->GetModelType() == MITYPE_VEHICLE); + CVehicleModelInfo* pVehicleInfo = (CVehicleModelInfo*)pInfo; + if (pVehicleInfo->m_vehicleType == VEHICLE_TYPE_CAR) { + switch (model) { + case MI_LANDSTAL: + case MI_LINERUN: + case MI_FIRETRUCK: + case MI_TRASH: + case MI_STRETCH: + case MI_MULE: + case MI_AMBULAN: + case MI_FBICAR: + case MI_MRWHOOP: + case MI_BFINJECT: + case MI_CORPSE: + case MI_POLICE: + case MI_ENFORCER: + case MI_SECURICA: + case MI_PREDATOR: + case MI_BUS: + case MI_RHINO: + case MI_BARRACKS: + case MI_TRAIN: + case MI_CHOPPER: + case MI_DODO: + case MI_COACH: + case MI_RCBANDIT: + case MI_BELLYUP: + case MI_MRWONGS: + case MI_MAFIA: + case MI_YARDIE: + case MI_YAKUZA: + case MI_DIABLOS: + case MI_COLUMB: + case MI_HOODS: + case MI_AIRTRAIN: + case MI_DEADDODO: + case MI_SPEEDER: + case MI_REEFER: + case MI_PANLANT: + case MI_FLATBED: + case MI_YANKEE: + case MI_ESCAPE: + case MI_BORGNINE: + case MI_TOYZ: + case MI_GHOST: + case MI_MIAMI_RCBARON: + case MI_MIAMI_RCRAIDER: + model = -1; + break; + case MI_IDAHO: + case MI_STINGER: + case MI_PEREN: + case MI_SENTINEL: + case MI_PATRIOT: + case MI_MANANA: + case MI_INFERNUS: + case MI_BLISTA: + case MI_PONY: + case MI_CHEETAH: + case MI_MOONBEAM: + case MI_ESPERANT: + case MI_TAXI: + case MI_KURUMA: + case MI_BOBCAT: + case MI_BANSHEE: + case MI_CABBIE: + case MI_STALLION: + case MI_RUMPO: + case 151: + case 152: + case 153: + break; + default: + printf("CREATE_RANDOM_CAR_FOR_CAR_PARK - Unknown car model %d\n", CStreaming::ms_vehiclesLoaded[index]); + model = -1; + break; + } + } + else + model = -1; + if (++index >= 50) + index = 0; + } + if (model == -1) + return 0; + CVehicle* car; + if (!CModelInfo::IsBikeModel(model)) + car = new CAutomobile(model, RANDOM_VEHICLE); + CVector pos = *(CVector*)&ScriptParams[0]; + pos.z += car->GetDistanceFromCentreOfMassToBaseOfModel(); + car->SetPosition(pos); + car->SetHeading(DEGTORAD(*(float*)&ScriptParams[3])); + CTheScripts::ClearSpaceForMissionEntity(pos, car); + car->SetStatus(STATUS_ABANDONED); + car->bIsLocked = false; + car->bIsCarParkVehicle = true; + CCarCtrl::JoinCarWithRoadSystem(car); + car->AutoPilot.m_nCarMission = MISSION_NONE; + car->AutoPilot.m_nTempAction = TEMPACT_NONE; + car->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + car->AutoPilot.m_nCruiseSpeed = car->AutoPilot.m_fMaxTrafficSpeed = 9.0f; + car->AutoPilot.m_nCurrentLane = car->AutoPilot.m_nNextLane = 0; + car->bEngineOn = false; + car->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pos); + CWorld::Add(car); + return 0; + } + case COMMAND_IS_COLLISION_IN_MEMORY: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CCollision::ms_collisionInMemory == ScriptParams[0]); + return 0; + case COMMAND_SET_WANTED_MULTIPLIER: + CollectParameters(&m_nIp, 1); + FindPlayerPed()->m_pWanted->m_fCrimeSensitivity = *(float*)&ScriptParams[0]; + return 0; + case COMMAND_SET_CAMERA_IN_FRONT_OF_PLAYER: + TheCamera.SetCameraDirectlyInFrontForFollowPed_CamOnAString(); + return 0; + case COMMAND_IS_CAR_VISIBLY_DAMAGED: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->bIsDamaged); + return 0; + } + case COMMAND_DOES_OBJECT_EXIST: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CPools::GetObjectPool()->GetAt(ScriptParams[0])); + return 0; + case COMMAND_LOAD_SCENE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + CTimer::Stop(); + CStreaming::LoadScene(pos); + CTimer::Update(); + return 0; + } + case COMMAND_ADD_STUCK_CAR_CHECK: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CTheScripts::StuckCars.AddCarToCheck(ScriptParams[0], *(float*)&ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_REMOVE_STUCK_CAR_CHECK: + { + CollectParameters(&m_nIp, 1); + CTheScripts::StuckCars.RemoveCarFromCheck(ScriptParams[0]); + return 0; + } + case COMMAND_IS_CAR_STUCK: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CTheScripts::StuckCars.HasCarBeenStuckForAWhile(ScriptParams[0])); + return 0; + case COMMAND_LOAD_MISSION_AUDIO: + strncpy(str, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + str[i] = tolower(str[i]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + DMAudio.PreloadMissionAudio(str); + return 0; + case COMMAND_HAS_MISSION_AUDIO_LOADED: + UpdateCompareFlag(DMAudio.GetMissionAudioLoadingStatus() == 1); + return 0; + case COMMAND_PLAY_MISSION_AUDIO: + DMAudio.PlayLoadedMissionAudio(); + return 0; + case COMMAND_HAS_MISSION_AUDIO_FINISHED: + UpdateCompareFlag(DMAudio.IsMissionAudioSampleFinished()); + return 0; + case COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + int node = ThePaths.FindNodeClosestToCoors(pos, 0, 999999.9f, true, true); + *(CVector*)&ScriptParams[0] = ThePaths.m_pathNodes[node].GetPosition(); + *(float*)&ScriptParams[3] = ThePaths.FindNodeOrientationForCarPlacement(node); + StoreParameters(&m_nIp, 4); + return 0; + } + case COMMAND_HAS_IMPORT_GARAGE_SLOT_BEEN_FILLED: + { + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(CGarages::HasImportExportGarageCollectedThisCar(ScriptParams[0], ScriptParams[1] - 1)); + return 0; + } + case COMMAND_CLEAR_THIS_PRINT: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CMessages::ClearThisPrint(text); + return 0; + } + case COMMAND_CLEAR_THIS_BIG_PRINT: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CMessages::ClearThisBigPrint(text); + return 0; + } + case COMMAND_SET_MISSION_AUDIO_POSITION: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + DMAudio.SetMissionAudioLocation(pos.x, pos.y, pos.z); + return 0; + } + case COMMAND_ACTIVATE_SAVE_MENU: + FrontEndMenuManager.m_bSaveMenuActive = true; + return 0; + case COMMAND_HAS_SAVE_GAME_FINISHED: + UpdateCompareFlag(!FrontEndMenuManager.m_bMenuActive); + return 0; + case COMMAND_NO_SPECIAL_CAMERA_FOR_THIS_GARAGE: + CollectParameters(&m_nIp, 1); + CGarages::SetLeaveCameraForThisGarage(ScriptParams[0]); + return 0; + case COMMAND_ADD_BLIP_FOR_PICKUP_OLD: + { + CollectParameters(&m_nIp, 3); + CObject* pObject = CPickups::aPickUps[CPickups::GetActualPickupIndex(ScriptParams[0])].m_pObject; + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CRadar::SetEntityBlip(BLIP_OBJECT, CPools::GetObjectPool()->GetIndex(pObject), ScriptParams[1], (eBlipDisplay)ScriptParams[2]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_PICKUP: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPickups::aPickUps[CPickups::GetActualPickupIndex(ScriptParams[0])].m_pObject; + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetEntityBlip(BLIP_OBJECT, CPools::GetObjectPool()->GetIndex(pObject), 6, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_SPRITE_BLIP_FOR_PICKUP: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPickups::aPickUps[CPickups::GetActualPickupIndex(ScriptParams[0])].m_pObject; + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetEntityBlip(BLIP_OBJECT, CPools::GetObjectPool()->GetIndex(pObject), 6, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(handle, ScriptParams[1]); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_PED_DENSITY_MULTIPLIER: + CollectParameters(&m_nIp, 1); + CPopulation::PedDensityMultiplier = *(float*)&ScriptParams[0]; + return 0; + case COMMAND_FORCE_RANDOM_PED_TYPE: + CollectParameters(&m_nIp, 1); + CPopulation::m_AllRandomPedsThisType = ScriptParams[0]; + return 0; + case COMMAND_SET_TEXT_DRAW_BEFORE_FADE: + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bTextBeforeFade = ScriptParams[0] != 0; + return 0; + case COMMAND_GET_COLLECTABLE1S_COLLECTED: + ScriptParams[0] = CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages; + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_REGISTER_EL_BURRO_TIME: + CollectParameters(&m_nIp, 1); + CStats::RegisterElBurroTime(ScriptParams[0]); + return 0; + case COMMAND_SET_SPRITES_DRAW_BEFORE_FADE: + CollectParameters(&m_nIp, 1); + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_bBeforeFade = ScriptParams[0] != 0; + return 0; + case COMMAND_SET_TEXT_RIGHT_JUSTIFY: + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bRightJustify = ScriptParams[0] != 0; + return 0; + case COMMAND_PRINT_HELP: + { + if (CCamera::m_bUseMouse3rdPerson && ( + strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "HELP15") == 0 || + strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "GUN_2A") == 0 || + strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "GUN_3A") == 0 || + strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "GUN_4A") == 0)) { + m_nIp += KEY_LENGTH_IN_SCRIPT; + return 0; + } + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CHud::SetHelpMessage(text, false); + return 0; + } + case COMMAND_CLEAR_HELP: + CHud::SetHelpMessage(nil, false); + return 0; + case COMMAND_FLASH_HUD_OBJECT: + CollectParameters(&m_nIp, 1); + CHud::m_ItemToFlash = ScriptParams[0]; + return 0; + default: + script_assert(0); + } + return -1; +} + +int32 CTheScripts::GetNewUniqueScriptSphereIndex(int32 index) +{ + if (ScriptSphereArray[index].m_Index >= UINT16_MAX - 1) + ScriptSphereArray[index].m_Index = 1; + else + ScriptSphereArray[index].m_Index++; + return (uint16)index | ScriptSphereArray[index].m_Index << 16; +} + +int32 CTheScripts::GetActualScriptSphereIndex(int32 index) +{ + if (index == -1) + return -1; + uint16 check = (uint32)index >> 16; + uint16 array_idx = index & (0xFFFF); + script_assert(array_idx < ARRAY_SIZE(ScriptSphereArray)); + if (check != ScriptSphereArray[array_idx].m_Index) + return -1; + return array_idx; +} + +void CTheScripts::DrawScriptSpheres() +{ + for (int i = 0; i < MAX_NUM_SCRIPT_SPHERES; i++) { + if (ScriptSphereArray[i].m_bInUse) + C3dMarkers::PlaceMarkerSet(ScriptSphereArray[i].m_Id, MARKERTYPE_CYLINDER, ScriptSphereArray[i].m_vecCenter, ScriptSphereArray[i].m_fRadius, + SPHERE_MARKER_R, SPHERE_MARKER_G, SPHERE_MARKER_B, SPHERE_MARKER_A, SPHERE_MARKER_PULSE_PERIOD, SPHERE_MARKER_PULSE_FRACTION, 0); + } +} + +int32 CTheScripts::AddScriptSphere(int32 id, CVector pos, float radius) +{ + int16 i = 0; + for (i = 0; i < MAX_NUM_SCRIPT_SPHERES; i++) { + if (!ScriptSphereArray[i].m_bInUse) + break; + } +#ifdef FIX_BUGS + if (i == MAX_NUM_SCRIPT_SPHERES) + return -1; +#endif + ScriptSphereArray[i].m_bInUse = true; + ScriptSphereArray[i].m_Id = id; + ScriptSphereArray[i].m_vecCenter = pos; + ScriptSphereArray[i].m_fRadius = radius; + return GetNewUniqueScriptSphereIndex(i); +} + +void CTheScripts::RemoveScriptSphere(int32 index) +{ + index = GetActualScriptSphereIndex(index); + if (index == -1) + return; + ScriptSphereArray[index].m_bInUse = false; + ScriptSphereArray[index].m_Id = 0; +} + +void CTheScripts::AddToBuildingSwapArray(CBuilding* pBuilding, int32 old_model, int32 new_model) +{ + int i = 0; + bool found = false; + while (i < MAX_NUM_BUILDING_SWAPS && !found) { + if (BuildingSwapArray[i].m_pBuilding == pBuilding) + found = true; + else + i++; + } + if (found) { + if (BuildingSwapArray[i].m_nOldModel == new_model) { + BuildingSwapArray[i].m_pBuilding = nil; + BuildingSwapArray[i].m_nOldModel = BuildingSwapArray[i].m_nNewModel = -1; + } + else { + BuildingSwapArray[i].m_nNewModel = new_model; + } + } + else { + i = 0; + while (i < MAX_NUM_BUILDING_SWAPS && !found) { + if (BuildingSwapArray[i].m_pBuilding == nil) + found = true; + else + i++; + } + if (found) { + BuildingSwapArray[i].m_pBuilding = pBuilding; + BuildingSwapArray[i].m_nNewModel = new_model; + BuildingSwapArray[i].m_nOldModel = old_model; + } + } +} + +void CTheScripts::AddToInvisibilitySwapArray(CEntity* pEntity, bool remove) +{ + int i = 0; + bool found = false; + while (i < MAX_NUM_INVISIBILITY_SETTINGS && !found) { + if (InvisibilitySettingArray[i] == pEntity) + found = true; + else + i++; + } + if (found) { + if (remove) + InvisibilitySettingArray[i] = nil; + } + else if (!remove) { + i = 0; + while (i < MAX_NUM_INVISIBILITY_SETTINGS && !found) { + if (InvisibilitySettingArray[i] == nil) + found = true; + else + i++; + } + if (found) + InvisibilitySettingArray[i] = pEntity; + } +} + +void CTheScripts::UndoBuildingSwaps() +{ + for (int i = 0; i < MAX_NUM_BUILDING_SWAPS; i++) { + if (BuildingSwapArray[i].m_pBuilding) { + BuildingSwapArray[i].m_pBuilding->ReplaceWithNewModel(BuildingSwapArray[i].m_nOldModel); + BuildingSwapArray[i].m_pBuilding = nil; + BuildingSwapArray[i].m_nOldModel = BuildingSwapArray[i].m_nNewModel = -1; + } + } +} + +void CTheScripts::UndoEntityInvisibilitySettings() +{ + for (int i = 0; i < MAX_NUM_INVISIBILITY_SETTINGS; i++) { + if (InvisibilitySettingArray[i]) { + InvisibilitySettingArray[i]->bIsVisible = true; + InvisibilitySettingArray[i] = nil; + } + } +} diff --git a/src/control/Script5.cpp b/src/control/Script5.cpp new file mode 100644 index 0000000..ce5fa4b --- /dev/null +++ b/src/control/Script5.cpp @@ -0,0 +1,2606 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "CarCtrl.h" +#include "BulletInfo.h" +#include "General.h" +#include "Lines.h" +#include "Messages.h" +#include "Pad.h" +#include "Pools.h" +#include "Population.h" +#include "RpAnimBlend.h" +#include "SaveBuf.h" +#include "Shadows.h" +#include "SpecialFX.h" +#include "World.h" +#include "main.h" + +void CRunningScript::UpdateCompareFlag(bool flag) +{ + if (m_bNotFlag) + flag = !flag; + if (m_nAndOrState == ANDOR_NONE) { + m_bCondResult = flag; + return; + } + if (m_nAndOrState >= ANDS_1 && m_nAndOrState <= ANDS_8) { + m_bCondResult &= flag; + if (m_nAndOrState == ANDS_1) { + m_nAndOrState = ANDOR_NONE; + return; + } + } + else if (m_nAndOrState >= ORS_1 && m_nAndOrState <= ORS_8) { + m_bCondResult |= flag; + if (m_nAndOrState == ORS_1) { + m_nAndOrState = ANDOR_NONE; + return; + } + } + else { + return; + } + m_nAndOrState--; +} + +void CRunningScript::LocatePlayerCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + switch (command) { + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D: + if (!CTheScripts::IsPlayerStopped(pPlayerInfo)) { + result = false; + decided = true; + } + break; + default: + break; + } + X = *(float*)&ScriptParams[1]; + Y = *(float*)&ScriptParams[2]; + if (b3D) { + Z = *(float*)&ScriptParams[3]; + dX = *(float*)&ScriptParams[4]; + dY = *(float*)&ScriptParams[5]; + dZ = *(float*)&ScriptParams[6]; + debug = ScriptParams[7]; + } else { + dX = *(float*)&ScriptParams[3]; + dY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (!decided) { + CVector pos = pPlayerInfo->GetPos(); + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_2D: + case COMMAND_LOCATE_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D: + result = true; + break; + case COMMAND_LOCATE_PLAYER_ON_FOOT_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D: + result = !pPlayerInfo->m_pPed->bInVehicle; + break; + case COMMAND_LOCATE_PLAYER_IN_CAR_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D: + result = pPlayerInfo->m_pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocatePlayerCharCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector pos = pPlayerInfo->GetPos(); + if (pTarget->bInVehicle) { + X = pTarget->m_pMyVehicle->GetPosition().x; + Y = pTarget->m_pMyVehicle->GetPosition().y; + Z = pTarget->m_pMyVehicle->GetPosition().z; + } else { + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + } + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_3D: + result = true; + break; + case COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_3D: + result = !pPlayerInfo->m_pPed->bInVehicle; + break; + case COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_3D: + result = pPlayerInfo->m_pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + UpdateCompareFlag(result); + if (debug) +#ifdef FIX_BUGS + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); +#else + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dX, b3D ? Z : MAP_Z_LOW_LIMIT); +#endif + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocatePlayerCarCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + CVehicle* pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector pos = pPlayerInfo->GetPos(); + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_3D: + result = true; + break; + case COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_3D: + result = !pPlayerInfo->m_pPed->bInVehicle; + break; + case COMMAND_LOCATE_PLAYER_IN_CAR_CAR_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CAR_3D: + result = pPlayerInfo->m_pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCharCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + switch (command) { + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D: + if (!CTheScripts::IsPedStopped(pPed)) { + result = false; + decided = true; + } + break; + default: + break; + } + X = *(float*)&ScriptParams[1]; + Y = *(float*)&ScriptParams[2]; + if (b3D) { + Z = *(float*)&ScriptParams[3]; + dX = *(float*)&ScriptParams[4]; + dY = *(float*)&ScriptParams[5]; + dZ = *(float*)&ScriptParams[6]; + debug = ScriptParams[7]; + } + else { + dX = *(float*)&ScriptParams[3]; + dY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (!decided) { + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D: + result = true; + break; + case COMMAND_LOCATE_CHAR_ON_FOOT_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_CHAR_IN_CAR_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCharCharCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_CHAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + if (pTarget->bInVehicle) { + X = pTarget->m_pMyVehicle->GetPosition().x; + Y = pTarget->m_pMyVehicle->GetPosition().y; + Z = pTarget->m_pMyVehicle->GetPosition().z; + } + else { + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + } + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_3D: + result = true; + break; + case COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_3D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_CHAR_IN_CAR_CHAR_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_CHAR_3D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + UpdateCompareFlag(result); + if (debug) +#ifdef FIX_BUGS + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); +#else + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dX, b3D ? Z : MAP_Z_LOW_LIMIT); +#endif + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCharCarCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CAR_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_3D: + result = true; + break; + case COMMAND_LOCATE_CHAR_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CAR_3D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_CHAR_IN_CAR_CAR_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_CAR_3D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +#if GTA_VERSION > GTA3_PS2_160 +void CRunningScript::LocateCharObjectCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CObject* pTarget = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_3D: + result = true; + break; + case COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_3D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_3D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} +#endif + +void CRunningScript::LocateCarCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_CAR_3D: + case COMMAND_LOCATE_STOPPED_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVector pos = pVehicle->GetPosition(); + switch (command) { + case COMMAND_LOCATE_STOPPED_CAR_2D: + case COMMAND_LOCATE_STOPPED_CAR_3D: + if (!CTheScripts::IsVehicleStopped(pVehicle)) { + result = false; + decided = true; + } + break; + default: + break; + } + X = *(float*)&ScriptParams[1]; + Y = *(float*)&ScriptParams[2]; + if (b3D) { + Z = *(float*)&ScriptParams[3]; + dX = *(float*)&ScriptParams[4]; + dY = *(float*)&ScriptParams[5]; + dZ = *(float*)&ScriptParams[6]; + debug = ScriptParams[7]; + } + else { + dX = *(float*)&ScriptParams[3]; + dY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (!decided) { + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + result = in_area; + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +#if GTA_VERSION > GTA3_PS2_160 +void CRunningScript::LocateSniperBulletCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_SNIPER_BULLET_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 7 : 5); + X = *(float*)&ScriptParams[0]; + Y = *(float*)&ScriptParams[1]; + if (b3D) { + Z = *(float*)&ScriptParams[2]; + dX = *(float*)&ScriptParams[3]; + dY = *(float*)&ScriptParams[4]; + dZ = *(float*)&ScriptParams[5]; + debug = ScriptParams[6]; + } + else { + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + debug = ScriptParams[4]; + } + result = CBulletInfo::TestForSniperBullet(X - dX, X + dX, Y - dY, Y + dY, b3D ? Z - dZ : -1000.0f, b3D ? Z + dZ : 1000.0f); + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} +#endif + +void CRunningScript::PlayerInAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float infX, infY, infZ, supX, supY, supZ; + switch (command) { + case COMMAND_IS_PLAYER_IN_AREA_3D: + case COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_IN_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + switch (command) { + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_2D: + if (!CTheScripts::IsPlayerStopped(pPlayerInfo)) { + result = false; + decided = true; + } + break; + default: + break; + } + infX = *(float*)&ScriptParams[1]; + infY = *(float*)&ScriptParams[2]; + if (b3D) { + infZ = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[6]; + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + debug = ScriptParams[7]; + } + else { + supX = *(float*)&ScriptParams[3]; + supY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (infX > supX) { + float tmp = infX; + infX = supX; + supX = tmp; + } + if (infY > supY) { + float tmp = infY; + infY = supY; + supY = tmp; + } + if (!decided) { + CVector pos = pPlayerInfo->GetPos(); + result = false; + bool in_area; + if (b3D) { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y && + infZ <= pos.z && + supZ >= pos.z; + } + else { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_IS_PLAYER_IN_AREA_2D: + case COMMAND_IS_PLAYER_IN_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D: + result = true; + break; + case COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D: + result = !pPlayerInfo->m_pPed->bInVehicle; + break; + case COMMAND_IS_PLAYER_IN_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_IN_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D: + result = pPlayerInfo->m_pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, infX, infY, supX, supY, b3D ? (infZ + supZ) / 2 : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + else + CTheScripts::DrawDebugSquare(infX, infY, supX, supY); + } +} + +void CRunningScript::PlayerInAngledAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float infX, infY, infZ, supX, supY, supZ, side2length; + switch (command) { + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 9 : 7); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + switch (command) { + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_2D: + if (!CTheScripts::IsPlayerStopped(pPlayerInfo)) { + result = false; + decided = true; + } + break; + default: + break; + } + infX = *(float*)&ScriptParams[1]; + infY = *(float*)&ScriptParams[2]; + if (b3D) { + infZ = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[6]; + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + side2length = *(float*)&ScriptParams[7]; + debug = ScriptParams[8]; + } + else { + supX = *(float*)&ScriptParams[3]; + supY = *(float*)&ScriptParams[4]; + side2length = *(float*)&ScriptParams[5]; + debug = ScriptParams[6]; + } + float initAngle = CGeneral::GetRadianAngleBetweenPoints(infX, infY, supX, supY) + HALFPI; + while (initAngle < 0.0f) + initAngle += TWOPI; + while (initAngle > TWOPI) + initAngle -= TWOPI; + // it looks like the idea is to use a rectangle using the diagonal of the rectangle as + // the side of new rectangle, with "length" being the length of second side + float rotatedSupX = supX + side2length * Sin(initAngle); + float rotatedSupY = supY - side2length * Cos(initAngle); + float rotatedInfX = infX + side2length * Sin(initAngle); + float rotatedInfY = infY - side2length * Cos(initAngle); + float side1X = supX - infX; + float side1Y = supY - infY; + float side1Length = CVector2D(side1X, side1Y).Magnitude(); + float side2X = rotatedInfX - infX; + float side2Y = rotatedInfY - infY; + float side2Length = CVector2D(side2X, side2Y).Magnitude(); // == side2length? + if (!decided) { + CVector pos = pPlayerInfo->GetPos(); + result = false; + float X = pos.x - infX; + float Y = pos.y - infY; + float positionAlongSide1 = X * side1X / side1Length + Y * side1Y / side1Length; + bool in_area = false; + if (positionAlongSide1 >= 0.0f && positionAlongSide1 <= side1Length) { + float positionAlongSide2 = X * side2X / side2Length + Y * side2Y / side2Length; + if (positionAlongSide2 >= 0.0f && positionAlongSide2 <= side2Length) { + in_area = !b3D || pos.z >= infZ && pos.z <= supZ; + } + } + + if (in_area) { + switch (command) { + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D: + result = true; + break; + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D: + result = !pPlayerInfo->m_pPed->bInVehicle; + break; + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D: + result = pPlayerInfo->m_pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantAngledArea((uintptr)this + m_nIp, infX, infY, supX, supY, + rotatedSupX, rotatedSupY, rotatedInfX, rotatedInfY, b3D ? (infZ + supZ) / 2 : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugAngledCube(infX, infY, infZ, supX, supY, supZ, + rotatedSupX, rotatedSupY, rotatedInfX, rotatedInfY); + else + CTheScripts::DrawDebugAngledSquare(infX, infY, supX, supY, + rotatedSupX, rotatedSupY, rotatedInfX, rotatedInfY); + } +} + +void CRunningScript::CharInAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float infX, infY, infZ, supX, supY, supZ; + switch (command) { + case COMMAND_IS_CHAR_IN_AREA_3D: + case COMMAND_IS_CHAR_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_IN_AREA_IN_CAR_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + switch (command) { + case COMMAND_IS_CHAR_STOPPED_IN_AREA_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_2D: + if (!CTheScripts::IsPedStopped(pPed)) { + result = false; + decided = true; + } + break; + default: + break; + } + infX = *(float*)&ScriptParams[1]; + infY = *(float*)&ScriptParams[2]; + if (b3D) { + infZ = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[6]; + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + debug = ScriptParams[7]; + } + else { + supX = *(float*)&ScriptParams[3]; + supY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (infX > supX) { + float tmp = infX; + infX = supX; + supX = tmp; + } + if (infY > supY) { + float tmp = infY; + infY = supY; + supY = tmp; + } + if (!decided) { + result = false; + bool in_area; + if (b3D) { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y && + infZ <= pos.z && + supZ >= pos.z; + } + else { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_IS_CHAR_IN_AREA_2D: + case COMMAND_IS_CHAR_IN_AREA_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_3D: + result = true; + break; + case COMMAND_IS_CHAR_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_CHAR_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D: + result = !pPed->bInVehicle; + break; + case COMMAND_IS_CHAR_IN_AREA_IN_CAR_2D: + case COMMAND_IS_CHAR_IN_AREA_IN_CAR_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, infX, infY, supX, supY, b3D ? (infZ + supZ) / 2 : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + else + CTheScripts::DrawDebugSquare(infX, infY, supX, supY); + } +} + +void CRunningScript::CarInAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float infX, infY, infZ, supX, supY, supZ; + switch (command) { + case COMMAND_IS_CAR_IN_AREA_3D: + case COMMAND_IS_CAR_STOPPED_IN_AREA_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVector pos = pVehicle->GetPosition(); + switch (command) { + case COMMAND_IS_CAR_STOPPED_IN_AREA_3D: + case COMMAND_IS_CAR_STOPPED_IN_AREA_2D: + if (!CTheScripts::IsVehicleStopped(pVehicle)) { + result = false; + decided = true; + } + break; + default: + break; + } + infX = *(float*)&ScriptParams[1]; + infY = *(float*)&ScriptParams[2]; + if (b3D) { + infZ = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[6]; + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + debug = ScriptParams[7]; + } + else { + supX = *(float*)&ScriptParams[3]; + supY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (infX > supX) { + float tmp = infX; + infX = supX; + supX = tmp; + } + if (infY > supY) { + float tmp = infY; + infY = supY; + supY = tmp; + } + if (!decided) { + result = false; + bool in_area; + if (b3D) { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y && + infZ <= pos.z && + supZ >= pos.z; + } + else { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_IS_CAR_IN_AREA_2D: + case COMMAND_IS_CAR_IN_AREA_3D: + case COMMAND_IS_CAR_STOPPED_IN_AREA_2D: + case COMMAND_IS_CAR_STOPPED_IN_AREA_3D: + result = true; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, infX, infY, supX, supY, b3D ? (infZ + supZ) / 2 : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + else + CTheScripts::DrawDebugSquare(infX, infY, supX, supY); + } +} + +void CRunningScript::DoDeatharrestCheck() +{ + if (!m_bDeatharrestEnabled) + return; + if (!CTheScripts::IsPlayerOnAMission()) + return; + CPlayerInfo* pPlayer = &CWorld::Players[CWorld::PlayerInFocus]; + if (!pPlayer->IsRestartingAfterDeath() && !pPlayer->IsRestartingAfterArrest() && !CTheScripts::UpsideDownCars.AreAnyCarsUpsideDown()) + return; +#ifdef MISSION_REPLAY + if (AllowMissionReplay != MISSION_RETRY_STAGE_NORMAL) + return; + if (CanAllowMissionReplay()) + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_SCRIPT_TO_TERMINATE; +#endif + script_assert(m_nStackPointer > 0); + while (m_nStackPointer > 1) + --m_nStackPointer; + m_nIp = m_anStack[--m_nStackPointer]; + int16 messageId; + if (pPlayer->IsRestartingAfterDeath()) + messageId = 0; + else if (pPlayer->IsRestartingAfterArrest()) + messageId = 5; + else + messageId = 10; + messageId += CGeneral::GetRandomNumberInRange(0, 5); + bool found = false; + for (int16 contact = 0; !found && contact < MAX_NUM_CONTACTS; contact++) { + int contactFlagOffset = CTheScripts::OnAMissionForContactFlag[contact]; + if (contactFlagOffset && CTheScripts::ScriptSpace[contactFlagOffset] == 1) { + messageId += CTheScripts::BaseBriefIdForContact[contact]; + found = true; + } + } + if (!found) + messageId = 8001; + char tmp[16]; + sprintf(tmp, "%d", messageId); + CMessages::ClearSmallMessagesOnly(); + wchar* text = TheText.Get(tmp); + // ...and do nothing about it + *(int32*)&CTheScripts::ScriptSpace[CTheScripts::OnAMissionFlag] = 0; + m_bDeatharrestExecuted = true; + m_nWakeTime = 0; +} + +int16 CRunningScript::GetPadState(uint16 pad, uint16 button) +{ + CPad* pPad = CPad::GetPad(pad); + switch (button) { + case 0: return pPad->NewState.LeftStickX; + case 1: return pPad->NewState.LeftStickY; + case 2: return pPad->NewState.RightStickX; + case 3: return pPad->NewState.RightStickY; + case 4: return pPad->NewState.LeftShoulder1; + case 5: return pPad->NewState.LeftShoulder2; + case 6: return pPad->NewState.RightShoulder1; + case 7: return pPad->NewState.RightShoulder2; + case 8: return pPad->NewState.DPadUp; + case 9: return pPad->NewState.DPadDown; + case 10: return pPad->NewState.DPadLeft; + case 11: return pPad->NewState.DPadRight; + case 12: return pPad->NewState.Start; + case 13: return pPad->NewState.Select; + case 14: return pPad->NewState.Square; + case 15: return pPad->NewState.Triangle; + case 16: return pPad->NewState.Cross; + case 17: return pPad->NewState.Circle; + case 18: return pPad->NewState.LeftShock; + case 19: return pPad->NewState.RightShock; + default: break; + } + return 0; +} + +#ifdef GTA_SCRIPT_COLLECTIVE +void CRunningScript::LocateCollectiveCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_COLL_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D: + b3D = false; + break; + default: + b3D = true; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + X = *(float*)&ScriptParams[1]; + Y = *(float*)&ScriptParams[2]; + if (b3D) { + Z = *(float*)&ScriptParams[3]; + dX = *(float*)&ScriptParams[4]; + dY = *(float*)&ScriptParams[5]; + dZ = *(float*)&ScriptParams[6]; + debug = ScriptParams[7]; + } + else { + dX = *(float*)&ScriptParams[3]; + dY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES && result; i++) { + if (ScriptParams[0] != CTheScripts::CollectiveArray[i].colIndex) + continue; + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + continue; + } + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + switch (command) { + case COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D: + if (!CTheScripts::IsPedStopped(pPed)) { + result = false; + decided = true; + } + break; + default: + break; + } + if (!decided) { + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + result = false; + if (in_area) { + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D: + result = true; + break; + case COMMAND_LOCATE_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_COLL_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCollectiveCharCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_COLL_IN_CAR_CHAR_2D: + b3D = false; + break; + default: + b3D = true; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + if (pTarget->bInVehicle) { + X = pTarget->m_pMyVehicle->GetPosition().x; + Y = pTarget->m_pMyVehicle->GetPosition().y; + Z = pTarget->m_pMyVehicle->GetPosition().z; + } + else { + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + } + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES && result; i++) { + if (ScriptParams[0] != CTheScripts::CollectiveArray[i].colIndex) + continue; + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + continue; + } + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + result = false; + if (in_area) { + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_CHAR_2D: + result = true; + break; + case COMMAND_LOCATE_COLL_ON_FOOT_CHAR_2D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_COLL_IN_CAR_CHAR_2D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCollectiveCarCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_COLL_IN_CAR_CAR_2D: + b3D = false; + break; + default: + b3D = true; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CVehicle* pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES && result; i++) { + if (ScriptParams[0] != CTheScripts::CollectiveArray[i].colIndex) + continue; + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + continue; + } + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + result = false; + if (in_area) { + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_CAR_2D: + result = true; + break; + case COMMAND_LOCATE_COLL_ON_FOOT_CAR_2D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_COLL_IN_CAR_CAR_2D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCollectivePlayerCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_PLAYER_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_PLAYER_2D: + case COMMAND_LOCATE_COLL_IN_CAR_PLAYER_2D: + b3D = false; + break; + default: + b3D = true; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CVector pos = CWorld::Players[ScriptParams[1]].GetPos(); + X = pos.x; + Y = pos.y; + Z = pos.z; + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES && result; i++) { + if (ScriptParams[0] != CTheScripts::CollectiveArray[i].colIndex) + continue; + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + continue; + } + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + result = false; + if (in_area) { + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_PLAYER_2D: + result = true; + break; + case COMMAND_LOCATE_COLL_ON_FOOT_PLAYER_2D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_COLL_IN_CAR_PLAYER_2D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::CollectiveInAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float infX, infY, infZ, supX, supY, supZ; + switch (command) { + case COMMAND_IS_COLL_IN_AREA_2D: + case COMMAND_IS_COLL_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_IN_AREA_IN_CAR_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D: + b3D = false; + break; + default: + b3D = true; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + infX = *(float*)&ScriptParams[1]; + infY = *(float*)&ScriptParams[2]; + if (b3D) { + infZ = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[6]; + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + debug = ScriptParams[7]; + } + else { + supX = *(float*)&ScriptParams[3]; + supY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (infX > supX) { + float tmp = infX; + infX = supX; + supX = tmp; + } + if (infY > supY) { + float tmp = infY; + infY = supY; + supY = tmp; + } + result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES && result; i++) { + if (ScriptParams[0] != CTheScripts::CollectiveArray[i].colIndex) + continue; + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + continue; + } + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + switch (command) { + case COMMAND_IS_COLL_STOPPED_IN_AREA_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D: + if (!CTheScripts::IsPedStopped(pPed)) { + result = false; + decided = true; + } + break; + default: + break; + } + if (!decided) { + bool in_area; + if (b3D) { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y && + infZ <= pos.z && + supZ >= pos.z; + } + else { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y; + } + result = false; + if (in_area) { + switch (command) { + case COMMAND_IS_COLL_IN_AREA_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_2D: + result = true; + break; + case COMMAND_IS_COLL_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D: + result = !pPed->bInVehicle; + break; + case COMMAND_IS_COLL_IN_AREA_IN_CAR_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, infX, infY, supX, supY, b3D ? (infZ + supZ) / 2 : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + else + CTheScripts::DrawDebugSquare(infX, infY, supX, supY); + } +} +#endif + +void CTheScripts::PrintListSizes() +{ + int active = 0; + int idle = 0; + + for (CRunningScript* pScript = pActiveScripts; pScript; pScript = pScript->GetNext()) + active++; + for (CRunningScript* pScript = pIdleScripts; pScript; pScript = pScript->GetNext()) + idle++; + + debug("active: %d, idle: %d", active, idle); +} + +uint32 DbgLineColour = 0x0000FFFF; // r = 0, g = 0, b = 255, a = 255 + +void CTheScripts::DrawDebugSquare(float infX, float infY, float supX, float supY) +{ + CColPoint tmpCP; + CEntity* tmpEP; + CVector p1, p2, p3, p4; + p1 = CVector(infX, infY, -1000.0f); + CWorld::ProcessVerticalLine(p1, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p1.z = 2.0f + tmpCP.point.z; + p2 = CVector(supX, supY, -1000.0f); + CWorld::ProcessVerticalLine(p2, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p2.z = 2.0f + tmpCP.point.z; + p3 = CVector(infX, supY, -1000.0f); + CWorld::ProcessVerticalLine(p3, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p3.z = 2.0f + tmpCP.point.z; + p4 = CVector(supX, infY, -1000.0f); + CWorld::ProcessVerticalLine(p4, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p4.z = 2.0f + tmpCP.point.z; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p3.x, p3.y, p3.z, p4.x, p4.y, p4.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p4.x, p4.y, p4.z, p1.x, p1.y, p1.z, DbgLineColour, DbgLineColour); +} + +void CTheScripts::DrawDebugAngledSquare(float infX, float infY, float supX, float supY, float rotSupX, float rotSupY, float rotInfX, float rotInfY) +{ + CColPoint tmpCP; + CEntity* tmpEP; + CVector p1, p2, p3, p4; + p1 = CVector(infX, infY, -1000.0f); + CWorld::ProcessVerticalLine(p1, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p1.z = 2.0f + tmpCP.point.z; + p2 = CVector(supX, supY, -1000.0f); + CWorld::ProcessVerticalLine(p2, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p2.z = 2.0f + tmpCP.point.z; + p3 = CVector(rotSupX, rotSupY, -1000.0f); + CWorld::ProcessVerticalLine(p3, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p3.z = 2.0f + tmpCP.point.z; + p4 = CVector(rotInfX, rotInfY, -1000.0f); + CWorld::ProcessVerticalLine(p4, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p4.z = 2.0f + tmpCP.point.z; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p3.x, p3.y, p3.z, p4.x, p4.y, p4.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p4.x, p4.y, p4.z, p1.x, p1.y, p1.z, DbgLineColour, DbgLineColour); +} + +void CTheScripts::DrawDebugCube(float infX, float infY, float infZ, float supX, float supY, float supZ) +{ + CTheScripts::ScriptDebugLine3D(infX, infY, infZ, supX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, infZ, supX, supY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, supY, infZ, infX, supY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, supY, infZ, infX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, infY, supZ, supX, infY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, supZ, supX, supY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, supY, supZ, infX, supY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, supY, supZ, infX, infY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, infY, supZ, infX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, supZ, supX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, supY, supZ, supX, supY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, supY, supZ, infX, supY, infZ, DbgLineColour, DbgLineColour); +} + +void CTheScripts::DrawDebugAngledCube(float infX, float infY, float infZ, float supX, float supY, float supZ, float rotSupX, float rotSupY, float rotInfX, float rotInfY) +{ + CTheScripts::ScriptDebugLine3D(infX, infY, infZ, supX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, infZ, rotSupX, rotSupY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotSupX, rotSupY, infZ, rotInfX, rotInfY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotInfX, rotInfY, infZ, infX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, infY, supZ, supX, infY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, supZ, rotSupX, rotSupY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotSupX, rotSupY, rotInfX, rotInfY, supY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotInfX, rotInfY, supZ, infX, infY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, infY, supZ, infX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, supZ, supX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotSupX, rotSupY, supZ, rotSupX, rotSupY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotInfX, rotInfY, supZ, rotInfX, rotInfY, infZ, DbgLineColour, DbgLineColour); +} + +void CTheScripts::ScriptDebugLine3D(float x1, float y1, float z1, float x2, float y2, float z2, uint32 col, uint32 col2) +{ + if (NumScriptDebugLines >= MAX_NUM_STORED_LINES) + return; + aStoredLines[NumScriptDebugLines].vecInf = CVector(x1, y1, z1); + aStoredLines[NumScriptDebugLines].vecSup = CVector(x2, y2, z2); + aStoredLines[NumScriptDebugLines].color1 = col; + aStoredLines[NumScriptDebugLines++].color2 = col2; +} + +void CTheScripts::RenderTheScriptDebugLines() +{ + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)1); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)1); + for (int i = 0; i < NumScriptDebugLines; i++) { + CLines::RenderLineWithClipping( + aStoredLines[i].vecInf.x, + aStoredLines[i].vecInf.y, + aStoredLines[i].vecInf.z, + aStoredLines[i].vecSup.x, + aStoredLines[i].vecSup.y, + aStoredLines[i].vecSup.z, + aStoredLines[i].color1, + aStoredLines[i].color2); + } + NumScriptDebugLines = 0; + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)0); +} + +#define SCRIPT_DATA_SIZE sizeof(CTheScripts::OnAMissionFlag) + sizeof(CTheScripts::BaseBriefIdForContact) + sizeof(CTheScripts::OnAMissionForContactFlag) +\ + sizeof(CTheScripts::CollectiveArray) + 4 * sizeof(uint32) * MAX_NUM_BUILDING_SWAPS + 2 * sizeof(uint32) * MAX_NUM_INVISIBILITY_SETTINGS + 5 * sizeof(uint32) + +void CTheScripts::SaveAllScripts(uint8* buf, uint32* size) +{ +INITSAVEBUF + uint32 varSpace = GetSizeOfVariableSpace(); + uint32 runningScripts = 0; + for (CRunningScript* pScript = pActiveScripts; pScript; pScript = pScript->GetNext()) + runningScripts++; + *size = CRunningScript::nSaveStructSize * runningScripts + varSpace + SCRIPT_DATA_SIZE + SAVE_HEADER_SIZE + 3 * sizeof(uint32); + WriteSaveHeader(buf, 'S', 'C', 'R', '\0', *size - SAVE_HEADER_SIZE); + WriteSaveBuf(buf, varSpace); + for (uint32 i = 0; i < varSpace; i++) + WriteSaveBuf(buf, ScriptSpace[i]); +#ifdef CHECK_STRUCT_SIZES + static_assert(SCRIPT_DATA_SIZE == 968, "CTheScripts::SaveAllScripts"); +#endif + uint32 script_data_size = SCRIPT_DATA_SIZE; + WriteSaveBuf(buf, script_data_size); + WriteSaveBuf(buf, OnAMissionFlag); + for (uint32 i = 0; i < MAX_NUM_CONTACTS; i++) { + WriteSaveBuf(buf, OnAMissionForContactFlag[i]); + WriteSaveBuf(buf, BaseBriefIdForContact[i]); + } + for (uint32 i = 0; i < MAX_NUM_COLLECTIVES; i++) + WriteSaveBuf(buf, CollectiveArray[i]); + WriteSaveBuf(buf, NextFreeCollectiveIndex); + for (uint32 i = 0; i < MAX_NUM_BUILDING_SWAPS; i++) { + CBuilding* pBuilding = BuildingSwapArray[i].m_pBuilding; + uint32 type, handle; + if (!pBuilding) { + type = 0; + handle = 0; + } else if (pBuilding->GetIsATreadable()) { + type = 1; + handle = CPools::GetTreadablePool()->GetJustIndex_NoFreeAssert((CTreadable*)pBuilding) + 1; + } else { + type = 2; + handle = CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert(pBuilding) + 1; + } + WriteSaveBuf(buf, type); + WriteSaveBuf(buf, handle); + WriteSaveBuf(buf, BuildingSwapArray[i].m_nNewModel); + WriteSaveBuf(buf, BuildingSwapArray[i].m_nOldModel); + } + for (uint32 i = 0; i < MAX_NUM_INVISIBILITY_SETTINGS; i++) { + CEntity* pEntity = InvisibilitySettingArray[i]; + uint32 type, handle; + if (!pEntity) { + type = 0; + handle = 0; + } else { + switch (pEntity->GetType()) { + case ENTITY_TYPE_BUILDING: + if (((CBuilding*)pEntity)->GetIsATreadable()) { + type = 1; + handle = CPools::GetTreadablePool()->GetJustIndex_NoFreeAssert((CTreadable*)pEntity) + 1; + } else { + type = 2; + handle = CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert((CBuilding*)pEntity) + 1; + } + break; + case ENTITY_TYPE_OBJECT: + type = 3; + handle = CPools::GetObjectPool()->GetJustIndex_NoFreeAssert((CObject*)pEntity) + 1; + break; + case ENTITY_TYPE_DUMMY: + type = 4; + handle = CPools::GetDummyPool()->GetJustIndex_NoFreeAssert((CDummy*)pEntity) + 1; + default: break; + } + } + WriteSaveBuf(buf, type); + WriteSaveBuf(buf, handle); + } + WriteSaveBuf(buf, bUsingAMultiScriptFile); + WriteSaveBuf(buf, (uint8)0); + WriteSaveBuf(buf, (uint16)0); + WriteSaveBuf(buf, MainScriptSize); + WriteSaveBuf(buf, LargestMissionScriptSize); + WriteSaveBuf(buf, NumberOfMissionScripts); + WriteSaveBuf(buf, (uint16)0); + WriteSaveBuf(buf, runningScripts); + for (CRunningScript* pScript = pActiveScripts; pScript; pScript = pScript->GetNext()) + pScript->Save(buf); +VALIDATESAVEBUF(*size) +} + +void CTheScripts::LoadAllScripts(uint8* buf, uint32 size) +{ + Init(); +INITSAVEBUF + CheckSaveHeader(buf, 'S', 'C', 'R', '\0', size - SAVE_HEADER_SIZE); + uint32 varSpace, type, handle; + uint32 tmp; + + ReadSaveBuf(&varSpace, buf); + for (uint32 i = 0; i < varSpace; i++) + ReadSaveBuf(&ScriptSpace[i], buf); + ReadSaveBuf(&tmp, buf); + script_assert(tmp == SCRIPT_DATA_SIZE); + ReadSaveBuf(&OnAMissionFlag, buf); + for (uint32 i = 0; i < MAX_NUM_CONTACTS; i++) { + ReadSaveBuf(&OnAMissionForContactFlag[i], buf); + ReadSaveBuf(&BaseBriefIdForContact[i], buf); + } + for (uint32 i = 0; i < MAX_NUM_COLLECTIVES; i++) + ReadSaveBuf(&CollectiveArray[i], buf); + ReadSaveBuf(&NextFreeCollectiveIndex, buf); + for (uint32 i = 0; i < MAX_NUM_BUILDING_SWAPS; i++) { + ReadSaveBuf(&type, buf); + ReadSaveBuf(&handle, buf); + switch (type) { + case 0: + BuildingSwapArray[i].m_pBuilding = nil; + break; + case 1: + BuildingSwapArray[i].m_pBuilding = CPools::GetTreadablePool()->GetSlot(handle - 1); + break; + case 2: + BuildingSwapArray[i].m_pBuilding = CPools::GetBuildingPool()->GetSlot(handle - 1); + break; + default: + script_assert(false); + } + ReadSaveBuf(&BuildingSwapArray[i].m_nNewModel, buf); + ReadSaveBuf(&BuildingSwapArray[i].m_nOldModel, buf); + if (BuildingSwapArray[i].m_pBuilding) + BuildingSwapArray[i].m_pBuilding->ReplaceWithNewModel(BuildingSwapArray[i].m_nNewModel); + } + for (uint32 i = 0; i < MAX_NUM_INVISIBILITY_SETTINGS; i++) { + ReadSaveBuf(&type, buf); + ReadSaveBuf(&handle, buf); + switch (type) { + case 0: + InvisibilitySettingArray[i] = nil; + break; + case 1: + InvisibilitySettingArray[i] = CPools::GetTreadablePool()->GetSlot(handle - 1); + break; + case 2: + InvisibilitySettingArray[i] = CPools::GetBuildingPool()->GetSlot(handle - 1); + break; + case 3: + InvisibilitySettingArray[i] = CPools::GetObjectPool()->GetSlot(handle - 1); + break; + case 4: + InvisibilitySettingArray[i] = CPools::GetDummyPool()->GetSlot(handle - 1); + break; + default: + script_assert(false); + } + if (InvisibilitySettingArray[i]) + InvisibilitySettingArray[i]->bIsVisible = false; + } + bool tmpBool; + ReadSaveBuf(&tmpBool, buf); + script_assert(tmpBool == bUsingAMultiScriptFile); + SkipSaveBuf(buf, 3); + ReadSaveBuf(&tmp, buf); + script_assert(tmp == MainScriptSize); + ReadSaveBuf(&tmp, buf); + script_assert(tmp == LargestMissionScriptSize); + uint16 tmp16; + ReadSaveBuf(&tmp16, buf); + script_assert(tmp16 == NumberOfMissionScripts); + SkipSaveBuf(buf, 2); + uint32 runningScripts; + ReadSaveBuf(&runningScripts, buf); + for (uint32 i = 0; i < runningScripts; i++) + StartNewScript(0)->Load(buf); +VALIDATESAVEBUF(size) +} + +#undef SCRIPT_DATA_SIZE + +void CRunningScript::Save(uint8*& buf) +{ +#ifdef COMPATIBLE_SAVES + ZeroSaveBuf(buf, 8); + for (int i = 0; i < 8; i++) + WriteSaveBuf(buf, m_abScriptName[i]); + WriteSaveBuf(buf, m_nIp); +#ifdef CHECK_STRUCT_SIZES + static_assert(MAX_STACK_DEPTH == 6, "Compatibility loss: MAX_STACK_DEPTH != 6"); +#endif + for (int i = 0; i < MAX_STACK_DEPTH; i++) + WriteSaveBuf(buf, m_anStack[i]); + WriteSaveBuf(buf, m_nStackPointer); + ZeroSaveBuf(buf, 2); +#ifdef CHECK_STRUCT_SIZES + static_assert(NUM_LOCAL_VARS + NUM_TIMERS == 18, "Compatibility loss: NUM_LOCAL_VARS + NUM_TIMERS != 18"); +#endif + for (int i = 0; i < NUM_LOCAL_VARS + NUM_TIMERS; i++) + WriteSaveBuf(buf, m_anLocalVariables[i]); + WriteSaveBuf(buf, m_bCondResult); + WriteSaveBuf(buf, m_bIsMissionScript); + WriteSaveBuf(buf, m_bSkipWakeTime); + ZeroSaveBuf(buf, 1); + WriteSaveBuf(buf, m_nWakeTime); + WriteSaveBuf(buf, m_nAndOrState); + WriteSaveBuf(buf, m_bNotFlag); + WriteSaveBuf(buf, m_bDeatharrestEnabled); + WriteSaveBuf(buf, m_bDeatharrestExecuted); + WriteSaveBuf(buf, m_bMissionFlag); + ZeroSaveBuf(buf, 2); +#else + WriteSaveBuf(buf, *this); +#endif +} + +void CRunningScript::Load(uint8*& buf) +{ +#ifdef COMPATIBLE_SAVES + SkipSaveBuf(buf, 8); + for (int i = 0; i < 8; i++) + ReadSaveBuf(&m_abScriptName[i], buf); + ReadSaveBuf(&m_nIp, buf); +#ifdef CHECK_STRUCT_SIZES + static_assert(MAX_STACK_DEPTH == 6, "Compatibility loss: MAX_STACK_DEPTH != 6"); +#endif + for (int i = 0; i < MAX_STACK_DEPTH; i++) + ReadSaveBuf(&m_anStack[i], buf); + ReadSaveBuf(&m_nStackPointer, buf); + SkipSaveBuf(buf, 2); +#ifdef CHECK_STRUCT_SIZES + static_assert(NUM_LOCAL_VARS + NUM_TIMERS == 18, "Compatibility loss: NUM_LOCAL_VARS + NUM_TIMERS != 18"); +#endif + for (int i = 0; i < NUM_LOCAL_VARS + NUM_TIMERS; i++) + ReadSaveBuf(&m_anLocalVariables[i], buf); + ReadSaveBuf(&m_bCondResult, buf); + ReadSaveBuf(&m_bIsMissionScript, buf); + ReadSaveBuf(&m_bSkipWakeTime, buf); + SkipSaveBuf(buf, 1); + ReadSaveBuf(&m_nWakeTime, buf); + ReadSaveBuf(&m_nAndOrState, buf); + ReadSaveBuf(&m_bNotFlag, buf); + ReadSaveBuf(&m_bDeatharrestEnabled, buf); + ReadSaveBuf(&m_bDeatharrestExecuted, buf); + ReadSaveBuf(&m_bMissionFlag, buf); + SkipSaveBuf(buf, 2); +#else + CRunningScript* n = next; + CRunningScript* p = prev; + ReadSaveBuf(this, buf); + next = n; + prev = p; +#endif +} + +void CTheScripts::ClearSpaceForMissionEntity(const CVector& pos, CEntity* pEntity) +{ + static CColPoint aTempColPoints[MAX_COLLISION_POINTS]; + int16 entities = 0; + CEntity* aEntities[16]; + CWorld::FindObjectsKindaColliding(pos, pEntity->GetBoundRadius(), false, &entities, 16, aEntities, false, true, true, false, false); + if (entities <= 0) + return; + for (uint16 i = 0; i < entities; i++) { + if (aEntities[i] != pEntity && aEntities[i]->IsPed() && ((CPed*)aEntities[i])->bInVehicle) + aEntities[i] = nil; + } + for (uint16 i = 0; i < entities; i++) { + if (aEntities[i] == pEntity || !aEntities[i]) + continue; + CEntity* pFound = aEntities[i]; + int cols; + if (pEntity->GetColModel()->numLines <= 0) + cols = CCollision::ProcessColModels(pEntity->GetMatrix(), *pEntity->GetColModel(), + pFound->GetMatrix(), *pFound->GetColModel(), aTempColPoints, nil, nil); + else { + float lines[4]; + lines[0] = lines[1] = lines[2] = lines[3] = 1.0f; + CColPoint tmp[4]; + cols = CCollision::ProcessColModels(pEntity->GetMatrix(), *pEntity->GetColModel(), + pFound->GetMatrix(), *pFound->GetColModel(), aTempColPoints,tmp, lines); + } + if (cols <= 0) + continue; + switch (pFound->GetType()) { + case ENTITY_TYPE_VEHICLE: + { + printf("Will try to delete a vehicle where a mission entity should be\n"); + CVehicle* pVehicle = (CVehicle*)pFound; + if (pVehicle->bIsLocked || !pVehicle->CanBeDeleted()) + break; + if (pVehicle->pDriver) { + CPopulation::RemovePed(pVehicle->pDriver); + pVehicle->pDriver = nil; + } + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) { + if (pVehicle->pPassengers[i]) { + CPopulation::RemovePed(pVehicle->pPassengers[i]); + pVehicle->pPassengers[i] = 0; + pVehicle->m_nNumPassengers--; + } + } + CCarCtrl::RemoveFromInterestingVehicleList(pVehicle); + CWorld::Remove(pVehicle); + delete pVehicle; + break; + } + case ENTITY_TYPE_PED: + { + CPed* pPed = (CPed*)pFound; + if (pPed->IsPlayer() || !pPed->CanBeDeleted()) + break; + CPopulation::RemovePed(pPed); + printf("Deleted a ped where a mission entity should be\n"); + break; + } + default: break; + } + } +} + +void CTheScripts::HighlightImportantArea(uint32 id, float x1, float y1, float x2, float y2, float z) +{ + float infX, infY, supX, supY; + if (x1 < x2) { + infX = x1; + supX = x2; + } else { + infX = x2; + supX = x1; + } + if (y1 < y2) { + infY = y1; + supY = y2; + } + else { + infY = y2; + supY = y1; + } + CVector center; + center.x = (infX + supX) / 2; + center.y = (infY + supY) / 2; + center.z = (z <= MAP_Z_LOW_LIMIT) ? CWorld::FindGroundZForCoord(center.x, center.y) : z; + CShadows::RenderIndicatorShadow(id, 2, gpGoalTex, ¢er, supX - center.x, 0.0f, 0.0f, center.y - supY, 0); +} + +void CTheScripts::HighlightImportantAngledArea(uint32 id, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float z) +{ + float infX, infY, supX, supY, X, Y; + X = (x1 + x2) / 2; + Y = (y1 + y2) / 2; + supX = infX = X; + supY = infY = Y; + X = (x2 + x3) / 2; + Y = (y2 + y3) / 2; + infX = Min(infX, X); + supX = Max(supX, X); + infY = Min(infY, Y); + supY = Max(supY, Y); + X = (x3 + x4) / 2; + Y = (y3 + y4) / 2; + infX = Min(infX, X); + supX = Max(supX, X); + infY = Min(infY, Y); + supY = Max(supY, Y); + X = (x4 + x1) / 2; + Y = (y4 + y1) / 2; + infX = Min(infX, X); + supX = Max(supX, X); + infY = Min(infY, Y); + supY = Max(supY, Y); + CVector center; + center.x = (infX + supX) / 2; + center.y = (infY + supY) / 2; + center.z = (z <= MAP_Z_LOW_LIMIT) ? CWorld::FindGroundZForCoord(center.x, center.y) : z; + CShadows::RenderIndicatorShadow(id, 2, gpGoalTex, ¢er, supX - center.x, 0.0f, 0.0f, center.y - supY, 0); +} + +#ifdef GTA_SCRIPT_COLLECTIVE +int CTheScripts::AddPedsInVehicleToCollective(int index) +{ + int colIndex = NextFreeCollectiveIndex; + AdvanceCollectiveIndex(); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(index); + script_assert(pVehicle); + CPed* pDriver = pVehicle->pDriver; + if (pDriver && !pDriver->IsPlayer() && pDriver->CharCreatedBy != MISSION_CHAR && pDriver->m_nPedType != PEDTYPE_COP) { + int index = FindFreeSlotInCollectiveArray(); + if (index > -1) { + CollectiveArray[index].colIndex = colIndex; + CollectiveArray[index].pedIndex = CPools::GetPedPool()->GetIndex(pDriver); + } + } + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) { + CPed* pPassenger = pVehicle->pPassengers[i]; + if (pPassenger && !pPassenger->IsPlayer() && pPassenger->CharCreatedBy != MISSION_CHAR && pPassenger->m_nPedType != PEDTYPE_COP) { + int index = FindFreeSlotInCollectiveArray(); + if (index > -1) { + CollectiveArray[index].colIndex = colIndex; + CollectiveArray[index].pedIndex = CPools::GetPedPool()->GetIndex(pPassenger); + } + } + } + return colIndex; +} + +int CTheScripts::AddPedsInAreaToCollective(float x, float y, float z, float radius) +{ + int16 numFound; + CEntity* pEntities[64]; + int colIndex = NextFreeCollectiveIndex; + AdvanceCollectiveIndex(); + CWorld::FindObjectsInRange(CVector(x, y, z), radius, true, &numFound, 64, pEntities, false, true, true, false, false); + for (int16 i = 0; i < numFound; i++) { + if (pEntities[i]->GetType() == ENTITY_TYPE_PED) { + CPed* pPed = (CPed*)pEntities[i]; + if (pPed && !pPed->IsPlayer() && pPed->CharCreatedBy != MISSION_CHAR && pPed->m_nPedType != PEDTYPE_COP) { + int index = FindFreeSlotInCollectiveArray(); + if (index > -1) { + CollectiveArray[index].colIndex = colIndex; + CollectiveArray[index].pedIndex = CPools::GetPedPool()->GetIndex(pPed); + } + } + } + else if (pEntities[i]->GetType() == ENTITY_TYPE_VEHICLE) { + CVehicle* pVehicle = (CVehicle*)pEntities[i]; + CPed* pDriver = pVehicle->pDriver; + if (pDriver && !pDriver->IsPlayer() && pDriver->CharCreatedBy != MISSION_CHAR && pDriver->m_nPedType != PEDTYPE_COP) { + int index = FindFreeSlotInCollectiveArray(); + if (index > -1) { + CollectiveArray[index].colIndex = colIndex; + CollectiveArray[index].pedIndex = CPools::GetPedPool()->GetIndex(pDriver); + } + } + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) { + CPed* pPassenger = pVehicle->pPassengers[i]; + if (pPassenger && !pPassenger->IsPlayer() && pPassenger->CharCreatedBy != MISSION_CHAR && pPassenger->m_nPedType != PEDTYPE_COP) { + int index = FindFreeSlotInCollectiveArray(); + if (index > -1) { + CollectiveArray[index].colIndex = colIndex; + CollectiveArray[index].pedIndex = CPools::GetPedPool()->GetIndex(pPassenger); + } + } + } + } + } + return colIndex; +} + +int CTheScripts::FindFreeSlotInCollectiveArray() +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == -1) + return i; + } + return -1; +} + +void CTheScripts::SetObjectiveForAllPedsInCollective(int colIndex, eObjective objective, int16 p1, int16 p2) +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == colIndex) { + CPed* pPed = CPools::GetPedPool()->GetAt(CollectiveArray[i].pedIndex); + if (pPed == nil) { + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + else { + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(objective, p1, p2); + } + } + } +} + +void CTheScripts::SetObjectiveForAllPedsInCollective(int colIndex, eObjective objective, CVector p1, float p2) +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == colIndex) { + CPed* pPed = CPools::GetPedPool()->GetAt(CollectiveArray[i].pedIndex); + if (pPed == nil) { + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + else { + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(objective, p1, p2); + } + } + } +} + +void CTheScripts::SetObjectiveForAllPedsInCollective(int colIndex, eObjective objective, CVector p1) +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == colIndex) { + CPed* pPed = CPools::GetPedPool()->GetAt(CollectiveArray[i].pedIndex); + if (pPed == nil) { + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + else { + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(objective, p1); + } + } + } +} + +void CTheScripts::SetObjectiveForAllPedsInCollective(int colIndex, eObjective objective, void* p1) +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == colIndex) { + CPed* pPed = CPools::GetPedPool()->GetAt(CollectiveArray[i].pedIndex); + if (pPed == nil) { + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + else { + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(objective, p1); + } + } + } +} + +void CTheScripts::SetObjectiveForAllPedsInCollective(int colIndex, eObjective objective) +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == colIndex) { + CPed* pPed = CPools::GetPedPool()->GetAt(CollectiveArray[i].pedIndex); + if (pPed == nil) { + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + else { + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(objective); + } + } + } +} +#endif //GTA_SCRIPT_COLLECTIVE + +bool CTheScripts::IsPedStopped(CPed* pPed) +{ + if (pPed->bInVehicle) + return IsVehicleStopped(pPed->m_pMyVehicle); + return pPed->m_nMoveState == PEDMOVE_NONE || pPed->m_nMoveState == PEDMOVE_STILL; +} + +bool CTheScripts::IsPlayerStopped(CPlayerInfo* pPlayer) +{ + CPed* pPed = pPlayer->m_pPed; + if (pPed->bInVehicle) + return IsVehicleStopped(pPed->m_pMyVehicle); + if (RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_RUNSTOP1) || + RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_RUNSTOP2) || + RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_JUMP_LAUNCH) || + RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_JUMP_GLIDE)) + return false; + return pPed->m_nMoveState == PEDMOVE_NONE || pPed->m_nMoveState == PEDMOVE_STILL; +} + +bool CTheScripts::IsVehicleStopped(CVehicle* pVehicle) +{ + return 0.01f * CTimer::GetTimeStep() >= pVehicle->m_fDistanceTravelled; +} + +void CTheScripts::CleanUpThisPed(CPed* pPed) +{ + if (!pPed) + return; + if (pPed->CharCreatedBy != MISSION_CHAR) + return; + pPed->CharCreatedBy = RANDOM_CHAR; + if (pPed->m_nPedType == PEDTYPE_PROSTITUTE) + pPed->m_objectiveTimer = CTimer::GetTimeInMilliseconds() + 30000; + if (pPed->bInVehicle) { + if (pPed->m_pMyVehicle->pDriver == pPed) { + if (pPed->m_pMyVehicle->m_vehType == VEHICLE_TYPE_CAR) { + CCarCtrl::JoinCarWithRoadSystem(pPed->m_pMyVehicle); + pPed->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + } + } + else { + if (pPed->m_pMyVehicle->m_vehType == VEHICLE_TYPE_CAR) { + pPed->SetObjective(OBJECTIVE_LEAVE_CAR, pPed->m_pMyVehicle); + pPed->bWanderPathAfterExitingCar = true; + } + } + } + bool flees = false; + PedState state; + eMoveState ms; + if (pPed->m_nPedState == PED_FLEE_ENTITY || pPed->m_nPedState == PED_FLEE_POS) { + ms = pPed->m_nMoveState; + state = pPed->m_nPedState; + flees = true; + } + pPed->ClearObjective(); + pPed->bRespondsToThreats = true; + pPed->bScriptObjectiveCompleted = false; + pPed->ClearLeader(); + if (pPed->IsPedInControl()) + pPed->SetWanderPath(CGeneral::GetRandomNumber() & 7); + if (flees) { + pPed->SetPedState(state); + pPed->SetMoveState(ms); + } + --CPopulation::ms_nTotalMissionPeds; +} + +void CTheScripts::CleanUpThisVehicle(CVehicle* pVehicle) +{ + if (!pVehicle) + return; + if (pVehicle->VehicleCreatedBy != MISSION_VEHICLE) + return; + pVehicle->bIsLocked = false; + CCarCtrl::RemoveFromInterestingVehicleList(pVehicle); + pVehicle->VehicleCreatedBy = RANDOM_VEHICLE; + ++CCarCtrl::NumRandomCars; + --CCarCtrl::NumMissionCars; +} + +void CTheScripts::CleanUpThisObject(CObject* pObject) +{ + if (!pObject) + return; + if (pObject->ObjectCreatedBy != MISSION_OBJECT) + return; + pObject->ObjectCreatedBy = TEMP_OBJECT; + pObject->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000; + pObject->m_nRefModelIndex = -1; + pObject->bUseVehicleColours = false; + ++CObject::nNoTempObjects; +} + +void CTheScripts::ReadObjectNamesFromScript() +{ + int32 varSpace = GetSizeOfVariableSpace(); + uint32 ip = varSpace + 8; + NumberOfUsedObjects = Read2BytesFromScript(&ip); + ip += 2; + for (uint16 i = 0; i < NumberOfUsedObjects; i++) { + for (int j = 0; j < USED_OBJECT_NAME_LENGTH; j++) + UsedObjectArray[i].name[j] = ScriptSpace[ip++]; + UsedObjectArray[i].index = 0; + } +} + +void CTheScripts::UpdateObjectIndices() +{ + char name[USED_OBJECT_NAME_LENGTH]; + char error[112]; + for (int i = 1; i < NumberOfUsedObjects; i++) { + bool found = false; + for (int j = 0; j < MODELINFOSIZE && !found; j++) { + CBaseModelInfo* pModel = CModelInfo::GetModelInfo(j); + if (!pModel) + continue; + strcpy(name, pModel->GetModelName()); +#ifdef FIX_BUGS + for (int k = 0; k < USED_OBJECT_NAME_LENGTH && name[k]; k++) +#else + for (int k = 0; k < USED_OBJECT_NAME_LENGTH; k++) +#endif + name[k] = toupper(name[k]); + if (strcmp(name, UsedObjectArray[i].name) == 0) { + found = true; + UsedObjectArray[i].index = j; + } + } + if (!found) { + sprintf(error, "CTheScripts::UpdateObjectIndices - Couldn't find %s", UsedObjectArray[i].name); + debug("%s\n", error); + } + } +} + +void CTheScripts::ReadMultiScriptFileOffsetsFromScript() +{ + int32 varSpace = GetSizeOfVariableSpace(); + uint32 ip = varSpace + 3; + int32 objectSize = Read4BytesFromScript(&ip); + ip = objectSize + 8; + MainScriptSize = Read4BytesFromScript(&ip); + LargestMissionScriptSize = Read4BytesFromScript(&ip); + NumberOfMissionScripts = Read2BytesFromScript(&ip); + ip += 2; + for (int i = 0; i < NumberOfMissionScripts; i++) { + MultiScriptArray[i] = Read4BytesFromScript(&ip); + } +} diff --git a/src/control/Script6.cpp b/src/control/Script6.cpp new file mode 100644 index 0000000..c576e3c --- /dev/null +++ b/src/control/Script6.cpp @@ -0,0 +1,1356 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "Bike.h" +#include "CarCtrl.h" +#include "Cranes.h" +#include "Credits.h" +#include "CutsceneMgr.h" +#include "DMAudio.h" +#include "FileMgr.h" +#include "Fire.h" +#include "Frontend.h" +#include "Garages.h" +#include "General.h" +#ifdef MISSION_REPLAY +#include "GenericGameStorage.h" +#endif +#include "Messages.h" +#include "Pad.h" +#include "Particle.h" +#include "Phones.h" +#include "Population.h" +#include "Pools.h" +#include "Record.h" +#include "Remote.h" +#include "Restart.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "Streaming.h" +#include "Weather.h" +#include "Zones.h" +#include "main.h" + +// NB: on PS2 this file did not exist; ProcessCommands1000To1099 was in Script5.cpp and ProcessCommands1100To1199 was only added on PC +// however to avoid redundant copies of code, Script6.cpp is used with PS2 defines + +int8 CRunningScript::ProcessCommands1000To1099(int32 command) +{ +#if GTA_VERSION <= GTA3_PS2_160 + char tmp[48]; +#endif + switch (command) { + //case COMMAND_FLASH_RADAR_BLIP: + case COMMAND_IS_CHAR_IN_CONTROL: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(pPed->IsPedInControl()); + return 0; + } + case COMMAND_SET_GENERATE_CARS_AROUND_CAMERA: + CollectParameters(&m_nIp, 1); + CCarCtrl::bCarsGeneratedAroundCamera = (ScriptParams[0] != 0); + return 0; + case COMMAND_CLEAR_SMALL_PRINTS: + CMessages::ClearSmallMessagesOnly(); + return 0; + case COMMAND_HAS_MILITARY_CRANE_COLLECTED_ALL_CARS: + UpdateCompareFlag(CCranes::HaveAllCarsBeenCollectedByMilitaryCrane()); + return 0; + case COMMAND_SET_UPSIDEDOWN_CAR_NOT_DAMAGED: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + CAutomobile* pCar = (CAutomobile*)pVehicle; + pCar->bNotDamagedUpsideDown = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_CAN_PLAYER_START_MISSION: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPlayerPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPlayerPed); + UpdateCompareFlag(pPlayerPed->IsPedInControl() || pPlayerPed->m_nPedState == PED_DRIVING); + return 0; + } + case COMMAND_MAKE_PLAYER_SAFE_FOR_CUTSCENE: + { + CollectParameters(&m_nIp, 1); +#ifdef MISSION_REPLAY + AllowMissionReplay = MISSION_RETRY_STAGE_NORMAL; + SaveGameForPause(SAVE_TYPE_QUICKSAVE_FOR_MISSION_REPLAY); +#endif + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + CPad::GetPad(ScriptParams[0])->SetDisablePlayerControls(PLAYERCONTROL_CUTSCENE); + pPlayerInfo->MakePlayerSafe(true); + CCutsceneMgr::StartCutsceneProcessing(); + return 0; + } + case COMMAND_USE_TEXT_COMMANDS: + CollectParameters(&m_nIp, 1); + CTheScripts::UseTextCommands = (ScriptParams[0] != 0) ? 2 : 1; + return 0; + case COMMAND_SET_THREAT_FOR_PED_TYPE: + CollectParameters(&m_nIp, 2); + CPedType::AddThreat(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_CLEAR_THREAT_FOR_PED_TYPE: + CollectParameters(&m_nIp, 2); + CPedType::RemoveThreat(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_GET_CAR_COLOURS: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = pVehicle->m_currentColour1; + ScriptParams[1] = pVehicle->m_currentColour2; + StoreParameters(&m_nIp, 2); + return 0; + } + case COMMAND_SET_ALL_CARS_CAN_BE_DAMAGED: + CollectParameters(&m_nIp, 1); + CWorld::SetAllCarsCanBeDamaged(ScriptParams[0] != 0); + if (!ScriptParams[0]) + CWorld::ExtinguishAllCarFiresInArea(FindPlayerCoors(), 4000.0f); + return 0; + case COMMAND_SET_CAR_CAN_BE_DAMAGED: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + pVehicle->bCanBeDamaged = ScriptParams[1] != 0; + if (!ScriptParams[1]) + pVehicle->ExtinguishCarFire(); + return 0; + } + //case COMMAND_MAKE_PLAYER_UNSAFE: + case COMMAND_LOAD_COLLISION: + { + CollectParameters(&m_nIp, 1); + CTimer::Stop(); + CGame::currLevel = (eLevelName)ScriptParams[0]; + ISLAND_LOADING_IS(LOW) + { + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + } + CCollision::SortOutCollisionAfterLoad(); + ISLAND_LOADING_ISNT(HIGH) + { + CStreaming::RequestIslands(CGame::currLevel); + CStreaming::LoadAllRequestedModels(true); + } + CTimer::Update(); + return 0; + } + case COMMAND_GET_BODY_CAST_HEALTH: + ScriptParams[0] = CObject::nBodyCastHealth; + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_SET_CHARS_CHATTING: + { + CollectParameters(&m_nIp, 3); + CPed* pPed1 = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pPed2 = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pPed1 && pPed2); + pPed1->SetChat(pPed2, ScriptParams[2]); + pPed2->SetChat(pPed1, ScriptParams[2]); + return 0; + } + //case COMMAND_MAKE_PLAYER_SAFE: + case COMMAND_SET_CAR_STAYS_IN_CURRENT_LEVEL: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + if (ScriptParams[1]) + pVehicle->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pVehicle->GetPosition()); + else + pVehicle->m_nZoneLevel = LEVEL_GENERIC; + return 0; + } + case COMMAND_SET_CHAR_STAYS_IN_CURRENT_LEVEL: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) + pPed->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pPed->GetPosition()); + else + pPed->m_nZoneLevel = LEVEL_GENERIC; + return 0; + } + case COMMAND_REGISTER_4X4_ONE_TIME: + CollectParameters(&m_nIp, 1); + CStats::Register4x4OneTime(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_4X4_TWO_TIME: + CollectParameters(&m_nIp, 1); + CStats::Register4x4TwoTime(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_4X4_THREE_TIME: + CollectParameters(&m_nIp, 1); + CStats::Register4x4ThreeTime(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_4X4_MAYHEM_TIME: + CollectParameters(&m_nIp, 1); + CStats::Register4x4MayhemTime(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_LIFE_SAVED: + CStats::AnotherLifeSavedWithAmbulance(); + return 0; + case COMMAND_REGISTER_CRIMINAL_CAUGHT: + CStats::AnotherCriminalCaught(); + return 0; + case COMMAND_REGISTER_AMBULANCE_LEVEL: + CollectParameters(&m_nIp, 1); + CStats::RegisterLevelAmbulanceMission(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_FIRE_EXTINGUISHED: + CStats::AnotherFireExtinguished(); + return 0; + case COMMAND_TURN_PHONE_ON: + CollectParameters(&m_nIp, 1); + gPhoneInfo.m_aPhones[ScriptParams[0]].m_nState = PHONE_STATE_9; + return 0; + case COMMAND_REGISTER_LONGEST_DODO_FLIGHT: + CollectParameters(&m_nIp, 1); + CStats::RegisterLongestFlightInDodo(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_DEFUSE_BOMB_TIME: + CollectParameters(&m_nIp, 1); + CStats::RegisterTimeTakenDefuseMission(ScriptParams[0]); + return 0; + case COMMAND_SET_TOTAL_NUMBER_OF_KILL_FRENZIES: + CollectParameters(&m_nIp, 1); + CStats::SetTotalNumberKillFrenzies(ScriptParams[0]); + return 0; + case COMMAND_BLOW_UP_RC_BUGGY: + CWorld::Players[CWorld::PlayerInFocus].BlowUpRCBuggy(); + return 0; + case COMMAND_REMOVE_CAR_FROM_CHASE: + CollectParameters(&m_nIp, 1); + CRecordDataForChase::RemoveCarFromChase(ScriptParams[0]); + return 0; + case COMMAND_IS_FRENCH_GAME: + UpdateCompareFlag(CGame::frenchGame); + return 0; + case COMMAND_IS_GERMAN_GAME: + UpdateCompareFlag(CGame::germanGame); + return 0; + case COMMAND_CLEAR_MISSION_AUDIO: + DMAudio.ClearMissionAudio(); + return 0; + case COMMAND_SET_FADE_IN_AFTER_NEXT_ARREST: + CollectParameters(&m_nIp, 1); + CRestart::bFadeInAfterNextArrest = !!ScriptParams[0]; + return 0; + case COMMAND_SET_FADE_IN_AFTER_NEXT_DEATH: + CollectParameters(&m_nIp, 1); + CRestart::bFadeInAfterNextDeath = !!ScriptParams[0]; + return 0; + case COMMAND_SET_GANG_PED_MODEL_PREFERENCE: + CollectParameters(&m_nIp, 2); + CGangs::SetGangPedModelOverride(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_SET_CHAR_USE_PEDNODE_SEEK: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) + pPed->m_pNextPathNode = nil; + pPed->bUsePedNodeSeek = !!ScriptParams[1]; + return 0; + } + case COMMAND_SWITCH_VEHICLE_WEAPONS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bGunSwitchedOff = !ScriptParams[1]; + return 0; + } + case COMMAND_SET_GET_OUT_OF_JAIL_FREE: + CollectParameters(&m_nIp, 2); + CWorld::Players[ScriptParams[0]].m_bGetOutOfJailFree = !!ScriptParams[1]; + return 0; + case COMMAND_SET_FREE_HEALTH_CARE: + CollectParameters(&m_nIp, 2); + CWorld::Players[ScriptParams[0]].m_bGetOutOfHospitalFree = !!ScriptParams[1]; + return 0; + case COMMAND_IS_CAR_DOOR_CLOSED: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(!pVehicle->IsDoorMissing((eDoors)ScriptParams[1]) && pVehicle->IsDoorClosed((eDoors)ScriptParams[1])); + return 0; + } + case COMMAND_LOAD_AND_LAUNCH_MISSION: + return 0; + case COMMAND_LOAD_AND_LAUNCH_MISSION_INTERNAL: + { + CollectParameters(&m_nIp, 1); +#ifdef MISSION_REPLAY + missionRetryScriptIndex = ScriptParams[0]; + if (missionRetryScriptIndex == 19) + CStats::LastMissionPassedName[0] = '\0'; +#endif + CTimer::Suspend(); + int offset = CTheScripts::MultiScriptArray[ScriptParams[0]]; +#ifdef USE_DEBUG_SCRIPT_LOADER + int handle = CTheScripts::OpenScript(); +#else + CFileMgr::ChangeDir("\\"); + int handle = CFileMgr::OpenFile("data\\main.scm", "rb"); +#endif + CFileMgr::Seek(handle, offset, 0); + CFileMgr::Read(handle, (const char*)&CTheScripts::ScriptSpace[SIZE_MAIN_SCRIPT], SIZE_MISSION_SCRIPT); + CFileMgr::CloseFile(handle); + CRunningScript* pMissionScript = CTheScripts::StartNewScript(SIZE_MAIN_SCRIPT); + CTimer::Resume(); + pMissionScript->m_bIsMissionScript = true; + pMissionScript->m_bMissionFlag = true; + CTheScripts::bAlreadyRunningAMissionScript = true; + return 0; + } + case COMMAND_SET_OBJECT_DRAW_LAST: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->bDrawLast = !!ScriptParams[1]; + return 0; + } + case COMMAND_GET_AMMO_IN_PLAYER_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CWeapon* pWeaponSlot = &pPed->m_weapons[ScriptParams[1]]; + if (pWeaponSlot->m_eWeaponType == (eWeaponType)ScriptParams[1]) + ScriptParams[0] = pWeaponSlot->m_nAmmoTotal; + else + ScriptParams[0] = 0; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_AMMO_IN_CHAR_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CWeapon* pWeaponSlot = &pPed->m_weapons[ScriptParams[1]]; + if (pWeaponSlot->m_eWeaponType == (eWeaponType)ScriptParams[1]) + ScriptParams[0] = pWeaponSlot->m_nAmmoTotal; + else + ScriptParams[0] = 0; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_REGISTER_KILL_FRENZY_PASSED: + CStats::AnotherKillFrenzyPassed(); + return 0; + case COMMAND_SET_CHAR_SAY: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + switch (ScriptParams[1]) { + case SCRIPT_SOUND_CHUNKY_RUN_SHOUT: + pPed->Say(SOUND_PED_FLEE_RUN); + break; + case SCRIPT_SOUND_SECURITY_GUARD_AWAY_SHOUT: + pPed->Say(SOUND_PED_FLEE_RUN); + break; + case SCRIPT_SOUND_SWAT_PED_SHOUT: + pPed->Say(SOUND_PED_PURSUIT_SWAT); + break; + case SCRIPT_SOUND_AMMUNATION_CHAT_1: + pPed->Say(SOUND_AMMUNATION_WELCOME_1); + break; + case SCRIPT_SOUND_AMMUNATION_CHAT_2: + pPed->Say(SOUND_AMMUNATION_WELCOME_2); + break; + case SCRIPT_SOUND_AMMUNATION_CHAT_3: + pPed->Say(SOUND_AMMUNATION_WELCOME_3); + break; + default: + break; + } + return 0; + } + case COMMAND_SET_NEAR_CLIP: + CollectParameters(&m_nIp, 1); + TheCamera.SetNearClipScript(*(float*)&ScriptParams[0]); + return 0; + case COMMAND_SET_RADIO_CHANNEL: + CollectParameters(&m_nIp, 2); + DMAudio.SetRadioChannel(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_OVERRIDE_HOSPITAL_LEVEL: + CollectParameters(&m_nIp, 1); + CRestart::OverrideHospitalLevel = ScriptParams[0]; + return 0; + case COMMAND_OVERRIDE_POLICE_STATION_LEVEL: + CollectParameters(&m_nIp, 1); + CRestart::OverridePoliceStationLevel = ScriptParams[0]; + return 0; + case COMMAND_FORCE_RAIN: + CollectParameters(&m_nIp, 1); + CWeather::bScriptsForceRain = !!ScriptParams[0]; + return 0; + case COMMAND_DOES_GARAGE_CONTAIN_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + UpdateCompareFlag(CGarages::IsThisCarWithinGarageArea(ScriptParams[0], pVehicle)); + return 0; + } + case COMMAND_SET_CAR_TRACTION: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + float fTraction = *(float*)&ScriptParams[1]; + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR || pVehicle->m_vehType == VEHICLE_TYPE_BIKE); + if (pVehicle->m_vehType == VEHICLE_TYPE_CAR) + ((CAutomobile*)pVehicle)->m_fTraction = fTraction; + else + // this is certainly not a boat, trane, heli or plane field + ((CBike*)pVehicle)->m_fTraction = fTraction; + return 0; + } + case COMMAND_ARE_MEASUREMENTS_IN_METRES: +#ifdef USE_MEASUREMENTS_IN_METERS + UpdateCompareFlag(true); +#else + UpdateCompareFlag(false); +#endif + return 0; + case COMMAND_CONVERT_METRES_TO_FEET: + { + CollectParameters(&m_nIp, 1); + float fMeterValue = *(float*)&ScriptParams[0]; + float fFeetValue = fMeterValue / METERS_IN_FOOT; + *(float*)&ScriptParams[0] = fFeetValue; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_MARK_ROADS_BETWEEN_LEVELS: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.MarkRoadsBetweenLevelsInArea(infX, supX, infY, supY, infZ, supZ); + return 0; + } + case COMMAND_MARK_PED_ROADS_BETWEEN_LEVELS: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.PedMarkRoadsBetweenLevelsInArea(infX, supX, infY, supY, infZ, supZ); + return 0; + } + case COMMAND_SET_CAR_AVOID_LEVEL_TRANSITIONS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_bStayInCurrentLevel = !!ScriptParams[1]; + return 0; + } + case COMMAND_SET_CHAR_AVOID_LEVEL_TRANSITIONS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pPed); + // not implemented + return 0; + } + case COMMAND_IS_THREAT_FOR_PED_TYPE: + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(CPedType::IsThreat(ScriptParams[0], ScriptParams[1])); + return 0; + case COMMAND_CLEAR_AREA_OF_CHARS: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + CWorld::ClearPedsFromArea(infX, infY, infZ, supX, supY, supZ); + return 0; + } + case COMMAND_SET_TOTAL_NUMBER_OF_MISSIONS: + CollectParameters(&m_nIp, 1); + CStats::SetTotalNumberMissions(ScriptParams[0]); + return 0; + case COMMAND_CONVERT_METRES_TO_FEET_INT: + CollectParameters(&m_nIp, 1); + ScriptParams[0] *= FEET_IN_METER; + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_REGISTER_FASTEST_TIME: + CollectParameters(&m_nIp, 2); + CStats::RegisterFastestTime(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_REGISTER_HIGHEST_SCORE: + CollectParameters(&m_nIp, 2); + CStats::RegisterHighestScore(ScriptParams[0], ScriptParams[1]); + return 0; + //case COMMAND_WARP_CHAR_INTO_CAR_AS_PASSENGER: + case COMMAND_IS_CAR_PASSENGER_SEAT_FREE: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(ScriptParams[1] < pVehicle->m_nNumMaxPassengers && pVehicle->pPassengers[ScriptParams[1]] == nil); + return 0; + } + case COMMAND_GET_CHAR_IN_CAR_PASSENGER_SEAT: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(ScriptParams[1] >= 0 && ScriptParams[1] < ARRAY_SIZE(pVehicle->pPassengers)); + CPed* pPassenger = pVehicle->pPassengers[ScriptParams[1]]; + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pPassenger); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_IS_CHRIS_CRIMINAL: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bChrisCriminal = !!ScriptParams[1]; + return 0; + } + case COMMAND_START_CREDITS: + CCredits::Start(); + return 0; + case COMMAND_STOP_CREDITS: + CCredits::Stop(); + return 0; + case COMMAND_ARE_CREDITS_FINISHED: + UpdateCompareFlag(CCredits::AreCreditsDone()); + return 0; + case COMMAND_CREATE_SINGLE_PARTICLE: + CollectParameters(&m_nIp, 8); + CParticle::AddParticle((tParticleType)ScriptParams[0], *(CVector*)&ScriptParams[1], + *(CVector*)&ScriptParams[4], nil, *(float*)&ScriptParams[7], 0, 0, 0, 0); + return 0; + case COMMAND_SET_CHAR_IGNORE_LEVEL_TRANSITIONS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) + pPed->m_nZoneLevel = LEVEL_IGNORE; + else + pPed->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pPed->GetPosition()); + return 0; + } + case COMMAND_GET_CHASE_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CRecordDataForChase::TurnChaseCarIntoScriptCar(ScriptParams[0]); + ScriptParams[0] = CPools::GetVehiclePool()->GetIndex(pVehicle); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CAR); + return 0; + } + case COMMAND_START_BOAT_FOAM_ANIMATION: + CSpecialParticleStuff::StartBoatFoamAnimation(); + return 0; + case COMMAND_UPDATE_BOAT_FOAM_ANIMATION: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CSpecialParticleStuff::UpdateBoatFoamAnimation(&pObject->GetMatrix()); + return 0; + } + case COMMAND_SET_MUSIC_DOES_FADE: + CollectParameters(&m_nIp, 1); + TheCamera.m_bIgnoreFadingStuffForMusic = (ScriptParams[0] == 0); + return 0; + case COMMAND_SET_INTRO_IS_PLAYING: + CollectParameters(&m_nIp, 1); + if (ScriptParams[0]) { + CGame::playingIntro = true; + CStreaming::RemoveCurrentZonesModels(); + } else { + CGame::playingIntro = false; + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + int mi; + CModelInfo::GetModelInfo("bridgefukb", &mi); + CStreaming::RequestModel(mi, STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + } + return 0; + case COMMAND_SET_PLAYER_HOOKER: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + if (ScriptParams[1] < 0) { + pPlayerInfo->m_pHooker = nil; + pPlayerInfo->m_nNextSexFrequencyUpdateTime = 0; + pPlayerInfo->m_nNextSexMoneyUpdateTime = 0; + } else { + CPed* pHooker = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pHooker); + pPlayerInfo->m_pHooker = (CCivilianPed*)pHooker; + pPlayerInfo->m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + 1000; + pPlayerInfo->m_nNextSexMoneyUpdateTime = CTimer::GetTimeInMilliseconds() + 3000; + } + return 0; + } + case COMMAND_PLAY_END_OF_GAME_TUNE: + DMAudio.PlayPreloadedCutSceneMusic(); + return 0; + case COMMAND_STOP_END_OF_GAME_TUNE: + DMAudio.StopCutSceneMusic(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + return 0; + case COMMAND_GET_CAR_MODEL: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = pVehicle->GetModelIndex(); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_PLAYER_SITTING_IN_CAR: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + UpdateCompareFlag(pPed->GetPedState() == PED_DRIVING && pPed->m_pMyVehicle == pVehicle); + return 0; + } + case COMMAND_IS_PLAYER_SITTING_IN_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->GetPedState() == PED_DRIVING); + return 0; + } + case COMMAND_SET_SCRIPT_FIRE_AUDIO: + CollectParameters(&m_nIp, 2); + gFireManager.SetScriptFireAudio(ScriptParams[0], !!ScriptParams[1]); + return 0; + case COMMAND_ARE_ANY_CAR_CHEATS_ACTIVATED: + UpdateCompareFlag(CVehicle::bAllDodosCheat || CVehicle::bCheat3); + return 0; + case COMMAND_SET_CHAR_SUFFERS_CRITICAL_HITS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bNoCriticalHits = (ScriptParams[0] == 0); + return 0; + } + case COMMAND_IS_PLAYER_LIFTING_A_PHONE: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->GetPedState() == PED_MAKE_CALL); + return 0; + } + case COMMAND_IS_CHAR_SITTING_IN_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + UpdateCompareFlag(pPed->GetPedState() == PED_DRIVING && pPed->m_pMyVehicle == pVehicle); + return 0; + } + case COMMAND_IS_CHAR_SITTING_IN_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->GetPedState() == PED_DRIVING); + return 0; + } + case COMMAND_IS_PLAYER_ON_FOOT: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(!pPed->bInVehicle && pPed->m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER && + pPed->m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER); + return 0; + } + case COMMAND_IS_CHAR_ON_FOOT: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(!pPed->bInVehicle && pPed->m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER && + pPed->m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER); + return 0; + } +#if GTA_VERSION > GTA3_PS2_160 + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands1100To1199(int32 command) +{ + char tmp[48]; + switch (command) { +#endif + case COMMAND_LOAD_COLLISION_WITH_SCREEN: + CollectParameters(&m_nIp, 1); + CTimer::Stop(); + CGame::currLevel = (eLevelName)ScriptParams[0]; + if (CGame::currLevel != CCollision::ms_collisionInMemory) { + ISLAND_LOADING_IS(LOW) + { + DMAudio.SetEffectsFadeVol(0); + CPad::StopPadsShaking(); + CCollision::LoadCollisionScreen(CGame::currLevel); + DMAudio.Service(); + } + CPopulation::DealWithZoneChange(CCollision::ms_collisionInMemory, CGame::currLevel, false); + + ISLAND_LOADING_IS(LOW) + { + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + } + CCollision::SortOutCollisionAfterLoad(); + + ISLAND_LOADING_ISNT(HIGH) + CStreaming::RequestIslands(CGame::currLevel); + + ISLAND_LOADING_IS(LOW) + CStreaming::RequestBigBuildings(CGame::currLevel); + + ISLAND_LOADING_ISNT(HIGH) + CStreaming::LoadAllRequestedModels(true); + + ISLAND_LOADING_IS(LOW) + DMAudio.SetEffectsFadeVol(127); + } + CTimer::Update(); + return 0; + case COMMAND_LOAD_SPLASH_SCREEN: + CTheScripts::ReadTextLabelFromScript(&m_nIp, tmp); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + tmp[i] = tolower(tmp[i]); + m_nIp += 8; + LoadSplash(tmp); + return 0; + case COMMAND_SET_CAR_IGNORE_LEVEL_TRANSITIONS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + if (ScriptParams[1]) + pVehicle->m_nZoneLevel = LEVEL_IGNORE; + else + pVehicle->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pVehicle->GetPosition()); + return 0; + } + case COMMAND_MAKE_CRAIGS_CAR_A_BIT_STRONGER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + CAutomobile* pCar = (CAutomobile*)pVehicle; + pCar->bMoreResistantToDamage = ScriptParams[1]; + return 0; + } + case COMMAND_SET_JAMES_CAR_ON_PATH_TO_PLAYER: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, FindPlayerCoors(), false); + return 0; + } + case COMMAND_LOAD_END_OF_GAME_TUNE: + DMAudio.ChangeMusicMode(MUSICMODE_CUTSCENE); + printf("Start preload end of game audio\n"); + DMAudio.PreloadCutSceneMusic(STREAMED_SOUND_GAME_COMPLETED); + printf("End preload end of game audio\n"); + return 0; + case COMMAND_ENABLE_PLAYER_CONTROL_CAMERA: + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_CAMERA); + return 0; +#if GTA_VERSION > GTA3_PS2_160 + // These are "beta" VC commands (with bugs) + case COMMAND_SET_OBJECT_ROTATION: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CWorld::Remove(pObject); + pObject->SetOrientation( + DEGTORAD(*(float*)&ScriptParams[1]), + DEGTORAD(*(float*)&ScriptParams[2]), + DEGTORAD(*(float*)&ScriptParams[3])); + pObject->GetMatrix().UpdateRW(); + pObject->UpdateRwFrame(); + CWorld::Add(pObject); + return 0; + } + case COMMAND_GET_DEBUG_CAMERA_COORDINATES: + *(CVector*)&ScriptParams[0] = TheCamera.Cams[2].Source; + StoreParameters(&m_nIp, 3); + return 0; + case COMMAND_GET_DEBUG_CAMERA_FRONT_VECTOR: + *(CVector*)&ScriptParams[0] = TheCamera.Cams[2].Front; + StoreParameters(&m_nIp, 3); + return 0; + case COMMAND_IS_PLAYER_TARGETTING_ANY_CHAR: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CEntity* pTarget = pPed->m_pPointGunAt; + UpdateCompareFlag(pTarget && pTarget->IsPed()); + return 0; + } + case COMMAND_IS_PLAYER_TARGETTING_CHAR: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CPed* pTestedPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTestedPed); + CEntity* pTarget = pPed->m_pPointGunAt; + UpdateCompareFlag(pTarget && pTarget->IsPed() && pTarget == pTestedPed); + return 0; + } + case COMMAND_IS_PLAYER_TARGETTING_OBJECT: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CObject* pTestedObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + script_assert(pTestedObject); + CEntity* pTarget = pPed->m_pPointGunAt; + UpdateCompareFlag(pTarget && pTarget->IsObject() && pTarget == pTestedObject); + return 0; + } + case COMMAND_TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME: + { + CTheScripts::ReadTextLabelFromScript(&m_nIp, tmp); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + tmp[i] = tolower(tmp[i]); + m_nIp += 8; + CRunningScript* pScript = CTheScripts::pActiveScripts; + while (pScript) { + CRunningScript* pNext = pScript->next; + if (strcmp(pScript->m_abScriptName, tmp) == 0) { + pScript->RemoveScriptFromList(&CTheScripts::pActiveScripts); + pScript->AddScriptToList(&CTheScripts::pIdleScripts); + } + pScript = pNext; + } + return 0; + } + case COMMAND_DISPLAY_TEXT_WITH_NUMBER: + { + CollectParameters(&m_nIp, 2); + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtX = *(float*)&ScriptParams[0]; + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtY = *(float*)&ScriptParams[1]; + CollectParameters(&m_nIp, 1); + CMessages::InsertNumberInString(text, ScriptParams[0], -1, -1, -1, -1, -1, + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame++].m_Text); + return 0; + } + case COMMAND_DISPLAY_TEXT_WITH_2_NUMBERS: + { + CollectParameters(&m_nIp, 2); + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtX = *(float*)&ScriptParams[0]; + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtY = *(float*)&ScriptParams[1]; + CollectParameters(&m_nIp, 2); + CMessages::InsertNumberInString(text, ScriptParams[0], ScriptParams[1], -1, -1, -1, -1, + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame++].m_Text); + return 0; + } + case COMMAND_FAIL_CURRENT_MISSION: + CTheScripts::FailCurrentMission = 2; + return 0; + case COMMAND_GET_CLOSEST_OBJECT_OF_TYPE: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float range = *(float*)&ScriptParams[3]; + int mi = ScriptParams[4] < 0 ? CTheScripts::UsedObjectArray[-ScriptParams[4]].index : ScriptParams[4]; + int16 total; + CEntity* apEntities[16]; + CWorld::FindObjectsOfTypeInRange(mi, pos, range, true, &total, 16, apEntities, false, false, false, true, true); + CEntity* pClosestEntity = nil; + float min_dist = 2.0f * range; + for (int i = 0; i < total; i++) { + float dist = (apEntities[i]->GetPosition() - pos).Magnitude(); + if (dist < min_dist) { + min_dist = dist; + pClosestEntity = apEntities[i]; + } + } + if (pClosestEntity && pClosestEntity->IsDummy()) { + CPopulation::ConvertToRealObject((CDummyObject*)pClosestEntity); + CWorld::FindObjectsOfTypeInRange(mi, pos, range, true, &total, 16, apEntities, false, false, false, true, true); + pClosestEntity = nil; + float min_dist = 2.0f * range; + for (int i = 0; i < total; i++) { + float dist = (apEntities[i]->GetPosition() - pos).Magnitude(); + if (dist < min_dist) { + min_dist = dist; + pClosestEntity = apEntities[i]; + } + } + if (pClosestEntity->IsDummy()) + pClosestEntity = nil; + } + if (pClosestEntity) { + script_assert(pClosestEntity->IsObject()); + CObject* pObject = (CObject*)pClosestEntity; + pObject->ObjectCreatedBy = MISSION_OBJECT; + ScriptParams[0] = CPools::GetObjectPool()->GetIndex(pObject); + } else { + ScriptParams[0] = -1; + } + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_PLACE_OBJECT_RELATIVE_TO_OBJECT: + { + CollectParameters(&m_nIp, 5); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CObject* pTarget = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector offset = *(CVector*)&ScriptParams[2]; + CPhysical::PlacePhysicalRelativeToOtherPhysical(pTarget, pObject, offset); + return 0; + } + case COMMAND_SET_ALL_OCCUPANTS_OF_CAR_LEAVE_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + if (pVehicle->pDriver) { + pVehicle->pDriver->bScriptObjectiveCompleted = false; + pVehicle->pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + } + for (int i = 0; i < ARRAY_SIZE(pVehicle->pPassengers); i++) + { + if (pVehicle->pPassengers[i]) { + pVehicle->pPassengers[i]->bScriptObjectiveCompleted = false; + pVehicle->pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + } + } + return 0; + } + case COMMAND_SET_INTERPOLATION_PARAMETERS: + CollectParameters(&m_nIp, 2); + TheCamera.SetParametersForScriptInterpolation(*(float*)&ScriptParams[0], 50.0f - *(float*)&ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_TOWARDS_POINT: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float destX = *(float*)&ScriptParams[3]; + float destY = *(float*)&ScriptParams[4]; + int32 nid = ThePaths.FindNodeClosestToCoors(pos, 0, 999999.9f, true, true); + CPathNode* pNode = &ThePaths.m_pathNodes[nid]; + *(CVector*)&ScriptParams[0] = pNode->GetPosition(); + *(float*)&ScriptParams[3] = ThePaths.FindNodeOrientationForCarPlacementFacingDestination(nid, destX, destY, true); + StoreParameters(&m_nIp, 4); + return 0; + } + case COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_AWAY_POINT: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float destX = *(float*)&ScriptParams[3]; + float destY = *(float*)&ScriptParams[4]; + int32 nid = ThePaths.FindNodeClosestToCoors(pos, 0, 999999.9f, true, true); + CPathNode* pNode = &ThePaths.m_pathNodes[nid]; + *(CVector*)&ScriptParams[0] = pNode->GetPosition(); + *(float*)&ScriptParams[3] = ThePaths.FindNodeOrientationForCarPlacementFacingDestination(nid, destX, destY, false); + StoreParameters(&m_nIp, 4); + return 0; + } + case COMMAND_GET_DEBUG_CAMERA_POINT_AT: + *(CVector*)&ScriptParams[0] = TheCamera.Cams[2].Source + TheCamera.Cams[2].Front; + StoreParameters(&m_nIp, 3); + return 0; + case COMMAND_ATTACH_CHAR_TO_CAR: + // empty implementation + return 0; + case COMMAND_DETACH_CHAR_FROM_CAR: + // empty implementation + return 0; + case COMMAND_SET_CAR_CHANGE_LANE: // for some reason changed in SA + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_bStayInFastLane = !ScriptParams[1]; + return 0; + } + case COMMAND_CLEAR_CHAR_LAST_WEAPON_DAMAGE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_lastWepDam = -1; + return 0; + } + case COMMAND_CLEAR_CAR_LAST_WEAPON_DAMAGE: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + pVehicle->m_nLastWeaponDamage = -1; + return 0; + } + case COMMAND_GET_RANDOM_COP_IN_AREA: + { + CollectParameters(&m_nIp, 4); + int ped_handle = -1; + CVector pos = FindPlayerCoors(); + float x1 = *(float*)&ScriptParams[0]; + float y1 = *(float*)&ScriptParams[1]; + float x2 = *(float*)&ScriptParams[2]; + float y2 = *(float*)&ScriptParams[3]; + int i = CPools::GetPedPool()->GetSize(); + while (--i && ped_handle == -1) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (CTheScripts::LastRandomPedId == CPools::GetPedPool()->GetIndex(pPed)) + continue; + if (pPed->m_nPedType != PEDTYPE_COP) + continue; + if (pPed->CharCreatedBy != RANDOM_CHAR) + continue; + if (!pPed->IsPedInControl() && pPed->GetPedState() != PED_DRIVING) + continue; + if (pPed->bRemoveFromWorld) + continue; + if (pPed->bFadeOut) + continue; + if (pPed->bIsLeader || pPed->m_leader) + continue; + if (!pPed->IsWithinArea(x1, y1, x2, y2)) + continue; + if (pos.z - PED_FIND_Z_OFFSET > pPed->GetPosition().z) + continue; + if (pos.z + PED_FIND_Z_OFFSET < pPed->GetPosition().z) + continue; + ped_handle = CPools::GetPedPool()->GetIndex(pPed); + CTheScripts::LastRandomPedId = ped_handle; + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + ++CPopulation::ms_nTotalMissionPeds; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ped_handle, CLEANUP_CHAR); + } + ScriptParams[0] = ped_handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_RANDOM_COP_IN_ZONE: + { + char zone[KEY_LENGTH_IN_SCRIPT]; + strncpy(zone, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + int nZone = CTheZones::FindZoneByLabelAndReturnIndex(zone); + if (nZone != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetZone(nZone); + int ped_handle = -1; + CVector pos = FindPlayerCoors(); + int i = CPools::GetPedPool()->GetSize(); + while (--i && ped_handle == -1) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (CTheScripts::LastRandomPedId == CPools::GetPedPool()->GetIndex(pPed)) + continue; + if (pPed->m_nPedType != PEDTYPE_COP) + continue; + if (pPed->CharCreatedBy != RANDOM_CHAR) + continue; + if (!pPed->IsPedInControl() && pPed->GetPedState() != PED_DRIVING) + continue; + if (pPed->bRemoveFromWorld) + continue; + if (pPed->bFadeOut) + continue; + if (pPed->bIsLeader || pPed->m_leader) + continue; + if (!CTheZones::PointLiesWithinZone(&pPed->GetPosition(), pZone)) + continue; + if (pos.z - PED_FIND_Z_OFFSET > pPed->GetPosition().z) + continue; + if (pos.z + PED_FIND_Z_OFFSET < pPed->GetPosition().z) + continue; + ped_handle = CPools::GetPedPool()->GetIndex(pPed); + CTheScripts::LastRandomPedId = ped_handle; + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + ++CPopulation::ms_nTotalMissionPeds; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ped_handle, CLEANUP_CHAR); + } + ScriptParams[0] = ped_handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CAR, pVehicle); + return 0; + } + case COMMAND_GET_DRIVER_OF_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CPed* pDriver = pVehicle->pDriver; + if (pDriver) + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pDriver); + else + ScriptParams[0] = -1; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_NUMBER_OF_FOLLOWERS: + { + CollectParameters(&m_nIp, 1); + CPed* pLeader = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pLeader); + int total = 0; + int i = CPools::GetPedPool()->GetSize(); + while (--i) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (pPed->m_leader == pLeader) + total++; + } + ScriptParams[0] = total; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GIVE_REMOTE_CONTROLLED_MODEL_TO_PLAYER: + { + CollectParameters(&m_nIp, 6); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRemote::GivePlayerRemoteControlledCar(pos.x, pos.y, pos.z, DEGTORAD(*(float*)&ScriptParams[4]), ScriptParams[5]); + return 0; + } + case COMMAND_GET_CURRENT_PLAYER_WEAPON: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + ScriptParams[0] = pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_CURRENT_CHAR_WEAPON: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + ScriptParams[0] = pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_3D: + LocateCharObjectCommand(command, &m_nIp); + return 0; + case COMMAND_SET_CAR_HANDBRAKE_TURN_LEFT: // this will be changed in final VC version to a more general SET_TEMP_ACTION + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_nTempAction = TEMPACT_HANDBRAKETURNLEFT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + ScriptParams[1]; + return 0; + } + case COMMAND_SET_CAR_HANDBRAKE_TURN_RIGHT: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_nTempAction = TEMPACT_HANDBRAKETURNRIGHT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + ScriptParams[1]; + return 0; + } + case COMMAND_SET_CAR_HANDBRAKE_STOP: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_nTempAction = TEMPACT_HANDBRAKESTRAIGHT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + ScriptParams[1]; + return 0; + } + case COMMAND_IS_CHAR_ON_ANY_BIKE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle&& pPed->m_pMyVehicle->m_vehType == VEHICLE_TYPE_BIKE); + return 0; + } + case COMMAND_LOCATE_SNIPER_BULLET_2D: + case COMMAND_LOCATE_SNIPER_BULLET_3D: + LocateSniperBulletCommand(command, &m_nIp); + return 0; + case COMMAND_GET_NUMBER_OF_SEATS_IN_MODEL: + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CVehicleModelInfo::GetMaximumNumberOfPassengersFromNumberOfDoors(ScriptParams[0]) + 1; + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_IS_PLAYER_ON_ANY_BIKE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->m_vehType == VEHICLE_TYPE_BIKE); + return 0; + } + case COMMAND_IS_CHAR_LYING_DOWN: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bFallenDown); + return 0; + } + case COMMAND_CAN_CHAR_SEE_DEAD_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + int pedtype = ScriptParams[1]; + bool can = false; + for (int i = 0; i < pPed->m_numNearPeds; i++) { + CPed* pTestPed = pPed->m_nearPeds[i]; + if (pTestPed->m_fHealth <= 0.0f && pTestPed->m_nPedType == pedtype && pPed->OurPedCanSeeThisOne(pTestPed)) + can = true; + } + UpdateCompareFlag(can); + return 0; + } + case COMMAND_SET_ENTER_CAR_RANGE_MULTIPLIER: + CollectParameters(&m_nIp, 1); +#ifdef FIX_BUGS + CPed::nEnterCarRangeMultiplier = *(float*)&ScriptParams[0]; +#else + CPed::nEnterCarRangeMultiplier = (float)ScriptParams[0]; +#endif + return 0; +#if GTA_VERSION < GTA3_PC_11 + case COMMAND_SET_THREAT_REACTION_RANGE_MULTIPLIER: + CollectParameters(&m_nIp, 1); +#ifdef FIX_BUGS + CPed::nThreatReactionRangeMultiplier = *(float*)&ScriptParams[0]; +#else + CPed::nThreatReactionRangeMultiplier = (float)ScriptParams[0]; +#endif + return 0; +#endif +#endif + default: + script_assert(0); + } + return -1; +} diff --git a/src/control/ScriptCommands.h b/src/control/ScriptCommands.h new file mode 100644 index 0000000..a33275f --- /dev/null +++ b/src/control/ScriptCommands.h @@ -0,0 +1,1194 @@ +#pragma once + +enum { + COMMAND_NOP = 0, + COMMAND_WAIT, + COMMAND_GOTO, + COMMAND_SHAKE_CAM, + COMMAND_SET_VAR_INT, + COMMAND_SET_VAR_FLOAT, + COMMAND_SET_LVAR_INT, + COMMAND_SET_LVAR_FLOAT, + COMMAND_ADD_VAL_TO_INT_VAR, + COMMAND_ADD_VAL_TO_FLOAT_VAR, + COMMAND_ADD_VAL_TO_INT_LVAR, + COMMAND_ADD_VAL_TO_FLOAT_LVAR, + COMMAND_SUB_VAL_FROM_INT_VAR, + COMMAND_SUB_VAL_FROM_FLOAT_VAR, + COMMAND_SUB_VAL_FROM_INT_LVAR, + COMMAND_SUB_VAL_FROM_FLOAT_LVAR, + COMMAND_MULT_INT_VAR_BY_VAL, + COMMAND_MULT_FLOAT_VAR_BY_VAL, + COMMAND_MULT_INT_LVAR_BY_VAL, + COMMAND_MULT_FLOAT_LVAR_BY_VAL, + COMMAND_DIV_INT_VAR_BY_VAL, + COMMAND_DIV_FLOAT_VAR_BY_VAL, + COMMAND_DIV_INT_LVAR_BY_VAL, + COMMAND_DIV_FLOAT_LVAR_BY_VAL, + COMMAND_IS_INT_VAR_GREATER_THAN_NUMBER, + COMMAND_IS_INT_LVAR_GREATER_THAN_NUMBER, + COMMAND_IS_NUMBER_GREATER_THAN_INT_VAR, + COMMAND_IS_NUMBER_GREATER_THAN_INT_LVAR, + COMMAND_IS_INT_VAR_GREATER_THAN_INT_VAR, + COMMAND_IS_INT_LVAR_GREATER_THAN_INT_LVAR, + COMMAND_IS_INT_VAR_GREATER_THAN_INT_LVAR, + COMMAND_IS_INT_LVAR_GREATER_THAN_INT_VAR, + COMMAND_IS_FLOAT_VAR_GREATER_THAN_NUMBER, + COMMAND_IS_FLOAT_LVAR_GREATER_THAN_NUMBER, + COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_VAR, + COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_VAR, + COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_LVAR, + COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_VAR, + COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_NUMBER, + COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_NUMBER, + COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_VAR, + COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_VAR, + COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_VAR, + COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_NUMBER, + COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_NUMBER, + COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_VAR, + COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_VAR, + COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_VAR, + COMMAND_IS_INT_VAR_EQUAL_TO_NUMBER, + COMMAND_IS_INT_LVAR_EQUAL_TO_NUMBER, + COMMAND_IS_INT_VAR_EQUAL_TO_INT_VAR, + COMMAND_IS_INT_LVAR_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_VAR_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_VAR_NOT_EQUAL_TO_NUMBER, + COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_NUMBER, + COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_VAR, + COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_LVAR, + COMMAND_IS_FLOAT_VAR_EQUAL_TO_NUMBER, + COMMAND_IS_FLOAT_LVAR_EQUAL_TO_NUMBER, + COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_VAR, + COMMAND_IS_FLOAT_LVAR_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_NUMBER, + COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_NUMBER, + COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_VAR, + COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_LVAR, + COMMAND_GOTO_IF_TRUE, + COMMAND_GOTO_IF_FALSE, + COMMAND_TERMINATE_THIS_SCRIPT, + COMMAND_START_NEW_SCRIPT, + COMMAND_GOSUB, + COMMAND_RETURN, + COMMAND_LINE, + COMMAND_CREATE_PLAYER, + COMMAND_GET_PLAYER_COORDINATES, + COMMAND_SET_PLAYER_COORDINATES, + COMMAND_IS_PLAYER_IN_AREA_2D, + COMMAND_IS_PLAYER_IN_AREA_3D, + COMMAND_ADD_INT_VAR_TO_INT_VAR, + COMMAND_ADD_FLOAT_VAR_TO_FLOAT_VAR, + COMMAND_ADD_INT_LVAR_TO_INT_LVAR, + COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_LVAR, + COMMAND_ADD_INT_VAR_TO_INT_LVAR, + COMMAND_ADD_FLOAT_VAR_TO_FLOAT_LVAR, + COMMAND_ADD_INT_LVAR_TO_INT_VAR, + COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_VAR, + COMMAND_SUB_INT_VAR_FROM_INT_VAR, + COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_VAR, + COMMAND_SUB_INT_LVAR_FROM_INT_LVAR, + COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_LVAR, + COMMAND_SUB_INT_VAR_FROM_INT_LVAR, + COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_LVAR, + COMMAND_SUB_INT_LVAR_FROM_INT_VAR, + COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_VAR, + COMMAND_MULT_INT_VAR_BY_INT_VAR, + COMMAND_MULT_FLOAT_VAR_BY_FLOAT_VAR, + COMMAND_MULT_INT_LVAR_BY_INT_LVAR, + COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_LVAR, + COMMAND_MULT_INT_VAR_BY_INT_LVAR, + COMMAND_MULT_FLOAT_VAR_BY_FLOAT_LVAR, + COMMAND_MULT_INT_LVAR_BY_INT_VAR, + COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_VAR, + COMMAND_DIV_INT_VAR_BY_INT_VAR, + COMMAND_DIV_FLOAT_VAR_BY_FLOAT_VAR, + COMMAND_DIV_INT_LVAR_BY_INT_LVAR, + COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_LVAR, + COMMAND_DIV_INT_VAR_BY_INT_LVAR, + COMMAND_DIV_FLOAT_VAR_BY_FLOAT_LVAR, + COMMAND_DIV_INT_LVAR_BY_INT_VAR, + COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_VAR, + COMMAND_ADD_TIMED_VAL_TO_FLOAT_VAR, + COMMAND_ADD_TIMED_VAL_TO_FLOAT_LVAR, + COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_VAR, + COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_LVAR, + COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_VAR, + COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_LVAR, + COMMAND_SUB_TIMED_VAL_FROM_FLOAT_VAR, + COMMAND_SUB_TIMED_VAL_FROM_FLOAT_LVAR, + COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_VAR, + COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_LVAR, + COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_VAR, + COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_LVAR, + COMMAND_SET_VAR_INT_TO_VAR_INT, + COMMAND_SET_LVAR_INT_TO_LVAR_INT, + COMMAND_SET_VAR_FLOAT_TO_VAR_FLOAT, + COMMAND_SET_LVAR_FLOAT_TO_LVAR_FLOAT, + COMMAND_SET_VAR_FLOAT_TO_LVAR_FLOAT, + COMMAND_SET_LVAR_FLOAT_TO_VAR_FLOAT, + COMMAND_SET_VAR_INT_TO_LVAR_INT, + COMMAND_SET_LVAR_INT_TO_VAR_INT, + COMMAND_CSET_VAR_INT_TO_VAR_FLOAT, + COMMAND_CSET_VAR_FLOAT_TO_VAR_INT, + COMMAND_CSET_LVAR_INT_TO_VAR_FLOAT, + COMMAND_CSET_LVAR_FLOAT_TO_VAR_INT, + COMMAND_CSET_VAR_INT_TO_LVAR_FLOAT, + COMMAND_CSET_VAR_FLOAT_TO_LVAR_INT, + COMMAND_CSET_LVAR_INT_TO_LVAR_FLOAT, + COMMAND_CSET_LVAR_FLOAT_TO_LVAR_INT, + COMMAND_ABS_VAR_INT, + COMMAND_ABS_LVAR_INT, + COMMAND_ABS_VAR_FLOAT, + COMMAND_ABS_LVAR_FLOAT, + COMMAND_GENERATE_RANDOM_FLOAT, + COMMAND_GENERATE_RANDOM_INT, + COMMAND_CREATE_CHAR, + COMMAND_DELETE_CHAR, + COMMAND_CHAR_WANDER_DIR, + COMMAND_CHAR_WANDER_RANGE, + COMMAND_CHAR_FOLLOW_PATH, + COMMAND_CHAR_SET_IDLE, + COMMAND_GET_CHAR_COORDINATES, + COMMAND_SET_CHAR_COORDINATES, + COMMAND_IS_CHAR_STILL_ALIVE, + COMMAND_IS_CHAR_IN_AREA_2D, + COMMAND_IS_CHAR_IN_AREA_3D, + COMMAND_CREATE_CAR, + COMMAND_DELETE_CAR, + COMMAND_CAR_GOTO_COORDINATES, + COMMAND_CAR_WANDER_RANDOMLY, + COMMAND_CAR_SET_IDLE, + COMMAND_GET_CAR_COORDINATES, + COMMAND_SET_CAR_COORDINATES, + COMMAND_IS_CAR_STILL_ALIVE, + COMMAND_SET_CAR_CRUISE_SPEED, + COMMAND_SET_CAR_DRIVING_STYLE, + COMMAND_SET_CAR_MISSION, + COMMAND_IS_CAR_IN_AREA_2D, + COMMAND_IS_CAR_IN_AREA_3D, + COMMAND_SPECIAL_0, + COMMAND_SPECIAL_1, + COMMAND_SPECIAL_2, + COMMAND_SPECIAL_3, + COMMAND_SPECIAL_4, + COMMAND_SPECIAL_5, + COMMAND_SPECIAL_6, + COMMAND_SPECIAL_7, + COMMAND_PRINT_BIG, + COMMAND_PRINT, + COMMAND_PRINT_NOW, + COMMAND_PRINT_SOON, + COMMAND_CLEAR_PRINTS, + COMMAND_GET_TIME_OF_DAY, + COMMAND_SET_TIME_OF_DAY, + COMMAND_GET_MINUTES_TO_TIME_OF_DAY, + COMMAND_IS_POINT_ON_SCREEN, + COMMAND_DEBUG_ON, + COMMAND_DEBUG_OFF, + COMMAND_RETURN_TRUE, + COMMAND_RETURN_FALSE, + COMMAND_VAR_INT, + COMMAND_VAR_FLOAT, + COMMAND_LVAR_INT, + COMMAND_LVAR_FLOAT, + COMMAND_LBRACKET, + COMMAND_RBRACKET, + COMMAND_REPEAT, + COMMAND_ENDREPEAT, + COMMAND_IF, + COMMAND_IFNOT, + COMMAND_ELSE, + COMMAND_ENDIF, + COMMAND_WHILE, + COMMAND_WHILENOT, + COMMAND_ENDWHILE, + COMMAND_ANDOR, + COMMAND_LAUNCH_MISSION, + COMMAND_MISSION_HAS_FINISHED, + COMMAND_STORE_CAR_CHAR_IS_IN, + COMMAND_STORE_CAR_PLAYER_IS_IN, + COMMAND_IS_CHAR_IN_CAR, + COMMAND_IS_PLAYER_IN_CAR, + COMMAND_IS_CHAR_IN_MODEL, + COMMAND_IS_PLAYER_IN_MODEL, + COMMAND_IS_CHAR_IN_ANY_CAR, + COMMAND_IS_PLAYER_IN_ANY_CAR, + COMMAND_IS_BUTTON_PRESSED, + COMMAND_GET_PAD_STATE, + COMMAND_LOCATE_PLAYER_ANY_MEANS_2D, + COMMAND_LOCATE_PLAYER_ON_FOOT_2D, + COMMAND_LOCATE_PLAYER_IN_CAR_2D, + COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_2D, + COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_2D, + COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_2D, + COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_2D, + COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_2D, + COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_2D, + COMMAND_LOCATE_CHAR_ANY_MEANS_2D, + COMMAND_LOCATE_CHAR_ON_FOOT_2D, + COMMAND_LOCATE_CHAR_IN_CAR_2D, + COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_2D, + COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_2D, + COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_2D, + COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_2D, + COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_2D, + COMMAND_LOCATE_CHAR_IN_CAR_CHAR_2D, + COMMAND_LOCATE_PLAYER_ANY_MEANS_3D, + COMMAND_LOCATE_PLAYER_ON_FOOT_3D, + COMMAND_LOCATE_PLAYER_IN_CAR_3D, + COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D, + COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D, + COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D, + COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_3D, + COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_3D, + COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_3D, + COMMAND_LOCATE_CHAR_ANY_MEANS_3D, + COMMAND_LOCATE_CHAR_ON_FOOT_3D, + COMMAND_LOCATE_CHAR_IN_CAR_3D, + COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D, + COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D, + COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D, + COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_3D, + COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_3D, + COMMAND_LOCATE_CHAR_IN_CAR_CHAR_3D, + COMMAND_CREATE_OBJECT, + COMMAND_DELETE_OBJECT, + COMMAND_ADD_SCORE, + COMMAND_IS_SCORE_GREATER, + COMMAND_STORE_SCORE, + COMMAND_GIVE_REMOTE_CONTROLLED_CAR_TO_PLAYER, + COMMAND_ALTER_WANTED_LEVEL, + COMMAND_ALTER_WANTED_LEVEL_NO_DROP, + COMMAND_IS_WANTED_LEVEL_GREATER, + COMMAND_CLEAR_WANTED_LEVEL, + COMMAND_SET_DEATHARREST_STATE, + COMMAND_HAS_DEATHARREST_BEEN_EXECUTED, + COMMAND_ADD_AMMO_TO_PLAYER, + COMMAND_ADD_AMMO_TO_CHAR, + COMMAND_ADD_AMMO_TO_CAR, + COMMAND_IS_PLAYER_STILL_ALIVE, + COMMAND_IS_PLAYER_DEAD, + COMMAND_IS_CHAR_DEAD, + COMMAND_IS_CAR_DEAD, + COMMAND_SET_CHAR_THREAT_SEARCH, + COMMAND_SET_CHAR_THREAT_REACTION, + COMMAND_SET_CHAR_OBJ_NO_OBJ, + COMMAND_ORDER_DRIVER_OUT_OF_CAR, + COMMAND_ORDER_CHAR_TO_DRIVE_CAR, + COMMAND_ADD_PATROL_POINT, + COMMAND_IS_PLAYER_IN_GANGZONE, + COMMAND_IS_PLAYER_IN_ZONE, + COMMAND_IS_PLAYER_PRESSING_HORN, + COMMAND_HAS_CHAR_SPOTTED_PLAYER, + COMMAND_ORDER_CHAR_TO_BACKDOOR, + COMMAND_ADD_CHAR_TO_GANG, + COMMAND_IS_CHAR_OBJECTIVE_PASSED, + COMMAND_SET_CHAR_DRIVE_AGGRESSION, + COMMAND_SET_CHAR_MAX_DRIVESPEED, + COMMAND_CREATE_CHAR_INSIDE_CAR, + COMMAND_WARP_PLAYER_FROM_CAR_TO_COORD, + COMMAND_MAKE_CHAR_DO_NOTHING, + COMMAND_SET_CHAR_INVINCIBLE, + COMMAND_SET_PLAYER_INVINCIBLE, + COMMAND_SET_CHAR_GRAPHIC_TYPE, + COMMAND_SET_PLAYER_GRAPHIC_TYPE, + COMMAND_HAS_PLAYER_BEEN_ARRESTED, + COMMAND_STOP_CHAR_DRIVING, + COMMAND_KILL_CHAR, + COMMAND_SET_FAVOURITE_CAR_MODEL_FOR_CHAR, + COMMAND_SET_CHAR_OCCUPATION, + COMMAND_CHANGE_CAR_LOCK, + COMMAND_SHAKE_CAM_WITH_POINT, + COMMAND_IS_CAR_MODEL, + COMMAND_IS_CAR_REMAP, + COMMAND_HAS_CAR_JUST_SUNK, + COMMAND_SET_CAR_NO_COLLIDE, + COMMAND_IS_CAR_DEAD_IN_AREA_2D, + COMMAND_IS_CAR_DEAD_IN_AREA_3D, + COMMAND_IS_TRAILER_ATTACHED, + COMMAND_IS_CAR_ON_TRAILER, + COMMAND_HAS_CAR_GOT_WEAPON, + COMMAND_PARK, + COMMAND_HAS_PARK_FINISHED, + COMMAND_KILL_ALL_PASSENGERS, + COMMAND_SET_CAR_BULLETPROOF, + COMMAND_SET_CAR_FLAMEPROOF, + COMMAND_SET_CAR_ROCKETPROOF, + COMMAND_IS_CARBOMB_ACTIVE, + COMMAND_GIVE_CAR_ALARM, + COMMAND_PUT_CAR_ON_TRAILER, + COMMAND_IS_CAR_CRUSHED, + COMMAND_CREATE_GANG_CAR, + COMMAND_CREATE_CAR_GENERATOR, + COMMAND_SWITCH_CAR_GENERATOR, + COMMAND_ADD_PAGER_MESSAGE, + COMMAND_DISPLAY_ONSCREEN_TIMER, + COMMAND_CLEAR_ONSCREEN_TIMER, + COMMAND_DISPLAY_ONSCREEN_COUNTER, + COMMAND_CLEAR_ONSCREEN_COUNTER, + COMMAND_SET_ZONE_CAR_INFO, + COMMAND_IS_CHAR_IN_GANG_ZONE, + COMMAND_IS_CHAR_IN_ZONE, + COMMAND_SET_CAR_DENSITY, + COMMAND_SET_PED_DENSITY, + COMMAND_POINT_CAMERA_AT_PLAYER, + COMMAND_POINT_CAMERA_AT_CAR, + COMMAND_POINT_CAMERA_AT_CHAR, + COMMAND_RESTORE_CAMERA, + COMMAND_SHAKE_PAD, + COMMAND_SET_ZONE_PED_INFO, + COMMAND_SET_TIME_SCALE, + COMMAND_IS_CAR_IN_AIR, + COMMAND_SET_FIXED_CAMERA_POSITION, + COMMAND_POINT_CAMERA_AT_POINT, + COMMAND_ADD_BLIP_FOR_CAR_OLD, + COMMAND_ADD_BLIP_FOR_CHAR_OLD, + COMMAND_ADD_BLIP_FOR_OBJECT_OLD, + COMMAND_REMOVE_BLIP, + COMMAND_CHANGE_BLIP_COLOUR, + COMMAND_DIM_BLIP, + COMMAND_ADD_BLIP_FOR_COORD_OLD, + COMMAND_CHANGE_BLIP_SCALE, + COMMAND_SET_FADING_COLOUR, + COMMAND_DO_FADE, + COMMAND_GET_FADING_STATUS, + COMMAND_ADD_HOSPITAL_RESTART, + COMMAND_ADD_POLICE_RESTART, + COMMAND_OVERRIDE_NEXT_RESTART, + COMMAND_DRAW_SHADOW, + COMMAND_GET_PLAYER_HEADING, + COMMAND_SET_PLAYER_HEADING, + COMMAND_GET_CHAR_HEADING, + COMMAND_SET_CHAR_HEADING, + COMMAND_GET_CAR_HEADING, + COMMAND_SET_CAR_HEADING, + COMMAND_GET_OBJECT_HEADING, + COMMAND_SET_OBJECT_HEADING, + COMMAND_IS_PLAYER_TOUCHING_OBJECT, + COMMAND_IS_CHAR_TOUCHING_OBJECT, + COMMAND_SET_PLAYER_AMMO, + COMMAND_SET_CHAR_AMMO, + COMMAND_SET_CAR_AMMO, + COMMAND_LOAD_CAMERA_SPLINE, + COMMAND_MOVE_CAMERA_ALONG_SPLINE, + COMMAND_GET_CAMERA_POSITION_ALONG_SPLINE, + COMMAND_DECLARE_MISSION_FLAG, + COMMAND_DECLARE_MISSION_FLAG_FOR_CONTACT, + COMMAND_DECLARE_BASE_BRIEF_ID_FOR_CONTACT, + COMMAND_IS_PLAYER_HEALTH_GREATER, + COMMAND_IS_CHAR_HEALTH_GREATER, + COMMAND_IS_CAR_HEALTH_GREATER, + COMMAND_ADD_BLIP_FOR_CAR, + COMMAND_ADD_BLIP_FOR_CHAR, + COMMAND_ADD_BLIP_FOR_OBJECT, + COMMAND_ADD_BLIP_FOR_CONTACT_POINT, + COMMAND_ADD_BLIP_FOR_COORD, + COMMAND_CHANGE_BLIP_DISPLAY, + COMMAND_ADD_ONE_OFF_SOUND, + COMMAND_ADD_CONTINUOUS_SOUND, + COMMAND_REMOVE_SOUND, + COMMAND_IS_CAR_STUCK_ON_ROOF, + COMMAND_ADD_UPSIDEDOWN_CAR_CHECK, + COMMAND_REMOVE_UPSIDEDOWN_CAR_CHECK, + COMMAND_SET_CHAR_OBJ_WAIT_ON_FOOT, + COMMAND_SET_CHAR_OBJ_FLEE_ON_FOOT_TILL_SAFE, + COMMAND_SET_CHAR_OBJ_GUARD_SPOT, + COMMAND_SET_CHAR_OBJ_GUARD_AREA, + COMMAND_SET_CHAR_OBJ_WAIT_IN_CAR, + COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_2D, + COMMAND_IS_PLAYER_IN_AREA_IN_CAR_2D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_2D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_2D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_2D, + COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_3D, + COMMAND_IS_PLAYER_IN_AREA_IN_CAR_3D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D, + COMMAND_IS_CHAR_IN_AREA_ON_FOOT_2D, + COMMAND_IS_CHAR_IN_AREA_IN_CAR_2D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_2D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_2D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_2D, + COMMAND_IS_CHAR_IN_AREA_ON_FOOT_3D, + COMMAND_IS_CHAR_IN_AREA_IN_CAR_3D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_3D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D, + COMMAND_IS_CAR_STOPPED_IN_AREA_2D, + COMMAND_IS_CAR_STOPPED_IN_AREA_3D, + COMMAND_LOCATE_CAR_2D, + COMMAND_LOCATE_STOPPED_CAR_2D, + COMMAND_LOCATE_CAR_3D, + COMMAND_LOCATE_STOPPED_CAR_3D, + COMMAND_GIVE_WEAPON_TO_PLAYER, + COMMAND_GIVE_WEAPON_TO_CHAR, + COMMAND_GIVE_WEAPON_TO_CAR, + COMMAND_SET_PLAYER_CONTROL, + COMMAND_FORCE_WEATHER, + COMMAND_FORCE_WEATHER_NOW, + COMMAND_RELEASE_WEATHER, + COMMAND_SET_CURRENT_PLAYER_WEAPON, + COMMAND_SET_CURRENT_CHAR_WEAPON, + COMMAND_SET_CURRENT_CAR_WEAPON, + COMMAND_GET_OBJECT_COORDINATES, + COMMAND_SET_OBJECT_COORDINATES, + COMMAND_GET_GAME_TIMER, + COMMAND_TURN_CHAR_TO_FACE_COORD, + COMMAND_TURN_PLAYER_TO_FACE_COORD, + COMMAND_STORE_WANTED_LEVEL, + COMMAND_IS_CAR_STOPPED, + COMMAND_MARK_CHAR_AS_NO_LONGER_NEEDED, + COMMAND_MARK_CAR_AS_NO_LONGER_NEEDED, + COMMAND_MARK_OBJECT_AS_NO_LONGER_NEEDED, + COMMAND_DONT_REMOVE_CHAR, + COMMAND_DONT_REMOVE_CAR, + COMMAND_DONT_REMOVE_OBJECT, + COMMAND_CREATE_CHAR_AS_PASSENGER, + COMMAND_SET_CHAR_OBJ_KILL_CHAR_ON_FOOT, + COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ON_FOOT, + COMMAND_SET_CHAR_OBJ_KILL_CHAR_ANY_MEANS, + COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ANY_MEANS, + COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE, + COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE, + COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS, + COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS, + COMMAND_SET_CHAR_OBJ_GOTO_CHAR_ON_FOOT, + COMMAND_SET_CHAR_OBJ_GOTO_PLAYER_ON_FOOT, + COMMAND_SET_CHAR_OBJ_LEAVE_CAR, + COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_PASSENGER, + COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_DRIVER, + COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_IN_CAR, + COMMAND_SET_CHAR_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE, + COMMAND_SET_CHAR_OBJ_DESTROY_OBJECT, + COMMAND_SET_CHAR_OBJ_DESTROY_CAR, + COMMAND_SET_CHAR_OBJ_GOTO_AREA_ON_FOOT, + COMMAND_SET_CHAR_OBJ_GOTO_AREA_IN_CAR, + COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, + COMMAND_SET_CHAR_OBJ_GUARD_ATTACK, + COMMAND_SET_CHAR_AS_LEADER, + COMMAND_SET_PLAYER_AS_LEADER, + COMMAND_LEAVE_GROUP, + COMMAND_SET_CHAR_OBJ_FOLLOW_ROUTE, + COMMAND_ADD_ROUTE_POINT, + COMMAND_PRINT_WITH_NUMBER_BIG, + COMMAND_PRINT_WITH_NUMBER, + COMMAND_PRINT_WITH_NUMBER_NOW, + COMMAND_PRINT_WITH_NUMBER_SOON, + COMMAND_SWITCH_ROADS_ON, + COMMAND_SWITCH_ROADS_OFF, + COMMAND_GET_NUMBER_OF_PASSENGERS, + COMMAND_GET_MAXIMUM_NUMBER_OF_PASSENGERS, + COMMAND_SET_CAR_DENSITY_MULTIPLIER, + COMMAND_SET_CAR_HEAVY, + COMMAND_CLEAR_CHAR_THREAT_SEARCH, + COMMAND_ACTIVATE_CRANE, + COMMAND_DEACTIVATE_CRANE, + COMMAND_SET_MAX_WANTED_LEVEL, + COMMAND_SAVE_VAR_INT, + COMMAND_SAVE_VAR_FLOAT, + COMMAND_IS_CAR_IN_AIR_PROPER, + COMMAND_IS_CAR_UPSIDEDOWN, + COMMAND_GET_PLAYER_CHAR, + COMMAND_CANCEL_OVERRIDE_RESTART, + COMMAND_SET_POLICE_IGNORE_PLAYER, + COMMAND_ADD_PAGER_MESSAGE_WITH_NUMBER, + COMMAND_START_KILL_FRENZY, + COMMAND_READ_KILL_FRENZY_STATUS, + COMMAND_SQRT, + COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_2D, + COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_2D, + COMMAND_LOCATE_PLAYER_IN_CAR_CAR_2D, + COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_3D, + COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_3D, + COMMAND_LOCATE_PLAYER_IN_CAR_CAR_3D, + COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_2D, + COMMAND_LOCATE_CHAR_ON_FOOT_CAR_2D, + COMMAND_LOCATE_CHAR_IN_CAR_CAR_2D, + COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_3D, + COMMAND_LOCATE_CHAR_ON_FOOT_CAR_3D, + COMMAND_LOCATE_CHAR_IN_CAR_CAR_3D, + COMMAND_GENERATE_RANDOM_FLOAT_IN_RANGE, + COMMAND_GENERATE_RANDOM_INT_IN_RANGE, + COMMAND_LOCK_CAR_DOORS, + COMMAND_EXPLODE_CAR, + COMMAND_ADD_EXPLOSION, + COMMAND_IS_CAR_UPRIGHT, + COMMAND_TURN_CHAR_TO_FACE_CHAR, + COMMAND_TURN_CHAR_TO_FACE_PLAYER, + COMMAND_TURN_PLAYER_TO_FACE_CHAR, + COMMAND_SET_CHAR_OBJ_GOTO_COORD_ON_FOOT, + COMMAND_SET_CHAR_OBJ_GOTO_COORD_IN_CAR, + COMMAND_CREATE_PICKUP, + COMMAND_HAS_PICKUP_BEEN_COLLECTED, + COMMAND_REMOVE_PICKUP, + COMMAND_SET_TAXI_LIGHTS, + COMMAND_PRINT_BIG_Q, + COMMAND_PRINT_WITH_NUMBER_BIG_Q, + COMMAND_SET_GARAGE, + COMMAND_SET_GARAGE_WITH_CAR_MODEL, + COMMAND_SET_TARGET_CAR_FOR_MISSION_GARAGE, + COMMAND_IS_CAR_IN_MISSION_GARAGE, + COMMAND_SET_FREE_BOMBS, + COMMAND_SET_POWERPOINT, + COMMAND_SET_ALL_TAXI_LIGHTS, + COMMAND_IS_CAR_ARMED_WITH_ANY_BOMB, + COMMAND_APPLY_BRAKES_TO_PLAYERS_CAR, + COMMAND_SET_PLAYER_HEALTH, + COMMAND_SET_CHAR_HEALTH, + COMMAND_SET_CAR_HEALTH, + COMMAND_GET_PLAYER_HEALTH, + COMMAND_GET_CHAR_HEALTH, + COMMAND_GET_CAR_HEALTH, + COMMAND_IS_CAR_ARMED_WITH_BOMB, + COMMAND_CHANGE_CAR_COLOUR, + COMMAND_SWITCH_PED_ROADS_ON, + COMMAND_SWITCH_PED_ROADS_OFF, + COMMAND_CHAR_LOOK_AT_CHAR_ALWAYS, + COMMAND_CHAR_LOOK_AT_PLAYER_ALWAYS, + COMMAND_PLAYER_LOOK_AT_CHAR_ALWAYS, + COMMAND_STOP_CHAR_LOOKING, + COMMAND_STOP_PLAYER_LOOKING, + COMMAND_SWITCH_HELICOPTER, + COMMAND_SET_GANG_ATTITUDE, + COMMAND_SET_GANG_GANG_ATTITUDE, + COMMAND_SET_GANG_PLAYER_ATTITUDE, + COMMAND_SET_GANG_PED_MODELS, + COMMAND_SET_GANG_CAR_MODEL, + COMMAND_SET_GANG_WEAPONS, + COMMAND_SET_CHAR_OBJ_RUN_TO_AREA, + COMMAND_SET_CHAR_OBJ_RUN_TO_COORD, + COMMAND_IS_PLAYER_TOUCHING_OBJECT_ON_FOOT, + COMMAND_IS_CHAR_TOUCHING_OBJECT_ON_FOOT, + COMMAND_LOAD_SPECIAL_CHARACTER, + COMMAND_HAS_SPECIAL_CHARACTER_LOADED, + COMMAND_FLASH_CAR, + COMMAND_FLASH_CHAR, + COMMAND_FLASH_OBJECT, + COMMAND_IS_PLAYER_IN_REMOTE_MODE, + COMMAND_ARM_CAR_WITH_BOMB, + COMMAND_SET_CHAR_PERSONALITY, + COMMAND_SET_CUTSCENE_OFFSET, + COMMAND_SET_ANIM_GROUP_FOR_CHAR, + COMMAND_SET_ANIM_GROUP_FOR_PLAYER, + COMMAND_REQUEST_MODEL, + COMMAND_HAS_MODEL_LOADED, + COMMAND_MARK_MODEL_AS_NO_LONGER_NEEDED, + COMMAND_GRAB_PHONE, + COMMAND_SET_REPEATED_PHONE_MESSAGE, + COMMAND_SET_PHONE_MESSAGE, + COMMAND_HAS_PHONE_DISPLAYED_MESSAGE, + COMMAND_TURN_PHONE_OFF, + COMMAND_DRAW_CORONA, + COMMAND_DRAW_LIGHT, + COMMAND_STORE_WEATHER, + COMMAND_RESTORE_WEATHER, + COMMAND_STORE_CLOCK, + COMMAND_RESTORE_CLOCK, + COMMAND_RESTART_CRITICAL_MISSION, + COMMAND_IS_PLAYER_PLAYING, + COMMAND_SET_COLL_OBJ_NO_OBJ, + COMMAND_SET_COLL_OBJ_WAIT_ON_FOOT, + COMMAND_SET_COLL_OBJ_FLEE_ON_FOOT_TILL_SAFE, + COMMAND_SET_COLL_OBJ_GUARD_SPOT, + COMMAND_SET_COLL_OBJ_GUARD_AREA, + COMMAND_SET_COLL_OBJ_WAIT_IN_CAR, + COMMAND_SET_COLL_OBJ_KILL_CHAR_ON_FOOT, + COMMAND_SET_COLL_OBJ_KILL_PLAYER_ON_FOOT, + COMMAND_SET_COLL_OBJ_KILL_CHAR_ANY_MEANS, + COMMAND_SET_COLL_OBJ_KILL_PLAYER_ANY_MEANS, + COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE, + COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE, + COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS, + COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS, + COMMAND_SET_COLL_OBJ_GOTO_CHAR_ON_FOOT, + COMMAND_SET_COLL_OBJ_GOTO_PLAYER_ON_FOOT, + COMMAND_SET_COLL_OBJ_LEAVE_CAR, + COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_PASSENGER, + COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_DRIVER, + COMMAND_SET_COLL_OBJ_FOLLOW_CAR_IN_CAR, + COMMAND_SET_COLL_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE, + COMMAND_SET_COLL_OBJ_DESTROY_OBJECT, + COMMAND_SET_COLL_OBJ_DESTROY_CAR, + COMMAND_SET_COLL_OBJ_GOTO_AREA_ON_FOOT, + COMMAND_SET_COLL_OBJ_GOTO_AREA_IN_CAR, + COMMAND_SET_COLL_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, + COMMAND_SET_COLL_OBJ_GUARD_ATTACK, + COMMAND_SET_COLL_OBJ_FOLLOW_ROUTE, + COMMAND_SET_COLL_OBJ_GOTO_COORD_ON_FOOT, + COMMAND_SET_COLL_OBJ_GOTO_COORD_IN_CAR, + COMMAND_SET_COLL_OBJ_RUN_TO_AREA, + COMMAND_SET_COLL_OBJ_RUN_TO_COORD, + COMMAND_ADD_PEDS_IN_AREA_TO_COLL, + COMMAND_ADD_PEDS_IN_VEHICLE_TO_COLL, + COMMAND_CLEAR_COLL, + COMMAND_IS_COLL_IN_CARS, + COMMAND_LOCATE_COLL_ANY_MEANS_2D, + COMMAND_LOCATE_COLL_ON_FOOT_2D, + COMMAND_LOCATE_COLL_IN_CAR_2D, + COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D, + COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D, + COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D, + COMMAND_LOCATE_COLL_ANY_MEANS_CHAR_2D, + COMMAND_LOCATE_COLL_ON_FOOT_CHAR_2D, + COMMAND_LOCATE_COLL_IN_CAR_CHAR_2D, + COMMAND_LOCATE_COLL_ANY_MEANS_CAR_2D, + COMMAND_LOCATE_COLL_ON_FOOT_CAR_2D, + COMMAND_LOCATE_COLL_IN_CAR_CAR_2D, + COMMAND_LOCATE_COLL_ANY_MEANS_PLAYER_2D, + COMMAND_LOCATE_COLL_ON_FOOT_PLAYER_2D, + COMMAND_LOCATE_COLL_IN_CAR_PLAYER_2D, + COMMAND_IS_COLL_IN_AREA_2D, + COMMAND_IS_COLL_IN_AREA_ON_FOOT_2D, + COMMAND_IS_COLL_IN_AREA_IN_CAR_2D, + COMMAND_IS_COLL_STOPPED_IN_AREA_2D, + COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D, + COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D, + COMMAND_GET_NUMBER_OF_PEDS_IN_COLL, + COMMAND_SET_CHAR_HEED_THREATS, + COMMAND_SET_PLAYER_HEED_THREATS, + COMMAND_GET_CONTROLLER_MODE, + COMMAND_SET_CAN_RESPRAY_CAR, + COMMAND_IS_TAXI, + COMMAND_UNLOAD_SPECIAL_CHARACTER, + COMMAND_RESET_NUM_OF_MODELS_KILLED_BY_PLAYER, + COMMAND_GET_NUM_OF_MODELS_KILLED_BY_PLAYER, + COMMAND_ACTIVATE_GARAGE, + COMMAND_SWITCH_TAXI_TIMER, + COMMAND_CREATE_OBJECT_NO_OFFSET, + COMMAND_IS_BOAT, + COMMAND_SET_CHAR_OBJ_GOTO_AREA_ANY_MEANS, + COMMAND_SET_COLL_OBJ_GOTO_AREA_ANY_MEANS, + COMMAND_IS_PLAYER_STOPPED, + COMMAND_IS_CHAR_STOPPED, + COMMAND_MESSAGE_WAIT, + COMMAND_ADD_PARTICLE_EFFECT, + COMMAND_SWITCH_WIDESCREEN, + COMMAND_ADD_SPRITE_BLIP_FOR_CAR, + COMMAND_ADD_SPRITE_BLIP_FOR_CHAR, + COMMAND_ADD_SPRITE_BLIP_FOR_OBJECT, + COMMAND_ADD_SPRITE_BLIP_FOR_CONTACT_POINT, + COMMAND_ADD_SPRITE_BLIP_FOR_COORD, + COMMAND_SET_CHAR_ONLY_DAMAGED_BY_PLAYER, + COMMAND_SET_CAR_ONLY_DAMAGED_BY_PLAYER, + COMMAND_SET_CHAR_PROOFS, + COMMAND_SET_CAR_PROOFS, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_2D, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_2D, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_2D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_2D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_2D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_2D, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_3D, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_3D, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_3D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D, + COMMAND_DEACTIVATE_GARAGE, + COMMAND_GET_NUMBER_OF_CARS_COLLECTED_BY_GARAGE, + COMMAND_HAS_CAR_BEEN_TAKEN_TO_GARAGE, + COMMAND_SET_SWAT_REQUIRED, + COMMAND_SET_FBI_REQUIRED, + COMMAND_SET_ARMY_REQUIRED, + COMMAND_IS_CAR_IN_WATER, + COMMAND_GET_CLOSEST_CHAR_NODE, + COMMAND_GET_CLOSEST_CAR_NODE, + COMMAND_CAR_GOTO_COORDINATES_ACCURATE, + COMMAND_START_PACMAN_RACE, + COMMAND_START_PACMAN_RECORD, + COMMAND_GET_NUMBER_OF_POWER_PILLS_EATEN, + COMMAND_CLEAR_PACMAN, + COMMAND_START_PACMAN_SCRAMBLE, + COMMAND_GET_NUMBER_OF_POWER_PILLS_CARRIED, + COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_CARRIED, + COMMAND_IS_CAR_ON_SCREEN, + COMMAND_IS_CHAR_ON_SCREEN, + COMMAND_IS_OBJECT_ON_SCREEN, + COMMAND_GOSUB_FILE, + COMMAND_GET_GROUND_Z_FOR_3D_COORD, + COMMAND_START_SCRIPT_FIRE, + COMMAND_IS_SCRIPT_FIRE_EXTINGUISHED, + COMMAND_REMOVE_SCRIPT_FIRE, + COMMAND_SET_COMEDY_CONTROLS, + COMMAND_BOAT_GOTO_COORDS, + COMMAND_BOAT_STOP, + COMMAND_IS_PLAYER_SHOOTING_IN_AREA, + COMMAND_IS_CHAR_SHOOTING_IN_AREA, + COMMAND_IS_CURRENT_PLAYER_WEAPON, + COMMAND_IS_CURRENT_CHAR_WEAPON, + COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_EATEN, + COMMAND_ADD_POWER_PILL, + COMMAND_SET_BOAT_CRUISE_SPEED, + COMMAND_GET_RANDOM_CHAR_IN_AREA, + COMMAND_GET_RANDOM_CHAR_IN_ZONE, + COMMAND_IS_PLAYER_IN_TAXI, + COMMAND_IS_PLAYER_SHOOTING, + COMMAND_IS_CHAR_SHOOTING, + COMMAND_CREATE_MONEY_PICKUP, + COMMAND_SET_CHAR_ACCURACY, + COMMAND_GET_CAR_SPEED, + COMMAND_LOAD_CUTSCENE, + COMMAND_CREATE_CUTSCENE_OBJECT, + COMMAND_SET_CUTSCENE_ANIM, + COMMAND_START_CUTSCENE, + COMMAND_GET_CUTSCENE_TIME, + COMMAND_HAS_CUTSCENE_FINISHED, + COMMAND_CLEAR_CUTSCENE, + COMMAND_RESTORE_CAMERA_JUMPCUT, + COMMAND_CREATE_COLLECTABLE1, + COMMAND_SET_COLLECTABLE1_TOTAL, + COMMAND_IS_PROJECTILE_IN_AREA, + COMMAND_DESTROY_PROJECTILES_IN_AREA, + COMMAND_DROP_MINE, + COMMAND_DROP_NAUTICAL_MINE, + COMMAND_IS_CHAR_MODEL, + COMMAND_LOAD_SPECIAL_MODEL, + COMMAND_CREATE_CUTSCENE_HEAD, + COMMAND_SET_CUTSCENE_HEAD_ANIM, + COMMAND_SIN, + COMMAND_COS, + COMMAND_GET_CAR_FORWARD_X, + COMMAND_GET_CAR_FORWARD_Y, + COMMAND_CHANGE_GARAGE_TYPE, + COMMAND_ACTIVATE_CRUSHER_CRANE, + COMMAND_PRINT_WITH_2_NUMBERS, + COMMAND_PRINT_WITH_2_NUMBERS_NOW, + COMMAND_PRINT_WITH_2_NUMBERS_SOON, + COMMAND_PRINT_WITH_3_NUMBERS, + COMMAND_PRINT_WITH_3_NUMBERS_NOW, + COMMAND_PRINT_WITH_3_NUMBERS_SOON, + COMMAND_PRINT_WITH_4_NUMBERS, + COMMAND_PRINT_WITH_4_NUMBERS_NOW, + COMMAND_PRINT_WITH_4_NUMBERS_SOON, + COMMAND_PRINT_WITH_5_NUMBERS, + COMMAND_PRINT_WITH_5_NUMBERS_NOW, + COMMAND_PRINT_WITH_5_NUMBERS_SOON, + COMMAND_PRINT_WITH_6_NUMBERS, + COMMAND_PRINT_WITH_6_NUMBERS_NOW, + COMMAND_PRINT_WITH_6_NUMBERS_SOON, + COMMAND_SET_CHAR_OBJ_FOLLOW_CHAR_IN_FORMATION, + COMMAND_PLAYER_MADE_PROGRESS, + COMMAND_SET_PROGRESS_TOTAL, + COMMAND_REGISTER_JUMP_DISTANCE, + COMMAND_REGISTER_JUMP_HEIGHT, + COMMAND_REGISTER_JUMP_FLIPS, + COMMAND_REGISTER_JUMP_SPINS, + COMMAND_REGISTER_JUMP_STUNT, + COMMAND_REGISTER_UNIQUE_JUMP_FOUND, + COMMAND_SET_UNIQUE_JUMPS_TOTAL, + COMMAND_REGISTER_PASSENGER_DROPPED_OFF_TAXI, + COMMAND_REGISTER_MONEY_MADE_TAXI, + COMMAND_REGISTER_MISSION_GIVEN, + COMMAND_REGISTER_MISSION_PASSED, + COMMAND_SET_CHAR_RUNNING, + COMMAND_REMOVE_ALL_SCRIPT_FIRES, + COMMAND_IS_FIRST_CAR_COLOUR, + COMMAND_IS_SECOND_CAR_COLOUR, + COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_WEAPON, + COMMAND_HAS_CAR_BEEN_DAMAGED_BY_WEAPON, + COMMAND_IS_CHAR_IN_CHARS_GROUP, + COMMAND_IS_CHAR_IN_PLAYERS_GROUP, + COMMAND_EXPLODE_CHAR_HEAD, + COMMAND_EXPLODE_PLAYER_HEAD, + COMMAND_ANCHOR_BOAT, + COMMAND_SET_ZONE_GROUP, + COMMAND_START_CAR_FIRE, + COMMAND_START_CHAR_FIRE, + COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_AREA, + COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_ZONE, + COMMAND_HAS_RESPRAY_HAPPENED, + COMMAND_SET_CAMERA_ZOOM, + COMMAND_CREATE_PICKUP_WITH_AMMO, + COMMAND_SET_CAR_RAM_CAR, + COMMAND_SET_CAR_BLOCK_CAR, + COMMAND_SET_CHAR_OBJ_CATCH_TRAIN, + COMMAND_SET_COLL_OBJ_CATCH_TRAIN, + COMMAND_SET_PLAYER_NEVER_GETS_TIRED, + COMMAND_SET_PLAYER_FAST_RELOAD, + COMMAND_SET_CHAR_BLEEDING, + COMMAND_SET_CAR_FUNNY_SUSPENSION, + COMMAND_SET_CAR_BIG_WHEELS, + COMMAND_SET_FREE_RESPRAYS, + COMMAND_SET_PLAYER_VISIBLE, + COMMAND_SET_CHAR_VISIBLE, + COMMAND_SET_CAR_VISIBLE, + COMMAND_IS_AREA_OCCUPIED, + COMMAND_START_DRUG_RUN, + COMMAND_HAS_DRUG_RUN_BEEN_COMPLETED, + COMMAND_HAS_DRUG_PLANE_BEEN_SHOT_DOWN, + COMMAND_SAVE_PLAYER_FROM_FIRES, + COMMAND_DISPLAY_TEXT, + COMMAND_SET_TEXT_SCALE, + COMMAND_SET_TEXT_COLOUR, + COMMAND_SET_TEXT_JUSTIFY, + COMMAND_SET_TEXT_CENTRE, + COMMAND_SET_TEXT_WRAPX, + COMMAND_SET_TEXT_CENTRE_SIZE, + COMMAND_SET_TEXT_BACKGROUND, + COMMAND_SET_TEXT_BACKGROUND_COLOUR, + COMMAND_SET_TEXT_BACKGROUND_ONLY_TEXT, + COMMAND_SET_TEXT_PROPORTIONAL, + COMMAND_SET_TEXT_FONT, + COMMAND_INDUSTRIAL_PASSED, + COMMAND_COMMERCIAL_PASSED, + COMMAND_SUBURBAN_PASSED, + COMMAND_ROTATE_OBJECT, + COMMAND_SLIDE_OBJECT, + COMMAND_REMOVE_CHAR_ELEGANTLY, + COMMAND_SET_CHAR_STAY_IN_SAME_PLACE, + COMMAND_IS_NASTY_GAME, + COMMAND_UNDRESS_CHAR, + COMMAND_DRESS_CHAR, + COMMAND_START_CHASE_SCENE, + COMMAND_STOP_CHASE_SCENE, + COMMAND_IS_EXPLOSION_IN_AREA, + COMMAND_IS_EXPLOSION_IN_ZONE, + COMMAND_START_DRUG_DROP_OFF, + COMMAND_HAS_DROP_OFF_PLANE_BEEN_SHOT_DOWN, + COMMAND_FIND_DROP_OFF_PLANE_COORDINATES, + COMMAND_CREATE_FLOATING_PACKAGE, + COMMAND_PLACE_OBJECT_RELATIVE_TO_CAR, + COMMAND_MAKE_OBJECT_TARGETTABLE, + COMMAND_ADD_ARMOUR_TO_PLAYER, + COMMAND_ADD_ARMOUR_TO_CHAR, + COMMAND_OPEN_GARAGE, + COMMAND_CLOSE_GARAGE, + COMMAND_WARP_CHAR_FROM_CAR_TO_COORD, + COMMAND_SET_VISIBILITY_OF_CLOSEST_OBJECT_OF_TYPE, + COMMAND_HAS_CHAR_SPOTTED_CHAR, + COMMAND_SET_CHAR_OBJ_HAIL_TAXI, + COMMAND_HAS_OBJECT_BEEN_DAMAGED, + COMMAND_START_KILL_FRENZY_HEADSHOT, + COMMAND_ACTIVATE_MILITARY_CRANE, + COMMAND_WARP_PLAYER_INTO_CAR, + COMMAND_WARP_CHAR_INTO_CAR, + COMMAND_SWITCH_CAR_RADIO, + COMMAND_SET_AUDIO_STREAM, + COMMAND_PRINT_WITH_2_NUMBERS_BIG, + COMMAND_PRINT_WITH_3_NUMBERS_BIG, + COMMAND_PRINT_WITH_4_NUMBERS_BIG, + COMMAND_PRINT_WITH_5_NUMBERS_BIG, + COMMAND_PRINT_WITH_6_NUMBERS_BIG, + COMMAND_SET_CHAR_WAIT_STATE, + COMMAND_SET_CAMERA_BEHIND_PLAYER, + COMMAND_SET_MOTION_BLUR, + COMMAND_PRINT_STRING_IN_STRING, + COMMAND_CREATE_RANDOM_CHAR, + COMMAND_SET_CHAR_OBJ_STEAL_ANY_CAR, + COMMAND_SET_2_REPEATED_PHONE_MESSAGES, + COMMAND_SET_2_PHONE_MESSAGES, + COMMAND_SET_3_REPEATED_PHONE_MESSAGES, + COMMAND_SET_3_PHONE_MESSAGES, + COMMAND_SET_4_REPEATED_PHONE_MESSAGES, + COMMAND_SET_4_PHONE_MESSAGES, + COMMAND_IS_SNIPER_BULLET_IN_AREA, + COMMAND_GIVE_PLAYER_DETONATOR, + COMMAND_SET_COLL_OBJ_STEAL_ANY_CAR, + COMMAND_SET_OBJECT_VELOCITY, + COMMAND_SET_OBJECT_COLLISION, + COMMAND_IS_ICECREAM_JINGLE_ON, + COMMAND_PRINT_STRING_IN_STRING_NOW, + COMMAND_PRINT_STRING_IN_STRING_SOON, + COMMAND_SET_5_REPEATED_PHONE_MESSAGES, + COMMAND_SET_5_PHONE_MESSAGES, + COMMAND_SET_6_REPEATED_PHONE_MESSAGES, + COMMAND_SET_6_PHONE_MESSAGES, + COMMAND_IS_POINT_OBSCURED_BY_A_MISSION_ENTITY, + COMMAND_LOAD_ALL_MODELS_NOW, + COMMAND_ADD_TO_OBJECT_VELOCITY, + COMMAND_DRAW_SPRITE, + COMMAND_DRAW_RECT, + COMMAND_LOAD_SPRITE, + COMMAND_LOAD_TEXTURE_DICTIONARY, + COMMAND_REMOVE_TEXTURE_DICTIONARY, + COMMAND_SET_OBJECT_DYNAMIC, + COMMAND_SET_CHAR_ANIM_SPEED, + COMMAND_PLAY_MISSION_PASSED_TUNE, + COMMAND_CLEAR_AREA, + COMMAND_FREEZE_ONSCREEN_TIMER, + COMMAND_SWITCH_CAR_SIREN, + COMMAND_SWITCH_PED_ROADS_ON_ANGLED, + COMMAND_SWITCH_PED_ROADS_OFF_ANGLED, + COMMAND_SWITCH_ROADS_ON_ANGLED, + COMMAND_SWITCH_ROADS_OFF_ANGLED, + COMMAND_SET_CAR_WATERTIGHT, + COMMAND_ADD_MOVING_PARTICLE_EFFECT, + COMMAND_SET_CHAR_CANT_BE_DRAGGED_OUT, + COMMAND_TURN_CAR_TO_FACE_COORD, + COMMAND_IS_CRANE_LIFTING_CAR, + COMMAND_DRAW_SPHERE, + COMMAND_SET_CAR_STATUS, + COMMAND_IS_CHAR_MALE, + COMMAND_SCRIPT_NAME, + COMMAND_CHANGE_GARAGE_TYPE_WITH_CAR_MODEL, + COMMAND_FIND_DRUG_PLANE_COORDINATES, + COMMAND_SAVE_INT_TO_DEBUG_FILE, + COMMAND_SAVE_FLOAT_TO_DEBUG_FILE, + COMMAND_SAVE_NEWLINE_TO_DEBUG_FILE, + COMMAND_POLICE_RADIO_MESSAGE, + COMMAND_SET_CAR_STRONG, + COMMAND_REMOVE_ROUTE, + COMMAND_SWITCH_RUBBISH, + COMMAND_REMOVE_PARTICLE_EFFECTS_IN_AREA, + COMMAND_SWITCH_STREAMING, + COMMAND_IS_GARAGE_OPEN, + COMMAND_IS_GARAGE_CLOSED, + COMMAND_START_CATALINA_HELI, + COMMAND_CATALINA_HELI_TAKE_OFF, + COMMAND_REMOVE_CATALINA_HELI, + COMMAND_HAS_CATALINA_HELI_BEEN_SHOT_DOWN, + COMMAND_SWAP_NEAREST_BUILDING_MODEL, + COMMAND_SWITCH_WORLD_PROCESSING, + COMMAND_REMOVE_ALL_PLAYER_WEAPONS, + COMMAND_GRAB_CATALINA_HELI, + COMMAND_CLEAR_AREA_OF_CARS, + COMMAND_SET_ROTATING_GARAGE_DOOR, + COMMAND_ADD_SPHERE, + COMMAND_REMOVE_SPHERE, + COMMAND_CATALINA_HELI_FLY_AWAY, + COMMAND_SET_EVERYONE_IGNORE_PLAYER, + COMMAND_STORE_CAR_CHAR_IS_IN_NO_SAVE, + COMMAND_STORE_CAR_PLAYER_IS_IN_NO_SAVE, + COMMAND_IS_PHONE_DISPLAYING_MESSAGE, + COMMAND_DISPLAY_ONSCREEN_TIMER_WITH_STRING, + COMMAND_DISPLAY_ONSCREEN_COUNTER_WITH_STRING, + COMMAND_CREATE_RANDOM_CAR_FOR_CAR_PARK, + COMMAND_IS_COLLISION_IN_MEMORY, + COMMAND_SET_WANTED_MULTIPLIER, + COMMAND_SET_CAMERA_IN_FRONT_OF_PLAYER, + COMMAND_IS_CAR_VISIBLY_DAMAGED, + COMMAND_DOES_OBJECT_EXIST, + COMMAND_LOAD_SCENE, + COMMAND_ADD_STUCK_CAR_CHECK, + COMMAND_REMOVE_STUCK_CAR_CHECK, + COMMAND_IS_CAR_STUCK, + COMMAND_LOAD_MISSION_AUDIO, + COMMAND_HAS_MISSION_AUDIO_LOADED, + COMMAND_PLAY_MISSION_AUDIO, + COMMAND_HAS_MISSION_AUDIO_FINISHED, + COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING, + COMMAND_HAS_IMPORT_GARAGE_SLOT_BEEN_FILLED, + COMMAND_CLEAR_THIS_PRINT, + COMMAND_CLEAR_THIS_BIG_PRINT, + COMMAND_SET_MISSION_AUDIO_POSITION, + COMMAND_ACTIVATE_SAVE_MENU, + COMMAND_HAS_SAVE_GAME_FINISHED, + COMMAND_NO_SPECIAL_CAMERA_FOR_THIS_GARAGE, + COMMAND_ADD_BLIP_FOR_PICKUP_OLD, + COMMAND_ADD_BLIP_FOR_PICKUP, + COMMAND_ADD_SPRITE_BLIP_FOR_PICKUP, + COMMAND_SET_PED_DENSITY_MULTIPLIER, + COMMAND_FORCE_RANDOM_PED_TYPE, + COMMAND_SET_TEXT_DRAW_BEFORE_FADE, + COMMAND_GET_COLLECTABLE1S_COLLECTED, + COMMAND_REGISTER_EL_BURRO_TIME, + COMMAND_SET_SPRITES_DRAW_BEFORE_FADE, + COMMAND_SET_TEXT_RIGHT_JUSTIFY, + COMMAND_PRINT_HELP, + COMMAND_CLEAR_HELP, + COMMAND_FLASH_HUD_OBJECT, + COMMAND_FLASH_RADAR_BLIP, + COMMAND_IS_CHAR_IN_CONTROL, + COMMAND_SET_GENERATE_CARS_AROUND_CAMERA, + COMMAND_CLEAR_SMALL_PRINTS, + COMMAND_HAS_MILITARY_CRANE_COLLECTED_ALL_CARS, + COMMAND_SET_UPSIDEDOWN_CAR_NOT_DAMAGED, + COMMAND_CAN_PLAYER_START_MISSION, + COMMAND_MAKE_PLAYER_SAFE_FOR_CUTSCENE, + COMMAND_USE_TEXT_COMMANDS, + COMMAND_SET_THREAT_FOR_PED_TYPE, + COMMAND_CLEAR_THREAT_FOR_PED_TYPE, + COMMAND_GET_CAR_COLOURS, + COMMAND_SET_ALL_CARS_CAN_BE_DAMAGED, + COMMAND_SET_CAR_CAN_BE_DAMAGED, + COMMAND_MAKE_PLAYER_UNSAFE, + COMMAND_LOAD_COLLISION, + COMMAND_GET_BODY_CAST_HEALTH, + COMMAND_SET_CHARS_CHATTING, + COMMAND_MAKE_PLAYER_SAFE, + COMMAND_SET_CAR_STAYS_IN_CURRENT_LEVEL, + COMMAND_SET_CHAR_STAYS_IN_CURRENT_LEVEL, + COMMAND_REGISTER_4X4_ONE_TIME, + COMMAND_REGISTER_4X4_TWO_TIME, + COMMAND_REGISTER_4X4_THREE_TIME, + COMMAND_REGISTER_4X4_MAYHEM_TIME, + COMMAND_REGISTER_LIFE_SAVED, + COMMAND_REGISTER_CRIMINAL_CAUGHT, + COMMAND_REGISTER_AMBULANCE_LEVEL, + COMMAND_REGISTER_FIRE_EXTINGUISHED, + COMMAND_TURN_PHONE_ON, + COMMAND_REGISTER_LONGEST_DODO_FLIGHT, + COMMAND_REGISTER_DEFUSE_BOMB_TIME, + COMMAND_SET_TOTAL_NUMBER_OF_KILL_FRENZIES, + COMMAND_BLOW_UP_RC_BUGGY, + COMMAND_REMOVE_CAR_FROM_CHASE, + COMMAND_IS_FRENCH_GAME, + COMMAND_IS_GERMAN_GAME, + COMMAND_CLEAR_MISSION_AUDIO, + COMMAND_SET_FADE_IN_AFTER_NEXT_ARREST, + COMMAND_SET_FADE_IN_AFTER_NEXT_DEATH, + COMMAND_SET_GANG_PED_MODEL_PREFERENCE, + COMMAND_SET_CHAR_USE_PEDNODE_SEEK, + COMMAND_SWITCH_VEHICLE_WEAPONS, + COMMAND_SET_GET_OUT_OF_JAIL_FREE, + COMMAND_SET_FREE_HEALTH_CARE, + COMMAND_IS_CAR_DOOR_CLOSED, + COMMAND_LOAD_AND_LAUNCH_MISSION, + COMMAND_LOAD_AND_LAUNCH_MISSION_INTERNAL, + COMMAND_SET_OBJECT_DRAW_LAST, + COMMAND_GET_AMMO_IN_PLAYER_WEAPON, + COMMAND_GET_AMMO_IN_CHAR_WEAPON, + COMMAND_REGISTER_KILL_FRENZY_PASSED, + COMMAND_SET_CHAR_SAY, + COMMAND_SET_NEAR_CLIP, + COMMAND_SET_RADIO_CHANNEL, + COMMAND_OVERRIDE_HOSPITAL_LEVEL, + COMMAND_OVERRIDE_POLICE_STATION_LEVEL, + COMMAND_FORCE_RAIN, + COMMAND_DOES_GARAGE_CONTAIN_CAR, + COMMAND_SET_CAR_TRACTION, + COMMAND_ARE_MEASUREMENTS_IN_METRES, + COMMAND_CONVERT_METRES_TO_FEET, + COMMAND_MARK_ROADS_BETWEEN_LEVELS, + COMMAND_MARK_PED_ROADS_BETWEEN_LEVELS, + COMMAND_SET_CAR_AVOID_LEVEL_TRANSITIONS, + COMMAND_SET_CHAR_AVOID_LEVEL_TRANSITIONS, + COMMAND_IS_THREAT_FOR_PED_TYPE, + COMMAND_CLEAR_AREA_OF_CHARS, + COMMAND_SET_TOTAL_NUMBER_OF_MISSIONS, + COMMAND_CONVERT_METRES_TO_FEET_INT, + COMMAND_REGISTER_FASTEST_TIME, + COMMAND_REGISTER_HIGHEST_SCORE, + COMMAND_WARP_CHAR_INTO_CAR_AS_PASSENGER, + COMMAND_IS_CAR_PASSENGER_SEAT_FREE, + COMMAND_GET_CHAR_IN_CAR_PASSENGER_SEAT, + COMMAND_SET_CHAR_IS_CHRIS_CRIMINAL, + COMMAND_START_CREDITS, + COMMAND_STOP_CREDITS, + COMMAND_ARE_CREDITS_FINISHED, + COMMAND_CREATE_SINGLE_PARTICLE, + COMMAND_SET_CHAR_IGNORE_LEVEL_TRANSITIONS, + COMMAND_GET_CHASE_CAR, + COMMAND_START_BOAT_FOAM_ANIMATION, + COMMAND_UPDATE_BOAT_FOAM_ANIMATION, + COMMAND_SET_MUSIC_DOES_FADE, + COMMAND_SET_INTRO_IS_PLAYING, + COMMAND_SET_PLAYER_HOOKER, + COMMAND_PLAY_END_OF_GAME_TUNE, + COMMAND_STOP_END_OF_GAME_TUNE, + COMMAND_GET_CAR_MODEL, + COMMAND_IS_PLAYER_SITTING_IN_CAR, + COMMAND_IS_PLAYER_SITTING_IN_ANY_CAR, + COMMAND_SET_SCRIPT_FIRE_AUDIO, + COMMAND_ARE_ANY_CAR_CHEATS_ACTIVATED, + COMMAND_SET_CHAR_SUFFERS_CRITICAL_HITS, + COMMAND_IS_PLAYER_LIFTING_A_PHONE, + COMMAND_IS_CHAR_SITTING_IN_CAR, + COMMAND_IS_CHAR_SITTING_IN_ANY_CAR, + COMMAND_IS_PLAYER_ON_FOOT, + COMMAND_IS_CHAR_ON_FOOT, + COMMAND_LOAD_COLLISION_WITH_SCREEN, + COMMAND_LOAD_SPLASH_SCREEN, + COMMAND_SET_CAR_IGNORE_LEVEL_TRANSITIONS, + COMMAND_MAKE_CRAIGS_CAR_A_BIT_STRONGER, + COMMAND_SET_JAMES_CAR_ON_PATH_TO_PLAYER, + COMMAND_LOAD_END_OF_GAME_TUNE, + COMMAND_ENABLE_PLAYER_CONTROL_CAMERA, +#if GTA_VERSION > GTA3_PS2_160 + COMMAND_SET_OBJECT_ROTATION, + COMMAND_GET_DEBUG_CAMERA_COORDINATES, + COMMAND_GET_DEBUG_CAMERA_FRONT_VECTOR, + COMMAND_IS_PLAYER_TARGETTING_ANY_CHAR, + COMMAND_IS_PLAYER_TARGETTING_CHAR, + COMMAND_IS_PLAYER_TARGETTING_OBJECT, + COMMAND_TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, + COMMAND_DISPLAY_TEXT_WITH_NUMBER, + COMMAND_DISPLAY_TEXT_WITH_2_NUMBERS, + COMMAND_FAIL_CURRENT_MISSION, + COMMAND_GET_CLOSEST_OBJECT_OF_TYPE, + COMMAND_PLACE_OBJECT_RELATIVE_TO_OBJECT, + COMMAND_SET_ALL_OCCUPANTS_OF_CAR_LEAVE_CAR, + COMMAND_SET_INTERPOLATION_PARAMETERS, + COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_TOWARDS_POINT, + COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_AWAY_POINT, + COMMAND_GET_DEBUG_CAMERA_POINT_AT, + COMMAND_ATTACH_CHAR_TO_CAR, + COMMAND_DETACH_CHAR_FROM_CAR, + COMMAND_SET_CAR_CHANGE_LANE, + COMMAND_CLEAR_CHAR_LAST_WEAPON_DAMAGE, + COMMAND_CLEAR_CAR_LAST_WEAPON_DAMAGE, + COMMAND_GET_RANDOM_COP_IN_AREA, + COMMAND_GET_RANDOM_COP_IN_ZONE, + COMMAND_SET_CHAR_OBJ_FLEE_CAR, + COMMAND_GET_DRIVER_OF_CAR, + COMMAND_GET_NUMBER_OF_FOLLOWERS, + COMMAND_GIVE_REMOTE_CONTROLLED_MODEL_TO_PLAYER, + COMMAND_GET_CURRENT_PLAYER_WEAPON, + COMMAND_GET_CURRENT_CHAR_WEAPON, + COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_2D, + COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_2D, + COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_2D, + COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_3D, + COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_3D, + COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_3D, + COMMAND_SET_CAR_HANDBRAKE_TURN_LEFT, + COMMAND_SET_CAR_HANDBRAKE_TURN_RIGHT, + COMMAND_SET_CAR_HANDBRAKE_STOP, + COMMAND_IS_CHAR_ON_ANY_BIKE, + COMMAND_LOCATE_SNIPER_BULLET_2D, + COMMAND_LOCATE_SNIPER_BULLET_3D, + COMMAND_GET_NUMBER_OF_SEATS_IN_MODEL, + COMMAND_IS_PLAYER_ON_ANY_BIKE, + COMMAND_IS_CHAR_LYING_DOWN, + COMMAND_CAN_CHAR_SEE_DEAD_CHAR, + COMMAND_SET_ENTER_CAR_RANGE_MULTIPLIER, +#if GTA_VERSION < GTA3_PC_11 + COMMAND_SET_THREAT_REACTION_RANGE_MULTIPLIER, +#endif +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LAST_SCRIPT_COMMAND +#endif +#endif +}; + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + +enum eScriptArgument +{ + ARGTYPE_NONE = 0, + ARGTYPE_INT, + ARGTYPE_FLOAT, + ARGTYPE_STRING, + ARGTYPE_LABEL, + ARGTYPE_BOOL, + ARGTYPE_PED_HANDLE, + ARGTYPE_VEHICLE_HANDLE, + ARGTYPE_OBJECT_HANDLE, + ARGTYPE_ANDOR +}; + +struct tScriptCommandData +{ + int id; + const char name[64]; + eScriptArgument input[18]; + eScriptArgument output[18]; + bool cond; + int position; + const char name_override[8]; +}; +#endif \ No newline at end of file diff --git a/src/control/ScriptDebug.cpp b/src/control/ScriptDebug.cpp new file mode 100644 index 0000000..6350821 --- /dev/null +++ b/src/control/ScriptDebug.cpp @@ -0,0 +1,1441 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "Debug.h" +#include "FileMgr.h" +#include "Messages.h" +#include "Timer.h" +#include "Stats.h" +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#include +#endif + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + +char CRunningScript::commandInfo[1024]; +uint32 CRunningScript::storedIp; + +#define REGISTER_COMMAND(command, in, out, cond, ovrd, visual) { command, #command, in, out, cond, ovrd, visual } +#define INPUT_ARGUMENTS(...) { __VA_ARGS__ ARGTYPE_NONE } +#define OUTPUT_ARGUMENTS(...) { __VA_ARGS__ ARGTYPE_NONE } +const tScriptCommandData commands[] = { + REGISTER_COMMAND(COMMAND_NOP, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WAIT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GOTO, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SHAKE_CAM, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_ADD_VAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_VAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_VAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_VAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_SUB_VAL_FROM_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_VAL_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_VAL_FROM_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_VAL_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_MULT_INT_VAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_VAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_INT_LVAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_LVAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_DIV_INT_VAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_VAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_INT_LVAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_LVAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_THAN_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_THAN_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_THAN_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_THAN_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_THAN_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_THAN_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_THAN_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_THAN_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_THAN_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_THAN_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_NOT_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_GOTO_IF_TRUE, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GOTO_IF_FALSE, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TERMINATE_THIS_SCRIPT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_NEW_SCRIPT, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GOSUB, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RETURN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LINE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PLAYER_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_INT_VAR_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_FLOAT_VAR_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_INT_LVAR_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_INT_VAR_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_FLOAT_VAR_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_INT_LVAR_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_SUB_INT_VAR_FROM_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_INT_LVAR_FROM_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_INT_VAR_FROM_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_INT_LVAR_FROM_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_MULT_INT_VAR_BY_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_VAR_BY_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_INT_LVAR_BY_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_INT_VAR_BY_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_VAR_BY_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_INT_LVAR_BY_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_DIV_INT_VAR_BY_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_VAR_BY_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_INT_LVAR_BY_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_INT_VAR_BY_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_VAR_BY_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_INT_LVAR_BY_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_ADD_TIMED_VAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_ADD_TIMED_VAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_VAL_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_VAL_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SET_VAR_INT_TO_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_INT_TO_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_VAR_FLOAT_TO_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_FLOAT_TO_LVAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_VAR_FLOAT_TO_LVAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_FLOAT_TO_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_VAR_INT_TO_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_INT_TO_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_CSET_VAR_INT_TO_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_VAR_FLOAT_TO_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_LVAR_INT_TO_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_LVAR_FLOAT_TO_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_VAR_INT_TO_LVAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_VAR_FLOAT_TO_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_LVAR_INT_TO_LVAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_LVAR_FLOAT_TO_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_ABS_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_ABS_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_ABS_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_ABS_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_GENERATE_RANDOM_FLOAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GENERATE_RANDOM_INT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_PED_HANDLE,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DELETE_CHAR, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_WANDER_DIR, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_WANDER_RANGE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_FOLLOW_PATH, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_SET_IDLE, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHAR_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STILL_ALIVE, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DELETE_CAR, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CAR_GOTO_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CAR_WANDER_RANDOMLY, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CAR_SET_IDLE, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STILL_ALIVE, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_CRUISE_SPEED, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_DRIVING_STYLE, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_MISSION, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_0, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_1, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_2, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_3, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_4, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_5, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_6, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_7, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_PRINTS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_TIME_OF_DAY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TIME_OF_DAY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_MINUTES_TO_TIME_OF_DAY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_POINT_ON_SCREEN, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DEBUG_ON, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DEBUG_OFF, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RETURN_TRUE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RETURN_FALSE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_VAR_INT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_VAR_FLOAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LVAR_INT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LVAR_FLOAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LBRACKET, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RBRACKET, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REPEAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ENDREPEAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IF, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IFNOT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ELSE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ENDIF, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WHILE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WHILENOT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ENDWHILE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ANDOR, INPUT_ARGUMENTS(ARGTYPE_ANDOR,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LAUNCH_MISSION, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MISSION_HAS_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_CAR_CHAR_IS_IN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_CAR_PLAYER_IS_IN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_BUTTON_PRESSED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PAD_STATE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), true, -1, ""), + REGISTER_COMMAND(COMMAND_DELETE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SCORE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_SCORE_GREATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_SCORE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_REMOTE_CONTROLLED_CAR_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ALTER_WANTED_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ALTER_WANTED_LEVEL_NO_DROP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_WANTED_LEVEL_GREATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_WANTED_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_DEATHARREST_STATE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_DEATHARREST_BEEN_EXECUTED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_AMMO_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_AMMO_TO_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_AMMO_TO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STILL_ALIVE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_DEAD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_DEAD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_DEAD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_THREAT_SEARCH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_THREAT_REACTION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_NO_OBJ, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ORDER_DRIVER_OUT_OF_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ORDER_CHAR_TO_DRIVE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PATROL_POINT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_GANGZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_PRESSING_HORN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_SPOTTED_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ORDER_CHAR_TO_BACKDOOR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_CHAR_TO_GANG, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_OBJECTIVE_PASSED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_DRIVE_AGGRESSION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_MAX_DRIVESPEED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CHAR_INSIDE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_WARP_PLAYER_FROM_CAR_TO_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_CHAR_DO_NOTHING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_INVINCIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_INVINCIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_GRAPHIC_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_GRAPHIC_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_PLAYER_BEEN_ARRESTED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_CHAR_DRIVING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_KILL_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FAVOURITE_CAR_MODEL_FOR_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OCCUPATION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_CAR_LOCK, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SHAKE_CAM_WITH_POINT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_REMAP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_JUST_SUNK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_NO_COLLIDE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_DEAD_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_DEAD_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_TRAILER_ATTACHED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_ON_TRAILER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_GOT_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_PARK, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_PARK_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_KILL_ALL_PASSENGERS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_BULLETPROOF, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_FLAMEPROOF, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_ROCKETPROOF, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CARBOMB_ACTIVE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_CAR_ALARM, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PUT_CAR_ON_TRAILER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_CRUSHED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_GANG_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CAR_GENERATOR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_CAR_GENERATOR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PAGER_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_ONSCREEN_TIMER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_ONSCREEN_TIMER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_ONSCREEN_COUNTER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_ONSCREEN_COUNTER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ZONE_CAR_INFO, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_GANG_ZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_DENSITY, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PED_DENSITY, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POINT_CAMERA_AT_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POINT_CAMERA_AT_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POINT_CAMERA_AT_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESTORE_CAMERA, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SHAKE_PAD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ZONE_PED_INFO, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TIME_SCALE, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_AIR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FIXED_CAMERA_POSITION, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POINT_CAMERA_AT_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_CAR_OLD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_CHAR_OLD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_OBJECT_OLD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_BLIP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_BLIP_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DIM_BLIP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_COORD_OLD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_BLIP_SCALE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FADING_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DO_FADE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_FADING_STATUS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_HOSPITAL_RESTART, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_POLICE_RESTART, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_OVERRIDE_NEXT_RESTART, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_SHADOW, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PLAYER_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHAR_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_OBJECT_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TOUCHING_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_TOUCHING_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_AMMO, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_AMMO, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_AMMO, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_CAMERA_SPLINE, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MOVE_CAMERA_ALONG_SPLINE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAMERA_POSITION_ALONG_SPLINE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DECLARE_MISSION_FLAG, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DECLARE_MISSION_FLAG_FOR_CONTACT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DECLARE_BASE_BRIEF_ID_FOR_CONTACT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_HEALTH_GREATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_HEALTH_GREATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_HEALTH_GREATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_CONTACT_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_COORD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_BLIP_DISPLAY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_ONE_OFF_SOUND, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_CONTINUOUS_SOUND, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_SOUND, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STUCK_ON_ROOF, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_UPSIDEDOWN_CAR_CHECK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_UPSIDEDOWN_CAR_CHECK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_WAIT_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GUARD_SPOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GUARD_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_WAIT_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STOPPED_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STOPPED_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_WEAPON_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_WEAPON_TO_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_WEAPON_TO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_CONTROL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FORCE_WEATHER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FORCE_WEATHER_NOW, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RELEASE_WEATHER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CURRENT_PLAYER_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CURRENT_CHAR_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CURRENT_CAR_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_OBJECT_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_GAME_TIMER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_CHAR_TO_FACE_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_PLAYER_TO_FACE_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_WANTED_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STOPPED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_CHAR_AS_NO_LONGER_NEEDED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_CAR_AS_NO_LONGER_NEEDED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_OBJECT_AS_NO_LONGER_NEEDED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DONT_REMOVE_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DONT_REMOVE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DONT_REMOVE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CHAR_AS_PASSENGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_KILL_CHAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_KILL_CHAR_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_CHAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_PLAYER_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_LEAVE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_PASSENGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_DRIVER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_DESTROY_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_DESTROY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_AREA_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_AREA_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GUARD_ATTACK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_AS_LEADER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_AS_LEADER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LEAVE_GROUP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FOLLOW_ROUTE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_ROUTE_POINT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_NUMBER_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_NUMBER, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_NUMBER_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_NUMBER_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_ROADS_ON, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_ROADS_OFF, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_PASSENGERS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_MAXIMUM_NUMBER_OF_PASSENGERS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_DENSITY_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HEAVY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CHAR_THREAT_SEARCH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ACTIVATE_CRANE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DEACTIVATE_CRANE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_MAX_WANTED_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_VAR_INT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_VAR_FLOAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_AIR_PROPER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_UPSIDEDOWN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PLAYER_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CANCEL_OVERRIDE_RESTART, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_POLICE_IGNORE_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PAGER_MESSAGE_WITH_NUMBER, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_KILL_FRENZY, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_READ_KILL_FRENZY_STATUS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SQRT, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GENERATE_RANDOM_FLOAT_IN_RANGE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GENERATE_RANDOM_INT_IN_RANGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCK_CAR_DOORS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_EXPLODE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_EXPLOSION, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_UPRIGHT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_CHAR_TO_FACE_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_CHAR_TO_FACE_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_PLAYER_TO_FACE_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_COORD_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_COORD_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_PICKUP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_PICKUP_BEEN_COLLECTED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_PICKUP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TAXI_LIGHTS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_BIG_Q, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_NUMBER_BIG_Q, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GARAGE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GARAGE_WITH_CAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TARGET_CAR_FOR_MISSION_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_MISSION_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FREE_BOMBS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_POWERPOINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ALL_TAXI_LIGHTS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_ARMED_WITH_ANY_BOMB, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_APPLY_BRAKES_TO_PLAYERS_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PLAYER_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHAR_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_ARMED_WITH_BOMB, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_CAR_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_PED_ROADS_ON, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_PED_ROADS_OFF, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_LOOK_AT_CHAR_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_LOOK_AT_PLAYER_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLAYER_LOOK_AT_CHAR_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_CHAR_LOOKING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_PLAYER_LOOKING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_HELICOPTER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_ATTITUDE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_GANG_ATTITUDE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_PLAYER_ATTITUDE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_PED_MODELS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_CAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_WEAPONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_RUN_TO_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_RUN_TO_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TOUCHING_OBJECT_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_TOUCHING_OBJECT_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_SPECIAL_CHARACTER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_SPECIAL_CHARACTER_LOADED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_FLASH_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FLASH_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FLASH_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_REMOTE_MODE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ARM_CAR_WITH_BOMB, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_PERSONALITY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CUTSCENE_OFFSET, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ANIM_GROUP_FOR_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ANIM_GROUP_FOR_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REQUEST_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_MODEL_LOADED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_MODEL_AS_NO_LONGER_NEEDED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GRAB_PHONE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_REPEATED_PHONE_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PHONE_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_PHONE_DISPLAYED_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_PHONE_OFF, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_CORONA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_LIGHT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_WEATHER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESTORE_WEATHER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_CLOCK, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESTORE_CLOCK, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESTART_CRITICAL_MISSION, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_PLAYING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_NO_OBJ, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_WAIT_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FLEE_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GUARD_SPOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GUARD_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_WAIT_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_KILL_CHAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_KILL_PLAYER_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_KILL_CHAR_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_KILL_PLAYER_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_CHAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_PLAYER_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_LEAVE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_PASSENGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_DRIVER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FOLLOW_CAR_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_DESTROY_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_DESTROY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_AREA_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_AREA_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GUARD_ATTACK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FOLLOW_ROUTE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_COORD_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_COORD_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_RUN_TO_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_RUN_TO_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PEDS_IN_AREA_TO_COLL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PEDS_IN_VEHICLE_TO_COLL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_COLL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_IN_CARS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ANY_MEANS_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ON_FOOT_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_IN_CAR_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ANY_MEANS_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ON_FOOT_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_IN_CAR_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ANY_MEANS_PLAYER_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ON_FOOT_PLAYER_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_IN_CAR_PLAYER_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_STOPPED_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_PEDS_IN_COLL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_HEED_THREATS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_HEED_THREATS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CONTROLLER_MODE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAN_RESPRAY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_TAXI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_UNLOAD_SPECIAL_CHARACTER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESET_NUM_OF_MODELS_KILLED_BY_PLAYER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUM_OF_MODELS_KILLED_BY_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ACTIVATE_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_TAXI_TIMER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_OBJECT_NO_OFFSET, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_BOAT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_AREA_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_AREA_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_MESSAGE_WAIT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PARTICLE_EFFECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_WIDESCREEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_CONTACT_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_COORD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_ONLY_DAMAGED_BY_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_ONLY_DAMAGED_BY_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_PROOFS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_PROOFS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DEACTIVATE_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_CARS_COLLECTED_BY_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_BEEN_TAKEN_TO_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_SWAT_REQUIRED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FBI_REQUIRED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ARMY_REQUIRED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_WATER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_CHAR_NODE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_CAR_NODE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CAR_GOTO_COORDINATES_ACCURATE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_PACMAN_RACE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_PACMAN_RECORD, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_POWER_PILLS_EATEN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_PACMAN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_PACMAN_SCRAMBLE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_POWER_PILLS_CARRIED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_CARRIED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_ON_SCREEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_ON_SCREEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_OBJECT_ON_SCREEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GOSUB_FILE, INPUT_ARGUMENTS(ARGTYPE_LABEL, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_GROUND_Z_FOR_3D_COORD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_SCRIPT_FIRE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_SCRIPT_FIRE_EXTINGUISHED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_SCRIPT_FIRE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COMEDY_CONTROLS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_BOAT_GOTO_COORDS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_BOAT_STOP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_SHOOTING_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_SHOOTING_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CURRENT_PLAYER_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CURRENT_CHAR_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_EATEN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_POWER_PILL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_BOAT_CRUISE_SPEED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_CHAR_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_CHAR_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_TAXI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_SHOOTING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_SHOOTING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_MONEY_PICKUP, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_ACCURACY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_SPEED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_CUTSCENE, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CUTSCENE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CUTSCENE_ANIM, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_CUTSCENE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CUTSCENE_TIME, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CUTSCENE_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CUTSCENE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESTORE_CAMERA_JUMPCUT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_COLLECTABLE1, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLLECTABLE1_TOTAL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PROJECTILE_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DESTROY_PROJECTILES_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DROP_MINE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DROP_NAUTICAL_MINE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_SPECIAL_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CUTSCENE_HEAD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CUTSCENE_HEAD_ANIM, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SIN, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_COS, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_FORWARD_X, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_FORWARD_Y, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_GARAGE_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ACTIVATE_CRUSHER_CRANE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_2_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_2_NUMBERS_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_2_NUMBERS_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_3_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_3_NUMBERS_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_3_NUMBERS_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_4_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_4_NUMBERS_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_4_NUMBERS_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_5_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_5_NUMBERS_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_5_NUMBERS_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_6_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_6_NUMBERS_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_6_NUMBERS_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FOLLOW_CHAR_IN_FORMATION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLAYER_MADE_PROGRESS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PROGRESS_TOTAL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_JUMP_DISTANCE, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_JUMP_HEIGHT, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_JUMP_FLIPS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_JUMP_SPINS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_JUMP_STUNT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_UNIQUE_JUMP_FOUND, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_UNIQUE_JUMPS_TOTAL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_PASSENGER_DROPPED_OFF_TAXI, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_MONEY_MADE_TAXI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_MISSION_GIVEN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_MISSION_PASSED, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_RUNNING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_ALL_SCRIPT_FIRES, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_FIRST_CAR_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_SECOND_CAR_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_BEEN_DAMAGED_BY_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_CHARS_GROUP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_PLAYERS_GROUP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_EXPLODE_CHAR_HEAD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_EXPLODE_PLAYER_HEAD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ANCHOR_BOAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ZONE_GROUP, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_CAR_FIRE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_CHAR_FIRE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_RESPRAY_HAPPENED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAMERA_ZOOM, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_PICKUP_WITH_AMMO, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_RAM_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_BLOCK_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_CATCH_TRAIN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_CATCH_TRAIN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_NEVER_GETS_TIRED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_FAST_RELOAD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_BLEEDING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_FUNNY_SUSPENSION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_BIG_WHEELS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FREE_RESPRAYS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_VISIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_VISIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_VISIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_AREA_OCCUPIED, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_START_DRUG_RUN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_DRUG_RUN_BEEN_COMPLETED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_DRUG_PLANE_BEEN_SHOT_DOWN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_PLAYER_FROM_FIRES, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_TEXT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_SCALE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_JUSTIFY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_CENTRE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_WRAPX, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_CENTRE_SIZE, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_BACKGROUND, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_BACKGROUND_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_BACKGROUND_ONLY_TEXT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_PROPORTIONAL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_FONT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_INDUSTRIAL_PASSED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_COMMERCIAL_PASSED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SUBURBAN_PASSED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ROTATE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SLIDE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_CHAR_ELEGANTLY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_STAY_IN_SAME_PLACE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_NASTY_GAME, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_UNDRESS_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRESS_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_CHASE_SCENE, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_CHASE_SCENE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_EXPLOSION_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_EXPLOSION_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_START_DRUG_DROP_OFF, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_DROP_OFF_PLANE_BEEN_SHOT_DOWN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FIND_DROP_OFF_PLANE_COORDINATES, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_FLOATING_PACKAGE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLACE_OBJECT_RELATIVE_TO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_OBJECT_TARGETTABLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_ARMOUR_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_ARMOUR_TO_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_OPEN_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLOSE_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WARP_CHAR_FROM_CAR_TO_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_VISIBILITY_OF_CLOSEST_OBJECT_OF_TYPE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_SPOTTED_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_HAIL_TAXI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_OBJECT_BEEN_DAMAGED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_START_KILL_FRENZY_HEADSHOT, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ACTIVATE_MILITARY_CRANE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WARP_PLAYER_INTO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WARP_CHAR_INTO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_CAR_RADIO, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_AUDIO_STREAM, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_2_NUMBERS_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_3_NUMBERS_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_4_NUMBERS_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_5_NUMBERS_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_6_NUMBERS_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_WAIT_STATE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAMERA_BEHIND_PLAYER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_MOTION_BLUR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_STRING_IN_STRING, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_RANDOM_CHAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_STEAL_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_2_REPEATED_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_2_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_3_REPEATED_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_3_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_4_REPEATED_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_4_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_SNIPER_BULLET_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_PLAYER_DETONATOR, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_STEAL_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_VELOCITY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_COLLISION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_ICECREAM_JINGLE_ON, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_STRING_IN_STRING_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_STRING_IN_STRING_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_5_REPEATED_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_5_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_6_REPEATED_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_6_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_POINT_OBSCURED_BY_A_MISSION_ENTITY, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_ALL_MODELS_NOW, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_TO_OBJECT_VELOCITY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_SPRITE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_RECT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_SPRITE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_TEXTURE_DICTIONARY, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_TEXTURE_DICTIONARY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_DYNAMIC, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_ANIM_SPEED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLAY_MISSION_PASSED_TUNE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FREEZE_ONSCREEN_TIMER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_CAR_SIREN, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_PED_ROADS_ON_ANGLED, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_PED_ROADS_OFF_ANGLED, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_ROADS_ON_ANGLED, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_ROADS_OFF_ANGLED, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_WATERTIGHT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_MOVING_PARTICLE_EFFECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_CANT_BE_DRAGGED_OUT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_CAR_TO_FACE_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CRANE_LIFTING_CAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_SPHERE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_STATUS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_MALE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SCRIPT_NAME, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_GARAGE_TYPE_WITH_CAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FIND_DRUG_PLANE_COORDINATES, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_INT_TO_DEBUG_FILE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_FLOAT_TO_DEBUG_FILE, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_NEWLINE_TO_DEBUG_FILE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POLICE_RADIO_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_STRONG, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_ROUTE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_RUBBISH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_PARTICLE_EFFECTS_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_STREAMING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_GARAGE_OPEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_GARAGE_CLOSED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_START_CATALINA_HELI, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CATALINA_HELI_TAKE_OFF, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_CATALINA_HELI, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CATALINA_HELI_BEEN_SHOT_DOWN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SWAP_NEAREST_BUILDING_MODEL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_WORLD_PROCESSING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_ALL_PLAYER_WEAPONS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GRAB_CATALINA_HELI, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_AREA_OF_CARS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ROTATING_GARAGE_DOOR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPHERE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_SPHERE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CATALINA_HELI_FLY_AWAY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_EVERYONE_IGNORE_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_CAR_CHAR_IS_IN_NO_SAVE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_CAR_PLAYER_IS_IN_NO_SAVE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PHONE_DISPLAYING_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_ONSCREEN_TIMER_WITH_STRING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_ONSCREEN_COUNTER_WITH_STRING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_RANDOM_CAR_FOR_CAR_PARK, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLLISION_IN_MEMORY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_WANTED_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAMERA_IN_FRONT_OF_PLAYER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_VISIBLY_DAMAGED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DOES_OBJECT_EXIST, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_SCENE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_STUCK_CAR_CHECK, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_STUCK_CAR_CHECK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STUCK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_MISSION_AUDIO, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_MISSION_AUDIO_LOADED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_PLAY_MISSION_AUDIO, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_MISSION_AUDIO_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_IMPORT_GARAGE_SLOT_BEEN_FILLED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_THIS_PRINT, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_THIS_BIG_PRINT, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_MISSION_AUDIO_POSITION, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ACTIVATE_SAVE_MENU, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_SAVE_GAME_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_NO_SPECIAL_CAMERA_FOR_THIS_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_PICKUP_OLD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_PICKUP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_PICKUP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PED_DENSITY_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FORCE_RANDOM_PED_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_DRAW_BEFORE_FADE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_COLLECTABLE1S_COLLECTED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_EL_BURRO_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_SPRITES_DRAW_BEFORE_FADE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_RIGHT_JUSTIFY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_HELP, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_HELP, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FLASH_HUD_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FLASH_RADAR_BLIP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_CONTROL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GENERATE_CARS_AROUND_CAMERA, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_SMALL_PRINTS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_MILITARY_CRANE_COLLECTED_ALL_CARS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_UPSIDEDOWN_CAR_NOT_DAMAGED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CAN_PLAYER_START_MISSION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_PLAYER_SAFE_FOR_CUTSCENE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_USE_TEXT_COMMANDS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_THREAT_FOR_PED_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_THREAT_FOR_PED_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_COLOURS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ALL_CARS_CAN_BE_DAMAGED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_CAN_BE_DAMAGED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_PLAYER_UNSAFE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_COLLISION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_BODY_CAST_HEALTH, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHARS_CHATTING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_PLAYER_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_STAYS_IN_CURRENT_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_STAYS_IN_CURRENT_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_4X4_ONE_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_4X4_TWO_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_4X4_THREE_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_4X4_MAYHEM_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_LIFE_SAVED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_CRIMINAL_CAUGHT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_AMBULANCE_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_FIRE_EXTINGUISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_PHONE_ON, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_LONGEST_DODO_FLIGHT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_DEFUSE_BOMB_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TOTAL_NUMBER_OF_KILL_FRENZIES, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_BLOW_UP_RC_BUGGY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_CAR_FROM_CHASE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_FRENCH_GAME, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_GERMAN_GAME, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_MISSION_AUDIO, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FADE_IN_AFTER_NEXT_ARREST, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FADE_IN_AFTER_NEXT_DEATH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_PED_MODEL_PREFERENCE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_USE_PEDNODE_SEEK, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_VEHICLE_WEAPONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GET_OUT_OF_JAIL_FREE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FREE_HEALTH_CARE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_DOOR_CLOSED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_AND_LAUNCH_MISSION, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_AND_LAUNCH_MISSION_INTERNAL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_DRAW_LAST, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_AMMO_IN_PLAYER_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_AMMO_IN_CHAR_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_KILL_FRENZY_PASSED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_SAY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_NEAR_CLIP, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_RADIO_CHANNEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_OVERRIDE_HOSPITAL_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_OVERRIDE_POLICE_STATION_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FORCE_RAIN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DOES_GARAGE_CONTAIN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_TRACTION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ARE_MEASUREMENTS_IN_METRES, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CONVERT_METRES_TO_FEET, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_ROADS_BETWEEN_LEVELS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_PED_ROADS_BETWEEN_LEVELS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_AVOID_LEVEL_TRANSITIONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_AVOID_LEVEL_TRANSITIONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_THREAT_FOR_PED_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_AREA_OF_CHARS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TOTAL_NUMBER_OF_MISSIONS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CONVERT_METRES_TO_FEET_INT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_FASTEST_TIME, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_HIGHEST_SCORE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WARP_CHAR_INTO_CAR_AS_PASSENGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_PASSENGER_SEAT_FREE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHAR_IN_CAR_PASSENGER_SEAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_IS_CHRIS_CRIMINAL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_CREDITS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_CREDITS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ARE_CREDITS_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_SINGLE_PARTICLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_IGNORE_LEVEL_TRANSITIONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHASE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_BOAT_FOAM_ANIMATION, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_UPDATE_BOAT_FOAM_ANIMATION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_MUSIC_DOES_FADE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_INTRO_IS_PLAYING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_HOOKER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLAY_END_OF_GAME_TUNE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_END_OF_GAME_TUNE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_SITTING_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_SITTING_IN_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_SCRIPT_FIRE_AUDIO, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ARE_ANY_CAR_CHEATS_ACTIVATED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_SUFFERS_CRITICAL_HITS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_LIFTING_A_PHONE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_SITTING_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_SITTING_IN_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_COLLISION_WITH_SCREEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_SPLASH_SCREEN, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_IGNORE_LEVEL_TRANSITIONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_CRAIGS_CAR_A_BIT_STRONGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_JAMES_CAR_ON_PATH_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_END_OF_GAME_TUNE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ENABLE_PLAYER_CONTROL_CAMERA, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), +#if GTA_VERSION > GTA3_PS2_160 + REGISTER_COMMAND(COMMAND_SET_OBJECT_ROTATION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DEBUG_CAMERA_COORDINATES, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DEBUG_CAMERA_FRONT_VECTOR, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TARGETTING_ANY_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TARGETTING_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TARGETTING_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_TEXT_WITH_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_STRING, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_TEXT_WITH_2_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FAIL_CURRENT_MISSION, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_OBJECT_OF_TYPE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLACE_OBJECT_RELATIVE_TO_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ALL_OCCUPANTS_OF_CAR_LEAVE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_INTERPOLATION_PARAMETERS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_TOWARDS_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_AWAY_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DEBUG_CAMERA_POINT_AT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ATTACH_CHAR_TO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DETACH_CHAR_FROM_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_CHANGE_LANE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CHAR_LAST_WEAPON_DAMAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CAR_LAST_WEAPON_DAMAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_COP_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_COP_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DRIVER_OF_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_FOLLOWERS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_REMOTE_CONTROLLED_MODEL_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CURRENT_PLAYER_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CURRENT_CHAR_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HANDBRAKE_TURN_LEFT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HANDBRAKE_TURN_RIGHT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HANDBRAKE_STOP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_ON_ANY_BIKE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_SNIPER_BULLET_2D, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_SNIPER_BULLET_3D, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_SEATS_IN_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_ON_ANY_BIKE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_LYING_DOWN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CAN_CHAR_SEE_DEAD_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ENTER_CAR_RANGE_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), +#if GTA_VERSION < GTA3_PC_11 + REGISTER_COMMAND(COMMAND_SET_THREAT_REACTION_RANGE_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), +#endif +#endif +}; +#undef REGISTER_COMMAND +#undef INPUT_ARGUMENTS +#undef OUTPUT_ARGUMENTS + +static_assert(ARRAY_SIZE(commands) == LAST_SCRIPT_COMMAND, "commands array not filled"); + +#if SCRIPT_LOG_FILE_LEVEL == 1 || SCRIPT_LOG_FILE_LEVEL == 2 +static FILE* dbg_log; +#endif + +static void PrintToLog(const char* format, ...) +{ + va_list va; + va_start(va, format); + char tmp[1024]; +#ifdef _WIN32 + vsprintf_s(tmp, 1024, format, va); +#else + vsprintf(tmp, format, va); +#endif + va_end(va); + +#if SCRIPT_LOG_FILE_LEVEL == 1 || SCRIPT_LOG_FILE_LEVEL == 2 + if (dbg_log) + fwrite(tmp, 1, strlen(tmp), dbg_log); +#endif +} + +int CRunningScript::CollectParameterForDebug(char* buf, bool& var) +{ + float tmp; + uint16 varIndex; + char tmpstr[24]; + var = false; + switch (CTheScripts::Read1ByteFromScript(&m_nIp)) + { + case ARGUMENT_INT32: + return CTheScripts::Read4BytesFromScript(&m_nIp); + case ARGUMENT_GLOBALVAR: + varIndex = CTheScripts::Read2BytesFromScript(&m_nIp); + script_assert(varIndex >= 8 && varIndex < CTheScripts::GetSizeOfVariableSpace()); + var = true; + sprintf(tmpstr, " $%d", varIndex / 4); + strcat(buf, tmpstr); + return *((int32*)&CTheScripts::ScriptSpace[varIndex]); + case ARGUMENT_LOCALVAR: + varIndex = CTheScripts::Read2BytesFromScript(&m_nIp); + script_assert(varIndex >= 0 && varIndex < ARRAY_SIZE(m_anLocalVariables)); + var = true; + sprintf(tmpstr, " %d@", varIndex); + strcat(buf, tmpstr); + return m_anLocalVariables[varIndex]; + case ARGUMENT_INT8: + return CTheScripts::Read1ByteFromScript(&m_nIp); + case ARGUMENT_INT16: + return CTheScripts::Read2BytesFromScript(&m_nIp); + case ARGUMENT_FLOAT: + tmp = CTheScripts::ReadFloatFromScript(&m_nIp); + return *(int32*)&tmp; + default: + PrintToLog("%s - script assertion failed in CollectParameterForDebug", buf); + script_assert(0); + break; + } + return 0; +} + +void CRunningScript::GetStoredParameterForDebug(char* buf) +{ + uint16 varIndex; + char tmpstr[24]; + switch (CTheScripts::Read1ByteFromScript(&m_nIp)) { + case ARGUMENT_GLOBALVAR: + varIndex = CTheScripts::Read2BytesFromScript(&m_nIp); + sprintf(tmpstr, " $%d", varIndex / 4); + strcat(buf, tmpstr); + break; + case ARGUMENT_LOCALVAR: + varIndex = CTheScripts::Read2BytesFromScript(&m_nIp); + sprintf(tmpstr, " %d@", varIndex); + strcat(buf, tmpstr); + break; + default: + PrintToLog("%s - script_assertion failed in GetStoredParameterForDebug", buf); + script_assert(0); + } +} + +void CTheScripts::LogAfterScriptInitializing() +{ +#if SCRIPT_LOG_FILE_LEVEL == 2 + CFileMgr::SetDirMyDocuments(); + if (dbg_log) + fclose(dbg_log); + dbg_log = fopen("SCRDBG.LOG", "w"); + static const char* init_msg = "Starting debug script log\n\n"; + PrintToLog(init_msg); + CFileMgr::SetDir(""); +#endif +} + +void CTheScripts::LogBeforeScriptProcessing() +{ + +#if SCRIPT_LOG_FILE_LEVEL == 1 + CFileMgr::SetDirMyDocuments(); + dbg_log = fopen("SCRDBG.LOG", "w"); + static const char* init_msg = "Starting debug script log\n\n"; + PrintToLog(init_msg); + CFileMgr::SetDir(""); +#endif + PrintToLog("------------------------\n"); + PrintToLog("CTheScripts::Process started, CTimer::GetTimeInMilliseconds == %u\n", CTimer::GetTimeInMilliseconds()); +} + +void CTheScripts::LogAfterScriptProcessing() +{ + PrintToLog("Script processing done, ScriptsUpdated: %d, CommandsExecuted: %d\n", ScriptsUpdated, CommandsExecuted); +#if SCRIPT_LOG_FILE_LEVEL == 1 + fclose(dbg_log); + dbg_log = nil; +#endif +} + +void CRunningScript::LogOnStartProcessing() +{ + PrintToLog("\n\nProcessing script %s (id %d)\n\n", m_abScriptName, this - CTheScripts::ScriptsArray); +} + +void CRunningScript::LogBeforeProcessingCommand(int32 command) +{ + storedIp = m_nIp; + if (command < ARRAY_SIZE(commands)) { + script_assert(commands[command].id == command); + m_nIp -= 2; + sprintf(commandInfo, m_nIp >= SIZE_MAIN_SCRIPT ? "M<%5d> " : "<%6d> ", m_nIp >= SIZE_MAIN_SCRIPT ? m_nIp - SIZE_MAIN_SCRIPT : m_nIp); + m_nIp += 2; + if (m_bNotFlag) + strcat(commandInfo, "NOT "); + if (commands[command].position == -1) + strcat(commandInfo, commands[command].name + sizeof("COMMAND_") - 1); + for (int i = 0; commands[command].input[i] != ARGTYPE_NONE; i++) { + char tmp[16]; + bool var = false; + int value; + switch (commands[command].input[i]) { + case ARGTYPE_INT: + case ARGTYPE_PED_HANDLE: + case ARGTYPE_VEHICLE_HANDLE: + case ARGTYPE_OBJECT_HANDLE: value = CollectParameterForDebug(commandInfo, var); sprintf(tmp, var ? " (%d)" : " %d", value); break; + case ARGTYPE_FLOAT: value = CollectParameterForDebug(commandInfo, var); sprintf(tmp, var ? " (%.3f)" : " %.3f", *(float*)&value); break; + case ARGTYPE_STRING: sprintf(tmp, " '%s'", (const char*)&CTheScripts::ScriptSpace[m_nIp]); m_nIp += KEY_LENGTH_IN_SCRIPT; break; + case ARGTYPE_LABEL: value = CollectParameterForDebug(commandInfo, var); sprintf(tmp, var ? " (%s(%d))" : " %s(%d)", value >= 0 ? "G" : "L", abs(value)); break; + case ARGTYPE_BOOL: value = CollectParameterForDebug(commandInfo, var); sprintf(tmp, var ? " (%s)" : " %s", value ? "TRUE" : "FALSE"); break; + case ARGTYPE_ANDOR: value = CollectParameterForDebug(commandInfo, var); sprintf(tmp, " %d %ss", (value + 1) % 10, value / 10 == 0 ? "AND" : "OR"); break; + default: script_assert(0); + } + strcat(commandInfo, tmp); + if (commands[command].position == i) + strcat(commandInfo, commands[command].name_override); + } + uint32 t = m_nIp; + m_nIp = storedIp; + storedIp = t; + } +} + +void CRunningScript::LogAfterProcessingCommand(int32 command) +{ + if (command < ARRAY_SIZE(commands)) { + if (commands[command].cond || commands[command].output[0] != ARGTYPE_NONE) { + strcat(commandInfo, " ->"); + if (commands[command].cond) + strcat(commandInfo, m_bCondResult ? " TRUE" : " FALSE"); + uint32 t = m_nIp; + m_nIp = storedIp; + storedIp = t; + for (int i = 0; commands[command].output[i] != ARGTYPE_NONE; i++) { + char tmp[16]; + switch (commands[command].output[i]) { + case ARGTYPE_INT: + case ARGTYPE_PED_HANDLE: + case ARGTYPE_VEHICLE_HANDLE: + case ARGTYPE_OBJECT_HANDLE: GetStoredParameterForDebug(commandInfo); sprintf(tmp, " (%d)", ScriptParams[i]); strcat(commandInfo, tmp); break; + case ARGTYPE_FLOAT: GetStoredParameterForDebug(commandInfo); sprintf(tmp, " (%8.3f)", *(float*)&ScriptParams[i]); strcat(commandInfo, tmp); break; + default: script_assert(0 && "Script only returns INTs and FLOATs"); + } + } + m_nIp = storedIp; + } + PrintToLog("%s\n", commandInfo); + if (m_bMissionFlag) { + for (int i = 0; commandInfo[i]; i++) { + if (commandInfo[i] == '_') + commandInfo[i] = ' '; + } + CDebug::DebugAddText(commandInfo); + } + } +} + +#endif + +void FlushLog() +{ +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#if SCRIPT_LOG_FILE_LEVEL == 1 || SCRIPT_LOG_FILE_LEVEL == 2 + if (dbg_log) + fflush(dbg_log); +#endif +#endif +} + + +#ifdef MISSION_SWITCHER +void +CTheScripts::SwitchToMission(int32 mission) +{ + for (CRunningScript* pScript = CTheScripts::pActiveScripts; pScript != nil; pScript = pScript->GetNext()) { + if (!pScript->m_bIsMissionScript || !pScript->m_bDeatharrestEnabled) { + continue; + } + while (pScript->m_nStackPointer > 0) + --pScript->m_nStackPointer; + + pScript->m_nIp = pScript->m_anStack[pScript->m_nStackPointer]; + *(int32*)&CTheScripts::ScriptSpace[CTheScripts::OnAMissionFlag] = 0; + pScript->m_nWakeTime = 0; + pScript->m_bDeatharrestExecuted = true; + + while (!pScript->ProcessOneCommand()); + + CMessages::ClearMessages(); + } + +#ifdef MISSION_REPLAY + missionRetryScriptIndex = mission; + if (missionRetryScriptIndex == 19) + CStats::LastMissionPassedName[0] = '\0'; +#endif + CTimer::Suspend(); + int offset = CTheScripts::MultiScriptArray[mission]; +#ifdef USE_DEBUG_SCRIPT_LOADER + int handle = OpenScript(); +#else + CFileMgr::ChangeDir("\\"); + int handle = CFileMgr::OpenFile("data\\main.scm", "rb"); +#endif + CFileMgr::Seek(handle, offset, 0); + CFileMgr::Read(handle, (const char*)&CTheScripts::ScriptSpace[SIZE_MAIN_SCRIPT], SIZE_MISSION_SCRIPT); + CFileMgr::CloseFile(handle); + CRunningScript* pMissionScript = CTheScripts::StartNewScript(SIZE_MAIN_SCRIPT); + CTimer::Resume(); + pMissionScript->m_bIsMissionScript = true; + pMissionScript->m_bMissionFlag = true; + CTheScripts::bAlreadyRunningAMissionScript = true; +} +#endif diff --git a/src/control/TrafficLights.cpp b/src/control/TrafficLights.cpp new file mode 100644 index 0000000..278366a --- /dev/null +++ b/src/control/TrafficLights.cpp @@ -0,0 +1,330 @@ +#include "common.h" + +#include "Camera.h" +#include "Clock.h" +#include "Coronas.h" +#include "General.h" +#include "PathFind.h" +#include "PointLights.h" +#include "Shadows.h" +#include "SpecialFX.h" +#include "Timecycle.h" +#include "Timer.h" +#include "TrafficLights.h" +#include "Vehicle.h" +#include "Weather.h" +#include "World.h" + +// TODO: figure out the meaning of this +enum { SOME_FLAG = 0x80 }; + +void +CTrafficLights::DisplayActualLight(CEntity *ent) +{ + if(ent->GetUp().z < 0.96f || ent->bRenderDamaged) + return; + + int phase; + if(FindTrafficLightType(ent) == 1) + phase = LightForCars1(); + else + phase = LightForCars2(); + + int i; + CBaseModelInfo *mi = CModelInfo::GetModelInfo(ent->GetModelIndex()); + float x = mi->Get2dEffect(0)->pos.x; + float yMin = mi->Get2dEffect(0)->pos.y; + float yMax = mi->Get2dEffect(0)->pos.y; + float zMin = mi->Get2dEffect(0)->pos.z; + float zMax = mi->Get2dEffect(0)->pos.z; + for(i = 1; i < 6; i++){ + assert(mi->Get2dEffect(i)); + yMin = Min(yMin, mi->Get2dEffect(i)->pos.y); + yMax = Max(yMax, mi->Get2dEffect(i)->pos.y); + zMin = Min(zMin, mi->Get2dEffect(i)->pos.z); + zMax = Max(zMax, mi->Get2dEffect(i)->pos.z); + } + + CVector pos1, pos2; + uint8 r, g; + int id; + switch(phase){ + case CAR_LIGHTS_GREEN: + r = 0; + g = 255; + pos1 = ent->GetMatrix() * CVector(x, yMax, zMin); + pos2 = ent->GetMatrix() * CVector(x, yMin, zMin); + id = 0; + break; + case CAR_LIGHTS_YELLOW: + r = 255; + g = 128; + pos1 = ent->GetMatrix() * CVector(x, yMax, (zMin+zMax)/2.0f); + pos2 = ent->GetMatrix() * CVector(x, yMin, (zMin+zMax)/2.0f); + id = 1; + break; + case CAR_LIGHTS_RED: + default: + r = 255; + g = 0; + pos1 = ent->GetMatrix() * CVector(x, yMax, zMax); + pos2 = ent->GetMatrix() * CVector(x, yMin, zMax); + id = 2; + break; + } + + if(CClock::GetHours() > 19 || CClock::GetHours() < 6 || CWeather::Foggyness > 0.05f) + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos1, CVector(0.0f, 0.0f, 0.0f), 8.0f, + r/255.0f, g/255.0f, 0/255.0f, CPointLights::FOG_NORMAL, true); + + CShadows::StoreStaticShadow((uintptr)ent, + SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos1, + 8.0f, 0.0f, 0.0f, -8.0f, 128, + r*CTimeCycle::GetLightOnGroundBrightness()/8.0f, + g*CTimeCycle::GetLightOnGroundBrightness()/8.0f, + 0*CTimeCycle::GetLightOnGroundBrightness()/8.0f, + 12.0f, 1.0f, 40.0f, false, 0.0f); + + if(DotProduct(TheCamera.GetForward(), ent->GetForward()) < 0.0f) + CCoronas::RegisterCorona((uintptr)ent + id, + r*CTimeCycle::GetSpriteBrightness()*0.7f, + g*CTimeCycle::GetSpriteBrightness()*0.7f, + 0*CTimeCycle::GetSpriteBrightness()*0.7f, + 255, + pos1, 1.75f*CTimeCycle::GetSpriteSize(), 50.0f, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::RegisterCorona((uintptr)ent + id + 3, + r*CTimeCycle::GetSpriteBrightness()*0.7f, + g*CTimeCycle::GetSpriteBrightness()*0.7f, + 0*CTimeCycle::GetSpriteBrightness()*0.7f, + 255, + pos2, 1.75f*CTimeCycle::GetSpriteSize(), 50.0f, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + + CBrightLights::RegisterOne(pos1, ent->GetUp(), ent->GetRight(), CVector(0.0f, 0.0f, 0.0f), id + BRIGHTLIGHT_TRAFFIC_GREEN); + CBrightLights::RegisterOne(pos2, ent->GetUp(), -ent->GetRight(), CVector(0.0f, 0.0f, 0.0f), id + BRIGHTLIGHT_TRAFFIC_GREEN); + + static const float top = -0.127f; + static const float bot = -0.539f; + static const float mid = bot + (top-bot)/3.0f; + static const float left = 1.256f; + static const float right = 0.706f; + phase = CTrafficLights::LightForPeds(); + if(phase == PED_LIGHTS_DONT_WALK){ + CVector p0(2.7f, right, top); + CVector p1(2.7f, left, top); + CVector p2(2.7f, right, mid); + CVector p3(2.7f, left, mid); + CShinyTexts::RegisterOne(ent->GetMatrix()*p0, ent->GetMatrix()*p1, ent->GetMatrix()*p2, ent->GetMatrix()*p3, + 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, + SHINYTEXT_WALK, 255, 0, 0, 60.0f); + }else if(phase == PED_LIGHTS_WALK || CTimer::GetTimeInMilliseconds() & 0x100){ + CVector p0(2.7f, right, mid); + CVector p1(2.7f, left, mid); + CVector p2(2.7f, right, bot); + CVector p3(2.7f, left, bot); + CShinyTexts::RegisterOne(ent->GetMatrix()*p0, ent->GetMatrix()*p1, ent->GetMatrix()*p2, ent->GetMatrix()*p3, + 1.0f, 0.5f, 0.0f, 0.5f, 1.0f, 1.0f, 0.0f, 1.0f, + SHINYTEXT_WALK, 255, 255, 255, 60.0f); + } +} + +void +CTrafficLights::ScanForLightsOnMap(void) +{ + int x, y; + int i, j, k, l; + CPtrNode *node; + + for(x = 0; x < NUMSECTORS_X; x++) + for(y = 0; y < NUMSECTORS_Y; y++){ + CPtrList &list = CWorld::GetSector(x, y)->m_lists[ENTITYLIST_DUMMIES]; + for(node = list.first; node; node = node->next){ + CEntity *light = (CEntity*)node->item; + if(light->GetModelIndex() != MI_TRAFFICLIGHTS) + continue; + + // Check cars + for(i = 0; i < ThePaths.m_numCarPathLinks; i++){ + CVector2D dist = ThePaths.m_carPathLinks[i].GetPosition() - light->GetPosition(); + float dotY = Abs(DotProduct2D(dist, light->GetForward())); // forward is direction of car light + float dotX = DotProduct2D(dist, light->GetRight()); // towards base of light + // it has to be on the correct side of the node and also not very far away + if(dotX < 0.0f && dotX > -15.0f && dotY < 3.0f){ + float dz = ThePaths.m_pathNodes[ThePaths.m_carPathLinks[i].pathNodeIndex].GetZ() - + light->GetPosition().z; + if(dz < 15.0f){ + ThePaths.m_carPathLinks[i].trafficLightType = FindTrafficLightType(light); + // Find two neighbour nodes of this one + int n1 = -1; + int n2 = -1; + for(j = 0; j < ThePaths.m_numPathNodes; j++) + for(l = 0; l < ThePaths.m_pathNodes[j].numLinks; l++) + if(ThePaths.m_carPathConnections[ThePaths.m_pathNodes[j].firstLink + l] == i){ + if(n1 == -1) + n1 = j; + else + n2 = j; + } + // What's going on here? + if(ThePaths.m_pathNodes[n1].numLinks <= ThePaths.m_pathNodes[n2].numLinks) + n1 = n2; + if(ThePaths.m_carPathLinks[i].pathNodeIndex != n1) + ThePaths.m_carPathLinks[i].trafficLightType |= SOME_FLAG; + } + } + } + + // Check peds + for(i = ThePaths.m_numCarPathNodes; i < ThePaths.m_numPathNodes; i++){ + float dist1, dist2; + dist1 = Abs(ThePaths.m_pathNodes[i].GetX() - light->GetPosition().x) + + Abs(ThePaths.m_pathNodes[i].GetY() - light->GetPosition().y); + if(dist1 < 50.0f){ + for(l = 0; l < ThePaths.m_pathNodes[i].numLinks; l++){ + j = ThePaths.m_pathNodes[i].firstLink + l; + if(ThePaths.ConnectionCrossesRoad(j)){ + k = ThePaths.ConnectedNode(j); + dist2 = Abs(ThePaths.m_pathNodes[k].GetX() - light->GetPosition().x) + + Abs(ThePaths.m_pathNodes[k].GetY() - light->GetPosition().y); + if(dist1 < 15.0f || dist2 < 15.0f) + ThePaths.ConnectionSetTrafficLight(j); + } + } + } + } + } + } +} + +bool +CTrafficLights::ShouldCarStopForLight(CVehicle *vehicle, bool alwaysStop) +{ + int node, type; + + node = vehicle->AutoPilot.m_nNextPathNodeInfo; + type = ThePaths.m_carPathLinks[node].trafficLightType; + if(type){ + if((type & SOME_FLAG || ThePaths.m_carPathLinks[node].pathNodeIndex == vehicle->AutoPilot.m_nNextRouteNode) && + (!(type & SOME_FLAG) || ThePaths.m_carPathLinks[node].pathNodeIndex != vehicle->AutoPilot.m_nNextRouteNode)) + if(alwaysStop || + (type&~SOME_FLAG) == 1 && LightForCars1() != CAR_LIGHTS_GREEN || + (type&~SOME_FLAG) == 2 && LightForCars2() != CAR_LIGHTS_GREEN){ + float dist = DotProduct2D(CVector2D(vehicle->GetPosition()) - ThePaths.m_carPathLinks[node].GetPosition(), + ThePaths.m_carPathLinks[node].GetDirection()); + if(vehicle->AutoPilot.m_nNextDirection == -1){ + if(dist > 0.0f && dist < 8.0f) + return true; + }else{ + if(dist < 0.0f && dist > -8.0f) + return true; + } + } + } + + node = vehicle->AutoPilot.m_nCurrentPathNodeInfo; + type = ThePaths.m_carPathLinks[node].trafficLightType; + if(type){ + if((type & SOME_FLAG || ThePaths.m_carPathLinks[node].pathNodeIndex == vehicle->AutoPilot.m_nCurrentRouteNode) && + (!(type & SOME_FLAG) || ThePaths.m_carPathLinks[node].pathNodeIndex != vehicle->AutoPilot.m_nCurrentRouteNode)) + if(alwaysStop || + (type&~SOME_FLAG) == 1 && LightForCars1() != CAR_LIGHTS_GREEN || + (type&~SOME_FLAG) == 2 && LightForCars2() != CAR_LIGHTS_GREEN){ + float dist = DotProduct2D(CVector2D(vehicle->GetPosition()) - ThePaths.m_carPathLinks[node].GetPosition(), + ThePaths.m_carPathLinks[node].GetDirection()); + if(vehicle->AutoPilot.m_nCurrentDirection == -1){ + if(dist > 0.0f && dist < 8.0f) + return true; + }else{ + if(dist < 0.0f && dist > -8.0f) + return true; + } + } + } + + if(vehicle->GetStatus() == STATUS_PHYSICS){ + node = vehicle->AutoPilot.m_nPreviousPathNodeInfo; + type = ThePaths.m_carPathLinks[node].trafficLightType; + if(type){ + if((type & SOME_FLAG || ThePaths.m_carPathLinks[node].pathNodeIndex == vehicle->AutoPilot.m_nPrevRouteNode) && + (!(type & SOME_FLAG) || ThePaths.m_carPathLinks[node].pathNodeIndex != vehicle->AutoPilot.m_nPrevRouteNode)) + if(alwaysStop || + (type&~SOME_FLAG) == 1 && LightForCars1() != CAR_LIGHTS_GREEN || + (type&~SOME_FLAG) == 2 && LightForCars2() != CAR_LIGHTS_GREEN){ + float dist = DotProduct2D(CVector2D(vehicle->GetPosition()) - ThePaths.m_carPathLinks[node].GetPosition(), + ThePaths.m_carPathLinks[node].GetDirection()); + if(vehicle->AutoPilot.m_nPreviousDirection == -1){ + if(dist > 0.0f && dist < 6.0f) + return true; + }else{ + if(dist < 0.0f && dist > -6.0f) + return true; + } + } + } + } + + return false; +} + +bool +CTrafficLights::ShouldCarStopForBridge(CVehicle *vehicle) +{ + return ThePaths.m_carPathLinks[vehicle->AutoPilot.m_nNextPathNodeInfo].bBridgeLights && + !ThePaths.m_carPathLinks[vehicle->AutoPilot.m_nCurrentPathNodeInfo].bBridgeLights; +} + +int +CTrafficLights::FindTrafficLightType(CEntity *light) +{ + float orientation = RADTODEG(CGeneral::GetATanOfXY(light->GetForward().x, light->GetForward().y)); + if((orientation > 60.0f && orientation < 60.0f + 90.0f) || + (orientation > 240.0f && orientation < 240.0f + 90.0f)) + return 1; + return 2; +} + +uint8 +CTrafficLights::LightForPeds(void) +{ + uint32 period = CTimer::GetTimeInMilliseconds() % 16384; + + if(period < 12000) + return PED_LIGHTS_DONT_WALK; + else if(period < 16384 - 1000) + return PED_LIGHTS_WALK; + else + return PED_LIGHTS_WALK_BLINK; +} + +uint8 +CTrafficLights::LightForCars1(void) +{ + uint32 period = CTimer::GetTimeInMilliseconds() % 16384; + + if(period < 5000) + return CAR_LIGHTS_GREEN; + else if(period < 5000 + 1000) + return CAR_LIGHTS_YELLOW; + else + return CAR_LIGHTS_RED; +} + +uint8 +CTrafficLights::LightForCars2(void) +{ + uint32 period = CTimer::GetTimeInMilliseconds() % 16384; + + if(period < 6000) + return CAR_LIGHTS_RED; + else if(period < 12000 - 1000) + return CAR_LIGHTS_GREEN; + else if(period < 12000) + return CAR_LIGHTS_YELLOW; + else + return CAR_LIGHTS_RED; +} diff --git a/src/control/TrafficLights.h b/src/control/TrafficLights.h new file mode 100644 index 0000000..f3df6cd --- /dev/null +++ b/src/control/TrafficLights.h @@ -0,0 +1,27 @@ +#pragma once + +class CEntity; +class CVehicle; + +enum { + PED_LIGHTS_WALK, + PED_LIGHTS_WALK_BLINK, + PED_LIGHTS_DONT_WALK, + + CAR_LIGHTS_GREEN = 0, + CAR_LIGHTS_YELLOW, + CAR_LIGHTS_RED +}; + +class CTrafficLights +{ +public: + static void DisplayActualLight(CEntity *ent); + static void ScanForLightsOnMap(void); + static int FindTrafficLightType(CEntity *light); + static uint8 LightForPeds(void); + static uint8 LightForCars1(void); + static uint8 LightForCars2(void); + static bool ShouldCarStopForLight(CVehicle*, bool); + static bool ShouldCarStopForBridge(CVehicle*); +}; diff --git a/src/core/Accident.cpp b/src/core/Accident.cpp new file mode 100644 index 0000000..c861132 --- /dev/null +++ b/src/core/Accident.cpp @@ -0,0 +1,128 @@ +#include "common.h" + +#include "Accident.h" + +#include "Ped.h" +#include "Pools.h" +#include "World.h" + +CAccidentManager gAccidentManager; + +CAccident* +CAccidentManager::GetNextFreeAccident() +{ + for (int i = 0; i < NUM_ACCIDENTS; i++) { + if (m_aAccidents[i].m_pVictim == nil) + return &m_aAccidents[i]; + } + + return nil; +} + +void +CAccidentManager::ReportAccident(CPed *ped) +{ + if (!ped->IsPlayer() && ped->CharCreatedBy != MISSION_CHAR && !ped->bRenderScorched && !ped->bBodyPartJustCameOff && ped->bAllowMedicsToReviveMe && !ped->bIsInWater) { + for (int i = 0; i < NUM_ACCIDENTS; i++) { + if (m_aAccidents[i].m_pVictim != nil && m_aAccidents[i].m_pVictim == ped) + return; + } + + if (ped->m_pCurrentPhysSurface == nil) { + CVector point = ped->GetPosition(); + point.z -= 2.0f; + + CColPoint colPoint; + CEntity *pEntity; + + if (!CWorld::ProcessVerticalLine(point, -100.0f, colPoint, pEntity, true, false, false, false, false, false, nil)) { + CAccident *accident = GetNextFreeAccident(); + if (accident != nil) { + accident->m_pVictim = ped; + ped->RegisterReference((CEntity**)&accident->m_pVictim); + accident->m_nMedicsPerformingCPR = 0; + accident->m_nMedicsAttending = 0; + ped->m_lastAccident = accident; + WorkToDoForMedics(); + } + } + } + } +} + +void +CAccidentManager::Update() +{ +#ifdef SQUEEZE_PERFORMANCE + // Handled after injury registered. + return; +#endif + int32 e; + if (CEventList::GetEvent(EVENT_INJURED_PED, &e)) { + CPed *ped = CPools::GetPed(gaEvent[e].entityRef); + if (ped) { + ReportAccident(ped); + CEventList::ClearEvent(e); + } + } +} + +CAccident* +CAccidentManager::FindNearestAccident(CVector vecPos, float *pDistance) +{ + for (int i = 0; i < MAX_MEDICS_TO_ATTEND_ACCIDENT; i++){ + int accidentId = -1; + float minDistance = 999999; + for (int j = 0; j < NUM_ACCIDENTS; j++){ + CPed* pVictim = m_aAccidents[j].m_pVictim; + if (!pVictim) + continue; + if (pVictim->CharCreatedBy == MISSION_CHAR) + continue; + if (pVictim->m_fHealth != 0.0f) + continue; + if (m_aAccidents[j].m_nMedicsPerformingCPR != i) + continue; + float distance = (pVictim->GetPosition() - vecPos).Magnitude2D(); + if (distance / 2 > pVictim->GetPosition().z - vecPos.z && distance < minDistance){ + minDistance = distance; + accidentId = j; + } + } + *pDistance = minDistance; + if (accidentId != -1) + return &m_aAccidents[accidentId]; + } + return nil; +} + +uint16 +CAccidentManager::CountActiveAccidents() +{ + uint16 accidents = 0; + for (int i = 0; i < NUM_ACCIDENTS; i++) { + if (m_aAccidents[i].m_pVictim) + accidents++; + } + return accidents; +} + +bool +CAccidentManager::WorkToDoForMedics() +{ + for (int i = 0; i < NUM_ACCIDENTS; i++) { + if (m_aAccidents[i].m_pVictim != nil && m_aAccidents[i].m_nMedicsAttending < MAX_MEDICS_TO_ATTEND_ACCIDENT) + return true; + } + return false; +} + +bool +CAccidentManager::UnattendedAccidents() +{ + for (int i = 0; i < NUM_ACCIDENTS; i++) { + if (m_aAccidents[i].m_pVictim != nil && m_aAccidents[i].m_nMedicsAttending == 0) + return true; + } + return false; +} diff --git a/src/core/Accident.h b/src/core/Accident.h new file mode 100644 index 0000000..568e114 --- /dev/null +++ b/src/core/Accident.h @@ -0,0 +1,31 @@ +#pragma once +#include "config.h" + +class CPed; + +class CAccident +{ +public: + CPed *m_pVictim; + uint32 m_nMedicsAttending; + uint32 m_nMedicsPerformingCPR; + CAccident() : m_pVictim(nil), m_nMedicsAttending(0), m_nMedicsPerformingCPR(0) {} +}; + +class CAccidentManager +{ + CAccident m_aAccidents[NUM_ACCIDENTS]; + enum { + MAX_MEDICS_TO_ATTEND_ACCIDENT = 2 + }; +public: + CAccident *GetNextFreeAccident(); + void ReportAccident(CPed *ped); + void Update(); + CAccident *FindNearestAccident(CVector vecPos, float *pDistance); + uint16 CountActiveAccidents(); + bool UnattendedAccidents(); + bool WorkToDoForMedics(); +}; + +extern CAccidentManager gAccidentManager; \ No newline at end of file diff --git a/src/core/AnimViewer.cpp b/src/core/AnimViewer.cpp new file mode 100644 index 0000000..946693a --- /dev/null +++ b/src/core/AnimViewer.cpp @@ -0,0 +1,422 @@ +#include "common.h" + +#include "Font.h" +#include "Pad.h" +#include "Text.h" +#include "main.h" +#include "Timer.h" +#include "DMAudio.h" +#include "FileMgr.h" +#include "Streaming.h" +#include "TxdStore.h" +#include "General.h" +#include "Camera.h" +#include "Vehicle.h" +#include "PlayerSkin.h" +#include "PlayerInfo.h" +#include "World.h" +#include "Renderer.h" +#include "AnimManager.h" +#include "AnimViewer.h" +#include "PlayerPed.h" +#include "Pools.h" +#include "References.h" +#include "PathFind.h" +#include "HandlingMgr.h" +#include "TempColModels.h" +#include "Particle.h" +#include "CdStream.h" +#include "Messages.h" +#include "CarCtrl.h" +#include "FileLoader.h" +#include "ModelIndices.h" +#include "Clock.h" +#include "Timecycle.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "Shadows.h" +#include "Radar.h" +#include "Hud.h" +#include "debugmenu.h" + +int CAnimViewer::animTxdSlot = 0; +CEntity *CAnimViewer::pTarget = nil; + +void +CAnimViewer::Render(void) { + if (pTarget) { +// pTarget->GetPosition() = CVector(0.0f, 0.0f, 0.0f); // Only on Mobile + if (pTarget) { +#ifdef FIX_BUGS +#ifdef PED_SKIN + if(pTarget->IsPed() && IsClumpSkinned(pTarget->GetClump())) + ((CPed*)pTarget)->UpdateRpHAnim(); +#endif +#endif + pTarget->Render(); + CRenderer::RenderOneNonRoad(pTarget); + } + } +} + +void +CAnimViewer::Initialise(void) { + // we need messages, messages needs hud, hud needs this + CHud::m_Wants_To_Draw_Hud = false; + + animTxdSlot = CTxdStore::AddTxdSlot("generic"); + CTxdStore::Create(animTxdSlot); + int hudSlot = CTxdStore::AddTxdSlot("hud"); + CTxdStore::LoadTxd(hudSlot, "MODELS/HUD.TXD"); + int particleSlot = CTxdStore::AddTxdSlot("particle"); + CTxdStore::LoadTxd(particleSlot, "MODELS/PARTICLE.TXD"); + CTxdStore::SetCurrentTxd(animTxdSlot); + CPools::Initialise(); + CReferences::Init(); + TheCamera.Init(); + TheCamera.SetRwCamera(Scene.camera); + TheCamera.Cams[TheCamera.ActiveCam].Distance = 5.0f; + + ThePaths.Init(); + ThePaths.AllocatePathFindInfoMem(4500); + CCollision::Init(); + CWorld::Initialise(); + mod_HandlingManager.Initialise(); + CTempColModels::Initialise(); + CAnimManager::Initialise(); + CModelInfo::Initialise(); + CParticle::Initialise(); + CCarCtrl::Init(); + CPedStats::Initialise(); + CMessages::Init(); + CdStreamAddImage("MODELS\\GTA3.IMG"); + CFileLoader::LoadLevel("DATA\\ANIMVIEWER.DAT"); + CStreaming::Init(); + CStreaming::LoadInitialPeds(); + CStreaming::RequestSpecialModel(MI_PLAYER, "player", STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + CRenderer::Init(); + CRadar::Initialise(); + CRadar::LoadTextures(); + CVehicleModelInfo::LoadVehicleColours(); +#ifdef FIX_BUGS + CVehicleModelInfo::LoadEnvironmentMaps(); +#endif + CAnimManager::LoadAnimFiles(); + CWorld::PlayerInFocus = 0; + CWeapon::InitialiseWeapons(); + CShadows::Init(); + CPed::Initialise(); + CTimer::Initialise(); + CClock::Initialise(60000); + CTimeCycle::Initialise(); + CCarCtrl::Init(); + CPlayerPed *player = new CPlayerPed(); + player->SetPosition(0.0f, 0.0f, 0.0f); // This is 1000.f for all axes on Xbox, but 0.f on mobile? + CWorld::Players[0].m_pPed = player; + CDraw::SetFOV(120.0f); + CDraw::ms_fLODDistance = 500.0f; + + int fd = CFileMgr::OpenFile("DATA\\SPECIAL.TXT", "r"); + char animGroup[32], modelName[32]; + if (fd) { + for (int lineId = 0; lineId < NUM_OF_SPECIAL_CHARS; lineId++) { + if (!CFileMgr::ReadLine(fd, gString, 255)) + break; + + sscanf(gString, "%s %s", modelName, animGroup); + int groupId; + for (groupId = 0; groupId < NUM_ANIM_ASSOC_GROUPS; groupId++) { + if (!strcmp(animGroup, CAnimManager::GetAnimGroupName((AssocGroupId)groupId))) + break; + } + + if (groupId != NUM_ANIM_ASSOC_GROUPS) + ((CPedModelInfo*)CModelInfo::GetModelInfo(MI_SPECIAL01 + lineId))->m_animGroup = groupId; + + CStreaming::RequestSpecialChar(lineId, modelName, STREAMFLAGS_DONT_REMOVE); + } + CFileMgr::CloseFile(fd); + } else { + // From xbox + CStreaming::RequestSpecialChar(0, "luigi", STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestSpecialChar(1, "joey", STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestSpecialChar(2, "tony", STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestSpecialChar(3, "curly", STREAMFLAGS_DONT_REMOVE); + } +} + +int +LastPedModelId(int modelId) +{ + CBaseModelInfo *model; + for(;;){ + assert(modelId < MODELINFOSIZE); + model = CModelInfo::GetModelInfo(modelId); + if (model && model->GetModelType() == MITYPE_PED) + break; + modelId--; + } + return modelId; +} + +int +FirstCarModelId(int modelId) +{ + CBaseModelInfo *model; + for(;;){ + assert(modelId < MODELINFOSIZE); + model = CModelInfo::GetModelInfo(modelId); + if (model && model->GetModelType() == MITYPE_VEHICLE) + break; + modelId++; + } + return modelId; +} + + +int +NextModelId(int modelId, int wantedChange) +{ + // Max. 2 trials wasn't here, it's me that added it. + + int tryCount = 2; + int ogModelId = modelId; + + while(tryCount != 0) { + modelId += wantedChange; + if (modelId < 0 || modelId >= MODELINFOSIZE) { + tryCount--; + wantedChange = -wantedChange; + } else if (modelId != 5 && modelId != 6 && modelId != 405) { + CBaseModelInfo *model = CModelInfo::GetModelInfo(modelId); + if (model) + { + //int type = model->m_type; + return modelId; + } + } + } + return ogModelId; +} + +void +PlayAnimation(RpClump *clump, AssocGroupId animGroup, AnimationId anim) +{ + CAnimBlendAssociation *currentAssoc = RpAnimBlendClumpGetAssociation(clump, anim); + + if (currentAssoc && currentAssoc->IsPartial()) + delete currentAssoc; + + RpAnimBlendClumpSetBlendDeltas(clump, ASSOC_PARTIAL, -8.0f); + + CAnimBlendAssociation *animAssoc = CAnimManager::BlendAnimation(clump, animGroup, anim, 8.0f); + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->SetCurrentTime(0.0f); + animAssoc->SetRun(); +} + +void +CAnimViewer::Update(void) +{ + static int modelId = 0; + static int animId = 0; + static bool reloadIFP = false; + + AssocGroupId animGroup = ASSOCGRP_STD; + int nextModelId = modelId; + CBaseModelInfo *modelInfo = CModelInfo::GetModelInfo(modelId); + + if (modelInfo->GetModelType() == MITYPE_PED) { + int animGroup = ((CPedModelInfo*)modelInfo)->m_animGroup; + + if (animId > ANIM_STD_IDLE) + animGroup = ASSOCGRP_STD; + + if (reloadIFP) { + if (pTarget) { + CWorld::Remove(pTarget); + if (pTarget) + delete pTarget; + } + pTarget = nil; + + // These calls were inside of LoadIFP function. + CAnimManager::Shutdown(); + CAnimManager::Initialise(); + CAnimManager::LoadAnimFiles(); + + reloadIFP = false; + } + } else { + animGroup = ASSOCGRP_STD; + } + CPad::UpdatePads(); + CPad* pad = CPad::GetPad(0); +#ifdef DEBUGMENU + DebugMenuProcess(); +#endif + + CStreaming::UpdateForAnimViewer(); + CStreaming::RequestModel(modelId, 0); + if (CStreaming::HasModelLoaded(modelId)) { + + if (!pTarget) { + + if (modelInfo->GetModelType() == MITYPE_VEHICLE) { + + CVehicleModelInfo* veh = (CVehicleModelInfo*)modelInfo; + if (veh->m_vehicleType == VEHICLE_TYPE_CAR) { + pTarget = new CAutomobile(modelId, RANDOM_VEHICLE); + } else if (veh->m_vehicleType == VEHICLE_TYPE_BOAT) { + pTarget = new CBoat(modelId, RANDOM_VEHICLE); + } else { + pTarget = new CObject(modelId, true); + if (!modelInfo->GetColModel()) { + modelInfo->SetColModel(&CTempColModels::ms_colModelWheel1); + } + } + pTarget->SetStatus(STATUS_ABANDONED); + } else if (modelInfo->GetModelType() == MITYPE_PED) { + pTarget = new CPed(PEDTYPE_CIVMALE); + pTarget->SetModelIndex(modelId); + } else { + pTarget = new CObject(modelId, true); + if (!modelInfo->GetColModel()) + { + modelInfo->SetColModel(&CTempColModels::ms_colModelWheel1); + } + pTarget->SetStatus(STATUS_ABANDONED); + } + pTarget->SetPosition(0.0f, 0.0f, 0.0f); + CWorld::Add(pTarget); + TheCamera.TakeControl(pTarget, CCam::MODE_MODELVIEW, JUMP_CUT, CAMCONTROL_SCRIPT); + } + if (pTarget->IsVehicle() || pTarget->IsPed() || pTarget->IsObject()) { + ((CPhysical*)pTarget)->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } +#ifdef FIX_BUGS + // so we don't end up in the water + pTarget->GetMatrix().GetPosition().z = 10.0f; +#else + pTarget->GetMatrix().GetPosition().z = 0.0f; + +#endif + + if (modelInfo->GetModelType() == MITYPE_PED) { + ((CPed*)pTarget)->bKindaStayInSamePlace = true; + + // Triangle in mobile + if (pad->GetSquareJustDown()) { + reloadIFP = true; + AsciiToUnicode("IFP reloaded", gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetCrossJustDown()) { + PlayAnimation(pTarget->GetClump(), animGroup, (AnimationId)animId); + AsciiToUnicode("Animation restarted", gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetCircleJustDown()) { + PlayAnimation(pTarget->GetClump(), animGroup, ANIM_STD_IDLE); + AsciiToUnicode("Idle animation playing", gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetDPadUpJustDown()) { + animId--; + if (animId < 0) { + animId = ANIM_STD_NUM - 1; + } + PlayAnimation(pTarget->GetClump(), animGroup, (AnimationId)animId); + + sprintf(gString, "Current anim: %d", animId); + AsciiToUnicode(gString, gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetDPadDownJustDown()) { + animId = (animId == (ANIM_STD_NUM - 1) ? 0 : animId + 1); + PlayAnimation(pTarget->GetClump(), animGroup, (AnimationId)animId); + + sprintf(gString, "Current anim: %d", animId); + AsciiToUnicode(gString, gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetStartJustDown()) { + + } else if (pad->GetLeftShoulder1JustDown()) { + nextModelId = FirstCarModelId(modelId); + AsciiToUnicode("Switched to vehicles", gUString); + CMessages::AddMessage(gUString, 1000, 0); + // Originally it was GetPad(1)->LeftShoulder2 + } else if (pad->NewState.Triangle) { +#ifdef PED_SKIN + if(IsClumpSkinned(pTarget->GetClump())) + ((CPedModelInfo *)CModelInfo::GetModelInfo(pTarget->GetModelIndex()))->AnimatePedColModelSkinned(pTarget->GetClump()); + else +#endif + CPedModelInfo::AnimatePedColModel(((CPedModelInfo *)CModelInfo::GetModelInfo(pTarget->GetModelIndex()))->GetHitColModel(), + RpClumpGetFrame(pTarget->GetClump())); + AsciiToUnicode("Ped Col model will be animated as long as you hold the button", gUString); + CMessages::AddMessage(gUString, 100, 0); + } + } else if (modelInfo->GetModelType() == MITYPE_VEHICLE) { + + if (pad->GetLeftShoulder1JustDown()) { + nextModelId = LastPedModelId(modelId); + AsciiToUnicode("Switched to peds", gUString); + CMessages::AddMessage(gUString, 1000, 0); + // Start in mobile + } else if (pad->GetSquareJustDown()) { + CVehicleModelInfo::LoadVehicleColours(); + AsciiToUnicode("Carcols.dat reloaded", gUString); + CMessages::AddMessage(gUString, 1000, 0); + } + } + } + + if (pad->GetDPadLeftJustDown()) { + nextModelId = NextModelId(modelId, -1); + + sprintf(gString, "Current model ID: %d", nextModelId); + AsciiToUnicode(gString, gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetDPadRightJustDown()) { + nextModelId = NextModelId(modelId, 1); + + sprintf(gString, "Current model ID: %d", nextModelId); + AsciiToUnicode(gString, gUString); + CMessages::AddMessage(gUString, 1000, 0); + } + // There were extra codes here to let us change model id by 50, but xbox CPad struct is different, so I couldn't port. + + if (nextModelId != modelId) { + modelId = nextModelId; + if (pTarget) { + CWorld::Remove(pTarget); + if (pTarget) + delete pTarget; + } + pTarget = nil; + return; + } + + CTimeCycle::Update(); + CWorld::Process(); + if (pTarget) + TheCamera.Process(); +} + +void +CAnimViewer::Shutdown(void) +{ + if (CWorld::Players[0].m_pPed) + delete CWorld::Players[0].m_pPed; + + CWorld::ShutDown(); + CModelInfo::ShutDown(); + CAnimManager::Shutdown(); + CTimer::Shutdown(); + CStreaming::Shutdown(); + CTxdStore::RemoveTxdSlot(animTxdSlot); +} diff --git a/src/core/AnimViewer.h b/src/core/AnimViewer.h new file mode 100644 index 0000000..13dbb8f --- /dev/null +++ b/src/core/AnimViewer.h @@ -0,0 +1,12 @@ +#pragma once + +class CAnimViewer { +public: + static int animTxdSlot; + static CEntity *pTarget; + + static void Initialise(); + static void Render(); + static void Shutdown(); + static void Update(); +}; \ No newline at end of file diff --git a/src/core/Cam.cpp b/src/core/Cam.cpp new file mode 100644 index 0000000..d4be858 --- /dev/null +++ b/src/core/Cam.cpp @@ -0,0 +1,5410 @@ +#include "common.h" + +#include "main.h" +#include "Draw.h" +#include "World.h" +#include "Vehicle.h" +#include "Automobile.h" +#include "Boat.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "CopPed.h" +#include "RpAnimBlend.h" +#include "ControllerConfig.h" +#include "Pad.h" +#include "Frontend.h" +#include "General.h" +#include "Renderer.h" +#include "Shadows.h" +#include "Hud.h" +#include "ZoneCull.h" +#include "SurfaceTable.h" +#include "WaterLevel.h" +#include "MBlur.h" +#include "SceneEdit.h" +#include "Debug.h" +#include "Camera.h" +#include "DMAudio.h" + +bool PrintDebugCode = false; +int16 DebugCamMode; + +#ifdef FREE_CAM +bool CCamera::bFreeCam = false; +int nPreviousMode = -1; +#endif + +void +CCam::Init(void) +{ + Mode = MODE_FOLLOWPED; + Front = CVector(0.0f, 0.0f, -1.0f); + Up = CVector(0.0f, 0.0f, 1.0f); + Rotating = false; + m_iDoCollisionChecksOnFrameNum = 1; + m_iDoCollisionCheckEveryNumOfFrames = 9; + m_iFrameNumWereAt = 0; + m_bCollisionChecksOn = false; + m_fRealGroundDist = 0.0f; + BetaSpeed = 0.0f; + AlphaSpeed = 0.0f; + DistanceSpeed = 0.0f; + f_max_role_angle = DEGTORAD(5.0f); + Distance = 30.0f; + DistanceSpeed = 0.0f; + m_pLastCarEntered = nil; + m_pLastPedLookedAt = nil; + ResetStatics = true; + Beta = 0.0f; + m_bFixingBeta = false; + CA_MIN_DISTANCE = 0.0f; + CA_MAX_DISTANCE = 0.0f; + LookingBehind = false; + LookingLeft = false; + LookingRight = false; + m_fPlayerInFrontSyphonAngleOffSet = DEGTORAD(20.0f); + m_fSyphonModeTargetZOffSet = 0.5f; + m_fRadiusForDead = 1.5f; + DirectionWasLooking = LOOKING_FORWARD; + LookBehindCamWasInFront = false; + f_Roll = 0.0f; + f_rollSpeed = 0.0f; + m_fCloseInPedHeightOffset = 0.0f; + m_fCloseInPedHeightOffsetSpeed = 0.0f; + m_fCloseInCarHeightOffset = 0.0f; + m_fCloseInCarHeightOffsetSpeed = 0.0f; + m_fPedBetweenCameraHeightOffset = 0.0f; + m_fTargetBeta = 0.0f; + m_fBufferedTargetBeta = 0.0f; + m_fBufferedTargetOrientation = 0.0f; + m_fBufferedTargetOrientationSpeed = 0.0f; + m_fDimensionOfHighestNearCar = 0.0f; + m_fRoadOffSet = 0.0f; +} + +void +CCam::Process(void) +{ + CVector CameraTarget; + float TargetSpeedVar = 0.0f; + float TargetOrientation = 0.0f; + + if(CamTargetEntity == nil) + CamTargetEntity = TheCamera.pTargetEntity; + + m_iFrameNumWereAt++; + if(m_iFrameNumWereAt > m_iDoCollisionCheckEveryNumOfFrames) + m_iFrameNumWereAt = 1; + m_bCollisionChecksOn = m_iFrameNumWereAt == m_iDoCollisionChecksOnFrameNum; + + if(m_bCamLookingAtVector){ + CameraTarget = m_cvecCamFixedModeVector; + }else if(CamTargetEntity->IsVehicle()){ + CameraTarget = CamTargetEntity->GetPosition(); + + if(CamTargetEntity->GetForward().x == 0.0f && CamTargetEntity->GetForward().y == 0.0f) + TargetOrientation = 0.0f; + else + TargetOrientation = CGeneral::GetATanOfXY(CamTargetEntity->GetForward().x, CamTargetEntity->GetForward().y); + + CVector Fwd(0.0f, 0.0f, 0.0f); + Fwd.x = CamTargetEntity->GetForward().x; + Fwd.y = CamTargetEntity->GetForward().y; + Fwd.Normalise(); + float FwdLength = Fwd.Magnitude2D(); + if(FwdLength != 0.0f){ + Fwd.x /= FwdLength; + Fwd.y /= FwdLength; + } + + float FwdSpeedX = ((CVehicle*)CamTargetEntity)->GetMoveSpeed().x * Fwd.x; + float FwdSpeedY = ((CVehicle*)CamTargetEntity)->GetMoveSpeed().y * Fwd.y; + if(FwdSpeedX + FwdSpeedY > 0.0f) + TargetSpeedVar = Min(Sqrt(SQR(FwdSpeedX) + SQR(FwdSpeedY))/0.9f, 1.0f); + else + TargetSpeedVar = -Min(Sqrt(SQR(FwdSpeedX) + SQR(FwdSpeedY))/1.8f, 0.5f); + SpeedVar = 0.895f*SpeedVar + 0.105*TargetSpeedVar; + }else{ + CameraTarget = CamTargetEntity->GetPosition(); + + if(CamTargetEntity->GetForward().x == 0.0f && CamTargetEntity->GetForward().y == 0.0f) + TargetOrientation = 0.0f; + else + TargetOrientation = CGeneral::GetATanOfXY(CamTargetEntity->GetForward().x, CamTargetEntity->GetForward().y); + TargetSpeedVar = 0.0f; + SpeedVar = 0.0f; + } + + switch(Mode){ + case MODE_TOPDOWN: + case MODE_GTACLASSIC: + Process_TopDown(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_BEHINDCAR: + Process_BehindCar(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_FOLLOWPED: +#ifdef PC_PLAYER_CONTROLS + if(CCamera::m_bUseMouse3rdPerson) + Process_FollowPedWithMouse(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + else +#endif +#ifdef FREE_CAM + if(CCamera::bFreeCam) + Process_FollowPed_Rotation(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + else +#endif + Process_FollowPed(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; +// case MODE_AIMING: + case MODE_DEBUG: + Process_Debug(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_SNIPER: + Process_Sniper(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_ROCKETLAUNCHER: + Process_Rocket(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_MODELVIEW: + Process_ModelView(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_BILL: + Process_Bill(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_SYPHON: + Process_Syphon(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_CIRCLE: + Process_Circle(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; +// case MODE_CHEESYZOOM: + case MODE_WHEELCAM: + Process_WheelCam(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_FIXED: + Process_Fixed(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_1STPERSON: + Process_1stPerson(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_FLYBY: + Process_FlyBy(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_CAM_ON_A_STRING: +#ifdef FREE_CAM + if(CCamera::bFreeCam && !CVehicle::bCheat5) + Process_FollowCar_SA(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + else +#endif + Process_Cam_On_A_String(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_REACTION: + Process_ReactionCam(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_FOLLOW_PED_WITH_BIND: + Process_FollowPed_WithBinding(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_CHRIS: + Process_Chris_With_Binding_PlusRotation(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_BEHINDBOAT: +#ifdef FREE_CAM + if (CCamera::bFreeCam) + Process_FollowCar_SA(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + else +#endif + Process_BehindBoat(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_PLAYER_FALLEN_WATER: + Process_Player_Fallen_Water(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_CAM_ON_TRAIN_ROOF: + Process_Cam_On_Train_Roof(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_CAM_RUNNING_SIDE_TRAIN: + Process_Cam_Running_Side_Train(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_BLOOD_ON_THE_TRACKS: + Process_Blood_On_The_Tracks(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_IM_THE_PASSENGER_WOOWOO: + Process_Im_The_Passenger_Woo_Woo(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_SYPHON_CRIM_IN_FRONT: + Process_Syphon_Crim_In_Front(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_PED_DEAD_BABY: + ProcessPedsDeadBaby(); + break; +// case MODE_PILLOWS_PAPS: +// case MODE_LOOK_AT_CARS: + case MODE_ARRESTCAM_ONE: + ProcessArrestCamOne(); + break; + case MODE_ARRESTCAM_TWO: + ProcessArrestCamTwo(); + break; + case MODE_M16_1STPERSON: + case MODE_HELICANNON_1STPERSON: // miami + Process_M16_1stPerson(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_SPECIAL_FIXED_FOR_SYPHON: + Process_SpecialFixedForSyphon(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_FIGHT_CAM: + Process_Fight_Cam(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_TOP_DOWN_PED: + Process_TopDownPed(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_SNIPER_RUNABOUT: + case MODE_ROCKETLAUNCHER_RUNABOUT: + case MODE_1STPERSON_RUNABOUT: + case MODE_M16_1STPERSON_RUNABOUT: + case MODE_FIGHT_CAM_RUNABOUT: + Process_1rstPersonPedOnPC(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; +#ifdef GTA_SCENE_EDIT + case MODE_EDITOR: + Process_Editor(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; +#endif + default: + Source = CVector(0.0f, 0.0f, 0.0f); + Front = CVector(0.0f, 1.0f, 0.0f); + Up = CVector(0.0f, 0.0f, 1.0f); + } + +#ifdef FREE_CAM + nPreviousMode = Mode; +#endif + CVector TargetToCam = Source - m_cvecTargetCoorsForFudgeInter; + float DistOnGround = TargetToCam.Magnitude2D(); + m_fTrueBeta = CGeneral::GetATanOfXY(TargetToCam.x, TargetToCam.y); + m_fTrueAlpha = CGeneral::GetATanOfXY(DistOnGround, TargetToCam.z); + if(TheCamera.m_uiTransitionState == 0) + KeepTrackOfTheSpeed(Source, m_cvecTargetCoorsForFudgeInter, Up, m_fTrueAlpha, m_fTrueBeta, FOV); + + // Look Behind, Left, Right + LookingBehind = false; + LookingLeft = false; + LookingRight = false; + SourceBeforeLookBehind = Source; + if(&TheCamera.Cams[TheCamera.ActiveCam] == this){ + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_1STPERSON || Mode == MODE_BEHINDBOAT) && + CamTargetEntity->IsVehicle()){ + if(CPad::GetPad(0)->GetLookBehindForCar()){ + LookBehind(); + if(DirectionWasLooking != LOOKING_BEHIND) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_BEHIND; + }else if(CPad::GetPad(0)->GetLookLeft()){ + LookLeft(); + if(DirectionWasLooking != LOOKING_LEFT) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_LEFT; + }else if(CPad::GetPad(0)->GetLookRight()){ + LookRight(); + if(DirectionWasLooking != LOOKING_RIGHT) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_RIGHT; + }else{ + if(DirectionWasLooking != LOOKING_FORWARD) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_FORWARD; + } + } + if(Mode == MODE_FOLLOWPED && CamTargetEntity->IsPed()){ + if(CPad::GetPad(0)->GetLookBehindForPed()){ + LookBehind(); + if(DirectionWasLooking != LOOKING_BEHIND) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_BEHIND; + }else + DirectionWasLooking = LOOKING_FORWARD; + } + } + + if(Mode == MODE_SNIPER || Mode == MODE_ROCKETLAUNCHER || Mode == MODE_M16_1STPERSON || + Mode == MODE_1STPERSON || Mode == MODE_HELICANNON_1STPERSON || GetWeaponFirstPersonOn()) + ClipIfPedInFrontOfPlayer(); +} + +// 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 * Abs(TargetSpeed - *CurrentSpeed) * CTimer::GetTimeStep(); +// else +// *CurrentSpeed -= Acceleration * Abs(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 +MakeAngleLessThan180(float &Angle) +{ + while(Angle >= PI) Angle -= 2*PI; + while(Angle < -PI) Angle += 2*PI; +} + +void +CCam::ProcessSpecialHeightRoutines(void) +{ + int i = 0; + bool StandingOnBoat = false; + static bool PreviouslyFailedRoadHeightCheck = false; + CVector CamToTarget, CamToPed; + float DistOnGround, BetaAngle; + CPed *Player; + int ClosestPed = 0; + bool FoundPed = false; + float ClosestPedDist, PedZDist; + CColPoint colPoint; + + CamToTarget = TheCamera.pTargetEntity->GetPosition() - TheCamera.GetGameCamPosition(); + DistOnGround = CamToTarget.Magnitude2D(); + BetaAngle = CGeneral::GetATanOfXY(CamToTarget.x, CamToTarget.y); + m_bTheHeightFixerVehicleIsATrain = false; + ClosestPedDist = 0.0f; + // CGeneral::GetATanOfXY(TheCamera.GetForward().x, TheCamera.GetForward().y); + Player = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + + if(DistOnGround > 10.0f) + DistOnGround = 10.0f; + + if(CamTargetEntity && CamTargetEntity->IsPed()){ + if(FindPlayerPed()->m_pCurSurface && FindPlayerPed()->m_pCurSurface->IsVehicle() && + ((CVehicle*)FindPlayerPed()->m_pCurSurface)->IsBoat()) + StandingOnBoat = true; + + // Move up the camera if there is a ped close to it + if(Mode == MODE_FOLLOWPED || Mode == MODE_FIGHT_CAM){ + // Find ped closest to camera + while(i < Player->m_numNearPeds){ + if(Player->m_nearPeds[i] && Player->m_nearPeds[i]->GetPedState() != PED_DEAD){ + CamToPed = Player->m_nearPeds[i]->GetPosition() - TheCamera.GetGameCamPosition(); + if(FoundPed){ + if(CamToPed.Magnitude2D() < ClosestPedDist){ + ClosestPed = i; + ClosestPedDist = CamToPed.Magnitude2D(); + } + }else{ + FoundPed = true; + ClosestPed = i; + ClosestPedDist = CamToPed.Magnitude2D(); + } + } + i++; + } + + if(FoundPed){ + float Offset = 0.0f; + CPed *Ped = Player->m_nearPeds[ClosestPed]; + CamToPed = Ped->GetPosition() - TheCamera.GetGameCamPosition(); + PedZDist = 0.0f; + float dist = CamToPed.Magnitude2D(); // should be same as ClosestPedDist + if(dist < 2.1f){ + // Ped is close to camera, move up + + // Z Distance between player and close ped + PedZDist = 0.0f; + if(Ped->bIsStanding) + PedZDist = Ped->GetPosition().z - Player->GetPosition().z; + // Ignore if too distant + if(PedZDist > 1.2f || PedZDist < -1.2f) + PedZDist = 0.0f; + + float DistScale = (2.1f - dist)/2.1f; + if(Mode == MODE_FOLLOWPED){ + if(TheCamera.PedZoomIndicator == CAM_ZOOM_1) + Offset = 0.45f*DistScale + PedZDist; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_2) + Offset = 0.35f*DistScale + PedZDist; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_3) + Offset = 0.25f*DistScale + PedZDist; + if(Abs(CGeneral::GetRadianAngleBetweenPoints(CamToPed.x, CamToPed.y, CamToTarget.x, CamToTarget.y)) > HALFPI) + Offset += 0.3f; + m_fPedBetweenCameraHeightOffset = Offset + 1.3f; + PedZDist = 0.0f; + }else if(Mode == MODE_FIGHT_CAM) + m_fPedBetweenCameraHeightOffset = PedZDist + 1.3f + 0.5f; + }else + m_fPedBetweenCameraHeightOffset = 0.0f; + }else{ + PedZDist = 0.0f; + m_fPedBetweenCameraHeightOffset = 0.0f; + } + }else + PedZDist = 0.0f; + + + // Move camera up for vehicles in the way + if(m_bCollisionChecksOn && (Mode == MODE_FOLLOWPED || Mode == MODE_FIGHT_CAM)){ + bool FoundCar = false; + CEntity *vehicle = nil; + float TestDist = DistOnGround + 1.25f; + float HighestCar = 0.0f; + CVector TestBase = CamTargetEntity->GetPosition(); + CVector TestPoint; + TestBase.z -= 0.15f; + + TestPoint = TestBase - TestDist * CVector(Cos(BetaAngle), Sin(BetaAngle), 0.0f); + if(CWorld::ProcessLineOfSight(CamTargetEntity->GetPosition(), TestPoint, colPoint, vehicle, false, true, false, false, false, false) && + vehicle->IsVehicle()){ + float height = vehicle->GetColModel()->boundingBox.GetSize().z; + FoundCar = true; + HighestCar = height; + if(((CVehicle*)vehicle)->IsTrain()) + m_bTheHeightFixerVehicleIsATrain = true; + } + + TestPoint = TestBase - TestDist * CVector(Cos(BetaAngle+DEGTORAD(28.0f)), Sin(BetaAngle+DEGTORAD(28.0f)), 0.0f); + if(CWorld::ProcessLineOfSight(CamTargetEntity->GetPosition(), TestPoint, colPoint, vehicle, false, true, false, false, false, false) && + vehicle->IsVehicle()){ + float height = vehicle->GetColModel()->boundingBox.GetSize().z; + if(FoundCar){ + HighestCar = Max(HighestCar, height); + }else{ + FoundCar = true; + HighestCar = height; + } + if(((CVehicle*)vehicle)->IsTrain()) + m_bTheHeightFixerVehicleIsATrain = true; + } + + TestPoint = TestBase - TestDist * CVector(Cos(BetaAngle-DEGTORAD(28.0f)), Sin(BetaAngle-DEGTORAD(28.0f)), 0.0f); + if(CWorld::ProcessLineOfSight(CamTargetEntity->GetPosition(), TestPoint, colPoint, vehicle, false, true, false, false, false, false) && + vehicle->IsVehicle()){ + float height = vehicle->GetColModel()->boundingBox.GetSize().z; + if(FoundCar){ + HighestCar = Max(HighestCar, height); + }else{ + FoundCar = true; + HighestCar = height; + } + if(((CVehicle*)vehicle)->IsTrain()) + m_bTheHeightFixerVehicleIsATrain = true; + } + + if(FoundCar){ + m_fDimensionOfHighestNearCar = HighestCar + 0.1f; + if(Mode == MODE_FIGHT_CAM) + m_fDimensionOfHighestNearCar += 0.75f; + }else + m_fDimensionOfHighestNearCar = 0.0f; + } + + // Move up for road + if(Mode == MODE_FOLLOWPED || Mode == MODE_FIGHT_CAM || + Mode == MODE_SYPHON || Mode == MODE_SYPHON_CRIM_IN_FRONT || Mode == MODE_SPECIAL_FIXED_FOR_SYPHON){ + bool Inside = false; + bool OnRoad = false; + + switch(((CPhysical*)CamTargetEntity)->m_nSurfaceTouched) + case SURFACE_GRASS: + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: + case SURFACE_THICK_METAL_PLATE: + case SURFACE_RUBBER: + case SURFACE_STEEP_CLIFF: + OnRoad = true; + + if(CCullZones::PlayerNoRain()) + Inside = true; + + if((m_bCollisionChecksOn || PreviouslyFailedRoadHeightCheck || OnRoad) && + m_fCloseInPedHeightOffset < 0.0001f && !Inside){ + CVector TestPoint; + CEntity *road; + float GroundZ = 0.0f; + bool FoundGround = false; + float RoofZ = 0.0f; + bool FoundRoof = false; + static float MinHeightAboveRoad = 0.9f; + + TestPoint = CamTargetEntity->GetPosition() - DistOnGround * CVector(Cos(BetaAngle), Sin(BetaAngle), 0.0f); + m_fRoadOffSet = 0.0f; + + if(CWorld::ProcessVerticalLine(TestPoint, -1000.0f, colPoint, road, true, false, false, false, false, false, nil)){ + FoundGround = true; + GroundZ = colPoint.point.z; + } + // Move up if too close to ground + if(FoundGround){ + if(TestPoint.z - GroundZ < MinHeightAboveRoad){ + m_fRoadOffSet = GroundZ + MinHeightAboveRoad - TestPoint.z; + PreviouslyFailedRoadHeightCheck = true; + }else{ + if(m_bCollisionChecksOn) + PreviouslyFailedRoadHeightCheck = false; + else + m_fRoadOffSet = 0.0f; + } + }else{ + if(CWorld::ProcessVerticalLine(TestPoint, 1000.0f, colPoint, road, true, false, false, false, false, false, nil)){ + FoundRoof = true; + RoofZ = colPoint.point.z; + } + if(FoundRoof){ + if(TestPoint.z - RoofZ < MinHeightAboveRoad){ + m_fRoadOffSet = RoofZ + MinHeightAboveRoad - TestPoint.z; + PreviouslyFailedRoadHeightCheck = true; + }else{ + if(m_bCollisionChecksOn) + PreviouslyFailedRoadHeightCheck = false; + else + m_fRoadOffSet = 0.0f; + } + } + } + } + } + + if(PreviouslyFailedRoadHeightCheck && m_fCloseInPedHeightOffset < 0.0001f){ + if(colPoint.surfaceB != SURFACE_TARMAC && + colPoint.surfaceB != SURFACE_GRASS && + colPoint.surfaceB != SURFACE_GRAVEL && + colPoint.surfaceB != SURFACE_MUD_DRY && + colPoint.surfaceB != SURFACE_STEEP_CLIFF){ + if(m_fRoadOffSet > 1.4f) + m_fRoadOffSet = 1.4f; + }else{ + if(Mode == MODE_FOLLOWPED){ + if(TheCamera.PedZoomIndicator == CAM_ZOOM_1) + m_fRoadOffSet += 0.2f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_2) + m_fRoadOffSet += 0.5f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_3) + m_fRoadOffSet += 0.95f; + } + } + } + } + + if(StandingOnBoat){ + m_fRoadOffSet = 0.0f; + m_fDimensionOfHighestNearCar = 1.0f; + m_fPedBetweenCameraHeightOffset = 0.0f; + } +} + +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); +} + +void +CCam::LookBehind(void) +{ + float Dist, DeltaBeta, TargetOrientation, Angle; + CVector TargetCoors, TargetFwd, TestCoors; + CColPoint colPoint; + CEntity *entity; + + TargetCoors = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_BEHINDBOAT) && CamTargetEntity->IsVehicle()){ + LookingBehind = true; + Dist = Mode == MODE_CAM_ON_A_STRING ? CA_MAX_DISTANCE : 15.5f; + TargetFwd = CamTargetEntity->GetForward(); + TargetFwd.Normalise(); + TargetOrientation = CGeneral::GetATanOfXY(TargetFwd.x, TargetFwd.y); + DeltaBeta = TargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(DirectionWasLooking != LOOKING_BEHIND) + LookBehindCamWasInFront = DeltaBeta <= -HALFPI || DeltaBeta >= HALFPI; + if(LookBehindCamWasInFront) + TargetOrientation += PI; + Source.x = Dist*Cos(TargetOrientation) + TargetCoors.x; + Source.y = Dist*Sin(TargetOrientation) + TargetCoors.y; + Source.z -= 1.0f; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, false, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + Source = colPoint.point; + } + Source.z += 1.0f; + Front = CamTargetEntity->GetPosition() - Source; + GetVectorsReadyForRW(); + } + if(Mode == MODE_1STPERSON && CamTargetEntity->IsVehicle()){ + LookingBehind = true; + RwCameraSetNearClipPlane(Scene.camera, 0.25f); + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + if(((CVehicle*)CamTargetEntity)->IsBoat()) + Source.z -= 0.5f; + Source += 0.25f*Front; + Front = -Front; +#ifdef FIX_BUGS + // not sure if this is a bug... + GetVectorsReadyForRW(); +#endif + } + if(CamTargetEntity->IsPed()){ + Angle = CGeneral::GetATanOfXY(Source.x - TargetCoors.x, Source.y - TargetCoors.y) + PI; + Source.x = 4.5f*Cos(Angle) + TargetCoors.x; + Source.y = 4.5f*Sin(Angle) + TargetCoors.y; + Source.z = 1.15f + TargetCoors.z; + TestCoors = TargetCoors; + TestCoors.z = Source.z; + if(CWorld::ProcessLineOfSight(TestCoors, Source, colPoint, entity, true, true, false, true, false, true, true)){ + Source.x = colPoint.point.x; + Source.y = colPoint.point.y; + if((TargetCoors - Source).Magnitude2D() < 1.15f) + RwCameraSetNearClipPlane(Scene.camera, 0.05f); + } + Front = TargetCoors - Source; + GetVectorsReadyForRW(); + } +} + +void +CCam::LookLeft(void) +{ + float Dist, TargetOrientation; + CVector TargetCoors, TargetFwd; + CColPoint colPoint; + CEntity *entity; + + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_BEHINDBOAT) && CamTargetEntity->IsVehicle()){ + LookingLeft = true; + TargetCoors = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + Dist = Mode == MODE_CAM_ON_A_STRING ? CA_MAX_DISTANCE : 9.0f; + TargetFwd = CamTargetEntity->GetForward(); + TargetFwd.Normalise(); + TargetOrientation = CGeneral::GetATanOfXY(TargetFwd.x, TargetFwd.y); + Source.x = Dist*Cos(TargetOrientation - HALFPI) + TargetCoors.x; + Source.y = Dist*Sin(TargetOrientation - HALFPI) + TargetCoors.y; + Source.z -= 1.0f; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, false, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source = colPoint.point; + } + Source.z += 1.0f; + Front = CamTargetEntity->GetPosition() - Source; + Front.z += 1.1f; + if(Mode == MODE_BEHINDBOAT) + Front.z += 1.2f; + GetVectorsReadyForRW(); + } + if(Mode == MODE_1STPERSON && CamTargetEntity->IsVehicle()){ + LookingLeft = true; + RwCameraSetNearClipPlane(Scene.camera, 0.25f); + if(((CVehicle*)CamTargetEntity)->IsBoat()) + Source.z -= 0.5f; + + Up = CamTargetEntity->GetUp(); + Up.Normalise(); + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Front = -CrossProduct(Front, Up); + Front.Normalise(); +#ifdef FIX_BUGS + // not sure if this is a bug... + GetVectorsReadyForRW(); +#endif + } +} + +void +CCam::LookRight(void) +{ + float Dist, TargetOrientation; + CVector TargetCoors, TargetFwd; + CColPoint colPoint; + CEntity *entity; + + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_BEHINDBOAT) && CamTargetEntity->IsVehicle()){ + LookingRight = true; + TargetCoors = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + Dist = Mode == MODE_CAM_ON_A_STRING ? CA_MAX_DISTANCE : 9.0f; + TargetFwd = CamTargetEntity->GetForward(); + TargetFwd.Normalise(); + TargetOrientation = CGeneral::GetATanOfXY(TargetFwd.x, TargetFwd.y); + Source.x = Dist*Cos(TargetOrientation + HALFPI) + TargetCoors.x; + Source.y = Dist*Sin(TargetOrientation + HALFPI) + TargetCoors.y; + Source.z -= 1.0f; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, false, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source = colPoint.point; + } + Source.z += 1.0f; + Front = CamTargetEntity->GetPosition() - Source; + Front.z += 1.1f; + if(Mode == MODE_BEHINDBOAT) + Front.z += 1.2f; + GetVectorsReadyForRW(); + } + if(Mode == MODE_1STPERSON && CamTargetEntity->IsVehicle()){ + LookingRight = true; + RwCameraSetNearClipPlane(Scene.camera, 0.25f); + if(((CVehicle*)CamTargetEntity)->IsBoat()) + Source.z -= 0.5f; + + Up = CamTargetEntity->GetUp(); + Up.Normalise(); + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Front = CrossProduct(Front, Up); + Front.Normalise(); +#ifdef FIX_BUGS + // not sure if this is a bug... + GetVectorsReadyForRW(); +#endif + } +} + +void +CCam::ClipIfPedInFrontOfPlayer(void) +{ + float FwdAngle, PedAngle, DeltaAngle, fDist, Near; + CVector vDist; + CPed *Player; + bool found = false; + int ped = 0; + + // unused: TheCamera.pTargetEntity->GetPosition() - TheCamera.GetGameCamPosition(); + + FwdAngle = CGeneral::GetATanOfXY(TheCamera.GetForward().x, TheCamera.GetForward().y); + Player = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + while(ped < Player->m_numNearPeds && !found) + if(Player->m_nearPeds[ped] && Player->m_nearPeds[ped]->GetPedState() != PED_DEAD) + found = true; + else + ped++; + if(found){ + vDist = Player->m_nearPeds[ped]->GetPosition() - TheCamera.GetGameCamPosition(); + PedAngle = CGeneral::GetATanOfXY(vDist.x, vDist.y); + DeltaAngle = FwdAngle - PedAngle; + while(DeltaAngle >= PI) DeltaAngle -= 2*PI; + while(DeltaAngle < -PI) DeltaAngle += 2*PI; + if(Abs(DeltaAngle) < HALFPI){ + fDist = vDist.Magnitude2D(); + if(fDist < 1.25f){ + Near = DEFAULT_NEAR - (1.25f - fDist); + if(Near < 0.05f) + Near = 0.05f; + RwCameraSetNearClipPlane(Scene.camera, Near); + } + } + } +} + +void +CCam::KeepTrackOfTheSpeed(const CVector &source, const CVector &target, const CVector &up, const float &alpha, const float &beta, const float &fov) +{ + static CVector PreviousSource = source; + static CVector PreviousTarget = target; + static CVector PreviousUp = up; + static float PreviousBeta = beta; + static float PreviousAlpha = alpha; + static float PreviousFov = fov; + + if(TheCamera.m_bJust_Switched){ + PreviousSource = source; + PreviousTarget = target; + PreviousUp = up; + } + + m_cvecSourceSpeedOverOneFrame = source - PreviousSource; + m_cvecTargetSpeedOverOneFrame = target - PreviousTarget; + m_cvecUpOverOneFrame = up - PreviousUp; + m_fFovSpeedOverOneFrame = fov - PreviousFov; + m_fBetaSpeedOverOneFrame = beta - PreviousBeta; + MakeAngleLessThan180(m_fBetaSpeedOverOneFrame); + m_fAlphaSpeedOverOneFrame = alpha - PreviousAlpha; + MakeAngleLessThan180(m_fAlphaSpeedOverOneFrame); + + PreviousSource = source; + PreviousTarget = target; + PreviousUp = up; + PreviousBeta = beta; + PreviousAlpha = alpha; + PreviousFov = fov; +} + +bool +CCam::Using3rdPersonMouseCam(void) +{ + return CCamera::m_bUseMouse3rdPerson && + (Mode == MODE_FOLLOWPED || + TheCamera.m_bPlayerIsInGarage && + FindPlayerPed() && FindPlayerPed()->m_nPedState != PED_DRIVING && + Mode != MODE_TOPDOWN && CamTargetEntity == FindPlayerPed()); +} + +bool +CCam::GetWeaponFirstPersonOn(void) +{ + return CamTargetEntity && CamTargetEntity->IsPed() && ((CPed*)CamTargetEntity)->GetWeapon()->m_bAddRotOffset; +} + +bool +CCam::IsTargetInWater(const CVector &CamCoors) +{ + if(CamTargetEntity == nil) + return false; + if(CamTargetEntity->IsPed()){ + if(!((CPed*)CamTargetEntity)->bIsInWater) + return false; + if(!((CPed*)CamTargetEntity)->bIsStanding) + return true; + return false; + } + return ((CPhysical*)CamTargetEntity)->bIsInWater; +} + +void +CCam::PrintMode(void) +{ + // Doesn't do anything + char buf[256]; + + if(PrintDebugCode){ + sprintf(buf, " "); + sprintf(buf, " "); + sprintf(buf, " "); + + static Const char *modes[] = { "None", + "Top Down", "GTA Classic", "Behind Car", "Follow Ped", + "Aiming", "Debug", "Sniper", "Rocket", "Model Viewer", "Bill", + "Syphon", "Circle", "Cheesy Zoom", "Wheel", "Fixed", + "1st Person", "Fly by", "on a String", "Reaction", + "Follow Ped with Bind", "Chris", "Behind Boat", + "Player fallen in Water", "Train Roof", "Train Side", + "Blood on the tracks", "Passenger", "Syphon Crim in Front", + "Dead Baby", "Pillow Paps", "Look at Cars", "Arrest One", + "Arrest Two", "M16", "Special fixed for Syphon", "Fight", + "Top Down Ped", + "Sniper run about", "Rocket run about", + "1st Person run about", "M16 run about", "Fight run about", + "Editor" + }; + sprintf(buf, "Cam: %s", modes[TheCamera.Cams[TheCamera.ActiveCam].Mode]); + CDebug::PrintAt(buf, 2, 5); + } + + if(DebugCamMode != MODE_NONE){ + switch(Mode){ + case MODE_FOLLOWPED: + sprintf(buf, "Debug:- Cam Choice1. No Locking, used as game default"); + break; + case MODE_REACTION: + sprintf(buf, "Debug:- Cam Choice2. Reaction Cam On A String "); + sprintf(buf, " Uses Locking Button LeftShoulder 1. "); // lie + break; + case MODE_FOLLOW_PED_WITH_BIND: + sprintf(buf, "Debug:- Cam Choice3. Game ReactionCam with Locking "); + sprintf(buf, " Uses Locking Button LeftShoulder 1. "); + break; + case MODE_CHRIS: + sprintf(buf, "Debug:- Cam Choice4. Chris's idea. "); + sprintf(buf, " Uses Locking Button LeftShoulder 1. "); + sprintf(buf, " Also control the camera using the right analogue stick."); + break; + } + } +} + +// 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; +} + +float DefaultAcceleration = 0.045f; +float DefaultMaxStep = 0.15f; + +void +CCam::Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + 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 StandingInTrain = false; + static int TimeIndicatedWantedToGoDown = 0; + static bool StartedCountingForGoDown = false; + float DeltaBeta; + + m_bFixingBeta = false; + bBelowMinDist = false; + bBehindPlayerDesired = false; + + // CenterDist should be > LateralDist because we don't have an angle for safety in this case + float CenterDist, LateralDist; + float AngleToGoToSpeed; + if(m_fCloseInPedHeightOffset > 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 == CAM_ZOOM_1 || TheCamera.PedZoomIndicator == CAM_ZOOM_TOPDOWN){ + 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; + + TargetCoors = DoAverageOnVector(TargetCoors); + TargetCoors.z += m_fRoadOffSet; + + 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); + + 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) + StandingInTrain = 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 == CAM_ZOOM_1) m_fRealGroundDist = 2.090556f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_2) m_fRealGroundDist = 3.34973f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_3) m_fRealGroundDist = 4.704914f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_TOPDOWN) m_fRealGroundDist = 2.090556f; + } + // And what is this? It's only used for collision and rotation it seems + float RealGroundDist; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_1) RealGroundDist = 2.090556f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_2) RealGroundDist = 3.34973f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_3) RealGroundDist = 4.704914f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_TOPDOWN) RealGroundDist = 2.090556f; + if(m_fCloseInPedHeightOffset > 0.00001f) + RealGroundDist = 1.7016f; + + + 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_fRoadOffSet; + + // 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_fRoadOffSet; + + + // 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 RotDistance = (IdealSource - TargetCoors).Magnitude2D(); + m_fDistanceBeforeChanges = RotDistance; + + 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 * Abs(ReqSpeed - BetaSpeed) * CTimer::GetTimeStep(); + else + BetaSpeed -= SpeedStep * Abs(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 + RotDistance * Cos(Beta); + Source.y = TargetCoors.y + RotDistance * Sin(Beta); + + // Check if we can stop rotating + DeltaBeta = FixedTargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < DEGTORAD(1.0f) && !bBehindPlayerDesired){ + // Stop rotation + PickedASide = false; + Rotating = false; + BetaSpeed = 0.0f; + } + } + + + if(TheCamera.m_bCamDirectlyBehind || TheCamera.m_bCamDirectlyInFront || + StandingInTrain || Rotating){ + if(TheCamera.m_bCamDirectlyBehind){ + Beta = TargetOrientation + PI; + Source.x = TargetCoors.x + RotDistance * Cos(Beta); + Source.y = TargetCoors.y + RotDistance * Sin(Beta); + } + if(TheCamera.m_bCamDirectlyInFront){ + Beta = TargetOrientation; + Source.x = TargetCoors.x + RotDistance * Cos(Beta); + Source.y = TargetCoors.y + RotDistance * Sin(Beta); + } + if(StandingInTrain){ + Beta = TargetOrientation + PI; + Source.x = TargetCoors.x + RotDistance * Cos(Beta); + Source.y = TargetCoors.y + RotDistance * 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_fRoadOffSet from both? + TargetCoors.z -= m_fRoadOffSet; + Source.z = IdealSource.z - m_fRoadOffSet; + + // 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_fRoadOffSet + 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_fRoadOffSet && TargetZOffSet > m_fCamBufferedHeight){ + // TODO: figure this out + bool foo = false; + switch(((CPhysical*)CamTargetEntity)->m_nSurfaceTouched) + case SURFACE_GRASS: + case SURFACE_GRAVEL: + case SURFACE_PAVEMENT: + case SURFACE_THICK_METAL_PLATE: + case SURFACE_RUBBER: + case SURFACE_STEEP_CLIFF: + 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; +} + +float fBaseDist = 1.7f; +float fAngleDist = 2.0f; +float fFalloff = 3.0f; +float fStickSens = 0.01f; +float fTweakFOV = 1.05f; +float fTranslateCamUp = 0.8f; +int16 nFadeControlThreshhold = 45; +float fDefaultAlphaOrient = -0.22f; + +void +CCam::Process_FollowPedWithMouse(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = DefaultFOV; + + if(!CamTargetEntity->IsPed()) + return; + + CVector TargetCoors; + float CamDist; + CColPoint colPoint; + CEntity *entity; + + if(ResetStatics){ + Rotating = false; + m_bCollisionChecksOn = true; + CPad::GetPad(0)->ClearMouseHistory(); + ResetStatics = false; + } + + bool OnTrain = FindPlayerVehicle() && FindPlayerVehicle()->IsTrain(); + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; + if((MouseX != 0.0f || MouseY != 0.0f) && !CPad::GetPad(0)->ArePlayerControlsDisabled()){ + UseMouse = true; + LookLeftRight = -2.5f*MouseX; + LookUpDown = 4.0f*MouseY; + }else{ + LookLeftRight = -CPad::GetPad(0)->LookAroundLeftRight(); + LookUpDown = CPad::GetPad(0)->LookAroundUpDown(); + } + float AlphaOffset, BetaOffset; + if(UseMouse){ + BetaOffset = LookLeftRight * TheCamera.m_fMouseAccelHorzntl * FOV/80.0f; + AlphaOffset = LookUpDown * TheCamera.m_fMouseAccelVertical * FOV/80.0f; + }else{ + BetaOffset = LookLeftRight * fStickSens * (1.0f/14.0f) * FOV/80.0f * CTimer::GetTimeStep(); + AlphaOffset = LookUpDown * fStickSens * (0.6f/14.0f) * FOV/80.0f * CTimer::GetTimeStep(); + } + + if(TheCamera.GetFading() && TheCamera.GetFadingDirection() == FADE_IN && nFadeControlThreshhold < CDraw::FadeValue || + CDraw::FadeValue > 200){ + if(Alpha < fDefaultAlphaOrient-0.05f) + AlphaOffset = 0.05f; + else if(Alpha < fDefaultAlphaOrient) + AlphaOffset = fDefaultAlphaOrient - Alpha; + else if(Alpha > fDefaultAlphaOrient+0.05f) + AlphaOffset = -0.05f; + else if(Alpha > fDefaultAlphaOrient) + AlphaOffset = fDefaultAlphaOrient - Alpha; + else + AlphaOffset = 0.0f; + } + + Alpha += AlphaOffset; + Beta += BetaOffset; + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(45.0f)) Alpha = DEGTORAD(45.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + TargetCoors = CameraTarget; + TargetCoors.z += fTranslateCamUp; + TargetCoors = DoAverageOnVector(TargetCoors); + + // SA code +#ifdef FREE_CAM + if((CCamera::bFreeCam && Alpha > 0.0f) || (!CCamera::bFreeCam && Alpha > fBaseDist)) +#else + if(Alpha > fBaseDist) // comparing an angle against a distance? +#endif + CamDist = fBaseDist + Cos(Min(Alpha*fFalloff, HALFPI))*fAngleDist; + else + CamDist = fBaseDist + Cos(Alpha)*fAngleDist; + + if(TheCamera.m_bUseTransitionBeta) + Beta = CGeneral::GetATanOfXY(-Cos(m_fTransitionBeta), -Sin(m_fTransitionBeta)); + + if(TheCamera.m_bCamDirectlyBehind) + Beta = TheCamera.m_PedOrientForBehindOrInFront; + if(TheCamera.m_bCamDirectlyInFront) + Beta = TheCamera.m_PedOrientForBehindOrInFront + PI; + if(OnTrain) + Beta = TargetOrientation; + + Front.x = Cos(Alpha) * Cos(Beta); + Front.y = Cos(Alpha) * Sin(Beta); + Front.z = Sin(Alpha); + Source = TargetCoors - Front*CamDist; + m_cvecTargetCoorsForFudgeInter = TargetCoors; + + // Clip Source and fix near clip + CWorld::pIgnoreEntity = CamTargetEntity; + entity = nil; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, true, true, true, false, false, true)){ + float PedColDist = (TargetCoors - colPoint.point).Magnitude(); + float ColCamDist = CamDist - PedColDist; + if(entity->IsPed() && ColCamDist > DEFAULT_NEAR + 0.1f){ + // Ped in the way but not clipping through + if(CWorld::ProcessLineOfSight(colPoint.point, Source, colPoint, entity, true, true, true, true, false, false, true)){ + PedColDist = (TargetCoors - colPoint.point).Magnitude(); + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + }else{ + RwCameraSetNearClipPlane(Scene.camera, Min(ColCamDist-0.35f, DEFAULT_NEAR)); + } + }else{ + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + } + } + CWorld::pIgnoreEntity = nil; + + float ViewPlaneHeight = Tan(DEGTORAD(FOV) / 2.0f); + float ViewPlaneWidth = ViewPlaneHeight * CDraw::FindAspectRatio() * fTweakFOV; + float Near = RwCameraGetNearClipPlane(Scene.camera); + float radius = ViewPlaneWidth*Near; + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, false); + int i = 0; + while(entity){ + CVector CamToCol = gaTempSphereColPoints[0].point - Source; + float frontDist = DotProduct(CamToCol, Front); + float dist = (CamToCol - Front*frontDist).Magnitude() / ViewPlaneWidth; + + // Try to decrease near clip + dist = Max(Min(Near, dist), 0.1f); + if(dist < Near) + RwCameraSetNearClipPlane(Scene.camera, dist); + + // Move forward a bit + if(dist == 0.1f) + Source += (TargetCoors - Source)*0.3f; + + Near = RwCameraGetNearClipPlane(Scene.camera); +#ifndef FIX_BUGS + // this is totally wrong... + radius = Tan(FOV / 2.0f) * Near; +#else + radius = ViewPlaneWidth*Near; +#endif + // Keep testing + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, false); + + i++; + if(i > 5) + entity = nil; + } + + if(CamTargetEntity->m_rwObject){ + // what's going on here? + if(RpAnimBlendClumpGetAssociation(CamTargetEntity->GetClump(), ANIM_STD_WEAPON_PUMP) || + RpAnimBlendClumpGetAssociation(CamTargetEntity->GetClump(), ANIM_STD_WEAPON_THROW) || + RpAnimBlendClumpGetAssociation(CamTargetEntity->GetClump(), ANIM_STD_THROW_UNDER) || + RpAnimBlendClumpGetAssociation(CamTargetEntity->GetClump(), ANIM_STD_START_THROW)){ + CPed *player = FindPlayerPed(); + float PlayerDist = (Source - player->GetPosition()).Magnitude(); + if(PlayerDist < 2.75f) + Near = PlayerDist/2.75f * DEFAULT_NEAR - 0.3f; + RwCameraSetNearClipPlane(Scene.camera, Max(Near, 0.1f)); + } + } + + TheCamera.m_bCamDirectlyInFront = false; + TheCamera.m_bCamDirectlyBehind = false; + + GetVectorsReadyForRW(); + + if(((CPed*)CamTargetEntity)->CanStrafeOrMouseControl() && CDraw::FadeValue < 250 && + (TheCamera.GetFadingDirection() != FADE_OUT || CDraw::FadeValue <= 100)){ + float Heading = Front.Heading(); + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Heading; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Heading; + TheCamera.pTargetEntity->SetHeading(Heading); + TheCamera.pTargetEntity->GetMatrix().UpdateRW(); + } +} + +float fBillsBetaOffset; // made up name, actually in CCam + +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 1 + // This is completely made up but Bill's cam manipulates an angle before calling this + // and otherwise calculating Beta doesn't make much sense. + Beta += fBillsBetaOffset; + fBillsBetaOffset = 0.0f; + Dist.x = -Length*Cos(Beta); + Dist.y = -Length*Sin(Beta); + Source = TargetCoors + Dist; +#endif + 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_fRoadOffSet = 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_TARMAC && + colpoint.surfaceB != SURFACE_GRASS && + colpoint.surfaceB != SURFACE_GRAVEL && + colpoint.surfaceB != SURFACE_MUD_DRY && + colpoint.surfaceB != SURFACE_PAVEMENT && + colpoint.surfaceB != SURFACE_THICK_METAL_PLATE && + colpoint.surfaceB != SURFACE_STEEP_CLIFF && + RoadHeightFix > 1.4f) + RoadHeightFix = 1.4f; + + Source.z += RoadHeightFix; +} + +void +CCam::WorkOutCamHeight(const CVector &TargetCoors, float TargetOrientation, float TargetHeight) +{ + float AlphaOffset = 0.0f; + bool CamClear = true; + + static float LastTargetAlphaWithCollisionOn = 0.0f; + static float LastTopAlphaSpeed = 0.0f; + static float LastAlphaSpeedStep = 0.0f; + static bool PreviousNearCheckNearClipSmall = false; + + 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 == CAM_ZOOM_1) + AlphaOffset = CGeneral::GetATanOfXY(23.0f, zoomvalue); // near + else if(TheCamera.CarZoomIndicator == CAM_ZOOM_2) + AlphaOffset = CGeneral::GetATanOfXY(10.8f, zoomvalue); // mid + else if(TheCamera.CarZoomIndicator == CAM_ZOOM_3) + AlphaOffset = CGeneral::GetATanOfXY(7.0f, zoomvalue); // far + + + float Length = (Source - TargetCoors).Magnitude2D(); + if(m_bCollisionChecksOn){ // there's another variable (on PC) but it's uninitialised + float CarAlpha = CGeneral::GetATanOfXY(CamTargetEntity->GetForward().Magnitude2D(), CamTargetEntity->GetForward().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 + CVector Forward = CamTargetEntity->GetForward(); + 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 + AlphaOffset) + 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 > AlphaOffset) + CamClear = true; + // Camera is constrained by collision in some way + PreviousNearCheckNearClipSmall = false; + if(!CamClear){ + PreviousNearCheckNearClipSmall = true; + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + + DeltaAlpha = TargetAlpha - (Alpha + AlphaOffset); + 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 + AlphaOffset) + m_fCloseInCarHeightOffset; + bool FoundGround, FoundRoof; + float CamGround2 = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, CamZ, &FoundGround); + if(FoundGround && CamClear){ + if(CamZ - CamGround2 < 1.5f){ + PreviousNearCheckNearClipSmall = true; + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + + float dz = CamGround2 + 1.5f - TargetCoors.z; + float a; + if(Length == 0.0f || dz == 0.0f) + a = Alpha; + else + a = CGeneral::GetATanOfXY(Length, dz); + 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, DEFAULT_NEAR); + + if(CamRoof2 > TargetCoors.z + 3.5f) + CamRoof2 = TargetCoors.z + 3.5f; + + float dz = CamRoof2 + 1.5f - TargetCoors.z; + float a; + if(Length == 0.0f || dz == 0.0f) + a = Alpha; + else + a = CGeneral::GetATanOfXY(Length, dz); + 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, DEFAULT_NEAR); + } + + WellBufferMe(LastTargetAlphaWithCollisionOn, &Alpha, &AlphaSpeed, LastTopAlphaSpeed, LastAlphaSpeedStep, true); + + Source.z = TargetCoors.z + Sin(Alpha + AlphaOffset)*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(Abs(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(Abs(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_CAM_ON_A_STRING){ + 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; + CVector TargetCoors = CameraTarget; + float BaseDist = Dimensions.Magnitude2D(); + + 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); + + Dist = Source - TargetCoors; + + 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 = nil; + + 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; +} + +void +CCam::Process_TopDown(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar) +{ + FOV = DefaultFOV; + + if(!CamTargetEntity->IsVehicle()) + return; + + float Dist; + float HeightTarget = 0.0f; + static float AdjustHeightTargetMoveBuffer = 0.0f; + static float AdjustHeightTargetMoveSpeed = 0.0f; + static float NearClipDistance = 1.5f; + const float FarClipDistance = 200.0f; + CVector TargetFront, Target; + CVector TestSource, TestTarget; + CColPoint colPoint; + CEntity *entity; + + TargetFront = CameraTarget; + TargetFront.x += 18.0f*CamTargetEntity->GetForward().x*SpeedVar; + TargetFront.y += 18.0f*CamTargetEntity->GetForward().y*SpeedVar; + + if(ResetStatics){ + AdjustHeightTargetMoveBuffer = 0.0f; + AdjustHeightTargetMoveSpeed = 0.0f; + } + + float f = Pow(0.8f, 4.0f); + Target = f*CameraTarget + (1.0f-f)*TargetFront; + if(Mode == MODE_GTACLASSIC) + SpeedVar = TargetSpeedVar; + Source = Target + CVector(0.0f, 0.0f, (40.0f*SpeedVar + 30.0f)*0.8f); + // What is this? looks horrible + if(Mode == MODE_GTACLASSIC) + Source.x += (uint8)(100.0f*CameraTarget.x)/500.0f; + + TestSource = Source; + TestTarget = TestSource; + TestTarget.z = Target.z; + if(CWorld::ProcessLineOfSight(TestTarget, TestSource, colPoint, entity, true, false, false, false, false, false, false)){ + if(Source.z < colPoint.point.z+3.0f) + HeightTarget = colPoint.point.z+3.0f - Source.z; + }else{ + TestSource = Source; + TestTarget = TestSource; + TestTarget.z += 10.0f; + if(CWorld::ProcessLineOfSight(TestTarget, TestSource, colPoint, entity, true, false, false, false, false, false, false)) + if(Source.z < colPoint.point.z+3.0f) + HeightTarget = colPoint.point.z+3.0f - Source.z; + } + WellBufferMe(HeightTarget, &AdjustHeightTargetMoveBuffer, &AdjustHeightTargetMoveSpeed, 0.2f, 0.02f, false); + Source.z += AdjustHeightTargetMoveBuffer; + + if(RwCameraGetFarClipPlane(Scene.camera) > FarClipDistance) + RwCameraSetFarClipPlane(Scene.camera, FarClipDistance); + RwCameraSetNearClipPlane(Scene.camera, NearClipDistance); + + Front = CVector(-0.01f, -0.01f, -1.0f); // look down + Front.Normalise(); + Dist = (Source - CameraTarget).Magnitude(); + m_cvecTargetCoorsForFudgeInter = Dist*Front + Source; + Up = CVector(0.0f, 1.0f, 0.0f); + + ResetStatics = false; +} + +void +CCam::AvoidWallsTopDownPed(const CVector &TargetCoors, const CVector &Offset, float *Adjuster, float *AdjusterSpeed, float yDistLimit) +{ + float Target = 0.0f; + float MaxSpeed = 0.13f; + float Acceleration = 0.015f; + float SpeedMult; + float dy; + CVector TestPoint2; + CVector TestPoint1; + CColPoint colPoint; + CEntity *entity; + + TestPoint2 = TargetCoors + Offset; + TestPoint1 = TargetCoors; + TestPoint1.z = TestPoint2.z; + if(CWorld::ProcessLineOfSight(TestPoint1, TestPoint2, colPoint, entity, true, false, false, false, false, false, false)){ + // What is this even? + dy = TestPoint1.y - colPoint.point.y; + if(dy > yDistLimit) + dy = yDistLimit; + SpeedMult = yDistLimit - Abs(dy/yDistLimit); + + Target = 2.5f; + MaxSpeed += SpeedMult*0.3f; + Acceleration += SpeedMult*0.03f; + } + WellBufferMe(Target, Adjuster, AdjusterSpeed, MaxSpeed, Acceleration, false); +} + +void +CCam::Process_TopDownPed(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + float Dist; + float HeightTarget; + static int NumPedPosCountsSoFar = 0; + static float PedAverageSpeed = 0.0f; + static float AdjustHeightTargetMoveBuffer = 0.0f; + static float AdjustHeightTargetMoveSpeed = 0.0f; + static float PedSpeedSoFar = 0.0f; + static float FarClipDistance = 200.0f; + static float NearClipDistance = 1.5f; + static float TargetAdjusterForSouth = 0.0f; + static float TargetAdjusterSpeedForSouth = 0.0f; + static float TargetAdjusterForNorth = 0.0f; + static float TargetAdjusterSpeedForNorth = 0.0f; + static float TargetAdjusterForEast = 0.0f; + static float TargetAdjusterSpeedForEast = 0.0f; + static float TargetAdjusterForWest = 0.0f; + static float TargetAdjusterSpeedForWest = 0.0f; + static CVector PreviousPlayerMoveSpeedVec; + CVector TargetCoors, PlayerMoveSpeed; + CVector TestSource, TestTarget; + CColPoint colPoint; + CEntity *entity; + + FOV = DefaultFOV; + TargetCoors = CameraTarget; + PlayerMoveSpeed = ((CPed*)CamTargetEntity)->GetMoveSpeed(); + + if(ResetStatics){ + PreviousPlayerMoveSpeedVec = PlayerMoveSpeed; + AdjustHeightTargetMoveBuffer = 0.0f; + AdjustHeightTargetMoveSpeed = 0.0f; + NumPedPosCountsSoFar = 0; + PedSpeedSoFar = 0.0f; + PedAverageSpeed = 0.0f; + TargetAdjusterForWest = 0.0f; + TargetAdjusterSpeedForWest = 0.0f; + TargetAdjusterForEast = 0.0f; + TargetAdjusterSpeedForEast = 0.0f; + TargetAdjusterForNorth = 0.0f; + TargetAdjusterSpeedForNorth = 0.0f; + TargetAdjusterForSouth = 0.0f; + TargetAdjusterSpeedForSouth = 0.0f; + } + + if(RwCameraGetFarClipPlane(Scene.camera) > FarClipDistance) + RwCameraSetFarClipPlane(Scene.camera, FarClipDistance); + RwCameraSetNearClipPlane(Scene.camera, NearClipDistance); + + // Average ped speed + NumPedPosCountsSoFar++; + PedSpeedSoFar += PlayerMoveSpeed.Magnitude(); + if(NumPedPosCountsSoFar == 5){ + PedAverageSpeed = 0.4f*PedAverageSpeed + 0.6*(PedSpeedSoFar/5.0f); + NumPedPosCountsSoFar = 0; + PedSpeedSoFar = 0.0f; + } + PreviousPlayerMoveSpeedVec = PlayerMoveSpeed; + + // Zoom out depending on speed + if(PedAverageSpeed > 0.01f && PedAverageSpeed <= 0.04f) + HeightTarget = 2.5f; + else if(PedAverageSpeed > 0.04f && PedAverageSpeed <= 0.145f) + HeightTarget = 4.5f; + else if(PedAverageSpeed > 0.145f) + HeightTarget = 7.0f; + else + HeightTarget = 0.0f; + + // Zoom out if locked on target is far away + if(FindPlayerPed()->m_pPointGunAt){ + Dist = (FindPlayerPed()->m_pPointGunAt->GetPosition() - CameraTarget).Magnitude2D(); + if(Dist > 6.0f) + HeightTarget = Max(HeightTarget, Dist/22.0f*37.0f); + } + + Source = TargetCoors + CVector(0.0f, -1.0f, 9.0f); + + // Collision checks + entity = nil; + TestSource = TargetCoors + CVector(0.0f, -1.0f, 9.0f); + TestTarget = TestSource; + TestTarget.z = TargetCoors.z; + if(CWorld::ProcessLineOfSight(TestTarget, TestSource, colPoint, entity, true, false, false, false, false, false, false)){ + if(TargetCoors.z+9.0f+HeightTarget < colPoint.point.z+3.0f) + HeightTarget = colPoint.point.z+3.0f - (TargetCoors.z+9.0f); + }else{ + TestSource = TargetCoors + CVector(0.0f, -1.0f, 9.0f); + TestTarget = TestSource; + TestSource.z += HeightTarget; + TestTarget.z = TestSource.z + 10.0f; + if(CWorld::ProcessLineOfSight(TestTarget, TestSource, colPoint, entity, true, false, false, false, false, false, false)){ + if(TargetCoors.z+9.0f+HeightTarget < colPoint.point.z+3.0f) + HeightTarget = colPoint.point.z+3.0f - (TargetCoors.z+9.0f); + } + } + + WellBufferMe(HeightTarget, &AdjustHeightTargetMoveBuffer, &AdjustHeightTargetMoveSpeed, 0.3f, 0.03f, false); + Source.z += AdjustHeightTargetMoveBuffer; + + // Wall checks + AvoidWallsTopDownPed(TargetCoors, CVector(0.0f, -3.0f, 3.0f), &TargetAdjusterForSouth, &TargetAdjusterSpeedForSouth, 1.0f); + Source.y += TargetAdjusterForSouth; + AvoidWallsTopDownPed(TargetCoors, CVector(0.0f, 3.0f, 3.0f), &TargetAdjusterForNorth, &TargetAdjusterSpeedForNorth, 1.0f); + Source.y -= TargetAdjusterForNorth; + // BUG: east and west flipped + AvoidWallsTopDownPed(TargetCoors, CVector(3.0f, 0.0f, 3.0f), &TargetAdjusterForWest, &TargetAdjusterSpeedForWest, 1.0f); + Source.x -= TargetAdjusterForWest; + AvoidWallsTopDownPed(TargetCoors, CVector(-3.0f, 0.0f, 3.0f), &TargetAdjusterForEast, &TargetAdjusterSpeedForEast, 1.0f); + Source.x += TargetAdjusterForEast; + + TargetCoors.y = Source.y + 1.0f; + TargetCoors.y += TargetAdjusterForSouth; + TargetCoors.x += TargetAdjusterForEast; + TargetCoors.x -= TargetAdjusterForWest; + + Front = TargetCoors - Source; + Front.Normalise(); +#ifdef FIX_BUGS + if(Front.x == 0.0f && Front.y == 0.0f) + Front.y = 0.0001f; +#else + // someone used = instead of == in the above check by accident + Front.x = 0.0f; +#endif + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Up = CrossProduct(Front, CVector(-1.0f, 0.0f, 0.0f)); + Up.Normalise(); + + ResetStatics = false; +} + +// Identical to M16 +void +CCam::Process_Rocket(const CVector &CameraTarget, float, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + static bool FailedTestTwelveFramesAgo = false; + RwV3d HeadPos; + CVector TargetCoors; + + FOV = DefaultFOV; + TargetCoors = CameraTarget; + + if(ResetStatics){ + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + FailedTestTwelveFramesAgo = false; + // static DPadVertical unused + // static DPadHorizontal unused + m_bCollisionChecksOn = true; + ResetStatics = false; + } + + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= 0.19f*Cos(m_fInitialPlayerOrientation); + Source.y -= 0.19f*Sin(m_fInitialPlayerOrientation); + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; + if(MouseX != 0.0f || MouseY != 0.0f){ + UseMouse = true; + LookLeftRight = -3.0f*MouseX; + LookUpDown = 4.0f*MouseY; + }else{ + LookLeftRight = -CPad::GetPad(0)->SniperModeLookLeftRight(); + LookUpDown = CPad::GetPad(0)->SniperModeLookUpDown(); + } + if(UseMouse){ + Beta += TheCamera.m_fMouseAccelHorzntl * LookLeftRight * FOV/80.0f; + Alpha += TheCamera.m_fMouseAccelVertical * LookUpDown * FOV/80.0f; + }else{ + float xdir = LookLeftRight < 0.0f ? -1.0f : 1.0f; + float ydir = LookUpDown < 0.0f ? -1.0f : 1.0f; + Beta += SQR(LookLeftRight/100.0f)*xdir*0.8f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += SQR(LookUpDown/150.0f)*ydir*1.0f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + } + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + TargetCoors.x = 3.0f * Cos(Alpha) * Cos(Beta) + Source.x; + TargetCoors.y = 3.0f * Cos(Alpha) * Sin(Beta) + Source.y; + TargetCoors.z = 3.0f * Sin(Alpha) + Source.z; + Front = TargetCoors - Source; + Front.Normalise(); + Source += Front*0.4f; + + if(m_bCollisionChecksOn){ + if(!CWorld::GetIsLineOfSightClear(TargetCoors, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + CVector TestPoint; + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta + DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta + DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta - DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta - DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else + FailedTestTwelveFramesAgo = false; + } + } + } + + if(FailedTestTwelveFramesAgo) + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source -= Front*0.4f; + + GetVectorsReadyForRW(); + float Rotation = CGeneral::GetATanOfXY(Front.x, Front.y) - HALFPI; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Rotation; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Rotation; +} + +// Identical to Rocket +void +CCam::Process_M16_1stPerson(const CVector &CameraTarget, float, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + static bool FailedTestTwelveFramesAgo = false; + RwV3d HeadPos; + CVector TargetCoors; + + FOV = DefaultFOV; + TargetCoors = CameraTarget; + + if(ResetStatics){ + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + FailedTestTwelveFramesAgo = false; + // static DPadVertical unused + // static DPadHorizontal unused + m_bCollisionChecksOn = true; + ResetStatics = false; + } + +#if GTA_VERSION < GTA3_PC_11 + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= 0.19f*Cos(m_fInitialPlayerOrientation); + Source.y -= 0.19f*Sin(m_fInitialPlayerOrientation); +#endif + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; + if(MouseX != 0.0f || MouseY != 0.0f){ + UseMouse = true; + LookLeftRight = -3.0f*MouseX; + LookUpDown = 4.0f*MouseY; + }else{ + LookLeftRight = -CPad::GetPad(0)->SniperModeLookLeftRight(); + LookUpDown = CPad::GetPad(0)->SniperModeLookUpDown(); + } + if(UseMouse){ + Beta += TheCamera.m_fMouseAccelHorzntl * LookLeftRight * FOV/80.0f; + Alpha += TheCamera.m_fMouseAccelVertical * LookUpDown * FOV/80.0f; + }else{ + float xdir = LookLeftRight < 0.0f ? -1.0f : 1.0f; + float ydir = LookUpDown < 0.0f ? -1.0f : 1.0f; + Beta += SQR(LookLeftRight/100.0f)*xdir*0.8f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += SQR(LookUpDown/150.0f)*ydir*1.0f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + } + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + +#if GTA_VERSION >= GTA3_PC_11 + HeadPos.x = 0.0f; + HeadPos.y = 0.0f; + HeadPos.z = 0.0f; + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= 0.19f * Cos(m_fInitialPlayerOrientation); + Source.y -= 0.19f * Sin(m_fInitialPlayerOrientation); +#endif + + TargetCoors.x = 3.0f * Cos(Alpha) * Cos(Beta) + Source.x; + TargetCoors.y = 3.0f * Cos(Alpha) * Sin(Beta) + Source.y; + TargetCoors.z = 3.0f * Sin(Alpha) + Source.z; + Front = TargetCoors - Source; + Front.Normalise(); + Source += Front*0.4f; + + if(m_bCollisionChecksOn){ + if(!CWorld::GetIsLineOfSightClear(TargetCoors, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + CVector TestPoint; + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta + DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta + DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta - DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta - DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else + FailedTestTwelveFramesAgo = false; + } + } + } + + if(FailedTestTwelveFramesAgo) + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source -= Front*0.4f; + + GetVectorsReadyForRW(); + float Rotation = CGeneral::GetATanOfXY(Front.x, Front.y) - HALFPI; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Rotation; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Rotation; +} + +void +CCam::Process_1stPerson(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + static float DontLookThroughWorldFixer = 0.0f; + CVector TargetCoors; + + FOV = DefaultFOV; + TargetCoors = CameraTarget; + if(CamTargetEntity->m_rwObject == nil) + return; + + if(ResetStatics){ + Beta = TargetOrientation; + Alpha = 0.0f; + m_fInitialPlayerOrientation = TargetOrientation; + if(CamTargetEntity->IsPed()){ + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + } + DontLookThroughWorldFixer = 0.0f; + } + + if(CamTargetEntity->IsPed()){ + static bool FailedTestTwelveFramesAgo = false; + RwV3d HeadPos; + + TargetCoors = CameraTarget; + + if(ResetStatics){ + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + FailedTestTwelveFramesAgo = false; + // static DPadVertical unused + // static DPadHorizontal unused + m_bCollisionChecksOn = true; + ResetStatics = false; + } + + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= 0.19f*Cos(m_fInitialPlayerOrientation); + Source.y -= 0.19f*Sin(m_fInitialPlayerOrientation); + + float LookLeftRight, LookUpDown; + LookLeftRight = -CPad::GetPad(0)->LookAroundLeftRight(); + LookUpDown = CPad::GetPad(0)->LookAroundUpDown(); + float xdir = LookLeftRight < 0.0f ? -1.0f : 1.0f; + float ydir = LookUpDown < 0.0f ? -1.0f : 1.0f; + Beta += SQR(LookLeftRight/100.0f)*xdir*0.8f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += SQR(LookUpDown/150.0f)*ydir*1.0f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + TargetCoors.x = 3.0f * Cos(Alpha) * Cos(Beta) + Source.x; + TargetCoors.y = 3.0f * Cos(Alpha) * Sin(Beta) + Source.y; + TargetCoors.z = 3.0f * Sin(Alpha) + Source.z; + Front = TargetCoors - Source; + Front.Normalise(); + Source += Front*0.4f; + + if(m_bCollisionChecksOn){ + if(!CWorld::GetIsLineOfSightClear(TargetCoors, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + CVector TestPoint; + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta + DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta + DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta - DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta - DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else + FailedTestTwelveFramesAgo = false; + } + } + } + + if(FailedTestTwelveFramesAgo) + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source -= Front*0.4f; + + GetVectorsReadyForRW(); + float Rotation = CGeneral::GetATanOfXY(Front.x, Front.y) - HALFPI; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Rotation; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Rotation; + }else{ + assert(CamTargetEntity->IsVehicle()); + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(CamTargetEntity->GetModelIndex()); + CVector CamPos = mi->GetFrontSeatPosn(); + CamPos.x = 0.0f; + CamPos.y += 0.08f; + CamPos.z += 0.62f; + FOV = 60.0f; + Source = Multiply3x3(CamTargetEntity->GetMatrix(), CamPos); + Source += CamTargetEntity->GetPosition(); + if(((CVehicle*)CamTargetEntity)->IsBoat()) + Source.z += 0.5f; + + if(((CVehicle*)CamTargetEntity)->IsUpsideDown()){ + if(DontLookThroughWorldFixer < 0.5f) + DontLookThroughWorldFixer += 0.03f; + else + DontLookThroughWorldFixer = 0.5f; + }else{ + if(DontLookThroughWorldFixer < 0.0f) +#ifdef FIX_BUGS + DontLookThroughWorldFixer += 0.03f; +#else + DontLookThroughWorldFixer -= 0.03f; +#endif + else + DontLookThroughWorldFixer = 0.0f; + } + Source.z += DontLookThroughWorldFixer; + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Up = CamTargetEntity->GetUp(); + Up.Normalise(); + CVector Right = CrossProduct(Front, Up); + Right.Normalise(); + Up = CrossProduct(Right, Front); + Up.Normalise(); + } + + ResetStatics = false; +} + +static CVector vecHeadCamOffset(0.06f, 0.05f, 0.0f); + +void +CCam::Process_1rstPersonPedOnPC(const CVector&, float TargetOrientation, float, float) +{ + // static int DontLookThroughWorldFixer = 0; // unused + static CVector InitialHeadPos; + + if(Mode != MODE_SNIPER_RUNABOUT) + FOV = DefaultFOV; + TheCamera.m_1rstPersonRunCloseToAWall = false; + if(CamTargetEntity->m_rwObject == nil) + return; + + if(CamTargetEntity->IsPed()){ + // static bool FailedTestTwelveFramesAgo = false; // unused + CVector HeadPos = vecHeadCamOffset; + CVector TargetCoors; + + ((CPed*)CamTargetEntity)->TransformToNode(HeadPos, PED_HEAD); + // This is done on PC, but checking for the clump frame is not necessary apparently +/* + RwFrame *frm = ((CPed*)CamTargetEntity)->m_pFrames[PED_HEAD]->frame; + while(frm){ + RwV3dTransformPoints(&HeadPos, &HeadPos, 1, RwFrameGetMatrix(frm)); + frm = RwFrameGetParent(frm); + if(frm == RpClumpGetFrame(CamTargetEntity->GetClump())) + frm = nil; + } +*/ + + if(ResetStatics){ + Beta = TargetOrientation; + Alpha = 0.0f; + m_fInitialPlayerOrientation = TargetOrientation; + if(CamTargetEntity->IsPed()){ // useless check + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + // FailedTestTwelveFramesAgo = false; + m_bCollisionChecksOn = true; + } + // DontLookThroughWorldFixer = false; + m_vecBufferedPlayerBodyOffset = HeadPos; + InitialHeadPos = HeadPos; + } + + m_vecBufferedPlayerBodyOffset.y = HeadPos.y; + + if(TheCamera.m_bHeadBob){ + m_vecBufferedPlayerBodyOffset.x = + TheCamera.m_fGaitSwayBuffer * m_vecBufferedPlayerBodyOffset.x + + (1.0f-TheCamera.m_fGaitSwayBuffer) * HeadPos.x; + m_vecBufferedPlayerBodyOffset.z = + TheCamera.m_fGaitSwayBuffer * m_vecBufferedPlayerBodyOffset.z + + (1.0f-TheCamera.m_fGaitSwayBuffer) * HeadPos.z; + HeadPos = (CamTargetEntity->GetMatrix() * m_vecBufferedPlayerBodyOffset); + }else{ + float HeadDelta = (HeadPos - InitialHeadPos).Magnitude2D(); + CVector Fwd = CamTargetEntity->GetForward(); + Fwd.z = 0.0f; + Fwd.Normalise(); + HeadPos = HeadDelta*1.23f*Fwd + CamTargetEntity->GetPosition(); + HeadPos.z += 0.59f; + } + Source = HeadPos; + + // unused: + // ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(MidPos, PED_MID); + // Source - MidPos; + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; + if(MouseX != 0.0f || MouseY != 0.0f){ + UseMouse = true; + LookLeftRight = -3.0f*MouseX; + LookUpDown = 4.0f*MouseY; + }else{ + LookLeftRight = -CPad::GetPad(0)->LookAroundLeftRight(); + LookUpDown = CPad::GetPad(0)->LookAroundUpDown(); + } + if(UseMouse){ + Beta += TheCamera.m_fMouseAccelHorzntl * LookLeftRight * FOV/80.0f; + Alpha += TheCamera.m_fMouseAccelVertical * LookUpDown * FOV/80.0f; + }else{ + float xdir = LookLeftRight < 0.0f ? -1.0f : 1.0f; + float ydir = LookUpDown < 0.0f ? -1.0f : 1.0f; + Beta += SQR(LookLeftRight/100.0f)*xdir*0.8f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += SQR(LookUpDown/150.0f)*ydir*1.0f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + } + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + TargetCoors.x = 3.0f * Cos(Alpha) * Cos(Beta) + Source.x; + TargetCoors.y = 3.0f * Cos(Alpha) * Sin(Beta) + Source.y; + TargetCoors.z = 3.0f * Sin(Alpha) + Source.z; + Front = TargetCoors - Source; + Front.Normalise(); + Source += Front*0.4f; + + TheCamera.m_AlphaForPlayerAnim1rstPerson = Alpha; + + GetVectorsReadyForRW(); + + float Heading = Front.Heading(); + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Heading; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Heading; + TheCamera.pTargetEntity->SetHeading(Heading); + TheCamera.pTargetEntity->GetMatrix().UpdateRW(); + + if(Mode == MODE_SNIPER_RUNABOUT){ + // no mouse wheel FOV buffering here like in normal sniper mode + if(CPad::GetPad(0)->SniperZoomIn() || CPad::GetPad(0)->SniperZoomOut()){ + if(CPad::GetPad(0)->SniperZoomOut()) + FOV *= (255.0f*CTimer::GetTimeStep() + 10000.0f) / 10000.0f; + else + FOV /= (255.0f*CTimer::GetTimeStep() + 10000.0f) / 10000.0f; + } + + TheCamera.SetMotionBlur(180, 255, 180, 120, MOTION_BLUR_SNIPER); + + if(FOV > DefaultFOV) + FOV = DefaultFOV; + if(FOV < 15.0f) + FOV = 15.0f; + } + } + + ResetStatics = false; + RwCameraSetNearClipPlane(Scene.camera, 0.05f); +} + +void +CCam::Process_Sniper(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + static bool FailedTestTwelveFramesAgo = false; + RwV3d HeadPos; + CVector TargetCoors; + TargetCoors = CameraTarget; + + static float TargetFOV = 0.0f; + + if(ResetStatics){ + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + FailedTestTwelveFramesAgo = false; + // static DPadVertical unused + // static DPadHorizontal unused + m_bCollisionChecksOn = true; + FOVSpeed = 0.0f; + TargetFOV = FOV; + ResetStatics = false; + } + + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= 0.19f*Cos(m_fInitialPlayerOrientation); + Source.y -= 0.19f*Sin(m_fInitialPlayerOrientation); + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; + if(MouseX != 0.0f || MouseY != 0.0f){ + UseMouse = true; + LookLeftRight = -3.0f*MouseX; + LookUpDown = 4.0f*MouseY; + }else{ + LookLeftRight = -CPad::GetPad(0)->SniperModeLookLeftRight(); + LookUpDown = CPad::GetPad(0)->SniperModeLookUpDown(); + } + if(UseMouse){ + Beta += TheCamera.m_fMouseAccelHorzntl * LookLeftRight * FOV/80.0f; + Alpha += TheCamera.m_fMouseAccelVertical * LookUpDown * FOV/80.0f; + }else{ + float xdir = LookLeftRight < 0.0f ? -1.0f : 1.0f; + float ydir = LookUpDown < 0.0f ? -1.0f : 1.0f; + Beta += SQR(LookLeftRight/100.0f)*xdir*0.8f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += SQR(LookUpDown/150.0f)*ydir*1.0f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + } + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + TargetCoors.x = 3.0f * Cos(Alpha) * Cos(Beta) + Source.x; + TargetCoors.y = 3.0f * Cos(Alpha) * Sin(Beta) + Source.y; + TargetCoors.z = 3.0f * Sin(Alpha) + Source.z; + + UseMouse = false; + int ZoomInButton = ControlsManager.GetMouseButtonAssociatedWithAction(PED_SNIPER_ZOOM_IN); + int ZoomOutButton = ControlsManager.GetMouseButtonAssociatedWithAction(PED_SNIPER_ZOOM_OUT); + if(ZoomInButton == rsMOUSEWHEELUPBUTTON || ZoomInButton == rsMOUSEWHEELDOWNBUTTON || ZoomOutButton == rsMOUSEWHEELUPBUTTON || ZoomOutButton == rsMOUSEWHEELDOWNBUTTON){ + if(CPad::GetPad(0)->GetMouseWheelUp() || CPad::GetPad(0)->GetMouseWheelDown()){ + if(CPad::GetPad(0)->SniperZoomIn()){ + TargetFOV = FOV - 10.0f; + UseMouse = true; + } + if(CPad::GetPad(0)->SniperZoomOut()){ + TargetFOV = FOV + 10.0f; + UseMouse = true; + } + } + } + if((CPad::GetPad(0)->SniperZoomIn() || CPad::GetPad(0)->SniperZoomOut()) && !UseMouse){ + if(CPad::GetPad(0)->SniperZoomOut()){ + FOV *= (255.0f*CTimer::GetTimeStep() + 10000.0f) / 10000.0f; + TargetFOV = FOV; + FOVSpeed = 0.0f; + }else{ + FOV /= (255.0f*CTimer::GetTimeStep() + 10000.0f) / 10000.0f; + TargetFOV = FOV; + FOVSpeed = 0.0f; + } + }else{ + if(Abs(TargetFOV - FOV) > 0.5f) + WellBufferMe(TargetFOV, &FOV, &FOVSpeed, 0.5f, 0.25f, false); + else + FOVSpeed = 0.0f; + } + + TheCamera.SetMotionBlur(180, 255, 180, 120, MOTION_BLUR_SNIPER); + + if(FOV > DefaultFOV) + FOV = DefaultFOV; + if(FOV < 15.0f) + FOV = 15.0f; + + Front = TargetCoors - Source; + Front.Normalise(); + Source += Front*0.4f; + + if(m_bCollisionChecksOn){ + if(!CWorld::GetIsLineOfSightClear(TargetCoors, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + CVector TestPoint; + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta + DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta + DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta - DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta - DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else + FailedTestTwelveFramesAgo = false; + } + } + } + + if(FailedTestTwelveFramesAgo) + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source -= Front*0.4f; + + GetVectorsReadyForRW(); + float Rotation = CGeneral::GetATanOfXY(Front.x, Front.y) - HALFPI; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Rotation; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Rotation; +} + +void +CCam::Process_Syphon(const CVector &CameraTarget, float, float, float) +{ + FOV = DefaultFOV; + + if(!CamTargetEntity->IsPed()) + return; + + static bool CameraObscured = false; + // unused FailedClippingTestPrevously + static float BetaOffset = DEGTORAD(18.0f); + // unused AngleToGoTo + // unused AngleToGoToSpeed + // unused DistBetweenPedAndPlayerPreviouslyOn + static float HeightDown = -0.5f; + static float PreviousDistForInter; + CVector TargetCoors; + CVector2D vDist; + float fDist, fAimingDist; + float TargetAlpha; + CColPoint colPoint; + CEntity *entity; + + TargetCoors = CameraTarget; + + if(TheCamera.Cams[TheCamera.ActiveCam].Mode != MODE_SYPHON) + return; + + vDist = Source - TargetCoors; + fDist = vDist.Magnitude(); + if(fDist == 0.0f) + Source = TargetCoors + CVector(1.0f, 1.0f, 0.0f); + else + Source = TargetCoors + CVector(vDist.x/fDist * 1.7f, vDist.y/fDist * 1.7f, 0.0f); + if(fDist > 1.7f) + fDist = 1.7f; + + Beta = CGeneral::GetATanOfXY(Source.x - TargetCoors.x, Source.y - TargetCoors.y); + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + float NewBeta = CGeneral::GetATanOfXY(TheCamera.m_cvecAimingTargetCoors.x - TargetCoors.x, TheCamera.m_cvecAimingTargetCoors.y - TargetCoors.y) + PI; + if(ResetStatics){ + CameraObscured = false; + float TestBeta1 = NewBeta - BetaOffset - Beta; + float TestBeta2 = NewBeta + BetaOffset - Beta; + MakeAngleLessThan180(TestBeta1); + MakeAngleLessThan180(TestBeta2); + if(Abs(TestBeta1) < Abs(TestBeta2)) + BetaOffset = -BetaOffset; + // some unuseds + ResetStatics = false; + } + Beta = NewBeta + BetaOffset; + Source = TargetCoors; + Source.x += 1.7f*Cos(Beta); + Source.y += 1.7f*Sin(Beta); + TargetCoors.z += m_fSyphonModeTargetZOffSet; + fAimingDist = (TheCamera.m_cvecAimingTargetCoors - TargetCoors).Magnitude2D(); + if(fAimingDist < 6.5f) + fAimingDist = 6.5f; + TargetAlpha = CGeneral::GetATanOfXY(fAimingDist, TheCamera.m_cvecAimingTargetCoors.z - TargetCoors.z); + while(TargetAlpha >= PI) TargetAlpha -= 2*PI; + while(TargetAlpha < -PI) TargetAlpha += 2*PI; + + // inlined + WellBufferMe(-TargetAlpha, &Alpha, &AlphaSpeed, 0.07f, 0.015f, true); + + Source.z += fDist*Sin(Alpha) + fDist*0.2f; + if(Source.z < TargetCoors.z + HeightDown) + Source.z = TargetCoors.z + HeightDown; + + CameraObscured = CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, false, false, true, false, true, true); + // PreviousDistForInter unused + if(CameraObscured){ + PreviousDistForInter = (TargetCoors - colPoint.point).Magnitude2D(); + Source = colPoint.point; + }else + PreviousDistForInter = 1.7f; + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + m_fMinDistAwayFromCamWhenInterPolating = Front.Magnitude2D(); + if(m_fMinDistAwayFromCamWhenInterPolating < 1.1f) + RwCameraSetNearClipPlane(Scene.camera, Max(m_fMinDistAwayFromCamWhenInterPolating - 0.35f, 0.05f)); + Front.Normalise(); + GetVectorsReadyForRW(); +} + +void +CCam::Process_Syphon_Crim_In_Front(const CVector &CameraTarget, float, float, float) +{ + FOV = DefaultFOV; + + if(!CamTargetEntity->IsPed()) + return; + + CVector TargetCoors = CameraTarget; + CVector vDist; + float fDist, TargetDist; + float zOffset; + float AimingAngle; + CColPoint colPoint; + CEntity *entity; + + TargetDist = TheCamera.m_fPedZoomValueSmooth * 0.5f + 4.0f; + vDist = Source - TargetCoors; + fDist = vDist.Magnitude2D(); + zOffset = TargetDist - 2.65f; + if(zOffset < 0.0f) + zOffset = 0.0f; + if(zOffset == 0.0f) + Source = TargetCoors + CVector(1.0f, 1.0f, zOffset); + else + Source = TargetCoors + CVector(vDist.x/fDist*TargetDist, vDist.y/fDist*TargetDist, zOffset); + + AimingAngle = CGeneral::GetATanOfXY(TheCamera.m_cvecAimingTargetCoors.x - TargetCoors.x, TheCamera.m_cvecAimingTargetCoors.y - TargetCoors.y); + while(AimingAngle >= PI) AimingAngle -= 2*PI; + while(AimingAngle < -PI) AimingAngle += 2*PI; + + if(ResetStatics){ + if(AimingAngle > 0.0f) + m_fPlayerInFrontSyphonAngleOffSet = -m_fPlayerInFrontSyphonAngleOffSet; + ResetStatics = false; + } + + if(TheCamera.PlayerWeaponMode.Mode == MODE_SYPHON) + Beta = AimingAngle + m_fPlayerInFrontSyphonAngleOffSet; + + Source.x = TargetCoors.x; + Source.y = TargetCoors.y; + Source.x += Cos(Beta) * TargetDist; + Source.y += Sin(Beta) * TargetDist; + + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, false, false, true, false, true, true)){ + Beta = CGeneral::GetATanOfXY(Source.x - TargetCoors.x, Source.y - TargetCoors.y); + fDist = (TargetCoors - colPoint.point).Magnitude2D(); + Source.x = TargetCoors.x; + Source.y = TargetCoors.y; + Source.x += Cos(Beta) * fDist; + Source.y += Sin(Beta) * fDist; + } + + TargetCoors = CameraTarget; + TargetCoors.z += m_fSyphonModeTargetZOffSet; + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + GetVectorsReadyForRW(); +} + +void +CCam::Process_BehindBoat(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsVehicle()){ + ResetStatics = false; + return; + } + + CVector TargetCoors = CameraTarget; + float DeltaBeta = 0.0f; + static CColPoint colPoint; + CEntity *entity; + static float TargetWhenChecksWereOn = 0.0f; + static float CenterObscuredWhenChecksWereOn = 0.0f; + static float WaterZAddition = 2.75f; + float WaterLevel = 0.0f; + float s, c; + + Beta = CGeneral::GetATanOfXY(TargetCoors.x - Source.x, TargetCoors.y - Source.y); + FOV = DefaultFOV; + + if(ResetStatics){ + CenterObscuredWhenChecksWereOn = 0.0f; + TargetWhenChecksWereOn = 0.0f; + Beta = TargetOrientation + PI; + } + + CWaterLevel::GetWaterLevelNoWaves(TargetCoors.x, TargetCoors.y, TargetCoors.z, &WaterLevel); + WaterLevel += WaterZAddition; + static float FixerForGoingBelowGround = 0.4f; + if(-FixerForGoingBelowGround < TargetCoors.z-WaterLevel) + WaterLevel += TargetCoors.z-WaterLevel - FixerForGoingBelowGround; + + bool Obscured; + if(m_bCollisionChecksOn || ResetStatics){ + CVector TestPoint; + // Weird calculations here, also casting bool to float... + c = Cos(TargetOrientation); + s = Sin(TargetOrientation); + TestPoint = TheCamera.CarZoomValueSmooth * CVector(-c, -s, 0.0f) + + (TheCamera.CarZoomValueSmooth+7.0f) * CVector(-c, -s, 0.0f) + + TargetCoors; + TestPoint.z = WaterLevel + TheCamera.CarZoomValueSmooth; + float Test1 = CWorld::GetIsLineOfSightClear(TestPoint, TargetCoors, true, false, false, true, false, true, true); + + c = Cos(TargetOrientation + 0.8f); + s = Sin(TargetOrientation + DEGTORAD(40.0f)); + TestPoint = TheCamera.CarZoomValueSmooth * CVector(-c, -s, 0.0f) + + (TheCamera.CarZoomValueSmooth+7.0f) * CVector(-c, -s, 0.0f) + + TargetCoors; + TestPoint.z = WaterLevel + TheCamera.CarZoomValueSmooth; + float Test2 = CWorld::GetIsLineOfSightClear(TestPoint, TargetCoors, true, false, false, true, false, true, true); + + c = Cos(TargetOrientation - 0.8); + s = Sin(TargetOrientation - DEGTORAD(40.0f)); + TestPoint = TheCamera.CarZoomValueSmooth * CVector(-c, -s, 0.0f) + + (TheCamera.CarZoomValueSmooth+7.0f) * CVector(-c, -s, 0.0f) + + TargetCoors; + TestPoint.z = WaterLevel + TheCamera.CarZoomValueSmooth; + float Test3 = CWorld::GetIsLineOfSightClear(TestPoint, TargetCoors, true, false, false, true, false, true, true); + + if(Test2 == 0.0f){ + DeltaBeta = TargetOrientation - Beta - DEGTORAD(40.0f); + if(ResetStatics) + Beta = TargetOrientation - DEGTORAD(40.0f); + }else if(Test3 == 0.0f){ + DeltaBeta = TargetOrientation - Beta + DEGTORAD(40.0f); + if(ResetStatics) + Beta = TargetOrientation + DEGTORAD(40.0f); + }else if(Test1 == 0.0f){ + DeltaBeta = 0.0f; + }else if(Test2 != 0.0f && Test3 != 0.0f && Test1 != 0.0f){ + if(ResetStatics) + Beta = TargetOrientation; + DeltaBeta = TargetOrientation - Beta; + } + + c = Cos(Beta); + s = Sin(Beta); + TestPoint.x = TheCamera.CarZoomValueSmooth * -c + + (TheCamera.CarZoomValueSmooth + 7.0f) * -c + + TargetCoors.x; + TestPoint.y = TheCamera.CarZoomValueSmooth * -s + + (TheCamera.CarZoomValueSmooth + 7.0f) * -s + + TargetCoors.y; + TestPoint.z = WaterLevel + TheCamera.CarZoomValueSmooth; + Obscured = CWorld::ProcessLineOfSight(TestPoint, TargetCoors, colPoint, entity, true, false, false, true, false, true, true); + CenterObscuredWhenChecksWereOn = Obscured; + + // now DeltaBeta == TargetWhenChecksWereOn - Beta, which we need for WellBufferMe below + TargetWhenChecksWereOn = DeltaBeta + Beta; + }else{ + // DeltaBeta = TargetWhenChecksWereOn - Beta; // unneeded since we don't inline WellBufferMe + Obscured = CenterObscuredWhenChecksWereOn != 0.0f; + } + + if(Obscured){ + CWorld::ProcessLineOfSight(Source, TargetCoors, colPoint, entity, true, false, false, true, false, true, true); + Source = colPoint.point; + }else{ + // inlined + WellBufferMe(TargetWhenChecksWereOn, &Beta, &BetaSpeed, 0.07f, 0.015f, true); + + s = Sin(Beta); + c = Cos(Beta); + Source = TheCamera.CarZoomValueSmooth * CVector(-c, -s, 0.0f) + + (TheCamera.CarZoomValueSmooth+7.0f) * CVector(-c, -s, 0.0f) + + TargetCoors; + Source.z = WaterLevel + TheCamera.CarZoomValueSmooth; + } + + if(TheCamera.CarZoomValueSmooth < 0.05f){ + static float AmountUp = 2.2f; + TargetCoors.z += AmountUp * (0.0f - TheCamera.CarZoomValueSmooth); + } + TargetCoors.z += TheCamera.CarZoomValueSmooth + 0.5f; + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + GetVectorsReadyForRW(); + ResetStatics = false; +} + +void +CCam::Process_Fight_Cam(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + FOV = DefaultFOV; + float BetaLeft, BetaRight, DeltaBetaLeft, DeltaBetaRight; + float BetaFix; + float Dist; + float BetaMaxSpeed = 0.015f; + float BetaAcceleration = 0.007f; + static bool PreviouslyFailedBuildingChecks = false; + float TargetCamHeight; + CVector TargetCoors; + + m_fMinDistAwayFromCamWhenInterPolating = 4.0f; + Front = Source - CameraTarget; + Beta = CGeneral::GetATanOfXY(Front.x, Front.y); + while(TargetOrientation >= PI) TargetOrientation -= 2*PI; + while(TargetOrientation < -PI) TargetOrientation += 2*PI; + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + // Figure out Beta + BetaLeft = TargetOrientation - HALFPI; + BetaRight = TargetOrientation + HALFPI; + DeltaBetaLeft = Beta - BetaLeft; + DeltaBetaRight = Beta - BetaRight; + while(DeltaBetaLeft >= PI) DeltaBetaLeft -= 2*PI; + while(DeltaBetaLeft < -PI) DeltaBetaLeft += 2*PI; + while(DeltaBetaRight >= PI) DeltaBetaRight -= 2*PI; + while(DeltaBetaRight < -PI) DeltaBetaRight += 2*PI; + + if(ResetStatics){ + if(Abs(DeltaBetaLeft) < Abs(DeltaBetaRight)) + m_fTargetBeta = DeltaBetaLeft; + else + m_fTargetBeta = DeltaBetaRight; + m_fBufferedTargetOrientation = TargetOrientation; + m_fBufferedTargetOrientationSpeed = 0.0f; + m_bCollisionChecksOn = true; + BetaSpeed = 0.0f; + }else if(CPad::GetPad(0)->WeaponJustDown()){ + if(Abs(DeltaBetaLeft) < Abs(DeltaBetaRight)) + m_fTargetBeta = DeltaBetaLeft; + else + m_fTargetBeta = DeltaBetaRight; + } + + // Check collisions + BetaFix = 0.0f; + Dist = Front.Magnitude2D(); + if(m_bCollisionChecksOn || PreviouslyFailedBuildingChecks){ + BetaFix = GetPedBetaAngleForClearView(CameraTarget, Dist+0.25f, 0.0f, true, false, false, true, false); + if(BetaFix == 0.0f){ + BetaFix = GetPedBetaAngleForClearView(CameraTarget, Dist+0.5f, DEGTORAD(24.0f), true, false, false, true, false); + if(BetaFix == 0.0f) + BetaFix = GetPedBetaAngleForClearView(CameraTarget, Dist+0.5f, -DEGTORAD(24.0f), true, false, false, true, false); + } + } + if(BetaFix != 0.0f){ + BetaMaxSpeed = 0.1f; + PreviouslyFailedBuildingChecks = true; + BetaAcceleration = 0.025f; + m_fTargetBeta = Beta + BetaFix; + } + WellBufferMe(m_fTargetBeta, &Beta, &BetaSpeed, BetaMaxSpeed, BetaAcceleration, true); + + Source = CameraTarget + 4.0f*CVector(Cos(Beta), Sin(Beta), 0.0f); + Source.z -= 0.5f; + + WellBufferMe(TargetOrientation, &m_fBufferedTargetOrientation, &m_fBufferedTargetOrientationSpeed, 0.07f, 0.004f, true); + TargetCoors = CameraTarget + 0.5f*CVector(Cos(m_fBufferedTargetOrientation), Sin(m_fBufferedTargetOrientation), 0.0f); + + TargetCamHeight = CameraTarget.z - Source.z + Max(m_fPedBetweenCameraHeightOffset, m_fRoadOffSet + m_fDimensionOfHighestNearCar) - 0.5f; + if(TargetCamHeight > m_fCamBufferedHeight) + WellBufferMe(TargetCamHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.15f, 0.04f, false); + else + WellBufferMe(0.0f, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.08f, 0.0175f, false); + Source.z += m_fCamBufferedHeight; + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + Front.Normalise(); + GetVectorsReadyForRW(); + + ResetStatics = false; +} + +/* +// Spline format is this, but game doesn't seem to use any kind of struct: +struct Spline +{ + float numFrames; + struct { + float time; + float f[3]; // CVector for Vector spline + } frames[1]; // numFrames +}; +*/ + +// These two functions are pretty ugly + +#define MS(t) (uint32)((t)*1000.0f) + +void +FindSplinePathPositionFloat(float *out, float *spline, uint32 time, uint32 &marker) +{ + // marker is at time + uint32 numFrames = spline[0]; + uint32 timeDelta = MS(spline[marker] - spline[marker-4]); + uint32 endTime = MS(spline[4*(numFrames-1) + 1]); + if(time < endTime){ + bool canAdvance = true; + if((marker-1)/4 > numFrames){ + canAdvance = false; + marker = 4*(numFrames-1) + 1; + } + // skipping over small time deltas apparently? + while(timeDelta <= 75 && canAdvance){ + marker += 4; + if((marker-1)/4 > numFrames){ + canAdvance = false; + marker = 4*(numFrames-1) + 1; + } + timeDelta = (spline[marker] - spline[marker-4]) * 1000.0f; + } + } + float a = ((float)time - (float)MS(spline[marker-4])) / (float)MS(spline[marker] - spline[marker-4]); + a = Clamp(a, 0.0f, 1.0f); + float b = 1.0f - a; + *out = b*b*b * spline[marker-3] + + 3.0f*a*b*b * spline[marker-1] + + 3.0f*a*a*b * spline[marker+2] + + a*a*a * spline[marker+1]; +} + +void +FindSplinePathPositionVector(CVector *out, float *spline, uint32 time, uint32 &marker) +{ + // marker is at time + uint32 numFrames = spline[0]; + uint32 timeDelta = MS(spline[marker] - spline[marker-10]); + uint32 endTime = MS(spline[10*(numFrames-1) + 1]); + if(time < endTime){ + bool canAdvance = true; + if((marker-1)/10 > numFrames){ + canAdvance = false; + marker = 10*(numFrames-1) + 1; + } + // skipping over small time deltas apparently? + while(timeDelta <= 75 && canAdvance){ + marker += 10; + if((marker-1)/10 > numFrames){ + canAdvance = false; + marker = 10*(numFrames-1) + 1; + } + timeDelta = (spline[marker] - spline[marker-10]) * 1000.0f; + } + } + + if((marker-1)/10 > numFrames){ + printf("Arraymarker %i \n", marker); + printf("Path zero %i \n", numFrames); + } + + float a = ((float)time - (float)MS(spline[marker-10])) / (float)MS(spline[marker] - spline[marker-10]); + a = Clamp(a, 0.0f, 1.0f); + float b = 1.0f - a; + out->x = + b*b*b * spline[marker-9] + + 3.0f*a*b*b * spline[marker-3] + + 3.0f*a*a*b * spline[marker+4] + + a*a*a * spline[marker+1]; + out->y = + b*b*b * spline[marker-8] + + 3.0f*a*b*b * spline[marker-2] + + 3.0f*a*a*b * spline[marker+5] + + a*a*a * spline[marker+2]; + out->z = + b*b*b * spline[marker-7] + + 3.0f*a*b*b * spline[marker-1] + + 3.0f*a*a*b * spline[marker+6] + + a*a*a * spline[marker+3]; + *out += TheCamera.m_vecCutSceneOffset; +} + +void +CCam::Process_FlyBy(const CVector&, float, float, float) +{ + float UpAngle = 0.0f; + static float FirstFOVValue = 0.0f; + static float PsuedoFOV; + static uint32 ArrayMarkerFOV; + static uint32 ArrayMarkerUp; + static uint32 ArrayMarkerSource; + static uint32 ArrayMarkerFront; + + if(TheCamera.m_bcutsceneFinished) + return; + + Up = CVector(0.0f, 0.0f, 1.0f); + if(TheCamera.m_bStartingSpline) + m_fTimeElapsedFloat += CTimer::GetTimeStepNonClippedInMilliseconds(); + else{ + m_fTimeElapsedFloat = 0.0f; + m_uiFinishTime = MS(TheCamera.m_arrPathArray[2].m_arr_PathData[10*((int)TheCamera.m_arrPathArray[2].m_arr_PathData[0]-1) + 1]); + TheCamera.m_bStartingSpline = true; + FirstFOVValue = TheCamera.m_arrPathArray[0].m_arr_PathData[2]; + PsuedoFOV = TheCamera.m_arrPathArray[0].m_arr_PathData[2]; + ArrayMarkerFOV = 5; + ArrayMarkerUp = 5; + ArrayMarkerSource = 11; + ArrayMarkerFront = 11; + } + + float fTime = m_fTimeElapsedFloat; + uint32 uiFinishTime = m_uiFinishTime; + uint32 uiTime = fTime; + if(uiTime < uiFinishTime){ + TheCamera.m_fPositionAlongSpline = (float) uiTime / uiFinishTime; + + while(uiTime >= (TheCamera.m_arrPathArray[2].m_arr_PathData[ArrayMarkerSource] - TheCamera.m_arrPathArray[2].m_arr_PathData[1])*1000.0f) + ArrayMarkerSource += 10; + FindSplinePathPositionVector(&Source, TheCamera.m_arrPathArray[2].m_arr_PathData, uiTime, ArrayMarkerSource); + + while(uiTime >= (TheCamera.m_arrPathArray[3].m_arr_PathData[ArrayMarkerFront] - TheCamera.m_arrPathArray[3].m_arr_PathData[1])*1000.0f) + ArrayMarkerFront += 10; + FindSplinePathPositionVector(&Front, TheCamera.m_arrPathArray[3].m_arr_PathData, uiTime, ArrayMarkerFront); + + while(uiTime >= (TheCamera.m_arrPathArray[1].m_arr_PathData[ArrayMarkerUp] - TheCamera.m_arrPathArray[1].m_arr_PathData[1])*1000.0f) + ArrayMarkerUp += 4; + FindSplinePathPositionFloat(&UpAngle, TheCamera.m_arrPathArray[1].m_arr_PathData, uiTime, ArrayMarkerUp); + UpAngle = DEGTORAD(UpAngle) + HALFPI; + Up.x = Cos(UpAngle); + Up.z = Sin(UpAngle); + + while(uiTime >= (TheCamera.m_arrPathArray[0].m_arr_PathData[ArrayMarkerFOV] - TheCamera.m_arrPathArray[0].m_arr_PathData[1])*1000.0f) + ArrayMarkerFOV += 4; + FindSplinePathPositionFloat(&PsuedoFOV, TheCamera.m_arrPathArray[0].m_arr_PathData, uiTime, ArrayMarkerFOV); + + m_cvecTargetCoorsForFudgeInter = Front; + Front = Front - Source; + Front.Normalise(); + CVector Left = CrossProduct(Up, Front); + Up = CrossProduct(Front, Left); + Up.Normalise(); + }else if(uiTime >= uiFinishTime){ + // end + ArrayMarkerSource = (TheCamera.m_arrPathArray[2].m_arr_PathData[0] - 1)*10 + 1; + ArrayMarkerFront = (TheCamera.m_arrPathArray[3].m_arr_PathData[0] - 1)*10 + 1; + ArrayMarkerUp = (TheCamera.m_arrPathArray[1].m_arr_PathData[0] - 1)*4 + 1; + ArrayMarkerFOV = (TheCamera.m_arrPathArray[0].m_arr_PathData[0] - 1)*4 + 1; + + FindSplinePathPositionVector(&Source, TheCamera.m_arrPathArray[2].m_arr_PathData, uiTime, ArrayMarkerSource); + FindSplinePathPositionVector(&Front, TheCamera.m_arrPathArray[3].m_arr_PathData, uiTime, ArrayMarkerFront); + FindSplinePathPositionFloat(&UpAngle, TheCamera.m_arrPathArray[1].m_arr_PathData, uiTime, ArrayMarkerUp); + UpAngle = DEGTORAD(UpAngle) + HALFPI; + Up.x = Cos(UpAngle); + Up.z = Sin(UpAngle); + FindSplinePathPositionFloat(&PsuedoFOV, TheCamera.m_arrPathArray[0].m_arr_PathData, uiTime, ArrayMarkerFOV); + + TheCamera.m_fPositionAlongSpline = 1.0f; + ArrayMarkerFOV = 0; + ArrayMarkerUp = 0; + ArrayMarkerSource = 0; + ArrayMarkerFront = 0; + + m_cvecTargetCoorsForFudgeInter = Front; + Front = Front - Source; + Front.Normalise(); + CVector Left = CrossProduct(Up, Front); + Up = CrossProduct(Front, Left); + Up.Normalise(); + } + FOV = PsuedoFOV; +} + +void +CCam::Process_WheelCam(const CVector&, float, float, float) +{ + FOV = DefaultFOV; + + if(CamTargetEntity->IsPed()){ + // what? ped with wheels or what? + Source = Multiply3x3(CamTargetEntity->GetMatrix(), CVector(-0.3f, -0.5f, 0.1f)); + Source += CamTargetEntity->GetPosition(); + Front = CVector(1.0f, 0.0f, 0.0f); + }else{ + Source = Multiply3x3(CamTargetEntity->GetMatrix(), CVector(-1.4f, -2.3f, 0.3f)); + Source += CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetForward(); + } + + CVector NewUp(0.0f, 0.0f, 1.0f); + CVector Right = CrossProduct(Front, NewUp); + Right.Normalise(); + NewUp = CrossProduct(Right, Front); + NewUp.Normalise(); + + float Roll = Cos((CTimer::GetTimeInMilliseconds()&0x1FFFF)/(float)0x1FFFF * TWOPI); + Up = Cos(Roll*0.4f)*NewUp + Sin(Roll*0.4f)*Right; +} + +void +CCam::Process_Fixed(const CVector &CameraTarget, float, float, float) +{ + Source = m_cvecCamFixedModeSource; + Front = CameraTarget - Source; + m_cvecTargetCoorsForFudgeInter = CameraTarget; + GetVectorsReadyForRW(); + + Up = CVector(0.0f, 0.0f, 1.0f) + m_cvecCamFixedModeUpOffSet; + Up.Normalise(); + CVector Right = CrossProduct(Front, Up); + Right.Normalise(); + Up = CrossProduct(Right, Front); + + FOV = DefaultFOV; + if(TheCamera.m_bUseSpecialFovTrain) + FOV = TheCamera.m_fFovForTrain; + +#ifdef PC_PLAYER_CONTROLS + if(CMenuManager::m_ControlMethod == CONTROL_STANDARD && Using3rdPersonMouseCam()){ + CPed *player = FindPlayerPed(); + if(player && player->CanStrafeOrMouseControl()){ + float Heading = Front.Heading(); + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Heading; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Heading; + TheCamera.pTargetEntity->SetHeading(Heading); + TheCamera.pTargetEntity->GetMatrix().UpdateRW(); + } + } +#endif +} + +void +CCam::Process_Player_Fallen_Water(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + CColPoint colPoint; + CEntity *entity = nil; + + FOV = DefaultFOV; + Source = CameraTarget; + Source.x += -4.5f*Cos(TargetOrientation); + Source.y += -4.5f*Sin(TargetOrientation); + Source.z = m_vecLastAboveWaterCamPosition.z + 4.0f; + + m_cvecTargetCoorsForFudgeInter = CameraTarget; + Front = CameraTarget - Source; + Front.Normalise(); + if(CWorld::ProcessLineOfSight(CameraTarget, Source, colPoint, entity, true, false, false, true, false, true, true)) + Source = colPoint.point; + GetVectorsReadyForRW(); + Front = CameraTarget - Source; + Front.Normalise(); +} + +// unused +void +CCam::Process_Circle(const CVector &CameraTarget, float, float, float) +{ + FOV = DefaultFOV; + + Front.x = Cos(0.7f) * Cos((CTimer::GetTimeInMilliseconds()&0xFFF)/(float)0xFFF * TWOPI); + Front.y = Cos(0.7f) * Sin((CTimer::GetTimeInMilliseconds()&0xFFF)/(float)0xFFF * TWOPI); + Front.z = -Sin(0.7f); + Source = CameraTarget - 4.0f*Front; + Source.z += 1.0f; + GetVectorsReadyForRW(); +} + +void +CCam::Process_SpecialFixedForSyphon(const CVector &CameraTarget, float, float, float) +{ + Source = m_cvecCamFixedModeSource; + m_cvecTargetCoorsForFudgeInter = CameraTarget; + m_cvecTargetCoorsForFudgeInter.z += m_fSyphonModeTargetZOffSet; + Front = CameraTarget - Source; + Front.z += m_fSyphonModeTargetZOffSet; + + GetVectorsReadyForRW(); + + Up += m_cvecCamFixedModeUpOffSet; + Up.Normalise(); + CVector Left = CrossProduct(Up, Front); + Left.Normalise(); + Front = CrossProduct(Left, Up); + Front.Normalise(); + FOV = DefaultFOV; +} + +#ifdef IMPROVED_CAMERA + +#define KEYJUSTDOWN(k) ControlsManager.GetIsKeyboardKeyJustDown((RsKeyCodes)k) +#define KEYDOWN(k) ControlsManager.GetIsKeyboardKeyDown((RsKeyCodes)k) +#define CTRLJUSTDOWN(key) \ + ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYJUSTDOWN((RsKeyCodes)key) || \ + (KEYJUSTDOWN(rsLCTRL) || KEYJUSTDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) +#define CTRLDOWN(key) ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) + + +void +CCam::Process_Debug(const CVector&, float, float, float) +{ + static float Speed = 0.0f; + static float PanSpeedX = 0.0f; + static float PanSpeedY = 0.0f; + CVector TargetCoors; + + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + FOV = DefaultFOV; + Alpha += DEGTORAD(CPad::GetPad(1)->GetLeftStickY()) / 50.0f; + Beta += DEGTORAD(CPad::GetPad(1)->GetLeftStickX()*1.5f) / 19.0f; + if(CPad::GetPad(0)->GetLeftMouse()){ + Alpha += DEGTORAD(CPad::GetPad(0)->GetMouseY()/2.0f); + Beta += DEGTORAD(CPad::GetPad(0)->GetMouseX()/2.0f); + } + + TargetCoors.x = Source.x + Cos(Alpha) * Sin(Beta) * 7.0f; + TargetCoors.y = Source.y + Cos(Alpha) * Cos(Beta) * 7.0f; + TargetCoors.z = Source.z + Sin(Alpha) * 3.0f; + + if(Alpha > DEGTORAD(89.5f)) Alpha = DEGTORAD(89.5f); + else if(Alpha < DEGTORAD(-89.5f)) Alpha = DEGTORAD(-89.5f); + + if(CPad::GetPad(1)->GetSquare() || KEYDOWN('W')) + Speed += 0.1f; + else if(CPad::GetPad(1)->GetCross() || KEYDOWN('S')) + Speed -= 0.1f; + else + Speed = 0.0f; + if(Speed > 70.0f) Speed = 70.0f; + if(Speed < -70.0f) Speed = -70.0f; + + + if(KEYDOWN(rsRIGHT) || KEYDOWN('D')) + PanSpeedX += 0.1f; + else if(KEYDOWN(rsLEFT) || KEYDOWN('A')) + PanSpeedX -= 0.1f; + else + PanSpeedX = 0.0f; + if(PanSpeedX > 70.0f) PanSpeedX = 70.0f; + if(PanSpeedX < -70.0f) PanSpeedX = -70.0f; + + + if(KEYDOWN(rsUP)) + PanSpeedY += 0.1f; + else if(KEYDOWN(rsDOWN)) + PanSpeedY -= 0.1f; + else + PanSpeedY = 0.0f; + if(PanSpeedY > 70.0f) PanSpeedY = 70.0f; + if(PanSpeedY < -70.0f) PanSpeedY = -70.0f; + + + Front = TargetCoors - Source; + Front.Normalise(); + Source = Source + Front*Speed; + + Up = CVector{ 0.0f, 0.0f, 1.0f }; + CVector Right = CrossProduct(Front, Up); + Up = CrossProduct(Right, Front); + Source = Source + Up*PanSpeedY + Right*PanSpeedX; + + if(Source.z < -450.0f) + Source.z = -450.0f; + + if(CPad::GetPad(1)->GetRightShoulder2JustDown() || KEYJUSTDOWN(rsENTER)){ + if(FindPlayerVehicle()) + FindPlayerVehicle()->Teleport(Source); + else + CWorld::Players[CWorld::PlayerInFocus].m_pPed->SetPosition(Source); + } + + // stay inside sectors + while(CWorld::GetSectorX(Source.x) > NUMSECTORS_X-5.0f) + Source.x -= 1.0f; + while(CWorld::GetSectorX(Source.x) < 5.0f) + Source.x += 1.0f; + while(CWorld::GetSectorY(Source.y) > NUMSECTORS_X-5.0f) + Source.y -= 1.0f; + while(CWorld::GetSectorY(Source.y) < 5.0f) + Source.y += 1.0f; + GetVectorsReadyForRW(); + +#ifdef FIX_BUGS + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_CAMERA); +#else + CPad::GetPad(0)->DisablePlayerControls = PLAYERCONTROL_CAMERA; +#endif + + if(CPad::GetPad(1)->GetLeftShockJustDown() && gbBigWhiteDebugLightSwitchedOn) + CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &Source, + 12.0f, 0.0f, 0.0f, -12.0f, + 128, 128, 128, 128, 1000.0f, false, 1.0f); + + if(CHud::m_Wants_To_Draw_Hud){ + char str[256]; + sprintf(str, "CamX: %f CamY: %f CamZ: %f", Source.x, Source.y, Source.z); + sprintf(str, "Frontx: %f, Fronty: %f, Frontz: %f ", Front.x, Front.y, Front.z); + sprintf(str, "Look@: %f, Look@: %f, Look@: %f ", Front.x + Source.x, Front.y + Source.y, Front.z + Source.z); + } +} +#else +void +CCam::Process_Debug(const CVector&, float, float, float) +{ + static float Speed = 0.0f; + CVector TargetCoors; + + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + FOV = DefaultFOV; + Alpha += DEGTORAD(CPad::GetPad(1)->GetLeftStickY()) / 50.0f; + Beta += DEGTORAD(CPad::GetPad(1)->GetLeftStickX()*1.5f) / 19.0f; + + TargetCoors.x = Source.x + Cos(Alpha) * Sin(Beta) * 7.0f; + TargetCoors.y = Source.y + Cos(Alpha) * Cos(Beta) * 7.0f; + TargetCoors.z = Source.z + Sin(Alpha) * 3.0f; + + if(Alpha > DEGTORAD(89.5f)) Alpha = DEGTORAD(89.5f); + else if(Alpha < DEGTORAD(-89.5f)) Alpha = DEGTORAD(-89.5f); + + if(CPad::GetPad(1)->GetSquare() || CPad::GetPad(1)->GetLeftMouse()) + Speed += 0.1f; + else if(CPad::GetPad(1)->GetCross() || CPad::GetPad(1)->GetRightMouse()) + Speed -= 0.1f; + else + Speed = 0.0f; + if(Speed > 70.0f) Speed = 70.0f; + if(Speed < -70.0f) Speed = -70.0f; + + Front = TargetCoors - Source; + Front.Normalise(); + Source = Source + Front*Speed; + + if(Source.z < -450.0f) + Source.z = -450.0f; + + if(CPad::GetPad(1)->GetRightShoulder2JustDown()){ + if(FindPlayerVehicle()) + FindPlayerVehicle()->Teleport(Source); + else + CWorld::Players[CWorld::PlayerInFocus].m_pPed->SetPosition(Source); + } + + // stay inside sectors + while(CWorld::GetSectorX(Source.x) > NUMSECTORS_X-5.0f) + Source.x -= 1.0f; + while(CWorld::GetSectorX(Source.x) < 5.0f) + Source.x += 1.0f; + while(CWorld::GetSectorY(Source.y) > NUMSECTORS_X-5.0f) + Source.y -= 1.0f; + while(CWorld::GetSectorY(Source.y) < 5.0f) + Source.y += 1.0f; + GetVectorsReadyForRW(); + + if(CPad::GetPad(1)->GetLeftShockJustDown() && gbBigWhiteDebugLightSwitchedOn) + CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &Source, + 12.0f, 0.0f, 0.0f, -12.0f, + 128, 128, 128, 128, 1000.0f, false, 1.0f); + + if(CHud::m_Wants_To_Draw_Hud){ + char str[256]; + sprintf(str, "CamX: %f CamY: %f CamZ: %f", Source.x, Source.y, Source.z); + sprintf(str, "Frontx: %f, Fronty: %f, Frontz: %f ", Front.x, Front.y, Front.z); + sprintf(str, "Look@: %f, Look@: %f, Look@: %f ", Front.x + Source.x, Front.y + Source.y, Front.z + Source.z); + } +} +#endif + +#ifdef GTA_SCENE_EDIT +void +CCam::Process_Editor(const CVector&, float, float, float) +{ + static float Speed = 0.0f; + CVector TargetCoors; + + if(ResetStatics){ + Source = CVector(796.0f, -937.0, 40.0f); + CamTargetEntity = nil; + } + ResetStatics = false; + + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + FOV = DefaultFOV; + Alpha += DEGTORAD(CPad::GetPad(1)->GetLeftStickY()) / 50.0f; + Beta += DEGTORAD(CPad::GetPad(1)->GetLeftStickX()*1.5f) / 19.0f; + + if(CamTargetEntity && CSceneEdit::m_bCameraFollowActor){ + TargetCoors = CamTargetEntity->GetPosition(); + }else if(CSceneEdit::m_bRecording){ + TargetCoors.x = Source.x + Cos(Alpha) * Sin(Beta) * 7.0f; + TargetCoors.y = Source.y + Cos(Alpha) * Cos(Beta) * 7.0f; + TargetCoors.z = Source.z + Sin(Alpha) * 7.0f; + }else + TargetCoors = CSceneEdit::m_vecCamHeading + Source; + CSceneEdit::m_vecCurrentPosition = TargetCoors; + CSceneEdit::m_vecCamHeading = TargetCoors - Source; + + if(Alpha > DEGTORAD(89.5f)) Alpha = DEGTORAD(89.5f); + else if(Alpha < DEGTORAD(-89.5f)) Alpha = DEGTORAD(-89.5f); + + if(CPad::GetPad(1)->GetSquare() || CPad::GetPad(1)->GetLeftMouse()) + Speed += 0.1f; + else if(CPad::GetPad(1)->GetCross() || CPad::GetPad(1)->GetRightMouse()) + Speed -= 0.1f; + else + Speed = 0.0f; + if(Speed > 70.0f) Speed = 70.0f; + if(Speed < -70.0f) Speed = -70.0f; + + Front = TargetCoors - Source; + Front.Normalise(); + Source = Source + Front*Speed; + + if(Source.z < -450.0f) + Source.z = -450.0f; + + if(CPad::GetPad(1)->GetRightShoulder2JustDown()){ + if(FindPlayerVehicle()) + FindPlayerVehicle()->Teleport(Source); + else + CWorld::Players[CWorld::PlayerInFocus].m_pPed->SetPosition(Source); + + } + + // stay inside sectors + while(CWorld::GetSectorX(Source.x) > NUMSECTORS_X-5.0f) + Source.x -= 1.0f; + while(CWorld::GetSectorX(Source.x) < 5.0f) + Source.x += 1.0f; + while(CWorld::GetSectorY(Source.y) > NUMSECTORS_X-5.0f) + Source.y -= 1.0f; + while(CWorld::GetSectorY(Source.y) < 5.0f) + Source.y += 1.0f; + GetVectorsReadyForRW(); + + if(CPad::GetPad(1)->GetLeftShockJustDown() && gbBigWhiteDebugLightSwitchedOn) + CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &Source, + 12.0f, 0.0f, 0.0f, -12.0f, + 128, 128, 128, 128, 1000.0f, false, 1.0f); + + if(CHud::m_Wants_To_Draw_Hud){ + char str[256]; + sprintf(str, "CamX: %f CamY: %f CamZ: %f", Source.x, Source.y, Source.z); + sprintf(str, "Frontx: %f, Fronty: %f, Frontz: %f ", Front.x, Front.y, Front.z); + sprintf(str, "Look@: %f, Look@: %f, Look@: %f ", Front.x + Source.x, Front.y + Source.y, Front.z + Source.z); + } +} +#endif + +void +CCam::Process_ModelView(const CVector &CameraTarget, float, float, float) +{ + CVector TargetCoors = CameraTarget; + float Angle = Atan2(Front.x, Front.y); + FOV = DefaultFOV; + + Angle += CPad::GetPad(0)->GetLeftStickX()/1280.0f; + if(Distance < 10.0f) + Distance += CPad::GetPad(0)->GetLeftStickY()/1000.0f; + else + Distance += CPad::GetPad(0)->GetLeftStickY() * ((Distance - 10.0f)/20.0f + 1.0f) / 1000.0f; +#ifdef IMPROVED_CAMERA + if(CPad::GetPad(0)->GetLeftMouse()){ + Distance += DEGTORAD(CPad::GetPad(0)->GetMouseY()/2.0f); + Angle += DEGTORAD(CPad::GetPad(0)->GetMouseX()/2.0f); + } +#endif + if(Distance < 1.5f) + Distance = 1.5f; + + Front.x = Cos(0.3f) * Sin(Angle); + Front.y = Cos(0.3f) * Cos(Angle); + Front.z = -Sin(0.3f); + Source = CameraTarget - Distance*Front; + + GetVectorsReadyForRW(); +} + +void +CCam::ProcessPedsDeadBaby(void) +{ + float Distance = 0.0f; + static bool SafeToRotate = false; + CVector TargetDist, TestPoint; + + FOV = DefaultFOV; + TargetDist = Source - CamTargetEntity->GetPosition(); + Distance = TargetDist.Magnitude(); + Beta = CGeneral::GetATanOfXY(TargetDist.x, TargetDist.y); + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + if(ResetStatics){ + TestPoint = CamTargetEntity->GetPosition() + + CVector(4.0f * Cos(Alpha) * Cos(Beta), + 4.0f * Cos(Alpha) * Sin(Beta), + 4.0f * Sin(Alpha)); + bool Safe1 = CWorld::GetIsLineOfSightClear(TestPoint, CamTargetEntity->GetPosition(), true, false, false, true, false, true, true); + + TestPoint = CamTargetEntity->GetPosition() + + CVector(4.0f * Cos(Alpha) * Cos(Beta + DEGTORAD(120.0f)), + 4.0f * Cos(Alpha) * Sin(Beta + DEGTORAD(120.0f)), + 4.0f * Sin(Alpha)); + bool Safe2 = CWorld::GetIsLineOfSightClear(TestPoint, CamTargetEntity->GetPosition(), true, false, false, true, false, true, true); + + TestPoint = CamTargetEntity->GetPosition() + + CVector(4.0f * Cos(Alpha) * Cos(Beta - DEGTORAD(120.0f)), + 4.0f * Cos(Alpha) * Sin(Beta - DEGTORAD(120.0f)), + 4.0f * Sin(Alpha)); + bool Safe3 = CWorld::GetIsLineOfSightClear(TestPoint, CamTargetEntity->GetPosition(), true, false, false, true, false, true, true); + + SafeToRotate = Safe1 && Safe2 && Safe3; + + ResetStatics = false; + } + + if(SafeToRotate) + WellBufferMe(Beta + DEGTORAD(175.0f), &Beta, &BetaSpeed, 0.015f, 0.007f, true); + + WellBufferMe(DEGTORAD(89.5f), &Alpha, &AlphaSpeed, 0.015f, 0.07f, true); + WellBufferMe(35.0f, &Distance, &DistanceSpeed, 0.006f, 0.007f, false); + + Source = CamTargetEntity->GetPosition() + + CVector(Distance * Cos(Alpha) * Cos(Beta), + Distance * Cos(Alpha) * Sin(Beta), + Distance * Sin(Alpha)); + m_cvecTargetCoorsForFudgeInter = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + Front.Normalise(); + GetVectorsReadyForRW(); +} + +bool +CCam::ProcessArrestCamOne(void) +{ + FOV = 45.0f; + if(!ResetStatics) + return true; + +#ifdef FIX_BUGS + if(!CamTargetEntity->IsPed() || ((CPlayerPed*)TheCamera.pTargetEntity)->m_pArrestingCop == nil) + return true; +#endif + + bool found; + float Ground; + CVector PlayerCoors = TheCamera.pTargetEntity->GetPosition(); + CVector CopCoors = ((CPlayerPed*)TheCamera.pTargetEntity)->m_pArrestingCop->GetPosition(); + Beta = CGeneral::GetATanOfXY(PlayerCoors.x - CopCoors.x, PlayerCoors.y - CopCoors.y); + + Source = PlayerCoors + 9.5f*CVector(Cos(Beta), Sin(Beta), 0.0f); + Source.z += 6.0f; + Ground = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, Source.z, &found); + if(!found){ + Ground = CWorld::FindRoofZFor3DCoord(Source.x, Source.y, Source.z, &found); + if(!found) + return false; + } + Source.z = Ground + 0.25f; + if(!CWorld::GetIsLineOfSightClear(Source, CopCoors, true, true, false, true, false, true, true)){ + Beta += DEGTORAD(115.0f); + Source = PlayerCoors + 9.5f*CVector(Cos(Beta), Sin(Beta), 0.0f); + Source.z += 6.0f; + Ground = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, Source.z, &found); + if(!found){ + Ground = CWorld::FindRoofZFor3DCoord(Source.x, Source.y, Source.z, &found); + if(!found) + return false; + } + Source.z = Ground + 0.25f; + + CopCoors.z += 0.35f; + Front = CopCoors - Source; + if(!CWorld::GetIsLineOfSightClear(Source, CopCoors, true, true, false, true, false, true, true)) + return false; + } + CopCoors.z += 0.35f; + m_cvecTargetCoorsForFudgeInter = CopCoors; + Front = CopCoors - Source; + ResetStatics = false; + GetVectorsReadyForRW(); + return true; +} + +bool +CCam::ProcessArrestCamTwo(void) +{ + CPed *player = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + if(!ResetStatics) + return true; + ResetStatics = false; + + CVector TargetCoors, ToCamera; + float BetaOffset; + float SourceX, SourceY; + if(&TheCamera.Cams[TheCamera.ActiveCam] == this){ + SourceX = TheCamera.Cams[(TheCamera.ActiveCam + 1) % 2].Source.x; + SourceY = TheCamera.Cams[(TheCamera.ActiveCam + 1) % 2].Source.y; + }else{ + SourceX = TheCamera.Cams[TheCamera.ActiveCam].Source.x; + SourceY = TheCamera.Cams[TheCamera.ActiveCam].Source.y; + } + + for(int i = 0; i <= 1; i++){ + int Dir = i == 0 ? 1 : -1; + + FOV = 60.0f; + TargetCoors = player->GetPosition(); + Beta = CGeneral::GetATanOfXY(TargetCoors.x-SourceX, TargetCoors.y-SourceY); + BetaOffset = DEGTORAD(Dir*80); + Source = TargetCoors + 11.5f*CVector(Cos(Beta+BetaOffset), Sin(Beta+BetaOffset), 0.0f); + + ToCamera = Source - TargetCoors; + ToCamera.Normalise(); + TargetCoors.x += 0.4f*ToCamera.x; + TargetCoors.y += 0.4f*ToCamera.y; + if(CWorld::GetIsLineOfSightClear(Source, TargetCoors, true, true, false, true, false, true, true)){ + Source.z += 5.5f; + TargetCoors += CVector(-0.8f*ToCamera.x, -0.8f*ToCamera.y, 2.2f); + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + ResetStatics = false; + GetVectorsReadyForRW(); + return true; + } + } + return false; +} + + +/* + * Unused PS2 cams + */ + +void +CCam::Process_Chris_With_Binding_PlusRotation(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + static float AngleToBinned = 0.0f; + static float StartingAngleLastChange = 0.0f; + static float FixedTargetOrientation = 0.0f; + static float DeadZoneReachedOnePrevious; + + FOV = DefaultFOV; // missing in game + + bool FixOrientation = true; + if(ResetStatics){ + Rotating = false; + DeadZoneReachedOnePrevious = 0.0f; + FixedTargetOrientation = 0.0f; + ResetStatics = false; + } + + CVector TargetCoors = CameraTarget; + + float StickX = CPad::GetPad(0)->GetRightStickX(); + float StickY = CPad::GetPad(0)->GetRightStickY(); + float StickAngle; + if(StickX != 0.0 || StickY != 0.0f) // BUG: game checks StickX twice + StickAngle = CGeneral::GetATanOfXY(StickX, StickY); // result unused? + else + FixOrientation = false; + + CVector Dist = Source - TargetCoors; + Source.z = TargetCoors.z + 0.75f; + float Length = Dist.Magnitude2D(); + if(Length > 2.5f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.5f; + Source.y = TargetCoors.y + Dist.y/Length * 2.5f; + }else if(Length < 2.4f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.4f; + Source.y = TargetCoors.y + Dist.y/Length * 2.4f; + } + + Beta = CGeneral::GetATanOfXY(Dist.x, Dist.y); + if(CPad::GetPad(0)->GetLeftShoulder1()){ + FixedTargetOrientation = TargetOrientation; + Rotating = true; + } + + if(FixOrientation){ + Rotating = true; + FixedTargetOrientation = StickX/128.0f + Beta - PI; + } + + if(Rotating){ + Dist = Source - TargetCoors; + Length = Dist.Magnitude2D(); + // inlined + WellBufferMe(FixedTargetOrientation+PI, &Beta, &BetaSpeed, 0.1f, 0.06f, true); + + Source.x = TargetCoors.x + Length*Cos(Beta); + Source.y = TargetCoors.y + Length*Sin(Beta); + + float DeltaBeta = FixedTargetOrientation+PI - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < 0.06f) + Rotating = false; + } + + Front = TargetCoors - Source; + Front.Normalise(); + CVector Front2 = Front; + Front2.Normalise(); // What? + // FIX: the meaning of this value must have changed somehow + Source -= Front2 * TheCamera.m_fPedZoomValueSmooth*1.5f; +// Source += Front2 * TheCamera.m_fPedZoomValueSmooth; + + GetVectorsReadyForRW(); +} + +void +CCam::Process_ReactionCam(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + static float AngleToBinned = 0.0f; + static float StartingAngleLastChange = 0.0f; + static float FixedTargetOrientation; + static float DeadZoneReachedOnePrevious; + static uint32 TimeOfLastChange; + uint32 Time; + bool DontBind = false; // BUG: left uninitialized + + FOV = DefaultFOV; // missing in game + + if(ResetStatics){ + Rotating = false; + DeadZoneReachedOnePrevious = 0.0f; + FixedTargetOrientation = 0.0f; + ResetStatics = false; + DontBind = false; + } + + CVector TargetCoors = CameraTarget; + + CVector Dist = Source - TargetCoors; + Source.z = TargetCoors.z + 0.75f; + float Length = Dist.Magnitude2D(); + if(Length > 2.5f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.5f; + Source.y = TargetCoors.y + Dist.y/Length * 2.5f; + }else if(Length < 2.4f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.4f; + Source.y = TargetCoors.y + Dist.y/Length * 2.4f; + } + + Beta = CGeneral::GetATanOfXY(Dist.x, Dist.y); + + float StickX = CPad::GetPad(0)->GetLeftStickX(); + float StickY = CPad::GetPad(0)->GetLeftStickY(); + float StickAngle; + if(StickX != 0.0 || StickY != 0.0f){ + StickAngle = CGeneral::GetATanOfXY(StickX, StickY); + while(StickAngle >= PI) StickAngle -= 2*PI; + while(StickAngle < -PI) StickAngle += 2*PI; + }else + StickAngle = 1000.0f; + + if(Abs(StickAngle-AngleToBinned) > DEGTORAD(15.0f)){ + DontBind = true; + Time = CTimer::GetTimeInMilliseconds(); + } + + if(CTimer::GetTimeInMilliseconds()-TimeOfLastChange > 200){ + if(Abs(HALFPI-StickAngle) > DEGTORAD(50.0f)){ + FixedTargetOrientation = TargetOrientation; + Rotating = true; + TimeOfLastChange = CTimer::GetTimeInMilliseconds(); + } + } + + // These two together don't make much sense. + // Only prevents rotation for one frame + AngleToBinned = StickAngle; + if(DontBind) + TimeOfLastChange = Time; + + if(Rotating){ + Dist = Source - TargetCoors; + Length = Dist.Magnitude2D(); + // inlined + WellBufferMe(FixedTargetOrientation+PI, &Beta, &BetaSpeed, 0.1f, 0.06f, true); + + Source.x = TargetCoors.x + Length*Cos(Beta); + Source.y = TargetCoors.y + Length*Sin(Beta); + + float DeltaBeta = FixedTargetOrientation+PI - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < 0.06f) + Rotating = false; + } + + Front = TargetCoors - Source; + Front.Normalise(); + CVector Front2 = Front; + Front2.Normalise(); // What? + // FIX: the meaning of this value must have changed somehow + Source -= Front2 * TheCamera.m_fPedZoomValueSmooth*1.5f; +// Source += Front2 * TheCamera.m_fPedZoomValueSmooth; + + GetVectorsReadyForRW(); +} + +void +CCam::Process_FollowPed_WithBinding(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + static float AngleToBinned = 0.0f; + static float StartingAngleLastChange = 0.0f; + static float FixedTargetOrientation; + static float DeadZoneReachedOnePrevious; + static uint32 TimeOfLastChange; + uint32 Time; + bool DontBind = false; + + FOV = DefaultFOV; // missing in game + + if(ResetStatics){ + Rotating = false; + DeadZoneReachedOnePrevious = 0.0f; + FixedTargetOrientation = 0.0f; + ResetStatics = false; + } + + CVector TargetCoors = CameraTarget; + + CVector Dist = Source - TargetCoors; + Source.z = TargetCoors.z + 0.75f; + float Length = Dist.Magnitude2D(); + if(Length > 2.5f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.5f; + Source.y = TargetCoors.y + Dist.y/Length * 2.5f; + }else if(Length < 2.4f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.4f; + Source.y = TargetCoors.y + Dist.y/Length * 2.4f; + } + + Beta = CGeneral::GetATanOfXY(Dist.x, Dist.y); + + float StickX = CPad::GetPad(0)->GetLeftStickX(); + float StickY = CPad::GetPad(0)->GetLeftStickY(); + float StickAngle; + if(StickX != 0.0 || StickY != 0.0f){ + StickAngle = CGeneral::GetATanOfXY(StickX, StickY); + while(StickAngle >= PI) StickAngle -= 2*PI; + while(StickAngle < -PI) StickAngle += 2*PI; + }else + StickAngle = 1000.0f; + + if(Abs(StickAngle-AngleToBinned) > DEGTORAD(15.0f)){ + DontBind = true; + Time = CTimer::GetTimeInMilliseconds(); + } + + if(CTimer::GetTimeInMilliseconds()-TimeOfLastChange > 200){ + if(Abs(HALFPI-StickAngle) > DEGTORAD(50.0f)){ + FixedTargetOrientation = TargetOrientation; + Rotating = true; + TimeOfLastChange = CTimer::GetTimeInMilliseconds(); + } + } + + if(CPad::GetPad(0)->GetLeftShoulder1JustDown()){ + FixedTargetOrientation = TargetOrientation; + Rotating = true; + TimeOfLastChange = CTimer::GetTimeInMilliseconds(); + } + + // These two together don't make much sense. + // Only prevents rotation for one frame + AngleToBinned = StickAngle; + if(DontBind) + TimeOfLastChange = Time; + + if(Rotating){ + Dist = Source - TargetCoors; + Length = Dist.Magnitude2D(); + // inlined + WellBufferMe(FixedTargetOrientation+PI, &Beta, &BetaSpeed, 0.1f, 0.06f, true); + + Source.x = TargetCoors.x + Length*Cos(Beta); + Source.y = TargetCoors.y + Length*Sin(Beta); + + float DeltaBeta = FixedTargetOrientation+PI - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < 0.06f) + Rotating = false; + } + + Front = TargetCoors - Source; + Front.Normalise(); + CVector Front2 = Front; + Front2.Normalise(); // What? + // FIX: the meaning of this value must have changed somehow + Source -= Front2 * TheCamera.m_fPedZoomValueSmooth*1.5f; +// Source += Front2 * TheCamera.m_fPedZoomValueSmooth; + + GetVectorsReadyForRW(); +} + +void +CCam::Process_Bill(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar) +{ +#ifdef FIX_BUGS + fBillsBetaOffset += CPad::GetPad(0)->GetRightStickX()/1000.0f; +#else + // just wtf is this? this code must be ancient + if(CPad::GetPad(0)->GetStart()) + fBillsBetaOffset += CPad::GetPad(0)->GetLeftStickX()/1000.0f; +#endif + while(fBillsBetaOffset > TWOPI) fBillsBetaOffset -= TWOPI; + while(fBillsBetaOffset < 0.0f) fBillsBetaOffset += TWOPI; + TargetOrientation += fBillsBetaOffset; + while(TargetOrientation > TWOPI) TargetOrientation -= TWOPI; + while(TargetOrientation < 0.0f) TargetOrientation += TWOPI; + Process_BehindCar(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); +} + +void +CCam::Process_Im_The_Passenger_Woo_Woo(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = 50.0f; + + Source = CamTargetEntity->GetPosition(); + Source.z += 2.5f; + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Source += 1.35f*Front; + float heading = CGeneral::GetATanOfXY(Front.x, Front.y) + DEGTORAD(45.0f); + Front.x = Cos(heading); + Front.y = Sin(heading); + Up = CamTargetEntity->GetUp(); + + GetVectorsReadyForRW(); +} + +void +CCam::Process_Blood_On_The_Tracks(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = 50.0f; + + Source = CamTargetEntity->GetPosition(); + Source.z += 5.45f; + + static CVector Test = -CamTargetEntity->GetForward(); +#ifdef FIX_BUGS + if(ResetStatics){ + Test = -CamTargetEntity->GetForward(); + ResetStatics = false; + } +#endif + + Source.x += 19.45*Test.x; + Source.y += 19.45*Test.y; + Front = Test; + Front.Normalise(); + Up = CamTargetEntity->GetUp(); + + GetVectorsReadyForRW(); +} + +void +CCam::Process_Cam_Running_Side_Train(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = 60.0f; + + Source = CamTargetEntity->GetPosition(); + Source.z += 4.0f; + CVector fwd = CamTargetEntity->GetForward(); + float heading = CGeneral::GetATanOfXY(fwd.x, fwd.y) - DEGTORAD(15.0f); + Source.x -= Cos(heading)*10.0f; + Source.y -= Sin(heading)*10.0f; + heading -= DEGTORAD(5.0f); + Front = fwd; + Front.x += Cos(heading); + Front.y += Sin(heading); + Front.z -= 0.056f; + Front.Normalise(); + Up = CamTargetEntity->GetUp(); + + GetVectorsReadyForRW(); +} + +void +CCam::Process_Cam_On_Train_Roof(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + static float RoofMultiplier = 1.5f; + + Source = CamTargetEntity->GetPosition(); + Source.z += 4.8f; + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Source += Front*RoofMultiplier; + Up = CamTargetEntity->GetUp(); + Up.Normalise(); + + GetVectorsReadyForRW(); +} + + +#ifdef FREE_CAM +void +CCam::Process_FollowPed_Rotation(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = DefaultFOV; + + const float MinDist = 2.0f; + const float MaxDist = 2.0f + TheCamera.m_fPedZoomValueSmooth; + const float BaseOffset = 0.75f; // base height of camera above target + + CVector TargetCoors = CameraTarget; + + TargetCoors.z += m_fSyphonModeTargetZOffSet; + TargetCoors = DoAverageOnVector(TargetCoors); + TargetCoors.z += BaseOffset; // add offset so alpha evens out to 0 +// TargetCoors.z += m_fRoadOffSet; + + CVector Dist = Source - TargetCoors; + CVector ToCam; + + bool Shooting = false; + if(((CPed*)CamTargetEntity)->GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) + if(CPad::GetPad(0)->GetWeapon()) + Shooting = true; + if(((CPed*)CamTargetEntity)->GetWeapon()->m_eWeaponType == WEAPONTYPE_DETONATOR || + ((CPed*)CamTargetEntity)->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT) + Shooting = false; + + + if(ResetStatics){ + // Coming out of top down here probably + // so keep Beta, reset alpha and calculate vectors + Beta = CGeneral::GetATanOfXY(Dist.x, Dist.y); + Alpha = 0.0f; + + Dist = MaxDist*CVector(Cos(Alpha) * Cos(Beta), Cos(Alpha) * Sin(Beta), Sin(Alpha)); + Source = TargetCoors + Dist; + + ResetStatics = false; + } + + // Drag the camera along at the look-down offset + float CamDist = Dist.Magnitude(); + if(CamDist == 0.0f) + Dist = CVector(1.0f, 1.0f, 0.0f); + else if(CamDist < MinDist) + Dist *= MinDist/CamDist; + else if(CamDist > MaxDist) + Dist *= MaxDist/CamDist; + CamDist = Dist.Magnitude(); + + // Beta = 0 is looking east, HALFPI is north, &c. + // Alpha positive is looking up + float GroundDist = Dist.Magnitude2D(); + Beta = CGeneral::GetATanOfXY(-Dist.x, -Dist.y); + Alpha = CGeneral::GetATanOfXY(GroundDist, -Dist.z); + while(Beta >= PI) Beta -= 2.0f*PI; + while(Beta < -PI) Beta += 2.0f*PI; + while(Alpha >= PI) Alpha -= 2.0f*PI; + while(Alpha < -PI) Alpha += 2.0f*PI; + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; +/* + if((MouseX != 0.0f || MouseY != 0.0f) && !CPad::GetPad(0)->ArePlayerControlsDisabled()){ + UseMouse = true; + LookLeftRight = -2.5f*MouseX; + LookUpDown = 4.0f*MouseY; + }else +*/ + { + LookLeftRight = -CPad::GetPad(0)->LookAroundLeftRight(); + LookUpDown = CPad::GetPad(0)->LookAroundUpDown(); + } + float AlphaOffset, BetaOffset; + if(UseMouse){ + BetaOffset = LookLeftRight * TheCamera.m_fMouseAccelHorzntl * FOV/80.0f; + AlphaOffset = LookUpDown * TheCamera.m_fMouseAccelVertical * FOV/80.0f; + }else{ + BetaOffset = LookLeftRight * fStickSens * (1.0f/20.0f) * FOV/80.0f * CTimer::GetTimeStep(); + AlphaOffset = LookUpDown * fStickSens * (0.6f/20.0f) * FOV/80.0f * CTimer::GetTimeStep(); + } + + // Stop centering once stick has been touched + if(BetaOffset) + Rotating = false; + + Beta += BetaOffset; + Alpha += AlphaOffset; + while(Beta >= PI) Beta -= 2.0f*PI; + while(Beta < -PI) Beta += 2.0f*PI; + if(Alpha > DEGTORAD(45.0f)) Alpha = DEGTORAD(45.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + + float BetaDiff = TargetOrientation+PI - Beta; + while(BetaDiff >= PI) BetaDiff -= 2.0f*PI; + while(BetaDiff < -PI) BetaDiff += 2.0f*PI; + float TargetAlpha = Alpha; + // 12deg to account for our little height offset. we're not working on the true alpha here + const float AlphaLimitUp = DEGTORAD(15.0f) + DEGTORAD(12.0f); + const float AlphaLimitDown = -DEGTORAD(15.0f) + DEGTORAD(12.0f); + if(Abs(BetaDiff) < DEGTORAD(25.0f) && ((CPed*)CamTargetEntity)->GetMoveSpeed().Magnitude2D() > 0.01f){ + // Limit alpha when player is walking towards camera + if(TargetAlpha > AlphaLimitUp) TargetAlpha = AlphaLimitUp; + if(TargetAlpha < AlphaLimitDown) TargetAlpha = AlphaLimitDown; + } + + WellBufferMe(TargetAlpha, &Alpha, &AlphaSpeed, 0.2f, 0.1f, true); + + if(CPad::GetPad(0)->ForceCameraBehindPlayer() || Shooting){ + m_fTargetBeta = TargetOrientation; + Rotating = true; + } + + if(Rotating){ + WellBufferMe(m_fTargetBeta, &Beta, &BetaSpeed, 0.1f, 0.06f, true); + float DeltaBeta = m_fTargetBeta - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < 0.06f) + Rotating = false; + } + + + if(TheCamera.m_bUseTransitionBeta) + Beta = CGeneral::GetATanOfXY(-Cos(m_fTransitionBeta), -Sin(m_fTransitionBeta)); + + Front = CVector(Cos(Alpha) * Cos(Beta), Cos(Alpha) * Sin(Beta), Sin(Alpha)); + Source = TargetCoors - Front*CamDist; + TargetCoors.z -= BaseOffset; // now get back to the real target coors again + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + + + Front = TargetCoors - Source; + Front.Normalise(); + + + + /* + * Handle collisions - taken from FollowPedWithMouse + */ + + CEntity *entity; + CColPoint colPoint; + // Clip Source and fix near clip + CWorld::pIgnoreEntity = CamTargetEntity; + entity = nil; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, true, true, true, false, false, true)){ + float PedColDist = (TargetCoors - colPoint.point).Magnitude(); + float ColCamDist = CamDist - PedColDist; + if(entity->IsPed() && ColCamDist > DEFAULT_NEAR + 0.1f){ + // Ped in the way but not clipping through + if(CWorld::ProcessLineOfSight(colPoint.point, Source, colPoint, entity, true, true, true, true, false, false, true)){ + PedColDist = (TargetCoors - colPoint.point).Magnitude(); + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + }else{ + RwCameraSetNearClipPlane(Scene.camera, Min(ColCamDist-0.35f, DEFAULT_NEAR)); + } + }else{ + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + } + } + CWorld::pIgnoreEntity = nil; + + float ViewPlaneHeight = Tan(DEGTORAD(FOV) / 2.0f); + float ViewPlaneWidth = ViewPlaneHeight * CDraw::FindAspectRatio() * fTweakFOV; + float Near = RwCameraGetNearClipPlane(Scene.camera); + float radius = ViewPlaneWidth*Near; + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, false); + int i = 0; + while(entity){ + CVector CamToCol = gaTempSphereColPoints[0].point - Source; + float frontDist = DotProduct(CamToCol, Front); + float dist = (CamToCol - Front*frontDist).Magnitude() / ViewPlaneWidth; + + // Try to decrease near clip + dist = Max(Min(Near, dist), 0.1f); + if(dist < Near) + RwCameraSetNearClipPlane(Scene.camera, dist); + + // Move forward a bit + if(dist == 0.1f) + Source += (TargetCoors - Source)*0.3f; + + // Keep testing + Near = RwCameraGetNearClipPlane(Scene.camera); + radius = ViewPlaneWidth*Near; + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, false); + + i++; + if(i > 5) + entity = nil; + } + + GetVectorsReadyForRW(); +} + +// LCS cam hehe +void +CCam::Process_FollowCar_SA(const CVector& CameraTarget, float TargetOrientation, float, float) +{ + // Missing things on III CCam + static CVector m_aTargetHistoryPosOne; + static CVector m_aTargetHistoryPosTwo; + static CVector m_aTargetHistoryPosThree; + static int m_nCurrentHistoryPoints = 0; + static float lastBeta = -9999.0f; + static float lastAlpha = -9999.0f; + static float stepsLeftToChangeBetaByMouse; + static float dontCollideWithCars; + static bool alphaCorrected; + static float heightIncreaseMult; + + if (!CamTargetEntity->IsVehicle()) + return; + + CVehicle* car = (CVehicle*)CamTargetEntity; + CVector TargetCoors = CameraTarget; + uint8 camSetArrPos = 0; + + // We may need those later + bool isPlane = car->GetModelIndex() == MI_DODO; + bool isHeli = false; + bool isBike = false; + bool isCar = car->IsCar() && !isPlane && !isHeli && !isBike; + + CPad* pad = CPad::GetPad(0); + + // Next direction is non-existent in III + uint8 nextDirectionIsForward = !(pad->GetLookBehindForCar() || pad->GetLookBehindForPed() || pad->GetLookLeft() || pad->GetLookRight()) && + DirectionWasLooking == LOOKING_FORWARD; + + if (car->GetModelIndex() == MI_FIRETRUCK) { + camSetArrPos = 7; + } else if (car->GetModelIndex() == MI_RCBANDIT) { + camSetArrPos = 5; + } else if (car->IsBoat()) { + camSetArrPos = 4; + } else if (isBike) { + camSetArrPos = 1; + } else if (isPlane) { + camSetArrPos = 3; + } else if (isHeli) { + camSetArrPos = 2; + } + + // LCS one but index 1(firetruck) moved to last + float CARCAM_SET[][15] = { + {1.3f, 1.0f, 0.4f, 10.0f, 15.0f, 0.5f, 1.0f, 1.0f, 0.85f, 0.2f, 0.075f, 0.05f, 0.8f, DEGTORAD(45.0f), DEGTORAD(89.0f)}, // cars + {1.1f, 1.0f, 0.1f, 10.0f, 11.0f, 0.5f, 1.0f, 1.0f, 0.85f, 0.2f, 0.075f, 0.05f, 0.75f, DEGTORAD(45.0f), DEGTORAD(89.0f)}, // bike + {1.1f, 1.0f, 0.2f, 10.0f, 15.0f, 0.05f, 0.05f, 0.0f, 0.9f, 0.05f, 0.01f, 0.05f, 1.0f, DEGTORAD(10.0f), DEGTORAD(70.0f)}, // heli (SA values) + {1.1f, 1.0f, 0.2f, 10.0f, 15.0f, 0.5f, 1.0f, 1.0f, 0.75f, 0.1f, 0.005f, 0.2f, 1.0f, DEGTORAD(89.0f), DEGTORAD(89.0f)}, // plane (SA values) + {0.9f, 1.0f, 0.1f, 10.0f, 15.0f, 0.5f, 1.0f, 0.0f, 0.9f, 0.05f, 0.005f, 0.05f, 1.0f, -0.2f, DEGTORAD(70.0f)}, // boat + {1.1f, 1.0f, 0.2f, 10.0f, 5.0f, 0.5f, 1.0f, 1.0f, 0.75f, 0.1f, 0.005f, 0.2f, 1.0f, DEGTORAD(45.0f), DEGTORAD(89.0f)}, // rc cars + {1.1f, 1.0f, 0.2f, 10.0f, 5.0f, 0.5f, 1.0f, 1.0f, 0.75f, 0.1f, 0.005f, 0.2f, 1.0f, DEGTORAD(20.0f), DEGTORAD(70.0f)}, // rc heli/planes + {1.3f, 1.0f, 0.4f, 10.0f, 15.0f, 0.5f, 1.0f, 1.0f, 0.85f, 0.2f, 0.075f, 0.05f, 0.8f, -0.18f, DEGTORAD(40.0f)}, // firetruck... + }; + + // RC Heli/planes use same alpha values with heli/planes (LCS firetruck will fallback to 0) + uint8 alphaArrPos = (camSetArrPos > 4 ? (isPlane ? 3 : (isHeli ? 2 : 0)) : camSetArrPos); + float zoomModeAlphaOffset = 0.0f; + static float ZmOneAlphaOffsetLCS[] = { 0.12f, 0.08f, 0.15f, 0.08f, 0.08f }; + static float ZmTwoAlphaOffsetLCS[] = { 0.1f, 0.08f, 0.3f, 0.08f, 0.08f }; + static float ZmThreeAlphaOffsetLCS[] = { 0.065f, 0.05f, 0.15f, 0.06f, 0.08f }; + + if (isHeli && car->GetStatus() == STATUS_PLAYER_REMOTE) + zoomModeAlphaOffset = ZmTwoAlphaOffsetLCS[alphaArrPos]; + else { + switch ((int)TheCamera.CarZoomIndicator) { + // near + case CAM_ZOOM_1: + zoomModeAlphaOffset = ZmOneAlphaOffsetLCS[alphaArrPos]; + break; + // mid + case CAM_ZOOM_2: + zoomModeAlphaOffset = ZmTwoAlphaOffsetLCS[alphaArrPos]; + break; + // far + case CAM_ZOOM_3: + zoomModeAlphaOffset = ZmThreeAlphaOffsetLCS[alphaArrPos]; + break; + default: + break; + } + } + + CColModel* carCol = (CColModel*)car->GetColModel(); + float colMaxZ = carCol->boundingBox.max.z; // As opposed to LCS and SA, VC does this: carCol->boundingBox.max.z - carCol->boundingBox.min.z; + float approxCarLength = 2.0f * Abs(carCol->boundingBox.min.y); // SA taxi min.y = -2.95, max.z = 0.883502f + + float newDistance = TheCamera.CarZoomValueSmooth + CARCAM_SET[camSetArrPos][1] + approxCarLength; + + float minDistForThisCar = approxCarLength * CARCAM_SET[camSetArrPos][3]; + + if (!isHeli || car->GetStatus() == STATUS_PLAYER_REMOTE) { + float radiusToStayOutside = colMaxZ * CARCAM_SET[camSetArrPos][0] - CARCAM_SET[camSetArrPos][2]; + if (radiusToStayOutside > 0.0f) { + TargetCoors.z += radiusToStayOutside; + newDistance += radiusToStayOutside; + zoomModeAlphaOffset += 0.3f / newDistance * radiusToStayOutside; + } + } else { + // 0.6f = fTestShiftHeliCamTarget + TargetCoors += 0.6f * car->GetUp() * colMaxZ; + } + + float minDistForVehType = CARCAM_SET[camSetArrPos][4]; + + if (TheCamera.CarZoomIndicator == CAM_ZOOM_1 && (camSetArrPos < 2 || camSetArrPos == 7)) { + minDistForVehType = minDistForVehType * 0.65f; + } + + float nextDistance = Max(newDistance, minDistForVehType); + + CA_MAX_DISTANCE = newDistance; + CA_MIN_DISTANCE = 3.5f; + + if (ResetStatics) { + FOV = DefaultFOV; + + // GTA 3 has this in veh. camera + if (TheCamera.m_bIdleOn) + TheCamera.m_uiTimeWeEnteredIdle = CTimer::GetTimeInMilliseconds(); + } else { + if (isCar || isBike) { + // 0.4f: CAR_FOV_START_SPEED + if (DotProduct(car->GetForward(), car->m_vecMoveSpeed) > 0.4f) + FOV += (DotProduct(car->GetForward(), car->m_vecMoveSpeed) - 0.4f) * CTimer::GetTimeStep(); + } + + if (FOV > DefaultFOV) + // 0.98f: CAR_FOV_FADE_MULT + FOV = Pow(0.98f, CTimer::GetTimeStep()) * (FOV - DefaultFOV) + DefaultFOV; + + FOV = Clamp(FOV, DefaultFOV, DefaultFOV + 30.0f); + } + + // WORKAROUND: I still don't know how looking behind works (m_bCamDirectlyInFront is unused in III, they seem to use m_bUseTransitionBeta) + if (pad->GetLookBehindForCar()) + if (DirectionWasLooking == LOOKING_FORWARD || !LookingBehind) + TheCamera.m_bCamDirectlyInFront = true; + + // Taken from RotCamIfInFrontCar, because we don't call it anymore + if (!(pad->GetLookBehindForCar() || pad->GetLookBehindForPed() || pad->GetLookLeft() || pad->GetLookRight())) + if (DirectionWasLooking != LOOKING_FORWARD) + TheCamera.m_bCamDirectlyBehind = true; + + // Called when we just entered the car, just started to look behind or returned back from looking left, right or behind + if (ResetStatics || TheCamera.m_bCamDirectlyBehind || TheCamera.m_bCamDirectlyInFront) { + ResetStatics = false; + Rotating = false; + m_bCollisionChecksOn = true; + // TheCamera.m_bResetOldMatrix = 1; + + // Garage exit cam is not working well in III... + // if (!TheCamera.m_bJustCameOutOfGarage) // && !sthForScript) + // { + Alpha = 0.0f; + Beta = car->GetForward().Heading() - HALFPI; + if (TheCamera.m_bCamDirectlyInFront) { + Beta += PI; + } + // } + + BetaSpeed = 0.0; + AlphaSpeed = 0.0; + Distance = 1000.0; + + Front.x = -(Cos(Beta) * Cos(Alpha)); + Front.y = -(Sin(Beta) * Cos(Alpha)); + Front.z = Sin(Alpha); + + m_aTargetHistoryPosOne = TargetCoors - nextDistance * Front; + + m_aTargetHistoryPosTwo = TargetCoors - newDistance * Front; + + m_nCurrentHistoryPoints = 0; + if (!TheCamera.m_bJustCameOutOfGarage) // && !sthForScript) + Alpha = -zoomModeAlphaOffset; + } + + Front = TargetCoors - m_aTargetHistoryPosOne; + Front.Normalise(); + + // Code that makes cam rotate around the car + float camRightHeading = Front.Heading() - HALFPI; + if (camRightHeading < -PI) + camRightHeading = camRightHeading + TWOPI; + + float velocityRightHeading; + if (car->m_vecMoveSpeed.Magnitude2D() <= 0.02f) + velocityRightHeading = camRightHeading; + else + velocityRightHeading = car->m_vecMoveSpeed.Heading() - HALFPI; + + if (velocityRightHeading < camRightHeading - PI) + velocityRightHeading = velocityRightHeading + TWOPI; + else if (velocityRightHeading > camRightHeading + PI) + velocityRightHeading = velocityRightHeading - TWOPI; + + float betaChangeMult1 = CTimer::GetTimeStep() * CARCAM_SET[camSetArrPos][10]; + float betaChangeLimit = CTimer::GetTimeStep() * CARCAM_SET[camSetArrPos][11]; + + float betaChangeMult2 = (car->m_vecMoveSpeed - DotProduct(car->m_vecMoveSpeed, Front) * Front).Magnitude(); + + float betaChange = Min(1.0f, betaChangeMult1 * betaChangeMult2) * (velocityRightHeading - camRightHeading); + if (betaChange <= betaChangeLimit) { + if (betaChange < -betaChangeLimit) + betaChange = -betaChangeLimit; + } else { + betaChange = betaChangeLimit; + } + float targetBeta = camRightHeading + betaChange; + + if (targetBeta < Beta - HALFPI) + targetBeta += TWOPI; + else if (targetBeta > Beta + PI) + targetBeta -= TWOPI; + + float carPosChange = (TargetCoors - m_aTargetHistoryPosTwo).Magnitude(); + if (carPosChange < newDistance && newDistance > minDistForThisCar) { + newDistance = Max(minDistForThisCar, carPosChange); + } + float maxAlphaAllowed = CARCAM_SET[camSetArrPos][13]; + + // Originally this is to prevent camera enter into car while we're stopping, but what about moving??? + // This is also original LCS and SA bug, or some attempt to fix lag. We'll never know + + // if (car->m_vecMoveSpeed.MagnitudeSqr() < sq(0.2f)) + if (car->GetModelIndex() != MI_FIRETRUCK) { + // if (!isBike || GetMysteriousWheelRelatedThingBike(car) > 3) + // if (!isHeli && (!isPlane || car->GetWheelsOnGround())) { + + CVector left = CrossProduct(car->GetForward(), CVector(0.0f, 0.0f, 1.0f)); + left.Normalise(); + CVector up = CrossProduct(left, car->GetForward()); + up.Normalise(); + float lookingUp = DotProduct(up, Front); + if (lookingUp > 0.0f) { + float v88 = Asin(Abs(Sin(Beta - (car->GetForward().Heading() - HALFPI)))); + float v200; + if (v88 <= Atan2(carCol->boundingBox.max.x, -carCol->boundingBox.min.y)) { + v200 = (1.5f - carCol->boundingBox.min.y) / Cos(v88); + } else { + float a6g = 1.2f + carCol->boundingBox.max.x; + v200 = a6g / Cos(Max(0.0f, HALFPI - v88)); + } + maxAlphaAllowed = Cos(Beta - (car->GetForward().Heading() - HALFPI)) * Atan2(car->GetForward().z, car->GetForward().Magnitude2D()) + + Atan2(TargetCoors.z - car->GetPosition().z + car->GetHeightAboveRoad(), v200 * 1.2f); + + if (isCar && ((CAutomobile*)car)->m_nWheelsOnGround > 1 && Abs(DotProduct(car->m_vecTurnSpeed, car->GetForward())) < 0.05f) { + maxAlphaAllowed += Cos(Beta - (car->GetForward().Heading() - HALFPI) + HALFPI) * Atan2(car->GetRight().z, car->GetRight().Magnitude2D()); + } + } + } + + float targetAlpha = Asin(Clamp(Front.z, -1.0f, 1.0f)) - zoomModeAlphaOffset; + if (targetAlpha <= maxAlphaAllowed) { + if (targetAlpha < -CARCAM_SET[camSetArrPos][14]) + targetAlpha = -CARCAM_SET[camSetArrPos][14]; + } else { + targetAlpha = maxAlphaAllowed; + } + float maxAlphaBlendAmount = CTimer::GetTimeStep() * CARCAM_SET[camSetArrPos][6]; + float targetAlphaBlendAmount = (1.0f - Pow(CARCAM_SET[camSetArrPos][5], CTimer::GetTimeStep())) * (targetAlpha - Alpha); + if (targetAlphaBlendAmount <= maxAlphaBlendAmount) { + if (targetAlphaBlendAmount < -maxAlphaBlendAmount) + targetAlphaBlendAmount = -maxAlphaBlendAmount; + } else { + targetAlphaBlendAmount = maxAlphaBlendAmount; + } + + // Using GetCarGun(LR/UD) will give us same unprocessed RightStick value as SA + float stickX = -(pad->GetCarGunLeftRight()); + float stickY = -pad->GetCarGunUpDown(); + + // In SA this is for not let num2/num8 move camera when Keyboard & Mouse controls are used. + // if (CCamera::m_bUseMouse3rdPerson) + // stickY = 0.0f; +#ifdef INVERT_LOOK_FOR_PAD + if (CPad::bInvertLook4Pad) + stickY = -stickY; +#endif + + float xMovement = Abs(stickX) * (FOV / 80.0f * 5.f / 70.f) * stickX * 0.007f * 0.007f; + float yMovement = Abs(stickY) * (FOV / 80.0f * 3.f / 70.f) * stickY * 0.007f * 0.007f; + + bool correctAlpha = true; + // if (SA checks if we aren't in work car, why?) { + if (!isCar || car->GetModelIndex() != MI_YARDIE) { + correctAlpha = false; + } + else { + xMovement = 0.0f; + yMovement = 0.0f; + } + // } else + // yMovement = 0.0; + + if (!nextDirectionIsForward) { + yMovement = 0.0f; + xMovement = 0.0f; + } + + if (camSetArrPos == 0 || camSetArrPos == 7) { + // This is not working on cars as SA + // Because III/VC doesn't have any buttons tied to LeftStick if you're not in Classic Configuration, using Dodo or using GInput/Pad, so :shrug: + if (Abs(pad->GetSteeringUpDown()) > 120.0f) { + if (car->pDriver && car->pDriver->m_objective != OBJECTIVE_LEAVE_CAR) { + yMovement += Abs(pad->GetSteeringUpDown()) * (FOV / 80.0f * 3.f / 70.f) * pad->GetSteeringUpDown() * 0.007f * 0.007f * 0.5; + } + } + } + + if (yMovement > 0.0) + yMovement = yMovement * 0.5; + + bool mouseChangesBeta = false; + + // FIX: Disable mouse movement in drive-by, it's buggy. Original SA bug. + if (/*bFreeMouseCam &&*/ CCamera::m_bUseMouse3rdPerson && !pad->ArePlayerControlsDisabled() && nextDirectionIsForward) { + float mouseY = pad->GetMouseY() * 2.0f; + float mouseX = pad->GetMouseX() * -2.0f; + + // If you want an ability to toggle free cam while steering with mouse, you can add an OR after DisableMouseSteering. + // There was a pad->NewState.m_bVehicleMouseLook in SA, which doesn't exists in III. + + if ((mouseX != 0.0 || mouseY != 0.0) && (CVehicle::m_bDisableMouseSteering)) { + yMovement = mouseY * FOV / 80.0f * TheCamera.m_fMouseAccelHorzntl; // Same as SA, horizontal sensitivity. + BetaSpeed = 0.0; + AlphaSpeed = 0.0; + xMovement = mouseX * FOV / 80.0f * TheCamera.m_fMouseAccelHorzntl; + targetAlpha = Alpha; + stepsLeftToChangeBetaByMouse = 1.0f * 50.0f; + mouseChangesBeta = true; + } else if (stepsLeftToChangeBetaByMouse > 0.0f) { + // Finish rotation by decreasing speed when we stopped moving mouse + BetaSpeed = 0.0; + AlphaSpeed = 0.0; + yMovement = 0.0; + xMovement = 0.0; + targetAlpha = Alpha; + stepsLeftToChangeBetaByMouse = Max(0.0f, stepsLeftToChangeBetaByMouse - CTimer::GetTimeStep()); + mouseChangesBeta = true; + } + } + + if (correctAlpha) { + if (nPreviousMode != MODE_CAM_ON_A_STRING) + alphaCorrected = false; + + if (!alphaCorrected && Abs(zoomModeAlphaOffset + Alpha) > 0.05f) { + yMovement = (-zoomModeAlphaOffset - Alpha) * 0.05f; + } else + alphaCorrected = true; + } + float alphaSpeedFromStickY = yMovement * CARCAM_SET[camSetArrPos][12]; + float betaSpeedFromStickX = xMovement * CARCAM_SET[camSetArrPos][12]; + + float newAngleSpeedMaxBlendAmount = CARCAM_SET[camSetArrPos][9]; + float angleChangeStep = Pow(CARCAM_SET[camSetArrPos][8], CTimer::GetTimeStep()); + float targetBetaWithStickBlendAmount = betaSpeedFromStickX + (targetBeta - Beta) / Max(CTimer::GetTimeStep(), 1.0f); + + if (targetBetaWithStickBlendAmount < -newAngleSpeedMaxBlendAmount) + targetBetaWithStickBlendAmount = -newAngleSpeedMaxBlendAmount; + else if (targetBetaWithStickBlendAmount > newAngleSpeedMaxBlendAmount) + targetBetaWithStickBlendAmount = newAngleSpeedMaxBlendAmount; + + float angleChangeStepLeft = 1.0f - angleChangeStep; + BetaSpeed = targetBetaWithStickBlendAmount * angleChangeStepLeft + angleChangeStep * BetaSpeed; + if (Abs(BetaSpeed) < 0.0001f) + BetaSpeed = 0.0f; + + float betaChangePerFrame; + if (mouseChangesBeta) + betaChangePerFrame = betaSpeedFromStickX; + else + betaChangePerFrame = CTimer::GetTimeStep() * BetaSpeed; + Beta = betaChangePerFrame + Beta; + + if (TheCamera.m_bJustCameOutOfGarage) { + float invHeading = Atan2(Front.y, Front.x); + if (invHeading < 0.0f) + invHeading += TWOPI; + + Beta = invHeading + PI; + } + + Beta = CGeneral::LimitRadianAngle(Beta); + if (Beta < 0.0f) + Beta += TWOPI; + + if ((camSetArrPos <= 1 || camSetArrPos == 7) && targetAlpha < Alpha && carPosChange >= newDistance) { + if (isCar && ((CAutomobile*)car)->m_nWheelsOnGround > 1) + // || isBike && GetMysteriousWheelRelatedThingBike(car) > 1) + alphaSpeedFromStickY += (targetAlpha - Alpha) * 0.075f; + } + + AlphaSpeed = angleChangeStepLeft * alphaSpeedFromStickY + angleChangeStep * AlphaSpeed; + float maxAlphaSpeed = newAngleSpeedMaxBlendAmount; + if (alphaSpeedFromStickY > 0.0f) + maxAlphaSpeed = maxAlphaSpeed * 0.5; + + if (AlphaSpeed <= maxAlphaSpeed) { + float minAlphaSpeed = -maxAlphaSpeed; + if (AlphaSpeed < minAlphaSpeed) + AlphaSpeed = minAlphaSpeed; + } else { + AlphaSpeed = maxAlphaSpeed; + } + + if (Abs(AlphaSpeed) < 0.0001f) + AlphaSpeed = 0.0f; + + float alphaWithSpeedAccounted; + if (mouseChangesBeta) { + alphaWithSpeedAccounted = alphaSpeedFromStickY + targetAlpha; + Alpha += alphaSpeedFromStickY; + } else { + alphaWithSpeedAccounted = CTimer::GetTimeStep() * AlphaSpeed + targetAlpha; + Alpha += targetAlphaBlendAmount; + } + + if (Alpha <= maxAlphaAllowed) { + float minAlphaAllowed = -CARCAM_SET[camSetArrPos][14]; + if (minAlphaAllowed > Alpha) { + Alpha = minAlphaAllowed; + AlphaSpeed = 0.0f; + } + } else { + Alpha = maxAlphaAllowed; + AlphaSpeed = 0.0f; + } + + // Prevent unsignificant angle changes + if (Abs(lastAlpha - Alpha) < 0.0001f) + Alpha = lastAlpha; + + lastAlpha = Alpha; + + if (Abs(lastBeta - Beta) < 0.0001f) + Beta = lastBeta; + + lastBeta = Beta; + + Front.x = -(Cos(Beta) * Cos(Alpha)); + Front.y = -(Sin(Beta) * Cos(Alpha)); + Front.z = Sin(Alpha); + GetVectorsReadyForRW(); + TheCamera.m_bCamDirectlyBehind = false; + TheCamera.m_bCamDirectlyInFront = false; + + Source = TargetCoors - newDistance * Front; + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + m_aTargetHistoryPosThree = m_aTargetHistoryPosOne; + float nextAlpha = alphaWithSpeedAccounted + zoomModeAlphaOffset; + float nextFrontX = -(Cos(Beta) * Cos(nextAlpha)); + float nextFrontY = -(Sin(Beta) * Cos(nextAlpha)); + float nextFrontZ = Sin(nextAlpha); + + m_aTargetHistoryPosOne.x = TargetCoors.x - nextFrontX * nextDistance; + m_aTargetHistoryPosOne.y = TargetCoors.y - nextFrontY * nextDistance; + m_aTargetHistoryPosOne.z = TargetCoors.z - nextFrontZ * nextDistance; + + m_aTargetHistoryPosTwo.x = TargetCoors.x - nextFrontX * newDistance; + m_aTargetHistoryPosTwo.y = TargetCoors.y - nextFrontY * newDistance; + m_aTargetHistoryPosTwo.z = TargetCoors.z - nextFrontZ * newDistance; + + // SA calls SetColVarsVehicle in here + if (nextDirectionIsForward) { + + // LCS uses exactly the same collision code as FollowPedWithMouse, so we will do so. + + // This is only in LCS! + float timestepFactor = Pow(0.99f, CTimer::GetTimeStep()); + dontCollideWithCars = (timestepFactor * dontCollideWithCars) + ((1.0f - timestepFactor) * car->m_vecMoveSpeed.Magnitude()); + + // Our addition +#define IS_TRAFFIC_LIGHT(ent) (ent->IsObject() && (IsStreetLight(ent->GetModelIndex()))) + + // Clip Source and fix near clip + CColPoint colPoint; + CEntity* entity; + CWorld::pIgnoreEntity = CamTargetEntity; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, dontCollideWithCars < 0.1f, false, true, false, true, true) && !IS_TRAFFIC_LIGHT(entity)){ + float PedColDist = (TargetCoors - colPoint.point).Magnitude(); + float ColCamDist = newDistance - PedColDist; + if(entity->IsPed() && ColCamDist > DEFAULT_NEAR + 0.1f){ + // Ped in the way but not clipping through + if(CWorld::ProcessLineOfSight(colPoint.point, Source, colPoint, entity, true, dontCollideWithCars < 0.1f, false, true, false, true, true) || IS_TRAFFIC_LIGHT(entity)){ + PedColDist = (TargetCoors - colPoint.point).Magnitude(); + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + }else{ + RwCameraSetNearClipPlane(Scene.camera, Min(ColCamDist-0.35f, DEFAULT_NEAR)); + } + }else{ + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + } + } + + CWorld::pIgnoreEntity = nil; + + // If we're seeing blue hell due to camera intersects some surface, fix it. + // SA and LCS have this unrolled. + + float ViewPlaneHeight = Tan(DEGTORAD(FOV) / 2.0f); + float ViewPlaneWidth = ViewPlaneHeight * CDraw::FindAspectRatio() * fTweakFOV; + float Near = RwCameraGetNearClipPlane(Scene.camera); + float radius = ViewPlaneWidth*Near; + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, true); + int i = 0; + while(entity){ + + if (IS_TRAFFIC_LIGHT(entity)) + break; + + CVector CamToCol = gaTempSphereColPoints[0].point - Source; + float frontDist = DotProduct(CamToCol, Front); + float dist = (CamToCol - Front*frontDist).Magnitude() / ViewPlaneWidth; + + // Try to decrease near clip + dist = Max(Min(Near, dist), 0.1f); + if(dist < Near) + RwCameraSetNearClipPlane(Scene.camera, dist); + + // Move forward a bit + if(dist == 0.1f) + Source += (TargetCoors - Source)*0.3f; + + // Keep testing + Near = RwCameraGetNearClipPlane(Scene.camera); + radius = ViewPlaneWidth*Near; + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, true); + + i++; + if(i > 5) + entity = nil; + } +#undef IS_TRAFFIC_LIGHT + } + TheCamera.m_bCamDirectlyBehind = false; + TheCamera.m_bCamDirectlyInFront = false; + + // ------- LCS specific part starts + + if (camSetArrPos == 5 && Source.z < 1.0f) // RC Bandit and Baron + Source.z = 1.0f; + + // Obviously some specific place in LC + if (Source.x > 11.0f && Source.x < 91.0f) { + if (Source.y > -680.0f && Source.y < -600.0f && Source.z < 24.4f) + Source.z = 24.4f; + } + + // CCam::FixSourceAboveWaterLevel + if (CameraTarget.z >= -2.0f) { + float level = -6000.0; + // +0.5f is needed for III + if (CWaterLevel::GetWaterLevelNoWaves(Source.x, Source.y, Source.z, &level)) { + if (Source.z < level + 0.5f) + Source.z = level + 0.5f; + } + } + Front = TargetCoors - Source; + + // -------- LCS specific part ends + + GetVectorsReadyForRW(); + // SA + // gTargetCoordsForLookingBehind = TargetCoors; + + // SA code from CAutomobile::TankControl/FireTruckControl. + if (car->GetModelIndex() == MI_RHINO || car->GetModelIndex() == MI_FIRETRUCK) { + + float &carGunLR = ((CAutomobile*)car)->m_fCarGunLR; + CVector hi = Multiply3x3(Front, car->GetMatrix()); + + // III/VC's firetruck turret angle is reversed + float angleToFace = (car->GetModelIndex() == MI_FIRETRUCK ? -hi.Heading() : hi.Heading()); + + if (angleToFace <= carGunLR + PI) { + if (angleToFace < carGunLR - PI) + angleToFace = angleToFace + TWOPI; + } else { + angleToFace = angleToFace - TWOPI; + } + + float neededTurn = angleToFace - carGunLR; + float turnPerFrame = CTimer::GetTimeStep() * (car->GetModelIndex() == MI_FIRETRUCK ? 0.05f : 0.015f); + if (neededTurn <= turnPerFrame) { + if (neededTurn < -turnPerFrame) + angleToFace = carGunLR - turnPerFrame; + } else { + angleToFace = turnPerFrame + carGunLR; + } + + if (car->GetModelIndex() == MI_RHINO && carGunLR != angleToFace) { + DMAudio.PlayOneShot(car->m_audioEntityId, SOUND_CAR_TANK_TURRET_ROTATE, Abs(angleToFace - carGunLR)); + } + carGunLR = angleToFace; + + if (carGunLR < -PI) { + carGunLR += TWOPI; + } else if (carGunLR > PI) { + carGunLR -= TWOPI; + } + + // Because firetruk turret also has Y movement + if (car->GetModelIndex() == MI_FIRETRUCK) { + float &carGunUD = ((CAutomobile*)car)->m_fCarGunUD; + + float alphaToFace = Atan2(hi.z, hi.Magnitude2D()) + DEGTORAD(15.0f); + float neededAlphaTurn = alphaToFace - carGunUD; + float alphaTurnPerFrame = CTimer::GetTimeStep() * 0.02f; + + if (neededAlphaTurn > alphaTurnPerFrame) { + neededTurn = alphaTurnPerFrame; + carGunUD = neededTurn + carGunUD; + } else { + if (neededAlphaTurn >= -alphaTurnPerFrame) { + carGunUD = alphaToFace; + } else { + carGunUD = carGunUD - alphaTurnPerFrame; + } + } + + float turretMinY = -DEGTORAD(20.0f); + float turretMaxY = DEGTORAD(20.0f); + if (turretMinY <= carGunUD) { + if (carGunUD > turretMaxY) + carGunUD = turretMaxY; + } else { + carGunUD = turretMinY; + } + } + } +} +#endif diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp new file mode 100644 index 0000000..f3b4165 --- /dev/null +++ b/src/core/Camera.cpp @@ -0,0 +1,3726 @@ +#include "common.h" + +#include "main.h" +#include "Draw.h" +#include "World.h" +#include "Vehicle.h" +#include "Train.h" +#include "Automobile.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Pad.h" +#include "ControllerConfig.h" +#include "General.h" +#include "ZoneCull.h" +#include "SurfaceTable.h" +#include "WaterLevel.h" +#include "World.h" +#include "Garages.h" +#include "Replay.h" +#include "CutsceneMgr.h" +#include "Renderer.h" +#include "MBlur.h" +#include "Text.h" +#include "Hud.h" +#include "DMAudio.h" +#include "FileMgr.h" +#include "Frontend.h" +#include "SceneEdit.h" +#include "Pools.h" +#include "Debug.h" +#include "GenericGameStorage.h" +#include "MemoryCard.h" +#include "Camera.h" + +enum +{ + // car + OBBE_WHEEL, + OBBE_1, + OBBE_2, + OBBE_3, + OBBE_1STPERSON, // unused + OBBE_5, + OBBE_ONSTRING, + OBBE_COPCAR, + OBBE_COPCAR_WHEEL, + // ped + OBBE_9, + OBBE_10, + OBBE_11, + OBBE_12, + OBBE_13, + + OBBE_INVALID +}; + +// abbreviate a few things +#define PLAYER (CWorld::Players[CWorld::PlayerInFocus].m_pPed) +// NB: removed explicit TheCamera from all functions + +CCamera TheCamera; +#ifdef PC_PLAYER_CONTROLS +bool CCamera::m_bUseMouse3rdPerson = true; +#else +bool CCamera::m_bUseMouse3rdPerson = false; +#endif +bool bDidWeProcessAnyCinemaCam; + +#ifdef IMPROVED_CAMERA +#define KEYJUSTDOWN(k) ControlsManager.GetIsKeyboardKeyJustDown((RsKeyCodes)k) +#define KEYDOWN(k) ControlsManager.GetIsKeyboardKeyDown((RsKeyCodes)k) +#define CTRLJUSTDOWN(key) \ + ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYJUSTDOWN((RsKeyCodes)key) || \ + (KEYJUSTDOWN(rsLCTRL) || KEYJUSTDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) +#define CTRLDOWN(key) ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) +#endif + +CCamera::CCamera(void) +{ +#if GTA_VERSION >= GTA3_PC_11 || defined(FIX_BUGS) + m_fMouseAccelHorzntl = 0.0025f; + m_fMouseAccelVertical = 0.003f; +#endif + Init(); +} + +CCamera::CCamera(float) +{ +} + +void +CCamera::Init(void) +{ +#if GTA_VERSION >= GTA3_PC_11 || defined(FIX_BUGS) + float fMouseAccelHorzntl = m_fMouseAccelHorzntl; + float fMouseAccelVertical = m_fMouseAccelVertical; +#endif + +#ifdef PS2_MENU + if ( !TheMemoryCard.m_bWantToLoad && !FrontEndMenuManager.m_bWantToRestart ) +#endif + { + #ifdef FIX_BUGS + static const CCamera DummyCamera = CCamera(0.f); + *this = DummyCamera; + #else + memset(this, 0, sizeof(CCamera)); // getting rid of vtable, eh? + #endif + + #if GTA_VERSION >= GTA3_PC_11 || defined(FIX_BUGS) + m_fMouseAccelHorzntl = fMouseAccelHorzntl; + m_fMouseAccelVertical = fMouseAccelVertical; + #endif + m_pRwCamera = nil; + + } + + m_1rstPersonRunCloseToAWall = false; + m_fPositionAlongSpline = 0.0f; + m_bCameraJustRestored = false; + Cams[0].Init(); + Cams[1].Init(); + Cams[2].Init(); + Cams[0].Mode = CCam::MODE_FOLLOWPED; + Cams[1].Mode = CCam::MODE_FOLLOWPED; + unknown = 0; + m_bUnknown = false; + ClearPlayerWeaponMode(); + m_bInATunnelAndABigVehicle = false; + m_iModeObbeCamIsInForCar = OBBE_INVALID; + Cams[0].CamTargetEntity = nil; + Cams[1].CamTargetEntity = nil; + Cams[2].CamTargetEntity = nil; + Cams[0].m_fCamBufferedHeight = 0.0f; + Cams[0].m_fCamBufferedHeightSpeed = 0.0f; + Cams[1].m_fCamBufferedHeight = 0.0f; + Cams[1].m_fCamBufferedHeightSpeed = 0.0f; + Cams[0].m_bCamLookingAtVector = false; + Cams[1].m_bCamLookingAtVector = false; + Cams[2].m_bCamLookingAtVector = false; + Cams[0].m_fPlayerVelocity = 0.0f; + Cams[1].m_fPlayerVelocity = 0.0f; + Cams[2].m_fPlayerVelocity = 0.0f; + m_bHeadBob = false; + m_fFractionInterToStopMoving = 0.25f; + m_fFractionInterToStopCatchUp = 0.75f; + m_fGaitSwayBuffer = 0.85f; + m_bScriptParametersSetForInterPol = false; + m_uiCamShakeStart = 0; + m_fCamShakeForce = 0.0f; + m_iModeObbeCamIsInForCar = OBBE_INVALID; + m_bIgnoreFadingStuffForMusic = false; + m_bWaitForInterpolToFinish = false; + pToGarageWeAreIn = nil; + pToGarageWeAreInForHackAvoidFirstPerson = nil; + m_bPlayerIsInGarage = false; + m_bJustCameOutOfGarage = false; + m_fNearClipScript = DEFAULT_NEAR; + m_bUseNearClipScript = false; + m_vecDoingSpecialInterPolation = false; + m_bAboveGroundTrainNodesLoaded = false; + m_bBelowGroundTrainNodesLoaded = false; + m_WideScreenOn = false; + m_fFOV_Wide_Screen = 0.0f; + m_bRestoreByJumpCut = false; + CarZoomIndicator = CAM_ZOOM_2; + PedZoomIndicator = CAM_ZOOM_2; + CarZoomValueSmooth = 0.0f; + m_fPedZoomValueSmooth = 0.0f; + pTargetEntity = nil; + if(FindPlayerVehicle()) + pTargetEntity = FindPlayerVehicle(); + else + pTargetEntity = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + m_bInitialNodeFound = false; + m_ScreenReductionPercentage = 0.0f; + m_ScreenReductionSpeed = 0.0f; + m_WideScreenOn = false; + m_bWantsToSwitchWidescreenOff = false; + WorldViewerBeingUsed = false; + PlayerExhaustion = 1.0f; + DebugCamMode = CCam::MODE_NONE; + m_PedOrientForBehindOrInFront = 0.0f; +#ifdef PS2_MENU + if ( !TheMemoryCard.m_bWantToLoad && !FrontEndMenuManager.m_bWantToRestart ) +#else + if(!FrontEndMenuManager.m_bWantToRestart) +#endif + { + m_bFading = false; + CDraw::FadeValue = 0; + m_fFLOATingFade = 0.0f; + m_bMusicFading = false; + m_fTimeToFadeMusic = 0.0f; + m_fFLOATingFadeMusic = 0.0f; + } + m_bMoveCamToAvoidGeom = false; +#ifdef PS2_MENU + if ( TheMemoryCard.m_bWantToLoad || FrontEndMenuManager.m_bWantToRestart ) +#else + if(FrontEndMenuManager.m_bWantToRestart) +#endif + m_bMoveCamToAvoidGeom = true; + m_bStartingSpline = false; + m_iTypeOfSwitch = INTERPOLATION; + m_bUseScriptZoomValuePed = false; + m_bUseScriptZoomValueCar = false; + m_fPedZoomValueScript = 0.0f; + m_fCarZoomValueScript = 0.0f; + m_bUseSpecialFovTrain = false; + m_fFovForTrain = 70.0f; // or DefaultFOV from Cam.cpp + m_iModeToGoTo = CCam::MODE_FOLLOWPED; + m_bJust_Switched = false; + m_bUseTransitionBeta = false; + GetMatrix().SetScale(1.0f); + m_bTargetJustBeenOnTrain = false; + m_bInitialNoNodeStaticsSet = false; + m_uiLongestTimeInMill = 5000; + m_uiTimeLastChange = 0; + m_uiTimeWeEnteredIdle = 0; + m_bIdleOn = false; + LODDistMultiplier = 1.0f; + m_bCamDirectlyBehind = false; + m_bCamDirectlyInFront = false; + m_motionBlur = 0; + m_bGarageFixedCamPositionSet = false; + SetMotionBlur(255, 255, 255, 0, 0); + m_bCullZoneChecksOn = false; + m_bFailedCullZoneTestPreviously = false; + m_iCheckCullZoneThisNumFrames = 6; + m_iZoneCullFrameNumWereAt = 0; + m_CameraAverageSpeed = 0.0f; + m_CameraSpeedSoFar = 0.0f; + m_PreviousCameraPosition = CVector(0.0f, 0.0f, 0.0f); + m_iWorkOutSpeedThisNumFrames = 4; + m_iNumFramesSoFar = 0; + m_bJustInitalised = true; + m_uiTransitionState = 0; + m_uiTimeTransitionStart = 0; + m_bLookingAtPlayer = true; +#if GTA_VERSION < GTA3_PC_11 && !defined(FIX_BUGS) + m_fMouseAccelHorzntl = 0.0025f; + m_fMouseAccelVertical = 0.003f; +#endif + m_f3rdPersonCHairMultX = 0.53f; + m_f3rdPersonCHairMultY = 0.4f; +} + +void +CCamera::Process(void) +{ + // static bool InterpolatorNotInitialised = true; // unused + static CVector PreviousFudgedTargetCoors; // only PS2 + static float PlayerMinDist = 1.6f; // not on PS2 + static bool WasPreviouslyInterSyhonFollowPed = false; // only used on PS2 + float FOV = 0.0f; + float oldBeta, newBeta; + float deltaBeta = 0.0f; + bool lookLRBVehicle = false; + CVector CamFront, CamUp, CamRight, CamSource, Target; + + m_bJust_Switched = false; + m_RealPreviousCameraPosition = GetPosition(); + + // Update target entity + if(m_bLookingAtPlayer || m_bTargetJustBeenOnTrain || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) + UpdateTargetEntity(); + if(pTargetEntity == nil) + pTargetEntity = FindPlayerPed(); + if(Cams[ActiveCam].CamTargetEntity == nil) + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + if(Cams[(ActiveCam+1)%2].CamTargetEntity == nil) + Cams[(ActiveCam+1)%2].CamTargetEntity = pTargetEntity; + + CamControl(); + if(m_bFading) + ProcessFade(); + if(m_bMusicFading) + ProcessMusicFade(); + if(m_WideScreenOn) + ProcessWideScreenOn(); + +#ifdef IMPROVED_CAMERA + if(CPad::GetPad(1)->GetCircleJustDown() || CTRLJUSTDOWN('B')){ +#else + if(CPad::GetPad(1)->GetCircleJustDown()){ +#endif + WorldViewerBeingUsed = !WorldViewerBeingUsed; + if(WorldViewerBeingUsed) + InitialiseCameraForDebugMode(); + else + CPad::m_bMapPadOneToPadTwo = false; + } + + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + + if(Cams[ActiveCam].Front.x == 0.0f && Cams[ActiveCam].Front.y == 0.0f) + oldBeta = 0.0f; + else + oldBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + + Cams[ActiveCam].Process(); + Cams[ActiveCam].ProcessSpecialHeightRoutines(); + + if(Cams[ActiveCam].Front.x == 0.0f && Cams[ActiveCam].Front.y == 0.0f) + newBeta = 0.0f; + else + newBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + + + // Stop transition when it's done + if(m_uiTransitionState != 0){ +#ifdef PS2_CAM_TRANSITION + if(!m_bWaitForInterpolToFinish){ + Cams[(ActiveCam+1)%2].Process(); + Cams[(ActiveCam+1)%2].ProcessSpecialHeightRoutines(); + } +#else + // done in CamControl on PS2 it seems + if(CTimer::GetTimeInMilliseconds() > m_uiTransitionDuration+m_uiTimeTransitionStart){ + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + m_bWaitForInterpolToFinish = false; + } +#endif + } + + if(m_bUseNearClipScript) + RwCameraSetNearClipPlane(Scene.camera, m_fNearClipScript); + + deltaBeta = newBeta - oldBeta; + while(deltaBeta >= PI) deltaBeta -= 2*PI; + while(deltaBeta < -PI) deltaBeta += 2*PI; + if(Abs(deltaBeta) > 0.3f) + m_bJust_Switched = true; + + // Debug stuff + if(!gbModelViewer) + Cams[ActiveCam].PrintMode(); + if(WorldViewerBeingUsed) + Cams[2].Process(); + + if(Cams[ActiveCam].DirectionWasLooking != LOOKING_FORWARD && pTargetEntity->IsVehicle()) + lookLRBVehicle = true; + + if(m_uiTransitionState != 0 && !lookLRBVehicle){ + // Process transition +#ifdef PS2_CAM_TRANSITION + bool lookingAtPlayerNow = false; + bool wasLookingAtPlayer = false; + bool transitionPedMode = false; + bool setWait = false; + if(Cams[ActiveCam].CamTargetEntity == Cams[(ActiveCam+1)%2].CamTargetEntity){ + if(Cams[ActiveCam].Mode == CCam::MODE_SYPHON || + Cams[ActiveCam].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT || + Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED || + Cams[ActiveCam].Mode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON) + lookingAtPlayerNow = true; + if(Cams[(ActiveCam+1)%2].Mode == CCam::MODE_SYPHON || + Cams[(ActiveCam+1)%2].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT || + Cams[(ActiveCam+1)%2].Mode == CCam::MODE_FOLLOWPED || + Cams[(ActiveCam+1)%2].Mode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON) // checked twice for some reason + wasLookingAtPlayer = true; + + if(!m_vecDoingSpecialInterPolation && + (Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED || Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM) && + (Cams[(ActiveCam+1)%2].Mode == CCam::MODE_FOLLOWPED || Cams[(ActiveCam+1)%2].Mode == CCam::MODE_FIGHT_CAM)) + transitionPedMode = true; + } + + if(lookingAtPlayerNow && wasLookingAtPlayer){ + CVector playerDist; + playerDist.x = FindPlayerPed()->GetPosition().x - GetPosition().x; + playerDist.y = FindPlayerPed()->GetPosition().y - GetPosition().y; + playerDist.z = FindPlayerPed()->GetPosition().z - GetPosition().z; + if(playerDist.Magnitude() > 17.5f && + (Cams[ActiveCam].Mode == CCam::MODE_SYPHON || Cams[ActiveCam].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT)) + setWait = true; + } + if(setWait) + m_bWaitForInterpolToFinish = true; + + + uint32 currentTime = CTimer::GetTimeInMilliseconds() - m_uiTimeTransitionStart; + if(currentTime >= m_uiTransitionDuration) + currentTime = m_uiTransitionDuration; + float inter = (float) currentTime / m_uiTransitionDuration; + inter = 0.5f - 0.5*Cos(inter*PI); // smooth it + + if(m_vecDoingSpecialInterPolation){ + Cams[(ActiveCam+1)%2].Source = m_vecOldSourceForInter; + Cams[(ActiveCam+1)%2].Front = m_vecOldFrontForInter; + Cams[(ActiveCam+1)%2].Up = m_vecOldUpForInter; + Cams[(ActiveCam+1)%2].FOV = m_vecOldFOVForInter; + if(WasPreviouslyInterSyhonFollowPed) + Cams[(ActiveCam+1)%2].m_cvecTargetCoorsForFudgeInter.z = PreviousFudgedTargetCoors.z; + } + + CamSource = inter*Cams[ActiveCam].Source + (1.0f-inter)*Cams[(ActiveCam+1)%2].Source; + FOV = inter*Cams[ActiveCam].FOV + (1.0f-inter)*Cams[(ActiveCam+1)%2].FOV; + + CVector tmpFront = Cams[(ActiveCam+1)%2].Front; + float Alpha_other = CGeneral::GetATanOfXY(tmpFront.Magnitude2D(), tmpFront.z); + if(Alpha_other > PI) Alpha_other -= TWOPI; + float Beta_other = 0.0f; + if(tmpFront.x != 0.0f || tmpFront.y != 0.0f) + Beta_other = CGeneral::GetATanOfXY(-tmpFront.y, tmpFront.x); + tmpFront = Cams[ActiveCam].Front; + float Alpha_active = CGeneral::GetATanOfXY(tmpFront.Magnitude2D(), tmpFront.z); + if(Alpha_active > PI) Alpha_active -= TWOPI; + float Beta_active = 0.0f; + if(tmpFront.x != 0.0f || tmpFront.y != 0.0f) + Beta_active = CGeneral::GetATanOfXY(-tmpFront.y, tmpFront.x); + + float DeltaBeta = Beta_active - Beta_other; + float Alpha = inter*Alpha_active + (1.0f-inter)*Alpha_other; + + if(m_uiTransitionJUSTStarted){ + while(DeltaBeta > PI) DeltaBeta -= TWOPI; + while(DeltaBeta <= -PI) DeltaBeta += TWOPI; + m_uiTransitionJUSTStarted = false; + }else{ + if(DeltaBeta < m_fOldBetaDiff) + while(Abs(DeltaBeta - m_fOldBetaDiff) > PI) DeltaBeta += TWOPI; + else + while(Abs(DeltaBeta - m_fOldBetaDiff) > PI) DeltaBeta -= TWOPI; + } + m_fOldBetaDiff = DeltaBeta; + float Beta = inter*DeltaBeta + Beta_other; + + CVector FudgedTargetCoors; + if(lookingAtPlayerNow && wasLookingAtPlayer){ + // BUG? how is this interpolation ever used when values are overwritten below? + float PlayerDist = (pTargetEntity->GetPosition() - CamSource).Magnitude2D(); + float MinDist = Min(Cams[(ActiveCam+1)%2].m_fMinDistAwayFromCamWhenInterPolating, Cams[ActiveCam].m_fMinDistAwayFromCamWhenInterPolating); + if(PlayerDist < MinDist){ + CamSource.x = pTargetEntity->GetPosition().x - MinDist*Cos(Beta - HALFPI); + CamSource.y = pTargetEntity->GetPosition().y - MinDist*Sin(Beta - HALFPI); + }else{ + CamSource.x = pTargetEntity->GetPosition().x - PlayerDist*Cos(Beta - HALFPI); + CamSource.y = pTargetEntity->GetPosition().y - PlayerDist*Sin(Beta - HALFPI); + } + + CColPoint colpoint; + CEntity *entity = nil; + if(CWorld::ProcessLineOfSight(pTargetEntity->GetPosition(), CamSource, colpoint, entity, true, false, false, true, false, true, true)){ + CamSource = colpoint.point; + RwCameraSetNearClipPlane(Scene.camera, 0.05f); + } + + CamFront = pTargetEntity->GetPosition() - CamSource; + FudgedTargetCoors = inter*Cams[ActiveCam].m_cvecTargetCoorsForFudgeInter + (1.0f-inter)*Cams[(ActiveCam+1)%2].m_cvecTargetCoorsForFudgeInter; + PreviousFudgedTargetCoors = FudgedTargetCoors; + CamFront.Normalise(); + CamUp = CVector(0.0f, 0.0f, 1.0f); + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + + WasPreviouslyInterSyhonFollowPed = true; + }else + WasPreviouslyInterSyhonFollowPed = false; + + if(transitionPedMode){ + FudgedTargetCoors = inter*Cams[ActiveCam].m_cvecTargetCoorsForFudgeInter + (1.0f-inter)*Cams[(ActiveCam+1)%2].m_cvecTargetCoorsForFudgeInter; + PreviousFudgedTargetCoors = FudgedTargetCoors; + CVector CamToTarget = pTargetEntity->GetPosition() - CamSource; + float tmpBeta = CGeneral::GetATanOfXY(CamToTarget.x, CamToTarget.y); + float PlayerDist = (pTargetEntity->GetPosition() - CamSource).Magnitude2D(); + float MinDist = Min(Cams[(ActiveCam+1)%2].m_fMinDistAwayFromCamWhenInterPolating, Cams[ActiveCam].m_fMinDistAwayFromCamWhenInterPolating); + if(PlayerDist < MinDist){ + CamSource.x = pTargetEntity->GetPosition().x - MinDist*Cos(tmpBeta - HALFPI); + CamSource.y = pTargetEntity->GetPosition().y - MinDist*Sin(tmpBeta - HALFPI); + } + CamFront = FudgedTargetCoors - CamSource; + CamFront.Normalise(); + CamUp = CVector(0.0f, 0.0f, 1.0f); + CamUp.Normalise(); + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + CamUp.Normalise(); + }else{ + CamFront.x = Cos(Alpha) * Sin(Beta); + CamFront.y = Cos(Alpha) * -Cos(Beta); + CamFront.z = Sin(Alpha); + CamFront.Normalise(); + CamUp = inter*Cams[ActiveCam].Up + (1.0f-inter)*Cams[(ActiveCam+1)%2].Up; + CamUp.Normalise(); + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + CamUp.Normalise(); + } +#else + uint32 currentTime = CTimer::GetTimeInMilliseconds() - m_uiTimeTransitionStart; + if(currentTime >= m_uiTransitionDuration) + currentTime = m_uiTransitionDuration; + float fractionInter = (float) currentTime / m_uiTransitionDuration; + + if(fractionInter <= m_fFractionInterToStopMoving){ + float inter; + if(m_fFractionInterToStopMoving == 0.0f) + inter = 0.0f; + else + inter = (m_fFractionInterToStopMoving - fractionInter)/m_fFractionInterToStopMoving; + inter = 0.5f - 0.5*Cos(inter*PI); // smooth it + + m_vecSourceWhenInterPol = m_cvecStartingSourceForInterPol + inter*m_cvecSourceSpeedAtStartInter; + m_vecTargetWhenInterPol = m_cvecStartingTargetForInterPol + inter*m_cvecTargetSpeedAtStartInter; + m_vecUpWhenInterPol = m_cvecStartingUpForInterPol + inter*m_cvecUpSpeedAtStartInter; + m_fFOVWhenInterPol = m_fStartingFOVForInterPol + inter*m_fFOVSpeedAtStartInter; + + CamSource = m_vecSourceWhenInterPol; + + if(m_bItsOkToLookJustAtThePlayer){ + m_vecTargetWhenInterPol.x = FindPlayerPed()->GetPosition().x; + m_vecTargetWhenInterPol.y = FindPlayerPed()->GetPosition().y; + m_fBetaWhenInterPol = m_fStartingBetaForInterPol + inter*m_fBetaSpeedAtStartInter; + + float dist = (CamSource - m_vecTargetWhenInterPol).Magnitude2D(); + if(dist < PlayerMinDist){ + if(dist > 0.0f){ + CamSource.x = m_vecTargetWhenInterPol.x + PlayerMinDist*Cos(m_fBetaWhenInterPol); + CamSource.y = m_vecTargetWhenInterPol.y + PlayerMinDist*Sin(m_fBetaWhenInterPol); + }else{ + // can only be 0.0 now... + float beta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + CamSource.x = m_vecTargetWhenInterPol.x + PlayerMinDist*Cos(beta); + CamSource.y = m_vecTargetWhenInterPol.y + PlayerMinDist*Sin(beta); + } + }else{ + CamSource.x = m_vecTargetWhenInterPol.x + dist*Cos(m_fBetaWhenInterPol); + CamSource.y = m_vecTargetWhenInterPol.y + dist*Sin(m_fBetaWhenInterPol); + } + } + + CamFront = m_vecTargetWhenInterPol - CamSource; + StoreValuesDuringInterPol(CamSource, m_vecTargetWhenInterPol, m_vecUpWhenInterPol, m_fFOVWhenInterPol); + Target = m_vecTargetWhenInterPol; + CamFront.Normalise(); + if(m_bLookingAtPlayer) + CamUp = CVector(0.0f, 0.0f, 1.0f); + else + CamUp = m_vecUpWhenInterPol; + CamUp.Normalise(); + + if(Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + CamFront.Normalise(); + CamRight = CVector(-1.0f, 0.0f, 0.0f); + CamUp = CrossProduct(CamFront, CamRight); + CamUp.Normalise(); + }else{ + CamFront.Normalise(); + CamUp.Normalise(); + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + CamUp.Normalise(); + } + FOV = m_fFOVWhenInterPol; + }else if(fractionInter > m_fFractionInterToStopMoving && fractionInter <= 1.0f){ + float inter; + if(m_fFractionInterToStopCatchUp == 0.0f) + inter = 0.0f; + else + inter = (fractionInter - m_fFractionInterToStopMoving)/m_fFractionInterToStopCatchUp; + inter = 0.5f - 0.5*Cos(inter*PI); // smooth it + + CamSource = m_vecSourceWhenInterPol + inter*(Cams[ActiveCam].Source - m_vecSourceWhenInterPol); + FOV = m_fFOVWhenInterPol + inter*(Cams[ActiveCam].FOV - m_fFOVWhenInterPol); + Target = m_vecTargetWhenInterPol + inter*(Cams[ActiveCam].m_cvecTargetCoorsForFudgeInter - m_vecTargetWhenInterPol); + CamUp = m_vecUpWhenInterPol + inter*(Cams[ActiveCam].Up - m_vecUpWhenInterPol); + deltaBeta = Cams[ActiveCam].m_fTrueBeta - m_fBetaWhenInterPol; + MakeAngleLessThan180(deltaBeta); + float interpBeta = m_fBetaWhenInterPol + inter*deltaBeta; + + if(m_bItsOkToLookJustAtThePlayer){ + Target.x = FindPlayerPed()->GetPosition().x; + Target.y = FindPlayerPed()->GetPosition().y; + + float dist = (CamSource - Target).Magnitude2D(); + if(dist < PlayerMinDist){ + if(dist > 0.0f){ + CamSource.x = Target.x + PlayerMinDist*Cos(interpBeta); + CamSource.y = Target.y + PlayerMinDist*Sin(interpBeta); + }else{ + // can only be 0.0 now... + float beta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + CamSource.x = Target.x + PlayerMinDist*Cos(beta); + CamSource.y = Target.y + PlayerMinDist*Sin(beta); + } + }else{ + CamSource.x = Target.x + dist*Cos(interpBeta); + CamSource.y = Target.y + dist*Sin(interpBeta); + } + } + + CamFront = Target - CamSource; + StoreValuesDuringInterPol(CamSource, Target, CamUp, FOV); + CamFront.Normalise(); + if(m_bLookingAtPlayer) + CamUp = CVector(0.0f, 0.0f, 1.0f); + + if(Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + CamFront.Normalise(); + CamRight = CVector(-1.0f, 0.0f, 0.0f); + CamUp = CrossProduct(CamFront, CamRight); + CamUp.Normalise(); + }else{ + CamFront.Normalise(); + CamUp.Normalise(); + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + CamUp.Normalise(); + } +#ifndef FIX_BUGS + // BUG: FOV was already interpolated but m_fFOVWhenInterPol was not + FOV = m_fFOVWhenInterPol; +#endif + } + + CVector Dist = CamSource - Target; + float DistOnGround = Dist.Magnitude2D(); + float Alpha = CGeneral::GetATanOfXY(DistOnGround, Dist.z); + float Beta = CGeneral::GetATanOfXY(Dist.x, Dist.y); + Cams[ActiveCam].KeepTrackOfTheSpeed(CamSource, Target, CamUp, Alpha, Beta, FOV); +#endif + }else{ + // No transition, take Cam values directly + if(WorldViewerBeingUsed){ + CamSource = Cams[2].Source; + CamFront = Cams[2].Front; + CamUp = Cams[2].Up; + FOV = Cams[2].FOV; + }else{ + CamSource = Cams[ActiveCam].Source; + CamFront = Cams[ActiveCam].Front; + CamUp = Cams[ActiveCam].Up; + FOV = Cams[ActiveCam].FOV; + } + WasPreviouslyInterSyhonFollowPed = false; // only used on PS2 + } + + if(m_uiTransitionState != 0) + if(!m_bLookingAtVector && m_bLookingAtPlayer && !CCullZones::CamStairsForPlayer() && !m_bPlayerIsInGarage){ + CEntity *entity = nil; + CColPoint colPoint; + if(CWorld::ProcessLineOfSight(pTargetEntity->GetPosition(), CamSource, colPoint, entity, true, false, false, true, false, true, true)){ + CamSource = colPoint.point; + RwCameraSetNearClipPlane(Scene.camera, 0.05f); + } + } + + GetMatrix().GetRight() = CrossProduct(CamUp, CamFront); // actually Left + GetMatrix().GetForward() = CamFront; + GetMatrix().GetUp() = CamUp; + GetMatrix().GetPosition() = CamSource; + + // Process Shake + float shakeStrength = m_fCamShakeForce - 0.28f*(CTimer::GetTimeInMilliseconds()-m_uiCamShakeStart)/1000.0f; + shakeStrength = Clamp(shakeStrength, 0.0f, 2.0f); + int shakeRand = CGeneral::GetRandomNumber(); + float shakeOffset = shakeStrength*0.1f; + GetMatrix().GetPosition().x += shakeOffset * ((shakeRand & 0xF) - 7); + GetMatrix().GetPosition().y += shakeOffset * (((shakeRand & 0xF0) >> 4) - 7); + GetMatrix().GetPosition().z += shakeOffset * (((shakeRand & 0xF00) >> 8) - 7); + + if(shakeOffset > 0.0f && m_BlurType != MOTION_BLUR_SNIPER) + SetMotionBlurAlpha(Min((int)(shakeStrength*255.0f) + 25, 150)); + if(Cams[ActiveCam].Mode == CCam::MODE_1STPERSON && FindPlayerVehicle() && FindPlayerVehicle()->GetUp().z < 0.2f) + SetMotionBlur(230, 230, 230, 215, MOTION_BLUR_LIGHT_SCENE); + + CalculateDerivedValues(); + CDraw::SetFOV(FOV); + + // Set RW camera + if(WorldViewerBeingUsed){ + RwFrame *frame = RwCameraGetFrame(m_pRwCamera); + CVector Source = Cams[2].Source; + CVector Front = Cams[2].Front; + CVector Up = Cams[2].Up; + + GetMatrix().GetRight() = CrossProduct(Up, Front); + GetMatrix().GetForward() = Front; + GetMatrix().GetUp() = Up; + GetMatrix().GetPosition() = Source; + + CDraw::SetFOV(Cams[2].FOV); + m_vecGameCamPos = Cams[ActiveCam].Source; + + *RwMatrixGetPos(RwFrameGetMatrix(frame)) = GetPosition(); + *RwMatrixGetAt(RwFrameGetMatrix(frame)) = GetForward(); + *RwMatrixGetUp(RwFrameGetMatrix(frame)) = GetUp(); + *RwMatrixGetRight(RwFrameGetMatrix(frame)) = GetRight(); + RwMatrixUpdate(RwFrameGetMatrix(frame)); + RwFrameUpdateObjects(frame); + }else{ + RwFrame *frame = RwCameraGetFrame(m_pRwCamera); + m_vecGameCamPos = GetPosition(); + *RwMatrixGetPos(RwFrameGetMatrix(frame)) = GetPosition(); + *RwMatrixGetAt(RwFrameGetMatrix(frame)) = GetForward(); + *RwMatrixGetUp(RwFrameGetMatrix(frame)) = GetUp(); + *RwMatrixGetRight(RwFrameGetMatrix(frame)) = GetRight(); + RwMatrixUpdate(RwFrameGetMatrix(frame)); + RwFrameUpdateObjects(frame); + } + + CDraw::SetNearClipZ(RwCameraGetNearClipPlane(m_pRwCamera)); + CDraw::SetFarClipZ(RwCameraGetFarClipPlane(m_pRwCamera)); + + UpdateSoundDistances(); + + if((CTimer::GetFrameCounter()&0xF) == 3) + DistanceToWater = CWaterLevel::CalcDistanceToWater(GetPosition().x, GetPosition().y); + + // LOD dist + if(!CCutsceneMgr::IsRunning() || CCutsceneMgr::UseLodMultiplier()){ + LODDistMultiplier = 70.0f/CDraw::GetFOV(); +#ifndef FIX_BUGS + // makes no sense and gone in VC + LODDistMultiplier *= CDraw::GetAspectRatio()/(4.0f/3.0f); +#endif + }else + LODDistMultiplier = 1.0f; +#if GTA_VERSION > GTA3_PS2_160 + GenerationDistMultiplier = LODDistMultiplier; + LODDistMultiplier *= CRenderer::ms_lodDistScale; +#endif + + // Keep track of speed + if(m_bJustInitalised || m_bJust_Switched){ + m_PreviousCameraPosition = GetPosition(); + m_bJustInitalised = false; + } + m_CameraSpeedSoFar += (GetPosition() - m_PreviousCameraPosition).Magnitude(); + m_iNumFramesSoFar++; + if(m_iNumFramesSoFar == m_iWorkOutSpeedThisNumFrames){ + m_CameraAverageSpeed = m_CameraSpeedSoFar / m_iWorkOutSpeedThisNumFrames; + m_CameraSpeedSoFar = 0.0f; + m_iNumFramesSoFar = 0; + } + m_PreviousCameraPosition = GetPosition(); + + // PS2 normalizes a CVector2D GetForward() here. is it used anywhere? + + if(Cams[ActiveCam].DirectionWasLooking != LOOKING_FORWARD && Cams[ActiveCam].Mode != CCam::MODE_TOP_DOWN_PED){ + Cams[ActiveCam].Source = Cams[ActiveCam].SourceBeforeLookBehind; + Orientation += PI; + } + + if(m_uiTransitionState != 0){ + int OtherCam = (ActiveCam+1)%2; + if(Cams[OtherCam].CamTargetEntity && + pTargetEntity && pTargetEntity->IsPed() && + !Cams[OtherCam].CamTargetEntity->IsVehicle() && + Cams[ActiveCam].Mode != CCam::MODE_TOP_DOWN_PED && Cams[ActiveCam].DirectionWasLooking != LOOKING_FORWARD){ + Cams[OtherCam].Source = Cams[ActiveCam%2].SourceBeforeLookBehind; + Orientation += PI; + } + } + + m_bCameraJustRestored = false; +} + +void +CCamera::CamControl(void) +{ + static bool PlaceForFixedWhenSniperFound = false; + static int16 ReqMode; + bool disableGarageCam = false; + bool switchByJumpCut = false; + bool stairs = false; + bool boatTarget = false; + CVector targetPos; + CVector garageCenter, garageDoorPos1, garageDoorPos2; + CVector garageCenterToDoor, garageCamPos; + int whichDoor; + + m_bObbeCinematicPedCamOn = false; + m_bObbeCinematicCarCamOn = false; + m_bUseTransitionBeta = false; + m_bUseSpecialFovTrain = false; + m_bJustCameOutOfGarage = false; + m_bTargetJustCameOffTrain = false; + m_bInATunnelAndABigVehicle = false; + + if(Cams[ActiveCam].CamTargetEntity == nil && pTargetEntity == nil) + pTargetEntity = PLAYER; + +#ifdef PS2_CAM_TRANSITION + // Stop transition when it's done + if(m_uiTransitionState != 0) + if(CTimer::GetTimeInMilliseconds() > m_uiTransitionDuration+m_uiTimeTransitionStart){ + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + m_bWaitForInterpolToFinish = false; + } +#endif + + m_iZoneCullFrameNumWereAt++; + if(m_iZoneCullFrameNumWereAt > m_iCheckCullZoneThisNumFrames) + m_iZoneCullFrameNumWereAt = 1; + m_bCullZoneChecksOn = m_iZoneCullFrameNumWereAt == m_iCheckCullZoneThisNumFrames; + if(m_bCullZoneChecksOn) + m_bFailedCullZoneTestPreviously = CCullZones::CamCloseInForPlayer(); + + if(m_bLookingAtPlayer){ + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_CAMERA); + FindPlayerPed()->bIsVisible = true; + } + + if(!CTimer::GetIsPaused()){ + float CloseInCarHeightTarget = 0.0f; + float CloseInPedHeightTarget = 0.0f; + + if(m_bTargetJustBeenOnTrain){ + // Getting off train + if(!pTargetEntity->IsVehicle() || !((CVehicle*)pTargetEntity)->IsTrain()){ + Restore(); + m_bTargetJustCameOffTrain = true; + m_bTargetJustBeenOnTrain = false; + SetWideScreenOff(); + } + } + + // Vehicle target + if(pTargetEntity->IsVehicle()){ + if(((CVehicle*)pTargetEntity)->IsTrain()){ + if(!m_bTargetJustBeenOnTrain){ + m_bInitialNodeFound = false; + m_bInitialNoNodeStaticsSet = false; + } + Process_Train_Camera_Control(); + }else{ + if(((CVehicle*)pTargetEntity)->IsBoat()) + boatTarget = true; + + // Change user selected mode + if(CPad::GetPad(0)->CycleCameraModeUpJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn) + CarZoomIndicator--; + if(CPad::GetPad(0)->CycleCameraModeDownJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn) + CarZoomIndicator++; + if(!m_bFailedCullZoneTestPreviously){ + if(CarZoomIndicator < CAM_ZOOM_1STPRS) CarZoomIndicator = CAM_ZOOM_CINEMATIC; + else if(CarZoomIndicator > CAM_ZOOM_CINEMATIC) CarZoomIndicator = CAM_ZOOM_1STPRS; + } + + if(m_bFailedCullZoneTestPreviously) + if(CarZoomIndicator != CAM_ZOOM_1STPRS && CarZoomIndicator != CAM_ZOOM_TOPDOWN) + ReqMode = CCam::MODE_CAM_ON_A_STRING; + + switch(((CVehicle*)pTargetEntity)->m_vehType){ + case VEHICLE_TYPE_CAR: + case VEHICLE_TYPE_BIKE: + if(CGarages::IsPointInAGarageCameraZone(pTargetEntity->GetPosition())){ + if(!m_bGarageFixedCamPositionSet && m_bLookingAtPlayer || + WhoIsInControlOfTheCamera == CAMCONTROL_OBBE){ + if(pToGarageWeAreIn){ + float ground; + bool foundGround; + + // This is all very strange.... + // targetPos = pTargetEntity->GetPosition(); // unused + if(pToGarageWeAreIn->m_pDoor1){ + whichDoor = 1; + garageDoorPos1.x = pToGarageWeAreIn->m_fDoor1X; + garageDoorPos1.y = pToGarageWeAreIn->m_fDoor1Y; + garageDoorPos1.z = 0.0f; + // targetPos.z = 0.0f; // unused + // (targetPos - doorPos1).Magnitude(); // unused + }else if(pToGarageWeAreIn->m_pDoor2){ + whichDoor = 2; +#ifdef FIX_BUGS + garageDoorPos2.x = pToGarageWeAreIn->m_fDoor2X; + garageDoorPos2.y = pToGarageWeAreIn->m_fDoor2Y; + garageDoorPos2.z = 0.0f; +#endif + }else{ + whichDoor = 1; + garageDoorPos1.x = pTargetEntity->GetPosition().x; + garageDoorPos1.y = pTargetEntity->GetPosition().y; +#ifdef FIX_BUGS + garageDoorPos1.z = 0.0f; +#else + garageDoorPos2.z = 0.0f; +#endif + } + garageCenter.x = (pToGarageWeAreIn->m_fX1 + pToGarageWeAreIn->m_fX2)/2.0f; + garageCenter.y = (pToGarageWeAreIn->m_fY1 + pToGarageWeAreIn->m_fY2)/2.0f; + garageCenter.z = 0.0f; + if(whichDoor == 1) + garageCenterToDoor = garageDoorPos1 - garageCenter; + else + garageCenterToDoor = garageDoorPos2 - garageCenter; + targetPos = pTargetEntity->GetPosition(); + ground = CWorld::FindGroundZFor3DCoord(targetPos.x, targetPos.y, targetPos.z, &foundGround); + if(!foundGround) + ground = targetPos.z - 0.2f; + garageCenterToDoor.z = 0.0f; + garageCenterToDoor.Normalise(); + if(whichDoor == 1) + garageCamPos = garageDoorPos1 + 13.0f*garageCenterToDoor; + else + garageCamPos = garageDoorPos2 + 13.0f*garageCenterToDoor; + garageCamPos.z = ground + 3.1f; + SetCamPositionForFixedMode(garageCamPos, CVector(0.0f, 0.0f, 0.0f)); + m_bGarageFixedCamPositionSet = true; + } + } + + if(CGarages::CameraShouldBeOutside() && m_bGarageFixedCamPositionSet && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE)){ + if(pToGarageWeAreIn){ + ReqMode = CCam::MODE_FIXED; + m_bPlayerIsInGarage = true; + } + }else{ + if(m_bPlayerIsInGarage){ + m_bJustCameOutOfGarage = true; + m_bPlayerIsInGarage = false; + } + ReqMode = CCam::MODE_CAM_ON_A_STRING; + } + }else{ + if(m_bPlayerIsInGarage){ + m_bJustCameOutOfGarage = true; + m_bPlayerIsInGarage = false; + } + m_bGarageFixedCamPositionSet = false; + ReqMode = CCam::MODE_CAM_ON_A_STRING; + } + break; + case VEHICLE_TYPE_BOAT: + ReqMode = CCam::MODE_BEHINDBOAT; + break; + default: break; + } + + // Car zoom value + if(CarZoomIndicator == CAM_ZOOM_1STPRS && !m_bPlayerIsInGarage){ + CarZoomValue = 0.0f; + ReqMode = CCam::MODE_1STPERSON; + } +#ifdef FREE_CAM + else if (bFreeCam) { + if (CarZoomIndicator == CAM_ZOOM_1) + CarZoomValue = ((CVehicle*)pTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_1 : FREE_CAR_ZOOM_VALUE_1; + else if (CarZoomIndicator == CAM_ZOOM_2) + CarZoomValue = ((CVehicle*)pTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_2 : FREE_CAR_ZOOM_VALUE_2; + else if (CarZoomIndicator == CAM_ZOOM_3) + CarZoomValue = ((CVehicle*)pTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_3 : FREE_CAR_ZOOM_VALUE_3; + } +#endif + else if(CarZoomIndicator == CAM_ZOOM_1) + CarZoomValue = DEFAULT_CAR_ZOOM_VALUE_1; + else if(CarZoomIndicator == CAM_ZOOM_2) + CarZoomValue = DEFAULT_CAR_ZOOM_VALUE_2; + else if(CarZoomIndicator == CAM_ZOOM_3) + CarZoomValue = DEFAULT_CAR_ZOOM_VALUE_3; + + if(CarZoomIndicator == CAM_ZOOM_TOPDOWN && !m_bPlayerIsInGarage){ + CarZoomValue = 1.0f; + ReqMode = CCam::MODE_TOPDOWN; + } + + // Check if we have to go into first person + if(((CVehicle*)pTargetEntity)->IsCar() && !m_bPlayerIsInGarage){ + if(CCullZones::Cam1stPersonForPlayer() && + pTargetEntity->GetColModel()->boundingBox.GetSize().z >= 3.026f && + pToGarageWeAreInForHackAvoidFirstPerson == nil){ + ReqMode = CCam::MODE_1STPERSON; + m_bInATunnelAndABigVehicle = true; + } + } + if(ReqMode == CCam::MODE_TOPDOWN && + (CCullZones::Cam1stPersonForPlayer() || CCullZones::CamNoRain() || CCullZones::PlayerNoRain())) + ReqMode = CCam::MODE_1STPERSON; + + // Smooth zoom value - ugly code + if(m_bUseScriptZoomValueCar){ + if(CarZoomValueSmooth < m_fCarZoomValueScript){ + CarZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Min(CarZoomValueSmooth, m_fCarZoomValueScript); + }else{ + CarZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Max(CarZoomValueSmooth, m_fCarZoomValueScript); + } + }else if(m_bFailedCullZoneTestPreviously){ + CloseInCarHeightTarget = 0.65f; + if(CarZoomValueSmooth < -0.65f){ + CarZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Min(CarZoomValueSmooth, -0.65f); + }else{ + CarZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Max(CarZoomValueSmooth, -0.65f); + } + }else{ + if(CarZoomValueSmooth < CarZoomValue){ + CarZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Min(CarZoomValueSmooth, CarZoomValue); + }else{ + CarZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Max(CarZoomValueSmooth, CarZoomValue); + } + } + + WellBufferMe(CloseInCarHeightTarget, &Cams[ActiveCam].m_fCloseInCarHeightOffset, &Cams[ActiveCam].m_fCloseInCarHeightOffsetSpeed, 0.1f, 0.25f, false); + + // Fallen into water + if(Cams[ActiveCam].IsTargetInWater(Cams[ActiveCam].Source) && !boatTarget && + !Cams[ActiveCam].CamTargetEntity->IsPed()) + ReqMode = CCam::MODE_PLAYER_FALLEN_WATER; + } + } + + // Ped target + else if(pTargetEntity->IsPed()){ + // Change user selected mode + if(CPad::GetPad(0)->CycleCameraModeUpJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn && !m_bFailedCullZoneTestPreviously){ + if(FrontEndMenuManager.m_ControlMethod == CONTROL_STANDARD){ + if(PedZoomIndicator == CAM_ZOOM_TOPDOWN) + PedZoomIndicator = CAM_ZOOM_1; + else + PedZoomIndicator = CAM_ZOOM_TOPDOWN; + }else + PedZoomIndicator--; + } + if(CPad::GetPad(0)->CycleCameraModeDownJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn && !m_bFailedCullZoneTestPreviously){ + if(FrontEndMenuManager.m_ControlMethod == CONTROL_STANDARD){ + if(PedZoomIndicator == CAM_ZOOM_TOPDOWN) + PedZoomIndicator = CAM_ZOOM_1; + else + PedZoomIndicator = CAM_ZOOM_TOPDOWN; + }else + PedZoomIndicator++; + } + // disabled obbe's cam here + if(PedZoomIndicator < CAM_ZOOM_1) PedZoomIndicator = CAM_ZOOM_TOPDOWN; + else if(PedZoomIndicator > CAM_ZOOM_TOPDOWN) PedZoomIndicator = CAM_ZOOM_1; + + ReqMode = CCam::MODE_FOLLOWPED; + + // Check 1st person mode + if(m_bLookingAtPlayer && pTargetEntity->IsPed() && !m_WideScreenOn && !Cams[0].Using3rdPersonMouseCam() +#ifdef FREE_CAM + && !CCamera::bFreeCam +#endif + ){ + // See if we want to enter first person mode + if(CPad::GetPad(0)->LookAroundLeftRight() || CPad::GetPad(0)->LookAroundUpDown()){ + m_uiFirstPersonCamLastInputTime = CTimer::GetTimeInMilliseconds(); + m_bFirstPersonBeingUsed = true; + }else if(m_bFirstPersonBeingUsed){ + // Or if we want to go back to 3rd person + if(CPad::GetPad(0)->GetPedWalkLeftRight() || CPad::GetPad(0)->GetPedWalkUpDown() || + CPad::GetPad(0)->GetSquare() || CPad::GetPad(0)->GetTriangle() || + CPad::GetPad(0)->GetCross() || CPad::GetPad(0)->GetCircle() || + CTimer::GetTimeInMilliseconds() - m_uiFirstPersonCamLastInputTime > 2850.0f) + m_bFirstPersonBeingUsed = false; + } + }else + m_bFirstPersonBeingUsed = false; + + if(!FindPlayerPed()->IsPedInControl() || FindPlayerPed()->m_fMoveSpeed > 0.0f) + m_bFirstPersonBeingUsed = false; + if(m_bFirstPersonBeingUsed){ + ReqMode = CCam::MODE_1STPERSON; + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_CAMERA); + } + + // Zoom value + if(PedZoomIndicator == CAM_ZOOM_1) + m_fPedZoomValue = 0.25f; + else if(PedZoomIndicator == CAM_ZOOM_2) + m_fPedZoomValue = 1.5f; + else if(PedZoomIndicator == CAM_ZOOM_3) + m_fPedZoomValue = 2.9f; + + // Smooth zoom value - ugly code + if(m_bUseScriptZoomValuePed){ + if(m_fPedZoomValueSmooth < m_fPedZoomValueScript){ + m_fPedZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Min(m_fPedZoomValueSmooth, m_fPedZoomValueScript); + }else{ + m_fPedZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Max(m_fPedZoomValueSmooth, m_fPedZoomValueScript); + } + }else if(m_bFailedCullZoneTestPreviously){ + static float PedZoomedInVal = 0.5f; + CloseInPedHeightTarget = 0.7f; + if(m_fPedZoomValueSmooth < PedZoomedInVal){ + m_fPedZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Min(m_fPedZoomValueSmooth, PedZoomedInVal); + }else{ + m_fPedZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Max(m_fPedZoomValueSmooth, PedZoomedInVal); + } + }else{ + if(m_fPedZoomValueSmooth < m_fPedZoomValue){ + m_fPedZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Min(m_fPedZoomValueSmooth, m_fPedZoomValue); + }else{ + m_fPedZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Max(m_fPedZoomValueSmooth, m_fPedZoomValue); + } + } + + WellBufferMe(CloseInPedHeightTarget, &Cams[ActiveCam].m_fCloseInPedHeightOffset, &Cams[ActiveCam].m_fCloseInPedHeightOffsetSpeed, 0.1f, 0.025f, false); + + // Check if entering fight cam + if(!m_bFirstPersonBeingUsed){ + if(FindPlayerPed()->GetPedState() == PED_FIGHT && !m_bUseMouse3rdPerson) + ReqMode = CCam::MODE_FIGHT_CAM; + if(((CPed*)pTargetEntity)->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT && + FindPlayerPed()->GetPedState() == PED_ATTACK && !m_bUseMouse3rdPerson) + ReqMode = CCam::MODE_FIGHT_CAM; + } + + // Garage cam + if(CCullZones::CamStairsForPlayer() && CCullZones::FindZoneWithStairsAttributeForPlayer()) + stairs = true; + // Some hack for Mr Whoopee in a bomb shop + if(Cams[ActiveCam].Using3rdPersonMouseCam() && CCollision::ms_collisionInMemory == LEVEL_COMMERCIAL){ + if(pTargetEntity->GetPosition().x < 83.0f && pTargetEntity->GetPosition().x > 18.0f && + pTargetEntity->GetPosition().y < -305.0f && pTargetEntity->GetPosition().y > -390.0f) + disableGarageCam = true; + } + if(!disableGarageCam && (CGarages::IsPointInAGarageCameraZone(pTargetEntity->GetPosition()) || stairs)){ + if(!m_bGarageFixedCamPositionSet && m_bLookingAtPlayer){ + if(pToGarageWeAreIn || stairs){ + float ground; + bool foundGround; + + if(pToGarageWeAreIn){ + // targetPos = pTargetEntity->GetPosition(); // unused + if(pToGarageWeAreIn->m_pDoor1){ + whichDoor = 1; + garageDoorPos1.x = pToGarageWeAreIn->m_fDoor1X; + garageDoorPos1.y = pToGarageWeAreIn->m_fDoor1Y; + garageDoorPos1.z = 0.0f; + // targetPos.z = 0.0f; // unused + // (targetPos - doorPos1).Magnitude(); // unused + }else if(pToGarageWeAreIn->m_pDoor2){ + whichDoor = 2; +#ifdef FIX_BUGS + garageDoorPos2.x = pToGarageWeAreIn->m_fDoor2X; + garageDoorPos2.y = pToGarageWeAreIn->m_fDoor2Y; + garageDoorPos2.z = 0.0f; +#endif + }else{ + whichDoor = 1; + garageDoorPos1.x = pTargetEntity->GetPosition().x; + garageDoorPos1.y = pTargetEntity->GetPosition().y; +#ifdef FIX_BUGS + garageDoorPos1.z = 0.0f; +#else + garageDoorPos2.z = 0.0f; +#endif + } + }else{ + whichDoor = 1; + garageDoorPos1 = Cams[ActiveCam].Source; + } + + if(pToGarageWeAreIn){ + garageCenter.x = (pToGarageWeAreIn->m_fX1 + pToGarageWeAreIn->m_fX2)/2.0f; + garageCenter.y = (pToGarageWeAreIn->m_fY1 + pToGarageWeAreIn->m_fY2)/2.0f; + garageCenter.z = 0.0f; + }else{ + garageDoorPos1.z = 0.0f; + if(stairs){ + CAttributeZone *az = CCullZones::FindZoneWithStairsAttributeForPlayer(); + garageCenter.x = (az->minx + az->maxx)/2.0f; + garageCenter.y = (az->miny + az->maxy)/2.0f; + garageCenter.z = 0.0f; + }else + garageCenter = CVector(pTargetEntity->GetPosition().x, pTargetEntity->GetPosition().y, 0.0f); + } + if(whichDoor == 1) + garageCenterToDoor = garageDoorPos1 - garageCenter; + else + garageCenterToDoor = garageDoorPos2 - garageCenter; + targetPos = pTargetEntity->GetPosition(); + ground = CWorld::FindGroundZFor3DCoord(targetPos.x, targetPos.y, targetPos.z, &foundGround); + if(!foundGround) + ground = targetPos.z - 0.2f; + garageCenterToDoor.z = 0.0f; + garageCenterToDoor.Normalise(); + if(whichDoor == 1){ + if(pToGarageWeAreIn == nil && stairs) + garageCamPos = garageDoorPos1 + 3.75f*garageCenterToDoor; + else + garageCamPos = garageDoorPos1 + 13.0f*garageCenterToDoor; + }else{ + garageCamPos = garageDoorPos2 + 13.0f*garageCenterToDoor; + } + if(PedZoomIndicator == CAM_ZOOM_TOPDOWN && !stairs){ + garageCamPos = garageCenter; + garageCamPos.z += FindPlayerPed()->GetPosition().z + 2.1f; + if(pToGarageWeAreIn && garageCamPos.z > pToGarageWeAreIn->m_fX2) // What? + garageCamPos.z = pToGarageWeAreIn->m_fX2; + }else + garageCamPos.z = ground + 3.1f; + SetCamPositionForFixedMode(garageCamPos, CVector(0.0f, 0.0f, 0.0f)); + m_bGarageFixedCamPositionSet = true; + } + } + + if((CGarages::CameraShouldBeOutside() || stairs) && m_bLookingAtPlayer && m_bGarageFixedCamPositionSet){ + if(pToGarageWeAreIn || stairs){ + ReqMode = CCam::MODE_FIXED; + m_bPlayerIsInGarage = true; + } + }else{ + if(m_bPlayerIsInGarage){ + m_bJustCameOutOfGarage = true; + m_bPlayerIsInGarage = false; + } + ReqMode = CCam::MODE_FOLLOWPED; + } + }else{ + if(m_bPlayerIsInGarage){ + m_bJustCameOutOfGarage = true; + m_bPlayerIsInGarage = false; + } + m_bGarageFixedCamPositionSet = false; + } + + // Fallen into water + if(Cams[ActiveCam].IsTargetInWater(Cams[ActiveCam].Source) && + Cams[ActiveCam].CamTargetEntity->IsPed()) + ReqMode = CCam::MODE_PLAYER_FALLEN_WATER; + + // Set top down + if(PedZoomIndicator == CAM_ZOOM_TOPDOWN && + !CCullZones::Cam1stPersonForPlayer() && + !CCullZones::CamNoRain() && + !CCullZones::PlayerNoRain() && + !m_bFirstPersonBeingUsed && + !m_bPlayerIsInGarage) + ReqMode = CCam::MODE_TOP_DOWN_PED; + + // Weapon mode + if(!CPad::GetPad(0)->GetTarget() && PlayerWeaponMode.Mode != CCam::MODE_HELICANNON_1STPERSON) + ClearPlayerWeaponMode(); + if(m_PlayerMode.Mode != CCam::MODE_NONE) + ReqMode = m_PlayerMode.Mode; + if(PlayerWeaponMode.Mode != CCam::MODE_NONE && !stairs){ + if(PlayerWeaponMode.Mode == CCam::MODE_SNIPER || + PlayerWeaponMode.Mode == CCam::MODE_ROCKETLAUNCHER || + PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON || + PlayerWeaponMode.Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].GetWeaponFirstPersonOn()){ + // First person weapon mode + if(PLAYER->GetPedState() == PED_SEEK_CAR){ + if(ReqMode == CCam::MODE_TOP_DOWN_PED || Cams[ActiveCam].GetWeaponFirstPersonOn()) + ReqMode = PlayerWeaponMode.Mode; + else + ReqMode = CCam::MODE_FOLLOWPED; + }else + ReqMode = PlayerWeaponMode.Mode; + }else if(ReqMode != CCam::MODE_TOP_DOWN_PED){ + // Syphon mode + float playerTargetDist; + float deadPedDist = 4.0f; + static float alivePedDist = 2.0f; // original name lost + float pedDist; // actually only used on dead target + bool targetDead = false; + float camAngle, targetAngle; + CVector playerToTarget = m_cvecAimingTargetCoors - pTargetEntity->GetPosition(); + CVector playerToCam = Cams[ActiveCam].Source - pTargetEntity->GetPosition(); + + if(PedZoomIndicator == CAM_ZOOM_1) + deadPedDist = 2.25f; + if(FindPlayerPed()->m_pPointGunAt){ + // BUG: this need not be a ped! + if(((CPed*)FindPlayerPed()->m_pPointGunAt)->DyingOrDead()){ + targetDead = true; + pedDist = deadPedDist; + }else + pedDist = alivePedDist; + playerTargetDist = playerToTarget.Magnitude2D(); + camAngle = CGeneral::GetATanOfXY(playerToCam.x, playerToCam.y); + targetAngle = CGeneral::GetATanOfXY(playerToTarget.x, playerToTarget.y); + ReqMode = PlayerWeaponMode.Mode; + + // Check whether to start aiming in crim-in-front mode + if(Cams[ActiveCam].Mode != CCam::MODE_SYPHON){ + float angleDiff = camAngle - targetAngle; + while(angleDiff >= PI) angleDiff -= 2*PI; + while(angleDiff < -PI) angleDiff += 2*PI; + if(Abs(angleDiff) < HALFPI && playerTargetDist < 3.5f && playerToTarget.z > -1.0f) + ReqMode = CCam::MODE_SYPHON_CRIM_IN_FRONT; + } + + // Check whether to go to special fixed mode + float fixedModeDist = 0.0f; + if((ReqMode == CCam::MODE_SYPHON_CRIM_IN_FRONT || ReqMode == CCam::MODE_SYPHON) && + (m_uiTransitionState == 0 || Cams[ActiveCam].Mode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON) && + playerTargetDist < pedDist && targetDead){ + if(ReqMode == CCam::MODE_SYPHON_CRIM_IN_FRONT) + fixedModeDist = 5.0f; + else + fixedModeDist = 3.0f; + ReqMode = CCam::MODE_SPECIAL_FIXED_FOR_SYPHON; + } + if(ReqMode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON){ + if(!PlaceForFixedWhenSniperFound){ + // Find position + CEntity *entity; + CColPoint colPoint; + CVector fixedPos = pTargetEntity->GetPosition(); + fixedPos.x += fixedModeDist*Cos(camAngle); + fixedPos.y += fixedModeDist*Sin(camAngle); + fixedPos.z += 1.15f; + if(CWorld::ProcessLineOfSight(pTargetEntity->GetPosition(), fixedPos, colPoint, entity, true, false, false, true, false, true, true)) + SetCamPositionForFixedMode(colPoint.point, CVector(0.0f, 0.0f, 0.0f)); + else + SetCamPositionForFixedMode(fixedPos, CVector(0.0f, 0.0f, 0.0f)); + PlaceForFixedWhenSniperFound = true; + } + }else + PlaceForFixedWhenSniperFound = false; + } + } + } + } + } + + m_bIdleOn = false; + + if(DebugCamMode) + ReqMode = DebugCamMode; + + + // Process arrested player + static int ThePickedArrestMode; + static int LastPedState; + bool startArrestCam = false; + + if(LastPedState != PED_ARRESTED && PLAYER->GetPedState() == PED_ARRESTED){ + if(CarZoomIndicator != CAM_ZOOM_1STPRS && pTargetEntity->IsVehicle()) + startArrestCam = true; + }else + startArrestCam = false; + LastPedState = PLAYER->GetPedState(); + if(startArrestCam){ + if(m_uiTransitionState) + ReqMode = Cams[ActiveCam].Mode; + else{ + bool valid; + if(pTargetEntity->IsPed()){ + // How can this happen if arrest cam is only done in cars? + Cams[(ActiveCam+1)%2].ResetStatics = true; + valid = Cams[(ActiveCam+1)%2].ProcessArrestCamOne(); + ReqMode = CCam::MODE_ARRESTCAM_ONE; + }else{ + Cams[(ActiveCam+1)%2].ResetStatics = true; + valid = Cams[(ActiveCam+1)%2].ProcessArrestCamTwo(); + ReqMode = CCam::MODE_ARRESTCAM_TWO; + } + if(!valid) + ReqMode = Cams[ActiveCam].Mode; + } + } + ThePickedArrestMode = ReqMode; + if(PLAYER->GetPedState() == PED_ARRESTED) + ReqMode = ThePickedArrestMode; // this is rather useless... + + // Process dead player + if(PLAYER->GetPedState() == PED_DEAD){ + if(Cams[ActiveCam].Mode == CCam::MODE_PED_DEAD_BABY) + ReqMode = CCam::MODE_PED_DEAD_BABY; + else{ + bool foundRoof; + CVector pos = FindPlayerPed()->GetPosition(); + CWorld::FindRoofZFor3DCoord(pos.x, pos.y, pos.z, &foundRoof); + if(!foundRoof) + ReqMode = CCam::MODE_PED_DEAD_BABY; + } + } + + // Restore with a jump cut + if(m_bRestoreByJumpCut){ + // PS2 just sets m_bCamDirectlyBehind here + if(ReqMode != CCam::MODE_FOLLOWPED && + ReqMode != CCam::MODE_M16_1STPERSON && + ReqMode != CCam::MODE_SNIPER && + ReqMode != CCam::MODE_ROCKETLAUNCHER || + !m_bUseMouse3rdPerson) + SetCameraDirectlyBehindForFollowPed_CamOnAString(); + + ReqMode = m_iModeToGoTo; + Cams[ActiveCam].Mode = ReqMode; + m_bJust_Switched = true; + Cams[ActiveCam].ResetStatics = true; + Cams[ActiveCam].m_cvecCamFixedModeVector = m_vecFixedModeVector; + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + Cams[ActiveCam].m_cvecCamFixedModeSource = m_vecFixedModeSource; + Cams[ActiveCam].m_cvecCamFixedModeUpOffSet = m_vecFixedModeUpOffSet; + // PS2 sets this to m_bLookingAtVector + Cams[ActiveCam].m_bCamLookingAtVector = false; + Cams[ActiveCam].m_vecLastAboveWaterCamPosition = Cams[(ActiveCam+1)%2].m_vecLastAboveWaterCamPosition; + m_bRestoreByJumpCut = false; + Cams[ActiveCam].ResetStatics = true; + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + CarZoomValueSmooth = CarZoomValue; + m_fPedZoomValueSmooth = m_fPedZoomValue; + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + } + + if(gbModelViewer) + ReqMode = CCam::MODE_MODELVIEW; + + // Turn on Obbe's cam + bool canUseObbeCam = true; + if(pTargetEntity){ + if(pTargetEntity->IsVehicle()){ + if(CarZoomIndicator == CAM_ZOOM_CINEMATIC) + m_bObbeCinematicCarCamOn = true; + }else{ + if(PedZoomIndicator == CAM_ZOOM_CINEMATIC) + m_bObbeCinematicPedCamOn = true; + } + } + if(m_bTargetJustBeenOnTrain || + ReqMode == CCam::MODE_SYPHON || ReqMode == CCam::MODE_SYPHON_CRIM_IN_FRONT || ReqMode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON || + ReqMode == CCam::MODE_PED_DEAD_BABY || ReqMode == CCam::MODE_ARRESTCAM_ONE || ReqMode == CCam::MODE_ARRESTCAM_TWO || + ReqMode == CCam::MODE_FIGHT_CAM || ReqMode == CCam::MODE_PLAYER_FALLEN_WATER || + ReqMode == CCam::MODE_SNIPER || ReqMode == CCam::MODE_ROCKETLAUNCHER || ReqMode == CCam::MODE_M16_1STPERSON || + ReqMode == CCam::MODE_SNIPER_RUNABOUT || ReqMode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + ReqMode == CCam::MODE_1STPERSON_RUNABOUT || ReqMode == CCam::MODE_M16_1STPERSON_RUNABOUT || + ReqMode == CCam::MODE_FIGHT_CAM_RUNABOUT || ReqMode == CCam::MODE_HELICANNON_1STPERSON || + WhoIsInControlOfTheCamera == CAMCONTROL_SCRIPT || + m_bJustCameOutOfGarage || m_bPlayerIsInGarage) + canUseObbeCam = false; + + if(m_bObbeCinematicPedCamOn && canUseObbeCam) + ProcessObbeCinemaCameraPed(); + else if(m_bObbeCinematicCarCamOn && canUseObbeCam) + ProcessObbeCinemaCameraCar(); + else{ + if(m_bPlayerIsInGarage && m_bObbeCinematicCarCamOn) + switchByJumpCut = true; + canUseObbeCam = false; + DontProcessObbeCinemaCamera(); + } + + // Start the transition or do a jump cut + if(m_bLookingAtPlayer){ + // Going into top down modes normally needs a jump cut (but see below) + if(ReqMode == CCam::MODE_TOPDOWN || ReqMode == CCam::MODE_1STPERSON || ReqMode == CCam::MODE_TOP_DOWN_PED){ + switchByJumpCut = true; + } + // Going from top down to vehicle + else if(ReqMode == CCam::MODE_CAM_ON_A_STRING || ReqMode == CCam::MODE_BEHINDBOAT){ + if(Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED) + switchByJumpCut = true; + }else if(ReqMode == CCam::MODE_FIXED){ + if(Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN) + switchByJumpCut = true; + } + + // Top down modes can interpolate between each other + if(ReqMode == CCam::MODE_TOPDOWN){ + if(Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED || Cams[ActiveCam].Mode == CCam::MODE_PED_DEAD_BABY) + switchByJumpCut = false; + }else if(ReqMode == CCam::MODE_TOP_DOWN_PED){ + if(Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || Cams[ActiveCam].Mode == CCam::MODE_PED_DEAD_BABY) + switchByJumpCut = false; + } + + if(ReqMode == CCam::MODE_1STPERSON || ReqMode == CCam::MODE_M16_1STPERSON || + ReqMode == CCam::MODE_SNIPER || ReqMode == CCam::MODE_ROCKETLAUNCHER || + ReqMode == CCam::MODE_SNIPER_RUNABOUT || ReqMode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + ReqMode == CCam::MODE_1STPERSON_RUNABOUT || ReqMode == CCam::MODE_M16_1STPERSON_RUNABOUT || + ReqMode == CCam::MODE_FIGHT_CAM_RUNABOUT || + ReqMode == CCam::MODE_HELICANNON_1STPERSON || + ReqMode == CCam::MODE_ARRESTCAM_ONE || ReqMode == CCam::MODE_ARRESTCAM_TWO){ + // Going into any 1st person mode is a jump cut + if(pTargetEntity->IsPed()) + switchByJumpCut = true; + }else if(ReqMode == CCam::MODE_FIXED && m_bPlayerIsInGarage){ + // Going from 1st peron mode into garage + if(Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED || + stairs || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON_RUNABOUT){ + if(pTargetEntity && pTargetEntity->IsVehicle()) + switchByJumpCut = true; + } + }else if(ReqMode == CCam::MODE_FOLLOWPED){ + if(Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER || + Cams[ActiveCam].Mode == CCam::MODE_ARRESTCAM_ONE || + Cams[ActiveCam].Mode == CCam::MODE_ARRESTCAM_TWO || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_PED_DEAD_BABY || + Cams[ActiveCam].Mode == CCam::MODE_PILLOWS_PAPS || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || + Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + if(!m_bJustCameOutOfGarage){ + if(Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON){ + float angle = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) - HALFPI; + ((CPed*)pTargetEntity)->m_fRotationCur = angle; + ((CPed*)pTargetEntity)->m_fRotationDest = angle; + } + m_bUseTransitionBeta = true; + switchByJumpCut = true; + if(Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + CVector front = Cams[ActiveCam].Source - FindPlayerPed()->GetPosition(); + front.z = 0.0f; // missing on PS2 + front.Normalise(); +#ifdef FIX_BUGS + // this is almost as bad as the bugged code + if(front.x == 0.001f && front.y == 0.001f) + front.y = 1.0f; +#else + // someone used = instead of == in the above check by accident + front.x = 0.001f; + front.y = 1.0f; +#endif + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(front.x, front.y); + }else + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) + PI; + } + } + }else if(ReqMode == CCam::MODE_FIGHT_CAM){ + if(Cams[ActiveCam].Mode == CCam::MODE_1STPERSON) + switchByJumpCut = true; + } + + if(ReqMode != Cams[ActiveCam].Mode && Cams[ActiveCam].CamTargetEntity == nil) + switchByJumpCut = true; + if(m_bPlayerIsInGarage && pToGarageWeAreIn){ + if(pToGarageWeAreIn->m_eGarageType == GARAGE_BOMBSHOP1 || + pToGarageWeAreIn->m_eGarageType == GARAGE_BOMBSHOP2 || + pToGarageWeAreIn->m_eGarageType == GARAGE_BOMBSHOP3){ + if(pTargetEntity->IsVehicle() && pTargetEntity->GetModelIndex() == MI_MRWHOOP && + ReqMode != Cams[ActiveCam].Mode) + switchByJumpCut = true; + } + } +#ifdef GTA_SCENE_EDIT + if(CSceneEdit::m_bEditOn) + ReqMode = CCam::MODE_EDITOR; +#endif + + if((m_uiTransitionState == 0 || switchByJumpCut) && ReqMode != Cams[ActiveCam].Mode){ + if(switchByJumpCut){ + // PS2 just sets m_bCamDirectlyBehind here + if(!m_bPlayerIsInGarage || m_bJustCameOutOfGarage){ + if(ReqMode != CCam::MODE_FOLLOWPED && + ReqMode != CCam::MODE_M16_1STPERSON && + ReqMode != CCam::MODE_SNIPER && + ReqMode != CCam::MODE_ROCKETLAUNCHER || + !m_bUseMouse3rdPerson) + SetCameraDirectlyBehindForFollowPed_CamOnAString(); + } + Cams[ActiveCam].Mode = ReqMode; + m_bJust_Switched = true; + Cams[ActiveCam].m_cvecCamFixedModeVector = m_vecFixedModeVector; + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + Cams[ActiveCam].m_cvecCamFixedModeSource = m_vecFixedModeSource; + Cams[ActiveCam].m_cvecCamFixedModeUpOffSet = m_vecFixedModeUpOffSet; + Cams[ActiveCam].m_bCamLookingAtVector = m_bLookingAtVector; + Cams[ActiveCam].m_vecLastAboveWaterCamPosition = Cams[(ActiveCam+1)%2].m_vecLastAboveWaterCamPosition; + CarZoomValueSmooth = CarZoomValue; + m_fPedZoomValueSmooth = m_fPedZoomValue; + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + m_bStartInterScript = false; + Cams[ActiveCam].ResetStatics = true; + + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + }else if(!m_bWaitForInterpolToFinish){ + StartTransition(ReqMode); + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + } + }else if(m_uiTransitionState != 0 && ReqMode != Cams[ActiveCam].Mode){ + bool startTransition = true; + + if(ReqMode == CCam::MODE_FIGHT_CAM || Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM) + startTransition = false; + if(ReqMode == CCam::MODE_FOLLOWPED && Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM) + startTransition = false; + +#ifndef PS2_CAM_TRANSITION + // done in Process on PS2 + if(!m_bWaitForInterpolToFinish && m_bLookingAtPlayer && m_uiTransitionState != 0){ + CVector playerDist; + playerDist.x = FindPlayerPed()->GetPosition().x - GetPosition().x; + playerDist.y = FindPlayerPed()->GetPosition().y - GetPosition().y; + playerDist.z = FindPlayerPed()->GetPosition().z - GetPosition().z; + // if player is too far away, keep interpolating and don't transition + if(pTargetEntity && pTargetEntity->IsPed()){ + if(playerDist.Magnitude() > 17.5f && + (ReqMode == CCam::MODE_SYPHON || ReqMode == CCam::MODE_SYPHON_CRIM_IN_FRONT)) + m_bWaitForInterpolToFinish = true; + } + } +#endif + if(m_bWaitForInterpolToFinish) + startTransition = false; + + if(startTransition){ + StartTransitionWhenNotFinishedInter(ReqMode); + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + } + }else if(ReqMode == CCam::MODE_FIXED && pTargetEntity != Cams[ActiveCam].CamTargetEntity && m_bPlayerIsInGarage){ +#ifdef PS2_CAM_TRANSITION + StartTransitionWhenNotFinishedInter(ReqMode); +#else + if(m_uiTransitionState != 0) + StartTransitionWhenNotFinishedInter(ReqMode); + else + StartTransition(ReqMode); +#endif + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + } + }else{ + // not following player + if(m_uiTransitionState == 0 && m_bStartInterScript && m_iTypeOfSwitch == INTERPOLATION){ + ReqMode = m_iModeToGoTo; + StartTransition(ReqMode); + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + }else if(m_uiTransitionState != 0 && m_bStartInterScript && m_iTypeOfSwitch == INTERPOLATION){ + ReqMode = m_iModeToGoTo; + StartTransitionWhenNotFinishedInter(ReqMode); + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + }else if(m_bStartInterScript && m_iTypeOfSwitch == JUMP_CUT){ + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + Cams[ActiveCam].Mode = m_iModeToGoTo; + m_bJust_Switched = true; + Cams[ActiveCam].ResetStatics = true; + Cams[ActiveCam].m_cvecCamFixedModeVector = m_vecFixedModeVector; + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + Cams[ActiveCam].m_cvecCamFixedModeSource = m_vecFixedModeSource; + Cams[ActiveCam].m_cvecCamFixedModeUpOffSet = m_vecFixedModeUpOffSet; + Cams[ActiveCam].m_bCamLookingAtVector = m_bLookingAtVector; + Cams[ActiveCam].m_vecLastAboveWaterCamPosition = Cams[(ActiveCam+1)%2].m_vecLastAboveWaterCamPosition; + m_bJust_Switched = true; + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + CarZoomValueSmooth = CarZoomValue; + m_fPedZoomValueSmooth = m_fPedZoomValue; + } + } + + m_bStartInterScript = false; + + if(Cams[ActiveCam].CamTargetEntity == nil) + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + + // Ped visibility + if((Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER) && pTargetEntity->IsPed() || + Cams[ActiveCam].Mode == CCam::MODE_FLYBY) + FindPlayerPed()->bIsVisible = false; + else + FindPlayerPed()->bIsVisible = true; + + if(!canUseObbeCam && WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) + Restore(); +} + +// What a mess! +void +CCamera::UpdateTargetEntity(void) +{ + bool enteringCar = false; // not on PS2 but only used as && !enteringCar so we can keep it + bool obbeCam = false; + + if(WhoIsInControlOfTheCamera == CAMCONTROL_OBBE){ + obbeCam = true; + if(m_iModeObbeCamIsInForCar == OBBE_COPCAR_WHEEL || m_iModeObbeCamIsInForCar == OBBE_COPCAR){ + if(FindPlayerPed()->GetPedState() != PED_ARRESTED) + obbeCam = false; + if(FindPlayerVehicle() == nil) + pTargetEntity = FindPlayerPed(); + } + } + + if((m_bLookingAtPlayer || obbeCam) && m_uiTransitionState == 0 || + pTargetEntity == nil || + m_bTargetJustBeenOnTrain){ + if(FindPlayerVehicle()) + pTargetEntity = FindPlayerVehicle(); + else{ + pTargetEntity = FindPlayerPed(); +#ifndef GTA_PS2_STUFF + // this keeps the camera on the player while entering cars + if(PLAYER->GetPedState() == PED_ENTER_CAR || + PLAYER->GetPedState() == PED_CARJACK || + PLAYER->GetPedState() == PED_OPEN_DOOR) + enteringCar = true; + + if(!enteringCar) + if(Cams[ActiveCam].CamTargetEntity != pTargetEntity) + Cams[ActiveCam].CamTargetEntity = pTargetEntity; +#endif + } + + bool cantOpen = true; + if(PLAYER && + PLAYER->m_pMyVehicle && + PLAYER->m_pMyVehicle->CanPedOpenLocks(PLAYER)) + cantOpen = false; + + if(PLAYER->GetPedState() == PED_ENTER_CAR && !cantOpen){ + if(!enteringCar && CarZoomIndicator != CAM_ZOOM_1STPRS){ + pTargetEntity = PLAYER->m_pMyVehicle; + if(PLAYER->m_pMyVehicle == nil) + pTargetEntity = PLAYER; + } + } + + if((PLAYER->GetPedState() == PED_CARJACK || PLAYER->GetPedState() == PED_OPEN_DOOR) && !cantOpen){ + if(!enteringCar && CarZoomIndicator != CAM_ZOOM_1STPRS) +#ifdef GTA_PS2_STUFF +// dunno if this has any amazing effects + { +#endif + pTargetEntity = PLAYER->m_pMyVehicle; + if(PLAYER->m_pMyVehicle == nil) + pTargetEntity = PLAYER; +#ifdef GTA_PS2_STUFF + } +#endif + } + + if(PLAYER->GetPedState() == PED_EXIT_CAR) + pTargetEntity = FindPlayerPed(); + if(PLAYER->GetPedState() == PED_DRAG_FROM_CAR) + pTargetEntity = FindPlayerPed(); + if(pTargetEntity->IsVehicle() && CarZoomIndicator == CAM_ZOOM_1STPRS && FindPlayerPed()->GetPedState() == PED_ARRESTED) + pTargetEntity = FindPlayerPed(); + } +} + +const float SOUND_DIST = 20.0f; + +void +CCamera::UpdateSoundDistances(void) +{ + CVector center, end; + CEntity *entity; + CColPoint colPoint; + float f; + int n; + + if((Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER) && + pTargetEntity->IsPed()) + center = GetPosition() + 0.5f*GetForward(); + else + center = GetPosition() + 5.0f*GetForward(); + + // check up + n = CTimer::GetFrameCounter() % 12; + if(n == 0){ + SoundDistUpAsReadOld = SoundDistUpAsRead; + if(CWorld::ProcessVerticalLine(center, center.z+SOUND_DIST, colPoint, entity, true, false, false, false, true, false, nil)) + SoundDistUpAsRead = colPoint.point.z - center.z; + else + SoundDistUpAsRead = SOUND_DIST; + } + f = (n + 1) / 6.0f; + SoundDistUp = (1.0f-f)*SoundDistUpAsReadOld + f*SoundDistUpAsRead; + + // check left + n = (CTimer::GetFrameCounter()+2) % 12; + if(n == 0){ + SoundDistLeftAsReadOld = SoundDistLeftAsRead; + end = center + SOUND_DIST*GetRight(); + if(CWorld::ProcessLineOfSight(center, end, colPoint, entity, true, false, false, false, true, true, true)) + SoundDistLeftAsRead = (colPoint.point - center).Magnitude(); + else + SoundDistLeftAsRead = SOUND_DIST; + } + f = (n + 1) / 6.0f; + SoundDistLeft = (1.0f-f)*SoundDistLeftAsReadOld + f*SoundDistLeftAsRead; + + // check right + // end = center - SOUND_DIST*GetRight(); // useless + n = (CTimer::GetFrameCounter()+4) % 12; + if(n == 0){ + SoundDistRightAsReadOld = SoundDistRightAsRead; + end = center - SOUND_DIST*GetRight(); + if(CWorld::ProcessLineOfSight(center, end, colPoint, entity, true, false, false, false, true, true, true)) + SoundDistRightAsRead = (colPoint.point - center).Magnitude(); + else + SoundDistRightAsRead = SOUND_DIST; + } + f = (n + 1) / 6.0f; + SoundDistRight = (1.0f-f)*SoundDistRightAsReadOld + f*SoundDistRightAsRead; +} + +void +CCamera::InitialiseCameraForDebugMode(void) +{ + if(FindPlayerVehicle()) + Cams[2].Source = FindPlayerVehicle()->GetPosition(); + else if(FindPlayerPed()) + Cams[2].Source = FindPlayerPed()->GetPosition(); + Cams[2].Alpha = 0.0f; + Cams[2].Beta = 0.0f; + Cams[2].Mode = CCam::MODE_DEBUG; +} + +void +CCamera::CamShake(float strength, float x, float y, float z) +{ + CVector Dist = Cams[ActiveCam].Source - CVector(x, y, z); + // a bit complicated... + float dist2d = Sqrt(SQR(Dist.x) + SQR(Dist.y)); + float dist3d = Sqrt(SQR(dist2d) + SQR(Dist.z)); + if(dist3d > 100.0f) dist3d = 100.0f; + if(dist3d < 0.0f) dist3d = 0.0f; + float mult = 1.0f - dist3d/100.0f; + + float curForce = mult*(m_fCamShakeForce - (CTimer::GetTimeInMilliseconds() - m_uiCamShakeStart)/1000.0f); + strength = mult*strength; + if(Clamp(curForce, 0.0f, 2.0f) < strength){ + m_fCamShakeForce = strength; + m_uiCamShakeStart = CTimer::GetTimeInMilliseconds(); + } +} + +// This seems to be CCamera::CamShake(float) on PS2 +void +CamShakeNoPos(CCamera *cam, float strength) +{ + float curForce = cam->m_fCamShakeForce - (CTimer::GetTimeInMilliseconds() - cam->m_uiCamShakeStart)/1000.0f; + if(Clamp(curForce, 0.0f, 2.0f) < strength){ + cam->m_fCamShakeForce = strength; + cam->m_uiCamShakeStart = CTimer::GetTimeInMilliseconds(); + } +} + + + +void +CCamera::TakeControl(CEntity *target, int16 mode, int16 typeOfSwitch, int32 controller) +{ + bool doSwitch = true; + if(controller == CAMCONTROL_OBBE && WhoIsInControlOfTheCamera == CAMCONTROL_SCRIPT) + doSwitch = false; + if(doSwitch){ + WhoIsInControlOfTheCamera = controller; + if(target){ + if(mode == CCam::MODE_NONE){ + // Why are we checking the old entity? + if(pTargetEntity->IsPed()) + mode = CCam::MODE_FOLLOWPED; + else if(pTargetEntity->IsVehicle()) + mode = CCam::MODE_CAM_ON_A_STRING; + } + }else if(FindPlayerVehicle()) + target = FindPlayerVehicle(); + else + target = PLAYER; + + m_bLookingAtVector = false; + pTargetEntity = target; + m_iModeToGoTo = mode; + m_iTypeOfSwitch = typeOfSwitch; + m_bLookingAtPlayer = false; + m_bStartInterScript = true; + // FindPlayerPed(); // unused + } +} + +void +CCamera::TakeControlNoEntity(const CVector &position, int16 typeOfSwitch, int32 controller) +{ + bool doSwitch = true; + if(controller == CAMCONTROL_OBBE && WhoIsInControlOfTheCamera == CAMCONTROL_SCRIPT) + doSwitch = false; + if(doSwitch){ + WhoIsInControlOfTheCamera = controller; + m_bLookingAtVector = true; + m_bLookingAtPlayer = false; + m_iModeToGoTo = CCam::MODE_FIXED; + m_vecFixedModeVector = position; + m_iTypeOfSwitch = typeOfSwitch; + m_bStartInterScript = true; + } +} + +void +CCamera::TakeControlWithSpline(int16 typeOfSwitch) +{ + m_iModeToGoTo = CCam::MODE_FLYBY; + m_bLookingAtPlayer = false; + m_bLookingAtVector = false; + m_bcutsceneFinished = false; + m_iTypeOfSwitch = typeOfSwitch; + m_bStartInterScript = true; + + //FindPlayerPed(); // unused +}; + +void +CCamera::Restore(void) +{ + m_bLookingAtPlayer = true; + m_bLookingAtVector = false; + m_iTypeOfSwitch = INTERPOLATION; + m_bUseNearClipScript = false; + m_iModeObbeCamIsInForCar = OBBE_INVALID; + m_fPositionAlongSpline = 0.0; + m_bStartingSpline = false; + m_bScriptParametersSetForInterPol = false; + WhoIsInControlOfTheCamera = CAMCONTROL_GAME; + + if(FindPlayerVehicle()){ + m_iModeToGoTo = CCam::MODE_CAM_ON_A_STRING; + pTargetEntity = FindPlayerVehicle(); + }else{ + m_iModeToGoTo = CCam::MODE_FOLLOWPED; + pTargetEntity = PLAYER; + } + + if(PLAYER->GetPedState() == PED_ENTER_CAR || + PLAYER->GetPedState() == PED_CARJACK || + PLAYER->GetPedState() == PED_OPEN_DOOR){ + m_iModeToGoTo = CCam::MODE_CAM_ON_A_STRING; + pTargetEntity = PLAYER->m_pSeekTarget; + } + if(PLAYER->GetPedState() == PED_EXIT_CAR){ + m_iModeToGoTo = CCam::MODE_FOLLOWPED; + pTargetEntity = PLAYER; + } + + m_bUseScriptZoomValuePed = false; + m_bUseScriptZoomValueCar = false; + m_bStartInterScript = true; + m_bCameraJustRestored = true; +} + +void +CCamera::RestoreWithJumpCut(void) +{ + m_bRestoreByJumpCut = true; + m_bLookingAtPlayer = true; + m_bLookingAtVector = false; + m_iTypeOfSwitch = JUMP_CUT; + m_bUseNearClipScript = false; + m_iModeObbeCamIsInForCar = OBBE_INVALID; + m_fPositionAlongSpline = 0.0; + m_bStartingSpline = false; + m_bScriptParametersSetForInterPol = false; + WhoIsInControlOfTheCamera = CAMCONTROL_GAME; + m_bCameraJustRestored = true; + + if(FindPlayerVehicle()){ + m_iModeToGoTo = CCam::MODE_CAM_ON_A_STRING; + pTargetEntity = FindPlayerVehicle(); + }else{ + m_iModeToGoTo = CCam::MODE_FOLLOWPED; + pTargetEntity = PLAYER; + } + + if(PLAYER->GetPedState() == PED_ENTER_CAR || + PLAYER->GetPedState() == PED_CARJACK || + PLAYER->GetPedState() == PED_OPEN_DOOR){ + m_iModeToGoTo = CCam::MODE_CAM_ON_A_STRING; + pTargetEntity = PLAYER->m_pSeekTarget; + } + if(PLAYER->GetPedState() == PED_EXIT_CAR){ + m_iModeToGoTo = CCam::MODE_FOLLOWPED; + pTargetEntity = PLAYER; + } + + m_bUseScriptZoomValuePed = false; + m_bUseScriptZoomValueCar = false; +} + +void +CCamera::SetCamPositionForFixedMode(const CVector &Source, const CVector &UpOffSet) +{ + m_vecFixedModeSource = Source; + m_vecFixedModeUpOffSet = UpOffSet; +} + + + +/* + * On PS2 the transition happens between Cams[0] and Cams[1]. + * On PC the whole system has been changed. + */ +void +CCamera::StartTransition(int16 newMode) +{ + bool switchSyphonMode = false; + bool switchPedToCar = false; + bool switchFromFight = false; + bool switchFromFixed = false; + bool switch1stPersonToVehicle = false; + float betaOffset, targetBeta, camBeta, deltaBeta; + int door; + bool vehicleVertical; + +#ifndef PS2_CAM_TRANSITION + m_bItsOkToLookJustAtThePlayer = false; + m_fFractionInterToStopMoving = 0.25f; + m_fFractionInterToStopCatchUp = 0.75f; + + if(Cams[ActiveCam].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT || + Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED || + Cams[ActiveCam].Mode == CCam::MODE_SYPHON || + Cams[ActiveCam].Mode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON){ + if(newMode == CCam::MODE_SYPHON_CRIM_IN_FRONT || + newMode == CCam::MODE_FOLLOWPED || + newMode == CCam::MODE_SYPHON || + newMode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON) + m_bItsOkToLookJustAtThePlayer = true; + if(newMode == CCam::MODE_CAM_ON_A_STRING) + switchPedToCar = true; + } +#endif + + if(Cams[ActiveCam].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT && newMode == CCam::MODE_SYPHON) + switchSyphonMode = true; + if(Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM && newMode == CCam::MODE_FOLLOWPED) + switchFromFight = true; +#ifndef PS2_CAM_TRANSITION + if(Cams[ActiveCam].Mode == CCam::MODE_FIXED) + switchFromFixed = true; +#endif + + m_bUseTransitionBeta = false; + + if((Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON_RUNABOUT) && + pTargetEntity->IsPed()){ + float angle = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) - HALFPI; + ((CPed*)pTargetEntity)->m_fRotationCur = angle; + ((CPed*)pTargetEntity)->m_fRotationDest = angle; + } + +#ifdef PS2_CAM_TRANSITION + ActiveCam = (ActiveCam+1)%2; + Cams[ActiveCam].Init(); + Cams[ActiveCam].Mode = newMode; +#endif + + Cams[ActiveCam].m_cvecCamFixedModeVector = m_vecFixedModeVector; + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + Cams[ActiveCam].m_cvecCamFixedModeSource = m_vecFixedModeSource; + Cams[ActiveCam].m_cvecCamFixedModeUpOffSet = m_vecFixedModeUpOffSet; + Cams[ActiveCam].m_bCamLookingAtVector = m_bLookingAtVector; + + if(newMode == CCam::MODE_SNIPER || + newMode == CCam::MODE_ROCKETLAUNCHER || + newMode == CCam::MODE_M16_1STPERSON || + newMode == CCam::MODE_SNIPER_RUNABOUT || + newMode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + newMode == CCam::MODE_1STPERSON_RUNABOUT || + newMode == CCam::MODE_M16_1STPERSON_RUNABOUT || + newMode == CCam::MODE_FIGHT_CAM_RUNABOUT || + newMode == CCam::MODE_HELICANNON_1STPERSON) + Cams[ActiveCam].Alpha = 0.0f; + + // PS2 also copies values to ActiveCam here + switch(Cams[ActiveCam].Mode) + case CCam::MODE_SNIPER_RUNABOUT: + case CCam::MODE_ROCKETLAUNCHER_RUNABOUT: + case CCam::MODE_1STPERSON_RUNABOUT: + case CCam::MODE_M16_1STPERSON_RUNABOUT: + case CCam::MODE_FIGHT_CAM_RUNABOUT: + if(newMode == CCam::MODE_CAM_ON_A_STRING || newMode == CCam::MODE_BEHINDBOAT) + switch1stPersonToVehicle = true; + + switch(newMode){ + case CCam::MODE_BEHINDCAR: +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; + Cams[ActiveCam].Beta = Cams[(ActiveCam+1)%2].Beta; +#endif + Cams[ActiveCam].BetaSpeed = 0.0f; + break; + + case CCam::MODE_BEHINDBOAT: +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; + Cams[ActiveCam].Beta = Cams[(ActiveCam+1)%2].Beta; +#endif + Cams[ActiveCam].BetaSpeed = 0.0f; + break; + + case CCam::MODE_FOLLOWPED: + // Getting out of vehicle normally + betaOffset = DEGTORAD(55.0f); +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; +#endif + if(m_bJustCameOutOfGarage){ + m_bUseTransitionBeta = true; +/* + // weird logic... + if(CMenuManager::m_ControlMethod == CONTROL_CLASSIC) + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) + PI; + else if(Cams[ActiveCam].Front.x != 0.0f && Cams[ActiveCam].Front.y != 0.0f) // && is wrong here + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) + PI; + else + Cams[ActiveCam].m_fTransitionBeta = 0.0f; +*/ + // this is better: + if(Cams[ActiveCam].Front.x != 0.0f || Cams[ActiveCam].Front.y != 0.0f) +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[(ActiveCam+1)%2].Front.x, Cams[(ActiveCam+1)%2].Front.y) + PI; +#else + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) + PI; +#endif + else + Cams[ActiveCam].m_fTransitionBeta = 0.0f; + } + if(m_bTargetJustCameOffTrain) + m_bCamDirectlyInFront = true; +#ifdef PS2_CAM_TRANSITION + if(Cams[(ActiveCam+1)%2].Mode != CCam::MODE_CAM_ON_A_STRING) +#else + if(Cams[ActiveCam].Mode != CCam::MODE_CAM_ON_A_STRING) +#endif + break; + m_bUseTransitionBeta = true; + vehicleVertical = false; + if(((CPed*)pTargetEntity)->m_carInObjective && + ((CPed*)pTargetEntity)->m_carInObjective->GetForward().x == 0.0f && + ((CPed*)pTargetEntity)->m_carInObjective->GetForward().y == 0.0f) + vehicleVertical = true; + if(vehicleVertical){ + Cams[ActiveCam].m_fTransitionBeta = 0.0f; + break; + } +#ifdef PS2_CAM_TRANSITION + camBeta = CGeneral::GetATanOfXY(Cams[(ActiveCam+1)%2].Front.x, Cams[(ActiveCam+1)%2].Front.y); +#else + camBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); +#endif + if(((CPed*)pTargetEntity)->m_carInObjective) + targetBeta = CGeneral::GetATanOfXY(((CPed*)pTargetEntity)->m_carInObjective->GetForward().x, ((CPed*)pTargetEntity)->m_carInObjective->GetForward().y); + else + targetBeta = camBeta; + deltaBeta = targetBeta - camBeta; + while(deltaBeta >= PI) deltaBeta -= 2*PI; + while(deltaBeta < -PI) deltaBeta += 2*PI; + deltaBeta = Abs(deltaBeta); + + door = FindPlayerPed()->m_vehDoor; + if(deltaBeta > HALFPI){ + if(((CPed*)pTargetEntity)->m_carInObjective){ + if(((CPed*)pTargetEntity)->m_carInObjective->IsUpsideDown()){ + if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) + betaOffset = -DEGTORAD(95.0f); + }else{ + if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = -DEGTORAD(95.0f); + } + } + Cams[ActiveCam].m_fTransitionBeta = targetBeta + betaOffset; + }else{ + if(((CPed*)pTargetEntity)->m_carInObjective){ + if(((CPed*)pTargetEntity)->m_carInObjective->IsUpsideDown()){ + if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = -DEGTORAD(55.0f); + else if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) + betaOffset = DEGTORAD(95.0f); + }else{ + if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) + betaOffset = -DEGTORAD(55.0f); + else if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = DEGTORAD(95.0f); + } + } + Cams[ActiveCam].m_fTransitionBeta = targetBeta + betaOffset + PI; + } + break; + + case CCam::MODE_SNIPER: + case CCam::MODE_ROCKETLAUNCHER: + case CCam::MODE_M16_1STPERSON: + case CCam::MODE_SNIPER_RUNABOUT: + case CCam::MODE_ROCKETLAUNCHER_RUNABOUT: + case CCam::MODE_1STPERSON_RUNABOUT: + case CCam::MODE_M16_1STPERSON_RUNABOUT: + case CCam::MODE_FIGHT_CAM_RUNABOUT: + case CCam::MODE_HELICANNON_1STPERSON: + if(FindPlayerVehicle()) + Cams[ActiveCam].Beta = Atan2(FindPlayerVehicle()->GetForward().x, FindPlayerVehicle()->GetForward().y); + else + Cams[ActiveCam].Beta = Atan2(PLAYER->GetForward().x, PLAYER->GetForward().y); + break; + + case CCam::MODE_SYPHON: +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Beta = Cams[(ActiveCam+1)%2].Beta; + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; +#endif + Cams[ActiveCam].Alpha = 0.0f; + Cams[ActiveCam].AlphaSpeed = 0.0f; + break; + + case CCam::MODE_CAM_ON_A_STRING: + // Get into vehicle + betaOffset = DEGTORAD(57.0f); +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; +#endif + if(!m_bLookingAtPlayer || m_bJustCameOutOfGarage) + break; + m_bUseTransitionBeta = true; + targetBeta = CGeneral::GetATanOfXY(pTargetEntity->GetForward().x, pTargetEntity->GetForward().y); +#ifdef PS2_CAM_TRANSITION + camBeta = CGeneral::GetATanOfXY(Cams[(ActiveCam+1)%2].Front.x, Cams[(ActiveCam+1)%2].Front.y); +#else + camBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); +#endif + deltaBeta = targetBeta - camBeta; + while(deltaBeta >= PI) deltaBeta -= 2*PI; + while(deltaBeta < -PI) deltaBeta += 2*PI; + deltaBeta = Abs(deltaBeta); +#ifndef PS2_CAM_TRANSITION + switchFromFixed = Cams[ActiveCam].Mode == CCam::MODE_FIXED; + if(switchFromFixed){ + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + break; + } +#endif + + door = FindPlayerPed()->m_vehDoor; + if(deltaBeta > HALFPI){ + if(((CVehicle*)pTargetEntity)->IsUpsideDown()){ + if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) // BUG: game checks LF twice + betaOffset = -DEGTORAD(57.0f); + }else{ + if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = -DEGTORAD(57.0f); + } + Cams[ActiveCam].m_fTransitionBeta = targetBeta + betaOffset + PI; + }else{ + if(((CVehicle*)pTargetEntity)->IsUpsideDown()){ + if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = -DEGTORAD(57.0f); + else if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) + betaOffset = DEGTORAD(57.0f); + }else{ + if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) + betaOffset = -DEGTORAD(57.0f); + else if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = DEGTORAD(57.0f); + } + Cams[ActiveCam].m_fTransitionBeta = targetBeta + betaOffset; + } + break; + + case CCam::MODE_PED_DEAD_BABY: +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; +#endif + Cams[ActiveCam].Alpha = DEGTORAD(15.0f); + break; + +#ifdef PS2_CAM_TRANSITION + case CCam::MODE_PLAYER_FALLEN_WATER: + Cams[ActiveCam].m_vecLastAboveWaterCamPosition = Cams[(ActiveCam+1)%2].m_vecLastAboveWaterCamPosition; + break; +#endif + + case CCam::MODE_FIGHT_CAM: +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; +#endif + Cams[ActiveCam].Beta = 0.0f; + Cams[ActiveCam].BetaSpeed = 0.0f; + Cams[ActiveCam].Alpha = 0.0f; + Cams[ActiveCam].AlphaSpeed = 0.0f; + break; + } + +#ifndef PS2_CAM_TRANSITION + Cams[ActiveCam].Init(); + Cams[ActiveCam].Mode = newMode; + + m_uiTransitionDuration = 1350; + if(switchSyphonMode) + m_uiTransitionDuration = 1800; + else if(switchFromFight) + m_uiTransitionDuration = 750; + else if(switchPedToCar){ + m_fFractionInterToStopMoving = 0.2f; + m_fFractionInterToStopCatchUp = 0.8f; + m_uiTransitionDuration = 950; + }else if(switchFromFixed){ + m_fFractionInterToStopMoving = 0.05f; + m_fFractionInterToStopCatchUp = 0.95f; + }else if(switch1stPersonToVehicle){ + m_fFractionInterToStopMoving = 0.0f; + m_fFractionInterToStopCatchUp = 1.0f; + m_uiTransitionDuration = 1; + }else + m_uiTransitionDuration = 1350; // already set above +#else + if(switchSyphonMode) + m_uiTransitionDuration = 1800; + else if(switchFromFight) + m_uiTransitionDuration = 750; + else + m_uiTransitionDuration = 1350; +#endif + m_uiTransitionState = 1; + m_uiTimeTransitionStart = CTimer::GetTimeInMilliseconds(); + m_uiTransitionJUSTStarted = 1; +#ifndef PS2_CAM_TRANSITION + if(m_vecDoingSpecialInterPolation){ + m_cvecStartingSourceForInterPol = SourceDuringInter; + m_cvecStartingTargetForInterPol = TargetDuringInter; + m_cvecStartingUpForInterPol = UpDuringInter; + m_fStartingAlphaForInterPol = m_fAlphaDuringInterPol; + m_fStartingBetaForInterPol = m_fBetaDuringInterPol; + }else{ + m_cvecStartingSourceForInterPol = Cams[ActiveCam].Source; + m_cvecStartingTargetForInterPol = Cams[ActiveCam].m_cvecTargetCoorsForFudgeInter; + m_cvecStartingUpForInterPol = Cams[ActiveCam].Up; + m_fStartingAlphaForInterPol = Cams[ActiveCam].m_fTrueAlpha; + m_fStartingBetaForInterPol = Cams[ActiveCam].m_fTrueBeta; + } + Cams[ActiveCam].m_bCamLookingAtVector = m_bLookingAtVector; + Cams[ActiveCam].m_cvecCamFixedModeVector = m_vecFixedModeVector; + Cams[ActiveCam].m_cvecCamFixedModeSource = m_vecFixedModeSource; + Cams[ActiveCam].m_cvecCamFixedModeUpOffSet = m_vecFixedModeUpOffSet; + Cams[ActiveCam].Mode = newMode; // already done above + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + m_uiTransitionState = 1; // these three already done above + m_uiTimeTransitionStart = CTimer::GetTimeInMilliseconds(); + m_uiTransitionJUSTStarted = 1; + m_fStartingFOVForInterPol = Cams[ActiveCam].FOV; + m_cvecSourceSpeedAtStartInter = Cams[ActiveCam].m_cvecSourceSpeedOverOneFrame; + m_cvecTargetSpeedAtStartInter = Cams[ActiveCam].m_cvecTargetSpeedOverOneFrame; + m_cvecUpSpeedAtStartInter = Cams[ActiveCam].m_cvecUpOverOneFrame; + m_fAlphaSpeedAtStartInter = Cams[ActiveCam].m_fAlphaSpeedOverOneFrame; + m_fBetaSpeedAtStartInter = Cams[ActiveCam].m_fBetaSpeedOverOneFrame; + m_fFOVSpeedAtStartInter = Cams[ActiveCam].m_fFovSpeedOverOneFrame; + Cams[ActiveCam].ResetStatics = true; + if(!m_bLookingAtPlayer && m_bScriptParametersSetForInterPol){ + m_fFractionInterToStopMoving = m_fScriptPercentageInterToStopMoving; + m_fFractionInterToStopCatchUp = m_fScriptPercentageInterToCatchUp; + m_uiTransitionDuration = m_fScriptTimeForInterPolation; + } +#endif +} + +void +CCamera::StartTransitionWhenNotFinishedInter(int16 mode) +{ +#ifdef PS2_CAM_TRANSITION + m_vecOldSourceForInter = GetPosition(); + m_vecOldFrontForInter = GetForward(); + m_vecOldUpForInter = GetUp(); + m_vecOldFOVForInter = CDraw::GetFOV(); +#endif + m_vecDoingSpecialInterPolation = true; + StartTransition(mode); +} + +#ifndef PS2_CAM_TRANSITION +void +CCamera::StoreValuesDuringInterPol(CVector &source, CVector &target, CVector &up, float &FOV) +{ + SourceDuringInter = source; + TargetDuringInter = target; + UpDuringInter = up; + FOVDuringInter = FOV; + CVector Dist = source - TargetDuringInter; + float DistOnGround = Dist.Magnitude2D(); + m_fBetaDuringInterPol = CGeneral::GetATanOfXY(Dist.x, Dist.y); + m_fAlphaDuringInterPol = CGeneral::GetATanOfXY(DistOnGround, Dist.z); +} +#endif + + +void +CCamera::SetWideScreenOn(void) +{ + m_WideScreenOn = true; +} + +void +CCamera::SetWideScreenOff(void) +{ + m_bWantsToSwitchWidescreenOff = m_WideScreenOn; +} + +void +CCamera::ProcessWideScreenOn(void) +{ + if(m_bWantsToSwitchWidescreenOff){ + m_bWantsToSwitchWidescreenOff = false; + m_WideScreenOn = false; + m_ScreenReductionPercentage = 0.0f; + m_fFOV_Wide_Screen = 0.0f; + m_fWideScreenReductionAmount = 0.0f; + }else{ + m_fFOV_Wide_Screen = 0.3f*Cams[ActiveCam].FOV; + m_fWideScreenReductionAmount = 1.0f; + m_ScreenReductionPercentage = 30.0f; + } +} + +void +CCamera::DrawBordersForWideScreen(void) +{ + if(m_BlurType == MOTION_BLUR_NONE || m_BlurType == MOTION_BLUR_LIGHT_SCENE) + SetMotionBlurAlpha(80); + + CSprite2d::DrawRect( +#ifdef FIX_BUGS + CRect(0.0f, (SCREEN_HEIGHT/2) * m_ScreenReductionPercentage/100.0f - SCREEN_SCALE_Y(8.0f), +#else + CRect(0.0f, (SCREEN_HEIGHT/2) * m_ScreenReductionPercentage/100.0f - 8.0f, +#endif + SCREEN_WIDTH, 0.0f), + CRGBA(0, 0, 0, 255)); + + CSprite2d::DrawRect( + CRect(0.0f, SCREEN_HEIGHT, +#ifdef FIX_BUGS + SCREEN_WIDTH, SCREEN_HEIGHT - (SCREEN_HEIGHT/2) * m_ScreenReductionPercentage/100.0f - SCREEN_SCALE_Y(8.0f)), +#else + SCREEN_WIDTH, SCREEN_HEIGHT - (SCREEN_HEIGHT/2) * m_ScreenReductionPercentage/100.0f - 8.0f), +#endif + CRGBA(0, 0, 0, 255)); +} + + + +bool +CCamera::IsItTimeForNewcam(int32 obbeMode, int32 time) +{ + CVehicle *veh; + uint32 t = time; // no annoying compiler warnings + CVector fwd; + + if(obbeMode < 0) + return true; + switch(obbeMode){ + case OBBE_WHEEL: + veh = FindPlayerVehicle(); + if(veh){ + if(veh->IsBoat() || veh->GetModelIndex() == MI_RHINO) + return true; + if(!CWorld::GetIsLineOfSightClear(pTargetEntity->GetPosition(), Cams[ActiveCam].Source, true, false, false, false, false, false, false)) + return true; + } + if(CTimer::GetTimeInMilliseconds() > t+5000) + return true; + SetNearClipScript(0.6f); + return false; + case OBBE_1: + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return true; + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 20.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + // too close + if(fwd.Magnitude() < 1.6f) + return true; + return false; + case OBBE_2: + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return true; + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + if(fwd.Magnitude() < 2.0f) + // very close, fix near clip + SetNearClipScript(Max(fwd.Magnitude()*0.5f, 0.05f)); + // too far and driving away from cam + if(fwd.Magnitude() > 19.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + // too close + if(fwd.Magnitude() < 1.6f) + return true; + return false; + case OBBE_3: + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 28.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_1STPERSON: + return CTimer::GetTimeInMilliseconds() > t+3000; + case OBBE_5: + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return true; + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 28.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_ONSTRING: + return CTimer::GetTimeInMilliseconds() > t+3000; + case OBBE_COPCAR: + return CTimer::GetTimeInMilliseconds() > t+2000 && !FindPlayerVehicle()->GetIsOnScreen(); + case OBBE_COPCAR_WHEEL: + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return true; + if(!CWorld::GetIsLineOfSightClear(pTargetEntity->GetPosition(), Cams[ActiveCam].Source, true, false, false, false, false, false, false)) + return true; + if(CTimer::GetTimeInMilliseconds() > t+1000) + return true; + SetNearClipScript(0.6f); + return false; + + // Ped modes + case OBBE_9: + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 20.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_10: + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 8.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_11: + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 25.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_12: + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 8.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_13: + return CTimer::GetTimeInMilliseconds() > t+5000; + default: + return false; + } +} + +bool +CCamera::TryToStartNewCamMode(int obbeMode) +{ + CVehicle *veh; + CVector target, camPos, playerSpeed, fwd; + float ground; + bool foundGround; + int i; + + if(obbeMode < 0) + return true; + switch(obbeMode){ + case OBBE_WHEEL: + veh = FindPlayerVehicle(); + if(veh == nil || veh->IsBoat() || veh->GetModelIndex() == MI_RHINO) + return false; + target = Multiply3x3(FindPlayerVehicle()->GetMatrix(), CVector(-1.4f, -2.3f, 0.3f)); + target += FindPlayerVehicle()->GetPosition(); + if(!CWorld::GetIsLineOfSightClear(veh->GetPosition(), target, true, false, false, false, false, false, false)) + return false; + TakeControl(veh, CCam::MODE_WHEELCAM, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_1: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 20.0f*playerSpeed; + camPos += 3.0f*CVector(playerSpeed.y, -playerSpeed.x, 0.0f); + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return false; + + ground = CWorld::FindGroundZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 1.5f; + else{ + ground = CWorld::FindRoofZFor3DCoord(camPos.x, camPos.y, camPos.z-5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 1.5f; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + fwd = FindPlayerCoors() - camPos; + fwd.z = 0.0f; + // too far and driving away from cam + if(fwd.Magnitude() > 20.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return false; + // too close + if(fwd.Magnitude() < 1.6f) + return true; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_2: + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return false; + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 16.0f*playerSpeed; + camPos += 2.5f*CVector(playerSpeed.y, -playerSpeed.x, 0.0f); + + ground = CWorld::FindGroundZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 0.5f; + else{ + ground = CWorld::FindRoofZFor3DCoord(camPos.x, camPos.y, camPos.z-5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 0.5f; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + fwd = FindPlayerCoors() - camPos; + fwd.z = 0.0f; + // too far and driving away from cam + if(fwd.Magnitude() > 19.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return false; + // too close + if(fwd.Magnitude() < 1.6f) + return true; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_3: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 30.0f*playerSpeed; + camPos += 8.0f*CVector(playerSpeed.y, -playerSpeed.x, 0.0f); + + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_1STPERSON: + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_5: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 30.0f*playerSpeed; + camPos += 6.0f*CVector(playerSpeed.y, -playerSpeed.x, 0.0f); + + ground = CWorld::FindGroundZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 3.5f; + else{ + ground = CWorld::FindRoofZFor3DCoord(camPos.x, camPos.y, camPos.z-5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 3.5f; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_ONSTRING: + TakeControl(FindPlayerEntity(), CCam::MODE_CAM_ON_A_STRING, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_COPCAR: +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return false; +#endif + if(FindPlayerPed()->m_pWanted->GetWantedLevel() < 1) + return false; + if(FindPlayerVehicle() == nil) + return false; + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return false; + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + veh = CPools::GetVehiclePool()->GetSlot(i); + if(veh && veh->IsCar() && veh != FindPlayerVehicle() && veh->bIsLawEnforcer){ + float dx = veh->GetPosition().x - FindPlayerCoors().x; + float dy = veh->GetPosition().y - FindPlayerCoors().y; + float dist = (veh->GetPosition() - FindPlayerCoors()).Magnitude(); + if(dist < 30.0f){ + if(dx*FindPlayerVehicle()->GetForward().x + dy*FindPlayerVehicle()->GetForward().y < 0.0f && + veh->GetForward().x*FindPlayerVehicle()->GetForward().x + veh->GetForward().y*FindPlayerVehicle()->GetForward().y > 0.8f){ + TakeControl(veh, CCam::MODE_CAM_ON_A_STRING, JUMP_CUT, CAMCONTROL_OBBE); + return true; + } + } + } + } + return false; + case OBBE_COPCAR_WHEEL: +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return false; +#endif + if(FindPlayerPed()->m_pWanted->GetWantedLevel() < 1) + return false; + if(FindPlayerVehicle() == nil) + return false; + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return false; + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + veh = CPools::GetVehiclePool()->GetSlot(i); + if(veh && veh->IsCar() && veh != FindPlayerVehicle() && veh->bIsLawEnforcer){ + float dx = veh->GetPosition().x - FindPlayerCoors().x; + float dy = veh->GetPosition().y - FindPlayerCoors().y; + float dist = (veh->GetPosition() - FindPlayerCoors()).Magnitude(); + if(dist < 30.0f){ + if(dx*FindPlayerVehicle()->GetForward().x + dy*FindPlayerVehicle()->GetForward().y < 0.0f && + veh->GetForward().x*FindPlayerVehicle()->GetForward().x + veh->GetForward().y*FindPlayerVehicle()->GetForward().y > 0.8f){ + target = Multiply3x3(veh->GetMatrix(), CVector(-1.4f, -2.3f, 0.3f)); + target += veh->GetPosition(); + if(!CWorld::GetIsLineOfSightClear(veh->GetPosition(), target, true, false, false, false, false, false, false)) + return false; + TakeControl(veh, CCam::MODE_WHEELCAM, JUMP_CUT, CAMCONTROL_OBBE); + return true; + } + } + } + } + return false; + + case OBBE_9: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 15.0f*playerSpeed; + camPos += CVector(2.0f, 1.0f, 0.0f); + + ground = CWorld::FindGroundZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 0.5f; + else{ + ground = CWorld::FindRoofZFor3DCoord(camPos.x, camPos.y, camPos.z-5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 0.5f; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_10: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 5.0f*playerSpeed; + camPos += CVector(2.0f, 1.0f, 0.5f); + + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_11: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 20.0f*playerSpeed; + camPos += CVector(2.0f, 1.0f, 20.0f); + + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_12: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 5.0f*playerSpeed; + camPos += CVector(2.0f, 1.0f, 10.5f); + + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_13: +#ifdef FIX_BUGS + TakeControl(FindPlayerEntity(), CCam::MODE_TOP_DOWN_PED, JUMP_CUT, CAMCONTROL_OBBE); +#else + TakeControl(FindPlayerEntity(), CCam::MODE_TOPDOWN, JUMP_CUT, CAMCONTROL_OBBE); +#endif + return true; + default: + return false; + } +} + +int32 SequenceOfCams[16] = { + OBBE_WHEEL, OBBE_COPCAR, OBBE_3, OBBE_1, OBBE_3, OBBE_COPCAR_WHEEL, + OBBE_2, OBBE_3, OBBE_COPCAR_WHEEL, OBBE_COPCAR, OBBE_2, OBBE_3, + OBBE_5, OBBE_3, + OBBE_ONSTRING // actually unused... +}; + +void +CCamera::ProcessObbeCinemaCameraCar(void) +{ + static int OldMode = -1; + static int32 TimeForNext = 0; + int i = 0; + + if(!bDidWeProcessAnyCinemaCam){ + OldMode = -1; + CHud::SetHelpMessage(TheText.Get("CINCAM"), true); + } + + if(!bDidWeProcessAnyCinemaCam || IsItTimeForNewcam(SequenceOfCams[OldMode], TimeForNext)){ + // This is very strange code... + for(OldMode = (OldMode+1) % 14; + !TryToStartNewCamMode(SequenceOfCams[OldMode]) && i <= 14; + OldMode = (OldMode+1) % 14) + i++; + TimeForNext = CTimer::GetTimeInMilliseconds(); + if(i >= 14){ + OldMode = 14; + TryToStartNewCamMode(SequenceOfCams[14]); + } + } + + m_iModeObbeCamIsInForCar = OldMode; + bDidWeProcessAnyCinemaCam = true; +} + +int32 SequenceOfPedCams[5] = { OBBE_9, OBBE_10, OBBE_11, OBBE_12, OBBE_13 }; + +void +CCamera::ProcessObbeCinemaCameraPed(void) +{ + // static bool bObbePedProcessed = false; // unused + static int PedOldMode = -1; + static int32 PedTimeForNext = 0; + + if(!bDidWeProcessAnyCinemaCam) + PedOldMode = -1; + + if(!bDidWeProcessAnyCinemaCam || IsItTimeForNewcam(SequenceOfPedCams[PedOldMode], PedTimeForNext)){ + for(PedOldMode = (PedOldMode+1) % 5; + !TryToStartNewCamMode(SequenceOfPedCams[PedOldMode]); + PedOldMode = (PedOldMode+1) % 5); + PedTimeForNext = CTimer::GetTimeInMilliseconds(); + } + bDidWeProcessAnyCinemaCam = true; +} + +void +CCamera::DontProcessObbeCinemaCamera(void) +{ + bDidWeProcessAnyCinemaCam = false; +} + +void +CCamera::LoadTrainCamNodes(char const *name) +{ + CFileMgr::SetDir("data"); + + char token[16] = { 0 }; + char filename[16] = { 0 }; + uint8 *buf; + ssize_t bufpos = 0; + int field = 0; + int tokpos = 0; + char c; + int i; + ssize_t len; + + strcpy(filename, name); + len = (int)strlen(filename); + filename[len] = '.'; + filename[len+1] = 'd'; + filename[len+2] = 'a'; + filename[len+3] = 't'; + + m_uiNumberOfTrainCamNodes = 0; + + buf = new uint8[20000]; + len = CFileMgr::LoadFile(filename, buf, 20000, "r"); + + for(i = 0; i < MAX_NUM_OF_NODES; i++){ + m_arrTrainCamNode[i].m_cvecPointToLookAt = CVector(0.0f, 0.0f, 0.0f); + m_arrTrainCamNode[i].m_cvecMinPointInRange = CVector(0.0f, 0.0f, 0.0f); + m_arrTrainCamNode[i].m_cvecMaxPointInRange = CVector(0.0f, 0.0f, 0.0f); + m_arrTrainCamNode[i].m_fDesiredFOV = 0.0f; + m_arrTrainCamNode[i].m_fNearClip = 0.0f; + } + + while(bufpos <= len){ + c = buf[bufpos]; + switch(c){ + case '-': + case '.': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': +// case '10': case '11': case '12': case '13': // ahem... + token[tokpos++] = c; + bufpos++; + break; + + case ',': + case ';': // game has the code for this duplicated but we handle both under the same case + switch((field+14)%14){ + case 0: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecCamPosition.x = atof(token); + break; + case 1: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecCamPosition.y = atof(token); + break; + case 2: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecCamPosition.z = atof(token); + break; + case 3: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecPointToLookAt.x = atof(token); + break; + case 4: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecPointToLookAt.y = atof(token); + break; + case 5: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecPointToLookAt.z = atof(token); + break; + case 6: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMinPointInRange.x = atof(token); + break; + case 7: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMinPointInRange.y = atof(token); + break; + case 8: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMinPointInRange.z = atof(token); + break; + case 9: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMaxPointInRange.x = atof(token); + break; + case 10: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMaxPointInRange.y = atof(token); + break; + case 11: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMaxPointInRange.z = atof(token); + break; + case 12: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_fDesiredFOV = atof(token); + break; + case 13: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_fNearClip = atof(token); + m_uiNumberOfTrainCamNodes++; + break; + } + field++; + bufpos++; + memset(token, 0, sizeof(token)); + tokpos = 0; + break; + + default: + bufpos++; + break; + } + } + + delete[] buf; + CFileMgr::SetDir(""); +} + +void +CCamera::Process_Train_Camera_Control(void) +{ + bool found = false; + CTrain *target = (CTrain*)pTargetEntity; + m_bUseSpecialFovTrain = true; + static bool OKtoGoBackToNodeCam = true; // only ever set to true + uint32 i; + + if(target->m_nTrackId == TRACK_ELTRAIN && !m_bAboveGroundTrainNodesLoaded){ + m_bAboveGroundTrainNodesLoaded = true; + m_bBelowGroundTrainNodesLoaded = false; + LoadTrainCamNodes("Train"); + m_uiTimeLastChange = CTimer::GetTimeInMilliseconds(); + OKtoGoBackToNodeCam = true; + m_iCurrentTrainCamNode = 0; + } + if(target->m_nTrackId == TRACK_SUBWAY && !m_bBelowGroundTrainNodesLoaded){ + m_bBelowGroundTrainNodesLoaded = true; + m_bAboveGroundTrainNodesLoaded = false; + LoadTrainCamNodes("Train2"); + m_uiTimeLastChange = CTimer::GetTimeInMilliseconds(); + OKtoGoBackToNodeCam = true; + m_iCurrentTrainCamNode = 0; + } + + m_bTargetJustBeenOnTrain = true; + uint32 node = m_iCurrentTrainCamNode; + for(i = 0; i < m_uiNumberOfTrainCamNodes && !found; i++){ + if(target->IsWithinArea(m_arrTrainCamNode[node].m_cvecMinPointInRange.x, + m_arrTrainCamNode[node].m_cvecMinPointInRange.y, + m_arrTrainCamNode[node].m_cvecMinPointInRange.z, + m_arrTrainCamNode[node].m_cvecMaxPointInRange.x, + m_arrTrainCamNode[node].m_cvecMaxPointInRange.y, + m_arrTrainCamNode[node].m_cvecMaxPointInRange.z)){ + m_iCurrentTrainCamNode = node; + found = true; + } + node++; + if(node >= m_uiNumberOfTrainCamNodes) + node = 0; + } +#ifdef FIX_BUGS + // Not really a bug but be nice and respect the debug mode + if(DebugCamMode){ + TakeControl(target, DebugCamMode, JUMP_CUT, CAMCONTROL_SCRIPT); + return; + } +#endif + + if(found){ + SetWideScreenOn(); + if(DotProduct(((CTrain*)pTargetEntity)->GetMoveSpeed(), pTargetEntity->GetForward()) < 0.001f){ + TakeControl(FindPlayerPed(), CCam::MODE_FOLLOWPED, JUMP_CUT, CAMCONTROL_SCRIPT); + if(target->Doors[0].IsFullyOpen()) + SetWideScreenOff(); + }else{ + SetCamPositionForFixedMode(m_arrTrainCamNode[m_iCurrentTrainCamNode].m_cvecCamPosition, CVector(0.0f, 0.0f, 0.0f)); + if(m_arrTrainCamNode[m_iCurrentTrainCamNode].m_cvecPointToLookAt.x == 999.0f && + m_arrTrainCamNode[m_iCurrentTrainCamNode].m_cvecPointToLookAt.y == 999.0f && + m_arrTrainCamNode[m_iCurrentTrainCamNode].m_cvecPointToLookAt.z == 999.0f) + TakeControl(target, CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_SCRIPT); + else + TakeControlNoEntity(m_arrTrainCamNode[m_iCurrentTrainCamNode].m_cvecPointToLookAt, JUMP_CUT, CAMCONTROL_SCRIPT); + RwCameraSetNearClipPlane(Scene.camera, m_arrTrainCamNode[m_iCurrentTrainCamNode].m_fNearClip); + } + }else{ + if(DotProduct(((CTrain*)pTargetEntity)->GetMoveSpeed(), pTargetEntity->GetForward()) < 0.001f){ + TakeControl(FindPlayerPed(), CCam::MODE_FOLLOWPED, JUMP_CUT, CAMCONTROL_SCRIPT); + if(target->Doors[0].IsFullyOpen()) + SetWideScreenOff(); + } + } +} + + +void +CCamera::LoadPathSplines(int file) +{ + bool reading = true; + char c, token[32] = { 0 }; + int i, j, n; + + n = 0; + + for(i = 0; i < MAX_NUM_OF_SPLINETYPES; i++) + for(j = 0; j < CCamPathSplines::MAXPATHLENGTH; j++) + m_arrPathArray[i].m_arr_PathData[j] = 0.0f; + + m_bStartingSpline = false; + + i = 0; + j = 0; + while(reading){ + CFileMgr::Read(file, &c, 1); + switch(c){ + case '\0': + reading = false; + break; + + case '+': case '-': case '.': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'e': case 'E': + token[n++] = c; + break; + + case ',': +#ifdef FIX_BUGS + if(i < MAX_NUM_OF_SPLINETYPES && j < CCamPathSplines::MAXPATHLENGTH) +#endif + m_arrPathArray[i].m_arr_PathData[j] = atof(token); + j++; + memset(token, 0, 32); + n = 0; + break; + + case ';': +#ifdef FIX_BUGS + if(i < MAX_NUM_OF_SPLINETYPES && j < CCamPathSplines::MAXPATHLENGTH) +#endif + m_arrPathArray[i].m_arr_PathData[j] = atof(token); + i++; + j = 0; + memset(token, 0, 32); + n = 0; + } + } +} + +void +CCamera::FinishCutscene(void) +{ + SetPercentAlongCutScene(100.0f); + m_fPositionAlongSpline = 1.0f; + m_bcutsceneFinished = true; +} + +uint32 +CCamera::GetCutSceneFinishTime(void) +{ + int cam = ActiveCam; + if (Cams[cam].Mode == CCam::MODE_FLYBY) + return Cams[cam].m_uiFinishTime; + cam = (cam + 1) % 2; + if (Cams[cam].Mode == CCam::MODE_FLYBY) + return Cams[cam].m_uiFinishTime; + + return 0; +} + +void +CCamera::SetCamCutSceneOffSet(const CVector &pos) +{ + m_vecCutSceneOffset = pos; +}; + +void +CCamera::SetPercentAlongCutScene(float percent) +{ + if(Cams[ActiveCam].Mode == CCam::MODE_FLYBY) + Cams[ActiveCam].m_fTimeElapsedFloat = percent/100.0f * Cams[ActiveCam].m_uiFinishTime; + else if(Cams[(ActiveCam+1)%2].Mode == CCam::MODE_FLYBY) + Cams[(ActiveCam+1)%2].m_fTimeElapsedFloat = percent/100.0f * Cams[(ActiveCam+1)%2].m_uiFinishTime; +} + +void +CCamera::SetParametersForScriptInterpolation(float stopMoving, float catchUp, int32 time) +{ + m_fScriptPercentageInterToStopMoving = stopMoving * 0.01f; + m_fScriptPercentageInterToCatchUp = catchUp * 0.01f; + m_fScriptTimeForInterPolation = time; + m_bScriptParametersSetForInterPol = true; +} + +void +CCamera::SetZoomValueFollowPedScript(int16 dist) +{ + switch (dist) { + case 0: m_fPedZoomValueScript = 0.25f; break; + case 1: m_fPedZoomValueScript = 1.5f; break; + case 2: m_fPedZoomValueScript = 2.9f; break; + default: break; + } + + m_bUseScriptZoomValuePed = true; +} + +void +CCamera::SetZoomValueCamStringScript(int16 dist) +{ +#ifdef FREE_CAM + if (bFreeCam) { + switch (dist) { + case 0: m_fCarZoomValueScript = ((CVehicle*)Cams[ActiveCam].CamTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_1 : FREE_CAR_ZOOM_VALUE_1; break; + case 1: m_fCarZoomValueScript = ((CVehicle*)Cams[ActiveCam].CamTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_2 : FREE_CAR_ZOOM_VALUE_2; break; + case 2: m_fCarZoomValueScript = ((CVehicle*)Cams[ActiveCam].CamTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_3 : FREE_CAR_ZOOM_VALUE_3; break; + default: break; + } + } else +#endif + { + switch (dist) { + case 0: m_fCarZoomValueScript = DEFAULT_CAR_ZOOM_VALUE_1; break; + case 1: m_fCarZoomValueScript = DEFAULT_CAR_ZOOM_VALUE_2; break; + case 2: m_fCarZoomValueScript = DEFAULT_CAR_ZOOM_VALUE_3; break; + default: break; + } + } + + m_bUseScriptZoomValueCar = true; +} + +void +CCamera::SetNearClipScript(float clip) +{ + m_fNearClipScript = clip; + m_bUseNearClipScript = true; +} + + + +void +CCamera::ProcessFade(void) +{ + float fade = (CTimer::GetTimeInMilliseconds() - m_uiFadeTimeStarted)/1000.0f; + // Why even set CDraw::FadeValue if m_fFLOATingFade sets it anyway? + if(m_bFading){ + if(m_iFadingDirection == FADE_IN){ + if(m_fTimeToFadeOut != 0.0f){ + m_fFLOATingFade = 255.0f - 255.0f*fade/m_fTimeToFadeOut; + if(m_fFLOATingFade <= 0.0f){ + m_bFading = false; + CDraw::FadeValue = 0; + m_fFLOATingFade = 0.0f; + } + }else{ + m_bFading = false; + CDraw::FadeValue = 0; + m_fFLOATingFade = 0.0f; + } + }else if(m_iFadingDirection == FADE_OUT){ + if(m_fTimeToFadeOut != 0.0f){ + m_fFLOATingFade = 255.0f*fade/m_fTimeToFadeOut; + if(m_fFLOATingFade >= 255.0f){ + m_bFading = false; + CDraw::FadeValue = 255; + m_fFLOATingFade = 255.0f; + } + }else{ + m_bFading = false; + CDraw::FadeValue = 255; + m_fFLOATingFade = 255.0f; + } + } + CDraw::FadeValue = m_fFLOATingFade; + } +} + +void +CCamera::ProcessMusicFade(void) +{ + float fade = (CTimer::GetTimeInMilliseconds() - m_uiFadeTimeStartedMusic)/1000.0f; + if(m_bMusicFading){ + if(m_iMusicFadingDirection == FADE_IN){ + if(m_fTimeToFadeMusic == 0.0f) + m_fTimeToFadeMusic = 1.0f; + + m_fFLOATingFadeMusic = 255.0f*fade/m_fTimeToFadeMusic; + if(m_fFLOATingFadeMusic > 255.0f){ + m_bMusicFading = false; + m_fFLOATingFadeMusic = 0.0f; + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); + }else{ + DMAudio.SetEffectsFadeVol(m_fFLOATingFadeMusic/255.0f * 127); + DMAudio.SetMusicFadeVol(m_fFLOATingFadeMusic/255.0f * 127); + } + }else if(m_iMusicFadingDirection == FADE_OUT){ + if(m_fTimeToFadeMusic == 0.0f) + m_fTimeToFadeMusic = 1.0f; + +#ifdef PS2_MENU + if(m_bMoveCamToAvoidGeom || TheMemoryCard.StillToFadeOut){ +#else + if(m_bMoveCamToAvoidGeom || StillToFadeOut){ +#endif + m_fFLOATingFadeMusic = 256.0f; + m_bMoveCamToAvoidGeom = false; + }else + m_fFLOATingFadeMusic = 255.0f*fade/m_fTimeToFadeMusic; + + if(m_fFLOATingFadeMusic > 255.0f){ + m_bMusicFading = false; + m_fFLOATingFadeMusic = 255.0f; + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + }else{ + DMAudio.SetEffectsFadeVol(127 - m_fFLOATingFadeMusic/255.0f * 127); + DMAudio.SetMusicFadeVol(127 - m_fFLOATingFadeMusic/255.0f * 127); + } + } + } +} + +void +CCamera::Fade(float timeout, int16 direction) +{ + m_bFading = true; + m_iFadingDirection = direction; + m_fTimeToFadeOut = timeout; + m_uiFadeTimeStarted = CTimer::GetTimeInMilliseconds(); + if(!m_bIgnoreFadingStuffForMusic){ + m_bMusicFading = true; + m_iMusicFadingDirection = direction; + m_fTimeToFadeMusic = timeout; + m_uiFadeTimeStartedMusic = CTimer::GetTimeInMilliseconds(); +// Not on PS2 + if(!m_bUnknown && m_iMusicFadingDirection == FADE_OUT){ + unknown++; + if(unknown >= 2){ + m_bUnknown = true; + unknown = 0; + }else + m_bMoveCamToAvoidGeom = true; + } + } +} + +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; +} + +bool +CCamera::GetFading(void) +{ + return m_bFading; +} + +int +CCamera::GetFadingDirection(void) +{ + if(m_bFading) + return m_iFadingDirection == FADE_IN ? FADE_IN : FADE_OUT; + else + return FADE_NONE; +} + +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::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::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; +} + + + +int +CCamera::GetLookDirection(void) +{ + if(Cams[ActiveCam].Mode == CCam::MODE_CAM_ON_A_STRING || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_BEHINDBOAT || + Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED) + return Cams[ActiveCam].DirectionWasLooking; + return LOOKING_FORWARD; +} + +bool +CCamera::GetLookingForwardFirstPerson(void) +{ + return Cams[ActiveCam].Mode == CCam::MODE_1STPERSON && + Cams[ActiveCam].DirectionWasLooking == LOOKING_FORWARD; +} + +bool +CCamera::GetLookingLRBFirstPerson(void) +{ + return Cams[ActiveCam].Mode == CCam::MODE_1STPERSON && Cams[ActiveCam].DirectionWasLooking != LOOKING_FORWARD; +} + +void +CCamera::SetCameraDirectlyBehindForFollowPed_CamOnAString(void) +{ + m_bCamDirectlyBehind = true; + CPlayerPed *player = FindPlayerPed(); + if (player) + m_PedOrientForBehindOrInFront = CGeneral::GetATanOfXY(player->GetForward().x, player->GetForward().y); +} + +void +CCamera::SetCameraDirectlyInFrontForFollowPed_CamOnAString(void) +{ + m_bCamDirectlyInFront = true; + CPlayerPed *player = FindPlayerPed(); + if (player) + m_PedOrientForBehindOrInFront = CGeneral::GetATanOfXY(player->GetForward().x, player->GetForward().y); +} + +void +CCamera::SetNewPlayerWeaponMode(int16 mode, int16 minZoom, int16 maxZoom) +{ + PlayerWeaponMode.Mode = mode; + PlayerWeaponMode.MaxZoom = maxZoom; + PlayerWeaponMode.MinZoom = minZoom; + PlayerWeaponMode.Duration = 0.0f; +} + +void +CCamera::ClearPlayerWeaponMode(void) +{ + PlayerWeaponMode.Mode = 0; + PlayerWeaponMode.MaxZoom = 1; + PlayerWeaponMode.MinZoom = -1; + PlayerWeaponMode.Duration = 0.0f; +} + +void +CCamera::UpdateAimingCoors(CVector const &coors) +{ + m_cvecAimingTargetCoors = coors; +} + +bool +CCamera::Find3rdPersonCamTargetVector(float dist, CVector pos, CVector &source, CVector &target) +{ + if(CPad::GetPad(0)->GetLookBehindForPed()){ + source = pos; + target = dist*Cams[ActiveCam].CamTargetEntity->GetForward() + source; + return false; + }else{ + float angleX = DEGTORAD((m_f3rdPersonCHairMultX-0.5f) * 1.8f * 0.5f * Cams[ActiveCam].FOV * CDraw::GetAspectRatio()); + float angleY = DEGTORAD((0.5f-m_f3rdPersonCHairMultY) * 1.8f * 0.5f * Cams[ActiveCam].FOV); + source = Cams[ActiveCam].Source; + target = Cams[ActiveCam].Front; + target += Cams[ActiveCam].Up * Tan(angleY); + target += CrossProduct(Cams[ActiveCam].Front, Cams[ActiveCam].Up) * Tan(angleX); + target.Normalise(); + source += DotProduct(pos - source, target)*target; + target = dist*target + source; + return true; + } +} + +float +CCamera::Find3rdPersonQuickAimPitch(void) +{ + float clampedFrontZ = Clamp(Cams[ActiveCam].Front.z, -1.0f, 1.0f); + + float rot = Asin(clampedFrontZ); + + return -(DEGTORAD(((0.5f - m_f3rdPersonCHairMultY) * 1.8f * 0.5f * Cams[ActiveCam].FOV)) + rot); +} + + + +void +CCamera::SetRwCamera(RwCamera *cam) +{ + m_pRwCamera = cam; + m_viewMatrix.Attach(RwCameraGetViewMatrix(m_pRwCamera), false); + CMBlur::MotionBlurOpen(m_pRwCamera); +} + +void +CCamera::CalculateDerivedValues(void) +{ + m_cameraMatrix = Invert(GetMatrix()); + + float hfov = DEGTORAD(CDraw::GetScaledFOV()/2.0f); + float c = Cos(hfov); + float s = Sin(hfov); + + // right plane + m_vecFrustumNormals[0] = CVector(c, -s, 0.0f); + // left plane + m_vecFrustumNormals[1] = CVector(-c, -s, 0.0f); + + c /= CDraw::FindAspectRatio(); + s /= CDraw::FindAspectRatio(); + // bottom plane + m_vecFrustumNormals[2] = CVector(0.0f, -s, -c); + // top plane + m_vecFrustumNormals[3] = CVector(0.0f, -s, c); + + if(GetForward().x == 0.0f && GetForward().y == 0.0f) + GetForward().x = 0.0001f; + else + Orientation = Atan2(GetForward().x, GetForward().y); + + CamFrontXNorm = GetForward().x; + CamFrontYNorm = GetForward().y; + float l = Sqrt(SQR(CamFrontXNorm) + SQR(CamFrontYNorm)); + if(l == 0.0f) + CamFrontXNorm = 1.0f; + else{ + CamFrontXNorm /= l; + CamFrontYNorm /= l; + } +} + +bool +CCamera::IsPointVisible(const CVector ¢er, const CMatrix *mat) +{ +#ifdef GTA_PS2 + CVuVector c; + TransformPoint(c, *mat, center); +#else + CVector c = center; + #ifdef FIX_BUGS + c = *mat * center; + #else + RwV3dTransformPoints(&c, &c, 1, (RwMatrix*)mat); + #endif +#endif + 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::IsSphereVisible(const CVector ¢er, float radius, Const CMatrix *mat) +{ +#ifdef GTA_PS2 + CVuVector c; + TransformPoint(c, *mat, center); +#else + CVector c = center; + #ifdef FIX_BUGS + c = *mat * center; + #else + RwV3dTransformPoints(&c, &c, 1, (RwMatrix*)mat); + #endif +#endif + 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::IsSphereVisible(const CVector ¢er, float radius) +{ +#if GTA_VERSION < GTA3_PC_10 // not sure this condition is the right one + // Maybe this was a copy of the other function with m_cameraMatrix + return IsSphereVisible(center, radius, &m_cameraMatrix); +#else + // ...and on PC they decided to call the other one with a default matrix. + CMatrix mat(GetCameraMatrix()); // this matrix construction is stupid and gone in VC + return IsSphereVisible(center, radius, &mat); +#endif +} + +bool +CCamera::IsBoxVisible(CVUVECTOR *box, const CMatrix *mat) +{ + int i; + int frustumTests[6] = { 0 }; +#ifdef GTA_PS2 + TransformPoints(box, 8, *mat, box); +#else + #ifdef FIX_BUGS + for (i = 0; i < 8; i++) + box[i] = *mat * box[i]; + #else + RwV3dTransformPoints(box, box, 8, (RwMatrix*)mat); + #endif +#endif + + 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; +} + + + +CCamPathSplines::CCamPathSplines(void) +{ + int i; + for(i = 0; i < MAXPATHLENGTH; i++) + m_arr_PathData[i] = 0.0f; +} diff --git a/src/core/Camera.h b/src/core/Camera.h new file mode 100644 index 0000000..07a05cb --- /dev/null +++ b/src/core/Camera.h @@ -0,0 +1,653 @@ +#pragma once +#include "Placeable.h" + +class CEntity; +class CPed; +class CAutomobile; +class CGarage; + +extern int16 DebugCamMode; + +enum +{ + NUMBER_OF_VECTORS_FOR_AVERAGE = 2, + MAX_NUM_OF_SPLINETYPES = 4, + MAX_NUM_OF_NODES = 800 // for trains +}; + +#define DEFAULT_NEAR (0.9f) +enum +{ + CAM_ZOOM_1STPRS, + CAM_ZOOM_1, + CAM_ZOOM_2, + CAM_ZOOM_3, + CAM_ZOOM_TOPDOWN, + CAM_ZOOM_CINEMATIC, +}; + +#ifdef FREE_CAM // LCS values +#define FREE_CAR_ZOOM_VALUE_1 (-1.0f) +#define FREE_CAR_ZOOM_VALUE_2 (2.0f) +#define FREE_CAR_ZOOM_VALUE_3 (6.0f) + +#define FREE_BOAT_ZOOM_VALUE_1 (-2.41f) +#define FREE_BOAT_ZOOM_VALUE_2 (6.49f) +#define FREE_BOAT_ZOOM_VALUE_3 (15.0f) +#endif + +#define DEFAULT_CAR_ZOOM_VALUE_1 (0.05f) +#define DEFAULT_CAR_ZOOM_VALUE_2 (1.9f) +#define DEFAULT_CAR_ZOOM_VALUE_3 (3.9f) + +const float DefaultFOV = 70.0f; // beta: 80.0f + +class CCam +{ +public: + enum + { + MODE_NONE = 0, + MODE_TOPDOWN, + MODE_GTACLASSIC, + MODE_BEHINDCAR, + MODE_FOLLOWPED, + MODE_AIMING, + MODE_DEBUG, + MODE_SNIPER, + MODE_ROCKETLAUNCHER, + MODE_MODELVIEW, + MODE_BILL, + MODE_SYPHON, + MODE_CIRCLE, + MODE_CHEESYZOOM, + MODE_WHEELCAM, + MODE_FIXED, + MODE_1STPERSON, + MODE_FLYBY, + MODE_CAM_ON_A_STRING, + MODE_REACTION, + MODE_FOLLOW_PED_WITH_BIND, + MODE_CHRIS, + MODE_BEHINDBOAT, + MODE_PLAYER_FALLEN_WATER, + MODE_CAM_ON_TRAIN_ROOF, + MODE_CAM_RUNNING_SIDE_TRAIN, + MODE_BLOOD_ON_THE_TRACKS, + MODE_IM_THE_PASSENGER_WOOWOO, + MODE_SYPHON_CRIM_IN_FRONT, + MODE_PED_DEAD_BABY, + MODE_PILLOWS_PAPS, + MODE_LOOK_AT_CARS, + MODE_ARRESTCAM_ONE, + MODE_ARRESTCAM_TWO, + MODE_M16_1STPERSON, + MODE_SPECIAL_FIXED_FOR_SYPHON, + MODE_FIGHT_CAM, + MODE_TOP_DOWN_PED, + MODE_SNIPER_RUNABOUT, + MODE_ROCKETLAUNCHER_RUNABOUT, + MODE_1STPERSON_RUNABOUT, + MODE_M16_1STPERSON_RUNABOUT, + MODE_FIGHT_CAM_RUNABOUT, + MODE_EDITOR, + MODE_HELICANNON_1STPERSON, // vice city leftover + }; + + 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; + bool LookingRight; + bool ResetStatics; //for interpolation type stuff to work + bool Rotating; + + int16 Mode; // CameraMode + uint32 m_uiFinishTime; + + int m_iDoCollisionChecksOnFrameNum; + int m_iDoCollisionCheckEveryNumOfFrames; + int m_iFrameNumWereAt; + 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_fRoadOffSet; + float m_fAmountFractionObscured; + float m_fAlphaSpeedOverOneFrame; + float m_fBetaSpeedOverOneFrame; + float m_fBufferedTargetBeta; + float m_fBufferedTargetOrientation; + float m_fBufferedTargetOrientationSpeed; + float m_fCamBufferedHeight; + float m_fCamBufferedHeightSpeed; + float m_fCloseInPedHeightOffset; + float m_fCloseInPedHeightOffsetSpeed; + float m_fCloseInCarHeightOffset; + float m_fCloseInCarHeightOffsetSpeed; + float m_fDimensionOfHighestNearCar; + float m_fDistanceBeforeChanges; + float m_fFovSpeedOverOneFrame; + float m_fMinDistAwayFromCamWhenInterPolating; + float m_fPedBetweenCameraHeightOffset; + float m_fPlayerInFrontSyphonAngleOffSet; + 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; + float m_fInitialPlayerOrientation; //used for first person + + float Alpha; + float AlphaSpeed; + float FOV; + float FOVSpeed; + float Beta; + float BetaSpeed; + float Distance; + float DistanceSpeed; + float CA_MIN_DISTANCE; + float CA_MAX_DISTANCE; + float SpeedVar; + + CVector m_cvecSourceSpeedOverOneFrame; + CVector m_cvecTargetSpeedOverOneFrame; + CVector m_cvecUpOverOneFrame; + + CVector m_cvecTargetCoorsForFudgeInter; + CVector m_cvecCamFixedModeVector; + CVector m_cvecCamFixedModeSource; + CVector m_cvecCamFixedModeUpOffSet; + CVector m_vecLastAboveWaterCamPosition; //helper for when the player has gone under the water + CVector m_vecBufferedPlayerBodyOffset; + + // The three vectors that determine this camera for this frame + CVector Front; // 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; + + CCam(void) { Init(); } + void Init(void); + void Process(void); + void ProcessSpecialHeightRoutines(void); + 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); + void LookBehind(void); + void LookLeft(void); + void LookRight(void); + void ClipIfPedInFrontOfPlayer(void); + void KeepTrackOfTheSpeed(const CVector &source, const CVector &target, const CVector &up, const float &alpha, const float &beta, const float &fov); + bool Using3rdPersonMouseCam(void); + bool GetWeaponFirstPersonOn(void); + bool IsTargetInWater(const CVector &CamCoors); + void AvoidWallsTopDownPed(const CVector &TargetCoors, const CVector &Offset, float *Adjuster, float *AdjusterSpeed, float yDistLimit); + void PrintMode(void); + + void Process_Debug(const CVector&, float, float, float); +#ifdef GTA_SCENE_EDIT + void Process_Editor(const CVector&, float, float, float); +#endif + void Process_ModelView(const CVector &CameraTarget, float, float, float); + void Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_FollowPedWithMouse(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); + void Process_TopDown(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar); + void Process_TopDownPed(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Rocket(const CVector &CameraTarget, float, float, float); + void Process_M16_1stPerson(const CVector &CameraTarget, float, float, float); + void Process_1stPerson(const CVector &CameraTarget, float, float, float); + void Process_1rstPersonPedOnPC(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Sniper(const CVector &CameraTarget, float, float, float); + void Process_Syphon(const CVector &CameraTarget, float, float, float); + void Process_Syphon_Crim_In_Front(const CVector &CameraTarget, float, float, float); + void Process_BehindBoat(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Fight_Cam(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_FlyBy(const CVector&, float, float, float); + void Process_WheelCam(const CVector&, float, float, float); + void Process_Fixed(const CVector &CameraTarget, float, float, float); + void Process_Player_Fallen_Water(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Circle(const CVector &CameraTarget, float, float, float); + void Process_SpecialFixedForSyphon(const CVector &CameraTarget, float, float, float); + void ProcessPedsDeadBaby(void); + bool ProcessArrestCamOne(void); + bool ProcessArrestCamTwo(void); + + /* Some of the unused PS2 cams */ + void Process_Chris_With_Binding_PlusRotation(const CVector &CameraTarget, float, float, float); + void Process_ReactionCam(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_FollowPed_WithBinding(const CVector &CameraTarget, float TargetOrientation, float, float); + // TODO: + // CCam::Process_CushyPillows_Arse + // CCam::Process_Look_At_Cars + // CCam::Process_CheesyZoom + // CCam::Process_Aiming + void Process_Bill(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar); + void Process_Im_The_Passenger_Woo_Woo(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Blood_On_The_Tracks(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Cam_Running_Side_Train(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Cam_On_Train_Roof(const CVector &CameraTarget, float TargetOrientation, float, float); + + // custom stuff + void Process_FollowPed_Rotation(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_FollowCar_SA(const CVector &CameraTarget, float TargetOrientation, float, float); +}; + +VALIDATE_SIZE(CCam, 0x1A4); + +class CCamPathSplines +{ +public: + enum {MAXPATHLENGTH=800}; + float m_arr_PathData[MAXPATHLENGTH]; + CCamPathSplines(void); +}; + +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: find better names + FADE_0, // faded in + FADE_1, // mid fade + FADE_2, // faded out + + // Direction + FADE_OUT = 0, + FADE_IN, + FADE_NONE +}; + +enum +{ + MOTION_BLUR_NONE = 0, + MOTION_BLUR_SNIPER, + MOTION_BLUR_LIGHT_SCENE, + MOTION_BLUR_SECURITY_CAM, + MOTION_BLUR_CUT_SCENE, + MOTION_BLUR_INTRO, + MOTION_BLUR_INTRO2, + MOTION_BLUR_SNIPER_ZOOM, + MOTION_BLUR_INTRO3, + MOTION_BLUR_INTRO4, +}; + +enum +{ + NONE = 0, + INTERPOLATION, + JUMP_CUT +}; + +enum +{ + CAMCONTROL_GAME, + CAMCONTROL_SCRIPT, + CAMCONTROL_OBBE +}; + +class CCamera : public CPlaceable +{ +public: + 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_bUnknown; + 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; + + 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; // some counter having to do with music + 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; +#ifdef FIX_BUGS + int32 CarZoomIndicator; +#else + float CarZoomIndicator; +#endif + float CarZoomValue; + float CarZoomValueSmooth; + + float DistanceToWater; +#ifndef PS2_CAM_TRANSITION + float FOVDuringInter; +#endif + float LODDistMultiplier; + float GenerationDistMultiplier; +#ifndef PS2_CAM_TRANSITION + 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; +#endif + 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; +#ifdef FIX_BUGS + int32 PedZoomIndicator; +#else + float PedZoomIndicator; +#endif + 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]; + CGarage *pToGarageWeAreIn; + CGarage *pToGarageWeAreInForHackAvoidFirstPerson; + CQueuedMode m_PlayerMode; + CQueuedMode PlayerWeaponMode; + CVector m_PreviousCameraPosition; + CVector m_RealPreviousCameraPosition; + CVector m_cvecAimingTargetCoors; + CVector m_vecFixedModeVector; + CVector m_vecFixedModeSource; + CVector m_vecFixedModeUpOffSet; + CVector m_vecCutSceneOffset; +#ifndef PS2_CAM_TRANSITION + 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; +#endif + CVector m_vecGameCamPos; +#ifndef PS2_CAM_TRANSITION + CVector SourceDuringInter; + CVector TargetDuringInter; + CVector UpDuringInter; +#endif + RwCamera *m_pRwCamera; + CEntity *pTargetEntity; + CCamPathSplines m_arrPathArray[MAX_NUM_OF_SPLINETYPES]; + CTrainCamNode m_arrTrainCamNode[MAX_NUM_OF_NODES]; + 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_fFractionInterToStopMoving; + float m_fFractionInterToStopCatchUp; + 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; +#ifdef FREE_CAM + static bool bFreeCam; +#endif + + // High level and misc + CCamera(void); + CCamera(float); + void Init(void); + void Process(void); + void CamControl(void); + void UpdateTargetEntity(void); + void UpdateSoundDistances(void); + void InitialiseCameraForDebugMode(void); + void CamShake(float strength, float x, float y, float z); + bool Get_Just_Switched_Status() { return m_bJust_Switched; } + + // Who's in control + void TakeControl(CEntity *target, int16 mode, int16 typeOfSwitch, int32 controller); + void TakeControlNoEntity(const CVector &position, int16 typeOfSwitch, int32 controller); + void TakeControlWithSpline(int16 typeOfSwitch); + void Restore(void); + void RestoreWithJumpCut(void); + void SetCamPositionForFixedMode(const CVector &Source, const CVector &UppOffSet); + + // Transition + void StartTransition(int16 mode); + void StartTransitionWhenNotFinishedInter(int16 mode); + void StoreValuesDuringInterPol(CVector &source, CVector &target, CVector &up, float &FOV); + + // Widescreen borders + void SetWideScreenOn(void); + void SetWideScreenOff(void); + void ProcessWideScreenOn(void); + void DrawBordersForWideScreen(void); + + // Obbe's cam + bool IsItTimeForNewcam(int32 obbeMode, int32 time); + bool TryToStartNewCamMode(int32 obbeMode); + void DontProcessObbeCinemaCamera(void); + void ProcessObbeCinemaCameraCar(void); + void ProcessObbeCinemaCameraPed(void); + + // Train + void LoadTrainCamNodes(char const *name); + void Process_Train_Camera_Control(void); + + // Script + void LoadPathSplines(int file); + void FinishCutscene(void); + float GetPositionAlongSpline(void) { return m_fPositionAlongSpline; } + uint32 GetCutSceneFinishTime(void); + void SetCamCutSceneOffSet(const CVector &pos); + void SetPercentAlongCutScene(float percent); + void SetParametersForScriptInterpolation(float stopMoving, float catchUp, int32 time); + void SetZoomValueFollowPedScript(int16 dist); + void SetZoomValueCamStringScript(int16 dist); + void SetNearClipScript(float); + + // Fading + void ProcessFade(void); + void ProcessMusicFade(void); + void Fade(float timeout, int16 direction); + void SetFadeColour(uint8 r, uint8 g, uint8 b); + bool GetFading(void); + int GetFadingDirection(void); + int GetScreenFadeStatus(void); + + // Motion blur + void RenderMotionBlur(void); + void SetMotionBlur(int r, int g, int b, int a, int type); + void SetMotionBlurAlpha(int a); + + // Player looking and aiming + int GetLookDirection(void); + bool GetLookingForwardFirstPerson(void); + bool GetLookingLRBFirstPerson(void); + void SetCameraDirectlyInFrontForFollowPed_CamOnAString(void); + void SetCameraDirectlyBehindForFollowPed_CamOnAString(void); + void SetNewPlayerWeaponMode(int16 mode, int16 minZoom, int16 maxZoom); + void ClearPlayerWeaponMode(void); + void UpdateAimingCoors(CVector const &coors); + bool Find3rdPersonCamTargetVector(float dist, CVector pos, CVector &source, CVector &target); + float Find3rdPersonQuickAimPitch(void); + + // Physical camera + void SetRwCamera(RwCamera *cam); + const CMatrix& GetCameraMatrix(void) { return m_cameraMatrix; } + CVector &GetGameCamPosition(void) { return m_vecGameCamPos; } + void CalculateDerivedValues(void); + bool IsPointVisible(const CVector ¢er, const CMatrix *mat); + bool IsSphereVisible(const CVector ¢er, float radius, Const CMatrix *mat); + bool IsSphereVisible(const CVector ¢er, float radius); + bool IsBoxVisible(CVUVECTOR *box, const CMatrix *mat); +}; + +VALIDATE_SIZE(CCamera, 0xE9D8); + +extern CCamera TheCamera; + +void CamShakeNoPos(CCamera*, float); +void MakeAngleLessThan180(float &Angle); +void WellBufferMe(float Target, float *CurrentValue, float *CurrentSpeed, float MaxSpeed, float Acceleration, bool IsAngle); diff --git a/src/core/CdStream.cpp b/src/core/CdStream.cpp new file mode 100644 index 0000000..977f16c --- /dev/null +++ b/src/core/CdStream.cpp @@ -0,0 +1,538 @@ +#ifdef _WIN32 +#define WITHWINDOWS +#include "common.h" + +#include "CdStream.h" +#include "rwcore.h" +#include "RwHelper.h" +#include "MemoryMgr.h" + +struct CdReadInfo +{ + uint32 nSectorOffset; + uint32 nSectorsToRead; + void *pBuffer; + char field_C; + bool bLocked; + bool bReading; + int32 nStatus; + HANDLE pDoneSemaphore; // used for CdStreamSync + 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; // released when we have new thing to read(so channel is set) +DWORD _gCdStreamThreadId; + +CdReadInfo *gpReadInfo; +Queue gChannelRequestQ; + +int32 lastPosnRead; + +BOOL _gbCdStreamOverlapped; +BOOL _gbCdStreamAsync; +DWORD _gdwCdStreamFlags; + +DWORD WINAPI CdStreamThread(LPVOID lpThreadParameter); + +void +CdStreamInitThread(void) +{ + SetLastError(0); + + if ( gNumChannels > 0 ) + { + for ( int32 i = 0; i < gNumChannels; i++ ) + { + gpReadInfo[i].pDoneSemaphore = CreateSemaphore(nil, 0, 2, nil); + + if ( gpReadInfo[i].pDoneSemaphore == nil ) + { + printf("%s: failed to create sync semaphore\n", "cdvd_stream"); + 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 ); + +#ifdef FIX_BUGS + gCdStreamSema = CreateSemaphore(nil, 0, 5, nil); +#else + gCdStreamSema = CreateSemaphore(nil, 0, 5, "CdStream"); +#endif + + if ( gCdStreamSema == nil ) + { + printf("%s: failed to create stream semaphore\n", "cdvd_stream"); + ASSERT(0); + return; + } + + _gCdStreamThread = CreateThread(nil, 64*1024/*64KB*/, CdStreamThread, nil, CREATE_SUSPENDED, &_gCdStreamThreadId); + + if ( _gCdStreamThread == nil ) + { + printf("%s: failed to create streaming thread\n", "cdvd_stream"); + 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; + +#ifndef FIX_BUGS // this just slows down streaming + if ( BytesPerSector <= CDSTREAM_SECTOR_SIZE ) + { + _gdwCdStreamFlags |= FILE_FLAG_NO_BUFFERING; + debug("Using no buffered loading for streaming\n"); + } +#endif + + _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 ); + + debug("%s: read info %p\n", "cdvd_stream", 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].pDoneSemaphore); + } + + 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->bReading ) + 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; + } + +#ifdef BIG_IMG + LARGE_INTEGER liDistanceToMove; + liDistanceToMove.QuadPart = _GET_OFFSET(offset); + liDistanceToMove.QuadPart *= CDSTREAM_SECTOR_SIZE; + SetFilePointerEx(hImage, liDistanceToMove, nil, FILE_BEGIN); +#else + SetFilePointer(hImage, _GET_OFFSET(offset) * CDSTREAM_SECTOR_SIZE, nil, FILE_BEGIN); +#endif + + 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->bReading ) + 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; +} + +// wait for channel to finish reading +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->pDoneSemaphore != nil ); + + // Deadlock fix 1 +#ifdef FIX_BUGS + // This is while loop on Posix streamer, for spurious wakeups + if (pChannel->bLocked && pChannel->nSectorsToRead != 0){ + WaitForSingleObject(pChannel->pDoneSemaphore, INFINITE); + } + pChannel->bLocked = false; +#else + WaitForSingleObject(pChannel->pDoneSemaphore, INFINITE); +#endif + } + + pChannel->bReading = false; + + return pChannel->nStatus; + } + + DWORD NumberOfBytesTransferred; + + if ( _gbCdStreamOverlapped && pChannel->hFile ) + { + ASSERT(pChannel->hFile != nil ); + // Beware: This is blocking call (because of last parameter) + 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->bReading = 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; + } + // Beware: This is blocking call (because of last parameter) + 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->pDoneSemaphore != nil ); + // Deadlock fix 2 +#ifdef FIX_BUGS + pChannel->bLocked = 0; +#endif + ReleaseSemaphore(pChannel->pDoneSemaphore, 1, NULL); + } + + pChannel->bReading = 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; +} +#endif diff --git a/src/core/CdStream.h b/src/core/CdStream.h new file mode 100644 index 0000000..516cef4 --- /dev/null +++ b/src/core/CdStream.h @@ -0,0 +1,48 @@ +#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); +bool CdStreamAddImage(char const *path); +char *CdStreamGetImageName(int32 cd); +void CdStreamRemoveImages(void); +int32 CdStreamGetNumImages(void); + +#ifdef FLUSHABLE_STREAMING +extern bool flushStream[MAX_CDCHANNELS]; +#endif diff --git a/src/core/CdStreamPosix.cpp b/src/core/CdStreamPosix.cpp new file mode 100644 index 0000000..bc9129e --- /dev/null +++ b/src/core/CdStreamPosix.cpp @@ -0,0 +1,604 @@ +#ifndef _WIN32 +#include "common.h" +#include "crossplatform.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#endif + +#include "CdStream.h" +#include "rwcore.h" +#include "MemoryMgr.h" + +#define CDDEBUG(f, ...) debug ("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) +#define CDTRACE(f, ...) printf("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) + +#ifdef FLUSHABLE_STREAMING +bool flushStream[MAX_CDCHANNELS]; +#endif + +#ifdef USE_UNNAMED_SEM + +#define RE3_SEM_OPEN(name, ...) re3_sem_open() +sem_t* +re3_sem_open(void) +{ + sem_t* sem = (sem_t*)malloc(sizeof(sem_t)); + if (sem_init(sem, 0, 1) == -1) { + sem = SEM_FAILED; + } + + return sem; +} + +#define RE3_SEM_CLOSE(sem, format, ...) re3_sem_close(sem) +void +re3_sem_close(sem_t* sem) +{ + sem_destroy(sem); + free(sem); +} + +#else + +#define RE3_SEM_OPEN re3_sem_open +sem_t* +re3_sem_open(const char* format, ...) +{ + char semName[21]; + va_list va; + va_start(va, format); + vsprintf(semName, format, va); + + return sem_open(semName, O_CREAT, 0644, 1); +} + +#define RE3_SEM_CLOSE re3_sem_close +void +re3_sem_close(sem_t* sem, const char* format, ...) +{ + sem_close(sem); + + char semName[21]; + va_list va; + va_start(va, format); + vsprintf(semName, format, va); + + sem_unlink(semName); +} + +#endif + +struct CdReadInfo +{ + uint32 nSectorOffset; + uint32 nSectorsToRead; + void *pBuffer; + bool bLocked; + bool bReading; + int32 nStatus; +#ifdef ONE_THREAD_PER_CHANNEL + int8 nThreadStatus; // 0: created 1:priority set up 2:abort now + pthread_t pChannelThread; + sem_t *pStartSemaphore; +#endif + sem_t *pDoneSemaphore; // used for CdStreamSync + int32 hFile; +}; + +char gCdImageNames[MAX_CDIMAGES+1][64]; +int32 gNumImages; +int32 gNumChannels; + +int32 gImgFiles[MAX_CDIMAGES]; // -1: error 0:unused otherwise: fd +char *gImgNames[MAX_CDIMAGES]; + +#ifndef ONE_THREAD_PER_CHANNEL +pthread_t _gCdStreamThread; +sem_t *gCdStreamSema; // released when we have new thing to read(so channel is set) +int8 gCdStreamThreadStatus; // 0: created 1:priority set up 2:abort now +Queue gChannelRequestQ; +bool _gbCdStreamOverlapped; +#endif + +CdReadInfo *gpReadInfo; + +int32 lastPosnRead; + +int _gdwCdStreamFlags; + +void *CdStreamThread(void* channelId); + +void +CdStreamInitThread(void) +{ + int status; +#ifndef ONE_THREAD_PER_CHANNEL + gChannelRequestQ.items = (int32 *)calloc(gNumChannels + 1, sizeof(int32)); + gChannelRequestQ.head = 0; + gChannelRequestQ.tail = 0; + gChannelRequestQ.size = gNumChannels + 1; + ASSERT(gChannelRequestQ.items != nil ); + gCdStreamSema = RE3_SEM_OPEN("/semaphore_cd_stream"); + + + if (gCdStreamSema == SEM_FAILED) { + CDTRACE("failed to create stream semaphore"); + ASSERT(0); + return; + } +#endif + + if ( gNumChannels > 0 ) + { + for ( int32 i = 0; i < gNumChannels; i++ ) + { + gpReadInfo[i].pDoneSemaphore = RE3_SEM_OPEN("/semaphore_done%d", i); + + if (gpReadInfo[i].pDoneSemaphore == SEM_FAILED) + { + CDTRACE("failed to create sync semaphore"); + ASSERT(0); + return; + } + +#ifdef ONE_THREAD_PER_CHANNEL + gpReadInfo[i].pStartSemaphore = RE3_SEM_OPEN("/semaphore_start%d", i); + + if (gpReadInfo[i].pStartSemaphore == SEM_FAILED) + { + CDTRACE("failed to create start semaphore"); + ASSERT(0); + return; + } + gpReadInfo[i].nThreadStatus = 0; + int *channelI = (int*)malloc(sizeof(int)); + *channelI = i; + status = pthread_create(&gpReadInfo[i].pChannelThread, NULL, CdStreamThread, (void*)channelI); + + if (status == -1) + { + CDTRACE("failed to create sync thread"); + ASSERT(0); + return; + } +#endif + } + } + +#ifndef ONE_THREAD_PER_CHANNEL + debug("Using one streaming thread for all channels\n"); + gCdStreamThreadStatus = 0; + status = pthread_create(&_gCdStreamThread, NULL, CdStreamThread, nil); + + if (status == -1) + { + CDTRACE("failed to create sync thread"); + ASSERT(0); + return; + } +#else + debug("Using separate streaming threads for each channel\n"); +#endif +} + +void +CdStreamInit(int32 numChannels) +{ + struct statvfs fsInfo; + + if((statvfs("models/gta3.img", &fsInfo)) < 0) + { + CDTRACE("can't get filesystem info"); + ASSERT(0); + return; + } +#ifdef __linux__ + _gdwCdStreamFlags = O_RDONLY | O_NOATIME; +#else + _gdwCdStreamFlags = O_RDONLY; +#endif + // People say it's slower +/* + if ( fsInfo.f_bsize <= CDSTREAM_SECTOR_SIZE ) + { + _gdwCdStreamFlags |= O_DIRECT; + debug("Using no buffered loading for streaming\n"); + } +*/ + void *pBuffer = (void *)RwMallocAlign(CDSTREAM_SECTOR_SIZE, (RwUInt32)fsInfo.f_bsize); + ASSERT( pBuffer != nil ); + + gNumImages = 0; + + gNumChannels = numChannels; + ASSERT( gNumChannels != 0 ); + + gpReadInfo = (CdReadInfo *)calloc(numChannels, sizeof(CdReadInfo)); + ASSERT( gpReadInfo != nil ); + + CDDEBUG("read info %p", gpReadInfo); + + CdStreamInitThread(); + + ASSERT( pBuffer != nil ); + RwFreeAlign(pBuffer); +} + +uint32 +GetGTA3ImgSize(void) +{ + ASSERT( gImgFiles[0] > 0 ); + struct stat statbuf; + + char path[PATH_MAX]; + realpath(gImgNames[0], path); + if (stat(path, &statbuf) == -1) { + // Try case-insensitivity + char* real = casepath(gImgNames[0], false); + if (real) + { + realpath(real, path); + free(real); + if (stat(path, &statbuf) != -1) + goto ok; + } + + CDTRACE("can't get size of gta3.img"); + ASSERT(0); + return 0; + } + ok: + return (uint32)statbuf.st_size; +} + +void +CdStreamShutdown(void) +{ + // Destroying semaphores and free(gpReadInfo) will be done at threads +#ifndef ONE_THREAD_PER_CHANNEL + gCdStreamThreadStatus = 2; + sem_post(gCdStreamSema); + pthread_join(_gCdStreamThread, nil); +#else + for ( int32 i = 0; i < gNumChannels; i++ ) { + gpReadInfo[i].nThreadStatus = 2; + sem_post(gpReadInfo[i].pStartSemaphore); + pthread_join(gpReadInfo[i].pChannelThread, nil); + } +#endif +} + + +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 ); + int32 hImage = gImgFiles[_GET_INDEX(offset)]; + ASSERT( hImage > 0 ); + + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + if ( pChannel->nSectorsToRead != 0 || pChannel->bReading ) { + if (pChannel->hFile == hImage - 1 && pChannel->nSectorOffset == _GET_OFFSET(offset) && pChannel->nSectorsToRead >= size) + return STREAM_SUCCESS; +#ifdef FLUSHABLE_STREAMING + flushStream[channel] = 1; + CdStreamSync(channel); +#else + return STREAM_NONE; +#endif + } + + pChannel->hFile = hImage - 1; + pChannel->nStatus = STREAM_NONE; + pChannel->nSectorOffset = _GET_OFFSET(offset); + pChannel->nSectorsToRead = size; + pChannel->pBuffer = buffer; + pChannel->bLocked = 0; + +#ifndef ONE_THREAD_PER_CHANNEL + AddToQueue(&gChannelRequestQ, channel); + if ( sem_post(gCdStreamSema) != 0 ) + printf("Signal Sema Error\n"); +#else + if ( sem_post(pChannel->pStartSemaphore) != 0 ) + printf("Signal Sema Error\n"); +#endif + + return STREAM_SUCCESS; +} + +int32 +CdStreamGetStatus(int32 channel) +{ + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + +#ifdef ONE_THREAD_PER_CHANNEL + if (pChannel->nThreadStatus == 2) + return STREAM_NONE; +#else + if (gCdStreamThreadStatus == 2) + return STREAM_NONE; +#endif + + if ( pChannel->bReading ) + 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; +} + +int32 +CdStreamGetLastPosn(void) +{ + return lastPosnRead; +} + +// wait for channel to finish reading +int32 +CdStreamSync(int32 channel) +{ + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + +#ifdef FLUSHABLE_STREAMING + if (flushStream[channel]) { + pChannel->nSectorsToRead = 0; +#ifdef ONE_THREAD_PER_CHANNEL + pthread_kill(pChannel->pChannelThread, SIGUSR1); + if (pChannel->bReading) { + pChannel->bLocked = true; +#else + if (pChannel->bReading) { + pChannel->bLocked = true; + pthread_kill(_gCdStreamThread, SIGUSR1); +#endif + while (pChannel->bLocked) + sem_wait(pChannel->pDoneSemaphore); + } + pChannel->bReading = false; + flushStream[channel] = false; + return STREAM_NONE; + } +#endif + + if ( pChannel->nSectorsToRead != 0 ) + { + pChannel->bLocked = true; + while (pChannel->bLocked && pChannel->nSectorsToRead != 0){ + sem_wait(pChannel->pDoneSemaphore); + } + pChannel->bLocked = false; + } + + pChannel->bReading = false; + + return pChannel->nStatus; +} + +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; +} + +void *CdStreamThread(void *param) +{ + debug("Created cdstream thread\n"); + +#ifndef ONE_THREAD_PER_CHANNEL + while (gCdStreamThreadStatus != 2) { + sem_wait(gCdStreamSema); + + int32 channel = GetFirstInQueue(&gChannelRequestQ); + + // spurious wakeup + if (channel == -1) + continue; +#else + int channel = *((int*)param); + while (gpReadInfo[channel].nThreadStatus != 2){ + sem_wait(gpReadInfo[channel].pStartSemaphore); +#endif + + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + // spurious wakeup or we sent interrupt signal for flushing + if(pChannel->nSectorsToRead == 0) + continue; + + pChannel->bReading = true; + + // Not standard POSIX :shrug: +#ifdef __linux__ +#ifdef ONE_THREAD_PER_CHANNEL + if (gpReadInfo[channel].nThreadStatus == 0){ + gpReadInfo[channel].nThreadStatus = 1; +#else + if (gCdStreamThreadStatus == 0){ + gCdStreamThreadStatus = 1; +#endif + pid_t tid = syscall(SYS_gettid); + int ret = setpriority(PRIO_PROCESS, tid, getpriority(PRIO_PROCESS, getpid()) + 1); + } +#endif + if ( pChannel->nStatus == STREAM_NONE ) + { + ASSERT(pChannel->hFile >= 0); + ASSERT(pChannel->pBuffer != nil ); + + lseek(pChannel->hFile, (size_t)pChannel->nSectorOffset * (size_t)CDSTREAM_SECTOR_SIZE, SEEK_SET); + if (read(pChannel->hFile, pChannel->pBuffer, pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE) == -1) { + // pChannel->nSectorsToRead == 0 at this point means we wanted to flush channel + // STREAM_WAITING is a little hack to make CStreaming not process this data + pChannel->nStatus = pChannel->nSectorsToRead == 0 ? STREAM_WAITING : STREAM_ERROR; + } else { + pChannel->nStatus = STREAM_NONE; + } + } + +#ifndef ONE_THREAD_PER_CHANNEL + RemoveFirstInQueue(&gChannelRequestQ); +#endif + + pChannel->nSectorsToRead = 0; + if ( pChannel->bLocked ) + { + pChannel->bLocked = 0; + sem_post(pChannel->pDoneSemaphore); + } + pChannel->bReading = false; + } + char semName[20]; +#ifndef ONE_THREAD_PER_CHANNEL + for ( int32 i = 0; i < gNumChannels; i++ ) + { + RE3_SEM_CLOSE(gpReadInfo[i].pDoneSemaphore, "/semaphore_done%d", i); + } + RE3_SEM_CLOSE(gCdStreamSema, "/semaphore_cd_stream"); + free(gChannelRequestQ.items); +#else + RE3_SEM_CLOSE(gpReadInfo[channel].pStartSemaphore, "/semaphore_start%d", channel); + + RE3_SEM_CLOSE(gpReadInfo[channel].pDoneSemaphore, "/semaphore_done%d", channel); +#endif + if (gpReadInfo) + free(gpReadInfo); + gpReadInfo = nil; + pthread_exit(nil); +} + +bool +CdStreamAddImage(char const *path) +{ + ASSERT(path != nil); + ASSERT(gNumImages < MAX_CDIMAGES); + + gImgFiles[gNumImages] = open(path, _gdwCdStreamFlags); + + // Fix case sensitivity and backslashes. + if (gImgFiles[gNumImages] == -1) { + char* real = casepath(path, false); + if (real) + { + gImgFiles[gNumImages] = open(real, _gdwCdStreamFlags); + free(real); + } + } + + if ( gImgFiles[gNumImages] == -1 ) { + assert(false); + return false; + } + + gImgNames[gNumImages] = strdup(path); + gImgFiles[gNumImages]++; // because -1: error 0: not used + + strcpy(gCdImageNames[gNumImages], path); + + gNumImages++; + + return true; +} + +char * +CdStreamGetImageName(int32 cd) +{ + ASSERT(cd < MAX_CDIMAGES); + if ( gImgFiles[cd] > 0) + return gCdImageNames[cd]; + + return nil; +} + +void +CdStreamRemoveImages(void) +{ + for ( int32 i = 0; i < gNumChannels; i++ ) { +#ifdef FLUSHABLE_STREAMING + flushStream[i] = 1; +#endif + CdStreamSync(i); + } + + for ( int32 i = 0; i < gNumImages; i++ ) + { + close(gImgFiles[i] - 1); + free(gImgNames[i]); + gImgFiles[i] = 0; + } + + gNumImages = 0; +} + +int32 +CdStreamGetNumImages(void) +{ + return gNumImages; +} +#endif diff --git a/src/core/Clock.cpp b/src/core/Clock.cpp new file mode 100644 index 0000000..e4b908e --- /dev/null +++ b/src/core/Clock.cpp @@ -0,0 +1,117 @@ +#include "common.h" + +#include "Timer.h" +#include "Pad.h" +#include "Clock.h" +#include "Stats.h" + +_TODO("gbFastTime"); +bool gbFastTime; + +uint8 CClock::ms_nGameClockHours; +uint8 CClock::ms_nGameClockMinutes; +uint16 CClock::ms_nGameClockSeconds; +uint8 CClock::ms_Stored_nGameClockHours; +uint8 CClock::ms_Stored_nGameClockMinutes; +uint16 CClock::ms_Stored_nGameClockSeconds; +uint32 CClock::ms_nMillisecondsPerGameMinute; +uint32 CClock::ms_nLastClockTick; +bool CClock::ms_bClockHasBeenStored; + +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; +} diff --git a/src/core/Clock.h b/src/core/Clock.h new file mode 100644 index 0000000..a611cd5 --- /dev/null +++ b/src/core/Clock.h @@ -0,0 +1,31 @@ +#pragma once + +class CClock +{ +public: + 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 uint32 ms_nLastClockTick; + static bool ms_bClockHasBeenStored; + + 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/ControllerConfig.cpp b/src/core/ControllerConfig.cpp new file mode 100644 index 0000000..8775792 --- /dev/null +++ b/src/core/ControllerConfig.cpp @@ -0,0 +1,2884 @@ +#define WITHDINPUT +#include "common.h" +#include "platform.h" +#include "crossplatform.h" +#include "ControllerConfig.h" +#include "Pad.h" +#include "FileMgr.h" +#include "Text.h" +#include "Font.h" +#include "Messages.h" +#include "Frontend.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Vehicle.h" +#include "World.h" +#include "ModelIndices.h" +#include "Camera.h" +#include "GenericGameStorage.h" + +CControllerConfigManager ControlsManager; + +CControllerConfigManager::CControllerConfigManager() +{ + m_bFirstCapture = false; + m_bMouseAssociated = false; + + MakeControllerActionsBlank(); + InitDefaultControlConfiguration(); + InitialiseControllerActionNameArray(); +} + +void CControllerConfigManager::MakeControllerActionsBlank() +{ +#ifdef LOAD_INI_SETTINGS + ms_padButtonsInited = 0; +#endif + for (int32 i = 0; i < MAX_CONTROLLERTYPES; i++) + { + for (int32 j = 0; j < MAX_CONTROLLERACTIONS; j++) + { + ClearSettingsAssociatedWithAction((e_ControllerAction)j, (eControllerType)i); + } + } +} + +#ifdef RW_GL3 +int MapIdToButtonId(int mapId) { + switch (mapId) { + case GLFW_GAMEPAD_BUTTON_A: // Cross + return 2; + case GLFW_GAMEPAD_BUTTON_B: // Circle + return 1; + case GLFW_GAMEPAD_BUTTON_X: // Square + return 3; + case GLFW_GAMEPAD_BUTTON_Y: // Triangle + return 4; + case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: + return 7; + case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: + return 8; + case GLFW_GAMEPAD_BUTTON_BACK: + return 9; + case GLFW_GAMEPAD_BUTTON_START: + return 12; + case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: + return 10; + case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: + return 11; + case GLFW_GAMEPAD_BUTTON_DPAD_UP: + return 13; + case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: + return 14; + case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: + return 15; + case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: + return 16; + // GLFW sends those as axes, so I added them here manually. + case 15: // Left trigger + return 5; + case 16: // Right trigger + return 6; + default: + return 0; + } +} +#endif + +int32 CControllerConfigManager::GetJoyButtonJustDown() +{ +#ifdef __DINPUT_INCLUDED__ +#ifdef FIX_BUGS + for (int32 i = 0; i < MAX_BUTTONS; i++) +#else + for (int32 i = 0; i < JOY_BUTTONS; i++) +#endif + { + if (m_NewState.rgbButtons[i] & 0x80 && !(m_OldState.rgbButtons[i] & 0x80)) + return i + 1; + } +#elif defined RW_GL3 + if (m_NewState.isGamepad) { + for (int32 i = 0; i < MAX_BUTTONS; i++) { + if (m_NewState.mappedButtons[i] && !(m_OldState.mappedButtons[i])) + return MapIdToButtonId(i); + } + } else { + for (int32 i = 0; i < Min(m_NewState.numButtons, MAX_BUTTONS); i++) { + if (m_NewState.buttons[i] && !(m_OldState.buttons[i])) + return i + 1; + } + } +#endif + return 0; +} + +void CControllerConfigManager::SaveSettings(int32 file) +{ + if (file) + { + for (int32 i = 0; i < MAX_CONTROLLERTYPES; i++) + { + for (int32 j = 0; j < MAX_CONTROLLERACTIONS; j++) + { + CFileMgr::Write(file, (char *)&ControlsManager.m_aSettings[j][i], sizeof(tControllerConfigBind)); + } + } + } +} + +void CControllerConfigManager::LoadSettings(int32 file) +{ + bool bValid = true; + +#ifdef BIND_VEHICLE_FIREWEAPON + bool skipVehicleFireWeapon = false; +#endif + + if (file) + { + char buff[29]; + CFileMgr::Read(file, buff, sizeof(buff)); + + if (!strncmp(buff, TopLineEmptyFile, sizeof(TopLineEmptyFile)-1)) + bValid = false; + else { + CFileMgr::Seek(file, 0, 0); + +#ifdef BIND_VEHICLE_FIREWEAPON + // HACK! + // All of this is hacky as fuck. + // We are checking the file size to read the .set file correctly. + // But because .set file is opened in text mode we have to read + // the WHOLE file to get the size we should be working with. + // Joy, ain't it? + char tempBuf[0x1000]; + size_t fileSize = 0, blockSize; + do + { + blockSize = CFileMgr::Read(file, tempBuf, sizeof(tempBuf)); + fileSize += blockSize; + } while (blockSize == sizeof(tempBuf)); + + CFileMgr::Seek(file, 0, 0); + + if (fileSize == 0x671) + skipVehicleFireWeapon = true; +#endif + } + } + + if (bValid) + { + ControlsManager.MakeControllerActionsBlank(); + +#ifdef BIND_VEHICLE_FIREWEAPON + // Set the default settings of VEHICLE_FIREWEAPON + if (skipVehicleFireWeapon) { + SetControllerKeyAssociatedWithAction(VEHICLE_FIREWEAPON, rsPADINS, KEYBOARD); + SetControllerKeyAssociatedWithAction(VEHICLE_FIREWEAPON, rsLCTRL, OPTIONAL_EXTRA); + if (m_bMouseAssociated) + SetMouseButtonAssociatedWithAction(VEHICLE_FIREWEAPON, 1); + } +#endif + + for (int32 i = 0; i < MAX_CONTROLLERTYPES; i++) + { + for (int32 j = 0; j < MAX_CONTROLLERACTIONS; j++) + { +#ifdef BIND_VEHICLE_FIREWEAPON + // Skip file read + if (skipVehicleFireWeapon && j == VEHICLE_FIREWEAPON) + continue; +#endif + CFileMgr::Read(file, (char *)&ControlsManager.m_aSettings[j][i], sizeof(tControllerConfigBind)); + } + } + } +} + +void CControllerConfigManager::InitDefaultControlConfiguration() +{ + SetControllerKeyAssociatedWithAction (VEHICLE_LOOKLEFT, rsPADEND, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_LOOKLEFT, 'Q', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (VEHICLE_LOOKRIGHT, rsPADDOWN, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_LOOKRIGHT, 'E', OPTIONAL_EXTRA); + + if ( _dwOperatingSystemVersion == OS_WIN98 ) + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsSHIFT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + else + { + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsLSHIFT, OPTIONAL_EXTRA); + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsRSHIFT, KEYBOARD); + } + + SetControllerKeyAssociatedWithAction (VEHICLE_HANDBRAKE, rsRCTRL, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_HANDBRAKE, ' ', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (VEHICLE_ENTER_EXIT, rsENTER, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_ENTER_EXIT, 'F', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (VEHICLE_ACCELERATE, rsUP, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_ACCELERATE, 'W', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (VEHICLE_CHANGE_RADIO_STATION, rsINS, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_CHANGE_RADIO_STATION, 'R', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (VEHICLE_BRAKE, rsDOWN, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_BRAKE, 'S', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (TOGGLE_SUBMISSIONS, rsPLUS, KEYBOARD); + SetControllerKeyAssociatedWithAction (TOGGLE_SUBMISSIONS, rsCAPSLK, OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (GO_LEFT, rsLEFT, KEYBOARD); + SetControllerKeyAssociatedWithAction (GO_LEFT, 'A', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (GO_RIGHT, rsRIGHT, KEYBOARD); + SetControllerKeyAssociatedWithAction (GO_RIGHT, 'D', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (GO_FORWARD, rsUP, KEYBOARD); + SetControllerKeyAssociatedWithAction (GO_FORWARD, 'W', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (GO_BACK, rsDOWN, KEYBOARD); + SetControllerKeyAssociatedWithAction (GO_BACK, 'S', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (PED_LOOKBEHIND, rsPADEND, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_LOOKBEHIND, rsCAPSLK, OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (PED_FIREWEAPON, rsPADINS, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_FIREWEAPON, rsLCTRL, OPTIONAL_EXTRA); +#ifdef BIND_VEHICLE_FIREWEAPON + SetControllerKeyAssociatedWithAction (VEHICLE_FIREWEAPON, rsPADINS, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_FIREWEAPON, rsLCTRL, OPTIONAL_EXTRA); +#endif + SetControllerKeyAssociatedWithAction (PED_CYCLE_WEAPON_LEFT, rsPADDEL, KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_CYCLE_WEAPON_RIGHT, rsPADENTER, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + + SetControllerKeyAssociatedWithAction (PED_LOCK_TARGET, rsDEL, KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_JUMPING, rsRCTRL, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_JUMPING, ' ', OPTIONAL_EXTRA); + + if ( _dwOperatingSystemVersion == OS_WIN98 ) + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsSHIFT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + else + { + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsLSHIFT, OPTIONAL_EXTRA); +#ifndef FIX_BUGS + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsRSHIFT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD +#else + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsRSHIFT, KEYBOARD); +#endif + } + + SetControllerKeyAssociatedWithAction (PED_CYCLE_TARGET_LEFT, '[', KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_CYCLE_TARGET_RIGHT, ']', OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + + SetControllerKeyAssociatedWithAction (PED_CENTER_CAMERA_BEHIND_PLAYER, '#', KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_IN, rsPGUP, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_IN, 'Z', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_OUT, rsPGDN, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_OUT, 'X', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_LEFT, rsPADLEFT, KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_RIGHT, rsPADRIGHT, KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_UP, rsPADUP, KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_DOWN, rsPAD5, KEYBOARD); + + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETLEFT, rsPADLEFT, KEYBOARD); + + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETRIGHT, rsPAD5, KEYBOARD); + + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETUP, rsPADPGUP, KEYBOARD); + + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETDOWN, rsPADRIGHT, KEYBOARD); + + SetControllerKeyAssociatedWithAction (CAMERA_CHANGE_VIEW_ALL_SITUATIONS, rsHOME, KEYBOARD); + SetControllerKeyAssociatedWithAction (CAMERA_CHANGE_VIEW_ALL_SITUATIONS, 'C', OPTIONAL_EXTRA); + + for (int32 i = 0; i < MAX_SIMS; i++) + { + m_aSimCheckers[i][KEYBOARD] = false; + m_aSimCheckers[i][OPTIONAL_EXTRA] = false; + m_aSimCheckers[i][MOUSE] = false; + m_aSimCheckers[i][JOYSTICK] = false; + } +} + +void CControllerConfigManager::InitDefaultControlConfigMouse(CMouseControllerState const &availableButtons) +{ + if (availableButtons.LMB) + { + m_bMouseAssociated = true; + SetMouseButtonAssociatedWithAction(PED_FIREWEAPON, 1); +#ifdef BIND_VEHICLE_FIREWEAPON + SetMouseButtonAssociatedWithAction(VEHICLE_FIREWEAPON, 1); +#endif + } + + if (availableButtons.RMB) + { + SetMouseButtonAssociatedWithAction(PED_LOCK_TARGET, 3); + + SetMouseButtonAssociatedWithAction(VEHICLE_HANDBRAKE, 3); + } + + if (availableButtons.MMB) + { + SetMouseButtonAssociatedWithAction(VEHICLE_LOOKBEHIND, 2); + + SetMouseButtonAssociatedWithAction(PED_LOOKBEHIND, 2); + } + + if (availableButtons.WHEELUP || availableButtons.WHEELDN) + { + SetMouseButtonAssociatedWithAction(PED_CYCLE_WEAPON_LEFT, 4); + + SetMouseButtonAssociatedWithAction(PED_CYCLE_WEAPON_RIGHT, 5); + + SetMouseButtonAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, 4); + } +} + +#ifdef LOAD_INI_SETTINGS +uint32 CControllerConfigManager::ms_padButtonsInited = 0; +#endif + +void CControllerConfigManager::InitDefaultControlConfigJoyPad(uint32 buttons) +{ +#ifdef XINPUT + // No manual bindings for you, honey. + return; +#endif + + m_bFirstCapture = true; + + uint32 btn = buttons; + if (buttons > 16) + btn = 16; + +#ifdef LOAD_INI_SETTINGS + uint32 buttonMin = ms_padButtonsInited; + if (buttonMin >= btn) + return; + + ms_padButtonsInited = btn; + + #define IF_BTN_IN_RANGE(n) \ + case n: \ + if (n <= buttonMin) \ + return; +#else + #define IF_BTN_IN_RANGE(n) \ + case n: +#endif + + // Now we use SDL Game Controller DB +#if defined RW_D3D9 || defined RWLIBS + if ( AllValidWinJoys.m_aJoys[JOYSTICK1].m_nVendorID == 0x3427 + && AllValidWinJoys.m_aJoys[JOYSTICK1].m_nProductID == 0x1190) +#else + if (0) +#endif + { + //GIC USB Joystick, PS2 Gamepad ? + + switch (btn) + { + IF_BTN_IN_RANGE(16) + SetControllerKeyAssociatedWithAction(GO_LEFT, 16, JOYSTICK); + IF_BTN_IN_RANGE(15) + SetControllerKeyAssociatedWithAction(GO_BACK, 15, JOYSTICK); + IF_BTN_IN_RANGE(14) + SetControllerKeyAssociatedWithAction(GO_RIGHT, 14, JOYSTICK); + IF_BTN_IN_RANGE(13) + SetControllerKeyAssociatedWithAction(GO_FORWARD, 13, JOYSTICK); + IF_BTN_IN_RANGE(12) + IF_BTN_IN_RANGE(11) + SetControllerKeyAssociatedWithAction(PED_LOOKBEHIND, 11, JOYSTICK); + SetControllerKeyAssociatedWithAction(TOGGLE_SUBMISSIONS, 11, JOYSTICK); + IF_BTN_IN_RANGE(10) + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, 10, JOYSTICK); + IF_BTN_IN_RANGE(9) + SetControllerKeyAssociatedWithAction(CAMERA_CHANGE_VIEW_ALL_SITUATIONS, 9, JOYSTICK); + IF_BTN_IN_RANGE(8) + SetControllerKeyAssociatedWithAction(VEHICLE_HANDBRAKE, 8, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_LOCK_TARGET, 8, JOYSTICK); + IF_BTN_IN_RANGE(7) + SetControllerKeyAssociatedWithAction(PED_CENTER_CAMERA_BEHIND_PLAYER, 7, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, 7, JOYSTICK); + IF_BTN_IN_RANGE(6) + SetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_RIGHT, 6, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_LOOKRIGHT, 6, JOYSTICK); + IF_BTN_IN_RANGE(5) + SetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_LEFT, 5, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_LOOKLEFT, 5, JOYSTICK); + /*******************************************************************************************/ + IF_BTN_IN_RANGE(4) + SetControllerKeyAssociatedWithAction(VEHICLE_BRAKE, 4, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_JUMPING, 4, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_IN, 4, JOYSTICK); + IF_BTN_IN_RANGE(3) + SetControllerKeyAssociatedWithAction(VEHICLE_ACCELERATE, 3, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SPRINT, 3, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_OUT, 3, JOYSTICK); + IF_BTN_IN_RANGE(2) + SetControllerKeyAssociatedWithAction(PED_FIREWEAPON, 2, JOYSTICK); +#ifdef BIND_VEHICLE_FIREWEAPON + SetControllerKeyAssociatedWithAction(VEHICLE_FIREWEAPON, 2, JOYSTICK); +#endif + IF_BTN_IN_RANGE(1) + SetControllerKeyAssociatedWithAction(VEHICLE_ENTER_EXIT, 1, JOYSTICK); + /*******************************************************************************************/ + } + } + else + { + switch (btn) + { + IF_BTN_IN_RANGE(16) + SetControllerKeyAssociatedWithAction(GO_LEFT, 16, JOYSTICK); + IF_BTN_IN_RANGE(15) + SetControllerKeyAssociatedWithAction(GO_BACK, 15, JOYSTICK); + IF_BTN_IN_RANGE(14) + SetControllerKeyAssociatedWithAction(GO_RIGHT, 14, JOYSTICK); + IF_BTN_IN_RANGE(13) + SetControllerKeyAssociatedWithAction(GO_FORWARD, 13, JOYSTICK); + IF_BTN_IN_RANGE(12) + IF_BTN_IN_RANGE(11) + SetControllerKeyAssociatedWithAction(PED_LOOKBEHIND, 11, JOYSTICK); + SetControllerKeyAssociatedWithAction(TOGGLE_SUBMISSIONS, 11, JOYSTICK); + IF_BTN_IN_RANGE(10) + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, 10, JOYSTICK); + IF_BTN_IN_RANGE(9) + SetControllerKeyAssociatedWithAction(CAMERA_CHANGE_VIEW_ALL_SITUATIONS, 9, JOYSTICK); + IF_BTN_IN_RANGE(8) + SetControllerKeyAssociatedWithAction(VEHICLE_HANDBRAKE, 8, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_LOCK_TARGET, 8, JOYSTICK); + IF_BTN_IN_RANGE(7) + SetControllerKeyAssociatedWithAction(PED_CENTER_CAMERA_BEHIND_PLAYER, 7, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, 7, JOYSTICK); + IF_BTN_IN_RANGE(6) + SetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_RIGHT, 6, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_LOOKRIGHT, 6, JOYSTICK); + IF_BTN_IN_RANGE(5) + SetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_LEFT, 5, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_LOOKLEFT, 5, JOYSTICK); + /*******************************************************************************************/ + IF_BTN_IN_RANGE(4) + SetControllerKeyAssociatedWithAction(VEHICLE_ENTER_EXIT, 4, JOYSTICK); + IF_BTN_IN_RANGE(3) + SetControllerKeyAssociatedWithAction(VEHICLE_BRAKE, 3, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_JUMPING, 3, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_IN, 3, JOYSTICK); + IF_BTN_IN_RANGE(2) + SetControllerKeyAssociatedWithAction(VEHICLE_ACCELERATE, 2, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SPRINT, 2, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_OUT, 2, JOYSTICK); + IF_BTN_IN_RANGE(1) + SetControllerKeyAssociatedWithAction(PED_FIREWEAPON, 1, JOYSTICK); +#ifdef BIND_VEHICLE_FIREWEAPON + SetControllerKeyAssociatedWithAction(VEHICLE_FIREWEAPON, 1, JOYSTICK); +#endif + /*******************************************************************************************/ + } + } +} + +void CControllerConfigManager::InitialiseControllerActionNameArray() +{ + wchar buf[ACTIONNAME_LENGTH + 2]; + +#define SETACTIONNAME(name) AsciiToUnicode(#name, buf); CMessages::WideStringCopy(m_aActionNames[name], buf, ACTIONNAME_LENGTH); + + SETACTIONNAME(PED_LOOKBEHIND); + SETACTIONNAME(PED_CYCLE_WEAPON_LEFT); + SETACTIONNAME(PED_CYCLE_WEAPON_RIGHT); + SETACTIONNAME(PED_LOCK_TARGET); + SETACTIONNAME(PED_JUMPING); + SETACTIONNAME(PED_SPRINT); + SETACTIONNAME(PED_CYCLE_TARGET_LEFT); + SETACTIONNAME(PED_CYCLE_TARGET_RIGHT); + SETACTIONNAME(PED_CENTER_CAMERA_BEHIND_PLAYER); + SETACTIONNAME(VEHICLE_LOOKBEHIND); + SETACTIONNAME(VEHICLE_LOOKLEFT); + SETACTIONNAME(VEHICLE_LOOKRIGHT); + SETACTIONNAME(VEHICLE_HORN); + SETACTIONNAME(VEHICLE_HANDBRAKE); + SETACTIONNAME(VEHICLE_ACCELERATE); + SETACTIONNAME(VEHICLE_BRAKE); + SETACTIONNAME(VEHICLE_CHANGE_RADIO_STATION); + SETACTIONNAME(TOGGLE_SUBMISSIONS); + SETACTIONNAME(PED_SNIPER_ZOOM_IN); + SETACTIONNAME(PED_SNIPER_ZOOM_OUT); + SETACTIONNAME(PED_1RST_PERSON_LOOK_LEFT); + SETACTIONNAME(PED_1RST_PERSON_LOOK_RIGHT); + SETACTIONNAME(PED_1RST_PERSON_LOOK_UP); + SETACTIONNAME(PED_1RST_PERSON_LOOK_DOWN); + SETACTIONNAME(SHOW_MOUSE_POINTER_TOGGLE); + SETACTIONNAME(CAMERA_CHANGE_VIEW_ALL_SITUATIONS); + SETACTIONNAME(PED_FIREWEAPON); +#ifdef BIND_VEHICLE_FIREWEAPON + SETACTIONNAME(VEHICLE_FIREWEAPON); +#endif + SETACTIONNAME(VEHICLE_ENTER_EXIT); + SETACTIONNAME(GO_LEFT); + SETACTIONNAME(GO_RIGHT); + SETACTIONNAME(GO_FORWARD); + SETACTIONNAME(GO_BACK); + SETACTIONNAME(NETWORK_TALK); + SETACTIONNAME(TOGGLE_DPAD); + SETACTIONNAME(SWITCH_DEBUG_CAM_ON); + SETACTIONNAME(TAKE_SCREEN_SHOT); + +#undef SETACTIONNAME +} + +void CControllerConfigManager::UpdateJoyInConfigMenus_ButtonDown(int32 button, int32 padnumber) +{ + if (button != 0) + { + CPad *pad = CPad::GetPad(padnumber); + if (pad != NULL) + { + switch (button) + { + case 16: + pad->PCTempJoyState.DPadLeft = 255; + break; + case 15: + pad->PCTempJoyState.DPadDown = 255; + break; + case 14: + pad->PCTempJoyState.DPadRight = 255; + break; + case 13: + pad->PCTempJoyState.DPadUp = 255; + break; +#ifdef REGISTER_START_BUTTON + case 12: + pad->PCTempJoyState.Start = 255; + break; +#endif + case 11: + pad->PCTempJoyState.RightShock = 255; + break; + case 10: + pad->PCTempJoyState.LeftShock = 255; + break; + case 9: + pad->PCTempJoyState.Select = 255; + break; + case 8: + pad->PCTempJoyState.RightShoulder1 = 255; + break; + case 7: + pad->PCTempJoyState.LeftShoulder1 = 255; + break; + case 6: + pad->PCTempJoyState.RightShoulder2 = 255; + break; + case 5: + pad->PCTempJoyState.LeftShoulder2 = 255; + break; + } + + // Now we use SDL Game Controller DB +#if defined RW_D3D9 || defined RWLIBS + if (AllValidWinJoys.m_aJoys[JOYSTICK1].m_nVendorID == 0x3427 + && AllValidWinJoys.m_aJoys[JOYSTICK1].m_nProductID == 0x1190) +#else + if (0) +#endif + { + //GIC USB Joystick, PS2 Gamepad ? + + switch (button) + { + case 4: + pad->PCTempJoyState.Square = 255; + break; + case 3: + pad->PCTempJoyState.Cross = 255; + break; + case 2: + pad->PCTempJoyState.Circle = 255; + break; + case 1: + pad->PCTempJoyState.Triangle = 255; + break; + } + } + else + { + switch (button) + { + case 4: + pad->PCTempJoyState.Triangle = 255; + break; + case 3: + pad->PCTempJoyState.Square = 255; + break; + case 2: + pad->PCTempJoyState.Cross = 255; + break; + case 1: + pad->PCTempJoyState.Circle = 255; + break; + } + } + } + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown(int32 button, eControllerType type) +{ + bool process = true; + + if ((type == KEYBOARD || type == OPTIONAL_EXTRA) && button == rsNULL) + process = false; + if (type == JOYSTICK && button == 0) + process = false; + if (type == MOUSE && button == 0) + process = false; + + if (process) + { + CPad *pad = CPad::GetPad(PAD1); + + bool firstPerson = false; + bool playerDriving = false; + + if (FindPlayerVehicle() != NULL) + { + CPlayerPed *plr = FindPlayerPed(); + if (plr != NULL) + { + if (plr->m_nPedState == PED_DRIVING) + playerDriving = true; + } + } + + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if ( mode == CCam::MODE_1STPERSON + || mode == CCam::MODE_SNIPER + || mode == CCam::MODE_ROCKETLAUNCHER + || mode == CCam::MODE_M16_1STPERSON) + { + firstPerson = true; + } + + CControllerState *state; + + switch (type) + { + case KEYBOARD: + case OPTIONAL_EXTRA: + state = &CPad::GetPad(PAD1)->PCTempKeyState; + break; + case JOYSTICK: + state = &CPad::GetPad(PAD1)->PCTempJoyState; + break; + case MOUSE: + state = &CPad::GetPad(PAD1)->PCTempMouseState; + break; + default: break; + } + + if (pad != NULL) + { + if (playerDriving) + { + AffectControllerStateOn_ButtonDown_Driving(button, type, *state); + AffectControllerStateOn_ButtonDown_VehicleAndThirdPersonOnly(button, type, *state); + } + else + { + AffectControllerStateOn_ButtonDown_FirstAndThirdPersonOnly(button, type, *state); + if (firstPerson) + AffectControllerStateOn_ButtonDown_FirstPersonOnly(button, type, *state); + else + { + AffectControllerStateOn_ButtonDown_ThirdPersonOnly(button, type, *state); + AffectControllerStateOn_ButtonDown_VehicleAndThirdPersonOnly(button, type, *state); + } + } + + AffectControllerStateOn_ButtonDown_AllStates(button, type, *state); + +#ifdef REGISTER_START_BUTTON + if (button == 12) + state->Start = 255; +#endif + } + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_Driving(int32 button, eControllerType type, CControllerState &state) +{ +#ifdef BIND_VEHICLE_FIREWEAPON + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_FIREWEAPON, type)) + state.Circle = 255; +#endif + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_LOOKBEHIND, type)) + { + state.LeftShoulder2 = 255; + state.RightShoulder2 = 255; + } + + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_LOOKLEFT, type)) + state.LeftShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_LOOKRIGHT, type)) + state.RightShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_HORN, type)) + state.LeftShock = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_HANDBRAKE, type)) + state.RightShoulder1 = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_ACCELERATE, type)) + state.Cross = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, type)) + state.LeftShoulder1 = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_BRAKE, type)) + state.Square = 255; + if (button == GetControllerKeyAssociatedWithAction(TOGGLE_SUBMISSIONS, type)) + state.RightShock = 255; + + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_TURRETLEFT, type)) + { + if (state.RightStickX == 128 || m_aSimCheckers[SIM_X2][type]) + { + state.RightStickX = 0; + m_aSimCheckers[SIM_X2][type] = true; + } + else + { + state.RightStickX = -128; + } + } + + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_TURRETRIGHT, type)) + { + if (state.RightStickX == -128 || m_aSimCheckers[SIM_X2][type]) + { + state.RightStickX = 0; + m_aSimCheckers[SIM_X2][type] = true; + } + else + state.RightStickX = 128; + } + + bool isDodo = false; + if (FindPlayerVehicle() && (FindPlayerVehicle()->IsVehicle() && ( + FindPlayerVehicle()->GetModelIndex() == MI_DODO +#ifdef FIX_BUGS + || CVehicle::bAllDodosCheat +#ifdef ALLCARSHELI_CHEAT + || bAllCarCheat +#endif +#endif + ))) + { + isDodo = true; + } + + + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_TURRETUP, type)) + { + if (isDodo == true) + { + if (state.LeftStickY == -128 || m_aSimCheckers[SIM_Y1][type]) // BUG: should be SIM_Y2. SIM_Y1 it's DPAD + { + state.LeftStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + state.LeftStickY = 128; + } + + else if (state.RightStickY == -128 || m_aSimCheckers[SIM_Y2][type]) + { + state.RightStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + { + state.RightStickY = 128; + } + } + + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_TURRETDOWN, type)) + { + if (isDodo == true) + { + if (state.LeftStickY == 128 || m_aSimCheckers[SIM_Y1][type]) // BUG: should be SIM_Y2. SIM_Y1 it's DPAD + { + state.LeftStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + state.LeftStickY = -128; + } + + else if (state.RightStickY == 128 || m_aSimCheckers[SIM_Y2][type]) + { + state.RightStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + state.RightStickY = -128; + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_FirstPersonOnly(int32 button, eControllerType type, CControllerState &state) +{ + if (button == GetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_IN, type)) + state.Square = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_OUT, type)) + state.Cross = 255; +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_ThirdPersonOnly(int32 button, eControllerType type, CControllerState &state) +{ + if (button == GetControllerKeyAssociatedWithAction(PED_LOOKBEHIND, type)) + state.RightShock = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_JUMPING, type)) + state.Square = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_LEFT, type)) + state.LeftShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_RIGHT, type)) + state.RightShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_SPRINT, type)) + state.Cross = 255; + + if (CMenuManager::m_ControlMethod == CONTROL_CLASSIC) + { + if (button == GetControllerKeyAssociatedWithAction(PED_CYCLE_TARGET_LEFT, type)) + state.LeftShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_CYCLE_TARGET_RIGHT, type)) + state.RightShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_CENTER_CAMERA_BEHIND_PLAYER, type)) + state.LeftShoulder1 = 255; + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_FirstAndThirdPersonOnly(int32 button, eControllerType type, CControllerState &state) +{ + CPad *pad = CPad::GetPad(PAD1); + +#ifdef BIND_VEHICLE_FIREWEAPON + if (button == GetControllerKeyAssociatedWithAction(PED_FIREWEAPON, type)) + state.Circle = 255; +#endif + if (button == GetControllerKeyAssociatedWithAction(PED_LOCK_TARGET, type)) + state.RightShoulder1 = 255; + + if (button == GetControllerKeyAssociatedWithAction(GO_FORWARD, type)) + { + if (state.DPadDown || m_aSimCheckers[SIM_Y1][type]) + { + m_aSimCheckers[SIM_Y1][type] = true; + state.DPadDown = 0; + state.DPadUp = 0; + } + else + state.DPadUp = 255; + } + + if (button == GetControllerKeyAssociatedWithAction(GO_BACK, type)) + { + if (state.DPadUp || m_aSimCheckers[SIM_Y1][type]) + { + m_aSimCheckers[SIM_Y1][type] = true; + state.DPadDown = 0; + state.DPadUp = 0; + } + else + state.DPadDown = 255; + } + + if (button == GetControllerKeyAssociatedWithAction(PED_1RST_PERSON_LOOK_LEFT, type)) + { + if (state.RightStickX == 128 || m_aSimCheckers[SIM_X2][type]) + { + state.RightStickX = 0; + m_aSimCheckers[SIM_X2][type] = true; + } + else + { + state.RightStickX = -128; + } + } + + if (button == GetControllerKeyAssociatedWithAction(PED_1RST_PERSON_LOOK_RIGHT, type)) + { + if (state.RightStickX == -128 || m_aSimCheckers[SIM_X2][type]) + { + state.RightStickX = 0; + m_aSimCheckers[SIM_X2][type] = true; + } + else + state.RightStickX = 128; + } + + if (CMenuManager::m_ControlMethod == CONTROL_CLASSIC) + { + if (button == GetControllerKeyAssociatedWithAction(PED_1RST_PERSON_LOOK_UP, type)) + { + if (state.RightStickY == -128 || m_aSimCheckers[SIM_Y2][type]) + { + state.RightStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + state.RightStickY = 128; + } + + if (button == GetControllerKeyAssociatedWithAction(PED_1RST_PERSON_LOOK_DOWN, type)) + { + if (state.RightStickY == 128 || m_aSimCheckers[SIM_Y2][type]) + { + state.RightStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + state.RightStickY = -128; + } + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_AllStates(int32 button, eControllerType type, CControllerState &state) +{ + if (button == GetControllerKeyAssociatedWithAction(CAMERA_CHANGE_VIEW_ALL_SITUATIONS, type)) + state.Select = 255; + +#ifndef BIND_VEHICLE_FIREWEAPON + if (button == GetControllerKeyAssociatedWithAction(PED_FIREWEAPON, type)) + state.Circle = 255; +#endif + + if (button == GetControllerKeyAssociatedWithAction(GO_LEFT, type)) + { + if (state.DPadRight || m_aSimCheckers[SIM_X1][type]) + { + m_aSimCheckers[SIM_X1][type] = true; + state.DPadLeft = 0; + state.DPadRight = 0; + } + else + state.DPadLeft = 255; + } + + if (button == GetControllerKeyAssociatedWithAction(GO_RIGHT, type)) + { + if (state.DPadLeft || m_aSimCheckers[SIM_X1][type]) + { + m_aSimCheckers[SIM_X1][type] = true; + state.DPadLeft = 0; + state.DPadRight = 0; + } + else + state.DPadRight = 255; + } + + if (button == GetControllerKeyAssociatedWithAction(NETWORK_TALK, type)) + state.NetworkTalk = 255; +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_VehicleAndThirdPersonOnly(int32 button, eControllerType type, CControllerState &state) +{ + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_ENTER_EXIT, type)) + state.Triangle = 255; +} + +void CControllerConfigManager::UpdateJoyInConfigMenus_ButtonUp(int32 button, int32 padnumber) +{ + if (button!=0) + { + CPad *pad = CPad::GetPad(padnumber); + + if (pad != NULL) + { + switch (button) + { + case 16: + pad->PCTempJoyState.DPadLeft = 0; + break; + case 15: + pad->PCTempJoyState.DPadDown = 0; + break; + case 14: + pad->PCTempJoyState.DPadRight = 0; + break; + case 13: + pad->PCTempJoyState.DPadUp = 0; + break; +#ifdef REGISTER_START_BUTTON + case 12: + pad->PCTempJoyState.Start = 0; + break; +#endif + case 11: + pad->PCTempJoyState.RightShock = 0; + break; + case 10: + pad->PCTempJoyState.LeftShock = 0; + break; + case 9: + pad->PCTempJoyState.Select = 0; + break; + case 8: + pad->PCTempJoyState.RightShoulder1 = 0; + break; + case 7: + pad->PCTempJoyState.LeftShoulder1 = 0; + break; + case 6: + pad->PCTempJoyState.RightShoulder2 = 0; + break; + case 5: + pad->PCTempJoyState.LeftShoulder2 = 0; + break; + } + + // Now we use SDL Game Controller DB +#if defined RW_D3D9 || defined RWLIBS + if (AllValidWinJoys.m_aJoys[JOYSTICK1].m_nVendorID == 0x3427 + && AllValidWinJoys.m_aJoys[JOYSTICK1].m_nProductID == 0x1190) +#else + if (0) +#endif + { + //GIC USB Joystick, PS2 Gamepad ? + + switch (button) + { + case 4: + pad->PCTempJoyState.Square = 0; + break; + case 3: + pad->PCTempJoyState.Cross = 0; + break; + case 2: + pad->PCTempJoyState.Circle = 0; + break; + case 1: + pad->PCTempJoyState.Triangle = 0; + break; + } + } + else + { + switch (button) + { + case 4: + pad->PCTempJoyState.Triangle = 0; + break; + case 3: + pad->PCTempJoyState.Square = 0; + break; + case 2: + pad->PCTempJoyState.Cross = 0; + break; + case 1: + pad->PCTempJoyState.Circle = 0; + break; + } + } + } + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonUp(int32 button, eControllerType type) +{ + bool process = true; + + if ((type == KEYBOARD || type == OPTIONAL_EXTRA) && button == rsNULL) + process = false; + if (type == JOYSTICK && button == 0) + process = false; + if (type == MOUSE && button == 0) + process = false; + + CControllerState *state; + + switch (type) + { + case KEYBOARD: + case OPTIONAL_EXTRA: + state = &CPad::GetPad(PAD1)->PCTempKeyState; + break; + case MOUSE: + state = &CPad::GetPad(PAD1)->PCTempMouseState; + break; + case JOYSTICK: + state = &CPad::GetPad(PAD1)->PCTempJoyState; + break; + default: break; + } + + if (process) + { + CPad *pad = CPad::GetPad(PAD1); + + if (pad != NULL) + { + if (FrontEndMenuManager.GetIsMenuActive()) + AffectControllerStateOn_ButtonUp_All_Player_States(button, type, *state); + +#ifdef REGISTER_START_BUTTON + if (button == 12) + state->Start = 0; +#endif + } + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonUp_All_Player_States(int32 button, eControllerType type, CControllerState &state) +{ + if (button == GetControllerKeyAssociatedWithAction(NETWORK_TALK, type)) + state.NetworkTalk = 0; +} + +void CControllerConfigManager::AffectPadFromKeyBoard() +{ + RsKeyCodes kc; + _InputTranslateShiftKeyUpDown(&kc); + + bool processdown = false; + if (!CPad::m_bMapPadOneToPadTwo && !FrontEndMenuManager.GetIsMenuActive()) + processdown = true; + + for (int32 i = 0; i < MAX_CONTROLLERACTIONS; i++) + { + int32 key = GetControllerKeyAssociatedWithAction((e_ControllerAction)i, KEYBOARD); + if (GetIsKeyboardKeyDown((RsKeyCodes)key) && processdown) + AffectControllerStateOn_ButtonDown(key, KEYBOARD); + + int32 extrakey = GetControllerKeyAssociatedWithAction((e_ControllerAction)i, OPTIONAL_EXTRA); + if (GetIsKeyboardKeyDown((RsKeyCodes)extrakey) && processdown) + AffectControllerStateOn_ButtonDown(extrakey, OPTIONAL_EXTRA); + + if (!GetIsKeyboardKeyDown((RsKeyCodes)key)) + AffectControllerStateOn_ButtonUp(key, KEYBOARD); + else if ( !GetIsKeyboardKeyDown((RsKeyCodes)extrakey)) + AffectControllerStateOn_ButtonUp(key, OPTIONAL_EXTRA); + } +} + +void CControllerConfigManager::AffectPadFromMouse() +{ + bool processdown = false; + if (!CPad::m_bMapPadOneToPadTwo && !FrontEndMenuManager.GetIsMenuActive()) + processdown = true; + + for (int32 i = 0; i < MAX_CONTROLLERACTIONS; i++) + { + int32 button = GetControllerKeyAssociatedWithAction((e_ControllerAction)i, MOUSE); + if (GetIsMouseButtonDown((RsKeyCodes)button) && processdown) + AffectControllerStateOn_ButtonDown(button, MOUSE); + if (GetIsMouseButtonUp((RsKeyCodes)button)) + AffectControllerStateOn_ButtonUp(button, MOUSE); + } +} + +void CControllerConfigManager::ClearSimButtonPressCheckers() +{ + for (int32 i = 0; i < MAX_SIMS; i++) + { + m_aSimCheckers[i][KEYBOARD] = false; + m_aSimCheckers[i][OPTIONAL_EXTRA] = false; + m_aSimCheckers[i][MOUSE] = false; + m_aSimCheckers[i][JOYSTICK] = false; + } +} + +bool CControllerConfigManager::GetIsKeyboardKeyDown(RsKeyCodes keycode) +{ + if (keycode < 255) + { + if (CPad::GetPad(PAD1)->GetChar(keycode)) + return true; + } + + for (int32 i = 0; i < 12; i++) + { + if (i + rsF1 == keycode) + { + if (CPad::GetPad(PAD1)->GetF(i)) + return true; + } + } + + switch (keycode) + { + case rsESC: + if (CPad::GetPad(PAD1)->GetEscape()) + return true; + break; + case rsINS: + if (CPad::GetPad(PAD1)->GetInsert()) + return true; + break; + case rsDEL: + if (CPad::GetPad(PAD1)->GetDelete()) + return true; + break; + case rsHOME: + if (CPad::GetPad(PAD1)->GetHome()) + return true; + break; + case rsEND: + if (CPad::GetPad(PAD1)->GetEnd()) + return true; + break; + case rsPGUP: + if (CPad::GetPad(PAD1)->GetPageUp()) + return true; + break; + case rsPGDN: + if (CPad::GetPad(PAD1)->GetPageDown()) + return true; + break; + case rsUP: + if (CPad::GetPad(PAD1)->GetUp()) + return true; + break; + case rsDOWN: + if (CPad::GetPad(PAD1)->GetDown()) + return true; + break; + case rsLEFT: + if (CPad::GetPad(PAD1)->GetLeft()) + return true; + break; + case rsRIGHT: + if (CPad::GetPad(PAD1)->GetRight()) + return true; + break; + case rsSCROLL: + if (CPad::GetPad(PAD1)->GetScrollLock()) + return true; + break; + case rsPAUSE: + if (CPad::GetPad(PAD1)->GetPause()) + return true; + break; + case rsNUMLOCK: + if (CPad::GetPad(PAD1)->GetNumLock()) + return true; + break; + case rsDIVIDE: + if (CPad::GetPad(PAD1)->GetDivide()) + return true; + break; + case rsTIMES: + if (CPad::GetPad(PAD1)->GetTimes()) + return true; + break; + case rsMINUS: + if (CPad::GetPad(PAD1)->GetMinus()) + return true; + break; + case rsPLUS: + if (CPad::GetPad(PAD1)->GetPlus()) + return true; + break; + case rsPADENTER: + if (CPad::GetPad(PAD1)->GetPadEnter()) + return true; + break; + case rsPADDEL: + if (CPad::GetPad(PAD1)->GetPadDel()) + return true; + break; + case rsPADEND: + if (CPad::GetPad(PAD1)->GetPad1()) + return true; + break; + case rsPADDOWN: + if (CPad::GetPad(PAD1)->GetPad2()) + return true; + break; + case rsPADPGDN: + if (CPad::GetPad(PAD1)->GetPad3()) + return true; + break; + case rsPADLEFT: + if (CPad::GetPad(PAD1)->GetPad4()) + return true; + break; + case rsPAD5: + if (CPad::GetPad(PAD1)->GetPad5()) + return true; + break; + case rsPADRIGHT: + if (CPad::GetPad(PAD1)->GetPad6()) + return true; + break; + case rsPADHOME: + if (CPad::GetPad(PAD1)->GetPad7()) + return true; + break; + case rsPADUP: + if (CPad::GetPad(PAD1)->GetPad8()) + return true; + break; + case rsPADPGUP: + if (CPad::GetPad(PAD1)->GetPad9()) + return true; + break; + case rsPADINS: + if (CPad::GetPad(PAD1)->GetPad0()) + return true; + break; + case rsBACKSP: + if (CPad::GetPad(PAD1)->GetBackspace()) + return true; + break; + case rsTAB: + if (CPad::GetPad(PAD1)->GetTab()) + return true; + break; + case rsCAPSLK: + if (CPad::GetPad(PAD1)->GetCapsLock()) + return true; + break; + case rsENTER: + if (CPad::GetPad(PAD1)->GetEnter()) + return true; + break; + case rsLSHIFT: + if (CPad::GetPad(PAD1)->GetLeftShift()) + return true; + break; + case rsSHIFT: + if (CPad::GetPad(PAD1)->GetShift()) + return true; + break; + case rsRSHIFT: + if (CPad::GetPad(PAD1)->GetRightShift()) + return true; + break; + case rsLCTRL: + if (CPad::GetPad(PAD1)->GetLeftCtrl()) + return true; + break; + case rsRCTRL: + if (CPad::GetPad(PAD1)->GetRightCtrl()) + return true; + break; + case rsLALT: + if (CPad::GetPad(PAD1)->GetLeftAlt()) + return true; + break; + case rsRALT: + if (CPad::GetPad(PAD1)->GetRightAlt()) + return true; + break; + case rsLWIN: + if (CPad::GetPad(PAD1)->GetLeftWin()) + return true; + break; + case rsRWIN: + if (CPad::GetPad(PAD1)->GetRightWin()) + return true; + break; + case rsAPPS: + if (CPad::GetPad(PAD1)->GetApps()) + return true; + break; + default: break; + } + + return false; +} + +bool CControllerConfigManager::GetIsKeyboardKeyJustDown(RsKeyCodes keycode) +{ + if (keycode < 255) + { + if (CPad::GetPad(PAD1)->GetCharJustDown(keycode)) + return true; + } + + for (int32 i = 0; i < 12; i++) + { + if (i + rsF1 == keycode) + { + if (CPad::GetPad(PAD1)->GetFJustDown(i)) + return true; + } + } + + switch (keycode) + { + case rsESC: + if (CPad::GetPad(PAD1)->GetEscapeJustDown()) + return true; + break; + case rsINS: + if (CPad::GetPad(PAD1)->GetInsertJustDown()) + return true; + break; + case rsDEL: + if (CPad::GetPad(PAD1)->GetDeleteJustDown()) + return true; + break; + case rsHOME: + if (CPad::GetPad(PAD1)->GetHomeJustDown()) + return true; + break; + case rsEND: + if (CPad::GetPad(PAD1)->GetEndJustDown()) + return true; + break; + case rsPGUP: + if (CPad::GetPad(PAD1)->GetPageUpJustDown()) + return true; + break; + case rsPGDN: + if (CPad::GetPad(PAD1)->GetPageDownJustDown()) + return true; + break; + case rsUP: + if (CPad::GetPad(PAD1)->GetUpJustDown()) + return true; + break; + case rsDOWN: + if (CPad::GetPad(PAD1)->GetDownJustDown()) + return true; + break; + case rsLEFT: + if (CPad::GetPad(PAD1)->GetLeftJustDown()) + return true; + break; + case rsRIGHT: + if (CPad::GetPad(PAD1)->GetRightJustDown()) + return true; + break; + case rsSCROLL: + if (CPad::GetPad(PAD1)->GetScrollLockJustDown()) + return true; + break; + case rsPAUSE: + if (CPad::GetPad(PAD1)->GetPauseJustDown()) + return true; + break; + case rsNUMLOCK: + if (CPad::GetPad(PAD1)->GetNumLockJustDown()) + return true; + break; + case rsDIVIDE: + if (CPad::GetPad(PAD1)->GetDivideJustDown()) + return true; + break; + case rsTIMES: + if (CPad::GetPad(PAD1)->GetTimesJustDown()) + return true; + break; + case rsMINUS: + if (CPad::GetPad(PAD1)->GetMinusJustDown()) + return true; + break; + case rsPLUS: + if (CPad::GetPad(PAD1)->GetPlusJustDown()) + return true; + break; + case rsPADENTER: + if (CPad::GetPad(PAD1)->GetPadEnterJustDown()) + return true; + break; + case rsPADDEL: + if (CPad::GetPad(PAD1)->GetPadDelJustDown()) + return true; + break; + case rsPADEND: + if (CPad::GetPad(PAD1)->GetPad1JustDown()) + return true; + break; + case rsPADDOWN: + if (CPad::GetPad(PAD1)->GetPad2JustDown()) + return true; + break; + case rsPADPGDN: + if (CPad::GetPad(PAD1)->GetPad3JustDown()) + return true; + break; + case rsPADLEFT: + if (CPad::GetPad(PAD1)->GetPad4JustDown()) + return true; + break; + case rsPAD5: + if (CPad::GetPad(PAD1)->GetPad5JustDown()) + return true; + break; + case rsPADRIGHT: + if (CPad::GetPad(PAD1)->GetPad6JustDown()) + return true; + break; + case rsPADHOME: + if (CPad::GetPad(PAD1)->GetPad7JustDown()) + return true; + break; + case rsPADUP: + if (CPad::GetPad(PAD1)->GetPad8JustDown()) + return true; + break; + case rsPADPGUP: + if (CPad::GetPad(PAD1)->GetPad9JustDown()) + return true; + break; + case rsPADINS: + if (CPad::GetPad(PAD1)->GetPad0JustDown()) + return true; + break; + case rsBACKSP: + if (CPad::GetPad(PAD1)->GetBackspaceJustDown()) + return true; + break; + case rsTAB: + if (CPad::GetPad(PAD1)->GetTabJustDown()) + return true; + break; + case rsCAPSLK: + if (CPad::GetPad(PAD1)->GetCapsLockJustDown()) + return true; + break; + case rsENTER: + if (CPad::GetPad(PAD1)->GetReturnJustDown()) + return true; + break; + case rsLSHIFT: + if (CPad::GetPad(PAD1)->GetLeftShiftJustDown()) + return true; + break; + case rsSHIFT: + if (CPad::GetPad(PAD1)->GetShiftJustDown()) + return true; + break; + case rsRSHIFT: + if (CPad::GetPad(PAD1)->GetRightShiftJustDown()) + return true; + break; + case rsLCTRL: + if (CPad::GetPad(PAD1)->GetLeftCtrlJustDown()) + return true; + break; + case rsRCTRL: + if (CPad::GetPad(PAD1)->GetRightCtrlJustDown()) + return true; + break; + case rsLALT: + if (CPad::GetPad(PAD1)->GetLeftAltJustDown()) + return true; + break; + case rsRALT: + if (CPad::GetPad(PAD1)->GetRightAltJustDown()) + return true; + break; + case rsLWIN: + if (CPad::GetPad(PAD1)->GetLeftWinJustDown()) + return true; + break; + case rsRWIN: + if (CPad::GetPad(PAD1)->GetRightWinJustDown()) + return true; + break; + case rsAPPS: + if (CPad::GetPad(PAD1)->GetAppsJustDown()) + return true; + break; + default: break; + } + + return false; +} + +bool CControllerConfigManager::GetIsMouseButtonDown(RsKeyCodes keycode) +{ + switch (keycode) + { + case rsMOUSELEFTBUTTON: + if (CPad::GetPad(PAD1)->GetLeftMouse()) + return true; + break; + case rsMOUSMIDDLEBUTTON: + if (CPad::GetPad(PAD1)->GetMiddleMouse()) + return true; + break; + case rsMOUSERIGHTBUTTON: + if (CPad::GetPad(PAD1)->GetRightMouse()) + return true; + break; + case rsMOUSEWHEELUPBUTTON: + if (CPad::GetPad(PAD1)->GetMouseWheelUp()) + return true; + break; + case rsMOUSEWHEELDOWNBUTTON: + if (CPad::GetPad(PAD1)->GetMouseWheelDown()) + return true; + break; + case rsMOUSEX1BUTTON: + if (CPad::GetPad(PAD1)->GetMouseX1()) + return true; + break; + case rsMOUSEX2BUTTON: + if (CPad::GetPad(PAD1)->GetMouseX2()) + return true; + break; + default: break; + } + + return false; +} + +bool CControllerConfigManager::GetIsMouseButtonUp(RsKeyCodes keycode) +{ + switch (keycode) + { + case rsMOUSELEFTBUTTON: + if (CPad::GetPad(PAD1)->GetLeftMouseUp()) + return true; + break; + case rsMOUSMIDDLEBUTTON: + if (CPad::GetPad(PAD1)->GetMiddleMouseUp()) + return true; + break; + case rsMOUSERIGHTBUTTON: + if (CPad::GetPad(PAD1)->GetRightMouseUp()) + return true; + break; + case rsMOUSEWHEELUPBUTTON: + if (CPad::GetPad(PAD1)->GetMouseWheelUpUp()) + return true; + break; + case rsMOUSEWHEELDOWNBUTTON: + if (CPad::GetPad(PAD1)->GetMouseWheelDownUp()) + return true; + break; + case rsMOUSEX1BUTTON: + if (CPad::GetPad(PAD1)->GetMouseX1Up()) + return true; + break; + case rsMOUSEX2BUTTON: + if (CPad::GetPad(PAD1)->GetMouseX2Up()) + return true; + break; + default: break; + } + + return false; +} + +#define CLEAR_ACTION_IF_NEEDED(action) \ +if (key == GetControllerKeyAssociatedWithAction(action, type))\ + ClearSettingsAssociatedWithAction(action, type); + +void CControllerConfigManager::DeleteMatchingCommonControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { + CLEAR_ACTION_IF_NEEDED(CAMERA_CHANGE_VIEW_ALL_SITUATIONS); +#ifndef BIND_VEHICLE_FIREWEAPON + CLEAR_ACTION_IF_NEEDED(PED_FIREWEAPON); +#endif + CLEAR_ACTION_IF_NEEDED(GO_LEFT); + CLEAR_ACTION_IF_NEEDED(GO_RIGHT); + CLEAR_ACTION_IF_NEEDED(NETWORK_TALK); + CLEAR_ACTION_IF_NEEDED(SWITCH_DEBUG_CAM_ON); + CLEAR_ACTION_IF_NEEDED(TOGGLE_DPAD); + CLEAR_ACTION_IF_NEEDED(TAKE_SCREEN_SHOT); + CLEAR_ACTION_IF_NEEDED(SHOW_MOUSE_POINTER_TOGGLE); + } +} + +void CControllerConfigManager::DeleteMatching3rdPersonControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { + CLEAR_ACTION_IF_NEEDED(PED_LOOKBEHIND); + CLEAR_ACTION_IF_NEEDED(PED_CYCLE_WEAPON_LEFT); + CLEAR_ACTION_IF_NEEDED(PED_CYCLE_WEAPON_RIGHT); + CLEAR_ACTION_IF_NEEDED(PED_JUMPING); + CLEAR_ACTION_IF_NEEDED(PED_SPRINT); + + if (CMenuManager::m_ControlMethod == CONTROL_CLASSIC) + { + CLEAR_ACTION_IF_NEEDED(PED_CYCLE_TARGET_LEFT); + CLEAR_ACTION_IF_NEEDED(PED_CYCLE_TARGET_RIGHT); + CLEAR_ACTION_IF_NEEDED(PED_CENTER_CAMERA_BEHIND_PLAYER); + } + } +} + +void CControllerConfigManager::DeleteMatching1rst3rdPersonControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { +#ifdef BIND_VEHICLE_FIREWEAPON + CLEAR_ACTION_IF_NEEDED(PED_FIREWEAPON); +#endif + CLEAR_ACTION_IF_NEEDED(PED_LOCK_TARGET); + CLEAR_ACTION_IF_NEEDED(GO_FORWARD); + CLEAR_ACTION_IF_NEEDED(GO_BACK); + + if (CMenuManager::m_ControlMethod == CONTROL_CLASSIC) + { + CLEAR_ACTION_IF_NEEDED(PED_1RST_PERSON_LOOK_LEFT); + CLEAR_ACTION_IF_NEEDED(PED_1RST_PERSON_LOOK_RIGHT); + CLEAR_ACTION_IF_NEEDED(PED_1RST_PERSON_LOOK_DOWN); + CLEAR_ACTION_IF_NEEDED(PED_1RST_PERSON_LOOK_UP); + } + } +} + +void CControllerConfigManager::DeleteMatchingVehicleControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { +#ifdef BIND_VEHICLE_FIREWEAPON + CLEAR_ACTION_IF_NEEDED(VEHICLE_FIREWEAPON); +#endif + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKBEHIND); + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKLEFT); + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKRIGHT); + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKBEHIND); // note: duplicate + CLEAR_ACTION_IF_NEEDED(VEHICLE_HORN); + CLEAR_ACTION_IF_NEEDED(VEHICLE_HANDBRAKE); + CLEAR_ACTION_IF_NEEDED(VEHICLE_ACCELERATE); + CLEAR_ACTION_IF_NEEDED(VEHICLE_BRAKE); + CLEAR_ACTION_IF_NEEDED(VEHICLE_CHANGE_RADIO_STATION); + CLEAR_ACTION_IF_NEEDED(TOGGLE_SUBMISSIONS); + CLEAR_ACTION_IF_NEEDED(VEHICLE_TURRETLEFT); + CLEAR_ACTION_IF_NEEDED(VEHICLE_TURRETRIGHT); + CLEAR_ACTION_IF_NEEDED(VEHICLE_TURRETUP); + CLEAR_ACTION_IF_NEEDED(VEHICLE_TURRETDOWN); + } +} + +void CControllerConfigManager::DeleteMatchingVehicle_3rdPersonControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { + CLEAR_ACTION_IF_NEEDED(VEHICLE_ENTER_EXIT); + } +} + +void CControllerConfigManager::DeleteMatching1rstPersonControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { + CLEAR_ACTION_IF_NEEDED(PED_SNIPER_ZOOM_IN); + CLEAR_ACTION_IF_NEEDED(PED_SNIPER_ZOOM_OUT); + } +} + +#undef CLEAR_ACTION_IF_NEEDED + +#ifdef RADIO_SCROLL_TO_PREV_STATION +#define CHECK_ACTION(action) \ +if (key == GetControllerKeyAssociatedWithAction(action, type))\ + return true; + +bool CControllerConfigManager::IsAnyVehicleActionAssignedToMouseKey(int32 key) +{ + const eControllerType type = MOUSE; + if (!GetIsKeyBlank(key, type)) + { +#ifdef BIND_VEHICLE_FIREWEAPON + CHECK_ACTION(VEHICLE_FIREWEAPON); +#endif + CHECK_ACTION(VEHICLE_LOOKBEHIND); + CHECK_ACTION(VEHICLE_LOOKLEFT); + CHECK_ACTION(VEHICLE_LOOKRIGHT); + CHECK_ACTION(VEHICLE_LOOKBEHIND); // note: duplicate + CHECK_ACTION(VEHICLE_HORN); + CHECK_ACTION(VEHICLE_HANDBRAKE); + CHECK_ACTION(VEHICLE_ACCELERATE); + CHECK_ACTION(VEHICLE_BRAKE); + CHECK_ACTION(VEHICLE_CHANGE_RADIO_STATION); + CHECK_ACTION(TOGGLE_SUBMISSIONS); + CHECK_ACTION(VEHICLE_TURRETLEFT); + CHECK_ACTION(VEHICLE_TURRETRIGHT); + CHECK_ACTION(VEHICLE_TURRETUP); + CHECK_ACTION(VEHICLE_TURRETDOWN); + CHECK_ACTION(VEHICLE_ENTER_EXIT); + CHECK_ACTION(CAMERA_CHANGE_VIEW_ALL_SITUATIONS); +#ifndef BIND_VEHICLE_FIREWEAPON + CHECK_ACTION(PED_FIREWEAPON); +#endif + CHECK_ACTION(GO_LEFT); + CHECK_ACTION(GO_RIGHT); + CHECK_ACTION(NETWORK_TALK); + CHECK_ACTION(SWITCH_DEBUG_CAM_ON); + CHECK_ACTION(TOGGLE_DPAD); + CHECK_ACTION(TAKE_SCREEN_SHOT); + CHECK_ACTION(SHOW_MOUSE_POINTER_TOGGLE); + } + return false; +} + +#undef CHECK_ACTION +#endif + +void CControllerConfigManager::DeleteMatchingActionInitiators(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { + switch (GetActionType(action)) + { + case ACTIONTYPE_1RSTPERSON: + DeleteMatchingCommonControls (action, key, type); + DeleteMatching1rstPersonControls (action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + break; + case ACTIONTYPE_3RDPERSON: + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingCommonControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + break; + case ACTIONTYPE_VEHICLE: + DeleteMatchingVehicleControls (action, key, type); + DeleteMatchingCommonControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + break; + case ACTIONTYPE_VEHICLE_3RDPERSON: + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingVehicleControls (action, key, type); + DeleteMatchingCommonControls (action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + break; + case ACTIONTYPE_1RST3RDPERSON: + DeleteMatching1rstPersonControls (action, key, type); + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingCommonControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + break; + case ACTIONTYPE_COMMON: + DeleteMatching1rstPersonControls (action, key, type); + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingVehicleControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + DeleteMatchingCommonControls (action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + break; + default: break; + } + } +} + +bool CControllerConfigManager::GetIsKeyBlank(int32 key, eControllerType type) +{ + switch (type) + { + case KEYBOARD: + case OPTIONAL_EXTRA: + if (key != rsNULL) + return false; + break; + + case JOYSTICK: + if (key != 0) + return false; + break; + + case MOUSE: + if (key != 0) + return false; + break; + default: break; + } + + return true; +} + +e_ControllerActionType CControllerConfigManager::GetActionType(e_ControllerAction action) +{ + switch (action) + { + case CAMERA_CHANGE_VIEW_ALL_SITUATIONS: +#ifndef BIND_VEHICLE_FIREWEAPON + case PED_FIREWEAPON: +#endif + case GO_LEFT: + case GO_RIGHT: + case NETWORK_TALK: + case SWITCH_DEBUG_CAM_ON: + case TOGGLE_DPAD: + case TAKE_SCREEN_SHOT: + case SHOW_MOUSE_POINTER_TOGGLE: + return ACTIONTYPE_COMMON; + break; + + case PED_LOOKBEHIND: + case PED_CYCLE_WEAPON_LEFT: + case PED_CYCLE_WEAPON_RIGHT: + case PED_JUMPING: + case PED_SPRINT: + case PED_CYCLE_TARGET_LEFT: + case PED_CYCLE_TARGET_RIGHT: + case PED_CENTER_CAMERA_BEHIND_PLAYER: + return ACTIONTYPE_3RDPERSON; + break; + +#ifdef BIND_VEHICLE_FIREWEAPON + case VEHICLE_FIREWEAPON: +#endif + case VEHICLE_LOOKBEHIND: + case VEHICLE_LOOKLEFT: + case VEHICLE_LOOKRIGHT: + case VEHICLE_HORN: + case VEHICLE_HANDBRAKE: + case VEHICLE_ACCELERATE: + case VEHICLE_BRAKE: + case VEHICLE_CHANGE_RADIO_STATION: + case TOGGLE_SUBMISSIONS: + case VEHICLE_TURRETLEFT: + case VEHICLE_TURRETRIGHT: + case VEHICLE_TURRETUP: + case VEHICLE_TURRETDOWN: + return ACTIONTYPE_VEHICLE; + break; + + case VEHICLE_ENTER_EXIT: + return ACTIONTYPE_VEHICLE_3RDPERSON; + break; + +#ifdef BIND_VEHICLE_FIREWEAPON + case PED_FIREWEAPON: +#endif + case PED_LOCK_TARGET: + case GO_FORWARD: + case GO_BACK: + case PED_1RST_PERSON_LOOK_LEFT: + case PED_1RST_PERSON_LOOK_RIGHT: + case PED_1RST_PERSON_LOOK_DOWN: + case PED_1RST_PERSON_LOOK_UP: + return ACTIONTYPE_1RST3RDPERSON; + break; + + case PED_SNIPER_ZOOM_IN: + case PED_SNIPER_ZOOM_OUT: + return ACTIONTYPE_1RSTPERSON; + break; + default: break; + } + + return ACTIONTYPE_NONE; +} + +void CControllerConfigManager::ClearSettingsAssociatedWithAction(e_ControllerAction action, eControllerType type) +{ + switch (type) + { + case KEYBOARD: + m_aSettings[action][type].m_Key = rsNULL; + m_aSettings[action][type].m_ContSetOrder = SETORDER_NONE; + break; + case OPTIONAL_EXTRA: + m_aSettings[action][type].m_Key = rsNULL; + m_aSettings[action][type].m_ContSetOrder = SETORDER_NONE; + break; + case MOUSE: + m_aSettings[action][type].m_Key = 0; + m_aSettings[action][type].m_ContSetOrder = SETORDER_NONE; + break; + case JOYSTICK: + m_aSettings[action][type].m_Key = 0; + m_aSettings[action][type].m_ContSetOrder = SETORDER_NONE; + break; + default: break; + } + + ResetSettingOrder(action); +} + +wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_ControllerAction action, eContSetOrder setorder) +{ + for (int i = 0; i < MAX_CONTROLLERTYPES; i++) + { + if (m_aSettings[action][i].m_ContSetOrder == setorder) + { + switch (i) + { + case KEYBOARD: + case OPTIONAL_EXTRA: + return GetControllerSettingTextKeyBoard(action, (eControllerType)i); + case MOUSE: + return GetControllerSettingTextMouse (action); + case JOYSTICK: + return GetControllerSettingTextJoystick(action); + default: break; + } + } + } + + return NULL; +} + +wchar *CControllerConfigManager::GetControllerSettingTextKeyBoard(e_ControllerAction action, eControllerType type) +{ + static wchar ActionText[50]; + static wchar NewStringWithNumber[30]; + + for (int32 i = 0; i < ARRAY_SIZE(ActionText); i++) + ActionText[i] = '\0'; + + if (GetControllerKeyAssociatedWithAction(action, type) != rsNULL) + { + if ( GetControllerKeyAssociatedWithAction(action, type) >= 0 + && GetControllerKeyAssociatedWithAction(action, type) <= 255) + { + char c = GetControllerKeyAssociatedWithAction(action, type); + if (c == ' ') + return TheText.Get("FEC_SPC"); // "SPC" + else + { + ActionText[0] = CFont::character_code(c); + if (ActionText[0] == '\0') + ActionText[0] = CFont::character_code('#'); + ActionText[1] = '\0'; + return ActionText; + } + } + else + { + switch (GetControllerKeyAssociatedWithAction(action, type)) + { + case rsF1: + case rsF2: + case rsF3: + case rsF4: + case rsF5: + case rsF6: + case rsF7: + case rsF8: + case rsF9: + case rsF10: + case rsF11: + case rsF12: + { + CMessages::InsertNumberInString(TheText.Get("FEC_FNC"), // "F~1~" + GetControllerKeyAssociatedWithAction(action, type) - rsESC, + -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsINS: + { + return TheText.Get("FEC_IRT"); // "INS" + break; + } + + case rsDEL: + { + return TheText.Get("FEC_DLL"); // "DEL" + break; + } + + case rsHOME: + { + return TheText.Get("FEC_HME"); // "HOME" + break; + } + + case rsEND: + { + return TheText.Get("FEC_END"); // "END" + break; + } + + case rsPGUP: + { + return TheText.Get("FEC_PGU"); // "PGUP" + break; + } + + case rsPGDN: + { + return TheText.Get("FEC_PGD"); // "PGDN" + break; + } + + case rsUP: + { + return TheText.Get("FEC_UPA"); // "UP" + break; + } + + case rsDOWN: + { + return TheText.Get("FEC_DWA"); // "DOWN" + break; + } + + case rsLEFT: + { + return TheText.Get("FEC_LFA"); // "LEFT" + break; + } + + case rsRIGHT: + { + return TheText.Get("FEC_RFA"); // "RIGHT" + break; + } + + case rsDIVIDE: + { + return TheText.Get("FEC_FWS"); // "NUM /" + break; + } + + case rsTIMES: + { + return TheText.Get("FEC_STR"); // "NUM STAR" + break; + } + + case rsPLUS: + { + return TheText.Get("FEC_PLS"); // "NUM +" + break; + } + + case rsMINUS: + { + return TheText.Get("FEC_MIN"); // "NUM -" + break; + } + + case rsPADDEL: + { + return TheText.Get("FEC_DOT"); // "NUM ." + break; + } + + case rsPADEND: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 1, -1, -1, -1, -1, -1, NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADDOWN: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 2, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADPGDN: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 3, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADLEFT: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 4, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPAD5: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 5, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsNUMLOCK: + { + return TheText.Get("FEC_NLK"); // "NUMLOCK" + break; + } + + case rsPADRIGHT: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 6, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADHOME: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 7, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADUP: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 8, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADPGUP: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 9, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADINS: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 0, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADENTER: + { + return TheText.Get("FEC_ETR"); // "ENT" + break; + } + + case rsSCROLL: + { + return TheText.Get("FEC_SLK"); // "SCROLL LOCK" + break; + } + + case rsPAUSE: + { + return TheText.Get("FEC_PSB"); // "BREAK" + break; + } + + case rsBACKSP: + { + return TheText.Get("FEC_BSP"); // "BSPACE" + break; + } + + case rsTAB: + { + return TheText.Get("FEC_TAB"); // "TAB" + break; + } + + case rsCAPSLK: + { + return TheText.Get("FEC_CLK"); // "CAPSLOCK" + break; + } + + case rsENTER: + { + return TheText.Get("FEC_RTN"); // "RET" + break; + } + + case rsLSHIFT: + { + return TheText.Get("FEC_LSF"); // "LSHIFT" + break; + } + + case rsRSHIFT: + { + return TheText.Get("FEC_RSF"); // "RSHIFT" + break; + } + + case rsLCTRL: + { + return TheText.Get("FEC_LCT"); // "LCTRL" + break; + } + + case rsRCTRL: + { + return TheText.Get("FEC_RCT"); // "RCTRL" + break; + } + + case rsLALT: + { + return TheText.Get("FEC_LAL"); // "LALT" + break; + } + + case rsRALT: + { + return TheText.Get("FEC_RAL"); // "RALT" + break; + } + + case rsLWIN: + { + return TheText.Get("FEC_LWD"); // "LWIN" + break; + } + + case rsRWIN: + { + return TheText.Get("FEC_RWD"); // "RWIN" + break; + } + + case rsAPPS: + { + return TheText.Get("FEC_WRC"); // "WINCLICK" + break; + } + + case rsSHIFT: + { + return TheText.Get("FEC_SFT"); // "SHIFT" + break; + } + default: break; + } + } + } + + return NULL; +} + +wchar *CControllerConfigManager::GetControllerSettingTextMouse(e_ControllerAction action) +{ + switch (m_aSettings[action][MOUSE].m_Key) + { + case 1: + return TheText.Get("FEC_MSL"); // LMB + break; + case 2: + return TheText.Get("FEC_MSM"); // MMB + break; + case 3: + return TheText.Get("FEC_MSR"); // RMB + break; + case 4: + return TheText.Get("FEC_MWF"); // WHEEL UP + break; + case 5: + return TheText.Get("FEC_MWB"); // WHEEL DN + break; + case 6: + return TheText.Get("FEC_MXO"); // MXB1 + break; + case 7: + return TheText.Get("FEC_MXT"); // MXB2 + break; + default: break; + } + + return NULL; +} + +wchar *CControllerConfigManager::GetControllerSettingTextJoystick(e_ControllerAction action) +{ + if (m_aSettings[action][JOYSTICK].m_Key == 0) + return NULL; + + static wchar NewStringWithNumber[30]; + + CMessages::InsertNumberInString(TheText.Get("FEC_JBO"), // JOY ~1~ + m_aSettings[action][JOYSTICK].m_Key, -1, -1, -1, -1, -1, + NewStringWithNumber); + + return NewStringWithNumber; +} + +int32 CControllerConfigManager::GetNumOfSettingsForAction(e_ControllerAction action) +{ + int32 num = 0; + + if (m_aSettings[action][KEYBOARD].m_Key != rsNULL) num++; + if (m_aSettings[action][OPTIONAL_EXTRA].m_Key != rsNULL) num++; + if (m_aSettings[action][MOUSE].m_Key != 0) num++; + if (m_aSettings[action][JOYSTICK].m_Key != 0) num++; + + return num; +} + +#ifdef BIND_VEHICLE_FIREWEAPON +#define VFB(b) b, +#else +#define VFB(b) +#endif + +#define CONTROLLER_BUTTONS(T, O, X, Q, L1, L2, L3, R1, R2, R3, SELECT) \ + {{ \ + O, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + nil, /* GO_LEFT */ \ + nil, /* GO_RIGHT */ \ + Q, /* PED_SNIPER_ZOOM_IN */ \ + X, /* PED_SNIPER_ZOOM_OUT */ \ + T, /* VEHICLE_ENTER_EXIT */ \ + SELECT, /* CAMERA_CHANGE_VIEW_ALL_SITUATIONS */ \ + Q, /* PED_JUMPING */ \ + X, /* PED_SPRINT */ \ + R3, /* PED_LOOKBEHIND */ \ + VFB(O) /* VEHICLE_FIREWEAPON */ \ + X, /* VEHICLE_ACCELERATE */ \ + Q, /* VEHICLE_BRAKE */ \ + L1, /* VEHICLE_CHANGE_RADIO_STATION */ \ + L3, /* VEHICLE_HORN */ \ + R3, /* TOGGLE_SUBMISSIONS */ \ + R1, /* VEHICLE_HANDBRAKE */ \ + nil, /* PED_1RST_PERSON_LOOK_LEFT */ \ + nil, /* PED_1RST_PERSON_LOOK_RIGHT */ \ + L2, /* VEHICLE_LOOKLEFT */ \ + R2, /* VEHICLE_LOOKRIGHT */ \ + nil, /* VEHICLE_LOOKBEHIND */ \ + nil, /* VEHICLE_TURRETLEFT */ \ + nil, /* VEHICLE_TURRETRIGHT */ \ + nil, /* VEHICLE_TURRETUP */ \ + nil, /* VEHICLE_TURRETDOWN */ \ + L2, /* PED_CYCLE_TARGET_LEFT */ \ + R2, /* PED_CYCLE_TARGET_RIGHT */ \ + L1, /* PED_CENTER_CAMERA_BEHIND_PLAYER */ \ + R1, /* PED_LOCK_TARGET */ \ + nil, /* NETWORK_TALK */ \ + nil, /* PED_1RST_PERSON_LOOK_UP */ \ + nil, /* PED_1RST_PERSON_LOOK_DOWN */ \ + nil, /* _CONTROLLERACTION_36 */ \ + nil, /* TOGGLE_DPAD */ \ + nil, /* SWITCH_DEBUG_CAM_ON */ \ + nil, /* TAKE_SCREEN_SHOT */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }, \ + { \ + O, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + nil, /* GO_LEFT */ \ + nil, /* GO_RIGHT */ \ + Q, /* PED_SNIPER_ZOOM_IN */ \ + X, /* PED_SNIPER_ZOOM_OUT */ \ + T, /* VEHICLE_ENTER_EXIT */ \ + SELECT, /* CAMERA_CHANGE_VIEW_ALL_SITUATIONS */ \ + Q, /* PED_JUMPING */ \ + X, /* PED_SPRINT */ \ + R3, /* PED_LOOKBEHIND */ \ + VFB(O) /* VEHICLE_FIREWEAPON */ \ + X, /* VEHICLE_ACCELERATE */ \ + Q, /* VEHICLE_BRAKE */ \ + SELECT, /* VEHICLE_CHANGE_RADIO_STATION */ \ + L1, /* VEHICLE_HORN */ \ + R3, /* TOGGLE_SUBMISSIONS */ \ + R1, /* VEHICLE_HANDBRAKE */ \ + nil, /* PED_1RST_PERSON_LOOK_LEFT */ \ + nil, /* PED_1RST_PERSON_LOOK_RIGHT */ \ + L2, /* VEHICLE_LOOKLEFT */ \ + R2, /* VEHICLE_LOOKRIGHT */ \ + nil, /* VEHICLE_LOOKBEHIND */ \ + nil, /* VEHICLE_TURRETLEFT */ \ + nil, /* VEHICLE_TURRETRIGHT */ \ + nil, /* VEHICLE_TURRETUP */ \ + nil, /* VEHICLE_TURRETDOWN */ \ + L2, /* PED_CYCLE_TARGET_LEFT */ \ + R2, /* PED_CYCLE_TARGET_RIGHT */ \ + L1, /* PED_CENTER_CAMERA_BEHIND_PLAYER */ \ + R1, /* PED_LOCK_TARGET */ \ + nil, /* NETWORK_TALK */ \ + nil, /* PED_1RST_PERSON_LOOK_UP */ \ + nil, /* PED_1RST_PERSON_LOOK_DOWN */ \ + nil, /* _CONTROLLERACTION_36 */ \ + nil, /* TOGGLE_DPAD */ \ + nil, /* SWITCH_DEBUG_CAM_ON */ \ + nil, /* TAKE_SCREEN_SHOT */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }, \ + { \ + X, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + nil, /* GO_LEFT */ \ + nil, /* GO_RIGHT */ \ + T, /* PED_SNIPER_ZOOM_IN */ \ + Q, /* PED_SNIPER_ZOOM_OUT */ \ + L1, /* VEHICLE_ENTER_EXIT */ \ + SELECT, /* CAMERA_CHANGE_VIEW_ALL_SITUATIONS */ \ + Q, /* PED_JUMPING */ \ + O, /* PED_SPRINT */ \ + R3, /* PED_LOOKBEHIND */ \ + VFB(O) /* VEHICLE_FIREWEAPON */ \ + X, /* VEHICLE_ACCELERATE */ \ + Q, /* VEHICLE_BRAKE */ \ + L3, /* VEHICLE_CHANGE_RADIO_STATION */ \ + R1, /* VEHICLE_HORN */ \ + R3, /* TOGGLE_SUBMISSIONS */ \ + T, /* VEHICLE_HANDBRAKE */ \ + nil, /* PED_1RST_PERSON_LOOK_LEFT */ \ + nil, /* PED_1RST_PERSON_LOOK_RIGHT */ \ + L2, /* VEHICLE_LOOKLEFT */ \ + R2, /* VEHICLE_LOOKRIGHT */ \ + nil, /* VEHICLE_LOOKBEHIND */ \ + nil, /* VEHICLE_TURRETLEFT */ \ + nil, /* VEHICLE_TURRETRIGHT */ \ + nil, /* VEHICLE_TURRETUP */ \ + nil, /* VEHICLE_TURRETDOWN */ \ + L2, /* PED_CYCLE_TARGET_LEFT */ \ + R2, /* PED_CYCLE_TARGET_RIGHT */ \ + T, /* PED_CENTER_CAMERA_BEHIND_PLAYER */ \ + R1, /* PED_LOCK_TARGET */ \ + nil, /* NETWORK_TALK */ \ + nil, /* PED_1RST_PERSON_LOOK_UP */ \ + nil, /* PED_1RST_PERSON_LOOK_DOWN */ \ + nil, /* _CONTROLLERACTION_36 */ \ + nil, /* TOGGLE_DPAD */ \ + nil, /* SWITCH_DEBUG_CAM_ON */ \ + nil, /* TAKE_SCREEN_SHOT */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }, \ + { \ + R1, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + nil, /* GO_LEFT */ \ + nil, /* GO_RIGHT */ \ + Q, /* PED_SNIPER_ZOOM_IN */ \ + X, /* PED_SNIPER_ZOOM_OUT */ \ + T, /* VEHICLE_ENTER_EXIT */ \ + SELECT, /* CAMERA_CHANGE_VIEW_ALL_SITUATIONS */ \ + Q, /* PED_JUMPING */ \ + X, /* PED_SPRINT */ \ + R3, /* PED_LOOKBEHIND */ \ + VFB(R1) /* VEHICLE_FIREWEAPON */ \ + nil, /* VEHICLE_ACCELERATE */ \ + nil, /* VEHICLE_BRAKE */ \ + O, /* VEHICLE_CHANGE_RADIO_STATION */ \ + L3, /* VEHICLE_HORN */ \ + Q, /* TOGGLE_SUBMISSIONS */ \ + L1, /* VEHICLE_HANDBRAKE */ \ + nil, /* PED_1RST_PERSON_LOOK_LEFT */ \ + nil, /* PED_1RST_PERSON_LOOK_RIGHT */ \ + L2, /* VEHICLE_LOOKLEFT */ \ + R2, /* VEHICLE_LOOKRIGHT */ \ + nil, /* VEHICLE_LOOKBEHIND */ \ + nil, /* VEHICLE_TURRETLEFT */ \ + nil, /* VEHICLE_TURRETRIGHT */ \ + nil, /* VEHICLE_TURRETUP */ \ + nil, /* VEHICLE_TURRETDOWN */ \ + L2, /* PED_CYCLE_TARGET_LEFT */ \ + R2, /* PED_CYCLE_TARGET_RIGHT */ \ + O, /* PED_CENTER_CAMERA_BEHIND_PLAYER */ \ + L1, /* PED_LOCK_TARGET */ \ + nil, /* NETWORK_TALK */ \ + nil, /* PED_1RST_PERSON_LOOK_UP */ \ + nil, /* PED_1RST_PERSON_LOOK_DOWN */ \ + nil, /* _CONTROLLERACTION_36 */ \ + nil, /* TOGGLE_DPAD */ \ + nil, /* SWITCH_DEBUG_CAM_ON */ \ + nil, /* TAKE_SCREEN_SHOT */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }} + + +const char *XboxButtons_noIcons[][MAX_CONTROLLERACTIONS] = CONTROLLER_BUTTONS("Y", "B", "A", "X", "LB", "LT", "LS", "RB", "RT", "RS", "BACK"); + +#ifdef BUTTON_ICONS +const char *XboxButtons[][MAX_CONTROLLERACTIONS] = CONTROLLER_BUTTONS("~T~", "~O~", "~X~", "~Q~", "~K~", "~M~", "~A~", "~J~", "~V~", "~C~", "BACK"); +#endif + + +#if 0 // set 1 for ps2 fonts +#define PS2_TRIANGLE "\"" +#define PS2_CIRCLE "|" +#define PS2_CROSS "/" +#define PS2_SQUARE "^" +#else +#define PS2_TRIANGLE "TRIANGLE" +#define PS2_CIRCLE "CIRCLE" +#define PS2_CROSS "CROSS" +#define PS2_SQUARE "SQUARE" +#endif + +const char *PlayStationButtons_noIcons[][MAX_CONTROLLERACTIONS] = + CONTROLLER_BUTTONS(PS2_TRIANGLE, PS2_CIRCLE, PS2_CROSS, PS2_SQUARE, "L1", "L2", "L3", "R1", "R2", "R3", "SELECT"); + +#ifdef BUTTON_ICONS +const char *PlayStationButtons[][MAX_CONTROLLERACTIONS] = + CONTROLLER_BUTTONS("~T~", "~O~", "~X~", "~Q~", "~K~", "~M~", "~A~", "~J~", "~V~", "~C~", "SELECT"); +#endif + +#undef PS2_TRIANGLE +#undef PS2_CIRCLE +#undef PS2_CROSS +#undef PS2_SQUARE + +const char *NintendoSwitchButtons_noIcons[][MAX_CONTROLLERACTIONS] = + CONTROLLER_BUTTONS("Y", "A", "B", "X", "L", "ZL", "LS", "R", "ZR", "RS", "BACK"); + +#ifdef BUTTON_ICONS +const char *NintendoSwitchButtons[][MAX_CONTROLLERACTIONS] = + CONTROLLER_BUTTONS("~T~", "~O~", "~X~", "~Q~", "~K~", "~M~", "~A~", "~J~", "~V~", "~C~", "BACK"); +#endif + +#undef CONTROLLER_BUTTONS +#undef VFB + +void CControllerConfigManager::GetWideStringOfCommandKeys(uint16 action, wchar *text, uint16 leight) +{ +#ifdef DETECT_PAD_INPUT_SWITCH + if (CPad::GetPad(0)->IsAffectedByController) { + wchar wstr[16]; + + const char* (*Buttons)[MAX_CONTROLLERACTIONS]; + +#ifdef BUTTON_ICONS + #ifdef GAMEPAD_MENU + switch (FrontEndMenuManager.m_PrefsControllerType) + { + case CMenuManager::CONTROLLER_DUALSHOCK2: + case CMenuManager::CONTROLLER_DUALSHOCK3: + case CMenuManager::CONTROLLER_DUALSHOCK4: + Buttons = CFont::ButtonsSlot != -1 ? PlayStationButtons : PlayStationButtons_noIcons; + break; + case CMenuManager::CONTROLLER_NINTENDO_SWITCH: + Buttons = CFont::ButtonsSlot != -1 ? NintendoSwitchButtons : NintendoSwitchButtons_noIcons; + break; + default: + #endif + Buttons = CFont::ButtonsSlot != -1 ? XboxButtons : XboxButtons_noIcons; + #ifdef GAMEPAD_MENU + break; + } + #endif +#else + switch (FrontEndMenuManager.m_PrefsControllerType) + { + case CMenuManager::CONTROLLER_DUALSHOCK2: + case CMenuManager::CONTROLLER_DUALSHOCK3: + case CMenuManager::CONTROLLER_DUALSHOCK4: + Buttons = PlayStationButtons_noIcons; + break; + case CMenuManager::CONTROLLER_NINTENDO_SWITCH: + Buttons = NintendoSwitchButtons_noIcons; + break; + default: + Buttons = XboxButtons_noIcons; + break; + } +#endif + + assert(Buttons[CPad::GetPad(0)->Mode][action] != nil); // we cannot use these + AsciiToUnicode(Buttons[CPad::GetPad(0)->Mode][action], wstr); + + CMessages::WideStringCopy(text, wstr, leight); + return; + } +#endif + + int32 nums = GetNumOfSettingsForAction((e_ControllerAction)action); + + int32 sets = 0; + + for (int32 i = SETORDER_1; i < MAX_SETORDERS; i++) + { + wchar *textorder = ControlsManager.GetControllerSettingTextWithOrderNumber((e_ControllerAction)action, (eContSetOrder)i); + if (textorder != NULL) + { + uint16 len = CMessages::GetWideStringLength(text); + CMessages::WideStringCopy(&text[len], textorder, leight - len); + + if (++sets < nums) + { + if (sets == nums - 1) + { + // if last - text += ' or '; + uint16 pos1 = CMessages::GetWideStringLength(text); + text[pos1] = ' '; + + CMessages::WideStringCopy(&text[pos1 + 1], + TheText.Get("FEC_ORR"), // "or" + leight - (pos1 + 1)); + + uint16 pos2 = CMessages::GetWideStringLength(text); + text[pos2 + 0] = ' '; + text[pos2 + 1] = '\0'; + } + else + { + // text += ', '; + uint16 pos1 = CMessages::GetWideStringLength(text); + text[pos1 + 0] = ','; + text[pos1 + 1] = ' '; + text[pos1 + 2] = '\0'; + + uint16 pos2 = CMessages::GetWideStringLength(text); + } + } + } + } +} + +int32 CControllerConfigManager::GetControllerKeyAssociatedWithAction(e_ControllerAction action, eControllerType type) +{ + return m_aSettings[action][type].m_Key; +} + +void CControllerConfigManager::UpdateJoyButtonState(int32 padnumber) +{ + for (int32 i = 0; i < MAX_BUTTONS; i++) + m_aButtonStates[i] = false; + +#ifdef __DINPUT_INCLUDED__ + for (int32 i = 0; i < MAX_BUTTONS; i++) + { + if (m_NewState.rgbButtons[i] & 0x80) + m_aButtonStates[i] = true; + else + m_aButtonStates[i] = false; + } +#elif defined RW_GL3 + if (m_NewState.isGamepad) { + for (int32 i = 0; i < MAX_BUTTONS; i++) { + if (i == GLFW_GAMEPAD_BUTTON_GUIDE) + continue; + + m_aButtonStates[MapIdToButtonId(i)-1] = m_NewState.mappedButtons[i]; + } + } else { + for (int32 i = 0; i < Min(m_NewState.numButtons, MAX_BUTTONS); i++) { + m_aButtonStates[i] = m_NewState.buttons[i]; + } + } +#endif +} + +bool CControllerConfigManager::GetIsActionAButtonCombo(e_ControllerAction action) +{ + switch (action) + { + case VEHICLE_LOOKBEHIND: + case PED_CYCLE_TARGET_LEFT: + case PED_CYCLE_TARGET_RIGHT: + return true; + break; + default: break; + } + + return false; +} + +wchar *CControllerConfigManager::GetButtonComboText(e_ControllerAction action) +{ + switch (action) + { + case PED_CYCLE_TARGET_LEFT: + return TheText.Get("FEC_PTL"); // Use LockTarget with Weapon Switch Left. + break; + + case PED_CYCLE_TARGET_RIGHT: + return TheText.Get("FEC_PTR"); // Use LockTarget with Weapon Switch Right. + break; + + case VEHICLE_LOOKBEHIND: + return TheText.Get("FEC_LBC"); // Use Look Left With Look Right. + break; + default: break; + } + + return NULL; +} + +void CControllerConfigManager::SetControllerKeyAssociatedWithAction(e_ControllerAction action, int32 key, eControllerType type) +{ + ResetSettingOrder(action); + int numOfSettings = GetNumOfSettingsForAction(action); + + m_aSettings[action][type].m_Key = key; + m_aSettings[action][type].m_ContSetOrder = numOfSettings + 1; +} + +int32 CControllerConfigManager::GetMouseButtonAssociatedWithAction(e_ControllerAction action) +{ + return m_aSettings[action][MOUSE].m_Key; +} + +void CControllerConfigManager::SetMouseButtonAssociatedWithAction(e_ControllerAction action, int32 button) +{ + int numOfSettings = GetNumOfSettingsForAction(action); + + m_aSettings[action][MOUSE].m_Key = button; + m_aSettings[action][MOUSE].m_ContSetOrder = numOfSettings + 1; +} + +void CControllerConfigManager::ResetSettingOrder(e_ControllerAction action) +{ + int32 conttype = KEYBOARD; + + for (int32 i = SETORDER_1; i < MAX_SETORDERS; i++) + { + bool isexist = false; + for (int32 j = 0; j < MAX_CONTROLLERTYPES; j++) + { + if (m_aSettings[action][j].m_ContSetOrder == i) + isexist = true; + } + + bool init = false; + + if (!isexist) + { + for (int32 k = 0; k < MAX_CONTROLLERTYPES; k++) + { + int32 setorder = m_aSettings[action][k].m_ContSetOrder; + if (setorder > i && setorder != 0) + { + if (init) + { + if (setorder < m_aSettings[action][conttype].m_ContSetOrder) + conttype = k; + } + else + { + init = true; + conttype = k; + } + } + } + + if (init) + m_aSettings[action][conttype].m_ContSetOrder = i; + } + } +} diff --git a/src/core/ControllerConfig.h b/src/core/ControllerConfig.h new file mode 100644 index 0000000..295f03b --- /dev/null +++ b/src/core/ControllerConfig.h @@ -0,0 +1,226 @@ +#pragma once + +#if defined RW_D3D9 || defined RWLIBS +#define DIRECTINPUT_VERSION 0x0800 +#include +#endif + +// based on x-gtasa + +enum eControllerType +{ + KEYBOARD = 0, + OPTIONAL_EXTRA, + MOUSE, + JOYSTICK, + MAX_CONTROLLERTYPES, +}; + +enum e_ControllerAction +{ + PED_FIREWEAPON = 0, + PED_CYCLE_WEAPON_RIGHT, + PED_CYCLE_WEAPON_LEFT, + GO_FORWARD, + GO_BACK, + GO_LEFT, + GO_RIGHT, + PED_SNIPER_ZOOM_IN, + PED_SNIPER_ZOOM_OUT, + VEHICLE_ENTER_EXIT, + CAMERA_CHANGE_VIEW_ALL_SITUATIONS, + PED_JUMPING, + PED_SPRINT, + PED_LOOKBEHIND, +#ifdef BIND_VEHICLE_FIREWEAPON + VEHICLE_FIREWEAPON, +#endif + VEHICLE_ACCELERATE, + VEHICLE_BRAKE, + VEHICLE_CHANGE_RADIO_STATION, + VEHICLE_HORN, + TOGGLE_SUBMISSIONS, + VEHICLE_HANDBRAKE, + PED_1RST_PERSON_LOOK_LEFT, + PED_1RST_PERSON_LOOK_RIGHT, + VEHICLE_LOOKLEFT, + VEHICLE_LOOKRIGHT, + VEHICLE_LOOKBEHIND, + VEHICLE_TURRETLEFT, + VEHICLE_TURRETRIGHT, + VEHICLE_TURRETUP, + VEHICLE_TURRETDOWN, + PED_CYCLE_TARGET_LEFT, + PED_CYCLE_TARGET_RIGHT, + PED_CENTER_CAMERA_BEHIND_PLAYER, + PED_LOCK_TARGET, + NETWORK_TALK, + PED_1RST_PERSON_LOOK_UP, + PED_1RST_PERSON_LOOK_DOWN, + _CONTROLLERACTION_36, // Unused + TOGGLE_DPAD, + SWITCH_DEBUG_CAM_ON, + TAKE_SCREEN_SHOT, + SHOW_MOUSE_POINTER_TOGGLE, + MAX_CONTROLLERACTIONS, +}; + +enum e_ControllerActionType +{ + ACTIONTYPE_1RSTPERSON = 0, + ACTIONTYPE_3RDPERSON, + ACTIONTYPE_VEHICLE, + ACTIONTYPE_VEHICLE_3RDPERSON, + ACTIONTYPE_COMMON, + ACTIONTYPE_1RST3RDPERSON, + ACTIONTYPE_NONE, +}; + +enum eContSetOrder +{ + SETORDER_NONE = 0, + SETORDER_1, + SETORDER_2, + SETORDER_3, + SETORDER_4, + MAX_SETORDERS, +}; + +enum eSimCheckers +{ + SIM_X1 = 0, SIM_Y1, // DPad + SIM_X2, SIM_Y2, // LeftStick + + MAX_SIMS +}; + +class CMouseControllerState; +class CControllerState; + + +#define JOY_BUTTONS 16 +#define MAX_BUTTONS (JOY_BUTTONS+1) + +#define ACTIONNAME_LENGTH 40 + +#ifdef RW_GL3 +struct GlfwJoyState { + int8 id; + bool isGamepad; + uint8 numButtons; + uint8* buttons; + bool mappedButtons[17]; +}; +#endif + +class CControllerConfigManager +{ +public: + struct tControllerConfigBind + { + int32 m_Key; + int32 m_ContSetOrder; + + tControllerConfigBind() + { + m_Key = 0; + m_ContSetOrder = 0; + } + }; + + bool m_bFirstCapture; +#if defined RW_GL3 + GlfwJoyState m_OldState; + GlfwJoyState m_NewState; +#else + DIJOYSTATE2 m_OldState; + DIJOYSTATE2 m_NewState; +#endif + wchar m_aActionNames[MAX_CONTROLLERACTIONS][ACTIONNAME_LENGTH]; + bool m_aButtonStates[MAX_BUTTONS]; + tControllerConfigBind m_aSettings[MAX_CONTROLLERACTIONS][MAX_CONTROLLERTYPES]; + bool m_aSimCheckers[MAX_SIMS][MAX_CONTROLLERTYPES]; + bool m_bMouseAssociated; + +#ifdef LOAD_INI_SETTINGS + static uint32 ms_padButtonsInited; +#endif + + CControllerConfigManager(); + + void MakeControllerActionsBlank(); + + int32 GetJoyButtonJustDown(); + + void SaveSettings(int32 file); + void LoadSettings(int32 file); + + void InitDefaultControlConfiguration(); + void InitDefaultControlConfigMouse(CMouseControllerState const &availableButtons); + void InitDefaultControlConfigJoyPad(uint32 buttons); + void InitialiseControllerActionNameArray(); + + void UpdateJoyInConfigMenus_ButtonDown (int32 button, int32 padnumber); + void AffectControllerStateOn_ButtonDown (int32 button, eControllerType type); + void AffectControllerStateOn_ButtonDown_Driving (int32 button, eControllerType type, CControllerState &state); + void AffectControllerStateOn_ButtonDown_FirstPersonOnly (int32 button, eControllerType type, CControllerState &state); + void AffectControllerStateOn_ButtonDown_ThirdPersonOnly (int32 button, eControllerType type, CControllerState &state); + void AffectControllerStateOn_ButtonDown_FirstAndThirdPersonOnly (int32 button, eControllerType type, CControllerState &state); + void AffectControllerStateOn_ButtonDown_AllStates (int32 button, eControllerType type, CControllerState &state); + void AffectControllerStateOn_ButtonDown_VehicleAndThirdPersonOnly(int32 button, eControllerType type, CControllerState &state); + + void UpdateJoyInConfigMenus_ButtonUp(int32 button, int32 padnumber); + void AffectControllerStateOn_ButtonUp(int32 button, eControllerType type); + void AffectControllerStateOn_ButtonUp_All_Player_States(int32 button, eControllerType type, CControllerState &state); + + void AffectPadFromKeyBoard(); + void AffectPadFromMouse(); + + void ClearSimButtonPressCheckers(); + + bool GetIsKeyboardKeyDown (RsKeyCodes keycode); + bool GetIsKeyboardKeyJustDown(RsKeyCodes keycode); + bool GetIsMouseButtonDown (RsKeyCodes keycode); + bool GetIsMouseButtonUp (RsKeyCodes keycode); + + + void DeleteMatchingCommonControls (e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatching3rdPersonControls (e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatching1rst3rdPersonControls (e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatchingVehicleControls (e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatchingVehicle_3rdPersonControls(e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatching1rstPersonControls (e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatchingActionInitiators (e_ControllerAction action, int32 key, eControllerType type); + +#ifdef RADIO_SCROLL_TO_PREV_STATION + bool IsAnyVehicleActionAssignedToMouseKey(int32 key); +#endif + + bool GetIsKeyBlank(int32 key, eControllerType type); + e_ControllerActionType GetActionType(e_ControllerAction action); + + void ClearSettingsAssociatedWithAction (e_ControllerAction action, eControllerType type); + wchar *GetControllerSettingTextWithOrderNumber(e_ControllerAction action, eContSetOrder setorder); + wchar *GetControllerSettingTextKeyBoard (e_ControllerAction action, eControllerType type); + wchar *GetControllerSettingTextMouse (e_ControllerAction action); + wchar *GetControllerSettingTextJoystick (e_ControllerAction action); + + int32 GetNumOfSettingsForAction(e_ControllerAction action); + void GetWideStringOfCommandKeys(uint16 action, wchar *text, uint16 leight); + int32 GetControllerKeyAssociatedWithAction(e_ControllerAction action, eControllerType type); + + void UpdateJoyButtonState(int32 padnumber); + + bool GetIsActionAButtonCombo (e_ControllerAction action); + wchar *GetButtonComboText (e_ControllerAction action); + void SetControllerKeyAssociatedWithAction(e_ControllerAction action, int32 key, eControllerType type); + int32 GetMouseButtonAssociatedWithAction (e_ControllerAction action); + void SetMouseButtonAssociatedWithAction (e_ControllerAction action, int32 button); + void ResetSettingOrder (e_ControllerAction action); +}; + +#ifndef RW_GL3 +VALIDATE_SIZE(CControllerConfigManager, 0x143C); +#endif + +extern CControllerConfigManager ControlsManager; \ No newline at end of file diff --git a/src/core/Crime.h b/src/core/Crime.h new file mode 100644 index 0000000..0582904 --- /dev/null +++ b/src/core/Crime.h @@ -0,0 +1,36 @@ +#pragma once + +enum eCrimeType { + CRIME_NONE, + CRIME_POSSESSION_GUN, + CRIME_HIT_PED, + CRIME_HIT_COP, + CRIME_SHOOT_PED, + CRIME_SHOOT_COP, + CRIME_STEAL_CAR, + CRIME_RUN_REDLIGHT, + CRIME_RECKLESS_DRIVING, + CRIME_SPEEDING, + CRIME_RUNOVER_PED, + CRIME_RUNOVER_COP, + CRIME_SHOOT_HELI, + CRIME_PED_BURNED, + CRIME_COP_BURNED, + CRIME_VEHICLE_BURNED, + CRIME_DESTROYED_CESSNA, + NUM_CRIME_TYPES +}; + +class CCrimeBeingQd +{ +public: + eCrimeType m_nType; + int32 m_nId; + uint32 m_nTime; + CVector m_vecPosn; + bool m_bReported; + bool m_bPoliceDoesntCare; + + CCrimeBeingQd() { }; + ~CCrimeBeingQd() { }; +}; diff --git a/src/core/Debug.cpp b/src/core/Debug.cpp new file mode 100644 index 0000000..e794dca --- /dev/null +++ b/src/core/Debug.cpp @@ -0,0 +1,170 @@ +#include "common.h" +#include "RwHelper.h" +#include "Debug.h" +#include "Lines.h" +#include "Font.h" +#include "main.h" +#include "Text.h" + +bool gbDebugStuffInRelease = false; + +#define DEBUG_X_POS (300) +#define DEBUG_Y_POS (41) +#define DEBUG_LINE_HEIGHT (22) + +int16 CDebug::ms_nCurrentTextLine; +char CDebug::ms_aTextBuffer[MAX_LINES][MAX_STR_LEN]; + +void +CDebug::DebugInitTextBuffer() +{ + ms_nCurrentTextLine = 0; +} + +void +CDebug::DebugAddText(const char *str) +{ + int32 i = 0; + if (*str != '\0') { + while (i < MAX_STR_LEN - 1) { + ms_aTextBuffer[ms_nCurrentTextLine][i++] = *(str++); + if (*str == '\0') + break; + } + } + + ms_aTextBuffer[ms_nCurrentTextLine++][i] = '\0'; + if (ms_nCurrentTextLine >= MAX_LINES) + ms_nCurrentTextLine = 0; +} + +void +CDebug::DebugDisplayTextBuffer() +{ +#ifndef MASTER + if (gbDebugStuffInRelease) + { + int32 i = 0; + int32 y = DEBUG_Y_POS; +#ifdef FIX_BUGS + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(1.0f, 1.0f); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); +#else + // this is not even readable + CFont::SetPropOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(1.0f, 1.0f); + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetPropOff(); +#endif + do { + char *line; + while (true) { + line = ms_aTextBuffer[(ms_nCurrentTextLine + i++) % MAX_LINES]; + if (*line != '\0') + break; + y += DEBUG_LINE_HEIGHT; + if (i == MAX_LINES) { + CFont::DrawFonts(); + return; + } + } + AsciiToUnicode(line, gUString); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(DEBUG_X_POS, y-1, gUString); + CFont::SetColor(CRGBA(255, 128, 128, 255)); + CFont::PrintString(DEBUG_X_POS+1, y, gUString); + y += DEBUG_LINE_HEIGHT; + } while (i != MAX_LINES); + CFont::DrawFonts(); + } +#endif +} + + +// custom + +CDebug::ScreenStr CDebug::ms_aScreenStrs[MAX_SCREEN_STRS]; +int CDebug::ms_nScreenStrs; + +void +CDebug::DisplayScreenStrings() +{ + int i; + + + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(1.0f, 1.0f); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetJustifyOff(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetWrapx(9999.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + + for(i = 0; i < ms_nScreenStrs; i++){ +/* + AsciiToUnicode(ms_aScreenStrs[i].str, gUString); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(ms_aScreenStrs[i].x, ms_aScreenStrs[i].y, gUString); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::PrintString(ms_aScreenStrs[i].x+1, ms_aScreenStrs[i].y+1, gUString); +*/ + ObrsPrintfString(ms_aScreenStrs[i].str, ms_aScreenStrs[i].x, ms_aScreenStrs[i].y); + } + CFont::DrawFonts(); + + ms_nScreenStrs = 0; +} + +void +CDebug::PrintAt(const char *str, int x, int y) +{ + if(ms_nScreenStrs >= MAX_SCREEN_STRS) + return; + strncpy(ms_aScreenStrs[ms_nScreenStrs].str, str, 256); + ms_aScreenStrs[ms_nScreenStrs].x = x;//*12; + ms_aScreenStrs[ms_nScreenStrs].y = y;//*22; + ms_nScreenStrs++; +} + +CDebug::Line CDebug::ms_aLines[MAX_DEBUG_LINES]; +int CDebug::ms_nLines; + +void +CDebug::AddLine(CVector p1, CVector p2, uint32 c1, uint32 c2) +{ + if(ms_nLines >= MAX_DEBUG_LINES) + return; + ms_aLines[ms_nLines].p1 = p1; + ms_aLines[ms_nLines].p2 = p2; + ms_aLines[ms_nLines].c1 = c1; + ms_aLines[ms_nLines].c2 = c2; + ms_nLines++; +} + +void +CDebug::DrawLines(void) +{ + int i; + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + for(i = 0; i < ms_nLines; i++){ + Line *l = &ms_aLines[i]; + CLines::RenderLineWithClipping(l->p1.x, l->p1.y, l->p1.z, l->p2.x, l->p2.y, l->p2.z, l->c1, l->c2); + } + ms_nLines = 0; +} diff --git a/src/core/Debug.h b/src/core/Debug.h new file mode 100644 index 0000000..4a25bf4 --- /dev/null +++ b/src/core/Debug.h @@ -0,0 +1,45 @@ +#pragma once + +class CDebug +{ + enum + { + MAX_LINES = 15, + MAX_STR_LEN = 80, + + MAX_SCREEN_STRS = 100, + MAX_DEBUG_LINES = 100, + }; + + static int16 ms_nCurrentTextLine; + static char ms_aTextBuffer[MAX_LINES][MAX_STR_LEN]; + + // custom + struct ScreenStr { + int x, y; + char str[256]; + }; + static ScreenStr ms_aScreenStrs[MAX_SCREEN_STRS]; + static int ms_nScreenStrs; + + struct Line { + CVector p1, p2; + uint32 c1, c2; + }; + static Line ms_aLines[MAX_DEBUG_LINES]; + static int ms_nLines; + +public: + static void DebugInitTextBuffer(); + static void DebugDisplayTextBuffer(); + static void DebugAddText(const char *str); + + // custom + static void PrintAt(const char *str, int x, int y); + static void DisplayScreenStrings(); + + static void AddLine(CVector p1, CVector p2, uint32 c1, uint32 c2); + static void DrawLines(void); +}; + +extern bool gbDebugStuffInRelease; diff --git a/src/core/Directory.cpp b/src/core/Directory.cpp new file mode 100644 index 0000000..0534406 --- /dev/null +++ b/src/core/Directory.cpp @@ -0,0 +1,74 @@ +#include "common.h" + +#include "General.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; + size_t 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); +#ifdef FIX_BUGS + // don't add if already exists + uint32 offset, size; + if(FindItem(dirinfo.name, offset, size)) + return; +#endif + entries[numEntries++] = dirinfo; +} + +void +CDirectory::AddItem(const DirectoryInfo &dirinfo, int32 imgId) +{ + DirectoryInfo di = dirinfo; + di.offset |= imgId<<24; + AddItem(di); +} + +bool +CDirectory::FindItem(const char *name, uint32 &offset, uint32 &size) +{ + int i; + + for(i = 0; i < numEntries; i++) + if(!CGeneral::faststricmp(entries[i].name, name)){ + offset = entries[i].offset; + size = entries[i].size; + return true; + } + return false; +} diff --git a/src/core/Directory.h b/src/core/Directory.h new file mode 100644 index 0000000..0fef080 --- /dev/null +++ b/src/core/Directory.h @@ -0,0 +1,23 @@ +#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); + void AddItem(const DirectoryInfo &dirinfo, int32 imgId); + bool FindItem(const char *name, uint32 &offset, uint32 &size); +}; diff --git a/src/core/EventList.cpp b/src/core/EventList.cpp new file mode 100644 index 0000000..93f72d4 --- /dev/null +++ b/src/core/EventList.cpp @@ -0,0 +1,237 @@ +#include "common.h" + +#include "Pools.h" +#include "ModelIndices.h" +#include "World.h" +#include "Wanted.h" +#include "EventList.h" +#include "Messages.h" +#include "Text.h" +#include "main.h" +#include "Accident.h" + +int32 CEventList::ms_nFirstFreeSlotIndex; +CEvent gaEvent[NUMEVENTS]; + +enum +{ + EVENT_STATE_0, + EVENT_STATE_CANDELETE, + EVENT_STATE_CLEAR, +}; + +void +CEventList::Initialise(void) +{ + int i; + + debug("Initialising CEventList..."); + for(i = 0; i < NUMEVENTS; i++){ + gaEvent[i].type = EVENT_NULL; + gaEvent[i].entityType = EVENT_ENTITY_NONE; + gaEvent[i].entityRef = 0; + gaEvent[i].posn.x = 0.0f; + gaEvent[i].posn.y = 0.0f; + gaEvent[i].posn.z = 0.0f; + gaEvent[i].timeout = 0; + gaEvent[i].state = EVENT_STATE_0; + } + ms_nFirstFreeSlotIndex = 0; +} + +void +CEventList::Update(void) +{ + int i; + + ms_nFirstFreeSlotIndex = 0; + for(i = 0; i < NUMEVENTS; i++){ + if(gaEvent[i].type == EVENT_NULL) + continue; + if(CTimer::GetTimeInMilliseconds() > gaEvent[i].timeout || gaEvent[i].state == EVENT_STATE_CANDELETE){ + gaEvent[i].type = EVENT_NULL; + gaEvent[i].state = EVENT_STATE_0; + } + if(gaEvent[i].state == EVENT_STATE_CLEAR) + gaEvent[i].state = EVENT_STATE_CANDELETE; + } +} + +void +CEventList::RegisterEvent(eEventType type, eEventEntity entityType, CEntity *ent, CPed *criminal, int32 timeout) +{ + int i; + int ref; + bool copsDontCare; + +#ifdef SQUEEZE_PERFORMANCE + if (type == EVENT_INJURED_PED) { + gAccidentManager.ReportAccident((CPed*)ent); + return; + } +#endif + + copsDontCare = false; + switch(entityType){ + case EVENT_ENTITY_PED: + ref = CPools::GetPedRef((CPed*)ent); + if(ent->GetModelIndex() >= MI_GANG01 && ent->GetModelIndex() <= MI_CRIMINAL02) + copsDontCare = true; + break; + case EVENT_ENTITY_VEHICLE: + ref = CPools::GetVehicleRef((CVehicle*)ent); + break; + case EVENT_ENTITY_OBJECT: + ref = CPools::GetObjectRef((CObject*)ent); + break; + default: + Error("Undefined entity type, RegisterEvent, EventList.cpp"); + ref = 0; + break; + } + + // only update time if event exists already + for(i = 0; i < NUMEVENTS; i++) + if(gaEvent[i].type == type && + gaEvent[i].entityType == entityType && + gaEvent[i].entityRef == ref){ + gaEvent[i].timeout = CTimer::GetTimeInMilliseconds() + timeout; + return; + } + + for(i = ms_nFirstFreeSlotIndex; i < NUMEVENTS; i++) + if(gaEvent[i].type == EVENT_NULL){ + ms_nFirstFreeSlotIndex = i; + break; + } + if(i < NUMEVENTS){ + gaEvent[i].type = type; + gaEvent[i].entityType = entityType; + gaEvent[i].timeout = CTimer::GetTimeInMilliseconds() + timeout; + gaEvent[i].entityRef = ref; + gaEvent[i].posn = ent->GetPosition(); + gaEvent[i].criminal = criminal; + if(gaEvent[i].criminal) + gaEvent[i].criminal->RegisterReference((CEntity**)&gaEvent[i].criminal); + if(type == EVENT_GUNSHOT) + gaEvent[i].state = EVENT_STATE_CLEAR; + else + gaEvent[i].state = EVENT_STATE_0; + } + + if(criminal == FindPlayerPed()) + ReportCrimeForEvent(type, (intptr)ent, copsDontCare); +} + +void +CEventList::RegisterEvent(eEventType type, CVector posn, int32 timeout) +{ + int i; + + // only update time if event exists already + for(i = 0; i < NUMEVENTS; i++) + if(gaEvent[i].type == type && + gaEvent[i].posn.x == posn.x && + gaEvent[i].posn.y == posn.y && + gaEvent[i].posn.z == posn.z && + gaEvent[i].entityType == EVENT_ENTITY_NONE){ + gaEvent[i].timeout = CTimer::GetTimeInMilliseconds() + timeout; + return; + } + + for(i = ms_nFirstFreeSlotIndex; i < NUMEVENTS; i++) + if(gaEvent[i].type == EVENT_NULL){ + ms_nFirstFreeSlotIndex = i; + break; + } + if(i < NUMEVENTS){ + gaEvent[i].type = type; + gaEvent[i].entityType = EVENT_ENTITY_NONE; + gaEvent[i].timeout = CTimer::GetTimeInMilliseconds() + timeout; + gaEvent[i].posn = posn; + gaEvent[i].entityRef = 0; + if(type == EVENT_GUNSHOT) + gaEvent[i].state = EVENT_STATE_CLEAR; + else + gaEvent[i].state = EVENT_STATE_0; + } +} + +bool +CEventList::GetEvent(eEventType type, int32 *event) +{ + int i; + for(i = 0; i < NUMEVENTS; i++) + if(gaEvent[i].type == type){ + *event = i; + return true; + } + return false; +} + +void +CEventList::ClearEvent(int32 event) +{ + if(gaEvent[event].state != EVENT_STATE_CANDELETE) + gaEvent[event].state = EVENT_STATE_CLEAR; +} + +bool +CEventList::FindClosestEvent(eEventType type, CVector posn, int32 *event) +{ + int i; + float dist; + bool found = false; + float mindist = 60.0f; + + for(i = 0; i < NUMEVENTS; i++){ + if(gaEvent[i].type != type) + continue; + dist = (posn - gaEvent[i].posn).Magnitude(); + if(dist < mindist){ + mindist = dist; + found = true; + *event = i; + } + } + return found; +} + +void +CEventList::ReportCrimeForEvent(eEventType type, intptr crimeId, bool copsDontCare) +{ + eCrimeType crime; + switch(type){ + case EVENT_ASSAULT: crime = CRIME_HIT_PED; break; + case EVENT_RUN_REDLIGHT: crime = CRIME_RUN_REDLIGHT; break; + case EVENT_ASSAULT_POLICE: crime = CRIME_HIT_COP; break; + case EVENT_GUNSHOT: crime = CRIME_POSSESSION_GUN; break; + case EVENT_STEAL_CAR: crime = CRIME_STEAL_CAR; break; + case EVENT_HIT_AND_RUN: crime = CRIME_RUNOVER_PED; break; + case EVENT_HIT_AND_RUN_COP: crime = CRIME_RUNOVER_COP; break; + case EVENT_SHOOT_PED: crime = CRIME_SHOOT_PED; break; + case EVENT_SHOOT_COP: crime = CRIME_SHOOT_COP; break; + case EVENT_PED_SET_ON_FIRE: crime = CRIME_PED_BURNED; break; + case EVENT_COP_SET_ON_FIRE: crime = CRIME_COP_BURNED; break; + case EVENT_CAR_SET_ON_FIRE: crime = CRIME_VEHICLE_BURNED; break; + default: crime = CRIME_NONE; break; + } + + if(crime == CRIME_NONE) + return; + + CVector playerPedCoors = FindPlayerPed()->GetPosition(); + CVector playerCoors = FindPlayerCoors(); + + if(CWanted::WorkOutPolicePresence(playerCoors, 14.0f) != 0){ + FindPlayerPed()->m_pWanted->RegisterCrime_Immediately(crime, playerPedCoors, crimeId, copsDontCare); + FindPlayerPed()->m_pWanted->SetWantedLevelNoDrop(1); + }else + FindPlayerPed()->m_pWanted->RegisterCrime(crime, playerPedCoors, crimeId, copsDontCare); + + if(type == EVENT_ASSAULT_POLICE) + FindPlayerPed()->SetWantedLevelNoDrop(1); + if(type == EVENT_SHOOT_COP) + FindPlayerPed()->SetWantedLevelNoDrop(2); + +} diff --git a/src/core/EventList.h b/src/core/EventList.h new file mode 100644 index 0000000..4ced3a8 --- /dev/null +++ b/src/core/EventList.h @@ -0,0 +1,65 @@ +#pragma once + +class CEntity; +class CPed; + +enum eEventType +{ + EVENT_NULL, + EVENT_ASSAULT, + EVENT_RUN_REDLIGHT, + EVENT_ASSAULT_POLICE, + EVENT_GUNSHOT, + EVENT_INJURED_PED, + EVENT_DEAD_PED, + EVENT_FIRE, + EVENT_STEAL_CAR, + EVENT_HIT_AND_RUN, + EVENT_HIT_AND_RUN_COP, + EVENT_SHOOT_PED, + EVENT_SHOOT_COP, + EVENT_EXPLOSION, + EVENT_PED_SET_ON_FIRE, + EVENT_COP_SET_ON_FIRE, + EVENT_CAR_SET_ON_FIRE, + EVENT_ASSAULT_NASTYWEAPON, // not sure + EVENT_ICECREAM, + EVENT_ATM, + EVENT_SHOPSTALL, // used on graffitis + EVENT_LAST_EVENT +}; + +enum eEventEntity +{ + EVENT_ENTITY_NONE, + EVENT_ENTITY_PED, + EVENT_ENTITY_VEHICLE, + EVENT_ENTITY_OBJECT +}; + +struct CEvent +{ + eEventType type; + eEventEntity entityType; + int32 entityRef; + CPed *criminal; + CVector posn; + uint32 timeout; + int32 state; +}; + +class CEventList +{ + static int32 ms_nFirstFreeSlotIndex; +public: + static void Initialise(void); + static void Update(void); + static void RegisterEvent(eEventType type, eEventEntity entityType, CEntity *ent, CPed *criminal, int32 timeout); + static void RegisterEvent(eEventType type, CVector posn, int32 timeout); + static bool GetEvent(eEventType type, int32 *event); + static void ClearEvent(int32 event); + static bool FindClosestEvent(eEventType type, CVector posn, int32 *event); + static void ReportCrimeForEvent(eEventType type, intptr, bool); +}; + +extern CEvent gaEvent[NUMEVENTS]; \ No newline at end of file diff --git a/src/core/FileLoader.cpp b/src/core/FileLoader.cpp new file mode 100644 index 0000000..afa2a66 --- /dev/null +++ b/src/core/FileLoader.cpp @@ -0,0 +1,1852 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "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 "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" +#include "MemoryHeap.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); + } +#if GTA_VERSION <= GTA3_PS2_160 + CFileMgr::ChangeDir("\\DATA\\"); + fd = CFileMgr::OpenFile(filename, "r"); + CFileMgr::ChangeDir("\\"); +#else + fd = CFileMgr::OpenFile(filename, "r"); +#endif + assert(fd > 0); + + for(line = LoadLine(fd); line; line = LoadLine(fd)){ + if(*line == '#') + continue; + +#ifdef FIX_BUGS + if(strncmp(line, "EXIT", 4) == 0) +#else + if(strncmp(line, "EXIT", 9) == 0) +#endif + break; + + if(strncmp(line, "IMAGEPATH", 9) == 0){ + RwImageSetPath(line + 10); + }else if(strncmp(line, "TEXDICTION", 10) == 0){ + PUSH_MEMID(MEMID_TEXTURES); + strcpy(txdname, line+11); + LoadingScreenLoadingFile(txdname); + RwTexDictionary *txd = LoadTexDictionary(txdname); + AddTexDictionaries(savedTxd, txd); + RwTexDictionaryDestroy(txd); + POP_MEMID(); + }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){ + PUSH_MEMID(MEMID_DEF_MODELS); + CModelInfo::ConstructMloClumps(); + POP_MEMID(); + CObjectData::Initialise("DATA\\OBJECT.DAT"); + objectsLoaded = true; + } + PUSH_MEMID(MEMID_WORLD); + LoadingScreenLoadingFile(line + 4); + LoadScene(line + 4); + POP_MEMID(); + }else if(strncmp(line, "MAPZONE", 7) == 0){ + LoadingScreenLoadingFile(line + 8); + LoadMapZones(line + 8); + }else if(strncmp(line, "SPLASH", 6) == 0){ +#ifndef DISABLE_LOADING_SCREEN + LoadSplash(GetRandomSplashScreen()); +#endif +#ifndef GTA_PS2 + }else if(strncmp(line, "CDIMAGE", 7) == 0){ + CdStreamAddImage(line + 8); +#endif + } + } + + 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; +} + +struct ColHeader +{ + uint32 ident; + uint32 size; +}; + +void +CFileLoader::LoadCollisionFile(const char *filename) +{ + int fd; + char modelname[24]; + CBaseModelInfo *mi; + ColHeader header; + + PUSH_MEMID(MEMID_COLLISION); + + debug("Loading collision file %s\n", filename); + fd = CFileMgr::OpenFile(filename, "rb"); + + while(CFileMgr::Read(fd, (char*)&header, sizeof(header))){ + assert(header.ident == 'LLOC'); + 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); + + POP_MEMID(); +} + +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)); + REGISTER_MEMPTR(&model.spheres); + 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)); + REGISTER_MEMPTR(&model.lines); + 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)); + REGISTER_MEMPTR(&model.boxes); + 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 = (CompressedVector*)RwMalloc(numVertices*sizeof(CompressedVector)); + REGISTER_MEMPTR(&model.vertices); + for(i = 0; i < numVertices; i++){ + model.vertices[i].Set(*(float*)buf, *(float*)(buf+4), *(float*)(buf+8)); + if(Abs(*(float*)buf) >= 256.0f || + Abs(*(float*)(buf+4)) >= 256.0f || + Abs(*(float*)(buf+8)) >= 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)); + REGISTER_MEMPTR(&model.triangles); + 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(RpAtomicGetFrame(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; +} + +#ifdef LIBRW +void +InitClump(RpClump *clump) +{ + RpClumpForAllAtomics(clump, ConvertPlatformAtomic, nil); +} +#else +#define InitClump(clump) +#endif + +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){ + InitClump(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); + if(mi){ + InitClump(clump); + assert(mi->IsClump()); + 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->GetModelType() == MITYPE_PED && id != 0 && RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)) { + // Read LOD ped + clump = RpClumpStreamRead(stream); + InitClump(clump); + 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)->GetModelName()); + 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)->GetModelName()); + clump = RpClumpGtaStreamRead2(stream); + + if(clump){ + InitClump(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; + InitClump(clump); + gpRelatedModelInfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + RpClumpForAllAtomics(clump, SetRelatedModelInfoCB, clump); + RpClumpDestroy(clump); + } + return true; +} + +#ifdef HARDCODED_MODEL_FLAGS +char *DoubleSidedNames[] = { + "chnabankdoor", + "Security_Hut", + "Hospital_Sub", + "phonebooth1", + "trafficlight1", + "sub_roadbarrier", + "redlightbuild09", + "doublestreetlght1", + "doc_shedbig31", + "com_land_128", + "garage7", + "proj_garage01", + "buildingground2", + "buildingground3", + "ch_roof_kb", + "overpassind", + "casino", + "ind_land100", + "fuckedup_skewlbus", + "Police_Station_ind", + "flagsitaly", + "sidebarrier_gaz1", + "bar_barrier12", + "bar_barrier10b", + "sidebarrier_gaz2", + "doc_shedbig3", + "doc_shedbig4", + "verticalift_bridge", + "verticalift_bridg2", + "usdcrdlrbuild01", + "apairporthanger", + "apairporthangerA", + "porthangerclosed", + "redlightbuild13", + "doc_rave", + "const_woodfence", + "const_woodfence2", + "const_woodfence3", + "subfraightback01", + "subfraightback02", + "subfraightback03", + "subfraightback04", + "subind_build03", + "chinabanner1", + "chinabanner2", + "chinabanner3", + "chinabanner4", + "Pumpfirescape", + "Pumphouse", + "amcounder", + "barrel1", + "barrel2", + "barrel3", + "barrel4", + "com_1way50", + "com_1way20", + "overpasscom01", + "overpasscom02", + "overpasscom03", + "overpasscom04", + "overpass_comse", + "newdockbuilding", + "newdockbuilding2", + "policeballhall", + "fuzballdoor", + "ind_land106", + "PoliceBallSigns", + "amcoudet", + "rustship_structure", + "impexpgrgesub", + "ind_land128", + "fshfctry_dstryd", + "railtrax_bentl", + "railtrax_lo4b", + "railtrax_straight", + "railtrax_bentrb", + "railtrax_skew", + "newtrackaaa", + "railtrax_skew5", + // these they forgot: + "railtrax_skewp", + "railtrax_ske2b", + "railtrax_strtshort", + "railtrax_2b", + "railtrax_straightss", + "railtrax_bentr", + "ind_land125", + "salvstrans", + "bridge_liftsec", + "subsign1", + "carparkfence", + "newairportwall4", + "apair_terminal", + "Helipad", + "bar_barrier10", + "damissionfence", + "sub_floodlite", + "suburbbridge1", + "damfencing", + "demfence08", + "damfence07", + "damfence06", + "damfence05", + "damfence04", + "damfence03", + "damfence02", + "damfence01", + "Dam_pod2", + "Dam_pod1", + "columansion_wall", + "wrckdhse020", + "wrckdhse01", + "arc_bridge", + "gRD_overpass19kbc", + "gRD_overpass19bkb", + "gRD_overpass19kb", + "gRD_overpass18kb", + "road_under", + "com_roadkb23", + "com_roadkb22", + "nbbridgerda", + "nbbridgerdb", + "policetenkb1", + "block3_scraper2", + "Clnm_cthdrlfcde", + "broadwaybuild", + "combillboard03", + "com_park3b", + "com_docksaa", + "newdockbuilding2", + "com_roadkb22", + "sidebarrier_gaz2", + "tunnelsupport1", + "skyscrpunbuilt2", + "cons_buid02", + "rail_platformw", + "railtrax_bent1", + "nrailstepswest", + "building_fucked", + "franksclb02", + "salvsdetail", + "crgoshp01", + "shp_wlkway", + "bar_barriergate1", + "plnt_pylon01", + "fishfctory", + "doc_crane_cab", + "nrailsteps", + "iten_club01", + "mak_Watertank", + "basketballcourt" + "carlift01", + "carlift02", + "iten_chinatown4", + "iten_details7", + "ind_customroad002" + "ind_brgrd1way", + "ind_customroad060", + "ind_customroad002", + "ind_land108", + "ind_customroad004", + "ind_customroad003", + "nbbridgcabls01", + "sbwy_tunl_bit", + "sbwy_tunl_bend", + "sbwy_tunl_cstm11", + "sbwy_tunl_cstm10", + "sbwy_tunl_cstm9", + "sbwy_tunl_cstm8", + "sbwy_tunl_cstm7", + "sbwy_tunl_cstm6", + "sbwy_tunl_cstm5", + "sbwy_tunl_cstm4", + "sbwy_tunl_cstm3", + "sbwy_tunl_cstm2", + "sbwy_tunl_cstm1", + "tenmnt6ad", + "" + +}; +char *TreeNames[] = { + "coast_treepatch", + "comparknewtrees", + "comtreepatchprk", + "condotree01", + "condotree1", + "indatree03", + "indtreepatch5", + "indtreepatch06f", + "new_carprktrees", + "new_carprktrees4", + "newcoasttrees1", + "newcoasttrees2", + "newcoasttrees3", + "newtreepatch_sub", + "newtrees1_sub", + "newunitrepatch", + "pinetree_narrow", + "pinetree_wide", + "treencom2", + "treepatch", + "treepatch01_sub", + "treepatch02_sub", + "treepatch2", + "treepatch2b", + "treepatch03", + "treepatch03_sub", + "treepatch04_sub", + "treepatch05_sub", + "treepatch06_sub", + "treepatch07_sub", + "treepatch08_sub", + "treepatch09_sub", + "treepatch10_sub", + "treepatch11_sub", + "treepatch12_sub", + "treepatch13_sub", + "treepatch14_sub", + "treepatch15_sub", + "treepatch16_sub", + "treepatch17_sub", + "treepatch18_sub", + "treepatch19_sub", + "treepatch20_sub", + "treepatch21_sub", + "treepatch22_sub", + "treepatch23_sub", + "treepatch24_sub", + "treepatch25_sub", + "treepatch26_sub", + "treepatch27_sub", + "treepatch28_sub", + "treepatch29_sub", + "treepatch30_sub", + "treepatch31_sub", + "treepatch32_sub", + "treepatch33_sub", + "treepatch34_sub", + "treepatch35_sub", + "treepatch69", + "treepatch152_sub", + "treepatch153_sub", + "treepatch171_sub", + "treepatch172_sub", + "treepatch173_sub", + "treepatch212_sub", + "treepatch213_sub", + "treepatch214_sub", + "treepatcha", + "treepatchb", + "treepatchcomtop1", + "treepatchd", + "treepatche", + "treepatchh", + "treepatchindaa2", + "treepatchindnew", + "treepatchindnew2", + "treepatchk", + "treepatchkb4", + "treepatchkb5", + "treepatchkb6", + "treepatchkb7", + "treepatchkb9", + "treepatchl", + "treepatchm", + "treepatchnew_sub", + "treepatchttwrs", + "treesuni1", + "trepatchindaa1", + "veg_bush2", + "veg_bush14", + "veg_tree1", + "veg_tree3", + "veg_treea1", + "veg_treea3", + "veg_treeb1", + "veg_treenew01", + "veg_treenew03", + "veg_treenew05", + "veg_treenew06", + "veg_treenew08", + "veg_treenew09", + "veg_treenew10", + "veg_treenew16", + "veg_treenew17", + "vegclubtree01", + "vegclubtree02", + "vegclubtree03", + "vegpathtree", + "" +}; +char *OptimizedNames[] = { + "coast_treepatch", + "comparknewtrees", + "comtreepatchprk", + "indtreepatch5", + "indtreepatch06f", + "new_carprktrees", + "new_carprktrees4", + "newcoasttrees1", + "newcoasttrees2", + "newcoasttrees3", + "newtreepatch_sub", + "newtrees1_sub", + "newunitrepatch", + "treepatch", + "treepatch01_sub", + "treepatch02_sub", + "treepatch2", + "treepatch2b", + "treepatch03", + "treepatch03_sub", + "treepatch04_sub", + "treepatch05_sub", + "treepatch06_sub", + "treepatch07_sub", + "treepatch08_sub", + "treepatch09_sub", + "treepatch10_sub", + "treepatch11_sub", + "treepatch12_sub", + "treepatch13_sub", + "treepatch14_sub", + "treepatch15_sub", + "treepatch16_sub", + "treepatch17_sub", + "treepatch18_sub", + "treepatch19_sub", + "treepatch20_sub", + "treepatch21_sub", + "treepatch22_sub", + "treepatch23_sub", + "treepatch24_sub", + "treepatch25_sub", + "treepatch26_sub", + "treepatch27_sub", + "treepatch28_sub", + "treepatch29_sub", + "treepatch30_sub", + "treepatch31_sub", + "treepatch32_sub", + "treepatch33_sub", + "treepatch34_sub", + "treepatch35_sub", + "treepatch69", + "treepatch152_sub", + "treepatch153_sub", + "treepatch171_sub", + "treepatch172_sub", + "treepatch173_sub", + "treepatch212_sub", + "treepatch213_sub", + "treepatch214_sub", + "treepatcha", + "treepatchb", + "treepatchcomtop1", + "treepatchd", + "treepatche", + "treepatchh", + "treepatchindaa2", + "treepatchindnew", + "treepatchindnew2", + "treepatchk", + "treepatchkb4", + "treepatchkb5", + "treepatchkb6", + "treepatchkb7", + "treepatchkb9", + "treepatchl", + "treepatchm", + "treepatchnew_sub", + "treepatchttwrs", + "treesuni1", + "trepatchindaa1", + "combtm_treeshad01", + "combtm_treeshad02", + "combtm_treeshad03", + "combtm_treeshad04", + "combtm_treeshad05", + "combtm_treeshad06", + "comtop_tshad", + "comtop_tshad2", + "comtop_tshad3", + "comtop_tshad4", + "comtop_tshad5", + "comtop_tshad6", + "se_treeshad01", + "se_treeshad02", + "se_treeshad03", + "se_treeshad04", + "se_treeshad05", + "se_treeshad06", + "treeshads01", + "treeshads02", + "treeshads03", + "treeshads04", + "treeshads05", + "" +}; +// not from mobile +static bool +MatchModelName(char *name, char **list) +{ + int i; + char *s; + for(i = 0; *list[i] != '\0'; i++) + if(strncmp(name, "LOD", 3) == 0){ + if(!CGeneral::faststricmp(name+3, list[i]+3)) + return true; + }else{ + if(!CGeneral::faststricmp(name, list[i])) + return true; + } + return false; +} +#endif + +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); + if(clump) + InitClump(clump); + 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); +} + +#define isLine3(l, a, b, c) ((l[0] == a) && (l[1] == b) && (l[2] == c)) +#define isLine4(l, a, b, c, d) ((l[0] == a) && (l[1] == b) && (l[2] == c) && (l[3] == d)) + +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(isLine4(line, 'o','b','j','s')) section = OBJS; + else if(isLine4(line, 't','o','b','j')) section = TOBJ; + else if(isLine4(line, 'h','i','e','r')) section = HIER; + else if(isLine4(line, 'c','a','r','s')) section = CARS; + else if(isLine4(line, 'p','e','d','s')) section = PEDS; + else if(isLine4(line, 'p','a','t','h')) section = PATH; + else if(isLine4(line, '2','d','f','x')) section = TWODFX; + }else if(isLine3(line, 'e','n','d')){ + section = section == MLO ? OBJS : NONE; + }else switch(section){ + case OBJS: + if(isLine3(line, 's','t','a')) + mlo = LoadMLO(line); + else + LoadObject(line); + break; + case MLO: + LoadMLOInstance(mlo, line); + 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(strcmp(pathTypeStr, "ped") == 0) + pathType = 1; + else if(strcmp(pathTypeStr, "car") == 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); +#ifdef EXTRA_MODEL_FLAGS + // same flag values as SA + mi->m_bIsTree = !!(flags & 0x2000); + mi->m_bIsDoubleSided = !!(flags & 0x200000); + // new value otherwise unused + mi->m_bCanBeIgnored = !!(flags & 0x10000); + +#ifdef HARDCODED_MODEL_FLAGS + // mobile sets these flags in CFileLoader::SetRelatedModelInfoCB, but that's stupid + if(MatchModelName(mi->GetModelName(), DoubleSidedNames)) mi->m_bIsDoubleSided = true; + if(MatchModelName(mi->GetModelName(), TreeNames)) mi->m_bIsTree = true; + if(MatchModelName(mi->GetModelName(), OptimizedNames)) mi->m_bCanBeIgnored = true; +#endif + +#endif +} + +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->SetModelName(model); + mi->SetNumAtomics(numObjs); + mi->SetLodDistances(dist); + SetModelInfoFlags(mi, flags); + mi->m_firstDamaged = damaged; + mi->SetTexDictionary(txd); + MatchModelString(model, id); +} + +int +CFileLoader::LoadMLO(const char *line) +{ + char smth[8]; + char name[24]; + int modelIndex; + float drawDist; + + sscanf(line, "%s %s %d %f", smth, name, &modelIndex, &drawDist); + CMloModelInfo *minfo = CModelInfo::AddMloModel(modelIndex); + minfo->SetModelName(name); + minfo->drawDist = drawDist; + int instId = CModelInfo::GetMloInstanceStore().allocPtr; + minfo->firstInstance = instId; + minfo->lastInstance = instId; + minfo->SetTexDictionary("generic"); + return modelIndex; +} + +void +CFileLoader::LoadMLOInstance(int id, const char *line) +{ + char name[24]; + RwV3d pos, scale, rot; + float angle; + int modelIndex; + + CMloModelInfo *minfo = (CMloModelInfo*)CModelInfo::GetModelInfo(id); + sscanf(line, "%d %s %f %f %f %f %f %f %f %f %f %f", + &modelIndex, + name, + &pos.x, &pos.y, &pos.z, + &scale.x, &scale.y, &scale.z, + &rot.x, &rot.y, &rot.z, + &angle); + float rad = Acos(angle) * 2.0f; + CInstance *inst = CModelInfo::GetMloInstanceStore().Alloc(); + minfo->lastInstance++; + + RwMatrix *matrix = RwMatrixCreate(); + RwMatrixScale(matrix, &scale, rwCOMBINEREPLACE); + RwMatrixRotate(matrix, &rot, -RADTODEG(rad), rwCOMBINEPOSTCONCAT); + RwMatrixTranslate(matrix, &pos, rwCOMBINEPOSTCONCAT); + + inst->GetMatrix() = CMatrix(matrix); + inst->GetMatrix().UpdateRW(); + + inst->m_modelIndex = modelIndex; + RwMatrixDestroy(matrix); +} + +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->SetModelName(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->SetModelName(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->SetModelName(model); + mi->SetTexDictionary(txd); + for(p = gamename; *p; p++) + if(*p == '_') *p = ' '; + strcpy(mi->m_gameName, gamename); + mi->m_level = level; + mi->m_compRules = comprules; + + if(strcmp(type, "car") == 0){ + mi->m_wheelId = misc; + mi->m_wheelScale = wheelScale; + mi->m_vehicleType = VEHICLE_TYPE_CAR; + }else if(strcmp(type, "boat") == 0){ + mi->m_vehicleType = VEHICLE_TYPE_BOAT; + }else if(strcmp(type, "train") == 0){ + mi->m_vehicleType = VEHICLE_TYPE_TRAIN; + }else if(strcmp(type, "heli") == 0){ + mi->m_vehicleType = VEHICLE_TYPE_HELI; + }else if(strcmp(type, "plane") == 0){ + mi->m_planeLodId = misc; + mi->m_wheelScale = 1.0f; + mi->m_vehicleType = VEHICLE_TYPE_PLANE; + }else if(strcmp(type, "bike") == 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(strcmp(vehclass, "poorfamily") == 0){ + mi->m_vehicleClass = CCarCtrl::POOR; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::POOR); + }else if(strcmp(vehclass, "richfamily") == 0){ + mi->m_vehicleClass = CCarCtrl::RICH; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::RICH); + }else if(strcmp(vehclass, "executive") == 0){ + mi->m_vehicleClass = CCarCtrl::EXEC; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::EXEC); + }else if(strcmp(vehclass, "worker") == 0){ + mi->m_vehicleClass = CCarCtrl::WORKER; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::WORKER); + }else if(strcmp(vehclass, "special") == 0){ + mi->m_vehicleClass = CCarCtrl::SPECIAL; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::SPECIAL); + }else if(strcmp(vehclass, "big") == 0){ + mi->m_vehicleClass = CCarCtrl::BIG; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::BIG); + }else if(strcmp(vehclass, "taxi") == 0){ + mi->m_vehicleClass = CCarCtrl::TAXI; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::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->SetModelName(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; + mi->m_carsCanDrive = carsCanDrive; + + // ??? + 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.shadowSize, + &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; + + if(flags & LIGHTFLAG_FOG_ALWAYS) + flags &= ~LIGHTFLAG_FOG_NORMAL; + 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.type = flags; +#ifdef FIX_BUGS + effect->attractor.probability = Clamp(probability, 0, 255); +#else + effect->attractor.probability = probability; +#endif + 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(isLine4(line, 'i','n','s','t')) section = INST; + else if(isLine4(line, 'z','o','n','e')) section = ZONE; + else if(isLine4(line, 'c','u','l','l')) section = CULL; + else if(isLine4(line, 'p','i','c','k')) section = PICK; + else if(isLine4(line, 'p','a','t','h')) section = PATH; + }else if(isLine3(line, 'e','n','d')){ + 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); + strcmp(pathTypeStr, "ped"); + // 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(isLine4(line, 'z','o','n','e')) section = ZONE; + }else if(isLine3(line, 'e','n','d')){ + 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"); +} + +void +CFileLoader::ReloadPaths(const char *filename) +{ + enum { + NONE, + PATH, + }; + char *line; + int section = NONE; + int id, pathType, pathIndex = -1; + char pathTypeStr[20]; + debug("Reloading paths from %s...\n", filename); + + int fd = CFileMgr::OpenFile(filename, "r"); + for (line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)) { + if (*line == '\0' || *line == '#') + continue; + + if (section == NONE) { + if (isLine4(line, 'p','a','t','h')) { + section = PATH; + ThePaths.AllocatePathFindInfoMem(4500); + } + } else if (isLine3(line, 'e','n','d')) { + section = NONE; + } else { + switch (section) { + case PATH: + if (pathIndex == -1) { + id = LoadPathHeader(line, pathTypeStr); + if (strcmp(pathTypeStr, "ped") == 0) + pathType = 1; + else if (strcmp(pathTypeStr, "car") == 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; + default: + break; + } + } + } + CFileMgr::CloseFile(fd); +} + +void +CFileLoader::ReloadObjectTypes(const char *filename) +{ + enum { + NONE, + OBJS, + TOBJ, + TWODFX + }; + char *line; + int section = NONE; + CModelInfo::ReInit2dEffects(); + debug("Reloading object types from %s...\n", filename); + + CFileMgr::ChangeDir("\\DATA\\MAPS\\"); + int fd = CFileMgr::OpenFile(filename, "r"); + CFileMgr::ChangeDir("\\"); + for (line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)) { + if (*line == '\0' || *line == '#') + continue; + + if (section == NONE) { + if (isLine4(line, 'o','b','j','s')) section = OBJS; + else if (isLine4(line, 't','o','b','j')) section = TOBJ; + else if (isLine4(line, '2','d','f','x')) section = TWODFX; + } else if (isLine3(line, 'e','n','d')) { + section = NONE; + } else { + switch (section) { + case OBJS: + case TOBJ: + ReloadObject(line); + break; + case TWODFX: + Load2dEffect(line); + break; + default: + break; + } + } + } + CFileMgr::CloseFile(fd); +} + +void +CFileLoader::ReloadObject(const char *line) +{ + int id, numObjs; + char model[24], txd[24]; + float dist[3]; + uint32 flags; + 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); + break; + case 2: + sscanf(line, "%d %s %s %d %f %f %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &flags); + break; + case 3: + sscanf(line, "%d %s %s %d %f %f %f %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &dist[2], &flags); + break; + } + + mi = (CSimpleModelInfo*) CModelInfo::GetModelInfo(id); + if ( +#ifdef FIX_BUGS + mi && +#endif + mi->GetModelType() == MITYPE_SIMPLE && !strcmp(mi->GetModelName(), model) && mi->m_numAtomics == numObjs) { + mi->SetLodDistances(dist); + SetModelInfoFlags(mi, flags); + } else { + printf("Can't reload %s\n", model); + } +} + +// unused mobile function - crashes +void +CFileLoader::ReLoadScene(const char *filename) +{ + char *line; + CFileMgr::ChangeDir("\\DATA\\"); + int fd = CFileMgr::OpenFile(filename, "r"); + CFileMgr::ChangeDir("\\"); + + for (line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)) { + if (*line == '#') + continue; + +#ifdef FIX_BUGS + if (strncmp(line, "EXIT", 4) == 0) +#else + if (strncmp(line, "EXIT", 9) == 0) +#endif + break; + + if (strncmp(line, "IDE", 3) == 0) { + LoadObjectTypes(line + 4); + } + } + CFileMgr::CloseFile(fd); +} diff --git a/src/core/FileLoader.h b/src/core/FileLoader.h new file mode 100644 index 0000000..87b8fe6 --- /dev/null +++ b/src/core/FileLoader.h @@ -0,0 +1,49 @@ +#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, struct 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 int LoadMLO(const char *line); + static void LoadMLOInstance(int id, 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); + + static void ReloadPaths(const char *filename); + static void ReloadObjectTypes(const char *filename); + static void ReloadObject(const char *line); + static void ReLoadScene(const char *filename); // unused mobile function +}; diff --git a/src/core/FileMgr.cpp b/src/core/FileMgr.cpp new file mode 100644 index 0000000..32aa404 --- /dev/null +++ b/src/core/FileMgr.cpp @@ -0,0 +1,313 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#ifdef _WIN32 +#include +#endif +#include "common.h" +#include "crossplatform.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]; + + +#if !defined(_WIN32) +#include +#include +#include +#define _getcwd getcwd + +// Case-insensitivity on linux (from https://github.com/OneSadCookie/fcaseopen) +void mychdir(char const *path) +{ + char* r = casepath(path, false); + if (r) { + chdir(r); + free(r); + } else { + errno = ENOENT; + } +} +#else +#define mychdir chdir +#endif + +/* 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 = fcaseopen(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 size_t +myfread(void *buf, size_t elt, size_t n, int fd) +{ + if(myfiles[fd].isText){ + unsigned char *p; + size_t i; + int c; + + n *= elt; + p = (unsigned char*)buf; + for(i = 0; i < n; i++){ + c = myfgetc(fd); + if(c == EOF) + break; + *p++ = (unsigned char)c; + } + return i / elt; + } + return fread(buf, elt, n, myfiles[fd].file); +} + +static size_t +myfwrite(void *buf, size_t elt, size_t n, int fd) +{ + if(myfiles[fd].isText){ + unsigned char *p; + size_t i; + int c; + + n *= elt; + p = (unsigned 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[128] = {'\0'}; +char CFileMgr::ms_dirName[128]; + +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, "\\"); + } + mychdir(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, "\\"); + } + mychdir(ms_dirName); +} + +void +CFileMgr::SetDirMyDocuments(void) +{ + SetDir(""); // better start at the root if user directory is relative + mychdir(_psGetUserFilesFolder()); +} + +ssize_t +CFileMgr::LoadFile(const char *file, uint8 *buf, int maxlen, const char *mode) +{ + int fd; + ssize_t n, len; + + fd = myfopen(file, mode); + if(fd == 0) + return -1; + len = 0; + do{ + n = myfread(buf + len, 1, 0x4000, fd); +#ifndef FIX_BUGS + if (n < 0) + return -1; +#endif + len += n; + assert(len < maxlen); + }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"); +} + +size_t +CFileMgr::Read(int fd, const char *buf, ssize_t len) +{ + return myfread((void*)buf, 1, len, fd); +} + +size_t +CFileMgr::Write(int fd, const char *buf, ssize_t len) +{ + return myfwrite((void*)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); +} diff --git a/src/core/FileMgr.h b/src/core/FileMgr.h new file mode 100644 index 0000000..f70451b --- /dev/null +++ b/src/core/FileMgr.h @@ -0,0 +1,23 @@ +#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 ssize_t LoadFile(const char *file, uint8 *buf, int maxlen, const char *mode); + static int OpenFile(const char *file, const char *mode); + static int OpenFile(const char *file) { return OpenFile(file, "rb"); } + static int OpenFileForWriting(const char *file); + static size_t Read(int fd, const char *buf, ssize_t len); + static size_t Write(int fd, const char *buf, ssize_t 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); + static char *GetRootDirName() { return ms_rootDirName; } +}; diff --git a/src/core/Fire.cpp b/src/core/Fire.cpp new file mode 100644 index 0000000..8b18462 --- /dev/null +++ b/src/core/Fire.cpp @@ -0,0 +1,440 @@ +#include "common.h" + +#include "Vector.h" +#include "PlayerPed.h" +#include "Entity.h" +#include "PointLights.h" +#include "Particle.h" +#include "Timer.h" +#include "Vehicle.h" +#include "Shadows.h" +#include "Automobile.h" +#include "World.h" +#include "General.h" +#include "EventList.h" +#include "DamageManager.h" +#include "Ped.h" +#include "Fire.h" + +CFireManager gFireManager; + +CFire::CFire() +{ + m_bIsOngoing = false; + m_bIsScriptFire = false; + m_bPropagationFlag = true; + m_bAudioSet = true; + m_vecPos = CVector(0.0f, 0.0f, 0.0f); + m_pEntity = nil; + m_pSource = nil; + m_nFiremenPuttingOut = 0; + m_nExtinguishTime = 0; + m_nStartTime = 0; + field_20 = 1; + m_nNextTimeToAddFlames = 0; + m_fStrength = 0.8f; +} + +CFire::~CFire() {} + +void +CFire::ProcessFire(void) +{ + float fDamagePlayer; + float fDamagePeds; + float fDamageVehicle; + int16 nRandNumber; + float fGreen; + float fRed; + CVector lightpos; + CVector firePos; + CPed *ped = (CPed *)m_pEntity; + CVehicle *veh = (CVehicle*)m_pEntity; + + if (m_pEntity) { + m_vecPos = m_pEntity->GetPosition(); + + if (((CPed *)m_pEntity)->IsPed()) { + if (ped->m_pFire != this) { + Extinguish(); + return; + } + if (ped->m_nMoveState != PEDMOVE_RUN) + m_vecPos.z -= 1.0f; + if (ped->bInVehicle && ped->m_pMyVehicle) { + if (ped->m_pMyVehicle->IsCar()) + ped->m_pMyVehicle->m_fHealth = 75.0f; + } else if (m_pEntity == (CPed *)FindPlayerPed()) { + fDamagePlayer = 1.2f * CTimer::GetTimeStep(); + + ((CPlayerPed *)m_pEntity)->InflictDamage( + (CPlayerPed *)m_pSource, WEAPONTYPE_FLAMETHROWER, + fDamagePlayer, PEDPIECE_TORSO, 0); + } else { + fDamagePeds = 1.2f * CTimer::GetTimeStep(); + + if (((CPlayerPed *)m_pEntity)->InflictDamage( + (CPlayerPed *)m_pSource, WEAPONTYPE_FLAMETHROWER, + fDamagePeds, PEDPIECE_TORSO, 0)) { + m_pEntity->bRenderScorched = true; + } + } + } else if (m_pEntity->IsVehicle()) { + if (veh->m_pCarFire != this) { + Extinguish(); + return; + } + if (!m_bIsScriptFire) { + fDamageVehicle = 1.2f * CTimer::GetTimeStep(); + veh->InflictDamage((CVehicle *)m_pSource, WEAPONTYPE_FLAMETHROWER, fDamageVehicle); + } + } + } + if (!FindPlayerVehicle() && +#ifdef FIX_BUGS + FindPlayerPed() && +#endif + !FindPlayerPed()->m_pFire && !(FindPlayerPed()->bFireProof) + && ((FindPlayerPed()->GetPosition() - m_vecPos).MagnitudeSqr() < 2.0f)) { + FindPlayerPed()->DoStuffToGoOnFire(); + gFireManager.StartFire(FindPlayerPed(), m_pSource, 0.8f, 1); + } + if (CTimer::GetTimeInMilliseconds() > m_nNextTimeToAddFlames) { + m_nNextTimeToAddFlames = CTimer::GetTimeInMilliseconds() + 80; + firePos = m_vecPos; + + if (veh && veh->IsVehicle() && veh->IsCar()) { + CVehicleModelInfo *mi = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(veh->GetModelIndex())); + CVector ModelInfo = mi->m_positions[CAR_POS_HEADLIGHTS]; + ModelInfo = m_pEntity->GetMatrix() * ModelInfo; + + firePos.x = ModelInfo.x; + firePos.y = ModelInfo.y; + firePos.z = ModelInfo.z + 0.15f; + } + + CParticle::AddParticle(PARTICLE_CARFLAME, firePos, + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.0125f, 0.1f) * m_fStrength), + 0, m_fStrength, 0, 0, 0, 0); + + CGeneral::GetRandomNumber(); CGeneral::GetRandomNumber(); CGeneral::GetRandomNumber(); /* unsure why these three rands are called */ + + CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, firePos, + CVector(0.0f, 0.0f, 0.0f), 0, 0.0f, 0, 0, 0, 0); + } + if (CTimer::GetTimeInMilliseconds() < m_nExtinguishTime || m_bIsScriptFire) { + if (CTimer::GetTimeInMilliseconds() > m_nStartTime) + m_nStartTime = CTimer::GetTimeInMilliseconds() + 400; + + nRandNumber = CGeneral::GetRandomNumber() & 127; + lightpos.x = m_vecPos.x; + lightpos.y = m_vecPos.y; + lightpos.z = m_vecPos.z + 5.0f; + + if (!m_pEntity) { + CShadows::StoreStaticShadow((uintptr)this, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &lightpos, 7.0f, 0.0f, 0.0f, -7.0f, 0, nRandNumber / 2, + nRandNumber / 2, 0, 10.0f, 1.0f, 40.0f, 0, 0.0f); + } + fGreen = nRandNumber / 128.f; + fRed = nRandNumber / 128.f; + + CPointLights::AddLight(CPointLights::LIGHT_POINT, m_vecPos, CVector(0.0f, 0.0f, 0.0f), 12.0f, fRed, fGreen, 0, 0, 0); + } else { + Extinguish(); + } +} + +void +CFire::ReportThisFire(void) +{ + gFireManager.m_nTotalFires++; + CEventList::RegisterEvent(EVENT_FIRE, m_vecPos, 1000); +} + +void +CFire::Extinguish(void) +{ + if (m_bIsOngoing) { + if (!m_bIsScriptFire) + gFireManager.m_nTotalFires--; + + m_nExtinguishTime = 0; + m_bIsOngoing = false; + + if (m_pEntity) { + if (m_pEntity->IsPed()) { + ((CPed *)m_pEntity)->RestorePreviousState(); + ((CPed *)m_pEntity)->m_pFire = nil; + } else if (m_pEntity->IsVehicle()) { + ((CVehicle *)m_pEntity)->m_pCarFire = nil; + } + m_pEntity = nil; + } + } +} + +void +CFireManager::StartFire(CVector pos, float size, bool propagation) +{ + CFire *fire = GetNextFreeFire(); + + if (fire) { + fire->m_bIsOngoing = true; + fire->m_bIsScriptFire = false; + fire->m_bPropagationFlag = propagation; + fire->m_bAudioSet = true; + fire->m_vecPos = pos; + fire->m_nExtinguishTime = CTimer::GetTimeInMilliseconds() + 10000; + fire->m_nStartTime = CTimer::GetTimeInMilliseconds() + 400; + fire->m_pEntity = nil; + fire->m_pSource = nil; + fire->m_nNextTimeToAddFlames = 0; + fire->ReportThisFire(); + fire->m_fStrength = size; + } +} + +CFire * +CFireManager::StartFire(CEntity *entityOnFire, CEntity *fleeFrom, float strength, bool propagation) +{ + CPed *ped = (CPed *)entityOnFire; + CVehicle *veh = (CVehicle *)entityOnFire; + + if (entityOnFire->IsPed()) { + if (ped->m_pFire) + return nil; + if (!ped->IsPedInControl()) + return nil; + } else if (entityOnFire->IsVehicle()) { + if (veh->m_pCarFire) + return nil; + if (veh->IsCar() && ((CAutomobile *)veh)->Damage.GetEngineStatus() >= 225) + return nil; + } + CFire *fire = GetNextFreeFire(); + + if (fire) { + if (entityOnFire->IsPed()) { + ped->m_pFire = fire; + if (ped != FindPlayerPed()) { + if (fleeFrom) { + ped->SetFlee(fleeFrom, 10000); + } else { + CVector2D pos = entityOnFire->GetPosition(); + ped->SetFlee(pos, 10000); + ped->m_fleeFrom = nil; + } + ped->bDrawLast = false; + ped->SetMoveState(PEDMOVE_SPRINT); + ped->SetMoveAnim(); + ped->SetPedState(PED_ON_FIRE); + } + if (fleeFrom) { + if (ped->m_nPedType == PEDTYPE_COP) { + CEventList::RegisterEvent(EVENT_COP_SET_ON_FIRE, EVENT_ENTITY_PED, + entityOnFire, (CPed *)fleeFrom, 10000); + } else { + CEventList::RegisterEvent(EVENT_PED_SET_ON_FIRE, EVENT_ENTITY_PED, + entityOnFire, (CPed *)fleeFrom, 10000); + } + } + } else { + if (entityOnFire->IsVehicle()) { + veh->m_pCarFire = fire; + if (fleeFrom) { + CEventList::RegisterEvent(EVENT_CAR_SET_ON_FIRE, EVENT_ENTITY_VEHICLE, + entityOnFire, (CPed *)fleeFrom, 10000); + } + } + } + + fire->m_bIsOngoing = true; + fire->m_bIsScriptFire = false; + fire->m_vecPos = entityOnFire->GetPosition(); + + if (entityOnFire && entityOnFire->IsPed() && ped->IsPlayer()) { + fire->m_nExtinguishTime = CTimer::GetTimeInMilliseconds() + 3333; + } else if (entityOnFire->IsVehicle()) { + fire->m_nExtinguishTime = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(4000, 5000); + } else { + fire->m_nExtinguishTime = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(10000, 11000); + } + fire->m_nStartTime = CTimer::GetTimeInMilliseconds() + 400; + fire->m_pEntity = entityOnFire; + + entityOnFire->RegisterReference(&fire->m_pEntity); + fire->m_pSource = fleeFrom; + + if (fleeFrom) + fleeFrom->RegisterReference(&fire->m_pSource); + fire->ReportThisFire(); + fire->m_nNextTimeToAddFlames = 0; + fire->m_fStrength = strength; + fire->m_bPropagationFlag = propagation; + fire->m_bAudioSet = true; + } + return fire; +} + +void +CFireManager::Update(void) +{ + for (int i = 0; i < NUM_FIRES; i++) { + if (m_aFires[i].m_bIsOngoing) + m_aFires[i].ProcessFire(); + } +} + +CFire* CFireManager::FindNearestFire(CVector vecPos, float *pDistance) +{ + for (int i = 0; i < MAX_FIREMEN_ATTENDING; i++) { + int fireId = -1; + float minDistance = 999999; + for (int j = 0; j < NUM_FIRES; j++) { + if (!m_aFires[j].m_bIsOngoing) + continue; + if (m_aFires[j].m_bIsScriptFire) + continue; + if (m_aFires[j].m_nFiremenPuttingOut != i) + continue; + float distance = (m_aFires[j].m_vecPos - vecPos).Magnitude2D(); + if (distance < minDistance) { + minDistance = distance; + fireId = j; + } + } + *pDistance = minDistance; + if (fireId != -1) + return &m_aFires[fireId]; + } + return nil; +} + +CFire * +CFireManager::FindFurthestFire_NeverMindFireMen(CVector coords, float minRange, float maxRange) +{ + int furthestFire = -1; + float lastFireDist = 0.0f; + float fireDist; + + for (int i = 0; i < NUM_FIRES; i++) { + if (m_aFires[i].m_bIsOngoing && !m_aFires[i].m_bIsScriptFire) { + fireDist = (m_aFires[i].m_vecPos - coords).Magnitude2D(); + if (fireDist > minRange && fireDist < maxRange && fireDist > lastFireDist) { + lastFireDist = fireDist; + furthestFire = i; + } + } + } + if (furthestFire == -1) + return nil; + else + return &m_aFires[furthestFire]; +} + +CFire * +CFireManager::GetNextFreeFire(void) +{ + for (int i = 0; i < NUM_FIRES; i++) { + if (!m_aFires[i].m_bIsOngoing && !m_aFires[i].m_bIsScriptFire) + return &m_aFires[i]; + } + return nil; +} + +uint32 +CFireManager::GetTotalActiveFires(void) const +{ + return m_nTotalFires; +} + +void +CFireManager::ExtinguishPoint(CVector point, float range) +{ + for (int i = 0; i < NUM_FIRES; i++) { + if (m_aFires[i].m_bIsOngoing) { + if ((point - m_aFires[i].m_vecPos).MagnitudeSqr() < sq(range)) + m_aFires[i].Extinguish(); + } + } +} + +int32 +CFireManager::StartScriptFire(const CVector &pos, CEntity *target, float strength, bool propagation) +{ + CFire *fire; + CPed *ped = (CPed *)target; + CVehicle *veh = (CVehicle *)target; + + if (target) { + if (target->IsPed()) { + if (ped->m_pFire) + ped->m_pFire->Extinguish(); + } else if (target->IsVehicle()) { + if (veh->m_pCarFire) + veh->m_pCarFire->Extinguish(); + if (veh->IsCar() && ((CAutomobile *)veh)->Damage.GetEngineStatus() >= 225) { + ((CAutomobile *)veh)->Damage.SetEngineStatus(215); + } + } + } + + fire = GetNextFreeFire(); + fire->m_bIsOngoing = true; + fire->m_bIsScriptFire = true; + fire->m_bPropagationFlag = propagation; + fire->m_bAudioSet = true; + fire->m_vecPos = pos; + fire->m_nStartTime = CTimer::GetTimeInMilliseconds() + 400; + fire->m_pEntity = target; + + if (target) + target->RegisterReference(&fire->m_pEntity); + fire->m_pSource = nil; + fire->m_nNextTimeToAddFlames = 0; + fire->m_fStrength = strength; + if (target) { + if (target->IsPed()) { + ped->m_pFire = fire; + if (target != FindPlayerPed()) { + CVector2D pos = target->GetPosition(); + ped->SetFlee(pos, 10000); + ped->SetMoveAnim(); + ped->SetPedState(PED_ON_FIRE); + } + } else if (target->IsVehicle()) { + veh->m_pCarFire = fire; + } + } + return fire - m_aFires; +} + +bool +CFireManager::IsScriptFireExtinguish(int16 index) +{ + return !m_aFires[index].m_bIsOngoing; +} + +void +CFireManager::RemoveAllScriptFires(void) +{ + for (int i = 0; i < NUM_FIRES; i++) { + if (m_aFires[i].m_bIsScriptFire) { + m_aFires[i].Extinguish(); + m_aFires[i].m_bIsScriptFire = false; + } + } +} + +void +CFireManager::RemoveScriptFire(int16 index) +{ + m_aFires[index].Extinguish(); + m_aFires[index].m_bIsScriptFire = false; +} + +void +CFireManager::SetScriptFireAudio(int16 index, bool state) +{ + m_aFires[index].m_bAudioSet = state; +} diff --git a/src/core/Fire.h b/src/core/Fire.h new file mode 100644 index 0000000..85e53f6 --- /dev/null +++ b/src/core/Fire.h @@ -0,0 +1,51 @@ +#pragma once + +class CEntity; + +class CFire +{ +public: + bool m_bIsOngoing; + bool m_bIsScriptFire; + bool m_bPropagationFlag; + bool m_bAudioSet; + CVector m_vecPos; + CEntity *m_pEntity; + CEntity *m_pSource; + uint32 m_nExtinguishTime; + uint32 m_nStartTime; + int32 field_20; + uint32 m_nNextTimeToAddFlames; + uint32 m_nFiremenPuttingOut; + float m_fStrength; + + CFire(); + ~CFire(); + void ProcessFire(void); + void ReportThisFire(void); + void Extinguish(void); +}; + +class CFireManager +{ + enum { + MAX_FIREMEN_ATTENDING = 2, + }; +public: + uint32 m_nTotalFires; + CFire m_aFires[NUM_FIRES]; + void StartFire(CVector pos, float size, bool propagation); + CFire *StartFire(CEntity *entityOnFire, CEntity *fleeFrom, float strength, bool propagation); + void Update(void); + CFire *FindFurthestFire_NeverMindFireMen(CVector coords, float minRange, float maxRange); + CFire *FindNearestFire(CVector vecPos, float *pDistance); + CFire *GetNextFreeFire(void); + uint32 GetTotalActiveFires() const; + void ExtinguishPoint(CVector point, float range); + int32 StartScriptFire(const CVector &pos, CEntity *target, float strength, bool propagation); + bool IsScriptFireExtinguish(int16 index); + void RemoveAllScriptFires(void); + void RemoveScriptFire(int16 index); + void SetScriptFireAudio(int16 index, bool state); +}; +extern CFireManager gFireManager; diff --git a/src/core/FrontEndControls.cpp b/src/core/FrontEndControls.cpp new file mode 100644 index 0000000..18f6b3b --- /dev/null +++ b/src/core/FrontEndControls.cpp @@ -0,0 +1,1887 @@ +#include "common.h" +#include "main.h" +#include "Timer.h" +#include "Sprite2d.h" +#include "Text.h" +#include "Font.h" +#include "FrontEndControls.h" + +#define X SCREEN_SCALE_X +#define Y(x) SCREEN_SCALE_Y(float(x)*(float(DEFAULT_SCREEN_HEIGHT)/float(SCREEN_HEIGHT_PAL))) + +void +CPlaceableShText::Draw(float x, float y) +{ + if(m_text == nil) + return; + + if(m_bRightJustify) + CFont::SetRightJustifyOn(); + if(m_bDropShadow){ + CFont::SetDropShadowPosition(m_shadowOffset.x); + CFont::SetDropColor(m_shadowColor); + } + CFont::SetColor(m_color); + CFont::PrintString(x+m_position.x, y+m_position.y, m_text); + if(m_bDropShadow) + CFont::SetDropShadowPosition(0); + if(m_bRightJustify) + CFont::SetRightJustifyOff(); +} + +void +CPlaceableShText::Draw(const CRGBA &color, float x, float y) +{ + if(m_text == nil) + return; + + if(m_bRightJustify) + CFont::SetRightJustifyOn(); + if(m_bDropShadow){ + CFont::SetDropShadowPosition(m_shadowOffset.x); + CFont::SetDropColor(m_shadowColor); + } + CFont::SetColor(color); + CFont::PrintString(x+m_position.x, y+m_position.y, m_text); + if(m_bDropShadow) + CFont::SetDropShadowPosition(0); + if(m_bRightJustify) + CFont::SetRightJustifyOff(); +} + +void +CPlaceableShTextTwoLines::Draw(float x, float y) +{ + if(m_line1.m_text == nil && m_line2.m_text == nil) + return; + + if(m_bRightJustify) + CFont::SetRightJustifyOn(); + if(m_bDropShadow){ + CFont::SetDropShadowPosition(m_shadowOffset.x); + CFont::SetDropColor(m_shadowColor); + } + + if(m_line1.m_text){ + CFont::SetColor(m_line1.m_color); + CFont::PrintString(x+m_line1.m_position.x, y+m_line1.m_position.y, m_line1.m_text); + } + if(m_line2.m_text){ + CFont::SetColor(m_line2.m_color); + CFont::PrintString(x+m_line2.m_position.x, y+m_line2.m_position.y, m_line2.m_text); + } + + if(m_bDropShadow) + CFont::SetDropShadowPosition(0); + if(m_bRightJustify) + CFont::SetRightJustifyOff(); +} + +void +CPlaceableShTextTwoLines::Draw(const CRGBA &color, float x, float y) +{ + if(m_line1.m_text == nil && m_line2.m_text == nil) + return; + + if(m_bRightJustify) + CFont::SetRightJustifyOn(); + if(m_bDropShadow){ + CFont::SetDropShadowPosition(m_shadowOffset.x); + CFont::SetDropColor(m_shadowColor); + } + + if(m_line1.m_text){ + CFont::SetColor(color); + CFont::PrintString(x+m_line1.m_position.x, y+m_line1.m_position.y, m_line1.m_text); + } + if(m_line2.m_text){ + CFont::SetColor(color); + CFont::PrintString(x+m_line2.m_position.x, y+m_line2.m_position.y, m_line2.m_text); + } + + if(m_bDropShadow) + CFont::SetDropShadowPosition(0); + if(m_bRightJustify) + CFont::SetRightJustifyOff(); +} + +void +CPlaceableShOption::Draw(const CRGBA &highlightColor, float x, float y, bool bHighlight) +{ + if(bHighlight) + CPlaceableShText::Draw(highlightColor, x, y); + else if(m_bSelected) + CPlaceableShText::Draw(m_selectedColor, x, y); + else + CPlaceableShText::Draw(x, y); +} + +void +CPlaceableShOptionTwoLines::Draw(const CRGBA &highlightColor, float x, float y, bool bHighlight) +{ + if(bHighlight) + CPlaceableShTextTwoLines::Draw(highlightColor, x, y); + else if(m_bSelected) + CPlaceableShTextTwoLines::Draw(m_selectedColor, x, y); + else + CPlaceableShTextTwoLines::Draw(x, y); +} + +void +CPlaceableSprite::Draw(float x, float y) +{ + Draw(m_color, x, y); +} + +void +CPlaceableSprite::Draw(const CRGBA &color, float x, float y) +{ + if(m_pSprite) + m_pSprite->Draw(CRect(m_position.x+x, m_position.y+y, + m_position.x+x + m_size.x, m_position.y+y + m_size.y), + color); +} + +void +CPlaceableShSprite::Draw(float x, float y) +{ + if(m_bDropShadow) + m_shadow.Draw(m_shadow.m_color, m_sprite.m_position.x+x, m_sprite.m_position.y+y); + m_sprite.Draw(x, y); +} + + +/* + * CMenuPictureAndText + */ + +void +CMenuPictureAndText::SetNewOldShadowWrapX(bool bWrapX, float newWrapX, float oldWrapX) +{ + m_bWrap = bWrapX; + m_wrapX = newWrapX; + m_oldWrapx = oldWrapX; +} + +void +CMenuPictureAndText::SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale) +{ + m_bSetTextScale = bTextScale; + m_textScale = newScale; + m_oldTextScale = oldScale; +} + +void +CMenuPictureAndText::SetTextsColor(CRGBA const &color) +{ + int i; + for(i = 0; i < m_numTexts; i++) + m_texts[i].m_color = color; +} + +void +CMenuPictureAndText::AddText(wchar *text, float positionX, float positionY, CRGBA const &color, bool bRightJustify) +{ + int i; + if(m_numTexts >= 20) + return; + i = m_numTexts++; + m_texts[i].m_text = text; + m_texts[i].m_position.x = positionX; + m_texts[i].m_position.y = positionY; + m_texts[i].m_color = color; + m_texts[i].m_bRightJustify = bRightJustify; +} + +void +CMenuPictureAndText::AddPicture(CSprite2d *sprite, CSprite2d *shadow, float positionX, float positionY, float width, float height, CRGBA const &color) +{ + int i; + if(m_numSprites >= 5) + return; + i = m_numSprites++; + m_sprites[i].m_sprite.m_pSprite = sprite; + m_sprites[i].m_shadow.m_pSprite = shadow; + m_sprites[i].m_sprite.m_position.x = positionX; + m_sprites[i].m_sprite.m_position.y = positionY; + m_sprites[i].m_sprite.m_size.x = width; + m_sprites[i].m_sprite.m_size.y = height; + m_sprites[i].m_shadow.m_size.x = width; + m_sprites[i].m_shadow.m_size.y = height; + m_sprites[i].m_sprite.m_color = color; +} + +void +CMenuPictureAndText::AddPicture(CSprite2d *sprite, float positionX, float positionY, float width, float height, CRGBA const &color) +{ + int i; + if(m_numSprites >= 5) + return; + i = m_numSprites++; + m_sprites[i].m_sprite.m_pSprite = sprite; + m_sprites[i].m_shadow.m_pSprite = nil; + m_sprites[i].m_sprite.m_position.x = positionX; + m_sprites[i].m_sprite.m_position.y = positionY; + m_sprites[i].m_sprite.m_size.x = width; + m_sprites[i].m_sprite.m_size.y = height; + m_sprites[i].m_shadow.m_size.x = width; + m_sprites[i].m_shadow.m_size.y = height; + m_sprites[i].m_sprite.m_color = color; +} + +void +CMenuPictureAndText::Draw(CRGBA const &,CRGBA const &, float x, float y) +{ + int i; + + for(i = 0; i < m_numSprites; i++) + m_sprites[i].Draw(m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + for(i = 0; i < m_numTexts; i++) + if(m_bWrap) + m_texts[i].DrawShWrap(m_position.x+x, m_position.y+y, m_wrapX, m_oldWrapx); + else + m_texts[i].Draw(m_position.x+x, m_position.y+y); + if(m_bSetTextScale) + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); +} + +void +CMenuPictureAndText::SetAlpha(uint8 alpha) +{ + int i; + + for(i = 0; i < m_numSprites; i++) + m_sprites[i].SetAlpha(alpha); + for(i = 0; i < m_numTexts; i++) + m_texts[i].SetAlpha(alpha); +} + +void +CMenuPictureAndText::SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) +{ + int i; + + for(i = 0; i < 5; i++) + m_sprites[i].SetShadows(bDropShadows, shadowColor, shadowOffset); + for(i = 0; i < 20; i++) + m_texts[i].SetShadows(bDropShadows, shadowColor, shadowOffset); +} + +/* + * CMenuMultiChoice + */ + +void +CMenuMultiChoice::AddTitle(wchar *text, float positionX, float positionY, bool bRightJustify) +{ + m_title.m_text = text; + m_title.SetPosition(positionX, positionY, bRightJustify); +} + +CPlaceableShOption* +CMenuMultiChoice::AddOption(wchar *text, float positionX, float positionY, bool bSelected, bool bRightJustify) +{ + if(m_numOptions == NUM_MULTICHOICE_OPTIONS) + return nil; + m_options[m_numOptions].m_text = text; + m_options[m_numOptions].SetPosition(positionX, positionY); + m_options[m_numOptions].m_bSelected = bSelected; + m_options[m_numOptions].m_bRightJustify = bRightJustify; + return &m_options[m_numOptions++]; +} + +void +CMenuMultiChoice::SetColors(const CRGBA &title, const CRGBA &normal, const CRGBA &selected) +{ + int i; + m_title.SetColor(title); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetColors(normal, selected); +} + +void +CMenuMultiChoice::SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale) +{ + m_bSetTextScale = bTextScale; + m_textScale = newScale; + m_oldTextScale = oldScale; + m_bSetTitleTextScale = bTitleTextScale; +} + +void +CMenuMultiChoice::Draw(CRGBA const &optionHighlight ,CRGBA const &titleHighlight, float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_cursor == -1) + m_title.Draw(m_position.x+x, m_position.y+y); + else + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + if(m_cursor == -1) + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + else + for(i = 0; i < m_numOptions; i++){ + if(i == m_cursor) + m_options[i].Draw(optionHighlight, m_position.x+x, m_position.y+y); + else + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + } + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoice::DrawNormal(float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + m_title.Draw(m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoice::DrawHighlighted(CRGBA const &titleHighlight, float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_cursor == -1) + m_title.Draw(m_position.x+x, m_position.y+y); + else + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoice::SetAlpha(uint8 alpha) +{ + int i; + m_title.SetAlpha(alpha); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetAlpha(alpha); +} + +void +CMenuMultiChoice::SetShadows(bool bDropShadows, CRGBA const &shadowColor, CVector2D const &shadowOffset) +{ + int i; + m_title.SetShadows(bDropShadows, shadowColor, shadowOffset); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetShadows(bDropShadows, shadowColor, shadowOffset); +} + + +bool +CMenuMultiChoice::GoNext(void) +{ + if(m_cursor == m_numOptions-1){ + m_cursor = -1; + return false; + }else{ + m_cursor++; + return true; + } +} + +bool +CMenuMultiChoice::GoPrev(void) +{ + if(m_cursor == 0){ + m_cursor = -1; + return false; + }else{ + m_cursor--; + return true; + } +} + +void +CMenuMultiChoice::SelectCurrentOptionUnderCursor(void) +{ + int i; + if(m_cursor == -1) + return; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].m_bSelected = false; + m_options[m_cursor].m_bSelected = true; +} + +int +CMenuMultiChoice::GetMenuSelection(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + if(m_options[i].m_bSelected) + return i; + return -1; +} + +void +CMenuMultiChoice::SetMenuSelection(int selection) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].m_bSelected = false; + m_options[selection%NUM_MULTICHOICE_OPTIONS].m_bSelected = true; +} + +/* + * CMenuMultiChoiceTriggered + */ + +void +CMenuMultiChoiceTriggered::Initialise(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_triggers[i] = nil; + m_defaultCancel = nil; +} + +CPlaceableShOption* +CMenuMultiChoiceTriggered::AddOption(wchar *text, float positionX, float positionY, Trigger trigger, bool bSelected, bool bRightJustify) +{ + CPlaceableShOption *option; + option = CMenuMultiChoice::AddOption(text, positionX, positionY, bSelected, bRightJustify); + if(option) + m_triggers[m_numOptions-1] = trigger; + return option; +} + +void +CMenuMultiChoiceTriggered::SelectCurrentOptionUnderCursor(void) +{ + CMenuMultiChoice::SelectCurrentOptionUnderCursor(); + if(m_cursor != -1 && m_triggers[m_cursor] != nil ) + m_triggers[m_cursor](this); +} + +void +CMenuMultiChoiceTriggered::SelectDefaultCancelAction(void) +{ + if(m_defaultCancel) + m_defaultCancel(this); +} + +/* + * CMenuMultiChoiceTriggeredAlways + */ + +void +CMenuMultiChoiceTriggeredAlways::Draw(CRGBA const &optionHighlight, CRGBA const &titleHighlight, float x, float y) +{ + if(m_alwaysTrigger) + m_alwaysTrigger(this); + CMenuMultiChoiceTriggered::Draw(optionHighlight, titleHighlight, x, y); +} + +void +CMenuMultiChoiceTriggeredAlways::DrawNormal(float x, float y) +{ + if(m_alwaysNormalTrigger) + m_alwaysNormalTrigger(this); + CMenuMultiChoiceTriggered::DrawNormal(x, y); +} + +void +CMenuMultiChoiceTriggeredAlways::DrawHighlighted(CRGBA const &titleHighlight, float x, float y) +{ + if(m_alwaysHighlightTrigger) + m_alwaysHighlightTrigger(this); + CMenuMultiChoiceTriggered::DrawHighlighted(titleHighlight, x, y); +} + +/* + * CMenuMultiChoicePictured + */ + +void +CMenuMultiChoicePictured::Initialise(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_bHasSprite[i] = false; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_sprites[i].m_pSprite = nil; +} + +CPlaceableShOption* +CMenuMultiChoicePictured::AddOption(CSprite2d *sprite, float positionX, float positionY, const CVector2D &size, bool bSelected) +{ + CPlaceableShOption *option; + option = CMenuMultiChoice::AddOption(nil, 0.0f, 0.0f, bSelected, false); + if(option){ + m_sprites[m_numOptions-1].m_pSprite = sprite; + m_sprites[m_numOptions-1].SetPosition(positionX, positionY); + m_sprites[m_numOptions-1].m_size = size; + m_bHasSprite[m_numOptions-1] = true; + } + return option; +} + +void +CMenuMultiChoicePictured::Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) +{ + int i; + + // The title and all the text + CMenuMultiChoice::Draw(optionHighlight, titleHighlight, x, y); + + CRGBA selectedColor = m_options[0].GetSelectedColor(); + CRGBA color = m_options[0].GetColor(); + + // The sprites + if(m_cursor == -1){ + for(i = 0; i < m_numOptions; i++) + if(m_bHasSprite[i]){ + if(m_options[i].m_bSelected) + m_sprites[i].Draw(selectedColor, m_position.x+x, m_position.y+y); + else + m_sprites[i].Draw(color, m_position.x+x, m_position.y+y); + } + }else{ + for(i = 0; i < m_numOptions; i++) + if(i == m_cursor){ + if(m_bHasSprite[i]) + { + uint8 color = Max(Max(optionHighlight.r, optionHighlight.g), optionHighlight.b); + m_sprites[i].Draw(CRGBA(color, color, color, optionHighlight.a), m_position.x+x, m_position.y+y); + } + }else{ + if(m_bHasSprite[i]){ + if(m_options[i].m_bSelected) + m_sprites[i].Draw(selectedColor, m_position.x+x, m_position.y+y); + else + m_sprites[i].Draw(color, m_position.x+x, m_position.y+y); + } + } + } +} + +void +CMenuMultiChoicePictured::DrawNormal(float x, float y) +{ + int i; + + // The title and all the text + CMenuMultiChoice::DrawNormal(x, y); + + CRGBA selectedColor = m_options[0].GetSelectedColor(); + CRGBA color = m_options[0].GetColor(); + + // The sprites + for(i = 0; i < m_numOptions; i++) + if(m_bHasSprite[i]){ + if(m_options[i].m_bSelected) + m_sprites[i].Draw(selectedColor, m_position.x+x, m_position.y+y); + else + m_sprites[i].Draw(color, m_position.x+x, m_position.y+y); + } +} + +void +CMenuMultiChoicePictured::DrawHighlighted(const CRGBA &titleHighlight, float x, float y) +{ + int i; + + // The title and all the text + CMenuMultiChoice::DrawHighlighted(titleHighlight, x, y); + + CRGBA selectedColor = m_options[0].GetSelectedColor(); + CRGBA color = m_options[0].GetColor(); + + // The sprites + for(i = 0; i < m_numOptions; i++) + if(m_bHasSprite[i]){ + if(m_options[i].m_bSelected) + m_sprites[i].Draw(selectedColor, m_position.x+x, m_position.y+y); + else + m_sprites[i].Draw(color, m_position.x+x, m_position.y+y); + } +} + +void +CMenuMultiChoicePictured::SetAlpha(uint8 alpha) +{ + int i; + CMenuMultiChoice::SetAlpha(alpha); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_sprites[i].SetAlpha(alpha); + +} + + +/* + * CMenuMultiChoicePicturedTriggered + */ + +void +CMenuMultiChoicePicturedTriggered::Initialise(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_triggers[i] = nil; + m_defaultCancel = nil; // missing on PS2 +} + +CPlaceableShOption* +CMenuMultiChoicePicturedTriggered::AddOption(CSprite2d *sprite, float positionX, float positionY, const CVector2D &size, Trigger trigger, bool bSelected) +{ + CPlaceableShOption *option; + option = CMenuMultiChoicePictured::AddOption(sprite, positionX, positionY, size, bSelected); + if(option) + m_triggers[m_numOptions-1] = trigger; + return option; +} + +void +CMenuMultiChoicePicturedTriggered::SelectCurrentOptionUnderCursor(void) +{ + CMenuMultiChoice::SelectCurrentOptionUnderCursor(); + if(m_cursor != -1) + m_triggers[m_cursor](this); +} + +void +CMenuMultiChoicePicturedTriggered::SelectDefaultCancelAction(void) +{ + if(m_defaultCancel) + m_defaultCancel(this); +} + +/* + * CMenuMultiChoicePicturedTriggeredAnyMove + */ + +void +CMenuMultiChoicePicturedTriggeredAnyMove::Initialise(void) +{ + int i; + CMenuMultiChoicePicturedTriggered::Initialise(); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++){ + m_moveTab[i].right = -1; + m_moveTab[i].left = -1; + m_moveTab[i].down = -1; + m_moveTab[i].up = -1; + } +} + +CPlaceableShOption* +CMenuMultiChoicePicturedTriggeredAnyMove::AddOption(CSprite2d *sprite, FEC_MOVETAB *moveTab, float positionX, float positionY, const CVector2D &size, Trigger trigger, bool bSelected) +{ + CPlaceableShOption *option; + option = CMenuMultiChoicePicturedTriggered::AddOption(sprite, positionX, positionY, size, trigger, bSelected); + if(option && moveTab) + m_moveTab[m_numOptions-1] = *moveTab; + return option; +} + +bool +CMenuMultiChoicePicturedTriggeredAnyMove::GoDown(void) +{ + int move = m_moveTab[m_cursor].down; + if(move == -1) + return GoNext(); + m_cursor = move; + return true; +} + +bool +CMenuMultiChoicePicturedTriggeredAnyMove::GoUp(void) +{ + int move = m_moveTab[m_cursor].up; + if(move == -1) + return GoPrev(); + m_cursor = move; + return true; +} + +bool +CMenuMultiChoicePicturedTriggeredAnyMove::GoLeft(void) +{ + int move = m_moveTab[m_cursor].left; + if(move == -1) + return GoPrev(); + m_cursor = move; + return true; +} + +bool +CMenuMultiChoicePicturedTriggeredAnyMove::GoRight(void) +{ + int move = m_moveTab[m_cursor].right; + if(move == -1) + return GoNext(); + m_cursor = move; + return true; +} + + +/* + * CMenuMultiChoiceTwoLines + */ + +void +CMenuMultiChoiceTwoLines::AddTitle(wchar *text, float positionX, float positionY, bool bRightJustify) +{ + m_title.m_text = text; + m_title.SetPosition(positionX, positionY, bRightJustify); +} + +CPlaceableShOptionTwoLines* +CMenuMultiChoiceTwoLines::AddOption(wchar *text, float positionX, float positionY, bool bSelected, bool bRightJustify) +{ + return AddOption(text, positionX, positionY, nil, 0.0f, 0.0f, bSelected, bRightJustify); +} + +CPlaceableShOptionTwoLines* +CMenuMultiChoiceTwoLines::AddOption(wchar *text1, float positionX1, float positionY1, wchar *text2, float positionX2, float positionY2, bool bSelected, bool bRightJustify) +{ + if(m_numOptions == NUM_MULTICHOICE_OPTIONS) + return nil; + m_options[m_numOptions].m_line1.m_text = text1; + m_options[m_numOptions].m_line2.m_text = text2; + m_options[m_numOptions].m_line1.SetPosition(positionX1, positionY1); + m_options[m_numOptions].m_line2.SetPosition(positionX2, positionY2); + m_options[m_numOptions].m_bSelected = bSelected; + m_options[m_numOptions].m_bRightJustify = bRightJustify; + return &m_options[m_numOptions++]; +} + + +void +CMenuMultiChoiceTwoLines::SetColors(const CRGBA &title, const CRGBA &normal, const CRGBA &selected) +{ + int i; + m_title.SetColor(title); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetColors(normal, selected); +} + +void +CMenuMultiChoiceTwoLines::SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale) +{ + m_bSetTextScale = bTextScale; + m_textScale = newScale; + m_oldTextScale = oldScale; + m_bSetTitleTextScale = bTitleTextScale; +} + +void +CMenuMultiChoiceTwoLines::Draw(CRGBA const &optionHighlight ,CRGBA const &titleHighlight, float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_cursor == -1) + m_title.Draw(m_position.x+x, m_position.y+y); + else + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + if(m_cursor == -1) + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + else + for(i = 0; i < m_numOptions; i++){ + if(i == m_cursor) + m_options[i].Draw(optionHighlight, m_position.x+x, m_position.y+y); + else + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + } + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoiceTwoLines::DrawNormal(float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + m_title.Draw(m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoiceTwoLines::DrawHighlighted(CRGBA const &titleHighlight, float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_cursor == -1) + m_title.Draw(m_position.x+x, m_position.y+y); + else + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoiceTwoLines::SetAlpha(uint8 alpha) +{ + int i; + m_title.SetAlpha(alpha); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetAlpha(alpha); +} + +void +CMenuMultiChoiceTwoLines::SetShadows(bool bDropShadows, CRGBA const &shadowColor, CVector2D const &shadowOffset) +{ + int i; + m_title.SetShadows(bDropShadows, shadowColor, shadowOffset); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetShadows(bDropShadows, shadowColor, shadowOffset); +} + + +bool +CMenuMultiChoiceTwoLines::GoNext(void) +{ + if(m_cursor == m_numOptions-1){ + m_cursor = -1; + return false; + }else{ + m_cursor++; + return true; + } +} + +bool +CMenuMultiChoiceTwoLines::GoPrev(void) +{ + if(m_cursor == 0){ + m_cursor = -1; + return false; + }else{ + m_cursor--; + return true; + } +} + +void +CMenuMultiChoiceTwoLines::SelectCurrentOptionUnderCursor(void) +{ + int i; + if(m_cursor == -1) + return; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].m_bSelected = false; + m_options[m_cursor].m_bSelected = true; +} + +int +CMenuMultiChoiceTwoLines::GetMenuSelection(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + if(m_options[i].m_bSelected) + return i; + return -1; +} + +void +CMenuMultiChoiceTwoLines::SetMenuSelection(int selection) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].m_bSelected = false; + m_options[selection%NUM_MULTICHOICE_OPTIONS].m_bSelected = true; +} + +/* + * CMenuMultiChoiceTwoLinesTriggered + */ + +void +CMenuMultiChoiceTwoLinesTriggered::Initialise(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_triggers[i] = nil; + m_defaultCancel = nil; +} + +CPlaceableShOptionTwoLines* +CMenuMultiChoiceTwoLinesTriggered::AddOption(wchar *text, float positionX, float positionY, Trigger trigger, bool bSelected, bool bRightJustify) +{ + CPlaceableShOptionTwoLines *option; + option = CMenuMultiChoiceTwoLines::AddOption(text, positionX, positionY, bSelected, bRightJustify); + if(option) + m_triggers[m_numOptions-1] = trigger; + return option; +} + +CPlaceableShOptionTwoLines* +CMenuMultiChoiceTwoLinesTriggered::AddOption(wchar *text1, float positionX1, float positionY1, wchar *text2, float positionX2, float positionY2, Trigger trigger, bool bSelected, bool bRightJustify) +{ + CPlaceableShOptionTwoLines *option; + option = CMenuMultiChoiceTwoLines::AddOption(text1, positionX1, positionY1, text2, positionX2, positionY2, bSelected, bRightJustify); + if(option) + m_triggers[m_numOptions-1] = trigger; + return option; +} + +void +CMenuMultiChoiceTwoLinesTriggered::SelectCurrentOptionUnderCursor(void) +{ + CMenuMultiChoiceTwoLines::SelectCurrentOptionUnderCursor(); + if(m_cursor != -1) + m_triggers[m_cursor](this); +} + +void +CMenuMultiChoiceTwoLinesTriggered::SelectDefaultCancelAction(void) +{ + if(m_defaultCancel) + m_defaultCancel(this); +} + + +/* + * CMenuOnOff + */ + +void +CMenuOnOff::SetColors(const CRGBA &title, const CRGBA &options) +{ + m_title.SetColors(title, title); + m_options[0].SetColor(options); + m_options[1].SetColor(options); +} + +void +CMenuOnOff::SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale) +{ + m_bSetTextScale = bTextScale; + m_textScale = newScale; + m_oldTextScale = oldScale; + m_bSetTitleTextScale = bTitleTextScale; +} + +void +CMenuOnOff::SetOptionPosition(float x, float y, bool bRightJustify) +{ + m_options[0].SetPosition(x, y, bRightJustify); + m_options[1].SetPosition(x, y, bRightJustify); +} + +void +CMenuOnOff::AddTitle(wchar *text, bool bSelected, float positionX, float positionY, bool bRightJustify) +{ + m_title.m_text = text; + m_title.m_bSelected = bSelected; + m_title.SetPosition(positionX, positionY, bRightJustify); +} + +void +CMenuOnOff::Draw(CRGBA const &optionHighlight, CRGBA const &titleHighlight, float x, float y) +{ + if(m_type == 1){ + m_options[0].m_text = TheText.Get("FEM_NO"); + m_options[1].m_text = TheText.Get("FEM_YES"); + }else if(m_type == 0){ + m_options[0].m_text = TheText.Get("FEM_OFF"); + m_options[1].m_text = TheText.Get("FEM_ON"); + } + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_bActive) + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + else + m_title.Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + if(m_bActive){ + if(m_title.m_bSelected) + m_options[1].Draw(optionHighlight, m_position.x+x, m_position.y+y); + else + m_options[0].Draw(optionHighlight, m_position.x+x, m_position.y+y); + }else{ + if(m_title.m_bSelected) + m_options[1].Draw(m_position.x+x, m_position.y+y); + else + m_options[0].Draw(m_position.x+x, m_position.y+y); + } + + if(m_bSetTextScale) + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); +} + +void +CMenuOnOff::DrawNormal(float x, float y) +{ + if(m_type == 1){ + m_options[0].m_text = TheText.Get("FEM_NO"); + m_options[1].m_text = TheText.Get("FEM_YES"); + }else if(m_type == 0){ + m_options[0].m_text = TheText.Get("FEM_OFF"); + m_options[1].m_text = TheText.Get("FEM_ON"); + } + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + m_title.Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + if(m_title.m_bSelected) + m_options[1].Draw(m_position.x+x, m_position.y+y); + else + m_options[0].Draw(m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); +} + +void +CMenuOnOff::DrawHighlighted(CRGBA const &titleHighlight, float x, float y) +{ + if(m_type == 1){ + m_options[0].m_text = TheText.Get("FEM_NO"); + m_options[1].m_text = TheText.Get("FEM_YES"); + }else if(m_type == 0){ + m_options[0].m_text = TheText.Get("FEM_OFF"); + m_options[1].m_text = TheText.Get("FEM_ON"); + } + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_bActive) + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + else + m_title.Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + if(m_title.m_bSelected) + m_options[1].Draw(m_position.x+x, m_position.y+y); + else + m_options[0].Draw(m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); +} + +void +CMenuOnOff::SetAlpha(uint8 alpha) +{ + m_title.SetAlpha(alpha); + m_options[0].SetAlpha(alpha); + m_options[1].SetAlpha(alpha); +} + +void +CMenuOnOff::SetShadows(bool bDropShadows, CRGBA const &shadowColor, CVector2D const &shadowOffset) +{ + m_title.SetShadows(bDropShadows, shadowColor, shadowOffset); + m_options[0].SetShadows(bDropShadows, shadowColor, shadowOffset); + m_options[1].SetShadows(bDropShadows, shadowColor, shadowOffset); +} + +/* + * CMenuOnOffTriggered + */ + +void +CMenuOnOffTriggered::SetOptionPosition(float x, float y, Trigger trigger, bool bRightJustify) +{ + CMenuOnOff::SetOptionPosition(x, y, bRightJustify); + if(trigger) + m_trigger = trigger; +} + +void +CMenuOnOffTriggered::SelectCurrentOptionUnderCursor(void) +{ + CMenuOnOff::SelectCurrentOptionUnderCursor(); + if(m_trigger) + m_trigger(this); +} + + + +/* + * CMenuSlider + */ + +char CMenuSlider::Buf8[8]; +wchar CMenuSlider::Buf16[8]; + +void +CMenuSlider::SetColors(const CRGBA &title, const CRGBA &percentage, const CRGBA &left, const CRGBA &right) +{ + m_title.SetColor(title); + m_percentageText.SetColor(percentage); + m_colors[0] = left; + m_colors[1] = right; +} + + +void +CMenuSlider::AddTickBox(float positionX, float positionY, float width, float heightLeft, float heightRight) +{ + m_box.SetPosition(positionX, positionY); + m_size[0].x = width; + m_size[0].y = heightLeft; + m_size[1].x = width; + m_size[1].y = heightRight; +} + +void +CMenuSlider::AddTitle(wchar *text, float positionX, float positionY) +{ + m_title.m_text = text; + m_title.SetPosition(positionX, positionY); +} + +static CRGBA SELECTED_TEXT_COLOR_0(255, 182, 48, 255); + +void +CMenuSlider::Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) +{ + if(m_bActive){ + CRGBA selectionCol = m_colors[0]; + if(optionHighlight.red == SELECTED_TEXT_COLOR_0.red && + optionHighlight.green == SELECTED_TEXT_COLOR_0.green && + optionHighlight.blue == SELECTED_TEXT_COLOR_0.blue && + optionHighlight.alpha == SELECTED_TEXT_COLOR_0.alpha) + selectionCol = m_colors[1]; + + if(m_style == 1){ + // solid bar + CRGBA shadowCol = m_box.GetShadowColor(); + float f = m_value/1000.0f; + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + if(m_box.m_bDropShadow) + CSprite2d::DrawRect( + CRect(boxPos.x + X(m_box.m_shadowOffset.x), + boxPos.y + Y(m_box.m_shadowOffset.y), + boxPos.x + X(m_box.m_shadowOffset.x) + m_size[0].x, + boxPos.y + Y(m_box.m_shadowOffset.y) + m_size[0].y), + shadowCol); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x, boxPos.y + m_size[0].y), + m_colors[1]); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x*f, boxPos.y + m_size[0].y), + selectionCol); + }else if(m_style == 0){ + // ticks... + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + DrawTicks(boxPos, m_size[0], m_size[1].y, + m_value/1000.0f, m_colors[0], selectionCol, m_colors[1], + m_box.m_bDropShadow, m_box.m_shadowOffset, m_box.GetShadowColor()); + } + + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + + if(m_bDrawPercentage){ + sprintf(Buf8, "%d%%", m_value/10); + AsciiToUnicode(Buf8, Buf16); + m_percentageText.m_text = Buf16; + m_percentageText.Draw(optionHighlight, m_position.x+x, m_position.y+y); + } + }else + CMenuSlider::DrawNormal(x, y); +} + +void +CMenuSlider::DrawNormal(float x, float y) +{ + if(m_style == 1){ + // solid bar + CRGBA shadowCol = m_box.GetShadowColor(); + float f = m_value/1000.0f; + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + if(m_box.m_bDropShadow) + CSprite2d::DrawRect( + CRect(boxPos.x + X(m_box.m_shadowOffset.x), + boxPos.y + Y(m_box.m_shadowOffset.y), + boxPos.x + X(m_box.m_shadowOffset.x) + m_size[0].x, + boxPos.y + Y(m_box.m_shadowOffset.y) + m_size[0].y), + shadowCol); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x, boxPos.y + m_size[0].y), + m_colors[1]); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x*f, boxPos.y + m_size[0].y), + m_colors[0]); + }else if(m_style == 0){ + // ticks... + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + DrawTicks(boxPos, m_size[0], m_size[1].y, + m_value/1000.0f, m_colors[0], m_colors[1], + m_box.m_bDropShadow, m_box.m_shadowOffset, m_box.GetShadowColor()); + } + + m_title.Draw(m_position.x+x, m_position.y+y); + + if(m_bDrawPercentage){ + sprintf(Buf8, "%d%%", m_value/10); + AsciiToUnicode(Buf8, Buf16); + m_percentageText.m_text = Buf16; + m_percentageText.Draw(m_percentageText.GetColor(), m_position.x+x, m_position.y+y); + } +} + +void +CMenuSlider::DrawHighlighted(const CRGBA &titleHighlight, float x, float y) +{ + if(m_bActive) + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + else + m_title.Draw(m_position.x+x, m_position.y+y); + + if(m_style == 1){ + // solid bar + CRGBA shadowCol = m_box.GetShadowColor(); + float f = m_value/1000.0f; + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + if(m_box.m_bDropShadow) + CSprite2d::DrawRect( + CRect(boxPos.x + X(m_box.m_shadowOffset.x), + boxPos.y + Y(m_box.m_shadowOffset.y), + boxPos.x + X(m_box.m_shadowOffset.x) + m_size[0].x, + boxPos.y + Y(m_box.m_shadowOffset.y) + m_size[0].y), + shadowCol); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x, boxPos.y + m_size[0].y), + m_colors[1]); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x*f, boxPos.y + m_size[0].y), + m_colors[0]); + }else if(m_style == 0){ + // ticks... + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + DrawTicks(boxPos, m_size[0], m_size[1].y, + m_value/1000.0f, m_colors[0], m_colors[1], + m_box.m_bDropShadow, m_box.m_shadowOffset, m_box.GetShadowColor()); + } + + if(m_bDrawPercentage){ + sprintf(Buf8, "%d%%", m_value/10); + AsciiToUnicode(Buf8, Buf16); + m_percentageText.m_text = Buf16; + m_percentageText.Draw(m_percentageText.GetColor(), m_position.x+x, m_position.y+y); + } +} + +void +CMenuSlider::DrawTicks(const CVector2D &position, const CVector2D &size, float heightRight, float level, const CRGBA &leftCol, const CRGBA &selCol, const CRGBA &rightCol, bool bShadow, const CVector2D &shadowOffset, const CRGBA &shadowColor) +{ + int i; + int numTicks = size.x / X(8.0f); + float dy = heightRight - size.y; + float stepy = dy / numTicks; + int left = level*numTicks; + int drewSelection = 0; + for(i = 0; i < numTicks; i++){ + CRect rect(position.x + X(8.0f)*i, position.y + dy - stepy*i, + position.x + X(8.0f)*i + X(4.0f), position.y + dy + size.y); + if(bShadow){ + CRect shadowRect = rect; + shadowRect.left += X(shadowOffset.x); + shadowRect.right += X(shadowOffset.x); + shadowRect.top += Y(shadowOffset.y); + shadowRect.bottom += Y(shadowOffset.y); + CSprite2d::DrawRect(shadowRect, shadowColor); + } + if(i < left) + CSprite2d::DrawRect(rect, leftCol); + else if(!drewSelection){ + CSprite2d::DrawRect(rect, selCol); + drewSelection = 1; + }else + CSprite2d::DrawRect(rect, rightCol); + } +} + +void +CMenuSlider::DrawTicks(const CVector2D &position, const CVector2D &size, float heightRight, float level, const CRGBA &leftCol, const CRGBA &rightCol, bool bShadow, const CVector2D &shadowOffset, const CRGBA &shadowColor) +{ + int i; + int numTicks = size.x / X(8.0f); + float dy = heightRight - size.y; + float stepy = dy / numTicks; + int left = level*numTicks; + for(i = 0; i < numTicks; i++){ + CRect rect(position.x + X(8.0f)*i, position.y + dy - stepy*i, + position.x + X(8.0f)*i + X(4.0f), position.y + dy + size.y); + if(bShadow){ + CRect shadowRect = rect; + shadowRect.left += X(shadowOffset.x); + shadowRect.right += X(shadowOffset.x); + shadowRect.top += Y(shadowOffset.y); + shadowRect.bottom += Y(shadowOffset.y); + CSprite2d::DrawRect(shadowRect, shadowColor); + } + if(i < left) + CSprite2d::DrawRect(rect, leftCol); + else + CSprite2d::DrawRect(rect, rightCol); + } +} + +void +CMenuSlider::SetAlpha(uint8 alpha) +{ + m_title.SetAlpha(alpha); + m_box.SetAlpha(alpha); + m_someAlpha = alpha; + m_percentageText.SetAlpha(alpha); + m_colors[0].alpha = alpha; + m_colors[1].alpha = alpha; +} + +void +CMenuSlider::SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) +{ + m_title.SetShadows(bDropShadows, shadowColor, shadowOffset); + m_box.SetShadows(bDropShadows, shadowColor, shadowOffset); + m_percentageText.SetShadows(bDropShadows, shadowColor, shadowOffset); +} + +/* + * CMenuSliderTriggered + */ + +void +CMenuSliderTriggered::AddTickBox(float positionX, float positionY, float width, float heightLeft, float heightRight, Trigger trigger, Trigger alwaysTrigger) +{ + CMenuSlider::AddTickBox(positionX, positionY, width, heightLeft, heightRight); + m_trigger = trigger; + m_alwaysTrigger = alwaysTrigger; +} + +void +CMenuSliderTriggered::Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) +{ + CMenuSlider::Draw(optionHighlight, titleHighlight, x, y); + if(m_alwaysTrigger) + m_alwaysTrigger(this); +} + +bool +CMenuSliderTriggered::GoLeft(void) +{ + CMenuSlider::GoLeft(); + if(m_trigger) + m_trigger(this); + return true; +} + +bool +CMenuSliderTriggered::GoRight(void) +{ + CMenuSlider::GoRight(); + if(m_trigger) + m_trigger(this); + return true; +} + +bool +CMenuSliderTriggered::GoLeftStill(void) +{ + CMenuSlider::GoLeftStill(); + if(m_trigger) + m_trigger(this); + return true; +} + +bool +CMenuSliderTriggered::GoRightStill(void) +{ + CMenuSlider::GoRightStill(); + if(m_trigger) + m_trigger(this); + return true; +} + +/* + * CMenuLineLister + */ + +CMenuLineLister::CMenuLineLister(void) + : m_numLines(0), m_width(0.0f), m_height(0.0f), + m_scrollPosition(0.0f), m_scrollSpeed(1.0f), m_lineSpacing(15.0f), field_10E8(0) +{ + int i; + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + m_lineAlphas[i] = 0; + m_lineFade[i] = 0; + } +} + + +void +CMenuLineLister::SetLinesColor(const CRGBA &color) +{ + int i; + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + m_linesLeft[i].SetColor(color); + m_linesRight[i].SetColor(color); + } +} + +void +CMenuLineLister::ResetNumberOfTextLines(void) +{ + int i; + m_numLines = 0; + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + m_lineAlphas[i] = 0; + m_lineFade[i] = 0; + } + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + // note this doesn't clear lines 0-14, probably an oversight + GetLeftLine(i)->m_text = nil; + GetRightLine(i)->m_text = nil; + } +} + +bool +CMenuLineLister::AddTextLine(wchar *left, wchar *right) +{ + CPlaceableShText *leftLine, *rightLine; + if(m_numLines == NUM_LINELISTER_LINES) + return false; + leftLine = GetLeftLine(m_numLines); + leftLine->m_text = left; + leftLine->SetPosition(0.0f, m_lineSpacing*(m_numLines+15)); + rightLine = GetRightLine(m_numLines); + rightLine->m_text = right; + rightLine->SetPosition(leftLine->m_position.x, leftLine->m_position.y); + m_numLines++; + return true; +} + +void +CMenuLineLister::Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) +{ + int i, n; + + m_scrollPosition += m_scrollSpeed; + n = m_numLines + 15; + if(m_scrollSpeed > 0.0f){ + if(m_scrollPosition > n*m_lineSpacing) + m_scrollPosition = 0.0f; + }else{ + if(m_scrollPosition < 0.0f) + m_scrollPosition = n*m_lineSpacing; + } + // this is a weird condition.... + for(i = 0; + m_scrollPosition < i*m_lineSpacing || m_scrollPosition >= (i+1)*m_lineSpacing; + i++); + + float screenPos = 0.0f; + for(; i < n; i++){ + CVector2D linePos = m_linesLeft[i].m_position; + + if(linePos.y+m_position.y - (m_scrollPosition+m_position.y) < Y(64.0f)) + m_lineFade[i] = -4.0f*Abs(m_scrollSpeed); + else + m_lineFade[i] = 4.0f*Abs(m_scrollSpeed); + int newAlpha = m_lineAlphas[i] + m_lineFade[i]; + if(newAlpha < 0) newAlpha = 0; + if(newAlpha > 255) newAlpha = 255; + m_lineAlphas[i] = newAlpha; + + uint8 alpha = m_linesLeft[i].m_shadowColor.alpha; + + // apply alpha + m_linesLeft[i].SetAlpha((alpha*m_lineAlphas[i])>>8); + m_linesRight[i].SetAlpha((alpha*m_lineAlphas[i])>>8); + + m_linesLeft[i].Draw(m_position.x+x, m_position.y+y - m_scrollPosition); + CFont::SetRightJustifyOn(); + m_linesRight[i].Draw(m_position.x+x + m_width, m_position.y+y - m_scrollPosition); + CFont::SetRightJustifyOff(); + + // restore alpha + m_linesLeft[i].SetAlpha(alpha); + m_linesRight[i].SetAlpha(alpha); + + screenPos += m_lineSpacing; + if(screenPos >= m_height) + break; + } + + m_scrollSpeed = 1.0f; +} + +void +CMenuLineLister::SetAlpha(uint8 alpha) +{ + int i; + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + m_linesLeft[i].SetAlpha(alpha); + m_linesRight[i].SetAlpha(alpha); + } +} + +void +CMenuLineLister::SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) +{ + int i; + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + m_linesLeft[i].SetShadows(bDropShadows, shadowColor, shadowOffset); + m_linesRight[i].SetShadows(bDropShadows, shadowColor, shadowOffset); + } +} + + +/* + * CMenuPage + */ + +void +CMenuPage::Initialise(void) +{ + int i; + m_numControls = 0; + m_pCurrentControl = nil; + m_cursor = 0; + for(i = 0; i < NUM_PAGE_WIDGETS; i++) + m_controls[i] = nil; +} + +bool +CMenuPage::AddMenu(CMenuBase *widget) +{ + if(m_numControls >= NUM_PAGE_WIDGETS) + return false; + m_controls[m_numControls] = widget; + if(m_numControls == 0){ + m_pCurrentControl = widget; + m_cursor = 0; + } + m_numControls++; + return true; +} + +bool +CMenuPage::IsActiveMenuTwoState(void) +{ + return m_pCurrentControl && m_pCurrentControl->m_bTwoState; +} + +void +CMenuPage::ActiveMenuTwoState_SelectNextPosition(void) +{ + int sel; + if(m_pCurrentControl == nil || !m_pCurrentControl->m_bTwoState) + return; + m_pCurrentControl->GoFirst(); + sel = m_pCurrentControl->GetMenuSelection(); + if(sel == 1) + m_pCurrentControl->SelectCurrentOptionUnderCursor(); + else if(sel == 0){ + if ( m_pCurrentControl ) + { + if ( !m_pCurrentControl->GoNext() ) + m_pCurrentControl->GoFirst(); + } + + m_pCurrentControl->SelectCurrentOptionUnderCursor(); + } +} + +void +CMenuPage::Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) +{ + int i; + for(i = 0; i < m_numControls; i++) + if(m_controls[i]){ + if(i == m_cursor) + m_controls[i]->Draw(optionHighlight, titleHighlight, x, y); + else + m_controls[i]->DrawNormal(x, y); + } +} + +void +CMenuPage::DrawHighlighted(const CRGBA &titleHighlight, float x, float y) +{ + int i; + for(i = 0; i< m_numControls; i++) + if(m_controls[i]){ + if(i == m_cursor) + m_controls[i]->DrawHighlighted(titleHighlight, x, y); + else + m_controls[i]->DrawNormal(x, y); + } +} + +void +CMenuPage::DrawNormal(float x, float y) +{ + int i; + for(i = 0; i< m_numControls; i++) + if(m_controls[i]) + m_controls[i]->DrawNormal(x, y); +} + +void +CMenuPage::ActivatePage(void) +{ + m_cursor = 0; + if(m_numControls == 0) + return; + for(;;){ + m_pCurrentControl = m_controls[m_cursor]; + if(m_pCurrentControl->GoFirst()) + return; + if(m_cursor == m_numControls-1) + m_cursor = 0; + else + m_cursor++; + } +} + +void +CMenuPage::SetAlpha(uint8 alpha) +{ + int i; + for(i = 0; i< m_numControls; i++) + if(m_controls[i]) + m_controls[i]->SetAlpha(alpha); +} + +void +CMenuPage::SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) +{ + int i; + for(i = 0; i< m_numControls; i++) + if(m_controls[i]) + m_controls[i]->SetShadows(bDropShadows, shadowColor, shadowOffset); +} + +void +CMenuPage::GoUpMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + do{ + if(m_cursor == 0) + m_cursor = m_numControls-1; + else + m_cursor--; + m_pCurrentControl = m_controls[m_cursor]; + }while(!m_pCurrentControl->GoLast()); +} + +void +CMenuPage::GoDownMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + do{ + if(m_cursor == m_numControls-1) + m_cursor = 0; + else + m_cursor++; + m_pCurrentControl = m_controls[m_cursor]; + }while(!m_pCurrentControl->GoFirst()); +} + +void +CMenuPage::GoLeftMenuOnPage(void) +{ + // same as up + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + do{ + if(m_cursor == 0) + m_cursor = m_numControls-1; + else + m_cursor--; + m_pCurrentControl = m_controls[m_cursor]; + }while(!m_pCurrentControl->GoLast()); +} + +void +CMenuPage::GoRightMenuOnPage(void) +{ + // same as right + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + do{ + if(m_cursor == m_numControls-1) + m_cursor = 0; + else + m_cursor++; + m_pCurrentControl = m_controls[m_cursor]; + }while(!m_pCurrentControl->GoFirst()); +} + +/* + * CMenuPageAnyMove + */ + +void +CMenuPageAnyMove::Initialise(void) +{ + int i; + CMenuPage::Initialise(); + for(i = 0; i < NUM_PAGE_WIDGETS; i++){ + m_moveTab[i].left = -1; + m_moveTab[i].right = -1; + m_moveTab[i].up = -1; + m_moveTab[i].down = -1; + } +} + +bool +CMenuPageAnyMove::AddMenu(CMenuBase *widget, FEC_MOVETAB *moveTab) +{ + if(AddMenu(widget)){ + m_moveTab[m_numControls-1] = *moveTab; + return true; + } + return false; +} + +void +CMenuPageAnyMove::GoUpMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + int move = m_moveTab[m_cursor].up; + if(move == -1) + CMenuPage::GoUpMenuOnPage(); + else{ // BUG: no else in original code + m_cursor = move; + m_pCurrentControl = m_controls[m_cursor]; + m_pCurrentControl->GoLast(); + } +} + +void +CMenuPageAnyMove::GoDownMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + int move = m_moveTab[m_cursor].down; + if(move == -1) + CMenuPage::GoDownMenuOnPage(); + else{ // BUG: no else in original code + m_cursor = move; + m_pCurrentControl = m_controls[m_cursor]; + m_pCurrentControl->GoLast(); + } +} + +void +CMenuPageAnyMove::GoLeftMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + int move = m_moveTab[m_cursor].left; + if(move == -1) + CMenuPage::GoLeftMenuOnPage(); + else{ // BUG: no else in original code + m_cursor = move; + m_pCurrentControl = m_controls[m_cursor]; + m_pCurrentControl->GoLast(); + } +} + +void +CMenuPageAnyMove::GoRightMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + int move = m_moveTab[m_cursor].right; + if(move == -1) + CMenuPage::GoRightMenuOnPage(); + else{ // BUG: no else in original code + m_cursor = move; + m_pCurrentControl = m_controls[m_cursor]; + m_pCurrentControl->GoLast(); + } +} diff --git a/src/core/FrontEndControls.h b/src/core/FrontEndControls.h new file mode 100644 index 0000000..68dab90 --- /dev/null +++ b/src/core/FrontEndControls.h @@ -0,0 +1,750 @@ +#pragma once + +enum { + NUM_MULTICHOICE_OPTIONS = 16, + // 50 actual lines and 15 for spacing + NUM_LINELISTER_LINES = 50, + NUM_LINELISTER_LINES_TOTAL = NUM_LINELISTER_LINES + 15, + NUM_PAGE_WIDGETS = 10, +}; + +class CTriggerCaller +{ + bool bHasTrigger; + void *pTrigger; + void (*pFunc)(void *); + int field_C; +public: + + CTriggerCaller() : bHasTrigger(false), pFunc(nil) + {} + + void SetTrigger(void *func, void *trigger) + { + if ( !bHasTrigger ) + { + pFunc = (void (*)(void *))func; + pTrigger = trigger; + bHasTrigger = true; + } + } + + void CallTrigger(void) + { + if ( bHasTrigger && pFunc != nil ) + pFunc(pTrigger); + + bHasTrigger = false; + pFunc = nil; + } + + bool CanCall() + { + return bHasTrigger; + } +}; + +class CPlaceableText +{ +public: + CVector2D m_position; + CRGBA m_color; + wchar *m_text; + + CPlaceableText(void) + : m_position(0.0f, 0.0f), m_color(255, 255, 255, 255), m_text(nil) {} + void SetPosition(float x, float y) { m_position.x = x; m_position.y = y; } + void SetColor(const CRGBA &color) { m_color = color; } + CRGBA GetColor(void) { return m_color; } + void SetAlpha(uint8 alpha) { m_color.alpha = alpha; } +}; + +// No trace of this in the game but it makes the other classes simpler +class CPlaceableTextTwoLines +{ +public: + CPlaceableText m_line1; + CPlaceableText m_line2; + + void SetColor(const CRGBA &color) { m_line1.SetColor(color); m_line2.SetColor(color); } + void SetAlpha(uint8 alpha) { m_line1.SetAlpha(alpha); m_line2.SetAlpha(alpha); } +}; + +// No trace of this in the game but it makes the other classes simpler +class CShadowInfo +{ +public: + bool m_bRightJustify; + bool m_bDropShadow; + CRGBA m_shadowColor; + CVector2D m_shadowOffset; + + CShadowInfo(void) + : m_bRightJustify(false), m_bDropShadow(false), + m_shadowColor(255, 255, 255, 255), + m_shadowOffset(-1.0f, -1.0f) {} + CRGBA GetShadowColor(void) { return m_shadowColor; } + void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset){ + m_bDropShadow = bDropShadows; + m_shadowColor = shadowColor; + m_shadowOffset = shadowOffset; + } +}; + +// No trace of this in the game but it makes the other classes simpler +class CSelectable +{ +public: + bool m_bSelected; + CRGBA m_selectedColor; + + CSelectable(void) : m_bSelected(false) {} + CRGBA GetSelectedColor(void) { return m_selectedColor; } +}; + +class CPlaceableShText : public CPlaceableText, public CShadowInfo +{ +public: + using CPlaceableText::SetPosition; + void SetPosition(float x, float y, bool bRightJustify) { SetPosition(x, y); m_bRightJustify = bRightJustify; } + void SetAlpha(uint8 alpha) { m_shadowColor.alpha = alpha; CPlaceableText::SetAlpha(alpha); } + + void Draw(float x, float y); + void Draw(const CRGBA &color, float x, float y); + // unused arguments it seems + void DrawShWrap(float x, float y, float wrapX, float wrapY) { Draw(x, y); } +}; + +class CPlaceableShTextTwoLines : public CPlaceableTextTwoLines, public CShadowInfo +{ +public: + void SetAlpha(uint8 alpha) { m_shadowColor.alpha = alpha; CPlaceableTextTwoLines::SetAlpha(alpha); } + + void Draw(float x, float y); + void Draw(const CRGBA &color, float x, float y); +}; + +class CPlaceableShOption : public CPlaceableShText, public CSelectable +{ +public: + void SetColors(const CRGBA &normal, const CRGBA &selection) { CPlaceableShText::SetColor(normal); m_selectedColor = selection; } + void SetAlpha(uint8 alpha) { m_selectedColor.alpha = alpha; CPlaceableShText::SetAlpha(alpha); } + + using CPlaceableShText::Draw; + void Draw(const CRGBA &highlightColor, float x, float y, bool bHighlight); +}; + +class CPlaceableShOptionTwoLines : public CPlaceableShTextTwoLines, public CSelectable +{ +public: + void SetColors(const CRGBA &normal, const CRGBA &selection) { CPlaceableShTextTwoLines::SetColor(normal); m_selectedColor = selection; } + void SetAlpha(uint8 alpha) { m_selectedColor.alpha = alpha; CPlaceableShTextTwoLines::SetAlpha(alpha); } + + using CPlaceableShTextTwoLines::Draw; + void Draw(const CRGBA &highlightColor, float x, float y, bool bHighlight); +}; + +class CPlaceableSprite +{ +public: + CSprite2d *m_pSprite; + CVector2D m_position; + CVector2D m_size; + CRGBA m_color; + + CPlaceableSprite(void) + : m_pSprite(nil), m_position(0.0f, 0.0f), + m_size(0.0f, 0.0f), m_color(255, 255, 255, 255) {} + + void SetPosition(float x, float y) { m_position.x = x; m_position.y = y; } + void SetAlpha(uint8 alpha) { m_color.alpha = alpha; } + + void Draw(float x, float y); + void Draw(const CRGBA &color, float x, float y); +}; + +class CPlaceableShSprite +{ +public: + CPlaceableSprite m_sprite; + CPlaceableSprite m_shadow; + bool m_bDropShadow; + + CPlaceableShSprite(void) : m_bDropShadow(false) {} + + void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset){ + m_bDropShadow = bDropShadows; + m_shadow.m_color = shadowColor; + m_shadow.m_position = shadowOffset; + } + void SetAlpha(uint8 alpha) { m_sprite.SetAlpha(alpha); m_shadow.SetAlpha(alpha); } + + void Draw(float x, float y); +}; + + +class CMenuBase +{ +public: + CVector2D m_position; + bool m_bTwoState; + + CMenuBase(void) + : m_position(0.0f, 0.0f), m_bTwoState(false) {} + void SetPosition(float x, float y) { m_position.x = x; m_position.y = y; } + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) = 0; + virtual void DrawNormal(float x, float y) = 0; + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y) = 0; + virtual void SetAlpha(uint8 alpha) = 0; + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) = 0; + virtual bool GoNext(void) = 0; + virtual bool GoPrev(void) = 0; + virtual bool GoDown(void) = 0; + virtual bool GoUp(void) = 0; + virtual bool GoDownStill(void) = 0; + virtual bool GoUpStill(void) = 0; + virtual bool GoLeft(void) = 0; + virtual bool GoRight(void) = 0; + virtual bool GoLeftStill(void) = 0; + virtual bool GoRightStill(void) = 0; + virtual bool GoFirst(void) = 0; + virtual bool GoLast(void) = 0; + virtual void SelectCurrentOptionUnderCursor(void) = 0; + virtual void SelectDefaultCancelAction(void) = 0; + virtual void ActivateMenu(bool first) = 0; + virtual void DeactivateMenu(void) = 0; + virtual int GetMenuSelection(void) = 0; + virtual void SetMenuSelection(int selection) = 0; +}; + +class CMenuDummy : public CMenuBase +{ +public: + bool m_bActive; + + virtual void Draw(const CRGBA &, const CRGBA &, float x, float y) {} + virtual void DrawNormal(float x, float y) {} + virtual void DrawHighlighted(const CRGBA &, float x, float y) {} + virtual void SetAlpha(uint8 alpha) {} + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) {} + virtual bool GoNext(void) { DeactivateMenu(); return false; } + virtual bool GoPrev(void) { DeactivateMenu(); return false; } + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return false; } + virtual bool GoUpStill(void) { return false; } + virtual bool GoLeft(void) { return true; } + virtual bool GoRight(void) { return true; } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { ActivateMenu(true); return true; } + virtual bool GoLast(void) { ActivateMenu(true); return true; } + virtual void SelectCurrentOptionUnderCursor(void) {} + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) { m_bActive = true; } + virtual void DeactivateMenu(void) { m_bActive = false; } + virtual int GetMenuSelection(void) { return -1; } + virtual void SetMenuSelection(int) {} +}; + +class CMenuPictureAndText : public CMenuBase +{ +public: + int m_numSprites; + CPlaceableShSprite m_sprites[5]; + int m_numTexts; + CPlaceableShText m_texts[20]; + + CVector2D m_oldTextScale; + CVector2D m_textScale; + bool m_bSetTextScale; + + float m_wrapX; + float m_oldWrapx; + bool m_bWrap; + // missing some? + + + CMenuPictureAndText(void) + : m_numSprites(0), m_numTexts(0), + m_bSetTextScale(false), m_bWrap(false) {} + + void SetNewOldShadowWrapX(bool bWrapX, float newWrapX, float oldWrapX); + void SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale); + void SetTextsColor(const CRGBA &color); + void AddText(wchar *text, float positionX, float positionY, const CRGBA &color, bool bRightJustify); + void AddPicture(CSprite2d *sprite, CSprite2d *shadow, float positionX, float positionY, float width, float height, const CRGBA &color); + void AddPicture(CSprite2d *sprite, float positionX, float positionY, float width, float height, const CRGBA &color); + + virtual void Draw(const CRGBA &, const CRGBA &, float x, float y); + virtual void DrawNormal(float x, float y) { Draw(CRGBA(0,0,0,0), CRGBA(0,0,0,0), x, y); } + virtual void DrawHighlighted(const CRGBA &, float x, float y) { Draw(CRGBA(0,0,0,0), CRGBA(0,0,0,0), x, y); } + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void) { return false; } + virtual bool GoPrev(void) { return false; } + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return false; } + virtual bool GoUpStill(void) { return false; } + virtual bool GoLeft(void) { return true; } + virtual bool GoRight(void) { return true; } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { return false; } + virtual bool GoLast(void) { return false; } + virtual void SelectCurrentOptionUnderCursor(void) {} + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) {} + virtual void DeactivateMenu(void) {} + virtual int GetMenuSelection(void) { return -1; } + virtual void SetMenuSelection(int) {} +}; + +class CMenuMultiChoice : public CMenuBase +{ +public: + int m_numOptions; + CPlaceableShText m_title; + CPlaceableShOption m_options[NUM_MULTICHOICE_OPTIONS]; + int m_cursor; + CVector2D m_oldTextScale; + CVector2D m_textScale; + bool m_bSetTextScale; + bool m_bSetTitleTextScale; + + CMenuMultiChoice(void) + : m_numOptions(0), m_cursor(-1), + m_bSetTextScale(false), m_bSetTitleTextScale(false) {} + + void AddTitle(wchar *text, float positionX, float positionY, bool bRightJustify); + CPlaceableShOption *AddOption(wchar *text, float positionX, float positionY, bool bSelected, bool bRightJustify); + void SetColors(const CRGBA &title, const CRGBA &normal, const CRGBA &selected); + void SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void); + virtual bool GoPrev(void); + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return false; } + virtual bool GoUpStill(void) { return false; } + virtual bool GoLeft(void) { return GoPrev(); } + virtual bool GoRight(void) { return GoNext(); } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { m_cursor = 0; return true; } + virtual bool GoLast(void) { m_cursor = m_numOptions-1; return true; } + virtual void SelectCurrentOptionUnderCursor(void); + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) { m_cursor = first ? 0 : m_numOptions-1; } + virtual void DeactivateMenu(void) { m_cursor = -1; } + virtual int GetMenuSelection(void); + virtual void SetMenuSelection(int selection); +}; + +class CMenuMultiChoiceTriggered : public CMenuMultiChoice +{ +public: + typedef void (*Trigger)(CMenuMultiChoiceTriggered *); + + Trigger m_triggers[NUM_MULTICHOICE_OPTIONS]; + Trigger m_defaultCancel; + + CMenuMultiChoiceTriggered(void) { Initialise(); } + + void Initialise(void); + CPlaceableShOption *AddOption(wchar *text, float positionX, float positionY, Trigger trigger, bool bSelected, bool bRightJustify); + + virtual void SelectCurrentOptionUnderCursor(void); + virtual void SelectDefaultCancelAction(void); +}; + +class CMenuMultiChoiceTriggeredAlways : public CMenuMultiChoiceTriggered +{ +public: + Trigger m_alwaysNormalTrigger; + Trigger m_alwaysHighlightTrigger; + Trigger m_alwaysTrigger; + + CMenuMultiChoiceTriggeredAlways(void) + : m_alwaysNormalTrigger(nil), m_alwaysHighlightTrigger(nil), m_alwaysTrigger(nil) {} + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); +}; + +class CMenuMultiChoicePictured : public CMenuMultiChoice +{ +public: + CPlaceableSprite m_sprites[NUM_MULTICHOICE_OPTIONS]; + bool m_bHasSprite[NUM_MULTICHOICE_OPTIONS]; + + CMenuMultiChoicePictured(void) { Initialise(); } + void Initialise(void); + using CMenuMultiChoice::AddOption; + CPlaceableShOption *AddOption(CSprite2d *sprite, float positionX, float positionY, const CVector2D &size, bool bSelected); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + virtual void SetAlpha(uint8 alpha); + // unnecessary - same as base class +// virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); +}; + +class CMenuMultiChoicePicturedTriggered : public CMenuMultiChoicePictured +{ +public: + typedef void (*Trigger)(CMenuMultiChoicePicturedTriggered *); + + Trigger m_triggers[NUM_MULTICHOICE_OPTIONS]; + Trigger m_defaultCancel; + + CMenuMultiChoicePicturedTriggered(void) { Initialise(); } + + void Initialise(void); + using CMenuMultiChoicePictured::AddOption; + CPlaceableShOption *AddOption(CSprite2d *sprite, float positionX, float positionY, const CVector2D &size, Trigger trigger, bool bSelected); + + virtual void SelectCurrentOptionUnderCursor(void); + virtual void SelectDefaultCancelAction(void); +}; + +struct FEC_MOVETAB +{ + int8 right; + int8 left; + int8 down; + int8 up; +}; + +class CMenuMultiChoicePicturedTriggeredAnyMove : public CMenuMultiChoicePicturedTriggered +{ +public: + FEC_MOVETAB m_moveTab[NUM_MULTICHOICE_OPTIONS]; + + CMenuMultiChoicePicturedTriggeredAnyMove(void) { Initialise(); } + + void Initialise(void); + using CMenuMultiChoicePicturedTriggered::AddOption; + CPlaceableShOption *AddOption(CSprite2d *sprite, FEC_MOVETAB *moveTab, float positionX, float positionY, const CVector2D &size, Trigger trigger, bool bSelected); + + virtual bool GoDown(void); + virtual bool GoUp(void); + virtual bool GoLeft(void); + virtual bool GoRight(void); +}; + +// copy of CMenuMultiChoice pretty much except for m_options type +class CMenuMultiChoiceTwoLines : public CMenuBase +{ +public: + int m_numOptions; + CPlaceableShText m_title; + CPlaceableShOptionTwoLines m_options[NUM_MULTICHOICE_OPTIONS]; + int m_cursor; + CVector2D m_oldTextScale; + CVector2D m_textScale; + bool m_bSetTextScale; + bool m_bSetTitleTextScale; + + CMenuMultiChoiceTwoLines(void) + : m_numOptions(0), m_cursor(-1), + m_bSetTextScale(false), m_bSetTitleTextScale(false) {} + + void AddTitle(wchar *text, float positionX, float positionY, bool bRightJustify); + CPlaceableShOptionTwoLines *AddOption(wchar *text, float positionX, float positionY, bool bSelected, bool bRightJustify); + CPlaceableShOptionTwoLines *AddOption(wchar *text1, float positionX1, float positionY1, wchar *text2, float positionX2, float positionY2, bool bSelected, bool bRightJustify); + void SetColors(const CRGBA &title, const CRGBA &normal, const CRGBA &selected); + void SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void); + virtual bool GoPrev(void); + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return true; } + virtual bool GoUpStill(void) { return true; } + virtual bool GoLeft(void) { return GoPrev(); } + virtual bool GoRight(void) { return GoNext(); } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { m_cursor = 0; return true; } + virtual bool GoLast(void) { m_cursor = m_numOptions-1; return true; } + virtual void SelectCurrentOptionUnderCursor(void); + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) { m_cursor = first ? 0 : m_numOptions-1; } + virtual void DeactivateMenu(void) { m_cursor = -1; } + virtual int GetMenuSelection(void); + virtual void SetMenuSelection(int selection); +}; + +// copy of CMenuMultiChoiceTriggered except for m_options +class CMenuMultiChoiceTwoLinesTriggered : public CMenuMultiChoiceTwoLines +{ +public: + typedef void (*Trigger)(CMenuMultiChoiceTwoLinesTriggered *); + + Trigger m_triggers[NUM_MULTICHOICE_OPTIONS]; + Trigger m_defaultCancel; + + CMenuMultiChoiceTwoLinesTriggered(void) { Initialise(); } + + void Initialise(void); + CPlaceableShOptionTwoLines *AddOption(wchar *text, float positionX, float positionY, Trigger trigger, bool bSelected, bool bRightJustify); + CPlaceableShOptionTwoLines *AddOption(wchar *text1, float positionX1, float positionY1, wchar *text2, float positionX2, float positionY2, Trigger trigger, bool bSelected, bool bRightJustify); + + virtual void SelectCurrentOptionUnderCursor(void); + virtual void SelectDefaultCancelAction(void); +}; + + +class CMenuOnOff : public CMenuBase +{ +public: + CPlaceableShOption m_title; + CPlaceableShText m_options[2]; + bool m_bActive; + bool m_bSetTextScale; + bool m_bSetTitleTextScale; + CVector2D m_textScale; + CVector2D m_oldTextScale; + int m_type; // 0: on/off 1: yes/no + + void SetColors(const CRGBA &title, const CRGBA &options); + void SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale); + void SetOptionPosition(float x, float y, bool bRightJustify); + void AddTitle(wchar *text, bool bSelected, float positionX, float positionY, bool bRightJustify); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void) { DeactivateMenu(); return false; } + virtual bool GoPrev(void) { DeactivateMenu(); return false; } + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return false; } + virtual bool GoUpStill(void) { return false; } + virtual bool GoLeft(void) { SelectCurrentOptionUnderCursor(); return true; } + virtual bool GoRight(void) { SelectCurrentOptionUnderCursor(); return true; } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { ActivateMenu(true); return true; } + virtual bool GoLast(void) { ActivateMenu(true); return true; } + virtual void SelectCurrentOptionUnderCursor(void) { m_title.m_bSelected ^= 1; } + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) { m_bActive = true; } + virtual void DeactivateMenu(void) { m_bActive = false; } + virtual int GetMenuSelection(void) { return m_title.m_bSelected; } + virtual void SetMenuSelection(int selection) { m_title.m_bSelected = selection; } +}; + +class CMenuOnOffTriggered : public CMenuOnOff +{ +public: + typedef void (*Trigger)(CMenuOnOffTriggered *); + + Trigger m_trigger; + + void SetOptionPosition(float x, float y, Trigger trigger, bool bRightJustify); + + virtual void SelectCurrentOptionUnderCursor(void); +}; + +class CMenuSlider : public CMenuBase +{ +public: + CPlaceableShText m_title; + CPlaceableShText m_box; // not really a text + CRGBA m_colors[2]; // left and right + CVector2D m_size[2]; // left and right + int m_value; + CPlaceableShText m_percentageText; + bool m_bDrawPercentage; +// char field_8D; +// char field_8E; +// char field_8F; + uint8 m_someAlpha; +// char field_91; +// char field_92; +// char field_93; + bool m_bActive; + int m_style; + + static char Buf8[8]; + static wchar Buf16[8]; + + CMenuSlider(void) + : m_value(0), m_bDrawPercentage(false), m_bActive(false), m_style(0) + { + AddTickBox(0.0f, 0.0f, 100.0f, 10.0f, 10.0f); //todo + } + + void SetColors(const CRGBA &title, const CRGBA &percentage, const CRGBA &left, const CRGBA &right); + void DrawTicks(const CVector2D &position, const CVector2D &size, float heightRight, float level, const CRGBA &leftCol, const CRGBA &selCol, const CRGBA &rightCol, bool bShadow, const CVector2D &shadowOffset, const CRGBA &shadowColor); + void DrawTicks(const CVector2D &position, const CVector2D &size, float heightRight, float level, const CRGBA &leftCol, const CRGBA &rightCol, bool bShadow, const CVector2D &shadowOffset, const CRGBA &shadowColor); + void AddTickBox(float positionX, float positionY, float width, float heigthLeft, float heightRight); + void AddTitle(wchar *text, float positionX, float positionY); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void) { DeactivateMenu(); return false; } + virtual bool GoPrev(void) { DeactivateMenu(); return false; } + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return false; } + virtual bool GoUpStill(void) { return false; } + virtual bool GoLeft(void) { if(m_value < 0) m_value = 0; return true; } + virtual bool GoRight(void) { if(m_value > 1000) m_value = 1000; return true; } + virtual bool GoLeftStill(void) { m_value -= 8; if(m_value < 0) m_value = 0; return true; } + virtual bool GoRightStill(void) { m_value += 8; if(m_value > 1000) m_value = 1000; return true; } + virtual bool GoFirst(void) { ActivateMenu(true); return true; } + virtual bool GoLast(void) { ActivateMenu(true); return true; } + virtual void SelectCurrentOptionUnderCursor(void) {} + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) { m_bActive = true; } + virtual void DeactivateMenu(void) { m_bActive = false; } + virtual int GetMenuSelection(void) { return m_value/10; } + virtual void SetMenuSelection(int selection) { m_value = selection*10; } +}; + +class CMenuSliderTriggered : public CMenuSlider +{ +public: + typedef void (*Trigger)(CMenuSliderTriggered *); + + Trigger m_trigger; + Trigger m_alwaysTrigger; + + CMenuSliderTriggered(void) + : m_trigger(nil), m_alwaysTrigger(nil) {} + + void AddTickBox(float positionX, float positionY, float width, float heigthLeft, float heightRight, Trigger trigger, Trigger alwaysTrigger); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual bool GoLeft(void); + virtual bool GoRight(void); + virtual bool GoLeftStill(void); + virtual bool GoRightStill(void); +}; + + +class CMenuLineLister : public CMenuBase +{ +public: + float m_width; + float m_height; + int m_numLines; + CPlaceableShText m_linesLeft[NUM_LINELISTER_LINES_TOTAL]; + CPlaceableShText m_linesRight[NUM_LINELISTER_LINES_TOTAL]; + uint8 m_lineAlphas[NUM_LINELISTER_LINES_TOTAL]; + int8 m_lineFade[NUM_LINELISTER_LINES_TOTAL]; + float m_scrollPosition; + float m_scrollSpeed; + int field_10E8; + float m_lineSpacing; + + CMenuLineLister(void); + + void SetLinesColor(const CRGBA &color); + void ResetNumberOfTextLines(void); + bool AddTextLine(wchar *left, wchar *right); + + CPlaceableShText *GetLeftLine(int i) { return &m_linesLeft[(i%NUM_LINELISTER_LINES) + 15]; }; + CPlaceableShText *GetRightLine(int i) { return &m_linesRight[(i%NUM_LINELISTER_LINES) + 15]; }; + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y) { Draw(CRGBA(0,0,0,0), CRGBA(0,0,0,0), x, y); } + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y) { Draw(CRGBA(0,0,0,0), CRGBA(0,0,0,0), x, y); } + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void) { return false; } + virtual bool GoPrev(void) { return false; } + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { m_scrollSpeed = 0.0f; return true; } + virtual bool GoUpStill(void) { m_scrollSpeed *= 6.0f; return true; } + virtual bool GoLeft(void) { return true; } + virtual bool GoRight(void) { return true; } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { return true; } + virtual bool GoLast(void) { return true; } + virtual void SelectCurrentOptionUnderCursor(void) {} + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) {} + virtual void DeactivateMenu(void) {} + virtual int GetMenuSelection(void) { return -1; } + virtual void SetMenuSelection(int selection) {} +}; + +class CMenuPage +{ +public: + CMenuBase *m_controls[NUM_PAGE_WIDGETS]; + int m_numControls; + CMenuBase *m_pCurrentControl; + int m_cursor; + + CMenuPage(void) { Initialise(); } + void Initialise(void); + bool AddMenu(CMenuBase *widget); + + bool IsActiveMenuTwoState(void); + void ActiveMenuTwoState_SelectNextPosition(void); + void Draw(const CRGBA &,const CRGBA &, float, float); + void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + void DrawNormal(float x, float y); + void ActivatePage(void); + void SetAlpha(uint8 alpha); + void SetShadows(bool, const CRGBA &, const CVector2D &); + void GoPrev(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoPrev()) m_pCurrentControl->GoLast(); } } + void GoNext(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoNext()) m_pCurrentControl->GoFirst(); } } + void GoLeft(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoLeft()) m_pCurrentControl->GoLast(); } } + void GoRight(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoRight()) m_pCurrentControl->GoFirst(); } } + void GoUp(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoUp()) m_pCurrentControl->GoLast(); } } + void GoDown(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoDown()) m_pCurrentControl->GoFirst(); } } + void GoLeftStill(void) { if(m_pCurrentControl) m_pCurrentControl->GoLeftStill(); } + void GoRightStill(void) { if(m_pCurrentControl) m_pCurrentControl->GoRightStill(); } + void GoUpStill(void) { if(m_pCurrentControl) m_pCurrentControl->GoUpStill(); } + void GoDownStill(void) { if(m_pCurrentControl) m_pCurrentControl->GoDownStill(); } + void SelectDefaultCancelAction(void) { if(m_pCurrentControl) m_pCurrentControl->SelectDefaultCancelAction(); } + void SelectCurrentOptionUnderCursor(void) { if(m_pCurrentControl) m_pCurrentControl->SelectCurrentOptionUnderCursor(); } + + virtual void GoUpMenuOnPage(void); + virtual void GoDownMenuOnPage(void); + virtual void GoLeftMenuOnPage(void); + virtual void GoRightMenuOnPage(void); +}; + +class CMenuPageAnyMove : public CMenuPage +{ +public: + FEC_MOVETAB m_moveTab[NUM_PAGE_WIDGETS]; + + CMenuPageAnyMove(void) { Initialise(); } + void Initialise(void); + using CMenuPage::AddMenu; + bool AddMenu(CMenuBase *widget, FEC_MOVETAB *moveTab); + + virtual void GoUpMenuOnPage(void); + virtual void GoDownMenuOnPage(void); + virtual void GoLeftMenuOnPage(void); + virtual void GoRightMenuOnPage(void); +}; \ No newline at end of file diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp new file mode 100644 index 0000000..666774f --- /dev/null +++ b/src/core/Frontend.cpp @@ -0,0 +1,6802 @@ +#define FORCE_PC_SCALING +#define WITHWINDOWS +#define WITHDINPUT +#include "common.h" +#ifndef PS2_MENU +#include "crossplatform.h" +#include "platform.h" +#include "Frontend.h" +#include "Font.h" +#include "Pad.h" +#include "Text.h" +#include "main.h" +#include "RwHelper.h" +#include "Timer.h" +#include "Game.h" +#include "DMAudio.h" +#include "FileMgr.h" +#include "Streaming.h" +#include "TxdStore.h" +#include "General.h" +#include "GenericGameStorage.h" +#include "Script.h" +#include "Camera.h" +#include "ControllerConfig.h" +#include "Vehicle.h" +#include "MBlur.h" +#include "PlayerSkin.h" +#include "PlayerInfo.h" +#include "World.h" +#include "Renderer.h" +#include "CdStream.h" +#include "Radar.h" +#include "Stats.h" +#include "Messages.h" +#include "FileLoader.h" +#include "frontendoption.h" + +// Game has colors inlined in code. +// For easier modification we collect them here: +const CRGBA LABEL_COLOR(235, 170, 50, 255); +const CRGBA SELECTION_HIGHLIGHTBG_COLOR(100, 200, 50, 50); +const CRGBA MENUOPTION_COLOR = LABEL_COLOR; +const CRGBA SELECTEDMENUOPTION_COLOR(255, 217, 106, 255); +const CRGBA HEADER_COLOR(0, 0, 0, 255); +const CRGBA DARKMENUOPTION_COLOR(155, 117, 6, 255); +const CRGBA SLIDERON_COLOR = SELECTEDMENUOPTION_COLOR; +const CRGBA SLIDEROFF_COLOR(185, 120, 0, 255); +const CRGBA LIST_BACKGROUND_COLOR(200, 200, 50, 50); +const CRGBA LIST_OPTION_COLOR(155, 155, 155, 255); +const CRGBA INACTIVE_RADIO_COLOR(225, 0, 0, 170); +const CRGBA SCROLLBAR_COLOR = LABEL_COLOR; +const CRGBA CONTSETUP_HIGHLIGHTBG_COLOR(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, 210); +const CRGBA CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, 150); + +// This is PS2 menu leftover, and variable name is original. They forgot it here and used in PrintBriefs once (but didn't use the output) +#if defined(FIX_BUGS) && !defined(PS2_LIKE_MENU) +const CRGBA TEXT_COLOR = LABEL_COLOR; +#else +const CRGBA TEXT_COLOR = CRGBA(150, 110, 30, 255); // PS2 option color +#endif + +#define TIDY_UP_PBP // ProcessButtonPresses +#define MAX_VISIBLE_LIST_ROW 30 +#define SCROLLBAR_MAX_HEIGHT 263.0f // not in end result +#define SCROLLABLE_PAGES +#define RED_DELETE_BACKGROUND + +#ifdef SCROLLABLE_STATS_PAGE +#define isPlainTextScreen(screen) (screen == MENUPAGE_BRIEFS) +#else +#define isPlainTextScreen(screen) (screen == MENUPAGE_BRIEFS || screen == MENUPAGE_STATS) +#endif + +#define hasNativeList(screen) (screen == MENUPAGE_MULTIPLAYER_FIND_GAME || screen == MENUPAGE_SKIN_SELECT \ + || screen == MENUPAGE_KEYBOARD_CONTROLS) + +#ifdef SCROLLABLE_PAGES +#define MAX_VISIBLE_OPTION 12 +#define MAX_VISIBLE_OPTION_ON_SCREEN (hasNativeList(m_nCurrScreen) ? MAX_VISIBLE_LIST_ROW : MAX_VISIBLE_OPTION) +#define SCREEN_HAS_AUTO_SCROLLBAR (m_nTotalListRow > MAX_VISIBLE_OPTION && !hasNativeList(m_nCurrScreen)) + +int GetOptionCount(int screen) +{ + int i = 0; + for (; i < NUM_MENUROWS && aScreens[screen].m_aEntries[i].m_Action != MENUACTION_NOTHING; i++); + return i; +} + +#define SETUP_SCROLLING(screen) \ + if (!hasNativeList(screen)) { \ + m_nTotalListRow = GetOptionCount(screen); \ + if (m_nTotalListRow > MAX_VISIBLE_OPTION) { \ + m_nSelectedListRow = 0; \ + m_nFirstVisibleRowOnList = 0; \ + m_nScrollbarTopMargin = 0; \ + } \ + } +#else +#define MAX_VISIBLE_OPTION_ON_SCREEN MAX_VISIBLE_LIST_ROW +#define SETUP_SCROLLING(screen) +#endif + +#ifdef TRIANGLE_BACK_BUTTON +#define GetBackJustUp GetTriangleJustUp +#define GetBackJustDown GetTriangleJustDown +#elif defined(CIRCLE_BACK_BUTTON) +#define GetBackJustUp GetCircleJustUp +#define GetBackJustDown GetCircleJustDown +#else +#define GetBackJustUp GetSquareJustUp +#define GetBackJustDown GetSquareJustDown +#endif + +#ifdef MENU_MAP +bool CMenuManager::bMenuMapActive = false; +float CMenuManager::fMapSize; +float CMenuManager::fMapCenterY; +float CMenuManager::fMapCenterX; +#endif + +#ifdef PS2_LIKE_MENU +BottomBarOption bbNames[8]; +int bbTabCount = 0; +bool bottomBarActive = false; +int pendingScreen = -1; +int pendingOption = -1; +int curBottomBarOption = -1; +int hoveredBottomBarOption = -1; +#endif + +#ifdef CUTSCENE_BORDERS_SWITCH +bool CMenuManager::m_PrefsCutsceneBorders = true; +#endif + +#ifdef MULTISAMPLING +int8 CMenuManager::m_nPrefsMSAALevel = 0; +int8 CMenuManager::m_nDisplayMSAALevel = 0; +#endif + +#ifdef NO_ISLAND_LOADING +int8 CMenuManager::m_PrefsIslandLoading = ISLAND_LOADING_LOW; +#endif + +#ifdef GAMEPAD_MENU +#ifdef __SWITCH__ +int8 CMenuManager::m_PrefsControllerType = CONTROLLER_NINTENDO_SWITCH; +#else +int8 CMenuManager::m_PrefsControllerType = CONTROLLER_XBOXONE; +#endif +#endif + +int32 CMenuManager::OS_Language = LANG_ENGLISH; +int8 CMenuManager::m_PrefsUseVibration; +int8 CMenuManager::m_DisplayControllerOnFoot; +int8 CMenuManager::m_PrefsVsync = 1; +int8 CMenuManager::m_PrefsVsyncDisp = 1; +int8 CMenuManager::m_PrefsFrameLimiter = 1; +int8 CMenuManager::m_PrefsShowSubtitles = 1; +int8 CMenuManager::m_PrefsSpeakers; +int32 CMenuManager::m_ControlMethod; +int8 CMenuManager::m_PrefsDMA = 1; +int32 CMenuManager::m_PrefsLanguage; +uint8 CMenuManager::m_PrefsStereoMono; // unused except restore settings + +bool CMenuManager::m_PrefsAllowNastyGame = true; +bool CMenuManager::m_bStartUpFrontEndRequested; +bool CMenuManager::m_bShutDownFrontEndRequested; + +#ifdef ASPECT_RATIO_SCALE +int8 CMenuManager::m_PrefsUseWideScreen = AR_AUTO; +#else +int8 CMenuManager::m_PrefsUseWideScreen; +#endif + +int8 CMenuManager::m_PrefsRadioStation; +int32 CMenuManager::m_PrefsBrightness = 256; +float CMenuManager::m_PrefsLOD = CRenderer::ms_lodDistScale; +int8 CMenuManager::m_bFrontEnd_ReloadObrTxtGxt; +int32 CMenuManager::m_PrefsMusicVolume = 102; +int32 CMenuManager::m_PrefsSfxVolume = 102; + +char CMenuManager::m_PrefsSkinFile[256] = DEFAULT_SKIN_NAME; + +int32 CMenuManager::m_KeyPressedCode = -1; + +float MENU_TEXT_SIZE_X = SMALLTEXT_X_SCALE; +float MENU_TEXT_SIZE_Y = SMALLTEXT_Y_SCALE; + +bool holdingScrollBar; // *(bool*)0x628D59; // not original name +int32 CMenuManager::m_SelectedMap; +int32 CMenuManager::m_SelectedGameType; + +// Used in a hidden menu +uint8 CMenuManager::m_PrefsPlayerRed = 255; +uint8 CMenuManager::m_PrefsPlayerGreen = 128; +uint8 CMenuManager::m_PrefsPlayerBlue; // why?? + +CMenuManager FrontEndMenuManager; + +uint32 TimeToStopPadShaking; +char *pEditString; +int32 *pControlEdit; +bool DisplayComboButtonErrMsg; +int32 MouseButtonJustClicked; +int32 JoyButtonJustClicked; +//int32 *pControlTemp = 0; + +#ifndef MASTER +bool CMenuManager::m_PrefsMarketing = false; +bool CMenuManager::m_PrefsDisableTutorials = false; +#endif // !MASTER + +const char* FrontendFilenames[][2] = { + {"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", "" }, +}; + +#ifdef MENU_MAP +const char* MapFilenames[][2] = { + {"mapMid01", "mapMid01A"}, + {"mapMid02", "mapMid02A"}, + {"mapMid03", "mapMid03A"}, + {"mapBot01", "mapBot01A"}, + {"mapBot02", "mapBot02A"}, + {"mapBot03", "mapBot03A"}, + {"mapTop01", "mapTop01A"}, + {"mapTop02", "mapTop02A"}, + {"mapTop03", "mapTop03A"}, +}; +CSprite2d CMenuManager::m_aMapSprites[NUM_MAP_SPRITES]; +#endif + +// 0x5F3344 +const char* MenuFilenames[][2] = { + {"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 } +}; + +#define MENU_X_RIGHT_ALIGNED(x) SCALE_AND_CENTER_X(DEFAULT_SCREEN_WIDTH - (x)) + +#ifdef ASPECT_RATIO_SCALE +// All of the defines below replace the StretchX function. Otherwise use SCREEN_SCALE_X. +#define MENU_X_LEFT_ALIGNED(x) SCALE_AND_CENTER_X(x) +#define MENU_X(x) SCREEN_SCALE_X(x) +#define MENU_Y(y) SCREEN_SCALE_Y(y) +#else +#define MENU_X_LEFT_ALIGNED(x) StretchX(x) +#define MENU_X(x) StretchX(x) +#define MENU_Y(y) StretchY(y) +#endif + +#ifdef PS2_LIKE_MENU +#define PAGE_NAME_X MENU_X_RIGHT_ALIGNED +#else +#define PAGE_NAME_X SCREEN_SCALE_FROM_RIGHT +#endif + +// Seperate func. in VC +#define ChangeScreen(screen, option, updateDelay, clearAlpha) \ + do { \ + m_nPrevScreen = m_nCurrScreen; \ + int newOpt = option; \ + SETUP_SCROLLING(screen) \ + m_nCurrScreen = screen; \ + m_nCurrOption = newOpt; \ + if(updateDelay) \ + m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode(); \ + if(clearAlpha) \ + m_nMenuFadeAlpha = 0; \ + } while(0) + +#define SET_FONT_FOR_MENU_HEADER \ + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, FadeIn(255))); \ + CFont::SetRightJustifyOn(); \ + CFont::SetScale(MENU_X(MENUHEADER_WIDTH), MENU_Y(MENUHEADER_HEIGHT)); \ + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + +#define RESET_FONT_FOR_NEW_PAGE \ + CFont::SetBackgroundOff(); \ + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT), MENU_Y(MENUACTION_SCALE_MULT)); \ + CFont::SetPropOn(); \ + CFont::SetCentreOff(); \ + CFont::SetJustifyOn(); \ + CFont::SetRightJustifyOff(); \ + CFont::SetBackGroundOnlyTextOn(); \ + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(MENU_X_MARGIN)); \ + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(MENU_X_MARGIN - 2.0f)); + +#define SET_FONT_FOR_HELPER_TEXT \ + CFont::SetCentreOn(); \ + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE), MENU_Y(SMALLESTTEXT_Y_SCALE)); \ + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + +#define SET_FONT_FOR_LIST_ITEM \ + CFont::SetRightJustifyOff(); \ + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE), MENU_Y(SMALLESTTEXT_Y_SCALE)); \ + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + +// value must be between 0.0-1.0 +#define ProcessSlider(value, increaseAction, decreaseAction, hoverStartX, hoverEndX) \ + do { \ + lastActiveBarX = DisplaySlider(MENU_X_RIGHT_ALIGNED(MENUSLIDER_X + columnWidth), MENU_Y(bitAboveNextItemY), MENU_Y(smallestSliderBar), MENU_Y(usableLineHeight), MENU_X(MENUSLIDER_UNK), value); \ + if (i != m_nCurrOption || !itemsAreSelectable) \ + break; \ + \ + if (CheckHover(hoverStartX, lastActiveBarX - MENU_X(10.0f), MENU_Y(nextYToUse), MENU_Y(28.0f + nextYToUse))) \ + m_nHoverOption = decreaseAction; \ + \ + if (!CheckHover(MENU_X(10.0f) + lastActiveBarX, hoverEndX, MENU_Y(nextYToUse), MENU_Y(28.0f + nextYToUse))) \ + break; \ + \ + m_nHoverOption = increaseAction; \ + if (m_nMousePosX < MENU_X_RIGHT_ALIGNED(MENUSLIDER_X + columnWidth)) \ + m_nHoverOption = HOVEROPTION_NOT_HOVERING; \ + } while(0) + +#define ProcessRadioIcon(sprite, x, y, radioId, hoverOpt) \ + do { \ + sprite.Draw(x, y, MENU_X(MENURADIO_ICON_SCALE), MENU_Y(MENURADIO_ICON_SCALE), radioId == m_PrefsRadioStation ? CRGBA(255, 255, 255, 255) : \ + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, INACTIVE_RADIO_COLOR.a)); \ + if (CheckHover(x, x + MENU_X(MENURADIO_ICON_SCALE), y, y + MENU_Y(MENURADIO_ICON_SCALE))) \ + m_nHoverOption = hoverOpt; \ + } while (0) + +// --- Functions not in the game/inlined starts + +void +CMenuManager::ScrollUpListByOne() +{ + if (m_nSelectedListRow == m_nFirstVisibleRowOnList) { + if (m_nFirstVisibleRowOnList > 0) { + m_nSelectedListRow--; + m_nFirstVisibleRowOnList--; + m_nScrollbarTopMargin -= SCROLLBAR_MAX_HEIGHT / m_nTotalListRow; + } + } else { + m_nSelectedListRow--; + } +} + +void +CMenuManager::ScrollDownListByOne() +{ + if (m_nSelectedListRow == m_nFirstVisibleRowOnList + MAX_VISIBLE_OPTION_ON_SCREEN - 1) { + if (m_nFirstVisibleRowOnList < m_nTotalListRow - MAX_VISIBLE_OPTION_ON_SCREEN) { + m_nSelectedListRow++; + m_nFirstVisibleRowOnList++; + m_nScrollbarTopMargin += SCROLLBAR_MAX_HEIGHT / m_nTotalListRow; + } + } else { + if (m_nSelectedListRow < m_nTotalListRow - 1) { + m_nSelectedListRow++; + } + } +} + +void +CMenuManager::PageUpList(bool playSoundOnSuccess) +{ + if (m_nTotalListRow > MAX_VISIBLE_OPTION_ON_SCREEN) { + if (m_nFirstVisibleRowOnList > 0) { + if(playSoundOnSuccess) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nFirstVisibleRowOnList = Max(0, m_nFirstVisibleRowOnList - MAX_VISIBLE_OPTION_ON_SCREEN); + m_nSelectedListRow = Min(m_nSelectedListRow, m_nFirstVisibleRowOnList + MAX_VISIBLE_OPTION_ON_SCREEN - 1); + } else { + m_nFirstVisibleRowOnList = 0; + m_nSelectedListRow = 0; + } + m_nScrollbarTopMargin = (SCROLLBAR_MAX_HEIGHT / m_nTotalListRow) * m_nFirstVisibleRowOnList; + } +} + +void +CMenuManager::PageDownList(bool playSoundOnSuccess) +{ + if (m_nTotalListRow > MAX_VISIBLE_OPTION_ON_SCREEN) { + if (m_nFirstVisibleRowOnList < m_nTotalListRow - MAX_VISIBLE_OPTION_ON_SCREEN) { + if(playSoundOnSuccess) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nFirstVisibleRowOnList = Min(m_nFirstVisibleRowOnList + MAX_VISIBLE_OPTION_ON_SCREEN, m_nTotalListRow - MAX_VISIBLE_OPTION_ON_SCREEN); + m_nSelectedListRow = Max(m_nSelectedListRow, m_nFirstVisibleRowOnList); + } else { + m_nFirstVisibleRowOnList = m_nTotalListRow - MAX_VISIBLE_OPTION_ON_SCREEN; + m_nSelectedListRow = m_nTotalListRow - 1; + } + m_nScrollbarTopMargin = (SCROLLBAR_MAX_HEIGHT / m_nTotalListRow) * m_nFirstVisibleRowOnList; + } +} + +#ifdef CUSTOM_FRONTEND_OPTIONS +bool ScreenHasOption(int screen, const char* gxtKey) +{ + for (int i = 0; i < NUM_MENUROWS; i++) { + if (strcmp(gxtKey, aScreens[screen].m_aEntries[i].m_EntryName) == 0) + return true; + } + return false; +} +#endif + +void +CMenuManager::ThingsToDoBeforeGoingBack() +{ + if ((m_nCurrScreen == MENUPAGE_SKIN_SELECT) && strcmp(m_aSkinName, m_PrefsSkinFile) != 0) { + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); +#ifdef CUSTOM_FRONTEND_OPTIONS + } else if (ScreenHasOption(m_nCurrScreen, "FEA_RSS")) { +#else + } else if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { +#endif + if (m_nPrefsAudio3DProviderIndex != -1) + m_nPrefsAudio3DProviderIndex = DMAudio.GetCurrent3DProviderIndex(); +#ifdef TIDY_UP_PBP + DMAudio.StopFrontEndTrack(); + OutputDebugString("FRONTEND AUDIO TRACK STOPPED"); +#endif + +#ifdef CUSTOM_FRONTEND_OPTIONS + } else if (ScreenHasOption(m_nCurrScreen, "FED_RES")) { +#else + } else if (m_nCurrScreen == MENUPAGE_DISPLAY_SETTINGS) { +#endif + m_nDisplayVideoMode = m_nPrefsVideoMode; + } + + if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + CPlayerSkin::EndFrontendSkinEdit(); + } + + if ((m_nCurrScreen == MENUPAGE_SKIN_SELECT) || (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS)) { + m_nTotalListRow = 0; + } + +#ifdef SCROLLABLE_PAGES + if (SCREEN_HAS_AUTO_SCROLLBAR) { + m_nSelectedListRow = 0; + m_nFirstVisibleRowOnList = 0; + m_nScrollbarTopMargin = 0; + } +#endif + +#ifdef CUSTOM_FRONTEND_OPTIONS + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption]; + + if (option.m_Action == MENUACTION_CFO_DYNAMIC) + if(option.m_CFODynamic->buttonPressFunc) + option.m_CFODynamic->buttonPressFunc(FEOPTION_ACTION_FOCUSLOSS); + + if (option.m_Action == MENUACTION_CFO_SELECT && option.m_CFOSelect->onlyApplyOnEnter && option.m_CFOSelect->lastSavedValue != option.m_CFOSelect->displayedValue) + option.m_CFOSelect->displayedValue = *(int8*)option.m_CFO->value = option.m_CFOSelect->lastSavedValue; + + if (aScreens[m_nCurrScreen].returnPrevPageFunc) { + aScreens[m_nCurrScreen].returnPrevPageFunc(); + } +#endif +} + +int8 +CMenuManager::GetPreviousPageOption() +{ +#ifndef CUSTOM_FRONTEND_OPTIONS + return !m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_ParentEntry[1] : aScreens[m_nCurrScreen].m_ParentEntry[0]; +#else + int8 prevPage = !m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0]; + + if (prevPage == -1) // Game also does same + return 0; + + prevPage = prevPage == MENUPAGE_NONE ? (!m_bGameNotLoaded ? MENUPAGE_PAUSE_MENU : MENUPAGE_START_MENU) : prevPage; + + for (int i = 0; i < NUM_MENUROWS; i++) { + if (aScreens[prevPage].m_aEntries[i].m_Action >= MENUACTION_NOTHING) { // CFO check + if (aScreens[prevPage].m_aEntries[i].m_TargetMenu == m_nCurrScreen) { + return i; + } + } + } + + // This shouldn't happen + return 0; +#endif +} + +void +CMenuManager::ProcessList(bool &goBack, bool &optionSelected) +{ + if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + m_nTotalListRow = m_nSkinsTotal; + } + if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + // GetNumOptionsCntrlConfigScreens would have been a better choice + m_nTotalListRow = m_ControlMethod == CONTROL_CLASSIC ? 30 : 25; + if (m_nSelectedListRow > m_nTotalListRow) + m_nSelectedListRow = m_nTotalListRow - 1; + } + +#ifndef TIDY_UP_PBP + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown()) { + m_bShowMouse = 0; + optionSelected = true; + } +#endif + if (CPad::GetPad(0)->GetBackspaceJustDown() && m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS && !field_535) { + if (m_nCurrExLayer == HOVEROPTION_LIST) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + m_bWaitingForNewKeyBind = true; + m_bStartWaitingForKeyBind = true; + m_bKeyChangeNotProcessed = true; + pControlEdit = &m_KeyPressedCode; + } + } else { + field_535 = false; + } + + static uint32 lastTimeClickedScrollButton = 0; + + if (CTimer::GetTimeInMillisecondsPauseMode() - lastTimeClickedScrollButton >= 200) { + m_bPressedPgUpOnList = false; + m_bPressedPgDnOnList = false; + m_bPressedUpOnList = false; + m_bPressedDownOnList = false; + m_bPressedScrollButton = false; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + } + + if (CPad::GetPad(0)->GetTabJustDown()) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + m_bShowMouse = false; + switch (m_nCurrExLayer) { + case HOVEROPTION_BACK: + default: + m_nCurrExLayer = HOVEROPTION_LIST; + break; + case HOVEROPTION_LIST: + m_nCurrExLayer = HOVEROPTION_USESKIN; + break; + case HOVEROPTION_USESKIN: + m_nCurrExLayer = HOVEROPTION_BACK; + } + if (((m_nCurrScreen == MENUPAGE_SKIN_SELECT) && (m_nCurrExLayer == HOVEROPTION_USESKIN)) && strcmp(m_aSkinName, m_PrefsSkinFile) == 0) { + m_nCurrExLayer = HOVEROPTION_BACK; + } + if ((m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) && (m_nCurrExLayer == HOVEROPTION_USESKIN)) { + m_nCurrExLayer = HOVEROPTION_BACK; + } + } + + bool pressed = false; + if (CPad::GetPad(0)->GetUp() || CPad::GetPad(0)->GetAnaloguePadUp() || CPad::GetPad(0)->GetDPadUpJustDown()) { + m_bShowMouse = false; + pressed = true; + } else if (CPad::GetPad(0)->GetMouseWheelUpJustUp()) { + m_bShowMouse = true; + pressed = true; + } + + // Up + if (pressed) { + m_nCurrExLayer = HOVEROPTION_LIST; + if (!m_bPressedUpOnList) { + m_bPressedUpOnList = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + ScrollUpListByOne(); + } + } else { + m_bPressedUpOnList = false; + } + + pressed = false; + if (CPad::GetPad(0)->GetDown() || CPad::GetPad(0)->GetAnaloguePadDown() || CPad::GetPad(0)->GetDPadDownJustDown()) { + m_bShowMouse = false; + pressed = true; + } else if (CPad::GetPad(0)->GetMouseWheelDownJustDown()) { + m_bShowMouse = true; + pressed = true; + } + + // Down + if (pressed) { + m_nCurrExLayer = HOVEROPTION_LIST; + if (!m_bPressedDownOnList) { + m_bPressedDownOnList = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + ScrollDownListByOne(); + } + } else { + m_bPressedDownOnList = false; + } + + if (m_nCurrScreen != MENUPAGE_KEYBOARD_CONTROLS) { + if (!CPad::GetPad(0)->GetPageUp()) { + m_bPressedPgUpOnList = false; + } else { + m_nCurrExLayer = HOVEROPTION_LIST; + if (!m_bPressedPgUpOnList) { + m_bPressedPgUpOnList = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + PageUpList(false); + } + } + if (!CPad::GetPad(0)->GetPageDown()) { + m_bPressedPgDnOnList = false; + } else { + m_nCurrExLayer = HOVEROPTION_LIST; + if (!m_bPressedPgDnOnList) { + m_bPressedPgDnOnList = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + PageDownList(false); + } + } + if (CPad::GetPad(0)->GetHome()) { + m_nCurrExLayer = HOVEROPTION_LIST; + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + if (m_nTotalListRow >= MAX_VISIBLE_OPTION_ON_SCREEN) { + m_nFirstVisibleRowOnList = 0; + } + m_nSelectedListRow = 0; + m_nScrollbarTopMargin = (SCROLLBAR_MAX_HEIGHT / m_nTotalListRow) * m_nFirstVisibleRowOnList; + } + if (CPad::GetPad(0)->GetEnd()) { + m_nCurrExLayer = HOVEROPTION_LIST; + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + if (m_nTotalListRow >= MAX_VISIBLE_OPTION_ON_SCREEN) { + m_nFirstVisibleRowOnList = m_nTotalListRow - MAX_VISIBLE_OPTION_ON_SCREEN; + } + m_nSelectedListRow = m_nTotalListRow - 1; + m_nScrollbarTopMargin = (SCROLLBAR_MAX_HEIGHT / m_nTotalListRow) * m_nFirstVisibleRowOnList; + } + } + +#ifndef TIDY_UP_PBP + if (CPad::GetPad(0)->GetEscapeJustDown() || CPad::GetPad(0)->GetBackJustDown()) { + m_bShowMouse = false; + goBack = true; + } +#endif + + if (CPad::GetPad(0)->GetLeftMouseJustDown()) { + switch (m_nHoverOption) { + case HOVEROPTION_BACK: + goBack = true; + break; + case HOVEROPTION_PAGEUP: + PageUpList(true); + break; + case HOVEROPTION_PAGEDOWN: + PageDownList(true); + break; + case HOVEROPTION_USESKIN: + if (m_nSkinsTotal > 0) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_pSelectedSkin = m_pSkinListHead.nextSkin; + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + SaveSettings(); + } + } + } + + if (CPad::GetPad(0)->GetLeftMouseJustDown()) { + switch (m_nHoverOption) { + case HOVEROPTION_OVER_SCROLL_UP: + m_nHoverOption = HOVEROPTION_CLICKED_SCROLL_UP; + break; + case HOVEROPTION_OVER_SCROLL_DOWN: + m_nHoverOption = HOVEROPTION_CLICKED_SCROLL_DOWN; + break; + case HOVEROPTION_LIST: + m_nHoverOption = HOVEROPTION_SKIN; + } + } else if ((CPad::GetPad(0)->GetLeftMouseJustUp()) + && ((m_nHoverOption == HOVEROPTION_CLICKED_SCROLL_UP || (m_nHoverOption == HOVEROPTION_CLICKED_SCROLL_DOWN)))) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + } + + if (!CPad::GetPad(0)->GetLeftMouse()) { + holdingScrollBar = false; + } else { + if ((m_nHoverOption == HOVEROPTION_HOLDING_SCROLLBAR) || holdingScrollBar) { + holdingScrollBar = true; + // TODO: This part is a bit hard to reverse. Not much code tho + assert(0 && "Holding scrollbar isn't done yet"); + } else { + switch (m_nHoverOption) { + case HOVEROPTION_OVER_SCROLL_UP: + case HOVEROPTION_CLICKED_SCROLL_UP: + if (!m_bPressedScrollButton) { + m_bPressedScrollButton = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + ScrollUpListByOne(); + } + break; + case HOVEROPTION_OVER_SCROLL_DOWN: + case HOVEROPTION_CLICKED_SCROLL_DOWN: + if (!m_bPressedScrollButton) { + m_bPressedScrollButton = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + ScrollDownListByOne(); + } + break; + default: + m_bPressedScrollButton = false; + } + } + } +} +// ------ Functions not in the game/inlined ends + +void +CMenuManager::BuildStatLine(Const char *text, void *stat, bool itsFloat, void *stat2) +{ + if (!text) + return; + +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese() && stat2) + if (itsFloat) + sprintf(gString2, " %.2f/%.2f", *(float*)stat, *(float*)stat2); + else + sprintf(gString2, " %d/%d", *(int*)stat, *(int*)stat2); + else +#endif + if (stat2) { + if (itsFloat) + sprintf(gString2, " %.2f %s %.2f", *(float*)stat, UnicodeToAscii(TheText.Get("FEST_OO")), *(float*)stat2); + else + sprintf(gString2, " %d %s %d", *(int*)stat, UnicodeToAscii(TheText.Get("FEST_OO")), *(int*)stat2); + } else if (stat) { + if (itsFloat) + sprintf(gString2, " %.2f", *(float*)stat); + else + sprintf(gString2, " %d", *(int*)stat); + } else + gString2[0] = '\0'; + + UnicodeStrcpy(gUString, TheText.Get(text)); + AsciiToUnicode(gString2, gUString2); +} + +void +CMenuManager::CentreMousePointer() +{ + if (SCREEN_WIDTH * 0.5f != 0.0f && 0.0f != SCREEN_HEIGHT * 0.5f) { +#if defined RW_D3D9 || defined RWLIBS + tagPOINT Point; + Point.x = SCREEN_WIDTH / 2; + Point.y = SCREEN_HEIGHT / 2; + ClientToScreen(PSGLOBAL(window), &Point); + SetCursorPos(Point.x, Point.y); +#elif defined RW_GL3 + glfwSetCursorPos(PSGLOBAL(window), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2); +#endif + + PSGLOBAL(lastMousePos.x) = SCREEN_WIDTH / 2; + PSGLOBAL(lastMousePos.y) = SCREEN_HEIGHT / 2; + } +} + +void +CMenuManager::CheckCodesForControls(int typeOfControl) +{ + DisplayComboButtonErrMsg = false; + bool invalidKey = false; + bool escPressed = false; + eControllerType typeToSave; + // GetStartOptionsCntrlConfigScreens(); + e_ControllerAction action = (e_ControllerAction) m_CurrCntrlAction; + + if (typeOfControl == KEYBOARD) { + if (*pControlEdit == rsESC) { + escPressed = true; + } else if (*pControlEdit != rsF1 && *pControlEdit != rsF2 && *pControlEdit != rsF3 && *pControlEdit != rsF9 && + *pControlEdit != rsLWIN && *pControlEdit != rsRWIN && *pControlEdit != rsRALT) { + typeToSave = KEYBOARD; + if (ControlsManager.GetControllerKeyAssociatedWithAction(action, KEYBOARD) != rsNULL && + *pControlEdit != ControlsManager.GetControllerKeyAssociatedWithAction(action, KEYBOARD)) { + typeToSave = OPTIONAL_EXTRA; + } + } else { + invalidKey = true; + } + } else if (typeOfControl == MOUSE) { + typeToSave = MOUSE; + } else if (typeOfControl == JOYSTICK) { + typeToSave = JOYSTICK; + if (ControlsManager.GetIsActionAButtonCombo(action)) + DisplayComboButtonErrMsg = true; + } + +#ifdef FIX_BUGS + if(!escPressed && !invalidKey) +#endif + ControlsManager.ClearSettingsAssociatedWithAction(action, typeToSave); + if (!DisplayComboButtonErrMsg && !escPressed && !invalidKey) { + if (typeOfControl == KEYBOARD) { + ControlsManager.DeleteMatchingActionInitiators(action, *pControlEdit, KEYBOARD); + ControlsManager.DeleteMatchingActionInitiators(action, *pControlEdit, OPTIONAL_EXTRA); + } else { + if (typeOfControl == MOUSE) { + ControlsManager.DeleteMatchingActionInitiators(action, MouseButtonJustClicked, MOUSE); + } else if (typeOfControl == JOYSTICK) { + ControlsManager.DeleteMatchingActionInitiators(action, JoyButtonJustClicked, JOYSTICK); + } + } + if (typeOfControl == KEYBOARD) { + ControlsManager.SetControllerKeyAssociatedWithAction(action, *pControlEdit, typeToSave); + + } else if (typeOfControl == MOUSE) { + ControlsManager.SetControllerKeyAssociatedWithAction(action, MouseButtonJustClicked, typeToSave); + } else { + if (typeOfControl == JOYSTICK) { + ControlsManager.SetControllerKeyAssociatedWithAction(action, JoyButtonJustClicked, typeToSave); + } + } + pControlEdit = nil; + m_bWaitingForNewKeyBind = false; + m_KeyPressedCode = -1; + m_bStartWaitingForKeyBind = false; +#ifdef LOAD_INI_SETTINGS + SaveINIControllerSettings(); +#else + SaveSettings(); +#endif + } + + if (escPressed) { + pControlEdit = nil; + m_bWaitingForNewKeyBind = false; + m_KeyPressedCode = -1; + m_bStartWaitingForKeyBind = false; +#ifdef LOAD_INI_SETTINGS + SaveINIControllerSettings(); +#else + SaveSettings(); +#endif + } +} + +bool +CMenuManager::CheckHover(int x1, int x2, int y1, int y2) +{ + return m_nMousePosX > x1 && m_nMousePosX < x2 && + m_nMousePosY > y1 && m_nMousePosY < y2; +} + +void +CMenuManager::CheckSliderMovement(int value) +{ + switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) { + case MENUACTION_BRIGHTNESS: + m_PrefsBrightness += value * (512/MENUSLIDER_LOGICAL_BARS); + m_PrefsBrightness = Clamp(m_PrefsBrightness, 0, 511); + break; + case MENUACTION_DRAWDIST: + if(value > 0) + m_PrefsLOD += ((1.8f - 0.8f) / MENUSLIDER_LOGICAL_BARS); + else + m_PrefsLOD -= ((1.8f - 0.8f) / MENUSLIDER_LOGICAL_BARS); + m_PrefsLOD = Clamp(m_PrefsLOD, 0.8f, 1.8f); + CRenderer::ms_lodDistScale = m_PrefsLOD; + break; + case MENUACTION_MUSICVOLUME: + m_PrefsMusicVolume += value * (128/MENUSLIDER_LOGICAL_BARS); + m_PrefsMusicVolume = Clamp(m_PrefsMusicVolume, 0, 127); + DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume); + break; + case MENUACTION_SFXVOLUME: + m_PrefsSfxVolume += value * (128/MENUSLIDER_LOGICAL_BARS); + m_PrefsSfxVolume = Clamp(m_PrefsSfxVolume, 0, 127); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); + break; + case MENUACTION_MOUSESENS: + TheCamera.m_fMouseAccelHorzntl += value * 1.0f/200.0f/15.0f; // probably because diving it to 15 instead of 16(MENUSLIDER_LOGICAL_BARS) had more accurate steps + TheCamera.m_fMouseAccelHorzntl = Clamp(TheCamera.m_fMouseAccelHorzntl, 1.0f/3200.0f, 1.0f/200.0f); +#ifdef FIX_BUGS + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl + 0.0005f; +#else + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl; +#endif + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_CFO_SLIDER: + { + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption]; + float oldValue = *(float*)option.m_CFOSlider->value; + *(float*)option.m_CFOSlider->value += value * ((option.m_CFOSlider->max - option.m_CFOSlider->min) / MENUSLIDER_LOGICAL_BARS); + *(float*)option.m_CFOSlider->value = Clamp(*(float*)option.m_CFOSlider->value, option.m_CFOSlider->min, option.m_CFOSlider->max); + + if (*(float*)option.m_CFOSlider->value != oldValue && option.m_CFOSlider->changeFunc) + option.m_CFOSlider->changeFunc(oldValue, *(float*)option.m_CFOSlider->value); + + break; + } +#endif + default: + return; + } + SaveSettings(); +} + +void +CMenuManager::DisplayHelperText() +{ + // there was a unused static bool + static uint32 LastFlash = 0; + int32 alpha; + + if (m_nHelperTextMsgId != 0 && m_nHelperTextMsgId != 1) { + + // FIX: High fps bug +#ifndef FIX_BUGS + if (CTimer::GetTimeInMillisecondsPauseMode() - LastFlash > 10) { + LastFlash = CTimer::GetTimeInMillisecondsPauseMode(); + m_nHelperTextAlpha -= 2; + } +#else + m_nHelperTextAlpha -= 2 * CTimer::GetLogicalFramesPassed(); +#endif + if (m_nHelperTextAlpha < 1) + ResetHelperText(); + + alpha = m_nHelperTextAlpha > 255 ? 255 : m_nHelperTextAlpha; + } + + SET_FONT_FOR_HELPER_TEXT + // TODO: name this cases? + switch (m_nHelperTextMsgId) { + case 0: + { + int action = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; + if (action != MENUACTION_CHANGEMENU && action != MENUACTION_KEYBOARDCTRLS && action != MENUACTION_RESTOREDEF) { + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(HELPER_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_MIG")); + } + break; + } + case 1: + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(HELPER_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_APP")); + break; + case 2: + CFont::SetColor(CRGBA(255, 255, 255, alpha)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(HELPER_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_HRD")); + break; + case 3: + CFont::SetColor(CRGBA(255, 255, 255, alpha)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(HELPER_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_RSO")); + break; + case 4: + CFont::SetColor(CRGBA(255, 255, 255, alpha)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(HELPER_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_RSC")); + break; + default: + break; + } + CFont::SetRightJustifyOff(); +} + +int +CMenuManager::DisplaySlider(float x, float y, float mostLeftBarSize, float mostRightBarSize, float rectSize, float progress) +{ + CRGBA color; + float maxBarHeight; + + int lastActiveBarX = 0; + float curBarX = 0.0f; + float spacing = SCREEN_SCALE_X(10.0f); + for (int i = 0; i < MENUSLIDER_BARS; i++) { + curBarX = i * rectSize/MENUSLIDER_BARS + x; + + if (i / (float)MENUSLIDER_BARS + 1 / (MENUSLIDER_BARS * 2.f) < progress) { + color = CRGBA(SLIDERON_COLOR.r, SLIDERON_COLOR.g, SLIDERON_COLOR.b, FadeIn(255)); + lastActiveBarX = curBarX; + } else + color = CRGBA(SLIDEROFF_COLOR.r, SLIDEROFF_COLOR.g, SLIDEROFF_COLOR.b, FadeIn(255)); + + maxBarHeight = Max(mostLeftBarSize, mostRightBarSize); + + float curBarFreeSpace = ((MENUSLIDER_BARS - i) * mostLeftBarSize + i * mostRightBarSize) / (float)MENUSLIDER_BARS; + float left = curBarX; + float top = y + maxBarHeight - curBarFreeSpace; + float right = spacing + curBarX; + float bottom = y + maxBarHeight; + float shadowOffset = SCREEN_SCALE_X(2.0f); + CSprite2d::DrawRect(CRect(left + shadowOffset, top + shadowOffset, right + shadowOffset, bottom + shadowOffset), CRGBA(0, 0, 0, FadeIn(200))); // Shadow + CSprite2d::DrawRect(CRect(left, top, right, bottom), color); + } + return lastActiveBarX; +} + +void +CMenuManager::DoSettingsBeforeStartingAGame() +{ +#ifdef PC_PLAYER_CONTROLS + CCamera::m_bUseMouse3rdPerson = m_ControlMethod == CONTROL_STANDARD; +#endif + if (m_PrefsVsyncDisp != m_PrefsVsync) + m_PrefsVsync = m_PrefsVsyncDisp; + + DMAudio.Service(); + m_bWantToRestart = true; + + ShutdownJustMenu(); + UnloadTextures(); + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); +} + +void +CMenuManager::Draw() +{ + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetBackGroundOnlyTextOn(); +#if GTA_VERSION >= GTA3_PC_11 && defined(DRAW_MENU_VERSION_TEXT) + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetScale(MENU_X(0.7f), MENU_Y(0.5f)); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::SetRightJustifyWrap(0.0f); + strcpy(gString, "V1.1"); + AsciiToUnicode(gString, gUString); + CFont::PrintString(SCREEN_WIDTH / 10, SCREEN_HEIGHT / 45, gUString); +#endif + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(MENU_X_MARGIN)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(MENU_X_MARGIN - 2.0f)); + + switch (m_nCurrScreen) { + case MENUPAGE_STATS: + PrintStats(); + break; + case MENUPAGE_BRIEFS: + PrintBriefs(); + break; +#ifdef MENU_MAP + case MENUPAGE_MAP: + PrintMap(); + break; +#endif + } + + // Header height isn't accounted, we will add that later. + float nextYToUse = 40.0f; + + // Page name +#ifdef PS2_SAVE_DIALOG + if(!m_bRenderGameInMenu) +#endif + if (aScreens[m_nCurrScreen].m_ScreenName[0] != '\0') { + + SET_FONT_FOR_MENU_HEADER + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), TheText.Get(aScreens[m_nCurrScreen].m_ScreenName)); + + // Weird place to put that. + nextYToUse += 24.0f + 10.0f; + } + + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT * MENU_TEXT_SIZE_X), MENU_Y(MENUACTION_SCALE_MULT * MENU_TEXT_SIZE_Y)); + CFont::SetRightJustifyOff(); + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + + // Label + 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] == SLOT_EMPTY) + 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; + } + +#ifdef FIX_BUGS + // Label is wrapped from right by StretchX(40)px, but wrapped from left by 40px. And this is only place R* didn't use StretchX in here. + CFont::PrintString(MENU_X_LEFT_ALIGNED(MENU_X_MARGIN), MENU_Y(MENUACTION_POS_Y), str); +#else + CFont::PrintString(MENU_X_MARGIN, MENUACTION_POS_Y, str); +#endif + } + + // Not a bug, we just want HFoV+ on menu +#ifdef ASPECT_RATIO_SCALE + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH)); +#else + CFont::SetCentreSize(SCREEN_WIDTH); +#endif + +#ifdef PS2_LIKE_MENU + bool itemsAreSelectable = !bottomBarActive; +#else + bool itemsAreSelectable = true; +#endif + int lineHeight; + int headerHeight; + int columnWidth; + switch (m_nCurrScreen) { + case MENUPAGE_STATS: + case MENUPAGE_BRIEFS: + columnWidth = 320; + headerHeight = 240; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + CFont::SetCentreOn(); + break; +#ifdef FIX_BUGS + case MENUPAGE_CONTROLLER_SETTINGS: + columnWidth = 50; + headerHeight = -50; + lineHeight = 20; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = MEDIUMTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = MEDIUMTEXT_Y_SCALE)); + CFont::SetRightJustifyOff(); + break; +#endif + case MENUPAGE_SOUND_SETTINGS: + case MENUPAGE_DISPLAY_SETTINGS: + case MENUPAGE_MULTIPLAYER_CREATE: + case MENUPAGE_SKIN_SELECT_OLD: + case MENUPAGE_CONTROLLER_PC_OLD1: + case MENUPAGE_CONTROLLER_PC_OLD2: + case MENUPAGE_CONTROLLER_PC_OLD3: + case MENUPAGE_CONTROLLER_PC_OLD4: + case MENUPAGE_CONTROLLER_DEBUG: + case MENUPAGE_MOUSE_CONTROLS: + columnWidth = 50; + headerHeight = 0; + lineHeight = 20; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = MEDIUMTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = MEDIUMTEXT_Y_SCALE)); + CFont::SetRightJustifyOff(); + break; + case MENUPAGE_CHOOSE_LOAD_SLOT: + case MENUPAGE_CHOOSE_DELETE_SLOT: + case MENUPAGE_CHOOSE_SAVE_SLOT: + columnWidth = 120; + headerHeight = 38; + lineHeight = 20; + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = SMALLTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = SMALLTEXT_Y_SCALE)); + CFont::SetRightJustifyOff(); + break; + case MENUPAGE_NEW_GAME_RELOAD: + case MENUPAGE_LOAD_SLOT_CONFIRM: + case MENUPAGE_DELETE_SLOT_CONFIRM: + case MENUPAGE_SAVE_OVERWRITE_CONFIRM: + case MENUPAGE_EXIT: + columnWidth = 320; + headerHeight = 60; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + CFont::SetCentreOn(); + break; + case MENUPAGE_START_MENU: + columnWidth = 320; + headerHeight = 140; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + CFont::SetCentreOn(); + break; + case MENUPAGE_PAUSE_MENU: + columnWidth = 320; + headerHeight = 117; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + CFont::SetCentreOn(); + break; +#ifdef PS2_SAVE_DIALOG + case MENUPAGE_SAVE: + columnWidth = 180; + headerHeight = 60; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + break; +#endif + default: +#ifdef CUSTOM_FRONTEND_OPTIONS + CCustomScreenLayout *custom = aScreens[m_nCurrScreen].layout; + if (custom) { + columnWidth = custom->columnWidth; + headerHeight = custom->headerHeight; + lineHeight = custom->lineHeight; + CFont::SetFontStyle(FONT_LOCALE(custom->font)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = custom->fontScaleX), MENU_Y(MENU_TEXT_SIZE_Y = custom->fontScaleY)); + if (custom->alignment == FESCREEN_LEFT_ALIGN) { + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + } else if (custom->alignment == FESCREEN_RIGHT_ALIGN) { + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + } else { + CFont::SetRightJustifyOff(); + CFont::SetCentreOn(); + } + } + if (!custom) +#endif + { + columnWidth = 320; + headerHeight = 40; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + CFont::SetCentreOn(); + } + break; + } + +#ifdef PS2_LIKE_MENU + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); +#endif + + switch (m_nCurrScreen) { + case MENUPAGE_CONTROLLER_PC_OLD1: + case MENUPAGE_CONTROLLER_PC_OLD2: + case MENUPAGE_CONTROLLER_PC_OLD3: + case MENUPAGE_CONTROLLER_PC_OLD4: + case MENUPAGE_CONTROLLER_DEBUG: + if (m_bWaitingForNewKeyBind) + itemsAreSelectable = false; + + DrawControllerScreenExtraText(nextYToUse - 8.0f, MENU_X_LEFT_ALIGNED(350), lineHeight); + break; + default: + break; + } + + float usableLineHeight = lineHeight * 0.9f; // also height of biggest bar in slider + float smallestSliderBar = lineHeight * 0.1f; + bool foundTheHoveringItem = false; + wchar unicodeTemp[64]; +#ifdef ASPECT_RATIO_SCALE + char asciiTemp[32]; +#endif + +#ifdef MENU_MAP + if (m_nCurrScreen == MENUPAGE_MAP) { + // Back button + wchar *backTx = TheText.Get("FEDS_TB"); + CFont::SetDropShadowPosition(1); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::PrintString(MENU_X(60.0f), SCREEN_SCALE_FROM_BOTTOM(120.0f), backTx); + CFont::SetDropShadowPosition(0); + if (!CheckHover(MENU_X(30.0f), MENU_X(30.0f) + CFont::GetStringWidth(backTx), SCREEN_SCALE_FROM_BOTTOM(125.0f), SCREEN_SCALE_FROM_BOTTOM(105.0f))) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + m_nCurrOption = m_nOptionMouseHovering = 0; + } else { + m_nHoverOption = HOVEROPTION_RANDOM_ITEM; + m_nCurrOption = m_nOptionMouseHovering = 1; + } + return; + } +#endif + +#ifdef CUSTOM_FRONTEND_OPTIONS + // Thanks R*, for checking mouse hovering in Draw(). + static int lastSelectedOpt = m_nCurrOption; +#endif + +#ifdef SCROLLABLE_PAGES + int firstOption = SCREEN_HAS_AUTO_SCROLLBAR ? m_nFirstVisibleRowOnList : 0; + for (int i = firstOption; i < firstOption + MAX_VISIBLE_OPTION && i < NUM_MENUROWS; ++i) { +#else + for (int i = 0; i < NUM_MENUROWS; ++i) { +#endif + +#ifdef CUSTOM_FRONTEND_OPTIONS + bool isOptionDisabled = false; +#endif + // Hide back button +#ifdef PS2_LIKE_MENU + if ((i == NUM_MENUROWS - 1 || aScreens[m_nCurrScreen].m_aEntries[i+1].m_EntryName[0] == '\0') && strcmp(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName, "FEDS_TB") == 0) + break; +#endif + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action != MENUACTION_LABEL && aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName[0] != '\0') { + wchar *rightText = nil; + wchar *leftText; + + if (aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot >= SAVESLOT_1 && aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot <= SAVESLOT_8) { + CFont::SetRightJustifyOff(); + leftText = GetNameOfSavedGame(i - 1); + + if (Slots[i] != SLOT_EMPTY) + rightText = GetSavedGameDateAndTime(i - 1); + + if (leftText[0] == '\0') { + sprintf(gString, "FEM_SL%d", i); + leftText = TheText.Get(gString); + } + } else { + leftText = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName); + } + + switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) { + case MENUACTION_CHANGEMENU: { + switch (aScreens[m_nCurrScreen].m_aEntries[i].m_TargetMenu) { + case MENUPAGE_MULTIPLAYER_MAP: + switch (m_SelectedMap) { + case 0: + rightText = TheText.Get("FEM_MA0"); + break; + case 1: + rightText = TheText.Get("FEM_MA1"); + break; + case 2: + rightText = TheText.Get("FEM_MA2"); + break; + case 3: + rightText = TheText.Get("FEM_MA3"); + break; + case 4: + rightText = TheText.Get("FEM_MA4"); + break; + case 5: + rightText = TheText.Get("FEM_MA5"); + break; + case 6: + rightText = TheText.Get("FEM_MA6"); + break; + case 7: + rightText = TheText.Get("FEM_MA7"); + break; + default: + break; + } + break; + case MENUPAGE_MULTIPLAYER_MODE: + switch (m_SelectedGameType) { + case 0: + rightText = TheText.Get("FEN_TY0"); + break; + case 1: + rightText = TheText.Get("FEN_TY1"); + break; + case 2: + rightText = TheText.Get("FEN_TY2"); + break; + case 3: + rightText = TheText.Get("FEN_TY3"); + break; + case 4: + rightText = TheText.Get("FEN_TY4"); + break; + case 5: + rightText = TheText.Get("FEN_TY5"); + break; + case 6: + rightText = TheText.Get("FEN_TY6"); + break; + case 7: + rightText = TheText.Get("FEN_TY7"); + break; + default: + break; + } + break; + default: + break; + } + break; + } + case MENUACTION_CTRLVIBRATION: + if (m_PrefsUseVibration) + rightText = TheText.Get("FEM_ON"); + else + rightText = TheText.Get("FEM_OFF"); + break; + case MENUACTION_CTRLCONFIG: + switch (CPad::GetPad(0)->Mode) { + case 0: + rightText = TheText.Get("FEC_CF1"); + break; + case 1: + rightText = TheText.Get("FEC_CF2"); + break; + case 2: + rightText = TheText.Get("FEC_CF3"); + break; + case 3: + rightText = TheText.Get("FEC_CF4"); + break; + } + break; + case MENUACTION_CTRLDISPLAY: + if (m_DisplayControllerOnFoot) + rightText = TheText.Get("FEC_ONF"); + else + rightText = TheText.Get("FEC_INC"); + break; + case MENUACTION_FRAMESYNC: + rightText = TheText.Get(m_PrefsVsyncDisp ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_FRAMELIMIT: + rightText = TheText.Get(m_PrefsFrameLimiter ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_TRAILS: + rightText = TheText.Get(CMBlur::BlurOn ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SUBTITLES: + rightText = TheText.Get(m_PrefsShowSubtitles ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_WIDESCREEN: +#ifndef ASPECT_RATIO_SCALE + rightText = TheText.Get(m_PrefsUseWideScreen ? "FEM_ON" : "FEM_OFF"); +#else + switch (m_PrefsUseWideScreen) { + case AR_AUTO: + rightText = TheText.Get("FEM_AUT"); + break; + case AR_4_3: + sprintf(asciiTemp, "4:3"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; + break; + case AR_5_4: + sprintf(asciiTemp, "5:4"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; + break; + case AR_16_10: + sprintf(asciiTemp, "16:10"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; + break; + case AR_16_9: + sprintf(asciiTemp, "16:9"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; + break; + case AR_21_9: + sprintf(asciiTemp, "21:9"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; + break; + } +#endif + break; + case MENUACTION_RADIO: + if (m_PrefsRadioStation > USERTRACK) + break; + + sprintf(gString, "FEA_FM%d", m_PrefsRadioStation); + rightText = TheText.Get(gString); + break; + case MENUACTION_SETDBGFLAG: + rightText = TheText.Get(CTheScripts::IsDebugOn() ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SWITCHBIGWHITEDEBUGLIGHT: + rightText = TheText.Get(gbBigWhiteDebugLightSwitchedOn ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_PEDROADGROUPS: + rightText = TheText.Get(gbShowPedRoadGroups ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_CARROADGROUPS: + rightText = TheText.Get(gbShowCarRoadGroups ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_COLLISIONPOLYS: + rightText = TheText.Get(gbShowCollisionPolys ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SHOWCULL: + rightText = TheText.Get(gbShowCullZoneDebugStuff ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SHOWHEADBOB: + rightText = TheText.Get(TheCamera.m_bHeadBob ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_INVVERT: + rightText = TheText.Get(MousePointerStateHelper.bInvertVertically ? "FEM_OFF" : "FEM_ON"); + break; + case MENUACTION_SCREENRES: + AsciiToUnicode(_psGetVideoModeList()[m_nDisplayVideoMode], unicodeTemp); + rightText = unicodeTemp; + break; + case MENUACTION_AUDIOHW: + if (m_nPrefsAudio3DProviderIndex == -1) + rightText = TheText.Get("FEA_NAH"); + else { + char *provider = DMAudio.Get3DProviderName(m_nPrefsAudio3DProviderIndex); + + if (!strcmp(strupr(provider), "DIRECTSOUND3D HARDWARE SUPPORT")) { + strcpy(provider, "DSOUND3D HARDWARE SUPPORT"); + } else if (!strcmp(strupr(provider), "DIRECTSOUND3D SOFTWARE EMULATION")) { + strcpy(provider, "DSOUND3D SOFTWARE EMULATION"); + } + AsciiToUnicode(provider, unicodeTemp); + rightText = unicodeTemp; + } + break; + case MENUACTION_SPEAKERCONF: { + if (m_nPrefsAudio3DProviderIndex == -1) + rightText = TheText.Get("FEA_NAH"); + else { + switch (m_PrefsSpeakers) { + case 0: + rightText = TheText.Get("FEA_2SP"); + break; + case 1: + rightText = TheText.Get("FEA_EAR"); + break; + case 2: + rightText = TheText.Get("FEA_4SP"); + break; + } + } + break; + } + case MENUACTION_CTRLMETHOD: { + switch (m_ControlMethod) { + case 0: + leftText = TheText.Get("FET_SCN"); + break; + case 1: + leftText = TheText.Get("FET_CCN"); + break; + } + break; + } + case MENUACTION_DYNAMICACOUSTIC: + rightText = TheText.Get(m_PrefsDMA ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_MOUSESTEER: + rightText = TheText.Get(CVehicle::m_bDisableMouseSteering ? "FEM_OFF" : "FEM_ON"); + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_CFO_DYNAMIC: + case MENUACTION_CFO_SELECT: + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[i]; + if (option.m_Action == MENUACTION_CFO_SELECT) { + + isOptionDisabled = option.m_CFOSelect->disableIfGameLoaded && !m_bGameNotLoaded; + if (option.m_CFOSelect->onlyApplyOnEnter){ + if (m_nCurrOption != i) { + if (option.m_CFOSelect->displayedValue != option.m_CFOSelect->lastSavedValue) + SetHelperText(3); // Restored original value + + // If that was previously selected option, restore it to default value. + // if (m_nCurrOption != lastSelectedOpt && lastSelectedOpt == i) + option.m_CFOSelect->displayedValue = option.m_CFOSelect->lastSavedValue = *(int8*)option.m_CFO->value; + + } else { + if (option.m_CFOSelect->displayedValue != *(int8*)option.m_CFO->value) + SetHelperText(1); // Enter to apply + else if (m_nHelperTextMsgId == 1) + ResetHelperText(); // Applied + } + } + + // To whom manipulate option.m_CFO->value of select options externally (like RestoreDef functions) + if (*(int8*)option.m_CFO->value != option.m_CFOSelect->lastSavedValue) + option.m_CFOSelect->displayedValue = option.m_CFOSelect->lastSavedValue = *(int8*)option.m_CFO->value; + + if (option.m_CFOSelect->displayedValue >= option.m_CFOSelect->numRightTexts || option.m_CFOSelect->displayedValue < 0) + option.m_CFOSelect->displayedValue = 0; + + rightText = TheText.Get(option.m_CFOSelect->rightTexts[option.m_CFOSelect->displayedValue]); + + } else if (option.m_Action == MENUACTION_CFO_DYNAMIC) { + if (m_nCurrOption != lastSelectedOpt && lastSelectedOpt == i) { + if(option.m_CFODynamic->buttonPressFunc) + option.m_CFODynamic->buttonPressFunc(FEOPTION_ACTION_FOCUSLOSS); + } + + if (option.m_CFODynamic->drawFunc) { + rightText = option.m_CFODynamic->drawFunc(&isOptionDisabled, m_nCurrOption == i); + } + } + break; +#endif + } + + float nextItemY = headerHeight + nextYToUse; + float bitAboveNextItemY = nextItemY - 2.0f; + int nextYToCheck = bitAboveNextItemY; + + if (!foundTheHoveringItem) { +#ifdef SCROLLABLE_PAGES + for (int rowToCheck = firstOption + (aScreens[m_nCurrScreen].m_aEntries[firstOption].m_Action == MENUACTION_LABEL); rowToCheck < firstOption + MAX_VISIBLE_OPTION && rowToCheck < NUM_MENUROWS; ++rowToCheck) { +#else + for (int rowToCheck = aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL; rowToCheck < NUM_MENUROWS; ++rowToCheck) { +#endif + if(aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_Action == MENUACTION_NOTHING) + break; + + // Hide back button +#ifdef PS2_LIKE_MENU + if ((rowToCheck == NUM_MENUROWS - 1 || aScreens[m_nCurrScreen].m_aEntries[rowToCheck+1].m_EntryName[0] == '\0') && + strcmp(aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_EntryName, "FEDS_TB") == 0) + break; +#endif + + int extraOffset = 0; + if (aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_Action == MENUACTION_RADIO) + extraOffset = MENURADIO_ICON_SCALE; + + // There were many unused codes in here to calculate how much space will texts gonna take. + + // FIX: nextYToCheck already starts with Y - 2, let's sync it with green bar bounds. +#ifdef FIX_BUGS + if (m_nMousePosY > MENU_Y(nextYToCheck) && +#else + if (m_nMousePosY > MENU_Y(nextYToCheck - 2) && +#endif + m_nMousePosY < MENU_Y((nextYToCheck + 2) + usableLineHeight)) { + + static int oldOption = -99; + static int oldScreen = m_nCurrScreen; + + m_nOptionMouseHovering = rowToCheck; + if (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY) { + m_nCurrOption = rowToCheck; + m_bShowMouse = true; + } + if (oldOption != m_nCurrOption) { + if (oldScreen == m_nCurrScreen && m_bShowMouse) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + oldOption = m_nCurrOption; + oldScreen = m_nCurrScreen; + } + if (oldScreen == m_nPrevScreen) + oldScreen = m_nCurrScreen; + + m_nHoverOption = HOVEROPTION_RANDOM_ITEM; + foundTheHoveringItem = true; + break; + } + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + nextYToCheck += extraOffset + lineHeight; + } + } + + // Green bar behind selected option +#ifdef PS2_SAVE_DIALOG + if (!m_bRenderGameInMenu) +#endif + if (i == m_nCurrOption && itemsAreSelectable) { +#ifdef PS2_LIKE_MENU + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(29.0f), MENU_Y(bitAboveNextItemY), + MENU_X_RIGHT_ALIGNED(29.0f), MENU_Y(usableLineHeight + nextItemY)), + CRGBA(SELECTION_HIGHLIGHTBG_COLOR.r, SELECTION_HIGHLIGHTBG_COLOR.g, SELECTION_HIGHLIGHTBG_COLOR.b, FadeIn(SELECTION_HIGHLIGHTBG_COLOR.a))); +#else + // We keep stretching, because we also stretch background image and we want that bar to be aligned with borders of background + CSprite2d::DrawRect(CRect(StretchX(10.0f), MENU_Y(bitAboveNextItemY), + SCREEN_STRETCH_FROM_RIGHT(11.0f), MENU_Y(usableLineHeight + nextItemY)), + CRGBA(SELECTION_HIGHLIGHTBG_COLOR.r, SELECTION_HIGHLIGHTBG_COLOR.g, SELECTION_HIGHLIGHTBG_COLOR.b, FadeIn(SELECTION_HIGHLIGHTBG_COLOR.a))); +#endif + } + + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(90))); + + // Button and it's shadow + for(int textLayer = 0; textLayer < 2; textLayer++) { + if (!CFont::Details.centre) + CFont::SetRightJustifyOff(); + + float itemY = MENU_Y(textLayer + nextItemY); + float itemX = MENU_X_LEFT_ALIGNED(textLayer + columnWidth); + CFont::PrintString(itemX, itemY, leftText); + if (rightText) { + if (!CFont::Details.centre) + CFont::SetRightJustifyOn(); + + if(textLayer == 1) + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName, "FED_RES") && !m_bGameNotLoaded +#ifdef CUSTOM_FRONTEND_OPTIONS + || isOptionDisabled +#endif + ) + CFont::SetColor(CRGBA(DARKMENUOPTION_COLOR.r, DARKMENUOPTION_COLOR.g, DARKMENUOPTION_COLOR.b, FadeIn(255))); + + CFont::PrintString(MENU_X_RIGHT_ALIGNED(columnWidth - textLayer), itemY, rightText); + } + if (i == m_nCurrOption && itemsAreSelectable){ + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + } + + if (m_nPrefsAudio3DProviderIndex == DMAudio.GetCurrent3DProviderIndex()) { + if(!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEA_3DH") && m_nHelperTextMsgId == 1) + ResetHelperText(); + } + if (m_nDisplayVideoMode == m_nPrefsVideoMode) { + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FED_RES") && m_nHelperTextMsgId == 1) + ResetHelperText(); + } + if (m_nPrefsAudio3DProviderIndex != DMAudio.GetCurrent3DProviderIndex()) { + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEA_3DH")) + SetHelperText(1); + } + if (m_nDisplayVideoMode != m_nPrefsVideoMode) { + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FED_RES")) + SetHelperText(1); + } + if (m_nPrefsAudio3DProviderIndex != DMAudio.GetCurrent3DProviderIndex()) { + if (strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEA_3DH") != 0 + // To make assigning built-in actions to new custom options possible. +#ifdef CUSTOM_FRONTEND_OPTIONS + && ScreenHasOption(m_nCurrScreen, "FEA_3DH") +#else + && m_nCurrScreen == MENUPAGE_SOUND_SETTINGS +#endif + && m_nPrefsAudio3DProviderIndex != -1) { + + m_nPrefsAudio3DProviderIndex = DMAudio.GetCurrent3DProviderIndex(); + SetHelperText(3); + } + } + if (m_nDisplayVideoMode != m_nPrefsVideoMode) { + if (strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FED_RES") != 0 + // To make assigning built-in actions to new custom options possible. +#ifdef CUSTOM_FRONTEND_OPTIONS + && ScreenHasOption(m_nCurrScreen, "FED_RES") +#else + && m_nCurrScreen == MENUPAGE_DISPLAY_SETTINGS +#endif + ){ + m_nDisplayVideoMode = m_nPrefsVideoMode; + SetHelperText(3); + } + } + + // Sliders + int lastActiveBarX; + switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) { + case MENUACTION_BRIGHTNESS: + ProcessSlider(m_PrefsBrightness / 512.0f, HOVEROPTION_INCREASE_BRIGHTNESS, HOVEROPTION_DECREASE_BRIGHTNESS, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + break; + case MENUACTION_DRAWDIST: + ProcessSlider((m_PrefsLOD - 0.8f) * 1.0f, HOVEROPTION_INCREASE_DRAWDIST, HOVEROPTION_DECREASE_DRAWDIST, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + break; + case MENUACTION_MUSICVOLUME: + ProcessSlider(m_PrefsMusicVolume / 128.0f, HOVEROPTION_INCREASE_MUSICVOLUME, HOVEROPTION_DECREASE_MUSICVOLUME, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + break; + case MENUACTION_SFXVOLUME: + ProcessSlider(m_PrefsSfxVolume / 128.0f, HOVEROPTION_INCREASE_SFXVOLUME, HOVEROPTION_DECREASE_SFXVOLUME, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + break; + case MENUACTION_MOUSESENS: + ProcessSlider(TheCamera.m_fMouseAccelHorzntl * 200.0f, HOVEROPTION_INCREASE_MOUSESENS, HOVEROPTION_DECREASE_MOUSESENS, MENU_X_LEFT_ALIGNED(200.0f), SCREEN_WIDTH); + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_CFO_SLIDER: + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[i]; + ProcessSlider((*(float*)option.m_CFOSlider->value - option.m_CFOSlider->min) / (option.m_CFOSlider->max - option.m_CFOSlider->min), HOVEROPTION_INCREASE_CFO_SLIDER, HOVEROPTION_DECREASE_CFO_SLIDER, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + break; +#endif + } + + // Needed after the bug fix in Font.cpp +#ifdef FIX_BUGS + if (!CFont::Details.centre) + CFont::SetRightJustifyOff(); +#endif + + // 60.0 is silly + nextYToUse += lineHeight * CFont::GetNumberLines(MENU_X_LEFT_ALIGNED(60.0f), MENU_Y(nextYToUse), leftText); + + // Radio icons + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action == MENUACTION_RADIO) { + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO1], MENU_X_LEFT_ALIGNED(30.0f), MENU_Y(nextYToUse), 0, HOVEROPTION_RADIO_0); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO2], MENU_X_LEFT_ALIGNED(90.0f), MENU_Y(nextYToUse), 1, HOVEROPTION_RADIO_1); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO5], MENU_X_LEFT_ALIGNED(150.0f), MENU_Y(nextYToUse), 2, HOVEROPTION_RADIO_2); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO7], MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(nextYToUse), 3, HOVEROPTION_RADIO_3); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO8], MENU_X_LEFT_ALIGNED(270.0f), MENU_Y(nextYToUse), 4, HOVEROPTION_RADIO_4); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO3], MENU_X_LEFT_ALIGNED(320.0f), MENU_Y(nextYToUse), 5, HOVEROPTION_RADIO_5); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO4], MENU_X_LEFT_ALIGNED(360.0f), MENU_Y(nextYToUse), 6, HOVEROPTION_RADIO_6); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO6], MENU_X_LEFT_ALIGNED(420.0f), MENU_Y(nextYToUse), 7, HOVEROPTION_RADIO_7); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO9], MENU_X_LEFT_ALIGNED(480.0f), MENU_Y(nextYToUse), 8, HOVEROPTION_RADIO_8); + + if (DMAudio.IsMP3RadioChannelAvailable()) + ProcessRadioIcon(m_aMenuSprites[MENUSPRITE_MP3LOGO], MENU_X_LEFT_ALIGNED(540.0f), MENU_Y(nextYToUse), 9, HOVEROPTION_RADIO_9); + + nextYToUse += 70.0f; + } + } + } + +#ifdef CUSTOM_FRONTEND_OPTIONS + lastSelectedOpt = m_nCurrOption; +#endif + +#ifdef SCROLLABLE_PAGES + #define SCROLLBAR_BOTTOM_Y 125.0f // only for background, scrollbar's itself is calculated + #define SCROLLBAR_RIGHT_X 36.0f + #define SCROLLBAR_WIDTH 9.5f + #define SCROLLBAR_TOP_Y 64 + + if (SCREEN_HAS_AUTO_SCROLLBAR) { + // Scrollbar background + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - 2), MENU_Y(SCROLLBAR_TOP_Y), + MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - 2 - SCROLLBAR_WIDTH), SCREEN_SCALE_FROM_BOTTOM(SCROLLBAR_BOTTOM_Y)), CRGBA(100, 100, 66, FadeIn(205))); + + float scrollbarHeight = SCROLLBAR_MAX_HEIGHT / (m_nTotalListRow / (float) MAX_VISIBLE_OPTION); + float scrollbarBottom, scrollbarTop; + + scrollbarBottom = MENU_Y(SCROLLBAR_TOP_Y - 8 + m_nScrollbarTopMargin + scrollbarHeight); + scrollbarTop = MENU_Y(SCROLLBAR_TOP_Y + m_nScrollbarTopMargin); + // Scrollbar shadow + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - 4), scrollbarTop, + MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - 1 - SCROLLBAR_WIDTH), scrollbarBottom + MENU_Y(1.0f)), + CRGBA(50, 50, 50, FadeIn(255))); + + // Scrollbar + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - 4), scrollbarTop, + MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - SCROLLBAR_WIDTH), scrollbarBottom), + CRGBA(SCROLLBAR_COLOR.r, SCROLLBAR_COLOR.g, SCROLLBAR_COLOR.b, FadeIn(255))); + + } +#endif + + switch (m_nCurrScreen) { + case MENUPAGE_CONTROLLER_SETTINGS: + case MENUPAGE_SOUND_SETTINGS: + case MENUPAGE_DISPLAY_SETTINGS: + case MENUPAGE_SKIN_SELECT: + case MENUPAGE_CONTROLLER_PC: + case MENUPAGE_MOUSE_CONTROLS: + DisplayHelperText(); + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + default: + if (aScreens[m_nCurrScreen].layout) { + if (aScreens[m_nCurrScreen].layout->showLeftRightHelper) { + DisplayHelperText(); + } + } + break; +#endif + } + + if (m_nCurrScreen == MENUPAGE_CONTROLLER_SETTINGS) + PrintController(); + else if (m_nCurrScreen == MENUPAGE_SKIN_SELECT_OLD) { + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(180), MENU_Y(98), MENU_X_LEFT_ALIGNED(230), MENU_Y(123)), CRGBA(255, 255, 255, FadeIn(255))); + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(181), MENU_Y(99), MENU_X_LEFT_ALIGNED(229), MENU_Y(122)), CRGBA(m_PrefsPlayerRed, m_PrefsPlayerGreen, m_PrefsPlayerBlue, FadeIn(255))); + } + +} + +int +CMenuManager::GetNumOptionsCntrlConfigScreens(void) +{ + int number = 0; + switch (m_nCurrScreen) { + case MENUPAGE_CONTROLLER_PC_OLD3: + number = 2; + break; + case MENUPAGE_CONTROLLER_DEBUG: + number = 4; + break; + case MENUPAGE_KEYBOARD_CONTROLS: + switch (m_ControlMethod) { + case CONTROL_STANDARD: + number = 25; + break; + case CONTROL_CLASSIC: + number = 30; + break; + } + break; + } + return number; +} + +void +CMenuManager::DrawControllerBound(int32 yStart, int32 xStart, int32 unused, int8 column) +{ + int controllerAction = PED_FIREWEAPON; + // GetStartOptionsCntrlConfigScreens(); + int numOptions = GetNumOptionsCntrlConfigScreens(); + int nextY = MENU_Y(yStart); + int bindingMargin = MENU_X(3.0f); + float rowHeight; + switch (m_ControlMethod) { + case CONTROL_STANDARD: + rowHeight = CONTSETUP_STANDARD_ROW_HEIGHT; + break; + case CONTROL_CLASSIC: + rowHeight = CONTSETUP_CLASSIC_ROW_HEIGHT; + break; + default: + break; + } + + // MENU_Y(rowHeight * 0.0f + yStart); + for (int optionIdx = 0; optionIdx < numOptions; nextY = MENU_Y(++optionIdx * rowHeight + yStart)) { + int nextX = xStart; + int bindingsForThisOpt = 0; + int contSetOrder = SETORDER_1; + CFont::SetColor(CRGBA(LIST_OPTION_COLOR.r, LIST_OPTION_COLOR.g, LIST_OPTION_COLOR.b, FadeIn(LIST_OPTION_COLOR.a))); + + if (column == CONTSETUP_PED_COLUMN) { + switch (optionIdx) { + case 0: + controllerAction = PED_FIREWEAPON; + break; + case 1: + controllerAction = PED_CYCLE_WEAPON_RIGHT; + break; + case 2: + controllerAction = PED_CYCLE_WEAPON_LEFT; + break; + case 3: + controllerAction = GO_FORWARD; + break; + case 4: + controllerAction = GO_BACK; + break; + case 5: + controllerAction = GO_LEFT; + break; + case 6: + controllerAction = GO_RIGHT; + break; + case 7: + controllerAction = PED_SNIPER_ZOOM_IN; + break; + case 8: + controllerAction = PED_SNIPER_ZOOM_OUT; + break; + case 9: + controllerAction = VEHICLE_ENTER_EXIT; + break; + case 10: + case 11: + case 12: + case 16: + case 18: + case 19: + case 20: + case 21: + controllerAction = -1; + break; + case 13: + controllerAction = CAMERA_CHANGE_VIEW_ALL_SITUATIONS; + break; + case 14: + controllerAction = PED_JUMPING; + break; + case 15: + controllerAction = PED_SPRINT; + break; + case 17: + controllerAction = PED_LOCK_TARGET; + break; + case 22: + controllerAction = PED_LOOKBEHIND; + break; + case 23: + if (m_ControlMethod == CONTROL_STANDARD) + controllerAction = -1; + else + controllerAction = PED_1RST_PERSON_LOOK_LEFT; + break; + case 24: + if (m_ControlMethod == CONTROL_STANDARD) + controllerAction = -1; + else + controllerAction = PED_1RST_PERSON_LOOK_RIGHT; + break; + case 25: + controllerAction = PED_1RST_PERSON_LOOK_UP; + break; + case 26: + controllerAction = PED_1RST_PERSON_LOOK_DOWN; + break; + case 27: + controllerAction = PED_CYCLE_TARGET_LEFT; + break; + case 28: + controllerAction = PED_CYCLE_TARGET_RIGHT; + break; + case 29: + controllerAction = PED_CENTER_CAMERA_BEHIND_PLAYER; + break; + default: + break; + } + } else if (column == CONTSETUP_VEHICLE_COLUMN) { + switch (optionIdx) { + case 0: +#ifdef BIND_VEHICLE_FIREWEAPON + controllerAction = VEHICLE_FIREWEAPON; +#else + controllerAction = PED_FIREWEAPON; +#endif + break; + case 1: + case 2: + case 7: + case 8: + case 14: + case 15: + case 17: + case 25: + case 26: + case 27: + case 28: + case 29: + controllerAction = -1; + break; + case 3: + controllerAction = VEHICLE_ACCELERATE; + break; + case 4: + controllerAction = VEHICLE_BRAKE; + break; + case 5: + controllerAction = GO_LEFT; + break; + case 6: + controllerAction = GO_RIGHT; + break; + case 9: + controllerAction = VEHICLE_ENTER_EXIT; + break; + case 10: + controllerAction = VEHICLE_CHANGE_RADIO_STATION; + break; + case 11: + controllerAction = VEHICLE_HORN; + break; + case 12: + controllerAction = TOGGLE_SUBMISSIONS; + break; + case 13: + controllerAction = CAMERA_CHANGE_VIEW_ALL_SITUATIONS; + break; + case 16: + controllerAction = VEHICLE_HANDBRAKE; + break; + case 18: + controllerAction = VEHICLE_TURRETLEFT; + break; + case 19: + controllerAction = VEHICLE_TURRETRIGHT; + break; + case 20: + controllerAction = VEHICLE_TURRETUP; + break; + case 21: + controllerAction = VEHICLE_TURRETDOWN; + break; + case 22: + controllerAction = -2; + break; + case 23: + controllerAction = VEHICLE_LOOKLEFT; + break; + case 24: + controllerAction = VEHICLE_LOOKRIGHT; + break; + default: + break; + } + } + int bindingWhite = 155; + + // Highlight selected column(and make its text black) + if (m_nSelectedListRow == optionIdx) { + int bgY = m_nSelectedListRow * rowHeight + yStart + 1.0f; + if (m_nCurrExLayer == HOVEROPTION_LIST) { + + if (column == CONTSETUP_PED_COLUMN && m_nSelectedContSetupColumn == CONTSETUP_PED_COLUMN) { +#ifdef FIX_BUGS + if (controllerAction == -1) { + CSprite2d::DrawRect(CRect(nextX, MENU_Y(bgY), nextX + MENU_X(CONTSETUP_BOUND_COLUMN_WIDTH), + MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.r, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.g, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.a))); + } else { + CSprite2d::DrawRect(CRect(nextX, MENU_Y(bgY), nextX + MENU_X(CONTSETUP_BOUND_COLUMN_WIDTH), + MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_HIGHLIGHTBG_COLOR.r, CONTSETUP_HIGHLIGHTBG_COLOR.g, CONTSETUP_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_HIGHLIGHTBG_COLOR.a))); + } +#else + if (controllerAction == -1) { + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(bgY), + MENU_X_LEFT_ALIGNED(400.0f), MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.r, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.g, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.a))); + } else { + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(bgY), + MENU_X_LEFT_ALIGNED(400.0f), MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_HIGHLIGHTBG_COLOR.r, CONTSETUP_HIGHLIGHTBG_COLOR.g, CONTSETUP_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_HIGHLIGHTBG_COLOR.a))); + } +#endif + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + bindingWhite = 0; + + } else if (column == CONTSETUP_VEHICLE_COLUMN && m_nSelectedContSetupColumn == CONTSETUP_VEHICLE_COLUMN) { +#ifdef FIX_BUGS + if (controllerAction == -1) { + CSprite2d::DrawRect(CRect(nextX, MENU_Y(bgY), nextX + MENU_X(CONTSETUP_BOUND_COLUMN_WIDTH), + MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.r, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.g, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.a))); + } else { + CSprite2d::DrawRect(CRect(nextX, MENU_Y(bgY), nextX + MENU_X(CONTSETUP_BOUND_COLUMN_WIDTH), + MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_HIGHLIGHTBG_COLOR.r, CONTSETUP_HIGHLIGHTBG_COLOR.g, CONTSETUP_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_HIGHLIGHTBG_COLOR.a))); + } +#else + if (controllerAction == -1) { + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(410.0f), MENU_Y(bgY), MENU_X_LEFT_ALIGNED(600.0f), MENU_Y(bgY + 10)), CRGBA(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.r, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.g, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.a))); + } else { + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(410.0f), MENU_Y(bgY), MENU_X_LEFT_ALIGNED(600.0f), MENU_Y(bgY + 10)), CRGBA(CONTSETUP_HIGHLIGHTBG_COLOR.r, CONTSETUP_HIGHLIGHTBG_COLOR.g, CONTSETUP_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_HIGHLIGHTBG_COLOR.a))); + } +#endif + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + bindingWhite = 0; + } + } + } + + // Print bindings, including seperator (-) between them + CFont::SetScale(MENU_X(0.25f), MENU_Y(SMALLESTTEXT_Y_SCALE)); +#ifdef FIX_BUGS + for (; contSetOrder < MAX_SETORDERS && controllerAction >= 0; contSetOrder++) { +#else + for (; contSetOrder < MAX_SETORDERS && controllerAction != -1; contSetOrder++) { +#endif + wchar *settingText = ControlsManager.GetControllerSettingTextWithOrderNumber((e_ControllerAction)controllerAction, (eContSetOrder)contSetOrder); + if (settingText) { + ++bindingsForThisOpt; + if (bindingsForThisOpt > 1) { + wchar *seperator = TheText.Get("FEC_IBT"); + CFont::SetColor(CRGBA(20, 20, 20, FadeIn(80))); + CFont::PrintString(nextX, nextY, seperator); + CFont::SetColor(CRGBA(bindingWhite, bindingWhite, bindingWhite, FadeIn(255))); + nextX += CFont::GetStringWidth(seperator, true) + bindingMargin; + } + CFont::PrintString(nextX, nextY, settingText); +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + nextX += CFont::GetStringWidth_Jap(settingText) + bindingMargin; + else +#endif + nextX += CFont::GetStringWidth(settingText, true) + bindingMargin; + } + } + if (controllerAction == -1) { + CFont::SetColor(CRGBA(20, 20, 20, FadeIn(80))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_NUS")); // not used + } else if (controllerAction == -2) { + CFont::SetColor(CRGBA(20, 20, 20, FadeIn(80))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_CMP")); // combo: l+r + } else if (bindingsForThisOpt == 0) { + if (m_nSelectedListRow != optionIdx) { + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_UNB")); // unbound + } else if (m_bWaitingForNewKeyBind) { + if (column != m_nSelectedContSetupColumn) { + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_UNB")); // unbound + } + } else { + if (column != m_nSelectedContSetupColumn) { + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + } + CFont::PrintString(nextX, nextY, TheText.Get("FEC_UNB")); // unbound + } + } + + if (column == CONTSETUP_PED_COLUMN && m_nSelectedContSetupColumn == CONTSETUP_PED_COLUMN || + column == CONTSETUP_VEHICLE_COLUMN && m_nSelectedContSetupColumn == CONTSETUP_VEHICLE_COLUMN) { + + if (optionIdx == m_nSelectedListRow && controllerAction != -1 && controllerAction != -2) { + m_CurrCntrlAction = controllerAction; + if (m_bWaitingForNewKeyBind) { + static bool showWaitingText = false; + if (bindingsForThisOpt > 0) { + wchar *seperator = TheText.Get("FEC_IBT"); + CFont::PrintString(nextX, nextY, seperator); + nextX += CFont::GetStringWidth(seperator, true) + bindingMargin; + } + static uint32 lastWaitingTextFlash = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastWaitingTextFlash > 150) { + showWaitingText = !showWaitingText; + lastWaitingTextFlash = CTimer::GetTimeInMillisecondsPauseMode(); + } + if (showWaitingText) { + CFont::SetColor(CRGBA(55, 55, 55, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_QUE")); // "???" + } + SET_FONT_FOR_HELPER_TEXT + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + if (m_bKeyChangeNotProcessed) { + CFont::PrintString(MENU_X_LEFT_ALIGNED(275.0f), SCREEN_SCALE_FROM_BOTTOM(114.0f), TheText.Get("FET_CIG")); // BACKSPACE TO CLEAR - LMB,RETURN TO CHANGE + } else { + CFont::PrintString(MENU_X_LEFT_ALIGNED(275.0f), SCREEN_SCALE_FROM_BOTTOM(114.0f), TheText.Get("FET_RIG")); // SELECT A NEW CONTROL FOR THIS ACTION OR ESC TO CANCEL + } + + SET_FONT_FOR_LIST_ITEM + if (!m_bKeyIsOK) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + + m_bKeyIsOK = true; + } else { + SET_FONT_FOR_HELPER_TEXT + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + CFont::PrintString(MENU_X_LEFT_ALIGNED(275.0f), SCREEN_SCALE_FROM_BOTTOM(114.0f), TheText.Get("FET_CIG")); // BACKSPACE TO CLEAR - LMB,RETURN TO CHANGE + SET_FONT_FOR_LIST_ITEM + m_bKeyIsOK = false; + m_bKeyChangeNotProcessed = false; + } + } else if (optionIdx == m_nSelectedListRow) { + SET_FONT_FOR_HELPER_TEXT + CFont::SetColor(CRGBA(55, 55, 55, FadeIn(255))); + CFont::PrintString(MENU_X_LEFT_ALIGNED(275.0f), SCREEN_SCALE_FROM_BOTTOM(114.0f), TheText.Get("FET_EIG")); // CANNOT SET A CONTROL FOR THIS ACTION + SET_FONT_FOR_LIST_ITEM + } + } + } +} + +void +CMenuManager::DrawControllerScreenExtraText(int yStart, int xStart, int lineHeight) +{ + int extraTextStart = GetStartOptionsCntrlConfigScreens(); + int numOpts = GetNumOptionsCntrlConfigScreens(); + int spacing = MENU_X(10.0f); + for (int i = extraTextStart; i < extraTextStart + numOpts; i++) { + int numTextsPrinted = 0; + int nextX = xStart; + for (int j = 1; j < 5; j++) { + wchar *text = ControlsManager.GetControllerSettingTextWithOrderNumber((e_ControllerAction)i, (eContSetOrder)j); + if (text) + ++numTextsPrinted; + + if (text) { + // Seperator + if (numTextsPrinted > 1) { + CFont::PrintString(nextX, MENU_Y(yStart), TheText.Get("FEC_IBT")); + nextX = CFont::GetStringWidth(TheText.Get("FEC_IBT"), true) + spacing + nextX; + } + CFont::PrintString(nextX, MENU_Y(yStart), text); + } + if (text) + nextX = CFont::GetStringWidth(text, true) + spacing + nextX; + } + if (m_nCurrOption == i - extraTextStart && m_bWaitingForNewKeyBind) { + static bool waitingTextVisible = false; + + // Seperator + if (numTextsPrinted > 0) { + CFont::PrintString(nextX, MENU_Y(yStart), TheText.Get("FEC_IBT")); + nextX = CFont::GetStringWidth(TheText.Get("FEC_IBT"), true) + spacing + nextX; + } + static uint32 lastStateChange = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastStateChange > 150) { + waitingTextVisible = !waitingTextVisible; + lastStateChange = CTimer::GetTimeInMillisecondsPauseMode(); + } + if (waitingTextVisible) { + CFont::SetColor(CRGBA(255, 255, 0, FadeIn(255))); + CFont::PrintString(nextX, MENU_Y(yStart), TheText.Get("FEC_QUE")); + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + } + yStart += lineHeight; + } + wchar *error = nil; + if (DisplayComboButtonErrMsg) + error = ControlsManager.GetButtonComboText((e_ControllerAction)(m_nCurrOption + extraTextStart)); + + if (error) { + CFont::SetColor(CRGBA(233, 22, 159, 255)); + CFont::PrintString(xStart, MENU_Y(yStart + 10), error); + } +} + +void +CMenuManager::DrawControllerSetupScreen() +{ + float rowHeight; + switch (m_ControlMethod) { + case CONTROL_STANDARD: + rowHeight = CONTSETUP_STANDARD_ROW_HEIGHT; + break; + case CONTROL_CLASSIC: + rowHeight = CONTSETUP_CLASSIC_ROW_HEIGHT; + break; + default: + break; + } + RESET_FONT_FOR_NEW_PAGE + + SET_FONT_FOR_MENU_HEADER + + switch (m_ControlMethod) { + case CONTROL_STANDARD: + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), + TheText.Get(aScreens[m_nCurrScreen].m_ScreenName)); + break; + case CONTROL_CLASSIC: + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), + TheText.Get("FET_CTI")); + break; + default: + break; + } + wchar *actionTexts[31]; + actionTexts[0] = TheText.Get("FEC_FIR"); + actionTexts[1] = TheText.Get("FEC_NWE"); + actionTexts[2] = TheText.Get("FEC_PWE"); + actionTexts[3] = TheText.Get("FEC_FOR"); + actionTexts[4] = TheText.Get("FEC_BAC"); + actionTexts[5] = TheText.Get("FEC_LEF"); + actionTexts[6] = TheText.Get("FEC_RIG"); + actionTexts[7] = TheText.Get("FEC_ZIN"); + actionTexts[8] = TheText.Get("FEC_ZOT"); + actionTexts[9] = TheText.Get("FEC_EEX"); + actionTexts[10] = TheText.Get("FEC_RAD"); + actionTexts[11] = TheText.Get("FEC_HRN"); + actionTexts[12] = TheText.Get("FEC_SUB"); + actionTexts[13] = TheText.Get("FEC_CMR"); + actionTexts[14] = TheText.Get("FEC_JMP"); + actionTexts[15] = TheText.Get("FEC_SPN"); + actionTexts[16] = TheText.Get("FEC_HND"); + actionTexts[17] = TheText.Get("FEC_TAR"); + if (m_ControlMethod == CONTROL_CLASSIC) { + actionTexts[18] = TheText.Get("FEC_TFL"); + actionTexts[19] = TheText.Get("FEC_TFR"); + actionTexts[20] = TheText.Get("FEC_TFU"); + actionTexts[21] = TheText.Get("FEC_TFD"); + actionTexts[22] = TheText.Get("FEC_LBA"); + actionTexts[23] = TheText.Get("FEC_LOL"); + actionTexts[24] = TheText.Get("FEC_LOR"); + actionTexts[25] = TheText.Get("FEC_LUD"); + actionTexts[26] = TheText.Get("FEC_LDU"); + actionTexts[27] = TheText.Get("FEC_NTR"); + actionTexts[28] = TheText.Get("FEC_PTT"); + actionTexts[29] = TheText.Get("FEC_CEN"); + actionTexts[30] = nil; + } else { + actionTexts[18] = TheText.Get("FEC_TFL"); + actionTexts[19] = TheText.Get("FEC_TFR"); + actionTexts[20] = TheText.Get("FEC_TFU"); + actionTexts[21] = TheText.Get("FEC_TFD"); + actionTexts[22] = TheText.Get("FEC_LBA"); + actionTexts[23] = TheText.Get("FEC_LOL"); + actionTexts[24] = TheText.Get("FEC_LOR"); + actionTexts[25] = nil; + } + + // Gray panel background + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(CONTSETUP_LIST_LEFT), MENU_Y(CONTSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(CONTSETUP_LIST_RIGHT), SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_LIST_BOTTOM)), + CRGBA(LIST_BACKGROUND_COLOR.r, LIST_BACKGROUND_COLOR.g, LIST_BACKGROUND_COLOR.b, FadeIn(LIST_BACKGROUND_COLOR.a))); + + if (m_nCurrExLayer == HOVEROPTION_LIST) + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + else + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + + // List header + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT), MENU_Y(MENUACTION_SCALE_MULT)); + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_1_X), MENU_Y(CONTSETUP_LIST_TOP), TheText.Get("FET_CAC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_2_X), MENU_Y(CONTSETUP_LIST_TOP), TheText.Get("FET_CFT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_3_X), MENU_Y(CONTSETUP_LIST_TOP), TheText.Get("FET_CCR")); + SET_FONT_FOR_LIST_ITEM + + int yStart; + if (m_ControlMethod == CONTROL_CLASSIC) + yStart = CONTSETUP_LIST_TOP + CONTSETUP_LIST_HEADER_HEIGHT + 1; + else + yStart = CONTSETUP_LIST_TOP + CONTSETUP_LIST_HEADER_HEIGHT + 5; + + float optionYBottom = yStart + rowHeight; + for (int i = 0; i < ARRAY_SIZE(actionTexts); ++i) { + wchar *actionText = actionTexts[i]; + if (!actionText) + break; + + if (m_nMousePosX > MENU_X_LEFT_ALIGNED(CONTSETUP_LIST_LEFT + 2.0f) && + m_nMousePosX < MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_3_X + CONTSETUP_BOUND_COLUMN_WIDTH)) { + + if (m_nMousePosY > MENU_Y(i * rowHeight + yStart) && m_nMousePosY < MENU_Y(i * rowHeight + optionYBottom)) { + if (m_nOptionMouseHovering != i && m_nCurrExLayer == HOVEROPTION_LIST) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nOptionMouseHovering = i; + if (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY) { + m_nCurrExLayer = HOVEROPTION_LIST; + m_nSelectedListRow = i; + + // why different number for 3rd column hovering X?? this function is a mess +#ifdef FIX_BUGS + if (m_nMousePosX > MENU_X_LEFT_ALIGNED(0.0f) && m_nMousePosX < MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_2_X + CONTSETUP_BOUND_COLUMN_WIDTH)) { +#else + if (m_nMousePosX > MENU_X_LEFT_ALIGNED(0.0f) && m_nMousePosX < MENU_X_LEFT_ALIGNED(370.0f)) { +#endif + if (m_nSelectedContSetupColumn != CONTSETUP_PED_COLUMN && m_nCurrExLayer == HOVEROPTION_LIST) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nSelectedContSetupColumn = CONTSETUP_PED_COLUMN; +#ifdef FIX_BUGS + } else if (m_nMousePosX > MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_2_X + CONTSETUP_BOUND_COLUMN_WIDTH) && m_nMousePosX < SCREEN_WIDTH) { +#else + } else if (m_nMousePosX > MENU_X_LEFT_ALIGNED(370.0f) && m_nMousePosX < SCREEN_WIDTH) { +#endif + if (m_nSelectedContSetupColumn != CONTSETUP_VEHICLE_COLUMN && m_nCurrExLayer == HOVEROPTION_LIST) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nSelectedContSetupColumn = CONTSETUP_VEHICLE_COLUMN; + } + } + // what?? + if (m_nHoverOption == HOVEROPTION_SKIN) { + if (i == m_nSelectedListRow) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + m_bWaitingForNewKeyBind = true; + m_bStartWaitingForKeyBind = true; + pControlEdit = &m_KeyPressedCode; + } + } else + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + } + } + if (m_nSelectedListRow != i) + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + else if (m_nCurrExLayer == HOVEROPTION_LIST) + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + + CFont::SetRightJustifyOff(); + if (m_PrefsLanguage == LANGUAGE_GERMAN && (i == 20 || i == 21)) + CFont::SetScale(MENU_X(0.32f), MENU_Y(SMALLESTTEXT_Y_SCALE)); + else + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE), MENU_Y(SMALLESTTEXT_Y_SCALE)); + + CFont::PrintString(MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_1_X), MENU_Y(i * rowHeight + yStart), actionText); + } + DrawControllerBound(yStart, MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_2_X), rowHeight, CONTSETUP_PED_COLUMN); + DrawControllerBound(yStart, MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_3_X), rowHeight, CONTSETUP_VEHICLE_COLUMN); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X), MENU_Y(MENU_TEXT_SIZE_Y)); + + if ((m_nMousePosX > MENU_X_RIGHT_ALIGNED(CONTSETUP_BACK_RIGHT) - CFont::GetStringWidth(TheText.Get("FEDS_TB"), true) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(CONTSETUP_BACK_RIGHT) && m_nMousePosY > SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_BACK_BOTTOM) + && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_BACK_BOTTOM - CONTSETUP_BACK_HEIGHT)) || m_nCurrExLayer == HOVEROPTION_BACK) { + m_nHoverOption = HOVEROPTION_BACK; + + } else if (m_nMousePosX > MENU_X_LEFT_ALIGNED(CONTSETUP_LIST_LEFT + 2.0f) && m_nMousePosX < MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_3_X + CONTSETUP_BOUND_COLUMN_WIDTH) + && m_nMousePosY > MENU_Y(CONTSETUP_LIST_TOP + CONTSETUP_LIST_HEADER_HEIGHT) && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_LIST_BOTTOM + 5.0f)) { + m_nHoverOption = HOVEROPTION_LIST; + + } else { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + } + + // Back button and it's shadow + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X), MENU_Y(MENU_TEXT_SIZE_Y)); + CFont::SetRightJustifyOn(); + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(90))); + for (int i = 0; i < 2; i++) { + CFont::PrintString(MENU_X_RIGHT_ALIGNED(CONTSETUP_BACK_RIGHT - 2.0f - i), + SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_BACK_BOTTOM - 4.0f - i), TheText.Get("FEDS_TB")); + + if (m_nHoverOption == HOVEROPTION_BACK) + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + else + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } +} + +void +CMenuManager::DrawFrontEnd() +{ + CFont::SetAlphaFade(255.0f); + +#ifdef PS2_LIKE_MENU + #define setBbItem(a, b, c) strcpy(a.name, b); a.screenId = c; + if (m_nCurrScreen == MENUPAGE_NONE) { + if (m_bGameNotLoaded) { + if (bbTabCount != 6) { + setBbItem(bbNames[0], "FEB_SAV",MENUPAGE_NEW_GAME) + setBbItem(bbNames[1], "FEB_CON",MENUPAGE_CONTROLLER_PC) + setBbItem(bbNames[2], "FEB_AUD",MENUPAGE_SOUND_SETTINGS) + setBbItem(bbNames[3], "FEB_DIS",MENUPAGE_DISPLAY_SETTINGS) + setBbItem(bbNames[4], "FEB_LAN",MENUPAGE_LANGUAGE_SETTINGS) + setBbItem(bbNames[5], "FESZ_QU",MENUPAGE_EXIT) + bbTabCount = 6; + } + } else { + if (bbTabCount != 8) { + setBbItem(bbNames[0], "FEB_STA",MENUPAGE_STATS) + setBbItem(bbNames[1], "FEB_SAV",MENUPAGE_NEW_GAME) + setBbItem(bbNames[2], "FEB_BRI",MENUPAGE_BRIEFS) + setBbItem(bbNames[3], "FEB_CON",MENUPAGE_CONTROLLER_PC) + setBbItem(bbNames[4], "FEB_AUD",MENUPAGE_SOUND_SETTINGS) + setBbItem(bbNames[5], "FEB_DIS",MENUPAGE_DISPLAY_SETTINGS) + setBbItem(bbNames[6], "FEB_LAN",MENUPAGE_LANGUAGE_SETTINGS) + setBbItem(bbNames[7], "FESZ_QU",MENUPAGE_EXIT) + bbTabCount = 8; + } + } + m_nCurrScreen = bbNames[0].screenId; + bottomBarActive = true; + curBottomBarOption = 0; + } + #undef setBbItem +#else + if (m_nCurrScreen == MENUPAGE_NONE) { + if (m_bGameNotLoaded) { + m_nCurrScreen = MENUPAGE_START_MENU; + } else { + m_nCurrScreen = MENUPAGE_PAUSE_MENU; + } + } +#endif + + if (m_nCurrOption == 0 && aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) + m_nCurrOption = 1; + +#ifdef PS2_SAVE_DIALOG + if(m_bRenderGameInMenu) + DrawFrontEndSaveZone(); + else +#endif + DrawFrontEndNormal(); + + PrintErrorMessage(); +} + +#ifdef PS2_SAVE_DIALOG +void +CMenuManager::DrawFrontEndSaveZone() +{ + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + + // Not original dimensions, have been changed to fit PC screen & PC menu layout. + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(30.0f), MENU_Y(50.0f), MENU_X_RIGHT_ALIGNED(30.0f), SCREEN_SCALE_FROM_BOTTOM(50.0f)), CRGBA(0, 0, 0, 175)); + + m_nMenuFadeAlpha = 255; + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + Draw(); + + CFont::DrawFonts(); + + // Draw mouse + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + if (m_bShowMouse) { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + CRect mouse(0.0f, 0.0f, MENU_X(75.0f), MENU_Y(75.0f)); + CRect shad(MENU_X(10.0f), MENU_Y(3.0f), MENU_X(85.0f), MENU_Y(78.0f)); + + mouse.Translate(m_nMousePosX, m_nMousePosY); + shad.Translate(m_nMousePosX, m_nMousePosY); + if(field_518 == 4){ + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(mouse, CRGBA(255, 255, 255, 255)); + }else{ + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(mouse, CRGBA(255, 255, 255, 255)); + } + } +} +#endif + +#ifdef PS2_LIKE_MENU +void +CMenuManager::DrawFrontEndNormal() +{ + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + + if (!m_bGameNotLoaded) { + CSprite2d *bg = LoadSplash(nil); + bg->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(48, 48, 48, 255)); + } else { + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(0, 0, 0, 255)); + } + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + m_aFrontEndSprites[FE2_MAINPANEL_UL].Draw(CRect(MENU_X_LEFT_ALIGNED(0.0f), 0.0f, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2), CRGBA(255, 255, 255, 255)); + m_aFrontEndSprites[FE2_MAINPANEL_UR].Draw(CRect(SCREEN_WIDTH / 2, 0.0f, MENU_X_RIGHT_ALIGNED(0.0f), SCREEN_HEIGHT / 2), CRGBA(255, 255, 255, 255)); + m_aFrontEndSprites[FE2_MAINPANEL_DL].Draw(CRect(MENU_X_LEFT_ALIGNED(0.0f), SCREEN_HEIGHT / 2, SCREEN_WIDTH / 2, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + m_aFrontEndSprites[FE2_MAINPANEL_DR].Draw(CRect(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, MENU_X_RIGHT_ALIGNED(0.0f), SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + eFrontendSprites currentSprite; + switch (m_nCurrScreen) { + case MENUPAGE_STATS: + case MENUPAGE_START_MENU: + case MENUPAGE_PAUSE_MENU: + case MENUPAGE_EXIT: + currentSprite = FE_ICONSTATS; + break; + case MENUPAGE_LANGUAGE_SETTINGS: + currentSprite = FE_ICONLANGUAGE; + break; + 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: + currentSprite = FE_ICONSAVE; + break; + case MENUPAGE_DISPLAY_SETTINGS: + currentSprite = FE_ICONDISPLAY; + break; + case MENUPAGE_SOUND_SETTINGS: + currentSprite = FE_ICONAUDIO; + break; + case MENUPAGE_CONTROLLER_PC: + case MENUPAGE_OPTIONS: + case MENUPAGE_CONTROLLER_SETTINGS: + case MENUPAGE_KEYBOARD_CONTROLS: + case MENUPAGE_MOUSE_CONTROLS: + currentSprite = FE_ICONCONTROLS; + break; + default: + /*case MENUPAGE_NEW_GAME: */ + /*case MENUPAGE_BRIEFS: */ + currentSprite = FE_ICONBRIEF; + break; + } + + static float fadeAlpha = 0.0f; + + if (m_nMenuFadeAlpha < 255) { + m_nMenuFadeAlpha += 20 * CTimer::GetLogicalFramesPassed(); + } else { + // TODO: what is this? waiting mouse? + if(field_518 == 4){ + if(m_nHoverOption == HOVEROPTION_3 || m_nHoverOption == HOVEROPTION_4 || + m_nHoverOption == HOVEROPTION_5 || m_nHoverOption == HOVEROPTION_6 || m_nHoverOption == HOVEROPTION_7) + + field_518 = 2; + else + field_518 = 1; + } + } + + m_aFrontEndSprites[currentSprite].Draw(CRect(MENU_X_LEFT_ALIGNED(50.0f), MENU_Y(50.0f), MENU_X_RIGHT_ALIGNED(50.0f), SCREEN_SCALE_FROM_BOTTOM(95.0f)), CRGBA(255, 255, 255, m_nMenuFadeAlpha > 255 ? 255 : m_nMenuFadeAlpha)); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + switch (m_nCurrScreen) { + case MENUPAGE_SKIN_SELECT: + DrawPlayerSetupScreen(); + break; + case MENUPAGE_KEYBOARD_CONTROLS: + DrawControllerSetupScreen(); + break; + default: + Draw(); + break; + } + + // Positions/style from PS2 menu, credits to Fire_Head + /* Draw controller buttons */ + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.35f), SCREEN_SCALE_Y(0.64f)); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(MENU_X_MARGIN)); // 600.0f + CFont::SetColor(CRGBA(16, 16, 16, 255)); + switch (m_nCurrScreen) { + + // Page names overlaps buttons on those. + case MENUPAGE_MOUSE_CONTROLS: + case MENUPAGE_KEYBOARD_CONTROLS: + break; + + default: + { + CFont::PrintString(MENU_X_LEFT_ALIGNED(52.0f), MENU_Y(360.0f), TheText.Get("FEDS_SE")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(52.0f), MENU_Y(372.0f), TheText.Get("FEDS_BA")); + if (!m_bGameNotLoaded) + CFont::PrintString(MENU_X_LEFT_ALIGNED(52.0f), MENU_Y(384.0f), TheText.Get("FEDS_ST")); + + if (bottomBarActive) + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f), MENU_Y(372.0f), TheText.Get("FEDS_AM")); // <>-CHANGE MENU + else if (m_nCurrScreen != MENUPAGE_STATS && m_nCurrScreen != MENUPAGE_BRIEFS) { + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f), MENU_Y(360.0f + 3.5f), TheText.Get("FEA_UP")); // ; + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f), MENU_Y(384.0f - 3.5f), TheText.Get("FEA_DO")); // = + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f - 10.0f), MENU_Y(372.0f), TheText.Get("FEA_LE")); // < + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f + 11.0f), MENU_Y(372.0f), TheText.Get("FEA_RI")); // > + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f + 20.0f), MENU_Y(372.0f), TheText.Get("FEDSAS3")); // - CHANGE SELECTION + } + + break; + } + } + + #define optionWidth MENU_X(66.0f) + #define rawOptionHeight 22.0f + #define optionBottom SCREEN_SCALE_FROM_BOTTOM(20.0f) + #define optionTop SCREEN_SCALE_FROM_BOTTOM(20.0f + rawOptionHeight) + #define leftPadding MENU_X_LEFT_ALIGNED(90.0f) + wchar *str; + hoveredBottomBarOption = -1; + if (curBottomBarOption != -1) { + + // This active tab sprite is needlessly big + m_aFrontEndSprites[FE2_TABACTIVE].Draw(CRect(leftPadding - MENU_X(2.0f) + (optionWidth) * curBottomBarOption, optionTop, + leftPadding - MENU_X(5.0f) + optionWidth * (curBottomBarOption + 2), optionBottom + MENU_Y(rawOptionHeight - 9.0f)), + CRGBA(CRGBA(255, 255, 255, 255))); + + for (int i = 0; i < bbTabCount; i++) { + float xStart = leftPadding + optionWidth * i; + if (CheckHover(xStart, xStart + optionWidth, optionTop, optionBottom)) + hoveredBottomBarOption = i; + + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetScale(MENU_X(0.35f), MENU_Y(0.7f)); + CFont::SetRightJustifyOff(); + if (hoveredBottomBarOption == i && hoveredBottomBarOption != curBottomBarOption) + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, 255)); + else { + if(bottomBarActive || curBottomBarOption == i) + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, 255)); + else + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, 110)); + } + + str = TheText.Get(bbNames[i].name); + + CFont::PrintString(xStart + MENU_X(4.0f), SCREEN_SCALE_FROM_BOTTOM(39.0f), str); + + } + } + #undef optionBottom + #undef optionTop + #undef leftPadding + #undef optionWidth + #undef rawOptionHeight + + CFont::DrawFonts(); + + // Draw mouse + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + if (m_bShowMouse) { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + CRect mouse(0.0f, 0.0f, MENU_X(75.0f), MENU_Y(75.0f)); + CRect shad(MENU_X(10.0f), MENU_Y(3.0f), MENU_X(85.0f), MENU_Y(78.0f)); + + mouse.Translate(m_nMousePosX, m_nMousePosY); + shad.Translate(m_nMousePosX, m_nMousePosY); + if(field_518 == 4){ + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(mouse, CRGBA(255, 255, 255, 255)); + }else{ + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(mouse, CRGBA(255, 255, 255, 255)); + } + } +} +#else +void +CMenuManager::DrawFrontEndNormal() +{ + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + + LoadSplash(nil); + + eMenuSprites previousSprite; + 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; + default: +#ifdef CUSTOM_FRONTEND_OPTIONS + CCustomScreenLayout *custom = aScreens[m_nPrevScreen].layout; + if (custom) { + previousSprite = custom->sprite; + break; + } + if (!custom) +#endif + previousSprite = MENUSPRITE_MAINMENU; + break; + } + + if (m_nPrevScreen == m_nCurrScreen) + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(0, 0, 0, 255 - m_nMenuFadeAlpha)); + else + m_aMenuSprites[previousSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255 - m_nMenuFadeAlpha)); + } + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + + eMenuSprites currentSprite = MENUSPRITE_MAINMENU; // actually uninitialized + 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; +#ifdef CUSTOM_FRONTEND_OPTIONS + default: + CCustomScreenLayout *custom = aScreens[m_nCurrScreen].layout; + if (custom) { + previousSprite = custom->sprite; + } + break; +#endif + } + + if (m_nMenuFadeAlpha < 255) { + + // Famous transparent menu bug +#ifdef FIX_BUGS + m_nMenuFadeAlpha += 20 * CTimer::GetLogicalFramesPassed(); +#else + static uint32 LastFade = 0; + + if(CTimer::GetTimeInMillisecondsPauseMode() - LastFade > 10){ + m_nMenuFadeAlpha += 20; + LastFade = CTimer::GetTimeInMillisecondsPauseMode(); + } +#endif + + if (m_nMenuFadeAlpha > 255){ + m_aMenuSprites[currentSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + } else { + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + 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)); + // TODO: what is this? waiting mouse? + if(field_518 == 4){ + if(m_nHoverOption == HOVEROPTION_3 || m_nHoverOption == HOVEROPTION_4 || + m_nHoverOption == HOVEROPTION_5 || m_nHoverOption == HOVEROPTION_6 || m_nHoverOption == HOVEROPTION_7) + + field_518 = 2; + else + field_518 = 1; + } + } + +#ifdef RED_DELETE_BACKGROUND + if (m_nCurrScreen == MENUPAGE_CHOOSE_DELETE_SLOT || m_nCurrScreen == MENUPAGE_DELETE_SLOT_CONFIRM) { + CSprite2d::Draw2DPolygon(0.0f, 0.0f, + SCREEN_WIDTH, 0.0f, + 0.0f, SCREEN_HEIGHT, + SCREEN_WIDTH, SCREEN_HEIGHT, + CRGBA(150, 0, 0, 80)); + } +#endif + + // GTA LOGO + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + if (m_nCurrScreen == MENUPAGE_START_MENU || m_nCurrScreen == MENUPAGE_PAUSE_MENU) { + if (CGame::frenchGame || CGame::germanGame || !CGame::nastyGame) + m_aMenuSprites[MENUSPRITE_GTA3LOGO].Draw(CRect(MENU_X_LEFT_ALIGNED(205.0f), MENU_Y(70.0f), MENU_X_LEFT_ALIGNED(435.0f), MENU_Y(180.0f)), CRGBA(255, 255, 255, FadeIn(255))); + else + m_aMenuSprites[MENUSPRITE_GTALOGO].Draw(CRect(MENU_X_LEFT_ALIGNED(225.0f), MENU_Y(40.0f), MENU_X_LEFT_ALIGNED(415.0f), MENU_Y(210.0f)), CRGBA(255, 255, 255, FadeIn(255))); + } + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + switch (m_nCurrScreen) { + case MENUPAGE_SKIN_SELECT: + DrawPlayerSetupScreen(); + break; + case MENUPAGE_KEYBOARD_CONTROLS: + DrawControllerSetupScreen(); + break; + default: + Draw(); + break; + } + + CFont::DrawFonts(); + + // Draw mouse + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + if (m_bShowMouse) { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + CRect mouse(0.0f, 0.0f, MENU_X(75.0f), MENU_Y(75.0f)); + CRect shad(MENU_X(10.0f), MENU_Y(3.0f), MENU_X(85.0f), MENU_Y(78.0f)); + + mouse.Translate(m_nMousePosX, m_nMousePosY); + shad.Translate(m_nMousePosX, m_nMousePosY); + if(field_518 == 4){ + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(mouse, CRGBA(255, 255, 255, 255)); + }else{ + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(mouse, CRGBA(255, 255, 255, 255)); + } + } +} +#endif + +void +CMenuManager::DrawPlayerSetupScreen() +{ + RESET_FONT_FOR_NEW_PAGE + + SET_FONT_FOR_MENU_HEADER + + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), TheText.Get("FET_PS")); + + // lstrcpy's changed with strcpy + + if (!m_bSkinsEnumerated) { + OutputDebugString("Enumerating skin filenames from skins..."); + m_pSkinListHead.nextSkin = nil; + m_pSelectedSkin = &m_pSkinListHead; + m_pSelectedSkin->nextSkin = new tSkinInfo; + m_pSelectedSkin = m_pSelectedSkin->nextSkin; + m_pSelectedSkin->skinId = 0; + strcpy(m_pSelectedSkin->skinNameOriginal, DEFAULT_SKIN_NAME); + strcpy(m_pSelectedSkin->skinNameDisplayed, UnicodeToAscii(TheText.Get("FET_DSN"))); + int nextSkinId = 1; + m_pSelectedSkin->nextSkin = nil; + + WIN32_FIND_DATA FindFileData; + SYSTEMTIME SystemTime; + HANDLE handle = FindFirstFile("skins\\*.bmp", &FindFileData); + for (int i = 1; handle != INVALID_HANDLE_VALUE && i; i = FindNextFile(handle, &FindFileData)) { + if (strcmp(FindFileData.cFileName, DEFAULT_SKIN_NAME) != 0) { + m_pSelectedSkin->nextSkin = new tSkinInfo; + m_pSelectedSkin = m_pSelectedSkin->nextSkin; + m_pSelectedSkin->skinId = nextSkinId; + strcpy(m_pSelectedSkin->skinNameOriginal, FindFileData.cFileName); + strcpy(m_pSelectedSkin->skinNameDisplayed, FindFileData.cFileName); + FileTimeToSystemTime(&FindFileData.ftLastWriteTime, &SystemTime); + GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &SystemTime, 0, m_pSelectedSkin->date, 255); + ++nextSkinId; + m_pSelectedSkin->nextSkin = nil; + } + } + FindClose(handle); + m_nSkinsTotal = nextSkinId; + char nameTemp[256]; + for (m_pSelectedSkin = m_pSkinListHead.nextSkin; m_pSelectedSkin; m_pSelectedSkin = m_pSelectedSkin->nextSkin) { + // Drop extension + int oldLength = (int)strlen(m_pSelectedSkin->skinNameDisplayed); + m_pSelectedSkin->skinNameDisplayed[oldLength - 4] = '\0'; + m_pSelectedSkin->skinNameOriginal[oldLength - 4] = '\0'; + + // Fill to 40 bytes-39 chars, idk why. This is done in sepearate function in game. + strncpy(nameTemp, m_pSelectedSkin->skinNameDisplayed, 39); // game doesn't do that, but in our day strncpy to same string is forbidden + strncpy(m_pSelectedSkin->skinNameDisplayed, nameTemp, 39); + if (oldLength - 4 > 39) + m_pSelectedSkin->skinNameDisplayed[39] = '\0'; + + // Make string lowercase, except first letter + strlwr(m_pSelectedSkin->skinNameDisplayed); + strncpy(nameTemp, m_pSelectedSkin->skinNameDisplayed, 1); + strupr(nameTemp); + strncpy(m_pSelectedSkin->skinNameDisplayed, nameTemp, 1); + + // Change some chars +#ifdef FIX_BUGS + for (int k = 0; m_pSelectedSkin->skinNameDisplayed[k] != '\0'; ++k) { +#else + for (int k = 0; m_pSelectedSkin->skinNameOriginal[k] != '\0'; ++k) { +#endif + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "_", 1)) + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], " ", 1); + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "@", 1)) + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], " ", 1); + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "{", 1)) + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], "(", 1); + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "}", 1)) + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], ")", 1); + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "£", 1)) + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], "$", 1); + } + + // Make letters after whitespace uppercase + for (int l = 0; m_pSelectedSkin->skinNameDisplayed[l] != '\0'; ++l) { + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[l], " ", 1)) { + if (m_pSelectedSkin->skinNameDisplayed[l + 1]) { + strncpy(nameTemp, &m_pSelectedSkin->skinNameDisplayed[l + 1], 1); + strupr(nameTemp); + strncpy(&m_pSelectedSkin->skinNameDisplayed[l + 1], nameTemp, 1); + } + } + } + } + OutputDebugString("Finished enumerating skin files."); + m_bSkinsEnumerated = true; + } + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM)), + CRGBA(LIST_BACKGROUND_COLOR.r, LIST_BACKGROUND_COLOR.g, LIST_BACKGROUND_COLOR.b, FadeIn(LIST_BACKGROUND_COLOR.a))); + + // Header (Skin - Date) + if (m_nCurrExLayer == HOVEROPTION_LIST) { + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT), MENU_Y(MENUACTION_SCALE_MULT)); + CFont::SetRightJustifyOn(); + CFont::PrintString(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_DATE_COLUMN_RIGHT), MENU_Y(PLAYERSETUP_LIST_TOP), TheText.Get("FES_DAT")); + switch (m_PrefsLanguage) { + case LANGUAGE_FRENCH: + case LANGUAGE_SPANISH: + CFont::SetScale(MENU_X(0.6f), MENU_Y(MENUACTION_SCALE_MULT)); + break; + default: + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT), MENU_Y(MENUACTION_SCALE_MULT)); + break; + } + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(PLAYERSETUP_SKIN_COLUMN_LEFT), MENU_Y(PLAYERSETUP_LIST_TOP), TheText.Get("FES_SKN")); + + // Skin list + SET_FONT_FOR_LIST_ITEM + if (m_nSkinsTotal > 0) { + for (m_pSelectedSkin = m_pSkinListHead.nextSkin; m_pSelectedSkin->skinId != m_nFirstVisibleRowOnList; + m_pSelectedSkin = m_pSelectedSkin->nextSkin); + + int rowTextY = PLAYERSETUP_LIST_BODY_TOP - 1; + int orderInVisibles = 0; + int rowEndY = PLAYERSETUP_LIST_BODY_TOP + PLAYERSETUP_ROW_HEIGHT + 1; + int rowStartY = PLAYERSETUP_LIST_BODY_TOP; + for (int rowIdx = m_nFirstVisibleRowOnList; + rowIdx < m_nFirstVisibleRowOnList + MAX_VISIBLE_LIST_ROW && m_pSelectedSkin; ) { + + if (m_nMousePosX > MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT) && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT)) { + if (m_nMousePosY > MENU_Y(rowStartY) && m_nMousePosY < MENU_Y(rowEndY)) { + m_nOptionMouseHovering = rowIdx; + if (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY) { + m_nCurrExLayer = HOVEROPTION_LIST; + } + if (m_nHoverOption == HOVEROPTION_SKIN) { + if (rowIdx == m_nSelectedListRow) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + if (m_nSkinsTotal > 0) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + SaveSettings(); + } + } else { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + m_nCurrExLayer = HOVEROPTION_LIST; + m_nSelectedListRow = rowIdx; + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + } + } + } + } + + // Preview skin/change color of row when we focused on another row. + if (orderInVisibles == m_nSelectedListRow - m_nFirstVisibleRowOnList) { + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + static int lastSelectedSkin = -1; + if (m_nSelectedListRow != lastSelectedSkin) { + strcpy(m_aSkinName, m_pSelectedSkin->skinNameOriginal); + CWorld::Players[0].SetPlayerSkin(m_aSkinName); + } + lastSelectedSkin = m_nSelectedListRow; + } else if (!strcmp(m_PrefsSkinFile, m_pSelectedSkin->skinNameOriginal)) { + CFont::SetColor(CRGBA(255, 255, 155, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(LIST_OPTION_COLOR.r, LIST_OPTION_COLOR.g, LIST_OPTION_COLOR.b, FadeIn(LIST_OPTION_COLOR.a))); + } + wchar unicodeTemp[80]; + AsciiToUnicode(m_pSelectedSkin->skinNameDisplayed, unicodeTemp); + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(PLAYERSETUP_SKIN_COLUMN_LEFT), MENU_Y(rowTextY), unicodeTemp); + + // If not "Default skin" option + if (rowIdx != 0) { + char dateTemp[32]; + sprintf(dateTemp, "%s", m_pSelectedSkin->date); + AsciiToUnicode(dateTemp, unicodeTemp); + CFont::SetRightJustifyOn(); + CFont::PrintString(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_DATE_COLUMN_RIGHT), MENU_Y(rowTextY), unicodeTemp); + } + ++orderInVisibles; + rowEndY += PLAYERSETUP_ROW_HEIGHT; + rowStartY += PLAYERSETUP_ROW_HEIGHT; + rowTextY += PLAYERSETUP_ROW_HEIGHT; + ++rowIdx; + m_pSelectedSkin = m_pSelectedSkin->nextSkin; + } + // Scrollbar background + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2 - PLAYERSETUP_SCROLLBAR_WIDTH), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM)), CRGBA(100, 100, 66, FadeIn(205))); + + float scrollbarHeight = SCROLLBAR_MAX_HEIGHT / (m_nSkinsTotal / (float) MAX_VISIBLE_LIST_ROW); + float scrollbarBottom, scrollbarTop; + if (m_nSkinsTotal <= MAX_VISIBLE_LIST_ROW) { + scrollbarBottom = SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 4.0f); + scrollbarTop = MENU_Y(PLAYERSETUP_LIST_BODY_TOP); + + // Scrollbar shadow + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 4), scrollbarTop, + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 1 - PLAYERSETUP_SCROLLBAR_WIDTH), scrollbarBottom + MENU_Y(1.0f)), CRGBA(50, 50, 50, FadeIn(255))); + } else { +#ifdef FIX_BUGS + scrollbarBottom = MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 8 + m_nScrollbarTopMargin + scrollbarHeight); + scrollbarTop = MENU_Y(PLAYERSETUP_LIST_BODY_TOP + m_nScrollbarTopMargin); +#else + scrollbarBottom = MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 4 + m_nScrollbarTopMargin + scrollbarHeight - SCROLLBAR_MAX_HEIGHT / m_nSkinsTotal); + scrollbarTop = MENU_Y(SCROLLBAR_MAX_HEIGHT / m_nSkinsTotal + PLAYERSETUP_LIST_BODY_TOP - 3 + m_nScrollbarTopMargin); +#endif + // Scrollbar shadow + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 4), scrollbarTop, + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 1 - PLAYERSETUP_SCROLLBAR_WIDTH), scrollbarBottom + MENU_Y(1.0f)), + CRGBA(50, 50, 50, FadeIn(255))); + + } + // Scrollbar + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 4), scrollbarTop, + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH), scrollbarBottom), + CRGBA(SCROLLBAR_COLOR.r, SCROLLBAR_COLOR.g, SCROLLBAR_COLOR.b, FadeIn(255))); + + // FIX: Scroll button dimensions are buggy, because: + // 1 - stretches the original image + // 2 - leaves gap between button and scrollbar + if (m_nHoverOption == HOVEROPTION_CLICKED_SCROLL_UP) { +#ifdef FIX_BUGS + m_aMenuSprites[MENUSPRITE_UPON].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION), MENU_Y(PLAYERSETUP_LIST_TOP + PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION)), + CRGBA(255, 255, 255, FadeIn(255))); +#else + m_aMenuSprites[MENUSPRITE_UPON].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(-20.0f), MENU_Y(PLAYERSETUP_LIST_TOP + 58)), + CRGBA(255, 255, 255, FadeIn(255))); +#endif + } else { +#ifdef FIX_BUGS + m_aMenuSprites[MENUSPRITE_UPOFF].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION), MENU_Y(PLAYERSETUP_LIST_TOP + PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION)), + CRGBA(255, 255, 255, FadeIn(255))); +#else + m_aMenuSprites[MENUSPRITE_UPOFF].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(-21.0f), MENU_Y(PLAYERSETUP_LIST_TOP + 58)), + CRGBA(255, 255, 255, FadeIn(255))); +#endif + } + + if (m_nHoverOption == HOVEROPTION_CLICKED_SCROLL_DOWN) { +#ifdef FIX_BUGS + m_aMenuSprites[MENUSPRITE_DOWNON].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION)), + CRGBA(255, 255, 255, FadeIn(255))); +#else + m_aMenuSprites[MENUSPRITE_DOWNON].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2), SCREEN_SCALE_FROM_BOTTOM(141.0f), + MENU_X_RIGHT_ALIGNED(-20.0f), SCREEN_SCALE_FROM_BOTTOM(83.0f)), + CRGBA(255, 255, 255, FadeIn(255))); +#endif + } else { +#ifdef FIX_BUGS + m_aMenuSprites[MENUSPRITE_DOWNOFF].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION)), + CRGBA(255, 255, 255, FadeIn(255))); +#else + m_aMenuSprites[MENUSPRITE_DOWNOFF].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3), SCREEN_SCALE_FROM_BOTTOM(141.0f), + MENU_X_RIGHT_ALIGNED(-21.0f), SCREEN_SCALE_FROM_BOTTOM(83.0f)), + CRGBA(255, 255, 255, FadeIn(255))); +#endif + + } + CPlayerSkin::RenderFrontendSkinEdit(); + + // Big apply button + if (strcmp(m_aSkinName, m_PrefsSkinFile) != 0) { + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + switch (m_PrefsLanguage) { + case LANGUAGE_FRENCH: + CFont::SetScale(MENU_X(1.1f), MENU_Y(1.9f)); + break; + case LANGUAGE_GERMAN: + CFont::SetScale(MENU_X(0.85f), MENU_Y(1.9f)); + break; + case LANGUAGE_ITALIAN: + case LANGUAGE_SPANISH: + CFont::SetScale(MENU_X(1.4f), MENU_Y(1.9f)); + break; + default: + CFont::SetScale(MENU_X(1.9f), MENU_Y(1.9f)); + break; + } + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(120))); + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(20.0f), MENU_Y(220.0f), TheText.Get("FET_APL")); + } + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + + CFont::SetScale(MENU_X(SMALLTEXT_X_SCALE), MENU_Y(SMALLTEXT_Y_SCALE)); + + if ((m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 1) - CFont::GetStringWidth(TheText.Get("FEDS_TB"), true) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 1) + && m_nMousePosY > SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 3) + && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 26)) + || m_nCurrExLayer == HOVEROPTION_BACK) { + if (m_nHoverOption != HOVEROPTION_BACK) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nHoverOption = HOVEROPTION_BACK; + + } else if ((strcmp(m_aSkinName, m_PrefsSkinFile) != 0 + && m_nMousePosX > MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT) + && m_nMousePosX < MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT) + CFont::GetStringWidth(TheText.Get("FES_SET"), true) + && m_nMousePosY > SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 3) + && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 26)) + || m_nCurrExLayer == HOVEROPTION_USESKIN) { + if (m_nHoverOption != HOVEROPTION_USESKIN) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nHoverOption = HOVEROPTION_USESKIN; + + } else if (m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH - 2) + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_TOP) + && m_nMousePosY < MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 3)) { + if (m_nHoverOption != HOVEROPTION_CLICKED_SCROLL_UP && m_nHoverOption != HOVEROPTION_CLICKED_SCROLL_DOWN) + m_nHoverOption = HOVEROPTION_OVER_SCROLL_UP; + + } else if (m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH - 2) + && m_nMousePosY > SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1) + && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM)) { + if (m_nHoverOption != HOVEROPTION_CLICKED_SCROLL_UP && m_nHoverOption != HOVEROPTION_CLICKED_SCROLL_DOWN) + m_nHoverOption = HOVEROPTION_OVER_SCROLL_DOWN; + + } else if (m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH - 2) + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 3) +#ifdef FIX_BUGS + && m_nMousePosY < MENU_Y(PLAYERSETUP_LIST_BODY_TOP + m_nScrollbarTopMargin)) { +#else + && m_nMousePosY < MENU_Y(SCROLLBAR_MAX_HEIGHT / m_nTotalListRow + PLAYERSETUP_LIST_BODY_TOP - 3 + m_nScrollbarTopMargin)) { +#endif + m_nHoverOption = HOVEROPTION_PAGEUP; + + } else if (m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH - 2) +#ifdef FIX_BUGS + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 8 + m_nScrollbarTopMargin + scrollbarHeight) +#else + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 3 + m_nScrollbarTopMargin + scrollbarHeight - SCROLLBAR_MAX_HEIGHT / m_nTotalListRow) +#endif + && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1)) { + m_nHoverOption = HOVEROPTION_PAGEDOWN; + + } else if (m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 4) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH) +#ifdef FIX_BUGS + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_BODY_TOP + m_nScrollbarTopMargin) + && m_nMousePosY < MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 8 + m_nScrollbarTopMargin + scrollbarHeight)) { +#else + && m_nMousePosY > MENU_Y(SCROLLBAR_MAX_HEIGHT / m_nTotalListRow + PLAYERSETUP_LIST_BODY_TOP - 3 + m_nScrollbarTopMargin) + && m_nMousePosY < MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 3 + m_nScrollbarTopMargin + scrollbarHeight - SCROLLBAR_MAX_HEIGHT / m_nTotalListRow)) { +#endif + m_nHoverOption = HOVEROPTION_HOLDING_SCROLLBAR; + + } else if (m_nMousePosX > MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT) && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT) + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_BODY_TOP + 1) && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM)) { + m_nHoverOption = HOVEROPTION_LIST; + + } else { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + } + } + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(SMALLTEXT_X_SCALE), MENU_Y(SMALLTEXT_Y_SCALE)); + CFont::SetRightJustifyOn(); + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(90))); + + // Back button + for (int i = 0; i < 2; i++) { + CFont::PrintString(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3 - i), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 5 - i), TheText.Get("FEDS_TB")); + if (m_nHoverOption == HOVEROPTION_BACK) { + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + } + CFont::SetRightJustifyOff(); + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(90))); + + // Use skin button + for (int i = 0; i < 2; i++) { + CFont::PrintString(MENU_X_LEFT_ALIGNED(i + PLAYERSETUP_LIST_LEFT), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 5 - i), TheText.Get("FES_SET")); + if (!strcmp(m_aSkinName, m_PrefsSkinFile)) { + CFont::SetColor(CRGBA(DARKMENUOPTION_COLOR.r, DARKMENUOPTION_COLOR.g, DARKMENUOPTION_COLOR.b, FadeIn(255))); + } else if (m_nHoverOption == HOVEROPTION_USESKIN) { + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + } + +} + +int +CMenuManager::FadeIn(int alpha) +{ + if (m_nCurrScreen == MENUPAGE_LOADING_IN_PROGRESS || + m_nCurrScreen == MENUPAGE_SAVING_IN_PROGRESS || + m_nCurrScreen == MENUPAGE_DELETING) + return alpha; + + return Min(m_nMenuFadeAlpha, alpha); +} + +void +CMenuManager::FilterOutColorMarkersFromString(wchar *str, CRGBA &newColor) +{ + int newIdx = 0; + wchar copy[256], *c; + UnicodeStrcpy(copy, str); + + for (c = copy; *c != '\0'; c++) { + if (*c == '~') { + c++; + switch (*c) { + case 'b': newColor = CRGBA(40, 40, 255, 255); break; + case 'g': newColor = CRGBA(40, 235, 40, 255); break; + // There is no case for "h", is that a mistake? + case 'l': newColor = CRGBA(0, 0, 0, 255); break; + case 'p': newColor = CRGBA(255, 0, 255, 255); break; + case 'r': newColor = CRGBA(255, 0, 0, 255); break; + case 'w': newColor = CRGBA(255, 255, 255, 255); break; + case 'y': newColor = CRGBA(255, 255, 0, 255); break; + } + while (*c != '~') c++; + } else { + str[newIdx++] = *c; + } + } + str[newIdx] = '\0'; +} + +int +CMenuManager::GetStartOptionsCntrlConfigScreens() +{ + int number = 0; + switch (m_nCurrScreen) { + case MENUPAGE_CONTROLLER_PC_OLD3: + number = 34; + break; + case MENUPAGE_CONTROLLER_DEBUG: + number = 35; + break; + case MENUPAGE_KEYBOARD_CONTROLS: + number = 0; + break; + default: + break; + } + return number; +} + +void +CMenuManager::InitialiseChangedLanguageSettings() +{ + if (m_bFrontEnd_ReloadObrTxtGxt) { + m_bFrontEnd_ReloadObrTxtGxt = false; +#ifdef FIX_BUGS + if (gGameState > GS_INIT_ONCE) +#endif + CTimer::Stop(); + TheText.Unload(); + TheText.Load(); +#ifdef FIX_BUGS + if (gGameState > GS_INIT_ONCE) +#endif + CTimer::Update(); + CGame::frenchGame = false; + CGame::germanGame = false; +#ifdef MORE_LANGUAGES + CGame::russianGame = false; + CGame::japaneseGame = false; + switch (m_PrefsLanguage) { + case LANGUAGE_POLISH: + CFont::ReloadFonts(FONT_LANGSET_POLISH); + break; + case LANGUAGE_RUSSIAN: + CFont::ReloadFonts(FONT_LANGSET_RUSSIAN); + break; + case LANGUAGE_JAPANESE: + CFont::ReloadFonts(FONT_LANGSET_JAPANESE); + break; + default: + CFont::ReloadFonts(FONT_LANGSET_EFIGS); + break; + } +#endif + + switch (m_PrefsLanguage) { + case LANGUAGE_FRENCH: + CGame::frenchGame = true; + break; + case LANGUAGE_GERMAN: + CGame::germanGame = true; + break; +#ifdef MORE_LANGUAGES + case LANGUAGE_RUSSIAN: + CGame::russianGame = true; + break; + case LANGUAGE_JAPANESE: + CGame::japaneseGame = true; + break; +#endif + default: + break; + } + } +} + +void +CMenuManager::LoadAllTextures() +{ + if (m_bSpritesLoaded) + return; + + CentreMousePointer(); + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); + m_nCurrOption = 0; + +#ifdef FIX_BUGS + static bool firstTime = true; + if (firstTime) { + DMAudio.SetRadioInCar(m_PrefsRadioStation); + firstTime = false; + } else +#endif + m_PrefsRadioStation = DMAudio.GetRadioInCar(); + + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (m_PrefsRadioStation > USERTRACK) + m_PrefsRadioStation = CGeneral::GetRandomNumber() % (USERTRACK + 1); + } else if (m_PrefsRadioStation > CHATTERBOX) + m_PrefsRadioStation = CGeneral::GetRandomNumber() % (CHATTERBOX + 1); + + CFileMgr::SetDir(""); + //CFileMgr::SetDir(""); + CTimer::Stop(); + CStreaming::MakeSpaceFor(350 * CDSTREAM_SECTOR_SIZE); // twice of it in mobile + CStreaming::ImGonnaUseStreamingMemory(); + CGame::TidyUpMemory(false, true); + CTxdStore::PushCurrentTxd(); + int frontendTxdSlot = CTxdStore::FindTxdSlot("frontend"); + + if(frontendTxdSlot == -1) + frontendTxdSlot = CTxdStore::AddTxdSlot("frontend"); + + printf("LOAD frontend\n"); + CTxdStore::LoadTxd(frontendTxdSlot, "MODELS/FRONTEND.TXD"); + CTxdStore::AddRef(frontendTxdSlot); + CTxdStore::SetCurrentTxd(frontendTxdSlot); +#if GTA_VERSION < GTA3_PC_11 + CStreaming::IHaveUsedStreamingMemory(); + CTimer::Update(); +#endif + + for (int i = 0; i < ARRAY_SIZE(FrontendFilenames); i++) { + m_aFrontEndSprites[i].SetTexture(FrontendFilenames[i][0], FrontendFilenames[i][1]); + m_aFrontEndSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } + +#ifdef GAMEPAD_MENU + LoadController(m_PrefsControllerType); +#endif + + int menuTxdSlot = CTxdStore::FindTxdSlot("menu"); + + if (menuTxdSlot == -1) + menuTxdSlot = CTxdStore::AddTxdSlot("menu"); + + printf("LOAD sprite\n"); + CTxdStore::LoadTxd(menuTxdSlot, "MODELS/MENU.TXD"); + CTxdStore::AddRef(menuTxdSlot); + CTxdStore::SetCurrentTxd(menuTxdSlot); + + for (int i = 0; i < ARRAY_SIZE(MenuFilenames); i++) { + m_aMenuSprites[i].SetTexture(MenuFilenames[i][0], MenuFilenames[i][1]); + m_aMenuSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } +#ifdef MENU_MAP + static bool menuOptionAdded = false; + for (int i = 0; i < ARRAY_SIZE(MapFilenames); i++) { + RwTexture *firstTile; + if (!menuOptionAdded && (firstTile = RwTextureRead(MapFilenames[i][0], MapFilenames[i][1]))) { + RwTextureDestroy(firstTile); + FrontendOptionSetCursor(MENUPAGE_PAUSE_MENU, 2, false); + FrontendOptionAddBuiltinAction("FEG_MAP", MENUACTION_CHANGEMENU, MENUPAGE_MAP, SAVESLOT_NONE); + menuOptionAdded = true; + } + m_aMapSprites[i].SetTexture(MapFilenames[i][0], MapFilenames[i][1]); + m_aMapSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } + fMapSize = SCREEN_HEIGHT * 2.0f; + fMapCenterX = 0.0f; + fMapCenterY = 0.0f; +#endif +#if GTA_VERSION >= GTA3_PC_11 + CStreaming::IHaveUsedStreamingMemory(); + CTimer::Update(); +#endif + m_bSpritesLoaded = true; + CTxdStore::PopCurrentTxd(); +} + +#ifdef GAMEPAD_MENU +const char* controllerTypesPaths[] = { + nil, + "MODELS/FRONTEND_DS3.TXD", + "MODELS/FRONTEND_DS4.TXD", + "MODELS/FRONTEND_X360.TXD", + "MODELS/FRONTEND_XONE.TXD", + "MODELS/FRONTEND_NSW.TXD", +}; + +void +CMenuManager::LoadController(int8 type) +{ + switch (type) + { + case CONTROLLER_DUALSHOCK2: + case CONTROLLER_DUALSHOCK3: + case CONTROLLER_DUALSHOCK4: + CFont::LoadButtons("MODELS/PS3BTNS.TXD"); + break; + case CONTROLLER_NINTENDO_SWITCH: + CFont::LoadButtons("MODELS/NSWBTNS.TXD"); + break; + default: + CFont::LoadButtons("MODELS/X360BTNS.TXD"); + break; + } + + // Unload current textures + for (int i = FE_CONTROLLER; i <= FE_ARROWS4; i++) + m_aFrontEndSprites[i].Delete(); + + // Unload txd + int frontend_controller = CTxdStore::FindTxdSlot("frontend_controller"); + if (frontend_controller != -1) + CTxdStore::RemoveTxd(frontend_controller); + + // Find the new txd to load + bool bTxdMissing = true; + if (controllerTypesPaths[type]) + if (int file = CFileMgr::OpenFile(controllerTypesPaths[type])) { + CFileMgr::CloseFile(file); + bTxdMissing = false; + } + + int txdSlot = -1; + + if (bTxdMissing) + // Not found, fall back to original textures + txdSlot = CTxdStore::FindTxdSlot("frontend"); + else { + // Found, load txd + txdSlot = frontend_controller; + if (txdSlot == -1) + txdSlot = CTxdStore::AddTxdSlot("frontend_controller"); + CTxdStore::LoadTxd(txdSlot, controllerTypesPaths[type]); + CTxdStore::AddRef(txdSlot); + } + + assert(txdSlot != -1); + // Load new textures + CTxdStore::SetCurrentTxd(txdSlot); + for (int i = FE_CONTROLLER; i <= FE_ARROWS4; i++) { + m_aFrontEndSprites[i].SetTexture(FrontendFilenames[i][0], FrontendFilenames[i][1]); + m_aFrontEndSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } +} +#endif // GAMEPAD_MENU + +void +CMenuManager::LoadSettings() +{ + CFileMgr::SetDirMyDocuments(); + int fileHandle = CFileMgr::OpenFile("gta3.set", "r"); + + int32 prevLang = m_PrefsLanguage; +#if GTA_VERSION >= GTA3_PC_11 + CMBlur::BlurOn = (_dwOperatingSystemVersion != OS_WIN98); +#else + CMBlur::BlurOn = true; +#endif + MousePointerStateHelper.bInvertVertically = true; + + // 50 is silly + char Ver[50]; + + if (fileHandle) { + CFileMgr::Read(fileHandle, Ver, 29); + + if (strncmp(Ver, TopLineEmptyFile, sizeof(TopLineEmptyFile) - 1)) { + CFileMgr::Seek(fileHandle, 0, 0); + ControlsManager.LoadSettings(fileHandle); +#ifdef IMPROVED_VIDEOMODE + CFileMgr::Read(fileHandle, (char*)&m_nPrefsWidth, sizeof(m_nPrefsWidth)); + CFileMgr::Read(fileHandle, (char*)&m_nPrefsHeight, sizeof(m_nPrefsHeight)); + CFileMgr::Read(fileHandle, (char*)&m_nPrefsDepth, sizeof(m_nPrefsDepth)); + CFileMgr::Read(fileHandle, (char*)&m_nPrefsWindowed, sizeof(m_nPrefsWindowed)); + CFileMgr::Read(fileHandle, (char*)&m_nPrefsSubsystem, sizeof(m_nPrefsSubsystem)); + if(m_nPrefsWindowed != 0 && m_nPrefsWindowed != 1){ + // garbage data from vanilla settings file + // let skeleton find something + m_nPrefsWidth = 0; + m_nPrefsHeight = 0; + m_nPrefsDepth = 0; + m_nPrefsWindowed = 0; + m_nPrefsSubsystem = 0; + } + m_nSelectedScreenMode = m_nPrefsWindowed; +#else + CFileMgr::Read(fileHandle, gString, 20); +#endif + CFileMgr::Read(fileHandle, gString, 20); + CFileMgr::Read(fileHandle, gString, 4); + CFileMgr::Read(fileHandle, gString, 4); + CFileMgr::Read(fileHandle, gString, 1); + CFileMgr::Read(fileHandle, gString, 1); + CFileMgr::Read(fileHandle, gString, 1); + CFileMgr::Read(fileHandle, (char*)&TheCamera.m_bHeadBob, 1); + CFileMgr::Read(fileHandle, (char*)&TheCamera.m_fMouseAccelHorzntl, 4); + CFileMgr::Read(fileHandle, (char*)&TheCamera.m_fMouseAccelVertical, 4); + CFileMgr::Read(fileHandle, (char*)&MousePointerStateHelper.bInvertVertically, 1); + CFileMgr::Read(fileHandle, (char*)&CVehicle::m_bDisableMouseSteering, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsSfxVolume, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsMusicVolume, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsRadioStation, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsSpeakers, 1); + CFileMgr::Read(fileHandle, (char*)&m_nPrefsAudio3DProviderIndex, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsDMA, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsBrightness, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsLOD, 4); + CFileMgr::Read(fileHandle, (char*)&m_PrefsShowSubtitles, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsUseWideScreen, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsVsyncDisp, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsFrameLimiter, 1); + CFileMgr::Read(fileHandle, (char*)&m_nDisplayVideoMode, 1); + CFileMgr::Read(fileHandle, (char*)&CMBlur::BlurOn, 1); + CFileMgr::Read(fileHandle, m_PrefsSkinFile, 256); + CFileMgr::Read(fileHandle, (char*)&m_ControlMethod, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsLanguage, 1); + } + } + + CFileMgr::CloseFile(fileHandle); + CFileMgr::SetDir(""); + +#ifdef LOAD_INI_SETTINGS + if (LoadINISettings()) { + LoadINIControllerSettings(); + } +#endif + + m_PrefsVsync = m_PrefsVsyncDisp; + CRenderer::ms_lodDistScale = m_PrefsLOD; + + if (m_nPrefsAudio3DProviderIndex == -1) + m_nPrefsAudio3DProviderIndex = -2; + + if (m_PrefsLanguage == prevLang) + m_bLanguageLoaded = false; + else { + m_bLanguageLoaded = true; + // Already called in InitialiseChangedLanguageSettings + /* + TheText.Unload(); + TheText.Load(); + */ + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + + OutputDebugString("The previously saved language is now in use"); + } + + WIN32_FIND_DATA FindFileData; + char skinfile[256+16]; // Stack analysis shows 16 bits gap, but I don't trust it. It may very well be MAX_PATH(260). + bool SkinFound = false; + HANDLE handle = FindFirstFile("skins\\*.bmp", &FindFileData); + for (int i = 1; handle != INVALID_HANDLE_VALUE && i; i = FindNextFile(handle, &FindFileData)) { + strcpy(skinfile, m_PrefsSkinFile); + strcat(skinfile, ".bmp"); + if (strcmp(FindFileData.cFileName, skinfile) == 0) + SkinFound = true; + } + FindClose(handle); + + if (!SkinFound) { + OutputDebugString("Default skin set as no other skins are available OR saved skin not found!"); + strcpy(m_PrefsSkinFile, DEFAULT_SKIN_NAME); + strcpy(m_aSkinName, DEFAULT_SKIN_NAME); + } +} + +void +CMenuManager::SaveSettings() +{ +#ifndef LOAD_INI_SETTINGS + static char RubbishString[48] = "stuffmorestuffevenmorestuff etc"; + + CFileMgr::SetDirMyDocuments(); + + int fileHandle = CFileMgr::OpenFile("gta3.set", "w+"); + if (fileHandle) { + ControlsManager.SaveSettings(fileHandle); +#ifdef IMPROVED_VIDEOMODE + CFileMgr::Write(fileHandle, (char*)&m_nPrefsWidth, sizeof(m_nPrefsWidth)); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsHeight, sizeof(m_nPrefsHeight)); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsDepth, sizeof(m_nPrefsDepth)); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsWindowed, sizeof(m_nPrefsWindowed)); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsSubsystem, sizeof(m_nPrefsSubsystem)); +#else + CFileMgr::Write(fileHandle, RubbishString, 20); +#endif + CFileMgr::Write(fileHandle, RubbishString, 20); + CFileMgr::Write(fileHandle, RubbishString, 4); + CFileMgr::Write(fileHandle, RubbishString, 4); + CFileMgr::Write(fileHandle, RubbishString, 1); + CFileMgr::Write(fileHandle, RubbishString, 1); + CFileMgr::Write(fileHandle, RubbishString, 1); + CFileMgr::Write(fileHandle, (char*)&TheCamera.m_bHeadBob, 1); + CFileMgr::Write(fileHandle, (char*)&TheCamera.m_fMouseAccelHorzntl, 4); + CFileMgr::Write(fileHandle, (char*)&TheCamera.m_fMouseAccelVertical, 4); + CFileMgr::Write(fileHandle, (char*)&MousePointerStateHelper.bInvertVertically, 1); + CFileMgr::Write(fileHandle, (char*)&CVehicle::m_bDisableMouseSteering, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsSfxVolume, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsMusicVolume, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsRadioStation, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsSpeakers, 1); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsAudio3DProviderIndex, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsDMA, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsBrightness, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsLOD, sizeof(m_PrefsLOD)); + CFileMgr::Write(fileHandle, (char*)&m_PrefsShowSubtitles, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsUseWideScreen, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsVsyncDisp, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsFrameLimiter, 1); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsVideoMode, 1); + CFileMgr::Write(fileHandle, (char*)&CMBlur::BlurOn, 1); + CFileMgr::Write(fileHandle, m_PrefsSkinFile, 256); + CFileMgr::Write(fileHandle, (char*)&m_ControlMethod, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsLanguage, 1); + } + + CFileMgr::CloseFile(fileHandle); + CFileMgr::SetDir(""); + +#else + SaveINISettings(); +#endif +} + +bool DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +void DoRWStuffEndOfFrame(void); + +void +CMenuManager::MessageScreen(const char *text) +{ + CSprite2d *splash = LoadSplash(nil); + if (!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) + return; + + 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)); + + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetJustifyOn(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCREEN_WIDTH - StretchX(170.0f)); // unused + CFont::SetRightJustifyWrap(SCREEN_WIDTH - StretchX(170.0f)); // unused + CSprite2d::DrawRect(CRect(StretchX(120.0f), StretchY(150.0f), SCREEN_WIDTH - StretchX(120.0f), SCREEN_HEIGHT - StretchY(220.0f)), CRGBA(50, 50, 50, 210)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetCentreSize(SCREEN_SCALE_X(380.0f)); + CFont::SetCentreOn(); + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, 255)); + CFont::SetScale(SCREEN_SCALE_X(SMALLTEXT_X_SCALE), SCREEN_SCALE_Y(SMALLTEXT_Y_SCALE)); + CFont::PrintString(StretchX(320.0f), StretchY(170.0f), TheText.Get(text)); + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +void +CMenuManager::PickNewPlayerColour() +{ + m_PrefsPlayerRed = 0; + m_PrefsPlayerGreen = 0; + m_PrefsPlayerBlue = 0; + while (true) { + int sum = m_PrefsPlayerRed + m_PrefsPlayerGreen + m_PrefsPlayerBlue; + if (sum >= 100 && sum <= 650) + break; + m_PrefsPlayerRed = CGeneral::GetRandomNumber(); + m_PrefsPlayerGreen = CGeneral::GetRandomNumber(); + m_PrefsPlayerBlue = CGeneral::GetRandomNumber(); + } +} + +void +CMenuManager::PrintBriefs() +{ + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetRightJustifyOff(); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X * 0.7), MENU_Y(MENU_TEXT_SIZE_Y * 0.9)); // second mulipliers are double, idk why + + float nextY = BRIEFS_TOP_MARGIN; + CRGBA newColor; + for (int i = 4; i >= 0; i--) { + tPreviousBrief &brief = CMessages::PreviousBriefs[i]; + if (brief.m_pText) { + CMessages::InsertNumberInString(brief.m_pText, + brief.m_nNumber[0], brief.m_nNumber[1], + brief.m_nNumber[2], brief.m_nNumber[3], + brief.m_nNumber[4], brief.m_nNumber[5], gUString); + CMessages::InsertStringInString(gUString, brief.m_pString); + CMessages::InsertPlayerControlKeysInString(gUString); + newColor = TEXT_COLOR; + FilterOutColorMarkersFromString(gUString, newColor); + if (newColor != TEXT_COLOR) { + newColor.r /= 2; + newColor.g /= 2; + newColor.b /= 2; + } + +#ifdef PS2_LIKE_MENU + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetDropShadowPosition(1); +#endif + +#if defined(FIX_BUGS) || defined(PS2_LIKE_MENU) + newColor.a = FadeIn(255); + CFont::SetColor(newColor); +#endif + CFont::PrintString(MENU_X_LEFT_ALIGNED(BRIEFS_LINE_X), nextY, gUString); + nextY += MENU_Y(BRIEFS_LINE_HEIGHT); + } + } + +#ifdef PS2_LIKE_MENU + CFont::SetDropShadowPosition(0); +#endif +} + +// Not sure about name. Not to be confused with CPad::PrintErrorMessage +void +CMenuManager::PrintErrorMessage() +{ + if (!CPad::bDisplayNoControllerMessage && !CPad::bObsoleteControllerMessage) + return; + + CSprite2d::DrawRect(CRect(SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(140.0f), SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(120.0f)), CRGBA(64, 16, 16, 224)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(MENU_X_MARGIN)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_X(50.0f), SCREEN_SCALE_Y(180.0f), TheText.Get(CPad::bDisplayNoControllerMessage ? "NOCONT" : "WRCONT")); +#else + CFont::PrintString(SCREEN_SCALE_X(50.0f), SCREEN_SCALE_Y(40.0f), TheText.Get(CPad::bDisplayNoControllerMessage ? "NOCONT" : "WRCONT")); +#endif + CFont::DrawFonts(); +} + +void +CMenuManager::PrintStats() +{ + int rowNum = ConstructStatLine(99999); +#if GTA_VERSION >= GTA3_PC_11 + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); +#endif + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X * 0.7), MENU_Y(MENU_TEXT_SIZE_Y * 0.9)); // second mulipliers are double, idk why + float nextYChange, y, alphaMult; + + // Scroll stats with mouse +#ifdef SCROLLABLE_STATS_PAGE + static float scrollY = 0; + static uint32 lastChange = m_nScreenChangeDelayTimer; + if (CPad::GetPad(0)->GetLeftMouse()) { + scrollY += (m_nMouseOldPosY - m_nMousePosY); + lastChange = CTimer::GetTimeInMillisecondsPauseMode(); + } else { + scrollY += MENU_Y(STATS_SLIDE_Y_PER_SECOND) / 1000.0f * (CTimer::GetTimeInMillisecondsPauseMode() - lastChange); + lastChange = CTimer::GetTimeInMillisecondsPauseMode(); + } +#else + // MENU_Y(30.0f) per second + float scrollY = MENU_Y(STATS_SLIDE_Y_PER_SECOND) * (CTimer::GetTimeInMillisecondsPauseMode() - m_nScreenChangeDelayTimer) / 1000.0f; +#endif + + for (int row = 0; row < rowNum; ++row) { + // Put just got hidden text at the top back to the bottom, in circular fashion + for (y = MENU_Y(STATS_ROW_HEIGHT - 1) * row + SCREEN_HEIGHT - scrollY; MENU_Y(STATS_PUT_BACK_TO_BOTTOM_Y) > y; y += nextYChange) { + nextYChange = (MENU_Y(STATS_ROW_HEIGHT) + rowNum) * MENU_Y(STATS_ROW_HEIGHT - 1); + } + + // If it's still on screen + if (y > 0.0f && SCREEN_HEIGHT > y) { + ConstructStatLine(row); + + // But about to dim from top + if (y - MENU_Y(STATS_BOTTOM_MARGIN) < MENU_Y(STATS_TOP_DIMMING_AREA_LENGTH)) { + if ((y - MENU_Y(STATS_BOTTOM_MARGIN)) / MENU_Y(STATS_TOP_DIMMING_AREA_LENGTH) < 0.0f) + alphaMult = 0.0f; + else + alphaMult = (y - MENU_Y(STATS_BOTTOM_MARGIN)) / MENU_Y(STATS_TOP_DIMMING_AREA_LENGTH); + + // About to dim from bottom + } else if (y > SCREEN_SCALE_FROM_BOTTOM(STATS_TOP_DIMMING_AREA_LENGTH) - MENU_Y(STATS_BOTTOM_DIMMING_AREA_LENGTH)) { + if ((SCREEN_SCALE_FROM_BOTTOM(STATS_BOTTOM_DIMMING_AREA_LENGTH) - y) / MENU_Y(STATS_TOP_DIMMING_AREA_LENGTH) < 0.0f) + alphaMult = 0.0f; + else + alphaMult = (SCREEN_SCALE_FROM_BOTTOM(STATS_BOTTOM_DIMMING_AREA_LENGTH) - y) / MENU_Y(STATS_TOP_DIMMING_AREA_LENGTH); + } else + alphaMult = 1.0f; + + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255.0f * alphaMult))); + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(STATS_ROW_X_MARGIN), y - MENU_Y(STATS_BOTTOM_MARGIN - STATS_TOP_MARGIN), gUString); + CFont::SetRightJustifyOn(); + CFont::PrintString(MENU_X_RIGHT_ALIGNED(STATS_ROW_X_MARGIN), y - MENU_Y(STATS_BOTTOM_MARGIN - STATS_TOP_MARGIN), gUString2); + } + } + // Game doesn't do that, but it's better + float nextX = MENU_X_LEFT_ALIGNED(STATS_RATING_X); + + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + CFont::SetRightJustifyOff(); + CFont::PrintString(nextX, MENU_Y(STATS_RATING_Y), TheText.Get("CRIMRA")); +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + nextX += MENU_X(10.0f) + CFont::GetStringWidth_Jap(TheText.Get("CRIMRA")); + else +#endif + nextX += MENU_X(10.0f) + CFont::GetStringWidth(TheText.Get("CRIMRA"), true); + UnicodeStrcpy(gUString, CStats::FindCriminalRatingString()); + CFont::PrintString(nextX, MENU_Y(STATS_RATING_Y), gUString); +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + nextX += MENU_X(6.0f) + CFont::GetStringWidth_Jap(gUString); + else +#endif + nextX += MENU_X(6.0f) + CFont::GetStringWidth(gUString, true); + sprintf(gString, "%d", CStats::FindCriminalRatingNumber()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(nextX, MENU_Y(STATS_RATING_Y), gUString); + + // ::Draw already does that. + /* + SET_FONT_FOR_MENU_HEADER + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), TheText.Get(aScreens[m_nCurrScreen].m_ScreenName)); + */ + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X), MENU_Y(MENU_TEXT_SIZE_Y)); +} + +void +CMenuManager::Process(void) +{ + m_bMenuStateChanged = false; + + if (!m_bSaveMenuActive && TheCamera.GetScreenFadeStatus() != FADE_0) + return; + + m_bWantToRestart = false; + InitialiseChangedLanguageSettings(); + + // Just a hack by R* to not make game continuously resume/pause. But we it seems we can live with it. + if (CPad::GetPad(0)->GetEscapeJustDown()) + RequestFrontEndStartUp(); + + SwitchMenuOnAndOff(); + + // Be able to re-open menu correctly. + if (m_bMenuActive) { + + // Load frontend textures. + LoadAllTextures(); + + // Set save/delete game pages. + if (m_nCurrScreen == MENUPAGE_DELETING) { + bool SlotPopulated = false; + + if (PcSaveHelper.DeleteSlot(m_nCurrSaveSlot)) { + PcSaveHelper.PopulateSlotInfo(); + SlotPopulated = true; + } + + if (SlotPopulated) + ChangeScreen(MENUPAGE_DELETE_SUCCESS, 0, true, false); + else + SaveLoadFileError_SetUpErrorScreen(); + } + if (m_nCurrScreen == MENUPAGE_SAVING_IN_PROGRESS) { + int8 SaveSlot = PcSaveHelper.SaveSlot(m_nCurrSaveSlot); + PcSaveHelper.PopulateSlotInfo(); + if (SaveSlot) + ChangeScreen(MENUPAGE_SAVE_SUCCESSFUL, 0, true, false); + else + SaveLoadFileError_SetUpErrorScreen(); + } + if (m_nCurrScreen == MENUPAGE_LOADING_IN_PROGRESS) { +#ifdef MISSION_REPLAY + if (doingMissionRetry) { + RetryMission(MISSION_RETRY_TYPE_BEGIN_RESTARTING); + m_nCurrSaveSlot = SLOT_COUNT; + doingMissionRetry = false; + } +#endif + if (CheckSlotDataValid(m_nCurrSaveSlot)) { +#ifdef USE_DEBUG_SCRIPT_LOADER + CTheScripts::ScriptToLoad = 0; +#endif +#ifdef PC_PLAYER_CONTROLS + TheCamera.m_bUseMouse3rdPerson = m_ControlMethod == CONTROL_STANDARD; +#endif + if (m_PrefsVsyncDisp != m_PrefsVsync) + m_PrefsVsync = m_PrefsVsyncDisp; + DMAudio.Service(); + m_bWantToRestart = true; + RequestFrontEndShutDown(); + m_bWantToLoad = true; + b_FoundRecentSavedGameWantToLoad = true; + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); + } else + SaveLoadFileError_SetUpErrorScreen(); + } + + ProcessButtonPresses(); + + // Set binding keys. + if (pEditString && CPad::EditString(pEditString, 0) == nil) { + if (*pEditString == 0) + strcpy(pEditString, "NoName"); + pEditString = nil; + SaveSettings(); + } + + if (m_bWaitingForNewKeyBind) { + if (m_bStartWaitingForKeyBind) + m_bStartWaitingForKeyBind = false; + else { + pControlEdit = CPad::EditCodesForControls(pControlEdit, 1); + JoyButtonJustClicked = false; + MouseButtonJustClicked = false; + + if (CPad::GetPad(0)->GetLeftMouseJustDown()) + MouseButtonJustClicked = rsMOUSELEFTBUTTON; + else if (CPad::GetPad(0)->GetRightMouseJustUp()) + MouseButtonJustClicked = rsMOUSERIGHTBUTTON; + else if (CPad::GetPad(0)->GetMiddleMouseJustUp()) + MouseButtonJustClicked = rsMOUSMIDDLEBUTTON; + else if (CPad::GetPad(0)->GetMouseWheelUpJustUp()) + MouseButtonJustClicked = rsMOUSEWHEELUPBUTTON; + else if (CPad::GetPad(0)->GetMouseWheelDownJustUp()) + MouseButtonJustClicked = rsMOUSEWHEELDOWNBUTTON; + else if (CPad::GetPad(0)->GetMouseX1JustUp()) + MouseButtonJustClicked = rsMOUSEX1BUTTON; + else if (CPad::GetPad(0)->GetMouseX2JustUp()) + MouseButtonJustClicked = rsMOUSEX2BUTTON; + + JoyButtonJustClicked = ControlsManager.GetJoyButtonJustDown(); + + int32 TypeOfControl = KEYBOARD; + if (JoyButtonJustClicked) + TypeOfControl = JOYSTICK; + if (MouseButtonJustClicked) + TypeOfControl = MOUSE; + if (*pControlEdit != rsNULL) + TypeOfControl = KEYBOARD; + + if (!m_bKeyIsOK) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + pControlEdit = nil; + m_bWaitingForNewKeyBind = false; + m_KeyPressedCode = -1; + m_bStartWaitingForKeyBind = false; + } else if (!m_bKeyChangeNotProcessed) { + if (*pControlEdit != rsNULL || MouseButtonJustClicked || JoyButtonJustClicked) + CheckCodesForControls(TypeOfControl); + + field_535 = true; + } else { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + for (int i = 0; i < 4; i++) + ControlsManager.ClearSettingsAssociatedWithAction((e_ControllerAction)m_CurrCntrlAction, (eControllerType)i); + m_bKeyIsOK = false; + m_bKeyChangeNotProcessed = false; + pControlEdit = nil; + m_bWaitingForNewKeyBind = false; + m_KeyPressedCode = -1; + m_bStartWaitingForKeyBind = false; + } + } + } + + if ((m_nCurrScreen == MENUPAGE_NO_MEMORY_CARD || m_nCurrScreen == MENUPAGE_PS2_LOAD_FAILED) && CTimer::GetTimeInMillisecondsPauseMode() > field_558) { + m_nCurrScreen = m_nPrevScreen; + m_nCurrOption = 0; + } + + // Reset pad shaking. + if (TimeToStopPadShaking && TimeToStopPadShaking < CTimer::GetTimeInMillisecondsPauseMode()) { + CPad::StopPadsShaking(); + TimeToStopPadShaking = 0; + } + + } else { + UnloadTextures(); + m_bRenderGameInMenu = false; + // byte_5F33E4 = 1; // unused + ChangeScreen(MENUPAGE_NONE, 0, false, false); + pEditString = nil; + m_bWaitingForNewKeyBind = false; + } + + if (!m_bWantToRestart) { + if (m_bGameNotLoaded) + DMAudio.Service(); + } +} + +void +CMenuManager::ProcessButtonPresses(void) +{ + if (pEditString || pControlEdit) + return; + + bool goBack = false; + bool optionSelected = false; + bool goUp = false; + bool goDown = false; +#ifdef TIDY_UP_PBP + bool assumeIncrease = false; +#endif + +#ifdef USE_DEBUG_SCRIPT_LOADER + if (m_nCurrScreen == MENUPAGE_START_MENU || m_nCurrScreen == MENUPAGE_NEW_GAME || m_nCurrScreen == MENUPAGE_NEW_GAME_RELOAD) { + if (CPad::GetPad(0)->GetChar('R')) { + CTheScripts::ScriptToLoad = 1; + DoSettingsBeforeStartingAGame(); + return; + } + if (CPad::GetPad(0)->GetChar('D')) { + CTheScripts::ScriptToLoad = 2; + DoSettingsBeforeStartingAGame(); + return; + } + } +#endif + + if (!m_bShowMouse && (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY)) { + m_bShowMouse = true; + } + + 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; + + if (hasNativeList(m_nCurrScreen)) { + // Not split to seperate function in III as in VC, but we need it for scrollable pages :) + ProcessList(goBack, optionSelected); + + } else if (isPlainTextScreen(m_nCurrScreen)) { +#ifndef TIDY_UP_PBP + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown() || CPad::GetPad(0)->GetLeftMouseJustDown()) { + optionSelected = true; + } + if (CPad::GetPad(0)->GetEscapeJustDown() || CPad::GetPad(0)->GetBackJustUp()) { + if (m_nCurrScreen != MENUPAGE_START_MENU) { + goBack = true; + } + } +#endif + } else { + if (CPad::GetPad(0)->GetDownJustDown() || CPad::GetPad(0)->GetAnaloguePadDown() || CPad::GetPad(0)->GetDPadDownJustDown()) { + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + goDown = true; + } else if (CPad::GetPad(0)->GetUpJustDown() || CPad::GetPad(0)->GetAnaloguePadUp() || CPad::GetPad(0)->GetDPadUpJustDown()) { + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + goUp = true; + } + +#ifndef TIDY_UP_PBP + if ((m_nCurrOption == 0) && (m_nCurrScreen == MENUPAGE_PAUSE_MENU)) { + if (CPad::GetPad(0)->GetEnterJustUp() || CPad::GetPad(0)->GetCrossJustUp()) { + m_bShowMouse = false; + optionSelected = true; + } + } else { + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown()) { + m_bShowMouse = false; + optionSelected = true; + } + } +#endif + + if (CPad::GetPad(0)->GetLeftMouseJustUp()) { +#ifndef TIDY_UP_PBP + if (((m_nCurrOption == 0) && (m_nCurrScreen == MENUPAGE_PAUSE_MENU)) && +#else + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_RESUME && +#endif + (m_nHoverOption == HOVEROPTION_RANDOM_ITEM)) { + m_nCurrOption = m_nOptionMouseHovering; + optionSelected = true; + } + } else if (CPad::GetPad(0)->GetLeftMouseJustDown()) { +#ifdef TIDY_UP_PBP + if (m_nHoverOption >= HOVEROPTION_RADIO_0 && m_nHoverOption <= HOVEROPTION_RADIO_9) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = m_nHoverOption - HOVEROPTION_RADIO_0; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + } else if (m_nHoverOption == HOVEROPTION_RANDOM_ITEM + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_RESUME) { + m_nCurrOption = m_nOptionMouseHovering; + optionSelected = true; + } +#else + switch (m_nHoverOption) { + case HOVEROPTION_RADIO_0: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = HEAD_RADIO; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_1: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = DOUBLE_CLEF; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_2: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = JAH_RADIO; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_3: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = RISE_FM; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_4: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = LIPS_106; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_5: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = GAME_FM; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_6: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = MSX_FM; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_7: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = FLASHBACK; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_8: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = CHATTERBOX; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_9: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = USERTRACK; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RANDOM_ITEM: + if (((m_nCurrOption != 0) || (m_nCurrScreen != MENUPAGE_PAUSE_MENU)) { + m_nCurrOption = m_nOptionMouseHovering; + optionSelected = true; + } + break; + } +#endif + } + + if (CPad::GetPad(0)->GetLeftMouse()) { +#ifndef TIDY_UP_PBP + switch (m_nHoverOption) { + case HOVEROPTION_INCREASE_BRIGHTNESS: + m_PrefsBrightness = m_PrefsBrightness + (512 / MENUSLIDER_LOGICAL_BARS); + if (m_PrefsBrightness < 0) { + m_PrefsBrightness = 0; + } + if (510 < m_PrefsBrightness) { + m_PrefsBrightness = 511; + } + SaveSettings(); + break; + case HOVEROPTION_DECREASE_BRIGHTNESS: + m_PrefsBrightness = m_PrefsBrightness - (512 / MENUSLIDER_LOGICAL_BARS); + if (m_PrefsBrightness < 0) { + m_PrefsBrightness = 0; + } + if (510 < m_PrefsBrightness) { + m_PrefsBrightness = 511; + } + SaveSettings(); + break; + case HOVEROPTION_INCREASE_DRAWDIST: + m_PrefsLOD = m_PrefsLOD + (1.0f / MENUSLIDER_LOGICAL_BARS); + m_PrefsLOD = min(1.8f, m_PrefsLOD); + CRenderer::ms_lodDistScale = m_PrefsLOD; + SaveSettings(); + break; + case HOVEROPTION_DECREASE_DRAWDIST: + m_PrefsLOD = m_PrefsLOD - (1.0f / MENUSLIDER_LOGICAL_BARS); + m_PrefsLOD = max(0.8f, m_PrefsLOD); + CRenderer::ms_lodDistScale = m_PrefsLOD; + SaveSettings(); + break; + case HOVEROPTION_INCREASE_MUSICVOLUME: + m_PrefsMusicVolume = m_PrefsMusicVolume + (128 / MENUSLIDER_LOGICAL_BARS); + m_PrefsMusicVolume = Clamp(m_PrefsMusicVolume, 0, 127); + DMAudio.SetMusicMasterVolume(uchar)(m_PrefsMusicVolume); + SaveSettings(); + break; + case HOVEROPTION_DECREASE_MUSICVOLUME: + m_PrefsMusicVolume = m_PrefsMusicVolume - (128 / MENUSLIDER_LOGICAL_BARS); + if (m_PrefsMusicVolume < 0) { + m_PrefsMusicVolume = 0; + } + if (126 < m_PrefsMusicVolume) { + m_PrefsMusicVolume = 127; + } + DMAudio.SetMusicMasterVolume(uchar)(m_PrefsMusicVolume); + SaveSettings(); + break; + case HOVEROPTION_INCREASE_SFXVOLUME: + m_PrefsSFXVolume = m_PrefsSFXVolume + (128 / MENUSLIDER_LOGICAL_BARS); + if (m_PrefsSFXVolume < 0) { + m_PrefsSFXVolume = 0; + } + if (126 < m_PrefsSFXVolume) { + m_PrefsSFXVolume = 127; + } + DMAudio.SetEffectsMasterVolume(uchar)(m_PrefsSFXVolume); + SaveSettings(); + break; + case HOVEROPTION_DECREASE_SFXVOLUME: + m_PrefsSFXVolume = m_PrefsSFXVolume - (128 / MENUSLIDER_LOGICAL_BARS); + if (m_PrefsSFXVolume < 0) { + m_PrefsSFXVolume = 0; + } + if (126 < m_PrefsSFXVolume) { + m_PrefsSFXVolume = 127; + } + DMAudio.SetEffectsMasterVolume(uchar)(m_PrefsSFXVolume); + SaveSettings(); + break; + case HOVEROPTION_INCREASE_MOUSESENS: + TheCamera.m_fMouseAccelHorzntl += 1.0f/200.0f/15.0f; // probably because diving it to 15 instead of 16(MENUSLIDER_LOGICAL_BARS) had more accurate steps + TheCamera.m_fMouseAccelHorzntl = Clamp(TheCamera.m_fMouseAccelHorzntl, 1.0f / 3200, 1.0f / 200); +#ifdef FIX_BUGS + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl + 0.0005f; +#else + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl; +#endif + SaveSettings(); + break; + case HOVEROPTION_DECREASE_MOUSESENS: + TheCamera.m_fMouseAccelHorzntl -= 1.0f/200.0f/15.0f; // probably because diving it to 15 instead of 16(MENUSLIDER_LOGICAL_BARS) had more accurate steps + TheCamera.m_fMouseAccelHorzntl = Clamp(TheCamera.m_fMouseAccelHorzntl, 1.0f / 3200, 1.0f / 200); +#ifdef FIX_BUGS + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl + 0.0005f; +#else + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl; +#endif + SaveSettings(); + break; + } +#else + switch (m_nHoverOption) { + case HOVEROPTION_INCREASE_BRIGHTNESS: + case HOVEROPTION_INCREASE_DRAWDIST: + case HOVEROPTION_INCREASE_MUSICVOLUME: + case HOVEROPTION_INCREASE_SFXVOLUME: + case HOVEROPTION_INCREASE_MOUSESENS: +#ifdef CUSTOM_FRONTEND_OPTIONS + case HOVEROPTION_INCREASE_CFO_SLIDER: +#endif + CheckSliderMovement(1); + break; + case HOVEROPTION_DECREASE_BRIGHTNESS: + case HOVEROPTION_DECREASE_DRAWDIST: + case HOVEROPTION_DECREASE_MUSICVOLUME: + case HOVEROPTION_DECREASE_SFXVOLUME: + case HOVEROPTION_DECREASE_MOUSESENS: +#ifdef CUSTOM_FRONTEND_OPTIONS + case HOVEROPTION_DECREASE_CFO_SLIDER: +#endif + CheckSliderMovement(-1); + break; + } +#endif + } + +#ifdef SCROLLABLE_PAGES + if (m_nTotalListRow > MAX_VISIBLE_OPTION) { + bool temp = false; + + m_nSelectedListRow = m_nCurrOption; + + // ignore detected back/select states, it's our screen's job + ProcessList(temp, temp); + + // and ignore our screen's goUp/Down, now it's ProcessList's job + goUp = false; + goDown = false; + m_nCurrOption = m_nSelectedListRow; + } + + // Prevent sound on scroll. Mouse wheel is now belongs to us! + if (!(m_nTotalListRow > MAX_VISIBLE_OPTION && (CPad::GetPad(0)->GetMouseWheelUpJustDown() || CPad::GetPad(0)->GetMouseWheelDownJustDown()))) +#endif + + if (CPad::GetPad(0)->GetLeftMouseJustUp() || CPad::GetPad(0)->GetLeftJustUp() || CPad::GetPad(0)->GetRightJustUp() + || CPad::GetPad(0)->GetDPadLeftJustUp() || CPad::GetPad(0)->GetDPadRightJustUp() + || CPad::GetPad(0)->GetAnaloguePadLeftJustUp() || CPad::GetPad(0)->GetAnaloguePadRightJustUp() + || CPad::GetPad(0)->GetMouseWheelUpJustDown() || CPad::GetPad(0)->GetMouseWheelDownJustDown()) { + int option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; + if (option == MENUACTION_BRIGHTNESS || option == MENUACTION_DRAWDIST +#ifdef CUSTOM_FRONTEND_OPTIONS + || option == MENUACTION_CFO_SLIDER +#endif + ) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + else if (option == MENUACTION_SFXVOLUME) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_AUDIO_TEST, 0); + else if (option == MENUACTION_MOUSESENS) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + + } + +#ifndef TIDY_UP_PBP + if (CPad::GetPad(0)->GetBackJustDown()) { + if (m_nCurrScreen != MENUPAGE_START_MENU && m_nCurrScreen != MENUPAGE_PAUSE_MENU) { + m_bShowMouse = false; + goBack = true; + } + } + + if (CPad::GetPad(0)->GetEscapeJustDown()) { + if (m_nCurrScreen != MENUPAGE_START_MENU) { + m_bShowMouse = false; + goBack = true; + } + } + + if (((goDown) || (goUp)) || (optionSelected)) { + goBack = false; + } +#endif + } + + // Centralized enter/back (except some conditions) +#ifdef TIDY_UP_PBP + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_RESUME) { + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown() || + (isPlainTextScreen(m_nCurrScreen) && CPad::GetPad(0)->GetLeftMouseJustDown())) { + + if (!isPlainTextScreen(m_nCurrScreen)) + m_bShowMouse = false; + + optionSelected = true; + } + } else { + if (CPad::GetPad(0)->GetEnterJustUp() || CPad::GetPad(0)->GetCrossJustUp()) { + m_bShowMouse = false; + optionSelected = true; + } + } + + if (!goDown && !goUp && !optionSelected) { + if (m_nCurrScreen != MENUPAGE_START_MENU) { + if (isPlainTextScreen(m_nCurrScreen)) { + if (CPad::GetPad(0)->GetEscapeJustDown() || CPad::GetPad(0)->GetBackJustUp()) { + goBack = true; + } + } else { + if (CPad::GetPad(0)->GetEscapeJustDown() || (m_nCurrScreen != MENUPAGE_PAUSE_MENU && CPad::GetPad(0)->GetBackJustDown())) { + m_bShowMouse = false; + goBack = true; + } + } + } + } +#endif + +#ifdef PS2_LIKE_MENU + if (CPad::GetPad(0)->GetLeftMouseJustDown() && hoveredBottomBarOption != -1) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + bottomBarActive = false; + curBottomBarOption = hoveredBottomBarOption; + ChangeScreen(bbNames[curBottomBarOption].screenId, 0, true, false); + if (bbNames[curBottomBarOption].screenId == MENUPAGE_SOUND_SETTINGS) + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + return; + } else if (bottomBarActive) { + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown()) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + bottomBarActive = false; + + if (bbNames[curBottomBarOption].screenId == MENUPAGE_SOUND_SETTINGS) + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + + return; + } else if (CPad::GetPad(0)->GetLeftJustDown() || CPad::GetPad(0)->GetAnaloguePadLeft() || CPad::GetPad(0)->GetDPadLeftJustDown() + || CPad::GetPad(0)->GetUpJustDown() || CPad::GetPad(0)->GetAnaloguePadUp() || CPad::GetPad(0)->GetDPadUpJustDown()) { + + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + curBottomBarOption = ((curBottomBarOption + bbTabCount) - 1) % bbTabCount; + ChangeScreen(bbNames[curBottomBarOption].screenId, 0, true, true); + return; + } else if (CPad::GetPad(0)->GetRightJustDown() || CPad::GetPad(0)->GetAnaloguePadRight() || CPad::GetPad(0)->GetDPadRightJustDown() + || CPad::GetPad(0)->GetDownJustDown() || CPad::GetPad(0)->GetAnaloguePadDown() || CPad::GetPad(0)->GetDPadDownJustDown()) { + + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + curBottomBarOption = ((curBottomBarOption + bbTabCount) + 1) % bbTabCount; + ChangeScreen(bbNames[curBottomBarOption].screenId, 0, true, true); + return; + } + optionSelected = false; + goDown = false; + goUp = false; + } +#endif + + int prevOption = m_nCurrOption; + if (goDown && (m_nCurrScreen != MENUPAGE_MULTIPLAYER_FIND_GAME)) { + m_nCurrOption++; + if (m_nCurrOption == NUM_MENUROWS || (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_NOTHING)) { + m_nCurrOption = 0; + } + } + if (goUp && (m_nCurrScreen != MENUPAGE_MULTIPLAYER_FIND_GAME)) { + if (m_nCurrOption == (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL)) { + while (m_nCurrOption != NUM_MENUROWS - 1 + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption + 1].m_Action != MENUACTION_NOTHING) { + m_nCurrOption++; + } + } else { + m_nCurrOption--; + } + } + + // Hide back button +#ifdef PS2_LIKE_MENU + if ((goUp || goDown) && m_nCurrScreen != MENUPAGE_MULTIPLAYER_FIND_GAME && strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEDS_TB") == 0) + m_nCurrOption = goUp ? m_nCurrOption - 1 : (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL); +#endif + + if (optionSelected) { + int option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; + if ((option == MENUACTION_CHANGEMENU) || (option == MENUACTION_POPULATESLOTS_CHANGEMENU)) { + if (strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEDS_TB") != 0 && + strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FESZ_CA") != 0) { + + if (m_nCurrScreen == MENUPAGE_CHOOSE_DELETE_SLOT) { + if (Slots[aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot - 1] == SLOT_EMPTY) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + } else { + // This is duplicate, back button already processed below +#ifndef TIDY_UP_PBP + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_EXIT, 0); + if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { + DMAudio.StopFrontEndTrack(); + OutputDebugString("FRONTEND AUDIO TRACK STOPPED"); + } +#endif + } + } else if (option == MENUACTION_CHECKSAVE) { + if (Slots[aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot - 1] == SLOT_EMPTY) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + } else { + if (m_nCurrScreen != MENUPAGE_NEW_GAME_RELOAD) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + } + } else if (option != MENUACTION_CHANGEMENU && option != MENUACTION_BRIGHTNESS && option != MENUACTION_DRAWDIST + && option != MENUACTION_MUSICVOLUME && option != MENUACTION_SFXVOLUME + && option != MENUACTION_CHECKSAVE && option != MENUACTION_UNK24 + && option != MENUACTION_MOUSESENS && option != MENUACTION_SCREENRES +#ifdef CUSTOM_FRONTEND_OPTIONS + && option != MENUACTION_CFO_SLIDER +#endif + ) + { + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + + if ((m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) || (m_nCurrScreen == MENUPAGE_SKIN_SELECT)) { + switch (m_nCurrExLayer) { + default: + goBack = true; + break; + case HOVEROPTION_LIST: + if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + m_bWaitingForNewKeyBind = true; + m_bStartWaitingForKeyBind = true; + pControlEdit = &m_KeyPressedCode; + } + if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + m_nCurrExLayer = HOVEROPTION_BACK; + SaveSettings(); + } + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + break; + case HOVEROPTION_USESKIN: + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + m_nCurrExLayer = HOVEROPTION_BACK; + SaveSettings(); + break; + } + } else if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu == MENUPAGE_NEW_GAME_RELOAD && m_bGameNotLoaded) { + DoSettingsBeforeStartingAGame(); +/* } else if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + // .. either empty or there was some outer if. :shrug: pointless anyway, keyboard_controls is handled in first if. +*/ + } else if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + if (m_nSkinsTotal > 0) { + m_pSelectedSkin = m_pSkinListHead.nextSkin; + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + SaveSettings(); + } else { +#ifndef TIDY_UP_PBP + ChangeScreen(!m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0], + GetPreviousPageOption(), true, true); +#else + goBack = true; +#endif + } + } else if (m_nCurrScreen != MENUPAGE_MULTIPLAYER_FIND_GAME) { + option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; + switch (option) { + case MENUACTION_RADIO: +#ifdef TIDY_UP_PBP + assumeIncrease = true; +#else + ++m_PrefsRadioStation; + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (m_PrefsRadioStation > USERTRACK) + m_PrefsRadioStation = HEAD_RADIO; + } else if (m_PrefsRadioStation > CHATTERBOX) { + m_PrefsRadioStation = USERTRACK; + } + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); +#endif + break; + case MENUACTION_LANG_ENG: + m_PrefsLanguage = LANGUAGE_AMERICAN; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + break; + case MENUACTION_LANG_FRE: + m_PrefsLanguage = LANGUAGE_FRENCH; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + break; + case MENUACTION_LANG_GER: + m_PrefsLanguage = LANGUAGE_GERMAN; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + break; + case MENUACTION_LANG_ITA: + m_PrefsLanguage = LANGUAGE_ITALIAN; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + break; + case MENUACTION_LANG_SPA: + m_PrefsLanguage = LANGUAGE_SPANISH; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + break; + case MENUACTION_POPULATESLOTS_CHANGEMENU: + PcSaveHelper.PopulateSlotInfo(); + + // fall through + case MENUACTION_CHANGEMENU: + { + bool changeMenu = true; + int saveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot; + + // This should be unused. + if (saveSlot >= 2 && saveSlot <= 9) { + m_nCurrSaveSlot = saveSlot - 2; + switch (m_nCurrScreen) { + case MENUPAGE_CHOOSE_LOAD_SLOT: + if (Slots[m_nCurrSaveSlot + 1] != SLOT_EMPTY) + changeMenu = false; + + break; + case MENUPAGE_CHOOSE_DELETE_SLOT: + if (Slots[m_nCurrSaveSlot + 1] == SLOT_EMPTY) + changeMenu = false; + + break; + } + } + if (changeMenu) { + if (strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEDS_TB") == 0) { +#ifndef TIDY_UP_PBP + ResetHelperText(); + ChangeScreen(!m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0], + GetPreviousPageOption(), true, true); +#else + goBack = true; + break; +#endif + } else { + ChangeScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu, 0, true, true); + } + } + break; + } + case MENUACTION_CHECKSAVE: + { + int saveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot; + + if (saveSlot >= 2 && saveSlot <= 9) { + m_nCurrSaveSlot = saveSlot - 2; + if (Slots[m_nCurrSaveSlot + 1] != SLOT_EMPTY && Slots[m_nCurrSaveSlot + 1] != SLOT_CORRUPTED) { + ChangeScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu, 0, true, true); + } + } + break; + } + case MENUACTION_NEWGAME: + DoSettingsBeforeStartingAGame(); + break; + case MENUACTION_RELOADIDE: + CFileLoader::ReloadObjectTypes("GTA3.IDE"); + break; + case MENUACTION_RELOADIPL: + CGame::ReloadIPLs(); + break; + case MENUACTION_SHOWCULL: + gbShowCullZoneDebugStuff = !gbShowCullZoneDebugStuff; + break; + case MENUACTION_MEMCARDSAVECONFIRM: + return; + case MENUACTION_RESUME_FROM_SAVEZONE: + RequestFrontEndShutDown(); + break; + case MENUACTION_MPMAP_LIBERTY: + case MENUACTION_MPMAP_REDLIGHT: + case MENUACTION_MPMAP_CHINATOWN: + case MENUACTION_MPMAP_TOWER: + case MENUACTION_MPMAP_SEWER: + case MENUACTION_MPMAP_INDUSTPARK: + case MENUACTION_MPMAP_DOCKS: + case MENUACTION_MPMAP_STAUNTON: + m_SelectedMap = option - MENUACTION_MPMAP_LIBERTY; + SaveSettings(); + ChangeScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu, 0, true, true); + break; + case MENUACTION_MPMAP_DEATHMATCH1: + case MENUACTION_MPMAP_DEATHMATCH2: + case MENUACTION_MPMAP_TEAMDEATH1: + case MENUACTION_MPMAP_TEAMDEATH2: + case MENUACTION_MPMAP_STASH: + case MENUACTION_MPMAP_CAPTURE: + case MENUACTION_MPMAP_RATRACE: + case MENUACTION_MPMAP_DOMINATION: + m_SelectedGameType = option - MENUACTION_MPMAP_DEATHMATCH1; + SaveSettings(); + ChangeScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu, 0, true, true); + break; + case MENUACTION_KEYBOARDCTRLS: + ChangeScreen(MENUPAGE_KEYBOARD_CONTROLS, 0, true, true); + m_nSelectedListRow = 0; + m_nCurrExLayer = HOVEROPTION_LIST; + break; + case MENUACTION_GETKEY: + m_CurrCntrlAction = GetStartOptionsCntrlConfigScreens() + m_nCurrOption; + m_bKeyIsOK = true; + m_bWaitingForNewKeyBind = true; + m_bStartWaitingForKeyBind = true; + pControlEdit = &m_KeyPressedCode; + break; + case MENUACTION_CANCELGAME: + DMAudio.Service(); + RsEventHandler(rsQUITAPP, nil); + break; + case MENUACTION_RESUME: +#ifndef TIDY_UP_PBP + if (m_PrefsVsyncDisp != m_PrefsVsync) { + m_PrefsVsync = m_PrefsVsyncDisp; + } + RequestFrontEndShutDown(); +#else + goBack = true; +#endif + break; + case MENUACTION_DONTCANCEL: + ChangeScreen(!m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0], + GetPreviousPageOption(), true, true); + break; + case MENUACTION_SCREENRES: + if (m_nDisplayVideoMode != m_nPrefsVideoMode) { + m_nPrefsVideoMode = m_nDisplayVideoMode; + _psSelectScreenVM(m_nPrefsVideoMode); + SetHelperText(0); + SaveSettings(); + } + break; + case MENUACTION_AUDIOHW: + { + int selectedProvider = m_nPrefsAudio3DProviderIndex; + if (selectedProvider != -1) { + m_nPrefsAudio3DProviderIndex = DMAudio.SetCurrent3DProvider(m_nPrefsAudio3DProviderIndex); + if (selectedProvider == m_nPrefsAudio3DProviderIndex) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SetHelperText(0); + } else { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + SetHelperText(4); + } + SaveSettings(); + } + break; + } + case MENUACTION_SPEAKERCONF: +#ifndef TIDY_UP_PBP + if (m_nPrefsAudio3DProviderIndex != -1) { + if (--m_PrefsSpeakers < 0) + m_PrefsSpeakers = 2; + DMAudio.SetSpeakerConfig(m_PrefsSpeakers); + SaveSettings(); + } +#else + assumeIncrease = true; +#endif + break; + case MENUACTION_PLAYERSETUP: + CPlayerSkin::BeginFrontendSkinEdit(); + ChangeScreen(MENUPAGE_SKIN_SELECT, 0, true, true); + m_nCurrExLayer = HOVEROPTION_LIST; + m_bSkinsEnumerated = false; + break; + case MENUACTION_RESTOREDEF: + if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { + m_PrefsSfxVolume = 102; + m_PrefsSpeakers = 0; + m_PrefsMusicVolume = 102; + m_PrefsStereoMono = 0; + m_PrefsRadioStation = HEAD_RADIO; + DMAudio.SetMusicMasterVolume(102); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + SaveSettings(); + } else if (m_nCurrScreen == MENUPAGE_DISPLAY_SETTINGS) { + m_PrefsFrameLimiter = true; + m_PrefsBrightness = 256; + m_PrefsVsyncDisp = true; + m_PrefsLOD = 1.2f; + m_PrefsVsync = true; + CRenderer::ms_lodDistScale = 1.2f; +#ifdef ASPECT_RATIO_SCALE + m_PrefsUseWideScreen = AR_AUTO; +#else + m_PrefsUseWideScreen = false; +#endif + m_PrefsShowSubtitles = true; + m_nDisplayVideoMode = m_nPrefsVideoMode; +#if GTA_VERSION >= GTA3_PC_11 + if (_dwOperatingSystemVersion == OS_WIN98) { + CMBlur::BlurOn = false; + CMBlur::MotionBlurClose(); + } else { + CMBlur::BlurOn = true; + CMBlur::MotionBlurOpen(Scene.camera); + } +#else + CMBlur::BlurOn = true; +#endif +#ifdef CUSTOM_FRONTEND_OPTIONS + extern void RestoreDefGraphics(int8); + extern void RestoreDefDisplay(int8); + + RestoreDefGraphics(FEOPTION_ACTION_SELECT); + RestoreDefDisplay(FEOPTION_ACTION_SELECT); +#endif + SaveSettings(); + } else if ((m_nCurrScreen != MENUPAGE_SKIN_SELECT_OLD) && (m_nCurrScreen == MENUPAGE_CONTROLLER_PC)) { + ControlsManager.MakeControllerActionsBlank(); + ControlsManager.InitDefaultControlConfiguration(); + ControlsManager.InitDefaultControlConfigMouse(MousePointerStateHelper.GetMouseSetUp()); +#if !defined RW_GL3 + if (AllValidWinJoys.m_aJoys[JOYSTICK1].m_bInitialised) { + DIDEVCAPS devCaps; + devCaps.dwSize = sizeof(DIDEVCAPS); + PSGLOBAL(joy1)->GetCapabilities(&devCaps); + ControlsManager.InitDefaultControlConfigJoyPad(devCaps.dwButtons); + } +#else + if (PSGLOBAL(joy1id) != -1 && glfwJoystickPresent(PSGLOBAL(joy1id))) { + int count; + glfwGetJoystickButtons(PSGLOBAL(joy1id), &count); + ControlsManager.InitDefaultControlConfigJoyPad(count); + } +#endif + m_ControlMethod = CONTROL_STANDARD; +#ifdef FIX_BUGS + MousePointerStateHelper.bInvertVertically = true; + TheCamera.m_fMouseAccelVertical = 0.003f; +#else + MousePointerStateHelper.bInvertVertically = false; +#endif + TheCamera.m_fMouseAccelHorzntl = 0.0025f; + CVehicle::m_bDisableMouseSteering = true; + TheCamera.m_bHeadBob = false; + SaveSettings(); +#ifdef LOAD_INI_SETTINGS + SaveINIControllerSettings(); +#endif + } + SetHelperText(2); + break; + case MENUACTION_CTRLMETHOD: +#ifndef TIDY_UP_PBP + if (m_ControlMethod == CONTROL_CLASSIC) { + CCamera::m_bUseMouse3rdPerson = true; + m_ControlMethod = CONTROL_STANDARD; + } else { + CCamera::m_bUseMouse3rdPerson = false; + m_ControlMethod = CONTROL_CLASSIC; + } + SaveSettings(); +#else + assumeIncrease = true; +#endif + break; + case MENUACTION_LOADRADIO: + ChangeScreen(MENUPAGE_SOUND_SETTINGS, 0, true, true); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("STARTED PLAYING FRONTEND AUDIO TRACK"); + break; +#ifdef MISSION_REPLAY + case MENUACTION_REJECT_RETRY: + doingMissionRetry = false; + AllowMissionReplay = 0; + RequestFrontEndShutDown(); + break; + case MENUACTION_UNK114: + doingMissionRetry = false; + RequestFrontEndShutDown(); + RetryMission(MISSION_RETRY_TYPE_BEGIN_RESTARTING); + return; +#endif +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_CFO_SELECT: + case MENUACTION_CFO_DYNAMIC: + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption]; + if (option.m_Action == MENUACTION_CFO_SELECT) { + if (option.m_CFOSelect->disableIfGameLoaded && !m_bGameNotLoaded) + break; + + if (!option.m_CFOSelect->onlyApplyOnEnter) { + option.m_CFOSelect->displayedValue++; + if (option.m_CFOSelect->displayedValue >= option.m_CFOSelect->numRightTexts || option.m_CFOSelect->displayedValue < 0) + option.m_CFOSelect->displayedValue = 0; + } + int8 oldValue = *(int8*)option.m_CFO->value; + + *(int8*)option.m_CFO->value = option.m_CFOSelect->lastSavedValue = option.m_CFOSelect->displayedValue; + + // Now everything is saved in .ini, and LOAD_INI_SETTINGS is fundamental for CFO + // if (option.m_CFOSelect->save) + SaveSettings(); + + if (option.m_CFOSelect->displayedValue != oldValue && option.m_CFOSelect->changeFunc) + option.m_CFOSelect->changeFunc(oldValue, option.m_CFOSelect->displayedValue); + + } else if (option.m_Action == MENUACTION_CFO_DYNAMIC) { + if (option.m_CFODynamic->buttonPressFunc) + option.m_CFODynamic->buttonPressFunc(FEOPTION_ACTION_SELECT); + } + + break; +#endif + } + } + ProcessOnOffMenuOptions(); + } + + if (goBack) { + ResetHelperText(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_BACK, 0); +#ifdef PS2_LIKE_MENU + if (m_nCurrScreen == MENUPAGE_PAUSE_MENU || bottomBarActive) { +#else + if (m_nCurrScreen == MENUPAGE_PAUSE_MENU) { +#endif + if (!m_bGameNotLoaded && !m_bMenuStateChanged) { + if (m_PrefsVsyncDisp != m_PrefsVsync) { + m_PrefsVsync = m_PrefsVsyncDisp; + } + RequestFrontEndShutDown(); + } + + // We're already resuming, we don't need further processing. +#if defined(FIX_BUGS) || defined(PS2_LIKE_MENU) + return; +#endif + } +#ifdef PS2_SAVE_DIALOG + else if (m_nCurrScreen == MENUPAGE_CHOOSE_SAVE_SLOT || m_nCurrScreen == MENUPAGE_SAVE) { +#else + else if (m_nCurrScreen == MENUPAGE_CHOOSE_SAVE_SLOT) { +#endif + RequestFrontEndShutDown(); + } + // It's now in ThingsToDoBeforeGoingBack() +#ifndef TIDY_UP_PBP + else if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { + DMAudio.StopFrontEndTrack(); + OutputDebugString("FRONTEND AUDIO TRACK STOPPED"); + } +#endif + + int oldScreen = !m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0]; + int oldOption = GetPreviousPageOption(); + + if (oldScreen != -1) { + ThingsToDoBeforeGoingBack(); + +#ifdef PS2_LIKE_MENU + if (!bottomBarActive && + (oldScreen == MENUPAGE_NONE || oldScreen == MENUPAGE_OPTIONS)) { + bottomBarActive = true; + } else +#endif + { + ChangeScreen(oldScreen, oldOption, true, true); + } + + // We will go back for sure at this point, why process other things?! +#ifdef FIX_BUGS + return; +#endif + } + } + +#ifdef PS2_LIKE_MENU + if (bottomBarActive) + return; +#endif + + int changeValueBy = 0; + bool decrease = false; +#ifdef TIDY_UP_PBP + bool increase = assumeIncrease; +#else + bool increase = false; +#endif + if (CPad::GetPad(0)->GetLeft() || CPad::GetPad(0)->GetPedWalkLeftRight() < 0 || CPad::GetPad(0)->GetDPadLeft()) { + static uint32 lastSliderDecrease = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastSliderDecrease > 150) { + CheckSliderMovement(-1); + lastSliderDecrease = CTimer::GetTimeInMillisecondsPauseMode(); + } + } else if (CPad::GetPad(0)->GetRight() || CPad::GetPad(0)->GetPedWalkLeftRight() > 0 || CPad::GetPad(0)->GetDPadRight()) { + static uint32 lastSliderIncrease = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastSliderIncrease > 150) { + CheckSliderMovement(1); + lastSliderIncrease = CTimer::GetTimeInMillisecondsPauseMode(); + } + } + + if (CPad::GetPad(0)->GetRightJustDown() || CPad::GetPad(0)->GetAnaloguePadRight() || CPad::GetPad(0)->GetDPadRightJustDown()) { + m_bShowMouse = false; + increase = true; + } else if ( +#ifdef SCROLLABLE_PAGES + !SCREEN_HAS_AUTO_SCROLLBAR && +#endif + CPad::GetPad(0)->GetMouseWheelUpJustDown() && m_nCurrScreen != MENUPAGE_KEYBOARD_CONTROLS) { + increase = true; + CheckSliderMovement(1); + m_bShowMouse = true; + } + + if (CPad::GetPad(0)->GetLeftJustDown() || CPad::GetPad(0)->GetAnaloguePadLeft() || CPad::GetPad(0)->GetDPadLeftJustDown()) { + m_bShowMouse = false; + decrease = true; + } else if ( +#ifdef SCROLLABLE_PAGES + !SCREEN_HAS_AUTO_SCROLLBAR && +#endif + CPad::GetPad(0)->GetMouseWheelDownJustDown() && m_nCurrScreen != MENUPAGE_KEYBOARD_CONTROLS) { + decrease = true; + CheckSliderMovement(-1); + m_bShowMouse = true; + } + + if (increase) + changeValueBy++; + else if (decrease) + changeValueBy--; + + if (changeValueBy != 0) { + switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) { +#ifdef FIX_BUGS + case MENUACTION_CTRLCONFIG: + CPad::GetPad(0)->Mode += changeValueBy; + if (CPad::GetPad(0)->Mode > 3) + CPad::GetPad(0)->Mode = 0; + else if (CPad::GetPad(0)->Mode < 0) + CPad::GetPad(0)->Mode = 3; + SaveSettings(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; +#endif + case MENUACTION_RADIO: + m_PrefsRadioStation += changeValueBy; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (m_PrefsRadioStation < HEAD_RADIO) + m_PrefsRadioStation = USERTRACK; + if (m_PrefsRadioStation > USERTRACK) + m_PrefsRadioStation = HEAD_RADIO; + } else { + if (m_PrefsRadioStation < HEAD_RADIO) + m_PrefsRadioStation = CHATTERBOX; + if (m_PrefsRadioStation > CHATTERBOX) + m_PrefsRadioStation = HEAD_RADIO; + } + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; +#ifdef ASPECT_RATIO_SCALE + case MENUACTION_WIDESCREEN: + if (changeValueBy > 0) { + m_PrefsUseWideScreen++; + if (m_PrefsUseWideScreen > AR_MAX-1) + m_PrefsUseWideScreen = 0; + } else { + m_PrefsUseWideScreen--; + if (m_PrefsUseWideScreen < 0) + m_PrefsUseWideScreen = AR_MAX-1; + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; +#endif + case MENUACTION_SCREENRES: + if (m_bGameNotLoaded) { + RwChar** videoMods = _psGetVideoModeList(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + if (changeValueBy > 0) { + do { + ++m_nDisplayVideoMode; + + if (m_nDisplayVideoMode >= _psGetNumVideModes()) + m_nDisplayVideoMode = 0; + } while (!videoMods[m_nDisplayVideoMode]); + } else { + do { + --m_nDisplayVideoMode; + + if (m_nDisplayVideoMode < 0) + m_nDisplayVideoMode = _psGetNumVideModes() - 1; + } while (!videoMods[m_nDisplayVideoMode]); + } + } + break; + case MENUACTION_AUDIOHW: + if (m_nPrefsAudio3DProviderIndex != -1) { + m_nPrefsAudio3DProviderIndex += changeValueBy; + m_nPrefsAudio3DProviderIndex = Clamp(m_nPrefsAudio3DProviderIndex, 0, DMAudio.GetNum3DProvidersAvailable() - 1); + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + break; + case MENUACTION_SPEAKERCONF: + if (m_nPrefsAudio3DProviderIndex != -1) { + m_PrefsSpeakers -= changeValueBy; + m_PrefsSpeakers = Clamp(m_PrefsSpeakers, 0, 2); + DMAudio.SetSpeakerConfig(m_PrefsSpeakers); + SaveSettings(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + break; + case MENUACTION_CTRLMETHOD: + m_ControlMethod = !m_ControlMethod; + CCamera::m_bUseMouse3rdPerson = !m_ControlMethod; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_CFO_SELECT: + case MENUACTION_CFO_DYNAMIC: + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption]; + if (option.m_Action == MENUACTION_CFO_SELECT) { + if (option.m_CFOSelect->disableIfGameLoaded && !m_bGameNotLoaded) + break; + + if (changeValueBy > 0) { + option.m_CFOSelect->displayedValue++; + if (option.m_CFOSelect->displayedValue >= option.m_CFOSelect->numRightTexts) + option.m_CFOSelect->displayedValue = 0; + } else { + option.m_CFOSelect->displayedValue--; + if (option.m_CFOSelect->displayedValue < 0) + option.m_CFOSelect->displayedValue = option.m_CFOSelect->numRightTexts - 1; + } + if (!option.m_CFOSelect->onlyApplyOnEnter) { + int8 oldValue = *(int8*)option.m_CFO->value; + + *(int8*)option.m_CFO->value = option.m_CFOSelect->lastSavedValue = option.m_CFOSelect->displayedValue; + + // Now everything is saved in .ini, and LOAD_INI_SETTINGS is fundamental for CFO + // if (option.m_CFOSelect->save) + SaveSettings(); + + if (option.m_CFOSelect->displayedValue != oldValue && option.m_CFOSelect->changeFunc) + option.m_CFOSelect->changeFunc(oldValue, option.m_CFOSelect->displayedValue); + } + } else if (option.m_Action == MENUACTION_CFO_DYNAMIC && option.m_CFODynamic->buttonPressFunc) { + option.m_CFODynamic->buttonPressFunc(changeValueBy > 0 ? FEOPTION_ACTION_RIGHT : FEOPTION_ACTION_LEFT); + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + + break; +#endif + } + ProcessOnOffMenuOptions(); + if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + if (changeValueBy < 1) { + m_nSelectedContSetupColumn = CONTSETUP_PED_COLUMN; + } else { + m_nSelectedContSetupColumn = CONTSETUP_VEHICLE_COLUMN; + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } +} + +void +CMenuManager::ProcessOnOffMenuOptions() +{ + switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) { + case MENUACTION_CTRLVIBRATION: + m_PrefsUseVibration = !m_PrefsUseVibration; + + if (m_PrefsUseVibration) { + CPad::GetPad(0)->StartShake(350, 150); + TimeToStopPadShaking = CTimer::GetTimeInMillisecondsPauseMode() + 500; + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); +#ifdef FIX_BUGS + SaveSettings(); +#endif // !FIX_BUGS + break; +#ifndef FIX_BUGS + case MENUACTION_CTRLCONFIG: + CPad::GetPad(0)->Mode++; + if (CPad::GetPad(0)->Mode > 3) + CPad::GetPad(0)->Mode = 0; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; +#endif // !FIX_BUGS + case MENUACTION_CTRLDISPLAY: + m_DisplayControllerOnFoot = !m_DisplayControllerOnFoot; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_FRAMESYNC: + m_PrefsVsyncDisp = !m_PrefsVsyncDisp; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_FRAMELIMIT: + m_PrefsFrameLimiter = !m_PrefsFrameLimiter; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_TRAILS: + CMBlur::BlurOn = !CMBlur::BlurOn; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + if (CMBlur::BlurOn) + CMBlur::MotionBlurOpen(Scene.camera); + else + CMBlur::MotionBlurClose(); + break; + case MENUACTION_SUBTITLES: + m_PrefsShowSubtitles = !m_PrefsShowSubtitles; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; +#ifndef ASPECT_RATIO_SCALE + case MENUACTION_WIDESCREEN: + m_PrefsUseWideScreen = !m_PrefsUseWideScreen; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; +#endif + case MENUACTION_SETDBGFLAG: + CTheScripts::InvertDebugFlag(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_SWITCHBIGWHITEDEBUGLIGHT: + gbBigWhiteDebugLightSwitchedOn = !gbBigWhiteDebugLightSwitchedOn; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_PEDROADGROUPS: + gbShowPedRoadGroups = !gbShowPedRoadGroups; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_CARROADGROUPS: + gbShowCarRoadGroups = !gbShowCarRoadGroups; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_COLLISIONPOLYS: + gbShowCollisionPolys = !gbShowCollisionPolys; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_MP_PLAYERCOLOR: + PickNewPlayerColour(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_SHOWHEADBOB: + TheCamera.m_bHeadBob = !TheCamera.m_bHeadBob; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_INVVERT: + MousePointerStateHelper.bInvertVertically = !MousePointerStateHelper.bInvertVertically; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_DYNAMICACOUSTIC: + m_PrefsDMA = !m_PrefsDMA; + DMAudio.SetDynamicAcousticModelingStatus(m_PrefsDMA); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_MOUSESTEER: + CVehicle::m_bDisableMouseSteering = !CVehicle::m_bDisableMouseSteering; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + } +} + +void +CMenuManager::RequestFrontEndShutDown() +{ + m_bShutDownFrontEndRequested = true; + DMAudio.ChangeMusicMode(MUSICMODE_GAME); +} + +void +CMenuManager::RequestFrontEndStartUp() +{ + m_bStartUpFrontEndRequested = true; +} + +void +CMenuManager::ResetHelperText() +{ + m_nHelperTextMsgId = 0; + m_nHelperTextAlpha = 300; +} + +void +CMenuManager::SaveLoadFileError_SetUpErrorScreen() +{ + switch (PcSaveHelper.nErrorCode) { + case SAVESTATUS_ERR_SAVE_CREATE: + case SAVESTATUS_ERR_SAVE_WRITE: + case SAVESTATUS_ERR_SAVE_CLOSE: + ChangeScreen(MENUPAGE_SAVE_FAILED, 0, true, false); + break; + case SAVESTATUS_ERR_LOAD_OPEN: + case SAVESTATUS_ERR_LOAD_READ: + case SAVESTATUS_ERR_LOAD_CLOSE: + ChangeScreen(MENUPAGE_LOAD_FAILED, 0, true, false); + break; + case SAVESTATUS_ERR_DATA_INVALID: + ChangeScreen(MENUPAGE_LOAD_FAILED_2, 0, true, false); + break; + case SAVESTATUS_DELETEFAILED8: + case SAVESTATUS_DELETEFAILED9: + case SAVESTATUS_DELETEFAILED10: + ChangeScreen(MENUPAGE_DELETE_FAILED, 0, true, false); + break; + default: break; + } +} + +void +CMenuManager::SetHelperText(int text) +{ + m_nHelperTextMsgId = text; + m_nHelperTextAlpha = 300; +} + +void +CMenuManager::ShutdownJustMenu() +{ + // In case we're windowed, keep mouse centered while in game. Done in main.cpp in other conditions. +#if defined(RW_GL3) && defined(IMPROVED_VIDEOMODE) + glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, GLFW_CURSOR_DISABLED); +#endif + m_bMenuActive = false; + CTimer::EndUserPause(); +} + +float +CMenuManager::StretchX(float x) +{ + if (SCREEN_WIDTH == DEFAULT_SCREEN_WIDTH) + return x; + else + // We won't make this SCREEN_SCALE, because many cases relies on stretching and we want the code to be portable. + // Instead we will use MENU_X_LEFT_ALIGNED or SCREEN_SCALE_X when needed. + return SCREEN_STRETCH_X(x); +} + +float CMenuManager::StretchY(float y) +{ + if (SCREEN_HEIGHT == DEFAULT_SCREEN_HEIGHT) + return y; + else + return SCREEN_STRETCH_Y(y); +} + +void +CMenuManager::SwitchMenuOnAndOff() +{ + bool menuWasActive = GetIsMenuActive(); + + // Reminder: You need REGISTER_START_BUTTON defined to make it work. + if (CPad::GetPad(0)->GetStartJustDown() +#ifdef FIX_BUGS + && !m_bGameNotLoaded +#endif + || m_bShutDownFrontEndRequested || m_bStartUpFrontEndRequested) { + + m_bMenuActive = !m_bMenuActive; +#ifdef FIX_BUGS + CPad::StopPadsShaking(); +#endif + + if (m_bShutDownFrontEndRequested) + m_bMenuActive = false; + if (m_bStartUpFrontEndRequested) + m_bMenuActive = true; + + if (m_bMenuActive) { + CTimer::StartUserPause(); + } else { +#ifdef PS2_LIKE_MENU + bottomBarActive = false; +#endif +#ifdef FIX_BUGS + ThingsToDoBeforeGoingBack(); +#endif + ShutdownJustMenu(); + SaveSettings(); +#ifdef LOAD_INI_SETTINGS + SaveINIControllerSettings(); +#endif + m_bStartUpFrontEndRequested = false; + pControlEdit = nil; + m_bShutDownFrontEndRequested = false; + DisplayComboButtonErrMsg = false; + +#ifdef REGISTER_START_BUTTON + int16 start1 = CPad::GetPad(0)->PCTempJoyState.Start, start2 = CPad::GetPad(0)->PCTempKeyState.Start, + start3 = CPad::GetPad(0)->OldState.Start, start4 = CPad::GetPad(0)->NewState.Start; +#endif + CPad::GetPad(0)->Clear(false); + CPad::GetPad(1)->Clear(false); +#ifdef REGISTER_START_BUTTON + CPad::GetPad(0)->PCTempJoyState.Start = start1; + CPad::GetPad(0)->PCTempKeyState.Start = start2; + CPad::GetPad(0)->OldState.Start = start3; + CPad::GetPad(0)->NewState.Start = start4; +#endif + m_nCurrScreen = MENUPAGE_NONE; + } + } + + // Just entered the save/safe zone + if (m_bSaveMenuActive && !m_bQuitGameNoCD) { + m_bSaveMenuActive = false; + m_bMenuActive = true; + CTimer::StartUserPause(); +#ifdef PS2_SAVE_DIALOG + m_nCurrScreen = MENUPAGE_SAVE; + m_bRenderGameInMenu = true; +#else + m_nCurrScreen = MENUPAGE_CHOOSE_SAVE_SLOT; +#endif + PcSaveHelper.PopulateSlotInfo(); + m_nCurrOption = 0; + } +/* // PS2 leftover + if (m_nCurrScreen != MENUPAGE_SOUND_SETTINGS && gMusicPlaying) + { + DMAudio.StopFrontEndTrack(); + OutputDebugString("FRONTEND AUDIO TRACK STOPPED"); + gMusicPlaying = 0; + } +*/ + if (m_bMenuActive != menuWasActive) { + m_bMenuStateChanged = true; + + // In case we're windowed, keep mouse centered while in game. Done in main.cpp in other conditions. +#if defined(RW_GL3) && defined(IMPROVED_VIDEOMODE) + glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, m_bMenuActive && m_nPrefsWindowed ? GLFW_CURSOR_HIDDEN : GLFW_CURSOR_DISABLED); +#endif + } + + m_bStartUpFrontEndRequested = false; + m_bShutDownFrontEndRequested = false; +} + +void +CMenuManager::UnloadTextures() +{ + if (!m_bSpritesLoaded) + return; + + printf("REMOVE frontend\n"); + for (int i = 0; i < ARRAY_SIZE(FrontendFilenames); ++i) + m_aFrontEndSprites[i].Delete(); + + int frontend = CTxdStore::FindTxdSlot("frontend"); + CTxdStore::RemoveTxd(frontend); + +#ifdef GAMEPAD_MENU + int frontend_controllerTxdSlot = CTxdStore::FindTxdSlot("frontend_controller"); + if (frontend_controllerTxdSlot != -1) + CTxdStore::RemoveTxd(frontend_controllerTxdSlot); +#endif + + printf("REMOVE menu textures\n"); + for (int i = 0; i < ARRAY_SIZE(MenuFilenames); ++i) + m_aMenuSprites[i].Delete(); +#ifdef MENU_MAP + for (int i = 0; i < ARRAY_SIZE(MapFilenames); ++i) + m_aMapSprites[i].Delete(); +#endif + int menu = CTxdStore::FindTxdSlot("menu"); + CTxdStore::RemoveTxd(menu); + + m_bSpritesLoaded = false; +} + +void +CMenuManager::WaitForUserCD() +{ + CSprite2d *splash; + char *splashscreen = nil; + +#if (!(defined RANDOMSPLASH) && GTA_VERSION < GTA3_PC_11) + if (CGame::frenchGame || CGame::germanGame || !CGame::nastyGame) + splashscreen = "mainsc2"; + else + splashscreen = "mainsc1"; +#endif + + splash = LoadSplash(splashscreen); + + if (RsGlobal.quit) + return; + + HandleExit(); + CPad::UpdatePads(); + MessageScreen("NO_PCCD"); + + if (CPad::GetPad(0)->GetEscapeJustDown()) { + m_bQuitGameNoCD = true; + RsEventHandler(rsQUITAPP, nil); + } +} + +#ifdef GAMEPAD_MENU +void +CMenuManager::PrintController(void) +{ + const float scale = 0.9f; + const float CONTROLLER_SIZE_X = 235.2f; + const float CONTROLLER_SIZE_Y = 175.2f; + const float CONTROLLER_POS_X = (DEFAULT_SCREEN_WIDTH - CONTROLLER_SIZE_X) / 2.0f; + const float CONTROLLER_POS_Y = 160.0f; + + float centerX = CONTROLLER_POS_X + CONTROLLER_SIZE_X / 2; + float centerY = CONTROLLER_POS_Y + CONTROLLER_SIZE_Y / 2; + +#define X(f) ((f)*scale + centerX) +#define Y(f) ((f)*scale + centerY) + + m_aFrontEndSprites[FE_CONTROLLERSH].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X((CONTROLLER_SIZE_X + 4.8f) * scale), MENU_Y((CONTROLLER_SIZE_Y + 4.8f) * scale), CRGBA(0, 0, 0, 255)); + m_aFrontEndSprites[FE_CONTROLLER].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X(CONTROLLER_SIZE_X * scale), MENU_Y(CONTROLLER_SIZE_Y * scale), CRGBA(255, 255, 255, 255)); + if (m_DisplayControllerOnFoot) { + if (CTimer::GetTimeInMillisecondsPauseMode() & 0x400) + m_aFrontEndSprites[FE_ARROWS1].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X(CONTROLLER_SIZE_X * scale), MENU_Y(CONTROLLER_SIZE_Y * scale), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[FE_ARROWS3].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X(CONTROLLER_SIZE_X * scale), MENU_Y(CONTROLLER_SIZE_Y * scale), CRGBA(255, 255, 255, 255)); + } else { + if (CTimer::GetTimeInMillisecondsPauseMode() & 0x400) + m_aFrontEndSprites[FE_ARROWS2].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X(CONTROLLER_SIZE_X * scale), MENU_Y(CONTROLLER_SIZE_Y * scale), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[FE_ARROWS4].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X(CONTROLLER_SIZE_X * scale), MENU_Y(CONTROLLER_SIZE_Y * scale), CRGBA(255, 255, 255, 255)); + } + + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * scale), MENU_Y(SMALLESTTEXT_Y_SCALE * scale)); // X + + // CFont::SetColor(CRGBA(128, 128, 128, FadeIn(255))); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetDropShadowPosition(1); + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + CFont::SetWrapx(SCREEN_WIDTH); + + float TEXT_L2_X = 50.0f + CONTROLLER_POS_X - centerX, TEXT_L2_Y = -14.0f + CONTROLLER_POS_Y - centerY; + float TEXT_L1_X = -4.0f + CONTROLLER_POS_X - centerX, TEXT_L1_Y = 25.0f + CONTROLLER_POS_Y - centerY, TEXT_L1_Y_VEH = 3.0f + TEXT_L1_Y; + float TEXT_DPAD_X = -4.0f + CONTROLLER_POS_X - centerX, TEXT_DPAD_Y = 65.0f + CONTROLLER_POS_Y - centerY; + float TEXT_LSTICK_X = -4.0f + CONTROLLER_POS_X - centerX, TEXT_LSTICK_Y = 97.0f + CONTROLLER_POS_Y - centerY; + float TEXT_SELECT_X = 103.0f + CONTROLLER_POS_X - centerX, TEXT_SELECT_Y = 141.0f + CONTROLLER_POS_Y - centerY; + float TEXT_START_X = 130.0f + CONTROLLER_POS_X - centerX, TEXT_START_Y = 128.0f + CONTROLLER_POS_Y - centerY; + float TEXT_R2_X = 184.0F + CONTROLLER_POS_X - centerX, TEXT_R2_Y = -14.0f + CONTROLLER_POS_Y - centerY; + float TEXT_R1_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_R1_Y = 25.0f + CONTROLLER_POS_Y - centerY; + + float TEXT_SQUARE_X = 144.0f + CONTROLLER_POS_X - centerX, TEXT_SQUARE_Y = 18.0f + CONTROLLER_POS_Y - centerY; + float TEXT_TRIANGLE_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_TRIANGLE_Y = 52.0f + CONTROLLER_POS_Y - centerY; + float TEXT_CIRCLE_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_CIRCLE_Y = 65.0f + CONTROLLER_POS_Y - centerY; + float TEXT_CROSS_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_CROSS_Y = 78.0f + CONTROLLER_POS_Y - centerY; + float TEXT_RSTICK_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_RSTICK_Y = 94.0f + CONTROLLER_POS_Y - centerY; + float TEXT_R3_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_R3_Y = 109.0f + CONTROLLER_POS_Y - centerY; + float TEXT_L3_X = 84.0f + CONTROLLER_POS_X - centerX, TEXT_L3_Y = 162.0f + CONTROLLER_POS_Y - centerY; + float TEXT_L2R2_X = 74.0f + CONTROLLER_POS_X - centerX, TEXT_L2R2_Y = -6.0f + CONTROLLER_POS_Y - centerY; + + switch (m_PrefsControllerType) + { + case CONTROLLER_DUALSHOCK4: + TEXT_L1_Y += 7.0f; + TEXT_L1_Y_VEH = TEXT_L1_Y; + TEXT_R1_Y += 7.0f; + TEXT_TRIANGLE_Y -= 1.0f; + TEXT_CIRCLE_Y -= 1.0f; + TEXT_CROSS_Y -= 1.0f; + TEXT_RSTICK_Y -= 4.0f; + TEXT_R3_Y -= 4.0f; + TEXT_DPAD_Y -= 1.0f; + TEXT_LSTICK_Y -= 6.0f; + TEXT_L3_X -= 2.0f; + break; + case CONTROLLER_XBOXONE: + TEXT_L2_X -= 2.0f; + TEXT_R2_X += 2.0f; + TEXT_L1_Y += 15.0f; + TEXT_L1_Y_VEH = TEXT_L1_Y; + TEXT_R1_Y += 15.0f; + TEXT_TRIANGLE_Y += 4.0f; + TEXT_CIRCLE_Y += 4.0f; + TEXT_CROSS_Y += 4.0f; + TEXT_RSTICK_Y += 1.0f; + TEXT_R3_Y += 1.0f; + TEXT_DPAD_Y += 29.0f; + TEXT_LSTICK_Y -= 22.0f; + TEXT_L3_X -= 36.0f; + TEXT_L2R2_Y += 5.0f; + TEXT_SELECT_X += 4.0f; + break; + case CONTROLLER_XBOX360: + TEXT_L2_X += 8.0f; + TEXT_R2_X -= 8.0f; + TEXT_L1_Y += 15.0f; + TEXT_L1_Y_VEH = TEXT_L1_Y; + TEXT_R1_Y += 15.0f; + TEXT_TRIANGLE_Y += 4.0f; + TEXT_CIRCLE_Y += 4.0f; + TEXT_CROSS_Y += 4.0f; + TEXT_RSTICK_Y += 4.0f; + TEXT_R3_Y += 4.0f; + TEXT_DPAD_Y += 30.0f; + TEXT_LSTICK_Y -= 21.0f; + TEXT_L3_X -= 36.0f; + TEXT_L2R2_Y += 5.0f; + TEXT_SELECT_X += 3.0f; + break; + case CONTROLLER_NINTENDO_SWITCH: + TEXT_L1_Y += 5.0f; + TEXT_L1_Y_VEH = TEXT_L1_Y; + TEXT_R1_Y += 5.0f; + TEXT_TRIANGLE_Y += 3.0f; + TEXT_CIRCLE_Y += 3.0f; + TEXT_CROSS_Y += 3.0f; + TEXT_LSTICK_Y -= 23.0f; + TEXT_DPAD_Y += 25.0; + TEXT_RSTICK_Y += 1.0f; + TEXT_R3_Y += 1.0f; + break; + }; + + if (m_DisplayControllerOnFoot) { + switch (CPad::GetPad(0)->Mode) { + case 0: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y)), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_MOV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y + 13.0f)), TheText.Get("FEC_R3")); + break; + case 1: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y)), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y + 13.0f)), TheText.Get("FEC_R3")); + break; + case 2: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y)), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_MOV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y + 13.0f)), TheText.Get("FEC_R3")); + break; + case 3: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y)), TheText.Get("FEC_TAR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_ATT")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y + 13.0f)), TheText.Get("FEC_R3")); + break; + default: + return; + } + } else { + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2R2_X)), MENU_Y(Y(TEXT_L2R2_Y)), TheText.Get("FEC_LB")); + switch (CPad::GetPad(0)->Mode) { + case 0: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y_VEH)), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_VES")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_HO3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_LR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_HAB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_BRA")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_TUC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_SM3")); + break; + case 1: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y_VEH)), TheText.Get("FEC_HOR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_LR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_HAB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_BRA")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_TUC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_SM3")); + break; + case 2: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y_VEH)), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_VES")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_RS3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_LR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_HOR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_BRA")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_TUC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_SM3")); + break; + case 3: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y_VEH)), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_TUC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_HO3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_LR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_CAW")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_SMT")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_BRA")); + break; + default: + return; + } + } + + CFont::SetDropShadowPosition(0); // X + +#undef X +#undef Y +} +#else +void +CMenuManager::PrintController(void) +{ + // FIX: Originally this function doesn't have StretchX/Y, everything had constant pixel size (due to screen was abandoned early?) + // Also texts and their alignment were very bad, so I tried to make them readable (commented out the original code, and marked the ones I added with X) + + m_aFrontEndSprites[FE_CONTROLLERSH].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(240.0f), MENU_Y(180.0f), CRGBA(0, 0, 0, 255)); + m_aFrontEndSprites[FE_CONTROLLER].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(235.2f), MENU_Y(175.2f), CRGBA(255, 255, 255, 255)); + if (m_DisplayControllerOnFoot) { + if (CTimer::GetTimeInMillisecondsPauseMode() & 0x400) + m_aFrontEndSprites[FE_ARROWS1].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(235.2f), MENU_Y(175.2f), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[FE_ARROWS3].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(235.2f), MENU_Y(175.2f), CRGBA(255, 255, 255, 255)); + } else { + if (CTimer::GetTimeInMillisecondsPauseMode() & 0x400) + m_aFrontEndSprites[FE_ARROWS2].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(235.2f), MENU_Y(175.2f), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[FE_ARROWS4].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(235.2f), MENU_Y(175.2f), CRGBA(255, 255, 255, 255)); + } + + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); // X + + // CFont::SetScale(0.4f, 0.4f); + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE), MENU_Y(SMALLESTTEXT_Y_SCALE)); // X + + // CFont::SetColor(CRGBA(128, 128, 128, FadeIn(255))); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); // X + CFont::SetDropShadowPosition(1); // X + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); // X + + if (m_DisplayControllerOnFoot) { + switch (CPad::GetPad(0)->Mode) { + case 0: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_MOV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_R3")); + break; + case 1: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_R3")); + break; + case 2: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_MOV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_R3")); + break; + case 3: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_TAR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_R3")); + break; + default: + return; + } + } else { + switch (CPad::GetPad(0)->Mode) { + case 0: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_VES")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_HO3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_LB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_LR")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_BRA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_TUC")); + // FIX: Coordinates of this line is undefined in PC... + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(304.0f), TheText.Get("FEC_SM3")); + break; + case 1: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_HOR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_LB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_LR")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_BRA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_TUC")); + // FIX: Coordinates of this line is undefined in PC... + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(304.0f), TheText.Get("FEC_SM3")); + break; + case 2: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_VES")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_RS3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_LB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_LR")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_HOR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_BRA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_TUC")); + // FIX: Coordinates of this line is undefined in PC... + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(304.0f), TheText.Get("FEC_SM3")); + break; + case 3: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_TUC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_HO3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_LB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_LR")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_SMT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_ACC")); + // FIX: Coordinates of this line is undefined in PC... + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(304.0f), TheText.Get("FEC_BRA")); + break; + default: + return; + } + } + + CFont::SetDropShadowPosition(0); // X +} +#endif + +#ifdef MENU_MAP + +#define ZOOM(x, y, in) \ + do { \ + if(fMapSize > SCREEN_HEIGHT * 3.0f && in) \ + break; \ + float z2 = in? 1.1f : 1.f/1.1f; \ + fMapCenterX += (x - fMapCenterX) * (1.0f - z2); \ + fMapCenterY += (y - fMapCenterY) * (1.0f - z2); \ + \ + if (fMapSize < SCREEN_HEIGHT / 2 && !in) \ + break; \ + \ + fMapSize *= z2; \ + } while(0) \ + +void +CMenuManager::PrintMap(void) +{ + CFont::SetJustifyOn(); + bMenuMapActive = true; + CRadar::InitFrontEndMap(); + + if (m_nMenuFadeAlpha < 255 && fMapCenterX == 0.f && fMapCenterY == 0.f) { + // Just entered. We need to do these transformations in here, because Radar knows whether map is active or not + CVector2D radarSpacePlayer; + CVector2D screenSpacePlayer; + CRadar::TransformRealWorldPointToRadarSpace(radarSpacePlayer, CVector2D(FindPlayerCoors())); + CRadar::TransformRadarPointToScreenSpace(screenSpacePlayer, radarSpacePlayer); + fMapCenterX = (-screenSpacePlayer.x) + SCREEN_WIDTH / 2; + fMapCenterY = (-screenSpacePlayer.y) + SCREEN_HEIGHT / 2; + } + + // Because fMapSize is half of the map length, and map consists of 3x3 tiles. + float halfTile = fMapSize / 3.0f; + + // Darken background a bit + CSprite2d::DrawRect(CRect(0, 0, + SCREEN_WIDTH, SCREEN_HEIGHT), + CRGBA(0, 0, 0, FadeIn(128))); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + + if (SCREEN_WIDTH >= fMapCenterX - fMapSize || SCREEN_HEIGHT >= fMapCenterY - fMapSize) { + m_aMapSprites[MAPTOP1].Draw(CRect(fMapCenterX - fMapSize, fMapCenterY - fMapSize, + fMapCenterX - halfTile, fMapCenterY - halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX - halfTile || SCREEN_HEIGHT >= fMapCenterY - fMapSize) { + m_aMapSprites[MAPTOP2].Draw(CRect(fMapCenterX - halfTile, fMapCenterY - fMapSize, + fMapCenterX + halfTile, fMapCenterY - halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX + halfTile || SCREEN_HEIGHT >= fMapCenterY - fMapSize) { + m_aMapSprites[MAPTOP3].Draw(CRect(fMapCenterX + halfTile, fMapCenterY - fMapSize, + fMapCenterX + fMapSize, fMapCenterY - halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX - fMapSize || SCREEN_HEIGHT >= fMapCenterY - halfTile) { + m_aMapSprites[MAPMID1].Draw(CRect(fMapCenterX - fMapSize, fMapCenterY - halfTile, + fMapCenterX - halfTile, fMapCenterY + halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX - halfTile || SCREEN_HEIGHT >= fMapCenterY - halfTile) { + m_aMapSprites[MAPMID2].Draw(CRect(fMapCenterX - halfTile, fMapCenterY - halfTile, + fMapCenterX + halfTile, fMapCenterY + halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX + halfTile || SCREEN_HEIGHT >= fMapCenterY - halfTile) { + m_aMapSprites[MAPMID3].Draw(CRect(fMapCenterX + halfTile, fMapCenterY - halfTile, + fMapCenterX + fMapSize, fMapCenterY + halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX - fMapSize || SCREEN_HEIGHT >= fMapCenterY + halfTile) { + m_aMapSprites[MAPBOT1].Draw(CRect(fMapCenterX - fMapSize, fMapCenterY + halfTile, + fMapCenterX - halfTile, fMapCenterY + fMapSize), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX - halfTile || SCREEN_HEIGHT >= fMapCenterY + halfTile) { + m_aMapSprites[MAPBOT2].Draw(CRect(fMapCenterX - halfTile, fMapCenterY + halfTile, + fMapCenterX + halfTile, fMapCenterY + fMapSize), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX + halfTile || SCREEN_HEIGHT >= fMapCenterY + halfTile) { + m_aMapSprites[MAPBOT3].Draw(CRect(fMapCenterX + halfTile, fMapCenterY + halfTile, + fMapCenterX + fMapSize, fMapCenterY + fMapSize), CRGBA(255, 255, 255, FadeIn(255))); + } + + CRadar::DrawBlips(); + static CVector2D mapCrosshair; + + if (m_nMenuFadeAlpha != 255 && !m_bShowMouse) { + mapCrosshair.x = SCREEN_WIDTH / 2; + mapCrosshair.y = SCREEN_HEIGHT / 2; + } else if (m_bShowMouse) { + mapCrosshair.x = m_nMousePosX; + mapCrosshair.y = m_nMousePosY; + } + + CSprite2d::DrawRect(CRect(mapCrosshair.x - MENU_X(1.0f), 0.0f, + mapCrosshair.x + MENU_X(1.0f), SCREEN_HEIGHT), + CRGBA(0, 0, 0, 150)); + CSprite2d::DrawRect(CRect(0.0f, mapCrosshair.y + MENU_X(1.0f), + SCREEN_WIDTH, mapCrosshair.y - MENU_X(1.0f)), + CRGBA(0, 0, 0, 150)); + + // Adding marker + if (m_nMenuFadeAlpha >= 255) { + if (CPad::GetPad(0)->GetRightMouseJustDown() || CPad::GetPad(0)->GetCrossJustDown()) { + if (mapCrosshair.y > fMapCenterY - fMapSize && mapCrosshair.y < fMapCenterY + fMapSize && + mapCrosshair.x > fMapCenterX - fMapSize && mapCrosshair.x < fMapCenterX + fMapSize) { + + float diffX = fMapCenterX - fMapSize, diffY = fMapCenterY - fMapSize; + float x = ((mapCrosshair.x - diffX) / (fMapSize * 2)) * 4000.0f - 2000.0f; + float y = 2000.0f - ((mapCrosshair.y - diffY) / (fMapSize * 2)) * 4000.0f; + CRadar::ToggleTargetMarker(x, y); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + } + } + + if (CPad::GetPad(0)->GetLeftMouse()) { + fMapCenterX += m_nMousePosX - m_nMouseOldPosX; + fMapCenterY += m_nMousePosY - m_nMouseOldPosY; + } else if (CPad::GetPad(0)->GetLeft() || CPad::GetPad(0)->GetDPadLeft()) { + fMapCenterX += 15.0f; + } else if (CPad::GetPad(0)->GetRight() || CPad::GetPad(0)->GetDPadRight()) { + fMapCenterX -= 15.0f; + } else if (CPad::GetPad(0)->GetLeftStickX()) { + fMapCenterX -= CPad::GetPad(0)->GetLeftStickX() / 128.0f * 20.0f; + } + + if (CPad::GetPad(0)->GetUp() || CPad::GetPad(0)->GetDPadUp()) { + fMapCenterY += 15.0f; + } else if (CPad::GetPad(0)->GetDown() || CPad::GetPad(0)->GetDPadDown()) { + fMapCenterY -= 15.0f; + } else if (CPad::GetPad(0)->GetLeftStickY()) { + fMapCenterY -= CPad::GetPad(0)->GetLeftStickY() / 128.0f * 20.0f; + } + + if (CPad::GetPad(0)->GetMouseWheelDown() || CPad::GetPad(0)->GetPageDown() || CPad::GetPad(0)->GetRightShoulder2()) { + if (CPad::GetPad(0)->GetMouseWheelDown()) + ZOOM(mapCrosshair.x, mapCrosshair.y, false); + else + ZOOM(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, false); + } else if (CPad::GetPad(0)->GetMouseWheelUp() || CPad::GetPad(0)->GetPageUp() || CPad::GetPad(0)->GetRightShoulder1()) { + if (CPad::GetPad(0)->GetMouseWheelUp()) + ZOOM(mapCrosshair.x, mapCrosshair.y, true); + else + ZOOM(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, true); + } + + if (fMapCenterX - fMapSize > SCREEN_WIDTH / 2) + fMapCenterX = fMapSize + SCREEN_WIDTH / 2; + + if (fMapCenterX + fMapSize < SCREEN_WIDTH / 2) + fMapCenterX = SCREEN_WIDTH / 2 - fMapSize; + + if (fMapCenterY + fMapSize < SCREEN_HEIGHT - MENU_Y(60.0f)) + fMapCenterY = SCREEN_HEIGHT - MENU_Y(60.0f) - fMapSize; + + fMapCenterY = Min(fMapCenterY, fMapSize); // To not show beyond north border + + bMenuMapActive = false; + + CSprite2d::DrawRect(CRect(MENU_X(14.0f), SCREEN_STRETCH_FROM_BOTTOM(95.0f), + SCREEN_STRETCH_FROM_RIGHT(11.0f), SCREEN_STRETCH_FROM_BOTTOM(59.0f)), + CRGBA(235, 170, 50, 255)); + + CFont::SetScale(MENU_X(0.4f), MENU_Y(0.7f)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, FadeIn(255))); + + float nextX = MENU_X(30.0f), nextY = 95.0f; + wchar *text; +#ifdef MORE_LANGUAGES +#define TEXT_PIECE(key,extraSpace) \ + text = TheText.Get(key);\ + CFont::PrintString(nextX, SCREEN_SCALE_FROM_BOTTOM(nextY), text);\ + if (CFont::IsJapanese())\ + nextX += CFont::GetStringWidth_Jap(text) + MENU_X(extraSpace);\ + else\ + nextX += CFont::GetStringWidth(text, true) + MENU_X(extraSpace); +#else +#define TEXT_PIECE(key,extraSpace) \ + text = TheText.Get(key); CFont::PrintString(nextX, SCREEN_SCALE_FROM_BOTTOM(nextY), text); nextX += CFont::GetStringWidth(text, true) + MENU_X(extraSpace); +#endif + + TEXT_PIECE("FEC_MWF", 3.0f); + TEXT_PIECE("FEC_PGU", 1.0f); + TEXT_PIECE("FEC_IBT", 1.0f); + TEXT_PIECE("FEC_ZIN", 20.0f); + TEXT_PIECE("FEC_MWB", 3.0f); + TEXT_PIECE("FEC_PGD", 1.0f); + TEXT_PIECE("FEC_IBT", 1.0f); + CFont::PrintString(nextX, SCREEN_SCALE_FROM_BOTTOM(nextY), TheText.Get("FEC_ZOT")); nextX = MENU_X(30.0f); nextY -= 11.0f; + TEXT_PIECE("FEC_UPA", 2.0f); + TEXT_PIECE("FEC_DWA", 2.0f); + TEXT_PIECE("FEC_LFA", 2.0f); + TEXT_PIECE("FEC_RFA", 2.0f); + TEXT_PIECE("FEC_MSL", 1.0f); + TEXT_PIECE("FEC_IBT", 1.0f); + CFont::PrintString(nextX, SCREEN_SCALE_FROM_BOTTOM(nextY), TheText.Get("FEC_MOV")); nextX = MENU_X(30.0f); nextY -= 11.0f; + TEXT_PIECE("FEC_MSR", 2.0f); + TEXT_PIECE("FEC_IBT", 1.0f); + CFont::PrintString(nextX, SCREEN_SCALE_FROM_BOTTOM(nextY), TheText.Get("FEM_TWP")); +#undef TEXT_PIECE +} + +#undef ZOOM +#endif + +// rowIdx 99999 returns total numbers of rows. otherwise it returns 0. +int +CMenuManager::ConstructStatLine(int rowIdx) +{ +#define int_STAT_IS_FLOAT false +#define float_STAT_IS_FLOAT true +#define STAT_LINE_1(varType, left, right1) \ + do { \ + if(counter == rowIdx){ \ + varType a = right1; \ + BuildStatLine(left, &a, varType##_STAT_IS_FLOAT, nil); \ + return 0; \ + } counter++; \ + } while(0) + +#define STAT_LINE_2(varType, left, right1, right2) \ + do { \ + if(counter == rowIdx){ \ + varType a = right1; \ + varType b = right2; \ + BuildStatLine(left, &a, varType##_STAT_IS_FLOAT, &b); \ + return 0; \ + } counter++; \ + } while(0) + +#define TEXT_ON_LEFT_GXT(name) \ + do { \ + if(counter == rowIdx){ \ + BuildStatLine(name, nil, false, nil); \ + return 0; \ + } counter++; \ + } while(0) + +#define TEXT_ON_RIGHT(text) \ + do { \ + if(counter == rowIdx){ \ + gUString[0] = '\0'; \ + UnicodeStrcpy(gUString2, text); \ + return 0; \ + } counter++; \ + } while(0) + + // Like TEXT_ON_LEFT_GXT, but counter wasn't initialized yet I think + if (rowIdx == 0) { + BuildStatLine("PL_STAT", nil, false, nil); + return 0; + } + + int percentCompleted = (CStats::TotalProgressInGame == 0 ? 0 : + CStats::ProgressMade * 100.0f / (CGame::nastyGame ? CStats::TotalProgressInGame : CStats::TotalProgressInGame - 1)); + percentCompleted = Min(percentCompleted, 100); + + switch (rowIdx) { + // 0 is the heading text above + case 1: { + BuildStatLine("PER_COM", &percentCompleted, false, nil); + return 0; + } + case 2: { + BuildStatLine("NMISON", &CStats::MissionsGiven, false, nil); + return 0; + } + case 3: { + BuildStatLine("FEST_MP", &CStats::MissionsPassed, false, &CStats::TotalNumberMissions); + return 0; + } + } + int counter = 4; + + if (CGame::nastyGame) + STAT_LINE_2(int, "FEST_RP", CStats::NumberKillFrenziesPassed, CStats::TotalNumberKillFrenzies); + + CPlayerInfo &player = CWorld::Players[CWorld::PlayerInFocus]; + + // Hidden packages shouldn't be shown with percent +#ifdef FIX_BUGS + STAT_LINE_2(int, "PERPIC", player.m_nCollectedPackages, player.m_nTotalPackages); +#else + float packagesPercent = 0.0f; + if (player.m_nTotalPackages != 0) + packagesPercent = player.m_nCollectedPackages * 100.0f / player.m_nTotalPackages; + + STAT_LINE_2(int, "PERPIC", packagesPercent, 100); +#endif + STAT_LINE_2(int, "NOUNIF", CStats::NumberOfUniqueJumpsFound, CStats::TotalNumberOfUniqueJumps); + STAT_LINE_1(int, "DAYSPS", CStats::DaysPassed); + if (CGame::nastyGame) { + STAT_LINE_1(int, "PE_WAST", CStats::PeopleKilledByPlayer); + STAT_LINE_1(int, "PE_WSOT", CStats::PeopleKilledByOthers); + } + STAT_LINE_1(int, "CAR_EXP", CStats::CarsExploded); + STAT_LINE_1(int, "TM_BUST", CStats::TimesArrested); + STAT_LINE_1(int, "TM_DED", CStats::TimesDied); + STAT_LINE_1(int, "GNG_WST", CStats::PedsKilledOfThisType[PEDTYPE_GANG9] + CStats::PedsKilledOfThisType[PEDTYPE_GANG8] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG7] + CStats::PedsKilledOfThisType[PEDTYPE_GANG6] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG5] + CStats::PedsKilledOfThisType[PEDTYPE_GANG4] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG3] + CStats::PedsKilledOfThisType[PEDTYPE_GANG2] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG1]); + STAT_LINE_1(int, "DED_CRI", CStats::PedsKilledOfThisType[PEDTYPE_CRIMINAL]); + STAT_LINE_1(int, "HEL_DST", CStats::HelisDestroyed); + STAT_LINE_1(int, "KGS_EXP", CStats::KgsOfExplosivesUsed); + STAT_LINE_1(int, "ACCURA", (CStats::InstantHitsFiredByPlayer == 0 ? 0 : + CStats::InstantHitsHitByPlayer * 100.0f / CStats::InstantHitsFiredByPlayer)); + + if (CStats::ElBurroTime > 0) { + STAT_LINE_1(int, "ELBURRO", CStats::ElBurroTime); + } + if (CStats::Record4x4One > 0) { + STAT_LINE_1(int, "FEST_R1", CStats::Record4x4One); + } + if (CStats::Record4x4Two > 0) { + STAT_LINE_1(int, "FEST_R2", CStats::Record4x4Two); + } + if (CStats::Record4x4Three > 0) { + STAT_LINE_1(int, "FEST_R3", CStats::Record4x4Three); + } + if (CStats::Record4x4Mayhem > 0) { + STAT_LINE_1(int, "FEST_RM", CStats::Record4x4Mayhem); + } + if (CStats::LongestFlightInDodo > 0) { + STAT_LINE_1(int, "FEST_LF", CStats::LongestFlightInDodo); + } + if (CStats::TimeTakenDefuseMission > 0) { + STAT_LINE_1(int, "FEST_BD", CStats::TimeTakenDefuseMission); + } + STAT_LINE_1(int, "CAR_CRU", CStats::CarsCrushed); + + if (CStats::HighestScores[0] > 0) { + TEXT_ON_LEFT_GXT("FEST_BB"); + STAT_LINE_1(int, "FEST_H0", CStats::HighestScores[0]); + } + if (CStats::HighestScores[4] + CStats::HighestScores[3] + CStats::HighestScores[2] + CStats::HighestScores[1] > 0) { + TEXT_ON_LEFT_GXT("FEST_GC"); + } + if (CStats::HighestScores[1] > 0) { + STAT_LINE_1(int, "FEST_H1", CStats::HighestScores[1]); + } + if (CStats::HighestScores[2] > 0) { + STAT_LINE_1(int, "FEST_H2", CStats::HighestScores[2]); + } + if (CStats::HighestScores[3] > 0) { + STAT_LINE_1(int, "FEST_H3", CStats::HighestScores[3]); + } + if (CStats::HighestScores[4] > 0) { + STAT_LINE_1(int, "FEST_H4", CStats::HighestScores[4]); + } + + switch (m_PrefsLanguage) { + case LANGUAGE_AMERICAN: +#ifndef USE_MEASUREMENTS_IN_METERS + STAT_LINE_1(float, "FEST_DF", CStats::DistanceTravelledOnFoot * MILES_IN_METER); + STAT_LINE_1(float, "FEST_DC", CStats::DistanceTravelledInVehicle * MILES_IN_METER); + STAT_LINE_1(int, "MMRAIN", CStats::mmRain); + STAT_LINE_1(float, "MXCARD", CStats::MaximumJumpDistance * FEET_IN_METER); + STAT_LINE_1(float, "MXCARJ", CStats::MaximumJumpHeight * FEET_IN_METER); + break; +#endif + case LANGUAGE_FRENCH: + case LANGUAGE_GERMAN: + case LANGUAGE_ITALIAN: + case LANGUAGE_SPANISH: +#ifdef MORE_LANGUAGES + case LANGUAGE_POLISH: + case LANGUAGE_RUSSIAN: + case LANGUAGE_JAPANESE: +#endif + STAT_LINE_1(float, "FESTDFM", CStats::DistanceTravelledOnFoot); + STAT_LINE_1(float, "FESTDCM", CStats::DistanceTravelledInVehicle); + STAT_LINE_1(int, "MMRAIN", CStats::mmRain); + STAT_LINE_1(float, "MXCARDM", CStats::MaximumJumpDistance); + STAT_LINE_1(float, "MXCARJM", CStats::MaximumJumpHeight); + break; + default: + break; + } + + STAT_LINE_1(int, "MXFLIP", CStats::MaximumJumpFlips); + STAT_LINE_1(int, "MXJUMP", CStats::MaximumJumpSpins); + TEXT_ON_LEFT_GXT("BSTSTU"); + + switch (CStats::BestStuntJump) { + case 1: + TEXT_ON_RIGHT(TheText.Get("INSTUN")); + break; + case 2: + TEXT_ON_RIGHT(TheText.Get("PRINST")); + break; + case 3: + TEXT_ON_RIGHT(TheText.Get("DBINST")); + break; + case 4: + TEXT_ON_RIGHT(TheText.Get("DBPINS")); + break; + case 5: + TEXT_ON_RIGHT(TheText.Get("TRINST")); + break; + case 6: + TEXT_ON_RIGHT(TheText.Get("PRTRST")); + break; + case 7: + TEXT_ON_RIGHT(TheText.Get("QUINST")); + break; + case 8: + TEXT_ON_RIGHT(TheText.Get("PQUINS")); + break; + default: + TEXT_ON_RIGHT(TheText.Get("NOSTUC")); + break; + } + + STAT_LINE_1(int, "PASDRO", CStats::PassengersDroppedOffWithTaxi); + STAT_LINE_1(int, "MONTAX", CStats::MoneyMadeWithTaxi); + STAT_LINE_1(int, "FEST_LS", CStats::LivesSavedWithAmbulance); + STAT_LINE_1(int, "FEST_HA", CStats::HighestLevelAmbulanceMission); + STAT_LINE_1(int, "FEST_CC", CStats::CriminalsCaught); + STAT_LINE_1(int, "FEST_FE", CStats::FiresExtinguished); + STAT_LINE_1(int, "DAYPLC", CTimer::GetTimeInMilliseconds() + 100); + return counter; + +#undef STAT_LINE_1 +#undef STAT_LINE_2 +#undef TEXT_ON_LEFT_GXT +#undef TEXT_ON_RIGHT +#undef int_STAT_IS_FLOAT +#undef float_STAT_IS_FLOAT +} + +#undef GetBackJustUp +#undef GetBackJustDown +#undef ChangeScreen + +#endif diff --git a/src/core/Frontend.h b/src/core/Frontend.h new file mode 100644 index 0000000..6e6c40f --- /dev/null +++ b/src/core/Frontend.h @@ -0,0 +1,839 @@ +#pragma once +#ifdef PS2_MENU +#include "Frontend_PS2.h" +#else + +#include "Sprite2d.h" + +#ifdef PS2_LIKE_MENU +#define MENUHEADER_POS_X 50.0f +#define MENUHEADER_POS_Y 75.0f +#define MENUHEADER_HEIGHT 1.3f +#else +#define MENUHEADER_POS_X 35.0f +#define MENUHEADER_POS_Y 93.0f +#define MENUHEADER_HEIGHT 1.6f +#endif +#define MENUHEADER_WIDTH 0.84f + +#define MENU_X_MARGIN 40.0f +#define MENUACTION_POS_Y 60.0f +#define MENUACTION_SCALE_MULT 0.9f + +#define MENURADIO_ICON_SCALE 60.0f + +#define MENUSLIDER_X 256.0f +#define MENUSLIDER_UNK 256.0f + +#define MENUSLIDER_BARS 16 +#define MENUSLIDER_LOGICAL_BARS MENUSLIDER_BARS + +#define BIGTEXT_X_SCALE 0.75f // For FONT_HEADING +#define BIGTEXT_Y_SCALE 0.9f +#define MEDIUMTEXT_X_SCALE 0.55f // For FONT_HEADING +#define MEDIUMTEXT_Y_SCALE 0.8f +#define SMALLTEXT_X_SCALE 0.45f // used for FONT_HEADING and FONT_BANK, but looks off for HEADING +#define SMALLTEXT_Y_SCALE 0.7f +#define SMALLESTTEXT_X_SCALE 0.4f // used for both FONT_HEADING and FONT_BANK +#define SMALLESTTEXT_Y_SCALE 0.6f + +#define HELPER_TEXT_LEFT_MARGIN 320.0f +#define HELPER_TEXT_BOTTOM_MARGIN 120.0f + +#define PLAYERSETUP_LIST_TOP 28.0f +#define PLAYERSETUP_LIST_BOTTOM 125.0f +#define PLAYERSETUP_LIST_LEFT 200.0f +#define PLAYERSETUP_LIST_RIGHT 36.0f +#ifdef FIX_BUGS // See the scrollbar button drawing code +#define PLAYERSETUP_SCROLLBAR_WIDTH 19.0f +#else +#define PLAYERSETUP_SCROLLBAR_WIDTH 16.0f +#endif +#define PLAYERSETUP_SCROLLBUTTON_HEIGHT 17.0f +#define PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION 64 +#define PLAYERSETUP_SKIN_COLUMN_LEFT 220.0f +#define PLAYERSETUP_DATE_COLUMN_RIGHT 56.0f +#define PLAYERSETUP_LIST_BODY_TOP 47 +#define PLAYERSETUP_ROW_HEIGHT 9 + +#define STATS_SLIDE_Y_PER_SECOND 30.0f +#define STATS_ROW_HEIGHT 20.0f +#define STATS_ROW_X_MARGIN 50.0f +#define STATS_BOTTOM_MARGIN 135.0f +#define STATS_TOP_MARGIN 40.0f +#define STATS_TOP_DIMMING_AREA_LENGTH (93.0f - STATS_TOP_MARGIN) +#define STATS_BOTTOM_DIMMING_AREA_LENGTH 55.0f +#define STATS_PUT_BACK_TO_BOTTOM_Y 50.0f +#define STATS_RATING_X 24.0f +#define STATS_RATING_Y 20.0f + +#define BRIEFS_TOP_MARGIN 40.0f +#define BRIEFS_LINE_X 50.0f +#define BRIEFS_LINE_HEIGHT 60.0f + +#define CONTSETUP_STANDARD_ROW_HEIGHT 10.7f +#define CONTSETUP_CLASSIC_ROW_HEIGHT 9.0f +#define CONTSETUP_BOUND_HIGHLIGHT_HEIGHT 10 +#define CONTSETUP_BOUND_COLUMN_WIDTH 190.0f +#define CONTSETUP_LIST_HEADER_HEIGHT 20.0f +#define CONTSETUP_LIST_TOP 28.0f +#define CONTSETUP_LIST_RIGHT 18.0f +#define CONTSETUP_LIST_BOTTOM 120.0f +#define CONTSETUP_LIST_LEFT 18.0f +#define CONTSETUP_COLUMN_1_X 40.0f +#define CONTSETUP_COLUMN_2_X 210.0f +#define CONTSETUP_COLUMN_3_X (CONTSETUP_COLUMN_2_X + CONTSETUP_BOUND_COLUMN_WIDTH + 10.0f) +#define CONTSETUP_BACK_RIGHT 35.0f +#define CONTSETUP_BACK_BOTTOM 122.0f +#define CONTSETUP_BACK_HEIGHT 25.0f + +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, + + NUM_FE_SPRITES +}; + +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, + MENUSPRITE_UNUSED, + NUM_MENU_SPRITES +}; + +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, +}; + +#ifdef MENU_MAP +enum MapSprites +{ + MAPMID1, + MAPMID2, + MAPMID3, + MAPBOT1, + MAPBOT2, + MAPBOT3, + MAPTOP1, + MAPTOP2, + MAPTOP3, + NUM_MAP_SPRITES +}; +#endif + +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_DISPLAY_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_NO_MEMORY_CARD = 13, // hud adjustment page in mobile + MENUPAGE_LOADING_IN_PROGRESS = 14, + MENUPAGE_DELETING_IN_PROGRESS = 15, + MENUPAGE_PS2_LOAD_FAILED = 16, + MENUPAGE_DELETE_FAILED = 17, + MENUPAGE_DEBUG_MENU = 18, + MENUPAGE_MEMORY_CARD_DEBUG = 19, + MENUPAGE_MEMORY_CARD_TEST = 20, + MENUPAGE_MULTIPLAYER_MAIN = 21, + MENUPAGE_PS2_SAVE_FAILED = 22, + MENUPAGE_PS2_SAVE_FAILED_2 = 23, + MENUPAGE_SAVE = 24, + MENUPAGE_NO_MEMORY_CARD_2 = 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_MISSION_RETRY = 57, +#ifdef CUSTOM_FRONTEND_OPTIONS + +#ifdef MENU_MAP + MENUPAGE_MAP = 58, +#endif +#ifdef GRAPHICS_MENU_OPTIONS + MENUPAGE_GRAPHICS_SETTINGS, +#endif +#ifdef DETECT_JOYSTICK_MENU + MENUPAGE_DETECT_JOYSTICK, +#endif + +#endif + MENUPAGE_UNK, // originally 58. Custom screens are inserted above, because last screen in CMenuScreens should always be empty to make CFO work + MENUPAGES + +}; + +enum eMenuAction +{ +#ifdef CUSTOM_FRONTEND_OPTIONS + MENUACTION_CFO_SLIDER = -3, + MENUACTION_CFO_SELECT = -2, + MENUACTION_CFO_DYNAMIC = -1, +#endif + 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_POPULATESLOTS_CHANGEMENU, + 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_RESUME_FROM_SAVEZONE, + 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_KEYBOARDCTRLS, + MENUACTION_UNK73, + MENUACTION_INITMP, + MENUACTION_MP_PLAYERCOLOR, + MENUACTION_MP_PLAYERNAME, + MENUACTION_MP_GAMENAME, + MENUACTION_GETKEY, + MENUACTION_SHOWHEADBOB, + MENUACTION_UNK80, + MENUACTION_INVVERT, + MENUACTION_CANCELGAME, + 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_DONTCANCEL, + 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, + MENUACTION_UNK111, + MENUACTION_UNK112, + MENUACTION_REJECT_RETRY, + MENUACTION_UNK114, +//#ifdef ANISOTROPIC_FILTERING +// MENUACTION_MIPMAPS, +// MENUACTION_TEXTURE_FILTERING, +//#endif +}; + +enum eCheckHover +{ + HOVEROPTION_0, + HOVEROPTION_1, + HOVEROPTION_RANDOM_ITEM, + HOVEROPTION_3, + HOVEROPTION_4, + HOVEROPTION_5, + HOVEROPTION_6, + HOVEROPTION_7, + HOVEROPTION_8, + HOVEROPTION_BACK, // also layer in controller setup and skin menu + HOVEROPTION_10, + HOVEROPTION_11, + HOVEROPTION_OVER_SCROLL_UP, + HOVEROPTION_OVER_SCROLL_DOWN, + HOVEROPTION_CLICKED_SCROLL_UP, + HOVEROPTION_CLICKED_SCROLL_DOWN, + HOVEROPTION_HOLDING_SCROLLBAR, + HOVEROPTION_PAGEUP, + HOVEROPTION_PAGEDOWN, + HOVEROPTION_LIST, // also layer in controller setup and skin menu + HOVEROPTION_SKIN, + HOVEROPTION_USESKIN, // also layer in controller setup and skin menu + HOVEROPTION_RADIO_0, + HOVEROPTION_RADIO_1, + HOVEROPTION_RADIO_2, + HOVEROPTION_RADIO_3, + HOVEROPTION_RADIO_4, + HOVEROPTION_RADIO_5, + HOVEROPTION_RADIO_6, + HOVEROPTION_RADIO_7, + HOVEROPTION_RADIO_8, + HOVEROPTION_RADIO_9, + HOVEROPTION_INCREASE_BRIGHTNESS, + HOVEROPTION_DECREASE_BRIGHTNESS, + HOVEROPTION_INCREASE_DRAWDIST, + HOVEROPTION_DECREASE_DRAWDIST, + HOVEROPTION_INCREASE_MUSICVOLUME, + HOVEROPTION_DECREASE_MUSICVOLUME, + HOVEROPTION_INCREASE_SFXVOLUME, + HOVEROPTION_DECREASE_SFXVOLUME, + HOVEROPTION_INCREASE_MOUSESENS, + HOVEROPTION_DECREASE_MOUSESENS, +#ifdef CUSTOM_FRONTEND_OPTIONS + HOVEROPTION_INCREASE_CFO_SLIDER, + HOVEROPTION_DECREASE_CFO_SLIDER, +#endif + HOVEROPTION_NOT_HOVERING, +}; + +enum +{ + NUM_MENUROWS = 18, +}; + +enum eControlMethod +{ + CONTROL_STANDARD = 0, + CONTROL_CLASSIC, +}; + +// Why?? +enum ControllerSetupColumn +{ + CONTSETUP_PED_COLUMN = 0, + CONTSETUP_VEHICLE_COLUMN = 14, +}; + +struct tSkinInfo +{ + int32 skinId; + char skinNameDisplayed[256]; + char skinNameOriginal[256]; + char date[256]; + tSkinInfo *nextSkin; +}; + +struct BottomBarOption +{ + char name[8]; + int32 screenId; +}; + +#ifndef CUSTOM_FRONTEND_OPTIONS +struct CMenuScreen +{ + char m_ScreenName[8]; + int32 unk; // 2 on MENUPAGE_MULTIPLAYER_START, 1 on everywhere else, 0 on unused. + int32 m_PreviousPage[2]; // eMenuScreen + int32 m_ParentEntry[2]; // row + + struct CMenuEntry + { + int32 m_Action; // eMenuAction + char m_EntryName[8]; + int32 m_SaveSlot; // eSaveSlot + int32 m_TargetMenu; // eMenuScreen + } m_aEntries[NUM_MENUROWS]; +}; +extern CMenuScreen aScreens[MENUPAGES]; +#else +#include "frontendoption.h" +struct CCustomScreenLayout { + eMenuSprites sprite; + int columnWidth; + int headerHeight; + int lineHeight; + int8 font; + int8 alignment; + bool showLeftRightHelper; + float fontScaleX; + float fontScaleY; +}; + +struct CCFO +{ + void *value; + const char *saveCat; + const char *save; +}; + +struct CCFOSelect : CCFO +{ + char** rightTexts; + int8 numRightTexts; + bool onlyApplyOnEnter; + int8 displayedValue; // only if onlyApplyOnEnter enabled for now + int8 lastSavedValue; // only if onlyApplyOnEnter enabled + ChangeFunc changeFunc; + bool disableIfGameLoaded; + + CCFOSelect() {}; + CCFOSelect(int8* value, const char* saveCat, const char* save, const char** rightTexts, int8 numRightTexts, bool onlyApplyOnEnter, ChangeFunc changeFunc = nil, bool disableIfGameLoaded = false){ + this->value = value; + if (value) + this->lastSavedValue = this->displayedValue = *value; + + this->saveCat = saveCat; + this->save = save; + this->rightTexts = (char**)rightTexts; + this->numRightTexts = numRightTexts; + this->onlyApplyOnEnter = onlyApplyOnEnter; + this->changeFunc = changeFunc; + this->disableIfGameLoaded = disableIfGameLoaded; + } +}; + +// Value is float in here +struct CCFOSlider : CCFO +{ + ChangeFuncFloat changeFunc; + float min; + float max; + + CCFOSlider() {}; + CCFOSlider(float* value, const char* saveCat, const char* save, float min, float max, ChangeFuncFloat changeFunc = nil){ + this->value = value; + this->saveCat = saveCat; + this->save = save; + this->changeFunc = changeFunc; + this->min = min; + this->max = max; + } +}; + +struct CCFODynamic : CCFO +{ + DrawFunc drawFunc; + ButtonPressFunc buttonPressFunc; + + CCFODynamic() {}; + CCFODynamic(int8* value, const char* saveCat, const char* save, DrawFunc drawFunc, ButtonPressFunc buttonPressFunc){ + this->value = value; + this->saveCat = saveCat; + this->save = save; + this->drawFunc = drawFunc; + this->buttonPressFunc = buttonPressFunc; + } +}; + +struct CMenuScreenCustom +{ + char m_ScreenName[8]; + int32 m_PreviousPage[2]; // eMenuScreen + CCustomScreenLayout *layout; + ReturnPrevPageFunc returnPrevPageFunc; + + struct CMenuEntry + { + int32 m_Action; // eMenuAction - below zero is CFO + char m_EntryName[8]; + struct { + union { + CCFO *m_CFO; // for initializing + CCFOSelect *m_CFOSelect; + CCFODynamic *m_CFODynamic; + CCFOSlider *m_CFOSlider; + }; + int32 m_SaveSlot; // eSaveSlot + int32 m_TargetMenu; // eMenuScreen + }; + } m_aEntries[NUM_MENUROWS]; +}; +extern CMenuScreenCustom aScreens[MENUPAGES]; +#endif + +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; + bool m_bMenuStateChanged; + bool m_bWaitingForNewKeyBind; + bool m_bWantToRestart; + bool m_bFirstTime; + bool m_bGameNotLoaded; + int32 m_nMousePosX; + int32 m_nMousePosY; + int32 m_nMouseTempPosX; + int32 m_nMouseTempPosY; + bool m_bShowMouse; + tSkinInfo m_pSkinListHead; + tSkinInfo *m_pSelectedSkin; + int32 m_nFirstVisibleRowOnList; + float m_nScrollbarTopMargin; + int32 m_nTotalListRow; + int32 m_nSkinsTotal; + char _unk0[4]; + int32 m_nSelectedListRow; + bool m_bSkinsEnumerated; + bool m_bQuitGameNoCD; + bool m_bRenderGameInMenu; + bool m_bSaveMenuActive; + bool m_bWantToLoad; + char field_455; + bool m_bStartWaitingForKeyBind; + bool m_bSpritesLoaded; + CSprite2d m_aFrontEndSprites[NUM_FE_SPRITES]; + CSprite2d m_aMenuSprites[NUM_MENU_SPRITES]; + int32 field_518; + int32 m_nMenuFadeAlpha; + bool m_bPressedPgUpOnList; + bool m_bPressedPgDnOnList; + bool m_bPressedUpOnList; + bool m_bPressedDownOnList; + bool m_bPressedScrollButton; + int32 m_CurrCntrlAction; + char _unk1[4]; + int32 m_nSelectedContSetupColumn; + bool m_bKeyIsOK; + bool field_535; + int8 m_nCurrExLayer; + int32 m_nHelperTextAlpha; + int32 m_nMouseOldPosX; + int32 m_nMouseOldPosY; + int32 m_nHoverOption; + int32 m_nCurrScreen; + int32 m_nCurrOption; + int32 m_nOptionMouseHovering; + int32 m_nPrevScreen; + uint32 field_558; + int32 m_nCurrSaveSlot; + int32 m_nScreenChangeDelayTimer; + +#ifdef IMPROVED_VIDEOMODE + int32 m_nPrefsWidth; + int32 m_nPrefsHeight; + int32 m_nPrefsDepth; + int32 m_nPrefsWindowed; + int32 m_nPrefsSubsystem; + int32 m_nSelectedScreenMode; +#endif +#ifdef MULTISAMPLING + static int8 m_nPrefsMSAALevel; + static int8 m_nDisplayMSAALevel; +#endif + + enum LANGUAGE + { + LANGUAGE_AMERICAN, + LANGUAGE_FRENCH, + LANGUAGE_GERMAN, + LANGUAGE_ITALIAN, + LANGUAGE_SPANISH, +#ifdef MORE_LANGUAGES + LANGUAGE_POLISH, + LANGUAGE_RUSSIAN, + LANGUAGE_JAPANESE, +#endif + }; +public: + bool GetIsMenuActive() {return !!m_bMenuActive;} + +public: + 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 int32 m_ControlMethod; + static int8 m_PrefsDMA; + static int32 m_PrefsLanguage; + static int32 m_PrefsBrightness; + static float m_PrefsLOD; + static int8 m_bFrontEnd_ReloadObrTxtGxt; + static int32 m_PrefsMusicVolume; + static int32 m_PrefsSfxVolume; + static char m_PrefsSkinFile[256]; + static int32 m_KeyPressedCode; + + static bool m_bStartUpFrontEndRequested; + static bool m_bShutDownFrontEndRequested; + static bool m_PrefsAllowNastyGame; + + static uint8 m_PrefsStereoMono; + static int32 m_SelectedMap; + static int32 m_SelectedGameType; + static uint8 m_PrefsPlayerRed; + static uint8 m_PrefsPlayerGreen; + static uint8 m_PrefsPlayerBlue; + +#ifdef CUTSCENE_BORDERS_SWITCH + static bool m_PrefsCutsceneBorders; +#endif + +#ifndef MASTER + static bool m_PrefsMarketing; + static bool m_PrefsDisableTutorials; +#endif // !MASTER + +#ifdef MENU_MAP + static bool bMenuMapActive; + static float fMapSize; + static float fMapCenterY; + static float fMapCenterX; + static CSprite2d m_aMapSprites[NUM_MAP_SPRITES]; + void PrintMap(); +#endif + +#ifdef NO_ISLAND_LOADING + enum + { + ISLAND_LOADING_LOW = 0, + ISLAND_LOADING_MEDIUM, + ISLAND_LOADING_HIGH + }; + + static int8 m_PrefsIslandLoading; + + #define ISLAND_LOADING_IS(p) if (CMenuManager::m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_##p) + #define ISLAND_LOADING_ISNT(p) if (CMenuManager::m_PrefsIslandLoading != CMenuManager::ISLAND_LOADING_##p) +#else + #define ISLAND_LOADING_IS(p) + #define ISLAND_LOADING_ISNT(p) +#endif + +#ifdef GAMEPAD_MENU + enum + { + CONTROLLER_DUALSHOCK2 = 0, + CONTROLLER_DUALSHOCK3, + CONTROLLER_DUALSHOCK4, + CONTROLLER_XBOX360, + CONTROLLER_XBOXONE, + CONTROLLER_NINTENDO_SWITCH, + }; + + static int8 m_PrefsControllerType; +#endif + +public: + static void BuildStatLine(Const char *text, void *stat, bool itsFloat, void *stat2); + static void CentreMousePointer(); + void CheckCodesForControls(int); + bool CheckHover(int x1, int x2, int y1, int y2); + void CheckSliderMovement(int); + int CostructStatLine(int); + void DisplayHelperText(); + int DisplaySlider(float, float, float, float, float, float); + void DoSettingsBeforeStartingAGame(); + void Draw(); + void DrawControllerBound(int32, int32, int32, int8); + void DrawControllerScreenExtraText(int, int, int); + void DrawControllerSetupScreen(); + void DrawFrontEnd(); + void DrawFrontEndNormal(); +#ifdef PS2_SAVE_DIALOG + void DrawFrontEndSaveZone(); +#endif + void DrawPlayerSetupScreen(); + int FadeIn(int alpha); + void FilterOutColorMarkersFromString(wchar*, CRGBA &); + int GetStartOptionsCntrlConfigScreens(); + static void InitialiseChangedLanguageSettings(); + void LoadAllTextures(); + void LoadSettings(); + void MessageScreen(const char *); + 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(); + float StretchX(float); + float StretchY(float); + void SwitchMenuOnAndOff(); + void UnloadTextures(); + void WaitForUserCD(); + void PrintController(); + int GetNumOptionsCntrlConfigScreens(); + int ConstructStatLine(int); + + // Those are either inlined in game, not in function yet, or I can't believe that they're not inlined. + // Names were made up by me. + void ThingsToDoBeforeGoingBack(); + void ScrollUpListByOne(); + void ScrollDownListByOne(); + void PageUpList(bool); + void PageDownList(bool); + int8 GetPreviousPageOption(); + void ProcessList(bool &goBack, bool &optionSelected); +#ifdef GAMEPAD_MENU + void LoadController(int8 type); +#endif +}; + +#ifndef IMPROVED_VIDEOMODE +VALIDATE_SIZE(CMenuManager, 0x564); +#endif + +extern CMenuManager FrontEndMenuManager; + +#endif diff --git a/src/core/FrontendTriggers.h b/src/core/FrontendTriggers.h new file mode 100644 index 0000000..44bae54 --- /dev/null +++ b/src/core/FrontendTriggers.h @@ -0,0 +1,1393 @@ +CTriggerCaller MemCardAccessTriggerCaller; + +void InitialiseTextsInMenuControllerInCar(CMenuPictureAndText *widget, CMenuManager::CONTRCONFIG cont); +void InitialiseTextsInMenuControllerOnFoot(CMenuPictureAndText *widget, CMenuManager::CONTRCONFIG cont); +void TriggerSave_BackToMainMenu(CMenuMultiChoiceTriggered *widget); +void TriggerSave_BackToMainMenuTwoLines(CMenuMultiChoiceTwoLinesTriggered *widget); +void TriggerSave_LoadGameLoadGameSelect(CMenuMultiChoiceTwoLinesTriggered *widget); +void TriggerSave_DeleteGameDeleteGameSelect(CMenuMultiChoiceTwoLinesTriggered *widget); +void TriggerSaveZone_BackToMainMenuTwoLines(CMenuMultiChoiceTwoLinesTriggered *widget); +void TriggerSaveZone_BackToMainMenuTwoLines(CMenuMultiChoiceTwoLinesTriggered *widget); +void TriggerSaveZone_SaveSlots(CMenuMultiChoiceTwoLinesTriggered *widget); + +void +DisplayWarningControllerMsg() +{ + if ( CPad::bDisplayNoControllerMessage ) + { + CSprite2d::DrawRect(CRect(X(20.0f), Y(140.0f), X(620.0f), Y(328.0)), CRGBA(64, 16, 16, 224)); // CRect(20.0f, 160.0f, 620.0f, 374.857117f) + + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(0.84f), Y(1.26f)); // 1.440000 + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + + CPlaceableShText text; + text.SetPosition(X(60.0f), Y(180.0f), false); // 205.714294 + text.SetColor(CRGBA(152, 152, 152, 255)); + text.m_text = TheText.Get("NOCONTE"); // Please re-insert the analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2) in controller port 1 to continue + text.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR*2.0f); + text.SetAlpha(255); + text.DrawShWrap(0.0f, 0.0f, X(600.0f+SHADOW_VECTOR.x), YF(600.0f)); //TODO check + + CFont::DrawFonts(); + } + else if ( CPad::bObsoleteControllerMessage ) + { + CSprite2d::DrawRect(CRect(X(20.0f), Y(140.0f), X(620.0f), Y(328.0)), CRGBA(64, 16, 16, 224)); // CRect(20.0f, 160.0f, 620.0f, 374.857117f) + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(0.84f), Y(1.26f)); // 1.440000 + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + + CPlaceableShText text; + text.SetPosition(X(60.0f), Y(180.0f), false); // 205.714294 + text.SetColor(CRGBA(152, 152, 152, 255)); + text.m_text = TheText.Get("WRCONTE"); // Please re-insert the analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2) in controller port 1 to continue + text.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR*2.0f); + text.SetAlpha(255); + text.DrawShWrap(0.0f, 0.0f, X(600.0f+SHADOW_VECTOR.x), YF(600.0f)); //TODO check + + CFont::DrawFonts(); + } + +} + +void +TriggerMCSUM_Yes(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + bMemoryCardStartUpMenus_ExitNow = true; +} + +int32 nStatLinesIndex; +wchar aStatLines[50+1][50]; +wchar *PrintStatLine(char const *text, void *stat, unsigned char itsFloat, void *stat2) +{ + if (text && stat && nStatLinesIndex < 50) + { + char line [64]; + wchar uline[64]; + + memset(line, 0, sizeof(line)); + memset(uline, 0, sizeof(uline)); + + if (stat2) + { + if ( itsFloat ) + sprintf(line, " %.2f %s %.2f", *(float*)stat, UnicodeToAscii(TheText.Get("FEST_OO")), *(float*)stat2); + else + sprintf(line, " %d %s %d", *(int32*)stat, UnicodeToAscii(TheText.Get("FEST_OO")), *(int32*)stat2); + } + else + { + if (itsFloat) + sprintf(line, " %.2f", *(float*)stat); + else + sprintf(line, " %d", *(int32*)stat); + } + + wchar *pStatLine = aStatLines[nStatLinesIndex++]; + + AsciiToUnicode(line, uline); + UnicodeStrcpy(pStatLine, uline); + + return pStatLine; + } + + return nil; +} + +void +DisplayMemoryCardAccessMsg(wchar *msg, CRGBA const &color) +{ + CSprite2d::DrawRect(CRect(X(70.0f), Y(100.0f), X(570.0f), Y(270.0f)), color); + + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(MEMCARD_ACCESS_MSG_SIZE_X), Y(MEMCARD_ACCESS_MSG_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(90.0f)); // 550.0f + CFont::SetCentreOn(); + CFont::SetCentreSize(SCRW-X(180.0f)); // 460.0f + + CPlaceableShText text; + + text.SetPosition(X(320.0f), Y(120.0f), false); // 137.142868 + text.SetColor(CRGBA(200, 200, 200, 255)); + text.m_text = msg; + + text.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + text.SetAlpha(255); + text.Draw(0.0f, 0.0f); + + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +void +FillMenuWithMemCardFileListing(CMenuMultiChoiceTwoLinesTriggered *widget, void (*cancelTrigger)(CMenuMultiChoiceTwoLinesTriggered *), void (*selectTrigger)(CMenuMultiChoiceTwoLinesTriggered *), wchar *text, int32 y, int32 height, int32 offset) +{ + if ( widget ) + { + int32 selected = 0; + if ( bMemoryCardSpecialZone ) + selected = widget->m_cursor != -1 ? widget->m_cursor : 0; + + widget->DeactivateMenu(); // TODO check + widget->m_numOptions = 0; + widget->AddTitle(nil, 0.0f, 0.0f, 0); + + TheMemoryCard.PopulateSlotInfo(CARD_ONE); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS) + { + widget->AddOption(TheText.Get("FES_CAN"), 0.0f, YF(y), cancelTrigger, 0, 0); + + FrontEndMenuManager.field_3C = 0; + + y += offset; + + char buff[100]; + + for ( int32 i = 0; i < CMemoryCard::MAX_SLOTS; i++ ) + { + // SAVE FILE + sprintf(buff, "%s %d ", UnicodeToAscii(TheText.Get("FES_SLO")), i+1); + AsciiToUnicode(buff, MemoryCard_FileNames[i]); + + wchar *datetime = nil; + + switch ( TheMemoryCard.GetInfoOnSpecificSlot(i) ) + { + case CMemoryCard::SLOT_CORRUPTED: + { + UnicodeStrcat(MemoryCard_FileNames[i], TheText.Get("FES_ISC")); // IS CORRUPTED + datetime = TheMemoryCard.GetDateAndTimeOfSavedGame(i); + break; + } + case CMemoryCard::SLOT_PRESENT: + { + if ( TheMemoryCard.GetNameOfSavedGame(i) != nil ) + { + UnicodeStrcpy(MemoryCard_FileNames[i], TheMemoryCard.GetNameOfSavedGame(i)); + datetime = TheMemoryCard.GetDateAndTimeOfSavedGame(i); + } + else + { + UnicodeStrcpy(MemoryCard_FileNames[i], TheText.Get("FES_SAG")); // PRESENT + datetime = TheMemoryCard.GetDateAndTimeOfSavedGame(i); + } + break; + } + case CMemoryCard::SLOT_NOTPRESENT: + { + UnicodeStrcat(MemoryCard_FileNames[i], TheText.Get("FES_ISF")); + datetime = TheMemoryCard.GetDateAndTimeOfSavedGame(i); + break; + } + } + + widget->AddOption(MemoryCard_FileNames[i], 0.0f, YF(y), datetime, 0.0f, YF(float(y)+(0.44f*height)), selectTrigger, 0, 0); + y += height; + } + } + else + { + if ( !gErrorSampleTriggered ) + { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + gErrorSampleTriggered = true; + } + + // Cancel + widget->AddOption(TheText.Get("FES_CAN"), 0.0f, YF(y+(height*2)), cancelTrigger, 0, 0); + + FrontEndMenuManager.field_3C = 1; + + y += height; + + TheMemoryCard.PopulateErrorMessage(); + + // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + if ( TheMemoryCard.GetErrorMessage()) + widget->AddTitle(TheMemoryCard.GetErrorMessage(), 0.0f, YF(y), 0); + else + widget->AddTitle(TheText.Get("FES_GME"), 0.0f, YF(y), 0); + } + + widget->SetMenuSelection(0); + widget->ActivateMenu(1); + + if ( bMemoryCardSpecialZone ) + { + widget->GoFirst(); + + for ( int32 i = 0; i < selected; i++ ) + widget->GoNext(); + } + } +} + +void +TriggerSaveZone_FormatFailedOK(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + pActiveMenuPage = &MenuPageSaveZone_SaveGame; +} + +void +TriggerSaveZone_BackToMainMenu(CMenuMultiChoiceTriggered *widget) +{ + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + pActiveMenuPage = &MenuPageSaveZone_SaveGame; +} + +void +TriggerSaveZone_QuitMenu(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + FrontEndMenuManager.m_bMenuActive = false; + FrontEndMenuManager.m_bInSaveZone = false; + CTimer::EndUserPause(); + } +} + +void +TriggerSaveZone_FormatCard(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + FillMenuWithMemCardFileListing(&MenuSaveZoneSSL_1, TriggerSaveZone_BackToMainMenuTwoLines, TriggerSaveZone_SaveSlots, nil, 0, 34, 22); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS) + { + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FES_AFO"), X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(5.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else if ( TheMemoryCard.GetError() != CMemoryCard::ERR_NOFORMAT) + { + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheMemoryCard.GetErrorMessage(), X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(15.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else + { + if ( !MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.SetTrigger(TriggerSaveZone_FormatCard, widget); + else + { + // Formatting Memory Card (PS2) in MEMORY CARD slot 1. Please do not remove the Memory Card (PS2), reset or switch off the console. + DisplayMemoryCardAccessMsg(TheText.Get("FEFD_WR"), CRGBA(200, 50, 50, 192)); + TheMemoryCard.FormatCard(CARD_ONE); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS ) + pActiveMenuPage = &MenuPageSaveZone_SaveGame; + else + { + TheMemoryCard.PopulateErrorMessage(); + + wchar *error = TheText.Get("FESZ_FF"); // Format Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + + // missing switch + + if ( !error ) error = TheText.Get("FES_GME"); // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(error, X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS ) + { + FillMenuWithMemCardFileListing(&MenuSaveZoneSSL_1, TriggerSaveZone_BackToMainMenuTwoLines, TriggerSaveZone_SaveSlots, nil, 0, 34, 22); + pActiveMenuPage = &MenuPageSaveZone_SaveSlots; + bMemoryCardSpecialZone = true; + bIgnoreTriangleButton = true; + pActiveMenuPage->ActivatePage(); + } + else + { + TheMemoryCard.PopulateErrorMessage(); + + // Format Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + wchar *error = TheText.Get("FESZ_FF"); + + switch ( TheMemoryCard.GetError() ) + { + case CMemoryCard::ERR_WRITEFULLDEVICE: + case CMemoryCard::ERR_DIRFULLDEVICE: + case CMemoryCard::ERR_SAVEFAILED: + { + error = TheMemoryCard.GetErrorMessage(); + break; + } + } + + // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + if ( !error ) error = TheText.Get("FES_GME"); + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(error, X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + } + } + } +} + +void +TriggerSaveZone_FormatCardSelect(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + FillMenuWithMemCardFileListing(&MenuSaveZoneSSL_1, TriggerSaveZone_BackToMainMenuTwoLines, TriggerSaveZone_SaveSlots, nil, 0, 34, 22); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS ) + { + // This Memory Card (PS2) is already formatted. + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FES_AFO"), X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(5.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else if ( TheMemoryCard.GetError() != CMemoryCard::ERR_NOFORMAT ) + { + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheMemoryCard.GetErrorMessage(), X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(15.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else + { + // Are you sure you wish to format the Memory Card (PS2) in MEMORY CARD slot 1? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QF"), X(-40.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSaveZone_FormatCard, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_QuestionYesNo; + } + } +} + +void +TriggerSaveZone_DeleteSaveGame(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + + if ( !MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.SetTrigger(TriggerSaveZone_DeleteSaveGame, widget); + else + { + // Overwriting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + DisplayMemoryCardAccessMsg(TheText.Get("FESZ_OW"), CRGBA(200, 50, 50, 192)); + + TheMemoryCard.DeleteSlot(MemoryCardSlotSelected); + + if ( TheMemoryCard.GetError() != CMemoryCard::NO_ERR_SUCCESS ) + { + TheMemoryCard.PopulateErrorMessage(); + + wchar *error = TheText.Get("FES_DEE"); // Deleting Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + + // switch missing + + if ( !error ) error = TheText.Get("FES_GME"); // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(error, X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else + { + TheMemoryCard.SaveSlot(MemoryCardSlotSelected); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS ) + { + // Game saved successfully! + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FESZ_L1"), X(-20.0f), YF(10.0f), TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(110.0f), 0.0f, TriggerSaveZone_QuitMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else + { + TheMemoryCard.PopulateErrorMessage(); + + wchar *error = TheText.Get("FESZ_SR"); // Save Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + + switch ( TheMemoryCard.GetError() ) + { + case CMemoryCard::ERR_WRITEFULLDEVICE: + case CMemoryCard::ERR_DIRFULLDEVICE: + case CMemoryCard::ERR_SAVEFAILED: + { + error = TheMemoryCard.GetErrorMessage(); + break; + } + } + + if ( !error ) error = TheText.Get("FES_GME"); // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(error, X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(120.0f), YF(30.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + } + } + } +} + +void +TriggerSaveZone_SaveGame(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + if ( !MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.SetTrigger(TriggerSaveZone_SaveGame, widget); + else + { + DisplayMemoryCardAccessMsg(TheText.Get("FESZ_WR"), CRGBA(200, 50, 50, 192)); + + TheMemoryCard.SaveSlot(MemoryCardSlotSelected); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS ) + { + // Game saved successfully! + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FESZ_L1"), X(-20.0f), YF(10.0f), TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(110.0f), 0.0f, TriggerSaveZone_QuitMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else + { + TheMemoryCard.PopulateErrorMessage(); + + wchar *error = TheText.Get("FESZ_SR"); // Save Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + + switch ( TheMemoryCard.GetError() ) + { + case CMemoryCard::ERR_WRITEFULLDEVICE: + case CMemoryCard::ERR_DIRFULLDEVICE: + case CMemoryCard::ERR_SAVEFAILED: + { + error = TheMemoryCard.GetErrorMessage(); + break; + } + } + + if ( !error ) error = TheText.Get("FES_GME"); // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(error, X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(120.0f), YF(30.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + } + } +} + +void +TriggerSaveZone_SaveSlots(CMenuMultiChoiceTwoLinesTriggered *widget) +{ + if ( widget ) + { + if ( widget->GetMenuSelection() > 0 ) + { + MemoryCardSlotSelected = widget->GetMenuSelection() - 1; + + switch ( TheMemoryCard.GetInfoOnSpecificSlot(MemoryCardSlotSelected) ) + { + case CMemoryCard::SLOT_PRESENT: + case CMemoryCard::SLOT_CORRUPTED: + { + // Proceed with overwriting this saved game? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QO"), X(-40.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSaveZone_DeleteSaveGame, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + bMemoryCardSpecialZone = false; + pActiveMenuPage = &MenuPageSaveZone_QuestionYesNo; + break; + } + + case CMemoryCard::SLOT_NOTPRESENT: + { + // PROCEED WITH SAVE ? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QS"), X(-40.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSaveZone_SaveGame, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + bMemoryCardSpecialZone = false; + pActiveMenuPage = &MenuPageSaveZone_QuestionYesNo; + break; + } + } + } + } +} + +void +TriggerSaveZone_SaveGameSelect(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + FillMenuWithMemCardFileListing(&MenuSaveZoneSSL_1, TriggerSaveZone_BackToMainMenuTwoLines, TriggerSaveZone_SaveSlots, nil, 0, 34, 22); + + if ( TheMemoryCard.GetError() == CMemoryCard::ERR_NOFORMAT) + { + gErrorSampleTriggered = false; + pActiveMenuPage = &MenuPageSaveZone_FormatCard; + } + else + { + bMemoryCardSpecialZone = true; + bIgnoreTriangleButton = true; + pActiveMenuPage = &MenuPageSaveZone_SaveSlots; + } + + pActiveMenuPage->ActivatePage(); + } +} + +void +TriggerControls_Vibrations(CMenuOnOffTriggered *widget) +{ + if ( widget ) + { + CMenuManager::m_PrefsUseVibration = widget->GetMenuSelection(); + if ( CMenuManager::m_PrefsUseVibration ) + { + CPad::GetPad(0)->StartShake(300, 150); + TimeToStopPadShaking = CTimer::GetTimeInMillisecondsPauseMode() + 500; + } + } +} + +void +TriggerControls_ContrDisplay(CMenuMultiChoiceTriggeredAlways *widget) +{ + if ( widget ) + { + int32 conf = MenuControls_1.GetMenuSelection(); + int32 i = MenuControls_2.GetMenuSelection(); + if ( i == 1 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_7; + else + MenuPage_Controls.m_controls[0] = &MenuControls_4; + } + else if ( i == 0 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_6; + else + MenuPage_Controls.m_controls[0] = &MenuControls_3; + } + } +} + +void +TriggerControls_DrawHNContrConfig(CMenuMultiChoiceTriggeredAlways *widget) +{ + if ( widget ) + { + int32 conf = widget->GetMenuSelection(); + + InitialiseTextsInMenuControllerOnFoot(&MenuControls_3, (CMenuManager::CONTRCONFIG)conf); + InitialiseTextsInMenuControllerInCar (&MenuControls_4, (CMenuManager::CONTRCONFIG)conf); + + int32 i = MenuControls_2.GetMenuSelection(); + if ( i == 1 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_7; + else + MenuPage_Controls.m_controls[0] = &MenuControls_4; + } + else if ( i == 0 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_6; + else + MenuPage_Controls.m_controls[0] = &MenuControls_3; + } + } +} + +void +TriggerControls_DrawContrConfig(CMenuMultiChoiceTriggeredAlways *widget) +{ + if ( widget ) + { + int32 conf = widget->GetMenuSelection(); + if ( widget->m_cursor != -1 ) + conf = widget->m_cursor; + + InitialiseTextsInMenuControllerOnFoot(&MenuControls_3, (CMenuManager::CONTRCONFIG)conf); + InitialiseTextsInMenuControllerInCar(&MenuControls_4, (CMenuManager::CONTRCONFIG)conf); + + int32 i = MenuControls_2.GetMenuSelection(); + if ( i == 1 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_7; + else + MenuPage_Controls.m_controls[0] = &MenuControls_4; + } + else if ( i == 0 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_6; + else + MenuPage_Controls.m_controls[0] = &MenuControls_3; + } + } +} + +void +TriggerControls_ContrConfig(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + int32 conf = widget->GetMenuSelection(); + + InitialiseTextsInMenuControllerOnFoot(&MenuControls_3, (CMenuManager::CONTRCONFIG)conf); + InitialiseTextsInMenuControllerInCar(&MenuControls_4, (CMenuManager::CONTRCONFIG)conf); + + int32 i = MenuControls_2.GetMenuSelection(); + if ( i == 1 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_7; + else + MenuPage_Controls.m_controls[0] = &MenuControls_4; + } + else if ( i == 0 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_6; + else + MenuPage_Controls.m_controls[0] = &MenuControls_3; + } + } +} + +void +TriggerLanguage_Language(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + if ( CMenuManager::m_PrefsLanguage != widget->GetMenuSelection() ) + { + CMenuManager::m_PrefsLanguage = widget->GetMenuSelection(); + FrontEndMenuManager.m_bInitialised = false; + bFrontEnd_ReloadObrTxtGxt = true; + } + } +} + +void +TriggerAudio_RadioStation(CMenuMultiChoicePicturedTriggered *widget) +{ + if ( widget ) + { + if ( CMenuManager::m_PrefsRadioStation != widget->GetMenuSelection() ) + { + CMenuManager::m_PrefsRadioStation = widget->GetMenuSelection(); + DMAudio.PlayFrontEndTrack(CMenuManager::m_PrefsRadioStation, TRUE); + DMAudio.SetRadioInCar(CMenuManager::m_PrefsRadioStation); + } + } +} + +void +TriggerAudio_StereoMono(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + if (widget->GetMenuSelection() == 1) + { + DMAudio.SetMonoMode(TRUE); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MONO, 0); + } + else + { + DMAudio.SetMonoMode(FALSE); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_STEREO, 0); + } + } +} + +void +TriggerAudio_MusicVolumeAlways(CMenuSliderTriggered *widget) +{ + ; +} + +void +TriggerAudio_SfxVolumeAlways(CMenuSliderTriggered *widget) +{ + if ( widget ) + { + static bool bTriggerTest = false; + + CMenuManager::m_PrefsSfxVolume = float(widget->GetMenuSelection()) / 100.0f * 127.0f + 0.5f; + + if ( CMenuManager::m_PrefsSfxVolume == 102 && !CPad::GetPad(0)->GetDPadLeft()&& !CPad::GetPad(0)->GetDPadRight() ) + { + if ( bTriggerTest ) + { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_AUDIO_TEST, 0); + bTriggerTest = false; + } + } + else + bTriggerTest = true; + + FrontEndMenuManager.SetSoundLevelsForMusicMenu(); + } +} + +void +TriggerAudio_MusicVolume(CMenuSliderTriggered *widget) +{ + if ( widget ) + { + CMenuManager::m_PrefsMusicVolume = float(widget->GetMenuSelection()) / 100.0f * 127.0f + 0.5f; + FrontEndMenuManager.SetSoundLevelsForMusicMenu(); + } +} + +void +TriggerAudio_SfxVolume(CMenuSliderTriggered *widget) +{ + ; +} + +void +TriggerSave_NewGameNewGame(CMenuMultiChoiceTriggered *widget) +{ + FrontEndMenuManager.m_bWantToRestart = true; + FrontEndMenuManager.m_bMenuActive = false; + FrontEndMenuManager.m_bInSaveZone = false; + bIgnoreTriangleButton = false; + + CTimer::EndUserPause(); + + FrontEndMenuManager.AnaliseMenuContents(); + + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); +} + +void +TriggerSave_NewGameSelectYes(CMenuMultiChoiceTriggered *widget) +{ + // Are you sure you want to start a new game? All progress since the last save game will be lost. Proceed? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QR"), X(-100.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(30.0f), TriggerSave_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), YF(10.0f), TriggerSave_NewGameNewGame, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + pMenuSave = &MenuPageSaveZone_QuestionYesNo; + bIgnoreTriangleButton = true; +} + +void +TriggerSave_DeleteGameDeleteGame(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + + if ( !MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.SetTrigger(TriggerSave_DeleteGameDeleteGame, widget); + else + { + // Deleting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + DisplayMemoryCardAccessMsg(TheText.Get("FEDL_WR"), CRGBA(200, 50, 50, 192)); + + TheMemoryCard.DeleteSlot(MemoryCardSlotSelected); + + if ( TheMemoryCard.GetError() != CMemoryCard::NO_ERR_SUCCESS) + { + // Deleting Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FES_DEE"), X(-80.0f), YF(20.0f), TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(15.0f), TriggerSave_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pMenuSave = &MenuPageSaveZone_Message; + + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = true; + } + else + { + FillMenuWithMemCardFileListing(&MenuSaveLG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_LoadGameLoadGameSelect, nil, 0, 34, 22); + FillMenuWithMemCardFileListing(&MenuSaveDG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_DeleteGameDeleteGameSelect, nil, 0, 34, 22); + + pMenuSave = &MenuPage_SaveBasic; + pMenuSave->ActivatePage(); + } + } + } +} + +void +TriggerSave_DeleteGameDeleteGameSelect(CMenuMultiChoiceTwoLinesTriggered *widget) +{ + if ( widget ) + { + if ( widget->GetMenuSelection() > 0 ) + { + MemoryCardSlotSelected = widget->GetMenuSelection() - 1; + + switch ( TheMemoryCard.GetInfoOnSpecificSlot(MemoryCardSlotSelected) ) + { + case CMemoryCard::SLOT_NOTPRESENT: + { + break; + } + case CMemoryCard::SLOT_CORRUPTED: + case CMemoryCard::SLOT_PRESENT: + { + // Proceed with deleting this saved game? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QD"), X(-40.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(20.0f), TriggerSave_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSave_DeleteGameDeleteGame, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + pMenuSave = &MenuPageSaveZone_QuestionYesNo; + bMemoryCardSpecialZone = false; + break; + } + } + } + } +} + +void +TriggerSave_DeleteGameSelect(CMenuMultiChoiceTriggered *widget) +{ + FillMenuWithMemCardFileListing(&MenuSaveDG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_DeleteGameDeleteGameSelect, nil, 0, 34, 22); + FillMenuWithMemCardFileListing(&MenuSaveLG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_LoadGameLoadGameSelect, nil, 0, 34, 22); + + pMenuSave = &MenuPage_SaveDeleteGame; + pMenuSave->ActivatePage(); + + gErrorSampleTriggered = false; + bMemoryCardSpecialZone = true; + bIgnoreTriangleButton = true; +} + +void +TriggerSave_LoadGameLoadGame(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + + if ( !MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.SetTrigger(TriggerSave_LoadGameLoadGame, widget); + else + { + // Loading data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + DisplayMemoryCardAccessMsg(TheText.Get("FELD_WR"), CRGBA(200, 50, 50, 192)); + TheMemoryCard.LoadSlotToBuffer(MemoryCardSlotSelected); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS) + { + FrontEndMenuManager.m_bWantToRestart = true; + FrontEndMenuManager.AnaliseMenuContents(); + FrontEndMenuManager.m_bMenuActive = false; + FrontEndMenuManager.m_bInSaveZone = false; + + CTimer::EndUserPause(); + + TheMemoryCard.m_bWantToLoad = true; + + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); + } + else + { + // Load Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FES_LOE"), X(-80.0f), YF(20.0f), TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(25.0f), TriggerSave_BackToMainMenu, 0, 0); + + pMenuSave = &MenuPageSaveZone_Message; + pMenuSave->ActivatePage(); + + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = true; + } + } + } +} + +void +TriggerSave_LoadGameLoadGameSelect(CMenuMultiChoiceTwoLinesTriggered *widget) +{ + if ( widget ) + { + if ( widget->GetMenuSelection() > 0 ) + { + MemoryCardSlotSelected = widget->GetMenuSelection() - 1; + + switch ( TheMemoryCard.GetInfoOnSpecificSlot(MemoryCardSlotSelected) ) + { + case CMemoryCard::SLOT_NOTPRESENT: + { + break; + } + case CMemoryCard::SLOT_CORRUPTED: + { + // Load Failed. + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FES_LOF"), X(50.0f), YF(20.0f), TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), 0.0f, TriggerSave_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pMenuSave = &MenuPageSaveZone_Message; + bMemoryCardSpecialZone = false; + break; + } + case CMemoryCard::SLOT_PRESENT: + { + // All unsaved progress in your current game will be lost. Proceed with loading? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QL"), X(-40.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(20.0f), TriggerSave_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSave_LoadGameLoadGame, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + pMenuSave = &MenuPageSaveZone_QuestionYesNo; + bMemoryCardSpecialZone = false; + break; + } + } + } + } +} + +void +TriggerSave_LoadGameSelect(CMenuMultiChoiceTriggered *widget) +{ + FillMenuWithMemCardFileListing(&MenuSaveLG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_LoadGameLoadGameSelect, nil, 0, 34, 22); + FillMenuWithMemCardFileListing(&MenuSaveDG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_DeleteGameDeleteGameSelect, nil, 0, 34, 22); + + pMenuSave = &MenuPage_SaveLoadGame; + pMenuSave->ActivatePage(); + + gErrorSampleTriggered = false; + bMemoryCardSpecialZone = true; + bIgnoreTriangleButton = true; +} + +void +TriggerSave_BackToMainMenu(CMenuMultiChoiceTriggered *widget) +{ + pMenuSave = &MenuPage_SaveBasic; + pMenuSave->ActivatePage(); + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; +} + +void InitialiseTextsInMenuControllerInCar(CMenuPictureAndText *widget, CMenuManager::CONTRCONFIG cont) +{ + if ( widget ) + { + widget->m_numTexts = 0; + + switch ( cont ) + { + case CMenuManager::CONFIG_1: + { + widget->AddText(TheText.Get("FEC_LL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_RSC"), X(-4.0f), Y(29.0f), PAD_TEXT_COLOR, true); // 33.142860f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_HO3"), X(84.0f), Y(162.0f), PAD_TEXT_COLOR, false); // 185.142868f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_LB"), X(68.0f), Y(-6.0f), PAD_TEXT_COLOR, false); // -6.857143f + widget->AddText(TheText.Get("FEC_LR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_HAB"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_BRA"), X(155.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_EXV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_CAW"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_ACC"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_TUC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_SM3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + + break; + } + + case CMenuManager::CONFIG_2: + { + widget->AddText(TheText.Get("FEC_LL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_HOR"), X(-4.0f), Y(29.0f), PAD_TEXT_COLOR, true); // 33.142860f + widget->AddText(TheText.Get("FEC_CAM"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_NA"), X(84.0f), Y(162.0f), PAD_TEXT_COLOR, false); // 185.142868f + widget->AddText(TheText.Get("FEC_RSC"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_LB"), X(68.0f), Y(-6.0f), PAD_TEXT_COLOR, false); // -6.857143f + widget->AddText(TheText.Get("FEC_LR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_HAB"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_BRA"), X(155.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_EXV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_CAW"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_ACC"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_TUC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_SM3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + + break; + } + + case CMenuManager::CONFIG_3: + { + widget->AddText(TheText.Get("FEC_LL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_EXV"), X(-4.0f), Y(29.0f), PAD_TEXT_COLOR, true); // 33.142860f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_RS3"), X(84.0f), Y(162.0f), PAD_TEXT_COLOR, false); // 185.142868f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_LB"), X(68.0f), Y(-6.0f), PAD_TEXT_COLOR, false); // -6.857143f + widget->AddText(TheText.Get("FEC_LR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_HOR"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_BRA"), X(155.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_HAB"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_CAW"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_ACC"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_TUC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_SM3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + + break; + } + + case CMenuManager::CONFIG_4: + { + widget->AddText(TheText.Get("FEC_LL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_HAB"), X(-4.0f), Y(29.0f), PAD_TEXT_COLOR, true); // 33.142860f + widget->AddText(TheText.Get("FEC_TUC"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_HO3"), X(84.0f), Y(162.0f), PAD_TEXT_COLOR, false); // 185.142868f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_LB"), X(68.0f), Y(-6.0f), PAD_TEXT_COLOR, false); // -6.857143f + widget->AddText(TheText.Get("FEC_LR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_CAW"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_SMT"), X(155.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_EXV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_RSC"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_NA"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_ACC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_BRA"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + + break; + } + } + } +} + +void InitialiseTextsInMenuControllerOnFoot(CMenuPictureAndText *widget, CMenuManager::CONTRCONFIG cont) +{ + if ( widget ) + { + widget->m_numTexts = 0; + + + switch ( cont ) + { + case CMenuManager::CONFIG_1: + { + widget->AddText(TheText.Get("FEC_CWL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_LOF"), X(-4.0f), Y(25.0f), PAD_TEXT_COLOR, true); // 28.571430f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_CWR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_TAR"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_JUM"), X(144.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_ENV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_ATT"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_RUN"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_FPC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_LB3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + widget->AddText(TheText.Get("FEC_R3"), X(238.0f), Y(122.0f), PAD_TEXT_COLOR, false); // 139.428574f + + break; + } + + case CMenuManager::CONFIG_2: + { + widget->AddText(TheText.Get("FEC_CWL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_LOF"), X(-4.0f), Y(25.0f), PAD_TEXT_COLOR, true); // 28.571430f + widget->AddText(TheText.Get("FEC_CAM"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_NA"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_CWR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_TAR"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_JUM"), X(144.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_ENV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_ATT"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_RUN"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_FPC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_LB3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + widget->AddText(TheText.Get("FEC_R3"), X(238.0f), Y(122.0f), PAD_TEXT_COLOR, false); // 139.428574f + + break; + } + + case CMenuManager::CONFIG_3: + { + widget->AddText(TheText.Get("FEC_CWL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_ENV"), X(-4.0f), Y(25.0f), PAD_TEXT_COLOR, true); // 28.571430f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_CWR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_TAR"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_JUM"), X(144.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_LOF"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_RUN"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_ATT"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_FPC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_LB3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + widget->AddText(TheText.Get("FEC_R3"), X(238.0f), Y(122.0f), PAD_TEXT_COLOR, false); // 139.428574f + + break; + } + + case CMenuManager::CONFIG_4: + { + widget->AddText(TheText.Get("FEC_CWL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_TAR"), X(-4.0f), Y(25.0f), PAD_TEXT_COLOR, true); // 28.571430f + widget->AddText(TheText.Get("FEC_NA"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_CWR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_ATT"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_JUM"), X(144.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_ENV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_LOF"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_RUN"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_FPC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_LB3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + widget->AddText(TheText.Get("FEC_R3"), X(238.0f), Y(122.0f), PAD_TEXT_COLOR, false); // 139.428574f + + break; + } + } + } +} + +void +TriggerSaveZone_BackToMainMenuTwoLines(CMenuMultiChoiceTwoLinesTriggered *widget) +{ + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + pActiveMenuPage = &MenuPageSaveZone_SaveGame; +} + +void +TriggerSave_BackToMainMenuTwoLines(CMenuMultiChoiceTwoLinesTriggered *widget) +{ + pMenuSave = &MenuPage_SaveBasic; + pMenuSave->ActivatePage(); + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; +} + +void +SetRandomActiveTextlineColor(uint8 bText) +{ + if ( bMemoryCardSpecialZone ) + rgbaATC = SELECTED_TEXT_COLOR; + else + { + bool bSelected = false; + bool bHighlignted = false; + + switch ( FrontEndMenuManager.m_pageState ) + { + case PAGESTATE_NORMAL: + break; + case PAGESTATE_HIGHLIGHTED: + bHighlignted = true; + break; + case PAGESTATE_SELECTED: + bSelected = true; + break; + } + + if ( FrontEndMenuManager.m_bInSaveZone ) + bSelected = true; + + if ( bSelected || bText ) + { + static uint32 delayTime = 0; + static bool bAddVal = true; + + if ( delayTime < CTimer::GetTimeInMillisecondsPauseMode() ) + { + delayTime = CTimer::GetTimeInMillisecondsPauseMode() + 200; + + if ( bAddVal ) + rgbaATC = TEXT_COLOR; + else + rgbaATC = SELECTED_TEXT_COLOR; + + bAddVal = !bAddVal; + } + } + + if ( bHighlignted ) + { + static uint32 delayTime = 0; + static bool bAddVal = true; + + if ( delayTime < CTimer::GetTimeInMillisecondsPauseMode() ) + { + delayTime = CTimer::GetTimeInMillisecondsPauseMode() + 200; + + if ( bAddVal ) + rgbaATC = TITLE_TEXT_COLOR; + else + rgbaATC = MENU_SELECTED_COLOR; + + bAddVal = !bAddVal; + } + } + } +} + +#ifdef GTA_PC + +void +TriggerDisplay_Trails(CMenuOnOffTriggered *widget) +{ + if ( widget ) + { + CMenuManager::m_PrefsShowTrails = widget->GetMenuSelection(); + CMBlur::BlurOn = CMenuManager::m_PrefsShowTrails; + + if ( CMBlur::BlurOn ) + CMBlur::MotionBlurOpen(Scene.camera); + else + CMBlur::MotionBlurClose(); + } +} + +#endif \ No newline at end of file diff --git a/src/core/Frontend_PS2.cpp b/src/core/Frontend_PS2.cpp new file mode 100644 index 0000000..1da15fb --- /dev/null +++ b/src/core/Frontend_PS2.cpp @@ -0,0 +1,3033 @@ +#include "common.h" +#ifdef PS2_MENU +#include "platform.h" +#include "main.h" +#include "Timer.h" +#include "Pad.h" +#include "Sprite2d.h" +#include "Text.h" +#include "Font.h" +#include "Hud.h" +#include "MBlur.h" +#include "DMAudio.h" +#include "Streaming.h" +#include "Camera.h" +#include "Credits.h" +#include "General.h" +#include "TxdStore.h" +#include "FileMgr.h" +#include "Messages.h" +#include "Frontend_PS2.h" +#include "Stats.h" +#include "Game.h" +#include "World.h" +#include "PlayerInfo.h" +#include "FrontEndControls.h" +#include "MemoryCard.h" + +#define CRect_SZ(x, y, w, h) CRect(x, y, x+w, y+h) + +wchar MemoryCard_FileNames[8][100+1]; +CMenuManager FrontEndMenuManager; + +// TEMP: put into header +bool DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +bool DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +void DoRWStuffEndOfFrame(void); + + +#define SCRW SCREEN_WIDTH +#define SCRH SCREEN_HEIGHT +//#define X SCREEN_STRETCH_X +//#define Y SCREEN_STRETCH_Y +#define X SCREEN_SCALE_X +#define Y SCREEN_SCALE_Y + +#define YF(x) Y(float(x)*(float(DEFAULT_SCREEN_HEIGHT)/float(SCREEN_HEIGHT_PAL))) +//#define X(x) ((x)/640.0f*SCRW) +//#define Y(y) ((y)/448.0f*SCRH) + + +static float MENU_TEXT_SIZE_X = 0.644f; +static float MENU_TEXT_SIZE_Y = 0.84f; //0.96f; +float BUTTONTAB_TEXT_SIZE_X = 0.35f; +float BUTTONTAB_TEXT_SIZE_Y = 0.7f; //0.8f; +float PANEL_TEXT_SIZE_X = 0.8f; +float PANEL_TEXT_SIZE_Y = 1.2f; //0.96f/0.7f; //?? +float MEMCARD_ACCESS_MSG_SIZE_X = 0.84f; +float MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f; //1.28f; + +CRGBA SELECTED_TEXT_COLOR(255, 182, 48, 255); +CRGBA BACKGROUND_SPLASH_COLOR(48, 48, 48, 255); + +CVector2D CONTR_DESCR_NEW_TEXTSCALE(0.4564f, 0.63f); // 0.72 +CVector2D CONFIGS_NEW_TEXTSCALE(0.49f, 0.7f); // 0.8 +CVector2D AUDIO_OUTPUT_POS(0.0f, 0.0f); +CVector2D AUDIO_RSTATION_POS(154.0f, 0.0f); +CVector2D DISPLAY_BRIGHTNESS_POS(0.0f, 0.0f); + +CRGBA TEXT_COLOR(150, 110, 30, 255); +CRGBA PAD_TEXT_COLOR(200, 200, 200, 255); +CRGBA CRIM_RATING_TEXT_COLOR(255, 182, 48, 255); +CRGBA SCROLL_TEXT_COLOR(150, 110, 30, 255); +CRGBA TITLE_TEXT_COLOR(170, 130, 50, 255); +CRGBA TEXT_SHADOW_COLOR(0, 0, 0, 255); +CVector2D SHADOW_VECTOR(1.0f, 1.0f); +CRGBA SLIDER_RIGHT_COLOR(20, 94, 136, 255); +CRGBA SLIDER_LEFT_COLOR(86, 196, 255, 255); +CRGBA MENU_SELECTED_COLOR(255, 212, 88, 255); +CRGBA rgbaATC(96, 96, 96, 255); // active text color. not constant + +float BUTTONTAB_TEXT_X_SCALES[NUM_PAGES] = { 1.0f }; +float PANEL_TEXT_X_SCALES[NUM_PAGES] = { 1.0f }; + +int32 MemoryCardSlotSelected; +uint32 TimeToStopPadShaking; +bool bFrontEnd_ReloadObrTxtGxt; + +bool bMemoryCardStartUpMenus_ExitNow; + +extern CMenuPage MenuPage_SaveBasic; +CMenuPage *pActiveMenuPage; +CMenuPage *pMenuSave = &MenuPage_SaveBasic; +bool bMemoryCardSpecialZone; +bool bIgnoreTriangleButton; +bool gErrorSampleTriggered; + +bool gMusicPlaying; + +CMenuPage MenuPage_Stats; + CMenuLineLister MenuStats_1; + CMenuPictureAndText MenuStats_2; // criminal rating +CMenuPage MenuPage_Briefs; + CMenuPictureAndText MenuBriefs_1; + CMenuDummy MenuBriefs_2; +CMenuPage MenuPage_SaveBasic; + CMenuMultiChoiceTriggered MenuSaveB_1; // "Load Game", "Delete Game", "New Game" +CMenuPage MenuPage_SaveNewGame; + CMenuPictureAndText MenuSaveNG_1; // "Load Game", "Delete Game", "New Game" + CMenuMultiChoiceTriggered MenuSaveNG_2; // "No", "Yes" +CMenuPage MenuPage_SaveLoadGame; + CMenuPictureAndText MenuSaveLG_1; // "Load Game", "Delete Game", "New Game" + CMenuMultiChoiceTwoLinesTriggered MenuSaveLG_2; // save games +CMenuPage MenuPage_SaveDeleteGame; + CMenuPictureAndText MenuSaveDG_1; // "Load Game", "Delete Game", "New Game" + CMenuMultiChoiceTwoLinesTriggered MenuSaveDG_2; // save games +CMenuPage MenuPage_Controls; + CMenuPictureAndText MenuControls_3; // controller images + CMenuPictureAndText MenuControls_6; + CMenuPictureAndText MenuControls_4; + CMenuPictureAndText MenuControls_7; + CMenuMultiChoiceTriggeredAlways MenuControls_1; // "Configuration:" "Setup1", "Setup2", "Setup3", "Setup4" + CMenuMultiChoiceTriggered MenuControls_2; // "Controller Display:" "On Foot", "In Car" + CMenuOnOffTriggered MenuControls_5; // "Vibration:" +CMenuPageAnyMove MenuPage_Audio; + CMenuSliderTriggered MenuAudio_1; // "Music Volume" + CMenuMultiChoiceTriggered MenuAudio_4; // "Output:" "Stereo", "Mono" + CMenuSliderTriggered MenuAudio_2; // "SFX Volume" + CMenuMultiChoicePicturedTriggeredAnyMove MenuAudio_3; // "Radio station select:" +CMenuPage MenuPage_Display; + CMenuSlider MenuDisplay_1; // "Brightness" +#ifdef GTA_PC + CMenuOnOffTriggered MenuDisplay_2; // "Trails:" +#else + CMenuOnOff MenuDisplay_2; // "Trails:" +#endif + CMenuOnOff MenuDisplay_3; // "Subtitles:" + CMenuOnOff MenuDisplay_4; // "Wide Screen:" +CMenuPage MenuPage_Language; + CMenuMultiChoiceTriggered MenuLanguage_1; // "English", "French", "German", "Italian", "Spanish" + +CMenuPage MenuPageSaveZone_SaveGame; + CMenuMultiChoiceTriggered MenuSaveZoneSG_1; // "Save game", "Cancel" +CMenuPage MenuPageSaveZone_SaveSlots; + CMenuMultiChoiceTwoLinesTriggered MenuSaveZoneSSL_1; // "Cancel" +CMenuPage MenuPageSaveZone_SavedSuccessfully; + CMenuPictureAndText MenuSaveZoneSS_1; // "Game saved successfully!" "Your saved filename is:" + CMenuMultiChoiceTriggered MenuSaveZoneSS_2; // "Quit" +CMenuPage MenuPageSaveZone_Message; + CMenuPictureAndText MenuSaveZoneMSG_1; // "Save Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again." + CMenuMultiChoiceTriggered MenuSaveZoneMSG_2; // "OK" +CMenuPage MenuPageSaveZone_QuestionYesNo; + CMenuPictureAndText MenuSaveZoneQYN_1; // "Save Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again." + CMenuMultiChoiceTriggered MenuSaveZoneQYN_2; // "Yes", "No" +CMenuPage MenuPageSaveZone_FormatCard; + CMenuMultiChoiceTriggered MenuSaveZoneFC_1; // "Memory card (PS2) in MEMORY CARD slot 1 is unformatted. Would you like to format memory card (PS2) in MEMORY CARD slot 1?" "No" "Yes" +CMenuPage MenuPageSaveZone_ErrorFormat; + CMenuMultiChoiceTriggered MenuSaveZoneEF_1; // "Format Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again." "OK" + + +VALIDATE_SIZE(CPlaceableText, 0x10); +VALIDATE_SIZE(CPlaceableShText, 0x20); +VALIDATE_SIZE(CPlaceableShTextTwoLines, 0x30); +VALIDATE_SIZE(CPlaceableShOption, 0x28); +VALIDATE_SIZE(CPlaceableShOptionTwoLines, 0x38); +VALIDATE_SIZE(CPlaceableSprite, 0x18); +VALIDATE_SIZE(CPlaceableShSprite, 0x34); +VALIDATE_SIZE(CMenuMultiChoice, 0x2CC); +VALIDATE_SIZE(CMenuMultiChoiceTriggered, 0x310); +VALIDATE_SIZE(CMenuMultiChoiceTwoLines, 0x3CC); +VALIDATE_SIZE(CMenuOnOff, 0x90); + +#include "FrontendTriggers.h" + +static const char* FrontendFilenames[][2] = +{ + {"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", "" }, +}; + +int32 CMenuManager::m_PrefsSfxVolume = 102; +int32 CMenuManager::m_PrefsMusicVolume = 102; +int32 CMenuManager::m_PrefsBrightness = 256; +bool CMenuManager::m_PrefsShowTrails = true; +bool CMenuManager::m_PrefsShowSubtitles = true; +bool CMenuManager::m_PrefsAllowNastyGame = true; + +int32 CMenuManager::m_PrefsRadioStation = 0; +int32 CMenuManager::m_PrefsStereoMono = 0; +int8 CMenuManager::m_PrefsUseWideScreen = 0; +int32 CMenuManager::m_PrefsLanguage = 0; +CMenuManager::CONTRCONFIG CMenuManager::m_PrefsControllerConfig = CONFIG_1; +bool CMenuManager::m_PrefsUseVibration = false; + + +#ifdef GTA_PC +#include "PlayerSkin.h" +int32 CMenuManager::OS_Language = 0; +int8 CMenuManager::m_PrefsVsync = 1; +int8 CMenuManager::m_PrefsVsyncDisp = 1; +int8 CMenuManager::m_PrefsFrameLimiter = 1; +int8 CMenuManager::m_PrefsSpeakers; +int32 CMenuManager::m_ControlMethod = CONTROL_CLASSIC; +int8 CMenuManager::m_PrefsDMA = 1; +float CMenuManager::m_PrefsLOD = 1.0f; +char CMenuManager::m_PrefsSkinFile[256] = DEFAULT_SKIN_NAME; + +#ifndef MASTER +bool CMenuManager::m_PrefsMarketing; +bool CMenuManager::m_PrefsDisableTutorials; +#endif // !MASTER + +#ifdef MENU_MAP +bool CMenuManager::bMenuMapActive; +float CMenuManager::fMapSize; +float CMenuManager::fMapCenterY; +float CMenuManager::fMapCenterX; +#endif + +#endif + + +CMenuManager::CMenuManager(void) +{ + int32 i; + + SetSoundLevelsForMusicMenu(); + + m_pageState = PAGESTATE_NORMAL; + m_currentPage = PAGE_FIRST; + m_newPage = PAGE_FIRST; + m_bMenuActive = false; + m_bSaveMenuActive = false; + m_bRenderGameInMenu = false; + m_bTexturesLoaded = false; + m_nPageLeftTimer = 0; + m_nPageRightTimer = 0; + m_nChangePageTimer = 0; + field_18 = 0; + m_fade = 255; + m_someAlpha = 255; + m_position.x = 0.0f; + m_position.y = 0.0f; + m_nSlidingDir = SLIDE_TO_BOTTOM; + m_nStartPauseTimer = 0; + m_nEndPauseTimer = 0; + m_bInitialised = false; + m_bWantToUpdateContent = false; + field_3C = 0; + m_bInSaveZone = false; + + for(i = 0; i < NUM_PAGES; i++){ + BUTTONTAB_TEXT_X_SCALES[i] = 1.0f; + PANEL_TEXT_X_SCALES[i] = 1.0f; + } + +#ifdef GTA_PC + TheCamera.m_bUseMouse3rdPerson = m_ControlMethod == CONTROL_STANDARD; + CMBlur::BlurOn = m_PrefsShowTrails; +#endif +} + +void +CMenuManager::LoadAllTextures(void) +{ + int32 i; + + if(m_bTexturesLoaded) + return; + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); + DMAudio.Service(); + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + CSprite2d *splash = LoadSplash(nil); + if(splash) + splash->Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + else // doesn't exist!! + CHud::Sprites[19].Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERMIPNEAREST); + DoRWStuffEndOfFrame(); + + CFileMgr::SetDir(""); + CFileMgr::SetDir(""); + + CTimer::Stop(); + CStreaming::MakeSpaceFor(60*1024); + CStreaming::ImGonnaUseStreamingMemory(); + CGame::TidyUpMemory(false, true); + int32 slot = CTxdStore::FindTxdSlot("frontend"); + if(slot == -1) + slot = CTxdStore::AddTxdSlot("frontend"); + printf("LOAD frontend\n"); + CTxdStore::LoadTxd(slot, "MODELS/FRONTEND.TXD"); + CTxdStore::SetCurrentTxd(slot); + CStreaming::IHaveUsedStreamingMemory(); + CTimer::Update(); + + for(i = 0; i < NUM_SPRIRES; i++) + { + m_sprites[i].SetTexture(FrontendFilenames[i][0], FrontendFilenames[i][1]); + m_sprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } + + m_bTexturesLoaded = true; +} + +void +CMenuManager::UnloadTextures(void) +{ + int32 slot; + int32 i; + + if ( !m_bTexturesLoaded ) + return; + + slot = CTxdStore::FindTxdSlot("frontend"); +#ifdef FIX_BUGS + for(i = 0; i < NUM_SPRIRES; i++) + m_sprites[i].Delete(); +#endif + + printf("REMOVE frontend\n"); + CTxdStore::RemoveTxd(slot); + m_bTexturesLoaded = false; +} + +void +CMenuManager::InitialiseMenusOnce(void) +{ + if(m_bInitialised) + return; + m_bInitialised = true; + + InitialiseChangedLanguageSettings(); + + // Normal menu + MenuPage_Stats.Initialise(); + MenuPage_Briefs.Initialise(); + MenuPage_SaveBasic.Initialise(); + MenuPage_SaveNewGame.Initialise(); + MenuPage_SaveLoadGame.Initialise(); + MenuPage_SaveDeleteGame.Initialise(); + MenuPage_Controls.Initialise(); + MenuPage_Audio.Initialise(); + MenuPage_Display.Initialise(); + MenuPage_Language.Initialise(); + + // Save menu + MenuPageSaveZone_SaveGame.Initialise(); + MenuPageSaveZone_SaveSlots.Initialise(); + MenuPageSaveZone_SavedSuccessfully.Initialise(); + MenuPageSaveZone_Message.Initialise(); + MenuPageSaveZone_QuestionYesNo.Initialise(); + MenuPageSaveZone_FormatCard.Initialise(); + MenuPageSaveZone_ErrorFormat.Initialise(); + + /* Stats */ + + MenuStats_1.ResetNumberOfTextLines(); + MenuStats_1.SetPosition(X(75.0f), Y(70.0f)); + MenuStats_1.m_width = X(480.0f); + MenuStats_1.m_height = Y(274.0f); + MenuStats_1.field_10E8 = 0; // unknown + MenuStats_1.m_lineSpacing = Y(20.0f); + MenuStats_1.m_scrollSpeed = 1.0f; + MenuStats_1.SetLinesColor(SCROLL_TEXT_COLOR); + MenuStats_1.ResetNumberOfTextLines(); + MenuPage_Stats.AddMenu(&MenuStats_1); + MenuStats_2.SetPosition(X(75.0f), Y(50.0f)); + MenuStats_2.SetTextsColor(CRIM_RATING_TEXT_COLOR); + MenuPage_Stats.AddMenu(&MenuStats_2); + MenuPage_Stats.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Stats.ActivatePage(); + + + CVector2D saveGameTextScale(X(0.49f), Y(0.7f)); + CVector2D defaultTextScale(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + + /* Basic Load/Delete/New Game */ + + MenuSaveB_1.m_numOptions = 0; + MenuSaveB_1.SetPosition(X(220.0f), Y(110.0f)); + MenuSaveB_1.AddOption(TheText.Get("FES_LGA"), 0.0f, Y(20.0f), TriggerSave_LoadGameSelect, false, true); + MenuSaveB_1.AddOption(TheText.Get("FES_DGA"), 0.0f, Y(40.0f), TriggerSave_DeleteGameSelect, false, true); + MenuSaveB_1.AddOption(TheText.Get("FES_NGA"), 0.0f, Y(60.0f), TriggerSave_NewGameSelectYes, false, true); + MenuSaveB_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuPage_SaveBasic.AddMenu(&MenuSaveB_1); + MenuPage_SaveBasic.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_SaveBasic.ActivatePage(); + + /* New Game - but unused */ + + MenuSaveNG_1.m_numTexts = 0; + MenuSaveNG_1.SetPosition(X(220.0f), Y(110.0f)); + MenuSaveNG_1.AddText(TheText.Get("FES_LGA"), 0.0f, Y(20.0f), TEXT_COLOR, true); + MenuSaveNG_1.AddText(TheText.Get("FES_DGA"), 0.0f, Y(40.0f), TEXT_COLOR, true); + MenuSaveNG_1.AddText(TheText.Get("FES_NGA"), 0.0f, Y(60.0f), SELECTED_TEXT_COLOR, true); + MenuPage_SaveNewGame.AddMenu(&MenuSaveNG_1); + MenuSaveNG_2.m_numOptions = 0; + MenuSaveNG_2.SetPosition(X(250.0f), Y(170.0f)); + MenuSaveNG_2.AddOption(TheText.Get("FEM_NO"), 0.0f, 0.0f, TriggerSave_BackToMainMenu, false, false); + MenuSaveNG_2.AddOption(TheText.Get("FEM_YES"), 0.0f, Y(20.0f), TriggerSave_NewGameSelectYes, false, false); + MenuSaveNG_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveNG_2.m_defaultCancel = TriggerSave_BackToMainMenu; + MenuPage_SaveNewGame.AddMenu(&MenuSaveNG_2); + MenuPage_SaveNewGame.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_SaveNewGame.ActivatePage(); + + /* Load Game */ + + MenuSaveLG_1.m_numTexts = 0; + MenuSaveLG_1.SetPosition(X(220.0f), Y(110.0f)); + MenuSaveLG_1.AddText(TheText.Get("FES_LGA"), 0.0f, Y(20.0f), SELECTED_TEXT_COLOR, true); + MenuSaveLG_1.AddText(TheText.Get("FES_DGA"), 0.0f, Y(40.0f), TEXT_COLOR, true); + MenuSaveLG_1.AddText(TheText.Get("FES_NGA"), 0.0f, Y(60.0f), TEXT_COLOR, true); + MenuPage_SaveLoadGame.AddMenu(&MenuSaveLG_1); + MenuSaveLG_2.m_numOptions = 0; + MenuSaveLG_2.SetPosition(X(250.0f), Y(60.0f)); + MenuSaveLG_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveLG_2.m_defaultCancel = TriggerSave_BackToMainMenuTwoLines; + MenuSaveLG_2.SetNewOldTextScale(true, saveGameTextScale, defaultTextScale, false); + MenuPage_SaveLoadGame.AddMenu(&MenuSaveLG_2); + MenuPage_SaveLoadGame.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_SaveLoadGame.ActivatePage(); + + /* Delete Game */ + + MenuSaveDG_1.m_numTexts = 0; + MenuSaveDG_1.SetPosition(X(220.0f), Y(110.0f)); + MenuSaveDG_1.AddText(TheText.Get("FES_LGA"), 0.0f, Y(20.0f), TEXT_COLOR, true); + MenuSaveDG_1.AddText(TheText.Get("FES_DGA"), 0.0f, Y(40.0f), SELECTED_TEXT_COLOR, true); + MenuSaveDG_1.AddText(TheText.Get("FES_NGA"), 0.0f, Y(60.0f), TEXT_COLOR, true); + MenuPage_SaveDeleteGame.AddMenu(&MenuSaveDG_1); + MenuSaveDG_2.m_numOptions = 0; + MenuSaveDG_2.SetPosition(X(250.0f), Y(60.0f)); + MenuSaveDG_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveDG_2.m_defaultCancel = TriggerSave_BackToMainMenuTwoLines; + MenuSaveDG_2.SetNewOldTextScale(true, saveGameTextScale, defaultTextScale, false); + MenuPage_SaveDeleteGame.AddMenu(&MenuSaveDG_2); + MenuPage_SaveDeleteGame.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_SaveDeleteGame.ActivatePage(); + + + CVector2D briefsTextScale(X(0.525f), Y(0.7f)); + CVector2D defaultTextScale1(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + + /* Briefs */ + + MenuBriefs_1.m_numTexts = 0; + MenuBriefs_1.SetPosition(X(60.0f), Y(60.0f)); + MenuBriefs_1.SetTextsColor(TEXT_COLOR); + MenuBriefs_1.SetNewOldTextScale(true, briefsTextScale, defaultTextScale1); + MenuBriefs_1.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x), X(600.0f)); + MenuPage_Briefs.AddMenu(&MenuBriefs_1); + MenuPage_Briefs.AddMenu(&MenuBriefs_2); + MenuPage_Briefs.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Briefs.ActivatePage(); + + + CVector2D defaultTextScale2(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + CVector2D defaultTextScale3(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + CVector2D CONTR_DESCR_NEW_TEXTSCALE_scaled(X(CONTR_DESCR_NEW_TEXTSCALE.x), Y(CONTR_DESCR_NEW_TEXTSCALE.y)); + CVector2D CONFIGS_NEW_TEXTSCALE_scaled(X(CONFIGS_NEW_TEXTSCALE.x), Y(CONFIGS_NEW_TEXTSCALE.y)); + + /* Controls */ + + MenuControls_3.m_numTexts = 0; + MenuControls_3.m_numSprites = 0; + MenuControls_3.SetPosition(X(170.0f), Y(88.0f)); + MenuControls_3.AddPicture(&m_sprites[FE_CONTROLLER], + &m_sprites[FE_CONTROLLERSH], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_3.AddPicture(&m_sprites[FE_ARROWS1], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_3.SetNewOldTextScale(true, CONTR_DESCR_NEW_TEXTSCALE_scaled, defaultTextScale2); + InitialiseTextsInMenuControllerOnFoot(&MenuControls_3, CMenuManager::m_PrefsControllerConfig); + MenuControls_3.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuControls_3.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x), X(600.0f)); + MenuPage_Controls.AddMenu(&MenuControls_3); + + MenuControls_6.m_numTexts = 0; + MenuControls_6.m_numSprites = 0; + MenuControls_6.SetPosition(X(170.0f), Y(88.0f)); + MenuControls_6.AddPicture(&m_sprites[FE_CONTROLLER], + &m_sprites[FE_CONTROLLERSH], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_6.AddPicture(&m_sprites[FE_ARROWS3], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_6.SetNewOldTextScale(true, CONTR_DESCR_NEW_TEXTSCALE_scaled, defaultTextScale2); + InitialiseTextsInMenuControllerOnFoot(&MenuControls_6, CMenuManager::CONFIG_2); + MenuControls_6.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuControls_6.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x), X(600.0f)); + + MenuControls_4.m_numTexts = 0; + MenuControls_4.m_numSprites = 0; + MenuControls_4.SetPosition(X(170.0f), Y(88.0f)); + MenuControls_4.AddPicture(&m_sprites[FE_CONTROLLER], + &m_sprites[FE_CONTROLLERSH], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_4.AddPicture(&m_sprites[FE_ARROWS2], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_4.SetNewOldTextScale(true, CONTR_DESCR_NEW_TEXTSCALE_scaled, defaultTextScale2); + InitialiseTextsInMenuControllerInCar(&MenuControls_4, CMenuManager::m_PrefsControllerConfig); + MenuControls_4.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuControls_4.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x), X(600.0f)); + + MenuControls_7.m_numTexts = 0; + MenuControls_7.m_numSprites = 0; + MenuControls_7.SetPosition(X(170.0f), Y(88.0f)); + MenuControls_7.AddPicture(&m_sprites[FE_CONTROLLER], + &m_sprites[FE_CONTROLLERSH], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_7.AddPicture(&m_sprites[FE_ARROWS4], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_7.SetNewOldTextScale(true, CONTR_DESCR_NEW_TEXTSCALE_scaled, defaultTextScale2); + InitialiseTextsInMenuControllerInCar(&MenuControls_7, CMenuManager::CONFIG_2); + MenuControls_7.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuControls_7.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x), X(600.0f)); + + MenuControls_1.m_numOptions = 0; + MenuControls_1.SetPosition(X(284.0f), Y(290.0f)); + MenuControls_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, SELECTED_TEXT_COLOR); + MenuControls_1.SetNewOldTextScale(true, CONFIGS_NEW_TEXTSCALE_scaled, defaultTextScale3, false); + MenuControls_1.AddTitle(TheText.Get("FEC_CCF"), 0.0f, 0.0f, true); + MenuControls_1.AddOption(TheText.Get("FEC_CF1"), X(15.0f), Y(2.0f), TriggerControls_ContrConfig, false, false); + MenuControls_1.AddOption(TheText.Get("FEC_CF2"), X(85.0f), Y(2.0f), TriggerControls_ContrConfig, false, false); + MenuControls_1.AddOption(TheText.Get("FEC_CF3"), X(155.0f), Y(2.0f), TriggerControls_ContrConfig, false, false); + MenuControls_1.AddOption(TheText.Get("FEC_CF4"), X(225.0f), Y(2.0f), TriggerControls_ContrConfig, false, false); + MenuPage_Controls.AddMenu(&MenuControls_1); + MenuControls_1.m_alwaysTrigger = (CMenuMultiChoiceTriggered::Trigger)TriggerControls_DrawContrConfig; + MenuControls_1.m_alwaysHighlightTrigger = (CMenuMultiChoiceTriggered::Trigger)TriggerControls_DrawHNContrConfig; + MenuControls_1.m_alwaysNormalTrigger = (CMenuMultiChoiceTriggered::Trigger)TriggerControls_DrawHNContrConfig; + + MenuControls_2.m_numOptions = 0; + MenuControls_2.SetPosition(X(284.0f), Y(310.0f)); + MenuControls_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, SELECTED_TEXT_COLOR); + MenuControls_2.SetNewOldTextScale(true, CONFIGS_NEW_TEXTSCALE_scaled, defaultTextScale3, false); + MenuControls_2.AddTitle(TheText.Get("FEC_CDP"), 0.0f, 0.0f, true); + MenuControls_2.AddOption(TheText.Get("FEC_ONF"), X(15.0f), Y(2.0f), (CMenuMultiChoiceTriggered::Trigger)TriggerControls_ContrDisplay, false, false); + MenuControls_2.AddOption(TheText.Get("FEC_INC"), X(105.0f), Y(2.0f), (CMenuMultiChoiceTriggered::Trigger)TriggerControls_ContrDisplay, false, false); + MenuPage_Controls.AddMenu(&MenuControls_2); + MenuControls_2.m_bTwoState = true; + MenuControls_2.SetMenuSelection(0); + + MenuControls_5.SetPosition(X(284.0f), Y(330.0f)); + MenuControls_5.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR); + MenuControls_5.SetNewOldTextScale(true, CONFIGS_NEW_TEXTSCALE_scaled, defaultTextScale3, false); + MenuControls_5.AddTitle(TheText.Get("FEC_VIB"), false, 0.0f, 0.0f, true); + MenuControls_5.SetOptionPosition(X(15.0f), Y(2.0f), TriggerControls_Vibrations, false); + MenuPage_Controls.AddMenu(&MenuControls_5); + MenuPage_Controls.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Controls.ActivatePage(); + + + /* Audio */ + + CVector2D audioOutputScale(X(0.49f), Y(0.63f)); + CVector2D defaultTextScale4(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + + FEC_MOVETAB movetab; + MenuAudio_1.SetPosition(X(70.0f), Y(80.0f)); + MenuAudio_1.SetColors(TEXT_COLOR, TEXT_COLOR, SLIDER_LEFT_COLOR, SLIDER_RIGHT_COLOR); + MenuAudio_1.AddTitle(TheText.Get("FEA_MUS"), 0.0f, 0.0f); + MenuAudio_1.AddTickBox(X(15.0f), Y(20.0f), X(150.0f), Y(5.0f), Y(45.0f), TriggerAudio_MusicVolume, TriggerAudio_MusicVolumeAlways); + movetab.right = 1; + movetab.left = 2; + movetab.down = 3; + movetab.up = 3; + MenuPage_Audio.AddMenu(&MenuAudio_1, &movetab); + + MenuAudio_4.m_numOptions = 0; + MenuAudio_4.SetPosition(X(280.0f), Y(80.0f)); + MenuAudio_4.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, SELECTED_TEXT_COLOR); + MenuAudio_4.SetNewOldTextScale(true, audioOutputScale, defaultTextScale4, false); + MenuAudio_4.AddTitle(TheText.Get("FEA_OUT"), X(AUDIO_OUTPUT_POS.x), Y(AUDIO_OUTPUT_POS.y), false); + MenuAudio_4.AddOption(TheText.Get("FEA_ST"), X(-15.0f), Y(30.0f), TriggerAudio_StereoMono, false, false); + MenuAudio_4.AddOption(TheText.Get("FEA_MNO"), X(55.0f), Y(30.0f), TriggerAudio_StereoMono, false, false); + movetab.right = 2; + movetab.left = 0; + movetab.down = 3; + movetab.up = 3; + MenuPage_Audio.AddMenu(&MenuAudio_4, &movetab); + MenuAudio_4.m_bTwoState = true; + + MenuAudio_2.SetPosition(X(410.0f), Y(80.0f)); + MenuAudio_2.SetColors(TEXT_COLOR, TEXT_COLOR, SLIDER_LEFT_COLOR, SLIDER_RIGHT_COLOR); + MenuAudio_2.AddTitle(TheText.Get("FEA_SFX"), 0.0f, 0.0f); + MenuAudio_2.AddTickBox(X(5.0f), Y(20.0f), X(150.0f), Y(5.0f), Y(45.0f), TriggerAudio_SfxVolume, TriggerAudio_SfxVolumeAlways); + movetab.right = 0; + movetab.left = 1; + movetab.down = 3; + movetab.up = 3; + MenuPage_Audio.AddMenu(&MenuAudio_2, &movetab); + + MenuAudio_3.m_numOptions = 0; + MenuAudio_3.SetPosition(X(50.0f), Y(170.0f)); + MenuAudio_3.SetColors(TITLE_TEXT_COLOR, CRGBA(64, 64, 64, 255), CRGBA(250, 250, 250, 255)); + MenuAudio_3.AddTitle(TheText.Get("FEA_RSS"), X(AUDIO_RSTATION_POS.x), Y(AUDIO_RSTATION_POS.y), false); + // first row + movetab.right = 1; + movetab.left = 4; + movetab.down = 5; + movetab.up = 5; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO1], &movetab, 0.0f, Y(18.0f), + CVector2D(X(96.0f), YF(72.0f)), TriggerAudio_RadioStation, false); + movetab.right = 2; + movetab.left = 0; + movetab.down = 6; + movetab.up = 6; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO2], &movetab, X(106.0f), Y(20.0f), + CVector2D(X(79.2f), YF(81.0f)), TriggerAudio_RadioStation, false); + movetab.right = 3; + movetab.left = 1; + movetab.down = 7; + movetab.up = 7; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO5], &movetab, X(210.0f), Y(20.0f), + CVector2D(X(86.4f), YF(72.0f)), TriggerAudio_RadioStation, false); + movetab.right = 4; + movetab.left = 2; + movetab.down = 8; + movetab.up = 8; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO7], &movetab, X(324.0f), Y(5.0f), + CVector2D(X(115.2f), YF(102.0f)), TriggerAudio_RadioStation, false); + movetab.right = 0; + movetab.left = 3; + movetab.down = 8; + movetab.up = 8; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO8], &movetab, X(446.0f), Y(5.0f), + CVector2D(X(102.96f), YF(101.4f)), TriggerAudio_RadioStation, false); + // second row + movetab.right = 6; + movetab.left = 8; + movetab.down = 0; + movetab.up = 0; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO3], &movetab, X(60.0f), Y(96.0f), + CVector2D(X(87.36f), YF(85.8f)), TriggerAudio_RadioStation, false); + movetab.right = 7; + movetab.left = 5; + movetab.down = 1; + movetab.up = 1; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO4], &movetab, X(130.0f), Y(72.0f), + CVector2D(X(129.6f), YF(129.0f)), TriggerAudio_RadioStation, false); + movetab.right = 8; + movetab.left = 6; + movetab.down = 2; + movetab.up = 2; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO6], &movetab, X(284.0f), Y(108.0f), + CVector2D(X(60.0f), YF(60.0f)), TriggerAudio_RadioStation, false); + movetab.right = 5; + movetab.left = 7; + movetab.down = 3; + movetab.up = 3; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO9], &movetab, X(404.0f), Y(85.0f), + CVector2D(X(81.12f), YF(101.4f)), TriggerAudio_RadioStation, false); + movetab.right = 2; + movetab.left = 0; + movetab.down = 1; + movetab.up = 1; + MenuPage_Audio.AddMenu(&MenuAudio_3, &movetab); + MenuPage_Audio.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Audio.ActivatePage(); + + + /* Display */ + + MenuDisplay_1.SetPosition(X(240.0f), Y(140.0f)); + MenuDisplay_1.SetColors(TEXT_COLOR, TEXT_COLOR, SLIDER_LEFT_COLOR, SLIDER_RIGHT_COLOR); + MenuDisplay_1.m_style = 0; // ticks + MenuDisplay_1.AddTitle(TheText.Get("FED_BRI"), X(DISPLAY_BRIGHTNESS_POS.x), Y(DISPLAY_BRIGHTNESS_POS.y)); + MenuDisplay_1.AddTickBox(X(-30.0f), Y(20.0f), X(200.0f), Y(40.0f), Y(40.0f)); + MenuPage_Display.AddMenu(&MenuDisplay_1); + MenuDisplay_2.SetPosition(X(290.0f), Y(240.0f)); + MenuDisplay_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR); + MenuDisplay_2.AddTitle(TheText.Get("FED_TRA"), false, 0.0f, 0.0f, true); +#ifdef GTA_PC + MenuDisplay_2.SetOptionPosition(X(40.0f), 0.0f, TriggerDisplay_Trails, false); +#else + MenuDisplay_2.SetOptionPosition(X(40.0f), 0.0f, false); +#endif + MenuDisplay_2.m_bTwoState = true; + MenuPage_Display.AddMenu(&MenuDisplay_2); + MenuDisplay_3.SetPosition(X(290.0f), Y(260.0f)); + MenuDisplay_3.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR); + MenuDisplay_3.AddTitle(TheText.Get("FED_SUB"), false, 0.0f, 0.0f, true); + MenuDisplay_3.SetOptionPosition(X(40.0f), 0.0f, false); + MenuDisplay_3.m_bTwoState = true; + MenuPage_Display.AddMenu(&MenuDisplay_3); + MenuDisplay_4.SetPosition(X(290.0f), Y(280.0f)); + MenuDisplay_4.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR); + MenuDisplay_4.AddTitle(TheText.Get("FED_WIS"), false, 0.0f, 0.0f, true); + MenuDisplay_4.SetOptionPosition(X(40.0f), 0.0f, false); + MenuDisplay_4.m_bTwoState = true; + MenuPage_Display.AddMenu(&MenuDisplay_4); + MenuPage_Display.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Display.ActivatePage(); + + + /* Language */ + MenuLanguage_1.m_numOptions = 0; + MenuLanguage_1.SetPosition(X(288.0f), Y(160.0f)); + MenuLanguage_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, SELECTED_TEXT_COLOR); + MenuLanguage_1.AddOption(TheText.Get("FEL_ENG"), 0.0f, 0.0f, TriggerLanguage_Language, false, false); + MenuLanguage_1.AddOption(TheText.Get("FEL_FRE"), 0.0f, Y(20.0f), TriggerLanguage_Language, false, false); + MenuLanguage_1.AddOption(TheText.Get("FEL_GER"), 0.0f, Y(40.0f), TriggerLanguage_Language, false, false); + MenuLanguage_1.AddOption(TheText.Get("FEL_ITA"), 0.0f, Y(60.0f), TriggerLanguage_Language, false, false); + MenuLanguage_1.AddOption(TheText.Get("FEL_SPA"), 0.0f, Y(80.0f), TriggerLanguage_Language, false, false); + MenuPage_Language.AddMenu(&MenuLanguage_1); + MenuPage_Language.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Language.ActivatePage(); + + + /* + * Save zone menu + */ + + CVector2D saveGameTextScale2(X(0.49f), Y(0.7f)); + CVector2D defaultTextScale5(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + + /* Save game */ + + MenuSaveZoneSG_1.m_numOptions = 0; + MenuSaveZoneSG_1.SetPosition(X(200.0f), Y(100.0f)); + MenuSaveZoneSG_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneSG_1.AddOption(TheText.Get("FESZ_SA"), 0.0f, Y(20.0f), TriggerSaveZone_SaveGameSelect, false, false); + MenuSaveZoneSG_1.AddOption(TheText.Get("FESZ_CA"), 0.0f, Y(40.0f), TriggerSaveZone_QuitMenu, false, false); + MenuSaveZoneSG_1.m_defaultCancel = TriggerSaveZone_QuitMenu; + MenuPageSaveZone_SaveGame.AddMenu(&MenuSaveZoneSG_1); + MenuSaveZoneSG_1.SetMenuSelection(1); + MenuPageSaveZone_SaveGame.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_SaveGame.ActivatePage(); + + /* Select slot */ + + MenuSaveZoneSSL_1.m_numOptions = 0; + MenuSaveZoneSSL_1.SetPosition(X(160.0f), Y(100.0f)); + MenuSaveZoneSSL_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneSSL_1.AddOption(TheText.Get("FESZ_CA"), 0.0f, 0.0f, TriggerSaveZone_BackToMainMenuTwoLines, false, false); + MenuSaveZoneSSL_1.SetNewOldTextScale(true, saveGameTextScale2, defaultTextScale5, true); + MenuPageSaveZone_SaveSlots.AddMenu(&MenuSaveZoneSSL_1); + MenuSaveZoneSSL_1.SetMenuSelection(0); + MenuPageSaveZone_SaveSlots.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_SaveSlots.ActivatePage(); + + /* Save successful */ + + MenuSaveZoneSS_1.m_numTexts = 0; + MenuSaveZoneSS_1.SetPosition(X(200.0f), Y(100.0f)); + MenuSaveZoneSS_1.AddText(TheText.Get("FESZ_L1"), X(-40.0f), 0.0f, TITLE_TEXT_COLOR, false); + MenuSaveZoneSS_1.AddText(TheText.Get("FESZ_L2"), X(-40.0f), Y(20.0f), TITLE_TEXT_COLOR, false); + // twice this line? + MenuSaveZoneSS_1.AddText(TheText.Get("FESZ_L2"), X(-40.0f), Y(40.0f), TEXT_COLOR, false); + MenuPageSaveZone_SavedSuccessfully.AddMenu(&MenuSaveZoneSS_1); + MenuSaveZoneSS_2.m_numOptions = 0; + MenuSaveZoneSS_2.SetPosition(X(200.0f), Y(170.0f)); + MenuSaveZoneSS_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneSS_2.AddOption(TheText.Get("FESZ_QU"), X(60.0f), 0.0f, TriggerSaveZone_QuitMenu, false, false); + MenuPageSaveZone_SavedSuccessfully.AddMenu(&MenuSaveZoneSS_2); + MenuPageSaveZone_SavedSuccessfully.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_SavedSuccessfully.ActivatePage(); + + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.SetPosition(X(170.0f), Y(130.0f)); + MenuSaveZoneMSG_1.AddText(TheText.Get("FESZ_SR"), X(-40.0f), 0.0f, TEXT_COLOR, false); + MenuSaveZoneMSG_1.SetTextsColor(TEXT_COLOR); + MenuSaveZoneMSG_1.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x-20.0f), X(580.0f)); + MenuPageSaveZone_Message.AddMenu(&MenuSaveZoneMSG_1); + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.SetPosition(X(170.0f), Y(180.0f)); + MenuSaveZoneMSG_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(40.0f), 0.0f, TriggerSaveZone_QuitMenu, false, false); + MenuPageSaveZone_Message.AddMenu(&MenuSaveZoneMSG_2); + MenuPageSaveZone_Message.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_Message.ActivatePage(); + + + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.SetPosition(X(170.0f), Y(130.0f)); + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_SR"), X(-40.0f), 0.0f, TEXT_COLOR, false); + MenuSaveZoneQYN_1.SetTextsColor(TEXT_COLOR); + MenuSaveZoneQYN_1.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x-20.0f), X(580.0f)); + MenuPageSaveZone_QuestionYesNo.AddMenu(&MenuSaveZoneQYN_1); + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.SetPosition(X(170.0f), Y(180.0f)); + MenuSaveZoneQYN_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSaveZone_QuitMenu, false, false); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), Y(20.0f), TriggerSaveZone_QuitMenu, false, false); + MenuPageSaveZone_QuestionYesNo.AddMenu(&MenuSaveZoneQYN_2); + MenuPageSaveZone_QuestionYesNo.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + + /* Format card */ + + MenuSaveZoneFC_1.m_numOptions = 0; + MenuSaveZoneFC_1.SetPosition(X(200.0f), Y(100.0f)); + MenuSaveZoneFC_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneFC_1.AddTitle(TheText.Get("FESZ_FM"), X(-100.0f), 0.0f, false); + MenuSaveZoneFC_1.AddOption(TheText.Get("FEM_NO"), X(40.0f), Y(95.0f), TriggerSaveZone_BackToMainMenu, false, false); + MenuSaveZoneFC_1.AddOption(TheText.Get("FEM_YES"), X(40.0f), Y(75.0f), TriggerSaveZone_FormatCardSelect, false, false); + MenuSaveZoneFC_1.m_defaultCancel = TriggerSaveZone_FormatCardSelect; + MenuPageSaveZone_FormatCard.AddMenu(&MenuSaveZoneFC_1); + MenuPageSaveZone_FormatCard.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_FormatCard.ActivatePage(); + + /* Format error */ + + MenuSaveZoneEF_1.m_numOptions = 0; + MenuSaveZoneEF_1.SetPosition(X(200.0f), Y(100.0f)); + MenuSaveZoneEF_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneEF_1.AddTitle(TheText.Get("FESZ_FF"), X(-40.0f), 0.0f, false); + MenuSaveZoneEF_1.AddOption(TheText.Get("FESZ_OK"), X(70.0f), Y(20.0f), TriggerSaveZone_FormatFailedOK, false, false); + MenuPageSaveZone_ErrorFormat.AddMenu(&MenuSaveZoneEF_1); + MenuPageSaveZone_ErrorFormat.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_ErrorFormat.ActivatePage(); + + pActiveMenuPage = &MenuPage_Stats; + pActiveMenuPage->ActivatePage(); + + InitialiseMenuContents(); + + m_bWantToUpdateContent = false; +} + +void +CMenuManager::InitialiseChangedLanguageSettings(void) +{ + if ( bFrontEnd_ReloadObrTxtGxt ) + { + bFrontEnd_ReloadObrTxtGxt = false; + + CTimer::Stop(); + TheText.Unload(); + TheText.Load(); + CTimer::Update(); + + FrontEndMenuManager.AnaliseMenuContents(); + CGame::frenchGame = false; + CGame::germanGame = false; + if ( m_PrefsAllowNastyGame ) + CGame::nastyGame = true; + + for ( int32 i = 0; i < NUM_PAGES; i++ ) + { + BUTTONTAB_TEXT_X_SCALES[i] = 1.0f; + PANEL_TEXT_X_SCALES[i] = 1.0f; + } + + switch ( m_PrefsLanguage ) + { + case LANGUAGE_AMERICAN: + { + MENU_TEXT_SIZE_X = 0.644f; + MENU_TEXT_SIZE_Y = 0.84f;//0.96f; + + BUTTONTAB_TEXT_SIZE_X = 0.35f; + BUTTONTAB_TEXT_SIZE_Y = 0.7f;//0.8f; + + BUTTONTAB_TEXT_X_SCALES[6] = 0.94f; + + CONTR_DESCR_NEW_TEXTSCALE.x = 0.4564f; + CONTR_DESCR_NEW_TEXTSCALE.y = 0.63f;//0.72f; + + CONFIGS_NEW_TEXTSCALE.x = 0.49f; + CONFIGS_NEW_TEXTSCALE.y = 0.7f;//0.8f; + + AUDIO_OUTPUT_POS.x = 0.0f; + AUDIO_OUTPUT_POS.y = 0.0f; + + AUDIO_RSTATION_POS.x = 154.0f; + AUDIO_RSTATION_POS.y = 0.0f; + + DISPLAY_BRIGHTNESS_POS.x = 0.0f; + DISPLAY_BRIGHTNESS_POS.y = 0.0f; + + MEMCARD_ACCESS_MSG_SIZE_X = 0.84f; + MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f;//1.28f; + + break; + } + + case LANGUAGE_FRENCH: + { + CGame::frenchGame = true; + if ( m_PrefsAllowNastyGame ) + CGame::nastyGame = false; + + MENU_TEXT_SIZE_X = 0.504f; + MENU_TEXT_SIZE_Y = 0.84f;//0.96f; + + BUTTONTAB_TEXT_SIZE_X = 0.32f; + BUTTONTAB_TEXT_SIZE_Y = 0.7f;//0.8f; + + BUTTONTAB_TEXT_X_SCALES[0] = 0.84f; + BUTTONTAB_TEXT_X_SCALES[3] = 0.84f; + PANEL_TEXT_X_SCALES[1] = 0.8f; + + CONTR_DESCR_NEW_TEXTSCALE.x = 0.385f; + CONTR_DESCR_NEW_TEXTSCALE.y = 0.63f;//0.72f; + + CONFIGS_NEW_TEXTSCALE.x = 0.455f; + CONFIGS_NEW_TEXTSCALE.y = 0.7f;//0.8f; + + AUDIO_OUTPUT_POS.x = -15.0f; + AUDIO_OUTPUT_POS.y = 0.0f; + + AUDIO_RSTATION_POS.x = 184.0f; + AUDIO_RSTATION_POS.y = 0.0f; + + DISPLAY_BRIGHTNESS_POS.x = 20.0f; + DISPLAY_BRIGHTNESS_POS.y = 0.0f; + + MEMCARD_ACCESS_MSG_SIZE_X = 0.84f; + MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f;//1.28f; + + break; + } + + case LANGUAGE_GERMAN: + { + CGame::germanGame = true; + if ( m_PrefsAllowNastyGame ) + CGame::nastyGame = false; + + MENU_TEXT_SIZE_X = 0.546f; + MENU_TEXT_SIZE_Y = 0.84f;//0.96f; + + BUTTONTAB_TEXT_SIZE_X = 0.32f; + BUTTONTAB_TEXT_SIZE_Y = 0.7f;//0.8f; + + CONTR_DESCR_NEW_TEXTSCALE.x = 0.35f; + CONTR_DESCR_NEW_TEXTSCALE.y = 0.63f;//0.72f; + + CONFIGS_NEW_TEXTSCALE.x = 0.434f; + CONFIGS_NEW_TEXTSCALE.y = 0.7f;//0.8f; + + AUDIO_OUTPUT_POS.x = -15.0f; + AUDIO_OUTPUT_POS.y = 0.0f; + + AUDIO_RSTATION_POS.x = 154.0f; + AUDIO_RSTATION_POS.y = 0.0f; + + DISPLAY_BRIGHTNESS_POS.x = 20.0f; + DISPLAY_BRIGHTNESS_POS.y = 0.0f; + + MEMCARD_ACCESS_MSG_SIZE_X = 0.7f; + MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f;//1.28f; + + break; + } + + case LANGUAGE_ITALIAN: + { + MENU_TEXT_SIZE_X = 0.574f; + MENU_TEXT_SIZE_Y = 0.84f;//0.96f; + + BUTTONTAB_TEXT_SIZE_X = 0.32f; + BUTTONTAB_TEXT_SIZE_Y = 0.7f;//0.8f; + + BUTTONTAB_TEXT_X_SCALES[0] = 0.86f; + PANEL_TEXT_X_SCALES[1] = 0.9f; + + CONTR_DESCR_NEW_TEXTSCALE.x = 0.385f; + CONTR_DESCR_NEW_TEXTSCALE.y = 0.63f;//0.72f; + + CONFIGS_NEW_TEXTSCALE.x = 0.42f; + CONFIGS_NEW_TEXTSCALE.y = 0.7f;//0.8f; + + AUDIO_OUTPUT_POS.x = 10.0f; + AUDIO_OUTPUT_POS.y = 0.0f; + + AUDIO_RSTATION_POS.x = 194.0f; + AUDIO_RSTATION_POS.y = 0.0f; + + DISPLAY_BRIGHTNESS_POS.x = 10.0f; + DISPLAY_BRIGHTNESS_POS.y = 0.0f; + + MEMCARD_ACCESS_MSG_SIZE_X = 0.84f; + MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f;//1.28f; + + break; + } + + case LANGUAGE_SPANISH: + { + MENU_TEXT_SIZE_X = 0.546f; + MENU_TEXT_SIZE_Y = 0.84f;//0.96f; + + BUTTONTAB_TEXT_SIZE_X = 0.35f; + BUTTONTAB_TEXT_SIZE_Y = 0.7f;//0.8f; + + BUTTONTAB_TEXT_X_SCALES[0] = 0.78f; + PANEL_TEXT_X_SCALES[1] = 0.95f; + + CONTR_DESCR_NEW_TEXTSCALE.x = 0.364f; + CONTR_DESCR_NEW_TEXTSCALE.y = 0.63f;//0.72f; + + CONFIGS_NEW_TEXTSCALE.x = 0.455f; + CONFIGS_NEW_TEXTSCALE.y = 0.7f;//0.8f; + + AUDIO_OUTPUT_POS.x = 10.0f; + AUDIO_OUTPUT_POS.y = 0.0f; + + AUDIO_RSTATION_POS.x = 124.0f; + AUDIO_RSTATION_POS.y = 0.0f; + + DISPLAY_BRIGHTNESS_POS.x = 30.0f; + DISPLAY_BRIGHTNESS_POS.y = 0.0f; + + MEMCARD_ACCESS_MSG_SIZE_X = 0.84f; + MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f;//1.28f; + + break; + } + } + } +} + +void +CMenuManager::InitialiseMenuContents(void) +{ + if ( m_bWantToUpdateContent == false ) + { + m_bWantToUpdateContent = true; + + m_pageState = PAGESTATE_NORMAL; + + switch ( CPad::GetPad(0)->GetMode() ) + { + case 3: m_PrefsControllerConfig = CONFIG_4; break; + case 2: m_PrefsControllerConfig = CONFIG_3; break; + case 1: m_PrefsControllerConfig = CONFIG_2; break; + case 0: m_PrefsControllerConfig = CONFIG_1; break; + } + + MenuControls_1.SetMenuSelection(m_PrefsControllerConfig); + MenuControls_5.SetMenuSelection(m_PrefsUseVibration); + + MenuAudio_1.SetMenuSelection(m_PrefsMusicVolume / 127.0f * 100.0f + 0.5f); + MenuAudio_2.SetMenuSelection(m_PrefsSfxVolume / 127.0f * 100.0f + 0.5f); + MenuAudio_3.SetMenuSelection(m_PrefsRadioStation); + MenuAudio_4.SetMenuSelection(m_PrefsStereoMono); + + MenuDisplay_1.SetMenuSelection(m_PrefsBrightness / 512.0f * 100.0f + 0.5f); +#ifdef PS2 + m_PrefsShowTrails = BlurOn; +#else + m_PrefsShowTrails = CMBlur::BlurOn; +#endif + MenuDisplay_2.SetMenuSelection(m_PrefsShowTrails); + MenuDisplay_3.SetMenuSelection(m_PrefsShowSubtitles); + MenuDisplay_4.SetMenuSelection(m_PrefsUseWideScreen); + + MenuLanguage_1.SetMenuSelection(m_PrefsLanguage); + + FillMenuWithMemCardFileListing(&MenuSaveLG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_LoadGameLoadGameSelect, nil, 0, 34, 22); + FillMenuWithMemCardFileListing(&MenuSaveDG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_DeleteGameDeleteGameSelect, nil, 0, 34, 22); + + MenuBriefs_1.m_numTexts = 0; + MenuBriefs_1.AddText(TheText.Get("FEB_PMB"), 0.0f, 0.0f, TITLE_TEXT_COLOR, 0); // Previous Mission Briefs: + + static wchar StringsToDisplay[NUMPREVIOUSBRIEFS][256]; + + CRGBA newColor; + int32 brierY = 36; + + for ( int32 i = NUMPREVIOUSBRIEFS-1; i >= 0; i-- ) + { + tPreviousBrief &brief = CMessages::PreviousBriefs[i]; + if (brief.m_pText) + { + CMessages::InsertNumberInString(brief.m_pText, + brief.m_nNumber[0], brief.m_nNumber[1], + brief.m_nNumber[2], brief.m_nNumber[3], + brief.m_nNumber[4], brief.m_nNumber[5], StringsToDisplay[i]); + CMessages::InsertStringInString(StringsToDisplay[i], brief.m_pString); + + newColor = TEXT_COLOR; + FilterOutColorMarkersFromString(StringsToDisplay[i], newColor); + + if (newColor != TEXT_COLOR) + { + newColor.r /= 2; + newColor.g /= 2; + newColor.b /= 2; + } + MenuBriefs_1.AddText(StringsToDisplay[i], 0.0f, YF((float)brierY), newColor, 0); + brierY += 54; + } + } + + MenuStats_1.m_scrollPosition = 0.0f; + MenuStats_1.ResetNumberOfTextLines(); + + nStatLinesIndex = 0; + + #define STAT_HEADER(str) do { MenuStats_1.AddTextLine(TheText.Get(str), nil); } while(0) + #define STAT_PARAM(str) do { MenuStats_1.AddTextLine(nil, TheText.Get(str)); } while(0) + #define STAT_LINE(str, left, isFloat, right) do { MenuStats_1.AddTextLine(TheText.Get(str), PrintStatLine(str, left, isFloat, right)); } while(0) + + int32 nTemp; + + STAT_HEADER("PL_STAT"); + + int32 percentCompleted = (CStats::TotalProgressInGame == 0 ? 0 : CStats::ProgressMade * 100.0f / (CGame::nastyGame ? CStats::TotalProgressInGame : CStats::TotalProgressInGame - 1)); + percentCompleted = Min(percentCompleted, 100); + + STAT_LINE("PER_COM", &percentCompleted, 0, nil); + + STAT_LINE("NMISON", &CStats::MissionsGiven, 0, nil); + + STAT_LINE("FEST_MP", &CStats::MissionsPassed, 0, &CStats::TotalNumberMissions); + + if ( CGame::nastyGame ) + STAT_LINE("FEST_RP", &CStats::NumberKillFrenziesPassed, 0, &CStats::TotalNumberKillFrenzies); + + CPlayerInfo &player = CWorld::Players[CWorld::PlayerInFocus]; + float packagesPercent = 0.0f; + if (player.m_nTotalPackages != 0) + packagesPercent = player.m_nCollectedPackages * 100.0f / player.m_nTotalPackages; + int32 nPackagesPercent = packagesPercent; + nTemp = 100; + + STAT_LINE("PERPIC", &nPackagesPercent, 0, &nTemp); + + STAT_LINE("NOUNIF", &CStats::NumberOfUniqueJumpsFound, 0, &CStats::TotalNumberOfUniqueJumps); + + STAT_LINE("DAYSPS", &CStats::DaysPassed, 0, nil); + + if ( CGame::nastyGame ) + { + STAT_LINE("PE_WAST", &CStats::PeopleKilledByPlayer, 0, nil); + STAT_LINE("PE_WSOT", &CStats::PeopleKilledByOthers, 0, nil); + } + + STAT_LINE("CAR_EXP", &CStats::CarsExploded, 0, nil); + + STAT_LINE("TM_BUST", &CStats::TimesArrested, 0, nil); + + STAT_LINE("TM_DED", &CStats::TimesDied, 0, nil); + + nTemp = CStats::PedsKilledOfThisType[PEDTYPE_GANG9] + CStats::PedsKilledOfThisType[PEDTYPE_GANG8] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG7] + CStats::PedsKilledOfThisType[PEDTYPE_GANG6] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG5] + CStats::PedsKilledOfThisType[PEDTYPE_GANG4] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG3] + CStats::PedsKilledOfThisType[PEDTYPE_GANG2] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG1]; + STAT_LINE("GNG_WST", &nTemp, 0, nil); + + nTemp = CStats::PedsKilledOfThisType[PEDTYPE_CRIMINAL]; + STAT_LINE("DED_CRI", &nTemp, 0, nil); + + STAT_LINE("HEL_DST", &CStats::HelisDestroyed, 0, nil); + + STAT_LINE("KGS_EXP", &CStats::KgsOfExplosivesUsed, 0, nil); + + nTemp = (CStats::InstantHitsFiredByPlayer == 0 ? 0 : CStats::InstantHitsHitByPlayer * 100.0f / CStats::InstantHitsFiredByPlayer); + STAT_LINE("ACCURA", &nTemp, 0, nil); + + if (CStats::ElBurroTime > 0) + STAT_LINE("ELBURRO", &CStats::ElBurroTime, 0, nil); + + if (CStats::Record4x4One > 0) + STAT_LINE("FEST_R1", &CStats::Record4x4One, 0, nil); + + if (CStats::Record4x4Two > 0) + STAT_LINE("FEST_R2", &CStats::Record4x4Two, 0, nil); + + if (CStats::Record4x4Three > 0) + STAT_LINE("FEST_R3", &CStats::Record4x4Three, 0, nil); + + if (CStats::Record4x4Mayhem > 0) + STAT_LINE("FEST_RM", &CStats::Record4x4Mayhem, 0, nil); + + if (CStats::LongestFlightInDodo > 0) + STAT_LINE("FEST_LF", &CStats::LongestFlightInDodo, 0, nil); + + if (CStats::TimeTakenDefuseMission > 0) + STAT_LINE("FEST_BD", &CStats::TimeTakenDefuseMission, 0, nil); + + STAT_LINE("CAR_CRU", &CStats::CarsCrushed, 0, nil); + + if (CStats::HighestScores[0] > 0) + { + STAT_HEADER("FEST_BB"); + STAT_LINE("FEST_H0", &CStats::HighestScores[0], 0, nil); + } + + int32 hs = 0; + for ( int32 i = 1; i < 5; i++ ) + hs += CStats::HighestScores[i]; + + if (hs > 0) + STAT_HEADER("FEST_GC"); + + if (CStats::HighestScores[1] > 0) + STAT_LINE("FEST_H1", &CStats::HighestScores[1], 0, nil); + + if (CStats::HighestScores[2] > 0) + STAT_LINE("FEST_H2", &CStats::HighestScores[2], 0, nil); + + if (CStats::HighestScores[3] > 0) + STAT_LINE("FEST_H3", &CStats::HighestScores[3], 0, nil); + + if (CStats::HighestScores[4] > 0) + STAT_LINE("FEST_H4", &CStats::HighestScores[4], 0, nil); + + STAT_LINE("FESTDFM", &CStats::DistanceTravelledOnFoot, 0, nil); + STAT_LINE("FESTDCM", &CStats::DistanceTravelledInVehicle, 0, nil); + STAT_LINE("MMRAIN", &CStats::mmRain, 0, nil); + nTemp = (int32)CStats::MaximumJumpDistance; + STAT_LINE("MXCARDM", &nTemp, 0, nil); + nTemp = (int32)CStats::MaximumJumpHeight; + STAT_LINE("MXCARJM", &nTemp, 0, nil); + + STAT_LINE("MXFLIP", &CStats::MaximumJumpFlips, 0, nil); + STAT_LINE("MXJUMP", &CStats::MaximumJumpSpins, 0, nil); + + STAT_HEADER("BSTSTU"); + + switch (CStats::BestStuntJump) + { + case 1: STAT_PARAM("INSTUN"); break; + case 2: STAT_PARAM("PRINST"); break; + case 3: STAT_PARAM("DBINST"); break; + case 4: STAT_PARAM("DBPINS"); break; + case 5: STAT_PARAM("TRINST"); break; + case 6: STAT_PARAM("PRTRST"); break; + case 7: STAT_PARAM("QUINST"); break; + case 8: STAT_PARAM("PQUINS"); break; + default: STAT_PARAM("NOSTUC"); break; + } + + STAT_LINE("PASDRO", &CStats::PassengersDroppedOffWithTaxi, 0, nil); + STAT_LINE("MONTAX", &CStats::MoneyMadeWithTaxi, 0, nil); + STAT_LINE("FEST_LS", &CStats::LivesSavedWithAmbulance, 0, nil); + STAT_LINE("FEST_HA", &CStats::HighestLevelAmbulanceMission, 0, nil); + STAT_LINE("FEST_CC", &CStats::CriminalsCaught, 0, nil); + STAT_LINE("FEST_FE", &CStats::FiresExtinguished, 0, nil); + int32 rnd = ((CGeneral::GetRandomNumber() & 255) + 100) * 2384; + STAT_LINE("DAYPLC", &rnd, 0, nil); + + #undef STAT_LINE + + MenuStats_2.m_numTexts = 0; + MenuStats_2.AddText(TheText.Get("CRIMRA"), 0.0f, 0.0f, CRIM_RATING_TEXT_COLOR, 0); + + char rating[16]; + wchar urating[16]; + sprintf(rating, " %d", CStats::FindCriminalRatingNumber()); + AsciiToUnicode(rating, urating); + + wchar *pStatLine = aStatLines[nStatLinesIndex++]; + UnicodeStrcpy(pStatLine, CStats::FindCriminalRatingString()); + UnicodeStrcat(pStatLine, urating); + + MenuStats_2.AddText(pStatLine, X(MenuStats_1.m_width), 0.0f, CRIM_RATING_TEXT_COLOR, 1); + + MenuSaveZoneSG_1.SetMenuSelection(1); + MenuSaveZoneFC_1.SetMenuSelection(1); + } +} + + +void +CMenuManager::AnaliseMenuContents(void) +{ + if ( m_bWantToUpdateContent ) + { + m_bWantToUpdateContent = false; + + m_PrefsControllerConfig = (CONTRCONFIG)MenuControls_1.GetMenuSelection(); + switch ( m_PrefsControllerConfig ) + { + case CONFIG_4: CPad::GetPad(0)->SetMode(3); break; + case CONFIG_3: CPad::GetPad(0)->SetMode(2); break; + case CONFIG_2: CPad::GetPad(0)->SetMode(1); break; + case CONFIG_1: CPad::GetPad(0)->SetMode(0); break; + } + + m_PrefsUseVibration = MenuControls_5.m_title.m_bSelected; + + m_PrefsMusicVolume = float(MenuAudio_1.GetMenuSelection())/100.0f*127.0f+0.5f; + m_PrefsSfxVolume = float(MenuAudio_2.GetMenuSelection())/100.0f*127.0f+0.5f; + m_PrefsRadioStation = MenuAudio_3.GetMenuSelection(); + m_PrefsStereoMono = MenuAudio_4.GetMenuSelection(); + m_PrefsBrightness = float(MenuDisplay_1.GetMenuSelection()) / 100.0f*512.0f + 0.5f; + m_PrefsShowTrails = MenuDisplay_2.GetMenuSelection(); + m_PrefsShowSubtitles = MenuDisplay_3.GetMenuSelection(); + m_PrefsUseWideScreen = MenuDisplay_4.GetMenuSelection(); +#ifdef PS2 + BlurOn = m_PrefsShowTrails; +#else + CMBlur::BlurOn = m_PrefsShowTrails; +#endif + + if ( m_PrefsLanguage != MenuLanguage_1.GetMenuSelection() ) + { + m_PrefsLanguage = MenuLanguage_1.GetMenuSelection(); + m_bInitialised = false; + bFrontEnd_ReloadObrTxtGxt = true; + } + } +} + +void +CMenuManager::InitialiseMenuContentsAfterLoadingGame(void) +{ + if ( MenuLanguage_1.GetMenuSelection() != m_PrefsLanguage ) + { + m_bInitialised = false; + bFrontEnd_ReloadObrTxtGxt = true; + } +} + +void +CMenuManager::DrawFrontEnd(void) +{ + CFont::SetAlphaFade(255.0f); + if(m_bInSaveZone) + DrawFrontEndSaveZone(); + else + DrawFrontEndNormal(); + + if ( MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.CallTrigger(); + + DisplayWarningControllerMsg(); +} + +void +CMenuManager::DrawFrontEndNormal(void) +{ + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + + if ( bMemoryCardSpecialZone ) + { + static uint8 counter = 0; + + counter++; + + if ( (counter & 63 ) == 0 ) + { + FillMenuWithMemCardFileListing(&MenuSaveLG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_LoadGameLoadGameSelect, nil, 0, 34, 22); + FillMenuWithMemCardFileListing(&MenuSaveDG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_DeleteGameDeleteGameSelect, nil, 0, 34, 22); + } + } + + m_fade = 255; + if ( m_nChangePageTimer != 0 && m_nChangePageTimer >= CTimer::GetTimeInMillisecondsPauseMode() ) + m_fade = uint32(float(m_nChangePageTimer - CTimer::GetTimeInMillisecondsPauseMode()) / 250.0f * 255.0f); + + m_someAlpha = 255; + + m_position.x = 0.0f; + m_position.y = 0.0f; + + if ( m_nStartPauseTimer != 0 && m_nStartPauseTimer >= CTimer::GetTimeInMillisecondsPauseMode() ) + { + float slide = float(m_nStartPauseTimer - CTimer::GetTimeInMillisecondsPauseMode()) / 800.0f; + float alpha = 1.0f; + + if ((m_nStartPauseTimer - CTimer::GetTimeInMillisecondsPauseMode()) <= 1600) + alpha = float(m_nStartPauseTimer - CTimer::GetTimeInMillisecondsPauseMode()) / 400.0f; + + m_someAlpha = 255 - Clamp(alpha, 0.0f, 1.0f) * 255.0f; + + switch ( m_nSlidingDir ) + { + case SLIDE_TO_RIGHT: m_position.x = slide * X(700.0f); break; + case SLIDE_TO_TOP: m_position.y = -(slide * Y(500.0f)); break; + case SLIDE_TO_LEFT: m_position.x = -(slide * X(700.0f)); break; + case SLIDE_TO_BOTTOM: m_position.y = slide * Y(500.0f); break; + default: m_position.y = slide * Y(500.0f); break; + } + } + + if ( m_nEndPauseTimer != 0 && m_nEndPauseTimer >= CTimer::GetTimeInMillisecondsPauseMode() ) + { + float slide = float(m_nEndPauseTimer - CTimer::GetTimeInMillisecondsPauseMode()) / 800.0f; + float alpha = float((int32)(m_nEndPauseTimer - CTimer::GetTimeInMillisecondsPauseMode()) + -266) / 533.0f; + + m_someAlpha = Clamp(alpha, 0.0f, 1.0f) * 255.0f; + + switch ( m_nSlidingDir ) + { + case SLIDE_TO_TOP: m_position.y = (1.0f - slide) * Y(500.0f); break; + case SLIDE_TO_RIGHT: m_position.x = (1.0f - slide) * X(700.0f); break; + case SLIDE_TO_LEFT: m_position.x = (1.0f - slide) * X(700.0f); break; + case SLIDE_TO_BOTTOM: m_position.y = -((1.0f - slide) * Y(500.0f)); break; + default: m_position.y = -((1.0f - slide) * Y(500.0f)); break; + } + } + + if ( m_someAlpha < 255 ) + m_fade = m_someAlpha; + + float posX, posY; + + /* Draw splash */ + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + CSprite2d *splash = LoadSplash(nil); + if(splash) + splash->Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + else + // doesn't exist!! + CHud::Sprites[19].Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERMIPNEAREST); + + /* Draw main panel */ + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + CRGBA panelColor(255, 255, 255, m_someAlpha); + m_sprites[FE2_MAINPANEL_UL].Draw( + CRect(m_position.x, m_position.y, m_position.x+SCRW/2.0f, m_position.y+SCRH/2.0f), + panelColor); + m_sprites[FE2_MAINPANEL_UR].Draw( + CRect(m_position.x+SCRW/2.0f, m_position.y, m_position.x+SCRW, m_position.y+SCRH/2.0f), + panelColor); + m_sprites[FE2_MAINPANEL_DL].Draw( + CRect(m_position.x, m_position.y+SCRH/2.0f, m_position.x+SCRW/2.0f, m_position.y+SCRH), + panelColor); + m_sprites[FE2_MAINPANEL_DR].Draw( + CRect(m_position.x+SCRW/2.0f, m_position.y+SCRH/2.0f, m_position.x+SCRW, m_position.y+SCRH), + panelColor); + + /* Draw icon backdrop */ + CRGBA iconColor(255, 255, 255, m_fade*0.75f); + float iconX = 48.0f; + float iconY = 54.0f; + float iconWidth = 540.0f; + float iconHeight = 296.0f; + int32 sprite = FE_ICONBRIEF; + +#ifdef PS2_MENU_USEALLPAGEICONS + switch(m_currentPage) + { + case PAGE_STATS: + sprite = FE_ICONSTATS; + break; + case PAGE_LOAD: + sprite = FE_ICONSAVE; + break; + case PAGE_CONTROLS: + sprite = FE_ICONCONTROLS; + break; + case PAGE_BRIEFS: + sprite = FE_ICONBRIEF; + break; + case PAGE_AUDIO: + sprite = FE_ICONAUDIO; + break; + case PAGE_DISPLAY: + sprite = FE_ICONDISPLAY; + break; + case PAGE_LANGUAGE: + sprite = FE_ICONLANGUAGE; + break; + } +#else + switch(m_currentPage) + { + case PAGE_STATS: + case PAGE_LOAD: + case PAGE_CONTROLS: + sprite = FE_ICONSTATS; // PS2 has the same texture for stats and brief + //sprite = FE_ICONBRIEF; + break; + case PAGE_BRIEFS: + sprite = FE_ICONBRIEF; + break; + case PAGE_AUDIO: + sprite = FE_ICONAUDIO; + break; + case PAGE_DISPLAY: + sprite = FE_ICONDISPLAY; + break; + case PAGE_LANGUAGE: + sprite = FE_ICONLANGUAGE; + break; + } +#endif + m_sprites[sprite].Draw( + CRect_SZ(m_position.x+X(iconX), m_position.y+Y(iconY), X(iconWidth), Y(iconHeight)), + iconColor); + + /* Overwrite tab buttons if entered page */ + bool bOverwriteTab = false; + + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + case PAGESTATE_HIGHLIGHTED: + break; + + case PAGESTATE_SELECTED: + bOverwriteTab = true; + break; + } + + if( bOverwriteTab ) + { + CRGBA shadow(41, 101, 102, m_someAlpha); + CRGBA green(40, 48, 57, m_someAlpha); + CSprite2d::DrawRect( + CRect_SZ(m_position.x+X(82.0f), m_position.y+Y(408.0f), X(476.0f), Y(18.0f)), + shadow); + CSprite2d::DrawRect( + CRect_SZ(m_position.x+X(82.0f), m_position.y+Y(408.0f), X(476.0f), Y(5.0f)), + green); + } +// stats, load, briefs, controls, audio, display, language + + /* Shadow of panel on top of tab buttons */ + CRGBA panelShadow(96, 96, 96, m_someAlpha*0.375f); + CSprite2d::DrawRect(CRect_SZ(m_position.x+X(87.0f), m_position.y+Y(408.0f), X(464.0f), Y(3.0f)), panelShadow); + /* Draw second shadow - seems unused */ + if ( m_nChangePageTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() < m_nChangePageTimer ) + { + posX = 0.0f; + switch(field_18) + { + case PAGE_STATS: posX = 88.0f; break; + case PAGE_LOAD: posX = 286.0f; break; // actually controls + case PAGE_BRIEFS: posX = 154.0f; break; // actually load + case PAGE_CONTROLS: posX = 220.0f; break; // actually briefs + case PAGE_AUDIO: posX = 352.0f; break; + case PAGE_DISPLAY: posX = 418.0f; break; + case PAGE_LANGUAGE: posX = 484.0f; break; + } + CSprite2d::DrawRect(CRect_SZ(m_position.x+X(posX), m_position.y+Y(411.0f), X(65.0f), Y(3.0f)), panelShadow); + } + + /* Active tab */ + posX = 0.0f; + switch(m_currentPage) + { + case PAGE_STATS: posX = 88.0f; break; + case PAGE_LOAD: posX = 154.0f; break; + case PAGE_BRIEFS: posX = 220.0f; break; + case PAGE_CONTROLS: posX = 286.0f; break; + case PAGE_AUDIO: posX = 352.0f; break; + case PAGE_DISPLAY: posX = 418.0f; break; + case PAGE_LANGUAGE: posX = 484.0f; break; + } + // PAL has 465 for 407 here - and actually 406 seems right + m_sprites[FE2_TABACTIVE].Draw(CRect_SZ(m_position.x+X(posX), m_position.y+YF(465.0f), X(128.0f), Y(32.0f)), CRGBA(255, 255, 255, m_someAlpha)); + + /* Draw page title */ + posX = m_position.x + X(592.0f); + posY = m_position.y + Y(376.0f); + CRGBA fontCol1(255, 193, 71, m_someAlpha); + CRGBA fontCol2(0, 0, 0, m_someAlpha); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetBackgroundOff(); + CFont::SetScale(X(PANEL_TEXT_SIZE_X), Y(PANEL_TEXT_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetRightJustifyOn(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + const char *key = nil; + switch(m_currentPage) + { + case PAGE_STATS: key = "FEP_STA"; break; + case PAGE_LOAD: key = "FEP_SAV"; break; + case PAGE_BRIEFS: key = "FEP_BRI"; break; + case PAGE_CONTROLS: key = "FEP_CON"; break; + case PAGE_AUDIO: key = "FEP_AUD"; break; + case PAGE_DISPLAY: key = "FEP_DIS"; break; + case PAGE_LANGUAGE: key = "FEP_LAN"; break; + } + CFont::SetScale(X(PANEL_TEXT_SIZE_X*PANEL_TEXT_X_SCALES[m_currentPage]), Y(PANEL_TEXT_SIZE_Y)); + CFont::SetColor(fontCol1); + CFont::PrintString(posX, posY, TheText.Get(key)); + CFont::SetColor(fontCol2); + CFont::PrintString(posX-X(1.0f), posY-Y(1.0f), TheText.Get(key)); + CFont::DrawFonts(); + + /* Draw controller buttons */ + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(0.35f), Y(0.64f)); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + CFont::SetColor(CRGBA(16, 16, 16, m_someAlpha)); + switch(m_currentPage) + { + case PAGE_STATS: + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(360.0f), TheText.Get("FEDS_ST")); + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(372.0f), TheText.Get("FEDS_AM")); + CFont::PrintString(m_position.x+X(242.0f), m_position.y+Y(360.0f), TheText.Get("FEDSSC1")); + CFont::PrintString(m_position.x+X(242.0f), m_position.y+Y(372.0f), TheText.Get("FEDSSC2")); + break; + + case PAGE_BRIEFS: + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(360.0f), TheText.Get("FEDS_ST")); + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(372.0f), TheText.Get("FEDS_AM")); + break; + + case PAGE_LOAD: + case PAGE_CONTROLS: + case PAGE_AUDIO: + case PAGE_DISPLAY: + case PAGE_LANGUAGE: + { + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(360.0f), TheText.Get("FEDS_SE")); + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(372.0f), TheText.Get("FEDS_BA")); + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(384.0f), TheText.Get("FEDS_ST")); + + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + CFont::PrintString(m_position.x+X(242.0f), m_position.y+Y(372.0f), TheText.Get("FEDS_AM")); // <>-CHANGE MENU + break; + + case PAGESTATE_HIGHLIGHTED: + case PAGESTATE_SELECTED: + { + CFont::PrintString(m_position.x+X(242.0f), m_position.y+Y(360.0f+3.5f), TheText.Get("FEA_UP")); // ; + CFont::PrintString(m_position.x+X(242.0f), m_position.y+Y(384.0f-3.5f), TheText.Get("FEA_DO")); // = + CFont::PrintString(m_position.x+X(242.0f-10.0f), m_position.y+Y(372.0f), TheText.Get("FEA_LE")); // < + CFont::PrintString(m_position.x+X(242.0f+11.0f), m_position.y+Y(372.0f), TheText.Get("FEA_RI")); // > + CFont::PrintString(m_position.x+X(242.0f+20.0f), m_position.y+Y(372.0f), TheText.Get("FEDSAS3")); // - CHANGE SELECTION + + break; + } + } + + break; + } + } + + CFont::DrawFonts(); + + /* Draw tab button texts */ + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetCentreOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + CFont::SetColor(CRGBA(16, 16, 16, m_someAlpha)); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_STATS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(92.0f), m_position.y+Y(408.0f), TheText.Get("FEB_STA")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_LOAD]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(158.0f), m_position.y+Y(408.0f), TheText.Get("FEB_SAV")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_BRIEFS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(224.0f), m_position.y+Y(408.0f), TheText.Get("FEB_BRI")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_CONTROLS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(290.0f), m_position.y+Y(408.0f), TheText.Get("FEB_CON")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_AUDIO]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(356.0f), m_position.y+Y(408.0f), TheText.Get("FEB_AUD")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_DISPLAY]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(422.0f), m_position.y+Y(408.0f), TheText.Get("FEB_DIS")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_LANGUAGE]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(488.0f), m_position.y+Y(408.0f), TheText.Get("FEB_LAN")); + + break; + } + + case PAGESTATE_HIGHLIGHTED: + case PAGESTATE_SELECTED: + { + CFont::SetColor(CRGBA(16, 16, 16, m_someAlpha)); + switch(m_currentPage) + { + // PAL has 466 for 408...probably rounded? + case PAGE_STATS: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_STATS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(92.0f), m_position.y+Y(408.0f), TheText.Get("FEB_STA")); + break; + case PAGE_LOAD: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_LOAD]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(158.0f), m_position.y+Y(408.0f), TheText.Get("FEB_SAV")); + break; + case PAGE_BRIEFS: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_BRIEFS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(224.0f), m_position.y+Y(408.0f), TheText.Get("FEB_BRI")); + break; + case PAGE_CONTROLS: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_CONTROLS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(290.0f), m_position.y+Y(408.0f), TheText.Get("FEB_CON")); + break; + case PAGE_AUDIO: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_AUDIO]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(356.0f), m_position.y+Y(408.0f), TheText.Get("FEB_AUD")); + break; + case PAGE_DISPLAY: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_DISPLAY]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(422.0f), m_position.y+Y(408.0f), TheText.Get("FEB_DIS")); + break; + case PAGE_LANGUAGE: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_LANGUAGE]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(488.0f), m_position.y+Y(408.0f), TheText.Get("FEB_LAN")); + break; + } + + break; + } + } + + CFont::DrawFonts(); + + pActiveMenuPage = nil; + switch(m_currentPage) + { + case PAGE_STATS: pActiveMenuPage = &MenuPage_Stats; break; + case PAGE_LOAD: pActiveMenuPage = pMenuSave; break; + case PAGE_BRIEFS: pActiveMenuPage = &MenuPage_Briefs; break; + case PAGE_CONTROLS: pActiveMenuPage = &MenuPage_Controls; break; + case PAGE_AUDIO: pActiveMenuPage = &MenuPage_Audio; break; + case PAGE_DISPLAY: pActiveMenuPage = &MenuPage_Display; break; + case PAGE_LANGUAGE: pActiveMenuPage = &MenuPage_Language; break; + } + + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + CFont::SetRightJustifyWrap(X(38.0f)); + + if(m_currentPage == PAGE_LANGUAGE) + { + CFont::SetCentreOn(); + CFont::SetCentreSize(SCRW-X(40.0f)); // 600.0f + } + + if ( m_nEndPauseTimer != 0 ) + { + switch ( m_currentPage ) + { + case PAGE_LOAD: + case PAGE_BRIEFS: + case PAGE_CONTROLS: + break; + + default: + CFont::SetWrapx(X(1200.0f)); + break; + } + } + + if(pActiveMenuPage) + { + pActiveMenuPage->SetAlpha(m_fade); + + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + pActiveMenuPage->DrawNormal(m_position.x, m_position.y); + break; + + case PAGESTATE_HIGHLIGHTED: + pActiveMenuPage->DrawHighlighted(CRGBA(rgbaATC.r, rgbaATC.g, rgbaATC.b, m_fade), m_position.x, m_position.y); + break; + + case PAGESTATE_SELECTED: + pActiveMenuPage->Draw(CRGBA(rgbaATC.r, rgbaATC.g, rgbaATC.b, m_fade), CRGBA(MENU_SELECTED_COLOR.r, MENU_SELECTED_COLOR.g, MENU_SELECTED_COLOR.b, m_fade), m_position.x, m_position.y); + break; + } + } + + CFont::DrawFonts(); + CFont::DrawFonts(); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSWRAP); +} + +void +CMenuManager::DrawFrontEndSaveZone(void) +{ + if ( bMemoryCardSpecialZone ) + { + static uint8 counter = 0; + counter++; + if ( counter & 63 ) + { + FillMenuWithMemCardFileListing(&MenuSaveZoneSSL_1, TriggerSaveZone_BackToMainMenuTwoLines, TriggerSaveZone_SaveSlots, nil, 0, 34, 22); + + if ( TheMemoryCard.GetError() == CMemoryCard::ERR_NOFORMAT ) + { + pActiveMenuPage = &MenuPageSaveZone_FormatCard; + pActiveMenuPage->ActivatePage(); + bMemoryCardSpecialZone = false; + } + } + } + + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + + m_fade = 255; + + CSprite2d::DrawRect(CRect(X(50.0f), Y(50.0f), X(590.0f), Y(398.0f)), CRGBA(0, 0, 0, 175)); //CRect(50.0f, 57.142f, 590.0f, 454.857147f) + + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetRightJustifyWrap(X(70.0f)); + CFont::SetWrapx(SCRW-X(70.0f)); // 570.0f + + if ( pActiveMenuPage ) + { + pActiveMenuPage->SetAlpha(m_fade); + pActiveMenuPage->Draw(CRGBA(rgbaATC.r, rgbaATC.g, rgbaATC.b, m_fade), TITLE_TEXT_COLOR, 0.0f, 0.0f); + } + + + CFont::DrawFonts(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(0.44f), Y(0.68f)); // 0.44f, 0.777143f + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); //600.0f + CFont::SetColor(TEXT_COLOR); + + wchar *text; + if ( pActiveMenuPage == &MenuPageSaveZone_FormatCard + || pActiveMenuPage == &MenuPageSaveZone_SaveSlots + || pActiveMenuPage == &MenuPageSaveZone_SaveGame ) + { + text = TheText.Get("FEDS_SB"); // / button - SELECT " button - BACK + } + else + { + text = TheText.Get("FEDS_SE"); // / button - SELECT + } + + CFont::PrintString(X(180.0f), Y(376.0f), text); // 180.0f, 429.714294f + CFont::DrawFonts(); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); +} + +void +CMenuManager::DrawMemoryCardStartUpMenus() +{ + CFont::SetAlphaFade(255.0f); + bMemoryCardStartUpMenus_ExitNow = false; + + CMenuPage page; // + 0x40 data + CMenuMultiChoiceTriggered MCMenu; + MCMenu.SetPosition(X(320.0f), Y(150.0f)); //171.428574f + + switch ( TheMemoryCard.CheckCardStateAtGameStartUp(CARD_ONE) ) + { + case CMemoryCard::MCSTATE_NEED_200KB: // 200KB + { + // There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 200KB is needed to save this application data. Do you wish to start? (YES or NO) + MCMenu.AddTitle(TheText.Get("MCGNSP"), 0.0f, 0.0f, 0); + break; + } + + case CMemoryCard::MCSTATE_NEED_500KB: // 500KB + { + // There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 500KB is needed to save this application data. Do you wish to start? (YES or NO) + MCMenu.AddTitle(TheText.Get("MCDNSP"), 0.0f, 0.0f, 0); + break; + } + + case CMemoryCard::MCSTATE_OK: + case CMemoryCard::MCSTATE_NOCARD: + { + return; + break; + } + } + + MCMenu.AddOption(TheText.Get("FEM_NO"), X(30.0f), Y(110.0f), nil, 0, 0);// 125.714294f + MCMenu.AddOption(TheText.Get("FEM_YES"), X(-30.0f), Y(110.0f), TriggerMCSUM_Yes, 0, 0);// 125.714294f + MCMenu.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + page.AddMenu(&MCMenu); + + MCMenu.GoFirst(); + + page.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + + CTimer::Initialise(); + CTimer::StartUserPause(); + + while ( !bMemoryCardStartUpMenus_ExitNow ) + { +#ifdef GTA_PC + HandleExit(); + + if(RsGlobal.quit) + return; +#endif + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadLeftJustDown() ) + page.GoLeft(); + if ( CPad::GetPad(0)->GetDPadRightJustDown() ) + page.GoRight(); + if ( CPad::GetPad(0)->GetDPadUpJustDown() ) + page.GoDown(); + if ( CPad::GetPad(0)->GetDPadDownJustDown() ) + page.GoUp(); + if ( CPad::GetPad(0)->GetCrossJustDown() || CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetRightMouseJustDown() ) + page.SelectCurrentOptionUnderCursor(); + + if ( CPad::GetPad(0)->GetCircleJustDown() || CPad::GetPad(0)->GetEscapeJustDown() ) + ; +#else + if ( CPad::GetPad(0)->GetDPadLeftJustDown() ) + page.GoLeft(); + if ( CPad::GetPad(0)->GetDPadRightJustDown() ) + page.GoRight(); + if ( CPad::GetPad(0)->GetDPadUpJustDown() ) + page.GoDown(); + if ( CPad::GetPad(0)->GetDPadDownJustDown() ) + page.GoUp(); + if ( CPad::GetPad(0)->GetCrossJustDown() ) + page.SelectCurrentOptionUnderCursor(); + if ( CPad::GetPad(0)->GetCircleJustDown() ) + ; +#endif + + static int32 MemCardStatusWaiter = 0; + + MemCardStatusWaiter++; + + if ( MemCardStatusWaiter > 120 ) + { + MemCardStatusWaiter = 0; + + switch ( TheMemoryCard.CheckCardStateAtGameStartUp(CARD_ONE) ) + { + case CMemoryCard::MCSTATE_NEED_200KB: + { + // There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 200KB is needed to save this application data. Do you wish to start? (YES or NO) + MCMenu.AddTitle(TheText.Get("MCGNSP"), 0.0f, 0.0f, 0); + break; + } + + case CMemoryCard::MCSTATE_NEED_500KB: + { + // There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 500KB is needed to save this application data. Do you wish to start? (YES or NO) + MCMenu.AddTitle(TheText.Get("MCDNSP"), 0.0f, 0.0f, 0); + break; + } + + case CMemoryCard::MCSTATE_NOCARD: + { + // There is no Memory Card (PS2) in MEMORY CARD slot 1. Do you wish to start? (YES or NO) + MCMenu.AddTitle(TheText.Get("MCSTNS"), 0.0f, 0.0f, 0); + break; + } + + case CMemoryCard::MCSTATE_OK: + { + bMemoryCardStartUpMenus_ExitNow = true; + break; + } + } + } + + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + CFont::InitPerFrame(); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + CSprite2d *splash = LoadSplash("splash1"); + splash->Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + + SetRandomActiveTextlineColor(1); + + CRGBA col(rgbaATC.r, rgbaATC.g, rgbaATC.b, 255); + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(60.0f)); // 580.0f + CFont::SetCentreOn(); + CFont::SetCentreSize(SCRW-X(120.0f)); // 520.0f + + MCMenu.Draw(col, TITLE_TEXT_COLOR, 0.0f, 0.0f); + CFont::DrawFonts(); + + CFont::SetFontStyle(FONT_BANK); + CFont::SetScale(X(0.4f), Y(0.64f)); // 0.731429 + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(60.0f)); // 580.0f + CFont::SetColor(TEXT_COLOR); + + + CPlaceableShText text; + text.SetPosition(X(240.0f), Y(378.0f), false); // 432.000000 + text.SetColor(TEXT_COLOR); + text.m_text = TheText.Get("FEDS_SE"); // / button - SELECT + text.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + text.Draw(0.0f, 0.0f); + + CFont::DrawFonts(); + DisplayWarningControllerMsg(); + DoRWStuffEndOfFrame(); + CPad::UpdatePads(); + CTimer::Update(); + } + + CTimer::EndUserPause(); + CTimer::Stop(); + + for ( int32 i = 0; i < 100; i++ ) + { +#ifdef GTA_PC + HandleExit(); +#endif + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + CSprite2d *splash = LoadSplash("splash1"); + splash->Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + + DoRWStuffEndOfFrame(); + } +} + +void +CMenuManager::Process(void) +{ + if ( m_bSaveMenuActive || m_bInSaveZone || TheCamera.GetScreenFadeStatus() == FADE_0 ) + { + InitialiseMenusOnce(); + m_bWantToRestart = false; + WorkOutMenuState(false); + + if ( m_bMenuActive ) + { + if ( !m_bInSaveZone ) + LoadAllTextures(); + InitialiseMenuContents(); + SetRandomActiveTextlineColor(0); + ProcessControllerInput(); + } + else + { + AnaliseMenuContents(); + pMenuSave = &MenuPage_SaveBasic; + m_pageState = PAGESTATE_NORMAL; + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + UnloadTextures(); + m_bInSaveZone = false; + m_bRenderGameInMenu = false; + gErrorSampleTriggered = true; + } + } +} + +void +CMenuManager::WorkOutMenuState(uint8 bExit) +{ +#ifdef GTA_PC + bool bIsStartPressed = CPad::GetPad(0)->GetStartJustDown() || (m_pageState == PAGESTATE_NORMAL && CPad::GetPad(0)->GetEscapeJustDown()); +#else + bool bIsStartPressed = CPad::GetPad(0)->GetStartJustDown(); +#endif + bool bIsCreditsOrDraw = CCredits::AreCreditsDone() || m_bMenuActive; + bool bIsDemoOrDraw = m_bMenuActive || CGame::bDemoMode; + + if ( (bIsStartPressed && bIsCreditsOrDraw) || bExit || (!bIsDemoOrDraw && CPad::IsNoOrObsolete()) ) + { + if ( m_nStartPauseTimer == 0 && m_nEndPauseTimer == 0 ) + { + m_bMenuActive = !m_bMenuActive; + + if ( !m_bMenuActive ) + { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + gMusicPlaying = false; + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + + m_bMenuActive = true; + + m_nEndPauseTimer = CTimer::GetTimeInMillisecondsPauseMode() + 800; + + if ( m_currentPage == PAGE_CONTROLS || m_currentPage == PAGE_BRIEFS || m_currentPage == PAGE_LOAD ) + { + m_nSlidingDir = CGeneral::GetRandomNumber() & (SLIDE_MAX-1); + + switch ( m_nSlidingDir ) //m_nSlidingDir &= ~1; + { + case SLIDE_TO_LEFT: m_nSlidingDir = SLIDE_TO_TOP; break; + case SLIDE_TO_RIGHT: m_nSlidingDir = SLIDE_TO_BOTTOM; break; + } + + m_position.y = Y(500.0f); // 571.428589f; + } + } + else + { + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + + if ( DMAudio.GetRadioInCar() < 9 ) + m_PrefsRadioStation = DMAudio.GetRadioInCar(); + else + m_PrefsRadioStation = CGeneral::GetRandomNumber() % 9; + + CTimer::StartUserPause(); + CPad::StopPadsShaking(); + m_nStartPauseTimer = CTimer::GetTimeInMillisecondsPauseMode() + 800; + m_nSlidingDir = CGeneral::GetRandomNumber() & (SLIDE_MAX-1); + + switch ( m_nSlidingDir ) + { + case SLIDE_TO_RIGHT: m_position.y = Y(612.5f); break; + case SLIDE_TO_LEFT: m_position.y = Y(612.5f); break; + case SLIDE_TO_BOTTOM: m_position.y = Y(500.0f); break; + case SLIDE_TO_TOP: m_position.y = Y(500.0f); break; + default: m_position.y = Y(500.0f); break; + } + + if ( m_currentPage == PAGE_CONTROLS || m_currentPage == PAGE_BRIEFS ) + { + m_nSlidingDir = CGeneral::GetRandomNumber() & (SLIDE_MAX-1); + + switch ( m_nSlidingDir ) //m_nSlidingDir &= ~1; + { + case SLIDE_TO_LEFT: m_nSlidingDir = SLIDE_TO_TOP; break; + case SLIDE_TO_RIGHT: m_nSlidingDir = SLIDE_TO_BOTTOM; break; + } + + m_position.y = Y(500.0f); //571.428589f + } + } + } + } + + if ( m_bSaveMenuActive && !m_bInSaveZone && !TheMemoryCard._bunk2) + { + m_bSaveMenuActive = false; + m_bInSaveZone = true; + m_bRenderGameInMenu = true; + m_bMenuActive = true; + CTimer::StartUserPause(); + pActiveMenuPage = &MenuPageSaveZone_SaveGame; + } + + if ( m_pageState == PAGESTATE_NORMAL && gMusicPlaying ) + { + DMAudio.StopFrontEndTrack(); + gMusicPlaying = false; + } + + if ( m_nChangePageTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() >= m_nChangePageTimer ) + { + m_nChangePageTimer = 0; + pMenuSave = &MenuPage_SaveBasic; + m_currentPage = m_newPage; + } + + if ( m_nPageLeftTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() >= m_nPageLeftTimer ) + m_nPageLeftTimer = 0; + + if ( m_nPageRightTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() >= m_nPageRightTimer ) + m_nPageRightTimer = 0; + + if ( m_nStartPauseTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() >= m_nStartPauseTimer ) + m_nStartPauseTimer = 0; + + if ( m_nEndPauseTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() >= m_nEndPauseTimer ) + { + m_nEndPauseTimer = 0; + m_bMenuActive = false; + m_bMenuActive = false; + m_bInSaveZone = false; + CTimer::EndUserPause(); + } +} + +void +CMenuManager::ProcessControllerInput(void) +{ + if ( TimeToStopPadShaking != 0 && TimeToStopPadShaking < CTimer::GetTimeInMillisecondsPauseMode() ) + { + CPad::StopPadsShaking(); + TimeToStopPadShaking = 0; + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadLeft() || CPad::GetPad(0)->GetLeft() ) +#else + if ( CPad::GetPad(0)->GetDPadLeft() ) +#endif + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + case PAGESTATE_HIGHLIGHTED: + break; + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoLeftStill(); + break; + } + } + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadRight() || CPad::GetPad(0)->GetRight() ) +#else + if ( CPad::GetPad(0)->GetDPadRight() ) +#endif + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + case PAGESTATE_HIGHLIGHTED: + break; + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoRightStill(); + break; + } + } + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadLeftJustDown() || CPad::GetPad(0)->GetLeftJustDown() ) +#else + if ( CPad::GetPad(0)->GetDPadLeftJustDown() ) +#endif + ProcessDPadLeftJustDown(); + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadRightJustDown() || CPad::GetPad(0)->GetRightJustDown() ) +#else + if ( CPad::GetPad(0)->GetDPadRightJustDown() ) +#endif + ProcessDPadRightJustDown(); + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadUp() || CPad::GetPad(0)->GetUp() ) +#else + if ( CPad::GetPad(0)->GetDPadUp() ) +#endif + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + if ( m_currentPage == PAGE_STATS ) + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoUpStill(); + } + break; + } + + case PAGESTATE_HIGHLIGHTED: + break; + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoUpStill(); + break; + } + } + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadDown() || CPad::GetPad(0)->GetDown() ) +#else + if ( CPad::GetPad(0)->GetDPadDown() ) +#endif + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + if ( m_currentPage == PAGE_STATS ) + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoDownStill(); + } + + break; + } + case PAGESTATE_HIGHLIGHTED: + break; + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoDownStill(); + break; + } + } + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadUpJustDown() || CPad::GetPad(0)->GetUpJustDown() ) +#else + if ( CPad::GetPad(0)->GetDPadUpJustDown() ) +#endif + ProcessDPadUpJustDown(); + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadDownJustDown() || CPad::GetPad(0)->GetDownJustDown() ) +#else + if ( CPad::GetPad(0)->GetDPadDownJustDown() ) +#endif + ProcessDPadDownJustDown(); + + if ( CPad::GetPad(0)->GetLeftShoulder1JustDown() ) + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + ProcessDPadLeftJustDown(); + break; + + case PAGESTATE_HIGHLIGHTED: + case PAGESTATE_SELECTED: + break; + } + } + + if ( CPad::GetPad(0)->GetRightShoulder1JustDown() ) + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + ProcessDPadRightJustDown(); + break; + + case PAGESTATE_HIGHLIGHTED: + case PAGESTATE_SELECTED: + break; + } + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetCrossJustDown() || CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetRightMouseJustDown() ) +#else + if ( CPad::GetPad(0)->GetCrossJustDown() ) +#endif + ProcessDPadCrossJustDown(); + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetTriangleJustDown() || CPad::GetPad(0)->GetBackspaceJustDown() || (m_pageState != PAGESTATE_NORMAL && CPad::GetPad(0)->GetEscapeJustDown()) ) +#else + if ( CPad::GetPad(0)->GetTriangleJustDown() ) +#endif + ProcessDPadTriangleJustDown(); +} + + +void +CMenuManager::ProcessDPadLeftJustDown(void) +{ + if ( m_bInSaveZone ) + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoLeft(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveZoneSSL_1 ) + { + if ( MenuSaveZoneSSL_1.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + else + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + if ( !bMemoryCardSpecialZone && !m_bInSaveZone ) + { + if ( m_nChangePageTimer == 0 ) + { + if ( --m_newPage < PAGE_FIRST ) m_newPage = PAGE_LAST; + + m_nPageLeftTimer = CTimer::GetTimeInMillisecondsPauseMode() + 300; + m_nPageRightTimer = 0; + m_nChangePageTimer = CTimer::GetTimeInMillisecondsPauseMode() + 250; + field_18 = m_newPage; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + } + } + + break; + } + + case PAGESTATE_HIGHLIGHTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoLeftMenuOnPage(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + break; + } + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoLeft(); + + if ( m_currentPage == PAGE_AUDIO) + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_1 ) + ; + else if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( m_currentPage == PAGE_DISPLAY) + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuDisplay_1 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveDG_2 ) + { + if ( MenuSaveDG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveLG_2 ) + { + if ( MenuSaveLG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + + break; + } + } + } +} + +void +CMenuManager::ProcessDPadRightJustDown(void) +{ + if ( m_bInSaveZone ) + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoRight(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveZoneSSL_1 ) + { + if ( MenuSaveZoneSSL_1.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + else + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + if ( !bMemoryCardSpecialZone && !m_bInSaveZone ) + { + if ( m_nChangePageTimer == 0 ) + { + if ( ++m_newPage > PAGE_LAST ) m_newPage = PAGE_FIRST; + + m_nPageLeftTimer = 0; + m_nPageRightTimer = CTimer::GetTimeInMillisecondsPauseMode() + 300; + m_nChangePageTimer = CTimer::GetTimeInMillisecondsPauseMode() + 250; + field_18 = m_newPage; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + } + } + + break; + } + + case PAGESTATE_HIGHLIGHTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoRightMenuOnPage(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + break; + } + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoRight(); + + if ( m_currentPage == PAGE_AUDIO) + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_1 ) + ; + else if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( m_currentPage == PAGE_DISPLAY) + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuDisplay_1 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveDG_2 ) + { + if ( MenuSaveDG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveLG_2 ) + { + if ( MenuSaveLG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + + break; + } + } + } +} + +void +CMenuManager::ProcessDPadUpJustDown(void) +{ + if ( m_bInSaveZone ) + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoUp(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveZoneSSL_1 ) + { + if ( MenuSaveZoneSSL_1.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + else + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + break; + + case PAGESTATE_HIGHLIGHTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoUpMenuOnPage(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + break; + } + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoUp(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveDG_2 ) + { + if ( MenuSaveDG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveLG_2 ) + { + if ( MenuSaveLG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + + break; + } + } + } +} + +void +CMenuManager::ProcessDPadDownJustDown(void) +{ + if ( m_bInSaveZone ) + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoDown(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveZoneSSL_1 ) + { + if ( MenuSaveZoneSSL_1.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + else + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + break; + + case PAGESTATE_HIGHLIGHTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoDownMenuOnPage(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + break; + } + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoDown(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveDG_2 ) + { + if ( MenuSaveDG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveLG_2 ) + { + if ( MenuSaveLG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + break; + } + } + } +} + +void +CMenuManager::ProcessDPadTriangleJustDown(void) +{ + if ( pActiveMenuPage ) + { + pActiveMenuPage->SelectDefaultCancelAction(); + + if ( m_bMenuActive || m_bInSaveZone ) + { + if ( bIgnoreTriangleButton ) + { + if ( m_bInSaveZone ) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_BACK, 0); + else if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveDG_2 || pActiveMenuPage->m_pCurrentControl == &MenuSaveLG_2 ) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_BACK, 0); + } + else if ( !bIgnoreTriangleButton ) + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + WorkOutMenuState(true); + break; + + case PAGESTATE_HIGHLIGHTED: + m_pageState = PAGESTATE_NORMAL; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + break; + + case PAGESTATE_SELECTED: + { + m_pageState = PAGESTATE_HIGHLIGHTED; + if ( pActiveMenuPage ) + { + if ( pActiveMenuPage->m_numControls == 1 ) + { + m_pageState = PAGESTATE_NORMAL; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_BACK, 0); + } + break; + } + } + } + } + } + else + { + if ( !bIgnoreTriangleButton ) + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + WorkOutMenuState(false); + break; + + case PAGESTATE_HIGHLIGHTED: + m_pageState = PAGESTATE_NORMAL; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + break; + + case PAGESTATE_SELECTED: + { + m_pageState = PAGESTATE_HIGHLIGHTED; + if ( pActiveMenuPage ) + { + if ( pActiveMenuPage->m_numControls == 1 ) + { + m_pageState = PAGESTATE_NORMAL; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_BACK, 0); + } + break; + } + } + } + } +} + +void +CMenuManager::ProcessDPadCrossJustDown(void) +{ + if ( m_bInSaveZone ) + { + if ( pActiveMenuPage ) + pActiveMenuPage->SelectCurrentOptionUnderCursor(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + else + { + if ( m_currentPage != PAGE_STATS && m_currentPage != PAGE_BRIEFS) + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + m_pageState = PAGESTATE_HIGHLIGHTED; + if ( pActiveMenuPage ) + { + if ( pActiveMenuPage->m_numControls == 1 ) + m_pageState = PAGESTATE_SELECTED; + } + + switch ( m_currentPage ) + { + case PAGE_AUDIO: + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_1 + || pActiveMenuPage->m_pCurrentControl == &MenuAudio_2 + || pActiveMenuPage->m_pCurrentControl == &MenuAudio_3 + || pActiveMenuPage->m_pCurrentControl == &MenuAudio_4 ) + { + if ( !gMusicPlaying ) + { + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + gMusicPlaying = true; + } + } + else + { + DMAudio.StopFrontEndTrack(); + gMusicPlaying = false; + } + break; + } + } + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + } + + case PAGESTATE_HIGHLIGHTED: + { + m_pageState = PAGESTATE_SELECTED; + DoHackingMenusAtPageBrowse(); + if ( pActiveMenuPage ) + { + if ( pActiveMenuPage->IsActiveMenuTwoState()) + { + m_pageState = PAGESTATE_HIGHLIGHTED; + pActiveMenuPage->ActiveMenuTwoState_SelectNextPosition(); + } + } + + switch ( m_currentPage ) + { + case PAGE_AUDIO: + { + if ( pActiveMenuPage->m_pCurrentControl != &MenuAudio_4 ) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + + break; + } + + default: + { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + DMAudio.StopFrontEndTrack(); + gMusicPlaying = false; + break; + } + } + break; + } + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->SelectCurrentOptionUnderCursor(); + + switch ( m_currentPage ) + { + case PAGE_AUDIO: + { + if ( pActiveMenuPage->m_pCurrentControl != &MenuAudio_3 ) + m_pageState = PAGESTATE_HIGHLIGHTED; + break; + } + + case PAGE_LOAD: + case PAGE_LANGUAGE: + break; + + default: + m_pageState = PAGESTATE_HIGHLIGHTED; + break; + } + } + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + } + } + } + } +} + +void +CMenuManager::DoHackingMenusAtPageBrowse(void) +{ + if ( pActiveMenuPage ) + { + switch ( m_currentPage ) + { + case PAGE_CONTROLS: + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuControls_1 ) + { + int32 sel = MenuControls_1.GetMenuSelection(); + MenuControls_1.GoFirst(); + + for ( int32 i = 0; i < sel; i++ ) + MenuControls_1.GoNext(); + } + break; + } + + case PAGE_AUDIO: + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_3 ) + { + int32 sel = MenuAudio_3.GetMenuSelection(); + MenuAudio_3.GoFirst(); + + for ( int32 i = 0; i < sel; i++ ) + MenuAudio_3.GoNext(); + } + break; + } + } + } +} + +void +CMenuManager::SetSoundLevelsForMusicMenu(void) +{ + DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); +} + +void +CMenuManager::FilterOutColorMarkersFromString(wchar *string, CRGBA &color) +{ + wchar buf[300]; + UnicodeStrcpy(buf, string); + + wchar *src = buf; + wchar *dst = string; + while ( *src != '\0' ) + { + if ( *src == '~' ) + { + src++; + + if ( *src == 'l' ) color = CRGBA(0, 0, 0, 255); + else if ( *src == 'p' ) color = CRGBA(255, 0, 255, 255); + else if ( *src == 'y' ) color = CRGBA(255, 255, 0, 255); + else if ( *src == 'w' ) color = CRGBA(255, 255, 255, 255); + else if ( *src == 'b' ) color = CRGBA(40, 40, 255, 255); + else if ( *src == 'g' ) color = CRGBA(40, 235, 40, 255); + else if ( *src == 'r' ) color = CRGBA(255, 0, 0, 255); + + while ( *src++ != '~' ) + ; + } + else + *dst++ = *src++; + } + + *dst = '\0'; +} + +#endif diff --git a/src/core/Frontend_PS2.h b/src/core/Frontend_PS2.h new file mode 100644 index 0000000..4bab7df --- /dev/null +++ b/src/core/Frontend_PS2.h @@ -0,0 +1,245 @@ +#pragma once +#include "Sprite2d.h" + +enum +{ + PAGE_STATS, + PAGE_LOAD, + PAGE_BRIEFS, + PAGE_CONTROLS, + PAGE_AUDIO, + PAGE_DISPLAY, + PAGE_LANGUAGE, + + NUM_PAGES, + PAGE_FIRST = PAGE_STATS, + PAGE_LAST = PAGE_LANGUAGE, +}; + +enum +{ + PAGESTATE_NORMAL = 0, + PAGESTATE_HIGHLIGHTED, + PAGESTATE_SELECTED +}; + + +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, + + NUM_FE_SPRITES +}; + + +class CSprite2d; +class CVector2D; + +#ifdef GTA_PC +enum eControlMethod +{ + CONTROL_STANDARD = 0, + CONTROL_CLASSIC, +}; +#endif + +class CMenuManager +{ +public: + enum LANGUAGE + { + LANGUAGE_AMERICAN, + LANGUAGE_FRENCH, + LANGUAGE_GERMAN, + LANGUAGE_ITALIAN, + LANGUAGE_SPANISH, +#ifdef MORE_LANGUAGES + LANGUAGE_POLISH, + LANGUAGE_RUSSIAN, + LANGUAGE_JAPANESE, +#endif + }; + + enum CONTRCONFIG + { + CONFIG_1 = 0, + CONFIG_2, + CONFIG_3, + CONFIG_4, + }; + + enum + { + NUM_SPRIRES = 28, + }; + + enum + { + PAGESTATE_NORMAL = 0, + PAGESTATE_HIGHLIGHTED = 1, + PAGESTATE_SELELECTED = 2, + }; + + enum + { + SLIDE_TO_BOTTOM = 0, + SLIDE_TO_RIGHT, + SLIDE_TO_TOP, + SLIDE_TO_LEFT, + SLIDE_MAX + }; + + int32 m_currentPage; + int32 m_newPage; + int32 m_pageState; + uint32 m_nPageLeftTimer; + uint32 m_nPageRightTimer; + uint32 m_nChangePageTimer; + int field_18; + uint8 m_fade; + uint8 m_someAlpha; + //char field_1E; // unused ? + //char field_1F; // unused ? + uint32 m_nStartPauseTimer; + uint32 m_nEndPauseTimer; + CVector2D m_position; + uint8 m_nSlidingDir; + //char field_31; // unused ? + //char field_32; // unused ? + //char field_33; // unused ? + bool m_bInitialised; + bool m_bWantToUpdateContent; + bool m_bMenuActive; + bool m_bWantToRestart; + //char field_38; //unused ? + bool m_bRenderGameInMenu; + bool m_bSaveMenuActive; + bool m_bInSaveZone; + char field_3C; + bool m_bTexturesLoaded; + //char field_3E; //unused ? + //char field_3F; //unused ? + CSprite2d m_sprites[NUM_SPRIRES]; + + static int32 m_PrefsSfxVolume; + static int32 m_PrefsMusicVolume; + static int32 m_PrefsBrightness; + static bool m_PrefsShowTrails; + static bool m_PrefsShowSubtitles; + static bool m_PrefsAllowNastyGame; + static int32 m_PrefsRadioStation; + static int32 m_PrefsStereoMono; + static int8 m_PrefsUseWideScreen; + static int32 m_PrefsLanguage; + static CONTRCONFIG m_PrefsControllerConfig; + static bool m_PrefsUseVibration; + +#define ISLAND_LOADING_IS(p) +#define ISLAND_LOADING_ISNT(p) +#ifdef GTA_PC + bool m_bQuitGameNoCD; + + int32 m_nMouseTempPosX; + int32 m_nMouseTempPosY; + int32 m_nPrefsVideoMode; + int32 m_nDisplayVideoMode; + int8 m_nPrefsAudio3DProviderIndex; + + static int32 OS_Language; + static int8 m_PrefsVsync; + static int8 m_PrefsVsyncDisp; + static int8 m_PrefsFrameLimiter; + static int8 m_PrefsSpeakers; + static int32 m_ControlMethod; + static int8 m_PrefsDMA; + static float m_PrefsLOD; + static char m_PrefsSkinFile[256]; + +#ifndef MASTER + static bool m_PrefsMarketing; + static bool m_PrefsDisableTutorials; +#endif // !MASTER + +#ifdef MENU_MAP + static bool bMenuMapActive; + static float fMapSize; + static float fMapCenterY; + static float fMapCenterX; +#endif + +#ifdef IMPROVED_VIDEOMODE + int32 m_nPrefsWidth = 640; + int32 m_nPrefsHeight = 480; + int32 m_nPrefsDepth = 32; + int32 m_nPrefsWindowed = 1; + int32 m_nPrefsSubsystem; + int32 m_nSelectedScreenMode; +#endif + + void WaitForUserCD() { } +#endif + + bool GetIsMenuActive() {return !!m_bMenuActive;} + + CMenuManager(void); +#ifdef FIX_BUGS + ~CMenuManager(void) + { + UnloadTextures(); + } +#endif + + void LoadAllTextures(void); + void UnloadTextures(void); + + void InitialiseMenusOnce(void); + void InitialiseChangedLanguageSettings(void); + void InitialiseMenuContents(void); + void AnaliseMenuContents(void); + void InitialiseMenuContentsAfterLoadingGame(void); + void DrawFrontEnd(void); + void DrawFrontEndNormal(void); + void DrawFrontEndSaveZone(void); + void DrawMemoryCardStartUpMenus(void); + void Process(void); + void WorkOutMenuState(uint8 bExit); + void ProcessControllerInput(void); + void ProcessDPadLeftJustDown(void); + void ProcessDPadRightJustDown(void); + void ProcessDPadUpJustDown(void); + void ProcessDPadDownJustDown(void); + void ProcessDPadTriangleJustDown(void); + void ProcessDPadCrossJustDown(void); + void DoHackingMenusAtPageBrowse(void); + void SetSoundLevelsForMusicMenu(void); + void FilterOutColorMarkersFromString(wchar *string, CRGBA &color); +}; + +extern CMenuManager FrontEndMenuManager; \ No newline at end of file diff --git a/src/core/Game.cpp b/src/core/Game.cpp new file mode 100644 index 0000000..b3dd1ed --- /dev/null +++ b/src/core/Game.cpp @@ -0,0 +1,1464 @@ +#include "common.h" +#include "platform.h" + +#include "Game.h" +#include "main.h" +#include "RwHelper.h" +#include "Accident.h" +#include "Antennas.h" +#include "Bridge.h" +#include "CarCtrl.h" +#include "CarGen.h" +#include "CdStream.h" +#include "Clock.h" +#include "Clouds.h" +#include "Collision.h" +#include "Console.h" +#include "Coronas.h" +#include "Cranes.h" +#include "Credits.h" +#include "CutsceneMgr.h" +#include "DMAudio.h" +#include "Darkel.h" +#include "Debug.h" +#include "EventList.h" +#include "FileLoader.h" +#include "FileMgr.h" +#include "Fire.h" +#include "Fluff.h" +#include "Font.h" +#include "Frontend.h" +#include "frontendoption.h" +#include "GameLogic.h" +#include "Garages.h" +#include "GenericGameStorage.h" +#include "Glass.h" +#include "HandlingMgr.h" +#include "Heli.h" +#include "Hud.h" +#include "IniFile.h" +#include "Lights.h" +#include "MBlur.h" +#include "Messages.h" +#include "MemoryCard.h" +#include "Pad.h" +#include "Particle.h" +#include "ParticleObject.h" +#include "PedRoutes.h" +#include "Phones.h" +#include "Pickups.h" +#include "Plane.h" +#include "PlayerSkin.h" +#include "Population.h" +#include "Radar.h" +#include "Record.h" +#include "References.h" +#include "Renderer.h" +#include "Replay.h" +#include "Restart.h" +#include "RoadBlocks.h" +#include "Rubbish.h" +#include "SceneEdit.h" +#include "Script.h" +#include "Shadows.h" +#include "Skidmarks.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "Streaming.h" +#include "SurfaceTable.h" +#include "TempColModels.h" +#include "Timecycle.h" +#include "TrafficLights.h" +#include "Train.h" +#include "TxdStore.h" +#include "User.h" +#include "VisibilityPlugins.h" +#include "WaterCannon.h" +#include "WaterLevel.h" +#include "Weapon.h" +#include "WeaponEffects.h" +#include "Weather.h" +#include "World.h" +#include "ZoneCull.h" +#include "Zones.h" +#include "debugmenu.h" +#include "postfx.h" +#include "custompipes.h" +#include "screendroplets.h" +#include "crossplatform.h" +#include "MemoryHeap.h" +#ifdef USE_TEXTURE_POOL +#include "TexturePools.h" +#endif + +eLevelName CGame::currLevel; +bool CGame::bDemoMode = true; +bool CGame::nastyGame = true; +bool CGame::frenchGame; +bool CGame::germanGame; +bool CGame::noProstitutes; +bool CGame::playingIntro; +char CGame::aDatFile[32]; +#ifdef MORE_LANGUAGES +bool CGame::russianGame = false; +bool CGame::japaneseGame = false; +#endif + +int gameTxdSlot; + + +bool DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +void DoRWStuffEndOfFrame(void); +#ifdef PS2_MENU +void MessageScreen(char *msg) +{ + CRect rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + CRGBA color(255, 255, 255, 255); + + DoRWStuffStartOfFrame(50, 50, 50, 0, 0, 0, 255); + + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + + CSprite2d *splash = LoadSplash(NULL); + splash->Draw(rect, color, color, color, color); +#ifdef FIX_BUGS + splash->DrawRect(CRect(SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(110.0f), SCREEN_WIDTH-SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(300.0f)), CRGBA(50, 50, 50, 192)); +#else + splash->DrawRect(CRect(20.0f, 110.0f, SCREEN_WIDTH-20.0f, 300.0f), CRGBA(50, 50, 50, 192)); +#endif + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(190)); +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.0f), SCREEN_SCALE_Y(1.0f)); +#else + CFont::SetScale(1.0f, 1.0f); +#endif + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 190)); // 450.0f + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::SetDropColor(CRGBA(32, 32, 32, 255)); + CFont::SetDropShadowPosition(3); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetPropOn(); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH/2, SCREEN_SCALE_Y(130.0f), TheText.Get(msg)); +#else + CFont::PrintString(SCREEN_WIDTH/2, 130.0f, TheText.Get(msg)); +#endif + CFont::DrawFonts(); + + DoRWStuffEndOfFrame(); +} +#endif + +bool +CGame::InitialiseOnceBeforeRW(void) +{ + CFileMgr::Initialise(); + CdStreamInit(MAX_CDCHANNELS); + ValidateVersion(); +#ifdef EXTENDED_COLOURFILTER + CPostFX::InitOnce(); +#endif +#ifdef CUSTOM_FRONTEND_OPTIONS + // Not needed here but may be needed in future + // if (numCustomFrontendOptions == 0 && numCustomFrontendScreens == 0) + CustomFrontendOptionsPopulate(); +#endif + return true; +} + +#ifndef LIBRW +#ifdef PS2_MATFX +void ReplaceMatFxCallback(); +#endif // PS2_MATFX +#ifdef PS2_ALPHA_TEST +void ReplaceAtomicPipeCallback(); +#endif // PS2_ALPHA_TEST +#endif // !LIBRW + +bool +CGame::InitialiseRenderWare(void) +{ +#ifdef USE_TEXTURE_POOL + _TexturePoolsInitialise(); +#endif + +#if GTA_VERSION > GTA3_PS2_160 + CTxdStore::Initialise(); // in GameInit on ps2 + CVisibilityPlugins::Initialise(); // in plugin attach on ps2 +#endif + + //InitialiseScene(Scene); // PS2 only, only clears Scene.camera + +#ifdef GTA_PS2 + RpSkySelectTrueTSClipper(TRUE); + RpSkySelectTrueTLClipper(TRUE); + + // PS2ManagerApplyDirectionalLightingCB() uploads the GTA lights + // directly without going through RpWorld and all that + SetupPS2ManagerDefaultLightingCallback(); + PreAllocateRwObjects(); +#endif + + /* Create camera */ + Scene.camera = CameraCreate(SCREEN_WIDTH, SCREEN_HEIGHT, TRUE); + ASSERT(Scene.camera != nil); + if (!Scene.camera) + { + return (false); + } + + RwCameraSetFarClipPlane(Scene.camera, 2000.0f); // 250.0f on PS2 but who cares + RwCameraSetNearClipPlane(Scene.camera, 0.9f); + + CameraSize(Scene.camera, nil, DEFAULT_VIEWWINDOW, DEFAULT_ASPECT_RATIO); + + /* Create a world */ + RwBBox bbox; + + bbox.sup.x = bbox.sup.y = bbox.sup.z = 10000.0f; + bbox.inf.x = bbox.inf.y = bbox.inf.z = -10000.0f; + + Scene.world = RpWorldCreate(&bbox); + ASSERT(Scene.world != nil); + if (!Scene.world) + { + CameraDestroy(Scene.camera); + Scene.camera = nil; + return (false); + } + + /* Add the camera to the world */ + RpWorldAddCamera(Scene.world, Scene.camera); + LightsCreate(Scene.world); + +#if GTA_VERSION > GTA3_PS2_160 + CreateDebugFont(); // in GameInit on PS2 +#else + RwImageSetPath("textures"); +#endif + +#ifdef LIBRW +#ifdef PS2_MATFX + rw::MatFX::envMapApplyLight = true; + rw::MatFX::envMapUseMatColor = true; + rw::MatFX::envMapFlipU = true; +#else + rw::MatFX::envMapApplyLight = false; + rw::MatFX::envMapUseMatColor = false; + rw::MatFX::envMapFlipU = false; +#endif + rw::RGBA envcol = { 128, 128, 128, 255 }; + rw::MatFX::envMapColor = envcol; +#else +#ifdef PS2_MATFX + ReplaceMatFxCallback(); +#endif // PS2_MATFX +#ifdef PS2_ALPHA_TEST + ReplaceAtomicPipeCallback(); +#endif // PS2_ALPHA_TEST +#endif // LIBRW + + +#if GTA_VERSION > GTA3_PS2_160 + // in GameInit on PS2 + PUSH_MEMID(MEMID_TEXTURES); + CFont::Initialise(); + CHud::Initialise(); + POP_MEMID(); + // TODO: define + CPlayerSkin::Initialise(); +#endif + +#ifdef EXTENDED_PIPELINES + CustomPipes::CustomPipeInit(); // need Scene.world for this +#endif +#ifdef SCREEN_DROPLETS + ScreenDroplets::InitDraw(); +#endif + + return (true); +} + +// missing altogether on PS2 +void CGame::ShutdownRenderWare(void) +{ +#ifdef SCREEN_DROPLETS + ScreenDroplets::Shutdown(); +#endif +#ifdef EXTENDED_PIPELINES + CustomPipes::CustomPipeShutdown(); +#endif + + CMBlur::MotionBlurClose(); + DestroySplashScreen(); + CHud::Shutdown(); + CFont::Shutdown(); + + for ( int32 i = 0; i < NUMPLAYERS; i++ ) + CWorld::Players[i].DeletePlayerSkin(); + + // TODO: define + CPlayerSkin::Shutdown(); + + DestroyDebugFont(); + + /* Destroy world */ + LightsDestroy(Scene.world); + RpWorldRemoveCamera(Scene.world, Scene.camera); + RpWorldDestroy(Scene.world); + + /* destroy camera */ + CameraDestroy(Scene.camera); + + Scene.world = nil; + Scene.camera = nil; + + CVisibilityPlugins::Shutdown(); + +#ifdef USE_TEXTURE_POOL + _TexturePoolsShutdown(); +#endif +} + +// missing altogether on PS2 +bool CGame::InitialiseOnceAfterRW(void) +{ +#if GTA_VERSION > GTA3_PS2_160 + TheText.Load(); + DMAudio.Initialise(); // before TheGame() on PS2 + CTimer::Initialise(); + CTempColModels::Initialise(); + mod_HandlingManager.Initialise(); + CSurfaceTable::Initialise("DATA\\SURFACE.DAT"); + CPedStats::Initialise(); + CTimeCycle::Initialise(); + +#ifndef GTA_PS2 + if ( DMAudio.GetNum3DProvidersAvailable() == 0 ) + FrontEndMenuManager.m_nPrefsAudio3DProviderIndex = -1; + + if ( FrontEndMenuManager.m_nPrefsAudio3DProviderIndex == -99 || FrontEndMenuManager.m_nPrefsAudio3DProviderIndex == -2 ) { + CMenuManager::m_PrefsSpeakers = 0; + int32 i; + for (i = 0; i < DMAudio.GetNum3DProvidersAvailable(); i++) { + wchar buff[64]; + +#ifdef AUDIO_OAL + extern int defaultProvider; + if (defaultProvider >= 0 && defaultProvider < DMAudio.GetNum3DProvidersAvailable()) + break; +#endif + char *name = DMAudio.Get3DProviderName(i); + AsciiToUnicode(name, buff); + char *providername = UnicodeToAscii(buff); + strupr(providername); +#if defined(AUDIO_MSS) + if (strcmp(providername, "MILES FAST 2D POSITIONAL AUDIO") == 0) + break; +#elif defined(AUDIO_OAL) + if (strcmp(providername, "OPENAL SOFT") == 0) + break; +#endif + } + + FrontEndMenuManager.m_nPrefsAudio3DProviderIndex = i; + } + + DMAudio.SetCurrent3DProvider(FrontEndMenuManager.m_nPrefsAudio3DProviderIndex); + DMAudio.SetSpeakerConfig(CMenuManager::m_PrefsSpeakers); + DMAudio.SetDynamicAcousticModelingStatus(CMenuManager::m_PrefsDMA); + DMAudio.SetMusicMasterVolume(CMenuManager::m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(CMenuManager::m_PrefsSfxVolume); + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); +#endif + CWorld::Players[0].SetPlayerSkin(CMenuManager::m_PrefsSkinFile); +#endif + return true; +} + +// missing altogether on PS2 +void +CGame::FinalShutdown(void) +{ + CTxdStore::Shutdown(); + CPedStats::Shutdown(); + CdStreamShutdown(); +} + +#if GTA_VERSION <= GTA3_PS2_160 +bool CGame::Initialise(void) +#else +bool CGame::Initialise(const char* datFile) +#endif +{ +#ifdef GTA_PS2 + // TODO: upload VU0 collision code here +#endif + +#if GTA_VERSION > GTA3_PS2_160 + ResetLoadingScreenBar(); + strcpy(aDatFile, datFile); + CPools::Initialise(); // done in CWorld on PS2 +#endif + +#ifndef GTA_PS2 +#ifdef PED_CAR_DENSITY_SLIDERS + // Load density values from gta3.ini only if our re3.ini have them 1.f + if (CIniFile::PedNumberMultiplier == 1.f && CIniFile::CarNumberMultiplier == 1.f) +#endif + CIniFile::LoadIniFile(); +#endif + + currLevel = LEVEL_INDUSTRIAL; + + PUSH_MEMID(MEMID_TEXTURES); + LoadingScreen("Loading the Game", "Loading generic textures", GetRandomSplashScreen()); + gameTxdSlot = CTxdStore::AddTxdSlot("generic"); + CTxdStore::Create(gameTxdSlot); + CTxdStore::AddRef(gameTxdSlot); + +#ifdef EXTENDED_PIPELINES + // for generic fallback + CustomPipes::SetTxdFindCallback(); +#endif + + LoadingScreen("Loading the Game", "Loading particles", nil); + int particleTxdSlot = CTxdStore::AddTxdSlot("particle"); + CTxdStore::LoadTxd(particleTxdSlot, "MODELS/PARTICLE.TXD"); + CTxdStore::AddRef(particleTxdSlot); + CTxdStore::SetCurrentTxd(gameTxdSlot); + LoadingScreen("Loading the Game", "Setup game variables", nil); + POP_MEMID(); + +#ifdef GTA_PS2 + CDma::SyncChannel(0, true); +#endif + + CGameLogic::InitAtStartOfGame(); + CReferences::Init(); + TheCamera.Init(); + TheCamera.SetRwCamera(Scene.camera); + CDebug::DebugInitTextBuffer(); + ThePaths.Init(); + ThePaths.AllocatePathFindInfoMem(4500); + CWeather::Init(); + CCullZones::Init(); + CCollision::Init(); +#ifdef PS2_MENU // TODO: is this the right define? + TheText.Load(); +#endif + CTheZones::Init(); + CUserDisplay::Init(); + CMessages::Init(); +#if GTA_VERSION > GTA3_PS2_160 + CMessages::ClearAllMessagesDisplayedByGame(); +#endif + CRecordDataForGame::Init(); + CRestart::Initialise(); + + PUSH_MEMID(MEMID_WORLD); + CWorld::Initialise(); + POP_MEMID(); + +#if GTA_VERSION <= GTA3_PS2_160 + mod_HandlingManager.Initialise(); + CSurfaceTable::Initialise("DATA\\SURFACE.DAT"); + CTempColModels::Initialise(); +#endif + + PUSH_MEMID(MEMID_TEXTURES); + CParticle::Initialise(); + POP_MEMID(); + +#if GTA_VERSION <= GTA3_PS2_160 + gStartX = -180.0f; + gStartY = 180.0f; + gStartZ = 14.0f; +#endif + + PUSH_MEMID(MEMID_ANIMATION); + CAnimManager::Initialise(); + CCutsceneMgr::Initialise(); + POP_MEMID(); + + PUSH_MEMID(MEMID_CARS); + CCarCtrl::Init(); + POP_MEMID(); + + PUSH_MEMID(MEMID_DEF_MODELS); +#if GTA_VERSION > GTA3_PS2_160 + InitModelIndices(); +#endif + CModelInfo::Initialise(); + +#if GTA_VERSION > GTA3_PS2_160 + // probably moved before LoadLevel for multiplayer maps? + CPickups::Init(); + CTheCarGenerators::Init(); + + CdStreamAddImage("MODELS\\GTA3.IMG"); + + CFileLoader::LoadLevel("DATA\\DEFAULT.DAT"); + CFileLoader::LoadLevel(datFile); +#else + CPedStats::Initialise(); // InitialiseOnceAfterRW + + CFileLoader::LoadLevel("GTA3.DAT"); +#endif + + CWorld::AddParticles(); + CVehicleModelInfo::LoadVehicleColours(); + CVehicleModelInfo::LoadEnvironmentMaps(); + CTheZones::PostZoneCreation(); + POP_MEMID(); + +#if GTA_VERSION <= GTA3_PS2_160 + TestModelIndices(); +#endif + LoadingScreen("Loading the Game", "Setup paths", GetRandomSplashScreen()); + ThePaths.PreparePathData(); +#if GTA_VERSION > GTA3_PS2_160 + for (int i = 0; i < NUMPLAYERS; i++) + CWorld::Players[i].Clear(); + CWorld::Players[0].LoadPlayerSkin(); + TestModelIndices(); +#endif + + LoadingScreen("Loading the Game", "Setup water", nil); + CWaterLevel::Initialise("DATA\\WATER.DAT"); +#if GTA_VERSION <= GTA3_PS2_160 + CTimeCycle::Initialise(); // InitialiseOnceAfterRW +#else + TheConsole.Init(); +#endif + CDraw::SetFOV(120.0f); + CDraw::ms_fLODDistance = 500.0f; + + LoadingScreen("Loading the Game", "Setup streaming", nil); + CStreaming::Init(); + CStreaming::LoadInitialVehicles(); + CStreaming::LoadInitialPeds(); + CStreaming::RequestBigBuildings(LEVEL_GENERIC); + CStreaming::LoadAllRequestedModels(false); +#if GTA_VERSION > GTA3_PS2_160 + printf("Streaming uses %zuK of its memory", CStreaming::ms_memoryUsed / 1024); // original modifier was %d +#endif + + LoadingScreen("Loading the Game", "Load animations", GetRandomSplashScreen()); + PUSH_MEMID(MEMID_ANIMATION); + CAnimManager::LoadAnimFiles(); + POP_MEMID(); + + CPed::Initialise(); + CRouteNode::Initialise(); + CEventList::Initialise(); +#ifdef SCREEN_DROPLETS + ScreenDroplets::Initialise(); +#endif + LoadingScreen("Loading the Game", "Find big buildings", nil); + CRenderer::Init(); + + LoadingScreen("Loading the Game", "Setup game variables", nil); + CRadar::Initialise(); + CRadar::LoadTextures(); + CWeapon::InitialiseWeapons(); + + LoadingScreen("Loading the Game", "Setup traffic lights", nil); + CTrafficLights::ScanForLightsOnMap(); + CRoadBlocks::Init(); + + LoadingScreen("Loading the Game", "Setup game variables", nil); + CPopulation::Initialise(); +#if GTA_VERSION <= GTA3_PS2_160 + for (int i = 0; i < NUMPLAYERS; i++) + CWorld::Players[i].Clear(); +// CWorld::Players[0].LoadPlayerSkin(); // TODO: use a define for this +#endif + CWorld::PlayerInFocus = 0; + CCoronas::Init(); + CShadows::Init(); + CWeaponEffects::Init(); + CSkidmarks::Init(); + CAntennas::Init(); + CGlass::Init(); + gPhoneInfo.Initialise(); +#ifdef GTA_SCENE_EDIT + CSceneEdit::Initialise(); +#endif + + LoadingScreen("Loading the Game", "Load scripts", nil); + PUSH_MEMID(MEMID_SCRIPT); + CTheScripts::Init(); + CGangs::Initialise(); + POP_MEMID(); + + LoadingScreen("Loading the Game", "Setup game variables", nil); +#if GTA_VERSION <= GTA3_PS2_160 + CTimer::Initialise(); +#endif + CClock::Initialise(1000); +#if GTA_VERSION <= GTA3_PS2_160 + CTheCarGenerators::Init(); +#endif + CHeli::InitHelis(); + CCranes::InitCranes(); + CMovingThings::Init(); + CDarkel::Init(); + CStats::Init(); +#if GTA_VERSION <= GTA3_PS2_160 + CPickups::Init(); +#endif + CPacManPickups::Init(); +#if GTA_VERSION <= GTA3_PS2_160 + CGarages::Init(); +#endif + CRubbish::Init(); + CClouds::Init(); +#if GTA_VERSION <= GTA3_PS2_160 + CRemote::Init(); +#endif + CSpecialFX::Init(); + CWaterCannons::Init(); + CBridge::Init(); +#if GTA_VERSION > GTA3_PS2_160 + CGarages::Init(); +#endif + + LoadingScreen("Loading the Game", "Position dynamic objects", nil); + CWorld::RepositionCertainDynamicObjects(); +#if GTA_VERSION <= GTA3_PS2_160 + CCullZones::ResolveVisibilities(); +#endif + + LoadingScreen("Loading the Game", "Initialise vehicle paths", nil); +#if GTA_VERSION > GTA3_PS2_160 + CCullZones::ResolveVisibilities(); +#endif + CTrain::InitTrains(); + CPlane::InitPlanes(); + CCredits::Init(); + CRecordDataForChase::Init(); + CReplay::Init(); + + LoadingScreen("Loading the Game", "Start script", nil); +#ifdef PS2_MENU + if ( !TheMemoryCard.m_bWantToLoad ) +#endif + { + CTheScripts::StartTestScript(); + CTheScripts::Process(); + TheCamera.Process(); + } + + LoadingScreen("Loading the Game", "Load scene", nil); + CModelInfo::RemoveColModelsFromOtherLevels(currLevel); + CCollision::ms_collisionInMemory = currLevel; + for (int i = 0; i < MAX_PADS; i++) + CPad::GetPad(i)->Clear(true); + return true; +} + +bool CGame::ShutDown(void) +{ + CReplay::FinishPlayback(); + CPlane::Shutdown(); + CTrain::Shutdown(); + CSpecialFX::Shutdown(); +#if GTA_VERSION > GTA3_PS2_160 + CGarages::Shutdown(); +#endif + CMovingThings::Shutdown(); + gPhoneInfo.Shutdown(); + CWeapon::ShutdownWeapons(); + CPedType::Shutdown(); + CMBlur::MotionBlurClose(); + + for (int32 i = 0; i < NUMPLAYERS; i++) + { + if ( CWorld::Players[i].m_pPed ) + { + CWorld::Remove(CWorld::Players[i].m_pPed); + delete CWorld::Players[i].m_pPed; + CWorld::Players[i].m_pPed = nil; + } + + CWorld::Players[i].Clear(); + } + + CRenderer::Shutdown(); + CWorld::ShutDown(); + DMAudio.DestroyAllGameCreatedEntities(); + CModelInfo::ShutDown(); + CAnimManager::Shutdown(); + CCutsceneMgr::Shutdown(); + CVehicleModelInfo::DeleteVehicleColourTextures(); + CVehicleModelInfo::ShutdownEnvironmentMaps(); + CRadar::Shutdown(); + CStreaming::Shutdown(); + CTxdStore::GameShutdown(); + CCollision::Shutdown(); + CWaterLevel::Shutdown(); + CRubbish::Shutdown(); + CClouds::Shutdown(); + CShadows::Shutdown(); + CCoronas::Shutdown(); + CSkidmarks::Shutdown(); + CWeaponEffects::Shutdown(); + CParticle::Shutdown(); +#if GTA_VERSION > GTA3_PS2_160 + CPools::ShutDown(); +#endif + CTxdStore::RemoveTxdSlot(gameTxdSlot); + CdStreamRemoveImages(); + return true; +} + +void CGame::ReInitGameObjectVariables(void) +{ + CGameLogic::InitAtStartOfGame(); +#ifdef PS2_MENU + if ( !TheMemoryCard.m_bWantToLoad ) +#endif + { + TheCamera.Init(); + TheCamera.SetRwCamera(Scene.camera); + } + CDebug::DebugInitTextBuffer(); + CWeather::Init(); + CUserDisplay::Init(); + CMessages::Init(); + CRestart::Initialise(); + CWorld::bDoingCarCollisions = false; + CHud::ReInitialise(); + CRadar::Initialise(); +#if GTA_VERSION <= GTA3_PS2_160 + gStartX = -180.0f; + gStartY = 180.0f; + gStartZ = 14.0f; +#endif + CCarCtrl::ReInit(); + CTimeCycle::Initialise(); + CDraw::SetFOV(120.0f); + CDraw::ms_fLODDistance = 500.0f; + CStreaming::RequestBigBuildings(LEVEL_GENERIC); + CStreaming::LoadAllRequestedModels(false); + CPed::Initialise(); + CEventList::Initialise(); +#ifdef SCREEN_DROPLETS + ScreenDroplets::Initialise(); +#endif + CWeapon::InitialiseWeapons(); + CPopulation::Initialise(); + + for (int i = 0; i < NUMPLAYERS; i++) + CWorld::Players[i].Clear(); + + CWorld::PlayerInFocus = 0; +#if GTA_VERSION <= GTA3_PS2_160 + CWeaponEffects::Init(); + CSkidmarks::Init(); +#endif + CAntennas::Init(); + CGlass::Init(); + gPhoneInfo.Initialise(); + + PUSH_MEMID(MEMID_SCRIPT); + CTheScripts::Init(); + CGangs::Initialise(); + POP_MEMID(); + + CTimer::Initialise(); + CClock::Initialise(1000); + CTheCarGenerators::Init(); + CHeli::InitHelis(); + CMovingThings::Init(); + CDarkel::Init(); + CStats::Init(); + CPickups::Init(); + CPacManPickups::Init(); + CGarages::Init(); +#if GTA_VERSION <= GTA3_PS2_160 + CClouds::Init(); + CRemote::Init(); +#endif + CSpecialFX::Init(); + CWaterCannons::Init(); + CParticle::ReloadConfig(); + CCullZones::ResolveVisibilities(); + +#ifdef PS2_MENU + if ( !TheMemoryCard.m_bWantToLoad ) +#else + if ( !FrontEndMenuManager.m_bWantToLoad ) +#endif + { + CCranes::InitCranes(); + CTheScripts::StartTestScript(); + CTheScripts::Process(); + TheCamera.Process(); + CTrain::InitTrains(); + CPlane::InitPlanes(); + } + + for (int32 i = 0; i < MAX_PADS; i++) + CPad::GetPad(i)->Clear(true); +} + +void CGame::ReloadIPLs(void) +{ + CTimer::Stop(); + CWorld::RemoveStaticObjects(); + ThePaths.Init(); + CCullZones::Init(); + CFileLoader::ReloadPaths("GTA3.IDE"); + CFileLoader::LoadScene("INDUST.IPL"); + CFileLoader::LoadScene("COMMER.IPL"); + CFileLoader::LoadScene("SUBURBAN.IPL"); + CFileLoader::LoadScene("CULL.IPL"); + ThePaths.PreparePathData(); + CTrafficLights::ScanForLightsOnMap(); + CRoadBlocks::Init(); + CCranes::InitCranes(); + CGarages::Init(); + CWorld::RepositionCertainDynamicObjects(); + CCullZones::ResolveVisibilities(); + CRenderer::SortBIGBuildings(); + CTimer::Update(); +} + +void CGame::ShutDownForRestart(void) +{ + CReplay::FinishPlayback(); + CReplay::EmptyReplayBuffer(); + DMAudio.DestroyAllGameCreatedEntities(); + + for (int i = 0; i < NUMPLAYERS; i++) + CWorld::Players[i].Clear(); + + CGarages::SetAllDoorsBackToOriginalHeight(); + CTheScripts::UndoBuildingSwaps(); + CTheScripts::UndoEntityInvisibilitySettings(); + CWorld::ClearForRestart(); + CTimer::Shutdown(); + CStreaming::FlushRequestList(); + CStreaming::DeleteAllRwObjects(); + CStreaming::RemoveAllUnusedModels(); + CStreaming::ms_disableStreaming = false; + CRadar::RemoveRadarSections(); + FrontEndMenuManager.UnloadTextures(); + CParticleObject::RemoveAllParticleObjects(); +#if GTA_VERSION >= GTA3_PS2_160 + CPedType::Shutdown(); + CSpecialFX::Shutdown(); +#endif + TidyUpMemory(true, false); +} + +void CGame::InitialiseWhenRestarting(void) +{ + CRect rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + CRGBA color(255, 255, 255, 255); + + CTimer::Initialise(); + CSprite2d::SetRecipNearClip(); + +#ifdef PS2_MENU + if ( TheMemoryCard.b_FoundRecentSavedGameWantToLoad == true || TheMemoryCard.m_bWantToLoad == false ) + { + if ( TheMemoryCard.m_bWantToLoad == true ) + MessageScreen("MCLOAD"); // Loading Data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + else + MessageScreen("RESTART"); // Starting new game + } +#endif + +#ifdef PS2_MENU + TheMemoryCard.b_FoundRecentSavedGameWantToLoad = false; +#else + b_FoundRecentSavedGameWantToLoad = false; +#endif + + TheCamera.Init(); + +#ifdef PS2_MENU + if ( TheMemoryCard.m_bWantToLoad == true ) + { + TheMemoryCard.RestoreForStartLoad(); + CStreaming::LoadScene(TheCamera.GetPosition()); + } +#else + if ( FrontEndMenuManager.m_bWantToLoad == true ) + { + RestoreForStartLoad(); + CStreaming::LoadScene(TheCamera.GetPosition()); + } +#endif + + ReInitGameObjectVariables(); + +#ifdef PS2_MENU + if ( TheMemoryCard.m_bWantToLoad == true ) + { + if ( TheMemoryCard.LoadSavedGame() == CMemoryCard::RES_SUCCESS ) + { + for ( int32 i = 0; i < 35; i++ ) + { + MessageScreen("FESZ_LS"); // Load Successful. + } + + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); + CTrain::InitTrains(); + CPlane::InitPlanes(); + } + else + { + for ( int32 i = 0; i < 50; i++ ) + { + DoRWStuffStartOfFrame(50, 50, 50, 0, 0, 0, 255); + + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + + CSprite2d *splash = LoadSplash(NULL); + splash->Draw(rect, color, color, color, color); +#ifdef FIX_BUGS + splash->DrawRect(CRect(SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(110.0f), SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_Y(300.0f)), CRGBA(50, 50, 50, 192)); +#else + splash->DrawRect(CRect(20.0f, 110.0f, SCREEN_WIDTH-20.0f, 300.0f), CRGBA(50, 50, 50, 192)); +#endif + + CFont::SetBackgroundOff(); +#ifdef ASPECT_RATIO_SCALE + CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(160.0f)); // because SCREEN_SCALE_FROM_RIGHT(x) != SCREEN_SCALE_X(640-x) +#else + CFont::SetWrapx(SCREEN_SCALE_X(480.0f)); +#endif + CFont::SetScale(SCREEN_SCALE_X(1.0f), SCREEN_SCALE_Y(1.0f)); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(480.0f)); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetDropColor(CRGBA(32, 32, 32, 255)); + CFont::SetDropShadowPosition(3); + CFont::SetPropOn(); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH/2, SCREEN_SCALE_Y(130.0f), TheText.Get("MC_LDFL")); // Load Failed! + CFont::PrintString(SCREEN_WIDTH/2, SCREEN_SCALE_Y(170.0f), TheText.Get("FES_NOC")); // No Memory Card (PS2) in MEMORY CARD slot 1. + CFont::PrintString(SCREEN_WIDTH/2, SCREEN_SCALE_Y(240.0f), TheText.Get("MC_NWRE")); // Now Restarting Game. +#else + CFont::PrintString(SCREEN_WIDTH/2, 130.0f, TheText.Get("MC_LDFL")); // Load Failed! + CFont::PrintString(SCREEN_WIDTH/2, 170.0f, TheText.Get("FES_NOC")); // No Memory Card (PS2) in MEMORY CARD slot 1. + CFont::PrintString(SCREEN_WIDTH/2, 240.0f, TheText.Get("MC_NWRE")); // Now Restarting Game. +#endif + CFont::DrawFonts(); + + DoRWStuffEndOfFrame(); + } + + ShutDownForRestart(); + CTimer::Stop(); + CTimer::Initialise(); + TheMemoryCard.m_bWantToLoad = false; + ReInitGameObjectVariables(); + currLevel = LEVEL_INDUSTRIAL; + CCollision::SortOutCollisionAfterLoad(); + + FrontEndMenuManager.SetSoundLevelsForMusicMenu(); + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); + } + } +#else + if ( FrontEndMenuManager.m_bWantToLoad == true ) + { + if ( GenericLoad() == true ) + { + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); + CTrain::InitTrains(); + CPlane::InitPlanes(); + } + else + { + for ( int32 i = 0; i < 50; i++ ) + { + HandleExit(); + FrontEndMenuManager.MessageScreen("FED_LFL"); // Loading save game has failed. The game will restart now. + } + + ShutDownForRestart(); + CTimer::Stop(); + CTimer::Initialise(); + FrontEndMenuManager.m_bWantToLoad = false; + ReInitGameObjectVariables(); + currLevel = LEVEL_INDUSTRIAL; + CCollision::SortOutCollisionAfterLoad(); + } + } +#endif + + CTimer::Update(); + + DMAudio.ChangeMusicMode(MUSICMODE_GAME); +} + +void CGame::Process(void) +{ + CPad::UpdatePads(); +#ifdef USE_CUSTOM_ALLOCATOR + ProcessTidyUpMemory(); +#endif + TheCamera.SetMotionBlurAlpha(0); + if (TheCamera.m_BlurType == MOTION_BLUR_NONE || TheCamera.m_BlurType == MOTION_BLUR_SNIPER || TheCamera.m_BlurType == MOTION_BLUR_LIGHT_SCENE) + TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE); +#ifdef DEBUGMENU + DebugMenuProcess(); +#endif + CCutsceneMgr::Update(); + + PUSH_MEMID(MEMID_FRONTEND); + if (!CCutsceneMgr::IsCutsceneProcessing() && !CTimer::GetIsCodePaused()) + FrontEndMenuManager.Process(); + POP_MEMID(); + + CStreaming::Update(); + if (!CTimer::GetIsPaused()) + { + CTheZones::Update(); + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + CRecordDataForGame::SaveOrRetrieveDataForThisFrame(); + CRecordDataForChase::SaveOrRetrieveDataForThisFrame(); + CPad::DoCheats(); + CClock::Update(); + CWeather::Update(); + + PUSH_MEMID(MEMID_SCRIPT); + CTheScripts::Process(); + POP_MEMID(); + + CCollision::Update(); + CTrain::UpdateTrains(); + CPlane::UpdatePlanes(); + CHeli::UpdateHelis(); + CDarkel::Update(); + CSkidmarks::Update(); + CAntennas::Update(); + CGlass::Update(); +#ifdef GTA_SCENE_EDIT + CSceneEdit::Update(); +#endif + CEventList::Update(); + CParticle::Update(); + gFireManager.Update(); + CPopulation::Update(); + CWeapon::UpdateWeapons(); + if (!CCutsceneMgr::IsRunning()) + CTheCarGenerators::Process(); + if (!CReplay::IsPlayingBack()) + CCranes::UpdateCranes(); + CClouds::Update(); + CMovingThings::Update(); + CWaterCannons::Update(); + CUserDisplay::Process(); + CReplay::Update(); + + PUSH_MEMID(MEMID_WORLD); + CWorld::Process(); + POP_MEMID(); + + gAccidentManager.Update(); + CPacManPickups::Update(); + CPickups::Update(); + CGarages::Update(); + CRubbish::Update(); + CSpecialFX::Update(); + CTimeCycle::Update(); + if (CReplay::ShouldStandardCameraBeProcessed()) + TheCamera.Process(); + CCullZones::Update(); + if (!CReplay::IsPlayingBack()) + CGameLogic::Update(); + CBridge::Update(); + CCoronas::DoSunAndMoon(); + CCoronas::Update(); + CShadows::UpdateStaticShadows(); + CShadows::UpdatePermanentShadows(); + gPhoneInfo.Update(); + if (!CReplay::IsPlayingBack()) + { + PUSH_MEMID(MEMID_CARS); + CCarCtrl::GenerateRandomCars(); + CRoadBlocks::GenerateRoadBlocks(); + CCarCtrl::RemoveDistantCars(); + POP_MEMID(); + } + } +#ifdef GTA_PS2 + CMemCheck::DoTest(); +#endif +} + +#ifdef USE_CUSTOM_ALLOCATOR + +int32 gNumMemMoved; + +bool +MoveMem(void **ptr) +{ + if(*ptr){ + gNumMemMoved++; + void *newPtr = gMainHeap.MoveMemory(*ptr); + if(*ptr != newPtr){ + *ptr = newPtr; + return true; + } + } + return false; +} + +// Some convenience structs +struct SkyDataPrefix +{ + uint32 pktSize1; + uint32 data; // pointer to data as read from TXD + uint32 pktSize2; + uint32 unused; +}; + +struct DMAGIFUpload +{ + uint32 tag1_qwc, tag1_addr; // dmaref + uint32 nop1, vif_direct1; + + uint32 giftag[4]; + uint32 gs_bitbltbuf[4]; + + uint32 tag2_qwc, tag2_addr; // dmaref + uint32 nop2, vif_direct2; +}; + +// This is very scary. it depends on the exact memory layout of the DMA chains and whatnot +RwTexture * +MoveTextureMemoryCB(RwTexture *texture, void *pData) +{ +#ifdef GTA_PS2 + bool *pRet = (bool*)pData; + RwRaster *raster = RwTextureGetRaster(texture); + _SkyRasterExt *rasterExt = RASTEREXTFROMRASTER(raster); + if(raster->originalPixels == nil || // the raw data + raster->cpPixels == raster->originalPixels || // old format, can't handle it + rasterExt->dmaRefCount != 0 && rasterExt->dmaClrCount != 0) + return texture; + + // this is the allocated pointer we will move + SkyDataPrefix *prefix = (SkyDataPrefix*)raster->originalPixels; + DMAGIFUpload *uploads = (DMAGIFUpload*)(prefix+1); + + // We have 4qw for each upload, + // i.e. for each buffer width of mip levels, + // and the palette if there is one. + // NB: this code does NOT support mipmaps! + // so we assume two uploads (pixels and palette) + // + // each upload looks like this: + // (DMAcnt; NOP; VIF DIRECT(2)) + // giftag (1, A+D) + // GS_BITBLTBUF + // (DMAref->pixel data; NOP; VIF DIRECT(5)) + // the DMArefs are what we have to adjust + uintptr dataDiff, upload1Diff, upload2Diff, pixelDiff, paletteDiff; + dataDiff = prefix->data - (uintptr)raster->originalPixels; + upload1Diff = uploads[0].tag2_addr - (uintptr)raster->originalPixels; + if(raster->palette) + upload2Diff = uploads[1].tag2_addr - (uintptr)raster->originalPixels; + pixelDiff = (uintptr)raster->cpPixels - (uintptr)raster->originalPixels; + if(raster->palette) + paletteDiff = (uintptr)raster->palette - (uintptr)raster->originalPixels; + uint8 *newptr = (uint8*)gMainHeap.MoveMemory(raster->originalPixels); + if(newptr != raster->originalPixels){ + // adjust everything + prefix->data = (uintptr)newptr + dataDiff; + uploads[0].tag2_addr = (uintptr)newptr + upload1Diff; + if(raster->palette) + uploads[1].tag2_addr = (uintptr)newptr + upload2Diff; + raster->originalPixels = newptr; + raster->cpPixels = newptr + pixelDiff; + if(raster->palette) + raster->palette = newptr + paletteDiff; + + if(pRet){ + *pRet = true; + return nil; + } + } +#else + // nothing to do here really, everything should be in videomemory +#endif + return texture; +} + +bool +MoveAtomicMemory(RpAtomic *atomic, bool onlyOne) +{ + RpGeometry *geo = RpAtomicGetGeometry(atomic); + +#if THIS_IS_COMPATIBLE_WITH_GTA3_RW31 + if(MoveMem((void**)&geo->triangles) && onlyOne) + return true; + if(MoveMem((void**)&geo->matList.materials) && onlyOne) + return true; + if(MoveMem((void**)&geo->preLitLum) && onlyOne) + return true; + if(MoveMem((void**)&geo->texCoords[0]) && onlyOne) + return true; + if(MoveMem((void**)&geo->texCoords[1]) && onlyOne) + return true; + + // verts and normals of morph target are allocated together + int vertDiff; + if(geo->morphTarget->normals) + vertDiff = geo->morphTarget->normals - geo->morphTarget->verts; + if(MoveMem((void**)&geo->morphTarget->verts)){ + if(geo->morphTarget->normals) + geo->morphTarget->normals = geo->morphTarget->verts + vertDiff; + if(onlyOne) + return true; + } + + RpMeshHeader *oldmesh = geo->mesh; + if(MoveMem((void**)&geo->mesh)){ + // index pointers are allocated together with meshes, + // have to relocate those too + RpMesh *mesh = (RpMesh*)(geo->mesh+1); + uintptr reloc = (uintptr)geo->mesh - (uintptr)oldmesh; + for(int i = 0; i < geo->mesh->numMeshes; i++) + mesh[i].indices = (RxVertexIndex*)((uintptr)mesh[i].indices + reloc); + if(onlyOne) + return true; + } +#else + // we could do something in librw here +#endif + return false; +} + +bool +MoveColModelMemory(CColModel &colModel, bool onlyOne) +{ +#if GTA_VERSION >= GTA3_PS2_160 + // hm...should probably only do this if ownsCollisionVolumes + // but it doesn't exist on PS2... + if(!colModel.ownsCollisionVolumes) + return false; +#endif + + if(MoveMem((void**)&colModel.spheres) && onlyOne) + return true; + if(MoveMem((void**)&colModel.lines) && onlyOne) + return true; + if(MoveMem((void**)&colModel.boxes) && onlyOne) + return true; + if(MoveMem((void**)&colModel.vertices) && onlyOne) + return true; + if(MoveMem((void**)&colModel.triangles) && onlyOne) + return true; + if(MoveMem((void**)&colModel.trianglePlanes) && onlyOne) + return true; + return false; +} + +RpAtomic* +MoveAtomicMemoryCB(RpAtomic *atomic, void *pData) +{ + bool *pRet = (bool*)pData; + if(pRet == nil) + MoveAtomicMemory(atomic, false); + else if(MoveAtomicMemory(atomic, true)){ + *pRet = true; + return nil; + } + return atomic; +} + +bool +TidyUpModelInfo(CBaseModelInfo *modelInfo, bool onlyone) +{ + if(modelInfo->GetColModel() && modelInfo->DoesOwnColModel()) + if(MoveColModelMemory(*modelInfo->GetColModel(), onlyone)) + return true; + + RwObject *rwobj = modelInfo->GetRwObject(); + if(RwObjectGetType(rwobj) == rpATOMIC) + if(MoveAtomicMemory((RpAtomic*)rwobj, onlyone)) + return true; + if(RwObjectGetType(rwobj) == rpCLUMP){ + bool ret = false; + if(onlyone) + RpClumpForAllAtomics((RpClump*)rwobj, MoveAtomicMemoryCB, &ret); + else + RpClumpForAllAtomics((RpClump*)rwobj, MoveAtomicMemoryCB, nil); + if(ret) + return true; + } + + if(modelInfo->GetModelType() == MITYPE_PED && ((CPedModelInfo*)modelInfo)->m_hitColModel) + if(MoveColModelMemory(*((CPedModelInfo*)modelInfo)->m_hitColModel, onlyone)) + return true; + + return false; +} +#endif + +void CGame::DrasticTidyUpMemory(bool flushDraw) +{ +#ifdef USE_CUSTOM_ALLOCATOR + bool removedCol = false; + + TidyUpMemory(true, flushDraw); + + if(gMainHeap.GetLargestFreeBlock() < 200000 && !playingIntro){ + CStreaming::RemoveIslandsNotUsed(LEVEL_INDUSTRIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_COMMERCIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN); + TidyUpMemory(true, flushDraw); + } + + if(gMainHeap.GetLargestFreeBlock() < 200000 && !playingIntro){ + CModelInfo::RemoveColModelsFromOtherLevels(LEVEL_GENERIC); + TidyUpMemory(true, flushDraw); + removedCol = true; + } + + if(gMainHeap.GetLargestFreeBlock() < 200000 && !playingIntro){ + CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL); + CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL); + CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN); + TidyUpMemory(true, flushDraw); + } + + if(removedCol){ + // different on PS2 + CFileLoader::LoadCollisionFromDatFile(CCollision::ms_collisionInMemory); + } + + if(!playingIntro) + CStreaming::RequestBigBuildings(currLevel); + + CStreaming::LoadAllRequestedModels(true); +#endif +} + +void CGame::TidyUpMemory(bool moveTextures, bool flushDraw) +{ +#ifdef USE_CUSTOM_ALLOCATOR + printf("Largest free block before tidy %d\n", gMainHeap.GetLargestFreeBlock()); + + if(moveTextures){ + if(flushDraw){ +#ifdef GTA_PS2 + for(int i = 0; i < sweMaxFlips+1; i++){ +#else + for(int i = 0; i < 5; i++){ // probably more than needed +#endif + RwCameraBeginUpdate(Scene.camera); + RwCameraEndUpdate(Scene.camera); + RwCameraShowRaster(Scene.camera, nil, 0); + } + } + int fontSlot = CTxdStore::FindTxdSlot("fonts"); + + for(int i = 0; i < TXDSTORESIZE; i++){ + if(i == fontSlot || + CTxdStore::GetSlot(i) == nil) + continue; + RwTexDictionary *txd = CTxdStore::GetSlot(i)->texDict; + if(txd) + RwTexDictionaryForAllTextures(txd, MoveTextureMemoryCB, nil); + } + } + + // animations + for(int i = 0; i < NUMANIMATIONS; i++){ + CAnimBlendHierarchy *anim = CAnimManager::GetAnimation(i); + if(anim == nil) + continue; // cannot happen + anim->MoveMemory(); + } + + // model info + for(int i = 0; i < MODELINFOSIZE; i++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(i); + if(mi == nil) + continue; + TidyUpModelInfo(mi, false); + } + + printf("Largest free block after tidy %d\n", gMainHeap.GetLargestFreeBlock()); +#endif +} + +void CGame::ProcessTidyUpMemory(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR + static int32 modelIndex = 0; + static int32 animIndex = 0; + static int32 txdIndex = 0; + bool txdReturn = false; + RwTexDictionary *txd = nil; + gNumMemMoved = 0; + + // model infos + for(int numCleanedUp = 0; numCleanedUp < 10; numCleanedUp++){ + CBaseModelInfo *mi; + do{ + mi = CModelInfo::GetModelInfo(modelIndex); + modelIndex++; + if(modelIndex >= MODELINFOSIZE) + modelIndex = 0; + }while(mi == nil); + + if(TidyUpModelInfo(mi, true)) + return; + } + + // tex dicts + for(int numCleanedUp = 0; numCleanedUp < 3; numCleanedUp++){ + if(gNumMemMoved > 80) + break; + + do{ +#ifdef FIX_BUGS + txd = nil; +#endif + if(CTxdStore::GetSlot(txdIndex)) + txd = CTxdStore::GetSlot(txdIndex)->texDict; + txdIndex++; + if(txdIndex >= TXDSTORESIZE) + txdIndex = 0; + }while(txd == nil); + + RwTexDictionaryForAllTextures(txd, MoveTextureMemoryCB, &txdReturn); + if(txdReturn) + return; + } + + // animations + CAnimBlendHierarchy *anim; + do{ + anim = CAnimManager::GetAnimation(animIndex); + animIndex++; + if(animIndex >= NUMANIMATIONS) + animIndex = 0; + }while(anim == nil); // always != nil + anim->MoveMemory(true); +#endif +} diff --git a/src/core/Game.h b/src/core/Game.h new file mode 100644 index 0000000..002033a --- /dev/null +++ b/src/core/Game.h @@ -0,0 +1,49 @@ +#pragma once + +enum eLevelName { + LEVEL_IGNORE = -1, // beware, this is only used in CPhysical's m_nZoneLevel + LEVEL_GENERIC = 0, + LEVEL_INDUSTRIAL, + LEVEL_COMMERCIAL, + LEVEL_SUBURBAN, + NUM_LEVELS +}; + +class CGame +{ +public: + static eLevelName currLevel; + static bool bDemoMode; + static bool nastyGame; + static bool frenchGame; + static bool germanGame; +#ifdef MORE_LANGUAGES + static bool russianGame; + static bool japaneseGame; +#endif + static bool noProstitutes; + static bool playingIntro; + static char aDatFile[32]; + + static bool InitialiseOnceBeforeRW(void); + static bool InitialiseRenderWare(void); + static void ShutdownRenderWare(void); + static bool InitialiseOnceAfterRW(void); + static void FinalShutdown(void); +#if GTA_VERSION <= GTA3_PS2_160 + static bool Initialise(void); +#else + static bool Initialise(const char *datFile); +#endif + static bool ShutDown(void); + static void ReInitGameObjectVariables(void); + static void ReloadIPLs(void); + static void ShutDownForRestart(void); + static void InitialiseWhenRestarting(void); + static void Process(void); + + // NB: these do something on PS2 + static void TidyUpMemory(bool, bool); + static void DrasticTidyUpMemory(bool); + static void ProcessTidyUpMemory(void); +}; diff --git a/src/core/General.h b/src/core/General.h new file mode 100644 index 0000000..d4b941d --- /dev/null +++ b/src/core/General.h @@ -0,0 +1,157 @@ +#pragma once + +#include + +class CGeneral +{ +public: + static float GetATanOfXY(float x, float y){ + if(x == 0.0f && y == 0.0f) + return 0.0f; + float xabs = Abs(x); + float yabs = Abs(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 LimitAngle(float angle) + { + float result = angle; + + while (result >= 180.0f) { + result -= 2 * 180.0f; + } + + while (result < -180.0f) { + result += 2 * 180.0f; + } + + return result; + } + + + static float LimitRadianAngle(float angle) + { + float result = Clamp(angle, -25.0f, 25.0f); + + while (result >= PI) { + result -= 2 * PI; + } + + while (result < -PI) { + result += 2 * PI; + } + + return result; + } + + // Returns an angle such that x2/y2 looks at x1/y1 with its forward vector if rotated by that angle + 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); + } + } + + static float GetAngleBetweenPoints(float x1, float y1, float x2, float y2) + { + return RADTODEG(GetRadianAngleBetweenPoints(x1, y1, x2, y2)); + } + + // should return direction in 0-8 range. fits perfectly to peds' path directions. + static int GetNodeHeadingFromVector(float x, float y) + { + float angle = CGeneral::GetRadianAngleBetweenPoints(x, y, 0.0f, 0.0f); + if (angle < 0.0f) + angle += TWOPI; + + angle = DEGTORAD(22.5f) + TWOPI - angle; + + if (angle >= TWOPI) + angle -= TWOPI; + + return (int)Floor(angle / DEGTORAD(45.0f)); + } + + // Unlike usual string comparison functions, these don't care about greater or lesser + static bool faststrcmp(const char *str1, const char *str2) + { + for (; *str1; str1++, str2++) { + if (*str1 != *str2) + return true; + } + return *str2 != '\0'; + } + + static bool faststrncmp(const char *str1, const char *str2, uint32 count) + { + for(uint32 i = 0; *str1 && i < count; str1++, str2++, i++) { + if (*str1 != *str2) + return true; + } + return false; + } + + static bool faststricmp(const char *str1, const char *str2) + { + for (; *str1; str1++, str2++) { +#ifndef ASCII_STRCMP + if (toupper(*str1) != toupper(*str2)) +#else + if (__ascii_toupper(*str1) != __ascii_toupper(*str2)) +#endif + return true; + } + return *str2 != '\0'; + } + + // not too sure about all these... + static uint16 GetRandomNumber(void) + { return myrand() & MYRAND_MAX; } + static bool GetRandomTrueFalse(void) + { return GetRandomNumber() < MYRAND_MAX / 2; } + // 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/IniFile.cpp b/src/core/IniFile.cpp new file mode 100644 index 0000000..524632f --- /dev/null +++ b/src/core/IniFile.cpp @@ -0,0 +1,28 @@ +#include "common.h" + +#include "IniFile.h" + +#include "CarCtrl.h" +#include "FileMgr.h" +#include "main.h" +#include "Population.h" + +float CIniFile::PedNumberMultiplier = 1.0f; +float CIniFile::CarNumberMultiplier = 1.0f; + +void CIniFile::LoadIniFile() +{ + CFileMgr::SetDir(""); + int f = CFileMgr::OpenFile("gta3.ini", "r"); + if (f){ + CFileMgr::ReadLine(f, gString, 200); + sscanf(gString, "%f", &PedNumberMultiplier); + PedNumberMultiplier = Min(3.0f, Max(0.5f, PedNumberMultiplier)); + CFileMgr::ReadLine(f, gString, 200); + sscanf(gString, "%f", &CarNumberMultiplier); + CarNumberMultiplier = Min(3.0f, Max(0.5f, CarNumberMultiplier)); + CFileMgr::CloseFile(f); + } + CPopulation::MaxNumberOfPedsInUse = DEFAULT_MAX_NUMBER_OF_PEDS * PedNumberMultiplier; + CCarCtrl::MaxNumberOfCarsInUse = DEFAULT_MAX_NUMBER_OF_CARS * CarNumberMultiplier; +} \ No newline at end of file diff --git a/src/core/IniFile.h b/src/core/IniFile.h new file mode 100644 index 0000000..30dc8c2 --- /dev/null +++ b/src/core/IniFile.h @@ -0,0 +1,13 @@ +#pragma once + +#define DEFAULT_MAX_NUMBER_OF_PEDS 25.0f +#define DEFAULT_MAX_NUMBER_OF_CARS 12.0f + +class CIniFile +{ +public: + static void LoadIniFile(); + + static float PedNumberMultiplier; + static float CarNumberMultiplier; +}; diff --git a/src/core/Lists.cpp b/src/core/Lists.cpp new file mode 100644 index 0000000..448a0ff --- /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 0000000..7572e88 --- /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.cpp b/src/core/MenuScreens.cpp new file mode 100644 index 0000000..247de98 --- /dev/null +++ b/src/core/MenuScreens.cpp @@ -0,0 +1,453 @@ +#include "common.h" +#include "Frontend.h" +#ifdef PC_MENU + +// Please don't touch this file, except for bug fixing or ports. +// Check MenuScreensCustom.cpp + +#ifndef CUSTOM_FRONTEND_OPTIONS +CMenuScreen aScreens[MENUPAGES] = { + // MENUPAGE_NONE = 0 + { "", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, }, + + // MENUPAGE_STATS = 1 + { "FET_STA", 1, MENUPAGE_NONE, MENUPAGE_NONE, 5, 2, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_NEW_GAME = 2 + { "FET_SGA", 1, MENUPAGE_NONE, MENUPAGE_NONE, 0, 1, + MENUACTION_CHANGEMENU, "FES_SNG", SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD, + MENUACTION_POPULATESLOTS_CHANGEMENU, "GMLOAD", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT, + MENUACTION_POPULATESLOTS_CHANGEMENU, "FES_DGA", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_BRIEFS = 3 + { "FET_BRE", 1, MENUPAGE_NONE, MENUPAGE_NONE, 6, 3, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CONTROLLER_SETTINGS = 4 + { "FET_CON", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 0, 0, + MENUACTION_CTRLCONFIG, "FEC_CCF", SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS, + MENUACTION_CTRLDISPLAY, "FEC_CDP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS, + MENUACTION_CTRLVIBRATION, "FEC_VIB", SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SOUND_SETTINGS = 5 + { "FET_AUD", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 1, 1, + MENUACTION_MUSICVOLUME, "FEA_MUS", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_SFXVOLUME, "FEA_SFX", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, +#ifdef EXTERNAL_3D_SOUND + MENUACTION_AUDIOHW, "FEA_3DH", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_SPEAKERCONF, "FEA_SPK", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, +#endif +#ifdef AUDIO_REFLECTIONS + MENUACTION_DYNAMICACOUSTIC, "FET_DAM", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, +#endif + 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_NONE, + }, + + // MENUPAGE_DISPLAY_SETTINGS = 6 + { "FET_DIS", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 2, 2, + MENUACTION_BRIGHTNESS, "FED_BRI", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_DRAWDIST, "FEM_LOD", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_FRAMESYNC, "FEM_VSC", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_FRAMELIMIT, "FEM_FRM", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_TRAILS, "FED_TRA", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_SUBTITLES, "FED_SUB", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_WIDESCREEN, "FED_WIS", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_SCREENRES, "FED_RES", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_LANGUAGE_SETTINGS = 7 + { "FET_LAN", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 3, 3, + MENUACTION_LANG_ENG, "FEL_ENG", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_LANG_FRE, "FEL_FRE", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_LANG_GER, "FEL_GER", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_LANG_ITA, "FEL_ITA", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_LANG_SPA, "FEL_SPA", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CHOOSE_LOAD_SLOT = 8 + { "FET_LG", 1, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, 1, 1, + MENUACTION_CHANGEMENU, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_CHECKSAVE, "FEM_SL0", SAVESLOT_1, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL1", SAVESLOT_2, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL2", SAVESLOT_3, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL3", SAVESLOT_4, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL4", SAVESLOT_5, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL5", SAVESLOT_6, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL6", SAVESLOT_7, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL7", SAVESLOT_8, MENUPAGE_LOAD_SLOT_CONFIRM, + }, + + // MENUPAGE_CHOOSE_DELETE_SLOT = 9 + { "FET_DG", 1, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, 2, 2, + MENUACTION_CHANGEMENU, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_CHANGEMENU, "FEM_SL0", SAVESLOT_1, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL1", SAVESLOT_2, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL2", SAVESLOT_3, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL3", SAVESLOT_4, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL4", SAVESLOT_5, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL5", SAVESLOT_6, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL6", SAVESLOT_7, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL7", SAVESLOT_8, MENUPAGE_DELETE_SLOT_CONFIRM, + }, + + // MENUPAGE_NEW_GAME_RELOAD = 10 + { "FET_NG", 1, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, 0, 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_NEW_GAME_RELOAD, + }, + + // MENUPAGE_LOAD_SLOT_CONFIRM = 11 + { "FET_LG", 1, MENUPAGE_CHOOSE_LOAD_SLOT, MENUPAGE_CHOOSE_LOAD_SLOT, 0, 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", 1, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, 0, 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_NO_MEMORY_CARD = 13 + { "FES_NOC", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + // hud adjustment page in mobile + }, + + // MENUPAGE_LOADING_IN_PROGRESS = 14 + { "FET_LG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FED_LDW", SAVESLOT_NONE, MENUPAGE_LOAD_SLOT_CONFIRM, + }, + + // MENUPAGE_DELETING_IN_PROGRESS = 15 + { "FET_DG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FEDL_WR", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_PS2_LOAD_FAILED = 16 + { "FET_LG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FES_LOE", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_DELETE_FAILED = 17 + { "FET_DG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 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", 1, MENUPAGE_NONE, MENUPAGE_NONE, 4, 0, + MENUACTION_RELOADIDE, "FED_RID", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_RELOADIPL, "FED_RIP", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SETDBGFLAG, "FED_DFL", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SWITCHBIGWHITEDEBUGLIGHT, "FED_DLS", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_PEDROADGROUPS, "FED_SPR", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CARROADGROUPS, "FED_SCR", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_COLLISIONPOLYS, "FED_SCP", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_PARSEHEAP, "FED_PAH", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SHOWCULL, "FED_SCZ", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_DEBUGSTREAM, "FED_DSR", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_MEMORY_CARD_DEBUG = 19 + { "FEM_MCM", 1, MENUPAGE_NONE, MENUPAGE_NONE, 7, 0, + MENUACTION_REGMEMCARD1, "FEM_RMC", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_TESTFORMATMEMCARD1, "FEM_TFM", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_TESTUNFORMATMEMCARD1, "FEM_TUM", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CREATEROOTDIR, "FEM_CRD", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CREATELOADICONS, "FEM_CLI", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_FILLWITHGUFF, "FEM_FFF", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SAVEONLYTHEGAME, "FEM_SOG", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SAVEGAME, "FEM_STG", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SAVEGAMEUNDERGTA, "FEM_STS", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CREATECOPYPROTECTED, "FEM_CPD", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_MEMORY_CARD_TEST = 20 + { "FEM_MC2", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_MAIN = 21 + { "FET_MP", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_PS2_SAVE_FAILED = 22 + { "MCDNSP", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_PS2_SAVE_FAILED_2 = 23 + { "MCGNSP", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // Unused in PC but anyway + // MENUPAGE_SAVE = 24 +#ifdef PS2_SAVE_DIALOG + { "FET_SG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_CHANGEMENU, "FESZ_SA", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE, + }, +#else + { "FET_SG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FES_SCG", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_POPULATESLOTS_CHANGEMENU, "GMSAVE", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE, + }, +#endif + + // MENUPAGE_NO_MEMORY_CARD_2 = 25 + { "FES_NOC", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_CHANGEMENU, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CHOOSE_SAVE_SLOT = 26 + { "FET_SG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_SL1", SAVESLOT_1, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL2", SAVESLOT_2, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL3", SAVESLOT_3, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL4", SAVESLOT_4, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL5", SAVESLOT_5, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL6", SAVESLOT_6, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL7", SAVESLOT_7, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL8", SAVESLOT_8, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + }, + + // MENUPAGE_SAVE_OVERWRITE_CONFIRM = 27 + { "FET_SG", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 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", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_CONNECTION = 29 + { "FET_CON", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_FIND_GAME = 30 + { "FET_FG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_MODE = 31 + { "FET_GT", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_CREATE = 32 + { "FET_HG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_START = 33 + { "FEN_STA", 2, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_SKIN_SELECT_OLD = 34 + { "FET_PS", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_CONTROLLER_PC = 35 + { "FET_CTL", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 0, 0, +#ifdef PC_PLAYER_CONTROLS + MENUACTION_CTRLMETHOD, "FET_CME", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, +#endif + MENUACTION_KEYBOARDCTRLS,"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", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 0, 0, + MENUACTION_GETKEY, "FEC_PLB", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_CWL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_CWR", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_LKT", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_PJP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_PSP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_TLF", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_TRG", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_CCM", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CONTROLLER_PC_OLD2 = 37 + { "FET_CTL", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 1, 1, + + }, + + // MENUPAGE_CONTROLLER_PC_OLD3 = 38 + { "FET_CTL", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 2, 2, + MENUACTION_GETKEY, "FEC_LUP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, + MENUACTION_GETKEY, "FEC_LDN", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, + MENUACTION_GETKEY, "FEC_SMS", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, + MENUACTION_SHOWHEADBOB, "FEC_GSL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CONTROLLER_PC_OLD4 = 39 + { "FET_CTL", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 3, 3, + + }, + + // MENUPAGE_CONTROLLER_DEBUG = 40 + { "FEC_DBG", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 3, 3, + MENUACTION_GETKEY, "FEC_TGD", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, + MENUACTION_GETKEY, "FEC_TDO", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, + MENUACTION_GETKEY, "FEC_TSS", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, + MENUACTION_GETKEY, "FEC_SMS", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_OPTIONS = 41 + { "FET_OPT", 1, MENUPAGE_NONE, MENUPAGE_NONE, 1, 4, + MENUACTION_CHANGEMENU, "FET_CTL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, + MENUACTION_LOADRADIO, "FET_AUD", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_CHANGEMENU, "FET_DIS", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_CHANGEMENU, "FET_LAN", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_PLAYERSETUP, "FET_PSU", SAVESLOT_NONE, MENUPAGE_SKIN_SELECT, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_EXIT = 42 + { "FET_QG", 1, MENUPAGE_NONE, MENUPAGE_NONE, 2, 5, + MENUACTION_LABEL, "FEQ_SRE", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_DONTCANCEL, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CANCELGAME, "FEM_YES", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SAVING_IN_PROGRESS = 43 + { "", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, + MENUACTION_LABEL, "FES_WAR", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SAVE_SUCCESSFUL = 44 + { "FET_SG", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, + MENUACTION_LABEL, "FES_SSC", SAVESLOT_LABEL, MENUPAGE_NONE, + MENUACTION_RESUME_FROM_SAVEZONE, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + }, + + // MENUPAGE_DELETING = 45 + { "FET_DG", 1, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, 0, 0, + MENUACTION_LABEL, "FED_DLW", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_DELETE_SUCCESS = 46 + { "FET_DG", 1, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, 0, 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", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 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", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, + MENUACTION_LABEL, "FEC_SVU", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_LOAD_FAILED_2 = 49 + { "FET_LG", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 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", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_START_MENU = 51 + { "FEM_MM", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 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", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 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", 1, MENUPAGE_NONE, MENUPAGE_NONE, 0, 1, + MENUACTION_CHANGEMENU, "FET_SP", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_INITMP, "FET_MP", SAVESLOT_NONE, MENUPAGE_MULTIPLAYER_MAIN, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SKIN_SELECT = 54 + { "FET_PSU", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 4, 4, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_MULTIPLAYER_MAIN, + }, + + // MENUPAGE_KEYBOARD_CONTROLS = 55 + { "FET_STI", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 1, 1, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, + }, + + // MENUPAGE_MOUSE_CONTROLS = 56 + { "FET_MTI", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 2, 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_MISSION_RETRY = 57 +#ifdef MISSION_REPLAY + + { "M_FAIL", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FESZ_RM", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS, + MENUACTION_REJECT_RETRY, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NONE + }, +#else + { "", 0, MENUPAGE_NONE, MENUPAGE_NONE, 0, 0, + // mission failed, wanna restart page in mobile + }, +#endif + + // MENUPAGE_UNK + { "", 0, MENUPAGE_NONE, MENUPAGE_NONE, 0, 0, + + }, + +}; + +#endif +#endif diff --git a/src/core/MenuScreensCustom.cpp b/src/core/MenuScreensCustom.cpp new file mode 100644 index 0000000..d33650f --- /dev/null +++ b/src/core/MenuScreensCustom.cpp @@ -0,0 +1,936 @@ +#include "common.h" +#if defined DETECT_JOYSTICK_MENU && defined XINPUT +#include +#include +#if !defined(PSAPI_VERSION) || (PSAPI_VERSION > 1) +#pragma comment( lib, "Xinput9_1_0.lib" ) +#else +#pragma comment( lib, "Xinput.lib" ) +#endif +#endif +#include "platform.h" +#include "crossplatform.h" +#include "Renderer.h" +#include "Frontend.h" +#include "Font.h" +#include "Camera.h" +#include "main.h" +#include "MBlur.h" +#include "postfx.h" +#include "custompipes.h" +#include "RwHelper.h" +#include "Text.h" +#include "Streaming.h" +#include "FileLoader.h" +#include "Collision.h" +#include "ModelInfo.h" +#include "Pad.h" +#include "ControllerConfig.h" +#include "IniFile.h" +#include "CarCtrl.h" +#include "Population.h" + +// Menu screens array is at the bottom of the file. + +#ifdef PC_MENU + +#ifdef CUSTOM_FRONTEND_OPTIONS + +#if defined(IMPROVED_VIDEOMODE) && !defined(GTA_HANDHELD) + #define VIDEOMODE_SELECTOR MENUACTION_CFO_SELECT, "FEM_SCF", { new CCFOSelect((int8*)&FrontEndMenuManager.m_nPrefsWindowed, "VideoMode", "Windowed", screenModes, 2, true, ScreenModeAfterChange, true) }, +#else + #define VIDEOMODE_SELECTOR +#endif + +#ifdef MULTISAMPLING + #define MULTISAMPLING_SELECTOR MENUACTION_CFO_DYNAMIC, "FED_AAS", { new CCFODynamic((int8*)&FrontEndMenuManager.m_nPrefsMSAALevel, "Graphics", "MultiSampling", MultiSamplingDraw, MultiSamplingButtonPress) }, +#else + #define MULTISAMPLING_SELECTOR +#endif + +#ifdef CUTSCENE_BORDERS_SWITCH + #define CUTSCENE_BORDERS_TOGGLE MENUACTION_CFO_SELECT, "FEM_CSB", { new CCFOSelect((int8 *)&CMenuManager::m_PrefsCutsceneBorders, "Display", "CutsceneBorders", off_on, 2, false) }, +#else + #define CUTSCENE_BORDERS_TOGGLE +#endif + +#ifdef FREE_CAM + #define FREE_CAM_TOGGLE MENUACTION_CFO_SELECT, "FEC_FRC", { new CCFOSelect((int8*)&TheCamera.bFreeCam, "Display", "FreeCam", off_on, 2, false) }, +#else + #define FREE_CAM_TOGGLE +#endif + +#ifdef PS2_ALPHA_TEST + #define DUALPASS_SELECTOR MENUACTION_CFO_SELECT, "FEM_2PR", { new CCFOSelect((int8*)&gPS2alphaTest, "Graphics", "PS2AlphaTest", off_on, 2, false) }, +#else + #define DUALPASS_SELECTOR +#endif + +#ifdef PED_CAR_DENSITY_SLIDERS + // 0.2f - 3.4f makes it possible to have 1.0f somewhere inbetween + #define DENSITY_SLIDERS \ + MENUACTION_CFO_SLIDER, "FEM_PED", { new CCFOSlider(&CIniFile::PedNumberMultiplier, "Display", "PedDensity", 0.2f, 3.4f, PedDensityChange) }, \ + MENUACTION_CFO_SLIDER, "FEM_CAR", { new CCFOSlider(&CIniFile::CarNumberMultiplier, "Display", "CarDensity", 0.2f, 3.4f, CarDensityChange) }, +#else + #define DENSITY_SLIDERS +#endif + +#ifdef NO_ISLAND_LOADING + #define ISLAND_LOADING_SELECTOR MENUACTION_CFO_SELECT, "FEM_ISL", { new CCFOSelect((int8*)&CMenuManager::m_PrefsIslandLoading, "Graphics", "IslandLoading", islandLoadingOpts, ARRAY_SIZE(islandLoadingOpts), true, IslandLoadingAfterChange) }, +#else + #define ISLAND_LOADING_SELECTOR +#endif + +#ifdef EXTENDED_COLOURFILTER + #define POSTFX_SELECTORS \ + MENUACTION_CFO_SELECT, "FED_CLF", { new CCFOSelect((int8*)&CPostFX::EffectSwitch, "Graphics", "ColourFilter", filterNames, ARRAY_SIZE(filterNames), false) }, \ + MENUACTION_CFO_SELECT, "FED_MBL", { new CCFOSelect((int8*)&CPostFX::MotionBlurOn, "Graphics", "MotionBlur", off_on, 2, false) }, +#else + #define POSTFX_SELECTORS +#endif + +#ifdef INVERT_LOOK_FOR_PAD + #define INVERT_PAD_SELECTOR MENUACTION_CFO_SELECT, "FEC_IVP", { new CCFOSelect((int8*)&CPad::bInvertLook4Pad, "Controller", "InvertPad", off_on, 2, false) }, +#else + #define INVERT_PAD_SELECTOR +#endif + +#ifdef GAMEPAD_MENU + #define SELECT_CONTROLLER_TYPE MENUACTION_CFO_SELECT, "FEC_TYP", { new CCFOSelect((int8*)&CMenuManager::m_PrefsControllerType, "Controller", "Type", controllerTypes, ARRAY_SIZE(controllerTypes), false, ControllerTypeAfterChange) }, +#else + #define SELECT_CONTROLLER_TYPE +#endif + +const char *filterNames[] = { "FEM_NON", "FEM_SIM", "FEM_NRM", "FEM_MOB" }; +const char *off_on[] = { "FEM_OFF", "FEM_ON" }; + +void RestoreDefGraphics(int8 action) { + if (action != FEOPTION_ACTION_SELECT) + return; + + #ifdef PS2_ALPHA_TEST + gPS2alphaTest = false; + #endif + #ifdef MULTISAMPLING + FrontEndMenuManager.m_nPrefsMSAALevel = FrontEndMenuManager.m_nDisplayMSAALevel = 0; + #endif + #ifdef NO_ISLAND_LOADING + if (!FrontEndMenuManager.m_bGameNotLoaded) { + FrontEndMenuManager.m_PrefsIslandLoading = FrontEndMenuManager.ISLAND_LOADING_LOW; + CCollision::bAlreadyLoaded = false; + CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel); + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + CStreaming::RequestIslands(CGame::currLevel); + CStreaming::LoadAllRequestedModels(true); + } else + FrontEndMenuManager.m_PrefsIslandLoading = FrontEndMenuManager.ISLAND_LOADING_LOW; + #endif + #ifdef GRAPHICS_MENU_OPTIONS // otherwise Frontend will handle those + CMenuManager::m_PrefsFrameLimiter = true; + CMenuManager::m_PrefsVsyncDisp = true; + CMenuManager::m_PrefsVsync = true; + CMenuManager::m_PrefsUseWideScreen = false; + FrontEndMenuManager.m_nDisplayVideoMode = FrontEndMenuManager.m_nPrefsVideoMode; + #if GTA_VERSION >= GTA3_PC_11 + if (_dwOperatingSystemVersion == OS_WIN98) { + CMBlur::BlurOn = false; + CMBlur::MotionBlurClose(); + } else { + CMBlur::BlurOn = true; + CMBlur::MotionBlurOpen(Scene.camera); + } + #else + CMBlur::BlurOn = true; + #endif + FrontEndMenuManager.SaveSettings(); + #endif +} + +void RestoreDefDisplay(int8 action) { + if (action != FEOPTION_ACTION_SELECT) + return; + + #ifdef CUTSCENE_BORDERS_SWITCH + CMenuManager::m_PrefsCutsceneBorders = true; + #endif + #ifdef FREE_CAM + TheCamera.bFreeCam = false; + #endif + #ifdef PED_CAR_DENSITY_SLIDERS + CIniFile::LoadIniFile(); + #endif + #ifdef GRAPHICS_MENU_OPTIONS // otherwise Frontend will handle those + CMenuManager::m_PrefsBrightness = 256; + CMenuManager::m_PrefsLOD = 1.2f; + CRenderer::ms_lodDistScale = 1.2f; + CMenuManager::m_PrefsShowSubtitles = true; + FrontEndMenuManager.SaveSettings(); + #endif +} + +#ifdef NO_ISLAND_LOADING +const char *islandLoadingOpts[] = { "FEM_LOW", "FEM_MED", "FEM_HIG" }; +void IslandLoadingAfterChange(int8 before, int8 after) { + if (!FrontEndMenuManager.m_bGameNotLoaded) { + if (after > FrontEndMenuManager.ISLAND_LOADING_LOW) { + FrontEndMenuManager.m_PrefsIslandLoading = before; // calls below needs previous mode :shrug: + + if (after == FrontEndMenuManager.ISLAND_LOADING_HIGH) + CStreaming::RemoveIslandsNotUsed(LEVEL_GENERIC); + if (before == FrontEndMenuManager.ISLAND_LOADING_LOW) { + if (CGame::currLevel != LEVEL_INDUSTRIAL) + CFileLoader::LoadCollisionFromDatFile(LEVEL_INDUSTRIAL); + if (CGame::currLevel != LEVEL_COMMERCIAL) + CFileLoader::LoadCollisionFromDatFile(LEVEL_COMMERCIAL); + if (CGame::currLevel != LEVEL_SUBURBAN) + CFileLoader::LoadCollisionFromDatFile(LEVEL_SUBURBAN); + CCollision::bAlreadyLoaded = true; + FrontEndMenuManager.m_PrefsIslandLoading = after; + CStreaming::RequestBigBuildings(CGame::currLevel); + + } else if (before == FrontEndMenuManager.ISLAND_LOADING_HIGH) { + FrontEndMenuManager.m_PrefsIslandLoading = after; + CStreaming::RequestIslands(CGame::currLevel); + } else + FrontEndMenuManager.m_PrefsIslandLoading = after; + + } else { // low + CCollision::bAlreadyLoaded = false; + CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel); + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + CStreaming::RequestIslands(CGame::currLevel); + } + + CStreaming::LoadAllRequestedModels(true); + } + + FrontEndMenuManager.SetHelperText(0); +} +#endif + +#ifdef PED_CAR_DENSITY_SLIDERS +void PedDensityChange(float before, float after) { + CPopulation::MaxNumberOfPedsInUse = DEFAULT_MAX_NUMBER_OF_PEDS * after; +} + +void CarDensityChange(float before, float after) { + CCarCtrl::MaxNumberOfCarsInUse = DEFAULT_MAX_NUMBER_OF_CARS * after; +} +#endif + +#ifndef MULTISAMPLING +void GraphicsGoBack() { +} +#else +void GraphicsGoBack() { + FrontEndMenuManager.m_nDisplayMSAALevel = FrontEndMenuManager.m_nPrefsMSAALevel; +} + +void MultiSamplingButtonPress(int8 action) { + if (action == FEOPTION_ACTION_SELECT) { + if (FrontEndMenuManager.m_nDisplayMSAALevel != FrontEndMenuManager.m_nPrefsMSAALevel) { + FrontEndMenuManager.m_nPrefsMSAALevel = FrontEndMenuManager.m_nDisplayMSAALevel; + _psSelectScreenVM(FrontEndMenuManager.m_nPrefsVideoMode); + FrontEndMenuManager.SetHelperText(0); + FrontEndMenuManager.SaveSettings(); + } + } else if (action == FEOPTION_ACTION_LEFT || action == FEOPTION_ACTION_RIGHT) { + if (FrontEndMenuManager.m_bGameNotLoaded) { + FrontEndMenuManager.m_nDisplayMSAALevel += (action == FEOPTION_ACTION_RIGHT ? 1 : -1); + + int i = 0; + int maxAA = RwD3D8EngineGetMaxMultiSamplingLevels(); + while (maxAA != 1) { + i++; + maxAA >>= 1; + } + + if (FrontEndMenuManager.m_nDisplayMSAALevel < 0) + FrontEndMenuManager.m_nDisplayMSAALevel = i; + else if (FrontEndMenuManager.m_nDisplayMSAALevel > i) + FrontEndMenuManager.m_nDisplayMSAALevel = 0; + } + } else if (action == FEOPTION_ACTION_FOCUSLOSS) { + if (FrontEndMenuManager.m_nDisplayMSAALevel != FrontEndMenuManager.m_nPrefsMSAALevel) { + FrontEndMenuManager.m_nDisplayMSAALevel = FrontEndMenuManager.m_nPrefsMSAALevel; + FrontEndMenuManager.SetHelperText(3); + } + } +} + +wchar* MultiSamplingDraw(bool *disabled, bool userHovering) { + static wchar unicodeTemp[64]; + if (userHovering) { + if (FrontEndMenuManager.m_nDisplayMSAALevel == FrontEndMenuManager.m_nPrefsMSAALevel) { + if (FrontEndMenuManager.m_nHelperTextMsgId == 1) // Press enter to apply + FrontEndMenuManager.ResetHelperText(); + } else { + FrontEndMenuManager.SetHelperText(1); + } + } else { + if (FrontEndMenuManager.m_nDisplayMSAALevel != FrontEndMenuManager.m_nPrefsMSAALevel) { + FrontEndMenuManager.m_nDisplayMSAALevel = FrontEndMenuManager.m_nPrefsMSAALevel; + } + } + + if (!FrontEndMenuManager.m_bGameNotLoaded) + *disabled = true; + + switch (FrontEndMenuManager.m_nDisplayMSAALevel) { + case 0: + return TheText.Get("FEM_OFF"); + default: + sprintf(gString, "%iX", 1 << (FrontEndMenuManager.m_nDisplayMSAALevel)); + AsciiToUnicode(gString, unicodeTemp); + return unicodeTemp; + } +} +#endif + +#ifdef IMPROVED_VIDEOMODE +const char* screenModes[] = { "FED_FLS", "FED_WND" }; +void ScreenModeAfterChange(int8 before, int8 after) +{ + _psSelectScreenVM(FrontEndMenuManager.m_nPrefsVideoMode); // apply same resolution + FrontEndMenuManager.SetHelperText(0); +} + +#endif + +#ifdef DETECT_JOYSTICK_MENU +wchar selectedJoystickUnicode[128]; +int cachedButtonNum = -1; + +wchar* DetectJoystickDraw(bool* disabled, bool userHovering) { + +#if defined RW_GL3 && !defined LIBRW_SDL2 + int numButtons; + int found = -1; + const char *joyname; + if (userHovering) { + for (int i = 0; i <= GLFW_JOYSTICK_LAST; i++) { + if ((joyname = glfwGetJoystickName(i))) { + const uint8* buttons = glfwGetJoystickButtons(i, &numButtons); + for (int j = 0; j < numButtons; j++) { + if (buttons[j]) { + found = i; + break; + } + } + if (found != -1) + break; + } + } + + if (found != -1 && PSGLOBAL(joy1id) != found) { + if (PSGLOBAL(joy1id) != -1 && PSGLOBAL(joy1id) != found) + PSGLOBAL(joy2id) = PSGLOBAL(joy1id); + else + PSGLOBAL(joy2id) = -1; + + strcpy(gSelectedJoystickName, joyname); + PSGLOBAL(joy1id) = found; + cachedButtonNum = numButtons; + } + } + if (PSGLOBAL(joy1id) == -1) +#elif defined XINPUT + int found = -1; + XINPUT_STATE xstate; + memset(&xstate, 0, sizeof(XINPUT_STATE)); + if (userHovering) { + for (int i = 0; i <= 3; i++) { + if (XInputGetState(i, &xstate) == ERROR_SUCCESS) { + if (xstate.Gamepad.bLeftTrigger || xstate.Gamepad.bRightTrigger) { + found = i; + break; + } + for (int j = XINPUT_GAMEPAD_DPAD_UP; j != XINPUT_GAMEPAD_Y << 1; j = (j << 1)) { + if (xstate.Gamepad.wButtons & j) { + found = i; + break; + } + } + if (found != -1) + break; + } + } + if (found != -1 && CPad::XInputJoy1 != found) { + // We should never leave pads -1, so we can process them when they're connected and kinda support hotplug. + CPad::XInputJoy2 = (CPad::XInputJoy1 == -1 ? (found + 1) % 4 : CPad::XInputJoy1); + CPad::XInputJoy1 = found; + cachedButtonNum = 0; // fake too, because xinput bypass CControllerConfig + } + } + sprintf(gSelectedJoystickName, "%d", CPad::XInputJoy1); // fake, on xinput we only store gamepad ids(thanks MS) so this is a temp variable to be used below + if (CPad::XInputJoy1 == -1) +#endif + AsciiToUnicode("Not found", selectedJoystickUnicode); + else + AsciiToUnicode(gSelectedJoystickName, selectedJoystickUnicode); + + return selectedJoystickUnicode; +} + +void DetectJoystickGoBack() { + if (cachedButtonNum != -1) { +#ifdef LOAD_INI_SETTINGS + ControlsManager.InitDefaultControlConfigJoyPad(cachedButtonNum); + SaveINIControllerSettings(); +#else + // Otherwise no way to save gSelectedJoystickName or ms_padButtonsInited anyway :shrug: Why do you even use this config.?? +#endif + cachedButtonNum = -1; + } +} +#endif + +#ifdef GAMEPAD_MENU +const char* controllerTypes[] = { "FEC_DS2", "FEC_DS3", "FEC_DS4", "FEC_360", "FEC_ONE", "FEC_NSW" }; +void ControllerTypeAfterChange(int8 before, int8 after) +{ + FrontEndMenuManager.LoadController(after); +} +#endif + +CMenuScreenCustom aScreens[MENUPAGES] = { + // MENUPAGE_NONE = 0 + { "", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, }, + + // MENUPAGE_STATS = 1 + { "FET_STA", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_NEW_GAME = 2 + { "FET_SGA", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_CHANGEMENU, "FES_SNG", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD }, + MENUACTION_POPULATESLOTS_CHANGEMENU, "GMLOAD", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT }, + MENUACTION_POPULATESLOTS_CHANGEMENU, "FES_DGA", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_BRIEFS = 3 + { "FET_BRE", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CONTROLLER_SETTINGS = 4 +#if defined(GAMEPAD_MENU) && !defined(GTA_HANDHELD) + { "FET_AGS", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, +#else + { "FET_AGS", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, +#endif + MENUACTION_CTRLCONFIG, "FEC_CCF", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, + MENUACTION_CTRLDISPLAY, "FEC_CDP", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, + INVERT_PAD_SELECTOR + MENUACTION_CTRLVIBRATION, "FEC_VIB", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, + SELECT_CONTROLLER_TYPE + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_SOUND_SETTINGS = 5 + { "FET_AUD", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, + MENUACTION_MUSICVOLUME, "FEA_MUS", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, + MENUACTION_SFXVOLUME, "FEA_SFX", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, +#ifdef EXTERNAL_3D_SOUND + MENUACTION_AUDIOHW, "FEA_3DH", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, + MENUACTION_SPEAKERCONF, "FEA_SPK", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, +#endif +#ifdef AUDIO_REFLECTIONS + MENUACTION_DYNAMICACOUSTIC, "FET_DAM", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, +#endif + MENUACTION_RADIO, "FEA_RSS", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, + MENUACTION_RESTOREDEF, "FET_DEF", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + +#ifndef GRAPHICS_MENU_OPTIONS + // MENUPAGE_DISPLAY_SETTINGS = 6 + { "FET_DIS", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, + MENUACTION_BRIGHTNESS, "FED_BRI", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_DRAWDIST, "FEM_LOD", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + DENSITY_SLIDERS + MENUACTION_FRAMESYNC, "FEM_VSC", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_FRAMELIMIT, "FEM_FRM", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, +#ifndef EXTENDED_COLOURFILTER + MENUACTION_TRAILS, "FED_TRA", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, +#endif + MENUACTION_SUBTITLES, "FED_SUB", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_WIDESCREEN, "FED_WIS", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_SCREENRES, "FED_RES", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + VIDEOMODE_SELECTOR + MULTISAMPLING_SELECTOR + ISLAND_LOADING_SELECTOR + DUALPASS_SELECTOR + CUTSCENE_BORDERS_TOGGLE + FREE_CAM_TOGGLE + POSTFX_SELECTORS + // re3.cpp inserts here pipeline selectors if neo/neo.txd exists and EXTENDED_PIPELINES defined + MENUACTION_RESTOREDEF, "FET_DEF", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#else + // MENUPAGE_DISPLAY_SETTINGS = 6 + { "FET_DIS", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, + MENUACTION_BRIGHTNESS, "FED_BRI", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_DRAWDIST, "FEM_LOD", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + DENSITY_SLIDERS + CUTSCENE_BORDERS_TOGGLE + FREE_CAM_TOGGLE + MENUACTION_SUBTITLES, "FED_SUB", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_CFO_DYNAMIC, "FET_DEF", { new CCFODynamic(nil, nil, nil, nil, RestoreDefDisplay) }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#endif + + // MENUPAGE_LANGUAGE_SETTINGS = 7 + { "FET_LAN", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, + MENUACTION_LANG_ENG, "FEL_ENG", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + MENUACTION_LANG_FRE, "FEL_FRE", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + MENUACTION_LANG_GER, "FEL_GER", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + MENUACTION_LANG_ITA, "FEL_ITA", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + MENUACTION_LANG_SPA, "FEL_SPA", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + // CustomFrontendOptionsPopulate will add languages here, if files are found + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CHOOSE_LOAD_SLOT = 8 + { "FET_LG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_CHANGEMENU, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + MENUACTION_CHECKSAVE, "FEM_SL0", { nil, SAVESLOT_1, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL1", { nil, SAVESLOT_2, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL2", { nil, SAVESLOT_3, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL3", { nil, SAVESLOT_4, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL4", { nil, SAVESLOT_5, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL5", { nil, SAVESLOT_6, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL6", { nil, SAVESLOT_7, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL7", { nil, SAVESLOT_8, MENUPAGE_LOAD_SLOT_CONFIRM }, + }, + + // MENUPAGE_CHOOSE_DELETE_SLOT = 9 + { "FET_DG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_CHANGEMENU, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + MENUACTION_CHANGEMENU, "FEM_SL0", { nil, SAVESLOT_1, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL1", { nil, SAVESLOT_2, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL2", { nil, SAVESLOT_3, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL3", { nil, SAVESLOT_4, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL4", { nil, SAVESLOT_5, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL5", { nil, SAVESLOT_6, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL6", { nil, SAVESLOT_7, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL7", { nil, SAVESLOT_8, MENUPAGE_DELETE_SLOT_CONFIRM }, + }, + + // MENUPAGE_NEW_GAME_RELOAD = 10 + { "FET_NG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_LABEL, "FESZ_QR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + MENUACTION_NEWGAME, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD }, + }, + + // MENUPAGE_LOAD_SLOT_CONFIRM = 11 + { "FET_LG", MENUPAGE_CHOOSE_LOAD_SLOT, MENUPAGE_CHOOSE_LOAD_SLOT, nil, nil, + MENUACTION_LABEL, "FESZ_QL", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT }, + MENUACTION_CHANGEMENU, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS }, + }, + + // MENUPAGE_DELETE_SLOT_CONFIRM = 12 + { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, nil, nil, + MENUACTION_LABEL, "FESZ_QD", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT }, + MENUACTION_CHANGEMENU, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_DELETING }, + }, + + // MENUPAGE_NO_MEMORY_CARD = 13 + { "FES_NOC", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + // hud adjustment page in mobile + }, + + // MENUPAGE_LOADING_IN_PROGRESS = 14 + { "FET_LG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FED_LDW", { nil, SAVESLOT_NONE, MENUPAGE_LOAD_SLOT_CONFIRM }, + }, + + // MENUPAGE_DELETING_IN_PROGRESS = 15 + { "FET_DG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FEDL_WR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_PS2_LOAD_FAILED = 16 + { "FET_LG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FES_LOE", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_DELETE_FAILED = 17 + { "FET_DG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FES_DEE", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEC_OKK", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT }, + }, + + // MENUPAGE_DEBUG_MENU = 18 + { "FED_DBG", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_RELOADIDE, "FED_RID", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_RELOADIPL, "FED_RIP", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SETDBGFLAG, "FED_DFL", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SWITCHBIGWHITEDEBUGLIGHT, "FED_DLS", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_PEDROADGROUPS, "FED_SPR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CARROADGROUPS, "FED_SCR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_COLLISIONPOLYS, "FED_SCP", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_PARSEHEAP, "FED_PAH", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SHOWCULL, "FED_SCZ", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_DEBUGSTREAM, "FED_DSR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_MEMORY_CARD_DEBUG = 19 + { "FEM_MCM", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_REGMEMCARD1, "FEM_RMC", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_TESTFORMATMEMCARD1, "FEM_TFM", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_TESTUNFORMATMEMCARD1, "FEM_TUM", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CREATEROOTDIR, "FEM_CRD", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CREATELOADICONS, "FEM_CLI", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_FILLWITHGUFF, "FEM_FFF", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SAVEONLYTHEGAME, "FEM_SOG", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SAVEGAME, "FEM_STG", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SAVEGAMEUNDERGTA, "FEM_STS", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CREATECOPYPROTECTED, "FEM_CPD", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_MEMORY_CARD_TEST = 20 + { "FEM_MC2", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_MAIN = 21 + { "FET_MP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_PS2_SAVE_FAILED = 22 + { "MCDNSP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_PS2_SAVE_FAILED_2 = 23 + { "MCGNSP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // Unused in PC but anyway + // MENUPAGE_SAVE = 24 +#ifdef PS2_SAVE_DIALOG + { "FET_SG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_CHANGEMENU, "FESZ_SA", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT }, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#else + { "FET_SG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FES_SCG", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_POPULATESLOTS_CHANGEMENU, "GMSAVE", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT }, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#endif + + // MENUPAGE_NO_MEMORY_CARD_2 = 25 + { "FES_NOC", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_CHANGEMENU, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CHOOSE_SAVE_SLOT = 26 + { "FET_SG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_SL1", { nil, SAVESLOT_1, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL2", { nil, SAVESLOT_2, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL3", { nil, SAVESLOT_3, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL4", { nil, SAVESLOT_4, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL5", { nil, SAVESLOT_5, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL6", { nil, SAVESLOT_6, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL7", { nil, SAVESLOT_7, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL8", { nil, SAVESLOT_8, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + }, + + // MENUPAGE_SAVE_OVERWRITE_CONFIRM = 27 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FESZ_QO", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_SAVING_IN_PROGRESS }, + MENUACTION_CHANGEMENU, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT }, + }, + + // MENUPAGE_MULTIPLAYER_MAP = 28 + { "FET_MAP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_CONNECTION = 29 + { "FET_CON", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_FIND_GAME = 30 + { "FET_FG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_MODE = 31 + { "FET_GT", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_CREATE = 32 + { "FET_HG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_START = 33 + { "FEN_STA", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_SKIN_SELECT_OLD = 34 + { "FET_PS", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_CONTROLLER_PC = 35 + { "FET_CTL", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, +#ifdef PC_PLAYER_CONTROLS + MENUACTION_CTRLMETHOD, "FET_CME", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC }, +#endif + MENUACTION_KEYBOARDCTRLS,"FET_RDK", { nil, SAVESLOT_NONE, MENUPAGE_KEYBOARD_CONTROLS }, +#ifdef GAMEPAD_MENU + MENUACTION_CHANGEMENU, "FET_AGS", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, +#endif +#ifdef DETECT_JOYSTICK_MENU + MENUACTION_CHANGEMENU, "FEC_JOD", { nil, SAVESLOT_NONE, MENUPAGE_DETECT_JOYSTICK }, +#endif + MENUACTION_CHANGEMENU, "FET_AMS", { nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS }, + MENUACTION_RESTOREDEF, "FET_DEF", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CONTROLLER_PC_OLD1 = 36 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_GETKEY, "FEC_PLB", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_CWL", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_CWR", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_LKT", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_PJP", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_PSP", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_TLF", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_TRG", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_CCM", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CONTROLLER_PC_OLD2 = 37 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + + }, + + // MENUPAGE_CONTROLLER_PC_OLD3 = 38 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_GETKEY, "FEC_LUP", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3 }, + MENUACTION_GETKEY, "FEC_LDN", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3 }, + MENUACTION_GETKEY, "FEC_SMS", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3 }, + MENUACTION_SHOWHEADBOB, "FEC_GSL", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3 }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CONTROLLER_PC_OLD4 = 39 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + + }, + + // MENUPAGE_CONTROLLER_DEBUG = 40 + { "FEC_DBG", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_GETKEY, "FEC_TGD", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG }, + MENUACTION_GETKEY, "FEC_TDO", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG }, + MENUACTION_GETKEY, "FEC_TSS", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG }, + MENUACTION_GETKEY, "FEC_SMS", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_OPTIONS = 41 + { "FET_OPT", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, +#ifdef GTA_HANDHELD + MENUACTION_CHANGEMENU, "FET_CTL", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, +#else + MENUACTION_CHANGEMENU, "FET_CTL", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC }, +#endif + MENUACTION_LOADRADIO, "FET_AUD", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, + MENUACTION_CHANGEMENU, "FET_DIS", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, +#ifdef GRAPHICS_MENU_OPTIONS + MENUACTION_CHANGEMENU, "FET_GFX", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, +#endif + MENUACTION_CHANGEMENU, "FET_LAN", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + MENUACTION_PLAYERSETUP, "FET_PSU", { nil, SAVESLOT_NONE, MENUPAGE_SKIN_SELECT }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_EXIT = 42 + { "FET_QG", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_LABEL, "FEQ_SRE", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_DONTCANCEL, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CANCELGAME, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_SAVING_IN_PROGRESS = 43 + { "", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FES_WAR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_SAVE_SUCCESSFUL = 44 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FES_SSC", { nil, SAVESLOT_LABEL, MENUPAGE_NONE }, + MENUACTION_RESUME_FROM_SAVEZONE, "FEC_OKK", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT }, + }, + + // MENUPAGE_DELETING = 45 + { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, nil, nil, + MENUACTION_LABEL, "FED_DLW", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_DELETE_SUCCESS = 46 + { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, nil, nil, + MENUACTION_LABEL, "DEL_FNM", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEC_OKK", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT }, + }, + + // MENUPAGE_SAVE_FAILED = 47 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FEC_SVU", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEC_OKK", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT }, + }, + + // MENUPAGE_LOAD_FAILED = 48 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FEC_SVU", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_LOAD_FAILED_2 = 49 + { "FET_LG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FEC_LUN", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT }, + }, + + // MENUPAGE_FILTER_GAME = 50 + { "FIL_FLT", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_START_MENU = 51 + { "FEM_MM", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_CHANGEMENU, "FEN_STA", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + MENUACTION_CHANGEMENU, "FET_OPT", { nil, SAVESLOT_NONE, MENUPAGE_OPTIONS }, + MENUACTION_CHANGEMENU, "FEM_QT", { nil, SAVESLOT_NONE, MENUPAGE_EXIT }, + }, + + // MENUPAGE_PAUSE_MENU = 52 + { "FET_PAU", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_RESUME, "FEM_RES", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEN_STA", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + // CMenuManager::LoadAllTextures will add map here, if MENU_MAP enabled and map textures are found + MENUACTION_CHANGEMENU, "FEP_STA", { nil, SAVESLOT_NONE, MENUPAGE_STATS }, + MENUACTION_CHANGEMENU, "FEP_BRI", { nil, SAVESLOT_NONE, MENUPAGE_BRIEFS }, + MENUACTION_CHANGEMENU, "FET_OPT", { nil, SAVESLOT_NONE, MENUPAGE_OPTIONS }, + MENUACTION_CHANGEMENU, "FEM_QT", { nil, SAVESLOT_NONE, MENUPAGE_EXIT }, + }, + + // MENUPAGE_CHOOSE_MODE = 53 + { "FEN_STA", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_CHANGEMENU, "FET_SP", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + MENUACTION_INITMP, "FET_MP", { nil, SAVESLOT_NONE, MENUPAGE_MULTIPLAYER_MAIN }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_SKIN_SELECT = 54 + { "FET_PSU", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_MULTIPLAYER_MAIN }, + }, + + // MENUPAGE_KEYBOARD_CONTROLS = 55 + { "FET_STI", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC }, + }, + + // MENUPAGE_MOUSE_CONTROLS = 56 + { "FET_MTI", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_MOUSESENS, "FEC_MSH", { nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS }, + MENUACTION_INVVERT, "FEC_IVV", { nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS }, +#ifndef GAMEPAD_MENU + INVERT_PAD_SELECTOR +#endif + MENUACTION_MOUSESTEER, "FET_MST", { nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + // MENUPAGE_MISSION_RETRY = 57 +#ifdef MISSION_REPLAY + + { "M_FAIL", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FESZ_RM", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS }, + MENUACTION_REJECT_RETRY, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#else + { "", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + // mission failed, wanna restart page in mobile + }, +#endif + +#ifdef MENU_MAP + // MENUPAGE_MAP + { "FEG_MAP", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_UNK110, "", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, // to prevent cross/enter to go back + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#endif + +#ifdef GRAPHICS_MENU_OPTIONS + // MENUPAGE_GRAPHICS_SETTINGS + { "FET_GFX", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, + new CCustomScreenLayout({MENUSPRITE_MAINMENU, 50, 0, 20, FONT_HEADING, FESCREEN_LEFT_ALIGN, true, MEDIUMTEXT_X_SCALE, MEDIUMTEXT_Y_SCALE}), GraphicsGoBack, + +#ifndef GTA_HANDHELD + MENUACTION_SCREENRES, "FED_RES", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, +#endif + MENUACTION_WIDESCREEN, "FED_WIS", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, + VIDEOMODE_SELECTOR + MENUACTION_FRAMESYNC, "FEM_VSC", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_FRAMELIMIT, "FEM_FRM", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MULTISAMPLING_SELECTOR + ISLAND_LOADING_SELECTOR + DUALPASS_SELECTOR +#ifdef EXTENDED_COLOURFILTER + POSTFX_SELECTORS +#else + MENUACTION_TRAILS, "FED_TRA", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, +#endif + // re3.cpp inserts here pipeline selectors if neo/neo.txd exists and EXTENDED_PIPELINES defined + MENUACTION_CFO_DYNAMIC, "FET_DEF", { new CCFODynamic(nil, nil, nil, nil, RestoreDefGraphics) }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#endif + +#ifdef DETECT_JOYSTICK_MENU + // MENUPAGE_DETECT_JOYSTICK + { "FEC_JOD", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, + new CCustomScreenLayout({MENUSPRITE_MAINMENU, 40, 60, 20, FONT_BANK, FESCREEN_LEFT_ALIGN, false, MEDIUMTEXT_X_SCALE, MEDIUMTEXT_Y_SCALE}), DetectJoystickGoBack, + + MENUACTION_LABEL, "FEC_JPR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CFO_DYNAMIC, "FEC_JDE", { new CCFODynamic(nil, nil, nil, DetectJoystickDraw, nil) }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#endif + + // MENUPAGE_UNK + { "", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + + }, + +}; + +#endif +#endif diff --git a/src/core/Pad.cpp b/src/core/Pad.cpp new file mode 100644 index 0000000..60bb7a7 --- /dev/null +++ b/src/core/Pad.cpp @@ -0,0 +1,3025 @@ +#define WITHDINPUT +#include "common.h" +#include "crossplatform.h" +#include "platform.h" +#ifdef XINPUT +#include +#if !defined(PSAPI_VERSION) || (PSAPI_VERSION > 1) +#pragma comment( lib, "Xinput9_1_0.lib" ) +#else +#pragma comment( lib, "Xinput.lib" ) +#endif +#endif + +#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 "Record.h" +#include "World.h" +#include "Vehicle.h" +#include "Ped.h" +#include "Population.h" +#include "Record.h" +#include "Replay.h" +#include "Weather.h" +#include "Streaming.h" +#include "PathFind.h" +#include "Wanted.h" +#include "General.h" + +#ifdef GTA_PS2 +#include "eetypes.h" +#include "libpad.h" +#endif + +CPad Pads[MAX_PADS]; +#ifdef GTA_PS2 +u_long128 pad_dma_buf[scePadDmaBufferMax] __attribute__((aligned(64))); +u_long128 pad2_dma_buf[scePadDmaBufferMax] __attribute__((aligned(64))); +#endif + +CMousePointerStateHelper MousePointerStateHelper; + +bool CPad::bDisplayNoControllerMessage; +bool CPad::bObsoleteControllerMessage; +bool CPad::bOldDisplayNoControllerMessage; +bool CPad::m_bMapPadOneToPadTwo; +#ifdef INVERT_LOOK_FOR_PAD +bool CPad::bInvertLook4Pad; +#endif +#ifdef GTA_PS2 +unsigned char act_direct[6]; +unsigned char act_align[6]; +#endif + +CKeyboardState CPad::OldKeyState; +CKeyboardState CPad::NewKeyState; +CKeyboardState CPad::TempKeyState; + +char CPad::KeyBoardCheatString[20]; + +CMouseControllerState CPad::OldMouseControllerState; +CMouseControllerState CPad::NewMouseControllerState; +CMouseControllerState CPad::PCTempMouseControllerState; + +#ifdef DETECT_PAD_INPUT_SWITCH +bool CPad::IsAffectedByController = false; +#endif + +_TODO("gbFastTime"); +extern bool gbFastTime; + +void WeaponCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT2"), true); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_BASEBALLBAT, 0); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_COLT45, 100); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_UZI, 100); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_SHOTGUN, 20); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_AK47, 200); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_M16, 200); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_SNIPERRIFLE, 5); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_ROCKETLAUNCHER, 5); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_MOLOTOV, 5); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_GRENADE, 5); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_FLAMETHROWER, 200); +} + +void HealthCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT3"), true); + FindPlayerPed()->m_fHealth = 100.0f; + if (FindPlayerVehicle()) { + FindPlayerVehicle()->m_fHealth = 1000.0f; + if (FindPlayerVehicle()->m_vehType == VEHICLE_TYPE_CAR) + ((CAutomobile*)FindPlayerVehicle())->Damage.SetEngineStatus(0); + } +} + +void TankCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CStreaming::RequestModel(MI_RHINO, 0); + CStreaming::LoadAllRequestedModels(false); + if (CStreaming::ms_aInfoForModel[MI_RHINO].m_loadState == STREAMSTATE_LOADED) { + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + int32 node = ThePaths.FindNodeClosestToCoors(FindPlayerCoors(), PATH_CAR, 100.0f); + + if (node < 0) return; + +#ifdef FIX_BUGS + CAutomobile* tank = new CAutomobile(MI_RHINO, RANDOM_VEHICLE); +#else + CAutomobile *tank = new CAutomobile(MI_RHINO, MISSION_VEHICLE); +#endif + if (tank != nil) { + CVector pos = ThePaths.m_pathNodes[node].GetPosition(); + pos.z += 4.0f; + tank->SetPosition(pos); + tank->SetOrientation(0.0f, 0.0f, DEGTORAD(200.0f)); + + tank->SetStatus(STATUS_ABANDONED); + tank->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(tank); + } + } +} + +void BlowUpCarsCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + + int i = CPools::GetVehiclePool()->GetSize(); + while (i-- > 0) { + if (CVehicle *veh = CPools::GetVehiclePool()->GetSlot(i)) + veh->BlowUpCar(nil); + } +} + +void ChangePlayerCheat() +{ + int modelId; + + if (FindPlayerPed()->IsPedInControl() && CModelInfo::GetModelInfo("player", nil)) { + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CPlayerPed *ped = FindPlayerPed(); + AssocGroupId AnimGrp = ped->m_animGroup; + do + { + do + modelId = CGeneral::GetRandomNumberInRange(0, MI_CAS_WOM+1); + while (!CModelInfo::GetModelInfo(modelId)); + } while (modelId >= MI_SPECIAL01 && modelId <= MI_SPECIAL04 || modelId == MI_TAXI_D); + + uint8 flags = CStreaming::ms_aInfoForModel[modelId].m_flags; + ped->DeleteRwObject(); + CStreaming::RequestModel(modelId, STREAMFLAGS_DEPENDENCY| STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + ped->m_modelIndex = -1; + ped->SetModelIndex(modelId); + ped->m_animGroup = AnimGrp; + if (modelId != MI_PLAYER) { + if (!(flags & STREAMFLAGS_DONT_REMOVE)) + CStreaming::SetModelIsDeletable(modelId); + } + } +} + +void MayhemCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + for (int i = PEDTYPE_CIVMALE; i < PEDTYPE_SPECIAL; i++) + CPedType::SetThreats(i, PED_FLAG_PLAYER1 | PED_FLAG_PLAYER2 | PED_FLAG_PLAYER3 | PED_FLAG_PLAYER4 | + PED_FLAG_CIVMALE | PED_FLAG_CIVFEMALE | PED_FLAG_COP | PED_FLAG_GANG1 | + PED_FLAG_GANG2 | PED_FLAG_GANG3 | PED_FLAG_GANG4 | PED_FLAG_GANG5 | + PED_FLAG_GANG6 | PED_FLAG_GANG7 | PED_FLAG_GANG8 | PED_FLAG_GANG9 | + PED_FLAG_EMERGENCY | PED_FLAG_PROSTITUTE | PED_FLAG_CRIMINAL | PED_FLAG_SPECIAL ); +} + +void EverybodyAttacksPlayerCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + for (int i = PEDTYPE_CIVMALE; i < PEDTYPE_SPECIAL; i++) + CPedType::AddThreat(i, PED_FLAG_PLAYER1); +} + +void WeaponsForAllCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CPopulation::ms_bGivePedsWeapons = !CPopulation::ms_bGivePedsWeapons; +} + +void FastTimeCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + if (CTimer::GetTimeScale() < 4.0f) + CTimer::SetTimeScale(CTimer::GetTimeScale() * 2.0f); +} + +void SlowTimeCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + if (CTimer::GetTimeScale() > 0.25f) + CTimer::SetTimeScale(CTimer::GetTimeScale() * 0.5f); +} + +void MoneyCheat() +{ + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 250000; + CHud::SetHelpMessage(TheText.Get("CHEAT6"), true); +} + +void ArmourCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT4"), true); + FindPlayerPed()->m_fArmour = 100.0f; +} + +void WantedLevelUpCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT5"), true); + FindPlayerPed()->SetWantedLevel(Min(FindPlayerPed()->m_pWanted->GetWantedLevel() + 2, 6)); +} + +void WantedLevelDownCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT5"), true); + FindPlayerPed()->SetWantedLevel(0); +} + +void SunnyWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT7"), true); + CWeather::ForceWeatherNow(WEATHER_SUNNY); +} + +void CloudyWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT7"), true); + CWeather::ForceWeatherNow(WEATHER_CLOUDY); +} + +void RainyWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT7"), true); + CWeather::ForceWeatherNow(WEATHER_RAINY); +} + +void FoggyWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT7"), true); + CWeather::ForceWeatherNow(WEATHER_FOGGY); +} + +void FastWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + gbFastTime = !gbFastTime; +} + +void OnlyRenderWheelsCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CVehicle::bWheelsOnlyCheat = !CVehicle::bWheelsOnlyCheat; +} + + +void ChittyChittyBangBangCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CVehicle::bAllDodosCheat = !CVehicle::bAllDodosCheat; +} + +void StrongGripCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CVehicle::bCheat3 = !CVehicle::bCheat3; +} + +void NastyLimbsCheat() +{ + CPed::bNastyLimbsCheat = !CPed::bNastyLimbsCheat; +} +////////////////////////////////////////////////////////////////////////// + +#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, true); + playerPed->m_ped_flagI80 = !playerPed->m_ped_flagI80; + + playerPed->m_fMass = m_fMass; + playerPed->m_fAirResistance = 0.4f / m_fMass; +} +#endif + +#ifdef ALLCARSHELI_CHEAT +void AllCarsHeliCheat(void) +{ + wchar* string; + if (bAllCarCheat) { + string = TheText.Get("CHEATOF"); + bAllCarCheat = false; + } + else { + string = TheText.Get("CHEAT1"); + bAllCarCheat = true; + } + CHud::SetHelpMessage(string, true); +} +#endif + +#ifdef ALT_DODO_CHEAT +void AltDodoCheat(void) +{ + wchar* string; + if (CVehicle::bAltDodoCheat) { + string = TheText.Get("CHEATOF"); + CVehicle::bAltDodoCheat = false; + } + else { + string = TheText.Get("CHEAT1"); + CVehicle::bAltDodoCheat = true; + } + CHud::SetHelpMessage(string, true); +} +#endif + +bool +CControllerState::CheckForInput(void) +{ + return !!RightStickX || !!RightStickY || !!LeftStickX || !!LeftStickY + || !!DPadUp || !!DPadDown || !!DPadLeft || !!DPadRight + || !!Triangle || !!Cross || !!Circle || !!Square + || !!Start || !!Select + || !!LeftShoulder1 || !!LeftShoulder2 || !!RightShoulder1 || !!RightShoulder2 + || !!LeftShock || !!RightShock; +} + +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 < ARRAY_SIZE(F); i++ ) + F[i] = 0; + + for ( int32 i = 0; i < ARRAY_SIZE(VK_KEYS); 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; +} + +#ifdef GTA_PS2_STUFF +void CPad::Initialise(void) +{ +#ifdef GTA_PS2 + scePadInit(0); + + scePadPortOpen(0, 0, pad_dma_buf ); + scePadPortOpen(1, 0, pad2_dma_buf ); +#endif + for (int i = 0; i < MAX_PADS; i++) + { + CPad::GetPad(i)->Clear(true); + CPad::GetPad(i)->Mode = 0; + } + + bObsoleteControllerMessage = false; + bOldDisplayNoControllerMessage = false; + bDisplayNoControllerMessage = false; +} +#endif + +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 = PLAYERCONTROL_ENABLED; + + bApplyBrakes = false; + + + for ( int32 i = 0; i < HORNHISTORY_SIZE; i++ ) + bHornHistory[i] = false; + + iCurrHornHistory = 0; + + for ( int32 i = 0; i < ARRAY_SIZE(CheatString); i++ ) + CheatString[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 defined RW_D3D9 || defined RWLIBS + 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; + } + } +#else + // It seems there is no way to get number of buttons on mouse, so assign all buttons if we have mouse. + double xpos = 1.0f, ypos; + glfwGetCursorPos(PSGLOBAL(window), &xpos, &ypos); + + if (xpos != 0.f) { + state.MMB = true; + state.RMB = true; + state.LMB = true; + state.WHEELDN = true; + state.WHEELUP = true; + } +#endif + + return state; +} + +void CPad::UpdateMouse() +{ +#if defined RW_D3D9 || defined RWLIBS + 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; + } + } +#else + if ( IsForegroundApp() && PSGLOBAL(cursorIsInWindow) ) + { + double xpos = 1.0f, ypos; + glfwGetCursorPos(PSGLOBAL(window), &xpos, &ypos); + if (xpos == 0.f) + return; + + 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 * (xpos - PSGLOBAL(lastMousePos.x))); + PCTempMouseControllerState.y = (float)(signy * (ypos - PSGLOBAL(lastMousePos.y))); + PCTempMouseControllerState.LMB = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_LEFT); + PCTempMouseControllerState.RMB = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_RIGHT); + PCTempMouseControllerState.MMB = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_MIDDLE); + PCTempMouseControllerState.MXB1 = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_4); + PCTempMouseControllerState.MXB2 = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_5); + + if (PSGLOBAL(mouseWheel) > 0) + PCTempMouseControllerState.WHEELUP = 1; + else if (PSGLOBAL(mouseWheel) < 0) + PCTempMouseControllerState.WHEELDN = 1; + + PSGLOBAL(lastMousePos.x) = xpos; + PSGLOBAL(lastMousePos.y) = ypos; + PSGLOBAL(mouseWheel) = 0.0f; + + OldMouseControllerState = NewMouseControllerState; + NewMouseControllerState = PCTempMouseControllerState; + } +#endif +} + +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; + } + } +} + +#ifdef GTA_PS2_STUFF +void CPad::AddToCheatString(char c) +{ + for ( int32 i = ARRAY_SIZE(CheatString) - 2; i >= 0; i-- ) + CheatString[i + 1] = CheatString[i]; + + CheatString[0] = c; + +#define _CHEATCMP(str) strncmp(str, CheatString, sizeof(str)-1) + // "4414LDRULDRU" - R2 R2 L1 R2 LEFT DOWN RIGHT UP LEFT DOWN RIGHT UP + if ( !_CHEATCMP("URDLURDL4144") ) + WeaponCheat(); + + // "4411LDRULDRU" - R2 R2 L1 L1 LEFT DOWN RIGHT UP LEFT DOWN RIGHT UP + else if ( !_CHEATCMP("URDLURDL1144") ) + MoneyCheat(); + + // "4412LDRULDRU" - R2 R2 L1 L2 LEFT DOWN RIGHT UP LEFT DOWN RIGHT UP + else if ( !_CHEATCMP("URDLURDL2144") ) + ArmourCheat(); + + // "4413LDRULDRU" - R2 R2 L1 R1 LEFT DOWN RIGHT UP LEFT DOWN RIGHT UP + else if ( !_CHEATCMP("URDLURDL3144") ) + HealthCheat(); + + // "4414LRLRLR" - R2 R2 L1 R2 LEFT RIGHT LEFT RIGHT LEFT RIGHT + else if ( !_CHEATCMP("RLRLRL4144") ) + WantedLevelUpCheat(); + + // "4414UDUDUD" - R2 R2 L1 R2 UP DOWN UP DOWN UP DOWN + else if ( !_CHEATCMP("DUDUDU4144") ) + WantedLevelDownCheat(); + + // "1234432T" - L1 L2 R1 R2 R2 R1 L2 TRIANGLE + else if ( !_CHEATCMP("T2344321") ) + SunnyWeatherCheat(); + + // "1234432S" - L1 L2 R1 R2 R2 R1 L2 SQUARE + else if ( !_CHEATCMP("S2344321") ) + CloudyWeatherCheat(); + + // "1234432C" - L1 L2 R1 R2 R2 R1 L2 CIRCLE + else if ( !_CHEATCMP("C2344321") ) + RainyWeatherCheat(); + + // "1234432X" - L1 L2 R1 R2 R2 R1 L2 CROSS + else if ( !_CHEATCMP("X2344321") ) + FoggyWeatherCheat(); + + // "CCCCCC321TCT" - CIRCLE CIRCLE CIRCLE CIRCLE CIRCLE CIRCLE R1 L2 L1 TRIANGLE CIRCLE TRIANGLE + else if ( !_CHEATCMP("TCT123CCCCCC") ) + TankCheat(); + + // "CCCSSSSS1TCT" - CIRCLE CIRCLE CIRCLE SQUARE SQUARE SQUARE SQUARE SQUARE L1 TRIANGLE CIRCLE TRIANGLE + else if ( !_CHEATCMP("TCT1SSSSSCCC") ) + FastWeatherCheat(); + + // "241324TSCT21" - L2 R2 L1 R1 L2 R2 TRIANGLE SQUARE CIRCLE TRIANGLE L2 L1 + else if ( !_CHEATCMP("12TCST423142") ) + BlowUpCarsCheat(); + + // "RDLU12ULDR" - RIGHT DOWN LEFT UP L1 L2 UP LEFT DOWN RIGHT + else if ( !_CHEATCMP("RDLU21ULDR") ) + ChangePlayerCheat(); + + // "DULUX3421" - DOWN UP LEFT UP CROSS R1 R2 L2 L1 + else if ( !_CHEATCMP("1243XULUD") ) + MayhemCheat(); + + // "DULUX3412" - DOWN UP LEFT UP CROSS R1 R2 L1 L2 + else if ( !_CHEATCMP("2143XULUD") ) + EverybodyAttacksPlayerCheat(); + + // "43TX21UD" - R2 R1 TRIANGLE CROSS L2 L1 UP DOWN + else if ( !_CHEATCMP("DU12XT34") ) + WeaponsForAllCheat(); + + // "TURDS12" - TRIANGLE UP RIGHT DOWN SQUARE L1 L2 + else if ( !_CHEATCMP("21SDRUT") ) + FastTimeCheat(); + + // "TURDS34" - TRIANGLE UP RIGHT DOWN SQUARE R1 R2 + else if ( !_CHEATCMP("43SDRUT") ) + SlowTimeCheat(); + + // "11S4T1T" - L1 L1 SQUARE R2 TRIANGLE L1 TRIANGLE + else if ( !_CHEATCMP("T1T4S11") ) + OnlyRenderWheelsCheat(); + + // "R4C32D13" - RIGHT R2 CIRCLE R1 L2 DOWN L1 R1 + else if ( !_CHEATCMP("31D23C4R") ) + ChittyChittyBangBangCheat(); + + // "3141L33T" - R1 L1 R2 L1 LEFT R1 R1 TRIANGLE + else if ( !_CHEATCMP("T33L1413") ) + StrongGripCheat(); + + // "S1CD13TR1X" - SQUARE L1 CIRCLE DOWN L1 R1 TRIANGLE RIGHT L1 CROSS + else if ( !_CHEATCMP("X1RT31DC1S") ) + NastyLimbsCheat(); + +#ifdef KANGAROO_CHEAT + // "X1DUC3RLS3" - R1 SQUARE LEFT RIGHT R1 CIRCLE UP DOWN L1 CROSS + else if (!_CHEATCMP("X1DUC3RLS3")) + KangarooCheat(); +#endif + +#ifndef MASTER + // "31UD13XUD" - DOWN UP CROSS R1 L1 DOWN UP L1 R1 + else if (!_CHEATCMP("31UD13XUD")) + CPed::SwitchDebugDisplay(); +#endif + +#ifdef ALLCARSHELI_CHEAT + // "UCCL3R1TT" - TRIANGLE TRIANGLE L1 RIGHT R1 LEFT CIRCLE CIRCLE UP + else if (!_CHEATCMP("UCCL3R1TT")) + AllCarsHeliCheat(); +#endif + +#ifdef ALT_DODO_CHEAT + // "DUU31XX13" - R1 L1 CROSS CROSS L1 R1 UP UP DOWN + else if (!_CHEATCMP("DUU31XX13")) + AltDodoCheat(); +#endif +#undef _CHEATCMP +} +#endif + +void CPad::AddToPCCheatString(char c) +{ + for ( int32 i = ARRAY_SIZE(KeyBoardCheatString) - 2; 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(); + +#if GTA_VERSION < GTA3_PC_11 + // "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 + +#ifndef MASTER + // "PEDDEBUG" + if (!_CHEATCMP("GUBEDDEP")) + CPed::SwitchDebugDisplay(); +#endif + +#ifdef ALLCARSHELI_CHEAT + // "CARSAREHELI" + if (!_CHEATCMP("ILEHERASRAC")) + AllCarsHeliCheat(); +#endif + +#ifdef ALT_DODO_CHEAT + // "IWANTTOMASTERDODO" + if (!_CHEATCMP("ODODRETSAMOTTNAWI")) + AltDodoCheat(); +#endif + + #undef _CHEATCMP +} + +#ifdef XINPUT +int CPad::XInputJoy1 = 0; +int CPad::XInputJoy2 = 1; +void CPad::AffectFromXinput(uint32 pad) +{ + pad = pad == 0 ? XInputJoy1 : XInputJoy2; + if (pad == -1) // LoadINIControllerSettings can set it to -1 + return; + + XINPUT_STATE xstate; + memset(&xstate, 0, sizeof(XINPUT_STATE)); + if (XInputGetState(pad, &xstate) == ERROR_SUCCESS) + { + PCTempJoyState.Circle = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_B) ? 255 : 0; + PCTempJoyState.Cross = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_A) ? 255 : 0; + PCTempJoyState.Square = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_X) ? 255 : 0; + PCTempJoyState.Triangle = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y) ? 255 : 0; + PCTempJoyState.DPadDown = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? 255 : 0; + PCTempJoyState.DPadLeft = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? 255 : 0; + PCTempJoyState.DPadRight = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? 255 : 0; + PCTempJoyState.DPadUp = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? 255 : 0; + PCTempJoyState.LeftShock = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? 255 : 0; + PCTempJoyState.LeftShoulder1 = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? 255 : 0; + PCTempJoyState.LeftShoulder2 = xstate.Gamepad.bLeftTrigger; + PCTempJoyState.RightShock = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? 255 : 0; + PCTempJoyState.RightShoulder1 = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? 255 : 0; + PCTempJoyState.RightShoulder2 = xstate.Gamepad.bRightTrigger; + + PCTempJoyState.Select = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) ? 255 : 0; +#ifdef REGISTER_START_BUTTON + PCTempJoyState.Start = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_START) ? 255 : 0; +#endif + float lx = (float)xstate.Gamepad.sThumbLX / (float)0x7FFF; + float ly = (float)xstate.Gamepad.sThumbLY / (float)0x7FFF; + float rx = (float)xstate.Gamepad.sThumbRX / (float)0x7FFF; + float ry = (float)xstate.Gamepad.sThumbRY / (float)0x7FFF; + + if (Abs(lx) > 0.3f || Abs(ly) > 0.3f) { + PCTempJoyState.LeftStickX = (int32)(lx * 128.0f); + PCTempJoyState.LeftStickY = (int32)(-ly * 128.0f); + } + + if (Abs(rx) > 0.3f || Abs(ry) > 0.3f) { + PCTempJoyState.RightStickX = (int32)(rx * 128.0f); + PCTempJoyState.RightStickY = (int32)(-ry * 128.0f); + } + + XINPUT_VIBRATION VibrationState; + + memset(&VibrationState, 0, sizeof(XINPUT_VIBRATION)); + + uint16 iLeftMotor = (uint16)((float)ShakeFreq / 255.0f * (float)0xffff); + uint16 iRightMotor = (uint16)((float)ShakeFreq / 255.0f * (float)0xffff); + + if (ShakeDur < CTimer::GetTimeStepInMilliseconds()) + ShakeDur = 0; + else + ShakeDur -= CTimer::GetTimeStepInMilliseconds(); + if (ShakeDur == 0) ShakeFreq = 0; + + VibrationState.wLeftMotorSpeed = iLeftMotor; + VibrationState.wRightMotorSpeed = iRightMotor; + + XInputSetState(pad, &VibrationState); + } +} +#endif + +void CPad::UpdatePads(void) +{ + bool bUpdate = true; + + GetPad(0)->UpdateMouse(); +#ifdef XINPUT + GetPad(0)->AffectFromXinput(m_bMapPadOneToPadTwo ? 1 : 0); + GetPad(1)->AffectFromXinput(m_bMapPadOneToPadTwo ? 0 : 1); +#else + CapturePad(0); +#endif + + // Improve keyboard input latency part 1 +#ifdef FIX_BUGS + OldKeyState = NewKeyState; + NewKeyState = TempKeyState; +#endif + +#ifdef DETECT_PAD_INPUT_SWITCH + if (GetPad(0)->PCTempJoyState.CheckForInput()) + IsAffectedByController = true; + else { +#endif + ControlsManager.ClearSimButtonPressCheckers(); + ControlsManager.AffectPadFromKeyBoard(); + ControlsManager.AffectPadFromMouse(); + +#ifdef DETECT_PAD_INPUT_SWITCH + } + if (IsAffectedByController && (GetPad(0)->PCTempKeyState.CheckForInput() || GetPad(0)->PCTempMouseState.CheckForInput())) + IsAffectedByController = false; +#endif + + if ( CReplay::IsPlayingBackFromFile() ) + bUpdate = false; + + if ( bUpdate ) + GetPad(0)->Update(0); + +#ifndef MASTER + GetPad(1)->Update(1); +#else + GetPad(1)->NewState.Clear(); + GetPad(1)->OldState.Clear(); +#endif + + // Improve keyboard input latency part 2 +#ifndef FIX_BUGS + OldKeyState = NewKeyState; + NewKeyState = TempKeyState; +#endif +} + +void CPad::ProcessPCSpecificStuff(void) +{ + ; +} + +void CPad::Update(int16 pad) +{ + OldState = NewState; + +#ifdef GTA_PS2 + bObsoleteControllerMessage = false; + + //int iPressureBtn; + int id; + int ext_id=0; + int state; + int rterm_id = 0; + unsigned short paddata, tpad; + unsigned char rdata[32]; + + state = scePadGetState(pad, 0); + + switch(Phase) + { + case 0: + if (state != scePadStateStable && state != scePadStateFindCTP1) + break; + id = scePadInfoMode(pad, 0, InfoModeCurID, 0); + if (id==0) break; + + ext_id = scePadInfoMode(pad, 0, InfoModeCurExID, 0); + if (ext_id>0) id = ext_id; + + switch(id) + { + case 4: // Digital controller + Phase = 40; // Try for analog(dualshock) + break; + case 7: // Dualshock2 controller + Phase = 50; + break; + default: + Phase = 99; + break; + } + break; + + // Analog Controller (old dualshock) + case 40: // Analog Contoller check valid (otherwise fail phase) + if (scePadInfoMode(pad, 0, InfoModeIdTable, -1)==0) + { + Phase = 99; + break; + } + Phase++; + + case 41: // Analog controller: Request Lock analog mode (asynchronous) + if (scePadSetMainMode(pad, 0, 1, 3)==1) Phase++; + break; + + case 42: // Analog controller: Check state of previous request + if (scePadGetReqState(pad, 0)==scePadReqStateFaild) + { + Phase--; + } + + if (scePadGetReqState(pad, 0)==scePadReqStateComplete) + { + // Lock mode complete + Phase=0; // Accept normal dualshock + } + break; + + // DualShock 2 Controller + case 50: // Analog Contoller check valid (otherwise fail phase) + if (scePadInfoMode(pad, 0, InfoModeIdTable, -1)==0) + { + Phase = 99; + break; + } + Phase++; + + case 51: // Analog controller: Request Lock analog mode (asynchronous) + if (scePadSetMainMode(pad, 0, 1, 3)==1) Phase++; + break; + + case 52: // Analog controller: Check state of previous request + if (scePadGetReqState(pad, 0)==scePadReqStateFaild) + { + Phase--; + } + + if (scePadGetReqState(pad, 0)==scePadReqStateComplete) + { + // Lock mode complete + Phase=0; // Accept normal dualshock + } + break; + + case 70: // DualShock 2 check pressure sensitive possible + if (scePadInfoPressMode(pad, 0)==1) + { + Phase = 76; + break; + } + Phase = 99; + break; + + case 76: // DualShock2 enable pressure sensitive mode (asynchronous function) + if (scePadEnterPressMode(pad, 0)==1) Phase++; + break; + + case 77: // Dualshock2 check status of request pressure sensitive mode + if (scePadGetReqState(pad, 0)==scePadReqStateFaild) Phase--; + if (scePadGetReqState(pad, 0)==scePadReqStateComplete) + { + Phase=80; + } + break; + + // DualShock 2 Controller + case 80: // Set motors + if (scePadInfoAct(pad, 0, -1, 0)==0) + { + Phase = 99; + } + + act_align[0] = 0; // Offset 0 for motor0 + act_align[1] = 1; // Offset 1 for motor1 + + act_align[2] = 0xff; + act_align[3] = 0xff; + act_align[4] = 0xff; + act_align[5] = 0xff; + + // Asynchronous function + if (scePadSetActAlign(pad, 0, act_align)==0) break; + Phase++; + break; + + + case 81: + if ( scePadGetState(pad, 0) != scePadStateExecCmd ) + { + Phase = 99; + } + + break; + + default: + if ( state == scePadStateError ) break; + + if ( state == scePadStateStable || state == scePadStateFindCTP1 ) + { + if ( ShakeDur ) + { + ShakeDur = Max(ShakeDur - (int32)CTimer::GetTimeStepInMilliseconds(), 0); + + if ( ShakeDur == 0 ) + { + act_direct[0] = 0; + act_direct[1] = 0; + scePadSetActDirect(pad, 0, act_direct); + } + else + { + act_direct[0] = 0; + act_direct[1] = (unsigned char) ShakeFreq; + scePadSetActDirect(pad, 0, act_direct); + } + } + + if (scePadRead( pad, 0, rdata )==0) + { + NewState.Clear(); + break; + } + + if ((rdata[0] == 0)) + { + paddata = (unsigned short) ( 0xffff ^ ((rdata[2]<<8)|rdata[3]) ); + rterm_id = (rdata[1]); + + if ( (rterm_id>>4) == 7 ) // DUALSHOCK + { + if (!CRecordDataForGame::IsPlayingBack() && !CRecordDataForChase::ShouldThisPadBeLeftAlone(pad)) + { + tpad = paddata; + + NewState.DPadUp = ( tpad & SCE_PADLup ) ? 255 : 0; + NewState.DPadDown = ( tpad & SCE_PADLdown ) ? 255 : 0; + NewState.DPadLeft = ( tpad & SCE_PADLleft ) ? 255 : 0; + NewState.DPadRight = ( tpad & SCE_PADLright ) ? 255 : 0; + NewState.Triangle = ( tpad & SCE_PADRup ) ? 255 : 0; + NewState.Cross = ( tpad & SCE_PADRdown ) ? 255 : 0; + NewState.Square = ( tpad & SCE_PADRleft ) ? 255 : 0; + NewState.Circle = ( tpad & SCE_PADRright ) ? 255 : 0; + NewState.Start = ( tpad & SCE_PADstart ) ? 255 : 0; + NewState.Select = ( tpad & SCE_PADselect ) ? 255 : 0; + NewState.LeftShoulder1 = ( tpad & SCE_PADL1 ) ? 255 : 0; + NewState.LeftShoulder2 = ( tpad & SCE_PADL2 ) ? 255 : 0; + NewState.RightShoulder1 = ( tpad & SCE_PADR1 ) ? 255 : 0; + NewState.RightShoulder2 = ( tpad & SCE_PADR2 ) ? 255 : 0; + NewState.LeftShock = ( tpad & SCE_PADi ) ? 255 : 0; + NewState.RightShock = ( tpad & SCE_PADj ) ? 255 : 0; + NewState.RightStickX = (short)rdata[4]; + NewState.RightStickY = (short)rdata[5]; + NewState.LeftStickX = (short)rdata[6]; + NewState.LeftStickY = (short)rdata[7]; + + #define CLAMP_AXIS(x) (((x) < 43 && (x) >= -42) ? 0 : (((x) > 0) ? (Max((x)-42, 0)*127/85) : Min((x)+42, 0)*127/85)) + #define FIX_AXIS(x) CLAMP_AXIS((x)-128) + + NewState.RightStickX = FIX_AXIS(NewState.RightStickX); + NewState.RightStickY = FIX_AXIS(NewState.RightStickY); + NewState.LeftStickX = FIX_AXIS(NewState.LeftStickX); + NewState.LeftStickY = FIX_AXIS(NewState.LeftStickY); + + #undef FIX_AXIS + #undef CLAMP_AXIS + } + } + else if ( (rterm_id>>4) == 4 ) // Controller (digital) + { + if ( pad == 0 ) + bObsoleteControllerMessage = true; + NewState.Clear(); + } + + if ( NewState.IsAnyButtonPressed() ) + LastTimeTouched = CTimer::GetTimeInMilliseconds(); + + break; + } + + if ( ++iCurrHornHistory >= HORNHISTORY_SIZE ) + iCurrHornHistory = 0; + + bHornHistory[iCurrHornHistory] = GetHorn(); + NewState.Clear(); + return; + } + break; + } + + if ( pad == 0 ) + { + bOldDisplayNoControllerMessage = bDisplayNoControllerMessage; + if ( state == scePadStateDiscon ) + { + bDisplayNoControllerMessage = true; + Phase = 0; + } + else + bDisplayNoControllerMessage = false; + } + + if ( ++iCurrHornHistory >= HORNHISTORY_SIZE ) + iCurrHornHistory = 0; + + bHornHistory[iCurrHornHistory] = GetHorn(); + + if ( !bDisplayNoControllerMessage ) + CGame::bDemoMode = false; +#endif + +#if (defined GTA_PS2 || defined FIX_BUGS) + if (!CRecordDataForGame::IsPlayingBack() && !CRecordDataForChase::ShouldThisPadBeLeftAlone(pad)) +#endif + { + NewState = ReconcileTwoControllersInput(PCTempKeyState, PCTempJoyState); + NewState = ReconcileTwoControllersInput(PCTempMouseState, NewState); + } + + PCTempJoyState.Clear(); + PCTempKeyState.Clear(); + PCTempMouseState.Clear(); + + ProcessPCSpecificStuff(); + + if ( ++iCurrHornHistory >= HORNHISTORY_SIZE ) + iCurrHornHistory = 0; + + bHornHistory[iCurrHornHistory] = GetHorn(); + + + if ( !bDisplayNoControllerMessage ) + CGame::bDemoMode = false; +} + +void CPad::DoCheats(void) +{ +#ifdef DETECT_PAD_INPUT_SWITCH + if (IsAffectedByController) +#endif + GetPad(0)->DoCheats(0); +} + +void CPad::DoCheats(int16 unk) +{ +#ifdef GTA_PS2_STUFF + 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 pad) +{ +#ifdef GTA_PS2_STUFF + ShakeFreq = 0; + ShakeDur = 0; + +#ifdef GTA_PS2 + if ( Phase == 99 ) + { + act_direct[0] = 0; + act_direct[1] = 0; + scePadSetActDirect(pad, 0, act_direct); + } +#endif + +#endif +} + +CPad *CPad::GetPad(int32 pad) +{ + return &Pads[pad]; +} +#ifdef DETECT_PAD_INPUT_SWITCH +#define CURMODE (IsAffectedByController ? Mode : 0) +#else +#define CURMODE (Mode) +#endif + + +int16 CPad::GetSteeringLeftRight(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + 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 (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.LeftShoulder2 && !NewState.RightShoulder2); +} + +bool CPad::GetLookRight(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.RightShoulder2 && !NewState.LeftShoulder2); +} + + +bool CPad::GetLookBehindForCar(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.RightShoulder2 && NewState.LeftShoulder2); +} + +bool CPad::GetLookBehindForPed(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!NewState.RightShock; +} + +bool CPad::GetHorn(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return !!NewState.Circle; + + break; + } + + case 3: + { + return !!NewState.RightShoulder1; + + break; + } + } + + return false; +} + +bool CPad::CarGunJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 3: + { + return !!NewState.Triangle; + + break; + } + + case 2: + { + return !!NewState.LeftShoulder1; + + break; + } + } + + return false; +} + +bool CPad::ExitVehicleJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + 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 (CURMODE) + { + 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 (CURMODE) + { + case 0: + case 2: + case 3: + { + return false; + + break; + } + + case 1: + { + return !!(NewState.DPadDown && !OldState.DPadDown); + + break; + } + } + + return false; +} + +bool CPad::ChangeStationJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); +} + +bool CPad::CycleWeaponRightJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); +} + +bool CPad::GetTarget(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return !!NewState.RightShoulder1; + + break; + } + + case 3: + { + return !!NewState.LeftShoulder1; + + break; + } + } + + return false; +} + +bool CPad::TargetJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.Square && !OldState.Square); +} + +bool CPad::GetSprint(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 3: + { + return !!NewState.Cross; + + break; + } + + case 2: + { + return !!NewState.Circle; + + break; + } + } + + return false; +} + +bool CPad::ShiftTargetLeftJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); +} + +bool CPad::ShiftTargetRightJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); +} + +#ifdef FIX_BUGS +// FIX: fixes from VC for the bug of double switching the controller setup +bool CPad::GetAnaloguePadUp(void) +{ + static int16 oldfStickY = 0; + + int16 leftStickY = CPad::GetPad(0)->GetLeftStickY(); + + if ( leftStickY < -15 && oldfStickY >= -5 ) + { + oldfStickY = leftStickY; + return true; + } + else + { + oldfStickY = leftStickY; + return false; + } +} + +bool CPad::GetAnaloguePadDown(void) +{ + static int16 oldfStickY = 0; + + int16 leftStickY = CPad::GetPad(0)->GetLeftStickY(); + + if ( leftStickY > 15 && oldfStickY <= 5 ) + { + oldfStickY = leftStickY; + return true; + } + else + { + oldfStickY = leftStickY; + return false; + } +} + +bool CPad::GetAnaloguePadLeft(void) +{ + static int16 oldfStickX = 0; + + int16 leftStickX = CPad::GetPad(0)->GetLeftStickX(); + + if ( leftStickX < -15 && oldfStickX >= -5 ) + { + oldfStickX = leftStickX; + return true; + } + else + { + oldfStickX = leftStickX; + return false; + } +} + +bool CPad::GetAnaloguePadRight(void) +{ + static int16 oldfStickX = 0; + + int16 leftStickX = CPad::GetPad(0)->GetLeftStickX(); + + if ( leftStickX > 15 && oldfStickX <= 5 ) + { + oldfStickX = leftStickX; + return true; + } + else + { + oldfStickX = leftStickX; + return false; + } +} + +bool CPad::GetAnaloguePadLeftJustUp(void) +{ + static int16 oldfStickX = 0; + + int16 X = GetPad(0)->GetPedWalkLeftRight(); + + if ( X == 0 && oldfStickX < 0 ) + { + oldfStickX = 0; + + 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 = 0; + + return true; + } + else + { + oldfStickX = X; + + return false; + } +} + +#else +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 = 0; + + 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 = 0; + + return true; + } + else + { + oldfStickX = X; + + return false; + } +} +#endif + +bool CPad::ForceCameraBehindPlayer(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + 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 ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 3: + { + return !!NewState.Square; + + break; + } + + case 2: + { + return !!NewState.Triangle; + + break; + } + } + + return false; +} + +bool CPad::SniperZoomOut(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 3: + { + return !!NewState.Cross; + + break; + } + + case 2: + { + return !!NewState.Square; + + break; + } + } + + return false; +} + +#undef CURMODE + +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; +#ifdef FIX_BUGS + axis = -axis; +#endif +#ifndef INVERT_LOOK_FOR_PAD + dpad = (NewState.DPadUp - NewState.DPadDown) / 2; +#else + if (CPad::bInvertLook4Pad) { + axis = -axis; + dpad = (NewState.DPadDown - NewState.DPadUp) / 2; + } else { + dpad = (NewState.DPadUp - NewState.DPadDown) / 2; + } +#endif + + if ( Abs(axis) > Abs(dpad) ) + return axis; + else + return dpad; +} + +int16 CPad::LookAroundLeftRight(void) +{ + float axis = GetPad(0)->NewState.RightStickX; + + 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) > 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; + +#ifdef FIX_BUGS + axis = -axis; +#endif +#ifdef INVERT_LOOK_FOR_PAD + if (CPad::bInvertLook4Pad) + axis = -axis; +#endif + + 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 ) + { +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(0.85f), SCREEN_SCALE_Y(1.0f)); +#else + CFont::SetScale(0.85f, 1.0f); +#endif + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 20); +#endif + 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 ) + { +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(0.85f), SCREEN_SCALE_Y(1.0f)); +#else + CFont::SetScale(0.85f, 1.0f); +#endif + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 20); +#endif + 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 = (int32)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 < ('9' - '0' + 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)->GetReturnJustDown() || 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)->GetReturnJustDown() ) + *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; +} diff --git a/src/core/Pad.h b/src/core/Pad.h new file mode 100644 index 0000000..b37659c --- /dev/null +++ b/src/core/Pad.h @@ -0,0 +1,474 @@ +#pragma once + +enum { + PLAYERCONTROL_ENABLED = 0, + PLAYERCONTROL_CAMERA = 1, + PLAYERCONTROL_UNK2 = 2, + PLAYERCONTROL_GARAGE = 4, + PLAYERCONTROL_UNK8 = 8, + PLAYERCONTROL_UNK10 = 16, + PLAYERCONTROL_PLAYERINFO = 32, + PLAYERCONTROL_PHONE = 64, + PLAYERCONTROL_CUTSCENE = 128, +}; + +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; }; + + bool CheckForInput(); + 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: + enum + { + HORNHISTORY_SIZE = 5, + }; + CControllerState NewState; + CControllerState OldState; + CControllerState PCTempKeyState; + CControllerState PCTempJoyState; + CControllerState PCTempMouseState; + // straight out of my IDB + int16 Phase; + int16 Mode; + int16 ShakeDur; + uint8 ShakeFreq; + bool bHornHistory[HORNHISTORY_SIZE]; + uint8 iCurrHornHistory; + uint8 DisablePlayerControls; + int8 bApplyBrakes; + char CheatString[12]; + int32 LastTimeTouched; + int32 AverageWeapon; + int32 AverageEntries; + +#ifdef DETECT_PAD_INPUT_SWITCH + static bool IsAffectedByController; +#endif + CPad() { } + ~CPad() { } + + static bool bDisplayNoControllerMessage; + static bool bObsoleteControllerMessage; + static bool bOldDisplayNoControllerMessage; + static bool m_bMapPadOneToPadTwo; +#ifdef INVERT_LOOK_FOR_PAD + static bool bInvertLook4Pad; +#endif + + static CKeyboardState OldKeyState; + static CKeyboardState NewKeyState; + static CKeyboardState TempKeyState; + static char KeyBoardCheatString[20]; + static CMouseControllerState OldMouseControllerState; + static CMouseControllerState NewMouseControllerState; + static CMouseControllerState PCTempMouseControllerState; + + +#ifdef GTA_PS2_STUFF + static void Initialise(void); +#endif + 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); +#ifdef GTA_PS2_STUFF + void AddToCheatString(char c); +#endif + void AddToPCCheatString(char c); + + static void UpdatePads(void); + void ProcessPCSpecificStuff(void); + void Update(int16 pad); + + static void DoCheats(void); + void DoCheats(int16 unk); + + static void StopPadsShaking(void); + void StopShaking(int16 pad); + + 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); + +#ifdef XINPUT + static int XInputJoy1; + static int XInputJoy2; + void AffectFromXinput(uint32 pad); +#endif + + // mouse + bool GetLeftMouseJustDown() { return !!(NewMouseControllerState.LMB && !OldMouseControllerState.LMB); } + bool GetRightMouseJustDown() { return !!(NewMouseControllerState.RMB && !OldMouseControllerState.RMB); } + bool GetMiddleMouseJustDown() { return !!(NewMouseControllerState.MMB && !OldMouseControllerState.MMB); } + bool GetMouseWheelUpJustDown() { return !!(NewMouseControllerState.WHEELUP && !OldMouseControllerState.WHEELUP); } + bool GetMouseWheelDownJustDown() { return !!(NewMouseControllerState.WHEELDN && !OldMouseControllerState.WHEELDN);} + bool GetMouseX1JustDown() { return !!(NewMouseControllerState.MXB1 && !OldMouseControllerState.MXB1); } + bool GetMouseX2JustDown() { return !!(NewMouseControllerState.MXB2 && !OldMouseControllerState.MXB2); } + + bool GetLeftMouseJustUp() { return !!(!NewMouseControllerState.LMB && OldMouseControllerState.LMB); } + bool GetRightMouseJustUp() { return !!(!NewMouseControllerState.RMB && OldMouseControllerState.RMB); } + bool GetMiddleMouseJustUp() { return !!(!NewMouseControllerState.MMB && OldMouseControllerState.MMB); } + bool GetMouseWheelUpJustUp() { return !!(!NewMouseControllerState.WHEELUP && OldMouseControllerState.WHEELUP); } + bool GetMouseWheelDownJustUp() { return !!(!NewMouseControllerState.WHEELDN && OldMouseControllerState.WHEELDN); } + bool GetMouseX1JustUp() { return !!(!NewMouseControllerState.MXB1 && OldMouseControllerState.MXB1); } + bool GetMouseX2JustUp() { return !!(!NewMouseControllerState.MXB2 && OldMouseControllerState.MXB2); } + + bool GetLeftMouse() { return NewMouseControllerState.LMB; } + bool GetRightMouse() { return NewMouseControllerState.RMB; } + bool GetMiddleMouse() { return NewMouseControllerState.MMB; } + bool GetMouseWheelUp() { return NewMouseControllerState.WHEELUP; } + bool GetMouseWheelDown() { return NewMouseControllerState.WHEELDN; } + bool GetMouseX1() { return NewMouseControllerState.MXB1; } + bool GetMouseX2() { return NewMouseControllerState.MXB2; } + + bool GetLeftMouseUp() { return !OldMouseControllerState.LMB; } + bool GetRightMouseUp() { return !OldMouseControllerState.RMB; } + bool GetMiddleMouseUp() { return !OldMouseControllerState.MMB; } + bool GetMouseWheelUpUp() { return !OldMouseControllerState.WHEELUP; } + bool GetMouseWheelDownUp() { return !OldMouseControllerState.WHEELDN; } + bool GetMouseX1Up() { return !OldMouseControllerState.MXB1; } + bool GetMouseX2Up() { return !OldMouseControllerState.MXB2; } + + + float GetMouseX() { return NewMouseControllerState.x; } + float GetMouseY() { return NewMouseControllerState.y; } + + // 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); } + 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 GetReturnJustDown() { 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); } + bool GetEnterJustDown() { return GetPadEnterJustDown() || GetReturnJustDown(); } + bool GetAltJustDown() { return GetLeftAltJustDown() || GetRightAltJustDown(); } + + bool GetLeftJustUp() { return !!(!NewKeyState.LEFT && OldKeyState.LEFT); } + bool GetRightJustUp() { return !!(!NewKeyState.RIGHT && OldKeyState.RIGHT); } + bool GetEnterJustUp() { return GetPadEnterJustUp() || GetReturnJustUp(); } + bool GetReturnJustUp() { return !!(!NewKeyState.EXTENTER && OldKeyState.EXTENTER); } + bool GetPadEnterJustUp() { return !!(!NewKeyState.ENTER && OldKeyState.ENTER); } + + bool GetChar(int32 c) { return NewKeyState.VK_KEYS[c]; } + bool GetF(int32 n) { return NewKeyState.F[n]; } + bool GetEscape() { return NewKeyState.ESC; } + bool GetInsert() { return NewKeyState.INS; } + bool GetDelete() { return NewKeyState.DEL; } + bool GetHome() { return NewKeyState.HOME; } + bool GetEnd() { return NewKeyState.END; } + bool GetPageUp() { return NewKeyState.PGUP; } + bool GetPageDown() { return NewKeyState.PGDN; } + bool GetUp() { return NewKeyState.UP; } + bool GetDown() { return NewKeyState.DOWN; } + bool GetLeft() { return NewKeyState.LEFT; } + bool GetRight() { return NewKeyState.RIGHT; } + bool GetScrollLock() { return NewKeyState.SCROLLLOCK; } + bool GetPause() { return NewKeyState.PAUSE; } + bool GetNumLock() { return NewKeyState.NUMLOCK; } + bool GetDivide() { return NewKeyState.DIV; } + bool GetTimes() { return NewKeyState.MUL; } + bool GetMinus() { return NewKeyState.SUB; } + bool GetPlus() { return NewKeyState.ADD; } + bool GetPadEnter() { return NewKeyState.ENTER; } // GetEnterJustDown + bool GetPadDel() { return NewKeyState.DECIMAL; } + bool GetPad1() { return NewKeyState.NUM1; } + bool GetPad2() { return NewKeyState.NUM2; } + bool GetPad3() { return NewKeyState.NUM3; } + bool GetPad4() { return NewKeyState.NUM4; } + bool GetPad5() { return NewKeyState.NUM5; } + bool GetPad6() { return NewKeyState.NUM6; } + bool GetPad7() { return NewKeyState.NUM7; } + bool GetPad8() { return NewKeyState.NUM8; } + bool GetPad9() { return NewKeyState.NUM9; } + bool GetPad0() { return NewKeyState.NUM0; } + bool GetBackspace() { return NewKeyState.BACKSP; } + bool GetTab() { return NewKeyState.TAB; } + bool GetCapsLock() { return NewKeyState.CAPSLOCK; } + bool GetEnter() { return NewKeyState.EXTENTER; } + bool GetLeftShift() { return NewKeyState.LSHIFT; } + bool GetShift() { return NewKeyState.SHIFT; } + bool GetRightShift() { return NewKeyState.RSHIFT; } + bool GetLeftCtrl() { return NewKeyState.LCTRL; } + bool GetRightCtrl() { return NewKeyState.RCTRL; } + bool GetLeftAlt() { return NewKeyState.LALT; } + bool GetRightAlt() { return NewKeyState.RALT; } + bool GetLeftWin() { return NewKeyState.LWIN; } + bool GetRightWin() { return NewKeyState.RWIN; } + bool GetApps() { return NewKeyState.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); } + bool GetLeftShockJustDown() { return !!(NewState.LeftShock && !OldState.LeftShock); } + bool GetRightShockJustDown() { return !!(NewState.RightShock && !OldState.RightShock); } + bool GetStartJustDown() { return !!(NewState.Start && !OldState.Start); } + bool GetLeftStickXJustDown() { return !!(NewState.LeftStickX && !OldState.LeftStickX); } + bool GetLeftStickYJustDown() { return !!(NewState.LeftStickY && !OldState.LeftStickY); } + + bool GetTriangleJustUp() { return !!(!NewState.Triangle && OldState.Triangle); } + bool GetCircleJustUp() { return !!(!NewState.Circle && OldState.Circle); } + bool GetCrossJustUp() { return !!(!NewState.Cross && OldState.Cross); } + bool GetSquareJustUp() { return !!(!NewState.Square && OldState.Square); } + bool GetDPadUpJustUp() { return !!(!NewState.DPadUp && OldState.DPadUp); } + bool GetDPadDownJustUp() { return !!(!NewState.DPadDown && OldState.DPadDown); } + bool GetDPadLeftJustUp() { return !!(!NewState.DPadLeft && OldState.DPadLeft); } + bool GetDPadRightJustUp() { return !!(!NewState.DPadRight && OldState.DPadRight); } + + bool GetTriangle() { return !!NewState.Triangle; } + bool GetCircle() { return !!NewState.Circle; } + bool GetCross() { return !!NewState.Cross; } + bool GetSquare() { return !!NewState.Square; } + bool GetDPadUp() { return !!NewState.DPadUp; } + bool GetDPadDown() { return !!NewState.DPadDown; } + bool GetDPadLeft() { return !!NewState.DPadLeft; } + bool GetDPadRight() { return !!NewState.DPadRight; } + bool GetLeftShoulder1(void) { return !!NewState.LeftShoulder1; } + bool GetLeftShoulder2(void) { return !!NewState.LeftShoulder2; } + bool GetRightShoulder1(void) { return !!NewState.RightShoulder1; } + bool GetRightShoulder2(void) { return !!NewState.RightShoulder2; } + bool GetStart() { return !!NewState.Start; } + int16 GetLeftStickX(void) { return NewState.LeftStickX; } + int16 GetLeftStickY(void) { return NewState.LeftStickY; } + int16 GetRightStickX(void) { return NewState.RightStickX; } + int16 GetRightStickY(void) { return NewState.RightStickY; } + + bool ArePlayerControlsDisabled(void) { return DisablePlayerControls != PLAYERCONTROL_ENABLED; } + void SetDisablePlayerControls(uint8 who) { DisablePlayerControls |= who; } + void SetEnablePlayerControls(uint8 who) { DisablePlayerControls &= ~who; } + bool IsPlayerControlsDisabledBy(uint8 who) { return DisablePlayerControls & who; } + + int16 GetMode() { return Mode; } + void SetMode(int16 mode) { Mode = mode; } + + static bool IsNoOrObsolete() { return bDisplayNoControllerMessage || bObsoleteControllerMessage; } +}; + +VALIDATE_SIZE(CPad, 0xFC); +extern CPad Pads[MAX_PADS]; + +#ifdef ALLCARSHELI_CHEAT +extern bool bAllCarCheat; +#endif diff --git a/src/core/Placeable.cpp b/src/core/Placeable.cpp new file mode 100644 index 0000000..162148f --- /dev/null +++ b/src/core/Placeable.cpp @@ -0,0 +1,66 @@ +#include "common.h" +#include "Placeable.h" + + +CPlaceable::CPlaceable(void) +{ + m_matrix.SetScale(1.0f); +} + +CPlaceable::~CPlaceable(void) +{ +} + +void +CPlaceable::SetHeading(float angle) +{ + CVector pos = GetMatrix().GetPosition(); + m_matrix.SetRotateZ(angle); + GetMatrix().Translate(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; +} diff --git a/src/core/Placeable.h b/src/core/Placeable.h new file mode 100644 index 0000000..2f246bc --- /dev/null +++ b/src/core/Placeable.h @@ -0,0 +1,37 @@ +#pragma once + +class CPlaceable +{ +protected: + CMatrix m_matrix; + +public: + // disable allocation + static void *operator new(size_t) throw(); + + CPlaceable(void); + virtual ~CPlaceable(void); + const CVector &GetPosition(void) { return m_matrix.GetPosition(); } + void SetPosition(float x, float y, float z) { + m_matrix.GetPosition().x = x; + m_matrix.GetPosition().y = y; + m_matrix.GetPosition().z = z; + } + void SetPosition(const CVector &pos) { m_matrix.GetPosition() = pos; } + 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 SetMatrix(CMatrix &newMatrix) { m_matrix = newMatrix; } + void SetTransform(RwMatrix *m) { m_matrix = CMatrix(m, false); } + void SetHeading(float angle); + void SetOrientation(float x, float y, float z){ + CVector pos = m_matrix.GetPosition(); + m_matrix.SetRotate(x, y, z); + m_matrix.Translate(pos); + } + bool IsWithinArea(float x1, float y1, float x2, float y2); + bool IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2); +}; + +VALIDATE_SIZE(CPlaceable, 0x4C); diff --git a/src/core/PlayerInfo.cpp b/src/core/PlayerInfo.cpp new file mode 100644 index 0000000..91bd069 --- /dev/null +++ b/src/core/PlayerInfo.cpp @@ -0,0 +1,662 @@ +#include "common.h" + +#include "Automobile.h" +#include "Bridge.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "Cranes.h" +#include "Darkel.h" +#include "Explosion.h" +#include "Fire.h" +#include "Frontend.h" +#include "General.h" +#include "HandlingMgr.h" +#include "Messages.h" +#include "Pad.h" +#include "PathFind.h" +#include "PlayerInfo.h" +#include "PlayerPed.h" +#include "PlayerSkin.h" +#include "ProjectileInfo.h" +#include "Remote.h" +#include "Renderer.h" +#include "Replay.h" +#include "Script.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "Streaming.h" +#include "Text.h" +#include "Wanted.h" +#include "WaterLevel.h" +#include "World.h" +#include "ZoneCull.h" +#include "main.h" + + +void +CPlayerInfo::Clear(void) +{ + m_pPed = nil; + m_pRemoteVehicle = nil; + if (m_pVehicleEx) { + m_pVehicleEx->bUsingSpecialColModel = false; + m_pVehicleEx = nil; + } + m_nVisibleMoney = 0; + m_nMoney = m_nVisibleMoney; + m_WBState = WBSTATE_PLAYING; + m_nWBTime = 0; + m_nTrafficMultiplier = 0; + m_fRoadDensity = 1.0f; + m_bInRemoteMode = false; + m_bUnusedTaxiThing = false; + m_nUnusedTaxiTimer = 0; + m_nCollectedPackages = 0; + m_nTotalPackages = 3; + m_nTimeLastHealthLoss = 0; + m_nTimeLastArmourLoss = 0; + m_nNextSexFrequencyUpdateTime = 0; + m_nNextSexMoneyUpdateTime = 0; + m_nSexFrequency = 0; + m_pHooker = nil; + m_nTimeTankShotGun = 0; + field_248 = 0; + m_nUpsideDownCounter = 0; + m_bInfiniteSprint = false; + m_bFastReload = false; + m_bGetOutOfJailFree = false; + m_bGetOutOfHospitalFree = false; + m_nPreviousTimeRewardedForExplosion = 0; + m_nExplosionsSinceLastReward = 0; +} + +void +CPlayerInfo::Process(void) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + // Unused taxi feature. Gives you a dollar for every second with a passenger. Can be toggled via 0x29A opcode. + bool startTaxiTimer = true; + if (m_bUnusedTaxiThing && m_pPed->bInVehicle) { + CVehicle *veh = m_pPed->m_pMyVehicle; + if ((veh->GetModelIndex() == MI_TAXI || veh->GetModelIndex() == MI_CABBIE || veh->GetModelIndex() == MI_BORGNINE) + && veh->pDriver == m_pPed && veh->m_nNumPassengers != 0) { + for (uint32 timePassed = CTimer::GetTimeInMilliseconds() - m_nUnusedTaxiTimer; timePassed >= 1000; m_nUnusedTaxiTimer += 1000) { + timePassed -= 1000; + ++m_nMoney; + } + startTaxiTimer = false; + } + } + if (startTaxiTimer) + m_nUnusedTaxiTimer = CTimer::GetTimeInMilliseconds(); + + // The effect that makes money counter does while earning/losing money + if (m_nVisibleMoney != m_nMoney) { + int diff = m_nMoney - m_nVisibleMoney; + int diffAbs = Abs(diff); + int changeBy; + + if (diffAbs > 100000) + changeBy = 12345; + else if (diffAbs > 10000) + changeBy = 1234; + else if (diffAbs > 1000) + changeBy = 123; + else if (diffAbs > 50) + changeBy = 42; + else + changeBy = 1; + + if (diff < 0) + m_nVisibleMoney -= changeBy; + else + m_nVisibleMoney += changeBy; + } + + if (!(CTimer::GetFrameCounter() & 15)) { + CVector2D playerPos = m_pPed->bInVehicle ? m_pPed->m_pMyVehicle->GetPosition() : m_pPed->GetPosition(); + m_fRoadDensity = ThePaths.CalcRoadDensity(playerPos.x, playerPos.y); + } + + m_fRoadDensity = Clamp(m_fRoadDensity, 0.4f, 1.45f); + + // Because vehicle enter/exit use same key binding. + bool enterOrExitVeh; + if (m_pPed->bVehExitWillBeInstant && m_pPed->bInVehicle) + enterOrExitVeh = CPad::GetPad(0)->ExitVehicleJustDown(); + else + enterOrExitVeh = CPad::GetPad(0)->GetExitVehicle(); + + if (enterOrExitVeh && m_pPed->m_nPedState != PED_SNIPER_MODE && m_pPed->m_nPedState != PED_ROCKET_MODE) { + if (m_pPed->bInVehicle) { + if (!m_pRemoteVehicle) { + CEntity *surfaceBelowVeh = m_pPed->m_pMyVehicle->m_pCurGroundEntity; + if (!surfaceBelowVeh || !CBridge::ThisIsABridgeObjectMovingUp(surfaceBelowVeh->GetModelIndex())) { + CVehicle *veh = m_pPed->m_pMyVehicle; + if (!veh->IsBoat() || veh->m_nDoorLock == CARLOCK_LOCKED_PLAYER_INSIDE) { + + // This condition will always return true, else block was probably WIP Miami code. + if (veh->m_vehType != VEHICLE_TYPE_BIKE || veh->m_nDoorLock == CARLOCK_LOCKED_PLAYER_INSIDE) { + if (veh->GetStatus() != STATUS_WRECKED && veh->GetStatus() != STATUS_TRAIN_MOVING && veh->m_nDoorLock != CARLOCK_LOCKED_PLAYER_INSIDE) { + if (veh->m_vecMoveSpeed.Magnitude() < 0.17f && CTimer::GetTimeScale() >= 0.5f && !veh->bIsInWater) { + m_pPed->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + } + } + } else { + CVector sth = 0.7f * veh->GetRight() + veh->GetPosition(); + bool found = false; + float groundZ = CWorld::FindGroundZFor3DCoord(sth.x, sth.y, 2.0f + sth.z, &found); + + if (found) + sth.z = 1.0f + groundZ; + m_pPed->SetPedState(PED_IDLE); + m_pPed->SetMoveState(PEDMOVE_STILL); + CPed::PedSetOutCarCB(0, m_pPed); + CAnimManager::BlendAnimation(m_pPed->GetClump(), m_pPed->m_animGroup, ANIM_STD_IDLE, 100.0f); + CAnimManager::BlendAnimation(m_pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_FALL_LAND, 100.0f); + m_pPed->SetPosition(sth); + m_pPed->SetMoveState(PEDMOVE_STILL); + m_pPed->m_vecMoveSpeed = veh->m_vecMoveSpeed; + } + } else { + // The code in here was under CPed::SetExitBoat in VC, did the same for here. + m_pPed->SetExitBoat(veh); + m_pPed->bTryingToReachDryLand = true; + } + } + } + } else { + // Enter vehicle + if (CPad::GetPad(0)->ExitVehicleJustDown()) { + bool weAreOnBoat = false; + float lastCloseness = 0.0f; + CVehicle *carBelow = nil; + CEntity *surfaceBelow = m_pPed->m_pCurrentPhysSurface; + if (surfaceBelow && surfaceBelow->IsVehicle()) { + carBelow = (CVehicle*)surfaceBelow; + if (carBelow->IsBoat()) { + weAreOnBoat = true; + m_pPed->bOnBoat = true; +#ifdef VC_PED_PORTS + if (carBelow->GetStatus() != STATUS_WRECKED && carBelow->GetUp().z > 0.3f) +#else + if (carBelow->GetStatus() != STATUS_WRECKED) +#endif + m_pPed->SetSeekBoatPosition(carBelow); + } + } + // Find closest car + if (!weAreOnBoat) { + float minX = m_pPed->GetPosition().x - 10.0f; + float maxX = 10.0f + m_pPed->GetPosition().x; + float minY = m_pPed->GetPosition().y - 10.0f; + float maxY = 10.0f + m_pPed->GetPosition().y; + + int minXSector = CWorld::GetSectorIndexX(minX); + if (minXSector < 0) minXSector = 0; + int minYSector = CWorld::GetSectorIndexY(minY); + if (minYSector < 0) minYSector = 0; + int maxXSector = CWorld::GetSectorIndexX(maxX); + if (maxXSector > NUMSECTORS_X - 1) maxXSector = NUMSECTORS_X - 1; + int maxYSector = CWorld::GetSectorIndexY(maxY); + if (maxYSector > NUMSECTORS_Y - 1) maxYSector = NUMSECTORS_Y - 1; + + CWorld::AdvanceCurrentScanCode(); + + for (int curY = minYSector; curY <= maxYSector; curY++) { + for (int curX = minXSector; curX <= maxXSector; curX++) { + CSector *sector = CWorld::GetSector(curX, curY); + FindClosestCarSectorList(sector->m_lists[ENTITYLIST_VEHICLES], m_pPed, + minX, minY, maxX, maxY, &lastCloseness, &carBelow); + FindClosestCarSectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], m_pPed, + minX, minY, maxX, maxY, &lastCloseness, &carBelow); + } + } + } + // carBelow is now closest vehicle + if (carBelow && !weAreOnBoat) { + if (carBelow->GetStatus() == STATUS_TRAIN_NOT_MOVING) { + m_pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, carBelow); + } else if (carBelow->IsBoat()) { + if (!carBelow->pDriver) { + m_pPed->m_vehDoor = 0; + m_pPed->SetEnterCar(carBelow, m_pPed->m_vehDoor); + } + } else { + m_pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, carBelow); + } + } + } + } + } + if (m_bInRemoteMode) { + uint32 timeWithoutRemoteCar = CTimer::GetTimeInMilliseconds() - m_nTimeLostRemoteCar; + if (CTimer::GetPreviousTimeInMilliseconds() - m_nTimeLostRemoteCar < 1000 && timeWithoutRemoteCar >= 1000 && m_WBState == WBSTATE_PLAYING) { + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(1.0f, FADE_OUT); + } + if (timeWithoutRemoteCar > 2000) { + if (m_WBState == WBSTATE_PLAYING) { + TheCamera.RestoreWithJumpCut(); + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(1.0f, FADE_IN); + TheCamera.Process(); + CTimer::Stop(); + CCullZones::ForceCullZoneCoors(TheCamera.GetPosition()); + CRenderer::RequestObjectsInFrustum(); + CStreaming::LoadAllRequestedModels(false); + CTimer::Update(); + } + m_bInRemoteMode = false; + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle = nil; + if (FindPlayerVehicle()) { + FindPlayerVehicle()->SetStatus(STATUS_PLAYER); + } + } + } + if (!(CTimer::GetFrameCounter() & 31)) { + CVehicle *veh = FindPlayerVehicle(); + if (veh && m_pPed->bInVehicle && veh->GetUp().z < 0.0f + && veh->m_vecMoveSpeed.Magnitude() < 0.05f && veh->IsCar() && !veh->bIsInWater) { + + if (veh->GetUp().z < -0.5f) { + m_nUpsideDownCounter += 2; + + } else { + m_nUpsideDownCounter++; + } + } else { + m_nUpsideDownCounter = 0; + } + + if (m_nUpsideDownCounter > 6 && veh->bCanBeDamaged) { + veh->m_fHealth = 249.0f < veh->m_fHealth ? 249.0f : veh->m_fHealth; + if (veh->IsCar()) { + CAutomobile* car = (CAutomobile*)veh; + car->Damage.SetEngineStatus(225); + car->m_pSetOnFireEntity = nil; + } + } + } + if (FindPlayerVehicle()) { + CVehicle *veh = FindPlayerVehicle(); + veh->m_nZoneLevel = LEVEL_IGNORE; + for (int i = 0; i < ARRAY_SIZE(veh->pPassengers); i++) { + if (veh->pPassengers[i]) + veh->pPassengers[i]->m_nZoneLevel = LEVEL_GENERIC; + } + CStats::DistanceTravelledInVehicle += veh->m_fDistanceTravelled; + } else { + CStats::DistanceTravelledOnFoot += FindPlayerPed()->m_fDistanceTravelled; + } +} + +bool +CPlayerInfo::IsPlayerInRemoteMode() +{ + return m_pRemoteVehicle || m_bInRemoteMode; +} + +void +CPlayerInfo::SavePlayerInfo(uint8 *buf, uint32 *size) +{ + // Interesting + *size = sizeof(CPlayerInfo); + +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); buf += sizeof(data); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nMoney); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_WBState); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nWBTime); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nTrafficMultiplier); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_fRoadDensity); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nTotalPackages); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bInfiniteSprint); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bFastReload); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfJailFree); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfHospitalFree); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_aPlayerName); +#undef CopyToBuf +} + +void +CPlayerInfo::LoadPlayerInfo(uint8 *buf, uint32 size) +{ +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); buf += sizeof(data); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nMoney); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_WBState); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nWBTime); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nTrafficMultiplier); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_fRoadDensity); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nTotalPackages); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bInfiniteSprint); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bFastReload); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfJailFree); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfHospitalFree); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_aPlayerName) +#undef CopyFromBuf +} + +void +CPlayerInfo::FindClosestCarSectorList(CPtrList& carList, CPed* ped, float unk1, float unk2, float unk3, float unk4, float* lastCloseness, CVehicle** closestCarOutput) +{ + for (CPtrNode* node = carList.first; node; node = node->next) { + CVehicle *car = (CVehicle*)node->item; + if(car->m_scanCode != CWorld::GetCurrentScanCode()) { + if (!car->bUsesCollision || !car->IsVehicle()) + continue; + + car->m_scanCode = CWorld::GetCurrentScanCode(); + if (car->GetStatus() != STATUS_WRECKED && car->GetStatus() != STATUS_TRAIN_MOVING + && (car->GetUp().z > 0.3f || (car->IsVehicle() && ((CVehicle*)car)->m_vehType == VEHICLE_TYPE_BIKE))) { + CVector carCentre = car->GetBoundCentre(); + + if (Abs(ped->GetPosition().z - carCentre.z) < 2.0f) { + float dist = (ped->GetPosition() - carCentre).Magnitude2D(); + if (dist <= 10.0f && !CCranes::IsThisCarBeingCarriedByAnyCrane(car)) { + EvaluateCarPosition(car, ped, dist, lastCloseness, closestCarOutput); + } + } + } + } + } +} + +// lastCloseness is passed to other calls of this function +void +CPlayerInfo::EvaluateCarPosition(CEntity *carToTest, CPed *player, float carBoundCentrePedDist, float *lastCloseness, CVehicle **closestCarOutput) +{ + // This dist used for determining the angle to face + CVector2D dist(carToTest->GetPosition() - player->GetPosition()); + float neededTurn = CGeneral::GetATanOfXY(player->GetForward().x, player->GetForward().y) - CGeneral::GetATanOfXY(dist.x, dist.y); + while (neededTurn >= PI) { + neededTurn -= 2 * PI; + } + + while (neededTurn < -PI) { + neededTurn += 2 * PI; + } + + // This dist used for evaluating cars' distances, weird... + // Accounts inverted needed turn (or needed turn in long way) and car dist. + float closeness = (1.0f - Abs(neededTurn) / TWOPI) * (10.0f - carBoundCentrePedDist); + if (closeness > *lastCloseness) { + *lastCloseness = closeness; + *closestCarOutput = (CVehicle*)carToTest; + } +} + +const CVector & +CPlayerInfo::GetPos() +{ +#ifdef FIX_BUGS + if (!m_pPed) + return TheCamera.GetPosition(); +#endif + if (m_pPed->InVehicle()) + return m_pPed->m_pMyVehicle->GetPosition(); + return m_pPed->GetPosition(); +} + +CVector +FindPlayerCoors(void) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return TheCamera.GetPosition(); +#endif + CPlayerPed *ped = FindPlayerPed(); + if(ped->InVehicle()) + return ped->m_pMyVehicle->GetPosition(); + else + return ped->GetPosition(); +} + +const CVector & +FindPlayerSpeed(void) +{ +#ifdef FIX_BUGS + static CVector vecTmpVector(0.0f, 0.0f, 0.0f); + if (CReplay::IsPlayingBack()) + return vecTmpVector; +#endif + CPlayerPed *ped = FindPlayerPed(); + if(ped->InVehicle()) + return ped->m_pMyVehicle->m_vecMoveSpeed; + else + return ped->m_vecMoveSpeed; +} + +CVehicle * +FindPlayerVehicle(void) +{ + CPlayerPed *ped = FindPlayerPed(); + if(ped && ped->InVehicle()) return ped->m_pMyVehicle; + return nil; +} + +CEntity * +FindPlayerEntity(void) +{ + CPlayerPed *ped = FindPlayerPed(); + if(ped->InVehicle()) + return ped->m_pMyVehicle; + else + return ped; +} + +CVehicle * +FindPlayerTrain(void) +{ + if(FindPlayerVehicle() && FindPlayerVehicle()->IsTrain()) + return FindPlayerVehicle(); + else + return nil; +} + +CPlayerPed * +FindPlayerPed(void) +{ + return CWorld::Players[CWorld::PlayerInFocus].m_pPed; +} + +const CVector & +FindPlayerCentreOfWorld(int32 player) +{ +#ifdef FIX_BUGS + if(CReplay::IsPlayingBack()) return TheCamera.GetPosition(); +#endif + 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(); +} + +const CVector & +FindPlayerCentreOfWorld_NoSniperShift(void) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) return TheCamera.GetPosition(); +#endif + 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 FindPlayerPed()->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 FindPlayerPed()->GetForward().Heading(); +} + +bool +CPlayerInfo::IsRestartingAfterDeath() +{ + return m_WBState == WBSTATE_WASTED; +} + +bool +CPlayerInfo::IsRestartingAfterArrest() +{ + return m_WBState == WBSTATE_BUSTED; +} + +void +CPlayerInfo::KillPlayer() +{ + if (m_WBState != WBSTATE_PLAYING) return; + + m_WBState = WBSTATE_WASTED; + m_nWBTime = CTimer::GetTimeInMilliseconds(); + CDarkel::ResetOnPlayerDeath(); + CMessages::AddBigMessage(TheText.Get("DEAD"), 4000, 2); + CStats::TimesDied++; +} + +void +CPlayerInfo::ArrestPlayer() +{ + if (m_WBState != WBSTATE_PLAYING) return; + + m_WBState = WBSTATE_BUSTED; + m_nWBTime = CTimer::GetTimeInMilliseconds(); + CDarkel::ResetOnPlayerDeath(); + CMessages::AddBigMessage(TheText.Get("BUSTED"), 5000, 2); + CStats::TimesArrested++; +} + +void +CPlayerInfo::PlayerFailedCriticalMission() +{ + if (m_WBState != WBSTATE_PLAYING) + return; + m_WBState = WBSTATE_FAILED_CRITICAL_MISSION; + m_nWBTime = CTimer::GetTimeInMilliseconds(); + CDarkel::ResetOnPlayerDeath(); +} + +void +CPlayerInfo::CancelPlayerEnteringCars(CVehicle *car) +{ + if (!car || car == m_pPed->m_pMyVehicle) { + if (m_pPed->EnteringCar()) + m_pPed->QuitEnteringCar(); + } + if (m_pPed->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER || m_pPed->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) + m_pPed->ClearObjective(); +} + +void +CPlayerInfo::MakePlayerSafe(bool toggle) +{ + if (toggle) { + CTheScripts::ResetCountdownToMakePlayerUnsafe(); + m_pPed->m_pWanted->m_bIgnoredByEveryone = true; + CWorld::StopAllLawEnforcersInTheirTracks(); + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_PLAYERINFO); + CPad::StopPadsShaking(); + m_pPed->bBulletProof = true; + m_pPed->bFireProof = true; + m_pPed->bCollisionProof = true; + m_pPed->bMeleeProof = true; + m_pPed->bOnlyDamagedByPlayer = true; + m_pPed->bExplosionProof = true; + m_pPed->m_bCanBeDamaged = false; + ((CPlayerPed*)m_pPed)->ClearAdrenaline(); + CancelPlayerEnteringCars(nil); + gFireManager.ExtinguishPoint(GetPos(), 4000.0f); + CExplosion::RemoveAllExplosionsInArea(GetPos(), 4000.0f); + CProjectileInfo::RemoveAllProjectiles(); + CWorld::SetAllCarsCanBeDamaged(false); + CWorld::ExtinguishAllCarFiresInArea(GetPos(), 4000.0f); + CReplay::DisableReplays(); + + } else if (!CGame::playingIntro && !CTheScripts::IsCountdownToMakePlayerUnsafeOn()) { + m_pPed->m_pWanted->m_bIgnoredByEveryone = false; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_PLAYERINFO); + m_pPed->bBulletProof = false; + m_pPed->bFireProof = false; + m_pPed->bCollisionProof = false; + m_pPed->bMeleeProof = false; + m_pPed->bOnlyDamagedByPlayer = false; + m_pPed->bExplosionProof = false; + m_pPed->m_bCanBeDamaged = true; + CWorld::SetAllCarsCanBeDamaged(true); + CReplay::EnableReplays(); + } +} + +void +CPlayerInfo::BlowUpRCBuggy(void) +{ + if (!m_pRemoteVehicle || m_pRemoteVehicle->bRemoveFromWorld) + return; + + CRemote::TakeRemoteControlledCarFromPlayer(); + m_pRemoteVehicle->BlowUpCar(FindPlayerPed()); +} + +// There is something unfinished in here... Sadly all IDBs we have have it unfinished. +void +CPlayerInfo::AwardMoneyForExplosion(CVehicle *wreckedCar) +{ + if (CTimer::GetTimeInMilliseconds() - m_nPreviousTimeRewardedForExplosion < 6000) + ++m_nExplosionsSinceLastReward; + else + m_nExplosionsSinceLastReward = 1; + + m_nPreviousTimeRewardedForExplosion = CTimer::GetTimeInMilliseconds(); + int award = wreckedCar->pHandling->nMonetaryValue * 0.002f; + sprintf(gString, "$%d", award); +#ifdef MONEY_MESSAGES + // This line is a leftover from PS2, I don't know what it was meant to be. + // CVector sth(TheCamera.GetPosition() * 4.0f); + + CMoneyMessages::RegisterOne(wreckedCar->GetPosition() + CVector(0.0f, 0.0f, 2.0f), gString, 0, 255, 0, 2.0f, 0.5f); +#endif + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += award; + + for (int i = m_nExplosionsSinceLastReward; i > 1; --i) { + CGeneral::GetRandomNumber(); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += award; + } +} + +#ifdef GTA_PC +void +CPlayerInfo::SetPlayerSkin(const char *skin) +{ + strncpy(m_aSkinName, skin, 32); + LoadPlayerSkin(); +} + +void +CPlayerInfo::LoadPlayerSkin() +{ + DeletePlayerSkin(); + + m_pSkinTexture = CPlayerSkin::GetSkinTexture(m_aSkinName); + if (!m_pSkinTexture) + m_pSkinTexture = CPlayerSkin::GetSkinTexture(DEFAULT_SKIN_NAME); +} + +void +CPlayerInfo::DeletePlayerSkin() +{ + if (m_pSkinTexture) { + RwTextureDestroy(m_pSkinTexture); + m_pSkinTexture = nil; + } +} +#endif \ No newline at end of file diff --git a/src/core/PlayerInfo.h b/src/core/PlayerInfo.h new file mode 100644 index 0000000..956756e --- /dev/null +++ b/src/core/PlayerInfo.h @@ -0,0 +1,97 @@ +#pragma once + +#include "ColModel.h" + +enum eWastedBustedState +{ + WBSTATE_PLAYING, + WBSTATE_WASTED, + WBSTATE_BUSTED, + WBSTATE_FAILED_CRITICAL_MISSION, +}; + +class CEntity; +class CPed; +class CVehicle; +class CPlayerPed; +class CCivilianPed; + +class CPlayerInfo +{ +public: + CPlayerPed *m_pPed; + CVehicle *m_pRemoteVehicle; + CColModel m_ColModel; + CVehicle *m_pVehicleEx; // vehicle using the col model above + char m_aPlayerName[70]; + int32 m_nMoney; + int32 m_nVisibleMoney; + int32 m_nCollectedPackages; + int32 m_nTotalPackages; + uint32 m_nLastBumpPlayerCarTimer; + uint32 m_nUnusedTaxiTimer; + bool m_bUnusedTaxiThing; + uint32 m_nNextSexFrequencyUpdateTime; + uint32 m_nNextSexMoneyUpdateTime; + int32 m_nSexFrequency; + CCivilianPed *m_pHooker; + int8 m_WBState; // eWastedBustedState + uint32 m_nWBTime; + bool m_bInRemoteMode; + uint32 m_nTimeLostRemoteCar; + uint32 m_nTimeLastHealthLoss; + uint32 m_nTimeLastArmourLoss; + uint32 m_nTimeTankShotGun; + int32 m_nUpsideDownCounter; + int32 field_248; + int16 m_nTrafficMultiplier; + float m_fRoadDensity; + uint32 m_nPreviousTimeRewardedForExplosion; + int32 m_nExplosionsSinceLastReward; + int32 field_268; + int32 field_272; + bool m_bInfiniteSprint; + bool m_bFastReload; + bool m_bGetOutOfJailFree; + bool m_bGetOutOfHospitalFree; +#ifdef GTA_PC + char m_aSkinName[32]; + RwTexture *m_pSkinTexture; +#endif + + void MakePlayerSafe(bool); + void AwardMoneyForExplosion(CVehicle *vehicle); + const CVector &GetPos(); + void Process(void); + void KillPlayer(void); + void ArrestPlayer(void); + bool IsPlayerInRemoteMode(void); + void PlayerFailedCriticalMission(void); + void Clear(void); + void BlowUpRCBuggy(void); + void CancelPlayerEnteringCars(CVehicle*); + bool IsRestartingAfterDeath(void); + bool IsRestartingAfterArrest(void); + void EvaluateCarPosition(CEntity*, CPed*, float, float*, CVehicle**); + void LoadPlayerInfo(uint8 *buf, uint32 size); + void SavePlayerInfo(uint8 *buf, uint32* size); + void FindClosestCarSectorList(CPtrList&, CPed*, float, float, float, float, float*, CVehicle**); + +#ifdef GTA_PC + void LoadPlayerSkin(); + void SetPlayerSkin(const char *skin); + void DeletePlayerSkin(); +#endif +}; + +CPlayerPed *FindPlayerPed(void); +CVehicle *FindPlayerVehicle(void); +CVehicle *FindPlayerTrain(void); +CEntity *FindPlayerEntity(void); +CVector FindPlayerCoors(void); +const CVector &FindPlayerSpeed(void); +const CVector &FindPlayerCentreOfWorld(int32 player); +const CVector &FindPlayerCentreOfWorld_NoSniperShift(void); +float FindPlayerHeading(void); + +VALIDATE_SIZE(CPlayerInfo, 0x13C); diff --git a/src/core/Pools.cpp b/src/core/Pools.cpp new file mode 100644 index 0000000..b024866 --- /dev/null +++ b/src/core/Pools.cpp @@ -0,0 +1,585 @@ +#include "common.h" + +#include "Pools.h" + +#include "Boat.h" +#include "CarCtrl.h" +#ifdef MISSION_REPLAY +#include "GenericGameStorage.h" +#endif +#include "Population.h" +#include "ProjectileInfo.h" +#include "SaveBuf.h" +#include "Streaming.h" +#include "Wanted.h" +#include "World.h" +#include "MemoryHeap.h" + +CCPtrNodePool *CPools::ms_pPtrNodePool; +CEntryInfoNodePool *CPools::ms_pEntryInfoNodePool; +CPedPool *CPools::ms_pPedPool; +CVehiclePool *CPools::ms_pVehiclePool; +CBuildingPool *CPools::ms_pBuildingPool; +CTreadablePool *CPools::ms_pTreadablePool; +CObjectPool *CPools::ms_pObjectPool; +CDummyPool *CPools::ms_pDummyPool; +CAudioScriptObjectPool *CPools::ms_pAudioScriptObjectPool; + +#ifdef GTA_PS2 // or USE_CUSTOM_ALLOCATOR +#define CHECKMEM(msg) CMemCheck::AllocateMemCheckBlock(msg) +#else +#define CHECKMEM(msg) +#endif + +void +CPools::Initialise(void) +{ + PUSH_MEMID(MEMID_POOLS); + CHECKMEM("before pools"); + ms_pPtrNodePool = new CCPtrNodePool(NUMPTRNODES); + CHECKMEM("after CPtrNodePool"); + ms_pEntryInfoNodePool = new CEntryInfoNodePool(NUMENTRYINFOS); + CHECKMEM("after CEntryInfoNodePool"); + ms_pPedPool = new CPedPool(NUMPEDS); + CHECKMEM("after CPedPool"); + ms_pVehiclePool = new CVehiclePool(NUMVEHICLES); + CHECKMEM("after CVehiclePool"); + ms_pBuildingPool = new CBuildingPool(NUMBUILDINGS); + CHECKMEM("after CBuildingPool"); + ms_pTreadablePool = new CTreadablePool(NUMTREADABLES); + CHECKMEM("after CTreadablePool"); + ms_pObjectPool = new CObjectPool(NUMOBJECTS); + CHECKMEM("after CObjectPool"); + ms_pDummyPool = new CDummyPool(NUMDUMMIES); + CHECKMEM("after CDummyPool"); + ms_pAudioScriptObjectPool = new CAudioScriptObjectPool(NUMAUDIOSCRIPTOBJECTS); + CHECKMEM("after pools"); + POP_MEMID(); +} + +void +CPools::ShutDown(void) +{ + debug("PtrNodes left %d\n", ms_pPtrNodePool->GetNoOfUsedSpaces()); + debug("EntryInfoNodes left %d\n", ms_pEntryInfoNodePool->GetNoOfUsedSpaces()); + debug("Peds left %d\n", ms_pPedPool->GetNoOfUsedSpaces()); + debug("Vehicles left %d\n", ms_pVehiclePool->GetNoOfUsedSpaces()); + debug("Buildings left %d\n", ms_pBuildingPool->GetNoOfUsedSpaces()); + debug("Treadables left %d\n", ms_pTreadablePool->GetNoOfUsedSpaces()); + debug("Objects left %d\n", ms_pObjectPool->GetNoOfUsedSpaces()); + debug("Dummys left %d\n", ms_pDummyPool->GetNoOfUsedSpaces()); + debug("AudioScriptObjects left %d\n", ms_pAudioScriptObjectPool->GetNoOfUsedSpaces()); + printf("Shutdown pool started\n"); + + delete ms_pPtrNodePool; + delete ms_pEntryInfoNodePool; + delete ms_pPedPool; + delete ms_pVehiclePool; + delete ms_pBuildingPool; + delete ms_pTreadablePool; + delete ms_pObjectPool; + delete ms_pDummyPool; + delete ms_pAudioScriptObjectPool; + + printf("Shutdown pool done\n"); +} + +int32 CPools::GetPedRef(CPed *ped) { return ms_pPedPool->GetIndex(ped); } +CPed *CPools::GetPed(int32 handle) { return ms_pPedPool->GetAt(handle); } +int32 CPools::GetVehicleRef(CVehicle *vehicle) { return ms_pVehiclePool->GetIndex(vehicle); } +CVehicle *CPools::GetVehicle(int32 handle) { return ms_pVehiclePool->GetAt(handle); } +int32 CPools::GetObjectRef(CObject *object) { return ms_pObjectPool->GetIndex(object); } +CObject *CPools::GetObject(int32 handle) { return ms_pObjectPool->GetAt(handle); } + +void +CPools::CheckPoolsEmpty() +{ + assert(ms_pPedPool->GetNoOfUsedSpaces() == 0); + assert(ms_pVehiclePool->GetNoOfUsedSpaces() == 0); + printf("pools have been cleared\n"); +} + + +void +CPools::MakeSureSlotInObjectPoolIsEmpty(int32 slot) +{ + if (ms_pObjectPool->GetIsFree(slot)) return; + + CObject *object = ms_pObjectPool->GetSlot(slot); + if (object->ObjectCreatedBy == TEMP_OBJECT) { + CWorld::Remove(object); + delete object; + } else if (!CProjectileInfo::RemoveIfThisIsAProjectile(object)) { + // relocate to another slot?? + CObject *newObject = new CObject(object->GetModelIndex(), false); + CWorld::Remove(object); +#if 0 // todo better + *newObject = *object; +#else + memcpy(newObject, object, ms_pObjectPool->GetMaxEntrySize()); +#endif + CWorld::Add(newObject); + object->m_rwObject = nil; + delete object; + newObject->m_pFirstReference = nil; + } +} + +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); + +void CPools::LoadVehiclePool(uint8* buf, uint32 size) +{ +INITSAVEBUF + int nNumCars, nNumBoats; + ReadSaveBuf(&nNumCars, buf); + ReadSaveBuf(&nNumBoats, buf); + for (int i = 0; i < nNumCars + nNumBoats; i++) { + uint32 type; + int16 model; + int32 slot; + + ReadSaveBuf(&type, buf); + ReadSaveBuf(&model, buf); + CStreaming::RequestModel(model, STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + ReadSaveBuf(&slot, buf); + CVehicle* pVehicle; +#ifdef COMPATIBLE_SAVES + if (type == VEHICLE_TYPE_BOAT) + pVehicle = new(slot) CBoat(model, RANDOM_VEHICLE); + else if (type == VEHICLE_TYPE_CAR) + pVehicle = new(slot) CAutomobile(model, RANDOM_VEHICLE); + else + assert(0); + --CCarCtrl::NumRandomCars; + pVehicle->Load(buf); + CWorld::Add(pVehicle); +#else + char* vbuf = new char[Max(CAutomobile::nSaveStructSize, CBoat::nSaveStructSize)]; + if (type == VEHICLE_TYPE_BOAT) { + memcpy(vbuf, buf, sizeof(CBoat)); + SkipSaveBuf(buf, sizeof(CBoat)); + CBoat* pBoat = new(slot) CBoat(model, RANDOM_VEHICLE); + pVehicle = pBoat; + --CCarCtrl::NumRandomCars; + } + else if (type == VEHICLE_TYPE_CAR) { + memcpy(vbuf, buf, sizeof(CAutomobile)); + SkipSaveBuf(buf, sizeof(CAutomobile)); + CStreaming::RequestModel(model, 0); // is it needed? + CStreaming::LoadAllRequestedModels(false); + CAutomobile* pAutomobile = new(slot) CAutomobile(model, RANDOM_VEHICLE); + pVehicle = pAutomobile; + CCarCtrl::NumRandomCars--; // why? + pAutomobile->Damage = ((CAutomobile*)vbuf)->Damage; + pAutomobile->SetupDamageAfterLoad(); + } + else + assert(0); + CVehicle* pBufferVehicle = (CVehicle*)vbuf; + pVehicle->GetMatrix() = pBufferVehicle->GetMatrix(); + pVehicle->VehicleCreatedBy = pBufferVehicle->VehicleCreatedBy; + pVehicle->m_currentColour1 = pBufferVehicle->m_currentColour1; + pVehicle->m_currentColour2 = pBufferVehicle->m_currentColour2; + pVehicle->m_nAlarmState = pBufferVehicle->m_nAlarmState; + pVehicle->m_nNumMaxPassengers = pBufferVehicle->m_nNumMaxPassengers; + pVehicle->field_1D0[0] = pBufferVehicle->field_1D0[0]; + pVehicle->field_1D0[1] = pBufferVehicle->field_1D0[1]; + pVehicle->field_1D0[2] = pBufferVehicle->field_1D0[2]; + pVehicle->field_1D0[3] = pBufferVehicle->field_1D0[3]; + pVehicle->m_fSteerAngle = pBufferVehicle->m_fSteerAngle; + pVehicle->m_fGasPedal = pBufferVehicle->m_fGasPedal; + pVehicle->m_fBrakePedal = pBufferVehicle->m_fBrakePedal; + pVehicle->bIsLawEnforcer = pBufferVehicle->bIsLawEnforcer; + pVehicle->bIsLocked = pBufferVehicle->bIsLocked; + pVehicle->bEngineOn = pBufferVehicle->bEngineOn; + pVehicle->bIsHandbrakeOn = pBufferVehicle->bIsHandbrakeOn; + pVehicle->bLightsOn = pBufferVehicle->bLightsOn; + pVehicle->bFreebies = pBufferVehicle->bFreebies; + pVehicle->m_fHealth = pBufferVehicle->m_fHealth; + pVehicle->m_nCurrentGear = pBufferVehicle->m_nCurrentGear; + pVehicle->m_fChangeGearTime = pBufferVehicle->m_fChangeGearTime; + pVehicle->m_nTimeOfDeath = pBufferVehicle->m_nTimeOfDeath; +#ifdef FIX_BUGS //must be copypaste + pVehicle->m_nBombTimer = pBufferVehicle->m_nBombTimer; +#else + pVehicle->m_nTimeOfDeath = pBufferVehicle->m_nTimeOfDeath; +#endif + pVehicle->m_nDoorLock = pBufferVehicle->m_nDoorLock; + pVehicle->SetStatus(pBufferVehicle->GetStatus()); + pVehicle->SetType(pBufferVehicle->GetType()); + (pVehicle->GetAddressOfEntityProperties())[0] = (pBufferVehicle->GetAddressOfEntityProperties())[0]; + (pVehicle->GetAddressOfEntityProperties())[1] = (pBufferVehicle->GetAddressOfEntityProperties())[1]; + pVehicle->AutoPilot = pBufferVehicle->AutoPilot; + CWorld::Add(pVehicle); + delete[] vbuf; +#endif + } +VALIDATESAVEBUF(size) +} + +void CPools::SaveVehiclePool(uint8* buf, uint32* size) +{ +INITSAVEBUF + int nNumCars = 0; + int nNumBoats = 0; + int nPoolSize = GetVehiclePool()->GetSize(); + for (int i = 0; i < nPoolSize; i++) { + CVehicle* pVehicle = GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + bool bHasPassenger = false; + for (int j = 0; j < ARRAY_SIZE(pVehicle->pPassengers); j++) { + if (pVehicle->pPassengers[j]) + bHasPassenger = true; + } +#ifdef MISSION_REPLAY + bool bForceSaving = CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pMyVehicle == pVehicle && IsQuickSave; +#ifdef FIX_BUGS + if ((!pVehicle->pDriver && !bHasPassenger) || bForceSaving) { +#else + if (!pVehicle->pDriver && !bHasPassenger) { +#endif + if (pVehicle->IsCar() && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) + ++nNumCars; + if (pVehicle->IsBoat() && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) + ++nNumBoats; +#else + if (!pVehicle->pDriver && !bHasPassenger) { + if (pVehicle->IsCar() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) + ++nNumCars; + if (pVehicle->IsBoat() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) + ++nNumBoats; +#endif + } + } + *size = nNumCars * (sizeof(uint32) + sizeof(int16) + sizeof(int32) + CAutomobile::nSaveStructSize) + sizeof(int) + + nNumBoats * (sizeof(uint32) + sizeof(int16) + sizeof(int32) + CBoat::nSaveStructSize) + sizeof(int); + WriteSaveBuf(buf, nNumCars); + WriteSaveBuf(buf, nNumBoats); + for (int i = 0; i < nPoolSize; i++) { + CVehicle* pVehicle = GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + bool bHasPassenger = false; + for (int j = 0; j < ARRAY_SIZE(pVehicle->pPassengers); j++) { + if (pVehicle->pPassengers[j]) + bHasPassenger = true; + } +#ifdef MISSION_REPLAY + bool bForceSaving = CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pMyVehicle == pVehicle && IsQuickSave; +#endif +#if defined FIX_BUGS && defined MISSION_REPLAY + if ((!pVehicle->pDriver && !bHasPassenger) || bForceSaving) { +#else + if (!pVehicle->pDriver && !bHasPassenger) { +#endif +#ifdef COMPATIBLE_SAVES +#ifdef MISSION_REPLAY + if ((pVehicle->IsCar() || pVehicle->IsBoat()) && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) { +#else + if ((pVehicle->IsCar() || pVehicle->IsBoat()) && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) { +#endif + WriteSaveBuf(buf, pVehicle->m_vehType); + WriteSaveBuf(buf, pVehicle->GetModelIndex()); + WriteSaveBuf(buf, GetVehicleRef(pVehicle)); + pVehicle->Save(buf); + } +#else +#ifdef MISSION_REPLAY + if (pVehicle->IsCar() && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) { +#else + if (pVehicle->IsCar() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) { +#endif + WriteSaveBuf(buf, pVehicle->m_vehType); + WriteSaveBuf(buf, pVehicle->GetModelIndex()); + WriteSaveBuf(buf, GetVehicleRef(pVehicle)); + memcpy(buf, pVehicle, sizeof(CAutomobile)); + SkipSaveBuf(buf, sizeof(CAutomobile)); + } +#ifdef MISSION_REPLAY + if (pVehicle->IsBoat() && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) { +#else + if (pVehicle->IsBoat() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) { +#endif + WriteSaveBuf(buf, pVehicle->m_vehType); + WriteSaveBuf(buf, pVehicle->GetModelIndex()); + WriteSaveBuf(buf, GetVehicleRef(pVehicle)); + memcpy(buf, pVehicle, sizeof(CBoat)); + SkipSaveBuf(buf, sizeof(CBoat)); + } +#endif + } + } +VALIDATESAVEBUF(*size) +} + +void CPools::SaveObjectPool(uint8* buf, uint32* size) +{ +INITSAVEBUF + CProjectileInfo::RemoveAllProjectiles(); + CObject::DeleteAllTempObjects(); + int nObjects = 0; + int nPoolSize = GetObjectPool()->GetSize(); + for (int i = 0; i < nPoolSize; i++) { + CObject* pObject = GetObjectPool()->GetSlot(i); + if (!pObject) + continue; + if (pObject->ObjectCreatedBy == MISSION_OBJECT) + ++nObjects; + } + *size = nObjects * (sizeof(int16) + sizeof(int) + sizeof(CCompressedMatrix) + + sizeof(float) + sizeof(CCompressedMatrix) + sizeof(int8) + 7 * sizeof(bool) + sizeof(float) + + sizeof(int8) + sizeof(int8) + sizeof(uint32) + 2 * sizeof(uint32)) + sizeof(int); + CopyToBuf(buf, nObjects); + for (int i = 0; i < nPoolSize; i++) { + CObject* pObject = GetObjectPool()->GetSlot(i); + if (!pObject) + continue; + if (pObject->ObjectCreatedBy == MISSION_OBJECT) { + bool bIsPickup = pObject->bIsPickup; + bool bPickupObjWithMessage = pObject->bPickupObjWithMessage; + bool bOutOfStock = pObject->bOutOfStock; + bool bGlassCracked = pObject->bGlassCracked; + bool bGlassBroken = pObject->bGlassBroken; + bool bHasBeenDamaged = pObject->bHasBeenDamaged; + bool bUseVehicleColours = pObject->bUseVehicleColours; + CCompressedMatrix tmp; + CopyToBuf(buf, pObject->m_modelIndex); + int32 ref = GetObjectRef(pObject); + CopyToBuf(buf, ref); + tmp.CompressFromFullMatrix(pObject->GetMatrix()); + CopyToBuf(buf, tmp); + CopyToBuf(buf, pObject->m_fUprootLimit); + tmp.CompressFromFullMatrix(pObject->m_objectMatrix); + CopyToBuf(buf, tmp); + CopyToBuf(buf, pObject->ObjectCreatedBy); + CopyToBuf(buf, bIsPickup); + CopyToBuf(buf, bPickupObjWithMessage); + CopyToBuf(buf, bOutOfStock); + CopyToBuf(buf, bGlassCracked); + CopyToBuf(buf, bGlassBroken); + CopyToBuf(buf, bHasBeenDamaged); + CopyToBuf(buf, bUseVehicleColours); + CopyToBuf(buf, pObject->m_fCollisionDamageMultiplier); + CopyToBuf(buf, pObject->m_nCollisionDamageEffect); + CopyToBuf(buf, pObject->m_nSpecialCollisionResponseCases); + CopyToBuf(buf, pObject->m_nEndOfLifeTime); +#ifdef COMPATIBLE_SAVES + pObject->SaveEntityFlags(buf); +#else + CopyToBuf(buf, (pObject->GetAddressOfEntityProperties())[0]); + CopyToBuf(buf, (pObject->GetAddressOfEntityProperties())[1]); +#endif + } + } +VALIDATESAVEBUF(*size) +} + +void CPools::LoadObjectPool(uint8* buf, uint32 size) +{ +INITSAVEBUF + int nObjects; + CopyFromBuf(buf, nObjects); + for (int i = 0; i < nObjects; i++) { + int16 mi; + CopyFromBuf(buf, mi); + int ref; + CopyFromBuf(buf, ref); + char* obuf = new char[sizeof(CObject)]; + CObject* pBufferObject = (CObject*)obuf; + CCompressedMatrix tmp; + CopyFromBuf(buf, tmp); + tmp.DecompressIntoFullMatrix(pBufferObject->GetMatrix()); + CopyFromBuf(buf, pBufferObject->m_fUprootLimit); + CopyFromBuf(buf, tmp); + tmp.DecompressIntoFullMatrix(pBufferObject->m_objectMatrix); + CopyFromBuf(buf, pBufferObject->ObjectCreatedBy); + int8 bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bIsPickup = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bPickupObjWithMessage = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bOutOfStock = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bGlassCracked = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bGlassBroken = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bHasBeenDamaged = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bUseVehicleColours = bitFlag; + CopyFromBuf(buf, pBufferObject->m_fCollisionDamageMultiplier); + CopyFromBuf(buf, pBufferObject->m_nCollisionDamageEffect); + CopyFromBuf(buf, pBufferObject->m_nSpecialCollisionResponseCases); + CopyFromBuf(buf, pBufferObject->m_nEndOfLifeTime); +#ifndef COMPATIBLE_SAVES + CopyFromBuf(buf, (pBufferObject->GetAddressOfEntityProperties())[0]); + CopyFromBuf(buf, (pBufferObject->GetAddressOfEntityProperties())[1]); +#endif + if (GetObjectPool()->GetSlot(ref >> 8)) + CPopulation::ConvertToDummyObject(GetObjectPool()->GetSlot(ref >> 8)); + CObject* pObject = new(ref) CObject(mi, false); + pObject->GetMatrix() = pBufferObject->GetMatrix(); +#ifdef COMPATIBLE_SAVES + pObject->LoadEntityFlags(buf); +#endif + pObject->m_fUprootLimit = pBufferObject->m_fUprootLimit; + pObject->m_objectMatrix = pBufferObject->m_objectMatrix; + pObject->ObjectCreatedBy = pBufferObject->ObjectCreatedBy; + pObject->bIsPickup = pBufferObject->bIsPickup; + pObject->bPickupObjWithMessage = pBufferObject->bPickupObjWithMessage; + pObject->bOutOfStock = pBufferObject->bOutOfStock; + pObject->bGlassCracked = pBufferObject->bGlassCracked; + pObject->bGlassBroken = pBufferObject->bGlassBroken; + pObject->bHasBeenDamaged = pBufferObject->bHasBeenDamaged; + pObject->bUseVehicleColours = pBufferObject->bUseVehicleColours; + pObject->m_fCollisionDamageMultiplier = pBufferObject->m_fCollisionDamageMultiplier; + pObject->m_nCollisionDamageEffect = pBufferObject->m_nCollisionDamageEffect; + pObject->m_nSpecialCollisionResponseCases = pBufferObject->m_nSpecialCollisionResponseCases; + pObject->m_nEndOfLifeTime = pBufferObject->m_nEndOfLifeTime; +#ifndef COMPATIBLE_SAVES + (pObject->GetAddressOfEntityProperties())[0] = (pBufferObject->GetAddressOfEntityProperties())[0]; + (pObject->GetAddressOfEntityProperties())[1] = (pBufferObject->GetAddressOfEntityProperties())[1]; +#endif + pObject->bHasCollided = false; + CWorld::Add(pObject); + delete[] obuf; + } +VALIDATESAVEBUF(size) +} + +void CPools::SavePedPool(uint8* buf, uint32* size) +{ +INITSAVEBUF + int nNumPeds = 0; + int nPoolSize = GetPedPool()->GetSize(); + for (int i = 0; i < nPoolSize; i++) { + CPed* pPed = GetPedPool()->GetSlot(i); + if (!pPed) + continue; +#ifdef MISSION_REPLAY + if ((!pPed->bInVehicle || (pPed == CWorld::Players[CWorld::PlayerInFocus].m_pPed && IsQuickSave)) && pPed->m_nPedType == PEDTYPE_PLAYER1) +#else + if (!pPed->bInVehicle && pPed->m_nPedType == PEDTYPE_PLAYER1) +#endif + nNumPeds++; + } + *size = sizeof(int) + nNumPeds * (sizeof(uint32) + sizeof(int16) + sizeof(int) + CPlayerPed::nSaveStructSize + + sizeof(CWanted::MaximumWantedLevel) + sizeof(CWanted::nMaximumWantedLevel) + MAX_MODEL_NAME); + CopyToBuf(buf, nNumPeds); + for (int i = 0; i < nPoolSize; i++) { + CPed* pPed = GetPedPool()->GetSlot(i); + if (!pPed) + continue; +#ifdef MISSION_REPLAY + if ((!pPed->bInVehicle || (pPed == CWorld::Players[CWorld::PlayerInFocus].m_pPed && IsQuickSave)) && pPed->m_nPedType == PEDTYPE_PLAYER1) { +#else + if (!pPed->bInVehicle && pPed->m_nPedType == PEDTYPE_PLAYER1) { +#endif + CopyToBuf(buf, pPed->m_nPedType); + CopyToBuf(buf, pPed->m_modelIndex); + int32 ref = GetPedRef(pPed); + CopyToBuf(buf, ref); +#ifdef COMPATIBLE_SAVES + pPed->Save(buf); +#else + memcpy(buf, pPed, sizeof(CPlayerPed)); + SkipSaveBuf(buf, sizeof(CPlayerPed)); +#endif + CopyToBuf(buf, CWanted::MaximumWantedLevel); + CopyToBuf(buf, CWanted::nMaximumWantedLevel); + memcpy(buf, CModelInfo::GetModelInfo(pPed->GetModelIndex())->GetModelName(), MAX_MODEL_NAME); + SkipSaveBuf(buf, MAX_MODEL_NAME); + } + } +VALIDATESAVEBUF(*size); +#undef CopyToBuf +} + +void CPools::LoadPedPool(uint8* buf, uint32 size) +{ +INITSAVEBUF + int nPeds; + CopyFromBuf(buf, nPeds); + for (int i = 0; i < nPeds; i++) { + uint32 pedtype; + int16 model; + int ref; + + CopyFromBuf(buf, pedtype); + CopyFromBuf(buf, model); + CopyFromBuf(buf, ref); +#ifdef COMPATIBLE_SAVES + CPed* pPed; + + char name[MAX_MODEL_NAME]; + // Unfortunate hack: player model is stored after ped structure. + // It could be avoided by just using "player" because in practice it is always true. + memcpy(name, buf + CPlayerPed::nSaveStructSize + 2 * sizeof(int32), MAX_MODEL_NAME); + CStreaming::RequestSpecialModel(model, name, STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + + if (pedtype == PEDTYPE_PLAYER1) + pPed = new(ref) CPlayerPed(); + else + assert(0); + + pPed->Load(buf); + if (pedtype == PEDTYPE_PLAYER1) { + CopyFromBuf(buf, CWanted::MaximumWantedLevel); + CopyFromBuf(buf, CWanted::nMaximumWantedLevel); + SkipSaveBuf(buf, MAX_MODEL_NAME); + } + + if (pedtype == PEDTYPE_PLAYER1) { + pPed->m_wepAccuracy = 100; + CWorld::Players[0].m_pPed = (CPlayerPed*)pPed; + } + CWorld::Add(pPed); +#else + char* pbuf = new char[sizeof(CPlayerPed)]; + CPlayerPed* pBufferPlayer = (CPlayerPed*)pbuf; + CPed* pPed; + char name[MAX_MODEL_NAME]; + // the code implies that there was idea to load non-player ped + if (pedtype == PEDTYPE_PLAYER1) { // always true + memcpy(pbuf, buf, sizeof(CPlayerPed)); + SkipSaveBuf(buf, sizeof(CPlayerPed)); + CopyFromBuf(buf, CWanted::MaximumWantedLevel); + CopyFromBuf(buf, CWanted::nMaximumWantedLevel); + CopyFromBuf(buf, name); + } + CStreaming::RequestSpecialModel(model, name, STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + if (pedtype == PEDTYPE_PLAYER1) { + CPlayerPed* pPlayerPed = new(ref) CPlayerPed(); + for (int i = 0; i < ARRAY_SIZE(pPlayerPed->m_nTargettableObjects); i++) + pPlayerPed->m_nTargettableObjects[i] = pBufferPlayer->m_nTargettableObjects[i]; + pPlayerPed->m_fMaxStamina = pBufferPlayer->m_fMaxStamina; + pPed = pPlayerPed; + } + pPed->SetPosition(pBufferPlayer->GetPosition()); + pPed->m_fHealth = pBufferPlayer->m_fHealth; + pPed->m_fArmour = pBufferPlayer->m_fArmour; + pPed->CharCreatedBy = pBufferPlayer->CharCreatedBy; + pPed->m_currentWeapon = 0; + pPed->m_maxWeaponTypeAllowed = pBufferPlayer->m_maxWeaponTypeAllowed; + for (int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++) + pPed->m_weapons[i] = pBufferPlayer->m_weapons[i]; + + if (pedtype == PEDTYPE_PLAYER1) { + pPed->m_wepAccuracy = 100; + CWorld::Players[0].m_pPed = (CPlayerPed*)pPed; + } + CWorld::Add(pPed); + delete[] pbuf; +#endif + } +VALIDATESAVEBUF(size) +} + +#undef CopyFromBuf +#undef CopyToBuf \ No newline at end of file diff --git a/src/core/Pools.h b/src/core/Pools.h new file mode 100644 index 0000000..b0ba659 --- /dev/null +++ b/src/core/Pools.h @@ -0,0 +1,61 @@ +#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" +#include "AudioScriptObject.h" + +typedef CPool CCPtrNodePool; +typedef CPool CEntryInfoNodePool; +typedef CPool CPedPool; +typedef CPool CVehiclePool; +typedef CPool CBuildingPool; +typedef CPool CTreadablePool; +typedef CPool CObjectPool; +typedef CPool CDummyPool; +typedef CPool CAudioScriptObjectPool; + +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; + static CAudioScriptObjectPool *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 CAudioScriptObjectPool *GetAudioScriptObjectPool(void) { return ms_pAudioScriptObjectPool; } + + static void Initialise(void); + static void ShutDown(void); + static int32 GetPedRef(CPed *ped); + static CPed *GetPed(int32 handle); + static int32 GetVehicleRef(CVehicle *vehicle); + static CVehicle *GetVehicle(int32 handle); + static int32 GetObjectRef(CObject *object); + static CObject *GetObject(int32 handle); + static void CheckPoolsEmpty(); + static void MakeSureSlotInObjectPoolIsEmpty(int32 slot); + static void LoadObjectPool(uint8 *buf, uint32 size); + static void LoadPedPool(uint8 *buf, uint32 size); + static void LoadVehiclePool(uint8 *buf, uint32 size); + static void SaveObjectPool(uint8 *buf, uint32 *size); + static void SavePedPool(uint8 *buf, uint32 *size); + static void SaveVehiclePool(uint8 *buf, uint32 *size); +}; diff --git a/src/core/Profile.cpp b/src/core/Profile.cpp new file mode 100644 index 0000000..0aa18ab --- /dev/null +++ b/src/core/Profile.cpp @@ -0,0 +1,71 @@ +#include "common.h" +#include "Profile.h" + +#ifndef MASTER +float CProfile::ms_afStartTime[NUM_PROFILES]; +float CProfile::ms_afCumulativeTime[NUM_PROFILES]; +float CProfile::ms_afEndTime[NUM_PROFILES]; +float CProfile::ms_afMaxEndTime[NUM_PROFILES]; +float CProfile::ms_afMaxCumulativeTime[NUM_PROFILES]; +Const char *CProfile::ms_pProfileString[NUM_PROFILES]; +RwRGBA CProfile::ms_aBarColours[NUM_PROFILES]; + +void CProfile::Initialise() +{ + ms_afMaxEndTime[PROFILE_FRAME_RATE] = 0.0f; + ms_afMaxEndTime[PROFILE_PHYSICS] = 0.0f; + ms_afMaxEndTime[PROFILE_COLLISION] = 0.0f; + ms_afMaxEndTime[PROFILE_PED_AI] = 0.0f; + ms_afMaxEndTime[PROFILE_PROCESSING_TIME] = 0.0f; + ms_afMaxEndTime[PROFILE_RENDERING_TIME] = 0.0f; + ms_afMaxEndTime[PROFILE_TOTAL] = 0.0f; + + ms_pProfileString[PROFILE_FRAME_RATE] = "Frame rate"; + ms_pProfileString[PROFILE_PHYSICS] = "Physics"; + ms_pProfileString[PROFILE_COLLISION] = "Collision"; + ms_pProfileString[PROFILE_PED_AI] = "Ped AI"; + ms_pProfileString[PROFILE_PROCESSING_TIME] = "Processing time"; + ms_pProfileString[PROFILE_RENDERING_TIME] = "Rendering time"; + ms_pProfileString[PROFILE_TOTAL] = "Total"; + + ms_afMaxCumulativeTime[PROFILE_FRAME_RATE] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_PHYSICS] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_COLLISION] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_PED_AI] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_PROCESSING_TIME] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_RENDERING_TIME] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_TOTAL] = 0.0f; + + ms_aBarColours[PROFILE_PHYSICS] = { 0, 127, 255, 255 }; + ms_aBarColours[PROFILE_COLLISION] = { 0, 255, 255, 255 }; + ms_aBarColours[PROFILE_PED_AI] = { 255, 0, 0, 255 }; + ms_aBarColours[PROFILE_PROCESSING_TIME] = { 0, 255, 0, 255 }; + ms_aBarColours[PROFILE_RENDERING_TIME] = { 0, 0, 255, 255 }; + ms_aBarColours[PROFILE_TOTAL] = { 255, 255, 255, 255 }; +} + +void CProfile::SuspendProfile(eProfile profile) +{ + ms_afEndTime[profile] = -ms_afStartTime[profile]; + ms_afCumulativeTime[profile] -= ms_afStartTime[profile]; +} + +void CProfile::ShowResults() +{ + ms_afMaxEndTime[PROFILE_FRAME_RATE] = Max(ms_afMaxEndTime[PROFILE_FRAME_RATE], ms_afEndTime[PROFILE_FRAME_RATE]); + ms_afMaxEndTime[PROFILE_PHYSICS] = Max(ms_afMaxEndTime[PROFILE_PHYSICS], ms_afEndTime[PROFILE_PHYSICS]); + ms_afMaxEndTime[PROFILE_COLLISION] = Max(ms_afMaxEndTime[PROFILE_COLLISION], ms_afEndTime[PROFILE_COLLISION]); + ms_afMaxEndTime[PROFILE_PED_AI] = Max(ms_afMaxEndTime[PROFILE_PED_AI], ms_afEndTime[PROFILE_PED_AI]); + ms_afMaxEndTime[PROFILE_PROCESSING_TIME] = Max(ms_afMaxEndTime[PROFILE_PROCESSING_TIME], ms_afEndTime[PROFILE_PROCESSING_TIME]); + ms_afMaxEndTime[PROFILE_RENDERING_TIME] = Max(ms_afMaxEndTime[PROFILE_RENDERING_TIME], ms_afEndTime[PROFILE_RENDERING_TIME]); + ms_afMaxEndTime[PROFILE_TOTAL] = Max(ms_afMaxEndTime[PROFILE_TOTAL], ms_afEndTime[PROFILE_TOTAL]); + + ms_afMaxCumulativeTime[PROFILE_FRAME_RATE] = Max(ms_afMaxCumulativeTime[PROFILE_FRAME_RATE], ms_afCumulativeTime[PROFILE_FRAME_RATE]); + ms_afMaxCumulativeTime[PROFILE_PHYSICS] = Max(ms_afMaxCumulativeTime[PROFILE_PHYSICS], ms_afCumulativeTime[PROFILE_PHYSICS]); + ms_afMaxCumulativeTime[PROFILE_COLLISION] = Max(ms_afMaxCumulativeTime[PROFILE_COLLISION], ms_afCumulativeTime[PROFILE_COLLISION]); + ms_afMaxCumulativeTime[PROFILE_PED_AI] = Max(ms_afMaxCumulativeTime[PROFILE_PED_AI], ms_afCumulativeTime[PROFILE_PED_AI]); + ms_afMaxCumulativeTime[PROFILE_PROCESSING_TIME] = Max(ms_afMaxCumulativeTime[PROFILE_PROCESSING_TIME], ms_afCumulativeTime[PROFILE_PROCESSING_TIME]); + ms_afMaxCumulativeTime[PROFILE_RENDERING_TIME] = Max(ms_afMaxCumulativeTime[PROFILE_RENDERING_TIME], ms_afCumulativeTime[PROFILE_RENDERING_TIME]); + ms_afMaxCumulativeTime[PROFILE_TOTAL] = Max(ms_afMaxCumulativeTime[PROFILE_TOTAL], ms_afCumulativeTime[PROFILE_TOTAL]); +} +#endif diff --git a/src/core/Profile.h b/src/core/Profile.h new file mode 100644 index 0000000..4fe693a --- /dev/null +++ b/src/core/Profile.h @@ -0,0 +1,28 @@ +#pragma once + +enum eProfile +{ + PROFILE_FRAME_RATE, + PROFILE_PHYSICS, + PROFILE_COLLISION, + PROFILE_PED_AI, + PROFILE_PROCESSING_TIME, + PROFILE_RENDERING_TIME, + PROFILE_TOTAL, + NUM_PROFILES, +}; + +class CProfile +{ + static float ms_afStartTime[NUM_PROFILES]; + static float ms_afCumulativeTime[NUM_PROFILES]; + static float ms_afEndTime[NUM_PROFILES]; + static float ms_afMaxEndTime[NUM_PROFILES]; + static float ms_afMaxCumulativeTime[NUM_PROFILES]; + static Const char *ms_pProfileString[NUM_PROFILES]; + static RwRGBA ms_aBarColours[NUM_PROFILES]; +public: + static void Initialise(); + static void SuspendProfile(eProfile profile); + static void ShowResults(); +}; diff --git a/src/core/Radar.cpp b/src/core/Radar.cpp new file mode 100644 index 0000000..b29c19e --- /dev/null +++ b/src/core/Radar.cpp @@ -0,0 +1,1568 @@ +#if (!defined(GTA_PS2_STUFF) && defined(RWLIBS)) || defined(__MWERKS__) +#define WITHD3D +#endif +#include "config.h" +#include "common.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 "SaveBuf.h" +#include "Streaming.h" +#include "SpecialFX.h" + +float CRadar::m_radarRange; +sRadarTrace CRadar::ms_RadarTrace[NUMRADARBLIPS]; +CVector2D vec2DRadarOrigin; +int32 gRadarTxdIds[64]; + +CSprite2d CRadar::AsukaSprite; +CSprite2d CRadar::BombSprite; +CSprite2d CRadar::CatSprite; +CSprite2d CRadar::CentreSprite; +CSprite2d CRadar::CopcarSprite; +CSprite2d CRadar::DonSprite; +CSprite2d CRadar::EightSprite; +CSprite2d CRadar::ElSprite; +CSprite2d CRadar::IceSprite; +CSprite2d CRadar::JoeySprite; +CSprite2d CRadar::KenjiSprite; +CSprite2d CRadar::LizSprite; +CSprite2d CRadar::LuigiSprite; +CSprite2d CRadar::NorthSprite; +CSprite2d CRadar::RaySprite; +CSprite2d CRadar::SalSprite; +CSprite2d CRadar::SaveSprite; +CSprite2d CRadar::SpraySprite; +CSprite2d CRadar::TonySprite; +CSprite2d CRadar::WeaponSprite; +#ifdef MENU_MAP +CSprite2d CRadar::WaypointSprite; +#endif + +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, +#ifdef MENU_MAP + &WaypointSprite +#endif +}; + +// Why this doesn't coincide with world coordinates i don't know +#define RADAR_MIN_X (-2000.0f) +#define RADAR_MIN_Y (-2000.0f) +#define RADAR_MAX_X (2000.0f) +#define RADAR_MAX_Y (2000.0f) +#define RADAR_SIZE_X (RADAR_MAX_X - RADAR_MIN_X) +#define RADAR_SIZE_Y (RADAR_MAX_Y - RADAR_MIN_Y) + +#define RADAR_NUM_TILES (8) +#define RADAR_TILE_SIZE (RADAR_SIZE_X / RADAR_NUM_TILES) +static_assert(RADAR_TILE_SIZE == (RADAR_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) + +#ifdef MENU_MAP +int CRadar::TargetMarkerId = -1; +CVector CRadar::TargetMarkerPos; +#endif + +// taken from VC +float CRadar::cachedCos; +float CRadar::cachedSin; + +void 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; +} + +void RequestMapSection(int32 x, int32 y) +{ + ClipRadarTileCoords(x, y); + CStreaming::RequestTxd(gRadarTxdIds[x + RADAR_NUM_TILES * y], STREAMFLAGS_DONT_REMOVE | STREAMFLAGS_DEPENDENCY); +} + +void RemoveMapSection(int32 x, int32 y) +{ + if (x >= 0 && x <= RADAR_NUM_TILES - 1 && y >= 0 && y <= RADAR_NUM_TILES - 1) + CStreaming::RemoveTxd(gRadarTxdIds[x + RADAR_NUM_TILES * y]); +} + +// Transform from section indices to world coordinates +void 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); +} + +uint8 CRadar::CalculateBlipAlpha(float dist) +{ +#ifdef MENU_MAP + if (CMenuManager::bMenuMapActive) + return 255; +#endif + if (dist <= 1.0f) + return 255; + + if (dist <= 5.0f) + return (128.0f * ((dist - 1.0f) / 4.0f)) + ((1.0f - (dist - 1.0f) / 4.0f) * 255.0f); + + return 128; +} + +void CRadar::ChangeBlipBrightness(int32 i, int32 bright) +{ + int index = GetActualBlipArrayIndex(i); + if (index != -1) + ms_RadarTrace[index].m_bDim = bright != 1; +} + +void CRadar::ChangeBlipColour(int32 i, int32 color) +{ + int index = GetActualBlipArrayIndex(i); + if (index != -1) + ms_RadarTrace[index].m_nColor = color; +} + +void CRadar::ChangeBlipDisplay(int32 i, eBlipDisplay display) +{ + int index = GetActualBlipArrayIndex(i); + if (index != -1) + ms_RadarTrace[index].m_eBlipDisplay = display; +} + +void CRadar::ChangeBlipScale(int32 i, int32 scale) +{ + int index = GetActualBlipArrayIndex(i); + if (index != -1) + ms_RadarTrace[index].m_wScale = scale; +} + +void CRadar::ClearBlip(int32 i) +{ + int index = GetActualBlipArrayIndex(i); + if (index != -1) { + SetRadarMarkerState(index, false); + ms_RadarTrace[index].m_bInUse = false; +#ifndef MENU_MAP + // Ssshhh + ms_RadarTrace[index].m_eBlipType = BLIP_NONE; + ms_RadarTrace[index].m_eBlipDisplay = BLIP_DISPLAY_NEITHER; + ms_RadarTrace[index].m_eRadarSprite = RADAR_SPRITE_NONE; +#endif + } +} + +void CRadar::ClearBlipForEntity(eBlipType 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, false); + ms_RadarTrace[i].m_bInUse = false; + ms_RadarTrace[i].m_eBlipType = BLIP_NONE; + ms_RadarTrace[i].m_eBlipDisplay = BLIP_DISPLAY_NEITHER; + ms_RadarTrace[i].m_eRadarSprite = RADAR_SPRITE_NONE; + } + }; +} + +// Why not a proper clipping algorithm? +#ifdef THIS_IS_STUPID + +bool 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; +} + +// clip line p1,p2 against (-1.0, 1.0) in x and y, set out to clipped point closest to p1 +int 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; +} + +int CRadar::ClipRadarPoly(CVector2D *poly, const CVector2D *rect) +{ + CVector2D corners[4] = { + CVector2D( 1.0f, -1.0f ), // top right + CVector2D( 1.0f, 1.0f ), // bottom right + CVector2D( -1.0f, 1.0f ), // bottom left + CVector2D( -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; +} +#else + +int +ClipPolyPlane(const CVector2D *in, int nin, CVector2D *out, CVector *plane) +{ + int j; + int nout; + int x1, x2; + float d1, d2, t; + + nout = 0; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + + d1 = plane->x*in[x1].x + plane->y*in[x1].y + plane->z; + d2 = plane->x*in[x2].x + plane->y*in[x2].y + plane->z; + if(d1*d2 < 0.0f){ + t = d1/(d1 - d2); + out[nout++] = in[x1]*(1.0f-t) + in[x2]*t; + } + if(d2 >= 0.0f) + out[nout++] = in[x2]; + } + return nout; +} + +int CRadar::ClipRadarPoly(CVector2D *poly, const CVector2D *rect) +{ + CVector planes[4] = { + CVector(-1.0f, 0.0f, 1.0f), + CVector( 1.0f, 0.0f, 1.0f), + CVector(0.0f, -1.0f, 1.0f), + CVector(0.0f, 1.0f, 1.0f) + }; + CVector2D tmp[8]; + int n; + if(n = ClipPolyPlane(rect, 4, tmp, &planes[0]), n == 0) return 0; + if(n = ClipPolyPlane(tmp, n, poly, &planes[1]), n == 0) return 0; + if(n = ClipPolyPlane(poly, n, tmp, &planes[2]), n == 0) return 0; + if(n = ClipPolyPlane(tmp, n, poly, &planes[3]), n == 0) return 0; + return n; +} +#endif + +bool CRadar::DisplayThisBlip(int32 counter) +{ + switch (ms_RadarTrace[counter].m_eRadarSprite) { + case RADAR_SPRITE_BOMB: + case RADAR_SPRITE_SPRAY: + case RADAR_SPRITE_WEAPON: + return true; + default: + return false; + } +} + +void CRadar::Draw3dMarkers() +{ + for (int i = 0; i < NUMRADARBLIPS; i++) { + if (ms_RadarTrace[i].m_bInUse) { + switch (ms_RadarTrace[i].m_eBlipType) { + case BLIP_CAR: + { + CEntity *entity = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[i].m_nEntityHandle); + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + CVector pos = entity->GetPosition(); + pos.z += 1.2f * CModelInfo::GetColModel(entity->GetModelIndex())->boundingBox.max.z + 2.5f; + C3dMarkers::PlaceMarker(i | (ms_RadarTrace[i].m_BlipIndex << 16), MARKERTYPE_ARROW, pos, 2.5f, 0, 128, 255, 255, 1024, 0.2f, 5); + } + break; + } + case BLIP_CHAR: + { + CEntity *entity = CPools::GetPedPool()->GetAt(ms_RadarTrace[i].m_nEntityHandle); + if (entity != nil) { + if (((CPed*)entity)->InVehicle()) + entity = ((CPed * )entity)->m_pMyVehicle; + } + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + CVector pos = entity->GetPosition(); + pos.z += 3.0f; + C3dMarkers::PlaceMarker(i | (ms_RadarTrace[i].m_BlipIndex << 16), MARKERTYPE_ARROW, pos, 1.5f, 0, 128, 255, 255, 1024, 0.2f, 5); + } + break; + } + case BLIP_OBJECT: + { + CEntity *entity = CPools::GetObjectPool()->GetAt(ms_RadarTrace[i].m_nEntityHandle); + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + CVector pos = entity->GetPosition(); + pos.z += CModelInfo::GetColModel(entity->GetModelIndex())->boundingBox.max.z + 1.0f + 1.0f; + C3dMarkers::PlaceMarker(i | (ms_RadarTrace[i].m_BlipIndex << 16), MARKERTYPE_ARROW, pos, 1.0f, 0, 128, 255, 255, 1024, 0.2f, 5); + } + break; + } + case BLIP_COORD: + break; + case BLIP_CONTACT_POINT: + if (!CTheScripts::IsPlayerOnAMission()) { + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) + C3dMarkers::PlaceMarkerSet(i | (ms_RadarTrace[i].m_BlipIndex << 16), MARKERTYPE_CYLINDER, ms_RadarTrace[i].m_vecPos, 2.0f, 0, 128, 255, 128, 2048, 0.2f, 0); + } + break; + } + } + } +} + +void CRadar::DrawBlips() +{ + if ((!TheCamera.m_WideScreenOn && CHud::m_Wants_To_Draw_Hud) +#ifdef MENU_MAP + || CMenuManager::bMenuMapActive +#endif + ) { + 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); + +#ifdef MENU_MAP + if (!CMenuManager::bMenuMapActive) { +#endif + float angle; + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN) + angle = PI + FindPlayerHeading(); +#ifdef FIX_BUGS + else if (TheCamera.GetLookDirection() != LOOKING_FORWARD) + angle = FindPlayerHeading() - (PI + (TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].SourceBeforeLookBehind).Heading()); +#endif + 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); +#ifdef MENU_MAP + } +#endif + + CEntity *blipEntity = nil; + for(int blipId = 0; blipId < NUMRADARBLIPS; blipId++) { +#ifdef MENU_MAP + // A little hack to reuse cleared blips in menu map. hehe + if (!CMenuManager::bMenuMapActive || ms_RadarTrace[blipId].m_eBlipType == BLIP_CAR || + ms_RadarTrace[blipId].m_eBlipType == BLIP_CHAR || ms_RadarTrace[blipId].m_eBlipType == BLIP_OBJECT) +#endif + if (!ms_RadarTrace[blipId].m_bInUse) + continue; + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_CAR: + case BLIP_CHAR: + case BLIP_OBJECT: + if (ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_BOMB || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_SAVE + || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_SPRAY || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_WEAPON) { + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_CAR: + blipEntity = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + break; + case BLIP_CHAR: + blipEntity = CPools::GetPedPool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + if (blipEntity != nil) { + if (((CPed*)blipEntity)->InVehicle()) + blipEntity = ((CPed*)blipEntity)->m_pMyVehicle; + } + break; + case BLIP_OBJECT: + blipEntity = CPools::GetObjectPool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + break; + default: + break; + } + if (blipEntity) { + uint32 color = GetRadarTraceColour(ms_RadarTrace[blipId].m_nColor, ms_RadarTrace[blipId].m_bDim); + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + if (CTheScripts::IsDebugOn()) { + ShowRadarMarker(blipEntity->GetPosition(), color, ms_RadarTrace[blipId].m_Radius); + ms_RadarTrace[blipId].m_Radius = ms_RadarTrace[blipId].m_Radius - 0.1f; + if (ms_RadarTrace[blipId].m_Radius < 1.0f) + ms_RadarTrace[blipId].m_Radius = 5.0f; + } + } + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) { + TransformRealWorldPointToRadarSpace(in, blipEntity->GetPosition()); + float dist = LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_NONE) { + DrawRadarSprite(ms_RadarTrace[blipId].m_eRadarSprite, out.x, out.y, CalculateBlipAlpha(dist)); + } else { +#ifdef TRIANGULAR_BLIPS + const CVector &pos = FindPlayerCentreOfWorld_NoSniperShift(); + const CVector &blipPos = blipEntity->GetPosition(); + uint8 mode = BLIP_MODE_TRIANGULAR_UP; + if (blipPos.z - pos.z <= 2.0f) { + if (blipPos.z - pos.z < -4.0f) mode = BLIP_MODE_TRIANGULAR_DOWN; + else mode = BLIP_MODE_SQUARE; + } + ShowRadarTraceWithHeight(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255, mode); +#else + ShowRadarTrace(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255); +#endif + } + } + } + } + break; + case BLIP_COORD: + case BLIP_CONTACT_POINT: + if ((ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_BOMB || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_SAVE + || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_SPRAY || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_WEAPON) + && (ms_RadarTrace[blipId].m_eBlipType != BLIP_CONTACT_POINT || !CTheScripts::IsPlayerOnAMission())) { + + uint32 color = GetRadarTraceColour(ms_RadarTrace[blipId].m_nColor, ms_RadarTrace[blipId].m_bDim); + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + if (CTheScripts::IsDebugOn()) { + ShowRadarMarker(ms_RadarTrace[blipId].m_vecPos, color, ms_RadarTrace[blipId].m_Radius); + ms_RadarTrace[blipId].m_Radius = ms_RadarTrace[blipId].m_Radius - 0.1f; + if (ms_RadarTrace[blipId].m_Radius < 1.0f) + ms_RadarTrace[blipId].m_Radius = 5.0f; + } + } + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) { + TransformRealWorldPointToRadarSpace(in, ms_RadarTrace[blipId].m_vec2DPos); + float dist = LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_NONE) { + DrawRadarSprite(ms_RadarTrace[blipId].m_eRadarSprite, out.x, out.y, CalculateBlipAlpha(dist)); + } else { +#ifdef TRIANGULAR_BLIPS + const CVector &pos = FindPlayerCentreOfWorld_NoSniperShift(); + const CVector &blipPos = ms_RadarTrace[blipId].m_vecPos; + uint8 mode = BLIP_MODE_TRIANGULAR_UP; + if (blipPos.z - pos.z <= 2.0f) { + if (blipPos.z - pos.z < -4.0f) mode = BLIP_MODE_TRIANGULAR_DOWN; + else mode = BLIP_MODE_SQUARE; + } + ShowRadarTraceWithHeight(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255, mode); +#else + ShowRadarTrace(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255); +#endif + } + } + } + break; + default: + break; + } + } + for(int blipId = 0; blipId < NUMRADARBLIPS; blipId++) { + if (!ms_RadarTrace[blipId].m_bInUse) + continue; + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_CAR: + case BLIP_CHAR: + case BLIP_OBJECT: + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_BOMB && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SAVE + && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SPRAY && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_WEAPON) { + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_CAR: + blipEntity = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + break; + case BLIP_CHAR: + blipEntity = CPools::GetPedPool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + if (blipEntity != nil) { + if (((CPed*)blipEntity)->InVehicle()) + blipEntity = ((CPed*)blipEntity)->m_pMyVehicle; + } + break; + case BLIP_OBJECT: + blipEntity = CPools::GetObjectPool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + break; + default: + break; + } + + if (blipEntity) { + uint32 color = GetRadarTraceColour(ms_RadarTrace[blipId].m_nColor, ms_RadarTrace[blipId].m_bDim); + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + if (CTheScripts::IsDebugOn()) { + ShowRadarMarker(blipEntity->GetPosition(), color, ms_RadarTrace[blipId].m_Radius); + ms_RadarTrace[blipId].m_Radius = ms_RadarTrace[blipId].m_Radius - 0.1f; + if (ms_RadarTrace[blipId].m_Radius < 1.0f) + ms_RadarTrace[blipId].m_Radius = 5.0f; + } + } + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) { + TransformRealWorldPointToRadarSpace(in, blipEntity->GetPosition()); + float dist = LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_NONE) + DrawRadarSprite(ms_RadarTrace[blipId].m_eRadarSprite, out.x, out.y, CalculateBlipAlpha(dist)); + else +#ifdef TRIANGULAR_BLIPS + { + const CVector &pos = FindPlayerCentreOfWorld_NoSniperShift(); + const CVector &blipPos = blipEntity->GetPosition(); + uint8 mode = BLIP_MODE_TRIANGULAR_UP; + if (blipPos.z - pos.z <= 2.0f) { + if (blipPos.z - pos.z < -4.0f) mode = BLIP_MODE_TRIANGULAR_DOWN; + else mode = BLIP_MODE_SQUARE; + } + ShowRadarTraceWithHeight(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255, mode); + } +#else + ShowRadarTrace(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255); +#endif + } + } + } + break; + default: + break; + } + } + for (int blipId = 0; blipId < NUMRADARBLIPS; blipId++) { + if (!ms_RadarTrace[blipId].m_bInUse) + continue; + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_COORD: + case BLIP_CONTACT_POINT: + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_BOMB && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SAVE + && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SPRAY && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_WEAPON + && (ms_RadarTrace[blipId].m_eBlipType != BLIP_CONTACT_POINT || !CTheScripts::IsPlayerOnAMission())) { + + uint32 color = GetRadarTraceColour(ms_RadarTrace[blipId].m_nColor, ms_RadarTrace[blipId].m_bDim); + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + if (CTheScripts::IsDebugOn()) { + ShowRadarMarker(ms_RadarTrace[blipId].m_vecPos, color, ms_RadarTrace[blipId].m_Radius); + ms_RadarTrace[blipId].m_Radius = ms_RadarTrace[blipId].m_Radius - 0.1f; + if (ms_RadarTrace[blipId].m_Radius < 1.0f) + ms_RadarTrace[blipId].m_Radius = 5.0f; + } + } + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) { + TransformRealWorldPointToRadarSpace(in, ms_RadarTrace[blipId].m_vec2DPos); + float dist = LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_NONE) + DrawRadarSprite(ms_RadarTrace[blipId].m_eRadarSprite, out.x, out.y, CalculateBlipAlpha(dist)); + else +#ifdef TRIANGULAR_BLIPS + { + const CVector &pos = FindPlayerCentreOfWorld_NoSniperShift(); + const CVector &blipPos = ms_RadarTrace[blipId].m_vecPos; + uint8 mode = BLIP_MODE_TRIANGULAR_UP; + if (blipPos.z - pos.z <= 2.0f) { + if (blipPos.z - pos.z < -4.0f) mode = BLIP_MODE_TRIANGULAR_DOWN; + else mode = BLIP_MODE_SQUARE; + } + ShowRadarTraceWithHeight(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255, mode); + } +#else + ShowRadarTrace(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255); +#endif + } + } + break; + default: + break; + } + } +#ifdef MENU_MAP + if (CMenuManager::bMenuMapActive) { + CVector2D in, out; + TransformRealWorldPointToRadarSpace(in, FindPlayerCentreOfWorld_NoSniperShift()); + LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + DrawYouAreHereSprite(out.x, out.y); + } +#endif + } +} + +void CRadar::DrawMap() +{ + if (!TheCamera.m_WideScreenOn && CHud::m_Wants_To_Draw_Hud) { +#if 1 // from VC + CalculateCachedSinCos(); +#endif + 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(); + } +} + +void CRadar::DrawRadarMap() +{ + // Game calculates an unused CRect here + + DrawRadarMask(); + + // top left ist (0, 0) + int x = Floor((vec2DRadarOrigin.x - RADAR_MIN_X) / RADAR_TILE_SIZE); + int y = Ceil((RADAR_NUM_TILES - 1) - (vec2DRadarOrigin.y - RADAR_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); +} + +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*)nil); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); +#if !defined(GTA_PS2_STUFF) && defined(RWLIBS) + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_ALWAYS); +#else + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); +#endif + + 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); + } +#if !defined(GTA_PS2_STUFF) && defined(RWLIBS) + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER); +#endif +} + +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); +} + +void CRadar::DrawRadarSprite(uint16 sprite, float x, float y, uint8 alpha) +{ +#ifdef MENU_MAP + if(sprite == RADAR_SPRITE_WAYPOINT) alpha = 255; +#endif + 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)); +} + +void CRadar::DrawRotatingRadarSprite(CSprite2d* sprite, float x, float y, float angle, int32 alpha) +{ + CVector curPosn[4]; + const float sizeX = SCREEN_SCALE_X(8.0f); + const float correctedAngle = angle - PI / 4.f; + const float sizeY = SCREEN_SCALE_Y(8.0f); + + for (uint32 i = 0; i < 4; i++) { + const float cornerAngle = i * HALFPI + correctedAngle; + curPosn[i].x = x + (0.0f * Cos(cornerAngle) + 1.0f * Sin(cornerAngle)) * sizeX; + curPosn[i].y = y - (0.0f * Sin(cornerAngle) - 1.0f * Cos(cornerAngle)) * sizeY; + } + + sprite->Draw(curPosn[3].x, curPosn[3].y, curPosn[2].x, curPosn[2].y, curPosn[0].x, curPosn[0].y, curPosn[1].x, curPosn[1].y, CRGBA(255, 255, 255, alpha)); +} + +int32 CRadar::GetActualBlipArrayIndex(int32 i) +{ + if (i == -1) + return -1; + else if ((i & 0xFFFF0000) >> 16 != ms_RadarTrace[(uint16)i].m_BlipIndex) + return -1; + else + return (uint16)i; +} + +int32 CRadar::GetNewUniqueBlipIndex(int32 i) +{ + if (ms_RadarTrace[i].m_BlipIndex >= UINT16_MAX - 1) + ms_RadarTrace[i].m_BlipIndex = 1; + else + ms_RadarTrace[i].m_BlipIndex++; + return i | (ms_RadarTrace[i].m_BlipIndex << 16); +} + +uint32 CRadar::GetRadarTraceColour(uint32 color, bool bright) +{ + uint32 c; + switch (color) { + case RADAR_TRACE_RED: + if (bright) + c = 0x712B49FF; + else + c = 0x7F0000FF; + break; + case RADAR_TRACE_GREEN: + if (bright) + c = 0x5FA06AFF; + else + c = 0x007F00FF; + break; + case RADAR_TRACE_LIGHT_BLUE: + if (bright) + c = 0x80A7F3FF; + else + c = 0x00007FFF; + break; + case RADAR_TRACE_GRAY: + if (bright) + c = 0xE1E1E1FF; + else + c = 0x7F7F7FFF; + break; + case RADAR_TRACE_YELLOW: + if (bright) + c = 0xFFFF00FF; + else + c = 0x7F7F00FF; + break; + case RADAR_TRACE_MAGENTA: + if (bright) + c = 0xFF00FFFF; + else + c = 0x7F007FFF; + break; + case RADAR_TRACE_CYAN: + if (bright) + c = 0x00FFFFFF; + else + c = 0x007F7FFF; + break; + default: + c = color; + break; + }; + return c; +} + +const char* gRadarTexNames[] = { + "radar00", "radar01", "radar02", "radar03", "radar04", "radar05", "radar06", "radar07", + "radar08", "radar09", "radar10", "radar11", "radar12", "radar13", "radar14", "radar15", + "radar16", "radar17", "radar18", "radar19", "radar20", "radar21", "radar22", "radar23", + "radar24", "radar25", "radar26", "radar27", "radar28", "radar29", "radar30", "radar31", + "radar32", "radar33", "radar34", "radar35", "radar36", "radar37", "radar38", "radar39", + "radar40", "radar41", "radar42", "radar43", "radar44", "radar45", "radar46", "radar47", + "radar48", "radar49", "radar50", "radar51", "radar52", "radar53", "radar54", "radar55", + "radar56", "radar57", "radar58", "radar59", "radar60", "radar61", "radar62", "radar63", +}; + +void +CRadar::Initialise() +{ +#ifdef MENU_MAP + TargetMarkerId = -1; +#endif + + for (int i = 0; i < NUMRADARBLIPS; i++) { + ms_RadarTrace[i].m_BlipIndex = 1; + SetRadarMarkerState(i, false); + ms_RadarTrace[i].m_bInUse = false; + ms_RadarTrace[i].m_eBlipType = BLIP_NONE; + ms_RadarTrace[i].m_eBlipDisplay = BLIP_DISPLAY_NEITHER; + ms_RadarTrace[i].m_eRadarSprite = RADAR_SPRITE_NONE; + } + + m_radarRange = 350.0f; + for (int i = 0; i < 64; i++) + gRadarTxdIds[i] = CTxdStore::FindTxdSlot(gRadarTexNames[i]); +} + +float CRadar::LimitRadarPoint(CVector2D &point) +{ + float dist, invdist; + + dist = point.Magnitude(); +#ifdef MENU_MAP + if (CMenuManager::bMenuMapActive) + return dist; +#endif + if (dist > 1.0f) { + invdist = 1.0f / dist; + point.x *= invdist; + point.y *= invdist; + } + return dist; +} + +void CRadar::LoadAllRadarBlips(uint8 *buf, uint32 size) +{ + Initialise(); +INITSAVEBUF + CheckSaveHeader(buf, 'R', 'D', 'R', '\0', size - SAVE_HEADER_SIZE); + + for (int i = 0; i < NUMRADARBLIPS; i++) + ReadSaveBuf(&ms_RadarTrace[i], buf); + +VALIDATESAVEBUF(size); +} + +void +CRadar::LoadTextures() +{ + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("hud")); + AsukaSprite.SetTexture("radar_asuka"); + BombSprite.SetTexture("radar_bomb"); + CatSprite.SetTexture("radar_cat"); + CentreSprite.SetTexture("radar_centre"); + CopcarSprite.SetTexture("radar_copcar"); + DonSprite.SetTexture("radar_don"); + EightSprite.SetTexture("radar_eight"); + ElSprite.SetTexture("radar_el"); + IceSprite.SetTexture("radar_ice"); + JoeySprite.SetTexture("radar_joey"); + KenjiSprite.SetTexture("radar_kenji"); + LizSprite.SetTexture("radar_liz"); + LuigiSprite.SetTexture("radar_luigi"); + NorthSprite.SetTexture("radar_north"); + RaySprite.SetTexture("radar_ray"); + SalSprite.SetTexture("radar_sal"); + SaveSprite.SetTexture("radar_save"); + SpraySprite.SetTexture("radar_spray"); + TonySprite.SetTexture("radar_tony"); + WeaponSprite.SetTexture("radar_weapon"); +#ifdef MENU_MAP + WaypointSprite.SetTexture("radar_waypoint"); + if(!WaypointSprite.m_pTexture) { + // create the texture if it's missing in TXD +#define WAYPOINT_R (255) +#define WAYPOINT_G (72) +#define WAYPOINT_B (77) + + RwRaster *raster = RwRasterCreate(16, 16, 0, rwRASTERTYPETEXTURE | rwRASTERFORMAT8888); + + RwUInt32 *pixels = (RwUInt32 *)RwRasterLock(raster, 0, rwRASTERLOCKWRITE); + for(int x = 0; x < 16; x++) + for(int y = 0; y < 16; y++) + { + int x2 = x < 8 ? x : 7 - (x & 7); + int y2 = y < 8 ? y : 7 - (y & 7); + if ((y2 >= 4 && x2 >= 4) // square in the center is transparent + || (x2 < 2 && y2 == 0) // two pixels on each side of first/last line are transparent + || (x2 < 1 && y2 == 1)) // one pixel on each side of second to first/last line is transparent + pixels[x + y * 16] = 0; + else if((x2 == 2 && y2 >= 2)|| (y2 == 2 && x2 >= 2) )// colored square inside +#ifdef RW_GL3 + pixels[x + y * 16] = WAYPOINT_R | (WAYPOINT_G << 8) | (WAYPOINT_B << 16) | (255 << 24); +#else + pixels[x + y * 16] = WAYPOINT_B | (WAYPOINT_G << 8) | (WAYPOINT_R << 16) | (255 << 24); +#endif + else + pixels[x + y * 16] = 0xFF000000; // black + } + RwRasterUnlock(raster); + WaypointSprite.m_pTexture = RwTextureCreate(raster); + RwTextureSetFilterMode(WaypointSprite.m_pTexture, rwFILTERLINEAR); +#undef WAYPOINT_R +#undef WAYPOINT_G +#undef WAYPOINT_B + } +#endif + CTxdStore::PopCurrentTxd(); +} + +void CRadar::RemoveRadarSections() +{ + for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) + RemoveMapSection(i, j); +} + +void CRadar::SaveAllRadarBlips(uint8 *buf, uint32 *size) +{ + *size = SAVE_HEADER_SIZE + sizeof(ms_RadarTrace); +INITSAVEBUF + WriteSaveHeader(buf, 'R', 'D', 'R', '\0', *size - SAVE_HEADER_SIZE); + +#ifdef MENU_MAP + bool bWaypointDeleted = false; + if (TargetMarkerId != -1) { + ClearBlip(TargetMarkerId); + TargetMarkerId = -1; + bWaypointDeleted = true; + } +#endif + + for (int i = 0; i < NUMRADARBLIPS; i++) + WriteSaveBuf(buf, ms_RadarTrace[i]); + + +#ifdef MENU_MAP + if(bWaypointDeleted) + ToggleTargetMarker(TargetMarkerPos.x, TargetMarkerPos.y); +#endif + +VALIDATESAVEBUF(*size); +} + +void CRadar::SetBlipSprite(int32 i, int32 icon) +{ + int index = CRadar::GetActualBlipArrayIndex(i); + if (index != -1) { + ms_RadarTrace[index].m_eRadarSprite = icon; + } +} + +int CRadar::SetCoordBlip(eBlipType type, CVector pos, int32 color, eBlipDisplay display) +{ + int nextBlip; + for (nextBlip = 0; nextBlip < NUMRADARBLIPS; nextBlip++) { + if (!ms_RadarTrace[nextBlip].m_bInUse) + break; + } +#ifdef FIX_BUGS + if (nextBlip == NUMRADARBLIPS) + return -1; +#endif + ms_RadarTrace[nextBlip].m_eBlipType = type; + ms_RadarTrace[nextBlip].m_nColor = color; + ms_RadarTrace[nextBlip].m_bDim = 1; + ms_RadarTrace[nextBlip].m_bInUse = 1; + ms_RadarTrace[nextBlip].m_Radius = 1.0f; + ms_RadarTrace[nextBlip].m_vec2DPos = pos; + ms_RadarTrace[nextBlip].m_vecPos = pos; + ms_RadarTrace[nextBlip].m_nEntityHandle = 0; + ms_RadarTrace[nextBlip].m_wScale = 1; + ms_RadarTrace[nextBlip].m_eBlipDisplay = display; + ms_RadarTrace[nextBlip].m_eRadarSprite = RADAR_SPRITE_NONE; + return CRadar::GetNewUniqueBlipIndex(nextBlip); +} + +int CRadar::SetEntityBlip(eBlipType type, int32 handle, int32 color, eBlipDisplay display) +{ + int nextBlip; + for (nextBlip = 0; nextBlip < NUMRADARBLIPS; nextBlip++) { + if (!ms_RadarTrace[nextBlip].m_bInUse) + break; + } +#ifdef FIX_BUGS + if (nextBlip == NUMRADARBLIPS) + return -1; +#endif + ms_RadarTrace[nextBlip].m_eBlipType = type; + ms_RadarTrace[nextBlip].m_nColor = color; + ms_RadarTrace[nextBlip].m_bDim = 1; + ms_RadarTrace[nextBlip].m_bInUse = 1; + ms_RadarTrace[nextBlip].m_Radius = 1.0f; + ms_RadarTrace[nextBlip].m_nEntityHandle = handle; + ms_RadarTrace[nextBlip].m_wScale = 1; + ms_RadarTrace[nextBlip].m_eBlipDisplay = display; + ms_RadarTrace[nextBlip].m_eRadarSprite = RADAR_SPRITE_NONE; + return GetNewUniqueBlipIndex(nextBlip); +} + +void CRadar::SetRadarMarkerState(int32 counter, bool 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; +} + +void CRadar::ShowRadarMarker(CVector pos, uint32 color, float radius) { + float f1 = radius * 1.4f; + float f2 = radius * 0.5f; + 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); +} + +void CRadar::ShowRadarTrace(float x, float y, uint32 size, uint8 red, uint8 green, uint8 blue, uint8 alpha) +{ + if ((TheCamera.m_WideScreenOn || !CHud::m_Wants_To_Draw_Hud) +#ifdef MENU_MAP + && !CMenuManager::bMenuMapActive +#endif + ) + return; + + 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)); +} + +void CRadar::ShowRadarTraceWithHeight(float x, float y, uint32 size, uint8 red, uint8 green, uint8 blue, uint8 alpha, uint8 mode) +{ + if ((TheCamera.m_WideScreenOn || !CHud::m_Wants_To_Draw_Hud) +#ifdef MENU_MAP + && !CMenuManager::bMenuMapActive +#endif + ) + return; + + switch (mode) + { + case BLIP_MODE_TRIANGULAR_UP: + // size++; // VC does size + 1 for triangles + CSprite2d::Draw2DPolygon(x + SCREEN_SCALE_X(size + 3.0f), y + SCREEN_SCALE_Y(size + 2.0f), x - (SCREEN_SCALE_X(size + 3.0f)), y + SCREEN_SCALE_Y(size + 2.0f), x, y - (SCREEN_SCALE_Y(size + 3.0f)), x, y - (SCREEN_SCALE_Y(size + 3.0f)), CRGBA(0, 0, 0, alpha)); + CSprite2d::Draw2DPolygon(x + SCREEN_SCALE_X(size + 1.0f), y + SCREEN_SCALE_Y(size + 1.0f), x - (SCREEN_SCALE_X(size + 1.0f)), y + SCREEN_SCALE_Y(size + 1.0f), x, y - (SCREEN_SCALE_Y(size + 1.0f)), x, y - (SCREEN_SCALE_Y(size + 1.0f)), CRGBA(red, green, blue, alpha)); + break; + case BLIP_MODE_TRIANGULAR_DOWN: + // size++; // VC does size + 1 for triangles + CSprite2d::Draw2DPolygon(x, y + SCREEN_SCALE_Y(size + 2.0f), x, y + SCREEN_SCALE_Y(size + 3.0f), x + SCREEN_SCALE_X(size + 3.0f), y - (SCREEN_SCALE_Y(size + 2.0f)), x - (SCREEN_SCALE_X(size + 3.0f)), y - (SCREEN_SCALE_Y(size + 2.0f)), CRGBA(0, 0, 0, alpha)); + CSprite2d::Draw2DPolygon(x, y + SCREEN_SCALE_Y(size + 1.0f), x, y + SCREEN_SCALE_Y(size + 1.0f), x + SCREEN_SCALE_X(size + 1.0f), y - (SCREEN_SCALE_Y(size + 1.0f)), x - (SCREEN_SCALE_X(size + 1.0f)), y - (SCREEN_SCALE_Y(size + 1.0f)), CRGBA(red, green, blue, alpha)); + break; + case BLIP_MODE_SQUARE: + 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)); + break; + } +} + +void CRadar::Shutdown() +{ + AsukaSprite.Delete(); + BombSprite.Delete(); + CatSprite.Delete(); + CentreSprite.Delete(); + CopcarSprite.Delete(); + DonSprite.Delete(); + EightSprite.Delete(); + ElSprite.Delete(); + IceSprite.Delete(); + JoeySprite.Delete(); + KenjiSprite.Delete(); + LizSprite.Delete(); + LuigiSprite.Delete(); + NorthSprite.Delete(); + RaySprite.Delete(); + SalSprite.Delete(); + SaveSprite.Delete(); + SpraySprite.Delete(); + TonySprite.Delete(); + WeaponSprite.Delete(); +#ifdef MENU_MAP + WaypointSprite.Delete(); +#endif + RemoveRadarSections(); +} + +void CRadar::StreamRadarSections(const CVector &posn) +{ + StreamRadarSections(Floor((2000.0f + posn.x) / 500.0f), Ceil(7.0f - (2000.0f + posn.y) / 500.0f)); +} + +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); + }; + }; +} + +void CRadar::TransformRealWorldToTexCoordSpace(CVector2D &out, const CVector2D &in, int32 x, int32 y) +{ + out.x = in.x - (x * RADAR_TILE_SIZE + RADAR_MIN_X); + out.y = -(in.y - ((RADAR_NUM_TILES - y) * RADAR_TILE_SIZE + RADAR_MIN_Y)); + out.x /= RADAR_TILE_SIZE; + out.y /= RADAR_TILE_SIZE; +} + +void CRadar::TransformRadarPointToRealWorldSpace(CVector2D &out, const CVector2D &in) +{ + float s, c; +#if 1 + s = -cachedSin; + c = cachedCos; +#else + // Original code + + s = -Sin(TheCamera.GetForward().Heading()); + c = Cos(TheCamera.GetForward().Heading()); + + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED) { + s = 0.0f; + c = 1.0f; + } + else if (TheCamera.GetLookDirection() != LOOKING_FORWARD) { + CVector forward; + + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON) { + 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()); + } +#endif + + out.x = s * in.y + c * in.x; + out.y = c * in.y - s * in.x; + + out = out * m_radarRange + vec2DRadarOrigin; +} + +// 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) +{ +#ifdef MENU_MAP + if (CMenuManager::bMenuMapActive) { + // fMapSize is actually half map size. Radar range is 1000, so if x is -2000, in.x + 2.0f is 0. + out.x = (CMenuManager::fMapCenterX - CMenuManager::fMapSize) + (in.x + 2.0f) * CMenuManager::fMapSize * 2.0f / 4.0f; + out.y = (CMenuManager::fMapCenterY - CMenuManager::fMapSize) + (2.0f - in.y) * CMenuManager::fMapSize * 2.0f / 4.0f; + } else +#endif + { +#ifdef FIX_BUGS + out.x = (in.x + 1.0f) * 0.5f * SCREEN_SCALE_X(RADAR_WIDTH) + SCREEN_SCALE_X(RADAR_LEFT); +#else + out.x = (in.x + 1.0f) * 0.5f * SCREEN_SCALE_X(RADAR_WIDTH) + RADAR_LEFT; +#endif + out.y = (1.0f - in.y) * 0.5f * SCREEN_SCALE_Y(RADAR_HEIGHT) + SCREEN_SCALE_FROM_BOTTOM(RADAR_BOTTOM + RADAR_HEIGHT); + } +} + +void CRadar::TransformRealWorldPointToRadarSpace(CVector2D &out, const CVector2D &in) +{ + float s, c; +#if 1 + s = cachedSin; + c = cachedCos; +#else + // Original code + + float s, c; + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED) { + 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_1STPERSON) { + 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()); + } +#endif + + 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; +} + +void +CRadar::CalculateCachedSinCos() +{ + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED +#ifdef MENU_MAP + || CMenuManager::bMenuMapActive +#endif + ) { + cachedSin = 0.0f; + cachedCos = 1.0f; + } else if (TheCamera.GetLookDirection() == LOOKING_FORWARD) { + cachedSin = Sin(TheCamera.GetForward().Heading()); + cachedCos = Cos(TheCamera.GetForward().Heading()); + } else { + CVector forward; + + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON) { + 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; + + cachedSin = Sin(forward.Heading()); + cachedCos = Cos(forward.Heading()); + } +} + +#ifdef MENU_MAP +void +CRadar::InitFrontEndMap() +{ + CalculateCachedSinCos(); + vec2DRadarOrigin.x = 0.0f; + vec2DRadarOrigin.y = 0.0f; + m_radarRange = 1000.0f; // doesn't mean anything, just affects the calculation in TransformRadarPointToScreenSpace +} + +void +CRadar::DrawYouAreHereSprite(float x, float y) +{ + static uint32 lastChange = 0; + static bool show = true; + + if (show) { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastChange > 500) { + lastChange = CTimer::GetTimeInMillisecondsPauseMode(); + show = !show; + } + } else { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastChange > 200) { + lastChange = CTimer::GetTimeInMillisecondsPauseMode(); + show = !show; + } + } + + if (show) { + float left = x - SCREEN_SCALE_X(12.0f); + float top = y; + float right = SCREEN_SCALE_X(12.0) + x; + float bottom = y - SCREEN_SCALE_Y(24.0f); + CentreSprite.Draw(CRect(left, top, right, bottom), CRGBA(255, 255, 255, 255)); + } +} + +void +CRadar::ToggleTargetMarker(float x, float y) +{ + if (TargetMarkerId == -1) { + int nextBlip; + for (nextBlip = NUMRADARBLIPS-1; nextBlip >= 0; nextBlip--) { + if (!ms_RadarTrace[nextBlip].m_bInUse) + break; + } +#ifdef FIX_BUGS + if (nextBlip == 0) + return; +#endif + ms_RadarTrace[nextBlip].m_eBlipType = BLIP_COORD; + ms_RadarTrace[nextBlip].m_nColor = RADAR_TRACE_GRAY; + ms_RadarTrace[nextBlip].m_bDim = 0; + ms_RadarTrace[nextBlip].m_bInUse = 1; + ms_RadarTrace[nextBlip].m_Radius = 1.0f; + CVector pos(x, y, CWorld::FindGroundZForCoord(x,y)); + TargetMarkerPos = pos; + ms_RadarTrace[nextBlip].m_vec2DPos = pos; + ms_RadarTrace[nextBlip].m_vecPos = pos; + ms_RadarTrace[nextBlip].m_nEntityHandle = 0; + ms_RadarTrace[nextBlip].m_wScale = 5; + ms_RadarTrace[nextBlip].m_eBlipDisplay = BLIP_DISPLAY_BLIP_ONLY; + ms_RadarTrace[nextBlip].m_eRadarSprite = RADAR_SPRITE_WAYPOINT; + TargetMarkerId = CRadar::GetNewUniqueBlipIndex(nextBlip); + } else { + ClearBlip(TargetMarkerId); + TargetMarkerId = -1; + } +} +#endif + diff --git a/src/core/Radar.h b/src/core/Radar.h new file mode 100644 index 0000000..ae87d0f --- /dev/null +++ b/src/core/Radar.h @@ -0,0 +1,201 @@ +#pragma once +#include "Sprite2d.h" +#include "Draw.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 +{ +#ifdef MENU_MAP + RADAR_SPRITE_ENTITY_BLIP = -2, + RADAR_SPRITE_COORD_BLIP = -1, +#endif + RADAR_SPRITE_NONE = 0, + RADAR_SPRITE_ASUKA, + RADAR_SPRITE_BOMB, + RADAR_SPRITE_CAT, + RADAR_SPRITE_CENTRE, + RADAR_SPRITE_COPCAR, + RADAR_SPRITE_DON, + RADAR_SPRITE_EIGHT, + RADAR_SPRITE_EL, + RADAR_SPRITE_ICE, + RADAR_SPRITE_JOEY, + RADAR_SPRITE_KENJI, + RADAR_SPRITE_LIZ, + RADAR_SPRITE_LUIGI, + RADAR_SPRITE_NORTH, + RADAR_SPRITE_RAY, + RADAR_SPRITE_SAL, + RADAR_SPRITE_SAVE, + RADAR_SPRITE_SPRAY, + RADAR_SPRITE_TONY, + RADAR_SPRITE_WEAPON, +#ifdef MENU_MAP + RADAR_SPRITE_WAYPOINT, +#endif + RADAR_SPRITE_COUNT +}; + +enum +{ + RADAR_TRACE_RED, + RADAR_TRACE_GREEN, + RADAR_TRACE_LIGHT_BLUE, + RADAR_TRACE_GRAY, + RADAR_TRACE_YELLOW, + RADAR_TRACE_MAGENTA, + RADAR_TRACE_CYAN +}; + +enum +{ + BLIP_MODE_TRIANGULAR_UP = 0, + BLIP_MODE_TRIANGULAR_DOWN, + BLIP_MODE_SQUARE, +}; + +struct sRadarTrace +{ + uint32 m_nColor; + uint32 m_eBlipType; // eBlipType + int32 m_nEntityHandle; + CVector2D m_vec2DPos; + CVector m_vecPos; + uint16 m_BlipIndex; + bool m_bDim; + bool m_bInUse; + float m_Radius; + int16 m_wScale; + uint16 m_eBlipDisplay; // eBlipDisplay + uint16 m_eRadarSprite; // eRadarSprite +}; +VALIDATE_SIZE(sRadarTrace, 0x30); + +// Values for screen space +#define RADAR_LEFT (40.0f) +#ifdef PS2_HUD +#define RADAR_BOTTOM (44.0f) +#else +#define RADAR_BOTTOM (47.0f) +#endif + +#ifdef FIX_RADAR +/* + The values are from an early screenshot taken before R* broke radar + #define RADAR_WIDTH (82.0f) + #define RADAR_HEIGHT (82.0f) +*/ +#define RADAR_WIDTH ((CDraw::ms_bFixRadar) ? (82.0f) : (94.0f)) +#define RADAR_HEIGHT ((CDraw::ms_bFixRadar) ? (82.0f) : (76.0f)) +#else +/* + broken since forever, someone tried to fix size for 640x512(PAL) + http://aap.rockstarvision.com/pics/gta3/ps2screens/gta3_interface.jpg + but failed: + http://aap.rockstarvision.com/pics/gta3/artwork/gta3_artwork_16.jpg + most likely the guy used something like this: + int y = 82 * (640.0/512.0)/(640.0/480.0); + int x = y * (640.0/512.0); +*/ +#define RADAR_WIDTH (94.0f) +#define RADAR_HEIGHT (76.0f) +#endif + +class CRadar +{ +public: + static float m_radarRange; + static sRadarTrace 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[RADAR_SPRITE_COUNT]; + static float cachedCos; + static float cachedSin; +#ifdef MENU_MAP + static CSprite2d WaypointSprite; + static int TargetMarkerId; + static CVector TargetMarkerPos; + + static void InitFrontEndMap(); + static void DrawYouAreHereSprite(float, float); + static void ToggleTargetMarker(float, float); +#endif + static uint8 CalculateBlipAlpha(float dist); + static void ChangeBlipBrightness(int32 i, int32 bright); + static void ChangeBlipColour(int32 i, int32); + static void ChangeBlipDisplay(int32 i, eBlipDisplay display); + static void ChangeBlipScale(int32 i, int32 scale); + static void ClearBlip(int32 i); + static void ClearBlipForEntity(eBlipType 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(uint16 sprite, float x, float y, uint8 alpha); + static void DrawRotatingRadarSprite(CSprite2d* sprite, float x, float y, float angle, int32 alpha); + static int32 GetActualBlipArrayIndex(int32 i); + static int32 GetNewUniqueBlipIndex(int32 i); + static uint32 GetRadarTraceColour(uint32 color, bool bright); + static void Initialise(); + static float LimitRadarPoint(CVector2D &point); + static void LoadAllRadarBlips(uint8 *buf, uint32 size); + static void LoadTextures(); + static void RemoveRadarSections(); + static void SaveAllRadarBlips(uint8*, uint32*); + static void SetBlipSprite(int32 i, int32 icon); + static int32 SetCoordBlip(eBlipType type, CVector pos, int32, eBlipDisplay); + static int32 SetEntityBlip(eBlipType type, int32, int32, eBlipDisplay); + static void SetRadarMarkerState(int32 i, bool flag); + static void ShowRadarMarker(CVector pos, uint32 color, float radius); + static void ShowRadarTrace(float x, float y, uint32 size, uint8 red, uint8 green, uint8 blue, uint8 alpha); + static void ShowRadarTraceWithHeight(float x, float y, uint32 size, uint8 red, uint8 green, uint8 blue, uint8 alpha, uint8 mode); + 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 CalculateCachedSinCos(); +}; diff --git a/src/core/Range2D.cpp b/src/core/Range2D.cpp new file mode 100644 index 0000000..33eafd0 --- /dev/null +++ b/src/core/Range2D.cpp @@ -0,0 +1,28 @@ +#include "common.h" +#include "Range2D.h" +#include "General.h" + +CRange2D::CRange2D(CVector2D _min, CVector2D _max) : min(_min), max(_max) {} + +bool +CRange2D::IsInRange(CVector2D vec) +{ + return min.x < vec.x && max.x > vec.x && min.y < vec.y && max.y > vec.y; +} + +void +CRange2D::DebugShowRange(float, int) +{ +} + +CVector2D +CRange2D::GetRandomPointInRange() +{ + int distX = Abs(max.x - min.x); + int distY = Abs(max.y - min.y); + + float outX = CGeneral::GetRandomNumber() % distX + min.x; + float outY = CGeneral::GetRandomNumber() % distY + min.y; + + return CVector2D(outX, outY); +} diff --git a/src/core/Range2D.h b/src/core/Range2D.h new file mode 100644 index 0000000..f239e7d --- /dev/null +++ b/src/core/Range2D.h @@ -0,0 +1,11 @@ +#pragma once + +class CRange2D +{ + CVector2D min, max; +public: + CRange2D(CVector2D _min, CVector2D _max); + bool IsInRange(CVector2D vec); + void DebugShowRange(float, int); + CVector2D GetRandomPointInRange(); +}; \ No newline at end of file diff --git a/src/core/Range3D.cpp b/src/core/Range3D.cpp new file mode 100644 index 0000000..7fa28d6 --- /dev/null +++ b/src/core/Range3D.cpp @@ -0,0 +1,30 @@ +#include "common.h" +#include "Range3D.h" +#include "General.h" + +CRange3D::CRange3D(CVector _min, CVector _max) : min(_min), max(_max) {} + +bool +CRange3D::IsInRange(CVector vec) +{ + return min.x < vec.x && max.x > vec.x && min.y < vec.y && max.y > vec.y && min.z < vec.z && max.z > vec.z; +} + +void +CRange3D::DebugShowRange(float, int) +{ +} + +CVector +CRange3D::GetRandomPointInRange() +{ + int distX = Abs(max.x - min.x); + int distY = Abs(max.y - min.y); + int distZ = Abs(max.z - min.z); + + float outX = CGeneral::GetRandomNumber() % distX + min.x; + float outY = CGeneral::GetRandomNumber() % distY + min.y; + float outZ = CGeneral::GetRandomNumber() % distZ + min.z; + + return CVector(outX, outY, outZ); +} diff --git a/src/core/Range3D.h b/src/core/Range3D.h new file mode 100644 index 0000000..f42b523 --- /dev/null +++ b/src/core/Range3D.h @@ -0,0 +1,11 @@ +#pragma once + +class CRange3D +{ + CVector min, max; +public: + CRange3D(CVector _min, CVector _max); + bool IsInRange(CVector vec); + void DebugShowRange(float, int); + CVector GetRandomPointInRange(); +}; \ No newline at end of file diff --git a/src/core/References.cpp b/src/core/References.cpp new file mode 100644 index 0000000..b778209 --- /dev/null +++ b/src/core/References.cpp @@ -0,0 +1,126 @@ +#include "common.h" + +#include "World.h" +#include "Vehicle.h" +#include "PlayerPed.h" +#include "Pools.h" +#include "References.h" + +CReference CReferences::aRefs[NUMREFERENCES]; +CReference *CReferences::pEmptyList; + +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 +CEntity::RegisterReference(CEntity **pent) +{ + if(IsBuilding()) + return; + CReference *ref; + // check if already registered + for(ref = m_pFirstReference; ref; ref = ref->next) + if(ref->pentity == pent) + return; + // have to allocate new reference + ref = CReferences::pEmptyList; + if(ref){ + CReferences::pEmptyList = ref->next; + + ref->pentity = pent; + ref->next = m_pFirstReference; + m_pFirstReference = ref; + } +} + +// Clear all references to this entity +void +CEntity::ResolveReferences(void) +{ + CReference *ref; + // clear pointers to this entity + for(ref = m_pFirstReference; ref; ref = ref->next) + if(*ref->pentity == this) + *ref->pentity = nil; + // free list + if(m_pFirstReference){ + for(ref = m_pFirstReference; ref->next; ref = ref->next) + ; + ref->next = CReferences::pEmptyList; + CReferences::pEmptyList = m_pFirstReference; + m_pFirstReference = nil; + } +} + +// Free all references that no longer point to this entity +void +CEntity::PruneReferences(void) +{ + CReference *ref, *next, **lastnextp; + lastnextp = &m_pFirstReference; + for(ref = m_pFirstReference; ref; ref = next){ + next = ref->next; + if(*ref->pentity == this) + lastnextp = &ref->next; + else{ + *lastnextp = ref->next; + ref->next = CReferences::pEmptyList; + CReferences::pEmptyList = ref; + } + } +} + +void +CReferences::RemoveReferencesToPlayer(void) +{ + if(FindPlayerVehicle()) + FindPlayerVehicle()->ResolveReferences(); +#ifdef FIX_BUGS + if (FindPlayerPed()) { + CPlayerPed* pPlayerPed = FindPlayerPed(); + FindPlayerPed()->ResolveReferences(); + CWorld::Players[CWorld::PlayerInFocus].m_pPed = pPlayerPed; + pPlayerPed->RegisterReference((CEntity**)&CWorld::Players[CWorld::PlayerInFocus].m_pPed); + } +#else + if(FindPlayerPed()) + FindPlayerPed()->ResolveReferences(); +#endif +} + +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(); + } +} diff --git a/src/core/References.h b/src/core/References.h new file mode 100644 index 0000000..e7a64de --- /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/Stats.cpp b/src/core/Stats.cpp new file mode 100644 index 0000000..9afd8ac --- /dev/null +++ b/src/core/Stats.cpp @@ -0,0 +1,420 @@ +#include "common.h" + +#include "Stats.h" +#include "Text.h" +#include "World.h" + +int32 CStats::DaysPassed; +int32 CStats::HeadsPopped; +int32 CStats::CommercialPassed; +int32 CStats::IndustrialPassed; +int32 CStats::SuburbanPassed; +int32 CStats::NumberKillFrenziesPassed; +int32 CStats::PeopleKilledByOthers; +int32 CStats::HelisDestroyed; +int32 CStats::PedsKilledOfThisType[NUM_PEDTYPES]; +int32 CStats::TimesDied; +int32 CStats::TimesArrested; +int32 CStats::KillsSinceLastCheckpoint; +float CStats::DistanceTravelledInVehicle; +float CStats::DistanceTravelledOnFoot; +int32 CStats::ProgressMade; +int32 CStats::TotalProgressInGame; +int32 CStats::CarsExploded; +int32 CStats::PeopleKilledByPlayer; +float CStats::MaximumJumpDistance; +float CStats::MaximumJumpHeight; +int32 CStats::MaximumJumpFlips; +int32 CStats::MaximumJumpSpins; +int32 CStats::BestStuntJump; +int32 CStats::NumberOfUniqueJumpsFound; +int32 CStats::TotalNumberOfUniqueJumps; +int32 CStats::PassengersDroppedOffWithTaxi; +int32 CStats::MoneyMadeWithTaxi; +int32 CStats::MissionsGiven; +int32 CStats::MissionsPassed; +char CStats::LastMissionPassedName[8]; +int32 CStats::TotalLegitimateKills; +int32 CStats::ElBurroTime; +int32 CStats::Record4x4One; +int32 CStats::Record4x4Two; +int32 CStats::Record4x4Three; +int32 CStats::Record4x4Mayhem; +int32 CStats::LivesSavedWithAmbulance; +int32 CStats::CriminalsCaught; +int32 CStats::HighestLevelAmbulanceMission; +int32 CStats::FiresExtinguished; +int32 CStats::LongestFlightInDodo; +int32 CStats::TimeTakenDefuseMission; +int32 CStats::TotalNumberKillFrenzies; +int32 CStats::TotalNumberMissions; +int32 CStats::RoundsFiredByPlayer; +int32 CStats::KgsOfExplosivesUsed; +int32 CStats::InstantHitsFiredByPlayer; +int32 CStats::InstantHitsHitByPlayer; +int32 CStats::BestTimeBombDefusal; +int32 CStats::mmRain; +int32 CStats::CarsCrushed; +int32 CStats::FastestTimes[CStats::TOTAL_FASTEST_TIMES]; +int32 CStats::HighestScores[CStats::TOTAL_HIGHEST_SCORES]; + +void CStats::Init() +{ + PeopleKilledByOthers = 0; + PeopleKilledByPlayer = 0; + RoundsFiredByPlayer = 0; + CarsExploded = 0; + HelisDestroyed = 0; + ProgressMade = 0; + KgsOfExplosivesUsed = 0; + InstantHitsFiredByPlayer = 0; + InstantHitsHitByPlayer = 0; + CarsCrushed = 0; + HeadsPopped = 0; + TimesArrested = 0; + TimesDied = 0; + DaysPassed = 0; + NumberOfUniqueJumpsFound = 0; + mmRain = 0; + MaximumJumpFlips = 0; + MaximumJumpSpins = 0; + MaximumJumpDistance = 0; + MaximumJumpHeight = 0; + BestStuntJump = 0; + TotalNumberOfUniqueJumps = 0; + Record4x4One = 0; + LongestFlightInDodo = 0; + Record4x4Two = 0; + PassengersDroppedOffWithTaxi = 0; + Record4x4Three = 0; + MoneyMadeWithTaxi = 0; + Record4x4Mayhem = 0; + LivesSavedWithAmbulance = 0; + ElBurroTime = 0; + CriminalsCaught = 0; + MissionsGiven = 0; + HighestLevelAmbulanceMission = 0; + MissionsPassed = 0; + FiresExtinguished = 0; + DistanceTravelledOnFoot = 0; + TimeTakenDefuseMission = 0; + NumberKillFrenziesPassed = 0; + DistanceTravelledInVehicle = 0; + TotalNumberKillFrenzies = 0; + TotalNumberMissions = 0; + KillsSinceLastCheckpoint = 0; + TotalLegitimateKills = 0; + for (int i = 0; i < TOTAL_FASTEST_TIMES; i++) + FastestTimes[i] = 0; + for (int i = 0; i < TOTAL_HIGHEST_SCORES; i++) + HighestScores[i] = 0; + for (int i = 0; i < NUM_PEDTYPES; i++) + PedsKilledOfThisType[i] = 0; + IndustrialPassed = 0; + CommercialPassed = 0; + SuburbanPassed = 0; +} + +void CStats::RegisterFastestTime(int32 index, int32 time) +{ + assert(index >= 0 && index < TOTAL_FASTEST_TIMES); + if (FastestTimes[index] == 0) + FastestTimes[index] = time; + else + FastestTimes[index] = Min(FastestTimes[index], time); +} + +void CStats::RegisterHighestScore(int32 index, int32 score) +{ + assert(index >= 0 && index < TOTAL_HIGHEST_SCORES); + HighestScores[index] = Max(HighestScores[index], score); +} + +void CStats::RegisterElBurroTime(int32 time) +{ + ElBurroTime = (ElBurroTime && ElBurroTime < time) ? ElBurroTime : time; +} + +void CStats::Register4x4OneTime(int32 time) +{ + Record4x4One = (Record4x4One && Record4x4One < time) ? Record4x4One : time; +} + +void CStats::Register4x4TwoTime(int32 time) +{ + Record4x4Two = (Record4x4Two && Record4x4Two < time) ? Record4x4Two : time; +} + +void CStats::Register4x4ThreeTime(int32 time) +{ + Record4x4Three = (Record4x4Three && Record4x4Three < time) ? Record4x4Three : time; +} + +void CStats::Register4x4MayhemTime(int32 time) +{ + Record4x4Mayhem = (Record4x4Mayhem && Record4x4Mayhem < time) ? Record4x4Mayhem : time; +} + +void CStats::AnotherLifeSavedWithAmbulance() +{ + ++LivesSavedWithAmbulance; +} + +void CStats::AnotherCriminalCaught() +{ + ++CriminalsCaught; +} + +void CStats::RegisterLevelAmbulanceMission(int32 level) +{ + HighestLevelAmbulanceMission = Max(HighestLevelAmbulanceMission, level); +} + +void CStats::AnotherFireExtinguished() +{ + ++FiresExtinguished; +} + +void CStats::RegisterLongestFlightInDodo(int32 time) +{ + LongestFlightInDodo = Max(LongestFlightInDodo, time); +} + +void CStats::RegisterTimeTakenDefuseMission(int32 time) +{ + TimeTakenDefuseMission = (TimeTakenDefuseMission && TimeTakenDefuseMission < time) ? TimeTakenDefuseMission : time; +} + +void CStats::AnotherKillFrenzyPassed() +{ + ++NumberKillFrenziesPassed; +} + +void CStats::SetTotalNumberKillFrenzies(int32 total) +{ + TotalNumberKillFrenzies = total; +} + +void CStats::SetTotalNumberMissions(int32 total) +{ + TotalNumberMissions = total; +} + +wchar *CStats::FindCriminalRatingString() +{ + int rating = FindCriminalRatingNumber(); + + if (rating < 10) return TheText.Get("RATNG1"); + if (rating < 25) return TheText.Get("RATNG2"); + if (rating < 70) return TheText.Get("RATNG3"); + if (rating < 150) return TheText.Get("RATNG4"); + if (rating < 250) return TheText.Get("RATNG5"); + if (rating < 450) return TheText.Get("RATNG6"); + if (rating < 700) return TheText.Get("RATNG7"); + if (rating < 1000) return TheText.Get("RATNG8"); + if (rating < 1400) return TheText.Get("RATNG9"); + if (rating < 1900) return TheText.Get("RATNG10"); + if (rating < 2500) return TheText.Get("RATNG11"); + if (rating < 3200) return TheText.Get("RATNG12"); + if (rating < 4000) return TheText.Get("RATNG13"); + if (rating < 5000) return TheText.Get("RATNG14"); + return TheText.Get("RATNG15"); +} + +int32 CStats::FindCriminalRatingNumber() +{ + int32 rating; + + rating = FiresExtinguished + 10 * HighestLevelAmbulanceMission + CriminalsCaught + LivesSavedWithAmbulance + + 30 * HelisDestroyed + TotalLegitimateKills - 3 * TimesArrested - 3 * TimesDied + + CWorld::Players[CWorld::PlayerInFocus].m_nMoney / 5000; + if (rating <= 0) rating = 0; + + if (InstantHitsFiredByPlayer > 100) + rating += (float)CStats::InstantHitsHitByPlayer / (float)CStats::InstantHitsFiredByPlayer * 500.0f; + if (TotalProgressInGame) + rating += (float)CStats::ProgressMade / (float)CStats::TotalProgressInGame * 1000.0f; + if (!IndustrialPassed && rating >= 3521) + rating = 3521; + if (!CommercialPassed && rating >= 4552) + rating = 4552; + return rating; +} + +void CStats::SaveStats(uint8 *buf, uint32 *size) +{ + CheckPointReachedSuccessfully(); + uint8 *buf_start = buf; + *size = sizeof(PeopleKilledByPlayer) + + sizeof(PeopleKilledByOthers) + + sizeof(CarsExploded) + + sizeof(RoundsFiredByPlayer) + + sizeof(PedsKilledOfThisType) + + sizeof(HelisDestroyed) + + sizeof(ProgressMade) + + sizeof(TotalProgressInGame) + + sizeof(KgsOfExplosivesUsed) + + sizeof(InstantHitsFiredByPlayer) + + sizeof(InstantHitsHitByPlayer) + + sizeof(CarsCrushed) + + sizeof(HeadsPopped) + + sizeof(TimesArrested) + + sizeof(TimesDied) + + sizeof(DaysPassed) + + sizeof(mmRain) + + sizeof(MaximumJumpDistance) + + sizeof(MaximumJumpHeight) + + sizeof(MaximumJumpFlips) + + sizeof(MaximumJumpSpins) + + sizeof(BestStuntJump) + + sizeof(NumberOfUniqueJumpsFound) + + sizeof(TotalNumberOfUniqueJumps) + + sizeof(MissionsGiven) + + sizeof(MissionsPassed) + + sizeof(PassengersDroppedOffWithTaxi) + + sizeof(MoneyMadeWithTaxi) + + sizeof(IndustrialPassed) + + sizeof(CommercialPassed) + + sizeof(SuburbanPassed) + + sizeof(ElBurroTime) + + sizeof(DistanceTravelledOnFoot) + + sizeof(DistanceTravelledInVehicle) + + sizeof(Record4x4One) + + sizeof(Record4x4Two) + + sizeof(Record4x4Three) + + sizeof(Record4x4Mayhem) + + sizeof(LivesSavedWithAmbulance) + + sizeof(CriminalsCaught) + + sizeof(HighestLevelAmbulanceMission) + + sizeof(FiresExtinguished) + + sizeof(LongestFlightInDodo) + + sizeof(TimeTakenDefuseMission) + + sizeof(NumberKillFrenziesPassed) + + sizeof(TotalNumberKillFrenzies) + + sizeof(TotalNumberMissions) + + sizeof(FastestTimes) + + sizeof(HighestScores) + + sizeof(KillsSinceLastCheckpoint) + + sizeof(TotalLegitimateKills) + + sizeof(LastMissionPassedName); + +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); buf += sizeof(data); + CopyToBuf(buf, PeopleKilledByPlayer); + CopyToBuf(buf, PeopleKilledByOthers); + CopyToBuf(buf, CarsExploded); + CopyToBuf(buf, RoundsFiredByPlayer); + CopyToBuf(buf, PedsKilledOfThisType); + CopyToBuf(buf, HelisDestroyed); + CopyToBuf(buf, ProgressMade); + CopyToBuf(buf, TotalProgressInGame); + CopyToBuf(buf, KgsOfExplosivesUsed); + CopyToBuf(buf, InstantHitsFiredByPlayer); + CopyToBuf(buf, InstantHitsHitByPlayer); + CopyToBuf(buf, CarsCrushed); + CopyToBuf(buf, HeadsPopped); + CopyToBuf(buf, TimesArrested); + CopyToBuf(buf, TimesDied); + CopyToBuf(buf, DaysPassed); + CopyToBuf(buf, mmRain); + CopyToBuf(buf, MaximumJumpDistance); + CopyToBuf(buf, MaximumJumpHeight); + CopyToBuf(buf, MaximumJumpFlips); + CopyToBuf(buf, MaximumJumpSpins); + CopyToBuf(buf, BestStuntJump); + CopyToBuf(buf, NumberOfUniqueJumpsFound); + CopyToBuf(buf, TotalNumberOfUniqueJumps); + CopyToBuf(buf, MissionsGiven); + CopyToBuf(buf, MissionsPassed); + CopyToBuf(buf, PassengersDroppedOffWithTaxi); + CopyToBuf(buf, MoneyMadeWithTaxi); + CopyToBuf(buf, IndustrialPassed); + CopyToBuf(buf, CommercialPassed); + CopyToBuf(buf, SuburbanPassed); + CopyToBuf(buf, ElBurroTime); + CopyToBuf(buf, DistanceTravelledOnFoot); + CopyToBuf(buf, DistanceTravelledInVehicle); + CopyToBuf(buf, Record4x4One); + CopyToBuf(buf, Record4x4Two); + CopyToBuf(buf, Record4x4Three); + CopyToBuf(buf, Record4x4Mayhem); + CopyToBuf(buf, LivesSavedWithAmbulance); + CopyToBuf(buf, CriminalsCaught); + CopyToBuf(buf, HighestLevelAmbulanceMission); + CopyToBuf(buf, FiresExtinguished); + CopyToBuf(buf, LongestFlightInDodo); + CopyToBuf(buf, TimeTakenDefuseMission); + CopyToBuf(buf, NumberKillFrenziesPassed); + CopyToBuf(buf, TotalNumberKillFrenzies); + CopyToBuf(buf, TotalNumberMissions); + CopyToBuf(buf, FastestTimes); + CopyToBuf(buf, HighestScores); + CopyToBuf(buf, KillsSinceLastCheckpoint); + CopyToBuf(buf, TotalLegitimateKills); + CopyToBuf(buf, LastMissionPassedName); + + assert(buf - buf_start == *size); +#undef CopyToBuf +} + +void CStats::LoadStats(uint8 *buf, uint32 size) +{ + uint8* buf_start = buf; + +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); buf += sizeof(data); + + CopyFromBuf(buf, PeopleKilledByPlayer); + CopyFromBuf(buf, PeopleKilledByOthers); + CopyFromBuf(buf, CarsExploded); + CopyFromBuf(buf, RoundsFiredByPlayer); + CopyFromBuf(buf, PedsKilledOfThisType); + CopyFromBuf(buf, HelisDestroyed); + CopyFromBuf(buf, ProgressMade); + CopyFromBuf(buf, TotalProgressInGame); + CopyFromBuf(buf, KgsOfExplosivesUsed); + CopyFromBuf(buf, InstantHitsFiredByPlayer); + CopyFromBuf(buf, InstantHitsHitByPlayer); + CopyFromBuf(buf, CarsCrushed); + CopyFromBuf(buf, HeadsPopped); + CopyFromBuf(buf, TimesArrested); + CopyFromBuf(buf, TimesDied); + CopyFromBuf(buf, DaysPassed); + CopyFromBuf(buf, mmRain); + CopyFromBuf(buf, MaximumJumpDistance); + CopyFromBuf(buf, MaximumJumpHeight); + CopyFromBuf(buf, MaximumJumpFlips); + CopyFromBuf(buf, MaximumJumpSpins); + CopyFromBuf(buf, BestStuntJump); + CopyFromBuf(buf, NumberOfUniqueJumpsFound); + CopyFromBuf(buf, TotalNumberOfUniqueJumps); + CopyFromBuf(buf, MissionsGiven); + CopyFromBuf(buf, MissionsPassed); + CopyFromBuf(buf, PassengersDroppedOffWithTaxi); + CopyFromBuf(buf, MoneyMadeWithTaxi); + CopyFromBuf(buf, IndustrialPassed); + CopyFromBuf(buf, CommercialPassed); + CopyFromBuf(buf, SuburbanPassed); + CopyFromBuf(buf, ElBurroTime); + CopyFromBuf(buf, DistanceTravelledOnFoot); + CopyFromBuf(buf, DistanceTravelledInVehicle); + CopyFromBuf(buf, Record4x4One); + CopyFromBuf(buf, Record4x4Two); + CopyFromBuf(buf, Record4x4Three); + CopyFromBuf(buf, Record4x4Mayhem); + CopyFromBuf(buf, LivesSavedWithAmbulance); + CopyFromBuf(buf, CriminalsCaught); + CopyFromBuf(buf, HighestLevelAmbulanceMission); + CopyFromBuf(buf, FiresExtinguished); + CopyFromBuf(buf, LongestFlightInDodo); + CopyFromBuf(buf, TimeTakenDefuseMission); + CopyFromBuf(buf, NumberKillFrenziesPassed); + CopyFromBuf(buf, TotalNumberKillFrenzies); + CopyFromBuf(buf, TotalNumberMissions); + CopyFromBuf(buf, FastestTimes); + CopyFromBuf(buf, HighestScores); + CopyFromBuf(buf, KillsSinceLastCheckpoint); + CopyFromBuf(buf, TotalLegitimateKills); + CopyFromBuf(buf, LastMissionPassedName); + + assert(buf - buf_start == size); +#undef CopyFromBuf +} diff --git a/src/core/Stats.h b/src/core/Stats.h new file mode 100644 index 0000000..6abcfb6 --- /dev/null +++ b/src/core/Stats.h @@ -0,0 +1,90 @@ +#pragma once + +#include "PedType.h" + +class CStats +{ +public: + enum { + TOTAL_FASTEST_TIMES = 16, + TOTAL_HIGHEST_SCORES = 16 + }; + static int32 DaysPassed; + static int32 HeadsPopped; + static int32 CommercialPassed; + static int32 IndustrialPassed; + static int32 SuburbanPassed; + static int32 NumberKillFrenziesPassed; + static int32 PeopleKilledByOthers; + static int32 HelisDestroyed; + static int32 PedsKilledOfThisType[NUM_PEDTYPES]; + static int32 TimesDied; + static int32 TimesArrested; + static int32 KillsSinceLastCheckpoint; + static float DistanceTravelledInVehicle; + static float DistanceTravelledOnFoot; + static int32 CarsExploded; + static int32 PeopleKilledByPlayer; + static int32 ProgressMade; + static int32 TotalProgressInGame; + static float MaximumJumpDistance; + static float MaximumJumpHeight; + static int32 MaximumJumpFlips; + static int32 MaximumJumpSpins; + static int32 BestStuntJump; + static int32 NumberOfUniqueJumpsFound; + static int32 TotalNumberOfUniqueJumps; + static int32 PassengersDroppedOffWithTaxi; + static int32 MoneyMadeWithTaxi; + static int32 MissionsGiven; + static int32 MissionsPassed; + static char LastMissionPassedName[8]; + static int32 TotalLegitimateKills; + static int32 ElBurroTime; + static int32 Record4x4One; + static int32 Record4x4Two; + static int32 Record4x4Three; + static int32 Record4x4Mayhem; + static int32 LivesSavedWithAmbulance; + static int32 CriminalsCaught; + static int32 HighestLevelAmbulanceMission; + static int32 FiresExtinguished; + static int32 LongestFlightInDodo; + static int32 TimeTakenDefuseMission; + static int32 TotalNumberKillFrenzies; + static int32 TotalNumberMissions; + static int32 RoundsFiredByPlayer; + static int32 KgsOfExplosivesUsed; + static int32 InstantHitsFiredByPlayer; + static int32 InstantHitsHitByPlayer; + static int32 BestTimeBombDefusal; + static int32 mmRain; + static int32 CarsCrushed; + static int32 FastestTimes[TOTAL_FASTEST_TIMES]; + static int32 HighestScores[TOTAL_HIGHEST_SCORES]; + +public: + static void Init(void); + static void RegisterFastestTime(int32, int32); + static void RegisterHighestScore(int32, int32); + static void RegisterElBurroTime(int32); + static void Register4x4OneTime(int32); + static void Register4x4TwoTime(int32); + static void Register4x4ThreeTime(int32); + static void Register4x4MayhemTime(int32); + static void AnotherLifeSavedWithAmbulance(); + static void AnotherCriminalCaught(); + static void RegisterLevelAmbulanceMission(int32); + static void AnotherFireExtinguished(); + static wchar *FindCriminalRatingString(); + static void RegisterLongestFlightInDodo(int32); + static void RegisterTimeTakenDefuseMission(int32); + static void AnotherKillFrenzyPassed(); + static void SetTotalNumberKillFrenzies(int32); + static void SetTotalNumberMissions(int32); + static void CheckPointReachedSuccessfully() { TotalLegitimateKills += KillsSinceLastCheckpoint; KillsSinceLastCheckpoint = 0; }; + static void CheckPointReachedUnsuccessfully() { KillsSinceLastCheckpoint = 0; }; + static int32 FindCriminalRatingNumber(); + static void SaveStats(uint8 *buf, uint32 *size); + static void LoadStats(uint8 *buf, uint32 size); +}; diff --git a/src/core/Streaming.cpp b/src/core/Streaming.cpp new file mode 100644 index 0000000..9ac2209 --- /dev/null +++ b/src/core/Streaming.cpp @@ -0,0 +1,2831 @@ +#include "common.h" + +#include "General.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 "Wanted.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" +#ifdef FIX_BUGS +#include "Replay.h" +#endif +#include "main.h" +#include "Frontend.h" +#include "Font.h" +#include "MemoryMgr.h" +#include "MemoryHeap.h" + +bool CStreaming::ms_disableStreaming; +bool CStreaming::ms_bLoadingBigModel; +int32 CStreaming::ms_numModelsRequested; +CStreamingInfo CStreaming::ms_aInfoForModel[NUMSTREAMINFO]; +CStreamingInfo CStreaming::ms_startLoadedList; +CStreamingInfo CStreaming::ms_endLoadedList; +CStreamingInfo CStreaming::ms_startRequestedList; +CStreamingInfo CStreaming::ms_endRequestedList; +int32 CStreaming::ms_oldSectorX; +int32 CStreaming::ms_oldSectorY; +int32 CStreaming::ms_streamingBufferSize; +#ifndef ONE_THREAD_PER_CHANNEL +int8 *CStreaming::ms_pStreamingBuffer[2]; +#else +int8 *CStreaming::ms_pStreamingBuffer[4]; +#endif +size_t CStreaming::ms_memoryUsed; +CStreamingChannel CStreaming::ms_channel[2]; +int32 CStreaming::ms_channelError; +int32 CStreaming::ms_numVehiclesLoaded; +int32 CStreaming::ms_vehiclesLoaded[MAXVEHICLESLOADED]; +int32 CStreaming::ms_lastVehicleDeleted; +CDirectory *CStreaming::ms_pExtraObjectsDir; +int32 CStreaming::ms_numPriorityRequests; +bool CStreaming::ms_hasLoadedLODs; +int32 CStreaming::ms_currentPedGrp; +int32 CStreaming::ms_currentPedLoading; +int32 CStreaming::ms_lastCullZone; +uint16 CStreaming::ms_loadedGangs; +uint16 CStreaming::ms_loadedGangCars; +int32 CStreaming::ms_imageOffsets[NUMCDIMAGES]; +int32 CStreaming::ms_lastImageRead; +int32 CStreaming::ms_imageSize; +size_t CStreaming::ms_memoryAvailable; + +int32 desiredNumVehiclesLoaded = 12; + +CEntity *pIslandLODindustEntity; +CEntity *pIslandLODcomIndEntity; +CEntity *pIslandLODcomSubEntity; +CEntity *pIslandLODsubIndEntity; +CEntity *pIslandLODsubComEntity; +int32 islandLODindust; +int32 islandLODcomInd; +int32 islandLODcomSub; +int32 islandLODsubInd; +int32 islandLODsubCom; + +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::Init2(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++; +#ifndef ONE_THREAD_PER_CHANNEL + 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; +#else + ms_pStreamingBuffer[0] = (int8*)RwMallocAlign(ms_streamingBufferSize*2*CDSTREAM_SECTOR_SIZE, CDSTREAM_SECTOR_SIZE); + ms_streamingBufferSize /= 2; + ms_pStreamingBuffer[1] = ms_pStreamingBuffer[0] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; + ms_pStreamingBuffer[2] = ms_pStreamingBuffer[1] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; + ms_pStreamingBuffer[3] = ms_pStreamingBuffer[2] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; +#endif + 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 size_t _dwMemAvailPhys; + ms_memoryAvailable = (_dwMemAvailPhys - 10*MB)/2; + if(ms_memoryAvailable < 50*MB) + ms_memoryAvailable = 50*MB; + desiredNumVehiclesLoaded = (int32)((ms_memoryAvailable / MB - 50) / 3 + 12); + if(desiredNumVehiclesLoaded > MAXVEHICLESLOADED) + desiredNumVehiclesLoaded = MAXVEHICLESLOADED; + debug("Memory allocated to Streaming is %zuMB", ms_memoryAvailable/MB); // original modifier was %d +#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 = CPools::GetBuildingPool()->GetSize()-1; i >= 0; 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::Init(void) +{ +#ifdef USE_TXD_CDIMAGE + int txdHandle = CFileMgr::OpenFile("MODELS\\TXD.IMG", "r"); + if (txdHandle) + CFileMgr::CloseFile(txdHandle); + if (!CheckVideoCardCaps() && txdHandle) { + CdStreamAddImage("MODELS\\TXD.IMG"); + CStreaming::Init2(); + } else { + CStreaming::Init2(); + if (CreateTxdImageForVideoCard()) { + CStreaming::Shutdown(); + CdStreamAddImage("MODELS\\TXD.IMG"); + CStreaming::Init2(); + } + } +#else + CStreaming::Init2(); +#endif +} + +void +CStreaming::Shutdown(void) +{ + RwFreeAlign(ms_pStreamingBuffer[0]); + ms_streamingBufferSize = 0; + if(ms_pExtraObjectsDir){ + delete ms_pExtraObjectsDir; +#ifdef FIX_BUGS + ms_pExtraObjectsDir = nil; +#endif + } +} + +#ifndef MASTER +uint64 timeProcessingTXD; +uint64 timeProcessingDFF; +#endif + +void +CStreaming::Update(void) +{ + CEntity *train; + CStreamingInfo *si, *prev; + bool requestedSubway = false; + +#ifndef MASTER + timeProcessingTXD = 0; + timeProcessingDFF = 0; +#endif + + 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 +#ifdef FIX_BUGS + && !CReplay::IsPlayingBack() +#endif + ){ + StreamVehiclesAndPeds(); + StreamZoneModels(FindPlayerCoors()); + } + + LoadRequestedModels(); + +#ifndef MASTER + if (CPad::GetPad(1)->GetLeftShoulder1JustDown() && CPad::GetPad(1)->GetRightShoulder1() && CPad::GetPad(1)->GetRightShoulder2()) + PrintStreamingBufferState(); + + // TODO: PrintRequestList + //if (CPad::GetPad(1)->GetLeftShoulder2JustDown() && CPad::GetPad(1)->GetRightShoulder1() && CPad::GetPad(1)->GetRightShoulder2()) + // PrintRequestList(); +#endif + + 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, '.'); + assert(dot); + if(dot) *dot = '\0'; + if(direntry.size > (uint32)ms_streamingBufferSize) + ms_streamingBufferSize = direntry.size; + + if(!CGeneral::faststrcmp(dot+1, "DFF") || !CGeneral::faststrcmp(dot+1, "dff")){ + 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{ +#ifdef FIX_BUGS + // remember which cdimage this came from + ms_pExtraObjectsDir->AddItem(direntry, n); +#else + ms_pExtraObjectsDir->AddItem(direntry); +#endif + lastID = -1; + } + }else if(!CGeneral::faststrcmp(dot+1, "TXD") || !CGeneral::faststrcmp(dot+1, "txd")){ + 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); +} + +#ifdef USE_CUSTOM_ALLOCATOR +RpAtomic* +RegisterAtomicMemPtrsCB(RpAtomic *atomic, void *data) +{ +#if THIS_IS_COMPATIBLE_WITH_GTA3_RW31 + // not quite sure what's going on here: + // gta3's RW 3.1 allocates separate memory for geometry data of RpGeometry. + // Is that a R* change? rpDefaultGeometryInstance also depends on it + RpGeometry *geo = RpAtomicGetGeometry(atomic); + if(geo->triangles) + REGISTER_MEMPTR(&geo->triangles); + if(geo->matList.materials) + REGISTER_MEMPTR(&geo->matList.materials); + if(geo->preLitLum) + REGISTER_MEMPTR(&geo->preLitLum); + if(geo->texCoords[0]) + REGISTER_MEMPTR(&geo->texCoords[0]); + if(geo->texCoords[1]) + REGISTER_MEMPTR(&geo->texCoords[1]); +#else + // normally RpGeometry is allocated in one block (excluding morph targets) + // so we don't really have allocated pointers in the struct. + // NB: in librw we actually do it in two allocations (geometry itself and data) + // so we could conceivably come up with something here +#endif + return atomic; +} +#endif + +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 +#ifdef FIX_BUGS + if(!HasTxdLoaded(mi->GetTxdSlot())){ +#else + // texDict will exist even if only first part has loaded + if(CTxdStore::GetSlot(mi->GetTxdSlot())->texDict == nil){ +#endif + debug("failed to load %s because TXD %s is not in memory\n", mi->GetModelName(), CTxdStore::GetTxdName(mi->GetTxdSlot())); + RemoveModel(streamId); +#ifndef FIX_BUGS + // if we're just waiting for it to load, don't remove this + RemoveTxd(mi->GetTxdSlot()); +#endif + ReRequestModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + + // Set Txd to use + CTxdStore::AddRef(mi->GetTxdSlot()); + + PUSH_MEMID(MEMID_STREAM_MODELS); + CTxdStore::SetCurrentTxd(mi->GetTxdSlot()); + if(mi->IsSimple()){ + success = CFileLoader::LoadAtomicFile(stream, streamId); +#ifdef USE_CUSTOM_ALLOCATOR + RegisterAtomicMemPtrsCB(((CSimpleModelInfo*)mi)->m_atomics[0], nil); +#endif + } else if (mi->GetModelType() == 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); +#ifdef USE_CUSTOM_ALLOCATOR + if(success) + RpClumpForAllAtomics((RpClump*)mi->GetRwObject(), RegisterAtomicMemPtrsCB, nil); +#endif + } + POP_MEMID(); + 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)->GetModelName()); + 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; + } + + PUSH_MEMID(MEMID_STREAM_TEXUTRES); + 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); + POP_MEMID(); + 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->GetModelName()); + 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->GetModelType() != MITYPE_VEHICLE && mi->GetModelType() != 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_CANT_REMOVE) == 0) + ms_aInfoForModel[streamId].AddToList(&ms_startLoadedList); + } + }else{ + // Txd + if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_CANT_REMOVE) == 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; +#ifndef USE_CUSTOM_ALLOCATOR + ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; +#endif + } + + 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)->GetModelName(), 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); + PUSH_MEMID(MEMID_STREAM_MODELS); + CTxdStore::SetCurrentTxd(mi->GetTxdSlot()); + success = CFileLoader::FinishLoadClumpFile(stream, streamId); + if(success){ +#ifdef USE_CUSTOM_ALLOCATOR + RpClumpForAllAtomics((RpClump*)mi->GetRwObject(), RegisterAtomicMemPtrsCB, nil); +#endif + success = AddToLoadedVehiclesList(streamId); + } + POP_MEMID(); + mi->RemoveRef(); + CTxdStore::RemoveRefWithoutDelete(mi->GetTxdSlot()); + }else{ + // Txd + CTxdStore::AddRef(streamId - STREAM_OFFSET_TXD); + PUSH_MEMID(MEMID_STREAM_TEXUTRES); + success = CTxdStore::FinishLoadTxd(streamId - STREAM_OFFSET_TXD, stream); + POP_MEMID(); + CTxdStore::RemoveRefWithoutDelete(streamId - STREAM_OFFSET_TXD); + } + + RwStreamClose(stream, &mem); + + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; // only done if success on PS2 +#ifndef USE_CUSTOM_ALLOCATOR + ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; +#endif + + if(!success){ + RemoveModel(streamId); + ReRequestModel(streamId); + UpdateMemoryUsed(); // directly after pop on PS2 + return false; + } + + UpdateMemoryUsed(); // directly after pop on PS2 + + 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)->GetModelName(), 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_CANT_REMOVE) == 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; + default: break; + } +} + +#define BIGBUILDINGFLAGS STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY + +void +CStreaming::RequestBigBuildings(eLevelName level) +{ + int i, n; + CBuilding *b; + + n = CPools::GetBuildingPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + b = CPools::GetBuildingPool()->GetSlot(i); + if(b && b->bIsBIGBuilding +#ifdef NO_ISLAND_LOADING + && (((CMenuManager::m_PrefsIslandLoading != CMenuManager::ISLAND_LOADING_LOW) && (b != pIslandLODindustEntity) && (b != pIslandLODcomIndEntity) && + (b != pIslandLODcomSubEntity) && (b != pIslandLODsubIndEntity) && (b != pIslandLODsubComEntity) + ) || (b->m_level == level)) +#else + && b->m_level == level +#endif + ) + RequestModel(b->GetModelIndex(), BIGBUILDINGFLAGS); + } + RequestIslands(level); + ms_hasLoadedLODs = false; +} + +void +CStreaming::RequestIslands(eLevelName level) +{ + ISLAND_LOADING_ISNT(HIGH) + switch(level){ + case LEVEL_INDUSTRIAL: + RequestModel(islandLODcomInd, BIGBUILDINGFLAGS); + RequestModel(islandLODsubInd, BIGBUILDINGFLAGS); + break; + case LEVEL_COMMERCIAL: + RequestModel(islandLODindust, BIGBUILDINGFLAGS); + RequestModel(islandLODsubCom, BIGBUILDINGFLAGS); + break; + case LEVEL_SUBURBAN: + RequestModel(islandLODindust, BIGBUILDINGFLAGS); + RequestModel(islandLODcomSub, BIGBUILDINGFLAGS); + break; + default: 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(!CGeneral::faststrcmp(mi->GetModelName(), modelName)){ + // Already have the correct name, just request it + RequestModel(modelId, flags); + return; + } + + strcpy(oldName, mi->GetModelName()); + mi->SetModelName(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); + + bool found = ms_pExtraObjectsDir->FindItem(modelName, pos, size); + assert(found); + 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); +#ifdef USE_CUSTOM_ALLOCATOR + UpdateMemoryUsed(); +#else + ms_memoryUsed -= ms_aInfoForModel[id].GetCdSize()*CDSTREAM_SECTOR_SIZE; +#endif + } + + 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); +#ifdef USE_CUSTOM_ALLOCATOR + UpdateMemoryUsed(); +#endif + } + + 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()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if (mi->GetNumRefs() == 0) + RemoveModel(e->GetModelIndex()); + } + } + } + + n = CPools::GetTreadablePool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetTreadablePool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if (mi->GetNumRefs() == 0) + RemoveModel(e->GetModelIndex()); + } + } + } + + n = CPools::GetObjectPool()->GetSize()-1; + for(i = n; i >= 0; 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->GetNumRefs() == 0) + RemoveModel(e->GetModelIndex()); + } + } + } + + n = CPools::GetDummyPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetDummyPool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if (mi->GetNumRefs() == 0) + RemoveModel(e->GetModelIndex()); + } + } + } +} + +void +CStreaming::RemoveUnusedBigBuildings(eLevelName level) +{ + ISLAND_LOADING_IS(LOW) + { + 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) +{ +#ifdef NO_ISLAND_LOADING + if (CMenuManager::m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_HIGH) { + DeleteIsland(pIslandLODindustEntity); + DeleteIsland(pIslandLODcomIndEntity); + DeleteIsland(pIslandLODcomSubEntity); + DeleteIsland(pIslandLODsubIndEntity); + DeleteIsland(pIslandLODsubComEntity); + } else +#endif + 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()-1; + for(i = n; i >= 0; 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->GetNumRefs() == 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_CANT_REMOVE) == 0 && CModelInfo::GetModelInfo(id)->GetNumRefs() == 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)->GetNumRefs() == 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)->GetNumRefs() == 0) { + RemoveModel(i); + ms_aInfoForModel[i].m_loadState = STREAMSTATE_NOTLOADED; + } + } +} + +bool +CStreaming::RemoveReferencedTxds(size_t 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; +} + +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_CANT_REMOVE) == 0 && CModelInfo::GetModelInfo(id)->GetNumRefs() == 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 than 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()-1; + for(i = n; i >= 0; 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)->GetModelType() != 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)->GetModelType() != 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::IsRecording() || + CRecordDataForGame::IsPlayingBack() +#ifdef FIX_BUGS + || CReplay::IsPlayingBack() +#endif + ) + 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 = 1; i <= 10; i++){ + model = CCarCtrl::ChooseCarModel(modelQualityClass); + modelQualityClass++; + if(modelQualityClass >= CCarCtrl::TOTAL_CUSTOM_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 < NUMMODELSPERPEDGROUP; i++){ + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] != -1){ + SetModelIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]); + SetModelTxdIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]); + } + } + + ms_currentPedGrp = info.pedGroup; + + for(i = 0; i < NUMMODELSPERPEDGROUP; i++){ + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] != -1) + 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<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 = INT32_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 +ModelNotLoaded(int32 modelId) +{ + CStreamingInfo *si = &CStreaming::ms_aInfoForModel[modelId]; + return si->m_loadState != STREAMSTATE_LOADED && si->m_loadState != STREAMSTATE_READING; +} + +inline bool TxdNotLoaded(int32 txdId) { return ModelNotLoaded(txdId + STREAM_OFFSET_TXD); } + +// 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 = UINT32_MAX; + posnNext = UINT32_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){ + int txdId = CModelInfo::GetModelInfo(streamId)->GetTxdSlot(); + if(TxdNotLoaded(txdId)){ + ReRequestTxd(txdId); + continue; + } + } + + 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. + */ + +// 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)->GetModelType() == MITYPE_PED || + haveBigFile && CModelInfo::GetModelInfo(streamId)->GetModelType() == MITYPE_VEHICLE || + TxdNotLoaded(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)->GetModelType() == MITYPE_PED) + havePed = 1; + if (CModelInfo::GetModelInfo(streamId)->GetModelType() == 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)->GetModelType() == MITYPE_VEHICLE && + ms_numVehiclesLoaded >= desiredNumVehiclesLoaded && + !RemoveLoadedVehicle() && + ((ms_aInfoForModel[id].m_flags & STREAMFLAGS_CANT_REMOVE) == 0 || GetAvailableVehicleSlot() == -1)){ + // can't load vehicle + RemoveModel(id); + if(ms_aInfoForModel[id].m_flags & STREAMFLAGS_CANT_REMOVE) + 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) +{ + Const 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_ERROR: + ms_channel[ch].numTries++; + if (CdStreamGetStatus(ch) == STREAM_READING || CdStreamGetStatus(ch) == STREAM_WAITING) break; + case CHANNELSTATE_IDLE: + 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; + } +} + +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; + } +} + + +// Let's load models in 4 threads; when one of them becomes idle, process the file, and fill thread with another file. Unfortunately processing models are still single-threaded. +// Currently only supported on POSIX streamer. +// WIP - some files are loaded swapped (CdStreamPosix problem?) +#if 0 //def ONE_THREAD_PER_CHANNEL +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()); + + int streamIds[ARRAY_SIZE(ms_pStreamingBuffer)]; + int streamSizes[ARRAY_SIZE(ms_pStreamingBuffer)]; + int streamPoses[ARRAY_SIZE(ms_pStreamingBuffer)]; + int readOrder[4] = {-1}; // Channel IDs ordered by read time + int readI = 0; + int processI = 0; + bool first = true; + + // All those "first" checks are because of variables aren't initialized in first pass. + + while (true) { + for (int i=0; i (uint32)ms_streamingBufferSize) { + if (i + 1 == ARRAY_SIZE(ms_pStreamingBuffer)) + break; + else if (!first && streamIds[i+1] != -1) + continue; + + } else { + // Buffer of current channel is part of a "big file", pass + if (i != 0 && streamIds[i-1] != -1 && streamSizes[i-1] > (uint32)ms_streamingBufferSize) + continue; + } + ms_aInfoForModel[streamId].RemoveFromList(); + DecrementRef(streamId); + + streamIds[i] = streamId; + streamSizes[i] = size; + streamPoses[i] = posn; + + if (!first) + assert(readOrder[readI] == -1); + + //printf("read: order %d, ch %d, id %d, size %d\n", readI, i, streamId, size); + + CdStreamRead(i, ms_pStreamingBuffer[i], imgOffset+posn, size); + readOrder[readI] = i; + if (first && readI+1 != ARRAY_SIZE(readOrder)) + readOrder[readI+1] = -1; + + readI = (readI + 1) % ARRAY_SIZE(readOrder); + } else { + ms_aInfoForModel[streamId].RemoveFromList(); + DecrementRef(streamId); + + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; + streamIds[i] = -1; + } + } else { + streamIds[i] = -1; + break; + } + } + + first = false; + int nextChannel = readOrder[processI]; + + // Now start processing + if (nextChannel == -1 || streamIds[nextChannel] == -1) + break; + + //printf("process: order %d, ch %d, id %d\n", processI, nextChannel, streamIds[nextChannel]); + + // Try again on error + while (CdStreamSync(nextChannel) != STREAM_NONE) { + CdStreamRead(nextChannel, ms_pStreamingBuffer[nextChannel], imgOffset+streamPoses[nextChannel], streamSizes[nextChannel]); + } + ms_aInfoForModel[streamIds[nextChannel]].m_loadState = STREAMSTATE_READING; + + MakeSpaceFor(streamSizes[nextChannel] * CDSTREAM_SECTOR_SIZE); + ConvertBufferToObject(ms_pStreamingBuffer[nextChannel], streamIds[nextChannel]); + if(ms_aInfoForModel[streamIds[nextChannel]].m_loadState == STREAMSTATE_STARTED) + FinishLoadingLargeFile(ms_pStreamingBuffer[nextChannel], streamIds[nextChannel]); + + if(streamIds[nextChannel] < STREAM_OFFSET_TXD){ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(streamIds[nextChannel]); + if(mi->IsSimple()) + mi->m_alpha = 255; + } + streamIds[nextChannel] = -1; + readOrder[processI] = -1; + processI = (processI + 1) % ARRAY_SIZE(readOrder); + } + + 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; +} +#else +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; +} +#endif + +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); + } +#ifdef FLUSHABLE_STREAMING + if(ms_channel[0].state == CHANNELSTATE_READING) { + flushStream[0] = 1; + } + if(ms_channel[1].state == CHANNELSTATE_READING) { + flushStream[1] = 1; + } +#endif + FlushChannels(); +} + + +void +CStreaming::ImGonnaUseStreamingMemory(void) +{ + PUSH_MEMID(MEMID_STREAM); +} + +void +CStreaming::IHaveUsedStreamingMemory(void) +{ + POP_MEMID(); + UpdateMemoryUsed(); +} + +void +CStreaming::UpdateMemoryUsed(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR + ms_memoryUsed = + gMainHeap.GetMemoryUsed(MEMID_STREAM) + + gMainHeap.GetMemoryUsed(MEMID_STREAM_MODELS) + + gMainHeap.GetMemoryUsed(MEMID_STREAM_TEXUTRES); +#endif +} + +#define STREAM_DIST 80.0f + +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->GetModelType() != 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->GetModelType() != 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::GetSectorIndexY(pos.y); + + for(x = 0; x < NUMSECTORS_X; x++) + for(y = 0; y < NUMSECTORS_Y; y++) + if(Abs(ix - x) > 3.0f && + Abs(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(size_t 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::GetSectorIndexY(TheCamera.GetPosition().y); + + if(Abs(TheCamera.GetForward().x) > Abs(TheCamera.GetForward().y)){ + // looking west/east + + ymin = Max(iy - 10, 0); + ymax = Min(iy + 10, NUMSECTORS_Y - 1); + assert(ymin <= ymax); + + // Delete a block of sectors that we know is behind the camera + if(TheCamera.GetForward().x > 0.0f){ + // looking east + xmax = Max(ix - 2, 0); + xmin = Max(ix - 10, 0); + inc = 1; + }else{ + // looking west + xmax = Min(ix + 2, NUMSECTORS_X - 1); + xmin = Min(ix + 10, NUMSECTORS_X - 1); + 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.0f){ + // looking east + xmax = Max(ix + 10, 0); + xmin = Max(ix - 2, 0); + inc = 1; + }else{ + // looking west + xmax = Min(ix - 10, NUMSECTORS_X - 1); + xmin = Min(ix + 2, NUMSECTORS_X - 1); + 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 - 1); + assert(xmin <= xmax); + + // Delete a block of sectors that we know is behind the camera + if(TheCamera.GetForward().y > 0.0f){ + // looking north + ymax = Max(iy - 2, 0); + ymin = Max(iy - 10, 0); + inc = 1; + }else{ + // looking south + ymax = Min(iy + 2, NUMSECTORS_Y - 1); + ymin = Min(iy + 10, NUMSECTORS_Y - 1); + 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.0f){ + // looking north + ymax = Max(iy + 10, 0); + ymin = Max(iy - 2, 0); + inc = 1; + }else{ + // looking south + ymax = Min(iy - 10, NUMSECTORS_Y - 1); + ymin = Min(iy + 2, NUMSECTORS_Y - 1); + 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(Abs(CWorld::GetSectorIndexX(e->GetPosition().x) - x) >= 2.0f) +// { + e->DeleteRwObject(); +// return; // BUG? +// } + else // FIX? + if(Abs(CWorld::GetSectorIndexY(e->GetPosition().y) - y) >= 2.0f) + e->DeleteRwObject(); + } + } +} + +bool +CStreaming::DeleteRwObjectsBehindCameraInSectorList(CPtrList &list, size_t 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())->GetNumRefs() == 0) { + RemoveModel(e->GetModelIndex()); + if(ms_memoryUsed < mem) + return true; + } + } + } + return false; +} + +bool +CStreaming::DeleteRwObjectsNotInFrustumInSectorList(CPtrList &list, size_t 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())->GetNumRefs() == 0) { + RemoveModel(e->GetModelIndex()); + if(ms_memoryUsed < mem) + return true; + } + } + } + return false; +} + +void +CStreaming::MakeSpaceFor(int32 size) +{ +#ifdef FIX_BUGS +#define MB (1024 * 1024) + if(ms_memoryAvailable == 0) { + extern size_t _dwMemAvailPhys; + ms_memoryAvailable = (_dwMemAvailPhys - 10 * MB) / 2; + if(ms_memoryAvailable < 50 * MB) ms_memoryAvailable = 50 * MB; + } +#undef MB +#endif + while(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 *buf, uint32 *size) +{ + int i; + + *size = NUM_DEFAULT_MODELS; + for(i = 0; i < NUM_DEFAULT_MODELS; i++) + if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED) + buf[i] = ms_aInfoForModel[i].m_flags; + else + buf[i] = 0xFF; +} + +void +CStreaming::MemoryCardLoad(uint8 *buf, uint32 size) +{ + uint32 i; + + assert(size == NUM_DEFAULT_MODELS); + for(i = 0; i < size; i++) + if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED) + if(buf[i] != 0xFF) + ms_aInfoForModel[i].m_flags = buf[i]; +} + +void +CStreaming::UpdateForAnimViewer(void) +{ + if (CStreaming::ms_channelError == -1) { + CStreaming::AddModelsToRequestList(CVector(0.0f, 0.0f, 0.0f)); + CStreaming::LoadRequestedModels(); + sprintf(gString, "Requested %d, memory size %zuK\n", CStreaming::ms_numModelsRequested, 2 * CStreaming::ms_memoryUsed); // original modifier was %d + } + else { + CStreaming::RetryLoadFile(CStreaming::ms_channelError); + } +} + + +void +CStreaming::PrintStreamingBufferState() +{ + char str[128]; + wchar wstr[128]; + uint32 offset, size; + + CTimer::Stop(); + int i = 0; + while (i < NUMSTREAMINFO) { + while (true) { + int j = 0; + DoRWStuffStartOfFrame(50, 50, 50, 0, 0, 0, 255); + CPad::UpdatePads(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + + CRect unusedRect(0, 0, RsGlobal.maximumWidth, RsGlobal.maximumHeight); + CRGBA unusedColor(255, 255, 255, 255); + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetWrapx(DEFAULT_SCREEN_WIDTH); + CFont::SetScale(0.5f, 0.75f); + CFont::SetCentreOff(); + CFont::SetCentreSize(DEFAULT_SCREEN_WIDTH); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(200, 200, 200, 200)); + CFont::SetBackGroundOnlyTextOff(); + int modelIndex = i; + if (modelIndex < NUMSTREAMINFO) { + int y = 24; + for ( ; j < 34 && modelIndex < NUMSTREAMINFO; modelIndex++) { + CStreamingInfo *streamingInfo = &ms_aInfoForModel[modelIndex]; + CBaseModelInfo *modelInfo = CModelInfo::GetModelInfo(modelIndex); + if (streamingInfo->m_loadState != STREAMSTATE_LOADED || !streamingInfo->GetCdPosnAndSize(offset, size)) + continue; + + if (modelIndex >= STREAM_OFFSET_TXD) + sprintf(str, "txd %s, refs %d, size %dK, flags 0x%x", CTxdStore::GetTxdName(modelIndex - STREAM_OFFSET_TXD), + CTxdStore::GetNumRefs(modelIndex - STREAM_OFFSET_TXD), 2 * size, streamingInfo->m_flags); + else + sprintf(str, "model %d,%s, refs%d, size%dK, flags%x", modelIndex, modelInfo->GetModelName(), modelInfo->GetNumRefs(), 2 * size, + streamingInfo->m_flags); + AsciiToUnicode(str, wstr); + CFont::PrintString(24.0f, y, wstr); + y += 12; + j++; + } + } + + if (CPad::GetPad(1)->GetCrossJustDown()) + i = modelIndex; + + if (!CPad::GetPad(1)->GetTriangleJustDown()) + break; + + i = 0; + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); + } + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); + } + CTimer::Update(); +} diff --git a/src/core/Streaming.h b/src/core/Streaming.h new file mode 100644 index 0000000..3294a88 --- /dev/null +++ b/src/core/Streaming.h @@ -0,0 +1,197 @@ +#pragma once + +#include "Game.h" + +enum { + STREAM_OFFSET_TXD = 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, + + STREAMFLAGS_CANT_REMOVE = 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; +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; +#ifndef ONE_THREAD_PER_CHANNEL + static int8 *ms_pStreamingBuffer[2]; +#else + static int8 *ms_pStreamingBuffer[4]; +#endif + static size_t 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 size_t ms_memoryAvailable; + + static void Init(void); + static void Init2(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 bool HasTxdLoaded(int32 id) { return HasModelLoaded(id+STREAM_OFFSET_TXD); } + static bool CanRemoveModel(int32 id) { return (ms_aInfoForModel[id].m_flags & STREAMFLAGS_CANT_REMOVE) == 0; } + static bool CanRemoveTxd(int32 id) { return CanRemoveModel(id+STREAM_OFFSET_TXD); } + 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(size_t mem); // originally signed + 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(size_t mem); // originally signed + static void DeleteRwObjectsInSectorList(CPtrList &list); + static void DeleteRwObjectsInOverlapSectorList(CPtrList &list, int32 x, int32 y); + static bool DeleteRwObjectsBehindCameraInSectorList(CPtrList &list, size_t mem); // originally signed + static bool DeleteRwObjectsNotInFrustumInSectorList(CPtrList &list, size_t mem); // originally signed + + static void LoadScene(const CVector &pos); + + static void MemoryCardSave(uint8 *buffer, uint32 *length); + static void MemoryCardLoad(uint8 *buffer, uint32 length); + + static void UpdateForAnimViewer(void); + + static void PrintStreamingBufferState(); +}; diff --git a/src/core/SurfaceTable.cpp b/src/core/SurfaceTable.cpp new file mode 100644 index 0000000..b1bcceb --- /dev/null +++ b/src/core/SurfaceTable.cpp @@ -0,0 +1,143 @@ +#include "common.h" + +#include "main.h" +#include "FileMgr.h" +#include "Weather.h" +#include "Collision.h" +#include "SurfaceTable.h" + +float CSurfaceTable::ms_aAdhesiveLimitTable[NUMADHESIVEGROUPS][NUMADHESIVEGROUPS]; + +void +CSurfaceTable::Initialise(Const 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_DEFAULT: return ADHESIVE_ROAD; + case SURFACE_TARMAC: return ADHESIVE_ROAD; + case SURFACE_GRASS: return ADHESIVE_LOOSE; + case SURFACE_GRAVEL: return ADHESIVE_LOOSE; + case SURFACE_MUD_DRY: return ADHESIVE_HARD; + case SURFACE_PAVEMENT: return ADHESIVE_ROAD; + case SURFACE_CAR: return ADHESIVE_HARD; + case SURFACE_GLASS: return ADHESIVE_HARD; + case SURFACE_TRANSPARENT_CLOTH: return ADHESIVE_HARD; + case SURFACE_GARAGE_DOOR: return ADHESIVE_HARD; + case SURFACE_CAR_PANEL: return ADHESIVE_HARD; + case SURFACE_THICK_METAL_PLATE: return ADHESIVE_HARD; + case SURFACE_SCAFFOLD_POLE: return ADHESIVE_HARD; + case SURFACE_LAMP_POST: return ADHESIVE_HARD; + case SURFACE_FIRE_HYDRANT: return ADHESIVE_HARD; + case SURFACE_GIRDER: return ADHESIVE_HARD; + case SURFACE_METAL_CHAIN_FENCE: return ADHESIVE_HARD; + case SURFACE_PED: return ADHESIVE_RUBBER; + case SURFACE_SAND: return ADHESIVE_LOOSE; + case SURFACE_WATER: return ADHESIVE_WET; + case SURFACE_WOOD_CRATES: return ADHESIVE_ROAD; + case SURFACE_WOOD_BENCH: return ADHESIVE_ROAD; + case SURFACE_WOOD_SOLID: return ADHESIVE_ROAD; + case SURFACE_RUBBER: return ADHESIVE_RUBBER; + case SURFACE_PLASTIC: return ADHESIVE_HARD; + case SURFACE_HEDGE: return ADHESIVE_LOOSE; + case SURFACE_STEEP_CLIFF: return ADHESIVE_LOOSE; + case SURFACE_CONTAINER: return ADHESIVE_HARD; + case SURFACE_NEWS_VENDOR: return ADHESIVE_HARD; + case SURFACE_WHEELBASE: return ADHESIVE_RUBBER; + case SURFACE_CARDBOARDBOX: return ADHESIVE_LOOSE; + case SURFACE_TRANSPARENT_STONE: return ADHESIVE_HARD; + case SURFACE_METAL_GATE: return ADHESIVE_HARD; + default: return ADHESIVE_ROAD; + } +} + +float +CSurfaceTable::GetWetMultiplier(uint8 surfaceType) +{ + switch(surfaceType){ + case SURFACE_DEFAULT: + case SURFACE_TARMAC: + case SURFACE_MUD_DRY: + case SURFACE_PAVEMENT: + case SURFACE_TRANSPARENT_CLOTH: + case SURFACE_WOOD_CRATES: + case SURFACE_WOOD_BENCH: + case SURFACE_WOOD_SOLID: + case SURFACE_HEDGE: + case SURFACE_CARDBOARDBOX: + case SURFACE_TRANSPARENT_STONE: + return 1.0f - CWeather::WetRoads*0.25f; + + case SURFACE_GRASS: + case SURFACE_CAR: + case SURFACE_GLASS: + case SURFACE_GARAGE_DOOR: + case SURFACE_CAR_PANEL: + case SURFACE_THICK_METAL_PLATE: + case SURFACE_SCAFFOLD_POLE: + case SURFACE_LAMP_POST: + case SURFACE_FIRE_HYDRANT: + case SURFACE_GIRDER: + case SURFACE_METAL_CHAIN_FENCE: + case SURFACE_PED: + case SURFACE_RUBBER: + case SURFACE_PLASTIC: + case SURFACE_STEEP_CLIFF: + case SURFACE_CONTAINER: + case SURFACE_NEWS_VENDOR: + case SURFACE_WHEELBASE: + case SURFACE_METAL_GATE: + 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)]; +} diff --git a/src/core/SurfaceTable.h b/src/core/SurfaceTable.h new file mode 100644 index 0000000..8ee1724 --- /dev/null +++ b/src/core/SurfaceTable.h @@ -0,0 +1,80 @@ +#pragma once + +enum eSurfaceType +{ + SURFACE_DEFAULT, + SURFACE_TARMAC, + SURFACE_GRASS, + SURFACE_GRAVEL, + SURFACE_MUD_DRY, + SURFACE_PAVEMENT, + SURFACE_CAR, + SURFACE_GLASS, + SURFACE_TRANSPARENT_CLOTH, + SURFACE_GARAGE_DOOR, + SURFACE_CAR_PANEL, + SURFACE_THICK_METAL_PLATE, + SURFACE_SCAFFOLD_POLE, + SURFACE_LAMP_POST, + SURFACE_FIRE_HYDRANT, + SURFACE_GIRDER, + SURFACE_METAL_CHAIN_FENCE, + SURFACE_PED, + SURFACE_SAND, + SURFACE_WATER, + SURFACE_WOOD_CRATES, + SURFACE_WOOD_BENCH, + SURFACE_WOOD_SOLID, + SURFACE_RUBBER, + SURFACE_PLASTIC, + SURFACE_HEDGE, + SURFACE_STEEP_CLIFF, + SURFACE_CONTAINER, + SURFACE_NEWS_VENDOR, + SURFACE_WHEELBASE, + SURFACE_CARDBOARDBOX, + SURFACE_TRANSPARENT_STONE, + SURFACE_METAL_GATE, + + // These are illegal + SURFACE_SAND_BEACH, + SURFACE_CONCRETE_BEACH, +}; + +enum +{ + ADHESIVE_RUBBER, + ADHESIVE_HARD, + ADHESIVE_ROAD, + ADHESIVE_LOOSE, + ADHESIVE_WET, + + NUMADHESIVEGROUPS +}; + +struct CColPoint; + +inline bool +IsSeeThrough(uint8 surfType) +{ + switch(surfType) + case SURFACE_GLASS: + case SURFACE_TRANSPARENT_CLOTH: +#if defined(FIX_BUGS) || defined(GTA_PS2) + case SURFACE_METAL_CHAIN_FENCE: + case SURFACE_TRANSPARENT_STONE: + case SURFACE_SCAFFOLD_POLE: +#endif + return true; + return false; +} + +class CSurfaceTable +{ + static float ms_aAdhesiveLimitTable[NUMADHESIVEGROUPS][NUMADHESIVEGROUPS]; +public: + static void Initialise(Const char *filename); + static int GetAdhesionGroup(uint8 surfaceType); + static float GetWetMultiplier(uint8 surfaceType); + static float GetAdhesiveLimit(CColPoint &colpoint); +}; diff --git a/src/core/TimeStep.cpp b/src/core/TimeStep.cpp new file mode 100644 index 0000000..09dae91 --- /dev/null +++ b/src/core/TimeStep.cpp @@ -0,0 +1,5 @@ +#include "TimeStep.h" + +float CTimeStep::ms_fTimeScale = 1.0f; +float CTimeStep::ms_fFramesPerUpdate = 1.0f; +float CTimeStep::ms_fTimeStep = 1.0f; diff --git a/src/core/TimeStep.h b/src/core/TimeStep.h new file mode 100644 index 0000000..6101b4c --- /dev/null +++ b/src/core/TimeStep.h @@ -0,0 +1,10 @@ +#pragma once + +// Pretty sure this class is not used by the game +class CTimeStep +{ +public: + static float ms_fTimeScale; + static float ms_fFramesPerUpdate; + static float ms_fTimeStep; +}; diff --git a/src/core/Timer.cpp b/src/core/Timer.cpp new file mode 100644 index 0000000..e4f5b01 --- /dev/null +++ b/src/core/Timer.cpp @@ -0,0 +1,329 @@ +#define WITHWINDOWS +#include "common.h" +#include "crossplatform.h" + +#include "DMAudio.h" +#include "Record.h" +#include "Timer.h" + +uint32 CTimer::m_snTimeInMilliseconds; +uint32 CTimer::m_snTimeInMillisecondsPauseMode = 1; +uint32 CTimer::m_snTimeInMillisecondsNonClipped; +uint32 CTimer::m_snPreviousTimeInMilliseconds; +uint32 CTimer::m_FrameCounter; +float CTimer::ms_fTimeScale; +float CTimer::ms_fTimeStep; +float CTimer::ms_fTimeStepNonClipped; +bool CTimer::m_UserPause; +bool CTimer::m_CodePause; +#ifdef FIX_BUGS +uint32 CTimer::m_LogicalFrameCounter; +uint32 CTimer::m_LogicalFramesPassed; +#endif + +uint32 _nCyclesPerMS = 1; + +#ifdef _WIN32 +LARGE_INTEGER _oldPerfCounter; +LARGE_INTEGER perfSuspendCounter; +#define RsTimerType uint32 +#else +#define RsTimerType double +#endif + +RsTimerType oldPcTimer; + +RsTimerType suspendPcTimer; + +uint32 suspendDepth; + +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; +#ifdef FIX_BUGS + m_LogicalFrameCounter = 0; + m_LogicalFramesPassed = 0; +#endif + +#ifdef _WIN32 + LARGE_INTEGER perfFreq; + if ( QueryPerformanceFrequency(&perfFreq) ) + { + OutputDebugString("Performance counter available\n"); + _nCyclesPerMS = uint32(perfFreq.QuadPart / 1000); + QueryPerformanceCounter(&_oldPerfCounter); + } + else +#endif + { + 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) +{ + ; +} +#ifdef FIX_BUGS +void CTimer::Update(void) +{ + static double frameTimeLogical = 0.0; + static double frameTimeFraction = 0.0; + static double frameTimeFractionScaled = 0.0; + double frameTime; + double dblUpdInMs; + + m_snPreviousTimeInMilliseconds = m_snTimeInMilliseconds; + +#ifdef _WIN32 + if ( (double)_nCyclesPerMS != 0.0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + + int32 updInCycles = (pc.LowPart - _oldPerfCounter.LowPart); // & 0x7FFFFFFF; pointless + + _oldPerfCounter = pc; + + // bugfix from VC + double updInCyclesScaled = GetIsPaused() ? updInCycles : updInCycles * ms_fTimeScale; + + frameTime = updInCyclesScaled / (double)_nCyclesPerMS; + + dblUpdInMs = (double)updInCycles / (double)_nCyclesPerMS; + } + else +#endif + { + RsTimerType timer = RsTimer(); + + RsTimerType updInMs = timer - oldPcTimer; + + // bugfix from VC + frameTime = GetIsPaused() ? (double)updInMs : (double)updInMs * ms_fTimeScale; + + oldPcTimer = timer; + + dblUpdInMs = (double)updInMs; + } + + // count frames as if we're running at 30 fps + m_LogicalFramesPassed = 0; + frameTimeLogical += dblUpdInMs; + while(frameTimeLogical >= 1000.0 / 30.0) { + frameTimeLogical -= 1000.0 / 30.0; + m_LogicalFramesPassed++; + } + m_LogicalFrameCounter += m_LogicalFramesPassed; + + frameTimeFraction += dblUpdInMs; + frameTimeFractionScaled += frameTime; + + m_snTimeInMillisecondsPauseMode += uint32(frameTimeFraction); + + if ( GetIsPaused() ) + ms_fTimeStep = 0.0f; + else + { + m_snTimeInMilliseconds += uint32(frameTimeFractionScaled); + m_snTimeInMillisecondsNonClipped += uint32(frameTimeFractionScaled); + ms_fTimeStep = frameTime / 1000.0f * 50.0f; + } + frameTimeFraction -= uint32(frameTimeFraction); + frameTimeFractionScaled -= uint32(frameTimeFractionScaled); + + if ( ms_fTimeStep < 0.01f && !GetIsPaused() ) + ms_fTimeStep = 0.01f; + + ms_fTimeStepNonClipped = ms_fTimeStep; + + if ( !CRecordDataForGame::IsPlayingBack() ) + { + ms_fTimeStep = Min(3.0f, ms_fTimeStep); + + if ( (m_snTimeInMilliseconds - m_snPreviousTimeInMilliseconds) > 60 ) + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 60; + } + + if ( CRecordDataForChase::IsRecording() ) + { + ms_fTimeStep = 1.0f; + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 16; + } + + m_FrameCounter++; +} +#else +void CTimer::Update(void) +{ + m_snPreviousTimeInMilliseconds = m_snTimeInMilliseconds; + +#ifdef _WIN32 + if ( (double)_nCyclesPerMS != 0.0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + + int32 updInCycles = (pc.LowPart - _oldPerfCounter.LowPart); // & 0x7FFFFFFF; pointless + + _oldPerfCounter = pc; + + float updInCyclesScaled = updInCycles * ms_fTimeScale; + + double frameTime = updInCyclesScaled / (double)_nCyclesPerMS; + m_snTimeInMillisecondsPauseMode = m_snTimeInMillisecondsPauseMode + frameTime; + + if ( GetIsPaused() ) + ms_fTimeStep = 0.0f; + else + { + m_snTimeInMilliseconds = m_snTimeInMilliseconds + frameTime; + m_snTimeInMillisecondsNonClipped = m_snTimeInMillisecondsNonClipped + frameTime; + ms_fTimeStep = frameTime / 1000.0f * 50.0f; + } + } + else +#endif + { + RsTimerType timer = RsTimer(); + + RsTimerType updInMs = timer - oldPcTimer; + + double frameTime = (double)updInMs * ms_fTimeScale; + + oldPcTimer = timer; + + m_snTimeInMillisecondsPauseMode = m_snTimeInMillisecondsPauseMode + frameTime; + + if ( GetIsPaused() ) + ms_fTimeStep = 0.0f; + else + { + m_snTimeInMilliseconds = m_snTimeInMilliseconds + frameTime; + m_snTimeInMillisecondsNonClipped = m_snTimeInMillisecondsNonClipped + frameTime; + ms_fTimeStep = frameTime / 1000.0f * 50.0f; + } + } + + if ( ms_fTimeStep < 0.01f && !GetIsPaused() ) + ms_fTimeStep = 0.01f; + + ms_fTimeStepNonClipped = ms_fTimeStep; + + if ( !CRecordDataForGame::IsPlayingBack() ) + { + ms_fTimeStep = Min(3.0f, ms_fTimeStep); + + if ( (m_snTimeInMilliseconds - m_snPreviousTimeInMilliseconds) > 60 ) + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 60; + } + + if ( CRecordDataForChase::IsRecording() ) + { + ms_fTimeStep = 1.0f; + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 16; + } + + m_FrameCounter++; +} +#endif + +void CTimer::Suspend(void) +{ + if ( ++suspendDepth > 1 ) + return; + +#ifdef _WIN32 + if ( (double)_nCyclesPerMS != 0.0 ) + QueryPerformanceCounter(&perfSuspendCounter); + else +#endif + suspendPcTimer = RsTimer(); +} + +void CTimer::Resume(void) +{ + if ( --suspendDepth != 0 ) + return; + +#ifdef _WIN32 + if ( (double)_nCyclesPerMS != 0.0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + + _oldPerfCounter.LowPart += pc.LowPart - perfSuspendCounter.LowPart; + } + else +#endif + oldPcTimer += RsTimer() - suspendPcTimer; +} + +uint32 CTimer::GetCyclesPerMillisecond(void) +{ +#ifdef _WIN32 + if (_nCyclesPerMS != 0) + return _nCyclesPerMS; + else +#endif + return 1; +} + +uint32 CTimer::GetCurrentTimeInCycles(void) +{ +#ifdef _WIN32 + if ( _nCyclesPerMS != 0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + return (pc.LowPart - _oldPerfCounter.LowPart); // & 0x7FFFFFFF; pointless + } + else +#endif + 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; +} + +uint32 CTimer::GetCyclesPerFrame() +{ + return 20; +} + diff --git a/src/core/Timer.h b/src/core/Timer.h new file mode 100644 index 0000000..819bd30 --- /dev/null +++ b/src/core/Timer.h @@ -0,0 +1,71 @@ +#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; +#ifdef FIX_BUGS + static uint32 m_LogicalFrameCounter; + static uint32 m_LogicalFramesPassed; +#endif +public: + static bool m_UserPause; + static bool m_CodePause; + + static const float &GetTimeStep(void) { return ms_fTimeStep; } + static void SetTimeStep(float ts) { ms_fTimeStep = ts; } + static float GetTimeStepInSeconds() { return ms_fTimeStep / 50.0f; } + static uint32 GetTimeStepInMilliseconds() { return ms_fTimeStep / 50.0f * 1000.0f; } + static const float &GetTimeStepNonClipped(void) { return ms_fTimeStepNonClipped; } + static float GetTimeStepNonClippedInSeconds(void) { return ms_fTimeStepNonClipped / 50.0f; } + static float GetTimeStepNonClippedInMilliseconds(void) { return ms_fTimeStepNonClipped / 50.0f * 1000.0f; } + static void SetTimeStepNonClipped(float ts) { ms_fTimeStepNonClipped = ts; } + static const uint32 &GetFrameCounter(void) { return m_FrameCounter; } + static void SetFrameCounter(uint32 fc) { m_FrameCounter = fc; } + static const 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 const float &GetTimeScale(void) { return ms_fTimeScale; } + static void SetTimeScale(float ts) { ms_fTimeScale = ts; } + static uint32 GetCyclesPerFrame(); + + static bool GetIsPaused() { return m_UserPause || m_CodePause; } + static bool GetIsUserPaused() { return m_UserPause; } + static bool GetIsCodePaused() { return m_CodePause; } + 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); + + friend bool GenericLoad(void); + friend bool GenericSave(int file); + friend class CMemoryCard; + +#ifdef FIX_BUGS + static float GetDefaultTimeStep(void) { return 50.0f / 30.0f; } + static float GetTimeStepFix(void) { return GetTimeStep() / GetDefaultTimeStep(); } + static uint32 GetLogicalFrameCounter(void) { return m_LogicalFrameCounter; } + static uint32 GetLogicalFramesPassed(void) { return m_LogicalFramesPassed; } +#endif +}; diff --git a/src/core/User.cpp b/src/core/User.cpp new file mode 100644 index 0000000..f906ae4 --- /dev/null +++ b/src/core/User.cpp @@ -0,0 +1,127 @@ +#include "common.h" + + +#include "Hud.h" +#include "PlayerPed.h" +#include "Replay.h" +#include "Text.h" +#include "User.h" +#include "Vehicle.h" +#include "World.h" +#include "Zones.h" + +CPlaceName CUserDisplay::PlaceName; +COnscreenTimer CUserDisplay::OnscnTimer; +CPager CUserDisplay::Pager; +CCurrentVehicle CUserDisplay::CurrentVehicle; + +CPlaceName::CPlaceName() +{ + Init(); +} + +void +CPlaceName::Init() +{ + m_pZone = nil; + m_pZone2 = nil; + m_nAdditionalTimer = 0; +} + +void +CPlaceName::Process() +{ + CVector pos = CWorld::Players[CWorld::PlayerInFocus].GetPos(); + CZone *navigZone = CTheZones::FindSmallestZonePositionType(&pos, ZONE_NAVIG); + CZone *defaultZone = CTheZones::FindSmallestZonePositionType(&pos, ZONE_DEFAULT); + + if (navigZone == nil) m_pZone = nil; + if (defaultZone == nil) m_pZone2 = nil; + + if (navigZone == m_pZone) { + if (defaultZone == m_pZone2 || m_pZone != nil) { + if (navigZone != nil || defaultZone != nil) { + if (m_nAdditionalTimer != 0) + m_nAdditionalTimer--; + } else { + m_nAdditionalTimer = 0; + m_pZone = nil; + m_pZone2 = nil; + } + } else { + m_pZone2 = defaultZone; + m_nAdditionalTimer = 250; + } + } else { + m_pZone = navigZone; + m_nAdditionalTimer = 250; + } + Display(); +} + +void +CPlaceName::Display() +{ + wchar *text; + if (m_pZone != nil) + text = m_pZone->GetTranslatedName(); + else if (m_pZone2 != nil) + text = m_pZone2->GetTranslatedName(); +#ifdef FIX_BUGS + else + text = nil; +#endif + CHud::SetZoneName(text); +} + +CCurrentVehicle::CCurrentVehicle() +{ + Init(); +} + +void +CCurrentVehicle::Init() +{ + m_pCurrentVehicle = nil; +} + +void +CCurrentVehicle::Process() +{ + if (CWorld::Players[CWorld::PlayerInFocus].m_pPed->InVehicle()) + m_pCurrentVehicle = CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pMyVehicle; + else + m_pCurrentVehicle = nil; + Display(); +} + +void +CCurrentVehicle::Display() +{ + wchar *text = nil; + if (m_pCurrentVehicle != nil) + text = TheText.Get(((CVehicleModelInfo*)CModelInfo::GetModelInfo(m_pCurrentVehicle->GetModelIndex()))->m_gameName); + CHud::SetVehicleName(text); +} + +void +CUserDisplay::Init() +{ + PlaceName.Init(); + OnscnTimer.Init(); + Pager.Init(); + CurrentVehicle.Init(); +} + +void +CUserDisplay::Process() +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + PlaceName.Process(); + OnscnTimer.Process(); + Pager.Process(); + CurrentVehicle.Process(); +} diff --git a/src/core/User.h b/src/core/User.h new file mode 100644 index 0000000..153ef57 --- /dev/null +++ b/src/core/User.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Pager.h" +#include "OnscreenTimer.h" + +class CZone; +class CVehicle; + +class CPlaceName +{ + CZone *m_pZone; + CZone *m_pZone2; + int16 m_nAdditionalTimer; +public: + CPlaceName(); + void Init(); + void Process(); + void Display(); +}; + +class CCurrentVehicle +{ + CVehicle *m_pCurrentVehicle; +public: + CCurrentVehicle(); + void Init(); + void Process(); + void Display(); +}; + +class CUserDisplay +{ +public: + static CPlaceName PlaceName; + static COnscreenTimer OnscnTimer; + static CPager Pager; + static CCurrentVehicle CurrentVehicle; + + static void Init(); + static void Process(); +}; diff --git a/src/core/Wanted.cpp b/src/core/Wanted.cpp new file mode 100644 index 0000000..909674d --- /dev/null +++ b/src/core/Wanted.cpp @@ -0,0 +1,492 @@ +#include "common.h" + +#include "Pools.h" +#include "ModelIndices.h" +#include "Timer.h" +#include "World.h" +#include "ZoneCull.h" +#include "Darkel.h" +#include "DMAudio.h" +#include "CopPed.h" +#include "Wanted.h" +#include "General.h" + +int32 CWanted::MaximumWantedLevel = 6; +int32 CWanted::nMaximumWantedLevel = 6400; + +void +CWanted::Initialise() +{ + m_nChaos = 0; + m_nLastUpdateTime = 0; + m_nLastWantedLevelChange = 0; + m_CurrentCops = 0; + m_MaxCops = 0; + m_MaximumLawEnforcerVehicles = 0; + m_RoadblockDensity = 0; + m_bIgnoredByCops = false; + m_bIgnoredByEveryone = false; + m_bSwatRequired = false; + m_bFbiRequired = false; + m_bArmyRequired = false; + m_fCrimeSensitivity = 1.0f; + m_nWantedLevel = 0; + m_CopsBeatingSuspect = 0; + + for (int i = 0; i < ARRAY_SIZE(m_pCops); i++) + m_pCops[i] = nil; + + ClearQdCrimes(); +} + +bool +CWanted::AreSwatRequired() +{ + return m_nWantedLevel == 4 || m_bSwatRequired; +} + +bool +CWanted::AreFbiRequired() +{ + return m_nWantedLevel == 5 || m_bFbiRequired; +} + +bool +CWanted::AreArmyRequired() +{ + return m_nWantedLevel == 6 || m_bArmyRequired; +} + +int32 +CWanted::NumOfHelisRequired() +{ + if (m_bIgnoredByCops || m_bIgnoredByEveryone) + return 0; + + switch (m_nWantedLevel) { + case 3: + case 4: + return 1; + case 5: + case 6: + return 2; + default: + return 0; + } +} + +void +CWanted::SetWantedLevel(int32 level) +{ + if (level > MaximumWantedLevel) + level = MaximumWantedLevel; + + ClearQdCrimes(); + switch (level) { + case 0: + m_nChaos = 0; + break; + case 1: + m_nChaos = 60; + break; + case 2: + m_nChaos = 220; + break; + case 3: + m_nChaos = 420; + break; + case 4: + m_nChaos = 820; + break; + case 5: + m_nChaos = 1620; + break; + case 6: + m_nChaos = 3220; + break; + default: + break; + } + UpdateWantedLevel(); +} + +void +CWanted::SetWantedLevelNoDrop(int32 level) +{ + if (level > m_nWantedLevel) + SetWantedLevel(level); +} + +void +CWanted::SetMaximumWantedLevel(int32 level) +{ + switch(level){ + case 0: + nMaximumWantedLevel = 0; + MaximumWantedLevel = 0; + break; + case 1: + nMaximumWantedLevel = 120; + MaximumWantedLevel = 1; + break; + case 2: + nMaximumWantedLevel = 300; + MaximumWantedLevel = 2; + break; + case 3: + nMaximumWantedLevel = 600; + MaximumWantedLevel = 3; + break; + case 4: + nMaximumWantedLevel = 1200; + MaximumWantedLevel = 4; + break; + case 5: + nMaximumWantedLevel = 2400; + MaximumWantedLevel = 5; + break; + case 6: + nMaximumWantedLevel = 4800; + MaximumWantedLevel = 6; + break; + } +} + +void +CWanted::RegisterCrime(eCrimeType type, const CVector &coors, uint32 id, bool policeDoesntCare) +{ + AddCrimeToQ(type, id, coors, false, policeDoesntCare); +} + +void +CWanted::RegisterCrime_Immediately(eCrimeType type, const CVector &coors, uint32 id, bool policeDoesntCare) +{ +#if defined FIX_SIGNIFICANT_BUGS || defined PEDS_REPORT_CRIMES_ON_PHONE + if (!AddCrimeToQ(type, id, coors, true, policeDoesntCare)) +#else + if (!AddCrimeToQ(type, id, coors, false, policeDoesntCare)) +#endif + ReportCrimeNow(type, coors, policeDoesntCare); +} + +void +CWanted::ClearQdCrimes() +{ + for (int i = 0; i < 16; i++) + m_aCrimes[i].m_nType = CRIME_NONE; +} + +// returns whether the crime had been reported already +bool +CWanted::AddCrimeToQ(eCrimeType type, int32 id, const CVector &coors, bool reported, bool policeDoesntCare) +{ + int i; + + for(i = 0; i < 16; i++) + if(m_aCrimes[i].m_nType == type && m_aCrimes[i].m_nId == id){ + if(m_aCrimes[i].m_bReported) + return true; + if(reported) + m_aCrimes[i].m_bReported = reported; + return false; + } + + for(i = 0; i < 16; i++) + if(m_aCrimes[i].m_nType == CRIME_NONE) + break; + if(i < 16){ + m_aCrimes[i].m_nType = type; + m_aCrimes[i].m_nId = id; + m_aCrimes[i].m_vecPosn = coors; + m_aCrimes[i].m_nTime = CTimer::GetTimeInMilliseconds(); + m_aCrimes[i].m_bReported = reported; + m_aCrimes[i].m_bPoliceDoesntCare = policeDoesntCare; + } + return false; +} + +void +CWanted::ReportCrimeNow(eCrimeType type, const CVector &coors, bool policeDoesntCare) +{ + float sensitivity, chaos; + int wantedLevelDrop; + + if(CDarkel::FrenzyOnGoing()) + sensitivity = m_fCrimeSensitivity*0.3f; + else + sensitivity = m_fCrimeSensitivity; + + wantedLevelDrop = Min(CCullZones::GetWantedLevelDrop(), 100); + + chaos = (1.0f - wantedLevelDrop/100.0f) * sensitivity; + if (policeDoesntCare) + chaos *= 0.333f; + switch(type){ + case CRIME_POSSESSION_GUN: +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + m_nChaos += 5.0f*chaos; +#endif + break; + case CRIME_HIT_PED: + m_nChaos += 5.0f*chaos; + break; + case CRIME_HIT_COP: + m_nChaos += 45.0f*chaos; + break; + case CRIME_SHOOT_PED: + m_nChaos += 30.0f*chaos; + break; + case CRIME_SHOOT_COP: + m_nChaos += 80.0f*chaos; + break; + case CRIME_STEAL_CAR: + m_nChaos += 15.0f*chaos; + break; + case CRIME_RUN_REDLIGHT: + m_nChaos += 10.0f*chaos; + break; + case CRIME_RECKLESS_DRIVING: + m_nChaos += 5.0f*chaos; + break; + case CRIME_SPEEDING: + m_nChaos += 5.0f*chaos; + break; + case CRIME_RUNOVER_PED: + m_nChaos += 18.0f*chaos; + break; + case CRIME_RUNOVER_COP: + m_nChaos += 80.0f*chaos; + break; + case CRIME_SHOOT_HELI: + m_nChaos += 400.0f*chaos; + break; + case CRIME_PED_BURNED: + m_nChaos += 20.0f*chaos; + break; + case CRIME_COP_BURNED: + m_nChaos += 80.0f*chaos; + break; + case CRIME_VEHICLE_BURNED: + m_nChaos += 20.0f*chaos; + break; + case CRIME_DESTROYED_CESSNA: + m_nChaos += 500.0f*chaos; + break; + default: + // Error("Undefined crime type, RegisterCrime, Crime.cpp"); // different file for some reason + Error("Undefined crime type, RegisterCrime, Wanted.cpp"); + } + DMAudio.ReportCrime(type, coors); + UpdateWantedLevel(); +} + +void +CWanted::UpdateWantedLevel() +{ + int32 CurrWantedLevel = m_nWantedLevel; + + if (m_nChaos > nMaximumWantedLevel) + m_nChaos = nMaximumWantedLevel; + + if (m_nChaos >= 0 && m_nChaos < 40) { + m_nWantedLevel = 0; + m_MaximumLawEnforcerVehicles = 0; + m_MaxCops = 0; + m_RoadblockDensity = 0; + } + else if (m_nChaos >= 40 && m_nChaos < 200) { + m_nWantedLevel = 1; + m_MaximumLawEnforcerVehicles = 1; + m_MaxCops = 1; + m_RoadblockDensity = 0; + } + else if (m_nChaos >= 200 && m_nChaos < 400) { + m_nWantedLevel = 2; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 3; + m_RoadblockDensity = 0; + } + else if (m_nChaos >= 400 && m_nChaos < 800) { + m_nWantedLevel = 3; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 4; + m_RoadblockDensity = 4; + } + else if (m_nChaos >= 800 && m_nChaos < 1600) { + m_nWantedLevel = 4; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 6; + m_RoadblockDensity = 8; + } + else if (m_nChaos >= 1600 && m_nChaos < 3200) { + m_nWantedLevel = 5; + m_MaximumLawEnforcerVehicles = 3; + m_MaxCops = 8; + m_RoadblockDensity = 10; + } + else if (m_nChaos >= 3200) { + m_nWantedLevel = 6; + m_MaximumLawEnforcerVehicles = 3; + m_MaxCops = 10; + m_RoadblockDensity = 12; + } + + if (CurrWantedLevel != m_nWantedLevel) + m_nLastWantedLevelChange = CTimer::GetTimeInMilliseconds(); +} + +int32 +CWanted::WorkOutPolicePresence(CVector posn, float radius) +{ + int i; + CPed *ped; + CVehicle *vehicle; + int numPolice = 0; + + i = CPools::GetPedPool()->GetSize(); + while(--i >= 0){ + ped = CPools::GetPedPool()->GetSlot(i); + if(ped && + IsPolicePedModel(ped->GetModelIndex()) && + (posn - ped->GetPosition()).Magnitude() < radius) + numPolice++; + } + + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + vehicle = CPools::GetVehiclePool()->GetSlot(i); + if(vehicle && + vehicle->bIsLawEnforcer && + IsPoliceVehicleModel(vehicle->GetModelIndex()) && + vehicle != FindPlayerVehicle() && + vehicle->GetStatus() != STATUS_ABANDONED && vehicle->GetStatus() != STATUS_WRECKED && + (posn - vehicle->GetPosition()).Magnitude() < radius) + numPolice++; + } + + return numPolice; +} + +void +CWanted::Update(void) +{ + if (CTimer::GetTimeInMilliseconds() - m_nLastUpdateTime > 1000) { + if (m_nWantedLevel > 1) { + m_nLastUpdateTime = CTimer::GetTimeInMilliseconds(); + } else { + float radius = 18.0f; + CVector playerPos = FindPlayerCoors(); + if (WorkOutPolicePresence(playerPos, radius) == 0) { + m_nLastUpdateTime = CTimer::GetTimeInMilliseconds(); + m_nChaos = Max(0, m_nChaos - 1); + UpdateWantedLevel(); + } + } + UpdateCrimesQ(); + bool orderMessedUp = false; + int currCopNum = 0; + bool foundEmptySlot = false; + for (int i = 0; i < ARRAY_SIZE(m_pCops); i++) { + if (m_pCops[i]) { + ++currCopNum; + if (foundEmptySlot) + orderMessedUp = true; + } else { + foundEmptySlot = true; + } + } + if (currCopNum != m_CurrentCops) { + printf("CopPursuit total messed up: re-setting\n"); + m_CurrentCops = currCopNum; + } + if (orderMessedUp) { + printf("CopPursuit pointer list messed up: re-sorting\n"); + bool fixed = true; + for (int i = 0; i < ARRAY_SIZE(m_pCops); i++) { + if (!m_pCops[i]) { + for (int j = i; j < ARRAY_SIZE(m_pCops); j++) { + if (m_pCops[j]) { + m_pCops[i] = m_pCops[j]; + m_pCops[j] = nil; + fixed = false; + break; + } + } + if (fixed) + break; + } + } + } + } +} + +void +CWanted::ResetPolicePursuit(void) +{ + for(int i = 0; i < ARRAY_SIZE(m_pCops); i++) { + CCopPed *cop = m_pCops[i]; + if (!cop) + continue; + + cop->m_bIsInPursuit = false; + cop->m_objective = OBJECTIVE_NONE; + cop->m_prevObjective = OBJECTIVE_NONE; + cop->m_nLastPedState = PED_NONE; + if (!cop->DyingOrDead()) { + cop->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + } + m_pCops[i] = nil; + } + m_CurrentCops = 0; +} + +void +CWanted::Reset(void) +{ + ResetPolicePursuit(); + Initialise(); +} + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +bool +CrimeShouldBeReportedOnPhone(eCrimeType crime) +{ + switch (crime) { + case CRIME_POSSESSION_GUN: + case CRIME_HIT_PED: + case CRIME_HIT_COP: + case CRIME_SHOOT_PED: + case CRIME_SHOOT_COP: + case CRIME_STEAL_CAR: + case CRIME_RECKLESS_DRIVING: + case CRIME_RUNOVER_PED: + case CRIME_RUNOVER_COP: + case CRIME_PED_BURNED: + case CRIME_COP_BURNED: + case CRIME_VEHICLE_BURNED: + return true; + default: + return false; + } +} +#endif + +void +CWanted::UpdateCrimesQ(void) +{ + for(int i = 0; i < ARRAY_SIZE(m_aCrimes); i++) { + + CCrimeBeingQd &crime = m_aCrimes[i]; + if (crime.m_nType != CRIME_NONE) { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + if (!CrimeShouldBeReportedOnPhone(crime.m_nType)) +#endif + if (CTimer::GetTimeInMilliseconds() > crime.m_nTime + 500 && !crime.m_bReported) { + ReportCrimeNow(crime.m_nType, crime.m_vecPosn, crime.m_bPoliceDoesntCare); + crime.m_bReported = true; + } + if (CTimer::GetTimeInMilliseconds() > crime.m_nTime + 10000) + crime.m_nType = CRIME_NONE; + } + } +} diff --git a/src/core/Wanted.h b/src/core/Wanted.h new file mode 100644 index 0000000..9f08e75 --- /dev/null +++ b/src/core/Wanted.h @@ -0,0 +1,58 @@ +#pragma once + +#include "Crime.h" + +class CEntity; +class CCopPed; + +class CWanted +{ +public: + int32 m_nChaos; + int32 m_nLastUpdateTime; + uint32 m_nLastWantedLevelChange; + float m_fCrimeSensitivity; + uint8 m_CurrentCops; + uint8 m_MaxCops; + uint8 m_MaximumLawEnforcerVehicles; + uint8 m_CopsBeatingSuspect; + int16 m_RoadblockDensity; + uint8 m_bIgnoredByCops : 1; + uint8 m_bIgnoredByEveryone : 1; + uint8 m_bSwatRequired : 1; + uint8 m_bFbiRequired : 1; + uint8 m_bArmyRequired : 1; + int32 m_nWantedLevel; + CCrimeBeingQd m_aCrimes[16]; + CCopPed *m_pCops[10]; + + static int32 MaximumWantedLevel; + static int32 nMaximumWantedLevel; + +public: + void Initialise(); + bool AreSwatRequired(); + bool AreFbiRequired(); + bool AreArmyRequired(); + int32 NumOfHelisRequired(); + void SetWantedLevel(int32); + void SetWantedLevelNoDrop(int32 level); + int32 GetWantedLevel() { return m_nWantedLevel; } + void RegisterCrime(eCrimeType type, const CVector &coors, uint32 id, bool policeDoesntCare); + void RegisterCrime_Immediately(eCrimeType type, const CVector &coors, uint32 id, bool policeDoesntCare); + void ClearQdCrimes(); + bool AddCrimeToQ(eCrimeType type, int32 id, const CVector &pos, bool reported, bool policeDoesntCare); + void ReportCrimeNow(eCrimeType type, const CVector &coors, bool policeDoesntCare); + void UpdateWantedLevel(); + void Reset(); + void ResetPolicePursuit(); + void UpdateCrimesQ(); + void Update(); + + bool IsIgnored(void) { return m_bIgnoredByCops || m_bIgnoredByEveryone; } + + static int32 WorkOutPolicePresence(CVector posn, float radius); + static void SetMaximumWantedLevel(int32 level); +}; + +VALIDATE_SIZE(CWanted, 0x204); diff --git a/src/core/World.cpp b/src/core/World.cpp new file mode 100644 index 0000000..1c34a63 --- /dev/null +++ b/src/core/World.cpp @@ -0,0 +1,2162 @@ +#include "common.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "CopPed.h" +#include "CutsceneMgr.h" +#include "DMAudio.h" +#include "EventList.h" +#include "Explosion.h" +#include "Fire.h" +#include "Garages.h" +#include "Glass.h" +#include "Messages.h" +#include "ModelIndices.h" +#include "ParticleObject.h" +#include "Population.h" +#include "ProjectileInfo.h" +#include "Record.h" +#include "References.h" +#include "Replay.h" +#include "RpAnimBlend.h" +#include "Shadows.h" +#include "TempColModels.h" +#include "WaterLevel.h" +#include "World.h" + + +#define OBJECT_REPOSITION_OFFSET_Z 2.0f + +CColPoint gaTempSphereColPoints[MAX_COLLISION_POINTS]; + +CPtrList CWorld::ms_bigBuildingsList[NUM_LEVELS]; +CPtrList CWorld::ms_listMovingEntityPtrs; +CSector CWorld::ms_aSectors[NUMSECTORS_Y][NUMSECTORS_X]; +uint16 CWorld::ms_nCurrentScanCode; + +uint8 CWorld::PlayerInFocus; +CPlayerInfo CWorld::Players[NUMPLAYERS]; +bool CWorld::bNoMoreCollisionTorque; +CEntity *CWorld::pIgnoreEntity; +bool CWorld::bIncludeDeadPeds; +bool CWorld::bSecondShift; +bool CWorld::bForceProcessControl; +bool CWorld::bProcessCutsceneOnly; + +bool CWorld::bDoingCarCollisions; +bool CWorld::bIncludeCarTyres; + +void +CWorld::Initialise() +{ +#if GTA_VERSION <= GTA3_PS2_160 + CPools::Initialise(); +#endif + pIgnoreEntity = nil; + bDoingCarCollisions = false; + bSecondShift = false; + bNoMoreCollisionTorque = false; + bProcessCutsceneOnly = false; + bIncludeDeadPeds = false; + bForceProcessControl = false; + bIncludeCarTyres = false; +} + +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->GetIsStatic()) ((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->GetIsStatic()) ((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; + } +} + +void +CWorld::ClearExcitingStuffFromArea(const CVector &pos, float radius, bool bRemoveProjectilesAndTidyUpShadows) +{ + CPedPool *pedPool = CPools::GetPedPool(); + for(int32 i = 0; i < pedPool->GetSize(); i++) { + CPed *pPed = pedPool->GetSlot(i); + if(pPed && !pPed->IsPlayer() && pPed->CanBeDeleted() && + CVector2D(pPed->GetPosition() - pos).MagnitudeSqr() < SQR(radius)) { + CPopulation::RemovePed(pPed); + } + } + CVehiclePool *VehiclePool = CPools::GetVehiclePool(); + for(int32 i = 0; i < VehiclePool->GetSize(); i++) { + CVehicle *pVehicle = VehiclePool->GetSlot(i); + if(pVehicle && CVector2D(pVehicle->GetPosition() - pos).MagnitudeSqr() < SQR(radius) && + !pVehicle->bIsLocked && pVehicle->CanBeDeleted()) { + if(pVehicle->pDriver) { + CPopulation::RemovePed(pVehicle->pDriver); + pVehicle->pDriver = nil; + } + for(int32 j = 0; j < pVehicle->m_nNumMaxPassengers; ++j) { + if(pVehicle->pPassengers[j]) { + CPopulation::RemovePed(pVehicle->pPassengers[j]); + pVehicle->pPassengers[j] = nil; + --pVehicle->m_nNumPassengers; + } + } + CCarCtrl::RemoveFromInterestingVehicleList(pVehicle); + Remove(pVehicle); + delete pVehicle; + } + } + CObject::DeleteAllTempObjectsInArea(pos, radius); + gFireManager.ExtinguishPoint(pos, radius); + ExtinguishAllCarFiresInArea(pos, radius); + CExplosion::RemoveAllExplosionsInArea(pos, radius); + if(bRemoveProjectilesAndTidyUpShadows) { + CProjectileInfo::RemoveAllProjectiles(); + CShadows::TidyUpShadows(); + } +} + +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 = GetSectorIndexY(point1.y); + xend = GetSectorIndexX(point2.x); + yend = GetSectorIndexY(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) { +#ifdef PED_SKIN + if(IsClumpSkinned(e->GetClump())) + colmodel = ((CPedModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex()))->AnimatePedColModelSkinned(e->GetClump()); + else +#endif + if(((CPed *)e)->UseGroundColModel()) + colmodel = &CTempColModels::ms_colModelPedGroundHit; + else +#ifdef ANIMATE_PED_COL_MODEL + colmodel = CPedModelInfo::AnimatePedColModel( + ((CPedModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex())) + ->GetHitColModel(), + RpClumpGetFrame(e->GetClump())); +#else + colmodel = + ((CPedModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex())) + ->GetHitColModel(); +#endif + } else + colmodel = nil; + } else if(e->bUsesCollision) + colmodel = CModelInfo::GetColModel(e->GetModelIndex()); + + if(colmodel && CCollision::ProcessLineOfSight(line, e->GetMatrix(), *colmodel, point, mindist, + 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 ProcessVerticalLineSector(*GetSector(GetSectorIndexX(point1.x), GetSectorIndexY(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::GetColModel(e->GetModelIndex()); + if(CCollision::ProcessVerticalLine(line, e->GetMatrix(), *colmodel, point, mindist, + 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 = GetSectorIndexY(point1.y); + xend = GetSectorIndexX(point2.x); + yend = GetSectorIndexY(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::GetColModel(e->GetModelIndex()); + + if(CCollision::TestLineOfSight(line, e->GetMatrix(), *colmodel, ignoreSeeThrough)) + return false; + } + } + } + + return true; +} + +void +CWorld::FindObjectsInRangeSectorList(CPtrList &list, Const CVector ¢re, float radius, bool ignoreZ, int16 *numObjects, + int16 lastObject, CEntity **objects) +{ + float radiusSqr = radius * radius; + float objDistSqr; + + for(CPtrNode *node = list.first; node; node = node->next) { + CEntity *object = (CEntity *)node->item; + if(object->m_scanCode != GetCurrentScanCode()) { + object->m_scanCode = GetCurrentScanCode(); + + CVector diff = centre - object->GetPosition(); + if(ignoreZ) + objDistSqr = diff.MagnitudeSqr2D(); + else + objDistSqr = diff.MagnitudeSqr(); + + if(objDistSqr < radiusSqr && *numObjects < lastObject) { + if(objects) { objects[*numObjects] = object; } + (*numObjects)++; + } + } + } +} + +void +CWorld::FindObjectsInRange(Const CVector ¢re, float radius, bool ignoreZ, int16 *numObjects, int16 lastObject, + CEntity **objects, bool checkBuildings, bool checkVehicles, bool checkPeds, + bool checkObjects, bool checkDummies) +{ + int minX = GetSectorIndexX(centre.x - radius); + if(minX <= 0) minX = 0; + + int minY = GetSectorIndexY(centre.y - radius); + if(minY <= 0) minY = 0; + + int maxX = GetSectorIndexX(centre.x + radius); +#ifdef FIX_BUGS + if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1; +#else + if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X; +#endif + + int maxY = GetSectorIndexY(centre.y + radius); +#ifdef FIX_BUGS + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; +#else + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y; +#endif + + AdvanceCurrentScanCode(); + + *numObjects = 0; + for(int curY = minY; curY <= maxY; curY++) { + for(int curX = minX; curX <= maxX; curX++) { + CSector *sector = GetSector(curX, curY); + if(checkBuildings) { + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_BUILDINGS], centre, radius, + ignoreZ, numObjects, lastObject, objects); + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], centre, + radius, ignoreZ, numObjects, lastObject, objects); + } + if(checkVehicles) { + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_VEHICLES], centre, radius, + ignoreZ, numObjects, lastObject, objects); + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], centre, + radius, ignoreZ, numObjects, lastObject, objects); + } + if(checkPeds) { + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_PEDS], centre, radius, ignoreZ, + numObjects, lastObject, objects); + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_PEDS_OVERLAP], centre, radius, + ignoreZ, numObjects, lastObject, objects); + } + if(checkObjects) { + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_OBJECTS], centre, radius, + ignoreZ, numObjects, lastObject, objects); + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], centre, + radius, ignoreZ, numObjects, lastObject, objects); + } + if(checkDummies) { + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_DUMMIES], centre, radius, + ignoreZ, numObjects, lastObject, objects); + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], centre, + radius, ignoreZ, numObjects, lastObject, objects); + } + } + } +} + +void +CWorld::FindObjectsOfTypeInRangeSectorList(uint32 modelId, CPtrList &list, const CVector &position, float radius, + bool bCheck2DOnly, int16 *nEntitiesFound, int16 maxEntitiesToFind, + CEntity **aEntities) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + if(pEntity->m_scanCode != GetCurrentScanCode()) { + pEntity->m_scanCode = GetCurrentScanCode(); + if (modelId == pEntity->GetModelIndex()) { + float fMagnitude = 0.0f; + if(bCheck2DOnly) + fMagnitude = (position - pEntity->GetPosition()).MagnitudeSqr2D(); + else + fMagnitude = (position - pEntity->GetPosition()).MagnitudeSqr(); + if(fMagnitude < radius * radius && *nEntitiesFound < maxEntitiesToFind) { + if(aEntities) aEntities[*nEntitiesFound] = pEntity; + ++*nEntitiesFound; + } + } + } + } +} + +void +CWorld::FindObjectsOfTypeInRange(uint32 modelId, const CVector &position, float radius, bool bCheck2DOnly, + int16 *nEntitiesFound, int16 maxEntitiesToFind, CEntity **aEntities, bool bBuildings, + bool bVehicles, bool bPeds, bool bObjects, bool bDummies) +{ + AdvanceCurrentScanCode(); + *nEntitiesFound = 0; + const CVector2D vecSectorStartPos(position.x - radius, position.y - radius); + const CVector2D vecSectorEndPos(position.x + radius, position.y + radius); + const int32 nStartX = Max(GetSectorIndexX(vecSectorStartPos.x), 0); + const int32 nStartY = Max(GetSectorIndexY(vecSectorStartPos.y), 0); + const int32 nEndX = Min(GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + if(bBuildings) { + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_BUILDINGS], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], position, radius, + bCheck2DOnly, nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bVehicles) { + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_VEHICLES], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], position, radius, + bCheck2DOnly, nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bPeds) { + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_PEDS], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bObjects) { + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_OBJECTS], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], position, radius, + bCheck2DOnly, nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bDummies) { + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_DUMMIES], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], position, radius, + bCheck2DOnly, nEntitiesFound, maxEntitiesToFind, aEntities); + } + } + } +} + +CEntity * +CWorld::TestSphereAgainstWorld(CVector centre, float radius, CEntity *entityToIgnore, bool checkBuildings, + bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, + bool ignoreSomeObjects) +{ + CEntity *foundE = nil; + + int minX = GetSectorIndexX(centre.x - radius); + if(minX <= 0) minX = 0; + + int minY = GetSectorIndexY(centre.y - radius); + if(minY <= 0) minY = 0; + + int maxX = GetSectorIndexX(centre.x + radius); +#ifdef FIX_BUGS + if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1; +#else + if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X; +#endif + + int maxY = GetSectorIndexY(centre.y + radius); +#ifdef FIX_BUGS + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; +#else + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y; +#endif + + AdvanceCurrentScanCode(); + + for(int curY = minY; curY <= maxY; curY++) { + for(int curX = minX; curX <= maxX; curX++) { + CSector *sector = GetSector(curX, curY); + if(checkBuildings) { + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_BUILDINGS], centre, + radius, entityToIgnore, false); + if(foundE) return foundE; + + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], + centre, radius, entityToIgnore, false); + if(foundE) return foundE; + } + if(checkVehicles) { + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_VEHICLES], centre, + radius, entityToIgnore, false); + if(foundE) return foundE; + + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], + centre, radius, entityToIgnore, false); + if(foundE) return foundE; + } + if(checkPeds) { + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_PEDS], centre, radius, + entityToIgnore, false); + if(foundE) return foundE; + + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_PEDS_OVERLAP], centre, + radius, entityToIgnore, false); + if(foundE) return foundE; + } + if(checkObjects) { + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_OBJECTS], centre, + radius, entityToIgnore, ignoreSomeObjects); + if(foundE) return foundE; + + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], + centre, radius, entityToIgnore, ignoreSomeObjects); + if(foundE) return foundE; + } + if(checkDummies) { + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_DUMMIES], centre, + radius, entityToIgnore, false); + if(foundE) return foundE; + + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], + centre, radius, entityToIgnore, false); + if(foundE) return foundE; + } + } + } + return foundE; +} + +CEntity * +CWorld::TestSphereAgainstSectorList(CPtrList &list, CVector spherePos, float radius, CEntity *entityToIgnore, + bool ignoreSomeObjects) +{ + static CColModel OurColModel; + + OurColModel.boundingSphere.center.x = 0.0f; + OurColModel.boundingSphere.center.y = 0.0f; + OurColModel.boundingSphere.center.z = 0.0f; + OurColModel.boundingSphere.radius = radius; + OurColModel.boundingBox.min.x = -radius; + OurColModel.boundingBox.min.y = -radius; + OurColModel.boundingBox.min.z = -radius; + OurColModel.boundingBox.max.x = radius; + OurColModel.boundingBox.max.y = radius; + OurColModel.boundingBox.max.z = radius; + OurColModel.numSpheres = 1; + OurColModel.spheres = &OurColModel.boundingSphere; + OurColModel.numLines = 0; + OurColModel.numBoxes = 0; + OurColModel.numTriangles = 0; + OurColModel.ownsCollisionVolumes = false; + + CMatrix sphereMat; + sphereMat.SetTranslate(spherePos); + + for(CPtrNode *node = list.first; node; node = node->next) { + CEntity *e = (CEntity *)node->item; + + if(e->m_scanCode != GetCurrentScanCode()) { + e->m_scanCode = GetCurrentScanCode(); + + if(e != entityToIgnore && e->bUsesCollision && + !(ignoreSomeObjects && CameraToIgnoreThisObject(e))) { +#ifdef FIX_BUGS + CVector diff = spherePos - e->GetBoundCentre(); +#else + CVector diff = spherePos - e->GetPosition(); +#endif + float distance = diff.Magnitude(); + + if(e->GetBoundRadius() + radius > distance) { + CColModel *eCol = CModelInfo::GetColModel(e->GetModelIndex()); + int collidedSpheres = + CCollision::ProcessColModels(sphereMat, OurColModel, e->GetMatrix(), *eCol, + gaTempSphereColPoints, nil, nil); + + if(collidedSpheres != 0 || + (e->IsVehicle() && ((CVehicle *)e)->m_vehType == VEHICLE_TYPE_CAR && e->GetModelIndex() != MI_DODO && + radius + eCol->boundingBox.max.x > distance)) { + return e; + } + } + } + } + } + + return nil; +} + +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; + } +} + +void +CWorld::RemoveReferencesToDeletedObject(CEntity *pDeletedObject) +{ + int32 i = CPools::GetPedPool()->GetSize(); + while(--i >= 0) { + CPed *pPed = CPools::GetPedPool()->GetSlot(i); + if(pPed && pPed != pDeletedObject) { + pPed->RemoveRefsToEntity(pDeletedObject); + if(pPed->m_pCurrentPhysSurface == pDeletedObject) pPed->m_pCurrentPhysSurface = nil; + } + } + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0) { + CVehicle *pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if(pVehicle && pVehicle != pDeletedObject) { + pVehicle->RemoveRefsToEntity(pDeletedObject); + pVehicle->RemoveRefsToVehicle(pDeletedObject); + } + } + i = CPools::GetObjectPool()->GetSize(); + while(--i >= 0) { + CObject *pObject = CPools::GetObjectPool()->GetSlot(i); + if(pObject && pObject != pDeletedObject) { pObject->RemoveRefsToEntity(pDeletedObject); } + } +} + +void +CWorld::FindObjectsKindaColliding(const CVector &position, float radius, bool bCheck2DOnly, int16 *nCollidingEntities, + int16 maxEntitiesToFind, CEntity **aEntities, bool bBuildings, bool bVehicles, + bool bPeds, bool bObjects, bool bDummies) +{ + AdvanceCurrentScanCode(); + *nCollidingEntities = 0; + const CVector2D vecSectorStartPos(position.x - radius, position.y - radius); + const CVector2D vecSectorEndPos(position.x + radius, position.y + radius); + const int32 nStartX = Max(GetSectorIndexX(vecSectorStartPos.x), 0); + const int32 nStartY = Max(GetSectorIndexY(vecSectorStartPos.y), 0); + const int32 nEndX = Min(GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + if(bBuildings) { + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_BUILDINGS], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + } + if(bVehicles) { + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + } + if(bPeds) { + FindObjectsKindaCollidingSectorList(pSector->m_lists[ENTITYLIST_PEDS], position, + radius, bCheck2DOnly, nCollidingEntities, + maxEntitiesToFind, aEntities); + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + } + if(bObjects) { + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + } + if(bDummies) { + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_DUMMIES], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + } + } + } +} + +void +CWorld::FindObjectsKindaCollidingSectorList(CPtrList &list, const CVector &position, float radius, bool bCheck2DOnly, + int16 *nCollidingEntities, int16 maxEntitiesToFind, CEntity **aEntities) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + if(pEntity->m_scanCode != GetCurrentScanCode()) { + pEntity->m_scanCode = GetCurrentScanCode(); + float fMagnitude = 0.0f; + if(bCheck2DOnly) + fMagnitude = (position - pEntity->GetPosition()).Magnitude2D(); + else + fMagnitude = (position - pEntity->GetPosition()).Magnitude(); + if(pEntity->GetBoundRadius() + radius > fMagnitude && *nCollidingEntities < maxEntitiesToFind) { + if(aEntities) aEntities[*nCollidingEntities] = pEntity; + ++*nCollidingEntities; + } + } + } +} + +void +CWorld::FindObjectsIntersectingCube(const CVector &vecStartPos, const CVector &vecEndPos, int16 *nIntersecting, + int16 maxEntitiesToFind, CEntity **aEntities, bool bBuildings, bool bVehicles, + bool bPeds, bool bObjects, bool bDummies) +{ + AdvanceCurrentScanCode(); + *nIntersecting = 0; + const int32 nStartX = Max(GetSectorIndexX(vecStartPos.x), 0); + const int32 nStartY = Max(GetSectorIndexY(vecStartPos.y), 0); + const int32 nEndX = Min(GetSectorIndexX(vecEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecEndPos.y), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + if(bBuildings) { + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_BUILDINGS], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], vecStartPos, vecEndPos, + nIntersecting, maxEntitiesToFind, aEntities); + } + if(bVehicles) { + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_VEHICLES], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], vecStartPos, vecEndPos, + nIntersecting, maxEntitiesToFind, aEntities); + } + if(bPeds) { + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_PEDS], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + } + if(bObjects) { + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_OBJECTS], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + } + if(bDummies) { + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_DUMMIES], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + } + } + } +} + +void +CWorld::FindObjectsIntersectingCubeSectorList(CPtrList &list, const CVector &vecStartPos, const CVector &vecEndPos, + int16 *nIntersecting, int16 maxEntitiesToFind, CEntity **aEntities) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + if(pEntity->m_scanCode != GetCurrentScanCode()) { + pEntity->m_scanCode = GetCurrentScanCode(); + float fRadius = pEntity->GetBoundRadius(); + const CVector &entityPos = pEntity->GetPosition(); + if(fRadius + entityPos.x >= vecStartPos.x && entityPos.x - fRadius <= vecEndPos.x && + fRadius + entityPos.y >= vecStartPos.y && entityPos.y - fRadius <= vecEndPos.y && + fRadius + entityPos.z >= vecStartPos.z && entityPos.z - fRadius <= vecEndPos.z && + *nIntersecting < maxEntitiesToFind) { + if(aEntities) aEntities[*nIntersecting] = pEntity; + ++*nIntersecting; + } + } + } +} + +void +CWorld::FindObjectsIntersectingAngledCollisionBox(const CColBox &boundingBox, const CMatrix &matrix, + const CVector &position, float fStartX, float fStartY, float fEndX, + float fEndY, int16 *nEntitiesFound, int16 maxEntitiesToFind, + CEntity **aEntities, bool bBuildings, bool bVehicles, bool bPeds, + bool bObjects, bool bDummies) +{ + AdvanceCurrentScanCode(); + *nEntitiesFound = 0; + const int32 nStartX = Max(GetSectorIndexX(fStartX), 0); + const int32 nStartY = Max(GetSectorIndexY(fStartY), 0); + const int32 nEndX = Min(GetSectorIndexX(fEndX), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(fEndY), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + if(bBuildings) { + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_BUILDINGS], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bVehicles) { + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bPeds) { + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_PEDS], boundingBox, matrix, position, nEntitiesFound, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bObjects) { + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS], boundingBox, matrix, position, nEntitiesFound, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bDummies) { + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_DUMMIES], boundingBox, matrix, position, nEntitiesFound, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + } + } +} + +void +CWorld::FindObjectsIntersectingAngledCollisionBoxSectorList(CPtrList &list, const CColBox &boundingBox, + const CMatrix &matrix, const CVector &position, + int16 *nEntitiesFound, int16 maxEntitiesToFind, + CEntity **aEntities) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + if(pEntity->m_scanCode != GetCurrentScanCode()) { + pEntity->m_scanCode = GetCurrentScanCode(); + CColSphere sphere; + CVector vecDistance = pEntity->GetPosition() - position; + sphere.radius = pEntity->GetBoundRadius(); + sphere.center = Multiply3x3(vecDistance, matrix); + if(CCollision::TestSphereBox(sphere, boundingBox) && *nEntitiesFound < maxEntitiesToFind) { + if(aEntities) aEntities[*nEntitiesFound] = pEntity; + ++*nEntitiesFound; + } + } + } +} + +void +CWorld::FindMissionEntitiesIntersectingCube(const CVector &vecStartPos, const CVector &vecEndPos, int16 *nIntersecting, + int16 maxEntitiesToFind, CEntity **aEntities, bool bVehicles, bool bPeds, + bool bObjects) +{ + AdvanceCurrentScanCode(); + *nIntersecting = 0; + const int32 nStartX = Max(GetSectorIndexX(vecStartPos.x), 0); + const int32 nStartY = Max(GetSectorIndexY(vecStartPos.y), 0); + const int32 nEndX = Min(GetSectorIndexX(vecEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecEndPos.y), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + if(bVehicles) { + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities, true, false); + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], vecStartPos, vecEndPos, + nIntersecting, maxEntitiesToFind, aEntities, true, false); + } + if(bPeds) { + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_PEDS], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities, false, true); + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities, false, true); + } + if(bObjects) { + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities, false, false); + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities, false, false); + } + } + } +} + +void +CWorld::FindMissionEntitiesIntersectingCubeSectorList(CPtrList &list, const CVector &vecStartPos, + const CVector &vecEndPos, int16 *nIntersecting, + int16 maxEntitiesToFind, CEntity **aEntities, bool bIsVehicleList, + bool bIsPedList) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + if(pEntity->m_scanCode != GetCurrentScanCode()) { + pEntity->m_scanCode = GetCurrentScanCode(); + bool bIsMissionEntity = false; + if(bIsVehicleList) + bIsMissionEntity = ((CVehicle *)pEntity)->VehicleCreatedBy == MISSION_VEHICLE; + else if(bIsPedList) + bIsMissionEntity = ((CPed *)pEntity)->CharCreatedBy == MISSION_CHAR; + else + bIsMissionEntity = ((CObject *)pEntity)->ObjectCreatedBy == MISSION_OBJECT; + float fRadius = pEntity->GetBoundRadius(); + const CVector &entityPos = pEntity->GetPosition(); + if(bIsMissionEntity && fRadius + entityPos.x >= vecStartPos.x && + entityPos.x - fRadius <= vecEndPos.x && fRadius + entityPos.y >= vecStartPos.y && + entityPos.y - fRadius <= vecEndPos.y && fRadius + entityPos.z >= vecStartPos.z && + entityPos.z - fRadius <= vecEndPos.z && *nIntersecting < maxEntitiesToFind) { + if(aEntities) aEntities[*nIntersecting] = pEntity; + ++*nIntersecting; + } + } + } +} + +void +CWorld::ClearCarsFromArea(float x1, float y1, float z1, float x2, float y2, float z2) +{ + CVehiclePool *pVehiclePool = CPools::GetVehiclePool(); + for(int32 i = 0; i < pVehiclePool->GetSize(); i++) { + CVehicle *pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if(pVehicle) { + const CVector &position = pVehicle->GetPosition(); + if(position.x >= x1 && position.x <= x2 && position.y >= y1 && position.y <= y2 && + position.z >= z1 && position.z <= z2 && !pVehicle->bIsLocked && pVehicle->CanBeDeleted()) { + if(pVehicle->pDriver) { + CPopulation::RemovePed(pVehicle->pDriver); + pVehicle->pDriver = nil; + } + for(int32 j = 0; j < pVehicle->m_nNumMaxPassengers; ++j) { + if(pVehicle->pPassengers[j]) { + CPopulation::RemovePed(pVehicle->pPassengers[j]); + pVehicle->pPassengers[j] = nil; + --pVehicle->m_nNumPassengers; + } + } + CCarCtrl::RemoveFromInterestingVehicleList(pVehicle); + Remove(pVehicle); + delete pVehicle; + } + } + } +} + +void +CWorld::ClearPedsFromArea(float x1, float y1, float z1, float x2, float y2, float z2) +{ + CPedPool *pPedPool = CPools::GetPedPool(); + for(int32 i = 0; i < pPedPool->GetSize(); i++) { + CPed *pPed = CPools::GetPedPool()->GetSlot(i); + if(pPed) { + const CVector &position = pPed->GetPosition(); + if(!pPed->IsPlayer() && pPed->CanBeDeleted() && position.x >= x1 && position.x <= x2 && + position.y >= y1 && position.y <= y2 && position.z >= z1 && position.z <= z2) { + CPopulation::RemovePed(pPed); + } + } + } +} + +void +CWorld::CallOffChaseForArea(float x1, float y1, float x2, float y2) +{ + AdvanceCurrentScanCode(); + float fStartX = x1 - 10.0f; + float fStartY = y1 - 10.0f; + float fEndX = x2 + 10.0f; + float fEndY = y2 + 10.0f; + const int32 nStartX = Max(GetSectorIndexX(fStartX), 0); + const int32 nStartY = Max(GetSectorIndexY(fStartY), 0); + const int32 nEndX = Min(GetSectorIndexX(fEndX), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(fEndY), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + CallOffChaseForAreaSectorListVehicles(pSector->m_lists[ENTITYLIST_VEHICLES], x1, y1, x2, + y2, fStartX, fStartY, fEndX, fEndY); + CallOffChaseForAreaSectorListVehicles(pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], x1, + y1, x2, y2, fStartX, fStartY, fEndX, fEndY); + CallOffChaseForAreaSectorListPeds(pSector->m_lists[ENTITYLIST_PEDS], x1, y1, x2, y2); + CallOffChaseForAreaSectorListPeds(pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], x1, y1, x2, + y2); + } + } +} + +void +CWorld::CallOffChaseForAreaSectorListVehicles(CPtrList &list, float x1, float y1, float x2, float y2, float fStartX, + float fStartY, float fEndX, float fEndY) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CVehicle *pVehicle = (CVehicle *)pNode->item; + if(pVehicle->m_scanCode != GetCurrentScanCode()) { + pVehicle->m_scanCode = GetCurrentScanCode(); + const CVector &vehiclePos = pVehicle->GetPosition(); + uint8 carMission = pVehicle->AutoPilot.m_nCarMission; + if(pVehicle != FindPlayerVehicle() && vehiclePos.x > fStartX && vehiclePos.x < fEndX && + vehiclePos.y > fStartY && vehiclePos.y < fEndY && pVehicle->bIsLawEnforcer && + (carMission == MISSION_RAMPLAYER_FARAWAY || carMission == MISSION_RAMPLAYER_CLOSE || + carMission == MISSION_BLOCKPLAYER_FARAWAY || carMission == MISSION_BLOCKPLAYER_CLOSE)) { + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2000; + CColModel *pColModel = pVehicle->GetColModel(); + bool bInsideSphere = false; + for(int32 i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pVehicle->GetMatrix() * pColModel->spheres[i].center; + float fRadius = pColModel->spheres[i].radius; + if(pos.x + fRadius > x1 && pos.x - fRadius < x2 && pos.y + fRadius > y1 && + pos.y - fRadius < y2) + bInsideSphere = true; + // Maybe break the loop when bInsideSphere is set to true? + } + if(bInsideSphere) { + if(pVehicle->GetPosition().x <= (x1 + x2) * 0.5f) + pVehicle->m_vecMoveSpeed.x = Min(pVehicle->m_vecMoveSpeed.x, 0.0f); + else + pVehicle->m_vecMoveSpeed.x = Max(pVehicle->m_vecMoveSpeed.x, 0.0f); + if(pVehicle->GetPosition().y <= (y1 + y2) * 0.5f) + pVehicle->m_vecMoveSpeed.y = Min(pVehicle->m_vecMoveSpeed.y, 0.0f); + else + pVehicle->m_vecMoveSpeed.y = Max(pVehicle->m_vecMoveSpeed.y, 0.0f); + } + } + } + } +} + +void +CWorld::CallOffChaseForAreaSectorListPeds(CPtrList &list, float x1, float y1, float x2, float y2) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CPed *pPed = (CPed *)pNode->item; + const CVector &pedPos = pPed->GetPosition(); + if(pPed->m_scanCode != GetCurrentScanCode()) { + pPed->m_scanCode = GetCurrentScanCode(); + if(pPed != FindPlayerPed() && pPed->m_leader != FindPlayerPed() && pedPos.x > x1 && + pedPos.x < x2 && pedPos.y > y1 && pedPos.y < y2 && + (pPed->m_pedInObjective == FindPlayerPed() || + pPed->m_carInObjective && pPed->m_carInObjective == FindPlayerVehicle()) && + pPed->m_nPedState != PED_DEAD && pPed->m_nPedState != PED_DIE && + (pPed->m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || + pPed->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || + pPed->m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS)) { + if(pPed->IsPedInControl()) { + if(pPed->m_nPedType == PEDTYPE_COP) + ((CCopPed *)pPed)->ClearPursuit(); + else + pPed->SetIdle(); + pPed->SetObjective(OBJECTIVE_NONE); + } else { + pPed->m_prevObjective = OBJECTIVE_NONE; + pPed->m_nLastPedState = PED_IDLE; + } + } + } + } +} + +void +CWorld::RemoveEntityInsteadOfProcessingIt(CEntity *ent) +{ + if(ent->IsPed()) { + if(FindPlayerPed() == ent) + Remove(ent); + else + CPopulation::RemovePed((CPed *)ent); + } else { + Remove(ent); + delete ent; + } +} + +void +CWorld::RemoveFallenPeds(void) +{ + int poolSize = CPools::GetPedPool()->GetSize(); + for(int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + CPed *ped = CPools::GetPedPool()->GetSlot(poolIndex); + if(ped) { + if(ped->GetPosition().z < MAP_Z_LOW_LIMIT) { + if(ped->CharCreatedBy != RANDOM_CHAR || ped->IsPlayer()) { + int closestNode = ThePaths.FindNodeClosestToCoors(ped->GetPosition(), PATH_PED, + 999999.9f, false, false); + CVector newPos = ThePaths.m_pathNodes[closestNode].GetPosition(); + newPos.z += 2.0f; + ped->Teleport(newPos); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } else { + CPopulation::RemovePed(ped); + } + } + } + } +} + +void +CWorld::RemoveFallenCars(void) +{ + int poolSize = CPools::GetVehiclePool()->GetSize(); + for(int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if(veh) { + if(veh->GetPosition().z < MAP_Z_LOW_LIMIT) { + if(veh->VehicleCreatedBy == MISSION_VEHICLE || veh == FindPlayerVehicle() || + (veh->pDriver && veh->pDriver->IsPlayer())) { + int closestNode = ThePaths.FindNodeClosestToCoors(veh->GetPosition(), PATH_CAR, + 999999.9f, false, false); + CVector newPos = ThePaths.m_pathNodes[closestNode].GetPosition(); + newPos.z += 3.0f; + veh->Teleport(newPos); + veh->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } else if(veh->VehicleCreatedBy == RANDOM_VEHICLE || + veh->VehicleCreatedBy == PARKED_VEHICLE) { + Remove(veh); + delete veh; + } + } + } + } +} + +void +CWorld::StopAllLawEnforcersInTheirTracks(void) +{ + int poolSize = CPools::GetVehiclePool()->GetSize(); + for(int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if(veh) { + if(veh->bIsLawEnforcer) veh->SetMoveSpeed(0.0f, 0.0f, 0.0f); + } + } +} + +void +CWorld::SetAllCarsCanBeDamaged(bool toggle) +{ + int poolSize = CPools::GetVehiclePool()->GetSize(); + for(int poolIndex = 0; poolIndex < poolSize; poolIndex++) { + CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if(veh) veh->bCanBeDamaged = toggle; + } +} + +void +CWorld::ExtinguishAllCarFiresInArea(CVector point, float range) +{ + int poolSize = CPools::GetVehiclePool()->GetSize(); + for(int poolIndex = 0; poolIndex < poolSize; poolIndex++) { + CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if(veh) { + if((point - veh->GetPosition()).MagnitudeSqr() < sq(range)) veh->ExtinguishCarFire(); + } + } +} + +inline void +AddSteamsFromGround(CPtrList& list) +{ + CPtrNode *pNode = list.first; + while (pNode) { + ((CEntity*)pNode->item)->AddSteamsFromGround(nil); + pNode = pNode->next; + } +} + +void +CWorld::AddParticles(void) +{ + for(int32 y = 0; y < NUMSECTORS_Y; y++) { + for(int32 x = 0; x < NUMSECTORS_X; x++) { + CSector *pSector = GetSector(x, y); + AddSteamsFromGround(pSector->m_lists[ENTITYLIST_BUILDINGS]); + AddSteamsFromGround(pSector->m_lists[ENTITYLIST_DUMMIES]); + } + } +} + +void +CWorld::ShutDown(void) +{ + for(int i = 0; i < NUMSECTORS_X * NUMSECTORS_Y; i++) { + CSector *pSector = GetSector(i % NUMSECTORS_X, i / NUMSECTORS_Y); + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_BUILDINGS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_VEHICLES].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_PEDS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_OBJECTS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_DUMMIES].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } +#ifndef FIX_BUGS + pSector->m_lists[ENTITYLIST_BUILDINGS].Flush(); + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP].Flush(); +#endif + } + for(int32 i = 0; i < NUM_LEVELS; i++) { + for(CPtrNode *pNode = ms_bigBuildingsList[i].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + // Maybe remove from world here? + delete pEntity; + } + ms_bigBuildingsList[i].Flush(); + } + for(int i = 0; i < NUMSECTORS_X * NUMSECTORS_Y; i++) { + CSector *pSector = GetSector(i % NUMSECTORS_X, i / NUMSECTORS_Y); +#ifdef FIX_BUGS + pSector->m_lists[ENTITYLIST_BUILDINGS].Flush(); + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP].Flush(); +#endif + if(pSector->m_lists[ENTITYLIST_BUILDINGS].first) { + sprintf(gString, "Building list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_BUILDINGS].Flush(); + } + if(pSector->m_lists[ENTITYLIST_DUMMIES].first) { + sprintf(gString, "Dummy list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_DUMMIES].Flush(); + } + if(pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].first) { + sprintf(gString, "Building overlap list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].Flush(); + } + if(pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP].first) { + sprintf(gString, "Vehicle overlap list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP].Flush(); + } + if(pSector->m_lists[ENTITYLIST_PEDS_OVERLAP].first) { + sprintf(gString, "Ped overlap list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_PEDS_OVERLAP].Flush(); + } + if(pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP].first) { + sprintf(gString, "Object overlap list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP].Flush(); + } + if(pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP].first) { + sprintf(gString, "Dummy overlap list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP].Flush(); + } + } + ms_listMovingEntityPtrs.Flush(); +#if GTA_VERSION <= GTA3_PS2_160 + CPools::Shutdown(); +#endif +} + +void +CWorld::ClearForRestart(void) +{ + if(CCutsceneMgr::HasLoaded()) CCutsceneMgr::DeleteCutsceneData(); + CProjectileInfo::RemoveAllProjectiles(); + CObject::DeleteAllTempObjects(); + CObject::DeleteAllMissionObjects(); + CPopulation::ConvertAllObjectsToDummyObjects(); + for(int i = 0; i < NUMSECTORS_X * NUMSECTORS_Y; i++) { + CSector *pSector = GetSector(i % NUMSECTORS_X, i / NUMSECTORS_Y); + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_PEDS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = GetBigBuildingList(LEVEL_GENERIC).first; pNode; pNode = pNode->next) { + CVehicle *pVehicle = (CVehicle *)pNode->item; + if(pVehicle && pVehicle->IsVehicle() && pVehicle->IsPlane()) { + Remove(pVehicle); + delete pVehicle; + } + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_VEHICLES].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + } + CPools::CheckPoolsEmpty(); +} + +void +CWorld::RepositionCertainDynamicObjects() +{ + int32 i = CPools::GetDummyPool()->GetSize(); + while(--i >= 0) { + CDummy *dummy = CPools::GetDummyPool()->GetSlot(i); + if(dummy) { RepositionOneObject(dummy); } + } +} + +void +CWorld::RepositionOneObject(CEntity *pEntity) +{ + int16 modelId = pEntity->GetModelIndex(); + if (IsStreetLight(modelId) || IsTreeModel(modelId) || modelId == MI_PARKINGMETER || + modelId == MI_PHONEBOOTH1 || modelId == MI_WASTEBIN || modelId == MI_BIN || modelId == MI_POSTBOX1 || + modelId == MI_NEWSSTAND || modelId == MI_TRAFFICCONE || modelId == MI_DUMP1 || + modelId == MI_ROADWORKBARRIER1 || modelId == MI_BUSSIGN1 || modelId == MI_NOPARKINGSIGN1 || + modelId == MI_PHONESIGN || modelId == MI_TAXISIGN || modelId == MI_FISHSTALL01 || + modelId == MI_FISHSTALL02 || modelId == MI_FISHSTALL03 || modelId == MI_FISHSTALL04 || + modelId == MI_BAGELSTAND2 || modelId == MI_FIRE_HYDRANT || modelId == MI_BOLLARDLIGHT || + modelId == MI_PARKTABLE) { + CVector &position = pEntity->GetMatrix().GetPosition(); + float fBoundingBoxMinZ = pEntity->GetColModel()->boundingBox.min.z; + position.z = FindGroundZFor3DCoord(position.x, position.y, + position.z + OBJECT_REPOSITION_OFFSET_Z, nil) - + fBoundingBoxMinZ; + pEntity->GetMatrix().UpdateRW(); + pEntity->UpdateRwFrame(); + } else if(modelId == MI_BUOY) { + float fWaterLevel = 0.0f; + bool bFound = true; + const CVector &position = pEntity->GetPosition(); + float fGroundZ = FindGroundZFor3DCoord(position.x, position.y, + position.z + OBJECT_REPOSITION_OFFSET_Z, &bFound); + if(CWaterLevel::GetWaterLevelNoWaves(position.x, position.y, position.z + OBJECT_REPOSITION_OFFSET_Z, + &fWaterLevel)) { + if(!bFound || fWaterLevel > fGroundZ) { + CColModel *pColModel = pEntity->GetColModel(); + float fHeight = pColModel->boundingBox.max.z - pColModel->boundingBox.min.z; + pEntity->GetMatrix().GetPosition().z = 0.2f * fHeight + fWaterLevel - 0.5f * fHeight; + } + } + } +} + +void +CWorld::SetCarsOnFire(float x, float y, float z, float radius, CEntity *reason) +{ + int poolSize = CPools::GetVehiclePool()->GetSize(); + for(int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if(veh && veh->GetStatus() != STATUS_WRECKED && !veh->m_pCarFire && !veh->bFireProof) { + if(Abs(veh->GetPosition().z - z) < 5.0f && Abs(veh->GetPosition().x - x) < radius && + Abs(veh->GetPosition().y - y) < radius) + gFireManager.StartFire(veh, reason, 0.8f, true); + } + } +} + +void +CWorld::SetPedsOnFire(float x, float y, float z, float radius, CEntity *reason) +{ + int32 poolSize = CPools::GetPedPool()->GetSize(); + for(int32 i = poolSize - 1; i >= 0; i--) { + CPed *pPed = CPools::GetPedPool()->GetSlot(i); + if(pPed && pPed->m_nPedState != PED_DEAD && !pPed->bInVehicle && !pPed->m_pFire && !pPed->bFireProof) { + if(Abs(pPed->GetPosition().z - z) < 5.0f && Abs(pPed->GetPosition().x - x) < radius && + Abs(pPed->GetPosition().y - y) < radius) + gFireManager.StartFire(pPed, reason, 0.8f, true); + } + } +} + +void +CWorld::RemoveStaticObjects() +{ + for(int i = 0; i < NUMSECTORS_X * NUMSECTORS_Y; i++) { + CSector *pSector = GetSector(i % NUMSECTORS_X, i / NUMSECTORS_Y); + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_BUILDINGS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_OBJECTS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_DUMMIES].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + pSector->m_lists[ENTITYLIST_BUILDINGS].Flush(); + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP].Flush(); + } +} + +void +CWorld::Process(void) +{ + if(!(CTimer::GetFrameCounter() & 63)) CReferences::PruneAllReferencesInWorld(); + + if(bProcessCutsceneOnly) { + for(int i = 0; i < NUMCUTSCENEOBJECTS; i++) { + CCutsceneObject *csObj = CCutsceneMgr::GetCutsceneObject(i); + if(csObj && csObj->m_entryInfoList.first) { + if(csObj->m_rwObject && RwObjectGetType(csObj->m_rwObject) == rpCLUMP && + RpAnimBlendClumpGetFirstAssociation(csObj->GetClump())) { + RpAnimBlendClumpUpdateAnimations(csObj->GetClump(), + csObj->IsObject() + ? CTimer::GetTimeStepNonClippedInSeconds() + : CTimer::GetTimeStepInSeconds()); + } + csObj->ProcessControl(); + csObj->ProcessCollision(); + csObj->GetMatrix().UpdateRW(); + csObj->UpdateRwFrame(); + } + } + CRecordDataForChase::ProcessControlCars(); + CRecordDataForChase::SaveOrRetrieveCarPositions(); + } else { + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; +#ifdef FIX_BUGS // from VC + if(!movingEnt->bRemoveFromWorld && movingEnt->m_rwObject && RwObjectGetType(movingEnt->m_rwObject) == rpCLUMP && +#else + if(movingEnt->m_rwObject && RwObjectGetType(movingEnt->m_rwObject) == rpCLUMP && +#endif + RpAnimBlendClumpGetFirstAssociation(movingEnt->GetClump())) { + RpAnimBlendClumpUpdateAnimations(movingEnt->GetClump(), + movingEnt->IsObject() + ? CTimer::GetTimeStepNonClippedInSeconds() + : CTimer::GetTimeStepInSeconds()); + } + } + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CPhysical *movingEnt = (CPhysical *)node->item; + if(movingEnt->bRemoveFromWorld) { + RemoveEntityInsteadOfProcessingIt(movingEnt); + } else { + movingEnt->ProcessControl(); + if(movingEnt->GetIsStatic()) { movingEnt->RemoveFromMovingList(); } + } + } + bForceProcessControl = true; + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CPhysical *movingEnt = (CPhysical *)node->item; + if(movingEnt->bWasPostponed) { + if(movingEnt->bRemoveFromWorld) { + RemoveEntityInsteadOfProcessingIt(movingEnt); + } else { + movingEnt->ProcessControl(); + if(movingEnt->GetIsStatic()) { movingEnt->RemoveFromMovingList(); } + } + } + } + bForceProcessControl = false; + if(CReplay::IsPlayingBack()) { + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; + movingEnt->bIsInSafePosition = true; + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + } + } else { + bNoMoreCollisionTorque = false; + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; + if(!movingEnt->bIsInSafePosition) { + movingEnt->ProcessCollision(); + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + } + } + bNoMoreCollisionTorque = true; + for(int i = 0; i < 4; i++) { + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; + if(!movingEnt->bIsInSafePosition) { + movingEnt->ProcessCollision(); + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + } + } + } + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; + if(!movingEnt->bIsInSafePosition) { + movingEnt->bIsStuck = true; + movingEnt->ProcessCollision(); + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + if(!movingEnt->bIsInSafePosition) { movingEnt->bIsStuck = true; } + } + } + bSecondShift = false; + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; + if(!movingEnt->bIsInSafePosition) { + movingEnt->ProcessShift(); + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + if(!movingEnt->bIsInSafePosition) { movingEnt->bIsStuck = true; } + } + } + bSecondShift = true; + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CPhysical *movingEnt = (CPhysical *)node->item; + if(!movingEnt->bIsInSafePosition) { + movingEnt->ProcessShift(); + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + if(!movingEnt->bIsInSafePosition) { + movingEnt->bIsStuck = true; + if(movingEnt->GetStatus() == STATUS_PLAYER) { + printf("STUCK: Final Step: Player Entity %d Is Stuck\n", movingEnt->GetModelIndex()); + movingEnt->m_vecMoveSpeed *= 0.3f; + movingEnt->ApplyMoveSpeed(); + movingEnt->ApplyTurnSpeed(); + } + } + } + } + } + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CPed *movingPed = (CPed *)node->item; + if(movingPed->IsPed()) { + if(movingPed->bInVehicle && movingPed->m_nPedState != PED_EXIT_TRAIN || + movingPed->EnteringCar()) { + CVehicle *movingCar = movingPed->m_pMyVehicle; + if(movingCar) { + if(movingCar->IsTrain()) { + movingPed->SetPedPositionInTrain(); + } else { + switch(movingPed->m_nPedState) { + case PED_ENTER_CAR: + case PED_CARJACK: movingPed->EnterCar(); break; + case PED_DRAG_FROM_CAR: movingPed->BeingDraggedFromCar(); break; + case PED_EXIT_CAR: movingPed->ExitCar(); break; + case PED_ARRESTED: + if(movingPed->m_nLastPedState == PED_DRAG_FROM_CAR) { + movingPed->BeingDraggedFromCar(); + break; + } + // fall through + default: movingPed->SetPedPositionInCar(); break; + } + } + movingPed->GetMatrix().UpdateRW(); + movingPed->UpdateRwFrame(); + } else { + movingPed->bInVehicle = false; + movingPed->QuitEnteringCar(); + } + } + } + } + CMessages::Process(); + Players[PlayerInFocus].Process(); + CRecordDataForChase::SaveOrRetrieveCarPositions(); + if((CTimer::GetFrameCounter() & 7) == 1) { + RemoveFallenPeds(); + } else if((CTimer::GetFrameCounter() & 7) == 5) { + RemoveFallenCars(); + } + } +} + +void +CWorld::TriggerExplosion(const CVector &position, float fRadius, float fPower, CEntity *pCreator, + bool bProcessVehicleBombTimer) +{ + CVector2D vecStartPos(position.x - fRadius, position.y - fRadius); + CVector2D vecEndPos(position.x + fRadius, position.y + fRadius); + const int32 nStartX = Max(GetSectorIndexX(vecStartPos.x), 0); + const int32 nStartY = Max(GetSectorIndexY(vecStartPos.y), 0); + const int32 nEndX = Min(GetSectorIndexX(vecEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecEndPos.y), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + TriggerExplosionSectorList(pSector->m_lists[ENTITYLIST_VEHICLES], position, fRadius, + fPower, pCreator, bProcessVehicleBombTimer); + TriggerExplosionSectorList(pSector->m_lists[ENTITYLIST_PEDS], position, fRadius, fPower, + pCreator, bProcessVehicleBombTimer); + TriggerExplosionSectorList(pSector->m_lists[ENTITYLIST_OBJECTS], position, fRadius, + fPower, pCreator, bProcessVehicleBombTimer); + } + } +} + +void +CWorld::TriggerExplosionSectorList(CPtrList &list, const CVector &position, float fRadius, float fPower, + CEntity *pCreator, bool bProcessVehicleBombTimer) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CPhysical *pEntity = (CPhysical *)pNode->item; + CVector vecDistance = pEntity->GetPosition() - position; + float fMagnitude = vecDistance.Magnitude(); + if(fRadius > fMagnitude) { + CWeapon::BlowUpExplosiveThings(pEntity); + CPed *pPed = (CPed *)pEntity; + CObject *pObject = (CObject *)pEntity; + CVehicle *pVehicle = (CVehicle *)pEntity; + if(!pEntity->bExplosionProof && (!pEntity->IsPed() || !pPed->bInVehicle)) { + if(pEntity->GetIsStatic()) { + if(pEntity->IsObject()) { + if (fPower > pObject->m_fUprootLimit || IsFence(pObject->GetModelIndex())) { + if (IsGlass(pObject->GetModelIndex())) { + CGlass::WindowRespondsToExplosion(pObject, position); + } else { + pObject->SetIsStatic(false); + pObject->AddToMovingList(); + int16 modelId = pEntity->GetModelIndex(); + if(modelId != MI_FIRE_HYDRANT || + pObject->bHasBeenDamaged) { + if(pEntity->IsObject() && + modelId != MI_EXPLODINGBARREL && + modelId != MI_PETROLPUMP) + pObject->bHasBeenDamaged = true; + } else { + CVector pos = pEntity->GetPosition(); + pos.z -= 0.5f; + CParticleObject::AddObject(POBJECT_FIRE_HYDRANT, + pos, true); + pObject->bHasBeenDamaged = true; + } + } + } + if(pEntity->GetIsStatic()) { + float fDamageMultiplier = + (fRadius - fMagnitude) * 2.0f / fRadius; + float fDamage = 300.0f * Min(fDamageMultiplier, 1.0f); + pObject->ObjectDamage(fDamage); + } + } else { + pEntity->SetIsStatic(false); + pEntity->AddToMovingList(); + } + } + if(!pEntity->GetIsStatic()) { + float fDamageMultiplier = Min((fRadius - fMagnitude) * 2.0f / fRadius, 1.0f); + CVector vecForceDir = + vecDistance * (fPower * pEntity->m_fMass / 1400.0f * fDamageMultiplier / + Max(fMagnitude, 0.01f)); + vecForceDir.z = Max(vecForceDir.z, 0.0f); + if(pEntity == FindPlayerPed()) vecForceDir.z = Min(vecForceDir.z, 1.0f); + pEntity->ApplyMoveForce(vecForceDir); + if(!pEntity->bPedPhysics) { + float fBoundRadius = pEntity->GetBoundRadius(); + float fDistanceZ = position.z - pEntity->GetPosition().z; + float fPointZ = fBoundRadius; + if(Max(fDistanceZ, -fBoundRadius) < fBoundRadius) { + if(fDistanceZ <= -fBoundRadius) + fPointZ = -fBoundRadius; + else + fPointZ = fDistanceZ; + } + pEntity->ApplyTurnForce(vecForceDir.x, vecForceDir.y, vecForceDir.z, + 0.0f, 0.0f, fPointZ); + } + switch(pEntity->GetType()) { + case ENTITY_TYPE_VEHICLE: + if(pEntity->GetStatus() == STATUS_SIMPLE) { + pEntity->SetStatus(STATUS_PHYSICS); + CCarCtrl::SwitchVehicleToRealPhysics(pVehicle); + } + pVehicle->InflictDamage(pCreator, WEAPONTYPE_EXPLOSION, + 1100.0f * fDamageMultiplier); + if(bProcessVehicleBombTimer) { + if(pVehicle->m_nBombTimer) pVehicle->m_nBombTimer /= 10; + } + break; + case ENTITY_TYPE_PED: { + int8 direction = pPed->GetLocalDirection(-vecForceDir); + pPed->bIsStanding = false; + pPed->ApplyMoveForce(0.0, 0.0, 2.0f); + float fDamage = 250.0f * fDamageMultiplier; + pPed->InflictDamage(pCreator, WEAPONTYPE_EXPLOSION, fDamage, + PEDPIECE_TORSO, direction); + if(pPed->m_nPedState != PED_DIE) + pPed->SetFall(2000, + (AnimationId)(direction + ANIM_STD_HIGHIMPACT_FRONT), 0); + if(pCreator && pCreator->IsPed()) { + eEventType eventType = EVENT_SHOOT_PED; + if(pPed->m_nPedType == PEDTYPE_COP) eventType = EVENT_SHOOT_COP; + CEventList::RegisterEvent(eventType, EVENT_ENTITY_PED, pEntity, + (CPed *)pCreator, 10000); + pPed->RegisterThreatWithGangPeds(pCreator); + } + break; + } + case ENTITY_TYPE_OBJECT: + pObject->ObjectDamage(300.0f * fDamageMultiplier); + break; + default: break; + } + } + } + } + } +} + +void +CWorld::UseDetonator(CEntity *pEntity) +{ + int32 i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0) { + CAutomobile *pVehicle = (CAutomobile *)CPools::GetVehiclePool()->GetSlot(i); + if(pVehicle && !pVehicle->m_vehType && pVehicle->m_bombType == CARBOMB_REMOTE && + pVehicle->m_pBombRigger == pEntity) { + pVehicle->m_bombType = CARBOMB_NONE; + pVehicle->m_nBombTimer = 500; + pVehicle->m_pBlowUpEntity = pVehicle->m_pBombRigger; + if(pVehicle->m_pBlowUpEntity) + pVehicle->m_pBlowUpEntity->RegisterReference(&pVehicle->m_pBlowUpEntity); + } + } +} diff --git a/src/core/World.h b/src/core/World.h new file mode 100644 index 0000000..3d55375 --- /dev/null +++ b/src/core/World.h @@ -0,0 +1,155 @@ +#pragma once + +#include "Game.h" +#include "Lists.h" +#include "PlayerInfo.h" +#include "Collision.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) + +#define MAP_Z_LOW_LIMIT -100.0f + +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]; +}; + +VALIDATE_SIZE(CSector, 0x28); + +class CWorld +{ + static CPtrList ms_bigBuildingsList[NUM_LEVELS]; + static CPtrList ms_listMovingEntityPtrs; + static CSector ms_aSectors[NUMSECTORS_Y][NUMSECTORS_X]; + static uint16 ms_nCurrentScanCode; + +public: + static uint8 PlayerInFocus; + static CPlayerInfo Players[NUMPLAYERS]; + static CEntity *pIgnoreEntity; + static bool bIncludeDeadPeds; + static bool bNoMoreCollisionTorque; + static bool bSecondShift; + static bool bForceProcessControl; + static bool bProcessCutsceneOnly; + static bool bDoingCarCollisions; + static bool bIncludeCarTyres; + + 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 void ClearExcitingStuffFromArea(const CVector &pos, float radius, bool bRemoveProjectilesAndTidyUpShadows); + + 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 CEntity *TestSphereAgainstWorld(CVector centre, float radius, CEntity *entityToIgnore, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSomeObjects); + static CEntity *TestSphereAgainstSectorList(CPtrList&, CVector, float, CEntity*, bool); + static void FindObjectsInRangeSectorList(CPtrList &list, Const CVector ¢re, float radius, bool ignoreZ, int16 *numObjects, int16 lastObject, CEntity **objects); + static void FindObjectsInRange(Const CVector ¢re, float radius, bool ignoreZ, int16 *numObjects, int16 lastObject, CEntity **objects, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies); + static void FindObjectsOfTypeInRangeSectorList(uint32 modelId, CPtrList& list, const CVector& position, float radius, bool bCheck2DOnly, int16* nEntitiesFound, int16 maxEntitiesToFind, CEntity** aEntities); + static void FindObjectsOfTypeInRange(uint32 modelId, const CVector& position, float radius, bool bCheck2DOnly, int16* nEntitiesFound, int16 maxEntitiesToFind, CEntity** aEntities, bool bBuildings, bool bVehicles, bool bPeds, bool bObjects, bool bDummies); + 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 void RemoveReferencesToDeletedObject(CEntity*); + static void FindObjectsKindaColliding(const CVector& position, float radius, bool bCheck2DOnly, int16* nCollidingEntities, int16 maxEntitiesToFind, CEntity** aEntities, bool bBuildings, bool bVehicles, bool bPeds, bool bObjects, bool bDummies); + static void FindObjectsKindaCollidingSectorList(CPtrList& list, const CVector& position, float radius, bool bCheck2DOnly, int16* nCollidingEntities, int16 maxEntitiesToFind, CEntity** aEntities); + static void FindObjectsIntersectingCube(const CVector& vecStartPos, const CVector& vecEndPos, int16* nIntersecting, int16 maxEntitiesToFind, CEntity** aEntities, bool bBuildings, bool bVehicles, bool bPeds, bool bObjects, bool bDummies); + static void FindObjectsIntersectingCubeSectorList(CPtrList& list, const CVector& vecStartPos, const CVector& vecEndPos, int16* nIntersecting, int16 maxEntitiesToFind, CEntity** aEntities); + static void FindObjectsIntersectingAngledCollisionBox(const CColBox &, const CMatrix &, const CVector &, float, float, float, float, int16*, int16, CEntity **, bool, bool, bool, bool, bool); + static void FindObjectsIntersectingAngledCollisionBoxSectorList(CPtrList& list, const CColBox& boundingBox, const CMatrix& matrix, const CVector& position, int16* nEntitiesFound, int16 maxEntitiesToFind, CEntity** aEntities); + static void FindMissionEntitiesIntersectingCube(const CVector& vecStartPos, const CVector& vecEndPos, int16* nIntersecting, int16 maxEntitiesToFind, CEntity** aEntities, bool bVehicles, bool bPeds, bool bObjects); + static void FindMissionEntitiesIntersectingCubeSectorList(CPtrList& list, const CVector& vecStartPos, const CVector& vecEndPos, int16* nIntersecting, int16 maxEntitiesToFind, CEntity** aEntities, bool bIsVehicleList, bool bIsPedList); + + static void ClearCarsFromArea(float x1, float y1, float z1, float x2, float y2, float z2); + static void ClearPedsFromArea(float x1, float y1, float z1, float x2, float y2, float z2); + static void CallOffChaseForArea(float x1, float y1, float x2, float y2); + static void CallOffChaseForAreaSectorListVehicles(CPtrList& list, float x1, float y1, float x2, float y2, float fStartX, float fStartY, float fEndX, float fEndY); + static void CallOffChaseForAreaSectorListPeds(CPtrList& list, float x1, float y1, float x2, float y2); + + 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; } + + static void RemoveEntityInsteadOfProcessingIt(CEntity* ent); + static void RemoveFallenPeds(); + static void RemoveFallenCars(); + + static void StopAllLawEnforcersInTheirTracks(); + static void SetAllCarsCanBeDamaged(bool); + static void ExtinguishAllCarFiresInArea(CVector, float); + static void SetCarsOnFire(float x, float y, float z, float radius, CEntity* reason); + static void SetPedsOnFire(float x, float y, float z, float radius, CEntity* reason); + + static void Initialise(); + static void AddParticles(); + static void ShutDown(); + static void ClearForRestart(void); + static void RepositionCertainDynamicObjects(); + static void RepositionOneObject(CEntity* pEntity); + static void RemoveStaticObjects(); + static void Process(); + static void TriggerExplosion(const CVector& position, float fRadius, float fPower, CEntity* pCreator, bool bProcessVehicleBombTimer); + static void TriggerExplosionSectorList(CPtrList& list, const CVector& position, float fRadius, float fPower, CEntity* pCreator, bool bProcessVehicleBombTimer); + static void UseDetonator(CEntity *pEntity); +}; + +extern CColPoint gaTempSphereColPoints[MAX_COLLISION_POINTS]; + diff --git a/src/core/ZoneCull.cpp b/src/core/ZoneCull.cpp new file mode 100644 index 0000000..5a76e5e --- /dev/null +++ b/src/core/ZoneCull.cpp @@ -0,0 +1,1589 @@ +#include "common.h" + +#include "General.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" +#include "Zones.h" + +#include "Debug.h" +#include "Renderer.h" + +int32 CCullZones::NumCullZones; +CCullZone CCullZones::aZones[NUMCULLZONES]; +int32 CCullZones::NumAttributeZones; +CAttributeZone CCullZones::aAttributeZones[NUMATTRIBZONES]; +uint16 CCullZones::aIndices[NUMZONEINDICES]; +int16 CCullZones::aPointersToBigBuildingsForBuildings[NUMBUILDINGS]; +int16 CCullZones::aPointersToBigBuildingsForTreadables[NUMTREADABLES]; + +int32 CCullZones::CurrentWantedLevelDrop_Player; +int32 CCullZones::CurrentFlags_Camera; +int32 CCullZones::CurrentFlags_Player; +int32 CCullZones::OldCullZone; +int32 CCullZones::EntityIndicesUsed; +bool CCullZones::bCurrentSubwayIsInvisible; +bool CCullZones::bCullZonesDisabled; + +#define NUMUNCOMPRESSED (6000) +#define NUMTEMPINDICES (140000) + +void +CCullZones::Init(void) +{ + int i; + + NumAttributeZones = 0; + CurrentWantedLevelDrop_Player = 0; + CurrentFlags_Camera = 0; + CurrentFlags_Player = 0; + bCurrentSubwayIsInvisible = false; + NumCullZones = 0; + OldCullZone = -1; + EntityIndicesUsed = 0; + + for(i = 0; i < NUMBUILDINGS; i++) + aPointersToBigBuildingsForBuildings[i] = -1; + for(i = 0; i < NUMTREADABLES; i++) + aPointersToBigBuildingsForTreadables[i] = -1; +} + + +uint16* pTempArrayIndices; +int TempEntityIndicesUsed; + +void +CCullZones::ResolveVisibilities(void) +{ + int fd; + + CFileMgr::SetDir(""); + fd = CFileMgr::OpenFile("DATA\\cullzone.dat", "rb"); + if(fd > 0){ + CFileMgr::Read(fd, (char*)&NumCullZones, sizeof(NumCullZones)); + CFileMgr::Read(fd, (char*)aZones, sizeof(aZones)); + CFileMgr::Read(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones)); + CFileMgr::Read(fd, (char*)aAttributeZones, sizeof(aAttributeZones)); + CFileMgr::Read(fd, (char*)aIndices, sizeof(aIndices)); + CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings)); + CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables)); + CFileMgr::CloseFile(fd); + }else{ +#ifndef MASTER + EntityIndicesUsed = 0; + BuildListForBigBuildings(); + pTempArrayIndices = new uint16[NUMTEMPINDICES]; + TempEntityIndicesUsed = 0; + +// if(!LoadTempFile()) // not in final game + { + for (int i = 0; i < NumCullZones; i++) { +//printf("testing zone %d (%d indices)\n", i, TempEntityIndicesUsed); + DoVisibilityTestCullZone(i, true); + } + +// SaveTempFile(); // not in final game + } + + CompressIndicesArray(); + delete[] pTempArrayIndices; + pTempArrayIndices = nil; + + fd = CFileMgr::OpenFileForWriting("data\\cullzone.dat"); + if (fd != 0) { + CFileMgr::Write(fd, (char*)&NumCullZones, sizeof(NumCullZones)); + CFileMgr::Write(fd, (char*)aZones, sizeof(aZones)); + CFileMgr::Write(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones)); + CFileMgr::Write(fd, (char*)&aAttributeZones, sizeof(aAttributeZones)); + CFileMgr::Write(fd, (char*)&aIndices, sizeof(aIndices)); + CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings)); + CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables)); + CFileMgr::CloseFile(fd); + } +#endif + } +} + +bool +CCullZones::LoadTempFile(void) +{ + int fd = CFileMgr::OpenFile("cullzone.tmp"); + if (fd != 0) { + CFileMgr::Read(fd, (char*)&NumCullZones, sizeof(NumCullZones)); + CFileMgr::Read(fd, (char*)aZones, sizeof(aZones)); + CFileMgr::Read(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones)); + CFileMgr::Read(fd, (char*)&aAttributeZones, sizeof(aAttributeZones)); + CFileMgr::Read(fd, (char*)pTempArrayIndices, NUMTEMPINDICES*sizeof(uint16)); + CFileMgr::Read(fd, (char*)&TempEntityIndicesUsed, sizeof(TempEntityIndicesUsed)); + CFileMgr::Read(fd, (char*)&aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings)); + CFileMgr::Read(fd, (char*)&aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables)); + CFileMgr::CloseFile(fd); + return true; + } + return false; +} + +void +CCullZones::SaveTempFile(void) +{ + int fd = CFileMgr::OpenFileForWriting("cullzone.tmp"); + if (fd != 0) { + CFileMgr::Write(fd, (char*)&NumCullZones, sizeof(NumCullZones)); + CFileMgr::Write(fd, (char*)aZones, sizeof(aZones)); + CFileMgr::Write(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones)); + CFileMgr::Write(fd, (char*)&aAttributeZones, sizeof(aAttributeZones)); + CFileMgr::Write(fd, (char*)pTempArrayIndices, NUMTEMPINDICES*sizeof(uint16)); + CFileMgr::Write(fd, (char*)&TempEntityIndicesUsed, sizeof(TempEntityIndicesUsed)); + CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings)); + CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables)); + CFileMgr::CloseFile(fd); + } +} + + +void +CCullZones::BuildListForBigBuildings() +{ + for (int i = CPools::GetBuildingPool()->GetSize()-1; i >= 0; i--) { + CBuilding *building = CPools::GetBuildingPool()->GetSlot(i); + if (building == nil || !building->bIsBIGBuilding) continue; + CSimpleModelInfo *nonlod = ((CSimpleModelInfo *)CModelInfo::GetModelInfo(building->GetModelIndex()))->GetRelatedModel(); + if (nonlod == nil) continue; + + for (int j = CPools::GetBuildingPool()->GetSize()-1; j >= 0; j--) { + CBuilding *building2 = CPools::GetBuildingPool()->GetSlot(j); + if (building2 == nil || building2->bIsBIGBuilding) continue; + if (CModelInfo::GetModelInfo(building2->GetModelIndex()) == nonlod) { + if ((building2->GetPosition() - building->GetPosition()).Magnitude() < 5.0f) { + aPointersToBigBuildingsForBuildings[j] = i; + } + } + } + + for (int j = CPools::GetTreadablePool()->GetSize()-1; j >= 0; j--) { + CTreadable *treadable = CPools::GetTreadablePool()->GetSlot(j); + if (treadable == nil || treadable->bIsBIGBuilding) continue; + if (CModelInfo::GetModelInfo(treadable->GetModelIndex()) == nonlod) { + if ((treadable->GetPosition() - building->GetPosition()).Magnitude() < 5.0f) { + aPointersToBigBuildingsForTreadables[j] = i; + } + } + } + } +} + +void +CCullZones::DoVisibilityTestCullZone(int zoneId, bool findIndices) +{ + aZones[zoneId].m_groupIndexCount[0] = 0; + aZones[zoneId].m_groupIndexCount[1] = 0; + aZones[zoneId].m_groupIndexCount[2] = 0; + aZones[zoneId].m_indexStart = TempEntityIndicesUsed; + aZones[zoneId].FindTestPoints(); + + if (!findIndices) return; + + for (int i = CPools::GetBuildingPool()->GetSize() - 1; i >= 0; i--) { + CBuilding *building = CPools::GetBuildingPool()->GetSlot(i); + if (building != nil && !building->bIsBIGBuilding && aZones[zoneId].IsEntityCloseEnoughToZone(building, aPointersToBigBuildingsForBuildings[i] != -1)) { + CBuilding *LODbuilding = nil; + if (aPointersToBigBuildingsForBuildings[i] != -1) + LODbuilding = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForBuildings[i]); + + if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 0.0f, LODbuilding)) { + assert(TempEntityIndicesUsed < NUMTEMPINDICES); + pTempArrayIndices[TempEntityIndicesUsed++] = i; + aZones[zoneId].m_groupIndexCount[0]++; + } + } + } + + for (int i = CPools::GetTreadablePool()->GetSize() - 1; i >= 0; i--) { + CBuilding* building = CPools::GetTreadablePool()->GetSlot(i); + if (building != nil && aZones[zoneId].IsEntityCloseEnoughToZone(building, aPointersToBigBuildingsForTreadables[i] != -1)) { + CBuilding *LODbuilding = nil; + if (aPointersToBigBuildingsForTreadables[i] != -1) + LODbuilding = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForTreadables[i]); + + if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 10.0f, LODbuilding)) { + assert(TempEntityIndicesUsed < NUMTEMPINDICES); + pTempArrayIndices[TempEntityIndicesUsed++] = i; + aZones[zoneId].m_groupIndexCount[1]++; + } + } + } + + for (int i = CPools::GetTreadablePool()->GetSize() - 1; i >= 0; i--) { + CBuilding *building = CPools::GetTreadablePool()->GetSlot(i); + if (building != nil && aZones[zoneId].CalcDistToCullZoneSquared(building->GetPosition().x, building->GetPosition().y) < SQR(200.0f)) { + int start = aZones[zoneId].m_groupIndexCount[0] + aZones[zoneId].m_indexStart; + int end = aZones[zoneId].m_groupIndexCount[1] + start; + + bool alreadyAdded = false; + + for (int k = start; k < end; k++) { +#ifdef FIX_BUGS + if (pTempArrayIndices[k] == i) +#else + if (aIndices[k] == i) +#endif + alreadyAdded = true; + } + + if (!alreadyAdded) { + CBuilding *LODbuilding = nil; + if (aPointersToBigBuildingsForTreadables[i] != -1) + LODbuilding = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForTreadables[i]); + if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 0.0f, LODbuilding)) { + assert(TempEntityIndicesUsed < NUMTEMPINDICES); + pTempArrayIndices[TempEntityIndicesUsed++] = i; + aZones[zoneId].m_groupIndexCount[2]++; + } + } + } + } +} + +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; + + if (wantedLevel) + *wantedLevel = 0; + 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 = Max(*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()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->bIsSubway) + e->bIsVisible = visible; + } + + n = CPools::GetTreadablePool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetTreadablePool()->GetSlot(i); + if(e && e->bIsSubway) + e->bIsVisible = visible; + } + + n = CPools::GetVehiclePool()->GetSize()-1; + for(i = n; i >= 0; i--){ + v = CPools::GetVehiclePool()->GetSlot(i); + if(v && v->IsTrain() && ((CTrain*)v)->m_nTrackId != TRACK_ELTRAIN) + 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; + // reposition start point to the start/end of the + // alley next to the big building in the industrial district. + // probably isn't analyzed correctly otherwise?s + 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->m_groupIndexCount[0] = 0; + cull->m_groupIndexCount[1] = 0; + cull->m_groupIndexCount[2] = 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; + } +} + +uint16 *pExtraArrayIndices; + +void +CCullZones::CompressIndicesArray() +{ + uint16 set[3]; + + // These are used to hold the compressed groups in sets of 3 + int numExtraIndices = 0; + pExtraArrayIndices = new uint16[NUMTEMPINDICES]; + + for(int numOccurrences = 6; numOccurrences > 1; numOccurrences--){ + if(NumCullZones == 0) + break; + +//printf("checking occurrences %d\n", numOccurrences); + int attempt = 0; + while(attempt < 10000){ + for(;;){ + attempt++; + + int zone = CGeneral::GetRandomNumber() % NumCullZones; + int group = CGeneral::GetRandomNumber() % 3; + if(!PickRandomSetForGroup(zone, group, set)) + break; + if(!DoWeHaveMoreThanXOccurencesOfSet(numOccurrences, set)) + break; + + // add this set + attempt = 1; + int setId = numExtraIndices + NUMUNCOMPRESSED; + pExtraArrayIndices[numExtraIndices++] = set[0]; + pExtraArrayIndices[numExtraIndices++] = set[1]; + pExtraArrayIndices[numExtraIndices++] = set[2]; + ReplaceSetForAllGroups(set, setId); + } + } + } + + TidyUpAndMergeLists(pExtraArrayIndices, numExtraIndices); + + delete[] pExtraArrayIndices; +} + +// Get three random indices for this group of a zone +bool +CCullZones::PickRandomSetForGroup(int32 zone, int32 group, uint16 *set) +{ + int32 start; + int32 size; + + aZones[zone].GetGroupStartAndSize(group, start, size); + if(size <= 0) + return false; + + int numIndices = 0; + for(int i = 0; i < size; i++) + if(pTempArrayIndices[start + i] != 0xFFFF) + numIndices++; + if(numIndices < 3) + return false; + + int first = CGeneral::GetRandomNumber() % (numIndices-2); + + numIndices = 0; + int n = 0; + for(int i = 0; i < size; i++) + if(pTempArrayIndices[start + i] != 0xFFFF){ + if(n++ < first) continue; + + set[numIndices++] = pTempArrayIndices[start + i]; + if(numIndices == 3) + break; + } + return true; +} + +bool +CCullZones::DoWeHaveMoreThanXOccurencesOfSet(int32 count, uint16 *set) +{ + int32 curCount; + int32 start; + int32 size; + + curCount = 0; + for (int i = 0; i < NumCullZones; i++) { + for (int group = 0; group < 3; group++) { + aZones[i].GetGroupStartAndSize(group, start, size); + if(size <= 0) continue; + + // check if the set is a subset of the group + int n = 0; + for (int j = 0; j < size; j++) { + for (int k = 0; k < 3; k++) { + if (pTempArrayIndices[start+j] == set[k]) + n++; + } + } + // yes it is + if(n == 3){ + curCount++; + // check if we have seen this set often enough + if(curCount >= count) + return true; + } + } + } + return false; +} + +void +CCullZones::ReplaceSetForAllGroups(uint16 *set, uint16 setid) +{ + int32 start; + int32 size; + + for(int i = 0; i < NumCullZones; i++) + for(int group = 0; group < 3; group++){ + aZones[i].GetGroupStartAndSize(group, start, size); + if(size <= 0) continue; + + // check if the set is a subset of the group + int n = 0; + for(int j = 0; j < size; j++){ + for(int k = 0; k < 3; k++){ + if(pTempArrayIndices[start+j] == set[k]) + n++; + } + } + + // yes it is, so replace it + if(n == 3){ + bool insertedSet = false; + for(int j = 0; j < size; j++){ + for(int k = 0; k < 3; k++){ + // replace first element by set, invalidate others + if(pTempArrayIndices[start+j] == set[k]){ + if(!insertedSet) + pTempArrayIndices[start+j] = setid; + else + pTempArrayIndices[start+j] = 0xFFFF; + insertedSet = true; + } + } + } + } + } +} + +void +CCullZones::TidyUpAndMergeLists(uint16 *extraIndices, int32 numExtraIndices) +{ + int numTempIndices = 0; + for(int i = 0; i < TempEntityIndicesUsed; i++) + if(pTempArrayIndices[i] != 0xFFFF) + numTempIndices++; + + // Fix up zone ranges such that there are no holes + for(int i = 0; i < NumCullZones; i++){ + int j; + int start = 0; + for(j = 0; j < aZones[i].m_indexStart; j++) + if(pTempArrayIndices[j] != 0xFFFF) + start++; + + aZones[i].m_indexStart = start; + aZones[i].m_numBuildings = 0; + aZones[i].m_numTreadablesPlus10m = 0; + aZones[i].m_numTreadables = 0; + + for(int k = 0; k < aZones[i].m_groupIndexCount[0]; k++) + if(pTempArrayIndices[j++] != 0xFFFF) + aZones[i].m_numBuildings++; + for(int k = 0; k < aZones[i].m_groupIndexCount[1]; k++) + if(pTempArrayIndices[j++] != 0xFFFF) + aZones[i].m_numTreadablesPlus10m++; + for(int k = 0; k < aZones[i].m_groupIndexCount[2]; k++) + if(pTempArrayIndices[j++] != 0xFFFF) + aZones[i].m_numTreadables++; + } + + // Now copy the actually used indices + EntityIndicesUsed = 0; + for(int i = 0; i < TempEntityIndicesUsed; i++) + if(pTempArrayIndices[i] != 0xFFFF){ + assert(EntityIndicesUsed < NUMZONEINDICES); + if(pTempArrayIndices[i] < NUMUNCOMPRESSED) + aIndices[EntityIndicesUsed++] = pTempArrayIndices[i]; + else + aIndices[EntityIndicesUsed++] = pTempArrayIndices[i] + numTempIndices; + } + for(int i = 0; i < numExtraIndices; i++) + if(extraIndices[i] != 0xFFFF){ + assert(EntityIndicesUsed < NUMZONEINDICES); + if(extraIndices[i] < NUMUNCOMPRESSED) + aIndices[EntityIndicesUsed++] = extraIndices[i]; + else + aIndices[EntityIndicesUsed++] = extraIndices[i] + numTempIndices; + } +} + + + +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 < NUMUNCOMPRESSED){ + CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = false; + bb = CCullZones::aPointersToBigBuildingsForBuildings[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false; + }else{ + i -= NUMUNCOMPRESSED; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffLeavingZone_OneTreadableBoth(uint16 i) +{ + int16 bb; + int j; + + if(i < NUMUNCOMPRESSED){ + 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 -= NUMUNCOMPRESSED; + 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 < NUMUNCOMPRESSED){ + CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = true; + bb = CCullZones::aPointersToBigBuildingsForBuildings[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true; + }else{ + i -= NUMUNCOMPRESSED; + for(j = 0; j < 3; j++) + DoStuffEnteringZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone_OneTreadablePlus10m(uint16 i) +{ + int16 bb; + int j; + + if(i < NUMUNCOMPRESSED){ + 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 -= NUMUNCOMPRESSED; + for(j = 0; j < 3; j++) + DoStuffEnteringZone_OneTreadablePlus10m(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone_OneTreadable(uint16 i) +{ + int16 bb; + int j; + + if(i < NUMUNCOMPRESSED){ + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true; + bb = CCullZones::aPointersToBigBuildingsForTreadables[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true; + }else{ + i -= NUMUNCOMPRESSED; + for(j = 0; j < 3; j++) + DoStuffEnteringZone_OneTreadable(CCullZones::aIndices[i+j]); + } +} + +float +CCullZone::CalcDistToCullZoneSquared(float x, float y) +{ + float rx, ry; + + if (x < minx) rx = sq(x - minx); + else if (x > maxx) rx = sq(x - maxx); + else rx = 0.0f; + + if (y < miny) ry = sq(y - miny); + else if (y > maxy) ry = sq(y - maxy); + else ry = 0.0f; + + return rx + ry; +} + +bool +CCullZone::TestLine(CVector vec1, CVector vec2) +{ + CColPoint colPoint; + CEntity *entity; + + if (CWorld::ProcessLineOfSight(vec1, vec2, colPoint, entity, true, false, false, false, false, true, false)) + return true; + if (CWorld::ProcessLineOfSight(CVector(vec1.x + 0.05f, vec1.y, vec1.z), CVector(vec2.x + 0.05f, vec2.y, vec2.z), colPoint, entity, true, false, false, false, false, true, false)) + return true; + if (CWorld::ProcessLineOfSight(CVector(vec1.x - 0.05f, vec1.y, vec1.z), CVector(vec2.x - 0.05f, vec2.y, vec2.z), colPoint, entity, true, false, false, false, false, true, false)) + return true; + if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y + 0.05f, vec1.z), CVector(vec2.x, vec2.y + 0.05f, vec2.z), colPoint, entity, true, false, false, false, false, true, false)) + return true; + if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y - 0.05f, vec1.z), CVector(vec2.x, vec2.y - 0.05f, vec2.z), colPoint, entity, true, false, false, false, false, true, false)) + return true; + if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y, vec1.z + 0.05f), CVector(vec2.x, vec2.y, vec2.z + 0.05f), colPoint, entity, true, false, false, false, false, true, false)) + return true; + return CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y, vec1.z - 0.05f), CVector(vec2.x, vec2.y, vec2.z - 0.05f), colPoint, entity, true, false, false, false, false, true, false); +} + +bool +CCullZone::DoThoroughLineTest(CVector start, CVector end, CEntity *testEntity) +{ + CColPoint colPoint; + CEntity *entity; + + if(CWorld::ProcessLineOfSight(start, end, colPoint, entity, true, false, false, false, false, true, false) && + testEntity != entity) + return false; + + CVector side; +#ifdef FIX_BUGS + if(start.x != end.x || start.y != end.y) +#else + if(start.x != end.x && start.y != end.y) +#endif + side = CVector(0.0f, 0.0f, 1.0f); + else + side = CVector(1.0f, 0.0f, 0.0f); + CVector up = CrossProduct(side, end - start); + side = CrossProduct(up, end - start); + side.Normalise(); + up.Normalise(); + side *= 0.1f; + up *= 0.1f; + + if(CWorld::ProcessLineOfSight(start+side, end+side, colPoint, entity, true, false, false, false, false, true, false) && + testEntity != entity) + return false; + if(CWorld::ProcessLineOfSight(start-side, end-side, colPoint, entity, true, false, false, false, false, true, false) && + testEntity != entity) + return false; + if(CWorld::ProcessLineOfSight(start+up, end+up, colPoint, entity, true, false, false, false, false, true, false) && + testEntity != entity) + return false; + if(CWorld::ProcessLineOfSight(start-up, end-up, colPoint, entity, true, false, false, false, false, true, false) && + testEntity != entity) + return false; + return true; +} + +bool +CCullZone::IsEntityCloseEnoughToZone(CEntity *entity, bool checkLevel) +{ + const CVector &pos = entity->GetPosition(); + + CSimpleModelInfo *minfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(entity->GetModelIndex()); + float distToZone = CalcDistToCullZone(pos.x, pos.y); + float lodDist; + if (minfo->m_noFade) + lodDist = minfo->GetLargestLodDistance() + STREAM_DISTANCE; + else + lodDist = minfo->GetLargestLodDistance() + STREAM_DISTANCE + FADE_DISTANCE; + + if (lodDist > distToZone) return true; + if (!checkLevel) return false; + CVector tempPos(minx, miny, minz); + return CTheZones::GetLevelFromPosition(&pos) == CTheZones::GetLevelFromPosition(&tempPos); +} + +bool +CCullZone::PointFallsWithinZone(CVector pos, float radius) +{ + if(minx - radius > pos.x || + maxx + radius < pos.x || + miny - radius > pos.y || + maxy + radius < pos.y || + minz - radius > pos.z || + maxz + radius < pos.z) + return false; + return true; +} + + +CVector ExtraFudgePointsCoors[] = { + CVector(978.0f, -394.0f, 18.0f), + CVector(1189.7f, -414.6f, 27.0f), + CVector(978.8f, -391.0f, 19.0f), + CVector(1199.0f, -502.3f, 28.0f), + CVector(1037.0f, -391.9f, 18.4f), + CVector(1140.0f, -608.7f, 16.0f), + CVector(1051.0f, -26.0f, 11.0f), + CVector(951.5f, -345.1f, 12.0f), + CVector(958.2f, -394.6f, 16.0f), + CVector(1036.5f, -390.0f, 15.2f), + CVector(960.6f, -390.5f, 20.9f), + CVector(1061.0f, -640.6f, 16.3f), + CVector(1034.5f, -388.96f, 14.78f), + CVector(1038.4f, -13.98f, 12.2f), + CVector(1047.2f, -16.7f, 10.6f), + CVector(1257.9f, -333.3f, 40.0f), + CVector(885.6f, -424.9f, 17.0f), + CVector(1127.5f, -795.8f, 17.7f), + CVector(1133.0f, -716.0f, 19.0f), + CVector(1125.0f, -694.0f, 18.5f), + CVector(1125.0f, -670.0f, 16.3f), + CVector(1051.6f, 36.3f, 17.9f), + CVector(1054.6f, -11.4f, 15.0f), + CVector(1058.9f, -278.0f, 15.0f), + CVector(1059.4f, -261.0f, 10.9f), + CVector(1051.5f, -638.5f, 16.5f), + CVector(1058.2f, -643.4f, 15.5f), + CVector(1058.2f, -643.4f, 18.0f), + CVector(826.0f, -260.0f, 7.0f), + CVector(826.0f, -260.0f, 11.0f), + CVector(833.0f, -603.6f, 16.4f), + CVector(833.0f, -603.6f, 20.0f), + CVector(1002.0f, -318.5f, 10.5f), + CVector(998.0f, -318.0f, 9.8f), + CVector(1127.0f, -183.0f, 18.1f), + CVector(1123.0f, -331.5f, 23.8f), + CVector(1123.8f, -429.0f, 24.0f), + CVector(1197.0f, -30.0f, 13.7f), + CVector(1117.5f, -230.0f, 17.3f), + CVector(1117.5f, -230.0f, 20.0f), + CVector(1120.0f, -281.6f, 21.5f), + CVector(1120.0f, -281.6f, 24.0f), + CVector(1084.5f, -1022.7f, 17.0f), + CVector(1071.5f, 5.4f, 4.6f), + CVector(1177.2f, -215.7f, 27.6f), + CVector(841.6f, -460.0f, 19.7f), + CVector(874.8f, -456.6f, 16.6f), + CVector(918.3f, -451.8f, 17.8f), + CVector(844.0f, -495.7f, 16.7f), + CVector(842.0f, -493.4f, 21.0f), + CVector(1433.5f, -774.4f, 16.9f), + CVector(1051.0f, -205.0f, 7.5f), + CVector(885.5f, -425.6f, 15.6f), + CVector(182.6f, -470.4f, 27.8f), + CVector(132.5f, -930.2f, 29.0f), + CVector(124.7f, -904.0f, 28.0f), + CVector(-50.0f, -686.0f, 22.0f), + CVector(-49.1f, -694.5f, 22.5f), + CVector(1063.8f, -404.45f, 16.2f), + CVector(1062.2f, -405.5f, 17.0f) +}; +int32 NumTestPoints; +int32 aTestPointsX[100]; +int32 aTestPointsY[100]; +int32 aTestPointsZ[100]; +CVector aTestPoints[100]; +int32 ElementsX, ElementsY, ElementsZ; +float StepX, StepY, StepZ; +int32 Memsize; +uint8 *pMem; +#define MEM(x, y, z) pMem[((x)*ElementsY + (y))*ElementsZ + (z)] +#define FLAG_FREE 1 +#define FLAG_PROCESSED 2 + +int32 MinValX, MaxValX; +int32 MinValY, MaxValY; +int32 MinValZ, MaxValZ; +int32 Point1, Point2; +int32 NewPointX, NewPointY, NewPointZ; + + +void +CCullZone::FindTestPoints() +{ + static int CZNumber; + + NumTestPoints = 0; + ElementsX = (maxx-minx) < 1.0f ? 2 : (maxx-minx)+1.0f; + ElementsY = (maxy-miny) < 1.0f ? 2 : (maxy-miny)+1.0f; + ElementsZ = (maxz-minz) < 1.0f ? 2 : (maxz-minz)+1.0f; + if(ElementsX > 32) ElementsX = 32; + if(ElementsY > 32) ElementsY = 32; + if(ElementsZ > 32) ElementsZ = 32; + Memsize = ElementsX * ElementsY * ElementsZ; + StepX = (maxx-minx)/(ElementsX-1); + StepY = (maxy-miny)/(ElementsY-1); + StepZ = (maxz-minz)/(ElementsZ-1); + + pMem = new uint8[Memsize]; + memset(pMem, 0, Memsize); + + // indices of center + int x = ElementsX * (position.x-minx)/(maxx-minx); + x = Clamp(x, 0, ElementsX-1); + int y = ElementsY * (position.y-miny)/(maxy-miny); + y = Clamp(y, 0, ElementsY-1); + int z = ElementsZ * (position.z-minz)/(maxz-minz); + z = Clamp(z, 0, ElementsZ-1); + + // Mark which test points inside the zone are not occupied by buildings. + // To do this, mark the start point as free and do a food fill. + + // NB: we just assume the start position is free here! + MEM(x, y, z) |= FLAG_FREE; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + NumTestPoints++; + + bool notDoneYet; + do{ + notDoneYet = false; + for(x = 0; x < ElementsX; x++){ + for(y = 0; y < ElementsY; y++){ + for(z = 0; z < ElementsZ; z++){ + if(!(MEM(x, y, z) & FLAG_FREE) || MEM(x, y, z) & FLAG_PROCESSED) + continue; + + float pX = x*StepX + minx; + float pY = y*StepY + miny; + float pZ = z*StepZ + minz; + + if(x > 0 && !(MEM(x-1, y, z) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX-StepX, pY, pZ))) + MEM(x-1, y, z) |= FLAG_FREE; + if(x < ElementsX-1 && !(MEM(x+1, y, z) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX+StepX, pY, pZ))) + MEM(x+1, y, z) |= FLAG_FREE; + + if(y > 0 && !(MEM(x, y-1, z) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX, pY-StepY, pZ))) + MEM(x, y-1, z) |= FLAG_FREE; + if(y < ElementsY-1 && !(MEM(x, y+1, z) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX, pY+StepY, pZ))) + MEM(x, y+1, z) |= FLAG_FREE; + + if(z > 0 && !(MEM(x, y, z-1) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX, pY, pZ-StepZ))) + MEM(x, y, z-1) |= FLAG_FREE; + if(z < ElementsZ-1 && !(MEM(x, y, z+1) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX, pY, pZ+StepZ))) + MEM(x, y, z+1) |= FLAG_FREE; + + notDoneYet = true; + MEM(x, y, z) |= FLAG_PROCESSED; + } + } + } + }while(notDoneYet); + + bool done; + + // Find bound planes of free space + + // increase x, bounds in y and z + x = 0; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(y = 0; y < ElementsY; y++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, y, z) & FLAG_FREE){ + if(y + z < minA){ + minA = y + z; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(y + z > maxA){ + maxA = y + z; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(y - z < minB){ + minB = y - z; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(y - z > maxB){ + maxB = y - z; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + x++; + }while(!done); + NumTestPoints += 4; + + // decrease x, bounds in y and z + x = ElementsX-1; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(y = 0; y < ElementsY; y++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, y, z) & FLAG_FREE){ + if(y + z < minA){ + minA = y + z; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(y + z > maxA){ + maxA = y + z; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(y - z < minB){ + minB = y - z; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(y - z > maxB){ + maxB = y - z; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + x--; + }while(!done); + NumTestPoints += 4; + + // increase y, bounds in x and z + y = 0; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(x = 0; x < ElementsX; x++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, y, z) & FLAG_FREE){ + if(x + z < minA){ + minA = x + z; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(x + z > maxA){ + maxA = x + z; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(x - z < minB){ + minB = x - z; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(x - z > maxB){ + maxB = x - z; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + y++; + }while(!done); + NumTestPoints += 4; + + // decrease y, bounds in x and z + y = ElementsY-1; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(x = 0; x < ElementsX; x++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, y, z) & FLAG_FREE){ + if(x + z < minA){ + minA = x + z; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(x + z > maxA){ + maxA = x + z; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(x - z < minB){ + minB = x - z; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(x - z > maxB){ + maxB = x - z; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + y--; + }while(!done); + NumTestPoints += 4; + + // increase z, bounds in x and y + z = 0; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(x = 0; x < ElementsX; x++) + for(y = 0; y < ElementsY; y++) + if(MEM(x, y, z) & FLAG_FREE){ + if(x + y < minA){ + minA = x + y; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(x + y > maxA){ + maxA = x + y; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(x - y < minB){ + minB = x - y; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(x - y > maxB){ + maxB = x - y; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + z++; + }while(!done); + NumTestPoints += 4; + + // decrease z, bounds in x and y + z = ElementsZ-1; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(x = 0; x < ElementsX; x++) + for(y = 0; y < ElementsY; y++) + if(MEM(x, y, z) & FLAG_FREE){ + if(x + y < minA){ + minA = x + y; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(x + y > maxA){ + maxA = x + y; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(x - y < minB){ + minB = x - y; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(x - y > maxB){ + maxB = x - y; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + z--; + }while(!done); + NumTestPoints += 4; + + // divide the axis aligned bounding planes into 4 and place some test points + + // x = 0 plane + MinValY = 999999; + MinValZ = 999999; + MaxValY = 0; + MaxValZ = 0; + for(y = 0; y < ElementsY; y++) + for(z = 0; z < ElementsZ; z++) + if(MEM(0, y, z) & FLAG_FREE){ + if(y < MinValY) MinValY = y; + if(z < MinValZ) MinValZ = z; + if(y > MaxValY) MaxValY = y; + if(z > MaxValZ) MaxValZ = z; + } + // pick 4 points in the found bounds and add new test points + if(MaxValY != 0 && MaxValZ != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointY = (Point1 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY; + NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ; + if(MEM(0, NewPointY, NewPointZ) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = 0; + aTestPointsY[NumTestPoints] = NewPointY; + aTestPointsZ[NumTestPoints] = NewPointZ; + NumTestPoints++; + } + } + + // x = ElementsX-1 plane + MinValY = 999999; + MinValZ = 999999; + MaxValY = 0; + MaxValZ = 0; + for(y = 0; y < ElementsY; y++) + for(z = 0; z < ElementsZ; z++) + if(MEM(ElementsX-1, y, z) & FLAG_FREE){ + if(y < MinValY) MinValY = y; + if(z < MinValZ) MinValZ = z; + if(y > MaxValY) MaxValY = y; + if(z > MaxValZ) MaxValZ = z; + } + // pick 4 points in the found bounds and add new test points + if(MaxValY != 0 && MaxValZ != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointY = (Point1 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY; + NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ; + if(MEM(ElementsX-1, NewPointY, NewPointZ) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = ElementsX-1; + aTestPointsY[NumTestPoints] = NewPointY; + aTestPointsZ[NumTestPoints] = NewPointZ; + NumTestPoints++; + } + } + + // y = 0 plane + MinValX = 999999; + MinValZ = 999999; + MaxValX = 0; + MaxValZ = 0; + for(x = 0; x < ElementsX; x++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, 0, z) & FLAG_FREE){ + if(x < MinValX) MinValX = x; + if(z < MinValZ) MinValZ = z; + if(x > MaxValX) MaxValX = x; + if(z > MaxValZ) MaxValZ = z; + } + // pick 4 points in the found bounds and add new test points + if(MaxValX != 0 && MaxValZ != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX; + NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ; + if(MEM(NewPointX, 0, NewPointZ) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = NewPointX; + aTestPointsY[NumTestPoints] = 0; + aTestPointsZ[NumTestPoints] = NewPointZ; + NumTestPoints++; + } + } + + // y = ElementsY-1 plane + MinValX = 999999; + MinValZ = 999999; + MaxValX = 0; + MaxValZ = 0; + for(x = 0; x < ElementsX; x++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, ElementsY-1, z) & FLAG_FREE){ + if(x < MinValX) MinValX = x; + if(z < MinValZ) MinValZ = z; + if(x > MaxValX) MaxValX = x; + if(z > MaxValZ) MaxValZ = z; + } + // pick 4 points in the found bounds and add new test points + if(MaxValX != 0 && MaxValZ != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX; + NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ; + if(MEM(NewPointX, ElementsY-1, NewPointZ) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = NewPointX; + aTestPointsY[NumTestPoints] = ElementsY-1; + aTestPointsZ[NumTestPoints] = NewPointZ; + NumTestPoints++; + } + } + + // z = 0 plane + MinValX = 999999; + MinValY = 999999; + MaxValX = 0; + MaxValY = 0; + for(x = 0; x < ElementsX; x++) + for(y = 0; y < ElementsY; y++) + if(MEM(x, y, 0) & FLAG_FREE){ + if(x < MinValX) MinValX = x; + if(y < MinValY) MinValY = y; + if(x > MaxValX) MaxValX = x; + if(y > MaxValY) MaxValY = y; + } + // pick 4 points in the found bounds and add new test points + if(MaxValX != 0 && MaxValY != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX; + NewPointY = (Point2 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY; + if(MEM(NewPointX, NewPointY, 0) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = NewPointX; + aTestPointsY[NumTestPoints] = NewPointY; + aTestPointsZ[NumTestPoints] = 0; + NumTestPoints++; + } + } + + // z = ElementsZ-1 plane + MinValX = 999999; + MinValY = 999999; + MaxValX = 0; + MaxValY = 0; + for(x = 0; x < ElementsX; x++) + for(y = 0; y < ElementsY; y++) + if(MEM(x, y, ElementsZ-1) & FLAG_FREE){ + if(x < MinValX) MinValX = x; + if(y < MinValY) MinValY = y; + if(x > MaxValX) MaxValX = x; + if(y > MaxValY) MaxValY = y; + } + // pick 4 points in the found bounds and add new test points + if(MaxValX != 0 && MaxValY != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX; + NewPointY = (Point2 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY; + if(MEM(NewPointX, NewPointY, ElementsZ-1) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = NewPointX; + aTestPointsY[NumTestPoints] = NewPointY; + aTestPointsZ[NumTestPoints] = ElementsZ-1; + NumTestPoints++; + } + } + + // add some hardcoded test points + for(int i = 0; i < ARRAY_SIZE(ExtraFudgePointsCoors); i++) + if(PointFallsWithinZone(ExtraFudgePointsCoors[i], 0.0f)){ + x = ElementsX * (ExtraFudgePointsCoors[i].x-minx)/(maxx-minx); + y = ElementsY * (ExtraFudgePointsCoors[i].y-miny)/(maxy-miny); + z = ElementsZ * (ExtraFudgePointsCoors[i].z-minz)/(maxz-minz); + if(MEM(x, y, z) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + NumTestPoints++; + } + } + + // remove duplicate points + for(int i = 0; i < NumTestPoints; i++) + for(int j = i+1; j < NumTestPoints; j++) + if(aTestPointsX[j] == aTestPointsX[i] && + aTestPointsY[j] == aTestPointsY[i] && + aTestPointsZ[j] == aTestPointsZ[i]){ + // get rid of [j] + for(int k = j; k < NumTestPoints-1; k++){ + aTestPointsX[k] = aTestPointsX[k+1]; + aTestPointsY[k] = aTestPointsY[k+1]; + aTestPointsZ[k] = aTestPointsZ[k+1]; + } + NumTestPoints--; + } + + // convert points to floating point + for(int i = 0; i < NumTestPoints; i++){ + aTestPoints[i].x = aTestPointsX[i]*StepX + minx; + aTestPoints[i].y = aTestPointsY[i]*StepY + miny; + aTestPoints[i].z = aTestPointsZ[i]*StepZ + minz; + } + + CZNumber++; + + delete[] pMem; + pMem = nil; +} + +bool +CCullZone::TestEntityVisibilityFromCullZone(CEntity *entity, float extraDist, CEntity *LODentity) +{ + CColModel *colmodel = entity->GetColModel(); + float boundMaxX = colmodel->boundingBox.max.x; + float boundMaxY = colmodel->boundingBox.max.y; + float boundMaxZ = colmodel->boundingBox.max.z; + float boundMinX = colmodel->boundingBox.min.x; + float boundMinY = colmodel->boundingBox.min.y; + float boundMinZ = colmodel->boundingBox.min.z; + if(LODentity){ + colmodel = LODentity->GetColModel(); + boundMaxX = Max(boundMaxX, colmodel->boundingBox.max.x); + boundMaxY = Max(boundMaxY, colmodel->boundingBox.max.y); + boundMaxZ = Max(boundMaxZ, colmodel->boundingBox.max.z); + boundMinX = Min(boundMinX, colmodel->boundingBox.min.x); + boundMinY = Min(boundMinY, colmodel->boundingBox.min.y); + boundMinZ = Min(boundMinZ, colmodel->boundingBox.min.z); + } + + if(boundMaxZ-boundMinZ + extraDist < 0.5f) + boundMaxZ = boundMinZ + 0.5f; + else + boundMaxZ += extraDist; + + CVector vecMin = entity->GetMatrix() * CVector(boundMinX, boundMinY, boundMinZ); + CVector vecMaxX = entity->GetMatrix() * CVector(boundMaxX, boundMinY, boundMinZ); + CVector vecMaxY = entity->GetMatrix() * CVector(boundMinX, boundMaxY, boundMinZ); + CVector vecMaxZ = entity->GetMatrix() * CVector(boundMinX, boundMinY, boundMaxZ); + CVector dirx = vecMaxX - vecMin; + CVector diry = vecMaxY - vecMin; + CVector dirz = vecMaxZ - vecMin; + + // If building intersects zone at all, it's visible + int x, y, z; + for(x = 0; x < 9; x++){ + CVector posX = vecMin + x/8.0f*dirx; + for(y = 0; y < 9; y++){ + CVector posY = posX + y/8.0f*diry; + for(z = 0; z < 9; z++){ + CVector posZ = posY + z/8.0f*dirz; + if(PointFallsWithinZone(posZ, 2.0f)) + return true; + } + } + } + + float distToZone = CalcDistToCullZone(entity->GetPosition().x, entity->GetPosition().y)/15.0f; + distToZone = Max(distToZone, 7.0f); + int numX = (boundMaxX - boundMinX)/distToZone + 2.0f; + int numY = (boundMaxY - boundMinY)/distToZone + 2.0f; + int numZ = (boundMaxZ - boundMinZ)/distToZone + 2.0f; + + float stepX = 1.0f/(numX-1); + float stepY = 1.0f/(numY-1); + float stepZ = 1.0f/(numZ-1); + float midX = (boundMaxX + boundMinX)/2.0f; + float midY = (boundMaxY + boundMinY)/2.0f; + float midZ = (boundMaxZ + boundMinZ)/2.0f; + + // check both xy planes + for(int i = 0; i < NumTestPoints; i++){ + CVector testPoint = aTestPoints[i]; + CVector mid = entity->GetMatrix() * CVector(midX, midY, midZ); + mid.z += 0.1f; + if(DoThoroughLineTest(testPoint, mid, entity)) + return true; + + CVector ray = entity->GetPosition() - testPoint; + + float dotX = DotProduct(ray, dirx); + float dotY = DotProduct(ray, diry); + float dotZ = DotProduct(ray, dirz); + + for(x = 0; x < numX; x++){ + CVector pMinZ = vecMin + x*stepX*dirx; + CVector pMaxZ = vecMin + x*stepX*dirx + dirz; + for(y = 0; y < numY; y++) + if(dotZ > 0.0f){ + if(DoThoroughLineTest(testPoint, pMinZ + y*stepY*diry, entity)) + return true; + }else{ + if(DoThoroughLineTest(testPoint, pMaxZ + y*stepY*diry, entity)) + return true; + } + } + + for(x = 0; x < numX; x++){ + CVector pMinY = vecMin + x*stepX*dirx; + CVector pMaxY = vecMin + x*stepX*dirx + diry; + for(z = 1; z < numZ-1; z++) // edge cases already handled + if(dotY > 0.0f){ + if(DoThoroughLineTest(testPoint, pMinY + z*stepZ*dirz, entity)) + return true; + }else{ + if(DoThoroughLineTest(testPoint, pMaxY + z*stepZ*dirz, entity)) + return true; + } + } + + for(y = 1; y < numY-1; y++){ // edge cases already handled + CVector pMinX = vecMin + y*stepY*diry; + CVector pMaxX = vecMin + y*stepY*diry + dirx; + for(z = 1; z < numZ-1; z++) // edge cases already handled + if(dotX > 0.0f){ + if(DoThoroughLineTest(testPoint, pMinX + z*stepZ*dirz, entity)) + return true; + }else{ + if(DoThoroughLineTest(testPoint, pMaxX + z*stepZ*dirz, entity)) + return true; + } + } + } + + return false; +} diff --git a/src/core/ZoneCull.h b/src/core/ZoneCull.h new file mode 100644 index 0000000..10742ff --- /dev/null +++ b/src/core/ZoneCull.h @@ -0,0 +1,137 @@ +class CEntity; + +class CCullZone +{ +public: + CVector position; + float minx; + float maxx; + float miny; + float maxy; + float minz; + float maxz; + + int32 m_indexStart; + int16 m_groupIndexCount[3]; // only useful during resolution stage + 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); + + + static bool TestLine(CVector vec1, CVector vec2); + static bool DoThoroughLineTest(CVector vec1, CVector vec2, CEntity *testEntity); + float CalcDistToCullZoneSquared(float x, float y); + float CalcDistToCullZone(float x, float y) { return Sqrt(CalcDistToCullZoneSquared(x, y)); }; + bool IsEntityCloseEnoughToZone(CEntity* entity, bool checkLevel); + bool PointFallsWithinZone(CVector pos, float radius); + bool TestEntityVisibilityFromCullZone(CEntity *entity, float extraDist, CEntity *LODentity); + void FindTestPoints(); + + void GetGroupStartAndSize(int32 groupid, int32 &start, int32 &size) { + switch (groupid) { + case 0: + default: + // buildings + start = m_indexStart; + size = m_groupIndexCount[0]; + break; + case 1: + // treadables + 10m + start = m_groupIndexCount[0] + m_indexStart; + size = m_groupIndexCount[1]; + break; + case 2: + // treadables + start = m_groupIndexCount[0] + m_groupIndexCount[1] + m_indexStart; + size = m_groupIndexCount[2]; + break; + } + } +}; + +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; } + + static void BuildListForBigBuildings(); + static void DoVisibilityTestCullZone(int zoneId, bool doIt); + static bool DoWeHaveMoreThanXOccurencesOfSet(int32 count, uint16 *set); + + static void CompressIndicesArray(); + static bool PickRandomSetForGroup(int32 zone, int32 group, uint16 *set); + static void ReplaceSetForAllGroups(uint16 *set, uint16 setid); + static void TidyUpAndMergeLists(uint16 *extraIndices, int32 numExtraIndices); + + // debug + static bool LoadTempFile(void); + static void SaveTempFile(void); +}; diff --git a/src/core/Zones.cpp b/src/core/Zones.cpp new file mode 100644 index 0000000..82fbc04 --- /dev/null +++ b/src/core/Zones.cpp @@ -0,0 +1,821 @@ +#include "common.h" + +#include + +#include "Zones.h" + +#include "Clock.h" +#include "Text.h" +#include "World.h" +#include "Timer.h" +#include "SaveBuf.h" + +#ifdef COMPATIBLE_SAVES +#define ZONEARRAY_SAVE_SIZE 0xAF0 +#define MAPZONEARRAY_SAVE_SIZE 0x578 +#else +#define ZONEARRAY_SAVE_SIZE sizeof(ZoneArray) +#define MAPZONEARRAY_SAVE_SIZE sizeof(MapZoneArray) +#endif + +eLevelName CTheZones::m_CurrLevel; +CZone *CTheZones::m_pPlayersZone; +int16 CTheZones::FindIndex; + +uint16 CTheZones::NumberOfAudioZones; +int16 CTheZones::AudioZoneArray[NUMAUDIOZONES]; +uint16 CTheZones::TotalNumberOfMapZones; +uint16 CTheZones::TotalNumberOfZones; +CZone CTheZones::ZoneArray[NUMZONES]; +CZone CTheZones::MapZoneArray[NUMMAPZONES]; +uint16 CTheZones::TotalNumberOfZoneInfos; +CZoneInfo CTheZones::ZoneInfoArray[2*NUMZONES]; + +#define SWAPF(a, b) { float t; t = a; a = b; b = t; } + +inline bool IsNormalZone(int type) { return type == ZONE_DEFAULT || type == ZONE_NAVIG || type == ZONE_INFO; } + +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; + + for(i = 0; i < NUMZONES; i++) + memset(&ZoneArray[i], 0, sizeof(CZone)); + + 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? + TotalNumberOfZones = 1; + + m_CurrLevel = LEVEL_GENERIC; + m_pPlayersZone = &ZoneArray[0]; + + 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_GENERIC; + + for(i = 0; i < NUMMAPZONES; i++){ + memset(&MapZoneArray[i], 0, sizeof(CZone)); + MapZoneArray[i].type = ZONE_MAPZONE; + } + + TotalNumberOfMapZones = 1; + + 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_GENERIC; +} + +void +CTheZones::Update(void) +{ +#ifdef SQUEEZE_PERFORMANCE + if (CTimer::GetFrameCounter() % 5 != 0) + return; +#endif + 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) +{ + char *p; + char tmpname[8]; + + 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 + strncpy(tmpname, name, 7); + tmpname[7] = '\0'; + strcpy(ZoneArray[TotalNumberOfZones].name, tmpname); + ZoneArray[TotalNumberOfZones].type = type; + ZoneArray[TotalNumberOfZones].minx = minx; + ZoneArray[TotalNumberOfZones].miny = miny; + ZoneArray[TotalNumberOfZones].minz = minz; + ZoneArray[TotalNumberOfZones].maxx = maxx; + ZoneArray[TotalNumberOfZones].maxy = maxy; + ZoneArray[TotalNumberOfZones].maxz = maxz; + ZoneArray[TotalNumberOfZones].level = level; + if(IsNormalZone(type)){ + ZoneArray[TotalNumberOfZones].zoneinfoDay = TotalNumberOfZoneInfos++; + ZoneArray[TotalNumberOfZones].zoneinfoNight = TotalNumberOfZoneInfos++; + } + TotalNumberOfZones++; +} + +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(IsNormalZone(ZoneArray[0].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(IsNormalZone(zone->type)) + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +int16 +CTheZones::FindZoneByLabelAndReturnIndex(Const 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(IsNormalZone(zone->type)) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].carDensity = cardensity; +} + +void +CTheZones::SetPedDensity(uint16 zoneid, uint8 day, uint16 peddensity) +{ + CZone *zone; + zone = GetZone(zoneid); + if(IsNormalZone(zone->type)) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedDensity = peddensity; +} + +void +CTheZones::SetPedGroup(uint16 zoneid, uint8 day, uint16 pedgroup) +{ + CZone *zone; + zone = GetZone(zoneid); + if(IsNormalZone(zone->type)) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedGroup = pedgroup; +} + +int16 +CTheZones::FindAudioZone(CVector *pos) +{ + int i; + + for(i = 0; i < NumberOfAudioZones; i++) + if(PointLiesWithinZone(pos, GetAudioZone(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_GENERIC; +} + +void +CTheZones::AddZoneToAudioZoneArray(CZone *zone) +{ + int i, z; + + if(zone->type != ZONE_DEFAULT) + 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; + } + } +} + +#ifdef COMPATIBLE_SAVES +static inline void +SaveOneZone(CZone &zone, uint8 *&buffer) +{ + memcpy(buffer, zone.name, sizeof(zone.name)); + SkipSaveBuf(buffer, sizeof(zone.name)); + WriteSaveBuf(buffer, zone.minx); + WriteSaveBuf(buffer, zone.miny); + WriteSaveBuf(buffer, zone.minz); + WriteSaveBuf(buffer, zone.maxx); + WriteSaveBuf(buffer, zone.maxy); + WriteSaveBuf(buffer, zone.maxz); + WriteSaveBuf(buffer, zone.type); + WriteSaveBuf(buffer, zone.level); + WriteSaveBuf(buffer, zone.zoneinfoDay); + WriteSaveBuf(buffer, zone.zoneinfoNight); + WriteSaveBuf(buffer, (int32)CTheZones::GetIndexForZonePointer(zone.child)); + WriteSaveBuf(buffer, (int32)CTheZones::GetIndexForZonePointer(zone.parent)); + WriteSaveBuf(buffer, (int32)CTheZones::GetIndexForZonePointer(zone.next)); +} +#endif + +void +CTheZones::SaveAllZones(uint8 *buffer, uint32 *size) +{ + INITSAVEBUF + int i; + + *size = SAVE_HEADER_SIZE + + sizeof(int32) // GetIndexForZonePointer + + sizeof(m_CurrLevel) + sizeof(FindIndex) + + sizeof(int16) // padding + + ZONEARRAY_SAVE_SIZE + sizeof(ZoneInfoArray) + + sizeof(TotalNumberOfZones) + sizeof(TotalNumberOfZoneInfos) + + MAPZONEARRAY_SAVE_SIZE + sizeof(AudioZoneArray) + + sizeof(TotalNumberOfMapZones) + sizeof(NumberOfAudioZones); + + WriteSaveHeader(buffer, 'Z', 'N', 'S', '\0', *size - SAVE_HEADER_SIZE); + + WriteSaveBuf(buffer, (int32)GetIndexForZonePointer(m_pPlayersZone)); + WriteSaveBuf(buffer, m_CurrLevel); + WriteSaveBuf(buffer, FindIndex); + WriteSaveBuf(buffer, (int16)0); // padding + + for(i = 0; i < ARRAY_SIZE(ZoneArray); i++){ +#ifdef COMPATIBLE_SAVES + SaveOneZone(ZoneArray[i], buffer); +#else + CZone *zone = WriteSaveBuf(buffer, ZoneArray[i]); + zone->child = (CZone*)GetIndexForZonePointer(ZoneArray[i].child); + zone->parent = (CZone*)GetIndexForZonePointer(ZoneArray[i].parent); + zone->next = (CZone*)GetIndexForZonePointer(ZoneArray[i].next); +#endif + } + + for(i = 0; i < ARRAY_SIZE(ZoneInfoArray); i++) + WriteSaveBuf(buffer, ZoneInfoArray[i]); + + WriteSaveBuf(buffer, TotalNumberOfZones); + WriteSaveBuf(buffer, TotalNumberOfZoneInfos); + + for(i = 0; i < ARRAY_SIZE(MapZoneArray); i++) { +#ifndef COMPATIBLE_SAVES + CZone* zone = WriteSaveBuf(buffer, MapZoneArray[i]); +#endif + + /* + The call of GetIndexForZonePointer is wrong, as it is + meant for a different array, but the game doesn't brake + if those fields are nil. Let's make sure they are. + */ + assert(MapZoneArray[i].child == nil); + assert(MapZoneArray[i].parent == nil); + assert(MapZoneArray[i].next == nil); +#ifndef COMPATIBLE_SAVES + zone->child = (CZone*)GetIndexForZonePointer(MapZoneArray[i].child); + zone->parent = (CZone*)GetIndexForZonePointer(MapZoneArray[i].parent); + zone->next = (CZone*)GetIndexForZonePointer(MapZoneArray[i].next); +#else + SaveOneZone(MapZoneArray[i], buffer); +#endif + } + + for(i = 0; i < ARRAY_SIZE(AudioZoneArray); i++) + WriteSaveBuf(buffer, AudioZoneArray[i]); + + WriteSaveBuf(buffer, TotalNumberOfMapZones); + WriteSaveBuf(buffer, NumberOfAudioZones); + + VALIDATESAVEBUF(*size) +} + +#ifdef COMPATIBLE_SAVES +static inline void +LoadOneZone(CZone &zone, uint8 *&buffer) +{ + memcpy(zone.name, buffer, sizeof(zone.name)); + SkipSaveBuf(buffer, sizeof(zone.name)); + ReadSaveBuf(&zone.minx, buffer); + ReadSaveBuf(&zone.miny, buffer); + ReadSaveBuf(&zone.minz, buffer); + ReadSaveBuf(&zone.maxx, buffer); + ReadSaveBuf(&zone.maxy, buffer); + ReadSaveBuf(&zone.maxz, buffer); + ReadSaveBuf(&zone.type, buffer); + ReadSaveBuf(&zone.level, buffer); + ReadSaveBuf(&zone.zoneinfoDay, buffer); + ReadSaveBuf(&zone.zoneinfoNight, buffer); + int32 tmp; + ReadSaveBuf(&tmp, buffer); + zone.child = CTheZones::GetPointerForZoneIndex(tmp); + ReadSaveBuf(&tmp, buffer); + zone.parent = CTheZones::GetPointerForZoneIndex(tmp); + ReadSaveBuf(&tmp, buffer); + zone.next = CTheZones::GetPointerForZoneIndex(tmp); +} +#endif + +void +CTheZones::LoadAllZones(uint8 *buffer, uint32 size) +{ + INITSAVEBUF + int32 i; + + CheckSaveHeader(buffer, 'Z', 'N', 'S', '\0', size - SAVE_HEADER_SIZE); + + ReadSaveBuf(&i, buffer); + m_pPlayersZone = GetPointerForZoneIndex(i); + ReadSaveBuf(&m_CurrLevel, buffer); + ReadSaveBuf(&FindIndex, buffer); + SkipSaveBuf(buffer, 2); + + for(i = 0; i < ARRAY_SIZE(ZoneArray); i++){ +#ifdef COMPATIBLE_SAVES + LoadOneZone(ZoneArray[i], buffer); +#else + ReadSaveBuf(&ZoneArray[i], buffer); + + ZoneArray[i].child = GetPointerForZoneIndex((uintptr)ZoneArray[i].child); + ZoneArray[i].parent = GetPointerForZoneIndex((uintptr)ZoneArray[i].parent); + ZoneArray[i].next = GetPointerForZoneIndex((uintptr)ZoneArray[i].next); +#endif + } + + for(i = 0; i < ARRAY_SIZE(ZoneInfoArray); i++) + ReadSaveBuf(&ZoneInfoArray[i], buffer); + + ReadSaveBuf(&TotalNumberOfZones, buffer); + ReadSaveBuf(&TotalNumberOfZoneInfos, buffer); + + for(i = 0; i < ARRAY_SIZE(MapZoneArray); i++){ +#ifdef COMPATIBLE_SAVES + LoadOneZone(MapZoneArray[i], buffer); +#else + ReadSaveBuf(&MapZoneArray[i], buffer); + + /* + The call of GetPointerForZoneIndex is wrong, as it is + meant for a different array, but the game doesn't brake + if save data stored is -1. + */ + MapZoneArray[i].child = GetPointerForZoneIndex((uintptr)MapZoneArray[i].child); + MapZoneArray[i].parent = GetPointerForZoneIndex((uintptr)MapZoneArray[i].parent); + MapZoneArray[i].next = GetPointerForZoneIndex((uintptr)MapZoneArray[i].next); +#endif + assert(MapZoneArray[i].child == nil); + assert(MapZoneArray[i].parent == nil); + assert(MapZoneArray[i].next == nil); + } + + for(i = 0; i < ARRAY_SIZE(AudioZoneArray); i++) + ReadSaveBuf(&AudioZoneArray[i], buffer); + + ReadSaveBuf(&TotalNumberOfMapZones, buffer); + ReadSaveBuf(&NumberOfAudioZones, buffer); + + VALIDATESAVEBUF(size) +} diff --git a/src/core/Zones.h b/src/core/Zones.h new file mode 100644 index 0000000..aa0466e --- /dev/null +++ b/src/core/Zones.h @@ -0,0 +1,114 @@ +#pragma once + +#include "Game.h" +#include "Gangs.h" + +enum eZoneType +{ + ZONE_DEFAULT, + ZONE_NAVIG, + ZONE_INFO, + 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[NUM_GANGS]; + + // Ped data + uint16 pedDensity; + uint16 copDensity; + uint16 gangDensity[NUM_GANGS]; + uint16 pedGroup; +}; + + +class CTheZones +{ + 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]; +public: + static eLevelName m_CurrLevel; + + 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 CZone *GetAudioZone(uint16 i) { return &ZoneArray[AudioZoneArray[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(const CVector *v); + static CZone *FindSmallestZonePosition(const CVector *v); + static CZone *FindSmallestZonePositionType(const CVector *v, eZoneType type); + static CZone *FindSmallestZonePositionILN(const CVector *v); + static int16 FindZoneByLabelAndReturnIndex(Const 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(ssize_t i) { return i == -1 ? nil : &ZoneArray[i]; } + static ssize_t 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 0000000..0d0528b --- /dev/null +++ b/src/core/common.h @@ -0,0 +1,401 @@ +#pragma once + +#define _CRT_SECURE_NO_WARNINGS +#define _USE_MATH_DEFINES +#pragma warning(disable: 4244) // int to float +#pragma warning(disable: 4800) // int to bool +#pragma warning(disable: 4838) // narrowing conversion +#pragma warning(disable: 4996) // POSIX names + +#ifdef __MWERKS__ +#define __STDC_LIMIT_MACROS // so we get UINT32_MAX etc +#endif + +#ifdef __SWITCH__ +#include +#endif + +#include +#include +#include + +#ifdef __MWERKS__ +#define AUDIO_MSS +#define RWLIBS // codewarrior doesn't support project level defines - so not even this is enough, but still catches most ifdefs +#endif + +#if !defined RW_D3D9 && defined LIBRW +#undef WITHD3D +#undef WITHDINPUT +#endif + +#if (defined WITHD3D && !defined LIBRW) +#define WITHWINDOWS +#endif + +#if defined _WIN32 && defined WITHWINDOWS && !defined _INC_WINDOWS +#include +#endif + +#ifdef WITHD3D + #ifdef LIBRW + #define WITH_D3D // librw includes d3d9 itself via this right now + #else + #ifndef USE_D3D9 + #include + #else + #include + #endif + #endif +#endif + +#ifdef WITHDINPUT +#define DIRECTINPUT_VERSION 0x0800 +#include +#endif + +#include +#include + +// gotta put this somewhere +#ifdef LIBRW +#define STREAMPOS(str) ((str)->tell()) +#define STREAMFILE(str) (((rw::StreamFile*)(str))->file) +#define HIERNODEINFO(hier) ((hier)->nodeInfo) +#define HIERNODEID(hier, i) ((hier)->nodeInfo[i].id) +#define HANIMFRAME(anim, i) ((RwUInt8*)(anim)->keyframes + (i)*(anim)->interpInfo->animKeyFrameSize) +#else +#define RWHALFPIXEL // always d3d +#define STREAMPOS(str) ((str)->Type.memory.position) +#define STREAMFILE(str) ((str)->Type.file.fpFile) +#define HIERNODEINFO(hier) ((hier)->pNodeInfo) +#define HIERNODEID(hier, i) ((hier)->pNodeInfo[i].nodeID) +#define HANIMFRAME(anim, i) ((RwUInt8*)(anim)->pFrames + (i)*(anim)->interpInfo->keyFrameSize) +#define RpHAnimStdInterpFrame RpHAnimStdKeyFrame +#endif + +#ifdef RWHALFPIXEL +#define HALFPX (0.5f) +#else +#define HALFPX (0.0f) +#endif + +#define rwVENDORID_ROCKSTAR 0x0253F2 + +#define Max(a,b) ((a) > (b) ? (a) : (b)) +#define Min(a,b) ((a) < (b) ? (a) : (b)) + +// Use this to add const that wasn't there in the original code +#define Const const + +typedef uint8_t uint8; +typedef int8_t int8; +typedef uint16_t uint16; +typedef int16_t int16; +#ifndef __MWERKS__ +typedef uint32_t uint32; +typedef int32_t int32; +#else +typedef unsigned int uint32; +typedef int int32; +#endif +typedef uintptr_t uintptr; +typedef intptr_t intptr; +typedef uint64_t uint64; +typedef int64_t int64; +// hardcode ucs-2 +typedef uint16_t wchar; + +typedef uint8 bool8; +typedef uint16 bool16; +typedef uint32 bool32; + +#if defined(_MSC_VER) || defined (__MWERKS__) +typedef ptrdiff_t ssize_t; +#endif + +#ifndef nil +#define nil NULL +#endif + +#include "config.h" + +#ifdef PED_SKIN +#include +#include +#endif + +#ifdef __GNUC__ +#define TYPEALIGN(n) __attribute__ ((aligned (n))) +#else +#ifdef _MSC_VER +#define TYPEALIGN(n) __declspec(align(n)) +#else +#define TYPEALIGN(n) // unknown compiler...ignore +#endif +#endif + +#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 & (1<r == right.r && this->g == right.g && this->b == right.b && this->a == right.a; + } + + bool operator !=(const CRGBA &right) + { + return !(*this == right); + } + + CRGBA &operator =(const CRGBA &right) + { + this->r = right.r; + this->g = right.g; + this->b = right.b; + this->a = right.a; + return *this; + } +#ifdef RWCORE_H + operator RwRGBA &(void) { + return rwRGBA; + } + + operator RwRGBA *(void) { + return &rwRGBA; + } + + operator RwRGBA (void) const { + return rwRGBA; + } + + CRGBA &operator =(const RwRGBA &right) + { + this->r = right.red; + this->g = right.green; + this->b = right.blue; + this->a = right.alpha; + return *this; + } +#endif +}; + +#if (defined(_MSC_VER)) +extern int strcasecmp(const char *str1, const char *str2); +#endif + +extern wchar *AllocUnicode(const char*src); + +#define Clamp(v, low, high) ((v)<(low) ? (low) : (v)>(high) ? (high) : (v)) + +#define Clamp2(v, center, radius) ((v) > (center) ? Min(v, center + radius) : Max(v, center - radius)) + +inline float sq(float x) { return x*x; } +#define SQR(x) ((x) * (x)) + +#ifdef __MWERKS__ +#define M_E 2.71828182845904523536 // e +#define M_LOG2E 1.44269504088896340736 // log2(e) +#define M_LOG10E 0.434294481903251827651 // log10(e) +#define M_LN2 0.693147180559945309417 // ln(2) +#define M_LN10 2.30258509299404568402 // ln(10) +#define M_PI 3.14159265358979323846 // pi +#define M_PI_2 1.57079632679489661923 // pi/2 +#define M_PI_4 0.785398163397448309616 // pi/4 +#define M_1_PI 0.318309886183790671538 // 1/pi +#define M_2_PI 0.636619772367581343076 // 2/pi +#define M_2_SQRTPI 1.12837916709551257390 // 2/sqrt(pi) +#define M_SQRT2 1.41421356237309504880 // sqrt(2) +#define M_SQRT1_2 0.707106781186547524401 // 1/sqrt(2) +#endif + +#define PI (float)M_PI +#define TWOPI (PI*2) +#define HALFPI (PI/2) +#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(const char *format, ...); +void re3_trace(const char *filename, unsigned int lineno, const char *func, const char *format, ...); +void re3_assert(const char *expr, const char *filename, unsigned int lineno, const char *func); +void re3_usererror(const char *format, ...); + +#define DEBUGBREAK() __debugbreak(); + +// Switch to enable development messages. +#if 1 +#define DEV(f, ...) +#else +#define DEV(f, ...) re3_debug("[DEV]: " f, ## __VA_ARGS__) +#endif + +#ifdef __MWERKS__ +void debug(char *f, ...); +void Error(char *f, ...); +__inline__ void TRACE(char *f, ...) { } // this is re3 only, and so the function needs to be inline - this way no call actually gets placed +// USERERROR only gets used in oal builds ... once +#else +#define debug(f, ...) re3_debug("[DBG]: " f, ## __VA_ARGS__) +#define Error(f, ...) re3_debug("[ERROR]: " f, ## __VA_ARGS__) +#ifndef MASTER +#define TRACE(f, ...) re3_trace(__FILE__, __LINE__, __FUNCTION__, f, ## __VA_ARGS__) +#define USERERROR(f, ...) re3_usererror(f, ## __VA_ARGS__) +#else +#define TRACE(f, ...) +#define USERERROR(f, ...) +#endif +#endif + +#ifndef MASTER +#define assert(_Expression) (void)( (!!(_Expression)) || (re3_assert(#_Expression, __FILE__, __LINE__, __FUNCTION__), 0) ) +#else +#define assert(_Expression) (_Expression) +#endif +#define ASSERT assert + +#ifdef __MWERKS__ +#define static_assert(bool_constexpr, message) +#endif + +#define _TODO(x) +#define _TODOCONST(x) (x) + +#ifdef CHECK_STRUCT_SIZES +template struct check_size { + static_assert(s == t, "Invalid structure size"); +}; +#define VALIDATE_SIZE(struc, size) check_size struc ## Check +#else +#define VALIDATE_SIZE(struc, size) +#endif +#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 ABS(a) (((a) < 0) ? (-(a)) : (a)) +#define norm(value, min, max) (((value) < (min)) ? 0 : (((value) > (max)) ? 1 : (((value) - (min)) / ((max) - (min))))) +#define lerp(norm, min, max) ( (norm) * ((max) - (min)) + (min) ) + +#define STRINGIFY(x) #x +#define STR(x) STRINGIFY(x) +#define CONCAT_(x,y) x##y +#define CONCAT(x,y) CONCAT_(x,y) diff --git a/src/core/config.h b/src/core/config.h new file mode 100644 index 0000000..9f1981b --- /dev/null +++ b/src/core/config.h @@ -0,0 +1,498 @@ +#pragma once + +// disables (most) stuff that wasn't in original gta3.exe +#ifdef __MWERKS__ +#define VANILLA_DEFINES +#endif + +enum Config { + NUMPLAYERS = 1, // 4 on PS2 + + NUMCDIMAGES = 12, // gta3.img duplicates (not used on PC) + MAX_CDIMAGES = 8, // additional cdimages + MAX_CDCHANNELS = 5, + + MODELINFOSIZE = 5500, // 3150 on PS2 +#ifdef VANILLA_DEFINES + TXDSTORESIZE = 850, +#else + TXDSTORESIZE = 1024, // for Xbox map +#endif + EXTRADIRSIZE = 128, + CUTSCENEDIRSIZE = 512, + + SIMPLEMODELSIZE = 5000, // 2910 on PS2 + MLOMODELSIZE = 1, + MLOINSTANCESIZE = 1, + TIMEMODELSIZE = 30, + CLUMPMODELSIZE = 5, + PEDMODELSIZE = 90, + VEHICLEMODELSIZE = 120, // 70 on PS2 + XTRACOMPSMODELSIZE = 2, + TWODFXSIZE = 2000, // 1210 on PS2 + + 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, + NUMCUTSCENEOBJECTS = 50, + + NUMANIMBLOCKS = 2, + NUMANIMATIONS = 250, + + NUMTEMPOBJECTS = 30, + + // Path data + NUM_PATHNODES = 4930, + NUM_CARPATHLINKS = 2076, + NUM_MAPOBJECTS = 1250, + NUM_PATHCONNECTIONS = 10260, + + // Link list lengths + NUMALPHALIST = 20, + NUMALPHAENTITYLIST = 150, + NUMCOLCACHELINKS = 200, + NUMREFERENCES = 800, + + // Zones + NUMAUDIOZONES = 36, + NUMZONES = 50, + NUMMAPZONES = 25, + + // Cull zones + NUMCULLZONES = 512, + NUMATTRIBZONES = 288, + NUMZONEINDICES = 55000, + + PATHNODESIZE = 4500, + + NUMWEATHERS = 4, + NUMHOURS = 24, + + NUMEXTRADIRECTIONALS = 4, + NUMANTENNAS = 8, + NUMCORONAS = 56, + NUMPOINTLIGHTS = 32, + NUM3DMARKERS = 32, + NUMBRIGHTLIGHTS = 32, + NUMSHINYTEXTS = 32, + NUMMONEYMESSAGES = 16, + NUMPICKUPMESSAGES = 16, + NUMBULLETTRACES = 16, + NUMMBLURSTREAKS = 4, + NUMSKIDMARKS = 32, + + NUMONSCREENTIMERENTRIES = 1, + NUMRADARBLIPS = 32, + NUMGENERALPICKUPS = 320, + NUMSCRIPTEDPICKUPS = 16, + NUMPICKUPS = NUMGENERALPICKUPS + NUMSCRIPTEDPICKUPS, + NUMCOLLECTEDPICKUPS = 20, + NUMPACMANPICKUPS = 256, + NUMEVENTS = 64, + + NUM_CARGENS = 160, + + NUM_PATH_NODES_IN_AUTOPILOT = 8, + + NUM_ACCIDENTS = 20, + NUM_FIRES = 40, + NUM_GARAGES = 32, + NUM_PROJECTILES = 32, + + NUM_GLASSPANES = 45, + NUM_GLASSENTITIES = 32, + NUM_WATERCANNONS = 3, + + NUMPEDROUTES = 200, + NUMPHONES = 50, + NUMPEDGROUPS = 31, + NUMMODELSPERPEDGROUP = 8, + NUMSHOTINFOS = 100, + + NUMROADBLOCKS = 600, + + NUMVISIBLEENTITIES = 2000, + NUMINVISIBLEENTITIES = 150, + + NUM_AUDIOENTITY_EVENTS = 4, + NUM_PED_COMMENTS_SLOTS = 20, + + NUM_SOUND_QUEUES = 2, + NUM_AUDIOENTITIES = 200, + + NUM_SCRIPT_MAX_ENTITIES = 40, + + NUM_GARAGE_STORED_CARS = 6, + + NUM_CRANES = 8, + + NUM_EXPLOSIONS = 48, +}; + +// 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 + +// Version defines +#define GTA3_PS2_140 300 +#define GTA3_PS2_160 301 +#define GTA3_PC_10 310 +#define GTA3_PC_11 311 +#define GTA3_PC_STEAM 312 +// TODO? maybe something for xbox or android? + +#define GTA_VERSION GTA3_PC_11 + +// Enable configuration for handheld console ports +#if defined(__SWITCH__) || defined(PSP2) + #define GTA_HANDHELD +#endif + +#if defined GTA_PS2 +# define GTA_PS2_STUFF +# define RANDOMSPLASH +# define USE_CUSTOM_ALLOCATOR +# define VU_COLLISION +# define ANIM_COMPRESSION +# define PS2_MENU +#elif defined GTA_PC +# define EXTERNAL_3D_SOUND +# define AUDIO_REFLECTIONS +# ifndef GTA_HANDHELD +# define PC_PLAYER_CONTROLS // mouse player/cam mode +# endif +# define GTA_REPLAY +# define GTA_SCENE_EDIT +# define PC_MENU +#elif defined GTA_XBOX +#endif + +// 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 + +// once and for all: +// pc: FINAL & MASTER +// mobile: FINAL + +// MASTER builds must be FINAL +#ifdef MASTER +#define FINAL +#endif + +// these are placed here to work with VANILLA_DEFINES for compatibility +#define NO_CDCHECK // skip audio CD check +#define DEFAULT_NATIVE_RESOLUTION // Set default video mode to your native resolution (fixes Windows 10 launch) + +#ifdef VANILLA_DEFINES +#if !defined(_WIN32) || defined(__LP64__) || defined(_WIN64) +#error Vanilla can only be built for win-x86 +#endif + +#define FINAL +#define MASTER +//#define USE_MY_DOCUMENTS +#define THIS_IS_STUPID +#define PC_PARTICLE +#define DONT_FIX_REPLAY_BUGS +#define USE_TXD_CDIMAGE // generate and load textures from txd.img +//#define USE_TEXTURE_POOL // not possible because R* used custom RW33 +#else +// This enables things from the PS2 version on PC +#define GTA_PS2_STUFF + +// quality of life fixes that should also be in FINAL +#define NASTY_GAME // nasty game for all languages + +// those infamous texts +#define DRAW_GAME_VERSION_TEXT +#ifdef DRAW_GAME_VERSION_TEXT + // unlike R* development builds, ours has runtime switch on debug menu & .ini, and disabled as default. + // If you disable this then game will fetch version from peds.col, as R* did while in development. + //#define USE_OUR_VERSIONING // enabled from buildfiles by default +#endif +//#define DRAW_MENU_VERSION_TEXT + +// Memory allocation and compression +// #define USE_CUSTOM_ALLOCATOR // use CMemoryHeap for allocation. use with care, not finished yet +//#define COMPRESSED_COL_VECTORS // use compressed vectors for collision vertices +//#define ANIM_COMPRESSION // only keep most recently used anims uncompressed + +#if defined GTA_PC && defined GTA_PS2_STUFF +# define USE_PS2_RAND +# define RANDOMSPLASH // use random splash as on PS2 +# define PS2_MATFX +#endif + +#ifdef VU_COLLISION +#define COMPRESSED_COL_VECTORS // currently need compressed vectors in this code +#endif + +#ifdef MASTER + // only in master builds + #undef DRAW_GAME_VERSION_TEXT +#else + // not in master builds + #define VALIDATE_SAVE_SIZE + + #define DEBUGMENU +#endif + +#ifdef FINAL + // in all games +# define USE_MY_DOCUMENTS // use my documents directory for user files +#else + // not in any game +# define CHATTYSPLASH // print what the game is loading +# define TIMEBARS // print debug timers +#endif + +#define FIX_BUGS // fixes bugs that we've came across during reversing. You can undefine this only on release builds. +#define MORE_LANGUAGES // Add more translations to the game +#define COMPATIBLE_SAVES // this allows changing structs while keeping saves compatible, and keeps saves compatible between platforms, needs to be enabled on 64bit builds! +#define FIX_INCOMPATIBLE_SAVES // try to fix incompatible saves, requires COMPATIBLE_SAVES +#define LOAD_INI_SETTINGS // as the name suggests. fundamental for CUSTOM_FRONTEND_OPTIONS + +#define NO_MOVIES // add option to disable intro videos + +#define EXTENDED_OFFSCREEN_DESPAWN_RANGE // Use onscreen despawn range for offscreen peds and vehicles to avoid them despawning in the distance when you look + // away + +#if defined(__LP64__) || defined(_WIN64) +#define FIX_BUGS_64 // Must have fixes to be able to run 64 bit build +#endif + +#define ASCII_STRCMP // use faster ascii str comparisons + +#if !defined _WIN32 || defined __MINGW32__ +#undef ASCII_STRCMP +#endif + +// Just debug menu entries +#ifdef DEBUGMENU +#define MISSION_SWITCHER // from debug menu +#endif + +// Rendering/display +//#define EXTRA_MODEL_FLAGS // from mobile to optimize rendering +//# define HARDCODED_MODEL_FLAGS // sets the flags enabled above from hardcoded model names. + // NB: keep this enabled unless your map IDEs have these flags baked in +#define ASPECT_RATIO_SCALE // Not just makes everything scale with aspect ratio, also adds support for all aspect ratios +#define PROPER_SCALING // use original DEFAULT_SCREEN_WIDTH/DEFAULT_SCREEN_HEIGHT from PS2 instead of PC(R* changed HEIGHT here to make radar look better, but broke other hud elements aspect ratio). +#define DEFAULT_NATIVE_RESOLUTION // Set default video mode to your native resolution (fixes Windows 10 launch) +#define USE_TXD_CDIMAGE // generate and load textures from txd.img +#define PS2_ALPHA_TEST // emulate ps2 alpha test +#define IMPROVED_VIDEOMODE // save and load videomode parameters instead of a magic number +#define DISABLE_LOADING_SCREEN // disable the loading screen which vastly improves the loading time +#ifdef DISABLE_LOADING_SCREEN +// enable the PC splash +#undef RANDOMSPLASH +#endif +#define DISABLE_VSYNC_ON_TEXTURE_CONVERSION // make texture conversion work faster by disabling vsync +#define ANISOTROPIC_FILTERING // set all textures to max anisotropic filtering +//#define USE_TEXTURE_POOL +#ifdef LIBRW +#define EXTENDED_COLOURFILTER // more options for colour filter (replaces mblur) +#define EXTENDED_PIPELINES // custom render pipelines (includes Neo) +#define SCREEN_DROPLETS // neo water droplets +#define NEW_RENDERER // leeds-like world rendering, needs librw +#endif + +#define FIX_SPRITES // fix sprites aspect ratio(moon, coronas, particle etc) + +#ifndef EXTENDED_COLOURFILTER +#undef SCREEN_DROPLETS // we need the backbuffer for this effect +#endif + +// Particle +//#define PC_PARTICLE +//#define PS2_ALTERNATIVE_CARSPLASH // unused on PS2 + +// Pad +#if !defined(RW_GL3) && defined(_WIN32) +#define XINPUT +#endif +#if defined XINPUT || (defined RW_GL3 && !defined LIBRW_SDL2 && !defined GTA_HANDHELD) +#define DETECT_JOYSTICK_MENU // Then we'll expect user to enter Controller->Detect joysticks if his joystick isn't detected at the start. +#endif +#define DETECT_PAD_INPUT_SWITCH // Adds automatic switch of pad related stuff between controller and kb/m +#define KANGAROO_CHEAT +#define ALLCARSHELI_CHEAT +#define ALT_DODO_CHEAT +#define REGISTER_START_BUTTON +#define BIND_VEHICLE_FIREWEAPON // Adds ability to rebind fire key for 'in vehicle' controls +#define BUTTON_ICONS // use textures to show controller buttons + +// Hud, frontend and radar +//#define PS2_HUD +#define HUD_ENHANCEMENTS // Adjusts some aspects to make the HUD look/behave a little bit better. +// #define BETA_SLIDING_TEXT +#define TRIANGULAR_BLIPS // height indicating triangular radar blips, as in VC +#define FIX_RADAR // use radar size from early version before R* broke it +// #define XBOX_SUBTITLES // the infamous outlines +#define RADIO_OFF_TEXT +#define PC_MENU + +#ifndef PC_MENU +# define PS2_MENU +//# define PS2_MENU_USEALLPAGEICONS +#else + +# if defined(XINPUT) || defined(GTA_HANDHELD) +# define GAMEPAD_MENU // Add gamepad menu +# endif + +# define SCROLLABLE_STATS_PAGE // only draggable by mouse atm +# define TRIANGLE_BACK_BUTTON +//# define CIRCLE_BACK_BUTTON +//# define PS2_LIKE_MENU // An effort to recreate PS2 menu, cycling through tabs, different bg etc. +//# define PS2_SAVE_DIALOG // PS2 style save dialog with transparent black box +# define CUSTOM_FRONTEND_OPTIONS + +# ifdef CUSTOM_FRONTEND_OPTIONS +# define MENU_MAP // VC-like menu map. Won't appear if you don't have our menu.txd +# define GRAPHICS_MENU_OPTIONS // otherwise Display settings will be scrollable +# define NO_ISLAND_LOADING // disable loadscreen between islands via loading all island data at once, consumes more memory and CPU +# define CUTSCENE_BORDERS_SWITCH +# define MULTISAMPLING // adds MSAA option +# define INVERT_LOOK_FOR_PAD // add bInvertLook4Pad from VC +# define PED_CAR_DENSITY_SLIDERS +# endif +#endif + +// Script +#define USE_DEBUG_SCRIPT_LOADER // Loads main.scm by default. Hold R for main_freeroam.scm and D for main_d.scm +#define USE_MEASUREMENTS_IN_METERS // makes game use meters instead of feet in script +#define USE_PRECISE_MEASUREMENT_CONVERTION // makes game convert feet to meeters more precisely +#ifdef PC_MENU +# define MISSION_REPLAY // mobile feature +#endif +//#define SIMPLIER_MISSIONS // apply simplifications from mobile +#define USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#define SCRIPT_LOG_FILE_LEVEL 0 // 0 == no log, 1 == overwrite every frame, 2 == full log + +#if SCRIPT_LOG_FILE_LEVEL == 0 +#undef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#endif + +#ifndef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#define USE_BASIC_SCRIPT_DEBUG_OUTPUT +#endif + +#ifdef MASTER +#undef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#undef USE_BASIC_SCRIPT_DEBUG_OUTPUT +#endif + +// Replay +//#define DONT_FIX_REPLAY_BUGS // keeps various bugs in CReplay, some of which are fairly cool! +//#define USE_BETA_REPLAY_MODE // adds another replay mode, a few seconds slomo (caution: buggy!) + +// Vehicles +#define EXPLODING_AIRTRAIN // can blow up jumbo jet with rocket launcher +//#define REMOVE_TREADABLE_PATHFIND + +// Pickups +//#define MONEY_MESSAGES +#define CAMERA_PICKUP + +// Peds +#define PED_SKIN // support for skinned geometry on peds, requires COMPATIBLE_SAVES +#define ANIMATE_PED_COL_MODEL +// #define VC_PED_PORTS // various ports from VC's CPed, mostly subtle +// #define NEW_WALK_AROUND_ALGORITHM // to make walking around vehicles/objects less awkward +#define CANCELLABLE_CAR_ENTER +//#define PEDS_REPORT_CRIMES_ON_PHONE // requires COMPATIBLE_SAVES + +// Camera +//#define PS2_CAM_TRANSITION // old way of transitioning between cam modes +#define IMPROVED_CAMERA // Better Debug cam, and maybe more in the future +#define FREE_CAM // Rotating cam + +// Audio +#define EXTERNAL_3D_SOUND // use external engine to simulate 3d audio spatialization. OpenAL would not work without it (because it works in a 3d space + // originally and making it work in 2d only requires more resource). Will not work on PS2 +#define AUDIO_REFLECTIONS // Enable audio reflections. Disabled on mobile, didn't exist yet on PS2. +#define RADIO_SCROLL_TO_PREV_STATION +#define AUDIO_CACHE +#define PS2_AUDIO_CHANNELS // increases the maximum number of audio channels to PS2 value of 44 (PC has 28 originally) +#define PS2_AUDIO_PATHS // changes audio paths for cutscenes and radio to PS2 paths (needs vbdec on MSS builds) +//#define AUDIO_OAL_USE_SNDFILE // use libsndfile to decode WAVs instead of our internal decoder +#define AUDIO_OAL_USE_MPG123 // use mpg123 to support mp3 files +#define PAUSE_RADIO_IN_FRONTEND // pause radio when game is paused +#define ATTACH_RELEASING_SOUNDS_TO_ENTITIES // sounds would follow ped and vehicles coordinates if not being queued otherwise +#define USE_TIME_SCALE_FOR_AUDIO // slow down/speed up sounds according to the speed of the game +#define MULTITHREADED_AUDIO // for streams. requires C++11 or later + +#ifdef AUDIO_OPUS +#define AUDIO_OAL_USE_OPUS // enable support of opus files +#define OPUS_AUDIO_PATHS // changes audio paths to opus paths (doesn't work if AUDIO_OAL_USE_OPUS isn't enabled) +#define OPUS_SFX // enable if your sfx.raw is encoded with opus (doesn't work if AUDIO_OAL_USE_OPUS isn't enabled) + +#ifndef AUDIO_OAL_USE_OPUS +#undef OPUS_AUDIO_PATHS +#undef OPUS_SFX +#endif + +#endif + +// Streaming +#if !defined(_WIN32) && !defined(__SWITCH__) + //#define ONE_THREAD_PER_CHANNEL // Don't use if you're not on SSD/Flash - also not utilized too much right now(see commented LoadAllRequestedModels in Streaming.cpp) + #define FLUSHABLE_STREAMING // Make it possible to interrupt reading when processing file isn't needed anymore. +#endif +#define BIG_IMG // Not complete - allows to read larger img files + +//#define SQUEEZE_PERFORMANCE +#ifdef SQUEEZE_PERFORMANCE + #undef PS2_ALPHA_TEST + #undef NO_ISLAND_LOADING + #undef PS2_AUDIO_CHANNELS + #undef EXTENDED_OFFSCREEN_DESPAWN_RANGE + #define PC_PARTICLE + #define VC_PED_PORTS // To not process collisions always. But should be tested if that's really beneficial + #define VC_RAIN_NERF // Reduces number of rain particles +#endif + +// if these defines are enabled saves are not vanilla compatible without COMPATIBLE_SAVES +#ifndef COMPATIBLE_SAVES +#undef PED_SKIN +#undef PEDS_REPORT_CRIMES_ON_PHONE +#endif + +#ifdef GTA_HANDHELD + #define IGNORE_MOUSE_KEYBOARD // ignore mouse & keyboard input +#endif + +#ifdef __SWITCH__ + #define USE_UNNAMED_SEM // named semaphores are unsupported on the switch +#endif + +#endif // VANILLA_DEFINES + +#if defined(AUDIO_OAL) && !defined(EXTERNAL_3D_SOUND) +#error AUDIO_OAL cannot work without EXTERNAL_3D_SOUND +#endif +#if defined(GTA_PS2) && defined(EXTERNAL_3D_SOUND) +#error EXTERNAL_3D_SOUND cannot work on PS2 +#endif +#if defined(AUDIO_REFLECTIONS) && GTA_VERSION < GTA3_PC_10 +#error AUDIO_REFLECTIONS cannot work with versions below GTA3_PC_10 +#endif \ No newline at end of file diff --git a/src/core/main.cpp b/src/core/main.cpp new file mode 100644 index 0000000..2a0a77c --- /dev/null +++ b/src/core/main.cpp @@ -0,0 +1,2493 @@ +#include "common.h" +#include +#include "rpmatfx.h" +#include "rphanim.h" +#include "rpskin.h" +#include "rtbmp.h" +#include "rtpng.h" +#ifdef ANISOTROPIC_FILTERING +#include "rpanisot.h" +#endif + +#include "main.h" +#include "CdStream.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" +#include "AnimViewer.h" +#include "Script.h" +#include "PathFind.h" +#include "Debug.h" +#include "Console.h" +#include "timebars.h" +#include "GenericGameStorage.h" +#include "MemoryCard.h" +#include "SceneEdit.h" +#include "debugmenu.h" +#include "Clock.h" +#include "postfx.h" +#include "custompipes.h" +#include "screendroplets.h" +#include "MemoryHeap.h" +#ifdef USE_OUR_VERSIONING +#include "GitSHA1.h" +#endif + +GlobalScene Scene; + +uint8 work_buff[55000]; +char gString[256]; +char gString2[512]; +wchar gUString[256]; +wchar gUString2[256]; + +float FramesPerSecond = 30.0f; + +bool gbPrintShite = false; +bool gbModelViewer; +#ifdef TIMEBARS +bool gbShowTimebars; +#endif +#ifdef DRAW_GAME_VERSION_TEXT +bool gbDrawVersionText; // Our addition, we think it was always enabled on !MASTER builds +#endif +#ifdef NO_MOVIES +bool gbNoMovies; +#endif + +volatile int32 frameCount; + +RwRGBA gColourTop; + +bool gameAlreadyInitialised; + +float NumberOfChunksLoaded; +#ifdef GTA_PS2 +#define TOTALNUMCHUNKS 48.0f +#else +#define TOTALNUMCHUNKS 73.0f +#endif + +bool g_SlowMode = false; +char version_name[64]; + + +void GameInit(void); +void SystemInit(void); +void TheGame(void); + +#ifdef DEBUGMENU +void DebugMenuPopulate(void); +#endif + +#ifndef FINAL +bool gbPrintMemoryUsage; +#endif + +#ifdef PS2_MENU +#define WANT_TO_LOAD TheMemoryCard.m_bWantToLoad +#define FOUND_GAME_TO_LOAD TheMemoryCard.b_FoundRecentSavedGameWantToLoad +#else +#define WANT_TO_LOAD FrontEndMenuManager.m_bWantToLoad +#define FOUND_GAME_TO_LOAD b_FoundRecentSavedGameWantToLoad +#endif + +#ifdef NEW_RENDERER +bool gbNewRenderer; +#define CLEARMODE (rwCAMERACLEARZ | rwCAMERACLEARSTENCIL) +#else +#define CLEARMODE (rwCAMERACLEARZ) +#endif + +#ifdef __MWERKS__ +void +debug(char *fmt, ...) +{ +#ifndef MASTER + // TODO put something here +#endif +} + +void +Error(char *fmt, ...) +{ +#ifndef MASTER + // TODO put something here +#endif +} +#endif + +void +ValidateVersion() +{ + int32 file = CFileMgr::OpenFile("models\\coll\\peds.col", "rb"); + char buff[128]; + + if ( file != -1 ) + { + CFileMgr::Seek(file, 100, SEEK_SET); + + for ( int i = 0; i < 128; i++ ) + { + CFileMgr::Read(file, &buff[i], sizeof(char)); + buff[i] -= 23; + if ( buff[i] == '\0' ) + break; + CFileMgr::Seek(file, 99, SEEK_CUR); + } + + if ( !strncmp(buff, "grandtheftauto3", 15) ) + { + strncpy(version_name, &buff[15], 64); + CFileMgr::CloseFile(file); + return; + } + } + + LoadingScreen("Invalid version", NULL, NULL); + + while(true) + { + ; + } +} + +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); + +#ifndef ASPECT_RATIO_SCALE + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, (CMenuManager::m_PrefsUseWideScreen ? 16.f / 9.f : 4.f / 3.f)); +#else + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); +#endif + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &TopColor.rwRGBA, CLEARMODE); + + if(!RsCameraBeginUpdate(Scene.camera)) + return false; + +#ifdef FIX_BUGS + CSprite2d::SetRecipNearClip(); +#endif + 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) +{ +#ifndef ASPECT_RATIO_SCALE + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, (CMenuManager::m_PrefsUseWideScreen ? 16.f/9.f : 4.f/3.f)); +#else + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); +#endif + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, CLEARMODE); + + if(!RsCameraBeginUpdate(Scene.camera)) + return false; + + TheCamera.m_viewMatrix.Update(); + CClouds::RenderBackground(TopRed, TopGreen, TopBlue, BottomRed, BottomGreen, BottomBlue, Alpha); + + return true; +} + +// This is certainly a very useful function +void +DoRWRenderHorizon(void) +{ + CClouds::RenderHorizon(); +} + +void +DoFade(void) +{ + if(CTimer::GetIsPaused()) + return; + +#ifdef PS2_MENU + if(TheMemoryCard.JustLoadedDontFadeInYet){ + TheMemoryCard.JustLoadedDontFadeInYet = false; + TheMemoryCard.TimeStartedCountingForFade = CTimer::GetTimeInMilliseconds(); + } +#else + if(JustLoadedDontFadeInYet){ + JustLoadedDontFadeInYet = false; + TimeStartedCountingForFade = CTimer::GetTimeInMilliseconds(); + } +#endif + +#ifdef PS2_MENU + if(TheMemoryCard.StillToFadeOut){ + if(CTimer::GetTimeInMilliseconds() - TheMemoryCard.TimeStartedCountingForFade > TheMemoryCard.TimeToStayFadedBeforeFadeOut){ + TheMemoryCard.StillToFadeOut = false; +#else + if(StillToFadeOut){ + if(CTimer::GetTimeInMilliseconds() - TimeStartedCountingForFade > TimeToStayFadedBeforeFadeOut){ + StillToFadeOut = false; +#endif + 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 +#ifdef CUTSCENE_BORDERS_SWITCH + && CMenuManager::m_PrefsCutsceneBorders +#endif + ){ + // what's this? + float y = SCREEN_HEIGHT/2 * TheCamera.m_ScreenReductionPercentage/100.0f; + rect.left = 0.0f; + rect.right = SCREEN_WIDTH; +#ifdef FIX_BUGS + rect.top = y - SCREEN_SCALE_Y(8.0f); + rect.bottom = SCREEN_HEIGHT - y - SCREEN_SCALE_Y(8.0f); +#else + rect.top = y - 8.0f; + rect.bottom = SCREEN_HEIGHT - y - 8.0f; +#endif // FIX_BUGS + }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); + } + } +} + +bool +RwGrabScreen(RwCamera *camera, RwChar *filename) +{ + char temp[255]; + RwImage *pImage = RsGrabScreen(camera); + bool result = true; + + if (pImage == nil) + return false; + + strcpy(temp, CFileMgr::GetRootDirName()); + strcat(temp, filename); + +#ifndef LIBRW + if (RtBMPImageWrite(pImage, &temp[0]) == nil) +#else + if (RtPNGImageWrite(pImage, &temp[0]) == nil) +#endif + result = false; + RwImageDestroy(pImage); + return result; +} + +#define TILE_WIDTH 576 +#define TILE_HEIGHT 432 + +void +DoRWStuffEndOfFrame(void) +{ + CDebug::DisplayScreenStrings(); // custom + CDebug::DebugDisplayTextBuffer(); + FlushObrsPrintfs(); + RwCameraEndUpdate(Scene.camera); + RsCameraShowRaster(Scene.camera); +#ifndef MASTER + char s[48]; +#ifdef THIS_IS_STUPID + if (CPad::GetPad(1)->GetLeftShockJustDown()) { + // try using both controllers for this thing... crazy bastards + if (CPad::GetPad(0)->GetRightStickY() > 0) { + sprintf(s, "screen%d%d.ras", CClock::ms_nGameClockHours, CClock::ms_nGameClockMinutes); + // TODO + //RtTileRender(Scene.camera, TILE_WIDTH * 2, TILE_HEIGHT * 2, TILE_WIDTH, TILE_HEIGHT, &NewTileRendererCB, nil, s); + } else { + sprintf(s, "screen%d%d.bmp", CClock::ms_nGameClockHours, CClock::ms_nGameClockMinutes); + RwGrabScreen(Scene.camera, s); + } + } +#else + if (CPad::GetPad(1)->GetLeftShockJustDown() || CPad::GetPad(0)->GetFJustDown(11)) { + sprintf(s, "screen_%011lld.png", time(nil)); + RwGrabScreen(Scene.camera, s); + } +#endif +#endif // !MASTER +} + +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; + } +#ifdef ANISOTROPIC_FILTERING + RpAnisotPluginAttach(); +#endif +#ifdef EXTENDED_PIPELINES + CustomPipes::CustomPipeRegister(); +#endif + + return TRUE; +} + +#ifdef GTA_PS2 +#define NUM_PREALLOC_ATOMICS 3245 +#define NUM_PREALLOC_CLUMPS 101 +#define NUM_PREALLOC_FRAMES 2821 +#define NUM_PREALLOC_GEOMETRIES 1404 +#define NUM_PREALLOC_TEXDICTS 106 +#define NUM_PREALLOC_TEXTURES 1900 +#define NUM_PREALLOC_MATERIALS 3300 +bool preAlloc; + +void +PreAllocateRwObjects(void) +{ + int i; + void **tmp = new void*[0x8000]; + preAlloc = true; + + for(i = 0; i < NUM_PREALLOC_ATOMICS; i++) + tmp[i] = RpAtomicCreate(); + for(i = 0; i < NUM_PREALLOC_ATOMICS; i++) + RpAtomicDestroy((RpAtomic*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_CLUMPS; i++) + tmp[i] = RpClumpCreate(); + for(i = 0; i < NUM_PREALLOC_CLUMPS; i++) + RpClumpDestroy((RpClump*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_FRAMES; i++) + tmp[i] = RwFrameCreate(); + for(i = 0; i < NUM_PREALLOC_FRAMES; i++) + RwFrameDestroy((RwFrame*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_GEOMETRIES; i++) + tmp[i] = RpGeometryCreate(0, 0, 0); + for(i = 0; i < NUM_PREALLOC_GEOMETRIES; i++) + RpGeometryDestroy((RpGeometry*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_TEXDICTS; i++) + tmp[i] = RwTexDictionaryCreate(); + for(i = 0; i < NUM_PREALLOC_TEXDICTS; i++) + RwTexDictionaryDestroy((RwTexDictionary*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_TEXTURES; i++) + tmp[i] = RwTextureCreate(RwRasterCreate(0, 0, 0, 0)); + for(i = 0; i < NUM_PREALLOC_TEXDICTS; i++) + RwTextureDestroy((RwTexture*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_MATERIALS; i++) + tmp[i] = RpMaterialCreate(); + for(i = 0; i < NUM_PREALLOC_MATERIALS; i++) + RpMaterialDestroy((RpMaterial*)tmp[i]); + + delete[] tmp; + preAlloc = false; +} +#endif + +static RwBool +Initialise3D(void *param) +{ + if (RsRwInitialize(param)) + { +#ifdef DEBUGMENU + DebugMenuInit(); + DebugMenuPopulate(); +#endif // !DEBUGMENU + return CGame::InitialiseRenderWare(); + } + + return (FALSE); +} + +static void +Terminate3D(void) +{ + CGame::ShutdownRenderWare(); +#ifdef DEBUGMENU + DebugMenuShutdown(); +#endif // !DEBUGMENU + + RsRwTerminate(); + + return; +} + +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; +} + +Const 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; +} + +Const char* +GetLevelSplashScreen(int level) +{ + static Const char *splashScreens[4] = { + nil, + "splash1", + "splash2", + "splash3", + }; + + return splashScreens[level]; +} + +void +ResetLoadingScreenBar() +{ + NumberOfChunksLoaded = 0.0f; +} + +void +LoadingScreen(const char *str1, const char *str2, const char *splashscreen) +{ + CSprite2d *splash; + +#ifdef DISABLE_LOADING_SCREEN + if (str1 && str2) + return; +#endif + +#ifndef RANDOMSPLASH + if(CGame::frenchGame || CGame::germanGame || !CGame::nastyGame) + splashscreen = "mainsc2"; + else + splashscreen = "mainsc1"; +#endif + + splash = LoadSplash(splashscreen); + +#ifndef GTA_PS2 + if(RsGlobal.quit) + return; +#endif + +#ifndef GTA_PS2 + if(DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) +#else + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); +#endif + { + 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 yscale = SCREEN_SCALE_Y(0.9f); + vpos -= 45*yscale; + CFont::SetScale(SCREEN_SCALE_X(0.75f), yscale); + CFont::SetPropOn(); + CFont::SetRightJustifyOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + AsciiToUnicode(str1, tmpstr); + CFont::PrintString(hpos, vpos, tmpstr); + vpos += 22*yscale; + if (str2) { + AsciiToUnicode(str2, tmpstr); + CFont::PrintString(hpos, vpos, tmpstr); + } +#endif + } + + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); + } +} + +void +LoadingIslandScreen(const char *levelName) +{ + CSprite2d *splash; + wchar *name; + char str[100]; + wchar wstr[80]; + CRGBA col; + + splash = LoadSplash(nil); + name = TheText.Get(levelName); + +#ifndef GTA_PS2 + if(!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) + return; +#else + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); +#endif + + 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(); +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.5f)); +#else + CFont::SetScale(1.5f, 1.5f); +#endif + CFont::SetPropOn(); + CFont::SetRightJustifyOn(); +#ifdef FIX_BUGS + CFont::SetRightJustifyWrap(SCREEN_SCALE_X(150.0f)); +#else + CFont::SetRightJustifyWrap(150.0f); +#endif + 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)); +#if !defined(PS2_HUD) && defined(GTA_PC) + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.2f)); +#endif + +#ifdef PS2_HUD + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(140.0f), TheText.Get("WELCOME")); + #else + CFont::PrintString(SCREEN_WIDTH - 20, SCREEN_HEIGHT - 140, TheText.Get("WELCOME")); + #endif +#else + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(110.0f), TheText.Get("WELCOME")); + #else + CFont::PrintString(SCREEN_WIDTH - 20, SCREEN_SCALE_FROM_BOTTOM(110.0f), TheText.Get("WELCOME")); + #endif +#endif + TextCopy(wstr, name); + TheText.UpperCase(wstr); + CFont::SetColor(CRGBA(243, 237, 71, 255)); +#if !defined(PS2_HUD) && defined(GTA_PC) + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.2f)); +#endif + +#ifdef PS2_HUD + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(110.0f), wstr); + #else + CFont::PrintString(SCREEN_WIDTH-20, SCREEN_HEIGHT - 110, wstr); + #endif +#else + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(80.0f), wstr); + #else + CFont::PrintString(SCREEN_WIDTH-20, SCREEN_SCALE_FROM_BOTTOM(80.0f), wstr); + #endif +#endif + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +void +ProcessSlowMode(void) +{ + int16 lX = CPad::GetPad(0)->NewState.LeftStickX; + int16 lY = CPad::GetPad(0)->NewState.LeftStickY; + int16 rX = CPad::GetPad(0)->NewState.RightStickX; + int16 rY = CPad::GetPad(0)->NewState.RightStickY; + int16 L1 = CPad::GetPad(0)->NewState.LeftShoulder1; + int16 L2 = CPad::GetPad(0)->NewState.LeftShoulder2; + int16 R1 = CPad::GetPad(0)->NewState.RightShoulder1; + int16 R2 = CPad::GetPad(0)->NewState.RightShoulder2; + int16 up = CPad::GetPad(0)->NewState.DPadUp; + int16 down = CPad::GetPad(0)->NewState.DPadDown; + int16 left = CPad::GetPad(0)->NewState.DPadLeft; + int16 right = CPad::GetPad(0)->NewState.DPadRight; + int16 start = CPad::GetPad(0)->NewState.Start; + int16 select = CPad::GetPad(0)->NewState.Select; + int16 square = CPad::GetPad(0)->NewState.Square; + int16 triangle = CPad::GetPad(0)->NewState.Triangle; + int16 cross = CPad::GetPad(0)->NewState.Cross; + int16 circle = CPad::GetPad(0)->NewState.Circle; + int16 L3 = CPad::GetPad(0)->NewState.LeftShock; + int16 R3 = CPad::GetPad(0)->NewState.RightShock; + int16 networktalk = CPad::GetPad(0)->NewState.NetworkTalk; + int16 stop = true; + + do + { + if ( CPad::GetPad(1)->GetLeftShoulder1JustDown() || CPad::GetPad(1)->GetRightShoulder1() ) + break; + + if ( stop ) + { + CTimer::Stop(); + stop = false; + } + + CPad::UpdatePads(); + + RwCameraBeginUpdate(Scene.camera); + RwCameraEndUpdate(Scene.camera); + + if ( CPad::GetPad(1)->GetLeftShoulder1JustDown() || CPad::GetPad(1)->GetRightShoulder1() ) + break; + + } while (!CPad::GetPad(1)->GetRightShoulder1()); + + + CPad::GetPad(0)->OldState.LeftStickX = lX; + CPad::GetPad(0)->OldState.LeftStickY = lY; + CPad::GetPad(0)->OldState.RightStickX = rX; + CPad::GetPad(0)->OldState.RightStickY = rY; + CPad::GetPad(0)->OldState.LeftShoulder1 = L1; + CPad::GetPad(0)->OldState.LeftShoulder2 = L2; + CPad::GetPad(0)->OldState.RightShoulder1 = R1; + CPad::GetPad(0)->OldState.RightShoulder2 = R2; + CPad::GetPad(0)->OldState.DPadUp = up; + CPad::GetPad(0)->OldState.DPadDown = down; + CPad::GetPad(0)->OldState.DPadLeft = left; + CPad::GetPad(0)->OldState.DPadRight = right; + CPad::GetPad(0)->OldState.Start = start; + CPad::GetPad(0)->OldState.Select = select; + CPad::GetPad(0)->OldState.Square = square; + CPad::GetPad(0)->OldState.Triangle = triangle; + CPad::GetPad(0)->OldState.Cross = cross; + CPad::GetPad(0)->OldState.Circle = circle; + CPad::GetPad(0)->OldState.LeftShock = L3; + CPad::GetPad(0)->OldState.RightShock = R3; + CPad::GetPad(0)->OldState.NetworkTalk = networktalk; + CPad::GetPad(0)->NewState.LeftStickX = lX; + CPad::GetPad(0)->NewState.LeftStickY = lY; + CPad::GetPad(0)->NewState.RightStickX = rX; + CPad::GetPad(0)->NewState.RightStickY = rY; + CPad::GetPad(0)->NewState.LeftShoulder1 = L1; + CPad::GetPad(0)->NewState.LeftShoulder2 = L2; + CPad::GetPad(0)->NewState.RightShoulder1 = R1; + CPad::GetPad(0)->NewState.RightShoulder2 = R2; + CPad::GetPad(0)->NewState.DPadUp = up; + CPad::GetPad(0)->NewState.DPadDown = down; + CPad::GetPad(0)->NewState.DPadLeft = left; + CPad::GetPad(0)->NewState.DPadRight = right; + CPad::GetPad(0)->NewState.Start = start; + CPad::GetPad(0)->NewState.Select = select; + CPad::GetPad(0)->NewState.Square = square; + CPad::GetPad(0)->NewState.Triangle = triangle; + CPad::GetPad(0)->NewState.Cross = cross; + CPad::GetPad(0)->NewState.Circle = circle; + CPad::GetPad(0)->NewState.LeftShock = L3; + CPad::GetPad(0)->NewState.RightShock = R3; + CPad::GetPad(0)->NewState.NetworkTalk = networktalk; +} + + +float FramesPerSecondCounter; +int32 FrameSamples; + +#ifndef MASTER +struct tZonePrint +{ + char name[12]; + CRect rect; +}; + +tZonePrint ZonePrint[] = +{ + { "suburban", CRect(-1639.4f, 1014.3f, -226.23f, -1347.9f) }, + { "comntop", CRect(-223.52f, 203.62f, 616.79f, -413.6f) }, + { "comnbtm", CRect(-227.24f, -413.6f, 620.51f, -911.84f) }, + { "comse", CRect( 200.35f, -911.84f, 620.51f, -1737.3f) }, + { "comsw", CRect(-223.52f, -911.84f, 200.35f, -1737.3f) }, + { "industsw", CRect( 744.05f, -473.0f, 1067.5f, -1331.5f) }, + { "industne", CRect( 1067.5f, 282.19f, 1915.3f, -473.0f) }, + { "industnw", CRect( 744.05f, 324.95f, 1067.5f, -473.0f) }, + { "industse", CRect( 1070.3f, -473.0f, 1918.1f, -1331.5f) }, + { "no zone", CRect( 0.0f, 0.0f, 0.0f, 0.0f) } +}; + +void +PrintMemoryUsage(void) +{ +// little hack +if(CPools::GetPtrNodePool() == nil) +return; + + // Style taken from LCS, modified for III +// CFont::SetFontStyle(FONT_PAGER); + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetWrapx(640.0f); +// CFont::SetScale(0.5f, 0.75f); + CFont::SetScale(0.4f, 0.75f); + CFont::SetCentreOff(); + CFont::SetCentreSize(640.0f); + CFont::SetJustifyOff(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(200, 200, 200, 200)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetDropShadowPosition(0); + + float y; + +#ifdef USE_CUSTOM_ALLOCATOR + y = 24.0f; + sprintf(gString, "Total: %d blocks, %d bytes", gMainHeap.m_totalBlocksUsed, gMainHeap.m_totalMemUsed); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Game: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_GAME), gMainHeap.GetMemoryUsed(MEMID_GAME)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "World: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_WORLD), gMainHeap.GetMemoryUsed(MEMID_WORLD)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Render: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_RENDER), gMainHeap.GetMemoryUsed(MEMID_RENDER)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Render List: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_RENDERLIST), gMainHeap.GetMemoryUsed(MEMID_RENDERLIST)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Default Models: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_DEF_MODELS), gMainHeap.GetMemoryUsed(MEMID_DEF_MODELS)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Textures: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_TEXTURES), gMainHeap.GetMemoryUsed(MEMID_TEXTURES)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Streaming: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_STREAM), gMainHeap.GetMemoryUsed(MEMID_STREAM)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Streamed Models: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_STREAM_MODELS), gMainHeap.GetMemoryUsed(MEMID_STREAM_MODELS)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Streamed Textures: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_STREAM_TEXUTRES), gMainHeap.GetMemoryUsed(MEMID_STREAM_TEXUTRES)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Animation: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_ANIMATION), gMainHeap.GetMemoryUsed(MEMID_ANIMATION)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Pools: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_POOLS), gMainHeap.GetMemoryUsed(MEMID_POOLS)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Collision: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_COLLISION), gMainHeap.GetMemoryUsed(MEMID_COLLISION)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Game Process: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_GAME_PROCESS), gMainHeap.GetMemoryUsed(MEMID_GAME_PROCESS)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Script: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_SCRIPT), gMainHeap.GetMemoryUsed(MEMID_SCRIPT)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Cars: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_CARS), gMainHeap.GetMemoryUsed(MEMID_CARS)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Frontend: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_FRONTEND), gMainHeap.GetMemoryUsed(MEMID_FRONTEND)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; +#endif + + y = 132.0f; + AsciiToUnicode("Pools usage:", gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "PtrNode: %d/%d", CPools::GetPtrNodePool()->GetNoOfUsedSpaces(), CPools::GetPtrNodePool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "EntryInfoNode: %d/%d", CPools::GetEntryInfoNodePool()->GetNoOfUsedSpaces(), CPools::GetEntryInfoNodePool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Ped: %d/%d", CPools::GetPedPool()->GetNoOfUsedSpaces(), CPools::GetPedPool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Vehicle: %d/%d", CPools::GetVehiclePool()->GetNoOfUsedSpaces(), CPools::GetVehiclePool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Building: %d/%d", CPools::GetBuildingPool()->GetNoOfUsedSpaces(), CPools::GetBuildingPool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Treadable: %d/%d", CPools::GetTreadablePool()->GetNoOfUsedSpaces(), CPools::GetTreadablePool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Object: %d/%d", CPools::GetObjectPool()->GetNoOfUsedSpaces(), CPools::GetObjectPool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Dummy: %d/%d", CPools::GetDummyPool()->GetNoOfUsedSpaces(), CPools::GetDummyPool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "AudioScriptObjects: %d/%d", CPools::GetAudioScriptObjectPool()->GetNoOfUsedSpaces(), CPools::GetAudioScriptObjectPool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; +} + +void +DisplayGameDebugText() +{ + static bool bDisplayPosn = false; + static bool bDisplayRate = false; +#ifndef FINAL + { + SETTWEAKPATH("Debug"); + TWEAKBOOL(bDisplayPosn); + TWEAKBOOL(bDisplayRate); + } + + if(gbPrintMemoryUsage) + PrintMemoryUsage(); +#endif + + char str[200]; + wchar ustr[200]; + +#ifdef DRAW_GAME_VERSION_TEXT + wchar ver[200]; + + if(gbDrawVersionText) // This realtime switch is our thing + { + +#ifdef USE_OUR_VERSIONING + char verA[200]; + sprintf(verA, +#if defined _WIN32 + "Win " +#elif defined __linux__ + "Linux " +#elif defined __APPLE__ + "Mac OS X " +#elif defined __FreeBSD__ + "FreeBSD " +#else + "Posix-compliant " +#endif +#if defined __LP64__ || defined _WIN64 + "64-bit " +#else + "32-bit " +#endif +#if defined RW_D3D9 + "D3D9 " +#elif defined RWLIBS + "D3D8 " +#elif defined RW_GL3 + "OpenGL " +#endif +#if defined AUDIO_OAL + "OAL " +#elif defined AUDIO_MSS + "MSS " +#endif +#if defined _DEBUG || defined DEBUG + "DEBUG " +#endif + "%.8s", + g_GIT_SHA1); + AsciiToUnicode(verA, ver); + CFont::SetScale(SCREEN_SCALE_X(0.5f), SCREEN_SCALE_Y(0.7f)); +#else + AsciiToUnicode(version_name, ver); + CFont::SetScale(SCREEN_SCALE_X(0.5f), SCREEN_SCALE_Y(0.5f)); +#endif + + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::SetJustifyOff(); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetColor(CRGBA(255, 108, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_X(10.0f), SCREEN_SCALE_Y(10.0f), ver); +#else + CFont::PrintString(10.0f, 10.0f, ver); +#endif + } +#endif // #ifdef DRAW_GAME_VERSION_TEXT + + FrameSamples++; +#ifdef FIX_BUGS + // this is inaccurate with over 1000 fps + static uint32 PreviousTimeInMillisecondsPauseMode = 0; + FramesPerSecondCounter += (CTimer::GetTimeInMillisecondsPauseMode() - PreviousTimeInMillisecondsPauseMode) / 1000.0f; // convert to seconds + PreviousTimeInMillisecondsPauseMode = CTimer::GetTimeInMillisecondsPauseMode(); + FramesPerSecond = FrameSamples / FramesPerSecondCounter; +#else + FramesPerSecondCounter += 1000.0f / CTimer::GetTimeStepNonClippedInMilliseconds(); + FramesPerSecond = FramesPerSecondCounter / FrameSamples; +#endif + + if ( FrameSamples > 30 ) + { + FramesPerSecondCounter = 0.0f; + FrameSamples = 0; + } + + if ( !TheCamera.WorldViewerBeingUsed + && CPad::GetPad(1)->GetSquare() + && CPad::GetPad(1)->GetTriangle() + && CPad::GetPad(1)->GetLeftShoulder2JustDown() ) + { + bDisplayPosn = !bDisplayPosn; + } + + if ( CPad::GetPad(1)->GetSquare() + && CPad::GetPad(1)->GetTriangle() + && CPad::GetPad(1)->GetRightShoulder2JustDown() ) + { + bDisplayRate = !bDisplayRate; + } + + if ( bDisplayPosn || bDisplayRate ) + { + CVector pos = FindPlayerCoors(); + int32 ZoneId = ARRAY_SIZE(ZonePrint)-1; // no zone + + for ( int32 i = 0; i < ARRAY_SIZE(ZonePrint)-1; i++ ) + { + if ( pos.x > ZonePrint[i].rect.left + && pos.x < ZonePrint[i].rect.right + && pos.y > ZonePrint[i].rect.bottom + && pos.y < ZonePrint[i].rect.top ) + { + ZoneId = i; + } + } + + //NOTE: fps should be 30, but its 29 due to different fp2int conversion + if ( bDisplayRate ) + sprintf(str, "X:%5.1f, Y:%5.1f, Z:%5.1f, F-%d, %s", pos.x, pos.y, pos.z, (int32)FramesPerSecond, ZonePrint[ZoneId].name); + else + sprintf(str, "X:%5.1f, Y:%5.1f, Z:%5.1f, %s", pos.x, pos.y, pos.z, ZonePrint[ZoneId].name); + + AsciiToUnicode(str, ustr); + + CFont::SetPropOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(0.7f), SCREEN_SCALE_Y(1.5f)); +#else + CFont::SetScale(0.7f, 1.5f); +#endif + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetJustifyOff(); + CFont::SetBackGroundOnlyTextOff(); +#ifdef FIX_BUGS + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); +#else + CFont::SetWrapx(DEFAULT_SCREEN_WIDTH); +#endif + CFont::SetFontStyle(FONT_HEADING); + + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_X(40.0f+2.0f), SCREEN_SCALE_Y(40.0f+2.0f), ustr); +#else + CFont::PrintString(40.0f+2.0f, 40.0f+2.0f, ustr); +#endif + + CFont::SetColor(CRGBA(255, 108, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_X(40.0f), SCREEN_SCALE_Y(40.0f), ustr); +#else + CFont::PrintString(40.0f, 40.0f, ustr); +#endif + } +} +#endif + +#ifdef NEW_RENDERER +bool gbRenderRoads = true; +bool gbRenderEverythingBarRoads = true; +//bool gbRenderFadingInUnderwaterEntities = true; +bool gbRenderFadingInEntities = true; +bool gbRenderWater = true; +bool gbRenderBoats = true; +bool gbRenderVehicles = true; +bool gbRenderWorld0 = true; +bool gbRenderWorld1 = true; +bool gbRenderWorld2 = true; + +void +MattRenderScene(void) +{ + // this calls CMattRenderer::Render + /// CWorld::AdvanceCurrentScanCode(); + // CMattRenderer::ResetRenderStates + /// CRenderer::ClearForFrame(); // before ConstructRenderList + // CClock::CalcEnvMapTimeMultiplicator +if(gbRenderWater) + CRenderer::RenderWater(); // actually CMattRenderer::RenderWater + // CClock::ms_EnvMapTimeMultiplicator = 1.0f; + // cWorldStream::ClearDynamics + /// CRenderer::ConstructRenderList(); // before PreRender +if(gbRenderWorld0) + CRenderer::RenderWorld(0); // roads + // CMattRenderer::ResetRenderStates + /// CRenderer::PreRender(); // has to be called before BeginUpdate because of cutscene shadows + CCoronas::RenderReflections(); +if(gbRenderWorld1) + CRenderer::RenderWorld(1); // opaque +if(gbRenderRoads) + CRenderer::RenderRoads(); + + CRenderer::RenderPeds(); + +if(gbRenderBoats) + CRenderer::RenderBoats(); +//if(gbRenderFadingInUnderwaterEntities) +// CRenderer::RenderFadingInUnderwaterEntities(); + +if(gbRenderEverythingBarRoads) + CRenderer::RenderEverythingBarRoads(); + // seam fixer + // moved this: + // CRenderer::RenderFadingInEntities(); +} + +void +RenderScene_new(void) +{ + PUSH_RENDERGROUP("RenderScene_new"); + CClouds::Render(); + DoRWRenderHorizon(); + + MattRenderScene(); + DefinedState(); + // CMattRenderer::ResetRenderStates + // moved CRenderer::RenderBoats to before transparent water + POP_RENDERGROUP(); +} + +// TODO +bool FredIsInFirstPersonCam(void) { return false; } +void +RenderEffects_new(void) +{ + PUSH_RENDERGROUP("RenderEffects_new"); +/* // stupid to do this before the whole world is drawn! + CShadows::RenderStaticShadows(); + // CRenderer::GenerateEnvironmentMap + CShadows::RenderStoredShadows(); + CSkidmarks::Render(); + CRubbish::Render(); +*/ + + // these aren't really effects + DefinedState(); + if(FredIsInFirstPersonCam()){ + DefinedState(); + C3dMarkers::Render(); // normally rendered in CSpecialFX::Render() +if(gbRenderWorld2) + CRenderer::RenderWorld(2); // transparent +if(gbRenderVehicles) + CRenderer::RenderVehicles(); + }else{ + // flipped these two, seems to give the best result +if(gbRenderWorld2) + CRenderer::RenderWorld(2); // transparent +if(gbRenderVehicles) + CRenderer::RenderVehicles(); + } + // better render these after transparent world +if(gbRenderFadingInEntities) + CRenderer::RenderFadingInEntities(); + + // actual effects here + + // from above + CShadows::RenderStaticShadows(); + CShadows::RenderStoredShadows(); + CSkidmarks::Render(); + CRubbish::Render(); + + CGlass::Render(); + // CMattRenderer::ResetRenderStates + DefinedState(); + CWeather::RenderRainStreaks(); + // CWeather::AddSnow + CWaterCannons::Render(); + CAntennas::Render(); + CSpecialFX::Render(); + CCoronas::Render(); + CParticle::Render(); + CPacManPickups::Render(); + CWeaponEffects::Render(); + CPointLights::RenderFogEffect(); + CMovingThings::Render(); + CRenderer::RenderFirstPersonVehicle(); + POP_RENDERGROUP(); +} +#endif + +void +RenderScene(void) +{ +#ifdef NEW_RENDERER + if(gbNewRenderer){ + RenderScene_new(); + return; + } +#endif + PUSH_RENDERGROUP("RenderScene"); + CClouds::Render(); + DoRWRenderHorizon(); + CRenderer::RenderRoads(); + CCoronas::RenderReflections(); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + CRenderer::RenderEverythingBarRoads(); + CRenderer::RenderBoats(); + DefinedState(); + CWaterLevel::RenderWater(); + CRenderer::RenderFadingInEntities(); +#ifndef SQUEEZE_PERFORMANCE + CRenderer::RenderVehiclesButNotBoats(); +#endif + CWeather::RenderRainStreaks(); + POP_RENDERGROUP(); +} + +void +RenderDebugShit(void) +{ + PUSH_RENDERGROUP("RenderDebugShit"); + CTheScripts::RenderTheScriptDebugLines(); +#ifndef FINAL + if(gbShowCollisionLines) + CRenderer::RenderCollisionLines(); + ThePaths.DisplayPathData(); + CDebug::DrawLines(); + DefinedState(); +#endif + POP_RENDERGROUP(); +} + +void +RenderEffects(void) +{ +#ifdef NEW_RENDERER + if(gbNewRenderer){ + RenderEffects_new(); + return; + } +#endif + PUSH_RENDERGROUP("RenderEffects"); + 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(); + POP_RENDERGROUP(); +} + +void +Render2dStuff(void) +{ + PUSH_RENDERGROUP("Render2dStuff"); + 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 +#ifdef CUTSCENE_BORDERS_SWITCH + && CMenuManager::m_PrefsCutsceneBorders +#endif + ) + 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_RUNABOUT || + cammode == CCam::MODE_ROCKETLAUNCHER || + cammode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT) + 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(); +#ifdef GTA_SCENE_EDIT + if(CSceneEdit::m_bEditOn) + CSceneEdit::Draw(); + else +#endif + CHud::Draw(); + CUserDisplay::OnscnTimer.ProcessForDisplay(); + CMessages::Display(); + CDarkel::DrawMessages(); + CGarages::PrintMessages(); + CPad::PrintErrorMessage(); + CFont::DrawFonts(); + +#ifdef DEBUGMENU + DebugMenuRender(); +#endif + POP_RENDERGROUP(); +} + +void +RenderMenus(void) +{ + if (FrontEndMenuManager.m_bMenuActive) + { + PUSH_RENDERGROUP("RenderMenus"); + PUSH_MEMID(MEMID_FRONTEND); + FrontEndMenuManager.DrawFrontEnd(); + POP_MEMID(); + POP_RENDERGROUP(); + } +} + +void +Render2dStuffAfterFade(void) +{ + PUSH_RENDERGROUP("Render2dStuffAfterFade"); +#ifndef MASTER + DisplayGameDebugText(); +#endif + + CHud::DrawAfterFade(); + CFont::DrawFonts(); + POP_RENDERGROUP(); +} + +void +Idle(void *arg) +{ +#ifdef ASPECT_RATIO_SCALE + CDraw::SetAspectRatio(CDraw::FindAspectRatio()); +#endif + + CTimer::Update(); + + tbInit(); + + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + + // We're basically merging FrontendIdle and Idle (just like TheGame on PS2) +#ifdef PS2_SAVE_DIALOG + // Only exists on PC FrontendIdle, probably some PS2 bug fix + if (FrontEndMenuManager.m_bMenuActive) + CSprite2d::SetRecipNearClip(); + + if (FrontEndMenuManager.m_bGameNotLoaded) { + CPad::UpdatePads(); + FrontEndMenuManager.Process(); + } else { + PUSH_MEMID(MEMID_GAME_PROCESS); + CPointLights::InitPerFrame(); + tbStartTimer(0, "CGame::Process"); + CGame::Process(); + tbEndTimer("CGame::Process"); + POP_MEMID(); + + tbStartTimer(0, "DMAudio.Service"); + DMAudio.Service(); + tbEndTimer("DMAudio.Service"); + } + + if (RsGlobal.quit) + return; +#else + + PUSH_MEMID(MEMID_GAME_PROCESS); + CPointLights::InitPerFrame(); + + tbStartTimer(0, "CGame::Process"); + CGame::Process(); + tbEndTimer("CGame::Process"); + POP_MEMID(); + + tbStartTimer(0, "DMAudio.Service"); + DMAudio.Service(); + tbEndTimer("DMAudio.Service"); +#endif + + if(CGame::bDemoMode && CTimer::GetTimeInMilliseconds() > (3*60 + 30)*1000 && !CCutsceneMgr::IsCutsceneProcessing()){ + WANT_TO_LOAD = false; + FrontEndMenuManager.m_bWantToRestart = true; + return; + } + + if(FrontEndMenuManager.m_bWantToRestart || FOUND_GAME_TO_LOAD) + { + return; + } + + SetLightsWithTimeOfDayColour(Scene.world); + + if(arg == nil) + return; + + PUSH_MEMID(MEMID_RENDER); + + if((!FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bRenderGameInMenu) && + TheCamera.GetScreenFadeStatus() != FADE_2) + { +#if defined(GTA_PC) && !defined(RW_GL3) && defined(FIX_BUGS) + // This is from SA, but it's nice for windowed mode + if (!FrontEndMenuManager.m_bRenderGameInMenu) { + RwV2d pos; + pos.x = SCREEN_WIDTH / 2.0f; + pos.y = SCREEN_HEIGHT / 2.0f; + RsMouseSetPos(&pos); + } +#endif + + PUSH_MEMID(MEMID_RENDERLIST); + tbStartTimer(0, "CnstrRenderList"); +#ifdef NEW_RENDERER + if(gbNewRenderer){ + CWorld::AdvanceCurrentScanCode(); // don't think this is even necessary + CRenderer::ClearForFrame(); + } +#endif + CRenderer::ConstructRenderList(); + tbEndTimer("CnstrRenderList"); + + tbStartTimer(0, "PreRender"); + CRenderer::PreRender(); + tbEndTimer("PreRender"); + POP_MEMID(); + +#ifdef FIX_BUGS + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); // TODO: temp? this fixes OpenGL render but there should be a better place for this + // This has to be done BEFORE RwCameraBeginUpdate + RwCameraSetFarClipPlane(Scene.camera, CTimeCycle::GetFarClip()); + RwCameraSetFogDistance(Scene.camera, CTimeCycle::GetFogStart()); +#endif + + if(CWeather::LightningFlash && !CCullZones::CamNoRain()){ + if(!DoRWStuffStartOfFrame_Horizon(255, 255, 255, 255, 255, 255, 255)) + goto popret; + }else{ + if(!DoRWStuffStartOfFrame_Horizon(CTimeCycle::GetSkyTopRed(), CTimeCycle::GetSkyTopGreen(), CTimeCycle::GetSkyTopBlue(), + CTimeCycle::GetSkyBottomRed(), CTimeCycle::GetSkyBottomGreen(), CTimeCycle::GetSkyBottomBlue(), + 255)) + goto popret; + } + + DefinedState(); + +#ifndef FIX_BUGS + RwCameraSetFarClipPlane(Scene.camera, CTimeCycle::GetFarClip()); + RwCameraSetFogDistance(Scene.camera, CTimeCycle::GetFogStart()); +#endif + + tbStartTimer(0, "RenderScene"); + RenderScene(); + tbEndTimer("RenderScene"); + +#ifdef EXTENDED_PIPELINES + CustomPipes::EnvMapRender(); +#endif + + RenderDebugShit(); + RenderEffects(); + + if((TheCamera.m_BlurType == MOTION_BLUR_NONE || TheCamera.m_BlurType == MOTION_BLUR_LIGHT_SCENE) && + TheCamera.m_ScreenReductionPercentage > 0.0f) + TheCamera.SetMotionBlurAlpha(150); + +#ifdef SCREEN_DROPLETS + CPostFX::GetBackBuffer(Scene.camera); + ScreenDroplets::Process(); + ScreenDroplets::Render(); +#endif + + tbStartTimer(0, "RenderMotionBlur"); + TheCamera.RenderMotionBlur(); + tbEndTimer("RenderMotionBlur"); + + tbStartTimer(0, "Render2dStuff"); + Render2dStuff(); + tbEndTimer("Render2dStuff"); + }else{ +#ifdef ASPECT_RATIO_SCALE + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); +#else + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, DEFAULT_ASPECT_RATIO); +#endif + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, CLEARMODE); + if(!RsCameraBeginUpdate(Scene.camera)) + goto popret; + } + +#ifdef PS2_SAVE_DIALOG + if (FrontEndMenuManager.m_bMenuActive) + DefinedState(); +#endif + tbStartTimer(0, "RenderMenus"); + RenderMenus(); + tbEndTimer("RenderMenus"); + +#ifdef PS2_MENU + if ( TheMemoryCard.m_bWantToLoad ) + goto popret; +#endif + + tbStartTimer(0, "DoFade"); + DoFade(); + tbEndTimer("DoFade"); + + tbStartTimer(0, "Render2dStuff-Fade"); + Render2dStuffAfterFade(); + tbEndTimer("Render2dStuff-Fade"); + + CCredits::Render(); + + + if (gbShowTimebars) + tbDisplay(); + + DoRWStuffEndOfFrame(); + + POP_MEMID(); // MEMID_RENDER + + if(g_SlowMode) + ProcessSlowMode(); + return; + +popret: POP_MEMID(); // MEMID_RENDER +} + +void +FrontendIdle(void) +{ +#ifdef ASPECT_RATIO_SCALE + CDraw::SetAspectRatio(CDraw::FindAspectRatio()); +#endif + + CTimer::Update(); + CSprite2d::SetRecipNearClip(); // this should be on InitialiseRenderWare according to PS2 asm. seems like a bug fix + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + CPad::UpdatePads(); + FrontEndMenuManager.Process(); + + if(RsGlobal.quit) + return; + +#ifdef ASPECT_RATIO_SCALE + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); +#else + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, DEFAULT_ASPECT_RATIO); +#endif + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, CLEARMODE); + if(!RsCameraBeginUpdate(Scene.camera)) + return; + + DefinedState(); // seems redundant, but breaks resolution change. + RenderMenus(); + DoFade(); + Render2dStuffAfterFade(); +// CFont::DrawFonts(); // redundant + DoRWStuffEndOfFrame(); +} + +void +InitialiseGame(void) +{ + LoadingScreen(nil, nil, "loadsc0"); + CGame::Initialise("DATA\\GTA3.DAT"); +} + +RsEventStatus +AppEventHandler(RsEvent event, void *param) +{ + switch( event ) + { + case rsINITIALIZE: + { + CGame::InitialiseOnceBeforeRW(); + return RsInitialize() ? rsEVENTPROCESSED : rsEVENTERROR; + } + + case rsCAMERASIZE: + { + + CameraSize(Scene.camera, (RwRect *)param, + SCREEN_VIEWWINDOW, DEFAULT_ASPECT_RATIO); + + return rsEVENTPROCESSED; + } + + case rsRWINITIALIZE: + { + 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: + { +#ifdef PS2_SAVE_DIALOG + Idle((void*)1); +#else + FrontendIdle(); +#endif + + return rsEVENTPROCESSED; + } + + case rsACTIVATE: + { + param ? DMAudio.ReacquireDigitalHandle() : DMAudio.ReleaseDigitalHandle(); + + return rsEVENTPROCESSED; + } + + default: + { + return rsEVENTNOTPROCESSED; + } + } +} + +#ifndef MASTER +void +TheModelViewer(void) +{ +#if (defined(GTA_PS2) || defined(GTA_XBOX)) + //TODO +#else + // This is III Mobile code. III Xbox code run it like main function, which is impossible to implement on PC's state machine implementation. + // Also we want 2D things initialized in here to print animation ids etc., our additions for that marked with X + +#ifdef ASPECT_RATIO_SCALE + CDraw::SetAspectRatio(CDraw::FindAspectRatio()); // X +#endif + CAnimViewer::Update(); + CTimer::Update(); + SetLightsWithTimeOfDayColour(Scene.world); + CRenderer::ConstructRenderList(); + DoRWStuffStartOfFrame(CTimeCycle::GetSkyTopRed(), CTimeCycle::GetSkyTopGreen(), CTimeCycle::GetSkyTopBlue(), + CTimeCycle::GetSkyBottomRed(), CTimeCycle::GetSkyBottomGreen(), CTimeCycle::GetSkyBottomBlue(), + 255); + + CSprite2d::InitPerFrame(); // X + CFont::InitPerFrame(); // X + DefinedState(); + CVisibilityPlugins::InitAlphaEntityList(); + CAnimViewer::Render(); + Render2dStuff(); // X + DoRWStuffEndOfFrame(); +#endif +} +#endif + + +#ifdef GTA_PS2 +void TheGame(void) +{ + printf("Into TheGame!!!\n"); + + PUSH_MEMID(MEMID_GAME); // NB: not popped + + CTimer::Initialise(); + +#if GTA_VERSION <= GTA3_PS2_160 + CGame::Initialise(); +#else + CGame::Initialise("DATA\\GTA3.DAT"); +#endif + + Const char *splash = GetRandomSplashScreen(); // inlined here + + LoadingScreen("Starting Game", NULL, splash); + +#ifdef GTA_PS2 + if ( TheMemoryCard.CheckCardInserted(CARD_ONE) == CMemoryCard::NO_ERR_SUCCESS + && TheMemoryCard.ChangeDirectory(CARD_ONE, TheMemoryCard.Cards[CARD_ONE].dir) + && TheMemoryCard.FindMostRecentFileName(CARD_ONE, TheMemoryCard.MostRecentFile) == true + && TheMemoryCard.CheckDataNotCorrupt(TheMemoryCard.MostRecentFile)) + { + strcpy(TheMemoryCard.LoadFileName, TheMemoryCard.MostRecentFile); + TheMemoryCard.b_FoundRecentSavedGameWantToLoad = true; + + if (CMenuManager::m_PrefsLanguage != TheMemoryCard.GetLanguageToLoad()) + { + CMenuManager::m_PrefsLanguage = TheMemoryCard.GetLanguageToLoad(); + TheText.Unload(); + TheText.Load(); + } + + CGame::currLevel = TheMemoryCard.GetLevelToLoad(); + } +#else + //TODO +#endif + + while (true) + { + if (WANT_TO_LOAD) + { + Const char *splash1 = GetLevelSplashScreen(CGame::currLevel); + LoadSplash(splash1); + } + + WANT_TO_LOAD = false; + + CTimer::Update(); + + while (!(FrontEndMenuManager.m_bWantToRestart || FOUND_GAME_TO_LOAD)) + { + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + + PUSH_MEMID(MEMID_GAME_PROCESS) + CPointLights::InitPerFrame(); + CGame::Process(); + POP_MEMID(); + + DMAudio.Service(); + + if (CGame::bDemoMode && CTimer::GetTimeInMilliseconds() > (3*60 + 30)*1000 && !CCutsceneMgr::IsCutsceneProcessing()) + { + WANT_TO_LOAD = false; + FrontEndMenuManager.m_bWantToRestart = true; + break; + } + + if (FrontEndMenuManager.m_bWantToRestart || FOUND_GAME_TO_LOAD) + break; + + SetLightsWithTimeOfDayColour(Scene.world); + + PUSH_MEMID(MEMID_RENDER); + + if ((!FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bRenderGameInMenu == true) && TheCamera.GetScreenFadeStatus() != FADE_2 ) + { + + PUSH_MEMID(MEMID_RENDERLIST); + CRenderer::ConstructRenderList(); + CRenderer::PreRender(); + POP_MEMID(); + +#ifdef FIX_BUGS + // This has to be done BEFORE RwCameraBeginUpdate + RwCameraSetFarClipPlane(Scene.camera, CTimeCycle::GetFarClip()); + RwCameraSetFogDistance(Scene.camera, CTimeCycle::GetFogStart()); +#endif + + if (CWeather::LightningFlash && !CCullZones::CamNoRain()) + DoRWStuffStartOfFrame_Horizon(255, 255, 255, 255, 255, 255, 255); + else + DoRWStuffStartOfFrame_Horizon(CTimeCycle::GetSkyTopRed(), CTimeCycle::GetSkyTopGreen(), CTimeCycle::GetSkyTopBlue(), CTimeCycle::GetSkyBottomRed(), CTimeCycle::GetSkyBottomGreen(), CTimeCycle::GetSkyBottomBlue(), 255); + + DefinedState(); +#ifndef FIX_BUGS + RwCameraSetFarClipPlane(Scene.camera, CTimeCycle::GetFarClip()); + RwCameraSetFogDistance(Scene.camera, CTimeCycle::GetFogStart()); +#endif + + RenderScene(); + RenderDebugShit(); + RenderEffects(); + + if ((TheCamera.m_BlurType == MOTION_BLUR_NONE || TheCamera.m_BlurType == MOTION_BLUR_LIGHT_SCENE) && TheCamera.m_ScreenReductionPercentage > 0.0f) + TheCamera.SetMotionBlurAlpha(150); + TheCamera.RenderMotionBlur(); + + Render2dStuff(); + } + else + { +#ifdef ASPECT_RATIO_SCALE + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); +#else + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, DEFAULT_ASPECT_RATIO); +#endif + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, CLEARMODE); + RsCameraBeginUpdate(Scene.camera); + } + + RenderMenus(); + + if (WANT_TO_LOAD) + { + POP_MEMID(); // MEMID_RENDER + break; + } + + DoFade(); + Render2dStuffAfterFade(); + CCredits::Render(); + + DoRWStuffEndOfFrame(); + + while (frameCount < 2) + ; + + frameCount = 0; + + CTimer::Update(); + + POP_MEMID(): // MEMID_RENDER + + if (g_SlowMode) + ProcessSlowMode(); + } + + CPad::ResetCheats(); + CPad::StopPadsShaking(); + DMAudio.ChangeMusicMode(MUSICMODE_DISABLE); + CGame::ShutDownForRestart(); + CTimer::Stop(); + + if (FrontEndMenuManager.m_bWantToRestart || FOUND_GAME_TO_LOAD) + { + if (FOUND_GAME_TO_LOAD) + { + FrontEndMenuManager.m_bWantToRestart = true; + WANT_TO_LOAD = true; + } + + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + FrontEndMenuManager.m_bWantToRestart = false; + + continue; + } + + break; + } + + DMAudio.Terminate(); +} + + +void SystemInit() +{ +#ifdef __MWERKS__ + mwInit(); +#endif + +#ifdef USE_CUSTOM_ALLOCATOR + InitMemoryMgr(); +#endif + +#ifdef GTA_PS2 + CFileMgr::InitCdSystem(); + + char path[256]; + + sprintf(path, "cdrom0:\\%s%s;1", "SYSTEM\\", "IOPRP23.IMG"); + + sceSifInitRpc(0); + + while ( !sceSifRebootIop(path) ) + ; + while( !sceSifSyncIop() ) + ; + + sceSifInitRpc(0); + + CFileMgr::InitCdSystem(); + + sceFsReset(); +#endif + + CFileMgr::Initialise(); + +#ifdef GTA_PS2 + CFileMgr::InitCd(); + + char modulepath[256]; + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "SIO2MAN.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "PADMAN.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "LIBSD.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "SDRDRV.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "MCMAN.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "MCSERV.IRX"); + LoadModule(modulepath); +#endif + + +#ifdef GTA_PS2 + ThreadParam param; + + param.entry = &IdleThread; + param.stack = idleThreadStack; + param.stackSize = 2048; + param.initPriority = 127; + param.gpReg = &_gp; + + int thread = CreateThread(¶m); + StartThread(thread, NULL); +#else + // +#endif + +#ifdef GTA_PS2_STUFF + CPad::Initialise(); +#endif + CPad::GetPad(0)->Mode = 0; + + CGame::frenchGame = false; + CGame::germanGame = false; + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + +#ifdef GTA_PS2 + int32 lang = sceScfGetLanguage(); + if ( lang == SCE_ITALIAN_LANGUAGE ) + CMenuManager::m_PrefsLanguage = LANGUAGE_ITALIAN; + else if ( lang == SCE_SPANISH_LANGUAGE ) + CMenuManager::m_PrefsLanguage = LANGUAGE_SPANISH; + else if ( lang == SCE_GERMAN_LANGUAGE ) + { + CGame::germanGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = LANGUAGE_GERMAN; + } + else if ( lang == SCE_FRENCH_LANGUAGE ) + { + CGame::frenchGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = LANGUAGE_FRENCH; + } + else + CMenuManager::m_PrefsLanguage = LANGUAGE_AMERICAN; + + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); +#else + // +#endif + +#ifdef GTA_PS2 + TheMemoryCard.Init(); +#endif +} + +int VBlankCounter(int ca) +{ + frameCount++; + ExitHandler(); + return 0; +} + +// linked against by RW! +extern "C" void WaitVBlank(void) +{ + int32 startFrame = frameCount; + while(startFrame == frameCount); +} + +void GameInit() +{ + if ( !gameAlreadyInitialised ) + { +#ifdef GTA_PS2 + char path[256]; + + strcpy(path, "cdrom0:\\"); + strcat(path, "SYSTEM\\"); + strcat(path, "CDSTREAM.IRX"); + LoadModule(path); + + strcpy(path, "cdrom0:\\"); + strcat(path, "SYSTEM\\"); + strcat(path, "SAMPMAN.IRX"); + LoadModule(path); + + strcpy(path, "cdrom0:\\"); + strcat(path, "SYSTEM\\"); + strcat(path, "MUSICSTR.IRX"); + LoadModule(path); +#endif + CdStreamInit(MAX_CDCHANNELS); + +#ifdef GTA_PS2 + Initialise3D(); //no params +#else + //TODO +#endif + +#ifdef GTA_PS2 + char *files[] = + { + "\\ANIM\\CUTS.IMG;1", + "\\ANIM\\CUTS.DIR;1", + "\\ANIM\\PED.IFP;1", + "\\MODELS\\FRONTEND.TXD;1", + "\\MODELS\\FONTS.TXD;1", + "\\MODELS\\HUD.TXD;1", + "\\MODELS\\PARTICLE.TXD;1", + "\\MODELS\\MISC.TXD;1", + "\\MODELS\\GENERIC.TXD;1", + "\\MODELS\\GTA3.DIR;1", + // TODO: japanese? +#ifdef GTA_PAL + "\\TEXT\\ENGLISH.GXT;1", + "\\TEXT\\FRENCH.GXT;1", + "\\TEXT\\GERMAN.GXT;1", + "\\TEXT\\ITALIAN.GXT;1", + "\\TEXT\\SPANISH.GXT;1", +#else + "\\TEXT\\AMERICAN.GXT;1", +#endif + "\\TXD\\LOADSC0.TXD;1", + "\\TXD\\LOADSC1.TXD;1", + "\\TXD\\LOADSC2.TXD;1", + "\\TXD\\LOADSC3.TXD;1", + "\\TXD\\LOADSC4.TXD;1", + "\\TXD\\LOADSC5.TXD;1", + "\\TXD\\LOADSC6.TXD;1", + "\\TXD\\LOADSC7.TXD;1", + "\\TXD\\LOADSC8.TXD;1", + "\\TXD\\LOADSC9.TXD;1", + "\\TXD\\LOADSC10.TXD;1", + "\\TXD\\LOADSC11.TXD;1", + "\\TXD\\LOADSC12.TXD;1", + "\\TXD\\LOADSC13.TXD;1", + "\\TXD\\LOADSC14.TXD;1", + "\\TXD\\LOADSC15.TXD;1", + "\\TXD\\LOADSC16.TXD;1", + "\\TXD\\LOADSC17.TXD;1", + "\\TXD\\LOADSC18.TXD;1", + "\\TXD\\LOADSC19.TXD;1", + "\\TXD\\LOADSC20.TXD;1", + "\\TXD\\LOADSC21.TXD;1", + "\\TXD\\LOADSC22.TXD;1", + "\\TXD\\LOADSC23.TXD;1", + "\\TXD\\LOADSC24.TXD;1", + "\\TXD\\LOADSC25.TXD;1", + "\\TXD\\NEWS.TXD;1", + "\\MODELS\\COLL\\GENERIC.COL;1", + "\\MODELS\\COLL\\INDUST.COL;1", + "\\MODELS\\COLL\\COMMER.COL;1", + "\\MODELS\\COLL\\SUBURB.COL;1", + "\\MODELS\\COLL\\WEAPONS.COL;1", + "\\MODELS\\COLL\\VEHICLES.COL;1", + "\\MODELS\\COLL\\PEDS.COL;1", + "\\MODELS\\GENERIC\\AIR_VLO.DFF;1", + "\\MODELS\\GENERIC\\WEAPONS.DFF;1", + "\\MODELS\\GENERIC\\WHEELS.DFF;1", + "\\MODELS\\GENERIC\\LOPLYGUY.DFF;1", + "\\MODELS\\GENERIC\\ARROW.DFF;1", + "\\MODELS\\GENERIC\\ZONECYLB.DFF;1", + "\\DATA\\MAPS\\COMNTOP.IPL;1", + "\\DATA\\MAPS\\COMNBTM.IPL;1", + "\\DATA\\MAPS\\COMSE.IPL;1", + "\\DATA\\MAPS\\COMSW.IPL;1", + "\\DATA\\MAPS\\CULL.IPL;1", + "\\DATA\\MAPS\\INDUSTNE.IPL;1", + "\\DATA\\MAPS\\INDUSTNW.IPL;1", + "\\DATA\\MAPS\\INDUSTSE.IPL;1", + "\\DATA\\MAPS\\INDUSTSW.IPL;1", + "\\DATA\\MAPS\\SUBURBNE.IPL;1", + "\\DATA\\MAPS\\SUBURBSW.IPL;1", + "\\DATA\\MAPS\\OVERVIEW.IPL;1", + "\\DATA\\MAPS\\PROPS.IPL;1", + "\\DATA\\MAPS\\GTA3.IDE;1", + "\\DATA\\PATHS\\FLIGHT.DAT;1", + "\\DATA\\PATHS\\FLIGHT2.DAT;1", + "\\DATA\\PATHS\\FLIGHT3.DAT;1", + "\\DATA\\PATHS\\FLIGHT4.DAT;1", + "\\DATA\\PATHS\\TRACKS.DAT;1", + "\\DATA\\PATHS\\TRACKS2.DAT;1", + "\\DATA\\PATHS\\CHASE0.DAT;1", + "\\DATA\\PATHS\\CHASE1.DAT;1", + "\\DATA\\PATHS\\CHASE2.DAT;1", + "\\DATA\\PATHS\\CHASE3.DAT;1", + "\\DATA\\PATHS\\CHASE4.DAT;1", + "\\DATA\\PATHS\\CHASE5.DAT;1", + "\\DATA\\PATHS\\CHASE6.DAT;1", + "\\DATA\\PATHS\\CHASE7.DAT;1", + "\\DATA\\PATHS\\CHASE10.DAT;1", + "\\DATA\\PATHS\\CHASE11.DAT;1", + "\\DATA\\PATHS\\CHASE14.DAT;1", + "\\DATA\\PATHS\\CHASE16.DAT;1", + "\\DATA\\PATHS\\CHASE18.DAT;1", + "\\DATA\\PATHS\\CHASE19.DAT;1" + }; + + for ( int32 i = 0; i < ARRAY_SIZE(files); i++ ) + SkyRegisterFileOnCd([i]); +#endif + + CreateDebugFont(); + +#ifdef GTA_PS2 + AddIntcHandler(INTC_VBLANK_S, VBlankCounter, 0); +#endif + + CameraSize(Scene.camera, NULL, DEFAULT_VIEWWINDOW, DEFAULT_ASPECT_RATIO); + + CSprite2d::SetRecipNearClip(); + CTxdStore::Initialise(); + + PUSH_MEMID(MEMID_TEXTURES); + CFont::Initialise(); + CHud::Initialise(); + POP_MEMID(); + + ValidateVersion(); + +#ifdef GTA_PS2 + sceCdCLOCK rtc; + sceCdReadClock(&rtc); + uint32 seed = rtc.minute + rtc.day; + uint32 seed2 = (seed << 4)-seed; + uint32 seed3 = (seed2 << 4)-seed2; + srand ((seed3<<4)+rtc.second); +#else + //TODO: mysrand(); +#endif + + gameAlreadyInitialised = true; + } +} + +void PlayIntroMPEGs() +{ +#ifdef GTA_PS2 + if (gameAlreadyInitialised) + RpSkySuspend(); + + InitMPEGPlayer(); + +#ifdef GTA_PAL + PlayMPEG("cdrom0:\\MOVIES\\DMAPAL.PSS;1", false); + + if (CGame::frenchGame || CGame::germanGame) + PlayMPEG("cdrom0:\\MOVIES\\INTROPAF.PSS;1", true); + else + PlayMPEG("cdrom0:\\MOVIES\\INTROPAL.PSS;1", true); +#else + PlayMPEG("cdrom0:\\MOVIES\\DMANTSC.PSS;1", false); + + PlayMPEG("cdrom0:\\MOVIES\\INTRNTSC.PSS;1", true); +#endif + + ShutdownMPEGPlayer(); + + if ( gameAlreadyInitialised ) + RpSkyResume(); +#else + //TODO +#endif +} + +int +main(int argc, char *argv[]) +{ +#ifdef __MWERKS__ + mwInit(); // metrowerks initialisation +#endif + + SystemInit(); + +#ifdef GTA_PS2 + int32 r = TheMemoryCard.CheckCardStateAtGameStartUp(CARD_ONE); + + if ( r == CMemoryCard::ERR_DIRNOENTRY || r == CMemoryCard::ERR_NOFORMAT ) + { + GameInit(); + + TheText.Unload(); + TheText.Load(); + + CFont::Initialise(); + + FrontEndMenuManager.DrawMemoryCardStartUpMenus(); + }else if(r == CMemoryCard::ERR_OPENNOENTRY || r == CMemoryCard::ERR_NONE){ + // eh? + } +#endif + + PlayIntroMPEGs(); + + GameInit(); + + if ( CGame::frenchGame || CGame::germanGame ) + LoadingScreen(NULL, version_name, "loadsc24"); + else + LoadingScreen(NULL, version_name, "loadsc0"); + + DMAudio.Initialise(); + + TheGame(); + + CGame::ShutDown(); + + RwEngineStop(); + RwEngineClose(); + RwEngineTerm(); + +#ifdef __MWERKS__ + mwExit(); // metrowerks shutdown +#endif + + return 0; +} +#endif diff --git a/src/core/main.h b/src/core/main.h new file mode 100644 index 0000000..803afb1 --- /dev/null +++ b/src/core/main.h @@ -0,0 +1,76 @@ +#pragma once + +#ifndef FINAL +// defined in RwHelpder.cpp +void PushRendergroup(const char *name); +void PopRendergroup(void); +#define PUSH_RENDERGROUP(str) PushRendergroup(str) +#define POP_RENDERGROUP() PopRendergroup() +#else +#define PUSH_RENDERGROUP(str) +#define POP_RENDERGROUP() +#endif + +struct GlobalScene +{ + RpWorld *world; + RwCamera *camera; +}; +extern GlobalScene Scene; + +extern uint8 work_buff[55000]; +extern char gString[256]; +extern char gString2[512]; +extern wchar gUString[256]; +extern wchar gUString2[256]; +extern bool gbPrintShite; +extern bool gbModelViewer; +#ifdef TIMEBARS +extern bool gbShowTimebars; +#else +#define gbShowTimebars false +#endif + +#ifndef FINAL +extern bool gbPrintMemoryUsage; +#endif + +class CSprite2d; + +bool DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +bool DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +void DoRWStuffEndOfFrame(void); +void PreAllocateRwObjects(void); +void InitialiseGame(void); +void LoadingScreen(const char *str1, const char *str2, const char *splashscreen); +void LoadingIslandScreen(const char *levelName); +CSprite2d *LoadSplash(const char *name); +void DestroySplashScreen(void); +Const char *GetLevelSplashScreen(int level); +Const char *GetRandomSplashScreen(void); +void LittleTest(void); +void ValidateVersion(); +void ResetLoadingScreenBar(void); +#ifndef MASTER +void TheModelViewer(void); +#endif + +#ifdef LOAD_INI_SETTINGS +bool LoadINISettings(); +void SaveINISettings(); +void LoadINIControllerSettings(); +void SaveINIControllerSettings(); +#endif + +#ifdef NEW_RENDERER +extern bool gbNewRenderer; +bool FredIsInFirstPersonCam(void); +#endif + +#ifdef DRAW_GAME_VERSION_TEXT +extern bool gbDrawVersionText; +#endif + +#ifdef NO_MOVIES +extern bool gbNoMovies; +#endif diff --git a/src/core/obrstr.cpp b/src/core/obrstr.cpp new file mode 100644 index 0000000..d9f7e9b --- /dev/null +++ b/src/core/obrstr.cpp @@ -0,0 +1,119 @@ +#include "common.h" +#include "Debug.h" +#include "obrstr.h" + +char obrstr[128]; +char obrstr2[128]; + +void ObrInt(int32 n1) +{ + IntToStr(n1, obrstr); + CDebug::DebugAddText(obrstr); +} + +void ObrInt2(int32 n1, int32 n2) +{ + IntToStr(n1, obrstr); + strcat(obrstr, " "); + IntToStr(n2, obrstr2); + strcat(obrstr, obrstr2); + CDebug::DebugAddText(obrstr); +} + +void ObrInt3(int32 n1, int32 n2, int32 n3) +{ + IntToStr(n1, obrstr); + strcat(obrstr, " "); + IntToStr(n2, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n3, obrstr2); + strcat(obrstr, obrstr2); + CDebug::DebugAddText(obrstr); +} + +void ObrInt4(int32 n1, int32 n2, int32 n3, int32 n4) +{ + IntToStr(n1, obrstr); + strcat(obrstr, " "); + IntToStr(n2, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n3, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n4, obrstr2); + strcat(obrstr, obrstr2); + CDebug::DebugAddText(obrstr); +} + +void ObrInt5(int32 n1, int32 n2, int32 n3, int32 n4, int32 n5) +{ + IntToStr(n1, obrstr); + strcat(obrstr, " "); + IntToStr(n2, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n3, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n4, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n5, obrstr2); + strcat(obrstr, obrstr2); + CDebug::DebugAddText(obrstr); +} + +void ObrInt6(int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + IntToStr(n1, obrstr); + strcat(obrstr, " "); + IntToStr(n2, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n3, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n4, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n5, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n6, obrstr2); + strcat(obrstr, obrstr2); + CDebug::DebugAddText(obrstr); +} + +void IntToStr(int32 inNum, char *outStr) +{ + bool isNeg = inNum < 0; + + if (isNeg) { + inNum = -inNum; + *outStr = '-'; + } + + int16 digits = 1; + + if (inNum > 9) { + int32 _inNum = inNum; + do { + digits++; + _inNum /= 10; + } while (_inNum > 9); + } + + int32 strSize = digits; + if (isNeg) + strSize++; + + char *pStr = &outStr[strSize]; + int32 i = 0; + do { + *(pStr-- - 1) = (inNum % 10) + '0'; + inNum /= 10; + } while (++i < strSize); + outStr[strSize] = '\0'; +} \ No newline at end of file diff --git a/src/core/obrstr.h b/src/core/obrstr.h new file mode 100644 index 0000000..c163361 --- /dev/null +++ b/src/core/obrstr.h @@ -0,0 +1,9 @@ +#pragma once + +void ObrInt(int32 n1); +void ObrInt2(int32 n1, int32 n2); +void ObrInt3(int32 n1, int32 n2, int32 n3); +void ObrInt4(int32 n1, int32 n2, int32 n3, int32 n4); +void ObrInt5(int32 n1, int32 n2, int32 n3, int32 n4, int32 n5); +void ObrInt6(int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); +void IntToStr(int32 inNum, char *outStr); \ No newline at end of file diff --git a/src/core/re3.cpp b/src/core/re3.cpp new file mode 100644 index 0000000..a721c23 --- /dev/null +++ b/src/core/re3.cpp @@ -0,0 +1,1233 @@ +#include +#define WITHWINDOWS +#include "common.h" +#if defined DETECT_JOYSTICK_MENU && defined XINPUT +#include +#if !defined(PSAPI_VERSION) || (PSAPI_VERSION > 1) +#pragma comment( lib, "Xinput9_1_0.lib" ) +#else +#pragma comment( lib, "Xinput.lib" ) +#endif +#endif +#include "Renderer.h" +#include "Credits.h" +#include "Camera.h" +#include "Weather.h" +#include "Clock.h" +#include "World.h" +#include "Vehicle.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "PathFind.h" +#include "Boat.h" +#include "Heli.h" +#include "Automobile.h" +#include "Console.h" +#include "Debug.h" +#include "Hud.h" +#include "SceneEdit.h" +#include "Pad.h" +#include "PlayerPed.h" +#include "Radar.h" +#include "debugmenu.h" +#include "Frontend.h" +#include "WaterLevel.h" +#include "main.h" +#include "Script.h" +#include "postfx.h" +#include "custompipes.h" +#include "MemoryHeap.h" +#include "FileMgr.h" +#include "Camera.h" +#include "MBlur.h" +#include "ControllerConfig.h" +#include "CarCtrl.h" +#include "Population.h" +#include "IniFile.h" +#include "Zones.h" + +#include "crossplatform.h" + +#ifndef _WIN32 +#include "assert.h" +#include +#endif + +#ifdef RWLIBS +extern "C" int vsprintf(char* const _Buffer, char const* const _Format, va_list _ArgList); +#endif + + +#ifdef USE_PS2_RAND +unsigned long long 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; +} + +#ifdef CUSTOM_FRONTEND_OPTIONS +#include "frontendoption.h" + +#ifdef MORE_LANGUAGES +void LangPolSelect(int8 action) +{ + if (action == FEOPTION_ACTION_SELECT) { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_POLISH; + FrontEndMenuManager.m_bFrontEnd_ReloadObrTxtGxt = true; + FrontEndMenuManager.InitialiseChangedLanguageSettings(); + FrontEndMenuManager.SaveSettings(); + } +} + +void LangRusSelect(int8 action) +{ + if (action == FEOPTION_ACTION_SELECT) { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_RUSSIAN; + FrontEndMenuManager.m_bFrontEnd_ReloadObrTxtGxt = true; + FrontEndMenuManager.InitialiseChangedLanguageSettings(); + FrontEndMenuManager.SaveSettings(); + } +} + +void LangJapSelect(int8 action) +{ + if (action == FEOPTION_ACTION_SELECT) { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_JAPANESE; + FrontEndMenuManager.m_bFrontEnd_ReloadObrTxtGxt = true; + FrontEndMenuManager.InitialiseChangedLanguageSettings(); + FrontEndMenuManager.SaveSettings(); + } +} +#endif + +void +CustomFrontendOptionsPopulate(void) +{ + // Most of custom options are done statically in MenuScreensCustom.cpp, we add them here only if they're dependent to extra files + + // These work only if we have neo folder + int fd; +#ifdef EXTENDED_PIPELINES + const char *vehPipelineNames[] = { "FED_MFX", "FED_NEO" }; + const char *off_on[] = { "FEM_OFF", "FEM_ON" }; + fd = CFileMgr::OpenFile("neo/neo.txd","r"); + if (fd) { +#ifdef GRAPHICS_MENU_OPTIONS + FrontendOptionSetCursor(MENUPAGE_GRAPHICS_SETTINGS, -3, false); + FrontendOptionAddSelect("FED_VPL", vehPipelineNames, ARRAY_SIZE(vehPipelineNames), (int8*)&CustomPipes::VehiclePipeSwitch, false, nil, "Graphics", "VehiclePipeline"); + FrontendOptionAddSelect("FED_PRM", off_on, 2, (int8*)&CustomPipes::RimlightEnable, false, nil, "Graphics", "NeoRimLight"); + FrontendOptionAddSelect("FED_WLM", off_on, 2, (int8*)&CustomPipes::LightmapEnable, false, nil, "Graphics", "NeoLightMaps"); + FrontendOptionAddSelect("FED_RGL", off_on, 2, (int8*)&CustomPipes::GlossEnable, false, nil, "Graphics", "NeoRoadGloss"); +#else + FrontendOptionSetCursor(MENUPAGE_DISPLAY_SETTINGS, -3, false); + FrontendOptionAddSelect("FED_VPL", vehPipelineNames, ARRAY_SIZE(vehPipelineNames), (int8*)&CustomPipes::VehiclePipeSwitch, false, nil, "Graphics", "VehiclePipeline"); + FrontendOptionAddSelect("FED_PRM", off_on, 2, (int8*)&CustomPipes::RimlightEnable, false, nil, "Graphics", "NeoRimLight"); + FrontendOptionAddSelect("FED_WLM", off_on, 2, (int8*)&CustomPipes::LightmapEnable, false, nil, "Graphics", "NeoLightMaps"); + FrontendOptionAddSelect("FED_RGL", off_on, 2, (int8*)&CustomPipes::GlossEnable, false, nil, "Graphics", "NeoRoadGloss"); +#endif + CFileMgr::CloseFile(fd); + } +#endif + + // Add outsourced language translations, if files are found +#ifdef MORE_LANGUAGES + int fd2; + FrontendOptionSetCursor(MENUPAGE_LANGUAGE_SETTINGS, 5, false); + if (fd = CFileMgr::OpenFile("text/polish.gxt","r")) { + if (fd2 = CFileMgr::OpenFile("models/fonts_p.txd","r")) { + FrontendOptionAddDynamic("FEL_POL", nil, nil, LangPolSelect, nil, nil); + CFileMgr::CloseFile(fd2); + } + CFileMgr::CloseFile(fd); + } + + if (fd = CFileMgr::OpenFile("text/russian.gxt","r")) { + if (fd2 = CFileMgr::OpenFile("models/fonts_r.txd","r")) { + FrontendOptionAddDynamic("FEL_RUS", nil, nil, LangRusSelect, nil, nil); + CFileMgr::CloseFile(fd2); + } + CFileMgr::CloseFile(fd); + } + + if (fd = CFileMgr::OpenFile("text/japanese.gxt","r")) { + if (fd2 = CFileMgr::OpenFile("models/fonts_j.txd","r")) { + FrontendOptionAddDynamic("FEL_JAP", nil, nil, LangJapSelect, nil, nil); + CFileMgr::CloseFile(fd2); + } + CFileMgr::CloseFile(fd); + } +#endif + +} +#endif + +#ifdef LOAD_INI_SETTINGS +#define MINI_CASE_SENSITIVE +#include "ini.h" + +mINI::INIFile ini("re3.ini"); +mINI::INIStructure cfg; + +bool ReadIniIfExists(const char *cat, const char *key, uint32 *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtoul(section.get(key).c_str(), &endPtr, 0); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, uint8 *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtoul(section.get(key).c_str(), &endPtr, 0); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, bool *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtoul(section.get(key).c_str(), &endPtr, 0); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, int32 *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtol(section.get(key).c_str(), &endPtr, 0); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, int8 *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtol(section.get(key).c_str(), &endPtr, 0); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, float *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtof(section.get(key).c_str(), &endPtr); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, char *out, int size) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + strncpy(out, section.get(key).c_str(), size - 1); + out[size - 1] = '\0'; + return true; + } + return false; +} + +void StoreIni(const char *cat, const char *key, uint32 val) +{ + char temp[11]; + sprintf(temp, "%u", val); + cfg[cat][key] = temp; +} + +void StoreIni(const char *cat, const char *key, uint8 val) +{ + char temp[11]; + sprintf(temp, "%u", val); + cfg[cat][key] = temp; +} + +void StoreIni(const char *cat, const char *key, int32 val) +{ + char temp[11]; + sprintf(temp, "%d", val); + cfg[cat][key] = temp; +} + +void StoreIni(const char *cat, const char *key, int8 val) +{ + char temp[11]; + sprintf(temp, "%d", val); + cfg[cat][key] = temp; +} + +void StoreIni(const char *cat, const char *key, float val) +{ + char temp[50]; + sprintf(temp, "%f", val); + cfg[cat][key] = temp; +} + +void StoreIni(const char *cat, const char *key, char *val, int size) +{ + cfg[cat][key] = val; +} + +const char *iniControllerActions[] = { "PED_FIREWEAPON", "PED_CYCLE_WEAPON_RIGHT", "PED_CYCLE_WEAPON_LEFT", "GO_FORWARD", "GO_BACK", "GO_LEFT", "GO_RIGHT", "PED_SNIPER_ZOOM_IN", + "PED_SNIPER_ZOOM_OUT", "VEHICLE_ENTER_EXIT", "CAMERA_CHANGE_VIEW_ALL_SITUATIONS", "PED_JUMPING", "PED_SPRINT", "PED_LOOKBEHIND", +#ifdef BIND_VEHICLE_FIREWEAPON + "VEHICLE_FIREWEAPON", +#endif + "VEHICLE_ACCELERATE", "VEHICLE_BRAKE", "VEHICLE_CHANGE_RADIO_STATION", "VEHICLE_HORN", "TOGGLE_SUBMISSIONS", "VEHICLE_HANDBRAKE", "PED_1RST_PERSON_LOOK_LEFT", + "PED_1RST_PERSON_LOOK_RIGHT", "VEHICLE_LOOKLEFT", "VEHICLE_LOOKRIGHT", "VEHICLE_LOOKBEHIND", "VEHICLE_TURRETLEFT", "VEHICLE_TURRETRIGHT", "VEHICLE_TURRETUP", "VEHICLE_TURRETDOWN", + "PED_CYCLE_TARGET_LEFT", "PED_CYCLE_TARGET_RIGHT", "PED_CENTER_CAMERA_BEHIND_PLAYER", "PED_LOCK_TARGET", "NETWORK_TALK", "PED_1RST_PERSON_LOOK_UP", "PED_1RST_PERSON_LOOK_DOWN", + "_CONTROLLERACTION_36", "TOGGLE_DPAD", "SWITCH_DEBUG_CAM_ON", "TAKE_SCREEN_SHOT", "SHOW_MOUSE_POINTER_TOGGLE" }; + +const char *iniControllerTypes[] = { "kbd:", "2ndKbd:", "mouse:", "joy:" }; + +const char *iniMouseButtons[] = {"LEFT","MIDDLE","RIGHT","WHLUP","WHLDOWN","X1","X2"}; + +const char *iniKeyboardButtons[] = {"ESC","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12", + "INS","DEL","HOME","END","PGUP","PGDN","UP","DOWN","LEFT","RIGHT","DIVIDE","TIMES","PLUS","MINUS","PADDEL", + "PADEND","PADDOWN","PADPGDN","PADLEFT","PAD5","NUMLOCK","PADRIGHT","PADHOME","PADUP","PADPGUP","PADINS", + "PADENTER", "SCROLL","PAUSE","BACKSP","TAB","CAPSLK","ENTER","LSHIFT","RSHIFT","SHIFT","LCTRL","RCTRL","LALT", + "RALT", "LWIN", "RWIN", "APPS", "NULL"}; + +void LoadINIControllerSettings() +{ +#ifdef DETECT_JOYSTICK_MENU +#ifdef XINPUT + int storedJoy1 = -1; + if (ReadIniIfExists("Controller", "JoystickName", &storedJoy1)) { + CPad::XInputJoy1 = -1; + CPad::XInputJoy2 = -1; + XINPUT_STATE xstate; + memset(&xstate, 0, sizeof(XINPUT_STATE)); + + // Firstly confirm & set joy 1 + if (XInputGetState(storedJoy1, &xstate) == ERROR_SUCCESS) { + CPad::XInputJoy1 = storedJoy1; + } + + for (int i = 0; i <= 3; i++) { + if (XInputGetState(i, &xstate) == ERROR_SUCCESS) { + if (CPad::XInputJoy1 == -1) + CPad::XInputJoy1 = i; + else if (CPad::XInputJoy2 == -1 && i != CPad::XInputJoy1) + CPad::XInputJoy2 = i; + } + } + + // There is no plug event on XInput, so let's leave XInputJoy1/2 as 0/1 respectively, and hotplug will be possible. + if (CPad::XInputJoy1 == -1) { + CPad::XInputJoy1 = 0; + CPad::XInputJoy2 = 1; + } else if (CPad::XInputJoy2 == -1) { + CPad::XInputJoy2 = (CPad::XInputJoy1 + 1) % 4; + } + } +#else + ReadIniIfExists("Controller", "JoystickName", gSelectedJoystickName, 128); +#endif +#endif + // force to default GTA behaviour (never overwrite bindings on joy change/initialization) if user init'ed/set bindings before we introduced that + if (!ReadIniIfExists("Controller", "PadButtonsInited", &ControlsManager.ms_padButtonsInited)) { + ControlsManager.ms_padButtonsInited = cfg.get("Bindings").size() != 0 ? 16 : 0; + } + + for (int32 i = 0; i < MAX_CONTROLLERACTIONS; i++) { + char value[128]; + if (ReadIniIfExists("Bindings", iniControllerActions[i], value, 128)) { + for (int32 j = 0; j < MAX_CONTROLLERTYPES; j++){ + ControlsManager.ClearSettingsAssociatedWithAction((e_ControllerAction)i, (eControllerType)j); + } + + for (char *binding = strtok(value,", "); binding != nil; binding = strtok(nil, ", ")) { + int contType = -1; + for (int32 k = 0; k < ARRAY_SIZE(iniControllerTypes); k++) { + int len = strlen(iniControllerTypes[k]); + if (strncmp(binding, iniControllerTypes[k], len) == 0) { + contType = k; + binding += len; + break; + } + } + if (contType == -1) + continue; + + int contKey; + if (contType == JOYSTICK) { + char *temp; + contKey = strtol(binding, &temp, 0); + + } else if (contType == KEYBOARD || contType == OPTIONAL_EXTRA) { + if (strlen(binding) == 1) { + contKey = binding[0]; + } else if(strcmp(binding, "SPC") == 0) { + contKey = ' '; + } else { + for (int32 k = 0; k < ARRAY_SIZE(iniKeyboardButtons); k++) { + if(strcmp(binding, iniKeyboardButtons[k]) == 0) { + contKey = 1000 + k; + break; + } + } + } + } else if (contType == MOUSE) { + for (int32 k = 0; k < ARRAY_SIZE(iniMouseButtons); k++) { + if(strcmp(binding, iniMouseButtons[k]) == 0) { + contKey = 1 + k; + break; + } + } + } + + ControlsManager.SetControllerKeyAssociatedWithAction((e_ControllerAction)i, contKey, (eControllerType)contType); + } + } + } +} + +void SaveINIControllerSettings() +{ + for (int32 i = 0; i < MAX_CONTROLLERACTIONS; i++) { + char value[128] = { '\0' }; + + // upper limit should've been GetNumOfSettingsForAction(i), but sadly even R* doesn't use it's own system correctly, and there are gaps between orders. + for (int32 j = SETORDER_1; j < MAX_SETORDERS; j++){ + + // We respect the m_ContSetOrder, and join/implode/order the bindings according to that; using comma as seperator. + for (int32 k = 0; k < MAX_CONTROLLERTYPES; k++){ + if (ControlsManager.m_aSettings[i][k].m_ContSetOrder == j) { + char next[32]; + if (k == JOYSTICK) { + snprintf(next, 32, "%s%d,", iniControllerTypes[k], ControlsManager.m_aSettings[i][k].m_Key); + + } else if (k == KEYBOARD || k == OPTIONAL_EXTRA) { + if (ControlsManager.m_aSettings[i][k].m_Key == ' ') + snprintf(next, 32, "%sSPC,", iniControllerTypes[k]); + else if (ControlsManager.m_aSettings[i][k].m_Key < 256) + snprintf(next, 32, "%s%c,", iniControllerTypes[k], ControlsManager.m_aSettings[i][k].m_Key); + else + snprintf(next, 32, "%s%s,", iniControllerTypes[k], iniKeyboardButtons[ControlsManager.m_aSettings[i][k].m_Key - 1000]); + + } else if (k == MOUSE) { + snprintf(next, 32, "%s%s,", iniControllerTypes[k], iniMouseButtons[ControlsManager.m_aSettings[i][k].m_Key - 1]); + } + strcat(value, next); + break; + } + } + } + int len = strlen(value); + if (len > 0) + value[len - 1] = '\0'; // to remove comma + + StoreIni("Bindings", iniControllerActions[i], value, 128); + } + +#ifdef DETECT_JOYSTICK_MENU +#ifdef XINPUT + StoreIni("Controller", "JoystickName", CPad::XInputJoy1); +#else + StoreIni("Controller", "JoystickName", gSelectedJoystickName, 128); +#endif +#endif + StoreIni("Controller", "PadButtonsInited", ControlsManager.ms_padButtonsInited); + + ini.write(cfg); +} + +bool LoadINISettings() +{ + if (!ini.read(cfg)) + return false; + +#ifdef IMPROVED_VIDEOMODE + ReadIniIfExists("VideoMode", "Width", &FrontEndMenuManager.m_nPrefsWidth); + ReadIniIfExists("VideoMode", "Height", &FrontEndMenuManager.m_nPrefsHeight); + ReadIniIfExists("VideoMode", "Depth", &FrontEndMenuManager.m_nPrefsDepth); + ReadIniIfExists("VideoMode", "Subsystem", &FrontEndMenuManager.m_nPrefsSubsystem); + // Windowed mode is loaded below in CUSTOM_FRONTEND_OPTIONS section +#else + ReadIniIfExists("Graphics", "VideoMode", &FrontEndMenuManager.m_nDisplayVideoMode); +#endif + ReadIniIfExists("Controller", "HeadBob1stPerson", &TheCamera.m_bHeadBob); + ReadIniIfExists("Controller", "VerticalMouseSens", &TheCamera.m_fMouseAccelVertical); + ReadIniIfExists("Controller", "HorizantalMouseSens", &TheCamera.m_fMouseAccelHorzntl); + ReadIniIfExists("Controller", "InvertMouseVertically", &MousePointerStateHelper.bInvertVertically); + ReadIniIfExists("Controller", "DisableMouseSteering", &CVehicle::m_bDisableMouseSteering); + ReadIniIfExists("Controller", "Vibration", &FrontEndMenuManager.m_PrefsUseVibration); + ReadIniIfExists("Audio", "SfxVolume", &FrontEndMenuManager.m_PrefsSfxVolume); + ReadIniIfExists("Audio", "MusicVolume", &FrontEndMenuManager.m_PrefsMusicVolume); + ReadIniIfExists("Audio", "Radio", &FrontEndMenuManager.m_PrefsRadioStation); +#ifdef EXTERNAL_3D_SOUND + ReadIniIfExists("Audio", "SpeakerType", &FrontEndMenuManager.m_PrefsSpeakers); + ReadIniIfExists("Audio", "Provider", &FrontEndMenuManager.m_nPrefsAudio3DProviderIndex); +#endif + ReadIniIfExists("Audio", "DynamicAcoustics", &FrontEndMenuManager.m_PrefsDMA); + ReadIniIfExists("Display", "Brightness", &FrontEndMenuManager.m_PrefsBrightness); + ReadIniIfExists("Display", "DrawDistance", &FrontEndMenuManager.m_PrefsLOD); + ReadIniIfExists("Display", "Subtitles", &FrontEndMenuManager.m_PrefsShowSubtitles); + ReadIniIfExists("Graphics", "AspectRatio", &FrontEndMenuManager.m_PrefsUseWideScreen); + ReadIniIfExists("Graphics", "VSync", &FrontEndMenuManager.m_PrefsVsyncDisp); + ReadIniIfExists("Graphics", "FrameLimiter", &FrontEndMenuManager.m_PrefsFrameLimiter); + ReadIniIfExists("Graphics", "Trails", &CMBlur::BlurOn); + ReadIniIfExists("General", "SkinFile", FrontEndMenuManager.m_PrefsSkinFile, 256); + ReadIniIfExists("Controller", "Method", &FrontEndMenuManager.m_ControlMethod); + ReadIniIfExists("General", "Language", &FrontEndMenuManager.m_PrefsLanguage); + +#ifdef EXTENDED_COLOURFILTER + ReadIniIfExists("CustomPipesValues", "PostFXIntensity", &CPostFX::Intensity); +#endif +#ifdef EXTENDED_PIPELINES + ReadIniIfExists("CustomPipesValues", "NeoVehicleShininess", &CustomPipes::VehicleShininess); + ReadIniIfExists("CustomPipesValues", "NeoVehicleSpecularity", &CustomPipes::VehicleSpecularity); + ReadIniIfExists("CustomPipesValues", "RimlightMult", &CustomPipes::RimlightMult); + ReadIniIfExists("CustomPipesValues", "LightmapMult", &CustomPipes::LightmapMult); + ReadIniIfExists("CustomPipesValues", "GlossMult", &CustomPipes::GlossMult); +#endif +#ifdef NEW_RENDERER + ReadIniIfExists("Rendering", "NewRenderer", &gbNewRenderer); +#endif + +#ifdef PROPER_SCALING + ReadIniIfExists("Draw", "ProperScaling", &CDraw::ms_bProperScaling); +#endif +#ifdef FIX_RADAR + ReadIniIfExists("Draw", "FixRadar", &CDraw::ms_bFixRadar); +#endif +#ifdef FIX_SPRITES + ReadIniIfExists("Draw", "FixSprites", &CDraw::ms_bFixSprites); +#endif +#ifdef DRAW_GAME_VERSION_TEXT + ReadIniIfExists("General", "DrawVersionText", &gbDrawVersionText); +#endif +#ifdef NO_MOVIES + ReadIniIfExists("General", "NoMovies", &gbNoMovies); +#endif + +#ifdef CUSTOM_FRONTEND_OPTIONS + bool migrate = cfg.get("FrontendOptions").size() != 0; + for (int i = 0; i < MENUPAGES; i++) { + for (int j = 0; j < NUM_MENUROWS; j++) { + CMenuScreenCustom::CMenuEntry &option = aScreens[i].m_aEntries[j]; + if (option.m_Action == MENUACTION_NOTHING) + break; + + // CFO check + if (option.m_Action < MENUACTION_NOTHING && option.m_CFO->save) { + // Migrate from old .ini to new .ini + // Old values can only be int8, new ones can contain float if it is slider + if (migrate && ReadIniIfExists("FrontendOptions", option.m_CFO->save, (int8*)option.m_CFO->value)) + cfg["FrontendOptions"].remove(option.m_CFO->save); + else if (option.m_Action == MENUACTION_CFO_SLIDER) + ReadIniIfExists(option.m_CFO->saveCat, option.m_CFO->save, (float*)option.m_CFO->value); + else + ReadIniIfExists(option.m_CFO->saveCat, option.m_CFO->save, (int8*)option.m_CFO->value); + + if (option.m_Action == MENUACTION_CFO_SELECT) { + option.m_CFOSelect->lastSavedValue = option.m_CFOSelect->displayedValue = *(int8*)option.m_CFO->value; + } + } + } + } +#endif + + // Fetched in above block, but needs evaluation +#ifdef PED_CAR_DENSITY_SLIDERS + CPopulation::MaxNumberOfPedsInUse = DEFAULT_MAX_NUMBER_OF_PEDS * CIniFile::PedNumberMultiplier; + CCarCtrl::MaxNumberOfCarsInUse = DEFAULT_MAX_NUMBER_OF_CARS * CIniFile::CarNumberMultiplier; +#endif + + return true; +} + +void SaveINISettings() +{ +#ifdef IMPROVED_VIDEOMODE + StoreIni("VideoMode", "Width", FrontEndMenuManager.m_nPrefsWidth); + StoreIni("VideoMode", "Height", FrontEndMenuManager.m_nPrefsHeight); + StoreIni("VideoMode", "Depth", FrontEndMenuManager.m_nPrefsDepth); + StoreIni("VideoMode", "Subsystem", FrontEndMenuManager.m_nPrefsSubsystem); + // Windowed mode is loaded below in CUSTOM_FRONTEND_OPTIONS section +#else + StoreIni("Graphics", "VideoMode", FrontEndMenuManager.m_nDisplayVideoMode); +#endif + StoreIni("Controller", "HeadBob1stPerson", TheCamera.m_bHeadBob); + StoreIni("Controller", "VerticalMouseSens", TheCamera.m_fMouseAccelVertical); + StoreIni("Controller", "HorizantalMouseSens", TheCamera.m_fMouseAccelHorzntl); + StoreIni("Controller", "InvertMouseVertically", MousePointerStateHelper.bInvertVertically); + StoreIni("Controller", "DisableMouseSteering", CVehicle::m_bDisableMouseSteering); + StoreIni("Controller", "Vibration", FrontEndMenuManager.m_PrefsUseVibration); + StoreIni("Audio", "SfxVolume", FrontEndMenuManager.m_PrefsSfxVolume); + StoreIni("Audio", "MusicVolume", FrontEndMenuManager.m_PrefsMusicVolume); + StoreIni("Audio", "Radio", FrontEndMenuManager.m_PrefsRadioStation); +#ifdef EXTERNAL_3D_SOUND + StoreIni("Audio", "SpeakerType", FrontEndMenuManager.m_PrefsSpeakers); + StoreIni("Audio", "Provider", FrontEndMenuManager.m_nPrefsAudio3DProviderIndex); +#endif + StoreIni("Audio", "DynamicAcoustics", FrontEndMenuManager.m_PrefsDMA); + StoreIni("Display", "Brightness", FrontEndMenuManager.m_PrefsBrightness); + StoreIni("Display", "DrawDistance", FrontEndMenuManager.m_PrefsLOD); + StoreIni("Display", "Subtitles", FrontEndMenuManager.m_PrefsShowSubtitles); + StoreIni("Graphics", "AspectRatio", FrontEndMenuManager.m_PrefsUseWideScreen); + StoreIni("Graphics", "VSync", FrontEndMenuManager.m_PrefsVsyncDisp); + StoreIni("Graphics", "FrameLimiter", FrontEndMenuManager.m_PrefsFrameLimiter); + StoreIni("Graphics", "Trails", CMBlur::BlurOn); + StoreIni("General", "SkinFile", FrontEndMenuManager.m_PrefsSkinFile, 256); + StoreIni("Controller", "Method", FrontEndMenuManager.m_ControlMethod); + StoreIni("General", "Language", FrontEndMenuManager.m_PrefsLanguage); + +#ifdef EXTENDED_COLOURFILTER + StoreIni("CustomPipesValues", "PostFXIntensity", CPostFX::Intensity); +#endif +#ifdef EXTENDED_PIPELINES + StoreIni("CustomPipesValues", "NeoVehicleShininess", CustomPipes::VehicleShininess); + StoreIni("CustomPipesValues", "NeoVehicleSpecularity", CustomPipes::VehicleSpecularity); + StoreIni("CustomPipesValues", "RimlightMult", CustomPipes::RimlightMult); + StoreIni("CustomPipesValues", "LightmapMult", CustomPipes::LightmapMult); + StoreIni("CustomPipesValues", "GlossMult", CustomPipes::GlossMult); +#endif +#ifdef NEW_RENDERER + StoreIni("Rendering", "NewRenderer", gbNewRenderer); +#endif + +#ifdef PROPER_SCALING + StoreIni("Draw", "ProperScaling", CDraw::ms_bProperScaling); +#endif +#ifdef FIX_RADAR + StoreIni("Draw", "FixRadar", CDraw::ms_bFixRadar); +#endif +#ifdef FIX_SPRITES + StoreIni("Draw", "FixSprites", CDraw::ms_bFixSprites); +#endif +#ifdef DRAW_GAME_VERSION_TEXT + StoreIni("General", "DrawVersionText", gbDrawVersionText); +#endif +#ifdef NO_MOVIES + StoreIni("General", "NoMovies", gbNoMovies); +#endif +#ifdef CUSTOM_FRONTEND_OPTIONS + for (int i = 0; i < MENUPAGES; i++) { + for (int j = 0; j < NUM_MENUROWS; j++) { + CMenuScreenCustom::CMenuEntry &option = aScreens[i].m_aEntries[j]; + if (option.m_Action == MENUACTION_NOTHING) + break; + + if (option.m_Action < MENUACTION_NOTHING && option.m_CFO->save) { + if (option.m_Action == MENUACTION_CFO_SLIDER) + StoreIni(option.m_CFO->saveCat, option.m_CFO->save, *(float*)option.m_CFO->value); + else + StoreIni(option.m_CFO->saveCat, option.m_CFO->save, *(int8*)option.m_CFO->value); + } + } + } +#endif + + ini.write(cfg); +} + +#endif + + +#ifdef DEBUGMENU +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(); + +DebugMenuEntry *carCol1; +DebugMenuEntry *carCol2; + +void +SpawnCar(int id) +{ + CVector playerpos; + CStreaming::RequestModel(id, 0); + CStreaming::LoadAllRequestedModels(false); + if(CStreaming::HasModelLoaded(id)){ + playerpos = FindPlayerCoors(); + int node; + if(!CModelInfo::IsBoatModel(id)){ + node = ThePaths.FindNodeClosestToCoors(playerpos, 0, 100.0f, false, false); + if(node < 0) + return; + } + + CVehicle *v; + if(CModelInfo::IsBoatModel(id)) + v = new CBoat(id, RANDOM_VEHICLE); + else + v = new CAutomobile(id, RANDOM_VEHICLE); + + v->bHasBeenOwnedByPlayer = true; + if(carCol1) + DebugMenuEntrySetAddress(carCol1, &v->m_currentColour1); + if(carCol2) + DebugMenuEntrySetAddress(carCol2, &v->m_currentColour2); + + if(CModelInfo::IsBoatModel(id)) + v->SetPosition(TheCamera.GetPosition() + TheCamera.GetForward()*15.0f); + else + v->SetPosition(ThePaths.m_pathNodes[node].GetPosition()); + + v->GetMatrix().GetPosition().z += 4.0f; + v->SetOrientation(0.0f, 0.0f, 3.49f); + v->SetStatus(STATUS_ABANDONED); + v->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(v); + } +} + +static void +FixCar(void) +{ + CVehicle *veh = FindPlayerVehicle(); + if(veh == nil) + return; + veh->m_fHealth = 1000.0f; + if(!veh->IsCar()) + return; + ((CAutomobile*)veh)->Damage.SetEngineStatus(0); + ((CAutomobile*)veh)->Fix(); +} + +#ifdef MENU_MAP +static void +TeleportToWaypoint(void) +{ + if (CRadar::TargetMarkerId == -1) + return; + CEntity* pEntityToTeleport = FindPlayerEntity(); + CVector vNewPos = CRadar::TargetMarkerPos; + CGame::currLevel = CTheZones::GetLevelFromPosition(&vNewPos); + CCollision::SortOutCollisionAfterLoad(); + CStreaming::LoadScene(vNewPos); + vNewPos.z = CWorld::FindGroundZForCoord(vNewPos.x, vNewPos.y) + pEntityToTeleport->GetDistanceFromCentreOfMassToBaseOfModel(); + pEntityToTeleport->Teleport(vNewPos); +} +#endif + +static void +SwitchCarCollision(void) +{ + if (FindPlayerVehicle() && FindPlayerVehicle()->IsCar()) + FindPlayerVehicle()->bUsesCollision = !FindPlayerVehicle()->bUsesCollision; +} + +static void +ToggleComedy(void) +{ + CVehicle *veh = FindPlayerVehicle(); + if(veh == nil) + return; + veh->bComedyControls = !veh->bComedyControls; +} + +static void +PlaceOnRoad(void) +{ + CVehicle *veh = FindPlayerVehicle(); + if(veh == nil) + return; + + if(veh->IsCar()) + ((CAutomobile*)veh)->PlaceOnRoadProperly(); +} + +static void +ResetCamStatics(void) +{ + TheCamera.Cams[TheCamera.ActiveCam].ResetStatics = true; +} + +#ifdef MISSION_SWITCHER +int8 nextMissionToSwitch = 0; +static void +SwitchToMission(void) +{ + CTheScripts::SwitchToMission(nextMissionToSwitch); +} +#endif + +#ifdef USE_CUSTOM_ALLOCATOR +static void ParseHeap(void) { gMainHeap.ParseHeap(); } +#endif + +static const char *carnames[] = { + "landstal", "idaho", "stinger", "linerun", "peren", "sentinel", "patriot", "firetruk", "trash", "stretch", "manana", "infernus", "blista", "pony", + "mule", "cheetah", "ambulan", "fbicar", "moonbeam", "esperant", "taxi", "kuruma", "bobcat", "mrwhoop", "bfinject", "corpse", "police", "enforcer", + "securica", "banshee", "predator", "bus", "rhino", "barracks", "train", "chopper", "dodo", "coach", "cabbie", "stallion", "rumpo", "rcbandit", + "bellyup", "mrwongs", "mafia", "yardie", "yakuza", "diablos", "columb", "hoods", "airtrain", "deaddodo", "speeder", "reefer", "panlant", "flatbed", + "yankee", "escape", "borgnine", "toyz", "ghost", +}; + +//#include + +static CTweakVar** TweakVarsList; +static int TweakVarsListSize = -1; +static bool bAddTweakVarsNow = false; +static const char *pTweakVarsDefaultPath = NULL; + +void CTweakVars::Add(CTweakVar *var) +{ + if(TweakVarsListSize == -1) { + TweakVarsList = (CTweakVar**)malloc(64 * sizeof(CTweakVar*)); + TweakVarsListSize = 0; + } + if(TweakVarsListSize > 63) + TweakVarsList = (CTweakVar**) realloc(TweakVarsList, (TweakVarsListSize + 1) * sizeof(CTweakVar*)); + + TweakVarsList[TweakVarsListSize++] = var; +// TweakVarsList.push_back(var); + + if ( bAddTweakVarsNow ) + var->AddDBG(pTweakVarsDefaultPath); +} + +void CTweakVars::AddDBG(const char *path) +{ + pTweakVarsDefaultPath = path; + + for(int i = 0; i < TweakVarsListSize; ++i) + TweakVarsList[i]->AddDBG(pTweakVarsDefaultPath); + + bAddTweakVarsNow = true; +} + +void CTweakSwitch::AddDBG(const char *path) +{ + DebugMenuEntry *e = DebugMenuAddVar(m_pPath == NULL ? path : m_pPath, m_pVarName, (int32_t *)m_pIntVar, m_pFunc, 1, m_nMin, m_nMax, m_aStr); + DebugMenuEntrySetWrap(e, true); +} + +void CTweakFunc::AddDBG (const char *path) { DebugMenuAddCmd (m_pPath == NULL ? path : m_pPath, m_pVarName, m_pFunc); } +void CTweakBool::AddDBG (const char *path) { DebugMenuAddVarBool8(m_pPath == NULL ? path : m_pPath, m_pVarName, (int8_t *)m_pBoolVar, NULL); } +void CTweakInt8::AddDBG (const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (int8_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakUInt8::AddDBG (const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (uint8_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakInt16::AddDBG (const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (int16_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakUInt16::AddDBG(const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (uint16_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakInt32::AddDBG (const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (int32_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakUInt32::AddDBG(const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (uint32_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakFloat::AddDBG (const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (float *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound); } + +/* +static const char *wt[] = { + "Sunny", "Cloudy", "Rainy", "Foggy" + }; + +SETTWEAKPATH("TEST"); +TWEAKSWITCH(CWeather::NewWeatherType, 0, 3, wt, NULL); +*/ + +void +DebugMenuPopulate(void) +{ + if(1){ + 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*)&CTimer::GetTimeScale(), 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); + + static int spawnCarId = MI_LANDSTAL; + e = DebugMenuAddVar("Spawn", "Spawn Car ID", &spawnCarId, nil, 1, MI_LANDSTAL, MI_GHOST, carnames); + DebugMenuEntrySetWrap(e, true); + DebugMenuAddCmd("Spawn", "Spawn Car", [](){ + if(spawnCarId == MI_TRAIN || + spawnCarId == MI_CHOPPER || + spawnCarId == MI_AIRTRAIN || + spawnCarId == MI_DEADDODO || + spawnCarId == MI_ESCAPE) + return; + SpawnCar(spawnCarId); + }); + static uint8 dummy; + carCol1 = DebugMenuAddVar("Spawn", "First colour", &dummy, nil, 1, 0, 255, nil); + carCol2 = DebugMenuAddVar("Spawn", "Second colour", &dummy, nil, 1, 0, 255, nil); + DebugMenuAddCmd("Spawn", "Spawn Stinger", [](){ SpawnCar(MI_STINGER); }); + DebugMenuAddCmd("Spawn", "Spawn Infernus", [](){ SpawnCar(MI_INFERNUS); }); + DebugMenuAddCmd("Spawn", "Spawn Cheetah", [](){ SpawnCar(MI_CHEETAH); }); + DebugMenuAddCmd("Spawn", "Spawn Esperanto", [](){ SpawnCar(MI_ESPERANT); }); + DebugMenuAddCmd("Spawn", "Spawn Stallion", [](){ SpawnCar(MI_STALLION); }); + DebugMenuAddCmd("Spawn", "Spawn Kuruma", [](){ SpawnCar(MI_KURUMA); }); + DebugMenuAddCmd("Spawn", "Spawn Taxi", [](){ SpawnCar(MI_TAXI); }); + DebugMenuAddCmd("Spawn", "Spawn Police", [](){ SpawnCar(MI_POLICE); }); + DebugMenuAddCmd("Spawn", "Spawn Enforcer", [](){ SpawnCar(MI_ENFORCER); }); + DebugMenuAddCmd("Spawn", "Spawn Banshee", [](){ SpawnCar(MI_BANSHEE); }); + DebugMenuAddCmd("Spawn", "Spawn Yakuza", [](){ SpawnCar(MI_YAKUZA); }); + DebugMenuAddCmd("Spawn", "Spawn Yardie", [](){ SpawnCar(MI_YARDIE); }); + DebugMenuAddCmd("Spawn", "Spawn Dodo", [](){ SpawnCar(MI_DODO); }); + DebugMenuAddCmd("Spawn", "Spawn Rhino", [](){ SpawnCar(MI_RHINO); }); + DebugMenuAddCmd("Spawn", "Spawn Firetruck", [](){ SpawnCar(MI_FIRETRUCK); }); + DebugMenuAddCmd("Spawn", "Spawn Predator", [](){ SpawnCar(MI_PREDATOR); }); + + DebugMenuAddVarBool8("Render", "Draw hud", &CHud::m_Wants_To_Draw_Hud, nil); + +#ifdef PROPER_SCALING + DebugMenuAddVarBool8("Render", "Proper Scaling", &CDraw::ms_bProperScaling, nil); +#endif +#ifdef FIX_RADAR + DebugMenuAddVarBool8("Render", "Fix Radar", &CDraw::ms_bFixRadar, nil); +#endif +#ifdef FIX_SPRITES + DebugMenuAddVarBool8("Render", "Fix Sprites", &CDraw::ms_bFixSprites, nil); +#endif + DebugMenuAddVarBool8("Render", "PS2 Alpha test Emu", &gPS2alphaTest, nil); + DebugMenuAddVarBool8("Render", "Frame limiter", &FrontEndMenuManager.m_PrefsFrameLimiter, nil); + DebugMenuAddVarBool8("Render", "VSynch", &FrontEndMenuManager.m_PrefsVsync, nil); + DebugMenuAddVar("Render", "Max FPS", &RsGlobal.maxFPS, nil, 1, 1, 1000, nil); +#ifdef NEW_RENDERER + DebugMenuAddVarBool8("Render", "New Renderer", &gbNewRenderer, nil); +extern bool gbRenderRoads; +extern bool gbRenderEverythingBarRoads; +//extern bool gbRenderFadingInUnderwaterEntities; +extern bool gbRenderFadingInEntities; +extern bool gbRenderWater; +extern bool gbRenderBoats; +extern bool gbRenderVehicles; +extern bool gbRenderWorld0; +extern bool gbRenderWorld1; +extern bool gbRenderWorld2; + DebugMenuAddVarBool8("Debug Render", "gbRenderRoads", &gbRenderRoads, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderEverythingBarRoads", &gbRenderEverythingBarRoads, nil); +// DebugMenuAddVarBool8("Debug Render", "gbRenderFadingInUnderwaterEntities", &gbRenderFadingInUnderwaterEntities, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderFadingInEntities", &gbRenderFadingInEntities, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderWater", &gbRenderWater, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderBoats", &gbRenderBoats, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderVehicles", &gbRenderVehicles, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderWorld0", &gbRenderWorld0, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderWorld1", &gbRenderWorld1, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderWorld2", &gbRenderWorld2, nil); +#endif + +#ifdef EXTENDED_COLOURFILTER + static const char *filternames[] = { "None", "Simple", "Normal", "Mobile" }; + e = DebugMenuAddVar("Render", "Colourfilter", &CPostFX::EffectSwitch, nil, 1, CPostFX::POSTFX_OFF, CPostFX::POSTFX_MOBILE, filternames); + DebugMenuEntrySetWrap(e, true); + DebugMenuAddVar("Render", "Intensity", &CPostFX::Intensity, nil, 0.05f, 0, 10.0f); + DebugMenuAddVarBool8("Render", "Motion Blur", &CPostFX::MotionBlurOn, nil); +#endif +#ifdef LIBRW + DebugMenuAddVarBool32("Render", "MatFX env map apply light", &rw::MatFX::envMapApplyLight, nil); + DebugMenuAddVarBool32("Render", "MatFX env map flip U", &rw::MatFX::envMapFlipU, nil); + DebugMenuAddVarBool32("Render", "MatFX env map use matcolor", &rw::MatFX::envMapUseMatColor, nil); +#endif +#ifdef EXTENDED_PIPELINES + static const char *vehpipenames[] = { "MatFX", "Neo" }; + e = DebugMenuAddVar("Render", "Vehicle Pipeline", &CustomPipes::VehiclePipeSwitch, nil, + 1, CustomPipes::VEHICLEPIPE_MATFX, CustomPipes::VEHICLEPIPE_NEO, vehpipenames); + DebugMenuEntrySetWrap(e, true); + DebugMenuAddVar("Render", "Neo Vehicle Shininess", &CustomPipes::VehicleShininess, nil, 0.1f, 0, 1.0f); + DebugMenuAddVar("Render", "Neo Vehicle Specularity", &CustomPipes::VehicleSpecularity, nil, 0.1f, 0, 1.0f); + DebugMenuAddVarBool8("Render", "Neo Ped Rim light enable", &CustomPipes::RimlightEnable, nil); + DebugMenuAddVar("Render", "Mult", &CustomPipes::RimlightMult, nil, 0.1f, 0, 1.0f); + DebugMenuAddVarBool8("Render", "Neo World Lightmaps enable", &CustomPipes::LightmapEnable, nil); + DebugMenuAddVar("Render", "Mult", &CustomPipes::LightmapMult, nil, 0.1f, 0, 1.0f); + DebugMenuAddVarBool8("Render", "Neo Road Gloss enable", &CustomPipes::GlossEnable, nil); + DebugMenuAddVar("Render", "Mult", &CustomPipes::GlossMult, nil, 0.1f, 0, 1.0f); +#endif + DebugMenuAddVarBool8("Debug Render", "Show Ped Paths", &gbShowPedPaths, nil); + DebugMenuAddVarBool8("Debug Render", "Show Car Paths", &gbShowCarPaths, nil); + DebugMenuAddVarBool8("Debug Render", "Show Car Path Links", &gbShowCarPathsLinks, nil); + DebugMenuAddVarBool8("Debug Render", "Show Ped Road Groups", &gbShowPedRoadGroups, nil); + DebugMenuAddVarBool8("Debug Render", "Show Car Road Groups", &gbShowCarRoadGroups, nil); + DebugMenuAddVarBool8("Debug Render", "Show Collision Lines", &gbShowCollisionLines, nil); + DebugMenuAddVarBool8("Debug Render", "Show Collision Polys", &gbShowCollisionPolys, nil); + DebugMenuAddVarBool8("Debug Render", "Don't render Buildings", &gbDontRenderBuildings, nil); + DebugMenuAddVarBool8("Debug Render", "Don't render Big Buildings", &gbDontRenderBigBuildings, nil); + DebugMenuAddVarBool8("Debug Render", "Don't render Peds", &gbDontRenderPeds, nil); + DebugMenuAddVarBool8("Debug Render", "Don't render Vehicles", &gbDontRenderVehicles, nil); + DebugMenuAddVarBool8("Debug Render", "Don't render Objects", &gbDontRenderObjects, nil); + DebugMenuAddVarBool8("Debug Render", "Don't Render Water", &gbDontRenderWater, nil); + + +#ifdef DRAW_GAME_VERSION_TEXT + DebugMenuAddVarBool8("Debug", "Version Text", &gbDrawVersionText, nil); +#endif + DebugMenuAddVarBool8("Debug", "Show DebugStuffInRelease", &gbDebugStuffInRelease, nil); +#ifdef TIMEBARS + DebugMenuAddVarBool8("Debug", "Show Timebars", &gbShowTimebars, nil); +#endif +#ifndef FINAL + DebugMenuAddVarBool8("Debug", "Use debug render groups", &bDebugRenderGroups, nil); + DebugMenuAddVarBool8("Debug", "Print Memory Usage", &gbPrintMemoryUsage, nil); +#ifdef USE_CUSTOM_ALLOCATOR + DebugMenuAddCmd("Debug", "Parse Heap", ParseHeap); +#endif +#endif + DebugMenuAddVarBool8("Debug", "Show cullzone debug stuff", &gbShowCullZoneDebugStuff, nil); + DebugMenuAddVarBool8("Debug", "Disable zone cull", &gbDisableZoneCull, nil); + + DebugMenuAddVarBool8("Debug", "pad 1 -> pad 2", &CPad::m_bMapPadOneToPadTwo, nil); +#ifdef GTA_SCENE_EDIT + DebugMenuAddVarBool8("Debug", "Edit on", &CSceneEdit::m_bEditOn, nil); +#endif + //DebugMenuAddCmd("Debug", "Start Credits", CCredits::Start); + //DebugMenuAddCmd("Debug", "Stop Credits", CCredits::Stop); + +#ifdef MENU_MAP + DebugMenuAddCmd("Game", "Teleport to map waypoint", TeleportToWaypoint); +#endif + DebugMenuAddCmd("Game", "Fix Car", FixCar); + DebugMenuAddCmd("Game", "Place Car on Road", PlaceOnRoad); + DebugMenuAddCmd("Game", "Switch car collision", SwitchCarCollision); + DebugMenuAddCmd("Game", "Toggle Comedy Controls", ToggleComedy); + + DebugMenuAddVarBool8("Game", "Toggle popping heads on headshot", &CPed::bPopHeadsOnHeadshot, nil); + +#ifdef MISSION_SWITCHER + DebugMenuEntry *missionEntry; + static const char* missions[] = { + "Intro Movie", "Hospital Info Scene", "Police Station Info Scene", + "RC Diablo Destruction", "RC Mafia Massacre", "RC Rumpo Rampage", "RC Casino Calamity", + "Patriot Playground", "A Ride In The Park", "Gripped!", "Multistorey Mayhem", + "Paramedic", "Firefighter", "Vigilante", "Taxi Driver", + "The Crook", "The Thieves", "The Wife", "Her Lover", + "Give Me Liberty and Luigi's Girls", "Don't Spank My Bitch Up", "Drive Misty For Me", "Pump-Action Pimp", "The Fuzz Ball", + "Mike Lips Last Lunch", "Farewell 'Chunky' Lee Chong", "Van Heist", "Cipriani's Chauffeur", "Dead Skunk In The Trunk", "The Getaway", + "Taking Out The Laundry", "The Pick-Up", "Salvatore's Called A Meeting", "Triads And Tribulations", "Blow Fish", "Chaperone", "Cutting The Grass", + "Bomb Da Base: Act I", "Bomb Da Base: Act II", "Last Requests", "Turismo", "I Scream, You Scream", "Trial By Fire", "Big'N'Veiny", "Sayonara Salvatore", + "Under Surveillance", "Paparazzi Purge", "Payday For Ray", "Two-Faced Tanner", "Kanbu Bust-Out", "Grand Theft Auto", "Deal Steal", "Shima", "Smack Down", + "Silence The Sneak", "Arms Shortage", "Evidence Dash", "Gone Fishing", "Plaster Blaster", "Marked Man", + "Liberator", "Waka-Gashira Wipeout!", "A Drop In The Ocean", "Bling-Bling Scramble", "Uzi Rider", "Gangcar Round-Up", "Kingdom Come", + "Grand Theft Aero", "Escort Service", "Decoy", "Love's Disappearance", "Bait", "Espresso-2-Go!", "S.A.M.", + "Uzi Money", "Toyminator", "Rigged To Blow", "Bullion Run", "Rumble", "The Exchange" + }; + + missionEntry = DebugMenuAddVar("Game", "Select mission", &nextMissionToSwitch, nil, 1, 0, ARRAY_SIZE(missions) - 1, missions); + DebugMenuEntrySetWrap(missionEntry, true); + DebugMenuAddCmd("Game", "Start selected mission ", SwitchToMission); +#endif + + extern bool PrintDebugCode; + extern int16 DebugCamMode; + DebugMenuAddVarBool8("Cam", "Use mouse Cam", &CCamera::m_bUseMouse3rdPerson, nil); +#ifdef FREE_CAM + DebugMenuAddVarBool8("Cam", "Free Cam", &CCamera::bFreeCam, nil); +#endif + DebugMenuAddVarBool8("Cam", "Print Debug Code", &PrintDebugCode, nil); + DebugMenuAddVar("Cam", "Cam Mode", &DebugCamMode, nil, 1, 0, CCam::MODE_EDITOR, nil); + DebugMenuAddCmd("Cam", "Normal", []() { DebugCamMode = 0; }); + // DebugMenuAddCmd("Cam", "Follow Ped With Bind", []() { DebugCamMode = CCam::MODE_FOLLOW_PED_WITH_BIND; }); + // DebugMenuAddCmd("Cam", "Reaction", []() { DebugCamMode = CCam::MODE_REACTION; }); + // DebugMenuAddCmd("Cam", "Chris", []() { DebugCamMode = CCam::MODE_CHRIS; }); + DebugMenuAddCmd("Cam", "Reset Statics", ResetCamStatics); + + CTweakVars::AddDBG("Debug"); + } +} +#endif + +#ifndef __MWERKS__ +#ifndef MASTER +const int re3_buffsize = 1024; +static char re3_buff[re3_buffsize]; +#endif + +#ifndef MASTER +void re3_assert(const char *expr, const char *filename, unsigned int lineno, const char *func) +{ +#ifdef _WIN32 + 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(); +#else + // TODO + printf("\nRE3 ASSERT FAILED\n\tFile: %s\n\tLine: %d\n\tFunction: %s\n\tExpression: %s\n",filename,lineno,func,expr); + assert(false); +#endif +} +#endif + +void re3_debug(const char *format, ...) +{ +#ifndef MASTER + va_list va; + va_start(va, format); +#ifdef _WIN32 + vsprintf_s(re3_buff, re3_buffsize, format, va); +#else + vsprintf(re3_buff, format, va); +#endif + va_end(va); + + printf("%s", re3_buff); + CDebug::DebugAddText(re3_buff); +#endif +} + +#ifndef MASTER +void re3_trace(const char *filename, unsigned int lineno, const char *func, const char *format, ...) +{ + char buff[re3_buffsize *2]; + va_list va; + va_start(va, format); +#ifdef _WIN32 + 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); +#else + vsprintf(re3_buff, format, va); + va_end(va); + + sprintf(buff, "[%s.%s:%d]: %s", filename, func, lineno, re3_buff); +#endif + + OutputDebugString(buff); +} +#endif + +#ifndef MASTER +void re3_usererror(const char *format, ...) +{ + va_list va; + va_start(va, format); +#ifdef _WIN32 + vsprintf_s(re3_buff, re3_buffsize, format, va); + va_end(va); + + ::MessageBoxA(nil, re3_buff, "RE3 Error!", + MB_OK|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL); + + raise(SIGABRT); + _exit(3); +#else + vsprintf(re3_buff, format, va); + printf("\nRE3 Error!\n\t%s\n",re3_buff); + assert(false); +#endif +} +#endif +#endif + +#ifdef VALIDATE_SAVE_SIZE +int32 _saveBufCount; +#endif diff --git a/src/core/templates.h b/src/core/templates.h new file mode 100644 index 0000000..545dac3 --- /dev/null +++ b/src/core/templates.h @@ -0,0 +1,269 @@ +#pragma once + +template +class CStore +{ +public: + int32 allocPtr; + T store[n]; + + T *Alloc(void){ + if(allocPtr >= n){ + printf("Size of this thing:%d needs increasing\n", n); + assert(0); + } + return &store[allocPtr++]; + } + void Clear(void){ + allocPtr = 0; + } + int32 GetIndex(T *item){ + assert(item >= &store[0]); + assert(item < &store[n]); + return item - store; + } + T *GetItem(int32 index){ + assert(index >= 0); + assert(index < n); + return &store[index]; + } +}; + +#define POOLFLAG_ID 0x7f +#define POOLFLAG_ISFREE 0x80 + +template +class CPool +{ + U *m_entries; + uint8 *m_flags; + int32 m_size; + int32 m_allocPtr; + +public: + CPool(int32 size){ + m_entries = (U*)new uint8[sizeof(U)*size]; + m_flags = new uint8[size]; + m_size = size; + m_allocPtr = 0; + for(int i = 0; i < size; i++){ + SetId(i, 0); + SetIsFree(i, true); + } + } + + int GetId(int i) const + { + return m_flags[i] & POOLFLAG_ID; + } + + bool GetIsFree(int i) const + { + return !!(m_flags[i] & POOLFLAG_ISFREE); + } + + void SetId(int i, int id) + { + m_flags[i] = (m_flags[i] & POOLFLAG_ISFREE) | (id & POOLFLAG_ID); + } + + void SetIsFree(int i, bool isFree) + { + if (isFree) + m_flags[i] |= POOLFLAG_ISFREE; + else + m_flags[i] &= ~POOLFLAG_ISFREE; + } + + ~CPool() { + Flush(); + } + void Flush() { + if (m_size > 0) { + delete[] (uint8*)m_entries; + delete[] m_flags; + m_entries = nil; + m_flags = nil; + m_size = 0; + m_allocPtr = 0; + } + } + int32 GetSize(void) const { return m_size; } + T *New(void){ + bool wrapped = false; + do +#ifdef FIX_BUGS + if (++m_allocPtr >= m_size) { + m_allocPtr = 0; + if (wrapped) + return nil; + wrapped = true; + } +#else + if(++m_allocPtr == m_size){ + if(wrapped) + return nil; + wrapped = true; + m_allocPtr = 0; + } +#endif + while(!GetIsFree(m_allocPtr)); + SetIsFree(m_allocPtr, false); + SetId(m_allocPtr, GetId(m_allocPtr)+1); + return (T*)&m_entries[m_allocPtr]; + } + T *New(int32 handle){ + T *entry = (T*)&m_entries[handle>>8]; + SetNotFreeAt(handle); + return entry; + } + void SetNotFreeAt(int32 handle){ + int idx = handle>>8; + SetIsFree(idx, false); + SetId(idx, handle & POOLFLAG_ID); + for(m_allocPtr = 0; m_allocPtr < m_size; m_allocPtr++) + if(GetIsFree(m_allocPtr)) + return; + } + void Delete(T *entry){ + int i = GetJustIndex(entry); + SetIsFree(i, true); + if(i < m_allocPtr) + m_allocPtr = i; + } + T *GetSlot(int i){ + return GetIsFree(i) ? nil : (T*)&m_entries[i]; + } + T *GetAt(int handle){ +#ifdef FIX_BUGS + if (handle == -1) + return nil; +#endif + return m_flags[handle>>8] == (handle & 0xFF) ? + (T*)&m_entries[handle >> 8] : nil; + } + int32 GetIndex(T *entry){ + int i = GetJustIndex_NoFreeAssert(entry); + return m_flags[i] + (i<<8); + } + int32 GetJustIndex(T *entry){ + int index = GetJustIndex_NoFreeAssert(entry); + assert(!GetIsFree(index)); + return index; + } + int32 GetJustIndex_NoFreeAssert(T* entry){ + int index = ((U*)entry - m_entries); + assert((U*)entry == (U*)&m_entries[index]); // cast is unsafe - check required + return index; + } + int32 GetNoOfUsedSpaces(void) const{ + int i; + int n = 0; + for(i = 0; i < m_size; i++) + if(!GetIsFree(i)) + n++; + return n; + } + void ClearStorage(uint8 *&flags, U *&entries){ + delete[] flags; + delete[] (uint8*)entries; + flags = nil; + entries = nil; + } + uint32 GetMaxEntrySize() const { return sizeof(U); } + 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, 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*)new uint8[sizeof(uint8)*m_size]; + entries = (U*)new uint8[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 +class CLink +{ +public: + T item; + CLink *prev; + CLink *next; + + void Insert(CLink *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 +class CLinkList +{ +public: + CLink head, tail; + CLink freeHead, freeTail; + CLink *links; + + void Init(int n){ + links = new CLink[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 *Insert(T const &item){ + CLink *node = freeHead.next; + if(node == &freeTail) + return nil; + node->item = item; + node->Remove(); // remove from free list + head.Insert(node); + return node; + } + CLink *InsertSorted(T const &item){ + CLink *sort; + for(sort = head.next; sort != &tail; sort = sort->next) + if(sort->item.sort >= item.sort) + break; + CLink *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 *link){ + link->Remove(); // remove from list + freeHead.Insert(link); // insert into free list + } + int32 Count(void){ + int n = 0; + CLink *lnk; + for(lnk = head.next; lnk != &tail; lnk = lnk->next) + n++; + return n; + } +}; diff --git a/src/core/timebars.cpp b/src/core/timebars.cpp new file mode 100644 index 0000000..94051b2 --- /dev/null +++ b/src/core/timebars.cpp @@ -0,0 +1,121 @@ +#include "common.h" +#ifndef MASTER +#include "Font.h" +#include "Frontend.h" +#include "Timer.h" +#include "Text.h" + +#define MAX_TIMERS (50) +#define MAX_MS_COLLECTED (40) + +// enables frame time output +#define FRAMETIME + +struct sTimeBar +{ + char name[20]; + float startTime; + float endTime; + int32 unk; +}; + +struct +{ + sTimeBar Timers[MAX_TIMERS]; + uint32 count; +} TimerBar; +float MaxTimes[MAX_TIMERS]; +float MaxFrameTime; + +uint32 curMS; +uint32 msCollected[MAX_MS_COLLECTED]; +#ifdef FRAMETIME +float FrameInitTime; +#endif + +void tbInit() +{ + TimerBar.count = 0; + uint32 i = CTimer::GetFrameCounter() & 0x7F; + if (i == 0) { + do + MaxTimes[i++] = 0.0f; + while (i != MAX_TIMERS); +#ifdef FRAMETIME + MaxFrameTime = 0.0f; +#endif + } +#ifdef FRAMETIME + FrameInitTime = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerFrame(); +#endif +} + +void tbStartTimer(int32 unk, Const char *name) +{ + strcpy(TimerBar.Timers[TimerBar.count].name, name); + TimerBar.Timers[TimerBar.count].unk = unk; + TimerBar.Timers[TimerBar.count].startTime = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerFrame(); + TimerBar.count++; +} + +void tbEndTimer(Const char* name) +{ + uint32 n = 1500; + for (uint32 i = 0; i < TimerBar.count; i++) { + if (strcmp(name, TimerBar.Timers[i].name) == 0) + n = i; + } + assert(n != 1500); + TimerBar.Timers[n].endTime = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerFrame(); +} + +float Diag_GetFPS() +{ + return 39000.0f / (msCollected[(curMS - 1) % MAX_MS_COLLECTED] - msCollected[curMS % MAX_MS_COLLECTED]); +} + +void tbDisplay() +{ + char temp[200]; + wchar wtemp[200]; + +#ifdef FRAMETIME + float FrameEndTime = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerFrame(); +#endif + + msCollected[(curMS++) % MAX_MS_COLLECTED] = RsTimer(); + CFont::SetBackgroundOff(); + CFont::SetBackgroundColor(CRGBA(0, 0, 0, 128)); + CFont::SetScale(0.48f, 1.12f); + CFont::SetCentreOff(); + CFont::SetJustifyOff(); + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetRightJustifyOff(); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_BANK); + sprintf(temp, "FPS: %.2f", Diag_GetFPS()); + AsciiToUnicode(temp, wtemp); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + if (!CMenuManager::m_PrefsMarketing || !CMenuManager::m_PrefsDisableTutorials) { + CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * (4.0f / DEFAULT_SCREEN_HEIGHT), wtemp); + +#ifndef FINAL + // Timers output (my own implementation) + for (uint32 i = 0; i < TimerBar.count; i++) { + MaxTimes[i] = Max(MaxTimes[i], TimerBar.Timers[i].endTime - TimerBar.Timers[i].startTime); + sprintf(temp, "%s: %.2f", &TimerBar.Timers[i].name[0], MaxTimes[i]); + AsciiToUnicode(temp, wtemp); + CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * ((8.0f * (i + 2)) / DEFAULT_SCREEN_HEIGHT), wtemp); + } + +#ifdef FRAMETIME + MaxFrameTime = Max(MaxFrameTime, FrameEndTime - FrameInitTime); + sprintf(temp, "Frame Time: %.2f", MaxFrameTime); + AsciiToUnicode(temp, wtemp); + + CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * ((8.0f * (TimerBar.count + 4)) / DEFAULT_SCREEN_HEIGHT), wtemp); +#endif // FRAMETIME +#endif // !FINAL + } +} +#endif // !MASTER \ No newline at end of file diff --git a/src/core/timebars.h b/src/core/timebars.h new file mode 100644 index 0000000..c493980 --- /dev/null +++ b/src/core/timebars.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef TIMEBARS +void tbInit(); +void tbStartTimer(int32, Const char*); +void tbEndTimer(Const char*); +void tbDisplay(); +#else +#define tbInit() +#define tbStartTimer(a, b) +#define tbEndTimer(a) +#define tbDisplay() +#endif diff --git a/src/entities/Dummy.cpp b/src/entities/Dummy.cpp new file mode 100644 index 0000000..d5fad3e --- /dev/null +++ b/src/entities/Dummy.cpp @@ -0,0 +1,52 @@ +#include "common.h" + +#include "Pools.h" +#include "World.h" +#include "Dummy.h" + +void *CDummy::operator new(size_t sz) throw() { return CPools::GetDummyPool()->New(); } +void CDummy::operator delete(void *p, size_t sz) throw() { CPools::GetDummyPool()->Delete((CDummy*)p); } + +void +CDummy::Add(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.top); + yend = CWorld::GetSectorIndexY(bounds.bottom); + ymid = CWorld::GetSectorIndexY((bounds.top + bounds.bottom)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) + list = &s->m_lists[ENTITYLIST_DUMMIES]; + else + list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP]; + CPtrNode *node = list->InsertItem(this); + assert(node); + m_entryInfoList.InsertItem(list, node, s); + } +} + +void +CDummy::Remove(void) +{ + CEntryInfoNode *node, *next; + for(node = m_entryInfoList.first; node; node = next){ + next = node->next; + node->list->DeleteNode(node->listnode); + m_entryInfoList.DeleteNode(node); + } +} diff --git a/src/entities/Dummy.h b/src/entities/Dummy.h new file mode 100644 index 0000000..6c3f12e --- /dev/null +++ b/src/entities/Dummy.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Lists.h" +#include "Entity.h" + +class CDummy : public CEntity +{ +public: + CEntryInfoList m_entryInfoList; + + CDummy(void) { m_type = ENTITY_TYPE_DUMMY; } + void Add(void); + void Remove(void); + + static void *operator new(size_t) throw(); + static void operator delete(void*, size_t) throw(); +}; + +VALIDATE_SIZE(CDummy, 0x68); + diff --git a/src/entities/Entity.cpp b/src/entities/Entity.cpp new file mode 100644 index 0000000..c38f12c --- /dev/null +++ b/src/entities/Entity.cpp @@ -0,0 +1,804 @@ +#include "common.h" + +#include "General.h" +#include "RwHelper.h" +#include "ModelIndices.h" +#include "Timer.h" +#include "Entity.h" +#include "Object.h" +#include "World.h" +#include "Camera.h" +#include "Glass.h" +#include "Weather.h" +#include "Timecycle.h" +#include "TrafficLights.h" +#include "Coronas.h" +#include "PointLights.h" +#include "Shadows.h" +#include "Pickups.h" +#include "SpecialFX.h" +#include "TxdStore.h" +#include "Zones.h" +#include "MemoryHeap.h" +#include "Bones.h" +#include "Debug.h" +#include "SaveBuf.h" + +int gBuildings; + +CEntity::CEntity(void) +{ + m_type = ENTITY_TYPE_NOTHING; + m_status = STATUS_ABANDONED; + + bUsesCollision = false; + bCollisionProcessed = false; + bIsStatic = false; + bHasContacted = false; + bPedPhysics = false; + bIsStuck = false; + bIsInSafePosition = false; + bUseCollisionRecords = false; + + bWasPostponed = false; + bExplosionProof = false; + bIsVisible = true; + bHasCollided = false; + bRenderScorched = false; + bHasBlip = false; + bIsBIGBuilding = false; + bRenderDamaged = false; + + bBulletProof = false; + bFireProof = false; + bCollisionProof = false; + bMeleeProof = false; + bOnlyDamagedByPlayer = false; + bStreamingDontDelete = false; + bZoneCulled = false; + bZoneCulled2 = false; + + bRemoveFromWorld = false; + bHasHitWall = false; + bImBeingRendered = false; + bTouchingWater = false; + bIsSubway = false; + bDrawLast = false; + bNoBrightHeadLights = false; + bDoNotRender = false; + + bDistanceFade = false; + m_flagE2 = false; + + m_scanCode = 0; + m_modelIndex = -1; + m_rwObject = nil; + m_randomSeed = CGeneral::GetRandomNumber(); + m_pFirstReference = nil; +} + +CEntity::~CEntity(void) +{ + DeleteRwObject(); + ResolveReferences(); +} + +void +CEntity::SetModelIndex(uint32 id) +{ + m_modelIndex = id; + CreateRwObject(); +} + +void +CEntity::SetModelIndexNoCreate(uint32 id) +{ + m_modelIndex = id; +} + +void +CEntity::CreateRwObject(void) +{ + CBaseModelInfo *mi; + + mi = CModelInfo::GetModelInfo(m_modelIndex); + + PUSH_MEMID(MEMID_WORLD); + m_rwObject = mi->CreateInstance(); + POP_MEMID(); + + if(m_rwObject){ + if(IsBuilding()) + gBuildings++; + if(RwObjectGetType(m_rwObject) == rpATOMIC) + GetMatrix().AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic *)m_rwObject)), false); + else if(RwObjectGetType(m_rwObject) == rpCLUMP) + GetMatrix().AttachRW(RwFrameGetMatrix(RpClumpGetFrame((RpClump *)m_rwObject)), false); + mi->AddRef(); + } +} + +void +CEntity::AttachToRwObject(RwObject *obj) +{ + m_rwObject = obj; + if(m_rwObject){ + if(RwObjectGetType(m_rwObject) == rpATOMIC) + GetMatrix().Attach(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic *)m_rwObject)), false); + else if(RwObjectGetType(m_rwObject) == rpCLUMP) + GetMatrix().Attach(RwFrameGetMatrix(RpClumpGetFrame((RpClump *)m_rwObject)), false); + CModelInfo::GetModelInfo(m_modelIndex)->AddRef(); + } +} + +void +CEntity::DetachFromRwObject(void) +{ + if(m_rwObject) + CModelInfo::GetModelInfo(m_modelIndex)->RemoveRef(); + m_rwObject = nil; + GetMatrix().Detach(); +} + +#ifdef PED_SKIN +RpAtomic* +AtomicRemoveAnimFromSkinCB(RpAtomic *atomic, void *data) +{ + if(RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic))){ + RpHAnimHierarchy *hier = RpSkinAtomicGetHAnimHierarchy(atomic); +#ifdef LIBRW + if(hier && hier->interpolator->currentAnim){ + RpHAnimAnimationDestroy(hier->interpolator->currentAnim); + hier->interpolator->currentAnim = nil; + } +#else + if(hier && hier->pCurrentAnim){ + RpHAnimAnimationDestroy(hier->pCurrentAnim); + hier->pCurrentAnim = nil; + } +#endif + } + return atomic; +} +#endif + +void +CEntity::DeleteRwObject(void) +{ + RwFrame *f; + + GetMatrix().Detach(); + if(m_rwObject){ + if(RwObjectGetType(m_rwObject) == rpATOMIC){ + f = RpAtomicGetFrame((RpAtomic*)m_rwObject); + RpAtomicDestroy((RpAtomic*)m_rwObject); + RwFrameDestroy(f); + }else if(RwObjectGetType(m_rwObject) == rpCLUMP){ +#ifdef PED_SKIN + if(IsClumpSkinned((RpClump*)m_rwObject)) + RpClumpForAllAtomics((RpClump*)m_rwObject, AtomicRemoveAnimFromSkinCB, nil); +#endif + RpClumpDestroy((RpClump*)m_rwObject); + } + m_rwObject = nil; + CModelInfo::GetModelInfo(m_modelIndex)->RemoveRef(); + if(IsBuilding()) + gBuildings--; + } +} + +CRect +CEntity::GetBoundRect(void) +{ + CRect rect; + CVector v; + CColModel *col = CModelInfo::GetColModel(m_modelIndex); + + rect.ContainPoint(GetMatrix() * col->boundingBox.min); + rect.ContainPoint(GetMatrix() * col->boundingBox.max); + + v = col->boundingBox.min; + v.x = col->boundingBox.max.x; + rect.ContainPoint(GetMatrix() * v); + + v = col->boundingBox.max; + v.x = col->boundingBox.min.x; + rect.ContainPoint(GetMatrix() * v); + + return rect; +} + +CVector +CEntity::GetBoundCentre(void) +{ + return GetMatrix() * CModelInfo::GetColModel(m_modelIndex)->boundingSphere.center; +} + +#ifdef GTA_PS2 +void +CEntity::GetBoundCentre(CVuVector &out) +{ + TransformPoint(out, GetMatrix(), CModelInfo::GetColModel(m_modelIndex)->boundingSphere.center); +} +#else +void +CEntity::GetBoundCentre(CVector &out) +{ + out = GetMatrix() * CModelInfo::GetColModel(m_modelIndex)->boundingSphere.center; +} +#endif + +float +CEntity::GetBoundRadius(void) +{ + return CModelInfo::GetColModel(m_modelIndex)->boundingSphere.radius; +} + +void +CEntity::UpdateRwFrame(void) +{ + if(m_rwObject) + RwFrameUpdateObjects((RwFrame*)rwObjectGetParent(m_rwObject)); +} + +#ifdef PED_SKIN +void +CEntity::UpdateRpHAnim(void) +{ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + RpHAnimHierarchyUpdateMatrices(hier); + +#if 0 + int i; + char buf[256]; + if(this == (CEntity*)FindPlayerPed()) + for(i = 0; i < hier->numNodes; i++){ + RpHAnimStdInterpFrame *kf = (RpHAnimStdInterpFrame*)rpHANIMHIERARCHYGETINTERPFRAME(hier, i); + sprintf(buf, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f %d %s", + kf->q.imag.x, kf->q.imag.y, kf->q.imag.z, kf->q.real, + kf->t.x, kf->t.y, kf->t.z, + HIERNODEID(hier, i), + ConvertBoneTag2BoneName(HIERNODEID(hier, i))); + CDebug::PrintAt(buf, 10, 1+i*3); + + RwMatrix *m = &RpHAnimHierarchyGetMatrixArray(hier)[i]; + sprintf(buf, "%6.3f %6.3f %6.3f %6.3f", + m->right.x, m->up.x, m->at.x, m->pos.x); + CDebug::PrintAt(buf, 80, 1+i*3+0); + sprintf(buf, "%6.3f %6.3f %6.3f %6.3f", + m->right.y, m->up.y, m->at.y, m->pos.y); + CDebug::PrintAt(buf, 80, 1+i*3+1); + sprintf(buf, "%6.3f %6.3f %6.3f %6.3f", + m->right.z, m->up.z, m->at.z, m->pos.z); + CDebug::PrintAt(buf, 80, 1+i*3+2); + } + + void RenderSkeleton(RpHAnimHierarchy *hier); + RenderSkeleton(hier); +#endif +} +#endif + +void +CEntity::PreRender(void) +{ + switch(m_type){ + case ENTITY_TYPE_BUILDING: + if(GetModelIndex() == MI_RAILTRACKS){ + CShadows::StoreShadowForPole(this, 0.0f, -10.949f, 5.0f, 8.0f, 1.0f, 0); + CShadows::StoreShadowForPole(this, 0.0f, 10.949f, 5.0f, 8.0f, 1.0f, 1); + }else if(IsTreeModel(GetModelIndex())){ + CShadows::StoreShadowForTree(this); + ModifyMatrixForTreeInWind(); + }else if(IsBannerModel(GetModelIndex())){ + ModifyMatrixForBannerInWind(); + } + break; + case ENTITY_TYPE_OBJECT: + if(GetModelIndex() == MI_COLLECTABLE1){ + CPickups::DoCollectableEffects(this); + GetMatrix().UpdateRW(); + UpdateRwFrame(); + }else if(GetModelIndex() == MI_MONEY){ + CPickups::DoMoneyEffects(this); + GetMatrix().UpdateRW(); + UpdateRwFrame(); + }else if(GetModelIndex() == MI_NAUTICALMINE || + GetModelIndex() == MI_CARMINE || + GetModelIndex() == MI_BRIEFCASE){ + if(((CObject*)this)->bIsPickup){ + CPickups::DoMineEffects(this); + GetMatrix().UpdateRW(); + UpdateRwFrame(); + } + }else if(IsPickupModel(GetModelIndex())){ + if(((CObject*)this)->bIsPickup){ + CPickups::DoPickUpEffects(this); + GetMatrix().UpdateRW(); + UpdateRwFrame(); + }else if(GetModelIndex() == MI_GRENADE){ + CMotionBlurStreaks::RegisterStreak((uintptr)this, + 100, 100, 100, + GetPosition() - 0.07f*TheCamera.GetRight(), + GetPosition() + 0.07f*TheCamera.GetRight()); + }else if(GetModelIndex() == MI_MOLOTOV){ + CMotionBlurStreaks::RegisterStreak((uintptr)this, + 0, 100, 0, + GetPosition() - 0.07f*TheCamera.GetRight(), + GetPosition() + 0.07f*TheCamera.GetRight()); + } + }else if(GetModelIndex() == MI_MISSILE){ + CVector pos = GetPosition(); + float flicker = (CGeneral::GetRandomNumber() & 0xF)/(float)0x10; + CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, + gpShadowExplosionTex, &pos, + 8.0f, 0.0f, 0.0f, -8.0f, + 255, 200.0f*flicker, 160.0f*flicker, 120.0f*flicker, + 20.0f, false, 1.0f); + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos, CVector(0.0f, 0.0f, 0.0f), + 8.0f, + 1.0f*flicker, + 0.8f*flicker, + 0.6f*flicker, + CPointLights::FOG_NONE, true); + CCoronas::RegisterCorona((uintptr)this, + 255.0f*flicker, 220.0f*flicker, 190.0f*flicker, 255, + pos, 6.0f*flicker, 80.0f, gpCoronaTexture[CCoronas::TYPE_STAR], + CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + }else if(IsGlass(GetModelIndex())){ + PreRenderForGlassWindow(); + } + // fall through + case ENTITY_TYPE_DUMMY: + if(GetModelIndex() == MI_TRAFFICLIGHTS){ + CTrafficLights::DisplayActualLight(this); + CShadows::StoreShadowForPole(this, 2.957f, 0.147f, 0.0f, 16.0f, 0.4f, 0); + }else if(GetModelIndex() == MI_SINGLESTREETLIGHTS1) + CShadows::StoreShadowForPole(this, 0.744f, 0.0f, 0.0f, 16.0f, 0.4f, 0); + else if(GetModelIndex() == MI_SINGLESTREETLIGHTS2) + CShadows::StoreShadowForPole(this, 0.043f, 0.0f, 0.0f, 16.0f, 0.4f, 0); + else if(GetModelIndex() == MI_SINGLESTREETLIGHTS3) + CShadows::StoreShadowForPole(this, 1.143f, 0.145f, 0.0f, 16.0f, 0.4f, 0); + else if(GetModelIndex() == MI_DOUBLESTREETLIGHTS) + CShadows::StoreShadowForPole(this, 0.0f, -0.048f, 0.0f, 16.0f, 0.4f, 0); + else if(GetModelIndex() == MI_STREETLAMP1 || + GetModelIndex() == MI_STREETLAMP2) + CShadows::StoreShadowForPole(this, 0.0f, 0.0f, 0.0f, 16.0f, 0.4f, 0); + break; + } + + if (CModelInfo::GetModelInfo(GetModelIndex())->GetNum2dEffects() != 0) + ProcessLightsForEntity(); +} + +void +CEntity::Render(void) +{ + if(m_rwObject){ + bImBeingRendered = true; + if(RwObjectGetType(m_rwObject) == rpATOMIC) + RpAtomicRender((RpAtomic*)m_rwObject); + else + RpClumpRender((RpClump*)m_rwObject); + bImBeingRendered = false; + } +} + + +bool +CEntity::GetIsTouching(CVUVECTOR const ¢er, float radius) +{ + CVUVECTOR boundCenter; + GetBoundCentre(boundCenter); + return sq(GetBoundRadius()+radius) > (boundCenter-center).MagnitudeSqr(); +} + +bool +CEntity::IsVisible(void) +{ + return m_rwObject && bIsVisible && GetIsOnScreen(); +} + +bool +CEntity::IsVisibleComplex(void) +{ + return m_rwObject && bIsVisible && GetIsOnScreenComplex(); +} + +bool +CEntity::GetIsOnScreen(void) +{ + return TheCamera.IsSphereVisible(GetBoundCentre(), GetBoundRadius()); +} + +bool +CEntity::GetIsOnScreenComplex(void) +{ +#ifdef GTA_PS2 + CVuVector boundBox[8]; +#else + CVector boundBox[8]; +#endif + + if(TheCamera.IsPointVisible(GetBoundCentre(), &TheCamera.GetCameraMatrix())) + return true; + + CRect rect = GetBoundRect(); + CColModel *colmodel = CModelInfo::GetColModel(m_modelIndex); + float z = GetPosition().z; + float minz = z + colmodel->boundingBox.min.z; + float maxz = z + colmodel->boundingBox.max.z; + boundBox[0].x = rect.left; + boundBox[0].y = rect.bottom; + boundBox[0].z = minz; + boundBox[1].x = rect.left; + boundBox[1].y = rect.top; + boundBox[1].z = minz; + boundBox[2].x = rect.right; + boundBox[2].y = rect.bottom; + boundBox[2].z = minz; + boundBox[3].x = rect.right; + boundBox[3].y = rect.top; + boundBox[3].z = minz; + boundBox[4].x = rect.left; + boundBox[4].y = rect.bottom; + boundBox[4].z = maxz; + boundBox[5].x = rect.left; + boundBox[5].y = rect.top; + boundBox[5].z = maxz; + boundBox[6].x = rect.right; + boundBox[6].y = rect.bottom; + boundBox[6].z = maxz; + boundBox[7].x = rect.right; + boundBox[7].y = rect.top; + boundBox[7].z = maxz; + + return TheCamera.IsBoxVisible(boundBox, &TheCamera.GetCameraMatrix()); +} + +void +CEntity::Add(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.top); + yend = CWorld::GetSectorIndexY(bounds.bottom); + ymid = CWorld::GetSectorIndexY((bounds.top + bounds.bottom)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES]; + break; + }else switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP]; + break; + } + list->InsertItem(this); + } +} + +void +CEntity::Remove(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.top); + yend = CWorld::GetSectorIndexY(bounds.bottom); + ymid = CWorld::GetSectorIndexY((bounds.top + bounds.bottom)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES]; + break; + }else switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP]; + break; + } + list->RemoveItem(this); + } +} + +float +CEntity::GetDistanceFromCentreOfMassToBaseOfModel(void) +{ + return -CModelInfo::GetColModel(m_modelIndex)->boundingBox.min.z; +} + +void +CEntity::SetupBigBuilding(void) +{ + CSimpleModelInfo *mi; + + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(m_modelIndex); + bIsBIGBuilding = true; + bStreamingDontDelete = true; + bUsesCollision = false; + m_level = CTheZones::GetLevelFromPosition(&GetPosition()); + if(m_level == LEVEL_GENERIC){ + if(mi->GetTxdSlot() != CTxdStore::FindTxdSlot("generic")){ + mi->SetTexDictionary("generic"); + printf("%d:%s txd has been set to generic\n", m_modelIndex, mi->GetModelName()); + } + } + if(mi->m_lodDistances[0] > 2000.0f) + m_level = LEVEL_GENERIC; +} + +float WindTabel[] = { + 1.0f, 0.5f, 0.2f, 0.7f, 0.4f, 1.0f, 0.5f, 0.3f, + 0.2f, 0.1f, 0.7f, 0.6f, 0.3f, 1.0f, 0.5f, 0.2f, +}; + +void +CEntity::ModifyMatrixForTreeInWind(void) +{ + uint16 t; + float f; + float strength, flutter; + + if(CTimer::GetIsPaused()) + return; + + CMatrix mat(GetMatrix().m_attachment); + + if(CWeather::Wind >= 0.5){ + t = m_randomSeed + 16*CTimer::GetTimeInMilliseconds(); + f = (t & 0xFFF)/(float)0x1000; + flutter = f * WindTabel[(t>>12)+1 & 0xF] + + (1.0f - f) * WindTabel[(t>>12) & 0xF] + + 1.0f; + strength = CWeather::Wind < 0.8f ? 0.008f : 0.014f; + }else if(CWeather::Wind >= 0.2){ + t = (uintptr)this + CTimer::GetTimeInMilliseconds(); + f = (t & 0xFFF)/(float)0x1000; + flutter = Sin(f * 6.28f); + strength = 0.008f; + }else{ + t = (uintptr)this + CTimer::GetTimeInMilliseconds(); + f = (t & 0xFFF)/(float)0x1000; + flutter = Sin(f * 6.28f); + strength = 0.005f; + } + + mat.GetUp().x = strength * flutter; + mat.GetUp().y = mat.GetUp().x; + + mat.UpdateRW(); + UpdateRwFrame(); +} + +float BannerWindTabel[] = { + 0.0f, 0.3f, 0.6f, 0.85f, 0.99f, 0.97f, 0.65f, 0.15f, + -0.1f, 0.0f, 0.35f, 0.57f, 0.55f, 0.35f, 0.45f, 0.67f, + 0.73f, 0.45f, 0.25f, 0.35f, 0.35f, 0.11f, 0.13f, 0.21f, + 0.28f, 0.28f, 0.22f, 0.1f, 0.0f, -0.1f, -0.17f, -0.12f +}; + +void +CEntity::ModifyMatrixForBannerInWind(void) +{ + uint16 t; + float f; + float strength, flutter; + CVector right, up; + + if(CTimer::GetIsPaused()) + return; + + if(CWeather::Wind < 0.1f) + strength = 0.2f; + else if(CWeather::Wind < 0.8f) + strength = 0.43f; + else + strength = 0.66f; + + t = ((int)(GetMatrix().GetPosition().x + GetMatrix().GetPosition().y) << 10) + 16*CTimer::GetTimeInMilliseconds(); + f = (t & 0x7FF)/(float)0x800; + flutter = f * BannerWindTabel[(t>>11)+1 & 0x1F] + + (1.0f - f) * BannerWindTabel[(t>>11) & 0x1F]; + flutter *= strength; + + right = CrossProduct(GetForward(), GetUp()); + right.z = 0.0f; + right.Normalise(); + up = right * flutter; + up.z = Sqrt(sq(1.0f) - sq(flutter)); + GetRight() = CrossProduct(GetForward(), up); + GetUp() = up; + + GetMatrix().UpdateRW(); + UpdateRwFrame(); +} + +void +CEntity::PreRenderForGlassWindow(void) +{ + CGlass::AskForObjectToBeRenderedInGlass(this); + bIsVisible = false; +} + +#ifdef COMPATIBLE_SAVES +void +CEntity::SaveEntityFlags(uint8*& buf) +{ + uint32 tmp = 0; + tmp |= (m_type & (BIT(3) - 1)); + tmp |= (m_status & (BIT(5) - 1)) << 3; + + if (bUsesCollision) tmp |= BIT(8); + if (bCollisionProcessed) tmp |= BIT(9); + if (bIsStatic) tmp |= BIT(10); + if (bHasContacted) tmp |= BIT(11); + if (bPedPhysics) tmp |= BIT(12); + if (bIsStuck) tmp |= BIT(13); + if (bIsInSafePosition) tmp |= BIT(14); + if (bUseCollisionRecords) tmp |= BIT(15); + + if (bWasPostponed) tmp |= BIT(16); + if (bExplosionProof) tmp |= BIT(17); + if (bIsVisible) tmp |= BIT(18); + if (bHasCollided) tmp |= BIT(19); + if (bRenderScorched) tmp |= BIT(20); + if (bHasBlip) tmp |= BIT(21); + if (bIsBIGBuilding) tmp |= BIT(22); + if (bRenderDamaged) tmp |= BIT(23); + + if (bBulletProof) tmp |= BIT(24); + if (bFireProof) tmp |= BIT(25); + if (bCollisionProof) tmp |= BIT(26); + if (bMeleeProof) tmp |= BIT(27); + if (bOnlyDamagedByPlayer) tmp |= BIT(28); + if (bStreamingDontDelete) tmp |= BIT(29); + if (bZoneCulled) tmp |= BIT(30); + if (bZoneCulled2) tmp |= BIT(31); + + WriteSaveBuf(buf, tmp); + + tmp = 0; + + if (bRemoveFromWorld) tmp |= BIT(0); + if (bHasHitWall) tmp |= BIT(1); + if (bImBeingRendered) tmp |= BIT(2); + if (bTouchingWater) tmp |= BIT(3); + if (bIsSubway) tmp |= BIT(4); + if (bDrawLast) tmp |= BIT(5); + if (bNoBrightHeadLights) tmp |= BIT(6); + if (bDoNotRender) tmp |= BIT(7); + + if (bDistanceFade) tmp |= BIT(8); + if (m_flagE2) tmp |= BIT(9); + + WriteSaveBuf(buf, tmp); +} + +void +CEntity::LoadEntityFlags(uint8*& buf) +{ + uint32 tmp; + ReadSaveBuf(&tmp, buf); + m_type = (tmp & ((BIT(3) - 1))); + m_status = ((tmp >> 3) & (BIT(5) - 1)); + + bUsesCollision = !!(tmp & BIT(8)); + bCollisionProcessed = !!(tmp & BIT(9)); + bIsStatic = !!(tmp & BIT(10)); + bHasContacted = !!(tmp & BIT(11)); + bPedPhysics = !!(tmp & BIT(12)); + bIsStuck = !!(tmp & BIT(13)); + bIsInSafePosition = !!(tmp & BIT(14)); + bUseCollisionRecords = !!(tmp & BIT(15)); + + bWasPostponed = !!(tmp & BIT(16)); + bExplosionProof = !!(tmp & BIT(17)); + bIsVisible = !!(tmp & BIT(18)); + bHasCollided = !!(tmp & BIT(19)); + bRenderScorched = !!(tmp & BIT(20)); + bHasBlip = !!(tmp & BIT(21)); + bIsBIGBuilding = !!(tmp & BIT(22)); + bRenderDamaged = !!(tmp & BIT(23)); + + bBulletProof = !!(tmp & BIT(24)); + bFireProof = !!(tmp & BIT(25)); + bCollisionProof = !!(tmp & BIT(26)); + bMeleeProof = !!(tmp & BIT(27)); + bOnlyDamagedByPlayer = !!(tmp & BIT(28)); + bStreamingDontDelete = !!(tmp & BIT(29)); + bZoneCulled = !!(tmp & BIT(30)); + bZoneCulled2 = !!(tmp & BIT(31)); + + ReadSaveBuf(&tmp, buf); + + bRemoveFromWorld = !!(tmp & BIT(0)); + bHasHitWall = !!(tmp & BIT(1)); + bImBeingRendered = !!(tmp & BIT(2)); + bTouchingWater = !!(tmp & BIT(3)); + bIsSubway = !!(tmp & BIT(4)); + bDrawLast = !!(tmp & BIT(5)); + bNoBrightHeadLights = !!(tmp & BIT(6)); + bDoNotRender = !!(tmp & BIT(7)); + + bDistanceFade = !!(tmp & BIT(8)); + m_flagE2 = !!(tmp & BIT(9)); +} + +#endif diff --git a/src/entities/Entity.h b/src/entities/Entity.h new file mode 100644 index 0000000..6174b61 --- /dev/null +++ b/src/entities/Entity.h @@ -0,0 +1,175 @@ +#pragma once + +#include "ModelInfo.h" +#include "Placeable.h" + +struct CReference; +class CPtrList; + +enum eEntityType +{ + ENTITY_TYPE_NOTHING = 0, + ENTITY_TYPE_BUILDING, + ENTITY_TYPE_VEHICLE, + ENTITY_TYPE_PED, + ENTITY_TYPE_OBJECT, + ENTITY_TYPE_DUMMY, +}; + +enum eEntityStatus +{ + STATUS_PLAYER, + STATUS_PLAYER_PLAYBACKFROMBUFFER, + STATUS_SIMPLE, + STATUS_PHYSICS, + STATUS_ABANDONED, + STATUS_WRECKED, + STATUS_TRAIN_MOVING, + STATUS_TRAIN_NOT_MOVING, + STATUS_HELI, + STATUS_PLANE, + STATUS_PLAYER_REMOTE, + STATUS_PLAYER_DISABLED, +}; + +class CEntity : public CPlaceable +{ +public: + RwObject *m_rwObject; +protected: + uint32 m_type : 3; +private: + uint32 m_status : 5; +public: + // flagsA + uint32 bUsesCollision : 1; // does entity use collision + uint32 bCollisionProcessed : 1; // has object been processed by a ProcessEntityCollision function + uint32 bIsStatic : 1; // is entity static + uint32 bHasContacted : 1; // has entity processed some contact forces + uint32 bPedPhysics : 1; + uint32 bIsStuck : 1; // is entity stuck + uint32 bIsInSafePosition : 1; // is entity in a collision free safe position + uint32 bUseCollisionRecords : 1; + + // flagsB + uint32 bWasPostponed : 1; // was entity control processing postponed + uint32 bExplosionProof : 1; + uint32 bIsVisible : 1; //is the entity visible + uint32 bHasCollided : 1; + uint32 bRenderScorched : 1; + uint32 bHasBlip : 1; + uint32 bIsBIGBuilding : 1; // Set if this entity is a big building + uint32 bRenderDamaged : 1; // use damaged LOD models for objects with applicable damage + + // flagsC + uint32 bBulletProof : 1; + uint32 bFireProof : 1; + uint32 bCollisionProof : 1; + uint32 bMeleeProof : 1; + uint32 bOnlyDamagedByPlayer : 1; + uint32 bStreamingDontDelete : 1; // Dont let the streaming remove this + uint32 bZoneCulled : 1; + uint32 bZoneCulled2 : 1; // only treadables+10m + + // flagsD + uint32 bRemoveFromWorld : 1; // remove this entity next time it should be processed + uint32 bHasHitWall : 1; // has collided with a building (changes subsequent collisions) + uint32 bImBeingRendered : 1; // don't delete me because I'm being rendered + uint32 bTouchingWater : 1; // used by cBuoyancy::ProcessBuoyancy + uint32 bIsSubway : 1; // set when subway, but maybe different meaning? + uint32 bDrawLast : 1; // draw object last + uint32 bNoBrightHeadLights : 1; + uint32 bDoNotRender : 1; + + // flagsE + uint32 bDistanceFade : 1; // Fade entity because it is far away + uint32 m_flagE2 : 1; + + uint16 m_scanCode; + uint16 m_randomSeed; + int16 m_modelIndex; + uint16 m_level; // int16 + CReference *m_pFirstReference; + +public: + uint8 GetType() const { return m_type; } + void SetType(uint8 type) { m_type = type; } + uint8 GetStatus() const { return m_status; } + void SetStatus(uint8 status) { m_status = status; } + CColModel *GetColModel(void) { return CModelInfo::GetModelInfo(m_modelIndex)->GetColModel(); } + bool GetIsStatic(void) const { return bIsStatic; } + void SetIsStatic(bool state) { bIsStatic = state; } +#ifdef COMPATIBLE_SAVES + void SaveEntityFlags(uint8*& buf); + void LoadEntityFlags(uint8*& buf); +#else + uint32* GetAddressOfEntityProperties() { /* AWFUL */ return (uint32*)((char*)&m_rwObject + sizeof(m_rwObject)); } +#endif + + CEntity(void); + ~CEntity(void); + + virtual void Add(void); + virtual void Remove(void); + virtual void SetModelIndex(uint32 id); + virtual void SetModelIndexNoCreate(uint32 id); + virtual void CreateRwObject(void); + virtual void DeleteRwObject(void); + virtual CRect GetBoundRect(void); + virtual void ProcessControl(void) {} + virtual void ProcessCollision(void) {} + virtual void ProcessShift(void) {} + virtual void Teleport(CVector v) {} + virtual void PreRender(void); + virtual void Render(void); + virtual bool SetupLighting(void); + virtual void RemoveLighting(bool); + virtual void FlagToDestroyWhenNextProcessed(void) {} + + bool IsBuilding(void) { return m_type == ENTITY_TYPE_BUILDING; } + bool IsVehicle(void) { return m_type == ENTITY_TYPE_VEHICLE; } + bool IsPed(void) { return m_type == ENTITY_TYPE_PED; } + bool IsObject(void) { return m_type == ENTITY_TYPE_OBJECT; } + bool IsDummy(void) { return m_type == ENTITY_TYPE_DUMMY; } + + RpAtomic *GetAtomic(void) { + assert(RwObjectGetType(m_rwObject) == rpATOMIC); + return (RpAtomic*)m_rwObject; + } + RpClump *GetClump(void) { + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + return (RpClump*)m_rwObject; + } + + void GetBoundCentre(CVUVECTOR &out); + CVector GetBoundCentre(void); + float GetBoundRadius(void); + float GetDistanceFromCentreOfMassToBaseOfModel(void); + bool GetIsTouching(CVUVECTOR const ¢er, float r); + bool GetIsOnScreen(void); + bool GetIsOnScreenComplex(void); + bool IsVisible(void); + bool IsVisibleComplex(void); + int16 GetModelIndex(void) const { return m_modelIndex; } + void UpdateRwFrame(void); + void SetupBigBuilding(void); + + void AttachToRwObject(RwObject *obj); + void DetachFromRwObject(void); + + void RegisterReference(CEntity **pent); + void ResolveReferences(void); + void PruneReferences(void); + +#ifdef PED_SKIN + void UpdateRpHAnim(void); +#endif + + void PreRenderForGlassWindow(void); + void AddSteamsFromGround(CVector *unused); + void ModifyMatrixForTreeInWind(void); + void ModifyMatrixForBannerInWind(void); + void ProcessLightsForEntity(void); +}; + +VALIDATE_SIZE(CEntity, 0x64); diff --git a/src/entities/Physical.cpp b/src/entities/Physical.cpp new file mode 100644 index 0000000..32a3df3 --- /dev/null +++ b/src/entities/Physical.cpp @@ -0,0 +1,2002 @@ +#include "common.h" + +#include "World.h" +#include "Timer.h" +#include "ModelIndices.h" +#include "Treadable.h" +#include "Vehicle.h" +#include "Ped.h" +#include "Object.h" +#include "Glass.h" +#include "ParticleObject.h" +#include "Particle.h" +#include "SurfaceTable.h" +#include "PathFind.h" +#include "CarCtrl.h" +#include "DMAudio.h" +#include "Automobile.h" +#include "Physical.h" +#include "Bike.h" + +CPhysical::CPhysical(void) +{ + int i; + +#ifdef FIX_BUGS + m_nLastTimeCollided = 0; +#endif + + m_fForceMultiplier = 1.0f; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveSpeedAvg = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeedAvg = CVector(0.0f, 0.0f, 0.0f); + + m_movingListNode = nil; + m_nStaticFrames = 0; + + m_nCollisionRecords = 0; + for(i = 0; i < 6; i++) + m_aCollisionRecords[i] = nil; + + m_bIsVehicleBeingShifted = false; + + m_nDamagePieceType = 0; + m_fDamageImpulse = 0.0f; + m_pDamageEntity = nil; + m_vecDamageNormal = CVector(0.0f, 0.0f, 0.0f); + + bUsesCollision = true; + m_audioEntityId = -5; + m_phys_unused1 = 100.0f; + m_vecCentreOfMass = CVector(0.0f, 0.0f, 0.0f); + m_phys_unused2 = 0; + + bIsHeavy = false; + bAffectedByGravity = true; + bInfiniteMass = false; + bIsInWater = false; + bHitByTrain = false; + bSkipLineCol = false; + + m_fDistanceTravelled = 0.0f; + m_treadable[PATH_CAR] = nil; + m_treadable[PATH_PED] = nil; + + m_phy_flagA10 = false; + m_phy_flagA20 = false; + +#ifdef FIX_BUGS + m_nSurfaceTouched = SURFACE_DEFAULT; +#endif + m_nZoneLevel = LEVEL_GENERIC; +} + +CPhysical::~CPhysical(void) +{ + m_entryInfoList.Flush(); +} + +void +CPhysical::Add(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.top); + yend = CWorld::GetSectorIndexY(bounds.bottom); + ymid = CWorld::GetSectorIndexY((bounds.top + bounds.bottom)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + default: + assert(0); + }else switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + default: + assert(0); + } + CPtrNode *node = list->InsertItem(this); + assert(node); + m_entryInfoList.InsertItem(list, node, s); + } +} + +void +CPhysical::Remove(void) +{ + CEntryInfoNode *node, *next; + for(node = m_entryInfoList.first; node; node = next){ + next = node->next; + node->list->DeleteNode(node->listnode); + m_entryInfoList.DeleteNode(node); + } +} + +void +CPhysical::RemoveAndAdd(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.top); + yend = CWorld::GetSectorIndexY(bounds.bottom); + ymid = CWorld::GetSectorIndexY((bounds.top + bounds.bottom)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + // we'll try to recycle nodes from here + CEntryInfoNode *next = m_entryInfoList.first; + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + }else switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + } + if(next){ + // If we still have old nodes, use them + next->list->RemoveNode(next->listnode); + list->InsertNode(next->listnode); + next->list = list; + next->sector = s; + next = next->next; + }else{ + CPtrNode *node = list->InsertItem(this); + m_entryInfoList.InsertItem(list, node, s); + } + } + + // Remove old nodes we no longer need + CEntryInfoNode *node; + for(node = next; node; node = next){ + next = node->next; + node->list->DeleteNode(node->listnode); + m_entryInfoList.DeleteNode(node); + } +} + +CRect +CPhysical::GetBoundRect(void) +{ + CVector center; + float radius; + center = GetBoundCentre(); + radius = GetBoundRadius(); + return CRect(center.x-radius, center.y-radius, center.x+radius, center.y+radius); +} + +void +CPhysical::AddToMovingList(void) +{ + m_movingListNode = CWorld::GetMovingEntityList().InsertItem(this); +} + +void +CPhysical::RemoveFromMovingList(void) +{ + if(m_movingListNode){ + CWorld::GetMovingEntityList().DeleteNode(m_movingListNode); + m_movingListNode = nil; + } +} + +void +CPhysical::SetDamagedPieceRecord(uint16 piece, float impulse, CEntity *entity, CVector dir) +{ + m_nDamagePieceType = piece; + m_fDamageImpulse = impulse; + m_pDamageEntity = entity; + entity->RegisterReference(&m_pDamageEntity); + m_vecDamageNormal = dir; +} + +void +CPhysical::AddCollisionRecord(CEntity *ent) +{ + AddCollisionRecord_Treadable(ent); + this->bHasCollided = true; + ent->bHasCollided = true; + if(IsVehicle() && ent->IsVehicle()){ + if(((CVehicle*)this)->m_nAlarmState == -1) + ((CVehicle*)this)->m_nAlarmState = 15000; + if(((CVehicle*)ent)->m_nAlarmState == -1) + ((CVehicle*)ent)->m_nAlarmState = 15000; + } + if(bUseCollisionRecords){ + int i; + for(i = 0; i < m_nCollisionRecords; i++) + if(m_aCollisionRecords[i] == ent) + return; + if(m_nCollisionRecords < PHYSICAL_MAX_COLLISIONRECORDS) + m_aCollisionRecords[m_nCollisionRecords++] = ent; + m_nLastTimeCollided = CTimer::GetTimeInMilliseconds(); + } +} + +void +CPhysical::AddCollisionRecord_Treadable(CEntity *ent) +{ + if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){ + CTreadable *t = (CTreadable*)ent; + if(t->m_nodeIndices[PATH_PED][0] >= 0 || + t->m_nodeIndices[PATH_PED][1] >= 0 || + t->m_nodeIndices[PATH_PED][2] >= 0 || + t->m_nodeIndices[PATH_PED][3] >= 0) + m_treadable[PATH_PED] = t; + if(t->m_nodeIndices[PATH_CAR][0] >= 0 || + t->m_nodeIndices[PATH_CAR][1] >= 0 || + t->m_nodeIndices[PATH_CAR][2] >= 0 || + t->m_nodeIndices[PATH_CAR][3] >= 0) + m_treadable[PATH_CAR] = t; + } +} + +bool +CPhysical::GetHasCollidedWith(CEntity *ent) +{ + int i; + if(bUseCollisionRecords) + for(i = 0; i < m_nCollisionRecords; i++) + if(m_aCollisionRecords[i] == ent) + return true; + return false; +} + +void +CPhysical::RemoveRefsToEntity(CEntity *ent) +{ + int i = 0, j; + + while(i < m_nCollisionRecords) { + if(m_aCollisionRecords[i] == ent){ + for(j = i; j < m_nCollisionRecords-1; j++) + m_aCollisionRecords[j] = m_aCollisionRecords[j+1]; + m_nCollisionRecords--; + } else + i++; + } +} + +void +CPhysical::PlacePhysicalRelativeToOtherPhysical(CPhysical *other, CPhysical *phys, CVector localPos) +{ + CVector worldPos = other->GetMatrix() * localPos; + float step = 0.9f * CTimer::GetTimeStep(); + CVector pos = other->m_vecMoveSpeed*step + worldPos; + + CWorld::Remove(phys); + phys->GetMatrix() = other->GetMatrix(); + phys->SetPosition(pos); + phys->m_vecMoveSpeed = other->m_vecMoveSpeed; + phys->GetMatrix().UpdateRW(); + phys->UpdateRwFrame(); + CWorld::Add(phys); +} + +int32 +CPhysical::ProcessEntityCollision(CEntity *ent, CColPoint *colpoints) +{ + int32 numSpheres = CCollision::ProcessColModels( + GetMatrix(), *GetColModel(), + ent->GetMatrix(), *ent->GetColModel(), + colpoints, + nil, nil); // No Lines allowed! + if(numSpheres > 0){ + AddCollisionRecord(ent); + if(!ent->IsBuilding()) // Can't this catch dummies too? + ((CPhysical*)ent)->AddCollisionRecord(this); + if(ent->IsBuilding() || ent->GetIsStatic()) + this->bHasHitWall = true; + } + return numSpheres; +} + +void +CPhysical::ProcessControl(void) +{ + if(!IsPed()) + bIsInWater = false; + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + + if(GetStatus() == STATUS_SIMPLE) + return; + + m_nCollisionRecords = 0; + bHasCollided = false; + m_nDamagePieceType = 0; + m_fDamageImpulse = 0.0f; + m_pDamageEntity = nil; + + if(!bIsStuck){ + if(IsObject() || + IsPed() && !bPedPhysics){ + m_vecMoveSpeedAvg = (m_vecMoveSpeedAvg + m_vecMoveSpeed)/2.0f; + m_vecTurnSpeedAvg = (m_vecTurnSpeedAvg + m_vecTurnSpeed)/2.0f; + float step = CTimer::GetTimeStep() * 0.003f; + if(m_vecMoveSpeedAvg.MagnitudeSqr() < step*step && + m_vecTurnSpeedAvg.MagnitudeSqr() < step*step){ + m_nStaticFrames++; + if(m_nStaticFrames > 10){ + m_nStaticFrames = 10; + SetIsStatic(true); + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = m_vecMoveSpeed; + m_vecTurnFriction = m_vecTurnSpeed; + return; + } + }else + m_nStaticFrames = 0; + } + } + ApplyGravity(); + ApplyFriction(); + ApplyAirResistance(); +} + +/* + * Some quantities (german in parens): + * + * acceleration: distance/time^2: a + * velocity: distance/time: v (GTA: speed) + * momentum (impuls): velocity*mass: p + * impulse (kraftstoss): delta momentum, force*time: J + * + * angular equivalents: + * velocity -> angular velocity (GTA: turn speed) + * momentum -> angular momentum (drehimpuls): L = r cross p + * force -> torque (drehmoment): tau = r cross F + * mass -> moment of inertia, angular mass (drehmoment, drehmasse): I = L/omega (GTA: turn mass) + */ + +CVector +CPhysical::GetSpeed(const CVector &r) +{ + return m_vecMoveSpeed + m_vecMoveFriction + CrossProduct(m_vecTurnFriction + m_vecTurnSpeed, r); +} + +void +CPhysical::ApplyMoveSpeed(void) +{ + GetMatrix().Translate(m_vecMoveSpeed * CTimer::GetTimeStep()); +} + +void +CPhysical::ApplyTurnSpeed(void) +{ + // Move the coordinate axes by their speed + // Note that this denormalizes the matrix + CVector turnvec = m_vecTurnSpeed*CTimer::GetTimeStep(); + GetRight() += CrossProduct(turnvec, GetRight()); + GetForward() += CrossProduct(turnvec, GetForward()); + GetUp() += CrossProduct(turnvec, GetUp()); +} + +void +CPhysical::ApplyMoveForce(float jx, float jy, float jz) +{ + m_vecMoveSpeed += CVector(jx, jy, jz)*(1.0f/m_fMass); +} + +void +CPhysical::ApplyTurnForce(float jx, float jy, float jz, float px, float py, float pz) +{ + CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz)); + m_vecTurnSpeed += turnimpulse*(1.0f/m_fTurnMass); +} + +void +CPhysical::ApplyFrictionMoveForce(float jx, float jy, float jz) +{ + m_vecMoveFriction += CVector(jx, jy, jz)*(1.0f/m_fMass); +} + +void +CPhysical::ApplyFrictionTurnForce(float jx, float jy, float jz, float px, float py, float pz) +{ + CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz)); + m_vecTurnFriction += turnimpulse*(1.0f/m_fTurnMass); +} + +bool +CPhysical::ApplySpringCollision(float springConst, CVector &springDir, CVector &point, float springRatio, float bias) +{ + float compression = 1.0f - springRatio; + if(compression > 0.0f){ + float step = Min(CTimer::GetTimeStep(), 3.0f); + float impulse = -GRAVITY*m_fMass*step * springConst * compression * bias*2.0f; + ApplyMoveForce(springDir*impulse); + ApplyTurnForce(springDir*impulse, point); + } + return true; +} + +// What exactly is speed? +bool +CPhysical::ApplySpringDampening(float damping, CVector &springDir, CVector &point, CVector &speed) +{ + float speedA = DotProduct(speed, springDir); + float speedB = DotProduct(GetSpeed(point), springDir); +#ifdef FIX_BUGS + if (speedB == 0.0f) + return true; +#endif + float step = Min(CTimer::GetTimeStep(), 3.0f); + float impulse = -damping * (speedA + speedB)/2.0f * m_fMass * step * 0.53f; + + // what is this? + float a = m_fTurnMass / ((point.MagnitudeSqr() + 1.0f) * 2.0f * m_fMass); + a = Min(a, 1.0f); + float b = Abs(impulse / (speedB * m_fMass)); + if(a < b) + impulse *= a/b; + + ApplyMoveForce(springDir*impulse); + ApplyTurnForce(springDir*impulse, point); + return true; +} + +void +CPhysical::ApplyGravity(void) +{ + if(bAffectedByGravity) + m_vecMoveSpeed.z -= GRAVITY * CTimer::GetTimeStep(); +} + +void +CPhysical::ApplyFriction(void) +{ + m_vecMoveSpeed += m_vecMoveFriction; + m_vecTurnSpeed += m_vecTurnFriction; + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); +} + +void +CPhysical::ApplyAirResistance(void) +{ + if(m_fAirResistance > 0.1f){ + float f = Pow(m_fAirResistance, CTimer::GetTimeStep()); + m_vecMoveSpeed *= f; + m_vecTurnSpeed *= f; + }else{ + float f = Pow(1.0f/Abs(m_fAirResistance*0.5f*m_vecMoveSpeed.MagnitudeSqr() + 1.0f), CTimer::GetTimeStep()); + m_vecMoveSpeed *= f; + m_vecTurnSpeed *= 0.99f; + } +} + +bool +CPhysical::ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB) +{ + float eA, eB; + CPhysical *A = this; + CObject *Bobj = (CObject*)B; + + bool ispedcontactA = false; + bool ispedcontactB = false; + + float massFactorA; + if(B->bPedPhysics){ + massFactorA = 10.0f; + if(B->IsPed() && ((CPed*)B)->m_pCurrentPhysSurface == A) + ispedcontactA = true; + }else + massFactorA = A->bIsHeavy ? 2.0f : 1.0f; + + float massFactorB; + if(A->bPedPhysics){ + if(A->IsPed() && ((CPed*)A)->IsPlayer() && B->IsVehicle() && + (B->GetStatus() == STATUS_ABANDONED || B->GetStatus() == STATUS_WRECKED || A->bHasHitWall)) + massFactorB = 2200.0f / B->m_fMass; + else + massFactorB = 10.0f; + + if(A->IsPed() && ((CPed*)A)->m_pCurrentPhysSurface == B) + ispedcontactB = true; + }else + massFactorB = B->bIsHeavy ? 2.0f : 1.0f; + + float speedA, speedB; + if(B->GetIsStatic()){ + if(A->bPedPhysics){ + speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + if(speedA < 0.0f){ + if(B->IsObject()){ + impulseA = -speedA * A->m_fMass; + impulseB = impulseA; + if(impulseA > Bobj->m_fUprootLimit){ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false); + else if(!B->bInfiniteMass) + B->SetIsStatic(false); + }else{ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToSoftCollision(B, impulseA); + if(!A->bInfiniteMass) + A->ApplyMoveForce(colpoint.GetNormal() * (1.0f + A->m_fElasticity) * impulseA); + return true; + } + }else if(!B->bInfiniteMass) + B->SetIsStatic(false); + + if(B->bInfiniteMass){ + impulseA = -speedA * A->m_fMass; + impulseB = 0.0f; + if(!A->bInfiniteMass) + A->ApplyMoveForce(colpoint.normal*(1.0f + A->m_fElasticity)*impulseA); + return true; + } + } + }else{ + CVector pointposA = colpoint.point - A->GetPosition(); + speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal); + if(speedA < 0.0f){ + if(B->IsObject()){ + if(A->bHasHitWall) + eA = -1.0f; + else + eA = -(1.0f + A->m_fElasticity); + impulseA = eA * speedA * A->GetMass(pointposA, colpoint.normal); + impulseB = impulseA; + + if(Bobj->m_nCollisionDamageEffect && impulseA > 20.0f){ + Bobj->ObjectDamage(impulseA); + if(!B->bUsesCollision){ + if(!A->bInfiniteMass){ + A->ApplyMoveForce(colpoint.normal*0.2f*impulseA); + A->ApplyTurnForce(colpoint.normal*0.2f*impulseA, pointposA); + } + return false; + } + } + + if((impulseA > Bobj->m_fUprootLimit || A->bIsStuck) && + !B->bInfiniteMass){ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false); + else + B->SetIsStatic(false); + int16 model = B->GetModelIndex(); + if(model == MI_FIRE_HYDRANT && !Bobj->bHasBeenDamaged){ + CParticleObject::AddObject(POBJECT_FIRE_HYDRANT, B->GetPosition() - CVector(0.0f, 0.0f, 0.5f), true); + Bobj->bHasBeenDamaged = true; + }else if(B->IsObject() && !IsExplosiveThingModel(model)) + Bobj->bHasBeenDamaged = true; + }else{ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToSoftCollision(B, impulseA); + CVector f = colpoint.GetNormal() * impulseA; + if(A->IsVehicle() && colpoint.normal.z < 0.7f) + f.z *= 0.3f; + if(!A->bInfiniteMass){ + A->ApplyMoveForce(f); + if(!A->IsVehicle() || !CWorld::bNoMoreCollisionTorque) + A->ApplyTurnForce(f, pointposA); + } + return true; + } + }else if(!B->bInfiniteMass) + B->SetIsStatic(false); + } + } + + if(B->GetIsStatic()) + return false; + if(!B->bInfiniteMass) + B->AddToMovingList(); + } + + // B is not static + + if(A->bPedPhysics && B->bPedPhysics){ + // negative if A is moving towards B + speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + // positive if B is moving towards A + // not interested in how much B moves into A apparently? + // only interested in cases where A collided into B + speedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + float speedSum = Max(0.0f, DotProduct(B->m_vecMoveSpeed, colpoint.normal)); + // A has moved into B + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA-speedA) * A->m_fMass * massFactorA; + if(!A->bInfiniteMass) + A->ApplyMoveForce(colpoint.normal*(impulseA/massFactorA)); + return true; + } + }else if(A->bPedPhysics){ + CVector pointposB = colpoint.point - B->GetPosition(); + speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal); + + float mA = A->m_fMass*massFactorA; + float mB = B->GetMassTweak(pointposB, colpoint.normal, massFactorB); + float speedSum = (mB*speedB + mA*speedA)/(mA + mB); + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + if(B->bHasHitWall) + eB = speedSum; + else + eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA - speedA) * mA; + impulseB = -(eB - speedB) * mB; + CVector fA = colpoint.normal*(impulseA/massFactorA); + CVector fB = colpoint.normal*(-impulseB/massFactorB); + if(!A->bInfiniteMass){ + if(fA.z < 0.0f) fA.z = 0.0f; + if(ispedcontactB){ + fA.x *= 2.0f; + fA.y *= 2.0f; + } + A->ApplyMoveForce(fA); + } + if(!B->bInfiniteMass && !ispedcontactB){ + B->ApplyMoveForce(fB); + B->ApplyTurnForce(fB, pointposB); + } + return true; + } + }else if(B->bPedPhysics){ + CVector pointposA = colpoint.point - A->GetPosition(); + speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal); + speedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + + float mA = A->GetMassTweak(pointposA, colpoint.normal, massFactorA); + float mB = B->m_fMass*massFactorB; + float speedSum = (mB*speedB + mA*speedA)/(mA + mB); + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + if(B->bHasHitWall) + eB = speedSum; + else + eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA - speedA) * mA; + impulseB = -(eB - speedB) * mB; + CVector fA = colpoint.normal*(impulseA/massFactorA); + CVector fB = colpoint.normal*(-impulseB/massFactorB); + if(!A->bInfiniteMass && !ispedcontactA){ + if(fA.z < 0.0f) fA.z = 0.0f; + A->ApplyMoveForce(fA); + A->ApplyTurnForce(fA, pointposA); + } + if(!B->bInfiniteMass){ + if(fB.z < 0.0f){ + fB.z = 0.0f; + if(Abs(speedA) < 0.01f) + fB *= 0.5f; + } + if(ispedcontactA){ + fB.x *= 2.0f; + fB.y *= 2.0f; + } + B->ApplyMoveForce(fB); + } + return true; + } + }else{ + CVector pointposA = colpoint.point - A->GetPosition(); + CVector pointposB = colpoint.point - B->GetPosition(); + speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal); + speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal); + float mA = A->GetMassTweak(pointposA, colpoint.normal, massFactorA); + float mB = B->GetMassTweak(pointposB, colpoint.normal, massFactorB); + float speedSum = (mB*speedB + mA*speedA)/(mA + mB); + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + if(B->bHasHitWall) + eB = speedSum; + else + eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA - speedA) * mA; + impulseB = -(eB - speedB) * mB; + CVector fA = colpoint.normal*(impulseA/massFactorA); + CVector fB = colpoint.normal*(-impulseB/massFactorB); + if(A->IsVehicle() && !A->bHasHitWall){ + fA.x *= 1.4f; + fA.y *= 1.4f; + if(colpoint.normal.z < 0.7f) + fA.z *= 0.3f; + if(A->GetStatus() == STATUS_PLAYER) + pointposA *= 0.8f; + if(CWorld::bNoMoreCollisionTorque){ + A->ApplyFrictionMoveForce(fA*-0.3f); + A->ApplyFrictionTurnForce(fA*-0.3f, pointposA); + } + } + if(B->IsVehicle() && !B->bHasHitWall){ + fB.x *= 1.4f; + fB.y *= 1.4f; + if(-colpoint.normal.z < 0.7f) + fB.z *= 0.3f; + if(B->GetStatus() == STATUS_PLAYER) + pointposB *= 0.8f; + if(CWorld::bNoMoreCollisionTorque){ +#ifdef FIX_BUGS + B->ApplyFrictionMoveForce(fB*-0.3f); + B->ApplyFrictionTurnForce(fB*-0.3f, pointposB); +#else + A->ApplyFrictionMoveForce(fB*-0.3f); + A->ApplyFrictionTurnForce(fB*-0.3f, pointposB); +#endif + } + } + if(!A->bInfiniteMass){ + A->ApplyMoveForce(fA); + A->ApplyTurnForce(fA, pointposA); + } + if(!B->bInfiniteMass){ + if(B->bIsInSafePosition) + B->UnsetIsInSafePosition(); + B->ApplyMoveForce(fB); + B->ApplyTurnForce(fB, pointposB); + } + return true; + } + } + return false; +} + +bool +CPhysical::ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed) +{ + float normalSpeed; + float e; + CVector speed; + CVector vImpulse; + + if(bPedPhysics){ + normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal); + if(normalSpeed < 0.0f){ + impulse = -normalSpeed * m_fMass; + ApplyMoveForce(colpoint.normal * impulse); + return true; + } + }else{ + CVector pointpos = colpoint.point - GetPosition(); + speed = GetSpeed(pointpos); + normalSpeed = DotProduct(speed, colpoint.normal); + if(normalSpeed < 0.0f){ + float minspeed = 1.3f*GRAVITY * CTimer::GetTimeStep(); +#if GTA_VERSION >= GTA3_PC_11 + if ((IsObject() || IsVehicle() && (GetUp().z < -0.3f || ((CVehicle*)this)->IsBike() && (GetStatus() == STATUS_ABANDONED || GetStatus() == STATUS_WRECKED))) && +#else + if((IsObject() || IsVehicle() && GetUp().z < -0.3f) && +#endif + !bHasContacted && + Abs(m_vecMoveSpeed.x) < minspeed && + Abs(m_vecMoveSpeed.y) < minspeed && + Abs(m_vecMoveSpeed.z) < minspeed*2.0f) + e = -1.0f; + else + e = -(m_fElasticity + 1.0f); + impulse = normalSpeed * e * GetMass(pointpos, colpoint.normal); + + // ApplyMoveForce + vImpulse = colpoint.normal*impulse; + if(IsVehicle() && + (!bHasHitWall || + !(m_vecMoveSpeed.MagnitudeSqr() > 0.1 || !(B->IsBuilding() || ((CPhysical*)B)->bInfiniteMass)))) + moveSpeed += vImpulse * 1.2f * (1.0f/m_fMass); + else + moveSpeed += vImpulse * (1.0f/m_fMass); + + // ApplyTurnForce + CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + CVector turnimpulse = CrossProduct(pointpos-com, vImpulse); + turnSpeed += turnimpulse*(1.0f/m_fTurnMass); + + return true; + } + } + return false; +} + +bool +CPhysical::ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint) +{ + CVector speedA, speedB; + float normalSpeedA, normalSpeedB; + CVector vOtherSpeedA, vOtherSpeedB; + float fOtherSpeedA, fOtherSpeedB; + float speedSum; + CVector frictionDir; + float impulseA, impulseB; + float impulseLimit; + CPhysical *A = this; + + if(A->bPedPhysics && B->bPedPhysics){ + normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA; + vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeedA; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); +#endif + + speedSum = (B->m_fMass*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(B->m_fMass + A->m_fMass); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * A->m_fMass; + impulseB = (speedSum - fOtherSpeedB) * B->m_fMass; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; +#ifdef FIX_BUGS + if(impulseB > impulseLimit) impulseB = impulseLimit; +#else + if(impulseA < -impulseLimit) impulseA = -impulseLimit; // duplicate +#endif + A->ApplyFrictionMoveForce(frictionDir*impulseA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + return true; + } + }else if(A->bPedPhysics){ + if(B->IsVehicle()) + return false; + CVector pointposB = colpoint.point - B->GetPosition(); + speedB = B->GetSpeed(pointposB); + + normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + normalSpeedB = DotProduct(speedB, colpoint.normal); + vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA; + vOtherSpeedB = speedB - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeedA; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); +#endif + float massB = B->GetMass(pointposB, frictionDir); + speedSum = (massB*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(massB + A->m_fMass); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * A->m_fMass; + impulseB = (speedSum - fOtherSpeedB) * massB; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; + if(impulseB > impulseLimit) impulseB = impulseLimit; + A->ApplyFrictionMoveForce(frictionDir*impulseA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB); + return true; + } + }else if(B->bPedPhysics){ + if(A->IsVehicle()) + return false; + CVector pointposA = colpoint.point - A->GetPosition(); + speedA = A->GetSpeed(pointposA); + + normalSpeedA = DotProduct(speedA, colpoint.normal); + normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + vOtherSpeedA = speedA - colpoint.normal*normalSpeedA; + vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeedA; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); +#endif + float massA = A->GetMass(pointposA, frictionDir); + speedSum = (B->m_fMass*fOtherSpeedB + massA*fOtherSpeedA)/(B->m_fMass + massA); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * massA; + impulseB = (speedSum - fOtherSpeedB) * B->m_fMass; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; + if(impulseB > impulseLimit) impulseB = impulseLimit; + A->ApplyFrictionMoveForce(frictionDir*impulseA); + A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + return true; + } + }else{ + CVector pointposA = colpoint.point - A->GetPosition(); + CVector pointposB = colpoint.point - B->GetPosition(); + speedA = A->GetSpeed(pointposA); + speedB = B->GetSpeed(pointposB); + + normalSpeedA = DotProduct(speedA, colpoint.normal); + normalSpeedB = DotProduct(speedB, colpoint.normal); + vOtherSpeedA = speedA - colpoint.normal*normalSpeedA; + vOtherSpeedB = speedB - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeedA; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); +#endif + float massA = A->GetMass(pointposA, frictionDir); + float massB = B->GetMass(pointposB, frictionDir); + speedSum = (massB*fOtherSpeedB + massA*fOtherSpeedA)/(massB + massA); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * massA; + impulseB = (speedSum - fOtherSpeedB) * massB; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; + if(impulseB > impulseLimit) impulseB = impulseLimit; + A->ApplyFrictionMoveForce(frictionDir*impulseA); + A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB); + return true; + } + } + return false; +} + +bool +CPhysical::ApplyFriction(float adhesiveLimit, CColPoint &colpoint) +{ + CVector speed; + float normalSpeed; + CVector vOtherSpeed; + float fOtherSpeed; + CVector frictionDir; + float fImpulse; + float impulseLimit; + + if(bPedPhysics){ + normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal); + vOtherSpeed = m_vecMoveSpeed - colpoint.normal*normalSpeed; + + fOtherSpeed = vOtherSpeed.Magnitude(); + if(fOtherSpeed > 0.0f){ +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeed; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeed * (1.0f/fOtherSpeed); +#endif + // not really impulse but speed + // maybe use ApplyFrictionMoveForce instead? + fImpulse = -fOtherSpeed; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep() / m_fMass; + if(fImpulse < -impulseLimit) fImpulse = -impulseLimit; + CVector vImpulse = frictionDir*fImpulse; + m_vecMoveFriction += CVector(vImpulse.x, vImpulse.y, 0.0f); + return true; + } + }else{ + CVector pointpos = colpoint.point - GetPosition(); + speed = GetSpeed(pointpos); + normalSpeed = DotProduct(speed, colpoint.normal); + vOtherSpeed = speed - colpoint.normal*normalSpeed; + + fOtherSpeed = vOtherSpeed.Magnitude(); + if(fOtherSpeed > 0.0f){ +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeed; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeed * (1.0f/fOtherSpeed); +#endif + fImpulse = -fOtherSpeed * m_fMass; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep() * 1.5; + if(fImpulse < -impulseLimit) fImpulse = -impulseLimit; + ApplyFrictionMoveForce(frictionDir*fImpulse); + ApplyFrictionTurnForce(frictionDir*fImpulse, pointpos); + + if(fOtherSpeed > 0.1f && + colpoint.surfaceB != SURFACE_GRASS && colpoint.surfaceB != SURFACE_MUD_DRY && + CSurfaceTable::GetAdhesionGroup(colpoint.surfaceA) == ADHESIVE_HARD){ + CVector v = frictionDir * fOtherSpeed * 0.25f; + for(int i = 0; i < 4; i++) + CParticle::AddParticle(PARTICLE_SPARK_SMALL, colpoint.point, v); + } + return true; + } + } + return false; +} + +bool +CPhysical::ProcessShiftSectorList(CPtrList *lists) +{ + int i, j; + CPtrList *list; + CPtrNode *node; + CPhysical *A, *B; + CObject *Bobj; + bool canshift; + CVUVECTOR center; + float radius; + + int numCollisions; + int mostColliding; + CColPoint colpoints[MAX_COLLISION_POINTS]; + CVector shift = CVector(0.0f, 0.0f, 0.0f); + bool doShift = false; + CEntity *boat = nil; + + bool skipShift; + + A = this; + + A->GetBoundCentre(center); + radius = A->GetBoundRadius(); + for(i = 0; i <= ENTITYLIST_PEDS_OVERLAP; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + B = (CPhysical*)node->item; + Bobj = (CObject*)B; + skipShift = false; + + if(B->IsBuilding() || + B->IsObject() && B->bInfiniteMass || + A->IsPed() && B->IsObject() && B->GetIsStatic() && !Bobj->bHasBeenDamaged) + canshift = true; + else + canshift = false; + + if(B == A || + B->m_scanCode == CWorld::GetCurrentScanCode() || + !B->bUsesCollision || + (A->bHasHitWall && !canshift) || + !B->GetIsTouching(center, radius)) + continue; + + // This could perhaps be done a bit nicer + + if(B->IsBuilding()) + skipShift = false; + else if(IsStreetLight(A->GetModelIndex()) && + (B->IsVehicle() || B->IsPed()) && + A->GetUp().z < 0.66f) + skipShift = true; + else if((A->IsVehicle() || A->IsPed()) && + B->GetUp().z < 0.66f && + IsStreetLight(B->GetModelIndex())) + skipShift = true; + else if(A->IsObject() && B->IsVehicle()){ + CObject *Aobj = (CObject*)A; + if(Aobj->ObjectCreatedBy != TEMP_OBJECT && + !Aobj->bHasBeenDamaged && + Aobj->GetIsStatic()){ + if(Aobj->m_pCollidingEntity == B) + Aobj->m_pCollidingEntity = nil; + }else if(Aobj->m_pCollidingEntity != B){ + CMatrix inv; + CVector size = CModelInfo::GetColModel(A->GetModelIndex())->boundingBox.GetSize(); + size = A->GetMatrix() * size; + if(size.z < B->GetPosition().z || + (Invert(B->GetMatrix(), inv) * size).z < 0.0f){ + skipShift = true; + Aobj->m_pCollidingEntity = B; + } + } else + skipShift = true; + }else if(B->IsObject() && A->IsVehicle()){ + CObject *Bobj = (CObject*)B; + if(Bobj->ObjectCreatedBy != TEMP_OBJECT && + !Bobj->bHasBeenDamaged && + Bobj->GetIsStatic()){ + if(Bobj->m_pCollidingEntity == A) + Bobj->m_pCollidingEntity = nil; + }else if(Bobj->m_pCollidingEntity != A){ + CMatrix inv; + CVector size = CModelInfo::GetColModel(B->GetModelIndex())->boundingBox.GetSize(); + size = B->GetMatrix() * size; + if(size.z < A->GetPosition().z || + (Invert(A->GetMatrix(), inv) * size).z < 0.0f) + skipShift = true; + } else + skipShift = true; + }else if(IsBodyPart(A->GetModelIndex()) && B->IsPed()) + skipShift = true; + else if(A->IsPed() && IsBodyPart(B->GetModelIndex())) + skipShift = true; + else if(A->IsPed() && ((CPed*)A)->m_pCollidingEntity == B || + B->IsPed() && ((CPed*)B)->m_pCollidingEntity == A) + skipShift = true; + else if(A->GetModelIndex() == MI_RCBANDIT && B->IsVehicle() || + B->GetModelIndex() == MI_RCBANDIT && (A->IsPed() || A->IsVehicle())) + skipShift = true; + + if(skipShift) + continue; + + B->m_scanCode = CWorld::GetCurrentScanCode(); + numCollisions = A->ProcessEntityCollision(B, colpoints); + if(numCollisions <= 0) + continue; + + mostColliding = 0; + for(j = 1; j < numCollisions; j++) + if (colpoints[j].GetDepth() > colpoints[mostColliding].GetDepth()) + mostColliding = j; + + if(CWorld::bSecondShift) + for(j = 0; j < numCollisions; j++) + shift += colpoints[j].GetNormal() * colpoints[j].GetDepth() * 1.5f / numCollisions; + else + for(j = 0; j < numCollisions; j++) + shift += colpoints[j].GetNormal() * colpoints[j].GetDepth() * 1.2f / numCollisions; + + if(A->IsVehicle() && B->IsVehicle()){ + CVector dir = A->GetPosition() - B->GetPosition(); + dir.Normalise(); + if(dir.z < 0.0f && dir.z < A->GetForward().z && dir.z < A->GetRight().z) + dir.z = Min(0.0f, Min(A->GetForward().z, A->GetRight().z)); + shift += dir * colpoints[mostColliding].GetDepth() * 0.5f; + }else if(A->IsPed() && B->IsVehicle() && ((CVehicle*)B)->IsBoat()){ + CVector dir = colpoints[mostColliding].GetNormal(); + float f = Min(Abs(dir.z), 0.9f); + dir.z = 0.0f; + dir.Normalise(); + shift += dir * colpoints[mostColliding].GetDepth() / (1.0f - f); + boat = B; + }else if(B->IsPed() && A->IsVehicle() && ((CVehicle*)A)->IsBoat()){ + CVector dir = colpoints[mostColliding].GetNormal() * -1.0f; + float f = Min(Abs(dir.z), 0.9f); + dir.z = 0.0f; + dir.Normalise(); + B->GetMatrix().Translate(dir * colpoints[mostColliding].GetDepth() / (1.0f - f)); + // BUG? how can that ever happen? A is a Ped + if(B->IsVehicle()) + B->ProcessEntityCollision(A, colpoints); + }else{ + if(CWorld::bSecondShift) + shift += colpoints[mostColliding].GetNormal() * colpoints[mostColliding].GetDepth() * 0.4f; + else + shift += colpoints[mostColliding].GetNormal() * colpoints[mostColliding].GetDepth() * 0.2f; + } + + doShift = true; + } + } + + if(!doShift) + return false; + GetMatrix().Translate(shift); + if(boat) + ProcessEntityCollision(boat, colpoints); + return true; +} + +bool +CPhysical::ProcessCollisionSectorList_SimpleCar(CPtrList *lists) +{ + static CColPoint aColPoints[MAX_COLLISION_POINTS]; + float radius; + CVUVECTOR center; + int listtype; + CPhysical *A, *B; + int numCollisions; + int i; + float impulseA = -1.0f; + float impulseB = -1.0f; + + A = (CPhysical*)this; + + radius = A->GetBoundRadius(); + A->GetBoundCentre(center); + + for(listtype = 3; listtype >= 0; listtype--){ + // Go through vehicles and objects + CPtrList *list; + switch(listtype){ + case 0: list = &lists[ENTITYLIST_VEHICLES]; break; + case 1: list = &lists[ENTITYLIST_VEHICLES_OVERLAP]; break; + case 2: list = &lists[ENTITYLIST_OBJECTS]; break; + case 3: list = &lists[ENTITYLIST_OBJECTS_OVERLAP]; break; + } + + // Find first collision in list + CPtrNode *listnode; + for(listnode = list->first; listnode; listnode = listnode->next){ + B = (CPhysical*)listnode->item; + if(B != A && + B->m_scanCode != CWorld::GetCurrentScanCode() && + B->bUsesCollision && + B->GetIsTouching(center, radius)){ + B->m_scanCode = CWorld::GetCurrentScanCode(); + numCollisions = A->ProcessEntityCollision(B, aColPoints); + if(numCollisions > 0) + goto collision; + } + } + } + // no collision + return false; + +collision: + + if(A->bHasContacted && B->bHasContacted){ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + } + }else if(A->bHasContacted){ + CVector savedMoveFriction = A->m_vecMoveFriction; + CVector savedTurnFriction = A->m_vecTurnFriction; + A->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + A->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + A->bHasContacted = false; + + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + + if(!A->bHasContacted){ + A->bHasContacted = true; + A->m_vecMoveFriction = savedMoveFriction; + A->m_vecTurnFriction = savedTurnFriction; + } + }else if(B->bHasContacted){ + CVector savedMoveFriction = B->m_vecMoveFriction; + CVector savedTurnFriction = B->m_vecTurnFriction; + B->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + B->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + B->bHasContacted = false; + + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + + if(!B->bHasContacted){ + B->bHasContacted = true; + B->m_vecMoveFriction = savedMoveFriction; + B->m_vecTurnFriction = savedTurnFriction; + } + }else{ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + } + + if(B->GetStatus() == STATUS_SIMPLE){ + B->SetStatus(STATUS_PHYSICS); + if(B->IsVehicle()) + CCarCtrl::SwitchVehicleToRealPhysics((CVehicle*)B); + } + + return true; +} + +bool +CPhysical::ProcessCollisionSectorList(CPtrList *lists) +{ + static CColPoint aColPoints[MAX_COLLISION_POINTS]; + float radius; + CVUVECTOR center; + CPtrList *list; + CPhysical *A, *B; + CObject *Aobj, *Bobj; + CPed *Aped, *Bped; + int numCollisions; + int numResponses; + int i, j; + bool skipCollision, altcollision; + float impulseA = -1.0f; + float impulseB = -1.0f; + + A = (CPhysical*)this; + Aobj = (CObject*)A; + Aped = (CPed*)A; + + radius = A->GetBoundRadius(); + A->GetBoundCentre(center); + + for(j = 0; j <= ENTITYLIST_PEDS_OVERLAP; j++){ + list = &lists[j]; + + CPtrNode *listnode; + for(listnode = list->first; listnode; listnode = listnode->next){ + B = (CPhysical*)listnode->item; + Bobj = (CObject*)B; + Bped = (CPed*)B; + + bool isTouching = true; + if(B == A || + B->m_scanCode == CWorld::GetCurrentScanCode() || + !B->bUsesCollision) + continue; + if(!B->GetIsTouching(center, radius)){ + if(A->IsObject() && Aobj->m_pCollidingEntity == B) + Aobj->m_pCollidingEntity = nil; + else if(B->IsObject() && Bobj->m_pCollidingEntity == A) + Bobj->m_pCollidingEntity = nil; + else if(A->IsPed() && Aped->m_pCollidingEntity == B) + Aped->m_pCollidingEntity = nil; + else if(B->IsPed() && Bped->m_pCollidingEntity == A) + Bped->m_pCollidingEntity = nil; + continue; + } + + A->bSkipLineCol = false; + skipCollision = false; + altcollision = false; + + if(B->IsBuilding()) + skipCollision = false; + else if(IsStreetLight(A->GetModelIndex()) && + (B->IsVehicle() || B->IsPed()) && + A->GetUp().z < 0.66f){ + skipCollision = true; + A->bSkipLineCol = true; + Aobj->m_pCollidingEntity = B; + }else if((A->IsVehicle() || A->IsPed()) && + B->GetUp().z < 0.66f && + IsStreetLight(B->GetModelIndex())){ + skipCollision = true; + A->bSkipLineCol = true; + Bobj->m_pCollidingEntity = A; + }else if(A->IsObject() && B->IsVehicle()){ + if(A->GetModelIndex() == MI_CAR_BUMPER || A->GetModelIndex() == MI_FILES) + skipCollision = true; + else if(Aobj->ObjectCreatedBy == TEMP_OBJECT || + Aobj->bHasBeenDamaged || + !Aobj->GetIsStatic()){ + if(Aobj->m_pCollidingEntity == B) + skipCollision = true; + else{ + CMatrix inv; + CVector size = CModelInfo::GetColModel(A->GetModelIndex())->boundingBox.GetSize(); + size = A->GetMatrix() * size; + if(size.z < B->GetPosition().z || + (Invert(B->GetMatrix(), inv) * size).z < 0.0f){ + skipCollision = true; + Aobj->m_pCollidingEntity = B; + } + } + } + }else if(B->IsObject() && A->IsVehicle()){ + if(B->GetModelIndex() == MI_CAR_BUMPER || B->GetModelIndex() == MI_FILES) + skipCollision = true; + else if(Bobj->ObjectCreatedBy == TEMP_OBJECT || + Bobj->bHasBeenDamaged || + !Bobj->GetIsStatic()){ + if(Bobj->m_pCollidingEntity == A) + skipCollision = true; + else{ + CMatrix inv; + CVector size = CModelInfo::GetColModel(B->GetModelIndex())->boundingBox.GetSize(); + size = B->GetMatrix() * size; + if(size.z < A->GetPosition().z || + (Invert(A->GetMatrix(), inv) * size).z < 0.0f){ + skipCollision = true; + } + } + } + }else if(IsBodyPart(A->GetModelIndex()) && B->IsPed()){ + skipCollision = true; + }else if(A->IsPed() && IsBodyPart(B->GetModelIndex())){ + skipCollision = true; + A->bSkipLineCol = true; + }else if(A->IsPed() && Aped->m_pCollidingEntity == B){ + skipCollision = true; + if(!Aped->bKnockedUpIntoAir) + A->bSkipLineCol = true; + }else if(B->IsPed() && Bped->m_pCollidingEntity == A){ + skipCollision = true; + A->bSkipLineCol = true; + }else if(A->GetModelIndex() == MI_RCBANDIT && (B->IsPed() || B->IsVehicle()) || + B->GetModelIndex() == MI_RCBANDIT && (A->IsPed() || A->IsVehicle())){ + skipCollision = true; + A->bSkipLineCol = true; + }else if(A->IsPed() && B->IsObject() && Bobj->m_fUprootLimit > 0.0f) + altcollision = true; + + + if(!A->bUsesCollision || skipCollision){ + B->m_scanCode = CWorld::GetCurrentScanCode(); + A->ProcessEntityCollision(B, aColPoints); + }else if(B->IsBuilding() || B->bIsStuck || B->bInfiniteMass || altcollision){ + + // This is the case where B doesn't move + + B->m_scanCode = CWorld::GetCurrentScanCode(); + numCollisions = A->ProcessEntityCollision(B, aColPoints); + if(numCollisions <= 0) + continue; + + CVector moveSpeed = CVector(0.0f, 0.0f, 0.0f); + CVector turnSpeed = CVector(0.0f, 0.0f, 0.0f); + numResponses = 0; + if(A->bHasContacted){ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollisionAlt(B, aColPoints[i], impulseA, moveSpeed, turnSpeed)) + continue; + + numResponses++; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + float imp = impulseA; + if(A->IsVehicle() && A->GetUp().z < -0.6f && + Abs(A->m_vecMoveSpeed.x) < 0.05f && + Abs(A->m_vecMoveSpeed.y) < 0.05f) + imp *= 0.1f; + + float turnSpeedDiff = A->m_vecTurnSpeed.MagnitudeSqr(); + float moveSpeedDiff = A->m_vecMoveSpeed.MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, imp, Max(turnSpeedDiff, moveSpeedDiff)); + } + }else{ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollisionAlt(B, aColPoints[i], impulseA, moveSpeed, turnSpeed)) + continue; + + numResponses++; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + float imp = impulseA; + if(A->IsVehicle() && A->GetUp().z < -0.6f && + Abs(A->m_vecMoveSpeed.x) < 0.05f && + Abs(A->m_vecMoveSpeed.y) < 0.05f) + imp *= 0.1f; + + float turnSpeedDiff = A->m_vecTurnSpeed.MagnitudeSqr(); + float moveSpeedDiff = A->m_vecMoveSpeed.MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, imp, Max(turnSpeedDiff, moveSpeedDiff)); + + float adhesion = CSurfaceTable::GetAdhesiveLimit(aColPoints[i]) / numCollisions; + + if(A->GetModelIndex() == MI_RCBANDIT) + adhesion *= 0.2f; + else if(IsBoatModel(A->GetModelIndex())){ + if(aColPoints[i].normal.z > 0.6f){ + if(CSurfaceTable::GetAdhesionGroup(aColPoints[i].surfaceB) == ADHESIVE_LOOSE) + adhesion *= 3.0f; + }else + adhesion = 0.0f; + }else if(A->IsVehicle()){ + if(A->GetStatus() == STATUS_WRECKED) + adhesion *= 3.0f; + else if(A->GetUp().z > 0.3f) + adhesion = 0.0f; + else + adhesion *= Min(5.0f, 0.03f*impulseA + 1.0f); + } + + if(A->ApplyFriction(adhesion, aColPoints[i])) + A->bHasContacted = true; + } + } + + if(numResponses){ + m_vecMoveSpeed += moveSpeed / numResponses; + m_vecTurnSpeed += turnSpeed / numResponses; + if(!CWorld::bNoMoreCollisionTorque && + A->GetStatus() == STATUS_PLAYER && A->IsVehicle() && + Abs(A->m_vecMoveSpeed.x) > 0.2f && + Abs(A->m_vecMoveSpeed.y) > 0.2f){ + A->m_vecMoveFriction.x += moveSpeed.x * -0.3f / numCollisions; + A->m_vecMoveFriction.y += moveSpeed.y * -0.3f / numCollisions; + A->m_vecTurnFriction += turnSpeed * -0.3f / numCollisions; + } + return true; + } + }else{ + + // B can move + + B->m_scanCode = CWorld::GetCurrentScanCode(); + numCollisions = A->ProcessEntityCollision(B, aColPoints); + if(numCollisions <= 0) + continue; + + float maxImpulseA = 0.0f; + float maxImpulseB = 0.0f; + if(A->bHasContacted && B->bHasContacted){ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + } + }else if(A->bHasContacted){ + CVector savedMoveFriction = A->m_vecMoveFriction; + CVector savedTurnFriction = A->m_vecTurnFriction; + A->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + A->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + A->bHasContacted = false; + + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + + if(!A->bHasContacted){ + A->bHasContacted = true; + A->m_vecMoveFriction = savedMoveFriction; + A->m_vecTurnFriction = savedTurnFriction; + } + }else if(B->bHasContacted){ + CVector savedMoveFriction = B->m_vecMoveFriction; + CVector savedTurnFriction = B->m_vecTurnFriction; + B->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + B->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + B->bHasContacted = false; + + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + + if(!B->bHasContacted){ + B->bHasContacted = true; + B->m_vecMoveFriction = savedMoveFriction; + B->m_vecTurnFriction = savedTurnFriction; + } + }else{ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + } + + if(B->IsPed() && A->IsVehicle() && + (!Bped->IsPlayer() || B->bHasHitWall && A->m_vecMoveSpeed.MagnitudeSqr() > SQR(0.05f))) + Bped->KillPedWithCar((CVehicle*)A, maxImpulseB); + else if(B->GetModelIndex() == MI_TRAIN && A->IsPed() && + (!Aped->IsPlayer() || A->bHasHitWall)) + Aped->KillPedWithCar((CVehicle*)B, maxImpulseA*2.0f); + else if(B->IsObject() && B->bUsesCollision && A->IsVehicle()){ + // BUG? not impulseA? + if(Bobj->m_nCollisionDamageEffect && maxImpulseB > 20.0f) + Bobj->ObjectDamage(maxImpulseB); + }else if(A->IsObject() && A->bUsesCollision && B->IsVehicle()){ + if(Aobj->m_nCollisionDamageEffect && maxImpulseB > 20.0f) + Aobj->ObjectDamage(maxImpulseB); + } + + if(B->GetStatus() == STATUS_SIMPLE){ + B->SetStatus(STATUS_PHYSICS); + if(B->IsVehicle()) + CCarCtrl::SwitchVehicleToRealPhysics((CVehicle*)B); + } + + return true; + } + + } + } + + return false; +} + +bool +CPhysical::CheckCollision(void) +{ + CEntryInfoNode *node; + + bCollisionProcessed = false; + CWorld::AdvanceCurrentScanCode(); + for(node = m_entryInfoList.first; node; node = node->next) + if(ProcessCollisionSectorList(node->sector->m_lists)) + return true; + return false; +} + +bool +CPhysical::CheckCollision_SimpleCar(void) +{ + CEntryInfoNode *node; + + bCollisionProcessed = false; + CWorld::AdvanceCurrentScanCode(); + for(node = m_entryInfoList.first; node; node = node->next) + if(ProcessCollisionSectorList_SimpleCar(node->sector->m_lists)) + return true; + return false; +} + +void +CPhysical::ProcessShift(void) +{ + m_fDistanceTravelled = 0.0f; + if(GetStatus() == STATUS_SIMPLE){ + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); + }else{ + CMatrix matrix(GetMatrix()); + ApplyMoveSpeed(); + ApplyTurnSpeed(); + GetMatrix().Reorthogonalise(); + + CWorld::AdvanceCurrentScanCode(); + + if(IsVehicle()) + m_bIsVehicleBeingShifted = true; + + CEntryInfoNode *node; + bool hasshifted = false; + for(node = m_entryInfoList.first; node; node = node->next) + hasshifted |= ProcessShiftSectorList(node->sector->m_lists); + m_bIsVehicleBeingShifted = false; + if(hasshifted){ + CWorld::AdvanceCurrentScanCode(); + for(node = m_entryInfoList.first; node; node = node->next) + if(ProcessCollisionSectorList(node->sector->m_lists)){ + GetMatrix() = matrix; + return; + } + } + bIsStuck = false; + bIsInSafePosition = true; + m_fDistanceTravelled = (GetPosition() - matrix.GetPosition()).Magnitude(); + RemoveAndAdd(); + } +} + +// x is the number of units (m) we would like to step +#define NUMSTEPS(x) Ceil(Sqrt(distSq) * (1.0f/(x))) + +void +CPhysical::ProcessCollision(void) +{ + int i; + CPed *ped = (CPed*)this; + + m_fDistanceTravelled = 0.0f; + m_bIsVehicleBeingShifted = false; + bSkipLineCol = false; + + if(!bUsesCollision){ + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); + return; + } + + if(GetStatus() == STATUS_SIMPLE){ + if(CheckCollision_SimpleCar() && GetStatus() == STATUS_SIMPLE){ + SetStatus(STATUS_PHYSICS); + if(IsVehicle()) + CCarCtrl::SwitchVehicleToRealPhysics((CVehicle*)this); + } + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); + return; + } + + // Save current state + CMatrix savedMatrix(GetMatrix()); + float savedTimeStep = CTimer::GetTimeStep(); + + int8 n = 1; // The number of steps we divide the time step into + float step = 0.0f; // divided time step + float distSq = m_vecMoveSpeed.MagnitudeSqr() * sq(CTimer::GetTimeStep()); + + if(IsPed() && (distSq >= sq(0.2f) || ped->IsPlayer())){ + if(ped->IsPlayer()) + n = Max(NUMSTEPS(0.2f), 2.0f); + else + n = NUMSTEPS(0.3f); + step = savedTimeStep / n; + }else if(IsVehicle() && distSq >= sq(0.4f)){ + if(GetStatus() == STATUS_PLAYER) + n = NUMSTEPS(0.2f); + else + n = distSq > 0.32f ? NUMSTEPS(0.3f) : NUMSTEPS(0.4f); + step = savedTimeStep / n; + }else if(IsObject()){ + int responsecase = ((CObject*)this)->m_nSpecialCollisionResponseCases; + if(responsecase == COLLRESPONSE_LAMPOST){ + CVector speedUp = CVector(0.0f, 0.0f, 0.0f); + CVector speedDown = CVector(0.0f, 0.0f, 0.0f); + speedUp.z = GetBoundRadius(); + speedDown.z = -speedUp.z; + speedUp = Multiply3x3(GetMatrix(), speedUp); + speedDown = Multiply3x3(GetMatrix(), speedDown); + speedUp = GetSpeed(speedUp); + speedDown = GetSpeed(speedDown); + distSq = Max(speedUp.MagnitudeSqr(), speedDown.MagnitudeSqr()) * sq(CTimer::GetTimeStep()); + if(distSq >= sq(0.3f)){ + n = NUMSTEPS(0.3f); + step = savedTimeStep / n; + } + }else if(responsecase == COLLRESPONSE_UNKNOWN5){ + if(distSq >= 0.009f){ + n = NUMSTEPS(0.09f); + step = savedTimeStep / n; + } + }else if(responsecase == COLLRESPONSE_SMALLBOX || responsecase == COLLRESPONSE_FENCEPART){ + if(distSq >= sq(0.15f)){ + n = NUMSTEPS(0.15f); + step = savedTimeStep / n; + } + }else{ + if(distSq >= sq(0.3f)){ + n = NUMSTEPS(0.3f); + step = savedTimeStep / n; + } + } + } + + for(i = 1; i < n; i++){ + CTimer::SetTimeStep(i * step); + ApplyMoveSpeed(); + ApplyTurnSpeed(); + // TODO: get rid of copy paste? + if(CheckCollision()){ + if(IsPed() && m_vecMoveSpeed.z == 0.0f && + !ped->bWasStanding && + ped->bIsStanding) + savedMatrix.GetPosition().z = GetPosition().z; + GetMatrix() = savedMatrix; + CTimer::SetTimeStep(savedTimeStep); + return; + } + if(IsPed() && m_vecMoveSpeed.z == 0.0f && + !ped->bWasStanding && + ped->bIsStanding) + savedMatrix.GetPosition().z = GetPosition().z; + GetMatrix() = savedMatrix; + CTimer::SetTimeStep(savedTimeStep); + if(IsVehicle()){ + CVehicle *veh = (CVehicle*)this; + if(veh->m_vehType == VEHICLE_TYPE_CAR){ + CAutomobile *car = (CAutomobile*)this; + car->m_aSuspensionSpringRatio[0] = 1.0f; + car->m_aSuspensionSpringRatio[1] = 1.0f; + car->m_aSuspensionSpringRatio[2] = 1.0f; + car->m_aSuspensionSpringRatio[3] = 1.0f; + }else if(veh->m_vehType == VEHICLE_TYPE_BIKE){ + CBike* bike = (CBike*)this; + bike->m_aSuspensionSpringRatio[0] = 1.0f; + bike->m_aSuspensionSpringRatio[1] = 1.0f; + bike->m_aSuspensionSpringRatio[2] = 1.0f; + bike->m_aSuspensionSpringRatio[3] = 1.0f; + } + } + } + + ApplyMoveSpeed(); + ApplyTurnSpeed(); + GetMatrix().Reorthogonalise(); + m_bIsVehicleBeingShifted = false; + bSkipLineCol = false; + if(!m_vecMoveSpeed.IsZero() || + !m_vecTurnSpeed.IsZero() || +#ifdef GTA_TRAIN + bHitByTrain || +#endif + GetStatus() == STATUS_PLAYER || + IsPed() && ped->IsPlayer()){ + if(IsVehicle()) + ((CVehicle*)this)->bVehicleColProcessed = true; + if(CheckCollision()){ + GetMatrix() = savedMatrix; + return; + } + } + bHitByTrain = false; + m_fDistanceTravelled = (GetPosition() - savedMatrix.GetPosition()).Magnitude(); + bSkipLineCol = false; + + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); +} diff --git a/src/entities/Physical.h b/src/entities/Physical.h new file mode 100644 index 0000000..a16bb21 --- /dev/null +++ b/src/entities/Physical.h @@ -0,0 +1,161 @@ +#pragma once + +#include "Lists.h" +#include "Timer.h" +#include "Entity.h" + +enum { + PHYSICAL_MAX_COLLISIONRECORDS = 6 +}; + +#define GRAVITY (0.008f) + +class CTreadable; + +class CPhysical : public CEntity +{ +public: + int32 m_audioEntityId; + float m_phys_unused1; + CTreadable *m_treadable[2]; // car and ped + uint32 m_nLastTimeCollided; + CVector m_vecMoveSpeed; // velocity + CVector m_vecTurnSpeed; // angular velocity + CVector m_vecMoveFriction; + CVector m_vecTurnFriction; + CVector m_vecMoveSpeedAvg; + CVector m_vecTurnSpeedAvg; + float m_fMass; + float m_fTurnMass; // moment of inertia + float m_fForceMultiplier; + float m_fAirResistance; + float m_fElasticity; + float m_fBuoyancy; + CVector m_vecCentreOfMass; + CEntryInfoList m_entryInfoList; + CPtrNode *m_movingListNode; + + int8 m_phys_unused2; + uint8 m_nStaticFrames; + uint8 m_nCollisionRecords; + bool m_bIsVehicleBeingShifted; + CEntity *m_aCollisionRecords[PHYSICAL_MAX_COLLISIONRECORDS]; + + float m_fDistanceTravelled; + + // damaged piece + float m_fDamageImpulse; + CEntity *m_pDamageEntity; + CVector m_vecDamageNormal; + int16 m_nDamagePieceType; + + uint8 bIsHeavy : 1; + uint8 bAffectedByGravity : 1; + uint8 bInfiniteMass : 1; + uint8 bIsInWater : 1; + uint8 m_phy_flagA10 : 1; // unused + uint8 m_phy_flagA20 : 1; // unused + uint8 bHitByTrain : 1; + uint8 bSkipLineCol : 1; + + uint8 m_nSurfaceTouched; + int8 m_nZoneLevel; + + CPhysical(void); + ~CPhysical(void); + + // from CEntity + void Add(void); + void Remove(void); + CRect GetBoundRect(void); + void ProcessControl(void); + void ProcessShift(void); + void ProcessCollision(void); + + virtual int32 ProcessEntityCollision(CEntity *ent, CColPoint *colpoints); + + void RemoveAndAdd(void); + void AddToMovingList(void); + void RemoveFromMovingList(void); + void SetDamagedPieceRecord(uint16 piece, float impulse, CEntity *entity, CVector dir); + void AddCollisionRecord(CEntity *ent); + void AddCollisionRecord_Treadable(CEntity *ent); + bool GetHasCollidedWith(CEntity *ent); + void RemoveRefsToEntity(CEntity *ent); + static void PlacePhysicalRelativeToOtherPhysical(CPhysical *other, CPhysical *phys, CVector localPos); + + // get speed of point p relative to entity center + CVector GetSpeed(const CVector &r); + CVector GetSpeed(void) { return GetSpeed(CVector(0.0f, 0.0f, 0.0f)); } + float GetMass(const CVector &pos, const CVector &dir) { + return 1.0f / (CrossProduct(pos, dir).MagnitudeSqr()/m_fTurnMass + + 1.0f/m_fMass); + } + float GetMassTweak(const CVector &pos, const CVector &dir, float t) { + return 1.0f / (CrossProduct(pos, dir).MagnitudeSqr()/(m_fTurnMass*t) + + 1.0f/(m_fMass*t)); + } + void UnsetIsInSafePosition(void) { + m_vecMoveSpeed *= -1.0f; + m_vecTurnSpeed *= -1.0f; + ApplyTurnSpeed(); + ApplyMoveSpeed(); + m_vecMoveSpeed *= -1.0f; + m_vecTurnSpeed *= -1.0f; + bIsInSafePosition = false; + } + + const CVector &GetMoveSpeed() { return m_vecMoveSpeed; } + void SetMoveSpeed(float x, float y, float z) { + m_vecMoveSpeed.x = x; + m_vecMoveSpeed.y = y; + m_vecMoveSpeed.z = z; + } + void SetMoveSpeed(const CVector& speed) { + m_vecMoveSpeed = speed; + } + const CVector &GetTurnSpeed() { return m_vecTurnSpeed; } + void SetTurnSpeed(float x, float y, float z) { + m_vecTurnSpeed.x = x; + m_vecTurnSpeed.y = y; + m_vecTurnSpeed.z = z; + } + const CVector &GetCenterOfMass() { return m_vecCentreOfMass; } + void SetCenterOfMass(float x, float y, float z) { + m_vecCentreOfMass.x = x; + m_vecCentreOfMass.y = y; + m_vecCentreOfMass.z = z; + } + + void ApplyMoveSpeed(void); + void ApplyTurnSpeed(void); + // Force actually means Impulse here + void ApplyMoveForce(float jx, float jy, float jz); + void ApplyMoveForce(const CVector &j) { ApplyMoveForce(j.x, j.y, j.z); } + // j(x,y,z) is direction of force, p(x,y,z) is point relative to model center where force is applied + void ApplyTurnForce(float jx, float jy, float jz, float px, float py, float pz); + // j is direction of force, p is point relative to model center where force is applied + void ApplyTurnForce(const CVector &j, const CVector &p) { ApplyTurnForce(j.x, j.y, j.z, p.x, p.y, p.z); } + void ApplyFrictionMoveForce(float jx, float jy, float jz); + void ApplyFrictionMoveForce(const CVector &j) { ApplyFrictionMoveForce(j.x, j.y, j.z); } + void ApplyFrictionTurnForce(float jx, float jy, float jz, float rx, float ry, float rz); + void ApplyFrictionTurnForce(const CVector &j, const CVector &p) { ApplyFrictionTurnForce(j.x, j.y, j.z, p.x, p.y, p.z); } + // springRatio: 1.0 fully extended, 0.0 fully compressed + bool ApplySpringCollision(float springConst, CVector &springDir, CVector &point, float springRatio, float bias); + bool ApplySpringDampening(float damping, CVector &springDir, CVector &point, CVector &speed); + void ApplyGravity(void); + void ApplyFriction(void); + void ApplyAirResistance(void); + bool ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB); + bool ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed); + bool ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint); + bool ApplyFriction(float adhesiveLimit, CColPoint &colpoint); + + bool ProcessShiftSectorList(CPtrList *ptrlists); + bool ProcessCollisionSectorList_SimpleCar(CPtrList *lists); + bool ProcessCollisionSectorList(CPtrList *lists); + bool CheckCollision(void); + bool CheckCollision_SimpleCar(void); +}; + +VALIDATE_SIZE(CPhysical, 0x128); diff --git a/src/extras/GitSHA1.cpp.in b/src/extras/GitSHA1.cpp.in new file mode 100644 index 0000000..6168dc7 --- /dev/null +++ b/src/extras/GitSHA1.cpp.in @@ -0,0 +1,2 @@ +#define GIT_SHA1 "@GIT_SHA1@" +const char* g_GIT_SHA1 = GIT_SHA1; diff --git a/src/extras/GitSHA1.h b/src/extras/GitSHA1.h new file mode 100644 index 0000000..359bfaf --- /dev/null +++ b/src/extras/GitSHA1.h @@ -0,0 +1 @@ +extern const char* g_GIT_SHA1; \ No newline at end of file diff --git a/src/extras/arrow.inc b/src/extras/arrow.inc new file mode 100644 index 0000000..8ea7828 --- /dev/null +++ b/src/extras/arrow.inc @@ -0,0 +1,16 @@ +255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/src/extras/cursor.inc b/src/extras/cursor.inc new file mode 100644 index 0000000..e8afd39 --- /dev/null +++ b/src/extras/cursor.inc @@ -0,0 +1,16 @@ +255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, diff --git a/src/extras/custompipes.cpp b/src/extras/custompipes.cpp new file mode 100644 index 0000000..092b3e2 --- /dev/null +++ b/src/extras/custompipes.cpp @@ -0,0 +1,543 @@ +#define WITHD3D +#include "common.h" + +#ifdef EXTENDED_PIPELINES + +#include "main.h" +#include "RwHelper.h" +#include "Lights.h" +#include "Timecycle.h" +#include "FileMgr.h" +#include "Clock.h" +#include "Weather.h" +#include "TxdStore.h" +#include "Renderer.h" +#include "World.h" +#include "custompipes.h" + +#ifndef LIBRW +#error "Need librw for EXTENDED_PIPELINES" +#endif + +namespace CustomPipes { + +rw::int32 CustomMatOffset; + +void* +CustomMatCtor(void *object, int32, int32) +{ + CustomMatExt *ext = GetCustomMatExt((rw::Material*)object); + ext->glossTex = nil; + ext->haveGloss = false; + return object; +} + +void* +CustomMatCopy(void *dst, void *src, int32, int32) +{ + CustomMatExt *srcext = GetCustomMatExt((rw::Material*)src); + CustomMatExt *dstext = GetCustomMatExt((rw::Material*)dst); + dstext->glossTex = srcext->glossTex; + dstext->haveGloss = srcext->haveGloss; + return dst; +} + + + +rw::TexDictionary *neoTxd; + +bool bRenderingEnvMap; +int32 EnvMapSize = 128; +rw::Camera *EnvMapCam; +rw::Texture *EnvMapTex; +rw::Texture *EnvMaskTex; +static rw::RWDEVICE::Im2DVertex EnvScreenQuad[4]; +static int16 QuadIndices[6] = { 0, 1, 2, 0, 2, 3 }; + +static rw::Camera* +CreateEnvMapCam(rw::World *world) +{ + rw::Raster *fbuf = rw::Raster::create(EnvMapSize, EnvMapSize, 0, rw::Raster::CAMERATEXTURE); + if(fbuf){ + rw::Raster *zbuf = rw::Raster::create(EnvMapSize, EnvMapSize, 0, rw::Raster::ZBUFFER); + if(zbuf){ + rw::Frame *frame = rw::Frame::create(); + if(frame){ + rw::Camera *cam = rw::Camera::create(); + if(cam){ + cam->frameBuffer = fbuf; + cam->zBuffer = zbuf; + cam->setFrame(frame); + cam->setNearPlane(0.1f); + cam->setFarPlane(250.0f); + rw::V2d vw = { 2.0f, 2.0f }; + cam->setViewWindow(&vw); + world->addCamera(cam); + EnvMapTex = rw::Texture::create(fbuf); + EnvMapTex->setFilter(rw::Texture::LINEAR); + + frame->matrix.right.x = -1.0f; + frame->matrix.up.y = -1.0f; + frame->matrix.update(); + return cam; + } + frame->destroy(); + } + zbuf->destroy(); + } + fbuf->destroy(); + } + return nil; +} + +static void +DestroyCam(rw::Camera *cam) +{ + if(cam == nil) + return; + if(cam->frameBuffer){ + cam->frameBuffer->destroy(); + cam->frameBuffer = nil; + } + if(cam->zBuffer){ + cam->zBuffer->destroy(); + cam->zBuffer = nil; + } + rw::Frame *f = cam->getFrame(); + if(f){ + cam->setFrame(nil); + f->destroy(); + } + cam->world->removeCamera(cam); + cam->destroy(); +} + +void +RenderEnvMapScene(void) +{ + CRenderer::RenderRoads(); + CRenderer::RenderEverythingBarRoads(); + CRenderer::RenderFadingInEntities(); +} + +void +EnvMapRender(void) +{ + if(VehiclePipeSwitch != VEHICLEPIPE_NEO) + return; + + RwCameraEndUpdate(Scene.camera); + + // Neo does this differently, but i'm not quite convinced it's much better + rw::V3d camPos = FindPlayerCoors(); + EnvMapCam->getFrame()->matrix.pos = camPos; + EnvMapCam->getFrame()->transform(&EnvMapCam->getFrame()->matrix, rw::COMBINEREPLACE); + + rw::RGBA skycol; + skycol.red = CTimeCycle::GetSkyBottomRed(); + skycol.green = CTimeCycle::GetSkyBottomGreen(); + skycol.blue = CTimeCycle::GetSkyBottomBlue(); + skycol.alpha = 255; + EnvMapCam->clear(&skycol, rwCAMERACLEARZ|rwCAMERACLEARIMAGE); + RwCameraBeginUpdate(EnvMapCam); + bRenderingEnvMap = true; + RenderEnvMapScene(); + bRenderingEnvMap = false; + + if(EnvMaskTex){ + rw::SetRenderState(rw::VERTEXALPHA, TRUE); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDZERO); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDSRCCOLOR); + rw::SetRenderStatePtr(rw::TEXTURERASTER, EnvMaskTex->raster); + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, EnvScreenQuad, 4, QuadIndices, 6); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDSRCALPHA); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDINVSRCALPHA); + } + RwCameraEndUpdate(EnvMapCam); + + + RwCameraBeginUpdate(Scene.camera); + + // debug env map +// rw::SetRenderStatePtr(rw::TEXTURERASTER, EnvMapTex->raster); +// rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, EnvScreenQuad, 4, QuadIndices, 6); +} + +static void +EnvMapInit(void) +{ + if(neoTxd) + EnvMaskTex = neoTxd->find("CarReflectionMask"); + + EnvMapCam = CreateEnvMapCam(Scene.world); + + int width = EnvMapCam->frameBuffer->width; + int height = EnvMapCam->frameBuffer->height; + float screenZ = RwIm2DGetNearScreenZ(); + float recipZ = 1.0f/EnvMapCam->nearPlane; + + EnvScreenQuad[0].setScreenX(0.0f); + EnvScreenQuad[0].setScreenY(0.0f); + EnvScreenQuad[0].setScreenZ(screenZ); + EnvScreenQuad[0].setCameraZ(EnvMapCam->nearPlane); + EnvScreenQuad[0].setRecipCameraZ(recipZ); + EnvScreenQuad[0].setColor(255, 255, 255, 255); + EnvScreenQuad[0].setU(0.0f, recipZ); + EnvScreenQuad[0].setV(0.0f, recipZ); + + EnvScreenQuad[1].setScreenX(0.0f); + EnvScreenQuad[1].setScreenY(height); + EnvScreenQuad[1].setScreenZ(screenZ); + EnvScreenQuad[1].setCameraZ(EnvMapCam->nearPlane); + EnvScreenQuad[1].setRecipCameraZ(recipZ); + EnvScreenQuad[1].setColor(255, 255, 255, 255); + EnvScreenQuad[1].setU(0.0f, recipZ); + EnvScreenQuad[1].setV(1.0f, recipZ); + + EnvScreenQuad[2].setScreenX(width); + EnvScreenQuad[2].setScreenY(height); + EnvScreenQuad[2].setScreenZ(screenZ); + EnvScreenQuad[2].setCameraZ(EnvMapCam->nearPlane); + EnvScreenQuad[2].setRecipCameraZ(recipZ); + EnvScreenQuad[2].setColor(255, 255, 255, 255); + EnvScreenQuad[2].setU(1.0f, recipZ); + EnvScreenQuad[2].setV(1.0f, recipZ); + + EnvScreenQuad[3].setScreenX(width); + EnvScreenQuad[3].setScreenY(0.0f); + EnvScreenQuad[3].setScreenZ(screenZ); + EnvScreenQuad[3].setCameraZ(EnvMapCam->nearPlane); + EnvScreenQuad[3].setRecipCameraZ(recipZ); + EnvScreenQuad[3].setColor(255, 255, 255, 255); + EnvScreenQuad[3].setU(1.0f, recipZ); + EnvScreenQuad[3].setV(0.0f, recipZ); +} + +static void +EnvMapShutdown(void) +{ + EnvMapTex->raster = nil; + EnvMapTex->destroy(); + EnvMapTex = nil; + DestroyCam(EnvMapCam); + EnvMapCam = nil; +} + +/* + * Tweak values + */ + +#define INTERP_SETUP \ + int h1 = CClock::GetHours(); \ + int h2 = (h1+1)%24; \ + int w1 = CWeather::OldWeatherType; \ + int w2 = CWeather::NewWeatherType; \ + float timeInterp = (CClock::GetSeconds()/60.0f + CClock::GetMinutes())/60.0f; \ + float c0 = (1.0f-timeInterp)*(1.0f-CWeather::InterpolationValue); \ + float c1 = timeInterp*(1.0f-CWeather::InterpolationValue); \ + float c2 = (1.0f-timeInterp)*CWeather::InterpolationValue; \ + float c3 = timeInterp*CWeather::InterpolationValue; +#define INTERP(v) v[h1][w1]*c0 + v[h2][w1]*c1 + v[h1][w2]*c2 + v[h2][w2]*c3; +#define INTERPF(v,f) v[h1][w1].f*c0 + v[h2][w1].f*c1 + v[h1][w2].f*c2 + v[h2][w2].f*c3; + +InterpolatedFloat::InterpolatedFloat(float init) +{ + curInterpolator = 61; // compared against second + for(int h = 0; h < 24; h++) + for(int w = 0; w < NUMWEATHERS; w++) + data[h][w] = init; +} + +void +InterpolatedFloat::Read(char *s, int line, int field) +{ + sscanf(s, "%f", &data[line][field]); +} + +float +InterpolatedFloat::Get(void) +{ + if(curInterpolator != CClock::GetSeconds()){ + INTERP_SETUP + curVal = INTERP(data); + curInterpolator = CClock::GetSeconds(); + } + return curVal; +} + +InterpolatedColor::InterpolatedColor(const Color &init) +{ + curInterpolator = 61; // compared against second + for(int h = 0; h < 24; h++) + for(int w = 0; w < NUMWEATHERS; w++) + data[h][w] = init; +} + +void +InterpolatedColor::Read(char *s, int line, int field) +{ + int r, g, b, a; + sscanf(s, "%i, %i, %i, %i", &r, &g, &b, &a); + data[line][field] = Color(r/255.0f, g/255.0f, b/255.0f, a/255.0f); +} + +Color +InterpolatedColor::Get(void) +{ + if(curInterpolator != CClock::GetSeconds()){ + INTERP_SETUP + curVal.r = INTERPF(data, r); + curVal.g = INTERPF(data, g); + curVal.b = INTERPF(data, b); + curVal.a = INTERPF(data, a); + curInterpolator = CClock::GetSeconds(); + } + return curVal; +} + +void +InterpolatedLight::Read(char *s, int line, int field) +{ + int r, g, b, a; + sscanf(s, "%i, %i, %i, %i", &r, &g, &b, &a); + data[line][field] = Color(r/255.0f, g/255.0f, b/255.0f, a/100.0f); +} + +char* +ReadTweakValueTable(char *fp, InterpolatedValue &interp) +{ + char buf[24], *p; + int c; + int line, field; + + line = 0; + c = *fp++; + while(c != '\0' && line < 24){ + field = 0; + if(c != '\0' && c != '#'){ + while(c != '\0' && c != '\n' && field < NUMWEATHERS){ + p = buf; + while(c != '\0' && c == '\t') + c = *fp++; + *p++ = c; + while(c = *fp++, c != '\0' && c != '\t' && c != '\n') + *p++ = c; + *p++ = '\0'; + interp.Read(buf, line, field); + field++; + } + line++; + } + while(c != '\0' && c != '\n') + c = *fp++; + c = *fp++; + } + return fp-1; +} + + + +/* + * Neo Vehicle pipe + */ + +int32 VehiclePipeSwitch = VEHICLEPIPE_MATFX; +float VehicleShininess = 0.7f; // the default is a bit extreme +float VehicleSpecularity = 1.0f; +InterpolatedFloat Fresnel(0.4f); +InterpolatedFloat Power(18.0f); +InterpolatedLight DiffColor(Color(0.0f, 0.0f, 0.0f, 0.0f)); +InterpolatedLight SpecColor(Color(0.7f, 0.7f, 0.7f, 1.0f)); +rw::ObjPipeline *vehiclePipe; + +void +AttachVehiclePipe(rw::Atomic *atomic) +{ + atomic->pipeline = vehiclePipe; +} + +void +AttachVehiclePipe(rw::Clump *clump) +{ + FORLIST(lnk, clump->atomics) + AttachVehiclePipe(rw::Atomic::fromClump(lnk)); +} + + + +/* + * Neo World pipe + */ + +bool LightmapEnable; +float LightmapMult = 1.0f; +InterpolatedFloat WorldLightmapBlend(1.0f); +rw::ObjPipeline *worldPipe; + +void +AttachWorldPipe(rw::Atomic *atomic) +{ + atomic->pipeline = worldPipe; +} + +void +AttachWorldPipe(rw::Clump *clump) +{ + FORLIST(lnk, clump->atomics) + AttachWorldPipe(rw::Atomic::fromClump(lnk)); +} + + + + +/* + * Neo Gloss pipe + */ + +bool GlossEnable; +float GlossMult = 1.0f; +rw::ObjPipeline *glossPipe; + +rw::Texture* +GetGlossTex(rw::Material *mat) +{ + if(neoTxd == nil) + return nil; + CustomMatExt *ext = GetCustomMatExt(mat); + if(!ext->haveGloss){ + char glossname[128]; + strcpy(glossname, mat->texture->name); + strcat(glossname, "_gloss"); + ext->glossTex = neoTxd->find(glossname); + ext->haveGloss = true; + } + return ext->glossTex; +} + +void +AttachGlossPipe(rw::Atomic *atomic) +{ + atomic->pipeline = glossPipe; +} + +void +AttachGlossPipe(rw::Clump *clump) +{ + FORLIST(lnk, clump->atomics) + AttachWorldPipe(rw::Atomic::fromClump(lnk)); +} + + + +/* + * Neo Rim pipes + */ + +bool RimlightEnable; +float RimlightMult = 1.0f; +InterpolatedColor RampStart(Color(0.0f, 0.0f, 0.0f, 1.0f)); +InterpolatedColor RampEnd(Color(1.0f, 1.0f, 1.0f, 1.0f)); +InterpolatedFloat Offset(0.5f); +InterpolatedFloat Scale(1.5f); +InterpolatedFloat Scaling(2.0f); +rw::ObjPipeline *rimPipe; +rw::ObjPipeline *rimSkinPipe; + +void +AttachRimPipe(rw::Atomic *atomic) +{ + if(rw::Skin::get(atomic->geometry)) + atomic->pipeline = rimSkinPipe; + else + atomic->pipeline = rimPipe; +} + +void +AttachRimPipe(rw::Clump *clump) +{ + FORLIST(lnk, clump->atomics) + AttachRimPipe(rw::Atomic::fromClump(lnk)); +} + +/* + * High level stuff + */ + +void +CustomPipeInit(void) +{ + RwStream *stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "neo/neo.txd"); + if(stream == nil) + printf("Error: couldn't open 'neo/neo.txd'\n"); + else{ + if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil)) + neoTxd = RwTexDictionaryGtaStreamRead(stream); + RwStreamClose(stream, nil); + } + + EnvMapInit(); + + CreateVehiclePipe(); + CreateWorldPipe(); + CreateGlossPipe(); + CreateRimLightPipes(); +} + +void +CustomPipeShutdown(void) +{ + DestroyVehiclePipe(); + DestroyWorldPipe(); + DestroyGlossPipe(); + DestroyRimLightPipes(); + + EnvMapShutdown(); + + if(neoTxd){ + neoTxd->destroy(); + neoTxd = nil; + } +} + +void +CustomPipeRegister(void) +{ +#ifdef RW_OPENGL + CustomPipeRegisterGL(); +#endif + + CustomMatOffset = rw::Material::registerPlugin(sizeof(CustomMatExt), MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x80), + CustomMatCtor, nil, CustomMatCopy); +} + + +// Load textures from generic as fallback + +rw::TexDictionary *genericTxd; +rw::Texture *(*defaultFindCB)(const char *name); + +static rw::Texture* +customFindCB(const char *name) +{ + rw::Texture *res = defaultFindCB(name); + if(res == nil) + res = genericTxd->find(name); + return res; +} + +void +SetTxdFindCallback(void) +{ + int slot = CTxdStore::FindTxdSlot("generic"); + CTxdStore::AddRef(slot); + // TODO: function for this + genericTxd = CTxdStore::GetSlot(slot)->texDict; + assert(genericTxd); + if(defaultFindCB == nil) + defaultFindCB = rw::Texture::findCB; + rw::Texture::findCB = customFindCB; +} + +} + +#endif diff --git a/src/extras/custompipes.h b/src/extras/custompipes.h new file mode 100644 index 0000000..7ad239f --- /dev/null +++ b/src/extras/custompipes.h @@ -0,0 +1,145 @@ +#pragma once + +#ifdef LIBRW +#ifdef EXTENDED_PIPELINES + +namespace CustomPipes { + + +extern rw::TexDictionary *neoTxd; + +struct CustomMatExt +{ + rw::Texture *glossTex; + bool haveGloss; +}; +extern rw::int32 CustomMatOffset; +inline CustomMatExt *GetCustomMatExt(rw::Material *mat) { + return PLUGINOFFSET(CustomMatExt, mat, CustomMatOffset); +} + + +struct Color +{ + float r, g, b, a; + Color(void) {} + Color(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) {} +}; + +class InterpolatedValue +{ +public: + virtual void Read(char *s, int line, int field) = 0; +}; + +class InterpolatedFloat : public InterpolatedValue +{ +public: + float data[24][NUMWEATHERS]; + float curInterpolator; + float curVal; + + InterpolatedFloat(float init); + void Read(char *s, int line, int field); + float Get(void); +}; + +class InterpolatedColor : public InterpolatedValue +{ +public: + Color data[24][NUMWEATHERS]; + float curInterpolator; + Color curVal; + + InterpolatedColor(const Color &init); + void Read(char *s, int line, int field); + Color Get(void); +}; + +class InterpolatedLight : public InterpolatedColor +{ +public: + InterpolatedLight(const Color &init) : InterpolatedColor(init) {} + void Read(char *s, int line, int field); +}; + +char *ReadTweakValueTable(char *fp, InterpolatedValue &interp); + + + + + +void CustomPipeRegister(void); +void CustomPipeRegisterGL(void); +void CustomPipeInit(void); +void CustomPipeShutdown(void); +void SetTxdFindCallback(void); + +extern bool bRenderingEnvMap; +extern int32 EnvMapSize; +extern rw::Camera *EnvMapCam; +extern rw::Texture *EnvMapTex; +extern rw::Texture *EnvMaskTex; +void EnvMapRender(void); + +enum { + VEHICLEPIPE_MATFX, + VEHICLEPIPE_NEO +}; +extern int32 VehiclePipeSwitch; +extern float VehicleShininess; +extern float VehicleSpecularity; +extern InterpolatedFloat Fresnel; +extern InterpolatedFloat Power; +extern InterpolatedLight DiffColor; +extern InterpolatedLight SpecColor; +extern rw::ObjPipeline *vehiclePipe; +void CreateVehiclePipe(void); +void DestroyVehiclePipe(void); +void AttachVehiclePipe(rw::Atomic *atomic); +void AttachVehiclePipe(rw::Clump *clump); + +extern bool LightmapEnable; +extern float LightmapMult; +extern InterpolatedFloat WorldLightmapBlend; +extern rw::ObjPipeline *worldPipe; +void CreateWorldPipe(void); +void DestroyWorldPipe(void); +void AttachWorldPipe(rw::Atomic *atomic); +void AttachWorldPipe(rw::Clump *clump); + +extern bool GlossEnable; +extern float GlossMult; +extern rw::ObjPipeline *glossPipe; +void CreateGlossPipe(void); +void DestroyGlossPipe(void); +void AttachGlossPipe(rw::Atomic *atomic); +void AttachGlossPipe(rw::Clump *clump); +rw::Texture *GetGlossTex(rw::Material *mat); + +extern bool RimlightEnable; +extern float RimlightMult; +extern InterpolatedColor RampStart; +extern InterpolatedColor RampEnd; +extern InterpolatedFloat Offset; +extern InterpolatedFloat Scale; +extern InterpolatedFloat Scaling; +extern rw::ObjPipeline *rimPipe; +extern rw::ObjPipeline *rimSkinPipe; +void CreateRimLightPipes(void); +void DestroyRimLightPipes(void); +void AttachRimPipe(rw::Atomic *atomic); +void AttachRimPipe(rw::Clump *clump); + +} + +#endif + +namespace WorldRender{ +extern int numBlendInsts[3]; +void AtomicFirstPass(RpAtomic *atomic, int pass); +void AtomicFullyTransparent(RpAtomic *atomic, int pass, int fadeAlpha); +void RenderBlendPass(int pass); +} + +#endif diff --git a/src/extras/custompipes_d3d9.cpp b/src/extras/custompipes_d3d9.cpp new file mode 100644 index 0000000..8b98444 --- /dev/null +++ b/src/extras/custompipes_d3d9.cpp @@ -0,0 +1,728 @@ +#define WITHD3D +#include "common.h" + +#ifdef RW_D3D9 +#include "main.h" +#include "RwHelper.h" +#include "Lights.h" +#include "Timecycle.h" +#include "FileMgr.h" +#include "Clock.h" +#include "Weather.h" +#include "TxdStore.h" +#include "Renderer.h" +#include "World.h" +#include "custompipes.h" + +#ifdef EXTENDED_PIPELINES + +#ifndef LIBRW +#error "Need librw for EXTENDED_PIPELINES" +#endif + +extern RwTexture *gpWhiteTexture; // from vehicle model info + +namespace CustomPipes { + +enum { + // rim pipe + VSLOC_boneMatrices = rw::d3d::VSLOC_afterLights, + VSLOC_viewVec = VSLOC_boneMatrices + 64*3, + VSLOC_rampStart, + VSLOC_rampEnd, + VSLOC_rimData, + + // gloss pipe + VSLOC_eye = rw::d3d::VSLOC_afterLights, + + VSLOC_reflProps, + VSLOC_specLights +}; + +/* + * Neo Vehicle pipe + */ + +static void *neoVehicle_VS; +static void *neoVehicle_PS; + +void +uploadSpecLights(void) +{ + struct VsLight { + rw::RGBAf color; + float pos[4]; // unused + rw::V3d dir; + float power; + } specLights[1 + NUMEXTRADIRECTIONALS]; + memset(specLights, 0, sizeof(specLights)); + for(int i = 0; i < 1+NUMEXTRADIRECTIONALS; i++) + specLights[i].power = 1.0f; + float power = Power.Get(); + Color speccol = SpecColor.Get(); + specLights[0].color.red = speccol.r; + specLights[0].color.green = speccol.g; + specLights[0].color.blue = speccol.b; + specLights[0].dir = pDirect->getFrame()->getLTM()->at; + specLights[0].power = power; + for(int i = 0; i < NUMEXTRADIRECTIONALS; i++){ + if(pExtraDirectionals[i]->getFlags() & rw::Light::LIGHTATOMICS){ + specLights[1+i].color = pExtraDirectionals[i]->color; + specLights[1+i].dir = pExtraDirectionals[i]->getFrame()->getLTM()->at; + specLights[1+i].power = power*2.0f; + } + } + rw::d3d::d3ddevice->SetVertexShaderConstantF(VSLOC_specLights, (float*)&specLights, 3*(1 + NUMEXTRADIRECTIONALS)); +} + +void +vehicleRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + // TODO: make this less of a kludge + if(VehiclePipeSwitch == VEHICLEPIPE_MATFX){ + matFXGlobals.pipelines[rw::platform]->render(atomic); + return; + } + + int vsBits; + rw::uint32 flags = atomic->geometry->flags; + setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride); + setIndices(header->indexBuffer); + setVertexDeclaration(header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadSpecLights(); + uploadMatrices(atomic->getFrame()->getLTM()); + + setVertexShader(neoVehicle_VS); + + V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos; + d3ddevice->SetVertexShaderConstantF(VSLOC_eye, (float*)&eyePos, 1); + + float reflProps[4]; + reflProps[0] = Fresnel.Get(); + reflProps[1] = SpecColor.Get().a; + + d3d::setTexture(1, EnvMapTex); + + SetRenderState(SRCBLEND, BLENDONE); + + InstanceData *inst = header->inst; + for(rw::uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + reflProps[2] = m->surfaceProps.specular * VehicleShininess; + reflProps[3] = m->surfaceProps.specular == 0.0f ? 0.0f : VehicleSpecularity; + d3ddevice->SetVertexShaderConstantF(VSLOC_reflProps, reflProps, 1); + + setMaterial(flags, m->color, m->surfaceProps); + + if(m->texture) + d3d::setTexture(0, m->texture); + else + d3d::setTexture(0, gpWhiteTexture); + setPixelShader(neoVehicle_PS); + + drawInst(header, inst); + inst++; + } + d3d::setTexture(1, nil); + + SetRenderState(SRCBLEND, BLENDSRCALPHA); +} + +void +CreateVehiclePipe(void) +{ + if(CFileMgr::LoadFile("neo/carTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/carTweakingTable.dat'\n"); + else{ + char *fp = (char*)work_buff; + fp = ReadTweakValueTable(fp, Fresnel); + fp = ReadTweakValueTable(fp, Power); + fp = ReadTweakValueTable(fp, DiffColor); + fp = ReadTweakValueTable(fp, SpecColor); + } + +#include "shaders/obj/neoVehicle_VS.inc" + neoVehicle_VS = rw::d3d::createVertexShader(neoVehicle_VS_cso); + assert(neoVehicle_VS); + +#include "shaders/obj/neoVehicle_PS.inc" + neoVehicle_PS = rw::d3d::createPixelShader(neoVehicle_PS_cso); + assert(neoVehicle_PS); + + + rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create(); + pipe->instanceCB = rw::d3d9::defaultInstanceCB; + pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB; + pipe->renderCB = vehicleRenderCB; + vehiclePipe = pipe; +} + +void +DestroyVehiclePipe(void) +{ + rw::d3d::destroyVertexShader(neoVehicle_VS); + neoVehicle_VS = nil; + + rw::d3d::destroyPixelShader(neoVehicle_PS); + neoVehicle_PS = nil; + + ((rw::d3d9::ObjPipeline*)vehiclePipe)->destroy(); + vehiclePipe = nil; +} + + + +/* + * Neo World pipe + */ + +static void *neoWorld_VS; +static void *neoWorldIII_PS; + +static void +worldRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + if(!LightmapEnable){ + defaultRenderCB_Shader(atomic, header); + return; + } + + int vsBits; + setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride); + setIndices(header->indexBuffer); + setVertexDeclaration(header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + + float lightfactor[4]; + + InstanceData *inst = header->inst; + for(rw::uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + if(MatFX::getEffects(m) == MatFX::DUAL){ + setVertexShader(neoWorld_VS); + + MatFX *matfx = MatFX::get(m); + Texture *dualtex = matfx->getDualTexture(); + if(dualtex == nil) + goto notex; + d3d::setTexture(1, dualtex); + lightfactor[0] = lightfactor[1] = lightfactor[2] = WorldLightmapBlend.Get()*LightmapMult; + }else{ + notex: + setVertexShader(default_amb_VS); + + d3d::setTexture(1, nil); + lightfactor[0] = lightfactor[1] = lightfactor[2] = 0.0f; + } + lightfactor[3] = m->color.alpha/255.0f; + d3d::setTexture(0, m->texture); + d3ddevice->SetPixelShaderConstantF(1, lightfactor, 1); + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + RGBA color = { 255, 255, 255, m->color.alpha }; + setMaterial(color, m->surfaceProps); + + if(m->texture) + d3d::setTexture(0, m->texture); + else + d3d::setTexture(0, gpWhiteTexture); + setPixelShader(neoWorldIII_PS); + + drawInst(header, inst); + inst++; + } + d3d::setTexture(1, nil); +} + +void +CreateWorldPipe(void) +{ + if(CFileMgr::LoadFile("neo/worldTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/worldTweakingTable.dat'\n"); + else + ReadTweakValueTable((char*)work_buff, WorldLightmapBlend); + +#include "shaders/obj/default_UV2_VS.inc" + neoWorld_VS = rw::d3d::createVertexShader(default_UV2_VS_cso); + assert(neoWorld_VS); + +#include "shaders/obj/neoWorldIII_PS.inc" + neoWorldIII_PS = rw::d3d::createPixelShader(neoWorldIII_PS_cso); + assert(neoWorldIII_PS); + + + rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create(); + pipe->instanceCB = rw::d3d9::defaultInstanceCB; + pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB; + pipe->renderCB = worldRenderCB; + worldPipe = pipe; +} + +void +DestroyWorldPipe(void) +{ + rw::d3d::destroyVertexShader(neoWorld_VS); + neoWorld_VS = nil; + rw::d3d::destroyPixelShader(neoWorldIII_PS); + neoWorldIII_PS = nil; + + + ((rw::d3d9::ObjPipeline*)worldPipe)->destroy(); + worldPipe = nil; +} + + + + +/* + * Neo Gloss pipe + */ + +static void *neoGloss_VS; +static void *neoGloss_PS; + +static void +glossRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header) +{ + worldRenderCB(atomic, header); + + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + if(!GlossEnable) + return; + + setVertexShader(neoGloss_VS); + setPixelShader(neoGloss_PS); + + V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos; + d3ddevice->SetVertexShaderConstantF(VSLOC_eye, (float*)&eyePos, 1); + d3ddevice->SetPixelShaderConstantF(1, (float*)&GlossMult, 1); + + SetRenderState(VERTEXALPHA, TRUE); + SetRenderState(SRCBLEND, BLENDONE); + SetRenderState(DESTBLEND, BLENDONE); + SetRenderState(ZWRITEENABLE, FALSE); + SetRenderState(ALPHATESTFUNC, ALPHAALWAYS); + + InstanceData *inst = header->inst; + for(rw::uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + if(m->texture){ + Texture *tex = GetGlossTex(m); + if(tex){ + d3d::setTexture(0, tex); + drawInst(header, inst); + } + } + inst++; + } + + SetRenderState(ZWRITEENABLE, TRUE); + SetRenderState(ALPHATESTFUNC, ALPHAGREATEREQUAL); + SetRenderState(SRCBLEND, BLENDSRCALPHA); + SetRenderState(DESTBLEND, BLENDINVSRCALPHA); +} + +void +CreateGlossPipe(void) +{ +#include "shaders/obj/neoGloss_VS.inc" + neoGloss_VS = rw::d3d::createVertexShader(neoGloss_VS_cso); + assert(neoGloss_VS); + +#include "shaders/obj/neoGloss_PS.inc" + neoGloss_PS = rw::d3d::createPixelShader(neoGloss_PS_cso); + assert(neoGloss_PS); + + + rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create(); + pipe->instanceCB = rw::d3d9::defaultInstanceCB; + pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB; + pipe->renderCB = glossRenderCB; + glossPipe = pipe; +} + +void +DestroyGlossPipe(void) +{ + rw::d3d::destroyVertexShader(neoGloss_VS); + neoGloss_VS = nil; + + rw::d3d::destroyPixelShader(neoGloss_PS); + neoGloss_PS = nil; + + ((rw::d3d9::ObjPipeline*)glossPipe)->destroy(); + glossPipe = nil; +} + + + +/* + * Neo Rim pipes + */ + +static void *neoRim_VS; +static void *neoRimSkin_VS; + +static void +uploadRimData(bool enable) +{ + using namespace rw; + using namespace rw::d3d; + + V3d viewVec = rw::engine->currentCamera->getFrame()->getLTM()->at; + d3ddevice->SetVertexShaderConstantF(VSLOC_viewVec, (float*)&viewVec, 1); + float rimData[4]; + rimData[0] = Offset.Get(); + rimData[1] = Scale.Get(); + if(enable) + rimData[2] = Scaling.Get()*RimlightMult; + else + rimData[2] = 0.0f; + rimData[3] = 0.0f; + d3ddevice->SetVertexShaderConstantF(VSLOC_rimData, rimData, 1); + Color col = RampStart.Get(); + d3ddevice->SetVertexShaderConstantF(VSLOC_rampStart, (float*)&col, 1); + col = RampEnd.Get(); + d3ddevice->SetVertexShaderConstantF(VSLOC_rampEnd, (float*)&col, 1); +} + +static void +rimRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + if(!RimlightEnable){ + defaultRenderCB_Shader(atomic, header); + return; + } + + int vsBits; + rw::uint32 flags = atomic->geometry->flags; + setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride); + setIndices(header->indexBuffer); + setVertexDeclaration(header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + setVertexShader(neoRim_VS); + + uploadRimData(atomic->geometry->flags & Geometry::LIGHT); + + InstanceData *inst = header->inst; + for(rw::uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + setMaterial(flags, m->color, m->surfaceProps); + + if(m->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(header, inst); + inst++; + } +} + +static void +rimSkinRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + if(!RimlightEnable){ + skinRenderCB(atomic, header); + return; + } + + int vsBits; + rw::uint32 flags = atomic->geometry->flags; + setStreamSource(0, (IDirect3DVertexBuffer9*)header->vertexStream[0].vertexBuffer, + 0, header->vertexStream[0].stride); + setIndices((IDirect3DIndexBuffer9*)header->indexBuffer); + setVertexDeclaration((IDirect3DVertexDeclaration9*)header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + uploadSkinMatrices(atomic); + + setVertexShader(neoRimSkin_VS); + + uploadRimData(atomic->geometry->flags & Geometry::LIGHT); + + InstanceData *inst = header->inst; + for(rw::uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + setMaterial(flags, m->color, m->surfaceProps); + + if(inst->material->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(header, inst); + inst++; + } +} + +void +CreateRimLightPipes(void) +{ + if(CFileMgr::LoadFile("neo/rimTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/rimTweakingTable.dat'\n"); + else{ + char *fp = (char*)work_buff; + fp = ReadTweakValueTable(fp, RampStart); + fp = ReadTweakValueTable(fp, RampEnd); + fp = ReadTweakValueTable(fp, Offset); + fp = ReadTweakValueTable(fp, Scale); + fp = ReadTweakValueTable(fp, Scaling); + } + + +#include "shaders/obj/neoRim_VS.inc" + neoRim_VS = rw::d3d::createVertexShader(neoRim_VS_cso); + assert(neoRim_VS); + +#include "shaders/obj/neoRimSkin_VS.inc" + neoRimSkin_VS = rw::d3d::createVertexShader(neoRimSkin_VS_cso); + assert(neoRimSkin_VS); + + + rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create(); + pipe->instanceCB = rw::d3d9::defaultInstanceCB; + pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB; + pipe->renderCB = rimRenderCB; + rimPipe = pipe; + + pipe = rw::d3d9::ObjPipeline::create(); + pipe->instanceCB = rw::d3d9::skinInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = rimSkinRenderCB; + rimSkinPipe = pipe; +} + +void +DestroyRimLightPipes(void) +{ + rw::d3d::destroyVertexShader(neoRim_VS); + neoRim_VS = nil; + + rw::d3d::destroyVertexShader(neoRimSkin_VS); + neoRimSkin_VS = nil; + + ((rw::d3d9::ObjPipeline*)rimPipe)->destroy(); + rimPipe = nil; + + ((rw::d3d9::ObjPipeline*)rimSkinPipe)->destroy(); + rimSkinPipe = nil; +} + +} + +#endif + +#ifdef NEW_RENDERER +#ifndef LIBRW +#error "Need librw for NEW_PIPELINES" +#endif + +namespace WorldRender +{ + +struct BuildingInst +{ + rw::RawMatrix combinedMat; + rw::d3d9::InstanceDataHeader *instHeader; + uint32 cullMode; + uint8 fadeAlpha; + bool lighting; +}; +BuildingInst blendInsts[3][2000]; +int numBlendInsts[3]; + +static RwRGBAReal black; + +static void +SetMatrix(BuildingInst *building, rw::Matrix *worldMat) +{ + using namespace rw; + RawMatrix world, worldview; + Camera *cam = engine->currentCamera; + convMatrix(&world, worldMat); + RawMatrix::mult(&worldview, &world, &cam->devView); + RawMatrix::mult(&building->combinedMat, &worldview, &cam->devProj); +} + +static bool +IsTextureTransparent(RwTexture *tex) +{ + if(tex == nil || tex->raster == nil) + return false; + return PLUGINOFFSET(rw::d3d::D3dRaster, tex->raster, rw::d3d::nativeRasterOffset)->hasAlpha; +} + +// Render all opaque meshes and put atomics that needs blending +// into the deferred list. +void +AtomicFirstPass(RpAtomic *atomic, int pass) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]]; + + atomic->getPipeline()->instance(atomic); + building->instHeader = (d3d9::InstanceDataHeader*)atomic->geometry->instData; + assert(building->instHeader != nil); + assert(building->instHeader->platform == PLATFORM_D3D9); + building->fadeAlpha = 255; + building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT); + building->cullMode = rw::GetRenderState(rw::CULLMODE); + rw::uint32 flags = atomic->geometry->flags; + + bool setupDone = false; + bool defer = false; + SetMatrix(building, atomic->getFrame()->getLTM()); + + InstanceData *inst = building->instHeader->inst; + for(rw::uint32 i = 0; i < building->instHeader->numMeshes; i++, inst++){ + Material *m = inst->material; + + if(inst->vertexAlpha || m->color.alpha != 255 || + IsTextureTransparent(m->texture)){ + defer = true; + continue; + } + + // alright we're rendering this atomic + if(!setupDone){ + rw::SetRenderState(rw::CULLMODE, building->cullMode); + setStreamSource(0, building->instHeader->vertexStream[0].vertexBuffer, 0, building->instHeader->vertexStream[0].stride); + setIndices(building->instHeader->indexBuffer); + setVertexDeclaration(building->instHeader->vertexDeclaration); + setVertexShader(default_amb_VS); + d3ddevice->SetVertexShaderConstantF(VSLOC_combined, (float*)&building->combinedMat, 4); + if(building->lighting) + setAmbient(pAmbient->color); + else + setAmbient(black); + setupDone = true; + } + + setMaterial(flags, m->color, m->surfaceProps); + + if(m->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(building->instHeader, inst); + } + if(defer) + numBlendInsts[pass]++; +} + +void +AtomicFullyTransparent(RpAtomic *atomic, int pass, int fadeAlpha) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]]; + + atomic->getPipeline()->instance(atomic); + building->instHeader = (d3d9::InstanceDataHeader*)atomic->geometry->instData; + assert(building->instHeader != nil); + assert(building->instHeader->platform == PLATFORM_D3D9); + building->fadeAlpha = fadeAlpha; + building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT); + building->cullMode = rw::GetRenderState(rw::CULLMODE); + SetMatrix(building, atomic->getFrame()->getLTM()); + numBlendInsts[pass]++; +} + +void +RenderBlendPass(int pass) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + setVertexShader(default_amb_VS); + + int i; + for(i = 0; i < numBlendInsts[pass]; i++){ + BuildingInst *building = &blendInsts[pass][i]; + + rw::SetRenderState(rw::CULLMODE, building->cullMode); + setStreamSource(0, building->instHeader->vertexStream[0].vertexBuffer, 0, building->instHeader->vertexStream[0].stride); + setIndices(building->instHeader->indexBuffer); + setVertexDeclaration(building->instHeader->vertexDeclaration); + d3ddevice->SetVertexShaderConstantF(VSLOC_combined, (float*)&building->combinedMat, 4); + if(building->lighting) + setAmbient(pAmbient->color); + else + setAmbient(black); + + InstanceData *inst = building->instHeader->inst; + for(rw::uint32 j = 0; j < building->instHeader->numMeshes; j++, inst++){ + Material *m = inst->material; + if(!inst->vertexAlpha && m->color.alpha == 255 && !IsTextureTransparent(m->texture) && building->fadeAlpha == 255) + continue; // already done this one + + rw::RGBA color = m->color; + color.alpha = (color.alpha * building->fadeAlpha)/255; + setMaterial(color, m->surfaceProps); // always modulate here + + if(m->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(building->instHeader, inst); + } + } +} +} +#endif + +#endif diff --git a/src/extras/custompipes_gl.cpp b/src/extras/custompipes_gl.cpp new file mode 100644 index 0000000..2b28cb5 --- /dev/null +++ b/src/extras/custompipes_gl.cpp @@ -0,0 +1,742 @@ +#include "common.h" + +#ifdef RW_OPENGL +#include "main.h" +#include "RwHelper.h" +#include "Lights.h" +#include "Timecycle.h" +#include "FileMgr.h" +#include "Clock.h" +#include "Weather.h" +#include "TxdStore.h" +#include "Renderer.h" +#include "World.h" +#include "custompipes.h" + +#ifdef EXTENDED_PIPELINES + +#ifndef LIBRW +#error "Need librw for EXTENDED_PIPELINES" +#endif + +namespace CustomPipes { + +static int32 u_viewVec; +static int32 u_rampStart; +static int32 u_rampEnd; +static int32 u_rimData; + +static int32 u_lightMap; + +static int32 u_eye; +static int32 u_reflProps; +static int32 u_specDir; +static int32 u_specColor; + +#define U(i) currentShader->uniformLocations[i] + +/* + * Neo Vehicle pipe + */ + +rw::gl3::Shader *neoVehicleShader; + +static void +uploadSpecLights(void) +{ + using namespace rw::gl3; + + rw::RGBAf colors[1 + NUMEXTRADIRECTIONALS]; + struct { + rw::V3d dir; + float power; + } dirs[1 + NUMEXTRADIRECTIONALS]; + memset(colors, 0, sizeof(colors)); + memset(dirs, 0, sizeof(dirs)); + for(int i = 0; i < 1+NUMEXTRADIRECTIONALS; i++) + dirs[i].power = 1.0f; + float power = Power.Get(); + Color speccol = SpecColor.Get(); + colors[0].red = speccol.r; + colors[0].green = speccol.g; + colors[0].blue = speccol.b; + dirs[0].dir = pDirect->getFrame()->getLTM()->at; + dirs[0].power = power; + for(int i = 0; i < NUMEXTRADIRECTIONALS; i++){ + if(pExtraDirectionals[i]->getFlags() & rw::Light::LIGHTATOMICS){ + colors[1+i] = pExtraDirectionals[i]->color; + dirs[1+i].dir = pExtraDirectionals[i]->getFrame()->getLTM()->at; + dirs[1+i].power = power*2.0f; + } + } + glUniform4fv(U(u_specDir), 1 + NUMEXTRADIRECTIONALS, (float*)&dirs); + glUniform4fv(U(u_specColor), 1 + NUMEXTRADIRECTIONALS, (float*)&colors); +} + +static void +vehicleRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::gl3; + + // TODO: make this less of a kludge + if(VehiclePipeSwitch == VEHICLEPIPE_MATFX){ + matFXGlobals.pipelines[rw::platform]->render(atomic); + return; + } + + Material *m; + + rw::uint32 flags = atomic->geometry->flags; + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + + setupVertexInput(header); + + InstanceData *inst = header->inst; + rw::int32 n = header->numMeshes; + + neoVehicleShader->use(); + + V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos; + glUniform3fv(U(u_eye), 1, (float*)&eyePos); + + uploadSpecLights(); + + float reflProps[4]; + reflProps[0] = Fresnel.Get(); + reflProps[1] = SpecColor.Get().a; + + setTexture(1, EnvMapTex); + + SetRenderState(SRCBLEND, BLENDONE); + + while(n--){ + m = inst->material; + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + reflProps[2] = m->surfaceProps.specular * VehicleShininess; + reflProps[3] = m->surfaceProps.specular == 0.0f ? 0.0f : VehicleSpecularity; + glUniform4fv(U(u_reflProps), 1, reflProps); + + drawInst(header, inst); + inst++; + } + + SetRenderState(SRCBLEND, BLENDSRCALPHA); + setTexture(1, nil); + + teardownVertexInput(header); +} + +void +CreateVehiclePipe(void) +{ + using namespace rw; + using namespace rw::gl3; + + if(CFileMgr::LoadFile("neo/carTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/carTweakingTable.dat'\n"); + else{ + char *fp = (char*)work_buff; + fp = ReadTweakValueTable(fp, Fresnel); + fp = ReadTweakValueTable(fp, Power); + fp = ReadTweakValueTable(fp, DiffColor); + fp = ReadTweakValueTable(fp, SpecColor); + } + + + { +#include "shaders/obj/neoVehicle_frag.inc" +#include "shaders/obj/neoVehicle_vert.inc" + const char *vs[] = { shaderDecl, "#define DIRECTIONALS\n", header_vert_src, neoVehicle_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, neoVehicle_frag_src, nil }; + neoVehicleShader = Shader::create(vs, fs); + assert(neoVehicleShader); + } + + + rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create(); + pipe->instanceCB = rw::gl3::defaultInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = vehicleRenderCB; + vehiclePipe = pipe; +} + +void +DestroyVehiclePipe(void) +{ + neoVehicleShader->destroy(); + neoVehicleShader = nil; + + ((rw::gl3::ObjPipeline*)vehiclePipe)->destroy(); + vehiclePipe = nil; +} + + + +/* + * Neo World pipe + */ + +rw::gl3::Shader *neoWorldShader; + +static void +worldRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::gl3; + + if(!LightmapEnable){ + gl3::defaultRenderCB(atomic, header); + return; + } + + Material *m; + + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + + setupVertexInput(header); + + InstanceData *inst = header->inst; + rw::int32 n = header->numMeshes; + + neoWorldShader->use(); + + float lightfactor[4]; + + while(n--){ + m = inst->material; + + if(MatFX::getEffects(m) == MatFX::DUAL){ + MatFX *matfx = MatFX::get(m); + Texture *dualtex = matfx->getDualTexture(); + if(dualtex == nil) + goto notex; + setTexture(1, dualtex); + lightfactor[0] = lightfactor[1] = lightfactor[2] = WorldLightmapBlend.Get()*LightmapMult; + }else{ + notex: + setTexture(1, nil); + lightfactor[0] = lightfactor[1] = lightfactor[2] = 0.0f; + } + lightfactor[3] = m->color.alpha/255.0f; + glUniform4fv(U(u_lightMap), 1, lightfactor); + + RGBA color = { 255, 255, 255, m->color.alpha }; + setMaterial(color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + drawInst(header, inst); + inst++; + } + setTexture(1, nil); + teardownVertexInput(header); +} + +void +CreateWorldPipe(void) +{ + using namespace rw; + using namespace rw::gl3; + + if(CFileMgr::LoadFile("neo/worldTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/worldTweakingTable.dat'\n"); + else + ReadTweakValueTable((char*)work_buff, WorldLightmapBlend); + + { +#include "shaders/obj/neoWorldIII_frag.inc" +#include "shaders/obj/default_UV2_vert.inc" + const char *vs[] = { shaderDecl, header_vert_src, default_UV2_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, neoWorldIII_frag_src, nil }; + neoWorldShader = Shader::create(vs, fs); + assert(neoWorldShader); + } + + + rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create(); + pipe->instanceCB = rw::gl3::defaultInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = worldRenderCB; + worldPipe = pipe; +} + +void +DestroyWorldPipe(void) +{ + neoWorldShader->destroy(); + neoWorldShader = nil; + + ((rw::gl3::ObjPipeline*)worldPipe)->destroy(); + worldPipe = nil; +} + + + + +/* + * Neo Gloss pipe + */ + +rw::gl3::Shader *neoGlossShader; + +static void +glossRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::gl3; + + worldRenderCB(atomic, header); + if(!GlossEnable) + return; + + Material *m; + + setupVertexInput(header); + + InstanceData *inst = header->inst; + rw::int32 n = header->numMeshes; + + neoGlossShader->use(); + + V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos; + glUniform3fv(U(u_eye), 1, (float*)&eyePos); + float reflProps[4]; + reflProps[0] = GlossMult; + reflProps[1] = 0.0f; + reflProps[2] = 0.0f; + reflProps[3] = 0.0f; + glUniform4fv(U(u_reflProps), 1, reflProps); + + SetRenderState(VERTEXALPHA, TRUE); + SetRenderState(SRCBLEND, BLENDONE); + SetRenderState(DESTBLEND, BLENDONE); + SetRenderState(ZWRITEENABLE, FALSE); + SetRenderState(ALPHATESTFUNC, ALPHAALWAYS); + + while(n--){ + m = inst->material; + + RGBA color = { 255, 255, 255, m->color.alpha }; + setMaterial(color, m->surfaceProps); + + if(m->texture){ + Texture *tex = GetGlossTex(m); + if(tex){ + setTexture(0, tex); + drawInst(header, inst); + } + } + inst++; + } + + SetRenderState(ZWRITEENABLE, TRUE); + SetRenderState(ALPHATESTFUNC, ALPHAGREATEREQUAL); + SetRenderState(SRCBLEND, BLENDSRCALPHA); + SetRenderState(DESTBLEND, BLENDINVSRCALPHA); + + teardownVertexInput(header); +} + +void +CreateGlossPipe(void) +{ + using namespace rw; + using namespace rw::gl3; + + { +#include "shaders/obj/neoGloss_frag.inc" +#include "shaders/obj/neoGloss_vert.inc" + const char *vs[] = { shaderDecl, header_vert_src, neoGloss_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, neoGloss_frag_src, nil }; + neoGlossShader = Shader::create(vs, fs); + assert(neoGlossShader); + } + + rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create(); + pipe->instanceCB = rw::gl3::defaultInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = glossRenderCB; + glossPipe = pipe; +} + +void +DestroyGlossPipe(void) +{ + neoGlossShader->destroy(); + neoGlossShader = nil; + + ((rw::gl3::ObjPipeline*)glossPipe)->destroy(); + glossPipe = nil; +} + + + +/* + * Neo Rim pipes + */ + +rw::gl3::Shader *neoRimShader; +rw::gl3::Shader *neoRimSkinShader; + +static void +uploadRimData(bool enable) +{ + using namespace rw; + using namespace rw::gl3; + + V3d viewVec = rw::engine->currentCamera->getFrame()->getLTM()->at; + glUniform3fv(U(u_viewVec), 1, (float*)&viewVec); + float rimData[4]; + rimData[0] = Offset.Get(); + rimData[1] = Scale.Get(); + if(enable) + rimData[2] = Scaling.Get()*RimlightMult; + else + rimData[2] = 0.0f; + rimData[3] = 0.0f; + glUniform3fv(U(u_rimData), 1, rimData); + Color col = RampStart.Get(); + glUniform4fv(U(u_rampStart), 1, (float*)&col); + col = RampEnd.Get(); + glUniform4fv(U(u_rampEnd), 1, (float*)&col); +} + +static void +rimSkinRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::gl3; + + if(!RimlightEnable){ + gl3::skinRenderCB(atomic, header); + return; + } + + Material *m; + + rw::uint32 flags = atomic->geometry->flags; + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + + setupVertexInput(header); + + InstanceData *inst = header->inst; + rw::int32 n = header->numMeshes; + + neoRimSkinShader->use(); + + uploadRimData(atomic->geometry->flags & Geometry::LIGHT); + + uploadSkinMatrices(atomic); + + while(n--){ + m = inst->material; + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + drawInst(header, inst); + inst++; + } + teardownVertexInput(header); +} + +static void +rimRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::gl3; + + if(!RimlightEnable){ + gl3::defaultRenderCB(atomic, header); + return; + } + + Material *m; + + rw::uint32 flags = atomic->geometry->flags; + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + + setupVertexInput(header); + + InstanceData *inst = header->inst; + rw::int32 n = header->numMeshes; + + neoRimShader->use(); + + uploadRimData(atomic->geometry->flags & Geometry::LIGHT); + + while(n--){ + m = inst->material; + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + drawInst(header, inst); + inst++; + } + teardownVertexInput(header); +} + +void +CreateRimLightPipes(void) +{ + using namespace rw::gl3; + + if(CFileMgr::LoadFile("neo/rimTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/rimTweakingTable.dat'\n"); + else{ + char *fp = (char*)work_buff; + fp = ReadTweakValueTable(fp, RampStart); + fp = ReadTweakValueTable(fp, RampEnd); + fp = ReadTweakValueTable(fp, Offset); + fp = ReadTweakValueTable(fp, Scale); + fp = ReadTweakValueTable(fp, Scaling); + } + + { +#include "shaders/obj/simple_frag.inc" +#include "shaders/obj/neoRimSkin_vert.inc" + const char *vs[] = { shaderDecl, "#define DIRECTIONALS\n", header_vert_src, neoRimSkin_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + neoRimSkinShader = Shader::create(vs, fs); + assert(neoRimSkinShader); + } + + { +#include "shaders/obj/simple_frag.inc" +#include "shaders/obj/neoRim_vert.inc" + const char *vs[] = { shaderDecl, "#define DIRECTIONALS\n", header_vert_src, neoRim_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + neoRimShader = Shader::create(vs, fs); + assert(neoRimShader); + } + + + rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create(); + pipe->instanceCB = rw::gl3::defaultInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = rimRenderCB; + rimPipe = pipe; + + pipe = rw::gl3::ObjPipeline::create(); + pipe->instanceCB = rw::gl3::skinInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = rimSkinRenderCB; + rimSkinPipe = pipe; +} + +void +DestroyRimLightPipes(void) +{ + neoRimShader->destroy(); + neoRimShader = nil; + + neoRimSkinShader->destroy(); + neoRimSkinShader = nil; + + ((rw::gl3::ObjPipeline*)rimPipe)->destroy(); + rimPipe = nil; + + ((rw::gl3::ObjPipeline*)rimSkinPipe)->destroy(); + rimSkinPipe = nil; +} + + + +void +CustomPipeRegisterGL(void) +{ + u_viewVec = rw::gl3::registerUniform("u_viewVec"); + u_rampStart = rw::gl3::registerUniform("u_rampStart"); + u_rampEnd = rw::gl3::registerUniform("u_rampEnd"); + u_rimData = rw::gl3::registerUniform("u_rimData"); + + u_lightMap = rw::gl3::registerUniform("u_lightMap"); + + u_eye = rw::gl3::registerUniform("u_eye"); + u_reflProps = rw::gl3::registerUniform("u_reflProps"); + u_specDir = rw::gl3::registerUniform("u_specDir"); + u_specColor = rw::gl3::registerUniform("u_specColor"); +} + + +} + +#endif + +#ifdef NEW_RENDERER +#ifndef LIBRW +#error "Need librw for NEW_PIPELINES" +#endif + +namespace WorldRender +{ + +struct BuildingInst +{ + rw::Matrix matrix; + rw::gl3::InstanceDataHeader *instHeader; + uint32 cullMode; + uint8 fadeAlpha; + bool lighting; +}; +BuildingInst blendInsts[3][2000]; +int numBlendInsts[3]; + +static RwRGBAReal black; + +static bool +IsTextureTransparent(RwTexture *tex) +{ + if(tex == nil || tex->raster == nil) + return false; + return PLUGINOFFSET(rw::gl3::Gl3Raster, tex->raster, rw::gl3::nativeRasterOffset)->hasAlpha; +} + +// Render all opaque meshes and put atomics that needs blending +// into the deferred list. +void +AtomicFirstPass(RpAtomic *atomic, int pass) +{ + using namespace rw; + using namespace rw::gl3; + + BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]]; + + atomic->getPipeline()->instance(atomic); + building->instHeader = (gl3::InstanceDataHeader*)atomic->geometry->instData; + assert(building->instHeader != nil); + assert(building->instHeader->platform == PLATFORM_GL3); + building->fadeAlpha = 255; + building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT); + building->cullMode = rw::GetRenderState(rw::CULLMODE); + rw::uint32 flags = atomic->geometry->flags; + + WorldLights lights; + lights.numAmbients = 1; + lights.numDirectionals = 0; + lights.numLocals = 0; + if(building->lighting) + lights.ambient = pAmbient->color; + else + lights.ambient = black; + + bool setupDone = false; + bool defer = false; + building->matrix = *atomic->getFrame()->getLTM(); + + InstanceData *inst = building->instHeader->inst; + for(rw::uint32 i = 0; i < building->instHeader->numMeshes; i++, inst++){ + Material *m = inst->material; + + if(inst->vertexAlpha || m->color.alpha != 255 || + IsTextureTransparent(m->texture)){ + defer = true; + continue; + } + + // alright we're rendering this atomic + if(!setupDone){ + rw::SetRenderState(rw::CULLMODE, building->cullMode); + defaultShader->use(); + setWorldMatrix(&building->matrix); + setupVertexInput(building->instHeader); + setLights(&lights); + setupDone = true; + } + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + drawInst(building->instHeader, inst); + } + teardownVertexInput(building->instHeader); + if(defer) + numBlendInsts[pass]++; +} + +void +AtomicFullyTransparent(RpAtomic *atomic, int pass, int fadeAlpha) +{ + using namespace rw; + using namespace rw::gl3; + + BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]]; + + atomic->getPipeline()->instance(atomic); + building->instHeader = (gl3::InstanceDataHeader*)atomic->geometry->instData; + assert(building->instHeader != nil); + assert(building->instHeader->platform == PLATFORM_GL3); + building->fadeAlpha = fadeAlpha; + building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT); + building->cullMode = rw::GetRenderState(rw::CULLMODE); + building->matrix = *atomic->getFrame()->getLTM(); + numBlendInsts[pass]++; +} + +void +RenderBlendPass(int pass) +{ + using namespace rw; + using namespace rw::gl3; + + defaultShader->use(); + WorldLights lights; + lights.numAmbients = 1; + lights.numDirectionals = 0; + lights.numLocals = 0; + + int i; + for(i = 0; i < numBlendInsts[pass]; i++){ + BuildingInst *building = &blendInsts[pass][i]; + + rw::SetRenderState(rw::CULLMODE, building->cullMode); + setupVertexInput(building->instHeader); + setWorldMatrix(&building->matrix); + if(building->lighting) + lights.ambient = pAmbient->color; + else + lights.ambient = black; + setLights(&lights); + + InstanceData *inst = building->instHeader->inst; + for(rw::uint32 j = 0; j < building->instHeader->numMeshes; j++, inst++){ + Material *m = inst->material; + if(!inst->vertexAlpha && m->color.alpha == 255 && !IsTextureTransparent(m->texture) && building->fadeAlpha == 255) + continue; // already done this one + + rw::RGBA color = m->color; + color.alpha = (color.alpha * building->fadeAlpha)/255; + setMaterial(color, m->surfaceProps); // always modulate here + + setTexture(0, m->texture); + + drawInst(building->instHeader, inst); + } + teardownVertexInput(building->instHeader); + } +} +} +#endif + +#endif diff --git a/src/extras/debugmenu.cpp b/src/extras/debugmenu.cpp new file mode 100644 index 0000000..533b97f --- /dev/null +++ b/src/extras/debugmenu.cpp @@ -0,0 +1,1312 @@ +#include "common.h" +#ifdef DEBUGMENU +#include "RwHelper.h" +#include "Pad.h" +#include "ControllerConfig.h" +#include "Timer.h" +#include "rtcharse.h" +#include "re3_inttypes.h" +#include "debugmenu.h" +#include + +#ifdef _WIN32 +#define snprintf _snprintf + +#define strdup _strdup +#endif + + +// Font stuff +struct Pt +{ + int x, y; +}; + +enum MenuFontStyle +{ + MENUFONT_NORMAL, + MENUFONT_SEL_ACTIVE, + MENUFONT_SEL_INACTIVE, + MENUFONT_MOUSE +}; + +RtCharset *fontStyles[4]; +RtCharsetDesc fontDesc; +int fontscale = 1; // not supported right now + +Pt +fontGetStringSize(const char *s) +{ + Pt sz = { 0, 0 }; + int x; + char c; + sz.y = fontDesc.height*fontscale; // always assume one line; + x = 0; + while(c = *s++){ + if(c == '\n'){ + sz.y += fontDesc.height*fontscale; + if(x > sz.x) + sz.x = x; + x = 0; + }else + x += fontDesc.width*fontscale; + } + if(x > sz.x) + sz.x = x; + return sz; +} + +Pt +fontPrint(const char *s, float x, float y, int style) +{ + RtCharsetPrintBuffered(fontStyles[style], s, x, y, false); + return fontGetStringSize(s); +} + +int +fontGetLen(int len) +{ + return len*fontDesc.width*fontscale; +} + + +void +createMenuFont(void) +{ + OpenCharsetSafe(); + + RwRGBA fg_normal = { 255, 255, 255, 255 }; + RwRGBA bg_normal = { 255, 255, 255, 0 }; + fontStyles[MENUFONT_NORMAL] = RtCharsetCreate(&fg_normal, &bg_normal); + assert(fontStyles[MENUFONT_NORMAL]); + + RwRGBA fg_sel_active = { 200, 200, 200, 255 }; + RwRGBA bg_sel_active = { 132, 132, 132, 255 }; + fontStyles[MENUFONT_SEL_ACTIVE] = RtCharsetCreate(&fg_sel_active, &bg_sel_active); + assert(fontStyles[MENUFONT_SEL_ACTIVE]); + + RwRGBA fg_sel_inactive = { 200, 200, 200, 255 }; + RwRGBA bg_sel_inactive = { 200, 200, 200, 0 }; + fontStyles[MENUFONT_SEL_INACTIVE] = RtCharsetCreate(&fg_sel_inactive, &bg_sel_inactive); + assert(fontStyles[MENUFONT_SEL_INACTIVE]); + + RwRGBA fg_mouse = { 255, 255, 255, 255 }; + RwRGBA bg_mouse = { 132, 132, 132, 255 }; + fontStyles[MENUFONT_MOUSE] = RtCharsetCreate(&fg_mouse, &bg_mouse); + assert(fontStyles[MENUFONT_MOUSE]); + + RtCharsetGetDesc(fontStyles[MENUFONT_NORMAL], &fontDesc); +} + +void +destroyMenuFont(void) +{ + RtCharsetDestroy(fontStyles[MENUFONT_NORMAL]); + fontStyles[MENUFONT_NORMAL] = nil; + RtCharsetDestroy(fontStyles[MENUFONT_SEL_ACTIVE]); + fontStyles[MENUFONT_SEL_ACTIVE] = nil; + RtCharsetDestroy(fontStyles[MENUFONT_SEL_INACTIVE]); + fontStyles[MENUFONT_SEL_INACTIVE] = nil; + RtCharsetDestroy(fontStyles[MENUFONT_MOUSE]); + fontStyles[MENUFONT_MOUSE] = nil; +} + + + + + + +enum EntryType +{ + MENUEMPTY = 0, + MENUSUB, + MENUVAR, + + MENUVAR_INT, + MENUVAR_FLOAT, + MENUVAR_CMD, + + MENUSCROLL // dummy +}; + +struct Menu +{ + Menu *parent; + RwRect r; + MenuEntry *entries; + int numEntries; + int maxNameWidth, maxValWidth; + + MenuEntry *findEntry(const char *entryname); + void insertEntrySorted(MenuEntry *entry); + void appendEntry(MenuEntry *entry); + + bool isScrollingUp, isScrollingDown; + int scrollStart; + int numVisible; + RwRect scrollUpR, scrollDownR; + void scroll(int off); + + int selection; + MenuEntry *selectedEntry; // updated by update + void changeSelection(int newsel); + void changeSelection(MenuEntry *e); + + void update(void); + void draw(void); + Menu(void){ memset(this, 0, sizeof(Menu)); } + ~Menu(void); +}; +extern Menu toplevel; + +struct MenuEntry_Sub : MenuEntry +{ + Menu *submenu; + + MenuEntry_Sub(const char *name, Menu *menu); + ~MenuEntry_Sub(void) { delete submenu; } +}; + +struct MenuEntry_Var : MenuEntry +{ + int maxvallen; + int vartype; + bool wrapAround; + + virtual void processInput(bool mouseOver, bool selected) = 0; + int getValWidth(void) { return maxvallen; } + virtual void getValStr(char *str, int len) = 0; + MenuEntry_Var(const char *name, int type); +}; + +struct MenuEntry_Int : MenuEntry_Var +{ + virtual void setStrings(const char **strings) = 0; + virtual int findStringLen(void) = 0; + MenuEntry_Int(const char *name); +}; + +#define INTTYPES \ + X(Int8, int8, 4, "%4" PRId8) \ + X(Int16, int16, 6, "%6" PRId16) \ + X(Int32, int32, 11, "%11" PRId32) \ + X(Int64, int64, 21, "%21" PRId64) \ + X(UInt8, uint8, 4, "%4" PRIu8) \ + X(UInt16, uint16, 6, "%6" PRIu16) \ + X(UInt32, uint32, 11, "%11" PRIu32) \ + X(UInt64, uint64, 21, "%21" PRIu64) +#define FLOATTYPES \ + X(Float32, float, 11, "%11.3f") \ + X(Float64, double, 11, "%11.3lf") + +#define X(NAME, TYPE, MAXLEN, FMT) \ +struct MenuEntry_##NAME : MenuEntry_Int \ +{ \ + TYPE *variable; \ + TYPE lowerBound, upperBound; \ + TYPE step; \ + TriggerFunc triggerFunc; \ + const char *fmt; \ + const char **strings; \ + \ + void processInput(bool mouseOver, bool selected); \ + void getValStr(char *str, int len); \ + \ + void setStrings(const char **strings); \ + int findStringLen(void); \ + MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings); \ +}; +INTTYPES +#undef X + +#define X(NAME, TYPE, MAXLEN, FMT) \ +struct MenuEntry_##NAME : MenuEntry_Var \ +{ \ + TYPE *variable; \ + TYPE lowerBound, upperBound; \ + TYPE step; \ + TriggerFunc triggerFunc; \ + const char *fmt; \ + \ + void processInput(bool mouseOver, bool selected); \ + void getValStr(char *str, int len); \ + \ + MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound); \ +}; +FLOATTYPES +#undef X + +struct MenuEntry_Cmd : MenuEntry_Var +{ + TriggerFunc triggerFunc; + + void processInput(bool mouseOver, bool selected); + void getValStr(char *str, int len); + + MenuEntry_Cmd(const char *name, TriggerFunc triggerFunc); +}; + + +Menu *findMenu(const char *name); + + + +#define MUHKEYS \ + X(leftjustdown, rsLEFT) \ + X(rightjustdown, rsRIGHT) \ + X(upjustdown, rsUP) \ + X(downjustdown, rsDOWN) \ + X(pgupjustdown, rsPGUP) \ + X(pgdnjustdown, rsPGDN) + +#define MUHBUTTONS \ + X(button1justdown, 1) \ + X(button2justdown, 2) \ + X(button3justdown, 3) + +#define REPEATDELAY 700 +#define REPEATINTERVAL 50 +#define X(var, keycode) static int var; +MUHKEYS +#undef X +static int downtime; +static int repeattime; +static int lastkeydown; +static int *keyptr; + +static int buttondown[3]; +static int lastbuttondown; +static int *buttonptr; +static int button1justdown, button2justdown, button3justdown; +static float mouseX, mouseY; + +static int menuOn; +static int menuInitialized; +static int screenWidth, screenHeight; +static RwRaster *cursor, *arrow; + +static int firstBorder = 10; +static int topBorder = 40; //10; +static int leading = 4; +static int gap = 10; +static int minwidth = 100; + +void drawMouse(void); +void drawArrow(RwRect r, int direction, int style); + +Menu toplevel; +Menu *activeMenu = &toplevel; +Menu *deepestMenu = &toplevel; +Menu *mouseOverMenu; +MenuEntry *mouseOverEntry; +MenuEntry scrollUpEntry("SCROLLUP"), scrollDownEntry("SCROLLDOWN"); // dummies + + +#define KEYJUSTDOWN(k) ControlsManager.GetIsKeyboardKeyJustDown((RsKeyCodes)k) +#define KEYDOWN(k) ControlsManager.GetIsKeyboardKeyDown((RsKeyCodes)k) +#define CTRLJUSTDOWN(key) \ + ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYJUSTDOWN((RsKeyCodes)key) || \ + (KEYJUSTDOWN(rsLCTRL) || KEYJUSTDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) +#define CTRLDOWN(key) ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) + + +bool +isMouseInRect(RwRect r) +{ + return (mouseX >= r.x && mouseX < r.x+r.w && + mouseY >= r.y && mouseY < r.y+r.h); +} + +/* + * MenuEntry + */ + +MenuEntry::MenuEntry(const char *name) +{ + this->type = MENUEMPTY; + this->name = strdup(name); + this->next = nil; + this->menu = nil; +} + +MenuEntry_Sub::MenuEntry_Sub(const char *name, Menu *menu) +: MenuEntry(name) +{ + this->type = MENUSUB; + this->submenu = menu; +} + +MenuEntry_Var::MenuEntry_Var(const char *name, int vartype) +: MenuEntry(name) +{ + this->type = MENUVAR; + this->vartype = vartype; + this->maxvallen = 0; + this->wrapAround = false; +} + +/* + * ***************************** + * MenuEntry_Int + * ***************************** + */ + +MenuEntry_Int::MenuEntry_Int(const char *name) +: MenuEntry_Var(name, MENUVAR_INT) +{ +} + +#define X(NAME, TYPE, MAXLEN, FMT) \ +int \ +MenuEntry_##NAME::findStringLen(void){ \ + TYPE i; \ + int len, maxlen = 0; \ + for(i = this->lowerBound; i <= this->upperBound; i++){ \ + len = strlen(this->strings[i-this->lowerBound]); \ + if(len > maxlen) \ + maxlen = len; \ + } \ + return maxlen; \ +} \ +void \ +MenuEntry_##NAME::processInput(bool mouseOver, bool selected) \ +{ \ + TYPE v, oldv; \ + int overflow = 0; \ + int underflow = 0; \ + \ + v = *this->variable; \ + oldv = v; \ + \ + if((selected && leftjustdown) || (mouseOver && button3justdown)){ \ + v -= this->step; \ + if(v > oldv) \ + underflow = 1; \ + } \ + if((selected && rightjustdown) || (mouseOver && button1justdown)){ \ + v += this->step; \ + if(v < oldv) \ + overflow = 1; \ + } \ + if(this->wrapAround){ \ + if(v > this->upperBound || overflow) v = this->lowerBound; \ + if(v < this->lowerBound || underflow) v = this->upperBound; \ + }else{ \ + if(v > this->upperBound || overflow) v = this->upperBound; \ + if(v < this->lowerBound || underflow) v = this->lowerBound; \ + } \ + \ + *this->variable = v; \ + if(oldv != v && this->triggerFunc) \ + this->triggerFunc(); \ +} \ +void \ +MenuEntry_##NAME::getValStr(char *str, int len) \ +{ \ + static char tmp[20]; \ + if(this->strings){ \ + snprintf(tmp, 20, "%%%ds", this->maxvallen); \ + if(*this->variable < this->lowerBound || *this->variable > this->upperBound){ \ + snprintf(str, len, "ERROR"); \ + return; \ + } \ + snprintf(str, len, tmp, this->strings[*this->variable-this->lowerBound]); \ + }else \ + snprintf(str, len, this->fmt, *this->variable); \ +} \ +void \ +MenuEntry_##NAME::setStrings(const char **strings) \ +{ \ + this->strings = strings; \ + if(this->strings) \ + this->maxvallen = findStringLen(); \ +} \ +MenuEntry_##NAME::MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings) \ +: MenuEntry_Int(name) \ +{ \ + this->variable = ptr; \ + this->step = step; \ + this->lowerBound = lowerBound; \ + this->upperBound = upperBound; \ + this->triggerFunc = triggerFunc; \ + this->maxvallen = MAXLEN; \ + this->fmt = FMT; \ + this->setStrings(strings); \ +} +INTTYPES +#undef X + +/* + * ***************************** + * MenuEntry_Float + * ***************************** + */ + +#define X(NAME, TYPE, MAXLEN, FMT) \ +MenuEntry_##NAME::MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound) \ +: MenuEntry_Var(name, MENUVAR_FLOAT) \ +{ \ + this->variable = ptr; \ + this->step = step; \ + this->lowerBound = lowerBound; \ + this->upperBound = upperBound; \ + this->triggerFunc = triggerFunc; \ + this->maxvallen = MAXLEN; \ + this->fmt = FMT; \ +} \ +void \ +MenuEntry_##NAME::getValStr(char *str, int len) \ +{ \ + snprintf(str, len, this->fmt, *this->variable); \ +} \ +void \ +MenuEntry_##NAME::processInput(bool mouseOver, bool selected) \ +{ \ + float v, oldv; \ + int overflow = 0; \ + int underflow = 0; \ + \ + v = *this->variable; \ + oldv = v; \ + \ + if((selected && leftjustdown) || (mouseOver && button3justdown)){ \ + v -= this->step; \ + if(v > oldv) \ + underflow = 1; \ + } \ + if((selected && rightjustdown) || (mouseOver && button1justdown)){ \ + v += this->step; \ + if(v < oldv) \ + overflow = 1; \ + } \ + if(this->wrapAround){ \ + if(v > this->upperBound || overflow) v = this->lowerBound; \ + if(v < this->lowerBound || underflow) v = this->upperBound; \ + }else{ \ + if(v > this->upperBound || overflow) v = this->upperBound; \ + if(v < this->lowerBound || underflow) v = this->lowerBound; \ + } \ + \ + *this->variable = v; \ + if(oldv != v && this->triggerFunc) \ + this->triggerFunc(); \ +} + +FLOATTYPES +#undef X + +/* + * ***************************** + * MenuEntry_Cmd + * ***************************** + */ + +void +MenuEntry_Cmd::processInput(bool mouseOver, bool selected) +{ + // Don't execute on button3 + if(this->triggerFunc && (selected && (leftjustdown || rightjustdown) || (mouseOver && button1justdown))) + this->triggerFunc(); +} + +void +MenuEntry_Cmd::getValStr(char *str, int len) +{ + strncpy(str, "<", len); +} + +MenuEntry_Cmd::MenuEntry_Cmd(const char *name, TriggerFunc triggerFunc) +: MenuEntry_Var(name, MENUVAR_CMD) +{ + this->maxvallen = 1; + this->triggerFunc = triggerFunc; +} + + +/* + * ***************************** + * Menu + * ***************************** + */ + +void +Menu::scroll(int off) { + if(isScrollingUp && off < 0) + scrollStart += off; + if(isScrollingDown && off > 0) + scrollStart += off; + if(scrollStart < 0) scrollStart = 0; + if(scrollStart > numEntries-numVisible) scrollStart = numEntries-numVisible; +} + +void +Menu::changeSelection(int newsel){ + selection = newsel; + if(selection < 0) selection = 0; + if(selection >= numEntries) selection = numEntries-1; + if(selection < scrollStart) scrollStart = selection; + if(selection >= scrollStart+numVisible) scrollStart = selection-numVisible+1; +} + +void +Menu::changeSelection(MenuEntry *sel) +{ + MenuEntry *e; + int i = 0; + for(e = this->entries; e; e = e->next){ + if(e == sel){ + this->selection = i; + this->selectedEntry = sel; + break; + } + i++; + } +} + + + +MenuEntry* +Menu::findEntry(const char *entryname) +{ + MenuEntry *m; + for(m = this->entries; m; m = m->next) + if(strcmp(entryname, m->name) == 0) + return m; + return nil; +} + +void +Menu::insertEntrySorted(MenuEntry *entry) +{ + MenuEntry **mp; + int cmp; + for(mp = &this->entries; *mp; mp = &(*mp)->next){ + cmp = strcmp(entry->name, (*mp)->name); + if(cmp == 0) + return; + if(cmp < 0) + break; + } + entry->next = *mp; + *mp = entry; + entry->menu = this; + this->numEntries++; +} + +void +Menu::appendEntry(MenuEntry *entry) +{ + MenuEntry **mp; + for(mp = &this->entries; *mp; mp = &(*mp)->next); + entry->next = *mp; + *mp = entry; + entry->menu = this; + this->numEntries++; +} + +void +Menu::update(void) +{ + int i; + int x, y; + Pt sz; + MenuEntry *e; + int onscreen; + x = this->r.x; + y = this->r.y + 18; + int end = this->r.y+this->r.h - 18; + this->numVisible = 0; + + deepestMenu = this; + + int bottomy = end; + onscreen = 1; + i = 0; + this->maxNameWidth = 0; + this->maxValWidth = 0; + this->isScrollingUp = this->scrollStart > 0; + this->isScrollingDown = false; + this->selectedEntry = nil; + for(e = this->entries; e; e = e->next){ + sz = fontGetStringSize(e->name); + e->r.x = x; + e->r.y = y; + e->r.w = sz.x; + e->r.h = sz.y; + + if(i == this->selection) + this->selectedEntry = e; + + if(i >= this->scrollStart) + y += sz.y + leading*fontscale; + if(y >= end){ + this->isScrollingDown = true; + onscreen = 0; + }else + bottomy = y; + if(i >= this->scrollStart && onscreen) + this->numVisible++; + + if(e->type == MENUVAR){ + int valwidth = fontGetLen(((MenuEntry_Var*)e)->getValWidth()); + if(valwidth > maxValWidth) + maxValWidth = valwidth; + } + if(e->r.w > maxNameWidth) + maxNameWidth = e->r.w; + i++; + } + if(this->r.w < maxNameWidth + maxValWidth + gap*fontscale) + this->r.w = maxNameWidth + maxValWidth + gap*fontscale; + + this->scrollUpR = this->r; + this->scrollUpR.h = 16; + this->scrollDownR = this->scrollUpR; + this->scrollDownR.y = bottomy; + + // Update active submenu + if(this->selectedEntry && this->selectedEntry->type == MENUSUB){ + Menu *submenu = ((MenuEntry_Sub*)this->selectedEntry)->submenu; + submenu->r.x = this->r.x+this->r.w + 10; + submenu->r.y = this->r.y; + submenu->r.w = minwidth; // update menu will expand + submenu->r.h = this->r.h; + submenu->update(); + } +} + +void +Menu::draw(void) +{ + static char val[100]; + int i; + MenuEntry *e; + i = 0; + for(e = this->entries; e; e = e->next){ + if(i >= this->scrollStart+this->numVisible) + break; + if(i >= this->scrollStart){ + int style = MENUFONT_NORMAL; + if(i == this->selection) + style = this == activeMenu ? MENUFONT_SEL_ACTIVE : MENUFONT_SEL_INACTIVE; + if(style != MENUFONT_SEL_ACTIVE && e == mouseOverEntry) + style = MENUFONT_MOUSE; + fontPrint(e->name, e->r.x, e->r.y, style); + if(e->type == MENUVAR){ + int valw = fontGetLen(((MenuEntry_Var*)e)->getValWidth()); + ((MenuEntry_Var*)e)->getValStr(val, 100); + fontPrint(val, e->r.x+this->r.w-valw, e->r.y, style); + } + } + i++; + } + + if(this->isScrollingUp) + drawArrow(this->scrollUpR, -1, isMouseInRect(this->scrollUpR)); + if(this->isScrollingDown) + drawArrow(this->scrollDownR, 1, isMouseInRect(this->scrollDownR)); + + if(this->selectedEntry && this->selectedEntry->type == MENUSUB) + ((MenuEntry_Sub*)this->selectedEntry)->submenu->draw(); +} + +Menu::~Menu(void) +{ + MenuEntry *e, *next; + for(e = entries; e; e = next){ + next = e->next; + delete e; + } + memset(this, 0, sizeof(Menu)); +} + +Menu* +findMenu(const char *name) +{ + Menu *m; + MenuEntry *e; + char *tmppath = strdup(name); + char *next, *curname; + + curname = tmppath; + next = curname; + + m = &toplevel; + while(*next){ + curname = next; + while(*next){ + if(*next == '|'){ + *next++ = '\0'; + break; + } + next++; + } + e = m->findEntry(curname); + if(e){ + // return an error if the entry exists but isn't a menu + if(e->type != MENUSUB){ + free(tmppath); + return nil; + } + m = ((MenuEntry_Sub*)e)->submenu; + }else{ + // Create submenus that don't exist yet + Menu *submenu = new Menu(); + submenu->parent = m; + MenuEntry *me = new MenuEntry_Sub(curname, submenu); + // Don't sort submenus outside the toplevel menu + if(m == &toplevel) + m->insertEntrySorted(me); + else + m->appendEntry(me); + m = submenu; + } + } + + free(tmppath); + return m; +} + +/* + * **************** + * debug menu + * **************** + */ + +static uint8 cursorPx[] = { +#include "cursor.inc" +}; + +static uint8 arrowPx[] = { +#include "arrow.inc" +}; + +void +DebugMenuInit(void) +{ + createMenuFont(); + + RwInt32 w, h, d, flags; + RwImage *img = RwImageCreate(16, 16, 32); + assert(img); + RwImageSetPixels(img, cursorPx); + RwImageSetStride(img, RwImageGetWidth(img)*4); + RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &w, &h, &d, &flags); + cursor = RwRasterCreate(w, h, d, flags); + cursor = RwRasterSetFromImage(cursor, img); + assert(cursor); + RwImageDestroy(img); + + img = RwImageCreate(32, 16, 32); + assert(img); + RwImageSetPixels(img, arrowPx); + RwImageSetStride(img, RwImageGetWidth(img)*4); + RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &w, &h, &d, &flags); + arrow = RwRasterCreate(w, h, d, flags); + arrow = RwRasterSetFromImage(arrow, img); + assert(arrow); + RwImageDestroy(img); + + + menuInitialized = true; +} + +void +DebugMenuShutdown(void) +{ + if(menuInitialized){ + destroyMenuFont(); + RwRasterDestroy(cursor); + cursor = nil; + RwRasterDestroy(arrow); + arrow = nil; + + toplevel.~Menu(); + new (&toplevel) Menu(); + + activeMenu = &toplevel; + deepestMenu = &toplevel; + mouseOverMenu = nil; + mouseOverEntry = nil; + } + menuInitialized = false; +} + +void +processInput(void) +{ + int shift = KEYDOWN(rsRSHIFT) || KEYDOWN(rsLSHIFT); +#define X(var, keycode) var = KEYJUSTDOWN(keycode); + MUHKEYS +#undef X + + // Implement auto-repeat +#define X(var, keycode) \ + if(var){ \ + repeattime = downtime = CTimer::GetTimeInMilliseconds(); \ + lastkeydown = keycode; \ + keyptr = &var; \ + } + MUHKEYS +#undef X + if(lastkeydown){ + if(KEYDOWN(lastkeydown)){ + int curtime = CTimer::GetTimeInMilliseconds(); + if(curtime - downtime > REPEATDELAY){ + if(curtime - repeattime > REPEATINTERVAL){ + repeattime = curtime; + *keyptr = 1; + } + } + }else{ + lastkeydown = 0; + } + } + + // Also for mouse buttons +#define X(var, num) \ + if(var){ \ + repeattime = downtime = CTimer::GetTimeInMilliseconds(); \ + lastbuttondown = num; \ + buttonptr = &var; \ + } + MUHBUTTONS +#undef X + if(lastbuttondown){ + if(buttondown[lastbuttondown-1]){ + int curtime = CTimer::GetTimeInMilliseconds(); + if(curtime - downtime > REPEATDELAY){ + if(curtime - repeattime > REPEATINTERVAL){ + repeattime = curtime; + *buttonptr = 1; + } + } + }else{ + lastbuttondown = 0; + } + } + + // Walk through all visible menus and figure out which one the mouse is over + mouseOverMenu = nil; + mouseOverEntry = nil; + Menu *menu; + for(menu = deepestMenu; menu; menu = menu->parent) + if(isMouseInRect(menu->r)){ + mouseOverMenu = menu; + break; + } + if(mouseOverMenu){ + // Walk all visibile entries and figure out which one the mouse is over + MenuEntry *e; + int i = 0; + for(e = mouseOverMenu->entries; e; e = e->next){ + if(i >= mouseOverMenu->scrollStart+mouseOverMenu->numVisible) + break; + if(i >= mouseOverMenu->scrollStart){ + RwRect r = e->r; + r.w = mouseOverMenu->r.w; // span the whole menu + if(isMouseInRect(r)){ + mouseOverEntry = e; + break; + } + } + i++; + } + if(mouseOverMenu->isScrollingUp && isMouseInRect(mouseOverMenu->scrollUpR)){ + mouseOverEntry = &scrollUpEntry; + mouseOverEntry->r = mouseOverMenu->scrollUpR; + mouseOverEntry->menu = mouseOverMenu; + mouseOverEntry->type = MENUSCROLL; + } + if(mouseOverMenu->isScrollingDown && isMouseInRect(mouseOverMenu->scrollDownR)){ + mouseOverEntry = &scrollDownEntry; + mouseOverEntry->r = mouseOverMenu->scrollDownR; + mouseOverEntry->menu = mouseOverMenu; + mouseOverEntry->type = MENUSCROLL; + } + } + + if(pgupjustdown) + activeMenu->scroll(shift ? -5 : -1); + if(pgdnjustdown) + activeMenu->scroll(shift ? 5 : 1); + if(downjustdown) + activeMenu->changeSelection(activeMenu->selection + (shift ? 5 : 1)); + if(upjustdown) + activeMenu->changeSelection(activeMenu->selection - (shift ? 5 : 1)); + + if(CPad::NewMouseControllerState.WHEELUP){ + if(mouseOverMenu) + activeMenu = mouseOverMenu; + activeMenu->scroll(shift ? -5 : -1); + } + if(CPad::NewMouseControllerState.WHEELDN){ + if(mouseOverMenu) + activeMenu = mouseOverMenu; + activeMenu->scroll(shift ? 5 : 1); + } + + if(mouseOverEntry == &scrollUpEntry){ + if(button1justdown){ + activeMenu = mouseOverEntry->menu; + activeMenu->scroll(shift ? -5 : -1); + } + } + if(mouseOverEntry == &scrollDownEntry){ + if(button1justdown){ + activeMenu = mouseOverEntry->menu; + activeMenu->scroll(shift ? 5 : 1); + } + } + + // Have to call this before processInput below because menu entry can change + if((button1justdown || button3justdown) && mouseOverEntry){ + activeMenu = mouseOverEntry->menu; + activeMenu->changeSelection(mouseOverEntry); + } + if(KEYJUSTDOWN(rsENTER)){ + if(activeMenu->selectedEntry && activeMenu->selectedEntry->type == MENUSUB) + activeMenu = ((MenuEntry_Sub*)activeMenu->selectedEntry)->submenu; + }else if(KEYJUSTDOWN(rsBACKSP)){ + if(activeMenu->parent) + activeMenu = activeMenu->parent; + }else{ + if(mouseOverEntry && mouseOverEntry->type == MENUVAR) + ((MenuEntry_Var*)mouseOverEntry)->processInput(true, mouseOverEntry == activeMenu->selectedEntry); + if(activeMenu->selectedEntry && activeMenu->selectedEntry->type == MENUVAR && + mouseOverEntry != activeMenu->selectedEntry) + ((MenuEntry_Var*)activeMenu->selectedEntry)->processInput(false, true); + } +} + +void +updateMouse(void) +{ + CPad *pad = CPad::GetPad(0); + int dirX = 1; + int dirY = 1; + + if(MousePointerStateHelper.bInvertHorizontally) dirX = -1; + if(MousePointerStateHelper.bInvertVertically) dirY = -1; + + mouseX += pad->NewMouseControllerState.x*dirX; + mouseY += pad->NewMouseControllerState.y*dirY; + + if(mouseX < 0.0f) mouseX = 0.0f; + if(mouseY < 0.0f) mouseY = 0.0f; + if(mouseX >= screenWidth) mouseX = screenWidth; + if(mouseY >= screenHeight) mouseY = screenHeight; + + button1justdown = pad->NewMouseControllerState.LMB && !pad->OldMouseControllerState.LMB; + button2justdown = pad->NewMouseControllerState.MMB && !pad->OldMouseControllerState.MMB; + button3justdown = pad->NewMouseControllerState.RMB && !pad->OldMouseControllerState.RMB; + buttondown[0] = pad->NewMouseControllerState.LMB; + buttondown[1] = pad->NewMouseControllerState.MMB; + buttondown[2] = pad->NewMouseControllerState.RMB; + + // Zero the mouse position so the camera won't move + pad->NewMouseControllerState.x = 0.0f; + pad->NewMouseControllerState.y = 0.0f; +} + +void +DebugMenuProcess(void) +{ + // We only process some input here + + CPad *pad = CPad::GetPad(0); + if(CTRLJUSTDOWN('M')) + menuOn = !menuOn; + if(!menuOn) + return; + + pad->DisablePlayerControls = 1; + // TODO: this could happen earlier + if(!menuInitialized) + DebugMenuInit(); + updateMouse(); + +} + +void +DebugMenuRender(void) +{ + if(!menuOn) + return; + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, 0); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, 0); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, 0); + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); + + RwCamera *cam = RwCameraGetCurrentCamera(); + screenWidth = RwRasterGetWidth(RwCameraGetRaster(cam)); + screenHeight = RwRasterGetHeight(RwCameraGetRaster(cam)); + +// if(screenHeight > 1080) +// fontscale = 2; +// else + fontscale = 1; + + Pt sz; + sz = fontPrint("Debug Menu", firstBorder*fontscale+30, topBorder, 0); + + toplevel.r.x = firstBorder*fontscale; + toplevel.r.y = topBorder + sz.y + 10; + toplevel.r.w = minwidth; // update menu will expand + toplevel.r.h = screenHeight - 10 - toplevel.r.y; + toplevel.update(); + toplevel.draw(); + processInput(); + RtCharsetBufferFlush(); + + drawMouse(); +} + + + +void +drawArrow(RwRect r, int direction, int style) +{ + static RwImVertexIndex indices[] = { 0, 1, 2, 2, 1, 3 }; + static RwIm2DVertex arrowVerts[4]; + + RwCamera *cam = RwCameraGetCurrentCamera(); + float recipz = 1.0f/RwCameraGetNearClipPlane(cam); + + int width = RwRasterGetWidth(arrow); + int height = RwRasterGetHeight(arrow); + + int left = r.x + (r.w - width)/2; + int right = left + width; + int top = r.y; + int bottom = r.y+r.h; + + float umin = HALFPX / width; + float vmin = HALFPX / height; + float umax = (width + HALFPX) / width; + float vmax = (height + HALFPX) / height; + if(direction < 0){ + vmin = (height - HALFPX) / height; + vmax = -HALFPX / height; + } + + if(style){ + RwIm2DVertexSetScreenX(&arrowVerts[0], r.x); + RwIm2DVertexSetScreenY(&arrowVerts[0], r.y-1); + RwIm2DVertexSetScreenZ(&arrowVerts[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[0], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[0], 132, 132, 132, 255); + + RwIm2DVertexSetScreenX(&arrowVerts[1], r.x+r.w); + RwIm2DVertexSetScreenY(&arrowVerts[1], r.y-1); + RwIm2DVertexSetScreenZ(&arrowVerts[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[1], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[1], 132, 132, 132, 255); + + RwIm2DVertexSetScreenX(&arrowVerts[2], r.x); + RwIm2DVertexSetScreenY(&arrowVerts[2], r.y+r.h+1); + RwIm2DVertexSetScreenZ(&arrowVerts[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[2], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[2], 132, 132, 132, 255); + + RwIm2DVertexSetScreenX(&arrowVerts[3], r.x+r.w); + RwIm2DVertexSetScreenY(&arrowVerts[3], r.y+r.h+1); + RwIm2DVertexSetScreenZ(&arrowVerts[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[3], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[3], 132, 132, 132, 255); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, arrowVerts, 4, indices, 6); + } + + + RwIm2DVertexSetScreenX(&arrowVerts[0], left); + RwIm2DVertexSetScreenY(&arrowVerts[0], top); + RwIm2DVertexSetScreenZ(&arrowVerts[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[0], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[0], 255, 255, 255, 255); + RwIm2DVertexSetU(&arrowVerts[0], umin, recipz); + RwIm2DVertexSetV(&arrowVerts[0], vmin, recipz); + + RwIm2DVertexSetScreenX(&arrowVerts[1], right); + RwIm2DVertexSetScreenY(&arrowVerts[1], top); + RwIm2DVertexSetScreenZ(&arrowVerts[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[1], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[1], 255, 255, 255, 255); + RwIm2DVertexSetU(&arrowVerts[1], umax, recipz); + RwIm2DVertexSetV(&arrowVerts[1], vmin, recipz); + + RwIm2DVertexSetScreenX(&arrowVerts[2], left); + RwIm2DVertexSetScreenY(&arrowVerts[2], bottom); + RwIm2DVertexSetScreenZ(&arrowVerts[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[2], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[2], 255, 255, 255, 255); + RwIm2DVertexSetU(&arrowVerts[2], umin, recipz); + RwIm2DVertexSetV(&arrowVerts[2], vmax, recipz); + + RwIm2DVertexSetScreenX(&arrowVerts[3], right); + RwIm2DVertexSetScreenY(&arrowVerts[3], bottom); + RwIm2DVertexSetScreenZ(&arrowVerts[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[3], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[3], 255, 255, 255, 255); + RwIm2DVertexSetU(&arrowVerts[3], umax, recipz); + RwIm2DVertexSetV(&arrowVerts[3], vmax, recipz); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, arrow); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, arrowVerts, 4, indices, 6); +} + +void +drawMouse(void) +{ + static RwImVertexIndex indices[] = { 0, 1, 2, 2, 1, 3 }; + static RwIm2DVertex vertices[4]; + RwIm2DVertex *vert; + RwCamera *cam; + cam = RwCameraGetCurrentCamera(); + float x = mouseX; + float y = mouseY; + float w = RwRasterGetWidth(cursor); + float h = RwRasterGetHeight(cursor); + float recipz = 1.0f/RwCameraGetNearClipPlane(cam); + + float umin = HALFPX / w; + float vmin = HALFPX / h; + float umax = (w + HALFPX) / w; + float vmax = (h + HALFPX) / h; + + vert = vertices; + RwIm2DVertexSetScreenX(vert, x); + RwIm2DVertexSetScreenY(vert, y); + RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(vert, recipz); + RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255); + RwIm2DVertexSetU(vert, umin, recipz); + RwIm2DVertexSetV(vert, vmin, recipz); + vert++; + + RwIm2DVertexSetScreenX(vert, x+w); + RwIm2DVertexSetScreenY(vert, y); + RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(vert, recipz); + RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255); + RwIm2DVertexSetU(vert, umax, recipz); + RwIm2DVertexSetV(vert, vmin, recipz); + vert++; + + RwIm2DVertexSetScreenX(vert, x); + RwIm2DVertexSetScreenY(vert, y+h); + RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(vert, recipz); + RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255); + RwIm2DVertexSetU(vert, umin, recipz); + RwIm2DVertexSetV(vert, vmax, recipz); + vert++; + + RwIm2DVertexSetScreenX(vert, x+w); + RwIm2DVertexSetScreenY(vert, y+h); + RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(vert, recipz); + RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255); + RwIm2DVertexSetU(vert, umax, recipz); + RwIm2DVertexSetV(vert, vmax, recipz); + vert++; + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, cursor); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, vertices, 4, indices, 6); +} + + + + +/* + * Generate interfaces + */ + + +#define X(NAME, TYPE, unused1, unused2) \ +MenuEntry* \ +DebugMenuAdd##NAME(const char *path, const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings) \ +{ \ + Menu *m = findMenu(path); \ + if(m == nil) \ + return nil; \ + MenuEntry *e = new MenuEntry_##NAME(name, ptr, triggerFunc, step, lowerBound, upperBound, strings); \ + m->appendEntry(e); \ + return e; \ +} +INTTYPES +#undef X + +#define X(NAME, TYPE, unused1, unused2) \ +MenuEntry* \ +DebugMenuAdd##NAME(const char *path, const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound) \ +{ \ + Menu *m = findMenu(path); \ + if(m == nil) \ + return nil; \ + MenuEntry *e = new MenuEntry_##NAME(name, ptr, triggerFunc, step, lowerBound, upperBound); \ + m->appendEntry(e); \ + return e; \ +} +FLOATTYPES +#undef X + +MenuEntry* \ +DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc) +{ + Menu *m = findMenu(path); + if(m == nil) + return nil; + MenuEntry *e = new MenuEntry_Cmd(name, triggerFunc); + m->appendEntry(e); + return e; +} + +void +DebugMenuEntrySetWrap(MenuEntry *e, bool wrap) +{ + if(e && e->type == MENUVAR) + ((MenuEntry_Var*)e)->wrapAround = wrap; +} + +void +DebugMenuEntrySetStrings(MenuEntry *e, const char **strings) +{ + if(e && e->type == MENUVAR_INT) + ((MenuEntry_Int*)e)->setStrings(strings); +} + +void +DebugMenuEntrySetAddress(MenuEntry *e, void *addr) +{ + if(e && e->type == MENUVAR){ + MenuEntry_Var *ev = (MenuEntry_Var*)e; + // HACK - we know the variable field is at the same address + // for all int/float classes. let's hope it stays that way. + if(ev->vartype = MENUVAR_INT) + ((MenuEntry_Int32*)e)->variable = (int32*)addr; + else if(ev->vartype = MENUVAR_FLOAT) + ((MenuEntry_Float32*)e)->variable = (float*)addr; + } +} +#endif \ No newline at end of file diff --git a/src/extras/debugmenu.h b/src/extras/debugmenu.h new file mode 100644 index 0000000..45b65d0 --- /dev/null +++ b/src/extras/debugmenu.h @@ -0,0 +1,204 @@ +#pragma once + +#ifdef DEBUGMENU + +// Tweaking stuff for debugmenu +#define TWEAKPATH ___tw___TWEAKPATH +#define SETTWEAKPATH(path) static const char *___tw___TWEAKPATH = path; +#define TWEAKFUNC(v) static CTweakFunc CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), TWEAKPATH); +#define TWEAKFUNCN(v, name) static CTweakFunc CONCAT(___tw___tweak, __COUNTER__)(&v, name, TWEAKPATH); +#define TWEAKBOOL(v) static CTweakBool CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), TWEAKPATH); +#define TWEAKBOOLN(v, name) static CTweakBool CONCAT(___tw___tweak, __COUNTER__)(&v, name, TWEAKPATH); +#define TWEAKINT32(v, lower, upper, step) static CTweakInt32 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKINT32N(v, lower, upper, step, name) static CTweakInt32 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKUINT32(v, lower, upper, step) static CTweakUInt32 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKUINT32N(v, lower, upper, step, name) static CTweakUInt32 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKINT16(v, lower, upper, step) static CTweakInt16 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKINT16N(v, lower, upper, step, name) static CTweakInt16 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKUINT16(v, lower, upper, step) static CTweakUInt16 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKUINT16N(v, lower, upper, step, name) static CTweakUInt16 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKINT8(v, lower, upper, step) static CTweakInt8 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKINT8N(v, lower, upper, step, name) static CTweakInt8 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKUINT8(v, lower, upper, step) static CTweakUInt8 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKUINT8N(v, lower, upper, step, name) static CTweakUInt8 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKFLOAT(v, lower, upper, step) static CTweakFloat CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKFLOATN(v, lower, upper, step, name) static CTweakFloat CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKSWITCH(v, lower, upper, str, f) static CTweakSwitch CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, str, f, TWEAKPATH); +#define TWEAKSWITCHN(v, lower, upper, str, f, name) static CTweakSwitch CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, str, f, TWEAKPATH); + +// interface +class CTweakVar +{ +public: + virtual void AddDBG(const char* path) = 0; +}; + +class CTweakVars +{ +public: + static void Add(CTweakVar* var); + static void AddDBG(const char* path); +}; + +class CTweakFunc : public CTweakVar +{ + const char* m_pPath, * m_pVarName; + void (*m_pFunc)(); +public: + CTweakFunc(void (*pFunc)(), const char* strName, const char* strPath) : + m_pPath(strPath), m_pVarName(strName), m_pFunc(pFunc) + { + CTweakVars::Add(this); + } + + void AddDBG(const char* path); +}; + +class CTweakBool : public CTweakVar +{ + const char* m_pPath, * m_pVarName; + bool* m_pBoolVar; +public: + CTweakBool(bool* pBool, const char* strName, const char* strPath) : + m_pPath(strPath), m_pVarName(strName), m_pBoolVar(pBool) + { + CTweakVars::Add(this); + } + + void AddDBG(const char* path); +}; + +class CTweakSwitch : public CTweakVar +{ + const char* m_pPath, * m_pVarName; + void* m_pIntVar; + int32 m_nMin, m_nMax; + const char** m_aStr; + void (*m_pFunc)(); +public: + CTweakSwitch(void* pInt, const char* strName, int32 nMin, int32 nMax, const char** aStr, + void (*pFunc)(), const char* strPath) + : m_pPath(strPath), m_pVarName(strName), m_pIntVar(pInt), m_nMin(nMin), m_nMax(nMax), + m_aStr(aStr) + { + CTweakVars::Add(this); + } + + void AddDBG(const char* path); +}; + +#define _TWEEKCLASS(name, type) \ + class name : public CTweakVar \ + { \ + public: \ + const char *m_pPath, *m_pVarName; \ + type *m_pIntVar, m_nLoawerBound, m_nUpperBound, m_nStep; \ + \ + name(type *pInt, const char *strName, type nLower, type nUpper, type nStep, \ + const char *strPath) \ + : m_pPath(strPath), m_pVarName(strName), m_pIntVar(pInt), \ + m_nLoawerBound(nLower), m_nUpperBound(nUpper), m_nStep(nStep) \ + \ + { \ + CTweakVars::Add(this); \ + } \ + \ + void AddDBG(const char *path); \ + }; + +_TWEEKCLASS(CTweakInt8, int8); +_TWEEKCLASS(CTweakUInt8, uint8); +_TWEEKCLASS(CTweakInt16, int16); +_TWEEKCLASS(CTweakUInt16, uint16); +_TWEEKCLASS(CTweakInt32, int32); +_TWEEKCLASS(CTweakUInt32, uint32); +_TWEEKCLASS(CTweakFloat, float); + +#undef _TWEEKCLASS + +typedef void (*TriggerFunc)(void); + +struct Menu; + +struct MenuEntry +{ + int type; + const char *name; + MenuEntry *next; + RwRect r; + Menu *menu; + + MenuEntry(const char *name); + virtual ~MenuEntry(void) { free((void*)name); } +}; + +typedef MenuEntry DebugMenuEntry; + +MenuEntry *DebugMenuAddInt8(const char *path, const char *name, int8 *ptr, TriggerFunc triggerFunc, int8 step, int8 lowerBound, int8 upperBound, const char **strings); +MenuEntry *DebugMenuAddInt16(const char *path, const char *name, int16 *ptr, TriggerFunc triggerFunc, int16 step, int16 lowerBound, int16 upperBound, const char **strings); +MenuEntry *DebugMenuAddInt32(const char *path, const char *name, int32 *ptr, TriggerFunc triggerFunc, int32 step, int32 lowerBound, int32 upperBound, const char **strings); +MenuEntry *DebugMenuAddInt64(const char *path, const char *name, int64 *ptr, TriggerFunc triggerFunc, int64 step, int64 lowerBound, int64 upperBound, const char **strings); +MenuEntry *DebugMenuAddUInt8(const char *path, const char *name, uint8 *ptr, TriggerFunc triggerFunc, uint8 step, uint8 lowerBound, uint8 upperBound, const char **strings); +MenuEntry *DebugMenuAddUInt16(const char *path, const char *name, uint16 *ptr, TriggerFunc triggerFunc, uint16 step, uint16 lowerBound, uint16 upperBound, const char **strings); +MenuEntry *DebugMenuAddUInt32(const char *path, const char *name, uint32 *ptr, TriggerFunc triggerFunc, uint32 step, uint32 lowerBound, uint32 upperBound, const char **strings); +MenuEntry *DebugMenuAddUInt64(const char *path, const char *name, uint64 *ptr, TriggerFunc triggerFunc, uint64 step, uint64 lowerBound, uint64 upperBound, const char **strings); +MenuEntry *DebugMenuAddFloat32(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound); +MenuEntry *DebugMenuAddFloat64(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound); +MenuEntry *DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc); +void DebugMenuEntrySetWrap(MenuEntry *e, bool wrap); +void DebugMenuEntrySetStrings(MenuEntry *e, const char **strings); +void DebugMenuEntrySetAddress(MenuEntry *e, void *addr); +void DebugMenuInit(void); +void DebugMenuShutdown(void); +void DebugMenuProcess(void); +void DebugMenuRender(void); + + +// Some overloads 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 DebugMenuAddInt8(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 DebugMenuAddInt16(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 DebugMenuAddInt32(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 DebugMenuAddInt64(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 DebugMenuAddUInt8(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 DebugMenuAddUInt16(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 DebugMenuAddUInt32(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 DebugMenuAddUInt64(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 DebugMenuAddFloat32(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 DebugMenuAddFloat64(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; +} +inline DebugMenuEntry *DebugMenuAddVarBool8(const char *path, const char *name, bool *ptr, TriggerFunc triggerFunc) +{ + return DebugMenuAddVarBool8(path, name, (int8_t*)ptr, triggerFunc); +} +#endif \ No newline at end of file diff --git a/src/extras/frontendoption.cpp b/src/extras/frontendoption.cpp new file mode 100644 index 0000000..5d388bf --- /dev/null +++ b/src/extras/frontendoption.cpp @@ -0,0 +1,182 @@ +#include "common.h" + +#ifdef CUSTOM_FRONTEND_OPTIONS +#include "Frontend.h" +#include "Text.h" + +int lastOgScreen = MENUPAGES; // means no new pages + +int numCustomFrontendOptions = 0; +int numCustomFrontendScreens = 0; + +int optionCursor = -2; +int currentMenu; +bool optionOverwrite = false; + +void ChangeScreen(int screen, int option, bool fadeIn) +{ + FrontEndMenuManager.m_nPrevScreen = FrontEndMenuManager.m_nCurrScreen; + FrontEndMenuManager.m_nCurrScreen = screen; + FrontEndMenuManager.m_nCurrOption = option; + if (fadeIn) + FrontEndMenuManager.m_nMenuFadeAlpha = 0; +} + +void GoBack(bool fadeIn) +{ + int screen = !FrontEndMenuManager.m_bGameNotLoaded ? + aScreens[FrontEndMenuManager.m_nCurrScreen].m_PreviousPage[1] : aScreens[FrontEndMenuManager.m_nCurrScreen].m_PreviousPage[0]; + int option = FrontEndMenuManager.GetPreviousPageOption(); + + FrontEndMenuManager.ThingsToDoBeforeGoingBack(); + + ChangeScreen(screen, option, fadeIn); +} + +uint8 +GetNumberOfMenuOptions(int screen) +{ + uint8 Rows = 0; + for (int i = 0; i < NUM_MENUROWS; i++) { + if (aScreens[screen].m_aEntries[i].m_Action == MENUACTION_NOTHING) + break; + + ++Rows; + } + return Rows; +} + +uint8 +GetLastMenuScreen() +{ + int8 page = -1; + for (int i = 0; i < MENUPAGES; i++) { + if (strcmp(aScreens[i].m_ScreenName, "") == 0 && aScreens[i].m_PreviousPage[0] == MENUPAGE_NONE) + break; + + ++page; + } + return page; +} + +int8 RegisterNewScreen(const char *name, int prevPage, ReturnPrevPageFunc returnPrevPageFunc) +{ + if (lastOgScreen == MENUPAGES) + lastOgScreen = GetLastMenuScreen(); + + numCustomFrontendScreens++; + int id = lastOgScreen + numCustomFrontendScreens; + assert(id < MENUPAGES && "No room for new custom frontend screens! Increase MENUPAGES"); + strncpy(aScreens[id].m_ScreenName, name, 8); + aScreens[id].m_PreviousPage[0] = aScreens[id].m_PreviousPage[1] = prevPage; + aScreens[id].returnPrevPageFunc = returnPrevPageFunc; + return id; +} + +int8 RegisterNewOption() +{ + numCustomFrontendOptions++; + uint8 numOptions = GetNumberOfMenuOptions(currentMenu); + uint8 curIdx; + if (optionCursor < 0) { + optionCursor = curIdx = numOptions + optionCursor + 1; + } else + curIdx = optionCursor; + + if (!optionOverwrite) { + if (aScreens[currentMenu].m_aEntries[curIdx].m_Action != MENUACTION_NOTHING) { + for (int i = numOptions - 1; i >= curIdx; i--) { + memcpy(&aScreens[currentMenu].m_aEntries[i + 1], &aScreens[currentMenu].m_aEntries[i], sizeof(CMenuScreenCustom::CMenuEntry)); + } + } + } + optionCursor++; + return curIdx; +} + +void FrontendOptionSetCursor(int screen, int8 option, bool overwrite) +{ + currentMenu = screen; + optionCursor = option; + optionOverwrite = overwrite; +} + +void FrontendOptionAddBuiltinAction(const char* gxtKey, int action, int targetMenu, int saveSlot) { + int8 screenOptionOrder = RegisterNewOption(); + + CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder]; + + // We can't use custom text on those :shrug: + switch (action) { + case MENUACTION_SCREENRES: + strcpy(option.m_EntryName, "FED_RES"); + break; + case MENUACTION_AUDIOHW: + strcpy(option.m_EntryName, "FEA_3DH"); + break; + default: + strncpy(option.m_EntryName, gxtKey, 8); + break; + } + option.m_Action = action; + option.m_SaveSlot = saveSlot; + option.m_TargetMenu = targetMenu; +} + +void FrontendOptionAddSelect(const char* gxtKey, const char** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, const char* saveCat, const char* saveKey, bool disableIfGameLoaded) +{ + int8 screenOptionOrder = RegisterNewOption(); + + CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder]; + option.m_Action = MENUACTION_CFO_SELECT; + strncpy(option.m_EntryName, gxtKey, 8); + option.m_CFOSelect = new CCFOSelect(); + option.m_CFOSelect->rightTexts = (char**)malloc(numRightTexts * sizeof(char*)); + memcpy(option.m_CFOSelect->rightTexts, rightTexts, numRightTexts * sizeof(char*)); + option.m_CFOSelect->numRightTexts = numRightTexts; + option.m_CFOSelect->value = var; + if (var) { + option.m_CFOSelect->displayedValue = *var; + option.m_CFOSelect->lastSavedValue = *var; + } + option.m_CFOSelect->saveCat = saveCat; + option.m_CFOSelect->save = saveKey; + option.m_CFOSelect->onlyApplyOnEnter = onlyApplyOnEnter; + option.m_CFOSelect->changeFunc = changeFunc; + option.m_CFOSelect->disableIfGameLoaded = disableIfGameLoaded; +} + +void FrontendOptionAddDynamic(const char* gxtKey, DrawFunc drawFunc, int8 *var, ButtonPressFunc buttonPressFunc, const char* saveCat, const char* saveKey) +{ + int8 screenOptionOrder = RegisterNewOption(); + + CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder]; + option.m_Action = MENUACTION_CFO_DYNAMIC; + strncpy(option.m_EntryName, gxtKey, 8); + option.m_CFODynamic = new CCFODynamic(); + option.m_CFODynamic->drawFunc = drawFunc; + option.m_CFODynamic->buttonPressFunc = buttonPressFunc; + option.m_CFODynamic->value = var; + option.m_CFODynamic->saveCat = saveCat; + option.m_CFODynamic->save = saveKey; +} + +uint8 FrontendScreenAdd(const char* gxtKey, eMenuSprites sprite, int prevPage, int columnWidth, int headerHeight, int lineHeight, + int8 font, float fontScaleX, float fontScaleY, int8 alignment, bool showLeftRightHelper, ReturnPrevPageFunc returnPrevPageFunc) { + + uint8 screenOrder = RegisterNewScreen(gxtKey, prevPage, returnPrevPageFunc); + + CCustomScreenLayout *screen = new CCustomScreenLayout(); + aScreens[screenOrder].layout = screen; + screen->sprite = sprite; + screen->columnWidth = columnWidth; + screen->headerHeight = headerHeight; + screen->lineHeight = lineHeight; + screen->font = font; + screen->fontScaleX = fontScaleX; + screen->fontScaleY = fontScaleY; + screen->alignment = alignment; + + return screenOrder; +} +#endif diff --git a/src/extras/frontendoption.h b/src/extras/frontendoption.h new file mode 100644 index 0000000..a571170 --- /dev/null +++ b/src/extras/frontendoption.h @@ -0,0 +1,93 @@ +#pragma once +#include "common.h" + +#ifdef CUSTOM_FRONTEND_OPTIONS + +// ! There are 2 ways to use CFO, +// 1st; by adding a new option to the array in MenuScreensCustom.cpp and passing attributes/CBs to it +// 2nd; by calling the functions listed at the bottom of this file. + +// -- Option types +// +// Static/select: You allocate the variable, pass it to function and game sets it from user input among the strings given to function, +// optionally you can add post-change event via ChangeFunc(only called on enter if onlyApplyOnEnter set, or set immediately) +// You can store the option in an INI file if you pass the key(as a char array) to corresponding parameter. +// +// Dynamic: Passing variable to function is only needed if you want to store it, otherwise you should do +// all the operations with ButtonPressFunc, this includes allocating the variable. +// Left-side text is passed while creating and static, but ofc right-side text is dynamic - +// you should return it in DrawFunc, which is called on every draw. +// +// Built-in action: As the name suggests, any action that game has built-in. But as an extra you can set the option text, + +// -- Returned via ButtonPressFunc() action param. +#define FEOPTION_ACTION_LEFT 0 +#define FEOPTION_ACTION_RIGHT 1 +#define FEOPTION_ACTION_SELECT 2 +#define FEOPTION_ACTION_FOCUSLOSS 3 + +// -- Passed via FrontendScreenAdd() +#define FESCREEN_CENTER 0 +#define FESCREEN_LEFT_ALIGN 1 +#define FESCREEN_RIGHT_ALIGN 2 + +// -- Callbacks + +// pretty much in everything I guess, and optional in all of them +typedef void (*ReturnPrevPageFunc)(); + +// for static options +typedef void (*ChangeFunc)(int8 before, int8 after); // called after updating the value. + // only called on enter if onlyApplyOnEnter set, otherwise called on every value change + +typedef void (*ChangeFuncFloat)(float before, float after); // called after updating the value. + +// for dynamic options +typedef wchar* (*DrawFunc)(bool* disabled, bool userHovering); // you must return a pointer for right text. + // you can also set *disabled if you want to gray it out. +typedef void (*ButtonPressFunc)(int8 action); // see FEOPTION_ACTIONs above + +// -- Internal things +void CustomFrontendOptionsPopulate(); +extern int lastOgScreen; // for reloading +extern int numCustomFrontendOptions; +extern int numCustomFrontendScreens; + +// -- To be used in ButtonPressFunc / ChangeFunc(this one would be weird): +void ChangeScreen(int screen, int option = 0, bool fadeIn = true); +void GoBack(bool fadeIn = true); + +uint8 GetNumberOfMenuOptions(int screen); + +// !!! We're now moved to MenuScreensCustom.cpp, which houses an array that keeps all original+custom options. +// But you can still use the APIs below, and manipulate aScreens while in game. + +// Limits: +// The code relies on that you won't use more then NUM_MENUROWS(18) options on one page, and won't exceed the MENUPAGES of pages. +// Also congrats if you can make 18 options visible at once. + +// Texts: +// All text parameters accept char[8] GXT key. + +// Execute direction: +// All of the calls below eventually manipulate the aScreens array, so keep in mind to add/replace options in order, +// i.e. don't set cursor to 8 first and then 3. + + +// -- Placing the cursor to append/overwrite option +// +// Done via FrontendOptionSetCursor(screen, position, overwrite = false), parameters explained below: +// Screen: as the name suggests. Also accepts the screen IDs returned from FrontendScreenAdd. +// Option: if positive, next AddOption call will put the option to there and progress the cursor. +// if negative, cursor will be placed on bottom-(pos+1), so -1 means the very bottom, -2 means before the back button etc. +// Overwrite: Use to overwrite the options, not appending a new one. AddOption calls will still progress the cursor. + +void FrontendOptionSetCursor(int screen, int8 option, bool overwrite = false); + +// var is optional in AddDynamic, enables you to save them in an INI file(also needs passing char array to and saveCat saveKey param. obv), otherwise pass nil/0 +void FrontendOptionAddBuiltinAction(const char* gxtKey, int action, int targetMenu = MENUPAGE_NONE, int saveSlot = SAVESLOT_NONE); +void FrontendOptionAddSelect(const char* gxtKey, const char** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, const char* saveCat = nil, const char* saveKey = nil, bool disableIfGameLoaded = false); +void FrontendOptionAddDynamic(const char* gxtKey, DrawFunc rightTextDrawFunc, int8 *var, ButtonPressFunc buttonPressFunc, const char* saveCat = nil, const char* saveKey = nil); + +uint8 FrontendScreenAdd(const char* gxtKey, eMenuSprites sprite, int prevPage, int columnWidth, int headerHeight, int lineHeight, int8 font, float fontScaleX, float fontScaleY, int8 alignment, bool showLeftRightHelper, ReturnPrevPageFunc returnPrevPageFunc = nil); +#endif diff --git a/src/extras/ini.h b/src/extras/ini.h new file mode 100644 index 0000000..44dd3d5 --- /dev/null +++ b/src/extras/ini.h @@ -0,0 +1,761 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2018 Danijel Durakovic + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/////////////////////////////////////////////////////////////////////////////// +// +// /mINI/ v0.9.10 +// An INI file reader and writer for the modern age. +// +/////////////////////////////////////////////////////////////////////////////// +// +// A tiny utility library for manipulating INI files with a straightforward +// API and a minimal footprint. It conforms to the (somewhat) standard INI +// format - sections and keys are case insensitive and all leading and +// trailing whitespace is ignored. Comments are lines that begin with a +// semicolon. Trailing comments are allowed on section lines. +// +// Files are read on demand, upon which data is kept in memory and the file +// is closed. This utility supports lazy writing, which only writes changes +// and updates to a file and preserves custom formatting and comments. A lazy +// write invoked by a write() call will read the output file, find what +// changes have been made and update the file accordingly. If you only need to +// generate files, use generate() instead. Section and key order is preserved +// on read, write and insert. +// +/////////////////////////////////////////////////////////////////////////////// +// +// /* BASIC USAGE EXAMPLE: */ +// +// /* read from file */ +// mINI::INIFile file("myfile.ini"); +// mINI::INIStructure ini; +// file.read(ini); +// +// /* read value; gets a reference to actual value in the structure. +// if key or section don't exist, a new empty value will be created */ +// std::string& value = ini["section"]["key"]; +// +// /* read value safely; gets a copy of value in the structure. +// does not alter the structure */ +// std::string value = ini.get("section").get("key"); +// +// /* set or update values */ +// ini["section"]["key"] = "value"; +// +// /* set multiple values */ +// ini["section2"].set({ +// {"key1", "value1"}, +// {"key2", "value2"} +// }); +// +// /* write updates back to file, preserving comments and formatting */ +// file.write(ini); +// +// /* or generate a file (overwrites the original) */ +// file.generate(ini); +// +/////////////////////////////////////////////////////////////////////////////// +// +// Long live the INI file!!! +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef MINI_INI_H_ +#define MINI_INI_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mINI +{ + namespace INIStringUtil + { + const char* const whitespaceDelimiters = " \t\n\r\f\v"; + inline void trim(std::string& str) + { + str.erase(str.find_last_not_of(whitespaceDelimiters) + 1); + str.erase(0, str.find_first_not_of(whitespaceDelimiters)); + } +#ifndef MINI_CASE_SENSITIVE + inline void toLower(std::string& str) + { + std::transform(str.begin(), str.end(), str.begin(), [](const char c) { + return static_cast(std::tolower(c)); + }); + } +#endif + inline void replace(std::string& str, std::string const& a, std::string const& b) + { + if (!a.empty()) + { + std::size_t pos = 0; + while ((pos = str.find(a, pos)) != std::string::npos) + { + str.replace(pos, a.size(), b); + pos += b.size(); + } + } + } +#ifdef _WIN32 + const char* const endl = "\r\n"; +#else + const char* const endl = "\n"; +#endif + }; + + template + class INIMap + { + private: + using T_DataIndexMap = std::unordered_map; + using T_DataItem = std::pair; + using T_DataContainer = std::vector; + using T_MultiArgs = typename std::vector>; + + T_DataIndexMap dataIndexMap; + T_DataContainer data; + + inline std::size_t setEmpty(std::string& key) + { + std::size_t index = data.size(); + dataIndexMap[key] = index; + data.emplace_back(key, T()); + return index; + } + + public: + using const_iterator = typename T_DataContainer::const_iterator; + + INIMap() { } + + INIMap(INIMap const& other) + { + std::size_t data_size = other.data.size(); + for (std::size_t i = 0; i < data_size; ++i) + { + auto const& key = other.data[i].first; + auto const& obj = other.data[i].second; + data.emplace_back(key, obj); + } + dataIndexMap = T_DataIndexMap(other.dataIndexMap); + } + + T& operator[](std::string key) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + bool hasIt = (it != dataIndexMap.end()); + std::size_t index = (hasIt) ? it->second : setEmpty(key); + return data[index].second; + } + T get(std::string key) const + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it == dataIndexMap.end()) + { + return T(); + } + return T(data[it->second].second); + } + bool has(std::string key) const + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + return (dataIndexMap.count(key) == 1); + } + void set(std::string key, T obj) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it != dataIndexMap.end()) + { + data[it->second].second = obj; + } + else + { + dataIndexMap[key] = data.size(); + data.emplace_back(key, obj); + } + } + void set(T_MultiArgs const& multiArgs) + { + for (auto const& it : multiArgs) + { + auto const& key = it.first; + auto const& obj = it.second; + set(key, obj); + } + } + bool remove(std::string key) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it != dataIndexMap.end()) + { + std::size_t index = it->second; + data.erase(data.begin() + index); + dataIndexMap.erase(it); + for (auto& it2 : dataIndexMap) + { + auto& vi = it2.second; + if (vi > index) + { + vi--; + } + } + return true; + } + return false; + } + void clear() + { + data.clear(); + dataIndexMap.clear(); + } + std::size_t size() const + { + return data.size(); + } + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } + }; + + using INIStructure = INIMap>; + + namespace INIParser + { + using T_ParseValues = std::pair; + + enum class PDataType : char + { + PDATA_NONE, + PDATA_COMMENT, + PDATA_SECTION, + PDATA_KEYVALUE, + PDATA_UNKNOWN + }; + + inline PDataType parseLine(std::string line, T_ParseValues& parseData) + { + parseData.first.clear(); + parseData.second.clear(); + INIStringUtil::trim(line); + if (line.empty()) + { + return PDataType::PDATA_NONE; + } + char firstCharacter = line[0]; + if (firstCharacter == ';') + { + return PDataType::PDATA_COMMENT; + } + if (firstCharacter == '[') + { + auto commentAt = line.find_first_of(';'); + if (commentAt != std::string::npos) + { + line = line.substr(0, commentAt); + } + auto closingBracketAt = line.find_last_of(']'); + if (closingBracketAt != std::string::npos) + { + auto section = line.substr(1, closingBracketAt - 1); + INIStringUtil::trim(section); + parseData.first = section; + return PDataType::PDATA_SECTION; + } + } + auto lineNorm = line; + INIStringUtil::replace(lineNorm, "\\=", " "); + auto equalsAt = lineNorm.find_first_of('='); + if (equalsAt != std::string::npos) + { + auto key = line.substr(0, equalsAt); + INIStringUtil::trim(key); + INIStringUtil::replace(key, "\\=", "="); + auto value = line.substr(equalsAt + 1); + INIStringUtil::trim(value); + parseData.first = key; + parseData.second = value; + return PDataType::PDATA_KEYVALUE; + } + return PDataType::PDATA_UNKNOWN; + } + }; + + class INIReader + { + public: + using T_LineData = std::vector; + using T_LineDataPtr = std::shared_ptr; + + private: + std::ifstream fileReadStream; + T_LineDataPtr lineData; + + T_LineData readFile() + { + std::string fileContents; + fileReadStream.seekg(0, std::ios::end); + fileContents.resize(fileReadStream.tellg()); + fileReadStream.seekg(0, std::ios::beg); + std::size_t fileSize = fileContents.size(); + fileReadStream.read(&fileContents[0], fileSize); + fileReadStream.close(); + T_LineData output; + if (fileSize == 0) + { + return output; + } + std::string buffer; + buffer.reserve(50); + for (std::size_t i = 0; i < fileSize; ++i) + { + char& c = fileContents[i]; + if (c == '\n') + { + output.emplace_back(buffer); + buffer.clear(); + continue; + } + if (c != '\0' && c != '\r') + { + buffer += c; + } + } + output.emplace_back(buffer); + return output; + } + + public: + INIReader(std::string const& filename, bool keepLineData = false) + { + fileReadStream.open(filename, std::ios::in | std::ios::binary); + if (keepLineData) + { + lineData = std::make_shared(); + } + } + ~INIReader() { } + + bool operator>>(INIStructure& data) + { + if (!fileReadStream.is_open()) + { + return false; + } + T_LineData fileLines = readFile(); + std::string section; + bool inSection = false; + INIParser::T_ParseValues parseData; + for (auto const& line : fileLines) + { + auto parseResult = INIParser::parseLine(line, parseData); + if (parseResult == INIParser::PDataType::PDATA_SECTION) + { + inSection = true; + data[section = parseData.first]; + } + else if (inSection && parseResult == INIParser::PDataType::PDATA_KEYVALUE) + { + auto const& key = parseData.first; + auto const& value = parseData.second; + data[section][key] = value; + } + if (lineData && parseResult != INIParser::PDataType::PDATA_UNKNOWN) + { + if (parseResult == INIParser::PDataType::PDATA_KEYVALUE && !inSection) + { + continue; + } + lineData->emplace_back(line); + } + } + return true; + } + T_LineDataPtr getLines() + { + return lineData; + } + }; + + class INIGenerator + { + private: + std::ofstream fileWriteStream; + + public: + bool prettyPrint = false; + + INIGenerator(std::string const& filename) + { + fileWriteStream.open(filename, std::ios::out | std::ios::binary); + } + ~INIGenerator() { } + + bool operator<<(INIStructure const& data) + { + if (!fileWriteStream.is_open()) + { + return false; + } + if (!data.size()) + { + return true; + } + auto it = data.begin(); + for (;;) + { + auto const& section = it->first; + auto const& collection = it->second; + fileWriteStream + << "[" + << section + << "]"; + if (collection.size()) + { + fileWriteStream << INIStringUtil::endl; + auto it2 = collection.begin(); + for (;;) + { + auto key = it2->first; + INIStringUtil::replace(key, "=", "\\="); + auto value = it2->second; + INIStringUtil::trim(value); + fileWriteStream + << key + << ((prettyPrint) ? " = " : "=") + << value; + if (++it2 == collection.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + } + } + if (++it == data.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + if (prettyPrint) + { + fileWriteStream << INIStringUtil::endl; + } + } + return true; + } + }; + + class INIWriter + { + private: + using T_LineData = std::vector; + using T_LineDataPtr = std::shared_ptr; + + std::string filename; + + T_LineData getLazyOutput(T_LineDataPtr const& lineData, INIStructure& data, INIStructure& original) + { + T_LineData output; + INIParser::T_ParseValues parseData; + std::string sectionCurrent; + bool parsingSection = false; + bool continueToNextSection = false; + bool discardNextEmpty = false; + bool writeNewKeys = false; + std::size_t lastKeyLine = 0; + for (auto line = lineData->begin(); line != lineData->end(); ++line) + { + if (!writeNewKeys) + { + auto parseResult = INIParser::parseLine(*line, parseData); + if (parseResult == INIParser::PDataType::PDATA_SECTION) + { + if (parsingSection) + { + writeNewKeys = true; + parsingSection = false; + --line; + continue; + } + sectionCurrent = parseData.first; + if (data.has(sectionCurrent)) + { + parsingSection = true; + continueToNextSection = false; + discardNextEmpty = false; + output.emplace_back(*line); + lastKeyLine = output.size(); + } + else + { + continueToNextSection = true; + discardNextEmpty = true; + continue; + } + } + else if (parseResult == INIParser::PDataType::PDATA_KEYVALUE) + { + if (continueToNextSection) + { + continue; + } + if (data.has(sectionCurrent)) + { + auto& collection = data[sectionCurrent]; + auto const& key = parseData.first; + auto const& value = parseData.second; + if (collection.has(key)) + { + auto outputValue = collection[key]; + if (value == outputValue) + { + output.emplace_back(*line); + } + else + { + INIStringUtil::trim(outputValue); + auto lineNorm = *line; + INIStringUtil::replace(lineNorm, "\\=", " "); + auto equalsAt = lineNorm.find_first_of('='); + auto valueAt = lineNorm.find_first_not_of( + INIStringUtil::whitespaceDelimiters, + equalsAt + 1 + ); + std::string outputLine = line->substr(0, valueAt); + if (prettyPrint && equalsAt + 1 == valueAt) + { + outputLine += " "; + } + outputLine += outputValue; + output.emplace_back(outputLine); + } + lastKeyLine = output.size(); + } + } + } + else + { + if (discardNextEmpty && line->empty()) + { + discardNextEmpty = false; + } + else if (parseResult != INIParser::PDataType::PDATA_UNKNOWN) + { + output.emplace_back(*line); + } + } + } + if (writeNewKeys || std::next(line) == lineData->end()) + { + T_LineData linesToAdd; + if (data.has(sectionCurrent) && original.has(sectionCurrent)) + { + auto const& collection = data[sectionCurrent]; + auto const& collectionOriginal = original[sectionCurrent]; + for (auto const& it : collection) + { + auto key = it.first; + if (collectionOriginal.has(key)) + { + continue; + } + auto value = it.second; + INIStringUtil::replace(key, "=", "\\="); + INIStringUtil::trim(value); + linesToAdd.emplace_back( + key + ((prettyPrint) ? " = " : "=") + value + ); + } + } + if (!linesToAdd.empty()) + { + output.insert( + output.begin() + lastKeyLine, + linesToAdd.begin(), + linesToAdd.end() + ); + } + if (writeNewKeys) + { + writeNewKeys = false; + --line; + } + } + } + for (auto const& it : data) + { + auto const& section = it.first; + if (original.has(section)) + { + continue; + } + if (prettyPrint && output.size() > 0 && !output.back().empty()) + { + output.emplace_back(); + } + output.emplace_back("[" + section + "]"); + auto const& collection = it.second; + for (auto const& it2 : collection) + { + auto key = it2.first; + auto value = it2.second; + INIStringUtil::replace(key, "=", "\\="); + INIStringUtil::trim(value); + output.emplace_back( + key + ((prettyPrint) ? " = " : "=") + value + ); + } + } + return output; + } + + public: + bool prettyPrint = false; + + INIWriter(std::string const& filename) + : filename(filename) + { + } + ~INIWriter() { } + + bool operator<<(INIStructure& data) + { + struct stat buf; + bool fileExists = (stat(filename.c_str(), &buf) == 0); + if (!fileExists) + { + INIGenerator generator(filename); + generator.prettyPrint = prettyPrint; + return generator << data; + } + INIStructure originalData; + T_LineDataPtr lineData; + bool readSuccess = false; + { + INIReader reader(filename, true); + if ((readSuccess = reader >> originalData)) + { + lineData = reader.getLines(); + } + } + if (!readSuccess) + { + return false; + } + T_LineData output = getLazyOutput(lineData, data, originalData); + std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary); + if (fileWriteStream.is_open()) + { + if (output.size()) + { + auto line = output.begin(); + for (;;) + { + fileWriteStream << *line; + if (++line == output.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + } + } + return true; + } + return false; + } + }; + + class INIFile + { + private: + std::string filename; + + public: + INIFile(std::string const& filename) + : filename(filename) + { } + + ~INIFile() { } + + bool read(INIStructure& data) const + { + if (data.size()) + { + data.clear(); + } + if (filename.empty()) + { + return false; + } + INIReader reader(filename); + return reader >> data; + } + bool generate(INIStructure const& data, bool pretty = false) const + { + if (filename.empty()) + { + return false; + } + INIGenerator generator(filename); + generator.prettyPrint = pretty; + return generator << data; + } + bool write(INIStructure& data, bool pretty = false) const + { + if (filename.empty()) + { + return false; + } + INIWriter writer(filename); + writer.prettyPrint = pretty; + return writer << data; + } + }; +} + +#endif // MINI_INI_H_ diff --git a/src/extras/postfx.cpp b/src/extras/postfx.cpp new file mode 100644 index 0000000..425a22d --- /dev/null +++ b/src/extras/postfx.cpp @@ -0,0 +1,476 @@ +#define WITHD3D +#include "common.h" + +#ifdef EXTENDED_COLOURFILTER + +#ifndef LIBRW +#error "Need librw for EXTENDED_COLOURFILTER" +#endif + +#include "main.h" +#include "RwHelper.h" +#include "Camera.h" +#include "MBlur.h" +#include "postfx.h" + +RwRaster *CPostFX::pFrontBuffer; +RwRaster *CPostFX::pBackBuffer; +bool CPostFX::bJustInitialised; +int CPostFX::EffectSwitch = POSTFX_NORMAL; +bool CPostFX::MotionBlurOn = false; + +static RwIm2DVertex Vertex[4]; +static RwIm2DVertex Vertex2[4]; +static RwImVertexIndex Index[6] = { 0, 1, 2, 0, 2, 3 }; + +#ifdef RW_D3D9 +void *colourfilterIII_PS; +void *contrast_PS; +#endif +#ifdef RW_OPENGL +int32 u_blurcolor; +int32 u_contrastAdd; +int32 u_contrastMult; +rw::gl3::Shader *colourFilterIII; +rw::gl3::Shader *contrast; +#endif + +void +CPostFX::InitOnce(void) +{ +#ifdef RW_OPENGL + u_blurcolor = rw::gl3::registerUniform("u_blurcolor"); + u_contrastAdd = rw::gl3::registerUniform("u_contrastAdd"); + u_contrastMult = rw::gl3::registerUniform("u_contrastMult"); +#endif +} + +void +CPostFX::Open(RwCamera *cam) +{ + if(pFrontBuffer) + Close(); + + uint32 width = Pow(2.0f, int32(log2(RwRasterGetWidth (RwCameraGetRaster(cam))))+1); + uint32 height = Pow(2.0f, int32(log2(RwRasterGetHeight(RwCameraGetRaster(cam))))+1); + uint32 depth = RwRasterGetDepth(RwCameraGetRaster(cam)); + pFrontBuffer = RwRasterCreate(width, height, depth, rwRASTERTYPECAMERATEXTURE); + pBackBuffer = RwRasterCreate(width, height, depth, rwRASTERTYPECAMERATEXTURE); + bJustInitialised = true; + + float zero, xmax, ymax; + + if(RwRasterGetDepth(RwCameraGetRaster(cam)) == 16){ + zero = HALFPX; + xmax = width + HALFPX; + ymax = height + HALFPX; + }else{ + zero = -HALFPX; + xmax = width - HALFPX; + ymax = height - HALFPX; + } + + RwIm2DVertexSetScreenX(&Vertex[0], zero); + RwIm2DVertexSetScreenY(&Vertex[0], zero); + RwIm2DVertexSetScreenZ(&Vertex[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[0], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[1], zero); + RwIm2DVertexSetScreenY(&Vertex[1], ymax); + RwIm2DVertexSetScreenZ(&Vertex[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[1], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[1], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[1], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[2], xmax); + RwIm2DVertexSetScreenY(&Vertex[2], ymax); + RwIm2DVertexSetScreenZ(&Vertex[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[2], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[3], xmax); + RwIm2DVertexSetScreenY(&Vertex[3], zero); + RwIm2DVertexSetScreenZ(&Vertex[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[3], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[3], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[3], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, 255); + + + RwIm2DVertexSetScreenX(&Vertex2[0], zero + 2.0f); + RwIm2DVertexSetScreenY(&Vertex2[0], zero + 2.0f); + RwIm2DVertexSetScreenZ(&Vertex2[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex2[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex2[0], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex2[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex2[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex2[0], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex2[1], 2.0f); + RwIm2DVertexSetScreenY(&Vertex2[1], ymax + 2.0f); + RwIm2DVertexSetScreenZ(&Vertex2[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex2[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex2[1], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex2[1], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex2[1], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex2[1], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex2[2], xmax + 2.0f); + RwIm2DVertexSetScreenY(&Vertex2[2], ymax + 2.0f); + RwIm2DVertexSetScreenZ(&Vertex2[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex2[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex2[2], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex2[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex2[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex2[2], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex2[3], xmax + 2.0f); + RwIm2DVertexSetScreenY(&Vertex2[3], zero + 2.0f); + RwIm2DVertexSetScreenZ(&Vertex2[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex2[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex2[3], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex2[3], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex2[3], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex2[3], 255, 255, 255, 255); + + +#ifdef RW_D3D9 +#include "shaders/obj/colourfilterIII_PS.inc" + colourfilterIII_PS = rw::d3d::createPixelShader(colourfilterIII_PS_cso); +#include "shaders/obj/contrastPS.inc" + contrast_PS = rw::d3d::createPixelShader(contrastPS_cso); +#endif +#ifdef RW_OPENGL + using namespace rw::gl3; + { +#include "shaders/obj/im2d_vert.inc" +#include "shaders/obj/colourfilterIII_frag.inc" + const char *vs[] = { shaderDecl, header_vert_src, im2d_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, colourfilterIII_frag_src, nil }; + colourFilterIII = Shader::create(vs, fs); + assert(colourFilterIII); + } + + { +#include "shaders/obj/im2d_vert.inc" +#include "shaders/obj/contrast_frag.inc" + const char *vs[] = { shaderDecl, header_vert_src, im2d_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, contrast_frag_src, nil }; + contrast = Shader::create(vs, fs); + assert(contrast); + } + +#endif +} + +void +CPostFX::Close(void) +{ + if(pFrontBuffer){ + RwRasterDestroy(pFrontBuffer); + pFrontBuffer = nil; + } + if(pBackBuffer){ + RwRasterDestroy(pBackBuffer); + pBackBuffer = nil; + } +#ifdef RW_D3D9 + if(colourfilterIII_PS){ + rw::d3d::destroyPixelShader(colourfilterIII_PS); + colourfilterIII_PS = nil; + } + if(contrast_PS){ + rw::d3d::destroyPixelShader(contrast_PS); + contrast_PS = nil; + } +#endif +#ifdef RW_OPENGL + if(colourFilterIII){ + colourFilterIII->destroy(); + colourFilterIII = nil; + } + if(contrast){ + contrast->destroy(); + contrast = nil; + } +#endif +} + +void +CPostFX::RenderOverlayBlur(RwCamera *cam, int32 r, int32 g, int32 b, int32 a) +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, pFrontBuffer); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[3], r, g, b, a); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); +} + +void +CPostFX::RenderOverlaySimple(RwCamera *cam, int32 r, int32 g, int32 b, int32 a) +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + r *= 0.6f; + g *= 0.6f; + b *= 0.6f; + a *= 0.6f; + + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[3], r, g, b, a); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); +} + +void +CPostFX::RenderOverlaySniper(RwCamera *cam, int32 r, int32 g, int32 b, int32 a) +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, pFrontBuffer); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, 80); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, 80); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, 80); + RwIm2DVertexSetIntRGBA(&Vertex[3], r, g, b, 80); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); +} + +float CPostFX::Intensity = 1.0f; + +void +CPostFX::RenderOverlayShader(RwCamera *cam, int32 r, int32 g, int32 b, int32 a) +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, pBackBuffer); + + if(EffectSwitch == POSTFX_MOBILE){ + float mult[3], add[3]; + mult[0] = (r-64)/384.0f + 1.14f; + mult[1] = (g-64)/384.0f + 1.14f; + mult[2] = (b-64)/384.0f + 1.14f; + add[0] = r/1536.f; + add[1] = g/1536.f; + add[2] = b/1536.f; +#ifdef RW_D3D9 + rw::d3d::d3ddevice->SetPixelShaderConstantF(10, mult, 1); + rw::d3d::d3ddevice->SetPixelShaderConstantF(11, add, 1); + + rw::d3d::im2dOverridePS = contrast_PS; +#endif +#ifdef RW_OPENGL + rw::gl3::im2dOverrideShader = contrast; + contrast->use(); + glUniform3fv(contrast->uniformLocations[u_contrastMult], 1, mult); + glUniform3fv(contrast->uniformLocations[u_contrastAdd], 1, add); +#endif + }else{ + float f = Intensity; + float blurcolors[4]; + blurcolors[0] = r/255.0f; + blurcolors[1] = g/255.0f; + blurcolors[2] = b/255.0f; + blurcolors[3] = a*f/255.0f; +#ifdef RW_D3D9 + rw::d3d::d3ddevice->SetPixelShaderConstantF(10, blurcolors, 1); + rw::d3d::im2dOverridePS = colourfilterIII_PS; +#endif +#ifdef RW_OPENGL + rw::gl3::im2dOverrideShader = colourFilterIII; + colourFilterIII->use(); + glUniform4fv(colourFilterIII->uniformLocations[u_blurcolor], 1, blurcolors); +#endif + } + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); +#ifdef RW_D3D9 + rw::d3d::im2dOverridePS = nil; +#endif +#ifdef RW_OPENGL + rw::gl3::im2dOverrideShader = nil; +#endif +} + +void +CPostFX::RenderMotionBlur(RwCamera *cam, uint32 blur) +{ + if(blur == 0) + return; + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, pFrontBuffer); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, blur); + RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, blur); + RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, blur); + RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, blur); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); +} + +bool +CPostFX::NeedBackBuffer(void) +{ + // Current frame -- needed for non-blur effect + switch(EffectSwitch){ + case POSTFX_OFF: + // no actual rendering here + return false; + case POSTFX_SIMPLE: + return false; + case POSTFX_NORMAL: + if(MotionBlurOn) + return false; + else + return true; + case POSTFX_MOBILE: + return true; + } + return false; +} + +bool +CPostFX::NeedFrontBuffer(int32 type) +{ + // Last frame -- needed for motion blur + if(MotionBlurOn) + return true; + if(type == MOTION_BLUR_SNIPER) + return true; + + return false; +} + +void +CPostFX::GetBackBuffer(RwCamera *cam) +{ + RwRasterPushContext(pBackBuffer); + RwRasterRenderFast(RwCameraGetRaster(cam), 0, 0); + RwRasterPopContext(); +} + +void +CPostFX::Render(RwCamera *cam, uint32 red, uint32 green, uint32 blue, uint32 blur, int32 type, uint32 bluralpha) +{ + switch(type) + { + case MOTION_BLUR_SECURITY_CAM: + red = 0; + green = 255; + blue = 0; + blur = 128; + break; + case MOTION_BLUR_INTRO: + red = 100; + green = 220; + blue = 230; + blur = 158; + break; + case MOTION_BLUR_INTRO2: + red = 80; + green = 255; + blue = 230; + blur = 138; + break; + case MOTION_BLUR_INTRO3: + red = 255; + green = 60; + blue = 60; + blur = 200; + break; + case MOTION_BLUR_INTRO4: + red = 255; + green = 180; + blue = 180; + blur = 128; + break; + } + + PUSH_RENDERGROUP("CPostFX::Render"); + if(pFrontBuffer == nil) + Open(cam); + assert(pFrontBuffer); + assert(pBackBuffer); + + if(NeedBackBuffer()) + GetBackBuffer(cam); + + DefinedState(); + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + + if(type == MOTION_BLUR_SNIPER){ + if(!bJustInitialised) + RenderOverlaySniper(cam, red, green, blue, blur); + }else switch(EffectSwitch){ + case POSTFX_OFF: + // no actual rendering here + break; + case POSTFX_SIMPLE: + RenderOverlaySimple(cam, red, green, blue, blur); + break; + case POSTFX_NORMAL: + if(MotionBlurOn){ + if(!bJustInitialised) + RenderOverlayBlur(cam, red, green, blue, blur); + }else{ + RenderOverlayShader(cam, red, green, blue, blur); + } + break; + case POSTFX_MOBILE: + RenderOverlayShader(cam, red, green, blue, blur); + break; + } + + // TODO? maybe we want this even without motion blur on sometimes? + if(MotionBlurOn) + if(!bJustInitialised) + RenderMotionBlur(cam, bluralpha); + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + if(NeedFrontBuffer(type)){ + RwRasterPushContext(pFrontBuffer); + RwRasterRenderFast(RwCameraGetRaster(cam), 0, 0); + RwRasterPopContext(); + bJustInitialised = false; + }else + bJustInitialised = true; + + POP_RENDERGROUP(); +} + +#endif diff --git a/src/extras/postfx.h b/src/extras/postfx.h new file mode 100644 index 0000000..f8779a6 --- /dev/null +++ b/src/extras/postfx.h @@ -0,0 +1,36 @@ +#pragma once + +#ifdef EXTENDED_COLOURFILTER + +class CPostFX +{ +public: + enum { + POSTFX_OFF, + POSTFX_SIMPLE, + POSTFX_NORMAL, + POSTFX_MOBILE + }; + static RwRaster *pFrontBuffer; + static RwRaster *pBackBuffer; + static bool bJustInitialised; + static int EffectSwitch; + static bool MotionBlurOn; // or use CMblur for that? + static float Intensity; + + static void InitOnce(void); + static void Open(RwCamera *cam); + static void Close(void); + static void RenderOverlayBlur(RwCamera *cam, int32 r, int32 g, int32 b, int32 a); + static void RenderOverlaySimple(RwCamera *cam, int32 r, int32 g, int32 b, int32 a); + static void RenderOverlaySniper(RwCamera *cam, int32 r, int32 g, int32 b, int32 a); + static void RenderOverlayShader(RwCamera *cam, int32 r, int32 g, int32 b, int32 a); + static void RenderMotionBlur(RwCamera *cam, uint32 blur); + static void Render(RwCamera *cam, uint32 red, uint32 green, uint32 blue, uint32 blur, int32 type, uint32 bluralpha); + static bool NeedBackBuffer(void); + static bool NeedFrontBuffer(int32 type); + static void GetBackBuffer(RwCamera *cam); + static bool UseBlurColours(void) { return EffectSwitch != POSTFX_SIMPLE; } +}; + +#endif diff --git a/src/extras/re3_inttypes.h b/src/extras/re3_inttypes.h new file mode 100644 index 0000000..bf0c53e --- /dev/null +++ b/src/extras/re3_inttypes.h @@ -0,0 +1,216 @@ +#define PRId8 "hhd" +#define PRId16 "hd" +#define PRId32 "ld" +#define PRId64 "lld" + +#define PRIdFAST8 "hhd" +#define PRIdFAST16 "hd" +#define PRIdFAST32 "ld" +#define PRIdFAST64 "lld" + +#define PRIdLEAST8 "hhd" +#define PRIdLEAST16 "hd" +#define PRIdLEAST32 "ld" +#define PRIdLEAST64 "lld" + +#define PRIdMAX "lld" +#define PRIdPTR "lld" + +#define PRIi8 "hhi" +#define PRIi16 "hi" +#define PRIi32 "li" +#define PRIi64 "lli" + +#define PRIiFAST8 "hhi" +#define PRIiFAST16 "hi" +#define PRIiFAST32 "li" +#define PRIiFAST64 "lli" + +#define PRIiLEAST8 "hhi" +#define PRIiLEAST16 "hi" +#define PRIiLEAST32 "li" +#define PRIiLEAST64 "lli" + +#define PRIiMAX "lli" +#define PRIiPTR "lli" + +#define PRIo8 "hho" +#define PRIo16 "ho" +#define PRIo32 "lo" +#define PRIo64 "llo" + +#define PRIoFAST8 "hho" +#define PRIoFAST16 "ho" +#define PRIoFAST32 "lo" +#define PRIoFAST64 "llo" + +#define PRIoLEAST8 "hho" +#define PRIoLEAST16 "ho" +#define PRIoLEAST32 "lo" +#define PRIoLEAST64 "llo" + +#define PRIoMAX "llo" +#define PRIoPTR "llo" + +#define PRIu8 "hhu" +#define PRIu16 "hu" +#define PRIu32 "lu" +#define PRIu64 "llu" + +#define PRIuFAST8 "hhu" +#define PRIuFAST16 "hu" +#define PRIuFAST32 "lu" +#define PRIuFAST64 "llu" + +#define PRIuLEAST8 "hhu" +#define PRIuLEAST16 "hu" +#define PRIuLEAST32 "lu" +#define PRIuLEAST64 "llu" + +#define PRIuMAX "llu" +#define PRIuPTR "llu" + +#define PRIx8 "hhx" +#define PRIx16 "hx" +#define PRIx32 "lx" +#define PRIx64 "llx" + +#define PRIxFAST8 "hhx" +#define PRIxFAST16 "hx" +#define PRIxFAST32 "lx" +#define PRIxFAST64 "llx" + +#define PRIxLEAST8 "hhx" +#define PRIxLEAST16 "hx" +#define PRIxLEAST32 "lx" +#define PRIxLEAST64 "llx" + +#define PRIxMAX "llx" +#define PRIxPTR "llx" + +#define PRIX8 "hhX" +#define PRIX16 "hX" +#define PRIX32 "lX" +#define PRIX64 "llX" + +#define PRIXFAST8 "hhX" +#define PRIXFAST16 "hX" +#define PRIXFAST32 "lX" +#define PRIXFAST64 "llX" + +#define PRIXLEAST8 "hhX" +#define PRIXLEAST16 "hX" +#define PRIXLEAST32 "lX" +#define PRIXLEAST64 "llX" + +#define PRIXMAX "llX" +#define PRIXPTR "llX" + + /* SCAN FORMAT MACROS */ +#define SCNd8 "hhd" +#define SCNd16 "hd" +#define SCNd32 "ld" +#define SCNd64 "lld" + +#define SCNdFAST8 "hhd" +#define SCNdFAST16 "hd" +#define SCNdFAST32 "ld" +#define SCNdFAST64 "lld" + +#define SCNdLEAST8 "hhd" +#define SCNdLEAST16 "hd" +#define SCNdLEAST32 "ld" +#define SCNdLEAST64 "lld" + +#define SCNdMAX "lld" +#define SCNdPTR "lld" + +#define SCNi8 "hhi" +#define SCNi16 "hi" +#define SCNi32 "li" +#define SCNi64 "lli" + +#define SCNiFAST8 "hhi" +#define SCNiFAST16 "hi" +#define SCNiFAST32 "li" +#define SCNiFAST64 "lli" + +#define SCNiLEAST8 "hhi" +#define SCNiLEAST16 "hi" +#define SCNiLEAST32 "li" +#define SCNiLEAST64 "lli" + +#define SCNiMAX "lli" +#define SCNiPTR "lli" + +#define SCNo8 "hho" +#define SCNo16 "ho" +#define SCNo32 "lo" +#define SCNo64 "llo" + +#define SCNoFAST8 "hho" +#define SCNoFAST16 "ho" +#define SCNoFAST32 "lo" +#define SCNoFAST64 "llo" + +#define SCNoLEAST8 "hho" +#define SCNoLEAST16 "ho" +#define SCNoLEAST32 "lo" +#define SCNoLEAST64 "llo" + +#define SCNoMAX "llo" +#define SCNoPTR "llo" + +#define SCNu8 "hhu" +#define SCNu16 "hu" +#define SCNu32 "lu" +#define SCNu64 "llu" + +#define SCNuFAST8 "hhu" +#define SCNuFAST16 "hu" +#define SCNuFAST32 "lu" +#define SCNuFAST64 "llu" + +#define SCNuLEAST8 "hhu" +#define SCNuLEAST16 "hu" +#define SCNuLEAST32 "lu" +#define SCNuLEAST64 "llu" + +#define SCNuMAX "llu" +#define SCNuPTR "llu" + +#define SCNx8 "hhx" +#define SCNx16 "hx" +#define SCNx32 "lx" +#define SCNx64 "llx" + +#define SCNxFAST8 "hhx" +#define SCNxFAST16 "hx" +#define SCNxFAST32 "lx" +#define SCNxFAST64 "llx" + +#define SCNxLEAST8 "hhx" +#define SCNxLEAST16 "hx" +#define SCNxLEAST32 "lx" +#define SCNxLEAST64 "llx" + +#define SCNxMAX "llx" +#define SCNxPTR "llx" + +#define SCNX8 "hhX" +#define SCNX16 "hX" +#define SCNX32 "lX" +#define SCNX64 "llX" + +#define SCNXFAST8 "hhX" +#define SCNXFAST16 "hX" +#define SCNXFAST32 "lX" +#define SCNXFAST64 "llX" + +#define SCNXLEAST8 "hhX" +#define SCNXLEAST16 "hX" +#define SCNXLEAST32 "lX" +#define SCNXLEAST64 "llX" + +#define SCNXMAX "llX" +#define SCNXPTR "llX" \ No newline at end of file diff --git a/src/extras/screendroplets.cpp b/src/extras/screendroplets.cpp new file mode 100644 index 0000000..cc86808 --- /dev/null +++ b/src/extras/screendroplets.cpp @@ -0,0 +1,817 @@ +#define WITHD3D +#include "common.h" + +#ifdef SCREEN_DROPLETS + +#ifndef LIBRW +#error "Need librw for SCREEN_DROPLETS" +#endif + +#include "General.h" +#include "main.h" +#include "RwHelper.h" +#include "Timer.h" +#include "Camera.h" +#include "World.h" +#include "ZoneCull.h" +#include "Weather.h" +#include "ParticleObject.h" + #include "Pad.h" +#include "RenderBuffer.h" +#include "custompipes.h" +#include "postfx.h" +#include "screendroplets.h" + +// for 640 +#define MAXSIZE 15 +#define MINSIZE 4 + +int ScreenDroplets::ms_initialised; +RwTexture *ScreenDroplets::ms_maskTex; +RwTexture *ScreenDroplets::ms_screenTex; + +bool ScreenDroplets::ms_enabled = true; +bool ScreenDroplets::ms_movingEnabled = true; + +ScreenDroplets::ScreenDrop ScreenDroplets::ms_drops[ScreenDroplets::MAXDROPS]; +int ScreenDroplets::ms_numDrops; +ScreenDroplets::ScreenDropMoving ScreenDroplets::ms_dropsMoving[ScreenDroplets::MAXDROPSMOVING]; +int ScreenDroplets::ms_numDropsMoving; + +CVector ScreenDroplets::ms_prevCamUp; +CVector ScreenDroplets::ms_prevCamPos; +CVector ScreenDroplets::ms_camMoveDelta; +float ScreenDroplets::ms_camMoveDist; +CVector ScreenDroplets::ms_screenMoveDelta; +float ScreenDroplets::ms_screenMoveDist; +float ScreenDroplets::ms_camUpAngle; + +int ScreenDroplets::ms_splashDuration; +CParticleObject *ScreenDroplets::ms_splashObject; + +struct Im2DVertexUV2 : rw::RWDEVICE::Im2DVertex +{ + rw::float32 u2, v2; +}; + +#ifdef RW_D3D9 +static void *screenDroplet_PS; +#endif +#ifdef RW_GL3 +static rw::gl3::Shader *screenDroplet; +#endif + +// platform specific +static void openim2d_uv2(void); +static void closeim2d_uv2(void); +static void RenderIndexedPrimitive_UV2(RwPrimitiveType primType, Im2DVertexUV2 *vertices, RwInt32 numVertices, RwImVertexIndex *indices, RwInt32 numIndices); + +static Im2DVertexUV2 VertexBuffer[TEMPBUFFERVERTSIZE]; + +void +ScreenDroplets::Initialise(void) +{ + Clear(); + ms_splashDuration = -1; + ms_splashObject = nil; +} + +// Create white circle mask for rain drops +static RwTexture* +CreateDropMask(int32 size) +{ + RwImage *img = RwImageCreate(size, size, 32); + RwImageAllocatePixels(img); + + uint8 *pixels = RwImageGetPixels(img); + int32 stride = RwImageGetStride(img); + + for(int y = 0; y < size; y++){ + float yf = ((y + 0.5f)/size - 0.5f)*2.0f; + for(int x = 0; x < size; x++){ + float xf = ((x + 0.5f)/size - 0.5f)*2.0f; + memset(&pixels[y*stride + x*4], xf*xf + yf*yf < 1.0f ? 0xFF : 0x00, 4); + } + } + + int32 width, height, depth, format; + RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &width, &height, &depth, &format); + RwRaster *ras = RwRasterCreate(width, height, depth, format); + RwRasterSetFromImage(ras, img); + RwImageDestroy(img); + return RwTextureCreate(ras); +} + +void +ScreenDroplets::InitDraw(void) +{ + ms_maskTex = CreateDropMask(64); + + ms_screenTex = RwTextureCreate(nil); + RwTextureSetFilterMode(ms_screenTex, rwFILTERLINEAR); + + openim2d_uv2(); +#ifdef RW_D3D9 +#include "shaders/obj/screenDroplet_PS.inc" + screenDroplet_PS = rw::d3d::createPixelShader(screenDroplet_PS_cso); +#endif +#ifdef RW_GL3 + using namespace rw::gl3; + { +#include "shaders/obj/im2d_UV2_vert.inc" +#include "shaders/obj/screenDroplet_frag.inc" + const char *vs[] = { shaderDecl, header_vert_src, im2d_UV2_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, screenDroplet_frag_src, nil }; + screenDroplet = Shader::create(vs, fs); + assert(screenDroplet); + } +#endif + + ms_initialised = 1; +} + +void +ScreenDroplets::Shutdown(void) +{ + if(ms_maskTex){ + RwTextureDestroy(ms_maskTex); + ms_maskTex = nil; + } + if(ms_screenTex){ + RwTextureSetRaster(ms_screenTex, nil); + RwTextureDestroy(ms_screenTex); + ms_screenTex = nil; + } +#ifdef RW_D3D9 + if(screenDroplet_PS){ + rw::d3d::destroyPixelShader(screenDroplet_PS); + screenDroplet_PS = nil; + } +#endif +#ifdef RW_GL3 + if(screenDroplet){ + screenDroplet->destroy(); + screenDroplet = nil; + } +#endif + + closeim2d_uv2(); +} + +void +ScreenDroplets::Process(void) +{ + ProcessCameraMovement(); + SprayDrops(); + ProcessMoving(); + Fade(); +} + +static void +FlushBuffer(void) +{ + if(TempBufferIndicesStored){ + RenderIndexedPrimitive_UV2(rwPRIMTYPETRILIST, + VertexBuffer, TempBufferVerticesStored, + TempBufferRenderIndexList, TempBufferIndicesStored); + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + } +} + +static int +StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, Im2DVertexUV2 **vertexStart) +{ + if(TempBufferIndicesStored + numIndices >= TEMPBUFFERINDEXSIZE || + TempBufferVerticesStored + numVertices >= TEMPBUFFERVERTSIZE) + FlushBuffer(); + *indexStart = &TempBufferRenderIndexList[TempBufferIndicesStored]; + *vertexStart = &VertexBuffer[TempBufferVerticesStored]; + int vertOffset = TempBufferVerticesStored; + TempBufferIndicesStored += numIndices; + TempBufferVerticesStored += numVertices; + return vertOffset; +} + +void +ScreenDroplets::Render(void) +{ + ScreenDrop *drop; + + DefinedState(); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(ms_maskTex)); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + RwTextureSetRaster(ms_screenTex, CPostFX::pBackBuffer); +#ifdef RW_D3D9 + rw::d3d::im2dOverridePS = screenDroplet_PS; + rw::d3d::setTexture(1, ms_screenTex); +#endif +#ifdef RW_GL3 + rw::gl3::im2dOverrideShader = screenDroplet; + rw::gl3::setTexture(1, ms_screenTex); +#endif + + RenderBuffer::ClearRenderBuffer(); + for(drop = &ms_drops[0]; drop < &ms_drops[MAXDROPS]; drop++) + if(drop->active) + AddToRenderList(drop); + FlushBuffer(); + +#ifdef RW_D3D9 + rw::d3d::im2dOverridePS = nil; + rw::d3d::setTexture(1, nil); +#endif +#ifdef RW_GL3 + rw::gl3::im2dOverrideShader = nil; + rw::gl3::setTexture(1, nil); +#endif + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, FALSE); +} + +void +ScreenDroplets::AddToRenderList(ScreenDroplets::ScreenDrop *drop) +{ + static float xy[] = { + -1.0f, -1.0f, + -1.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, -1.0f + }; + static float uv[] = { + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f + }; + + int i; + RwImVertexIndex *indices; + Im2DVertexUV2 *verts; + int first = StartStoring(6, 4, &indices, &verts); + + float scale = 0.5f*SCREEN_SCALE_X(drop->size); + + float screenz = RwIm2DGetNearScreenZ(); + float z = RwCameraGetNearClipPlane(Scene.camera); + float recipz = 1.0f/z; + + float magSize = SCREEN_SCALE_Y(drop->magnification*(300.0f-40.0f) + 40.0f); + float ul = drop->x - magSize; + float vt = drop->y - magSize; + float ur = drop->x + magSize; + float vb = drop->y + magSize; + ul = Max(ul, 0.0f)/RwRasterGetWidth(CPostFX::pBackBuffer); + vt = Max(vt, 0.0f)/RwRasterGetHeight(CPostFX::pBackBuffer); + ur = Min(ur, SCREEN_WIDTH)/RwRasterGetWidth(CPostFX::pBackBuffer); + vb = Min(vb, SCREEN_HEIGHT)/RwRasterGetHeight(CPostFX::pBackBuffer); + + for(i = 0; i < 4; i++){ + RwIm2DVertexSetScreenX(&verts[i], drop->x + xy[i*2]*scale); + RwIm2DVertexSetScreenY(&verts[i], drop->y + xy[i*2+1]*scale); + RwIm2DVertexSetScreenZ(&verts[i], screenz); + RwIm2DVertexSetCameraZ(&verts[i], z); + RwIm2DVertexSetRecipCameraZ(&verts[i], recipz); + RwIm2DVertexSetIntRGBA(&verts[i], drop->color.r, drop->color.g, drop->color.b, drop->color.a); + RwIm2DVertexSetU(&verts[i], uv[i*2], recipz); + RwIm2DVertexSetV(&verts[i], uv[i*2+1], recipz); + + verts[i].u2 = i < 2 ? ul : ur; + verts[i].v2 = i % 3 ? vt : vb; + } + indices[0] = first + 0; + indices[1] = first + 1; + indices[2] = first + 2; + indices[3] = first + 2; + indices[4] = first + 3; + indices[5] = first + 0; +} + +void +ScreenDroplets::Clear(void) +{ + ScreenDrop *drop; + for(drop = &ms_drops[0]; drop < &ms_drops[MAXDROPS]; drop++) + drop->active = false; + ms_numDrops = 0; +} + +ScreenDroplets::ScreenDrop* +ScreenDroplets::NewDrop(float x, float y, float size, float lifetime, bool fades, int r, int g, int b) +{ + ScreenDrop *drop; + int i; + + for(i = 0, drop = ms_drops; i < MAXDROPS; i++, drop++) + if(!ms_drops[i].active) + goto found; + return nil; +found: + ms_numDrops++; + drop->x = x; + drop->y = y; + drop->size = size; + drop->magnification = (MAXSIZE - size + 1.0f) / (MAXSIZE - MINSIZE + 1.0f); + drop->fades = fades; + drop->active = true; + drop->color.r = r; + drop->color.g = g; + drop->color.b = b; + drop->color.a = 255; + drop->time = 0.0f; + drop->lifetime = lifetime; + return drop; +} + +void +ScreenDroplets::SetMoving(ScreenDroplets::ScreenDrop *drop) +{ + ScreenDropMoving *moving; + for(moving = ms_dropsMoving; moving < &ms_dropsMoving[MAXDROPSMOVING]; moving++) + if(moving->drop == nil) + goto found; + return; +found: + ms_numDropsMoving++; + moving->drop = drop; + moving->dist = 0.0f; +} + +void +ScreenDroplets::FillScreen(int n) +{ + float x, y, size; + ScreenDrop *drop; + + if(!ms_initialised) + return; + ms_numDrops = 0; + for(drop = &ms_drops[0]; drop < &ms_drops[MAXDROPS]; drop++){ + drop->active = false; + if(drop < &ms_drops[n]){ + x = CGeneral::GetRandomNumber() % (int)SCREEN_WIDTH; + y = CGeneral::GetRandomNumber() % (int)SCREEN_HEIGHT; + size = CGeneral::GetRandomNumberInRange(MINSIZE, MAXSIZE); + NewDrop(x, y, size, 2000.0f, true); + } + } +} + +void +ScreenDroplets::FillScreenMoving(float amount, bool isBlood) +{ + int n = (ms_screenMoveDelta.z > 5.0f ? 1.5f : 1.0f)*amount*20.0f; + float x, y, size; + ScreenDrop *drop; + + while(n--) + if(ms_numDrops < MAXDROPS && ms_numDropsMoving < MAXDROPSMOVING){ + x = CGeneral::GetRandomNumber() % (int)SCREEN_WIDTH; + y = CGeneral::GetRandomNumber() % (int)SCREEN_HEIGHT; + size = CGeneral::GetRandomNumberInRange(MINSIZE, MAXSIZE); + drop = nil; + if(isBlood) + drop = NewDrop(x, y, size, 2000.0f, true, 255, 0, 0); + else + drop = NewDrop(x, y, size, 2000.0f, true); + if(drop) + SetMoving(drop); + } +} + +void +ScreenDroplets::RegisterSplash(CParticleObject *pobj) +{ + CVector dist = pobj->GetPosition() - ms_prevCamPos; + if(dist.MagnitudeSqr() < 50.0f){ // 20 originally + ms_splashDuration = 14; + ms_splashObject = pobj; + } +} + +void +ScreenDroplets::ProcessCameraMovement(void) +{ + RwMatrix *camMat = RwFrameGetMatrix(RwCameraGetFrame(Scene.camera)); + CVector camPos = camMat->pos; + CVector camUp = camMat->at; + ms_camMoveDelta = camPos - ms_prevCamPos; + ms_camMoveDist = ms_camMoveDelta.Magnitude(); + + ms_prevCamUp = camUp; + ms_prevCamPos = camPos; + + ms_screenMoveDelta.x = -RwV3dDotProduct(&camMat->right, &ms_camMoveDelta); + ms_screenMoveDelta.y = RwV3dDotProduct(&camMat->up, &ms_camMoveDelta); + ms_screenMoveDelta.z = RwV3dDotProduct(&camMat->at, &ms_camMoveDelta); + ms_screenMoveDelta *= 10.0f; + ms_screenMoveDist = ms_screenMoveDelta.Magnitude2D(); + + uint16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + bool isTopDown = mode == CCam::MODE_TOPDOWN || mode == CCam::MODE_GTACLASSIC || mode == CCam::MODE_TOP_DOWN_PED; + bool isLookingInDirection = FindPlayerVehicle() && mode == CCam::MODE_1STPERSON && + (CPad::GetPad(0)->GetLookBehindForCar() || CPad::GetPad(0)->GetLookLeft() || CPad::GetPad(0)->GetLookRight()); + ms_enabled = !isTopDown && !isLookingInDirection; + ms_movingEnabled = !isTopDown && !isLookingInDirection; + + // 0 when looking stright up, 180 when looking up or down + ms_camUpAngle = RADTODEG(Acos(Clamp(camUp.z, -1.0f, 1.0f))); +} + +void +ScreenDroplets::SprayDrops(void) +{ + bool noRain = CCullZones::PlayerNoRain() || CCullZones::CamNoRain(); + if(!noRain && CWeather::Rain > 0.0f && ms_enabled){ + // 180 when looking stright up, 0 when looking up or down + float angle = 180.0f - ms_camUpAngle; + angle = Max(angle, 40.0f); // want at least some rain + FillScreenMoving((angle - 40.0f) / 150.0f * CWeather::Rain * 0.5f); + } + + int i; + for(i = 0; i < MAX_AUDIOHYDRANTS; i++){ + CAudioHydrant *hyd = CAudioHydrant::Get(i); + if (hyd->pParticleObject){ + CVector dist = hyd->pParticleObject->GetPosition() - ms_prevCamPos; + if(dist.MagnitudeSqr() > 40.0f || + DotProduct(dist, ms_prevCamUp) < 0.0f) continue; + + FillScreenMoving(1.0f); + } + } + + static int ndrops[] = { + 125, 250, 500, 1000, 1000, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if(ms_splashDuration >= 0){ + if(ms_numDrops < MAXDROPS) { + float numDropMult = 1.0f; + if(ms_splashObject){ + float dist = (ms_splashObject->GetPosition() - ms_prevCamPos).Magnitude(); + numDropMult = 1.0f - (dist - 5.0f)/15.0f; + if(numDropMult < 0) numDropMult = 0.0f; // fix + } + int n = ndrops[ms_splashDuration] * numDropMult; + while(n--) + if(ms_numDrops < MAXDROPS){ + float x = CGeneral::GetRandomNumber() % (int)SCREEN_WIDTH; + float y = CGeneral::GetRandomNumber() % (int)SCREEN_HEIGHT; + float size = CGeneral::GetRandomNumberInRange(MINSIZE, MAXSIZE); + NewDrop(x, y, size, 10000.0f, false); + } + } + ms_splashDuration--; + } +} + +void +ScreenDroplets::NewTrace(ScreenDroplets::ScreenDropMoving *moving) +{ + if(ms_numDrops < MAXDROPS){ + moving->dist = 0.0f; + NewDrop(moving->drop->x, moving->drop->y, MINSIZE, 500.0f, true, + moving->drop->color.r, moving->drop->color.g, moving->drop->color.b); + } +} + +void +ScreenDroplets::MoveDrop(ScreenDroplets::ScreenDropMoving *moving) +{ + ScreenDrop *drop = moving->drop; + if(!ms_movingEnabled) + return; + if(!drop->active){ + moving->drop = nil; + ms_numDropsMoving--; + return; + } + if(ms_screenMoveDelta.z > 0.0f && ms_camMoveDist > 0.3f){ + if(ms_screenMoveDist > 0.5f && TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_1STPERSON){ + // movement when camera turns + moving->dist += ms_screenMoveDist; + if(moving->dist > 20.0f && drop->color.a > 100) + NewTrace(moving); + + drop->x -= ms_screenMoveDelta.x; + drop->y += ms_screenMoveDelta.y; + }else{ + // movement out of center + float d = ms_screenMoveDelta.z*0.2f; + float dx, dy, sum; + dx = drop->x - SCREEN_WIDTH*0.5f + ms_screenMoveDelta.x; + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON) + dy = drop->y - SCREEN_HEIGHT*1.2f - ms_screenMoveDelta.y; + else + dy = drop->y - SCREEN_HEIGHT*0.5f - ms_screenMoveDelta.y; + sum = fabs(dx) + fabs(dy); + if(sum > 0.001f){ + dx /= sum; + dy /= sum; + } + moving->dist += d; + if(moving->dist > 20.0f && drop->color.a > 100) + NewTrace(moving); + drop->x += dx * d; + drop->y += dy * d; + } + + if(drop->x < 0.0f || drop->y < 0.0f || + drop->x > SCREEN_WIDTH || drop->y > SCREEN_HEIGHT){ + moving->drop = nil; + ms_numDropsMoving--; + } + } +} + +void +ScreenDroplets::ProcessMoving(void) +{ + ScreenDropMoving *moving; + if(!ms_movingEnabled) + return; + for(moving = ms_dropsMoving; moving < &ms_dropsMoving[MAXDROPSMOVING]; moving++) + if(moving->drop) + MoveDrop(moving); +} + +void +ScreenDroplets::Fade(void) +{ + ScreenDrop *drop; + for(drop = &ms_drops[0]; drop < &ms_drops[MAXDROPS]; drop++) + if(drop->active) + drop->Fade(); +} + +void +ScreenDroplets::ScreenDrop::Fade(void) +{ + int delta = CTimer::GetTimeStepInMilliseconds(); + time += delta; + if(time < lifetime){ + color.a = 255 - time/lifetime*255; + }else if(fades){ + ScreenDroplets::ms_numDrops--; + active = false; + } +} + + +/* + * Im2D with two uv coors + */ + +#ifdef RW_D3D9 +// stolen from RW, not in a public header +namespace rw { +namespace d3d { +void addDynamicVB(uint32 length, uint32 fvf, IDirect3DVertexBuffer9 **buf); // NB: don't share this pointer +void removeDynamicVB(IDirect3DVertexBuffer9 **buf); +void addDynamicIB(uint32 length, IDirect3DIndexBuffer9 **buf); // NB: don't share this pointer +void removeDynamicIB(IDirect3DIndexBuffer9 **buf); +} +} +// different than im2d +#define NUMINDICES 1024 +#define NUMVERTICES 1024 + +static int primTypeMap[] = { + D3DPT_POINTLIST, // invalid + D3DPT_LINELIST, + D3DPT_LINESTRIP, + D3DPT_TRIANGLELIST, + D3DPT_TRIANGLESTRIP, + D3DPT_TRIANGLEFAN, + D3DPT_POINTLIST, // actually not supported! +}; +// end of stolen stuff + + +static IDirect3DVertexDeclaration9 *im2ddecl_uv2; +static IDirect3DVertexBuffer9 *im2dvertbuf_uv2; +static IDirect3DIndexBuffer9 *im2dindbuf_uv2; + +void +openim2d_uv2(void) +{ + using namespace rw; + using namespace d3d; + D3DVERTEXELEMENT9 elements[5] = { + { 0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT, 0 }, + { 0, offsetof(Im2DVertexUV2, color), D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 }, + { 0, offsetof(Im2DVertexUV2, u), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, + { 0, offsetof(Im2DVertexUV2, u2), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 }, + D3DDECL_END() + }; + assert(im2ddecl_uv2 == nil); + im2ddecl_uv2 = (IDirect3DVertexDeclaration9*)d3d9::createVertexDeclaration((d3d9::VertexElement*)elements); + assert(im2ddecl_uv2); + + assert(im2dvertbuf_uv2 == nil); + im2dvertbuf_uv2 = (IDirect3DVertexBuffer9*)createVertexBuffer(NUMVERTICES*sizeof(Im2DVertexUV2), 0, true); + assert(im2dvertbuf_uv2); + addDynamicVB(NUMVERTICES*sizeof(Im2DVertexUV2), 0, &im2dvertbuf_uv2); + + assert(im2dindbuf_uv2 == nil); + im2dindbuf_uv2 = (IDirect3DIndexBuffer9*)createIndexBuffer(NUMINDICES*sizeof(rw::uint16), true); + assert(im2dindbuf_uv2); + addDynamicIB(NUMINDICES*sizeof(rw::uint16), &im2dindbuf_uv2); +} + +void +closeim2d_uv2(void) +{ + using namespace rw; + using namespace d3d; + + d3d9::destroyVertexDeclaration(im2ddecl_uv2); + im2ddecl_uv2 = nil; + + removeDynamicVB(&im2dvertbuf_uv2); + destroyVertexBuffer(im2dvertbuf_uv2); + im2dvertbuf_uv2 = nil; + + removeDynamicIB(&im2dindbuf_uv2); + destroyIndexBuffer(im2dindbuf_uv2); + im2dindbuf_uv2 = nil; +} + +void +RenderIndexedPrimitive_UV2(RwPrimitiveType primType, Im2DVertexUV2 *vertices, RwInt32 numVertices, RwImVertexIndex *indices, RwInt32 numIndices) +{ + using namespace rw; + using namespace d3d; + + if(numVertices > NUMVERTICES || + numIndices > NUMINDICES){ + // TODO: error + return; + } + rw::uint16 *lockedindices = lockIndices(im2dindbuf_uv2, 0, numIndices*sizeof(rw::uint16), D3DLOCK_DISCARD); + memcpy(lockedindices, indices, numIndices*sizeof(rw::uint16)); + unlockIndices(im2dindbuf_uv2); + + rw::uint8 *lockedvertices = lockVertices(im2dvertbuf_uv2, 0, numVertices*sizeof(Im2DVertexUV2), D3DLOCK_DISCARD); + memcpy(lockedvertices, vertices, numVertices*sizeof(Im2DVertexUV2)); + unlockVertices(im2dvertbuf_uv2); + + setStreamSource(0, im2dvertbuf_uv2, 0, sizeof(Im2DVertexUV2)); + setIndices(im2dindbuf_uv2); + setVertexDeclaration(im2ddecl_uv2); + + if(im2dOverridePS) + setPixelShader(im2dOverridePS); + else if(engine->device.getRenderState(TEXTURERASTER)) + setPixelShader(im2d_tex_PS); + else + setPixelShader(im2d_PS); + + d3d::flushCache(); + + rw::uint32 primCount = 0; + switch(primType){ + case PRIMTYPELINELIST: + primCount = numIndices/2; + break; + case PRIMTYPEPOLYLINE: + primCount = numIndices-1; + break; + case PRIMTYPETRILIST: + primCount = numIndices/3; + break; + case PRIMTYPETRISTRIP: + primCount = numIndices-2; + break; + case PRIMTYPETRIFAN: + primCount = numIndices-2; + break; + case PRIMTYPEPOINTLIST: + primCount = numIndices; + break; + } + d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)primTypeMap[primType], 0, + 0, numVertices, + 0, primCount); +} +#endif + +#ifdef RW_GL3 +// different than im2d +#define NUMINDICES 1024 +#define NUMVERTICES 1024 + +static rw::gl3::AttribDesc im2d_UV2_attribDesc[4] = { + { rw::gl3::ATTRIB_POS, GL_FLOAT, GL_FALSE, 4, + sizeof(Im2DVertexUV2), 0 }, + { rw::gl3::ATTRIB_COLOR, GL_UNSIGNED_BYTE, GL_TRUE, 4, + sizeof(Im2DVertexUV2), offsetof(Im2DVertexUV2, r) }, + { rw::gl3::ATTRIB_TEXCOORDS0, GL_FLOAT, GL_FALSE, 2, + sizeof(Im2DVertexUV2), offsetof(Im2DVertexUV2, u) }, + { rw::gl3::ATTRIB_TEXCOORDS1, GL_FLOAT, GL_FALSE, 2, + sizeof(Im2DVertexUV2), offsetof(Im2DVertexUV2, u2) } +}; + +static int primTypeMap[] = { + GL_POINTS, // invalid + GL_LINES, + GL_LINE_STRIP, + GL_TRIANGLES, + GL_TRIANGLE_STRIP, + GL_TRIANGLE_FAN, + GL_POINTS +}; + +static int32 u_xform; + +uint32 im2D_UV2_Vbo, im2D_UV2_Ibo; +#ifdef RW_GL_USE_VAOS +uint32 im2D_UV2_Vao; +#endif + +void +openim2d_uv2(void) +{ + u_xform = rw::gl3::registerUniform("u_xform", rw::gl3::UNIFORM_VEC4); // this doesn't add a new one, so it's safe + + glGenBuffers(1, &im2D_UV2_Ibo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im2D_UV2_Ibo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, NUMINDICES*2, nil, GL_STREAM_DRAW); + + glGenBuffers(1, &im2D_UV2_Vbo); + glBindBuffer(GL_ARRAY_BUFFER, im2D_UV2_Vbo); + glBufferData(GL_ARRAY_BUFFER, NUMVERTICES*sizeof(Im2DVertexUV2), nil, GL_STREAM_DRAW); + +#ifdef RW_GL_USE_VAOS + glGenVertexArrays(1, &im2D_UV2_Vao); + glBindVertexArray(im2D_UV2_Vao); + setAttribPointers(im2d_UV2_attribDesc, 4); +#endif +} + +void +closeim2d_uv2(void) +{ + glDeleteBuffers(1, &im2D_UV2_Ibo); + glDeleteBuffers(1, &im2D_UV2_Vbo); +#ifdef RW_GL_USE_VAOS + glDeleteVertexArrays(1, &im2D_UV2_Vao); +#endif +} + +void +RenderIndexedPrimitive_UV2(RwPrimitiveType primType, Im2DVertexUV2 *vertices, RwInt32 numVertices, RwImVertexIndex *indices, RwInt32 numIndices) +{ + using namespace rw; + using namespace gl3; + + GLfloat xform[4]; + Camera *cam; + cam = (Camera*)engine->currentCamera; + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(im2D_UV2_Vao); +#endif + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im2D_UV2_Ibo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, NUMINDICES*2, nil, GL_STREAM_DRAW); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, numIndices*2, indices); + + glBindBuffer(GL_ARRAY_BUFFER, im2D_UV2_Vbo); + glBufferData(GL_ARRAY_BUFFER, NUMVERTICES*sizeof(Im2DVertexUV2), nil, GL_STREAM_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices*sizeof(Im2DVertexUV2), vertices); + + xform[0] = 2.0f/cam->frameBuffer->width; + xform[1] = -2.0f/cam->frameBuffer->height; + xform[2] = -1.0f; + xform[3] = 1.0f; + + if(im2dOverrideShader) + im2dOverrideShader->use(); + else + assert(0);//im2dShader->use(); +#ifndef RW_GL_USE_VAOS + setAttribPointers(im2d_UV2_attribDesc, 4); +#endif + + setUniform(u_xform, xform); + + flushCache(); + glDrawElements(primTypeMap[primType], numIndices, + GL_UNSIGNED_SHORT, nil); +#ifndef RW_GL_USE_VAOS + disableAttribPointers(im2d_UV2_attribDesc, 4); +#endif +} +#endif + +#endif diff --git a/src/extras/screendroplets.h b/src/extras/screendroplets.h new file mode 100644 index 0000000..090b192 --- /dev/null +++ b/src/extras/screendroplets.h @@ -0,0 +1,78 @@ +#pragma once + +#ifdef SCREEN_DROPLETS + +class CParticleObject; + +class ScreenDroplets +{ +public: + enum { + MAXDROPS = 2000, + MAXDROPSMOVING = 700 + }; + + class ScreenDrop + { + public: + float x, y, time; // shorts on xbox (short float?) + float size, magnification, lifetime; // " + CRGBA color; + bool active; + bool fades; + + void Fade(void); + }; + + struct ScreenDropMoving + { + ScreenDrop *drop; + float dist; + }; + + static int ms_initialised; + static RwTexture *ms_maskTex; + static RwTexture *ms_screenTex; + + static bool ms_enabled; + static bool ms_movingEnabled; + + static ScreenDrop ms_drops[MAXDROPS]; + static int ms_numDrops; + static ScreenDropMoving ms_dropsMoving[MAXDROPSMOVING]; + static int ms_numDropsMoving; + + static CVector ms_prevCamUp; + static CVector ms_prevCamPos; + static CVector ms_camMoveDelta; + static float ms_camMoveDist; + static CVector ms_screenMoveDelta; + static float ms_screenMoveDist; + static float ms_camUpAngle; + + static int ms_splashDuration; + static CParticleObject *ms_splashObject; + + static void Initialise(void); + static void InitDraw(void); + static void Shutdown(void); + static void Process(void); + static void Render(void); + static void AddToRenderList(ScreenDrop *drop); + + static void Clear(void); + static ScreenDrop *NewDrop(float x, float y, float size, float lifetime, bool fades, int r = 255, int g = 255, int b = 255); + static void SetMoving(ScreenDroplets::ScreenDrop *drop); + static void FillScreen(int n); + static void FillScreenMoving(float amount, bool isBlood = false); + static void RegisterSplash(CParticleObject *pobj); + + static void ProcessCameraMovement(void); + static void SprayDrops(void); + static void NewTrace(ScreenDroplets::ScreenDropMoving *moving); + static void MoveDrop(ScreenDropMoving *moving); + static void ProcessMoving(void); + static void Fade(void); +}; + +#endif diff --git a/src/extras/shaders/colourfilterIII.frag b/src/extras/shaders/colourfilterIII.frag new file mode 100644 index 0000000..95e5d05 --- /dev/null +++ b/src/extras/shaders/colourfilterIII.frag @@ -0,0 +1,24 @@ +uniform sampler2D tex0; +uniform vec4 u_blurcolor; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN float v_fog; + +void +main(void) +{ + float a = u_blurcolor.a; + vec4 dst = texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + vec4 prev = dst; + for(int i = 0; i < 5; i++){ + vec4 tmp = dst*(1.0-a) + prev*u_blurcolor*a; + prev = clamp(tmp, 0.0, 1.0); + } + vec4 color; + color.rgb = prev.rgb; + color.a = 1.0; + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/colourfilterIII_PS.hlsl b/src/extras/shaders/colourfilterIII_PS.hlsl new file mode 100644 index 0000000..3d893c3 --- /dev/null +++ b/src/extras/shaders/colourfilterIII_PS.hlsl @@ -0,0 +1,15 @@ +sampler2D tex : register(s0); +float4 blurcol : register(c10); + +float4 main(in float2 texcoord : TEXCOORD0) : COLOR0 +{ + float a = blurcol.a; + float4 dst = tex2D(tex, texcoord.xy); + float4 prev = dst; + for(int i = 0; i < 5; i++){ + float4 tmp = dst*(1-a) + prev*blurcol*a; + prev = saturate(tmp); + } + prev.a = 1.0; + return prev; +} diff --git a/src/extras/shaders/contrast.frag b/src/extras/shaders/contrast.frag new file mode 100644 index 0000000..2d394f6 --- /dev/null +++ b/src/extras/shaders/contrast.frag @@ -0,0 +1,19 @@ +uniform sampler2D tex0; +uniform vec3 u_contrastAdd; +uniform vec3 u_contrastMult; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN float v_fog; + +void +main(void) +{ + vec4 dst = texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + vec4 color; + color.rgb = dst.rgb*u_contrastMult + u_contrastAdd; + color.a = 1.0; + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/contrastPS.hlsl b/src/extras/shaders/contrastPS.hlsl new file mode 100644 index 0000000..a1de1d8 --- /dev/null +++ b/src/extras/shaders/contrastPS.hlsl @@ -0,0 +1,21 @@ +struct PS_INPUT +{ + float4 position : POSITION; + float3 texcoord0 : TEXCOORD0; + float4 color : COLOR0; +}; + +uniform float3 contrastMult : register(c10); +uniform float3 contrastAdd : register(c11); + +sampler2D tex : register(s0); + +float4 +main(PS_INPUT In) : COLOR +{ + float4 dst = tex2D(tex, In.texcoord0.xy); + + dst.rgb = dst.rgb*contrastMult + contrastAdd; + dst.a = 1.0; + return dst; +} diff --git a/src/extras/shaders/default_UV2.vert b/src/extras/shaders/default_UV2.vert new file mode 100644 index 0000000..694c012 --- /dev/null +++ b/src/extras/shaders/default_UV2.vert @@ -0,0 +1,25 @@ +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT vec2 v_tex1; +VSOUT float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + v_tex1 = in_tex1; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/extras/shaders/default_UV2_VS.hlsl b/src/extras/shaders/default_UV2_VS.hlsl new file mode 100644 index 0000000..e78a990 --- /dev/null +++ b/src/extras/shaders/default_UV2_VS.hlsl @@ -0,0 +1,54 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float2 TexCoord1 : TEXCOORD1; + float4 Prelight : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; +}; + + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 Vertex = mul(worldMat, input.Position).xyz; + float3 Normal = mul(normalMat, input.Normal); + + output.TexCoord0.xy = input.TexCoord; + output.TexCoord1.xy = input.TexCoord1; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse; +#endif +#ifdef POINTLIGHTS + for(i = 0; i < numPointLights; i++) + output.Color.xyz += DoPointLight(lights[i+firstPointLight], Vertex.xyz, Normal)*surfDiffuse; +#endif +#ifdef SPOTLIGHTS + for(i = 0; i < numSpotLights; i++) + output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], Vertex.xyz, Normal)*surfDiffuse; +#endif + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/extras/shaders/im2d.vert b/src/extras/shaders/im2d.vert new file mode 100644 index 0000000..fcd81c2 --- /dev/null +++ b/src/extras/shaders/im2d.vert @@ -0,0 +1,19 @@ +uniform vec4 u_xform; + +VSIN(ATTRIB_POS) vec4 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + gl_Position = in_pos; + gl_Position.w = 1.0; + gl_Position.xy = gl_Position.xy * u_xform.xy + u_xform.zw; + v_fog = DoFog(gl_Position.z); + gl_Position.xyz *= gl_Position.w; + v_color = in_color; + v_tex0 = in_tex0; +} diff --git a/src/extras/shaders/im2d_UV2.vert b/src/extras/shaders/im2d_UV2.vert new file mode 100644 index 0000000..e5fd4d0 --- /dev/null +++ b/src/extras/shaders/im2d_UV2.vert @@ -0,0 +1,21 @@ +uniform vec4 u_xform; + +VSIN(ATTRIB_POS) vec4 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT vec2 v_tex1; +VSOUT float v_fog; + +void +main(void) +{ + gl_Position = in_pos; + gl_Position.w = 1.0; + gl_Position.xy = gl_Position.xy * u_xform.xy + u_xform.zw; + v_fog = DoFog(gl_Position.z); + gl_Position.xyz *= gl_Position.w; + v_color = in_color; + v_tex0 = in_tex0; + v_tex1 = in_tex1; +} diff --git a/src/extras/shaders/lighting.h b/src/extras/shaders/lighting.h new file mode 100644 index 0000000..4b08196 --- /dev/null +++ b/src/extras/shaders/lighting.h @@ -0,0 +1,44 @@ +struct Light +{ + float4 color; // and radius + float4 position; // and -cos(angle) + float4 direction; // and falloff clamp +}; + +float3 DoDirLight(Light L, float3 N) +{ + float l = max(0.0, dot(N, -L.direction.xyz)); + return l*L.color.xyz; +} + +float3 DoDirLightSpec(Light L, float3 N, float3 V, float power) +{ + return pow(saturate(dot(N, normalize(V + -L.direction.xyz))), power)*L.color.xyz; +} + +float3 DoPointLight(Light L, float3 V, float3 N) +{ + // As on PS2 + float3 dir = V - L.position.xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/L.color.w)); + float l = max(0.0, dot(N, -normalize(dir))); + return l*L.color.xyz*atten; +} + +float3 DoSpotLight(Light L, float3 V, float3 N) +{ + // As on PS2 + float3 dir = V - L.position.xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/L.color.w)); + dir /= dist; + float l = max(0.0, dot(N, -dir)); + float pcos = dot(dir, L.direction.xyz); // cos to point + float ccos = -L.position.w; // cos of cone + float falloff = (pcos-ccos)/(1.0-ccos); + if(falloff < 0) // outside of cone + l = 0; + l *= max(falloff, L.direction.w); // falloff clamp + return l*L.color.xyz*atten; +} diff --git a/src/extras/shaders/make_glsl.sh b/src/extras/shaders/make_glsl.sh new file mode 100644 index 0000000..0af9896 --- /dev/null +++ b/src/extras/shaders/make_glsl.sh @@ -0,0 +1,9 @@ +#!sh +for i in *.vert; do + echo $i + ./makeinc_glsl.sh $i +done +for i in *.frag; do + echo $i + ./makeinc_glsl.sh $i +done diff --git a/src/extras/shaders/make_hlsl.cmd b/src/extras/shaders/make_hlsl.cmd new file mode 100644 index 0000000..dee9528 --- /dev/null +++ b/src/extras/shaders/make_hlsl.cmd @@ -0,0 +1,3 @@ +@echo off +for %%f in (*PS.hlsl) do "%DXSDK_DIR%\Utilities\bin\x86\fxc.exe" /T ps_2_0 /nologo /E main /Fo obj\%%~nf.cso %%f +for %%f in (*VS.hlsl) do "%DXSDK_DIR%\Utilities\bin\x86\fxc.exe" /T vs_2_0 /nologo /E main /Fo obj\%%~nf.cso %%f diff --git a/src/extras/shaders/makeinc_glsl.sh b/src/extras/shaders/makeinc_glsl.sh new file mode 100644 index 0000000..2bc6a38 --- /dev/null +++ b/src/extras/shaders/makeinc_glsl.sh @@ -0,0 +1,6 @@ +#!sh +ext=${1##*.} +name=${1%.*} +(echo "const char *${name}_${ext}_src =";\ +sed 's/..*/"&\\n"/' $1;\ +echo ';') > obj/${name}_${ext}.inc diff --git a/src/extras/shaders/makeinc_hlsl.sh b/src/extras/shaders/makeinc_hlsl.sh new file mode 100644 index 0000000..a5b1286 --- /dev/null +++ b/src/extras/shaders/makeinc_hlsl.sh @@ -0,0 +1,6 @@ +#!sh +cd obj +for i in *cso; do + (echo -n 'static ' + xxd -i $i | grep -v '_len = ') > ${i%cso}inc +done diff --git a/src/extras/shaders/neoGloss.frag b/src/extras/shaders/neoGloss.frag new file mode 100644 index 0000000..4f097b0 --- /dev/null +++ b/src/extras/shaders/neoGloss.frag @@ -0,0 +1,26 @@ +uniform sampler2D tex0; + +uniform vec4 u_reflProps; + +#define glossMult (u_reflProps.x) + +FSIN vec3 v_normal; +FSIN vec3 v_light; +FSIN vec2 v_tex0; +FSIN float v_fog; + +void +main(void) +{ + vec4 color = texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + vec3 n = 2.0*v_normal-1.0; // unpack + vec3 v = 2.0*v_light-1.0; // + + float s = dot(n, v); + color = s*s*s*s*s*s*s*s*color*v_fog*glossMult; + + DoAlphaTest(color.a); + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/neoGloss.vert b/src/extras/shaders/neoGloss.vert new file mode 100644 index 0000000..41102f3 --- /dev/null +++ b/src/extras/shaders/neoGloss.vert @@ -0,0 +1,25 @@ +uniform vec3 u_eye; + +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec3 v_normal; +VSOUT vec3 v_light; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + + vec3 viewVec = normalize(u_eye - Vertex.xyz); + vec3 Light = normalize(viewVec - u_lightDirection[0].xyz); + v_normal = 0.5*(1.0 + vec3(0.0, 0.0, 1.0)); // compress + v_light = 0.5*(1.0 + Light); // + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/extras/shaders/neoGloss_PS.hlsl b/src/extras/shaders/neoGloss_PS.hlsl new file mode 100644 index 0000000..b3c9763 --- /dev/null +++ b/src/extras/shaders/neoGloss_PS.hlsl @@ -0,0 +1,20 @@ +sampler2D tex0 : register(s0); +float glossMult : register(c1); + +struct VS_out +{ + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float3 Normal : COLOR0; + float3 Light : COLOR1; +}; + +float4 main(VS_out input) : COLOR +{ + float4 color = tex2D(tex0, input.TexCoord0.xy); + float3 n = 2.0*input.Normal-1.0; // unpack + float3 v = 2.0*input.Light-1.0; // + + float s = dot(n, v); + return s*s*s*s*s*s*s*s*color*input.TexCoord0.z*glossMult; +} diff --git a/src/extras/shaders/neoGloss_VS.hlsl b/src/extras/shaders/neoGloss_VS.hlsl new file mode 100644 index 0000000..d166171 --- /dev/null +++ b/src/extras/shaders/neoGloss_VS.hlsl @@ -0,0 +1,35 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float2 TexCoord : TEXCOORD0; +}; + +struct VS_out +{ + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float3 Normal : COLOR0; + float3 Light : COLOR1; +}; + +float3 eye : register(c41); + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 Vertex = mul(worldMat, input.Position).xyz; + output.TexCoord0.xy = input.TexCoord; + + float3 viewVec = normalize(eye - Vertex); + float3 Light = normalize(viewVec - lights[0].direction.xyz); + output.Normal = 0.5*(1.0 + float3(0.0, 0.0, 1.0)); // compress + output.Light = 0.5*(1.0 + Light); // + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/extras/shaders/neoRim.vert b/src/extras/shaders/neoRim.vert new file mode 100644 index 0000000..81ee109 --- /dev/null +++ b/src/extras/shaders/neoRim.vert @@ -0,0 +1,34 @@ +uniform vec3 u_viewVec; +uniform vec4 u_rampStart; +uniform vec4 u_rampEnd; +uniform vec3 u_rimData; + +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + + // rim light + float f = u_rimData.x - u_rimData.y*dot(Normal, u_viewVec); + vec4 rimlight = clamp(mix(u_rampEnd, u_rampStart, f)*u_rimData.z, 0.0, 1.0); + v_color.rgb += rimlight.rgb; + + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/extras/shaders/neoRimSkin.vert b/src/extras/shaders/neoRimSkin.vert new file mode 100644 index 0000000..1515ad7 --- /dev/null +++ b/src/extras/shaders/neoRimSkin.vert @@ -0,0 +1,43 @@ +uniform mat4 u_boneMatrices[64]; + +uniform vec3 u_viewVec; +uniform vec4 u_rampStart; +uniform vec4 u_rampEnd; +uniform vec3 u_rimData; + +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + vec3 SkinVertex = vec3(0.0, 0.0, 0.0); + vec3 SkinNormal = vec3(0.0, 0.0, 0.0); + for(int i = 0; i < 4; i++){ + SkinVertex += (u_boneMatrices[int(in_indices[i])] * vec4(in_pos, 1.0)).xyz * in_weights[i]; + SkinNormal += (mat3(u_boneMatrices[int(in_indices[i])]) * in_normal) * in_weights[i]; + } + + vec4 Vertex = u_world * vec4(SkinVertex, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * SkinNormal; + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + + // rim light + float f = u_rimData.x - u_rimData.y*dot(Normal, u_viewVec); + vec4 rimlight = clamp(mix(u_rampEnd, u_rampStart, f)*u_rimData.z, 0.0, 1.0); + v_color.rgb += rimlight.rgb; + + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.z); +} diff --git a/src/extras/shaders/neoRimSkin_VS.hlsl b/src/extras/shaders/neoRimSkin_VS.hlsl new file mode 100644 index 0000000..87cc093 --- /dev/null +++ b/src/extras/shaders/neoRimSkin_VS.hlsl @@ -0,0 +1,73 @@ +#include "standardConstants.h" + +float4x3 boneMatrices[64] : register(c41); + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; + float4 Weights : BLENDWEIGHT; + int4 Indices : BLENDINDICES; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float4 Color : COLOR0; +}; + +float3 viewVec : register(c233); +float4 rampStart : register(c234); +float4 rampEnd : register(c235); +float3 rimData : register(c236); + +VS_out main(in VS_in input) +{ + VS_out output; + + int j; + float3 SkinVertex = float3(0.0, 0.0, 0.0); + float3 SkinNormal = float3(0.0, 0.0, 0.0); + for(j = 0; j < 4; j++){ + SkinVertex += mul(input.Position, boneMatrices[input.Indices[j]]).xyz * input.Weights[j]; + SkinNormal += mul(input.Normal, (float3x3)boneMatrices[input.Indices[j]]).xyz * input.Weights[j]; + } + + output.Position = mul(combinedMat, float4(SkinVertex, 1.0)); + float3 Vertex = mul(worldMat, float4(SkinVertex, 1.0)).xyz; + float3 Normal = mul(normalMat, SkinNormal); + + output.TexCoord0.xy = input.TexCoord; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +//#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse; +//#endif +//#ifdef POINTLIGHTS +// for(i = 0; i < numPointLights; i++) +// output.Color.xyz += DoPointLight(lights[i+firstPointLight], Vertex.xyz, Normal)*surfDiffuse; +//#endif +//#ifdef SPOTLIGHTS +// for(i = 0; i < numSpotLights; i++) +// output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], Vertex.xyz, Normal)*surfDiffuse; +//#endif + + // rim light + float f = rimData.x - rimData.y*dot(Normal, viewVec); + float4 rimlight = saturate(lerp(rampEnd, rampStart, f)*rimData.z); + output.Color.xyz += rimlight.xyz; + + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/extras/shaders/neoRim_VS.hlsl b/src/extras/shaders/neoRim_VS.hlsl new file mode 100644 index 0000000..7f95166 --- /dev/null +++ b/src/extras/shaders/neoRim_VS.hlsl @@ -0,0 +1,61 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float4 Color : COLOR0; +}; + +float3 viewVec : register(c233); +float4 rampStart : register(c234); +float4 rampEnd : register(c235); +float3 rimData : register(c236); + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 Vertex = mul(worldMat, input.Position).xyz; + float3 Normal = mul(normalMat, input.Normal); + + output.TexCoord0.xy = input.TexCoord; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +//#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse; +//#endif +//#ifdef POINTLIGHTS +// for(i = 0; i < numPointLights; i++) +// output.Color.xyz += DoPointLight(lights[i+firstPointLight], Vertex.xyz, Normal)*surfDiffuse; +//#endif +//#ifdef SPOTLIGHTS +// for(i = 0; i < numSpotLights; i++) +// output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], Vertex.xyz, Normal)*surfDiffuse; +//#endif + + // rim light + float f = rimData.x - rimData.y*dot(Normal, viewVec); + float4 rimlight = saturate(lerp(rampEnd, rampStart, f)*rimData.z); + output.Color.xyz += rimlight.xyz; + + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/extras/shaders/neoVehicle.frag b/src/extras/shaders/neoVehicle.frag new file mode 100644 index 0000000..2ac24f7 --- /dev/null +++ b/src/extras/shaders/neoVehicle.frag @@ -0,0 +1,29 @@ +uniform sampler2D tex0; +uniform sampler2D tex1; + +FSIN vec4 v_color; +FSIN vec4 v_reflcolor; +FSIN vec2 v_tex0; +FSIN vec2 v_tex1; +FSIN float v_fog; + +void +main(void) +{ + vec4 pass1 = v_color*texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + vec3 envmap = texture(tex1, vec2(v_tex1.x, 1.0-v_tex1.y)).rgb; + pass1.rgb = mix(pass1.rgb, envmap, v_reflcolor.a); + pass1.rgb = mix(u_fogColor.rgb, pass1.rgb, v_fog); +// pass1.rgb += v_reflcolor.rgb * v_fog; + + vec3 pass2 = v_reflcolor.rgb * v_fog; + + vec4 color; + color.rgb = pass1.rgb*pass1.a + pass2; + color.a = pass1.a; + +// color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog); + DoAlphaTest(color.a); + + FRAGCOLOR(color); +} diff --git a/src/extras/shaders/neoVehicle.vert b/src/extras/shaders/neoVehicle.vert new file mode 100644 index 0000000..6985a68 --- /dev/null +++ b/src/extras/shaders/neoVehicle.vert @@ -0,0 +1,51 @@ +uniform vec3 u_eye; +uniform vec4 u_reflProps; +uniform vec4 u_specDir[5]; +uniform vec4 u_specColor[5]; + +#define fresnel (u_reflProps.x) +#define lightStrength (u_reflProps.y) // speclight alpha +#define shininess (u_reflProps.z) +#define specularity (u_reflProps.w) + +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec4 v_reflcolor; +VSOUT vec2 v_tex0; +VSOUT vec2 v_tex1; +VSOUT float v_fog; + +vec3 DoDirLightSpec(vec3 Ldir, vec3 Lcol, vec3 N, vec3 V, float power) +{ + return pow(clamp(dot(N, normalize(V + -Ldir)), 0.0, 1.0), power)*Lcol; +} + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + vec3 viewVec = normalize(u_eye - Vertex.xyz); + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse*lightStrength; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + // reflect V along Normal + vec3 uv2 = Normal*dot(viewVec, Normal)*2.0 - viewVec; + v_tex1 = uv2.xy*0.5 + 0.5; + float b = 1.0 - clamp(dot(viewVec, Normal), 0.0, 1.0); + v_reflcolor = vec4(0.0, 0.0, 0.0, 1.0); + v_reflcolor.a = mix(b*b*b*b*b, 1.0, fresnel)*shininess; + + for(int i = 0; i < 5; i++) + v_reflcolor.rgb += DoDirLightSpec(u_specDir[i].xyz, u_specColor[i].rgb, Normal, viewVec, u_specDir[i].w)*specularity*lightStrength; + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/extras/shaders/neoVehicle_PS.hlsl b/src/extras/shaders/neoVehicle_PS.hlsl new file mode 100644 index 0000000..fa030dd --- /dev/null +++ b/src/extras/shaders/neoVehicle_PS.hlsl @@ -0,0 +1,34 @@ +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; + float4 ReflColor : COLOR1; +}; + +sampler2D tex0 : register(s0); +sampler2D tex1 : register(s1); + +float4 fogColor : register(c0); + +float4 main(VS_out input) : COLOR +{ + float4 pass1 = input.Color; +//#ifdef TEX + pass1 *= tex2D(tex0, input.TexCoord0.xy); +//#endif + float3 envmap = tex2D(tex1, input.TexCoord1).rgb; + pass1.rgb = lerp(pass1.rgb, envmap, input.ReflColor.a); +// pass1.rgb = envmap; +// pass1.rgb *= input.ReflColor.a; + pass1.rgb = lerp(fogColor.rgb, pass1.rgb, input.TexCoord0.z); +// pass1.rgb += input.ReflColor.rgb * input.TexCoord0.z; + + float3 pass2 = input.ReflColor.rgb*input.TexCoord0.z; + + float4 color; + color.rgb = pass1.rgb*pass1.a + pass2; + color.a = pass1.a; + + return color; +} diff --git a/src/extras/shaders/neoVehicle_VS.hlsl b/src/extras/shaders/neoVehicle_VS.hlsl new file mode 100644 index 0000000..fb73009 --- /dev/null +++ b/src/extras/shaders/neoVehicle_VS.hlsl @@ -0,0 +1,64 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; + float4 ReflColor : COLOR1; +}; + +float3 eye : register(c41); +float4 reflProps : register(c42); +Light specLights[5] : register(c43); + + +#define fresnel (reflProps.x) +#define lightStrength (reflProps.y) // speclight alpha +#define shininess (reflProps.z) +#define specularity (reflProps.w) + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 Vertex = mul(worldMat, input.Position).xyz; + float3 Normal = mul(normalMat, input.Normal); + float3 viewVec = normalize(eye - Vertex); + + output.TexCoord0.xy = input.TexCoord; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient*lightStrength; + + int i; + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse*lightStrength; + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + // reflect V along Normal + float3 uv2 = Normal*dot(viewVec, Normal)*2.0 - viewVec; + output.TexCoord1 = uv2.xy*0.5 + 0.5; + float b = 1.0 - saturate(dot(viewVec, Normal)); + output.ReflColor = float4(0.0, 0.0, 0.0, 1.0); + output.ReflColor.a = lerp(b*b*b*b*b, 1.0, fresnel)*shininess; + + //Light mainLight = lights[0]; + for(i = 0; i < 5; i++) + output.ReflColor.xyz += DoDirLightSpec(specLights[i], Normal, viewVec, specLights[i].direction.w)*specularity*lightStrength; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/extras/shaders/neoWorldIII.frag b/src/extras/shaders/neoWorldIII.frag new file mode 100644 index 0000000..d8bb715 --- /dev/null +++ b/src/extras/shaders/neoWorldIII.frag @@ -0,0 +1,25 @@ +uniform sampler2D tex0; +uniform sampler2D tex1; + +uniform vec4 u_lightMap; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN vec2 v_tex1; +FSIN float v_fog; + +void +main(void) +{ + vec4 t0 = texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + vec4 t1 = texture(tex1, vec2(v_tex1.x, 1.0-v_tex1.y)); + + vec4 color = t0*v_color*(1.0 + u_lightMap*(2.0*t1-1.0)); + color.a = v_color.a*t0.a*u_lightMap.a; + + color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog); + DoAlphaTest(color.a); + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/neoWorldIII_PS.hlsl b/src/extras/shaders/neoWorldIII_PS.hlsl new file mode 100644 index 0000000..8a3d5d8 --- /dev/null +++ b/src/extras/shaders/neoWorldIII_PS.hlsl @@ -0,0 +1,25 @@ +sampler2D Diffuse : register(s0); +sampler2D Light : register(s1); +float4 fogColor : register(c0); +float4 lm : register(c1); + +struct PS_INPUT +{ + float4 Color : COLOR0; + float3 Tex0 : TEXCOORD0; + float2 Tex1 : TEXCOORD1; +}; + +float4 +main(PS_INPUT IN) : COLOR +{ + float4 t0 = tex2D(Diffuse, IN.Tex0.xy); + float4 t1 = tex2D(Light, IN.Tex1); + + float4 col = t0*IN.Color*(1 + lm*(2*t1-1)); + col.a = IN.Color.a*t0.a*lm.a; + + col.rgb = lerp(fogColor.rgb, col.rgb, IN.Tex0.z); + + return col; +} diff --git a/src/extras/shaders/screenDroplet.frag b/src/extras/shaders/screenDroplet.frag new file mode 100644 index 0000000..84d30bd --- /dev/null +++ b/src/extras/shaders/screenDroplet.frag @@ -0,0 +1,18 @@ +uniform sampler2D tex0; +uniform sampler2D tex1; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN vec2 v_tex1; +FSIN float v_fog; + +void +main(void) +{ + vec4 color; + color = v_color*texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + color *= texture(tex1, vec2(v_tex1.x, 1.0-v_tex1.y)); + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/screenDroplet_PS.hlsl b/src/extras/shaders/screenDroplet_PS.hlsl new file mode 100644 index 0000000..4d41da6 --- /dev/null +++ b/src/extras/shaders/screenDroplet_PS.hlsl @@ -0,0 +1,17 @@ +struct VS_out { + float4 Position : POSITION; + float2 TexCoord0 : TEXCOORD0; + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; +}; + +sampler2D tex0 : register(s0); +sampler2D tex1 : register(s1); + +float4 main(VS_out input) : COLOR +{ + float4 color = input.Color; + color *= tex2D(tex0, input.TexCoord0.xy); + color *= tex2D(tex1, input.TexCoord1.xy); + return color; +} diff --git a/src/extras/shaders/simple.frag b/src/extras/shaders/simple.frag new file mode 100644 index 0000000..c85bf08 --- /dev/null +++ b/src/extras/shaders/simple.frag @@ -0,0 +1,17 @@ +uniform sampler2D tex0; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN float v_fog; + +void +main(void) +{ + vec4 color; + color = v_color*texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog); + DoAlphaTest(color.a); + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/standardConstants.h b/src/extras/shaders/standardConstants.h new file mode 100644 index 0000000..088df7d --- /dev/null +++ b/src/extras/shaders/standardConstants.h @@ -0,0 +1,28 @@ +float4x4 combinedMat : register(c0); +float4x4 worldMat : register(c4); +float3x3 normalMat : register(c8); +float4 matCol : register(c12); +float4 surfProps : register(c13); +float4 fogData : register(c14); +float4 ambientLight : register(c15); + +#define surfAmbient (surfProps.x) +#define surfSpecular (surfProps.y) +#define surfDiffuse (surfProps.z) + +#define fogStart (fogData.x) +#define fogEnd (fogData.y) +#define fogRange (fogData.z) +#define fogDisable (fogData.w) + +#include "lighting.h" + +int numDirLights : register(i0); +int numPointLights : register(i1); +int numSpotLights : register(i2); +int4 firstLight : register(c16); +Light lights[8] : register(c17); + +#define firstDirLight (firstLight.x) +#define firstPointLight (firstLight.y) +#define firstSpotLight (firstLight.z) diff --git a/src/fakerw/fake.cpp b/src/fakerw/fake.cpp new file mode 100644 index 0000000..6dfebb3 --- /dev/null +++ b/src/fakerw/fake.cpp @@ -0,0 +1,1010 @@ +#define _CRT_SECURE_NO_WARNINGS +#define WITH_D3D // not WITHD3D, so it's librw define +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include "crossplatform.h" +#endif + +using namespace rw; + +RwUInt8 RwObjectGetType(const RwObject *obj) { return obj->type; } +RwFrame* rwObjectGetParent(const RwObject *obj) { return (RwFrame*)obj->parent; } + +void *RwMalloc(size_t size) { return engine->memfuncs.rwmalloc(size, 0); } +void *RwCalloc(size_t numObj, size_t sizeObj) { + void *mem = RwMalloc(numObj*sizeObj); + if(mem) + memset(mem, 0, numObj*sizeObj); + return mem; +} +void RwFree(void *mem) { engine->memfuncs.rwfree(mem); } + + +//RwReal RwV3dNormalize(RwV3d * out, const RwV3d * in); +RwReal RwV3dLength(const RwV3d * in) { return length(*in); } +//RwReal RwV2dLength(const RwV2d * in); +//RwReal RwV2dNormalize(RwV2d * out, const RwV2d * in); +//void RwV2dAssign(RwV2d * out, const RwV2d * ina); +//void RwV2dAdd(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +//void RwV2dLineNormal(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +//void RwV2dSub(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +//void RwV2dPerp(RwV2d * out, const RwV2d * in); +//void RwV2dScale(RwV2d * out, const RwV2d * in, RwReal scalar); +//RwReal RwV2dDotProduct(const RwV2d * ina, const RwV2d * inb); +//void RwV3dAssign(RwV3d * out, const RwV3d * ina); +void RwV3dAdd(RwV3d * out, const RwV3d * ina, const RwV3d * inb) { *out = add(*ina, *inb); } +void RwV3dSub(RwV3d * out, const RwV3d * ina, const RwV3d * inb) { *out = sub(*ina, *inb); } +void RwV3dScale(RwV3d * out, const RwV3d * in, RwReal scalar) { *out = scale(*in, scalar); } +void RwV3dIncrementScaled(RwV3d * out, const RwV3d * in, RwReal scalar) { *out = add(*out, scale(*in, scalar)); } +void RwV3dNegate(RwV3d * out, const RwV3d * in) { *out = neg(*in); } +RwReal RwV3dDotProduct(const RwV3d * ina, const RwV3d * inb) { return dot(*ina, *inb); } +//void RwV3dCrossProduct(RwV3d * out, const RwV3d * ina, const RwV3d * inb); +RwV3d *RwV3dTransformPoints(RwV3d * pointsOut, const RwV3d * pointsIn, RwInt32 numPoints, const RwMatrix * matrix) + { V3d::transformPoints(pointsOut, pointsIn, numPoints, matrix); return pointsOut; } +//RwV3d *RwV3dTransformVectors(RwV3d * vectorsOut, const RwV3d * vectorsIn, RwInt32 numPoints, const RwMatrix * matrix); + + + +RwBool RwMatrixDestroy(RwMatrix *mpMat) { mpMat->destroy(); return true; } +RwMatrix *RwMatrixCreate(void) { return Matrix::create(); } +void RwMatrixCopy(RwMatrix * dstMatrix, const RwMatrix * srcMatrix) { *dstMatrix = *srcMatrix; } +void RwMatrixSetIdentity(RwMatrix * matrix) { matrix->setIdentity(); } +RwMatrix *RwMatrixMultiply(RwMatrix * matrixOut, const RwMatrix * MatrixIn1, const RwMatrix * matrixIn2); +RwMatrix *RwMatrixTransform(RwMatrix * matrix, const RwMatrix * transform, RwOpCombineType combineOp) + { matrix->transform(transform, (rw::CombineOp)combineOp); return matrix; } +//RwMatrix *RwMatrixOrthoNormalize(RwMatrix * matrixOut, const RwMatrix * matrixIn); +RwMatrix *RwMatrixInvert(RwMatrix * matrixOut, const RwMatrix * matrixIn) { Matrix::invert(matrixOut, matrixIn); return matrixOut; } +RwMatrix *RwMatrixScale(RwMatrix * matrix, const RwV3d * scale, RwOpCombineType combineOp) + { matrix->scale(scale, (rw::CombineOp)combineOp); return matrix; } +RwMatrix *RwMatrixTranslate(RwMatrix * matrix, const RwV3d * translation, RwOpCombineType combineOp) + { matrix->translate(translation, (rw::CombineOp)combineOp); return matrix; } +RwMatrix *RwMatrixRotate(RwMatrix * matrix, const RwV3d * axis, RwReal angle, RwOpCombineType combineOp) + { matrix->rotate(axis, angle, (rw::CombineOp)combineOp); return matrix; } +//RwMatrix *RwMatrixRotateOneMinusCosineSine(RwMatrix * matrix, const RwV3d * unitAxis, RwReal oneMinusCosine, RwReal sine, RwOpCombineType combineOp); +//const RwMatrix *RwMatrixQueryRotate(const RwMatrix * matrix, RwV3d * unitAxis, RwReal * angle, RwV3d * center); +RwV3d *RwMatrixGetRight(RwMatrix * matrix) { return &matrix->right; } +RwV3d *RwMatrixGetUp(RwMatrix * matrix) { return &matrix->up; } +RwV3d *RwMatrixGetAt(RwMatrix * matrix) { return &matrix->at; } +RwV3d *RwMatrixGetPos(RwMatrix * matrix) { return &matrix->pos; } +RwMatrix *RwMatrixUpdate(RwMatrix * matrix) { matrix->update(); return matrix; } +//RwMatrix *RwMatrixOptimize(RwMatrix * matrix, const RwMatrixTolerance *tolerance); + + + + +RwFrame *RwFrameForAllObjects(RwFrame * frame, RwObjectCallBack callBack, void *data) { + FORLIST(lnk, frame->objectList) + if(callBack(&ObjectWithFrame::fromFrame(lnk)->object, data) == nil) + break; + return frame; +} +RwFrame *RwFrameTranslate(RwFrame * frame, const RwV3d * v, RwOpCombineType combine) { frame->translate(v, (CombineOp)combine); return frame; } +RwFrame *RwFrameRotate(RwFrame * frame, const RwV3d * axis, RwReal angle, RwOpCombineType combine) { frame->rotate(axis, angle, (CombineOp)combine); return frame; } +RwFrame *RwFrameScale(RwFrame * frame, const RwV3d * v, RwOpCombineType combine) { frame->scale(v, (CombineOp)combine); return frame; } +RwFrame *RwFrameTransform(RwFrame * frame, const RwMatrix * m, RwOpCombineType combine) { frame->transform(m, (CombineOp)combine); return frame; } +//TODO: actually implement this! +RwFrame *RwFrameOrthoNormalize(RwFrame * frame) { return frame; } +RwFrame *RwFrameSetIdentity(RwFrame * frame) { frame->matrix.setIdentity(); frame->updateObjects(); return frame; } +//RwFrame *RwFrameCloneHierarchy(RwFrame * root); +//RwBool RwFrameDestroyHierarchy(RwFrame * frame); +RwFrame *RwFrameForAllChildren(RwFrame * frame, RwFrameCallBack callBack, void *data) + { return frame->forAllChildren(callBack, data); } +RwFrame *RwFrameRemoveChild(RwFrame * child) { child->removeChild(); return child; } +RwFrame *RwFrameAddChild(RwFrame * parent, RwFrame * child) { parent->addChild(child); return parent; } +RwFrame *RwFrameGetParent(const RwFrame * frame) { return frame->getParent(); } +//RwFrame *RwFrameGetRoot(const RwFrame * frame); +RwMatrix *RwFrameGetLTM(RwFrame * frame) { return frame->getLTM(); } +RwMatrix *RwFrameGetMatrix(RwFrame * frame) { return &frame->matrix; } +RwFrame *RwFrameUpdateObjects(RwFrame * frame) { frame->updateObjects(); return frame; } +RwFrame *RwFrameCreate(void) { return rw::Frame::create(); } +//RwBool RwFrameInit(RwFrame *frame); +//RwBool RwFrameDeInit(RwFrame *frame); +RwBool RwFrameDestroy(RwFrame * frame) { frame->destroy(); return true; } +//void _rwFrameInit(RwFrame *frame); +//void _rwFrameDeInit(RwFrame *frame); +//RwBool RwFrameDirty(const RwFrame * frame); +//RwInt32 RwFrameCount(RwFrame * frame); +//RwBool RwFrameSetStaticPluginsSize(RwInt32 size); +RwInt32 RwFrameRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) + { return Frame::registerPlugin(size, pluginID, constructCB, destructCB, (CopyConstructor)copyCB); } +//RwInt32 RwFrameGetPluginOffset(RwUInt32 pluginID); +//RwBool RwFrameValidatePlugins(const RwFrame * frame); +//RwFrame *_rwFrameCloneAndLinkClones(RwFrame * root); +//RwFrame *_rwFramePurgeClone(RwFrame *root); + +RwInt32 RwFrameRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) + { return Frame::registerPluginStream(pluginID, readCB, (StreamWrite)writeCB, (StreamGetSize)getSizeCB); } + + +rwFrameList *rwFrameListDeinitialize(rwFrameList *frameList) { + rwFree(frameList->frames); + frameList->frames = nil; + return frameList; +} +rwFrameList *rwFrameListStreamRead(RwStream *stream, rwFrameList *fl) { return fl->streamRead(stream); } + + + + +RwCamera *RwCameraBeginUpdate(RwCamera * camera) { camera->beginUpdate(); return camera; } +RwCamera *RwCameraEndUpdate(RwCamera * camera) { camera->endUpdate(); return camera; } +RwCamera *RwCameraClear(RwCamera * camera, RwRGBA * colour, RwInt32 clearMode) { camera->clear(colour, clearMode); return camera; } +// WARNING: ignored argument +RwCamera *RwCameraShowRaster(RwCamera * camera, void *pDev, RwUInt32 flags) { camera->showRaster(flags); return camera; } +RwBool RwCameraDestroy(RwCamera * camera) { camera->destroy(); return true; } +RwCamera *RwCameraCreate(void) { return rw::Camera::create(); } +RwCamera *RwCameraClone(RwCamera * camera) { return camera->clone(); } +RwCamera *RwCameraSetViewOffset(RwCamera *camera, const RwV2d *offset) { camera->setViewOffset(offset); return camera; } +RwCamera *RwCameraSetViewWindow(RwCamera *camera, const RwV2d *viewWindow) { camera->setViewWindow(viewWindow); return camera; } +RwCamera *RwCameraSetProjection(RwCamera *camera, RwCameraProjection projection) { camera->projection = projection; return camera; } +RwCamera *RwCameraSetNearClipPlane(RwCamera *camera, RwReal nearClip) { camera->setNearPlane(nearClip); return camera; } +RwCamera *RwCameraSetFarClipPlane(RwCamera *camera, RwReal farClip) { camera->setFarPlane(farClip); return camera; } +RwInt32 RwCameraRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwCameraGetPluginOffset(RwUInt32 pluginID); +RwBool RwCameraValidatePlugins(const RwCamera * camera); +RwFrustumTestResult RwCameraFrustumTestSphere(const RwCamera * camera, const RwSphere * sphere) { return (RwFrustumTestResult)camera->frustumTestSphere(sphere); } +const RwV2d *RwCameraGetViewOffset(const RwCamera *camera) { return &camera->viewOffset; } +RwCamera *RwCameraSetRaster(RwCamera *camera, RwRaster *raster) { camera->frameBuffer = raster; return camera; } +RwRaster *RwCameraGetRaster(const RwCamera *camera) { return camera->frameBuffer; } +RwCamera *RwCameraSetZRaster(RwCamera *camera, RwRaster *zRaster) { camera->zBuffer = zRaster; return camera; } +RwRaster *RwCameraGetZRaster(const RwCamera *camera) { return camera->zBuffer; } +RwReal RwCameraGetNearClipPlane(const RwCamera *camera) { return camera->nearPlane; } +RwReal RwCameraGetFarClipPlane(const RwCamera *camera) { return camera->farPlane; } +RwCamera *RwCameraSetFogDistance(RwCamera *camera, RwReal fogDistance) { camera->fogPlane = fogDistance; return camera; } +RwReal RwCameraGetFogDistance(const RwCamera *camera) { return camera->fogPlane; } +RwCamera *RwCameraGetCurrentCamera(void) { return rw::engine->currentCamera; } +RwCameraProjection RwCameraGetProjection(const RwCamera *camera); +const RwV2d *RwCameraGetViewWindow(const RwCamera *camera) { return &camera->viewWindow; } +RwMatrix *RwCameraGetViewMatrix(RwCamera *camera) { return &camera->viewMatrix; } +RwCamera *RwCameraSetFrame(RwCamera *camera, RwFrame *frame) { camera->setFrame(frame); return camera; } +RwFrame *RwCameraGetFrame(const RwCamera *camera) { return camera->getFrame(); } + + + + + +RwImage *RwImageCreate(RwInt32 width, RwInt32 height, RwInt32 depth) { return Image::create(width, height, depth); } +RwBool RwImageDestroy(RwImage * image) { image->destroy(); return true; } +RwImage *RwImageAllocatePixels(RwImage * image) { image->allocate(); return image; } +RwImage *RwImageFreePixels(RwImage * image) { image->free(); return image; } +RwImage *RwImageCopy(RwImage * destImage, const RwImage * sourceImage); +RwImage *RwImageResize(RwImage * image, RwInt32 width, RwInt32 height); +RwImage *RwImageApplyMask(RwImage * image, const RwImage * mask); +RwImage *RwImageMakeMask(RwImage * image); +RwImage *RwImageReadMaskedImage(const RwChar * imageName, const RwChar * maskname); +RwImage *RwImageRead(const RwChar * imageName); +RwImage *RwImageWrite(RwImage * image, const RwChar * imageName); +RwChar *RwImageGetPath(void); +const RwChar *RwImageSetPath(const RwChar * path) { Image::setSearchPath(path); return path; } +RwImage *RwImageSetStride(RwImage * image, RwInt32 stride) { image->stride = stride; return image; } +RwImage *RwImageSetPixels(RwImage * image, RwUInt8 * pixels) { image->pixels = pixels; return image; } +RwImage *RwImageSetPalette(RwImage * image, RwRGBA * palette) { image->palette = (uint8*)palette; return image; } +RwInt32 RwImageGetWidth(const RwImage * image) { return image->width; } +RwInt32 RwImageGetHeight(const RwImage * image) { return image->height; } +RwInt32 RwImageGetDepth(const RwImage * image) { return image->depth; } +RwInt32 RwImageGetStride(const RwImage * image) { return image->stride; } +RwUInt8 *RwImageGetPixels(const RwImage * image) { return image->pixels; } +RwRGBA *RwImageGetPalette(const RwImage * image) { return (RwRGBA*)image->palette; } +RwUInt32 RwRGBAToPixel(RwRGBA * rgbIn, RwInt32 rasterFormat); +RwRGBA *RwRGBASetFromPixel(RwRGBA * rgbOut, RwUInt32 pixelValue, RwInt32 rasterFormat); +RwBool RwImageSetGamma(RwReal gammaValue); +RwReal RwImageGetGamma(void); +RwImage *RwImageGammaCorrect(RwImage * image); +RwRGBA *RwRGBAGammaCorrect(RwRGBA * rgb); +RwInt32 RwImageRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwImageGetPluginOffset(RwUInt32 pluginID); +RwBool RwImageValidatePlugins(const RwImage * image); +//RwBool RwImageRegisterImageFormat(const RwChar * extension, RwImageCallBackRead imageRead, RwImageCallBackWrite imageWrite); +const RwChar *RwImageFindFileType(const RwChar * imageName); +RwInt32 RwImageStreamGetSize(const RwImage * image); +RwImage *RwImageStreamRead(RwStream * stream); +const RwImage *RwImageStreamWrite(const RwImage * image, RwStream * stream); + +RwImage *RwImageFindRasterFormat(RwImage *ipImage,RwInt32 nRasterType, RwInt32 *npWidth,RwInt32 *npHeight, RwInt32 *npDepth,RwInt32 *npFormat) +{ + return Raster::imageFindRasterFormat(ipImage, nRasterType, npWidth, npHeight, npDepth, npFormat) ? ipImage : nil; +} + + + + +RwRaster *RwRasterCreate(RwInt32 width, RwInt32 height, RwInt32 depth, RwInt32 flags) { return Raster::create(width, height, depth, flags); } +RwBool RwRasterDestroy(RwRaster * raster) { raster->destroy(); return true; } +RwInt32 RwRasterGetWidth(const RwRaster *raster) { return raster->width; } +RwInt32 RwRasterGetHeight(const RwRaster *raster) { return raster->height; } +RwInt32 RwRasterGetStride(const RwRaster *raster); +RwInt32 RwRasterGetDepth(const RwRaster *raster) { return raster->depth; } +RwInt32 RwRasterGetFormat(const RwRaster *raster); +RwInt32 RwRasterGetType(const RwRaster *raster); +RwRaster *RwRasterGetParent(const RwRaster *raster) { return raster->parent; } +RwRaster *RwRasterGetOffset(RwRaster *raster, RwInt16 *xOffset, RwInt16 *yOffset); +RwInt32 RwRasterGetNumLevels(RwRaster * raster); +RwRaster *RwRasterSubRaster(RwRaster * subRaster, RwRaster * raster, RwRect * rect); +RwRaster *RwRasterRenderFast(RwRaster * raster, RwInt32 x, RwInt32 y) { return raster->renderFast(x, y) ? raster : nil; } +RwRaster *RwRasterRender(RwRaster * raster, RwInt32 x, RwInt32 y); +RwRaster *RwRasterRenderScaled(RwRaster * raster, RwRect * rect); +RwRaster *RwRasterPushContext(RwRaster * raster) { return Raster::pushContext(raster); } +RwRaster *RwRasterPopContext(void) { return Raster::popContext(); } +RwRaster *RwRasterGetCurrentContext(void) { return Raster::getCurrentContext(); } +RwBool RwRasterClear(RwInt32 pixelValue); +RwBool RwRasterClearRect(RwRect * rpRect, RwInt32 pixelValue); +RwRaster *RwRasterShowRaster(RwRaster * raster, void *dev, RwUInt32 flags); +RwUInt8 *RwRasterLock(RwRaster * raster, RwUInt8 level, RwInt32 lockMode) { return raster->lock(level, lockMode); } +RwRaster *RwRasterUnlock(RwRaster * raster) { raster->unlock(0); return raster; } +RwUInt8 *RwRasterLockPalette(RwRaster * raster, RwInt32 lockMode); +RwRaster *RwRasterUnlockPalette(RwRaster * raster); +RwInt32 RwRasterRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwRasterGetPluginOffset(RwUInt32 pluginID); +RwBool RwRasterValidatePlugins(const RwRaster * raster); + +RwRaster *RwRasterSetFromImage(RwRaster *raster, RwImage *image) { return raster->setFromImage(image); } + + + + +RwTexture *RwTextureCreate(RwRaster * raster) { return Texture::create(raster); } +RwBool RwTextureDestroy(RwTexture * texture) { texture->destroy(); return true; } +RwTexture *RwTextureAddRef(RwTexture *texture) { texture->addRef(); return texture; } +// TODO +RwBool RwTextureSetMipmapping(RwBool enable) { return true; } +RwBool RwTextureGetMipmapping(void); +// TODO +RwBool RwTextureSetAutoMipmapping(RwBool enable) { return true; } +RwBool RwTextureGetAutoMipmapping(void); +RwBool RwTextureSetMipmapGenerationCallBack(RwTextureCallBackMipmapGeneration callback); +RwTextureCallBackMipmapGeneration RwTextureGetMipmapGenerationCallBack(void); +RwBool RwTextureSetMipmapNameCallBack(RwTextureCallBackMipmapName callback); +RwTextureCallBackMipmapName RwTextureGetMipmapNameCallBack(void); +RwBool RwTextureGenerateMipmapName(RwChar * name, RwChar * maskName, RwUInt8 mipLevel, RwInt32 format); +RwBool RwTextureRasterGenerateMipmaps(RwRaster * raster, RwImage * image); +RwTextureCallBackRead RwTextureGetReadCallBack(void); +RwBool RwTextureSetReadCallBack(RwTextureCallBackRead fpCallBack); +RwTexture *RwTextureSetName(RwTexture * texture, const RwChar * name) { strncpy(texture->name, name, 32); return texture; } +RwTexture *RwTextureSetMaskName(RwTexture * texture, const RwChar * maskName); +RwChar *RwTextureGetName(RwTexture *texture) { return texture->name; } +RwChar *RwTextureGetMaskName(RwTexture *texture); +RwTexture *RwTextureSetRaster(RwTexture * texture, RwRaster * raster) { texture->raster = raster; return texture; } +RwTexture *RwTextureRead(const RwChar * name, const RwChar * maskName) { return Texture::read(name, maskName); } +RwRaster *RwTextureGetRaster(const RwTexture *texture) { return texture->raster; } +RwInt32 RwTextureRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwTextureGetPluginOffset(RwUInt32 pluginID); +RwBool RwTextureValidatePlugins(const RwTexture * texture); + +RwTexDictionary *RwTextureGetDictionary(RwTexture *texture); +RwTexture *RwTextureSetFilterMode(RwTexture *texture, RwTextureFilterMode filtering) { texture->setFilter((Texture::FilterMode)filtering); return texture; } +RwTextureFilterMode RwTextureGetFilterMode(const RwTexture *texture); +RwTexture *RwTextureSetAddressing(RwTexture *texture, RwTextureAddressMode addressing) { + texture->setAddressU((Texture::Addressing)addressing); + texture->setAddressV((Texture::Addressing)addressing); + return texture; +} +RwTexture *RwTextureSetAddressingU(RwTexture *texture, RwTextureAddressMode addressing) { + texture->setAddressU((Texture::Addressing)addressing); + return texture; +} +RwTexture *RwTextureSetAddressingV(RwTexture *texture, RwTextureAddressMode addressing) { + texture->setAddressV((Texture::Addressing)addressing); + return texture; +} +RwTextureAddressMode RwTextureGetAddressing(const RwTexture *texture); +RwTextureAddressMode RwTextureGetAddressingU(const RwTexture *texture); +RwTextureAddressMode RwTextureGetAddressingV(const RwTexture *texture); + +// TODO +void _rwD3D8TexDictionaryEnableRasterFormatConversion(bool enable) { } + +// hack for reading native textures +RwBool rwNativeTextureHackRead(RwStream *stream, RwTexture **tex, RwInt32 size) +{ + *tex = Texture::streamReadNative(stream); +#ifdef LIBRW + (*tex)->raster = rw::Raster::convertTexToCurrentPlatform((*tex)->raster); +#endif + return *tex != nil; +} + + + + + +RwTexDictionary *RwTexDictionaryCreate(void) { return TexDictionary::create(); } +RwBool RwTexDictionaryDestroy(RwTexDictionary * dict) { dict->destroy(); return true; } +RwTexture *RwTexDictionaryAddTexture(RwTexDictionary * dict, RwTexture * texture) { dict->addFront(texture); return texture; } +//RwTexture *RwTexDictionaryRemoveTexture(RwTexture * texture); +RwTexture *RwTexDictionaryFindNamedTexture(RwTexDictionary * dict, const RwChar * name) { return dict->find(name); } +RwTexDictionary *RwTexDictionaryGetCurrent(void) { return TexDictionary::getCurrent(); } +RwTexDictionary *RwTexDictionarySetCurrent(RwTexDictionary * dict) { TexDictionary::setCurrent(dict); return dict; } +const RwTexDictionary *RwTexDictionaryForAllTextures(const RwTexDictionary * dict, RwTextureCallBack fpCallBack, void *pData) { + FORLIST(lnk, ((RwTexDictionary*)dict)->textures) + if(fpCallBack(Texture::fromDict(lnk), pData) == nil) + break; + return dict; +} +RwBool RwTexDictionaryForAllTexDictionaries(RwTexDictionaryCallBack fpCallBack, void *pData); +RwInt32 RwTexDictionaryRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwTexDictionaryGetPluginOffset(RwUInt32 pluginID); +RwBool RwTexDictionaryValidatePlugins(const RwTexDictionary * dict); +RwUInt32 RwTexDictionaryStreamGetSize(const RwTexDictionary *texDict); +RwTexDictionary *RwTexDictionaryStreamRead(RwStream *stream); +const RwTexDictionary *RwTexDictionaryStreamWrite(const RwTexDictionary *texDict, RwStream *stream) { + ((RwTexDictionary*)texDict)->streamWrite(stream); + return texDict; +} + + + + + +RwStream *RwStreamOpen(RwStreamType type, RwStreamAccessType accessType, const void *pData) { + StreamFile *file; + StreamMemory *mem; + RwMemory *memargs; + const char *mode; + + switch(accessType){ + case rwSTREAMREAD: mode = "rb"; break; + case rwSTREAMWRITE: mode = "wb"; break; + case rwSTREAMAPPEND: mode = "ab"; break; + default: return nil; + } + + // oh god this is horrible. librw streams really need fixing + switch(type){ + case rwSTREAMFILENAME:{ + StreamFile fakefile; + file = rwNewT(StreamFile, 1, 0); + memcpy(file, &fakefile, sizeof(StreamFile)); +#ifndef _WIN32 + char *r = casepath((char*)pData); + if (r) { + if (file->open((char*)r, mode)) { + free(r); + return file; + } + free(r); + } else +#endif + { + if (file->open((char*)pData, mode)) + return file; + } + rwFree(file); + return nil; + } + case rwSTREAMMEMORY:{ + StreamMemory fakemem; + memargs = (RwMemory*)pData; + mem = rwNewT(StreamMemory, 1, 0); + memcpy(mem, &fakemem, sizeof(StreamMemory)); + if(mem->open(memargs->start, memargs->length)) + return mem; + rwFree(mem); + return nil; + } + default: + assert(0 && "unknown type"); + return nil; + } +} +RwBool RwStreamClose(RwStream * stream, void *pData) { stream->close(); rwFree(stream); return true; } +RwUInt32 RwStreamRead(RwStream * stream, void *buffer, RwUInt32 length) { return stream->read8(buffer, length); } +RwStream *RwStreamWrite(RwStream * stream, const void *buffer, RwUInt32 length) { stream->write8(buffer, length); return stream; } +RwStream *RwStreamSkip(RwStream * stream, RwUInt32 offset) { stream->seek(offset); return stream; } + +RwBool RwStreamFindChunk(RwStream *stream, RwUInt32 type, RwUInt32 *lengthOut, RwUInt32 *versionOut) + { return findChunk(stream, type, lengthOut, versionOut); } + + + +void RwIm2DVertexSetCameraX(RwIm2DVertex *vert, RwReal camx) { } +void RwIm2DVertexSetCameraY(RwIm2DVertex *vert, RwReal camy) { } +void RwIm2DVertexSetCameraZ(RwIm2DVertex *vert, RwReal camz) { vert->setCameraZ(camz); } +void RwIm2DVertexSetRecipCameraZ(RwIm2DVertex *vert, RwReal recipz) { vert->setRecipCameraZ(recipz); } +void RwIm2DVertexSetScreenX(RwIm2DVertex *vert, RwReal scrnx) { vert->setScreenX(scrnx); } +void RwIm2DVertexSetScreenY(RwIm2DVertex *vert, RwReal scrny) { vert->setScreenY(scrny); } +void RwIm2DVertexSetScreenZ(RwIm2DVertex *vert, RwReal scrnz) { vert->setScreenZ(scrnz); } +void RwIm2DVertexSetU(RwIm2DVertex *vert, RwReal texU, RwReal recipz) { vert->setU(texU, recipz); } +void RwIm2DVertexSetV(RwIm2DVertex *vert, RwReal texV, RwReal recipz) { vert->setV(texV, recipz); } +void RwIm2DVertexSetIntRGBA(RwIm2DVertex *vert, RwUInt8 red, RwUInt8 green, RwUInt8 blue, RwUInt8 alpha) { vert->setColor(red, green, blue, alpha); } + +RwReal RwIm2DGetNearScreenZ(void) { return im2d::GetNearZ(); } +RwReal RwIm2DGetFarScreenZ(void) { return im2d::GetFarZ(); } +RwBool RwIm2DRenderLine(RwIm2DVertex *vertices, RwInt32 numVertices, RwInt32 vert1, RwInt32 vert2) + { im2d::RenderLine(vertices, numVertices, vert1, vert2); return true; } +RwBool RwIm2DRenderTriangle(RwIm2DVertex *vertices, RwInt32 numVertices, RwInt32 vert1, RwInt32 vert2, RwInt32 vert3 ) + { im2d::RenderTriangle(vertices, numVertices, vert1, vert2, vert3); return true; } +RwBool RwIm2DRenderPrimitive(RwPrimitiveType primType, RwIm2DVertex *vertices, RwInt32 numVertices) + { im2d::RenderPrimitive((PrimitiveType)primType, vertices, numVertices); return true; } +RwBool RwIm2DRenderIndexedPrimitive(RwPrimitiveType primType, RwIm2DVertex *vertices, RwInt32 numVertices, RwImVertexIndex *indices, RwInt32 numIndices) + { im2d::RenderIndexedPrimitive((PrimitiveType)primType, vertices, numVertices, indices, numIndices); return true; } + + +void RwIm3DVertexSetPos(RwIm3DVertex *vert, RwReal x, RwReal y, RwReal z) { vert->setX(x); vert->setY(y); vert->setZ(z); } +void RwIm3DVertexSetU(RwIm3DVertex *vert, RwReal u) { vert->setU(u); } +void RwIm3DVertexSetV(RwIm3DVertex *vert, RwReal v) { vert->setV(v); } +void RwIm3DVertexSetRGBA(RwIm3DVertex *vert, RwUInt8 r, RwUInt8 g, RwUInt8 b, RwUInt8 a) { vert->setColor(r, g, b, a); } + +void *RwIm3DTransform(RwIm3DVertex *pVerts, RwUInt32 numVerts, RwMatrix *ltm, RwUInt32 flags) { im3d::Transform(pVerts, numVerts, ltm, flags); return pVerts; } +RwBool RwIm3DEnd(void) { im3d::End(); return true; } +RwBool RwIm3DRenderLine(RwInt32 vert1, RwInt32 vert2) { + RwImVertexIndex indices[2]; + indices[0] = vert1; + indices[1] = vert2; + im3d::RenderIndexedPrimitive((PrimitiveType)PRIMTYPELINELIST, indices, 2); + return true; +} +RwBool RwIm3DRenderTriangle(RwInt32 vert1, RwInt32 vert2, RwInt32 vert3); +RwBool RwIm3DRenderIndexedPrimitive(RwPrimitiveType primType, RwImVertexIndex *indices, RwInt32 numIndices) { im3d::RenderIndexedPrimitive((PrimitiveType)primType, indices, numIndices); return true; } +RwBool RwIm3DRenderPrimitive(RwPrimitiveType primType); + + + + + +RwBool RwRenderStateGet(RwRenderState state, void *value) +{ + uint32 *uival = (uint32*)value; + uint32 fog; + switch(state){ + case rwRENDERSTATETEXTURERASTER: *(void**)value = GetRenderStatePtr(TEXTURERASTER); return true; + case rwRENDERSTATETEXTUREADDRESS: *uival = GetRenderState(TEXTUREADDRESS); return true; + case rwRENDERSTATETEXTUREADDRESSU: *uival = GetRenderState(TEXTUREADDRESSU); return true; + case rwRENDERSTATETEXTUREADDRESSV: *uival = GetRenderState(TEXTUREADDRESSV); return true; + case rwRENDERSTATETEXTUREPERSPECTIVE: *uival = 1; return true; + case rwRENDERSTATEZTESTENABLE: *uival = GetRenderState(ZTESTENABLE); return true; + case rwRENDERSTATESHADEMODE: *uival = rwSHADEMODEGOURAUD; return true; + case rwRENDERSTATEZWRITEENABLE: *uival = GetRenderState(ZWRITEENABLE); return true; + case rwRENDERSTATETEXTUREFILTER: *uival = GetRenderState(TEXTUREFILTER); return true; + case rwRENDERSTATESRCBLEND: *uival = GetRenderState(SRCBLEND); return true; + case rwRENDERSTATEDESTBLEND: *uival = GetRenderState(DESTBLEND); return true; + case rwRENDERSTATEVERTEXALPHAENABLE: *uival = GetRenderState(VERTEXALPHA); return true; + case rwRENDERSTATEBORDERCOLOR: *uival = 0; return true; + case rwRENDERSTATEFOGENABLE: *uival = GetRenderState(FOGENABLE); return true; + case rwRENDERSTATEFOGCOLOR: + // have to swap R and B here + fog = GetRenderState(FOGCOLOR); + *uival = (fog>>16)&0xFF; + *uival |= (fog&0xFF)<<16; + *uival |= fog&0xFF00; + *uival |= fog&0xFF000000; + return true; + case rwRENDERSTATEFOGTYPE: *uival = rwFOGTYPELINEAR; return true; + case rwRENDERSTATEFOGDENSITY: *(float*)value = 1.0f; return true; + case rwRENDERSTATECULLMODE: *uival = GetRenderState(CULLMODE); return true; + + // all unsupported + case rwRENDERSTATEFOGTABLE: + case rwRENDERSTATEALPHAPRIMITIVEBUFFER: + + case rwRENDERSTATESTENCILENABLE: + case rwRENDERSTATESTENCILFAIL: + case rwRENDERSTATESTENCILZFAIL: + case rwRENDERSTATESTENCILPASS: + case rwRENDERSTATESTENCILFUNCTION: + case rwRENDERSTATESTENCILFUNCTIONREF: + case rwRENDERSTATESTENCILFUNCTIONMASK: + case rwRENDERSTATESTENCILFUNCTIONWRITEMASK: + default: + return false; + } +} +RwBool RwRenderStateSet(RwRenderState state, void *value) +{ + uint32 uival = (uintptr)value; + uint32 fog; + switch(state){ + case rwRENDERSTATETEXTURERASTER: SetRenderStatePtr(TEXTURERASTER, value); return true; + case rwRENDERSTATETEXTUREADDRESS: SetRenderState(TEXTUREADDRESS, uival); return true; + case rwRENDERSTATETEXTUREADDRESSU: SetRenderState(TEXTUREADDRESSU, uival); return true; + case rwRENDERSTATETEXTUREADDRESSV: SetRenderState(TEXTUREADDRESSV, uival); return true; + case rwRENDERSTATETEXTUREPERSPECTIVE: return true; + case rwRENDERSTATEZTESTENABLE: SetRenderState(ZTESTENABLE, uival); return true; + case rwRENDERSTATESHADEMODE: return true; + case rwRENDERSTATEZWRITEENABLE: SetRenderState(ZWRITEENABLE, uival); return true; + case rwRENDERSTATETEXTUREFILTER: SetRenderState(TEXTUREFILTER, uival); return true; + case rwRENDERSTATESRCBLEND: SetRenderState(SRCBLEND, uival); return true; + case rwRENDERSTATEDESTBLEND: SetRenderState(DESTBLEND, uival); return true; + case rwRENDERSTATEVERTEXALPHAENABLE: SetRenderState(VERTEXALPHA, uival); return true; + case rwRENDERSTATEBORDERCOLOR: return true; + case rwRENDERSTATEFOGENABLE: SetRenderState(FOGENABLE, uival); return true; + case rwRENDERSTATEFOGCOLOR: + // have to swap R and B here + fog = (uival>>16)&0xFF; + fog |= (uival&0xFF)<<16; + fog |= uival&0xFF00; + fog |= uival&0xFF000000; + SetRenderState(FOGCOLOR, fog); + return true; + case rwRENDERSTATEFOGTYPE: return true; + case rwRENDERSTATEFOGDENSITY: return true; + case rwRENDERSTATEFOGTABLE: return true; + case rwRENDERSTATEALPHAPRIMITIVEBUFFER: return true; + case rwRENDERSTATECULLMODE: SetRenderState(CULLMODE, uival); return true; + + // all unsupported + case rwRENDERSTATESTENCILENABLE: + case rwRENDERSTATESTENCILFAIL: + case rwRENDERSTATESTENCILZFAIL: + case rwRENDERSTATESTENCILPASS: + case rwRENDERSTATESTENCILFUNCTION: + case rwRENDERSTATESTENCILFUNCTIONREF: + case rwRENDERSTATESTENCILFUNCTIONMASK: + case rwRENDERSTATESTENCILFUNCTIONWRITEMASK: + default: + return true; + } +} + +static rw::MemoryFunctions gMemfuncs; +static void *(*real_malloc)(size_t size); +static void *(*real_realloc)(void *mem, size_t newSize); +static void *mallocWrap(size_t sz, uint32 hint) { if(sz == 0) return nil; return real_malloc(sz); } +static void *reallocWrap(void *p, size_t sz, uint32 hint) { return real_realloc(p, sz); } + + +// WARNING: unused parameters +RwBool RwEngineInit(RwMemoryFunctions *memFuncs, RwUInt32 initFlags, RwUInt32 resArenaSize) { + if(memFuncs){ + real_malloc = memFuncs->rwmalloc; + real_realloc = memFuncs->rwrealloc; + gMemfuncs.rwmalloc = mallocWrap; + gMemfuncs.rwrealloc = reallocWrap; + gMemfuncs.rwfree = memFuncs->rwfree; + Engine::init(&gMemfuncs); + }else{ + Engine::init(nil); + } + return true; +} +// TODO: this is platform dependent +RwBool RwEngineOpen(RwEngineOpenParams *initParams) { + static EngineOpenParams openParams; +#ifdef RW_D3D9 + openParams.window = (HWND)initParams->displayID; +#else + openParams = *(EngineOpenParams*)initParams->displayID; +#endif + return Engine::open(&openParams); +} +RwBool RwEngineStart(void) { + rw::d3d::isP8supported = false; + return Engine::start(); +} +RwBool RwEngineStop(void) { Engine::stop(); return true; } +RwBool RwEngineClose(void) { Engine::close(); return true; } +RwBool RwEngineTerm(void) { Engine::term(); return true; } +RwInt32 RwEngineRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor initCB, RwPluginObjectDestructor termCB); +RwInt32 RwEngineGetPluginOffset(RwUInt32 pluginID); +RwInt32 RwEngineGetNumSubSystems(void) { return Engine::getNumSubSystems(); } +RwSubSystemInfo *RwEngineGetSubSystemInfo(RwSubSystemInfo *subSystemInfo, RwInt32 subSystemIndex) + { return Engine::getSubSystemInfo(subSystemInfo, subSystemIndex); } +RwInt32 RwEngineGetCurrentSubSystem(void) { return Engine::getCurrentSubSystem(); } +RwBool RwEngineSetSubSystem(RwInt32 subSystemIndex) { return Engine::setSubSystem(subSystemIndex); } +RwInt32 RwEngineGetNumVideoModes(void) { return Engine::getNumVideoModes(); } +RwVideoMode *RwEngineGetVideoModeInfo(RwVideoMode *modeinfo, RwInt32 modeIndex) + { return Engine::getVideoModeInfo(modeinfo, modeIndex); } +RwInt32 RwEngineGetCurrentVideoMode(void) { return Engine::getCurrentVideoMode(); } +RwBool RwEngineSetVideoMode(RwInt32 modeIndex) { return Engine::setVideoMode(modeIndex); } +RwInt32 RwEngineGetTextureMemorySize(void); +RwInt32 RwEngineGetMaxTextureSize(void); + + + +// TODO +void RwD3D8EngineSetRefreshRate(RwUInt32 refreshRate) {} +RwBool RwD3D8DeviceSupportsDXTTexture(void) { return true; } + + +void RwD3D8EngineSetMultiSamplingLevels(RwUInt32 level) { Engine::setMultiSamplingLevels(level); } +RwUInt32 RwD3D8EngineGetMaxMultiSamplingLevels(void) { return Engine::getMaxMultiSamplingLevels(); } + + +RpMaterial *RpMaterialCreate(void) { return Material::create(); } +RwBool RpMaterialDestroy(RpMaterial *material) { material->destroy(); return true; } +//RpMaterial *RpMaterialClone(RpMaterial *material); +RpMaterial *RpMaterialSetTexture(RpMaterial *material, RwTexture *texture) { material->setTexture(texture); return material; } +//RpMaterial *RpMaterialAddRef(RpMaterial *material); +RwTexture *RpMaterialGetTexture(const RpMaterial *material) { return material->texture; } +RpMaterial *RpMaterialSetColor(RpMaterial *material, const RwRGBA *color) { material->color = *color; return material; } +const RwRGBA *RpMaterialGetColor(const RpMaterial *material) { return &material->color; } +RpMaterial *RpMaterialSetSurfaceProperties(RpMaterial *material, const RwSurfaceProperties *surfaceProperties); +const RwSurfaceProperties *RpMaterialGetSurfaceProperties(const RpMaterial *material) { return &material->surfaceProps; } +//RwInt32 RpMaterialRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +//RwInt32 RpMaterialRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +//RwInt32 RpMaterialSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +//RwInt32 RpMaterialGetPluginOffset(RwUInt32 pluginID); +//RwBool RpMaterialValidatePlugins(const RpMaterial *material); +//RwUInt32 RpMaterialStreamGetSize(const RpMaterial *material); +//RpMaterial *RpMaterialStreamRead(RwStream *stream); +//const RpMaterial *RpMaterialStreamWrite(const RpMaterial *material, RwStream *stream); +//RpMaterialChunkInfo *_rpMaterialChunkInfoRead(RwStream *stream, RpMaterialChunkInfo *materialChunkInfo, RwInt32 *bytesRead); + + + + + +RwReal RpLightGetRadius(const RpLight *light) { return light->radius; } +//const RwRGBAReal *RpLightGetColor(const RpLight *light); +RpLight *RpLightSetFrame(RpLight *light, RwFrame *frame) { light->setFrame(frame); return light; } +RwFrame *RpLightGetFrame(const RpLight *light) { return light->getFrame(); } +//RpLightType RpLightGetType(const RpLight *light); +RpLight *RpLightSetFlags(RpLight *light, RwUInt32 flags) { light->setFlags(flags); return light; } +//RwUInt32 RpLightGetFlags(const RpLight *light); +RpLight *RpLightCreate(RwInt32 type) { return rw::Light::create(type); } +RwBool RpLightDestroy(RpLight *light) { light->destroy(); return true; } +RpLight *RpLightSetRadius(RpLight *light, RwReal radius) { light->radius = radius; return light; } +RpLight *RpLightSetColor(RpLight *light, const RwRGBAReal *color) { light->setColor(color->red, color->green, color->blue); return light; } +//RwReal RpLightGetConeAngle(const RpLight *light); +//RpLight *RpLightSetConeAngle(RpLight * ight, RwReal angle); +//RwUInt32 RpLightStreamGetSize(const RpLight *light); +//RpLight *RpLightStreamRead(RwStream *stream); +//const RpLight *RpLightStreamWrite(const RpLight *light, RwStream *stream); +//RpLightChunkInfo *_rpLightChunkInfoRead(RwStream *stream, RpLightChunkInfo *lightChunkInfo, RwInt32 *bytesRead); +//RwInt32 RpLightRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +//RwInt32 RpLightRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +//RwInt32 RpLightSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +//RwInt32 RpLightGetPluginOffset(RwUInt32 pluginID); +//RwBool RpLightValidatePlugins(const RpLight * light); + + + + + +RpGeometry *RpGeometryCreate(RwInt32 numVert, RwInt32 numTriangles, RwUInt32 format) { return Geometry::create(numVert, numTriangles, format); } +RwBool RpGeometryDestroy(RpGeometry *geometry) { geometry->destroy(); return true; } +RpGeometry *_rpGeometryAddRef(RpGeometry *geometry); +RpGeometry *RpGeometryLock(RpGeometry *geometry, RwInt32 lockMode) { geometry->lock(lockMode); return geometry; } +RpGeometry *RpGeometryUnlock(RpGeometry *geometry) { geometry->unlock(); return geometry; } +RpGeometry *RpGeometryTransform(RpGeometry *geometry, const RwMatrix *matrix); +RpGeometry *RpGeometryCreateSpace(RwReal radius); +RpMorphTarget *RpMorphTargetSetBoundingSphere(RpMorphTarget *morphTarget, const RwSphere *boundingSphere) { morphTarget->boundingSphere = *boundingSphere; return morphTarget; } +RwSphere *RpMorphTargetGetBoundingSphere(RpMorphTarget *morphTarget) { return &morphTarget->boundingSphere; } +const RpMorphTarget *RpMorphTargetCalcBoundingSphere(const RpMorphTarget *morphTarget, RwSphere *boundingSphere) { *boundingSphere = morphTarget->calculateBoundingSphere(); return morphTarget; } +RwInt32 RpGeometryAddMorphTargets(RpGeometry *geometry, RwInt32 mtcount) { RwInt32 n = geometry->numMorphTargets; geometry->addMorphTargets(mtcount); return n; } +RwInt32 RpGeometryAddMorphTarget(RpGeometry *geometry) { return RpGeometryAddMorphTargets(geometry, 1); } +RpGeometry *RpGeometryRemoveMorphTarget(RpGeometry *geometry, RwInt32 morphTarget); +RwInt32 RpGeometryGetNumMorphTargets(const RpGeometry *geometry); +RpMorphTarget *RpGeometryGetMorphTarget(const RpGeometry *geometry, RwInt32 morphTarget) { return &geometry->morphTargets[morphTarget]; } +RwRGBA *RpGeometryGetPreLightColors(const RpGeometry *geometry) { return geometry->colors; } +RwTexCoords *RpGeometryGetVertexTexCoords(const RpGeometry *geometry, RwTextureCoordinateIndex uvIndex) { + if(uvIndex == rwNARWTEXTURECOORDINATEINDEX) + return nil; + return geometry->texCoords[uvIndex-rwTEXTURECOORDINATEINDEX0]; +} +RwInt32 RpGeometryGetNumTexCoordSets(const RpGeometry *geometry) { return geometry->numTexCoordSets; } +RwInt32 RpGeometryGetNumVertices (const RpGeometry *geometry) { return geometry->numVertices; } +RwV3d *RpMorphTargetGetVertices(const RpMorphTarget *morphTarget) { return morphTarget->vertices; } +RwV3d *RpMorphTargetGetVertexNormals(const RpMorphTarget *morphTarget) { return morphTarget->normals; } +RpTriangle *RpGeometryGetTriangles(const RpGeometry *geometry) { return geometry->triangles; } +RwInt32 RpGeometryGetNumTriangles(const RpGeometry *geometry) { return geometry->numTriangles; } +RpMaterial *RpGeometryGetMaterial(const RpGeometry *geometry, RwInt32 matNum) { return geometry->matList.materials[matNum]; } +const RpGeometry *RpGeometryTriangleSetVertexIndices(const RpGeometry *geometry, RpTriangle *triangle, RwUInt16 vert1, RwUInt16 vert2, RwUInt16 vert3) + { triangle->v[0] = vert1; triangle->v[1] = vert2; triangle->v[2] = vert3; return geometry; } +RpGeometry *RpGeometryTriangleSetMaterial(RpGeometry *geometry, RpTriangle *triangle, RpMaterial *material) { + int id = geometry->matList.findIndex(material); + if(id < 0) + id = geometry->matList.appendMaterial(material); + if(id < 0) + return nil; + triangle->matId = id; + return geometry; +} +const RpGeometry *RpGeometryTriangleGetVertexIndices(const RpGeometry *geometry, const RpTriangle *triangle, RwUInt16 *vert1, RwUInt16 *vert2, RwUInt16 *vert3); +RpMaterial *RpGeometryTriangleGetMaterial(const RpGeometry *geometry, const RpTriangle *triangle); +RwInt32 RpGeometryGetNumMaterials(const RpGeometry *geometry); +RpGeometry *RpGeometryForAllMaterials(RpGeometry *geometry, RpMaterialCallBack fpCallBack, void *pData) { + int i; + for(i = 0; i < geometry->matList.numMaterials; i++) + if(fpCallBack(geometry->matList.materials[i], pData) == nil) + break; + return geometry; +} +//const RpGeometry *RpGeometryForAllMeshes(const RpGeometry *geometry, RpMeshCallBack fpCallBack, void *pData); +RwInt32 RpGeometryRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpGeometryRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpGeometrySetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpGeometryGetPluginOffset(RwUInt32 pluginID); +RwBool RpGeometryValidatePlugins(const RpGeometry *geometry); +RwUInt32 RpGeometryStreamGetSize(const RpGeometry *geometry); +const RpGeometry *RpGeometryStreamWrite(const RpGeometry *geometry, RwStream *stream); +RpGeometry *RpGeometryStreamRead(RwStream *stream) { return Geometry::streamRead(stream); } +//RpGeometryChunkInfo *_rpGeometryChunkInfoRead(RwStream *stream, RpGeometryChunkInfo *geometryChunkInfo, RwInt32 *bytesRead); +RwUInt32 RpGeometryGetFlags(const RpGeometry *geometry) { return geometry->flags; } +RpGeometry *RpGeometrySetFlags(RpGeometry *geometry, RwUInt32 flags) { geometry->flags = flags; return geometry; } +const RwSurfaceProperties *_rpGeometryGetSurfaceProperties(const RpGeometry *geometry); +RpGeometry *_rpGeometrySetSurfaceProperties(RpGeometry *geometry, const RwSurfaceProperties *surfaceProperties); + + + + + +RwFrame *RpClumpGetFrame(const RpClump * clump) { return clump->getFrame(); } +RpClump *RpClumpSetFrame(RpClump * clump, RwFrame * frame) { clump->setFrame(frame); return clump; } +RpClump *RpClumpForAllAtomics(RpClump * clump, RpAtomicCallBack callback, void *pData) { + FORLIST(lnk, clump->atomics) + if(callback(Atomic::fromClump(lnk), pData) == nil) + break; + return clump; +} +RpClump *RpClumpForAllLights(RpClump * clump, RpLightCallBack callback, void *pData); +RpClump *RpClumpForAllCameras(RpClump * clump, RwCameraCallBack callback, void *pData); +//RpClump *RpClumpCreateSpace(const RwV3d * position, RwReal radius); +RpClump *RpClumpRender(RpClump * clump) { clump->render(); return clump; } +RpClump *RpClumpRemoveAtomic(RpClump * clump, RpAtomic * atomic) { clump->removeAtomic(atomic); return clump; } +RpClump *RpClumpAddAtomic(RpClump * clump, RpAtomic * atomic) { clump->addAtomic(atomic); return clump; } +//RpClump *RpClumpRemoveLight(RpClump * clump, RpLight * light); +//RpClump *RpClumpAddLight(RpClump * clump, RpLight * light); +//RpClump *RpClumpRemoveCamera(RpClump * clump, RwCamera * camera); +//RpClump *RpClumpAddCamera(RpClump * clump, RwCamera * camera); +RwBool RpClumpDestroy(RpClump * clump) { clump->destroy(); return true; } +RpClump *RpClumpCreate(void) { return rw::Clump::create(); } +RpClump *RpClumpClone(RpClump * clump) { return clump->clone(); } +//RpClump *RpClumpSetCallBack(RpClump * clump, RpClumpCallBack callback); +//RpClumpCallBack RpClumpGetCallBack(const RpClump * clump); +RwInt32 RpClumpGetNumAtomics(RpClump * clump) { return clump->countAtomics(); } +//RwInt32 RpClumpGetNumLights(RpClump * clump); +//RwInt32 RpClumpGetNumCameras(RpClump * clump); +RpClump *RpClumpStreamRead(RwStream * stream) { return rw::Clump::streamRead(stream); } +//RpClump *RpClumpStreamWrite(RpClump * clump, RwStream * stream); +RwInt32 RpClumpRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) + { return Clump::registerPlugin(size, pluginID, constructCB, destructCB, (CopyConstructor)copyCB); } +RwInt32 RpClumpRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) + { return Clump::registerPluginStream(pluginID, readCB, (StreamWrite)writeCB, (StreamGetSize)getSizeCB); } +//RwInt32 RpClumpSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +//RwInt32 RpClumpGetPluginOffset(RwUInt32 pluginID); +//RwBool RpClumpValidatePlugins(const RpClump * clump); + + + +RpAtomic *RpAtomicCreate(void) { return rw::Atomic::create(); } +RwBool RpAtomicDestroy(RpAtomic * atomic) { atomic->destroy(); return true; } +RpAtomic *RpAtomicClone(RpAtomic * atomic) { return atomic->clone(); } +RpAtomic *RpAtomicSetFrame(RpAtomic * atomic, RwFrame * frame) { atomic->setFrame(frame); return atomic; } +RpAtomic *RpAtomicSetGeometry(RpAtomic * atomic, RpGeometry * geometry, RwUInt32 flags) { atomic->setGeometry(geometry, flags); return atomic; } + +RwFrame *RpAtomicGetFrame(const RpAtomic * atomic) { return atomic->getFrame(); } +RpAtomic *RpAtomicSetFlags(RpAtomic * atomic, RwUInt32 flags) { atomic->setFlags(flags); return atomic; } +RwUInt32 RpAtomicGetFlags(const RpAtomic * atomic) { return atomic->getFlags(); } +RwSphere *RpAtomicGetBoundingSphere(RpAtomic * atomic) { return &atomic->boundingSphere; } +RpAtomic *RpAtomicRender(RpAtomic * atomic) { atomic->render(); return atomic; } +RpClump *RpAtomicGetClump(const RpAtomic * atomic) { return atomic->clump; } +//RpInterpolator *RpAtomicGetInterpolator(RpAtomic * atomic); +RpGeometry *RpAtomicGetGeometry(const RpAtomic * atomic) { return atomic->geometry; } +// WARNING: illegal cast +void RpAtomicSetRenderCallBack(RpAtomic * atomic, RpAtomicCallBackRender callback) { atomic->setRenderCB((Atomic::RenderCB)callback); } +RpAtomicCallBackRender RpAtomicGetRenderCallBack(const RpAtomic * atomic) { return (RpAtomicCallBackRender)atomic->renderCB; } +//RwBool RpAtomicInstance(RpAtomic *atomic); +//RwUInt32 RpAtomicStreamGetSize(RpAtomic * atomic); +//RpAtomic *RpAtomicStreamRead(RwStream * stream); +//RpAtomic *RpAtomicStreamWrite(RpAtomic * atomic, RwStream * stream); +RwInt32 RpAtomicRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) + { return Atomic::registerPlugin(size, pluginID, constructCB, destructCB, (CopyConstructor)copyCB); } +//RwInt32 RpAtomicRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +//RwInt32 RpAtomicSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +//RwInt32 RpAtomicSetStreamRightsCallBack(RwUInt32 pluginID, RwPluginDataChunkRightsCallBack rightsCB); +//RwInt32 RpAtomicGetPluginOffset(RwUInt32 pluginID); +//RwBool RpAtomicValidatePlugins(const RpAtomic * atomic); + +RpAtomic *AtomicDefaultRenderCallBack(RpAtomic * atomic) { Atomic::defaultRenderCB(atomic); return atomic; } + + +// TODO: this is extremely simplified +RpWorld *RpWorldCreate(RwBBox * boundingBox) { return World::create(); } +RwBool RpWorldDestroy(RpWorld * world) { world->destroy(); return true; } + +RwBool RpWorldPluginAttach(void) { + registerMeshPlugin(); + registerNativeDataPlugin(); + registerAtomicRightsPlugin(); + registerMaterialRightsPlugin(); + + // not sure if this goes here + rw::xbox::registerVertexFormatPlugin(); + return true; +} + +RpWorld *RpWorldRemoveCamera(RpWorld *world, RwCamera *camera) { world->removeCamera(camera); return world; } +RpWorld *RpWorldAddCamera(RpWorld *world, RwCamera *camera) { world->addCamera(camera); return world; } +RpWorld *RwCameraGetWorld(const RwCamera *camera); +RpWorld *RpWorldRemoveAtomic(RpWorld *world, RpAtomic *atomic); +RpWorld *RpWorldAddAtomic(RpWorld *world, RpAtomic *atomic); +RpWorld *RpAtomicGetWorld(const RpAtomic *atomic); +RpWorld *RpWorldAddClump(RpWorld *world, RpClump *clump); +RpWorld *RpWorldRemoveClump(RpWorld *world, RpClump *clump); +RpWorld *RpClumpGetWorld(const RpClump *clump); +RpWorld *RpWorldAddLight(RpWorld *world, RpLight *light) { world->addLight(light); return world; } +RpWorld *RpWorldRemoveLight(RpWorld *world, RpLight *light) { world->removeLight(light); return world; } +RpWorld *RpLightGetWorld(const RpLight *light); +RwCamera *RwCameraForAllClumpsInFrustum(RwCamera *camera, void *data); +RwCamera *RwCameraForAllClumpsNotInFrustum(RwCamera *camera, RwInt32 numClumps, void *data); + + + + +RwBool RpMatFXPluginAttach( void ) { registerMatFXPlugin(); return true; } +RpAtomic *RpMatFXAtomicEnableEffects( RpAtomic *atomic ) { MatFX::enableEffects(atomic); return atomic; } +RpMatFXMaterialFlags RpMatFXMaterialGetEffects( const RpMaterial *material ){ return (RpMatFXMaterialFlags)MatFX::getEffects(material); } +RpMaterial *RpMatFXMaterialSetEffects( RpMaterial *material, RpMatFXMaterialFlags flags ) { MatFX::setEffects(material, (uint32)flags); return material; } +RpMaterial *RpMatFXMaterialSetupEnvMap( RpMaterial *material, RwTexture *texture, RwFrame *frame, RwBool useFrameBufferAlpha, RwReal coef ) { + MatFX *mfx = MatFX::get(material); + mfx->setEnvTexture(texture); + mfx->setEnvFrame(frame); + mfx->setEnvCoefficient(coef); + return material; +} +RpMaterial *RpMatFXMaterialSetEnvMapFrame( RpMaterial *material, RwFrame *frame ) +{ + MatFX *mfx = MatFX::get(material); + mfx->setEnvFrame(frame); + return material; +} +RpMaterial *RpMatFXMaterialSetEnvMapFrameBufferAlpha( RpMaterial *material, RwBool useFrameBufferAlpha ) +{ + MatFX *mfx = MatFX::get(material); + mfx->setEnvFBAlpha(useFrameBufferAlpha); + return material; +} +RpMaterial *RpMatFXMaterialSetEnvMapCoefficient( RpMaterial *material, RwReal coef ) +{ + MatFX *mfx = MatFX::get(material); + mfx->setEnvCoefficient(coef); + return material; +} +RwReal RpMatFXMaterialGetEnvMapCoefficient( const RpMaterial *material ) +{ + MatFX *mfx = MatFX::get(material); + return mfx->getEnvCoefficient(); +} + + + +RwBool RpHAnimPluginAttach(void) { + registerHAnimPlugin(); + return true; +} + +RwInt32 RpHAnimFrameGetID(RwFrame *frame) { return HAnimData::get(frame)->id; } + +RwInt32 RpHAnimIDGetIndex(RpHAnimHierarchy *hierarchy, RwInt32 ID) { return hierarchy->getIndex(ID); } + +RwBool RpHAnimFrameSetHierarchy(RwFrame *frame, RpHAnimHierarchy *hierarchy) { HAnimData::get(frame)->hierarchy = hierarchy; return true; } +RpHAnimHierarchy *RpHAnimFrameGetHierarchy(RwFrame *frame) { return HAnimHierarchy::get(frame); } + +RpHAnimHierarchy *RpHAnimHierarchySetFlags(RpHAnimHierarchy *hierarchy, RpHAnimHierarchyFlag flags) { hierarchy->flags = flags; return hierarchy; } + +RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy *hierarchy, RpHAnimAnimation *anim) { hierarchy->interpolator->setCurrentAnim(anim); return true; } +RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy *hierarchy, RwReal time) { hierarchy->interpolator->addTime(time); return true; } + +RwMatrix *RpHAnimHierarchyGetMatrixArray(RpHAnimHierarchy *hierarchy) { return hierarchy->matrices; } +RwBool RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy *hierarchy) { hierarchy->updateMatrices(); return true; } + +RpHAnimAnimation *RpHAnimAnimationCreate(RwInt32 typeID, RwInt32 numFrames, RwInt32 flags, RwReal duration) + { return Animation::create(AnimInterpolatorInfo::find(typeID), numFrames, flags, duration); } +RpHAnimAnimation *RpHAnimAnimationDestroy(RpHAnimAnimation *animation) { animation->destroy(); return animation; } +RpHAnimAnimation *RpHAnimAnimationStreamRead(RwStream *stream) { return Animation::streamRead(stream); } + + + + + + +RwBool RpSkinPluginAttach(void) { + registerSkinPlugin(); + return true; +} + +RwUInt32 RpSkinGetNumBones( RpSkin *skin ) { return skin->numBones; } +const RwMatrixWeights *RpSkinGetVertexBoneWeights( RpSkin *skin ) { return (RwMatrixWeights*)skin->weights; } +const RwUInt32 *RpSkinGetVertexBoneIndices( RpSkin *skin ) { return (RwUInt32*)skin->indices; } +const RwMatrix *RpSkinGetSkinToBoneMatrices( RpSkin *skin ) { return (const RwMatrix*)skin->inverseMatrices; } + +RpSkin *RpSkinGeometryGetSkin( RpGeometry *geometry ) { return Skin::get(geometry); } + +RpAtomic *RpSkinAtomicSetHAnimHierarchy( RpAtomic *atomic, RpHAnimHierarchy *hierarchy ) { Skin::setHierarchy(atomic, hierarchy); return atomic; } +RpHAnimHierarchy *RpSkinAtomicGetHAnimHierarchy( const RpAtomic *atomic ) { return Skin::getHierarchy(atomic); } + +RwImage * +RtBMPImageWrite(RwImage *image, const RwChar *imageName) +{ +#ifndef _WIN32 + char *r = casepath(imageName); + if (r) { + rw::writeBMP(image, r); + free(r); + } else { + rw::writeBMP(image, imageName); + } + +#else + rw::writeBMP(image, imageName); +#endif + return image; +} +RwImage * +RtBMPImageRead(const RwChar *imageName) +{ +#ifndef _WIN32 + RwImage *image; + char *r = casepath(imageName); + if (r) { + image = rw::readBMP(r); + free(r); + } else { + image = rw::readBMP(imageName); + } + return image; + +#else + return rw::readBMP(imageName); +#endif +} + + +RwImage * +RtPNGImageWrite(RwImage *image, const RwChar *imageName) +{ +#ifndef _WIN32 + char *r = casepath(imageName); + if (r) { + rw::writePNG(image, r); + free(r); + } else { + rw::writePNG(image, imageName); + } + +#else + rw::writePNG(image, imageName); +#endif + return image; +} +RwImage * +RtPNGImageRead(const RwChar *imageName) +{ +#ifndef _WIN32 + RwImage *image; + char *r = casepath(imageName); + if (r) { + image = rw::readPNG(r); + free(r); + } else { + image = rw::readPNG(imageName); + } + return image; + +#else + return rw::readPNG(imageName); +#endif +} + +#include "rtquat.h" + +RtQuat *RtQuatRotate(RtQuat * quat, const RwV3d * axis, RwReal angle, RwOpCombineType combineOp) { return (RtQuat*)((rw::Quat*)quat)->rotate(axis, angle/180.0f*3.14159f, (CombineOp)combineOp); } +void RtQuatConvertToMatrix(const RtQuat * const qpQuat, RwMatrix * const mpMatrix) { mpMatrix->rotate(*(rw::Quat*)qpQuat, COMBINEREPLACE); } + + +#include "rtcharse.h" + +RwBool RtCharsetOpen(void) { return Charset::open(); } +void RtCharsetClose(void) { return Charset::close(); } +RtCharset *RtCharsetPrint(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y) { charSet->print(string, x, y, true); return charSet; } +RtCharset *RtCharsetPrintBuffered(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y, RwBool hideSpaces) { charSet->printBuffered(string, x, y, hideSpaces); return charSet; } +RwBool RtCharsetBufferFlush(void) { Charset::flushBuffer(); return true; } +RtCharset *RtCharsetSetColors(RtCharset * charSet, const RwRGBA * foreGround, const RwRGBA * backGround) { return charSet->setColors(foreGround, backGround); } +RtCharset *RtCharsetGetDesc(RtCharset * charset, RtCharsetDesc * desc) { *desc = charset->desc; return charset; } +RtCharset *RtCharsetCreate(const RwRGBA * foreGround, const RwRGBA * backGround) { return Charset::create(foreGround, backGround); } +RwBool RtCharsetDestroy(RtCharset * charSet) { charSet->destroy(); return true; } + + + +#include + +RwInt8 RpAnisotGetMaxSupportedMaxAnisotropy(void) { return rw::getMaxSupportedMaxAnisotropy(); } +RwTexture *RpAnisotTextureSetMaxAnisotropy(RwTexture *tex, RwInt8 val) { tex->setMaxAnisotropy(val); return tex; } +RwInt8 RpAnisotTextureGetMaxAnisotropy(RwTexture *tex) { return tex->getMaxAnisotropy(); } +RwBool RpAnisotPluginAttach(void) { rw::registerAnisotropyPlugin(); return true; } diff --git a/src/fakerw/rpanisot.h b/src/fakerw/rpanisot.h new file mode 100644 index 0000000..a886512 --- /dev/null +++ b/src/fakerw/rpanisot.h @@ -0,0 +1,6 @@ +#pragma once + +RwInt8 RpAnisotGetMaxSupportedMaxAnisotropy(void); +RwTexture *RpAnisotTextureSetMaxAnisotropy(RwTexture *tex, RwInt8 val); +RwInt8 RpAnisotTextureGetMaxAnisotropy(RwTexture *tex); +RwBool RpAnisotPluginAttach(void); diff --git a/src/fakerw/rphanim.h b/src/fakerw/rphanim.h new file mode 100644 index 0000000..6305980 --- /dev/null +++ b/src/fakerw/rphanim.h @@ -0,0 +1,72 @@ +#pragma once + +#include "rtquat.h" + +//struct RpHAnimHierarchy; +typedef rw::HAnimHierarchy RpHAnimHierarchy; +//struct RpHAnimAnimation; +typedef rw::Animation RpHAnimAnimation; + +#define rpHANIMSTDKEYFRAMETYPEID 0x1 + +// same as rw::HAnimKeyFrame, but we need RtQuat in this one +struct RpHAnimStdKeyFrame +{ + RpHAnimStdKeyFrame *prevFrame; + RwReal time; + RtQuat q; + RwV3d t; +}; +// same story, this one only exists in later RW versions +// but we need it for 64 bit builds because offset and size differs! +struct RpHAnimStdInterpFrame +{ + RpHAnimStdKeyFrame *keyFrame1; + RpHAnimStdKeyFrame *keyFrame2; + RtQuat q; + RwV3d t; +}; + +enum RpHAnimHierarchyFlag +{ + rpHANIMHIERARCHYSUBHIERARCHY = rw::HAnimHierarchy::SUBHIERARCHY, + rpHANIMHIERARCHYNOMATRICES = rw::HAnimHierarchy::NOMATRICES, + + rpHANIMHIERARCHYUPDATEMODELLINGMATRICES = rw::HAnimHierarchy::UPDATEMODELLINGMATRICES, + rpHANIMHIERARCHYUPDATELTMS = rw::HAnimHierarchy::UPDATELTMS, + rpHANIMHIERARCHYLOCALSPACEMATRICES = rw::HAnimHierarchy::LOCALSPACEMATRICES +}; + +#define rpHANIMPOPPARENTMATRIX rw::HAnimHierarchy::POP +#define rpHANIMPUSHPARENTMATRIX rw::HAnimHierarchy::PUSH + +RwBool RpHAnimPluginAttach(void); + +RwBool RpHAnimFrameSetID(RwFrame *frame, RwInt32 id); +RwInt32 RpHAnimFrameGetID(RwFrame *frame); + +RwInt32 RpHAnimIDGetIndex(RpHAnimHierarchy *hierarchy, RwInt32 ID); + +RwBool RpHAnimFrameSetHierarchy(RwFrame *frame, RpHAnimHierarchy *hierarchy); +RpHAnimHierarchy *RpHAnimFrameGetHierarchy(RwFrame *frame); + +RpHAnimHierarchy *RpHAnimHierarchySetFlags(RpHAnimHierarchy *hierarchy, RpHAnimHierarchyFlag flags); +RpHAnimHierarchyFlag RpHAnimHierarchyGetFlags(RpHAnimHierarchy *hierarchy); + +RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy *hierarchy, RpHAnimAnimation *anim); +RwBool RpHAnimHierarchySetCurrentAnimTime(RpHAnimHierarchy *hierarchy, RwReal time); +RwBool RpHAnimHierarchySubAnimTime(RpHAnimHierarchy *hierarchy, RwReal time); +RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy *hierarchy, RwReal time); + +RwMatrix *RpHAnimHierarchyGetMatrixArray(RpHAnimHierarchy *hierarchy); +RwBool RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy *hierarchy); + +#define rpHANIMHIERARCHYGETINTERPFRAME( hierarchy, nodeIndex ) \ + ( (void *)( ( (RwUInt8 *)&(hierarchy->interpolator[1]) + \ + ((nodeIndex) * \ + hierarchy->interpolator->currentInterpKeyFrameSize) ) ) ) + + +RpHAnimAnimation *RpHAnimAnimationCreate(RwInt32 typeID, RwInt32 numFrames, RwInt32 flags, RwReal duration); +RpHAnimAnimation *RpHAnimAnimationDestroy(RpHAnimAnimation *animation); +RpHAnimAnimation *RpHAnimAnimationStreamRead(RwStream *stream); diff --git a/src/fakerw/rpmatfx.h b/src/fakerw/rpmatfx.h new file mode 100644 index 0000000..87c8fb2 --- /dev/null +++ b/src/fakerw/rpmatfx.h @@ -0,0 +1,43 @@ +#pragma once + +enum RpMatFXMaterialFlags +{ + rpMATFXEFFECTNULL = rw::MatFX::NOTHING, + rpMATFXEFFECTBUMPMAP = rw::MatFX::BUMPMAP, + rpMATFXEFFECTENVMAP = rw::MatFX::ENVMAP, + rpMATFXEFFECTBUMPENVMAP = rw::MatFX::BUMPENVMAP, + rpMATFXEFFECTDUAL = rw::MatFX::DUAL, + + rpMATFXEFFECTMAX, + rpMATFXNUMEFFECTS = rpMATFXEFFECTMAX - 1, +}; + +RwBool RpMatFXPluginAttach( void ); +RpAtomic *RpMatFXAtomicEnableEffects( RpAtomic *atomic ); +RwBool RpMatFXAtomicQueryEffects( RpAtomic *atomic ); +//RpWorldSector *RpMatFXWorldSectorEnableEffects( RpWorldSector *worldSector ); +//RwBool RpMatFXWorldSectorQueryEffects( RpWorldSector *worldSector ); +RpMaterial *RpMatFXMaterialSetEffects( RpMaterial *material, RpMatFXMaterialFlags flags ); +RpMaterial *RpMatFXMaterialSetupBumpMap( RpMaterial *material, RwTexture *texture, RwFrame *frame, RwReal coef ); +RpMaterial *RpMatFXMaterialSetupEnvMap( RpMaterial *material, RwTexture *texture, RwFrame *frame, RwBool useFrameBufferAlpha, RwReal coef ); +RpMaterial *RpMatFXMaterialSetupDualTexture( RpMaterial *material, RwTexture *texture, RwBlendFunction srcBlendMode, RwBlendFunction dstBlendMode ); +RpMatFXMaterialFlags RpMatFXMaterialGetEffects( const RpMaterial *material ); +RpMaterial *RpMatFXMaterialSetBumpMapTexture( RpMaterial *material, RwTexture *texture ); +RpMaterial *RpMatFXMaterialSetBumpMapFrame( RpMaterial *material, RwFrame *frame ); +RpMaterial *RpMatFXMaterialSetBumpMapCoefficient( RpMaterial *material, RwReal coef ); +RwTexture *RpMatFXMaterialGetBumpMapTexture( const RpMaterial *material ); +RwTexture *RpMatFXMaterialGetBumpMapBumpedTexture( const RpMaterial *material ); +RwFrame *RpMatFXMaterialGetBumpMapFrame( const RpMaterial *material ); +RwReal RpMatFXMaterialGetBumpMapCoefficient( const RpMaterial *material ); +RpMaterial *RpMatFXMaterialSetEnvMapTexture( RpMaterial *material, RwTexture *texture ); +RpMaterial *RpMatFXMaterialSetEnvMapFrame( RpMaterial *material, RwFrame *frame ); +RpMaterial *RpMatFXMaterialSetEnvMapFrameBufferAlpha( RpMaterial *material, RwBool useFrameBufferAlpha ); +RpMaterial *RpMatFXMaterialSetEnvMapCoefficient( RpMaterial *material, RwReal coef ); +RwTexture *RpMatFXMaterialGetEnvMapTexture( const RpMaterial *material ); +RwFrame *RpMatFXMaterialGetEnvMapFrame( const RpMaterial *material ); +RwBool RpMatFXMaterialGetEnvMapFrameBufferAlpha( const RpMaterial *material ); +RwReal RpMatFXMaterialGetEnvMapCoefficient( const RpMaterial *material ); +RpMaterial *RpMatFXMaterialSetDualTexture( RpMaterial *material, RwTexture *texture ); +RpMaterial *RpMatFXMaterialSetDualBlendModes( RpMaterial *material, RwBlendFunction srcBlendMode, RwBlendFunction dstBlendMode ); +RwTexture *RpMatFXMaterialGetDualTexture( const RpMaterial *material ); +const RpMaterial *RpMatFXMaterialGetDualBlendModes( const RpMaterial *material, RwBlendFunction *srcBlendMode, RwBlendFunction *dstBlendMode ); diff --git a/src/fakerw/rpskin.h b/src/fakerw/rpskin.h new file mode 100644 index 0000000..1ffc9f2 --- /dev/null +++ b/src/fakerw/rpskin.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +//struct RpSkin; +typedef rw::Skin RpSkin; + +struct RwMatrixWeights +{ + RwReal w0; + RwReal w1; + RwReal w2; + RwReal w3; +}; + +RwBool RpSkinPluginAttach(void); + +RwUInt32 RpSkinGetNumBones( RpSkin *skin ); +const RwMatrixWeights *RpSkinGetVertexBoneWeights( RpSkin *skin ); +const RwUInt32 *RpSkinGetVertexBoneIndices( RpSkin *skin ); +const RwMatrix *RpSkinGetSkinToBoneMatrices( RpSkin *skin ); + +RpSkin *RpSkinGeometryGetSkin( RpGeometry *geometry ); + +RpAtomic *RpSkinAtomicSetHAnimHierarchy( RpAtomic *atomic, RpHAnimHierarchy *hierarchy ); +RpHAnimHierarchy *RpSkinAtomicGetHAnimHierarchy( const RpAtomic *atomic ); diff --git a/src/fakerw/rpworld.h b/src/fakerw/rpworld.h new file mode 100644 index 0000000..f10a375 --- /dev/null +++ b/src/fakerw/rpworld.h @@ -0,0 +1,336 @@ +#pragma once + +#define rpATOMIC rw::Atomic::ID +#define rpCLUMP rw::Clump::ID + +/* + *********************************************** + * + * RpMaterial + * + *********************************************** + */ + +//struct RpMaterial; +typedef rw::Material RpMaterial; + +typedef RpMaterial *(*RpMaterialCallBack)(RpMaterial *material, void *data); + +RpMaterial *RpMaterialCreate(void); +RwBool RpMaterialDestroy(RpMaterial *material); +RpMaterial *RpMaterialClone(RpMaterial *material); +RpMaterial *RpMaterialSetTexture(RpMaterial *material, RwTexture *texture); +RpMaterial *RpMaterialAddRef(RpMaterial *material); +RwTexture *RpMaterialGetTexture(const RpMaterial *material); +RpMaterial *RpMaterialSetColor(RpMaterial *material, const RwRGBA *color); +const RwRGBA *RpMaterialGetColor(const RpMaterial *material); +RpMaterial *RpMaterialSetSurfaceProperties(RpMaterial *material, const RwSurfaceProperties *surfaceProperties); +const RwSurfaceProperties *RpMaterialGetSurfaceProperties(const RpMaterial *material); +RwInt32 RpMaterialRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpMaterialRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpMaterialSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpMaterialGetPluginOffset(RwUInt32 pluginID); +RwBool RpMaterialValidatePlugins(const RpMaterial *material); +RwUInt32 RpMaterialStreamGetSize(const RpMaterial *material); +RpMaterial *RpMaterialStreamRead(RwStream *stream); +const RpMaterial *RpMaterialStreamWrite(const RpMaterial *material, RwStream *stream); +//RpMaterialChunkInfo *_rpMaterialChunkInfoRead(RwStream *stream, RpMaterialChunkInfo *materialChunkInfo, RwInt32 *bytesRead); + + +/* + *********************************************** + * + * RpLight + * + *********************************************** + */ + +//struct RpLight; +typedef rw::Light RpLight; + +enum RpLightType +{ + rpNALIGHTTYPE = 0, + rpLIGHTDIRECTIONAL, + rpLIGHTAMBIENT, + rpLIGHTPOINT = 0x80, + rpLIGHTSPOT, + rpLIGHTSPOTSOFT, +}; + +enum RpLightFlag +{ + rpLIGHTLIGHTATOMICS = 0x01, + rpLIGHTLIGHTWORLD = 0x02, +}; + +typedef RpLight *(*RpLightCallBack) (RpLight * light, void *data); + +RwReal RpLightGetRadius(const RpLight *light); +const RwRGBAReal *RpLightGetColor(const RpLight *light); +RpLight *RpLightSetFrame(RpLight *light, RwFrame *frame); +RwFrame *RpLightGetFrame(const RpLight *light); +RpLightType RpLightGetType(const RpLight *light); +RpLight *RpLightSetFlags(RpLight *light, RwUInt32 flags); +RwUInt32 RpLightGetFlags(const RpLight *light); +RpLight *RpLightCreate(RwInt32 type); +RwBool RpLightDestroy(RpLight *light); +RpLight *RpLightSetRadius(RpLight *light, RwReal radius); +RpLight *RpLightSetColor(RpLight *light, const RwRGBAReal *color); +RwReal RpLightGetConeAngle(const RpLight *light); +RpLight *RpLightSetConeAngle(RpLight * ight, RwReal angle); +RwUInt32 RpLightStreamGetSize(const RpLight *light); +RpLight *RpLightStreamRead(RwStream *stream); +const RpLight *RpLightStreamWrite(const RpLight *light, RwStream *stream); +//RpLightChunkInfo *_rpLightChunkInfoRead(RwStream *stream, RpLightChunkInfo *lightChunkInfo, RwInt32 *bytesRead); +RwInt32 RpLightRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpLightRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpLightSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpLightGetPluginOffset(RwUInt32 pluginID); +RwBool RpLightValidatePlugins(const RpLight * light); + +/* + *********************************************** + * + * RpGeometry + * + *********************************************** + */ + +typedef rw::Triangle RpTriangle; + +//struct RpGeometry; +typedef rw::Geometry RpGeometry; +//struct RpMorphTarget; +typedef rw::MorphTarget RpMorphTarget; + +enum RpGeometryFlag +{ + rpGEOMETRYTRISTRIP = rw::Geometry::TRISTRIP, + rpGEOMETRYPOSITIONS = rw::Geometry::POSITIONS, + rpGEOMETRYTEXTURED = rw::Geometry::TEXTURED, + rpGEOMETRYPRELIT = rw::Geometry::PRELIT, + rpGEOMETRYNORMALS = rw::Geometry::NORMALS, + rpGEOMETRYLIGHT = rw::Geometry::LIGHT, + rpGEOMETRYMODULATEMATERIALCOLOR = rw::Geometry::MODULATE, + rpGEOMETRYTEXTURED2 = rw::Geometry::TEXTURED2, + rpGEOMETRYNATIVE = rw::Geometry::NATIVE, + rpGEOMETRYNATIVEINSTANCE = rw::Geometry::NATIVEINSTANCE, + rpGEOMETRYFLAGSMASK = 0x000000FF, + rpGEOMETRYNATIVEFLAGSMASK = 0x0F000000, +}; + +enum RpGeometryLockMode +{ + rpGEOMETRYLOCKPOLYGONS = rw::Geometry::LOCKPOLYGONS, + rpGEOMETRYLOCKVERTICES = rw::Geometry::LOCKVERTICES, + rpGEOMETRYLOCKNORMALS = rw::Geometry::LOCKNORMALS, + rpGEOMETRYLOCKPRELIGHT = rw::Geometry::LOCKPRELIGHT, + rpGEOMETRYLOCKTEXCOORDS = rw::Geometry::LOCKTEXCOORDS, + rpGEOMETRYLOCKTEXCOORDS1 = rw::Geometry::LOCKTEXCOORDS1, + rpGEOMETRYLOCKTEXCOORDS2 = rw::Geometry::LOCKTEXCOORDS2, + rpGEOMETRYLOCKTEXCOORDS3 = rw::Geometry::LOCKTEXCOORDS3, + rpGEOMETRYLOCKTEXCOORDS4 = rw::Geometry::LOCKTEXCOORDS4, + rpGEOMETRYLOCKTEXCOORDS5 = rw::Geometry::LOCKTEXCOORDS5, + rpGEOMETRYLOCKTEXCOORDS6 = rw::Geometry::LOCKTEXCOORDS6, + rpGEOMETRYLOCKTEXCOORDS7 = rw::Geometry::LOCKTEXCOORDS7, + rpGEOMETRYLOCKTEXCOORDS8 = rw::Geometry::LOCKTEXCOORDS8, + rpGEOMETRYLOCKTEXCOORDSALL = rw::Geometry::LOCKTEXCOORDSALL, + rpGEOMETRYLOCKALL = rw::Geometry::LOCKALL +}; + +RpGeometry *RpGeometryCreate(RwInt32 numVert, RwInt32 numTriangles, RwUInt32 format); +RwBool RpGeometryDestroy(RpGeometry *geometry); +RpGeometry *_rpGeometryAddRef(RpGeometry *geometry); +RpGeometry *RpGeometryLock(RpGeometry *geometry, RwInt32 lockMode); +RpGeometry *RpGeometryUnlock(RpGeometry *geometry); +RpGeometry *RpGeometryTransform(RpGeometry *geometry, const RwMatrix *matrix); +RpGeometry *RpGeometryCreateSpace(RwReal radius); +RpMorphTarget *RpMorphTargetSetBoundingSphere(RpMorphTarget *morphTarget, const RwSphere *boundingSphere); +RwSphere *RpMorphTargetGetBoundingSphere(RpMorphTarget *morphTarget); +const RpMorphTarget *RpMorphTargetCalcBoundingSphere(const RpMorphTarget *morphTarget, RwSphere *boundingSphere); +RwInt32 RpGeometryAddMorphTargets(RpGeometry *geometry, RwInt32 mtcount); +RwInt32 RpGeometryAddMorphTarget(RpGeometry *geometry); +RpGeometry *RpGeometryRemoveMorphTarget(RpGeometry *geometry, RwInt32 morphTarget); +RwInt32 RpGeometryGetNumMorphTargets(const RpGeometry *geometry); +RpMorphTarget *RpGeometryGetMorphTarget(const RpGeometry *geometry, RwInt32 morphTarget); +RwRGBA *RpGeometryGetPreLightColors(const RpGeometry *geometry); +RwTexCoords *RpGeometryGetVertexTexCoords(const RpGeometry *geometry, RwTextureCoordinateIndex uvIndex); +RwInt32 RpGeometryGetNumTexCoordSets(const RpGeometry *geometry); +RwInt32 RpGeometryGetNumVertices (const RpGeometry *geometry); +RwV3d *RpMorphTargetGetVertices(const RpMorphTarget *morphTarget); +RwV3d *RpMorphTargetGetVertexNormals(const RpMorphTarget *morphTarget); +RpTriangle *RpGeometryGetTriangles(const RpGeometry *geometry); +RwInt32 RpGeometryGetNumTriangles(const RpGeometry *geometry); +RpMaterial *RpGeometryGetMaterial(const RpGeometry *geometry, RwInt32 matNum); +const RpGeometry *RpGeometryTriangleSetVertexIndices(const RpGeometry *geometry, RpTriangle *triangle, RwUInt16 vert1, RwUInt16 vert2, RwUInt16 vert3); +RpGeometry *RpGeometryTriangleSetMaterial(RpGeometry *geometry, RpTriangle *triangle, RpMaterial *material); +const RpGeometry *RpGeometryTriangleGetVertexIndices(const RpGeometry *geometry, const RpTriangle *triangle, RwUInt16 *vert1, RwUInt16 *vert2, RwUInt16 *vert3); +RpMaterial *RpGeometryTriangleGetMaterial(const RpGeometry *geometry, const RpTriangle *triangle); +RwInt32 RpGeometryGetNumMaterials(const RpGeometry *geometry); +RpGeometry *RpGeometryForAllMaterials(RpGeometry *geometry, RpMaterialCallBack fpCallBack, void *pData); +//const RpGeometry *RpGeometryForAllMeshes(const RpGeometry *geometry, RpMeshCallBack fpCallBack, void *pData); +RwInt32 RpGeometryRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpGeometryRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpGeometrySetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpGeometryGetPluginOffset(RwUInt32 pluginID); +RwBool RpGeometryValidatePlugins(const RpGeometry *geometry); +RwUInt32 RpGeometryStreamGetSize(const RpGeometry *geometry); +const RpGeometry *RpGeometryStreamWrite(const RpGeometry *geometry, RwStream *stream); +RpGeometry *RpGeometryStreamRead(RwStream *stream); +//RpGeometryChunkInfo *_rpGeometryChunkInfoRead(RwStream *stream, RpGeometryChunkInfo *geometryChunkInfo, RwInt32 *bytesRead); +RwUInt32 RpGeometryGetFlags(const RpGeometry *geometry); +RpGeometry *RpGeometrySetFlags(RpGeometry *geometry, RwUInt32 flags); +const RwSurfaceProperties *_rpGeometryGetSurfaceProperties(const RpGeometry *geometry); +RpGeometry *_rpGeometrySetSurfaceProperties(RpGeometry *geometry, const RwSurfaceProperties *surfaceProperties); + + +/* + *********************************************** + * + * RpAtomic and RpClump + * + *********************************************** + */ + +//struct RpAtomic; +typedef rw::Atomic RpAtomic; + +enum RpAtomicFlag +{ + rpATOMICCOLLISIONTEST = 0x01, + rpATOMICRENDER = 0x04, +}; + +enum RpAtomicSetGeomFlag +{ + rpATOMICSAMEBOUNDINGSPHERE = 0x01, +}; + +typedef RpAtomic *(*RpAtomicCallBack) (RpAtomic * atomic, void *data); +typedef RpAtomic *(*RpAtomicCallBackRender) (RpAtomic * atomic); + + +//struct RpClump; +typedef rw::Clump RpClump; + +struct RpClumpChunkInfo +{ + RwInt32 numAtomics; + RwInt32 numLights; + RwInt32 numCameras; +}; + +typedef RpClump *(*RpClumpCallBack) (RpClump * clump, void *data); + + +RpAtomic *AtomicDefaultRenderCallBack(RpAtomic * atomic); +//void _rpAtomicResyncInterpolatedSphere(RpAtomic * atomic); +//const RwSphere *RpAtomicGetWorldBoundingSphere(RpAtomic * atomic); + +RwFrame *RpClumpGetFrame(const RpClump * clump); +RpClump *RpClumpSetFrame(RpClump * clump, RwFrame * frame); +RpClump *RpClumpForAllAtomics(RpClump * clump, RpAtomicCallBack callback, void *pData); +RpClump *RpClumpForAllLights(RpClump * clump, RpLightCallBack callback, void *pData); +RpClump *RpClumpForAllCameras(RpClump * clump, RwCameraCallBack callback, void *pData); +RpClump *RpClumpCreateSpace(const RwV3d * position, RwReal radius); +RpClump *RpClumpRender(RpClump * clump); +RpClump *RpClumpRemoveAtomic(RpClump * clump, RpAtomic * atomic); +RpClump *RpClumpAddAtomic(RpClump * clump, RpAtomic * atomic); +RpClump *RpClumpRemoveLight(RpClump * clump, RpLight * light); +RpClump *RpClumpAddLight(RpClump * clump, RpLight * light); +RpClump *RpClumpRemoveCamera(RpClump * clump, RwCamera * camera); +RpClump *RpClumpAddCamera(RpClump * clump, RwCamera * camera); +RwBool RpClumpDestroy(RpClump * clump); +RpClump *RpClumpCreate(void); +RpClump *RpClumpClone(RpClump * clump); +RpClump *RpClumpSetCallBack(RpClump * clump, RpClumpCallBack callback); +RpClumpCallBack RpClumpGetCallBack(const RpClump * clump); +RwInt32 RpClumpGetNumAtomics(RpClump * clump); +RwInt32 RpClumpGetNumLights(RpClump * clump); +RwInt32 RpClumpGetNumCameras(RpClump * clump); +RwUInt32 RpClumpStreamGetSize(RpClump * clump); +RpClump *RpClumpStreamRead(RwStream * stream); +RpClump *RpClumpStreamWrite(RpClump * clump, RwStream * stream); +RwInt32 RpClumpRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpClumpRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpClumpSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpClumpGetPluginOffset(RwUInt32 pluginID); +RwBool RpClumpValidatePlugins(const RpClump * clump); + +RpAtomic *RpAtomicCreate(void); +RwBool RpAtomicDestroy(RpAtomic * atomic); +RpAtomic *RpAtomicClone(RpAtomic * atomic); +RpAtomic *RpAtomicSetFrame(RpAtomic * atomic, RwFrame * frame); +RpAtomic *RpAtomicSetGeometry(RpAtomic * atomic, RpGeometry * geometry, RwUInt32 flags); + +RwFrame *RpAtomicGetFrame(const RpAtomic * atomic); +RpAtomic *RpAtomicSetFlags(RpAtomic * atomic, RwUInt32 flags); +RwUInt32 RpAtomicGetFlags(const RpAtomic * atomic); +RwSphere *RpAtomicGetBoundingSphere(RpAtomic * atomic); +RpAtomic *RpAtomicRender(RpAtomic * atomic); +RpClump *RpAtomicGetClump(const RpAtomic * atomic); +//RpInterpolator *RpAtomicGetInterpolator(RpAtomic * atomic); +RpGeometry *RpAtomicGetGeometry(const RpAtomic * atomic); +void RpAtomicSetRenderCallBack(RpAtomic * atomic, RpAtomicCallBackRender callback); +RpAtomicCallBackRender RpAtomicGetRenderCallBack(const RpAtomic * atomic); +RwBool RpAtomicInstance(RpAtomic *atomic); +RwUInt32 RpAtomicStreamGetSize(RpAtomic * atomic); +RpAtomic *RpAtomicStreamRead(RwStream * stream); +RpAtomic *RpAtomicStreamWrite(RpAtomic * atomic, RwStream * stream); +RwInt32 RpAtomicRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpAtomicRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpAtomicSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpAtomicSetStreamRightsCallBack(RwUInt32 pluginID, RwPluginDataChunkRightsCallBack rightsCB); +RwInt32 RpAtomicGetPluginOffset(RwUInt32 pluginID); +RwBool RpAtomicValidatePlugins(const RpAtomic * atomic); + +//RwInt32 RpInterpolatorGetEndMorphTarget(const RpInterpolator * interpolator); +//RwInt32 RpInterpolatorGetStartMorphTarget(const RpInterpolator * interpolator); +//RwReal RpInterpolatorGetValue(const RpInterpolator * interpolator); +//RwReal RpInterpolatorGetScale(const RpInterpolator * interpolator); +//RpInterpolator *RpInterpolatorSetEndMorphTarget(RpInterpolator * interpolator, RwInt32 morphTarget, RpAtomic * atomic); +//RpInterpolator *RpInterpolatorSetStartMorphTarget(RpInterpolator * interpolator, RwInt32 morphTarget, RpAtomic * atomic); +//RpInterpolator *RpInterpolatorSetValue(RpInterpolator * interpolator, RwReal value, RpAtomic *atomic); +//RpInterpolator *RpInterpolatorSetScale(RpInterpolator * interpolator, RwReal scale, RpAtomic *atomic); + + +RpClump *RpLightGetClump(const RpLight *light); +RpClump *RwCameraGetClump(const RwCamera *camera); + +/* + *********************************************** + * + * RpWorld + * + *********************************************** + */ + +//struct RpWorld; +typedef rw::World RpWorld; + +RwBool RpWorldDestroy(RpWorld * world); +RpWorld *RpWorldCreate(RwBBox * boundingBox); + +RwBool RpWorldPluginAttach(void); + +RpWorld *RpWorldRemoveCamera(RpWorld *world, RwCamera *camera); +RpWorld *RpWorldAddCamera(RpWorld *world, RwCamera *camera); +RpWorld *RwCameraGetWorld(const RwCamera *camera); +RpWorld *RpWorldRemoveAtomic(RpWorld *world, RpAtomic *atomic); +RpWorld *RpWorldAddAtomic(RpWorld *world, RpAtomic *atomic); +RpWorld *RpAtomicGetWorld(const RpAtomic *atomic); +RpWorld *RpWorldAddClump(RpWorld *world, RpClump *clump); +RpWorld *RpWorldRemoveClump(RpWorld *world, RpClump *clump); +RpWorld *RpClumpGetWorld(const RpClump *clump); +RpWorld *RpWorldAddLight(RpWorld *world, RpLight *light); +RpWorld *RpWorldRemoveLight(RpWorld *world, RpLight *light); +RpWorld *RpLightGetWorld(const RpLight *light); +RwCamera *RwCameraForAllClumpsInFrustum(RwCamera *camera, void *data); +RwCamera *RwCameraForAllClumpsNotInFrustum(RwCamera *camera, RwInt32 numClumps, void *data); +//RwCamera *RwCameraForAllSectorsInFrustum(RwCamera *camera, RpWorldSectorCallBack callBack, void *pData); +//RpLight *RpLightForAllWorldSectors(RpLight *light, RpWorldSectorCallBack callback, void *data); +//RpAtomic *RpAtomicForAllWorldSectors(RpAtomic *atomic, RpWorldSectorCallBack callback, void *data); +//RpWorldSector *RpWorldSectorForAllAtomics(RpWorldSector *sector, RpAtomicCallBack callback, void *data); +//RpWorldSector *RpWorldSectorForAllCollisionAtomics(RpWorldSector *sector, RpAtomicCallBack callback, void *data); +//RpWorldSector *RpWorldSectorForAllLights(RpWorldSector *sector, RpLightCallBack callback, void *data); diff --git a/src/fakerw/rtbmp.h b/src/fakerw/rtbmp.h new file mode 100644 index 0000000..896d276 --- /dev/null +++ b/src/fakerw/rtbmp.h @@ -0,0 +1,4 @@ +#pragma once + +RwImage *RtBMPImageWrite(RwImage * image, const RwChar * imageName); +RwImage *RtBMPImageRead(const RwChar * imageName); diff --git a/src/fakerw/rtcharse.h b/src/fakerw/rtcharse.h new file mode 100644 index 0000000..10eb1f3 --- /dev/null +++ b/src/fakerw/rtcharse.h @@ -0,0 +1,14 @@ +#pragma once + +typedef rw::Charset RtCharset; +typedef rw::Charset::Desc RtCharsetDesc; + +RwBool RtCharsetOpen(void); +void RtCharsetClose(void); +RtCharset *RtCharsetPrint(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y); +RtCharset *RtCharsetPrintBuffered(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y, RwBool hideSpaces); +RwBool RtCharsetBufferFlush(void); +RtCharset *RtCharsetSetColors(RtCharset * charSet, const RwRGBA * foreGround, const RwRGBA * backGround); +RtCharset *RtCharsetGetDesc(RtCharset * charset, RtCharsetDesc * desc); +RtCharset *RtCharsetCreate(const RwRGBA * foreGround, const RwRGBA * backGround); +RwBool RtCharsetDestroy(RtCharset * charSet); diff --git a/src/fakerw/rtpng.h b/src/fakerw/rtpng.h new file mode 100644 index 0000000..80f2902 --- /dev/null +++ b/src/fakerw/rtpng.h @@ -0,0 +1,4 @@ +#pragma once + +RwImage *RtPNGImageWrite(RwImage * image, const RwChar * imageName); +RwImage *RtPNGImageRead(const RwChar * imageName); diff --git a/src/fakerw/rtquat.h b/src/fakerw/rtquat.h new file mode 100644 index 0000000..450342b --- /dev/null +++ b/src/fakerw/rtquat.h @@ -0,0 +1,15 @@ +#pragma once + +// Same layout as rw::Quat but with ugly imag,real separation which i don't want in librw +struct RtQuat +{ + rw::V3d imag; + rw::float32 real; +}; + +RwBool RtQuatConvertFromMatrix(RtQuat * const qpQuat, const RwMatrix * const mpMatrix); +RtQuat *RtQuatRotate(RtQuat * quat, const RwV3d * axis, RwReal angle, RwOpCombineType combineOp); +const RtQuat *RtQuatQueryRotate(const RtQuat *quat, RwV3d * unitAxis, RwReal * angle); +RwV3d *RtQuatTransformVectors(RwV3d * vectorsOut, const RwV3d * vectorsIn, const RwInt32 numPoints, const RtQuat *quat); + +void RtQuatConvertToMatrix(const RtQuat * const qpQuat, RwMatrix * const mpMatrix); diff --git a/src/fakerw/rwcore.h b/src/fakerw/rwcore.h new file mode 100644 index 0000000..ab0a719 --- /dev/null +++ b/src/fakerw/rwcore.h @@ -0,0 +1,423 @@ +#pragma once + +#define RWCORE_H // needed by CVector + +#include + +#include + +/* + *********************************************** + * + * RwIm2D and RwIm3D + * + *********************************************** + */ + +typedef rw::RWDEVICE::Im2DVertex RwIm2DVertex; +typedef rw::RWDEVICE::Im3DVertex RwIm3DVertex; +typedef RwUInt16 RwImVertexIndex; + +enum RwIm3DTransformFlags +{ + rwIM3D_VERTEXUV = rw::im3d::VERTEXUV, + rwIM3D_ALLOPAQUE = rw::im3d::ALLOPAQUE, + rwIM3D_NOCLIP = rw::im3d::NOCLIP, + rwIM3D_VERTEXXYZ = rw::im3d::VERTEXXYZ, + rwIM3D_VERTEXRGBA = rw::im3d::VERTEXRGBA, +}; + +void RwIm2DVertexSetCameraX(RwIm2DVertex *vert, RwReal camx); +void RwIm2DVertexSetCameraY(RwIm2DVertex *vert, RwReal camy); +void RwIm2DVertexSetCameraZ(RwIm2DVertex *vert, RwReal camz); +void RwIm2DVertexSetRecipCameraZ(RwIm2DVertex *vert, RwReal recipz); +void RwIm2DVertexSetScreenX(RwIm2DVertex *vert, RwReal scrnx); +void RwIm2DVertexSetScreenY(RwIm2DVertex *vert, RwReal scrny); +void RwIm2DVertexSetScreenZ(RwIm2DVertex *vert, RwReal scrnz); +void RwIm2DVertexSetU(RwIm2DVertex *vert, RwReal texU, RwReal recipz); +void RwIm2DVertexSetV(RwIm2DVertex *vert, RwReal texV, RwReal recipz); +void RwIm2DVertexSetIntRGBA(RwIm2DVertex *vert, RwUInt8 red, RwUInt8 green, RwUInt8 blue, RwUInt8 alpha); + +RwReal RwIm2DGetNearScreenZ(void); +RwReal RwIm2DGetFarScreenZ(void); +RwBool RwIm2DRenderLine(RwIm2DVertex *vertices, RwInt32 numVertices, RwInt32 vert1, RwInt32 vert2); +RwBool RwIm2DRenderTriangle(RwIm2DVertex *vertices, RwInt32 numVertices, RwInt32 vert1, RwInt32 vert2, RwInt32 vert3 ); +RwBool RwIm2DRenderPrimitive(RwPrimitiveType primType, RwIm2DVertex *vertices, RwInt32 numVertices); +RwBool RwIm2DRenderIndexedPrimitive(RwPrimitiveType primType, RwIm2DVertex *vertices, RwInt32 numVertices, RwImVertexIndex *indices, RwInt32 numIndices); + + +void RwIm3DVertexSetPos(RwIm3DVertex *vert, RwReal x, RwReal y, RwReal z); +void RwIm3DVertexSetU(RwIm3DVertex *vert, RwReal u); +void RwIm3DVertexSetV(RwIm3DVertex *vert, RwReal v); +void RwIm3DVertexSetRGBA(RwIm3DVertex *vert, RwUInt8 r, RwUInt8 g, RwUInt8 b, RwUInt8 a); + +void *RwIm3DTransform(RwIm3DVertex *pVerts, RwUInt32 numVerts, RwMatrix *ltm, RwUInt32 flags); +RwBool RwIm3DEnd(void); +RwBool RwIm3DRenderLine(RwInt32 vert1, RwInt32 vert2); +RwBool RwIm3DRenderTriangle(RwInt32 vert1, RwInt32 vert2, RwInt32 vert3); +RwBool RwIm3DRenderIndexedPrimitive(RwPrimitiveType primType, RwImVertexIndex *indices, RwInt32 numIndices); +RwBool RwIm3DRenderPrimitive(RwPrimitiveType primType); + + +/* + *********************************************** + * + * RwRaster + * + *********************************************** + */ + +//struct RwRaster; +typedef rw::Raster RwRaster; + +enum RwRasterType +{ + rwRASTERTYPENORMAL = rw::Raster::NORMAL, + rwRASTERTYPEZBUFFER = rw::Raster::ZBUFFER, + rwRASTERTYPECAMERA = rw::Raster::CAMERA, + rwRASTERTYPETEXTURE = rw::Raster::TEXTURE, + rwRASTERTYPECAMERATEXTURE = rw::Raster::CAMERATEXTURE, + rwRASTERTYPEMASK = 0x07, + rwRASTERDONTALLOCATE = rw::Raster::DONTALLOCATE, +}; + +enum RwRasterFormat +{ + rwRASTERFORMATDEFAULT = rw::Raster::DEFAULT, + rwRASTERFORMAT1555 = rw::Raster::C1555, + rwRASTERFORMAT565 = rw::Raster::C565, + rwRASTERFORMAT4444 = rw::Raster::C4444, + rwRASTERFORMATLUM8 = rw::Raster::LUM8, + rwRASTERFORMAT8888 = rw::Raster::C8888, + rwRASTERFORMAT888 = rw::Raster::C888, + rwRASTERFORMAT16 = rw::Raster::D16, + rwRASTERFORMAT24 = rw::Raster::D24, + rwRASTERFORMAT32 = rw::Raster::D32, + rwRASTERFORMAT555 = rw::Raster::C555, + + rwRASTERFORMATAUTOMIPMAP = rw::Raster::AUTOMIPMAP, + rwRASTERFORMATPAL8 = rw::Raster::PAL8, + rwRASTERFORMATPAL4 = rw::Raster::PAL4, + rwRASTERFORMATMIPMAP = rw::Raster::MIPMAP, + + rwRASTERFORMATPIXELFORMATMASK = 0x0f00, + rwRASTERFORMATMASK = 0xff00 +}; + +enum RwRasterLockMode +{ + rwRASTERLOCKWRITE = rw::Raster::LOCKWRITE, + rwRASTERLOCKREAD = rw::Raster::LOCKREAD, + rwRASTERLOCKNOFETCH = rw::Raster::LOCKNOFETCH, + rwRASTERLOCKRAW = rw::Raster::LOCKRAW, +}; + +enum RwRasterFlipMode +{ + rwRASTERFLIPDONTWAIT = 0, + rwRASTERFLIPWAITVSYNC = 1, +}; + +RwRaster *RwRasterCreate(RwInt32 width, RwInt32 height, RwInt32 depth, RwInt32 flags); +RwBool RwRasterDestroy(RwRaster * raster); +RwInt32 RwRasterGetWidth(const RwRaster *raster); +RwInt32 RwRasterGetHeight(const RwRaster *raster); +RwInt32 RwRasterGetStride(const RwRaster *raster); +RwInt32 RwRasterGetDepth(const RwRaster *raster); +RwInt32 RwRasterGetFormat(const RwRaster *raster); +RwInt32 RwRasterGetType(const RwRaster *raster); +RwRaster *RwRasterGetParent(const RwRaster *raster); +RwRaster *RwRasterGetOffset(RwRaster *raster, RwInt16 *xOffset, RwInt16 *yOffset); +RwInt32 RwRasterGetNumLevels(RwRaster * raster); +RwRaster *RwRasterSubRaster(RwRaster * subRaster, RwRaster * raster, RwRect * rect); +RwRaster *RwRasterRenderFast(RwRaster * raster, RwInt32 x, RwInt32 y); +RwRaster *RwRasterRender(RwRaster * raster, RwInt32 x, RwInt32 y); +RwRaster *RwRasterRenderScaled(RwRaster * raster, RwRect * rect); +RwRaster *RwRasterPushContext(RwRaster * raster); +RwRaster *RwRasterPopContext(void); +RwRaster *RwRasterGetCurrentContext(void); +RwBool RwRasterClear(RwInt32 pixelValue); +RwBool RwRasterClearRect(RwRect * rpRect, RwInt32 pixelValue); +RwRaster *RwRasterShowRaster(RwRaster * raster, void *dev, RwUInt32 flags); +RwUInt8 *RwRasterLock(RwRaster * raster, RwUInt8 level, RwInt32 lockMode); +RwRaster *RwRasterUnlock(RwRaster * raster); +RwUInt8 *RwRasterLockPalette(RwRaster * raster, RwInt32 lockMode); +RwRaster *RwRasterUnlockPalette(RwRaster * raster); +RwInt32 RwRasterRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwRasterGetPluginOffset(RwUInt32 pluginID); +RwBool RwRasterValidatePlugins(const RwRaster * raster); + + +/* + *********************************************** + * + * RwImage + * + *********************************************** + */ + +//struct RwImage; +typedef rw::Image RwImage; + +RwImage *RwImageCreate(RwInt32 width, RwInt32 height, RwInt32 depth); +RwBool RwImageDestroy(RwImage * image); +RwImage *RwImageAllocatePixels(RwImage * image); +RwImage *RwImageFreePixels(RwImage * image); +RwImage *RwImageCopy(RwImage * destImage, const RwImage * sourceImage); +RwImage *RwImageResize(RwImage * image, RwInt32 width, RwInt32 height); +RwImage *RwImageApplyMask(RwImage * image, const RwImage * mask); +RwImage *RwImageMakeMask(RwImage * image); +RwImage *RwImageReadMaskedImage(const RwChar * imageName, const RwChar * maskname); +RwImage *RwImageRead(const RwChar * imageName); +RwImage *RwImageWrite(RwImage * image, const RwChar * imageName); +RwChar *RwImageGetPath(void); +const RwChar *RwImageSetPath(const RwChar * path); +RwImage *RwImageSetStride(RwImage * image, RwInt32 stride); +RwImage *RwImageSetPixels(RwImage * image, RwUInt8 * pixels); +RwImage *RwImageSetPalette(RwImage * image, RwRGBA * palette); +RwInt32 RwImageGetWidth(const RwImage * image); +RwInt32 RwImageGetHeight(const RwImage * image); +RwInt32 RwImageGetDepth(const RwImage * image); +RwInt32 RwImageGetStride(const RwImage * image); +RwUInt8 *RwImageGetPixels(const RwImage * image); +RwRGBA *RwImageGetPalette(const RwImage * image); +RwUInt32 RwRGBAToPixel(RwRGBA * rgbIn, RwInt32 rasterFormat); +RwRGBA *RwRGBASetFromPixel(RwRGBA * rgbOut, RwUInt32 pixelValue, RwInt32 rasterFormat); +RwBool RwImageSetGamma(RwReal gammaValue); +RwReal RwImageGetGamma(void); +RwImage *RwImageGammaCorrect(RwImage * image); +RwRGBA *RwRGBAGammaCorrect(RwRGBA * rgb); +RwInt32 RwImageRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwImageGetPluginOffset(RwUInt32 pluginID); +RwBool RwImageValidatePlugins(const RwImage * image); +//RwBool RwImageRegisterImageFormat(const RwChar * extension, RwImageCallBackRead imageRead, RwImageCallBackWrite imageWrite); +const RwChar *RwImageFindFileType(const RwChar * imageName); +RwInt32 RwImageStreamGetSize(const RwImage * image); +RwImage *RwImageStreamRead(RwStream * stream); +const RwImage *RwImageStreamWrite(const RwImage * image, RwStream * stream); + + +/* + *********************************************** + * + * RwTexture + * + *********************************************** + */ + +//struct RwTexture; +typedef rw::Texture RwTexture; +//struct RwTexDictionary; +typedef rw::TexDictionary RwTexDictionary; + +typedef RwTexture *(*RwTextureCallBackRead)(const RwChar *name, const RwChar *maskName); +typedef RwTexture *(*RwTextureCallBack)(RwTexture *texture, void *pData); +typedef RwTexDictionary *(*RwTexDictionaryCallBack)(RwTexDictionary *dict, void *data); +typedef RwRaster *(*RwTextureCallBackMipmapGeneration)(RwRaster * raster, RwImage * image); +typedef RwBool (*RwTextureCallBackMipmapName)(RwChar *name, RwChar *maskName, RwUInt8 mipLevel, RwInt32 format); + +RwTexture *RwTextureCreate(RwRaster * raster); +RwBool RwTextureDestroy(RwTexture * texture); +RwTexture *RwTextureAddRef(RwTexture *texture); +RwBool RwTextureSetMipmapping(RwBool enable); +RwBool RwTextureGetMipmapping(void); +RwBool RwTextureSetAutoMipmapping(RwBool enable); +RwBool RwTextureGetAutoMipmapping(void); +RwBool RwTextureSetMipmapGenerationCallBack(RwTextureCallBackMipmapGeneration callback); +RwTextureCallBackMipmapGeneration RwTextureGetMipmapGenerationCallBack(void); +RwBool RwTextureSetMipmapNameCallBack(RwTextureCallBackMipmapName callback); +RwTextureCallBackMipmapName RwTextureGetMipmapNameCallBack(void); +RwBool RwTextureGenerateMipmapName(RwChar * name, RwChar * maskName, RwUInt8 mipLevel, RwInt32 format); +RwBool RwTextureRasterGenerateMipmaps(RwRaster * raster, RwImage * image); +RwTextureCallBackRead RwTextureGetReadCallBack(void); +RwBool RwTextureSetReadCallBack(RwTextureCallBackRead fpCallBack); +RwTexture *RwTextureSetName(RwTexture * texture, const RwChar * name); +RwTexture *RwTextureSetMaskName(RwTexture * texture, const RwChar * maskName); +RwChar *RwTextureGetName(RwTexture *texture); +RwChar *RwTextureGetMaskName(RwTexture *texture); +RwTexture *RwTextureSetRaster(RwTexture * texture, RwRaster * raster); +RwTexture *RwTextureRead(const RwChar * name, const RwChar * maskName); +RwRaster *RwTextureGetRaster(const RwTexture *texture); +RwInt32 RwTextureRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwTextureGetPluginOffset(RwUInt32 pluginID); +RwBool RwTextureValidatePlugins(const RwTexture * texture); + +RwTexDictionary *RwTextureGetDictionary(RwTexture *texture); +RwTexture *RwTextureSetFilterMode(RwTexture *texture, RwTextureFilterMode filtering); +RwTextureFilterMode RwTextureGetFilterMode(const RwTexture *texture); +RwTexture *RwTextureSetAddressing(RwTexture *texture, RwTextureAddressMode addressing); +RwTexture *RwTextureSetAddressingU(RwTexture *texture, RwTextureAddressMode addressing); +RwTexture *RwTextureSetAddressingV(RwTexture *texture, RwTextureAddressMode addressing); +RwTextureAddressMode RwTextureGetAddressing(const RwTexture *texture); +RwTextureAddressMode RwTextureGetAddressingU(const RwTexture *texture); +RwTextureAddressMode RwTextureGetAddressingV(const RwTexture *texture); + +void _rwD3D8TexDictionaryEnableRasterFormatConversion(bool enable); + +// hack for reading native textures +RwBool rwNativeTextureHackRead(RwStream *stream, RwTexture **tex, RwInt32 size); + + +RwTexDictionary *RwTexDictionaryCreate(void); +RwBool RwTexDictionaryDestroy(RwTexDictionary * dict); +RwTexture *RwTexDictionaryAddTexture(RwTexDictionary * dict, RwTexture * texture); +RwTexture *RwTexDictionaryRemoveTexture(RwTexture * texture); +RwTexture *RwTexDictionaryFindNamedTexture(RwTexDictionary * dict, const RwChar * name); +RwTexDictionary *RwTexDictionaryGetCurrent(void); +RwTexDictionary *RwTexDictionarySetCurrent(RwTexDictionary * dict); +const RwTexDictionary *RwTexDictionaryForAllTextures(const RwTexDictionary * dict, RwTextureCallBack fpCallBack, void *pData); +RwBool RwTexDictionaryForAllTexDictionaries(RwTexDictionaryCallBack fpCallBack, void *pData); +RwInt32 RwTexDictionaryRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwTexDictionaryGetPluginOffset(RwUInt32 pluginID); +RwBool RwTexDictionaryValidatePlugins(const RwTexDictionary * dict); +RwUInt32 RwTexDictionaryStreamGetSize(const RwTexDictionary *texDict); +RwTexDictionary *RwTexDictionaryStreamRead(RwStream *stream); +const RwTexDictionary *RwTexDictionaryStreamWrite(const RwTexDictionary *texDict, RwStream *stream); + +/* RwImage/RwRaster */ + +RwImage *RwImageSetFromRaster(RwImage *image, RwRaster *raster); +RwRaster *RwRasterSetFromImage(RwRaster *raster, RwImage *image); +RwRGBA *RwRGBAGetRasterPixel(RwRGBA *rgbOut, RwRaster *raster, RwInt32 x, RwInt32 y); +RwRaster *RwRasterRead(const RwChar *filename); +RwRaster *RwRasterReadMaskedRaster(const RwChar *filename, const RwChar *maskname); +RwImage *RwImageFindRasterFormat(RwImage *ipImage,RwInt32 nRasterType, RwInt32 *npWidth,RwInt32 *npHeight, RwInt32 *npDepth,RwInt32 *npFormat); + + +/* + *********************************************** + * + * RwFrame + * + *********************************************** + */ + +//struct RwFrame; +typedef rw::Frame RwFrame; + +typedef RwFrame *(*RwFrameCallBack)(RwFrame *frame, void *data); + + +RwFrame *RwFrameForAllObjects(RwFrame * frame, RwObjectCallBack callBack, void *data); +RwFrame *RwFrameTranslate(RwFrame * frame, const RwV3d * v, RwOpCombineType combine); +RwFrame *RwFrameRotate(RwFrame * frame, const RwV3d * axis, RwReal angle, RwOpCombineType combine); +RwFrame *RwFrameScale(RwFrame * frame, const RwV3d * v, RwOpCombineType combine); +RwFrame *RwFrameTransform(RwFrame * frame, const RwMatrix * m, RwOpCombineType combine); +RwFrame *RwFrameOrthoNormalize(RwFrame * frame); +RwFrame *RwFrameSetIdentity(RwFrame * frame); +RwFrame *RwFrameCloneHierarchy(RwFrame * root); +RwBool RwFrameDestroyHierarchy(RwFrame * frame); +RwFrame *RwFrameForAllChildren(RwFrame * frame, RwFrameCallBack callBack, void *data); +RwFrame *RwFrameRemoveChild(RwFrame * child); +RwFrame *RwFrameAddChild(RwFrame * parent, RwFrame * child); +RwFrame *RwFrameGetParent(const RwFrame * frame); +RwFrame *RwFrameGetRoot(const RwFrame * frame); +RwMatrix *RwFrameGetLTM(RwFrame * frame); +RwMatrix *RwFrameGetMatrix(RwFrame * frame); +RwFrame *RwFrameUpdateObjects(RwFrame * frame); +RwFrame *RwFrameCreate(void); +RwBool RwFrameInit(RwFrame *frame); +RwBool RwFrameDeInit(RwFrame *frame); +RwBool RwFrameDestroy(RwFrame * frame); +void _rwFrameInit(RwFrame *frame); +void _rwFrameDeInit(RwFrame *frame); +RwBool RwFrameDirty(const RwFrame * frame); +RwInt32 RwFrameCount(RwFrame * frame); +RwBool RwFrameSetStaticPluginsSize(RwInt32 size); +RwInt32 RwFrameRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwFrameGetPluginOffset(RwUInt32 pluginID); +RwBool RwFrameValidatePlugins(const RwFrame * frame); +RwFrame *_rwFrameCloneAndLinkClones(RwFrame * root); +RwFrame *_rwFramePurgeClone(RwFrame *root); + +RwInt32 RwFrameRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RwFrameSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); + +typedef rw::FrameList_ rwFrameList; +rwFrameList *rwFrameListInitialize(rwFrameList *frameList, RwFrame *frame); +RwBool rwFrameListFindFrame(const rwFrameList *frameList, const RwFrame *frame, RwInt32 *npIndex); +rwFrameList *rwFrameListDeinitialize(rwFrameList *frameList); +RwUInt32 rwFrameListStreamGetSize(const rwFrameList *frameList); +rwFrameList *rwFrameListStreamRead(RwStream *stream, rwFrameList *fl); +const rwFrameList *rwFrameListStreamWrite(const rwFrameList *frameList, RwStream *stream); + + +typedef rw::BBox RwBBox; + +/* + *********************************************** + * + * RwCamera + * + *********************************************** + */ + +//struct RwCamera; +typedef rw::Camera RwCamera; + +typedef RwCamera *(*RwCameraCallBack)(RwCamera *camera, void *data); + +enum RwCameraClearMode +{ + rwCAMERACLEARIMAGE = 0x1, + rwCAMERACLEARZ = 0x2, + rwCAMERACLEARSTENCIL = 0x4 +}; + +enum RwCameraProjection +{ + rwNACAMERAPROJECTION = 0, + rwPERSPECTIVE = 1, + rwPARALLEL = 2 +}; + +enum RwFrustumTestResult +{ + rwSPHEREOUTSIDE = 0, + rwSPHEREBOUNDARY = 1, + rwSPHEREINSIDE = 2 +}; + +RwCamera *RwCameraBeginUpdate(RwCamera * camera); +RwCamera *RwCameraEndUpdate(RwCamera * camera); +RwCamera *RwCameraClear(RwCamera * camera, RwRGBA * colour, RwInt32 clearMode); +RwCamera *RwCameraShowRaster(RwCamera * camera, void *pDev, RwUInt32 flags); +RwBool RwCameraDestroy(RwCamera * camera); +RwCamera *RwCameraCreate(void); +RwCamera *RwCameraClone(RwCamera * camera); +RwCamera *RwCameraSetViewOffset(RwCamera *camera, const RwV2d *offset); +RwCamera *RwCameraSetViewWindow(RwCamera *camera, const RwV2d *viewWindow); +RwCamera *RwCameraSetProjection(RwCamera *camera, RwCameraProjection projection); +RwCamera *RwCameraSetNearClipPlane(RwCamera *camera, RwReal nearClip); +RwCamera *RwCameraSetFarClipPlane(RwCamera *camera, RwReal farClip); +RwInt32 RwCameraRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwCameraGetPluginOffset(RwUInt32 pluginID); +RwBool RwCameraValidatePlugins(const RwCamera * camera); +RwFrustumTestResult RwCameraFrustumTestSphere(const RwCamera * camera, const RwSphere * sphere); +const RwV2d *RwCameraGetViewOffset(const RwCamera *camera); +RwCamera *RwCameraSetRaster(RwCamera *camera, RwRaster *raster); +RwRaster *RwCameraGetRaster(const RwCamera *camera); +RwCamera *RwCameraSetZRaster(RwCamera *camera, RwRaster *zRaster); +RwRaster *RwCameraGetZRaster(const RwCamera *camera); +RwReal RwCameraGetNearClipPlane(const RwCamera *camera); +RwReal RwCameraGetFarClipPlane(const RwCamera *camera); +RwCamera *RwCameraSetFogDistance(RwCamera *camera, RwReal fogDistance); +RwReal RwCameraGetFogDistance(const RwCamera *camera); +RwCamera *RwCameraGetCurrentCamera(void); +RwCameraProjection RwCameraGetProjection(const RwCamera *camera); +const RwV2d *RwCameraGetViewWindow(const RwCamera *camera); +RwMatrix *RwCameraGetViewMatrix(RwCamera *camera); +RwCamera *RwCameraSetFrame(RwCamera *camera, RwFrame *frame); +RwFrame *RwCameraGetFrame(const RwCamera *camera); + + +/* + * + * D3D-engine specific stuff + * + */ + +void RwD3D8EngineSetRefreshRate(RwUInt32 refreshRate); +RwBool RwD3D8DeviceSupportsDXTTexture(void); +void RwD3D8EngineSetMultiSamplingLevels(RwUInt32 level); +RwUInt32 RwD3D8EngineGetMaxMultiSamplingLevels(void); diff --git a/src/fakerw/rwplcore.h b/src/fakerw/rwplcore.h new file mode 100644 index 0000000..69c921c --- /dev/null +++ b/src/fakerw/rwplcore.h @@ -0,0 +1,498 @@ +#pragma once + +typedef rw::int8 RwInt8; +typedef rw::int16 RwInt16; +typedef rw::int32 RwInt32; +typedef rw::uint8 RwUInt8; +typedef rw::uint16 RwUInt16; +typedef rw::uint32 RwUInt32; +typedef rw::float32 RwReal; + +typedef char RwChar; +typedef RwInt32 RwBool; + +#define __RWUNUSED__ + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE !FALSE +#endif + +// used for unicode +#define RWSTRING(x) x + +typedef rw::V2d RwV2d; + +typedef rw::V3d RwV3d; + +typedef rw::Rect RwRect; + +typedef rw::Sphere RwSphere; + +enum RwTextureCoordinateIndex +{ + rwNARWTEXTURECOORDINATEINDEX = 0, + rwTEXTURECOORDINATEINDEX0, + rwTEXTURECOORDINATEINDEX1, + rwTEXTURECOORDINATEINDEX2, + rwTEXTURECOORDINATEINDEX3, + rwTEXTURECOORDINATEINDEX4, + rwTEXTURECOORDINATEINDEX5, + rwTEXTURECOORDINATEINDEX6, + rwTEXTURECOORDINATEINDEX7, +}; + +typedef rw::TexCoords RwTexCoords; + +typedef rw::SurfaceProperties RwSurfaceProperties; + +#define RWRGBALONG(r,g,b,a) \ + ((RwUInt32) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))) + + +#define MAKECHUNKID(vendorID, chunkID) (((vendorID & 0xFFFFFF) << 8) | (chunkID & 0xFF)) + +enum RwCorePluginID +{ + rwID_NAOBJECT = 0x00, + rwID_STRUCT = 0x01, + rwID_STRING = 0x02, + rwID_EXTENSION = 0x03, + rwID_CAMERA = 0x05, + rwID_TEXTURE = 0x06, + rwID_MATERIAL = 0x07, + rwID_MATLIST = 0x08, + rwID_ATOMICSECT = 0x09, + rwID_PLANESECT = 0x0A, + rwID_WORLD = 0x0B, + rwID_SPLINE = 0x0C, + rwID_MATRIX = 0x0D, + rwID_FRAMELIST = 0x0E, + rwID_GEOMETRY = 0x0F, + rwID_CLUMP = 0x10, + rwID_LIGHT = 0x12, + rwID_UNICODESTRING = 0x13, + rwID_ATOMIC = 0x14, + rwID_TEXTURENATIVE = 0x15, + rwID_TEXDICTIONARY = 0x16, + rwID_ANIMDATABASE = 0x17, + rwID_IMAGE = 0x18, + rwID_SKINANIMATION = 0x19, + rwID_GEOMETRYLIST = 0x1A, + rwID_HANIMANIMATION = 0x1B, + rwID_TEAM = 0x1C, + rwID_CROWD = 0x1D, + rwID_DMORPHANIMATION = 0x1E, + rwID_RIGHTTORENDER = 0x1f, + rwID_MTEFFECTNATIVE = 0x20, + rwID_MTEFFECTDICT = 0x21, + rwID_TEAMDICTIONARY = 0x22, + rwID_PITEXDICTIONARY = 0x23, + rwID_TOC = 0x24, + rwID_PRTSTDGLOBALDATA = 0x25, + /* Insert before MAX and increment MAX */ + rwID_COREPLUGINIDMAX = 0x26, +}; + + +/* + *********************************************** + * + * RwObject + * + *********************************************** + */ + +//struct RwObject; +typedef rw::Object RwObject; +typedef rw::Frame RwFrame; + +typedef RwObject *(*RwObjectCallBack)(RwObject *object, void *data); + +RwUInt8 RwObjectGetType(const RwObject *obj); +RwFrame* rwObjectGetParent(const RwObject *obj); + +#define rwsprintf sprintf +#define rwvsprintf vsprintf +#define rwstrcpy strcpy +#define rwstrncpy strncpy +#define rwstrcat strcat +#define rwstrncat strncat +#define rwstrrchr strrchr +#define rwstrchr strchr +#define rwstrstr strstr +#define rwstrcmp strcmp +#define rwstricmp stricmp +#define rwstrlen strlen +#define rwstrupr strupr +#define rwstrlwr strlwr +#define rwstrtok strtok +#define rwsscanf sscanf + + +/* + *********************************************** + * + * Memory + * + *********************************************** + */ + +struct RwMemoryFunctions +{ + // NB: from RW 3.6 on the allocating functions take + // a hint parameter! + void *(*rwmalloc)(size_t size); + void (*rwfree)(void *mem); + void *(*rwrealloc)(void *mem, size_t newSize); + void *(*rwcalloc)(size_t numObj, size_t sizeObj); +}; + +void *RwMalloc(size_t size); +void RwFree(void *mem); +void *RwRealloc(void *mem, size_t newSize); +void *RwCalloc(size_t numObj, size_t sizeObj); + +/* + *********************************************** + * + * RwStream + * + *********************************************** + */ + +//struct RwStream; +typedef rw::Stream RwStream; + +struct RwMemory +{ + RwUInt8 *start; + RwUInt32 length; +}; + +enum RwStreamType +{ + rwNASTREAM = 0, + rwSTREAMFILE, + rwSTREAMFILENAME, + rwSTREAMMEMORY, + rwSTREAMCUSTOM +}; + +enum RwStreamAccessType +{ + rwNASTREAMACCESS = 0, + rwSTREAMREAD, + rwSTREAMWRITE, + rwSTREAMAPPEND +}; + +RwStream *RwStreamOpen(RwStreamType type, RwStreamAccessType accessType, const void *pData); +RwBool RwStreamClose(RwStream * stream, void *pData); +RwUInt32 RwStreamRead(RwStream * stream, void *buffer, RwUInt32 length); +RwStream *RwStreamWrite(RwStream * stream, const void *buffer, RwUInt32 length); +RwStream *RwStreamSkip(RwStream * stream, RwUInt32 offset); + + +/* + *********************************************** + * + * Plugin Registry + * + *********************************************** + */ + +#define RWPLUGINOFFSET(_type, _base, _offset) \ + ((_type *)((RwUInt8 *)(_base) + (_offset))) + +typedef RwStream *(*RwPluginDataChunkWriteCallBack)(RwStream *stream, RwInt32 binaryLength, const void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef RwStream *(*RwPluginDataChunkReadCallBack)(RwStream *stream, RwInt32 binaryLength, void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef RwInt32(*RwPluginDataChunkGetSizeCallBack)(const void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef RwBool(*RwPluginDataChunkAlwaysCallBack)(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef RwBool(*RwPluginDataChunkRightsCallBack)(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject, RwUInt32 extraData); +typedef void *(*RwPluginObjectConstructor)(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef void *(*RwPluginObjectCopy)(void *dstObject, const void *srcObject, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef void *(*RwPluginObjectDestructor)(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); + +/* + *********************************************** + * + * RwMatrix + * + *********************************************** + */ + +typedef rw::Matrix RwMatrix; + +enum RwOpCombineType +{ + rwCOMBINEREPLACE = rw::COMBINEREPLACE, + rwCOMBINEPRECONCAT = rw::COMBINEPRECONCAT, + rwCOMBINEPOSTCONCAT = rw::COMBINEPOSTCONCAT +}; + +enum RwMatrixType +{ + rwMATRIXTYPENORMAL = rw::Matrix::TYPENORMAL, + rwMATRIXTYPEORTHOGANAL = rw::Matrix::TYPEORTHOGONAL, + rwMATRIXTYPEORTHONORMAL = rw::Matrix::TYPEORTHONORMAL, + rwMATRIXTYPEMASK = 0x00000003, +}; + +typedef rw::Matrix::Tolerance RwMatrixTolerance; + +RwBool RwMatrixDestroy(RwMatrix *mpMat); +RwMatrix *RwMatrixCreate(void); +void RwMatrixCopy(RwMatrix * dstMatrix, const RwMatrix * srcMatrix); +void RwMatrixSetIdentity(RwMatrix * matrix); +RwMatrix *RwMatrixMultiply(RwMatrix * matrixOut, const RwMatrix * MatrixIn1, const RwMatrix * matrixIn2); +RwMatrix *RwMatrixTransform(RwMatrix * matrix, const RwMatrix * transform, RwOpCombineType combineOp); +RwMatrix *RwMatrixOrthoNormalize(RwMatrix * matrixOut, const RwMatrix * matrixIn); +RwMatrix *RwMatrixInvert(RwMatrix * matrixOut, const RwMatrix * matrixIn); +RwMatrix *RwMatrixScale(RwMatrix * matrix, const RwV3d * scale, RwOpCombineType combineOp); +RwMatrix *RwMatrixTranslate(RwMatrix * matrix, const RwV3d * translation, RwOpCombineType combineOp); +RwMatrix *RwMatrixRotate(RwMatrix * matrix, const RwV3d * axis, RwReal angle, RwOpCombineType combineOp); +RwMatrix *RwMatrixRotateOneMinusCosineSine(RwMatrix * matrix, const RwV3d * unitAxis, RwReal oneMinusCosine, RwReal sine, RwOpCombineType combineOp); +const RwMatrix *RwMatrixQueryRotate(const RwMatrix * matrix, RwV3d * unitAxis, RwReal * angle, RwV3d * center); +RwV3d *RwMatrixGetRight(RwMatrix * matrix); +RwV3d *RwMatrixGetUp(RwMatrix * matrix); +RwV3d *RwMatrixGetAt(RwMatrix * matrix); +RwV3d *RwMatrixGetPos(RwMatrix * matrix); +RwMatrix *RwMatrixUpdate(RwMatrix * matrix); +RwMatrix *RwMatrixOptimize(RwMatrix * matrix, const RwMatrixTolerance *tolerance); + +/* + *********************************************** + * + * RwRGBA + * + *********************************************** + */ + +typedef rw::RGBA RwRGBA; +typedef rw::RGBAf RwRGBAReal; + + +inline void RwRGBAAssign(RwRGBA *target, const RwRGBA *source) { *target = *source; } + + +RwReal RwV3dNormalize(RwV3d * out, const RwV3d * in); +RwReal RwV3dLength(const RwV3d * in); +RwReal RwV2dLength(const RwV2d * in); +RwReal RwV2dNormalize(RwV2d * out, const RwV2d * in); +void RwV2dAssign(RwV2d * out, const RwV2d * ina); +void RwV2dAdd(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +void RwV2dLineNormal(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +void RwV2dSub(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +void RwV2dPerp(RwV2d * out, const RwV2d * in); +void RwV2dScale(RwV2d * out, const RwV2d * in, RwReal scalar); +RwReal RwV2dDotProduct(const RwV2d * ina, const RwV2d * inb); +void RwV3dAssign(RwV3d * out, const RwV3d * ina); +void RwV3dAdd(RwV3d * out, const RwV3d * ina, const RwV3d * inb); +void RwV3dSub(RwV3d * out, const RwV3d * ina, const RwV3d * inb); +void RwV3dScale(RwV3d * out, const RwV3d * in, RwReal scalar); +void RwV3dIncrementScaled(RwV3d * out, const RwV3d * in, RwReal scalar); +void RwV3dNegate(RwV3d * out, const RwV3d * in); +RwReal RwV3dDotProduct(const RwV3d * ina, const RwV3d * inb); +void RwV3dCrossProduct(RwV3d * out, const RwV3d * ina, const RwV3d * inb); +RwV3d *RwV3dTransformPoints(RwV3d * pointsOut, const RwV3d * pointsIn, RwInt32 numPoints, const RwMatrix * matrix); +RwV3d *RwV3dTransformVectors(RwV3d * vectorsOut, const RwV3d * vectorsIn, RwInt32 numPoints, const RwMatrix * matrix); + + +/* + *********************************************** + * + * Render States + * + *********************************************** + */ + +// not librw because we don't support all of them (yet?) - mapping in wrapper functions +enum RwRenderState +{ + rwRENDERSTATENARENDERSTATE = 0, + rwRENDERSTATETEXTURERASTER, + rwRENDERSTATETEXTUREADDRESS, + rwRENDERSTATETEXTUREADDRESSU, + rwRENDERSTATETEXTUREADDRESSV, + rwRENDERSTATETEXTUREPERSPECTIVE, + rwRENDERSTATEZTESTENABLE, + rwRENDERSTATESHADEMODE, + rwRENDERSTATEZWRITEENABLE, + rwRENDERSTATETEXTUREFILTER, + rwRENDERSTATESRCBLEND, + rwRENDERSTATEDESTBLEND, + rwRENDERSTATEVERTEXALPHAENABLE, + rwRENDERSTATEBORDERCOLOR, + rwRENDERSTATEFOGENABLE, + rwRENDERSTATEFOGCOLOR, + rwRENDERSTATEFOGTYPE, + rwRENDERSTATEFOGDENSITY, + rwRENDERSTATEFOGTABLE, + rwRENDERSTATEALPHAPRIMITIVEBUFFER, + rwRENDERSTATECULLMODE, + rwRENDERSTATESTENCILENABLE, + rwRENDERSTATESTENCILFAIL, + rwRENDERSTATESTENCILZFAIL, + rwRENDERSTATESTENCILPASS, + rwRENDERSTATESTENCILFUNCTION, + rwRENDERSTATESTENCILFUNCTIONREF, + rwRENDERSTATESTENCILFUNCTIONMASK, + rwRENDERSTATESTENCILFUNCTIONWRITEMASK +}; + +// not supported - we only do gouraud +enum RwShadeMode +{ + rwSHADEMODENASHADEMODE = 0, + rwSHADEMODEFLAT, + rwSHADEMODEGOURAUD +}; + +enum RwBlendFunction +{ + rwBLENDNABLEND = 0, + rwBLENDZERO = rw::BLENDZERO, + rwBLENDONE = rw::BLENDONE, + rwBLENDSRCCOLOR = rw::BLENDSRCCOLOR, + rwBLENDINVSRCCOLOR = rw::BLENDINVSRCCOLOR, + rwBLENDSRCALPHA = rw::BLENDSRCALPHA, + rwBLENDINVSRCALPHA = rw::BLENDINVSRCALPHA, + rwBLENDDESTALPHA = rw::BLENDDESTALPHA, + rwBLENDINVDESTALPHA = rw::BLENDINVDESTALPHA, + rwBLENDDESTCOLOR = rw::BLENDDESTCOLOR, + rwBLENDINVDESTCOLOR = rw::BLENDINVDESTCOLOR, + rwBLENDSRCALPHASAT = rw::BLENDSRCALPHASAT +}; + +// unsupported - we only need linear +enum RwFogType +{ + rwFOGTYPENAFOGTYPE = 0, + rwFOGTYPELINEAR, + rwFOGTYPEEXPONENTIAL, + rwFOGTYPEEXPONENTIAL2 +}; + +enum RwTextureFilterMode +{ + rwFILTERNAFILTERMODE = 0, + rwFILTERNEAREST = rw::Texture::NEAREST, + rwFILTERLINEAR = rw::Texture::LINEAR, + rwFILTERMIPNEAREST = rw::Texture::MIPNEAREST, + rwFILTERMIPLINEAR = rw::Texture::MIPLINEAR, + rwFILTERLINEARMIPNEAREST = rw::Texture::LINEARMIPNEAREST, + rwFILTERLINEARMIPLINEAR = rw::Texture::LINEARMIPLINEAR +}; + +enum RwTextureAddressMode +{ + rwTEXTUREADDRESSNATEXTUREADDRESS = 0, + rwTEXTUREADDRESSWRAP = rw::Texture::WRAP, + rwTEXTUREADDRESSMIRROR = rw::Texture::MIRROR, + rwTEXTUREADDRESSCLAMP = rw::Texture::CLAMP, + rwTEXTUREADDRESSBORDER = rw::Texture::BORDER +}; + +enum RwCullMode +{ + rwCULLMODENACULLMODE = 0, + rwCULLMODECULLNONE = rw::CULLNONE, + rwCULLMODECULLBACK = rw::CULLBACK, + rwCULLMODECULLFRONT = rw::CULLFRONT +}; + +enum RwPrimitiveType +{ + rwPRIMTYPENAPRIMTYPE = rw::PRIMTYPENONE, + rwPRIMTYPELINELIST = rw::PRIMTYPELINELIST, + rwPRIMTYPEPOLYLINE = rw::PRIMTYPEPOLYLINE, + rwPRIMTYPETRILIST = rw::PRIMTYPETRILIST, + rwPRIMTYPETRISTRIP = rw::PRIMTYPETRISTRIP, + rwPRIMTYPETRIFAN = rw::PRIMTYPETRIFAN, + rwPRIMTYPEPOINTLIST = rw::PRIMTYPEPOINTLIST +}; + + +RwBool RwRenderStateGet(RwRenderState state, void *value); +RwBool RwRenderStateSet(RwRenderState state, void *value); + + +/* + *********************************************** + * + * Engine + * + *********************************************** + */ + +struct RwEngineOpenParams +{ + void *displayID; +}; + +typedef rw::SubSystemInfo RwSubSystemInfo; + +enum RwVideoModeFlag +{ + rwVIDEOMODEEXCLUSIVE = rw::VIDEOMODEEXCLUSIVE, +/* + rwVIDEOMODEINTERLACE = 0x2, + rwVIDEOMODEFFINTERLACE = 0x4, + rwVIDEOMODEFSAA0 = 0x8, + rwVIDEOMODEFSAA1 = 0x10 +*/ +}; + +typedef rw::VideoMode RwVideoMode; + +#if 0 +struct RwFileFunctions +{ + rwFnFexist rwfexist; /**< Pointer to fexist function */ + rwFnFopen rwfopen; /**< Pointer to fopen function */ + rwFnFclose rwfclose; /**< Pointer to fclose function */ + rwFnFread rwfread; /**< Pointer to fread function */ + rwFnFwrite rwfwrite; /**< Pointer to fwrite function */ + rwFnFgets rwfgets; /**< Pointer to fgets function */ + rwFnFputs rwfputs; /**< Pointer to puts function */ + rwFnFeof rwfeof; /**< Pointer to feof function */ + rwFnFseek rwfseek; /**< Pointer to fseek function */ + rwFnFflush rwfflush; /**< Pointer to fflush function */ + rwFnFtell rwftell; /**< Pointer to ftell function */ +}; +RwFileFunctions *RwOsGetFileInterface(void); +#endif + +RwBool RwEngineInit(RwMemoryFunctions *memFuncs, RwUInt32 initFlags, RwUInt32 resArenaSize); +RwInt32 RwEngineRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor initCB, RwPluginObjectDestructor termCB); +RwInt32 RwEngineGetPluginOffset(RwUInt32 pluginID); +RwBool RwEngineOpen(RwEngineOpenParams *initParams); +RwBool RwEngineStart(void); +RwBool RwEngineStop(void); +RwBool RwEngineClose(void); +RwBool RwEngineTerm(void); +RwInt32 RwEngineGetNumSubSystems(void); +RwSubSystemInfo *RwEngineGetSubSystemInfo(RwSubSystemInfo *subSystemInfo, RwInt32 subSystemIndex); +RwInt32 RwEngineGetCurrentSubSystem(void); +RwBool RwEngineSetSubSystem(RwInt32 subSystemIndex); +RwInt32 RwEngineGetNumVideoModes(void); +RwVideoMode *RwEngineGetVideoModeInfo(RwVideoMode *modeinfo, RwInt32 modeIndex); +RwInt32 RwEngineGetCurrentVideoMode(void); +RwBool RwEngineSetVideoMode(RwInt32 modeIndex); +RwInt32 RwEngineGetTextureMemorySize(void); +RwInt32 RwEngineGetMaxTextureSize(void); + + +/* + *********************************************** + * + * Binary stream + * + *********************************************** + */ + +RwBool RwStreamFindChunk(RwStream *stream, RwUInt32 type, RwUInt32 *lengthOut, RwUInt32 *versionOut); diff --git a/src/math/Matrix.cpp b/src/math/Matrix.cpp new file mode 100644 index 0000000..b11e8a1 --- /dev/null +++ b/src/math/Matrix.cpp @@ -0,0 +1,544 @@ +#include "common.h" + +CMatrix::CMatrix(void) +{ + m_attachment = nil; + m_hasRwMatrix = false; +} + +CMatrix::CMatrix(CMatrix const &m) +{ + m_attachment = nil; + m_hasRwMatrix = false; + *this = m; +} + +CMatrix::CMatrix(RwMatrix *matrix, bool owner) +{ + m_attachment = nil; + Attach(matrix, owner); +} + +CMatrix::~CMatrix(void) +{ + if (m_hasRwMatrix && m_attachment) + RwMatrixDestroy(m_attachment); +} + +void +CMatrix::Attach(RwMatrix *matrix, bool owner) +{ +#ifdef FIX_BUGS + if (m_attachment && m_hasRwMatrix) +#else + if (m_hasRwMatrix && m_attachment) +#endif + RwMatrixDestroy(m_attachment); + m_attachment = matrix; + m_hasRwMatrix = owner; + Update(); +} + +void +CMatrix::AttachRW(RwMatrix *matrix, bool owner) +{ + if (m_hasRwMatrix && m_attachment) + RwMatrixDestroy(m_attachment); + m_attachment = matrix; + m_hasRwMatrix = owner; + UpdateRW(); +} + +void +CMatrix::Detach(void) +{ + if (m_hasRwMatrix && m_attachment) + RwMatrixDestroy(m_attachment); + m_attachment = nil; +} + +void +CMatrix::Update(void) +{ + GetRight() = m_attachment->right; + GetForward() = m_attachment->up; + GetUp() = m_attachment->at; + GetPosition() = m_attachment->pos; +} + +void +CMatrix::UpdateRW(void) +{ + if (m_attachment) { + m_attachment->right = GetRight(); + m_attachment->up = GetForward(); + m_attachment->at = GetUp(); + m_attachment->pos = GetPosition(); + RwMatrixUpdate(m_attachment); + } +} + +void +CMatrix::operator=(CMatrix const &rhs) +{ + memcpy(this, &rhs, sizeof(f)); + if (m_attachment) + UpdateRW(); +} + +void +CMatrix::CopyOnlyMatrix(const CMatrix &other) +{ + memcpy(this, &other, sizeof(f)); +} + +CMatrix & +CMatrix::operator+=(CMatrix const &rhs) +{ + GetRight() += rhs.GetRight(); + GetForward() += rhs.GetForward(); + GetUp() += rhs.GetUp(); + GetPosition() += rhs.GetPosition(); + return *this; +} + +void +CMatrix::SetUnity(void) +{ + rx = 1.0f; + ry = 0.0f; + rz = 0.0f; + fx = 0.0f; + fy = 1.0f; + fz = 0.0f; + ux = 0.0f; + uy = 0.0f; + uz = 1.0f; + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + +void +CMatrix::ResetOrientation(void) +{ + rx = 1.0f; + ry = 0.0f; + rz = 0.0f; + fx = 0.0f; + fy = 1.0f; + fz = 0.0f; + ux = 0.0f; + uy = 0.0f; + uz = 1.0f; +} + +void +CMatrix::SetScale(float s) +{ + rx = s; + ry = 0.0f; + rz = 0.0f; + + fx = 0.0f; + fy = s; + fz = 0.0f; + + ux = 0.0f; + uy = 0.0f; + uz = s; + + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + +void +CMatrix::SetTranslate(float x, float y, float z) +{ + rx = 1.0f; + ry = 0.0f; + rz = 0.0f; + + fx = 0.0f; + fy = 1.0f; + fz = 0.0f; + + ux = 0.0f; + uy = 0.0f; + uz = 1.0f; + + px = x; + py = y; + pz = z; +} + +void +CMatrix::SetRotateXOnly(float angle) +{ + float c = Cos(angle); + float s = Sin(angle); + + rx = 1.0f; + ry = 0.0f; + rz = 0.0f; + + fx = 0.0f; + fy = c; + fz = s; + + ux = 0.0f; + uy = -s; + uz = c; +} + +void +CMatrix::SetRotateYOnly(float angle) +{ + float c = Cos(angle); + float s = Sin(angle); + + rx = c; + ry = 0.0f; + rz = -s; + + fx = 0.0f; + fy = 1.0f; + fz = 0.0f; + + ux = s; + uy = 0.0f; + uz = c; +} + +void +CMatrix::SetRotateZOnly(float angle) +{ + float c = Cos(angle); + float s = Sin(angle); + + rx = c; + ry = s; + rz = 0.0f; + + fx = -s; + fy = c; + fz = 0.0f; + + ux = 0.0f; + uy = 0.0f; + uz = 1.0f; +} + +void +CMatrix::SetRotateX(float angle) +{ + SetRotateXOnly(angle); + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + + +void +CMatrix::SetRotateY(float angle) +{ + SetRotateYOnly(angle); + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + +void +CMatrix::SetRotateZ(float angle) +{ + SetRotateZOnly(angle); + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + +void +CMatrix::SetRotate(float xAngle, float yAngle, float zAngle) +{ + float cX = Cos(xAngle); + float sX = Sin(xAngle); + float cY = Cos(yAngle); + float sY = Sin(yAngle); + float cZ = Cos(zAngle); + float sZ = Sin(zAngle); + + rx = cZ * cY - (sZ * sX) * sY; + ry = (cZ * sX) * sY + sZ * cY; + rz = -cX * sY; + + fx = -sZ * cX; + fy = cZ * cX; + fz = sX; + + ux = (sZ * sX) * cY + cZ * sY; + uy = sZ * sY - (cZ * sX) * cY; + uz = cX * cY; + + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + +void +CMatrix::RotateX(float x) +{ + float c = Cos(x); + float s = Sin(x); + + float ry = this->ry; + float rz = this->rz; + float uy = this->fy; + float uz = this->fz; + float ay = this->uy; + float az = this->uz; + float py = this->py; + float pz = this->pz; + + this->ry = c * ry - s * rz; + this->rz = c * rz + s * ry; + this->fy = c * uy - s * uz; + this->fz = c * uz + s * uy; + this->uy = c * ay - s * az; + this->uz = c * az + s * ay; + this->py = c * py - s * pz; + this->pz = c * pz + s * py; +} + +void +CMatrix::RotateY(float y) +{ + float c = Cos(y); + float s = Sin(y); + + float rx = this->rx; + float rz = this->rz; + float ux = this->fx; + float uz = this->fz; + float ax = this->ux; + float az = this->uz; + float px = this->px; + float pz = this->pz; + + this->rx = c * rx + s * rz; + this->rz = c * rz - s * rx; + this->fx = c * ux + s * uz; + this->fz = c * uz - s * ux; + this->ux = c * ax + s * az; + this->uz = c * az - s * ax; + this->px = c * px + s * pz; + this->pz = c * pz - s * px; +} + +void +CMatrix::RotateZ(float z) +{ + float c = Cos(z); + float s = Sin(z); + + float ry = this->ry; + float rx = this->rx; + float uy = this->fy; + float ux = this->fx; + float ay = this->uy; + float ax = this->ux; + float py = this->py; + float px = this->px; + + this->rx = c * rx - s * ry; + this->ry = c * ry + s * rx; + this->fx = c * ux - s * uy; + this->fy = c * uy + s * ux; + this->ux = c * ax - s * ay; + this->uy = c * ay + s * ax; + this->px = c * px - s * py; + this->py = c * py + s * px; + +} + +void +CMatrix::Rotate(float x, float y, float z) +{ + float cX = Cos(x); + float sX = Sin(x); + float cY = Cos(y); + float sY = Sin(y); + float cZ = Cos(z); + float sZ = Sin(z); + + float rx = this->rx; + float ry = this->ry; + float rz = this->rz; + float ux = this->fx; + float uy = this->fy; + float uz = this->fz; + float ax = this->ux; + float ay = this->uy; + float az = this->uz; + float px = this->px; + float py = this->py; + float pz = this->pz; + + float x1 = cZ * cY - (sZ * sX) * sY; + float x2 = (cZ * sX) * sY + sZ * cY; + float x3 = -cX * sY; + float y1 = -sZ * cX; + float y2 = cZ * cX; + float y3 = sX; + float z1 = (sZ * sX) * cY + cZ * sY; + float z2 = sZ * sY - (cZ * sX) * cY; + float z3 = cX * cY; + + this->rx = x1 * rx + y1 * ry + z1 * rz; + this->ry = x2 * rx + y2 * ry + z2 * rz; + this->rz = x3 * rx + y3 * ry + z3 * rz; + this->fx = x1 * ux + y1 * uy + z1 * uz; + this->fy = x2 * ux + y2 * uy + z2 * uz; + this->fz = x3 * ux + y3 * uy + z3 * uz; + this->ux = x1 * ax + y1 * ay + z1 * az; + this->uy = x2 * ax + y2 * ay + z2 * az; + this->uz = x3 * ax + y3 * ay + z3 * az; + this->px = x1 * px + y1 * py + z1 * pz; + this->py = x2 * px + y2 * py + z2 * pz; + this->pz = x3 * px + y3 * py + z3 * pz; +} + +CMatrix & +CMatrix::operator*=(CMatrix const &rhs) +{ + // TODO: VU0 code + *this = *this * rhs; + return *this; +} + +void +CMatrix::Reorthogonalise(void) +{ + CVector &r = GetRight(); + CVector &f = GetForward(); + CVector &u = GetUp(); + u = CrossProduct(r, f); + u.Normalise(); + r = CrossProduct(f, u); + r.Normalise(); + f = CrossProduct(u, r); +} + +CMatrix +operator*(const CMatrix &m1, const CMatrix &m2) +{ + // TODO: VU0 code + CMatrix out; + out.rx = m1.rx * m2.rx + m1.fx * m2.ry + m1.ux * m2.rz; + out.ry = m1.ry * m2.rx + m1.fy * m2.ry + m1.uy * m2.rz; + out.rz = m1.rz * m2.rx + m1.fz * m2.ry + m1.uz * m2.rz; + out.fx = m1.rx * m2.fx + m1.fx * m2.fy + m1.ux * m2.fz; + out.fy = m1.ry * m2.fx + m1.fy * m2.fy + m1.uy * m2.fz; + out.fz = m1.rz * m2.fx + m1.fz * m2.fy + m1.uz * m2.fz; + out.ux = m1.rx * m2.ux + m1.fx * m2.uy + m1.ux * m2.uz; + out.uy = m1.ry * m2.ux + m1.fy * m2.uy + m1.uy * m2.uz; + out.uz = m1.rz * m2.ux + m1.fz * m2.uy + m1.uz * m2.uz; + out.px = m1.rx * m2.px + m1.fx * m2.py + m1.ux * m2.pz + m1.px; + out.py = m1.ry * m2.px + m1.fy * m2.py + m1.uy * m2.pz + m1.py; + out.pz = m1.rz * m2.px + m1.fz * m2.py + m1.uz * m2.pz + m1.pz; + return out; +} + +CMatrix & +Invert(const CMatrix &src, CMatrix &dst) +{ + // TODO: VU0 code + // GTA handles this as a raw 4x4 orthonormal matrix + // and trashes the RW flags, let's not do that + dst.f[3][0] = dst.f[3][1] = dst.f[3][2] = 0.0f; +#ifndef FIX_BUGS + dst.f[3][3] = src.f[3][3]; +#endif + + dst.f[0][0] = src.f[0][0]; + dst.f[0][1] = src.f[1][0]; + dst.f[0][2] = src.f[2][0]; +#ifndef FIX_BUGS + dst.f[0][3] = src.f[3][0]; +#endif + dst.f[1][0] = src.f[0][1]; + dst.f[1][1] = src.f[1][1]; + dst.f[1][2] = src.f[2][1]; +#ifndef FIX_BUGS + dst.f[1][3] = src.f[3][1]; +#endif + dst.f[2][0] = src.f[0][2]; + dst.f[2][1] = src.f[1][2]; + dst.f[2][2] = src.f[2][2]; +#ifndef FIX_BUGS + dst.f[2][3] = src.f[3][2]; +#endif + + dst.f[3][0] += dst.f[0][0] * src.f[3][0]; + dst.f[3][1] += dst.f[0][1] * src.f[3][0]; + dst.f[3][2] += dst.f[0][2] * src.f[3][0]; +#ifndef FIX_BUGS + dst.f[3][3] += dst.f[0][3] * src.f[3][0]; +#endif + + dst.f[3][0] += dst.f[1][0] * src.f[3][1]; + dst.f[3][1] += dst.f[1][1] * src.f[3][1]; + dst.f[3][2] += dst.f[1][2] * src.f[3][1]; +#ifndef FIX_BUGS + dst.f[3][3] += dst.f[1][3] * src.f[3][1]; +#endif + + dst.f[3][0] += dst.f[2][0] * src.f[3][2]; + dst.f[3][1] += dst.f[2][1] * src.f[3][2]; + dst.f[3][2] += dst.f[2][2] * src.f[3][2]; +#ifndef FIX_BUGS + dst.f[3][3] += dst.f[2][3] * src.f[3][2]; +#endif + + dst.f[3][0] = -dst.f[3][0]; + dst.f[3][1] = -dst.f[3][1]; + dst.f[3][2] = -dst.f[3][2]; +#ifndef FIX_BUGS + dst.f[3][3] = src.f[3][3] - dst.f[3][3]; +#endif + + return dst; +} + +CMatrix +Invert(const CMatrix &matrix) +{ + CMatrix inv; + return Invert(matrix, inv); +} + +void +CCompressedMatrixNotAligned::CompressFromFullMatrix(CMatrix &other) +{ + m_rightX = 127.0f * other.GetRight().x; + m_rightY = 127.0f * other.GetRight().y; + m_rightZ = 127.0f * other.GetRight().z; + m_upX = 127.0f * other.GetForward().x; + m_upY = 127.0f * other.GetForward().y; + m_upZ = 127.0f * other.GetForward().z; + m_vecPos = other.GetPosition(); +} + +void +CCompressedMatrixNotAligned::DecompressIntoFullMatrix(CMatrix &other) +{ + other.GetRight().x = m_rightX / 127.0f; + other.GetRight().y = m_rightY / 127.0f; + other.GetRight().z = m_rightZ / 127.0f; + other.GetForward().x = m_upX / 127.0f; + other.GetForward().y = m_upY / 127.0f; + other.GetForward().z = m_upZ / 127.0f; + other.GetUp() = CrossProduct(other.GetRight(), other.GetForward()); + other.GetPosition() = m_vecPos; + other.Reorthogonalise(); +} \ No newline at end of file diff --git a/src/math/Matrix.h b/src/math/Matrix.h new file mode 100644 index 0000000..6404b50 --- /dev/null +++ b/src/math/Matrix.h @@ -0,0 +1,131 @@ +#pragma once + +class CMatrix +{ +public: + union + { + float f[4][4]; + struct + { + float rx, ry, rz, rw; + float fx, fy, fz, fw; + float ux, uy, uz, uw; + float px, py, pz, pw; + }; + }; + + RwMatrix *m_attachment; + bool m_hasRwMatrix; // are we the owner? + + CMatrix(void); + CMatrix(CMatrix const &m); + CMatrix(RwMatrix *matrix, bool owner = false); + CMatrix(float scale){ + m_attachment = nil; + m_hasRwMatrix = false; + SetScale(scale); + } + ~CMatrix(void); + void Attach(RwMatrix *matrix, bool owner = false); + void AttachRW(RwMatrix *matrix, bool owner = false); + void Detach(void); + void Update(void); + void UpdateRW(void); + void operator=(CMatrix const &rhs); + CMatrix &operator+=(CMatrix const &rhs); + CMatrix &operator*=(CMatrix const &rhs); + + CVector &GetPosition(void) { return *(CVector*)&px; } + CVector &GetRight(void) { return *(CVector*)℞ } + CVector &GetForward(void) { return *(CVector*)&fx; } + CVector &GetUp(void) { return *(CVector*)&ux; } + + const CVector &GetPosition(void) const { return *(CVector*)&px; } + const CVector &GetRight(void) const { return *(CVector*)℞ } + const CVector &GetForward(void) const { return *(CVector*)&fx; } + const CVector &GetUp(void) const { return *(CVector*)&ux; } + + + void SetTranslate(float x, float y, float z); + void SetTranslate(const CVector &trans){ SetTranslate(trans.x, trans.y, trans.z); } + void Translate(float x, float y, float z){ + px += x; + py += y; + pz += z; + } + void Translate(const CVector &trans){ Translate(trans.x, trans.y, trans.z); } + + void SetScale(float s); + void Scale(float scale) + { + for (int i = 0; i < 3; i++) +#ifdef FIX_BUGS // BUGFIX from VC + for (int j = 0; j < 3; j++) +#else + for (int j = 0; j < 4; j++) +#endif + f[i][j] *= scale; + } + + + void SetRotateXOnly(float angle); + void SetRotateYOnly(float angle); + void SetRotateZOnly(float angle); + void SetRotateX(float angle); + void SetRotateY(float angle); + void SetRotateZ(float angle); + void SetRotate(float xAngle, float yAngle, float zAngle); + void Rotate(float x, float y, float z); + void RotateX(float x); + void RotateY(float y); + void RotateZ(float z); + + void Reorthogonalise(void); + void CopyOnlyMatrix(const CMatrix &other); + void SetUnity(void); + void ResetOrientation(void); + void SetTranslateOnly(float x, float y, float z) { + px = x; + py = y; + pz = z; + } + void SetTranslateOnly(const CVector& pos) { + SetTranslateOnly(pos.x, pos.y, pos.z); + } + void CheckIntegrity(){} +}; + + +CMatrix &Invert(const CMatrix &src, CMatrix &dst); +CMatrix Invert(const CMatrix &matrix); +CMatrix operator*(const CMatrix &m1, const CMatrix &m2); +inline CVector MultiplyInverse(const CMatrix &mat, const CVector &vec) +{ + CVector v(vec.x - mat.px, vec.y - mat.py, vec.z - mat.pz); + return CVector( + mat.rx * v.x + mat.ry * v.y + mat.rz * v.z, + mat.fx * v.x + mat.fy * v.y + mat.fz * v.z, + mat.ux * v.x + mat.uy * v.y + mat.uz * v.z); +} + + + +class CCompressedMatrixNotAligned +{ + CVector m_vecPos; + int8 m_rightX; + int8 m_rightY; + int8 m_rightZ; + int8 m_upX; + int8 m_upY; + int8 m_upZ; +public: + void CompressFromFullMatrix(CMatrix &other); + void DecompressIntoFullMatrix(CMatrix &other); +}; + +class CCompressedMatrix : public CCompressedMatrixNotAligned +{ + int _alignment; // no clue what would this align to +}; \ No newline at end of file diff --git a/src/math/Quaternion.cpp b/src/math/Quaternion.cpp new file mode 100644 index 0000000..b0e782e --- /dev/null +++ b/src/math/Quaternion.cpp @@ -0,0 +1,177 @@ +#include "common.h" +#include "Quaternion.h" + +void +CQuaternion::Normalise(void) +{ + float sq = MagnitudeSqr(); + if (sq == 0.0f) + w = 1.0f; + else { + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + z *= invsqrt; + w *= invsqrt; + } +} + +void +CQuaternion::Slerp(const CQuaternion &q1, const CQuaternion &q2, float theta, float invSin, float t) +{ + if (theta == 0.0f) + *this = q2; + else { + float w1, w2; + if (theta > PI / 2) { + theta = PI - theta; + w1 = Sin((1.0f - t) * theta) * invSin; + w2 = -Sin(t * theta) * invSin; + } else { + w1 = Sin((1.0f - t) * theta) * invSin; + w2 = Sin(t * theta) * invSin; + } + // TODO: VU0 code + *this = w1 * q1 + w2 * q2; + } +} + +void +CQuaternion::Multiply(const CQuaternion &q1, const CQuaternion &q2) +{ + x = (q2.z * q1.y) - (q1.z * q2.y) + (q1.x * q2.w) + (q2.x * q1.w); + y = (q2.x * q1.z) - (q1.x * q2.z) + (q1.y * q2.w) + (q2.y * q1.w); + z = (q2.y * q1.x) - (q1.y * q2.x) + (q1.z * q2.w) + (q2.z * q1.w); + w = (q2.w * q1.w) - (q2.x * q1.x) - (q2.y * q1.y) - (q2.z * q1.z); +} + +void +CQuaternion::Get(RwV3d *axis, float *angle) +{ + *angle = Acos(w); + float s = Sin(*angle); + + axis->x = x * (1.0f / s); + axis->y = y * (1.0f / s); + axis->z = z * (1.0f / s); +} + +void +CQuaternion::Set(RwV3d *axis, float angle) +{ + float halfCos = Cos(angle * 0.5f); + float halfSin = Sin(angle * 0.5f); + x = axis->x * halfSin; + y = axis->y * halfSin; + z = axis->z * halfSin; + w = halfCos; +} + +void +CQuaternion::Get(RwMatrix *matrix) +{ + float x2 = x + x; + float y2 = y + y; + float z2 = z + z; + + float x_2x = x * x2; + float x_2y = x * y2; + float x_2z = x * z2; + float y_2y = y * y2; + float y_2z = y * z2; + float z_2z = z * z2; + float w_2x = w * x2; + float w_2y = w * y2; + float w_2z = w * z2; + + matrix->right.x = 1.0f - (y_2y + z_2z); + matrix->up.x = x_2y - w_2z; + matrix->at.x = x_2z + w_2y; + matrix->right.y = x_2y + w_2z; + matrix->up.y = 1.0f - (x_2x + z_2z); + matrix->at.y = y_2z - w_2x; + matrix->right.z = x_2z - w_2y; + matrix->up.z = y_2z + w_2x; + matrix->at.z = 1.0f - (x_2x + y_2y); +} + +void +CQuaternion::Set(const RwMatrix &matrix) +{ + float f, s, m; + + f = matrix.up.y + matrix.right.x + matrix.at.z; + if (f >= 0.0f) { + s = Sqrt(f + 1.0f); + w = 0.5f * s; + m = 0.5f / s; + x = (matrix.up.z - matrix.at.y) * m; + y = (matrix.at.x - matrix.right.z) * m; + z = (matrix.right.y - matrix.up.x) * m; + return; + } + + f = matrix.right.x - matrix.up.y - matrix.at.z; + if (f >= 0.0f) { + s = Sqrt(f + 1.0f); + x = 0.5f * s; + m = 0.5f / s; + y = (matrix.up.x + matrix.right.y) * m; + z = (matrix.at.x + matrix.right.z) * m; + w = (matrix.up.z - matrix.at.y) * m; + return; + } + + f = matrix.up.y - matrix.right.x - matrix.at.z; + if (f >= 0.0f) { + s = Sqrt(f + 1.0f); + y = 0.5f * s; + m = 0.5f / s; + w = (matrix.at.x - matrix.right.z) * m; + x = (matrix.up.x - matrix.right.y) * m; + z = (matrix.at.y + matrix.up.z) * m; + return; + } + + f = matrix.at.z - (matrix.up.y + matrix.right.x); + s = Sqrt(f + 1.0f); + z = 0.5f * s; + m = 0.5f / s; + w = (matrix.right.y - matrix.up.x) * m; + x = (matrix.at.x + matrix.right.z) * m; + y = (matrix.at.y + matrix.up.z) * m; +} + +void +CQuaternion::Get(float *f1, float *f2, float *f3) +{ + RwMatrix matrix; + + Get(&matrix); + *f3 = Atan2(matrix.right.y, matrix.up.y); + if (*f3 < 0.0f) + *f3 += TWOPI; + float s = Sin(*f3); + float c = Cos(*f3); + *f1 = Atan2(-matrix.at.y, s * matrix.right.y + c * matrix.up.y); + if (*f1 < 0.0f) + *f1 += TWOPI; + *f2 = Atan2(-(matrix.right.z * c - matrix.up.z * s), matrix.right.x * c - matrix.up.x * s); + if (*f2 < 0.0f) + *f2 += TWOPI; +} + +void +CQuaternion::Set(float f1, float f2, float f3) +{ + float c1 = Cos(f1 * 0.5f); + float c2 = Cos(f2 * 0.5f); + float c3 = Cos(f3 * 0.5f); + float s1 = Sin(f1 * 0.5f); + float s2 = Sin(f2 * 0.5f); + float s3 = Sin(f3 * 0.5f); + x = ((c2 * c1) * s3) - ((s2 * s1) * c3); + y = ((s1 * c2) * c3) + ((s2 * c1) * s3); + z = ((s2 * c1) * c3) - ((s1 * c2) * s3); + w = ((c2 * c1) * c3) + ((s2 * s1) * s3); +} \ No newline at end of file diff --git a/src/math/Quaternion.h b/src/math/Quaternion.h new file mode 100644 index 0000000..47c94f7 --- /dev/null +++ b/src/math/Quaternion.h @@ -0,0 +1,95 @@ +#pragma once + +// TODO: actually implement this +class CQuaternion +{ +public: + float x, y, z, w; + CQuaternion(void) {} + CQuaternion(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} + + float Magnitude(void) const { return Sqrt(x*x + y*y + z*z + w*w); } + float MagnitudeSqr(void) const { return x*x + y*y + z*z + w*w; } + void Normalise(void); + void Multiply(const CQuaternion &q1, const CQuaternion &q2); + void Invert(void){ // Conjugate would have been a better name + x = -x; + y = -y; + z = -z; + } + + const CQuaternion &operator+=(CQuaternion const &right) { + x += right.x; + y += right.y; + z += right.z; + w += right.w; + return *this; + } + + const CQuaternion &operator-=(CQuaternion const &right) { + x -= right.x; + y -= right.y; + z -= right.z; + w -= right.w; + return *this; + } + + const CQuaternion &operator*=(float right) { + x *= right; + y *= right; + z *= right; + w *= right; + return *this; + } + + const CQuaternion &operator/=(float right) { + x /= right; + y /= right; + z /= right; + w /= right; + return *this; + } + + CQuaternion operator-() const { + return CQuaternion(-x, -y, -z, -w); + } + + void Slerp(const CQuaternion &q1, const CQuaternion &q2, float theta, float invSin, float t); + void Get(RwV3d *axis, float *angle); + void Set(RwV3d *axis, float angle); + void Get(RwMatrix *matrix); + void Set(const RwMatrix &matrix); + void Set(float f1, float f2, float f3); + void Get(float *f1, float *f2, float *f3); +}; + +inline float +DotProduct(const CQuaternion &q1, const CQuaternion &q2) +{ + return q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; +} + +inline CQuaternion operator+(const CQuaternion &left, const CQuaternion &right) +{ + return CQuaternion(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w); +} + +inline CQuaternion operator-(const CQuaternion &left, const CQuaternion &right) +{ + return CQuaternion(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w); +} + +inline CQuaternion operator*(const CQuaternion &left, float right) +{ + return CQuaternion(left.x * right, left.y * right, left.z * right, left.w * right); +} + +inline CQuaternion operator*(float left, const CQuaternion &right) +{ + return CQuaternion(left * right.x, left * right.y, left * right.z, left * right.w); +} + +inline CQuaternion operator/(const CQuaternion &left, float right) +{ + return CQuaternion(left.x / right, left.y / right, left.z / right, left.w / right); +} diff --git a/src/math/Rect.cpp b/src/math/Rect.cpp new file mode 100644 index 0000000..de6320a --- /dev/null +++ b/src/math/Rect.cpp @@ -0,0 +1,17 @@ +#include "common.h" + +CRect::CRect(void) +{ + left = 1000000.0f; + top = 1000000.0f; + right = -1000000.0f; + bottom = -1000000.0f; +} + +CRect::CRect(float l, float t, float r, float b) +{ + left = l; + top = t; + right = r; + bottom = b; +} \ No newline at end of file diff --git a/src/math/Rect.h b/src/math/Rect.h new file mode 100644 index 0000000..e9b2589 --- /dev/null +++ b/src/math/Rect.h @@ -0,0 +1,71 @@ +#pragma once + +class CRect +{ +public: + float left; // x min + float bottom; // y max + float right; // x max + float top; // y min + + CRect(void); + CRect(float l, float t, float r, float b); + void ContainPoint(CVector const &v){ + if(v.x < left) left = v.x; + if(v.x > right) right = v.x; + if(v.y < top) top = v.y; + if(v.y > bottom) bottom = v.y; + } + void ContainRect(const CRect &r){ + if(r.left < left) left = r.left; + if(r.right > right) right = r.right; + if(r.top < top) top = r.top; + if(r.bottom > bottom) bottom = r.bottom; + } + + bool IsPointInside(const CVector2D &p){ + return p.x >= left && + p.x <= right && + p.y >= top && + p.y <= bottom; + } + bool IsPointInside(const CVector2D &p, float extraRadius){ + return p.x >= left-extraRadius && + p.x <= right+extraRadius && + p.y >= top-extraRadius && + p.y <= bottom+extraRadius; + } + + void Translate(float x, float y){ + left += x; + right += x; + bottom += y; + top += y; + } + + void Grow(float r) { + left -= r; + right += r; + top -= r; + bottom += r; + } + + void Grow(float l, float r) + { + left -= l; + top -= l; + right += r; + bottom += r; + } + + void Grow(float l, float r, float t, float b) + { + left -= l; + top -= t; + right += r; + bottom += b; + } + + float GetWidth(void) { return right - left; } + float GetHeight(void) { return bottom - top; } +}; diff --git a/src/math/Vector.cpp b/src/math/Vector.cpp new file mode 100644 index 0000000..ee76e55 --- /dev/null +++ b/src/math/Vector.cpp @@ -0,0 +1,46 @@ +#include "common.h" + +void +CVector::Normalise(void) +{ + float sq = MagnitudeSqr(); + if (sq > 0.0f) { + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + z *= invsqrt; + } else + x = 1.0f; +} + +CVector +CrossProduct(const CVector &v1, const CVector &v2) +{ + return CVector(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x); +} + +CVector +Multiply3x3(const CMatrix &mat, const CVector &vec) +{ + // TODO: VU0 code + return CVector(mat.rx * vec.x + mat.fx * vec.y + mat.ux * vec.z, + mat.ry * vec.x + mat.fy * vec.y + mat.uy * vec.z, + mat.rz * vec.x + mat.fz * vec.y + mat.uz * vec.z); +} + +CVector +Multiply3x3(const CVector &vec, const CMatrix &mat) +{ + return CVector(mat.rx * vec.x + mat.ry * vec.y + mat.rz * vec.z, + mat.fx * vec.x + mat.fy * vec.y + mat.fz * vec.z, + mat.ux * vec.x + mat.uy * vec.y + mat.uz * vec.z); +} + +CVector +operator*(const CMatrix &mat, const CVector &vec) +{ + // TODO: VU0 code + return CVector(mat.rx * vec.x + mat.fx * vec.y + mat.ux * vec.z + mat.px, + mat.ry * vec.x + mat.fy * vec.y + mat.uy * vec.z + mat.py, + mat.rz * vec.x + mat.fz * vec.y + mat.uz * vec.z + mat.pz); +} diff --git a/src/math/Vector.h b/src/math/Vector.h new file mode 100644 index 0000000..776bfcf --- /dev/null +++ b/src/math/Vector.h @@ -0,0 +1,129 @@ +#pragma once + +class CVector : public RwV3d +{ +public: + CVector(void) {} + CVector(float x, float y, float z) + { + this->x = x; + this->y = y; + this->z = z; + } + + CVector(const RwV3d &v) + { + x = v.x; + y = v.y; + z = v.z; + } + // (0,1,0) means no rotation. So get right vector and its atan + float Heading(void) const { return Atan2(-x, y); } + float Magnitude(void) const { return Sqrt(x*x + y*y + z*z); } + float MagnitudeSqr(void) const { return x*x + y*y + z*z; } + float Magnitude2D(void) const { return Sqrt(x*x + y*y); } + float MagnitudeSqr2D(void) const { return x*x + y*y; } + void Normalise(void); + + void Normalise2D(void) { + float sq = MagnitudeSqr2D(); + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + } + + const CVector &operator+=(CVector const &right) { + x += right.x; + y += right.y; + z += right.z; + return *this; + } + + const CVector &operator-=(CVector const &right) { + x -= right.x; + y -= right.y; + z -= right.z; + return *this; + } + + const CVector &operator*=(float right) { + x *= right; + y *= right; + z *= right; + return *this; + } + + const CVector &operator/=(float right) { + x /= right; + y /= right; + z /= right; + return *this; + } + + CVector operator-() const { + return CVector(-x, -y, -z); + } + + const bool operator==(CVector const &right) { + return x == right.x && y == right.y && z == right.z; + } + + const bool operator!=(CVector const &right) { + return x != right.x || y != right.y || z != right.z; + } + + bool IsZero(void) const { return x == 0.0f && y == 0.0f && z == 0.0f; } +}; + +inline CVector operator+(const CVector &left, const CVector &right) +{ + return CVector(left.x + right.x, left.y + right.y, left.z + right.z); +} + +inline CVector operator-(const CVector &left, const CVector &right) +{ + return CVector(left.x - right.x, left.y - right.y, left.z - right.z); +} + +inline CVector operator*(const CVector &left, float right) +{ + return CVector(left.x * right, left.y * right, left.z * right); +} + +inline CVector operator*(float left, const CVector &right) +{ + return CVector(left * right.x, left * right.y, left * right.z); +} + +inline CVector operator/(const CVector &left, float right) +{ + return CVector(left.x / right, left.y / right, left.z / right); +} + +inline float +DotProduct(const CVector &v1, const CVector &v2) +{ + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; +} + +CVector CrossProduct(const CVector &v1, const CVector &v2); + +inline float +Distance(const CVector &v1, const CVector &v2) +{ + return (v2 - v1).Magnitude(); +} + +inline float +Distance2D(const CVector &v1, const CVector &v2) +{ + float x = v2.x - v1.x; + float y = v2.y - v1.y; + return Sqrt(x*x + y*y); +} + +class CMatrix; + +CVector Multiply3x3(const CMatrix &mat, const CVector &vec); +CVector Multiply3x3(const CVector &vec, const CMatrix &mat); +CVector operator*(const CMatrix &mat, const CVector &vec); \ No newline at end of file diff --git a/src/math/Vector2D.h b/src/math/Vector2D.h new file mode 100644 index 0000000..0235dbe --- /dev/null +++ b/src/math/Vector2D.h @@ -0,0 +1,109 @@ +#pragma once + +class CVector2D +{ +public: + float x, y; + CVector2D(void) {} + CVector2D(float x, float y) : x(x), y(y) {} + CVector2D(const CVector &v) : x(v.x), y(v.y) {} + float Heading(void) const { return Atan2(-x, y); } + float Magnitude(void) const { return Sqrt(x*x + y*y); } + float MagnitudeSqr(void) const { return x*x + y*y; } + + void Normalise(void) { + float sq = MagnitudeSqr(); + // assert(sq != 0.0f); // just be safe here + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + } + + void NormaliseSafe(void) { + float sq = MagnitudeSqr(); + if(sq > 0.0f){ + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + }else + x = 1.0f; + } + + const CVector2D &operator+=(CVector2D const &right) { + x += right.x; + y += right.y; + return *this; + } + + const CVector2D &operator-=(CVector2D const &right) { + x -= right.x; + y -= right.y; + return *this; + } + + const CVector2D &operator*=(float right) { + x *= right; + y *= right; + return *this; + } + + const CVector2D &operator/=(float right) { + x /= right; + y /= right; + return *this; + } + CVector2D operator-(const CVector2D &rhs) const { + return CVector2D(x-rhs.x, y-rhs.y); + } + CVector2D operator+(const CVector2D &rhs) const { + return CVector2D(x+rhs.x, y+rhs.y); + } + CVector2D operator/(float t) const { + return CVector2D(x/t, y/t); + } +}; + +inline float +DotProduct2D(const CVector2D &v1, const CVector2D &v2) +{ + return v1.x*v2.x + v1.y*v2.y; +} + +inline float +CrossProduct2D(const CVector2D &v1, const CVector2D &v2) +{ + return v1.x*v2.y - v1.y*v2.x; +} + +inline float +Distance2D(const CVector2D &v, float x, float y) +{ + return Sqrt((v.x-x)*(v.x-x) + (v.y-y)*(v.y-y)); +} + +inline float +DistanceSqr2D(const CVector2D &v, float x, float y) +{ + return (v.x-x)*(v.x-x) + (v.y-y)*(v.y-y); +} + +inline void +NormalizeXY(float &x, float &y) +{ + float l = Sqrt(x*x + y*y); + if(l != 0.0f){ + x /= l; + y /= l; + }else + x = 1.0f; +} + +inline CVector2D operator*(const CVector2D &left, float right) +{ + return CVector2D(left.x * right, left.y * right); +} + +inline CVector2D operator*(float left, const CVector2D &right) +{ + return CVector2D(left * right.x, left * right.y); +} diff --git a/src/math/VuVector.h b/src/math/VuVector.h new file mode 100644 index 0000000..4158409 --- /dev/null +++ b/src/math/VuVector.h @@ -0,0 +1,32 @@ +#pragma once + +class TYPEALIGN(16) CVuVector : public CVector +{ +public: + float w; + CVuVector(void) {} + CVuVector(float x, float y, float z) : CVector(x, y, z) {} + CVuVector(float x, float y, float z, float w) : CVector(x, y, z), w(w) {} + CVuVector(const CVector &v) : CVector(v.x, v.y, v.z) {} + CVuVector(const RwV3d &v) : CVector(v) {} +/* + void Normalise(void) { + float sq = MagnitudeSqr(); + // TODO: VU0 code + if(sq > 0.0f){ + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + z *= invsqrt; + }else + x = 1.0f; + } +*/ + + // TODO: operator- +}; + +void TransformPoint(CVuVector &out, const CMatrix &mat, const CVuVector &in); +void TransformPoint(CVuVector &out, const CMatrix &mat, const RwV3d &in); +void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const RwV3d *in, int stride); +void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const CVuVector *in); diff --git a/src/math/math.cpp b/src/math/math.cpp new file mode 100644 index 0000000..8cb56da --- /dev/null +++ b/src/math/math.cpp @@ -0,0 +1,118 @@ +#include "common.h" + +#include "VuVector.h" + +// TODO: move more stuff into here + + +void TransformPoint(CVuVector &out, const CMatrix &mat, const CVuVector &in) +{ +#ifdef GTA_PS2 + __asm__ __volatile__("\n\ + lqc2 vf01,0x0(%2)\n\ + lqc2 vf02,0x0(%1)\n\ + lqc2 vf03,0x10(%1)\n\ + lqc2 vf04,0x20(%1)\n\ + lqc2 vf05,0x30(%1)\n\ + vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + sqc2 vf06,0x0(%0)\n\ + ": : "r" (&out) , "r" (&mat) ,"r" (&in): "memory"); +#else + out = mat * in; +#endif +} + +void TransformPoint(CVuVector &out, const CMatrix &mat, const RwV3d &in) +{ +#ifdef GTA_PS2 + __asm__ __volatile__("\n\ + ldr $8,0x0(%2)\n\ + ldl $8,0x7(%2)\n\ + lw $9,0x8(%2)\n\ + pcpyld $10,$9,$8\n\ + qmtc2 $10,vf01\n\ + lqc2 vf02,0x0(%1)\n\ + lqc2 vf03,0x10(%1)\n\ + lqc2 vf04,0x20(%1)\n\ + lqc2 vf05,0x30(%1)\n\ + vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + sqc2 vf06,0x0(%0)\n\ + ": : "r" (&out) , "r" (&mat) ,"r" (&in): "memory"); +#else + out = mat * in; +#endif +} + +void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const RwV3d *in, int stride) +{ +#ifdef GTA_PS3 + __asm__ __volatile__("\n\ + paddub $3,%4,$0\n\ + lqc2 vf02,0x0(%2)\n\ + lqc2 vf03,0x10(%2)\n\ + lqc2 vf04,0x20(%2)\n\ + lqc2 vf05,0x30(%2)\n\ + ldr $8,0x0(%3)\n\ + ldl $8,0x7(%3)\n\ + lw $9,0x8(%3)\n\ + pcpyld $10,$9,$8\n\ + qmtc2 $10,vf01\n\ + 1: vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + add %3,%3,$3\n\ + ldr $8,0x0(%3)\n\ + ldl $8,0x7(%3)\n\ + lw $9,0x8(%3)\n\ + pcpyld $10,$9,$8\n\ + qmtc2 $10,vf01\n\ + addi %1,%1,-1\n\ + addiu %0,%0,0x10\n\ + sqc2 vf06,-0x10(%0)\n\ + bnez %1,1b\n\ + ": : "r" (out) , "r" (n), "r" (&mat), "r" (in), "r" (stride): "memory"); +#else + while(n--){ + *out = mat * *in; + in = (RwV3d*)((uint8*)in + stride); + out++; + } +#endif +} + +void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const CVuVector *in) +{ +#ifdef GTA_PS2 + __asm__ __volatile__("\n\ + lqc2 vf02,0x0(%2)\n\ + lqc2 vf03,0x10(%2)\n\ + lqc2 vf04,0x20(%2)\n\ + lqc2 vf05,0x30(%2)\n\ + lqc2 vf01,0x0(%3)\n\ + nop\n\ + 1: vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + lqc2 vf01,0x10(%3)\n\ + addiu %3,%3,0x10\n\ + addi %1,%1,-1\n\ + addiu %0,%0,0x10\n\ + sqc2 vf06,-0x10(%0)\n\ + bnez %1,1b\n\ + ": : "r" (out) , "r" (n), "r" (&mat) ,"r" (in): "memory"); +#else + while(n--){ + *out = mat * *in; + in++; + out++; + } +#endif +} diff --git a/src/math/maths.h b/src/math/maths.h new file mode 100644 index 0000000..6a22803 --- /dev/null +++ b/src/math/maths.h @@ -0,0 +1,19 @@ +#pragma once + +// wrapper around float versions of functions +// in gta they are in CMaths but that makes the code rather noisy + +inline float Sin(float x) { return sinf(x); } +inline float Asin(float x) { return asinf(x); } +inline float Cos(float x) { return cosf(x); } +inline float Acos(float x) { return acosf(x); } +inline float Tan(float x) { return tanf(x); } +inline float Atan(float x) { return atanf(x); } +inline float Atan2(float y, float x) { return atan2f(y, x); } +inline float Abs(float x) { return fabsf(x); } +inline float Sqrt(float x) { return sqrtf(x); } +inline float RecipSqrt(float x, float y) { return x/Sqrt(y); } +inline float RecipSqrt(float x) { return RecipSqrt(1.0f, x); } +inline float Pow(float x, float y) { return powf(x, y); } +inline float Floor(float x) { return floorf(x); } +inline float Ceil(float x) { return ceilf(x); } diff --git a/src/modelinfo/BaseModelInfo.cpp b/src/modelinfo/BaseModelInfo.cpp new file mode 100644 index 0000000..7137c60 --- /dev/null +++ b/src/modelinfo/BaseModelInfo.cpp @@ -0,0 +1,101 @@ +#include "common.h" + +#include "templates.h" +#include "TxdStore.h" +#include "2dEffect.h" +#include "BaseModelInfo.h" +#include "ColModel.h" + +CBaseModelInfo::CBaseModelInfo(ModelInfoType type) +{ + m_colModel = nil; + m_twodEffects = nil; + m_objectId = -1; + m_refCount = 0; + m_txdSlot = -1; + m_type = type; + m_num2dEffects = 0; + m_bOwnsColModel = false; +} + +void +CBaseModelInfo::Shutdown(void) +{ + DeleteCollisionModel(); + DeleteRwObject(); + m_twodEffects = nil; + m_num2dEffects = 0; + m_txdSlot = -1; +} + +void +CBaseModelInfo::DeleteCollisionModel(void) +{ + if(m_colModel && m_bOwnsColModel){ + if(m_colModel) + delete m_colModel; + m_colModel = nil; + } +} + +void +CBaseModelInfo::AddRef(void) +{ + m_refCount++; + AddTexDictionaryRef(); +} + +void +CBaseModelInfo::RemoveRef(void) +{ + m_refCount--; + RemoveTexDictionaryRef(); +} + +void +CBaseModelInfo::SetTexDictionary(const char *name) +{ + int slot = CTxdStore::FindTxdSlot(name); + if(slot == -1) + slot = CTxdStore::AddTxdSlot(name); + m_txdSlot = slot; +} + +void +CBaseModelInfo::AddTexDictionaryRef(void) +{ + CTxdStore::AddRef(m_txdSlot); +} + +void +CBaseModelInfo::RemoveTexDictionaryRef(void) +{ + CTxdStore::RemoveRef(m_txdSlot); +} + +void +CBaseModelInfo::Init2dEffects(void) +{ + m_twodEffects = nil; + m_num2dEffects = 0; +} + +void +CBaseModelInfo::Add2dEffect(C2dEffect *fx) +{ + if(m_twodEffects) + m_num2dEffects++; + else{ + m_twodEffects = fx; + m_num2dEffects = 1; + } +} + +C2dEffect* +CBaseModelInfo::Get2dEffect(int n) +{ + if(m_twodEffects) + return &m_twodEffects[n]; + else + return nil; +} diff --git a/src/modelinfo/BaseModelInfo.h b/src/modelinfo/BaseModelInfo.h new file mode 100644 index 0000000..f46cea8 --- /dev/null +++ b/src/modelinfo/BaseModelInfo.h @@ -0,0 +1,80 @@ +#pragma once + +struct CColModel; + +#define MAX_MODEL_NAME (24) + +enum ModelInfoType +{ + MITYPE_NA = 0, + MITYPE_SIMPLE = 1, + MITYPE_MLO = 2, + MITYPE_TIME = 3, + MITYPE_CLUMP = 4, + MITYPE_VEHICLE = 5, + MITYPE_PED = 6, + MITYPE_XTRACOMPS = 7, +}; + +class C2dEffect; + +class CBaseModelInfo +{ +protected: + char m_name[MAX_MODEL_NAME]; + CColModel *m_colModel; + C2dEffect *m_twodEffects; + int16 m_objectId; + uint16 m_refCount; + int16 m_txdSlot; + uint8 m_type; + uint8 m_num2dEffects; + bool m_bOwnsColModel; +#ifdef EXTRA_MODEL_FLAGS +public: + // from mobile + bool m_bIsDoubleSided; + bool m_bIsTree; + bool m_bCanBeIgnored; // for low-end devices + bool RenderDoubleSided(void) { return m_bIsDoubleSided || m_bIsTree; } +#endif + +public: + CBaseModelInfo(ModelInfoType type); + virtual ~CBaseModelInfo() {} + virtual void Shutdown(void); + virtual void DeleteRwObject(void) = 0; + virtual RwObject *CreateInstance(void) = 0; + virtual RwObject *CreateInstance(RwMatrix *) = 0; + virtual RwObject *GetRwObject(void) = 0; + + // one day it becomes virtual + uint8 GetModelType() const { return m_type; } + bool IsSimple(void) { return m_type == MITYPE_SIMPLE || m_type == MITYPE_TIME; } + bool IsClump(void) { return m_type == MITYPE_CLUMP || m_type == MITYPE_PED || m_type == MITYPE_VEHICLE || + m_type == MITYPE_MLO || m_type == MITYPE_XTRACOMPS; // unused but what the heck + } + char *GetModelName(void) { return m_name; } + void SetModelName(const char *name) { strncpy(m_name, name, MAX_MODEL_NAME); } + void SetColModel(CColModel *col, bool owns = false){ + m_colModel = col; m_bOwnsColModel = owns; } + CColModel *GetColModel(void) { return m_colModel; } + bool DoesOwnColModel(void) { return m_bOwnsColModel; } + void DeleteCollisionModel(void); + void ClearTexDictionary(void) { m_txdSlot = -1; } + int16 GetObjectID(void) { return m_objectId; } + void SetObjectID(int16 id) { m_objectId = id; } + int16 GetTxdSlot(void) { return m_txdSlot; } + void AddRef(void); + void RemoveRef(void); + void SetTexDictionary(const char *name); + void AddTexDictionaryRef(void); + void RemoveTexDictionaryRef(void); + void Init2dEffects(void); + void Add2dEffect(C2dEffect *fx); + C2dEffect *Get2dEffect(int n); + uint8 GetNum2dEffects() const { return m_num2dEffects; } + uint16 GetNumRefs() const { return m_refCount; } +}; + +VALIDATE_SIZE(CBaseModelInfo, 0x30); diff --git a/src/modelinfo/ClumpModelInfo.cpp b/src/modelinfo/ClumpModelInfo.cpp new file mode 100644 index 0000000..44a62af --- /dev/null +++ b/src/modelinfo/ClumpModelInfo.cpp @@ -0,0 +1,210 @@ +#include "common.h" + +#include "RwHelper.h" +#include "General.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "ModelInfo.h" +#include "ModelIndices.h" + +void +CClumpModelInfo::DeleteRwObject(void) +{ + if(m_clump){ + RpClumpDestroy(m_clump); + m_clump = nil; + RemoveTexDictionaryRef(); + } +} + +#ifdef PED_SKIN +static RpAtomic* +SetHierarchyForSkinAtomic(RpAtomic *atomic, void *data) +{ + RpSkinAtomicSetHAnimHierarchy(atomic, (RpHAnimHierarchy*)data); + return nil; +} +#endif + +RwObject* +CClumpModelInfo::CreateInstance(void) +{ + if(m_clump == nil) + return nil; + RpClump *clone = RpClumpClone(m_clump); +#ifdef PED_SKIN + if(IsClumpSkinned(clone)){ + RpHAnimHierarchy *hier; + RpHAnimAnimation *anim; + + hier = GetAnimHierarchyFromClump(clone); + assert(hier); + // This seems dangerous as only the first atomic will get a hierarchy + // can we guarantee this if hands and head are also in the clump? + RpClumpForAllAtomics(clone, SetHierarchyForSkinAtomic, hier); + anim = HAnimAnimationCreateForHierarchy(hier); + RpHAnimHierarchySetCurrentAnim(hier, anim); + RpHAnimHierarchySetFlags(hier, (RpHAnimHierarchyFlag)(rpHANIMHIERARCHYUPDATEMODELLINGMATRICES|rpHANIMHIERARCHYUPDATELTMS)); + // the rest is xbox only: + // RpSkinGetNumBones(RpSkinGeometryGetSkin(RpAtomicGetGeometry(IsClumpSkinned(clone)))); + RpHAnimHierarchyUpdateMatrices(hier); + } +#endif + return (RwObject*)clone; +} + +RwObject* +CClumpModelInfo::CreateInstance(RwMatrix *m) +{ + if(m_clump){ + RpClump *clump = (RpClump*)CreateInstance(); + *RwFrameGetMatrix(RpClumpGetFrame(clump)) = *m; + return (RwObject*)clump; + } + return nil; +} + +RpAtomic* +CClumpModelInfo::SetAtomicRendererCB(RpAtomic *atomic, void *data) +{ + CVisibilityPlugins::SetAtomicRenderCallback(atomic, (RpAtomicCallBackRender)data); + return atomic; +} + +void +CClumpModelInfo::SetClump(RpClump *clump) +{ + m_clump = clump; + CVisibilityPlugins::SetClumpModelInfo(m_clump, this); + AddTexDictionaryRef(); + RpClumpForAllAtomics(clump, SetAtomicRendererCB, nil); + +#ifdef PED_SKIN + if(IsClumpSkinned(clump)){ + int i; + RpHAnimHierarchy *hier; + RpAtomic *skinAtomic; + RpSkin *skin; + + // mobile: +// hier = nil; +// RwFrameForAllChildren(RpClumpGetFrame(clump), GetHierarchyFromChildNodesCB, &hier); +// assert(hier); +// RpClumpForAllAtomics(clump, SetHierarchyForSkinAtomic, hier); +// skinAtomic = GetFirstAtomic(clump); + + // xbox: + hier = GetAnimHierarchyFromClump(clump); + assert(hier); + RpSkinAtomicSetHAnimHierarchy(IsClumpSkinned(clump), hier); + skinAtomic = IsClumpSkinned(clump); + + assert(skinAtomic); + skin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(skinAtomic)); + // ignore const + for(i = 0; i < RpGeometryGetNumVertices(RpAtomicGetGeometry(skinAtomic)); i++){ + RwMatrixWeights *weights = (RwMatrixWeights*)&RpSkinGetVertexBoneWeights(skin)[i]; + float sum = weights->w0 + weights->w1 + weights->w2 + weights->w3; + weights->w0 /= sum; + weights->w1 /= sum; + weights->w2 /= sum; + weights->w3 /= sum; + } + RpHAnimHierarchySetFlags(hier, (RpHAnimHierarchyFlag)(rpHANIMHIERARCHYUPDATEMODELLINGMATRICES|rpHANIMHIERARCHYUPDATELTMS)); + } + if(strcmp(GetModelName(), "playerh") == 0){ + // playerh is incompatible with the xbox player skin + // so check if player model is skinned and only apply skin to head if it isn't + CPedModelInfo *body = (CPedModelInfo*)CModelInfo::GetModelInfo(MI_PLAYER); + if(!(body->m_clump && IsClumpSkinned(body->m_clump))) + RpClumpForAllAtomics(clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); + } +#else + if(strcmp(GetModelName(), "playerh") == 0) + RpClumpForAllAtomics(clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); +#endif +} + +void +CClumpModelInfo::SetFrameIds(RwObjectNameIdAssocation *assocs) +{ + int32 i; + RwObjectNameAssociation objname; + + for(i = 0; assocs[i].name; i++) + if((assocs[i].flags & CLUMP_FLAG_NO_HIERID) == 0){ + objname.frame = nil; + objname.name = assocs[i].name; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), FindFrameFromNameWithoutIdCB, &objname); + if(objname.frame) + CVisibilityPlugins::SetFrameHierarchyId(objname.frame, assocs[i].hierId); + } +} + +RwFrame* +CClumpModelInfo::FindFrameFromIdCB(RwFrame *frame, void *data) +{ + RwObjectIdAssociation *assoc = (RwObjectIdAssociation*)data; + + if(CVisibilityPlugins::GetFrameHierarchyId(frame) == assoc->id){ + assoc->frame = frame; + return nil; + } + RwFrameForAllChildren(frame, FindFrameFromIdCB, assoc); + return assoc->frame ? nil : frame; +} + +RwFrame* +CClumpModelInfo::FindFrameFromNameCB(RwFrame *frame, void *data) +{ + RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data; + + if(!CGeneral::faststricmp(GetFrameNodeName(frame), assoc->name)){ + assoc->frame = frame; + return nil; + } + RwFrameForAllChildren(frame, FindFrameFromNameCB, assoc); + return assoc->frame ? nil : frame; +} + +RwFrame* +CClumpModelInfo::FindFrameFromNameWithoutIdCB(RwFrame *frame, void *data) +{ + RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data; + + if(CVisibilityPlugins::GetFrameHierarchyId(frame) == 0 && + !CGeneral::faststricmp(GetFrameNodeName(frame), assoc->name)){ + assoc->frame = frame; + return nil; + } + RwFrameForAllChildren(frame, FindFrameFromNameWithoutIdCB, assoc); + return assoc->frame ? nil : frame; +} + +RwFrame* +CClumpModelInfo::FillFrameArrayCB(RwFrame *frame, void *data) +{ + int32 id; + RwFrame **frames = (RwFrame**)data; + id = CVisibilityPlugins::GetFrameHierarchyId(frame); + if(id > 0) + frames[id] = frame; + RwFrameForAllChildren(frame, FillFrameArrayCB, data); + return frame; +} + +void +CClumpModelInfo::FillFrameArray(RpClump *clump, RwFrame **frames) +{ + RwFrameForAllChildren(RpClumpGetFrame(clump), FillFrameArrayCB, frames); +} + +RwFrame* +CClumpModelInfo::GetFrameFromId(RpClump *clump, int32 id) +{ + RwObjectIdAssociation assoc; + assoc.id = id; + assoc.frame = nil; + RwFrameForAllChildren(RpClumpGetFrame(clump), FindFrameFromIdCB, &assoc); + return assoc.frame; +} diff --git a/src/modelinfo/ClumpModelInfo.h b/src/modelinfo/ClumpModelInfo.h new file mode 100644 index 0000000..58b6de1 --- /dev/null +++ b/src/modelinfo/ClumpModelInfo.h @@ -0,0 +1,54 @@ +#pragma once + +#include "BaseModelInfo.h" + +struct RwObjectNameIdAssocation +{ + const char *name; + int32 hierId; + uint32 flags; +}; + +struct RwObjectNameAssociation +{ + const char *name; + RwFrame *frame; +}; + +struct RwObjectIdAssociation +{ + int32 id; + RwFrame *frame; +}; + +enum { + CLUMP_FLAG_NO_HIERID = 0x1, +}; + + +class CClumpModelInfo : public CBaseModelInfo +{ +public: + RpClump *m_clump; + + CClumpModelInfo(void) : CBaseModelInfo(MITYPE_CLUMP) {} + CClumpModelInfo(ModelInfoType id) : CBaseModelInfo(id) {} + ~CClumpModelInfo() {} + void DeleteRwObject(void); + RwObject *CreateInstance(void); + RwObject *CreateInstance(RwMatrix *); + RwObject *GetRwObject(void) { return (RwObject*)m_clump; } + + virtual void SetClump(RpClump *); + + static RpAtomic *SetAtomicRendererCB(RpAtomic *atomic, void *data); + void SetFrameIds(RwObjectNameIdAssocation *assocs); + static RwFrame *FindFrameFromNameCB(RwFrame *frame, void *data); + static RwFrame *FindFrameFromNameWithoutIdCB(RwFrame *frame, void *data); + static RwFrame *FindFrameFromIdCB(RwFrame *frame, void *data); + static void FillFrameArray(RpClump *clump, RwFrame **frames); + static RwFrame *FillFrameArrayCB(RwFrame *frame, void *data); + static RwFrame *GetFrameFromId(RpClump *clump, int32 id); +}; + +VALIDATE_SIZE(CClumpModelInfo, 0x34); diff --git a/src/modelinfo/MloModelInfo.cpp b/src/modelinfo/MloModelInfo.cpp new file mode 100644 index 0000000..7535e6c --- /dev/null +++ b/src/modelinfo/MloModelInfo.cpp @@ -0,0 +1,39 @@ +#include "common.h" + +#include "VisibilityPlugins.h" +#include "ModelInfo.h" + +void +CMloModelInfo::ConstructClump() +{ + m_clump = RpClumpCreate(); + RwFrame *mainFrame = RwFrameCreate(); + RwFrameSetIdentity(mainFrame); + RpClumpSetFrame(m_clump, mainFrame); + + for (int i = firstInstance; i < lastInstance; i++) { + int modelId = CModelInfo::GetMloInstanceStore().store[i].m_modelIndex; + RwMatrix *attMat = CModelInfo::GetMloInstanceStore().store[i].GetMatrix().m_attachment; + CSimpleModelInfo *minfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(modelId); + + if (minfo->m_atomics[0] != nil) { + RpAtomic *newAtomic = RpAtomicClone(minfo->m_atomics[0]); + RwFrame *newFrame = RwFrameCreate(); + if (newAtomic != nil && newFrame != nil) { + *RwFrameGetMatrix(newFrame) = *attMat; + RpAtomicSetFrame(newAtomic, newFrame); + RwFrameAddChild(mainFrame, newFrame); + RpClumpAddAtomic(m_clump, newAtomic); + } else { + debug("Failed to allocate memory while creating template MLO.\n"); + } + } + } + + if (RpClumpGetNumAtomics(m_clump) != 0) { + CVisibilityPlugins::SetClumpModelInfo(m_clump, this); + } else { + RpClumpDestroy(m_clump); + m_clump = nil; + } +} \ No newline at end of file diff --git a/src/modelinfo/MloModelInfo.h b/src/modelinfo/MloModelInfo.h new file mode 100644 index 0000000..b1ae329 --- /dev/null +++ b/src/modelinfo/MloModelInfo.h @@ -0,0 +1,14 @@ +#pragma once + +#include "ClumpModelInfo.h" + +class CMloModelInfo : public CClumpModelInfo +{ +public: + float drawDist; + int firstInstance; + int lastInstance; +public: + CMloModelInfo(void) : CClumpModelInfo(MITYPE_MLO) {} + void ConstructClump(); +}; \ No newline at end of file diff --git a/src/modelinfo/ModelIndices.cpp b/src/modelinfo/ModelIndices.cpp new file mode 100644 index 0000000..98c7fb3 --- /dev/null +++ b/src/modelinfo/ModelIndices.cpp @@ -0,0 +1,34 @@ +#include "common.h" + +#include "General.h" +#include "ModelIndices.h" + +#define X(name, var) int16 var = -1; + MODELINDICES +#undef X + +void +InitModelIndices(void) +{ +#define X(name, var) var = -1; + MODELINDICES +#undef X +} + +void +MatchModelString(const char *modelname, int16 id) +{ +#define X(name, var) \ + if(!CGeneral::faststrcmp(name, modelname)){ \ + var = id; \ + return; \ + } + MODELINDICES +#undef X +} + +void +TestModelIndices(void) +{ + ; +} diff --git a/src/modelinfo/ModelIndices.h b/src/modelinfo/ModelIndices.h new file mode 100644 index 0000000..c0f0192 --- /dev/null +++ b/src/modelinfo/ModelIndices.h @@ -0,0 +1,509 @@ +#pragma once + +#define MODELINDICES \ + X("fire_hydrant", MI_FIRE_HYDRANT) \ + X("bagelstnd02", MI_BAGELSTAND2) \ + X("fish01", MI_FISHSTALL01) \ + X("fishstall02", MI_FISHSTALL02) \ + X("fishstall03", MI_FISHSTALL03) \ + X("fishstall04", MI_FISHSTALL04) \ + X("taxisign", MI_TAXISIGN) \ + X("phonesign", MI_PHONESIGN) \ + X("noparkingsign1", MI_NOPARKINGSIGN1) \ + X("bussign1", MI_BUSSIGN1) \ + X("roadworkbarrier1", MI_ROADWORKBARRIER1) \ + X("dump1", MI_DUMP1) \ + X("trafficcone", MI_TRAFFICCONE) \ + X("newsstand1", MI_NEWSSTAND) \ + X("postbox1", MI_POSTBOX1) \ + X("bin1", MI_BIN) \ + X("wastebin", MI_WASTEBIN) \ + X("phonebooth1", MI_PHONEBOOTH1) \ + X("parkingmeter", MI_PARKINGMETER) \ + X("trafficlight1", MI_TRAFFICLIGHTS) \ + X("lamppost1", MI_SINGLESTREETLIGHTS1) \ + X("lamppost2", MI_SINGLESTREETLIGHTS2) \ + X("lamppost3", MI_SINGLESTREETLIGHTS3) \ + X("doublestreetlght1", MI_DOUBLESTREETLIGHTS) \ + X("rd_Road2A10", MI_ROADSFORROADBLOCKSSTART) \ + X("rd_Road1A30", MI_ROADSFORROADBLOCKSEND) \ + X("veg_tree1", MI_TREE1) \ + X("veg_tree3", MI_TREE2) \ + X("veg_treea1", MI_TREE3) \ + X("veg_treenew01", MI_TREE4) \ + X("veg_treenew05", MI_TREE5) \ + X("veg_treeb1", MI_TREE6) \ + X("veg_treenew10", MI_TREE7) \ + X("veg_treea3", MI_TREE8) \ + X("veg_treenew09", MI_TREE9) \ + X("veg_treenew08", MI_TREE10) \ + X("veg_treenew03", MI_TREE11) \ + X("veg_treenew16", MI_TREE12) \ + X("veg_treenew17", MI_TREE13) \ + X("veg_treenew06", MI_TREE14) \ + X("doc_crane_cab", MODELID_CRANE_1) \ + X("cranetopb", MODELID_CRANE_2) \ + X("cranetopa", MODELID_CRANE_3) \ + X("package1", MI_COLLECTABLE1) \ + X("Money", MI_MONEY) \ + X("barrel1", MI_CARMINE) \ + X("oddjgaragdoor", MI_GARAGEDOOR1) \ + X("bombdoor", MI_GARAGEDOOR2) \ + X("door_bombshop", MI_GARAGEDOOR3) \ + X("vheistlocdoor", MI_GARAGEDOOR4) \ + X("door2_garage", MI_GARAGEDOOR5) \ + X("ind_slidedoor", MI_GARAGEDOOR6) \ + X("bankjobdoor", MI_GARAGEDOOR7) \ + X("door_jmsgrage", MI_GARAGEDOOR9) \ + X("jamesgrge_kb", MI_GARAGEDOOR10) \ + X("door_sfehousegrge", MI_GARAGEDOOR11) \ + X("shedgaragedoor", MI_GARAGEDOOR12) \ + X("door4_garage", MI_GARAGEDOOR13) \ + X("door_col_compnd_01", MI_GARAGEDOOR14) \ + X("door_col_compnd_02", MI_GARAGEDOOR15) \ + X("door_col_compnd_03", MI_GARAGEDOOR16) \ + X("door_col_compnd_04", MI_GARAGEDOOR17) \ + X("door_col_compnd_05", MI_GARAGEDOOR18) \ + X("impex_door", MI_GARAGEDOOR19) \ + X("SalvGarage", MI_GARAGEDOOR20) \ + X("door3_garage", MI_GARAGEDOOR21) \ + X("leveldoor2", MI_GARAGEDOOR22) \ + X("double_garage_dr", MI_GARAGEDOOR23) \ + X("amcogaragedoor", MI_GARAGEDOOR24) \ + X("towergaragedoor1", MI_GARAGEDOOR25) \ + X("towergaragedoor2", MI_GARAGEDOOR26) \ + X("towergaragedoor3", MI_GARAGEDOOR27) \ + X("plysve_gragedoor", MI_GARAGEDOOR28) \ + X("impexpsubgrgdoor", MI_GARAGEDOOR29) \ + X("Sub_sprayshopdoor", MI_GARAGEDOOR30) \ + X("ind_plyrwoor", MI_GARAGEDOOR31) \ + X("8ballsuburbandoor", MI_GARAGEDOOR32) \ + X("barrel2", MI_NAUTICALMINE) \ + X("crushercrush", MI_CRUSHERBODY) \ + X("crushertop", MI_CRUSHERLID) \ + X("donkeymag", MI_DONKEYMAG) \ + X("bullion", MI_BULLION) \ + X("floatpackge1", MI_FLOATPACKAGE1) \ + X("briefcase", MI_BRIEFCASE) \ + X("chinabanner1", MI_CHINABANNER1) \ + X("chinabanner2", MI_CHINABANNER2) \ + X("chinabanner3", MI_CHINABANNER3) \ + X("chinabanner4", MI_CHINABANNER4) \ + X("iten_chinatown5", MI_CHINABANNER5) \ + X("iten_chinatown7", MI_CHINABANNER6) \ + X("iten_chinatown3", MI_CHINABANNER7) \ + X("iten_chinatown2", MI_CHINABANNER8) \ + X("iten_chinatown4", MI_CHINABANNER9) \ + X("iten_washline01", MI_CHINABANNER10) \ + X("iten_washline02", MI_CHINABANNER11) \ + X("iten_washline03", MI_CHINABANNER12) \ + X("chinalanterns", MI_CHINALANTERN) \ + X("glassfx1", MI_GLASS1) \ + X("glassfx2", MI_GLASS2) \ + X("glassfx3", MI_GLASS3) \ + X("glassfx4", MI_GLASS4) \ + X("glassfx55", MI_GLASS5) \ + X("glassfxsub1", MI_GLASS6) \ + X("glassfxsub2", MI_GLASS7) \ + X("glassfx_composh", MI_GLASS8) \ + X("bridge_liftsec", MI_BRIDGELIFT) \ + X("bridge_liftweight", MI_BRIDGEWEIGHT) \ + X("subbridge_lift", MI_BRIDGEROADSEGMENT) \ + X("barrel4", MI_EXPLODINGBARREL) \ + X("flagsitaly", MI_ITALYBANNER1) \ + X("adrenaline", MI_PICKUP_ADRENALINE) \ + X("bodyarmour", MI_PICKUP_BODYARMOUR) \ + X("info", MI_PICKUP_INFO) \ + X("health", MI_PICKUP_HEALTH) \ + X("bonus", MI_PICKUP_BONUS) \ + X("bribe", MI_PICKUP_BRIBE) \ + X("killfrenzy", MI_PICKUP_KILLFRENZY) \ + X("camerapickup", MI_PICKUP_CAMERA) \ + X("bollardlight", MI_BOLLARDLIGHT) \ + X("magnet", MI_MAGNET) \ + X("streetlamp1", MI_STREETLAMP1) \ + X("streetlamp2", MI_STREETLAMP2) \ + X("railtrax_lo4b", MI_RAILTRACKS) \ + X("bar_barrier10", MI_FENCE) \ + X("bar_barrier12", MI_FENCE2) \ + X("petrolpump", MI_PETROLPUMP) \ + X("bodycast", MI_BODYCAST) \ + X("backdoor", MI_BACKDOOR) \ + X("coffee", MI_COFFEE) \ + X("bouy", MI_BUOY) \ + X("parktable1", MI_PARKTABLE) \ + X("sbwy_tunl_start", MI_SUBWAY1) \ + X("sbwy_tunl_bit", MI_SUBWAY2) \ + X("sbwy_tunl_bend", MI_SUBWAY3) \ + X("sbwy_tunl_cstm6", MI_SUBWAY4) \ + X("sbwy_tunl_cstm7", MI_SUBWAY5) \ + X("sbwy_tunl_cstm8", MI_SUBWAY6) \ + X("sbwy_tunl_cstm10", MI_SUBWAY7) \ + X("sbwy_tunl_cstm9", MI_SUBWAY8) \ + X("sbwy_tunl_cstm11", MI_SUBWAY9) \ + X("sbwy_tunl_cstm1", MI_SUBWAY10) \ + X("sbwy_tunl_cstm2", MI_SUBWAY11) \ + X("sbwy_tunl_cstm4", MI_SUBWAY12) \ + X("sbwy_tunl_cstm3", MI_SUBWAY13) \ + X("sbwy_tunl_cstm5", MI_SUBWAY14) \ + X("subplatform_n2", MI_SUBWAY15) \ + X("suby_tunl_start", MI_SUBWAY16) \ + X("sbwy_tunl_start2", MI_SUBWAY17) \ + X("indy_tunl_start", MI_SUBWAY18) \ + X("indsubway03", MI_SUBPLATFORM_IND) \ + X("comerside_subway", MI_SUBPLATFORM_COMS) \ + X("subplatform", MI_SUBPLATFORM_COMS2) \ + X("subplatform_n", MI_SUBPLATFORM_COMN) \ + X("Otherside_subway", MI_SUBPLATFORM_SUB) \ + X("subplatform_sub", MI_SUBPLATFORM_SUB2) \ + X("files", MI_FILES) + +#define X(name, var) extern int16 var; + MODELINDICES +#undef X + +// and some hardcoded ones +// expand as needed +enum +{ + MI_PLAYER = 0, + MI_COP, + MI_SWAT, + MI_FBI, + MI_ARMY, + MI_MEDIC, + MI_FIREMAN, + MI_MALE01, + MI_TAXI_D, + MI_PIMP, + MI_GANG01, + MI_GANG02, + MI_GANG03, + MI_GANG04, + MI_GANG05, + MI_GANG06, + MI_GANG07, + MI_GANG08, + MI_GANG09, + MI_GANG10, + MI_GANG11, + MI_GANG12, + MI_GANG13, + MI_GANG14, + MI_CRIMINAL01, + MI_CRIMINAL02, + MI_SPECIAL01, + MI_SPECIAL02, + MI_SPECIAL03, + MI_SPECIAL04, + MI_MALE02, + MI_MALE03, + MI_FATMALE01, + MI_FATMALE02, + MI_FEMALE01, + MI_FEMALE02, + MI_FEMALE03, + MI_FATFEMALE01, + MI_FATFEMALE02, + MI_PROSTITUTE, + MI_PROSTITUTE2, + MI_P_MAN1, + MI_P_MAN2, + MI_P_WOM1, + MI_P_WOM2, + MI_CT_MAN1, + MI_CT_MAN2, + MI_CT_WOM1, + MI_CT_WOM2, + MI_LI_MAN1, + MI_LI_MAN2, + MI_LI_WOM1, + MI_LI_WOM2, + MI_DOCKER1, + MI_DOCKER2, + MI_SCUM_MAN, + MI_SCUM_WOM, + MI_WORKER1, + MI_WORKER2, + MI_B_MAN1, + MI_B_MAN2, + MI_B_MAN3, + MI_B_WOM1, + MI_B_WOM2, + MI_B_WOM3, + MI_MOD_MAN, + MI_MOD_WOM, + MI_ST_MAN, + MI_ST_WOM, + MI_FAN_MAN1, + MI_FAN_MAN2, + MI_FAN_WOM, + MI_HOS_MAN, + MI_HOS_WOM, + MI_CONST1, + MI_CONST2, + MI_SHOPPER1, + MI_SHOPPER2, + MI_SHOPPER3, + MI_STUD_MAN, + MI_STUD_WOM, + MI_CAS_MAN, + MI_CAS_WOM, + MI_BUSKER1, + MI_BUSKER2, + MI_BUSKER3, + MI_BUSKER4, + // three more peds possible + + MI_LAST_PED = 89, + MI_FIRST_VEHICLE, + + MI_LANDSTAL = MI_FIRST_VEHICLE, + MI_IDAHO, + MI_STINGER, + MI_LINERUN, + MI_PEREN, + MI_SENTINEL, + MI_PATRIOT, + MI_FIRETRUCK, + MI_TRASH, + MI_STRETCH, + MI_MANANA, + MI_INFERNUS, + MI_BLISTA, + MI_PONY, + MI_MULE, + MI_CHEETAH, + MI_AMBULAN, + MI_FBICAR, + MI_MOONBEAM, + MI_ESPERANT, + MI_TAXI, + MI_KURUMA, + MI_BOBCAT, + MI_MRWHOOP, + MI_BFINJECT, + MI_CORPSE, + MI_POLICE, + MI_ENFORCER, + MI_SECURICA, + MI_BANSHEE, + MI_PREDATOR, + MI_BUS, + MI_RHINO, + MI_BARRACKS, + MI_TRAIN, + MI_CHOPPER, + MI_DODO, + MI_COACH, + MI_CABBIE, + MI_STALLION, + MI_RUMPO, + MI_RCBANDIT, + MI_BELLYUP, + MI_MRWONGS, + MI_MAFIA, + MI_YARDIE, + MI_YAKUZA, + MI_DIABLOS, + MI_COLUMB , + MI_HOODS, + MI_AIRTRAIN, + MI_DEADDODO, + MI_SPEEDER, + MI_REEFER, + MI_PANLANT, + MI_FLATBED, + MI_YANKEE, + MI_ESCAPE, + MI_BORGNINE, + MI_TOYZ, + MI_GHOST, + + // leftovers on PC + MI_MIAMI_RCBARON = 154, + MI_MIAMI_RCRAIDER = 155, + MI_MIAMI_SPARROW = 159, + + MI_GRENADE = 170, + MI_AK47, + MI_BASEBALL_BAT, + MI_COLT, + MI_MOLOTOV, + MI_ROCKETLAUNCHER, + MI_SHOTGUN, + MI_SNIPER, + MI_UZI, + MI_MISSILE, + MI_M16, + MI_FLAMETHROWER, + MI_BOMB, + MI_FINGERS, + + MI_CUTOBJ01 = 185, + MI_CUTOBJ02, + MI_CUTOBJ03, + MI_CUTOBJ04, + MI_CUTOBJ05, + + MI_CAR_DOOR = 190, + MI_CAR_BUMPER, + MI_CAR_PANEL, + MI_CAR_BONNET, + MI_CAR_BOOT, + MI_CAR_WHEEL, + MI_BODYPARTA, + MI_BODYPARTB, + + MI_AIRTRAIN_VLO = 198, + MI_LOPOLYGUY, + + NUM_DEFAULT_MODELS +}; + +enum{ + NUM_OF_SPECIAL_CHARS = 4, + NUM_OF_CUTSCENE_OBJECTS = 5 +}; + +void InitModelIndices(void); +void MatchModelString(const char *name, int16 id); +void TestModelIndices(void); + +inline bool +IsGlass(int16 id) +{ + return id == MI_GLASS1 || + id == MI_GLASS2 || + id == MI_GLASS3 || + id == MI_GLASS4 || + id == MI_GLASS5 || + id == MI_GLASS6 || + id == MI_GLASS7 || + id == MI_GLASS8; +} + +inline bool +IsStreetLight(int16 id) +{ + return id == MI_TRAFFICLIGHTS || + id == MI_SINGLESTREETLIGHTS1 || + id == MI_SINGLESTREETLIGHTS2 || + id == MI_SINGLESTREETLIGHTS3 || + id == MI_DOUBLESTREETLIGHTS; +} + +inline bool +IsBodyPart(int16 id) +{ + return id == MI_BODYPARTA || id == MI_BODYPARTB; +} + +// This is bad and should perhaps not be used +inline bool +IsBoatModel(int16 id) +{ + return id == MI_PREDATOR || + id == MI_REEFER || + id == MI_SPEEDER || + id == MI_GHOST; +} + +inline bool +IsPedModel(int16 id) +{ + return id >= MI_PLAYER && id <= MI_LAST_PED; +} + +inline bool +IsTreeModel(int16 id) +{ + return id == MI_TREE1 || + id == MI_TREE2 || + id == MI_TREE3 || + id == MI_TREE4 || + id == MI_TREE5 || + id == MI_TREE6 || + id == MI_TREE7 || + id == MI_TREE8 || + id == MI_TREE9 || + id == MI_TREE10 || + id == MI_TREE11 || + id == MI_TREE12 || + id == MI_TREE13 || + id == MI_TREE14; +} + +inline bool +IsBannerModel(int16 id) +{ + return id == MI_CHINABANNER1 || + id == MI_CHINABANNER2 || + id == MI_CHINABANNER3 || + id == MI_CHINABANNER4 || + id == MI_CHINABANNER5 || + id == MI_CHINABANNER6 || + id == MI_CHINABANNER7 || + id == MI_CHINABANNER8 || + id == MI_CHINABANNER9 || + id == MI_CHINABANNER10 || + id == MI_CHINABANNER11 || + id == MI_CHINABANNER12 || + id == MI_ITALYBANNER1 || + id == MI_CHINALANTERN; +} +inline bool +IsPickupModel(int16 id) +{ + return id == MI_GRENADE || + id == MI_AK47 || + id == MI_BASEBALL_BAT || + id == MI_COLT || + id == MI_MOLOTOV || + id == MI_ROCKETLAUNCHER || + id == MI_SHOTGUN || + id == MI_SNIPER || + id == MI_UZI || + id == MI_M16 || + id == MI_FLAMETHROWER || + id == MI_PICKUP_ADRENALINE || + id == MI_PICKUP_BODYARMOUR || + id == MI_PICKUP_INFO || + id == MI_PICKUP_HEALTH || + id == MI_PICKUP_BONUS || + id == MI_PICKUP_BRIBE || + id == MI_PICKUP_KILLFRENZY || + id == MI_PICKUP_CAMERA; +} + +inline bool +IsPolicePedModel(int16 id) +{ + return id == MI_COP || + id == MI_SWAT || + id == MI_FBI || + id == MI_ARMY; +} + +inline bool +IsPoliceVehicleModel(int16 id) +{ + return id == MI_CHOPPER || + id == MI_PREDATOR || + id == MI_POLICE || + id == MI_ENFORCER; +} + +inline bool +IsExplosiveThingModel(int16 id) +{ + return id == MI_EXPLODINGBARREL || + id == MI_PETROLPUMP; +} + +inline bool +IsFence(int16 id) +{ + return id == MI_FENCE || + id == MI_FENCE2; +} \ No newline at end of file diff --git a/src/modelinfo/ModelInfo.cpp b/src/modelinfo/ModelInfo.cpp new file mode 100644 index 0000000..7aa5fc8 --- /dev/null +++ b/src/modelinfo/ModelInfo.cpp @@ -0,0 +1,254 @@ +#include "common.h" + +#include "General.h" +#include "TempColModels.h" +#include "ModelIndices.h" +#include "ModelInfo.h" +#include "Frontend.h" + +CBaseModelInfo *CModelInfo::ms_modelInfoPtrs[MODELINFOSIZE]; + +CStore CModelInfo::ms_simpleModelStore; +CStore CModelInfo::ms_mloModelStore; +CStore CModelInfo::ms_mloInstanceStore; +CStore CModelInfo::ms_timeModelStore; +CStore CModelInfo::ms_clumpModelStore; +CStore CModelInfo::ms_pedModelStore; +CStore CModelInfo::ms_vehicleModelStore; +CStore CModelInfo::ms_xtraCompsModelStore; +CStore CModelInfo::ms_2dEffectStore; + +void +CModelInfo::Initialise(void) +{ + int i; + CSimpleModelInfo *m; + + for(i = 0; i < MODELINFOSIZE; i++) + ms_modelInfoPtrs[i] = nil; + ms_2dEffectStore.Clear(); + ms_mloInstanceStore.Clear(); + ms_xtraCompsModelStore.Clear(); + ms_simpleModelStore.Clear(); + ms_timeModelStore.Clear(); + ms_mloModelStore.Clear(); + ms_clumpModelStore.Clear(); + ms_pedModelStore.Clear(); + ms_vehicleModelStore.Clear(); + + m = AddSimpleModel(MI_CAR_DOOR); + m->SetColModel(&CTempColModels::ms_colModelDoor1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_CAR_BUMPER); + m->SetColModel(&CTempColModels::ms_colModelBumper1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_CAR_PANEL); + m->SetColModel(&CTempColModels::ms_colModelPanel1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_CAR_BONNET); + m->SetColModel(&CTempColModels::ms_colModelBonnet1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_CAR_BOOT); + m->SetColModel(&CTempColModels::ms_colModelBoot1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_CAR_WHEEL); + m->SetColModel(&CTempColModels::ms_colModelWheel1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_BODYPARTA); + m->SetColModel(&CTempColModels::ms_colModelBodyPart1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_BODYPARTB); + m->SetColModel(&CTempColModels::ms_colModelBodyPart2); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; +} + +void +CModelInfo::ShutDown(void) +{ + int i; + for(i = 0; i < ms_simpleModelStore.allocPtr; i++) + ms_simpleModelStore.store[i].Shutdown(); + for(i = 0; i < ms_mloInstanceStore.allocPtr; i++) + ms_mloInstanceStore.store[i].Shutdown(); + for(i = 0; i < ms_timeModelStore.allocPtr; i++) + ms_timeModelStore.store[i].Shutdown(); + for(i = 0; i < ms_clumpModelStore.allocPtr; i++) + ms_clumpModelStore.store[i].Shutdown(); + for(i = 0; i < ms_vehicleModelStore.allocPtr; i++) + ms_vehicleModelStore.store[i].Shutdown(); + for(i = 0; i < ms_pedModelStore.allocPtr; i++) + ms_pedModelStore.store[i].Shutdown(); + for(i = 0; i < ms_xtraCompsModelStore.allocPtr; i++) + ms_xtraCompsModelStore.store[i].Shutdown(); + for(i = 0; i < ms_mloInstanceStore.allocPtr; i++) + ms_mloInstanceStore.store[i].Shutdown(); + for(i = 0; i < ms_2dEffectStore.allocPtr; i++) + ms_2dEffectStore.store[i].Shutdown(); + + ms_2dEffectStore.Clear(); + ms_simpleModelStore.Clear(); + ms_mloInstanceStore.Clear(); + ms_mloModelStore.Clear(); + ms_xtraCompsModelStore.Clear(); + ms_timeModelStore.Clear(); + ms_pedModelStore.Clear(); + ms_clumpModelStore.Clear(); + ms_vehicleModelStore.Clear(); +} + +CSimpleModelInfo* +CModelInfo::AddSimpleModel(int id) +{ + CSimpleModelInfo *modelinfo; + modelinfo = CModelInfo::ms_simpleModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->Init(); + return modelinfo; +} + +CMloModelInfo * +CModelInfo::AddMloModel(int id) +{ + CMloModelInfo *modelinfo; + modelinfo = CModelInfo::ms_mloModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + modelinfo->firstInstance = 0; + modelinfo->lastInstance = 0; + return modelinfo; +} + +CTimeModelInfo* +CModelInfo::AddTimeModel(int id) +{ + CTimeModelInfo *modelinfo; + modelinfo = CModelInfo::ms_timeModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->Init(); + return modelinfo; +} + +CClumpModelInfo* +CModelInfo::AddClumpModel(int id) +{ + CClumpModelInfo *modelinfo; + modelinfo = CModelInfo::ms_clumpModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + return modelinfo; +} + +CPedModelInfo* +CModelInfo::AddPedModel(int id) +{ + CPedModelInfo *modelinfo; + modelinfo = CModelInfo::ms_pedModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + return modelinfo; +} + +CVehicleModelInfo* +CModelInfo::AddVehicleModel(int id) +{ + CVehicleModelInfo *modelinfo; + modelinfo = CModelInfo::ms_vehicleModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + modelinfo->m_vehicleType = -1; + modelinfo->m_wheelId = -1; + modelinfo->m_materials1[0] = nil; + modelinfo->m_materials2[0] = nil; + modelinfo->m_bikeSteerAngle = 999.99f; + return modelinfo; +} + +CBaseModelInfo* +CModelInfo::GetModelInfo(const char *name, int *id) +{ + CBaseModelInfo *modelinfo; + for(int i = 0; i < MODELINFOSIZE; i++){ + modelinfo = CModelInfo::ms_modelInfoPtrs[i]; + if(modelinfo && !CGeneral::faststricmp(modelinfo->GetModelName(), name)){ + if(id) + *id = i; + return modelinfo; + } + } + return nil; +} + +bool +CModelInfo::IsBoatModel(int32 id) +{ + return GetModelInfo(id)->GetModelType() == MITYPE_VEHICLE && + ((CVehicleModelInfo*)GetModelInfo(id))->m_vehicleType == VEHICLE_TYPE_BOAT; +} + +bool +CModelInfo::IsBikeModel(int32 id) +{ + return GetModelInfo(id)->GetModelType() == MITYPE_VEHICLE && + ((CVehicleModelInfo*)GetModelInfo(id))->m_vehicleType == VEHICLE_TYPE_BIKE; +} + +void +CModelInfo::RemoveColModelsFromOtherLevels(eLevelName level) +{ + ISLAND_LOADING_IS(LOW) + { + int i; + CBaseModelInfo *mi; + CColModel *colmodel; + + for (i = 0; i < MODELINFOSIZE; i++) { + mi = GetModelInfo(i); + if (mi) { + colmodel = mi->GetColModel(); + if (colmodel && colmodel->level != LEVEL_GENERIC && colmodel->level != level) + colmodel->RemoveCollisionVolumes(); + } + } + } +} + +void +CModelInfo::ConstructMloClumps() +{ + for (int i = 0; i < ms_mloModelStore.allocPtr; i++) + ms_mloModelStore.store[i].ConstructClump(); +} + +void +CModelInfo::ReInit2dEffects() +{ + ms_2dEffectStore.Clear(); + + for (int i = 0; i < MODELINFOSIZE; i++) { + if (ms_modelInfoPtrs[i]) + ms_modelInfoPtrs[i]->Init2dEffects(); + } +} diff --git a/src/modelinfo/ModelInfo.h b/src/modelinfo/ModelInfo.h new file mode 100644 index 0000000..4d24e78 --- /dev/null +++ b/src/modelinfo/ModelInfo.h @@ -0,0 +1,55 @@ +#pragma once + +#include "2dEffect.h" +#include "BaseModelInfo.h" +#include "SimpleModelInfo.h" +#include "MloModelInfo.h" +#include "TimeModelInfo.h" +#include "ClumpModelInfo.h" +#include "PedModelInfo.h" +#include "VehicleModelInfo.h" +#include "XtraCompsModelInfo.h" +#include "Instance.h" +#include "Game.h" + +class CModelInfo +{ + static CBaseModelInfo *ms_modelInfoPtrs[MODELINFOSIZE]; + static CStore ms_simpleModelStore; + static CStore ms_mloModelStore; + static CStore ms_mloInstanceStore; + static CStore ms_timeModelStore; + static CStore ms_clumpModelStore; + static CStore ms_pedModelStore; + static CStore ms_vehicleModelStore; + static CStore ms_2dEffectStore; + static CStore ms_xtraCompsModelStore; + +public: + static void Initialise(void); + static void ShutDown(void); + + static CSimpleModelInfo *AddSimpleModel(int id); + static CMloModelInfo *AddMloModel(int id); + static CTimeModelInfo *AddTimeModel(int id); + static CClumpModelInfo *AddClumpModel(int id); + static CPedModelInfo *AddPedModel(int id); + static CVehicleModelInfo *AddVehicleModel(int id); + + static CStore &Get2dEffectStore(void) { return ms_2dEffectStore; } + static CStore &GetMloInstanceStore(void) { return ms_mloInstanceStore; } + + static CBaseModelInfo *GetModelInfo(const char *name, int *id); + static CBaseModelInfo *GetModelInfo(int id){ + return ms_modelInfoPtrs[id]; + } + static CColModel *GetColModel(int id){ + return ms_modelInfoPtrs[id]->GetColModel(); + } + + static bool IsBoatModel(int32 id); + static bool IsBikeModel(int32 id); + static void RemoveColModelsFromOtherLevels(eLevelName level); + static void ConstructMloClumps(); + static void ReInit2dEffects(); +}; diff --git a/src/modelinfo/PedModelInfo.cpp b/src/modelinfo/PedModelInfo.cpp new file mode 100644 index 0000000..2cce48f --- /dev/null +++ b/src/modelinfo/PedModelInfo.cpp @@ -0,0 +1,378 @@ +#include "common.h" + +#include "RwHelper.h" +#include "General.h" +#include "Bones.h" +#include "SurfaceTable.h" +#include "Ped.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "ModelInfo.h" +#include "custompipes.h" + +void +CPedModelInfo::DeleteRwObject(void) +{ + if(m_hitColModel) + delete m_hitColModel; + m_hitColModel = nil; +#ifdef PED_SKIN + RwFrame *frame; + if(m_head){ + frame = RpAtomicGetFrame(m_head); + RpAtomicDestroy(m_head); + RwFrameDestroy(frame); + m_head = nil; + } + if(m_lhand){ + frame = RpAtomicGetFrame(m_lhand); + RpAtomicDestroy(m_lhand); + RwFrameDestroy(frame); + m_lhand = nil; + } + if(m_rhand){ + frame = RpAtomicGetFrame(m_rhand); + RpAtomicDestroy(m_rhand); + RwFrameDestroy(frame); + m_rhand = nil; + } +#endif + CClumpModelInfo::DeleteRwObject(); // PC calls this first +} + +RwObjectNameIdAssocation CPedModelInfo::m_pPedIds[PED_NODE_MAX] = { + { "Smid", PED_MID, 0, }, // that is strange... + { "Shead", PED_HEAD, 0, }, + { "Supperarml", PED_UPPERARML, 0, }, + { "Supperarmr", PED_UPPERARMR, 0, }, + { "SLhand", PED_HANDL, 0, }, + { "SRhand", PED_HANDR, 0, }, + { "Supperlegl", PED_UPPERLEGL, 0, }, + { "Supperlegr", PED_UPPERLEGR, 0, }, + { "Sfootl", PED_FOOTL, 0, }, + { "Sfootr", PED_FOOTR, 0, }, + { "Slowerlegr", PED_LOWERLEGR, 0, }, + { nil, 0, 0, }, +}; + +#ifdef PED_SKIN +struct LimbCBarg +{ + CPedModelInfo *mi; + RpClump *clump; + int32 frameIDs[3]; +}; + +RpAtomic* +CPedModelInfo::findLimbsCb(RpAtomic *atomic, void *data) +{ + LimbCBarg *limbs = (LimbCBarg*)data; + RwFrame *frame = RpAtomicGetFrame(atomic); + const char *name = GetFrameNodeName(frame); + if(CGeneral::faststricmp(name, "Shead01") == 0){ + limbs->frameIDs[0] = RpHAnimFrameGetID(frame); + limbs->mi->m_head = atomic; + RpClumpRemoveAtomic(limbs->clump, atomic); + RwFrameRemoveChild(frame); + }else if(CGeneral::faststricmp(name, "SLhand01") == 0){ + limbs->frameIDs[1] = RpHAnimFrameGetID(frame); + limbs->mi->m_lhand = atomic; + RpClumpRemoveAtomic(limbs->clump, atomic); + RwFrameRemoveChild(frame); + }else if(CGeneral::faststricmp(name, "SRhand01") == 0){ + limbs->frameIDs[2] = RpHAnimFrameGetID(frame); + limbs->mi->m_rhand = atomic; + RpClumpRemoveAtomic(limbs->clump, atomic); + RwFrameRemoveChild(frame); + } + return atomic; +} +#endif + +void +CPedModelInfo::SetClump(RpClump *clump) +{ +#ifdef EXTENDED_PIPELINES + CustomPipes::AttachRimPipe(clump); +#endif +#ifdef PED_SKIN + // CB has to be set here before atomics are detached from clump + if(strcmp(GetModelName(), "player") == 0) + RpClumpForAllAtomics(clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); + if(IsClumpSkinned(clump)){ + LimbCBarg limbs = { this, clump, { 0, 0, 0 } }; + RpClumpForAllAtomics(clump, findLimbsCb, &limbs); + } + CClumpModelInfo::SetClump(clump); + SetFrameIds(m_pPedIds); + if(m_hitColModel == nil && !IsClumpSkinned(clump)) + CreateHitColModel(); + // And again because CClumpModelInfo resets it + if(strcmp(GetModelName(), "player") == 0) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); + else if(IsClumpSkinned(clump)) + // skinned peds have no low detail version, so they don't have the right render Cb + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedCB); +#else + CClumpModelInfo::SetClump(clump); + SetFrameIds(m_pPedIds); + if(m_hitColModel == nil) + CreateHitColModel(); + if(strcmp(GetModelName(), "player") == 0) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); +#endif +} + +RpAtomic* +CountAtomicsCB(RpAtomic *atomic, void *data) +{ + (*(int32*)data)++; + return atomic; +} + +RpAtomic* +GetAtomicListCB(RpAtomic *atomic, void *data) +{ + **(RpAtomic***)data = atomic; + (*(RpAtomic***)data)++; + return atomic; +} + +RwFrame* +FindPedFrameFromNameCB(RwFrame *frame, void *data) +{ + RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data; + + if(CGeneral::faststricmp(GetFrameNodeName(frame)+1, assoc->name+1)){ + RwFrameForAllChildren(frame, FindPedFrameFromNameCB, assoc); + return assoc->frame ? nil : frame; + }else{ + assoc->frame = frame; + return nil; + } +} + +void +CPedModelInfo::SetLowDetailClump(RpClump *lodclump) +{ + RpAtomic *atomics[16]; + RpAtomic **pAtm; + int32 numAtm, numLodAtm; + int i; + RwObjectNameAssociation assoc; + + numAtm = 0; + numLodAtm = 0; + RpClumpForAllAtomics(m_clump, CountAtomicsCB, &numAtm); // actually unused + RpClumpForAllAtomics(lodclump, CountAtomicsCB, &numLodAtm); + + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedHiDetailCB); + RpClumpForAllAtomics(lodclump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedLowDetailCB); + + pAtm = atomics; + RpClumpForAllAtomics(lodclump, GetAtomicListCB, &pAtm); + + for(i = 0; i < numLodAtm; i++){ + assoc.name = GetFrameNodeName(RpAtomicGetFrame(atomics[i])); + assoc.frame = nil; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), FindPedFrameFromNameCB, &assoc); + if(assoc.frame){ + RpAtomicSetFrame(atomics[i], assoc.frame); + RpClumpRemoveAtomic(lodclump, atomics[i]); + RpClumpAddAtomic(m_clump, atomics[i]); + } + } +} + +struct ColNodeInfo +{ + Const char *name; + int pedNode; + int pieceType; + float x, z; + float radius; +}; + +#define NUMPEDINFONODES 8 +ColNodeInfo m_pColNodeInfos[NUMPEDINFONODES] = { + { nil, PED_HEAD, PEDPIECE_HEAD, 0.0f, 0.05f, 0.2f }, + { "Storso", 0, PEDPIECE_TORSO, 0.0f, 0.15f, 0.2f }, + { "Storso", 0, PEDPIECE_TORSO, 0.0f, -0.05f, 0.3f }, + { nil, PED_MID, PEDPIECE_MID, 0.0f, -0.07f, 0.3f }, + { nil, PED_UPPERARML, PEDPIECE_LEFTARM, 0.07f, -0.1f, 0.2f }, + { nil, PED_UPPERARMR, PEDPIECE_RIGHTARM, -0.07f, -0.1f, 0.2f }, + { "Slowerlegl", 0, PEDPIECE_LEFTLEG, 0.0f, 0.07f, 0.25f }, + { nil, PED_LOWERLEGR, PEDPIECE_RIGHTLEG, 0.0f, 0.07f, 0.25f }, +}; + +RwObject* +FindHeadRadiusCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + *(float*)data = RpAtomicGetBoundingSphere(atomic)->radius; + return nil; +} + +void +CPedModelInfo::CreateHitColModel(void) +{ + RwObjectNameAssociation nameAssoc; + RwObjectIdAssociation idAssoc; + RwFrame *nodeFrame; + CColModel *colmodel = new CColModel; + CColSphere *spheres = (CColSphere*)RwMalloc(NUMPEDINFONODES*sizeof(CColSphere)); + RwFrame *root = RpClumpGetFrame(m_clump); + RwMatrix *mat = RwMatrixCreate(); + for(int i = 0; i < NUMPEDINFONODES; i++){ + nodeFrame = nil; + if(m_pColNodeInfos[i].name){ + nameAssoc.name = m_pColNodeInfos[i].name; + nameAssoc.frame = nil; + RwFrameForAllChildren(root, FindFrameFromNameCB, &nameAssoc); + nodeFrame = nameAssoc.frame; + }else{ + idAssoc.id = m_pColNodeInfos[i].pedNode; + idAssoc.frame = nil; + RwFrameForAllChildren(root, FindFrameFromIdCB, &idAssoc); + nodeFrame = idAssoc.frame; + } + if(nodeFrame){ + float radius = m_pColNodeInfos[i].radius; + if(m_pColNodeInfos[i].pieceType == PEDPIECE_HEAD) + RwFrameForAllObjects(nodeFrame, FindHeadRadiusCB, &radius); + RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEREPLACE); + const char *name = GetFrameNodeName(nodeFrame); + for(nodeFrame = RwFrameGetParent(nodeFrame); + nodeFrame; + nodeFrame = RwFrameGetParent(nodeFrame)){ + name = GetFrameNodeName(nodeFrame); + RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEPOSTCONCAT); + if(RwFrameGetParent(nodeFrame) == root) + break; + } + spheres[i].center = mat->pos + CVector(m_pColNodeInfos[i].x, 0.0f, m_pColNodeInfos[i].z); + spheres[i].radius = radius; + spheres[i].surface = SURFACE_PED; + spheres[i].piece = m_pColNodeInfos[i].pieceType; + } + } + RwMatrixDestroy(mat); + colmodel->spheres = spheres; + colmodel->numSpheres = NUMPEDINFONODES; + colmodel->boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f), SURFACE_DEFAULT, 0); + colmodel->boundingBox.Set(CVector(-0.5f, -0.5f, -1.2f), CVector(0.5f, 0.5f, 1.2f), SURFACE_DEFAULT, 0); + colmodel->level = LEVEL_GENERIC; + m_hitColModel = colmodel; +} + +CColModel* +CPedModelInfo::AnimatePedColModel(CColModel* colmodel, RwFrame* frame) +{ + RwObjectNameAssociation nameAssoc; + RwObjectIdAssociation idAssoc; + RwMatrix* mat = RwMatrixCreate(); + CColSphere* spheres = colmodel->spheres; + + for (int i = 0; i < NUMPEDINFONODES; i++) { + RwFrame* f = nil; + if (m_pColNodeInfos[i].name) { + nameAssoc.name = m_pColNodeInfos[i].name; + nameAssoc.frame = nil; + RwFrameForAllChildren(frame, FindFrameFromNameCB, &nameAssoc); + f = nameAssoc.frame; + } + else { + idAssoc.id = m_pColNodeInfos[i].pedNode; + idAssoc.frame = nil; + RwFrameForAllChildren(frame, FindFrameFromIdCB, &idAssoc); + f = idAssoc.frame; + } + if (f) { + RwMatrixCopy(mat, RwFrameGetMatrix(f)); + + for (f = RwFrameGetParent(f); f; f = RwFrameGetParent(f)) { + RwMatrixTransform(mat, RwFrameGetMatrix(f), rwCOMBINEPOSTCONCAT); + if (RwFrameGetParent(f) == frame) + break; + } + + spheres[i].center = mat->pos + CVector(m_pColNodeInfos[i].x, 0.0f, m_pColNodeInfos[i].z); + } + } + + return colmodel; +} + +#ifdef PED_SKIN +void +CPedModelInfo::CreateHitColModelSkinned(RpClump *clump) +{ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump); + CColModel *colmodel = new CColModel; + CColSphere *spheres = (CColSphere*)RwMalloc(NUMPEDINFONODES*sizeof(CColSphere)); + RwFrame *root = RpClumpGetFrame(m_clump); + RwMatrix *invmat = RwMatrixCreate(); + RwMatrix *mat = RwMatrixCreate(); + RwMatrixInvert(invmat, RwFrameGetMatrix(RpClumpGetFrame(clump))); + + for(int i = 0; i < NUMPEDINFONODES; i++){ + *mat = *invmat; + // From LCS. Otherwise gives FPE +#ifdef FIX_BUGS + spheres[i].center = CVector(0.0f, 0.0f, 0.0f); +#else + int id = ConvertPedNode2BoneTag(m_pColNodeInfos[i].pedNode); // this is wrong, wtf R* ??? + int idx = RpHAnimIDGetIndex(hier, id); + + // This doesn't really work as the positions are not initialized yet + RwMatrixTransform(mat, &RpHAnimHierarchyGetMatrixArray(hier)[idx], rwCOMBINEPRECONCAT); + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + RwV3dTransformPoints(&pos, &pos, 1, mat); + + spheres[i].center = pos + CVector(m_pColNodeInfos[i].x, 0.0f, m_pColNodeInfos[i].z); +#endif + spheres[i].radius = m_pColNodeInfos[i].radius; + spheres[i].surface = SURFACE_PED; + spheres[i].piece = m_pColNodeInfos[i].pieceType; + } + RwMatrixDestroy(invmat); + RwMatrixDestroy(mat); + colmodel->spheres = spheres; + colmodel->numSpheres = NUMPEDINFONODES; + colmodel->boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f), SURFACE_DEFAULT, 0); + colmodel->boundingBox.Set(CVector(-0.5f, -0.5f, -1.2f), CVector(0.5f, 0.5f, 1.2f), SURFACE_DEFAULT, 0); + colmodel->level = LEVEL_GENERIC; + m_hitColModel = colmodel; +} + +CColModel* +CPedModelInfo::AnimatePedColModelSkinned(RpClump *clump) +{ + if(m_hitColModel == nil){ + CreateHitColModelSkinned(clump); + return m_hitColModel; + } + RwMatrix *invmat, *mat; + CColSphere *spheres = m_hitColModel->spheres; + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump); + invmat = RwMatrixCreate(); + mat = RwMatrixCreate(); + RwMatrixInvert(invmat, RwFrameGetMatrix(RpClumpGetFrame(clump))); + + for(int i = 0; i < NUMPEDINFONODES; i++){ + *mat = *invmat; + int id = ConvertPedNode2BoneTag(m_pColNodeInfos[i].pedNode); + int idx = RpHAnimIDGetIndex(hier, id); + + RwMatrixTransform(mat, &RpHAnimHierarchyGetMatrixArray(hier)[idx], rwCOMBINEPRECONCAT); + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + RwV3dTransformPoints(&pos, &pos, 1, mat); + + spheres[i].center = pos + CVector(m_pColNodeInfos[i].x, 0.0f, m_pColNodeInfos[i].z); + } + RwMatrixDestroy(invmat); + RwMatrixDestroy(mat); + return m_hitColModel; +} + +#endif diff --git a/src/modelinfo/PedModelInfo.h b/src/modelinfo/PedModelInfo.h new file mode 100644 index 0000000..26ab3c3 --- /dev/null +++ b/src/modelinfo/PedModelInfo.h @@ -0,0 +1,67 @@ +#pragma once + +#include "ClumpModelInfo.h" +#include "ColModel.h" +#include "PedType.h" + +enum PedNode { + PED_TORSO, + PED_MID, // Smid on PS2/PC, Storso on mobile/xbox + PED_HEAD, + PED_UPPERARML, + PED_UPPERARMR, + PED_HANDL, + PED_HANDR, + PED_UPPERLEGL, + PED_UPPERLEGR, + PED_FOOTL, + PED_FOOTR, + PED_LOWERLEGR, + PED_NODE_MAX// Not valid: PED_LOWERLEGL +}; + +class CPedModelInfo : public CClumpModelInfo +{ +public: + uint32 m_animGroup; + ePedType m_pedType; + ePedStats m_pedStatType; + uint32 m_carsCanDrive; + CColModel *m_hitColModel; +#ifdef PED_SKIN + RpAtomic *m_head; + RpAtomic *m_lhand; + RpAtomic *m_rhand; +#endif + + static RwObjectNameIdAssocation m_pPedIds[PED_NODE_MAX]; + + CPedModelInfo(void) : CClumpModelInfo(MITYPE_PED) { + m_hitColModel = nil; +#ifdef PED_SKIN + m_head = nil; + m_lhand = nil; + m_rhand = nil; +#endif + } + ~CPedModelInfo(void) { delete m_hitColModel; } + void DeleteRwObject(void); + void SetClump(RpClump *); + + void SetLowDetailClump(RpClump*); + void CreateHitColModel(void); + void CreateHitColModelSkinned(RpClump *clump); + CColModel *GetHitColModel(void) { return m_hitColModel; } + static CColModel *AnimatePedColModel(CColModel* colmodel, RwFrame* frame); + CColModel *AnimatePedColModelSkinned(RpClump *clump); + +#ifdef PED_SKIN + static RpAtomic *findLimbsCb(RpAtomic *atomic, void *data); + RpAtomic *getHead(void) { return m_head; } + RpAtomic *getLeftHand(void) { return m_lhand; } + RpAtomic *getRightHand(void) { return m_rhand; } +#endif +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CPedModelInfo, 0x48); +#endif \ No newline at end of file diff --git a/src/modelinfo/SimpleModelInfo.cpp b/src/modelinfo/SimpleModelInfo.cpp new file mode 100644 index 0000000..9fc0dd6 --- /dev/null +++ b/src/modelinfo/SimpleModelInfo.cpp @@ -0,0 +1,173 @@ +#include "common.h" + +#include "General.h" +#include "Camera.h" +#include "Renderer.h" +#include "ModelInfo.h" +#include "custompipes.h" + +void +CSimpleModelInfo::DeleteRwObject(void) +{ + int i; + RwFrame *f; + for(i = 0; i < m_numAtomics; i++) + if(m_atomics[i]){ + f = RpAtomicGetFrame(m_atomics[i]); + RpAtomicDestroy(m_atomics[i]); + RwFrameDestroy(f); + m_atomics[i] = nil; + RemoveTexDictionaryRef(); + } +} + +RwObject* +CSimpleModelInfo::CreateInstance(void) +{ + RpAtomic *atomic; + if(m_atomics[0] == nil) + return nil; + atomic = RpAtomicClone(m_atomics[0]); + RpAtomicSetFrame(atomic, RwFrameCreate()); + return (RwObject*)atomic; +} + +RwObject* +CSimpleModelInfo::CreateInstance(RwMatrix *matrix) +{ + RpAtomic *atomic; + RwFrame *frame; + + if(m_atomics[0] == nil) + return nil; + atomic = RpAtomicClone(m_atomics[0]); + frame = RwFrameCreate(); + *RwFrameGetMatrix(frame) = *matrix; + RpAtomicSetFrame(atomic, frame); + return (RwObject*)atomic; +} + +void +CSimpleModelInfo::Init(void) +{ + m_atomics[0] = nil; + m_atomics[1] = nil; + m_atomics[2] = nil; + m_numAtomics = 0; + m_firstDamaged = 0; + m_normalCull = 0; + m_isDamaged = 0; + m_isBigBuilding = 0; + m_noFade = 0; + m_drawLast = 0; + m_additive = 0; + m_isSubway = 0; + m_ignoreLight = 0; + m_noZwrite = 0; +} + +void +CSimpleModelInfo::SetAtomic(int n, RpAtomic *atomic) +{ + AddTexDictionaryRef(); + m_atomics[n] = atomic; + if(m_ignoreLight){ + RpGeometry *geo = RpAtomicGetGeometry(atomic); + RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) & ~rpGEOMETRYLIGHT); + } + +#ifdef EXTENDED_PIPELINES + CustomPipes::AttachWorldPipe(atomic); +#endif +} + +void +CSimpleModelInfo::SetLodDistances(float *dist) +{ + m_lodDistances[0] = dist[0]; + m_lodDistances[1] = dist[1]; + m_lodDistances[2] = dist[2]; +} + +void +CSimpleModelInfo::IncreaseAlpha(void) +{ + if(m_alpha >= 0xEF) + m_alpha = 0xFF; + else + m_alpha += 0x10; +} + +float +CSimpleModelInfo::GetLodDistance(int i) +{ + return m_lodDistances[i] * TheCamera.LODDistMultiplier; +} + +float +CSimpleModelInfo::GetNearDistance(void) +{ + return m_lodDistances[2] * TheCamera.LODDistMultiplier; +} + +float +CSimpleModelInfo::GetLargestLodDistance(void) +{ + float d; + if(m_firstDamaged == 0 || m_isDamaged) + d = m_lodDistances[m_numAtomics-1]; + else + d = m_lodDistances[m_firstDamaged-1]; + return d * TheCamera.LODDistMultiplier; +} + +RpAtomic* +CSimpleModelInfo::GetAtomicFromDistance(float dist) +{ + int i; + i = 0; + if(m_isDamaged) + i = m_firstDamaged; + for(; i < m_numAtomics; i++) + if(dist < m_lodDistances[i] * TheCamera.LODDistMultiplier) + return m_atomics[i]; + return nil; +} + +void +CSimpleModelInfo::FindRelatedModel(void) +{ + int i; + CBaseModelInfo *mi; + for(i = 0; i < MODELINFOSIZE; i++){ + mi = CModelInfo::GetModelInfo(i); + if(mi && mi != this && + !CGeneral::faststrcmp(GetModelName()+3, mi->GetModelName()+3)){ + assert(mi->IsSimple()); + this->SetRelatedModel((CSimpleModelInfo*)mi); + return; + } + } +} + +void +CSimpleModelInfo::SetupBigBuilding(void) +{ + CSimpleModelInfo *related; + if(m_lodDistances[0] > LOD_DISTANCE && GetRelatedModel() == nil){ + m_isBigBuilding = 1; + FindRelatedModel(); + related = GetRelatedModel(); + if(related) + m_lodDistances[2] = related->GetLargestLodDistance()/TheCamera.LODDistMultiplier; + else +#ifdef FIX_BUGS + if(toupper(m_name[0]) == 'L' && toupper(m_name[1]) == 'O' && toupper(m_name[2]) == 'D') + m_lodDistances[2] = 100.0f; + else + m_lodDistances[2] = 0.0f; +#else + m_lodDistances[2] = 100.0f; +#endif + } +} diff --git a/src/modelinfo/SimpleModelInfo.h b/src/modelinfo/SimpleModelInfo.h new file mode 100644 index 0000000..94e55a2 --- /dev/null +++ b/src/modelinfo/SimpleModelInfo.h @@ -0,0 +1,65 @@ +#pragma once + +#include "BaseModelInfo.h" + +class CSimpleModelInfo : public CBaseModelInfo +{ +public: + // atomics[2] is often a pointer to the non-LOD modelinfo + RpAtomic *m_atomics[3]; + // m_lodDistances[2] holds the near distance for LODs + float m_lodDistances[3]; + uint8 m_numAtomics; + uint8 m_alpha; + /* // For reference, PS2 has: + uint8 m_firstDamaged; + uint8 m_normalCull : 1; + uint8 m_isDamaged : 1; + uint8 m_isBigBuilding : 1; + uint8 m_noFade : 1; + uint8 m_drawLast : 1; + uint8 m_additive : 1; + uint8 m_isSubway : 1; + uint8 m_ignoreLight : 1; + // m_noZwrite is missing because not needed + */ + uint16 m_firstDamaged : 2; // 0: no damage model + // 1: 1 and 2 are damage models + // 2: 2 is damage model + uint16 m_normalCull : 1; + uint16 m_isDamaged : 1; + uint16 m_isBigBuilding : 1; + uint16 m_noFade : 1; + uint16 m_drawLast : 1; + uint16 m_additive : 1; + uint16 m_isSubway : 1; + uint16 m_ignoreLight : 1; + uint16 m_noZwrite : 1; + + CSimpleModelInfo(void) : CBaseModelInfo(MITYPE_SIMPLE) {} + CSimpleModelInfo(ModelInfoType id) : CBaseModelInfo(id) {} + ~CSimpleModelInfo() {} + void DeleteRwObject(void); + RwObject *CreateInstance(void); + RwObject *CreateInstance(RwMatrix *); + RwObject *GetRwObject(void) { return (RwObject*)m_atomics[0]; } + + void Init(void); + void IncreaseAlpha(void); + void SetAtomic(int n, RpAtomic *atomic); + void SetLodDistances(float *dist); + float GetLodDistance(int i); + float GetNearDistance(void); + float GetLargestLodDistance(void); + RpAtomic *GetAtomicFromDistance(float dist); + void FindRelatedModel(void); + void SetupBigBuilding(void); + + void SetNumAtomics(int n) { m_numAtomics = n; } + CSimpleModelInfo *GetRelatedModel(void){ + return (CSimpleModelInfo*)m_atomics[2]; } + void SetRelatedModel(CSimpleModelInfo *m){ + m_atomics[2] = (RpAtomic*)m; } +}; + +VALIDATE_SIZE(CSimpleModelInfo, 0x4C); diff --git a/src/modelinfo/TimeModelInfo.cpp b/src/modelinfo/TimeModelInfo.cpp new file mode 100644 index 0000000..0db5fb7 --- /dev/null +++ b/src/modelinfo/TimeModelInfo.cpp @@ -0,0 +1,33 @@ +#include "common.h" + +#include "Camera.h" +#include "ModelInfo.h" +#include "General.h" + +CTimeModelInfo* +CTimeModelInfo::FindOtherTimeModel(void) +{ + char name[40]; + char *p; + int i; + + strcpy(name, GetModelName()); + // change _nt to _dy + if(p = strstr(name, "_nt")) + strncpy(p, "_dy", 4); + // change _dy to _nt + else if(p = strstr(name, "_dy")) + strncpy(p, "_nt", 4); + else + return nil; + + for(i = 0; i < MODELINFOSIZE; i++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(i); + if (mi && mi->GetModelType() == MITYPE_TIME && + !CGeneral::faststrncmp(name, mi->GetModelName(), MAX_MODEL_NAME)){ + m_otherTimeModelID = i; + return (CTimeModelInfo*)mi; + } + } + return nil; +} diff --git a/src/modelinfo/TimeModelInfo.h b/src/modelinfo/TimeModelInfo.h new file mode 100644 index 0000000..73b6ab2 --- /dev/null +++ b/src/modelinfo/TimeModelInfo.h @@ -0,0 +1,21 @@ +#pragma once + +#include "SimpleModelInfo.h" + +class CTimeModelInfo : public CSimpleModelInfo +{ + int32 m_timeOn; + int32 m_timeOff; + int32 m_otherTimeModelID; +public: + CTimeModelInfo(void) : CSimpleModelInfo(MITYPE_TIME) { m_otherTimeModelID = -1; } + + int32 GetTimeOn(void) { return m_timeOn; } + int32 GetTimeOff(void) { return m_timeOff; } + void SetTimes(int32 on, int32 off) { m_timeOn = on; m_timeOff = off; } + int32 GetOtherTimeModel(void) { return m_otherTimeModelID; } + void SetOtherTimeModel(int32 other) { m_otherTimeModelID = other; } + CTimeModelInfo *FindOtherTimeModel(void); +}; + +VALIDATE_SIZE(CTimeModelInfo, 0x58); diff --git a/src/modelinfo/VehicleModelInfo.cpp b/src/modelinfo/VehicleModelInfo.cpp new file mode 100644 index 0000000..685b6ef --- /dev/null +++ b/src/modelinfo/VehicleModelInfo.cpp @@ -0,0 +1,1121 @@ +#include "common.h" +#include + +#include "RwHelper.h" +#include "General.h" +#include "NodeName.h" +#include "TxdStore.h" +#include "Weather.h" +#include "HandlingMgr.h" +#include "VisibilityPlugins.h" +#include "FileMgr.h" +#include "World.h" +#include "Vehicle.h" +#include "Automobile.h" +#include "Boat.h" +#include "Train.h" +#include "Plane.h" +#include "Heli.h" +#include "Bike.h" +#include "ModelIndices.h" +#include "ModelInfo.h" +#include "custompipes.h" + +int8 CVehicleModelInfo::ms_compsToUse[2] = { -2, -2 }; +int8 CVehicleModelInfo::ms_compsUsed[2]; +RwTexture *CVehicleModelInfo::ms_pEnvironmentMaps[NUM_VEHICLE_ENVMAPS]; +RwRGBA CVehicleModelInfo::ms_vehicleColourTable[256]; +RwTexture *CVehicleModelInfo::ms_colourTextureTable[256]; + +RwTexture *gpWhiteTexture; +RwFrame *pMatFxIdentityFrame; + +enum { + VEHICLE_FLAG_COLLAPSE = 0x2, + VEHICLE_FLAG_ADD_WHEEL = 0x4, + VEHICLE_FLAG_POS = 0x8, + VEHICLE_FLAG_DOOR = 0x10, + VEHICLE_FLAG_LEFT = 0x20, + VEHICLE_FLAG_RIGHT = 0x40, + VEHICLE_FLAG_FRONT = 0x80, + VEHICLE_FLAG_REAR = 0x100, + VEHICLE_FLAG_COMP = 0x200, + VEHICLE_FLAG_DRAWLAST = 0x400, + VEHICLE_FLAG_WINDSCREEN = 0x800, + VEHICLE_FLAG_ANGLECULL = 0x1000, + VEHICLE_FLAG_REARDOOR = 0x2000, + VEHICLE_FLAG_FRONTDOOR = 0x4000, +}; + +RwObjectNameIdAssocation carIds[] = { + { "wheel_rf_dummy", CAR_WHEEL_RF, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_rm_dummy", CAR_WHEEL_RM, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_rb_dummy", CAR_WHEEL_RB, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_lf_dummy", CAR_WHEEL_LF, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_lm_dummy", CAR_WHEEL_LM, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_lb_dummy", CAR_WHEEL_LB, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL }, + { "bump_front_dummy", CAR_BUMP_FRONT, VEHICLE_FLAG_FRONT | VEHICLE_FLAG_COLLAPSE }, + { "bonnet_dummy", CAR_BONNET, VEHICLE_FLAG_COLLAPSE }, + { "wing_rf_dummy", CAR_WING_RF, VEHICLE_FLAG_COLLAPSE }, + { "wing_rr_dummy", CAR_WING_RR, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_COLLAPSE }, + { "door_rf_dummy", CAR_DOOR_RF, VEHICLE_FLAG_FRONTDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "door_rr_dummy", CAR_DOOR_RR, VEHICLE_FLAG_REARDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_REAR | VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "wing_lf_dummy", CAR_WING_LF, VEHICLE_FLAG_COLLAPSE }, + { "wing_lr_dummy", CAR_WING_LR, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE }, + { "door_lf_dummy", CAR_DOOR_LF, VEHICLE_FLAG_FRONTDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_LEFT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "door_lr_dummy", CAR_DOOR_LR, VEHICLE_FLAG_REARDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_REAR | VEHICLE_FLAG_LEFT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "boot_dummy", CAR_BOOT, VEHICLE_FLAG_REAR | VEHICLE_FLAG_COLLAPSE }, + { "bump_rear_dummy", CAR_BUMP_REAR, VEHICLE_FLAG_REAR | VEHICLE_FLAG_COLLAPSE }, + { "windscreen_dummy", CAR_WINDSCREEN, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_FRONT | VEHICLE_FLAG_COLLAPSE }, + + { "ped_frontseat", CAR_POS_FRONTSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_backseat", CAR_POS_BACKSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "headlights", CAR_POS_HEADLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "taillights", CAR_POS_TAILLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "exhaust", CAR_POS_EXHAUST, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "extra1", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra2", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra3", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra4", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra5", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra6", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation boatIds[] = { + { "boat_moving_hi", BOAT_MOVING, VEHICLE_FLAG_COLLAPSE }, + { "boat_rudder_hi", BOAT_RUDDER, VEHICLE_FLAG_COLLAPSE }, + { "windscreen", BOAT_WINDSCREEN, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_COLLAPSE }, + { "ped_frontseat", BOAT_POS_FRONTSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation trainIds[] = { + { "door_lhs_dummy", TRAIN_DOOR_LHS, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE }, + { "door_rhs_dummy", TRAIN_DOOR_RHS, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE }, + { "light_front", TRAIN_POS_LIGHT_FRONT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "light_rear", TRAIN_POS_LIGHT_REAR, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_left_entry", TRAIN_POS_LEFT_ENTRY, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_mid_entry", TRAIN_POS_MID_ENTRY, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_right_entry", TRAIN_POS_RIGHT_ENTRY, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation heliIds[] = { + { "chassis_dummy", HELI_CHASSIS, VEHICLE_FLAG_COLLAPSE }, + { "toprotor", HELI_TOPROTOR, 0 }, + { "backrotor", HELI_BACKROTOR, 0 }, + { "tail", HELI_TAIL, 0 }, + { "topknot", HELI_TOPKNOT, 0 }, + { "skid_left", HELI_SKID_LEFT, 0 }, + { "skid_right", HELI_SKID_RIGHT, 0 }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation planeIds[] = { + { "wheel_front_dummy", PLANE_WHEEL_FRONT, 0 }, + { "wheel_rear_dummy", PLANE_WHEEL_READ, 0 }, + { "light_tailplane", PLANE_POS_LIGHT_TAIL, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "light_left", PLANE_POS_LIGHT_LEFT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "light_right", PLANE_POS_LIGHT_RIGHT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation bikeIds[] = { + { "chassis_dummy", BIKE_CHASSIS, 0 }, + { "forks_front", BIKE_FORKS_FRONT, 0 }, + { "forks_rear", BIKE_FORKS_REAR, 0 }, + { "wheel_front", BIKE_WHEEL_FRONT, 0 }, + { "wheel_rear", BIKE_WHEEL_REAR, 0 }, + { "mudguard", BIKE_MUDGUARD, 0 }, + { "ped_frontseat", CAR_POS_FRONTSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "headlights", CAR_POS_HEADLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "taillights", CAR_POS_TAILLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "exhaust", CAR_POS_EXHAUST, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "extra1", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra2", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra3", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra4", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra5", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra6", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation *CVehicleModelInfo::ms_vehicleDescs[] = { + carIds, + boatIds, + trainIds, + heliIds, + planeIds, + bikeIds +}; + + +CVehicleModelInfo::CVehicleModelInfo(void) + : CClumpModelInfo(MITYPE_VEHICLE) +{ + int32 i; + for(i = 0; i < NUM_VEHICLE_POSITIONS; i++){ + m_positions[i].x = 0.0f; + m_positions[i].y = 0.0f; + m_positions[i].z = 0.0f; + } + m_numColours = 0; +} + +void +CVehicleModelInfo::DeleteRwObject(void) +{ + int32 i; + RwFrame *f; + + for(i = 0; i < m_numComps; i++){ + f = RpAtomicGetFrame(m_comps[i]); + RpAtomicDestroy(m_comps[i]); + RwFrameDestroy(f); + } + m_numComps = 0; + CClumpModelInfo::DeleteRwObject(); +} + +RwObject* +CVehicleModelInfo::CreateInstance(void) +{ + RpClump *clump; + RpAtomic *atomic; + RwFrame *clumpframe, *f; + int32 comp1, comp2; + + clump = (RpClump*)CClumpModelInfo::CreateInstance(); + if(m_numComps != 0){ + clumpframe = RpClumpGetFrame(clump); + + comp1 = ChooseComponent(); + if(comp1 != -1){ + atomic = RpAtomicClone(m_comps[comp1]); + f = RwFrameCreate(); + RwFrameTransform(f, + RwFrameGetMatrix(RpAtomicGetFrame(m_comps[comp1])), + rwCOMBINEREPLACE); + RpAtomicSetFrame(atomic, f); + RpClumpAddAtomic(clump, atomic); + RwFrameAddChild(clumpframe, f); + } + ms_compsUsed[0] = comp1; + + comp2 = ChooseSecondComponent(); + if(comp2 != -1){ + atomic = RpAtomicClone(m_comps[comp2]); + f = RwFrameCreate(); + RwFrameTransform(f, + RwFrameGetMatrix(RpAtomicGetFrame(m_comps[comp2])), + rwCOMBINEREPLACE); + RpAtomicSetFrame(atomic, f); + RpClumpAddAtomic(clump, atomic); + RwFrameAddChild(clumpframe, f); + } + ms_compsUsed[1] = comp2; + }else{ + ms_compsUsed[0] = -1; + ms_compsUsed[1] = -1; + } + return (RwObject*)clump; +} + +void +CVehicleModelInfo::SetClump(RpClump *clump) +{ + CClumpModelInfo::SetClump(clump); + SetAtomicRenderCallbacks(); + SetFrameIds(ms_vehicleDescs[m_vehicleType]); + PreprocessHierarchy(); + FindEditableMaterialList(); + m_envMap = nil; + SetEnvironmentMap(); +} + +RwFrame* +CVehicleModelInfo::CollapseFramesCB(RwFrame *frame, void *data) +{ + RwFrameForAllChildren(frame, CollapseFramesCB, data); + RwFrameForAllObjects(frame, MoveObjectsCB, data); + RwFrameDestroy(frame); + return frame; +} + +RwObject* +CVehicleModelInfo::MoveObjectsCB(RwObject *object, void *data) +{ + RpAtomicSetFrame((RpAtomic*)object, (RwFrame*)data); + return object; +} + +RpAtomic* +CVehicleModelInfo::HideDamagedAtomicCB(RpAtomic *atomic, void *data) +{ + if(strstr(GetFrameNodeName(RpAtomicGetFrame(atomic)), "_dam")){ + RpAtomicSetFlags(atomic, 0); + CVisibilityPlugins::SetAtomicFlag(atomic, ATOMIC_FLAG_DAM); + }else if(strstr(GetFrameNodeName(RpAtomicGetFrame(atomic)), "_ok")) + CVisibilityPlugins::SetAtomicFlag(atomic, ATOMIC_FLAG_OK); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::HideAllComponentsAtomicCB(RpAtomic *atomic, void *data) +{ + if(CVisibilityPlugins::GetAtomicId(atomic) & (uintptr)data) + RpAtomicSetFlags(atomic, 0); + else + RpAtomicSetFlags(atomic, rpATOMICRENDER); + return atomic; +} + +RpMaterial* +CVehicleModelInfo::HasAlphaMaterialCB(RpMaterial *material, void *data) +{ + if(RpMaterialGetColor(material)->alpha != 0xFF){ + *(bool*)data = true; + return nil; + } + return material; +} + + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB(RpAtomic *atomic, void *data) +{ + RpClump *clump; + char *name; + bool alpha; + + clump = (RpClump*)data; + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + alpha = false; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha); + if(strstr(name, "_hi") || !CGeneral::faststrncmp(name, "extra", 5)) { + if(alpha || strncmp(name, "windscreen", 10) == 0) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB); + }else if(strstr(name, "_lo")){ + RpClumpRemoveAtomic(clump, atomic); + RpAtomicDestroy(atomic); + return atomic; // BUG: not done by gta + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_BigVehicle(RpAtomic *atomic, void *data) +{ + char *name; + bool alpha; + + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + alpha = false; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha); + if(strstr(name, "_hi") || !CGeneral::faststrncmp(name, "extra", 5)) { + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle); + }else if(strstr(name, "_lo")){ + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle); + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_Train(RpAtomic *atomic, void *data) +{ + char *name; + bool alpha; + + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + alpha = false; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha); + if(strstr(name, "_hi")){ + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderTrainHiDetailAlphaCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderTrainHiDetailCB); + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_Boat(RpAtomic *atomic, void *data) +{ + RpClump *clump; + char *name; + + clump = (RpClump*)data; + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + if(strcmp(name, "boat_hi") == 0 || !CGeneral::faststrncmp(name, "extra", 5)) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB_Boat); + else if(strstr(name, "_hi")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB); + else if(strstr(name, "_lo")){ + RpClumpRemoveAtomic(clump, atomic); + RpAtomicDestroy(atomic); + return atomic; // BUG: not done by gta + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_Heli(RpAtomic *atomic, void *data) +{ + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + return atomic; +} + +void +CVehicleModelInfo::SetAtomicRenderCallbacks(void) +{ + switch(m_vehicleType){ + case VEHICLE_TYPE_TRAIN: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Train, nil); + break; + case VEHICLE_TYPE_HELI: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Heli, nil); + break; + case VEHICLE_TYPE_PLANE: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_BigVehicle, nil); + break; + case VEHICLE_TYPE_BOAT: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Boat, m_clump); + break; + default: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, m_clump); + break; + } +} + +RwObject* +CVehicleModelInfo::SetAtomicFlagCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + assert(RwObjectGetType(object) == rpATOMIC); + CVisibilityPlugins::SetAtomicFlag(atomic, (uintptr)data); + return object; +} + +RwObject* +CVehicleModelInfo::ClearAtomicFlagCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + assert(RwObjectGetType(object) == rpATOMIC); + CVisibilityPlugins::ClearAtomicFlag(atomic, (uintptr)data); + return object; +} + +RwObject* +GetOkAndDamagedAtomicCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + if(CVisibilityPlugins::GetAtomicId(atomic) & ATOMIC_FLAG_OK) + ((RpAtomic**)data)[0] = atomic; + else if(CVisibilityPlugins::GetAtomicId(atomic) & ATOMIC_FLAG_DAM) + ((RpAtomic**)data)[1] = atomic; + return object; +} + +void +CVehicleModelInfo::PreprocessHierarchy(void) +{ + int32 i; + RwObjectNameIdAssocation *desc; + RwFrame *f; + RpAtomic *atomic; + RwV3d *rwvec; + + desc = ms_vehicleDescs[m_vehicleType]; + m_numDoors = 0; + m_numComps = 0; + + for(i = 0; desc[i].name; i++){ + RwObjectNameAssociation assoc; + + if((desc[i].flags & (VEHICLE_FLAG_COMP|VEHICLE_FLAG_POS)) == 0) + continue; + assoc.frame = nil; + assoc.name = desc[i].name; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), + FindFrameFromNameWithoutIdCB, &assoc); + if(assoc.frame == nil) + continue; + + if(desc[i].flags & VEHICLE_FLAG_DOOR) + m_numDoors++; + + if(desc[i].flags & VEHICLE_FLAG_POS){ + f = assoc.frame; + rwvec = &m_positions[desc[i].hierId]; + *rwvec = *RwMatrixGetPos(RwFrameGetMatrix(f)); + for(f = RwFrameGetParent(f); f; f = RwFrameGetParent(f)) + RwV3dTransformPoints(rwvec, rwvec, 1, RwFrameGetMatrix(f)); + RwFrameDestroy(assoc.frame); + }else{ + atomic = (RpAtomic*)GetFirstObject(assoc.frame); + RpClumpRemoveAtomic(m_clump, atomic); + RwFrameRemoveChild(assoc.frame); + SetVehicleComponentFlags(assoc.frame, desc[i].flags); + m_comps[m_numComps++] = atomic; + } + } + + for(i = 0; desc[i].name; i++){ + RwObjectIdAssociation assoc; + + if(desc[i].flags & (VEHICLE_FLAG_COMP|VEHICLE_FLAG_POS)) + continue; + assoc.frame = nil; + assoc.id = desc[i].hierId; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), + FindFrameFromIdCB, &assoc); + if(assoc.frame == nil) + continue; + + if(desc[i].flags & VEHICLE_FLAG_DOOR) + m_numDoors++; + + if(desc[i].flags & VEHICLE_FLAG_COLLAPSE){ + RpAtomic *okdam[2] = { nil, nil }; + RwFrameForAllChildren(assoc.frame, CollapseFramesCB, assoc.frame); + RwFrameUpdateObjects(assoc.frame); + RwFrameForAllObjects(assoc.frame, GetOkAndDamagedAtomicCB, okdam); + if(okdam[0] && okdam[1]) + RpAtomicSetRenderCallBack(okdam[1], RpAtomicGetRenderCallBack(okdam[0])); + } + + SetVehicleComponentFlags(assoc.frame, desc[i].flags); + + if(desc[i].flags & VEHICLE_FLAG_ADD_WHEEL){ + if(m_wheelId == -1) + RwFrameDestroy(assoc.frame); + else{ + RwV3d scale; + atomic = (RpAtomic*)CModelInfo::GetModelInfo(m_wheelId)->CreateInstance(); + RwFrameDestroy(RpAtomicGetFrame(atomic)); + RpAtomicSetFrame(atomic, assoc.frame); + RpClumpAddAtomic(m_clump, atomic); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, + CVisibilityPlugins::RenderWheelAtomicCB); + scale.x = m_wheelScale; + scale.y = m_wheelScale; + scale.z = m_wheelScale; + RwFrameScale(assoc.frame, &scale, rwCOMBINEPRECONCAT); + } + } + } +} + +void +CVehicleModelInfo::SetVehicleComponentFlags(RwFrame *frame, uint32 flags) +{ + tHandlingData *handling; + + handling = mod_HandlingManager.GetHandlingData((tVehicleType)m_handlingId); + +#define SETFLAGS(f) RwFrameForAllObjects(frame, SetAtomicFlagCB, (void*)(f)) + + if(flags & VEHICLE_FLAG_WINDSCREEN){ + if(this == CModelInfo::GetModelInfo(MI_RHINO)) + return; + SETFLAGS(ATOMIC_FLAG_WINDSCREEN); + } + + if(flags & VEHICLE_FLAG_ANGLECULL) + SETFLAGS(ATOMIC_FLAG_ANGLECULL); + + if(flags & VEHICLE_FLAG_FRONT) + SETFLAGS(ATOMIC_FLAG_FRONT); + else if(flags & VEHICLE_FLAG_REAR && (handling->Flags & HANDLING_IS_VAN || (flags & (VEHICLE_FLAG_LEFT|VEHICLE_FLAG_RIGHT)) == 0)) + SETFLAGS(ATOMIC_FLAG_REAR); + else if(flags & VEHICLE_FLAG_LEFT) + SETFLAGS(ATOMIC_FLAG_LEFT); + else if(flags & VEHICLE_FLAG_RIGHT) + SETFLAGS(ATOMIC_FLAG_RIGHT); + + if(flags & VEHICLE_FLAG_REARDOOR) + SETFLAGS(ATOMIC_FLAG_REARDOOR); + else if(flags & VEHICLE_FLAG_FRONTDOOR) + SETFLAGS(ATOMIC_FLAG_FRONTDOOR); + + if(flags & VEHICLE_FLAG_DRAWLAST) + SETFLAGS(ATOMIC_FLAG_DRAWLAST); +} + +#define COMPRULE_RULE(comprule) (((comprule) >> 12) & 0xF) +#define COMPRULE_COMPS(comprule) ((comprule) & 0xFFF) +#define COMPRULE_COMPN(comps, n) (((comps) >> 4*(n)) & 0xF) +#define COMPRULE2_RULE(comprule) (((comprule) >> (12+16)) & 0xF) +#define COMPRULE2_COMPS(comprule) ((comprule >> 16) & 0xFFF) +#define COMPRULE2_COMPN(comps, n) (((comps >> 16) >> 4*(n)) & 0xF) + +bool +IsValidCompRule(int rule) +{ + if(rule == 2) + return CWeather::OldWeatherType == WEATHER_RAINY || + CWeather::NewWeatherType == WEATHER_RAINY; + return true; +} + +int32 +CountCompsInRule(int comps) +{ + int32 n; + for(n = 0; comps != 0; comps >>= 4) + if((comps & 0xF) != 0xF) + n++; + return n; +} + +int32 +ChooseComponent(int32 rule, int32 comps) +{ + int32 n; + switch(rule){ + // identical cases.... + case 1: + n = CGeneral::GetRandomNumberInRange(0, CountCompsInRule(comps)); + return COMPRULE_COMPN(comps, n); + case 2: + // only valid in rain + n = CGeneral::GetRandomNumberInRange(0, CountCompsInRule(comps)); + return COMPRULE_COMPN(comps, n); + } + return -1; +} + +int32 +GetListOfComponentsNotUsedByRules(uint32 comprules, int32 numComps, int32 *comps) +{ + int32 i, n; + int32 unused[6] = { 0, 1, 2, 3, 4, 5 }; + + // first comprule + if(COMPRULE_RULE(comprules) && IsValidCompRule(COMPRULE_RULE(comprules))) + for(i = 0; i < 3; i++){ + n = COMPRULE_COMPN(comprules, i); + if(n != 0xF) + unused[n] = 0xF; + } + // second comprule + comprules >>= 16; + if(COMPRULE_RULE(comprules) && IsValidCompRule(COMPRULE_RULE(comprules))) + for(i = 0; i < 3; i++){ + n = COMPRULE_COMPN(comprules, i); + if(n != 0xF) + unused[n] = 0xF; + } + + n = 0; + for(i = 0; i < numComps; i++) + if(unused[i] != 0xF) + comps[n++] = unused[i]; + return n; +} + +int32 wheelIds[] = { CAR_WHEEL_LF, CAR_WHEEL_LB, CAR_WHEEL_RF, CAR_WHEEL_RB }; + +void +CVehicleModelInfo::GetWheelPosn(int32 n, CVector &pos) +{ + RwMatrix *m = RwFrameGetMatrix(GetFrameFromId(m_clump, wheelIds[n])); + pos.x = RwMatrixGetPos(m)->x; + pos.y = RwMatrixGetPos(m)->y; + pos.z = RwMatrixGetPos(m)->z; +} + + +int32 +CVehicleModelInfo::ChooseComponent(void) +{ + int32 comp; + int32 comps[8]; + int32 n; + + comp = -1; + if(ms_compsToUse[0] == -2){ + if(COMPRULE_RULE(m_compRules) && IsValidCompRule(COMPRULE_RULE(m_compRules))) + comp = ::ChooseComponent(COMPRULE_RULE(m_compRules), COMPRULE_COMPS(m_compRules)); + else if(CGeneral::GetRandomNumberInRange(0, 3) < 2){ + n = GetListOfComponentsNotUsedByRules(m_compRules, m_numComps, comps); + if(n) + comp = comps[(int)CGeneral::GetRandomNumberInRange(0, n)]; + } + }else{ + comp = ms_compsToUse[0]; + ms_compsToUse[0] = -2; + } + return comp; +} + +int32 +CVehicleModelInfo::ChooseSecondComponent(void) +{ + int32 comp; + int32 comps[8]; + int32 n; + + comp = -1; + if(ms_compsToUse[1] == -2){ + if(COMPRULE2_RULE(m_compRules) && IsValidCompRule(COMPRULE2_RULE(m_compRules))) + comp = ::ChooseComponent(COMPRULE2_RULE(m_compRules), COMPRULE2_COMPS(m_compRules)); + else if(COMPRULE_RULE(m_compRules) && IsValidCompRule(COMPRULE_RULE(m_compRules)) && + CGeneral::GetRandomNumberInRange(0, 3) < 2){ + + n = GetListOfComponentsNotUsedByRules(m_compRules, m_numComps, comps); + if(n) + comp = comps[(int)CGeneral::GetRandomNumberInRange(0, n)]; + } + }else{ + comp = ms_compsToUse[1]; + ms_compsToUse[1] = -2; + } + return comp; +} + +struct editableMatCBData +{ + CVehicleModelInfo *vehicle; + int32 numMats1; + int32 numMats2; +}; + +RpMaterial* +CVehicleModelInfo::GetEditableMaterialListCB(RpMaterial *material, void *data) +{ + RwRGBA white = { 255, 255, 255, 255 }; + const RwRGBA *col; + editableMatCBData *cbdata; + + cbdata = (editableMatCBData*)data; + col = RpMaterialGetColor(material); + if(col->red == 0x3C && col->green == 0xFF && col->blue == 0){ + cbdata->vehicle->m_materials1[cbdata->numMats1++] = material; + RpMaterialSetColor(material, &white); + }else if(col->red == 0xFF && col->green == 0 && col->blue == 0xAF){ + cbdata->vehicle->m_materials2[cbdata->numMats2++] = material; + RpMaterialSetColor(material, &white); + } + return material; +} + +RpAtomic* +CVehicleModelInfo::GetEditableMaterialListCB(RpAtomic *atomic, void *data) +{ + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), GetEditableMaterialListCB, data); + return atomic; +} + +void +CVehicleModelInfo::FindEditableMaterialList(void) +{ + editableMatCBData cbdata; + int32 i; + + cbdata.vehicle = this; + cbdata.numMats1 = 0; + cbdata.numMats2 = 0; + RpClumpForAllAtomics(m_clump, GetEditableMaterialListCB, &cbdata); + for(i = 0; i < m_numComps; i++) + GetEditableMaterialListCB(m_comps[i], &cbdata); + m_materials1[cbdata.numMats1] = nil; + m_materials2[cbdata.numMats2] = nil; + m_currentColour1 = -1; + m_currentColour2 = -1; +} + +void +CVehicleModelInfo::SetVehicleColour(uint8 c1, uint8 c2) +{ + RwRGBA col, *colp; + RwTexture *coltex; + RpMaterial **matp; + + if(c1 != m_currentColour1){ + col = ms_vehicleColourTable[c1]; + coltex = ms_colourTextureTable[c1]; + for(matp = m_materials1; *matp; matp++){ + if(RpMaterialGetTexture(*matp) && RwTextureGetName(RpMaterialGetTexture(*matp))[0] != '@'){ + colp = (RwRGBA*)RpMaterialGetColor(*matp); // get rid of const + colp->red = col.red; + colp->green = col.green; + colp->blue = col.blue; + }else + RpMaterialSetTexture(*matp, coltex); + } + m_currentColour1 = c1; + } + + if(c2 != m_currentColour2){ + col = ms_vehicleColourTable[c2]; + coltex = ms_colourTextureTable[c2]; + for(matp = m_materials2; *matp; matp++){ + if(RpMaterialGetTexture(*matp) && RwTextureGetName(RpMaterialGetTexture(*matp))[0] != '@'){ + colp = (RwRGBA*)RpMaterialGetColor(*matp); // get rid of const + colp->red = col.red; + colp->green = col.green; + colp->blue = col.blue; + }else + RpMaterialSetTexture(*matp, coltex); + } + m_currentColour2 = c2; + } +} + +void +CVehicleModelInfo::ChooseVehicleColour(uint8 &col1, uint8 &col2) +{ + if(m_numColours == 0){ + col1 = 0; + col2 = 0; + }else{ + m_lastColorVariation = (m_lastColorVariation+1) % m_numColours; + col1 = m_colours1[m_lastColorVariation]; + col2 = m_colours2[m_lastColorVariation]; + if(m_numColours > 1){ + CVehicle *veh = FindPlayerVehicle(); + if(veh && CModelInfo::GetModelInfo(veh->GetModelIndex()) == this && + veh->m_currentColour1 == col1 && + veh->m_currentColour2 == col2){ + m_lastColorVariation = (m_lastColorVariation+1) % m_numColours; + col1 = m_colours1[m_lastColorVariation]; + col2 = m_colours2[m_lastColorVariation]; + } + } + } +} + +void +CVehicleModelInfo::AvoidSameVehicleColour(uint8 *col1, uint8 *col2) +{ + int i, n; + + if(m_numColours > 1) + for(i = 0; i < 8; i++){ + if(*col1 != m_lastColour1 || *col2 != m_lastColour2) + break; + n = CGeneral::GetRandomNumberInRange(0, m_numColours); + *col1 = m_colours1[n]; + *col2 = m_colours2[n]; + } + m_lastColour1 = *col1; + m_lastColour2 = *col2; +} + +RwTexture* +CreateCarColourTexture(uint8 r, uint8 g, uint8 b) +{ + RwImage *img; + RwRaster *ras; + RwTexture *tex; + RwUInt8 *pixels; + RwInt32 width, height, depth, format; + + img = RwImageCreate(2, 2, 32); + pixels = (RwUInt8*)RwMalloc(2*2*4); + pixels[0] = r; + pixels[1] = g; + pixels[2] = b; + pixels[3] = 0xFF; + pixels[4] = r; + pixels[5] = g; + pixels[6] = b; + pixels[7] = 0xFF; + pixels[8] = r; + pixels[9] = g; + pixels[10] = b; + pixels[11] = 0xFF; + pixels[12] = r; + pixels[13] = g; + pixels[14] = b; + pixels[15] = 0xFF; + RwImageSetPixels(img, pixels); + RwImageSetStride(img, 8); + RwImageSetPalette(img, nil); + RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &width, &height, &depth, &format); + ras = RwRasterCreate(width, height, depth, format); + RwRasterSetFromImage(ras, img); + RwImageDestroy(img); + RwFree(pixels); + tex = RwTextureCreate(ras); + RwTextureGetName(tex)[0] = '@'; + return tex; +} + +void +CVehicleModelInfo::LoadVehicleColours(void) +{ + int fd; + int i; + char line[1024]; + int start, end; + int section, numCols; + enum { + NONE, + COLOURS, + CARS + }; + int r, g, b; + char name[64]; + int colors[16]; + int n; + + CFileMgr::ChangeDir("\\DATA\\"); + fd = CFileMgr::OpenFile("CARCOLS.DAT", "r"); + CFileMgr::ChangeDir("\\"); + + for(i = 0; i < 256; i++) + ms_colourTextureTable[i] = nil; + + section = 0; + numCols = 0; + while(CFileMgr::ReadLine(fd, line, sizeof(line))){ + // find first valid character in line + for(start = 0; ; start++) + if(line[start] > ' ' || line[start] == '\0' || line[start] == '\n') + break; + // find end of line + for(end = start; ; end++){ + if(line[end] == '\0' || line[end] == '\n') + break; + if(line[end] == ',' || line[end] == '\r') + line[end] = ' '; + } + line[end] = '\0'; + + // empty line + if(line[start] == '#' || line[start] == '\0') + continue; + + if(section == NONE){ + if(line[start] == 'c' && line[start + 1] == 'o' && line[start + 2] == 'l') + section = COLOURS; + else if(line[start] == 'c' && line[start + 1] == 'a' && line[start + 2] == 'r') + section = CARS; + }else if(line[start] == 'e' && line[start + 1] == 'n' && line[start + 2] == 'd'){ + section = NONE; + }else if(section == COLOURS){ + sscanf(&line[start], // BUG: games doesn't add start + "%d %d %d", &r, &g, &b); + ms_vehicleColourTable[numCols].red = r; + ms_vehicleColourTable[numCols].green = g; + ms_vehicleColourTable[numCols].blue = b; + ms_vehicleColourTable[numCols].alpha = 0xFF; + ms_colourTextureTable[numCols] = CreateCarColourTexture(r, g, b); + numCols++; + }else if(section == CARS){ + n = sscanf(&line[start], // BUG: games doesn't add start + "%s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", + name, + &colors[0], &colors[1], + &colors[2], &colors[3], + &colors[4], &colors[5], + &colors[6], &colors[7], + &colors[8], &colors[9], + &colors[10], &colors[11], + &colors[12], &colors[13], + &colors[14], &colors[15]); + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(name, nil); + assert(mi); + mi->m_numColours = (n-1)/2; + for(i = 0; i < mi->m_numColours; i++){ + mi->m_colours1[i] = colors[i*2 + 0]; + mi->m_colours2[i] = colors[i*2 + 1]; + } + } + } + + CFileMgr::CloseFile(fd); +} + +void +CVehicleModelInfo::DeleteVehicleColourTextures(void) +{ + int i; + + for(i = 0; i < 256; i++){ + if(ms_colourTextureTable[i]){ + RwTextureDestroy(ms_colourTextureTable[i]); +#if GTA_VERSION >= GTA3_PC_11 + ms_colourTextureTable[i] = nil; +#endif + } + } +} + +RpMaterial* +CVehicleModelInfo::HasSpecularMaterialCB(RpMaterial *material, void *data) +{ + if(RpMaterialGetSurfaceProperties(material)->specular <= 0.0f) + return material; + *(bool*)data = true; + return nil; +} + +RpMaterial* +CVehicleModelInfo::SetEnvironmentMapCB(RpMaterial *material, void *data) +{ + float spec; + + spec = RpMaterialGetSurfaceProperties(material)->specular; + if(spec <= 0.0f) + RpMatFXMaterialSetEffects(material, rpMATFXEFFECTNULL); + else{ + if(RpMaterialGetTexture(material) == nil) + RpMaterialSetTexture(material, gpWhiteTexture); + RpMatFXMaterialSetEffects(material, rpMATFXEFFECTENVMAP); +#ifndef PS2_MATFX + spec *= 0.5f; // Tone down a bit for PC +#endif + RpMatFXMaterialSetupEnvMap(material, (RwTexture*)data, pMatFxIdentityFrame, false, spec); + } + return material; +} + +bool initialised; + +RpAtomic* +CVehicleModelInfo::SetEnvironmentMapCB(RpAtomic *atomic, void *data) +{ + bool hasSpec; + RpGeometry *geo; + + geo = RpAtomicGetGeometry(atomic); + hasSpec = 0; + RpGeometryForAllMaterials(geo, HasSpecularMaterialCB, &hasSpec); + if(hasSpec){ + RpGeometryForAllMaterials(geo, SetEnvironmentMapCB, data); + RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) | rpGEOMETRYMODULATEMATERIALCOLOR); + RpMatFXAtomicEnableEffects(atomic); +#ifdef GTA_PS2 + if(!initialised){ + SetupPS2ManagerLightingCallback(RpAtomicGetInstancePipeline(atomic)); + initialised = true; + } +#endif + } + return atomic; +} + +void +CVehicleModelInfo::SetEnvironmentMap(void) +{ + CSimpleModelInfo *wheelmi; + int32 i; + + if(pMatFxIdentityFrame == nil){ + pMatFxIdentityFrame = RwFrameCreate(); + RwMatrixSetIdentity(RwFrameGetMatrix(pMatFxIdentityFrame)); + RwFrameUpdateObjects(pMatFxIdentityFrame); + RwFrameGetLTM(pMatFxIdentityFrame); + } + + if(m_envMap != ms_pEnvironmentMaps[0]){ + m_envMap = ms_pEnvironmentMaps[0]; + RpClumpForAllAtomics(m_clump, SetEnvironmentMapCB, m_envMap); + if(m_wheelId != -1){ + wheelmi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(m_wheelId); + for(i = 0; i < wheelmi->m_numAtomics; i++) + SetEnvironmentMapCB(wheelmi->m_atomics[i], m_envMap); + } + } + +#ifdef EXTENDED_PIPELINES + CustomPipes::AttachVehiclePipe(m_clump); +#endif +} + +void +CVehicleModelInfo::LoadEnvironmentMaps(void) +{ + const char *texnames[] = { + "reflection01", // only one used + "reflection02", + "reflection03", + "reflection04", + "reflection05", + "reflection06", + }; + int32 txdslot; + int32 i; + + txdslot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(txdslot); + for(i = 0; i < NUM_VEHICLE_ENVMAPS; i++){ + ms_pEnvironmentMaps[i] = RwTextureRead(texnames[i], nil); + RwTextureSetFilterMode(ms_pEnvironmentMaps[i], rwFILTERLINEAR); + } + if(gpWhiteTexture == nil){ + gpWhiteTexture = RwTextureRead("white", nil); + RwTextureGetName(gpWhiteTexture)[0] = '@'; + RwTextureSetFilterMode(gpWhiteTexture, rwFILTERLINEAR); + } + CTxdStore::PopCurrentTxd(); +} + +void +CVehicleModelInfo::ShutdownEnvironmentMaps(void) +{ + int32 i; + + // ignoring "initialised" as that's a PS2 thing only + RwTextureDestroy(gpWhiteTexture); + gpWhiteTexture = nil; + for(i = 0; i < NUM_VEHICLE_ENVMAPS; i++) + if(ms_pEnvironmentMaps[i]) + RwTextureDestroy(ms_pEnvironmentMaps[i]); + RwFrameDestroy(pMatFxIdentityFrame); + pMatFxIdentityFrame = nil; +} + +int +CVehicleModelInfo::GetMaximumNumberOfPassengersFromNumberOfDoors(int id) +{ + int n; + + switch(id){ + case MI_TRAIN: + n = 3; + break; + case MI_FIRETRUCK: + n = 2; + break; + default: + n = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(id))->m_numDoors; + } + + if(n == 0) + return id == MI_RCBANDIT ? 0 : 1; + + if(id == MI_COACH) + return 8; + + return n - 1; +} diff --git a/src/modelinfo/VehicleModelInfo.h b/src/modelinfo/VehicleModelInfo.h new file mode 100644 index 0000000..e6ba576 --- /dev/null +++ b/src/modelinfo/VehicleModelInfo.h @@ -0,0 +1,165 @@ +#pragma once + +#include "ClumpModelInfo.h" + +enum { + NUM_FIRST_MATERIALS = 26, + NUM_SECOND_MATERIALS = 26, + NUM_VEHICLE_COLOURS = 8, + NUM_VEHICLE_ENVMAPS = 1 +}; + +enum { + ATOMIC_FLAG_NONE = 0x0, + ATOMIC_FLAG_OK = 0x1, + ATOMIC_FLAG_DAM = 0x2, + ATOMIC_FLAG_LEFT = 0x4, + ATOMIC_FLAG_RIGHT = 0x8, + ATOMIC_FLAG_FRONT = 0x10, + ATOMIC_FLAG_REAR = 0x20, + ATOMIC_FLAG_DRAWLAST = 0x40, + ATOMIC_FLAG_WINDSCREEN = 0x80, + ATOMIC_FLAG_ANGLECULL = 0x100, + ATOMIC_FLAG_REARDOOR = 0x200, + ATOMIC_FLAG_FRONTDOOR = 0x400, + ATOMIC_FLAG_NOCULL = 0x800, +}; + +enum eVehicleType { + VEHICLE_TYPE_CAR, + VEHICLE_TYPE_BOAT, + VEHICLE_TYPE_TRAIN, + VEHICLE_TYPE_HELI, + VEHICLE_TYPE_PLANE, + VEHICLE_TYPE_BIKE, + NUM_VEHICLE_TYPES +}; + +enum eCarPositions +{ + CAR_POS_HEADLIGHTS, + CAR_POS_TAILLIGHTS, + CAR_POS_FRONTSEAT, + CAR_POS_BACKSEAT, + // these are unused so we don't know the actual values + CAR_POS_REVERSELIGHTS, + CAR_POS_BRAKELIGHTS, + CAR_POS_INDICATORS_FRONT, + CAR_POS_INDICATORS_BACK, + CAR_POS_STEERWHEEL, + // + CAR_POS_EXHAUST +}; + +enum eBoatPositions +{ + BOAT_POS_FRONTSEAT +}; + +enum eTrainPositions +{ + TRAIN_POS_LIGHT_FRONT, + TRAIN_POS_LIGHT_REAR, + TRAIN_POS_LEFT_ENTRY, + TRAIN_POS_MID_ENTRY, + TRAIN_POS_RIGHT_ENTRY +}; + +enum ePlanePositions +{ + PLANE_POS_LIGHT_LEFT, + PLANE_POS_LIGHT_RIGHT, + PLANE_POS_LIGHT_TAIL, +}; + +enum { + NUM_VEHICLE_POSITIONS = 10 +}; + +class CVehicleModelInfo : public CClumpModelInfo +{ +public: + uint8 m_lastColour1; + uint8 m_lastColour2; + char m_gameName[32]; + int32 m_vehicleType; + union { + int32 m_wheelId; + int32 m_planeLodId; + }; + float m_wheelScale; + int32 m_numDoors; + int32 m_handlingId; + int32 m_vehicleClass; + int32 m_level; + CVector m_positions[NUM_VEHICLE_POSITIONS]; + uint32 m_compRules; + float m_bikeSteerAngle; + RpMaterial *m_materials1[NUM_FIRST_MATERIALS]; + RpMaterial *m_materials2[NUM_SECOND_MATERIALS]; + uint8 m_colours1[NUM_VEHICLE_COLOURS]; + uint8 m_colours2[NUM_VEHICLE_COLOURS]; + uint8 m_numColours; + uint8 m_lastColorVariation; + uint8 m_currentColour1; + uint8 m_currentColour2; + RwTexture *m_envMap; + RpAtomic *m_comps[6]; + int32 m_numComps; + + static int8 ms_compsToUse[2]; + static int8 ms_compsUsed[2]; + static RwTexture *ms_pEnvironmentMaps[NUM_VEHICLE_ENVMAPS]; + static RwRGBA ms_vehicleColourTable[256]; + static RwTexture *ms_colourTextureTable[256]; + static RwObjectNameIdAssocation *ms_vehicleDescs[NUM_VEHICLE_TYPES]; + + CVehicleModelInfo(void); + void DeleteRwObject(void); + RwObject *CreateInstance(void); + void SetClump(RpClump *); + + static RwFrame *CollapseFramesCB(RwFrame *frame, void *data); + static RwObject *MoveObjectsCB(RwObject *object, void *data); + static RpAtomic *HideDamagedAtomicCB(RpAtomic *atomic, void *data); + static RpAtomic *HideAllComponentsAtomicCB(RpAtomic *atomic, void *data); + static RpMaterial *HasAlphaMaterialCB(RpMaterial *material, void *data); + + static RpAtomic *SetAtomicRendererCB(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_BigVehicle(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_Train(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_Boat(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_Heli(RpAtomic *atomic, void *data); + void SetAtomicRenderCallbacks(void); + + static RwObject *SetAtomicFlagCB(RwObject *object, void *data); + static RwObject *ClearAtomicFlagCB(RwObject *atomic, void *data); + void SetVehicleComponentFlags(RwFrame *frame, uint32 flags); + void PreprocessHierarchy(void); + void GetWheelPosn(int32 n, CVector &pos); + const CVector &GetFrontSeatPosn(void) { return m_vehicleType == VEHICLE_TYPE_BOAT ? m_positions[BOAT_POS_FRONTSEAT] : m_positions[CAR_POS_FRONTSEAT]; } + + int32 ChooseComponent(void); + int32 ChooseSecondComponent(void); + + static RpMaterial *GetEditableMaterialListCB(RpMaterial *material, void *data); + static RpAtomic *GetEditableMaterialListCB(RpAtomic *atomic, void *data); + void FindEditableMaterialList(void); + void SetVehicleColour(uint8 c1, uint8 c2); + void ChooseVehicleColour(uint8 &col1, uint8 &col2); + void AvoidSameVehicleColour(uint8 *col1, uint8 *col2); + static void LoadVehicleColours(void); + static void DeleteVehicleColourTextures(void); + + static RpAtomic *SetEnvironmentMapCB(RpAtomic *atomic, void *data); + static RpMaterial *SetEnvironmentMapCB(RpMaterial *material, void *data); + static RpMaterial *HasSpecularMaterialCB(RpMaterial *material, void *data); + void SetEnvironmentMap(void); + static void LoadEnvironmentMaps(void); + static void ShutdownEnvironmentMaps(void); + + static int GetMaximumNumberOfPassengersFromNumberOfDoors(int id); + static void SetComponentsToUse(int8 c1, int8 c2) { ms_compsToUse[0] = c1; ms_compsToUse[1] = c2; } +}; + +VALIDATE_SIZE(CVehicleModelInfo, 0x1F8); diff --git a/src/modelinfo/XtraCompsModelInfo.h b/src/modelinfo/XtraCompsModelInfo.h new file mode 100644 index 0000000..ab308a8 --- /dev/null +++ b/src/modelinfo/XtraCompsModelInfo.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ClumpModelInfo.h" + +class CXtraCompsModelInfo : public CClumpModelInfo +{ + int field_34; +public: + CXtraCompsModelInfo(void) : CClumpModelInfo(MITYPE_XTRACOMPS) { field_34 = 0; } + void Shutdown(void) {}; + RwObject *CreateInstance(void) { return nil; } + void SetClump(RpClump*) {}; +}; \ No newline at end of file diff --git a/src/objects/CutsceneHead.cpp b/src/objects/CutsceneHead.cpp new file mode 100644 index 0000000..19b3a59 --- /dev/null +++ b/src/objects/CutsceneHead.cpp @@ -0,0 +1,230 @@ +#include "common.h" +#include + +#include "main.h" +#include "RwHelper.h" +#include "RpAnimBlend.h" +#include "AnimBlendClumpData.h" +#include "Bones.h" +#include "Directory.h" +#include "CutsceneMgr.h" +#include "Streaming.h" +#include "CutsceneHead.h" +#include "CdStream.h" + +#ifdef GTA_PS2_STUFF +// this is a total hack to switch between PC and PS2 code +static bool lastLoadedSKA; +#endif + +CCutsceneHead::CCutsceneHead(CObject *obj) +{ + RpAtomic *atm; + + assert(RwObjectGetType(obj->m_rwObject) == rpCLUMP); +#ifdef PED_SKIN + unk1 = 0; + bIsSkinned = false; + m_parentObject = (CCutsceneObject*)obj; + // Hide original head + if(IsClumpSkinned(obj->GetClump())){ + m_parentObject->SetRenderHead(false); + bIsSkinned = true; + }else +#endif + { + m_pHeadNode = RpAnimBlendClumpFindFrame((RpClump*)obj->m_rwObject, "Shead")->frame; + atm = (RpAtomic*)GetFirstObject(m_pHeadNode); + if(atm){ + assert(RwObjectGetType((RwObject*)atm) == rpATOMIC); + RpAtomicSetFlags(atm, RpAtomicGetFlags(atm) & ~rpATOMICRENDER); + } + } +} + +void +CCutsceneHead::CreateRwObject(void) +{ + RpAtomic *atm; + + CEntity::CreateRwObject(); + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + atm = GetFirstAtomic((RpClump*)m_rwObject); + RpSkinAtomicSetHAnimHierarchy(atm, RpHAnimFrameGetHierarchy(GetFirstChild(RpClumpGetFrame((RpClump*)m_rwObject)))); +} + +void +CCutsceneHead::DeleteRwObject(void) +{ + CEntity::DeleteRwObject(); +} + +void +CCutsceneHead::ProcessControl(void) +{ + RpAtomic *atm; + RpHAnimHierarchy *hier; + + // android/xbox calls is at the end + CPhysical::ProcessControl(); + +#ifdef PED_SKIN + if(bIsSkinned){ + UpdateRpHAnim(); + UpdateRwFrame(); + + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(m_parentObject->GetClump()); + int idx = RpHAnimIDGetIndex(hier, BONE_head); + RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + if(RwV3dLength(&mat->pos) > 100.0f){ + m_matrix.SetRotateY(PI/2); + m_matrix = CMatrix(mat) * m_matrix; + } + }else +#endif + { + m_matrix.SetRotateY(PI/2); + m_matrix = CMatrix(RwFrameGetLTM(m_pHeadNode)) * m_matrix; + } + + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + atm = GetFirstAtomic((RpClump*)m_rwObject); + hier = RpSkinAtomicGetHAnimHierarchy(atm); +#ifdef GTA_PS2_STUFF + // PS2 only plays anims in cutscene, PC always plays anims + if(!lastLoadedSKA || CCutsceneMgr::IsRunning()) +#endif + RpHAnimHierarchyAddAnimTime(hier, CTimer::GetTimeStepNonClippedInSeconds()); +} + +void +CCutsceneHead::Render(void) +{ + RpAtomic *atm; + +#ifdef PED_SKIN + if(bIsSkinned){ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(m_parentObject->GetClump()); + RpHAnimHierarchyUpdateMatrices(hier); + int idx = RpHAnimIDGetIndex(hier, BONE_head); + RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + if(RwV3dLength(&mat->pos) > 100.0f){ + m_matrix.SetRotateY(PI/2); + m_matrix = CMatrix(mat) * m_matrix; + } + // This is head...it has no limbs +#ifndef FIX_BUGS + RenderLimb(BONE_Lhand); + RenderLimb(BONE_Rhand); +#endif + }else +#endif + { + m_matrix.SetRotateY(PI/2); + m_matrix = CMatrix(RwFrameGetLTM(m_pHeadNode)) * m_matrix; + } + + UpdateRwFrame(); + + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + atm = GetFirstAtomic((RpClump*)m_rwObject); + RpHAnimHierarchyUpdateMatrices(RpSkinAtomicGetHAnimHierarchy(atm)); + + CObject::Render(); +} + +#ifdef PED_SKIN +void +CCutsceneHead::RenderLimb(int32 bone) +{ + // It's not clear what this is... + // modelinfo for this object is not a ped so it also doesn't have any limbs +#ifndef FIX_BUGS + RpAtomic *atomic; + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(m_parentObject->GetClump()); + int idx = RpHAnimIDGetIndex(hier, bone); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + CPedModelInfo *mi = (CPedModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + assert(mi->GetModelType() == MITYPE_PED); + switch(bone){ + case BONE_Lhand: + atomic = mi->getLeftHand(); + break; + case BONE_Rhand: + atomic = mi->getRightHand(); + break; + default: + return; + } + if(atomic){ + RwFrame *frame = RpAtomicGetFrame(atomic); + RwMatrixTransform(RwFrameGetMatrix(frame), &mats[idx], rwCOMBINEREPLACE); + RwFrameUpdateObjects(frame); + RpAtomicRender(atomic); + } +#endif +} +#endif + +void +CCutsceneHead::PlayAnimation(const char *animName) +{ + RpAtomic *atm; + RpHAnimHierarchy *hier; + RpHAnimAnimation *anim; + uint32 offset, size; + RwStream *stream; + +#ifdef GTA_PS2_STUFF + lastLoadedSKA = false; +#endif + + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + atm = GetFirstAtomic((RpClump*)m_rwObject); + hier = RpSkinAtomicGetHAnimHierarchy(atm); + + sprintf(gString, "%s.anm", animName); + + if(CCutsceneMgr::ms_pCutsceneDir->FindItem(gString, offset, size)){ + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "ANIM\\CUTS.IMG"); + assert(stream); + + CStreaming::MakeSpaceFor(size * CDSTREAM_SECTOR_SIZE); + CStreaming::ImGonnaUseStreamingMemory(); + + RwStreamSkip(stream, offset*2048); + if(RwStreamFindChunk(stream, rwID_HANIMANIMATION, nil, nil)){ + anim = RpHAnimAnimationStreamRead(stream); + RpHAnimHierarchySetCurrentAnim(hier, anim); + } + + CStreaming::IHaveUsedStreamingMemory(); + + RwStreamClose(stream, nil); + } +#ifdef GTA_PS2_STUFF +#ifdef LIBRW + else{ + sprintf(gString, "%s.ska", animName); + + if(CCutsceneMgr::ms_pCutsceneDir->FindItem(gString, offset, size)){ + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "ANIM\\CUTS.IMG"); + assert(stream); + + CStreaming::MakeSpaceFor(size * CDSTREAM_SECTOR_SIZE); + CStreaming::ImGonnaUseStreamingMemory(); + + RwStreamSkip(stream, offset*2048); + anim = rw::Animation::streamReadLegacy(stream); + RpHAnimHierarchySetCurrentAnim(hier, anim); + + CStreaming::IHaveUsedStreamingMemory(); + + RwStreamClose(stream, nil); + + lastLoadedSKA = true; + } + } +#endif +#endif +} diff --git a/src/objects/CutsceneHead.h b/src/objects/CutsceneHead.h new file mode 100644 index 0000000..c931eb0 --- /dev/null +++ b/src/objects/CutsceneHead.h @@ -0,0 +1,28 @@ +#pragma once + +#include "CutsceneObject.h" + +class CCutsceneHead : public CCutsceneObject +{ +public: + RwFrame *m_pHeadNode; +#ifdef PED_SKIN + int32 unk1; + CCutsceneObject *m_parentObject; + int32 unk2; + int32 bIsSkinned; +#endif + + CCutsceneHead(CObject *obj); + + void CreateRwObject(void); + void DeleteRwObject(void); + void ProcessControl(void); + void Render(void); + void RenderLimb(int32 bone); + + void PlayAnimation(const char *animName); +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CCutsceneHead, 0x19C); +#endif diff --git a/src/objects/CutsceneObject.cpp b/src/objects/CutsceneObject.cpp new file mode 100644 index 0000000..64e5780 --- /dev/null +++ b/src/objects/CutsceneObject.cpp @@ -0,0 +1,153 @@ +#include "common.h" + +#include "main.h" +#include "RwHelper.h" +#include "Lights.h" +#include "PointLights.h" +#include "RpAnimBlend.h" +#include "AnimBlendClumpData.h" +#include "Bones.h" +#include "Renderer.h" +#include "ModelIndices.h" +#include "Shadows.h" +#include "Timecycle.h" +#include "CutsceneObject.h" + +CCutsceneObject::CCutsceneObject(void) +{ + SetStatus(STATUS_SIMPLE); + bUsesCollision = false; + bStreamingDontDelete = true; + ObjectCreatedBy = CUTSCENE_OBJECT; + m_fMass = 1.0f; + m_fTurnMass = 1.0f; + +#ifdef PED_SKIN + bRenderHead = true; + bRenderRightHand = true; + bRenderLeftHand = true; +#endif +} + +void +CCutsceneObject::SetModelIndex(uint32 id) +{ + CEntity::SetModelIndex(id); + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + RpAnimBlendClumpInit((RpClump*)m_rwObject); + (*RPANIMBLENDCLUMPDATA(m_rwObject))->velocity3d = &m_vecMoveSpeed; + (*RPANIMBLENDCLUMPDATA(m_rwObject))->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION_3D; +} + +void +CCutsceneObject::ProcessControl(void) +{ + CPhysical::ProcessControl(); + + if(CTimer::GetTimeStep() < 1/100.0f) + m_vecMoveSpeed *= 100.0f; + else + m_vecMoveSpeed *= 1.0f/CTimer::GetTimeStep(); + + ApplyMoveSpeed(); + +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())) + UpdateRpHAnim(); +#endif +} + +static RpMaterial* +MaterialSetAlpha(RpMaterial *material, void *data) +{ + ((RwRGBA*)RpMaterialGetColor(material))->alpha = (uint8)(uintptr)data; + return material; +} + +void +CCutsceneObject::PreRender(void) +{ + if(IsPedModel(GetModelIndex())){ + CShadows::StoreShadowForPedObject(this, + CTimeCycle::m_fShadowDisplacementX[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowDisplacementY[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowFrontX[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowFrontY[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowSideX[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowSideY[CTimeCycle::m_CurrentStoredValue]); + // For some reason xbox/android limbs are transparent here... + RpGeometry *geometry = RpAtomicGetGeometry(GetFirstAtomic(GetClump())); + RpGeometrySetFlags(geometry, RpGeometryGetFlags(geometry) | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geometry, MaterialSetAlpha, (void*)255); + } +} + +void +CCutsceneObject::Render(void) +{ +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + if(bRenderLeftHand) RenderLimb(BONE_Lhand); + if(bRenderRightHand) RenderLimb(BONE_Rhand); + if(bRenderHead) RenderLimb(BONE_head); + } +#endif + CObject::Render(); +} + +#ifdef PED_SKIN +void +CCutsceneObject::RenderLimb(int32 bone) +{ + RpAtomic *atomic; + CPedModelInfo *mi = (CPedModelInfo *)CModelInfo::GetModelInfo(GetModelIndex()); + switch(bone){ + case BONE_head: + atomic = mi->getHead(); + break; + case BONE_Lhand: + atomic = mi->getLeftHand(); + break; + case BONE_Rhand: + atomic = mi->getRightHand(); + break; + default: + return; + } + if(atomic){ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int idx = RpHAnimIDGetIndex(hier, bone); + RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwFrame *frame = RpAtomicGetFrame(atomic); + *RwFrameGetMatrix(frame) = *mat; + RwFrameUpdateObjects(frame); + RpAtomicRender(atomic); + } +} +#endif + +bool +CCutsceneObject::SetupLighting(void) +{ + ActivateDirectional(); + SetAmbientColoursForPedsCarsAndObjects(); + + if(bRenderScorched){ + WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f); + }else{ + CVector coors = GetPosition(); + float lighting = CPointLights::GenerateLightsAffectingObject(&coors); + if(!bHasBlip && lighting != 1.0f){ + SetAmbientAndDirectionalColours(lighting); + return true; + } + } + + return false; +} + +void +CCutsceneObject::RemoveLighting(bool reset) +{ + CRenderer::RemoveVehiclePedLights(this, reset); +} diff --git a/src/objects/CutsceneObject.h b/src/objects/CutsceneObject.h new file mode 100644 index 0000000..407adcc --- /dev/null +++ b/src/objects/CutsceneObject.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Object.h" + +class CCutsceneObject : public CObject +{ +public: +#ifdef PED_SKIN + bool bRenderHead; + bool bRenderRightHand; + bool bRenderLeftHand; + + bool GetRenderHead(void) { return bRenderHead; } + bool GetRenderRightHand(void) { return bRenderRightHand; } + bool GetRenderLeftHand(void) { return bRenderLeftHand; } + void SetRenderHead(bool render) { bRenderHead = render; } + void SetRenderRightHand(bool render) { bRenderRightHand = render; } + void SetRenderLeftHand(bool render) { bRenderLeftHand = render; } +#endif + + CCutsceneObject(void); + + void SetModelIndex(uint32 id); + void ProcessControl(void); + void PreRender(void); + void Render(void); + void RenderLimb(int32 bone); + bool SetupLighting(void); + void RemoveLighting(bool reset); +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CCutsceneObject, 0x198); +#endif diff --git a/src/objects/DummyObject.cpp b/src/objects/DummyObject.cpp new file mode 100644 index 0000000..d580507 --- /dev/null +++ b/src/objects/DummyObject.cpp @@ -0,0 +1,13 @@ +#include "common.h" + +#include "DummyObject.h" +#include "Pools.h" + +CDummyObject::CDummyObject(CObject *obj) +{ + SetModelIndexNoCreate(obj->GetModelIndex()); + if(obj->m_rwObject) + AttachToRwObject(obj->m_rwObject); + obj->DetachFromRwObject(); + m_level = obj->m_level; +} diff --git a/src/objects/DummyObject.h b/src/objects/DummyObject.h new file mode 100644 index 0000000..d6f8833 --- /dev/null +++ b/src/objects/DummyObject.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Dummy.h" + +class CObject; + +class CDummyObject : public CDummy +{ +public: + CDummyObject(void) {} + CDummyObject(CObject *obj); +}; + +VALIDATE_SIZE(CDummyObject, 0x68); diff --git a/src/objects/Object.cpp b/src/objects/Object.cpp new file mode 100644 index 0000000..2a7de2c --- /dev/null +++ b/src/objects/Object.cpp @@ -0,0 +1,419 @@ +#include "common.h" + +#include "main.h" +#include "Lights.h" +#include "Pools.h" +#include "Radar.h" +#include "Object.h" +#include "DummyObject.h" +#include "Particle.h" +#include "General.h" +#include "ObjectData.h" +#include "World.h" +#include "Floater.h" +#include "soundlist.h" + +int16 CObject::nNoTempObjects; +int16 CObject::nBodyCastHealth = 1000; + +// Object pools tends to be full sometimes, let's free a temp. object in this case. +#ifdef FIX_BUGS +void *CObject::operator new(size_t sz) throw() { + CObject *obj = CPools::GetObjectPool()->New(); + if (!obj) { + CObjectPool *objectPool = CPools::GetObjectPool(); + for (int32 i = 0; i < objectPool->GetSize(); i++) { + CObject *existing = objectPool->GetSlot(i); + if (existing && existing->ObjectCreatedBy == TEMP_OBJECT) { + int32 handle = objectPool->GetIndex(existing); + CWorld::Remove(existing); + delete existing; + obj = objectPool->New(handle); + break; + } + } + } + return obj; +} +#else +void *CObject::operator new(size_t sz) throw() { return CPools::GetObjectPool()->New(); } +#endif +void *CObject::operator new(size_t sz, int handle) throw() { return CPools::GetObjectPool()->New(handle); }; + +void CObject::operator delete(void *p, size_t sz) throw() { CPools::GetObjectPool()->Delete((CObject*)p); } +void CObject::operator delete(void *p, int handle) throw() { CPools::GetObjectPool()->Delete((CObject*)p); } + +CObject::CObject(void) +{ + m_type = ENTITY_TYPE_OBJECT; + m_fUprootLimit = 0.0f; + m_nCollisionDamageEffect = 0; + m_nSpecialCollisionResponseCases = COLLRESPONSE_NONE; + m_bCameraToAvoidThisObject = false; + ObjectCreatedBy = UNKNOWN_OBJECT; + m_nEndOfLifeTime = 0; +// m_nRefModelIndex = -1; // duplicate +// bUseVehicleColours = false; // duplicate + m_colour2 = 0; + m_colour1 = m_colour2; + m_nBonusValue = 0; + bIsPickup = false; + bPickupObjWithMessage = false; + bOutOfStock = false; + bGlassCracked = false; + bGlassBroken = false; + bHasBeenDamaged = false; + m_nRefModelIndex = -1; + bUseVehicleColours = false; + m_pCurSurface = nil; + m_pCollidingEntity = nil; +} + +CObject::CObject(int32 mi, bool createRW) +{ + if (createRW) + SetModelIndex(mi); + else + SetModelIndexNoCreate(mi); + Init(); +} + +CObject::CObject(CDummyObject *dummy) +{ + SetModelIndexNoCreate(dummy->GetModelIndex()); + + if (dummy->m_rwObject) + AttachToRwObject(dummy->m_rwObject); + else + SetMatrix(dummy->GetMatrix()); + + m_objectMatrix = dummy->GetMatrix(); + dummy->DetachFromRwObject(); + Init(); + m_level = dummy->m_level; +} + +CObject::~CObject(void) +{ + CRadar::ClearBlipForEntity(BLIP_OBJECT, CPools::GetObjectPool()->GetIndex(this)); + + if(m_nRefModelIndex != -1) + CModelInfo::GetModelInfo(m_nRefModelIndex)->RemoveRef(); + + if(ObjectCreatedBy == TEMP_OBJECT && nNoTempObjects != 0) + nNoTempObjects--; +} + +void +CObject::ProcessControl(void) +{ + CVector point, impulse; + if (m_nCollisionDamageEffect) + ObjectDamage(m_fDamageImpulse); + CPhysical::ProcessControl(); + if (mod_Buoyancy.ProcessBuoyancy(this, m_fBuoyancy, &point, &impulse)) { + bIsInWater = true; + SetIsStatic(false); + ApplyMoveForce(impulse); + ApplyTurnForce(impulse, point); + float fTimeStep = Pow(0.97f, CTimer::GetTimeStep()); + m_vecMoveSpeed *= fTimeStep; + m_vecTurnSpeed *= fTimeStep; + } + if ((GetModelIndex() == MI_EXPLODINGBARREL || GetModelIndex() == MI_PETROLPUMP) && bHasBeenDamaged && bIsVisible + && (CGeneral::GetRandomNumber() & 0x1F) == 10) { + bExplosionProof = true; + bIsVisible = false; + bUsesCollision = false; + bAffectedByGravity = false; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } +} + +void +CObject::Teleport(CVector vecPos) +{ + CWorld::Remove(this); + GetMatrix().GetPosition() = vecPos; + GetMatrix().UpdateRW(); + UpdateRwFrame(); + CWorld::Add(this); +} + +void +CObject::Render(void) +{ + if(bDoNotRender) + return; + + if(m_nRefModelIndex != -1 && ObjectCreatedBy == TEMP_OBJECT && bUseVehicleColours){ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(m_nRefModelIndex); + assert(mi->GetModelType() == MITYPE_VEHICLE); + mi->SetVehicleColour(m_colour1, m_colour2); + } + + CEntity::Render(); +} + +bool +CObject::SetupLighting(void) +{ + DeActivateDirectional(); + SetAmbientColours(); + + if(bRenderScorched){ + WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f); + return true; + } + return false; +} + +void +CObject::RemoveLighting(bool reset) +{ + if(reset) + WorldReplaceScorchedLightsWithNormal(Scene.world); +} + +void +CObject::ObjectDamage(float amount) +{ + if (!m_nCollisionDamageEffect || !bUsesCollision) + return; + static int8 nFrameGen = 0; + bool bBodyCastDamageEffect = false; + if (GetModelIndex() == MI_BODYCAST) { + if (amount > 50.0f) + nBodyCastHealth = (int16)(nBodyCastHealth - 0.5f * amount); + if (nBodyCastHealth < 0) + nBodyCastHealth = 0; + if (nBodyCastHealth < 200) + bBodyCastDamageEffect = true; + amount = 0.0f; + } + if ((amount * m_fCollisionDamageMultiplier > 150.0f || bBodyCastDamageEffect) && m_nCollisionDamageEffect) { + const CVector& vecPos = GetMatrix().GetPosition(); + const float fDirectionZ = 0.0002f * amount; + switch (m_nCollisionDamageEffect) + { + case DAMAGE_EFFECT_CHANGE_MODEL: + bRenderDamaged = true; + break; + case DAMAGE_EFFECT_SPLIT_MODEL: + break; + case DAMAGE_EFFECT_SMASH_COMPLETELY: + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + break; + case DAMAGE_EFFECT_CHANGE_THEN_SMASH: + if (!bRenderDamaged) { + bRenderDamaged = true; + } + else { + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + } + break; + case DAMAGE_EFFECT_SMASH_CARDBOARD_COMPLETELY: { + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + const RwRGBA color = { 96, 48, 0, 255 }; + for (int32 i = 0; i < 25; i++) { + CVector vecDir(CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.1f, 0.25f) + fDirectionZ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + float fRandom = CGeneral::GetRandomNumberInRange(0.01f, 1.0f); + RwRGBA randomColor = { uint8(color.red * fRandom), uint8(color.green * fRandom) , color.blue, color.alpha }; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, randomColor, nRotationSpeed, 0, currentFrame, 0); + } + PlayOneShotScriptObject(SCRIPT_SOUND_BOX_DESTROYED_2, vecPos); + break; + } + case DAMAGE_EFFECT_SMASH_WOODENBOX_COMPLETELY: { + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + const RwRGBA color = { 128, 128, 128, 255 }; + for (int32 i = 0; i < 45; i++) { + CVector vecDir(CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.1f, 0.25f) + fDirectionZ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + float fRandom = CGeneral::GetRandomNumberInRange(0.5f, 1.0f); + RwRGBA randomColor = { uint8(color.red * fRandom), uint8(color.green * fRandom), uint8(color.blue * fRandom), color.alpha }; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, randomColor, nRotationSpeed, 0, currentFrame, 0); + } + PlayOneShotScriptObject(SCRIPT_SOUND_BOX_DESTROYED_1, vecPos); + break; + } + case DAMAGE_EFFECT_SMASH_TRAFFICCONE_COMPLETELY: { + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + const RwRGBA color1 = { 200, 0, 0, 255 }; + const RwRGBA color2 = { 200, 200, 200, 255 }; + for (int32 i = 0; i < 10; i++) { + CVector vecDir(CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.1f, 0.25f) + fDirectionZ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + RwRGBA color = color2; + if (nFrameGen & 1) + color = color1; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, color, nRotationSpeed, 0, currentFrame, 0); + } + PlayOneShotScriptObject(SCRIPT_SOUND_TIRE_COLLISION, vecPos); + break; + } + case DAMAGE_EFFECT_SMASH_BARPOST_COMPLETELY: { + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + const RwRGBA color1 = { 200, 0, 0, 255 }; + const RwRGBA color2 = { 200, 200, 200, 255 }; + for (int32 i = 0; i < 32; i++) { + CVector vecDir(CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.1f, 0.25f) + fDirectionZ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + RwRGBA color = color2; + if (nFrameGen & 1) + color = color1; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, color, nRotationSpeed, 0, currentFrame, 0); + } + PlayOneShotScriptObject(SCRIPT_SOUND_METAL_COLLISION, vecPos); + break; + } + } + } +} + +void +CObject::RefModelInfo(int32 modelId) +{ + m_nRefModelIndex = modelId; + CModelInfo::GetModelInfo(modelId)->AddRef(); +} + +void +CObject::Init(void) +{ + m_type = ENTITY_TYPE_OBJECT; + CObjectData::SetObjectData(GetModelIndex(), *this); + m_nEndOfLifeTime = 0; + ObjectCreatedBy = GAME_OBJECT; + SetIsStatic(true); + bIsPickup = false; + bPickupObjWithMessage = false; + bOutOfStock = false; + bGlassCracked = false; + bGlassBroken = false; + bHasBeenDamaged = false; + bUseVehicleColours = false; + m_nRefModelIndex = -1; + m_colour1 = 0; + m_colour2 = 0; + m_nBonusValue = 0; + m_pCollidingEntity = nil; + CColPoint point; + CEntity* outEntity = nil; + const CVector& vecPos = GetMatrix().GetPosition(); + if (CWorld::ProcessVerticalLine(vecPos, vecPos.z - 10.0f, point, outEntity, true, false, false, false, false, false, nil)) + m_pCurSurface = outEntity; + else + m_pCurSurface = nil; + if (GetModelIndex() == MI_BODYCAST) + nBodyCastHealth = 1000; + else if (GetModelIndex() == MI_BUOY) + bTouchingWater = true; +} + +bool +CObject::CanBeDeleted(void) +{ + switch (ObjectCreatedBy) { + case GAME_OBJECT: + return true; + case MISSION_OBJECT: + return false; + case TEMP_OBJECT: + return true; + case CUTSCENE_OBJECT: + return false; + default: + return true; + } +} + +void +CObject::DeleteAllMissionObjects() +{ + CObjectPool* objectPool = CPools::GetObjectPool(); + for (int32 i = 0; i < objectPool->GetSize(); i++) { + CObject* pObject = objectPool->GetSlot(i); + if (pObject && pObject->ObjectCreatedBy == MISSION_OBJECT) { + CWorld::Remove(pObject); + delete pObject; + } + } +} + +void +CObject::DeleteAllTempObjects() +{ + CObjectPool* objectPool = CPools::GetObjectPool(); + for (int32 i = 0; i < objectPool->GetSize(); i++) { + CObject* pObject = objectPool->GetSlot(i); + if (pObject && pObject->ObjectCreatedBy == TEMP_OBJECT) { + CWorld::Remove(pObject); + delete pObject; + } + } +} + +void +CObject::DeleteAllTempObjectsInArea(CVector point, float fRadius) +{ + CObjectPool *objectPool = CPools::GetObjectPool(); + for (int32 i = 0; i < objectPool->GetSize(); i++) { + CObject *pObject = objectPool->GetSlot(i); + if (pObject && pObject->ObjectCreatedBy == TEMP_OBJECT && (point - pObject->GetPosition()).MagnitudeSqr() < SQR(fRadius)) { + CWorld::Remove(pObject); + delete pObject; + } + } +} diff --git a/src/objects/Object.h b/src/objects/Object.h new file mode 100644 index 0000000..114a1a9 --- /dev/null +++ b/src/objects/Object.h @@ -0,0 +1,94 @@ +#pragma once + +#include "Physical.h" + +enum { + UNKNOWN_OBJECT = 0, + GAME_OBJECT = 1, + MISSION_OBJECT = 2, + TEMP_OBJECT = 3, + CUTSCENE_OBJECT = 4, +}; + +enum CollisionSpecialResponseCase +{ + COLLRESPONSE_NONE, + COLLRESPONSE_LAMPOST, + COLLRESPONSE_SMALLBOX, + COLLRESPONSE_BIGBOX, + COLLRESPONSE_FENCEPART, + COLLRESPONSE_UNKNOWN5 +}; + +enum CollisionDamageEffect +{ + DAMAGE_EFFECT_NONE, + DAMAGE_EFFECT_CHANGE_MODEL, + DAMAGE_EFFECT_SPLIT_MODEL, + DAMAGE_EFFECT_SMASH_COMPLETELY, + DAMAGE_EFFECT_CHANGE_THEN_SMASH, + + DAMAGE_EFFECT_SMASH_CARDBOARD_COMPLETELY = 50, + DAMAGE_EFFECT_SMASH_WOODENBOX_COMPLETELY = 60, + DAMAGE_EFFECT_SMASH_TRAFFICCONE_COMPLETELY = 70, + DAMAGE_EFFECT_SMASH_BARPOST_COMPLETELY = 80 +}; + +class CVehicle; +class CDummyObject; + +class CObject : public CPhysical +{ +public: + CMatrix m_objectMatrix; + float m_fUprootLimit; + int8 ObjectCreatedBy; + int8 bIsPickup : 1; + int8 bPickupObjWithMessage : 1; + int8 bOutOfStock : 1; + int8 bGlassCracked : 1; + int8 bGlassBroken : 1; + int8 bHasBeenDamaged : 1; + int8 bUseVehicleColours : 1; + int8 m_nBonusValue; + float m_fCollisionDamageMultiplier; + uint8 m_nCollisionDamageEffect; + uint8 m_nSpecialCollisionResponseCases; + bool m_bCameraToAvoidThisObject; + uint32 m_obj_unused1; + uint32 m_nEndOfLifeTime; + int16 m_nRefModelIndex; + CEntity *m_pCurSurface; + CEntity *m_pCollidingEntity; + int8 m_colour1, m_colour2; + + static int16 nNoTempObjects; + static int16 nBodyCastHealth; + + static void *operator new(size_t) throw(); + static void *operator new(size_t, int) throw(); + static void operator delete(void*, size_t) throw(); + static void operator delete(void*, int) throw(); + + CObject(void); + CObject(int32, bool); + CObject(CDummyObject*); + ~CObject(void); + + void ProcessControl(void); + void Teleport(CVector vecPos); + void Render(void); + bool SetupLighting(void); + void RemoveLighting(bool reset); + + void ObjectDamage(float amount); + void RefModelInfo(int32 modelId); + void Init(void); + bool CanBeDeleted(void); + + static void DeleteAllMissionObjects(); + static void DeleteAllTempObjects(); + static void DeleteAllTempObjectsInArea(CVector point, float fRadius); +}; + +VALIDATE_SIZE(CObject, 0x198); diff --git a/src/objects/ObjectData.cpp b/src/objects/ObjectData.cpp new file mode 100644 index 0000000..589cc3f --- /dev/null +++ b/src/objects/ObjectData.cpp @@ -0,0 +1,98 @@ +#include "common.h" + +#include "main.h" +#include "ModelInfo.h" +#include "Object.h" +#include "FileMgr.h" +#include "ObjectData.h" + +CObjectInfo CObjectData::ms_aObjectInfo[NUMOBJECTINFO]; + +// Another ugly file reader +void +CObjectData::Initialise(const char *filename) +{ + char *p, *lp; + char line[1024], name[256]; + int id; + float percentSubmerged; + int damageEffect, responseCase, camAvoid; + CBaseModelInfo *mi; + + CFileMgr::SetDir(""); + CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r"); + + id = 0; + p = (char*)work_buff; + while(*p != '*'){ + // skip over white space and comments + while(*p == ' ' || *p == '\n' || *p == '\r' || *p == ';') + if(*p == ';') + while(*p != '\n' && *p != '*') + p++; + else + p++; + + if(*p == '*') + break; + + // read one line + lp = line; + while(*p != '\n' && *p != '*'){ + *lp++ = *p == ',' ? ' ' : *p; + p++; + } + if(*p == '\n') + p++; + *lp = '\0'; // FIX: game wrote '\n' here + + assert(id < NUMOBJECTINFO); + sscanf(line, "%s %f %f %f %f %f %f %f %d %d %d", name, + &ms_aObjectInfo[id].m_fMass, + &ms_aObjectInfo[id].m_fTurnMass, + &ms_aObjectInfo[id].m_fAirResistance, + &ms_aObjectInfo[id].m_fElasticity, + &percentSubmerged, + &ms_aObjectInfo[id].m_fUprootLimit, + &ms_aObjectInfo[id].m_fCollisionDamageMultiplier, + &damageEffect, &responseCase, &camAvoid); + + ms_aObjectInfo[id].m_fBuoyancy = 100.0f/percentSubmerged * GRAVITY *ms_aObjectInfo[id].m_fMass; + ms_aObjectInfo[id].m_nCollisionDamageEffect = damageEffect; + ms_aObjectInfo[id].m_nSpecialCollisionResponseCases = responseCase; + ms_aObjectInfo[id].m_bCameraToAvoidThisObject = camAvoid; + + mi = CModelInfo::GetModelInfo(name, nil); + if(mi) + mi->SetObjectID(id++); + else + debug("CObjectData: Cannot find object %s\n", name); + } +} + +void +CObjectData::SetObjectData(int32 modelId, CObject &object) +{ + CObjectInfo *objinfo; + + if(CModelInfo::GetModelInfo(modelId)->GetObjectID() == -1) + return; + + objinfo = &ms_aObjectInfo[CModelInfo::GetModelInfo(modelId)->GetObjectID()]; + + object.m_fMass = objinfo->m_fMass; + object.m_fTurnMass = objinfo->m_fTurnMass; + object.m_fAirResistance = objinfo->m_fAirResistance; + object.m_fElasticity = objinfo->m_fElasticity; + object.m_fBuoyancy = objinfo->m_fBuoyancy; + object.m_fUprootLimit = objinfo->m_fUprootLimit; + object.m_fCollisionDamageMultiplier = objinfo->m_fCollisionDamageMultiplier; + object.m_nCollisionDamageEffect = objinfo->m_nCollisionDamageEffect; + object.m_nSpecialCollisionResponseCases = objinfo->m_nSpecialCollisionResponseCases; + object.m_bCameraToAvoidThisObject = objinfo->m_bCameraToAvoidThisObject; + if(object.m_fMass >= 99998.0f){ + object.bInfiniteMass = true; + object.bAffectedByGravity = false; + object.bExplosionProof = true; + } +} diff --git a/src/objects/ObjectData.h b/src/objects/ObjectData.h new file mode 100644 index 0000000..e25c1ae --- /dev/null +++ b/src/objects/ObjectData.h @@ -0,0 +1,27 @@ +#pragma once + +class CObject; + +class CObjectInfo +{ +public: + float m_fMass; + float m_fTurnMass; + float m_fAirResistance; + float m_fElasticity; + float m_fBuoyancy; + float m_fUprootLimit; + float m_fCollisionDamageMultiplier; + uint8 m_nCollisionDamageEffect; + uint8 m_nSpecialCollisionResponseCases; + bool m_bCameraToAvoidThisObject; +}; +VALIDATE_SIZE(CObjectInfo, 0x20); + +class CObjectData +{ + static CObjectInfo ms_aObjectInfo[NUMOBJECTINFO]; +public: + static void Initialise(const char *filename); + static void SetObjectData(int32 modelId, CObject &object); +}; diff --git a/src/objects/ParticleObject.cpp b/src/objects/ParticleObject.cpp new file mode 100644 index 0000000..5d480ec --- /dev/null +++ b/src/objects/ParticleObject.cpp @@ -0,0 +1,1365 @@ +#include "common.h" + +#include "ParticleObject.h" +#include "Timer.h" +#include "General.h" +#include "ParticleMgr.h" +#include "Particle.h" +#include "Camera.h" +#include "Game.h" +#include "DMAudio.h" +#include "screendroplets.h" + +#ifdef COMPATIBLE_SAVES +#define PARTICLE_OBJECT_SIZEOF 0x88 +#else +#define PARTICLE_OBJECT_SIZEOF sizeof(CParticleObject) +#endif + + +CParticleObject gPObjectArray[MAX_PARTICLEOBJECTS]; + +CParticleObject *CParticleObject::pCloseListHead; +CParticleObject *CParticleObject::pFarListHead; +CParticleObject *CParticleObject::pUnusedListHead; + +CAudioHydrant List[MAX_AUDIOHYDRANTS]; + +CAudioHydrant *CAudioHydrant::Get(int n) { return &List[n]; } + +bool +CAudioHydrant::Add(CParticleObject *particleobject) +{ + for ( int32 i = 0; i < MAX_AUDIOHYDRANTS; i++ ) + { + if ( List[i].AudioEntity == AEHANDLE_NONE ) + { + List[i].AudioEntity = DMAudio.CreateEntity(AUDIOTYPE_FIREHYDRANT, particleobject); + + if ( AEHANDLE_IS_FAILED(List[i].AudioEntity) ) + return false; + + DMAudio.SetEntityStatus(List[i].AudioEntity, TRUE); + + List[i].pParticleObject = particleobject; + + return true; + } + } + + return false; +} + +void +CAudioHydrant::Remove(CParticleObject *particleobject) +{ + for ( int32 i = 0; i < MAX_AUDIOHYDRANTS; i++ ) + { + if ( List[i].pParticleObject == particleobject ) + { + DMAudio.DestroyEntity(List[i].AudioEntity); + List[i].AudioEntity = AEHANDLE_NONE; + List[i].pParticleObject = NULL; + } + } +} + +CParticleObject::CParticleObject() : + CPlaceable(), + m_nFrameCounter(0), + m_nState(POBJECTSTATE_INITIALISED), + m_pNext(NULL), + m_pPrev(NULL), + m_nRemoveTimer(0) + +{ + ; +} + +CParticleObject::~CParticleObject() +{ + +} + +void +CParticleObject::Initialise() +{ + pCloseListHead = NULL; + pFarListHead = NULL; + + pUnusedListHead = &gPObjectArray[0]; + + for ( int32 i = 0; i < MAX_PARTICLEOBJECTS; i++ ) + { + if ( i == 0 ) + gPObjectArray[i].m_pPrev = NULL; + else + gPObjectArray[i].m_pPrev = &gPObjectArray[i - 1]; + + if ( i == MAX_PARTICLEOBJECTS-1 ) + gPObjectArray[i].m_pNext = NULL; + else + gPObjectArray[i].m_pNext = &gPObjectArray[i + 1]; + + gPObjectArray[i].m_nState = POBJECTSTATE_FREE; + } +} + +CParticleObject * +CParticleObject::AddObject(uint16 type, CVector const &pos, uint8 remove) +{ + CRGBA color(0, 0, 0, 0); + CVector target(0.0f, 0.0f, 0.0f); + return AddObject(type, pos, target, 0.0f, 0, color, remove); +} + +CParticleObject * +CParticleObject::AddObject(uint16 type, CVector const &pos, float size, uint8 remove) +{ + CRGBA color(0, 0, 0, 0); + CVector target(0.0f, 0.0f, 0.0f); + return AddObject(type, pos, target, size, 0, color, remove); +} + +CParticleObject * +CParticleObject::AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint8 remove) +{ + CRGBA color(0, 0, 0, 0); + return AddObject(type, pos, target, size, 0, color, remove); +} + +CParticleObject * +CParticleObject::AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, RwRGBA const &color, uint8 remove) +{ + CParticleObject *pobj = pUnusedListHead; + + ASSERT(pobj != NULL); + + if ( pobj == NULL ) + { + printf("Error: No particle objects available!\n"); + return NULL; + } + + MoveToList(&pUnusedListHead, &pCloseListHead, pobj); + + pobj->m_nState = POBJECTSTATE_UPDATE_CLOSE; + pobj->m_Type = (eParticleObjectType)type; + + pobj->SetPosition(pos); + pobj->m_vecTarget = target; + + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_nFrameCounter = 0; + + pobj->m_bRemove = remove; + + pobj->m_pParticle = NULL; + + if ( lifeTime != 0 ) + pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds() + lifeTime; + else + pobj->m_nRemoveTimer = 0; + + if ( color.alpha != 0 ) + pobj->m_Color = color; + else + pobj->m_Color.alpha = 0; + + pobj->m_fSize = size; + pobj->m_fRandVal = 0.0f; + + if ( type <= POBJECT_CATALINAS_SHOTGUNFLASH ) + { + switch ( type ) + { + case POBJECT_PAVEMENT_STEAM: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 3; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 8; + break; + } + + case POBJECT_PAVEMENT_STEAM_SLOWMOTION: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY_SLOWMOTION; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 8; + break; + } + + case POBJECT_WALL_STEAM: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 3; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 8; + break; + } + + case POBJECT_WALL_STEAM_SLOWMOTION: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY_SLOWMOTION; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 8; + break; + } + + case POBJECT_DARK_SMOKE: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 3; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 8; + pobj->m_Color = CRGBA(16, 16, 16, 255); + break; + } + + case POBJECT_FIRE_HYDRANT: + { + pobj->m_ParticleType = PARTICLE_WATER_HYDRANT; + pobj->m_nNumEffectCycles = 4; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.3f); + pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds() + 5000; + CAudioHydrant::Add(pobj); + break; + } + + case POBJECT_CAR_WATER_SPLASH: + case POBJECT_PED_WATER_SPLASH: + { + pobj->m_ParticleType = PARTICLE_CAR_SPLASH; + pobj->m_nNumEffectCycles = 0; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 1; +#else + pobj->m_nSkipFrames = 3; +#endif + pobj->m_nCreationChance = 0; +#ifdef SCREEN_DROPLETS + ScreenDroplets::RegisterSplash(pobj); +#endif + break; + } + + case POBJECT_SPLASHES_AROUND: + { + pobj->m_ParticleType = PARTICLE_SPLASH; +#ifdef PC_PARTICLE + pobj->m_nNumEffectCycles = 15; +#else + pobj->m_nNumEffectCycles = 30; +#endif + pobj->m_nSkipFrames = 2; + pobj->m_nCreationChance = 0; + break; + } + + case POBJECT_SMALL_FIRE: + { + pobj->m_ParticleType = PARTICLE_FLAME; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 2; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 2; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f); + break; + } + + case POBJECT_BIG_FIRE: + { + pobj->m_ParticleType = PARTICLE_FLAME; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 2; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 4; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f); + break; + } + + case POBJECT_DRY_ICE: + { + pobj->m_ParticleType = PARTICLE_SMOKE; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f); + break; + } + + case POBJECT_DRY_ICE_SLOWMOTION: + { + pobj->m_ParticleType = PARTICLE_SMOKE_SLOWMOTION; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f); + break; + } + + case POBJECT_FIRE_TRAIL: + { + pobj->m_ParticleType = PARTICLE_EXPLOSION_MEDIUM; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 3; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 2; + pobj->m_fRandVal = 0.01f; + break; + } + + case POBJECT_SMOKE_TRAIL: + { + pobj->m_ParticleType = PARTICLE_FIREBALL_SMOKE; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 2; + pobj->m_fRandVal = 0.02f; + break; + } + + case POBJECT_FIREBALL_AND_SMOKE: + { + pobj->m_ParticleType = PARTICLE_FLAME; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 2; + pobj->m_fRandVal = 0.1f; + break; + } + + case POBJECT_ROCKET_TRAIL: + { + pobj->m_ParticleType = PARTICLE_FLAME; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 2; + pobj->m_nCreationChance = 8; + pobj->m_fRandVal = 0.1f; + break; + } + + case POBJECT_EXPLOSION_ONCE: + { + pobj->m_ParticleType = PARTICLE_EXPLOSION_LARGE; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds(); + break; + } + + case POBJECT_CATALINAS_GUNFLASH: + case POBJECT_CATALINAS_SHOTGUNFLASH: + { + pobj->m_ParticleType = PARTICLE_GUNFLASH_NOANIM; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds(); + pobj->m_vecTarget.Normalise(); + break; + } + } + } + + return pobj; +} + +void +CParticleObject::RemoveObject(void) +{ + switch ( this->m_nState ) + { + case POBJECTSTATE_UPDATE_CLOSE: + { + MoveToList(&pCloseListHead, &pUnusedListHead, this); + this->m_nState = POBJECTSTATE_FREE; + break; + } + case POBJECTSTATE_UPDATE_FAR: + { + MoveToList(&pFarListHead, &pUnusedListHead, this); + this->m_nState = POBJECTSTATE_FREE; + break; + } + } +} + +void +CParticleObject::UpdateAll(void) +{ + { + CParticleObject *pobj = pCloseListHead; + CParticleObject *nextpobj; + if ( pobj != NULL ) + { + do + { + nextpobj = pobj->m_pNext; + pobj->UpdateClose(); + pobj = nextpobj; + } + while ( nextpobj != NULL ); + } + } + + { + int32 frame = CTimer::GetFrameCounter() & 31; + int32 counter = 0; + + CParticleObject *pobj = pFarListHead; + CParticleObject *nextpobj; + if ( pobj != NULL ) + { + do + { + nextpobj = pobj->m_pNext; + + if ( counter == frame ) + { + pobj->UpdateFar(); + frame += 32; + } + + counter++; + + pobj = nextpobj; + } + while ( nextpobj != NULL ); + } + } +} + +void CParticleObject::UpdateClose(void) +{ + if ( !CGame::playingIntro ) + { + if ( (this->GetPosition() - TheCamera.GetPosition()).MagnitudeSqr2D() > SQR(100.0f) ) + { + if ( this->m_bRemove ) + { + if ( this->m_Type == POBJECT_FIRE_HYDRANT ) + CAudioHydrant::Remove(this); + + MoveToList(&pCloseListHead, &pUnusedListHead, this); + this->m_nState = POBJECTSTATE_FREE; + } + else + { + MoveToList(&pCloseListHead, &pFarListHead, this); + this->m_nState = POBJECTSTATE_UPDATE_FAR; + } + + return; + } + } + + if ( ++this->m_nFrameCounter >= this->m_nSkipFrames ) + { + this->m_nFrameCounter = 0; + + int32 randVal; + if ( this->m_nCreationChance != 0 ) + randVal = CGeneral::GetRandomNumber() % this->m_nCreationChance; + + if ( this->m_nCreationChance == 0 + || randVal == 0 && this->m_nCreationChance < 0 + || randVal != 0 && this->m_nCreationChance > 0) + { + switch ( this->m_Type ) + { + case POBJECT_SMALL_FIRE: + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + CVector flamevel; + + flamevel.x = vel.x; + flamevel.y = vel.y; + flamevel.z = CGeneral::GetRandomNumberInRange(0.0125f*size, 0.1f*size); + + CParticle::AddParticle(PARTICLE_FLAME, pos, flamevel, NULL, size); + + + CVector possmoke = pos; + + possmoke.x += CGeneral::GetRandomNumberInRange(0.625f*-size, size*0.625f); + possmoke.y += CGeneral::GetRandomNumberInRange(0.625f*-size, size*0.625f); + possmoke.z += CGeneral::GetRandomNumberInRange(0.625f* size, size*2.5f); + + CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, possmoke, vel); + + break; + } + + case POBJECT_BIG_FIRE: + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + + float s = 0.7f*size; + + CVector flamevel; + + flamevel.x = vel.x; + flamevel.y = vel.y; + flamevel.z = CGeneral::GetRandomNumberInRange(0.0125f*s, 0.1f*s); + + float flamesize = 0.8f*size; + + CParticle::AddParticle(PARTICLE_FLAME, pos, flamevel, NULL, flamesize); + + + for ( int32 i = 0; i < 4; i++ ) + { + CVector smokepos = pos; + + smokepos.x += CGeneral::GetRandomNumberInRange(0.625f*-size, 0.625f*size); + smokepos.y += CGeneral::GetRandomNumberInRange(0.625f*-size, 0.625f*size); + smokepos.z += CGeneral::GetRandomNumberInRange(0.625f* size, 3.5f *size); + + CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, smokepos, vel); + } + + break; + } + + case POBJECT_FIREBALL_AND_SMOKE: + { + if ( this->m_pParticle == NULL ) + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + CVector expvel = 1.2f*vel; + float expsize = 1.2f*size; + this->m_pParticle = CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, expvel, NULL, expsize); + } + else + { + CVector pos = this->GetPosition(); // this->m_pParticle->m_vecPosition ? + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + CVector veloffset = 0.35f*vel; + CVector fireballvel = vel; + + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + fireballvel.x += CGeneral::GetRandomNumberInRange(-veloffset.x, veloffset.x); + fireballvel.y += CGeneral::GetRandomNumberInRange(-veloffset.y, veloffset.y); + fireballvel.z += CGeneral::GetRandomNumberInRange(-veloffset.z, veloffset.z); + + CParticle::AddParticle(PARTICLE_FIREBALL_SMOKE, pos, fireballvel, NULL, size); + } + } + + break; + } + + case POBJECT_ROCKET_TRAIL: + { + if ( this->m_pParticle == NULL ) + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + this->m_pParticle = CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, vel, NULL, size); + } + else + { + CVector pos = this->m_pParticle->m_vecPosition; + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + float fireballsize = size * 1.5f; + CVector fireballvel = vel * -0.8f; + + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CParticle::AddParticle(PARTICLE_FIREBALL_SMOKE, pos, fireballvel, NULL, fireballsize); + } + } + + break; + } + + case POBJECT_FIRE_TRAIL: + { + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CVector vel = this->m_vecTarget; + + if ( vel.x != 0.0f ) + vel.x += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + if ( vel.y != 0.0f ) + vel.y += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + if ( vel.z != 0.0f ) + vel.z += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), vel, NULL, this->m_fSize, + CGeneral::GetRandomNumberInRange(-6.0f, 6.0f)); + } + + break; + } + + case POBJECT_PED_WATER_SPLASH: + { +#ifdef PC_PARTICLE + CRGBA colorsmoke(255, 255, 255, 196); + + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + + for ( int32 i = 0; i < 3; i++ ) + { + int32 angle = 90 * i; + float fCos = CParticle::Cos(angle); + float fSin = CParticle::Sin(angle); + + CVector splashpos; + CVector splashvel; + + splashpos = pos + CVector(0.75f*fCos, 0.75f*fSin, 0.0f); + splashvel = vel + CVector(0.05f*fCos, 0.05f*fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f)); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color); + + + splashpos = pos + CVector(0.75f*fCos, 0.75f*-fSin, 0.0f); + splashvel = vel + CVector(0.05f*fCos, 0.05f*-fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f)); + + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color); + + + splashpos = pos + CVector(0.75f*-fCos, 0.75f*fSin, 0.0f); + splashvel = vel + CVector(0.05f*-fCos, 0.05f*fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f)); + + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color); + + + splashpos = pos + CVector(0.75f*-fCos, 0.75f*-fSin, 0.0f); + splashvel = vel + CVector(0.05f*-fCos, 0.05f*-fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f)); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color); + } + + for ( int32 i = 0; i < 1; i++ ) + { + int32 angle = 180 * (i + 1); + + float fCos = CParticle::Cos(angle); + float fSin = CParticle::Sin(angle); + + CVector splashpos; + CVector splashvel; + + splashpos = pos + CVector(0.5f*fCos, 0.5f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color); + + + splashpos = pos + CVector(0.5f*fCos, 0.5f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color); + + + splashpos = pos + CVector(0.5f*-fCos, 0.5f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color); + + + splashpos = pos + CVector(0.5f*-fCos, 0.5f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color); + } +#else + CVector pos; + CVector vel; + + for ( int32 i = -2; i < 2; i++ ) + { + pos = this->GetPosition(); + pos += CVector(-0.75f, 0.5f * float(i), 0.0f); + + vel = this->m_vecTarget; + vel.x += -1.5 * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color); + + pos = this->GetPosition(); + pos += CVector(0.75f, 0.5f * float(i), 0.0f); + + vel = this->m_vecTarget; + vel.x += 1.5f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color); + + pos = this->GetPosition(); + pos += CVector(0.5f * float(i), -0.75, 0.0f); + + vel = this->m_vecTarget; + vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += -1.5f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color); + + + pos = this->GetPosition(); + pos += CVector(0.5f * float(i), 0.75, 0.0f); + + vel = this->m_vecTarget; + vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += 1.5f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color); + } + + + for ( int32 i = 0; i < 4; i++ ) + { + pos = this->GetPosition(); + + pos.x += CGeneral::GetRandomNumberInRange(-1.5f, 1.5f); + pos.y += CGeneral::GetRandomNumberInRange(-1.5f, 1.5f); + pos.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + + vel = this->m_vecTarget; + CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color); + } +#endif + break; + } + + case POBJECT_CAR_WATER_SPLASH: + { +#ifdef PC_PARTICLE + CRGBA colorsmoke(255, 255, 255, 196); + + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + + float size = CGeneral::GetRandomNumberInRange(1.0f, 2.5f); + + for ( int32 i = 0; i < 3; i++ ) + { + int32 angle = 90 * i; + + float fCos = CParticle::Cos(angle); + float fSin = CParticle::Sin(angle); + + CVector splashpos; + CVector splashvel; + + splashpos = pos + CVector(2.0f*fCos, 2.0f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + + splashpos = pos + CVector(2.0f*fCos, 2.0f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + splashpos = pos + CVector(2.0f*-fCos, 2.0f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + splashpos = pos + CVector(2.0f*-fCos, 2.0f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + } + + for ( int32 i = 0; i < 1; i++ ) + { + int32 angle = 180 * (i + 1); + + float fCos = CParticle::Cos(angle); + float fSin = CParticle::Sin(angle); + + CVector splashpos; + CVector splashvel; + + + splashpos = pos + CVector(1.25f*fCos, 1.25f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + splashpos = pos + CVector(1.25f*fCos, 1.25f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + splashpos = pos + CVector(1.25f*-fCos, 1.25f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + splashpos = pos + CVector(1.25f*-fCos, 1.25f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + } +#else + CVector pos; + CVector vel; + + for ( int32 i = -3; i < 4; i++ ) + { + pos = this->GetPosition(); + pos += CVector(-1.5f, 0.5f * float(i), 0.0f); + + + vel = this->m_vecTarget; + vel.x += -3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color); + + + pos = this->GetPosition(); + pos += CVector(1.5f, 0.5f * float(i), 0.0f); + + vel = this->m_vecTarget; + vel.x += 3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color); + + + pos = this->GetPosition(); + pos += CVector(0.5f * float(i), -1.5f, 0.0f); + + vel = this->m_vecTarget; + vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += -3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color); + + + pos = this->GetPosition(); + pos += CVector(0.5f * float(i), 1.5f, 0.0f); + + + vel = this->m_vecTarget; + vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += 3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color); + } + + for ( int32 i = 0; i < 8; i++ ) + { + pos = this->GetPosition(); + pos.x += CGeneral::GetRandomNumberInRange(-3.0f, 3.0f); + pos.y += CGeneral::GetRandomNumberInRange(-3.0f, 3.0f); + + vel = this->m_vecTarget; + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color); + } +#endif + break; + } + + case POBJECT_SPLASHES_AROUND: + { + CVector pos = this->GetPosition(); + float size = this->m_fSize; + + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CVector splashpos = pos; + + splashpos.x += CGeneral::GetRandomNumberInRange(-size, size); + splashpos.y += CGeneral::GetRandomNumberInRange(-size, size); + + if ( CGeneral::GetRandomNumber() & 1 ) + { + CParticle::AddParticle(PARTICLE_RAIN_SPLASH, splashpos, CVector(0.0f, 0.0f, 0.0f), + NULL, 0.1f, this->m_Color); + } + else + { + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, splashpos, CVector(0.0f, 0.0f, 0.0f), + NULL, 0.12f, this->m_Color); + } + } + + break; + } + + case POBJECT_CATALINAS_GUNFLASH: + { + CRGBA flashcolor(120, 120, 120, 255); + + CVector vel = this->m_vecTarget; + CVector pos = this->GetPosition(); + + float size = 1.0f; + if ( this->m_fSize != 0.0f ) + size = this->m_fSize; + + CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.12f*size, flashcolor); + + pos += size * (0.06f * vel); + CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.08f*size, flashcolor); + + pos += size * (0.04f * vel); + CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.04f*size, flashcolor); + + CVector smokepos = this->GetPosition(); + CVector smokevel = 0.1f * vel; + CParticle::AddParticle(PARTICLE_GUNSMOKE2, smokepos, smokevel, NULL, 0.005f*size); + + break; + } + + case POBJECT_CATALINAS_SHOTGUNFLASH: + { + CRGBA flashcolor(120, 120, 120, 255); + + CVector vel = this->m_vecTarget; + + float size = 1.0f; + if ( this->m_fSize != 0.0f ) + size = this->m_fSize; + + CVector pos = this->GetPosition(); + + CVector velstep = size * (0.1f * vel); + CVector flashpos = pos; + + flashpos += velstep; + CParticle::AddParticle(PARTICLE_GUNFLASH, flashpos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.0f, flashcolor); + + flashpos += velstep; + CParticle::AddParticle(PARTICLE_GUNFLASH, flashpos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.15f*size, flashcolor); + + flashpos += velstep; + CParticle::AddParticle(PARTICLE_GUNFLASH, flashpos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.2f*size, flashcolor); + + + CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.0f, flashcolor); + + CVector smokepos = this->GetPosition(); + CVector smokevel = 0.1f*vel; + CParticle::AddParticle(PARTICLE_GUNSMOKE2, smokepos, smokevel, NULL, 0.1f*size); + + break; + } + + default: + { + if ( this->m_fRandVal != 0.0f ) + { + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CVector vel = this->m_vecTarget; + + if ( vel.x != 0.0f ) + vel.x += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + if ( vel.y != 0.0f ) + vel.y += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + if ( vel.z != 0.0f ) + vel.z += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), vel, NULL, + this->m_fSize, this->m_Color); + } + } + else + { + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), this->m_vecTarget, NULL, + this->m_fSize, this->m_Color); + } + } + + break; + } + } + } + } + + if ( this->m_nRemoveTimer != 0 && this->m_nRemoveTimer < CTimer::GetTimeInMilliseconds() ) + { + MoveToList(&pCloseListHead, &pUnusedListHead, this); + this->m_nState = POBJECTSTATE_FREE; + + if ( this->m_Type == POBJECT_FIRE_HYDRANT ) + CAudioHydrant::Remove(this); + } +} + +void +CParticleObject::UpdateFar(void) +{ + if ( this->m_nRemoveTimer != 0 && this->m_nRemoveTimer < CTimer::GetTimeInMilliseconds() ) + { + MoveToList(&pFarListHead, &pUnusedListHead, this); + this->m_nState = POBJECTSTATE_FREE; + + if ( this->m_Type == POBJECT_FIRE_HYDRANT ) + CAudioHydrant::Remove(this); + } + + CVector2D dist = this->GetPosition() - TheCamera.GetPosition(); + if ( dist.MagnitudeSqr() < SQR(100.0f)/*10000.0f*/ ) + { + MoveToList(&pFarListHead, &pCloseListHead, this); + this->m_nState = POBJECTSTATE_UPDATE_CLOSE; + } +} + +#ifdef COMPATIBLE_SAVES +static inline void +SaveOneParticle(CParticleObject *p, uint8 *&buffer) +{ +#define SkipBuf(buf, num) buf += num +#define ZeroBuf(buf, num) memset(buf, 0, num); SkipBuf(buf, num) +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipBuf(buf, sizeof(data)) + // CPlaceable + { + ZeroBuf(buffer, 4); + CopyToBuf(buffer, p->GetMatrix().f); + ZeroBuf(buffer, 4); + CopyToBuf(buffer, p->GetMatrix().m_hasRwMatrix); + ZeroBuf(buffer, 3); + } + + // CParticleObject + { + ZeroBuf(buffer, 4); + ZeroBuf(buffer, 4); + ZeroBuf(buffer, 4); + CopyToBuf(buffer, p->m_nRemoveTimer); + CopyToBuf(buffer, p->m_Type); + CopyToBuf(buffer, p->m_ParticleType); + CopyToBuf(buffer, p->m_nNumEffectCycles); + CopyToBuf(buffer, p->m_nSkipFrames); + CopyToBuf(buffer, p->m_nFrameCounter); + CopyToBuf(buffer, p->m_nState); + ZeroBuf(buffer, 2); + CopyToBuf(buffer, p->m_vecTarget); + CopyToBuf(buffer, p->m_fRandVal); + CopyToBuf(buffer, p->m_fSize); + CopyToBuf(buffer, p->m_Color); + CopyToBuf(buffer, p->m_bRemove); + CopyToBuf(buffer, p->m_nCreationChance); + ZeroBuf(buffer, 2); + } +#undef SkipBuf +#undef ZeroBuf +#undef CopyToBuf +} +#endif + +bool +CParticleObject::SaveParticle(uint8 *buffer, uint32 *length) +{ + ASSERT( buffer != NULL ); + ASSERT( length != NULL ); + + int32 numObjects = 0; + + for ( CParticleObject *p = pCloseListHead; p != NULL; p = p->m_pNext ) + ++numObjects; + + for ( CParticleObject *p = pFarListHead; p != NULL; p = p->m_pNext ) + ++numObjects; + + *(int32 *)buffer = numObjects; + buffer += sizeof(int32); + + int32 objectsLength = PARTICLE_OBJECT_SIZEOF * (numObjects + 1); + int32 dataLength = objectsLength + sizeof(int32); + + for ( CParticleObject *p = pCloseListHead; p != NULL; p = p->m_pNext ) + { +#ifdef COMPATIBLE_SAVES + SaveOneParticle(p, buffer); +#else +#ifdef THIS_IS_STUPID + *(CParticleObject*)buffer = *p; +#else + memcpy(buffer, p, sizeof(CParticleObject)); +#endif + buffer += sizeof(CParticleObject); +#endif + } + + for ( CParticleObject *p = pFarListHead; p != NULL; p = p->m_pNext ) + { +#ifdef COMPATIBLE_SAVES + SaveOneParticle(p, buffer); +#else +#ifdef THIS_IS_STUPID + *(CParticleObject*)buffer = *p; +#else + memcpy(buffer, p, sizeof(CParticleObject)); +#endif + buffer += sizeof(CParticleObject); +#endif + } + + *length = dataLength; + + return true; +} + +bool +CParticleObject::LoadParticle(uint8 *buffer, uint32 length) +{ + ASSERT( buffer != NULL ); + + RemoveAllParticleObjects(); + + int32 numObjects = *(int32 *)buffer; + buffer += sizeof(int32); + + if ( length != PARTICLE_OBJECT_SIZEOF * (numObjects + 1) + sizeof(int32) ) + return false; + + if ( numObjects == 0 ) + return true; + + + int32 i = 0; + while ( i < numObjects ) + { + CParticleObject *dst = pUnusedListHead; +#ifndef COMPATIBLE_SAVES + CParticleObject *src = (CParticleObject *)buffer; + buffer += sizeof(CParticleObject); +#endif + + if ( dst == NULL ) + return false; + + MoveToList(&pUnusedListHead, &pCloseListHead, dst); + +#ifndef COMPATIBLE_SAVES + dst->m_nState = POBJECTSTATE_UPDATE_CLOSE; + dst->m_Type = src->m_Type; + dst->m_ParticleType = src->m_ParticleType; + dst->SetPosition(src->GetPosition()); + dst->m_vecTarget = src->m_vecTarget; + dst->m_nFrameCounter = src->m_nFrameCounter; + dst->m_bRemove = src->m_bRemove; + dst->m_pParticle = NULL; + dst->m_nRemoveTimer = src->m_nRemoveTimer; + dst->m_Color = src->m_Color; + dst->m_fSize = src->m_fSize; + dst->m_fRandVal = src->m_fRandVal; + dst->m_nNumEffectCycles = src->m_nNumEffectCycles; + dst->m_nSkipFrames = src->m_nSkipFrames; + dst->m_nCreationChance = src->m_nCreationChance; +#else + dst->m_nState = POBJECTSTATE_UPDATE_CLOSE; + dst->m_pParticle = NULL; + +#define SkipBuf(buf, num) buf += num +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipBuf(buf, sizeof(data)) + // CPlaceable + { + SkipBuf(buffer, 4); + CMatrix matrix; + CopyFromBuf(buffer, matrix.f); + SkipBuf(buffer, 4); + CopyFromBuf(buffer, matrix.m_hasRwMatrix); + SkipBuf(buffer, 3); + dst->SetPosition(matrix.GetPosition()); + } + + // CParticleObject + { + SkipBuf(buffer, 4); + SkipBuf(buffer, 4); + SkipBuf(buffer, 4); + CopyFromBuf(buffer, dst->m_nRemoveTimer); + CopyFromBuf(buffer, dst->m_Type); + CopyFromBuf(buffer, dst->m_ParticleType); + CopyFromBuf(buffer, dst->m_nNumEffectCycles); + CopyFromBuf(buffer, dst->m_nSkipFrames); + CopyFromBuf(buffer, dst->m_nFrameCounter); + SkipBuf(buffer, 2); + SkipBuf(buffer, 2); + CopyFromBuf(buffer, dst->m_vecTarget); + CopyFromBuf(buffer, dst->m_fRandVal); + CopyFromBuf(buffer, dst->m_fSize); + CopyFromBuf(buffer, dst->m_Color); + CopyFromBuf(buffer, dst->m_bRemove); + CopyFromBuf(buffer, dst->m_nCreationChance); + SkipBuf(buffer, 2); + } +#undef CopyFromBuf +#undef SkipBuf +#endif + + i++; + } + + return true; +} + +void +CParticleObject::RemoveAllParticleObjects(void) +{ + pUnusedListHead = &gPObjectArray[0]; + + pCloseListHead = NULL; + pFarListHead = NULL; + + for ( int32 i = 0; i < MAX_PARTICLEOBJECTS; i++ ) + { + if ( i == 0 ) + gPObjectArray[i].m_pPrev = NULL; + else + gPObjectArray[i].m_pPrev = &gPObjectArray[i - 1]; + + if ( i == MAX_PARTICLEOBJECTS-1 ) + gPObjectArray[i].m_pNext = NULL; + else + gPObjectArray[i].m_pNext = &gPObjectArray[i + 1]; + + gPObjectArray[i].m_nState = POBJECTSTATE_FREE; + } +} + +void +CParticleObject::MoveToList(CParticleObject **from, CParticleObject **to, CParticleObject *obj) +{ + ASSERT( from != NULL ); + ASSERT( to != NULL ); + ASSERT( obj != NULL ); + + if ( obj->m_pPrev == NULL ) + { + *from = obj->m_pNext; + if ( *from ) + (*from)->m_pPrev = NULL; + } + else + { + if ( obj->m_pNext == NULL ) + obj->m_pPrev->m_pNext = NULL; + else + { + obj->m_pNext->m_pPrev = obj->m_pPrev; + obj->m_pPrev->m_pNext = obj->m_pNext; + } + } + + obj->m_pNext = *to; + obj->m_pPrev = NULL; + *to = obj; + + if ( obj->m_pNext ) + obj->m_pNext->m_pPrev = obj; +} diff --git a/src/objects/ParticleObject.h b/src/objects/ParticleObject.h new file mode 100644 index 0000000..e4e7fcd --- /dev/null +++ b/src/objects/ParticleObject.h @@ -0,0 +1,108 @@ +#pragma once + +#include "AudioManager.h" +#include "ParticleType.h" +#include "Placeable.h" + +#define MAX_PARTICLEOBJECTS 100 +#define MAX_AUDIOHYDRANTS 8 + +enum eParticleObjectType +{ + POBJECT_PAVEMENT_STEAM, + POBJECT_PAVEMENT_STEAM_SLOWMOTION, + POBJECT_WALL_STEAM, + POBJECT_WALL_STEAM_SLOWMOTION, + POBJECT_DARK_SMOKE, + POBJECT_FIRE_HYDRANT, + POBJECT_CAR_WATER_SPLASH, + POBJECT_PED_WATER_SPLASH, + POBJECT_SPLASHES_AROUND, + POBJECT_SMALL_FIRE, + POBJECT_BIG_FIRE, + POBJECT_DRY_ICE, + POBJECT_DRY_ICE_SLOWMOTION, + POBJECT_FIRE_TRAIL, + POBJECT_SMOKE_TRAIL, + POBJECT_FIREBALL_AND_SMOKE, + POBJECT_ROCKET_TRAIL, + POBJECT_EXPLOSION_ONCE, + POBJECT_CATALINAS_GUNFLASH, + POBJECT_CATALINAS_SHOTGUNFLASH, +}; + +enum eParticleObjectState +{ + POBJECTSTATE_INITIALISED = 0, + POBJECTSTATE_UPDATE_CLOSE, + POBJECTSTATE_UPDATE_FAR, + POBJECTSTATE_FREE, +}; + +class CParticle; + +class CParticleObject : public CPlaceable +{ +public: + CParticleObject *m_pNext; + CParticleObject *m_pPrev; + CParticle *m_pParticle; + uint32 m_nRemoveTimer; + eParticleObjectType m_Type; + tParticleType m_ParticleType; + uint8 m_nNumEffectCycles; + uint8 m_nSkipFrames; + uint16 m_nFrameCounter; + uint16 m_nState; + CVector m_vecTarget; + float m_fRandVal; + float m_fSize; + CRGBA m_Color; + uint8 m_bRemove; + int8 m_nCreationChance; + + static CParticleObject *pCloseListHead; + static CParticleObject *pFarListHead; + static CParticleObject *pUnusedListHead; + + CParticleObject(); + ~CParticleObject(); + + static void Initialise(void); + + static CParticleObject *AddObject(uint16 type, CVector const &pos, uint8 remove); + static CParticleObject *AddObject(uint16 type, CVector const &pos, float size, uint8 remove); + static CParticleObject *AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint8 remove); + static CParticleObject *AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, RwRGBA const &color, uint8 remove); + + void RemoveObject(void); + + static void UpdateAll(void); + void UpdateClose(void); + void UpdateFar(void); + + static bool SaveParticle(uint8 *buffer, uint32 *length); + static bool LoadParticle(uint8 *buffer, uint32 length); + + static void RemoveAllParticleObjects(void); + static void MoveToList(CParticleObject **from, CParticleObject **to, CParticleObject *obj); +}; + +extern CParticleObject gPObjectArray[MAX_PARTICLEOBJECTS]; + +class CAudioHydrant +{ +public: + int32 AudioEntity; + CParticleObject *pParticleObject; + + CAudioHydrant() : + AudioEntity(AEHANDLE_NONE), + pParticleObject(NULL) + { } + + static bool Add (CParticleObject *particleobject); + static void Remove(CParticleObject *particleobject); + + static CAudioHydrant *Get(int n); // for neo screen droplets +}; \ No newline at end of file diff --git a/src/objects/Projectile.cpp b/src/objects/Projectile.cpp new file mode 100644 index 0000000..fe8b0c6 --- /dev/null +++ b/src/objects/Projectile.cpp @@ -0,0 +1,15 @@ +#include "common.h" + +#include "Projectile.h" + +CProjectile::CProjectile(int32 model) : CObject() +{ + m_fMass = 1.0f; + m_fTurnMass = 1.0f; + m_fAirResistance = 0.99999f; + m_fElasticity = 0.75f; + m_fBuoyancy = GRAVITY * m_fMass * 0.1f; + bExplosionProof = true; + SetModelIndex(model); + ObjectCreatedBy = MISSION_OBJECT; +} diff --git a/src/objects/Projectile.h b/src/objects/Projectile.h new file mode 100644 index 0000000..4b3eb4b --- /dev/null +++ b/src/objects/Projectile.h @@ -0,0 +1,9 @@ +#pragma once + +#include "Object.h" + +class CProjectile : public CObject +{ +public: + CProjectile(int32); +}; diff --git a/src/peds/CivilianPed.cpp b/src/peds/CivilianPed.cpp new file mode 100644 index 0000000..1c4f10f --- /dev/null +++ b/src/peds/CivilianPed.cpp @@ -0,0 +1,467 @@ +#include "common.h" + +#include "CivilianPed.h" +#include "Phones.h" +#include "General.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "World.h" +#include "Vehicle.h" +#include "SurfaceTable.h" + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +eCrimeType +EventTypeToCrimeType(eEventType event) +{ + eCrimeType crime; + switch (event) { + case EVENT_ASSAULT: crime = CRIME_HIT_PED; break; + case EVENT_RUN_REDLIGHT: crime = CRIME_RUN_REDLIGHT; break; + case EVENT_ASSAULT_POLICE: crime = CRIME_HIT_COP; break; + case EVENT_GUNSHOT: crime = CRIME_POSSESSION_GUN; break; + case EVENT_STEAL_CAR: crime = CRIME_STEAL_CAR; break; + case EVENT_HIT_AND_RUN: crime = CRIME_RUNOVER_PED; break; + case EVENT_HIT_AND_RUN_COP: crime = CRIME_RUNOVER_COP; break; + case EVENT_SHOOT_PED: crime = CRIME_SHOOT_PED; break; + case EVENT_SHOOT_COP: crime = CRIME_SHOOT_COP; break; + case EVENT_PED_SET_ON_FIRE: crime = CRIME_PED_BURNED; break; + case EVENT_COP_SET_ON_FIRE: crime = CRIME_COP_BURNED; break; + case EVENT_CAR_SET_ON_FIRE: crime = CRIME_VEHICLE_BURNED; break; + default: crime = CRIME_NONE; break; + } + return crime; +} +#endif + +CCivilianPed::CCivilianPed(ePedType pedtype, uint32 mi) : CPed(pedtype) +{ + SetModelIndex(mi); + for (int i = 0; i < ARRAY_SIZE(m_nearPeds); i++) { + m_nearPeds[i] = nil; + } +} + +void +CCivilianPed::CivilianAI(void) +{ + if (CTimer::GetTimeInMilliseconds() <= m_fleeTimer || m_objective != OBJECTIVE_NONE && !bRespondsToThreats + || !IsPedInControl()) { + + if (m_objective == OBJECTIVE_GUARD_SPOT) + return; + + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) { + if (m_pedInObjective && m_pedInObjective->IsPlayer()) + return; + } + if (CTimer::GetTimeInMilliseconds() <= m_lookTimer) + return; + + uint32 closestThreatFlag = ScanForThreats(); + if (closestThreatFlag == PED_FLAG_EXPLOSION) { + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + m_eventOrThreat.x, m_eventOrThreat.y, + GetPosition().x, GetPosition().y); + SetLookFlag(angleToFace, true); + SetLookTimer(500); + + } else if (closestThreatFlag == PED_FLAG_GUN) { + SetLookFlag(m_threatEntity, true); + SetLookTimer(500); + } + return; + } + uint32 closestThreatFlag = ScanForThreats(); + if (closestThreatFlag == PED_FLAG_GUN) { + if (!m_threatEntity || !m_threatEntity->IsPed()) + return; + + CPed *threatPed = (CPed*)m_threatEntity; + float threatDistSqr = (m_threatEntity->GetPosition() - GetPosition()).MagnitudeSqr2D(); + if (m_pedStats->m_fear <= m_pedStats->m_lawfulness) { + if (m_pedStats->m_temper <= m_pedStats->m_fear) { + if (!threatPed->IsPlayer() || !RunToReportCrime(CRIME_POSSESSION_GUN)) { + if (threatDistSqr < sq(10.0f)) { + Say(SOUND_PED_FLEE_SPRINT); + SetFindPathAndFlee(m_threatEntity, 10000); + } else { + SetFindPathAndFlee(m_threatEntity->GetPosition(), 5000, true); + } + } + } else if (m_objective != OBJECTIVE_NONE || GetWeapon()->IsTypeMelee()) { + SetFindPathAndFlee(m_threatEntity, 5000); + if (threatDistSqr < sq(20.0f)) { + SetMoveState(PEDMOVE_RUN); + Say(SOUND_PED_FLEE_SPRINT); + } else { + SetMoveState(PEDMOVE_WALK); + } + } else if (threatPed->IsPlayer() && FindPlayerPed()->m_pWanted->m_CurrentCops != 0) { + SetFindPathAndFlee(m_threatEntity, 5000); + if (threatDistSqr < sq(10.0f)) { + SetMoveState(PEDMOVE_RUN); + } else { + SetMoveState(PEDMOVE_WALK); + } + } else { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + } + } else { + if (threatDistSqr < sq(10.0f)) { + Say(SOUND_PED_FLEE_SPRINT); + SetFindPathAndFlee(m_threatEntity, 10000); + SetMoveState(PEDMOVE_SPRINT); + } else { + Say(SOUND_PED_FLEE_SPRINT); + SetFindPathAndFlee(m_threatEntity, 5000); + SetMoveState(PEDMOVE_RUN); + } + } + SetLookFlag(m_threatEntity, false); + SetLookTimer(500); + } else if (closestThreatFlag == PED_FLAG_DEADPEDS) { + float eventDistSqr = (m_pEventEntity->GetPosition() - GetPosition()).MagnitudeSqr2D(); + if (IsGangMember() && m_nPedType == ((CPed*)m_pEventEntity)->m_nPedType) { + if (eventDistSqr < sq(5.0f)) { + SetFindPathAndFlee(m_pEventEntity, 2000); + SetMoveState(PEDMOVE_RUN); + } + } else if (IsGangMember() || eventDistSqr > sq(5.0f)) { + bool investigateDeadPed = true; + CEntity *killerOfDeadPed = ((CPed*)m_pEventEntity)->m_threatEntity; + if (killerOfDeadPed && killerOfDeadPed->IsPed()) { + CVector killerPos = killerOfDeadPed->GetPosition(); + CVector deadPedPos = m_pEventEntity->GetPosition(); + if (CVector2D(killerPos - deadPedPos).MagnitudeSqr() < sq(10.0f)) + investigateDeadPed = false; + } + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + int32 eventId = CheckForPlayerCrimes((CPed*)m_pEventEntity); + eCrimeType crime = (eventId == -1 ? CRIME_NONE : EventTypeToCrimeType(gaEvent[eventId].type)); + bool eligibleToReport = crime != CRIME_NONE && m_pedStats->m_fear <= m_pedStats->m_lawfulness && m_pedStats->m_temper <= m_pedStats->m_fear; + if (IsGangMember() || !eligibleToReport || !RunToReportCrime(crime)) +#endif + if (investigateDeadPed) + SetInvestigateEvent(EVENT_DEAD_PED, CVector2D(m_pEventEntity->GetPosition()), 1.0f, 20000, 0.0f); + + } else { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + int32 eventId = CheckForPlayerCrimes((CPed*)m_pEventEntity); + eCrimeType crime = (eventId == -1 ? CRIME_NONE : EventTypeToCrimeType(gaEvent[eventId].type)); + bool eligibleToReport = crime != CRIME_NONE && m_pedStats->m_fear <= m_pedStats->m_lawfulness && m_pedStats->m_temper <= m_pedStats->m_fear; + if(!eligibleToReport || !RunToReportCrime(crime)) +#endif + { + SetFindPathAndFlee(m_pEventEntity, 5000); + SetMoveState(PEDMOVE_RUN); + } + } + } else if (closestThreatFlag == PED_FLAG_EXPLOSION) { + CVector2D eventDistVec = m_eventOrThreat - GetPosition(); + float eventDistSqr = eventDistVec.MagnitudeSqr(); + if (eventDistSqr < sq(20.0f)) { + Say(SOUND_PED_FLEE_SPRINT); + SetFlee(m_eventOrThreat, 2000); + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + m_eventOrThreat.x, m_eventOrThreat.y, + GetPosition().x, GetPosition().y); + SetLookFlag(angleToFace, true); + SetLookTimer(500); + } else if (eventDistSqr < sq(40.0f)) { + if (bGonnaInvestigateEvent) { + if (CharCreatedBy != MISSION_CHAR && !IsGangMember()) + SetInvestigateEvent(EVENT_EXPLOSION, m_eventOrThreat, 6.0f, 30000, 0.0f); + + } else { + float eventHeading = CGeneral::GetRadianAngleBetweenPoints(eventDistVec.x, eventDistVec.y, 0.0f, 0.0f); + eventHeading = CGeneral::LimitRadianAngle(eventHeading); + if (eventHeading < 0.0f) + eventHeading = eventHeading + TWOPI; + + SetWanderPath(eventHeading / 8.0f); + } + } + } else { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + bool youShouldRunEventually = false; + bool dontGoToPhone = false; +#endif + if (m_threatEntity && m_threatEntity->IsPed()) { + CPed *threatPed = (CPed*)m_threatEntity; + if (m_pedStats->m_fear <= 100 - threatPed->m_pedStats->m_temper && threatPed->m_nPedType != PEDTYPE_COP) { + if (threatPed->GetWeapon(m_currentWeapon).IsTypeMelee() || !GetWeapon()->IsTypeMelee()) { + if (threatPed->IsPlayer() && FindPlayerPed()->m_pWanted->m_CurrentCops != 0) { + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + dontGoToPhone = true; +#endif + SetFindPathAndFlee(m_threatEntity, 10000); + } + } else { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + dontGoToPhone = true; +#endif + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + } + } + } else { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + youShouldRunEventually = true; +#else + SetFindPathAndFlee(m_threatEntity, 10000, true); +#endif + } + } + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + if (!dontGoToPhone) { + int32 eventId = CheckForPlayerCrimes(nil); + eCrimeType crime = (eventId == -1 ? CRIME_NONE : EventTypeToCrimeType(gaEvent[eventId].type)); + bool eligibleToReport = crime != CRIME_NONE && m_pedStats->m_fear <= m_pedStats->m_lawfulness; + + if ((!eligibleToReport || !RunToReportCrime(crime)) && youShouldRunEventually) { + SetFindPathAndFlee(m_threatEntity, 10000, true); + } + } +#endif + } +} + +void +CCivilianPed::ProcessControl(void) +{ + if (m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory) + return; + + CPed::ProcessControl(); + + if (bWasPostponed) + return; + + if (DyingOrDead()) + return; + + GetWeapon()->Update(m_audioEntityId); + switch (m_nPedState) { + case PED_WANDER_RANGE: + case PED_WANDER_PATH: + if (IsVisible()) + ScanForInterestingStuff(); + break; + case PED_SEEK_ENTITY: + if (!m_pSeekTarget) { + RestorePreviousState(); + break; + } + m_vecSeekPos = m_pSeekTarget->GetPosition(); + + // fall through + case PED_SEEK_POS: + if (Seek()) { + if ((m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA) && m_pNextPathNode) { + m_pNextPathNode = nil; +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + } else if (bRunningToPhone && m_objective < OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE) { + if (crimeReporters[m_phoneId] != this) { + RestorePreviousState(); + m_phoneId = -1; + bRunningToPhone = false; + } else { + m_facePhoneStart = true; + SetPedState(PED_FACE_PHONE); + } +#else + } else if (bRunningToPhone) { + if (gPhoneInfo.m_aPhones[m_phoneId].m_nState != PHONE_STATE_FREE) { + RestorePreviousState(); + m_phoneId = -1; + } else { + gPhoneInfo.m_aPhones[m_phoneId].m_nState = PHONE_STATE_REPORTING_CRIME; + SetPedState(PED_FACE_PHONE); + } +#endif + } else if (m_objective != OBJECTIVE_KILL_CHAR_ANY_MEANS && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + if (m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION) { + if (m_moved.Magnitude() == 0.0f) { + if (m_pedInObjective->m_nMoveState == PEDMOVE_STILL) + m_fRotationDest = m_pedInObjective->m_fRotationCur; + } + } else if (m_objective == OBJECTIVE_GOTO_CHAR_ON_FOOT + && m_pedInObjective && m_pedInObjective->m_nMoveState != PEDMOVE_STILL) { + SetMoveState(m_pedInObjective->m_nMoveState); + } else if (m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA) { + SetIdle(); + } else { + RestorePreviousState(); + } + } + } + break; + case PED_FACE_PHONE: + if (FacePhone()) + SetPedState(PED_MAKE_CALL); + break; + case PED_MAKE_CALL: + if (MakePhonecall()) + SetWanderPath(CGeneral::GetRandomNumber() & 7); + break; + case PED_MUG: + Mug(); + break; + case PED_SOLICIT: + Solicit(); + break; + case PED_UNKNOWN: + { + int pedsInSameState = 0; + Idle(); + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->m_nPedType == m_nPedType && nearPed->m_nPedState == PED_UNKNOWN) { + ++pedsInSameState; + } + } + if (pedsInSameState < 5) { + for (int j = 0; j < m_numNearPeds; ++j) { + CPed *nearPed = m_nearPeds[j]; + if (nearPed->m_nPedType == m_nPedType && nearPed->m_nPedState == PED_WANDER_PATH) { + nearPed->SetPedState(PED_UNKNOWN); + } + } + } + break; + } + case PED_DRIVING: + if (m_nPedType != PEDTYPE_PROSTITUTE) + break; + + if (CWorld::Players[CWorld::PlayerInFocus].m_pHooker != this) + break; + + if (CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime) { + if (m_nPedState == PED_DRIVING + && m_pMyVehicle->pDriver && m_pMyVehicle->pDriver->IsPlayer() && m_pMyVehicle->pDriver->m_nPedState == PED_DRIVING) { + CColPoint foundCol; + CEntity* foundEnt; + + CWorld::ProcessVerticalLine(m_pMyVehicle->GetPosition(), -100.0f, + foundCol, foundEnt, true, false, false, false, false, false, nil); + + if (m_pMyVehicle->m_vecMoveSpeed.MagnitudeSqr() < sq(0.01f) + && foundCol.surfaceB != SURFACE_DEFAULT && foundCol.surfaceB != SURFACE_TARMAC && foundCol.surfaceB != SURFACE_PAVEMENT) { + + if (m_pMyVehicle->CarHasRoof()) { + m_pMyVehicle->ApplyTurnForce(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(-0.8f, -1.2f) * m_fMass, + GetPosition().x - m_pMyVehicle->GetPosition().x, GetPosition().y - m_pMyVehicle->GetPosition().y, 0.0f); + + DMAudio.PlayOneShot(m_pMyVehicle->m_audioEntityId, SOUND_CAR_JERK, 0.0f); + + int playerSexFrequency = CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency; + if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney >= 10 && playerSexFrequency > 250) { + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + playerSexFrequency; + if (playerSexFrequency >= 350) { + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = Max(250, playerSexFrequency - 30); + } else { + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = Max(250, playerSexFrequency - 10); + } + + m_pMyVehicle->pDriver->m_fHealth = Min(125.0f, 1.0f + m_pMyVehicle->pDriver->m_fHealth); + if (CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency == 250) + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + 3000; + } else { + bWanderPathAfterExitingCar = true; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } else { + bWanderPathAfterExitingCar = true; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil; + m_pMyVehicle->pDriver->m_fHealth = 125.0f; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } else { + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + 3000; + int playerSexFrequency = CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency; + if (playerSexFrequency >= 1000 || playerSexFrequency <= 250) + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 1200; + else + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 250; + } + } else { + bWanderPathAfterExitingCar = true; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } + + if (CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nNextSexMoneyUpdateTime) { + int playerMoney = CWorld::Players[CWorld::PlayerInFocus].m_nMoney; + if (playerMoney <= 1) { + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 250; + } else { + CWorld::Players[CWorld::PlayerInFocus].m_nMoney = Max(0, playerMoney - 1); + } + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexMoneyUpdateTime = CTimer::GetTimeInMilliseconds() + 1000; + } + break; + default: + break; + } + if (IsPedInControl()) + CivilianAI(); + + if (CTimer::GetTimeInMilliseconds() > m_timerUnused) { + m_stateUnused = 0; + m_timerUnused = 0; + } + + if (m_moved.Magnitude() > 0.0f) + Avoid(); +} + +// It's "CPhoneInfo::ProcessNearestFreePhone" in PC IDB but that's not true, someone made it up. +bool +CPed::RunToReportCrime(eCrimeType crimeToReport) +{ +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + if (bRunningToPhone) { + if (!isPhoneAvailable(m_phoneId) && crimeReporters[m_phoneId] != this) { + crimeReporters[m_phoneId] = nil; + m_phoneId = -1; + bIsRunning = false; + ClearSeek(); // clears bRunningToPhone + return false; + } + + return true; + } +#else + // They changed true into false to make this function unusable. So running to phone actually starts but first frame after that cancels it. + if (m_nPedState == PED_SEEK_POS) + return false; +#endif + + CVector pos = GetPosition(); + int phoneId = gPhoneInfo.FindNearestFreePhone(&pos); + + if (phoneId == -1) + return false; + + CPhone *phone = &gPhoneInfo.m_aPhones[phoneId]; +#ifndef PEDS_REPORT_CRIMES_ON_PHONE + if (phone->m_nState != PHONE_STATE_FREE) + return false; +#else + crimeReporters[phoneId] = this; +#endif + + bRunningToPhone = true; + SetSeek(phone->m_pEntity->GetMatrix() * -phone->m_pEntity->GetForward(), 1.0f); // original: phone.m_vecPos, 0.3f + SetMoveState(PEDMOVE_RUN); + bIsRunning = true; // not there in original + m_phoneId = phoneId; + m_crimeToReportOnPhone = crimeToReport; + return true; +} \ No newline at end of file diff --git a/src/peds/CivilianPed.h b/src/peds/CivilianPed.h new file mode 100644 index 0000000..8418a99 --- /dev/null +++ b/src/peds/CivilianPed.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Ped.h" + +class CCivilianPed : public CPed +{ +public: + CCivilianPed(ePedType, uint32); + ~CCivilianPed(void) { } + + void CivilianAI(void); + void ProcessControl(void); +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CCivilianPed, 0x53C); +#endif diff --git a/src/peds/CopPed.cpp b/src/peds/CopPed.cpp new file mode 100644 index 0000000..44e3baf --- /dev/null +++ b/src/peds/CopPed.cpp @@ -0,0 +1,736 @@ +#include "common.h" + +#include "World.h" +#include "PlayerPed.h" +#include "CopPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "ModelIndices.h" +#include "Vehicle.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "General.h" +#include "ZoneCull.h" +#include "PathFind.h" +#include "RoadBlocks.h" +#include "CarCtrl.h" +#include "Renderer.h" +#include "Camera.h" + +CCopPed::CCopPed(eCopType copType) : CPed(PEDTYPE_COP) +{ + m_nCopType = copType; + switch (copType) { + case COP_STREET: + SetModelIndex(MI_COP); + GiveWeapon(WEAPONTYPE_COLT45, 1000); + m_currentWeapon = WEAPONTYPE_UNARMED; + m_fArmour = 0.0f; + m_wepSkills = 208; /* TODO: what is this? seems unused */ + m_wepAccuracy = 60; + break; + case COP_FBI: + SetModelIndex(MI_FBI); + GiveWeapon(WEAPONTYPE_COLT45, 1000); + GiveWeapon(WEAPONTYPE_AK47, 1000); + SetCurrentWeapon(WEAPONTYPE_AK47); + m_fArmour = 100.0f; + m_wepSkills = 176; /* TODO: what is this? seems unused */ + m_wepAccuracy = 76; + break; + case COP_SWAT: + SetModelIndex(MI_SWAT); + GiveWeapon(WEAPONTYPE_COLT45, 1000); + GiveWeapon(WEAPONTYPE_UZI, 1000); + SetCurrentWeapon(WEAPONTYPE_UZI); + m_fArmour = 50.0f; + m_wepSkills = 32; /* TODO: what is this? seems unused */ + m_wepAccuracy = 68; + break; + case COP_ARMY: + SetModelIndex(MI_ARMY); + GiveWeapon(WEAPONTYPE_COLT45, 1000); + GiveWeapon(WEAPONTYPE_M16, 1000); + GiveWeapon(WEAPONTYPE_GRENADE, 10); + SetCurrentWeapon(WEAPONTYPE_M16); + m_fArmour = 100.0f; + m_wepSkills = 32; /* TODO: what is this? seems unused */ + m_wepAccuracy = 84; + break; + default: + break; + } + m_bIsInPursuit = false; + field_1350 = 1; + m_bIsDisabledCop = false; + m_fAbseilPos = 0.0f; + m_attackTimer = 0; + m_bBeatingSuspect = false; + m_bStopAndShootDisabledZone = false; + m_bZoneDisabled = false; + field_1364 = -1; + SetWeaponLockOnTarget(nil); + + // VC also initializes in here, but as nil +#ifdef FIX_BUGS + m_nRoadblockNode = -1; +#endif +} + +CCopPed::~CCopPed() +{ + ClearPursuit(); +} + +// Parameter should always be CPlayerPed, but it seems they considered making civilians arrestable at some point +void +CCopPed::SetArrestPlayer(CPed *player) +{ + if (!IsPedInControl() || !player) + return; + + switch (m_nCopType) { + case COP_FBI: + Say(SOUND_PED_ARREST_FBI); + break; + case COP_SWAT: + Say(SOUND_PED_ARREST_SWAT); + break; + default: + Say(SOUND_PED_ARREST_COP); + break; + } + if (player->EnteringCar()) { + if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) + return; + + // why? + player->bGonnaKillTheCarJacker = true; + + // Genius + FindPlayerPed()->m_bCanBeDamaged = false; + ((CPlayerPed*)player)->m_pArrestingCop = this; + this->RegisterReference((CEntity**) &((CPlayerPed*)player)->m_pArrestingCop); + + } else if (player->m_nPedState != PED_DIE && player->m_nPedState != PED_DEAD && player->m_nPedState != PED_ARRESTED) { + player->m_nLastPedState = player->m_nPedState; + player->SetPedState(PED_ARRESTED); + + FindPlayerPed()->m_bCanBeDamaged = false; + ((CPlayerPed*)player)->m_pArrestingCop = this; + this->RegisterReference((CEntity**) &((CPlayerPed*)player)->m_pArrestingCop); + } + + SetPedState(PED_ARREST_PLAYER); + SetObjective(OBJECTIVE_NONE); + m_prevObjective = OBJECTIVE_NONE; + bIsPointingGunAt = false; + m_pSeekTarget = player; + m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget); + SetCurrentWeapon(WEAPONTYPE_COLT45); + if (player->InVehicle()) { + player->m_pMyVehicle->m_nNumGettingIn = 0; + player->m_pMyVehicle->m_nGettingInFlags = 0; + player->m_pMyVehicle->bIsHandbrakeOn = true; + player->m_pMyVehicle->SetStatus(STATUS_PLAYER_DISABLED); + } + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) + SetCurrentWeapon(WEAPONTYPE_COLT45); +} + +void +CCopPed::ClearPursuit(void) +{ + CPlayerPed *player = FindPlayerPed(); + if (!player) + return; + + CWanted *wanted = player->m_pWanted; + int ourCopId = 0; + bool foundMyself = false; + int biggestCopId = 0; + if (!m_bIsInPursuit) + return; + + m_bIsInPursuit = false; + for (int i = 0; i < Max(wanted->m_MaxCops, wanted->m_CurrentCops); ++i) { + if (!foundMyself && wanted->m_pCops[i] == this) { + wanted->m_pCops[i] = nil; + --wanted->m_CurrentCops; + foundMyself = true; + ourCopId = i; + biggestCopId = i; + } else { + if (wanted->m_pCops[i]) + biggestCopId = i; + } + } + if (foundMyself && biggestCopId > ourCopId) { + wanted->m_pCops[ourCopId] = wanted->m_pCops[biggestCopId]; + wanted->m_pCops[biggestCopId] = nil; + } + m_objective = OBJECTIVE_NONE; + m_prevObjective = OBJECTIVE_NONE; + m_nLastPedState = PED_NONE; + bIsRunning = false; + bNotAllowedToDuck = false; + bKindaStayInSamePlace = false; + m_bStopAndShootDisabledZone = false; + m_bZoneDisabled = false; + ClearObjective(); + if (IsPedInControl()) { + if (!m_pMyVehicle || wanted->GetWantedLevel() != 0) { + if (m_pMyVehicle && (m_pMyVehicle->GetPosition() - GetPosition()).MagnitudeSqr() < sq(5.0f)) { + m_nLastPedState = PED_IDLE; + SetSeek((CEntity*)m_pMyVehicle, 2.5f); + } else { + m_nLastPedState = PED_WANDER_PATH; + SetFindPathAndFlee(FindPlayerPed()->GetPosition(), 10000, true); + } + } else { + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } +} + +// TODO: I don't know why they needed that parameter. +void +CCopPed::SetPursuit(bool ignoreCopLimit) +{ + CWanted *wanted = FindPlayerPed()->m_pWanted; + if (m_bIsInPursuit || !IsPedInControl()) + return; + + if (wanted->m_CurrentCops < wanted->m_MaxCops || ignoreCopLimit) { + for (int i = 0; i < wanted->m_MaxCops; ++i) { + if (!wanted->m_pCops[i]) { + m_bIsInPursuit = true; + ++wanted->m_CurrentCops; + wanted->m_pCops[i] = this; + break; + } + } + if (m_bIsInPursuit) { + ClearObjective(); + m_prevObjective = OBJECTIVE_NONE; + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, FindPlayerPed()); + SetObjectiveTimer(0); + bNotAllowedToDuck = true; + bIsRunning = true; + m_bStopAndShootDisabledZone = false; + } + } +} + +void +CCopPed::ArrestPlayer(void) +{ + m_pVehicleAnim = nil; + CPed *suspect = (CPed*)m_pSeekTarget; + if (suspect) { + if (suspect->CanSetPedState()) + suspect->SetPedState(PED_ARRESTED); + + if (suspect->bInVehicle && m_pMyVehicle && suspect->m_pMyVehicle == m_pMyVehicle) { + + // BUG? I will never understand why they used LINE_UP_TO_CAR_2... + LineUpPedWithCar(LINE_UP_TO_CAR_2); + } + + if (suspect && (suspect->m_nPedState == PED_ARRESTED || suspect->DyingOrDead() || suspect->EnteringCar())) { + + CAnimBlendAssociation *arrestAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ARREST); + if (!arrestAssoc || arrestAssoc->blendDelta < 0.0f) + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ARREST, 4.0f); + + CVector suspMidPos; + suspect->m_pedIK.GetComponentPosition(suspMidPos, PED_MID); + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(suspMidPos.x, suspMidPos.y, + GetPosition().x, GetPosition().y); + + m_fRotationCur = m_fRotationDest; + SetOrientation(0.0f, 0.0f, m_fRotationCur); + } else { + ClearPursuit(); + } + } else { + ClearPursuit(); + } +} + +void +CCopPed::ScanForCrimes(void) +{ + CVehicle *playerVeh = FindPlayerVehicle(); + + // Look for car alarms + if (playerVeh && playerVeh->IsCar()) { + if (playerVeh->IsAlarmOn()) { + if ((playerVeh->GetPosition() - GetPosition()).MagnitudeSqr() < sq(20.0f)) + FindPlayerPed()->SetWantedLevelNoDrop(1); + } + } + + // Look for stolen cop cars + if (!m_bIsInPursuit) { + CPlayerPed *player = FindPlayerPed(); + if ((m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) + && player->m_pWanted->GetWantedLevel() == 0) { + + if (player->m_pMyVehicle +#ifdef FIX_BUGS + && m_pMyVehicle == player->m_pMyVehicle +#endif + && player->m_pMyVehicle->bIsLawEnforcer) + player->SetWantedLevelNoDrop(1); + } + } +} + +void +CCopPed::CopAI(void) +{ + CWanted *wanted = FindPlayerPed()->m_pWanted; + int wantedLevel = wanted->GetWantedLevel(); + CPhysical *playerOrHisVeh = FindPlayerVehicle() ? (CPhysical*)FindPlayerVehicle() : (CPhysical*)FindPlayerPed(); + + if (wanted->m_bIgnoredByEveryone || wanted->m_bIgnoredByCops) { + if (m_nPedState != PED_ARREST_PLAYER) + ClearPursuit(); + + return; + } + if (CCullZones::NoPolice() && m_bIsInPursuit && !m_bIsDisabledCop) { + if (bHitSomethingLastFrame) { + m_bZoneDisabled = true; + m_bIsDisabledCop = true; +#ifdef FIX_BUGS + m_nRoadblockNode = -1; +#else + m_nRoadblockNode = 0; +#endif + bKindaStayInSamePlace = true; + bIsRunning = false; + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + SetIdle(); + ClearObjective(); + ClearPursuit(); + m_prevObjective = OBJECTIVE_NONE; + m_nLastPedState = PED_NONE; + SetAttackTimer(0); + + // Safe distance for disabled zone? Or to just make game easier? + if (m_fDistanceToTarget > 15.0f) + m_bStopAndShootDisabledZone = true; + } + } else if (m_bZoneDisabled && !CCullZones::NoPolice()) { + m_bZoneDisabled = false; + m_bIsDisabledCop = false; + m_bStopAndShootDisabledZone = false; + bKindaStayInSamePlace = false; + bCrouchWhenShooting = false; + bDuckAndCover = false; + ClearPursuit(); + } + if (wantedLevel > 0) { + if (!m_bIsDisabledCop) { + if (!m_bIsInPursuit || wanted->m_CurrentCops > wanted->m_MaxCops) { + CCopPed *copFarthestToTarget = nil; + float copFarthestToTargetDist = m_fDistanceToTarget; + + int oldCopNum = wanted->m_CurrentCops; + int maxCops = wanted->m_MaxCops; + + for (int i = 0; i < Max(maxCops, oldCopNum); i++) { + CCopPed *cop = wanted->m_pCops[i]; + if (cop && cop->m_fDistanceToTarget > copFarthestToTargetDist) { + copFarthestToTargetDist = cop->m_fDistanceToTarget; + copFarthestToTarget = wanted->m_pCops[i]; + } + } + + if (m_bIsInPursuit) { + if (copFarthestToTarget && oldCopNum > maxCops) { + if (copFarthestToTarget == this && m_fDistanceToTarget > 10.0f) { + ClearPursuit(); + } else if(copFarthestToTargetDist > 10.0f) + copFarthestToTarget->ClearPursuit(); + } + } else { + if (oldCopNum < maxCops) { + SetPursuit(true); + } else { + if (m_fDistanceToTarget <= 10.0f || copFarthestToTarget && m_fDistanceToTarget < copFarthestToTargetDist) { + if (copFarthestToTarget && copFarthestToTargetDist > 10.0f) + copFarthestToTarget->ClearPursuit(); + + SetPursuit(true); + } + } + } + } else + SetPursuit(false); + + if (!m_bIsInPursuit) + return; + + if (wantedLevel > 1 && GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) + SetCurrentWeapon(WEAPONTYPE_COLT45); + else if (wantedLevel == 1 && GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && !FindPlayerPed()->m_pCurrentPhysSurface) { + // i.e. if player is on top of car, cop will still use colt45. + SetCurrentWeapon(WEAPONTYPE_UNARMED); + } + + if (FindPlayerVehicle()) { + if (m_bBeatingSuspect) { + --wanted->m_CopsBeatingSuspect; + m_bBeatingSuspect = false; + } + if (m_fDistanceToTarget * FindPlayerSpeed().Magnitude() > 4.0f) + ClearPursuit(); + } + return; + } + float weaponRange = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_fRange; + SetLookFlag(playerOrHisVeh, true); + TurnBody(); + SetCurrentWeapon(WEAPONTYPE_COLT45); + if (!bIsDucking) { + if (m_attackTimer >= CTimer::GetTimeInMilliseconds()) { + if (m_nPedState != PED_ATTACK && m_nPedState != PED_FIGHT && !m_bZoneDisabled) { + CVector targetDist = playerOrHisVeh->GetPosition() - GetPosition(); + if (m_fDistanceToTarget > 30.0f) { + CAnimBlendAssociation* crouchShootAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RBLOCK_SHOOT); + if (crouchShootAssoc) + crouchShootAssoc->blendDelta = -1000.0f; + + // Target is coming onto us + if (DotProduct(playerOrHisVeh->m_vecMoveSpeed, targetDist) > 0.0f) { + m_bIsDisabledCop = false; + bKindaStayInSamePlace = false; + bNotAllowedToDuck = false; + bDuckAndCover = false; + SetPursuit(false); + SetObjective(OBJECTIVE_KILL_CHAR_ANY_MEANS, FindPlayerPed()); + } + } else if (m_fDistanceToTarget < 5.0f + && (!FindPlayerVehicle() || FindPlayerVehicle()->m_vecMoveSpeed.MagnitudeSqr() < sq(1.f/200.f))) { + m_bIsDisabledCop = false; + bKindaStayInSamePlace = false; + bNotAllowedToDuck = false; + bDuckAndCover = false; + } else { + // VC checks for != nil compared to buggy behaviour of III. I check for != -1 here. +#ifdef VC_PED_PORTS + float dotProd; + if (m_nRoadblockNode != -1) { + CTreadable *roadBlockRoad = ThePaths.m_mapObjects[CRoadBlocks::RoadBlockObjects[m_nRoadblockNode]]; + dotProd = DotProduct2D(playerOrHisVeh->GetPosition() - roadBlockRoad->GetPosition(), GetPosition() - roadBlockRoad->GetPosition()); + } else + dotProd = -1.0f; + + if(dotProd >= 0.0f) { +#else + +#ifndef FIX_BUGS + float copRoadDotProd, targetRoadDotProd; +#else + float copRoadDotProd = 1.0f, targetRoadDotProd = 1.0f; + if (m_nRoadblockNode != -1) +#endif + { + CTreadable* roadBlockRoad = ThePaths.m_mapObjects[CRoadBlocks::RoadBlockObjects[m_nRoadblockNode]]; + CVector2D roadFwd = roadBlockRoad->GetForward(); + copRoadDotProd = DotProduct2D(GetPosition() - roadBlockRoad->GetPosition(), roadFwd); + targetRoadDotProd = DotProduct2D(playerOrHisVeh->GetPosition() - roadBlockRoad->GetPosition(), roadFwd); + } + // Roadblock may be towards road's fwd or opposite, so check both + if ((copRoadDotProd >= 0.0f || targetRoadDotProd >= 0.0f) + && (copRoadDotProd <= 0.0f || targetRoadDotProd <= 0.0f)) { +#endif + bIsPointingGunAt = true; + } else { + m_bIsDisabledCop = false; + bKindaStayInSamePlace = false; + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + bIsDucking = false; + bDuckAndCover = false; + SetPursuit(false); + } + } + } + } else { + if (m_fDistanceToTarget < weaponRange) { + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + CVector gunPos = weaponInfo->m_vecFireOffset; + TransformToNode(gunPos, PED_HANDR); + + CColPoint foundCol; + CEntity *foundEnt; + if (!CWorld::ProcessLineOfSight(gunPos, playerOrHisVeh->GetPosition(), foundCol, foundEnt, + false, true, false, false, true, false, false) + || foundEnt && foundEnt == playerOrHisVeh) { + SetWeaponLockOnTarget(playerOrHisVeh); + SetAttack(playerOrHisVeh); + SetShootTimer(CGeneral::GetRandomNumberInRange(500, 1000)); + } + SetAttackTimer(CGeneral::GetRandomNumberInRange(200, 300)); + } + SetMoveState(PEDMOVE_STILL); + } + } + } else { + if (!m_bIsDisabledCop || m_bZoneDisabled) { + if (m_nPedState != PED_AIM_GUN) { + if (m_bIsInPursuit) + ClearPursuit(); + + if (IsPedInControl()) { + // Entering the vehicle + if (m_pMyVehicle && !bInVehicle) { + if (m_pMyVehicle->IsLawEnforcementVehicle()) { + if (m_pMyVehicle->pDriver) { + if (m_pMyVehicle->pDriver->m_nPedType == PEDTYPE_COP) { + if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER) + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_pMyVehicle); + } else if (m_pMyVehicle->pDriver->IsPlayer()) { + FindPlayerPed()->SetWantedLevelNoDrop(1); + } + } else if (m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } else { + m_pMyVehicle = nil; + ClearObjective(); + SetWanderPath(CGeneral::GetRandomNumber() & 7); + } + } +#ifdef VC_PED_PORTS + else { + if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT && CharCreatedBy == RANDOM_CHAR) { + for (int i = 0; i < m_numNearPeds; i++) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->CharCreatedBy == RANDOM_CHAR) { + if ((nearPed->m_nPedType == PEDTYPE_CRIMINAL || nearPed->IsGangMember()) + && nearPed->IsPedInControl()) { + + bool anotherCopChasesHim = false; + if (nearPed->m_nPedState == PED_FLEE_ENTITY) { + if (nearPed->m_fleeFrom && nearPed->m_fleeFrom->IsPed() && + ((CPed*)nearPed->m_fleeFrom)->m_nPedType == PEDTYPE_COP) { + anotherCopChasesHim = true; + } + } + if (!anotherCopChasesHim) { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, nearPed); + nearPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, this); + nearPed->bBeingChasedByPolice = true; + return; + } + } + } + } + } + } +#endif + } + } + } else { + if (m_bIsInPursuit && m_nPedState != PED_AIM_GUN) + ClearPursuit(); + + m_bIsDisabledCop = false; + bKindaStayInSamePlace = false; + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + bIsDucking = false; + bDuckAndCover = false; + if (m_pMyVehicle) + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } +} + +void +CCopPed::ProcessControl(void) +{ + if (m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory) + return; + + CPed::ProcessControl(); + if (bWasPostponed) + return; + + if (m_nPedState == PED_DEAD) { + ClearPursuit(); + m_objective = OBJECTIVE_NONE; + return; + } + if (m_nPedState == PED_DIE) + return; + + if (m_nPedState == PED_ARREST_PLAYER) { + ArrestPlayer(); + return; + } + GetWeapon()->Update(m_audioEntityId); + if (m_moved.Magnitude() > 0.0f) + Avoid(); + + CPhysical *playerOrHisVeh = FindPlayerVehicle() ? (CPhysical*)FindPlayerVehicle() : (CPhysical*)FindPlayerPed(); + CPlayerPed *player = FindPlayerPed(); + + m_fDistanceToTarget = (playerOrHisVeh->GetPosition() - GetPosition()).Magnitude(); + if (player->m_nPedState == PED_ARRESTED || player->DyingOrDead()) { + if (m_fDistanceToTarget < 5.0f) { + SetArrestPlayer(player); + return; + } + if (IsPedInControl()) + SetIdle(); + } + if (m_bIsInPursuit) { + if (player->m_nPedState != PED_ARRESTED && !player->DyingOrDead()) { + switch (m_nCopType) { + case COP_FBI: + Say(SOUND_PED_PURSUIT_FBI); + break; + case COP_SWAT: + Say(SOUND_PED_PURSUIT_SWAT); + break; + case COP_ARMY: + Say(SOUND_PED_PURSUIT_ARMY); + break; + default: + Say(SOUND_PED_PURSUIT_COP); + break; + } + } + } + + if (IsPedInControl()) { + CopAI(); + /* switch (m_nCopType) + { + case COP_FBI: + CopAI(); + break; + case COP_SWAT: + CopAI(); + break; + case COP_ARMY: + CopAI(); + break; + default: + CopAI(); + break; + } */ + } else if (InVehicle()) { + if (m_pMyVehicle->pDriver == this && m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_NONE && + CanPedDriveOff() && m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE) { + + CCarCtrl::JoinCarWithRoadSystem(m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 17; + } + } + if (IsPedInControl() || m_nPedState == PED_DRIVING) + ScanForCrimes(); + + // They may have used goto to jump here in case of PED_ATTACK. + if (m_nPedState == PED_IDLE || m_nPedState == PED_ATTACK) { + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && + player && player->EnteringCar() && m_fDistanceToTarget < 1.3f) { + SetArrestPlayer(player); + } + } else { + if (m_nPedState == PED_SEEK_POS) { + if (player->m_nPedState == PED_ARRESTED) { + SetIdle(); + SetLookFlag(player, false); + SetLookTimer(1000); + RestorePreviousObjective(); + } else { + if (player->m_pMyVehicle && player->m_pMyVehicle->m_nNumGettingIn != 0) { + // This is 1.3f when arresting in car without seeking first (in above) +#if defined(VC_PED_PORTS) || defined(FIX_BUGS) + m_distanceToCountSeekDone = 1.3f; +#else + m_distanceToCountSeekDone = 2.0f; +#endif + } + + if (bDuckAndCover) { +#if GTA_VERSION < GTA3_PC_11 && !defined(VC_PED_PORTS) + if (!bNotAllowedToDuck && Seek()) { + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + SetPointGunAt(m_pedInObjective); + } +#endif + } else if (Seek()) { + CVehicle *playerVeh = FindPlayerVehicle(); + if (!playerVeh && player && player->EnteringCar()) { + SetArrestPlayer(player); + } else if (1.5f + GetPosition().z <= m_vecSeekPos.z || GetPosition().z - 0.3f >= m_vecSeekPos.z) { + SetMoveState(PEDMOVE_STILL); + } else if (playerVeh && playerVeh->CanPedEnterCar() && playerVeh->m_nNumGettingIn == 0) { + SetCarJack(playerVeh); + } + } + } + } else if (m_nPedState == PED_SEEK_ENTITY) { + if (!m_pSeekTarget) { + RestorePreviousState(); + } else { + m_vecSeekPos = m_pSeekTarget->GetPosition(); + if (Seek()) { + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && m_fDistanceToTarget < 2.5f && player) { + if (player->m_nPedState == PED_ARRESTED || player->m_nPedState == PED_ENTER_CAR || + (player->m_nPedState == PED_CARJACK && m_fDistanceToTarget < 1.3f)) { + SetArrestPlayer(player); + } else + RestorePreviousState(); + } else { + RestorePreviousState(); + } + + } + } + } + } + if (!m_bStopAndShootDisabledZone) + return; + + bool dontShoot = false; + if (GetIsOnScreen() && CRenderer::IsEntityCullZoneVisible(this)) { + if (((CTimer::GetFrameCounter() + m_randomSeed) & 0x1F) == 17) { + CEntity *foundBuilding = nil; + CColPoint foundCol; + CVector lookPos = GetPosition() + CVector(0.0f, 0.0f, 0.7f); + CVector camPos = TheCamera.GetGameCamPosition(); + CWorld::ProcessLineOfSight(camPos, lookPos, foundCol, foundBuilding, + true, false, false, false, false, false, false); + + // He's at least 15.0 far, in disabled zone, collided into somewhere (that's why m_bStopAndShootDisabledZone set), + // and now has building on front of him. He's stupid, we don't need him. + if (foundBuilding) { + FlagToDestroyWhenNextProcessed(); + dontShoot = true; + } + } + } else { + FlagToDestroyWhenNextProcessed(); + dontShoot = true; + } + + if (!dontShoot) { + bStopAndShoot = true; + bKindaStayInSamePlace = true; + bIsPointingGunAt = true; + SetAttack(m_pedInObjective); + } +} diff --git a/src/peds/CopPed.h b/src/peds/CopPed.h new file mode 100644 index 0000000..5346d9a --- /dev/null +++ b/src/peds/CopPed.h @@ -0,0 +1,41 @@ +#pragma once +#include "Ped.h" + +enum eCopType +{ + COP_STREET = 0, + COP_FBI = 1, + COP_SWAT = 2, + COP_ARMY = 3, +}; + +class CCopPed : public CPed +{ +public: + int16 m_nRoadblockNode; + float m_fDistanceToTarget; + bool m_bIsInPursuit; + bool m_bIsDisabledCop; + int8 field_1350; + bool m_bBeatingSuspect; + bool m_bStopAndShootDisabledZone; + bool m_bZoneDisabled; + float m_fAbseilPos; // VC leftover, unused + eCopType m_nCopType; + int8 field_1364; + + CCopPed(eCopType); + ~CCopPed(); + + void ClearPursuit(void); + void ProcessControl(void); + void SetArrestPlayer(CPed*); + void SetPursuit(bool); + void ArrestPlayer(void); + void ScanForCrimes(void); + void CopAI(void); +}; + +#ifndef PED_SKIN +VALIDATE_SIZE(CCopPed, 0x558); +#endif diff --git a/src/peds/DummyPed.h b/src/peds/DummyPed.h new file mode 100644 index 0000000..ea617c7 --- /dev/null +++ b/src/peds/DummyPed.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Dummy.h" + +// actually unused +class CDummyPed : CDummy +{ + int32 pedType; + int32 unknown; +}; + +VALIDATE_SIZE(CDummyPed, 0x70); diff --git a/src/peds/EmergencyPed.cpp b/src/peds/EmergencyPed.cpp new file mode 100644 index 0000000..6a5ca25 --- /dev/null +++ b/src/peds/EmergencyPed.cpp @@ -0,0 +1,417 @@ +#include "common.h" + +#include "EmergencyPed.h" +#include "DMAudio.h" +#include "ModelIndices.h" +#include "Vehicle.h" +#include "Fire.h" +#include "General.h" +#include "CarCtrl.h" +#include "Accident.h" + +CEmergencyPed::CEmergencyPed(uint32 type) : CPed(type) +{ + switch (type){ + case PEDTYPE_EMERGENCY: + SetModelIndex(MI_MEDIC); + m_pRevivedPed = nil; + field_1360 = 0; + break; + case PEDTYPE_FIREMAN: + SetModelIndex(MI_FIREMAN); + m_pRevivedPed = nil; + break; + default: + break; + } + m_nEmergencyPedState = EMERGENCY_PED_READY; + m_pAttendedAccident = nil; + m_bStartedToCPR = false; +} + +bool +CEmergencyPed::InRange(CPed *victim) +{ + if (!m_pMyVehicle) + return true; + + if ((m_pMyVehicle->GetPosition() - victim->GetPosition()).Magnitude() > 30.0f) + return false; + + return true; +} + +void +CEmergencyPed::ProcessControl(void) +{ + if (m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory) + return; + + CPed::ProcessControl(); + if (bWasPostponed) + return; + + if(!DyingOrDead()) { + GetWeapon()->Update(m_audioEntityId); + + if (IsPedInControl() && m_moved.Magnitude() > 0.0f) + Avoid(); + + switch (m_nPedState) { + case PED_SEEK_POS: + Seek(); + break; + case PED_SEEK_ENTITY: + if (m_pSeekTarget) { + m_vecSeekPos = m_pSeekTarget->GetPosition(); + Seek(); + } else { + ClearSeek(); + } + break; + default: + break; + } + + switch (m_nPedType) { + case PEDTYPE_EMERGENCY: + if (IsPedInControl() || m_nPedState == PED_DRIVING) + MedicAI(); + break; + case PEDTYPE_FIREMAN: + if (IsPedInControl()) + FiremanAI(); + break; + default: + return; + } + } +} + +// This function was buggy and incomplete in both III and VC, firemen had to be in 5.0m range of fire etc. etc. +// Copied some code from MedicAI to make it work. +void +CEmergencyPed::FiremanAI(void) +{ + float fireDist; + CFire *nearestFire; + + switch (m_nEmergencyPedState) { + case EMERGENCY_PED_READY: + nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist); + if (nearestFire) { + SetPedState(PED_NONE); + SetSeek(nearestFire->m_vecPos, 1.0f); + SetMoveState(PEDMOVE_RUN); + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + m_pAttendedFire = nearestFire; +#ifdef FIX_BUGS + bIsRunning = true; + ++nearestFire->m_nFiremenPuttingOut; +#endif + } + break; + case EMERGENCY_PED_DETERMINE_NEXT_STATE: + nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist); + if (nearestFire && nearestFire != m_pAttendedFire) { + SetPedState(PED_NONE); + SetSeek(nearestFire->m_vecPos, 1.0f); + SetMoveState(PEDMOVE_RUN); +#ifdef FIX_BUGS + bIsRunning = true; + if (m_pAttendedFire) { + --m_pAttendedFire->m_nFiremenPuttingOut; + } + ++nearestFire->m_nFiremenPuttingOut; + m_pAttendedFire = nearestFire; + } else if (!nearestFire) { +#else + m_pAttendedFire = nearestFire; + } else { +#endif + m_nEmergencyPedState = EMERGENCY_PED_STOP; + } + + // "Extinguish" the fire (Will overwrite the stop decision above if the attended and nearest fires are same) + if (fireDist < 5.0f) { + SetIdle(); + m_nEmergencyPedState = EMERGENCY_PED_STAND_STILL; + } + break; + case EMERGENCY_PED_STAND_STILL: + if (!m_pAttendedFire->m_bIsOngoing) + m_nEmergencyPedState = EMERGENCY_PED_STOP; + + // Leftover + // fireDist = 30.0f; + nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist); + if (nearestFire) { +#ifdef FIX_BUGS + if(nearestFire != m_pAttendedFire && (nearestFire->m_vecPos - GetPosition()).Magnitude() < 30.0f) +#endif + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + } + Say(SOUND_PED_EXTINGUISHING_FIRE); + break; + case EMERGENCY_PED_STOP: +#ifdef FIX_BUGS + bIsRunning = false; + if (m_pAttendedFire) +#endif + --m_pAttendedFire->m_nFiremenPuttingOut; + + SetPedState(PED_NONE); + SetWanderPath(CGeneral::GetRandomNumber() & 7); + m_pAttendedFire = nil; + m_nEmergencyPedState = EMERGENCY_PED_READY; + SetMoveState(PEDMOVE_WALK); + break; + default: break; + } +} + +void +CEmergencyPed::MedicAI(void) +{ + float distToEmergency; + if (!bInVehicle && IsPedInControl()) { + ScanForThreats(); + if (m_threatEntity && m_threatEntity->IsPed() && ((CPed*)m_threatEntity)->IsPlayer()) { + if (((CPed*)m_threatEntity)->GetWeapon()->IsTypeMelee()) { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + } else { + SetFlee(m_threatEntity, 6000); + Say(SOUND_PED_FLEE_SPRINT); + } + return; + } + } + + if (InVehicle()) { + if (m_pMyVehicle->IsCar() && m_objective != OBJECTIVE_LEAVE_CAR) { + if (gAccidentManager.FindNearestAccident(m_pMyVehicle->GetPosition(), &distToEmergency) + && distToEmergency < 25.0f && m_pMyVehicle->m_vecMoveSpeed.Magnitude() < 0.01f) { + + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + Say(SOUND_PED_LEAVE_VEHICLE); + } else if (m_pMyVehicle->pDriver == this && m_nPedState == PED_DRIVING + && m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_NONE && !(CGeneral::GetRandomNumber() & 31)) { + + bool waitUntilMedicEntersCar = false; + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->m_nPedType == PEDTYPE_EMERGENCY) { + if ((nearPed->m_nPedState == PED_SEEK_CAR || nearPed->m_nPedState == PED_ENTER_CAR) + && nearPed->m_pMyVehicle == m_pMyVehicle) { + waitUntilMedicEntersCar = true; + break; + } + } + } + if (!waitUntilMedicEntersCar) { + CCarCtrl::JoinCarWithRoadSystem(m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + m_pMyVehicle->m_bSirenOrAlarm = false; + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 12; + m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_SLOW_DOWN_FOR_CARS; + if (m_pMyVehicle->bIsAmbulanceOnDuty) { + m_pMyVehicle->bIsAmbulanceOnDuty = false; + --CCarCtrl::NumAmbulancesOnDuty; + } + } + } + } + } + + CVector headPos, midPos; + CAccident *nearestAccident; + if (IsPedInControl()) { + switch (m_nEmergencyPedState) { + case EMERGENCY_PED_READY: + nearestAccident = gAccidentManager.FindNearestAccident(GetPosition(), &distToEmergency); + field_1360 = 0; + if (nearestAccident) { + m_pRevivedPed = nearestAccident->m_pVictim; + m_pRevivedPed->RegisterReference((CEntity**)&m_pRevivedPed); + m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID); + m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD); + SetSeek((headPos + midPos) * 0.5f, 1.0f); + SetObjective(OBJECTIVE_NONE); + bIsRunning = true; + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + m_pAttendedAccident = nearestAccident; + ++m_pAttendedAccident->m_nMedicsAttending; + } else { + if (m_pMyVehicle) { + if (!bInVehicle) { + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_pMyVehicle->pDriver || m_pMyVehicle->m_nGettingInFlags) { + + CPed* driver = m_pMyVehicle->pDriver; + if (driver && driver->m_nPedType != PEDTYPE_EMERGENCY && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, driver); + } else if (m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER + && m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER + && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_pMyVehicle); + } + } else { + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } + } else if (m_nPedState != PED_WANDER_PATH) { + SetWanderPath(CGeneral::GetRandomNumber() & 7); + } + } + break; + case EMERGENCY_PED_DETERMINE_NEXT_STATE: + nearestAccident = gAccidentManager.FindNearestAccident(GetPosition(), &distToEmergency); + if (nearestAccident) { + if (nearestAccident != m_pAttendedAccident || m_nPedState != PED_SEEK_POS) { + m_pRevivedPed = nearestAccident->m_pVictim; + m_pRevivedPed->RegisterReference((CEntity**)&m_pRevivedPed); + if (!InRange(m_pRevivedPed)) { + m_nEmergencyPedState = EMERGENCY_PED_STOP; + break; + } + m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID); + m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD); + SetSeek((headPos + midPos) * 0.5f, nearestAccident->m_nMedicsPerformingCPR * 0.5f + 1.0f); + SetObjective(OBJECTIVE_NONE); + bIsRunning = true; + --m_pAttendedAccident->m_nMedicsAttending; + ++nearestAccident->m_nMedicsAttending; + m_pAttendedAccident = nearestAccident; + } + } else { + m_nEmergencyPedState = EMERGENCY_PED_STOP; + bIsRunning = false; + } + if (distToEmergency < 5.0f) { + if (m_pRevivedPed->m_pFire) { + bIsRunning = false; + SetMoveState(PEDMOVE_STILL); + } else if (distToEmergency < 4.5f) { + bIsRunning = false; + SetMoveState(PEDMOVE_WALK); + if (distToEmergency < 1.0f + || distToEmergency < 4.5f && m_pAttendedAccident->m_nMedicsPerformingCPR) { + m_nEmergencyPedState = EMERGENCY_PED_START_CPR; + } + } + } + break; + case EMERGENCY_PED_START_CPR: + if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f || m_pRevivedPed->bFadeOut) { + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + } else { + m_pRevivedPed->m_bloodyFootprintCountOrDeathTime = CTimer::GetTimeInMilliseconds(); + SetMoveState(PEDMOVE_STILL); + SetPedState(PED_CPR); + m_nLastPedState = PED_CPR; + SetLookFlag(m_pRevivedPed, 0); + SetLookTimer(500); + Say(SOUND_PED_HEALING); + if (m_pAttendedAccident->m_nMedicsPerformingCPR) { + SetIdle(); + m_nEmergencyPedState = EMERGENCY_PED_STAND_STILL; + } else { + m_nEmergencyPedState = EMERGENCY_PED_FACE_TO_PATIENT; + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_MEDIC_CPR, 4.0f); + bIsDucking = true; + } + SetLookTimer(2000); + ++m_pAttendedAccident->m_nMedicsPerformingCPR; + m_bStartedToCPR = true; + } + break; + case EMERGENCY_PED_FACE_TO_PATIENT: + if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f) + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + else { + m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID); + m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD); + midPos = (headPos + midPos) * 0.5f; + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + midPos.x, midPos.y, + GetPosition().x, GetPosition().y); + m_fRotationDest = CGeneral::LimitAngle(m_fRotationDest); + m_pLookTarget = m_pRevivedPed; + m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget); + TurnBody(); + + if (Abs(m_fRotationCur - m_fRotationDest) < DEGTORAD(45.0f)) + m_nEmergencyPedState = EMERGENCY_PED_PERFORM_CPR; + else + m_fRotationCur = (m_fRotationCur + m_fRotationDest) * 0.5f; + } + break; + case EMERGENCY_PED_PERFORM_CPR: + if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f) { + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + break; + } + m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID); + m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD); + midPos = (headPos + midPos) * 0.5f; + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + midPos.x, midPos.y, + GetPosition().x, GetPosition().y); + m_fRotationDest = CGeneral::LimitAngle(m_fRotationDest); + m_pLookTarget = m_pRevivedPed; + m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget); + TurnBody(); + if (CTimer::GetTimeInMilliseconds() <= m_lookTimer) { + SetMoveState(PEDMOVE_STILL); + break; + } + m_nEmergencyPedState = EMERGENCY_PED_STOP_CPR; + SetPedState(PED_NONE); + SetMoveState(PEDMOVE_WALK); + m_pVehicleAnim = nil; + if (!m_pRevivedPed->bBodyPartJustCameOff) { + m_pRevivedPed->m_fHealth = 100.0f; + m_pRevivedPed->SetPedState(PED_NONE); + m_pRevivedPed->m_nLastPedState = PED_WANDER_PATH; + m_pRevivedPed->SetGetUp(); + m_pRevivedPed->bUsesCollision = true; + m_pRevivedPed->SetMoveState(PEDMOVE_WALK); + m_pRevivedPed->RestartNonPartialAnims(); + m_pRevivedPed->bIsPedDieAnimPlaying = false; + m_pRevivedPed->bKnockedUpIntoAir = false; + m_pRevivedPed->m_pCollidingEntity = nil; + } + break; + case EMERGENCY_PED_STOP_CPR: + m_nEmergencyPedState = EMERGENCY_PED_STOP; + bIsDucking = true; + break; + case EMERGENCY_PED_STAND_STILL: + if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f) + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + else { + if (!m_pAttendedAccident->m_pVictim) + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + if (!m_pAttendedAccident->m_nMedicsPerformingCPR) + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + if (gAccidentManager.UnattendedAccidents()) + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + } + break; + case EMERGENCY_PED_STOP: + m_bStartedToCPR = false; + SetPedState(PED_NONE); + if (m_pAttendedAccident) { + m_pAttendedAccident->m_pVictim = nil; + --m_pAttendedAccident->m_nMedicsAttending; + m_pAttendedAccident = nil; + } + SetWanderPath(CGeneral::GetRandomNumber() & 7); + m_pRevivedPed = nil; + m_nEmergencyPedState = EMERGENCY_PED_READY; + SetMoveState(PEDMOVE_WALK); + break; + default: break; + } + } +} diff --git a/src/peds/EmergencyPed.h b/src/peds/EmergencyPed.h new file mode 100644 index 0000000..390ba0b --- /dev/null +++ b/src/peds/EmergencyPed.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Ped.h" + +class CAccident; +class CFire; + +enum EmergencyPedState +{ + EMERGENCY_PED_READY = 0x0, + EMERGENCY_PED_DETERMINE_NEXT_STATE = 0x1, // you can set that anytime you want + EMERGENCY_PED_START_CPR = 0x2, + EMERGENCY_PED_FLAG_4 = 0x4, // unused + EMERGENCY_PED_FLAG_8 = 0x8, // unused + EMERGENCY_PED_FACE_TO_PATIENT = 0x10, // for CPR + EMERGENCY_PED_PERFORM_CPR = 0x20, + EMERGENCY_PED_STOP_CPR = 0x40, + EMERGENCY_PED_STAND_STILL = 0x80, // waiting colleagues for medics, "extinguishing" fire for firemen + EMERGENCY_PED_STOP = 0x100, +}; + +class CEmergencyPed : public CPed +{ +public: + CPed *m_pRevivedPed; + EmergencyPedState m_nEmergencyPedState; + CAccident *m_pAttendedAccident; + CFire *m_pAttendedFire; + bool m_bStartedToCPR; // set but unused + int32 field_1360; // set to 0 but unused + + CEmergencyPed(uint32); + ~CEmergencyPed() { } + bool InRange(CPed*); + void ProcessControl(void); + void FiremanAI(void); + void MedicAI(void); +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CEmergencyPed, 0x554); +#endif diff --git a/src/peds/Gangs.cpp b/src/peds/Gangs.cpp new file mode 100644 index 0000000..be29379 --- /dev/null +++ b/src/peds/Gangs.cpp @@ -0,0 +1,78 @@ +#include "common.h" + +#include "ModelIndices.h" +#include "Gangs.h" +#include "Weapon.h" +#include "SaveBuf.h" + +CGangInfo CGangs::Gang[NUM_GANGS]; + +CGangInfo::CGangInfo() : + m_nVehicleMI(MI_BUS), + m_nPedModelOverride(-1), + m_Weapon1(WEAPONTYPE_UNARMED), + m_Weapon2(WEAPONTYPE_UNARMED) +{} + +void CGangs::Initialise(void) +{ + Gang[GANG_MAFIA].m_nVehicleMI = MI_MAFIA; + Gang[GANG_TRIAD].m_nVehicleMI = MI_BELLYUP; + Gang[GANG_DIABLOS].m_nVehicleMI = MI_DIABLOS; + Gang[GANG_YAKUZA].m_nVehicleMI = MI_YAKUZA; + Gang[GANG_YARDIE].m_nVehicleMI = MI_YARDIE; + Gang[GANG_COLUMB].m_nVehicleMI = MI_COLUMB; + Gang[GANG_HOODS].m_nVehicleMI = MI_HOODS; + Gang[GANG_7].m_nVehicleMI = -1; + Gang[GANG_8].m_nVehicleMI = -1; +#ifdef FIX_BUGS + for (int i = 0; i < NUM_GANGS; i++) + Gang[i].m_nPedModelOverride = -1; +#endif +} + +void CGangs::SetGangVehicleModel(int16 gang, int32 model) +{ + GetGangInfo(gang)->m_nVehicleMI = model; +} + +void CGangs::SetGangWeapons(int16 gang, int32 weapon1, int32 weapon2) +{ + CGangInfo *gi = GetGangInfo(gang); + gi->m_Weapon1 = weapon1; + gi->m_Weapon2 = weapon2; +} + +void CGangs::SetGangPedModelOverride(int16 gang, int8 ovrd) +{ + GetGangInfo(gang)->m_nPedModelOverride = ovrd; +} + +int8 CGangs::GetGangPedModelOverride(int16 gang) +{ + return GetGangInfo(gang)->m_nPedModelOverride; +} + +void CGangs::SaveAllGangData(uint8 *buf, uint32 *size) +{ +INITSAVEBUF + + *size = SAVE_HEADER_SIZE + sizeof(Gang); + WriteSaveHeader(buf, 'G','N','G','\0', *size - SAVE_HEADER_SIZE); + for (int i = 0; i < NUM_GANGS; i++) + WriteSaveBuf(buf, Gang[i]); + +VALIDATESAVEBUF(*size); +} + +void CGangs::LoadAllGangData(uint8 *buf, uint32 size) +{ + Initialise(); + +INITSAVEBUF + CheckSaveHeader(buf, 'G','N','G','\0', size - SAVE_HEADER_SIZE); + + for (int i = 0; i < NUM_GANGS; i++) + ReadSaveBuf(&Gang[i], buf); +VALIDATESAVEBUF(size); +} diff --git a/src/peds/Gangs.h b/src/peds/Gangs.h new file mode 100644 index 0000000..c8ea291 --- /dev/null +++ b/src/peds/Gangs.h @@ -0,0 +1,44 @@ +#pragma once + +struct CGangInfo +{ + int32 m_nVehicleMI; + int8 m_nPedModelOverride; + int32 m_Weapon1; + int32 m_Weapon2; + + CGangInfo(); +}; + +VALIDATE_SIZE(CGangInfo, 0x10); + +enum { + GANG_MAFIA = 0, + GANG_TRIAD, + GANG_DIABLOS, + GANG_YAKUZA, + GANG_YARDIE, + GANG_COLUMB, + GANG_HOODS, + GANG_7, + GANG_8, + NUM_GANGS +}; + +class CGangs +{ +public: + static void Initialise(void); + static void SetGangVehicleModel(int16, int32); + static void SetGangWeapons(int16, int32, int32); + static void SetGangPedModelOverride(int16, int8); + static int8 GetGangPedModelOverride(int16); + static void SaveAllGangData(uint8 *, uint32 *); + static void LoadAllGangData(uint8 *, uint32); + + static int32 GetGangVehicleModel(int16 gang) { return Gang[gang].m_nVehicleMI; } + static CGangInfo *GetGangInfo(int16 gang) { return &Gang[gang]; } + +private: + static CGangInfo Gang[NUM_GANGS]; +}; diff --git a/src/peds/Ped.cpp b/src/peds/Ped.cpp new file mode 100644 index 0000000..6b28dcb --- /dev/null +++ b/src/peds/Ped.cpp @@ -0,0 +1,8557 @@ +#include "common.h" + +#include "main.h" +#include "Pools.h" +#include "Particle.h" +#include "RpAnimBlend.h" +#include "Bones.h" +#include "Ped.h" +#include "AnimBlendAssociation.h" +#include "Fire.h" +#include "DMAudio.h" +#include "General.h" +#include "VisibilityPlugins.h" +#include "HandlingMgr.h" +#include "Replay.h" +#include "Radar.h" +#include "PedPlacement.h" +#include "Shadows.h" +#include "Weather.h" +#include "ZoneCull.h" +#include "Population.h" +#include "Pad.h" +#include "Phones.h" +#include "TrafficLights.h" +#include "CopPed.h" +#include "Script.h" +#include "CarCtrl.h" +#include "Garages.h" +#include "WaterLevel.h" +#include "Timecycle.h" +#include "ParticleObject.h" +#include "Floater.h" +#include "Range2D.h" +#include "Wanted.h" +#include "SaveBuf.h" + +CPed *gapTempPedList[50]; +uint16 gnNumTempPedList; + +static CColPoint aTempPedColPts[MAX_COLLISION_POINTS]; + + +uint16 CPed::nThreatReactionRangeMultiplier = 1; +uint16 CPed::nEnterCarRangeMultiplier = 1; + +bool CPed::bNastyLimbsCheat; +bool CPed::bPedCheat2; +bool CPed::bPedCheat3; +CVector2D CPed::ms_vec2DFleePosition; + +void *CPed::operator new(size_t sz) throw() { return CPools::GetPedPool()->New(); } +void *CPed::operator new(size_t sz, int handle) throw() { return CPools::GetPedPool()->New(handle); } +void CPed::operator delete(void *p, size_t sz) throw() { CPools::GetPedPool()->Delete((CPed*)p); } +void CPed::operator delete(void *p, int handle) throw() { CPools::GetPedPool()->Delete((CPed*)p); } + +#ifdef DEBUGMENU +bool CPed::bPopHeadsOnHeadshot = false; +#endif + +CPed::CPed(uint32 pedType) : m_pedIK(this) +{ + m_type = ENTITY_TYPE_PED; + bPedPhysics = true; + bUseCollisionRecords = true; + + m_vecAnimMoveDelta.x = 0.0f; + m_vecAnimMoveDelta.y = 0.0f; + m_fHealth = 100.0f; + m_fArmour = 0.0f; + m_nPedType = pedType; + m_lastSoundStart = 0; + m_soundStart = 0; + m_lastQueuedSound = SOUND_NO_SOUND; + m_queuedSound = SOUND_NO_SOUND; + m_objective = OBJECTIVE_NONE; + m_prevObjective = OBJECTIVE_NONE; +#ifdef FIX_BUGS + m_objectiveTimer = 0; +#endif + CharCreatedBy = RANDOM_CHAR; + m_leader = nil; + m_pedInObjective = nil; + m_carInObjective = nil; + bInVehicle = false; + m_pMyVehicle = nil; + m_pVehicleAnim = nil; + m_vecOffsetSeek.x = 0.0f; + m_vecOffsetSeek.y = 0.0f; + m_vecOffsetSeek.z = 0.0f; + m_pedFormation = FORMATION_UNDEFINED; + m_collidingThingTimer = 0; + m_nPedStateTimer = 0; + m_actionX = 0.0f; + m_actionY = 0.0f; + m_phoneTalkTimer = 0; + m_stateUnused = 0; + m_leaveCarTimer = 0; + m_getUpTimer = 0; + m_attackTimer = 0; + m_timerUnused = 0; + m_lookTimer = 0; + m_chatTimer = 0; + m_shootTimer = 0; + m_carJackTimer = 0; + m_duckAndCoverTimer = 0; + m_moved = CVector2D(0.0f, 0.0f); + m_fRotationCur = 0.0f; + m_headingRate = 15.0f; + m_fRotationDest = 0.0f; + m_vehDoor = CAR_DOOR_LF; + m_walkAroundType = 0; + m_pCurrentPhysSurface = nil; + m_vecOffsetFromPhysSurface = CVector(0.0f, 0.0f, 0.0f); + m_pSeekTarget = nil; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_wepSkills = 0; + m_distanceToCountSeekDone = 1.0f; + bRunningToPhone = false; + m_phoneId = -1; + m_lastAccident = 0; + m_fleeFrom = nil; + m_fleeFromPosX = 0; + m_fleeFromPosY = 0; + m_fleeTimer = 0; + m_vecSeekPosEx = CVector(0.0f, 0.0f, 0.0f); + m_distanceToCountSeekDoneEx = 0.0f; + m_nWaitState = WAITSTATE_FALSE; + m_nWaitTimer = 0; + m_pCollidingEntity = nil; + m_nPedState = PED_IDLE; + m_nLastPedState = PED_NONE; + m_nMoveState = PEDMOVE_STILL; +#ifdef FIX_BUGS + m_nPrevMoveState = PEDMOVE_NONE; +#endif + m_nStoredMoveState = PEDMOVE_NONE; + m_pFire = nil; + m_pPointGunAt = nil; + m_pLookTarget = nil; + m_fLookDirection = 0.0f; + m_pCurSurface = nil; + m_wanderRangeBounds = nil; + m_nPathNodes = 0; + m_nCurPathNode = 0; + m_nPathDir = 0; + m_pLastPathNode = nil; + m_pNextPathNode = nil; + m_routeLastPoint = -1; + m_routeStartPoint = 0; + m_routePointsPassed = 0; + m_routeType = 0; + m_bodyPartBleeding = -1; + + m_fMass = 70.0f; + m_fTurnMass = 100.0f; + m_fAirResistance = 0.4f / m_fMass; + m_fElasticity = 0.05f; + + bIsStanding = false; + bWasStanding = false; + bIsAttacking = false; + bIsPointingGunAt = false; + bIsLooking = false; + bKeepTryingToLook = false; + bIsRestoringLook = false; + bIsAimingGun = false; + + bIsRestoringGun = false; + bCanPointGunAtTarget = false; + bIsTalking = false; + bIsInTheAir = false; + bIsLanding = false; + bIsRunning = false; + bHitSomethingLastFrame = false; + bVehEnterDoorIsBlocked = false; + + bCanPedEnterSeekedCar = false; + bRespondsToThreats = true; + bRenderPedInCar = true; + bChangedSeat = false; + bUpdateAnimHeading = false; + bBodyPartJustCameOff = false; + bIsShooting = false; + bFindNewNodeAfterStateRestore = false; + + bGonnaInvestigateEvent = false; + bPedIsBleeding = false; + bStopAndShoot = false; + bIsPedDieAnimPlaying = false; + bUsePedNodeSeek = false; + bObjectiveCompleted = false; + bScriptObjectiveCompleted = false; + + bKindaStayInSamePlace = false; + bBeingChasedByPolice = false; + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + bIsDucking = false; + bGetUpAnimStarted = false; + bDoBloodyFootprints = false; + bFleeAfterExitingCar = false; + + bWanderPathAfterExitingCar = false; + bIsLeader = false; + bDontDragMeOutCar = false; + m_ped_flagF8 = false; + bWillBeQuickJacked = false; + bCancelEnteringCar = false; + bObstacleShowedUpDuringKillObjective = false; + bDuckAndCover = false; + + bStillOnValidPoly = false; + bAllowMedicsToReviveMe = true; + bResetWalkAnims = false; + bStartWanderPathOnFoot = false; + bOnBoat = false; + bBusJacked = false; + bGonnaKillTheCarJacker = false; + bFadeOut = false; + + bKnockedUpIntoAir = false; + bHitSteepSlope = false; + bCullExtraFarAway = false; + bClearObjective = false; + bTryingToReachDryLand = false; + bCollidedWithMyVehicle = false; + bRichFromMugging = false; + bChrisCriminal = false; + + bShakeFist = false; + bNoCriticalHits = false; + bVehExitWillBeInstant = false; + bHasAlreadyBeenRecorded = false; + bFallenDown = false; +#ifdef KANGAROO_CHEAT + m_ped_flagI80 = false; +#endif +#ifdef VC_PED_PORTS + bSomeVCflag1 = false; +#endif + + if (CGeneral::GetRandomNumber() & 3) + bHasACamera = false; + else + bHasACamera = true; + + m_audioEntityId = DMAudio.CreateEntity(AUDIOTYPE_PHYSICAL, this); + DMAudio.SetEntityStatus(m_audioEntityId, TRUE); + m_fearFlags = CPedType::GetThreats(m_nPedType); + m_threatEntity = nil; + m_eventOrThreat = CVector2D(0.0f, 0.0f); + m_pEventEntity = nil; + m_fAngleToEvent = 0.0f; + m_numNearPeds = 0; + + for (int i = 0; i < ARRAY_SIZE(m_nearPeds); i++) { + m_nearPeds[i] = nil; + if (i < ARRAY_SIZE(m_pPathNodesStates)) { + m_pPathNodesStates[i] = nil; + } + } + m_maxWeaponTypeAllowed = WEAPONTYPE_UNARMED; + m_currentWeapon = WEAPONTYPE_UNARMED; + m_storedWeapon = WEAPONTYPE_UNIDENTIFIED; + + for(int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++) { + CWeapon &weapon = GetWeapon(i); + weapon.m_eWeaponType = WEAPONTYPE_UNARMED; + weapon.m_eWeaponState = WEAPONSTATE_READY; + weapon.m_nAmmoInClip = 0; + weapon.m_nAmmoTotal = 0; + weapon.m_nTimer = 0; + } + + m_curFightMove = FIGHTMOVE_NULL; + GiveWeapon(WEAPONTYPE_UNARMED, 0); + m_wepAccuracy = 60; + m_lastWepDam = -1; + m_collPoly.valid = false; + m_fCollisionSpeed = 0.0f; + m_wepModelID = -1; +#ifdef PED_SKIN + m_pWeaponModel = nil; +#endif + CPopulation::UpdatePedCount((ePedType)m_nPedType, false); +} + +CPed::~CPed(void) +{ + CWorld::Remove(this); + CRadar::ClearBlipForEntity(BLIP_CHAR, CPools::GetPedPool()->GetIndex(this)); + if (InVehicle()){ + uint8 door_flag = GetCarDoorFlag(m_vehDoor); + if (m_pMyVehicle->pDriver == this) + m_pMyVehicle->pDriver = nil; + else { + // FIX: Passenger counter now decreasing after removing ourself from vehicle. + m_pMyVehicle->RemovePassenger(this); + } + if (m_nPedState == PED_EXIT_CAR || m_nPedState == PED_DRAG_FROM_CAR) + m_pMyVehicle->m_nGettingOutFlags &= ~door_flag; + bInVehicle = false; + m_pMyVehicle = nil; + } else if (EnteringCar()) { + QuitEnteringCar(); + } + if (m_pFire) + m_pFire->Extinguish(); + CPopulation::UpdatePedCount((ePedType)m_nPedType, true); + DMAudio.DestroyEntity(m_audioEntityId); + + // Because of the nature of ped lists in GTA, it can sometimes be outdated. + // Remove ourself from nearPeds list of the Peds in our nearPeds list. +#ifdef FIX_BUGS + for(int i = 0; i < m_numNearPeds; i++) { + CPed *nearPed = m_nearPeds[i]; + assert(nearPed != nil); + if (!nearPed->IsPointerValid()) + continue; + + for(int j = 0; j < nearPed->m_numNearPeds;) { + assert(j == ARRAY_SIZE(m_nearPeds) - 1 || nearPed->m_nearPeds[j] || !nearPed->m_nearPeds[j+1]); // ensure nil comes after nil + + if (nearPed->m_nearPeds[j] == this) { + for (int k = j; k < ARRAY_SIZE(m_nearPeds) - 1; k++) { + nearPed->m_nearPeds[k] = nearPed->m_nearPeds[k + 1]; + nearPed->m_nearPeds[k + 1] = nil; + } + nearPed->m_nearPeds[ARRAY_SIZE(m_nearPeds) - 1] = nil; + nearPed->m_numNearPeds--; + } else + j++; + } + } +#endif +} + +void +CPed::Initialise(void) +{ + debug("Initialising CPed...\n"); + CPedType::Initialise(); + LoadFightData(); + SetAnimOffsetForEnterOrExitVehicle(); + debug("CPed ready\n"); +} + +void +CPed::SetModelIndex(uint32 mi) +{ + CEntity::SetModelIndex(mi); + RpAnimBlendClumpInit(GetClump()); + RpAnimBlendClumpFillFrameArray(GetClump(), m_pFrames); + CPedModelInfo *modelInfo = (CPedModelInfo *)CModelInfo::GetModelInfo(GetModelIndex()); + SetPedStats(modelInfo->m_pedStatType); + m_headingRate = m_pedStats->m_headingChangeRate; + m_animGroup = (AssocGroupId) modelInfo->m_animGroup; + CAnimManager::AddAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE); + + (*RPANIMBLENDCLUMPDATA(m_rwObject))->velocity2d = &m_vecAnimMoveDelta; + +#ifdef PED_SKIN + if(modelInfo->GetHitColModel() == nil) + modelInfo->CreateHitColModelSkinned(GetClump()); +#endif +} + +void +CPed::SetPedStats(ePedStats pedStat) +{ + m_pedStats = CPedStats::ms_apPedStats[pedStat]; +} + +void +CPed::BuildPedLists(void) +{ + if (((CTimer::GetFrameCounter() + m_randomSeed) % 16) == 0) { + CVector centre = CEntity::GetBoundCentre(); + CRect rect(centre.x - 20.0f, + centre.y - 20.0f, + centre.x + 20.0f, + centre.y + 20.0f); + int xstart = CWorld::GetSectorIndexX(rect.left); + int ystart = CWorld::GetSectorIndexY(rect.top); + int xend = CWorld::GetSectorIndexX(rect.right); + int yend = CWorld::GetSectorIndexY(rect.bottom); + gnNumTempPedList = 0; + + for(int y = ystart; y <= yend; y++) { + for(int x = xstart; x <= xend; x++) { + for (CPtrNode *pedPtrNode = CWorld::GetSector(x,y)->m_lists[ENTITYLIST_PEDS].first; pedPtrNode; pedPtrNode = pedPtrNode->next) { + CPed *ped = (CPed*)pedPtrNode->item; + if (ped != this && !ped->bInVehicle) { + float dist = (ped->GetPosition() - GetPosition()).Magnitude2D(); + if (nThreatReactionRangeMultiplier * 30.0f > dist) { +#ifdef FIX_BUGS + // If the gap ped list is full, sort it and truncate it + // before pushing more unsorted peds + if( gnNumTempPedList == ARRAY_SIZE(gapTempPedList) - 1 ) + { + gapTempPedList[gnNumTempPedList] = nil; + SortPeds(gapTempPedList, 0, gnNumTempPedList - 1); + gnNumTempPedList = ARRAY_SIZE(m_nearPeds); + } +#endif + + gapTempPedList[gnNumTempPedList] = ped; + gnNumTempPedList++; + // NOTE: We cannot absolutely fill the gap list, as the list is null-terminated before being passed to SortPeds + assert(gnNumTempPedList < ARRAY_SIZE(gapTempPedList)); + } + } + } + } + } + gapTempPedList[gnNumTempPedList] = nil; + SortPeds(gapTempPedList, 0, gnNumTempPedList - 1); + for (m_numNearPeds = 0; m_numNearPeds < ARRAY_SIZE(m_nearPeds); m_numNearPeds++) { + CPed *ped = gapTempPedList[m_numNearPeds]; + if (!ped) + break; + + m_nearPeds[m_numNearPeds] = ped; + } + for (int pedToClear = m_numNearPeds; pedToClear < ARRAY_SIZE(m_nearPeds); pedToClear++) + m_nearPeds[pedToClear] = nil; + } else { + for(int i = 0; i < ARRAY_SIZE(m_nearPeds); ) { + bool removePed = false; + if (m_nearPeds[i]) { + if (m_nearPeds[i]->IsPointerValid()) { + float distSqr = (GetPosition() - m_nearPeds[i]->GetPosition()).MagnitudeSqr2D(); + if (distSqr > 900.0f) + removePed = true; + } else + removePed = true; + } + + assert(i == ARRAY_SIZE(m_nearPeds) - 1 || m_nearPeds[i] || !m_nearPeds[i+1]); // ensure nil comes after nil + + if (removePed) { + // If we arrive here, the ped we're checking isn't "near", so we should remove it. + for (int j = i; j < ARRAY_SIZE(m_nearPeds) - 1; j++) { + m_nearPeds[j] = m_nearPeds[j + 1]; + m_nearPeds[j + 1] = nil; + } + m_nearPeds[ARRAY_SIZE(m_nearPeds) - 1] = nil; + m_numNearPeds--; + } else + i++; + } + } +} + +bool +CPed::OurPedCanSeeThisOne(CEntity *target) +{ + CColPoint colpoint; + CEntity *ent; + + CVector2D dist = CVector2D(target->GetPosition()) - CVector2D(GetPosition()); + + // Check if target is behind ped + if (DotProduct2D(dist, CVector2D(GetForward())) < 0.0f) + return false; + + // Check if target is too far away + if (dist.Magnitude() >= 40.0f) + return false; + + // Check line of sight from head + CVector headPos = this->GetPosition(); + headPos.z += 1.0f; + return !CWorld::ProcessLineOfSight(headPos, target->GetPosition(), colpoint, ent, true, false, false, false, false, false); +} + +// Some kind of binary sort +void +CPed::SortPeds(CPed **list, int min, int max) +{ + if (min >= max) + return; + + CVector leftDiff, rightDiff; + CVector middleDiff = GetPosition() - list[(max + min) / 2]->GetPosition(); + float middleDist = middleDiff.Magnitude(); + + int left = max; + int right = min; + while(right <= left){ + float rightDist, leftDist; + do { + rightDiff = GetPosition() - list[right]->GetPosition(); + rightDist = rightDiff.Magnitude(); + } while (middleDist > rightDist && ++right); + + do { + leftDiff = GetPosition() - list[left]->GetPosition(); + leftDist = leftDiff.Magnitude(); + } while (middleDist < leftDist && left--); + + if (right <= left) { + CPed *ped = list[right]; + list[right] = list[left]; + list[left] = ped; + right++; + left--; + } + } + SortPeds(list, min, left); + SortPeds(list, right, max); +} + +void +CPed::SetMoveState(eMoveState state) +{ + m_nMoveState = state; +} + +void +CPed::SetMoveAnim(void) +{ + if (m_nStoredMoveState == m_nMoveState || !IsPedInControl()) + return; + + if (m_nMoveState == PEDMOVE_NONE) { + m_nStoredMoveState = PEDMOVE_NONE; + return; + } + + AssocGroupId animGroupToUse; + if (m_leader && m_leader->IsPlayer()) + animGroupToUse = ASSOCGRP_PLAYER; + else + animGroupToUse = m_animGroup; + + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_BLOCK); + if (!animAssoc) { + CAnimBlendAssociation *fightIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); + animAssoc = fightIdleAssoc; + if (fightIdleAssoc && m_nPedState == PED_FIGHT) + return; + + if (fightIdleAssoc) { + CAnimBlendAssociation *idleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + if (!idleAssoc || idleAssoc->blendDelta <= 0.0f) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_IDLE, 8.0f); + } + } + } + if (!animAssoc) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + if (animAssoc) + if (m_nWaitState == WAITSTATE_STUCK || m_nWaitState == WAITSTATE_FINISH_FLEE) + return; + + if (animAssoc) { + CAnimBlendAssociation *idleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + if (!idleAssoc || idleAssoc->blendDelta <= 0.0f) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_IDLE, 4.0f); + } + } + } + if (!animAssoc) { + m_nStoredMoveState = m_nMoveState; + if (m_nMoveState == PEDMOVE_WALK || m_nMoveState == PEDMOVE_RUN || m_nMoveState == PEDMOVE_SPRINT) { + for (CAnimBlendAssociation *assoc = RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_PARTIAL); + assoc; assoc = RpAnimBlendGetNextAssociation(assoc, ASSOC_PARTIAL)) { + + if (!(assoc->flags & ASSOC_FADEOUTWHENDONE)) { + assoc->blendDelta = -2.0f; + assoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + + ClearAimFlag(); + ClearLookFlag(); + } + + switch (m_nMoveState) { + case PEDMOVE_STILL: + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_IDLE, 4.0f); + break; + case PEDMOVE_WALK: + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_WALK, 1.0f); + break; + case PEDMOVE_RUN: + if (m_nPedState == PED_FLEE_ENTITY) { + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_RUN, 3.0f); + } else { + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_RUN, 1.0f); + } + break; + case PEDMOVE_SPRINT: + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_RUNFAST, 1.0f); + break; + default: + break; + } + + if (animAssoc) { + if (m_leader) { + CAnimBlendAssociation *walkAssoc = RpAnimBlendClumpGetAssociation(m_leader->GetClump(), ANIM_STD_WALK); + if (!walkAssoc) + walkAssoc = RpAnimBlendClumpGetAssociation(m_leader->GetClump(), ANIM_STD_RUN); + + if (!walkAssoc) + walkAssoc = RpAnimBlendClumpGetAssociation(m_leader->GetClump(), ANIM_STD_RUNFAST); + + if (walkAssoc) { + animAssoc->speed = walkAssoc->speed; + } else { + if (CharCreatedBy == MISSION_CHAR) + animAssoc->speed = 1.0f; + else + animAssoc->speed = 1.2f - m_randomSeed * 0.4f / MYRAND_MAX; + + } + } else { + if (CharCreatedBy == MISSION_CHAR) + animAssoc->speed = 1.0f; + else + animAssoc->speed = 1.2f - m_randomSeed * 0.4f / MYRAND_MAX; + } + } + } +} + +void +CPed::StopNonPartialAnims(void) +{ + CAnimBlendAssociation *assoc; + + for (assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + if (!assoc->IsPartial()) + assoc->flags &= ~ASSOC_RUNNING; + } +} + +void +CPed::RestartNonPartialAnims(void) +{ + CAnimBlendAssociation *assoc; + + for (assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + if (!assoc->IsPartial()) + assoc->SetRun(); + } +} + +void +CPed::SetStoredState(void) +{ + if (m_nLastPedState != PED_NONE || !CanPedReturnToState()) + return; + + if (m_nPedState == PED_WANDER_PATH) { + bFindNewNodeAfterStateRestore = true; + if (m_nMoveState == PEDMOVE_NONE || m_nMoveState == PEDMOVE_STILL) + m_nMoveState = PEDMOVE_WALK; + } +#ifdef VC_PED_PORTS + if (m_nPedState != PED_IDLE) +#endif + { + m_nLastPedState = m_nPedState; + if (m_nMoveState >= m_nPrevMoveState) + m_nPrevMoveState = m_nMoveState; + } +} + +void +CPed::RestorePreviousState(void) +{ + if(!CanSetPedState() || m_nPedState == PED_FALL) + return; + + if (m_nPedState == PED_GETUP && !bGetUpAnimStarted) + return; + + if (InVehicle()) { + SetPedState(PED_DRIVING); + m_nLastPedState = PED_NONE; + } else { + if (m_nLastPedState == PED_NONE) { + if (!IsPlayer() && CharCreatedBy != MISSION_CHAR && m_objective == OBJECTIVE_NONE) { + if (SetWanderPath(CGeneral::GetRandomNumber() & 7) != 0) + return; + } + SetIdle(); + return; + } + + switch (m_nLastPedState) { + case PED_IDLE: + SetIdle(); + break; + case PED_WANDER_PATH: + SetPedState(PED_WANDER_PATH); + bIsRunning = false; + if (bFindNewNodeAfterStateRestore) { + if (m_pNextPathNode) { + CVector diff = m_pNextPathNode->GetPosition() - GetPosition(); + if (diff.MagnitudeSqr() < sq(7.0f)) { + SetMoveState(PEDMOVE_WALK); + break; + } + } + } + SetWanderPath(CGeneral::GetRandomNumber() & 7); + break; + default: + SetPedState(m_nLastPedState); + SetMoveState((eMoveState) m_nPrevMoveState); + break; + } + m_nLastPedState = PED_NONE; + } +} + +uint32 +CPed::ScanForThreats(void) +{ + int fearFlags = m_fearFlags; + CVector ourPos = GetPosition(); + float closestPedDist = 60.0f; + CVector2D explosionPos = GetPosition(); + if (fearFlags & PED_FLAG_EXPLOSION && CheckForExplosions(explosionPos)) { + m_eventOrThreat = explosionPos; + return PED_FLAG_EXPLOSION; + } + + CPed *shooter = nil; + if ((fearFlags & PED_FLAG_GUN) && (shooter = CheckForGunShots()) && (m_nPedType != shooter->m_nPedType || m_nPedType == PEDTYPE_CIVMALE || m_nPedType == PEDTYPE_CIVFEMALE)) { + if (!IsGangMember()) { + m_threatEntity = shooter; + m_threatEntity->RegisterReference((CEntity **) &m_threatEntity); + return PED_FLAG_GUN; + } + + if (CPedType::GetFlag(shooter->m_nPedType) & fearFlags) { + m_threatEntity = shooter; + m_threatEntity->RegisterReference((CEntity **) &m_threatEntity); + return CPedType::GetFlag(shooter->m_nPedType); + } + } + + CPed *deadPed; + if (fearFlags & PED_FLAG_DEADPEDS && CharCreatedBy != MISSION_CHAR + && (deadPed = CheckForDeadPeds()) != nil && (deadPed->GetPosition() - ourPos).MagnitudeSqr() < sq(20.0f) +#ifdef FIX_BUGS + && !deadPed->bIsInWater +#endif + ) { + m_pEventEntity = deadPed; + m_pEventEntity->RegisterReference((CEntity **) &m_pEventEntity); + return PED_FLAG_DEADPEDS; + } else { + uint32 flagsOfNearPed = 0; + + CPed *pedToFearFrom = nil; +#ifndef VC_PED_PORTS + for (int i = 0; i < m_numNearPeds; i++) { + if (CharCreatedBy != RANDOM_CHAR || m_nearPeds[i]->CharCreatedBy != MISSION_CHAR || m_nearPeds[i]->IsPlayer()) { + CPed *nearPed = m_nearPeds[i]; + + // BUG: WTF Rockstar?! Putting this here will result in returning the flags of farthest ped to us, since m_nearPeds is sorted by distance. + // Fixed at the bottom of the function. + flagsOfNearPed = CPedType::GetFlag(nearPed->m_nPedType); + + if (flagsOfNearPed & fearFlags) { + if (nearPed->m_fHealth > 0.0f && OurPedCanSeeThisOne(m_nearPeds[i])) { + // FIX: Taken from VC +#ifdef FIX_BUGS + float nearPedDistSqr = (nearPed->GetPosition() - ourPos).MagnitudeSqr2D(); +#else + float nearPedDistSqr = (CVector2D(ourPos) - explosionPos).MagnitudeSqr(); +#endif + if (sq(closestPedDist) > nearPedDistSqr) { + closestPedDist = Sqrt(nearPedDistSqr); + pedToFearFrom = m_nearPeds[i]; + } + } + } + } + } +#else + bool weSawOurEnemy = false; + bool weMaySeeOurEnemy = false; + float closestEnemyDist = 60.0f; + if ((CTimer::GetFrameCounter() + (uint8)m_randomSeed + 16) & 4) { + + for (int i = 0; i < m_numNearPeds; ++i) { + if (CharCreatedBy == RANDOM_CHAR && m_nearPeds[i]->CharCreatedBy == MISSION_CHAR && !m_nearPeds[i]->IsPlayer()) { + continue; + } + + // BUG: Explained at the same occurence of this bug above. Fixed at the bottom of the function. + flagsOfNearPed = CPedType::GetFlag(m_nearPeds[i]->m_nPedType); + + if (flagsOfNearPed & fearFlags) { + if (m_nearPeds[i]->m_fHealth > 0.0f) { + + // VC also has ability to include objects to line of sight check here (via last bit of flagsL) + if (OurPedCanSeeThisOne(m_nearPeds[i])) { + if (m_nearPeds[i]->m_nPedState == PED_ATTACK) { + if (m_nearPeds[i]->m_pedInObjective == this) { + + float enemyDistSqr = (m_nearPeds[i]->GetPosition() - ourPos).MagnitudeSqr2D(); + if (sq(closestEnemyDist) > enemyDistSqr) { + float enemyDist = Sqrt(enemyDistSqr); + weSawOurEnemy = true; + closestPedDist = enemyDist; + closestEnemyDist = enemyDist; + pedToFearFrom = m_nearPeds[i]; + } + } + } else { + float nearPedDistSqr = (m_nearPeds[i]->GetPosition() - ourPos).MagnitudeSqr2D(); + if (sq(closestPedDist) > nearPedDistSqr && !weSawOurEnemy) { + closestPedDist = Sqrt(nearPedDistSqr); + pedToFearFrom = m_nearPeds[i]; + } + } + } else if (!weSawOurEnemy) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->m_nPedState == PED_ATTACK) { + CColPoint foundCol; + CEntity *foundEnt; + + // We don't see him yet but he's behind a ped, vehicle or object + // VC also has ability to include objects to line of sight check here (via last bit of flagsL) + if (!CWorld::ProcessLineOfSight(ourPos, nearPed->GetPosition(), foundCol, foundEnt, + true, false, false, false, false, false, false)) { + + if (nearPed->m_pedInObjective == this) { + float enemyDistSqr = (m_nearPeds[i]->GetPosition() - ourPos).MagnitudeSqr2D(); + if (sq(closestEnemyDist) > enemyDistSqr) { + float enemyDist = Sqrt(enemyDistSqr); + weMaySeeOurEnemy = true; + closestPedDist = enemyDist; + closestEnemyDist = enemyDist; + pedToFearFrom = m_nearPeds[i]; + } + } else if (!nearPed->GetWeapon()->IsTypeMelee() && !weMaySeeOurEnemy) { + float nearPedDistSqr = (m_nearPeds[i]->GetPosition() - ourPos).MagnitudeSqr2D(); + if (sq(closestPedDist) > nearPedDistSqr) { + weMaySeeOurEnemy = true; + closestPedDist = Sqrt(nearPedDistSqr); + pedToFearFrom = m_nearPeds[i]; + } + } + } + } + } + } + } + } + } +#endif + int16 lastVehicle; + CEntity* vehicles[8]; + CWorld::FindObjectsInRange(ourPos, 20.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + CVehicle* foundVeh = nil; + for (int i = 0; i < lastVehicle; i++) { + CVehicle* nearVeh = (CVehicle*)vehicles[i]; + + CPed *driver = nearVeh->pDriver; + if (driver) { + + // BUG: Same bug as above. Fixed at the bottom of function. + flagsOfNearPed = CPedType::GetFlag(driver->m_nPedType); + if (flagsOfNearPed & fearFlags) { + if (driver->m_fHealth > 0.0f && OurPedCanSeeThisOne(nearVeh->pDriver)) { + // FIX: Taken from VC +#ifdef FIX_BUGS + float driverDistSqr = (driver->GetPosition() - ourPos).MagnitudeSqr2D(); +#else + float driverDistSqr = (CVector2D(ourPos) - explosionPos).MagnitudeSqr(); +#endif + if (sq(closestPedDist) > driverDistSqr) { + closestPedDist = Sqrt(driverDistSqr); + pedToFearFrom = nearVeh->pDriver; + } + } + } + } + } + m_threatEntity = pedToFearFrom; + if (m_threatEntity) + m_threatEntity->RegisterReference((CEntity **) &m_threatEntity); + +#ifdef FIX_BUGS + if (pedToFearFrom) + flagsOfNearPed = CPedType::GetFlag(((CPed*)m_threatEntity)->m_nPedType); + else + flagsOfNearPed = 0; +#endif + + return flagsOfNearPed; + } +} + +void +CPed::SetLookFlag(float direction, bool keepTryingToLook) +{ + if (m_lookTimer < CTimer::GetTimeInMilliseconds()) { + bIsLooking = true; + bIsRestoringLook = false; + m_pLookTarget = nil; + m_fLookDirection = direction; + m_lookTimer = 0; + bKeepTryingToLook = keepTryingToLook; + if (m_nPedState != PED_DRIVING) { + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + } + } +} + +void +CPed::SetLookFlag(CEntity *target, bool keepTryingToLook) +{ + if (m_lookTimer < CTimer::GetTimeInMilliseconds()) { + bIsLooking = true; + bIsRestoringLook = false; + m_pLookTarget = target; + m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget); + m_fLookDirection = 999999.0f; + m_lookTimer = 0; + bKeepTryingToLook = keepTryingToLook; + if (m_nPedState != PED_DRIVING) { + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + } + } +} + +void +CPed::ClearLookFlag(void) { + if (bIsLooking) { + bIsLooking = false; + bIsRestoringLook = true; + bShakeFist = false; + + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + if (IsPlayer()) + m_lookTimer = CTimer::GetTimeInMilliseconds() + 2000; + else + m_lookTimer = CTimer::GetTimeInMilliseconds() + 4000; + + if (m_nPedState == PED_LOOK_HEADING || m_nPedState == PED_LOOK_ENTITY) { + ClearLook(); + } + } +} + +void +FinishFuckUCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (animAssoc->animId == ANIM_STD_PARTIAL_FUCKU && ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) + ped->RemoveWeaponModel(0); +} + +void +CPed::MoveHeadToLook(void) +{ + CVector lookPos; + + if (m_lookTimer && CTimer::GetTimeInMilliseconds() > m_lookTimer) { + ClearLookFlag(); + } else if (m_nPedState == PED_DRIVING) { + m_pedIK.m_flags |= CPedIK::LOOKAROUND_HEAD_ONLY; + } + + if (m_pLookTarget) { + + if (!bShakeFist && GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) { + + CAnimBlendAssociation *fuckUAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_PARTIAL_FUCKU); + if (fuckUAssoc) { + + float animTime = fuckUAssoc->currentTime; + if (animTime > 4.0f / 30.0f && animTime - fuckUAssoc->timeStep > 4.0f / 30.0f) { + + bool lookingToCop = false; + if (m_pLookTarget->GetModelIndex() == MI_POLICE + || m_pLookTarget->IsPed() && ((CPed*)m_pLookTarget)->m_nPedType == PEDTYPE_COP) { + + lookingToCop = true; + } + + if (IsPlayer() && (m_pedStats->m_temper >= 52 || lookingToCop)) { + AddWeaponModel(MI_FINGERS); + ((CPlayerPed*)this)->AnnoyPlayerPed(true); + + } else if ((CGeneral::GetRandomNumber() & 3) == 0) { + AddWeaponModel(MI_FINGERS); + } + } + } + } + + if (m_pLookTarget->IsPed()) { + ((CPed*)m_pLookTarget)->m_pedIK.GetComponentPosition(lookPos, PED_MID); + } else { + lookPos = m_pLookTarget->GetPosition(); + } + + if (!m_pedIK.LookAtPosition(lookPos)) { + if (!bKeepTryingToLook) { + ClearLookFlag(); + } + return; + } + + if (!bShakeFist || bIsAimingGun || bIsRestoringGun) + return; + + if (m_lookTimer - CTimer::GetTimeInMilliseconds() >= 1000) + return; + + bool notRocketLauncher = false; + bool notTwoHanded = false; + AnimationId animToPlay = ANIM_STD_NUM; + + if (!GetWeapon()->IsType2Handed()) + notTwoHanded = true; + + if (notTwoHanded && GetWeapon()->m_eWeaponType != WEAPONTYPE_ROCKETLAUNCHER) + notRocketLauncher = true; + + if (IsPlayer() && notRocketLauncher) { + + if (m_pLookTarget->IsPed()) { + + if (m_pedStats->m_temper >= 49 && ((CPed*)m_pLookTarget)->m_nPedType != PEDTYPE_COP) { + + // FIX: Unreachable and meaningless condition +#ifndef FIX_BUGS + if (m_pedStats->m_temper < 47) +#endif + animToPlay = ANIM_STD_PARTIAL_PUNCH; + } else { + animToPlay = ANIM_STD_PARTIAL_FUCKU; + } + } else if (m_pedStats->m_temper > 49 || m_pLookTarget->GetModelIndex() == MI_POLICE) { + animToPlay = ANIM_STD_PARTIAL_FUCKU; + } + } else if (notRocketLauncher && (CGeneral::GetRandomNumber() & 1)) { + animToPlay = ANIM_STD_PARTIAL_FUCKU; + } + + if (animToPlay != ANIM_STD_NUM) { + CAnimBlendAssociation *newAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animToPlay, 4.0f); + + if (newAssoc) { + newAssoc->flags |= ASSOC_FADEOUTWHENDONE; + newAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (newAssoc->animId == ANIM_STD_PARTIAL_FUCKU) + newAssoc->SetDeleteCallback(FinishFuckUCB, this); + } + } + bShakeFist = false; + return; + } else if (999999.0f == m_fLookDirection) { + ClearLookFlag(); + } else if (!m_pedIK.LookInDirection(m_fLookDirection, 0.0f)) { + if (!bKeepTryingToLook) + ClearLookFlag(); + } +} + +void +CPed::RestoreHeadPosition(void) +{ + if (m_pedIK.RestoreLookAt()) { + bIsRestoringLook = false; + } +} + +void +CPed::SetAimFlag(float angle) +{ + bIsAimingGun = true; + bIsRestoringGun = false; + m_fLookDirection = angle; + m_lookTimer = 0; + m_pLookTarget = nil; + m_pSeekTarget = nil; + if (CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) + m_pedIK.m_flags |= CPedIK::AIMS_WITH_ARM; + else + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; +} + +void +CPed::SetAimFlag(CEntity *to) +{ + bIsAimingGun = true; + bIsRestoringGun = false; + m_pLookTarget = to; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + m_pSeekTarget = to; + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + m_lookTimer = 0; +} + +void +CPed::ClearAimFlag(void) +{ + if (bIsAimingGun) { + bIsAimingGun = false; + bIsRestoringGun = true; + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; +#if defined VC_PED_PORTS || defined FIX_BUGS + m_lookTimer = 0; +#endif + } + + if (IsPlayer()) { + ((CPlayerPed*)this)->m_fFPSMoveHeading = 0.0f; +#ifdef FREE_CAM + ((CPlayerPed*)this)->m_bFreeAimActive = false; +#endif + } +} + +void +CPed::AimGun(void) +{ + CVector vector; + + if (m_pSeekTarget) { + if (m_pSeekTarget->IsPed()) { + ((CPed*)m_pSeekTarget)->m_pedIK.GetComponentPosition(vector, PED_MID); + } else { + vector = m_pSeekTarget->GetPosition(); + } + Say(SOUND_PED_ATTACK); + + bCanPointGunAtTarget = m_pedIK.PointGunAtPosition(vector); + if (m_pLookTarget != m_pSeekTarget) { + SetLookFlag(m_pSeekTarget, true); + } + + } else { + if (IsPlayer()) { + bCanPointGunAtTarget = m_pedIK.PointGunInDirection(m_fLookDirection, ((CPlayerPed*)this)->m_fFPSMoveHeading); + } else { + bCanPointGunAtTarget = m_pedIK.PointGunInDirection(m_fLookDirection, 0.0f); + } + } +} + +void +CPed::RestoreGunPosition(void) +{ + if (bIsLooking) { + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + bIsRestoringGun = false; + } else if (m_pedIK.RestoreGunPosn()) { + bIsRestoringGun = false; + } else { + if (IsPlayer()) + ((CPlayerPed*)this)->m_fFPSMoveHeading = 0.0f; + } +} + +void +CPed::ScanForInterestingStuff(void) +{ + if (!IsPedInControl()) + return; + + if (m_objective != OBJECTIVE_NONE) + return; + + if (CharCreatedBy == MISSION_CHAR) + return; + + LookForSexyPeds(); + LookForSexyCars(); + if (LookForInterestingNodes()) + return; + + if (m_nPedType == PEDTYPE_CRIMINAL && m_carJackTimer < CTimer::GetTimeInMilliseconds()) { + // Find a car to steal or a ped to mug if we haven't already decided to steal a car + if (CGeneral::GetRandomNumber() % 100 < 10) { + int mostExpensiveVehAround = -1; + int bestMonetaryValue = 0; + + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity *vehicles[8]; + CWorld::FindObjectsInRange(pos, 10.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int i = 0; i < lastVehicle; i++) { + CVehicle* veh = (CVehicle*)vehicles[i]; + + if (veh->VehicleCreatedBy != MISSION_VEHICLE) { + if (veh->m_vecMoveSpeed.Magnitude() <= 0.1f && veh->IsVehicleNormal() + && veh->IsCar() && bestMonetaryValue < veh->pHandling->nMonetaryValue) { + mostExpensiveVehAround = i; + bestMonetaryValue = veh->pHandling->nMonetaryValue; + } + } + } + if (bestMonetaryValue > 2000 && mostExpensiveVehAround != -1 && vehicles[mostExpensiveVehAround]) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, vehicles[mostExpensiveVehAround]); + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 5000; + return; + } + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 5000; + } else if (m_objective != OBJECTIVE_MUG_CHAR && !(CGeneral::GetRandomNumber() & 7)) { + CPed *charToMug = nil; + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + + if ((nearPed->GetPosition() - GetPosition()).MagnitudeSqr() > sq(7.0f)) + break; + + if ((nearPed->m_nPedType == PEDTYPE_CIVFEMALE || nearPed->m_nPedType == PEDTYPE_CIVMALE + || nearPed->m_nPedType == PEDTYPE_CRIMINAL || nearPed->m_nPedType == PEDTYPE_UNUSED1 + || nearPed->m_nPedType == PEDTYPE_PROSTITUTE) + && nearPed->CharCreatedBy != MISSION_CHAR + && nearPed->IsPedShootable() + && nearPed->m_objective != OBJECTIVE_MUG_CHAR) { + charToMug = nearPed; + break; + } + } + if (charToMug) + SetObjective(OBJECTIVE_MUG_CHAR, charToMug); + + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 5000; + } + } + + if (m_nPedState == PED_WANDER_PATH) { +#ifndef VC_PED_PORTS + if (CTimer::GetTimeInMilliseconds() > m_chatTimer) { + + // += 2 is weird + for (int i = 0; i < m_numNearPeds; i += 2) { + if (m_nearPeds[i]->m_nPedState == PED_WANDER_PATH && WillChat(m_nearPeds[i])) { + if (CGeneral::GetRandomNumberInRange(0, 100) >= 100) + m_chatTimer = CTimer::GetTimeInMilliseconds() + 30000; + else { + if ((GetPosition() - m_nearPeds[i]->GetPosition()).Magnitude() >= 1.8f) { + m_chatTimer = CTimer::GetTimeInMilliseconds() + 30000; + } else if (CanSeeEntity(m_nearPeds[i])) { + int time = CGeneral::GetRandomNumber() % 4000 + 10000; + SetChat(m_nearPeds[i], time); + m_nearPeds[i]->SetChat(this, time); + return; + } + } + } + } + } +#else + if (CGeneral::GetRandomNumberInRange(0.0f, 1.0f) < 0.5f) { + if (CTimer::GetTimeInMilliseconds() > m_chatTimer) { + for (int i = 0; i < m_numNearPeds; i ++) { + if (m_nearPeds[i] && m_nearPeds[i]->m_nPedState == PED_WANDER_PATH) { + if ((GetPosition() - m_nearPeds[i]->GetPosition()).Magnitude() < 1.8f + && CanSeeEntity(m_nearPeds[i]) + && m_nearPeds[i]->CanSeeEntity(this) + && WillChat(m_nearPeds[i])) { + + int time = CGeneral::GetRandomNumber() % 4000 + 10000; + SetChat(m_nearPeds[i], time); + m_nearPeds[i]->SetChat(this, time); + return; + } + } + } + } + } else { + m_chatTimer = CTimer::GetTimeInMilliseconds() + 200; + } +#endif + } + + // Parts below aren't there in VC, they're in somewhere else. + if (!CGame::noProstitutes && m_nPedType == PEDTYPE_PROSTITUTE && CharCreatedBy != MISSION_CHAR + && m_objectiveTimer < CTimer::GetTimeInMilliseconds() && !CTheScripts::IsPlayerOnAMission()) { + + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity* vehicles[8]; + CWorld::FindObjectsInRange(pos, CHECK_NEARBY_THINGS_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int i = 0; i < lastVehicle; i++) { + CVehicle* veh = (CVehicle*)vehicles[i]; + + if (veh->IsVehicleNormal()) { + if (veh->IsCar()) { + if ((GetPosition() - veh->GetPosition()).Magnitude() < 5.0f && veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, nil)) { + SetObjective(OBJECTIVE_SOLICIT_VEHICLE, veh); + Say(SOUND_PED_SOLICIT); + return; + } + } + } + } + } + if (m_nPedType == PEDTYPE_CIVMALE || m_nPedType == PEDTYPE_CIVFEMALE) { + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity* vehicles[8]; + CWorld::FindObjectsInRange(pos, CHECK_NEARBY_THINGS_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int i = 0; i < lastVehicle; i++) { + CVehicle* veh = (CVehicle*)vehicles[i]; + + if (veh->GetModelIndex() == MI_MRWHOOP) { + if (veh->GetStatus() != STATUS_ABANDONED && veh->GetStatus() != STATUS_WRECKED) { + if ((GetPosition() - veh->GetPosition()).Magnitude() < 5.0f) { + SetObjective(OBJECTIVE_BUY_ICE_CREAM, veh); + return; + } + } + } + } + } +} + +bool +CPed::WillChat(CPed *stranger) +{ + if (m_pNextPathNode && m_pLastPathNode) { + if (m_pNextPathNode != m_pLastPathNode && ThePaths.TestCrossesRoad(m_pNextPathNode, m_pLastPathNode)) { + return false; + } + } + if (m_nSurfaceTouched == SURFACE_TARMAC) + return false; + if (stranger == this) + return false; + if (m_nPedType == stranger->m_nPedType) + return true; + if (m_nPedType == PEDTYPE_CRIMINAL) + return false; + if ((IsGangMember() || stranger->IsGangMember()) && m_nPedType != stranger->m_nPedType) + return false; + return true; +} + +void +CPed::CalculateNewVelocity(void) +{ + if (IsPedInControl()) { + float headAmount = DEGTORAD(m_headingRate) * CTimer::GetTimeStep(); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + float limitedRotDest = CGeneral::LimitRadianAngle(m_fRotationDest); + + if (m_fRotationCur - PI > limitedRotDest) { + limitedRotDest += 2 * PI; + } else if(PI + m_fRotationCur < limitedRotDest) { + limitedRotDest -= 2 * PI; + } + +#ifdef FREE_CAM + if (!CCamera::bFreeCam || !TheCamera.Cams[0].Using3rdPersonMouseCam()) +#endif + if (IsPlayer() && m_nPedState == PED_ATTACK) + headAmount /= 4.0f; + + float neededTurn = limitedRotDest - m_fRotationCur; + if (neededTurn <= headAmount) { + if (neededTurn > (-headAmount)) + m_fRotationCur += neededTurn; + else + m_fRotationCur -= headAmount; + } else { + m_fRotationCur += headAmount; + } + } + + CVector2D forward(Sin(m_fRotationCur), Cos(m_fRotationCur)); + + m_moved.x = CrossProduct2D(m_vecAnimMoveDelta, forward); // (m_vecAnimMoveDelta.x * Cos(m_fRotationCur)) + -Sin(m_fRotationCur) * m_vecAnimMoveDelta.y; + m_moved.y = DotProduct2D(m_vecAnimMoveDelta, forward); // m_vecAnimMoveDelta.y* Cos(m_fRotationCur) + (m_vecAnimMoveDelta.x * Sin(m_fRotationCur)); + + if (CTimer::GetTimeStep() >= 0.01f) { + m_moved = m_moved * (1 / CTimer::GetTimeStep()); + } else { + m_moved = m_moved * (1 / 100.0f); + } + + if ((!TheCamera.Cams[TheCamera.ActiveCam].GetWeaponFirstPersonOn() && !TheCamera.Cams[0].Using3rdPersonMouseCam()) + || FindPlayerPed() != this || !CanStrafeOrMouseControl()) + return; + + float walkAngle = WorkOutHeadingForMovingFirstPerson(m_fRotationCur); + float pedSpeed = m_moved.Magnitude(); + float localWalkAngle = CGeneral::LimitRadianAngle(walkAngle - m_fRotationCur); + + if (localWalkAngle < -0.5f * PI) { + localWalkAngle += PI; + } else if (localWalkAngle > 0.5f * PI) { + localWalkAngle -= PI; + } + + // Interestingly this part is responsible for diagonal walking. + if (localWalkAngle > -DEGTORAD(50.0f) && localWalkAngle < DEGTORAD(50.0f)) { + TheCamera.Cams[TheCamera.ActiveCam].m_fPlayerVelocity = pedSpeed; + m_moved = CVector2D(-Sin(walkAngle), Cos(walkAngle)) * pedSpeed; + } + + CAnimBlendAssociation *idleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + CAnimBlendAssociation *fightAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); +#ifdef VC_PED_PORTS + if ((!idleAssoc || idleAssoc->blendAmount < 0.5f) && !fightAssoc && !bIsDucking) { +#else + if ((!idleAssoc || idleAssoc->blendAmount < 0.5f) && !fightAssoc) { +#endif + LimbOrientation newUpperLegs; + newUpperLegs.yaw = localWalkAngle; + + if (newUpperLegs.yaw < -DEGTORAD(100.0f)) { + newUpperLegs.yaw += PI; + } else if (newUpperLegs.yaw > DEGTORAD(100.0f)) { + newUpperLegs.yaw -= PI; + } + + if (newUpperLegs.yaw > -DEGTORAD(50.0f) && newUpperLegs.yaw < DEGTORAD(50.0f)) { +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ +/* + // this looks shit + newUpperLegs.pitch = 0.0f; + RwV3d axis = { -1.0f, 0.0f, 0.0f }; + RtQuatRotate(&m_pFrames[PED_UPPERLEGL]->hanimFrame->q, &axis, RADTODEG(newUpperLegs.yaw), rwCOMBINEPRECONCAT); + RtQuatRotate(&m_pFrames[PED_UPPERLEGR]->hanimFrame->q, &axis, RADTODEG(newUpperLegs.yaw), rwCOMBINEPRECONCAT); +*/ + newUpperLegs.pitch = 0.1f; + RwV3d Xaxis = { 1.0f, 0.0f, 0.0f }; + RwV3d Zaxis = { 0.0f, 0.0f, 1.0f }; + RtQuatRotate(&m_pFrames[PED_UPPERLEGL]->hanimFrame->q, &Zaxis, RADTODEG(newUpperLegs.pitch), rwCOMBINEPOSTCONCAT); + RtQuatRotate(&m_pFrames[PED_UPPERLEGL]->hanimFrame->q, &Xaxis, RADTODEG(newUpperLegs.yaw), rwCOMBINEPOSTCONCAT); + RtQuatRotate(&m_pFrames[PED_UPPERLEGR]->hanimFrame->q, &Zaxis, RADTODEG(newUpperLegs.pitch), rwCOMBINEPOSTCONCAT); + RtQuatRotate(&m_pFrames[PED_UPPERLEGR]->hanimFrame->q, &Xaxis, RADTODEG(newUpperLegs.yaw), rwCOMBINEPOSTCONCAT); + + bDontAcceptIKLookAts = true; + }else +#endif + { + newUpperLegs.pitch = 0.0f; + m_pedIK.RotateTorso(m_pFrames[PED_UPPERLEGL], &newUpperLegs, false); + m_pedIK.RotateTorso(m_pFrames[PED_UPPERLEGR], &newUpperLegs, false); + } + } + } +} + +float +CPed::WorkOutHeadingForMovingFirstPerson(float offset) +{ + if (!IsPlayer()) + return 0.0f; + + CPad *pad0 = CPad::GetPad(0); + float leftRight = pad0->GetPedWalkLeftRight(); + float upDown = pad0->GetPedWalkUpDown(); + float &angle = ((CPlayerPed*)this)->m_fWalkAngle; + + if (upDown != 0.0f) { + angle = CGeneral::GetRadianAngleBetweenPoints(0.0f, 0.0f, -leftRight, upDown); + } else { + if (leftRight < 0.0f) + angle = 0.5f * PI; + else if (leftRight > 0.0f) + angle = -0.5f * PI; + } + + return CGeneral::LimitRadianAngle(offset + angle); +} + +void +CPed::UpdatePosition(void) +{ + if (CReplay::IsPlayingBack() || !bIsStanding) + return; + + CVector2D velocityChange; + + SetHeading(m_fRotationCur); + if (m_pCurrentPhysSurface) { + CVector2D velocityOfSurface; + if (!IsPlayer() && m_pCurrentPhysSurface->IsVehicle() && ((CVehicle*)m_pCurrentPhysSurface)->IsBoat()) { + + // It seems R* didn't like m_vecOffsetFromPhysSurface for boats + CVector offsetToSurface = GetPosition() - m_pCurrentPhysSurface->GetPosition(); + offsetToSurface.z -= FEET_OFFSET; + + CVector surfaceMoveVelocity = m_pCurrentPhysSurface->m_vecMoveSpeed; + CVector surfaceTurnVelocity = CrossProduct(m_pCurrentPhysSurface->m_vecTurnSpeed, offsetToSurface); + + // Also we use that weird formula instead of friction if it's boat + float slideMult = -m_pCurrentPhysSurface->m_vecTurnSpeed.MagnitudeSqr(); + velocityOfSurface = slideMult * offsetToSurface * CTimer::GetTimeStep() + (surfaceTurnVelocity + surfaceMoveVelocity); + m_vecMoveSpeed.z = slideMult * offsetToSurface.z * CTimer::GetTimeStep() + (surfaceTurnVelocity.z + surfaceMoveVelocity.z); + } else { + velocityOfSurface = m_pCurrentPhysSurface->GetSpeed(m_vecOffsetFromPhysSurface); + } + // Reminder: m_moved is displacement from walking/running. + velocityChange = m_moved + velocityOfSurface - m_vecMoveSpeed; + m_fRotationCur += m_pCurrentPhysSurface->m_vecTurnSpeed.z * CTimer::GetTimeStep(); + m_fRotationDest += m_pCurrentPhysSurface->m_vecTurnSpeed.z * CTimer::GetTimeStep(); + } else if (m_nSurfaceTouched == SURFACE_STEEP_CLIFF && (m_vecDamageNormal.x != 0.0f || m_vecDamageNormal.y != 0.0f)) { + // Ped got damaged by steep slope + m_vecMoveSpeed = CVector(0.0f, 0.0f, -0.001f); + // some kind of + CVector2D reactionForce = m_vecDamageNormal; + reactionForce.Normalise(); + + velocityChange = 0.02f * reactionForce + m_moved; + + float reactionAndVelocityDotProd = DotProduct2D(reactionForce, velocityChange); + // they're in same direction + if (reactionAndVelocityDotProd < 0.0f) { + velocityChange -= reactionAndVelocityDotProd * reactionForce; + } + } else { + velocityChange = m_moved - m_vecMoveSpeed; + } + + // Take time step into account + if (m_pCurrentPhysSurface) { + float speedChange = velocityChange.Magnitude(); + float changeMult = speedChange; + if (m_nPedState == PED_DIE && m_pCurrentPhysSurface->IsVehicle()) { + changeMult = 0.002f * CTimer::GetTimeStep(); + } else if (!(m_pCurrentPhysSurface->IsVehicle() && ((CVehicle*)m_pCurrentPhysSurface)->IsBoat())) { + changeMult = 0.01f * CTimer::GetTimeStep(); + } + + if (speedChange > changeMult) { + velocityChange = velocityChange * (changeMult / speedChange); + } + } + m_vecMoveSpeed.x += velocityChange.x; + m_vecMoveSpeed.y += velocityChange.y; +} + +void +CPed::CalculateNewOrientation(void) +{ + if (CReplay::IsPlayingBack() || !IsPedInControl()) + return; + + SetHeading(m_fRotationCur); +} + +void +CPed::ClearAll(void) +{ + if (!IsPedInControl() && m_nPedState != PED_DEAD) + return; + + SetPedState(PED_NONE); + SetMoveState(PEDMOVE_NONE); + m_pSeekTarget = nil; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_fleeFromPosX = 0.0f; + m_fleeFromPosY = 0.0f; + m_fleeFrom = nil; + m_fleeTimer = 0; + bUsesCollision = true; +#ifdef VC_PED_PORTS + ClearPointGunAt(); +#else + ClearAimFlag(); + ClearLookFlag(); +#endif + bIsPointingGunAt = false; + bRenderPedInCar = true; + bKnockedUpIntoAir = false; + m_pCollidingEntity = nil; +} + +void +CPed::ProcessBuoyancy(void) +{ + static uint32 nGenerateRaindrops = 0; + static uint32 nGenerateWaterCircles = 0; + CRGBA color(((0.5f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed()) * 127.5f), + ((0.5f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue()) * 127.5f), + ((0.5f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen()) * 127.5f), + CGeneral::GetRandomNumberInRange(48.0f, 96.0f)); + + if (bInVehicle) + return; + + CVector buoyancyPoint; + CVector buoyancyImpulse; + +#ifndef VC_PED_PORTS + float buoyancyLevel = (m_nPedState == PED_DEAD ? 1.5f : 1.3f); +#else + float buoyancyLevel = (m_nPedState == PED_DEAD ? 1.8f : 1.1f); +#endif + + if (mod_Buoyancy.ProcessBuoyancy(this, GRAVITY * m_fMass * buoyancyLevel, &buoyancyPoint, &buoyancyImpulse)) { + bTouchingWater = true; + CEntity *entity; + CColPoint point; + if (CWorld::ProcessVerticalLine(GetPosition(), GetPosition().z - 3.0f, point, entity, false, true, false, false, false, false, nil) + && entity->IsVehicle() && ((CVehicle*)entity)->IsBoat()) { + bIsInWater = false; + return; + } + bIsInWater = true; + ApplyMoveForce(buoyancyImpulse); + if (!DyingOrDead()) { + if (bTryingToReachDryLand) { + if (buoyancyImpulse.z / m_fMass > GRAVITY * 0.4f * CTimer::GetTimeStep()) { + bTryingToReachDryLand = false; + CVector pos = GetPosition(); + if (PlacePedOnDryLand()) { + if (m_fHealth > 20.0f) + InflictDamage(nil, WEAPONTYPE_DROWNING, 15.0f, PEDPIECE_TORSO, false); + + if (bIsInTheAir) { + RpAnimBlendClumpSetBlendDeltas(GetClump(), ASSOC_PARTIAL, -1000.0f); + bIsInTheAir = false; + } + pos.z = pos.z - 0.8f; +#ifdef PC_PARTICLE + CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, pos, CVector(0.0f, 0.0f, 0.0f), 0.0f, 50, color, true); +#else + CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, pos, CVector(0.0f, 0.0f, 0.0f), 0.0f, 50, CRGBA(0, 0, 0, 0), true); +#endif + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + SetPedState(PED_IDLE); + return; + } + } + } + float speedMult = 0.0f; + if (buoyancyImpulse.z / m_fMass > GRAVITY * 0.75f * CTimer::GetTimeStep() + || mod_Buoyancy.m_waterlevel > GetPosition().z) { + speedMult = pow(0.9f, CTimer::GetTimeStep()); + m_vecMoveSpeed.x *= speedMult; + m_vecMoveSpeed.y *= speedMult; + m_vecMoveSpeed.z *= speedMult; + bIsStanding = false; + InflictDamage(nil, WEAPONTYPE_DROWNING, 3.0f * CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + } + if (buoyancyImpulse.z / m_fMass > GRAVITY * 0.25f * CTimer::GetTimeStep()) { + if (speedMult == 0.0f) { + speedMult = pow(0.9f, CTimer::GetTimeStep()); + } + m_vecMoveSpeed.x *= speedMult; + m_vecMoveSpeed.y *= speedMult; + if (m_vecMoveSpeed.z >= -0.1f) { + if (m_vecMoveSpeed.z < -0.04f) + m_vecMoveSpeed.z = -0.02f; + } else { + m_vecMoveSpeed.z = -0.01f; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SPLASH, 0.0f); +#ifdef PC_PARTICLE + CVector aBitForward = 2.2f * m_vecMoveSpeed + GetPosition(); + float level = 0.0f; + if (CWaterLevel::GetWaterLevel(aBitForward, &level, false)) + aBitForward.z = level; + + CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, aBitForward, CVector(0.0f, 0.0f, 0.1f), 0.0f, 200, color, true); + nGenerateRaindrops = CTimer::GetTimeInMilliseconds() + 80; + nGenerateWaterCircles = CTimer::GetTimeInMilliseconds() + 100; +#else + CVector aBitForward = 1.6f * m_vecMoveSpeed + GetPosition(); + float level = 0.0f; + if (CWaterLevel::GetWaterLevel(aBitForward, &level, false)) + aBitForward.z = level + 0.5f; + + CVector vel = m_vecMoveSpeed * 0.1f; + vel.z = 0.18f; + CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, aBitForward, vel, 0.0f, 350, CRGBA(0, 0, 0, 0), true); + nGenerateRaindrops = CTimer::GetTimeInMilliseconds() + 300; + nGenerateWaterCircles = CTimer::GetTimeInMilliseconds() + 60; +#endif + } + } + } else + return; + } else + bTouchingWater = false; + + if (nGenerateWaterCircles && CTimer::GetTimeInMilliseconds() >= nGenerateWaterCircles) { + CVector pos = GetPosition(); + float level = 0.0f; + if (CWaterLevel::GetWaterLevel(pos, &level, false)) + pos.z = level; + + if (pos.z != 0.0f) { + nGenerateWaterCircles = 0; + for(int i = 0; i < 4; i++) { +#ifdef PC_PARTICLE + pos.x += CGeneral::GetRandomNumberInRange(-0.75f, 0.75f); + pos.y += CGeneral::GetRandomNumberInRange(-0.75f, 0.75f); + CParticle::AddParticle(PARTICLE_RAIN_SPLASH_BIGGROW, pos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, color, 0, 0, 0, 0); +#else + pos.x += CGeneral::GetRandomNumberInRange(-2.5f, 2.5f); + pos.y += CGeneral::GetRandomNumberInRange(-2.5f, 2.5f); + CParticle::AddParticle(PARTICLE_RAIN_SPLASH_BIGGROW, pos+CVector(0.0f, 0.0f, 1.0f), CVector(0.0f, 0.0f, 0.0f)); +#endif + } + } + } + + if (nGenerateRaindrops && CTimer::GetTimeInMilliseconds() >= nGenerateRaindrops) { + CVector pos = GetPosition(); + float level = 0.0f; + if (CWaterLevel::GetWaterLevel(pos, &level, false)) + pos.z = level; + + if (pos.z >= 0.0f) { +#ifdef PC_PARTICLE + pos.z += 0.25f; +#else + pos.z += 0.5f; +#endif + nGenerateRaindrops = 0; +#ifdef PC_PARTICLE + CParticleObject::AddObject(POBJECT_SPLASHES_AROUND, pos, CVector(0.0f, 0.0f, 0.0f), 4.5f, 1500, CRGBA(0,0,0,0), true); +#else + CParticleObject::AddObject(POBJECT_SPLASHES_AROUND, pos, CVector(0.0f, 0.0f, 0.0f), 4.5f, 2500, CRGBA(0,0,0,0), true); +#endif + } + } +} + +void +CPed::ProcessControl(void) +{ + CColPoint foundCol; + CEntity *foundEnt = nil; + + if (m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory) + return; + + int alpha = CVisibilityPlugins::GetClumpAlpha(GetClump()); + if (!bFadeOut) { + if (alpha < 255) { + alpha += 16; + if (alpha > 255) + alpha = 255; + } + } else { + alpha -= 8; + if (alpha < 0) + alpha = 0; + } + + CVisibilityPlugins::SetClumpAlpha(GetClump(), alpha); + bIsShooting = false; + BuildPedLists(); + bIsInWater = false; + ProcessBuoyancy(); + + if (m_nPedState != PED_ARRESTED) { + if (m_nPedState == PED_DEAD) { + DeadPedMakesTyresBloody(); +#ifndef VC_PED_PORTS + if (CGame::nastyGame) { +#else + if (CGame::nastyGame && !bIsInWater) { +#endif + uint32 remainingBloodyFpTime = CTimer::GetTimeInMilliseconds() - m_bloodyFootprintCountOrDeathTime; + float timeDependentDist; + if (remainingBloodyFpTime >= 2000) { + if (remainingBloodyFpTime <= 7000) + timeDependentDist = (remainingBloodyFpTime - 2000) / 5000.0f * 0.75f; + else + timeDependentDist = 0.75f; + } else { + timeDependentDist = 0.0f; + } + + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (!nearPed->DyingOrDead()) { + CVector dist = nearPed->GetPosition() - GetPosition(); + if (dist.MagnitudeSqr() < sq(timeDependentDist)) { + nearPed->m_bloodyFootprintCountOrDeathTime = 200; + nearPed->bDoBloodyFootprints = true; + if (nearPed->IsPlayer()) { + if (!nearPed->bIsLooking && nearPed->m_nPedState != PED_ATTACK) { + int16 camMode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if (camMode != CCam::MODE_SNIPER + && camMode != CCam::MODE_ROCKETLAUNCHER + && camMode != CCam::MODE_M16_1STPERSON + && camMode != CCam::MODE_1STPERSON + && camMode != CCam::MODE_HELICANNON_1STPERSON + && !TheCamera.Cams[TheCamera.ActiveCam].GetWeaponFirstPersonOn()) { + + nearPed->SetLookFlag(this, true); + nearPed->SetLookTimer(500); + } + } + } + } + } + } + + if (remainingBloodyFpTime > 2000) { + CVector bloodPos = GetPosition(); + if (remainingBloodyFpTime - 2000 >= 5000) { + if (!m_deadBleeding) { + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &bloodPos, + 0.75f, 0.0f, 0.0f, -0.75f, 255, 255, 0, 0, 4.0f, 40000, 1.0f); + m_deadBleeding = true; + } + } else { + CShadows::StoreStaticShadow( + (uintptr)this + 17, SHADOWTYPE_DARK, gpBloodPoolTex, &bloodPos, + (remainingBloodyFpTime - 2000) / 5000.0f * 0.75f, 0.0f, + 0.0f, (remainingBloodyFpTime - 2000) / 5000.0f * -0.75f, + 255, 255, 0, 0, 4.0f, 1.0f, 40.0f, false, 0.0f); + } + } + } + if (ServiceTalkingWhenDead()) + ServiceTalking(); + +#ifdef VC_PED_PORTS + if (bIsInWater) { + bIsStanding = false; + bWasStanding = false; + CPhysical::ProcessControl(); + } +#endif + return; + } + + bWasStanding = false; + if (bIsStanding) { + if (!CWorld::bForceProcessControl) { + if (m_pCurrentPhysSurface && m_pCurrentPhysSurface->bIsInSafePosition) { + bWasPostponed = true; + return; + } + } + } + + if (!IsPedInControl() || m_nWaitState != WAITSTATE_FALSE || 0.01f * CTimer::GetTimeStep() <= m_fDistanceTravelled + || (m_nStoredMoveState != PEDMOVE_WALK && m_nStoredMoveState != PEDMOVE_RUN && m_nStoredMoveState != PEDMOVE_SPRINT)) + m_panicCounter = 0; + else if (m_panicCounter < 50) + ++m_panicCounter; + + if (m_fHealth <= 1.0f && m_nPedState <= PED_STATES_NO_AI && !bIsInTheAir && !bIsLanding) + SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + + bCollidedWithMyVehicle = false; + + CEntity *collidingEnt = m_pDamageEntity; +#ifndef VC_PED_PORTS + if (!bUsesCollision || m_fDamageImpulse <= 0.0f || m_nPedState == PED_DIE || !collidingEnt) { +#else + if (!bUsesCollision || ((!collidingEnt || m_fDamageImpulse <= 0.0f) && (!IsPlayer() || !bIsStuck)) || m_nPedState == PED_DIE) { +#endif + bHitSomethingLastFrame = false; + if (m_nPedStateTimer <= 500 && bIsInTheAir) { + if (m_nPedStateTimer) + m_nPedStateTimer--; + } else if (m_nPedStateTimer < 1001) { + m_nPedStateTimer = 0; + } + } else { + if (m_panicCounter == 50 && IsPedInControl()) { + SetWaitState(WAITSTATE_STUCK, nil); + // Leftover + /* + if (m_nPedType < PEDTYPE_COP) { + + } else { + + } + */ +#ifndef VC_PED_PORTS + } else { +#else + } else if (collidingEnt) { +#endif + switch (collidingEnt->GetType()) + { + case ENTITY_TYPE_BUILDING: + case ENTITY_TYPE_OBJECT: + { + CBaseModelInfo *collidingModel = CModelInfo::GetModelInfo(collidingEnt->GetModelIndex()); + CColModel *collidingCol = collidingModel->GetColModel(); + if (collidingEnt->IsObject() && ((CObject*)collidingEnt)->m_nSpecialCollisionResponseCases != COLLRESPONSE_FENCEPART + || collidingCol->boundingBox.max.x < 3.0f + && collidingCol->boundingBox.max.y < 3.0f) { + + if (!IsPlayer()) { + SetDirectionToWalkAroundObject(collidingEnt); + break; + } + } + if (IsPlayer()) { + bHitSomethingLastFrame = true; + break; + } + + float angleToFaceWhenHit = CGeneral::GetRadianAngleBetweenPoints( + GetPosition().x, + GetPosition().y, + m_vecDamageNormal.x + GetPosition().x, + m_vecDamageNormal.y + GetPosition().y); + + float neededTurn = Abs(m_fRotationCur - angleToFaceWhenHit); + + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + float oldDestRot = CGeneral::LimitRadianAngle(m_fRotationDest); + + if (m_pedInObjective && + (m_objective == OBJECTIVE_GOTO_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT)) { + + if (m_pedInObjective->IsPlayer() + && (neededTurn < DEGTORAD(20.0f) || m_panicCounter > 10)) { + if (CanPedJumpThis(collidingEnt)) { + SetJump(); + } else if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT) { + SetWaitState(WAITSTATE_LOOK_ABOUT, nil); + } else { + SetWaitState(WAITSTATE_PLAYANIM_TAXI, nil); + m_headingRate = 0.0f; + SetLookFlag(m_pedInObjective, true); + SetLookTimer(3000); + Say(SOUND_PED_TAXI_CALL); + } + } else { + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + } + } else { + if (m_nPedType != PEDTYPE_COP && neededTurn < DEGTORAD(15.0f) && m_nWaitState == WAITSTATE_FALSE) { + if ((m_nStoredMoveState == PEDMOVE_RUN || m_nStoredMoveState == PEDMOVE_SPRINT) && m_vecDamageNormal.z < 0.3f) { + CAnimBlendAssociation *runAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUN); + if (!runAssoc) + runAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNFAST); + + if (runAssoc && runAssoc->blendAmount > 0.9f && runAssoc->IsRunning()) { + SetWaitState(WAITSTATE_HITWALL, nil); + } + } + } + if (m_nPedState == PED_FLEE_POS) { + CVector2D fleePos = collidingEnt->GetPosition(); + uint32 oldFleeTimer = m_fleeTimer; + SetFlee(fleePos, 5000); + if (oldFleeTimer != m_fleeTimer) + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 500; + + } else { + if (m_nPedState == PED_FLEE_ENTITY && (neededTurn < DEGTORAD(25.0f) || m_panicCounter > 10)) { + m_collidingThingTimer = CTimer::GetTimeInMilliseconds() + 2500; + m_collidingEntityWhileFleeing = collidingEnt; + m_collidingEntityWhileFleeing->RegisterReference((CEntity **) &m_collidingEntityWhileFleeing); + + uint8 currentDir = Floor((PI + m_fRotationCur) / DEGTORAD(45.0f)); + uint8 nextDir; + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, currentDir, &nextDir); + + } else { + if (neededTurn < DEGTORAD(60.0f)) { + CVector posToHead = m_vecDamageNormal * 4.0f; + posToHead.z = 0.0f; + posToHead += GetPosition(); + int closestNodeId = ThePaths.FindNodeClosestToCoors(posToHead, PATH_PED, + 999999.9f, false, false); + float angleToFace; + + if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT && m_objective != OBJECTIVE_KILL_CHAR_ANY_MEANS) { + if (m_nPedState != PED_SEEK_POS && m_nPedState != PED_SEEK_CAR) { + if (m_nPedState == PED_WANDER_PATH) { + m_pNextPathNode = &ThePaths.m_pathNodes[closestNodeId]; + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + m_pNextPathNode->GetX(), m_pNextPathNode->GetY(), + GetPosition().x, GetPosition().y); + } else { + if (ThePaths.m_pathNodes[closestNodeId].GetX() == 0.0f + || ThePaths.m_pathNodes[closestNodeId].GetY() == 0.0f) { + posToHead = (3.0f * m_vecDamageNormal) + GetPosition(); + posToHead.x += (CGeneral::GetRandomNumber() % 512) / 250.0f - 1.0f; + posToHead.y += (CGeneral::GetRandomNumber() % 512) / 250.0f - 1.0f; + } else { + posToHead.x = ThePaths.m_pathNodes[closestNodeId].GetX(); + posToHead.y = ThePaths.m_pathNodes[closestNodeId].GetY(); + } + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + posToHead.x, posToHead.y, + GetPosition().x, GetPosition().y); + + if (m_nPedState != PED_FOLLOW_PATH) + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 500; + } + } else { + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + ThePaths.m_pathNodes[closestNodeId].GetX(), + ThePaths.m_pathNodes[closestNodeId].GetY(), + GetPosition().x, + GetPosition().y); + + CVector2D distToNode = ThePaths.m_pathNodes[closestNodeId].GetPosition() - GetPosition(); + CVector2D distToSeekPos = m_vecSeekPos - GetPosition(); + + if (DotProduct2D(distToNode, distToSeekPos) < 0.0f) { + m_fRotationCur = m_fRotationDest; + break; + } + } + } else { + float angleToFaceAwayDamage = CGeneral::GetRadianAngleBetweenPoints( + m_vecDamageNormal.x, + m_vecDamageNormal.y, + 0.0f, + 0.0f); + + if (angleToFaceAwayDamage < m_fRotationCur) + angleToFaceAwayDamage += TWOPI; + + float neededTurn = angleToFaceAwayDamage - m_fRotationCur; + + if (neededTurn <= PI) { + angleToFace = 0.5f * neededTurn + m_fRotationCur; + m_fRotationCur += DEGTORAD(m_pedStats->m_headingChangeRate) * 2.0f; + } else { + angleToFace = m_fRotationCur - (TWOPI - neededTurn) * 0.5f; + m_fRotationCur -= DEGTORAD(m_pedStats->m_headingChangeRate) * 2.0f; + } + + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 200; + if (m_nPedType == PEDTYPE_COP) { + if (m_pedInObjective) { + float angleToLookCriminal = CGeneral::GetRadianAngleBetweenPoints( + m_pedInObjective->GetPosition().x, + m_pedInObjective->GetPosition().y, + GetPosition().x, + GetPosition().y); + + angleToLookCriminal = CGeneral::LimitRadianAngle(angleToLookCriminal); + angleToFace = CGeneral::LimitRadianAngle(angleToFace); + + if (angleToLookCriminal < angleToFace) + angleToLookCriminal += TWOPI; + + float neededTurnToCriminal = angleToLookCriminal - angleToFace; + + if (neededTurnToCriminal > DEGTORAD(150.0f) && neededTurnToCriminal < DEGTORAD(210.0f)) { + ((CCopPed*)this)->m_bStopAndShootDisabledZone = true; + } + } + } + } + m_fRotationDest = CGeneral::LimitRadianAngle(angleToFace); + + if (m_fRotationCur - PI > m_fRotationDest) { + m_fRotationDest += TWOPI; + } else if (PI + m_fRotationCur < m_fRotationDest) { + m_fRotationDest -= TWOPI; + } + + if (oldDestRot == m_fRotationDest && CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 200; + m_fRotationDest += HALFPI; + } + } + } + } + } + + if (m_nPedState != PED_WANDER_PATH && m_nPedState != PED_FLEE_ENTITY) + m_pNextPathNode = nil; + + bHitSomethingLastFrame = true; + break; + } + case ENTITY_TYPE_VEHICLE: + { + CVehicle* collidingVeh = ((CVehicle*)collidingEnt); + float collidingVehSpeedSqr = collidingVeh->m_vecMoveSpeed.MagnitudeSqr(); + + if (collidingVeh == m_pMyVehicle) + bCollidedWithMyVehicle = true; +#ifdef VC_PED_PORTS + float oldHealth = m_fHealth; + bool playerSufferSound = false; + + if (collidingVehSpeedSqr <= 1.0f / 400.0f) { + if (IsPedInControl() + && (!IsPlayer() + || m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT + || m_objective == OBJECTIVE_RUN_TO_AREA + || m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER)) { + + if (collidingVeh != m_pCurrentPhysSurface || IsPlayer()) { + if (!bVehEnterDoorIsBlocked) { + if (collidingVeh->GetStatus() != STATUS_PLAYER || CharCreatedBy == MISSION_CHAR) { + + // VC calls SetDirectionToWalkAroundVehicle instead if ped is in PED_SEEK_CAR. + SetDirectionToWalkAroundObject(collidingVeh); + CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer = m_nPedStateTimer; + } else { + if (CTimer::GetTimeInMilliseconds() >= CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer + || m_nPedStateTimer >= CTimer::GetTimeInMilliseconds()) { + + // VC calls SetDirectionToWalkAroundVehicle instead if ped is in PED_SEEK_CAR. + SetDirectionToWalkAroundObject(collidingVeh); + CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer = m_nPedStateTimer; + + } else if (m_fleeFrom != collidingVeh) { + SetFlee(collidingVeh, 4000); + bUsePedNodeSeek = false; + SetMoveState(PEDMOVE_WALK); + } + } + } + } else { + float angleLeftToCompleteTurn = Abs(m_fRotationCur - m_fRotationDest); + if (angleLeftToCompleteTurn < 0.01f && CanPedJumpThis(collidingVeh)) { + SetJump(); + } + } + } else if (IsPlayer() && !bIsInTheAir) { + + if (IsPedInControl() && ((CPlayerPed*)this)->m_fMoveSpeed == 0.0f + && !bIsLooking && CTimer::GetTimeInMilliseconds() > m_lookTimer && collidingVeh->pDriver) { + + ((CPlayerPed*)this)->AnnoyPlayerPed(false); + SetLookFlag(collidingVeh, true); + SetLookTimer(1300); + + eWeaponType weaponType = GetWeapon()->m_eWeaponType; + if (weaponType == WEAPONTYPE_UNARMED + || weaponType == WEAPONTYPE_BASEBALLBAT + || weaponType == WEAPONTYPE_COLT45 + || weaponType == WEAPONTYPE_UZI) { + bShakeFist = true; + } + } else { + SetLookFlag(collidingVeh, true); + SetLookTimer(500); + } + } + } else { + float adjustedImpulse = m_fDamageImpulse; + if (IsPlayer()) { + if (bIsStanding) { + float forwardVecAndDamageDirDotProd = DotProduct(m_vecAnimMoveDelta.y * GetForward(), m_vecDamageNormal); + if (forwardVecAndDamageDirDotProd < 0.0f) { + adjustedImpulse = forwardVecAndDamageDirDotProd * m_fMass + m_fDamageImpulse; + if (adjustedImpulse < 0.0f) + adjustedImpulse = 0.0f; + } + } + } + if (m_fMass / 20.0f < adjustedImpulse) + DMAudio.PlayOneShot(collidingVeh->m_audioEntityId, SOUND_CAR_PED_COLLISION, adjustedImpulse); + + if (IsPlayer()) { + if (adjustedImpulse > 20.0f) + adjustedImpulse = 20.0f; + + if (adjustedImpulse > 5.0f) { + if (adjustedImpulse <= 13.0f) + playerSufferSound = true; + else + Say(SOUND_PED_DAMAGE); + } + + CColModel* collidingCol = CModelInfo::GetModelInfo(collidingVeh->m_modelIndex)->GetColModel(); + CVector colMinVec = collidingCol->boundingBox.min; + CVector colMaxVec = collidingCol->boundingBox.max; + + CVector vehColCenterDist = collidingVeh->GetMatrix() * ((colMinVec + colMaxVec) * 0.5f) - GetPosition(); + + // TLVC = To look vehicle center + + float angleToVehFront = collidingVeh->GetForward().Heading(); + float angleDiffFromLookingFrontTLVC = angleToVehFront - vehColCenterDist.Heading(); + angleDiffFromLookingFrontTLVC = CGeneral::LimitRadianAngle(angleDiffFromLookingFrontTLVC); + + // I don't know why do we use that + float vehTopRightHeading = Atan2(colMaxVec.x - colMinVec.x, colMaxVec.y - colMinVec.y); + + CVector vehDist = GetPosition() - collidingVeh->GetPosition(); + vehDist.Normalise(); + + float vehRightVecAndSpeedDotProd; + + if (Abs(angleDiffFromLookingFrontTLVC) >= vehTopRightHeading && Abs(angleDiffFromLookingFrontTLVC) < PI - vehTopRightHeading) { + if (angleDiffFromLookingFrontTLVC <= 0.0f) { + vehRightVecAndSpeedDotProd = DotProduct(collidingVeh->GetRight(), collidingVeh->m_vecMoveSpeed); + + // vehRightVecAndSpeedDotProd < 0.1f = Vehicle being overturned or spinning to it's right? + if (collidingVehSpeedSqr > 1.0f / 100.0f && vehRightVecAndSpeedDotProd < 0.1f) { + + // Car's right faces towards us and isn't coming directly to us + if (DotProduct(collidingVeh->GetRight(), GetForward()) < 0.0f + && DotProduct(vehDist, collidingVeh->m_vecMoveSpeed) > 0.0f) { + SetEvasiveStep(collidingVeh, 1); + } + } + } else { + vehRightVecAndSpeedDotProd = DotProduct(-1.0f * collidingVeh->GetRight(), collidingVeh->m_vecMoveSpeed); + + if (collidingVehSpeedSqr > 1.0f / 100.0f && vehRightVecAndSpeedDotProd < 0.1f) { + if (DotProduct(collidingVeh->GetRight(), GetForward()) > 0.0f + && DotProduct(vehDist, collidingVeh->m_vecMoveSpeed) > 0.0f) { + SetEvasiveStep(collidingVeh, 1); + } + } + } + } else { + vehRightVecAndSpeedDotProd = DotProduct(vehDist, collidingVeh->m_vecMoveSpeed); + } + + if (vehRightVecAndSpeedDotProd <= 0.1f) { + if (m_nPedState != PED_FIGHT) { + SetLookFlag(collidingVeh, true); + SetLookTimer(700); + } + } else { + bIsStanding = false; + CVector2D collidingEntMoveDir = -collidingVeh->m_vecMoveSpeed; + int dir = GetLocalDirection(collidingEntMoveDir); + SetFall(1000, (AnimationId)(dir + ANIM_STD_HIGHIMPACT_FRONT), false); + + float damage; + if (collidingVeh->m_modelIndex == MI_TRAIN) { + damage = 50.0f; + } else { + damage = 20.0f; + } + + InflictDamage(collidingVeh, WEAPONTYPE_RAMMEDBYCAR, damage, PEDPIECE_TORSO, dir); + Say(SOUND_PED_DAMAGE); + } + } else { + KillPedWithCar(collidingVeh, m_fDamageImpulse); + } + + /* VC specific + if (m_pCollidingEntity != collidingEnt) + bPushedAlongByCar = true; + */ + } + if (m_fHealth < oldHealth && playerSufferSound) + Say(SOUND_PED_HIT); +#else + if (collidingVehSpeedSqr <= 1.0f / 400.0f) { + if (!IsPedInControl() + || IsPlayer() + && m_objective != OBJECTIVE_GOTO_AREA_ON_FOOT + && m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER + && m_objective != OBJECTIVE_RUN_TO_AREA) { + + if (IsPlayer() && !bIsInTheAir) { + + if (IsPedInControl() + && ((CPlayerPed*)this)->m_fMoveSpeed == 0.0f + && !bIsLooking + && CTimer::GetTimeInMilliseconds() > m_lookTimer + && collidingVeh->pDriver) { + + ((CPlayerPed*)this)->AnnoyPlayerPed(false); + SetLookFlag(collidingVeh, true); + SetLookTimer(1300); + + eWeaponType weaponType = GetWeapon()->m_eWeaponType; + if (weaponType == WEAPONTYPE_UNARMED + || weaponType == WEAPONTYPE_BASEBALLBAT + || weaponType == WEAPONTYPE_COLT45 + || weaponType == WEAPONTYPE_UZI) { + bShakeFist = true; + } + } else { + SetLookFlag(collidingVeh, true); + SetLookTimer(500); + } + } + + } else if (!bVehEnterDoorIsBlocked) { + if (collidingVeh->GetStatus() != STATUS_PLAYER || CharCreatedBy == MISSION_CHAR) { + + SetDirectionToWalkAroundObject(collidingVeh); + + } else if (CTimer::GetTimeInMilliseconds() >= CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer + || m_nPedStateTimer >= CTimer::GetTimeInMilliseconds()) { + + SetDirectionToWalkAroundObject(collidingVeh); + CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer = m_nPedStateTimer; + + } else if (m_fleeFrom != collidingVeh) { + SetFlee(collidingVeh, 4000); + bUsePedNodeSeek = false; + SetMoveState(PEDMOVE_WALK); + } + } + } else { + DMAudio.PlayOneShot(collidingVeh->m_audioEntityId, SOUND_CAR_PED_COLLISION, m_fDamageImpulse); + if (IsPlayer()) { + CColModel *collidingCol = CModelInfo::GetColModel(collidingVeh->GetModelIndex()); + CVector colMinVec = collidingCol->boundingBox.min; + CVector colMaxVec = collidingCol->boundingBox.max; + + CVector vehColCenterDist = collidingVeh->GetMatrix() * ((colMinVec + colMaxVec) * 0.5f) - GetPosition(); + + // TLVC = To look vehicle center + + float angleToVehFront = collidingVeh->GetForward().Heading(); + float angleDiffFromLookingFrontTLVC = angleToVehFront - vehColCenterDist.Heading(); + angleDiffFromLookingFrontTLVC = CGeneral::LimitRadianAngle(angleDiffFromLookingFrontTLVC); + + // I don't know why do we use that + float vehTopRightHeading = Atan2(colMaxVec.x - colMinVec.x, colMaxVec.y - colMinVec.y); + + CVector vehDist = GetPosition() - collidingVeh->GetPosition(); + vehDist.Normalise(); + + float vehRightVecAndSpeedDotProd; + + if (Abs(angleDiffFromLookingFrontTLVC) >= vehTopRightHeading && Abs(angleDiffFromLookingFrontTLVC) < PI - vehTopRightHeading) { + if (angleDiffFromLookingFrontTLVC <= 0.0f) { + vehRightVecAndSpeedDotProd = DotProduct(collidingVeh->GetRight(), collidingVeh->m_vecMoveSpeed); + + // vehRightVecAndSpeedDotProd < 0.1f = Vehicle being overturned or spinning to it's right? + if (collidingVehSpeedSqr > 1.0f / 100.0f && vehRightVecAndSpeedDotProd < 0.1f) { + + // Car's right faces towards us and isn't coming directly to us + if (DotProduct(collidingVeh->GetRight(), GetForward()) < 0.0f + && DotProduct(vehDist, collidingVeh->m_vecMoveSpeed) > 0.0f) { + SetEvasiveStep(collidingVeh, 1); + } + } + } else { + vehRightVecAndSpeedDotProd = DotProduct(-1.0f * collidingVeh->GetRight(), collidingVeh->m_vecMoveSpeed); + + if (collidingVehSpeedSqr > 1.0f / 100.0f && vehRightVecAndSpeedDotProd < 0.1f) { + if (DotProduct(collidingVeh->GetRight(), GetForward()) > 0.0f + && DotProduct(vehDist, collidingVeh->m_vecMoveSpeed) > 0.0f) { + SetEvasiveStep(collidingVeh, 1); + } + } + } + } else { + vehRightVecAndSpeedDotProd = DotProduct(vehDist, collidingVeh->m_vecMoveSpeed); + } + + if (vehRightVecAndSpeedDotProd <= 0.1f) { + if (m_nPedState != PED_FIGHT) { + SetLookFlag(collidingVeh, true); + SetLookTimer(700); + } + } else { + bIsStanding = false; + CVector2D collidingEntMoveDir = -collidingVeh->m_vecMoveSpeed; + int dir = GetLocalDirection(collidingEntMoveDir); + SetFall(1000, (AnimationId)(dir + ANIM_STD_HIGHIMPACT_FRONT), false); + CPed *driver = collidingVeh->pDriver; + + float damage; + if (driver && driver->IsPlayer()) { + damage = vehRightVecAndSpeedDotProd * 1000.0f; + } else if (collidingVeh->GetModelIndex() == MI_TRAIN) { + damage = 50.0f; + } else { + damage = 20.0f; + } + + InflictDamage(collidingVeh, WEAPONTYPE_RAMMEDBYCAR, damage, PEDPIECE_TORSO, dir); + Say(SOUND_PED_DAMAGE); + } + } else { + KillPedWithCar(collidingVeh, m_fDamageImpulse); + } + } +#endif + break; + } + case ENTITY_TYPE_PED: + { + CollideWithPed((CPed*)collidingEnt); + if (((CPed*)collidingEnt)->IsPlayer()) { + CPlayerPed *player = ((CPlayerPed*)collidingEnt); + Say(SOUND_PED_CHAT); + if (m_nMoveState > PEDMOVE_STILL && player->IsPedInControl()) { + if (player->m_fMoveSpeed < 1.0f) { + if (!player->bIsLooking) { + if (CTimer::GetTimeInMilliseconds() > player->m_lookTimer) { + player->AnnoyPlayerPed(false); + player->SetLookFlag(this, true); + player->SetLookTimer(1300); + eWeaponType weapon = player->GetWeapon()->m_eWeaponType; + if (weapon == WEAPONTYPE_UNARMED + || weapon == WEAPONTYPE_BASEBALLBAT + || weapon == WEAPONTYPE_COLT45 + || weapon == WEAPONTYPE_UZI) { + player->bShakeFist = true; + } + } + } + } + } + } + break; + } + default: + break; + } + } + CVector forceDir; + if (!bIsInTheAir && m_nPedState != PED_JUMP +#ifdef VC_PED_PORTS + && m_fDamageImpulse > 0.0f +#endif + ) { + + forceDir = m_vecDamageNormal; + forceDir.z = 0.0f; + if (!bIsStanding) { + forceDir *= 4.0f; + } else { + forceDir *= 0.5f; + } + + ApplyMoveForce(forceDir); + } + if ((bIsInTheAir && !DyingOrDead()) +#ifdef VC_PED_PORTS + || (!bIsStanding && !bWasStanding && m_nPedState == PED_FALL) +#endif + ) { + if (m_nPedStateTimer > 0 && m_nPedStateTimer <= 1000) { + forceDir = GetPosition() - m_vecHitLastPos; + } else { + m_nPedStateTimer = 0; + m_vecHitLastPos = GetPosition(); + forceDir = CVector(0.0f, 0.0f, 0.0f); + } + + CVector offsetToCheck; + m_nPedStateTimer++; + + float adjustedTs = Max(CTimer::GetTimeStep(), 0.01f); + + CPad *pad0 = CPad::GetPad(0); + if ((m_nPedStateTimer <= 50.0f / (4.0f * adjustedTs) || m_nPedStateTimer * 0.01f <= forceDir.MagnitudeSqr()) + && (m_nCollisionRecords <= 1 || m_nPedStateTimer <= 50.0f / (2.0f * adjustedTs) || m_nPedStateTimer * 1.0f / 250.0f <= Abs(forceDir.z))) { + + if (m_nCollisionRecords == 1 && m_aCollisionRecords[0] != nil && m_aCollisionRecords[0]->IsBuilding() + && m_nPedStateTimer > 50.0f / (2.0f * adjustedTs) && m_nPedStateTimer * 1.0f / 250.0f > Abs(forceDir.z)) { + offsetToCheck.x = -forceDir.y; +#ifdef VC_PED_PORTS + offsetToCheck.z = 1.0f; +#else + offsetToCheck.z = 0.0f; +#endif + offsetToCheck.y = forceDir.x; + offsetToCheck.Normalise(); + + CVector posToCheck = GetPosition() + offsetToCheck; + + // These are either obstacle or ground to land, I don't know which one. + float obstacleForFlyingZ, obstacleForFlyingOtherDirZ; + CColPoint obstacleForFlying, obstacleForFlyingOtherDir; + + // Check is there any room for being knocked up in reverse direction of force + if (CWorld::ProcessVerticalLine(posToCheck, -20.0f, obstacleForFlying, foundEnt, true, false, false, false, false, false, nil)) { + obstacleForFlyingZ = obstacleForFlying.point.z; + } else { + obstacleForFlyingZ = 500.0f; + } + + posToCheck = GetPosition() - offsetToCheck; + + // Now check for direction of force this time + if (CWorld::ProcessVerticalLine(posToCheck, -20.0f, obstacleForFlyingOtherDir, foundEnt, true, false, false, false, false, false, nil)) { + obstacleForFlyingOtherDirZ = obstacleForFlyingOtherDir.point.z; + } else { + obstacleForFlyingOtherDirZ = 501.0f; + } +#ifdef VC_PED_PORTS + int16 flyDir = 0; + float feetZ = GetPosition().z - FEET_OFFSET; +#ifdef FIX_BUGS + if (obstacleForFlyingZ > feetZ && obstacleForFlyingZ < 500.0f) + flyDir = 1; + else if (obstacleForFlyingOtherDirZ > feetZ && obstacleForFlyingOtherDirZ < 501.0f) + flyDir = 2; +#else + if ((obstacleForFlyingZ > feetZ && obstacleForFlyingOtherDirZ < 500.0f) || (obstacleForFlyingZ > feetZ && obstacleForFlyingOtherDirZ > feetZ)) + flyDir = 1; + else if (obstacleForFlyingOtherDirZ > feetZ && obstacleForFlyingZ < 499.0f) + flyDir = 2; +#endif + + if (flyDir > 0 && !bSomeVCflag1) { + GetMatrix().SetTranslateOnly((flyDir == 2 ? obstacleForFlyingOtherDir.point : obstacleForFlying.point)); + GetMatrix().GetPosition().z += FEET_OFFSET; + GetMatrix().UpdateRW(); + SetLanding(); + bIsStanding = true; + } +#endif + if (obstacleForFlyingZ < obstacleForFlyingOtherDirZ) { + offsetToCheck *= -1.0f; + } + offsetToCheck.z = 1.0f; + forceDir = 4.0f * offsetToCheck; + forceDir.z = 4.0f; + ApplyMoveForce(forceDir); + + // What was that for?? It pushes player inside of collision sometimes and kills him. +#ifdef FIX_BUGS + if (!IsPlayer()) +#endif + GetMatrix().GetPosition() += 0.25f * offsetToCheck; + + m_fRotationCur = CGeneral::GetRadianAngleBetweenPoints(offsetToCheck.x, offsetToCheck.y, 0.0f, 0.0f); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + m_fRotationDest = m_fRotationCur; + SetHeading(m_fRotationCur); + + if (m_nPedState != PED_FALL && !bIsPedDieAnimPlaying) { + SetFall(1000, ANIM_STD_HIGHIMPACT_BACK, true); + } + bIsInTheAir = false; + } else if (m_vecDamageNormal.z > 0.4f) { +#ifndef VC_PED_PORTS + forceDir = m_vecDamageNormal; + forceDir.z = 0.0f; + forceDir.Normalise(); + ApplyMoveForce(2.0f * forceDir); +#else + if (m_nPedState == PED_JUMP) { + if (m_nWaitTimer <= 2000) { + if (m_nWaitTimer < 1000) + m_nWaitTimer += CTimer::GetTimeStepInMilliseconds(); + } else { + m_nWaitTimer = 0; + } + } + forceDir = m_vecDamageNormal; + forceDir.z = 0.0f; + forceDir.Normalise(); + if (m_nPedState != PED_JUMP || m_nWaitTimer >= 300) { + ApplyMoveForce(2.0f * forceDir); + } else { + ApplyMoveForce(-4.0f * forceDir); + } +#endif + } + } else if ((CTimer::GetFrameCounter() + m_randomSeed % 256 + 3) & 7) { + if (IsPlayer() && m_nPedState != PED_JUMP && pad0->JumpJustDown()) { + int16 padWalkX = pad0->GetPedWalkLeftRight(); + int16 padWalkY = pad0->GetPedWalkUpDown(); + if (Abs(padWalkX) > 0.0f || Abs(padWalkY) > 0.0f) { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(0.0f, 0.0f, -padWalkX, padWalkY); + m_fRotationDest -= TheCamera.Orientation; + m_fRotationDest = CGeneral::LimitRadianAngle(m_fRotationDest); + m_fRotationCur = m_fRotationDest; + SetHeading(m_fRotationCur); + } + SetJump(); + m_nPedStateTimer = 0; + m_vecHitLastPos = GetPosition(); + + // Why? forceDir is unused after this point. + forceDir = CVector(0.0f, 0.0f, 0.0f); + } else if (IsPlayer()) { + int16 padWalkX = pad0->GetPedWalkLeftRight(); + int16 padWalkY = pad0->GetPedWalkUpDown(); + if (Abs(padWalkX) > 0.0f || Abs(padWalkY) > 0.0f) { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(0.0f, 0.0f, -padWalkX, padWalkY); + m_fRotationDest -= TheCamera.Orientation; + m_fRotationDest = CGeneral::LimitRadianAngle(m_fRotationDest); + m_fRotationCur = m_fRotationDest; + SetHeading(m_fRotationCur); + } + CAnimBlendAssociation *jumpAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_JUMP_GLIDE); + + if (!jumpAssoc) + jumpAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL_GLIDE); + + if (jumpAssoc) { + jumpAssoc->blendDelta = -3.0f; + jumpAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + if (m_nPedState == PED_JUMP) + m_nPedState = PED_IDLE; + } else { + CAnimBlendAssociation *jumpAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_JUMP_GLIDE); + + if (!jumpAssoc) + jumpAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL_GLIDE); + + if (jumpAssoc) { + jumpAssoc->blendDelta = -3.0f; + jumpAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + } else { + offsetToCheck = GetPosition(); + offsetToCheck.z += 0.5f; + + if (CWorld::ProcessVerticalLine(offsetToCheck, GetPosition().z - FEET_OFFSET, foundCol, foundEnt, true, true, false, true, false, false, nil)) { +#ifdef VC_PED_PORTS + if (!bSomeVCflag1 || FEET_OFFSET + foundCol.point.z < GetPosition().z) { + GetMatrix().GetPosition().z = FEET_OFFSET + foundCol.point.z; + GetMatrix().UpdateRW(); + if (bSomeVCflag1) + bSomeVCflag1 = false; + } +#else + GetMatrix().GetPosition().z = FEET_OFFSET + foundCol.point.z; + GetMatrix().UpdateRW(); +#endif + SetLanding(); + bIsStanding = true; + } + } + } else if (m_nPedStateTimer < 1001) { + m_nPedStateTimer = 0; + } + } + + if (bIsDucking) + Duck(); + + if (bStartWanderPathOnFoot) { + if (IsPedInControl()) { + ClearAll(); + SetWanderPath(m_nPathDir); + bStartWanderPathOnFoot = false; + } else if (m_nPedState == PED_DRIVING) { + bWanderPathAfterExitingCar = true; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } + + if (!bIsStanding && m_vecMoveSpeed.z > 0.25f) { + float airResistance = Pow(0.95f, CTimer::GetTimeStep()); + + m_vecMoveSpeed *= airResistance; + } +#ifdef VC_PED_PORTS + if (IsPlayer() || !bIsStanding || m_vecMoveSpeed.x != 0.0f || m_vecMoveSpeed.y != 0.0f || m_vecMoveSpeed.z != 0.0f + || (m_nMoveState != PEDMOVE_NONE && m_nMoveState != PEDMOVE_STILL) + || m_vecAnimMoveDelta.x != 0.0f || m_vecAnimMoveDelta.y != 0.0f + || m_nPedState == PED_JUMP + || bIsInTheAir + || m_pCurrentPhysSurface) { + + CPhysical::ProcessControl(); + } else { + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + m_nCollisionRecords = 0; + bHasCollided = false; + m_nDamagePieceType = 0; + m_fDamageImpulse = 0.0f; + m_pDamageEntity = nil; + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + } +#else + CPhysical::ProcessControl(); +#endif + if (m_nPedState != PED_DIE || bIsPedDieAnimPlaying) { + if (m_nPedState != PED_DEAD) { + CalculateNewVelocity(); + CalculateNewOrientation(); + } + UpdatePosition(); + PlayFootSteps(); + if (IsPedInControl() && !bIsStanding && !m_pDamageEntity && CheckIfInTheAir()) { + SetInTheAir(); +#ifdef VC_PED_PORTS + bSomeVCflag1 = false; +#endif + } +#ifdef VC_PED_PORTS + if (bSomeVCflag1) { + CVector posToCheck = GetPosition(); + posToCheck.z += 0.9f; + if (!CWorld::TestSphereAgainstWorld(posToCheck, 0.2f, this, true, true, false, true, false, false)) + bSomeVCflag1 = false; + } +#endif + ProcessObjective(); + if (!bIsAimingGun) { + if (bIsRestoringGun) + RestoreGunPosition(); + } else { + AimGun(); + } + + if (bIsLooking) { + MoveHeadToLook(); + } else if (bIsRestoringLook) { + RestoreHeadPosition(); + } + + if (bIsInTheAir) + InTheAir(); + + if (bUpdateAnimHeading) { + if (m_nPedState != PED_GETUP && m_nPedState != PED_FALL) { + m_fRotationCur -= HALFPI; + m_fRotationDest = m_fRotationCur; + bUpdateAnimHeading = false; + } + } + + if (m_nWaitState != WAITSTATE_FALSE) + Wait(); + + if (m_nPedState != PED_IDLE) { + CAnimBlendAssociation *idleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_BIGGUN); + if(idleAssoc) { + idleAssoc->blendDelta = -8.0f; + idleAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + +#ifdef CANCELLABLE_CAR_ENTER + static bool cancelJack = false; + if (IsPlayer()) { + if (EnteringCar() && m_pVehicleAnim) { + CPad *pad = CPad::GetPad(0); + + if (!pad->ArePlayerControlsDisabled()) { + int vehAnim = m_pVehicleAnim->animId; + + int16 padWalkX = pad->GetPedWalkLeftRight(); + int16 padWalkY = pad->GetPedWalkUpDown(); + if (Abs(padWalkX) > 0.0f || Abs(padWalkY) > 0.0f) { + if (vehAnim == ANIM_STD_CAR_OPEN_DOOR_LHS || vehAnim == ANIM_STD_CAR_OPEN_DOOR_RHS || vehAnim == ANIM_STD_COACH_OPEN_LHS || vehAnim == ANIM_STD_COACH_OPEN_RHS || + vehAnim == ANIM_STD_VAN_OPEN_DOOR_REAR_LHS || vehAnim == ANIM_STD_VAN_OPEN_DOOR_REAR_RHS) { + + if (!m_pMyVehicle->pDriver) { + cancelJack = false; + bCancelEnteringCar = true; + } else + cancelJack = true; + } else if (vehAnim == ANIM_STD_QUICKJACK && m_pVehicleAnim->GetTimeLeft() > 0.75f) { + cancelJack = true; + } else if (vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LO_LHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LO_RHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_RHS) { + bCancelEnteringCar = true; + cancelJack = false; + } + } + if (cancelJack && vehAnim == ANIM_STD_QUICKJACK && m_pVehicleAnim->GetTimeLeft() > 0.75f && m_pVehicleAnim->GetTimeLeft() < 0.78f) { + cancelJack = false; + QuitEnteringCar(); + RestorePreviousObjective(); + } + if (cancelJack && (vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LO_LHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LO_RHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_RHS)) { + cancelJack = false; + bCancelEnteringCar = true; + } + } + } else + cancelJack = false; + } +#endif + + switch (m_nPedState) { + case PED_IDLE: + Idle(); + break; + case PED_LOOK_ENTITY: + case PED_LOOK_HEADING: + Look(); + break; + case PED_WANDER_RANGE: + WanderRange(); + CheckAroundForPossibleCollisions(); + break; + case PED_WANDER_PATH: + WanderPath(); + break; + case PED_ENTER_CAR: + case PED_CARJACK: + break; + case PED_FLEE_POS: + ms_vec2DFleePosition.x = m_fleeFromPosX; + ms_vec2DFleePosition.y = m_fleeFromPosY; + Flee(); + break; + case PED_FLEE_ENTITY: + if (!m_fleeFrom) { + SetIdle(); + break; + } + + if (CTimer::GetTimeInMilliseconds() <= m_nPedStateTimer) + break; + + ms_vec2DFleePosition = m_fleeFrom->GetPosition(); + Flee(); + break; + case PED_FOLLOW_PATH: + FollowPath(); + break; + case PED_PAUSE: + Pause(); + break; + case PED_ATTACK: + Attack(); + break; + case PED_FIGHT: + Fight(); + break; + case PED_CHAT: + Chat(); + break; + case PED_AIM_GUN: + if (m_pPointGunAt && m_pPointGunAt->IsPed() +#ifdef FIX_BUGS + && !GetWeapon()->IsTypeMelee() +#endif + && ((CPed*)m_pPointGunAt)->CanSeeEntity(this, CAN_SEE_ENTITY_ANGLE_THRESHOLD * 2)) { + ((CPed*)m_pPointGunAt)->ReactToPointGun(this); + } + PointGunAt(); + break; + case PED_SEEK_CAR: + SeekCar(); + break; + case PED_SEEK_IN_BOAT: + SeekBoatPosition(); + break; + case PED_INVESTIGATE: + InvestigateEvent(); + break; + case PED_ON_FIRE: + if (IsPlayer()) + break; + + if (CTimer::GetTimeInMilliseconds() <= m_fleeTimer) { + if (m_fleeFrom) { + ms_vec2DFleePosition = m_fleeFrom->GetPosition(); + } else { + ms_vec2DFleePosition.x = m_fleeFromPosX; + ms_vec2DFleePosition.y = m_fleeFromPosY; + } + Flee(); + } else { + if (m_pFire) + m_pFire->Extinguish(); + } + break; + case PED_FALL: + Fall(); + break; + case PED_GETUP: + SetGetUp(); + break; + case PED_ENTER_TRAIN: + EnterTrain(); + break; + case PED_EXIT_TRAIN: + ExitTrain(); + break; + case PED_DRIVING: + { + if (!m_pMyVehicle) { + bInVehicle = false; + FlagToDestroyWhenNextProcessed(); + return; + } + + if (m_pMyVehicle->pDriver != this || m_pMyVehicle->IsBoat()) { + LookForSexyPeds(); + LookForSexyCars(); + break; + } + + if (m_pMyVehicle->m_vehType == VEHICLE_TYPE_BIKE || !m_pMyVehicle->pDriver->IsPlayer()) { + break; + } + + CPad* pad = CPad::GetPad(0); + +#ifdef CAR_AIRBREAK + if (!pad->ArePlayerControlsDisabled()) { + if (pad->GetHorn()) { + float c = Cos(m_fRotationCur); + float s = Sin(m_fRotationCur); + m_pMyVehicle->GetRight() = CVector(1.0f, 0.0f, 0.0f); + m_pMyVehicle->GetForward() = CVector(0.0f, 1.0f, 0.0f); + m_pMyVehicle->GetUp() = CVector(0.0f, 0.0f, 1.0f); + if (pad->GetAccelerate()) { + m_pMyVehicle->ApplyMoveForce(GetForward() * 30.0f); + } else if (pad->GetBrake()) { + m_pMyVehicle->ApplyMoveForce(-GetForward() * 30.0f); + } else { + int16 lr = pad->GetSteeringLeftRight(); + if (lr < 0) { + //m_pMyVehicle->ApplyTurnForce(20.0f * -GetRight(), GetForward()); + m_pMyVehicle->ApplyMoveForce(-GetRight() * 30.0f); + } else if (lr > 0) { + m_pMyVehicle->ApplyMoveForce(GetRight() * 30.0f); + } else { + m_pMyVehicle->ApplyMoveForce(0.0f, 0.0f, 50.0f); + } + } + } + } +#endif + float steerAngle = m_pMyVehicle->m_fSteerAngle; + CAnimBlendAssociation *lDriveAssoc; + CAnimBlendAssociation *rDriveAssoc; + CAnimBlendAssociation *lbAssoc; + CAnimBlendAssociation *sitAssoc; + if (m_pMyVehicle->bLowVehicle) { + sitAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_LO); + + if (!sitAssoc || sitAssoc->blendAmount < 1.0f) { + break; + } + + lDriveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_LEFT_LO); + lbAssoc = nil; + rDriveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_RIGHT_LO); + } else { + sitAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT); + + if (!sitAssoc || sitAssoc->blendAmount < 1.0f) { + break; + } + + lDriveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_LEFT); + rDriveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_RIGHT); + lbAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_LOOKBEHIND); + + if (lbAssoc && + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON + && TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking == LOOKING_LEFT) { + lbAssoc->blendDelta = -1000.0f; + } + } + + CAnimBlendAssociation *driveByAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + + if (!driveByAssoc) + driveByAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + + if (m_pMyVehicle->bLowVehicle || m_pMyVehicle->m_fGasPedal >= 0.0f || driveByAssoc) { + if (steerAngle == 0.0f || driveByAssoc) { + if (lDriveAssoc) + lDriveAssoc->blendAmount = 0.0f; + if (rDriveAssoc) + rDriveAssoc->blendAmount = 0.0f; + + } else if (steerAngle <= 0.0f) { + if (lDriveAssoc) + lDriveAssoc->blendAmount = 0.0f; + + if (rDriveAssoc) + rDriveAssoc->blendAmount = Clamp(steerAngle * -100.0f / 61.0f, 0.0f, 1.0f); + else if (m_pMyVehicle->bLowVehicle) + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVE_RIGHT_LO); + else + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVE_RIGHT); + + } else { + if (rDriveAssoc) + rDriveAssoc->blendAmount = 0.0f; + + if (lDriveAssoc) + lDriveAssoc->blendAmount = Clamp(steerAngle * 100.0f / 61.0f, 0.0f, 1.0f); + else if (m_pMyVehicle->bLowVehicle) + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVE_LEFT_LO); + else + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVE_LEFT); + } + + if (lbAssoc) + lbAssoc->blendDelta = -4.0f; + } else { + + if ((TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_1STPERSON + || TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking != LOOKING_LEFT) + && (!lbAssoc || lbAssoc->blendAmount < 1.0f)) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_LOOKBEHIND, 4.0f); + } + } + break; + } + case PED_DIE: + Die(); + break; + case PED_HANDS_UP: + if (m_pedStats->m_temper <= 50) { + if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HANDSCOWER)) { + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSCOWER); + Say(SOUND_PED_HANDS_COWER); + } + } else if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HANDSUP)) { + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSUP); + Say(SOUND_PED_HANDS_UP); + } + break; + default: break; + } + SetMoveAnim(); + if (bPedIsBleeding) { + if (CGame::nastyGame) { + if (!(CTimer::GetFrameCounter() & 3)) { + CVector cameraDist = GetPosition() - TheCamera.GetPosition(); + if (cameraDist.MagnitudeSqr() < sq(50.0f)) { + + float length = (CGeneral::GetRandomNumber() & 127) * 0.0015f + 0.15f; + CVector bloodPos( + ((CGeneral::GetRandomNumber() & 127) - 64) * 0.007f, + ((CGeneral::GetRandomNumber() & 127) - 64) * 0.007f, + 1.0f); + bloodPos += GetPosition(); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &bloodPos, length, 0.0f, + 0.0f, -length, 255, 255, 0, 0, 4.0f, (CGeneral::GetRandomNumber() & 4095) + 2000, 1.0f); + } + } + } + } + ServiceTalking(); + if (bInVehicle && !m_pMyVehicle) + bInVehicle = false; +#ifndef VC_PED_PORTS + m_pCurrentPhysSurface = nil; +#endif + } else { + if (bIsStanding && (!m_pCurrentPhysSurface || IsPlayer()) + || bIsInWater || !bUsesCollision) { + SetDead(); + } + m_pCurrentPhysSurface = nil; + } + } +} + +int32 +CPed::ProcessEntityCollision(CEntity *collidingEnt, CColPoint *collidingPoints) +{ + bool collidedWithBoat = false; + bool belowTorsoCollided = false; + float gravityEffect = -0.15f * CTimer::GetTimeStep(); + CColPoint intersectionPoint; + CColLine ourLine; + + CColModel *ourCol = CModelInfo::GetColModel(GetModelIndex()); + CColModel *hisCol = CModelInfo::GetColModel(collidingEnt->GetModelIndex()); + + if (!bUsesCollision) + return 0; + + if (collidingEnt->IsVehicle() && ((CVehicle*)collidingEnt)->IsBoat()) + collidedWithBoat = true; + + // ofc we're not vehicle + if (!m_bIsVehicleBeingShifted && !bSkipLineCol +#ifdef VC_PED_PORTS + && !collidingEnt->IsPed() +#endif + ) { + if (!bCollisionProcessed) { +#ifdef VC_PED_PORTS + m_pCurrentPhysSurface = nil; +#endif + if (bIsStanding) { + bIsStanding = false; + bWasStanding = true; + } + bCollisionProcessed = true; + m_fCollisionSpeed += m_vecMoveSpeed.Magnitude2D() * CTimer::GetTimeStep(); + bStillOnValidPoly = false; + if (IsPlayer() || m_fCollisionSpeed >= 1.0f + && (m_fCollisionSpeed >= 2.0f || m_nPedState != PED_WANDER_PATH)) { + m_collPoly.valid = false; + m_fCollisionSpeed = 0.0f; + bHitSteepSlope = false; + } else { + CVector pos = GetPosition(); + float potentialGroundZ = GetPosition().z - FEET_OFFSET; + if (bWasStanding) { + pos.z += -0.25f; + potentialGroundZ += gravityEffect; + } + if (CCollision::IsStoredPolyStillValidVerticalLine(pos, potentialGroundZ, intersectionPoint, &m_collPoly)) { + bStillOnValidPoly = true; +#ifdef VC_PED_PORTS + if(!bSomeVCflag1 || FEET_OFFSET + intersectionPoint.point.z < GetPosition().z) { + GetMatrix().GetPosition().z = FEET_OFFSET + intersectionPoint.point.z; + if (bSomeVCflag1) + bSomeVCflag1 = false; + } +#else + GetMatrix().GetPosition().z = FEET_OFFSET + intersectionPoint.point.z; +#endif + + m_vecMoveSpeed.z = 0.0f; + bIsStanding = true; + } else { + m_collPoly.valid = false; + m_fCollisionSpeed = 0.0f; + bHitSteepSlope = false; + } + } + } + + if (!bStillOnValidPoly) { + CVector potentialCenter = GetPosition(); + potentialCenter.z = GetPosition().z - 0.52f; + + // 0.52f should be a ped's approx. radius + float totalRadiusWhenCollided = collidingEnt->GetBoundRadius() + 0.52f - gravityEffect; + if (bWasStanding) { + if (collidedWithBoat) { + potentialCenter.z += 2.0f * gravityEffect; + totalRadiusWhenCollided += Abs(gravityEffect); + } else { + potentialCenter.z += gravityEffect; + } + } + if (sq(totalRadiusWhenCollided) > (potentialCenter - collidingEnt->GetBoundCentre()).MagnitudeSqr()) { + ourLine.p0 = GetPosition(); + ourLine.p1 = GetPosition(); + ourLine.p1.z = GetPosition().z - FEET_OFFSET; + if (bWasStanding) { + ourLine.p1.z = ourLine.p1.z + gravityEffect; + ourLine.p0.z = ourLine.p0.z + -0.25f; + } + float minDist = 1.0f; + belowTorsoCollided = CCollision::ProcessVerticalLine(ourLine, collidingEnt->GetMatrix(), *hisCol, + intersectionPoint, minDist, false, &m_collPoly); + + if (collidedWithBoat && bWasStanding && !belowTorsoCollided) { + ourLine.p0.z = ourLine.p1.z; + ourLine.p1.z = ourLine.p1.z + gravityEffect; + belowTorsoCollided = CCollision::ProcessVerticalLine(ourLine, collidingEnt->GetMatrix(), *hisCol, + intersectionPoint, minDist, false, &m_collPoly); + } + if (belowTorsoCollided) { +#ifndef VC_PED_PORTS + if (!collidingEnt->IsPed()) { +#endif + if (!bIsStanding + || FEET_OFFSET + intersectionPoint.point.z > GetPosition().z + || collidedWithBoat && 3.12f + intersectionPoint.point.z > GetPosition().z) { + + if (!collidingEnt->IsVehicle() && !collidingEnt->IsObject()) { + m_pCurSurface = collidingEnt; + collidingEnt->RegisterReference((CEntity**)&m_pCurSurface); + bTryingToReachDryLand = false; + bOnBoat = false; + } else { + m_pCurrentPhysSurface = (CPhysical*)collidingEnt; + collidingEnt->RegisterReference((CEntity**)&m_pCurrentPhysSurface); + m_vecOffsetFromPhysSurface = intersectionPoint.point - collidingEnt->GetPosition(); + m_pCurSurface = collidingEnt; + collidingEnt->RegisterReference((CEntity**)&m_pCurSurface); + m_collPoly.valid = false; + if (collidingEnt->IsVehicle() && ((CVehicle*)collidingEnt)->IsBoat()) { + bOnBoat = true; + } else { + bOnBoat = false; + } + } +#ifdef VC_PED_PORTS + if (!bSomeVCflag1 || FEET_OFFSET + intersectionPoint.point.z < GetPosition().z) { + GetMatrix().GetPosition().z = FEET_OFFSET + intersectionPoint.point.z; + if (bSomeVCflag1) + bSomeVCflag1 = false; + } +#else + GetMatrix().GetPosition().z = FEET_OFFSET + intersectionPoint.point.z; +#endif + m_nSurfaceTouched = intersectionPoint.surfaceB; + if (m_nSurfaceTouched == SURFACE_STEEP_CLIFF) { + bHitSteepSlope = true; + m_vecDamageNormal = intersectionPoint.normal; + } + } +#ifdef VC_PED_PORTS + float upperSpeedLimit = 0.33f; + float lowerSpeedLimit = -0.25f; + float speed = m_vecMoveSpeed.Magnitude2D(); + if (m_nPedState == PED_IDLE) { + upperSpeedLimit *= 2.0f; + lowerSpeedLimit *= 1.5f; + } + CAnimBlendAssociation *fallAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL); + if (!bWasStanding && ((speed > upperSpeedLimit /* ||!bPushedAlongByCar*/) || (m_vecMoveSpeed.z < lowerSpeedLimit)) + && m_pCollidingEntity != collidingEnt) { + + float damage = 100.0f * Max(speed - 0.25f, 0.0f); + float damage2 = damage; + if (m_vecMoveSpeed.z < -0.25f) + damage += (-0.25f - m_vecMoveSpeed.z) * 150.0f; + + uint8 dir = 2; // from backward + if (m_vecMoveSpeed.x > 0.01f || m_vecMoveSpeed.x < -0.01f || m_vecMoveSpeed.y > 0.01f || m_vecMoveSpeed.y < -0.01f) { + CVector2D offset = -m_vecMoveSpeed; + dir = GetLocalDirection(offset); + } + + InflictDamage(collidingEnt, WEAPONTYPE_FALL, damage, PEDPIECE_TORSO, dir); + if (IsPlayer() && damage2 > 5.0f) + Say(SOUND_PED_LAND); + + } else if (!bWasStanding && fallAnim && -0.016f * CTimer::GetTimeStep() > m_vecMoveSpeed.z) { + InflictDamage(collidingEnt, WEAPONTYPE_FALL, 15.0f, PEDPIECE_TORSO, 2); + } +#else + float speedSqr = 0.0f; + CAnimBlendAssociation *fallAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL); + if (!bWasStanding && (m_vecMoveSpeed.z < -0.25f || (speedSqr = m_vecMoveSpeed.MagnitudeSqr()) > sq(0.5f))) { + if (speedSqr == 0.0f) + speedSqr = sq(m_vecMoveSpeed.z); + + uint8 dir = 2; // from backward + if (m_vecMoveSpeed.x > 0.01f || m_vecMoveSpeed.x < -0.01f || m_vecMoveSpeed.y > 0.01f || m_vecMoveSpeed.y < -0.01f) { + CVector2D offset = -m_vecMoveSpeed; + dir = GetLocalDirection(offset); + } + InflictDamage(collidingEnt, WEAPONTYPE_FALL, 350.0f * sq(speedSqr), PEDPIECE_TORSO, dir); + + } else if (!bWasStanding && fallAnim && -0.016f * CTimer::GetTimeStep() > m_vecMoveSpeed.z) { + InflictDamage(collidingEnt, WEAPONTYPE_FALL, 15.0f, PEDPIECE_TORSO, 2); + } +#endif + m_vecMoveSpeed.z = 0.0f; + bIsStanding = true; +#ifndef VC_PED_PORTS + } else { + bOnBoat = false; + } +#endif + } else { + bOnBoat = false; + } + } + } + } + + int ourCollidedSpheres = CCollision::ProcessColModels(GetMatrix(), *ourCol, collidingEnt->GetMatrix(), *hisCol, collidingPoints, nil, nil); + if (ourCollidedSpheres > 0 || belowTorsoCollided) { + AddCollisionRecord(collidingEnt); + if (!collidingEnt->IsBuilding()) + ((CPhysical*)collidingEnt)->AddCollisionRecord(this); + + if (ourCollidedSpheres > 0 && (collidingEnt->IsBuilding() || collidingEnt->GetIsStatic())) { + bHasHitWall = true; + } + } + if (collidingEnt->IsBuilding() || collidingEnt->GetIsStatic()) { + + if (bWasStanding) { + CVector sphereNormal; + float normalLength; + for(int sphere = 0; sphere < ourCollidedSpheres; sphere++) { + sphereNormal = collidingPoints[sphere].normal; +#ifdef VC_PED_PORTS + if (sphereNormal.z >= -1.0f || !IsPlayer()) { +#endif + normalLength = sphereNormal.Magnitude2D(); + if (normalLength != 0.0f) { + sphereNormal.x = sphereNormal.x / normalLength; + sphereNormal.y = sphereNormal.y / normalLength; + } +#ifdef VC_PED_PORTS + } else { + float speed = m_vecMoveSpeed.Magnitude2D(); + sphereNormal.x = -m_vecMoveSpeed.x / Max(0.001f, speed); + sphereNormal.y = -m_vecMoveSpeed.y / Max(0.001f, speed); + GetMatrix().GetPosition().z -= 0.05f; + bSomeVCflag1 = true; + } +#endif + sphereNormal.Normalise(); + collidingPoints[sphere].normal = sphereNormal; + if (collidingPoints[sphere].surfaceB == SURFACE_STEEP_CLIFF) + bHitSteepSlope = true; + } + } + } + return ourCollidedSpheres; +} + +static void +particleProduceFootSplash(CPed *ped, CVector const &pos, float size, int times) +{ +#ifdef PC_PARTICLE + for (int i = 0; i < times; i++) { + CVector adjustedPos = pos; + adjustedPos.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + adjustedPos.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + + CVector direction = ped->GetForward() * -0.05f; + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, adjustedPos, direction, nil, size, CRGBA(32, 32, 32, 32), 0, 0, CGeneral::GetRandomNumber() & 1, 200); + } +#else + for ( int32 i = 0; i < times; i++ ) + { + CVector adjustedPos = pos; + adjustedPos.x += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + adjustedPos.y += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, adjustedPos, CVector(0.0f, 0.0f, 0.0f), nil, size, CRGBA(0, 0, 0, 0), 0, 0, CGeneral::GetRandomNumber() & 1, 200); + } +#endif +} + +static void +particleProduceFootDust(CPed *ped, CVector const &pos, float size, int times) +{ + switch (ped->m_nSurfaceTouched) + { + case SURFACE_TARMAC: + case SURFACE_GRAVEL: + case SURFACE_PAVEMENT: + case SURFACE_SAND: + for (int i = 0; i < times; ++i) { + CVector adjustedPos = pos; + adjustedPos.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + adjustedPos.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + CParticle::AddParticle(PARTICLE_PEDFOOT_DUST, adjustedPos, CVector(0.0f, 0.0f, 0.0f), nil, size, CRGBA(0, 0, 0, 0), 0, 0, 0, 0); + } + break; + default: + break; + } +} + +void +CPed::PlayFootSteps(void) +{ + if (bDoBloodyFootprints) { + if (m_bloodyFootprintCountOrDeathTime > 0 && m_bloodyFootprintCountOrDeathTime < 300) { + m_bloodyFootprintCountOrDeathTime--; + + if (m_bloodyFootprintCountOrDeathTime == 0) + bDoBloodyFootprints = false; + } + } + + if (!bIsStanding) + return; + + CAnimBlendAssociation *assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); + CAnimBlendAssociation *walkRunAssoc = nil; + float walkRunAssocBlend = 0.0f, idleAssocBlend = 0.0f; + + for (; assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + if (assoc->flags & ASSOC_WALK) { + walkRunAssoc = assoc; + walkRunAssocBlend += assoc->blendAmount; + } else if ((assoc->flags & ASSOC_NOWALK) == 0) { + idleAssocBlend += assoc->blendAmount; + } + } + +#ifdef GTA_PS2_STUFF + CAnimBlendAssociation *runStopAsoc = NULL; + + if ( IsPlayer() ) + { + runStopAsoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + + if ( runStopAsoc == NULL ) + runStopAsoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + } + + if ( runStopAsoc != NULL && runStopAsoc->blendAmount > 0.1f ) + { + { + CVector pos(0.0f, 0.0f, 0.0f); + TransformToNode(pos, PED_FOOTL); + + pos.z -= 0.1f; + pos += GetForward()*0.2f; + particleProduceFootDust(this, pos, 0.02f, 1); + } + + { + CVector pos(0.0f, 0.0f, 0.0f); + TransformToNode(pos, PED_FOOTR); + + pos.z -= 0.1f; + pos += GetForward()*0.2f; + particleProduceFootDust(this, pos, 0.02f, 1); + } + } +#endif + + + if (walkRunAssoc && walkRunAssocBlend > 0.5f && idleAssocBlend < 1.0f) { + float stepStart = 1 / 15.0f; + float stepEnd = walkRunAssoc->hierarchy->totalLength / 2.0f + stepStart; + float currentTime = walkRunAssoc->currentTime; + int stepPart = 0; + + if (currentTime >= stepStart && currentTime - walkRunAssoc->timeStep < stepStart) + stepPart = 1; + else if (currentTime >= stepEnd && currentTime - walkRunAssoc->timeStep < stepEnd) + stepPart = 2; + + if (stepPart != 0) { + DMAudio.PlayOneShot(m_audioEntityId, stepPart == 1 ? SOUND_STEP_START : SOUND_STEP_END, 1.0f); + CVector footPos(0.0f, 0.0f, 0.0f); + TransformToNode(footPos, stepPart == 1 ? PED_FOOTL : PED_FOOTR); + + CVector forward = GetForward(); + + footPos.z -= 0.1f; + footPos += 0.2f * forward; + + if (bDoBloodyFootprints) { + CVector2D top(forward * 0.26f); + CVector2D right(GetRight() * 0.14f); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &footPos, + top.x, top.y, + right.x, right.y, + 255, 255, 0, 0, 4.0f, 3000.0f, 1.0f); + + if (m_bloodyFootprintCountOrDeathTime <= 20) { + m_bloodyFootprintCountOrDeathTime = 0; + bDoBloodyFootprints = false; + } else { + m_bloodyFootprintCountOrDeathTime -= 20; + } + } + if (CWeather::Rain <= 0.1f || CCullZones::CamNoRain() || CCullZones::PlayerNoRain()) { + if(IsPlayer()) + particleProduceFootDust(this, footPos, 0.0f, 4); + } +#ifdef PC_PARTICLE + else if(stepPart == 2) +#else + else +#endif + { + particleProduceFootSplash(this, footPos, 0.15f, 4); + } + } + } + + if (m_nSurfaceTouched == SURFACE_WATER) { + float pedSpeed = CVector2D(m_vecMoveSpeed).Magnitude(); + if (pedSpeed > 0.03f && CTimer::GetFrameCounter() % 2 == 0 && pedSpeed > 0.13f) { +#ifdef PC_PARTICLE + float particleSize = pedSpeed * 2.0f; + + if (particleSize < 0.25f) + particleSize = 0.25f; + + if (particleSize > 0.75f) + particleSize = 0.75f; + + CVector particlePos = GetPosition() + GetForward() * 0.3f; + particlePos.z -= 1.2f; + + CVector particleDir = m_vecMoveSpeed * -0.75f; + + particleDir.z = CGeneral::GetRandomNumberInRange(0.01f, 0.03f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, particlePos, particleDir, nil, 0.8f * particleSize, CRGBA(155,155,185,128), 0, 0, 0, 0); + + particleDir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.05f); + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, particlePos, particleDir, nil, particleSize, CRGBA(255,255,255,255), 0, 0, 0, 0); +#else + CVector particlePos = (GetPosition() - 0.3f * GetUp()) + GetForward()*0.3f; + CVector particleDir = m_vecMoveSpeed * 0.45f; + particleDir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.05f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, particlePos-CVector(0.0f, 0.0f, 1.2f), particleDir, nil, 0.0f, CRGBA(155, 185, 155, 255)); +#endif + } + } +} + +// Actually GetLocalDirectionTo(Turn/Look) +int +CPed::GetLocalDirection(const CVector2D &posOffset) +{ + int direction; + float angle; + + for (angle = posOffset.Heading() - m_fRotationCur + DEGTORAD(45.0f); angle < 0.0f; angle += TWOPI); + + for (direction = RADTODEG(angle)/90.0f; direction > 3; direction -= 4); + + // 0-forward, 1-left, 2-backward, 3-right. + return direction; +} + +#ifdef NEW_WALK_AROUND_ALGORITHM +CVector +LocalPosForWalkAround(CVector2D colMin, CVector2D colMax, int walkAround, uint32 enterDoorNode, bool itsVan) { + switch (walkAround) { + case 0: + if (enterDoorNode == CAR_DOOR_LF) + return CVector(colMin.x, colMax.y - 1.0f, 0.0f); + case 1: + return CVector(colMin.x, colMax.y, 0.0f); + case 2: + case 3: + if (walkAround == 3 && enterDoorNode == CAR_DOOR_RF) + return CVector(colMax.x, colMax.y - 1.0f, 0.0f); + + return CVector(colMax.x, colMax.y, 0.0f); + case 4: + if (enterDoorNode == CAR_DOOR_RR && !itsVan) + return CVector(colMax.x, colMin.y + 1.0f, 0.0f); + case 5: + return CVector(colMax.x, colMin.y, 0.0f); + case 6: + case 7: + if (walkAround == 7 && enterDoorNode == CAR_DOOR_LR && !itsVan) + return CVector(colMin.x, colMin.y + 1.0f, 0.0f); + + return CVector(colMin.x, colMin.y, 0.0f); + default: + return CVector(0.0f, 0.0f, 0.0f); + } +} + +bool +CanWeSeeTheCorner(CVector2D dist, CVector2D fwdOffset) +{ + // because fov isn't important if dist is more then 5 unit, we want shortest way + if (dist.Magnitude() > 5.0f) + return true; + + if (DotProduct2D(dist, fwdOffset) < 0.0f) + return false; + + return true; +} +#endif + +// This function looks completely same on VC. +void +CPed::SetDirectionToWalkAroundObject(CEntity *obj) +{ + float distLimitForTimer = 8.0f; + CColModel *objCol = CModelInfo::GetColModel(obj->GetModelIndex()); + CVector objColMin = objCol->boundingBox.min; + CVector objColMax = objCol->boundingBox.max; + CVector objColCenter = (objColMin + objColMax) / 2.0f; + CMatrix objMat(obj->GetMatrix()); + float dirToSet = obj->GetForward().Heading(); + bool goingToEnterCarAndItsVan = false; + bool goingToEnterCar = false; + bool objUpsideDown = false; + + float checkIntervalInDist = (objColMax.y - objColMin.y) * 0.1f; + float checkIntervalInTime; + + if (m_nMoveState == PEDMOVE_NONE || m_nMoveState == PEDMOVE_STILL) + return; + +#ifndef PEDS_REPORT_CRIMES_ON_PHONE + if (CharCreatedBy != MISSION_CHAR && obj->GetModelIndex() == MI_PHONEBOOTH1) { + bool isRunning = m_nMoveState == PEDMOVE_RUN || m_nMoveState == PEDMOVE_SPRINT; + SetFindPathAndFlee(obj, 5000, !isRunning); + return; + } +#endif + + CVector2D adjustedColMin(objColMin.x - 0.35f, objColMin.y - 0.35f); + CVector2D adjustedColMax(objColMax.x + 0.35f, objColMax.y + 0.35f); + + checkIntervalInDist = Max(checkIntervalInDist, 0.5f); + checkIntervalInDist = Min(checkIntervalInDist, (objColMax.z - objColMin.z) / 2.0f); + checkIntervalInDist = Min(checkIntervalInDist, (adjustedColMax.x - adjustedColMin.x) / 2.0f); + + if (objMat.GetUp().z < 0.0f) + objUpsideDown = true; + + if (obj->GetModelIndex() != MI_TRAFFICLIGHTS && obj->GetModelIndex() != MI_SINGLESTREETLIGHTS1 && obj->GetModelIndex() != MI_SINGLESTREETLIGHTS2) { + objColCenter = obj->GetMatrix() * objColCenter; + } else { + checkIntervalInDist = 0.4f; + if (objMat.GetUp().z <= 0.57f) { + + // Specific calculations for traffic lights, didn't get a bit. + adjustedColMin.x = 1.2f * (adjustedColMin.x < adjustedColMin.y ? adjustedColMin.x : adjustedColMin.y); + adjustedColMax.x = 1.2f * (adjustedColMax.x > adjustedColMax.y ? adjustedColMax.x : adjustedColMax.y); + adjustedColMin.y = 1.2f * objColMin.z; + adjustedColMax.y = 1.2f * objColMax.z; + dirToSet = objMat.GetUp().Heading(); + objMat.SetUnity(); + objMat.RotateZ(dirToSet); + objMat.GetPosition() += obj->GetPosition(); + objColCenter = obj->GetPosition(); + } else { + objColCenter.x = adjustedColMax.x - 0.25f; + objColCenter = obj->GetMatrix() * objColCenter; + distLimitForTimer = 0.75f; + } + objUpsideDown = false; + } + float oldRotDest = m_fRotationDest; +#ifndef NEW_WALK_AROUND_ALGORITHM + float angleToFaceObjCenter = (objColCenter - GetPosition()).Heading(); + float angleDiffBtwObjCenterAndForward = CGeneral::LimitRadianAngle(dirToSet - angleToFaceObjCenter); + float objTopRightHeading = Atan2(adjustedColMax.x - adjustedColMin.x, adjustedColMax.y - adjustedColMin.y); +#endif + + if (IsPlayer()) { + if (FindPlayerPed()->m_fMoveSpeed <= 0.0f) + checkIntervalInTime = 0.0f; + else + checkIntervalInTime = 2.0f / FindPlayerPed()->m_fMoveSpeed; + } else { + switch (m_nMoveState) { + case PEDMOVE_WALK: + checkIntervalInTime = 2.0f; + break; + case PEDMOVE_RUN: + checkIntervalInTime = 0.5f; + break; + case PEDMOVE_SPRINT: + checkIntervalInTime = 0.5f; + break; + default: + checkIntervalInTime = 0.0f; + break; + } + } + if (m_pSeekTarget == obj && obj->IsVehicle()) { + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER + || m_objective == OBJECTIVE_SOLICIT_VEHICLE) { + goingToEnterCar = true; + if (IsPlayer()) + checkIntervalInTime = 0.0f; + + if (((CVehicle*)obj)->bIsVan) + goingToEnterCarAndItsVan = true; + } + } + + int entityOnTopLeftOfObj = 0; + int entityOnBottomLeftOfObj = 0; + int entityOnTopRightOfObj = 0; + int entityOnBottomRightOfObj = 0; + + if (CTimer::GetTimeInMilliseconds() > m_collidingThingTimer || m_collidingEntityWhileFleeing != obj) { + bool collidingThingChanged = true; + CEntity *obstacle; + +#ifndef NEW_WALK_AROUND_ALGORITHM + if (!obj->IsVehicle() || objUpsideDown) { + collidingThingChanged = false; + } else { +#else + CVector cornerToGo = CVector(10.0f, 10.0f, 10.0f); + int dirToGo; + m_walkAroundType = 0; + int iWouldPreferGoingBack = 0; // 1:left 2:right +#endif + float adjustedCheckInterval = 0.7f * checkIntervalInDist; + CVector posToCheck; + + // Top left of obj + posToCheck.x = adjustedColMin.x + adjustedCheckInterval; + posToCheck.y = adjustedColMax.y - adjustedCheckInterval; + posToCheck.z = 0.0f; + posToCheck = objMat * posToCheck; + posToCheck.z += 0.6f; + obstacle = CWorld::TestSphereAgainstWorld(posToCheck, checkIntervalInDist, obj, + true, true, false, true, false, false); + if (obstacle) { + if (obstacle->IsBuilding()) { + entityOnTopLeftOfObj = 1; + } else if (obstacle->IsVehicle()) { + entityOnTopLeftOfObj = 2; + } else { + entityOnTopLeftOfObj = 3; + } + } +#ifdef NEW_WALK_AROUND_ALGORITHM + else { + CVector tl = obj->GetMatrix() * CVector(adjustedColMin.x, adjustedColMax.y, 0.0f) - GetPosition(); + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR)) { + cornerToGo = tl; + m_walkAroundType = 1; + + if (m_vehDoor == CAR_DOOR_LR) + iWouldPreferGoingBack = 1; + } else if(CanWeSeeTheCorner(tl, GetForward())){ + cornerToGo = tl; + dirToGo = GetLocalDirection(tl); + if (dirToGo == 1) + m_walkAroundType = 0; // ALL of the next turns will be right turn + else if (dirToGo == 3) + m_walkAroundType = 1; // ALL of the next turns will be left turn + } + } +#endif + + // Top right of obj + posToCheck.x = adjustedColMax.x - adjustedCheckInterval; + posToCheck.y = adjustedColMax.y - adjustedCheckInterval; + posToCheck.z = 0.0f; + posToCheck = objMat * posToCheck; + posToCheck.z += 0.6f; + obstacle = CWorld::TestSphereAgainstWorld(posToCheck, checkIntervalInDist, obj, + true, true, false, true, false, false); + if (obstacle) { + if (obstacle->IsBuilding()) { + entityOnTopRightOfObj = 1; + } else if (obstacle->IsVehicle()) { + entityOnTopRightOfObj = 2; + } else { + entityOnTopRightOfObj = 3; + } + } +#ifdef NEW_WALK_AROUND_ALGORITHM + else { + CVector tr = obj->GetMatrix() * CVector(adjustedColMax.x, adjustedColMax.y, 0.0f) - GetPosition(); + if (tr.Magnitude2D() < cornerToGo.Magnitude2D()) { + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR)) { + cornerToGo = tr; + m_walkAroundType = 2; + + if (m_vehDoor == CAR_DOOR_RR) + iWouldPreferGoingBack = 2; + } else if (CanWeSeeTheCorner(tr, GetForward())) { + cornerToGo = tr; + dirToGo = GetLocalDirection(tr); + if (dirToGo == 1) + m_walkAroundType = 2; // ALL of the next turns will be right turn + else if (dirToGo == 3) + m_walkAroundType = 3; // ALL of the next turns will be left turn + } + } + } +#endif + + // Bottom right of obj + posToCheck.x = adjustedColMax.x - adjustedCheckInterval; + posToCheck.y = adjustedColMin.y + adjustedCheckInterval; + posToCheck.z = 0.0f; + posToCheck = objMat * posToCheck; + posToCheck.z += 0.6f; + obstacle = CWorld::TestSphereAgainstWorld(posToCheck, checkIntervalInDist, obj, + true, true, false, true, false, false); + if (obstacle) { + if (obstacle->IsBuilding()) { + entityOnBottomRightOfObj = 1; + } else if (obstacle->IsVehicle()) { + entityOnBottomRightOfObj = 2; + } else { + entityOnBottomRightOfObj = 3; + } + } +#ifdef NEW_WALK_AROUND_ALGORITHM + else { + CVector br = obj->GetMatrix() * CVector(adjustedColMax.x, adjustedColMin.y, 0.0f) - GetPosition(); + if (iWouldPreferGoingBack == 2) + m_walkAroundType = 4; + else if (br.Magnitude2D() < cornerToGo.Magnitude2D()) { + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR)) { + cornerToGo = br; + m_walkAroundType = 5; + } else if (CanWeSeeTheCorner(br, GetForward())) { + cornerToGo = br; + dirToGo = GetLocalDirection(br); + if (dirToGo == 1) + m_walkAroundType = 4; // ALL of the next turns will be right turn + else if (dirToGo == 3) + m_walkAroundType = 5; // ALL of the next turns will be left turn + } + } + } +#endif + + // Bottom left of obj + posToCheck.x = adjustedColMin.x + adjustedCheckInterval; + posToCheck.y = adjustedColMin.y + adjustedCheckInterval; + posToCheck.z = 0.0f; + posToCheck = objMat * posToCheck; + posToCheck.z += 0.6f; + obstacle = CWorld::TestSphereAgainstWorld(posToCheck, checkIntervalInDist, obj, + true, true, false, true, false, false); + if (obstacle) { + if (obstacle->IsBuilding()) { + entityOnBottomLeftOfObj = 1; + } else if (obstacle->IsVehicle()) { + entityOnBottomLeftOfObj = 2; + } else { + entityOnBottomLeftOfObj = 3; + } + } +#ifdef NEW_WALK_AROUND_ALGORITHM + else { + CVector bl = obj->GetMatrix() * CVector(adjustedColMin.x, adjustedColMin.y, 0.0f) - GetPosition(); + if (iWouldPreferGoingBack == 1) + m_walkAroundType = 7; + else if (bl.Magnitude2D() < cornerToGo.Magnitude2D()) { + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR)) { + cornerToGo = bl; + m_walkAroundType = 6; + } else if (CanWeSeeTheCorner(bl, GetForward())) { + cornerToGo = bl; + dirToGo = GetLocalDirection(bl); + if (dirToGo == 1) + m_walkAroundType = 6; // ALL of the next turns will be right turn + else if (dirToGo == 3) + m_walkAroundType = 7; // ALL of the next turns will be left turn + } + } + } +#else + } + + if (entityOnTopLeftOfObj && entityOnTopRightOfObj && entityOnBottomRightOfObj && entityOnBottomLeftOfObj) { + collidingThingChanged = false; + entityOnTopLeftOfObj = 0; + entityOnBottomLeftOfObj = 0; + entityOnTopRightOfObj = 0; + entityOnBottomRightOfObj = 0; + } + + if (!collidingThingChanged) { + m_walkAroundType = 0; + } else { + if (Abs(angleDiffBtwObjCenterAndForward) >= objTopRightHeading) { + if (PI - objTopRightHeading >= Abs(angleDiffBtwObjCenterAndForward)) { + if ((angleDiffBtwObjCenterAndForward <= 0.0f || objUpsideDown) && (angleDiffBtwObjCenterAndForward < 0.0f || !objUpsideDown)) { + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR)) { + m_walkAroundType = 0; + } else { + if (CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) >= 0.0f) { + if (entityOnBottomRightOfObj == 1 || entityOnBottomRightOfObj && !entityOnTopLeftOfObj && !entityOnTopRightOfObj) { + m_walkAroundType = 1; + } else if (entityOnBottomLeftOfObj == 1 || entityOnBottomLeftOfObj && !entityOnTopLeftOfObj && !entityOnTopRightOfObj) { + m_walkAroundType = 1; + } + } else { + if (entityOnTopRightOfObj == 1 || entityOnTopRightOfObj && !entityOnBottomRightOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 4; + } else if (entityOnTopLeftOfObj == 1 || entityOnTopLeftOfObj && !entityOnBottomRightOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 4; + } + } + } + } else { + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR)) { + m_walkAroundType = 0; + } else { + if (CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) <= 0.0f) { + if (entityOnBottomLeftOfObj == 1 || entityOnBottomLeftOfObj && !entityOnTopLeftOfObj && !entityOnTopRightOfObj) { + m_walkAroundType = 2; + } else if (entityOnBottomRightOfObj == 1 || entityOnBottomRightOfObj && !entityOnTopLeftOfObj && !entityOnTopRightOfObj) { + m_walkAroundType = 2; + } + } else { + if (entityOnTopLeftOfObj == 1 || entityOnTopLeftOfObj && !entityOnBottomRightOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 3; + } else if (entityOnTopRightOfObj == 1 || entityOnTopRightOfObj && !entityOnBottomRightOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 3; + } + } + } + } + } else if (goingToEnterCar && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) + || CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) < 0.0f) { + if (entityOnTopLeftOfObj == 1 || entityOnTopLeftOfObj && !entityOnTopRightOfObj && !entityOnBottomRightOfObj) { + m_walkAroundType = 3; + } + } else if (entityOnTopRightOfObj == 1 || entityOnTopRightOfObj && !entityOnTopLeftOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 4; + } + } else if (goingToEnterCar && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) + || CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) > 0.0f) { + if (entityOnBottomLeftOfObj == 1 || entityOnBottomLeftOfObj && !entityOnTopRightOfObj && !entityOnBottomRightOfObj) { + m_walkAroundType = 2; + } + } else if (entityOnBottomRightOfObj == 1 || entityOnBottomRightOfObj && !entityOnTopLeftOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 1; + } else { + m_walkAroundType = 0; + } + } +#endif + } + m_collidingEntityWhileFleeing = obj; + m_collidingEntityWhileFleeing->RegisterReference((CEntity**) &m_collidingEntityWhileFleeing); + + // TODO: This random may need to be changed. + m_collidingThingTimer = CTimer::GetTimeInMilliseconds() + 512 + CGeneral::GetRandomNumber(); + + CVector localPosToHead; + +#ifdef NEW_WALK_AROUND_ALGORITHM + int nextWalkAround = m_walkAroundType; + if (m_walkAroundType % 2 == 0) { + nextWalkAround += 2; + if (nextWalkAround > 6) + nextWalkAround = 0; + } else { + nextWalkAround -= 2; + if (nextWalkAround < 0) + nextWalkAround = 7; + } + + CVector nextPosToHead = objMat * LocalPosForWalkAround(adjustedColMin, adjustedColMax, nextWalkAround, goingToEnterCar ? m_vehDoor : 0, goingToEnterCarAndItsVan); + bool nextRouteIsClear = CWorld::GetIsLineOfSightClear(GetPosition(), nextPosToHead, true, true, true, true, true, true, false); + + if(nextRouteIsClear) + m_walkAroundType = nextWalkAround; + else { + CVector posToHead = objMat * LocalPosForWalkAround(adjustedColMin, adjustedColMax, m_walkAroundType, goingToEnterCar ? m_vehDoor : 0, goingToEnterCarAndItsVan); + bool currentRouteIsClear = CWorld::GetIsLineOfSightClear(GetPosition(), posToHead, + true, true, true, true, true, true, false); + + /* Either; + * - Some obstacle came in and it's impossible to reach current destination + * - We reached to the destination, but since next route is not clear, we're turning around us + */ + if (!currentRouteIsClear || + ((posToHead - GetPosition()).Magnitude2D() < 0.8f && + !CWorld::GetIsLineOfSightClear(GetPosition() + GetForward(), nextPosToHead, + true, true, true, true, true, true, false))) { + + // Change both target and direction (involves changing even/oddness) + if (m_walkAroundType % 2 == 0) { + m_walkAroundType -= 2; + if (m_walkAroundType < 0) + m_walkAroundType = 7; + else + m_walkAroundType += 1; + } else { + m_walkAroundType += 2; + if (m_walkAroundType > 7) + m_walkAroundType = 0; + else + m_walkAroundType -= 1; + } + } + } + + localPosToHead = LocalPosForWalkAround(adjustedColMin, adjustedColMax, m_walkAroundType, goingToEnterCar ? m_vehDoor : 0, goingToEnterCarAndItsVan); +#else + if (Abs(angleDiffBtwObjCenterAndForward) < objTopRightHeading) { + if (goingToEnterCar) { + if (goingToEnterCarAndItsVan) { + if (m_vehDoor == CAR_DOOR_LR || m_vehDoor == CAR_DOOR_RR) + return; + } + if (m_vehDoor != CAR_DOOR_LF && m_vehDoor != CAR_DOOR_LR && (!entityOnBottomRightOfObj || entityOnBottomLeftOfObj)) { + m_fRotationDest = CGeneral::LimitRadianAngle(dirToSet - HALFPI); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(HALFPI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } + } else { + if (m_walkAroundType != 1 && m_walkAroundType != 4 + && (m_walkAroundType || CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) <= 0.0f)) { + + m_fRotationDest = CGeneral::LimitRadianAngle(dirToSet - HALFPI); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(HALFPI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } + } + } else { + if (PI - objTopRightHeading >= Abs(angleDiffBtwObjCenterAndForward)) { + if (angleDiffBtwObjCenterAndForward <= 0.0f) { + if (!goingToEnterCar || !goingToEnterCarAndItsVan || m_vehDoor != CAR_DOOR_LR && m_vehDoor != CAR_DOOR_RR) { + if (goingToEnterCar) { + if (m_vehDoor == CAR_DOOR_RF || (m_vehDoor == CAR_DOOR_RR && !goingToEnterCarAndItsVan)) + return; + } + if (m_walkAroundType == 4 || m_walkAroundType == 3 + || !m_walkAroundType && CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) > 0.0f) { + + m_fRotationDest = CGeneral::LimitRadianAngle(PI + dirToSet); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } else { + m_fRotationDest = dirToSet; + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(PI + dirToSet); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } + } else if (goingToEnterCar && goingToEnterCarAndItsVan && (m_vehDoor == CAR_DOOR_LR || m_vehDoor == CAR_DOOR_RR)) { + m_fRotationDest = CGeneral::LimitRadianAngle(PI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } else { + if (goingToEnterCar) { + if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR && !goingToEnterCarAndItsVan) + return; + } + if (m_walkAroundType == 1 || m_walkAroundType == 2 + || !m_walkAroundType && CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) > 0.0f) { + + m_fRotationDest = dirToSet; + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(PI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } + } + } else { + if (goingToEnterCar && (!goingToEnterCarAndItsVan || m_vehDoor != CAR_DOOR_LR && m_vehDoor != CAR_DOOR_RR)) { + if (m_vehDoor != CAR_DOOR_LF && m_vehDoor != CAR_DOOR_LR && (!entityOnTopRightOfObj || entityOnTopLeftOfObj)) { + + m_fRotationDest = CGeneral::LimitRadianAngle(dirToSet - HALFPI); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(HALFPI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } + } else { + if (m_walkAroundType == 2 || m_walkAroundType == 3 + || !m_walkAroundType && CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) > 0.0f) { + + m_fRotationDest = CGeneral::LimitRadianAngle(dirToSet - HALFPI); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(HALFPI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } + } + } + } +#endif + if (objUpsideDown) + localPosToHead.x = localPosToHead.x * -1.0f; + + localPosToHead = objMat * localPosToHead; + m_actionX = localPosToHead.x; + m_actionY = localPosToHead.y; + localPosToHead -= GetPosition(); + m_fRotationDest = CGeneral::LimitRadianAngle(localPosToHead.Heading()); + + if (m_fRotationDest != m_fRotationCur && bHitSomethingLastFrame) { + if (m_fRotationDest == oldRotDest) { + m_fRotationDest = oldRotDest; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(PI + dirToSet); + } + } + + float dist = localPosToHead.Magnitude2D(); + if (dist < 0.5f) + dist = 0.5f; + + if (dist > distLimitForTimer) + dist = distLimitForTimer; + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 280.0f * dist * checkIntervalInTime; +} + +bool +CPed::IsPedInControl(void) +{ + return m_nPedState <= PED_STATES_NO_AI + && !bIsInTheAir && !bIsLanding + && m_fHealth > 0.0f; +} + +bool +CPed::IsPedShootable(void) +{ + return m_nPedState <= PED_STATES_NO_ST; +} + +bool +CPed::UseGroundColModel(void) +{ + return m_nPedState == PED_FALL || + m_nPedState == PED_DIVE_AWAY || + m_nPedState == PED_DIE || + m_nPedState == PED_DEAD; +} + +bool +CPed::CanPedReturnToState(void) +{ + return m_nPedState <= PED_STATES_NO_AI && m_nPedState != PED_AIM_GUN && m_nPedState != PED_ATTACK && + m_nPedState != PED_FIGHT && m_nPedState != PED_STEP_AWAY && m_nPedState != PED_SNIPER_MODE && m_nPedState != PED_LOOK_ENTITY; +} + +bool +CPed::CanSetPedState(void) +{ + return !DyingOrDead() && m_nPedState != PED_ARRESTED && !EnteringCar() && m_nPedState != PED_STEAL_CAR; +} + +bool +CPed::CanStrafeOrMouseControl(void) +{ +#ifdef FREE_CAM + if (CCamera::bFreeCam) + return false; +#endif + return m_nPedState == PED_NONE || m_nPedState == PED_IDLE || m_nPedState == PED_FLEE_POS || m_nPedState == PED_FLEE_ENTITY || + m_nPedState == PED_ATTACK || m_nPedState == PED_FIGHT || m_nPedState == PED_AIM_GUN || m_nPedState == PED_JUMP; +} + +void +CPed::PedGetupCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + if (ped->m_nPedState == PED_GETUP) + RpAnimBlendClumpSetBlendDeltas(ped->GetClump(), ASSOC_PARTIAL, -1000.0f); + + ped->bFallenDown = false; + animAssoc->blendDelta = -1000.0f; + if (ped->m_nPedState == PED_GETUP) + ped->RestorePreviousState(); + + if (ped->m_nPedState != PED_FLEE_POS && ped->m_nPedState != PED_FLEE_ENTITY) + ped->SetMoveState(PEDMOVE_STILL); + else + ped->SetMoveState(PEDMOVE_RUN); + + ped->SetMoveAnim(); + ped->bGetUpAnimStarted = false; +} + +void +CPed::PedLandCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + animAssoc->blendDelta = -1000.0f; + ped->bIsLanding = false; + + if (ped->m_nPedState == PED_JUMP) + ped->RestorePreviousState(); +} + +void +CPed::PedStaggerCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + /* + CPed *ped = (CPed*)arg; + + if (ped->m_nPedState == PED_STAGGER) + // nothing + */ +} + +void +CPed::PedSetOutCarCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CVehicle *veh = ped->m_pMyVehicle; + + bool startedToRun = false; + ped->bUsesCollision = true; + ped->m_actionX = 0.0f; + ped->m_actionY = 0.0f; + ped->bVehExitWillBeInstant = false; + if (veh && veh->IsBoat()) + ped->ApplyMoveSpeed(); + + if (ped->m_objective == OBJECTIVE_LEAVE_CAR) + ped->RestorePreviousObjective(); +#ifdef VC_PED_PORTS + else if (ped->m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) { + ped->m_fHealth = 0.0f; + ped->SetDie(ANIM_STD_HIT_FLOOR, 4.0f, 0.5f); + } +#endif + + ped->bInVehicle = false; + if (veh && veh->IsCar() && !veh->IsRoomForPedToLeaveCar(ped->m_vehDoor, nil)) { + ped->PositionPedOutOfCollision(); + } + + if (ped->m_nPedState == PED_EXIT_CAR) { + if (ped->m_nPedType == PEDTYPE_COP) + ped->SetIdle(); + else + ped->RestorePreviousState(); + + veh = ped->m_pMyVehicle; + if (ped->bFleeAfterExitingCar && veh) { + ped->bFleeAfterExitingCar = false; + ped->SetFlee(veh->GetPosition(), 12000); + ped->bUsePedNodeSeek = true; + ped->m_pNextPathNode = nil; + if (CGeneral::GetRandomNumber() & 1 || ped->m_pedStats->m_fear > 70) { + ped->SetMoveState(PEDMOVE_SPRINT); + ped->Say(SOUND_PED_FLEE_SPRINT); + } else { + ped->SetMoveState(PEDMOVE_RUN); + ped->Say(SOUND_PED_FLEE_RUN); + } + startedToRun = true; + + // This is not a good way to do this... + ped->m_nLastPedState = PED_WANDER_PATH; + + } else if (ped->bWanderPathAfterExitingCar) { + ped->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + ped->bWanderPathAfterExitingCar = false; + if (ped->m_nPedType == PEDTYPE_PROSTITUTE) + ped->SetObjectiveTimer(30000); + ped->m_nLastPedState = PED_NONE; + + } else if (ped->bGonnaKillTheCarJacker) { + + // Kill objective is already given at this point. + ped->bGonnaKillTheCarJacker = false; + if (ped->m_pedInObjective) { + if (!(CGeneral::GetRandomNumber() & 1) + && ped->m_nPedType != PEDTYPE_COP + && (!ped->m_pedInObjective->IsPlayer() || !CTheScripts::IsPlayerOnAMission())) { + ped->ClearObjective(); + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, veh); + } + ped->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 1500; + } + int waitTime = 1500; + ped->SetWaitState(WAITSTATE_PLAYANIM_COWER, &waitTime); + ped->SetMoveState(PEDMOVE_RUN); + startedToRun = true; + } else if (ped->m_objective == OBJECTIVE_NONE && ped->CharCreatedBy != MISSION_CHAR && ped->m_nPedState == PED_IDLE && !ped->IsPlayer()) { + ped->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + } + } +#ifdef VC_PED_PORTS + else { + ped->m_nPedState = PED_IDLE; + } +#endif + + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + ped->RestartNonPartialAnims(); + ped->m_pVehicleAnim = nil; + CVector posFromZ = ped->GetPosition(); + CPedPlacement::FindZCoorForPed(&posFromZ); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + ped->SetPosition(posFromZ); + veh = ped->m_pMyVehicle; + if (veh) { + if (ped->m_nPedType == PEDTYPE_PROSTITUTE) { + if (veh->pDriver) { + if (veh->pDriver->IsPlayer() && ped->CharCreatedBy == RANDOM_CHAR) { + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexMoneyUpdateTime = 0; + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = 0; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil; + CWorld::Players[CWorld::PlayerInFocus].m_nMoney -= 100; + if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney < 0) + CWorld::Players[CWorld::PlayerInFocus].m_nMoney = 0; + } + } + } + veh->m_nGettingOutFlags &= ~GetCarDoorFlag(ped->m_vehDoor); + if (veh->pDriver == ped) { + veh->RemoveDriver(); +#ifndef FIX_BUGS // RemoveDriver does it anyway + veh->SetStatus(STATUS_ABANDONED); +#endif + if (veh->m_nDoorLock == CARLOCK_LOCKED_INITIALLY) + veh->m_nDoorLock = CARLOCK_UNLOCKED; + if (ped->m_nPedType == PEDTYPE_COP && veh->IsLawEnforcementVehicle()) + veh->ChangeLawEnforcerState(false); + } else { + veh->RemovePassenger(ped); + } + + if (veh->bIsBus && !veh->IsUpsideDown() && !veh->IsOnItsSide()) { + float angleAfterExit; + if (ped->m_vehDoor == CAR_DOOR_LF) { + angleAfterExit = HALFPI + veh->GetForward().Heading(); + } else { + angleAfterExit = veh->GetForward().Heading() - HALFPI; + } + ped->SetHeading(angleAfterExit); + ped->m_fRotationDest = angleAfterExit; + ped->m_fRotationCur = angleAfterExit; + if (!ped->bBusJacked) + ped->SetMoveState(PEDMOVE_WALK); + } + if (CGarages::IsPointWithinAnyGarage(ped->GetPosition())) + veh->bLightsOn = false; + } + + if (ped->IsPlayer()) + AudioManager.PlayerJustLeftCar(); + + ped->ReplaceWeaponWhenExitingVehicle(); + + ped->bOnBoat = false; + if (ped->bBusJacked) { + ped->SetFall(1500, ANIM_STD_HIGHIMPACT_BACK, false); + ped->bBusJacked = false; + } + ped->m_nStoredMoveState = PEDMOVE_NONE; + if (!ped->IsPlayer()) { + // It's a shame... +#ifdef FIX_BUGS + int createdBy = ped->CharCreatedBy; +#else + int createdBy = !ped->CharCreatedBy; +#endif + + if (createdBy == MISSION_CHAR && !startedToRun) + ped->SetMoveState(PEDMOVE_WALK); + } +} + +void +CPed::PedSetDraggedOutCarCB(CAnimBlendAssociation *dragAssoc, void *arg) +{ + CAnimBlendAssociation *quickJackedAssoc; + CVehicle *vehicle; + CPed *ped = (CPed*)arg; + + quickJackedAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), ANIM_STD_QUICKJACKED); + if (ped->m_nPedState != PED_ARRESTED) { + ped->m_nLastPedState = PED_NONE; + if (dragAssoc) + dragAssoc->blendDelta = -1000.0f; + } + ped->RestartNonPartialAnims(); + ped->m_pVehicleAnim = nil; + ped->m_pSeekTarget = nil; + vehicle = ped->m_pMyVehicle; + + if (vehicle) { + vehicle->m_nGettingOutFlags &= ~GetCarDoorFlag(ped->m_vehDoor); + + if (vehicle->pDriver == ped) { + vehicle->RemoveDriver(); + if (vehicle->m_nDoorLock == CARLOCK_LOCKED_INITIALLY) + vehicle->m_nDoorLock = CARLOCK_UNLOCKED; + + if (ped->m_nPedType == PEDTYPE_COP && vehicle->IsLawEnforcementVehicle()) + vehicle->ChangeLawEnforcerState(false); + } else { + vehicle->RemovePassenger(ped); + } + } + ped->bInVehicle = false; + if (ped->IsPlayer()) + AudioManager.PlayerJustLeftCar(); + +#ifdef VC_PED_PORTS + if (ped->m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) { + dragAssoc->SetDeleteCallback(PedSetDraggedOutCarPositionCB, ped); + ped->m_fHealth = 0.0f; + ped->SetDie(ANIM_STD_HIT_FLOOR, 1000.0f, 0.5f); + return; + } +#endif + + if (quickJackedAssoc) { + dragAssoc->SetDeleteCallback(PedSetQuickDraggedOutCarPositionCB, ped); + } else { + dragAssoc->SetDeleteCallback(PedSetDraggedOutCarPositionCB, ped); + if (ped->CanSetPedState()) + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_GET_UP, 1000.0f); + } + + ped->ReplaceWeaponWhenExitingVehicle(); + + ped->m_nStoredMoveState = PEDMOVE_NONE; + ped->bVehExitWillBeInstant = false; +} + +void +CPed::PedSetInCarCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CVehicle *veh = ped->m_pMyVehicle; + + // Pointless code + if (!veh) + return; + +#ifdef VC_PED_PORTS + // Situation of entering car as a driver while there is already a driver exiting atm. + CPed *driver = veh->pDriver; + if (driver && driver->m_nPedState == PED_DRIVING && !veh->bIsBus && driver->m_objective == OBJECTIVE_LEAVE_CAR + && (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || ped->m_nPedState == PED_CARJACK)) { + + if (!ped->IsPlayer() && (ped->CharCreatedBy != MISSION_CHAR || driver->IsPlayer())) { + ped->QuitEnteringCar(); + return; + } + if (driver->CharCreatedBy == MISSION_CHAR) { + PedSetOutCarCB(nil, veh->pDriver); + if (driver->m_pMyVehicle) { + driver->PositionPedOutOfCollision(); + } else { + driver->m_pMyVehicle = veh; + driver->PositionPedOutOfCollision(); + driver->m_pMyVehicle = nil; + } + veh->pDriver = nil; + } else { + driver->SetDead(); + driver->FlagToDestroyWhenNextProcessed(); + veh->pDriver = nil; + } + } +#endif + + if (!ped->IsNotInWreckedVehicle() || ped->DyingOrDead()) + return; + + ped->bInVehicle = true; + if (ped->m_nPedType == PEDTYPE_PROSTITUTE) { + if (veh->pDriver) { + if (veh->pDriver->IsPlayer() && ped->CharCreatedBy == RANDOM_CHAR) { + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 1000; + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexMoneyUpdateTime = CTimer::GetTimeInMilliseconds() + 1000; + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + 3000; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = (CCivilianPed*)ped; + } + } + } + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER +#if defined VC_PED_PORTS || defined FIX_BUGS + || ped->m_nPedState == PED_CARJACK +#endif + ) + veh->bIsBeingCarJacked = false; + + if (veh->m_nNumGettingIn) + --veh->m_nNumGettingIn; + + if (ped->IsPlayer() && ((CPlayerPed*)ped)->m_bAdrenalineActive) + ((CPlayerPed*)ped)->ClearAdrenaline(); + + if (veh->IsBoat()) { + if (ped->IsPlayer()) { +#if defined VC_PED_PORTS || defined FIX_BUGS + CCarCtrl::RegisterVehicleOfInterest(veh); +#endif + if (veh->GetStatus() == STATUS_SIMPLE) { + veh->m_vecMoveSpeed = CVector(0.0f, 0.0f, -0.00001f); + veh->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + } + veh->SetStatus(STATUS_PLAYER); + AudioManager.PlayerJustGotInCar(); + } + veh->SetDriver(ped); + if (!veh->bEngineOn) + veh->bEngineOn = true; + + ped->SetPedState(PED_DRIVING); + ped->StopNonPartialAnims(); + return; + } + + if (ped->m_pVehicleAnim) + ped->m_pVehicleAnim->blendDelta = -1000.0f; + + ped->bDoBloodyFootprints = false; + if (veh->m_nAlarmState == -1) + veh->m_nAlarmState = 15000; + + if (ped->IsPlayer()) { + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (veh->GetStatus() == STATUS_SIMPLE) { + veh->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + veh->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + } + veh->SetStatus(STATUS_PLAYER); + } + AudioManager.PlayerJustGotInCar(); + } else if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (veh->GetStatus() == STATUS_SIMPLE) { + veh->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + veh->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + } + veh->SetStatus(STATUS_PHYSICS); + } + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + for (int i = 0; i < veh->m_nNumMaxPassengers; ++i) { + CPed *passenger = veh->pPassengers[i]; + if (passenger && passenger->CharCreatedBy == RANDOM_CHAR) { + passenger->SetObjective(OBJECTIVE_LEAVE_CAR, veh); +#ifdef VC_PED_PORTS + passenger->m_leaveCarTimer = CTimer::GetTimeInMilliseconds(); +#endif + } + } + } + // This shouldn't happen at all. Passengers can't enter with PED_CARJACK. Even though they did, we shouldn't call AddPassenger in here and SetDriver in below. +#if !defined VC_PED_PORTS && !defined FIX_BUGS + else if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + if (ped->m_nPedState == PED_CARJACK) { + veh->AddPassenger(ped, 0); + ped->SetPedState(PED_DRIVING); + ped->RestorePreviousObjective(); + ped->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + } else if (veh->pDriver && ped->CharCreatedBy == RANDOM_CHAR) { + veh->AutoPilot.m_nCruiseSpeed = 17; + } + } +#endif + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || ped->m_nPedState == PED_CARJACK) { + veh->SetDriver(ped); + if (veh->VehicleCreatedBy == PARKED_VEHICLE) { + veh->VehicleCreatedBy = RANDOM_VEHICLE; + ++CCarCtrl::NumRandomCars; + --CCarCtrl::NumParkedCars; + } + if (veh->bIsAmbulanceOnDuty) { + veh->bIsAmbulanceOnDuty = false; + --CCarCtrl::NumAmbulancesOnDuty; + } + if (veh->bIsFireTruckOnDuty) { + veh->bIsFireTruckOnDuty = false; + --CCarCtrl::NumFiretrucksOnDuty; + } + if (ped->m_nPedType == PEDTYPE_COP && veh->IsLawEnforcementVehicle()) + veh->ChangeLawEnforcerState(true); + + if (!veh->bEngineOn) { + veh->bEngineOn = true; + DMAudio.PlayOneShot(ped->m_audioEntityId, SOUND_CAR_ENGINE_START, 1.0f); + } + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER && ped->CharCreatedBy == RANDOM_CHAR + && ped != FindPlayerPed() && ped->m_nPedType != PEDTYPE_EMERGENCY) { + + CCarCtrl::JoinCarWithRoadSystem(veh); + veh->AutoPilot.m_nCarMission = MISSION_CRUISE; + veh->AutoPilot.m_nTempAction = TEMPACT_NONE; + veh->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + veh->AutoPilot.m_nCruiseSpeed = 25; + } + ped->SetPedState(PED_DRIVING); + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + + if (ped->m_prevObjective == OBJECTIVE_RUN_TO_AREA || ped->m_prevObjective == OBJECTIVE_GOTO_CHAR_ON_FOOT || ped->m_prevObjective == OBJECTIVE_KILL_CHAR_ON_FOOT) + ped->m_prevObjective = OBJECTIVE_NONE; + + ped->RestorePreviousObjective(); + } + + } else if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + if (veh->bIsBus) { + veh->AddPassenger(ped); + } else { + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: + veh->AddPassenger(ped, 0); + break; + case CAR_DOOR_RR: + veh->AddPassenger(ped, 2); + break; + case CAR_DOOR_LR: + veh->AddPassenger(ped, 1); + break; + default: + veh->AddPassenger(ped); + break; + } + } + ped->SetPedState(PED_DRIVING); + if (ped->m_prevObjective == OBJECTIVE_RUN_TO_AREA || ped->m_prevObjective == OBJECTIVE_GOTO_CHAR_ON_FOOT || ped->m_prevObjective == OBJECTIVE_KILL_CHAR_ON_FOOT) + ped->m_prevObjective = OBJECTIVE_NONE; + + ped->RestorePreviousObjective(); +#ifdef VC_PED_PORTS + if(veh->pDriver && ped->CharCreatedBy == RANDOM_CHAR) + veh->AutoPilot.m_nCruiseSpeed = 17; +#endif + } + + veh->m_nGettingInFlags &= ~GetCarDoorFlag(ped->m_vehDoor); + + if (veh->bIsBus && !veh->m_nGettingInFlags) + ((CAutomobile*)veh)->SetBusDoorTimer(1000, 1); + + switch (ped->m_objective) { + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_LEAVE_CAR: + case OBJECTIVE_FOLLOW_CAR_IN_CAR: + case OBJECTIVE_GOTO_AREA_ANY_MEANS: + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_RUN_TO_AREA: + break; + default: + ped->SetObjective(OBJECTIVE_NONE); + } + + if (veh->pDriver == ped) { + if (veh->bLowVehicle) { + ped->m_pVehicleAnim = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT_LO, 100.0f); + } else { + ped->m_pVehicleAnim = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT, 100.0f); + } + } else if (veh->bLowVehicle) { + ped->m_pVehicleAnim = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT_P_LO, 100.0f); + } else { + ped->m_pVehicleAnim = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT_P, 100.0f); + } + + ped->StopNonPartialAnims(); + if (veh->bIsBus) + ped->bRenderPedInCar = false; + + // FIX: RegisterVehicleOfInterest not just registers the vehicle, but also updates register time. So remove the IsThisVehicleInteresting check. +#ifndef FIX_BUGS + if (ped->IsPlayer() && !CCarCtrl::IsThisVehicleInteresting(veh) && veh->VehicleCreatedBy != MISSION_VEHICLE) { +#else + if (ped->IsPlayer() && veh->VehicleCreatedBy != MISSION_VEHICLE) { +#endif + CCarCtrl::RegisterVehicleOfInterest(veh); + + if (!veh->bHasBeenOwnedByPlayer && veh->VehicleCreatedBy != MISSION_VEHICLE) + CEventList::RegisterEvent(EVENT_STEAL_CAR, EVENT_ENTITY_VEHICLE, veh, ped, 1500); + + veh->bHasBeenOwnedByPlayer = true; + } + ped->bChangedSeat = true; +} + +bool +CPed::CanBeDeleted(void) +{ + if (bInVehicle) + return false; + + switch (CharCreatedBy) { + case RANDOM_CHAR: + return true; + case MISSION_CHAR: + return false; + default: + return true; + } +} + +void +CPed::AddWeaponModel(int id) +{ + RpAtomic *atm; + + if (id != -1) { +#ifdef PED_SKIN + if (IsClumpSkinned(GetClump())) { + if (m_pWeaponModel) + RemoveWeaponModel(-1); + + m_pWeaponModel = (RpAtomic*)CModelInfo::GetModelInfo(id)->CreateInstance(); + } else +#endif + { + atm = (RpAtomic*)CModelInfo::GetModelInfo(id)->CreateInstance(); + RwFrameDestroy(RpAtomicGetFrame(atm)); + RpAtomicSetFrame(atm, m_pFrames[PED_HANDR]->frame); + RpClumpAddAtomic(GetClump(), atm); + } + m_wepModelID = id; + } +} + +static RwObject* +RemoveAllModelCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + if (CVisibilityPlugins::GetAtomicModelInfo(atomic)) { + RpClumpRemoveAtomic(RpAtomicGetClump(atomic), atomic); + RpAtomicDestroy(atomic); + } + return object; +} + +void +CPed::RemoveWeaponModel(int modelId) +{ + // modelId is not used!! This function just removes the current weapon. +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + if(m_pWeaponModel){ + RwFrame *frm = RpAtomicGetFrame(m_pWeaponModel); + RpAtomicDestroy(m_pWeaponModel); + RwFrameDestroy(frm); + m_pWeaponModel = nil; + } + }else +#endif + RwFrameForAllObjects(m_pFrames[PED_HANDR]->frame,RemoveAllModelCB,nil); + m_wepModelID = -1; +} + +uint32 +CPed::GiveWeapon(eWeaponType weaponType, uint32 ammo) +{ + CWeapon &weapon = GetWeapon(weaponType); + + if (HasWeapon(weaponType)) { + if (weapon.m_nAmmoTotal + ammo > 99999) + weapon.m_nAmmoTotal = 99999; + else + weapon.m_nAmmoTotal += ammo; + + weapon.Reload(); + } else { + weapon.Initialise(weaponType, ammo); + // TODO: It seems game uses this as both weapon count and max WeaponType we have, which is ofcourse erroneous. + m_maxWeaponTypeAllowed++; + } + if (weapon.m_eWeaponState == WEAPONSTATE_OUT_OF_AMMO) + weapon.m_eWeaponState = WEAPONSTATE_READY; + + return weaponType; +} + +// Some kind of VC leftover I think +int +CPed::GetWeaponSlot(eWeaponType weaponType) +{ + if (HasWeapon(weaponType)) + return weaponType; + else + return -1; +} + +void +CPed::SetCurrentWeapon(uint32 weaponType) +{ + CWeaponInfo *weaponInfo; + if (HasWeapon(weaponType)) { + weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(weaponInfo->m_nModelId); + + m_currentWeapon = weaponType; + + weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + AddWeaponModel(weaponInfo->m_nModelId); + } +} + +void +CPed::GrantAmmo(eWeaponType weaponType, uint32 ammo) +{ + if (HasWeapon(weaponType)) { + GetWeapon(weaponType).m_nAmmoTotal += ammo; + } else { + GetWeapon(weaponType).Initialise(weaponType, ammo); + m_maxWeaponTypeAllowed++; + } +} + +void +CPed::SetAmmo(eWeaponType weaponType, uint32 ammo) +{ + if (HasWeapon(weaponType)) { + GetWeapon(weaponType).m_nAmmoTotal = ammo; + } else { + GetWeapon(weaponType).Initialise(weaponType, ammo); + m_maxWeaponTypeAllowed++; + } +} + +void +CPed::ClearWeapons(void) +{ + CWeaponInfo *currentWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(currentWeapon->m_nModelId); + + m_maxWeaponTypeAllowed = WEAPONTYPE_BASEBALLBAT; + m_currentWeapon = WEAPONTYPE_UNARMED; + + currentWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + AddWeaponModel(currentWeapon->m_nModelId); + for(int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++) { + CWeapon &weapon = GetWeapon(i); + weapon.m_eWeaponType = WEAPONTYPE_UNARMED; + weapon.m_eWeaponState = WEAPONSTATE_READY; + weapon.m_nAmmoInClip = 0; + weapon.m_nAmmoTotal = 0; + weapon.m_nTimer = 0; + } +} + +void +CPed::PreRender(void) +{ + CShadows::StoreShadowForPed(this, + CTimeCycle::m_fShadowDisplacementX[CTimeCycle::m_CurrentStoredValue], CTimeCycle::m_fShadowDisplacementY[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowFrontX[CTimeCycle::m_CurrentStoredValue], CTimeCycle::m_fShadowFrontY[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowSideX[CTimeCycle::m_CurrentStoredValue], CTimeCycle::m_fShadowSideY[CTimeCycle::m_CurrentStoredValue]); + +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + UpdateRpHAnim(); + + if(bBodyPartJustCameOff && m_bodyPartBleeding == PED_HEAD){ + // scale head to 0 if shot off + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int32 idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_HEAD)); + RwMatrix *head = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwV3d zero = { 0.0f, 0.0f, 0.0f }; + RwMatrixScale(head, &zero, rwCOMBINEPRECONCAT); + } + } +#endif + + if (bBodyPartJustCameOff && bIsPedDieAnimPlaying && m_bodyPartBleeding != -1 && (CTimer::GetFrameCounter() & 7) > 3) { + CVector bloodDir(0.0f, 0.0f, 0.0f); + CVector bloodPos(0.0f, 0.0f, 0.0f); + + TransformToNode(bloodPos, m_bodyPartBleeding); + + switch (m_bodyPartBleeding) { + case PED_HEAD: + bloodDir = 0.1f * GetUp(); + break; + case PED_UPPERARML: + bloodDir = 0.04f * GetUp() - 0.04f * GetRight(); + break; + case PED_UPPERARMR: + bloodDir = 0.04f * GetUp() - 0.04f * GetRight(); + break; + case PED_UPPERLEGL: + bloodDir = 0.04f * GetUp() + 0.05f * GetForward(); + break; + case PED_UPPERLEGR: + bloodDir = 0.04f * GetUp() + 0.05f * GetForward(); + break; + default: + bloodDir = CVector(0.0f, 0.0f, 0.0f); + break; + } + + for(int i = 0; i < 4; i++) + CParticle::AddParticle(PARTICLE_BLOOD_SPURT, bloodPos, bloodDir, nil, 0.0f, 0, 0, 0, 0); + } + if (CWeather::Rain > 0.3f && TheCamera.SoundDistUp > 15.0f) { + if ((TheCamera.GetPosition() - GetPosition()).Magnitude() < 25.0f) { + bool doSplashUp = true; + CColModel *ourCol = CModelInfo::GetColModel(GetModelIndex()); + CVector speed = FindPlayerSpeed(); + + if (Abs(speed.x) <= 0.05f && Abs(speed.y) <= 0.05f) { + if (!OnGround() && m_nPedState != PED_ATTACK && m_nPedState != PED_FIGHT) { + if (!IsPedHeadAbovePos(0.3f) || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED)) { + doSplashUp = false; + } + } else + doSplashUp = false; + } else + doSplashUp = false; + + if (doSplashUp && ourCol->numSpheres > 0) { + for(int i = 0; i < ourCol->numSpheres; i++) { + CColSphere *sphere = &ourCol->spheres[i]; + CVector splashPos; + switch (sphere->piece) { + case PEDPIECE_LEFTARM: + case PEDPIECE_RIGHTARM: + case PEDPIECE_HEAD: + splashPos = GetMatrix() * ourCol->spheres[i].center; + splashPos.z += 0.7f * sphere->radius; + splashPos.x += CGeneral::GetRandomNumberInRange(-0.15f, 0.15f); + splashPos.y += CGeneral::GetRandomNumberInRange(-0.15f, 0.15f); + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, splashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, CGeneral::GetRandomNumber() & 1, 0); + break; + default: + break; + } + } + } + } + } +} + +void +CPed::Render(void) +{ + if (bInVehicle && m_nPedState != PED_EXIT_CAR && m_nPedState != PED_DRAG_FROM_CAR) { + if (!bRenderPedInCar) + return; + + float camDistSq = (TheCamera.GetPosition() - GetPosition()).MagnitudeSqr(); + if (camDistSq > SQR(25.0f * TheCamera.LODDistMultiplier)) + return; + } + + CEntity::Render(); + +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + renderLimb(PED_HEAD); + renderLimb(PED_HANDL); + renderLimb(PED_HANDR); + } + if(m_pWeaponModel && IsClumpSkinned(GetClump())){ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int idx = RpHAnimIDGetIndex(hier, m_pFrames[PED_HANDR]->nodeID); + RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwFrame *frame = RpAtomicGetFrame(m_pWeaponModel); + *RwFrameGetMatrix(frame) = *mat; + RwFrameUpdateObjects(frame); + RpAtomicRender(m_pWeaponModel); + } +#endif +} + +void +CPed::CheckAroundForPossibleCollisions(void) +{ + CVector ourCentre, objCentre; + CEntity *objects[8]; + int16 maxObject; + + if (CTimer::GetTimeInMilliseconds() <= m_nPedStateTimer) + return; + + GetBoundCentre(ourCentre); + + CWorld::FindObjectsInRange(ourCentre, 10.0f, true, &maxObject, 6, objects, false, true, false, true, false); + for (int i = 0; i < maxObject; i++) { + CEntity *object = objects[i]; + if (bRunningToPhone) { + if (gPhoneInfo.PhoneAtThisPosition(object->GetPosition())) + break; + } + object->GetBoundCentre(objCentre); + float radius = object->GetBoundRadius(); + if (radius > 4.5f || radius < 1.0f) + radius = 1.0f; + + // Developers gave up calculating Z diff. later according to asm. + float diff = CVector(ourCentre - objCentre).MagnitudeSqr2D(); + + if (sq(radius + 1.0f) > diff) + m_fRotationDest += DEGTORAD(22.5f); + } +} + +void +CPed::SetIdle(void) +{ + if (m_nPedState != PED_IDLE && m_nPedState != PED_MUG && m_nPedState != PED_FLEE_ENTITY) { +#ifdef VC_PED_PORTS + if (m_nPedState == PED_AIM_GUN) + ClearPointGunAt(); + + m_nLastPedState = PED_NONE; +#endif + SetPedState(PED_IDLE); + SetMoveState(PEDMOVE_STILL); + } + if (m_nWaitState == WAITSTATE_FALSE) { + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(2000, 4000); + } +} + +void +CPed::Idle(void) +{ + CVehicle *veh = m_pMyVehicle; + if (veh && veh->m_nGettingOutFlags && m_vehDoor) { + + if (veh->m_nGettingOutFlags & GetCarDoorFlag(m_vehDoor)) { + + if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + + CVector doorPos = GetPositionToOpenCarDoor(veh, m_vehDoor); + CVector doorDist = GetPosition() - doorPos; + + if (doorDist.MagnitudeSqr() < sq(0.5f)) { + SetMoveState(PEDMOVE_WALK); + return; + } + } + } + } + + CAnimBlendAssociation *armedIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_BIGGUN); + CAnimBlendAssociation *unarmedIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + int waitTime; + + if (m_nMoveState == PEDMOVE_STILL) { + + eWeaponType curWeapon = GetWeapon()->m_eWeaponType; + if (!armedIdleAssoc || + CTimer::GetTimeInMilliseconds() <= m_nWaitTimer && curWeapon != WEAPONTYPE_UNARMED && curWeapon != WEAPONTYPE_MOLOTOV && curWeapon != WEAPONTYPE_GRENADE) { + + if ((!GetWeapon()->IsType2Handed() || curWeapon == WEAPONTYPE_SHOTGUN) && curWeapon != WEAPONTYPE_BASEBALLBAT + || !unarmedIdleAssoc || unarmedIdleAssoc->blendAmount <= 0.95f || m_nWaitState != WAITSTATE_FALSE || CTimer::GetTimeInMilliseconds() <= m_nWaitTimer) { + + m_moved = CVector2D(0.0f, 0.0f); + return; + } + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_BIGGUN, 3.0f); + waitTime = CGeneral::GetRandomNumberInRange(4000, 7500); + } else { + armedIdleAssoc->blendDelta = -2.0f; + armedIdleAssoc->flags |= ASSOC_DELETEFADEDOUT; + waitTime = CGeneral::GetRandomNumberInRange(3000, 8500); + } + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + waitTime; + } else { + if (armedIdleAssoc) { + armedIdleAssoc->blendDelta = -8.0f; + armedIdleAssoc->flags |= ASSOC_DELETEFADEDOUT; + m_nWaitTimer = 0; + } + if (!IsPlayer()) + SetMoveState(PEDMOVE_STILL); + } + m_moved = CVector2D(0.0f, 0.0f); +} + +void +CPed::ClearPause(void) +{ + RestorePreviousState(); +} + +void +CPed::Pause(void) +{ + m_moved = CVector2D(0.0f, 0.0f); + if (CTimer::GetTimeInMilliseconds() > m_leaveCarTimer) + ClearPause(); +} + +void +CPed::SetFall(int extraTime, AnimationId animId, uint8 evenIfNotInControl) +{ + if (!IsPedInControl() && (!evenIfNotInControl || DyingOrDead())) + return; + + ClearLookFlag(); + ClearAimFlag(); + SetStoredState(); + SetPedState(PED_FALL); + CAnimBlendAssociation *fallAssoc = RpAnimBlendClumpGetAssociation(GetClump(), animId); + + if (fallAssoc) { + fallAssoc->SetCurrentTime(0.0f); + fallAssoc->blendAmount = 0.0f; + fallAssoc->blendDelta = 8.0f; + fallAssoc->SetRun(); + } else { + fallAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animId, 8.0f); + } + + if (extraTime == -1) { + m_getUpTimer = UINT32_MAX; + } else if (fallAssoc) { + if (IsPlayer()) { + m_getUpTimer = 1000.0f * fallAssoc->hierarchy->totalLength + + CTimer::GetTimeInMilliseconds() + + 500.0f; + } else { + m_getUpTimer = 1000.0f * fallAssoc->hierarchy->totalLength + + CTimer::GetTimeInMilliseconds() + + extraTime + + ((m_randomSeed + CTimer::GetFrameCounter()) % 1000); + } + } else { + m_getUpTimer = extraTime + + CTimer::GetTimeInMilliseconds() + + 1000 + + ((m_randomSeed + CTimer::GetFrameCounter()) % 1000); + } + bFallenDown = true; +} + +void +CPed::ClearFall(void) +{ + SetGetUp(); +} + +void +CPed::Fall(void) +{ + if (m_getUpTimer != UINT32_MAX && CTimer::GetTimeInMilliseconds() > m_getUpTimer +#ifdef VC_PED_PORTS + && bIsStanding +#endif + ) + ClearFall(); + + // VC plays animations ANIM_STD_FALL_ONBACK and ANIM_STD_FALL_ONFRONT in here, which doesn't exist in III. +} + +bool +CPed::CheckIfInTheAir(void) +{ + if (bInVehicle) + return false; + + CVector pos = GetPosition(); + CColPoint foundColPoint; + CEntity *foundEntity; + + float startZ = pos.z - 1.54f; + bool foundGround = CWorld::ProcessVerticalLine(pos, startZ, foundColPoint, foundEntity, true, true, false, true, false, false, nil); + if (!foundGround && m_nPedState != PED_JUMP) + { + pos.z -= FEET_OFFSET; + if (CWorld::TestSphereAgainstWorld(pos, 0.15f, this, true, false, false, false, false, false)) + foundGround = true; + } + return !foundGround; +} + +void +CPed::SetInTheAir(void) +{ + if (bIsInTheAir) + return; + + bIsInTheAir = true; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FALL_GLIDE, 4.0f); + + if (m_nPedState == PED_ATTACK) { + ClearAttack(); + ClearPointGunAt(); + } else if (m_nPedState == PED_FIGHT) { + EndFight(ENDFIGHT_FAST); + } + +} + +void +CPed::InTheAir(void) +{ + CColPoint foundCol; + CEntity *foundEnt; + + CVector ourPos = GetPosition(); + CVector bitBelow = GetPosition(); + bitBelow.z -= 4.04f; + + if (m_vecMoveSpeed.z < 0.0f && !bIsPedDieAnimPlaying) { + if (!DyingOrDead()) { + if (CWorld::ProcessLineOfSight(ourPos, bitBelow, foundCol, foundEnt, true, true, false, true, false, false, false)) { + if (GetPosition().z - foundCol.point.z < 1.3f +#ifdef VC_PED_PORTS + || bIsStanding +#endif + ) + SetLanding(); + } else { + if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL)) { + if (m_vecMoveSpeed.z < -0.1f) + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FALL, 4.0f); + } + } + } + } +} + +void +CPed::SetLanding(void) +{ + if (DyingOrDead()) + return; + + CAnimBlendAssociation *fallAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL); + CAnimBlendAssociation *landAssoc; + + RpAnimBlendClumpSetBlendDeltas(GetClump(), ASSOC_PARTIAL, -1000.0f); + if (fallAssoc) { + landAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FALL_COLLAPSE); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FALL_COLLAPSE, 1.0f); + + if (IsPlayer()) + Say(SOUND_PED_LAND); + + } else { + landAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FALL_LAND); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FALL_LAND, 1.0f); + } + + landAssoc->SetFinishCallback(PedLandCB, this); + bIsInTheAir = false; + bIsLanding = true; +} + +void +CPed::SetGetUp(void) +{ + if (m_nPedState == PED_GETUP && bGetUpAnimStarted) + return; + + if (!CanSetPedState()) + return; + + if (m_fHealth >= 1.0f || IsPedHeadAbovePos(-0.3f)) { + if (bUpdateAnimHeading) { + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + m_fRotationCur -= HALFPI; + bUpdateAnimHeading = false; + } + if (m_nPedState != PED_GETUP) { + SetStoredState(); + SetPedState(PED_GETUP); + } + + CVehicle *collidingVeh = (CVehicle*)m_pCollidingEntity; + CVehicle *veh = (CVehicle*)CPedPlacement::IsPositionClearOfCars(&GetPosition()); + if (veh && veh->m_vehType != VEHICLE_TYPE_BIKE || + collidingVeh && collidingVeh->IsVehicle() && collidingVeh->m_vehType != VEHICLE_TYPE_BIKE + && ((uint8)(CTimer::GetFrameCounter() + m_randomSeed + 5) % 8 || + CCollision::ProcessColModels(GetMatrix(), *GetColModel(), collidingVeh->GetMatrix(), *collidingVeh->GetColModel(), + aTempPedColPts, nil, nil) > 0)) { + + bGetUpAnimStarted = false; + if (IsPlayer()) + InflictDamage(nil, WEAPONTYPE_RUNOVERBYCAR, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + else { + if (!CPad::GetPad(0)->ArePlayerControlsDisabled()) + return; + + InflictDamage(nil, WEAPONTYPE_RUNOVERBYCAR, 1000.0f, PEDPIECE_TORSO, 0); + } + return; + } + bGetUpAnimStarted = true; + m_pCollidingEntity = nil; + bKnockedUpIntoAir = false; + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNFAST); + if (animAssoc) { + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUN)) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_RUN, 8.0f); + } else { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 8.0f); + } + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + + if (RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL)) + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GET_UP_FRONT, 1000.0f); + else + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GET_UP, 1000.0f); + + animAssoc->SetFinishCallback(PedGetupCB,this); + } else { + m_fHealth = 0.0f; + SetDie(ANIM_STD_NUM, 4.0f, 0.0f); + } +} + +void +CPed::Mug(void) +{ + if (m_pSeekTarget && m_pSeekTarget->IsPed()) { + + if (CTimer::GetTimeInMilliseconds() <= m_attackTimer - 2000) { + if ((m_pSeekTarget->GetPosition() - GetPosition()).Magnitude() > 3.0f) + m_wepSkills = 50; + + Say(SOUND_PED_MUGGING); + ((CPed*)m_pSeekTarget)->Say(SOUND_PED_ROBBED); + } else { + SetWanderPath(CGeneral::GetRandomNumber() & 7); + SetFlee(m_pSeekTarget, 20000); + } + + } else { + SetIdle(); + } +} + +void +CPed::SetLookTimer(int time) +{ + if (CTimer::GetTimeInMilliseconds() > m_lookTimer) { + m_lookTimer = CTimer::GetTimeInMilliseconds() + time; + } +} + +void +CPed::SetAttackTimer(uint32 time) +{ + if (CTimer::GetTimeInMilliseconds() > m_attackTimer) + m_attackTimer = Max(m_shootTimer, CTimer::GetTimeInMilliseconds()) + time; +} + +void +CPed::SetShootTimer(uint32 time) +{ + if (CTimer::GetTimeInMilliseconds() > m_shootTimer) { + m_shootTimer = CTimer::GetTimeInMilliseconds() + time; + } +} + +void +CPed::ClearLook(void) +{ + RestorePreviousState(); + ClearLookFlag(); +} + +void +CPed::Look(void) +{ + // UNUSED: This is a perfectly empty function. +} + +bool +CPed::TurnBody(void) +{ + bool turnDone = true; + + if (m_pLookTarget) + m_fLookDirection = CGeneral::GetRadianAngleBetweenPoints( + m_pLookTarget->GetPosition().x, + m_pLookTarget->GetPosition().y, + GetPosition().x, + GetPosition().y); + + float limitedLookDir = CGeneral::LimitRadianAngle(m_fLookDirection); + float currentRot = m_fRotationCur; + + if (currentRot - PI > limitedLookDir) + limitedLookDir += 2 * PI; + else if (PI + currentRot < limitedLookDir) + limitedLookDir -= 2 * PI; + + float neededTurn = currentRot - limitedLookDir; + m_fRotationDest = limitedLookDir; + + if (Abs(neededTurn) > 0.05f) { + turnDone = false; + currentRot -= neededTurn * 0.2f; + } + + m_fRotationCur = currentRot; + m_fLookDirection = limitedLookDir; + return turnDone; +} + +void +CPed::SetSeek(CVector pos, float distanceToCountDone) +{ + if (!IsPedInControl() + || (m_nPedState == PED_SEEK_POS && m_vecSeekPos.x == pos.x && m_vecSeekPos.y == pos.y)) + return; + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_M16 + || GetWeapon()->m_eWeaponType == WEAPONTYPE_AK47 + || GetWeapon()->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE + || GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER + || GetWeapon()->m_eWeaponType == WEAPONTYPE_SHOTGUN) { + ClearPointGunAt(); + } + + if (m_nPedState != PED_SEEK_POS) + SetStoredState(); + + SetPedState(PED_SEEK_POS); + m_distanceToCountSeekDone = distanceToCountDone; + m_vecSeekPos = pos; +} + +void +CPed::SetSeek(CEntity *seeking, float distanceToCountDone) +{ + if (!IsPedInControl()) + return; + + if (m_nPedState == PED_SEEK_ENTITY && m_pSeekTarget == seeking) + return; + + if (!seeking) + return; + + if (m_nPedState != PED_SEEK_ENTITY) + SetStoredState(); + + SetPedState(PED_SEEK_ENTITY); + m_distanceToCountSeekDone = distanceToCountDone; + m_pSeekTarget = seeking; + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + SetMoveState(PEDMOVE_STILL); +} + +void +CPed::ClearSeek(void) +{ + SetIdle(); + bRunningToPhone = false; +} + +bool +CPed::Seek(void) +{ + float distanceToCountItDone = m_distanceToCountSeekDone; + eMoveState nextMove = PEDMOVE_NONE; + + if (m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) { + + if (m_nPedState != PED_EXIT_TRAIN && m_nPedState != PED_ENTER_TRAIN && m_nPedState != PED_SEEK_IN_BOAT && + m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER && m_objective != OBJECTIVE_SOLICIT_VEHICLE && !bDuckAndCover) { + + if ((!m_pedInObjective || !m_pedInObjective->bInVehicle) + && !((CTimer::GetFrameCounter() + (m_randomSeed % 256) + 17) & 7)) { + + CEntity *obstacle = CWorld::TestSphereAgainstWorld(m_vecSeekPos, 0.4f, nil, + false, true, false, false, false, false); + + if (obstacle) { + if (!obstacle->IsVehicle() || ((CVehicle*)obstacle)->m_vehType == VEHICLE_TYPE_CAR) { + distanceToCountItDone = 2.5f; + } else { + CVehicleModelInfo *vehModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(obstacle->GetModelIndex()); + float yLength = vehModel->GetColModel()->boundingBox.max.y + - vehModel->GetColModel()->boundingBox.min.y; + distanceToCountItDone = yLength * 0.55f; + } + } + } + } + } + + if (!m_pSeekTarget && m_nPedState == PED_SEEK_ENTITY) + ClearSeek(); + + float seekPosDist = (m_vecSeekPos - GetPosition()).Magnitude2D(); + if (seekPosDist < 2.0f || m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT) { + + if (m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION) { + + if (m_pedInObjective->m_nMoveState != PEDMOVE_STILL) + nextMove = m_pedInObjective->m_nMoveState; + } else + nextMove = PEDMOVE_WALK; + + } else if (m_objective != OBJECTIVE_FOLLOW_CHAR_IN_FORMATION) { + + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS || m_objective == OBJECTIVE_RUN_TO_AREA || bIsRunning) + nextMove = PEDMOVE_RUN; + else + nextMove = PEDMOVE_WALK; + + } else if (seekPosDist <= 2.0f) { + + if (m_pedInObjective->m_nMoveState != PEDMOVE_STILL) + nextMove = m_pedInObjective->m_nMoveState; + + } else { + nextMove = PEDMOVE_RUN; + } + + if (m_nPedState == PED_SEEK_ENTITY) { + if (m_pSeekTarget->IsPed()) { + if (((CPed*)m_pSeekTarget)->bInVehicle) + distanceToCountItDone += 2.0f; + } + } + + if (seekPosDist >= distanceToCountItDone) { + if (bIsRunning) + nextMove = PEDMOVE_RUN; + + if (CTimer::GetTimeInMilliseconds() <= m_nPedStateTimer) { + + if (m_actionX != 0.0f && m_actionY != 0.0f) { + + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + m_actionX, m_actionY, + GetPosition().x, GetPosition().y); + + float neededTurn = Abs(m_fRotationDest - m_fRotationCur); + + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + if (neededTurn > HALFPI) { + if (seekPosDist >= 1.0f) { + if (seekPosDist < 2.0f) { + if (bIsRunning) + nextMove = PEDMOVE_RUN; + else + nextMove = PEDMOVE_WALK; + } + } else { + nextMove = PEDMOVE_STILL; + } + } + + CVector2D moveDist(GetPosition().x - m_actionX, GetPosition().y - m_actionY); + if (moveDist.Magnitude() < 0.5f) { + m_nPedStateTimer = 0; + m_actionX = 0; + m_actionY = 0; + } + } + } else { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + m_vecSeekPos.x, m_vecSeekPos.y, + GetPosition().x, GetPosition().y); + + float neededTurn = Abs(m_fRotationDest - m_fRotationCur); + + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + if (neededTurn > HALFPI) { + if (seekPosDist >= 1.0 && neededTurn <= DEGTORAD(135.0f)) { + if (seekPosDist < 2.0f) + nextMove = PEDMOVE_WALK; + } else { + nextMove = PEDMOVE_STILL; + } + } + } + + if (((m_nPedState == PED_FLEE_POS || m_nPedState == PED_FLEE_ENTITY) && m_nMoveState < nextMove) + || (m_nPedState != PED_FLEE_POS && m_nPedState != PED_FLEE_ENTITY && m_objective != OBJECTIVE_GOTO_CHAR_ON_FOOT && m_nWaitState == WAITSTATE_FALSE)) { + + SetMoveState(nextMove); + } + + SetMoveAnim(); + return false; + } + + if ((m_objective != OBJECTIVE_FOLLOW_CHAR_IN_FORMATION || m_pedInObjective->m_nMoveState == PEDMOVE_STILL) && m_nMoveState != PEDMOVE_STILL) { + m_nPedStateTimer = 0; + m_actionX = 0; + m_actionY = 0; + } + + if (m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA || m_objective == OBJECTIVE_GOTO_AREA_ANY_MEANS) { + if (m_pNextPathNode) + m_pNextPathNode = nil; + else + bScriptObjectiveCompleted = true; + + bUsePedNodeSeek = true; + } + + if (SeekFollowingPath(nil)) + m_nCurPathNode++; + + return true; +} + +void +CPed::SetFlee(CVector2D const &from, int time) +{ + if (CTimer::GetTimeInMilliseconds() < m_nPedStateTimer || !IsPedInControl() || bKindaStayInSamePlace) + return; + + if (m_nPedState != PED_FLEE_ENTITY) { + SetStoredState(); + SetPedState(PED_FLEE_POS); + SetMoveState(PEDMOVE_RUN); + m_fleeFromPosX = from.x; + m_fleeFromPosY = from.y; + } + + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + m_fleeTimer = CTimer::GetTimeInMilliseconds() + time; + + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + GetPosition().x, GetPosition().y, + from.x, from.y); + + m_fRotationDest = CGeneral::LimitRadianAngle(angleToFace); + if (m_fRotationCur - PI > m_fRotationDest) { + m_fRotationDest += 2 * PI; + } else if (PI + m_fRotationCur < m_fRotationDest) { + m_fRotationDest -= 2 * PI; + } +} + +void +CPed::SetFlee(CEntity *fleeFrom, int time) +{ + if (!IsPedInControl() || bKindaStayInSamePlace || !fleeFrom) + return; + + SetStoredState(); + SetPedState(PED_FLEE_ENTITY); + bUsePedNodeSeek = true; + SetMoveState(PEDMOVE_RUN); + m_fleeFrom = fleeFrom; + m_fleeFrom->RegisterReference((CEntity **) &m_fleeFrom); + + if (time <= 0) + m_fleeTimer = 0; + else + m_fleeTimer = CTimer::GetTimeInMilliseconds() + time; + + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + GetPosition().x, GetPosition().y, + fleeFrom->GetPosition().x, fleeFrom->GetPosition().y); + + m_fRotationDest = CGeneral::LimitRadianAngle(angleToFace); + if (m_fRotationCur - PI > m_fRotationDest) { + m_fRotationDest += 2 * PI; + } else if (PI + m_fRotationCur < m_fRotationDest) { + m_fRotationDest -= 2 * PI; + } +} + +void +CPed::ClearFlee(void) +{ + RestorePreviousState(); + bUsePedNodeSeek = false; + m_chatTimer = 0; + m_fleeTimer = 0; +} + +void +CPed::Flee(void) +{ + if (CTimer::GetTimeInMilliseconds() > m_fleeTimer && m_fleeTimer) { + bool mayFinishFleeing = true; + if (m_nPedState == PED_FLEE_ENTITY) { + if ((CVector2D(GetPosition()) - ms_vec2DFleePosition).MagnitudeSqr() < sq(30.0f)) + mayFinishFleeing = false; + } + + if (mayFinishFleeing) { + eMoveState moveState = m_nMoveState; + ClearFlee(); + + if (m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE || m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS) + RestorePreviousObjective(); + + if ((m_nPedState == PED_IDLE || m_nPedState == PED_WANDER_PATH) && CGeneral::GetRandomNumber() & 1) { + SetWaitState(moveState <= PEDMOVE_WALK ? WAITSTATE_CROSS_ROAD_LOOK : WAITSTATE_FINISH_FLEE, nil); + } + return; + } + m_fleeTimer = CTimer::GetTimeInMilliseconds() + 5000; + } + + if (bUsePedNodeSeek) { + CPathNode *realLastNode = nil; + uint8 nextDirection = 0; + uint8 curDirectionShouldBe = 9; // means not defined yet + + if (m_nPedStateTimer < CTimer::GetTimeInMilliseconds() + && m_collidingThingTimer < CTimer::GetTimeInMilliseconds()) { + + if (m_pNextPathNode && CTimer::GetTimeInMilliseconds() > m_chatTimer) { + + curDirectionShouldBe = CGeneral::GetNodeHeadingFromVector(GetPosition().x - ms_vec2DFleePosition.x, GetPosition().y - ms_vec2DFleePosition.y); + if (m_nPathDir < curDirectionShouldBe) + m_nPathDir += 8; + + int dirDiff = m_nPathDir - curDirectionShouldBe; + if (dirDiff > 2 && dirDiff < 6) { + realLastNode = nil; + m_pLastPathNode = m_pNextPathNode; + m_pNextPathNode = nil; + } + } + + if (m_pNextPathNode) { + m_vecSeekPos = m_pNextPathNode->GetPosition(); + if (m_nMoveState == PEDMOVE_RUN) + bIsRunning = true; + + eMoveState moveState = m_nMoveState; + if (Seek()) { + realLastNode = m_pLastPathNode; + m_pLastPathNode = m_pNextPathNode; + m_pNextPathNode = nil; + } + bIsRunning = false; + SetMoveState(moveState); + } + } + + if (!m_pNextPathNode) { + if (curDirectionShouldBe == 9) { + curDirectionShouldBe = CGeneral::GetNodeHeadingFromVector(GetPosition().x - ms_vec2DFleePosition.x, GetPosition().y - ms_vec2DFleePosition.y); + } + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, + curDirectionShouldBe, + &nextDirection); + + if (curDirectionShouldBe < nextDirection) + curDirectionShouldBe += 8; + + if (m_pNextPathNode && m_pNextPathNode != realLastNode && m_pNextPathNode != m_pLastPathNode && curDirectionShouldBe - nextDirection != 4) { + m_nPathDir = nextDirection; + m_chatTimer = CTimer::GetTimeInMilliseconds() + 2000; + } else { + bUsePedNodeSeek = false; + SetMoveState(PEDMOVE_RUN); + Flee(); + } + } + return; + } + + if ((m_nPedState == PED_FLEE_ENTITY || m_nPedState == PED_ON_FIRE) && m_nPedStateTimer < CTimer::GetTimeInMilliseconds()) { + + float angleToFleeFromPos = CGeneral::GetRadianAngleBetweenPoints( + GetPosition().x, + GetPosition().y, + ms_vec2DFleePosition.x, + ms_vec2DFleePosition.y); + + m_fRotationDest = CGeneral::LimitRadianAngle(angleToFleeFromPos); + + if (m_fRotationCur - PI > m_fRotationDest) + m_fRotationDest += TWOPI; + else if (PI + m_fRotationCur < m_fRotationDest) + m_fRotationDest -= TWOPI; + } + + if (CTimer::GetTimeInMilliseconds() & 0x20) { + //CVector forwardPos = GetPosition(); + CMatrix forwardMat(GetMatrix()); + forwardMat.GetPosition() += Multiply3x3(forwardMat, CVector(0.0f, 4.0f, 0.0f)); + CVector forwardPos = forwardMat.GetPosition(); + + CEntity *foundEnt; + CColPoint foundCol; + bool found = CWorld::ProcessVerticalLine(forwardPos, forwardMat.GetPosition().z - 100.0f, foundCol, foundEnt, 1, 0, 0, 0, 1, 0, 0); + + if (!found || Abs(forwardPos.z - forwardMat.GetPosition().z) > 1.0f) { + m_fRotationDest += DEGTORAD(112.5f); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 2000; + } + } + + if (CTimer::GetTimeInMilliseconds() >= m_collidingThingTimer) + return; + + if (!m_collidingEntityWhileFleeing) + return; + + double collidingThingPriorityMult = (double)(m_collidingThingTimer - CTimer::GetTimeInMilliseconds()) * 2.0 / 2500; + + if (collidingThingPriorityMult <= 1.5) { + + double angleToFleeEntity = CGeneral::GetRadianAngleBetweenPoints( + GetPosition().x, + GetPosition().y, + m_collidingEntityWhileFleeing->GetPosition().x, + m_collidingEntityWhileFleeing->GetPosition().y); + angleToFleeEntity = CGeneral::LimitRadianAngle(angleToFleeEntity); + + double angleToFleeCollidingThing = CGeneral::GetRadianAngleBetweenPoints( + m_vecDamageNormal.x, + m_vecDamageNormal.y, + 0.0f, + 0.0f); + angleToFleeCollidingThing = CGeneral::LimitRadianAngle(angleToFleeCollidingThing); + + if (angleToFleeEntity - PI > angleToFleeCollidingThing) + angleToFleeCollidingThing += TWOPI; + else if (PI + angleToFleeEntity < angleToFleeCollidingThing) + angleToFleeCollidingThing -= TWOPI; + + if (collidingThingPriorityMult <= 1.0f) { + // Range [0.0, 1.0] + + float angleToFleeBoth = (angleToFleeCollidingThing + angleToFleeEntity) * 0.5f; + + if (m_fRotationDest - PI > angleToFleeBoth) + angleToFleeBoth += TWOPI; + else if (PI + m_fRotationDest < angleToFleeBoth) + angleToFleeBoth -= TWOPI; + + m_fRotationDest = (1.0f - collidingThingPriorityMult) * m_fRotationDest + collidingThingPriorityMult * angleToFleeBoth; + } else { + // Range (1.0, 1.5] + + double adjustedMult = (collidingThingPriorityMult - 1.0f) * 2.0f; + m_fRotationDest = angleToFleeEntity * (1.0 - adjustedMult) + adjustedMult * angleToFleeCollidingThing; + } + } else { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + m_vecDamageNormal.x, + m_vecDamageNormal.y, + 0.0f, + 0.0f); + m_fRotationDest = CGeneral::LimitRadianAngle(m_fRotationDest); + } + + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + + if (m_fRotationCur - PI > m_fRotationDest) + m_fRotationDest += TWOPI; + else if (PI + m_fRotationCur < m_fRotationDest) + m_fRotationDest -= TWOPI; + +} + +// "Wander range" state is unused in game, and you can't use it without SetWanderRange anyway +void +CPed::WanderRange(void) +{ + bool arrived = Seek(); + if (arrived) { + Idle(); + if ((m_randomSeed + 3 * CTimer::GetFrameCounter()) % 1000 > 997) { + CVector2D newCoords2D = m_wanderRangeBounds->GetRandomPointInRange(); + SetSeek(CVector(newCoords2D.x, newCoords2D.y, GetPosition().z), 2.5f); + } + } +} + +bool +CPed::SetWanderPath(int8 pathStateDest) +{ + uint8 nextPathState; + + if (IsPedInControl()) { + if (bKindaStayInSamePlace) { + SetIdle(); + return false; + } else { + m_nPathDir = pathStateDest; + if (pathStateDest == 0) + pathStateDest = CGeneral::GetRandomNumberInRange(1, 7); + + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, + m_nPathDir, &nextPathState); + + // Circular loop until we find a node for current m_nPathDir + while (!m_pNextPathNode) { + m_nPathDir = (m_nPathDir+1) % 8; + + // We're at where we started and couldn't find any node + if (m_nPathDir == pathStateDest) { + ClearAll(); + SetIdle(); + return false; + } + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, + m_nPathDir, &nextPathState); + } + + // We did it, save next path state and return true + m_nPathDir = nextPathState; + SetPedState(PED_WANDER_PATH); + SetMoveState(PEDMOVE_WALK); + bIsRunning = false; + return true; + } + } else { + m_nPathDir = pathStateDest; + bStartWanderPathOnFoot = true; + return false; + } +} + +void +CPed::WanderPath(void) +{ + if (!m_pNextPathNode) { + printf("THIS SHOULDN@T HAPPEN TOO OFTEN\n"); + SetIdle(); + return; + } + if (m_nWaitState == WAITSTATE_FALSE) { + if (m_nMoveState == PEDMOVE_STILL || m_nMoveState == PEDMOVE_NONE) + SetMoveState(PEDMOVE_WALK); + } + m_vecSeekPos = m_pNextPathNode->GetPosition(); + m_vecSeekPos.z += 1.0f; + + // Only returns true when ped is stuck(not stopped) I think, then we should assign new direction or wait state to him. + if (!Seek()) + return; + + CPathNode *previousLastNode = m_pLastPathNode; + uint8 randVal = (m_randomSeed + 3 * CTimer::GetFrameCounter()) % 100; + + // We don't prefer 180-degree turns in normal situations + uint8 dirWeWouldntPrefer = m_nPathDir; + if (dirWeWouldntPrefer <= 3) + dirWeWouldntPrefer += 4; + else + dirWeWouldntPrefer -= 4; + + CPathNode *nodeWeWouldntPrefer = nil; + uint8 dirToSet = 9; // means undefined + uint8 dirWeWouldntPrefer2 = 9; // means undefined + if (randVal <= 90) { + if (randVal > 80) { + m_nPathDir += 2; + m_nPathDir %= 8; + } + } else { + m_nPathDir -= 2; + if (m_nPathDir < 0) + m_nPathDir += 8; + } + + m_pLastPathNode = m_pNextPathNode; + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, + m_nPathDir, &dirToSet); + + uint8 tryCount = 0; + + // NB: SetWanderPath checks for m_nPathDir == dirToStartWith, this one checks for tryCount > 7 + while (!m_pNextPathNode) { + tryCount++; + m_nPathDir = (m_nPathDir + 1) % 8; + + // We're at where we started and couldn't find any node + if (tryCount > 7) { + if (!nodeWeWouldntPrefer) { + ClearAll(); + SetIdle(); + // Probably this text carried over here after copy-pasting this loop from early version of SetWanderPath. + Error("Can't find valid path node, SetWanderPath, Ped.cpp"); + return; + } + m_pNextPathNode = nodeWeWouldntPrefer; + dirToSet = dirWeWouldntPrefer2; + } else { + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, + m_nPathDir, &dirToSet); + if (m_pNextPathNode) { + if (dirToSet == dirWeWouldntPrefer) { + nodeWeWouldntPrefer = m_pNextPathNode; + dirWeWouldntPrefer2 = dirToSet; + m_pNextPathNode = nil; + } + } + } + } + + m_nPathDir = dirToSet; + if (m_pLastPathNode == m_pNextPathNode) { + m_pNextPathNode = previousLastNode; + SetWaitState(WAITSTATE_DOUBLEBACK, nil); + Say(SOUND_PED_WAIT_DOUBLEBACK); + } else if (ThePaths.TestForPedTrafficLight(m_pLastPathNode, m_pNextPathNode)) { + SetWaitState(WAITSTATE_TRAFFIC_LIGHTS, nil); + } else if (ThePaths.TestCrossesRoad(m_pLastPathNode, m_pNextPathNode)) { + SetWaitState(WAITSTATE_CROSS_ROAD, nil); + } else if (m_pNextPathNode == previousLastNode) { + SetWaitState(WAITSTATE_DOUBLEBACK, nil); + Say(SOUND_PED_WAIT_DOUBLEBACK); + } +} + +void +CPed::Avoid(void) +{ + CPed *nearestPed; + + if(m_pedStats->m_temper > m_pedStats->m_fear && m_pedStats->m_temper > 50) + return; + + if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { + + if (m_nMoveState != PEDMOVE_NONE && m_nMoveState != PEDMOVE_STILL) { + nearestPed = m_nearPeds[0]; + + if (nearestPed && nearestPed->m_nPedState != PED_DEAD && nearestPed != m_pSeekTarget && nearestPed != m_pedInObjective) { + + // Check if this ped wants to avoid the nearest one + if (CPedType::GetAvoid(m_nPedType) & CPedType::GetFlag(nearestPed->m_nPedType)) { + + // Further codes checks whether the distance between us and ped will be equal or below 1.0, if we walk up to him by 1.25 meters. + // If so, we want to avoid it, so we turn our body 45 degree and look to somewhere else. + + // Game converts from radians to degress and back again here, doesn't make much sense + CVector2D forward(-Sin(m_fRotationCur), Cos(m_fRotationCur)); + forward.Normalise(); // this is kinda pointless + + // Move forward 1.25 meters + CVector2D testPosition = CVector2D(GetPosition()) + forward*1.25f; + + // Get distance to ped we want to avoid + CVector2D distToPed = CVector2D(nearestPed->GetPosition()) - testPosition; + + if (distToPed.Magnitude() <= 1.0f && OurPedCanSeeThisOne((CEntity*)nearestPed)) { + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + + 500 + (m_randomSeed + 3 * CTimer::GetFrameCounter()) + % 1000 / 5; + + m_fRotationDest += DEGTORAD(45.0f); + if (!bIsLooking) { + SetLookFlag(nearestPed, false); + SetLookTimer(CGeneral::GetRandomNumberInRange(500, 800)); + } + } + } + } + } + } +} + +bool +CPed::SeekFollowingPath(CVector *unused) +{ + return m_nCurPathNode <= m_nPathNodes && m_nPathNodes; +} + +bool +CPed::SetFollowPath(CVector dest) +{ + if (m_nPedState == PED_FOLLOW_PATH) + return false; + + if (FindPlayerPed() != this) + return false; + + if ((dest - GetPosition()).Magnitude() <= 2.0f) + return false; + + CVector pointPoses[7]; + int16 pointsFound; + CPedPath::CalcPedRoute(0, GetPosition(), dest, pointPoses, &pointsFound, 7); + for(int i = 0; i < pointsFound; i++) { + m_stPathNodeStates[i].x = pointPoses[i].x; + m_stPathNodeStates[i].y = pointPoses[i].y; + } + + m_nCurPathNode = 0; + m_nPathNodes = pointsFound; + if (m_nPathNodes < 1) + return false; + + SetStoredState(); + SetPedState(PED_FOLLOW_PATH); + SetMoveState(PEDMOVE_WALK); + return true; +} + +void +CPed::FollowPath(void) +{ + m_vecSeekPos.x = m_stPathNodeStates[m_nCurPathNode].x; + m_vecSeekPos.y = m_stPathNodeStates[m_nCurPathNode].y; + m_vecSeekPos.z = GetPosition().z; + + // Mysterious code +/* int v4 = 0; + int maxNodeIndex = m_nPathNodes - 1; + if (maxNodeIndex > 0) { + if (maxNodeIndex > 8) { + while (v4 < maxNodeIndex - 8) + v4 += 8; + } + + while (v4 < maxNodeIndex) + v4++; + + } +*/ + if (Seek()) { + m_nCurPathNode++; + if (m_nCurPathNode == m_nPathNodes) + RestorePreviousState(); + } +} + +void +CPed::SetEvasiveStep(CPhysical *reason, uint8 animType) +{ + AnimationId stepAnim; + + if (m_nPedState == PED_STEP_AWAY || !IsPedInControl() || ((IsPlayer() || !bRespondsToThreats) && animType == 0)) + return; + + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + reason->GetPosition().x, reason->GetPosition().y, + GetPosition().x, GetPosition().y); + angleToFace = CGeneral::LimitRadianAngle(angleToFace); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + float neededTurn = Abs(angleToFace - m_fRotationCur); + bool vehPressedHorn = false; + + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + if (reason->IsVehicle() && ((CVehicle*)reason)->IsCar()) { + CVehicle *veh = (CVehicle*)reason; + if (veh->m_nCarHornTimer != 0) { + vehPressedHorn = true; + if (!IsPlayer()) + animType = 1; + } + } + + if (neededTurn <= DEGTORAD(90.0f) || reason->GetModelIndex() == MI_RCBANDIT || vehPressedHorn || animType != 0) { + SetLookFlag(reason, true); + if ((CGeneral::GetRandomNumber() & 1) && reason->GetModelIndex() != MI_RCBANDIT && animType == 0) { + stepAnim = ANIM_STD_HAILTAXI; + } else { + + float dangerDirection = CGeneral::GetRadianAngleBetweenPoints( + reason->m_vecMoveSpeed.x, reason->m_vecMoveSpeed.y, + 0.0f, 0.0f); + + // Let's turn our back to the "reason" + angleToFace += PI; + + if (angleToFace > PI) + angleToFace -= TWOPI; + + // We don't want to run towards car's direction + float dangerZone = angleToFace - dangerDirection; + dangerZone = CGeneral::LimitRadianAngle(dangerZone); + + // So, add or subtract 90deg (jump to left/right) according to that + if (dangerZone > 0.0f) + angleToFace = dangerDirection - HALFPI; + else + angleToFace = dangerDirection + HALFPI; + + stepAnim = ANIM_STD_NUM; + if (animType == 0 || animType == 1) + stepAnim = ANIM_STD_EVADE_STEP; + else if (animType == 2) + stepAnim = ANIM_STD_HANDSCOWER; + } + if (!RpAnimBlendClumpGetAssociation(GetClump(), stepAnim)) { + CAnimBlendAssociation *stepAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, stepAnim, 8.0f); + stepAssoc->flags &= ~ASSOC_DELETEFADEDOUT; + stepAssoc->SetFinishCallback(PedEvadeCB, this); + + if (animType == 0) + Say(SOUND_PED_EVADE); + + m_fRotationCur = CGeneral::LimitRadianAngle(angleToFace); + ClearAimFlag(); + SetStoredState(); + SetPedState(PED_STEP_AWAY); + } + } +} + +void +CPed::SetEvasiveDive(CPhysical *reason, uint8 onlyRandomJump) +{ + if (!IsPedInControl() || !bRespondsToThreats) + return; + + CAnimBlendAssociation *animAssoc; + float angleToFace, neededTurn; + bool handsUp = false; + + angleToFace = m_fRotationCur; + CVehicle *veh = (CVehicle*) reason; + if (reason->IsVehicle() && veh->m_vehType == VEHICLE_TYPE_CAR && veh->m_nCarHornTimer != 0 && !IsPlayer()) { + onlyRandomJump = true; + } + + if (onlyRandomJump) { + if (reason) { + // Simple version of my bug fix below. Doesn't calculate "danger zone", selects jump direction randomly. + // Also doesn't include random hands up, sound etc. Only used on player ped and peds running from gun shots. + + float vehDirection = CGeneral::GetRadianAngleBetweenPoints( + veh->m_vecMoveSpeed.x, veh->m_vecMoveSpeed.y, + 0.0f, 0.0f); + angleToFace = (CGeneral::GetRandomNumber() & 1) * PI + (-0.5f*PI) + vehDirection; + angleToFace = CGeneral::LimitRadianAngle(angleToFace); + } + } else { + if (IsPlayer()) { + ((CPlayerPed*)this)->m_nEvadeAmount = 5; + ((CPlayerPed*)this)->m_pEvadingFrom = reason; + reason->RegisterReference((CEntity**) &((CPlayerPed*)this)->m_pEvadingFrom); + return; + } + + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + reason->GetPosition().x, reason->GetPosition().y, + GetPosition().x, GetPosition().y); + angleToFace = CGeneral::LimitRadianAngle(angleToFace); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + + // FIX: Peds no more dive into cars. Taken from SetEvasiveStep, last if statement inverted +#ifdef FIX_BUGS + float vehDirection = CGeneral::GetRadianAngleBetweenPoints( + veh->m_vecMoveSpeed.x, veh->m_vecMoveSpeed.y, + 0.0f, 0.0f); + + // Let's turn our back to the "reason" + angleToFace += PI; + + if (angleToFace > PI) + angleToFace -= 2 * PI; + + // We don't want to dive towards car's direction + float dangerZone = angleToFace - vehDirection; + dangerZone = CGeneral::LimitRadianAngle(dangerZone); + + // So, add or subtract 90deg (jump to left/right) according to that + if (dangerZone > 0.0f) + angleToFace = 0.5f * PI + vehDirection; + else + angleToFace = vehDirection - 0.5f * PI; +#endif + + neededTurn = Abs(angleToFace - m_fRotationCur); + + if (neededTurn > PI) + neededTurn = 2 * PI - neededTurn; + + if (neededTurn <= 0.5f*PI) { + if (CGeneral::GetRandomNumber() & 1) + handsUp = true; + } else { + if (CGeneral::GetRandomNumber() & 7) + return; + } + Say(SOUND_PED_EVADE); + } + + if (handsUp || !IsPlayer() && m_pedStats->m_flags & STAT_NO_DIVE) { + m_fRotationCur = angleToFace; + ClearLookFlag(); + ClearAimFlag(); + SetLookFlag(reason, true); + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HANDSUP); + if (animAssoc) + return; + + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSUP, 8.0f); + animAssoc->flags &= ~ASSOC_DELETEFADEDOUT; + animAssoc->SetFinishCallback(PedEvadeCB, this); + SetStoredState(); + SetPedState(PED_STEP_AWAY); + } else { + m_fRotationCur = angleToFace; + ClearLookFlag(); + ClearAimFlag(); + SetStoredState(); + SetPedState(PED_DIVE_AWAY); + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_EVADE_DIVE, 8.0f); + animAssoc->SetFinishCallback(PedEvadeCB, this); + } + + if (reason->IsVehicle() && m_nPedType == PEDTYPE_COP) { + if (veh->pDriver && veh->pDriver->IsPlayer()) { + CWanted *wanted = FindPlayerPed()->m_pWanted; + wanted->RegisterCrime_Immediately(CRIME_RECKLESS_DRIVING, GetPosition(), (uintptr)this, false); + wanted->RegisterCrime_Immediately(CRIME_SPEEDING, GetPosition(), (uintptr)this, false); + } + } +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + else if (reason->IsVehicle()) { + if (veh->pDriver && veh->pDriver->IsPlayer()) { + CWanted* wanted = FindPlayerPed()->m_pWanted; + wanted->RegisterCrime(CRIME_RECKLESS_DRIVING, GetPosition(), (uintptr)this, false); + } + } +#endif +} + +void +CPed::PedEvadeCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + if (!animAssoc) { + ped->ClearLookFlag(); + if (ped->m_nPedState == PED_DIVE_AWAY || ped->m_nPedState == PED_STEP_AWAY) + ped->RestorePreviousState(); + + } else if (animAssoc->animId == ANIM_STD_EVADE_DIVE) { + ped->bUpdateAnimHeading = true; + ped->ClearLookFlag(); + if (ped->m_nPedState == PED_DIVE_AWAY) + { + ped->m_getUpTimer = CTimer::GetTimeInMilliseconds() + 1; + ped->SetPedState(PED_FALL); + } + animAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + + } else if (animAssoc->flags & ASSOC_FADEOUTWHENDONE) { + ped->ClearLookFlag(); + if (ped->m_nPedState == PED_DIVE_AWAY || ped->m_nPedState == PED_STEP_AWAY) + ped->RestorePreviousState(); + + } else if (ped->m_nPedState != PED_ARRESTED) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (animAssoc->blendDelta >= 0.0f) + animAssoc->blendDelta = -4.0f; + + ped->ClearLookFlag(); + if (ped->m_nPedState == PED_DIVE_AWAY || ped->m_nPedState == PED_STEP_AWAY) { + ped->RestorePreviousState(); + } + } +} + +void +CPed::SetDie(AnimationId animId, float delta, float speed) +{ + CPlayerPed *player = FindPlayerPed(); + if (player == this) { + if (!player->m_bCanBeDamaged) + return; + } + + m_threatEntity = nil; + if (DyingOrDead()) + return; + + if (m_nPedState == PED_FALL || m_nPedState == PED_GETUP) + delta *= 0.5f; + + SetStoredState(); + ClearAll(); + m_fHealth = 0.0f; + if (m_nPedState == PED_DRIVING) { + if (!IsPlayer()) + FlagToDestroyWhenNextProcessed(); + } else if (bInVehicle) { + if (m_pVehicleAnim) + m_pVehicleAnim->blendDelta = -1000.0f; + } else if (EnteringCar()) { + QuitEnteringCar(); + } + + SetPedState(PED_DIE); + if (animId == ANIM_STD_NUM) { + bIsPedDieAnimPlaying = false; + } else { + CAnimBlendAssociation *dieAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animId, delta); + if (speed > 0.0f) + dieAssoc->speed = speed; + + dieAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + if (dieAssoc->IsRunning()) { + dieAssoc->SetFinishCallback(FinishDieAnimCB, this); + bIsPedDieAnimPlaying = true; + } + } + + Say(SOUND_PED_DEATH); + if (m_nLastPedState == PED_ENTER_CAR || m_nLastPedState == PED_CARJACK) + QuitEnteringCar(); + if (!bInVehicle) + StopNonPartialAnims(); + + m_bloodyFootprintCountOrDeathTime = CTimer::GetTimeInMilliseconds(); +} + +void +CPed::FinishDieAnimCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (ped->bIsPedDieAnimPlaying) + ped->bIsPedDieAnimPlaying = false; +} + +void +CPed::SetDead(void) +{ + bUsesCollision = false; + + m_fHealth = 0.0f; + if (m_nPedState == PED_DRIVING) + bIsVisible = false; + + SetPedState(PED_DEAD); + m_pVehicleAnim = nil; + m_pCollidingEntity = nil; + + CWeaponInfo *weapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(weapon->m_nModelId); + + m_currentWeapon = WEAPONTYPE_UNARMED; + CEventList::RegisterEvent(EVENT_INJURED_PED, EVENT_ENTITY_PED, this, nil, 250); + if (this != FindPlayerPed()) { + CreateDeadPedWeaponPickups(); + CreateDeadPedMoney(); + } + + m_bloodyFootprintCountOrDeathTime = CTimer::GetTimeInMilliseconds(); + m_deadBleeding = false; + bDoBloodyFootprints = false; + bVehExitWillBeInstant = false; + CEventList::RegisterEvent(EVENT_DEAD_PED, EVENT_ENTITY_PED, this, nil, 1000); +} + +void +CPed::Die(void) +{ + // UNUSED: This is a perfectly empty function. +} + +void +CPed::SetChat(CEntity *chatWith, uint32 time) +{ + if(m_nPedState != PED_CHAT) + SetStoredState(); + + SetPedState(PED_CHAT); + SetMoveState(PEDMOVE_STILL); +#if defined VC_PED_PORTS || defined FIX_BUGS + m_lookTimer = 0; +#endif + SetLookFlag(chatWith, true); + m_chatTimer = CTimer::GetTimeInMilliseconds() + time; + m_lookTimer = CTimer::GetTimeInMilliseconds() + 3000; +} + +void +CPed::Chat(void) +{ + // We're already looking to our partner + if (bIsLooking && TurnBody()) + ClearLookFlag(); + + if (!m_pLookTarget || !m_pLookTarget->IsPed()) { + ClearChat(); + return; + } + + CPed *partner = (CPed*) m_pLookTarget; + + if (partner->m_nPedState != PED_CHAT) { + ClearChat(); + if (partner->m_pedInObjective) { + if (partner->m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || + partner->m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE) + ReactToAttack(partner->m_pedInObjective); + } + return; + } + if (bIsTalking) { + if (CGeneral::GetRandomNumber() < 512) { + CAnimBlendAssociation *chatAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CHAT); + if (chatAssoc) { + chatAssoc->blendDelta = -4.0f; + chatAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + bIsTalking = false; + } else + Say(SOUND_PED_CHAT); + + } else { + + if (CGeneral::GetRandomNumber() < 20 && !RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_IDLE)) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_XPRESS_SCRATCH, 4.0f); + } + if (!bIsTalking && !RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_IDLE)) { + CAnimBlendAssociation *chatAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CHAT, 4.0f); + float chatTime = CGeneral::GetRandomNumberInRange(0.0f, 3.0f); + chatAssoc->SetCurrentTime(chatTime); + + bIsTalking = true; + Say(SOUND_PED_CHAT); + } + } + if (m_chatTimer && CTimer::GetTimeInMilliseconds() > m_chatTimer) { + ClearChat(); + m_chatTimer = CTimer::GetTimeInMilliseconds() + 30000; + } +} + +void +CPed::ClearChat(void) +{ + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CHAT); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + bIsTalking = false; + ClearLookFlag(); + RestorePreviousState(); +} + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +void +ReportPhonePickUpCB(CAnimBlendAssociation* assoc, void* arg) +{ + CPed* ped = (CPed*)arg; + ped->m_nMoveState = PEDMOVE_STILL; + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE, 8.0f); + + if (assoc->blendAmount > 0.5f && ped) { + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_TALK, 8.0f); + } +} + +void +ReportPhonePutDownCB(CAnimBlendAssociation* assoc, void* arg) +{ + assoc->flags |= ASSOC_DELETEFADEDOUT; + assoc->blendDelta = -1000.0f; + CPed* ped = (CPed*)arg; + + if (ped->m_phoneId != -1 && crimeReporters[ped->m_phoneId] == ped) { + crimeReporters[ped->m_phoneId] = nil; + gPhoneInfo.m_aPhones[ped->m_phoneId].m_nState = PHONE_STATE_FREE; + ped->m_phoneId = -1; + } + + if (assoc->blendAmount > 0.5f) + ped->bUpdateAnimHeading = true; + + ped->SetWanderPath(CGeneral::GetRandomNumber() & 7); +} +#endif + +bool +CPed::FacePhone(void) +{ + // This function was broken since it's left unused early in development. +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + float phoneDir = CGeneral::GetRadianAngleBetweenPoints( + gPhoneInfo.m_aPhones[m_phoneId].m_vecPos.x, gPhoneInfo.m_aPhones[m_phoneId].m_vecPos.y, + GetPosition().x, GetPosition().y); + + if (m_facePhoneStart) { + m_lookTimer = 0; + SetLookFlag(phoneDir, true); + m_lookTimer = CTimer::GetTimeInMilliseconds() + 3000; + m_facePhoneStart = false; + } + + if (bIsLooking && TurnBody()) { + ClearLookFlag(); + SetIdle(); + m_phoneTalkTimer = CTimer::GetTimeInMilliseconds() + 10000; + CAnimBlendAssociation* assoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_IN, 4.0f); + assoc->SetFinishCallback(ReportPhonePickUpCB, this); + return true; + } + + return false; +#else + float currentRot = RADTODEG(m_fRotationCur); + float phoneDir = CGeneral::GetRadianAngleBetweenPoints( + gPhoneInfo.m_aPhones[m_phoneId].m_vecPos.x, + gPhoneInfo.m_aPhones[m_phoneId].m_vecPos.y, + GetPosition().x, + GetPosition().y); + + SetLookFlag(phoneDir, false); + phoneDir = CGeneral::LimitAngle(phoneDir); + m_moved = CVector2D(0.0f, 0.0f); + + if (currentRot - 180.0f > phoneDir) + phoneDir += 2 * 180.0f; + else if (180.0f + currentRot < phoneDir) + phoneDir -= 2 * 180.0f; + + float neededTurn = currentRot - phoneDir; + + if (Abs(neededTurn) <= 0.75f) { + SetIdle(); + ClearLookFlag(); + m_phoneTalkTimer = CTimer::GetTimeInMilliseconds() + 10000; + return true; + } else { + m_fRotationCur = DEGTORAD(currentRot - neededTurn * 0.2f); + return false; + } +#endif +} +bool +CPed::MakePhonecall(void) +{ +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + if (!IsPlayer() && CTimer::GetTimeInMilliseconds() > m_phoneTalkTimer - 7000 && bRunningToPhone) { + + FindPlayerPed()->m_pWanted->RegisterCrime_Immediately(m_crimeToReportOnPhone, GetPosition(), + (m_crimeToReportOnPhone == CRIME_POSSESSION_GUN ? (uintptr)m_threatEntity : (uintptr)m_victimOfPlayerCrime), false); + + if (m_crimeToReportOnPhone != CRIME_POSSESSION_GUN) + FindPlayerPed()->m_pWanted->SetWantedLevelNoDrop(1); + + bRunningToPhone = false; + } +#endif + if (CTimer::GetTimeInMilliseconds() <= m_phoneTalkTimer) + return false; + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + CAnimBlendAssociation* talkAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_PHONE_TALK); + if (talkAssoc && talkAssoc->blendAmount > 0.5f) { + CAnimBlendAssociation* endAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_OUT, 8.0f); + endAssoc->flags &= ~ASSOC_DELETEFADEDOUT; + endAssoc->SetFinishCallback(ReportPhonePutDownCB, this); + } +#endif + SetIdle(); + + gPhoneInfo.m_aPhones[m_phoneId].m_nState = PHONE_STATE_FREE; +#ifndef PEDS_REPORT_CRIMES_ON_PHONE + m_phoneId = -1; +#endif + + // Because SetWanderPath is now done async in ReportPhonePutDownCB +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + return false; +#else + return true; +#endif +} + +void +CPed::Teleport(CVector pos) +{ + CWorld::Remove(this); + SetPosition(pos); + bIsStanding = false; + m_nPedStateTimer = 0; + m_actionX = 0.0f; + m_actionY = 0.0f; + m_pDamageEntity = nil; + CWorld::Add(this); +} + +void +CPed::SetSeekCar(CVehicle *car, uint32 doorNode) +{ + if (m_nPedState == PED_SEEK_CAR) + return; + +#ifdef VC_PED_PORTS + if (!CanSetPedState() || m_nPedState == PED_DRIVING) + return; +#endif + + SetStoredState(); + m_pSeekTarget = car; + m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget); + m_carInObjective = car; + m_carInObjective->RegisterReference((CEntity**) &m_carInObjective); + m_pMyVehicle = car; + m_pMyVehicle->RegisterReference((CEntity**) &m_pMyVehicle); + // m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget); + m_vehDoor = doorNode; + m_distanceToCountSeekDone = 0.5f; + SetPedState(PED_SEEK_CAR); + +} + +void +CPed::SeekCar(void) +{ + CVehicle *vehToSeek = m_carInObjective; + CVector dest(0.0f, 0.0f, 0.0f); + if (!vehToSeek) { + RestorePreviousState(); + return; + } + + if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + if (m_vehDoor && m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (IsRoomToBeCarJacked()) { + dest = GetPositionToOpenCarDoor(vehToSeek, m_vehDoor); + } else if (m_nPedType == PEDTYPE_COP) { + dest = GetPositionToOpenCarDoor(vehToSeek, CAR_DOOR_RF); + } else { + SetMoveState(PEDMOVE_STILL); + } + } else + GetNearestDoor(vehToSeek, dest); + } else { + if (m_carJackTimer > CTimer::GetTimeInMilliseconds()) { + SetMoveState(PEDMOVE_STILL); + return; + } + if (vehToSeek->GetModelIndex() == MI_COACH) { + GetNearestDoor(vehToSeek, dest); + } else { + if (vehToSeek->IsTrain()) { + if (vehToSeek->GetStatus() != STATUS_TRAIN_NOT_MOVING) { + RestorePreviousObjective(); + RestorePreviousState(); + return; + } + if (!GetNearestTrainDoor(vehToSeek, dest)) { + RestorePreviousObjective(); + RestorePreviousState(); + return; + } + } else { + if (!GetNearestPassengerDoor(vehToSeek, dest)) { + if (vehToSeek->m_nNumPassengers == vehToSeek->m_nNumMaxPassengers) { + RestorePreviousObjective(); + RestorePreviousState(); + } else { + SetMoveState(PEDMOVE_STILL); + } + bVehEnterDoorIsBlocked = true; + return; + } + bVehEnterDoorIsBlocked = false; + } + } + } + + if (dest.x == 0.0f && dest.y == 0.0f) { +#ifdef FIX_BUGS + if ((!IsPlayer() && CharCreatedBy != MISSION_CHAR) || vehToSeek->VehicleCreatedBy != MISSION_VEHICLE || vehToSeek->pDriver || !vehToSeek->CanPedOpenLocks(this)) { +#else + if ((!IsPlayer() && CharCreatedBy != MISSION_CHAR) || vehToSeek->VehicleCreatedBy != MISSION_VEHICLE || vehToSeek->pDriver) { +#endif + RestorePreviousState(); + if (IsPlayer()) { + ClearObjective(); + } else if (CharCreatedBy == RANDOM_CHAR) { + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 30000; + } + SetMoveState(PEDMOVE_STILL); + TheCamera.ClearPlayerWeaponMode(); + CCarCtrl::RemoveFromInterestingVehicleList(vehToSeek); + return; + } + dest = vehToSeek->GetPosition(); + if (bCollidedWithMyVehicle) { + WarpPedIntoCar(m_pMyVehicle); + return; + } + } + bool foundBetterPosToSeek = PossiblyFindBetterPosToSeekCar(&dest, vehToSeek); + m_vecSeekPos = dest; + float distToDestSqr = (m_vecSeekPos - GetPosition()).MagnitudeSqr(); +#ifndef VC_PED_PORTS + if (bIsRunning) + SetMoveState(PEDMOVE_RUN); +#else + if (bIsRunning || + vehToSeek->pDriver && distToDestSqr > sq(2.0f) && (Abs(vehToSeek->m_vecMoveSpeed.x) > 0.01f || Abs(vehToSeek->m_vecMoveSpeed.y) > 0.01f)) + SetMoveState(PEDMOVE_RUN); +#endif + else if (distToDestSqr < sq(2.0f)) + SetMoveState(PEDMOVE_WALK); + + if (distToDestSqr >= 1.0f) + bCanPedEnterSeekedCar = false; + else if (2.0f * vehToSeek->GetColModel()->boundingBox.max.x > distToDestSqr) + bCanPedEnterSeekedCar = true; + + if (vehToSeek->m_nGettingInFlags & GetCarDoorFlag(m_vehDoor)) + bVehEnterDoorIsBlocked = true; + else + bVehEnterDoorIsBlocked = false; + + // Arrived to the car + if (Seek()) { + if (!foundBetterPosToSeek) { + if (1.5f + GetPosition().z > dest.z && GetPosition().z - 0.5f < dest.z) { + if (vehToSeek->IsTrain()) { + SetEnterTrain(vehToSeek, m_vehDoor); + } else { + m_fRotationCur = m_fRotationDest; + if (!bVehEnterDoorIsBlocked) { + vehToSeek->SetIsStatic(false); + if (m_objective == OBJECTIVE_SOLICIT_VEHICLE) { + SetSolicit(1000); + } else if (m_objective == OBJECTIVE_BUY_ICE_CREAM) { + SetBuyIceCream(); + } else if (vehToSeek->m_nNumGettingIn < vehToSeek->m_nNumMaxPassengers + 1 + && vehToSeek->CanPedEnterCar()) { + + switch (vehToSeek->GetStatus()) { + case STATUS_PLAYER: + case STATUS_SIMPLE: + case STATUS_PHYSICS: + case STATUS_PLAYER_DISABLED: + if (!vehToSeek->bIsBus && (!m_leader || m_leader != vehToSeek->pDriver) && + (m_vehDoor == CAR_DOOR_LF && vehToSeek->pDriver || m_vehDoor == CAR_DOOR_RF && vehToSeek->pPassengers[0] || m_vehDoor == CAR_DOOR_LR && vehToSeek->pPassengers[1] || m_vehDoor == CAR_DOOR_RR && vehToSeek->pPassengers[2])) { + SetCarJack(vehToSeek); + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER && m_vehDoor != CAR_DOOR_LF) + vehToSeek->pDriver->bFleeAfterExitingCar = true; + } else { + SetEnterCar(vehToSeek, m_vehDoor); + } + break; + case STATUS_ABANDONED: + if (m_vehDoor == CAR_DOOR_RF && vehToSeek->pPassengers[0]) { + if (vehToSeek->pPassengers[0]->bDontDragMeOutCar) { + if (IsPlayer()) + SetEnterCar(vehToSeek, m_vehDoor); + } else { + SetCarJack(vehToSeek); + } + } else { + SetEnterCar(vehToSeek, m_vehDoor); + } + break; + case STATUS_WRECKED: + SetIdle(); + break; + default: + return; + } + } else { + RestorePreviousState(); + } + } else { + SetMoveState(PEDMOVE_STILL); + } + } + } + } + } +} + +bool +CPed::CheckForExplosions(CVector2D &area) +{ + int event = 0; + if (CEventList::FindClosestEvent(EVENT_EXPLOSION, GetPosition(), &event)) { + area.x = gaEvent[event].posn.x; + area.y = gaEvent[event].posn.y; + CEntity *actualEntity = nil; + + switch (gaEvent[event].entityType) { + case EVENT_ENTITY_PED: + actualEntity = CPools::GetPed(gaEvent[event].entityRef); + break; + case EVENT_ENTITY_VEHICLE: + actualEntity = CPools::GetVehicle(gaEvent[event].entityRef); + break; + case EVENT_ENTITY_OBJECT: + actualEntity = CPools::GetObject(gaEvent[event].entityRef); + break; + default: + break; + } + + if (actualEntity) { + m_pEventEntity = actualEntity; + m_pEventEntity->RegisterReference((CEntity **) &m_pEventEntity); + bGonnaInvestigateEvent = true; + } else + bGonnaInvestigateEvent = false; + + CEventList::ClearEvent(event); + return true; + } else if (CEventList::FindClosestEvent(EVENT_FIRE, GetPosition(), &event)) { + area.x = gaEvent[event].posn.x; + area.y = gaEvent[event].posn.y; + CEventList::ClearEvent(event); + bGonnaInvestigateEvent = false; + return true; + } + + bGonnaInvestigateEvent = false; + return false; +} + +CPed * +CPed::CheckForGunShots(void) +{ + int event; + if (CEventList::FindClosestEvent(EVENT_GUNSHOT, GetPosition(), &event)) { + if (gaEvent[event].entityType == EVENT_ENTITY_PED) { + // Probably due to we don't want peds to go gunshot area? (same on VC) + bGonnaInvestigateEvent = false; + return CPools::GetPed(gaEvent[event].entityRef); + } + } + bGonnaInvestigateEvent = false; + return nil; +} + +CPed * +CPed::CheckForDeadPeds(void) +{ + int event; + if (CEventList::FindClosestEvent(EVENT_DEAD_PED, GetPosition(), &event)) { + int pedHandle = gaEvent[event].entityRef; + if (pedHandle && gaEvent[event].entityType == EVENT_ENTITY_PED) { + bGonnaInvestigateEvent = true; + return CPools::GetPed(pedHandle); + } + } + bGonnaInvestigateEvent = false; + return nil; +} + +bool +CPed::IsPlayer(void) const +{ + return m_nPedType == PEDTYPE_PLAYER1 || m_nPedType == PEDTYPE_PLAYER2 || + m_nPedType == PEDTYPE_PLAYER3 || m_nPedType == PEDTYPE_PLAYER4; +} + +bool +CPed::IsGangMember(void) const +{ + return m_nPedType >= PEDTYPE_GANG1 && m_nPedType <= PEDTYPE_GANG9; +} + +bool +CPed::IsPointerValid(void) +{ + int pedIndex = CPools::GetPedPool()->GetIndex(this) >> 8; + if (pedIndex < 0 || pedIndex >= NUMPEDS) + return false; + + if (m_entryInfoList.first || FindPlayerPed() == this) + return true; + + return false; +} + +void +CPed::SetPedPositionInCar(void) +{ + if (CReplay::IsPlayingBack()) + return; + + if (bChangedSeat) { + bool notYet = false; + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_GET_IN_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_GET_IN_LO_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_CLOSE_DOOR_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_CLOSE_DOOR_LO_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SHUFFLE_RHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SHUFFLE_LO_RHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_VAN_GET_IN_REAR_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_VAN_GET_IN_REAR_RHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_COACH_GET_IN_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_COACH_GET_IN_RHS)) { + notYet = true; + } + if (notYet) { + LineUpPedWithCar(LINE_UP_TO_CAR_START); + bChangedSeat = false; + return; + } + } + CVehicleModelInfo *vehModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(m_pMyVehicle->GetModelIndex()); + CMatrix newMat(m_pMyVehicle->GetMatrix()); + CVector seatPos; + if (m_pMyVehicle->pDriver == this) { + seatPos = vehModel->GetFrontSeatPosn(); + if (!m_pMyVehicle->IsBoat() && m_pMyVehicle->m_vehType != VEHICLE_TYPE_BIKE) + seatPos.x = -seatPos.x; + + } else if (m_pMyVehicle->pPassengers[0] == this) { + seatPos = vehModel->GetFrontSeatPosn(); + } else if (m_pMyVehicle->pPassengers[1] == this) { + seatPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + seatPos.x = -seatPos.x; + } else { + if (m_pMyVehicle->pPassengers[2] == this) { + seatPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + } else { + seatPos = vehModel->GetFrontSeatPosn(); + } + } + newMat.GetPosition() += Multiply3x3(newMat, seatPos); + // Already done below (SetTranslate(0.0f, 0.0f, 0.0f)) + // tempMat.SetUnity(); + + // Rear seats on vans don't face to front, so rotate them HALFPI. + if (m_pMyVehicle->bIsVan) { + CMatrix tempMat; + if (m_pMyVehicle->pPassengers[1] == this) { + m_fRotationCur = m_pMyVehicle->GetForward().Heading() - HALFPI; + tempMat.SetTranslate(0.0f, 0.0f, 0.0f); + tempMat.RotateZ(-HALFPI); + newMat = newMat * tempMat; + } else if (m_pMyVehicle->pPassengers[2] == this) { + m_fRotationCur = m_pMyVehicle->GetForward().Heading() + HALFPI; + tempMat.SetTranslate(0.0f, 0.0f, 0.0f); + tempMat.RotateZ(HALFPI); + newMat = newMat * tempMat; + } else { + m_fRotationCur = m_pMyVehicle->GetForward().Heading(); + } + } else { + m_fRotationCur = m_pMyVehicle->GetForward().Heading(); + } + SetMatrix(newMat); +} + +void +CPed::LookForSexyPeds(void) +{ + if ((!IsPedInControl() && m_nPedState != PED_DRIVING) + || m_lookTimer >= CTimer::GetTimeInMilliseconds() || +#ifdef FIX_BUGS + // gang members have these lines too + (!IsGangMember() && m_nPedType != PEDTYPE_CIVMALE) +#else + m_nPedType != PEDTYPE_CIVMALE +#endif + ) + return; + + for (int i = 0; i < m_numNearPeds; i++) { + if (CanSeeEntity(m_nearPeds[i])) { + if ((GetPosition() - m_nearPeds[i]->GetPosition()).Magnitude() < 10.0f) { + CPed *nearPed = m_nearPeds[i]; + if ((nearPed->m_pedStats->m_sexiness > m_pedStats->m_sexiness) +#ifdef FIX_BUGS + // react to prostitutes as well + && ((nearPed->m_nPedType == PEDTYPE_CIVFEMALE) || (nearPed->m_nPedType == PEDTYPE_PROSTITUTE))) { +#else + && nearPed->m_nPedType == PEDTYPE_CIVFEMALE) { +#endif + + SetLookFlag(nearPed, true); + m_lookTimer = CTimer::GetTimeInMilliseconds() + 4000; + Say(SOUND_PED_CHAT_SEXY); + return; + } + } + } + } + m_lookTimer = CTimer::GetTimeInMilliseconds() + 10000; +} + +void +CPed::LookForSexyCars(void) +{ + CEntity *vehicles[8]; + CVehicle *veh; + int foundVehId = 0; + int bestPriceYet = 0; + int16 lastVehicle; + + if (!IsPedInControl() && m_nPedState != PED_DRIVING) + return; + + if (m_lookTimer < CTimer::GetTimeInMilliseconds()) { + CWorld::FindObjectsInRange(GetPosition(), 10.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int vehId = 0; vehId < lastVehicle; vehId++) { + veh = (CVehicle*)vehicles[vehId]; + if (veh != m_pMyVehicle && bestPriceYet < veh->pHandling->nMonetaryValue) { + foundVehId = vehId; + bestPriceYet = veh->pHandling->nMonetaryValue; + } + } + if (lastVehicle > 0 && bestPriceYet > 40000) + SetLookFlag(vehicles[foundVehId], false); + + m_lookTimer = CTimer::GetTimeInMilliseconds() + 10000; + } +} + +bool +CPed::LookForInterestingNodes(void) +{ + CBaseModelInfo *model; + CPtrNode *ptrNode; + CVector effectDist; + C2dEffect *effect; + CMatrix *objMat; + + if ((CTimer::GetFrameCounter() + (m_randomSeed % 256)) & 7 || CTimer::GetTimeInMilliseconds() <= m_chatTimer) { + return false; + } + bool found = false; + uint8 randVal = CGeneral::GetRandomNumber() % 256; + + int minX = CWorld::GetSectorIndexX(GetPosition().x - CHECK_NEARBY_THINGS_MAX_DIST); + if (minX < 0) minX = 0; + int minY = CWorld::GetSectorIndexY(GetPosition().y - CHECK_NEARBY_THINGS_MAX_DIST); + if (minY < 0) minY = 0; + int maxX = CWorld::GetSectorIndexX(GetPosition().x + CHECK_NEARBY_THINGS_MAX_DIST); +#ifdef FIX_BUGS + if (maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1; +#else + if (maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X; +#endif + + int maxY = CWorld::GetSectorIndexY(GetPosition().y + CHECK_NEARBY_THINGS_MAX_DIST); +#ifdef FIX_BUGS + if (maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; +#else + if (maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y; +#endif + + for (int curY = minY; curY <= maxY && !found; curY++) { + for (int curX = minX; curX <= maxX && !found; curX++) { + CSector *sector = CWorld::GetSector(curX, curY); + + for (ptrNode = sector->m_lists[ENTITYLIST_VEHICLES].first; ptrNode && !found; ptrNode = ptrNode->next) { + CVehicle *veh = (CVehicle*)ptrNode->item; + model = veh->GetModelInfo(); + if (model->GetNum2dEffects() != 0) { + for (int e = 0; e < model->GetNum2dEffects(); e++) { + effect = model->Get2dEffect(e); + if (effect->type == EFFECT_ATTRACTOR && effect->attractor.probability >= randVal) { + objMat = &veh->GetMatrix(); + CVector effectPos = veh->GetMatrix() * effect->pos; + effectDist = effectPos - GetPosition(); + if (effectDist.MagnitudeSqr() < sq(8.0f)) { + found = true; + break; + } + } + } + } + } + for (ptrNode = sector->m_lists[ENTITYLIST_OBJECTS].first; ptrNode && !found; ptrNode = ptrNode->next) { + CObject *obj = (CObject*)ptrNode->item; + model = CModelInfo::GetModelInfo(obj->GetModelIndex()); + if (model->GetNum2dEffects() != 0) { + for (int e = 0; e < model->GetNum2dEffects(); e++) { + effect = model->Get2dEffect(e); + if (effect->type == EFFECT_ATTRACTOR && effect->attractor.probability >= randVal) { + objMat = &obj->GetMatrix(); + CVector effectPos = obj->GetMatrix() * effect->pos; + effectDist = effectPos - GetPosition(); + if (effectDist.MagnitudeSqr() < sq(8.0f)) { + found = true; + break; + } + } + } + } + } + for (ptrNode = sector->m_lists[ENTITYLIST_BUILDINGS].first; ptrNode && !found; ptrNode = ptrNode->next) { + CBuilding *building = (CBuilding*)ptrNode->item; + model = CModelInfo::GetModelInfo(building->GetModelIndex()); + if (model->GetNum2dEffects() != 0) { + for (int e = 0; e < model->GetNum2dEffects(); e++) { + effect = model->Get2dEffect(e); + if (effect->type == EFFECT_ATTRACTOR && effect->attractor.probability >= randVal) { + objMat = &building->GetMatrix(); + CVector effectPos = building->GetMatrix() * effect->pos; + effectDist = effectPos - GetPosition(); + if (effectDist.MagnitudeSqr() < sq(8.0f)) { + found = true; + break; + } + } + } + } + } + for (ptrNode = sector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].first; ptrNode && !found; ptrNode = ptrNode->next) { + CBuilding *building = (CBuilding*)ptrNode->item; + model = CModelInfo::GetModelInfo(building->GetModelIndex()); + if (model->GetNum2dEffects() != 0) { + for (int e = 0; e < model->GetNum2dEffects(); e++) { + effect = model->Get2dEffect(e); + if (effect->type == EFFECT_ATTRACTOR && effect->attractor.probability >= randVal) { + objMat = &building->GetMatrix(); + CVector effectPos = building->GetMatrix() * effect->pos; + effectDist = effectPos - GetPosition(); + if (effectDist.MagnitudeSqr() < sq(8.0f)) { + found = true; + break; + } + } + } + } + } + } + } + + if (!found) + return false; + + CVector effectFrontLocal = Multiply3x3(*objMat, effect->attractor.dir); + float angleToFace = CGeneral::GetRadianAngleBetweenPoints(effectFrontLocal.x, effectFrontLocal.y, 0.0f, 0.0f); + randVal = CGeneral::GetRandomNumber() % 256; + if (randVal <= m_randomSeed % 256) { + m_chatTimer = CTimer::GetTimeInMilliseconds() + 2000; + SetLookFlag(angleToFace, true); + SetLookTimer(1000); + return false; + } + + CVector2D effectPos = *objMat * effect->pos; + switch (effect->attractor.type) { + case ATTRACTORTYPE_ICECREAM: + SetInvestigateEvent(EVENT_ICECREAM, effectPos, 0.1f, 15000, angleToFace); + break; + case ATTRACTORTYPE_STARE: + SetInvestigateEvent(EVENT_SHOPSTALL, effectPos, 1.0f, + CGeneral::GetRandomNumberInRange(8000, 10 * effect->attractor.probability + 8500), + angleToFace); + break; + default: + return true; + } + return true; +} + +void +CPed::SetWaitState(eWaitState state, void *time) +{ + AnimationId waitAnim = ANIM_STD_NUM; + CAnimBlendAssociation *animAssoc; + + if (!IsPedInControl()) + return; + + if (state != m_nWaitState) + FinishedWaitCB(nil, this); + + switch (state) { + case WAITSTATE_TRAFFIC_LIGHTS: + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 500; + SetMoveState(PEDMOVE_STILL); + break; + case WAITSTATE_CROSS_ROAD: + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 1000; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_HBHB, 4.0f); + break; + case WAITSTATE_CROSS_ROAD_LOOK: + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ROADCROSS, 8.0f); + + if (time) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + *(int*)time; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(2000,5000); + + break; + case WAITSTATE_LOOK_PED: + case WAITSTATE_LOOK_SHOP: + case WAITSTATE_LOOK_ACCIDENT: + case WAITSTATE_FACEOFF_GANG: + break; + case WAITSTATE_DOUBLEBACK: + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 3500; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_HBHB, 4.0f); +#ifdef FIX_BUGS + animAssoc->SetFinishCallback(RestoreHeadingRateCB, this); +#endif + break; + case WAITSTATE_HITWALL: + m_headingRate = 2.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_WALL, 16.0f); + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->flags |= ASSOC_FADEOUTWHENDONE; + animAssoc->SetDeleteCallback(FinishedWaitCB, this); + + if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER && CharCreatedBy == RANDOM_CHAR && m_nPedState == PED_SEEK_CAR) { + ClearObjective(); + RestorePreviousState(); + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 30000; + } + break; + case WAITSTATE_TURN180: + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_TURN180, 4.0f); + animAssoc->SetFinishCallback(FinishedWaitCB, this); + animAssoc->SetDeleteCallback(RestoreHeadingRateCB, this); + break; + case WAITSTATE_SURPRISE: + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 2000; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_WALL, 4.0f); + animAssoc->SetFinishCallback(FinishedWaitCB, this); + break; + case WAITSTATE_STUCK: + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_TIRED, 4.0f); +#ifdef FIX_BUGS + animAssoc->SetFinishCallback(RestoreHeadingRateCB, this); +#endif + + if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER && CharCreatedBy == RANDOM_CHAR && m_nPedState == PED_SEEK_CAR) { + ClearObjective(); + RestorePreviousState(); + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 30000; + } + break; + case WAITSTATE_LOOK_ABOUT: + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_HBHB, 4.0f); +#ifdef FIX_BUGS + animAssoc->SetFinishCallback(RestoreHeadingRateCB, this); +#endif + + break; + case WAITSTATE_PLAYANIM_COWER: + waitAnim = ANIM_STD_HANDSCOWER; + case WAITSTATE_PLAYANIM_HANDSUP: + if (waitAnim == ANIM_STD_NUM) + waitAnim = ANIM_STD_HANDSUP; + case WAITSTATE_PLAYANIM_HANDSCOWER: + if (waitAnim == ANIM_STD_NUM) + waitAnim = ANIM_STD_HANDSCOWER; + m_headingRate = 0.0f; + if (time) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + *(int*)time; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 3000; + + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, waitAnim, 4.0f); + animAssoc->SetDeleteCallback(FinishedWaitCB, this); + break; + case WAITSTATE_PLAYANIM_DUCK: + waitAnim = ANIM_STD_DUCK_DOWN; + case WAITSTATE_PLAYANIM_TAXI: + if (waitAnim == ANIM_STD_NUM) + waitAnim = ANIM_STD_HAILTAXI; + case WAITSTATE_PLAYANIM_CHAT: + if (waitAnim == ANIM_STD_NUM) + waitAnim = ANIM_STD_CHAT; + if (time) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + *(int*)time; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 3000; + + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, waitAnim, 4.0f); + animAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->SetDeleteCallback(FinishedWaitCB, this); + break; + case WAITSTATE_FINISH_FLEE: + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 2500; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_TIRED, 4.0f); +#ifdef FIX_BUGS + animAssoc->SetFinishCallback(RestoreHeadingRateCB, this); +#endif + break; + default: + m_nWaitState = WAITSTATE_FALSE; + RestoreHeadingRate(); + return; + } + m_nWaitState = state; +} + +void +CPed::Wait(void) +{ + AnimationId mustHaveAnim = ANIM_STD_NUM; + CAnimBlendAssociation *animAssoc; + CPed *pedWeLook; + + if (DyingOrDead()) { + m_nWaitState = WAITSTATE_FALSE; + RestoreHeadingRate(); + return; + } + + switch (m_nWaitState) { + + case WAITSTATE_TRAFFIC_LIGHTS: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + if (CTrafficLights::LightForPeds() == PED_LIGHTS_WALK) { + m_nWaitState = WAITSTATE_FALSE; + SetMoveState(PEDMOVE_WALK); + } + } + break; + + case WAITSTATE_CROSS_ROAD: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + if (CGeneral::GetRandomNumber() & 1 || !m_nWaitTimer) + m_nWaitState = WAITSTATE_FALSE; + else + SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, nil); + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_HBHB); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + break; + + case WAITSTATE_CROSS_ROAD_LOOK: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + m_nWaitState = WAITSTATE_FALSE; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROADCROSS); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + break; + + case WAITSTATE_DOUBLEBACK: + if (CTimer::GetTimeInMilliseconds() <= m_nWaitTimer) { + uint32 timeLeft = m_nWaitTimer - CTimer::GetTimeInMilliseconds(); + if (timeLeft < 2500 && timeLeft > 2000) { + m_nWaitTimer -= 500; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_XPRESS_SCRATCH, 4.0f); + } + } else { + m_nWaitState = WAITSTATE_FALSE; + SetMoveState(PEDMOVE_WALK); + } + break; + + case WAITSTATE_HITWALL: + if (CTimer::GetTimeInMilliseconds() <= m_nWaitTimer) { + if (m_collidingThingTimer > CTimer::GetTimeInMilliseconds()) { + m_collidingThingTimer = CTimer::GetTimeInMilliseconds() + 2500; + } + } else { + m_nWaitState = WAITSTATE_FALSE; + } + break; + + case WAITSTATE_TURN180: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + m_nWaitState = WAITSTATE_FALSE; + SetMoveState(PEDMOVE_WALK); + m_fRotationCur = m_fRotationCur + PI; + if (m_nPedState == PED_INVESTIGATE) + ClearInvestigateEvent(); + } + + if (m_collidingThingTimer > CTimer::GetTimeInMilliseconds()) { + m_collidingThingTimer = CTimer::GetTimeInMilliseconds() + 2500; + } + break; + + case WAITSTATE_SURPRISE: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HIT_WALL)) { + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_XPRESS_SCRATCH, 4.0f); + animAssoc->SetFinishCallback(FinishedWaitCB, this); + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + } else { + m_nWaitState = WAITSTATE_FALSE; + } + } + break; + + case WAITSTATE_STUCK: + if (CTimer::GetTimeInMilliseconds() <= m_nWaitTimer) + break; + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_TURN180); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_XPRESS_SCRATCH); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROADCROSS); + + if (animAssoc) { + if (animAssoc->IsPartial()) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } else { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + } + + if (animAssoc->animId == ANIM_STD_TURN180) { + m_fRotationCur = CGeneral::LimitRadianAngle(PI + m_fRotationCur); + m_nWaitState = WAITSTATE_FALSE; + SetMoveState(PEDMOVE_WALK); + m_nStoredMoveState = PEDMOVE_NONE; + m_panicCounter = 0; + return; + } + } + + AnimationId animToPlay; + + switch (CGeneral::GetRandomNumber() & 3) { + case 0: + animToPlay = ANIM_STD_ROADCROSS; + break; + case 1: + animToPlay = ANIM_STD_IDLE_TIRED; + break; + case 2: + animToPlay = ANIM_STD_XPRESS_SCRATCH; + break; + case 3: + animToPlay = ANIM_STD_TURN180; + break; + default: + break; + } + + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animToPlay, 4.0f); + + if (animToPlay == ANIM_STD_TURN180) + animAssoc->SetFinishCallback(FinishedWaitCB, this); + + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(1500, 5000); + break; + + case WAITSTATE_LOOK_ABOUT: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + m_nWaitState = WAITSTATE_FALSE; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_HBHB); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + break; + + case WAITSTATE_PLAYANIM_HANDSUP: + mustHaveAnim = ANIM_STD_HANDSUP; + + case WAITSTATE_PLAYANIM_HANDSCOWER: + if (mustHaveAnim == ANIM_STD_NUM) + mustHaveAnim = ANIM_STD_HANDSCOWER; + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), mustHaveAnim); + pedWeLook = (CPed*) m_pLookTarget; + + if ((!m_pLookTarget || !m_pLookTarget->IsPed() || pedWeLook->m_pPointGunAt) + && m_nPedState != PED_FLEE_ENTITY + && m_nPedState != PED_ATTACK + && CTimer::GetTimeInMilliseconds() <= m_nWaitTimer + && animAssoc) { + + TurnBody(); + } else { + m_nWaitState = WAITSTATE_FALSE; + m_nWaitTimer = 0; + if (m_pLookTarget && m_pLookTarget->IsPed()) { + + if (m_nPedState != PED_FLEE_ENTITY && m_nPedState != PED_ATTACK) { + + if (m_pedStats->m_fear <= 100 - pedWeLook->m_pedStats->m_temper) { + + if (GetWeapon()->IsTypeMelee()) { +#ifdef VC_PED_PORTS + if(m_pedStats->m_flags & STAT_GUN_PANIC) { +#endif + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, m_pLookTarget); + if (m_nPedState == PED_FLEE_ENTITY || m_nPedState == PED_FLEE_POS) { + + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + } + if (m_nMoveState != PEDMOVE_RUN) + SetMoveState(PEDMOVE_WALK); + + if (m_nPedType != PEDTYPE_COP) { + ProcessObjective(); + SetMoveState(PEDMOVE_WALK); + } +#ifdef VC_PED_PORTS + } else { + SetObjective(OBJECTIVE_NONE); + SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + } +#endif + } else { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_pLookTarget); + SetObjectiveTimer(20000); + } + } else { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, m_pLookTarget); + if (m_nPedState == PED_FLEE_ENTITY || m_nPedState == PED_FLEE_POS) + { + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + } + SetMoveState(PEDMOVE_RUN); + Say(SOUND_PED_FLEE_RUN); + } + } + } + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), mustHaveAnim); + if (animAssoc) { + animAssoc->blendDelta = -4.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + break; + case WAITSTATE_PLAYANIM_COWER: + mustHaveAnim = ANIM_STD_HANDSCOWER; + + case WAITSTATE_PLAYANIM_DUCK: + if (mustHaveAnim == ANIM_STD_NUM) + mustHaveAnim = ANIM_STD_DUCK_DOWN; + + case WAITSTATE_PLAYANIM_TAXI: + if (mustHaveAnim == ANIM_STD_NUM) + mustHaveAnim = ANIM_STD_HAILTAXI; + + case WAITSTATE_PLAYANIM_CHAT: + if (mustHaveAnim == ANIM_STD_NUM) + mustHaveAnim = ANIM_STD_CHAT; + + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), mustHaveAnim); + if (animAssoc) { + animAssoc->blendDelta = -4.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + m_nWaitState = WAITSTATE_FALSE; + } +#ifdef VC_PED_PORTS + else if (m_nWaitState == WAITSTATE_PLAYANIM_TAXI) { + if (m_pedInObjective) { + if (m_objective == OBJECTIVE_GOTO_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT) { + + // VC also calls CleanUpOldReference here for old LookTarget. + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + } + } + } +#endif + break; + + case WAITSTATE_FINISH_FLEE: + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + if (animAssoc) { + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + int timer = 2000; + m_nWaitState = WAITSTATE_FALSE; + SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, &timer); + } + } else { + m_nWaitState = WAITSTATE_FALSE; + } + break; + default: + break; + } + + if(!m_nWaitState) + RestoreHeadingRate(); +} + +void +CPed::FinishedWaitCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + ped->m_nWaitTimer = 0; + ped->RestoreHeadingRate(); + ped->Wait(); +} + +void +CPed::RestoreHeadingRate(void) +{ + m_headingRate = m_pedStats->m_headingChangeRate; +} + +void +CPed::RestoreHeadingRateCB(CAnimBlendAssociation *assoc, void *arg) +{ + ((CPed*)arg)->m_headingRate = ((CPed*)arg)->m_pedStats->m_headingChangeRate; +} + +void +CPed::FlagToDestroyWhenNextProcessed(void) +{ + bRemoveFromWorld = true; + if (!InVehicle()) + return; + if (m_pMyVehicle->pDriver == this){ + m_pMyVehicle->pDriver = nil; + if (IsPlayer() && m_pMyVehicle->GetStatus() != STATUS_WRECKED) + m_pMyVehicle->SetStatus(STATUS_ABANDONED); + }else{ + m_pMyVehicle->RemovePassenger(this); + } + bInVehicle = false; + m_pMyVehicle = nil; + + if (CharCreatedBy == MISSION_CHAR) + SetPedState(PED_DEAD); + else + SetPedState(PED_NONE); + m_pVehicleAnim = nil; +} + +void +CPed::SetSolicit(uint32 time) +{ + if (m_nPedState == PED_SOLICIT || !IsPedInControl() || !m_carInObjective) + return; + + if (CharCreatedBy != MISSION_CHAR && m_carInObjective->m_nNumGettingIn == 0 + && CTimer::GetTimeInMilliseconds() < m_objectiveTimer) { + if (m_vehDoor == CAR_DOOR_LF) { + m_fRotationDest = m_carInObjective->GetForward().Heading() - HALFPI; + } else { + m_fRotationDest = m_carInObjective->GetForward().Heading() + HALFPI; + } + + if (Abs(m_fRotationDest - m_fRotationCur) < HALFPI) { + m_chatTimer = CTimer::GetTimeInMilliseconds() + time; + + if(!m_carInObjective->bIsVan && !m_carInObjective->bIsBus) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_HOOKERTALK, 4.0f); + + SetPedState(PED_SOLICIT); + } + } +} + +void +CPed::Solicit(void) +{ + if (m_chatTimer >= CTimer::GetTimeInMilliseconds() && m_carInObjective) { + CVector doorPos = GetPositionToOpenCarDoor(m_carInObjective, m_vehDoor, 0.0f); + SetMoveState(PEDMOVE_STILL); + + // Game uses GetAngleBetweenPoints and converts it to radian + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + doorPos.x, doorPos.y, + GetPosition().x, GetPosition().y); + + if (m_fRotationDest < 0.0f) { + m_fRotationDest = m_fRotationDest + TWOPI; + } else if (m_fRotationDest > TWOPI) { + m_fRotationDest = m_fRotationDest - TWOPI; + } + + if ((GetPosition() - doorPos).MagnitudeSqr() <= 1.0f) + return; + CAnimBlendAssociation *talkAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_HOOKERTALK); + if (talkAssoc) { + talkAssoc->blendDelta = -1000.0f; + talkAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + RestorePreviousState(); + RestorePreviousObjective(); + SetObjectiveTimer(10000); + } else if (!m_carInObjective) { + RestorePreviousState(); + RestorePreviousObjective(); + SetObjectiveTimer(10000); + } else if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney <= 100) { + m_carInObjective = nil; + } else { + m_pVehicleAnim = nil; + SetLeader(m_carInObjective->pDriver); + } +} + +void +CPed::SetBuyIceCream(void) +{ + if (m_nPedState == PED_BUY_ICECREAM || !IsPedInControl()) + return; + + if (!m_carInObjective) + return; + +#ifdef FIX_ICECREAM + + // Simulating BuyIceCream + CPed* driver = m_carInObjective->pDriver; + if (driver) { + SetPedState(PED_BUY_ICECREAM); + bFindNewNodeAfterStateRestore = true; + SetObjectiveTimer(8000); + SetChat(driver, 8000); + driver->SetChat(this, 8000); + return; + } +#endif + + // Side of the Ice Cream van + m_fRotationDest = m_carInObjective->GetForward().Heading() - HALFPI; + + if (Abs(m_fRotationDest - m_fRotationCur) < HALFPI) { + m_chatTimer = CTimer::GetTimeInMilliseconds() + 3000; + SetPedState(PED_BUY_ICECREAM); + } +} + +bool +CPed::PossiblyFindBetterPosToSeekCar(CVector *pos, CVehicle *veh) +{ + bool foundIt = false; + + CVector helperPos = GetPosition(); + helperPos.z = pos->z - 0.5f; + + CVector foundPos = *pos; + foundPos.z -= 0.5f; + + // If there is another car between target car and us. + if (CWorld::TestSphereAgainstWorld((foundPos + helperPos) / 2.0f, 0.25f, veh, false, true, false, false, false, false)) { + + CColModel *vehCol = veh->GetModelInfo()->GetColModel(); + CVector *colMin = &vehCol->boundingBox.min; + CVector *colMax = &vehCol->boundingBox.max; + + CVector leftRearPos = CVector(colMin->x - 0.5f, colMin->y - 0.5f, 0.0f); + CVector rightRearPos = CVector(0.5f + colMax->x, colMin->y - 0.5f, 0.0f); + CVector leftFrontPos = CVector(colMin->x - 0.5f, 0.5f + colMax->y, 0.0f); + CVector rightFrontPos = CVector(0.5f + colMax->x, 0.5f + colMax->y, 0.0f); + + leftRearPos = veh->GetMatrix() * leftRearPos; + rightRearPos = veh->GetMatrix() * rightRearPos; + leftFrontPos = veh->GetMatrix() * leftFrontPos; + rightFrontPos = veh->GetMatrix() * rightFrontPos; + + // Makes helperPos veh-ped distance vector. + helperPos -= veh->GetPosition(); + + // ?!? I think it's absurd to use this unless another function like SeekCar finds next pos. with it and we're trying to simulate it's behaviour. + // On every run it returns another pos. for ped, with same distance to the veh. + // Sequence of positions are not guaranteed, it depends on global pos. (So sometimes it returns positions to make ped draw circle, sometimes don't) + helperPos = veh->GetMatrix() * helperPos; + + float vehForwardHeading = veh->GetForward().Heading(); + + // I'm absolutely not sure about these namings. + // NTVF = needed turn if we're looking to vehicle front and wanna look to... + + float potentialLrHeading = Atan2(leftRearPos.x - helperPos.x, leftRearPos.y - helperPos.y); + float NTVF_LR = CGeneral::LimitRadianAngle(potentialLrHeading - vehForwardHeading); + + float potentialRrHeading = Atan2(rightRearPos.x - helperPos.x, rightRearPos.y - helperPos.y); + float NTVF_RR = CGeneral::LimitRadianAngle(potentialRrHeading - vehForwardHeading); + + float potentialLfHeading = Atan2(leftFrontPos.x - helperPos.x, leftFrontPos.y - helperPos.y); + float NTVF_LF = CGeneral::LimitRadianAngle(potentialLfHeading - vehForwardHeading); + + float potentialRfHeading = Atan2(rightFrontPos.x - helperPos.x, rightFrontPos.y - helperPos.y); + float NTVF_RF = CGeneral::LimitRadianAngle(potentialRfHeading - vehForwardHeading); + + bool canHeadToLr = NTVF_LR <= -PI || NTVF_LR >= -HALFPI; + + bool canHeadToRr = NTVF_RR <= HALFPI || NTVF_RR >= PI; + + bool canHeadToLf = NTVF_LF >= 0.0f || NTVF_LF <= -HALFPI; + + bool canHeadToRf = NTVF_RF <= 0.0f || NTVF_RF >= HALFPI; + + // Only order of conditions are different among enterTypes. + if (m_vehDoor == CAR_DOOR_RR) { + if (canHeadToRr) { + foundPos = rightRearPos; + foundIt = true; + } else if (canHeadToRf) { + foundPos = rightFrontPos; + foundIt = true; + } else if (canHeadToLr) { + foundPos = leftRearPos; + foundIt = true; + } else if (canHeadToLf) { + foundPos = leftFrontPos; + foundIt = true; + } + } else if(m_vehDoor == CAR_DOOR_RF) { + if (canHeadToRf) { + foundPos = rightFrontPos; + foundIt = true; + } else if (canHeadToRr) { + foundPos = rightRearPos; + foundIt = true; + } else if (canHeadToLf) { + foundPos = leftFrontPos; + foundIt = true; + } else if (canHeadToLr) { + foundPos = leftRearPos; + foundIt = true; + } + } else if (m_vehDoor == CAR_DOOR_LF) { + if (canHeadToLf) { + foundPos = leftFrontPos; + foundIt = true; + } else if (canHeadToLr) { + foundPos = leftRearPos; + foundIt = true; + } else if (canHeadToRf) { + foundPos = rightFrontPos; + foundIt = true; + } else if (canHeadToRr) { + foundPos = rightRearPos; + foundIt = true; + } + } else if (m_vehDoor == CAR_DOOR_LR) { + if (canHeadToLr) { + foundPos = leftRearPos; + foundIt = true; + } else if (canHeadToLf) { + foundPos = leftFrontPos; + foundIt = true; + } else if (canHeadToRr) { + foundPos = rightRearPos; + foundIt = true; + } else if (canHeadToRf) { + foundPos = rightFrontPos; + foundIt = true; + } + } + } + if (!foundIt) + return false; + + helperPos = GetPosition() - foundPos; + helperPos.z = 0.0f; + if (helperPos.MagnitudeSqr() <= sq(0.5f)) + return false; + + pos->x = foundPos.x; + pos->y = foundPos.y; + return true; +} + +void +CPed::SetLeader(CEntity *leader) +{ + m_leader = (CPed*)leader; + + if(m_leader) + m_leader->RegisterReference((CEntity **)&m_leader); +} + +#ifdef VC_PED_PORTS +bool +CPed::CanPedJumpThis(CEntity *unused, CVector *damageNormal) +{ + if (m_nSurfaceTouched == SURFACE_WATER) + return true; + + CVector pos = GetPosition(); + CVector forwardOffset = GetForward(); + if (damageNormal && damageNormal->z > 0.17f) { + if (damageNormal->z > 0.9f) + return false; + + CColModel *ourCol = CModelInfo::GetModelInfo(m_modelIndex)->GetColModel(); + pos.z = ourCol->spheres->center.z - ourCol->spheres->radius * damageNormal->z + pos.z; + pos.z = pos.z + 0.05f; + float collPower = damageNormal->Magnitude2D(); + if (damageNormal->z > 0.5f) { + CVector invDamageNormal(-damageNormal->x, -damageNormal->y, 0.0f); + invDamageNormal *= 1.0f / collPower; + CVector estimatedJumpDist = invDamageNormal + collPower * invDamageNormal * ourCol->spheres->radius; + forwardOffset = estimatedJumpDist * Min(2.0f / collPower, 4.0f); + } else { + forwardOffset += collPower * ourCol->spheres->radius * forwardOffset; + } + } else { + pos.z -= 0.15f; + } + + CVector forwardPos = pos + forwardOffset; + return CWorld::GetIsLineOfSightClear(pos, forwardPos, true, false, false, true, false, false, false); +} +#else +bool +CPed::CanPedJumpThis(CEntity *unused) +{ + CVector2D forward(-Sin(m_fRotationCur), Cos(m_fRotationCur)); + CVector pos = GetPosition(); + CVector forwardPos( + forward.x + pos.x, + forward.y + pos.y, + pos.z); + + return CWorld::GetIsLineOfSightClear(pos, forwardPos, true, false, false, true, false, false, false); +} +#endif + +void +CPed::SetJump(void) +{ + if (!bInVehicle && +#if defined VC_PED_PORTS || defined FIX_BUGS + m_nPedState != PED_JUMP && !RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_JUMP_LAUNCH) && +#endif + (m_nSurfaceTouched != SURFACE_STEEP_CLIFF || DotProduct(GetForward(), m_vecDamageNormal) >= 0.0f)) { + SetStoredState(); + SetPedState(PED_JUMP); + CAnimBlendAssociation *jumpAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_JUMP_LAUNCH, 8.0f); + jumpAssoc->SetFinishCallback(FinishLaunchCB, this); + m_fRotationDest = m_fRotationCur; + } +} + +void +CPed::FinishLaunchCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (ped->m_nPedState != PED_JUMP) + return; + + CVector forward(0.15f * ped->GetForward() + ped->GetPosition()); + forward.z += CModelInfo::GetColModel(ped->GetModelIndex())->spheres->center.z + 0.25f; + + CEntity *obstacle = CWorld::TestSphereAgainstWorld(forward, 0.25f, nil, true, true, false, true, false, false); + if (!obstacle) { + // Forward of forward + forward += 0.15f * ped->GetForward(); + forward.z += 0.15f; + obstacle = CWorld::TestSphereAgainstWorld(forward, 0.25f, nil, true, true, false, true, false, false); + } + + if (obstacle) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + + // ANIM_HIT_WALL in VC (which makes more sense) + CAnimBlendAssociation *handsCoverAssoc = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSCOWER, 8.0f); + handsCoverAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + handsCoverAssoc->SetFinishCallback(FinishHitHeadCB, ped); + ped->bIsLanding = true; + return; + } + + float velocityFromAnim = 0.1f; + CAnimBlendAssociation *sprintAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), ANIM_STD_RUNFAST); + + if (sprintAssoc) { + velocityFromAnim = 0.05f * sprintAssoc->blendAmount + 0.17f; + } else { + CAnimBlendAssociation *runAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), ANIM_STD_RUN); + if (runAssoc) { + velocityFromAnim = 0.07f * runAssoc->blendAmount + 0.1f; + } + } + + if (ped->IsPlayer() +#ifdef VC_PED_PORTS + || ped->m_pedInObjective && ped->m_pedInObjective->IsPlayer() +#endif + ) + ped->ApplyMoveForce(0.0f, 0.0f, 8.5f); + else + ped->ApplyMoveForce(0.0f, 0.0f, 4.5f); + + if (sq(velocityFromAnim) > ped->m_vecMoveSpeed.MagnitudeSqr2D() +#ifdef VC_PED_PORTS + || ped->m_pCurrentPhysSurface +#endif + ) { + +#ifdef FREE_CAM + if (TheCamera.Cams[0].Using3rdPersonMouseCam() && !CCamera::bFreeCam) { +#else + if (TheCamera.Cams[0].Using3rdPersonMouseCam()) { +#endif + float fpsAngle = ped->WorkOutHeadingForMovingFirstPerson(ped->m_fRotationCur); + ped->m_vecMoveSpeed.x = -velocityFromAnim * Sin(fpsAngle); + ped->m_vecMoveSpeed.y = velocityFromAnim * Cos(fpsAngle); + } else { + ped->m_vecMoveSpeed.x = -velocityFromAnim * Sin(ped->m_fRotationCur); + ped->m_vecMoveSpeed.y = velocityFromAnim * Cos(ped->m_fRotationCur); + } +#ifdef VC_PED_PORTS + if (ped->m_pCurrentPhysSurface) { + ped->m_vecMoveSpeed.x += ped->m_pCurrentPhysSurface->m_vecMoveSpeed.x; + ped->m_vecMoveSpeed.y += ped->m_pCurrentPhysSurface->m_vecMoveSpeed.y; + } +#endif + } + + ped->bIsStanding = false; + ped->bIsInTheAir = true; + animAssoc->blendDelta = -1000.0f; + CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_JUMP_GLIDE); + + if (ped->bDoBloodyFootprints) { + CVector bloodPos(0.0f, 0.0f, 0.0f); + ped->TransformToNode(bloodPos, PED_FOOTL); + + bloodPos.z -= 0.1f; + bloodPos += 0.2f * ped->GetForward(); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &bloodPos, + 0.26f * ped->GetForward().x, + 0.26f * ped->GetForward().y, + 0.14f * ped->GetRight().x, + 0.14f * ped->GetRight().y, + 255, 255, 0, 0, 4.0f, 3000, 1.0f); + + bloodPos = CVector(0.0f, 0.0f, 0.0f); + ped->TransformToNode(bloodPos, PED_FOOTR); + + bloodPos.z -= 0.1f; + bloodPos += 0.2f * ped->GetForward(); + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &bloodPos, + 0.26f * ped->GetForward().x, + 0.26f * ped->GetForward().y, + 0.14f * ped->GetRight().x, + 0.14f * ped->GetRight().y, + 255, 255, 0, 0, 4.0f, 3000, 1.0f); + + if (ped->m_bloodyFootprintCountOrDeathTime <= 40) { + ped->m_bloodyFootprintCountOrDeathTime = 0; + ped->bDoBloodyFootprints = false; + } else { + ped->m_bloodyFootprintCountOrDeathTime -= 40; + } + } +} + +void +CPed::FinishJumpCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + ped->bResetWalkAnims = true; + ped->bIsLanding = false; + + animAssoc->blendDelta = -1000.0f; +} + +void +CPed::FinishHitHeadCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (animAssoc) { + animAssoc->blendDelta = -4.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + + if (ped->m_nPedState == PED_JUMP) + ped->RestorePreviousState(); + + ped->bIsLanding = false; +} + +bool +CPed::CanPedDriveOff(void) +{ + if (m_nPedState != PED_DRIVING || m_lookTimer > CTimer::GetTimeInMilliseconds()) + return false; + + for (int i = 0; i < m_numNearPeds; i++) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->m_nPedType == m_nPedType && nearPed->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER && nearPed->m_carInObjective == m_carInObjective) { + m_lookTimer = CTimer::GetTimeInMilliseconds() + 1000; + return false; + } + } + return true; +} + +// These categories are purely random, most of ped models have no correlation. So I don't think making an enum. +uint8 +CPed::GetPedRadioCategory(uint32 modelIndex) +{ + switch (modelIndex) { + case MI_MALE01: + case MI_FEMALE03: + case MI_PROSTITUTE2: + case MI_WORKER1: + case MI_MOD_MAN: + case MI_MOD_WOM: + case MI_ST_WOM: + case MI_FAN_WOM: + return 3; + case MI_TAXI_D: + case MI_PIMP: + case MI_MALE02: + case MI_FEMALE02: + case MI_FATFEMALE01: + case MI_FATFEMALE02: + case MI_DOCKER1: + case MI_WORKER2: + case MI_FAN_MAN2: + return 9; + case MI_GANG01: + case MI_GANG02: + case MI_SCUM_MAN: + case MI_SCUM_WOM: + case MI_HOS_WOM: + case MI_CONST1: + return 1; + case MI_GANG03: + case MI_GANG04: + case MI_GANG07: + case MI_GANG08: + case MI_CT_MAN2: + case MI_CT_WOM2: + case MI_B_MAN3: + case MI_SHOPPER3: + return 4; + case MI_GANG05: + case MI_GANG06: + case MI_GANG11: + case MI_GANG12: + case MI_CRIMINAL02: + case MI_B_WOM2: + case MI_ST_MAN: + case MI_HOS_MAN: + return 5; + case MI_FATMALE01: + case MI_LI_MAN2: + case MI_SHOPPER1: + case MI_CAS_MAN: + return 6; + case MI_PROSTITUTE: + case MI_P_WOM2: + case MI_LI_WOM2: + case MI_B_WOM3: + case MI_CAS_WOM: + return 2; + case MI_P_WOM1: + case MI_DOCKER2: + case MI_STUD_MAN: + return 7; + case MI_CT_MAN1: + case MI_CT_WOM1: + case MI_LI_MAN1: + case MI_LI_WOM1: + case MI_B_MAN1: + case MI_B_MAN2: + case MI_B_WOM1: + case MI_SHOPPER2: + case MI_STUD_WOM: + return 8; + default: + return 0; + } +} + +void +CPed::SetRadioStation(void) +{ + static const uint8 radiosPerRadioCategories[10][4] = { + {JAH_RADIO, RISE_FM, GAME_FM, MSX_FM}, + {HEAD_RADIO, DOUBLE_CLEF, LIPS_106, FLASHBACK}, + {RISE_FM, GAME_FM, MSX_FM, FLASHBACK}, + {HEAD_RADIO, RISE_FM, LIPS_106, MSX_FM}, + {HEAD_RADIO, RISE_FM, MSX_FM, FLASHBACK}, + {JAH_RADIO, RISE_FM, LIPS_106, FLASHBACK}, + {HEAD_RADIO, RISE_FM, LIPS_106, FLASHBACK}, + {HEAD_RADIO, JAH_RADIO, LIPS_106, FLASHBACK}, + {HEAD_RADIO, DOUBLE_CLEF, LIPS_106, FLASHBACK}, + {CHATTERBOX, HEAD_RADIO, LIPS_106, GAME_FM} + }; + uint8 orderInCat = 0; // BUG: this wasn't initialized + + if (IsPlayer() || !m_pMyVehicle || m_pMyVehicle->pDriver != this) + return; + + uint8 category = GetPedRadioCategory(GetModelIndex()); + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (CGeneral::GetRandomNumber() & 15) { + for (orderInCat = 0; orderInCat < 4; orderInCat++) { + if (m_pMyVehicle->m_nRadioStation == radiosPerRadioCategories[category][orderInCat]) + break; + } + } else { + m_pMyVehicle->m_nRadioStation = USERTRACK; + } + } else { + for (orderInCat = 0; orderInCat < 4; orderInCat++) { + if (m_pMyVehicle->m_nRadioStation == radiosPerRadioCategories[category][orderInCat]) + break; + } + } + if (orderInCat == 4) { + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (CGeneral::GetRandomNumber() & 15) + m_pMyVehicle->m_nRadioStation = radiosPerRadioCategories[category][CGeneral::GetRandomNumber() & 3]; + else + m_pMyVehicle->m_nRadioStation = USERTRACK; + } else { + m_pMyVehicle->m_nRadioStation = radiosPerRadioCategories[category][CGeneral::GetRandomNumber() & 3]; + } + } +} + +void +CPed::WarpPedIntoCar(CVehicle *car) +{ + bInVehicle = true; + m_pMyVehicle = car; + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + m_carInObjective = car; + m_carInObjective->RegisterReference((CEntity **) &m_carInObjective); + SetPedState(PED_DRIVING); + bUsesCollision = false; + bIsInTheAir = false; + bVehExitWillBeInstant = true; + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + car->SetDriver(this); + car->pDriver->RegisterReference((CEntity **) &car->pDriver); + + } else if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + for (int i = 0; i < 4; i++) { + if (!car->pPassengers[i]) { + car->pPassengers[i] = this; + car->pPassengers[i]->RegisterReference((CEntity **) &car->pPassengers[i]); + break; + } + } + } else + return; + + if (IsPlayer()) { + car->SetStatus(STATUS_PLAYER); + AudioManager.PlayerJustGotInCar(); + CCarCtrl::RegisterVehicleOfInterest(car); + } else { + car->SetStatus(STATUS_PHYSICS); + } + + CWorld::Remove(this); + SetPosition(car->GetPosition()); + CWorld::Add(this); + + if (car->bIsAmbulanceOnDuty) { + car->bIsAmbulanceOnDuty = false; + --CCarCtrl::NumAmbulancesOnDuty; + } + if (car->bIsFireTruckOnDuty) { + car->bIsFireTruckOnDuty = false; + --CCarCtrl::NumFiretrucksOnDuty; + } + if (!car->bEngineOn) { + car->bEngineOn = true; + DMAudio.PlayOneShot(car->m_audioEntityId, SOUND_CAR_ENGINE_START, 1.0f); + } + +#ifdef VC_PED_PORTS + RpAnimBlendClumpSetBlendDeltas(GetClump(), ASSOC_PARTIAL, -1000.0f); + + // VC uses AddInCarAnims but we don't have that + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, car->GetDriverAnim(), 100.0f); + RemoveWeaponWhenEnteringVehicle(); +#else + if (car->IsBoat()) { +#ifndef FIX_BUGS + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_BOAT_DRIVE, 100.0f); +#else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, car->GetDriverAnim(), 100.0f); +#endif + CWeaponInfo *ourWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(ourWeapon->m_nModelId); + } else { + // Because we can use Uzi for drive by + RemoveWeaponWhenEnteringVehicle(); + + if (car->bLowVehicle) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT_LO, 100.0f); + else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT, 100.0f); + } +#endif + + StopNonPartialAnims(); + if (car->bIsBus) + bRenderPedInCar = false; + + bChangedSeat = true; +} + + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +// returns event id, parameter is optional +int32 +CPed::CheckForPlayerCrimes(CPed *victim) +{ + int i; + float dist; + float mindist = 60.0f; + CPlayerPed *player = FindPlayerPed(); + int32 victimRef = (victim ? CPools::GetPedRef(victim) : 0); + int event = -1; + + for (i = 0; i < NUMEVENTS; i++) { + if (gaEvent[i].type == EVENT_NULL || gaEvent[i].type > EVENT_CAR_SET_ON_FIRE) + continue; + + // those are already handled in game, also DEAD_PED isn't registered alone, most of the time there is SHOOT_PED etc. + if (gaEvent[i].type == EVENT_DEAD_PED || gaEvent[i].type == EVENT_GUNSHOT || gaEvent[i].type == EVENT_EXPLOSION) + continue; + + if (victim && gaEvent[i].entityRef != victimRef) + continue; + + if (gaEvent[i].criminal != player) + continue; + + dist = (GetPosition() - gaEvent[i].posn).Magnitude(); + if (dist < mindist) { + mindist = dist; + event = i; + } + } + + if (event != -1) { + if (victim) { + m_victimOfPlayerCrime = victim; + } else { + switch (gaEvent[event].entityType) { + case EVENT_ENTITY_PED: + m_victimOfPlayerCrime = CPools::GetPed(gaEvent[event].entityRef); + break; + case EVENT_ENTITY_VEHICLE: + m_victimOfPlayerCrime = CPools::GetVehicle(gaEvent[event].entityRef); + break; + case EVENT_ENTITY_OBJECT: + m_victimOfPlayerCrime = CPools::GetObject(gaEvent[event].entityRef); + break; + default: + break; + } + } + } + + return event; +} +#endif + +#ifdef PED_SKIN +static RpMaterial* +SetLimbAlphaCB(RpMaterial *material, void *data) +{ + ((RwRGBA*)RpMaterialGetColor(material))->alpha = *(uint8*)data; + return material; +} + +void +CPed::renderLimb(int node) +{ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID); + RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + CPedModelInfo *mi = (CPedModelInfo *)CModelInfo::GetModelInfo(GetModelIndex()); + RpAtomic *atomic; + switch(node){ + case PED_HEAD: + atomic = mi->getHead(); + break; + case PED_HANDL: + atomic = mi->getLeftHand(); + break; + case PED_HANDR: + atomic = mi->getRightHand(); + break; + default: + return; + } + if(atomic == nil) + return; + + RwFrame *frame = RpAtomicGetFrame(atomic); + *RwFrameGetMatrix(frame) = *mat; + RwFrameUpdateObjects(frame); + int alpha = CVisibilityPlugins::GetClumpAlpha(GetClump()); + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), SetLimbAlphaCB, &alpha); + RpAtomicRender(atomic); +} +#endif + +#ifdef COMPATIBLE_SAVES +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +void +CPed::Save(uint8*& buf) +{ + ZeroSaveBuf(buf, 52); + CopyToBuf(buf, GetPosition().x); + CopyToBuf(buf, GetPosition().y); + CopyToBuf(buf, GetPosition().z); + ZeroSaveBuf(buf, 288); + CopyToBuf(buf, CharCreatedBy); + ZeroSaveBuf(buf, 351); + CopyToBuf(buf, m_fHealth); + CopyToBuf(buf, m_fArmour); + ZeroSaveBuf(buf, 148); + for (int i = 0; i < 13; i++) // has to be hardcoded + m_weapons[i].Save(buf); + ZeroSaveBuf(buf, 5); + CopyToBuf(buf, m_maxWeaponTypeAllowed); + ZeroSaveBuf(buf, 162); +} + +void +CPed::Load(uint8*& buf) +{ + SkipSaveBuf(buf, 52); + CopyFromBuf(buf, GetMatrix().GetPosition().x); + CopyFromBuf(buf, GetMatrix().GetPosition().y); + CopyFromBuf(buf, GetMatrix().GetPosition().z); + SkipSaveBuf(buf, 288); + CopyFromBuf(buf, CharCreatedBy); + SkipSaveBuf(buf, 351); + CopyFromBuf(buf, m_fHealth); + CopyFromBuf(buf, m_fArmour); + SkipSaveBuf(buf, 148); + for (int i = 0; i < 13; i++) // has to be hardcoded + m_weapons[i].Load(buf); + SkipSaveBuf(buf, 5); + CopyFromBuf(buf, m_maxWeaponTypeAllowed); + SkipSaveBuf(buf, 162); +} +#undef CopyFromBuf +#undef CopyToBuf +#endif diff --git a/src/peds/Ped.h b/src/peds/Ped.h new file mode 100644 index 0000000..9cd85ef --- /dev/null +++ b/src/peds/Ped.h @@ -0,0 +1,962 @@ +#pragma once + +#include "RwHelper.h" +#include "AnimManager.h" +#include "Crime.h" +#include "EventList.h" +#include "PedIK.h" +#include "PedType.h" +#include "Physical.h" +#include "Weapon.h" +#include "WeaponInfo.h" +#include "Collision.h" + +#define FEET_OFFSET 1.04f +#define CHECK_NEARBY_THINGS_MAX_DIST 15.0f +#define ENTER_CAR_MAX_DIST 30.0f +#define CAN_SEE_ENTITY_ANGLE_THRESHOLD DEGTORAD(60.0f) + +struct CPathNode; +class CAccident; +class CObject; +class CFire; +struct AnimBlendFrameData; +class CAnimBlendAssociation; + +struct PedAudioData +{ + int m_nFixedDelayTime; + int m_nOverrideFixedDelayTime; + int m_nOverrideMaxRandomDelayTime; + int m_nMaxRandomDelayTime; +}; + +enum eFormation +{ + FORMATION_UNDEFINED, + FORMATION_REAR, + FORMATION_REAR_LEFT, + FORMATION_REAR_RIGHT, + FORMATION_FRONT_LEFT, + FORMATION_FRONT_RIGHT, + FORMATION_LEFT, + FORMATION_RIGHT, + FORMATION_FRONT +}; + +enum FightState { + FIGHTSTATE_MOVE_FINISHED = -2, + FIGHTSTATE_JUST_ATTACKED, + FIGHTSTATE_NO_MOVE, + FIGHTSTATE_1 +}; + +enum +{ + ENDFIGHT_NORMAL, + ENDFIGHT_WITH_A_STEP, + ENDFIGHT_FAST +}; + +enum PedRouteType +{ + PEDROUTE_STOP_WHEN_DONE = 1, + PEDROUTE_GO_BACKWARD_WHEN_DONE, + PEDROUTE_GO_TO_START_WHEN_DONE +}; + +enum FightMoveHitLevel +{ + HITLEVEL_NULL, + HITLEVEL_GROUND, + HITLEVEL_LOW, + HITLEVEL_MEDIUM, + HITLEVEL_HIGH +}; + +struct FightMove +{ + AnimationId animId; + float startFireTime; + float endFireTime; + float comboFollowOnTime; + float strikeRadius; + uint8 hitLevel; // FightMoveHitLevel + uint8 damage; + uint8 flags; +}; +VALIDATE_SIZE(FightMove, 0x18); + +// TODO: This is eFightState on mobile. +enum PedFightMoves +{ + FIGHTMOVE_NULL, + // Attacker + FIGHTMOVE_STDPUNCH, + FIGHTMOVE_IDLE, + FIGHTMOVE_SHUFFLE_F, + FIGHTMOVE_KNEE, + FIGHTMOVE_HEADBUTT, + FIGHTMOVE_PUNCHJAB, + FIGHTMOVE_PUNCHHOOK, + FIGHTMOVE_KICK, + FIGHTMOVE_LONGKICK, + FIGHTMOVE_ROUNDHOUSE, + FIGHTMOVE_BODYBLOW, + FIGHTMOVE_GROUNDKICK, + // Opponent + FIGHTMOVE_HITFRONT, + FIGHTMOVE_HITBACK, + FIGHTMOVE_HITRIGHT, + FIGHTMOVE_HITLEFT, + FIGHTMOVE_HITBODY, + FIGHTMOVE_HITCHEST, + FIGHTMOVE_HITHEAD, + FIGHTMOVE_HITBIGSTEP, + FIGHTMOVE_HITONFLOOR, + FIGHTMOVE_HITBEHIND, + FIGHTMOVE_IDLE2NORM, + NUM_FIGHTMOVES +}; + +enum ePedPieceTypes +{ + PEDPIECE_TORSO, + PEDPIECE_MID, + PEDPIECE_LEFTARM, + PEDPIECE_RIGHTARM, + PEDPIECE_LEFTLEG, + PEDPIECE_RIGHTLEG, + PEDPIECE_HEAD, +}; + +enum eWaitState { + WAITSTATE_FALSE, + WAITSTATE_TRAFFIC_LIGHTS, + WAITSTATE_CROSS_ROAD, + WAITSTATE_CROSS_ROAD_LOOK, + WAITSTATE_LOOK_PED, + WAITSTATE_LOOK_SHOP, + WAITSTATE_LOOK_ACCIDENT, + WAITSTATE_FACEOFF_GANG, + WAITSTATE_DOUBLEBACK, + WAITSTATE_HITWALL, + WAITSTATE_TURN180, + WAITSTATE_SURPRISE, + WAITSTATE_STUCK, + WAITSTATE_LOOK_ABOUT, + WAITSTATE_PLAYANIM_DUCK, + WAITSTATE_PLAYANIM_COWER, + WAITSTATE_PLAYANIM_TAXI, + WAITSTATE_PLAYANIM_HANDSUP, + WAITSTATE_PLAYANIM_HANDSCOWER, + WAITSTATE_PLAYANIM_CHAT, + WAITSTATE_FINISH_FLEE +}; + +enum eObjective { + OBJECTIVE_NONE, + OBJECTIVE_WAIT_ON_FOOT, + OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE, + OBJECTIVE_GUARD_SPOT, + OBJECTIVE_GUARD_AREA, // not implemented + OBJECTIVE_WAIT_IN_CAR, + OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT, + OBJECTIVE_KILL_CHAR_ON_FOOT, + OBJECTIVE_KILL_CHAR_ANY_MEANS, + OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, + OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, + OBJECTIVE_GOTO_CHAR_ON_FOOT, + OBJECTIVE_FOLLOW_CHAR_IN_FORMATION, + OBJECTIVE_LEAVE_CAR, + OBJECTIVE_ENTER_CAR_AS_PASSENGER, + OBJECTIVE_ENTER_CAR_AS_DRIVER, + OBJECTIVE_FOLLOW_CAR_IN_CAR, // not implemented + OBJECTIVE_FIRE_AT_OBJECT_FROM_VEHICLE, // not implemented + OBJECTIVE_DESTROY_OBJECT, // not implemented + OBJECTIVE_DESTROY_CAR, + OBJECTIVE_GOTO_AREA_ANY_MEANS, + OBJECTIVE_GOTO_AREA_ON_FOOT, + OBJECTIVE_RUN_TO_AREA, + OBJECTIVE_GOTO_AREA_IN_CAR, // not implemented + OBJECTIVE_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, // not implemented + OBJECTIVE_GUARD_ATTACK, + OBJECTIVE_SET_LEADER, + OBJECTIVE_FOLLOW_ROUTE, + OBJECTIVE_SOLICIT_VEHICLE, + OBJECTIVE_HAIL_TAXI, + OBJECTIVE_CATCH_TRAIN, + OBJECTIVE_BUY_ICE_CREAM, + OBJECTIVE_STEAL_ANY_CAR, + OBJECTIVE_MUG_CHAR, + OBJECTIVE_FLEE_CAR, +#ifdef VC_PED_PORTS + OBJECTIVE_LEAVE_CAR_AND_DIE +#endif +}; + +enum { + RANDOM_CHAR = 1, + MISSION_CHAR, +}; + +enum PedLineUpPhase { + LINE_UP_TO_CAR_START, + LINE_UP_TO_CAR_END, + LINE_UP_TO_CAR_2 // Buggy. Used for cops arresting you from passenger door +}; + +enum PedOnGroundState { + NO_PED, + PED_IN_FRONT_OF_ATTACKER, + PED_ON_THE_FLOOR, + PED_DEAD_ON_THE_FLOOR +}; + +enum PointBlankNecessity { + NO_POINT_BLANK_PED, + POINT_BLANK_FOR_WANTED_PED, + POINT_BLANK_FOR_SOMEONE_ELSE +}; + +enum PedState +{ + PED_NONE, + PED_IDLE, + PED_LOOK_ENTITY, + PED_LOOK_HEADING, + PED_WANDER_RANGE, + PED_WANDER_PATH, + PED_SEEK_POS, + PED_SEEK_ENTITY, + PED_FLEE_POS, + PED_FLEE_ENTITY, + PED_PURSUE, + PED_FOLLOW_PATH, + PED_SNIPER_MODE, + PED_ROCKET_MODE, + PED_DUMMY, + PED_PAUSE, + PED_ATTACK, + PED_FIGHT, + PED_FACE_PHONE, + PED_MAKE_CALL, + PED_CHAT, + PED_MUG, + PED_AIM_GUN, + PED_AI_CONTROL, + PED_SEEK_CAR, + PED_SEEK_IN_BOAT, + PED_FOLLOW_ROUTE, + PED_CPR, + PED_SOLICIT, + PED_BUY_ICECREAM, + PED_INVESTIGATE, + PED_STEP_AWAY, + PED_ON_FIRE, + + PED_UNKNOWN, // Same with IDLE, but also infects up to 5 peds with same pedType and WANDER_PATH, so they become stone too. HANG_OUT in Fire_Head's idb + + PED_STATES_NO_AI, + + // One of these states isn't on PS2 - start + PED_JUMP, + PED_FALL, + PED_GETUP, + PED_STAGGER, + PED_DIVE_AWAY, + + PED_STATES_NO_ST, + PED_ENTER_TRAIN, + PED_EXIT_TRAIN, + PED_ARREST_PLAYER, + // One of these states isn't on PS2 - end + + PED_DRIVING, + PED_PASSENGER, + PED_TAXI_PASSENGER, + PED_OPEN_DOOR, + PED_DIE, + PED_DEAD, + PED_CARJACK, + PED_DRAG_FROM_CAR, + PED_ENTER_CAR, + PED_STEAL_CAR, + PED_EXIT_CAR, + PED_HANDS_UP, + PED_ARRESTED, +}; + +enum eMoveState { + PEDMOVE_NONE, + PEDMOVE_STILL, + PEDMOVE_WALK, + PEDMOVE_RUN, + PEDMOVE_SPRINT, +}; + +class CVehicle; + +class CPed : public CPhysical +{ +public: + // 0x128 + CStoredCollPoly m_collPoly; + float m_fCollisionSpeed; + + // cf. https://github.com/DK22Pac/plugin-sdk/blob/master/plugin_sa/game_sa/CPed.h from R* + uint32 bIsStanding : 1; + uint32 bWasStanding : 1; + uint32 bIsAttacking : 1; // doesn't reset after fist fight + uint32 bIsPointingGunAt : 1; + uint32 bIsLooking : 1; + uint32 bKeepTryingToLook : 1; // if we can't look somewhere due to unreachable angles + uint32 bIsRestoringLook : 1; + uint32 bIsAimingGun : 1; + + uint32 bIsRestoringGun : 1; + uint32 bCanPointGunAtTarget : 1; + uint32 bIsTalking : 1; + uint32 bIsInTheAir : 1; + uint32 bIsLanding : 1; + uint32 bIsRunning : 1; // on some conditions + uint32 bHitSomethingLastFrame : 1; + uint32 bVehEnterDoorIsBlocked : 1; // because someone else enters/exits from there + + uint32 bCanPedEnterSeekedCar : 1; + uint32 bRespondsToThreats : 1; + uint32 bRenderPedInCar : 1; + uint32 bChangedSeat : 1; + uint32 bUpdateAnimHeading : 1; + uint32 bBodyPartJustCameOff : 1; + uint32 bIsShooting : 1; + uint32 bFindNewNodeAfterStateRestore : 1; + + uint32 bHasACamera : 1; // does ped possess a camera to document accidents involves fire/explosion + uint32 bGonnaInvestigateEvent : 1; + uint32 bPedIsBleeding : 1; + uint32 bStopAndShoot : 1; // Ped cannot reach target to attack with fist, need to use gun + uint32 bIsPedDieAnimPlaying : 1; + uint32 bUsePedNodeSeek : 1; + uint32 bObjectiveCompleted : 1; + uint32 bScriptObjectiveCompleted : 1; + + uint32 bKindaStayInSamePlace : 1; + uint32 bBeingChasedByPolice : 1; // Unused VC leftover. Should've been set for criminal/gang members + uint32 bNotAllowedToDuck : 1; + uint32 bCrouchWhenShooting : 1; + uint32 bIsDucking : 1; + uint32 bGetUpAnimStarted : 1; + uint32 bDoBloodyFootprints : 1; + uint32 bFleeAfterExitingCar : 1; + + uint32 bWanderPathAfterExitingCar : 1; + uint32 bIsLeader : 1; + uint32 bDontDragMeOutCar : 1; // unfinished feature + uint32 m_ped_flagF8 : 1; + uint32 bWillBeQuickJacked : 1; + uint32 bCancelEnteringCar : 1; // after door is opened or couldn't be opened due to it's locked + uint32 bObstacleShowedUpDuringKillObjective : 1; + uint32 bDuckAndCover : 1; + + uint32 bStillOnValidPoly : 1; // set if the polygon the ped is on is still valid for collision + uint32 bAllowMedicsToReviveMe : 1; + uint32 bResetWalkAnims : 1; + uint32 bStartWanderPathOnFoot : 1; // exits the car if he's in it, reset after path found + uint32 bOnBoat : 1; // not just driver, may be just standing + uint32 bBusJacked : 1; + uint32 bGonnaKillTheCarJacker : 1; // only set when car is jacked from right door and when arrested by police + uint32 bFadeOut : 1; + + uint32 bKnockedUpIntoAir : 1; // has ped been knocked up into the air by a car collision + uint32 bHitSteepSlope : 1; // has ped collided/is standing on a steep slope (surface type) + uint32 bCullExtraFarAway : 1; // special ped only gets culled if it's extra far away (for roadblocks) + uint32 bClearObjective : 1; + uint32 bTryingToReachDryLand : 1; // has ped just exited boat and trying to get to dry land + uint32 bCollidedWithMyVehicle : 1; + uint32 bRichFromMugging : 1; // ped has lots of cash cause they've been mugging people + uint32 bChrisCriminal : 1; // Is a criminal as killed during Chris' police mission (should be counted as such) + + uint32 bShakeFist : 1; // test shake hand at look entity + uint32 bNoCriticalHits : 1; // if set, limbs won't came off + uint32 bVehExitWillBeInstant : 1; + uint32 bHasAlreadyBeenRecorded : 1; + uint32 bFallenDown : 1; +#ifdef VC_PED_PORTS + uint32 bSomeVCflag1 : 1; +#endif +#ifdef PED_SKIN + uint32 bDontAcceptIKLookAts : 1; // TODO: find uses of this +#endif + uint32 m_ped_flagI40 : 1; + uint32 m_ped_flagI80 : 1; // originally unused, KANGAROO_CHEAT define makes use of this as cheat toggle + + uint8 CharCreatedBy; + eObjective m_objective; + eObjective m_prevObjective; + CPed *m_pedInObjective; + CVehicle *m_carInObjective; + CVector m_nextRoutePointPos; + CPed *m_leader; + eFormation m_pedFormation; + uint32 m_fearFlags; + CEntity *m_threatEntity; + CVector2D m_eventOrThreat; + uint32 m_eventType; + CEntity* m_pEventEntity; + float m_fAngleToEvent; + AnimBlendFrameData *m_pFrames[PED_NODE_MAX]; +#ifdef PED_SKIN + // stored inside the clump with non-skin ped + RpAtomic *m_pWeaponModel; +#endif + AssocGroupId m_animGroup; + CAnimBlendAssociation *m_pVehicleAnim; + CVector2D m_vecAnimMoveDelta; + CVector m_vecOffsetSeek; + CPedIK m_pedIK; + float m_actionX; + float m_actionY; + uint32 m_nPedStateTimer; + PedState m_nPedState; + PedState m_nLastPedState; + eMoveState m_nMoveState; + int32 m_nStoredMoveState; + int32 m_nPrevMoveState; + eWaitState m_nWaitState; + uint32 m_nWaitTimer; + void *m_pPathNodesStates[8]; // unused, probably leftover from VC + CVector2D m_stPathNodeStates[10]; + uint16 m_nPathNodes; + int16 m_nCurPathNode; + int8 m_nPathDir; +public: + CPathNode *m_pLastPathNode; + CPathNode *m_pNextPathNode; + float m_fHealth; + float m_fArmour; + int16 m_routeLastPoint; + uint16 m_routeStartPoint; + int16 m_routePointsPassed; + int16 m_routeType; // See PedRouteType + int16 m_routePointsBeingPassed; + CVector2D m_moved; + float m_fRotationCur; + float m_fRotationDest; + float m_headingRate; + uint16 m_vehDoor; + int16 m_walkAroundType; + CPhysical *m_pCurrentPhysSurface; + CVector m_vecOffsetFromPhysSurface; + CEntity *m_pCurSurface; + CVector m_vecSeekPos; + CEntity *m_pSeekTarget; + CVehicle *m_pMyVehicle; + bool bInVehicle; + float m_distanceToCountSeekDone; + bool bRunningToPhone; + int16 m_phoneId; +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + bool m_facePhoneStart; + CEntity *m_victimOfPlayerCrime; +#endif + eCrimeType m_crimeToReportOnPhone; + uint32 m_phoneTalkTimer; + CAccident *m_lastAccident; + uint32 m_nPedType; + CPedStats *m_pedStats; + float m_fleeFromPosX; + float m_fleeFromPosY; + CEntity *m_fleeFrom; + uint32 m_fleeTimer; + CEntity* m_collidingEntityWhileFleeing; + uint32 m_collidingThingTimer; + CEntity *m_pCollidingEntity; + uint8 m_stateUnused; + uint32 m_timerUnused; + class CRange2D *m_wanderRangeBounds; + CWeapon m_weapons[WEAPONTYPE_TOTAL_INVENTORY_WEAPONS]; + eWeaponType m_storedWeapon; + uint8 m_currentWeapon; // eWeaponType + uint8 m_maxWeaponTypeAllowed; // eWeaponType + uint8 m_wepSkills; + uint8 m_wepAccuracy; + CEntity *m_pPointGunAt; + CVector m_vecHitLastPos; + uint32 m_curFightMove; + uint8 m_fightButtonPressure; + int8 m_fightState; + bool m_takeAStepAfterAttack; + CFire *m_pFire; + CEntity *m_pLookTarget; + float m_fLookDirection; + int32 m_wepModelID; + uint32 m_leaveCarTimer; + uint32 m_getUpTimer; + uint32 m_lookTimer; + uint32 m_chatTimer; + uint32 m_attackTimer; + uint32 m_shootTimer; // shooting is a part of attack + uint32 m_carJackTimer; + uint32 m_objectiveTimer; + uint32 m_duckTimer; + uint32 m_duckAndCoverTimer; + uint32 m_bloodyFootprintCountOrDeathTime; // Death time when bDoBloodyFootprints is false. Weird decision + uint8 m_panicCounter; + bool m_deadBleeding; + int8 m_bodyPartBleeding; // PedNode, but -1 if there isn't + CPed *m_nearPeds[10]; + uint16 m_numNearPeds; + int8 m_lastWepDam; + uint32 m_lastSoundStart; + uint32 m_soundStart; + uint16 m_lastQueuedSound; + uint16 m_queuedSound; + CVector m_vecSeekPosEx; // used for OBJECTIVE_GUARD_SPOT + float m_distanceToCountSeekDoneEx; // used for OBJECTIVE_GUARD_SPOT + + static void *operator new(size_t) throw(); + static void *operator new(size_t, int) throw(); + static void operator delete(void*, size_t) throw(); + static void operator delete(void*, int) throw(); + + CPed(uint32 pedType); + ~CPed(void); + + void SetModelIndex(uint32 mi); + void ProcessControl(void); + void Teleport(CVector); + void PreRender(void); + void Render(void); + bool SetupLighting(void); + void RemoveLighting(bool); + void FlagToDestroyWhenNextProcessed(void); + int32 ProcessEntityCollision(CEntity*, CColPoint*); + + virtual void SetMoveAnim(void); + + void AddWeaponModel(int id); + void AimGun(void); + void KillPedWithCar(CVehicle *veh, float impulse); + void Say(uint16 audio); + void SetLookFlag(CEntity *target, bool keepTryingToLook); + void SetLookFlag(float direction, bool keepTryingToLook); + void SetLookTimer(int time); + void SetDie(AnimationId anim, float arg1, float arg2); + void SetDead(void); + void ApplyHeadShot(eWeaponType weaponType, CVector pos, bool evenOnPlayer); + void RemoveBodyPart(PedNode nodeId, int8 direction); + bool OurPedCanSeeThisOne(CEntity *target); + void Avoid(void); + void Attack(void); + void ClearAimFlag(void); + void ClearLookFlag(void); + void RestorePreviousState(void); + void ClearAttack(void); + bool IsPedHeadAbovePos(float zOffset); + void RemoveWeaponModel(int modelId); + void SetCurrentWeapon(uint32 weaponType); + void Duck(void); + void ClearDuck(void); + void ClearPointGunAt(void); + void BeingDraggedFromCar(void); + void RestartNonPartialAnims(void); + void LineUpPedWithCar(PedLineUpPhase phase); + void SetPedPositionInCar(void); + void PlayFootSteps(void); + void QuitEnteringCar(void); + void BuildPedLists(void); + uint32 GiveWeapon(eWeaponType weaponType, uint32 ammo); + void CalculateNewOrientation(void); + float WorkOutHeadingForMovingFirstPerson(float); + void CalculateNewVelocity(void); + bool CanSeeEntity(CEntity*, float threshold = CAN_SEE_ENTITY_ANGLE_THRESHOLD); + void RestorePreviousObjective(void); + void SetIdle(void); +#ifdef _MSC_VER +#if _MSC_VER >= 1920 && _MSC_VER < 1929 + __declspec(noinline) // workaround for a compiler bug, hooray MS :P +#endif +#endif + void SetObjective(eObjective, void*); + void SetObjective(eObjective); + void SetObjective(eObjective, int16, int16); + void SetObjective(eObjective, CVector); + void SetObjective(eObjective, CVector, float); + void ClearChat(void); + void InformMyGangOfAttack(CEntity*); + void ReactToAttack(CEntity*); + void SetDuck(uint32); + void RegisterThreatWithGangPeds(CEntity*); + bool TurnBody(void); + void Chat(void); + void CheckAroundForPossibleCollisions(void); + void SetSeek(CVector, float); + void SetSeek(CEntity*, float); + bool MakePhonecall(void); + bool FacePhone(void); + CPed *CheckForDeadPeds(void); +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + int32 CheckForPlayerCrimes(CPed *victim = nil); +#endif + bool CheckForExplosions(CVector2D &area); + CPed *CheckForGunShots(void); + uint8 CheckForPointBlankPeds(CPed*); + bool CheckIfInTheAir(void); + void ClearAll(void); + void SetPointGunAt(CEntity*); + bool Seek(void); + bool SetWanderPath(int8); + bool SetFollowPath(CVector); + void ClearAttackByRemovingAnim(void); + void SetStoredState(void); + void StopNonPartialAnims(void); + bool InflictDamage(CEntity*, eWeaponType, float, ePedPieceTypes, uint8); + void ClearFlee(void); + void ClearFall(void); + void SetGetUp(void); + void ClearInvestigateEvent(void); + void ClearLeader(void); + void ClearLook(void); + void ClearObjective(void); + void ClearPause(void); + void ClearSeek(void); + void ClearWeapons(void); + void RestoreGunPosition(void); + void RestoreHeadingRate(void); + void SetAimFlag(CEntity* to); + void SetAimFlag(float angle); + void SetAmmo(eWeaponType weaponType, uint32 ammo); + void SetEvasiveStep(CPhysical*, uint8); + void GrantAmmo(eWeaponType, uint32); + void SetEvasiveDive(CPhysical*, uint8); + void SetAttack(CEntity*); + void StartFightAttack(uint8); + void SetWaitState(eWaitState, void*); + bool FightStrike(CVector&); + int GetLocalDirection(const CVector2D &); + void StartFightDefend(uint8, uint8, uint8); + void PlayHitSound(CPed*); + void SetFall(int, AnimationId, uint8); + void SetFlee(CEntity*, int); + void SetFlee(CVector2D const &, int); + void RemoveInCarAnims(void); + void CollideWithPed(CPed*); + void SetDirectionToWalkAroundObject(CEntity*); + void CreateDeadPedMoney(void); + void CreateDeadPedWeaponPickups(void); + void SetAttackTimer(uint32); + void SetBeingDraggedFromCar(CVehicle*, uint32, bool); + void SetRadioStation(void); + void SetBuyIceCream(void); + void SetChat(CEntity*, uint32); + void DeadPedMakesTyresBloody(void); + void MakeTyresMuddySectorList(CPtrList&); + uint8 DoesLOSBulletHitPed(CColPoint &point); + bool DuckAndCover(void); + void EndFight(uint8); + void EnterCar(void); + uint8 GetNearestTrainPedPosition(CVehicle*, CVector&); + uint8 GetNearestTrainDoor(CVehicle*, CVector&); + void LineUpPedWithTrain(void); + void ExitCar(void); + void Fight(void); + bool FindBestCoordsFromNodes(CVector, CVector*); + void Wait(void); + void ProcessObjective(void); + bool SeekFollowingPath(CVector*); + void Flee(void); + void FollowPath(void); + CVector GetFormationPosition(void); + void GetNearestDoor(CVehicle*, CVector&); + bool GetNearestPassengerDoor(CVehicle*, CVector&); + int GetNextPointOnRoute(void); + uint8 GetPedRadioCategory(uint32); + int GetWeaponSlot(eWeaponType); + void GoToNearestDoor(CVehicle*); + bool HaveReachedNextPointOnRoute(float); + void Idle(void); + void InTheAir(void); + void SetLanding(void); + void InvestigateEvent(void); + bool IsPedDoingDriveByShooting(void); + bool IsRoomToBeCarJacked(void); + void SetInvestigateEvent(eEventType, CVector2D, float, uint16, float); + bool LookForInterestingNodes(void); + void LookForSexyCars(void); + void LookForSexyPeds(void); + void Mug(void); + void MoveHeadToLook(void); + void Pause(void); + void ProcessBuoyancy(void); + void ServiceTalking(void); + void SetJump(void); + void WanderPath(void); + void ReactToPointGun(CEntity*); + void SeekCar(void); + bool PositionPedOutOfCollision(void); + bool RunToReportCrime(eCrimeType); + bool PlacePedOnDryLand(void); + bool PossiblyFindBetterPosToSeekCar(CVector*, CVehicle*); + void UpdateFromLeader(void); + uint32 ScanForThreats(void); + void SetEnterCar(CVehicle*, uint32); + bool WarpPedToNearEntityOffScreen(CEntity*); + void SetExitCar(CVehicle*, uint32); + void SetFormation(eFormation); + bool WillChat(CPed*); + void SetEnterTrain(CVehicle*, uint32); + void SetEnterCar_AllClear(CVehicle*, uint32, uint32); + void SetSolicit(uint32 time); + void ScanForInterestingStuff(void); + void WarpPedIntoCar(CVehicle*); + void SetCarJack(CVehicle*); + bool WarpPedToNearLeaderOffScreen(void); + void Solicit(void); + void SetExitBoat(CVehicle*); + + // Static methods + static CVector GetLocalPositionToOpenCarDoor(CVehicle *veh, uint32 component, float offset); + static CVector GetPositionToOpenCarDoor(CVehicle *veh, uint32 component, float seatPosMult); + static CVector GetPositionToOpenCarDoor(CVehicle* veh, uint32 component); + static void Initialise(void); + static void SetAnimOffsetForEnterOrExitVehicle(void); + static void LoadFightData(void); + + // Callbacks + static void PedGetupCB(CAnimBlendAssociation *assoc, void *arg); + static void PedStaggerCB(CAnimBlendAssociation *assoc, void *arg); + static void PedEvadeCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishDieAnimCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishedWaitCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishLaunchCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishHitHeadCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimGetInCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimDoorOpenCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimPullPedOutCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimDoorCloseCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetInCarCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetOutCarCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimAlignCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetDraggedOutCarCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimStepOutCarCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetInTrainCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetOutTrainCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishedAttackCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishFightMoveCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimDoorCloseRollingCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishJumpCB(CAnimBlendAssociation *assoc, void *arg); + static void PedLandCB(CAnimBlendAssociation *assoc, void *arg); + static void RestoreHeadingRateCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetQuickDraggedOutCarPositionCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetDraggedOutCarPositionCB(CAnimBlendAssociation *assoc, void *arg); + + bool IsPlayer(void) const; + bool UseGroundColModel(void); + bool CanSetPedState(void); + bool IsPedInControl(void); + bool CanPedDriveOff(void); + bool CanBeDeleted(void); + bool CanStrafeOrMouseControl(void); + bool CanPedReturnToState(void); + void SetMoveState(eMoveState); + bool IsTemporaryObjective(eObjective objective); + void SetObjectiveTimer(int); + bool SelectGunIfArmed(void); + bool IsPointerValid(void); + void SortPeds(CPed**, int, int); + void ForceStoredObjective(eObjective); + void SetStoredObjective(void); + void SetLeader(CEntity* leader); + void SetPedStats(ePedStats); + bool IsGangMember(void) const; + void Die(void); + void EnterTrain(void); + void ExitTrain(void); + void Fall(void); + bool IsPedShootable(void); + void Look(void); + void SetInTheAir(void); + void RestoreHeadPosition(void); + void PointGunAt(void); + bool ServiceTalkingWhenDead(void); + void SetPedPositionInTrain(void); + void SetShootTimer(uint32); + void SetSeekCar(CVehicle*, uint32); + void SetSeekBoatPosition(CVehicle*); + void SetExitTrain(CVehicle*); + void WanderRange(void); + void SetFollowRoute(int16, int16); + void SeekBoatPosition(void); + void UpdatePosition(void); + CObject *SpawnFlyingComponent(int, int8); + void SetCarJack_AllClear(CVehicle*, uint32, uint32); +#ifdef VC_PED_PORTS + bool CanPedJumpThis(CEntity *unused, CVector *damageNormal = nil); +#else + bool CanPedJumpThis(CEntity*); +#endif + + bool HasWeapon(uint8 weaponType) { return m_weapons[weaponType].m_eWeaponType == weaponType; } + CWeapon &GetWeapon(uint8 weaponType) { return m_weapons[weaponType]; } + CWeapon *GetWeapon(void) { return &m_weapons[m_currentWeapon]; } + + PedState GetPedState(void) { return m_nPedState; } + void SetPedState(PedState state) { m_nPedState = state; } + bool Dead(void) { return m_nPedState == PED_DEAD; } + bool Dying(void) { return m_nPedState == PED_DIE; } + bool DyingOrDead(void) { return m_nPedState == PED_DIE || m_nPedState == PED_DEAD; } + bool OnGround(void) { return m_nPedState == PED_FALL || m_nPedState == PED_DIE || m_nPedState == PED_DEAD; } + bool OnGroundOrGettingUp(void) { return OnGround() || m_nPedState == PED_GETUP; } + + bool Driving(void) { return m_nPedState == PED_DRIVING; } + bool InVehicle(void) { return bInVehicle && m_pMyVehicle; } // True when ped is sitting/standing in vehicle, not in enter/exit state. + bool EnteringCar(void) { return m_nPedState == PED_ENTER_CAR || m_nPedState == PED_CARJACK; } + + // It was inlined in III but not in VC. + inline void + ReplaceWeaponWhenExitingVehicle(void) + { + eWeaponType weaponType = GetWeapon()->m_eWeaponType; + + // If it's Uzi, we may have stored weapon. Uzi is the only gun we can use in car. + if (IsPlayer() && weaponType == WEAPONTYPE_UZI) { + if (/*IsPlayer() && */ m_storedWeapon != WEAPONTYPE_UNIDENTIFIED) { + SetCurrentWeapon(m_storedWeapon); + m_storedWeapon = WEAPONTYPE_UNIDENTIFIED; + } + } else { + AddWeaponModel(CWeaponInfo::GetWeaponInfo(weaponType)->m_nModelId); + } + } + + // It was inlined in III but not in VC. + inline void + RemoveWeaponWhenEnteringVehicle(void) + { + if (IsPlayer() && HasWeapon(WEAPONTYPE_UZI) && GetWeapon(WEAPONTYPE_UZI).m_nAmmoTotal > 0) { + if (m_storedWeapon == WEAPONTYPE_UNIDENTIFIED) + m_storedWeapon = GetWeapon()->m_eWeaponType; + SetCurrentWeapon(WEAPONTYPE_UZI); + } else { + CWeaponInfo *ourWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(ourWeapon->m_nModelId); + } + } + bool IsNotInWreckedVehicle() + { + return m_pMyVehicle != nil && ((CEntity*)m_pMyVehicle)->GetStatus() != STATUS_WRECKED; + } + // My additions, because there were many, many instances of that. + inline void SetFindPathAndFlee(CEntity *fleeFrom, int time, bool walk = false) + { + SetFlee(fleeFrom, time); + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + if (walk) + SetMoveState(PEDMOVE_WALK); + } + + inline void SetFindPathAndFlee(CVector2D const &from, int time, bool walk = false) + { + SetFlee(from, time); + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + if (walk) + SetMoveState(PEDMOVE_WALK); + } + + inline void SetWeaponLockOnTarget(CEntity *target) + { + m_pPointGunAt = (CPed *)target; + if(target) + ((CEntity *)target)->RegisterReference(&m_pPointGunAt); + } + + // Using this to abstract nodes of skinned and non-skinned meshes + CVector GetNodePosition(int32 node) + { +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int32 idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + // this is just stupid + //RwV3dTransformPoints(&pos, &pos, 1, &mats[idx]); + pos = mats[idx].pos; + return pos; + }else +#endif + { + RwMatrix mat; + CPedIK::GetWorldMatrix(m_pFrames[node]->frame, &mat); + return mat.pos; + } + } + void TransformToNode(CVector &pos, int32 node) + { +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int32 idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + RwV3dTransformPoints(&pos, &pos, 1, &mats[idx]); + }else +#endif + { + RwFrame *frame; + for (frame = m_pFrames[node]->frame; frame; frame = RwFrameGetParent(frame)) + RwV3dTransformPoints(&pos, &pos, 1, RwFrameGetMatrix(frame)); + } + } + + // set by 0482:set_threat_reaction_range_multiplier opcode + static uint16 nThreatReactionRangeMultiplier; + + // set by 0481:set_enter_car_range_multiplier opcode + static uint16 nEnterCarRangeMultiplier; + + static bool bNastyLimbsCheat; + static bool bPedCheat2; + static bool bPedCheat3; + static CVector2D ms_vec2DFleePosition; + +#ifdef DEBUGMENU + static bool bPopHeadsOnHeadshot; +#endif + +#ifndef MASTER + // Mobile things + void DebugDrawPedDestination(CPed *, int, int); + void DebugDrawPedDesiredHeading(CPed *, int, int); + void DebugDrawCollisionRadius(float, float, float, float, int); + void DebugDrawVisionRange(CVector, float); + void DebugDrawVisionSimple(CVector, float); + void DebugDrawLook(); + void DebugDrawPedPsyche(); + void DebugDrawDebugLines(); + + static void SwitchDebugDisplay(void); + static int GetDebugDisplay(void); + + void DebugDrawLookAtPoints(); + void DebugRenderOnePedText(void); + void DebugRenderClosePedText(); +#endif + +#ifdef PED_SKIN + void renderLimb(int node); +#endif + +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif +}; + +void FinishFuckUCB(CAnimBlendAssociation *assoc, void *arg); + +#ifndef PED_SKIN +VALIDATE_SIZE(CPed, 0x53C); +#endif diff --git a/src/peds/PedAI.cpp b/src/peds/PedAI.cpp new file mode 100644 index 0000000..8bd6791 --- /dev/null +++ b/src/peds/PedAI.cpp @@ -0,0 +1,5404 @@ +#include "common.h" + +#include "main.h" +#include "Particle.h" +#include "RpAnimBlend.h" +#include "Ped.h" +#include "Wanted.h" +#include "AnimBlendAssociation.h" +#include "DMAudio.h" +#include "General.h" +#include "HandlingMgr.h" +#include "Replay.h" +#include "Camera.h" +#include "PedPlacement.h" +#include "ZoneCull.h" +#include "Pad.h" +#include "Pickups.h" +#include "Train.h" +#include "PedRoutes.h" +#include "CopPed.h" +#include "Script.h" +#include "CarCtrl.h" +#include "WaterLevel.h" +#include "CarAI.h" +#include "Zones.h" +#include "Cranes.h" + +CVector vecPedCarDoorAnimOffset; +CVector vecPedCarDoorLoAnimOffset; +CVector vecPedVanRearDoorAnimOffset; +CVector vecPedQuickDraggedOutCarAnimOffset; +CVector vecPedDraggedOutCarAnimOffset; +CVector vecPedTrainDoorAnimOffset; + +void +CPed::SetObjectiveTimer(int time) +{ + if (time == 0) { + m_objectiveTimer = 0; + } else if (CTimer::GetTimeInMilliseconds() > m_objectiveTimer) { + m_objectiveTimer = CTimer::GetTimeInMilliseconds() + time; + } +} + +void +CPed::SetStoredObjective(void) +{ + if (m_objective == m_prevObjective) + return; + + switch (m_objective) + { + case OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE: + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + case OBJECTIVE_LEAVE_CAR: + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_RUN_TO_AREA: + return; + default: + m_prevObjective = m_objective; + } +} + +void +CPed::ForceStoredObjective(eObjective objective) +{ + if (objective != OBJECTIVE_ENTER_CAR_AS_DRIVER && objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + m_prevObjective = m_objective; + return; + } + + switch (m_objective) + { + case OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE: + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_RUN_TO_AREA: + return; + default: + m_prevObjective = m_objective; + } +} + +bool +CPed::IsTemporaryObjective(eObjective objective) +{ + return objective == OBJECTIVE_LEAVE_CAR || objective == OBJECTIVE_SET_LEADER || +#ifdef VC_PED_PORTS + objective == OBJECTIVE_LEAVE_CAR_AND_DIE || +#endif + objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER; +} + +void +CPed::SetObjective(eObjective newObj) +{ + if (DyingOrDead()) + return; + + if (newObj == OBJECTIVE_NONE) { + if ((m_objective == OBJECTIVE_LEAVE_CAR || m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER +#ifdef VC_PED_PORTS + || m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) + && !IsPlayer() +#else + ) +#endif + && !IsPedInControl()) { + + bStartWanderPathOnFoot = true; + return; + } + // Unused code from assembly... + /* + else if(m_objective == OBJECTIVE_FLEE_CAR) { + + } else { + + } + */ + m_objective = OBJECTIVE_NONE; + m_prevObjective = OBJECTIVE_NONE; + } else if (m_prevObjective != newObj || m_prevObjective == OBJECTIVE_NONE) { + SetObjectiveTimer(0); + + if (m_objective == newObj) + return; + + if (IsTemporaryObjective(m_objective)) { + m_prevObjective = newObj; + } else { + if (m_objective != newObj) + SetStoredObjective(); + + m_objective = newObj; + } + bObjectiveCompleted = false; + + switch (newObj) { + case OBJECTIVE_NONE: + m_prevObjective = OBJECTIVE_NONE; + break; + case OBJECTIVE_HAIL_TAXI: + m_nWaitTimer = 0; + SetIdle(); + SetMoveState(PEDMOVE_STILL); + break; + default: + break; + } + } +} + +void +CPed::SetObjective(eObjective newObj, void *entity) +{ + if (DyingOrDead()) + return; + + if (m_prevObjective == newObj) { + // Why? + if (m_prevObjective != OBJECTIVE_NONE) + return; + } + + if (entity == this) + return; + + SetObjectiveTimer(0); + if (m_objective == newObj) { + switch (newObj) { + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + case OBJECTIVE_GOTO_AREA_ANY_MEANS: + case OBJECTIVE_GUARD_ATTACK: + if (m_pedInObjective == entity) + return; + + break; + case OBJECTIVE_LEAVE_CAR: + case OBJECTIVE_FLEE_CAR: +#ifdef VC_PED_PORTS + case OBJECTIVE_LEAVE_CAR_AND_DIE: +#endif + return; + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + case OBJECTIVE_DESTROY_CAR: + case OBJECTIVE_SOLICIT_VEHICLE: + case OBJECTIVE_BUY_ICE_CREAM: + if (m_carInObjective == entity) + return; + + break; + case OBJECTIVE_SET_LEADER: + if (m_leader == entity) + return; + + break; + default: + break; + } + } else { + if ((newObj == OBJECTIVE_LEAVE_CAR +#ifdef VC_PED_PORTS + || newObj == OBJECTIVE_LEAVE_CAR_AND_DIE +#endif + ) && !bInVehicle) + return; + } + +#ifdef VC_PED_PORTS + ClearPointGunAt(); +#endif + bObjectiveCompleted = false; + if (IsTemporaryObjective(m_objective) && !IsTemporaryObjective(newObj)) { + m_prevObjective = newObj; + } else { + if (m_objective != newObj) { + if (IsTemporaryObjective(newObj)) + ForceStoredObjective(newObj); + else + SetStoredObjective(); + } + m_objective = newObj; + } + + switch (newObj) { + case OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT: + + // In this special case, entity parameter isn't CEntity, but int. + SetObjectiveTimer((uintptr)entity); + break; + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_MUG_CHAR: + m_pNextPathNode = nil; + bUsePedNodeSeek = false; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_pedInObjective = (CPed*)entity; + m_pedInObjective->RegisterReference((CEntity**)&m_pedInObjective); + m_pLookTarget = (CEntity*)entity; + m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget); + break; + case OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_GUARD_ATTACK: + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_pedInObjective = (CPed*)entity; + m_pedInObjective->RegisterReference((CEntity**)&m_pedInObjective); + break; + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + m_pedInObjective = (CPed*)entity; + m_pedInObjective->RegisterReference((CEntity**)&m_pedInObjective); + m_pedFormation = FORMATION_REAR; + break; + case OBJECTIVE_LEAVE_CAR: +#ifdef VC_PED_PORTS + case OBJECTIVE_LEAVE_CAR_AND_DIE: +#endif + case OBJECTIVE_FLEE_CAR: + m_carInObjective = (CVehicle*)entity; + m_carInObjective->RegisterReference((CEntity **)&m_carInObjective); + if (m_carInObjective->bIsBus && m_leaveCarTimer == 0) { + for (int i = 0; i < m_carInObjective->m_nNumMaxPassengers; i++) { + if (m_carInObjective->pPassengers[i] == this) { + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 1200 * i; + break; + } + } + } + + break; + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + if (m_nMoveState == PEDMOVE_STILL) + SetMoveState(PEDMOVE_RUN); + + if (((CVehicle*)entity)->m_vehType == VEHICLE_TYPE_BOAT && !IsPlayer()) { + RestorePreviousObjective(); + break; + } + // fall through + case OBJECTIVE_DESTROY_CAR: + case OBJECTIVE_SOLICIT_VEHICLE: + case OBJECTIVE_BUY_ICE_CREAM: + m_carInObjective = (CVehicle*)entity; + m_carInObjective->RegisterReference((CEntity**)&m_carInObjective); + m_pSeekTarget = m_carInObjective; + m_pSeekTarget->RegisterReference((CEntity**)&m_pSeekTarget); + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + if (newObj == OBJECTIVE_SOLICIT_VEHICLE) { + m_objectiveTimer = CTimer::GetTimeInMilliseconds() + 10000; + } else if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER && CharCreatedBy == MISSION_CHAR && + (m_carInObjective->GetStatus() == STATUS_PLAYER_DISABLED || CPad::GetPad(CWorld::PlayerInFocus)->ArePlayerControlsDisabled())) { + SetObjectiveTimer(14000); + } else { + m_objectiveTimer = 0; + } + break; + case OBJECTIVE_SET_LEADER: + SetLeader((CEntity*)entity); + RestorePreviousObjective(); + break; + default: + break; + } +} + +void +CPed::SetObjective(eObjective newObj, CVector dest, float safeDist) +{ + if (DyingOrDead()) + return; + + if (m_prevObjective != OBJECTIVE_NONE && m_prevObjective == newObj) + return; + + SetObjectiveTimer(0); + if (m_objective == newObj) { + if (newObj == OBJECTIVE_GOTO_AREA_ANY_MEANS || newObj == OBJECTIVE_GOTO_AREA_ON_FOOT || newObj == OBJECTIVE_RUN_TO_AREA) { + if (m_nextRoutePointPos == dest && m_distanceToCountSeekDone == safeDist) + return; + } else if (newObj == OBJECTIVE_GUARD_SPOT) { + if (m_vecSeekPosEx == dest && m_distanceToCountSeekDoneEx == safeDist) + return; + } + } + +#ifdef VC_PED_PORTS + ClearPointGunAt(); +#endif + bObjectiveCompleted = false; + if (IsTemporaryObjective(m_objective)) { + m_prevObjective = newObj; + } else { + if (m_objective != newObj) + SetStoredObjective(); + + m_objective = newObj; + } + + if (newObj == OBJECTIVE_GUARD_SPOT) { + m_vecSeekPosEx = dest; + m_distanceToCountSeekDoneEx = safeDist; + } else if (newObj == OBJECTIVE_GOTO_AREA_ANY_MEANS || newObj == OBJECTIVE_GOTO_AREA_ON_FOOT || newObj == OBJECTIVE_RUN_TO_AREA) { + m_pNextPathNode = nil; + m_nextRoutePointPos = dest; + m_vecSeekPos = m_nextRoutePointPos; + bUsePedNodeSeek = true; + } +} + +// Only used in 01E1: SET_CHAR_OBJ_FOLLOW_ROUTE opcode +// IDA fails very badly in here, puts a fake loop and ignores SetFollowRoute call... +void +CPed::SetObjective(eObjective newObj, int16 routePoint, int16 routeType) +{ + if (DyingOrDead()) + return; + + if (m_prevObjective == newObj && m_prevObjective != OBJECTIVE_NONE) + return; + + SetObjectiveTimer(0); + + if (m_objective == newObj && newObj == OBJECTIVE_FOLLOW_ROUTE && m_routeLastPoint == routePoint && m_routeType == routeType) + return; + + bObjectiveCompleted = false; + if (IsTemporaryObjective(m_objective)) { + m_prevObjective = newObj; + } else { + if (m_objective != newObj) + SetStoredObjective(); + + m_objective = newObj; + } + + if (newObj == OBJECTIVE_FOLLOW_ROUTE) { + SetFollowRoute(routePoint, routeType); + } +} + +void +CPed::SetObjective(eObjective newObj, CVector dest) +{ + if (DyingOrDead()) + return; + + if (m_prevObjective != OBJECTIVE_NONE && m_prevObjective == newObj) + return; + + SetObjectiveTimer(0); + if (m_objective == newObj) { + if (newObj == OBJECTIVE_GOTO_AREA_ANY_MEANS || newObj == OBJECTIVE_GOTO_AREA_ON_FOOT || newObj == OBJECTIVE_RUN_TO_AREA) { + if (m_nextRoutePointPos == dest) + return; + } else if (newObj == OBJECTIVE_GUARD_SPOT) { + if (m_vecSeekPosEx == dest) + return; + } + } + +#ifdef VC_PED_PORTS + ClearPointGunAt(); +#endif + bObjectiveCompleted = false; + switch (newObj) { + case OBJECTIVE_GUARD_SPOT: + m_vecSeekPosEx = dest; + m_distanceToCountSeekDoneEx = 5.0f; + SetMoveState(PEDMOVE_STILL); + break; + case OBJECTIVE_GUARD_AREA: + case OBJECTIVE_WAIT_IN_CAR: + case OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT: + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + case OBJECTIVE_LEAVE_CAR: + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + case OBJECTIVE_FOLLOW_CAR_IN_CAR: + case OBJECTIVE_FIRE_AT_OBJECT_FROM_VEHICLE: + case OBJECTIVE_DESTROY_OBJECT: + case OBJECTIVE_DESTROY_CAR: + break; + case OBJECTIVE_GOTO_AREA_ANY_MEANS: + case OBJECTIVE_GOTO_AREA_ON_FOOT: + bIsRunning = false; + m_pNextPathNode = nil; + m_nextRoutePointPos = dest; + m_vecSeekPos = m_nextRoutePointPos; + m_distanceToCountSeekDone = 0.5f; + bUsePedNodeSeek = true; + if (sq(m_distanceToCountSeekDone) > (m_nextRoutePointPos - GetPosition()).MagnitudeSqr2D()) + return; + break; + case OBJECTIVE_RUN_TO_AREA: + bIsRunning = true; + m_pNextPathNode = nil; + m_nextRoutePointPos = dest; + m_vecSeekPos = m_nextRoutePointPos; + m_distanceToCountSeekDone = 0.5f; + bUsePedNodeSeek = true; + if (sq(m_distanceToCountSeekDone) > (m_nextRoutePointPos - GetPosition()).MagnitudeSqr2D()) + return; + break; + default: break; + } + + if (IsTemporaryObjective(m_objective)) { + m_prevObjective = newObj; + } else { + if (m_objective != newObj) + SetStoredObjective(); + + m_objective = newObj; + } +} + +void +CPed::ClearObjective(void) +{ + if (IsPedInControl() || m_nPedState == PED_DRIVING) { + m_objective = OBJECTIVE_NONE; +#ifdef VC_PED_PORTS + m_pedInObjective = nil; + m_carInObjective = nil; +#endif + if (m_nPedState == PED_DRIVING && m_pMyVehicle) { + + if (m_pMyVehicle->pDriver != this) { +#if defined VC_PED_PORTS || defined FIX_BUGS + if(!IsPlayer()) +#endif + bWanderPathAfterExitingCar = true; + + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } +#ifdef VC_PED_PORTS + m_nLastPedState = PED_NONE; +#endif + } else { + SetIdle(); + SetMoveState(PEDMOVE_STILL); + } + } else { + bClearObjective = true; + } +} + +void +CPed::ClearLeader(void) +{ + if (!m_leader) + return; + + m_leader = nil; + if (IsPedInControl()) { + SetObjective(OBJECTIVE_NONE); + if (CharCreatedBy == MISSION_CHAR) { + SetIdle(); + } else { + SetWanderPath(CGeneral::GetRandomNumberInRange(0,8)); + } + } else if (m_objective != OBJECTIVE_NONE) { + bClearObjective = true; + } +} + +void +CPed::UpdateFromLeader(void) +{ + if (CTimer::GetTimeInMilliseconds() <= m_objectiveTimer) + return; + + if (!m_leader) + return; + + CVector leaderDist; + if (m_leader->InVehicle()) + leaderDist = m_leader->m_pMyVehicle->GetPosition() - GetPosition(); + else + leaderDist = m_leader->GetPosition() - GetPosition(); + + if (leaderDist.Magnitude() > 30.0f) { + if (IsPedInControl()) { + SetObjective(OBJECTIVE_NONE); + SetIdle(); + SetMoveState(PEDMOVE_STILL); + } + SetLeader(nil); + return; + } + + if (IsPedInControl()) { + if (m_nWaitState == WAITSTATE_PLAYANIM_TAXI) + WarpPedToNearLeaderOffScreen(); + + if (m_leader->m_nPedState == PED_DEAD) { + SetLeader(nil); + SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + return; + } + if (!m_leader->bInVehicle) { + if (m_leader->m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (bInVehicle) { + if (m_objective != OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT && m_objective != OBJECTIVE_LEAVE_CAR) + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + + return; + } + if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + RestorePreviousObjective(); + RestorePreviousState(); + } + } + if (m_nPedType == PEDTYPE_PROSTITUTE && CharCreatedBy == RANDOM_CHAR) { + SetLeader(nil); + return; + } + } + if (!bInVehicle && m_leader->bInVehicle && m_leader->m_nPedState == PED_DRIVING) { + if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER && m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (m_leader->m_pMyVehicle->m_nNumPassengers < m_leader->m_pMyVehicle->m_nNumMaxPassengers) + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_leader->m_pMyVehicle); + } + } else if (m_leader->m_objective == OBJECTIVE_NONE || (m_leader->IsPlayer() && m_leader->m_objective == OBJECTIVE_WAIT_ON_FOOT) + || m_objective == m_leader->m_objective) { + + if (m_leader->m_nPedState == PED_ATTACK) { + CEntity *lookTargetOfLeader = m_leader->m_pLookTarget; + if (lookTargetOfLeader && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT + && lookTargetOfLeader->IsPed() && lookTargetOfLeader != this) { + + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, lookTargetOfLeader); + SetObjectiveTimer(8000); + SetLookFlag(m_leader->m_pLookTarget, false); + SetLookTimer(500); + } + } else { + if (IsPedInControl() && m_nPedState != PED_ATTACK) { +#ifndef VC_PED_PORTS + SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT, m_leader); + SetObjectiveTimer(0); +#else + if (m_leader->m_objective == OBJECTIVE_NONE && m_objective == OBJECTIVE_NONE + && m_leader->m_nPedState == PED_CHAT && m_nPedState == PED_CHAT) { + + SetObjective(OBJECTIVE_NONE); + } else { + SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT, m_leader); + SetObjectiveTimer(0); + } +#endif + } + if (m_nPedState == PED_IDLE && m_leader->IsPlayer()) { + if (ScanForThreats() && m_threatEntity) { + m_pLookTarget = m_threatEntity; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + if (m_attackTimer < CTimer::GetTimeInMilliseconds() && !GetWeapon()->IsTypeMelee()) { + SetWeaponLockOnTarget(m_threatEntity); + SetAttack(m_threatEntity); + } + } + } + } + } else { + switch (m_leader->m_objective) { + case OBJECTIVE_WAIT_ON_FOOT: + case OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE: + case OBJECTIVE_WAIT_IN_CAR: + case OBJECTIVE_FOLLOW_ROUTE: + SetObjective(m_leader->m_objective); + m_objectiveTimer = m_leader->m_objectiveTimer; + break; + case OBJECTIVE_GUARD_SPOT: + SetObjective(OBJECTIVE_GUARD_SPOT, m_leader->m_vecSeekPosEx); + m_objectiveTimer = m_leader->m_objectiveTimer; + break; + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + if (m_leader->m_pedInObjective) { + SetObjective(m_leader->m_objective, m_leader->m_pedInObjective); + m_objectiveTimer = m_leader->m_objectiveTimer; + } + break; + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + if (m_leader->m_carInObjective) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_leader->m_carInObjective); + return; + } + break; + case OBJECTIVE_GUARD_ATTACK: + return; + case OBJECTIVE_HAIL_TAXI: + m_leader = nil; + SetObjective(OBJECTIVE_NONE); + break; + default: + SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT, m_leader); + SetObjectiveTimer(0); + break; + } + } + } else if (bInVehicle) { + if ((!m_leader->bInVehicle || m_leader->m_nPedState == PED_EXIT_CAR) && m_objective != OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT) { + + switch (m_leader->m_objective) { + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + if (m_pMyVehicle == m_leader->m_pMyVehicle || m_pMyVehicle == m_leader->m_carInObjective) + break; + + // fall through + default: + if (m_pMyVehicle && m_objective != OBJECTIVE_LEAVE_CAR) { +#ifdef VC_PED_PORTS + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 250; +#endif + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + + break; + } + } + } +} + +void +CPed::RestorePreviousObjective(void) +{ + if (m_objective == OBJECTIVE_NONE) + return; + + if (m_objective != OBJECTIVE_LEAVE_CAR && m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER && m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER +#if defined VC_PED_PORTS || defined FIX_BUGS + && m_nPedState != PED_CARJACK +#endif + ) + m_pedInObjective = nil; + + if (m_objective == OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT) { + m_objective = OBJECTIVE_NONE; + if (m_pMyVehicle) + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + + } else { + m_objective = m_prevObjective; + m_prevObjective = OBJECTIVE_NONE; + } + bObjectiveCompleted = false; +} + +void +CPed::ProcessObjective(void) +{ + if (bClearObjective && (IsPedInControl() || m_nPedState == PED_DRIVING)) { + ClearObjective(); + bClearObjective = false; + } + UpdateFromLeader(); + + CVector carOrOurPos; + CVector targetCarOrHisPos; + CVector distWithTarget; + + if (m_objective != OBJECTIVE_NONE && (IsPedInControl() || m_nPedState == PED_DRIVING)) { + if (bInVehicle) { + if (!m_pMyVehicle) { + bInVehicle = false; + return; + } + carOrOurPos = m_pMyVehicle->GetPosition(); + } else { + carOrOurPos = GetPosition(); + } + + if (m_pedInObjective) { + if (m_pedInObjective->InVehicle() && m_pedInObjective->m_nPedState != PED_DRAG_FROM_CAR) { + targetCarOrHisPos = m_pedInObjective->m_pMyVehicle->GetPosition(); + } else { + targetCarOrHisPos = m_pedInObjective->GetPosition(); + } + distWithTarget = targetCarOrHisPos - carOrOurPos; + } else if (m_carInObjective) { + targetCarOrHisPos = m_carInObjective->GetPosition(); + distWithTarget = targetCarOrHisPos - carOrOurPos; + } + + switch (m_objective) { + case OBJECTIVE_NONE: + case OBJECTIVE_GUARD_AREA: + case OBJECTIVE_FOLLOW_CAR_IN_CAR: + case OBJECTIVE_FIRE_AT_OBJECT_FROM_VEHICLE: + case OBJECTIVE_DESTROY_OBJECT: + case OBJECTIVE_GOTO_AREA_IN_CAR: + case OBJECTIVE_FOLLOW_CAR_ON_FOOT_WITH_OFFSET: + case OBJECTIVE_SET_LEADER: + break; + case OBJECTIVE_WAIT_ON_FOOT: + SetIdle(); + m_objective = OBJECTIVE_NONE; + SetMoveState(PEDMOVE_STILL); + break; + case OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE: + if (InVehicle()) { + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + bFleeAfterExitingCar = true; + } else if (m_nPedState != PED_FLEE_POS) { + CVector2D fleePos = GetPosition(); + SetFlee(fleePos, 10000); + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + } + break; + case OBJECTIVE_GUARD_SPOT: + { + distWithTarget = m_vecSeekPosEx - GetPosition(); + if (m_pedInObjective) { + SetLookFlag(m_pedInObjective, true); + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + } + float distWithTargetSc = distWithTarget.Magnitude(); + if (2.0f * m_distanceToCountSeekDoneEx >= distWithTargetSc) { + if (m_pedInObjective) { + if (distWithTargetSc <= m_distanceToCountSeekDoneEx) + SetIdle(); + else + SetSeek(m_vecSeekPosEx, m_distanceToCountSeekDoneEx); + } else if (CTimer::GetTimeInMilliseconds() > m_lookTimer) { + int threatType = ScanForThreats(); + SetLookTimer(CGeneral::GetRandomNumberInRange(500, 1500)); + + // Second condition is pointless and isn't there in Mobile. + if (threatType == PED_FLAG_GUN || (threatType == PED_FLAG_EXPLOSION && m_threatEntity) || m_threatEntity) { + if (m_threatEntity->IsPed()) + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + } + } + } else { + SetSeek(m_vecSeekPosEx, m_distanceToCountSeekDoneEx); + } + break; + } + case OBJECTIVE_WAIT_IN_CAR: + SetPedState(PED_DRIVING); + break; + case OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT: + SetPedState(PED_DRIVING); + break; + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + { + if (m_pedInObjective) { + if (m_pedInObjective->IsPlayer() && CharCreatedBy != MISSION_CHAR + && m_nPedType != PEDTYPE_COP && FindPlayerPed()->m_pWanted->m_CurrentCops != 0 + && !bKindaStayInSamePlace) { + + SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + break; + } + if (InVehicle()) { + if (distWithTarget.Magnitude() >= 20.0f + || m_pMyVehicle->m_vecMoveSpeed.MagnitudeSqr() >= sq(0.02f)) { + if (m_pMyVehicle->pDriver == this + && !m_pMyVehicle->m_nGettingInFlags) { + m_pMyVehicle->SetStatus(STATUS_PHYSICS); + m_pMyVehicle->AutoPilot.m_nPrevRouteNode = 0; + if (m_nPedType == PEDTYPE_COP) { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = (FindPlayerPed()->m_pWanted->GetWantedLevel() * 0.1f + 0.6f) * (GAME_SPEED_TO_CARAI_SPEED * m_pMyVehicle->pHandling->Transmission.fMaxCruiseVelocity); + m_pMyVehicle->AutoPilot.m_nCarMission = CCarAI::FindPoliceCarMissionForWantedLevel(); + } else { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = GAME_SPEED_TO_CARAI_SPEED * m_pMyVehicle->pHandling->Transmission.fMaxCruiseVelocity * 0.8f; + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_RAMPLAYER_FARAWAY; + } + m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + } + } else { + bool targetHasVeh = m_pedInObjective->bInVehicle; + if (!targetHasVeh + || targetHasVeh && m_pedInObjective->m_pMyVehicle->CanPedExitCar()) { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } + break; + } + if (distWithTarget.Magnitude() > 30.0f && !bKindaStayInSamePlace) { + if (m_pMyVehicle) { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } else { + float closestVehDist = 60.0f; + int16 lastVehicle; + CEntity* vehicles[8]; + CWorld::FindObjectsInRange(GetPosition(), 25.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + CVehicle *foundVeh = nil; + for(int i = 0; i < lastVehicle; i++) { + CVehicle *nearVeh = (CVehicle*)vehicles[i]; + /* + Not used. + CVector vehSpeed = nearVeh->GetSpeed(); + CVector ourSpeed = GetSpeed(); + */ + CVector vehDistVec = nearVeh->GetPosition() - GetPosition(); + if (vehDistVec.Magnitude() < closestVehDist && m_pedInObjective->m_pMyVehicle != nearVeh + && nearVeh->CanPedOpenLocks(this)) { + + foundVeh = nearVeh; + closestVehDist = vehDistVec.Magnitude(); + } + } + m_pMyVehicle = foundVeh; + if (m_pMyVehicle) { + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } else if (!GetIsOnScreen()) { + CVector ourPos = GetPosition(); + int closestNode = ThePaths.FindNodeClosestToCoors(ourPos, PATH_CAR, 20.0f); + if (closestNode >= 0) { + int16 colliding; + CWorld::FindObjectsKindaColliding( + ThePaths.m_pathNodes[closestNode].GetPosition(), 10.0f, true, &colliding, 2, nil, false, true, true, false, false); + if (!colliding) { + CZoneInfo zoneInfo; + int chosenCarClass; + CTheZones::GetZoneInfoForTimeOfDay(&ourPos, &zoneInfo); + int chosenModel = CCarCtrl::ChooseModel(&zoneInfo, &ourPos, &chosenCarClass); + CAutomobile *newVeh = new CAutomobile(chosenModel, RANDOM_VEHICLE); + if (newVeh) { + newVeh->GetMatrix().GetPosition() = ThePaths.m_pathNodes[closestNode].GetPosition(); + newVeh->GetMatrix().GetPosition().z += 4.0f; + newVeh->SetHeading(DEGTORAD(200.0f)); + newVeh->SetStatus(STATUS_ABANDONED); + newVeh->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(newVeh); + m_pMyVehicle = newVeh; + if (m_pMyVehicle) { + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } + } + } + } + } + break; + } + } else { + ClearLookFlag(); + bObjectiveCompleted = true; + } + } + case OBJECTIVE_KILL_CHAR_ON_FOOT: + { + bool killPlayerInNoPoliceZone = false; + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && InVehicle()) { + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + break; + } + if (!m_pedInObjective || m_pedInObjective->DyingOrDead()) { + ClearLookFlag(); + bObjectiveCompleted = true; + SetMoveAnim(); + break; + } + if (m_pedInObjective->IsPlayer() && CCullZones::NoPolice()) + killPlayerInNoPoliceZone = true; + + if (!bNotAllowedToDuck || killPlayerInNoPoliceZone) { + if (m_nPedType == PEDTYPE_COP && !m_pedInObjective->GetWeapon()->IsTypeMelee() && !GetWeapon()->IsTypeMelee()) + bNotAllowedToDuck = true; + } else { + if (!m_pedInObjective->bInVehicle) { + if (m_pedInObjective->GetWeapon()->IsTypeMelee() || GetWeapon()->IsTypeMelee()) { + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + } else if (DuckAndCover()) { + break; + } + } else { + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + } + } + if (m_leaveCarTimer > CTimer::GetTimeInMilliseconds() && !bKindaStayInSamePlace) { + SetMoveState(PEDMOVE_STILL); + break; + } + if (m_pedInObjective->IsPlayer()) { + CPlayerPed *player = FindPlayerPed(); + if (m_nPedType == PEDTYPE_COP && player->m_pWanted->m_bIgnoredByCops + || player->m_pWanted->m_bIgnoredByEveryone + || m_pedInObjective->bIsInWater + || m_pedInObjective->m_nPedState == PED_ARRESTED) { + + if (m_nPedState != PED_ARREST_PLAYER) + SetIdle(); + + break; + } + } + CWeaponInfo *wepInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + float wepRange = wepInfo->m_fRange; + float maxDistToKeep; + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + maxDistToKeep = wepRange / 3.0f; + } else { + if (m_nPedState == PED_FIGHT) { + if (!IsPlayer() && !(m_pedStats->m_flags & STAT_CAN_KICK)) + wepRange = 2.0f; + } else { + wepRange = 1.3f; + } + maxDistToKeep = wepRange; + } + if (m_pedInObjective->m_getUpTimer > CTimer::GetTimeInMilliseconds() && maxDistToKeep < 2.5f) { + maxDistToKeep = 2.5f; + } + if (m_pedInObjective->IsPlayer() && m_nPedType != PEDTYPE_COP + && CharCreatedBy != MISSION_CHAR && FindPlayerPed()->m_pWanted->m_CurrentCops) { + SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + break; + } + if (m_pedInObjective->m_fHealth <= 0.0f) { + bObjectiveCompleted = true; + bScriptObjectiveCompleted = true; + SetMoveAnim(); + break; + } + float distWithTargetSc = distWithTarget.Magnitude(); + if (m_pedInObjective->bInVehicle && m_pedInObjective->m_nPedState != PED_DRAG_FROM_CAR) { + CVehicle *vehOfTarget = m_pedInObjective->m_pMyVehicle; + if (vehOfTarget->bIsInWater || vehOfTarget->GetStatus() == STATUS_PLAYER_DISABLED + || m_pedInObjective->IsPlayer() && CPad::GetPad(0)->ArePlayerControlsDisabled()) { + SetIdle(); + return; + } + SetLookFlag(vehOfTarget, false); + if (m_nPedState != PED_CARJACK) { + if (m_pedInObjective->m_nPedState != PED_ARRESTED) { + if (m_attackTimer < CTimer::GetTimeInMilliseconds() && wepInfo->m_eWeaponFire != WEAPON_FIRE_MELEE + && distWithTargetSc < wepRange && distWithTargetSc > 3.0f) { + + // I hope so + CVector ourHead = GetMatrix() * CVector(0.5f, 0.0f, 0.6f); + CVector maxShotPos = vehOfTarget->GetPosition() - ourHead; + maxShotPos *= wepInfo->m_fRange / maxShotPos.Magnitude(); + maxShotPos += ourHead; + + CColPoint foundCol; + CEntity *foundEnt; + + CWorld::bIncludeDeadPeds = true; + CWorld::ProcessLineOfSight(ourHead, maxShotPos, foundCol, foundEnt, true, true, true, true, false, true, false); + CWorld::bIncludeDeadPeds = false; + if (foundEnt == vehOfTarget) { + SetAttack(vehOfTarget); + SetWeaponLockOnTarget(vehOfTarget); + SetShootTimer(CGeneral::GetRandomNumberInRange(500, 2000)); + if (distWithTargetSc <= m_distanceToCountSeekDone) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(200, 500)); + SetMoveState(PEDMOVE_STILL); + } else { + SetAttackTimer(CGeneral::GetRandomNumberInRange(2000, 5000)); + } + } + } else if (m_nPedState != PED_ATTACK && !bKindaStayInSamePlace && !killPlayerInNoPoliceZone) { + if (vehOfTarget) { + if (m_nPedType == PEDTYPE_COP || vehOfTarget->bIsBus) { + GoToNearestDoor(vehOfTarget); + } else { + m_vehDoor = 0; + if (m_pedInObjective == vehOfTarget->pDriver || vehOfTarget->bIsBus) { + m_vehDoor = CAR_DOOR_LF; + } else if (m_pedInObjective == vehOfTarget->pPassengers[0]) { + m_vehDoor = CAR_DOOR_RF; + } else if (m_pedInObjective == vehOfTarget->pPassengers[1]) { + m_vehDoor = CAR_DOOR_LR; + } else if (m_pedInObjective == vehOfTarget->pPassengers[2]) { + m_vehDoor = CAR_DOOR_RR; + } + // Unused + // GetPositionToOpenCarDoor(vehOfTarget, m_vehDoor); + SetSeekCar(vehOfTarget, m_vehDoor); + SetMoveState(PEDMOVE_RUN); + } + } + } + } + } + SetMoveAnim(); + break; + } + if (m_nMoveState == PEDMOVE_STILL && IsPedInControl()) { + SetLookFlag(m_pedInObjective, false); + TurnBody(); + } + if (m_nPedType == PEDTYPE_COP && distWithTargetSc < 1.5f && m_pedInObjective->IsPlayer()) { + if (m_pedInObjective->m_getUpTimer > CTimer::GetTimeInMilliseconds() + || m_pedInObjective->m_nPedState == PED_DRAG_FROM_CAR) { + + ((CCopPed*)this)->SetArrestPlayer(m_pedInObjective); + return; + } + } + if (!bKindaStayInSamePlace && !bStopAndShoot && m_nPedState != PED_ATTACK && !killPlayerInNoPoliceZone) { + if (distWithTargetSc > wepRange + || m_pedInObjective->m_getUpTimer > CTimer::GetTimeInMilliseconds() + || m_pedInObjective->m_nPedState == PED_ARRESTED + || m_pedInObjective->EnteringCar() && distWithTargetSc < 3.0f + || distWithTargetSc > m_distanceToCountSeekDone && !CanSeeEntity(m_pedInObjective)) { + + if (m_pedInObjective->EnteringCar()) + maxDistToKeep = 2.0f; + + if (bUsePedNodeSeek) { + CVector bestCoords(0.0f, 0.0f, 0.0f); + m_vecSeekPos = m_pedInObjective->GetPosition(); + + if (!m_pNextPathNode) + FindBestCoordsFromNodes(m_vecSeekPos, &bestCoords); + + if (m_pNextPathNode) + m_vecSeekPos = m_pNextPathNode->GetPosition(); + + SetSeek(m_vecSeekPos, m_distanceToCountSeekDone); + } else { + SetSeek(m_pedInObjective, maxDistToKeep); + } + bCrouchWhenShooting = false; + if (m_pedInObjective->m_pCurrentPhysSurface && distWithTargetSc < 5.0f) { + if (wepRange <= 5.0f) { + if (m_pedInObjective->IsPlayer() + && FindPlayerPed()->m_bSpeedTimerFlag + && (IsGangMember() || m_nPedType == PEDTYPE_COP) + && GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) { + GiveWeapon(WEAPONTYPE_COLT45, 1000); + SetCurrentWeapon(WEAPONTYPE_COLT45); + } + } else { + bStopAndShoot = true; + } + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + break; + } + bStopAndShoot = false; + SetMoveAnim(); + break; + } + } + if (m_attackTimer < CTimer::GetTimeInMilliseconds() + && distWithTargetSc < wepRange && m_pedInObjective->m_nPedState != PED_GETUP && m_pedInObjective->m_nPedState != PED_DRAG_FROM_CAR) { + if (bIsDucking) { + CAnimBlendAssociation *duckAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (duckAnim) { + duckAnim->blendDelta = -2.0f; + break; + } + bIsDucking = false; + } else if (wepRange <= 5.0f) { + SetMoveState(PEDMOVE_STILL); + SetAttack(m_pedInObjective); + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + m_pedInObjective->GetPosition().x, m_pedInObjective->GetPosition().y, + GetPosition().x, GetPosition().y); + SetShootTimer(CGeneral::GetRandomNumberInRange(0.0f, 500.0f)); + SetAttackTimer(CGeneral::GetRandomNumberInRange(0.0f, 1500.0f)); + bObstacleShowedUpDuringKillObjective = false; + + } else { + CVector target; + CVector ourHead = GetMatrix() * CVector(0.5f, 0.0f, 0.6f); + if (m_pedInObjective->IsPed()) + m_pedInObjective->m_pedIK.GetComponentPosition(target, PED_MID); + else + target = m_pedInObjective->GetPosition(); + + target -= ourHead; + target *= wepInfo->m_fRange / target.Magnitude(); + target += ourHead; + + CColPoint foundCol; + CEntity *foundEnt = nil; + + CWorld::bIncludeDeadPeds = true; + CWorld::ProcessLineOfSight(ourHead, target, foundCol, foundEnt, true, true, true, true, false, true, false); + CWorld::bIncludeDeadPeds = false; + if (foundEnt == m_pedInObjective) { + SetAttack(m_pedInObjective); + SetWeaponLockOnTarget(m_pedInObjective); + SetShootTimer(CGeneral::GetRandomNumberInRange(500.0f, 2000.0f)); + + int time; + if (distWithTargetSc <= maxDistToKeep) + time = CGeneral::GetRandomNumberInRange(100.0f, 500.0f); + else + time = CGeneral::GetRandomNumberInRange(1500.0f, 3000.0f); + + SetAttackTimer(time); + bObstacleShowedUpDuringKillObjective = false; + + } else { + if (foundEnt) { + if (foundEnt->IsPed()) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(500.0f, 1000.0f)); + bObstacleShowedUpDuringKillObjective = false; + } else { + if (foundEnt->IsObject()) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(200.0f, 400.0f)); + bObstacleShowedUpDuringKillObjective = true; + } else if (foundEnt->IsVehicle()) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(400.0f, 600.0f)); + bObstacleShowedUpDuringKillObjective = true; + } else { + SetAttackTimer(CGeneral::GetRandomNumberInRange(700.0f, 1200.0f)); + bObstacleShowedUpDuringKillObjective = true; + } + } + + m_fleeFrom = foundEnt; + m_fleeFrom->RegisterReference((CEntity**) &m_fleeFrom); + } + SetPointGunAt(m_pedInObjective); + } + } + } else { + if (!m_pedInObjective->m_pCurrentPhysSurface) + bStopAndShoot = false; + + if (m_nPedState != PED_ATTACK && m_nPedState != PED_FIGHT) { + + // This is weird... + if (bNotAllowedToDuck && bKindaStayInSamePlace) { + if (!bIsDucking) { + CAnimBlendAssociation* duckAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (!duckAnim || duckAnim->blendDelta < 0.0f) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_DUCK_DOWN, 4.0f); + bIsDucking = true; + } + break; + } else { + CAnimBlendAssociation* duckAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (!duckAnim || duckAnim->blendDelta < 0.0f) { + bIsDucking = false; + } else { + break; + } + } + } + if (bObstacleShowedUpDuringKillObjective) { + if (m_nPedType == PEDTYPE_COP) { + if (GetWeapon()->m_eWeaponType > WEAPONTYPE_COLT45 + || m_fleeFrom && m_fleeFrom->IsObject()) { + maxDistToKeep = 6.0f; + } else if (m_fleeFrom && m_fleeFrom->IsVehicle()) { + maxDistToKeep = 4.0f; + } else { + maxDistToKeep = 2.0f; + } + } else { + maxDistToKeep = 2.0f; + } + } + if (distWithTargetSc <= maxDistToKeep) { + SetMoveState(PEDMOVE_STILL); + bIsPointingGunAt = true; + if (m_nPedState != PED_AIM_GUN && !bDuckAndCover) { + m_attackTimer = CTimer::GetTimeInMilliseconds(); + SetIdle(); + } + } else { + if (m_nPedState != PED_SEEK_ENTITY && m_nPedState != PED_SEEK_POS + && !bStopAndShoot && !killPlayerInNoPoliceZone && !bKindaStayInSamePlace) { + Say(SOUND_PED_ATTACK); + SetSeek(m_pedInObjective, maxDistToKeep); + bIsRunning = true; + } + } + } + } + + if (distWithTargetSc < 2.5f && wepRange > 5.0f + && wepInfo->m_eWeaponFire != WEAPON_FIRE_MELEE) { + + SetAttack(m_pedInObjective); + if (m_attackTimer < CTimer::GetTimeInMilliseconds()) { + int time = CGeneral::GetRandomNumberInRange(500.0f, 1000.0f); + SetAttackTimer(time); + SetShootTimer(time - 500); + } + SetMoveState(PEDMOVE_STILL); + } + if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(m_pedInObjective->GetPosition().x, m_pedInObjective->GetPosition().y, GetPosition().x, GetPosition().y); + + SetMoveAnim(); + break; + } + case OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS: + { + if (InVehicle()) { + if (m_nPedState == PED_DRIVING) + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } else if (m_nPedState != PED_FLEE_ENTITY) { + int time; + if (m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS) + time = 0; + else + time = 6000; + + SetFindPathAndFlee(m_pedInObjective, time); + } + break; + } + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + { + if (m_pedInObjective) { + float safeDistance = 2.0f; + if (m_pedInObjective->bInVehicle) + safeDistance = 3.0f; + + float distWithTargetSc = distWithTarget.Magnitude(); + if (m_nPedStateTimer < CTimer::GetTimeInMilliseconds()) { + if (distWithTargetSc <= safeDistance) { + bScriptObjectiveCompleted = true; + if (m_nPedState != PED_ATTACK) { + SetIdle(); + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + } + if (distWithTargetSc > 2.0f) + SetMoveState(m_pedInObjective->m_nMoveState); + else + SetMoveState(PEDMOVE_STILL); + } else { + SetSeek(m_pedInObjective, safeDistance); + if (distWithTargetSc >= 5.0f) { + if (m_leader && m_leader->m_nMoveState == PEDMOVE_SPRINT) + SetMoveState(PEDMOVE_SPRINT); + else + SetMoveState(PEDMOVE_RUN); + } else { + if (m_leader && m_leader->m_nMoveState != PEDMOVE_STILL + && m_leader->m_nMoveState != PEDMOVE_NONE) { + if (m_leader->IsPlayer()) { + if (distWithTargetSc >= 3.0f && FindPlayerPed()->m_fMoveSpeed >= 1.3f) + SetMoveState(PEDMOVE_RUN); + else + SetMoveState(PEDMOVE_WALK); + } else { + SetMoveState(m_leader->m_nMoveState); + } + } else if (distWithTargetSc <= 3.0f) { + SetMoveState(PEDMOVE_WALK); + } else { + SetMoveState(PEDMOVE_RUN); + } + } + } + } + } else { + SetObjective(OBJECTIVE_NONE); + } + break; + } + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + { + if (m_pedInObjective) { + CVector posToGo = GetFormationPosition(); + distWithTarget = posToGo - carOrOurPos; + SetSeek(posToGo, 1.0f); + if (distWithTarget.Magnitude() <= 3.0f) { + SetSeek(posToGo, 1.0f); + if (m_pedInObjective->m_nMoveState != PEDMOVE_STILL) + SetMoveState(m_pedInObjective->m_nMoveState); + } else { + SetSeek(posToGo, 1.0f); + SetMoveState(PEDMOVE_RUN); + } + } else { + SetObjective(OBJECTIVE_NONE); + } + break; + } + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + { + if (m_carInObjective) { + if (!bInVehicle && m_carInObjective->m_nNumPassengers >= m_carInObjective->m_nNumMaxPassengers) { + RestorePreviousObjective(); + RestorePreviousState(); + if (IsPedInControl()) + m_pMyVehicle = nil; + + break; + } + + if (m_prevObjective == OBJECTIVE_HAIL_TAXI && !((CAutomobile*)m_carInObjective)->bTaxiLight) { + RestorePreviousObjective(); + ClearObjective(); + SetWanderPath(CGeneral::GetRandomNumber() & 7); + bIsRunning = false; + break; + } + if (m_objectiveTimer && m_objectiveTimer < CTimer::GetTimeInMilliseconds()) { + if (!EnteringCar()) { + bool foundSeat = false; + if (m_carInObjective->pPassengers[0] || m_carInObjective->m_nGettingInFlags & CAR_DOOR_FLAG_RF) { + if (m_carInObjective->pPassengers[1] || m_carInObjective->m_nGettingInFlags & CAR_DOOR_FLAG_LR) { + if (m_carInObjective->pPassengers[2] || m_carInObjective->m_nGettingInFlags & CAR_DOOR_FLAG_RR) { + foundSeat = false; + } else { + m_vehDoor = CAR_DOOR_RR; + foundSeat = true; + } + } else { + m_vehDoor = CAR_DOOR_LR; + foundSeat = true; + } + } else { + m_vehDoor = CAR_DOOR_RF; + foundSeat = true; + } + for (int i = 2; i < m_carInObjective->m_nNumMaxPassengers; ++i) { + if (!m_carInObjective->pPassengers[i] && !(m_carInObjective->m_nGettingInFlags & CAR_DOOR_FLAG_RF)) { + m_vehDoor = CAR_DOOR_RF; + foundSeat = true; + } + } + if (foundSeat) { + SetPosition(GetPositionToOpenCarDoor(m_carInObjective, m_vehDoor)); + SetEnterCar(m_carInObjective, m_vehDoor); + } + } + m_objectiveTimer = 0; + } + } + // fall through + } + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + { + if (!m_carInObjective || bInVehicle) { +#ifdef VC_PED_PORTS + if (bInVehicle && m_pMyVehicle != m_carInObjective) { + SetExitCar(m_pMyVehicle, 0); + } else +#endif + { + bObjectiveCompleted = true; + bScriptObjectiveCompleted = true; + RestorePreviousState(); + } + } else { + if (m_leaveCarTimer > CTimer::GetTimeInMilliseconds()) { + SetMoveState(PEDMOVE_STILL); + break; + } + if (IsPedInControl()) { + if (m_prevObjective == OBJECTIVE_KILL_CHAR_ANY_MEANS) { + if (distWithTarget.Magnitude() < 20.0f) { + RestorePreviousObjective(); + RestorePreviousState(); + return; + } + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (m_carInObjective->pDriver +#ifdef VC_PED_PORTS + && !IsPlayer() +#endif + ) { + if (m_carInObjective->pDriver->m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS && m_carInObjective->pDriver != m_pedInObjective) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_carInObjective); + m_carInObjective->bIsBeingCarJacked = false; + } + } + } + } else if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + if (m_carInObjective->pDriver +#ifdef VC_PED_PORTS + && (CharCreatedBy != MISSION_CHAR || m_carInObjective->pDriver->CharCreatedBy != RANDOM_CHAR) +#endif + ) { + if (m_carInObjective->pDriver->m_nPedType == m_nPedType) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_carInObjective); + m_carInObjective->bIsBeingCarJacked = false; + } + } + } + if (m_carInObjective->IsUpsideDown() && m_carInObjective->m_vehType != VEHICLE_TYPE_BIKE) { + RestorePreviousObjective(); + RestorePreviousState(); + return; + } + if (!m_carInObjective->IsBoat() || m_nPedState == PED_SEEK_IN_BOAT) { + if (m_nPedState != PED_SEEK_CAR) + SetSeekCar(m_carInObjective, 0); + } else { + SetSeekBoatPosition(m_carInObjective); + } + if (m_nMoveState == PEDMOVE_STILL && !bVehEnterDoorIsBlocked) + SetMoveState(PEDMOVE_RUN); + + if (m_carInObjective && m_carInObjective->m_fHealth > 0.0f) { + distWithTarget = m_carInObjective->GetPosition() - GetPosition(); + if (!bInVehicle) { + if (nEnterCarRangeMultiplier * ENTER_CAR_MAX_DIST < distWithTarget.Magnitude()) { + if (!m_carInObjective->pDriver && !m_carInObjective->GetIsOnScreen() && !GetIsOnScreen()) + WarpPedToNearEntityOffScreen(m_carInObjective); + + if (CharCreatedBy != MISSION_CHAR || m_prevObjective == OBJECTIVE_KILL_CHAR_ANY_MEANS +#ifdef VC_PED_PORTS + || IsPlayer() && !CPad::GetPad(0)->ArePlayerControlsDisabled() +#endif + ) { + RestorePreviousObjective(); + RestorePreviousState(); + if (IsPedInControl()) + m_pMyVehicle = nil; + } else { + SetIdle(); + SetMoveState(PEDMOVE_STILL); + } + } + } + } else if (!bInVehicle) { + RestorePreviousObjective(); + RestorePreviousState(); + if (IsPedInControl()) + m_pMyVehicle = nil; + } + } + } + break; + } + case OBJECTIVE_DESTROY_CAR: + { + if (!m_carInObjective) { + ClearLookFlag(); + bObjectiveCompleted = true; + break; + } + float distWithTargetSc = distWithTarget.Magnitude(); + CWeaponInfo *wepInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + float wepRange = wepInfo->m_fRange; + m_pLookTarget = m_carInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + + m_pSeekTarget = m_carInObjective; + m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget); + + TurnBody(); + if (m_carInObjective->m_fHealth <= 0.0f) { + ClearLookFlag(); + bScriptObjectiveCompleted = true; + break; + } + + if (m_attackTimer < CTimer::GetTimeInMilliseconds() && wepInfo->m_eWeaponFire != WEAPON_FIRE_MELEE + && distWithTargetSc < wepRange) { + + // I hope so + CVector ourHead = GetMatrix() * CVector(0.5f, 0.0f, 0.6f); + CVector maxShotPos = m_carInObjective->GetPosition() - ourHead; + maxShotPos *= wepInfo->m_fRange / maxShotPos.Magnitude(); + maxShotPos += ourHead; + + CColPoint foundCol; + CEntity *foundEnt; + + CWorld::bIncludeDeadPeds = true; + CWorld::ProcessLineOfSight(ourHead, maxShotPos, foundCol, foundEnt, true, true, true, true, false, true, false); + CWorld::bIncludeDeadPeds = false; + if (foundEnt == m_carInObjective) { + SetAttack(m_carInObjective); + SetWeaponLockOnTarget(m_carInObjective); + SetShootTimer(CGeneral::GetRandomNumberInRange(500, 2000)); + if (distWithTargetSc > 10.0f && !bKindaStayInSamePlace) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(2000, 5000)); + } else { + SetAttackTimer(CGeneral::GetRandomNumberInRange(50, 300)); + SetMoveState(PEDMOVE_STILL); + } + } + } else if (m_nPedState != PED_ATTACK && !bKindaStayInSamePlace) { + + float safeDistance; + if (wepRange <= 5.0f) + safeDistance = 3.0f; + else + safeDistance = wepRange * 0.25f; + + SetSeek(m_carInObjective, safeDistance); + SetMoveState(PEDMOVE_RUN); + } + SetLookFlag(m_carInObjective, false); + TurnBody(); + break; + } + case OBJECTIVE_GOTO_AREA_ANY_MEANS: + { + distWithTarget = m_nextRoutePointPos - GetPosition(); + distWithTarget.z = 0.0f; + if (InVehicle()) { + CCarAI::GetCarToGoToCoors(m_pMyVehicle, &m_nextRoutePointPos); + CCarCtrl::RegisterVehicleOfInterest(m_pMyVehicle); + if (distWithTarget.MagnitudeSqr() < sq(20.0f)) { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + ForceStoredObjective(OBJECTIVE_GOTO_AREA_ANY_MEANS); + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + break; + } + if (distWithTarget.Magnitude() > 30.0f) { + if (m_pMyVehicle) { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + } else { + float closestVehDist = SQR(60.0f); + int16 lastVehicle; + CEntity* vehicles[8]; + CWorld::FindObjectsInRange(GetPosition(), 25.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + CVehicle* foundVeh = nil; + for (int i = 0; i < lastVehicle; i++) { + CVehicle* nearVeh = (CVehicle*)vehicles[i]; + /* + Not used. + CVector vehSpeed = nearVeh->GetSpeed(); + CVector ourSpeed = GetSpeed(); + */ + CVector vehDistVec = nearVeh->GetPosition() - GetPosition(); + if (vehDistVec.MagnitudeSqr() < closestVehDist + && m_pedInObjective->m_pMyVehicle != nearVeh) + { + foundVeh = nearVeh; + closestVehDist = vehDistVec.MagnitudeSqr(); + } + } + m_pMyVehicle = foundVeh; + if (m_pMyVehicle) { + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } + break; + } + // fall through + } + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_RUN_TO_AREA: + { + if ((m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA) + && InVehicle()) { + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } else { + distWithTarget = m_nextRoutePointPos - GetPosition(); + distWithTarget.z = 0.0f; + if (sq(m_distanceToCountSeekDone) >= distWithTarget.MagnitudeSqr()) { + bObjectiveCompleted = true; + bScriptObjectiveCompleted = true; + SetMoveState(PEDMOVE_STILL); + } else if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer || m_nPedState != PED_SEEK_POS) { + if (bUsePedNodeSeek) { + CVector bestCoords(0.0f, 0.0f, 0.0f); + m_vecSeekPos = m_nextRoutePointPos; + + if (!m_pNextPathNode) + FindBestCoordsFromNodes(m_vecSeekPos, &bestCoords); + + if (m_pNextPathNode) + m_vecSeekPos = m_pNextPathNode->GetPosition(); + } + SetSeek(m_vecSeekPos, m_distanceToCountSeekDone); + } + } + + break; + } + case OBJECTIVE_GUARD_ATTACK: + { + if (m_pedInObjective) { + SetLookFlag(m_pedInObjective, true); + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + m_lookTimer = m_attackTimer; + TurnBody(); + float distWithTargetSc = distWithTarget.Magnitude(); + if (distWithTargetSc >= 20.0f) { + RestorePreviousObjective(); + } else if (m_attackTimer < CTimer::GetTimeInMilliseconds()) { + if (m_nPedState != PED_SEEK_ENTITY && distWithTargetSc >= 2.0f) { + SetSeek(m_pedInObjective, 1.0f); + } else { + SetAttack(m_pedInObjective); + SetShootTimer(CGeneral::GetRandomNumberInRange(500.0f, 1500.0f)); + } + SetAttackTimer(1000); + } + } else { + RestorePreviousObjective(); + } + break; + } + case OBJECTIVE_FOLLOW_ROUTE: + if (HaveReachedNextPointOnRoute(1.0f)) { + int nextPoint = GetNextPointOnRoute(); + m_nextRoutePointPos = CRouteNode::GetPointPosition(nextPoint); + } else { + SetSeek(m_nextRoutePointPos, 0.8f); + } + break; + case OBJECTIVE_SOLICIT_VEHICLE: + if (m_carInObjective) { + if (m_objectiveTimer <= CTimer::GetTimeInMilliseconds()) { + if (!bInVehicle) { + SetObjective(OBJECTIVE_NONE); + SetWanderPath(CGeneral::GetRandomNumber() & 7); + m_objectiveTimer = CTimer::GetTimeInMilliseconds() + 10000; + if (IsPedInControl()) + m_pMyVehicle = nil; + } + } else { + if (m_nPedState != PED_FLEE_ENTITY && m_nPedState != PED_SOLICIT) + SetSeekCar(m_carInObjective, 0); + } + } else { + RestorePreviousObjective(); + RestorePreviousState(); + if (IsPedInControl()) + m_pMyVehicle = nil; + } + break; + case OBJECTIVE_HAIL_TAXI: + if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HAILTAXI) && CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + Say(SOUND_PED_TAXI_WAIT); + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HAILTAXI, 4.0f); + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 2000; + } + break; + case OBJECTIVE_CATCH_TRAIN: + { + if (m_carInObjective) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_carInObjective); + } else { + CVehicle* trainToEnter = nil; + float closestCarDist = CHECK_NEARBY_THINGS_MAX_DIST; + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity* vehicles[8]; + + CWorld::FindObjectsInRange(pos, 10.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + for (int i = 0; i < lastVehicle; i++) { + CVehicle* nearVeh = (CVehicle*)vehicles[i]; + if (nearVeh->IsTrain()) { + CVector vehDistVec = GetPosition() - nearVeh->GetPosition(); + float vehDist = vehDistVec.Magnitude(); + if (vehDist < closestCarDist && m_pedInObjective->m_pMyVehicle != nearVeh) + { + trainToEnter = nearVeh; + closestCarDist = vehDist; + } + } + } + if (trainToEnter) { + m_carInObjective = trainToEnter; + m_carInObjective->RegisterReference((CEntity **) &m_carInObjective); + } + } + break; + } + case OBJECTIVE_BUY_ICE_CREAM: + if (m_carInObjective) { + if (m_nPedState != PED_FLEE_ENTITY && m_nPedState != PED_BUY_ICECREAM) + SetSeekCar(m_carInObjective, 0); + } else { + RestorePreviousObjective(); + RestorePreviousState(); + if (IsPedInControl()) + m_pMyVehicle = nil; + } + break; + case OBJECTIVE_STEAL_ANY_CAR: + { + if (bInVehicle) { + bScriptObjectiveCompleted = true; + RestorePreviousObjective(); + } else if (m_carJackTimer < CTimer::GetTimeInMilliseconds()) { + CVehicle *carToSteal = nil; + float closestCarDist = ENTER_CAR_MAX_DIST; + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity *vehicles[8]; + + // NB: This should've been ENTER_CAR_MAX_DIST actually, and is fixed in VC. + CWorld::FindObjectsInRange(pos, CHECK_NEARBY_THINGS_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + for(int i = 0; i < lastVehicle; i++) { + CVehicle *nearVeh = (CVehicle*)vehicles[i]; + if (nearVeh->VehicleCreatedBy != MISSION_VEHICLE) { + if (nearVeh->m_vecMoveSpeed.Magnitude() <= 0.1f) { + if (nearVeh->CanPedOpenLocks(this)) { + CVector vehDistVec = GetPosition() - nearVeh->GetPosition(); + float vehDist = vehDistVec.Magnitude(); + if (vehDist < closestCarDist) { + carToSteal = nearVeh; + closestCarDist = vehDist; + } + } + } + } + } + if (carToSteal) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, carToSteal); + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 5000; + } else { + RestorePreviousObjective(); + RestorePreviousState(); + } + } + break; + } + case OBJECTIVE_MUG_CHAR: + { + if (m_pedInObjective) { + if (m_pedInObjective->IsPlayer() || m_pedInObjective->bInVehicle || m_pedInObjective->m_fHealth <= 0.0f) { + ClearObjective(); + return; + } + if (m_pedInObjective->m_nMoveState > PEDMOVE_WALK) { + ClearObjective(); + return; + } + if (m_pedInObjective->m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && m_pedInObjective->m_pedInObjective == this) { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, m_pedInObjective); + SetMoveState(PEDMOVE_SPRINT); + return; + } + if (m_pedInObjective->m_nPedState == PED_FLEE_ENTITY && m_fleeFrom == this + || m_pedInObjective->m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE && m_pedInObjective->m_pedInObjective == this) { + ClearObjective(); + SetFindPathAndFlee(m_pedInObjective, 15000, true); + return; + } + float distWithTargetScSqr = distWithTarget.MagnitudeSqr(); + if (distWithTargetScSqr <= sq(10.0f)) { + if (distWithTargetScSqr <= sq(1.4f)) { + CAnimBlendAssociation *reloadAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_AK_RELOAD); + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + m_pedInObjective->GetPosition().x, m_pedInObjective->GetPosition().y, + GetPosition().x, GetPosition().y); + + if (reloadAssoc || !m_pedInObjective->IsPedShootable()) { + if (reloadAssoc && + (!reloadAssoc->IsRunning() || reloadAssoc->currentTime / reloadAssoc->hierarchy->totalLength > 0.8f)) { + CAnimBlendAssociation *punchAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PARTIAL_PUNCH, 8.0f); + punchAssoc->flags |= ASSOC_DELETEFADEDOUT; + punchAssoc->flags |= ASSOC_FADEOUTWHENDONE; + CVector2D offset(distWithTarget.x, distWithTarget.y); + int dir = m_pedInObjective->GetLocalDirection(offset); + m_pedInObjective->StartFightDefend(dir, HITLEVEL_HIGH, 5); + m_pedInObjective->ReactToAttack(this); + m_pedInObjective->Say(SOUND_PED_ROBBED); + Say(SOUND_PED_MUGGING); + bRichFromMugging = true; + + // VC FIX: ClearObjective() clears m_pedInObjective in VC (also same with VC_PED_PORTS), so get it before call + CPed *victim = m_pedInObjective; + ClearObjective(); + if (victim->m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT + || victim->m_pedInObjective != this) { + SetFindPathAndFlee(victim, 15000, true); + m_nLastPedState = PED_WANDER_PATH; + } else { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, victim); + SetMoveState(PEDMOVE_SPRINT); + m_nLastPedState = PED_WANDER_PATH; + } + } + } else { + eWeaponType weaponType = GetWeapon()->m_eWeaponType; + if (weaponType != WEAPONTYPE_UNARMED && weaponType != WEAPONTYPE_BASEBALLBAT) + SetCurrentWeapon(WEAPONTYPE_UNARMED); + + CAnimBlendAssociation *newReloadAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_AK_RELOAD, 8.0f); + newReloadAssoc->flags |= ASSOC_DELETEFADEDOUT; + newReloadAssoc->flags |= ASSOC_FADEOUTWHENDONE; + } + } else { + SetSeek(m_pedInObjective, 1.0f); + CAnimBlendAssociation *walkAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_WALK); + + if (walkAssoc) + walkAssoc->speed = 1.3f; + } + } else { + ClearObjective(); + SetWanderPath(CGeneral::GetRandomNumber() & 7); + } + } else { +#ifdef VC_PED_PORTS + m_objective = OBJECTIVE_NONE; +#endif + ClearObjective(); + } + break; + } + case OBJECTIVE_FLEE_CAR: + if (!bInVehicle && m_nPedState != PED_FLEE_ENTITY && m_pMyVehicle) { + RestorePreviousObjective(); + SetFlee(m_pMyVehicle, 6000); + break; + } + // fall through + case OBJECTIVE_LEAVE_CAR: + if (CTimer::GetTimeInMilliseconds() > m_leaveCarTimer) { + if (InVehicle() +#ifdef VC_PED_PORTS + && (FindPlayerPed() != this || !CPad::GetPad(0)->GetAccelerate() + || bBusJacked) +#endif + ) { + if (m_nPedState != PED_EXIT_CAR && m_nPedState != PED_DRAG_FROM_CAR && m_nPedState != PED_EXIT_TRAIN + && (m_nPedType != PEDTYPE_COP +#ifdef VC_PED_PORTS + || m_pMyVehicle->IsBoat() +#endif + || m_pMyVehicle->m_vecMoveSpeed.MagnitudeSqr2D() < sq(0.005f))) { + if (m_pMyVehicle->IsTrain()) + SetExitTrain(m_pMyVehicle); +#ifdef VC_PED_PORTS + else if (m_pMyVehicle->IsBoat()) + SetExitBoat(m_pMyVehicle); +#endif + else + SetExitCar(m_pMyVehicle, 0); + } + } else { + RestorePreviousObjective(); + } + } + break; +#ifdef VC_PED_PORTS + case OBJECTIVE_LEAVE_CAR_AND_DIE: + { + if (CTimer::GetTimeInMilliseconds() > m_leaveCarTimer) { + if (InVehicle()) { + if (m_nPedState != PED_EXIT_CAR && m_nPedState != PED_DRAG_FROM_CAR && m_nPedState != PED_EXIT_TRAIN) { + if (m_pMyVehicle->IsBoat()) + SetExitBoat(m_pMyVehicle); + else if (m_pMyVehicle->bIsBus) + SetExitCar(m_pMyVehicle, 0); + else { + eCarNodes doorNode = CAR_DOOR_LF; + if (m_pMyVehicle->pDriver != this) { + if (m_pMyVehicle->pPassengers[0] == this) { + doorNode = CAR_DOOR_RF; + } else if (m_pMyVehicle->pPassengers[1] == this) { + doorNode = CAR_DOOR_LR; + } else if (m_pMyVehicle->pPassengers[2] == this) { + doorNode = CAR_DOOR_RR; + } + } + SetBeingDraggedFromCar(m_pMyVehicle, doorNode, false); + } + } + } + } + break; + } +#endif + } + if (bObjectiveCompleted + || m_objectiveTimer > 0 && CTimer::GetTimeInMilliseconds() > m_objectiveTimer) { + RestorePreviousObjective(); + if (m_objectiveTimer > CTimer::GetTimeInMilliseconds() || !m_objectiveTimer) + m_objectiveTimer = CTimer::GetTimeInMilliseconds() - 1; + + if (CharCreatedBy != RANDOM_CHAR || bInVehicle) { + if (IsPedInControl()) + RestorePreviousState(); + } else { + SetWanderPath(CGeneral::GetRandomNumber() & 7); + } + ClearAimFlag(); + ClearLookFlag(); + } + } +} + +void +CPed::SetFollowRoute(int16 currentPoint, int16 routeType) +{ + m_routeLastPoint = currentPoint; + m_routeStartPoint = CRouteNode::GetRouteStart(currentPoint); + m_routePointsPassed = 0; + m_routeType = routeType; + m_routePointsBeingPassed = 1; + m_objective = OBJECTIVE_FOLLOW_ROUTE; + m_nextRoutePointPos = CRouteNode::GetPointPosition(GetNextPointOnRoute()); +} + +int +CPed::GetNextPointOnRoute(void) +{ + int16 nextPoint = m_routePointsBeingPassed + m_routePointsPassed + m_routeStartPoint; + + // Route is complete + if (nextPoint < 0 || nextPoint > NUMPEDROUTES || m_routeLastPoint != CRouteNode::GetRouteThisPointIsOn(nextPoint)) { + + switch (m_routeType) { + case PEDROUTE_STOP_WHEN_DONE: + nextPoint = -1; + break; + case PEDROUTE_GO_BACKWARD_WHEN_DONE: + m_routePointsBeingPassed = -m_routePointsBeingPassed; + nextPoint = m_routePointsBeingPassed + m_routePointsPassed + m_routeStartPoint; + break; + case PEDROUTE_GO_TO_START_WHEN_DONE: + m_routePointsPassed = -1; + nextPoint = m_routePointsBeingPassed + m_routePointsPassed + m_routeStartPoint; + break; + default: + break; + } + } + return nextPoint; +} + +bool +CPed::HaveReachedNextPointOnRoute(float distToCountReached) +{ + if ((m_nextRoutePointPos - GetPosition()).Magnitude2D() >= distToCountReached) + return false; + + m_routePointsPassed += m_routePointsBeingPassed; + return true; +} + +bool +CPed::CanSeeEntity(CEntity *entity, float threshold) +{ + float neededAngle = CGeneral::GetRadianAngleBetweenPoints( + entity->GetPosition().x, + entity->GetPosition().y, + GetPosition().x, + GetPosition().y); + + if (neededAngle < 0.0f) + neededAngle += TWOPI; + else if (neededAngle > TWOPI) + neededAngle -= TWOPI; + + float ourAngle = m_fRotationCur; + if (ourAngle < 0.0f) + ourAngle += TWOPI; + else if (ourAngle > TWOPI) + ourAngle -= TWOPI; + + float neededTurn = Abs(neededAngle - ourAngle); + + return neededTurn < threshold || TWOPI - threshold < neededTurn; +} + +// Only used while deciding which gun ped should switch to, if no ammo left. +bool +CPed::SelectGunIfArmed(void) +{ + for (int i = 0; i < m_maxWeaponTypeAllowed; i++) { + if (GetWeapon(i).m_nAmmoTotal > 0) { + eWeaponType weaponType = GetWeapon(i).m_eWeaponType; + if (weaponType == WEAPONTYPE_BASEBALLBAT || weaponType == WEAPONTYPE_COLT45 || weaponType == WEAPONTYPE_UZI || weaponType == WEAPONTYPE_SHOTGUN || + weaponType == WEAPONTYPE_M16 || weaponType == WEAPONTYPE_SNIPERRIFLE || weaponType == WEAPONTYPE_ROCKETLAUNCHER) { + SetCurrentWeapon(i); + return true; + } + } + } + SetCurrentWeapon(WEAPONTYPE_UNARMED); + return false; +} + +void +CPed::ReactToPointGun(CEntity *entWithGun) +{ + CPed *pedWithGun = (CPed*)entWithGun; + int waitTime; + + if (IsPlayer() || !IsPedInControl() || CharCreatedBy == MISSION_CHAR) + return; + + if (m_leader == pedWithGun) + return; + + if (m_nWaitState == WAITSTATE_PLAYANIM_HANDSUP || m_nWaitState == WAITSTATE_PLAYANIM_HANDSCOWER || + (GetPosition() - pedWithGun->GetPosition()).MagnitudeSqr2D() > 225.0f) + return; + + if (m_leader) { + if (FindPlayerPed() == m_leader) + return; + + ClearLeader(); + } + if (m_pedStats->m_flags & STAT_GUN_PANIC + && (m_nPedState != PED_ATTACK || GetWeapon()->IsTypeMelee()) + && m_nPedState != PED_FLEE_ENTITY && m_nPedState != PED_AIM_GUN) { + + waitTime = CGeneral::GetRandomNumberInRange(3000, 6000); + SetWaitState(WAITSTATE_PLAYANIM_HANDSCOWER, &waitTime); + Say(SOUND_PED_HANDS_COWER); + m_pLookTarget = pedWithGun; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + SetMoveState(PEDMOVE_NONE); + + } else if (m_nPedType != pedWithGun->m_nPedType) { + if (IsGangMember() || m_nPedType == PEDTYPE_EMERGENCY || m_nPedType == PEDTYPE_FIREMAN) { + RegisterThreatWithGangPeds(pedWithGun); + } + + if (m_nPedType == PEDTYPE_COP) { + if (pedWithGun->IsPlayer()) { + ((CPlayerPed*)pedWithGun)->m_pWanted->SetWantedLevelNoDrop(2); + if (bCrouchWhenShooting || bKindaStayInSamePlace) { + SetDuck(CGeneral::GetRandomNumberInRange(1000, 3000)); + return; + } + } + } + + if (m_nPedType != PEDTYPE_COP + && (m_nPedState != PED_ATTACK || GetWeapon()->IsTypeMelee()) + && (m_nPedState != PED_FLEE_ENTITY || pedWithGun->IsPlayer() && m_fleeFrom != pedWithGun) + && m_nPedState != PED_AIM_GUN && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + + waitTime = CGeneral::GetRandomNumberInRange(3000, 6000); + SetWaitState(WAITSTATE_PLAYANIM_HANDSUP, &waitTime); + Say(SOUND_PED_HANDS_UP); + m_pLookTarget = pedWithGun; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + SetMoveState(PEDMOVE_NONE); + if (m_nPedState == PED_FLEE_ENTITY) { + m_fleeFrom = pedWithGun; + m_fleeFrom->RegisterReference((CEntity **) &m_fleeFrom); + } + + if (FindPlayerPed() == pedWithGun && bRichFromMugging) { + int money = CGeneral::GetRandomNumberInRange(100, 300); + int pickupCount = money / 40 + 1; + int moneyPerPickup = money / pickupCount; + + for (int i = 0; i < pickupCount; i++) { + float pickupX = 1.5f * Sin((CGeneral::GetRandomNumber() % 256)/256.0f * TWOPI) + GetPosition().x; + float pickupY = 1.5f * Cos((CGeneral::GetRandomNumber() % 256)/256.0f * TWOPI) + GetPosition().y; + bool found = false; + float groundZ = CWorld::FindGroundZFor3DCoord(pickupX, pickupY, GetPosition().z, &found) + 0.5f; + if (found) { + CPickups::GenerateNewOne(CVector(pickupX, pickupY, groundZ), MI_MONEY, PICKUP_MONEY, moneyPerPickup + (CGeneral::GetRandomNumber() & 7)); + } + } + bRichFromMugging = false; + } + } + } +} + +void +CPed::ReactToAttack(CEntity *attacker) +{ + if (IsPlayer() && attacker->IsPed()) { + InformMyGangOfAttack(attacker); + SetLookFlag(attacker, true); + SetLookTimer(700); + return; + } + +#ifdef VC_PED_PORTS + if (m_nPedState == PED_DRIVING && InVehicle() + && (m_pMyVehicle->pDriver == this || m_pMyVehicle->pDriver && m_pMyVehicle->pDriver->m_nPedState == PED_DRIVING && m_pMyVehicle->pDriver->m_objective != OBJECTIVE_LEAVE_CAR_AND_DIE)) { + + if (m_pMyVehicle->VehicleCreatedBy == RANDOM_VEHICLE + && (m_pMyVehicle->GetStatus() == STATUS_SIMPLE || m_pMyVehicle->GetStatus() == STATUS_PHYSICS) + && m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE) { + + CCarCtrl::SwitchVehicleToRealPhysics(m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = GAME_SPEED_TO_CARAI_SPEED * m_pMyVehicle->pHandling->Transmission.fMaxCruiseVelocity; + m_pMyVehicle->SetStatus(STATUS_PHYSICS); + } + } else +#endif + if (IsPedInControl() && (CharCreatedBy != MISSION_CHAR || bRespondsToThreats)) { + CPed *ourLeader = m_leader; + if (ourLeader != attacker && (!ourLeader || FindPlayerPed() != ourLeader) + && attacker->IsPed()) { + + CPed *attackerPed = (CPed*)attacker; + if (bNotAllowedToDuck) { + if (!attackerPed->GetWeapon()->IsTypeMelee()) { + m_duckAndCoverTimer = CTimer::GetTimeInMilliseconds(); + return; + } + } else if (bCrouchWhenShooting || bKindaStayInSamePlace) { + SetDuck(CGeneral::GetRandomNumberInRange(1000, 3000)); + return; + } + + if (m_pedStats->m_fear <= 100 - attackerPed->m_pedStats->m_temper) { + if (m_pedStats != attackerPed->m_pedStats) { + if (IsGangMember() || m_nPedType == PEDTYPE_EMERGENCY || m_nPedType == PEDTYPE_FIREMAN) { + RegisterThreatWithGangPeds(attackerPed); + } + if (!attackerPed->GetWeapon()->IsTypeMelee() && GetWeapon()->IsTypeMelee()) { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, attacker); + SetMoveState(PEDMOVE_RUN); + } else { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, attacker); + SetObjectiveTimer(20000); + } + } + } else { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, attackerPed); + SetMoveState(PEDMOVE_RUN); + if (attackerPed->GetWeapon()->IsTypeMelee()) + Say(SOUND_PED_FLEE_RUN); + } + } + } +} + +void +CPed::PedAnimAlignCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CVehicle *veh = ped->m_pMyVehicle; + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (!ped->IsNotInWreckedVehicle()) + return; + + if (!ped->EnteringCar()) { +#ifdef VC_PED_PORTS + if (ped->m_nPedState != PED_DRIVING) +#endif + ped->QuitEnteringCar(); + + return; + } + if (ped->m_fHealth == 0.0f) { + ped->QuitEnteringCar(); + return; + } + bool itsVan = !!veh->bIsVan; + bool itsBus = !!veh->bIsBus; +#ifdef FIX_BUGS + bool itsLow = !!veh->bLowVehicle; +#endif + eDoors enterDoor; + AnimationId enterAnim; + + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: + itsVan = false; + enterDoor = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_RR: + enterDoor = DOOR_REAR_RIGHT; + break; + case CAR_DOOR_LF: + itsVan = false; + enterDoor = DOOR_FRONT_LEFT; + break; + case CAR_DOOR_LR: + enterDoor = DOOR_REAR_LEFT; + break; + default: + break; + } + + if (veh->IsDoorMissing(enterDoor) || veh->IsDoorFullyOpen(enterDoor)) { + + veh->AutoPilot.m_nCruiseSpeed = 0; + if (ped->m_nPedState == PED_CARJACK) { + ped->PedAnimDoorOpenCB(nil, ped); + return; + } + if (enterDoor != DOOR_FRONT_LEFT && enterDoor != DOOR_REAR_LEFT) { + if (itsVan) { + enterAnim = ANIM_STD_VAN_GET_IN_REAR_RHS; + } else if (itsBus) { + enterAnim = ANIM_STD_COACH_GET_IN_RHS; +#ifdef FIX_BUGS + } else if (itsLow) { + enterAnim = ANIM_STD_CAR_GET_IN_LO_RHS; +#endif + } else { + enterAnim = ANIM_STD_CAR_GET_IN_RHS; + } + } else if (itsVan) { + enterAnim = ANIM_STD_VAN_GET_IN_REAR_LHS; + } else if (itsBus) { + enterAnim = ANIM_STD_COACH_GET_IN_LHS; +#ifdef FIX_BUGS + } else if (itsLow) { + enterAnim = ANIM_STD_CAR_GET_IN_LO_LHS; +#endif + } else { + enterAnim = ANIM_STD_CAR_GET_IN_LHS; + } + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, enterAnim); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + + } else if (veh->CanPedOpenLocks(ped)) { + + veh->AutoPilot.m_nCruiseSpeed = 0; + if (enterDoor != DOOR_FRONT_LEFT && enterDoor != DOOR_REAR_LEFT) { + if (itsVan) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_VAN_OPEN_DOOR_REAR_RHS); + } else if (itsBus) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_COACH_OPEN_RHS); + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_OPEN_DOOR_RHS); + } + } else if (itsVan) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_VAN_OPEN_DOOR_REAR_LHS); + } else if (itsBus) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_COACH_OPEN_LHS); + } else { + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER && veh->pDriver) { + + if (!veh->bLowVehicle + && veh->pDriver->CharCreatedBy != MISSION_CHAR + && veh->pDriver->m_nPedState == PED_DRIVING) { + + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_QUICKJACK); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + veh->pDriver->SetBeingDraggedFromCar(veh, ped->m_vehDoor, true); + + if (veh->pDriver->IsGangMember()) + veh->pDriver->RegisterThreatWithGangPeds(ped); + return; + } + } + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_OPEN_DOOR_LHS); + } + ped->m_pVehicleAnim->SetFinishCallback(PedAnimDoorOpenCB, ped); + + } else { + if (enterDoor != DOOR_FRONT_LEFT && enterDoor != DOOR_REAR_LEFT) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CARDOOR_LOCKED_RHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CARDOOR_LOCKED_LHS); + + ped->bCancelEnteringCar = true; + ped->m_pVehicleAnim->SetFinishCallback(PedAnimDoorOpenCB, ped); + } +} + +void +CPed::PedAnimDoorOpenCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + CVehicle* veh = ped->m_pMyVehicle; + + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (!ped->IsNotInWreckedVehicle()) + return; + + if (!ped->EnteringCar()) { +#ifdef VC_PED_PORTS + if (ped->m_nPedState != PED_DRIVING) +#endif + ped->QuitEnteringCar(); + + return; + } + + eDoors door; + CPed *pedInSeat = nil; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: door = DOOR_FRONT_RIGHT; pedInSeat = veh->pPassengers[0]; break; + case CAR_DOOR_RR: door = DOOR_REAR_RIGHT; pedInSeat = veh->pPassengers[2]; break; + case CAR_DOOR_LF: door = DOOR_FRONT_LEFT; pedInSeat = veh->pDriver; break; + case CAR_DOOR_LR: door = DOOR_REAR_LEFT; pedInSeat = veh->pPassengers[1]; break; + default: assert(0); + } + + if (ped->m_fHealth == 0.0f || CPad::GetPad(0)->ArePlayerControlsDisabled() && pedInSeat && pedInSeat->IsPlayer()) { + ped->QuitEnteringCar(); + return; + } + + bool isVan = veh->bIsVan; + bool isBus = veh->bIsBus; + bool isLow = veh->bLowVehicle; + bool vehUpsideDown = veh->IsUpsideDown(); + if (ped->bCancelEnteringCar) { + if (ped->IsPlayer()) { + if (veh->pDriver) { + if (veh->pDriver->m_nPedType == PEDTYPE_COP) { + FindPlayerPed()->SetWantedLevelNoDrop(1); + } + } + } +#ifdef CANCELLABLE_CAR_ENTER + if (!veh->IsDoorMissing(door) && veh->CanPedOpenLocks(ped) && veh->IsCar()) { + ((CAutomobile*)veh)->Damage.SetDoorStatus(door, DOOR_STATUS_SWINGING); + } +#endif + ped->QuitEnteringCar(); + ped->RestorePreviousObjective(); + ped->bCancelEnteringCar = false; + return; + } + if (!veh->IsDoorMissing(door) && veh->IsCar()) { + ((CAutomobile*)veh)->Damage.SetDoorStatus(door, DOOR_STATUS_SWINGING); + } + + if (veh->m_vecMoveSpeed.Magnitude() > 0.2f) { + ped->QuitEnteringCar(); + if (ped->m_vehDoor != CAR_DOOR_LF && ped->m_vehDoor != CAR_DOOR_LR) + ped->SetFall(1000, ANIM_STD_HIGHIMPACT_LEFT, false); + else + ped->SetFall(1000, ANIM_STD_HIGHIMPACT_RIGHT, false); + + return; + } + veh->ProcessOpenDoor(ped->m_vehDoor, ANIM_STD_CAR_OPEN_DOOR_LHS, 1.0f); + + if (ped->m_vehDoor == CAR_DOOR_LF || ped->m_vehDoor == CAR_DOOR_RF) + isVan = false; + + if (ped->m_nPedState != PED_CARJACK || isBus) { + AnimationId animToPlay; + if (ped->m_vehDoor != CAR_DOOR_LF && ped->m_vehDoor != CAR_DOOR_LR) { + + if (isVan) { + animToPlay = ANIM_STD_VAN_GET_IN_REAR_RHS; + } else if (isBus) { + animToPlay = ANIM_STD_COACH_GET_IN_RHS; + } else if (isLow) { + animToPlay = ANIM_STD_CAR_GET_IN_LO_RHS; + } else { + animToPlay = ANIM_STD_CAR_GET_IN_RHS; + } + } else if (isVan) { + animToPlay = ANIM_STD_VAN_GET_IN_REAR_LHS; + } else if (isBus) { + animToPlay = ANIM_STD_COACH_GET_IN_LHS; + } else if (isLow) { + animToPlay = ANIM_STD_CAR_GET_IN_LO_LHS; + } else { + animToPlay = ANIM_STD_CAR_GET_IN_LHS; + } + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, animToPlay); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + } else { + CPed *pedToDragOut = nil; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: pedToDragOut = veh->pPassengers[0]; break; + case CAR_DOOR_RR: pedToDragOut = veh->pPassengers[2]; break; + case CAR_DOOR_LF: pedToDragOut = veh->pDriver; break; + case CAR_DOOR_LR: pedToDragOut = veh->pPassengers[1]; break; + default: assert(0); + } + + if (vehUpsideDown) { + ped->QuitEnteringCar(); + if (ped->m_nPedType == PEDTYPE_COP) + ((CCopPed*)ped)->SetArrestPlayer(ped->m_pedInObjective); + } + + if (ped->m_vehDoor != CAR_DOOR_LF && ped->m_vehDoor != CAR_DOOR_LR) { + if (pedToDragOut && !pedToDragOut->bDontDragMeOutCar) { + if (pedToDragOut->m_nPedState != PED_DRIVING) { + ped->QuitEnteringCar(); + pedToDragOut = nil; + } else { + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_PULL_OUT_PED_LO_RHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_PULL_OUT_PED_RHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedAnimPullPedOutCB, ped); + } + } else if (ped->m_nPedType == PEDTYPE_COP) { + ped->QuitEnteringCar(); + if (ped->m_pedInObjective && ped->m_pedInObjective->m_nPedState == PED_DRIVING) { + veh->SetStatus(STATUS_PLAYER_DISABLED); + ((CCopPed*)ped)->SetArrestPlayer(ped->m_pedInObjective); + } else if (!veh->IsDoorMissing(DOOR_FRONT_RIGHT)) { + ((CAutomobile*)veh)->Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_SWINGING); + } + } else { + // BUG: Probably we will sit on top of the passenger if his m_ped_flagF4 is true. + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_LHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + } + } else { + if (pedToDragOut) { + if (pedToDragOut->m_nPedState != PED_DRIVING || pedToDragOut->bDontDragMeOutCar) { + + // BUG: Player freezes in that condition due to its objective isn't restored. It's an unfinished feature, used in VC. + ped->QuitEnteringCar(); + pedToDragOut = nil; + } else { + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_PULL_OUT_PED_LO_LHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_PULL_OUT_PED_LHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedAnimPullPedOutCB, ped); + } + } else { + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_LHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + } + } + + if (pedToDragOut) { + pedToDragOut->SetBeingDraggedFromCar(veh, ped->m_vehDoor, false); + if (pedToDragOut->IsGangMember()) + pedToDragOut->RegisterThreatWithGangPeds(ped); + } + } + + if (veh->pDriver && ped) { + veh->pDriver->SetLookFlag(ped, true); + veh->pDriver->SetLookTimer(1000); + } + return; +} + +void +CPed::PedAnimPullPedOutCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + CVehicle* veh = ped->m_pMyVehicle; + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (ped->EnteringCar()) { + if (!ped->IsNotInWreckedVehicle()) + return; + +#ifdef CANCELLABLE_CAR_ENTER + if (ped->bCancelEnteringCar) { + ped->QuitEnteringCar(); + ped->RestorePreviousObjective(); + ped->bCancelEnteringCar = false; + return; + } +#endif + + bool isLow = !!veh->bLowVehicle; + + int padNo; + if (ped->IsPlayer()) { + + // BUG? This will cause crash if m_nPedType is bigger then 1, there are only 2 pads + switch (ped->m_nPedType) { + case PEDTYPE_PLAYER1: + padNo = 0; + break; + case PEDTYPE_PLAYER2: + padNo = 1; + break; + case PEDTYPE_PLAYER3: + padNo = 2; + break; + case PEDTYPE_PLAYER4: + padNo = 3; + break; + } + CPad *pad = CPad::GetPad(padNo); + + if (!pad->ArePlayerControlsDisabled()) { + + if (pad->GetTarget() + || pad->NewState.LeftStickX + || pad->NewState.LeftStickY + || pad->NewState.DPadUp + || pad->NewState.DPadDown + || pad->NewState.DPadLeft + || pad->NewState.DPadRight) { + ped->QuitEnteringCar(); + ped->RestorePreviousObjective(); + return; + } + } + } + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + AnimationId animToPlay; + if (ped->m_vehDoor != CAR_DOOR_LF && ped->m_vehDoor != CAR_DOOR_LR) { + if (isLow) + animToPlay = ANIM_STD_CAR_GET_IN_LO_RHS; + else + animToPlay = ANIM_STD_CAR_GET_IN_RHS; + } else if (isLow) { + animToPlay = ANIM_STD_CAR_GET_IN_LO_LHS; + } else { + animToPlay = ANIM_STD_CAR_GET_IN_LHS; + } + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, animToPlay); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + } else { + ped->QuitEnteringCar(); + } + } else { + ped->QuitEnteringCar(); + } +} + +void +CPed::PedAnimGetInCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*) arg; + + CVehicle *veh = ped->m_pMyVehicle; + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (!ped->IsNotInWreckedVehicle() || ped->DyingOrDead()) + return; + + if (!ped->EnteringCar()) { +#ifdef VC_PED_PORTS + if(ped->m_nPedState != PED_DRIVING) +#endif + ped->QuitEnteringCar(); + return; + } + + if (ped->IsPlayer() && ped->bGonnaKillTheCarJacker && ((CPlayerPed*)ped)->m_pArrestingCop) { + PedSetInCarCB(nil, ped); + ped->m_nLastPedState = ped->m_nPedState; + ped->SetPedState(PED_ARRESTED); + ped->bGonnaKillTheCarJacker = false; + if (veh) { + veh->m_nNumGettingIn = 0; + veh->m_nGettingInFlags = 0; + veh->bIsHandbrakeOn = true; + veh->SetStatus(STATUS_PLAYER_DISABLED); + } + return; + } + if (ped->IsPlayer() && ped->m_vehDoor == CAR_DOOR_LF + && (Pads[0].GetAccelerate() >= 255.0f || Pads[0].GetBrake() >= 255.0f) + && veh->IsCar()) { + if (((CAutomobile*)veh)->Damage.GetDoorStatus(DOOR_FRONT_LEFT) != DOOR_STATUS_MISSING) + ((CAutomobile*)veh)->Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_SWINGING); + + PedSetInCarCB(nil, ped); + return; + } + bool isVan = !!veh->bIsVan; + bool isBus = !!veh->bIsBus; + bool isLow = !!veh->bLowVehicle; + eDoors enterDoor; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: + isVan = false; + enterDoor = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_RR: + enterDoor = DOOR_REAR_RIGHT; + break; + case CAR_DOOR_LF: + isVan = false; + enterDoor = DOOR_FRONT_LEFT; + break; + case CAR_DOOR_LR: + enterDoor = DOOR_REAR_LEFT; + break; + default: + break; + } + if (!veh->IsDoorMissing(enterDoor)) { + if (veh->IsCar()) + ((CAutomobile*)veh)->Damage.SetDoorStatus(enterDoor, DOOR_STATUS_SWINGING); + } + CPed *driver = veh->pDriver; + if (driver && (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || ped->m_nPedState == PED_CARJACK)) { + if (veh->bIsBus) { + driver->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + if (driver->IsPlayer()) { + veh->bIsHandbrakeOn = true; + veh->SetStatus(STATUS_PLAYER_DISABLED); + } + driver->bBusJacked = true; + veh->bIsBeingCarJacked = false; + PedSetInCarCB(nil, ped); + if (ped->m_nPedType == PEDTYPE_COP + || ped->m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT + || ped->m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) { + ped->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + } + ped->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 400; + return; + } + if (driver != ped && ped->m_vehDoor != CAR_DOOR_LF) { + if (!driver->IsPlayer()) { + driver->bUsePedNodeSeek = true; + driver->m_pLastPathNode = nil; + if (driver->m_pedStats->m_temper <= driver->m_pedStats->m_fear + || driver->CharCreatedBy == MISSION_CHAR + || veh->VehicleCreatedBy == MISSION_VEHICLE) { + driver->bFleeAfterExitingCar = true; + } else { + driver->bGonnaKillTheCarJacker = true; + veh->pDriver->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, ped); + + if (veh->pDriver->m_nPedType == PEDTYPE_COP && ped->IsPlayer()) { + FindPlayerPed()->SetWantedLevelNoDrop(1); + } + } + } + if ((ped->m_nPedType != PEDTYPE_EMERGENCY || veh->pDriver->m_nPedType != PEDTYPE_EMERGENCY) + && (ped->m_nPedType != PEDTYPE_COP || veh->pDriver->m_nPedType != PEDTYPE_COP)) { + veh->pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + veh->pDriver->Say(SOUND_PED_CAR_JACKED); +#ifdef VC_PED_PORTS + veh->pDriver->SetRadioStation(); +#endif + } else { + ped->m_objective = OBJECTIVE_ENTER_CAR_AS_PASSENGER; + } + } + } + if (veh->IsDoorMissing(enterDoor) || isBus) { + PedAnimDoorCloseCB(nil, ped); + } else { + AnimationId animToPlay; + if (enterDoor != DOOR_FRONT_LEFT && enterDoor != DOOR_REAR_LEFT) { + if (isVan) { + animToPlay = ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS; + } else if (isLow) { + animToPlay = ANIM_STD_CAR_CLOSE_DOOR_LO_RHS; + } else { + animToPlay = ANIM_STD_CAR_CLOSE_DOOR_RHS; + } + } else if (isVan) { + animToPlay = ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS; + } else if (isLow) { + animToPlay = ANIM_STD_CAR_CLOSE_DOOR_LO_LHS; + } else { + animToPlay = ANIM_STD_CAR_CLOSE_DOOR_LHS; + } + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, animToPlay); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimDoorCloseCB, ped); + } +} + +void +CPed::PedAnimDoorCloseCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CAutomobile *veh = (CAutomobile*)(ped->m_pMyVehicle); + + if (!ped->IsNotInWreckedVehicle() || ped->DyingOrDead()) + return; + + if (ped->EnteringCar()) { + bool isLow = !!veh->bLowVehicle; + + if (!veh->bIsBus) + veh->ProcessOpenDoor(ped->m_vehDoor, ANIM_STD_CAR_CLOSE_DOOR_LHS, 1.0f); + + eDoors door; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: door = DOOR_FRONT_RIGHT; break; + case CAR_DOOR_RR: door = DOOR_REAR_RIGHT; break; + case CAR_DOOR_LF: door = DOOR_FRONT_LEFT; break; + case CAR_DOOR_LR: door = DOOR_REAR_LEFT; break; + default: assert(0); + } + + if (veh->Damage.GetDoorStatus(door) == DOOR_STATUS_SWINGING) + veh->Damage.SetDoorStatus(door, DOOR_STATUS_OK); + + if (door == DOOR_FRONT_LEFT || ped->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER || veh->bIsBus) { + PedSetInCarCB(nil, ped); + } else if (ped->m_vehDoor == CAR_DOOR_RF + && (veh->m_nGettingInFlags & CAR_DOOR_FLAG_LF || + (veh->pDriver != nil && + (veh->pDriver->m_objective != OBJECTIVE_LEAVE_CAR +#ifdef VC_PED_PORTS + && veh->pDriver->m_objective != OBJECTIVE_LEAVE_CAR_AND_DIE +#endif + || !veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, nil))))) { + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER +#if defined VC_PED_PORTS || defined FIX_BUGS + || ped->m_nPedState == PED_CARJACK +#endif + ) + veh->bIsBeingCarJacked = false; + + ped->m_objective = OBJECTIVE_ENTER_CAR_AS_PASSENGER; + PedSetInCarCB(nil, ped); + + ped->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + if (!ped->IsPlayer()) + ped->bFleeAfterExitingCar = true; + + ped->bUsePedNodeSeek = true; + ped->m_pNextPathNode = nil; + + } else { + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SHUFFLE_LO_RHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SHUFFLE_RHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedSetInCarCB, ped); + } + } else { +#ifdef VC_PED_PORTS + if (ped->m_nPedState != PED_DRIVING) +#endif + ped->QuitEnteringCar(); + } +} + +void +CPed::SetFormation(eFormation type) +{ + // FIX: Formations in GetFormationPosition were in range 1-8, whereas in here it's 0-7. + // To not change the behaviour, range in here tweaked by 1 with the use of enum. + + switch (m_pedFormation) { + case FORMATION_REAR: + case FORMATION_REAR_LEFT: + case FORMATION_REAR_RIGHT: + case FORMATION_FRONT_LEFT: + case FORMATION_FRONT_RIGHT: + case FORMATION_LEFT: + case FORMATION_RIGHT: + case FORMATION_FRONT: + break; + default: + Error("Unknown formation type, PedAI.cpp"); + break; + } + m_pedFormation = type; +} + +CVector +CPed::GetFormationPosition(void) +{ + if (m_pedInObjective->m_nPedState == PED_DEAD) { + if (!m_pedInObjective->m_pedInObjective) { + m_pedInObjective = nil; + return GetPosition(); + } + m_pedInObjective = m_pedInObjective->m_pedInObjective; + } + + CVector formationOffset; + switch (m_pedFormation) { + case FORMATION_REAR: + formationOffset = CVector(0.0f, -1.5f, 0.0f); + break; + case FORMATION_REAR_LEFT: + formationOffset = CVector(-1.5f, -1.5f, 0.0f); + break; + case FORMATION_REAR_RIGHT: + formationOffset = CVector(1.5f, -1.5f, 0.0f); + break; + case FORMATION_FRONT_LEFT: + formationOffset = CVector(-1.5f, 1.5f, 0.0f); + break; + case FORMATION_FRONT_RIGHT: + formationOffset = CVector(1.5f, 1.5f, 0.0f); + break; + case FORMATION_LEFT: + formationOffset = CVector(-1.5f, 0.0f, 0.0f); + break; + case FORMATION_RIGHT: + formationOffset = CVector(1.5f, 0.0f, 0.0f); + break; + case FORMATION_FRONT: + formationOffset = CVector(0.0f, 1.5f, 0.0f); + break; + default: + formationOffset = CVector(0.0f, 0.0f, 0.0f); + break; + } + return formationOffset + m_pedInObjective->GetPosition(); +} + +void +CPed::PedAnimStepOutCarCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + CVehicle* veh = ped->m_pMyVehicle; + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (!veh) { + PedSetOutCarCB(nil, ped); + return; + } +#ifdef VC_PED_PORTS + CVector posForZ = ped->GetPosition(); + CPedPlacement::FindZCoorForPed(&posForZ); + if (ped->GetPosition().z - 0.5f > posForZ.z) { + PedSetOutCarCB(nil, ped); + return; + } +#endif + veh->m_nStaticFrames = 0; + veh->m_vecMoveSpeed += CVector(0.001f, 0.001f, 0.001f); + veh->m_vecTurnSpeed += CVector(0.001f, 0.001f, 0.001f); + if (!veh->bIsBus) + veh->ProcessOpenDoor(ped->m_vehDoor, ANIM_STD_GETOUT_LHS, 1.0f); + + /* + // Duplicate and only in PC for some reason + if (!veh) { + PedSetOutCarCB(nil, ped); + return; + } + */ + eDoors door; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: + door = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_RR: + door = DOOR_REAR_RIGHT; + break; + case CAR_DOOR_LF: + door = DOOR_FRONT_LEFT; + break; + case CAR_DOOR_LR: + door = DOOR_REAR_LEFT; + break; + default: + break; + } + bool closeDoor = !veh->IsDoorMissing(door); + + int padNo; + if (ped->IsPlayer()) { + + // BUG? This will cause crash if m_nPedType is bigger then 1, there are only 2 pads + switch (ped->m_nPedType) { + case PEDTYPE_PLAYER1: + padNo = 0; + break; + case PEDTYPE_PLAYER2: + padNo = 1; + break; + case PEDTYPE_PLAYER3: + padNo = 2; + break; + case PEDTYPE_PLAYER4: + padNo = 3; + break; + } + CPad* pad = CPad::GetPad(padNo); + bool engineIsIntact = veh->IsCar() && ((CAutomobile*)veh)->Damage.GetEngineStatus() >= 225; + if (!pad->ArePlayerControlsDisabled() && veh->m_nDoorLock != CARLOCK_FORCE_SHUT_DOORS + && (pad->GetTarget() + || pad->NewState.LeftStickX + || pad->NewState.LeftStickY + || pad->NewState.DPadUp + || pad->NewState.DPadDown + || pad->NewState.DPadLeft + || pad->NewState.DPadRight) + || veh->bIsBus + || veh->m_pCarFire + || engineIsIntact) { + closeDoor = false; + } + } + +#ifdef VC_PED_PORTS + if (ped->m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) + closeDoor = false; +#endif + + if (!closeDoor) { + if (!veh->IsDoorMissing(door) && !veh->bIsBus) { + ((CAutomobile*)veh)->Damage.SetDoorStatus(door, DOOR_STATUS_SWINGING); + } + PedSetOutCarCB(nil, ped); + return; + } + + if (ped->bFleeAfterExitingCar || ped->bGonnaKillTheCarJacker) { +#ifdef FIX_BUGS + if (!veh->IsDoorMissing(door)) + ((CAutomobile*)veh)->Damage.SetDoorStatus(door, DOOR_STATUS_SWINGING); + PedSetOutCarCB(nil, ped); + return; +#else + if (!veh->IsDoorMissing(door)) + ((CAutomobile*)veh)->Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_SWINGING); +#endif + } else { + switch (door) { + case DOOR_FRONT_LEFT: + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_LHS); + break; + case DOOR_FRONT_RIGHT: + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_RHS); + break; + case DOOR_REAR_LEFT: + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_LHS); + break; + case DOOR_REAR_RIGHT: + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_RHS); + break; + default: + break; + } + } + + if (ped->m_pVehicleAnim) + ped->m_pVehicleAnim->SetFinishCallback(PedSetOutCarCB, ped); + return; +} + +void +CPed::LineUpPedWithCar(PedLineUpPhase phase) +{ + bool vehIsUpsideDown = false; + bool stillGettingInOut = false; + int vehAnim; + float seatPosMult = 0.0f; + float currentZ; + float adjustedTimeStep; + + if (CReplay::IsPlayingBack()) + return; + + if (!bChangedSeat && phase != LINE_UP_TO_CAR_2) { + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT)) { + SetPedPositionInCar(); + return; + } + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_LO)) { + SetPedPositionInCar(); + return; + } + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_P)) { + SetPedPositionInCar(); + return; + } + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_P_LO)) { + SetPedPositionInCar(); + return; + } + bChangedSeat = true; + } + if (phase == LINE_UP_TO_CAR_START) { + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } + CVehicle *veh = m_pMyVehicle; + + // Not quite right, IsUpsideDown func. checks for <= -0.9f. + if (veh->GetUp().z <= -0.8f) + vehIsUpsideDown = true; + + if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) { + if (vehIsUpsideDown) { + m_fRotationDest = -PI + veh->GetForward().Heading(); + } else if (veh->bIsBus) { + m_fRotationDest = 0.5f * PI + veh->GetForward().Heading(); + } else { + m_fRotationDest = veh->GetForward().Heading(); + } + } else if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) { + if (vehIsUpsideDown) { + m_fRotationDest = veh->GetForward().Heading(); + } else if (veh->bIsBus) { + m_fRotationDest = -0.5f * PI + veh->GetForward().Heading(); + } else { + m_fRotationDest = veh->GetForward().Heading(); + } + } else { + // I don't know will this part ever run(maybe boats?), but the game also handles that. I don't know is it intentional. + + if (vehIsUpsideDown) { + m_fRotationDest = veh->GetForward().Heading(); + } else if (veh->bIsBus) { + m_fRotationDest = 0.5f * PI + veh->GetForward().Heading(); + } else { + m_fRotationDest = veh->GetForward().Heading(); + } + } + + if (!bInVehicle) + seatPosMult = 1.0f; + +#ifdef VC_PED_PORTS + bool multExtractedFromAnim = false; + bool multExtractedFromAnimBus = false; + float zBlend; +#endif + if (m_pVehicleAnim) { + vehAnim = m_pVehicleAnim->animId; + + switch (vehAnim) { + case ANIM_STD_JACKEDCAR_RHS: + case ANIM_STD_JACKEDCAR_LO_RHS: + case ANIM_STD_JACKEDCAR_LHS: + case ANIM_STD_JACKEDCAR_LO_LHS: + case ANIM_STD_VAN_GET_IN_REAR_LHS: + case ANIM_STD_VAN_GET_IN_REAR_RHS: +#ifdef VC_PED_PORTS + multExtractedFromAnim = true; + zBlend = Max(m_pVehicleAnim->currentTime / m_pVehicleAnim->hierarchy->totalLength - 0.3f, 0.0f) / (1.0f - 0.3f); + // fall through +#endif + case ANIM_STD_QUICKJACKED: + case ANIM_STD_GETOUT_LHS: + case ANIM_STD_GETOUT_LO_LHS: + case ANIM_STD_GETOUT_RHS: + case ANIM_STD_GETOUT_LO_RHS: +#ifdef VC_PED_PORTS + if (!multExtractedFromAnim) { + multExtractedFromAnim = true; + zBlend = Max(m_pVehicleAnim->currentTime / m_pVehicleAnim->hierarchy->totalLength - 0.5f, 0.0f) / (1.0f - 0.5f); + } + // fall through +#endif + case ANIM_STD_CRAWLOUT_LHS: + case ANIM_STD_CRAWLOUT_RHS: + case ANIM_STD_VAN_GET_OUT_REAR_LHS: + case ANIM_STD_VAN_GET_OUT_REAR_RHS: + seatPosMult = m_pVehicleAnim->currentTime / m_pVehicleAnim->hierarchy->totalLength; + break; + case ANIM_STD_CAR_GET_IN_RHS: + case ANIM_STD_CAR_GET_IN_LHS: +#ifdef VC_PED_PORTS + if (veh && veh->IsCar() && veh->bIsBus) { + multExtractedFromAnimBus = true; + zBlend = Min(m_pVehicleAnim->currentTime / m_pVehicleAnim->hierarchy->totalLength, 0.5f) / 0.5f; + } + // fall through +#endif + case ANIM_STD_QUICKJACK: + case ANIM_STD_CAR_GET_IN_LO_LHS: + case ANIM_STD_CAR_GET_IN_LO_RHS: + case ANIM_STD_BOAT_DRIVE: + seatPosMult = m_pVehicleAnim->GetTimeLeft() / m_pVehicleAnim->hierarchy->totalLength; + break; + case ANIM_STD_CAR_CLOSE_DOOR_LHS: + case ANIM_STD_CAR_CLOSE_DOOR_LO_LHS: + case ANIM_STD_CAR_CLOSE_DOOR_RHS: + case ANIM_STD_CAR_CLOSE_DOOR_LO_RHS: + case ANIM_STD_CAR_SHUFFLE_RHS: + case ANIM_STD_CAR_SHUFFLE_LO_RHS: + seatPosMult = 0.0f; + break; + case ANIM_STD_CAR_CLOSE_LHS: + case ANIM_STD_CAR_CLOSE_RHS: + case ANIM_STD_COACH_OPEN_LHS: + case ANIM_STD_COACH_OPEN_RHS: + case ANIM_STD_COACH_GET_IN_LHS: + case ANIM_STD_COACH_GET_IN_RHS: + case ANIM_STD_COACH_GET_OUT_LHS: + seatPosMult = 1.0f; + break; + default: + break; + } + } + + CVector neededPos; + + if (phase == LINE_UP_TO_CAR_2) { + neededPos = GetPosition(); + } else { + neededPos = GetPositionToOpenCarDoor(veh, m_vehDoor, seatPosMult); + } + + CVector autoZPos = neededPos; + + if (veh->bIsInWater) { + if (veh->m_vehType == VEHICLE_TYPE_BOAT && veh->IsUpsideDown()) + autoZPos.z += 1.0f; + } else { + CPedPlacement::FindZCoorForPed(&autoZPos); + } + + if (phase == LINE_UP_TO_CAR_END || phase == LINE_UP_TO_CAR_2) { + neededPos.z = GetPosition().z; + + // Getting out + if (!veh->bIsBus || (veh->bIsBus && vehIsUpsideDown)) { + float nextZSpeed = m_vecMoveSpeed.z - GRAVITY * CTimer::GetTimeStep(); + + // If we're not in ground at next step, apply animation + if (neededPos.z + nextZSpeed >= autoZPos.z) { + m_vecMoveSpeed.z = nextZSpeed; + ApplyMoveSpeed(); + // Removing below line breaks the animation + neededPos.z = GetPosition().z; + } else { + neededPos.z = autoZPos.z; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } + } + } + + if (autoZPos.z > neededPos.z) { +#ifdef VC_PED_PORTS + if (multExtractedFromAnim) { + neededPos.z += (autoZPos.z - neededPos.z) * zBlend; + } else { +#endif + currentZ = GetPosition().z; + if (m_pVehicleAnim && vehAnim != ANIM_STD_VAN_GET_IN_REAR_LHS && vehAnim != ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS && vehAnim != ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS && vehAnim != ANIM_STD_VAN_GET_IN_REAR_RHS) { + neededPos.z = autoZPos.z; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } else if (neededPos.z < currentZ && m_pVehicleAnim && vehAnim != ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS && vehAnim != ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS) { + adjustedTimeStep = Max(m_pVehicleAnim->timeStep, 0.1f); + + // Smoothly change ped position + neededPos.z = currentZ - (currentZ - neededPos.z) / (m_pVehicleAnim->GetTimeLeft() / adjustedTimeStep); + } +#ifdef VC_PED_PORTS + } +#endif + } else { + // We may need to raise up the ped + if (phase == LINE_UP_TO_CAR_START) { + currentZ = GetPosition().z; + + if (neededPos.z > currentZ) { +#ifdef VC_PED_PORTS + if (multExtractedFromAnimBus) { + neededPos.z = (neededPos.z - currentZ) * zBlend + currentZ; + } else { +#endif + if (m_pVehicleAnim && + (vehAnim == ANIM_STD_CAR_GET_IN_RHS || vehAnim == ANIM_STD_CAR_GET_IN_LO_RHS || vehAnim == ANIM_STD_CAR_GET_IN_LHS || vehAnim == ANIM_STD_CAR_GET_IN_LO_LHS + || vehAnim == ANIM_STD_QUICKJACK || vehAnim == ANIM_STD_VAN_GET_IN_REAR_LHS || vehAnim == ANIM_STD_VAN_GET_IN_REAR_RHS)) { + adjustedTimeStep = Max(m_pVehicleAnim->timeStep, 0.1f); + + // Smoothly change ped position + neededPos.z = (neededPos.z - currentZ) / (m_pVehicleAnim->GetTimeLeft() / adjustedTimeStep) + currentZ; + } else if (EnteringCar()) { + neededPos.z = Max(currentZ, autoZPos.z); + } +#ifdef VC_PED_PORTS + } +#endif + } + } + } + + if (CTimer::GetTimeInMilliseconds() < m_nPedStateTimer) + stillGettingInOut = veh->m_vehType != VEHICLE_TYPE_BOAT || bOnBoat; + + if (!stillGettingInOut) { + m_fRotationCur = m_fRotationDest; + } else { + float limitedDest = CGeneral::LimitRadianAngle(m_fRotationDest); + float timeUntilStateChange = (m_nPedStateTimer - CTimer::GetTimeInMilliseconds())/600.0f; + + if (timeUntilStateChange <= 0.0f) { + m_vecOffsetSeek.x = 0.0f; + m_vecOffsetSeek.y = 0.0f; + } + m_vecOffsetSeek.z = 0.0f; + + neededPos -= timeUntilStateChange * m_vecOffsetSeek; + + if (PI + m_fRotationCur < limitedDest) { + limitedDest -= 2 * PI; + } else if (m_fRotationCur - PI > limitedDest) { + limitedDest += 2 * PI; + } + m_fRotationCur -= (m_fRotationCur - limitedDest) * (1.0f - timeUntilStateChange); + } + + if (seatPosMult > 0.2f || vehIsUpsideDown) { + SetPosition(neededPos); + SetHeading(m_fRotationCur); + } else { + CMatrix vehDoorMat(veh->GetMatrix()); + vehDoorMat.GetPosition() += Multiply3x3(vehDoorMat, GetLocalPositionToOpenCarDoor(veh, m_vehDoor, 0.0f)); + // VC couch anims are inverted, so they're fixing it here. + GetMatrix() = vehDoorMat; + } + +} + +void +CPed::SetCarJack(CVehicle* car) +{ + uint8 doorFlag; + eDoors door; + CPed *pedInSeat = nil; + + if (car->IsBoat()) + return; + + switch (m_vehDoor) { + case CAR_DOOR_RF: + doorFlag = CAR_DOOR_FLAG_RF; + door = DOOR_FRONT_RIGHT; + if (car->pPassengers[0]) { + pedInSeat = car->pPassengers[0]; + } else if (m_nPedType == PEDTYPE_COP) { + pedInSeat = car->pDriver; + } + break; + case CAR_DOOR_RR: + doorFlag = CAR_DOOR_FLAG_RR; + door = DOOR_REAR_RIGHT; + pedInSeat = car->pPassengers[2]; + break; + case CAR_DOOR_LF: + doorFlag = CAR_DOOR_FLAG_LF; + door = DOOR_FRONT_LEFT; + pedInSeat = car->pDriver; + break; + case CAR_DOOR_LR: + doorFlag = CAR_DOOR_FLAG_LR; + door = DOOR_REAR_LEFT; + pedInSeat = car->pPassengers[1]; + break; + default: + doorFlag = CAR_DOOR_FLAG_UNKNOWN; + break; + } + + if(car->bIsBus) + pedInSeat = car->pDriver; + + if (m_fHealth > 0.0f && (IsPlayer() || m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS || + (car->VehicleCreatedBy != MISSION_VEHICLE && car->GetModelIndex() != MI_DODO))) + if (pedInSeat && !pedInSeat->IsPedDoingDriveByShooting() && pedInSeat->m_nPedState == PED_DRIVING) + if (m_nPedState != PED_CARJACK && !m_pVehicleAnim) + if ((car->IsDoorReady(door) || car->IsDoorFullyOpen(door))) + if (!car->bIsBeingCarJacked && !(doorFlag & car->m_nGettingInFlags) && !(doorFlag & car->m_nGettingOutFlags)) + SetCarJack_AllClear(car, m_vehDoor, doorFlag); +} + +void +CPed::SetCarJack_AllClear(CVehicle *car, uint32 doorNode, uint32 doorFlag) +{ + RemoveWeaponWhenEnteringVehicle(); + if (m_nPedState != PED_SEEK_CAR) + SetStoredState(); + + m_pSeekTarget = car; + m_pSeekTarget->RegisterReference((CEntity**)&m_pSeekTarget); + SetPedState(PED_CARJACK); + car->bIsBeingCarJacked = true; + m_pMyVehicle = (CVehicle*)m_pSeekTarget; + m_pMyVehicle->RegisterReference((CEntity**)&m_pMyVehicle); + ((CVehicle*)m_pSeekTarget)->m_nNumGettingIn++; + + Say(m_nPedType == PEDTYPE_COP ? SOUND_PED_ARREST_COP : SOUND_PED_CAR_JACKING); + CVector carEnterPos; + carEnterPos = GetPositionToOpenCarDoor(car, m_vehDoor); + + car->m_nGettingInFlags |= doorFlag; + m_vecOffsetSeek = carEnterPos - GetPosition(); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 600; + float zDiff = Max(0.0f, carEnterPos.z - GetPosition().z); + bUsesCollision = false; + + if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, zDiff > 4.4f ? ANIM_STD_CAR_ALIGNHI_DOOR_LHS : ANIM_STD_CAR_ALIGN_DOOR_LHS, 4.0f); + else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, zDiff > 4.4f ? ANIM_STD_CAR_ALIGNHI_DOOR_RHS : ANIM_STD_CAR_ALIGN_DOOR_RHS, 4.0f); + + m_pVehicleAnim->SetFinishCallback(PedAnimAlignCB, this); +} + +void +CPed::SetBeingDraggedFromCar(CVehicle *veh, uint32 vehEnterType, bool quickJack) +{ + if (m_nPedState == PED_DRAG_FROM_CAR) + return; + + bUsesCollision = false; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_nLastPedState = PED_IDLE; + SetMoveState(PEDMOVE_STILL); + m_pSeekTarget = veh; + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + m_vehDoor = vehEnterType; + if (m_vehDoor == CAR_DOOR_LF) { + if (veh->pDriver && veh->pDriver->IsPlayer()) + veh->SetStatus(STATUS_PLAYER_DISABLED); + else + veh->SetStatus(STATUS_ABANDONED); + } + RemoveInCarAnims(); + SetMoveState(PEDMOVE_NONE); + LineUpPedWithCar(LINE_UP_TO_CAR_START); + m_pVehicleAnim = nil; + SetPedState(PED_DRAG_FROM_CAR); + bChangedSeat = false; + bWillBeQuickJacked = quickJack; + + SetHeading(m_fRotationCur); + + Say(SOUND_PED_CAR_JACKED); + SetRadioStation(); + veh->m_nGettingOutFlags |= GetCarDoorFlag(m_vehDoor); +} + +void +CPed::BeingDraggedFromCar(void) +{ + CAnimBlendAssociation *animAssoc; + AnimationId enterAnim; + bool dontRunAnim = false; + PedLineUpPhase lineUpType; + + if (!m_pVehicleAnim) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 100.0f); + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT); + if (!animAssoc) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_LO); + if (!animAssoc) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_P); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_P_LO); + } + } + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) { + if (bWillBeQuickJacked) { + enterAnim = ANIM_STD_QUICKJACKED; + } else if (m_pMyVehicle->bLowVehicle) { + enterAnim = ANIM_STD_JACKEDCAR_LO_LHS; + } else { + enterAnim = ANIM_STD_JACKEDCAR_LHS; + } + } else if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) { + if (m_pMyVehicle->bLowVehicle) + enterAnim = ANIM_STD_JACKEDCAR_LO_RHS; + else + enterAnim = ANIM_STD_JACKEDCAR_RHS; + } else + dontRunAnim = true; + + + if (!dontRunAnim) + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, enterAnim); + + m_pVehicleAnim->SetFinishCallback(PedSetDraggedOutCarCB, this); + lineUpType = LINE_UP_TO_CAR_START; + } else if (m_pVehicleAnim->currentTime <= 1.4f) { + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + lineUpType = LINE_UP_TO_CAR_START; + } else { + lineUpType = LINE_UP_TO_CAR_2; + } + + LineUpPedWithCar(lineUpType); +#ifdef VC_PED_PORTS + if (m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) { + if (m_pMyVehicle) { + m_pMyVehicle->ProcessOpenDoor(m_vehDoor, ANIM_STD_NUM, m_pVehicleAnim->currentTime * 5.0f); + } + } +#endif +} + +void +CPed::SetEnterCar(CVehicle *car, uint32 unused) +{ + if (CCranes::IsThisCarBeingCarriedByAnyCrane(car)) { + RestorePreviousState(); + RestorePreviousObjective(); + } else { + uint8 doorFlag; + eDoors door; + switch (m_vehDoor) { + case CAR_DOOR_RF: + doorFlag = CAR_DOOR_FLAG_RF; + door = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_RR: + doorFlag = CAR_DOOR_FLAG_RR; + door = DOOR_REAR_RIGHT; + break; + case CAR_DOOR_LF: + doorFlag = CAR_DOOR_FLAG_LF; + door = DOOR_FRONT_LEFT; + break; + case CAR_DOOR_LR: + doorFlag = CAR_DOOR_FLAG_LR; + door = DOOR_REAR_LEFT; + break; + default: + doorFlag = CAR_DOOR_FLAG_UNKNOWN; + break; + } + if (!IsPedInControl() || m_fHealth <= 0.0f + || doorFlag & car->m_nGettingInFlags || doorFlag & car->m_nGettingOutFlags + || car->bIsBeingCarJacked || m_pVehicleAnim + || doorFlag && !car->IsDoorReady(door) && !car->IsDoorFullyOpen(door)) + SetMoveState(PEDMOVE_STILL); + else + SetEnterCar_AllClear(car, m_vehDoor, doorFlag); + } +} + +void +CPed::SetEnterCar_AllClear(CVehicle *car, uint32 doorNode, uint32 doorFlag) +{ + float zDiff = 0.0f; + RemoveWeaponWhenEnteringVehicle(); + car->m_nGettingInFlags |= doorFlag; + bVehEnterDoorIsBlocked = false; + if (m_nPedState != PED_SEEK_CAR && m_nPedState != PED_SEEK_IN_BOAT) + SetStoredState(); + + m_pSeekTarget = car; + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + m_vehDoor = doorNode; + SetPedState(PED_ENTER_CAR); + if (m_vehDoor == CAR_DOOR_RF && m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER && car->m_vehType != VEHICLE_TYPE_BIKE) { + car->bIsBeingCarJacked = true; + } + + m_pMyVehicle = (CVehicle*)m_pSeekTarget; + m_pMyVehicle->RegisterReference((CEntity**) &m_pMyVehicle); + ((CVehicle*)m_pSeekTarget)->m_nNumGettingIn++; + bUsesCollision = false; + CVector doorOpenPos = GetPositionToOpenCarDoor(car, m_vehDoor); + + // Because buses have stairs + if (!m_pMyVehicle->bIsBus) + zDiff = Max(0.0f, doorOpenPos.z - GetPosition().z); + + m_vecOffsetSeek = doorOpenPos - GetPosition(); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 600; + if (car->IsBoat()) { +#ifndef FIX_BUGS + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_BOAT_DRIVE, 100.0f); +#else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, car->GetDriverAnim(), 100.0f); +#endif + + // Otherwise boat enter key sometimes processed multiple times, so you enter/exit instantly +#if defined VC_PED_PORTS || defined FIX_BUGS + PedSetInCarCB(nil, this); + bVehExitWillBeInstant = true; +#else + m_pVehicleAnim->SetFinishCallback(PedSetInCarCB, this); +#endif + if (IsPlayer()) + CWaterLevel::AllocateBoatWakeArray(); + } else { + if (zDiff > 4.4f) { + if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_ALIGNHI_DOOR_RHS, 4.0f); + else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_ALIGNHI_DOOR_LHS, 4.0f); + + } else { + if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_ALIGN_DOOR_RHS, 4.0f); + else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_ALIGN_DOOR_LHS, 4.0f); + } + m_pVehicleAnim->SetFinishCallback(PedAnimAlignCB, this); + car->AutoPilot.m_nCruiseSpeed = 0; + } +} + +void +CPed::EnterCar(void) +{ + if (IsNotInWreckedVehicle() && m_fHealth > 0.0f) { + CVehicle *veh = (CVehicle*)m_pSeekTarget; + + // Not used. + // CVector posForDoor = GetPositionToOpenCarDoor(veh, m_vehDoor); + + if (veh->CanPedOpenLocks(this)) { + if (m_vehDoor && m_pVehicleAnim) { + veh->ProcessOpenDoor(m_vehDoor, m_pVehicleAnim->animId, m_pVehicleAnim->currentTime); + } + } + bIsInTheAir = false; + LineUpPedWithCar(LINE_UP_TO_CAR_START); + } else { + QuitEnteringCar(); + SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } +} + +void +CPed::QuitEnteringCar(void) +{ + CVehicle *veh = m_pMyVehicle; + if (m_pVehicleAnim) + m_pVehicleAnim->blendDelta = -1000.0f; + + RestartNonPartialAnims(); + + if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE)) + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 100.0f); + + if (veh) { + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_nPedState == PED_CARJACK) + veh->bIsBeingCarJacked = false; + + if (veh->m_nNumGettingIn != 0) + veh->m_nNumGettingIn--; + +#ifdef VC_PED_PORTS + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) + RestorePreviousObjective(); +#endif + + veh->m_nGettingInFlags &= ~GetCarDoorFlag(m_vehDoor); + } + + bUsesCollision = true; + + ReplaceWeaponWhenExitingVehicle(); + + if (DyingOrDead()) { + if (m_pVehicleAnim) { + m_pVehicleAnim->blendDelta = -4.0f; + m_pVehicleAnim->flags |= ASSOC_DELETEFADEDOUT; + m_pVehicleAnim->flags &= ~ASSOC_RUNNING; + } + } else + SetIdle(); + + m_pVehicleAnim = nil; + + if (veh) { +#ifdef VC_PED_PORTS + if (veh->AutoPilot.m_nCruiseSpeed == 0 && veh->VehicleCreatedBy == RANDOM_VEHICLE) +#else + if (veh->AutoPilot.m_nCruiseSpeed == 0) +#endif + veh->AutoPilot.m_nCruiseSpeed = 17; + } +} + +void +AddYardieDoorSmoke(CVehicle *veh, uint32 doorNode) +{ + eDoors door; + switch (doorNode) { + case CAR_DOOR_RF: + door = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_LF: + door = DOOR_FRONT_LEFT; + break; + default: + break; + } + + if (!veh->IsDoorMissing(door) && veh->IsComponentPresent(doorNode)) { + CVector pos; +#ifdef FIX_BUGS + veh->GetComponentWorldPosition(doorNode, pos); +#else + veh->GetComponentWorldPosition(CAR_DOOR_LF, pos); +#endif + CParticle::AddYardieDoorSmoke(pos, veh->GetMatrix()); + } +} + +// Seperate function in VC, more logical. Not sure is it inlined in III. +void +CPed::SetExitBoat(CVehicle *boat) +{ +#ifndef VC_PED_PORTS + SetPedState(PED_IDLE); + CVector firstPos = GetPosition(); + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 100.0f); + if (boat->GetModelIndex() == MI_SPEEDER && boat->IsUpsideDown()) { + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CRAWLOUT_LHS, 8.0f); + m_pVehicleAnim->SetFinishCallback(PedSetOutCarCB, this); + m_vehDoor = CAR_DOOR_RF; + SetPedState(PED_EXIT_CAR); + } else { + m_vehDoor = CAR_DOOR_RF; + PedSetOutCarCB(nil, this); + bIsStanding = true; + m_pCurSurface = boat; + m_pCurSurface->RegisterReference((CEntity**)&m_pCurSurface); + } + SetPosition(firstPos); + SetMoveState(PEDMOVE_STILL); + m_vecMoveSpeed = boat->m_vecMoveSpeed; + bTryingToReachDryLand = true; +#else + SetPedState(PED_IDLE); + CVector newPos = GetPosition(); + RemoveInCarAnims(); + CColModel* boatCol = boat->GetColModel(); + if (boat->IsUpsideDown()) { + newPos = { 0.0f, 0.0f, boatCol->boundingBox.min.z }; + newPos = boat->GetMatrix() * newPos; + newPos.z += 1.0f; + m_vehDoor = CAR_DOOR_RF; + PedSetOutCarCB(nil, this); + bIsStanding = true; + m_pCurSurface = boat; + m_pCurSurface->RegisterReference((CEntity**)&m_pCurSurface); + m_pCurrentPhysSurface = boat; + } else { +/* if (boat->m_modelIndex != MI_SKIMMER || boat->bIsInWater) { + if (boat->m_modelIndex == MI_SKIMMER) + newPos.z += 2.0f +*/ + m_vehDoor = CAR_DOOR_RF; + PedSetOutCarCB(nil, this); + bIsStanding = true; + m_pCurSurface = boat; + m_pCurSurface->RegisterReference((CEntity**)&m_pCurSurface); + m_pCurrentPhysSurface = boat; + CColPoint foundCol; + CEntity *foundEnt = nil; + if (CWorld::ProcessVerticalLine(newPos, newPos.z - 1.4f, foundCol, foundEnt, false, true, false, false, false, false, nil)) + newPos.z = FEET_OFFSET + foundCol.point.z; +/* // VC specific + } else { + m_vehDoor = CAR_DOOR_RF; + PedSetOutCarCB(nil, this); + bIsStanding = true; + SetMoveState(PEDMOVE_STILL); + bTryingToReachDryLand = true; + float upMult = 1.04f + boatCol->boundingBox.min.z; + float rightMult = 0.6f * boatCol->boundingBox.max.x; + newPos = upMult * boat->GetUp() + rightMult * boat->GetRight() + boat->GetPosition(); + GetPosition() = newPos; + if (m_pMyVehicle) { + PositionPedOutOfCollision(); + } else { + m_pMyVehicle = boat; + PositionPedOutOfCollision(); + m_pMyVehicle = nil; + } + return; + } +*/ } + SetPosition(newPos); + SetMoveState(PEDMOVE_STILL); + m_vecMoveSpeed = boat->m_vecMoveSpeed; +#endif + // Not there in VC. + CWaterLevel::FreeBoatWakeArray(); +} + +// wantedDoorNode = 0 means that func. will determine it +void +CPed::SetExitCar(CVehicle *veh, uint32 wantedDoorNode) +{ + uint32 optedDoorNode = wantedDoorNode; + bool teleportNeeded = false; + bool isLow = !!veh->bLowVehicle; + if (!veh->CanPedExitCar()) { + if (veh->pDriver && !veh->pDriver->IsPlayer()) { + veh->AutoPilot.m_nCruiseSpeed = 0; + veh->AutoPilot.m_nCarMission = MISSION_NONE; + } + return; + } + + if (m_nPedState == PED_EXIT_CAR || m_nPedState == PED_DRAG_FROM_CAR) + return; + + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + if (wantedDoorNode == 0) { + optedDoorNode = CAR_DOOR_LF; + if (!veh->bIsBus) { + if (veh->pDriver == this) { + optedDoorNode = CAR_DOOR_LF; + } else if (veh->pPassengers[0] == this) { + optedDoorNode = CAR_DOOR_RF; + } else if (veh->pPassengers[1] == this) { + optedDoorNode = CAR_DOOR_LR; + } else if (veh->pPassengers[2] == this) { + optedDoorNode = CAR_DOOR_RR; + } else { + for (int i = 3; i < veh->m_nNumMaxPassengers; ++i) { + if (veh->pPassengers[i] == this) { + if (i & 1) + optedDoorNode = CAR_DOOR_RR; + else + optedDoorNode = CAR_DOOR_LR; + + break; + } + } + } + } + } + bool someoneExitsFromOurExitDoor = false; + bool someoneEntersFromOurExitDoor = false; + switch (optedDoorNode) { + case CAR_DOOR_RF: + if (veh->m_nGettingInFlags & CAR_DOOR_FLAG_RF) + someoneEntersFromOurExitDoor = true; + if (veh->m_nGettingOutFlags & CAR_DOOR_FLAG_RF) + someoneExitsFromOurExitDoor = true; + break; + case CAR_DOOR_RR: + if (veh->m_nGettingInFlags & CAR_DOOR_FLAG_RR) + someoneEntersFromOurExitDoor = true; + if (veh->m_nGettingOutFlags & CAR_DOOR_FLAG_RR) + someoneExitsFromOurExitDoor = true; + break; + case CAR_DOOR_LF: + if (veh->m_nGettingInFlags & CAR_DOOR_FLAG_LF) + someoneEntersFromOurExitDoor = true; + if (veh->m_nGettingOutFlags & CAR_DOOR_FLAG_LF) + someoneExitsFromOurExitDoor = true; + break; + case CAR_DOOR_LR: + if (veh->m_nGettingInFlags & CAR_DOOR_FLAG_LR) + someoneEntersFromOurExitDoor = true; + if (veh->m_nGettingOutFlags & CAR_DOOR_FLAG_LR) + someoneExitsFromOurExitDoor = true; + break; + default: + break; + } + if (someoneEntersFromOurExitDoor && m_objective == OBJECTIVE_LEAVE_CAR) { + RestorePreviousObjective(); + return; + } + if (!someoneExitsFromOurExitDoor || m_nPedType == PEDTYPE_COP && veh->bIsBus) { + // Again, unused... + // CVector exitPos = GetPositionToOpenCarDoor(veh, optedDoorNode); + bool thereIsRoom = veh->IsRoomForPedToLeaveCar(optedDoorNode, nil); + if (veh->IsOnItsSide()) { + teleportNeeded = true; + } else if (!thereIsRoom) { + bool trySideSeat = false; + CPed *pedOnSideSeat = nil; + switch (optedDoorNode) { + case CAR_DOOR_RF: + if (veh->pDriver || veh->m_nGettingInFlags & CAR_DOOR_FLAG_LF) { + pedOnSideSeat = veh->pDriver; + trySideSeat = true; + } else + optedDoorNode = CAR_DOOR_LF; + + break; + case CAR_DOOR_RR: + if (veh->pPassengers[1] || veh->m_nGettingInFlags & CAR_DOOR_FLAG_LR) { + pedOnSideSeat = veh->pPassengers[1]; + trySideSeat = true; + } else + optedDoorNode = CAR_DOOR_LR; + + break; + case CAR_DOOR_LF: + if (veh->pPassengers[0] || veh->m_nGettingInFlags & CAR_DOOR_FLAG_RF) { + pedOnSideSeat = veh->pPassengers[0]; + trySideSeat = true; + } else + optedDoorNode = CAR_DOOR_RF; + + break; + case CAR_DOOR_LR: + if (veh->pPassengers[2] || veh->m_nGettingInFlags & CAR_DOOR_FLAG_RR) { + pedOnSideSeat = (CPed*)veh->pPassengers[2]; + trySideSeat = true; + } else + optedDoorNode = CAR_DOOR_RR; + + break; + default: + break; + } + if (trySideSeat) { + if (!pedOnSideSeat || !IsPlayer() && CharCreatedBy != MISSION_CHAR) + return; + + switch (optedDoorNode) { + case CAR_DOOR_RF: + optedDoorNode = CAR_DOOR_LF; + break; + case CAR_DOOR_RR: + optedDoorNode = CAR_DOOR_LR; + break; + case CAR_DOOR_LF: + optedDoorNode = CAR_DOOR_RF; + break; + case CAR_DOOR_LR: + optedDoorNode = CAR_DOOR_RR; + break; + default: + break; + } + } + // ... + // CVector exitPos = GetPositionToOpenCarDoor(veh, optedDoorNode); + if (!veh->IsRoomForPedToLeaveCar(optedDoorNode, nil)) { + if (!IsPlayer() && CharCreatedBy != MISSION_CHAR) + return; + + teleportNeeded = true; + } + } + if (m_nPedState == PED_FLEE_POS) { + m_nLastPedState = PED_FLEE_POS; + m_nPrevMoveState = PEDMOVE_RUN; + SetMoveState(PEDMOVE_SPRINT); + } else { + m_nLastPedState = PED_IDLE; + m_nPrevMoveState = PEDMOVE_STILL; + SetMoveState(PEDMOVE_STILL); + } + + ReplaceWeaponWhenExitingVehicle(); + bUsesCollision = false; + m_pSeekTarget = veh; + m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget); + m_vehDoor = optedDoorNode; + SetPedState(PED_EXIT_CAR); + if (m_pVehicleAnim && m_pVehicleAnim->flags & ASSOC_PARTIAL) + m_pVehicleAnim->blendDelta = -1000.0f; + SetMoveState(PEDMOVE_NONE); + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 100.0f); + RemoveInCarAnims(); + veh->AutoPilot.m_nCruiseSpeed = 0; + if (teleportNeeded) { + PedSetOutCarCB(nil, this); + + // This is same code with CPedPlacement::FindZCoorForPed, except we start from z + 1.5 and also check vehicles. + float zForPed; + float startZ = GetPosition().z - 100.0f; + float foundColZ = -100.0f; + float foundColZ2 = -100.0f; + CColPoint foundCol; + CEntity* foundEnt; + + CVector vec = GetPosition(); + vec.z += 1.5f; + + if (CWorld::ProcessVerticalLine(vec, startZ, foundCol, foundEnt, true, true, false, false, true, false, nil)) + foundColZ = foundCol.point.z; + + // Adjust coords and do a second test + vec.x += 0.1f; + vec.y += 0.1f; + + if (CWorld::ProcessVerticalLine(vec, startZ, foundCol, foundEnt, true, true, false, false, true, false, nil)) + foundColZ2 = foundCol.point.z; + + zForPed = Max(foundColZ, foundColZ2); + + if (zForPed > -99.0f) + GetMatrix().GetPosition().z = FEET_OFFSET + zForPed; + } else { + if (veh->GetUp().z > -0.8f) { + bool addDoorSmoke = false; + if (veh->GetModelIndex() == MI_YARDIE) + addDoorSmoke = true; + + switch (m_vehDoor) { + case CAR_DOOR_RF: + if (veh->bIsBus) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_COACH_GET_OUT_LHS); + } else { + if (isLow) + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LO_RHS); + else + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_RHS); + + if (addDoorSmoke) + AddYardieDoorSmoke(veh, CAR_DOOR_RF); + } + break; + case CAR_DOOR_RR: + if (veh->bIsVan) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_VAN_GET_OUT_REAR_RHS); + } else if (isLow) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LO_RHS); + } else { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_RHS); + } + break; + case CAR_DOOR_LF: + if (veh->bIsBus) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_COACH_GET_OUT_LHS); + } else { + if (isLow) + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LO_LHS); + else + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LHS); + + if (addDoorSmoke) + AddYardieDoorSmoke(veh, CAR_DOOR_LF); + } + break; + case CAR_DOOR_LR: + if (veh->bIsVan) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_VAN_GET_OUT_REAR_LHS); + } else if (isLow) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LO_LHS); + } else { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LHS); + } + break; + default: + break; + } + if (!bBusJacked) { + switch (m_vehDoor) { + case CAR_DOOR_RF: + veh->m_nGettingOutFlags |= CAR_DOOR_FLAG_RF; + break; + case CAR_DOOR_RR: + veh->m_nGettingOutFlags |= CAR_DOOR_FLAG_RR; + break; + case CAR_DOOR_LF: + veh->m_nGettingOutFlags |= CAR_DOOR_FLAG_LF; + break; + case CAR_DOOR_LR: + veh->m_nGettingOutFlags |= CAR_DOOR_FLAG_LR; + break; + default: + break; + } + } + m_pVehicleAnim->SetFinishCallback(PedAnimStepOutCarCB, this); + } else { + if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CRAWLOUT_RHS); + } else if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CRAWLOUT_LHS); + } + m_pVehicleAnim->SetFinishCallback(PedSetOutCarCB, this); + } + } + bChangedSeat = false; + if (veh->bIsBus) + bRenderPedInCar = true; + + SetRadioStation(); + if (veh->pDriver == this) { + if (IsPlayer()) + veh->SetStatus(STATUS_PLAYER_DISABLED); + else + veh->SetStatus(STATUS_ABANDONED); + } + } +} + +void +CPed::ExitCar(void) +{ + if (!m_pVehicleAnim) + return; + + AnimationId exitAnim = (AnimationId) m_pVehicleAnim->animId; + float animTime = m_pVehicleAnim->currentTime; + + m_pMyVehicle->ProcessOpenDoor(m_vehDoor, exitAnim, animTime); + + if (m_pSeekTarget) { + // Car is upside down + if (m_pMyVehicle->GetUp().z > -0.8f) { + if (exitAnim == ANIM_STD_CAR_CLOSE_RHS || exitAnim == ANIM_STD_CAR_CLOSE_LHS || animTime > 0.3f) + LineUpPedWithCar(LINE_UP_TO_CAR_END); + else + LineUpPedWithCar((m_pMyVehicle->GetModelIndex() == MI_DODO ? LINE_UP_TO_CAR_END : LINE_UP_TO_CAR_START)); + } else { + LineUpPedWithCar(LINE_UP_TO_CAR_END); + } + } + + // If there is someone in front of the door, make him fall while we exit. + if (m_nPedState == PED_EXIT_CAR) { + CPed *foundPed = nil; + for (int i = 0; i < m_numNearPeds; i++) { + if ((m_nearPeds[i]->GetPosition() - GetPosition()).MagnitudeSqr2D() < 0.04f) { + foundPed = m_nearPeds[i]; + break; + } + } + if (foundPed && animTime > 0.4f && foundPed->IsPedInControl()) + foundPed->SetFall(1000, ANIM_STD_HIGHIMPACT_FRONT, 1); + } +} + +// This function was mostly duplicate of GetLocalPositionToOpenCarDoor, so I've used it. +CVector +CPed::GetPositionToOpenCarDoor(CVehicle *veh, uint32 component) +{ + CVector localPos; + CVector vehDoorPos; + + localPos = GetLocalPositionToOpenCarDoor(veh, component, 1.0f); + vehDoorPos = Multiply3x3(veh->GetMatrix(), localPos) + veh->GetPosition(); + +/* + // Not used. + CVector localVehDoorOffset; + + if (veh->bIsVan && (component == VEHICLE_ENTER_REAR_LEFT || component == VEHICLE_ENTER_REAR_RIGHT)) { + localVehDoorOffset = vecPedVanRearDoorAnimOffset; + } else { + if (veh->bIsLow) { + localVehDoorOffset = vecPedCarDoorLoAnimOffset; + } else { + localVehDoorOffset = vecPedCarDoorAnimOffset; + } + } + + vehDoorPosWithoutOffset = Multiply3x3(veh->GetMatrix(), localPos + localVehDoorOffset) + veh->GetPosition(); +*/ + return vehDoorPos; +} + +void +CPed::GetNearestDoor(CVehicle *veh, CVector &posToOpen) +{ + CVector *enterOffset = nil; + if (m_vehDoor == CAR_DOOR_LF && veh->pDriver + || m_vehDoor == CAR_DOOR_RF && veh->pPassengers[0] + || m_vehDoor == CAR_DOOR_LR && veh->pPassengers[1] + || m_vehDoor == CAR_DOOR_RR && veh->pPassengers[2]) + { + enterOffset = &vecPedQuickDraggedOutCarAnimOffset; + } + + CVector lfPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_LF); + CVector rfPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_RF); + + // Left front door is closer + if ((lfPos - GetPosition()).MagnitudeSqr2D() < (rfPos - GetPosition()).MagnitudeSqr2D()) { + + if (veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, enterOffset)) { + m_vehDoor = CAR_DOOR_LF; + posToOpen = lfPos; + } else if (veh->IsRoomForPedToLeaveCar(CAR_DOOR_RF, enterOffset)) { + m_vehDoor = CAR_DOOR_RF; + posToOpen = rfPos; + } + } else { + + if (veh->IsRoomForPedToLeaveCar(CAR_DOOR_RF, enterOffset)) { + + CPed *rfPassenger = veh->pPassengers[0]; + if (rfPassenger && (rfPassenger->m_leader == this || rfPassenger->bDontDragMeOutCar || + veh->VehicleCreatedBy == MISSION_VEHICLE && m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) + && veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, enterOffset) + || (veh->m_nGettingInFlags & CAR_DOOR_FLAG_RF) && veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, enterOffset)) { + + m_vehDoor = CAR_DOOR_LF; + posToOpen = lfPos; + } else { + m_vehDoor = CAR_DOOR_RF; + posToOpen = rfPos; + } + } else if (veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, enterOffset)) { + m_vehDoor = CAR_DOOR_LF; + posToOpen = lfPos; + } + } +} + +bool +CPed::GetNearestPassengerDoor(CVehicle *veh, CVector &posToOpen) +{ + CVector rfPos, lrPos, rrPos; + bool canEnter = false; + + CVehicleModelInfo *vehModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(veh->GetModelIndex()); + + switch (veh->GetModelIndex()) { + case MI_BUS: + m_vehDoor = CAR_DOOR_RF; + posToOpen = GetPositionToOpenCarDoor(veh, CAR_DOOR_RF); + return true; + case MI_RHINO: + default: + break; + } + + CVector2D rfPosDist(999.0f, 999.0f); + CVector2D lrPosDist(999.0f, 999.0f); + CVector2D rrPosDist(999.0f, 999.0f); + + if (!veh->pPassengers[0] + && !(veh->m_nGettingInFlags & CAR_DOOR_FLAG_RF) + && veh->IsRoomForPedToLeaveCar(CAR_DOOR_RF, nil)) { + + rfPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_RF); + canEnter = true; + rfPosDist = rfPos - GetPosition(); + } + if (vehModel->m_numDoors == 4) { + if (!veh->pPassengers[1] + && !(veh->m_nGettingInFlags & CAR_DOOR_FLAG_LR) + && veh->IsRoomForPedToLeaveCar(CAR_DOOR_LR, nil)) { + lrPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_LR); + canEnter = true; + lrPosDist = lrPos - GetPosition(); + } + if (!veh->pPassengers[2] + && !(veh->m_nGettingInFlags & CAR_DOOR_FLAG_RR) + && veh->IsRoomForPedToLeaveCar(CAR_DOOR_RR, nil)) { + rrPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_RR); + canEnter = true; + rrPosDist = rrPos - GetPosition(); + } + + // When the door we should enter is blocked by some object. + if (!canEnter) + veh->ShufflePassengersToMakeSpace(); + } + + CVector2D nextToCompare = rfPosDist; + posToOpen = rfPos; + m_vehDoor = CAR_DOOR_RF; + if (lrPosDist.MagnitudeSqr() < nextToCompare.MagnitudeSqr()) { + m_vehDoor = CAR_DOOR_LR; + posToOpen = lrPos; + nextToCompare = lrPosDist; + } + + if (rrPosDist.MagnitudeSqr() < nextToCompare.MagnitudeSqr()) { + m_vehDoor = CAR_DOOR_RR; + posToOpen = rrPos; + } + return canEnter; +} + +void +CPed::GoToNearestDoor(CVehicle *veh) +{ + CVector posToOpen; + GetNearestDoor(veh, posToOpen); + SetSeek(posToOpen, 0.5f); + SetMoveState(PEDMOVE_RUN); +} + +void +CPed::SetAnimOffsetForEnterOrExitVehicle(void) +{ + // FIX: If there were no translations on enter anims, there were overflows all over this function. + + CAnimBlendHierarchy *enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_JACKEDCAR_LHS)->hierarchy; + CAnimBlendSequence *seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedDraggedOutCarAnimOffset = lastFrame->translation; + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LHS)->hierarchy; + seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedCarDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedCarDoorAnimOffset = lastFrame->translation; + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_LHS)->hierarchy; + seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedCarDoorLoAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedCarDoorLoAnimOffset = lastFrame->translation; + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_QUICKJACKED)->hierarchy; + seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedQuickDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedQuickDraggedOutCarAnimOffset = lastFrame->translation; + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_VAN_GET_IN_REAR_LHS)->hierarchy; + seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedVanRearDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedVanRearDoorAnimOffset = lastFrame->translation; + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_TRAIN_GETOUT)->hierarchy; + seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedTrainDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedTrainDoorAnimOffset = lastFrame->translation; + } + } +} + +void +CPed::PedSetQuickDraggedOutCarPositionCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CVehicle *veh = ped->m_pMyVehicle; + + CVector finalPos; + CVector draggedOutOffset; + + CMatrix pedMat(ped->GetMatrix()); + ped->bUsesCollision = true; + ped->RestartNonPartialAnims(); + draggedOutOffset = vecPedQuickDraggedOutCarAnimOffset; + if (ped->m_vehDoor == CAR_DOOR_RF || ped->m_vehDoor == CAR_DOOR_RR) + draggedOutOffset.x = -draggedOutOffset.x; + + finalPos = Multiply3x3(pedMat, draggedOutOffset) + ped->GetPosition(); + CPedPlacement::FindZCoorForPed(&finalPos); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + ped->SetPosition(finalPos); + + if (veh) { + ped->m_fRotationDest = veh->GetForward().Heading() - HALFPI; + ped->m_fRotationCur = ped->m_fRotationDest; + ped->CalculateNewOrientation(); + + if (!veh->IsRoomForPedToLeaveCar(ped->m_vehDoor, &vecPedQuickDraggedOutCarAnimOffset)) + ped->PositionPedOutOfCollision(); + } + + if (!ped->CanSetPedState()) + return; + + ped->SetIdle(); + if (veh) { + if (ped->bFleeAfterExitingCar) { + ped->bFleeAfterExitingCar = false; + ped->SetFlee(veh->GetPosition(), 14000); + + } else if (ped->bWanderPathAfterExitingCar) { + ped->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + ped->bWanderPathAfterExitingCar = false; + + } else if (ped->bGonnaKillTheCarJacker) { + ped->bGonnaKillTheCarJacker = false; + if (ped->m_pedInObjective && CGeneral::GetRandomNumber() & 1) { + if (ped->m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) + ped->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, ped->m_pedInObjective); + + } else { + CPed *driver = veh->pDriver; + if (!driver || driver == ped || driver->IsPlayer() && CTheScripts::IsPlayerOnAMission()) { + ped->SetFlee(veh->GetPosition(), 14000); + } else { + ped->ClearObjective(); + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, veh); + } + ped->bUsePedNodeSeek = true; + ped->m_pNextPathNode = nil; + ped->Say(SOUND_PED_FLEE_RUN); + } + } else if (ped->m_pedStats->m_temper > ped->m_pedStats->m_fear + && ped->CharCreatedBy != MISSION_CHAR && veh->VehicleCreatedBy != MISSION_VEHICLE + && veh->pDriver && veh->pDriver->IsPlayer() + && !CTheScripts::IsPlayerOnAMission()) { + +#ifndef VC_PED_PORTS + if (CGeneral::GetRandomNumber() < MYRAND_MAX / 2) { + ped->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, veh->pDriver); + } else +#endif + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, veh); + +#ifdef VC_PED_PORTS + } else if (ped->m_pedStats->m_temper > ped->m_pedStats->m_fear + && ped->CharCreatedBy != MISSION_CHAR && veh->VehicleCreatedBy != MISSION_VEHICLE + && !veh->pDriver && FindPlayerPed()->m_carInObjective == veh + && !CTheScripts::IsPlayerOnAMission()) { + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, veh); +#endif + } else { + ped->SetFindPathAndFlee(veh->GetPosition(), 10000); + if (CGeneral::GetRandomNumber() & 1 || ped->m_pedStats->m_fear > 70) { + ped->SetMoveState(PEDMOVE_SPRINT); + ped->Say(SOUND_PED_FLEE_SPRINT); + } else { + ped->Say(SOUND_PED_FLEE_RUN); + } + } + } + if (ped->m_nLastPedState == PED_IDLE) + ped->m_nLastPedState = PED_WANDER_PATH; +} + +void +CPed::PedSetDraggedOutCarPositionCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed *ped = (CPed*)arg; + + ped->bUsesCollision = true; + ped->RestartNonPartialAnims(); + bool itsRearDoor = false; + + if (ped->m_vehDoor == CAR_DOOR_RF || ped->m_vehDoor == CAR_DOOR_RR) + itsRearDoor = true; + + CMatrix pedMat(ped->GetMatrix()); + CVector posAfterBeingDragged = Multiply3x3(pedMat, (itsRearDoor ? -vecPedDraggedOutCarAnimOffset : vecPedDraggedOutCarAnimOffset)); + posAfterBeingDragged += ped->GetPosition(); +#ifndef VC_PED_PORTS + posAfterBeingDragged.z += 1.0f; +#endif + CPedPlacement::FindZCoorForPed(&posAfterBeingDragged); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + ped->SetPosition(posAfterBeingDragged); + + if (ped->m_pMyVehicle && !ped->m_pMyVehicle->IsRoomForPedToLeaveCar(ped->m_vehDoor, &vecPedDraggedOutCarAnimOffset)) { + ped->PositionPedOutOfCollision(); + } + + if (!ped->CanSetPedState()) + return; + + if (!ped->m_pMyVehicle) { + ped->SetIdle(); + ped->SetGetUp(); + return; + } + + CPed *driver = ped->m_pMyVehicle->pDriver; + + if (ped->IsPlayer()) { + ped->SetIdle(); + + } else if (ped->bFleeAfterExitingCar) { + ped->bFleeAfterExitingCar = false; + ped->SetFlee(ped->m_pMyVehicle->GetPosition(), 4000); + + } else if (ped->bWanderPathAfterExitingCar) { + ped->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + ped->bWanderPathAfterExitingCar = false; + + } else if (ped->bGonnaKillTheCarJacker) { + // Kill objective is already set at this point. + + ped->bGonnaKillTheCarJacker = false; + if (!ped->m_pedInObjective || !(CGeneral::GetRandomNumber() & 1)) { + if (!driver || driver == ped || driver->IsPlayer() && CTheScripts::IsPlayerOnAMission()) { + ped->SetPedState(PED_NONE); + ped->m_nLastPedState = PED_NONE; + ped->SetFlee(ped->m_pMyVehicle->GetPosition(), 4000); + } else { + ped->ClearObjective(); + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, ped->m_pMyVehicle); + } + } + + } else if (ped->m_pedStats->m_temper > ped->m_pedStats->m_fear && ped->CharCreatedBy != MISSION_CHAR + && ped->m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE && driver + && driver->IsPlayer() && !CTheScripts::IsPlayerOnAMission()) { + +#ifndef VC_PED_PORTS + if (CGeneral::GetRandomNumber() & 1) + ped->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, driver); + else +#endif + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, ped->m_pMyVehicle); + + } else { +#ifdef VC_PED_PORTS + if (ped->m_pedStats->m_temper > ped->m_pedStats->m_fear && ped->CharCreatedBy != MISSION_CHAR + && ped->m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE && !driver + && FindPlayerPed()->m_carInObjective == ped->m_pMyVehicle && !CTheScripts::IsPlayerOnAMission()) + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, ped->m_pMyVehicle); + else +#endif + { + ped->SetPedState(PED_NONE); + ped->m_nLastPedState = PED_NONE; + ped->SetFindPathAndFlee(ped->m_pMyVehicle->GetPosition(), 10000); + } + } + ped->SetGetUp(); +} + +uint8 +CPed::GetNearestTrainDoor(CVehicle *train, CVector &doorPos) +{ + GetNearestTrainPedPosition(train, doorPos); +/* + // Not used. + CVehicleModelInfo* trainModel = (CVehicleModelInfo*)CModelInfo::GetModelInfo(train->m_modelIndex); + CMatrix trainMat = CMatrix(train->GetMatrix()); + + doorPos = trainModel->m_positions[m_vehDoor]; + doorPos.x -= 1.5f; + doorPos = Multiply3x3(trainMat, doorPos); + doorPos += train->GetPosition(); +*/ + return 1; +} + +uint8 +CPed::GetNearestTrainPedPosition(CVehicle *train, CVector &enterPos) +{ + CVector enterStepOffset; + CVehicleModelInfo *trainModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(train->GetModelIndex()); + CMatrix trainMat = CMatrix(train->GetMatrix()); + CVector leftEntryPos, rightEntryPos, midEntryPos; + float distLeftEntry, distRightEntry, distMidEntry; + + // enterStepOffset = vecPedCarDoorAnimOffset; + enterStepOffset = CVector(1.5f, 0.0f, 0.0f); + + if (train->pPassengers[TRAIN_POS_LEFT_ENTRY]) { + distLeftEntry = 999.0f; + } else { + leftEntryPos = trainModel->m_positions[TRAIN_POS_LEFT_ENTRY] - enterStepOffset; + leftEntryPos = Multiply3x3(trainMat, leftEntryPos); + leftEntryPos += train->GetPosition(); + distLeftEntry = (leftEntryPos - GetPosition()).Magnitude(); + } + + if (train->pPassengers[TRAIN_POS_MID_ENTRY]) { + distMidEntry = 999.0f; + } else { + midEntryPos = trainModel->m_positions[TRAIN_POS_MID_ENTRY] - enterStepOffset; + midEntryPos = Multiply3x3(trainMat, midEntryPos); + midEntryPos += train->GetPosition(); + distMidEntry = (midEntryPos - GetPosition()).Magnitude(); + } + + if (train->pPassengers[TRAIN_POS_RIGHT_ENTRY]) { + distRightEntry = 999.0f; + } else { + rightEntryPos = trainModel->m_positions[TRAIN_POS_RIGHT_ENTRY] - enterStepOffset; + rightEntryPos = Multiply3x3(trainMat, rightEntryPos); + rightEntryPos += train->GetPosition(); + distRightEntry = (rightEntryPos - GetPosition()).Magnitude(); + } + + if (distMidEntry < distLeftEntry) { + if (distMidEntry < distRightEntry) { + enterPos = midEntryPos; + m_vehDoor = TRAIN_POS_MID_ENTRY; + } else { + enterPos = rightEntryPos; + m_vehDoor = TRAIN_POS_RIGHT_ENTRY; + } + } else if (distRightEntry < distLeftEntry) { + enterPos = rightEntryPos; + m_vehDoor = TRAIN_POS_RIGHT_ENTRY; + } else { + enterPos = leftEntryPos; + m_vehDoor = TRAIN_POS_LEFT_ENTRY; + } + + return 1; +} + +void +CPed::PedSetInTrainCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed *ped = (CPed*)arg; + CTrain *veh = (CTrain*)ped->m_pMyVehicle; + + if (!veh) + return; + + ped->bInVehicle = true; + ped->SetPedState(PED_DRIVING); + ped->RestorePreviousObjective(); + ped->SetMoveState(PEDMOVE_STILL); + veh->AddPassenger(ped); +} + +void +CPed::SetEnterTrain(CVehicle *train, uint32 unused) +{ + if (m_nPedState == PED_ENTER_TRAIN || !((CTrain*)train)->Doors[0].IsFullyOpen()) + return; + + /* + // Not used + CVector enterPos; + GetNearestTrainPedPosition(train, enterPos); + */ + m_fRotationCur = train->GetForward().Heading() - HALFPI; + m_pMyVehicle = train; + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + + SetPedState(PED_ENTER_TRAIN); + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_TRAIN_GETIN, 4.0f); + m_pVehicleAnim->SetFinishCallback(PedSetInTrainCB, this); + bUsesCollision = false; + LineUpPedWithTrain(); + if (IsPlayer()) { + if (((CPlayerPed*)this)->m_bAdrenalineActive) + ((CPlayerPed*)this)->ClearAdrenaline(); + } +} + +void +CPed::EnterTrain(void) +{ + LineUpPedWithTrain(); +} + +void +CPed::SetPedPositionInTrain(void) +{ + LineUpPedWithTrain(); +} + +void +CPed::LineUpPedWithTrain(void) +{ + CVector lineUpPos; + CVehicleModelInfo *trainModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(m_pMyVehicle->GetModelIndex()); + CVector enterOffset(1.5f, 0.0f, -0.2f); + + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_fRotationCur = m_pMyVehicle->GetForward().Heading() - HALFPI; + m_fRotationDest = m_fRotationCur; + + if (!bInVehicle) { + GetNearestTrainDoor(m_pMyVehicle, lineUpPos); + lineUpPos.z += 0.2f; + } else { + if (m_pMyVehicle->pPassengers[TRAIN_POS_LEFT_ENTRY] == this) { + + lineUpPos = trainModel->m_positions[TRAIN_POS_LEFT_ENTRY] - enterOffset; + + } else if (m_pMyVehicle->pPassengers[TRAIN_POS_MID_ENTRY] == this) { + + lineUpPos = trainModel->m_positions[TRAIN_POS_MID_ENTRY] - enterOffset; + + } else if (m_pMyVehicle->pPassengers[TRAIN_POS_RIGHT_ENTRY] == this) { + + lineUpPos = trainModel->m_positions[TRAIN_POS_RIGHT_ENTRY] - enterOffset; + } + lineUpPos = Multiply3x3(m_pMyVehicle->GetMatrix(), lineUpPos); + lineUpPos += m_pMyVehicle->GetPosition(); + } + + if (m_pVehicleAnim) { + float percentageLeft = m_pVehicleAnim->GetTimeLeft() / m_pVehicleAnim->hierarchy->totalLength; + lineUpPos += (GetPosition() - lineUpPos) * percentageLeft; + } + + SetPosition(lineUpPos); + SetHeading(m_fRotationCur); +} + +void +CPed::SetExitTrain(CVehicle* train) +{ + if (m_nPedState == PED_EXIT_TRAIN || train->GetStatus() != STATUS_TRAIN_NOT_MOVING || !((CTrain*)train)->Doors[0].IsFullyOpen()) + return; + + /* + // Not used + CVector exitPos; + GetNearestTrainPedPosition(train, exitPos); + */ + SetPedState(PED_EXIT_TRAIN); + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_TRAIN_GETOUT, 4.0f); + m_pVehicleAnim->SetFinishCallback(PedSetOutTrainCB, this); + bUsesCollision = false; + LineUpPedWithTrain(); +} + +void +CPed::ExitTrain(void) +{ + LineUpPedWithTrain(); +} + +void +CPed::PedSetOutTrainCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CVehicle *veh = ped->m_pMyVehicle; + + if (ped->m_pVehicleAnim) + ped->m_pVehicleAnim->blendDelta = -1000.0f; + + ped->bUsesCollision = true; + ped->m_pVehicleAnim = nil; + ped->bInVehicle = false; + ped->SetPedState(PED_IDLE); + ped->RestorePreviousObjective(); + ped->SetMoveState(PEDMOVE_STILL); + + CMatrix pedMat(ped->GetMatrix()); + ped->m_fRotationCur = HALFPI + veh->GetForward().Heading(); + ped->m_fRotationDest = ped->m_fRotationCur; + CVector posAfterExit = Multiply3x3(pedMat, vecPedTrainDoorAnimOffset); + posAfterExit += ped->GetPosition(); + CPedPlacement::FindZCoorForPed(&posAfterExit); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + ped->SetPosition(posAfterExit); + ped->SetHeading(ped->m_fRotationCur); + veh->RemovePassenger(ped); +} + +void +CPed::RegisterThreatWithGangPeds(CEntity *attacker) +{ + CPed *attackerPed = nil; + if (attacker) { + if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT && m_objective != OBJECTIVE_KILL_CHAR_ANY_MEANS) { + if (attacker->IsPed()) { + attackerPed = (CPed*)attacker; + } else { + if (!attacker->IsVehicle()) + return; + + attackerPed = ((CVehicle*)attacker)->pDriver; + if (!attackerPed) + return; + } + + if (attackerPed && (attackerPed->IsPlayer() || attackerPed->IsGangMember())) { + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->IsPointerValid()) { + if (nearPed != this && nearPed->m_nPedType == m_nPedType) + nearPed->m_fearFlags |= CPedType::GetFlag(attackerPed->m_nPedType); + } + } + } + } + } + + if (attackerPed && attackerPed->IsPlayer() && (attackerPed->m_nPedState == PED_CARJACK || attackerPed->bInVehicle)) { + if (!attackerPed->m_pMyVehicle || attackerPed->m_pMyVehicle->GetModelIndex() != MI_TOYZ) { + int16 lastVehicle; + CEntity *vehicles[8]; + CWorld::FindObjectsInRange(GetPosition(), ENTER_CAR_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + if (lastVehicle > 8) + lastVehicle = 8; + + for (int j = 0; j < lastVehicle; ++j) { + CVehicle *nearVeh = (CVehicle*) vehicles[j]; + + if (nearVeh->VehicleCreatedBy != MISSION_VEHICLE) { + CPed *nearVehDriver = nearVeh->pDriver; + + if (nearVehDriver && nearVehDriver != this && nearVehDriver->m_nPedType == m_nPedType) { + + if (nearVeh->IsVehicleNormal() && nearVeh->IsCar()) { + nearVeh->AutoPilot.m_nCruiseSpeed = GAME_SPEED_TO_CARAI_SPEED * nearVeh->pHandling->Transmission.fMaxCruiseVelocity * 0.8f; + nearVeh->AutoPilot.m_nCarMission = MISSION_RAMPLAYER_FARAWAY; + nearVeh->SetStatus(STATUS_PHYSICS); + nearVeh->AutoPilot.m_nTempAction = TEMPACT_NONE; + nearVeh->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + } + } + } + } + } + } +} + +// Some helper function which doesn't exist in og game. +inline void +SelectClosestNodeForSeek(CPed *ped, CPathNode *node, CVector2D closeDist, CVector2D farDist, CPathNode *closeNode, CPathNode *closeNode2, int runCount = 3) +{ + for (int i = 0; i < node->numLinks; i++) { + + CPathNode *testNode = &ThePaths.m_pathNodes[ThePaths.ConnectedNode(i + node->firstLink)]; + + if (testNode && testNode != closeNode && testNode != closeNode2) { + CVector2D posDiff(ped->m_vecSeekPos - testNode->GetPosition()); + float dist = posDiff.MagnitudeSqr(); + + if (farDist.MagnitudeSqr() > dist) { + + if (closeDist.MagnitudeSqr() <= dist) { + ped->m_pNextPathNode = closeNode; + closeDist = posDiff; + } else { + ped->m_pNextPathNode = (closeNode2 ? closeNode2 : testNode); + farDist = posDiff; + } + } + + if (--runCount > 0) + SelectClosestNodeForSeek(ped, testNode, closeDist, farDist, closeNode, (closeNode2 ? closeNode2 : testNode), runCount); + } + } +} + +bool +CPed::FindBestCoordsFromNodes(CVector unused, CVector *bestCoords) +{ + if (m_pNextPathNode || !bUsePedNodeSeek) + return false; + + CVector ourPos = GetPosition(); + + int closestNodeId = ThePaths.FindNodeClosestToCoors(GetPosition(), 1, 999999.9f); + + CVector seekObjPos = m_vecSeekPos; + seekObjPos.z += 1.0f; + + if (CWorld::GetIsLineOfSightClear(ourPos, seekObjPos, true, false, false, true, false, false, false)) + return false; + + m_pNextPathNode = nil; + + CVector2D seekPosDist (m_vecSeekPos - ourPos); + + CPathNode *closestNode = &ThePaths.m_pathNodes[closestNodeId]; + CVector2D closeDist(m_vecSeekPos - closestNode->GetPosition()); + + SelectClosestNodeForSeek(this, closestNode, closeDist, seekPosDist, closestNode, nil); + + // Above function decided that going to the next node is more logical than seeking the object. + if (m_pNextPathNode) { + + CVector pathToNextNode = m_pNextPathNode->GetPosition() - ourPos; + if (pathToNextNode.MagnitudeSqr2D() < seekPosDist.MagnitudeSqr()) { + *bestCoords = m_pNextPathNode->GetPosition(); + return true; + } + m_pNextPathNode = nil; + } + + return false; +} + +bool +CPed::DuckAndCover(void) +{ + if (!m_pedInObjective || CTimer::GetTimeInMilliseconds() <= m_duckAndCoverTimer) + return false; + + if (bKindaStayInSamePlace){ + + if (CTimer::GetTimeInMilliseconds() <= m_leaveCarTimer) { + if (!m_pLookTarget || m_pLookTarget != m_pedInObjective) { + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + } + if (!bIsAimingGun) + SetAimFlag(m_pedInObjective); + + } else { + bCrouchWhenShooting = false; + bKindaStayInSamePlace = false; + bIsDucking = false; + bDuckAndCover = false; + m_headingRate = 10.0f; + m_duckAndCoverTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(20000,30000); + if (m_pSeekTarget && m_pSeekTarget->IsVehicle()) + ((CVehicle*)m_pSeekTarget)->m_numPedsUseItAsCover--; + } + return false; + } + + bool justDucked = false; + CVehicle *foundVeh = nil; + float maxDist = 225.0f; + bIsDucking = false; + bCrouchWhenShooting = false; + if (CTimer::GetTimeInMilliseconds() > m_leaveCarTimer) { + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity *vehicles[8]; + CWorld::FindObjectsInRange(pos, CHECK_NEARBY_THINGS_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int i = 0; i < lastVehicle; i++) { + CVehicle *veh = (CVehicle*) vehicles[i]; + if (veh->m_vecMoveSpeed.Magnitude() <= 0.02f + && !veh->bIsBus + && !veh->bIsVan + && !veh->bIsBig + && veh->m_numPedsUseItAsCover < 3) { + float dist = (GetPosition() - veh->GetPosition()).MagnitudeSqr(); + if (dist < maxDist) { + maxDist = dist; + foundVeh = veh; + } + } + } + if (foundVeh) { + // Unused. + // CVector lfWheelPos, rfWheelPos; + // foundVeh->GetComponentWorldPosition(CAR_WHEEL_RF, rfWheelPos); + // foundVeh->GetComponentWorldPosition(CAR_WHEEL_LF, lfWheelPos); + CVector rightSide, leftSide; + + // 3 persons can use the car as cover. Found the correct position for us. + if (foundVeh->m_numPedsUseItAsCover == 2) { + rightSide = CVector(1.5f, -0.5f, 0.0f); + leftSide = CVector(-1.5f, -0.5f, 0.0f); + } else if (foundVeh->m_numPedsUseItAsCover == 1) { + rightSide = CVector(1.5f, 0.5f, 0.0f); + leftSide = CVector(-1.5f, 0.5f, 0.0f); + } else if (foundVeh->m_numPedsUseItAsCover == 0) { + rightSide = CVector(1.5f, 0.0f, 0.0f); + leftSide = CVector(-1.5f, 0.0f, 0.0f); + } + + CMatrix vehMatrix(foundVeh->GetMatrix()); + CVector duckAtRightSide = Multiply3x3(vehMatrix, rightSide) + foundVeh->GetPosition(); + + CVector duckAtLeftSide = Multiply3x3(vehMatrix, leftSide) + foundVeh->GetPosition(); + + CVector distWithPedRightSide = m_pedInObjective->GetPosition() - duckAtRightSide; + CVector distWithPedLeftSide = m_pedInObjective->GetPosition() - duckAtLeftSide; + + CVector duckPos; + if (distWithPedRightSide.MagnitudeSqr() <= distWithPedLeftSide.MagnitudeSqr()) + duckPos = duckAtLeftSide; + else + duckPos = duckAtRightSide; + + if (CWorld::TestSphereAgainstWorld(duckPos, 0.5f, nil, true, true, true, false, false, false) + && CWorld::GetIsLineOfSightClear(GetPosition(), duckPos, 1, 0, 0, 1, 0, 0, 0)) { + SetSeek(duckPos, 1.0f); + m_headingRate = 15.0f; + bIsRunning = true; + bDuckAndCover = true; + justDucked = true; + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 500; + if (foundVeh->bIsLawEnforcer) + m_carInObjective = foundVeh; + + // BUG? Shouldn't we register the reference? + m_pSeekTarget = foundVeh; + ClearPointGunAt(); + } else { + m_duckAndCoverTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(10000, 15000); + bDuckAndCover = false; + } + } else { + bDuckAndCover = false; + } + } + + if (!justDucked && !bDuckAndCover) + return false; + + if (!Seek()) + return true; + + bKindaStayInSamePlace = true; + bDuckAndCover = false; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + if (m_pSeekTarget && m_pSeekTarget->IsVehicle()) + ((CVehicle*)m_pSeekTarget)->m_numPedsUseItAsCover++; + + SetIdle(); + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + if (!m_pLookTarget || m_pLookTarget != m_pedInObjective) { + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + } + + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(3000, 6000); + return false; +} + +CVector +CPed::GetPositionToOpenCarDoor(CVehicle *veh, uint32 component, float offset) +{ + CVector doorPos; + CMatrix vehMat(veh->GetMatrix()); + + doorPos = Multiply3x3(vehMat, GetLocalPositionToOpenCarDoor(veh, component, offset)); + + return veh->GetPosition() + doorPos; +} + +CVector +CPed::GetLocalPositionToOpenCarDoor(CVehicle *veh, uint32 component, float seatPosMult) +{ + CVehicleModelInfo *vehModel; + CVector vehDoorPos; + CVector vehDoorOffset; + float seatOffset; + + vehModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(veh->GetModelIndex()); + if (veh->bIsVan && (component == CAR_DOOR_LR || component == CAR_DOOR_RR)) { + seatOffset = 0.0f; + vehDoorOffset = vecPedVanRearDoorAnimOffset; + } else { + seatOffset = veh->pHandling->fSeatOffsetDistance * seatPosMult; + if (veh->bLowVehicle) { + vehDoorOffset = vecPedCarDoorLoAnimOffset; + } else { + vehDoorOffset = vecPedCarDoorAnimOffset; + } + } + + switch (component) { + case CAR_DOOR_RF: + vehDoorPos = vehModel->GetFrontSeatPosn(); + vehDoorPos.x += seatOffset; + vehDoorOffset.x = -vehDoorOffset.x; + break; + + case CAR_DOOR_RR: + vehDoorPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + vehDoorPos.x += seatOffset; + vehDoorOffset.x = -vehDoorOffset.x; + break; + + case CAR_DOOR_LF: + vehDoorPos = vehModel->GetFrontSeatPosn(); + vehDoorPos.x = -(vehDoorPos.x + seatOffset); + break; + + case CAR_DOOR_LR: + vehDoorPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + vehDoorPos.x = -(vehDoorPos.x + seatOffset); + break; + + default: + vehDoorPos = vehModel->GetFrontSeatPosn(); + vehDoorOffset = CVector(0.0f, 0.0f, 0.0f); + } + return vehDoorPos - vehDoorOffset; +} + +void +CPed::SetDuck(uint32 time) +{ + if (bIsDucking || CTimer::GetTimeInMilliseconds() <= m_duckTimer) + return; + + if (bCrouchWhenShooting && (m_nPedState == PED_ATTACK || m_nPedState == PED_AIM_GUN)) { + CAnimBlendAssociation *duckAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_LOW); + if (!duckAssoc || duckAssoc->blendDelta < 0.0f) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_DUCK_LOW, 4.0f); + bIsDucking = true; + m_duckTimer = CTimer::GetTimeInMilliseconds() + time; + } + } else { + CAnimBlendAssociation *duckAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (!duckAssoc || duckAssoc->blendDelta < 0.0f) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_DUCK_DOWN, 4.0f); + bIsDucking = true; + m_duckTimer = CTimer::GetTimeInMilliseconds() + time; + } + } +} + +void +CPed::Duck(void) +{ + if (CTimer::GetTimeInMilliseconds() > m_duckTimer) + ClearDuck(); +} + +void +CPed::ClearDuck(void) +{ + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (!animAssoc) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_LOW); + + if (!animAssoc) { + bIsDucking = false; + return; + } + } + + if (!bCrouchWhenShooting) + return; + + if (m_nPedState != PED_ATTACK && m_nPedState != PED_AIM_GUN) + return; + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RBLOCK_SHOOT); + if (!animAssoc || animAssoc->blendDelta < 0.0f) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_RBLOCK_SHOOT, 4.0f); + } +} + +void +CPed::InformMyGangOfAttack(CEntity *attacker) +{ + CPed *attackerPed; + + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) + return; + + if (attacker->IsPed()) { + attackerPed = (CPed*)attacker; + } else { + if (!attacker->IsVehicle()) + return; + + attackerPed = ((CVehicle*)attacker)->pDriver; + if (!attackerPed) + return; + } + + if (attackerPed->m_nPedType == PEDTYPE_COP) + return; + + for (int i = 0; i < m_numNearPeds; i++) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed && nearPed != this) { + CPed *leader = nearPed->m_leader; + if (leader && leader == this && nearPed->m_pedStats->m_fear < nearPed->m_pedStats->m_temper) + { + nearPed->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, attackerPed); + nearPed->SetObjectiveTimer(30000); + } + } + } +} + +void +CPed::PedAnimDoorCloseRollingCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + CAutomobile* veh = (CAutomobile*)(ped->m_pMyVehicle); + + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (veh->bLowVehicle) { + veh->ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS, 1.0f); + } else { + veh->ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, 1.0f); + } + + veh->m_nGettingOutFlags &= ~CAR_DOOR_FLAG_LF; + + if (veh->Damage.GetDoorStatus(DOOR_FRONT_LEFT) == DOOR_STATUS_SWINGING) + veh->Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_OK); +} + +void +CPed::SetSeekBoatPosition(CVehicle *boat) +{ + if (m_nPedState == PED_SEEK_IN_BOAT || boat->pDriver +#if defined VC_PED_PORTS || defined FIX_BUGS + || !IsPedInControl() +#endif + ) + return; + + SetStoredState(); + m_carInObjective = boat; + m_carInObjective->RegisterReference((CEntity **) &m_carInObjective); + m_pMyVehicle = boat; + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + m_distanceToCountSeekDone = 0.5f; + SetPedState(PED_SEEK_IN_BOAT); +} + +void +CPed::SeekBoatPosition(void) +{ + if (m_carInObjective && !m_carInObjective->pDriver) { + CVehicleModelInfo *boatModel = m_carInObjective->GetModelInfo(); + + CVector enterOffset; + enterOffset = boatModel->GetFrontSeatPosn(); + enterOffset.x = 0.0f; + CMatrix boatMat(m_carInObjective->GetMatrix()); + CVector boatEnterPos = Multiply3x3(boatMat, enterOffset); + boatEnterPos += m_carInObjective->GetPosition(); + SetMoveState(PEDMOVE_WALK); + m_vecSeekPos = boatEnterPos; + if (Seek()) { + // We arrived to the boat + m_vehDoor = 0; + SetEnterCar(m_carInObjective, 0); + } + } else + RestorePreviousState(); +} + +bool +CPed::IsRoomToBeCarJacked(void) +{ + if (!m_pMyVehicle) + return false; + + CVector offset; + if (m_pMyVehicle->bLowVehicle || m_nPedType == PEDTYPE_COP) { + offset = vecPedDraggedOutCarAnimOffset; + } else { + offset = vecPedQuickDraggedOutCarAnimOffset; + } + + offset.z = 0.0f; + if (m_pMyVehicle->IsRoomForPedToLeaveCar(CAR_DOOR_LF, &offset)) { + return true; + } + + return false; +} + +void +CPed::RemoveInCarAnims(void) +{ + if (!IsPlayer()) + return; + + CAnimBlendAssociation *animAssoc; + + if (m_pMyVehicle && m_pMyVehicle->bLowVehicle) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_LEFT_LO); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_RIGHT_LO); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + } else { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_LEFT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_RIGHT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + } + +#ifdef VC_PED_PORTS + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_BOAT_DRIVE); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; +#endif + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_LOOKBEHIND); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; +} + +bool +CPed::PositionPedOutOfCollision(void) +{ + CVehicle *veh; + CVector posNearVeh; + CVector posSomewhereClose; + bool putNearVeh = false; + bool putSomewhereClose = false; + int smallestDistNearVeh = 999; + int smallestDistSomewhereClose = 999; + + if (!m_pMyVehicle) + return false; + + CVector vehPos = m_pMyVehicle->GetPosition(); + CVector potentialPos; + potentialPos.y = GetPosition().y - 3.5f; + potentialPos.z = GetPosition().z; + + for (int yTry = 0; yTry < 15; yTry++) { + potentialPos.x = GetPosition().x - 3.5f; + + for (int xTry = 0; xTry < 15; xTry++) { + CPedPlacement::FindZCoorForPed(&potentialPos); + CVector distVec = potentialPos - vehPos; + float dist = distVec.Magnitude(); + + // Makes close distances bigger for some reason. + float mult = (0.6f + dist) / dist; + CVector adjustedPotentialPos = distVec * mult + vehPos; + if (CWorld::GetIsLineOfSightClear(vehPos, adjustedPotentialPos, true, false, false, true, false, false, false) + && !CWorld::TestSphereAgainstWorld(potentialPos, 0.6f, this, true, false, false, true, false, false)) { + + float potentialChangeSqr = (potentialPos - GetPosition()).MagnitudeSqr(); + veh = (CVehicle*)CWorld::TestSphereAgainstWorld(potentialPos, 0.6f, this, false, true, false, false, false, false); + if (veh) { + if (potentialChangeSqr < smallestDistNearVeh) { + posNearVeh = potentialPos; + putNearVeh = true; + smallestDistNearVeh = potentialChangeSqr; + } + } else if (potentialChangeSqr < smallestDistSomewhereClose) { + smallestDistSomewhereClose = potentialChangeSqr; + posSomewhereClose = potentialPos; + putSomewhereClose = true; + } + } + potentialPos.x += 0.5f; + } + potentialPos.y += 0.5f; + } + + if (!putSomewhereClose && !putNearVeh) + return false; + + // We refrain from using posNearVeh, probably because of it may be top of the vehicle. + if (putSomewhereClose) { + SetPosition(posSomewhereClose); + } else { + CVector vehSize = veh->GetModelInfo()->GetColModel()->boundingBox.max; + posNearVeh.z += vehSize.z; + SetPosition(posNearVeh); + } + return true; +} + +bool +CPed::WarpPedToNearLeaderOffScreen(void) +{ + bool teleported = false; + if (GetIsOnScreen() || m_leaveCarTimer > CTimer::GetTimeInMilliseconds()) + return false; + + CVector warpToPos = m_leader->GetPosition(); + CVector distVec = warpToPos - GetPosition(); + float halfOfDist = distVec.Magnitude() * 0.5f; + CVector halfNormalizedDist = distVec / halfOfDist; + + CVector appropriatePos = GetPosition(); + CVector zCorrectedPos = appropriatePos; + int tryCount = Min(10, halfOfDist); + for (int i = 0; i < tryCount; ++i) { + appropriatePos += halfNormalizedDist; + CPedPlacement::FindZCoorForPed(&zCorrectedPos); + + if (Abs(zCorrectedPos.z - warpToPos.z) >= 3.0f && Abs(zCorrectedPos.z - appropriatePos.z) >= 3.0f) + continue; + + appropriatePos.z = zCorrectedPos.z; + if (!TheCamera.IsSphereVisible(appropriatePos, 0.6f) + && CWorld::GetIsLineOfSightClear(appropriatePos, warpToPos, true, true, false, true, false, false, false) + && !CWorld::TestSphereAgainstWorld(appropriatePos, 0.6f, this, true, true, false, true, false, false)) { + teleported = true; + Teleport(appropriatePos); + } + } + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 3000; + return teleported; +} + +bool +CPed::WarpPedToNearEntityOffScreen(CEntity *warpTo) +{ + bool teleported = false; + if (GetIsOnScreen() || m_leaveCarTimer > CTimer::GetTimeInMilliseconds()) + return false; + + CVector warpToPos = warpTo->GetPosition(); + CVector distVec = warpToPos - GetPosition(); + float halfOfDist = distVec.Magnitude() * 0.5f; + CVector halfNormalizedDist = distVec / halfOfDist; + + CVector appropriatePos = GetPosition(); + CVector zCorrectedPos = appropriatePos; + int tryCount = Min(10, halfOfDist); + for (int i = 0; i < tryCount; ++i) { + appropriatePos += halfNormalizedDist; + CPedPlacement::FindZCoorForPed(&zCorrectedPos); + + if (Abs(zCorrectedPos.z - warpToPos.z) >= 3.0f && Abs(zCorrectedPos.z - appropriatePos.z) >= 3.0f) + continue; + + appropriatePos.z = zCorrectedPos.z; + if (!TheCamera.IsSphereVisible(appropriatePos, 0.6f) + && CWorld::GetIsLineOfSightClear(appropriatePos, warpToPos, true, true, false, true, false, false, false) + && !CWorld::TestSphereAgainstWorld(appropriatePos, 0.6f, this, true, true, false, true, false, false)) { + teleported = true; + Teleport(appropriatePos); + } + } + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 3000; + return teleported; +} \ No newline at end of file diff --git a/src/peds/PedChat.cpp b/src/peds/PedChat.cpp new file mode 100644 index 0000000..907f575 --- /dev/null +++ b/src/peds/PedChat.cpp @@ -0,0 +1,152 @@ +#include "common.h" +#include "Camera.h" +#include "DMAudio.h" +#include "General.h" +#include "Ped.h" + +// Corresponds to ped sounds (from SOUND_PED_DEATH to SOUND_PED_TAXI_CALL) +PedAudioData CommentWaitTime[39] = { + {500, 800, 500, 2}, + {500, 800, 500, 2}, + {500, 800, 500, 2}, + {500, 800, 500, 2}, + {100, 2, 100, 2}, + {700, 500, 1000, 500}, + {700, 500, 1000, 500}, + {5000, 2000, 15000, 3000}, + {5000, 2000, 15000, 3000}, + {5000, 2000, 15000, 3000}, + {6000, 6000, 6000, 6000}, + {1000, 1000, 2000, 2000}, + {1000, 500, 2000, 1500}, + {1000, 500, 2000, 1500}, + {800, 200, 1000, 500}, + {800, 200, 1000, 500}, + {800, 400, 2000, 1000}, + {800, 400, 2000, 1000}, + {400, 300, 2000, 1000}, + {2000, 1000, 2500, 1500}, + {200, 200, 200, 200}, + {6000, 3000, 5000, 6000}, + {6000, 3000, 9000, 5000}, + {6000, 3000, 9000, 5000}, + {6000, 3000, 9000, 5000}, + {400, 300, 4000, 1000}, + {400, 300, 4000, 1000}, + {400, 300, 4000, 1000}, + {1000, 500, 3000, 1000}, + {1000, 500, 1000, 1000}, + {3000, 2000, 3000, 2000}, + {1000, 500, 3000, 6000}, + {1000, 500, 2000, 4000}, + {1000, 500, 2000, 5000}, + {1000, 500, 3000, 2000}, + {1600, 1000, 2000, 2000}, + {3000, 2000, 5000, 3000}, + {1000, 1000, 1000, 1000}, + {1000, 1000, 5000, 5000}, +}; + +bool +CPed::ServiceTalkingWhenDead(void) +{ + return m_queuedSound == SOUND_PED_DEATH; +} + +void +CPed::ServiceTalking(void) +{ + if (bBodyPartJustCameOff && m_bodyPartBleeding == PED_HEAD) + return; + + if (!CGeneral::faststricmp(CModelInfo::GetModelInfo(GetModelIndex())->GetModelName(), "bomber")) + m_queuedSound = SOUND_PED_BOMBER; + else if (m_nPedState == PED_ON_FIRE) + m_queuedSound = SOUND_PED_BURNING; + + if (m_queuedSound != SOUND_NO_SOUND) { + if (m_queuedSound == SOUND_PED_DEATH) + m_soundStart = CTimer::GetTimeInMilliseconds() - 1; + + if (CTimer::GetTimeInMilliseconds() > m_soundStart) { + DMAudio.PlayOneShot(m_audioEntityId, m_queuedSound, 1.0f); + m_lastSoundStart = CTimer::GetTimeInMilliseconds(); + m_soundStart = + CommentWaitTime[m_queuedSound - SOUND_PED_DEATH].m_nFixedDelayTime + + CTimer::GetTimeInMilliseconds() + + CGeneral::GetRandomNumberInRange(0, CommentWaitTime[m_queuedSound - SOUND_PED_DEATH].m_nOverrideFixedDelayTime); + m_lastQueuedSound = m_queuedSound; + m_queuedSound = SOUND_NO_SOUND; + } + } +} + +void +CPed::Say(uint16 audio) +{ + if (IsPlayer()) { + + // Ofc this part isn't in VC. + switch (audio) { + case SOUND_PED_DEATH: + audio = SOUND_PED_DAMAGE; + break; + case SOUND_PED_DAMAGE: + case SOUND_PED_HIT: + case SOUND_PED_LAND: + break; + case SOUND_PED_BULLET_HIT: + case SOUND_PED_CAR_JACKED: + case SOUND_PED_DEFEND: + audio = SOUND_PED_HIT; + break; + default: + return; + } + } else { + if (TheCamera.GetPosition().z + 3.0f < GetPosition().z) + return; + + if (TheCamera.m_CameraAverageSpeed > 1.65f) { +#ifdef VC_PED_PORTS + if (audio != SOUND_PED_DAMAGE && audio != SOUND_PED_HIT && audio != SOUND_PED_LAND) +#endif + return; + + } else if (TheCamera.m_CameraAverageSpeed > 1.25f) { + if (audio != SOUND_PED_DEATH && +#ifdef VC_PED_PORTS + audio != SOUND_PED_DAMAGE && audio != SOUND_PED_HIT && audio != SOUND_PED_LAND && +#endif + audio != SOUND_PED_TAXI_WAIT && audio != SOUND_PED_EVADE) + return; + + } else if (TheCamera.m_CameraAverageSpeed > 0.9f) { + switch (audio) { + case SOUND_PED_DEATH: +#ifdef VC_PED_PORTS + case SOUND_PED_DAMAGE: + case SOUND_PED_HIT: + case SOUND_PED_LAND: +#endif + case SOUND_PED_BURNING: + case SOUND_PED_FLEE_SPRINT: + case SOUND_PED_TAXI_WAIT: + case SOUND_PED_EVADE: + case SOUND_PED_ANNOYED_DRIVER: + break; + default: + return; + } + } + } + + if (audio < m_queuedSound) { + if (audio != m_lastQueuedSound || audio == SOUND_PED_DEATH + || CommentWaitTime[audio - SOUND_PED_DEATH].m_nOverrideMaxRandomDelayTime + + m_lastSoundStart + + (uint32) CGeneral::GetRandomNumberInRange(0, CommentWaitTime[audio - SOUND_PED_DEATH].m_nMaxRandomDelayTime) <= CTimer::GetTimeInMilliseconds()) { + m_queuedSound = audio; + } + } +} \ No newline at end of file diff --git a/src/peds/PedDebug.cpp b/src/peds/PedDebug.cpp new file mode 100644 index 0000000..1c22963 --- /dev/null +++ b/src/peds/PedDebug.cpp @@ -0,0 +1,310 @@ +#include "common.h" +#ifndef MASTER +#include "main.h" +#include "Camera.h" +#include "Font.h" +#include "Ped.h" +#include "Sprite.h" +#include "Text.h" + + +static char ObjectiveText[][28] = { + "No Obj", + "Wait on Foot", + "Flee on Foot Till Safe", + "Guard Spot", + "Guard Area", + "Wait in Car", + "Wait in Car then Getout", + "Kill Char on Foot", + "Kill Char Any Means", + "Flee Char on Foot Till Safe", + "Flee Char on Foot Always", + "GoTo Char on Foot", + "Follow Char in Formation", + "Leave Car", + "Enter Car as Passenger", + "Enter Car as Driver", + "Follow Car in Car", + "Fire at Obj from Vehicle", + "Destroy Obj", + "Destroy Car", + "GoTo Area Any Means", + "GoTo Area on Foot", + "Run to Area", + "GoTo Area in Car", + "Follow Car on Foot Woffset", + "Guard Attack", + "Set Leader", + "Follow Route", + "Solicit", + "Take Taxi", + "Catch Train", + "Buy IceCream", + "Steal Any Car", + "Mug Char", +#ifdef VC_PED_PORTS + "Leave Car and Die" +#endif +}; + +static char StateText[][18] = { + "None", + "Idle", + "Look Entity", + "Look Heading", + "Wander Range", + "Wander Path", + "Seek Pos", + "Seek Entity", + "Flee Pos", + "Flee Entity", + "Pursue", + "Follow Path", + "Sniper Mode", + "Rocket Mode", + "Dummy", + "Pause", + "Attack", + "Fight", + "Face Phone", + "Make Call", + "Chat", + "Mug", + "AimGun", + "AI Control", + "Seek Car", + "Seek InBoat", + "Follow Route", + "C.P.R.", + "Solicit", + "Buy IceCream", + "Investigate", + "Step away", + "On Fire", + "Unknown", + "STATES_NO_AI", + "Jump", + "Fall", + "GetUp", + "Stagger", + "Dive away", + "STATES_NO_ST", + "Enter Train", + "Exit Train", + "Arrest Plyr", + "Driving", + "Passenger", + "Taxi Passngr", + "Open Door", + "Die", + "Dead", + "CarJack", + "Drag fm Car", + "Enter Car", + "Steal Car", + "Exit Car", + "Hands Up", + "Arrested", +}; + +static char PersonalityTypeText[][18] = { + "Player", + "Cop", + "Medic", + "Fireman", + "Gang 1", + "Gang 2", + "Gang 3", + "Gang 4", + "Gang 5", + "Gang 6", + "Gang 7", + "Street Guy", + "Suit Guy", + "Sensible Guy", + "Geek Guy", + "Old Guy", + "Tough Guy", + "Street Girl", + "Suit Girl", + "Sensible Girl", + "Geek Girl", + "Old Girl", + "Tough Girl", + "Tramp Male", + "Tramp Female", + "Tourist", + "Prostitute", + "Criminal", + "Busker", + "Taxi Driver", + "Psycho", + "Steward", + "Sports Fan", + "Shopper", + "Old Shopper" +}; + +static char WaitStateText[][16] = { + "No Wait", + "Traffic Lights", + "Pause CrossRoad", + "Look CrossRoad", + "Look Ped", + "Look Shop", + "Look Accident", + "FaceOff Gang", + "Double Back", + "Hit Wall", + "Turn 180deg", + "Surprised", + "Ped Stuck", + "Look About", + "Play Duck", + "Play Cower", + "Play Taxi", + "Play HandsUp", + "Play HandsCower", + "Play Chat", + "Finish Flee", +}; + +void +CPed::DebugDrawPedDestination(CPed *, int, int) +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawPedDesiredHeading(CPed *, int, int) +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawCollisionRadius(float, float, float, float, int) +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawVisionRange(CVector a1, float) +{ + for (int i = a1.x - 90; i < a1.x + 89; i += 30) { +#ifndef FINAL + // TODO: something was here +#endif // !FINAL + } +} + +void +CPed::DebugDrawVisionSimple(CVector, float) +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawLook() +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawPedPsyche() +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawDebugLines() +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +int nDisplayDebugInfo = 0; + +void +CPed::SwitchDebugDisplay(void) +{ + if (++nDisplayDebugInfo > 2) + nDisplayDebugInfo = 0; +} + +int +CPed::GetDebugDisplay(void) +{ + return nDisplayDebugInfo; +} + +void +CPed::DebugDrawLookAtPoints() +{ + // TODO: mobile code +} + +void +CPed::DebugRenderOnePedText(void) +{ + if ((GetPosition() - TheCamera.GetPosition()).MagnitudeSqr() < sq(30.0f)) { + float width, height; + RwV3d screenCoords; + CVector bitAbove = GetPosition(); + bitAbove.z += 2.0f; + if (CSprite::CalcScreenCoors(bitAbove, &screenCoords, &width, &height, true)) { + + float lineHeight = SCREEN_SCALE_Y(Min(height / 100.0f, 0.7f) * 22.0f); + + DefinedState(); + CFont::SetPropOn(); + CFont::SetBackgroundOn(); + + // Originally both of them were being divided by 60.0f. + float xScale = Min(width / 240.0f, 0.7f); + float yScale = Min(height / 80.0f, 0.7f); + + CFont::SetScale(SCREEN_SCALE_X(xScale), SCREEN_SCALE_Y(yScale)); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_WIDTH); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 0, 255)); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetFontStyle(0); + AsciiToUnicode(StateText[m_nPedState], gUString); + CFont::PrintString(screenCoords.x, screenCoords.y, gUString); + AsciiToUnicode(ObjectiveText[m_objective], gUString); + CFont::PrintString(screenCoords.x, screenCoords.y + lineHeight, gUString); + AsciiToUnicode(PersonalityTypeText[m_pedStats->m_type], gUString); + CFont::PrintString(screenCoords.x, screenCoords.y + 2 * lineHeight, gUString); + AsciiToUnicode(WaitStateText[m_nWaitState], gUString); + CFont::PrintString(screenCoords.x, screenCoords.y + 3 * lineHeight, gUString); + if (m_nPedState == PED_SEEK_POS || m_nPedState == PED_SEEK_ENTITY) { + sprintf(gString, "Safe distance to target: %.2f", m_distanceToCountSeekDone); + AsciiToUnicode(gString, gUString); + CFont::PrintString(screenCoords.x, screenCoords.y + 4 * lineHeight, gUString); + } + DefinedState(); + } + } +} + +void +CPed::DebugRenderClosePedText() +{ + // TODO: mobile code +} +#endif \ No newline at end of file diff --git a/src/peds/PedFight.cpp b/src/peds/PedFight.cpp new file mode 100644 index 0000000..03d5c75 --- /dev/null +++ b/src/peds/PedFight.cpp @@ -0,0 +1,3268 @@ +#include "common.h" + +#include "main.h" +#include "RpAnimBlend.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendAssociation.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "Darkel.h" +#include "DMAudio.h" +#include "FileMgr.h" +#include "General.h" +#include "Object.h" +#include "Pad.h" +#include "Particle.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Stats.h" +#include "TempColModels.h" +#include "VisibilityPlugins.h" +#include "Vehicle.h" +#include "Automobile.h" +#include "WaterLevel.h" +#include "World.h" + +uint16 nPlayerInComboMove; + +RpClump *flyingClumpTemp; + +// This is beta fistfite.dat array. Not used anymore since they're being fetched from fistfite.dat. +FightMove tFightMoves[NUM_FIGHTMOVES] = { + {ANIM_STD_NUM, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_PUNCH, 0.2f, 8.0f / 30.0f, 0.0f, 0.3f, HITLEVEL_HIGH, 1, 0}, + {ANIM_STD_FIGHT_IDLE, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_FIGHT_SHUFFLE_F, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_FIGHT_KNEE, 4.0f / 30.0f, 0.2f, 0.0f, 0.6f, HITLEVEL_LOW, 2, 0}, + {ANIM_STD_FIGHT_HEAD, 4.0f / 30.0f, 0.2f, 0.0f, 0.7f, HITLEVEL_HIGH, 3, 0}, + {ANIM_STD_FIGHT_PUNCH, 4.0f / 30.0f, 7.0f / 30.0f, 10.0f / 30.0f, 0.4f, HITLEVEL_HIGH, 1, 0}, + {ANIM_STD_FIGHT_LHOOK, 8.0f / 30.0f, 10.0f / 30.0f, 0.0f, 0.4f, HITLEVEL_HIGH, 3, 0}, + {ANIM_STD_FIGHT_KICK, 8.0f / 30.0f, 10.0f / 30.0f, 0.0f, 0.5, HITLEVEL_MEDIUM, 2, 0}, + {ANIM_STD_FIGHT_LONGKICK, 8.0f / 30.0f, 10.0f / 30.0f, 0.0f, 0.5, HITLEVEL_MEDIUM, 4, 0}, + {ANIM_STD_FIGHT_ROUNDHOUSE, 8.0f / 30.0f, 10.0f / 30.0f, 0.0f, 0.6f, HITLEVEL_MEDIUM, 4, 0}, + {ANIM_STD_FIGHT_BODYBLOW, 5.0f / 30.0f, 7.0f / 30.0f, 0.0f, 0.35f, HITLEVEL_LOW, 2, 0}, + {ANIM_STD_KICKGROUND, 10.0f / 30.0f, 14.0f / 30.0f, 0.0f, 0.4f, HITLEVEL_GROUND, 1, 0}, + {ANIM_STD_HIT_FRONT, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_BACK, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_RIGHT, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_LEFT, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_BODYBLOW, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_CHEST, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_HEAD, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_WALK, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_FLOOR, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_BEHIND, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_FIGHT_2IDLE, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, +}; + +static PedOnGroundState +CheckForPedsOnGroundToAttack(CPed *attacker, CPed **pedOnGround) +{ + PedOnGroundState stateToReturn; + float angleToFace; + CPed *currentPed = nil; + PedState currentPedState; + CPed *pedOnTheFloor = nil; + CPed *deadPed = nil; + CPed *pedBelow = nil; + bool foundDead = false; + bool foundOnTheFloor = false; + bool foundBelow = false; + float angleDiff; + float distance; + + if (!CGame::nastyGame) + return NO_PED; + + for (int currentPedId = 0; currentPedId < attacker->m_numNearPeds; currentPedId++) { + + currentPed = attacker->m_nearPeds[currentPedId]; + + CVector posDifference = currentPed->GetPosition() - attacker->GetPosition(); + distance = posDifference.Magnitude(); + + if (distance < 2.0f) { + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + currentPed->GetPosition().x, currentPed->GetPosition().y, + attacker->GetPosition().x, attacker->GetPosition().y); + + angleToFace = CGeneral::LimitRadianAngle(angleToFace); + attacker->m_fRotationCur = CGeneral::LimitRadianAngle(attacker->m_fRotationCur); + + angleDiff = Abs(angleToFace - attacker->m_fRotationCur); + + if (angleDiff > PI) + angleDiff = 2 * PI - angleDiff; + + currentPedState = currentPed->m_nPedState; + + if (currentPed->OnGroundOrGettingUp()) { + if (distance < 2.0f && angleDiff < DEGTORAD(65.0f)) { + if (currentPedState == PED_DEAD) { + foundDead = 1; + if (!deadPed) + deadPed = currentPed; + } else if (!currentPed->IsPedHeadAbovePos(-0.6f)) { + foundOnTheFloor = 1; + if (!pedOnTheFloor) + pedOnTheFloor = currentPed; + } + } + } else if ((distance < 0.8f && angleDiff < DEGTORAD(75.0f)) + || (distance < 1.3f && angleDiff < DEGTORAD(55.0f)) + || (distance < 1.7f && angleDiff < DEGTORAD(35.0f)) + || (distance < 2.0f && angleDiff < DEGTORAD(30.0f))) { + + // Either this condition or below one was probably returning 4 early in development. See Fight(). + foundBelow = 1; + pedBelow = currentPed; + break; + } else { + if (angleDiff < DEGTORAD(75.0f)) { + foundBelow = 1; + if (!pedBelow) + pedBelow = currentPed; + } + } + } + } + + if (foundOnTheFloor) { + currentPed = pedOnTheFloor; + stateToReturn = PED_ON_THE_FLOOR; + } else if (foundDead) { + currentPed = deadPed; + stateToReturn = PED_DEAD_ON_THE_FLOOR; + } else if (foundBelow) { + currentPed = pedBelow; + stateToReturn = PED_IN_FRONT_OF_ATTACKER; + } else { + currentPed = nil; + stateToReturn = NO_PED; + } + + if (pedOnGround) + *pedOnGround = currentPed; + + return stateToReturn; +} + +void +CPed::SetPointGunAt(CEntity *to) +{ + if (to) { + SetLookFlag(to, true); + SetAimFlag(to); +#ifdef VC_PED_PORTS + SetLookTimer(INT32_MAX); +#endif + } + + if (m_nPedState == PED_AIM_GUN || bIsDucking || m_nWaitState == WAITSTATE_PLAYANIM_DUCK) + return; + + if (m_nPedState != PED_ATTACK) + SetStoredState(); + + SetPedState(PED_AIM_GUN); + bIsPointingGunAt = true; + CWeaponInfo *curWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + SetMoveState(PEDMOVE_NONE); + + CAnimBlendAssociation *aimAssoc; + + if (bCrouchWhenShooting) + aimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), curWeapon->m_Anim2ToPlay); + else + aimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), curWeapon->m_AnimToPlay); + + if (!aimAssoc || aimAssoc->blendDelta < 0.0f) { + if (bCrouchWhenShooting) + aimAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, curWeapon->m_Anim2ToPlay, 4.0f); + else + aimAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, curWeapon->m_AnimToPlay); + + aimAssoc->blendAmount = 0.0f; + aimAssoc->blendDelta = 8.0f; + } + if (to) + Say(SOUND_PED_ATTACK); +} + +void +CPed::PointGunAt(void) +{ + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + CAnimBlendAssociation *weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weaponInfo->m_AnimToPlay); + if (!weaponAssoc || weaponAssoc->blendDelta < 0.0f) + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weaponInfo->m_Anim2ToPlay); + + if (weaponAssoc && weaponAssoc->currentTime > weaponInfo->m_fAnimLoopStart) { + weaponAssoc->SetCurrentTime(weaponInfo->m_fAnimLoopStart); + weaponAssoc->flags &= ~ASSOC_RUNNING; + + if (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) + m_pedIK.m_flags |= CPedIK::AIMS_WITH_ARM; + else + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; + } +} + +void +CPed::ClearPointGunAt(void) +{ + CAnimBlendAssociation *animAssoc; + CWeaponInfo *weaponInfo; + + ClearLookFlag(); + ClearAimFlag(); + bIsPointingGunAt = false; +#ifndef VC_PED_PORTS + if (m_nPedState == PED_AIM_GUN) { + RestorePreviousState(); +#else + if (m_nPedState == PED_AIM_GUN || m_nPedState == PED_ATTACK) { + SetPedState(PED_IDLE); + RestorePreviousState(); + } +#endif + weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weaponInfo->m_AnimToPlay); + if (!animAssoc || animAssoc->blendDelta < 0.0f) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weaponInfo->m_Anim2ToPlay); + } + if (animAssoc) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->blendDelta = -4.0f; + } +#ifndef VC_PED_PORTS + } +#endif +} + +void +CPed::SetAttack(CEntity *victim) +{ + CPed *victimPed = nil; + if (victim && victim->IsPed()) + victimPed = (CPed*)victim; + + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_BIGGUN); + if (animAssoc) { + animAssoc->blendDelta = -1000.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + + if (m_attackTimer > CTimer::GetTimeInMilliseconds() || m_nWaitState == WAITSTATE_SURPRISE) + return; + + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HGUN_RELOAD)) { + bIsAttacking = false; + return; + } + + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_AK_RELOAD)) { + if (!IsPlayer() || m_nPedState != PED_ATTACK || ((CPlayerPed*)this)->m_bHaveTargetSelected) + bIsAttacking = false; + else + bIsAttacking = true; + + return; + } + + CWeaponInfo *curWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + if (curWeapon->m_eWeaponFire == WEAPON_FIRE_INSTANT_HIT && !IsPlayer()) { + if (GetWeapon()->HitsGround(this, nil, victim)) + return; + } + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) { + if (IsPlayer() || + (m_nPedState != PED_FIGHT && m_nMoveState != PEDMOVE_NONE && m_nMoveState != PEDMOVE_STILL && !(m_pedStats->m_flags & STAT_SHOPPING_BAGS))) { + + if (m_nPedState != PED_ATTACK) { + SetPedState(PED_ATTACK); + bIsAttacking = false; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, curWeapon->m_AnimToPlay, 8.0f); + animAssoc->SetRun(); + if (animAssoc->currentTime == animAssoc->hierarchy->totalLength) + animAssoc->SetCurrentTime(0.0f); + + animAssoc->SetFinishCallback(FinishedAttackCB, this); + } + } else { + StartFightAttack(CGeneral::GetRandomNumber() % 256); + } + return; + } + + m_pSeekTarget = victim; + if (m_pSeekTarget) + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + + if (curWeapon->IsFlagSet(WEAPONFLAG_CANAIM)) { + CVector aimPos = GetRight() * 0.1f + GetForward() * 0.2f + GetPosition(); + CEntity *obstacle = CWorld::TestSphereAgainstWorld(aimPos, 0.2f, nil, true, false, false, true, false, false); + if (obstacle) + return; + + m_pLookTarget = victim; + if (victim) { + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + } + + if (m_pLookTarget) { + SetAimFlag(m_pLookTarget); +#ifdef FREE_CAM + } else if (this != FindPlayerPed() || !((CPlayerPed*)this)->m_bFreeAimActive) { +#else + } else { +#endif + SetAimFlag(m_fRotationCur); + + if (FindPlayerPed() == this && TheCamera.Cams[0].Using3rdPersonMouseCam()) + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); + } + } +#ifdef FIX_BUGS + // fix aiming for flamethrower while using PC controls + else if (GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER && TheCamera.Cams[0].Using3rdPersonMouseCam() && this == FindPlayerPed()) + { + SetAimFlag(m_fRotationCur); + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); + } +#endif + if (m_nPedState == PED_ATTACK) { + bIsAttacking = true; + return; + } + + if (IsPlayer() || !victimPed || victimPed->IsPedInControl()) { + if (IsPlayer()) + CPad::GetPad(0)->ResetAverageWeapon(); + + uint8 pointBlankStatus; + if ((curWeapon->m_eWeaponFire == WEAPON_FIRE_INSTANT_HIT || GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER) + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON_RUNABOUT + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER_RUNABOUT + && (pointBlankStatus = CheckForPointBlankPeds(victimPed)) != NO_POINT_BLANK_PED) { + ClearAimFlag(); + + // This condition is pointless + if (pointBlankStatus == POINT_BLANK_FOR_WANTED_PED || !victimPed) + StartFightAttack(200); + } else { + if (!curWeapon->IsFlagSet(WEAPONFLAG_CANAIM)) + m_pSeekTarget = nil; + + if (m_nPedState != PED_AIM_GUN) + SetStoredState(); + + SetPedState(PED_ATTACK); + SetMoveState(PEDMOVE_NONE); + if (bCrouchWhenShooting) { + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_RBLOCK_SHOOT, 4.0f); + } else { + float animDelta = 8.0f; + if (curWeapon->m_eWeaponFire == WEAPON_FIRE_MELEE) + animDelta = 1000.0f; + + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_BASEBALLBAT + || CheckForPedsOnGroundToAttack(this, nil) < PED_ON_THE_FLOOR) { + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, curWeapon->m_AnimToPlay, animDelta); + } else { + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, curWeapon->m_Anim2ToPlay, animDelta); + } + } + + animAssoc->SetRun(); + if (animAssoc->currentTime == animAssoc->hierarchy->totalLength) + animAssoc->SetCurrentTime(0.0f); + + animAssoc->SetFinishCallback(FinishedAttackCB, this); + } + return; + } + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT && victimPed->m_nPedState == PED_GETUP) + SetWaitState(WAITSTATE_SURPRISE, nil); + + SetLookFlag(victim, false); + SetLookTimer(100); +} + +void +CPed::ClearAttack(void) +{ + if (m_nPedState != PED_ATTACK || bIsDucking || m_nWaitState == WAITSTATE_PLAYANIM_DUCK) + return; + +#ifdef VC_PED_PORTS + // VC uses CCamera::Using1stPersonWeaponMode + if (FindPlayerPed() == this && (TheCamera.PlayerWeaponMode.Mode == CCam::MODE_SNIPER || + TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_ROCKETLAUNCHER)) { + SetPointGunAt(nil); + } else +#endif + if (bIsPointingGunAt) { + if (m_pLookTarget) + SetPointGunAt(m_pLookTarget); + else + ClearPointGunAt(); + } else if (m_objective != OBJECTIVE_NONE) { + SetIdle(); + } else { + RestorePreviousState(); + } +} + +void +CPed::ClearAttackByRemovingAnim(void) +{ + if (m_nPedState != PED_ATTACK || bIsDucking) + return; + + CWeaponInfo *weapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + CAnimBlendAssociation *weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weapon->m_AnimToPlay); + if (!weaponAssoc) { + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weapon->m_Anim2ToPlay); + + if (!weaponAssoc && weapon->IsFlagSet(WEAPONFLAG_THROW)) + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_THROW_UNDER); + + if (!weaponAssoc) { + ClearAttack(); + return; + } + } + weaponAssoc->blendDelta = -8.0f; + weaponAssoc->flags &= ~ASSOC_RUNNING; + weaponAssoc->flags |= ASSOC_DELETEFADEDOUT; + weaponAssoc->SetDeleteCallback(FinishedAttackCB, this); +} + +void +CPed::FinishedAttackCB(CAnimBlendAssociation *attackAssoc, void *arg) +{ + CWeaponInfo *currentWeapon; + CAnimBlendAssociation *newAnim; + CPed *ped = (CPed*)arg; + + if (attackAssoc) { + switch (attackAssoc->animId) { + case ANIM_STD_START_THROW: + // what?! + if ((!ped->IsPlayer() || ((CPlayerPed*)ped)->m_bHaveTargetSelected) && ped->IsPlayer()) { + attackAssoc->blendDelta = -1000.0f; + newAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_THROW_UNDER); + } else { + attackAssoc->blendDelta = -1000.0f; + newAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_WEAPON_THROW); + } + + newAnim->SetFinishCallback(FinishedAttackCB, ped); + return; + + case ANIM_STD_PARTIAL_PUNCH: + attackAssoc->blendDelta = -8.0f; + attackAssoc->flags |= ASSOC_DELETEFADEDOUT; + ped->ClearAttack(); + return; + + case ANIM_STD_WEAPON_THROW: + case ANIM_STD_THROW_UNDER: + if (ped->GetWeapon()->m_nAmmoTotal > 0) { + currentWeapon = CWeaponInfo::GetWeaponInfo(ped->GetWeapon()->m_eWeaponType); + ped->AddWeaponModel(currentWeapon->m_nModelId); + } + break; + default: + break; + } + } + + if (!ped->bIsAttacking) + ped->ClearAttack(); +} + +uint8 +CPed::CheckForPointBlankPeds(CPed *pedToVerify) +{ + float pbDistance = 1.1f; + if (GetWeapon()->IsType2Handed()) + pbDistance = 1.6f; + + for (int i = 0; i < m_numNearPeds; i++) { + CPed *nearPed = m_nearPeds[i]; + + if (!pedToVerify || pedToVerify == nearPed) { + + CVector diff = nearPed->GetPosition() - GetPosition(); + if (diff.Magnitude() < pbDistance) { + + float neededAngle = CGeneral::GetRadianAngleBetweenPoints( + nearPed->GetPosition().x, nearPed->GetPosition().y, + GetPosition().x, GetPosition().y); + neededAngle = CGeneral::LimitRadianAngle(neededAngle); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + + float neededTurn = Abs(neededAngle - m_fRotationCur); + + if (neededTurn > PI) + neededTurn = 2*PI - neededTurn; + + if (nearPed->OnGroundOrGettingUp() || nearPed->m_nPedState == PED_DIVE_AWAY) + return NO_POINT_BLANK_PED; + + if (neededTurn < CAN_SEE_ENTITY_ANGLE_THRESHOLD) { + if (pedToVerify == nearPed) + return POINT_BLANK_FOR_WANTED_PED; + else + return POINT_BLANK_FOR_SOMEONE_ELSE; + } + } + } + } + return NO_POINT_BLANK_PED; +} + +void +CPed::Attack(void) +{ + CAnimBlendAssociation *weaponAnimAssoc; + int32 weaponAnim; + float animStart; + float weaponAnimTime; + float animLoopEnd; + CWeaponInfo *ourWeapon; + bool attackShouldContinue; + AnimationId reloadAnim; + CAnimBlendAssociation *reloadAnimAssoc; + float delayBetweenAnimAndFire; + CVector firePos; + + ourWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ourWeapon->m_AnimToPlay); + attackShouldContinue = bIsAttacking; + reloadAnimAssoc = nil; + reloadAnim = ANIM_STD_NUM; + delayBetweenAnimAndFire = ourWeapon->m_fAnimFrameFire; + weaponAnim = ourWeapon->m_AnimToPlay; + + if (weaponAnim == ANIM_STD_WEAPON_HGUN_BODY) + reloadAnim = ANIM_STD_HGUN_RELOAD; + else if (weaponAnim == ANIM_STD_WEAPON_AK_BODY) + reloadAnim = ANIM_STD_AK_RELOAD; + + if (reloadAnim != ANIM_STD_NUM) + reloadAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), reloadAnim); + + if (bIsDucking) + return; + + if (reloadAnimAssoc) { + if (!IsPlayer() || ((CPlayerPed*)this)->m_bHaveTargetSelected) + ClearAttack(); + + return; + } + + if (CTimer::GetTimeInMilliseconds() < m_shootTimer) + attackShouldContinue = true; + + if (!weaponAnimAssoc) { + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ourWeapon->m_Anim2ToPlay); + delayBetweenAnimAndFire = ourWeapon->m_fAnim2FrameFire; + + // Long throw granade, molotov + if (!weaponAnimAssoc && ourWeapon->IsFlagSet(WEAPONFLAG_THROW)) { + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_THROW_UNDER); + delayBetweenAnimAndFire = 0.2f; + } + + if (!weaponAnimAssoc) { + if (attackShouldContinue) { + if (ourWeapon->m_eWeaponFire != WEAPON_FIRE_PROJECTILE || !IsPlayer() || ((CPlayerPed*)this)->m_bHaveTargetSelected) { + if (!CGame::nastyGame || ourWeapon->m_eWeaponFire != WEAPON_FIRE_MELEE || CheckForPedsOnGroundToAttack(this, nil) < PED_ON_THE_FLOOR) { + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ourWeapon->m_AnimToPlay, 8.0f); + } + else { + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ourWeapon->m_Anim2ToPlay, 8.0f); + } + + weaponAnimAssoc->SetFinishCallback(FinishedAttackCB, this); + weaponAnimAssoc->SetRun(); + + if (weaponAnimAssoc->currentTime == weaponAnimAssoc->hierarchy->totalLength) + weaponAnimAssoc->SetCurrentTime(0.0f); + + if (IsPlayer()) { + ((CPlayerPed*)this)->m_fAttackButtonCounter = 0.0f; + ((CPlayerPed*)this)->m_bHaveTargetSelected = false; + } + } + } else + FinishedAttackCB(nil, this); + + return; + } + } + + animStart = ourWeapon->m_fAnimLoopStart; + weaponAnimTime = weaponAnimAssoc->currentTime; + if (weaponAnimTime > animStart && weaponAnimTime - weaponAnimAssoc->timeStep <= animStart) { + if (ourWeapon->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) + m_pedIK.m_flags |= CPedIK::AIMS_WITH_ARM; + else + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; + } + + if (weaponAnimTime <= delayBetweenAnimAndFire || weaponAnimTime - weaponAnimAssoc->timeStep > delayBetweenAnimAndFire || !weaponAnimAssoc->IsRunning()) { + if (weaponAnimAssoc->speed < 1.0f) + weaponAnimAssoc->speed = 1.0f; + + } else { + firePos = ourWeapon->m_vecFireOffset; + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT) { + if (weaponAnimAssoc->animId == ourWeapon->m_Anim2ToPlay) + firePos.z = 0.7f * ourWeapon->m_fRadius - 1.0f; + + firePos = GetMatrix() * firePos; + } else if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + TransformToNode(firePos, weaponAnimAssoc->animId == ANIM_STD_KICKGROUND ? PED_FOOTR : PED_HANDR); + } else { + firePos = GetMatrix() * firePos; + } + + GetWeapon()->Fire(this, &firePos); + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_MOLOTOV || GetWeapon()->m_eWeaponType == WEAPONTYPE_GRENADE) { + RemoveWeaponModel(ourWeapon->m_nModelId); + } + if (!GetWeapon()->m_nAmmoTotal && ourWeapon->m_eWeaponFire != WEAPON_FIRE_MELEE && FindPlayerPed() != this) { + SelectGunIfArmed(); + } + + if (GetWeapon()->m_eWeaponState != WEAPONSTATE_MELEE_MADECONTACT) { + // If reloading just began, start the animation + // Last condition will always return true, even IDA hides it + if (GetWeapon()->m_eWeaponState == WEAPONSTATE_RELOADING && reloadAnim != ANIM_STD_NUM /* && !reloadAnimAssoc*/) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, reloadAnim, 8.0f); + ClearLookFlag(); + ClearAimFlag(); + bIsAttacking = false; + bIsPointingGunAt = false; + m_shootTimer = CTimer::GetTimeInMilliseconds(); + return; + } + } else { + if (weaponAnimAssoc->animId == ANIM_STD_WEAPON_BAT_V || weaponAnimAssoc->animId == ANIM_STD_WEAPON_BAT_H) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_BAT_ATTACK, 1.0f); + } else if (weaponAnimAssoc->animId == ANIM_STD_PARTIAL_PUNCH) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FIGHT_PUNCH_39, 0.0f); + } + + weaponAnimAssoc->speed = 0.5f; + + if (bIsAttacking || CTimer::GetTimeInMilliseconds() < m_shootTimer) { + weaponAnimAssoc->callbackType = 0; + } + } + + attackShouldContinue = false; + } + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_SHOTGUN) { + weaponAnimTime = weaponAnimAssoc->currentTime; + firePos = ourWeapon->m_vecFireOffset; + + if (weaponAnimTime > 1.0f && weaponAnimTime - weaponAnimAssoc->timeStep <= 1.0f && weaponAnimAssoc->IsRunning()) { + TransformToNode(firePos, PED_HANDR); + + CVector gunshellPos( + firePos.x - 0.6f * GetForward().x, + firePos.y - 0.6f * GetForward().y, + firePos.z - 0.15f * GetUp().z + ); + + CVector2D gunshellRot( + GetRight().x, + GetRight().y + ); + + gunshellRot.Normalise(); + GetWeapon()->AddGunshell(this, gunshellPos, gunshellRot, 0.025f); + } + } +#ifdef VC_PED_PORTS + if (IsPlayer()) { + if (CPad::GetPad(0)->GetSprint()) { + // animBreakout is a member of WeaponInfo in VC, so it's me that added the below line. + float animBreakOut = ((GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER || GetWeapon()->m_eWeaponType == WEAPONTYPE_UZI || GetWeapon()->m_eWeaponType == WEAPONTYPE_SHOTGUN) ? 25 / 30.0f : 99 / 30.0f); + if (!attackShouldContinue && weaponAnimAssoc->currentTime > animBreakOut) { + weaponAnimAssoc->blendDelta = -4.0f; + FinishedAttackCB(nil, this); + return; + } + } + } +#endif + animLoopEnd = ourWeapon->m_fAnimLoopEnd; + if (ourWeapon->m_eWeaponFire == WEAPON_FIRE_MELEE && weaponAnimAssoc->animId == ourWeapon->m_Anim2ToPlay) + animLoopEnd = 3.4f/6.0f; + + weaponAnimTime = weaponAnimAssoc->currentTime; + + // Anim loop end, either start the loop again or finish the attack + if (weaponAnimTime > animLoopEnd || !weaponAnimAssoc->IsRunning() && ourWeapon->m_eWeaponFire != WEAPON_FIRE_PROJECTILE) { + + if (weaponAnimTime - 2.0f * weaponAnimAssoc->timeStep <= animLoopEnd + && (bIsAttacking || CTimer::GetTimeInMilliseconds() < m_shootTimer) + && GetWeapon()->m_eWeaponState != WEAPONSTATE_RELOADING) { + + weaponAnim = weaponAnimAssoc->animId; + if (ourWeapon->m_eWeaponFire != WEAPON_FIRE_MELEE || CheckForPedsOnGroundToAttack(this, nil) < PED_ON_THE_FLOOR) { + if (weaponAnim != ourWeapon->m_Anim2ToPlay || weaponAnim == ANIM_STD_RBLOCK_SHOOT) { + weaponAnimAssoc->Start(ourWeapon->m_fAnimLoopStart); + } else { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ourWeapon->m_AnimToPlay, 8.0f); + } + } else { + if (weaponAnim == ourWeapon->m_Anim2ToPlay) + weaponAnimAssoc->SetCurrentTime(0.1f); + else + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ourWeapon->m_Anim2ToPlay, 8.0f); + } +#ifdef VC_PED_PORTS + } else if (IsPlayer() && m_pPointGunAt && bIsAimingGun && GetWeapon()->m_eWeaponState != WEAPONSTATE_RELOADING) { + weaponAnimAssoc->SetCurrentTime(ourWeapon->m_fAnimLoopEnd); + weaponAnimAssoc->flags &= ~ASSOC_RUNNING; + SetPointGunAt(m_pPointGunAt); +#endif +#ifdef FREE_CAM + } else if (IsPlayer() && ((CPlayerPed*)this)->m_bFreeAimActive && GetWeapon()->m_eWeaponState != WEAPONSTATE_RELOADING) { + float limitedCam = CGeneral::LimitRadianAngle(-TheCamera.Orientation); + SetLookFlag(limitedCam, true); + SetAimFlag(limitedCam); + SetLookTimer(INT32_MAX); + SetPointGunAt(nil); + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); +#endif + } else { + ClearAimFlag(); + + // Echoes of bullets, at the end of the attack. (Bug: doesn't play while reloading) + if (weaponAnimAssoc->currentTime - weaponAnimAssoc->timeStep <= ourWeapon->m_fAnimLoopEnd) { + switch (GetWeapon()->m_eWeaponType) { + case WEAPONTYPE_UZI: + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_UZI_BULLET_ECHO, 0.0f); + break; + case WEAPONTYPE_AK47: + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_AK47_BULLET_ECHO, 0.0f); + break; + case WEAPONTYPE_M16: + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_M16_BULLET_ECHO, 0.0f); + break; + default: + break; + } + } + + // Fun fact: removing this part leds to reloading flamethrower + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER && weaponAnimAssoc->IsRunning()) { + weaponAnimAssoc->flags |= ASSOC_DELETEFADEDOUT; + weaponAnimAssoc->flags &= ~ASSOC_RUNNING; + weaponAnimAssoc->blendDelta = -4.0f; + } + } + } + if (weaponAnimAssoc->currentTime > delayBetweenAnimAndFire) + attackShouldContinue = false; + + bIsAttacking = attackShouldContinue; +} + +void +CPed::StartFightAttack(uint8 buttonPressure) +{ + if (!IsPedInControl() || m_attackTimer > CTimer::GetTimeInMilliseconds()) + return; + + if (m_nPedState == PED_FIGHT) { + m_fightButtonPressure = buttonPressure; + return; + } + + if (m_nPedState != PED_AIM_GUN) + SetStoredState(); + + if (m_nWaitState != WAITSTATE_FALSE) { + m_nWaitState = WAITSTATE_FALSE; + RestoreHeadingRate(); + } + + SetPedState(PED_FIGHT); + m_fightButtonPressure = 0; + RpAnimBlendClumpRemoveAssociations(GetClump(), ASSOC_REPEAT); + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_STARTWALK); + + if (animAssoc) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->blendDelta = -1000.0f; + } + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP1); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + + if (animAssoc) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->blendDelta = -1000.0f; + RestoreHeadingRate(); + } + + SetMoveState(PEDMOVE_NONE); + m_nStoredMoveState = PEDMOVE_NONE; + + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_IDLE)->blendAmount = 1.0f; + + CPed *pedOnGround = nil; + if (IsPlayer() && CheckForPedsOnGroundToAttack(this, &pedOnGround) > PED_IN_FRONT_OF_ATTACKER) { + m_curFightMove = FIGHTMOVE_GROUNDKICK; + } else if (m_pedStats->m_flags & STAT_SHOPPING_BAGS) { + m_curFightMove = FIGHTMOVE_ROUNDHOUSE; + } else { + m_curFightMove = FIGHTMOVE_STDPUNCH; + } + + if (pedOnGround && IsPlayer()) { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + pedOnGround->GetPosition().x, pedOnGround->GetPosition().y, + GetPosition().x, GetPosition().y); + + m_fRotationDest = CGeneral::LimitRadianAngle(m_fRotationDest); + m_fRotationCur = m_fRotationDest; + m_lookTimer = 0; + SetLookFlag(pedOnGround, true); + SetLookTimer(1500); + } + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 4.0f); + animAssoc->SetFinishCallback(FinishFightMoveCB, this); + m_fightState = FIGHTSTATE_NO_MOVE; + m_takeAStepAfterAttack = false; + bIsAttacking = true; + + if (IsPlayer()) + nPlayerInComboMove = 0; +} + +void +CPed::StartFightDefend(uint8 direction, uint8 hitLevel, uint8 unk) +{ + if (m_nPedState == PED_DEAD) { + if (CGame::nastyGame) { + if (hitLevel == HITLEVEL_GROUND) { + CAnimBlendAssociation *floorHitAssoc; + if (RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL)) { + floorHitAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR_FRONT, 8.0f); + } else { + floorHitAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[FIGHTMOVE_HITONFLOOR].animId, 8.0f); + } + if (floorHitAssoc) { + floorHitAssoc->SetCurrentTime(0.0f); + floorHitAssoc->SetRun(); + floorHitAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + } + } + if (CGame::nastyGame) { + CVector headPos = GetNodePosition(PED_HEAD); + for(int i = 0; i < 4; ++i) { + CVector bloodDir(0.0f, 0.0f, 0.1f); + CVector bloodPos = headPos - 0.2f * GetForward(); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, bloodDir, nil, 0.0f, 0, 0, 0, 0); + } + } + } + } else if (m_nPedState == PED_FALL) { + if (hitLevel == HITLEVEL_GROUND && !IsPedHeadAbovePos(-0.3f)) { + CAnimBlendAssociation *floorHitAssoc = RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL) ? + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR_FRONT, 8.0f) : + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR, 8.0f); + if (floorHitAssoc) { + floorHitAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + floorHitAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + } else if (IsPedInControl()) { + if ((IsPlayer() && m_nPedState != PED_FIGHT && ((CPlayerPed*)this)->m_fMoveSpeed > 1.0f) + || (!IsPlayer() && m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE)) { +#ifndef VC_PED_PORTS + if (hitLevel != HITLEVEL_HIGH && hitLevel != HITLEVEL_LOW || (IsPlayer() || CGeneral::GetRandomNumber() & 3) && CGeneral::GetRandomNumber() & 7) { + if (IsPlayer() || CGeneral::GetRandomNumber() & 3) { +#else + if (hitLevel != HITLEVEL_HIGH && hitLevel != HITLEVEL_LOW || (IsPlayer() || CGeneral::GetRandomNumber() & 1) && CGeneral::GetRandomNumber() & 7) { + if (IsPlayer() || CGeneral::GetRandomNumber() & 1) { +#endif + AnimationId shotAnim; + switch (direction) { + case 1: + shotAnim = ANIM_STD_HITBYGUN_LEFT; + break; + case 2: + shotAnim = ANIM_STD_HITBYGUN_BACK; + break; + case 3: + shotAnim = ANIM_STD_HITBYGUN_RIGHT; + break; + default: + shotAnim = ANIM_STD_HITBYGUN_FRONT; + break; + } + CAnimBlendAssociation *shotAssoc = RpAnimBlendClumpGetAssociation(GetClump(), shotAnim); + if (!shotAssoc || shotAssoc->blendDelta < 0.0f) + shotAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, shotAnim, 8.0f); + + shotAssoc->SetCurrentTime(0.0f); + shotAssoc->SetRun(); + shotAssoc->flags |= ASSOC_FADEOUTWHENDONE; + } else { + int time = CGeneral::GetRandomNumberInRange(1000, 3000); + SetWaitState(WAITSTATE_PLAYANIM_DUCK, &time); + } + } else { +#ifndef VC_PED_PORTS + switch (direction) { + case 1: + SetFall(500, ANIM_STD_HIGHIMPACT_LEFT, false); + break; + case 2: + SetFall(500, ANIM_STD_HIGHIMPACT_BACK, false); + break; + case 3: + SetFall(500, ANIM_STD_HIGHIMPACT_RIGHT, false); + break; + default: + SetFall(500, ANIM_STD_KO_SHOT_STOMACH, false); + break; + } +#else + bool fall = true; + AnimationId hitAnim; + switch (direction) { + case 1: + hitAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + if (CGeneral::GetRandomNumber() & 1) { + fall = false; + hitAnim = ANIM_STD_HIT_BACK; + } else { + hitAnim = ANIM_STD_HIGHIMPACT_BACK; + } + break; + case 3: + hitAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + if (hitLevel == HITLEVEL_LOW) { + hitAnim = ANIM_STD_KO_SHOT_STOMACH; + } else if (CGeneral::GetRandomNumber() & 1) { + fall = false; + hitAnim = ANIM_STD_HIT_WALK; + } else if (CGeneral::GetRandomNumber() & 1) { + fall = false; + hitAnim = ANIM_STD_HIT_HEAD; + } else { + hitAnim = ANIM_STD_KO_SHOT_FACE; + } + break; + } + if (fall) { + SetFall(500, hitAnim, false); + } else { + CAnimBlendAssociation *hitAssoc = RpAnimBlendClumpGetAssociation(GetClump(), hitAnim); + if (!hitAssoc || hitAssoc->blendDelta < 0.0f) + hitAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, hitAnim, 8.0f); + + hitAssoc->SetCurrentTime(0.0f); + hitAssoc->SetRun(); + hitAssoc->flags |= ASSOC_FADEOUTWHENDONE; + } +#endif + } + Say(SOUND_PED_DEFEND); + } else { + Say(SOUND_PED_DEFEND); + switch (hitLevel) { + case HITLEVEL_GROUND: + m_curFightMove = FIGHTMOVE_HITONFLOOR; + break; + case HITLEVEL_LOW: +#ifndef VC_PED_PORTS + if (direction == 2) { + SetFall(1000, ANIM_STD_HIGHIMPACT_BACK, false); + return; + } +#else + if (direction == 2 && (!IsPlayer() || ((CGeneral::GetRandomNumber() & 1) && m_fHealth < 30.0f))) { + SetFall(1000, ANIM_STD_HIGHIMPACT_BACK, false); + return; + } else if (direction != 2 && !IsPlayer() && (CGeneral::GetRandomNumber() & 1) && m_fHealth < 30.0f) { + SetFall(1000, ANIM_STD_KO_SHOT_STOMACH, false); + return; + } +#endif + m_curFightMove = FIGHTMOVE_HITBODY; + break; + case HITLEVEL_HIGH: + switch (direction) { + case 1: + m_curFightMove = FIGHTMOVE_HITLEFT; + break; + case 2: + m_curFightMove = FIGHTMOVE_HITBACK; + break; + case 3: + m_curFightMove = FIGHTMOVE_HITRIGHT; + break; + default: + if (unk <= 5) + m_curFightMove = FIGHTMOVE_HITHEAD; + else + m_curFightMove = FIGHTMOVE_HITBIGSTEP; + break; + } + break; + default: + switch (direction) { + case 1: + m_curFightMove = FIGHTMOVE_HITLEFT; + break; + case 2: + m_curFightMove = FIGHTMOVE_HITBACK; + break; + case 3: + m_curFightMove = FIGHTMOVE_HITRIGHT; + break; + default: + if (unk <= 5) + m_curFightMove = FIGHTMOVE_HITCHEST; + else + m_curFightMove = FIGHTMOVE_HITBIGSTEP; + break; + } + break; + } + if (m_nPedState == PED_GETUP && !IsPedHeadAbovePos(0.0f)) + m_curFightMove = FIGHTMOVE_HITONFLOOR; + + if (m_nPedState == PED_FIGHT) { + CAnimBlendAssociation *moveAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 8.0f); + moveAssoc->SetCurrentTime(0.0f); + moveAssoc->SetFinishCallback(FinishFightMoveCB, this); + if (IsPlayer()) + moveAssoc->speed = 1.3f; + + m_takeAStepAfterAttack = false; + m_fightButtonPressure = 0; + } else if (IsPlayer() && m_currentWeapon != WEAPONTYPE_UNARMED) { + CAnimBlendAssociation *moveAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 4.0f); + moveAssoc->SetCurrentTime(0.0f); + moveAssoc->speed = 1.3f; + } else { + if (m_nPedState != PED_AIM_GUN && m_nPedState != PED_ATTACK) + SetStoredState(); + + if (m_nWaitState != WAITSTATE_FALSE) { + m_nWaitState = WAITSTATE_FALSE; + RestoreHeadingRate(); + } + SetPedState(PED_FIGHT); + m_fightButtonPressure = 0; + RpAnimBlendClumpRemoveAssociations(GetClump(), ASSOC_REPEAT); + CAnimBlendAssociation *walkStartAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_STARTWALK); + if (walkStartAssoc) { + walkStartAssoc->flags |= ASSOC_DELETEFADEDOUT; + walkStartAssoc->blendDelta = -1000.0f; + } + CAnimBlendAssociation *walkStopAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP1); + if (!walkStopAssoc) + walkStopAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + if (walkStopAssoc) { + walkStopAssoc->flags |= ASSOC_DELETEFADEDOUT; + walkStopAssoc->blendDelta = -1000.0f; + RestoreHeadingRate(); + } + SetMoveState(PEDMOVE_NONE); + m_nStoredMoveState = PEDMOVE_NONE; + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_IDLE)->blendAmount = 1.0f; + CAnimBlendAssociation *moveAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 8.0f); + moveAssoc->SetFinishCallback(FinishFightMoveCB, this); + m_fightState = FIGHTSTATE_NO_MOVE; + m_takeAStepAfterAttack = false; + bIsAttacking = true; + } + } + } +} + +void +CPed::Fight(void) +{ + CAnimBlendAssociation *currentAssoc, *animAssoc; + bool hasShoppingBags, punchOnly, canKick, canKneeHead, canRoundhouse; + float angleToFace, nextAngle; + bool goForward = false; + int nextFightMove; + + switch (m_curFightMove) { + case FIGHTMOVE_NULL: + return; + case FIGHTMOVE_IDLE2NORM: + m_curFightMove = FIGHTMOVE_NULL; + RestorePreviousState(); + + // FIX: Uninitialized + currentAssoc = nil; + break; + case FIGHTMOVE_IDLE: + currentAssoc = nil; + break; + default: + currentAssoc = RpAnimBlendClumpGetAssociation(GetClump(), tFightMoves[m_curFightMove].animId); + break; + } + + if (!bIsAttacking && IsPlayer()) { + if (currentAssoc) { + currentAssoc->blendDelta = -1000.0f; + currentAssoc->flags |= ASSOC_DELETEFADEDOUT; + currentAssoc->flags &= ~ASSOC_RUNNING; + } + if (m_takeAStepAfterAttack) + EndFight(ENDFIGHT_WITH_A_STEP); + else + EndFight(ENDFIGHT_FAST); + + } else if (currentAssoc && m_fightState > FIGHTSTATE_MOVE_FINISHED) { + float animTime = currentAssoc->currentTime; + FightMove &curMove = tFightMoves[m_curFightMove]; + if (curMove.hitLevel != HITLEVEL_NULL && animTime > curMove.startFireTime && animTime <= curMove.endFireTime && m_fightState >= FIGHTSTATE_NO_MOVE) { + + CVector touchingNodePos(0.0f, 0.0f, 0.0f); + + switch (m_curFightMove) { + case FIGHTMOVE_STDPUNCH: + case FIGHTMOVE_PUNCHHOOK: + case FIGHTMOVE_BODYBLOW: + TransformToNode(touchingNodePos, PED_HANDR); + break; + case FIGHTMOVE_IDLE: + case FIGHTMOVE_SHUFFLE_F: + break; + case FIGHTMOVE_KNEE: + TransformToNode(touchingNodePos, PED_LOWERLEGR); + break; + case FIGHTMOVE_HEADBUTT: + TransformToNode(touchingNodePos, PED_HEAD); + break; + case FIGHTMOVE_PUNCHJAB: + TransformToNode(touchingNodePos, PED_HANDL); + break; + case FIGHTMOVE_KICK: + case FIGHTMOVE_LONGKICK: + case FIGHTMOVE_ROUNDHOUSE: + case FIGHTMOVE_GROUNDKICK: + TransformToNode(touchingNodePos, PED_FOOTR); + break; + } + + if (m_curFightMove == FIGHTMOVE_PUNCHJAB) { + touchingNodePos += 0.1f * GetForward(); + } else if (m_curFightMove == FIGHTMOVE_PUNCHHOOK) { + touchingNodePos += 0.22f * GetForward(); + } + FightStrike(touchingNodePos); + m_fightButtonPressure = 0; + return; + } + + if (curMove.hitLevel != HITLEVEL_NULL) { + if (animTime > curMove.endFireTime) { + if (IsPlayer()) + currentAssoc->speed = 1.0f; + else + currentAssoc->speed = 0.8f; + } + + if (IsPlayer() && !nPlayerInComboMove) { + if (curMove.comboFollowOnTime > 0.0f && m_fightButtonPressure != 0 && animTime > curMove.comboFollowOnTime) { + + // Notice that it increases fight move index, because we're in combo! + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[++m_curFightMove].animId, 8.0f); + animAssoc->SetFinishCallback(FinishFightMoveCB, this); + animAssoc->SetCurrentTime(0.1f * animAssoc->hierarchy->totalLength); + m_fightButtonPressure = 0; + nPlayerInComboMove = 1; + } + } + } else { + if (curMove.startFireTime > 0.0f && m_curFightMove != FIGHTMOVE_SHUFFLE_F && animTime > curMove.startFireTime) { + if (IsPlayer()) + currentAssoc->speed = 1.3f; + else + currentAssoc->speed = 0.8f; + } + } + } else if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + EndFight(ENDFIGHT_FAST); + + } else if (m_fightButtonPressure != 0) { + bool canAffectMultiplePeople = true; + nextAngle = m_fRotationCur; + bool kickGround = false; + float angleForGroundKick = 0.0f; + CPed *pedOnGround = nil; + + Say(SOUND_PED_ATTACK); + + if (IsPlayer()) { + canRoundhouse = false; + punchOnly = false; + canKick = true; + nextFightMove = (m_fightButtonPressure > 190 ? FIGHTMOVE_HEADBUTT : FIGHTMOVE_KNEE); + hasShoppingBags = false; + canKneeHead = true; + nPlayerInComboMove = 0; + } else { + nextFightMove = (m_fightButtonPressure > 120 ? FIGHTMOVE_HEADBUTT : FIGHTMOVE_KNEE); + uint16 pedFeatures = m_pedStats->m_flags; + punchOnly = pedFeatures & STAT_PUNCH_ONLY; + canRoundhouse = pedFeatures & STAT_CAN_ROUNDHOUSE; + canKneeHead = pedFeatures & STAT_CAN_KNEE_HEAD; + canKick = pedFeatures & STAT_CAN_KICK; + hasShoppingBags = pedFeatures & STAT_SHOPPING_BAGS; + } + + // Attack isn't scripted, find the victim + if (IsPlayer() || !m_pedInObjective) { + + for (int i = 0; i < m_numNearPeds; i++) { + + CPed *nearPed = m_nearPeds[i]; + float nearPedDist = (nearPed->GetPosition() - GetPosition()).Magnitude(); + if (nearPedDist < 3.0f) { + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + nearPed->GetPosition().x, nearPed->GetPosition().y, + GetPosition().x, GetPosition().y); + + nextAngle = CGeneral::LimitRadianAngle(angleToFace); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + + float neededTurn = Abs(nextAngle - m_fRotationCur); + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + if (!nearPed->OnGroundOrGettingUp()) { + + if (nearPedDist < 0.8f && neededTurn < DEGTORAD(75.0f) && canKneeHead) { + canAffectMultiplePeople = false; + } else if (nearPedDist >= 1.3f || neededTurn >= DEGTORAD(55.0f) || hasShoppingBags) { + + if (nearPedDist < 1.7f + && neededTurn < DEGTORAD(35.0f) + && (canKick || hasShoppingBags)) { + + nextFightMove = FIGHTMOVE_KICK; + if (hasShoppingBags) { + nextFightMove = FIGHTMOVE_ROUNDHOUSE; + } else if (canRoundhouse && CGeneral::GetRandomNumber() & 1) { + nextFightMove = FIGHTMOVE_ROUNDHOUSE; + } + canAffectMultiplePeople = false; + } else if (nearPedDist < 2.0f && neededTurn < DEGTORAD(30.0f) && canKick) { + canAffectMultiplePeople = false; + nextFightMove = FIGHTMOVE_LONGKICK; + } else if (neededTurn < DEGTORAD(30.0f)) { + goForward = true; + } + } else { + nextFightMove += 2; // Makes it 6 or 7 + if (punchOnly) + nextFightMove = FIGHTMOVE_PUNCHJAB; + + canAffectMultiplePeople = false; + } + } else if (!CGame::nastyGame + || nearPedDist >= 1.3f + || neededTurn >= DEGTORAD(55.0f) + || punchOnly) { + + if (nearPedDist > 0.8f + && nearPedDist < 3.0f + && neededTurn < DEGTORAD(30.0f)) { + goForward = true; + } + + } else if (nearPed->m_nPedState != PED_DEAD || pedOnGround) { + if (!nearPed->IsPedHeadAbovePos(-0.3f)) { + canAffectMultiplePeople = false; + nextFightMove = FIGHTMOVE_GROUNDKICK; + } + + } else { + pedOnGround = nearPed; + kickGround = true; + angleForGroundKick = nextAngle; + } + } + + if (!canAffectMultiplePeople) { + m_fRotationDest = nextAngle; + if (IsPlayer()) { + m_fRotationCur = m_fRotationDest; + m_lookTimer = 0; + SetLookFlag(nearPed, true); + SetLookTimer(1500); + } + break; + } + } + } else { + // Because we're in a scripted fight with some particular ped. + canAffectMultiplePeople = false; + + float fightingPedDist = (m_pedInObjective->GetPosition() - GetPosition()).Magnitude(); + if (hasShoppingBags) { + if (fightingPedDist >= 1.7f) + nextFightMove = FIGHTMOVE_SHUFFLE_F; + else + nextFightMove = FIGHTMOVE_ROUNDHOUSE; + + } else if (punchOnly) { + if (fightingPedDist >= 1.3f) + nextFightMove = FIGHTMOVE_SHUFFLE_F; + else + nextFightMove = FIGHTMOVE_PUNCHJAB; + + } else if (fightingPedDist >= 3.0f) { + nextFightMove = FIGHTMOVE_STDPUNCH; + + } else { + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + m_pedInObjective->GetPosition().x, + m_pedInObjective->GetPosition().y, + GetPosition().x, + GetPosition().y); + + nextAngle = CGeneral::LimitRadianAngle(angleToFace); + m_fRotationDest = nextAngle; + m_fRotationCur = m_fRotationDest; + if (!m_pedInObjective->OnGroundOrGettingUp()) { + + if (fightingPedDist >= 0.8f || !canKneeHead) { + + if (fightingPedDist >= 1.3f) { + + if (fightingPedDist < 1.7f && canKick) { + nextFightMove = FIGHTMOVE_KICK; + if (canRoundhouse && CGeneral::GetRandomNumber() & 1) + nextFightMove = FIGHTMOVE_ROUNDHOUSE; + + } else if (fightingPedDist < 2.0f && canKick) { + nextFightMove += 5; // Makes it 9 or 10 + + } else { + nextFightMove = FIGHTMOVE_SHUFFLE_F; + + } + } else { + nextFightMove += 2; // Makes it 6 or 7 + } + } + } else if (!CGame::nastyGame + || fightingPedDist >= 1.3f + || m_pedInObjective->IsPlayer() + || m_pedInObjective->m_nPedState != PED_DEAD && m_pedInObjective->IsPedHeadAbovePos(-0.3f)) { + nextFightMove = FIGHTMOVE_IDLE; + } else { + nextFightMove = FIGHTMOVE_GROUNDKICK; + } + } + } + + if (canAffectMultiplePeople) { + if (kickGround && IsPlayer()) { + m_fRotationDest = angleForGroundKick; + nextFightMove = FIGHTMOVE_GROUNDKICK; + m_fRotationCur = m_fRotationDest; + m_lookTimer = 0; + SetLookFlag(pedOnGround, true); + SetLookTimer(1500); + } else if (goForward) { + nextFightMove = FIGHTMOVE_SHUFFLE_F; + } else { + nextFightMove = FIGHTMOVE_STDPUNCH; + } + } + + if (nextFightMove != FIGHTMOVE_IDLE) { + m_curFightMove = nextFightMove; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 4.0f); + + animAssoc->SetFinishCallback(FinishFightMoveCB, this); + if (m_fightState == FIGHTSTATE_MOVE_FINISHED && animAssoc->currentTime != 0.0f) { + animAssoc->SetCurrentTime(0.0f); + animAssoc->SetRun(); + } + m_fightButtonPressure = 0; + } + m_fightState = FIGHTSTATE_NO_MOVE; + } else if (m_takeAStepAfterAttack && m_curFightMove != FIGHTMOVE_SHUFFLE_F +#ifndef FIX_BUGS + && CheckForPedsOnGroundToAttack(this, nil) == 4) { +#else + && CheckForPedsOnGroundToAttack(this, nil) == PED_IN_FRONT_OF_ATTACKER) { +#endif + m_curFightMove = FIGHTMOVE_SHUFFLE_F; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), tFightMoves[m_curFightMove].animId); + + if (animAssoc) { + animAssoc->SetCurrentTime(0.0f); + animAssoc->blendDelta = 4.0f; + animAssoc->SetRun(); + } else { + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 32.0f); + } + animAssoc->SetFinishCallback(FinishFightMoveCB, this); + m_fightState = FIGHTSTATE_NO_MOVE; + m_fightButtonPressure = 0; + m_takeAStepAfterAttack = false; + + } else if (m_takeAStepAfterAttack) { + EndFight(ENDFIGHT_FAST); + + } else if (m_curFightMove == FIGHTMOVE_IDLE) { + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + EndFight(ENDFIGHT_NORMAL); + } + + } else { + m_curFightMove = FIGHTMOVE_IDLE; + if (IsPlayer()) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 500; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 2000; + } +} + +void +CPed::EndFight(uint8 endType) +{ + if (m_nPedState != PED_FIGHT) + return; + + m_curFightMove = FIGHTMOVE_NULL; + RestorePreviousState(); + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); + if (animAssoc) + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + + switch (endType) { + case ENDFIGHT_NORMAL: + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 8.0f); + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_2IDLE, 8.0f); + break; + case ENDFIGHT_WITH_A_STEP: + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 1.0f); + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_STARTWALK, 8.0f); + break; + case ENDFIGHT_FAST: + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 8.0f); + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_2IDLE, 8.0f)->speed = 2.0f; + break; + default: + break; + } + m_nWaitTimer = 0; +} + + +void +CPed::PlayHitSound(CPed *hitTo) +{ + // That was very complicated to reverse for me... + // First index is our fight move ID (from 1 to 12, total 12), second is the one of we fight with (from 13 to 22, total 10). + enum { + S33 = SOUND_FIGHT_PUNCH_33, + S34 = SOUND_FIGHT_KICK_34, + S35 = SOUND_FIGHT_HEADBUTT_35, + S36 = SOUND_FIGHT_PUNCH_36, + S37 = SOUND_FIGHT_PUNCH_37, + S38 = SOUND_FIGHT_CLOSE_PUNCH_38, + S39 = SOUND_FIGHT_PUNCH_39, + S40 = SOUND_FIGHT_PUNCH_OR_KICK_BELOW_40 , + S41 = SOUND_FIGHT_PUNCH_41, + S42 = SOUND_FIGHT_PUNCH_FROM_BEHIND_42, + S43 = SOUND_FIGHT_KNEE_OR_KICK_43, + S44 = SOUND_FIGHT_KICK_44, + NO_SND = SOUND_NO_SOUND + }; + uint16 hitSoundsByFightMoves[12][10] = { + {S39,S42,S43,S43,S39,S39,S39,S39,S39,S42}, + {NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND}, + {NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND}, + {S39,S39,S39,S39,S33,S43,S39,S39,S39,S39}, + {S39,S39,S39,S39,S35,S39,S38,S38,S39,S39}, + {S39,S39,S39,S39,S33,S39,S41,S36,S39,S39}, + {S39,S39,S39,S39,S37,S40,S38,S38,S39,S39}, + {S39,S39,S39,S39,S34,S43,S44,S37,S39,S39}, + {S39,S39,S39,S39,S34,S43,S44,S37,S39,S39}, + {S39,S39,S39,S39,S34,S43,S44,S37,S39,S40}, + {S39,S39,S39,S39,S33,S39,S41,S37,S39,S40}, + {S39,S39,S39,S39,S39,S39,S39,S39,S33,S33} + }; + + // This is why first dimension is between FightMove 1 and 12. + if (m_curFightMove == FIGHTMOVE_NULL || m_curFightMove >= FIGHTMOVE_HITFRONT) + return; + + uint16 soundId; + + // And this is why second dimension is between 13 and 22. + if (hitTo->m_curFightMove > FIGHTMOVE_GROUNDKICK && hitTo->m_curFightMove < FIGHTMOVE_IDLE2NORM) { + soundId = hitSoundsByFightMoves[m_curFightMove - FIGHTMOVE_STDPUNCH][hitTo->m_curFightMove - FIGHTMOVE_HITFRONT]; + + } else { + if (hitTo->m_nPedState == PED_DEAD || hitTo->UseGroundColModel()) { + soundId = hitSoundsByFightMoves[m_curFightMove - FIGHTMOVE_STDPUNCH][FIGHTMOVE_HITONFLOOR - FIGHTMOVE_HITFRONT]; + } else { + soundId = hitSoundsByFightMoves[m_curFightMove - FIGHTMOVE_STDPUNCH][FIGHTMOVE_HITFRONT - FIGHTMOVE_HITFRONT]; + } + } + + if (soundId != NO_SND) + DMAudio.PlayOneShot(m_audioEntityId, soundId, 0.0f); +} + +bool +CPed::FightStrike(CVector &touchedNodePos) +{ + CColModel *ourCol; + CVector attackDistance; + ePedPieceTypes closestPedPiece = PEDPIECE_TORSO; + float maxDistanceToBeBeaten; + CPed *nearPed; + int state = m_fightState; + bool pedFound = false; + + if (state == FIGHTSTATE_JUST_ATTACKED) + return false; + + // Pointless code + if (state > FIGHTSTATE_NO_MOVE) + attackDistance = touchedNodePos - m_vecHitLastPos; + + for (int i = 0; i < m_numNearPeds; i++) { + nearPed = m_nearPeds[i]; + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) + maxDistanceToBeBeaten = nearPed->GetBoundRadius() + tFightMoves[m_curFightMove].strikeRadius + 0.1f; + else + maxDistanceToBeBeaten = nearPed->GetBoundRadius() + tFightMoves[m_curFightMove].strikeRadius; + + if (nearPed->bUsesCollision || nearPed->m_nPedState == PED_DEAD) { + CVector nearPedCentre; + nearPed->GetBoundCentre(nearPedCentre); + CVector potentialAttackDistance = nearPedCentre - touchedNodePos; + + // He can beat us + if (sq(maxDistanceToBeBeaten) > potentialAttackDistance.MagnitudeSqr()) { + +#ifdef PED_SKIN + // Have to animate a skinned clump because the initial col model is useless + if(IsClumpSkinned(GetClump())) + ourCol = ((CPedModelInfo *)CModelInfo::GetModelInfo(GetModelIndex()))->AnimatePedColModelSkinned(GetClump()); + else +#endif + if (nearPed->OnGround() || !nearPed->IsPedHeadAbovePos(-0.3f)) { + ourCol = &CTempColModels::ms_colModelPedGroundHit; + } else { +#ifdef ANIMATE_PED_COL_MODEL + ourCol = CPedModelInfo::AnimatePedColModel(((CPedModelInfo *)CModelInfo::GetModelInfo(GetModelIndex()))->GetHitColModel(), + RpClumpGetFrame(GetClump())); +#else + ourCol = ((CPedModelInfo*)CModelInfo::GetModelInfo(m_modelIndex))->GetHitColModel(); +#endif + } + + for (int j = 0; j < ourCol->numSpheres; j++) { + attackDistance = nearPed->GetPosition() + ourCol->spheres[j].center; + attackDistance -= touchedNodePos; + CColSphere *ourPieces = ourCol->spheres; + float maxDistanceToBeat = ourPieces[j].radius + tFightMoves[m_curFightMove].strikeRadius; + + // We can beat him too + if (sq(maxDistanceToBeat) > attackDistance.MagnitudeSqr()) { + pedFound = true; + closestPedPiece = (ePedPieceTypes) ourPieces[j].piece; + break; + } + } + } + } + if (pedFound) + break; + } + + if (pedFound) { + if (nearPed->IsPlayer() && nearPed->m_nPedState == PED_GETUP) + return false; + + float oldVictimHealth = nearPed->m_fHealth; + CVector bloodPos = 0.5f * attackDistance + touchedNodePos; + int damageMult = tFightMoves[m_curFightMove].damage * ((CGeneral::GetRandomNumber() & 1) + 2) + 1; + + CVector2D diff (GetPosition() - nearPed->GetPosition()); + int direction = nearPed->GetLocalDirection(diff); + if (IsPlayer()) { + if (((CPlayerPed*)this)->m_bAdrenalineActive) + damageMult = 20; + } else { + damageMult *= m_pedStats->m_attackStrength; + } + + // Change direction if we used kick. + if (m_curFightMove == FIGHTMOVE_KICK) { + if (CGeneral::GetRandomNumber() & 1) { + direction++; + if (direction > 3) + direction -= 4; + } + } + nearPed->ReactToAttack(this); + + // Mostly unused. if > 5, ANIM_HIT_WALK will be run, that's it. + int unk2; + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && !nearPed->IsPlayer()) + unk2 = 101; + else + unk2 = damageMult; + + nearPed->StartFightDefend(direction, tFightMoves[m_curFightMove].hitLevel, unk2); + PlayHitSound(nearPed); + m_fightState = FIGHTSTATE_JUST_ATTACKED; + RpAnimBlendClumpGetAssociation(GetClump(), tFightMoves[m_curFightMove].animId)->speed = 0.6f; + if (!nearPed->DyingOrDead()) { + nearPed->InflictDamage(this, WEAPONTYPE_UNARMED, damageMult * 3.0f, closestPedPiece, direction); + } + + if (CGame::nastyGame + && tFightMoves[m_curFightMove].hitLevel > HITLEVEL_MEDIUM + && nearPed->m_nPedState == PED_DIE + && nearPed->GetIsOnScreen()) { + + // Just for blood particle. We will restore it below. + attackDistance /= (10.0f * attackDistance.Magnitude()); + for(int i=0; i<4; i++) { + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, attackDistance, nil, 0.0f, 0, 0, 0, 0); + } + } + if (!nearPed->OnGround()) { + float curVictimHealth = nearPed->m_fHealth; + if (curVictimHealth > 0.0f + && (curVictimHealth < 40.0f && oldVictimHealth > 40.0f && !nearPed->IsPlayer() + || nearPed->m_fHealth < 20.0f && oldVictimHealth > 20.0f + || GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && IsPlayer() + || nearPed->m_pedStats->m_flags & STAT_ONE_HIT_KNOCKDOWN)) { + + nearPed->SetFall(0, (AnimationId)(direction + ANIM_STD_HIGHIMPACT_FRONT), 0); + if (nearPed->m_nPedState == PED_FALL) + nearPed->bIsStanding = false; + } + } + if (nearPed->m_nPedState == PED_DIE || !nearPed->bIsStanding) { + attackDistance = nearPed->GetPosition() - GetPosition(); + attackDistance.Normalise(); + attackDistance.z = 1.0f; + nearPed->bIsStanding = false; + + float moveMult; + if (m_curFightMove == FIGHTMOVE_GROUNDKICK) { + moveMult = Min(damageMult * 0.6f, 4.0f); + } else { + if (nearPed->m_nPedState != PED_DIE || damageMult >= 20) { + moveMult = damageMult; + } else { + moveMult = Min(damageMult * 2.0f, 14.0f); + } + } + + nearPed->ApplyMoveForce(moveMult * 0.6f * attackDistance); + } + CEventList::RegisterEvent(nearPed->m_nPedType == PEDTYPE_COP ? EVENT_ASSAULT_POLICE : EVENT_ASSAULT, EVENT_ENTITY_PED, nearPed, this, 2000); + } + + if (m_fightState == FIGHTSTATE_NO_MOVE) + m_fightState = FIGHTSTATE_1; + + m_vecHitLastPos = touchedNodePos; + return false; +} + +void +CPed::FinishFightMoveCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (tFightMoves[ped->m_curFightMove].animId == animAssoc->animId) { + ped->m_fightState = FIGHTSTATE_MOVE_FINISHED; + animAssoc->blendDelta = -1000.0f; + } +} + +void +CPed::LoadFightData(void) +{ + float startFireTime, endFireTime, comboFollowOnTime, strikeRadius; + int damage, flags; + char line[256], moveName[32], animName[32], hitLevel; + int moveId = 0; + + CAnimBlendAssociation *animAssoc; + + size_t bp, buflen; + int lp, linelen; + + buflen = CFileMgr::LoadFile("DATA\\fistfite.dat", work_buff, sizeof(work_buff), "r"); + + for (bp = 0; bp < buflen; ) { + // read file line by line + for (linelen = 0; work_buff[bp] != '\n' && bp < buflen; bp++) { + line[linelen++] = work_buff[bp]; + } + bp++; + line[linelen] = '\0'; + + // skip white space + for (lp = 0; line[lp] <= ' ' && line[lp] != '\0'; lp++); + + if (line[lp] == '\0' || + line[lp] == '#') + continue; + + sscanf( + &line[lp], + "%s %f %f %f %f %c %s %d %d", + moveName, + &startFireTime, + &endFireTime, + &comboFollowOnTime, + &strikeRadius, + &hitLevel, + animName, + &damage, + &flags); + + if (strncmp(moveName, "ENDWEAPONDATA", 13) == 0) + return; + + tFightMoves[moveId].startFireTime = startFireTime / 30.0f; + tFightMoves[moveId].endFireTime = endFireTime / 30.0f; + tFightMoves[moveId].comboFollowOnTime = comboFollowOnTime / 30.0f; + tFightMoves[moveId].strikeRadius = strikeRadius; + tFightMoves[moveId].damage = damage; + tFightMoves[moveId].flags = flags; + + switch (hitLevel) { + case 'G': + tFightMoves[moveId].hitLevel = HITLEVEL_GROUND; + break; + case 'H': + tFightMoves[moveId].hitLevel = HITLEVEL_HIGH; + break; + case 'L': + tFightMoves[moveId].hitLevel = HITLEVEL_LOW; + break; + case 'M': + tFightMoves[moveId].hitLevel = HITLEVEL_MEDIUM; + break; + case 'N': + tFightMoves[moveId].hitLevel = HITLEVEL_NULL; + break; + default: + break; + } + + if (strcmp(animName, "null") != 0) { + animAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, animName); + tFightMoves[moveId].animId = (AnimationId)animAssoc->animId; + } else { + tFightMoves[moveId].animId = ANIM_STD_WALK; + } + moveId++; + } +} + +void +CPed::SetInvestigateEvent(eEventType event, CVector2D pos, float distanceToCountDone, uint16 time, float angle) +{ + if (!IsPedInControl() || CharCreatedBy == MISSION_CHAR) + return; + + SetStoredState(); + bFindNewNodeAfterStateRestore = false; + SetPedState(PED_INVESTIGATE); + m_chatTimer = CTimer::GetTimeInMilliseconds() + time; + m_eventType = event; + m_eventOrThreat = pos; + m_distanceToCountSeekDone = distanceToCountDone; + m_fAngleToEvent = angle; + + if (m_eventType >= EVENT_ICECREAM) + m_lookTimer = 0; + else + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSCOWER, 4.0f); + +} + +void +CPed::InvestigateEvent(void) +{ + CAnimBlendAssociation *animAssoc; + AnimationId animToPlay; + AssocGroupId animGroup; + + if (m_nWaitState == WAITSTATE_TURN180) + return; + + if (CTimer::GetTimeInMilliseconds() > m_chatTimer) { + + if (m_chatTimer) { + if (m_eventType < EVENT_ASSAULT_NASTYWEAPON) + SetWaitState(WAITSTATE_TURN180, nil); + + m_chatTimer = 0; + } else { + ClearInvestigateEvent(); + } + return; + } + + CVector2D vecDist = m_eventOrThreat - GetPosition(); + float distSqr = vecDist.MagnitudeSqr(); + if (sq(m_distanceToCountSeekDone) >= distSqr) { + + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(vecDist.x, vecDist.y, 0.0f, 0.0f); + SetMoveState(PEDMOVE_STILL); + + switch (m_eventType) { + case EVENT_DEAD_PED: + case EVENT_HIT_AND_RUN: + case EVENT_HIT_AND_RUN_COP: + + if (CTimer::GetTimeInMilliseconds() > m_lookTimer) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROADCROSS); + + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (m_pEventEntity) + SetLookFlag(m_pEventEntity, true); + + SetLookTimer(CGeneral::GetRandomNumberInRange(1500, 4000)); + + } else if (CGeneral::GetRandomNumber() & 3) { + ClearLookFlag(); + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ROADCROSS, 4.0f); + + SetLookTimer(CGeneral::GetRandomNumberInRange(1000, 2500)); + Say(SOUND_PED_CHAT_EVENT); + + } else { + ClearInvestigateEvent(); + } + } + break; + case EVENT_FIRE: + case EVENT_EXPLOSION: + + if (bHasACamera && CTimer::GetTimeInMilliseconds() > m_lookTimer) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_CAM); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + + if (animAssoc && animAssoc->animId == ANIM_STD_IDLE_CAM) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(1000, 2500)); + + } else if (CGeneral::GetRandomNumber() & 3) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_CAM, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(2500, 5000)); + Say(SOUND_PED_CHAT_EVENT); + + } else { + m_chatTimer = 0; + } + + } else if (CTimer::GetTimeInMilliseconds() > m_lookTimer) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_HBHB); + + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_XPRESS_SCRATCH); + + if (animAssoc && animAssoc->animId == ANIM_STD_IDLE) { + if (CGeneral::GetRandomNumber() & 1) + animToPlay = ANIM_STD_IDLE_HBHB; + else + animToPlay = ANIM_STD_XPRESS_SCRATCH; + + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animToPlay, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(1500, 4000)); + + } else if (animAssoc && animAssoc->animId == ANIM_STD_IDLE_HBHB) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (CGeneral::GetRandomNumber() & 1) { + animToPlay = ANIM_STD_IDLE; + animGroup = m_animGroup; + } else { + animToPlay = ANIM_STD_XPRESS_SCRATCH; + animGroup = ASSOCGRP_STD; + } + + CAnimManager::BlendAnimation(GetClump(), animGroup, animToPlay, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(1000, 2500)); + + } else { + if (CGeneral::GetRandomNumber() & 1) { + animToPlay = ANIM_STD_IDLE; + animGroup = m_animGroup; + } else { + animToPlay = ANIM_STD_IDLE_HBHB; + animGroup = ASSOCGRP_STD; + } + + CAnimManager::BlendAnimation(GetClump(), animGroup, animToPlay, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(1000, 2500)); + } + Say(SOUND_PED_CHAT_EVENT); + } + break; + case EVENT_ICECREAM: + case EVENT_SHOPSTALL: + + m_fRotationDest = m_fAngleToEvent; + if (CTimer::GetTimeInMilliseconds() > m_lookTimer) { + + if (m_lookTimer) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROADCROSS); + + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (m_eventType == EVENT_ICECREAM) + animToPlay = ANIM_STD_CHAT; + else + animToPlay = ANIM_STD_XPRESS_SCRATCH; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animToPlay,4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(2000, 5000)); + + } else { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CHAT); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + ClearInvestigateEvent(); + } else { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_XPRESS_SCRATCH); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + ClearInvestigateEvent(); + } + } + } else { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ROADCROSS, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(1000, 2500)); + } + } + break; + default: + return; + } + } else { + m_vecSeekPos.x = m_eventOrThreat.x; + m_vecSeekPos.y = m_eventOrThreat.y; + m_vecSeekPos.z = GetPosition().z; + Seek(); + + if (m_eventType < EVENT_ICECREAM) { + if (sq(5.0f + m_distanceToCountSeekDone) < distSqr) { + SetMoveState(PEDMOVE_RUN); + return; + } + } + if (m_eventType <= EVENT_EXPLOSION || m_eventType >= EVENT_SHOPSTALL) { + SetMoveState(PEDMOVE_WALK); + return; + } + if (distSqr > sq(1.2f)) { + SetMoveState(PEDMOVE_WALK); + return; + } + + for (int i = 0; i < m_numNearPeds; i++) { + if ((m_eventOrThreat - m_nearPeds[i]->GetPosition()).MagnitudeSqr() < sq(0.4f)) { + SetMoveState(PEDMOVE_STILL); + return; + } + } + + SetMoveState(PEDMOVE_WALK); + } +} + +void +CPed::ClearInvestigateEvent(void) +{ + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROADCROSS); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_XPRESS_SCRATCH); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_HBHB); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CHAT); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + if (m_eventType > EVENT_EXPLOSION) + m_chatTimer = CTimer::GetTimeInMilliseconds() + 15000; + + bGonnaInvestigateEvent = false; + m_pEventEntity = nil; + ClearLookFlag(); + RestorePreviousState(); + if(m_nMoveState == PEDMOVE_NONE || m_nMoveState == PEDMOVE_STILL) + SetMoveState(PEDMOVE_WALK); +} + +bool +CPed::InflictDamage(CEntity *damagedBy, eWeaponType method, float damage, ePedPieceTypes pedPiece, uint8 direction) +{ + CPlayerPed *player = FindPlayerPed(); + float dieDelta = 4.0f; + float dieSpeed = 0.0f; + AnimationId dieAnim = ANIM_STD_KO_FRONT; + bool headShot = false; + bool willLinger = false; + int random; + + if (player == this) { + if (!player->m_bCanBeDamaged) + return false; + + player->AnnoyPlayerPed(false); + } + + if (DyingOrDead()) + return false; + + if (!bUsesCollision && method != WEAPONTYPE_DROWNING) + return false; + + if (bOnlyDamagedByPlayer && damagedBy != player && damagedBy != FindPlayerVehicle() && + method != WEAPONTYPE_DROWNING && method != WEAPONTYPE_EXPLOSION) + return false; + + float healthImpact; + if (IsPlayer()) + healthImpact = damage * 0.33f; + else + healthImpact = damage * m_pedStats->m_defendWeakness; + + bool detectDieAnim = true; + if (m_nPedState == PED_FALL || m_nPedState == PED_GETUP) { + if (!IsPedHeadAbovePos(-0.3f)) { + if (RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL)) + dieAnim = ANIM_STD_HIT_FLOOR_FRONT; + else + dieAnim = ANIM_STD_HIT_FLOOR; + dieDelta *= 2.0f; + dieSpeed = 0.5f; + detectDieAnim = false; + } else if (m_nPedState == PED_FALL) { + dieAnim = ANIM_STD_NUM; + detectDieAnim = false; + } + } + if (detectDieAnim) { + switch (method) { + case WEAPONTYPE_UNARMED: + if (bMeleeProof) + return false; + + if (m_nPedState == PED_FALL) { + if (IsPedHeadAbovePos(-0.3f)) { + dieAnim = ANIM_STD_NUM; + } else { + if (RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL)) + dieAnim = ANIM_STD_HIT_FLOOR_FRONT; + else + dieAnim = ANIM_STD_HIT_FLOOR; + dieDelta = dieDelta * 2.0f; + dieSpeed = 0.5f; + } + } else { + switch (direction) { + case 0: + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + break; + case 1: + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + break; + case 3: + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + } + break; + case WEAPONTYPE_BASEBALLBAT: + if (bMeleeProof) + return false; + + if (m_nPedState == PED_FALL) { + if (IsPedHeadAbovePos(-0.3f)) { + dieAnim = ANIM_STD_NUM; + } else { + if (RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL)) + dieAnim = ANIM_STD_HIT_FLOOR_FRONT; + else + dieAnim = ANIM_STD_HIT_FLOOR; + dieDelta = dieDelta * 2.0f; + dieSpeed = 0.5f; + } + } else { + switch (direction) { + case 0: + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + break; + case 1: + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + break; + case 3: + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + } + break; + case WEAPONTYPE_COLT45: + case WEAPONTYPE_UZI: + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_AK47: + case WEAPONTYPE_M16: + case WEAPONTYPE_SNIPERRIFLE: + if (bBulletProof) + return false; + + bool dontRemoveLimb; + if (IsPlayer() || bNoCriticalHits) + dontRemoveLimb = true; + else { + switch (method) { + case WEAPONTYPE_SNIPERRIFLE: + dontRemoveLimb = false; + break; + case WEAPONTYPE_M16: + dontRemoveLimb = false; + break; + case WEAPONTYPE_SHOTGUN: + dontRemoveLimb = CGeneral::GetRandomNumber() & 7; + break; + default: + dontRemoveLimb = CGeneral::GetRandomNumber() & 15; + break; + } + } + + if (dontRemoveLimb) { + if (method == WEAPONTYPE_SHOTGUN) { + switch (direction) { + case 0: + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + break; + case 1: + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + break; + case 3: + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + } else + dieAnim = ANIM_STD_KO_FRONT; + + willLinger = false; + } else { + switch (pedPiece) { + case PEDPIECE_TORSO: + willLinger = false; + dieAnim = ANIM_STD_KO_FRONT; + break; + case PEDPIECE_MID: + willLinger = false; + dieAnim = ANIM_STD_KO_SHOT_STOMACH; + break; + case PEDPIECE_LEFTARM: + dieAnim = ANIM_STD_KO_SHOT_ARM_L; + RemoveBodyPart(PED_UPPERARML, direction); + willLinger = true; + break; + case PEDPIECE_RIGHTARM: + dieAnim = ANIM_STD_KO_SHOT_ARM_R; + RemoveBodyPart(PED_UPPERARMR, direction); + willLinger = true; + break; + case PEDPIECE_LEFTLEG: + dieAnim = ANIM_STD_KO_SHOT_LEG_L; + RemoveBodyPart(PED_UPPERLEGL, direction); + willLinger = true; + break; + case PEDPIECE_RIGHTLEG: + dieAnim = ANIM_STD_KO_SHOT_LEG_R; + RemoveBodyPart(PED_UPPERLEGR, direction); + willLinger = true; + break; + case PEDPIECE_HEAD: + dieAnim = ANIM_STD_KO_SHOT_FACE; + RemoveBodyPart(PED_HEAD, direction); + headShot = true; + willLinger = true; + break; + default: + break; + } + } + break; + case WEAPONTYPE_ROCKETLAUNCHER: + case WEAPONTYPE_GRENADE: + case WEAPONTYPE_EXPLOSION: + if (bExplosionProof) + return false; + + if (CGame::nastyGame && !IsPlayer() && !bInVehicle && + 1.0f + healthImpact > m_fArmour + m_fHealth) { + + random = CGeneral::GetRandomNumber(); + if (random & 1) + RemoveBodyPart(PED_UPPERARML, direction); + if (random & 2) + RemoveBodyPart(PED_UPPERLEGR, direction); + if (random & 4) + RemoveBodyPart(PED_HEAD, direction); + if (random & 8) + RemoveBodyPart(PED_UPPERARMR, direction); + if (random & 0x10) + RemoveBodyPart(PED_UPPERLEGL, direction); + if (bBodyPartJustCameOff) + willLinger = true; + } + // fall through + case WEAPONTYPE_MOLOTOV: + if (bExplosionProof) + return false; + + switch (direction) { + case 0: + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + break; + case 1: + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + break; + case 3: + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + break; + case WEAPONTYPE_FLAMETHROWER: + if (bFireProof) + return false; + + dieAnim = ANIM_STD_KO_FRONT; + break; + case WEAPONTYPE_RAMMEDBYCAR: + case WEAPONTYPE_RUNOVERBYCAR: + if (bCollisionProof) + return false; + + random = CGeneral::GetRandomNumber() & 3; + switch (random) { + case 0: + if ((pedPiece != PEDPIECE_LEFTARM || random <= 1) + && (pedPiece != PEDPIECE_MID || random != 1)) { + if (pedPiece == PEDPIECE_RIGHTARM && random > 1 + || pedPiece == PEDPIECE_MID && random == 2) + + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + else + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + } else + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + + break; + case 1: + if (m_nPedState == PED_DIVE_AWAY) + dieAnim = ANIM_STD_SPINFORWARD_LEFT; + else + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + if ((pedPiece != PEDPIECE_LEFTARM || random <= 1) + && (pedPiece != PEDPIECE_MID || random != 1)) { + if ((pedPiece != PEDPIECE_RIGHTARM || random <= 1) + && (pedPiece != PEDPIECE_MID || random != 2)) { + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + } else { + dieAnim = ANIM_STD_SPINFORWARD_RIGHT; + } + } else + dieAnim = ANIM_STD_SPINFORWARD_LEFT; + break; + case 3: + if (m_nPedState == PED_DIVE_AWAY) + dieAnim = ANIM_STD_SPINFORWARD_RIGHT; + else + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + if (damagedBy) { + CVehicle *vehicle = (CVehicle*)damagedBy; + if (method == WEAPONTYPE_RAMMEDBYCAR) { + float vehSpeed = vehicle->m_vecMoveSpeed.Magnitude(); + dieDelta = 8.0f * vehSpeed + 4.0f; + } else { + float vehSpeed = vehicle->m_vecMoveSpeed.Magnitude(); + dieDelta = 12.0f * vehSpeed + 4.0f; + dieSpeed = 16.0f * vehSpeed + 1.0f; + } + } + break; + case WEAPONTYPE_DROWNING: + dieAnim = ANIM_STD_DROWN; + break; + case WEAPONTYPE_FALL: + if (bCollisionProof) + return false; + + switch (direction) { + case 0: + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + break; + case 1: + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + break; + case 3: + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + break; + default: + break; + } + } + + if (m_fArmour != 0.0f && method != WEAPONTYPE_DROWNING) { + if (player == this) + CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastArmourLoss = CTimer::GetTimeInMilliseconds(); + + if (healthImpact < m_fArmour) { + m_fArmour = m_fArmour - healthImpact; + healthImpact = 0.0f; + } else { + healthImpact = healthImpact - m_fArmour; + m_fArmour = 0.0f; + } + } + + if (healthImpact != 0.0f) { + if (player == this) + CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss = CTimer::GetTimeInMilliseconds(); + + m_lastWepDam = method; + } + + if (m_fHealth - healthImpact >= 1.0f && !willLinger) { + m_fHealth -= healthImpact; + return false; + } + + if (bInVehicle) { + if (method != WEAPONTYPE_DROWNING) { +#ifdef VC_PED_PORTS + if (m_pMyVehicle) { + if (m_pMyVehicle->IsCar() && m_pMyVehicle->pDriver == this) { + if (m_pMyVehicle->GetStatus() == STATUS_SIMPLE) { + m_pMyVehicle->SetStatus(STATUS_PHYSICS); + CCarCtrl::SwitchVehicleToRealPhysics(m_pMyVehicle); + } + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + m_pMyVehicle->AutoPilot.m_nTempAction = TEMPACT_HANDBRAKESTRAIGHT; + m_pMyVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2000; + } + if (m_pMyVehicle->CanPedExitCar()) { + SetObjective(OBJECTIVE_LEAVE_CAR_AND_DIE, m_pMyVehicle); + } else { + m_fHealth = 0.0f; + if (m_pMyVehicle && m_pMyVehicle->pDriver == this) { + SetRadioStation(); + m_pMyVehicle->SetStatus(STATUS_ABANDONED); + } + SetDie(dieAnim, dieDelta, dieSpeed); + /* + if (damagedBy == FindPlayerPed() && damagedBy != this) { + // PlayerInfo stuff + } + */ + } + for (int i = 0; i < ARRAY_SIZE(m_pMyVehicle->pPassengers); i++) { + CPed* passenger = m_pMyVehicle->pPassengers[i]; + if (passenger && passenger != this && damagedBy) + passenger->ReactToAttack(damagedBy); + } + + CPed *driverOfVeh = m_pMyVehicle->pDriver; + if (driverOfVeh && driverOfVeh != this && damagedBy) + driverOfVeh->ReactToAttack(damagedBy); + + if (damagedBy == FindPlayerPed() || damagedBy && damagedBy == FindPlayerVehicle()) { + CDarkel::RegisterKillByPlayer(this, method, headShot); + m_threatEntity = FindPlayerPed(); + } else { + CDarkel::RegisterKillNotByPlayer(this, method); + } + } +#endif + m_fHealth = 1.0f; + return false; + } + m_fHealth = 0.0f; + if (player == this) + m_pMyVehicle->SetStatus(STATUS_PLAYER_DISABLED); + + SetDie(ANIM_STD_NUM, 4.0f, 0.0f); + return true; + } else { + m_fHealth = 0.0f; + SetDie(dieAnim, dieDelta, dieSpeed); + + if (damagedBy == player || damagedBy && damagedBy == FindPlayerVehicle()) { + + // There are PlayerInfo stuff here in VC + CDarkel::RegisterKillByPlayer(this, method, headShot); + m_threatEntity = player; + } else { + CDarkel::RegisterKillNotByPlayer(this, method); + } + if (method == WEAPONTYPE_DROWNING) + bIsInTheAir = false; + + return true; + } +} + +static RwObject* +SetPedAtomicVisibilityCB(RwObject* object, void* data) +{ + if (data == nil) + RpAtomicSetFlags((RpAtomic*)object, 0); + return object; +} + +static RwFrame* +RecurseFrameChildrenVisibilityCB(RwFrame* frame, void* data) +{ + RwFrameForAllObjects(frame, SetPedAtomicVisibilityCB, data); + RwFrameForAllChildren(frame, RecurseFrameChildrenVisibilityCB, nil); + return frame; +} + +static RwObject* +CloneAtomicToFrameCB(RwObject *frame, void *data) +{ + RpAtomic *newAtomic = RpAtomicClone((RpAtomic*)frame); + RpAtomicSetFrame(newAtomic, (RwFrame*)data); + RpClumpAddAtomic(flyingClumpTemp, newAtomic); + CVisibilityPlugins::SetAtomicRenderCallback(newAtomic, nil); + return frame; +} + +static RwFrame* +RecurseFrameChildrenToCloneCB(RwFrame *frame, void *data) +{ + RwFrame *newFrame = RwFrameCreate(); + RwFrameAddChild((RwFrame*)data, newFrame); + RwFrameTransform(newFrame, RwFrameGetMatrix(frame), rwCOMBINEREPLACE); + RwFrameForAllObjects(frame, CloneAtomicToFrameCB, newFrame); + RwFrameForAllChildren(frame, RecurseFrameChildrenToCloneCB, newFrame); + return newFrame; +} + +void +CPed::RemoveBodyPart(PedNode nodeId, int8 direction) +{ + RwFrame *frame; + CVector pos; + + frame = m_pFrames[nodeId]->frame; + if (frame) { + if (CGame::nastyGame) { +#ifdef PED_SKIN + if(!IsClumpSkinned(GetClump())) +#endif + { +#ifdef DEBUGMENU + if (bPopHeadsOnHeadshot || nodeId != PED_HEAD) +#else + if (nodeId != PED_HEAD) +#endif + SpawnFlyingComponent(nodeId, direction); + + RecurseFrameChildrenVisibilityCB(frame, nil); + } + pos.x = 0.0f; + pos.y = 0.0f; + pos.z = 0.0f; + TransformToNode(pos, PED_HEAD); + + if (CEntity::GetIsOnScreen()) { + CParticle::AddParticle(PARTICLE_TEST, pos, + CVector(0.0f, 0.0f, 0.0f), + nil, 0.1f, 0, 0, 0, 0); + + for (int i = 0; i < 16; i++) { + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, + pos, + CVector(0.0f, 0.0f, 0.03f), + nil, 0.0f, 0, 0, 0, 0); + } + } + bBodyPartJustCameOff = true; + m_bodyPartBleeding = nodeId; + } + } else { + printf("Trying to remove ped component"); + } +} + +CObject* +CPed::SpawnFlyingComponent(int pedNode, int8 direction) +{ + if (CObject::nNoTempObjects >= NUMTEMPOBJECTS) + return nil; + +#ifdef PED_SKIN + assert(!IsClumpSkinned(GetClump())); +#endif + + CObject *obj = new CObject(); + if (!obj) + return nil; + + RwFrame *frame = RwFrameCreate(); + RpClump *clump = RpClumpCreate(); + RpClumpSetFrame(clump, frame); + RwMatrix *matrix = RwFrameGetLTM(m_pFrames[pedNode]->frame); + *RwFrameGetMatrix(frame) = *matrix; + + flyingClumpTemp = clump; + RwFrameForAllObjects(m_pFrames[pedNode]->frame, CloneAtomicToFrameCB, frame); + RwFrameForAllChildren(m_pFrames[pedNode]->frame, RecurseFrameChildrenToCloneCB, frame); + flyingClumpTemp = nil; + switch (pedNode) { + case PED_HEAD: + // So popping head would have wheel collision. They disabled it anyway + obj->SetModelIndexNoCreate(MI_CAR_WHEEL); + break; + case PED_UPPERARML: + case PED_UPPERARMR: + obj->SetModelIndexNoCreate(MI_BODYPARTB); + obj->SetCenterOfMass(0.25f, 0.0f, 0.0f); + break; + case PED_UPPERLEGL: + case PED_UPPERLEGR: + obj->SetModelIndexNoCreate(MI_BODYPARTA); + obj->SetCenterOfMass(0.4f, 0.0f, 0.0f); + break; + default: + break; + } + obj->RefModelInfo(GetModelIndex()); + obj->AttachToRwObject((RwObject*)clump); + obj->m_fMass = 15.0f; + obj->m_fTurnMass = 5.0f; + obj->m_fAirResistance = 0.99f; + obj->m_fElasticity = 0.03f; + obj->m_fBuoyancy = m_fMass*GRAVITY/0.75f; + obj->ObjectCreatedBy = TEMP_OBJECT; + obj->SetIsStatic(false); + obj->bIsPickup = false; + obj->m_nSpecialCollisionResponseCases = COLLRESPONSE_SMALLBOX; + + // life time - the more objects the are, the shorter this one will live + CObject::nNoTempObjects++; + if (CObject::nNoTempObjects > 20) + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 12000; + else if (CObject::nNoTempObjects > 10) + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 30000; + else + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 60000; + + CVector localForcePos, forceDir; + + if (direction == 2) { + obj->m_vecMoveSpeed = 0.03f * GetForward(); + obj->m_vecMoveSpeed.z = (CGeneral::GetRandomNumber() & 0x3F) * 0.001f; + obj->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + localForcePos = CVector(0.0f, 0.0f, 0.0f); + forceDir = GetForward(); + } else { + obj->m_vecMoveSpeed = -0.03f * GetForward(); + obj->m_vecMoveSpeed.z = (CGeneral::GetRandomNumber() & 0x3F) * 0.001f; + obj->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + localForcePos = CVector(0.0f, 0.0f, 0.0f); + forceDir = -GetForward(); + } + obj->ApplyTurnForce(forceDir, localForcePos); + CWorld::Add(obj); + + return obj; +} + +void +CPed::ApplyHeadShot(eWeaponType weaponType, CVector pos, bool evenOnPlayer) +{ + CVector pos2 = CVector( + pos.x, + pos.y, + pos.z + 0.1f + ); + + if (!IsPlayer() || evenOnPlayer) { + ++CStats::HeadsPopped; + + // BUG: This condition will always return true. Even fixing it won't work, because these states are unused. + // if (m_nPedState != PED_PASSENGER || m_nPedState != PED_TAXI_PASSENGER) { + SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + // } + + bBodyPartJustCameOff = true; + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 150; + + CParticle::AddParticle(PARTICLE_TEST, pos2, + CVector(0.0f, 0.0f, 0.0f), nil, 0.2f, 0, 0, 0, 0); + + if (CEntity::GetIsOnScreen()) { + for(int i=0; i < 32; i++) { + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, + pos2, CVector(0.0f, 0.0f, 0.03f), + nil, 0.0f, 0, 0, 0, 0); + } + + for (int i = 0; i < 16; i++) { + CParticle::AddParticle(PARTICLE_DEBRIS2, + pos2, + CVector(0.0f, 0.0f, 0.01f), + nil, 0.0f, 0, 0, 0, 0); + } + } + } +} + +uint8 +CPed::DoesLOSBulletHitPed(CColPoint &colPoint) +{ +#ifdef FIX_BUGS + return 1; +#else + uint8 retVal = 2; + + float headZ = GetNodePosition(PED_HEAD).z; + + if (m_nPedState == PED_FALL) + retVal = 1; + + float colZ = colPoint.point.z; + if (colZ < headZ) + retVal = 1; + + if (headZ + 0.2f <= colZ) + retVal = 0; + + return retVal; +#endif +} + +bool +CPed::IsPedHeadAbovePos(float zOffset) +{ + return zOffset + GetPosition().z < GetNodePosition(PED_HEAD).z; +} + +bool +CPed::PlacePedOnDryLand(void) +{ + float waterLevel = 0.0f; + CEntity *foundEnt = nil; + CColPoint foundCol; + float foundColZ; + + CWaterLevel::GetWaterLevelNoWaves(GetPosition().x, GetPosition().y, GetPosition().z, &waterLevel); + + CVector potentialGround = GetPosition(); + potentialGround.z = waterLevel; + + if (!CWorld::TestSphereAgainstWorld(potentialGround, 5.0f, nil, true, false, false, false, false, false)) + return false; + + CVector potentialGroundDist = gaTempSphereColPoints[0].point - GetPosition(); + potentialGroundDist.z = 0.0f; + potentialGroundDist.Normalise(); + + CVector posToCheck = 0.5f * potentialGroundDist + gaTempSphereColPoints[0].point; + posToCheck.z = 3.0f + waterLevel; + + if (CWorld::ProcessVerticalLine(posToCheck, waterLevel - 1.0f, foundCol, foundEnt, true, true, false, true, false, false, nil)) { + foundColZ = foundCol.point.z; + if (foundColZ >= waterLevel) { + posToCheck.z = 0.8f + foundColZ; + SetPosition(posToCheck); + bIsStanding = true; + bWasStanding = true; + return true; + } + } + + posToCheck = 5.0f * potentialGroundDist + GetPosition(); + posToCheck.z = 3.0f + waterLevel; + + if (!CWorld::ProcessVerticalLine(posToCheck, waterLevel - 1.0f, foundCol, foundEnt, true, true, false, true, false, false, nil)) + return false; + + foundColZ = foundCol.point.z; + if (foundColZ < waterLevel) + return false; + + posToCheck.z = 0.8f + foundColZ; + SetPosition(posToCheck); + bIsStanding = true; + bWasStanding = true; + return true; +} + +void +CPed::CollideWithPed(CPed *collideWith) +{ + CAnimBlendAssociation *animAssoc; + AnimationId animToPlay; + + bool weAreMissionChar = CharCreatedBy == MISSION_CHAR; + bool heIsMissionChar = collideWith->CharCreatedBy == MISSION_CHAR; + CVector posDiff = collideWith->GetPosition() - GetPosition(); + int waitTime = 0; + + if (weAreMissionChar || !collideWith->IsPlayer() || collideWith->m_nPedState != PED_MAKE_CALL) { + bool weDontLookToHim = DotProduct(posDiff, GetForward()) > 0.0f; + bool heLooksToUs = DotProduct(posDiff, collideWith->GetForward()) < 0.0f; + + if (m_nMoveState != PEDMOVE_NONE && m_nMoveState != PEDMOVE_STILL) { + + if ((!IsPlayer() || ((CPlayerPed*)this)->m_fMoveSpeed <= 1.8f) + && (IsPlayer() || heIsMissionChar && weAreMissionChar || m_nMoveState != PEDMOVE_RUN && m_nMoveState != PEDMOVE_SPRINT +#ifdef VC_PED_PORTS + || m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION && m_pedInObjective == collideWith + || collideWith->m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION && collideWith->m_pedInObjective == this +#endif + )) { + + if (m_objective != OBJECTIVE_FOLLOW_CHAR_IN_FORMATION && m_objective != OBJECTIVE_GOTO_CHAR_ON_FOOT) { + + if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { + + if (heIsMissionChar || !weAreMissionChar && collideWith->m_nMoveState != PEDMOVE_STILL) { + + if (weAreMissionChar && (m_nPedState == PED_SEEK_POS || m_nPedState == PED_SEEK_ENTITY)) { + + if (collideWith->m_nMoveState != PEDMOVE_STILL + && (!collideWith->IsPlayer() || collideWith->IsPlayer() && CPad::GetPad(0)->ArePlayerControlsDisabled())) { + float seekPosDist = (GetPosition() - m_vecSeekPos).MagnitudeSqr2D(); + float heAndSeekPosDist = (collideWith->GetPosition() - m_vecSeekPos).MagnitudeSqr2D(); + + if (seekPosDist <= heAndSeekPosDist) { + waitTime = 1000; + collideWith->SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, &waitTime); + collideWith->m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + waitTime; + } else { + waitTime = 500; + SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, &waitTime); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + waitTime; + } + } else if (collideWith->m_nMoveState == PEDMOVE_STILL) { + SetDirectionToWalkAroundObject(collideWith); + } + } else { + if (weAreMissionChar || m_pedStats->m_fear <= 100 - collideWith->m_pedStats->m_temper + || (collideWith->IsPlayer() || collideWith->m_nMoveState == PEDMOVE_NONE || collideWith->m_nMoveState == PEDMOVE_STILL) && + (!collideWith->IsPlayer() || ((CPlayerPed*)collideWith)->m_fMoveSpeed <= 1.0f)) { + SetDirectionToWalkAroundObject(collideWith); + if (!weAreMissionChar) + Say(SOUND_PED_CHAT); + } else { + SetEvasiveStep(collideWith, 2); + } + } + } else { + if (m_pedStats->m_temper <= m_pedStats->m_fear + || GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED + || weAreMissionChar + || collideWith->m_nPedType == PEDTYPE_CIVFEMALE + || collideWith->m_nPedType == m_nPedType + || collideWith->GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + SetDirectionToWalkAroundObject(collideWith); + Say(SOUND_PED_CHAT); + } else { + TurnBody(); + SetAttack(collideWith); +#ifdef VC_PED_PORTS + m_fRotationCur = 0.3f + m_fRotationCur; + m_fRotationDest = m_fRotationCur; +#endif + } + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(250, 450); + } + } + } else { +#ifdef VC_PED_PORTS + if (m_pedInObjective && (collideWith == m_pedInObjective || collideWith->m_pedInObjective == m_pedInObjective) && CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { +#else + if (m_pedInObjective && collideWith == m_pedInObjective && CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { +#endif + if (heLooksToUs) { + SetEvasiveStep(collideWith, 1); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 3000; + } + } else if (weDontLookToHim && IsPedInControl()) { + + if (m_pedStats != collideWith->m_pedStats) { + + if (collideWith->m_pedStats->m_fear <= 100 - m_pedStats->m_temper +#ifdef VC_PED_PORTS + || collideWith->IsPlayer() || CTimer::GetTimeInMilliseconds() < m_nPedStateTimer +#endif + ) { + + if (collideWith->IsPlayer()) { + // He's on our right side + if (DotProduct(posDiff,GetRight()) <= 0.0f) + m_fRotationCur -= m_headingRate; + else + m_fRotationCur += m_headingRate; + } else { + // He's on our right side + if (DotProduct(posDiff, collideWith->GetRight()) <= 0.0f) + collideWith->m_fRotationCur -= collideWith->m_headingRate; + else + collideWith->m_fRotationCur += collideWith->m_headingRate; + } + } else { + SetLookFlag(collideWith, false); + TurnBody(); + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PARTIAL_PUNCH, 8.0f); + animAssoc->flags |= ASSOC_FADEOUTWHENDONE; +#ifdef VC_PED_PORTS + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 2000; +#endif + if (!heIsMissionChar) { + CVector2D posDiff2D(posDiff); + int direction = collideWith->GetLocalDirection(posDiff2D); + collideWith->StartFightDefend(direction, HITLEVEL_HIGH, 5); + } + } + } + } + } + } else if (collideWith->m_pedStats->m_defendWeakness <= 1.5f || heIsMissionChar +#ifdef VC_PED_PORTS + || m_pedStats->m_defendWeakness <= collideWith->m_pedStats->m_defendWeakness +#endif + ) { + // He looks us and we're not at his right side + if (heLooksToUs && DotProduct(posDiff,collideWith->GetRight()) > 0.0f) { + CVector moveForce = GetRight(); + moveForce.z += 0.1f; + ApplyMoveForce(moveForce); + if (collideWith->m_nMoveState != PEDMOVE_RUN && collideWith->m_nMoveState != PEDMOVE_SPRINT) + animToPlay = ANIM_STD_HIT_LEFT; + else + animToPlay = ANIM_STD_HITBYGUN_LEFT; + } else if (heLooksToUs) { + CVector moveForce = GetRight() * -1.0f; + moveForce.z += 0.1f; + ApplyMoveForce(moveForce); + if (collideWith->m_nMoveState != PEDMOVE_RUN && collideWith->m_nMoveState != PEDMOVE_SPRINT) + animToPlay = ANIM_STD_HIT_RIGHT; + else + animToPlay = ANIM_STD_HITBYGUN_RIGHT; + } else { + if (collideWith->m_nMoveState != PEDMOVE_RUN && collideWith->m_nMoveState != PEDMOVE_SPRINT) + animToPlay = ANIM_STD_HIT_BACK; + else + animToPlay = ANIM_STD_HITBYGUN_BACK; + } + + if (collideWith->IsPedInControl() && CTimer::GetTimeInMilliseconds() > collideWith->m_nPedStateTimer) { + animAssoc = CAnimManager::BlendAnimation(collideWith->GetClump(), ASSOCGRP_STD, animToPlay, 8.0f); + animAssoc->flags |= ASSOC_FADEOUTWHENDONE; + collideWith->m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 1000; + if (m_nPedState == PED_ATTACK) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FIGHT_PUNCH_39, 0.0f); + } + } else { + // We're at his right side + if (DotProduct(posDiff, collideWith->GetRight()) <= 0.0f) { + CVector moveForce = GetRight() * -1.0f; + moveForce.z += 0.1f; + ApplyMoveForce(moveForce); + if (heLooksToUs) + animToPlay = ANIM_STD_HIGHIMPACT_RIGHT; + else + animToPlay = ANIM_STD_SPINFORWARD_RIGHT; + } else { + CVector moveForce = GetRight(); + moveForce.z += 0.1f; + ApplyMoveForce(moveForce); + if (heLooksToUs) + animToPlay = ANIM_STD_HIGHIMPACT_LEFT; + else + animToPlay = ANIM_STD_SPINFORWARD_LEFT; + } + + if (m_nPedState == PED_ATTACK && collideWith->IsPedInControl()) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FIGHT_PUNCH_39, 0.0f); + + collideWith->SetFall(3000, animToPlay, 0); + } + } else { + if (!IsPedInControl()) + return; + + if (collideWith->m_nMoveState == PEDMOVE_NONE || collideWith->m_nMoveState == PEDMOVE_STILL) + return; + + if (m_nPedType != collideWith->m_nPedType || m_nPedType == PEDTYPE_CIVMALE || m_nPedType == PEDTYPE_CIVFEMALE) { + + if (!weAreMissionChar && heLooksToUs && m_pedStats->m_fear > 100 - collideWith->m_pedStats->m_temper) { + + if (CGeneral::GetRandomNumber() & 1 && CTimer::GetTimeInMilliseconds() < m_nPedStateTimer){ + SetEvasiveStep(collideWith, 2); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 3000; + } else if (collideWith->m_nMoveState > PEDMOVE_WALK) { + waitTime = 2000; + SetWaitState(WAITSTATE_PLAYANIM_DUCK, &waitTime); + } + } + } else if (heLooksToUs + && collideWith->m_nPedState != PED_STEP_AWAY + && m_nPedState != PED_STEP_AWAY + && CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { + + SetEvasiveStep(collideWith, 1); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 3000; + } + } + + if (IsPlayer()) { + SetLookFlag(collideWith, true); + SetLookTimer(800); + } + } else { + bool isRunning = m_nMoveState == PEDMOVE_RUN || m_nMoveState == PEDMOVE_SPRINT; + SetFindPathAndFlee(collideWith, 5000, !isRunning); + } +} + +void +CPed::KillPedWithCar(CVehicle *car, float impulse) +{ + CVehicleModelInfo *vehModel; + CColModel *vehColModel; + uint8 damageDir; + PedNode nodeToDamage; + eWeaponType killMethod; + + if (m_nPedState == PED_FALL || m_nPedState == PED_DIE) { + if (!this->m_pCollidingEntity || car->GetStatus() == STATUS_PLAYER) + this->m_pCollidingEntity = car; + return; + } + + if (m_nPedState == PED_DEAD) + return; + + if (m_pCurSurface) { + if (m_pCurSurface->IsVehicle() && (((CVehicle*)m_pCurSurface)->m_vehType == VEHICLE_TYPE_BOAT || IsPlayer())) + return; + } + + CVector distVec = GetPosition() - car->GetPosition(); + + if ((impulse > 12.0f || car->GetModelIndex() == MI_TRAIN) && !IsPlayer()) { + nodeToDamage = PED_TORSO; + killMethod = WEAPONTYPE_RAMMEDBYCAR; + uint8 randVal = CGeneral::GetRandomNumber() & 3; + + if (car == FindPlayerVehicle()) { + float carSpeed = car->m_vecMoveSpeed.Magnitude(); + uint8 shakeFreq; + if (100.0f * carSpeed * 2000.0f / car->m_fMass + 80.0f <= 250.0f) { + shakeFreq = 100.0f * carSpeed * 2000.0f / car->m_fMass + 80.0f; + } else { + shakeFreq = 250.0f; + } + CPad::GetPad(0)->StartShake(40000 / shakeFreq, shakeFreq); + } + bIsStanding = false; + damageDir = GetLocalDirection(-m_vecMoveSpeed); + vehModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(car->GetModelIndex()); + vehColModel = vehModel->GetColModel(); + float carRightAndDistDotProd = DotProduct(distVec, car->GetRight()); + + if (car->GetModelIndex() == MI_TRAIN) { + killMethod = WEAPONTYPE_RUNOVERBYCAR; + nodeToDamage = PED_HEAD; + m_vecMoveSpeed = 0.9f * car->m_vecMoveSpeed; + m_vecMoveSpeed.z = 0.0f; + if (damageDir == 1 || damageDir == 3) + damageDir = 2; + if (CGame::nastyGame) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SPLATTER, 0.0f); + + // Car doesn't look to us + } else if (DotProduct(car->m_vecMoveSpeed, car->GetForward()) >= 0.0f){ + + if (0.99f * vehColModel->boundingBox.max.x < Abs(carRightAndDistDotProd)) { + + // We're at the right of the car + if (carRightAndDistDotProd <= 0.0f) + nodeToDamage = PED_UPPERARML; + else + nodeToDamage = PED_UPPERARMR; + + if (Abs(DotProduct(distVec, car->GetForward())) < 0.85f * vehColModel->boundingBox.max.y) { + killMethod = WEAPONTYPE_RUNOVERBYCAR; + m_vecMoveSpeed = 0.9f * car->m_vecMoveSpeed; + m_vecMoveSpeed.z = 0.0f; + if (damageDir == 1 || damageDir == 3) + damageDir = 2; + if (CGame::nastyGame) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SPLATTER, 0.0f); + + } + } else { + float carFrontAndDistDotProd = DotProduct(distVec, car->GetForward()); + + // carFrontAndDistDotProd <= 0.0 car looks to us + if ((carFrontAndDistDotProd <= 0.1 || randVal == 1) && randVal != 0) { + killMethod = WEAPONTYPE_RUNOVERBYCAR; + nodeToDamage = PED_HEAD; + m_vecMoveSpeed = 0.9f * car->m_vecMoveSpeed; + m_vecMoveSpeed.z = 0.0f; + if (damageDir == 1 || damageDir == 3) + damageDir = 2; + + if (CGame::nastyGame) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SPLATTER, 0.0f); + + } else { + nodeToDamage = PED_MID; + float vehColMaxY = vehColModel->boundingBox.max.y; + float vehColMinY = vehColModel->boundingBox.min.y; + float vehColMaxZ = vehColModel->boundingBox.max.z; + float carFrontZ = car->GetForward().z; + float carHighestZ, carLength; + + if (carFrontZ < -0.2f) { + // Highest point of car's back + carHighestZ = (car->GetMatrix() * CVector(0.0f, vehColMinY, vehColMaxZ)).z; + carLength = vehColMaxY - vehColMinY; + + } else if (carFrontZ > 0.1f) { + // Highest point of car's front + carHighestZ = (car->GetMatrix() * CVector(0.0f, vehColMaxY, vehColMaxZ)).z; + float highestZDist = carHighestZ - GetPosition().z; + if (highestZDist > 0.0f) { + GetMatrix().GetPosition().z += 0.5f * highestZDist; + carHighestZ += highestZDist * 0.25f; + } + carLength = vehColMaxY; + + } else { + // Highest point of car's front + carHighestZ = (car->GetMatrix() * CVector(0.0f, vehColMaxY, vehColMaxZ)).z; + carLength = vehColMaxY; + } + + float pedJumpSpeedToReachHighestZ = (carHighestZ - GetPosition().z) / (carLength / car->m_vecMoveSpeed.Magnitude()); + + // TODO: What are we doing down here? + float unknown = ((CGeneral::GetRandomNumber() % 256) * 0.002 + 1.5) * pedJumpSpeedToReachHighestZ; + + // After this point, distVec isn't distVec anymore. + distVec = car->m_vecMoveSpeed; + distVec.Normalise(); + distVec *= 0.2 * unknown; + + if (damageDir != 1 && damageDir != 3) + distVec.z += unknown; + else + distVec.z += 1.5f * unknown; + + m_vecMoveSpeed = distVec; + damageDir += 2; + if (damageDir > 3) + damageDir = damageDir - 4; + + if (car->m_vehType == VEHICLE_TYPE_CAR) { + CObject *bonnet = ((CAutomobile*)car)->RemoveBonnetInPedCollision(); + + if (bonnet) { + if (CGeneral::GetRandomNumber() & 1) { + bonnet->m_vecMoveSpeed += Multiply3x3(car->GetMatrix(), CVector(0.1f, 0.0f, 0.5f)); + } else { + bonnet->m_vecMoveSpeed += Multiply3x3(car->GetMatrix(), CVector(-0.1f, 0.0f, 0.5f)); + } + CVector forceDir = car->GetUp() * 10.0f; + bonnet->ApplyTurnForce(forceDir, car->GetForward()); + } + } + } + } + } + + if (car->pDriver) { + CEventList::RegisterEvent((m_nPedType == PEDTYPE_COP ? EVENT_HIT_AND_RUN_COP : EVENT_HIT_AND_RUN), EVENT_ENTITY_PED, this, car->pDriver, 1000); + } + + ePedPieceTypes pieceToDamage; + switch (nodeToDamage) { + case PED_HEAD: + pieceToDamage = PEDPIECE_HEAD; + break; + case PED_UPPERARML: + pieceToDamage = PEDPIECE_LEFTARM; + break; + case PED_UPPERARMR: + pieceToDamage = PEDPIECE_RIGHTARM; + break; + default: + pieceToDamage = PEDPIECE_MID; + break; + } + InflictDamage(car, killMethod, 1000.0f, pieceToDamage, damageDir); + + if (DyingOrDead() + && bIsPedDieAnimPlaying && !m_pCollidingEntity) { + m_pCollidingEntity = car; + } + if (nodeToDamage == PED_MID) + bKnockedUpIntoAir = true; + else + bKnockedUpIntoAir = false; + + distVec.Normalise(); + +#ifdef VC_PED_PORTS + distVec *= Min(car->m_fMass / 1400.0f, 1.0f); +#endif + car->ApplyMoveForce(distVec * -100.0f); + Say(SOUND_PED_DEFEND); + + } else if (m_vecDamageNormal.z < -0.8f && impulse > 3.0f + || impulse > 6.0f && (!IsPlayer() || impulse > 10.0f)) { + + bIsStanding = false; + uint8 fallDirection = GetLocalDirection(-car->m_vecMoveSpeed); + float damage; + if (IsPlayer() && car->GetModelIndex() == MI_TRAIN) + damage = 150.0f; + else + damage = 30.0f; + + InflictDamage(car, WEAPONTYPE_RAMMEDBYCAR, damage, PEDPIECE_TORSO, fallDirection); + SetFall(1000, (AnimationId)(fallDirection + ANIM_STD_HIGHIMPACT_FRONT), true); + + if (OnGround() && !m_pCollidingEntity && + (!IsPlayer() || bHasHitWall || car->GetModelIndex() == MI_TRAIN || m_vecDamageNormal.z < -0.8f)) { + + m_pCollidingEntity = car; + } + + bKnockedUpIntoAir = false; + if (car->GetModelIndex() != MI_TRAIN && !bHasHitWall) { + m_vecMoveSpeed = car->m_vecMoveSpeed * 0.75f; + } + m_vecMoveSpeed.z = 0.0f; + distVec.Normalise(); +#ifdef VC_PED_PORTS + distVec *= Min(car->m_fMass / 1400.0f, 1.0f); +#endif + car->ApplyMoveForce(distVec * -60.0f); + Say(SOUND_PED_DEFEND); + } + +#ifdef VC_PED_PORTS + // Killing gang members with car wasn't triggering a fight, until now... Taken from VC. + if (IsGangMember()) { + CPed *driver = car->pDriver; + if (driver && driver->IsPlayer() +#ifdef FIX_BUGS + && (CharCreatedBy != MISSION_CHAR || bRespondsToThreats) && (!m_leader || m_leader != driver) +#endif + ) { + RegisterThreatWithGangPeds(driver); + } + } +#endif +} \ No newline at end of file diff --git a/src/peds/PedIK.cpp b/src/peds/PedIK.cpp new file mode 100644 index 0000000..8358a19 --- /dev/null +++ b/src/peds/PedIK.cpp @@ -0,0 +1,560 @@ +#include "common.h" + +#include "Bones.h" +#include "Camera.h" +#include "PedIK.h" +#include "Ped.h" +#include "General.h" +#include "RwHelper.h" + +LimbMovementInfo CPedIK::ms_torsoInfo = { DEGTORAD(50.0f), DEGTORAD(-50.0f), DEGTORAD(15.0f), DEGTORAD(45.0f), DEGTORAD(-45.0f), DEGTORAD(7.0f) }; +LimbMovementInfo CPedIK::ms_headInfo = { DEGTORAD(90.0f), DEGTORAD(-90.0f), DEGTORAD(10.0f), DEGTORAD(45.0f), DEGTORAD(-45.0f), DEGTORAD(5.0f) }; +LimbMovementInfo CPedIK::ms_headRestoreInfo = { DEGTORAD(90.0f), DEGTORAD(-90.0f), DEGTORAD(10.0f), DEGTORAD(45.0f), DEGTORAD(-45.0f), DEGTORAD(5.0f) }; +LimbMovementInfo CPedIK::ms_upperArmInfo = { DEGTORAD(20.0f), DEGTORAD(-100.0f), DEGTORAD(20.0f), DEGTORAD(70.0f), DEGTORAD(-70.0f), DEGTORAD(10.0f) }; +LimbMovementInfo CPedIK::ms_lowerArmInfo = { DEGTORAD(80.0f), DEGTORAD(0.0f), DEGTORAD(20.0f), DEGTORAD(90.0f), DEGTORAD(-90.0f), DEGTORAD(5.0f) }; + +const RwV3d XaxisIK = { 1.0f, 0.0f, 0.0f}; +const RwV3d YaxisIK = { 0.0f, 1.0f, 0.0f}; +const RwV3d ZaxisIK = { 0.0f, 0.0f, 1.0f}; + +CPedIK::CPedIK(CPed *ped) : m_ped(ped) +{ + assert(ped != nil); + m_flags = 0; + m_headOrient.yaw = 0.0f; + m_headOrient.pitch = 0.0f; + m_torsoOrient.yaw = 0.0f; + m_torsoOrient.pitch = 0.0f; + m_upperArmOrient.yaw = 0.0f; + m_upperArmOrient.pitch = 0.0f; + m_lowerArmOrient.yaw = 0.0f; + m_lowerArmOrient.pitch = 0.0f; +} + +#ifdef PED_SKIN +inline RwMatrix* +GetBoneMatrix(CPed *ped, int32 bone) +{ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(ped->GetClump()); + int idx = RpHAnimIDGetIndex(hier, bone); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + return &mats[idx]; +} +inline RwMatrix* +GetComponentMatrix(CPed *ped, int32 node) +{ + return GetBoneMatrix(ped, ped->m_pFrames[node]->nodeID); +} +#endif + +void +CPedIK::RotateTorso(AnimBlendFrameData *node, LimbOrientation *limb, bool changeRoll) +{ +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + RtQuat *q = &node->hanimFrame->q; +#ifndef FIX_BUGS + // this is what the game does (also VC), but it does not look great + RtQuatRotate(q, &XaxisIK, RADTODEG(limb->yaw), rwCOMBINEPRECONCAT); + RtQuatRotate(q, &ZaxisIK, RADTODEG(limb->pitch), rwCOMBINEPRECONCAT); // pitch +#else + // copied the code from the non-skinned case + // this seems to work ok + + // We can't get the parent matrix of an hanim frame but + // this function is always called with PED_MID, so we know the parent frame. + // Trouble is that PED_MID is "Smid" on PS2/PC but BONE_torso on mobile/xbox... + // Assuming BONE_torso, the parent is BONE_mid, so let's use that: + RwMatrix *mat = GetBoneMatrix(m_ped, BONE_mid); + + RwV3d vec1, vec2; + vec1.x = mat->right.z; + vec1.y = mat->up.z; + vec1.z = mat->at.z; + float c = Cos(m_ped->m_fRotationCur); + float s = Sin(m_ped->m_fRotationCur); + vec2.x = -(c*mat->right.x + s*mat->right.y); + vec2.y = -(c*mat->up.x + s*mat->up.y); + vec2.z = -(c*mat->at.x + s*mat->at.y); + + // Not sure what exactly to do here + RtQuatRotate(q, &vec1, RADTODEG(limb->yaw), rwCOMBINEPRECONCAT); + RtQuatRotate(q, &vec2, RADTODEG(limb->pitch), rwCOMBINEPRECONCAT); +#endif + m_ped->bDontAcceptIKLookAts = true; + }else +#endif + { + RwFrame *f = node->frame; + RwMatrix *mat = GetWorldMatrix(RwFrameGetParent(f), RwMatrixCreate()); + + RwV3d upVector = { mat->right.z, mat->up.z, mat->at.z }; + RwV3d rightVector; + RwV3d pos = RwFrameGetMatrix(f)->pos; + + // rotation == 0 -> looking in y direction + // left? vector + float c = Cos(m_ped->m_fRotationCur); + float s = Sin(m_ped->m_fRotationCur); + rightVector.x = -(c*mat->right.x + s*mat->right.y); + rightVector.y = -(c*mat->up.x + s*mat->up.y); + rightVector.z = -(c*mat->at.x + s*mat->at.y); + + if(changeRoll){ + // Used when aiming only involves over the legs.(canAimWithArm) + // Automatically changes roll(forward rotation) axis of the parts above upper legs while moving, based on position of upper legs. + // Not noticeable in normal conditions... + + RwV3d forwardVector; + CVector inversedForward = CrossProduct(CVector(0.0f, 0.0f, 1.0f), mat->up); + inversedForward.Normalise(); + float dotProduct = DotProduct(mat->at, inversedForward); + if(dotProduct > 1.0f) dotProduct = 1.0f; + if(dotProduct < -1.0f) dotProduct = -1.0f; + float alpha = Acos(dotProduct); + + if(mat->at.z < 0.0f) + alpha = -alpha; + + forwardVector.x = s * mat->right.x - c * mat->right.y; + forwardVector.y = s * mat->up.x - c * mat->up.y; + forwardVector.z = s * mat->at.x - c * mat->at.y; + + float curYaw, curPitch; + ExtractYawAndPitchWorld(mat, &curYaw, &curPitch); + RwMatrixRotate(RwFrameGetMatrix(f), &rightVector, RADTODEG(limb->pitch), rwCOMBINEPOSTCONCAT); + RwMatrixRotate(RwFrameGetMatrix(f), &upVector, RADTODEG(limb->yaw - (curYaw - m_ped->m_fRotationCur)), rwCOMBINEPOSTCONCAT); + RwMatrixRotate(RwFrameGetMatrix(f), &forwardVector, RADTODEG(alpha), rwCOMBINEPOSTCONCAT); + }else{ + // pitch + RwMatrixRotate(RwFrameGetMatrix(f), &rightVector, RADTODEG(limb->pitch), rwCOMBINEPOSTCONCAT); + // yaw + RwMatrixRotate(RwFrameGetMatrix(f), &upVector, RADTODEG(limb->yaw), rwCOMBINEPOSTCONCAT); + } + RwFrameGetMatrix(f)->pos = pos; + RwMatrixDestroy(mat); + } +} + +void +CPedIK::GetComponentPosition(RwV3d &pos, uint32 node) +{ + RwFrame *f; + RwMatrix *mat; + +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + pos.x = 0.0f; + pos.y = 0.0f; + pos.z = 0.0f; + mat = GetComponentMatrix(m_ped, node); + // could just copy the position out of the matrix... + RwV3dTransformPoints(&pos, &pos, 1, mat); + }else +#endif + { + f = m_ped->m_pFrames[node]->frame; + mat = RwFrameGetMatrix(f); + pos = mat->pos; + + for (f = RwFrameGetParent(f); f; f = RwFrameGetParent(f)) + RwV3dTransformPoints(&pos, &pos, 1, RwFrameGetMatrix(f)); + } +} + +RwMatrix* +CPedIK::GetWorldMatrix(RwFrame *source, RwMatrix *destination) +{ + RwFrame *i; + + *destination = *RwFrameGetMatrix(source); + + for (i = RwFrameGetParent(source); i; i = RwFrameGetParent(i)) + RwMatrixTransform(destination, RwFrameGetMatrix(i), rwCOMBINEPOSTCONCAT); + + return destination; +} + +LimbMoveStatus +CPedIK::MoveLimb(LimbOrientation &limb, float targetYaw, float targetPitch, LimbMovementInfo &moveInfo) +{ + LimbMoveStatus result = ONE_ANGLE_COULDNT_BE_SET_EXACTLY; + + // yaw + + if (limb.yaw > targetYaw) { + limb.yaw -= moveInfo.yawD; + } else if (limb.yaw < targetYaw) { + limb.yaw += moveInfo.yawD; + } + + if (Abs(limb.yaw - targetYaw) < moveInfo.yawD) { + limb.yaw = targetYaw; + result = ANGLES_SET_EXACTLY; + } + + if (limb.yaw > moveInfo.maxYaw || limb.yaw < moveInfo.minYaw) { + limb.yaw = Clamp(limb.yaw, moveInfo.minYaw, moveInfo.maxYaw); + result = ANGLES_SET_TO_MAX; + } + + // pitch + + if (limb.pitch > targetPitch) { + limb.pitch -= moveInfo.pitchD; + } else if (limb.pitch < targetPitch) { + limb.pitch += moveInfo.pitchD; + } + + if (Abs(limb.pitch - targetPitch) < moveInfo.pitchD) + limb.pitch = targetPitch; + else + result = ONE_ANGLE_COULDNT_BE_SET_EXACTLY; + + if (limb.pitch > moveInfo.maxPitch || limb.pitch < moveInfo.minPitch) { + limb.pitch = Clamp(limb.pitch, moveInfo.minPitch, moveInfo.maxPitch); + result = ANGLES_SET_TO_MAX; + } + return result; +} + +bool +CPedIK::RestoreGunPosn(void) +{ + LimbMoveStatus limbStatus = MoveLimb(m_torsoOrient, 0.0f, 0.0f, ms_torsoInfo); + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + return limbStatus == ANGLES_SET_EXACTLY; +} + +#ifdef PED_SKIN +void +CPedIK::RotateHead(void) +{ + RtQuat *q = &m_ped->m_pFrames[PED_HEAD]->hanimFrame->q; + RtQuatRotate(q, &XaxisIK, RADTODEG(m_headOrient.yaw), rwCOMBINEREPLACE); + RtQuatRotate(q, &ZaxisIK, RADTODEG(m_headOrient.pitch), rwCOMBINEPOSTCONCAT); + m_ped->bDontAcceptIKLookAts = true; +} +#endif + +bool +CPedIK::LookInDirection(float targetYaw, float targetPitch) +{ + bool success = true; + float yaw, pitch; +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + if (!(m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION)) { + m_ped->m_pFrames[PED_HEAD]->flag |= AnimBlendFrameData::IGNORE_ROTATION; + ExtractYawAndPitchLocalSkinned(m_ped->m_pFrames[PED_HEAD], &m_headOrient.yaw, &m_headOrient.pitch); + } + + // parent of head is torso + RwMatrix worldMat = *GetBoneMatrix(m_ped, BONE_torso); + ExtractYawAndPitchWorld(&worldMat, &yaw, &pitch); + + LimbMoveStatus headStatus = MoveLimb(m_headOrient, CGeneral::LimitRadianAngle(targetYaw - yaw), + CGeneral::LimitRadianAngle(DEGTORAD(10.0f)), ms_headInfo); + if (headStatus == ANGLES_SET_TO_MAX) + success = false; + + if (headStatus != ANGLES_SET_EXACTLY){ + if (!(m_flags & LOOKAROUND_HEAD_ONLY)){ + if (MoveLimb(m_torsoOrient, CGeneral::LimitRadianAngle(targetYaw), targetPitch, ms_torsoInfo)) + success = true; + }else{ + RotateHead(); + return success; + } + } + + if (!(m_flags & LOOKAROUND_HEAD_ONLY)) + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + RotateHead(); + }else +#endif + { + RwFrame *frame = m_ped->m_pFrames[PED_HEAD]->frame; + RwMatrix *frameMat = RwFrameGetMatrix(frame); + + if (!(m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION)) { + m_ped->m_pFrames[PED_HEAD]->flag |= AnimBlendFrameData::IGNORE_ROTATION; + ExtractYawAndPitchLocal(frameMat, &m_headOrient.yaw, &m_headOrient.pitch); + } + + RwMatrix *worldMat = RwMatrixCreate(); + worldMat = GetWorldMatrix(RwFrameGetParent(frame), worldMat); + + ExtractYawAndPitchWorld(worldMat, &yaw, &pitch); + RwMatrixDestroy(worldMat); + + yaw += m_torsoOrient.yaw; + float neededYawTurn = CGeneral::LimitRadianAngle(targetYaw - yaw); + pitch *= Cos(neededYawTurn); + + float neededPitchTurn = CGeneral::LimitRadianAngle(targetPitch - pitch); + LimbMoveStatus headStatus = MoveLimb(m_headOrient, neededYawTurn, neededPitchTurn, ms_headInfo); + if (headStatus == ANGLES_SET_TO_MAX) + success = false; + + if (headStatus != ANGLES_SET_EXACTLY && !(m_flags & LOOKAROUND_HEAD_ONLY)) { + float remainingTurn = CGeneral::LimitRadianAngle(targetYaw - m_ped->m_fRotationCur); + if (MoveLimb(m_torsoOrient, remainingTurn, targetPitch, ms_torsoInfo)) + success = true; + } + CMatrix nextFrame = CMatrix(frameMat); + CVector framePos = nextFrame.GetPosition(); + + nextFrame.SetRotateZ(m_headOrient.pitch); + nextFrame.RotateX(m_headOrient.yaw); + nextFrame.GetPosition() += framePos; + nextFrame.UpdateRW(); + + if (!(m_flags & LOOKAROUND_HEAD_ONLY)) + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + + } + return success; +} + +bool +CPedIK::LookAtPosition(CVector const &pos) +{ + float yawToFace = CGeneral::GetRadianAngleBetweenPoints( + pos.x, pos.y, + m_ped->GetPosition().x, m_ped->GetPosition().y); + + float pitchToFace = CGeneral::GetRadianAngleBetweenPoints( + pos.z, (m_ped->GetPosition() - pos).Magnitude2D(), + m_ped->GetPosition().z, 0.0f); + + return LookInDirection(yawToFace, pitchToFace); +} + +bool +CPedIK::PointGunInDirection(float targetYaw, float targetPitch) +{ + bool result = true; + bool armPointedToGun = false; + float angle = CGeneral::LimitRadianAngle(targetYaw - m_ped->m_fRotationCur); + m_flags &= (~GUN_POINTED_SUCCESSFULLY); + m_flags |= LOOKAROUND_HEAD_ONLY; + if (m_flags & AIMS_WITH_ARM) { + armPointedToGun = PointGunInDirectionUsingArm(angle, targetPitch); + angle = CGeneral::LimitRadianAngle(angle - m_upperArmOrient.yaw); + } + if (armPointedToGun) { + if (m_flags & AIMS_WITH_ARM && m_torsoOrient.yaw * m_upperArmOrient.yaw < 0.0f) + MoveLimb(m_torsoOrient, 0.0f, m_torsoOrient.pitch, ms_torsoInfo); + } else { + // Unused code + RwMatrix *matrix; + float yaw, pitch; +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + matrix = RwMatrixCreate(); + *matrix = *GetComponentMatrix(m_ped, PED_UPPERARMR); + ExtractYawAndPitchWorld(matrix, &yaw, &pitch); + RwMatrixDestroy(matrix); + }else +#endif + { + matrix = GetWorldMatrix(RwFrameGetParent(m_ped->m_pFrames[PED_UPPERARMR]->frame), RwMatrixCreate()); + ExtractYawAndPitchWorld(matrix, &yaw, &pitch); + RwMatrixDestroy(matrix); + } + // + + LimbMoveStatus status = MoveLimb(m_torsoOrient, angle, targetPitch, ms_torsoInfo); + if (status == ANGLES_SET_TO_MAX) + result = false; + else if (status == ANGLES_SET_EXACTLY) + m_flags |= GUN_POINTED_SUCCESSFULLY; + } + if (TheCamera.Cams[TheCamera.ActiveCam].Using3rdPersonMouseCam() && m_flags & AIMS_WITH_ARM) + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, true); + else + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + return result; +} + +bool +CPedIK::PointGunInDirectionUsingArm(float targetYaw, float targetPitch) +{ + bool result = false; + + RwV3d upVector; // only for non-skinned + RwMatrix *matrix; + float yaw, pitch; +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + matrix = RwMatrixCreate(); + *matrix = *GetComponentMatrix(m_ped, PED_UPPERARMR); + ExtractYawAndPitchWorld(matrix, &yaw, &pitch); + RwMatrixDestroy(matrix); + }else +#endif + { + RwFrame *frame = m_ped->m_pFrames[PED_UPPERARMR]->frame; + matrix = GetWorldMatrix(RwFrameGetParent(frame), RwMatrixCreate()); + + // with PED_SKIN this is actually done below (with a memory leak) + upVector.x = matrix->right.z; + upVector.y = matrix->up.z; + upVector.z = matrix->at.z; + + ExtractYawAndPitchWorld(matrix, &yaw, &pitch); + RwMatrixDestroy(matrix); + } + + RwV3d rightVector = { 0.0f, 0.0f, 1.0f }; + RwV3d forwardVector = { 1.0f, 0.0f, 0.0f }; + + float uaYaw, uaPitch; +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + uaYaw = targetYaw; + uaPitch = targetPitch + DEGTORAD(10.0f); + }else +#endif + { + uaYaw = targetYaw - m_torsoOrient.yaw - DEGTORAD(15.0f); + uaPitch = CGeneral::LimitRadianAngle(targetPitch - pitch); + } + LimbMoveStatus uaStatus = MoveLimb(m_upperArmOrient, uaYaw, uaPitch, ms_upperArmInfo); + if (uaStatus == ANGLES_SET_EXACTLY) { + m_flags |= GUN_POINTED_SUCCESSFULLY; + result = true; + } + +#ifdef PED_SKIN + // this code is completely missing on xbox & android, but we can keep it with the check + // TODO? implement it for skinned geometry? + if(!IsClumpSkinned(m_ped->GetClump())) +#endif + if (uaStatus == ANGLES_SET_TO_MAX) { + float laYaw = uaYaw - m_upperArmOrient.yaw; + + LimbMoveStatus laStatus; + if (laYaw > 0.0f) + laStatus = MoveLimb(m_lowerArmOrient, laYaw, -DEGTORAD(45.0f), ms_lowerArmInfo); + else + laStatus = MoveLimb(m_lowerArmOrient, laYaw, 0.0f, ms_lowerArmInfo); + + if (laStatus == ANGLES_SET_EXACTLY) { + m_flags |= GUN_POINTED_SUCCESSFULLY; + result = true; + } + RwFrame *child = GetFirstChild(m_ped->m_pFrames[PED_UPPERARMR]->frame); + RwV3d pos = RwFrameGetMatrix(child)->pos; + RwMatrixRotate(RwFrameGetMatrix(child), &forwardVector, RADTODEG(m_lowerArmOrient.pitch), rwCOMBINEPOSTCONCAT); + RwMatrixRotate(RwFrameGetMatrix(child), &rightVector, RADTODEG(-m_lowerArmOrient.yaw), rwCOMBINEPOSTCONCAT); + RwFrameGetMatrix(child)->pos = pos; + } + +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + RtQuat *q = &m_ped->m_pFrames[PED_UPPERARMR]->hanimFrame->q; + RtQuatRotate(q, &XaxisIK, RADTODEG(m_upperArmOrient.yaw), rwCOMBINEPOSTCONCAT); + RtQuatRotate(q, &ZaxisIK, RADTODEG(m_upperArmOrient.pitch), rwCOMBINEPOSTCONCAT); + m_ped->bDontAcceptIKLookAts = true; + }else +#endif + { + RwFrame *frame = m_ped->m_pFrames[PED_UPPERARMR]->frame; + // with PED_SKIN we're also getting upVector here + RwV3d pos = RwFrameGetMatrix(frame)->pos; + RwMatrixRotate(RwFrameGetMatrix(frame), &rightVector, RADTODEG(m_upperArmOrient.pitch), rwCOMBINEPOSTCONCAT); + RwMatrixRotate(RwFrameGetMatrix(frame), &upVector, RADTODEG(m_upperArmOrient.yaw), rwCOMBINEPOSTCONCAT); + RwFrameGetMatrix(frame)->pos = pos; + } + return result; +} + +bool +CPedIK::PointGunAtPosition(CVector const& position) +{ + return PointGunInDirection( + CGeneral::GetRadianAngleBetweenPoints(position.x, position.y, m_ped->GetPosition().x, m_ped->GetPosition().y), + CGeneral::GetRadianAngleBetweenPoints(position.z, Distance2D(m_ped->GetPosition(), position.x, position.y), + m_ped->GetPosition().z, + 0.0f)); +} + +bool +CPedIK::RestoreLookAt(void) +{ + bool result = false; + float yaw, pitch; + +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + if (m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION) { + m_ped->m_pFrames[PED_HEAD]->flag &= (~AnimBlendFrameData::IGNORE_ROTATION); + } else { + ExtractYawAndPitchLocalSkinned(m_ped->m_pFrames[PED_HEAD], &yaw, &pitch); + if (MoveLimb(m_headOrient, yaw, pitch, ms_headRestoreInfo) == ANGLES_SET_EXACTLY) + result = true; + } + RotateHead(); + }else +#endif + { + RwMatrix *mat = RwFrameGetMatrix(m_ped->m_pFrames[PED_HEAD]->frame); + if (m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION) { + m_ped->m_pFrames[PED_HEAD]->flag &= (~AnimBlendFrameData::IGNORE_ROTATION); + } else { + ExtractYawAndPitchLocal(mat, &yaw, &pitch); + if (MoveLimb(m_headOrient, yaw, pitch, ms_headRestoreInfo) == ANGLES_SET_EXACTLY) + result = true; + } + + CMatrix matrix(mat); + CVector pos = matrix.GetPosition(); + matrix.SetRotateZ(m_headOrient.pitch); + matrix.RotateX(m_headOrient.yaw); + matrix.Translate(pos); + matrix.UpdateRW(); + } + if (!(m_flags & LOOKAROUND_HEAD_ONLY)){ + MoveLimb(m_torsoOrient, 0.0f, 0.0f, ms_torsoInfo); + if (!(m_flags & LOOKAROUND_HEAD_ONLY)) + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + } + return result; +} + +void +CPedIK::ExtractYawAndPitchWorld(RwMatrix *mat, float *yaw, float *pitch) +{ + float f = Clamp(DotProduct(mat->up, CVector(0.0f, 1.0f, 0.0f)), -1.0f, 1.0f); + *yaw = Acos(f); + if (mat->up.x > 0.0f) *yaw = -*yaw; + + f = Clamp(DotProduct(mat->right, CVector(0.0f, 0.0f, 1.0f)), -1.0f, 1.0f); + *pitch = Acos(f); + if (mat->up.z > 0.0f) *pitch = -*pitch; +} + +void +CPedIK::ExtractYawAndPitchLocal(RwMatrix *mat, float *yaw, float *pitch) +{ + float f = Clamp(DotProduct(mat->at, CVector(0.0f, 0.0f, 1.0f)), -1.0f, 1.0f); + *yaw = Acos(f); + if (mat->at.y > 0.0f) *yaw = -*yaw; + + f = Clamp(DotProduct(mat->right, CVector(1.0f, 0.0f, 0.0f)), -1.0f, 1.0f); + *pitch = Acos(f); + if (mat->up.x > 0.0f) *pitch = -*pitch; +} + +#ifdef PED_SKIN +void +CPedIK::ExtractYawAndPitchLocalSkinned(AnimBlendFrameData *node, float *yaw, float *pitch) +{ + RwMatrix *mat = RwMatrixCreate(); + RtQuatConvertToMatrix(&node->hanimFrame->q, mat); + ExtractYawAndPitchLocal(mat, yaw, pitch); + RwMatrixDestroy(mat); +} +#endif diff --git a/src/peds/PedIK.h b/src/peds/PedIK.h new file mode 100644 index 0000000..1543fa3 --- /dev/null +++ b/src/peds/PedIK.h @@ -0,0 +1,68 @@ +#pragma once +#include "common.h" +#include "AnimBlendClumpData.h" + +struct LimbOrientation +{ + float yaw; + float pitch; +}; + +struct LimbMovementInfo { + float maxYaw; + float minYaw; + float yawD; + float maxPitch; + float minPitch; + float pitchD; +}; + +enum LimbMoveStatus { + ANGLES_SET_TO_MAX, // because given angles were unreachable + ONE_ANGLE_COULDNT_BE_SET_EXACTLY, // because it can't be reached in a jiffy + ANGLES_SET_EXACTLY +}; + +class CPed; + +class CPedIK +{ +public: + enum { + GUN_POINTED_SUCCESSFULLY = 1, + LOOKAROUND_HEAD_ONLY = 2, + AIMS_WITH_ARM = 4, + }; + + CPed *Const m_ped; + LimbOrientation m_headOrient; + LimbOrientation m_torsoOrient; + LimbOrientation m_upperArmOrient; + LimbOrientation m_lowerArmOrient; + int32 m_flags; + + static LimbMovementInfo ms_torsoInfo; + static LimbMovementInfo ms_headInfo; + static LimbMovementInfo ms_headRestoreInfo; + static LimbMovementInfo ms_upperArmInfo; + static LimbMovementInfo ms_lowerArmInfo; + + CPedIK(CPed *ped); + bool PointGunInDirection(float targetYaw, float targetPitch); + bool PointGunInDirectionUsingArm(float targetYaw, float targetPitch); + bool PointGunAtPosition(CVector const& position); + void GetComponentPosition(RwV3d &pos, uint32 node); + static RwMatrix *GetWorldMatrix(RwFrame *source, RwMatrix *destination); + void RotateTorso(AnimBlendFrameData* animBlend, LimbOrientation* limb, bool changeRoll); + void ExtractYawAndPitchLocal(RwMatrix *mat, float *yaw, float *pitch); + void ExtractYawAndPitchLocalSkinned(AnimBlendFrameData *node, float *yaw, float *pitch); + void ExtractYawAndPitchWorld(RwMatrix *mat, float *yaw, float *pitch); + LimbMoveStatus MoveLimb(LimbOrientation &limb, float targetYaw, float targetPitch, LimbMovementInfo &moveInfo); + bool RestoreGunPosn(void); + void RotateHead(void); + bool LookInDirection(float targetYaw, float targetPitch); + bool LookAtPosition(CVector const& pos); + bool RestoreLookAt(void); +}; + +VALIDATE_SIZE(CPedIK, 0x28); diff --git a/src/peds/PedPlacement.cpp b/src/peds/PedPlacement.cpp new file mode 100644 index 0000000..2d4a92f --- /dev/null +++ b/src/peds/PedPlacement.cpp @@ -0,0 +1,51 @@ +#include "common.h" + +#include "Ped.h" +#include "PedPlacement.h" +#include "World.h" + +void +CPedPlacement::FindZCoorForPed(CVector* pos) +{ + float zForPed; + float startZ = pos->z - 100.0f; + float foundColZ = -100.0f; + float foundColZ2 = -100.0f; + CColPoint foundCol; + CEntity* foundEnt; + + CVector vec( + pos->x, + pos->y, + pos->z + 1.0f + ); + + if (CWorld::ProcessVerticalLine(vec, startZ, foundCol, foundEnt, true, false, false, false, true, false, nil)) + foundColZ = foundCol.point.z; + + // Adjust coords and do a second test + vec.x += 0.1f; + vec.y += 0.1f; + + if (CWorld::ProcessVerticalLine(vec, startZ, foundCol, foundEnt, true, false, false, false, true, false, nil)) + foundColZ2 = foundCol.point.z; + + zForPed = Max(foundColZ, foundColZ2); + + if (zForPed > -99.0f) + pos->z = FEET_OFFSET + zForPed; +} + +CEntity* +CPedPlacement::IsPositionClearOfCars(Const CVector *pos) +{ + return CWorld::TestSphereAgainstWorld(*pos, 0.25f, nil, true, true, false, false, false, false); +} + +bool +CPedPlacement::IsPositionClearForPed(CVector* pos) +{ + int16 count; + CWorld::FindObjectsKindaColliding(*pos, 0.75f, true, &count, 2, nil, false, true, true, false, false); + return count == 0; +} diff --git a/src/peds/PedPlacement.h b/src/peds/PedPlacement.h new file mode 100644 index 0000000..b51e2aa --- /dev/null +++ b/src/peds/PedPlacement.h @@ -0,0 +1,8 @@ +#pragma once + +class CPedPlacement { +public: + static void FindZCoorForPed(CVector* pos); + static CEntity* IsPositionClearOfCars(Const CVector*); + static bool IsPositionClearForPed(CVector*); +}; \ No newline at end of file diff --git a/src/peds/PedRoutes.cpp b/src/peds/PedRoutes.cpp new file mode 100644 index 0000000..3ff080e --- /dev/null +++ b/src/peds/PedRoutes.cpp @@ -0,0 +1,79 @@ +#include "common.h" + +#include "main.h" +#include "PedRoutes.h" + +CRouteNode gaRoutes[NUMPEDROUTES]; + +void +CRouteNode::Initialise() +{ + for (int i = 0; i < NUMPEDROUTES; i++) { + gaRoutes[i].m_route = -1; + gaRoutes[i].m_pos = CVector(0.0f, 0.0f, 0.0f); + } +} + +int16 +CRouteNode::GetRouteThisPointIsOn(int16 point) +{ + return gaRoutes[point].m_route; +} + +// Actually GetFirstPointOfRoute +int16 +CRouteNode::GetRouteStart(int16 route) +{ + for (int i = 0; i < NUMPEDROUTES; i++) { + if (route == gaRoutes[i].m_route) + return i; + } + return -1; +} + +CVector +CRouteNode::GetPointPosition(int16 point) +{ + return gaRoutes[point].m_pos; +} + +void +CRouteNode::AddRoutePoint(int16 route, CVector pos) +{ + uint16 point; + for (point = 0; point < NUMPEDROUTES; point++) { + if (gaRoutes[point].m_route == -1) + break; + } +#ifdef FIX_BUGS + if (point == NUMPEDROUTES) + return; +#endif + gaRoutes[point].m_route = route; + gaRoutes[point].m_pos = pos; +} + +void +CRouteNode::RemoveRoute(int16 route) +{ + uint16 first_point, last_point, i; + for (first_point = 0; first_point < NUMPEDROUTES; first_point++) { + if (gaRoutes[first_point].m_route == route) + break; + } + if (first_point == NUMPEDROUTES) + return; + for (last_point = first_point; last_point < NUMPEDROUTES; last_point++) + if (gaRoutes[last_point].m_route != route) + break; + uint16 diff = last_point - first_point; +#ifdef FIX_BUGS + for (i = first_point; i < NUMPEDROUTES - diff; i++) + gaRoutes[i] = gaRoutes[i + diff]; +#else + for (i = 0; i < diff; i++) + gaRoutes[first_point + i] = gaRoutes[last_point + i]; +#endif + for (i = NUMPEDROUTES - diff; i < NUMPEDROUTES; i++) + gaRoutes[i].m_route = -1; +} diff --git a/src/peds/PedRoutes.h b/src/peds/PedRoutes.h new file mode 100644 index 0000000..c478e38 --- /dev/null +++ b/src/peds/PedRoutes.h @@ -0,0 +1,15 @@ +#pragma once + +class CRouteNode +{ +public: + int16 m_route; + CVector m_pos; + + static int16 GetRouteThisPointIsOn(int16); + static CVector GetPointPosition(int16); + static int16 GetRouteStart(int16); + static void AddRoutePoint(int16, CVector); + static void RemoveRoute(int16); + static void Initialise(void); +}; diff --git a/src/peds/PedType.cpp b/src/peds/PedType.cpp new file mode 100644 index 0000000..dcd4c71 --- /dev/null +++ b/src/peds/PedType.cpp @@ -0,0 +1,317 @@ +#include "common.h" + +#include "General.h" +#include "FileMgr.h" +#include "PedType.h" +#include "SaveBuf.h" + +CPedType *CPedType::ms_apPedType[NUM_PEDTYPES]; +CPedStats *CPedStats::ms_apPedStats[NUM_PEDSTATS]; + +void +CPedType::Initialise(void) +{ + int i; + + debug("Initialising CPedType...\n"); + for(i = 0; i < NUM_PEDTYPES; i++){ + ms_apPedType[i] = new CPedType; + ms_apPedType[i]->m_flag = PED_FLAG_PLAYER1; + ms_apPedType[i]->unknown1 = 0.0f; + ms_apPedType[i]->unknown2 = 0.0f; + // unknown3 not initialized + ms_apPedType[i]->unknown4 = 0.0f; + ms_apPedType[i]->unknown5 = 0.0f; + ms_apPedType[i]->m_threats = 0; + ms_apPedType[i]->m_avoid = 0; + } + debug("Loading ped data...\n"); + LoadPedData(); + debug("CPedType ready\n"); +} + +void +CPedType::Shutdown(void) +{ + int i; + debug("Shutting down CPedType...\n"); + for(i = 0; i < NUM_PEDTYPES; i++) + delete ms_apPedType[i]; + debug("CPedType shut down\n"); +} + +void +CPedType::LoadPedData(void) +{ + char *buf; + char line[256]; + char word[32]; + ssize_t bp, buflen; + int lp, linelen; + int type; + uint32 flags; + float f1, f2, f3, f4, f5; + + type = NUM_PEDTYPES; + buf = new char[16 * 1024]; + + CFileMgr::SetDir("DATA"); + buflen = CFileMgr::LoadFile("PED.DAT", (uint8*)buf, 16 * 1024, "r"); + CFileMgr::SetDir(""); + + for(bp = 0; bp < buflen; ){ + // read file line by line + for(linelen = 0; buf[bp] != '\n' && bp < buflen; bp++){ + if(buf[bp] == '\r' || buf[bp] == ',' || buf[bp] == '\t') + line[linelen++] = ' '; + else + line[linelen++] = buf[bp]; + } + bp++; + line[linelen] = '\0'; + + // skip white space + for(lp = 0; line[lp] <= ' '; lp++); + + if(lp >= linelen || // FIX: game uses == here, but this is safer if we have empty lines + line[lp] == '#') + continue; + + // Game uses just "line" here since sscanf already trims whitespace, but this is safer + sscanf(&line[lp], "%s", word); + + if(strcmp(word, "Threat") == 0){ + flags = 0; + lp += 7; + while(sscanf(&line[lp], "%s", word) == 1 && lp <= linelen){ + flags |= FindPedFlag(word); + // skip word + while(line[lp] != ' ' && line[lp] != '\n' && line[lp] != '\0') + lp++; + // skip white space + while(line[lp] == ' ') + lp++; + } + ms_apPedType[type]->m_threats = flags; + }else if(strcmp(word, "Avoid") == 0){ + flags = 0; + lp += 6; + while(sscanf(&line[lp], "%s", word) == 1 && lp <= linelen){ + flags |= FindPedFlag(word); + // skip word + while(line[lp] != ' ' && line[lp] != '\n' && line[lp] != '\0') + lp++; + // skip white space + while(line[lp] == ' ') + lp++; + } + ms_apPedType[type]->m_avoid = flags; + }else{ + sscanf(line, "%s %f %f %f %f %f", word, &f1, &f2, &f3, &f4, &f5); + type = FindPedType(word); + ms_apPedType[type]->m_flag = FindPedFlag(word); + // unknown values + ms_apPedType[type]->unknown1 = f1 / 50.0f; + ms_apPedType[type]->unknown2 = f2 / 50.0f; + ms_apPedType[type]->unknown3 = f3 / 50.0f; + ms_apPedType[type]->unknown4 = f4; + ms_apPedType[type]->unknown5 = f5; + + } + } + + delete[] buf; +} + +ePedType +CPedType::FindPedType(char *type) +{ + if(strcmp(type, "PLAYER1") == 0) return PEDTYPE_PLAYER1; + if(strcmp(type, "PLAYER2") == 0) return PEDTYPE_PLAYER2; + if(strcmp(type, "PLAYER3") == 0) return PEDTYPE_PLAYER3; + if(strcmp(type, "PLAYER4") == 0) return PEDTYPE_PLAYER4; + if(strcmp(type, "CIVMALE") == 0) return PEDTYPE_CIVMALE; + if(strcmp(type, "CIVFEMALE") == 0) return PEDTYPE_CIVFEMALE; + if(strcmp(type, "COP") == 0) return PEDTYPE_COP; + if(strcmp(type, "GANG1") == 0) return PEDTYPE_GANG1; + if(strcmp(type, "GANG2") == 0) return PEDTYPE_GANG2; + if(strcmp(type, "GANG3") == 0) return PEDTYPE_GANG3; + if(strcmp(type, "GANG4") == 0) return PEDTYPE_GANG4; + if(strcmp(type, "GANG5") == 0) return PEDTYPE_GANG5; + if(strcmp(type, "GANG6") == 0) return PEDTYPE_GANG6; + if(strcmp(type, "GANG7") == 0) return PEDTYPE_GANG7; + if(strcmp(type, "GANG8") == 0) return PEDTYPE_GANG8; + if(strcmp(type, "GANG9") == 0) return PEDTYPE_GANG9; + if(strcmp(type, "EMERGENCY") == 0) return PEDTYPE_EMERGENCY; + if(strcmp(type, "FIREMAN") == 0) return PEDTYPE_FIREMAN; + if(strcmp(type, "CRIMINAL") == 0) return PEDTYPE_CRIMINAL; + if(strcmp(type, "SPECIAL") == 0) return PEDTYPE_SPECIAL; + if(strcmp(type, "PROSTITUTE") == 0) return PEDTYPE_PROSTITUTE; + Error("Unknown ped type, Pedtype.cpp"); + return NUM_PEDTYPES; +} + +uint32 +CPedType::FindPedFlag(char *type) +{ + if(strcmp(type, "PLAYER1") == 0) return PED_FLAG_PLAYER1; + if(strcmp(type, "PLAYER2") == 0) return PED_FLAG_PLAYER2; + if(strcmp(type, "PLAYER3") == 0) return PED_FLAG_PLAYER3; + if(strcmp(type, "PLAYER4") == 0) return PED_FLAG_PLAYER4; + if(strcmp(type, "CIVMALE") == 0) return PED_FLAG_CIVMALE; + if(strcmp(type, "CIVFEMALE") == 0) return PED_FLAG_CIVFEMALE; + if(strcmp(type, "COP") == 0) return PED_FLAG_COP; + if(strcmp(type, "GANG1") == 0) return PED_FLAG_GANG1; + if(strcmp(type, "GANG2") == 0) return PED_FLAG_GANG2; + if(strcmp(type, "GANG3") == 0) return PED_FLAG_GANG3; + if(strcmp(type, "GANG4") == 0) return PED_FLAG_GANG4; + if(strcmp(type, "GANG5") == 0) return PED_FLAG_GANG5; + if(strcmp(type, "GANG6") == 0) return PED_FLAG_GANG6; + if(strcmp(type, "GANG7") == 0) return PED_FLAG_GANG7; + if(strcmp(type, "GANG8") == 0) return PED_FLAG_GANG8; + if(strcmp(type, "GANG9") == 0) return PED_FLAG_GANG9; + if(strcmp(type, "EMERGENCY") == 0) return PED_FLAG_EMERGENCY; + if(strcmp(type, "FIREMAN") == 0) return PED_FLAG_FIREMAN; + if(strcmp(type, "CRIMINAL") == 0) return PED_FLAG_CRIMINAL; + if(strcmp(type, "SPECIAL") == 0) return PED_FLAG_SPECIAL; + if(strcmp(type, "GUN") == 0) return PED_FLAG_GUN; + if(strcmp(type, "COP_CAR") == 0) return PED_FLAG_COP_CAR; + if(strcmp(type, "FAST_CAR") == 0) return PED_FLAG_FAST_CAR; + if(strcmp(type, "EXPLOSION") == 0) return PED_FLAG_EXPLOSION; + if(strcmp(type, "PROSTITUTE") == 0) return PED_FLAG_PROSTITUTE; + if(strcmp(type, "DEADPEDS") == 0) return PED_FLAG_DEADPEDS; + return 0; +} + +void +CPedType::Save(uint8 *buf, uint32 *size) +{ + *size = sizeof(CPedType) * NUM_PEDTYPES + SAVE_HEADER_SIZE; +INITSAVEBUF + WriteSaveHeader(buf, 'P','T','P','\0', *size - SAVE_HEADER_SIZE); + for(int i = 0; i < NUM_PEDTYPES; i++) + WriteSaveBuf(buf, *ms_apPedType[i]); +VALIDATESAVEBUF(*size) +} + +void +CPedType::Load(uint8 *buf, uint32 size) +{ +INITSAVEBUF + // original: SkipSaveBuf(buf, SAVE_HEADER_SIZE); + CheckSaveHeader(buf, 'P', 'T', 'P', '\0', size - SAVE_HEADER_SIZE); + + for(int i = 0; i < NUM_PEDTYPES; i++) + ReadSaveBuf(ms_apPedType[i], buf); +VALIDATESAVEBUF(size) +} + +void +CPedStats::Initialise(void) +{ + int i; + + debug("Initialising CPedStats...\n"); + for(i = 0; i < NUM_PEDSTATS; i++){ + ms_apPedStats[i] = new CPedStats; + ms_apPedStats[i]->m_type = PEDSTAT_PLAYER; + ms_apPedStats[i]->m_name[8] = 'R'; // WHAT? + ms_apPedStats[i]->m_fleeDistance = 20.0f; + ms_apPedStats[i]->m_headingChangeRate = 15.0f; + ms_apPedStats[i]->m_fear = 50; + ms_apPedStats[i]->m_temper = 50; + ms_apPedStats[i]->m_lawfulness = 50; + ms_apPedStats[i]->m_sexiness = 50; + ms_apPedStats[i]->m_attackStrength = 1.0f; + ms_apPedStats[i]->m_defendWeakness = 1.0f; + ms_apPedStats[i]->m_flags = 0; + } + debug("Loading pedstats data...\n"); + CPedStats::LoadPedStats(); + debug("CPedStats ready\n"); +} + +void +CPedStats::Shutdown(void) +{ + int i; + debug("Shutting down CPedStats...\n"); + for(i = 0; i < NUM_PEDSTATS; i++) + delete ms_apPedStats[i]; + debug("CPedStats shut down\n"); +} + +void +CPedStats::LoadPedStats(void) +{ + char *buf; + char line[256]; + char name[32]; + ssize_t bp, buflen; + int lp, linelen; + int type; + float fleeDist, headingChangeRate, attackStrength, defendWeakness; + int fear, temper, lawfullness, sexiness, flags; + + type = 0; + buf = new char[16 * 1024]; + + CFileMgr::SetDir("DATA"); + buflen = CFileMgr::LoadFile("PEDSTATS.DAT", (uint8*)buf, 16 * 1024, "r"); + CFileMgr::SetDir(""); + + for(bp = 0; bp < buflen; ){ + // read file line by line + for(linelen = 0; buf[bp] != '\n' && bp < buflen; bp++){ + if(buf[bp] == '\r' || buf[bp] == ',' || buf[bp] == '\t') + line[linelen++] = ' '; + else + line[linelen++] = buf[bp]; + } + bp++; + line[linelen] = '\0'; + + // skip white space + for(lp = 0; line[lp] <= ' '; lp++); + + if(lp >= linelen || // FIX: game uses == here, but this is safer if we have empty lines + line[lp] == '#') + continue; + + sscanf(&line[lp], "%s %f %f %d %d %d %d %f %f %d", + name, + &fleeDist, + &headingChangeRate, + &fear, + &temper, + &lawfullness, + &sexiness, + &attackStrength, + &defendWeakness, + &flags); + ms_apPedStats[type]->m_type = (ePedStats)type; + strncpy(ms_apPedStats[type]->m_name, name, 24); // FIX: game uses strcpy + ms_apPedStats[type]->m_fleeDistance = fleeDist; + ms_apPedStats[type]->m_headingChangeRate = headingChangeRate; + ms_apPedStats[type]->m_fear = fear; + ms_apPedStats[type]->m_temper = temper; + ms_apPedStats[type]->m_lawfulness = lawfullness; + ms_apPedStats[type]->m_sexiness = sexiness; + ms_apPedStats[type]->m_attackStrength = attackStrength; + ms_apPedStats[type]->m_defendWeakness = defendWeakness; + ms_apPedStats[type]->m_flags = flags; + type++; + } + + delete[] buf; +} + +ePedStats +CPedStats::GetPedStatType(char *name) +{ + for(uint16 type = 0; type < NUM_PEDSTATS; type++) + if(!CGeneral::faststrcmp(ms_apPedStats[type]->m_name, name)) + return (ePedStats) type; + + return NUM_PEDSTATS; +} diff --git a/src/peds/PedType.h b/src/peds/PedType.h new file mode 100644 index 0000000..3e23c24 --- /dev/null +++ b/src/peds/PedType.h @@ -0,0 +1,173 @@ +#pragma once + +// Index into the PedType array +enum ePedType +{ + PEDTYPE_PLAYER1, + PEDTYPE_PLAYER2, + PEDTYPE_PLAYER3, + PEDTYPE_PLAYER4, + PEDTYPE_CIVMALE, + PEDTYPE_CIVFEMALE, + PEDTYPE_COP, + PEDTYPE_GANG1, + PEDTYPE_GANG2, + PEDTYPE_GANG3, + PEDTYPE_GANG4, + PEDTYPE_GANG5, + PEDTYPE_GANG6, + PEDTYPE_GANG7, + PEDTYPE_GANG8, + PEDTYPE_GANG9, + PEDTYPE_EMERGENCY, + PEDTYPE_FIREMAN, + PEDTYPE_CRIMINAL, + PEDTYPE_UNUSED1, + PEDTYPE_PROSTITUTE, + PEDTYPE_SPECIAL, + PEDTYPE_UNUSED2, + + NUM_PEDTYPES +}; + +enum +{ + PED_FLAG_PLAYER1 = 1 << 0, + PED_FLAG_PLAYER2 = 1 << 1, + PED_FLAG_PLAYER3 = 1 << 2, + PED_FLAG_PLAYER4 = 1 << 3, + PED_FLAG_CIVMALE = 1 << 4, + PED_FLAG_CIVFEMALE = 1 << 5, + PED_FLAG_COP = 1 << 6, + PED_FLAG_GANG1 = 1 << 7, + PED_FLAG_GANG2 = 1 << 8, + PED_FLAG_GANG3 = 1 << 9, + PED_FLAG_GANG4 = 1 << 10, + PED_FLAG_GANG5 = 1 << 11, + PED_FLAG_GANG6 = 1 << 12, + PED_FLAG_GANG7 = 1 << 13, + PED_FLAG_GANG8 = 1 << 14, + PED_FLAG_GANG9 = 1 << 15, + PED_FLAG_EMERGENCY = 1 << 16, + PED_FLAG_PROSTITUTE = 1 << 17, + PED_FLAG_CRIMINAL = 1 << 18, + PED_FLAG_SPECIAL = 1 << 19, + PED_FLAG_GUN = 1 << 20, + PED_FLAG_COP_CAR = 1 << 21, + PED_FLAG_FAST_CAR = 1 << 22, + PED_FLAG_EXPLOSION = 1 << 23, + PED_FLAG_FIREMAN = 1 << 24, + PED_FLAG_DEADPEDS = 1 << 25, +}; + +class CPedType +{ + uint32 m_flag; + float unknown1; + float unknown2; + float unknown3; + float unknown4; + float unknown5; + uint32 m_threats; + uint32 m_avoid; + + static CPedType *ms_apPedType[NUM_PEDTYPES]; +public: + + static void Initialise(void); + static void Shutdown(void); + static void LoadPedData(void); + static ePedType FindPedType(char *type); + static uint32 FindPedFlag(char *type); + static void Save(uint8 *buf, uint32 *size); + static void Load(uint8 *buf, uint32 size); + + static uint32 GetFlag(int type) { return ms_apPedType[type]->m_flag; } + static uint32 GetAvoid(int type) { return ms_apPedType[type]->m_avoid; } + static uint32 GetThreats(int type) { return ms_apPedType[type]->m_threats; } + static void SetThreats(int type, uint32 threat) { ms_apPedType[type]->m_threats = threat; } + static void AddThreat(int type, int threat) { ms_apPedType[type]->m_threats |= threat; } + static void RemoveThreat(int type, int threat) { ms_apPedType[type]->m_threats &= ~threat; } + static bool IsThreat(int type, int threat) { return ms_apPedType[type]->m_threats & threat; } +}; + +VALIDATE_SIZE(CPedType, 0x20); + +enum ePedStats +{ + PEDSTAT_PLAYER, + PEDSTAT_COP, + PEDSTAT_MEDIC, + PEDSTAT_FIREMAN, + PEDSTAT_GANG1, + PEDSTAT_GANG2, + PEDSTAT_GANG3, + PEDSTAT_GANG4, + PEDSTAT_GANG5, + PEDSTAT_GANG6, + PEDSTAT_GANG7, + PEDSTAT_STREET_GUY, + PEDSTAT_SUIT_GUY, + PEDSTAT_SENSIBLE_GUY, + PEDSTAT_GEEK_GUY, + PEDSTAT_OLD_GUY, + PEDSTAT_TOUGH_GUY, + PEDSTAT_STREET_GIRL, + PEDSTAT_SUIT_GIRL, + PEDSTAT_SENSIBLE_GIRL, + PEDSTAT_GEEK_GIRL, + PEDSTAT_OLD_GIRL, + PEDSTAT_TOUGH_GIRL, + PEDSTAT_TRAMP_MALE, + PEDSTAT_TRAMP_FEMALE, + PEDSTAT_TOURIST, + PEDSTAT_PROSTITUTE, + PEDSTAT_CRIMINAL, + PEDSTAT_BUSKER, + PEDSTAT_TAXIDRIVER, + PEDSTAT_PSYCHO, + PEDSTAT_STEWARD, + PEDSTAT_SPORTSFAN, + PEDSTAT_SHOPPER, + PEDSTAT_OLDSHOPPER, + + NUM_PEDSTATS +}; + +// flags +enum +{ + STAT_PUNCH_ONLY = 1, + STAT_CAN_KNEE_HEAD = 2, + STAT_CAN_KICK = 4, + STAT_CAN_ROUNDHOUSE = 8, + STAT_NO_DIVE = 0x10, + STAT_ONE_HIT_KNOCKDOWN = 0x20, + STAT_SHOPPING_BAGS = 0x40, + STAT_GUN_PANIC = 0x80 +}; + +class CPedStats +{ +public: + ePedStats m_type; + char m_name[24]; + float m_fleeDistance; + float m_headingChangeRate; + int8 m_fear; + int8 m_temper; + int8 m_lawfulness; + int8 m_sexiness; + float m_attackStrength; + float m_defendWeakness; + int16 m_flags; + + static CPedStats *ms_apPedStats[NUM_PEDSTATS]; + + static void Initialise(void); + static void Shutdown(void); + static void LoadPedStats(void); + static ePedStats GetPedStatType(char *name); +}; + +VALIDATE_SIZE(CPedStats, 0x34); \ No newline at end of file diff --git a/src/peds/PlayerPed.cpp b/src/peds/PlayerPed.cpp new file mode 100644 index 0000000..ef87796 --- /dev/null +++ b/src/peds/PlayerPed.cpp @@ -0,0 +1,1581 @@ +#include "common.h" + +#include "RwHelper.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Fire.h" +#include "DMAudio.h" +#include "Pad.h" +#include "Camera.h" +#include "WeaponEffects.h" +#include "ModelIndices.h" +#include "World.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "General.h" +#include "Pools.h" +#include "Darkel.h" +#include "CarCtrl.h" +#include "SaveBuf.h" + +#define PAD_MOVE_TO_GAME_WORLD_MOVE 60.0f + +#ifdef VC_PED_PORTS +bool CPlayerPed::bDontAllowWeaponChange; +#endif + +const uint32 CPlayerPed::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 1520; +#else + sizeof(CPlayerPed); +#endif + +CPlayerPed::~CPlayerPed() +{ + delete m_pWanted; +} + +CPlayerPed::CPlayerPed(void) : CPed(PEDTYPE_PLAYER1) +{ + m_fMoveSpeed = 0.0f; + SetModelIndex(MI_PLAYER); +#ifdef FIX_BUGS + m_fCurrentStamina = m_fMaxStamina = 150.0f; +#endif + SetInitialState(); + + m_pWanted = new CWanted(); + m_pWanted->Initialise(); + m_pArrestingCop = nil; + m_currentWeapon = WEAPONTYPE_UNARMED; + m_nSelectedWepSlot = WEAPONTYPE_UNARMED; + m_nSpeedTimer = 0; + m_bSpeedTimerFlag = false; + SetWeaponLockOnTarget(nil); + SetPedState(PED_IDLE); +#ifndef FIX_BUGS + m_fCurrentStamina = m_fMaxStamina = 150.0f; +#endif + m_fStaminaProgress = 0.0f; + m_nEvadeAmount = 0; + field_1367 = 0; + m_nHitAnimDelayTimer = 0; + m_fAttackButtonCounter = 0.0f; + m_bHaveTargetSelected = false; + m_bHasLockOnTarget = false; + m_bCanBeDamaged = true; + m_fWalkAngle = 0.0f; + m_fFPSMoveHeading = 0.0f; + m_nTargettableObjects[0] = m_nTargettableObjects[1] = m_nTargettableObjects[2] = m_nTargettableObjects[3] = -1; + field_1413 = 0; + for (int i = 0; i < 6; i++) { + m_vecSafePos[i] = CVector(0.0f, 0.0f, 0.0f); + m_pPedAtSafePos[i] = nil; + } +} + +void CPlayerPed::ClearWeaponTarget() +{ + if (m_nPedType == PEDTYPE_PLAYER1) { + SetWeaponLockOnTarget(nil); + TheCamera.ClearPlayerWeaponMode(); + CWeaponEffects::ClearCrossHair(); + } + ClearPointGunAt(); +} + +void +CPlayerPed::SetWantedLevel(int32 level) +{ + m_pWanted->SetWantedLevel(level); +} + +void +CPlayerPed::SetWantedLevelNoDrop(int32 level) +{ + m_pWanted->SetWantedLevelNoDrop(level); +} + +void +CPlayerPed::MakeObjectTargettable(int32 handle) +{ + for (int i = 0; i < ARRAY_SIZE(m_nTargettableObjects); i++) { + if ( +#ifdef FIX_BUGS + m_nTargettableObjects[i] == -1 || +#endif + CPools::GetObjectPool()->GetAt(m_nTargettableObjects[i]) == nil) { + m_nTargettableObjects[i] = handle; + return; + } + } +} + +// I don't know the actual purpose of parameter +void +CPlayerPed::AnnoyPlayerPed(bool annoyedByPassingEntity) +{ + if (m_pedStats->m_temper < 52) { + m_pedStats->m_temper++; + } else if (annoyedByPassingEntity && m_pedStats->m_temper < 55) { + m_pedStats->m_temper++; + } else if (annoyedByPassingEntity) { + m_pedStats->m_temper = 46; + } +} + +void +CPlayerPed::ClearAdrenaline(void) +{ + if (m_bAdrenalineActive && m_nAdrenalineTime != 0) { + m_nAdrenalineTime = 0; + CTimer::SetTimeScale(1.0f); + } +} + +CPlayerInfo * +CPlayerPed::GetPlayerInfoForThisPlayerPed() +{ + if (CWorld::Players[0].m_pPed == this) + return &CWorld::Players[0]; + + return nil; +} + +void +CPlayerPed::SetupPlayerPed(int32 index) +{ + CPlayerPed *player = new CPlayerPed(); + CWorld::Players[index].m_pPed = player; +#ifdef FIX_BUGS + player->RegisterReference((CEntity**)&CWorld::Players[index].m_pPed); +#endif + + player->SetOrientation(0.0f, 0.0f, 0.0f); + + CWorld::Add(player); + player->m_wepAccuracy = 100; +} + +void +CPlayerPed::DeactivatePlayerPed(int32 index) +{ + CWorld::Remove(CWorld::Players[index].m_pPed); +} + +void +CPlayerPed::ReactivatePlayerPed(int32 index) +{ + CWorld::Add(CWorld::Players[index].m_pPed); +} + +void +CPlayerPed::UseSprintEnergy(void) +{ + if (m_fCurrentStamina > -150.0f && !CWorld::Players[CWorld::PlayerInFocus].m_bInfiniteSprint + && !m_bAdrenalineActive) { + m_fCurrentStamina = m_fCurrentStamina - CTimer::GetTimeStep(); + m_fStaminaProgress = m_fStaminaProgress + CTimer::GetTimeStep(); + } + + if (m_fStaminaProgress >= 500.0f) { + m_fStaminaProgress = 0; + if (m_fMaxStamina < 1000.0f) + m_fMaxStamina += 10.0f; + } +} + +void +CPlayerPed::MakeChangesForNewWeapon(int8 weapon) +{ + if (m_nPedState == PED_SNIPER_MODE) { + RestorePreviousState(); + TheCamera.ClearPlayerWeaponMode(); + } + SetCurrentWeapon(weapon); + + GetWeapon()->m_nAmmoInClip = Min(GetWeapon()->m_nAmmoTotal, CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_nAmountofAmmunition); + + if (!CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM)) + ClearWeaponTarget(); + + CAnimBlendAssociation *weaponAnim = RpAnimBlendClumpGetAssociation(GetClump(), CWeaponInfo::GetWeaponInfo(WEAPONTYPE_SNIPERRIFLE)->m_AnimToPlay); + if (weaponAnim) { + weaponAnim->SetRun(); + weaponAnim->flags |= ASSOC_FADEOUTWHENDONE; + } + TheCamera.ClearPlayerWeaponMode(); +} + +void +CPlayerPed::ReApplyMoveAnims(void) +{ + static AnimationId moveAnims[] = { ANIM_STD_WALK, ANIM_STD_RUN, ANIM_STD_RUNFAST, ANIM_STD_IDLE, ANIM_STD_STARTWALK }; + + for(int i = 0; i < ARRAY_SIZE(moveAnims); i++) { + CAnimBlendAssociation *curMoveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), moveAnims[i]); + if (curMoveAssoc) { + if (CGeneral::faststrcmp(CAnimManager::GetAnimAssociation(m_animGroup, moveAnims[i])->hierarchy->name, curMoveAssoc->hierarchy->name)) { + CAnimBlendAssociation *newMoveAssoc = CAnimManager::AddAnimation(GetClump(), m_animGroup, moveAnims[i]); + newMoveAssoc->blendDelta = curMoveAssoc->blendDelta; + newMoveAssoc->blendAmount = curMoveAssoc->blendAmount; + curMoveAssoc->blendDelta = -1000.0f; + curMoveAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + } +} + +void +CPlayerPed::SetInitialState(void) +{ + m_bAdrenalineActive = false; + m_nAdrenalineTime = 0; + CTimer::SetTimeScale(1.0f); + m_pSeekTarget = nil; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_fleeFromPosX = 0.0f; + m_fleeFromPosY = 0.0f; + m_fleeFrom = nil; + m_fleeTimer = 0; + m_objective = OBJECTIVE_NONE; + m_prevObjective = OBJECTIVE_NONE; + bUsesCollision = true; + ClearAimFlag(); + ClearLookFlag(); + bIsPointingGunAt = false; + bRenderPedInCar = true; + if (m_pFire) + m_pFire->Extinguish(); + RpAnimBlendClumpRemoveAllAssociations(GetClump()); + SetPedState(PED_IDLE); + SetMoveState(PEDMOVE_STILL); + m_nLastPedState = PED_NONE; + m_animGroup = ASSOCGRP_PLAYER; + m_fMoveSpeed = 0.0f; + m_nSelectedWepSlot = WEAPONTYPE_UNARMED; + m_nEvadeAmount = 0; + m_pEvadingFrom = nil; + bIsPedDieAnimPlaying = false; + SetRealMoveAnim(); + m_bCanBeDamaged = true; + m_pedStats->m_temper = 50; + m_fWalkAngle = 0.0f; +} + +void +CPlayerPed::SetRealMoveAnim(void) +{ + CAnimBlendAssociation *curWalkAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_WALK); + CAnimBlendAssociation *curRunAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUN); + CAnimBlendAssociation *curSprintAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNFAST); + CAnimBlendAssociation *curWalkStartAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_STARTWALK); + CAnimBlendAssociation *curIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + CAnimBlendAssociation *curRunStopAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP1); + CAnimBlendAssociation *curRunStopRAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + if (bResetWalkAnims) { + if (curWalkAssoc) + curWalkAssoc->SetCurrentTime(0.0f); + if (curRunAssoc) + curRunAssoc->SetCurrentTime(0.0f); + if (curSprintAssoc) + curSprintAssoc->SetCurrentTime(0.0f); + bResetWalkAnims = false; + } + + if (!curIdleAssoc) + curIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + if (!curIdleAssoc) + curIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); + + if (!((curRunStopAssoc && curRunStopAssoc->IsRunning()) || (curRunStopRAssoc && curRunStopRAssoc->IsRunning()))) { + + if (curRunStopAssoc && curRunStopAssoc->blendDelta >= 0.0f || curRunStopRAssoc && curRunStopRAssoc->blendDelta >= 0.0f) { + if (curRunStopAssoc) { + curRunStopAssoc->flags |= ASSOC_DELETEFADEDOUT; + curRunStopAssoc->blendAmount = 1.0f; + curRunStopAssoc->blendDelta = -8.0f; + } else if (curRunStopRAssoc) { + curRunStopRAssoc->flags |= ASSOC_DELETEFADEDOUT; + curRunStopRAssoc->blendAmount = 1.0f; + curRunStopRAssoc->blendDelta = -8.0f; + } + + RestoreHeadingRate(); + if (!curIdleAssoc) { + if (m_fCurrentStamina < 0.0f && !CWorld::TestSphereAgainstWorld(GetPosition(), 0.5f, + nil, true, false, false, false, false, false)) { + curIdleAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_TIRED, 8.0f); + + } else { + curIdleAssoc = CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 8.0f); + } + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(2500, 4000); + } + curIdleAssoc->blendAmount = 0.0f; + curIdleAssoc->blendDelta = 8.0f; + + } else if (m_fMoveSpeed == 0.0f && !curSprintAssoc) { + if (!curIdleAssoc) { + if (m_fCurrentStamina < 0.0f && !CWorld::TestSphereAgainstWorld(GetPosition(), 0.5f, + nil, true, false, false, false, false, false)) { + curIdleAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_TIRED, 4.0f); + + } else { + curIdleAssoc = CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + } + + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(2500, 4000); + } + + if (m_fCurrentStamina > 0.0f && curIdleAssoc->animId == ANIM_STD_IDLE_TIRED) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + + } else if (m_nPedState != PED_FIGHT) { + if (m_fCurrentStamina < 0.0f && curIdleAssoc->animId != ANIM_STD_IDLE_TIRED + && !CWorld::TestSphereAgainstWorld(GetPosition(), 0.5f, nil, true, false, false, false, false, false)) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_TIRED, 4.0f); + + } else if (curIdleAssoc->animId != ANIM_STD_IDLE) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + } + } + m_nMoveState = PEDMOVE_STILL; + + } else { + if (curIdleAssoc) { + if (curWalkStartAssoc) { + curWalkStartAssoc->blendAmount = 1.0f; + curWalkStartAssoc->blendDelta = 0.0f; + } else { + curWalkStartAssoc = CAnimManager::AddAnimation(GetClump(), m_animGroup, ANIM_STD_STARTWALK); + } + if (curWalkAssoc) + curWalkAssoc->SetCurrentTime(0.0f); + if (curRunAssoc) + curRunAssoc->SetCurrentTime(0.0f); + + delete curIdleAssoc; + delete RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + delete RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); + delete curSprintAssoc; + + curSprintAssoc = nil; + m_nMoveState = PEDMOVE_WALK; + } + if (curRunStopAssoc) { + delete curRunStopAssoc; + RestoreHeadingRate(); + } + if (curRunStopRAssoc) { + delete curRunStopRAssoc; + RestoreHeadingRate(); + } + if (!curWalkAssoc) { + curWalkAssoc = CAnimManager::AddAnimation(GetClump(), m_animGroup, ANIM_STD_WALK); + curWalkAssoc->blendAmount = 0.0f; + } + if (!curRunAssoc) { + curRunAssoc = CAnimManager::AddAnimation(GetClump(), m_animGroup, ANIM_STD_RUN); + curRunAssoc->blendAmount = 0.0f; + } + if (curWalkStartAssoc && !(curWalkStartAssoc->IsRunning())) { + delete curWalkStartAssoc; + curWalkStartAssoc = nil; + curWalkAssoc->SetRun(); + curRunAssoc->SetRun(); + } + if (m_nMoveState == PEDMOVE_SPRINT) { + if (m_fCurrentStamina < 0.0f && (m_fCurrentStamina <= -150.0f || !curSprintAssoc || curSprintAssoc->blendDelta < 0.0f)) + m_nMoveState = PEDMOVE_STILL; + + if (curWalkStartAssoc) + m_nMoveState = PEDMOVE_STILL; + } + + if (curSprintAssoc && (m_nMoveState != PEDMOVE_SPRINT || m_fMoveSpeed < 0.4f)) { + // Stop sprinting in various conditions + if (curSprintAssoc->blendAmount == 0.0f) { + curSprintAssoc->blendDelta = -1000.0f; + curSprintAssoc->flags |= ASSOC_DELETEFADEDOUT; + + } else if (curSprintAssoc->blendDelta >= 0.0f || curSprintAssoc->blendAmount >= 0.8f) { + if (m_fMoveSpeed < 0.4f) { + AnimationId runStopAnim; + if (curSprintAssoc->currentTime / curSprintAssoc->hierarchy->totalLength < 0.5) // double + runStopAnim = ANIM_STD_RUNSTOP1; + else + runStopAnim = ANIM_STD_RUNSTOP2; + CAnimBlendAssociation* newRunStopAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, runStopAnim); + newRunStopAssoc->blendAmount = 1.0f; + newRunStopAssoc->SetDeleteCallback(RestoreHeadingRateCB, this); + m_headingRate = 0.0f; + curSprintAssoc->flags |= ASSOC_DELETEFADEDOUT; + curSprintAssoc->blendDelta = -1000.0f; + curWalkAssoc->flags &= ~ASSOC_RUNNING; + curWalkAssoc->blendAmount = 0.0f; + curWalkAssoc->blendDelta = 0.0f; + curRunAssoc->flags &= ~ASSOC_RUNNING; + curRunAssoc->blendAmount = 0.0f; + curRunAssoc->blendDelta = 0.0f; + + } else if (curSprintAssoc->blendDelta >= 0.0f) { + // Stop sprinting when tired + curSprintAssoc->flags |= ASSOC_DELETEFADEDOUT; + curSprintAssoc->blendDelta = -1.0f; + curRunAssoc->blendDelta = 1.0f; + } + } else if (m_fMoveSpeed < 1.0f) { + curSprintAssoc->blendDelta = -8.0f; + curRunAssoc->blendDelta = 8.0f; + } + + } else if (curWalkStartAssoc) { + // Walk start and walk/run shouldn't run at the same time + curWalkAssoc->flags &= ~ASSOC_RUNNING; + curRunAssoc->flags &= ~ASSOC_RUNNING; + curWalkAssoc->blendAmount = 0.0f; + curRunAssoc->blendAmount = 0.0f; + + } else if (m_nMoveState == PEDMOVE_SPRINT) { + if (curSprintAssoc) { + // We have anim, do it + if (curSprintAssoc->blendDelta < 0.0f) { + curSprintAssoc->blendDelta = 2.0f; + curRunAssoc->blendDelta = -2.0f; + } + } else { + // Transition between run-sprint + curWalkAssoc->blendAmount = 0.0f; + curRunAssoc->blendAmount = 1.0f; + curSprintAssoc = CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_RUNFAST, 2.0f); + } + UseSprintEnergy(); + } else { + if (m_fMoveSpeed < 1.0f) { + curWalkAssoc->blendAmount = 1.0f; + curRunAssoc->blendAmount = 0.0f; + m_nMoveState = PEDMOVE_WALK; + } else if (m_fMoveSpeed < 2.0f) { + curWalkAssoc->blendAmount = 2.0f - m_fMoveSpeed; + curRunAssoc->blendAmount = m_fMoveSpeed - 1.0f; + m_nMoveState = PEDMOVE_RUN; + } else { + curWalkAssoc->blendAmount = 0.0f; + curRunAssoc->blendAmount = 1.0f; + m_nMoveState = PEDMOVE_RUN; + } + } + } + } + if (m_bAdrenalineActive) { + if (CTimer::GetTimeInMilliseconds() > m_nAdrenalineTime) { + m_bAdrenalineActive = false; + CTimer::SetTimeScale(1.0f); + if (curWalkStartAssoc) + curWalkStartAssoc->speed = 1.0f; + if (curWalkAssoc) + curWalkAssoc->speed = 1.0f; + if (curRunAssoc) + curRunAssoc->speed = 1.0f; + if (curSprintAssoc) + curSprintAssoc->speed = 1.0f; + } else { + CTimer::SetTimeScale(1.0f / 3); + if (curWalkStartAssoc) + curWalkStartAssoc->speed = 2.0f; + if (curWalkAssoc) + curWalkAssoc->speed = 2.0f; + if (curRunAssoc) + curRunAssoc->speed = 2.0f; + if (curSprintAssoc) + curSprintAssoc->speed = 2.0f; + } + } +} + +void +CPlayerPed::RestoreSprintEnergy(float restoreSpeed) +{ + if (m_fCurrentStamina < m_fMaxStamina) + m_fCurrentStamina += restoreSpeed * CTimer::GetTimeStep() * 0.5f; +} + +bool +CPlayerPed::DoWeaponSmoothSpray(void) +{ + if (m_nPedState == PED_ATTACK && !m_pPointGunAt) { + eWeaponType weapon = GetWeapon()->m_eWeaponType; +#ifdef FREE_CAM + if(CCamera::bFreeCam && TheCamera.Cams[0].Using3rdPersonMouseCam() && (weapon == WEAPONTYPE_COLT45 || weapon == WEAPONTYPE_UZI)) + return false; +#endif + if (weapon == WEAPONTYPE_FLAMETHROWER || weapon == WEAPONTYPE_COLT45 || weapon == WEAPONTYPE_UZI || weapon == WEAPONTYPE_SHOTGUN || + weapon == WEAPONTYPE_AK47 || weapon == WEAPONTYPE_M16 || weapon == WEAPONTYPE_HELICANNON) + return true; + } + return false; +} + +void +CPlayerPed::DoStuffToGoOnFire(void) +{ + if (m_nPedState == PED_SNIPER_MODE) + TheCamera.ClearPlayerWeaponMode(); +} + +bool +CPlayerPed::DoesTargetHaveToBeBroken(CVector target, CWeapon *weaponUsed) +{ + CVector distVec = target - GetPosition(); + + if (distVec.Magnitude() > CWeaponInfo::GetWeaponInfo(weaponUsed->m_eWeaponType)->m_fRange) + return true; + + if (weaponUsed->m_eWeaponType != WEAPONTYPE_SHOTGUN && weaponUsed->m_eWeaponType != WEAPONTYPE_AK47) + return false; + + distVec.Normalise(); + + if (DotProduct(distVec,GetForward()) < 0.4f) + return true; + + return false; +} + +// Cancels landing anim while running & jumping? I think +void +CPlayerPed::RunningLand(CPad *padUsed) +{ + CAnimBlendAssociation *landAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL_LAND); + if (landAssoc && landAssoc->currentTime == 0.0f && m_fMoveSpeed > 1.5f + && padUsed && (padUsed->GetPedWalkLeftRight() != 0.0f || padUsed->GetPedWalkUpDown() != 0.0f)) { + + landAssoc->blendDelta = -1000.0f; + landAssoc->flags |= ASSOC_DELETEFADEDOUT; + + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_JUMP_LAND)->SetFinishCallback(FinishJumpCB, this); + + if (m_nPedState == PED_JUMP) + RestorePreviousState(); + } +} + +bool +CPlayerPed::IsThisPedAttackingPlayer(CPed *suspect) +{ + if (suspect->m_pPointGunAt == this) + return true; + + switch (suspect->m_objective) { + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + if (suspect->m_pedInObjective == this) + return true; + + break; + default: + break; + } + return false; +} + +void +CPlayerPed::PlayerControlSniper(CPad *padUsed) +{ + ProcessWeaponSwitch(padUsed); + TheCamera.PlayerExhaustion = (1.0f - (m_fCurrentStamina - -150.0f) / 300.0f) * 0.9f + 0.1f; + + if (!padUsed->GetTarget()) { + RestorePreviousState(); + TheCamera.ClearPlayerWeaponMode(); + } + + if (padUsed->WeaponJustDown()) { + CVector firePos(0.0f, 0.0f, 0.6f); + firePos = GetMatrix() * firePos; + GetWeapon()->Fire(this, &firePos); + } + GetWeapon()->Update(m_audioEntityId); +} + +// I think R* also used goto in here. +void +CPlayerPed::ProcessWeaponSwitch(CPad *padUsed) +{ + if (CDarkel::FrenzyOnGoing()) + goto switchDetectDone; + +#ifdef VC_PED_PORTS + if (padUsed->CycleWeaponRightJustDown() && !m_pPointGunAt && !bDontAllowWeaponChange) { +#else + if (padUsed->CycleWeaponRightJustDown() && !m_pPointGunAt) { +#endif + + if (TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON_RUNABOUT + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER_RUNABOUT + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_ROCKETLAUNCHER + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_ROCKETLAUNCHER_RUNABOUT) { + + for (m_nSelectedWepSlot = m_currentWeapon + 1; m_nSelectedWepSlot < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; ++m_nSelectedWepSlot) { + if (HasWeapon(m_nSelectedWepSlot) && GetWeapon(m_nSelectedWepSlot).HasWeaponAmmoToBeUsed()) { + goto switchDetectDone; + } + } + m_nSelectedWepSlot = WEAPONTYPE_UNARMED; + } +#ifdef VC_PED_PORTS + } else if (padUsed->CycleWeaponLeftJustDown() && !m_pPointGunAt && !bDontAllowWeaponChange) { +#else + } else if (padUsed->CycleWeaponLeftJustDown() && !m_pPointGunAt) { +#endif + if (TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_ROCKETLAUNCHER) { + + for (m_nSelectedWepSlot = m_currentWeapon - 1; ; --m_nSelectedWepSlot) { + if (m_nSelectedWepSlot < WEAPONTYPE_UNARMED) + m_nSelectedWepSlot = WEAPONTYPE_DETONATOR; + + if (HasWeapon(m_nSelectedWepSlot) && GetWeapon(m_nSelectedWepSlot).HasWeaponAmmoToBeUsed()) { + goto switchDetectDone; + } + } + } + // Out of ammo, switch to another weapon + } else if (CWeaponInfo::GetWeaponInfo((eWeaponType)m_currentWeapon)->m_eWeaponFire != WEAPON_FIRE_MELEE) { + if (GetWeapon(m_currentWeapon).m_nAmmoTotal <= 0) { + if (TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON + || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_SNIPER + || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_ROCKETLAUNCHER) + return; + + for (m_nSelectedWepSlot = m_currentWeapon - 1; m_nSelectedWepSlot >= 0; --m_nSelectedWepSlot) { + if (m_nSelectedWepSlot == WEAPONTYPE_BASEBALLBAT && HasWeapon(WEAPONTYPE_BASEBALLBAT) + || GetWeapon(m_nSelectedWepSlot).m_nAmmoTotal > 0 && m_nSelectedWepSlot != WEAPONTYPE_MOLOTOV && m_nSelectedWepSlot != WEAPONTYPE_GRENADE) { + goto switchDetectDone; + } + } + m_nSelectedWepSlot = WEAPONTYPE_UNARMED; + } + } + +switchDetectDone: + if (m_nSelectedWepSlot != m_currentWeapon) { + if (m_nPedState != PED_ATTACK && m_nPedState != PED_AIM_GUN && m_nPedState != PED_FIGHT) + MakeChangesForNewWeapon(m_nSelectedWepSlot); + } +} + +void +CPlayerPed::PlayerControlM16(CPad *padUsed) +{ + ProcessWeaponSwitch(padUsed); + TheCamera.PlayerExhaustion = (1.0f - (m_fCurrentStamina - -150.0f) / 300.0f) * 0.9f + 0.1f; + + if (!padUsed->GetTarget()) { + RestorePreviousState(); + TheCamera.ClearPlayerWeaponMode(); + } + + if (padUsed->GetWeapon()) { + CVector firePos(0.0f, 0.0f, 0.6f); + firePos = GetMatrix() * firePos; + GetWeapon()->Fire(this, &firePos); + } + GetWeapon()->Update(m_audioEntityId); +} + +void +CPlayerPed::PlayerControlFighter(CPad *padUsed) +{ + float leftRight = padUsed->GetPedWalkLeftRight(); + float upDown = padUsed->GetPedWalkUpDown(); + float padMove = CVector2D(leftRight, upDown).Magnitude(); + + if (padMove > 0.0f) { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(0.0f, 0.0f, -leftRight, upDown) - TheCamera.Orientation; + m_takeAStepAfterAttack = padMove > (2 * PAD_MOVE_TO_GAME_WORLD_MOVE); + if (padUsed->GetSprint() && padMove > (1 * PAD_MOVE_TO_GAME_WORLD_MOVE)) + bIsAttacking = false; + } + + if (!CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY) && padUsed->JumpJustDown()) { + if (m_nEvadeAmount != 0 && m_pEvadingFrom) { + SetEvasiveDive((CPhysical*)m_pEvadingFrom, 1); + m_nEvadeAmount = 0; + m_pEvadingFrom = nil; + } else { + SetJump(); + } + } +} + +void +CPlayerPed::PlayerControl1stPersonRunAround(CPad *padUsed) +{ + float leftRight = padUsed->GetPedWalkLeftRight(); + float upDown = padUsed->GetPedWalkUpDown(); + float padMove = CVector2D(leftRight, upDown).Magnitude(); + float padMoveInGameUnit = padMove / PAD_MOVE_TO_GAME_WORLD_MOVE; + if (padMoveInGameUnit > 0.0f) { + m_fRotationDest = CGeneral::LimitRadianAngle(TheCamera.Orientation); + m_fMoveSpeed = Min(padMoveInGameUnit, 0.07f * CTimer::GetTimeStep() + m_fMoveSpeed); + } else { + m_fMoveSpeed = 0.0f; + } + + if (m_nPedState == PED_JUMP) { + if (bIsInTheAir) { + if (bUsesCollision && !bHitSteepSlope && (!bHitSomethingLastFrame || m_vecDamageNormal.z > 0.6f) + && m_fDistanceTravelled < CTimer::GetTimeStepInSeconds() && m_vecMoveSpeed.MagnitudeSqr() < 0.01f) { + + float angleSin = Sin(m_fRotationCur); // originally sin(DEGTORAD(RADTODEG(m_fRotationCur))) o_O + float angleCos = Cos(m_fRotationCur); + ApplyMoveForce(-angleSin * 3.0f, 3.0f * angleCos, 0.05f); + } + } else if (bIsLanding) { + m_fMoveSpeed = 0.0f; + } + } + if (!CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY) && padUsed->GetSprint()) { + m_nMoveState = PEDMOVE_SPRINT; + } + if (m_nPedState != PED_FIGHT) + SetRealMoveAnim(); + + if (!bIsInTheAir && !(CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY)) + && padUsed->JumpJustDown() && m_nPedState != PED_JUMP) { + ClearAttack(); + ClearWeaponTarget(); + if (m_nEvadeAmount != 0 && m_pEvadingFrom) { + SetEvasiveDive((CPhysical*)m_pEvadingFrom, 1); + m_nEvadeAmount = 0; + m_pEvadingFrom = nil; + } else { + SetJump(); + } + } +} + +void +CPlayerPed::KeepAreaAroundPlayerClear(void) +{ + BuildPedLists(); + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->CharCreatedBy == RANDOM_CHAR && !nearPed->DyingOrDead()) { + if (nearPed->GetIsOnScreen()) { + if (nearPed->m_objective == OBJECTIVE_NONE) { + nearPed->SetFindPathAndFlee(this, 5000, true); + } else { + if (nearPed->EnteringCar()) + nearPed->QuitEnteringCar(); + + nearPed->ClearObjective(); + } + } else { + nearPed->FlagToDestroyWhenNextProcessed(); + } + } + } + CVector playerPos = (InVehicle() ? m_pMyVehicle->GetPosition() : GetPosition()); + + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity *vehicles[8]; + CWorld::FindObjectsInRange(pos, CHECK_NEARBY_THINGS_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int i = 0; i < lastVehicle; i++) { + CVehicle *veh = (CVehicle*)vehicles[i]; + if (veh->VehicleCreatedBy != MISSION_VEHICLE) { + if (veh->GetStatus() != STATUS_PLAYER && veh->GetStatus() != STATUS_PLAYER_DISABLED) { + if ((veh->GetPosition() - playerPos).MagnitudeSqr() > 25.0f) { + veh->AutoPilot.m_nTempAction = TEMPACT_WAIT; + veh->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 5000; + } else { + if (DotProduct2D(playerPos - veh->GetPosition(), veh->GetForward()) > 0.0f) + veh->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + else + veh->AutoPilot.m_nTempAction = TEMPACT_GOFORWARD; + + veh->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2000; + } + CCarCtrl::PossiblyRemoveVehicle(veh); + } + } + } +} + +void +CPlayerPed::EvaluateNeighbouringTarget(CEntity *candidate, CEntity **targetPtr, float *lastCloseness, float distLimit, float angleOffset, bool lookToLeft) +{ + CVector distVec = candidate->GetPosition() - GetPosition(); + if (distVec.Magnitude2D() <= distLimit) { + if (!DoesTargetHaveToBeBroken(candidate->GetPosition(), GetWeapon())) { +#ifdef VC_PED_PORTS + float angleBetweenUs = CGeneral::GetATanOfXY(candidate->GetPosition().x - TheCamera.GetPosition().x, + candidate->GetPosition().y - TheCamera.GetPosition().y); +#else + float angleBetweenUs = CGeneral::GetATanOfXY(distVec.x, distVec.y); +#endif + angleBetweenUs = CGeneral::LimitAngle(angleBetweenUs - angleOffset); + float closeness; + if (lookToLeft) { + closeness = angleBetweenUs > 0.0f ? -Abs(angleBetweenUs) : -100000.0f; + } else { + closeness = angleBetweenUs > 0.0f ? -100000.0f : -Abs(angleBetweenUs); + } + + if (closeness > *lastCloseness) { + *targetPtr = candidate; + *lastCloseness = closeness; + } + } + } +} + +void +CPlayerPed::EvaluateTarget(CEntity *candidate, CEntity **targetPtr, float *lastCloseness, float distLimit, float angleOffset, bool priority) +{ + CVector distVec = candidate->GetPosition() - GetPosition(); + float dist = distVec.Magnitude2D(); + if (dist <= distLimit) { + if (!DoesTargetHaveToBeBroken(candidate->GetPosition(), GetWeapon())) { + float angleBetweenUs = CGeneral::GetATanOfXY(distVec.x, distVec.y); + angleBetweenUs = CGeneral::LimitAngle(angleBetweenUs - angleOffset); + + float closeness = -dist - 5.0f * Abs(angleBetweenUs); + if (priority) { + closeness += 5.0f; + } + + if (closeness > *lastCloseness) { + *targetPtr = candidate; + *lastCloseness = closeness; + } + } + } +} + +bool +CPlayerPed::FindNextWeaponLockOnTarget(CEntity *previousTarget, bool lookToLeft) +{ + CEntity *nextTarget = nil; + float weaponRange = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_fRange; + // nextTarget = nil; // duplicate + float lastCloseness = -10000.0f; + // CGeneral::GetATanOfXY(GetForward().x, GetForward().y); // unused +#ifdef VC_PED_PORTS + CVector distVec = previousTarget->GetPosition() - TheCamera.GetPosition(); +#else + CVector distVec = previousTarget->GetPosition() - GetPosition(); +#endif + float referenceBeta = CGeneral::GetATanOfXY(distVec.x, distVec.y); + + for (int h = CPools::GetPedPool()->GetSize() - 1; h >= 0; h--) { + CPed *pedToCheck = CPools::GetPedPool()->GetSlot(h); + if (pedToCheck) { + if (pedToCheck != FindPlayerPed() && pedToCheck != previousTarget) { + if (!pedToCheck->DyingOrDead() && !pedToCheck->bInVehicle + && pedToCheck->m_leader != FindPlayerPed() && OurPedCanSeeThisOne(pedToCheck)) { + + EvaluateNeighbouringTarget(pedToCheck, &nextTarget, &lastCloseness, + weaponRange, referenceBeta, lookToLeft); + } + } + } + } + for (int i = 0; i < ARRAY_SIZE(m_nTargettableObjects); i++) { + CObject *obj = CPools::GetObjectPool()->GetAt(m_nTargettableObjects[i]); + if (obj) + EvaluateNeighbouringTarget(obj, &nextTarget, &lastCloseness, weaponRange, referenceBeta, lookToLeft); + } + if (!nextTarget) + return false; + + SetWeaponLockOnTarget(nextTarget); +#ifdef VC_PED_PORTS + bDontAllowWeaponChange = true; +#endif + SetPointGunAt(nextTarget); + return true; +} + +bool +CPlayerPed::FindWeaponLockOnTarget(void) +{ + CEntity *nextTarget = nil; + float weaponRange = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_fRange; + + if (m_pPointGunAt) { + CVector distVec = m_pPointGunAt->GetPosition() - GetPosition(); + if (distVec.Magnitude2D() > weaponRange) { + SetWeaponLockOnTarget(nil); + return false; + } else { + return true; + } + } + + // nextTarget = nil; // duplicate + float lastCloseness = -10000.0f; + float referenceBeta = CGeneral::GetATanOfXY(GetForward().x, GetForward().y); + for (int h = CPools::GetPedPool()->GetSize() - 1; h >= 0; h--) { + CPed *pedToCheck = CPools::GetPedPool()->GetSlot(h); + if (pedToCheck) { + if (pedToCheck != FindPlayerPed()) { + if (!pedToCheck->DyingOrDead() && !pedToCheck->bInVehicle + && pedToCheck->m_leader != FindPlayerPed() && OurPedCanSeeThisOne(pedToCheck)) { + + EvaluateTarget(pedToCheck, &nextTarget, &lastCloseness, + weaponRange, referenceBeta, IsThisPedAttackingPlayer(pedToCheck)); + } + } + } + } + for (int i = 0; i < ARRAY_SIZE(m_nTargettableObjects); i++) { + CObject *obj = CPools::GetObjectPool()->GetAt(m_nTargettableObjects[i]); + if (obj) + EvaluateTarget(obj, &nextTarget, &lastCloseness, weaponRange, referenceBeta, false); + } + if (!nextTarget) + return false; + + SetWeaponLockOnTarget(nextTarget); +#ifdef VC_PED_PORTS + bDontAllowWeaponChange = true; +#endif + SetPointGunAt(nextTarget); + return true; +} + +void +CPlayerPed::ProcessAnimGroups(void) +{ + AssocGroupId groupToSet; + +#ifdef PC_PLAYER_CONTROLS + if ((m_fWalkAngle <= -DEGTORAD(50.0f) || m_fWalkAngle >= DEGTORAD(50.0f)) + && TheCamera.Cams[TheCamera.ActiveCam].Using3rdPersonMouseCam() + && CanStrafeOrMouseControl()) { + + if (m_fWalkAngle >= -DEGTORAD(130.0f) && m_fWalkAngle <= DEGTORAD(130.0f)) { + if (m_fWalkAngle > 0.0f) { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) + groupToSet = ASSOCGRP_ROCKETLEFT; + else + groupToSet = ASSOCGRP_PLAYERLEFT; + } else { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) + groupToSet = ASSOCGRP_ROCKETRIGHT; + else + groupToSet = ASSOCGRP_PLAYERRIGHT; + } + } else { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) + groupToSet = ASSOCGRP_ROCKETBACK; + else + groupToSet = ASSOCGRP_PLAYERBACK; + } + } else +#endif + { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) { + groupToSet = ASSOCGRP_PLAYERROCKET; + } else { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT) { + groupToSet = ASSOCGRP_PLAYERBBBAT; + } else if (GetWeapon()->m_eWeaponType != WEAPONTYPE_COLT45 && GetWeapon()->m_eWeaponType != WEAPONTYPE_UZI) { + if (!GetWeapon()->IsType2Handed()) { + groupToSet = ASSOCGRP_PLAYER; + } else { + groupToSet = ASSOCGRP_PLAYER2ARMED; + } + } else { + groupToSet = ASSOCGRP_PLAYER1ARMED; + } + } + } + + if (m_animGroup != groupToSet) { + m_animGroup = groupToSet; + ReApplyMoveAnims(); + } +} + +void +CPlayerPed::ProcessPlayerWeapon(CPad *padUsed) +{ + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + if (m_bHasLockOnTarget && !m_pPointGunAt) { + TheCamera.ClearPlayerWeaponMode(); + CWeaponEffects::ClearCrossHair(); + ClearPointGunAt(); + } + if (!m_pFire) { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER || + GetWeapon()->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE || GetWeapon()->m_eWeaponType == WEAPONTYPE_M16) { + if (padUsed->TargetJustDown()) { + SetStoredState(); + SetPedState(PED_SNIPER_MODE); +#ifdef FREE_CAM + if (CCamera::bFreeCam && TheCamera.Cams[0].Using3rdPersonMouseCam()) { + m_fRotationCur = CGeneral::LimitRadianAngle(-TheCamera.Orientation); + SetHeading(m_fRotationCur); + } +#endif + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_ROCKETLAUNCHER, 0, 0); + else if (GetWeapon()->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE) + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_SNIPER, 0, 0); + else + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_M16_1STPERSON, 0, 0); + + m_fMoveSpeed = 0.0f; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE, 1000.0f); + } + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER || GetWeapon()->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE + || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON) + return; + } + } + + if (padUsed->GetWeapon() && m_nMoveState != PEDMOVE_SPRINT) { + if (m_nSelectedWepSlot == m_currentWeapon) { + if (m_pPointGunAt) { +#ifdef FREE_CAM + if (CCamera::bFreeCam && weaponInfo->m_eWeaponFire == WEAPON_FIRE_MELEE && m_fMoveSpeed < 1.0f) + StartFightAttack(padUsed->GetWeapon()); + else +#endif + SetAttack(m_pPointGunAt); + } else if (m_currentWeapon != WEAPONTYPE_UNARMED) { + if (m_nPedState == PED_ATTACK) { + if (padUsed->WeaponJustDown()) { + m_bHaveTargetSelected = true; + } else if (!m_bHaveTargetSelected) { + m_fAttackButtonCounter += CTimer::GetTimeStepNonClipped(); + } + } else { + m_fAttackButtonCounter = 0.0f; + m_bHaveTargetSelected = false; + } + SetAttack(nil); + } else if (padUsed->WeaponJustDown()) { + if (m_fMoveSpeed < 1.0f) + StartFightAttack(padUsed->GetWeapon()); + else + SetAttack(nil); + } + } + } else { + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + if (m_nPedState == PED_ATTACK) { + m_bHaveTargetSelected = true; + bIsAttacking = false; + } + } + +#ifdef FREE_CAM + static int8 changedHeadingRate = 0; + static int8 pointedGun = 0; + if (changedHeadingRate == 2) changedHeadingRate = 1; + if (pointedGun == 2) pointedGun = 1; + + // Rotate player/arm when shooting. We don't have auto-rotation anymore + if (CCamera::m_bUseMouse3rdPerson && CCamera::bFreeCam && + m_nSelectedWepSlot == m_currentWeapon && m_nMoveState != PEDMOVE_SPRINT) { + + // Weapons except throwable and melee ones + if (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM) || weaponInfo->IsFlagSet(WEAPONFLAG_1ST_PERSON) || weaponInfo->IsFlagSet(WEAPONFLAG_EXPANDS)) { + if ((padUsed->GetTarget() && weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) || padUsed->GetWeapon()) { + float limitedCam = CGeneral::LimitRadianAngle(-TheCamera.Orientation); + + m_cachedCamSource = TheCamera.Cams[TheCamera.ActiveCam].Source; + m_cachedCamFront = TheCamera.Cams[TheCamera.ActiveCam].Front; + m_cachedCamUp = TheCamera.Cams[TheCamera.ActiveCam].Up; + + // On this one we can rotate arm. + if (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) { + pointedGun = 2; + m_bFreeAimActive = true; + SetLookFlag(limitedCam, true); + SetAimFlag(limitedCam); +#ifdef VC_PED_PORTS + SetLookTimer(INT32_MAX); // removing this makes head move for real, but I experinced some bugs. +#endif + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); + if (m_nPedState != PED_ATTACK && m_nPedState != PED_AIM_GUN) { + // This is a seperate ped state just for pointing gun. Used for target button + SetPointGunAt(nil); + } + } else { + m_fRotationDest = limitedCam; + changedHeadingRate = 2; + m_headingRate = 12.5f; + + // Anim. fix for shotgun, ak47 and m16 (we must finish rot. it quickly) + if (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM) && padUsed->WeaponJustDown()) { + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + float limitedRotDest = m_fRotationDest; + + if (m_fRotationCur - PI > m_fRotationDest) { + limitedRotDest += 2 * PI; + } else if (PI + m_fRotationCur < m_fRotationDest) { + limitedRotDest -= 2 * PI; + } + + m_fRotationCur += (limitedRotDest - m_fRotationCur) / 2; + } + } + } + } + } + if (changedHeadingRate == 1) { + changedHeadingRate = 0; + RestoreHeadingRate(); + } + if (pointedGun == 1) { + if (m_nPedState == PED_ATTACK) { + if (!padUsed->GetWeapon() && (m_pedIK.m_flags & CPedIK::GUN_POINTED_SUCCESSFULLY) == 0) { + float limitedCam = CGeneral::LimitRadianAngle(-TheCamera.Orientation); + + SetAimFlag(limitedCam); + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); + m_bFreeAimActive = true; + } + } else { + pointedGun = 0; + ClearPointGunAt(); + } + } +#endif + + if (padUsed->GetTarget() && m_nSelectedWepSlot == m_currentWeapon && m_nMoveState != PEDMOVE_SPRINT) { + if (m_pPointGunAt) { + // what?? + if (!m_pPointGunAt +#ifdef FREE_CAM + || (!CCamera::bFreeCam && CCamera::m_bUseMouse3rdPerson) +#else + || CCamera::m_bUseMouse3rdPerson +#endif + || m_pPointGunAt->IsPed() && ((CPed*)m_pPointGunAt)->bInVehicle) { + ClearWeaponTarget(); + return; + } + if (CPlayerPed::DoesTargetHaveToBeBroken(m_pPointGunAt->GetPosition(), GetWeapon())) { + ClearWeaponTarget(); + return; + } + if (m_pPointGunAt) { + if (padUsed->ShiftTargetLeftJustDown()) + FindNextWeaponLockOnTarget(m_pPointGunAt, true); + if (padUsed->ShiftTargetRightJustDown()) + FindNextWeaponLockOnTarget(m_pPointGunAt, false); + } + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_SYPHON, 0, 0); + TheCamera.UpdateAimingCoors(m_pPointGunAt->GetPosition()); + } +#ifdef FREE_CAM + else if ((CCamera::bFreeCam && weaponInfo->m_eWeaponFire == WEAPON_FIRE_MELEE) || (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM) && !CCamera::m_bUseMouse3rdPerson)) { +#else + else if (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM) && !CCamera::m_bUseMouse3rdPerson) { +#endif + if (padUsed->TargetJustDown()) + FindWeaponLockOnTarget(); + } + } else if (m_pPointGunAt) { + ClearWeaponTarget(); + } + + if (m_pPointGunAt) { +#ifndef VC_PED_PORTS + CVector markPos = m_pPointGunAt->GetPosition(); +#else + CVector markPos; + if (m_pPointGunAt->IsPed()) { + ((CPed*)m_pPointGunAt)->m_pedIK.GetComponentPosition(markPos, PED_MID); + } else { + markPos = m_pPointGunAt->GetPosition(); + } +#endif + if (bCanPointGunAtTarget) { + CWeaponEffects::MarkTarget(markPos, 64, 0, 0, 255, 0.8f); + } else { + CWeaponEffects::MarkTarget(markPos, 64, 32, 0, 255, 0.8f); + } + } + m_bHasLockOnTarget = m_pPointGunAt != nil; +} + +void +CPlayerPed::PlayerControlZelda(CPad *padUsed) +{ + bool doSmoothSpray = DoWeaponSmoothSpray(); + float camOrientation = TheCamera.Orientation; + float leftRight = padUsed->GetPedWalkLeftRight(); + float upDown = padUsed->GetPedWalkUpDown(); + float padMoveInGameUnit; + bool smoothSprayWithoutMove = false; + + if (doSmoothSpray && upDown > 0.0f) { + padMoveInGameUnit = 0.0f; + smoothSprayWithoutMove = true; + } else { + padMoveInGameUnit = CVector2D(leftRight, upDown).Magnitude() / PAD_MOVE_TO_GAME_WORLD_MOVE; + } + +#ifdef FREE_CAM + if(CCamera::bFreeCam && TheCamera.Cams[0].Using3rdPersonMouseCam() && doSmoothSpray) { + padMoveInGameUnit = 0.0f; + smoothSprayWithoutMove = false; + } +#endif + + if (padMoveInGameUnit > 0.0f || smoothSprayWithoutMove) { + float padHeading = CGeneral::GetRadianAngleBetweenPoints(0.0f, 0.0f, -leftRight, upDown); + float neededTurn = CGeneral::LimitRadianAngle(padHeading - camOrientation); + if (doSmoothSpray) { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER || GetWeapon()->m_eWeaponType == WEAPONTYPE_COLT45 + || GetWeapon()->m_eWeaponType == WEAPONTYPE_UZI) + m_fRotationDest = m_fRotationCur - leftRight / 128.0f * (PI / 80.0f) * CTimer::GetTimeStep(); + else + m_fRotationDest = m_fRotationCur - leftRight / 128.0f * (PI / 128.0f) * CTimer::GetTimeStep(); + } else { + m_fRotationDest = neededTurn; + } + + float maxAcc = 0.07f * CTimer::GetTimeStep(); + m_fMoveSpeed = Min(padMoveInGameUnit, m_fMoveSpeed + maxAcc); + + } else { + m_fMoveSpeed = 0.0f; + } + + if (m_nPedState == PED_JUMP) { + if (bIsInTheAir) { + if (bUsesCollision && !bHitSteepSlope && (!bHitSomethingLastFrame || m_vecDamageNormal.z > 0.6f) + && m_fDistanceTravelled < CTimer::GetTimeStepInSeconds() && m_vecMoveSpeed.MagnitudeSqr() < 0.01f) { + + float angleSin = Sin(m_fRotationCur); // originally sin(DEGTORAD(RADTODEG(m_fRotationCur))) o_O + float angleCos = Cos(m_fRotationCur); + ApplyMoveForce(-angleSin * 3.0f, 3.0f * angleCos, 0.05f); + } + } else if (bIsLanding) { + m_fMoveSpeed = 0.0f; + } + } + + if (!CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY) && padUsed->GetSprint()) { + m_nMoveState = PEDMOVE_SPRINT; + } + if (m_nPedState != PED_FIGHT) + SetRealMoveAnim(); + + if (!bIsInTheAir && !(CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY)) + && padUsed->JumpJustDown() && m_nPedState != PED_JUMP) { + ClearAttack(); + ClearWeaponTarget(); + if (m_nEvadeAmount != 0 && m_pEvadingFrom) { + SetEvasiveDive((CPhysical*)m_pEvadingFrom, 1); + m_nEvadeAmount = 0; + m_pEvadingFrom = nil; + } else { + SetJump(); + } + } +} + +void +CPlayerPed::ProcessControl(void) +{ + if (m_nEvadeAmount != 0) + --m_nEvadeAmount; + + if (m_nEvadeAmount == 0) + m_pEvadingFrom = nil; + + if (m_pCurrentPhysSurface && m_pCurrentPhysSurface->IsVehicle() && ((CVehicle*)m_pCurrentPhysSurface)->IsBoat()) { + bTryingToReachDryLand = true; + } else if (!(((uint8)CTimer::GetFrameCounter() + m_randomSeed) & 0xF)) { + CVehicle *nearVeh = (CVehicle*)CWorld::TestSphereAgainstWorld(GetPosition(), 7.0f, nil, + false, true, false, false, false, false); + if (nearVeh && nearVeh->IsBoat()) + bTryingToReachDryLand = true; + else + bTryingToReachDryLand = false; + } + CPed::ProcessControl(); + if (bWasPostponed) + return; + + CPad *padUsed = CPad::GetPad(0); + m_pWanted->Update(); + CEntity::PruneReferences(); + + if (m_nMoveState != PEDMOVE_RUN && m_nMoveState != PEDMOVE_SPRINT) + RestoreSprintEnergy(1.0f); + else if (m_nMoveState == PEDMOVE_RUN) + RestoreSprintEnergy(0.3f); + + if (m_nPedState == PED_DEAD) { + ClearWeaponTarget(); + return; + } + if (m_nPedState == PED_DIE) { + ClearWeaponTarget(); + if (CTimer::GetTimeInMilliseconds() > m_bloodyFootprintCountOrDeathTime + 4000) + SetDead(); + return; + } + if (m_nPedState == PED_DRIVING && m_objective != OBJECTIVE_LEAVE_CAR) { + if (m_pMyVehicle->IsCar() && ((CAutomobile*)m_pMyVehicle)->Damage.GetDoorStatus(DOOR_FRONT_LEFT) == DOOR_STATUS_SWINGING) { + CAnimBlendAssociation *rollDoorAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS); + + if (m_pMyVehicle->m_nGettingOutFlags & CAR_DOOR_FLAG_LF || rollDoorAssoc || (rollDoorAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS))) { + if (rollDoorAssoc) + m_pMyVehicle->ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, rollDoorAssoc->currentTime); + + } else { + // These comparisons are wrong, they return uint16 + if (padUsed && (padUsed->GetAccelerate() != 0.0f || padUsed->GetSteeringLeftRight() != 0.0f || padUsed->GetBrake() != 0.0f)) { + if (rollDoorAssoc) + m_pMyVehicle->ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, rollDoorAssoc->currentTime); + + } else { + m_pMyVehicle->m_nGettingOutFlags |= CAR_DOOR_FLAG_LF; + if (m_pMyVehicle->bLowVehicle) + rollDoorAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS); + else + rollDoorAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS); + + rollDoorAssoc->SetFinishCallback(PedAnimDoorCloseRollingCB, this); + } + } + } + return; + } + if (m_objective == OBJECTIVE_NONE) + m_nMoveState = PEDMOVE_STILL; + if (bIsLanding) + RunningLand(padUsed); + if (padUsed && padUsed->WeaponJustDown() && m_nPedState != PED_SNIPER_MODE) { + + // ...Really? + eWeaponType playerWeapon = FindPlayerPed()->GetWeapon()->m_eWeaponType; + if (playerWeapon == WEAPONTYPE_SNIPERRIFLE) { + DMAudio.PlayFrontEndSound(SOUND_WEAPON_SNIPER_SHOT_NO_ZOOM, 0); + } else if (playerWeapon == WEAPONTYPE_ROCKETLAUNCHER) { + DMAudio.PlayFrontEndSound(SOUND_WEAPON_ROCKET_SHOT_NO_ZOOM, 0); + } + } + + switch (m_nPedState) { + case PED_NONE: + case PED_IDLE: + case PED_FLEE_POS: + case PED_FLEE_ENTITY: + case PED_ATTACK: + case PED_FIGHT: + case PED_AIM_GUN: + if (!RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_BLOCK)) { + if (TheCamera.Cams[0].Using3rdPersonMouseCam() +#ifdef FREE_CAM + && !CCamera::bFreeCam +#endif + ) { + if (padUsed) + PlayerControl1stPersonRunAround(padUsed); + + } else if (m_nPedState == PED_FIGHT) { + if (padUsed) + PlayerControlFighter(padUsed); + + } else if (padUsed) { + PlayerControlZelda(padUsed); + } + } + if (IsPedInControl() && padUsed) + ProcessPlayerWeapon(padUsed); + break; + case PED_SEEK_ENTITY: + m_vecSeekPos = m_pSeekTarget->GetPosition(); + + // fall through + case PED_SEEK_POS: + switch (m_nMoveState) { + case PEDMOVE_WALK: + m_fMoveSpeed = 1.0f; + break; + case PEDMOVE_RUN: + m_fMoveSpeed = 1.8f; + break; + case PEDMOVE_SPRINT: + m_fMoveSpeed = 2.5f; + break; + default: + m_fMoveSpeed = 0.0f; + break; + } + SetRealMoveAnim(); + if (Seek()) { + RestorePreviousState(); + SetMoveState(PEDMOVE_STILL); + } + break; + case PED_SNIPER_MODE: + if (FindPlayerPed()->GetWeapon()->m_eWeaponType == WEAPONTYPE_M16) { + if (padUsed) + PlayerControlM16(padUsed); + + } else if (padUsed) { + PlayerControlSniper(padUsed); + } + break; + case PED_SEEK_CAR: + case PED_SEEK_IN_BOAT: + if (bVehEnterDoorIsBlocked || bKindaStayInSamePlace) { + m_fMoveSpeed = 0.0f; + } else { + m_fMoveSpeed = Min(2.0f, 2.0f * (m_vecSeekPos - GetPosition()).Magnitude2D()); + } + if (padUsed && !padUsed->ArePlayerControlsDisabled()) { + if (padUsed->GetTarget() || padUsed->GetLeftStickXJustDown() || padUsed->GetLeftStickYJustDown() || + padUsed->GetDPadUpJustDown() || padUsed->GetDPadDownJustDown() || padUsed->GetDPadLeftJustDown() || + padUsed->GetDPadRightJustDown()) { + + RestorePreviousState(); + if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER || m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + RestorePreviousObjective(); + } + } + } + if (padUsed && padUsed->GetSprint()) + m_nMoveState = PEDMOVE_SPRINT; + SetRealMoveAnim(); + break; + case PED_JUMP: + if (padUsed) + PlayerControlZelda(padUsed); + if (bIsLanding) + break; + + // This has been added later it seems + return; + case PED_FALL: + case PED_GETUP: + case PED_ENTER_TRAIN: + case PED_EXIT_TRAIN: + case PED_CARJACK: + case PED_DRAG_FROM_CAR: + case PED_ENTER_CAR: + case PED_STEAL_CAR: + case PED_EXIT_CAR: + ClearWeaponTarget(); + break; + case PED_ARRESTED: + if (m_nLastPedState == PED_DRAG_FROM_CAR && m_pVehicleAnim) + BeingDraggedFromCar(); + break; + default: + break; + } + if (padUsed && IsPedShootable()) { + ProcessWeaponSwitch(padUsed); + GetWeapon()->Update(m_audioEntityId); + } + ProcessAnimGroups(); + if (padUsed) { + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FOLLOWPED + && TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking == LOOKING_BEHIND) { + + m_lookTimer = 0; + float camAngle = CGeneral::LimitRadianAngle(TheCamera.Cams[TheCamera.ActiveCam].Front.Heading()); + float angleBetweenPlayerAndCam = Abs(camAngle - m_fRotationCur); + if (m_nPedState != PED_ATTACK && angleBetweenPlayerAndCam > DEGTORAD(30.0f) && angleBetweenPlayerAndCam < DEGTORAD(330.0f)) { + + if (angleBetweenPlayerAndCam > DEGTORAD(150.0f) && angleBetweenPlayerAndCam < DEGTORAD(210.0f)) { + float rightTurnAngle = CGeneral::LimitRadianAngle(m_fRotationCur - DEGTORAD(150.0f)); + float leftTurnAngle = CGeneral::LimitRadianAngle(DEGTORAD(150.0f) + m_fRotationCur); + if (m_fLookDirection == 999999.0f) + camAngle = rightTurnAngle; + else if (Abs(rightTurnAngle - m_fLookDirection) < Abs(leftTurnAngle - m_fLookDirection)) + camAngle = rightTurnAngle; + else + camAngle = leftTurnAngle; + } + SetLookFlag(camAngle, true); + SetLookTimer(CTimer::GetTimeStepInMilliseconds() * 5.0f); + } else { + ClearLookFlag(); + } + } + } + if (m_nMoveState == PEDMOVE_SPRINT && bIsLooking) { + ClearLookFlag(); + SetLookTimer(250); + } + + if (m_vecMoveSpeed.Magnitude2D() < 0.1f) { + if (m_nSpeedTimer) { + if (CTimer::GetTimeInMilliseconds() > m_nSpeedTimer) + m_bSpeedTimerFlag = true; + } else { + m_nSpeedTimer = CTimer::GetTimeInMilliseconds() + 500; + } + } else { + m_nSpeedTimer = 0; + m_bSpeedTimerFlag = false; + } + +#ifdef VC_PED_PORTS + if (bDontAllowWeaponChange && FindPlayerPed() == this) { + if (!CPad::GetPad(0)->GetTarget()) + bDontAllowWeaponChange = false; + } +#endif + +#ifdef PED_SKIN + if (!bIsVisible && IsClumpSkinned(GetClump())) + UpdateRpHAnim(); +#endif +} + +#ifdef COMPATIBLE_SAVES +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +void +CPlayerPed::Save(uint8*& buf) +{ + CPed::Save(buf); + ZeroSaveBuf(buf, 16); + CopyToBuf(buf, m_fMaxStamina); + ZeroSaveBuf(buf, 28); + CopyToBuf(buf, m_nTargettableObjects[0]); + CopyToBuf(buf, m_nTargettableObjects[1]); + CopyToBuf(buf, m_nTargettableObjects[2]); + CopyToBuf(buf, m_nTargettableObjects[3]); + ZeroSaveBuf(buf, 116); +} + +void +CPlayerPed::Load(uint8*& buf) +{ + CPed::Load(buf); + SkipSaveBuf(buf, 16); + CopyFromBuf(buf, m_fMaxStamina); + SkipSaveBuf(buf, 28); + CopyFromBuf(buf, m_nTargettableObjects[0]); + CopyFromBuf(buf, m_nTargettableObjects[1]); + CopyFromBuf(buf, m_nTargettableObjects[2]); + CopyFromBuf(buf, m_nTargettableObjects[3]); + SkipSaveBuf(buf, 116); +} +#undef CopyFromBuf +#undef CopyToBuf +#endif diff --git a/src/peds/PlayerPed.h b/src/peds/PlayerPed.h new file mode 100644 index 0000000..2e9f798 --- /dev/null +++ b/src/peds/PlayerPed.h @@ -0,0 +1,98 @@ +#pragma once + +#include "Ped.h" + +class CPad; +class CCopPed; +class CWanted; + +class CPlayerPed : public CPed +{ +public: + CWanted *m_pWanted; + CCopPed *m_pArrestingCop; + float m_fMoveSpeed; + float m_fCurrentStamina; + float m_fMaxStamina; + float m_fStaminaProgress; + int8 m_nSelectedWepSlot; // eWeaponType + bool m_bSpeedTimerFlag; + uint8 m_nEvadeAmount; + int8 field_1367; + uint32 m_nSpeedTimer; + uint32 m_nHitAnimDelayTimer; + float m_fAttackButtonCounter; + bool m_bHaveTargetSelected; // may have better name + CEntity *m_pEvadingFrom; // is this CPhysical? + int32 m_nTargettableObjects[4]; + bool m_bAdrenalineActive; + bool m_bHasLockOnTarget; + uint32 m_nAdrenalineTime; + bool m_bCanBeDamaged; + int8 field_1413; + CVector m_vecSafePos[6]; // safe places from the player, for example behind a tree + CPed *m_pPedAtSafePos[6]; + float m_fWalkAngle; + float m_fFPSMoveHeading; +#ifdef FREE_CAM + bool m_bFreeAimActive; + CVector m_cachedCamSource; + CVector m_cachedCamFront; + CVector m_cachedCamUp; +#endif +#ifdef VC_PED_PORTS + static bool bDontAllowWeaponChange; +#endif + + CPlayerPed(); + ~CPlayerPed(); + void SetMoveAnim() { }; + + void ReApplyMoveAnims(void); + void ClearWeaponTarget(void); + void SetWantedLevel(int32 level); + void SetWantedLevelNoDrop(int32 level); + void KeepAreaAroundPlayerClear(void); + void AnnoyPlayerPed(bool); + void MakeChangesForNewWeapon(int8); + void SetInitialState(void); + void ProcessControl(void); + void ClearAdrenaline(void); + void UseSprintEnergy(void); + class CPlayerInfo *GetPlayerInfoForThisPlayerPed(); + void SetRealMoveAnim(void); + void RestoreSprintEnergy(float); + bool DoWeaponSmoothSpray(void); + void DoStuffToGoOnFire(void); + bool DoesTargetHaveToBeBroken(CVector, CWeapon*); + void RunningLand(CPad*); + bool IsThisPedAttackingPlayer(CPed*); + void PlayerControlSniper(CPad*); + void PlayerControlM16(CPad*); + void PlayerControlFighter(CPad*); + void ProcessWeaponSwitch(CPad*); + void MakeObjectTargettable(int32); + void PlayerControl1stPersonRunAround(CPad *padUsed); + void EvaluateNeighbouringTarget(CEntity*, CEntity**, float*, float, float, bool); + void EvaluateTarget(CEntity*, CEntity**, float*, float, float, bool); + bool FindNextWeaponLockOnTarget(CEntity*, bool); + bool FindWeaponLockOnTarget(void); + void ProcessAnimGroups(void); + void ProcessPlayerWeapon(CPad*); + void PlayerControlZelda(CPad*); + + static void SetupPlayerPed(int32); + static void DeactivatePlayerPed(int32); + static void ReactivatePlayerPed(int32); + +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + + static const uint32 nSaveStructSize; +}; + +#ifndef PED_SKIN +VALIDATE_SIZE(CPlayerPed, 0x5F0); +#endif diff --git a/src/peds/Population.cpp b/src/peds/Population.cpp new file mode 100644 index 0000000..5c80702 --- /dev/null +++ b/src/peds/Population.cpp @@ -0,0 +1,1174 @@ +#include "common.h" + +#include "Game.h" +#include "General.h" +#include "World.h" +#include "Population.h" +#include "CopPed.h" +#include "Wanted.h" +#include "FileMgr.h" +#include "Gangs.h" +#include "ModelIndices.h" +#include "Zones.h" +#include "CivilianPed.h" +#include "EmergencyPed.h" +#include "Replay.h" +#include "Camera.h" +#include "CutsceneMgr.h" +#include "CarCtrl.h" +#include "IniFile.h" +#include "VisibilityPlugins.h" +#include "PedPlacement.h" +#include "DummyObject.h" +#include "Script.h" +#include "Shadows.h" +#include "Bike.h" + +#define MIN_CREATION_DIST 40.0f // not for start of the game (look at the GeneratePedsAtStartOfGame) +#define CREATION_RANGE 10.0f // added over the MIN_CREATION_DIST. +#define OFFSCREEN_CREATION_MULT 0.5f +#define PED_REMOVE_DIST (MIN_CREATION_DIST + CREATION_RANGE + 1.0f) +#define PED_REMOVE_DIST_SPECIAL (MIN_CREATION_DIST + CREATION_RANGE + 15.0f) // for peds with bCullExtraFarAway flag + +// Transition areas between zones +const RegenerationPoint aSafeZones[] = { + LEVEL_INDUSTRIAL, LEVEL_COMMERCIAL, 400.0f, 814.0f, -954.0f, -903.0f, 30.0f, 100.0f, + 790.0f, -917.0f, 39.0f, 775.0f, -921.0f, 39.0f, 424.0f, -942.0f, 38.0f, 439.0f, -938.0f, 38.0f, + LEVEL_INDUSTRIAL, LEVEL_COMMERCIAL, 555.0f, 711.0f, 118.0f, 186.0f, -30.0f, -10.0f, + CVector(698.0f, 182.0f, -20.0f), CVector(681.0f, 178.0f, -20.0f), CVector(586.0f, 144.0f, -20.0f), CVector(577.0f, 135.0f, -20.0f), + LEVEL_INDUSTRIAL, LEVEL_COMMERCIAL, 626.0f, 744.0f, -124.0f, -87.0f, -20.0f, -6.0f, + CVector(736.0f, -117.0f, -13.0f), CVector(730.0f, -115.0f, -13.0f), CVector(635.0f, -93.0f, -12.5f), CVector(650.0f, -89.0f, -12.5f), + LEVEL_INDUSTRIAL, LEVEL_COMMERCIAL, 645.0f, 734.0f, -780.0f, -750.0f, -25.0f, -6.0f, + CVector(729.0f, -764.0f, -18.0f), CVector(720.0f, -769.0f, -17.0f), CVector(652.0f, -774.0f, -10.5f), CVector(659.0f, -770.0f, -10.5f), + LEVEL_COMMERCIAL, LEVEL_SUBURBAN, -532.0f, -136.0f, -668.0f, -599.0f, 34.0f, 60.0f, + CVector(-172.0f, -619.0f, 44.0f), CVector(-183.0f, -623.0f, 44.0f), CVector(-511.0f, -645.0f, 41.0f), CVector(-493.0f, -639.0f, 41.5f), + LEVEL_COMMERCIAL, LEVEL_SUBURBAN, -325.0f, -175.0f, 27.0f, 75.0f, -30.0f, -10.0f, + CVector(-185.0f, 40.8f, -20.5f), CVector(-202.0f, 37.0f, -20.5f), CVector(-315.0f, 65.5f, -20.5f), CVector(-306.0f, 62.4f, -20.5f), + LEVEL_COMMERCIAL, LEVEL_SUBURBAN, -410.0f, -310.0f, -1055.0f, -1030.0f, -20.0f, -6.0f, + CVector(-321.0f, -1043.0f, -13.2f), CVector(-328.0f, -1045.0f, -13.2f), CVector(-398.0f, -1044.0f, -13.5f), CVector(-390.0f, -1040.5f, -13.5f), + LEVEL_COMMERCIAL, LEVEL_SUBURBAN, -425.0f, -280.0f, -471.0f, -447.0f, -20.0f, -5.0f, + CVector(-292.0f, -457.0f, -11.6f), CVector(-310.0f, -461.0f, -11.6f), CVector(-413.0f, -461.0f, -11.5f), CVector(-399.0f, -457.0f, -11.3f) +}; + +PedGroup CPopulation::ms_pPedGroups[NUMPEDGROUPS]; +bool CPopulation::ms_bGivePedsWeapons; +int32 CPopulation::m_AllRandomPedsThisType = -1; +float CPopulation::PedDensityMultiplier = 1.0f; +uint32 CPopulation::ms_nTotalMissionPeds; +int32 CPopulation::MaxNumberOfPedsInUse = DEFAULT_MAX_NUMBER_OF_PEDS; +uint32 CPopulation::ms_nNumCivMale; +uint32 CPopulation::ms_nNumCivFemale; +uint32 CPopulation::ms_nNumCop; +bool CPopulation::bZoneChangeHasHappened; +uint32 CPopulation::ms_nNumEmergency; +int8 CPopulation::m_CountDownToPedsAtStart; +uint32 CPopulation::ms_nNumGang1; +uint32 CPopulation::ms_nNumGang2; +uint32 CPopulation::ms_nTotalPeds; +uint32 CPopulation::ms_nNumGang3; +uint32 CPopulation::ms_nTotalGangPeds; +uint32 CPopulation::ms_nNumGang4; +uint32 CPopulation::ms_nTotalCivPeds; +uint32 CPopulation::ms_nNumGang5; +uint32 CPopulation::ms_nNumDummy; +uint32 CPopulation::ms_nNumGang6; +uint32 CPopulation::ms_nNumGang9; +uint32 CPopulation::ms_nNumGang7; +uint32 CPopulation::ms_nNumGang8; +CVector CPopulation::RegenerationPoint_a; +CVector CPopulation::RegenerationPoint_b; +CVector CPopulation::RegenerationFront; + +void +CPopulation::Initialise() +{ + debug("Initialising CPopulation...\n"); + + ms_nNumCivMale = 0; + ms_nNumCivFemale = 0; + ms_nNumCop = 0; + ms_nNumEmergency = 0; + ms_nNumGang1 = 0; + ms_nNumGang2 = 0; + ms_nNumGang3 = 0; + ms_nNumGang4 = 0; + ms_nNumGang5 = 0; + ms_nNumGang6 = 0; + ms_nNumGang7 = 0; + ms_nNumGang8 = 0; + ms_nNumGang9 = 0; + ms_nNumDummy = 0; + + m_AllRandomPedsThisType = -1; + PedDensityMultiplier = 1.0f; + bZoneChangeHasHappened = false; + m_CountDownToPedsAtStart = 2; + + ms_nTotalMissionPeds = 0; + ms_nTotalPeds = 0; + ms_nTotalGangPeds = 0; + ms_nTotalCivPeds = 0; + + LoadPedGroups(); + DealWithZoneChange(LEVEL_COMMERCIAL, LEVEL_INDUSTRIAL, true); + + debug("CPopulation ready\n"); +} + +void +CPopulation::RemovePed(CPed *ent) +{ + CWorld::Remove((CEntity*)ent); + delete ent; +} + +int32 +CPopulation::ChooseCivilianOccupation(int32 group) +{ + return ms_pPedGroups[group].models[CGeneral::GetRandomNumberInRange(0, NUMMODELSPERPEDGROUP)]; +} + +// returns eCopType +int32 +CPopulation::ChoosePolicePedOccupation() +{ + CGeneral::GetRandomNumber(); + return COP_STREET; +} + +void +CPopulation::LoadPedGroups() +{ + int fd; + char line[1024]; + int nextPedGroup = 0; + // char unused[16]; // non-existence of that in mobile kinda verifies that + char modelName[256]; + + CFileMgr::ChangeDir("\\DATA\\"); + fd = CFileMgr::OpenFile("PEDGRP.DAT", "r"); + CFileMgr::ChangeDir("\\"); + while (CFileMgr::ReadLine(fd, line, sizeof(line))) { + int end; + // find end of line + for (end = 0; ; end++) { + if (line[end] == '\n') + break; + if (line[end] == ',' || line[end] == '\r') + line[end] = ' '; + } + line[end] = '\0'; + int cursor = 0; + int i; + for (i = 0; i < NUMMODELSPERPEDGROUP; i++) { + while (line[cursor] <= ' ' && line[cursor] != '\0') + ++cursor; + + if (line[cursor] == '#') + break; + + // find next whitespace + int nextWhitespace; + for (nextWhitespace = cursor; line[nextWhitespace] > ' '; ++nextWhitespace) + ; + + if (cursor == nextWhitespace) + break; + + // read until next whitespace + strncpy(modelName, &line[cursor], nextWhitespace - cursor); + modelName[nextWhitespace - cursor] = '\0'; + CModelInfo::GetModelInfo(modelName, &ms_pPedGroups[nextPedGroup].models[i]); + cursor = nextWhitespace; + } + if (i == NUMMODELSPERPEDGROUP) + nextPedGroup++; + } + CFileMgr::CloseFile(fd); +} + +void +CPopulation::UpdatePedCount(ePedType pedType, bool decrease) +{ + if (decrease) { + switch (pedType) { + case PEDTYPE_PLAYER1: + case PEDTYPE_PLAYER2: + case PEDTYPE_PLAYER3: + case PEDTYPE_PLAYER4: + case PEDTYPE_UNUSED1: + case PEDTYPE_SPECIAL: + return; + case PEDTYPE_CIVMALE: + --ms_nNumCivMale; + break; + case PEDTYPE_CIVFEMALE: + --ms_nNumCivFemale; + break; + case PEDTYPE_COP: + --ms_nNumCop; + break; + case PEDTYPE_GANG1: + --ms_nNumGang1; + break; + case PEDTYPE_GANG2: + --ms_nNumGang2; + break; + case PEDTYPE_GANG3: + --ms_nNumGang3; + break; + case PEDTYPE_GANG4: + --ms_nNumGang4; + break; + case PEDTYPE_GANG5: + --ms_nNumGang5; + break; + case PEDTYPE_GANG6: + --ms_nNumGang6; + break; + case PEDTYPE_GANG7: + --ms_nNumGang7; + break; + case PEDTYPE_GANG8: + --ms_nNumGang8; + break; + case PEDTYPE_GANG9: + --ms_nNumGang9; + break; + case PEDTYPE_EMERGENCY: + case PEDTYPE_FIREMAN: + --ms_nNumEmergency; + break; + case PEDTYPE_CRIMINAL: + --ms_nNumCivMale; + break; + case PEDTYPE_PROSTITUTE: + --ms_nNumCivFemale; + break; + case PEDTYPE_UNUSED2: + --ms_nNumDummy; + break; + default: + Error("Unknown ped type, UpdatePedCount, Population.cpp"); + break; + } + } else { + switch (pedType) { + case PEDTYPE_PLAYER1: + case PEDTYPE_PLAYER2: + case PEDTYPE_PLAYER3: + case PEDTYPE_PLAYER4: + case PEDTYPE_UNUSED1: + case PEDTYPE_SPECIAL: + return; + case PEDTYPE_CIVMALE: + ++ms_nNumCivMale; + break; + case PEDTYPE_CIVFEMALE: + ++ms_nNumCivFemale; + break; + case PEDTYPE_COP: + ++ms_nNumCop; + break; + case PEDTYPE_GANG1: + ++ms_nNumGang1; + break; + case PEDTYPE_GANG2: + ++ms_nNumGang2; + break; + case PEDTYPE_GANG3: + ++ms_nNumGang3; + break; + case PEDTYPE_GANG4: + ++ms_nNumGang4; + break; + case PEDTYPE_GANG5: + ++ms_nNumGang5; + break; + case PEDTYPE_GANG6: + ++ms_nNumGang6; + break; + case PEDTYPE_GANG7: + ++ms_nNumGang7; + break; + case PEDTYPE_GANG8: + ++ms_nNumGang8; + break; + case PEDTYPE_GANG9: + ++ms_nNumGang9; + break; + case PEDTYPE_EMERGENCY: + case PEDTYPE_FIREMAN: + ++ms_nNumEmergency; + break; + case PEDTYPE_CRIMINAL: + ++ms_nNumCivMale; + break; + case PEDTYPE_PROSTITUTE: + ++ms_nNumCivFemale; + break; + case PEDTYPE_UNUSED2: + ++ms_nNumDummy; + break; + default: + Error("Unknown ped type, UpdatePedCount, Population.cpp"); + break; + } + } +} + +int +CPopulation::ChooseGangOccupation(int gangId) +{ + int8 modelOverride = CGangs::GetGangPedModelOverride(gangId); + + // All gangs have 2 models + int firstGangModel = 2 * gangId + MI_GANG01; + + // GetRandomNumberInRange never returns max. value + if (modelOverride == -1) + return CGeneral::GetRandomNumberInRange(firstGangModel, firstGangModel + 2); + + if (modelOverride != 0) + return firstGangModel + 1; + else + return firstGangModel; +} + +void +CPopulation::DealWithZoneChange(eLevelName oldLevel, eLevelName newLevel, bool forceIndustrialZone) +{ + bZoneChangeHasHappened = true; + + CVector findSafeZoneAround; + int safeZone; + + if (forceIndustrialZone) { + // Commercial to industrial transition area on Callahan Bridge + findSafeZoneAround.x = 690.0f; + findSafeZoneAround.y = -920.0f; + findSafeZoneAround.z = 42.0f; + } else { + findSafeZoneAround = FindPlayerCoors(); + } + eLevelName level; + FindCollisionZoneForCoors(&findSafeZoneAround, &safeZone, &level); + + // We aren't in a "safe zone", find closest one + if (safeZone < 0) + FindClosestZoneForCoors(&findSafeZoneAround, &safeZone, oldLevel, newLevel); + + // No, there should be one! + if (safeZone < 0) { + if (newLevel == LEVEL_INDUSTRIAL) { + safeZone = 0; + } else if (newLevel == LEVEL_SUBURBAN) { + safeZone = 4; + } + } + + if (aSafeZones[safeZone].srcLevel == newLevel) { + CPopulation::RegenerationPoint_a = aSafeZones[safeZone].srcPosA; + CPopulation::RegenerationPoint_b = aSafeZones[safeZone].srcPosB; + CPopulation::RegenerationFront = aSafeZones[safeZone].destPosA - aSafeZones[safeZone].srcPosA; + RegenerationFront.Normalise(); + } else if (aSafeZones[safeZone].destLevel == newLevel) { + CPopulation::RegenerationPoint_a = aSafeZones[safeZone].destPosA; + CPopulation::RegenerationPoint_b = aSafeZones[safeZone].destPosB; + CPopulation::RegenerationFront = aSafeZones[safeZone].srcPosA - aSafeZones[safeZone].destPosA; + RegenerationFront.Normalise(); + } +} + +void +CPopulation::FindCollisionZoneForCoors(CVector *coors, int *safeZoneOut, eLevelName *levelOut) +{ + *safeZoneOut = -1; + for (int i = 0; i < ARRAY_SIZE(aSafeZones); i++) { + if (coors->x > aSafeZones[i].x1 && coors->x < aSafeZones[i].x2) { + if (coors->y > aSafeZones[i].y1 && coors->y < aSafeZones[i].y2) { + if (coors->z > aSafeZones[i].z1 && coors->z < aSafeZones[i].z2) + *safeZoneOut = i; + } + } + } + // Then it's transition area + if (*safeZoneOut >= 0) + *levelOut = LEVEL_GENERIC; + else + *levelOut = CTheZones::GetLevelFromPosition(coors); +} + +void +CPopulation::FindClosestZoneForCoors(CVector *coors, int *safeZoneOut, eLevelName level1, eLevelName level2) +{ + float minDist = 10000000.0f; + int closestSafeZone = -1; + for (int i = 0; i < ARRAY_SIZE(aSafeZones); i++) { + if ((level1 == aSafeZones[i].srcLevel || level1 == aSafeZones[i].destLevel) && (level2 == aSafeZones[i].srcLevel || level2 == aSafeZones[i].destLevel)) { + CVector2D safeZoneDistVec(coors->x - (aSafeZones[i].x1 + aSafeZones[i].x2) * 0.5f, coors->y - (aSafeZones[i].y1 + aSafeZones[i].y2) * 0.5f); + float safeZoneDist = safeZoneDistVec.Magnitude(); + if (safeZoneDist < minDist) { + minDist = safeZoneDist; + closestSafeZone = i; + } + } + } + *safeZoneOut = closestSafeZone; +} + +void +CPopulation::Update() +{ + if (!CReplay::IsPlayingBack()) { + ManagePopulation(); + MoveCarsAndPedsOutOfAbandonedZones(); + if (m_CountDownToPedsAtStart != 0) { + if (--m_CountDownToPedsAtStart == 0) + GeneratePedsAtStartOfGame(); + } else { + ms_nTotalCivPeds = ms_nNumCivFemale + ms_nNumCivMale; + ms_nTotalGangPeds = ms_nNumGang9 + ms_nNumGang8 + ms_nNumGang7 + + ms_nNumGang6 + ms_nNumGang5 + ms_nNumGang4 + ms_nNumGang3 + + ms_nNumGang2 + ms_nNumGang1; + ms_nTotalPeds = ms_nNumDummy + ms_nNumEmergency + ms_nNumCop + + ms_nTotalGangPeds + ms_nNumCivFemale + ms_nNumCivMale; + if (!CCutsceneMgr::IsRunning()) { + float pcdm = PedCreationDistMultiplier(); + AddToPopulation(pcdm * (MIN_CREATION_DIST * TheCamera.GenerationDistMultiplier), + pcdm * ((MIN_CREATION_DIST + CREATION_RANGE) * TheCamera.GenerationDistMultiplier), + pcdm * (MIN_CREATION_DIST + CREATION_RANGE) * OFFSCREEN_CREATION_MULT - CREATION_RANGE, + pcdm * (MIN_CREATION_DIST + CREATION_RANGE) * OFFSCREEN_CREATION_MULT); + } + } + } +} + +void +CPopulation::GeneratePedsAtStartOfGame() +{ + for (int i = 0; i < 50; i++) { + ms_nTotalCivPeds = ms_nNumCivFemale + ms_nNumCivMale; + ms_nTotalGangPeds = ms_nNumGang9 + ms_nNumGang8 + ms_nNumGang7 + + ms_nNumGang6 + ms_nNumGang5 + ms_nNumGang4 + + ms_nNumGang3 + ms_nNumGang2 + ms_nNumGang1; + ms_nTotalPeds = ms_nNumDummy + ms_nNumEmergency + ms_nNumCop + + ms_nTotalGangPeds + ms_nNumCivFemale + ms_nNumCivMale; + + // Min dist is 10.0f only for start of the game (naturally) + AddToPopulation(10.0f, PedCreationDistMultiplier() * (MIN_CREATION_DIST + CREATION_RANGE), + 10.0f, PedCreationDistMultiplier() * (MIN_CREATION_DIST + CREATION_RANGE)); + } +} + +bool +CPopulation::IsPointInSafeZone(CVector *coors) +{ + for (int i = 0; i < ARRAY_SIZE(aSafeZones); i++) { + if (coors->x > aSafeZones[i].x1 && coors->x < aSafeZones[i].x2) { + if (coors->y > aSafeZones[i].y1 && coors->y < aSafeZones[i].y2) { + if (coors->z > aSafeZones[i].z1 && coors->z < aSafeZones[i].z2) + return true; + } + } + } + return false; +} + +// More speed = wider area to spawn peds +float +CPopulation::PedCreationDistMultiplier() +{ + CVehicle *veh = FindPlayerVehicle(); + if (!veh) + return 1.0f; + + float vehSpeed = veh->m_vecMoveSpeed.Magnitude2D(); + return Clamp(vehSpeed - 0.1f + 1.0f, 1.0f, 1.5f); +} + +CPed* +CPopulation::AddPed(ePedType pedType, uint32 miOrCopType, CVector const &coors) +{ + switch (pedType) { + case PEDTYPE_CIVMALE: + case PEDTYPE_CIVFEMALE: + { + CCivilianPed *ped = new CCivilianPed(pedType, miOrCopType); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + if (ms_bGivePedsWeapons) { + eWeaponType weapon = (eWeaponType)CGeneral::GetRandomNumberInRange(WEAPONTYPE_UNARMED, WEAPONTYPE_DETONATOR); + if (weapon != WEAPONTYPE_UNARMED) { + ped->SetCurrentWeapon(ped->GiveWeapon(weapon, 25001)); + } + } + return ped; + } + case PEDTYPE_COP: + { + CCopPed *ped = new CCopPed((eCopType)miOrCopType); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + return ped; + } + case PEDTYPE_GANG1: + case PEDTYPE_GANG2: + case PEDTYPE_GANG3: + case PEDTYPE_GANG4: + case PEDTYPE_GANG5: + case PEDTYPE_GANG6: + case PEDTYPE_GANG7: + case PEDTYPE_GANG8: + case PEDTYPE_GANG9: + { + CCivilianPed *ped = new CCivilianPed(pedType, miOrCopType); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + + uint32 weapon; + if (CGeneral::GetRandomNumberInRange(0, 100) >= 50) + weapon = ped->GiveWeapon((eWeaponType)CGangs::GetGangInfo(pedType - PEDTYPE_GANG1)->m_Weapon2, 25001); + else + weapon = ped->GiveWeapon((eWeaponType)CGangs::GetGangInfo(pedType - PEDTYPE_GANG1)->m_Weapon1, 25001); + ped->SetCurrentWeapon(weapon); + return ped; + } + case PEDTYPE_EMERGENCY: + { + CEmergencyPed *ped = new CEmergencyPed(PEDTYPE_EMERGENCY); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + return ped; + } + case PEDTYPE_FIREMAN: + { + CEmergencyPed *ped = new CEmergencyPed(PEDTYPE_FIREMAN); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + return ped; + } + case PEDTYPE_CRIMINAL: + case PEDTYPE_PROSTITUTE: + { + CCivilianPed *ped = new CCivilianPed(pedType, miOrCopType); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + return ped; + } + default: + Error("Unknown ped type, AddPed, Population.cpp"); + return nil; + } +} + +void +CPopulation::AddToPopulation(float minDist, float maxDist, float minDistOffScreen, float maxDistOffScreen) +{ + uint32 pedTypeToAdd; + int32 modelToAdd; + int pedAmount; + + CZoneInfo zoneInfo; + CPed *gangLeader = nil; + bool addCop = false; + CPlayerInfo *playerInfo = &CWorld::Players[CWorld::PlayerInFocus]; + CVector playerCentreOfWorld = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + CTheZones::GetZoneInfoForTimeOfDay(&playerCentreOfWorld, &zoneInfo); + CWanted *wantedInfo = playerInfo->m_pPed->m_pWanted; + if (wantedInfo->GetWantedLevel() > 2) { + if (ms_nNumCop < wantedInfo->m_MaxCops && !playerInfo->m_pPed->bInVehicle + && (CCarCtrl::NumLawEnforcerCars >= wantedInfo->m_MaximumLawEnforcerVehicles + || CCarCtrl::NumRandomCars >= playerInfo->m_nTrafficMultiplier * CCarCtrl::CarDensityMultiplier + || CCarCtrl::NumFiretrucksOnDuty + CCarCtrl::NumAmbulancesOnDuty + CCarCtrl::NumParkedCars + + CCarCtrl::NumMissionCars + CCarCtrl::NumLawEnforcerCars + CCarCtrl::NumRandomCars >= CCarCtrl::MaxNumberOfCarsInUse)) { + addCop = true; + minDist = PedCreationDistMultiplier() * MIN_CREATION_DIST; + maxDist = PedCreationDistMultiplier() * (MIN_CREATION_DIST + CREATION_RANGE); + } + } + // Yeah, float + float maxPossiblePedsForArea = (zoneInfo.pedDensity + zoneInfo.carDensity) * playerInfo->m_fRoadDensity * PedDensityMultiplier * CIniFile::PedNumberMultiplier; + maxPossiblePedsForArea = Min(maxPossiblePedsForArea, MaxNumberOfPedsInUse); + + if (ms_nTotalPeds < maxPossiblePedsForArea || addCop) { + int decisionThreshold = CGeneral::GetRandomNumberInRange(0, 1000); + if (decisionThreshold < zoneInfo.copDensity || addCop) { + pedTypeToAdd = PEDTYPE_COP; + modelToAdd = ChoosePolicePedOccupation(); + } else { + int16 density = zoneInfo.copDensity; + for (int i = 0; i < NUM_GANGS; i++) { + density += zoneInfo.gangDensity[i]; + if (decisionThreshold < density) { + pedTypeToAdd = PEDTYPE_GANG1 + i; + break; + } + + if (i == NUM_GANGS - 1) { + modelToAdd = ChooseCivilianOccupation(zoneInfo.pedGroup); + if (modelToAdd == -1) + return; + pedTypeToAdd = ((CPedModelInfo*)CModelInfo::GetModelInfo(modelToAdd))->m_pedType; + } + + } + } + if (!addCop && m_AllRandomPedsThisType > PEDTYPE_PLAYER1) + pedTypeToAdd = m_AllRandomPedsThisType; + + if (pedTypeToAdd >= PEDTYPE_GANG1 && pedTypeToAdd <= PEDTYPE_GANG9) { + int randVal = CGeneral::GetRandomNumber() % 100; + if (randVal < 50) + return; + + if (randVal < 57) { + pedAmount = 1; + } else if (randVal >= 74) { + if (randVal >= 85) + pedAmount = 4; + else + pedAmount = 3; + } else { + pedAmount = 2; + } + } else + pedAmount = 1; + + CVector generatedCoors; + int node1, node2; + float randomPos; + bool foundCoors = !!ThePaths.GeneratePedCreationCoors(playerCentreOfWorld.x, playerCentreOfWorld.y, minDist, maxDist, minDistOffScreen, maxDistOffScreen, + &generatedCoors, &node1, &node2, &randomPos, nil); + + if (!foundCoors) + return; + + for (int i = 0; i < pedAmount; ++i) { + if (pedTypeToAdd >= PEDTYPE_GANG1 && pedTypeToAdd <= PEDTYPE_GANG9) + modelToAdd = ChooseGangOccupation(pedTypeToAdd - PEDTYPE_GANG1); + + if (pedTypeToAdd == PEDTYPE_COP) { + // Unused code, ChoosePolicePedOccupation returns COP_STREET. Spawning FBI/SWAT/Army done in somewhere else. + if (modelToAdd == COP_STREET) { + if (!CModelInfo::GetModelInfo(MI_COP)->GetRwObject()) + return; + + } else if (modelToAdd == COP_FBI) { + if (!CModelInfo::GetModelInfo(MI_FBI)->GetRwObject()) + return; + + } else if (modelToAdd == COP_SWAT) { + if (!CModelInfo::GetModelInfo(MI_SWAT)->GetRwObject()) + return; + + } else if (modelToAdd == COP_ARMY && !CModelInfo::GetModelInfo(MI_ARMY)->GetRwObject()) { + return; + } + } else if (!CModelInfo::GetModelInfo(modelToAdd)->GetRwObject()) { + return; + } + generatedCoors.z += 0.7f; + + // What? How can this not be met? + if (i < pedAmount) { + //rand() + if (gangLeader) { + // Align gang members in formation. (btw i can't be 0 in here) + float offsetMin = i * 0.75f; + float offsetMax = (i + 1.0f) * 0.75f - offsetMin; + float xOffset = CGeneral::GetRandomNumberInRange(offsetMin, offsetMin + offsetMax); + float yOffset = CGeneral::GetRandomNumberInRange(offsetMin, offsetMin + offsetMax); + if (CGeneral::GetRandomNumber() & 1) + xOffset = -xOffset; + if (CGeneral::GetRandomNumber() & 1) + yOffset = -yOffset; + generatedCoors.x = xOffset + gangLeader->GetPosition().x; + generatedCoors.y = yOffset + gangLeader->GetPosition().y; + } + } + if (!CPedPlacement::IsPositionClearForPed(&generatedCoors)) + break; + + // Why no love for last gang member?! + if (i + 1 < pedAmount) { + bool foundGround; + float groundZ = CWorld::FindGroundZFor3DCoord(generatedCoors.x, generatedCoors.y, 2.0f + generatedCoors.z, &foundGround) + 0.7f; + if (!foundGround) + return; + + generatedCoors.z = Max(generatedCoors.z, groundZ); + } + bool farEnoughToAdd = true; + if (TheCamera.IsSphereVisible(generatedCoors, 2.0f)) { + if (PedCreationDistMultiplier() * MIN_CREATION_DIST > (generatedCoors - playerCentreOfWorld).Magnitude2D()) + farEnoughToAdd = false; + } + if (!farEnoughToAdd) + break; + CPed *newPed = AddPed((ePedType)pedTypeToAdd, modelToAdd, generatedCoors); + newPed->SetWanderPath(CGeneral::GetRandomNumberInRange(0, 8)); + + if (i != 0) { + // Gang member + newPed->SetLeader(gangLeader); +#if !defined(FIX_BUGS) && GTA_VERSION >= GTA3_PC_10 + // seems to be a miami leftover (this code is not on PS2) but gang peds end up just being frozen + newPed->SetPedState(PED_UNKNOWN); + gangLeader->SetPedState(PED_UNKNOWN); + newPed->m_fRotationCur = CGeneral::GetRadianAngleBetweenPoints( + gangLeader->GetPosition().x, gangLeader->GetPosition().y, + newPed->GetPosition().x, newPed->GetPosition().y); + newPed->m_fRotationDest = newPed->m_fRotationCur; +#endif + } else { + gangLeader = newPed; + } + CVisibilityPlugins::SetClumpAlpha(newPed->GetClump(), 0); + /* + // Pointless, this is already a for loop + if (i + 1 > pedAmount) + break; + if (pedAmount <= 1) + break; */ + } + } +} + +CPed* +CPopulation::AddPedInCar(CVehicle* car) +{ + int defaultModel = MI_MALE01; + bool imSureThatModelIsLoaded = true; + CVector coors = FindPlayerCoors(); + CZoneInfo zoneInfo; + int pedType; + + // May be eCopType, model index or non-sense(for medic), AddPed knows that by looking to ped type. + int preferredModel; + + CTheZones::GetZoneInfoForTimeOfDay(&coors, &zoneInfo); + switch (car->GetModelIndex()) { + case MI_FIRETRUCK: + preferredModel = 0; + pedType = PEDTYPE_FIREMAN; + break; + case MI_AMBULAN: + preferredModel = 0; + pedType = PEDTYPE_EMERGENCY; + break; + case MI_FBICAR: + preferredModel = COP_FBI; + pedType = PEDTYPE_COP; + break; + case MI_POLICE: + preferredModel = COP_STREET; + pedType = PEDTYPE_COP; + break; + case MI_ENFORCER: + preferredModel = COP_SWAT; + pedType = PEDTYPE_COP; + break; + case MI_RHINO: + case MI_BARRACKS: + preferredModel = COP_ARMY; + pedType = PEDTYPE_COP; + break; + case MI_TAXI: + case MI_CABBIE: + case MI_BORGNINE: + if (CGeneral::GetRandomTrueFalse()) { + pedType = PEDTYPE_CIVMALE; + preferredModel = MI_TAXI_D; + break; + } + defaultModel = MI_TAXI_D; + + // fall through + default: + int gangOfPed = 0; + imSureThatModelIsLoaded = false; + + while (gangOfPed < NUM_GANGS && CGangs::GetGangInfo(gangOfPed)->m_nVehicleMI != car->GetModelIndex()) + gangOfPed++; + + if (gangOfPed < NUM_GANGS) { + pedType = gangOfPed + PEDTYPE_GANG1; + preferredModel = ChooseGangOccupation(gangOfPed); + } else if (gangOfPed == NUM_GANGS) { + CVehicleModelInfo *carModelInfo = ((CVehicleModelInfo *)CModelInfo::GetModelInfo(car->GetModelIndex())); + int i = 15; + for(; i >= 0; i--) { + // Should return random model each time + preferredModel = ChooseCivilianOccupation(zoneInfo.pedGroup); + if (preferredModel == -1) + preferredModel = defaultModel; + + if (((CPedModelInfo*)CModelInfo::GetModelInfo(preferredModel))->m_carsCanDrive & (1 << carModelInfo->m_vehicleClass)) + break; + } + if (i == -1) + preferredModel = defaultModel; + + pedType = ((CPedModelInfo*)CModelInfo::GetModelInfo(preferredModel))->m_pedType; + } + break; + } + if (!imSureThatModelIsLoaded && !((CPedModelInfo*)CModelInfo::GetModelInfo(preferredModel))->GetRwObject()) { + preferredModel = defaultModel; + pedType = ((CPedModelInfo*)CModelInfo::GetModelInfo(defaultModel))->m_pedType; + } + + CPed *newPed = CPopulation::AddPed((ePedType)pedType, preferredModel, car->GetPosition()); + newPed->bUsesCollision = false; + + // what?? + if (pedType != PEDTYPE_COP) { + newPed->SetCurrentWeapon(WEAPONTYPE_COLT45); + newPed->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(newPed->GetWeapon()->m_eWeaponType)->m_nModelId); + } + + // Miami leftover + if (car->m_vehType == VEHICLE_TYPE_BIKE) { + newPed->m_pVehicleAnim = CAnimManager::BlendAnimation(newPed->GetClump(), ASSOCGRP_STD, ((CBike*)car)->m_bikeSitAnimation, 100.0f); + } else + + // FIX: Make peds comfortable while driving car/boat +#ifdef FIX_BUGS + { + newPed->m_pVehicleAnim = CAnimManager::BlendAnimation(newPed->GetClump(), ASSOCGRP_STD, car->GetDriverAnim(), 100.0f); + } +#else + { + newPed->m_pVehicleAnim = CAnimManager::BlendAnimation(newPed->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT, 100.0f); + } +#endif + + newPed->StopNonPartialAnims(); + return newPed; +} + +void +CPopulation::MoveCarsAndPedsOutOfAbandonedZones() +{ + eLevelName level; + int zone; + int frame = CTimer::GetFrameCounter() & 7; + if (frame == 1) { + int movedVehicleCount = 0; + int poolSize = CPools::GetVehiclePool()->GetSize(); + for (int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + + CVehicle* veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if (veh && veh->m_nZoneLevel == LEVEL_GENERIC && veh->IsCar()) { + + if(veh->GetStatus() != STATUS_ABANDONED && veh->GetStatus() != STATUS_WRECKED && veh->GetStatus() != STATUS_PLAYER && + veh->GetStatus() != STATUS_PLAYER_REMOTE) { + + CVector vehPos(veh->GetPosition()); + CPopulation::FindCollisionZoneForCoors(&vehPos, &zone, &level); + + // Level 0 is transition zones, and we don't wanna touch cars on transition zones. + if (level != LEVEL_GENERIC && level != CCollision::ms_collisionInMemory && vehPos.z > -4.0f) { + if (veh->bIsLocked || !veh->CanBeDeleted()) { + switch (movedVehicleCount & 3) { + case 0: + veh->SetPosition(RegenerationPoint_a); + break; + case 1: + veh->SetPosition(RegenerationPoint_b); + break; + case 2: + veh->SetPosition(RegenerationPoint_a.x, RegenerationPoint_b.y, RegenerationPoint_a.z); + break; + case 3: + veh->SetPosition(RegenerationPoint_b.x, RegenerationPoint_a.y, RegenerationPoint_a.z); + break; + default: + break; + } + veh->GetMatrix().GetPosition().z += (movedVehicleCount / 4) * 7.0f; + veh->GetMatrix().GetForward() = RegenerationFront; + ((CAutomobile*)veh)->PlaceOnRoadProperly(); + CCarCtrl::JoinCarWithRoadSystem(veh); + CTheScripts::ClearSpaceForMissionEntity(veh->GetPosition(), veh); + ++movedVehicleCount; + } else { + CWorld::Remove(veh); + delete veh; + } + } + } + } + } + } else if (frame == 5) { + int poolSize = CPools::GetPedPool()->GetSize(); + for (int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + + CPed *ped = CPools::GetPedPool()->GetSlot(poolIndex); + if (ped && ped->m_nZoneLevel == LEVEL_GENERIC && !ped->bInVehicle) { + + CVector pedPos(ped->GetPosition()); + CPopulation::FindCollisionZoneForCoors(&pedPos, &zone, &level); + + // Level 0 is transition zones, and we don't wanna touch peds on transition zones. + if (level != LEVEL_GENERIC && level != CCollision::ms_collisionInMemory && pedPos.z > -4.0f) { + if (ped->CanBeDeleted()) { + CWorld::Remove(ped); + delete ped; + } else if (ped->m_nPedType != PEDTYPE_PLAYER1 && ped->m_nPedType != PEDTYPE_PLAYER2) { + ped->SetPosition(RegenerationPoint_a); + + bool foundGround; + float groundZ = CWorld::FindGroundZFor3DCoord(ped->GetPosition().x, ped->GetPosition().y, + ped->GetPosition().z + 2.0f, &foundGround); + + if (foundGround) { + ped->GetMatrix().GetPosition().z = 1.0f + groundZ; + //ped->GetPosition().z += 0.0f; + CTheScripts::ClearSpaceForMissionEntity(ped->GetPosition(), ped); + } + } + } + } + } + } +} + +void +CPopulation::ConvertAllObjectsToDummyObjects() +{ + uint32 i = CPools::GetObjectPool()->GetSize(); + while(i--) { + CObject *obj = CPools::GetObjectPool()->GetSlot(i); + if (obj) { + if (obj->CanBeDeleted()) + ConvertToDummyObject(obj); + } + } +} + +void +CPopulation::ConvertToRealObject(CDummyObject *dummy) +{ + if (!TestSafeForRealObject(dummy)) + return; + + CObject *obj = new CObject(dummy); + if (!obj) + return; + + CWorld::Remove(dummy); + delete dummy; + CWorld::Add(obj); + + if (IsGlass(obj->GetModelIndex())) { + obj->bIsVisible = false; + } else if (obj->GetModelIndex() == MI_BUOY) { + obj->SetIsStatic(false); + obj->m_vecMoveSpeed = CVector(0.0f, 0.0f, -0.001f); + obj->bTouchingWater = true; + obj->AddToMovingList(); + } +} + +void +CPopulation::ConvertToDummyObject(CObject *obj) +{ + CDummyObject *dummy = new CDummyObject(obj); + + dummy->GetMatrix() = obj->m_objectMatrix; + dummy->GetMatrix().UpdateRW(); + dummy->UpdateRwFrame(); + + if (IsGlass(obj->GetModelIndex())) + dummy->bIsVisible = false; + + CWorld::Remove(obj); + delete obj; + CWorld::Add(dummy); +} + +bool +CPopulation::TestRoomForDummyObject(CObject *obj) +{ + int16 collidingObjs; + CWorld::FindObjectsKindaColliding(obj->m_objectMatrix.GetPosition(), CModelInfo::GetColModel(obj->GetModelIndex())->boundingSphere.radius, + false, &collidingObjs, 2, nil, false, true, true, false, false); + + return collidingObjs == 0; +} + +bool +CPopulation::TestSafeForRealObject(CDummyObject *dummy) +{ + CPtrNode *ptrNode; + CColModel *dummyCol = dummy->GetColModel(); + + float radius = dummyCol->boundingSphere.radius; + int minX = CWorld::GetSectorIndexX(dummy->GetPosition().x - radius); + if (minX < 0) minX = 0; + int minY = CWorld::GetSectorIndexY(dummy->GetPosition().y - radius); + if (minY < 0) minY = 0; + int maxX = CWorld::GetSectorIndexX(dummy->GetPosition().x + radius); +#ifdef FIX_BUGS + if (maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1; +#else + if (maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X; +#endif + + int maxY = CWorld::GetSectorIndexY(dummy->GetPosition().y + radius); +#ifdef FIX_BUGS + if (maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; +#else + if (maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y; +#endif + + float colRadius = dummy->GetBoundRadius(); + CVUVECTOR colCentre; + dummy->GetBoundCentre(colCentre); + + static CColPoint aTempColPoints[MAX_COLLISION_POINTS]; + + for (int curY = minY; curY <= maxY; curY++) { + for (int curX = minX; curX <= maxX; curX++) { + CSector *sector = CWorld::GetSector(curX, curY); + + for (ptrNode = sector->m_lists[ENTITYLIST_VEHICLES].first; ptrNode; ptrNode = ptrNode->next) { + CVehicle *veh = (CVehicle*)ptrNode->item; + if (veh->m_scanCode != CWorld::GetCurrentScanCode()) { + if (veh->GetIsTouching(colCentre, colRadius)) { + veh->m_scanCode = CWorld::GetCurrentScanCode(); + if (CCollision::ProcessColModels(dummy->GetMatrix(), *dummyCol, veh->GetMatrix(), *veh->GetColModel(), aTempColPoints, nil, nil) > 0) + return false; + } + } + } + + for (ptrNode = sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP].first; ptrNode; ptrNode = ptrNode->next) { + CVehicle *veh = (CVehicle*)ptrNode->item; + if (veh->m_scanCode != CWorld::GetCurrentScanCode()) { + if (veh->GetIsTouching(colCentre, colRadius)) { + veh->m_scanCode = CWorld::GetCurrentScanCode(); + if (CCollision::ProcessColModels(dummy->GetMatrix(), *dummyCol, veh->GetMatrix(), *veh->GetColModel(), aTempColPoints, nil, nil) > 0) + return false; + } + } + } + } + } + return true; +} + +void +CPopulation::ManagePopulation(void) +{ + int frameMod32 = CTimer::GetFrameCounter() & 31; + CVector playerPos = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + + // Why this code is here?! Delete temporary objects when they got too far, and convert others to "dummy" objects. (like lamp posts) + int objectPoolSize = CPools::GetObjectPool()->GetSize(); + for (int i = objectPoolSize * frameMod32 / 32; i < objectPoolSize * (frameMod32 + 1) / 32; i++) { + CObject *obj = CPools::GetObjectPool()->GetSlot(i); + if (obj && obj->CanBeDeleted()) { + if ((obj->GetPosition() - playerPos).Magnitude() <= 80.0f || + (obj->m_objectMatrix.GetPosition() - playerPos).Magnitude() <= 80.0f) { + if (obj->ObjectCreatedBy == TEMP_OBJECT && CTimer::GetTimeInMilliseconds() > obj->m_nEndOfLifeTime) { + CWorld::Remove(obj); + delete obj; + } + } else { + if (obj->ObjectCreatedBy == TEMP_OBJECT) { + CWorld::Remove(obj); + delete obj; + } else if (obj->ObjectCreatedBy != CUTSCENE_OBJECT && TestRoomForDummyObject(obj)) { + ConvertToDummyObject(obj); + } + } + } + } + + // Convert them back to real objects. Dummy objects don't have collisions, so they need to be converted. + int dummyPoolSize = CPools::GetDummyPool()->GetSize(); + for (int i = dummyPoolSize * frameMod32 / 32; i < dummyPoolSize * (frameMod32 + 1) / 32; i++) { + CDummy *dummy = CPools::GetDummyPool()->GetSlot(i); + if (dummy) { + if ((dummy->GetPosition() - playerPos).Magnitude() < 80.0f) + ConvertToRealObject((CDummyObject*)dummy); + } + } + + int pedPoolSize = CPools::GetPedPool()->GetSize(); +#ifndef SQUEEZE_PERFORMANCE + for (int poolIndex = pedPoolSize-1; poolIndex >= 0; poolIndex--) { +#else + for (int poolIndex = (pedPoolSize * (frameMod32 + 1) / 32) - 1; poolIndex >= pedPoolSize * frameMod32 / 32; poolIndex--) { +#endif + CPed *ped = CPools::GetPedPool()->GetSlot(poolIndex); + + if (ped && !ped->IsPlayer() && ped->CanBeDeleted() && !ped->bInVehicle) { + if (ped->m_nPedState == PED_DEAD && CTimer::GetTimeInMilliseconds() - ped->m_bloodyFootprintCountOrDeathTime > 60000) + ped->bFadeOut = true; + + if (ped->bFadeOut && CVisibilityPlugins::GetClumpAlpha(ped->GetClump()) == 0) { + RemovePed(ped); + continue; + } + + float dist = (ped->GetPosition() - playerPos).Magnitude2D(); + + bool pedIsFarAway = false; + if (PedCreationDistMultiplier() * (PED_REMOVE_DIST_SPECIAL * TheCamera.GenerationDistMultiplier) < dist + || (!ped->bCullExtraFarAway && PedCreationDistMultiplier() * PED_REMOVE_DIST * TheCamera.GenerationDistMultiplier < dist) +#ifndef EXTENDED_OFFSCREEN_DESPAWN_RANGE + || (PedCreationDistMultiplier() * (MIN_CREATION_DIST + CREATION_RANGE) * OFFSCREEN_CREATION_MULT < dist + && !ped->GetIsOnScreen() + && TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_SNIPER + && TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_SNIPER_RUNABOUT + && !TheCamera.Cams[TheCamera.ActiveCam].LookingLeft + && !TheCamera.Cams[TheCamera.ActiveCam].LookingRight + && !TheCamera.Cams[TheCamera.ActiveCam].LookingBehind) +#endif + ) + pedIsFarAway = true; + + if (!pedIsFarAway) + continue; + + if (ped->m_nPedState == PED_DEAD && !ped->bFadeOut) { + CVector pedPos = ped->GetPosition(); + + float randAngle = (uint8) CGeneral::GetRandomNumber() * (3.14f / 128.0f); // Not PI, 3.14 + switch (CGeneral::GetRandomNumber() % 3) { + case 0: + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpOutline1Tex, &pedPos, + 0.9f * Cos(randAngle), 0.9f * Sin(randAngle), 0.9f * Sin(randAngle), -0.9f * Cos(randAngle), + 255, 255, 255, 255, 4.0f, 40000, 1.0f); + break; + case 1: + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpOutline2Tex, &pedPos, + 0.9f * Cos(randAngle), 0.9f * Sin(randAngle), 0.9f * Sin(randAngle), -0.9f * Cos(randAngle), + 255, 255, 255, 255, 4.0f, 40000, 1.0f); + break; + case 2: + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpOutline3Tex, &pedPos, + 0.9f * Cos(randAngle), 0.9f * Sin(randAngle), 0.9f * Sin(randAngle), -0.9f * Cos(randAngle), + 255, 255, 255, 255, 4.0f, 40000, 1.0f); + break; + default: + break; + } + } + if (ped->GetIsOnScreen()) + ped->bFadeOut = true; + else + RemovePed(ped); + } + } +} diff --git a/src/peds/Population.h b/src/peds/Population.h new file mode 100644 index 0000000..61f0bdb --- /dev/null +++ b/src/peds/Population.h @@ -0,0 +1,89 @@ +#pragma once + +#include "Game.h" +#include "PedType.h" + +class CPed; +class CVehicle; +class CDummyObject; +class CObject; + +struct PedGroup +{ + int32 models[NUMMODELSPERPEDGROUP]; +}; + +// Don't know the original name +struct RegenerationPoint +{ + eLevelName srcLevel; // this and below one may need to be exchanged + eLevelName destLevel; + float x1; + float x2; + float y1; + float y2; + float z1; + float z2; + RwV3d destPosA; + RwV3d destPosB; + RwV3d srcPosA; + RwV3d srcPosB; +}; + +class CPopulation +{ +public: + static PedGroup ms_pPedGroups[NUMPEDGROUPS]; + static bool ms_bGivePedsWeapons; + static int32 m_AllRandomPedsThisType; + static float PedDensityMultiplier; + static uint32 ms_nTotalMissionPeds; + static int32 MaxNumberOfPedsInUse; + static uint32 ms_nNumCivMale; + static uint32 ms_nNumCivFemale; + static uint32 ms_nNumCop; + static bool bZoneChangeHasHappened; + static uint32 ms_nNumEmergency; + static int8 m_CountDownToPedsAtStart; + static uint32 ms_nNumGang1; + static uint32 ms_nNumGang2; + static uint32 ms_nTotalPeds; + static uint32 ms_nNumGang3; + static uint32 ms_nTotalGangPeds; + static uint32 ms_nNumGang4; + static uint32 ms_nTotalCivPeds; + static uint32 ms_nNumGang5; + static uint32 ms_nNumDummy; + static uint32 ms_nNumGang6; + static uint32 ms_nNumGang9; + static uint32 ms_nNumGang7; + static uint32 ms_nNumGang8; + static CVector RegenerationPoint_a; + static CVector RegenerationPoint_b; + static CVector RegenerationFront; + + static void Initialise(); + static void Update(void); + static void LoadPedGroups(); + static void UpdatePedCount(ePedType, bool); + static void DealWithZoneChange(eLevelName oldLevel, eLevelName newLevel, bool); + static CPed *AddPedInCar(CVehicle *car); + static bool IsPointInSafeZone(CVector *coors); + static void RemovePed(CPed *ent); + static int32 ChooseCivilianOccupation(int32); + static int32 ChoosePolicePedOccupation(); + static int32 ChooseGangOccupation(int); + static void FindCollisionZoneForCoors(CVector*, int*, eLevelName*); + static void FindClosestZoneForCoors(CVector*, int*, eLevelName, eLevelName); + static void GeneratePedsAtStartOfGame(); + static float PedCreationDistMultiplier(); + static CPed *AddPed(ePedType pedType, uint32 mi, CVector const &coors); + static void AddToPopulation(float, float, float, float); + static void ManagePopulation(void); + static void MoveCarsAndPedsOutOfAbandonedZones(void); + static void ConvertToRealObject(CDummyObject*); + static void ConvertToDummyObject(CObject*); + static void ConvertAllObjectsToDummyObjects(void); + static bool TestRoomForDummyObject(CObject*); + static bool TestSafeForRealObject(CDummyObject*); +}; diff --git a/src/renderer/2dEffect.h b/src/renderer/2dEffect.h new file mode 100644 index 0000000..a8013b3 --- /dev/null +++ b/src/renderer/2dEffect.h @@ -0,0 +1,93 @@ +#pragma once + +enum { + EFFECT_LIGHT, + EFFECT_PARTICLE, + EFFECT_ATTRACTOR +}; + +enum { + LIGHT_ON, + LIGHT_ON_NIGHT, + LIGHT_FLICKER, + LIGHT_FLICKER_NIGHT, + LIGHT_FLASH1, + LIGHT_FLASH1_NIGHT, + LIGHT_FLASH2, + LIGHT_FLASH2_NIGHT, + LIGHT_FLASH3, + LIGHT_FLASH3_NIGHT, + LIGHT_RANDOM_FLICKER, + LIGHT_RANDOM_FLICKER_NIGHT, + LIGHT_SPECIAL, + LIGHT_BRIDGE_FLASH1, + LIGHT_BRIDGE_FLASH2, +}; + +enum { + ATTRACTORTYPE_ICECREAM, + ATTRACTORTYPE_STARE +}; + +enum { + LIGHTFLAG_LOSCHECK = 1, + // same order as CPointLights flags, must start at 2 + LIGHTFLAG_FOG_NORMAL = 2, // can have light and fog + LIGHTFLAG_FOG_ALWAYS = 4, // fog only + LIGHTFLAG_FOG = (LIGHTFLAG_FOG_NORMAL|LIGHTFLAG_FOG_ALWAYS) +}; + +class C2dEffect +{ +public: + struct Light { + float dist; + float range; // of pointlight + float size; + float shadowSize; + uint8 lightType; // LIGHT_ + uint8 roadReflection; + uint8 flareType; + uint8 shadowIntensity; + uint8 flags; // LIGHTFLAG_ + RwTexture *corona; + RwTexture *shadow; + }; + struct Particle { + int particleType; + CVector dir; + float scale; + }; + struct Attractor { + CVector dir; + int8 type; + uint8 probability; + }; + + CVector pos; + CRGBA col; + uint8 type; + union { + Light light; + Particle particle; + Attractor attractor; + }; + + C2dEffect(void) {} + void Shutdown(void){ + if(type == EFFECT_LIGHT){ + if(light.corona) + RwTextureDestroy(light.corona); +#if GTA_VERSION >= GTA3_PC_11 + light.corona = nil; +#endif + if(light.shadow) + RwTextureDestroy(light.shadow); +#if GTA_VERSION >= GTA3_PC_11 + light.shadow = nil; +#endif + } + } +}; + +VALIDATE_SIZE(C2dEffect, 0x34); diff --git a/src/renderer/Antennas.cpp b/src/renderer/Antennas.cpp new file mode 100644 index 0000000..5e30aca --- /dev/null +++ b/src/renderer/Antennas.cpp @@ -0,0 +1,129 @@ +#include "common.h" + +#include "main.h" +#include "Antennas.h" + +CAntenna CAntennas::aAntennas[NUMANTENNAS]; + +void +CAntennas::Init(void) +{ + int i; + for(i = 0; i < NUMANTENNAS; i++){ + aAntennas[i].active = false; + aAntennas[i].updatedLastFrame = false; + } +} + +// Free antennas that aren't used anymore +void +CAntennas::Update(void) +{ + int i; + + for(i = 0; i < NUMANTENNAS; i++){ + if(aAntennas[i].active && !aAntennas[i].updatedLastFrame) + aAntennas[i].active = false; + aAntennas[i].updatedLastFrame = false; + } +} + +// Add a new one or update an old one +void +CAntennas::RegisterOne(uint32 id, CVector dir, CVector position, float length) +{ + int i, j; + + for(i = 0; i < NUMANTENNAS; i++) + if(aAntennas[i].active && aAntennas[i].id == id) + break; + + if(i >= NUMANTENNAS){ + // not found, register new one + + // find empty slot + for(i = 0; i < NUMANTENNAS; i++) + if(!aAntennas[i].active) + break; + + // there is space + if(i < NUMANTENNAS){ + aAntennas[i].active = true; + aAntennas[i].updatedLastFrame = true; + aAntennas[i].id = id; + aAntennas[i].segmentLength = length/6.0f; + for(j = 0; j < 6; j++){ + aAntennas[i].pos[j] = position + dir*j*aAntennas[i].segmentLength; + aAntennas[i].speed[j] = CVector(0.0f, 0.0f, 0.0f); + } + } + }else{ + // found, update + aAntennas[i].Update(dir, position); + aAntennas[i].updatedLastFrame = true; + } +} + +static RwIm3DVertex vertexbufferA[2]; + +void +CAntennas::Render(void) +{ + int i, j; + + PUSH_RENDERGROUP("CAntennas::Render"); + for(i = 0; i < NUMANTENNAS; i++){ + if(!aAntennas[i].active) + continue; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + for(j = 0; j < 5; j++){ + RwIm3DVertexSetRGBA(&vertexbufferA[0], 200, 200, 200, 100); + RwIm3DVertexSetPos(&vertexbufferA[0], + aAntennas[i].pos[j].x, + aAntennas[i].pos[j].y, + aAntennas[i].pos[j].z); + RwIm3DVertexSetRGBA(&vertexbufferA[1], 200, 200, 200, 100); + RwIm3DVertexSetPos(&vertexbufferA[1], + aAntennas[i].pos[j+1].x, + aAntennas[i].pos[j+1].y, + aAntennas[i].pos[j+1].z); + + // LittleTest(); + if(RwIm3DTransform(vertexbufferA, 2, nil, 0)){ + RwIm3DRenderLine(0, 1); + RwIm3DEnd(); + } + } + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + + POP_RENDERGROUP(); +} + +void +CAntenna::Update(CVector dir, CVector basepos) +{ + int i; + + pos[0] = basepos; + pos[1] = basepos + dir*segmentLength; + + for(i = 2; i < 6; i++){ + CVector basedir = pos[i-1] - pos[i-2]; + CVector newdir = pos[i] - pos[i-1] + // drag along + dir*0.1f + // also drag up a bit for stiffness + speed[i]; // and keep moving + newdir.Normalise(); + newdir *= segmentLength; + CVector newpos = pos[i-1] + (basedir + newdir)/2.0f; + speed[i] = (newpos - pos[i])*0.9f; + pos[i] = newpos; + } +} diff --git a/src/renderer/Antennas.h b/src/renderer/Antennas.h new file mode 100644 index 0000000..47cb1da --- /dev/null +++ b/src/renderer/Antennas.h @@ -0,0 +1,25 @@ +#pragma once + +class CAntenna +{ +public: + bool active; + bool updatedLastFrame; + uint32 id; + float segmentLength; + CVector pos[6]; + CVector speed[6]; + + void Update(CVector dir, CVector pos); +}; + +class CAntennas +{ + // no need to use game's array + static CAntenna aAntennas[NUMANTENNAS]; +public: + static void Init(void); + static void Update(void); + static void RegisterOne(uint32 id, CVector dir, CVector position, float length); + static void Render(void); +}; diff --git a/src/renderer/Clouds.cpp b/src/renderer/Clouds.cpp new file mode 100644 index 0000000..957844a --- /dev/null +++ b/src/renderer/Clouds.cpp @@ -0,0 +1,466 @@ +#include "common.h" + +#include "main.h" +#include "Sprite.h" +#include "Sprite2d.h" +#include "General.h" +#include "Coronas.h" +#include "Camera.h" +#include "TxdStore.h" +#include "Weather.h" +#include "Clock.h" +#include "Timer.h" +#include "Timecycle.h" +#include "Renderer.h" +#include "Clouds.h" + +#define SMALLSTRIPHEIGHT 4.0f +#define HORIZSTRIPHEIGHT 48.0f + +RwTexture *gpCloudTex[5]; + +float CClouds::CloudRotation; +uint32 CClouds::IndividualRotation; + +float CClouds::ms_cameraRoll; +float CClouds::ms_horizonZ; +CRGBA CClouds::ms_colourTop; +CRGBA CClouds::ms_colourBottom; + +void +CClouds::Init(void) +{ + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle")); + gpCloudTex[0] = RwTextureRead("cloud1", nil); + gpCloudTex[1] = RwTextureRead("cloud2", nil); + gpCloudTex[2] = RwTextureRead("cloud3", nil); + gpCloudTex[3] = RwTextureRead("cloudhilit", nil); + gpCloudTex[4] = RwTextureRead("cloudmasked", nil); + CTxdStore::PopCurrentTxd(); + CloudRotation = 0.0f; +} + +void +CClouds::Shutdown(void) +{ + RwTextureDestroy(gpCloudTex[0]); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex[0] = nil; +#endif + RwTextureDestroy(gpCloudTex[1]); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex[1] = nil; +#endif + RwTextureDestroy(gpCloudTex[2]); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex[2] = nil; +#endif + RwTextureDestroy(gpCloudTex[3]); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex[3] = nil; +#endif + RwTextureDestroy(gpCloudTex[4]); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex[4] = nil; +#endif +} + +void +CClouds::Update(void) +{ + float s = Sin(TheCamera.Orientation - 0.85f); +#ifdef FIX_BUGS + CloudRotation += CWeather::Wind*s*0.0025f*CTimer::GetTimeStepFix(); + IndividualRotation += (CWeather::Wind*CTimer::GetTimeStep() + 0.3f*CTimer::GetTimeStepFix()) * 60.0f; +#else + CloudRotation += CWeather::Wind*s*0.0025f; + IndividualRotation += (CWeather::Wind*CTimer::GetTimeStep() + 0.3f) * 60.0f; +#endif +} + +float StarCoorsX[9] = { 0.0f, 0.05f, 0.12f, 0.5f, 0.8f, 0.6f, 0.27f, 0.55f, 0.75f }; +float StarCoorsY[9] = { 0.0f, 0.45f, 0.9f, 1.0f, 0.85f, 0.52f, 0.48f, 0.35f, 0.2f }; +float StarSizes[9] = { 1.0f, 1.4f, 0.9f, 1.0f, 0.6f, 1.5f, 1.3f, 1.0f, 0.8f }; + +float LowCloudsX[12] = { 1.0f, 0.7f, 0.0f, -0.7f, -1.0f, -0.7f, 0.0f, 0.7f, 0.8f, -0.8f, 0.4f, -0.4f }; +float LowCloudsY[12] = { 0.0f, -0.7f, -1.0f, -0.7f, 0.0f, 0.7f, 1.0f, 0.7f, 0.4f, 0.4f, -0.8f, -0.8f }; +float LowCloudsZ[12] = { 0.0f, 1.0f, 0.5f, 0.0f, 1.0f, 0.3f, 0.9f, 0.4f, 1.3f, 1.4f, 1.2f, 1.7f }; + +float CoorsOffsetX[37] = { + 0.0f, 60.0f, 72.0f, 48.0f, 21.0f, 12.0f, + 9.0f, -3.0f, -8.4f, -18.0f, -15.0f, -36.0f, + -40.0f, -48.0f, -60.0f, -24.0f, 100.0f, 100.0f, + 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, + 100.0f, 100.0f, -30.0f, -20.0f, 10.0f, 30.0f, + 0.0f, -100.0f, -100.0f, -100.0f, -100.0f, -100.0f, -100.0f +}; +float CoorsOffsetY[37] = { + 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, + 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, + 100.0f, 100.0f, 100.0f, 100.0f, -30.0f, 10.0f, + -25.0f, -5.0f, 28.0f, -10.0f, 10.0f, 0.0f, + 15.0f, 40.0f, -100.0f, -100.0f, -100.0f, -100.0f, + -100.0f, -40.0f, -20.0f, 0.0f, 10.0f, 30.0f, 35.0f +}; +float CoorsOffsetZ[37] = { + 2.0f, 1.0f, 0.0f, 0.3f, 0.7f, 1.4f, + 1.7f, 0.24f, 0.7f, 1.3f, 1.6f, 1.0f, + 1.2f, 0.3f, 0.7f, 1.4f, 0.0f, 0.1f, + 0.5f, 0.4f, 0.55f, 0.75f, 1.0f, 1.4f, + 1.7f, 2.0f, 2.0f, 2.3f, 1.9f, 2.4f, + 2.0f, 2.0f, 1.5f, 1.2f, 1.7f, 1.5f, 2.1f +}; + +uint8 BowRed[6] = { 30, 30, 30, 10, 0, 15 }; +uint8 BowGreen[6] = { 0, 15, 30, 30, 0, 0 }; +uint8 BowBlue[6] = { 0, 0, 0, 10, 30, 30 }; + +void +CClouds::Render(void) +{ + int i; + float szx, szy; + RwV3d screenpos; + RwV3d worldpos; + + PUSH_RENDERGROUP("CClouds::Render"); + + CCoronas::SunBlockedByClouds = false; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + CSprite::InitSpriteBuffer(); + + int minute = CClock::GetHours()*60 + CClock::GetMinutes(); + RwV3d campos = TheCamera.GetPosition(); + + // Moon + int moonfadeout = Abs(minute - 180); // fully visible at 3AM + if(moonfadeout < 180){ // fade in/out 3 hours + float coverage = Max(CWeather::Foggyness, CWeather::CloudCoverage); + int brightness = (1.0f - coverage) * (180 - moonfadeout); + RwV3d pos = { 0.0f, -100.0f, 15.0f }; + RwV3dAdd(&worldpos, &campos, &pos); + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[2])); + if(CCoronas::bSmallMoon){ + szx *= 4.0f; + szy *= 4.0f; + }else{ + szx *= 10.0f; + szy *= 10.0f; + } + CSprite::RenderOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + szx, szy, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255); + } + } + + // The R* logo + int starintens = 0; + if(CClock::GetHours() < 22 && CClock::GetHours() > 5) + starintens = 0; + else if(CClock::GetHours() > 22 || CClock::GetHours() < 5) + starintens = 255; + else if(CClock::GetHours() == 22) + starintens = 255 * CClock::GetMinutes()/60.0f; + else if(CClock::GetHours() == 5) + starintens = 255 * (60 - CClock::GetMinutes())/60.0f; + if(starintens != 0){ + float coverage = Max(CWeather::Foggyness, CWeather::CloudCoverage); + int brightness = (1.0f - coverage) * starintens; + + // R + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[0])); + for(i = 0; i < 11; i++){ + RwV3d pos = { 100.0f, 0.0f, 10.0f }; + if(i >= 9) pos.x = -pos.x; + RwV3dAdd(&worldpos, &campos, &pos); + worldpos.y -= 90.0f*StarCoorsX[i%9]; + worldpos.z += 80.0f*StarCoorsY[i%9]; + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + float sz = 0.8f*StarSizes[i%9]; + CSprite::RenderBufferedOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + szx*sz, szy*sz, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255); + } + } + CSprite::FlushSpriteBuffer(); + + // * + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[0])); + RwV3d pos = { 100.0f, 0.0f, 10.0f }; + RwV3dAdd(&worldpos, &campos, &pos); + worldpos.y -= 90.0f; + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + brightness *= (CGeneral::GetRandomNumber()&127) / 640.0f + 0.5f; + CSprite::RenderOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + szx*5.0f, szy*5.0f, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255); + } + } + + // Low clouds + float lowcloudintensity = 1.0f - Max(CWeather::Foggyness, CWeather::CloudCoverage); + int r = CTimeCycle::GetLowCloudsRed() * lowcloudintensity; + int g = CTimeCycle::GetLowCloudsGreen() * lowcloudintensity; + int b = CTimeCycle::GetLowCloudsBlue() * lowcloudintensity; + for(int cloudtype = 0; cloudtype < 3; cloudtype++){ + for(i = cloudtype; i < 12; i += 3){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCloudTex[cloudtype])); + RwV3d pos = { 800.0f*LowCloudsX[i], 800.0f*LowCloudsY[i], 60.0f*LowCloudsZ[i] }; + worldpos.x = campos.x + pos.x; + worldpos.y = campos.y + pos.y; + worldpos.z = 40.0f + pos.z; + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)) + CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(screenpos.x, screenpos.y, screenpos.z, + szx*320.0f, szy*40.0f, r, g, b, 255, 1.0f/screenpos.z, ms_cameraRoll, 255); + } + CSprite::FlushSpriteBuffer(); + } + + // Fluffy clouds + float rot_sin = Sin(CloudRotation); + float rot_cos = Cos(CloudRotation); + int fluffyalpha = 160 * (1.0f - CWeather::Foggyness); + if(fluffyalpha != 0){ + static bool bCloudOnScreen[37]; + float hilight; + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCloudTex[4])); + for(i = 0; i < 37; i++){ + RwV3d pos = { 2.0f*CoorsOffsetX[i], 2.0f*CoorsOffsetY[i], 40.0f*CoorsOffsetZ[i] + 40.0f }; + worldpos.x = pos.x*rot_cos + pos.y*rot_sin + campos.x; + worldpos.y = pos.x*rot_sin - pos.y*rot_cos + campos.y; + worldpos.z = pos.z; + + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + float sundist = Sqrt(sq(screenpos.x-CCoronas::SunScreenX) + sq(screenpos.y-CCoronas::SunScreenY)); + int tr = CTimeCycle::GetFluffyCloudsTopRed(); + int tg = CTimeCycle::GetFluffyCloudsTopGreen(); + int tb = CTimeCycle::GetFluffyCloudsTopBlue(); + int br = CTimeCycle::GetFluffyCloudsBottomRed(); + int bg = CTimeCycle::GetFluffyCloudsBottomGreen(); + int bb = CTimeCycle::GetFluffyCloudsBottomBlue(); + if(sundist < SCREEN_WIDTH/2){ + hilight = (1.0f - Max(CWeather::Foggyness, CWeather::CloudCoverage)) * (1.0f - sundist/(SCREEN_WIDTH/2)); + tr = tr*(1.0f-hilight) + 255*hilight; + tg = tg*(1.0f-hilight) + 190*hilight; + tb = tb*(1.0f-hilight) + 190*hilight; + br = br*(1.0f-hilight) + 255*hilight; + bg = bg*(1.0f-hilight) + 190*hilight; + bb = bb*(1.0f-hilight) + 190*hilight; + if(sundist < SCREEN_WIDTH/10) + CCoronas::SunBlockedByClouds = true; + }else + hilight = 0.0f; + CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours(screenpos.x, screenpos.y, screenpos.z, + szx*55.0f, szy*55.0f, + tr, tg, tb, br, bg, bb, 0.0f, -1.0f, + 1.0f/screenpos.z, + (uint16)IndividualRotation/65336.0f * 6.28f + ms_cameraRoll, + fluffyalpha); + bCloudOnScreen[i] = true; + }else + bCloudOnScreen[i] = false; + } + CSprite::FlushSpriteBuffer(); + + // Highlights + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCloudTex[3])); + + for(i = 0; i < 37; i++){ + RwV3d pos = { 2.0f*CoorsOffsetX[i], 2.0f*CoorsOffsetY[i], 40.0f*CoorsOffsetZ[i] + 40.0f }; + worldpos.x = pos.x*rot_cos + pos.y*rot_sin + campos.x; + worldpos.y = pos.x*rot_sin - pos.y*rot_cos + campos.y; + worldpos.z = pos.z; + if(bCloudOnScreen[i] && CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + // BUG: this is stupid....would have to do this for each cloud individually + if(hilight > 0.0f){ + CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(screenpos.x, screenpos.y, screenpos.z, + szx*30.0f, szy*30.0f, + 200*hilight, 0, 0, 255, 1.0f/screenpos.z, + 1.7f - CGeneral::GetATanOfXY(screenpos.x-CCoronas::SunScreenX, screenpos.y-CCoronas::SunScreenY) + CClouds::ms_cameraRoll, 255); + } + } + } + CSprite::FlushSpriteBuffer(); + } + + // Rainbow + if(CWeather::Rainbow != 0.0f){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[0])); + for(i = 0; i < 6; i++){ + RwV3d pos = { i*1.5f, 100.0f, 5.0f }; + RwV3dAdd(&worldpos, &campos, &pos); + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)) + CSprite::RenderBufferedOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + 2.0f*szx, 50.0*szy, + BowRed[i]*CWeather::Rainbow, BowGreen[i]*CWeather::Rainbow, BowBlue[i]*CWeather::Rainbow, + 255, 1.0f/screenpos.z, 255); + + } + CSprite::FlushSpriteBuffer(); + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + POP_RENDERGROUP(); +} + +bool +UseDarkBackground(void) +{ + return TheCamera.GetForward().z < -0.9f || gbShowCollisionPolys; +} + +void +CClouds::RenderBackground(int16 topred, int16 topgreen, int16 topblue, + int16 botred, int16 botgreen, int16 botblue, int16 alpha) +{ + PUSH_RENDERGROUP("CClouds::RenderBackground"); + + CVector left = TheCamera.GetRight(); + float c = left.Magnitude2D(); + if(c > 1.0f) + c = 1.0f; + ms_cameraRoll = Acos(c); + if(left.z < 0.0f) + ms_cameraRoll = -ms_cameraRoll; + + if(UseDarkBackground()){ + ms_colourTop.r = 50; + ms_colourTop.g = 50; + ms_colourTop.b = 50; + ms_colourTop.a = 255; + if(gbShowCollisionPolys){ + if(CTimer::GetFrameCounter() & 1){ + ms_colourTop.r = 0; + ms_colourTop.g = 0; + ms_colourTop.b = 0; + }else{ + ms_colourTop.r = 255; + ms_colourTop.g = 255; + ms_colourTop.b = 255; + } + } + ms_colourBottom = ms_colourTop; + CRect r(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + CSprite2d::DrawRect(r, ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + }else{ + ms_horizonZ = CSprite::CalcHorizonCoors(); + + // Draw top/bottom gradient + float gradheight = SCREEN_HEIGHT/2.0f; + float topedge = ms_horizonZ - gradheight; + float botpos, toppos; + if(ms_horizonZ > 0.0f && topedge < SCREEN_HEIGHT){ + ms_colourTop.r = topred; + ms_colourTop.g = topgreen; + ms_colourTop.b = topblue; + ms_colourTop.a = alpha; + ms_colourBottom.r = botred; + ms_colourBottom.g = botgreen; + ms_colourBottom.b = botblue; + ms_colourBottom.a = alpha; + + if(ms_horizonZ < SCREEN_HEIGHT) + botpos = ms_horizonZ; + else{ + float f = (ms_horizonZ - SCREEN_HEIGHT)/gradheight; + ms_colourBottom.r = topred*f + (1.0f-f)*botred; + ms_colourBottom.g = topgreen*f + (1.0f-f)*botgreen; + ms_colourBottom.b = topblue*f + (1.0f-f)*botblue; + botpos = SCREEN_HEIGHT; + } + if(topedge >= 0.0f) + toppos = topedge; + else{ + float f = (0.0f - topedge)/gradheight; + ms_colourTop.r = botred*f + (1.0f-f)*topred; + ms_colourTop.g = botgreen*f + (1.0f-f)*topgreen; + ms_colourTop.b = botblue*f + (1.0f-f)*topblue; + toppos = 0.0f; + } + CSprite2d::DrawRect(CRect(0, toppos, SCREEN_WIDTH, botpos), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + } + + // draw the small stripe (whatever it's supposed to be) + if(ms_horizonZ > -SMALLSTRIPHEIGHT && ms_horizonZ < SCREEN_HEIGHT){ + // Same colour as fog + ms_colourTop.r = (topred + 2 * botred) / 3; + ms_colourTop.g = (topgreen + 2 * botgreen) / 3; + ms_colourTop.b = (topblue + 2 * botblue) / 3; + CSprite2d::DrawRect(CRect(0, ms_horizonZ, SCREEN_WIDTH, ms_horizonZ+SMALLSTRIPHEIGHT), + ms_colourTop, ms_colourTop, ms_colourTop, ms_colourTop); + } + + // Only top + if(topedge > 0.0f){ + ms_colourTop.r = topred; + ms_colourTop.g = topgreen; + ms_colourTop.b = topblue; + ms_colourTop.a = alpha; + ms_colourBottom.r = topred; + ms_colourBottom.g = topgreen; + ms_colourBottom.b = topblue; + ms_colourBottom.a = alpha; + + botpos = Min(SCREEN_HEIGHT, topedge); + CSprite2d::DrawRect(CRect(0, 0, SCREEN_WIDTH, botpos), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + } + + // Set both to fog colour for RenderHorizon + ms_colourTop.r = (topred + 2 * botred) / 3; + ms_colourTop.g = (topgreen + 2 * botgreen) / 3; + ms_colourTop.b = (topblue + 2 * botblue) / 3; + ms_colourBottom.r = (topred + 2 * botred) / 3; + ms_colourBottom.g = (topgreen + 2 * botgreen) / 3; + ms_colourBottom.b = (topblue + 2 * botblue) / 3; + } + + POP_RENDERGROUP(); +} + +void +CClouds::RenderHorizon(void) +{ + if(UseDarkBackground()) + return; + + ms_colourBottom.a = 230; + ms_colourTop.a = 80; + + if(ms_horizonZ > SCREEN_HEIGHT) + return; + + PUSH_RENDERGROUP("CClouds::RenderHorizon"); + + float z1 = Min(ms_horizonZ + SMALLSTRIPHEIGHT, SCREEN_HEIGHT); + CSprite2d::DrawRectXLU(CRect(0, ms_horizonZ, SCREEN_WIDTH, z1), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + + // This is just weird + float a = SCREEN_HEIGHT/400.0f * HORIZSTRIPHEIGHT + + SCREEN_HEIGHT/300.0f * Max(TheCamera.GetPosition().z, 0.0f); + float b = TheCamera.GetUp().z < 0.0f ? + SCREEN_HEIGHT : + SCREEN_HEIGHT * Abs(TheCamera.GetRight().z); + float z2 = z1 + (a + b)*TheCamera.LODDistMultiplier; + z2 = Min(z2, SCREEN_HEIGHT); + CSprite2d::DrawRect(CRect(0, z1, SCREEN_WIDTH, z2), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + + POP_RENDERGROUP(); +} diff --git a/src/renderer/Clouds.h b/src/renderer/Clouds.h new file mode 100644 index 0000000..4d8cd2c --- /dev/null +++ b/src/renderer/Clouds.h @@ -0,0 +1,21 @@ +#pragma once + +class CClouds +{ +public: + static float CloudRotation; + static uint32 IndividualRotation; + + static float ms_cameraRoll; + static float ms_horizonZ; + static CRGBA ms_colourTop; + static CRGBA ms_colourBottom; + + static void Init(void); + static void Shutdown(void); + static void Update(void); + static void Render(void); + static void RenderBackground(int16 topred, int16 topgreen, int16 topblue, + int16 botred, int16 botgreen, int16 botblue, int16 alpha); + static void RenderHorizon(void); +}; diff --git a/src/renderer/Console.cpp b/src/renderer/Console.cpp new file mode 100644 index 0000000..8ea5b7a --- /dev/null +++ b/src/renderer/Console.cpp @@ -0,0 +1,96 @@ +#include "common.h" +#include + +#include "Console.h" +#include "Font.h" +#include "Timer.h" + +#define CONSOLE_X_POS (30.0f) +#define CONSOLE_Y_POS (10.0f) +#define CONSOLE_LINE_HEIGHT (12.0f) + +CConsole TheConsole; + +void +CConsole::AddLine(char *s, uint8 r, uint8 g, uint8 b) +{ + char tempstr[MAX_STR_LEN+1]; + + while (strlen(s) > MAX_STR_LEN) { + strncpy(tempstr, s, MAX_STR_LEN); + tempstr[MAX_STR_LEN-1] = '\0'; + s += MAX_STR_LEN - 1; + AddOneLine(tempstr, r, g, b); + } + AddOneLine(s, r, g, b); +} + +void +CConsole::AddOneLine(char *s, uint8 r, uint8 g, uint8 b) +{ + int32 StrIndex = (m_nLineCount + m_nCurrentLine) % MAX_LINES; + + for (int32 i = 0; i < MAX_STR_LEN; i++) { + Buffers[StrIndex][i] = s[i]; + if (s[i] == '\0') break; + } + + uint8 _strNum1 = m_nLineCount; + if (_strNum1 < MAX_LINES) + _strNum1++; + + m_aTimer[StrIndex] = CTimer::GetTimeInMilliseconds(); + Buffers[StrIndex][MAX_STR_LEN-1] = '\0'; + m_aRed[StrIndex] = r; + m_aGreen[StrIndex] = g; + m_aBlue[StrIndex] = b; + + if (_strNum1 >= MAX_LINES) + m_nCurrentLine = (m_nCurrentLine + 1) % MAX_LINES; + else + m_nLineCount = _strNum1; + +} + +void +CConsole::Display() +{ + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(0.6f, 0.6f); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); +#ifndef FIX_BUGS + CFont::SetPropOff(); // not sure why this is here anyway +#endif + CFont::SetWrapx(RsGlobal.width); + + while (m_nLineCount != 0 && CTimer::GetTimeInMilliseconds() - m_aTimer[m_nCurrentLine] > 20000) { + m_nLineCount--; + m_nCurrentLine = (m_nCurrentLine + 1) % MAX_LINES; + } + + for (int16 i = 0; i < m_nLineCount; i++) { + int16 line = (i + m_nCurrentLine) % MAX_LINES; + CFont::SetColor(CRGBA(0, 0, 0, 200)); + CFont::PrintString(CONSOLE_X_POS + 1.0f, CONSOLE_Y_POS + 1.0f + i * CONSOLE_LINE_HEIGHT, Buffers[line]); + CFont::SetColor(CRGBA(m_aRed[line], m_aGreen[line], m_aBlue[line], 200)); + CFont::PrintString(CONSOLE_X_POS, CONSOLE_Y_POS + i * CONSOLE_LINE_HEIGHT, Buffers[line]); + } +} + +void +cprintf(char* format, ...) +{ + char s[256]; + va_list vl1, vl2; + + va_start(vl1, format); + va_copy(vl2, vl1); + vsprintf(s, format, vl1); + TheConsole.AddLine(s, 255, 255, 128); +} diff --git a/src/renderer/Console.h b/src/renderer/Console.h new file mode 100644 index 0000000..9f22236 --- /dev/null +++ b/src/renderer/Console.h @@ -0,0 +1,27 @@ +#pragma once + +class CConsole +{ + enum + { + MAX_LINES = 8, // BUG? only shows 7 + MAX_STR_LEN = 40, + }; + + uint8 m_nLineCount; + uint8 m_nCurrentLine; + wchar Buffers[MAX_LINES][MAX_STR_LEN]; + uint32 m_aTimer[MAX_LINES]; + uint8 m_aRed[MAX_LINES]; + uint8 m_aGreen[MAX_LINES]; + uint8 m_aBlue[MAX_LINES]; +public: + void AddLine(char *s, uint8 r, uint8 g, uint8 b); + void AddOneLine(char *s, uint8 r, uint8 g, uint8 b); + void Display(); + void Init() { m_nCurrentLine = 0; m_nLineCount = 0; } +}; + +extern CConsole TheConsole; + +void cprintf(char*, ...); \ No newline at end of file diff --git a/src/renderer/Coronas.cpp b/src/renderer/Coronas.cpp new file mode 100644 index 0000000..e9f9e66 --- /dev/null +++ b/src/renderer/Coronas.cpp @@ -0,0 +1,779 @@ +#include "common.h" + +#include "main.h" +#include "General.h" +#include "Entity.h" +#include "TxdStore.h" +#include "Camera.h" +#include "Sprite.h" +#include "Timer.h" +#include "World.h" +#include "Weather.h" +#include "Collision.h" +#include "Timecycle.h" +#include "Coronas.h" +#include "PointLights.h" +#include "Shadows.h" +#include "Clock.h" +#include "Bridge.h" + +struct FlareDef +{ + float position; + float size; + int16 red; + int16 green; + int16 blue; + int16 alpha; + int16 texture; +}; + +FlareDef SunFlareDef[] = { + { -0.5f, 15.0f, 50, 50, 0, 200, 1 }, + { -1.0f, 10.0f, 50, 20, 0, 200, 2 }, + { -1.5f, 15.0f, 50, 0, 0, 200, 3 }, + { -2.5f, 25.0f, 50, 0, 0, 200, 1 }, + { 0.5f, 12.5f, 40, 40, 25, 200, 1 }, + { 0.05f, 20.0f, 30, 22, 9, 200, 2 }, + { 1.3f, 7.5f, 50, 30, 9, 200, 3 }, + { 0.0f, 0.0f, 255, 255, 255, 255, 0 } +}; + +FlareDef HeadLightsFlareDef[] = { + { -0.5f, 15.5, 70, 70, 70, 200, 1 }, + { -1.0f, 10.0, 70, 70, 70, 200, 2 }, + { -1.5f, 5.5f, 50, 50, 50, 200, 3 }, + { 0.5f, 12.0f, 50, 50, 50, 200, 1 }, + { 0.05f, 20.0f, 40, 40, 40, 200, 2 }, + { 1.3f, 8.0f, 60, 60, 60, 200, 3 }, + { -2.0f, 12.0f, 50, 50, 50, 200, 1 }, + { -2.3f, 15.0f, 40, 40, 40, 200, 2 }, + { -3.0f, 16.0f, 40, 40, 40, 200, 3 }, + { 0.0f, 0.0f, 255, 255, 255, 255, 0 } +}; + + +RwTexture *gpCoronaTexture[9] = { nil, nil, nil, nil, nil, nil, nil, nil, nil }; + +float CCoronas::LightsMult = 1.0f; +float CCoronas::SunScreenX; +float CCoronas::SunScreenY; +bool CCoronas::bSmallMoon; +bool CCoronas::SunBlockedByClouds; +int CCoronas::bChangeBrightnessImmediately; + +CRegisteredCorona CCoronas::aCoronas[NUMCORONAS]; + +const char aCoronaSpriteNames[][32] = { + "coronastar", + "corona", + "coronamoon", + "coronareflect", + "coronaheadlightline", + "coronahex", + "coronacircle", + "coronaringa", + "streek" +}; + +void +CCoronas::Init(void) +{ + int i; + + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle")); + + for(i = 0; i < 9; i++) + if(gpCoronaTexture[i] == nil) + gpCoronaTexture[i] = RwTextureRead(aCoronaSpriteNames[i], nil); + + CTxdStore::PopCurrentTxd(); + + for(i = 0; i < NUMCORONAS; i++) + aCoronas[i].id = 0; +} + +void +CCoronas::Shutdown(void) +{ + int i; + for(i = 0; i < 9; i++) + if(gpCoronaTexture[i]){ + RwTextureDestroy(gpCoronaTexture[i]); + gpCoronaTexture[i] = nil; + } +} + +void +CCoronas::Update(void) +{ + int i; + static int LastCamLook = 0; + + LightsMult = Min(LightsMult + 0.03f * CTimer::GetTimeStep(), 1.0f); + + int CamLook = 0; + if(TheCamera.Cams[TheCamera.ActiveCam].LookingLeft) CamLook |= 1; + if(TheCamera.Cams[TheCamera.ActiveCam].LookingRight) CamLook |= 2; + if(TheCamera.Cams[TheCamera.ActiveCam].LookingBehind) CamLook |= 4; + // BUG? + if(TheCamera.GetLookDirection() == LOOKING_BEHIND) CamLook |= 8; + + if(LastCamLook != CamLook) + bChangeBrightnessImmediately = 3; + else + bChangeBrightnessImmediately = Max(bChangeBrightnessImmediately-1, 0); + LastCamLook = CamLook; + + for(i = 0; i < NUMCORONAS; i++) + if(aCoronas[i].id != 0) + aCoronas[i].Update(); +} + +void +CCoronas::RegisterCorona(uint32 id, uint8 red, uint8 green, uint8 blue, uint8 alpha, + const CVector &coors, float size, float drawDist, RwTexture *tex, + int8 flareType, uint8 reflection, uint8 LOScheck, uint8 drawStreak, float someAngle) +{ + int i; + + if(sq(drawDist) < (TheCamera.GetPosition() - coors).MagnitudeSqr2D()) + return; + + for(i = 0; i < NUMCORONAS; i++) + if(aCoronas[i].id == id) + break; + + if(i == NUMCORONAS){ + // add a new one + + // find empty slot + for(i = 0; i < NUMCORONAS; i++) + if(aCoronas[i].id == 0) + break; + if(i == NUMCORONAS) + return; // no space + + aCoronas[i].fadeAlpha = 0; + aCoronas[i].offScreen = true; + aCoronas[i].firstUpdate = true; + aCoronas[i].renderReflection = false; + aCoronas[i].lastLOScheck = 0; + aCoronas[i].sightClear = false; + aCoronas[i].hasValue[0] = false; + aCoronas[i].hasValue[1] = false; + aCoronas[i].hasValue[2] = false; + aCoronas[i].hasValue[3] = false; + aCoronas[i].hasValue[4] = false; + aCoronas[i].hasValue[5] = false; + + }else{ + // use existing one + + if(aCoronas[i].fadeAlpha == 0 && alpha == 0){ + // unregister + aCoronas[i].id = 0; + return; + } + } + + aCoronas[i].id = id; + aCoronas[i].red = red; + aCoronas[i].green = green; + aCoronas[i].blue = blue; + aCoronas[i].alpha = alpha; + aCoronas[i].coors = coors; + aCoronas[i].size = size; + aCoronas[i].someAngle = someAngle; + aCoronas[i].registeredThisFrame = true; + aCoronas[i].drawDist = drawDist; + aCoronas[i].texture = tex; + aCoronas[i].flareType = flareType; + aCoronas[i].reflection = reflection; + aCoronas[i].LOScheck = LOScheck; + aCoronas[i].drawStreak = drawStreak; +} + +void +CCoronas::RegisterCorona(uint32 id, uint8 red, uint8 green, uint8 blue, uint8 alpha, + const CVector &coors, float size, float drawDist, uint8 type, + int8 flareType, uint8 reflection, uint8 LOScheck, uint8 drawStreak, float someAngle) +{ + RegisterCorona(id, red, green, blue, alpha, coors, size, drawDist, + gpCoronaTexture[type], flareType, reflection, LOScheck, drawStreak, someAngle); +} + +void +CCoronas::UpdateCoronaCoors(uint32 id, const CVector &coors, float drawDist, float someAngle) +{ + int i; + + if(sq(drawDist) < (TheCamera.GetPosition() - coors).MagnitudeSqr2D()) + return; + + for(i = 0; i < NUMCORONAS; i++) + if(aCoronas[i].id == id) + break; + + if(i == NUMCORONAS) + return; + + if(aCoronas[i].fadeAlpha == 0) + aCoronas[i].id = 0; // faded out, remove + else{ + aCoronas[i].coors = coors; + aCoronas[i].someAngle = someAngle; + } +} + +static RwIm2DVertex vertexbufferX[2]; + +void +CCoronas::Render(void) +{ + int i, j; + int screenw, screenh; + + PUSH_RENDERGROUP("CCoronas::Render"); + + screenw = RwRasterGetWidth(RwCameraGetRaster(Scene.camera)); + screenh = RwRasterGetHeight(RwCameraGetRaster(Scene.camera)); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + + for(i = 0; i < NUMCORONAS; i++){ + for(j = 5; j > 0; j--){ + aCoronas[i].prevX[j] = aCoronas[i].prevX[j-1]; + aCoronas[i].prevY[j] = aCoronas[i].prevY[j-1]; + aCoronas[i].prevRed[j] = aCoronas[i].prevRed[j-1]; + aCoronas[i].prevGreen[j] = aCoronas[i].prevGreen[j-1]; + aCoronas[i].prevBlue[j] = aCoronas[i].prevBlue[j-1]; + aCoronas[i].hasValue[j] = aCoronas[i].hasValue[j-1]; + } + aCoronas[i].hasValue[0] = false; + + if(aCoronas[i].id == 0 || + aCoronas[i].fadeAlpha == 0 && aCoronas[i].alpha == 0) + continue; + + CVector spriteCoors; + float spritew, spriteh; + if(!CSprite::CalcScreenCoors(aCoronas[i].coors, &spriteCoors, &spritew, &spriteh, true)){ + aCoronas[i].offScreen = true; + aCoronas[i].sightClear = false; + }else{ + aCoronas[i].offScreen = false; + + if(spriteCoors.x < 0.0f || spriteCoors.y < 0.0f || + spriteCoors.x > screenw || spriteCoors.y > screenh){ + aCoronas[i].offScreen = true; + aCoronas[i].sightClear = false; + }else{ + if(CTimer::GetTimeInMilliseconds() > aCoronas[i].lastLOScheck + 2000){ + aCoronas[i].lastLOScheck = CTimer::GetTimeInMilliseconds(); + aCoronas[i].sightClear = CWorld::GetIsLineOfSightClear( + aCoronas[i].coors, TheCamera.Cams[TheCamera.ActiveCam].Source, + true, true, false, false, false, true, false); + } + + // add new streak point + if(aCoronas[i].sightClear){ + aCoronas[i].prevX[0] = spriteCoors.x; + aCoronas[i].prevY[0] = spriteCoors.y; + aCoronas[i].prevRed[0] = aCoronas[i].red; + aCoronas[i].prevGreen[0] = aCoronas[i].green; + aCoronas[i].prevBlue[0] = aCoronas[i].blue; + aCoronas[i].hasValue[0] = true; + } + + // if distance too big, break streak + if(aCoronas[i].hasValue[1]){ + if(Abs(aCoronas[i].prevX[0] - aCoronas[i].prevX[1]) > 50.0f || + Abs(aCoronas[i].prevY[0] - aCoronas[i].prevY[1]) > 50.0f) + aCoronas[i].hasValue[0] = false; + } + } + + + if(aCoronas[i].fadeAlpha && spriteCoors.z < aCoronas[i].drawDist){ + float recipz = 1.0f/spriteCoors.z; + float fadeDistance = aCoronas[i].drawDist / 2.0f; + float distanceFade = spriteCoors.z < fadeDistance ? 1.0f : 1.0f - (spriteCoors.z - fadeDistance)/fadeDistance; + int totalFade = aCoronas[i].fadeAlpha * distanceFade; + + if(aCoronas[i].LOScheck) + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + else + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + + // render corona itself + if(aCoronas[i].texture){ + float fogscale = CWeather::Foggyness*Min(spriteCoors.z, 40.0f)/40.0f + 1.0f; + if(CCoronas::aCoronas[i].id == SUN_CORE) + spriteCoors.z = 0.95f * RwCameraGetFarClipPlane(Scene.camera); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(aCoronas[i].texture)); + spriteCoors.z -= 1.5f; + + if(aCoronas[i].texture == gpCoronaTexture[8]){ + // what's this? + float f = 1.0f - aCoronas[i].someAngle*2.0f/PI; + float wscale = 6.0f*sq(sq(sq(f))) + 0.5f; + float hscale = 0.35f - (wscale - 0.5f) * 0.06f; + hscale = Max(hscale, 0.15f); + + CSprite::RenderOneXLUSprite(spriteCoors.x, spriteCoors.y, spriteCoors.z, + spritew * aCoronas[i].size * wscale, + spriteh * aCoronas[i].size * fogscale * hscale, + CCoronas::aCoronas[i].red / fogscale, + CCoronas::aCoronas[i].green / fogscale, + CCoronas::aCoronas[i].blue / fogscale, + totalFade, + recipz, + 255); + }else{ + CSprite::RenderOneXLUSprite_Rotate_Aspect( + spriteCoors.x, spriteCoors.y, spriteCoors.z, + spritew * aCoronas[i].size * fogscale, + spriteh * aCoronas[i].size * fogscale, + CCoronas::aCoronas[i].red / fogscale, + CCoronas::aCoronas[i].green / fogscale, + CCoronas::aCoronas[i].blue / fogscale, + totalFade, + recipz, + 20.0f * recipz, + 255); + } + } + + // render flares + if(aCoronas[i].flareType != FLARE_NONE){ + FlareDef *flare; + + switch(aCoronas[i].flareType){ + case FLARE_SUN: flare = SunFlareDef; break; + case FLARE_HEADLIGHTS: flare = HeadLightsFlareDef; break; + default: assert(0); + } + + for(; flare->texture; flare++){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[flare->texture + 4])); + CSprite::RenderOneXLUSprite( + (spriteCoors.x - (screenw/2)) * flare->position + (screenw/2), + (spriteCoors.y - (screenh/2)) * flare->position + (screenh/2), + spriteCoors.z, + 4.0f*flare->size * spritew/spriteh, + 4.0f*flare->size, + (flare->red * aCoronas[i].red)>>8, + (flare->green * aCoronas[i].green)>>8, + (flare->blue * aCoronas[i].blue)>>8, + (totalFade * flare->alpha)>>8, + recipz, 255); + } + } + } + } + } + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + // streaks + for(i = 0; i < NUMCORONAS; i++){ + if(aCoronas[i].id == 0 || !aCoronas[i].drawStreak) + continue; + + for(j = 0; j < 5; j++){ + if(!aCoronas[i].hasValue[j] || !aCoronas[i].hasValue[j+1]) + continue; + + int alpha1 = (float)(6 - j) / 6 * 128; + int alpha2 = (float)(6 - (j+1)) / 6 * 128; + + RwIm2DVertexSetScreenX(&vertexbufferX[0], aCoronas[i].prevX[j]); + RwIm2DVertexSetScreenY(&vertexbufferX[0], aCoronas[i].prevY[j]); + RwIm2DVertexSetIntRGBA(&vertexbufferX[0], aCoronas[i].prevRed[j] * alpha1 / 256, aCoronas[i].prevGreen[j] * alpha1 / 256, aCoronas[i].prevBlue[j] * alpha1 / 256, 255); + RwIm2DVertexSetScreenX(&vertexbufferX[1], aCoronas[i].prevX[j+1]); + RwIm2DVertexSetScreenY(&vertexbufferX[1], aCoronas[i].prevY[j+1]); + RwIm2DVertexSetIntRGBA(&vertexbufferX[1], aCoronas[i].prevRed[j+1] * alpha2 / 256, aCoronas[i].prevGreen[j+1] * alpha2 / 256, aCoronas[i].prevBlue[j+1] * alpha2 / 256, 255); + +#ifdef FIX_BUGS + RwIm2DVertexSetScreenZ(&vertexbufferX[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&vertexbufferX[0], RwCameraGetNearClipPlane(Scene.camera)); + RwIm2DVertexSetRecipCameraZ(&vertexbufferX[0], 1.0f/RwCameraGetNearClipPlane(Scene.camera)); + RwIm2DVertexSetScreenZ(&vertexbufferX[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&vertexbufferX[1], RwCameraGetNearClipPlane(Scene.camera)); + RwIm2DVertexSetRecipCameraZ(&vertexbufferX[1], 1.0f/RwCameraGetNearClipPlane(Scene.camera)); +#endif + + RwIm2DRenderLine(vertexbufferX, 2, 0, 1); + } + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + + POP_RENDERGROUP(); +} + +void +CCoronas::RenderReflections(void) +{ + int i; + CColPoint point; + CEntity *entity; + + if(CWeather::WetRoads > 0.0f){ + PUSH_RENDERGROUP("CCoronas::RenderReflections"); + +#ifdef FIX_BUGS + CSprite::InitSpriteBuffer(); +#endif + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[3])); + + for(i = 0; i < NUMCORONAS; i++){ + if(aCoronas[i].id == 0 || + aCoronas[i].fadeAlpha == 0 && aCoronas[i].alpha == 0 || + aCoronas[i].reflection == 0) + continue; + + // check if we want a reflection on this corona + if(aCoronas[i].renderReflection){ + if(((CTimer::GetFrameCounter() + i) & 0xF) == 0 && + CWorld::ProcessVerticalLine(aCoronas[i].coors, -1000.0f, point, entity, true, false, false, false, true, false, nil)) + aCoronas[i].heightAboveRoad = aCoronas[i].coors.z - point.point.z; + }else{ + if(CWorld::ProcessVerticalLine(aCoronas[i].coors, -1000.0f, point, entity, true, false, false, false, true, false, nil)){ + aCoronas[i].heightAboveRoad = aCoronas[i].coors.z - point.point.z; + aCoronas[i].renderReflection = true; + } + } + + // Don't draw if reflection is too high + if(aCoronas[i].renderReflection && aCoronas[i].heightAboveRoad < 20.0f){ + // don't draw if camera is below road + if(CCoronas::aCoronas[i].coors.z - aCoronas[i].heightAboveRoad > TheCamera.GetPosition().z) + continue; + + CVector coors = aCoronas[i].coors; + coors.z -= 2.0f*aCoronas[i].heightAboveRoad; + + CVector spriteCoors; + float spritew, spriteh; + if(CSprite::CalcScreenCoors(coors, &spriteCoors, &spritew, &spriteh, true)){ + float drawDist = 0.75f * aCoronas[i].drawDist; + drawDist = Min(drawDist, 55.0f); + if(spriteCoors.z < drawDist){ + float fadeDistance = drawDist / 2.0f; + float distanceFade = spriteCoors.z < fadeDistance ? 1.0f : 1.0f - (spriteCoors.z - fadeDistance)/fadeDistance; + distanceFade = Clamp(distanceFade, 0.0f, 1.0f); + float recipz = 1.0f/RwCameraGetNearClipPlane(Scene.camera); + float heightFade = (20.0f - aCoronas[i].heightAboveRoad)/20.0f; + int intensity = distanceFade*heightFade * 230.0 * CWeather::WetRoads; + + CSprite::RenderBufferedOneXLUSprite( +#ifdef FIX_BUGS + spriteCoors.x, spriteCoors.y, spriteCoors.z, +#else + spriteCoors.x, spriteCoors.y, RwIm2DGetNearScreenZ(), +#endif + spritew * aCoronas[i].size * 0.75f, + spriteh * aCoronas[i].size * 2.0f, + (intensity * CCoronas::aCoronas[i].red)>>8, + (intensity * CCoronas::aCoronas[i].green)>>8, + (intensity * CCoronas::aCoronas[i].blue)>>8, + 255, + recipz, + 255); + } + } + } + } + CSprite::FlushSpriteBuffer(); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + + POP_RENDERGROUP(); + }else{ + for(i = 0; i < NUMCORONAS; i++) + aCoronas[i].renderReflection = false; + } +} + +void +CCoronas::DoSunAndMoon(void) +{ + // yeah, moon is done somewhere else.... + + CVector sunCoors = CTimeCycle::GetSunDirection(); + sunCoors *= 150.0f; + sunCoors += TheCamera.GetPosition(); + + if(CTimeCycle::GetSunDirection().z > -0.2f){ + float size = ((CGeneral::GetRandomNumber()&0xFF) * 0.005f + 10.0f) * CTimeCycle::GetSunSize(); + RegisterCorona(SUN_CORE, + CTimeCycle::GetSunCoreRed(), CTimeCycle::GetSunCoreGreen(), CTimeCycle::GetSunCoreBlue(), + 255, sunCoors, size, + 999999.88f, TYPE_STAR, FLARE_NONE, REFLECTION_OFF, LOSCHECK_OFF, STREAK_OFF, 0.0f); + + if(CTimeCycle::GetSunDirection().z > 0.0f) + RegisterCorona(SUN_CORONA, + CTimeCycle::GetSunCoronaRed(), CTimeCycle::GetSunCoronaGreen(), CTimeCycle::GetSunCoronaBlue(), + 255, sunCoors, 25.0f * CTimeCycle::GetSunSize(), + 999999.88f, TYPE_STAR, FLARE_SUN, REFLECTION_OFF, LOSCHECK_ON, STREAK_OFF, 0.0f); + } + + CVector spriteCoors; + float spritew, spriteh; + if(CSprite::CalcScreenCoors(sunCoors, &spriteCoors, &spritew, &spriteh, true)){ + SunScreenX = spriteCoors.x; + SunScreenY = spriteCoors.y; + }else{ + SunScreenX = 1000000.0f; + SunScreenY = 1000000.0f; + } +} + +void +CRegisteredCorona::Update(void) +{ + if(!registeredThisFrame) + alpha = 0; + + if(LOScheck && + (CCoronas::SunBlockedByClouds && id == CCoronas::SUN_CORONA || + !CWorld::GetIsLineOfSightClear(coors, TheCamera.GetPosition(), true, false, false, false, false, false))){ + // Corona is blocked, fade out + fadeAlpha = Max(fadeAlpha - 15.0f*CTimer::GetTimeStep(), 0.0f); + }else if(offScreen){ + // Same when off screen + fadeAlpha = Max(fadeAlpha - 15.0f*CTimer::GetTimeStep(), 0.0f); + }else{ + // Visible + if(alpha > fadeAlpha){ + // fade in + fadeAlpha = Min(fadeAlpha + 15.0f*CTimer::GetTimeStep(), alpha); + if(CCoronas::bChangeBrightnessImmediately) + fadeAlpha = alpha; + }else if(alpha < fadeAlpha){ + // too visible, decrease alpha but not below alpha + fadeAlpha = Max(fadeAlpha - 15.0f*CTimer::GetTimeStep(), alpha); + } + + // darken scene when the sun is visible + if(id == CCoronas::SUN_CORONA) + CCoronas::LightsMult = Max(CCoronas::LightsMult - CTimer::GetTimeStep()*0.06f, 0.6f); + } + + // remove if invisible + if(fadeAlpha == 0 && !firstUpdate) + id = 0; + firstUpdate = false; + registeredThisFrame = false; +} + +void +CEntity::ProcessLightsForEntity(void) +{ + int i, n; + C2dEffect *effect; + CVector pos; + bool lightOn, lightFlickering; + uint32 flashTimer1, flashTimer2, flashTimer3; + + if(bRenderDamaged || !bIsVisible || GetUp().z < 0.96f) + return; + + flashTimer1 = 0; + flashTimer2 = 0; + flashTimer3 = 0; + + n = CModelInfo::GetModelInfo(GetModelIndex())->GetNum2dEffects(); + for(i = 0; i < n; i++, flashTimer1 += 0x80, flashTimer2 += 0x100, flashTimer3 += 0x200){ + effect = CModelInfo::GetModelInfo(GetModelIndex())->Get2dEffect(i); + + if(effect->type != EFFECT_LIGHT) + continue; + + pos = GetMatrix() * effect->pos; + + lightOn = false; + lightFlickering = false; + switch(effect->light.lightType){ + case LIGHT_ON: + lightOn = true; + break; + case LIGHT_ON_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7) + lightOn = true; + break; + case LIGHT_FLICKER: + if((CTimer::GetTimeInMilliseconds() ^ m_randomSeed) & 0x60) + lightOn = true; + else + lightFlickering = true; + if((CTimer::GetTimeInMilliseconds()>>11 ^ m_randomSeed) & 3) + lightOn = true; + break; + case LIGHT_FLICKER_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7 || CWeather::WetRoads > 0.5f){ + if((CTimer::GetTimeInMilliseconds() ^ m_randomSeed) & 0x60) + lightOn = true; + else + lightFlickering = true; + if((CTimer::GetTimeInMilliseconds()>>11 ^ m_randomSeed) & 3) + lightOn = true; + } + break; + case LIGHT_FLASH1: + if((CTimer::GetTimeInMilliseconds() + flashTimer1) & 0x200) + lightOn = true; + break; + case LIGHT_FLASH1_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7) + if((CTimer::GetTimeInMilliseconds() + flashTimer1) & 0x200) + lightOn = true; + break; + case LIGHT_FLASH2: + if((CTimer::GetTimeInMilliseconds() + flashTimer2) & 0x400) + lightOn = true; + break; + case LIGHT_FLASH2_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7) + if((CTimer::GetTimeInMilliseconds() + flashTimer2) & 0x400) + lightOn = true; + break; + case LIGHT_FLASH3: + if((CTimer::GetTimeInMilliseconds() + flashTimer3) & 0x800) + lightOn = true; + break; + case LIGHT_FLASH3_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7) + if((CTimer::GetTimeInMilliseconds() + flashTimer3) & 0x800) + lightOn = true; + break; + case LIGHT_RANDOM_FLICKER: + if(m_randomSeed > 16) + lightOn = true; + else{ + if((CTimer::GetTimeInMilliseconds() ^ m_randomSeed*8) & 0x60) + lightOn = true; + else + lightFlickering = true; + if((CTimer::GetTimeInMilliseconds()>>11 ^ m_randomSeed*8) & 3) + lightOn = true; + } + break; + case LIGHT_RANDOM_FLICKER_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7){ + if(m_randomSeed > 16) + lightOn = true; + else{ + if((CTimer::GetTimeInMilliseconds() ^ m_randomSeed*8) & 0x60) + lightOn = true; + else + lightFlickering = true; + if((CTimer::GetTimeInMilliseconds()>>11 ^ m_randomSeed*8) & 3) + lightOn = true; + } + } + break; + case LIGHT_BRIDGE_FLASH1: + if(CBridge::ShouldLightsBeFlashing() && CTimer::GetTimeInMilliseconds() & 0x200) + lightOn = true; + break; + case LIGHT_BRIDGE_FLASH2: + if(CBridge::ShouldLightsBeFlashing() && (CTimer::GetTimeInMilliseconds() & 0x1FF) < 60) + lightOn = true; + break; + } + + // Corona + if(lightOn) + CCoronas::RegisterCorona((uintptr)this + i, + effect->col.r, effect->col.g, effect->col.b, 255, + pos, effect->light.size, effect->light.dist, + effect->light.corona, effect->light.flareType, effect->light.roadReflection, + effect->light.flags&LIGHTFLAG_LOSCHECK, CCoronas::STREAK_OFF, 0.0f); + else if(lightFlickering) + CCoronas::RegisterCorona((uintptr)this + i, + 0, 0, 0, 255, + pos, effect->light.size, effect->light.dist, + effect->light.corona, effect->light.flareType, effect->light.roadReflection, + effect->light.flags&LIGHTFLAG_LOSCHECK, CCoronas::STREAK_OFF, 0.0f); + + // Pointlight + if(effect->light.flags & LIGHTFLAG_FOG_ALWAYS){ + CPointLights::AddLight(CPointLights::LIGHT_FOGONLY_ALWAYS, + pos, CVector(0.0f, 0.0f, 0.0f), + effect->light.range, + effect->col.r/255.0f, effect->col.g/255.0f, effect->col.b/255.0f, + CPointLights::FOG_ALWAYS, true); + }else if(effect->light.flags & LIGHTFLAG_FOG_NORMAL && lightOn && effect->light.range == 0.0f){ + CPointLights::AddLight(CPointLights::LIGHT_FOGONLY, + pos, CVector(0.0f, 0.0f, 0.0f), + effect->light.range, + effect->col.r/255.0f, effect->col.g/255.0f, effect->col.b/255.0f, + CPointLights::FOG_NORMAL, true); + }else if(lightOn && effect->light.range != 0.0f){ + if(effect->col.r == 0 && effect->col.g == 0 && effect->col.b == 0){ + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos, CVector(0.0f, 0.0f, 0.0f), + effect->light.range, + 0.0f, 0.0f, 0.0f, + CPointLights::FOG_NONE, true); + }else{ + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos, CVector(0.0f, 0.0f, 0.0f), + effect->light.range, + effect->col.r*CTimeCycle::GetSpriteBrightness()/255.0f, + effect->col.g*CTimeCycle::GetSpriteBrightness()/255.0f, + effect->col.b*CTimeCycle::GetSpriteBrightness()/255.0f, + // half-useless because LIGHTFLAG_FOG_ALWAYS can't be on + (effect->light.flags & LIGHTFLAG_FOG) >> 1, + true); + } + } + + // Light shadow + if(effect->light.shadowSize != 0.0f){ + if(lightOn){ + CShadows::StoreStaticShadow((uintptr)this + i, SHADOWTYPE_ADDITIVE, + effect->light.shadow, &pos, + effect->light.shadowSize, 0.0f, + 0.0f, -effect->light.shadowSize, + 128, + effect->col.r*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, + effect->col.g*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, + effect->col.b*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, + 15.0f, 1.0f, 40.0f, false, 0.0f); + }else if(lightFlickering){ + CShadows::StoreStaticShadow((uintptr)this + i, SHADOWTYPE_ADDITIVE, + effect->light.shadow, &pos, + effect->light.shadowSize, 0.0f, + 0.0f, -effect->light.shadowSize, + 0, 0.0f, 0.0f, 0.0f, + 15.0f, 1.0f, 40.0f, false, 0.0f); + } + } + } +} diff --git a/src/renderer/Coronas.h b/src/renderer/Coronas.h new file mode 100644 index 0000000..46eb431 --- /dev/null +++ b/src/renderer/Coronas.h @@ -0,0 +1,101 @@ +#pragma once + +extern RwTexture *gpCoronaTexture[9]; + +struct CRegisteredCorona +{ + uint32 id; + uint32 lastLOScheck; + RwTexture *texture; + uint8 red; + uint8 green; + uint8 blue; + uint8 alpha; // alpha when fully visible + uint8 fadeAlpha; // actual value used for rendering, faded + CVector coors; + float size; + float someAngle; + bool registeredThisFrame; + float drawDist; + int8 flareType; + int8 reflection; + + uint8 LOScheck : 1; + uint8 offScreen : 1; + uint8 firstUpdate : 1; + uint8 drawStreak : 1; + uint8 sightClear : 1; + + bool renderReflection; + float heightAboveRoad; + + float prevX[6]; + float prevY[6]; + uint8 prevRed[6]; + uint8 prevGreen[6]; + uint8 prevBlue[6]; + bool hasValue[6]; + + void Update(void); +}; + +VALIDATE_SIZE(CRegisteredCorona, 0x80); + +class CCoronas +{ + static CRegisteredCorona aCoronas[NUMCORONAS]; +public: + enum { + SUN_CORE = 1, + SUN_CORONA + }; + enum { + TYPE_STAR, + TYPE_NORMAL, + TYPE_MOON, + TYPE_REFLECT, + TYPE_HEADLIGHT, + TYPE_HEX, + TYPE_CIRCLE, + TYPE_RING, + TYPE_STREAK, + }; + enum { + FLARE_NONE, + FLARE_SUN, + FLARE_HEADLIGHTS + }; + enum { + REFLECTION_OFF, + REFLECTION_ON, + }; + enum { + LOSCHECK_OFF, + LOSCHECK_ON, + }; + enum { + STREAK_OFF, + STREAK_ON, + }; + + static float LightsMult; + static float SunScreenY; + static float SunScreenX; + static bool bSmallMoon; + static bool SunBlockedByClouds; + static int bChangeBrightnessImmediately; + + static void Init(void); + static void Shutdown(void); + static void Update(void); + static void RegisterCorona(uint32 id, uint8 red, uint8 green, uint8 blue, uint8 alpha, + const CVector &coors, float size, float drawDist, RwTexture *tex, + int8 flareType, uint8 reflection, uint8 LOScheck, uint8 drawStreak, float someAngle); + static void RegisterCorona(uint32 id, uint8 red, uint8 green, uint8 blue, uint8 alpha, + const CVector &coors, float size, float drawDist, uint8 type, + int8 flareType, uint8 reflection, uint8 LOScheck, uint8 drawStreak, float someAngle); + static void UpdateCoronaCoors(uint32 id, const CVector &coors, float drawDist, float someAngle); + static void Render(void); + static void RenderReflections(void); + static void DoSunAndMoon(void); +}; diff --git a/src/renderer/Credits.cpp b/src/renderer/Credits.cpp new file mode 100644 index 0000000..6058179 --- /dev/null +++ b/src/renderer/Credits.cpp @@ -0,0 +1,518 @@ +#include "common.h" + +#include "Timer.h" +#include "Font.h" +#include "Frontend.h" +#include "RwHelper.h" +#include "Camera.h" +#include "Text.h" +#include "Credits.h" + +bool CCredits::bCreditsGoing; +uint32 CCredits::CreditsStartTime; + +void +CCredits::Init(void) +{ + Stop(); +} + +void +CCredits::Start(void) +{ + bCreditsGoing = true; + CreditsStartTime = CTimer::GetTimeInMilliseconds(); +} + +void +CCredits::Stop(void) +{ + bCreditsGoing = false; +} + +void +CCredits::PrintCreditSpace(float space, uint32 &line) +{ + line += space * 25.0f; +} + +void +CCredits::PrintCreditText(float scaleX, float scaleY, wchar *text, uint32 &lineoffset, float scrolloffset) +{ +#ifdef FIX_BUGS + float start = DEFAULT_SCREEN_HEIGHT + 50.0f; +#else + float start = SCREEN_HEIGHT + 50.0f; +#endif + float y = lineoffset + start - scrolloffset; + if(y > -50.0f && y < start){ +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(scaleX), SCREEN_SCALE_Y(scaleY)); + CFont::PrintString(SCREEN_WIDTH/2.0f, SCREEN_SCALE_Y(y), (uint16*)text); +#else + CFont::SetScale(scaleX, scaleY); + CFont::PrintString(SCREEN_WIDTH/2.0f, y, (uint16*)text); +#endif + } + lineoffset += scaleY*25.0f; +} + +void +CCredits::Render(void) +{ + uint32 lineoffset; + float scrolloffset; + + if(!bCreditsGoing || FrontEndMenuManager.m_bMenuActive) + return; + + DefinedState(); + lineoffset = 0; + scrolloffset = (CTimer::GetTimeInMilliseconds() - CreditsStartTime) / 24.0f; + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 20); +#endif + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(220, 220, 220, 220)); + CFont::SetFontStyle(FONT_HEADING); + + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED002"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED003"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED004"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED005"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED006"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED007"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED008"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED009"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED010"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED011"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED012"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED013"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED014"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED015"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED016"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED017"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED018"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED019"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED020"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED021"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED022"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED245"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED023"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED024"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED025"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED026"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED027"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED028"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED257"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED029"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED030"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED031"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED032"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED033"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED244"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED034"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED035"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED247"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED036"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED037"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED038"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED039"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED040"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED041"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED042"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED043"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED044"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED045"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED046"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED047"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED048"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED049"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED050"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRD050A"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED051"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED052"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED053"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED054"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED055"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED056"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED248"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED249"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED250"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED251"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED252"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED253"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED057"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED058"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED059"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED254"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED255"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED060"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED061"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED062"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED063"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED064"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED065"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED066"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED067"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED068"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED069"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED070"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED071"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED072"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED073"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED074"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED075"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED076"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED077"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED078"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED079"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED080"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED081"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED082"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED083"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED084"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED242"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED259"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED260"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED261"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED262"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED085"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED086"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED087"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED088"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED089"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED090"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED091"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED094"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED095"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED096"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED097"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED098"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED099"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED263"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED264"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED265"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED267"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED270"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED266"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED100"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED101"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED102"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED103"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED104"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED105"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED106"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED268"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED269"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED107"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED108"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED109"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED110"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED111"), lineoffset, scrolloffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED112"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED113"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED114"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED115"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED116"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED117"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED118"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED119"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED120"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED121"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED122"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED123"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED124"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED125"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED126"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED127"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED128"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED129"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED130"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED131"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED132"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED133"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED134"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED135"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED136"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD136A"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED137"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD137A"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED138"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD138A"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD138B"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED139"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED140"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD140A"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD140B"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD140C"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD140D"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD140E"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED141"), lineoffset, scrolloffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED142"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED143"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED144"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED145"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED146"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED147"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED148"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED149"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED150"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED151"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED152"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED153"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED154"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED155"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED156"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED157"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED158"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED159"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED160"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED161"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED162"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED163"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED164"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED165"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED166"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED167"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED168"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED169"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED170"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED171"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED172"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED173"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED174"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED175"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED176"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED177"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED178"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED179"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED180"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED181"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED182"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED183"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED184"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED185"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED186"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED187"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED188"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED189"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED190"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED191"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED192"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED193"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED194"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED195"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED196"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED197"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED198"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED199"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED200"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED201"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED202"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED203"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED204"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED205"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED206"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED207"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED208"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED209"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED210"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED211"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED212"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED213"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED214"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED215"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED216"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED241"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED217"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED218"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD218A"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD218B"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED219"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED220"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED221"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED222"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED223"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED224"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED225"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED226"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED227"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED228"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED229"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED230"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED231"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED232"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED233"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED234"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED235"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED236"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED237"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED238"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED239"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED240"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("LITTLE"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("NICK"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED243"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED244"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + + + CFont::DrawFonts(); + if(TheCamera.m_WideScreenOn) + TheCamera.DrawBordersForWideScreen(); + +#ifdef FIX_BUGS + if(lineoffset + DEFAULT_SCREEN_HEIGHT - scrolloffset < -10.0f) +#else + if(lineoffset + SCREEN_HEIGHT - scrolloffset < -10.0f) +#endif + { + bCreditsGoing = false; + } +} + +bool CCredits::AreCreditsDone(void) +{ + return !bCreditsGoing; +} diff --git a/src/renderer/Credits.h b/src/renderer/Credits.h new file mode 100644 index 0000000..e049ce7 --- /dev/null +++ b/src/renderer/Credits.h @@ -0,0 +1,15 @@ +#pragma once + +class CCredits +{ + static bool bCreditsGoing; + static uint32 CreditsStartTime; +public: + static void Init(void); + static void Start(void); + static void Stop(void); + static bool AreCreditsDone(void); + static void Render(void); + static void PrintCreditSpace(float space, uint32 &line); + static void PrintCreditText(float scaleX, float scaleY, wchar *text, uint32 &lineoffset, float scrolloffset); +}; diff --git a/src/renderer/Draw.cpp b/src/renderer/Draw.cpp new file mode 100644 index 0000000..f702f18 --- /dev/null +++ b/src/renderer/Draw.cpp @@ -0,0 +1,95 @@ +#include "common.h" + +#include "Draw.h" +#include "Frontend.h" +#include "Camera.h" +#include "CutsceneMgr.h" + +#ifdef ASPECT_RATIO_SCALE +float CDraw::ms_fAspectRatio = DEFAULT_ASPECT_RATIO; +float CDraw::ms_fScaledFOV = 45.0f; +#endif + +float CDraw::ms_fNearClipZ; +float CDraw::ms_fFarClipZ; +float CDraw::ms_fFOV = 45.0f; +float CDraw::ms_fLODDistance; + +uint8 CDraw::FadeValue; +uint8 CDraw::FadeRed; +uint8 CDraw::FadeGreen; +uint8 CDraw::FadeBlue; + +#ifdef PROPER_SCALING +bool CDraw::ms_bProperScaling = true; +#endif +#ifdef FIX_RADAR +bool CDraw::ms_bFixRadar = true; +#endif +#ifdef FIX_SPRITES +bool CDraw::ms_bFixSprites = true; +#endif + +float +CDraw::FindAspectRatio(void) +{ +#ifndef ASPECT_RATIO_SCALE + if(FrontEndMenuManager.m_PrefsUseWideScreen) + return 16.0f/9.0f; + else + return 4.0f/3.0f; +#else + switch (FrontEndMenuManager.m_PrefsUseWideScreen) { + case AR_AUTO: + return SCREEN_WIDTH / SCREEN_HEIGHT; + default: + case AR_4_3: + return 4.0f / 3.0f; + case AR_5_4: + return 5.0f / 4.0f; + case AR_16_10: + return 16.0f / 10.0f; + case AR_16_9: + return 16.0f / 9.0f; + case AR_21_9: + return 21.0f / 9.0f; + }; +#endif +} + +#ifdef ASPECT_RATIO_SCALE +// convert a 4:3 hFOV to vFOV, +// then convert that vFOV to hFOV for our aspect ratio, +// i.e. HOR+ +float +CDraw::ConvertFOV(float hfov) +{ + // => tan(hFOV/2) = tan(vFOV/2)*aspectRatio + // => tan(vFOV/2) = tan(hFOV/2)/aspectRatio + float ar1 = DEFAULT_ASPECT_RATIO; + float ar2 = GetAspectRatio(); + hfov = DEGTORAD(hfov); + float vfov = Atan(tan(hfov/2) / ar1) *2; + hfov = Atan(tan(vfov/2) * ar2) *2; + return RADTODEG(hfov); +} +#endif + +void +CDraw::SetFOV(float fov) +{ +#ifdef ASPECT_RATIO_SCALE + if (!CCutsceneMgr::IsRunning()) + ms_fScaledFOV = ConvertFOV(fov); + else + ms_fScaledFOV = fov; +#endif + ms_fFOV = fov; +} + +#ifdef PROPER_SCALING +float CDraw::ScaleY(float y) +{ + return ms_bProperScaling ? y : y * ((float)DEFAULT_SCREEN_HEIGHT/SCREEN_HEIGHT_NTSC); +} +#endif \ No newline at end of file diff --git a/src/renderer/Draw.h b/src/renderer/Draw.h new file mode 100644 index 0000000..8727e0e --- /dev/null +++ b/src/renderer/Draw.h @@ -0,0 +1,73 @@ +#pragma once + +enum eAspectRatio +{ + // Make sure these work the same as FrontEndMenuManager.m_PrefsUseWideScreen + // without widescreen support + AR_AUTO, + AR_4_3, + AR_5_4, + AR_16_10, + AR_16_9, + AR_21_9, + + AR_MAX, +}; + +class CDraw +{ +private: + static float ms_fNearClipZ; + static float ms_fFarClipZ; + static float ms_fFOV; +#ifdef ASPECT_RATIO_SCALE + // we use this variable to scale a lot of 2D elements + // so better cache it + static float ms_fAspectRatio; + // similar thing for 3D rendering + static float ms_fScaledFOV; +#endif +public: + static float ms_fLODDistance; // set but unused? + + static uint8 FadeValue; + static uint8 FadeRed; + static uint8 FadeGreen; + static uint8 FadeBlue; + +#ifdef PROPER_SCALING + static bool ms_bProperScaling; +#endif +#ifdef FIX_RADAR + static bool ms_bFixRadar; +#endif +#ifdef FIX_SPRITES + static bool ms_bFixSprites; +#endif + + static void SetNearClipZ(float nearclip) { ms_fNearClipZ = nearclip; } + static float GetNearClipZ(void) { return ms_fNearClipZ; } + static void SetFarClipZ(float farclip) { ms_fFarClipZ = farclip; } + static float GetFarClipZ(void) { return ms_fFarClipZ; } + + static void SetFOV(float fov); + static float GetFOV(void) { return ms_fFOV; } +#ifdef ASPECT_RATIO_SCALE + static float GetScaledFOV(void) { return ms_fScaledFOV; } +#else + static float GetScaledFOV(void) { return ms_fFOV; } +#endif + + static float FindAspectRatio(void); +#ifdef ASPECT_RATIO_SCALE + static float ConvertFOV(float fov); + static float GetAspectRatio(void) { return ms_fAspectRatio; } + static void SetAspectRatio(float ratio) { ms_fAspectRatio = ratio; } +#else + static float GetAspectRatio(void) { return FindAspectRatio(); } +#endif + +#ifdef PROPER_SCALING + static float ScaleY(float y); +#endif +}; diff --git a/src/renderer/Fluff.cpp b/src/renderer/Fluff.cpp new file mode 100644 index 0000000..c4cfe7f --- /dev/null +++ b/src/renderer/Fluff.cpp @@ -0,0 +1,870 @@ +#include "common.h" +#include "main.h" + +#include "Entity.h" +#include "Fluff.h" +#include "Camera.h" +#include "Sprite.h" +#include "Coronas.h" +#include "General.h" +#include "Timer.h" +#include "Clock.h" +#include "Weather.h" +#include "Stats.h" +#include "maths.h" +#include "Frontend.h" + +uint8 ScrollCharSet[59][5] = { + { 0x00, 0x00, 0x00, 0x00, 0x00 }, // ' ' + { 0x00, 0x00, 0x1D, 0x00, 0x00 }, // '!' + { 0x00, 0x00, 0x00, 0x00, 0x00 }, // '"' + { 0x0A, 0x1F, 0x0A, 0x1F, 0x0A }, // '#' + { 0x00, 0x09, 0x1F, 0x12, 0x00 }, // '$' + { 0x18, 0x18, 0x00, 0x03, 0x03 }, // '%' + { 0x00, 0x00, 0x00, 0x00, 0x00 }, // '&' + { 0x00, 0x00, 0x00, 0x00, 0x00 }, // ''' + { 0x01, 0x02, 0x04, 0x08, 0x10 }, // '(' + { 0x00, 0x00, 0x18, 0x00, 0x00 }, // ')' + { 0x15, 0x04, 0x1F, 0x04, 0x15 }, // '*' + { 0x00, 0x04, 0x0E, 0x04, 0x00 }, // '+' + { 0x00, 0x00, 0x03, 0x00, 0x00 }, // ',' + { 0x00, 0x04, 0x04, 0x04, 0x00 }, // '-' + { 0x00, 0x00, 0x01, 0x00, 0x00 }, // '.' + { 0x00, 0x00, 0x00, 0x00, 0x00 }, // '/' + { 0x0E, 0x11, 0x11, 0x11, 0x0E }, // '0' + { 0x01, 0x09, 0x1F, 0x01, 0x01 }, // '1' + { 0x03, 0x15, 0x15, 0x15, 0x09 }, // '2' + { 0x11, 0x11, 0x15, 0x15, 0x0A }, // '3' + { 0x02, 0x06, 0x0A, 0x1F, 0x02 }, // '4' + { 0x1D, 0x15, 0x15, 0x15, 0x12 }, // '5' + { 0x0E, 0x15, 0x15, 0x15, 0x12 }, // '6' + { 0x18, 0x10, 0x13, 0x14, 0x18 }, // '7' + { 0x0A, 0x15, 0x15, 0x15, 0x0A }, // '8' + { 0x08, 0x15, 0x15, 0x15, 0x0E }, // '9' + { 0x00, 0x00, 0x0A, 0x00, 0x00 }, // ':' + { 0x18, 0x18, 0x00, 0x03, 0x03 }, // ';' + { 0x04, 0x08, 0x1F, 0x08, 0x04 }, // '<' + { 0x00, 0x0A, 0x0A, 0x0A, 0x00 }, // '=' + { 0x04, 0x02, 0x1F, 0x02, 0x04 }, // '>' + { 0x10, 0x10, 0x15, 0x14, 0x1D }, // '?' + { 0x00, 0x1C, 0x14, 0x1C, 0x00 }, // '@' + { 0x0F, 0x12, 0x12, 0x12, 0x0F }, // 'A' + { 0x1F, 0x15, 0x15, 0x15, 0x0A }, // 'B' + { 0x0E, 0x11, 0x11, 0x11, 0x0A }, // 'C' + { 0x1F, 0x11, 0x11, 0x11, 0x0E }, // 'D' + { 0x1F, 0x15, 0x15, 0x11, 0x11 }, // 'E' + { 0x1F, 0x14, 0x14, 0x10, 0x10 }, // 'F' + { 0x0E, 0x11, 0x15, 0x15, 0x06 }, // 'G' + { 0x1F, 0x04, 0x04, 0x04, 0x1F }, // 'H' + { 0x11, 0x11, 0x1F, 0x11, 0x11 }, // 'I' + { 0x02, 0x01, 0x01, 0x01, 0x1E }, // 'J' + { 0x1F, 0x04, 0x0C, 0x12, 0x01 }, // 'K' + { 0x1F, 0x01, 0x01, 0x01, 0x01 }, // 'L' + { 0x1F, 0x08, 0x06, 0x08, 0x1F }, // 'M' + { 0x1F, 0x08, 0x04, 0x02, 0x1F }, // 'N' + { 0x0E, 0x11, 0x11, 0x11, 0x0E }, // 'O' + { 0x1F, 0x12, 0x12, 0x12, 0x0C }, // 'P' + { 0x0C, 0x12, 0x12, 0x13, 0x0D }, // 'Q' + { 0x1F, 0x14, 0x14, 0x16, 0x09 }, // 'R' + { 0x09, 0x15, 0x15, 0x15, 0x02 }, // 'S' + { 0x10, 0x10, 0x1F, 0x10, 0x10 }, // 'T' + { 0x1E, 0x01, 0x01, 0x01, 0x1E }, // 'U' + { 0x1C, 0x02, 0x01, 0x02, 0x1C }, // 'V' + { 0x1E, 0x01, 0x06, 0x01, 0x1E }, // 'W' + { 0x11, 0x0A, 0x04, 0x0A, 0x11 }, // 'X' + { 0x18, 0x04, 0x03, 0x04, 0x18 }, // 'Y' + { 0x11, 0x13, 0x15, 0x19, 0x11 } // 'Z' +}; + +// ---------- CMovingThings ---------- +enum eScrollBarTypes +{ + SCROLL_BUSINESS, + SCROLL_TRAFFIC, + SCROLL_ENTERTAINMENT, + SCROLL_AIRPORT_DOORS, + SCROLL_AIRPORT_FRONT, + SCROLL_STORE, + SCROLL_USED_CARS +}; + +CScrollBar aScrollBars[11]; +CTowerClock aTowerClocks[2]; +CDigitalClock aDigitalClocks[3]; + +CMovingThing CMovingThings::StartCloseList; +CMovingThing CMovingThings::EndCloseList; +int16 CMovingThings::Num; +CMovingThing CMovingThings::aMovingThings[NUMMOVINGTHINGS]; + +void CMovingThings::Init() +{ + StartCloseList.m_pNext = &CMovingThings::EndCloseList; + StartCloseList.m_pPrev = nil; + EndCloseList.m_pNext = nil; + EndCloseList.m_pPrev = &CMovingThings::StartCloseList; + Num = 0; + + // Initialize scroll bars + aScrollBars[0].Init(CVector( 228.3f, -669.0f, 39.0f ), SCROLL_BUSINESS, 0.0f, 0.5f, 0.5f, 255, 128, 0, 0.3f); + aScrollBars[1].Init(CVector( 772.0f, 164.0f, -9.5f ), SCROLL_TRAFFIC, 0.0f, 0.5f, 0.25f, 128, 255, 0, 0.3f); + aScrollBars[2].Init(CVector(-1089.61f, -584.224f, 13.246f), SCROLL_AIRPORT_DOORS, 0.0f, -0.1706f, 0.107f, 255, 0, 0, 0.11f); + aScrollBars[3].Init(CVector(-1089.61f, -602.04602f, 13.246f), SCROLL_AIRPORT_DOORS, 0.0f, -0.1706f, 0.107f, 0, 255, 0, 0.11f); + aScrollBars[4].Init(CVector(-1089.61f, -619.81702f, 13.246f), SCROLL_AIRPORT_DOORS, 0.0f, -0.1706f, 0.107f, 255, 128, 0, 0.11f); + aScrollBars[5].Init(CVector(-754.578f, -633.50897f, 18.411f), SCROLL_AIRPORT_FRONT, 0.0f, 0.591f, 0.52f, 100, 100, 255, 0.3f); + aScrollBars[6].Init(CVector( -754.578f, -586.672f, 18.411f), SCROLL_AIRPORT_FRONT, 0.0f, 0.591f, 0.52f, 100, 100, 255, 0.3f); + aScrollBars[7].Init(CVector( 85.473f, -1069.512f, 30.5f ), SCROLL_STORE, 0.625f, -0.3125f, 0.727f, 100, 100, 255, 0.5f); + aScrollBars[8].Init(CVector( 74.823f, -1086.879f, 31.495f), SCROLL_ENTERTAINMENT, -0.2083f, 0.1041f, 0.5f, 255, 255, 128, 0.3f); + aScrollBars[9].Init(CVector( -36.459f, -1031.2371f, 32.534f), SCROLL_ENTERTAINMENT, -0.1442f, 0.0721f, 0.229f, 150, 255, 50, 0.3f); + aScrollBars[10].Init(CVector( 1208.0f, -62.208f, 19.157f), SCROLL_USED_CARS, 0.0642f, -0.20365f, 0.229f, 255, 128, 0, 0.3f); + + // Initialize tower clocks + aTowerClocks[0].Init(CVector(59.4f, -1081.3f, 54.15f), -1.0f, 0.0f, 0, 0, 0, 80.0f, 2.0f); + aTowerClocks[1].Init(CVector(55.4f, -1083.6f, 54.15f), 0.0f, -1.0f, 0, 0, 0, 80.0f, 2.0f); + + // Initialize digital clocks + CVector2D sz(3.7f, 2.144f); + sz.Normalise(); + aDigitalClocks[0].Init( + CVector(54.485f - sz.x * 0.05f + sz.y * 0.3f, -1081.679f - sz.y * 0.05f - sz.x * 0.3f, 32.803f), + sz.y, -sz.x, 255, 0, 0, 100.0f, 0.8f + ); + aDigitalClocks[1].Init( + CVector(60.564f + sz.x * 0.05f - sz.y * 0.3f, -1083.089f + sz.y * 0.05f + sz.x * 0.3f, 32.803f), + -sz.y, sz.x, 0, 0, 255, 100.0f, 0.8f + ); + aDigitalClocks[2].Init( + CVector(58.145f - sz.y * 0.05f - sz.x * 0.3f, -1079.268f + sz.x * 0.05f - sz.y * 0.3f, 32.803f), + -sz.x, -sz.y, 0, 255, 0, 100.0f, 0.8f + ); +} + +void CMovingThings::Shutdown() +{ + int i; + for (i = 0; i < ARRAY_SIZE(aScrollBars); ++i) + aScrollBars[i].SetVisibility(false); + for (i = 0; i < ARRAY_SIZE(aTowerClocks); ++i) + aTowerClocks[i].SetVisibility(false); + for (i = 0; i < ARRAY_SIZE(aDigitalClocks); ++i) + aDigitalClocks[i].SetVisibility(false); +} + +void CMovingThings::Update() +{ + int16 i; +#ifndef SQUEEZE_PERFORMANCE + const int TIME_SPAN = 64; // frames to process all aMovingThings + + int block = CTimer::GetFrameCounter() % TIME_SPAN; + + for (i = (block * NUMMOVINGTHINGS) / TIME_SPAN; i < ((block + 1) * NUMMOVINGTHINGS) / TIME_SPAN; i++) { + if (aMovingThings[i].m_nHidden == 1) + aMovingThings[i].Update(); + } + + for (i = 0; i < CMovingThings::Num; i++) { + if (aMovingThings[i].m_nHidden == 0) + aMovingThings[i].Update(); + } +#endif + + for (i = 0; i < ARRAY_SIZE(aScrollBars); ++i) + { + if (aScrollBars[i].IsVisible() || (CTimer::GetFrameCounter() + i) % 8 == 0) + aScrollBars[i].Update(); + } + for (i = 0; i < ARRAY_SIZE(aTowerClocks); ++i) + { + if (aTowerClocks[i].IsVisible() || (CTimer::GetFrameCounter() + i) % 8 == 0) + aTowerClocks[i].Update(); + } + for (i = 0; i < ARRAY_SIZE(aDigitalClocks); ++i) + { + if (aDigitalClocks[i].IsVisible() || (CTimer::GetFrameCounter() + i) % 8 == 0) + aDigitalClocks[i].Update(); + } +} + +void CMovingThings::Render() +{ + int i; + PUSH_RENDERGROUP("CMovingThings::Render"); + for (i = 0; i < ARRAY_SIZE(aScrollBars); ++i) + { + if (aScrollBars[i].IsVisible()) + aScrollBars[i].Render(); + } + for (i = 0; i < ARRAY_SIZE(aTowerClocks); ++i) + { + if (aTowerClocks[i].IsVisible()) + aTowerClocks[i].Render(); + } + for (i = 0; i < ARRAY_SIZE(aDigitalClocks); ++i) + { + if (aDigitalClocks[i].IsVisible()) + aDigitalClocks[i].Render(); + } + POP_RENDERGROUP(); +} + +// ---------- CMovingThing ---------- +void CMovingThing::Update() +{ + m_pEntity->GetMatrix().UpdateRW(); + m_pEntity->UpdateRwFrame(); + + if (SQR(m_pEntity->GetPosition().x - TheCamera.GetPosition().x) + SQR(m_pEntity->GetPosition().y - TheCamera.GetPosition().y) < 40000.0f) { + if (m_nHidden == 1) { + AddToList(&CMovingThings::StartCloseList); + m_nHidden = 0; + } + } else { + if (m_nHidden == 0) { + RemoveFromList(); + m_nHidden = 1; + } + } +} + +void CMovingThing::AddToList(CMovingThing *pThing) +{ + m_pNext = pThing->m_pNext; + m_pPrev = pThing; + pThing->m_pNext = this; + m_pNext->m_pPrev = this; +} + +void CMovingThing::RemoveFromList() +{ + m_pNext->m_pPrev = m_pPrev; + m_pPrev->m_pNext = m_pNext; +} + +int16 CMovingThing::SizeList() +{ + CMovingThing *next = m_pNext; + int16 count = 0; + + while (next != nil) { + next = next->m_pNext; + count++; + } + + return count; +} + +// ---------- Find message functions ---------- +const char* FindTunnelMessage() +{ + if (CStats::CommercialPassed) + return "LIBERTY TUNNEL HAS BEEN OPENED TO ALL TRAFFIC . . . "; + + if (CStats::IndustrialPassed) + return "FIRST PHASE LIBERTY TUNNEL HAS BEEN COMPLETED . . . "; + + return "FIRST PHASE LIBERTY TUNNEL ABOUT TO BE COMPLETED . . . "; +} + +const char* FindBridgeMessage() +{ + if (CStats::CommercialPassed) + return "STAUNTON LIFT BRIDGE IS OPERATIONAL AGAIN "; + + if (CStats::IndustrialPassed) + return "LONG DELAYS BEHIND US AS CALLAHAN BRIDGE IS FIXED . . . STAUNTON LIFT BRIDGE STUCK OPEN "; + + return "CHAOS AS CALLAHAN BRIDGE IS UNDER REPAIR. . . "; +} + +char String_Time[] = "THE TIME IS 12:34 "; +const char* FindTimeMessage() +{ + String_Time[12] = '0' + CClock::GetHours() / 10; + String_Time[13] = '0' + CClock::GetHours() % 10; + String_Time[15] = '0' + CClock::GetMinutes() / 10; + String_Time[16] = '0' + CClock::GetMinutes() % 10; + return String_Time; +} + +char String_DigitalClock[] = "12:34"; +const char* FindDigitalClockMessage() +{ + if (((CTimer::GetTimeInMilliseconds() >> 10) & 7) < 6) + { + String_DigitalClock[0] = '0' + CClock::GetHours() / 10; + String_DigitalClock[1] = '0' + CClock::GetHours() % 10; + String_DigitalClock[2] = CTimer::GetTimeInMilliseconds() & 0x200 ? ':' : ' '; + String_DigitalClock[3] = '0' + CClock::GetMinutes() / 10; + String_DigitalClock[4] = '0' + CClock::GetMinutes() % 10; + } + else + { + // they didn't use rad2deg here because of 3.14 + int temperature = 13.0f - 6.0f * Cos((CClock::GetMinutes() + 60.0f * CClock::GetHours()) / (4.0f * 180.0f / 3.14f) - 1.0f); + String_DigitalClock[0] = '0' + temperature / 10; + if (String_DigitalClock[0] == '0') + String_DigitalClock[0] = ' '; + String_DigitalClock[1] = '0' + temperature % 10; + String_DigitalClock[2] = ' '; + String_DigitalClock[3] = '@'; + String_DigitalClock[4] = 'C'; + } + return String_DigitalClock; +} + +// ---------- CScrollBar ---------- +void CScrollBar::Init(CVector position, uint8 type, float sizeX, float sizeY, float sizeZ, uint8 red, uint8 green, uint8 blue, float scale) +{ + for (int i = 0; i < ARRAY_SIZE(m_MessageBar); ++i) + m_MessageBar[i] = 0; + + m_pMessage = ". "; + m_MessageCurrentChar = 0; + m_MessageLength = 2; + + m_Counter = 0; + m_bVisible = false; + m_Position = position; + m_Type = type; + m_Size.x = sizeX; + m_Size.y = sizeY; + m_Size.z = sizeZ; + m_uRed = red; + m_uGreen = green; + m_uBlue = blue; + m_fScale = scale; +} + +void CScrollBar::Update() +{ + float distanceFromCamera = (TheCamera.GetPosition() - m_Position).Magnitude(); + if (distanceFromCamera > 100.0f) + { + m_bVisible = false; + return; + } + + m_bVisible = true; + + if (distanceFromCamera < 75.0f) + m_fIntensity = 1.0f; + else + m_fIntensity = 1.0f - 4.0f * (distanceFromCamera - 75.0f) / 100.0f; + + m_Counter = (m_Counter + 1) % 8; + + // if message is fully printed, load up the next one + if (m_Counter == 0 && ++m_MessageCurrentChar >= m_MessageLength) + { + const char* previousMessage = m_pMessage; + switch (m_Type) + { + case SCROLL_BUSINESS: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 7) + { + case 0: + m_pMessage = "SHARES UYE<10% DWD<20% NDWE>22% . . . "; + break; + case 1: + m_pMessage = "CRIME WAVE HITS LIBERTY CITY . . . "; + break; + case 2: + m_pMessage = "SHARES OBR>29% MADD<76% LEZ<11% ADAMSKI>53% AAG>110%. . . "; + break; + case 3: + m_pMessage = FindTunnelMessage(); + break; + case 4: + m_pMessage = FindBridgeMessage(); + break; + case 5: + m_pMessage = FindTimeMessage(); + break; + case 6: + if (CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH || CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_GERMAN) + m_pMessage = FindTimeMessage(); + else + m_pMessage = "WWW.GRANDTHEFTAUTO3.COM "; + break; + } + } + break; + case SCROLL_TRAFFIC: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 8) + { + case 0: + m_pMessage = "DRIVE CAREFULLY . . . "; + break; + case 1: + m_pMessage = "RECENT WAVE OF CARJACKINGS. KEEP YOUR DOORS LOCKED !!! "; + break; + case 2: + m_pMessage = "CHECK YOUR SPEED . . . "; + break; + case 3: + m_pMessage = "KEEP YOUR EYES ON THE ROAD AND NOT ON THIS SIGN "; + break; + case 4: + if (CWeather::Foggyness > 0.5f) + m_pMessage = "POOR VISIBILITY ! "; + else if (CWeather::WetRoads > 0.5f) + m_pMessage = "ROADS ARE SLIPPERY ! "; + else + m_pMessage = "ENJOY YOUR TRIP "; + break; + case 5: + m_pMessage = FindTunnelMessage(); + break; + case 6: + m_pMessage = FindBridgeMessage(); + break; + case 7: + m_pMessage = FindTimeMessage(); + break; + } + } + break; + case SCROLL_ENTERTAINMENT: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 12) + { + case 0: + m_pMessage = " )69TH STREET) STILL HOLDS TOP POSITION THIS MONTH AT THE BOX-OFFICE WITH )MY FAIR LADYBOY) JUST CREEPING UP BEHIND. "; + break; + case 1: + m_pMessage = " TALKING OF )FANNIE). THERE IS STILL TIME TO CATCH THIS LOVELY FAMILY MUSICAL, ABOUT THE ORPHAN WHO IS SO EASILY TAKEN IN BY ANY MAN WITH LOADS OF MONEY. "; + break; + case 2: + m_pMessage = " DO NOT MISS )GTA3, THE MUSICAL) . . . "; + break; + case 3: + m_pMessage = + " STILL RUNNING ARE )RATS) AND )GUYS AND DOGS), BETWEEN THEN THEY SHOULD HAVE THE LEGS TO LAST TILL THE AND OF THE YEAR. . . " + " ALSO FOR FOUR LEGGED FANS, THE STAGE VERSION OF THE GRITTY REALISTIC )SATERDAY NIGHT BEAVER) OPENED LAST WEEKEND," + " AND I FOR ONE CERTAINLY ENJOYED THAT. "; + break; + case 4: + m_pMessage = + " NOW SHOWING STATE-WIDE, ARNOLD STEELONE, HOLLYWOODS BEST LIVING SPECIAL EFFECT, APPEARS AGAIN AS A HALF_MAN," + " HALF ANDROID IN THE HALF-BAKED ROMP, )TOP DOWN CITY). AN HOMAGE TO HIS EARLIER TWO MULTI_MILLION MAKING MOVIES," + " IN WHICH HE PLAYED TWO-DEE, AN OUT OF CONTROL MONSTER, INTENT ON CORRUPTING CIVILISATION! "; + break; + case 5: + m_pMessage = + " ALSO APPEARING THIS WEEK )HALF-COCKED) SEES CHUCK SCHWARTZ UP TO HIS USUAL NONSENSE AS HE TAKES ON HALF OF LIBERTY CITY" + " IN AN ATTEMPT TO SAVE HIS CROSS-DRESSING LADY-BOY SIDEKICK, )MISS PING-PONG), FROM A GANG OF RUTHLESS COSMETIC SURGEONS. "; + break; + case 6: + m_pMessage = + " STILL SHOWING: )SOLDIERS OF MISFORTUNE), ATTROCIOUS ACTING WHICH SEES BOYZ 2 GIRLZ) TRANSITION FROM THE CHARTS TO THE BIG SCREEN," + " AT LEAST THEY ALL DIE AT THE END. . . "; + break; + case 7: + m_pMessage = + " )BADFELLAS) IS STILL GOING STRONG WITH CROWDS ALMOST BEING PUSHED INTO CINEMAS TO SEE THIS ONE." + " ANOTHER ONE WORTH LOOKING INTO IS )THE TUNNEL). "; + break; + case 8: + m_pMessage = FindTunnelMessage(); + break; + case 9: + m_pMessage = FindBridgeMessage(); + break; + case 10: + m_pMessage = FindTimeMessage(); + break; + case 11: + m_pMessage = "WWW.ROCKSTARGAMES.COM "; + break; + } + } + break; + case SCROLL_AIRPORT_DOORS: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 4) + { + case 0: + m_pMessage = "WELCOME TO LIBERTY CITY . . . "; + break; + case 1: + m_pMessage = "PLEASE HAVE YOUR PASSPORT READY . . . "; + break; + case 2: + m_pMessage = "PLACE KEYS, FIREARMS, CHANGE AND OTHER METAL OBJECTS ON THE TRAY PLEASE . . . "; + break; + case 3: + m_pMessage = FindTimeMessage(); + break; + } + } + break; + case SCROLL_AIRPORT_FRONT: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 4) + { + case 0: + m_pMessage = "WELCOME TO FRANCIS INTERNATIONAL AIRPORT . . . "; + break; + case 1: + m_pMessage = "PLEASE DO NOT LEAVE LUGGAGE UNATTENDED . . . "; + break; + case 2: + m_pMessage = "FOLLOW 1 FOR LONG AND SHORT TERM PARKING "; + break; + case 3: + m_pMessage = FindTimeMessage(); + break; + } + } + break; + case SCROLL_STORE: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 10) + { + case 0: + m_pMessage = "WWW.ROCKSTARGAMES.COM "; + break; + case 1: + m_pMessage = "GTA3 OUT NOW . . . "; + break; + case 2: + m_pMessage = "OUR STUFF IS CHEAP CHEAP CHEAP "; + break; + case 3: + m_pMessage = "BUY 12 CDS GET ONE FREE . . . "; + break; + case 4: + m_pMessage = "APPEARING IN SHOP SOON, )THE BLOODY CHOPPERS), WITH THEIR NEW ALBUM, )IS THAT MY DAUGHTER?) "; + break; + case 5: + m_pMessage = "THIS MONTH IS OUR CRAZY CLEAROUT MONTH, EVERYTHING MUST GO, CDS, DVDS, STAFF, EVEN OUR CARPETS! "; + break; + case 6: + m_pMessage = + "OUT THIS WEEK: THE THEME TUNE TO )BOYS TO GIRLS) FIRST MOVIE )SOLDIERS OF MISFORTUNE), " + "THE SINGLE )LET ME IN YOU)RE BODY-BAG) IS TAKEN FROM THE SOUNDTRACK ALBUM, )BOOT CAMP BOYS). " + "ALSO INCLUDES THE SMASH SINGLE, )PRAY IT GOES OK). "; + break; + case 7: + m_pMessage = + "ALBUMS OUT THIS WEEK: MARYDANCING, )MUTHA O) CHRIST), FEATURING THE SINGLE )WASH HIM OFF), " + "ALSO CRAIG GRAYS) DEBUT, )FADE AWAY), INCLUDES THE SINGLE OF THE SAME NAME. . . "; + break; + case 8: + m_pMessage = + "ON THE FILM FRONT, A NELY COMPILED COMPILATION OF ARNOLD STEELONES GREATEST MOVIES ON DVD. " + "THE PACK INCLUDES THE EARLY )BY-CEP), THE CULT CLASSIC )FUTURE ANNHILATOR), AND THE HILARIOUS CROSS-DRESSING COMEDY )SISTERS). " + "ONE FOR ALL THE FAMILY. . . "; + break; + case 9: + m_pMessage = FindTimeMessage(); + break; + } + } + break; + case SCROLL_USED_CARS: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 11) + { + case 0: + m_pMessage = "QUICK, TAKE A LOOK AT OUR CURRENT STOCK )CAUSE THESE AUTOS ARE MOVIN) FAST . . . "; + break; + case 1: + m_pMessage = "THAT)S RIGHT, HERE AT )CAPITAL AUTO SALES) OUR VEHICLES ARE SO GOOD THAT THEY PRACTICALLY DRIVE THEMSELVES OFF OUR LOT . . . "; + break; + case 2: + m_pMessage = "EASY CREDIT ON ALL CARS . . . "; + break; + case 3: + m_pMessage = "FEEL LIKE A STUD IN ONE OF OUR STALLIONS OR TEST-DRIVE OUR BANSHEE, IT)S A REAL STEAL!!! "; + break; + case 4: + m_pMessage = "TRY OUR HARDY PERENNIAL, IT)LL LAST YOU THE WHOLE YEAR. OUR BOBCATS AIN)T NO PUSSIES EITHER!!! "; + break; + case 5: + m_pMessage = "IF IT)S A GUARANTEE YOU'RE AFTER, GO SOMEWHERE ELSE, )CAPITAL) CARS ARE THAT GOOD THEY DON)T NEED GUARANTEES!!! "; + break; + case 6: + m_pMessage = "TOP DOLLAR OFFERED FOR YOUR OLD WHEELS, NOT YOUR CAR, JUST IT)S WHEELS. . . "; + break; + case 7: + m_pMessage = "THAT)S RIGHT WE)RE CAR SILLY. TEST DRIVE ANY CAR, YOU WON)T WANT TO BRING IT BACK!!! "; + break; + case 8: + m_pMessage = "FREE FLUFFY DICE WITH ALL PURCHASES. . ."; + break; + case 9: + if (CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH || CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_GERMAN) + m_pMessage = "QUICK, TAKE A LOOK AT OUR CURRENT STOCK )CAUSE THESE AUTOS ARE MOVIN) FAST . . . "; + else + m_pMessage = "HTTP:((ROCKSTARGAMES.COM(GRANDTHEFTAUTO3(CAPITALAUTOS "; + break; + case 10: + m_pMessage = FindTimeMessage(); + break; + } + } + break; + } + + m_MessageLength = (uint32)strlen(m_pMessage); + m_MessageCurrentChar = 0; + } + + // Scroll + for (int i = 0; i < ARRAY_SIZE(m_MessageBar)-1; i++) + m_MessageBar[i] = m_MessageBar[i + 1]; + m_MessageBar[ARRAY_SIZE(m_MessageBar)-1] = m_Counter < 5 ? ScrollCharSet[m_pMessage[m_MessageCurrentChar] - ' '][m_Counter] : 0; + + // Introduce some random displaying glitches; signs aren't supposed to be perfect :P + switch (CGeneral::GetRandomNumber() & 0xFF) + { + case 0x0D: m_MessageBar[ARRAY_SIZE(m_MessageBar)-1] = 0; break; + case 0xE3: m_MessageBar[ARRAY_SIZE(m_MessageBar)-1] = 0xE3; break; + case 0x64: m_MessageBar[ARRAY_SIZE(m_MessageBar)-1] = ~m_MessageBar[ARRAY_SIZE(m_MessageBar)-1]; break; + } +} + +void CScrollBar::Render() +{ + if (!TheCamera.IsSphereVisible(m_Position, 2.0f * 20.0f * (ABS(m_Size.x) + ABS(m_Size.y)))) + return; + + CSprite::InitSpriteBuffer(); + + // Calculate intensity of colours + uint8 r = m_fIntensity * m_uRed; + uint8 g = m_fIntensity * m_uGreen; + uint8 b = m_fIntensity * m_uBlue; + + // Set render states + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[0])); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + + CVector coronaCoord, screenCoord; + float screenW, screenH; + for (int i = 1; i < ARRAY_SIZE(m_MessageBar); ++i) + { + for (int j = 0; j < 5; ++j) + { + coronaCoord.x = m_Position.x + m_Size.x * i; + coronaCoord.y = m_Position.y + m_Size.y * i; + coronaCoord.z = m_Position.z + m_Size.z * j; + + // Render main coronas + if (m_MessageBar[i] & (1 << j)) + { + if (CSprite::CalcScreenCoors(coronaCoord, &screenCoord, &screenW, &screenH, true)) + { + CSprite::RenderBufferedOneXLUSprite( + screenCoord.x, screenCoord.y, screenCoord.z, + screenW * m_fScale, screenH * m_fScale, + r, g, b, + 255, 1.0f / screenCoord.z, 255); + } + } + // Render smaller and faded coronas for a trailing effect + else if (m_MessageBar[i - 1] & (1 << j)) + { + if (CSprite::CalcScreenCoors(coronaCoord, &screenCoord, &screenW, &screenH, true)) + { + CSprite::RenderBufferedOneXLUSprite( + screenCoord.x, screenCoord.y, screenCoord.z, + screenW * m_fScale * 0.8f, + screenH * m_fScale * 0.8f, + r / 2, + g / 2, + b / 2, + 255, 1.0f / screenCoord.z, 255); + } + } + } + } + + CSprite::FlushSpriteBuffer(); +} + +// ---------- CTowerClock ---------- +void CTowerClock::Init(CVector position, float sizeX, float sizeY, uint8 red, uint8 green, uint8 blue, float drawDistance, float scale) +{ + m_bVisible = false; + m_Position = position; + m_Size.x = sizeX; + m_Size.y = sizeY; + m_Size.z = 0.0f; + m_uRed = red; + m_uGreen = green; + m_uBlue = blue; + m_fDrawDistance = drawDistance; + m_fScale = scale; +} + +void CTowerClock::Update() +{ + float distanceFromCamera = (TheCamera.GetPosition() - m_Position).Magnitude(); + if (distanceFromCamera < m_fDrawDistance) + { + m_bVisible = true; + if (distanceFromCamera < 0.75f * m_fDrawDistance) + m_fIntensity = 1.0f; + else + m_fIntensity = 1.0f - (distanceFromCamera - 0.75f * m_fDrawDistance) * 4.0f / m_fDrawDistance; + } + else + m_bVisible = false; +} + +RwIm3DVertex TempV[4]; +void CTowerClock::Render() +{ + if (TheCamera.IsSphereVisible(m_Position, m_fScale)) + { + // Calculate angle for each clock index + float angleHour = 2.0f * (float)PI * (CClock::GetMinutes() + 60.0f * CClock::GetHours()) / 720.0f; + float angleMinute = 2.0f * (float)PI * (CClock::GetSeconds() + 60.0f * CClock::GetMinutes()) / 3600.0f; + + // Prepare render states + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + // Set vertices colors + RwIm3DVertexSetRGBA(&TempV[0], m_uRed, m_uGreen, m_uBlue, (uint8)(m_fIntensity * 255.0f)); + RwIm3DVertexSetRGBA(&TempV[1], m_uRed, m_uGreen, m_uBlue, (uint8)(m_fIntensity * 255.0f)); + RwIm3DVertexSetRGBA(&TempV[2], m_uRed, m_uGreen, m_uBlue, (uint8)(m_fIntensity * 255.0f)); + RwIm3DVertexSetRGBA(&TempV[3], m_uRed, m_uGreen, m_uBlue, (uint8)(m_fIntensity * 255.0f)); + + // Set vertices position + RwIm3DVertexSetPos(&TempV[0], m_Position.x, m_Position.y, m_Position.z); + RwIm3DVertexSetPos( + &TempV[1], + m_Position.x + Sin(angleMinute) * m_fScale * m_Size.x, + m_Position.y + Sin(angleMinute) * m_fScale * m_Size.y, + m_Position.z + Cos(angleMinute) * m_fScale + ); + RwIm3DVertexSetPos(&TempV[2], m_Position.x, m_Position.y, m_Position.z); + RwIm3DVertexSetPos( + &TempV[3], + m_Position.x + Sin(angleHour) * 0.75f * m_fScale * m_Size.x, + m_Position.y + Sin(angleHour) * 0.75f * m_fScale * m_Size.y, + m_Position.z + Cos(angleHour) * 0.75f * m_fScale + ); + + LittleTest(); + + // Draw lines + if (RwIm3DTransform(TempV, 4, nil, 0)) + { + RwIm3DRenderLine(0, 1); + RwIm3DRenderLine(2, 3); + RwIm3DEnd(); + } + } +} + +// ---------- CDigitalClock ---------- +void CDigitalClock::Init(CVector position, float sizeX, float sizeY, uint8 red, uint8 green, uint8 blue, float drawDistance, float scale) +{ + m_bVisible = false; + m_Position = position; + m_Size.x = sizeX; + m_Size.y = sizeY; + m_Size.z = 0.0f; + m_uRed = red; + m_uGreen = green; + m_uBlue = blue; + m_fDrawDistance = drawDistance; + m_fScale = scale; +} + +void CDigitalClock::Update() +{ + float distanceFromCamera = (TheCamera.GetPosition() - m_Position).Magnitude(); + if (distanceFromCamera < m_fDrawDistance) + { + m_bVisible = true; + if (distanceFromCamera < 0.75f * m_fDrawDistance) + m_fIntensity = 1.0f; + else + m_fIntensity = 1.0f - (distanceFromCamera - 0.75f * m_fDrawDistance) * 4.0f / m_fDrawDistance; + } + else + m_bVisible = false; +} + +void CDigitalClock::Render() +{ + if (TheCamera.IsSphereVisible(m_Position, 5.0f * m_fScale)) + { + CSprite::InitSpriteBuffer(); + + // Simulate flicker + float currentIntensity = m_fIntensity * CGeneral::GetRandomNumberInRange(0x300, 0x400) / 1024.0f; + + uint8 r = currentIntensity * m_uRed; + uint8 g = currentIntensity * m_uGreen; + uint8 b = currentIntensity * m_uBlue; + + // Set render states + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[0])); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + + const char* clockMessage = FindDigitalClockMessage(); + + CVector coronaCoord, screenCoord; + float screenW, screenH; + for (int c = 0; c < 5; ++c) // for each char to be displayed + { + for (int i = 0; i < 5; ++i) // for each column of coronas + { + for (int j = 0; j < 5; ++j) // for each row of coronas + { + if (ScrollCharSet[clockMessage[c] - ' '][i] & (1 << j)) + { + coronaCoord.x = m_Position.x + (8 * c + i) * m_Size.x * m_fScale / 8.0f; + coronaCoord.y = m_Position.y + (8 * c + i) * m_Size.y * m_fScale / 8.0f; + coronaCoord.z = m_Position.z + j * m_fScale / 8.0f; + + if (CSprite::CalcScreenCoors(coronaCoord, &screenCoord, &screenW, &screenH, true)) + { + CSprite::RenderBufferedOneXLUSprite( + screenCoord.x, screenCoord.y, screenCoord.z, + screenW * m_fScale * 0.12f, + screenW * m_fScale * 0.12f, + r, g, b, + 255, + 1.0f / screenCoord.z, + 255); + } + } + } + } + } + + CSprite::FlushSpriteBuffer(); + } +} diff --git a/src/renderer/Fluff.h b/src/renderer/Fluff.h new file mode 100644 index 0000000..fe3ab25 --- /dev/null +++ b/src/renderer/Fluff.h @@ -0,0 +1,106 @@ +#pragma once +#include "common.h" +#include "Vector.h" + +class CMovingThing +{ +public: + CMovingThing *m_pNext; + CMovingThing *m_pPrev; + int16 m_nType; + int16 m_nHidden; + CVector m_vecPosn; + CEntity* m_pEntity; + + void Update(); + void AddToList(CMovingThing *pThing); + void RemoveFromList(); + int16 SizeList(); +}; + +#define NUMMOVINGTHINGS 128 + +class CMovingThings +{ +public: + static CMovingThing StartCloseList; + static CMovingThing EndCloseList; + static int16 Num; + static CMovingThing aMovingThings[NUMMOVINGTHINGS]; + + static void Init(); + static void Shutdown(); + static void Update(); + static void Render(); +}; + +class CScrollBar +{ +private: + uint8 m_Counter; + const char* m_pMessage; + CVector m_Position; + uint32 m_MessageCurrentChar; + uint32 m_MessageLength; + CVector m_Size; + float m_fIntensity; + uint8 m_MessageBar[40]; + uint8 m_Type; + bool m_bVisible; + uint8 m_uRed; + uint8 m_uGreen; + uint8 m_uBlue; + float m_fScale; + +public: + void SetVisibility(bool visible) { m_bVisible = visible; } + bool IsVisible() { return m_bVisible; } + + void Init(CVector, uint8, float, float, float, uint8, uint8, uint8, float); + void Update(); + void Render(); +}; + +class CTowerClock +{ +private: + CVector m_Position; + CVector m_Size; + float m_fDrawDistance; + float m_fScale; + uint8 m_uRed; + uint8 m_uGreen; + uint8 m_uBlue; + bool m_bVisible; + float m_fIntensity; + +public: + void SetVisibility(bool visible) { m_bVisible = visible; } + bool IsVisible() { return m_bVisible; } + + void Init(CVector, float, float, uint8, uint8, uint8, float, float); + void Update(); + void Render(); +}; + +class CDigitalClock +{ +private: + CVector m_Position; + CVector m_Size; + float m_fDrawDistance; + float m_fScale; + uint8 m_uRed; + uint8 m_uGreen; + uint8 m_uBlue; + bool m_bVisible; + float m_fIntensity; + +public: + void SetVisibility(bool visible) { m_bVisible = visible; } + bool IsVisible() { return m_bVisible; } + + void Init(CVector, float, float, uint8, uint8, uint8, float, float); + void Update(); + void Render(); +}; \ No newline at end of file diff --git a/src/renderer/Font.cpp b/src/renderer/Font.cpp new file mode 100644 index 0000000..6a9944e --- /dev/null +++ b/src/renderer/Font.cpp @@ -0,0 +1,1628 @@ +#include "common.h" + +#include "Sprite2d.h" +#include "TxdStore.h" +#include "Font.h" +#ifdef BUTTON_ICONS +#include "FileMgr.h" +#endif + +void +AsciiToUnicode(const char *src, wchar *dst) +{ + while((*dst++ = (unsigned char)*src++) != '\0'); +} + +void +UnicodeStrcat(wchar *dst, wchar *append) +{ + UnicodeStrcpy(&dst[UnicodeStrlen(dst)], append); +} + +void +UnicodeStrcpy(wchar *dst, const wchar *src) +{ + while((*dst++ = *src++) != '\0'); +} + +int +UnicodeStrlen(const wchar *str) +{ + int len; + for(len = 0; *str != '\0'; len++, str++); + return len; +} + +CFontDetails CFont::Details; +bool16 CFont::NewLine; +CSprite2d CFont::Sprite[MAX_FONTS]; + +#ifdef MORE_LANGUAGES +uint8 CFont::LanguageSet = FONT_LANGSET_EFIGS; +int32 CFont::Slot = -1; +#define JAP_TERMINATION (0x8000 | '~') + +int16 CFont::Size[LANGSET_MAX][MAX_FONTS][193] = { + { +#else +int16 CFont::Size[MAX_FONTS][193] = { +#endif + +#if !defined(GTA_PS2) || defined(FIX_BUGS) + { + 13, 12, 31, 35, 23, 35, 31, 9, 14, 15, 25, 30, 11, 17, 13, 31, + 23, 16, 22, 21, 24, 23, 23, 20, 23, 22, 10, 35, 26, 26, 26, 26, + 30, 26, 24, 23, 24, 22, 21, 24, 26, 10, 20, 26, 22, 29, 26, 25, + 23, 25, 24, 24, 22, 25, 24, 29, 29, 23, 25, 37, 22, 37, 35, 37, + 35, 21, 22, 21, 21, 22, 13, 22, 21, 10, 16, 22, 11, 32, 21, 21, + 23, 22, 16, 20, 14, 21, 20, 30, 25, 21, 21, 33, 33, 33, 33, 35, + 27, 27, 27, 27, 32, 24, 23, 23, 23, 23, 11, 11, 11, 11, 26, 26, + 26, 26, 26, 26, 26, 25, 26, 21, 21, 21, 21, 32, 23, 22, 22, 22, + 22, 11, 11, 11, 11, 22, 22, 22, 22, 22, 22, 22, 22, 26, 21, 24, + 12, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 18, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 20 + }, + + { + 13, 9, 21, 35, 23, 35, 35, 11, 35, 35, 25, 35, 11, 17, 13, 33, + 28, 14, 22, 21, 24, 23, 23, 21, 23, 22, 10, 35, 13, 35, 13, 33, + 5, 25, 22, 23, 24, 21, 21, 24, 24, 9, 20, 24, 21, 27, 25, 25, + 22, 25, 23, 20, 23, 23, 23, 31, 23, 23, 23, 37, 33, 37, 35, 37, + 35, 21, 19, 19, 21, 19, 17, 21, 21, 8, 17, 18, 14, 24, 21, 21, + 20, 22, 19, 20, 20, 19, 20, 26, 21, 20, 21, 33, 33, 33, 33, 35, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 16 + }, + + { + 15, 14, 16, 25, 19, 26, 22, 11, 18, 18, 27, 26, 13, 19, 9, 27, + 19, 18, 19, 19, 22, 19, 20, 18, 19, 20, 12, 32, 15, 32, 15, 35, + 15, 19, 19, 19, 19, 19, 16, 19, 20, 9, 19, 20, 14, 29, 19, 20, + 19, 19, 19, 19, 21, 19, 20, 32, 20, 19, 19, 33, 31, 39, 37, 39, + 37, 21, 21, 21, 23, 21, 19, 23, 23, 10, 19, 20, 16, 26, 23, 23, + 20, 20, 20, 22, 21, 22, 22, 26, 22, 22, 23, 35, 35, 35, 35, 37, + 19, 19, 19, 19, 29, 19, 19, 19, 19, 19, 9, 9, 9, 9, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 30, 19, 19, 19, 19, + 19, 10, 10, 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 23, 35, + 12, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 11, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19 + } +#else // #if defined(GTA_PS2) && !defined(FIX_BUGS) + { + 13, 12, 31, 35, 23, 35, 31, 9, 14, 15, 25, 30, 11, 17, 13, 31, + 23, 16, 22, 21, 24, 23, 23, 20, 23, 22, 10, 35, 26, 26, 26, 26, + 30, 26, 24, 23, 24, 22, 21, 24, 26, 10, 20, 26, 22, 29, 26, 25, + 24, 25, 24, 24, 22, 25, 24, 29, 29, 23, 25, 37, 22, 37, 35, 37, + 35, 21, 22, 21, 21, 22, 13, 22, 21, 10, 16, 22, 11, 32, 21, 21, + 23, 22, 16, 20, 14, 21, 20, 30, 25, 21, 21, 33, 33, 33, 33, 35, + 27, 27, 27, 27, 32, 24, 23, 23, 23, 23, 11, 11, 11, 11, 26, 26, + 26, 26, 26, 26, 26, 25, 26, 21, 21, 21, 21, 32, 23, 22, 22, 22, + 22, 11, 11, 11, 11, 22, 22, 22, 22, 22, 22, 22, 22, 26, 21, 24, + 12, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 18, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 20 + }, + + { + 13, 9, 21, 35, 23, 35, 35, 11, 35, 35, 25, 35, 11, 17, 13, 33, + 28, 14, 22, 21, 24, 23, 23, 21, 23, 22, 10, 35, 13, 35, 13, 33, + 5, 25, 22, 23, 24, 21, 21, 24, 24, 9, 20, 24, 21, 27, 25, 25, + 22, 25, 23, 20, 23, 23, 23, 31, 23, 23, 23, 37, 33, 37, 35, 37, + 35, 21, 19, 19, 21, 19, 17, 21, 21, 8, 17, 18, 14, 24, 21, 21, + 20, 22, 19, 20, 20, 19, 20, 26, 21, 20, 21, 33, 33, 33, 33, 35, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 16 + }, + + { + 15, 14, 16, 25, 19, 26, 22, 11, 18, 18, 27, 26, 13, 19, 9, 27, + 19, 18, 19, 19, 21, 19, 20, 18, 19, 20, 12, 32, 15, 32, 15, 35, + 15, 19, 19, 19, 19, 19, 16, 19, 20, 9, 19, 20, 14, 29, 19, 19, + 19, 19, 19, 19, 21, 19, 20, 32, 20, 19, 19, 33, 31, 39, 37, 39, + 37, 21, 21, 21, 23, 21, 19, 23, 23, 10, 19, 20, 16, 26, 23, 23, + 20, 20, 20, 22, 21, 22, 22, 26, 22, 22, 23, 35, 35, 35, 35, 37, + 19, 19, 19, 19, 29, 19, 19, 19, 19, 19, 9, 9, 9, 9, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 30, 19, 19, 19, 19, 19, + 10, 10, 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 23, 35, 12, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 11, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19 + } +#endif + +#ifdef MORE_LANGUAGES + }, + { + { 13, 12, 31, 35, 23, 35, 31, 9, 14, 15, 25, 30, 11, 17, + 13, 31, 23, 16, 22, 21, 24, 23, 23, 20, 23, 22, 10, + 35, 26, 26, 26, 26, 30, 26, 24, 23, 24, 22, 21, 24, + 26, 10, 20, 26, 22, 29, 26, 25, 23, 25, 24, 24, 22, + 25, 24, 29, 29, 23, 25, 37, 22, 37, 35, 37, 35, 21, + 22, 21, 21, 22, 13, 22, 21, 10, 16, 22, 11, 32, 21, + 21, 23, 22, 16, 20, 14, 21, 20, 30, 25, 21, 21, 13, + 33, 13, 13, 13, 24, 22, 22, 19, 26, 21, 30, 20, 23, + 23, 21, 24, 26, 23, 22, 23, 21, 22, 20, 20, 26, 25, + 24, 22, 31, 32, 23, 30, 22, 22, 32, 23, 19, 18, 18, + 15, 22, 19, 27, 19, 20, 20, 18, 22, 24, 20, 19, 19, + 20, 19, 16, 19, 28, 20, 20, 18, 26, 27, 19, 26, 18, + 19, 27, 19, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 18, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 20 }, + { 13, 9, 21, 35, 23, 35, 35, 11, 35, 35, 25, 35, 11, + 17, 13, 33, 28, 14, 22, 21, 24, 23, 23, 21, 23, 22, + 10, 35, 13, 35, 13, 33, 5, 25, 22, 23, 24, 21, 21, 24, + 24, 9, 20, 24, 21, 27, 25, 25, 22, 25, 23, 20, 23, 23, + 23, 31, 23, 23, 23, 37, 33, 37, 35, 37, 35, 21, 19, + 19, 21, 19, 17, 21, 21, 8, 17, 18, 14, 24, 21, 21, 20, + 22, 19, 20, 20, 19, 20, 26, 21, 20, 21, 33, 33, 33, + 33, 35, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 16, }, + { 15, 14, 16, 25, 19, + 26, 22, 11, 18, 18, 27, 26, 13, 19, 9, 27, 19, 18, 19, + 19, 22, 19, 20, 18, 19, 20, 12, 32, 15, 32, 15, 35, + 15, 19, 19, 19, 19, 19, 16, 19, 20, 9, 19, 20, 14, 29, + 19, 20, 19, 19, 19, 19, 21, 19, 20, 32, 20, 19, 19, + 33, 31, 39, 37, 39, 37, 21, 21, 21, 23, 21, 19, 23, 23, 10, 19, 20, 16, 26, 23, + 21, 21, 20, 20, 22, 21, 22, 22, 26, 22, 22, 23, 35, + 35, 35, 35, 37, 19, 19, 19, 19, 19, 19, 29, 19, 19, + 19, 20, 22, 31, 19, 19, 19, 19, 19, 29, 19, 29, 19, + 21, 19, 30, 31, 21, 29, 19, 19, 29, 19, 21, 23, 32, + 21, 21, 30, 31, 22, 21, 32, 33, 23, 32, 21, 21, 32, + 21, 19, 19, 30, 31, 22, 22, 21, 32, 33, 23, 32, 21, + 21, 32, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 11, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19 }, + }, + + { + { + 13, 12, 31, 35, 23, 35, 31, 9, 14, 15, 25, 30, 11, 17, 13, 31, + 23, 16, 22, 21, 24, 23, 23, 20, 23, 22, 10, 35, 26, 26, 26, 26, + 30, 26, 24, 23, 24, 22, 21, 24, 26, 10, 20, 26, 22, 29, 26, 25, + 23, 25, 24, 24, 22, 25, 24, 29, 29, 23, 25, 37, 22, 37, 35, 37, + 35, 21, 22, 21, 21, 22, 13, 22, 21, 10, 16, 22, 11, 32, 21, 21, + 23, 22, 16, 20, 14, 21, 20, 30, 25, 21, 21, 33, 33, 33, 33, 35, + 27, 27, 27, 27, 32, 24, 23, 23, 23, 23, 11, 11, 11, 11, 26, 26, + 26, 26, 26, 26, 26, 25, 26, 21, 21, 21, 21, 32, 23, 22, 22, 22, + 22, 11, 11, 11, 11, 22, 22, 22, 22, 22, 22, 22, 22, 26, 21, 24, + 12, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 18, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 20 + }, + + { + 13, 9, 21, 35, 23, 35, 35, 11, 35, 35, 25, 35, 11, 17, 13, 33, + 28, 14, 22, 21, 24, 23, 23, 21, 23, 22, 10, 35, 13, 35, 13, 33, + 5, 25, 22, 23, 24, 21, 21, 24, 24, 9, 20, 24, 21, 27, 25, 25, + 22, 25, 23, 20, 23, 23, 23, 31, 23, 23, 23, 37, 33, 37, 35, 37, + 35, 21, 19, 19, 21, 19, 17, 21, 21, 8, 17, 18, 14, 24, 21, 21, + 20, 22, 19, 20, 20, 19, 20, 26, 21, 20, 21, 33, 33, 33, 33, 35, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 16 + }, + + { + 15, 14, 16, 25, 19, 26, 22, 11, 18, 18, 27, 26, 13, 19, 9, 27, + 19, 18, 19, 19, 22, 19, 20, 18, 19, 20, 12, 32, 15, 32, 15, 35, + 15, 19, 19, 19, 19, 19, 16, 19, 20, 9, 19, 20, 14, 29, 19, 20, + 19, 19, 19, 19, 21, 19, 20, 32, 20, 19, 19, 33, 31, 39, 37, 39, + 37, 21, 21, 21, 23, 21, 19, 23, 23, 10, 19, 20, 16, 26, 23, 23, + 20, 20, 20, 22, 21, 22, 22, 26, 22, 22, 23, 35, 35, 35, 35, 37, + 19, 19, 19, 19, 29, 19, 19, 19, 19, 19, 9, 9, 9, 9, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 30, 19, 19, 19, 19, + 19, 10, 10, 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 23, 35, + 12, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 11, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19 + } + } +#endif +}; + +#ifdef MORE_LANGUAGES +int16 Size_jp[] = { + 15, 14, 16, 20, 19, 26, 22, 11, 18, 18, 27, 26, 13, //; 0 + 19, 20, 27, 19, 15, 19, 19, 21, 19, 20, 18, 19, 15, //; 13 + 13, 28, 15, 32, 15, 35, 15, 19, 19, 19, 19, 17, 16, //; 26 + 19, 20, 15, 19, 20, 14, 17, 19, 19, 19, 19, 19, 19, //; 39 + 19, 19, 20, 25, 20, 19, 19, 33, 31, 39, 37, 39, 37, //; 52 + 21, 21, 21, 19, 17, 15, 23, 21, 15, 19, 20, 16, 19, //; 65 + 19, 19, 20, 20, 17, 22, 19, 22, 22, 19, 22, 22, 23, //; 78 + 35, 35, 35, 35, 37, 19, 19, 19, 19, 29, 19, 19, 19, //; 91 + 19, 19, 9, 9, 9, 9, 19, 19, 19, 19, 19, 19, 19, 19, //; 104 + 19, 19, 19, 19, 19, 30, 19, 19, 19, 19, 19, 10, 10, //; 118 + 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 23, 35, //; 131 + 12, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, //; 144 + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, //; 157 + 19, 19, 19, 11, 19, 19, 19, 19, 19, 19, 19, 19, 19, //; 170 + 19, 19, 19, 19, 19, 19, 19, 19, 19, 21 +}; +#endif + +wchar foreign_table[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 177, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 175, + 128, 129, 130, 0, 131, 0, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, + 0, 173, 142, 143, 144, 0, 145, 0, 0, 146, 147, 148, 149, 0, 0, 150, + 151, 152, 153, 0, 154, 0, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 0, 174, 165, 166, 167, 0, 168, 0, 0, 169, 170, 171, 172, 0, 0, 0, +}; + +#ifdef BUTTON_ICONS +CSprite2d CFont::ButtonSprite[MAX_BUTTON_ICONS]; +int CFont::PS2Symbol = BUTTON_NONE; +int CFont::ButtonsSlot = -1; +#endif // BUTTON_ICONS + +void +CFont::Initialise(void) +{ + int slot; + + slot = CTxdStore::AddTxdSlot("fonts"); +#ifdef MORE_LANGUAGES + Slot = slot; + switch (LanguageSet) + { + case FONT_LANGSET_EFIGS: + default: + CTxdStore::LoadTxd(slot, "MODELS/FONTS.TXD"); + break; + case FONT_LANGSET_POLISH: + CTxdStore::LoadTxd(slot, "MODELS/FONTS_P.TXD"); + break; + case FONT_LANGSET_RUSSIAN: + CTxdStore::LoadTxd(slot, "MODELS/FONTS_R.TXD"); + break; + case FONT_LANGSET_JAPANESE: + CTxdStore::LoadTxd(slot, "MODELS/FONTS_J.TXD"); + break; + } +#else + CTxdStore::LoadTxd(slot, "MODELS/FONTS.TXD"); +#endif + CTxdStore::AddRef(slot); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(slot); + Sprite[0].SetTexture("font2", "font2_mask"); +#ifdef MORE_LANGUAGES + if (IsJapanese()) { + Sprite[1].SetTexture("FONTJAP", "FONTJAP_mask"); + Sprite[3].SetTexture("FONTJAP", "FONTJAP_mask"); + } + else +#endif // MORE_LANGUAGES + Sprite[1].SetTexture("pager", "pager_mask"); + Sprite[2].SetTexture("font1", "font1_mask"); + SetScale(1.0f, 1.0f); + SetSlantRefPoint(SCREEN_WIDTH, 0.0f); + SetSlant(0.0f); + SetColor(CRGBA(255, 255, 255, 0)); + SetJustifyOff(); + SetCentreOff(); +#ifdef FIX_BUGS + SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + SetCentreSize(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); +#else + SetWrapx(DEFAULT_SCREEN_WIDTH); + SetCentreSize(DEFAULT_SCREEN_WIDTH); +#endif + SetBackgroundOff(); + SetBackgroundColor(CRGBA(128, 128, 128, 128)); + SetBackGroundOnlyTextOff(); + SetPropOn(); + SetFontStyle(FONT_BANK); + SetRightJustifyWrap(0.0f); + SetAlphaFade(255.0f); + SetDropShadowPosition(0); + CTxdStore::PopCurrentTxd(); + +#if !defined(GAMEPAD_MENU) && defined(BUTTON_ICONS) + // loaded in CMenuManager with GAMEPAD_MENU defined + LoadButtons("MODELS/X360BTNS.TXD"); +#endif +} + +#ifdef BUTTON_ICONS +void +CFont::LoadButtons(const char* txdPath) +{ + if (int file = CFileMgr::OpenFile(txdPath)) { + CFileMgr::CloseFile(file); + if (ButtonsSlot == -1) + ButtonsSlot = CTxdStore::AddTxdSlot("buttons"); + else { + for (int i = 0; i < MAX_BUTTON_ICONS; i++) + ButtonSprite[i].Delete(); + CTxdStore::RemoveTxd(ButtonsSlot); + } + CTxdStore::LoadTxd(ButtonsSlot, txdPath); + CTxdStore::AddRef(ButtonsSlot); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(ButtonsSlot); +#if 0 // unused + ButtonSprite[BUTTON_UP].SetTexture("up"); + ButtonSprite[BUTTON_DOWN].SetTexture("down"); + ButtonSprite[BUTTON_LEFT].SetTexture("left"); + ButtonSprite[BUTTON_RIGHT].SetTexture("right"); +#endif + ButtonSprite[BUTTON_CROSS].SetTexture("cross"); + ButtonSprite[BUTTON_CIRCLE].SetTexture("circle"); + ButtonSprite[BUTTON_SQUARE].SetTexture("square"); + ButtonSprite[BUTTON_TRIANGLE].SetTexture("triangle"); + ButtonSprite[BUTTON_L1].SetTexture("l1"); + ButtonSprite[BUTTON_L2].SetTexture("l2"); + ButtonSprite[BUTTON_L3].SetTexture("l3"); + ButtonSprite[BUTTON_R1].SetTexture("r1"); + ButtonSprite[BUTTON_R2].SetTexture("r2"); + ButtonSprite[BUTTON_R3].SetTexture("r3"); + CTxdStore::PopCurrentTxd(); + } + else { + if (ButtonsSlot != -1) { + for (int i = 0; i < MAX_BUTTON_ICONS; i++) + ButtonSprite[i].Delete(); + CTxdStore::RemoveTxdSlot(ButtonsSlot); + ButtonsSlot = -1; + } + } +} +#endif // BUTTON_ICONS + +#ifdef MORE_LANGUAGES +void +CFont::ReloadFonts(uint8 set) +{ + if (Slot != -1 && LanguageSet != set) { + Sprite[0].Delete(); + Sprite[1].Delete(); + Sprite[2].Delete(); + if (IsJapanese()) + Sprite[3].Delete(); + CTxdStore::PushCurrentTxd(); + CTxdStore::RemoveTxd(Slot); + switch (set) + { + case FONT_LANGSET_EFIGS: + default: + CTxdStore::LoadTxd(Slot, "MODELS/FONTS.TXD"); + break; + case FONT_LANGSET_POLISH: + CTxdStore::LoadTxd(Slot, "MODELS/FONTS_P.TXD"); + break; + case FONT_LANGSET_RUSSIAN: + CTxdStore::LoadTxd(Slot, "MODELS/FONTS_R.TXD"); + break; + case FONT_LANGSET_JAPANESE: + CTxdStore::LoadTxd(Slot, "MODELS/FONTS_J.TXD"); + break; + } + CTxdStore::SetCurrentTxd(Slot); + Sprite[0].SetTexture("font2", "font2_mask"); + if (set == FONT_LANGSET_JAPANESE) { + Sprite[1].SetTexture("FONTJAP", "FONTJAP_mask"); + Sprite[3].SetTexture("FONTJAP", "FONTJAP_mask"); + } + else + Sprite[1].SetTexture("pager", "pager_mask"); + Sprite[2].SetTexture("font1", "font1_mask"); + CTxdStore::PopCurrentTxd(); + } + LanguageSet = set; +} +#endif + +void +CFont::Shutdown(void) +{ +#ifdef BUTTON_ICONS + if (ButtonsSlot != -1) { + for (int i = 0; i < MAX_BUTTON_ICONS; i++) + ButtonSprite[i].Delete(); + CTxdStore::RemoveTxdSlot(ButtonsSlot); + ButtonsSlot = -1; + } +#endif + Sprite[0].Delete(); + Sprite[1].Delete(); + Sprite[2].Delete(); +#ifdef MORE_LANGUAGES + if (IsJapanese()) + Sprite[3].Delete(); + CTxdStore::RemoveTxdSlot(Slot); + Slot = -1; +#else + CTxdStore::RemoveTxdSlot(CTxdStore::FindTxdSlot("fonts")); +#endif +} + +void +CFont::InitPerFrame(void) +{ + Details.bank = CSprite2d::GetBank(30, Sprite[0].m_pTexture); + CSprite2d::GetBank(15, Sprite[1].m_pTexture); + CSprite2d::GetBank(15, Sprite[2].m_pTexture); +#ifdef MORE_LANGUAGES + if (IsJapanese()) + CSprite2d::GetBank(15, Sprite[3].m_pTexture); +#endif + SetDropShadowPosition(0); + NewLine = false; +#ifdef BUTTON_ICONS + PS2Symbol = BUTTON_NONE; +#endif +} + +#ifdef BUTTON_ICONS +void +CFont::DrawButton(float x, float y) +{ + if (x <= 0.0f || x > SCREEN_WIDTH || y <= 0.0f || y > SCREEN_HEIGHT) + return; + + if (PS2Symbol != BUTTON_NONE) { + CRect rect; + rect.left = x; + rect.top = Details.scaleY + Details.scaleY + y; + rect.right = Details.scaleY * 17.0f + x; + rect.bottom = Details.scaleY * 19.0f + y; + + int vertexAlphaState; + RwRenderStateGet(rwRENDERSTATEVERTEXALPHAENABLE, &vertexAlphaState); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + ButtonSprite[PS2Symbol].Draw(rect, CRGBA(255, 255, 255, Details.color.a)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)vertexAlphaState); + } +} +#endif + +void +CFont::PrintChar(float x, float y, wchar c) +{ + if(x <= 0.0f || x > SCREEN_WIDTH || +#ifdef FIX_BUGS + y <= 0.0f || y > SCREEN_HEIGHT) +#else + y <= 0.0f || y > SCREEN_WIDTH) +#endif + return; + + float w = GetCharacterWidth(c) / 32.0f; + float xoff = c % 16; + float yoff = c / 16; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) { + w = 21.0f; + xoff = (float)(c % 48); + yoff = c / 48; + } +#endif + + if(Details.style == FONT_BANK || Details.style == FONT_HEADING){ + if(Details.dropShadowPosition != 0){ + CSprite2d::AddSpriteToBank( +#ifdef FIX_BUGS + Details.bank + Details.style, +#else + Details.style, // BUG: game doesn't add bank +#endif +#ifdef FIX_BUGS + CRect(x + SCREEN_SCALE_X(Details.dropShadowPosition), + y + SCREEN_SCALE_Y(Details.dropShadowPosition), + x + SCREEN_SCALE_X(Details.dropShadowPosition) + 32.0f * Details.scaleX * 1.0f, + y + SCREEN_SCALE_Y(Details.dropShadowPosition) + 40.0f * Details.scaleY * 0.5f), +#else + CRect(x + Details.dropShadowPosition, + y + Details.dropShadowPosition, + x + Details.dropShadowPosition + 32.0f * Details.scaleX * 1.0f, + y + Details.dropShadowPosition + 40.0f * Details.scaleY * 0.5f), +#endif + Details.dropColor, + xoff/16.0f, yoff/12.8f, + (xoff+1.0f)/16.0f - 0.001f, yoff/12.8f, + xoff/16.0f, (yoff+1.0f)/12.8f, + (xoff+1.0f)/16.0f - 0.001f, (yoff+1.0f)/12.8f - 0.0001f); + } + CSprite2d::AddSpriteToBank( +#ifdef FIX_BUGS + Details.bank + Details.style, +#else + Details.style, // BUG: game doesn't add bank +#endif + CRect(x, y, + x + 32.0f * Details.scaleX * 1.0f, + y + 40.0f * Details.scaleY * 0.5f), + Details.color, + xoff/16.0f, yoff/12.8f, + (xoff+1.0f)/16.0f - 0.001f, yoff/12.8f, + xoff/16.0f, (yoff+1.0f)/12.8f - 0.002f, + (xoff+1.0f)/16.0f - 0.001f, (yoff+1.0f)/12.8f - 0.002f); +#ifdef MORE_LANGUAGES + }else if (IsJapaneseFont()) { + if (Details.dropShadowPosition != 0) { + CSprite2d::AddSpriteToBank( +#ifdef FIX_BUGS + Details.bank + Details.style, +#else + Details.style, // BUG: game doesn't add bank +#endif +#ifdef FIX_BUGS + CRect(x + SCREEN_SCALE_X(Details.dropShadowPosition), + y + SCREEN_SCALE_Y(Details.dropShadowPosition), + x + SCREEN_SCALE_X(Details.dropShadowPosition) + 32.0f * Details.scaleX * 1.0f, + y + SCREEN_SCALE_Y(Details.dropShadowPosition) + 40.0f * Details.scaleY / 2.75f), +#else + CRect(x + Details.dropShadowPosition, + y + Details.dropShadowPosition, + x + Details.dropShadowPosition + 32.0f * Details.scaleX * 1.0f, + y + Details.dropShadowPosition + 40.0f * Details.scaleY / 2.75f), +#endif + Details.dropColor, + xoff * w / 1024.0f, yoff / 25.6f, + xoff * w / 1024.0f + (1.0f / 48.0f) - 0.001f, yoff / 25.6f, + xoff * w / 1024.0f, (yoff + 1.0f) / 25.6f, + xoff * w / 1024.0f + (1.0f / 48.0f) - 0.001f, (yoff + 1.0f) / 25.6f - 0.0001f); + } + CSprite2d::AddSpriteToBank(Details.bank + Details.style, // BUG: game doesn't add bank + CRect(x, y, + x + 32.0f * Details.scaleX * 1.0f, + y + 40.0f * Details.scaleY / 2.75f), + Details.color, + xoff * w / 1024.0f, yoff / 25.6f, + xoff * w / 1024.0f + (1.0f / 48.0f) - 0.001f, yoff / 25.6f, + xoff * w / 1024.0f, (yoff + 1.0f) / 25.6f - 0.002f, + xoff * w / 1024.0f + (1.0f / 48.0f) - 0.001f, (yoff + 1.0f) / 25.6f - 0.0001f); +#endif + }else + { + CSprite2d::AddSpriteToBank( +#ifdef FIX_BUGS + Details.bank + Details.style, +#else + Details.style, // BUG: game doesn't add bank +#endif + CRect(x, y, + x + 32.0f * Details.scaleX * w, + y + 32.0f * Details.scaleY * 0.5f), + Details.color, + xoff/16.0f, yoff/16.0f, + (xoff+w)/16.0f, yoff/16.0f, + xoff/16.0f, (yoff+1.0f)/16.0f, + (xoff+w)/16.0f - 0.0001f, (yoff+1.0f)/16.0f - 0.0001f); + } +} + +#ifdef MORE_LANGUAGES +bool CFont::IsJapanesePunctuation(wchar *str) +{ + return (*str == 0xE7 || *str == 0x124 || *str == 0x126 || *str == 0x128 || *str == 0x104 || *str == ',' || *str == '>' || *str == '!' || *str == 0x99 || *str == '?' || *str == ':'); +} + +bool CFont::IsAnsiCharacter(wchar *s) +{ + if (*s >= 'A' && *s <= 'Z') + return true; + if (*s >= 'a' && *s <= 'z') + return true; + if (*s >= '0' && *s <= ':') + return true; + if (*s == '(' || *s == ')') + return true; + if (*s == 'D' || *s == '$') + return true; + return false; +} +#endif + +void +CFont::PrintString(float xstart, float ystart, wchar *s) +{ + CRect rect; + int numSpaces; + float lineLength; + float x, y; + bool first; + wchar *start, *t; + + if(*s == '*') + return; + + if(Details.background){ + GetNumberLines(xstart, ystart, s); // BUG: result not used + GetTextRect(&rect, xstart, ystart, s); + CSprite2d::DrawRect(rect, Details.backgroundColor); + } + + lineLength = 0.0f; + numSpaces = 0; + first = true; + if(Details.centre || Details.rightJustify) + x = 0.0f; + else + x = xstart; + y = ystart; + start = s; + + // This is super ugly, I blame R* + for(;;){ + for(;;){ + for(;;){ + if(*s == '\0') + return; + float xend = Details.centre ? Details.centreSize : + Details.rightJustify ? xstart - Details.rightJustifyWrap : + Details.wrapX; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) + xend -= SCREEN_SCALE_X(21.0f * 2.0f); +#endif + if(x + GetStringWidth(s) > xend && !first){ +#ifdef MORE_LANGUAGES + if (IsJapanese() && IsJapanesePunctuation(s)) + s--; +#endif + // flush line + float spaceWidth = !Details.justify || Details.centre ? 0.0f : + (Details.wrapX - lineLength) / numSpaces; + float xleft = Details.centre ? xstart - x/2 : + Details.rightJustify ? xstart - x : + xstart; +#ifdef MORE_LANGUAGES + PrintString(xleft, y, start, s, spaceWidth, xstart); +#else + PrintString(xleft, y, start, s, spaceWidth); +#endif + // reset things + lineLength = 0.0f; + numSpaces = 0; + first = true; + if(Details.centre || Details.rightJustify) + x = 0.0f; + else + x = xstart; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) + y += 32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY; + else +#endif + y += 32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY; + start = s; + }else + break; + } + // advance by one word + t = GetNextSpace(s); + if(t[0] == '\0' || + t[0] == ' ' && t[1] == '\0') + break; + if(!first) + numSpaces++; + first = false; + x += GetStringWidth(s) + GetCharacterSize(*t - ' '); +#ifdef MORE_LANGUAGES + if (IsJapaneseFont() && IsAnsiCharacter(s)) + x += 21.0f; +#endif + lineLength = x; + s = t+1; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont() && !*s) { + x += GetStringWidth(s); + if (IsAnsiCharacter(s)) + x += 21.0f; + float xleft = Details.centre ? xstart - x / 2 : + Details.rightJustify ? xstart - x : + xstart; + if (PrintString(xleft, y, start, s, 0.0f, xstart)) + { + start = s; + if (!Details.centre && !Details.rightJustify) + x = xstart; + else + x = 0.0f; + + y += 32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY; + numSpaces = 0; + first = true; + lineLength = 0.0f; + } + } +#endif + } + // print rest + if(t[0] == ' ' && t[1] == '\0') + t[0] = '\0'; + x += GetStringWidth(s); + s = t; + float xleft = Details.centre ? xstart - x/2 : + Details.rightJustify ? xstart - x : + xstart; +#ifdef MORE_LANGUAGES + if (PrintString(xleft, y, start, s, 0.0f, xstart) && IsJapaneseFont()) { + start = s; + if (!Details.centre && !Details.rightJustify) + x = xstart; + else + x = 0.0f; + y += 32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY; + numSpaces = 0; + first = true; + lineLength = 0.0f; + } +#else + PrintString(xleft, y, start, s, 0.0f); +#endif + } +} + +int +CFont::GetNumberLines(float xstart, float ystart, wchar *s) +{ + int n; + float x, y; + wchar *t; + n = 0; + +#ifdef MORE_LANGUAGES + bool bSomeJapBool = false; + + if (IsJapanese()) { + t = s; + wchar unused; + while (*t) { + if (*t == JAP_TERMINATION || *t == '~') + t = ParseToken(t, &unused, true); + if (NewLine) { + n++; + NewLine = false; + bSomeJapBool = true; + } + t++; + } + } + + if (bSomeJapBool) n--; +#endif + + if(Details.centre || Details.rightJustify) + x = 0.0f; + else + x = xstart; + y = ystart; + + while(*s){ +#ifdef FIX_BUGS + float f = Details.centre ? Details.centreSize : + Details.rightJustify ? xstart - Details.rightJustifyWrap : + Details.wrapX; +#else + float f = (Details.centre ? Details.centreSize : Details.wrapX); +#endif + +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) + f -= SCREEN_SCALE_X(21.0f * 2.0f); +#endif + + if(x + GetStringWidth(s) > f){ +#ifdef MORE_LANGUAGES + if (IsJapanese()) + { + if (IsJapanesePunctuation(s)) + s--; + } +#endif + // reached end of line + if(Details.centre || Details.rightJustify) + x = 0.0f; + else + x = xstart; + n++; + // Why even? +#ifdef MORE_LANGUAGES + if (IsJapanese()) + y += 32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY; + else +#endif + y += 32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY; + }else{ + // still space in current line + t = GetNextSpace(s); + if(*t == '\0'){ + // end of string + x += GetStringWidth(s); +#ifdef MORE_LANGUAGES + if (IsJapanese() && IsAnsiCharacter(s)) + x += 21.0f; +#endif + n++; + s = t; + }else{ + x += GetStringWidth(s); +#ifdef MORE_LANGUAGES + if (IsJapanese() && IsAnsiCharacter(s)) + x += 21.0f; +#endif + s = t+1; + x += GetCharacterSize(*t - ' '); +#ifdef MORE_LANGUAGES + if (IsJapanese() && !*s) + n++; +#endif + } + } + } + + return n; +} + +void +CFont::GetTextRect(CRect *rect, float xstart, float ystart, wchar *s) +{ + int numLines; + float x, y; + int16 maxlength; + wchar *t; + + maxlength = 0; + numLines = 0; + +#ifdef MORE_LANGUAGES + if (IsJapanese()) { + numLines = GetNumberLines(xstart, ystart, s); + }else{ +#endif + +#ifdef FIX_BUGS + if(Details.centre || Details.rightJustify) +#else + if(Details.centre) +#endif + x = 0.0f; + else + x = xstart; + y = ystart; + +#ifdef FIX_BUGS + float xEnd = Details.centre ? Details.centreSize : + Details.rightJustify ? xstart - Details.rightJustifyWrap : + Details.wrapX; +#else + float xEnd = (Details.centre ? Details.centreSize : Details.wrapX); +#endif + while(*s){ + if(x + GetStringWidth(s) > xEnd){ + // reached end of line + if(x > maxlength) + maxlength = x; +#ifdef FIX_BUGS + if(Details.centre || Details.rightJustify) +#else + if(Details.centre) +#endif + x = 0.0f; + else + x = xstart; + numLines++; + y += 32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY; + }else{ + // still space in current line + t = GetNextSpace(s); + if(*t == '\0'){ + // end of string + x += GetStringWidth(s); + if(x > maxlength) + maxlength = x; + numLines++; + s = t; + }else{ + x += GetStringWidth(s); + x += GetCharacterSize(*t - ' '); + s = t+1; + } + } + } +#ifdef MORE_LANGUAGES + } +#endif + + if(Details.centre){ + if(Details.backgroundOnlyText){ + rect->left = xstart - maxlength/2 - 4.0f; + rect->right = xstart + maxlength/2 + 4.0f; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) { + rect->bottom = (32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY) * numLines + ystart + (4.0f / 2.75f); + rect->top = ystart - (4.0f / 2.75f); + } else { +#endif + rect->bottom = (32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY) * numLines + ystart + 2.0f; + rect->top = ystart - 2.0f; +#ifdef MORE_LANGUAGES + } +#endif + }else{ + rect->left = xstart - Details.centreSize*0.5f - 4.0f; + rect->right = xstart + Details.centreSize*0.5f + 4.0f; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) { + rect->bottom = (32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY) * numLines + ystart + (4.0f / 2.75f); + rect->top = ystart - (4.0f / 2.75f); + } else { +#endif + rect->bottom = (32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY) * numLines + ystart + 2.0f; + rect->top = ystart - 2.0f; +#ifdef MORE_LANGUAGES + } +#endif + } + }else{ + rect->left = xstart - 4.0f; + rect->right = Details.wrapX; + // WTF? + rect->bottom = ystart - 4.0f + 4.0f; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) + rect->top = (32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY) * numLines + ystart + 2.0f + (4.0f / 2.75f); + else +#endif + rect->top = (32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY) * numLines + ystart + 2.0f + 2.0f; + } +} + +#ifdef MORE_LANGUAGES +bool +CFont::PrintString(float x, float y, wchar *start, wchar *&end, float spwidth, float japX) +{ + wchar *s, c, unused; + + if (IsJapanese()) { + float jx = 0.0f; + for (s = start; s < end; s++) { + if (*s == JAP_TERMINATION || *s == '~') + s = ParseToken(s, &unused, true); + if (NewLine) { + NewLine = false; + break; + } + jx += GetCharacterSize(*s - ' '); + } + s = start; + if (Details.centre) + x = japX - jx / 2.0f; + else if (Details.rightJustify) + x = japX - jx; + } + + for (s = start; s < end; s++) { + if (*s == '~' || (IsJapanese() && *s == JAP_TERMINATION)) + s = ParseToken(s, &unused); + if (NewLine && IsJapanese()) { + NewLine = false; + end = s; + return true; + } + c = *s - ' '; + if (Details.slant != 0.0f && !IsJapanese()) + y = (Details.slantRefX - x) * Details.slant + Details.slantRefY; + +#ifdef BUTTON_ICONS + if (PS2Symbol != BUTTON_NONE) { + DrawButton(x, y); + x += Details.scaleY * 17.0f; + PS2Symbol = BUTTON_NONE; + } +#endif + + PrintChar(x, y, c); + x += GetCharacterSize(c); + if (c == 0 && (!NewLine || !IsJapanese())) // space + x += spwidth; + } + return false; +} +#else +void +CFont::PrintString(float x, float y, wchar *start, wchar *end, float spwidth) +{ + wchar *s, c, unused; + + for(s = start; s < end; s++){ + if(*s == '~') + s = ParseToken(s, &unused); + c = *s - ' '; + if(Details.slant != 0.0f) + y = (Details.slantRefX - x)*Details.slant + Details.slantRefY; + PrintChar(x, y, c); + x += GetCharacterSize(c); + if(c == 0) // space + x += spwidth; + } +} +#endif + +void +CFont::PrintStringFromBottom(float x, float y, wchar *str) +{ +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) + y -= (32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY) * GetNumberLines(x, y, str); + else +#endif + y -= (32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY) * GetNumberLines(x, y, str); + PrintString(x, y, str); +} + +#ifdef XBOX_SUBTITLES +void +CFont::PrintOutlinedString(float x, float y, wchar *str, float outlineStrength, bool fromBottom, CRGBA outlineColor) +{ + CRGBA textColor = Details.color; + SetColor(outlineColor); + CVector2D offsets[] = { {1.f, 1.f}, {1.f, -1.f}, {-1.f, 1.f}, {-1.f, -1.f} }; + for(int i = 0; i < ARRAY_SIZE(offsets); i++){ + if (fromBottom) + PrintStringFromBottom(x + SCREEN_SCALE_X(offsets[i].x * outlineStrength), y + SCREEN_SCALE_Y(offsets[i].y * outlineStrength), str); + else + PrintString(x + SCREEN_SCALE_X(offsets[i].x * outlineStrength), y + SCREEN_SCALE_Y(offsets[i].y * outlineStrength), str); + } + SetColor(textColor); + + if (fromBottom) + PrintStringFromBottom(x, y, str); + else + PrintString(x, y, str); +} +#endif + +float +CFont::GetCharacterWidth(wchar c) +{ +#ifdef MORE_LANGUAGES + if (IsJapanese()) { + if (!Details.proportional) + return Size[0][Details.style][192]; + if (c <= 94 || Details.style == FONT_HEADING || Details.style == FONT_BANK) { + switch (Details.style) + { + case FONT_JAPANESE: + return Size_jp[c]; + default: + return Size[0][Details.style][c]; + } + } + if (c < 254 && Details.style == FONT_PAGER) + return 29.4f; + + switch (Details.style) + { + case FONT_JAPANESE: + return 29.4f; + case FONT_BANK: + return 10.0f; + case FONT_PAGER: + return 31.5f; + default: + return Size[0][Details.style][c]; + } + } + + else if (Details.proportional) + return Size[LanguageSet][Details.style][c]; + else + return Size[LanguageSet][Details.style][192]; +#else + if (Details.proportional) + return Size[Details.style][c]; + else + return Size[Details.style][192]; +#endif // MORE_LANGUAGES +} + +float +CFont::GetCharacterSize(wchar c) +{ +#ifdef MORE_LANGUAGES + + if (IsJapanese()) + { + if (!Details.proportional) + return Size[0][Details.style][192] * Details.scaleX; + if (c <= 94 || Details.style == FONT_HEADING || Details.style == FONT_BANK) { + switch (Details.style) + { + case FONT_JAPANESE: + return Size_jp[c] * Details.scaleX; + default: + return Size[0][Details.style][c] * Details.scaleX; + } + } + if (c < 254 && (Details.style == FONT_PAGER)) + return 29.4f * Details.scaleX; + + switch (Details.style) + { + case FONT_JAPANESE: + return 29.4f * Details.scaleX; + case FONT_BANK: + return 10.0f * Details.scaleX; + case FONT_PAGER: + return 31.5f * Details.scaleX; + default: + return Size[0][Details.style][c] * Details.scaleX; + } + } + else if(Details.proportional) + return Size[LanguageSet][Details.style][c] * Details.scaleX; + else + return Size[LanguageSet][Details.style][192] * Details.scaleX; +#else + if (Details.proportional) + return Size[Details.style][c] * Details.scaleX; + else + return Size[Details.style][192] * Details.scaleX; +#endif // MORE_LANGUAGES +} + +float +CFont::GetStringWidth(wchar *s, bool spaces) +{ + float w; + + w = 0.0f; +#ifdef MORE_LANGUAGES + if (IsJapanese()) + { + do + { + if ((*s != ' ' || spaces) && *s != '\0') { + do { + while (*s == '~' || *s == JAP_TERMINATION) { + s++; +#ifdef BUTTON_ICONS + switch (*s) { +#if 0 // unused + case 'U': + case 'D': + case '<': + case '>': +#endif + case 'X': + case 'O': + case 'Q': + case 'T': + case 'K': + case 'M': + case 'A': + case 'J': + case 'V': + case 'C': + w += 17.0f * Details.scaleY; + break; + default: + break; + } +#endif + while (!(*s == '~' || *s == JAP_TERMINATION)) s++; + s++; + } + w += GetCharacterSize(*s - ' '); + ++s; + } while (*s == '~' || *s == JAP_TERMINATION); + } + } while (IsAnsiCharacter(s)); + } else +#endif + { + for (; (*s != ' ' || spaces) && *s != '\0'; s++) { + if (*s == '~') { + s++; +#ifdef BUTTON_ICONS + switch (*s) { +#if 0 // unused + case 'U': + case 'D': + case '<': + case '>': +#endif + case 'X': + case 'O': + case 'Q': + case 'T': + case 'K': + case 'M': + case 'A': + case 'J': + case 'V': + case 'C': + w += 17.0f * Details.scaleY; + break; + default: + break; + } +#endif + while (*s != '~') s++; +#ifndef FIX_BUGS + s++; + if (*s == ' ' && !spaces) + break; + } +#else + } else +#endif + w += GetCharacterSize(*s - ' '); + } + } + return w; +} + +#ifdef MORE_LANGUAGES +float +CFont::GetStringWidth_Jap(wchar* s) +{ + float w; + + w = 0.0f; + for (; *s != '\0';) { + do { + while (*s == '~' || *s == JAP_TERMINATION) { + s++; + while (!(*s == '~' || *s == JAP_TERMINATION)) s++; + s++; + } + w += GetCharacterSize(*s - ' '); + ++s; + } while (*s == '~' || *s == JAP_TERMINATION); + } + return w; +} +#endif + +wchar* +CFont::GetNextSpace(wchar *s) +{ +#ifdef MORE_LANGUAGES + if (IsJapanese()) { + do + { + if (*s != ' ' && *s != '\0') { + do { + while (*s == '~' || *s == JAP_TERMINATION) { + s++; + while (!(*s == '~' || *s == JAP_TERMINATION)) s++; + s++; + } + ++s; + } while (*s == '~' || *s == JAP_TERMINATION); + } + } while (IsAnsiCharacter(s)); + } else +#endif + { + for(; *s != ' ' && *s != '\0'; s++) + if(*s == '~'){ + s++; + while(*s != '~') s++; +#ifndef FIX_BUGS + s++; + if(*s == ' ') + break; +#endif + } + } + return s; +} + +#ifdef MORE_LANGUAGES +wchar* +CFont::ParseToken(wchar *s, wchar* ss, bool japShit) +{ + s++; + if ((Details.color.r || Details.color.g || Details.color.b) && !japShit) { + wchar c = *s; + if (IsJapanese()) + c &= 0x7FFF; + switch (c) { + case 'N': + case 'n': + NewLine = true; + break; + case 'b': SetColor(CRGBA(128, 167, 243, 255)); break; + case 'g': SetColor(CRGBA(95, 160, 106, 255)); break; + case 'h': SetColor(CRGBA(225, 225, 225, 255)); break; + case 'l': SetColor(CRGBA(0, 0, 0, 255)); break; + case 'p': SetColor(CRGBA(168, 110, 252, 255)); break; + case 'r': SetColor(CRGBA(113, 43, 73, 255)); break; + case 'w': SetColor(CRGBA(175, 175, 175, 255)); break; + case 'y': SetColor(CRGBA(210, 196, 106, 255)); break; +#ifdef BUTTON_ICONS +#if 0 // unused + case 'U': PS2Symbol = BUTTON_UP; break; + case 'D': PS2Symbol = BUTTON_DOWN; break; + case '<': PS2Symbol = BUTTON_LEFT; break; + case '>': PS2Symbol = BUTTON_RIGHT; break; +#endif + case 'X': PS2Symbol = BUTTON_CROSS; break; + case 'O': PS2Symbol = BUTTON_CIRCLE; break; + case 'Q': PS2Symbol = BUTTON_SQUARE; break; + case 'T': PS2Symbol = BUTTON_TRIANGLE; break; + case 'K': PS2Symbol = BUTTON_L1; break; + case 'M': PS2Symbol = BUTTON_L2; break; + case 'A': PS2Symbol = BUTTON_L3; break; + case 'J': PS2Symbol = BUTTON_R1; break; + case 'V': PS2Symbol = BUTTON_R2; break; + case 'C': PS2Symbol = BUTTON_R3; break; +#endif + } + } else if (IsJapanese()) { + if ((*s & 0x7FFF) == 'N' || (*s & 0x7FFF) == 'n') + NewLine = true; + } + while ((!IsJapanese() || (*s != JAP_TERMINATION)) && *s != '~') s++; +#ifdef FIX_BUGS + if (*(++s) == '~') + s = ParseToken(s, ss, japShit); + return s; +#else + return s + 1; +#endif +} +#else +wchar* +CFont::ParseToken(wchar *s, wchar*) +{ + s++; + if(Details.color.r || Details.color.g || Details.color.b) + switch(*s){ + case 'N': + case 'n': + NewLine = true; + break; + case 'b': SetColor(CRGBA(128, 167, 243, 255)); break; + case 'g': SetColor(CRGBA(95, 160, 106, 255)); break; + case 'h': SetColor(CRGBA(225, 225, 225, 255)); break; + case 'l': SetColor(CRGBA(0, 0, 0, 255)); break; + case 'p': SetColor(CRGBA(168, 110, 252, 255)); break; + case 'r': SetColor(CRGBA(113, 43, 73, 255)); break; + case 'w': SetColor(CRGBA(175, 175, 175, 255)); break; + case 'y': SetColor(CRGBA(210, 196, 106, 255)); break; +#ifdef BUTTON_ICONS +#if 0 // unused + case 'U': PS2Symbol = BUTTON_UP; break; + case 'D': PS2Symbol = BUTTON_DOWN; break; + case '<': PS2Symbol = BUTTON_LEFT; break; + case '>': PS2Symbol = BUTTON_RIGHT; break; +#endif + case 'X': PS2Symbol = BUTTON_CROSS; break; + case 'O': PS2Symbol = BUTTON_CIRCLE; break; + case 'Q': PS2Symbol = BUTTON_SQUARE; break; + case 'T': PS2Symbol = BUTTON_TRIANGLE; break; + case 'K': PS2Symbol = BUTTON_L1; break; + case 'M': PS2Symbol = BUTTON_L2; break; + case 'A': PS2Symbol = BUTTON_L3; break; + case 'J': PS2Symbol = BUTTON_R1; break; + case 'V': PS2Symbol = BUTTON_R2; break; + case 'C': PS2Symbol = BUTTON_R3; break; +#endif + } + while(*s != '~') s++; + return s+1; +} +#endif + +void +CFont::DrawFonts(void) +{ + CSprite2d::DrawBank(Details.bank); + CSprite2d::DrawBank(Details.bank+1); + CSprite2d::DrawBank(Details.bank+2); +#ifdef MORE_LANGUAGES + if (IsJapanese()) + CSprite2d::DrawBank(Details.bank+3); +#endif +} + + +void +CFont::SetScale(float x, float y) +{ +#ifdef MORE_LANGUAGES + /*if (IsJapanese()) { + x *= 1.35f; + y *= 1.25f; + }*/ +#endif + Details.scaleX = x; + Details.scaleY = y; +} + +void +CFont::SetSlantRefPoint(float x, float y) +{ + Details.slantRefX = x; + Details.slantRefY = y; +} + +void +CFont::SetSlant(float s) +{ + Details.slant = s; +} + +void +CFont::SetColor(CRGBA col) +{ + Details.color = col; + if (Details.alphaFade < 255.0f) + Details.color.a *= Details.alphaFade / 255.0f; +} + +void +CFont::SetJustifyOn(void) +{ + Details.justify = true; + Details.centre = false; + Details.rightJustify = false; +} + +void +CFont::SetJustifyOff(void) +{ + Details.justify = false; + Details.rightJustify = false; +} + +void +CFont::SetCentreOn(void) +{ + Details.centre = true; + Details.justify = false; + Details.rightJustify = false; +} + +void +CFont::SetCentreOff(void) +{ + Details.centre = false; +} + +void +CFont::SetWrapx(float x) +{ + Details.wrapX = x; +} + +void +CFont::SetCentreSize(float s) +{ + Details.centreSize = s; +} + +void +CFont::SetBackgroundOn(void) +{ + Details.background = true; +} + +void +CFont::SetBackgroundOff(void) +{ + Details.background = false; +} + +void +CFont::SetBackgroundColor(CRGBA col) +{ + Details.backgroundColor = col; +} + +void +CFont::SetBackGroundOnlyTextOn(void) +{ + Details.backgroundOnlyText = true; +} + +void +CFont::SetBackGroundOnlyTextOff(void) +{ + Details.backgroundOnlyText = false; +} + +void +CFont::SetRightJustifyOn(void) +{ + Details.rightJustify = true; + Details.justify = false; + Details.centre = false; +} + +void +CFont::SetRightJustifyOff(void) +{ + Details.rightJustify = false; + Details.justify = false; + Details.centre = false; +} + +void +CFont::SetPropOn(void) +{ + Details.proportional = true; +} + +void +CFont::SetPropOff(void) +{ + Details.proportional = false; +} + +void +CFont::SetFontStyle(int16 style) +{ + Details.style = style; +} + +void +CFont::SetRightJustifyWrap(float wrap) +{ + Details.rightJustifyWrap = wrap; +} + +void +CFont::SetAlphaFade(float fade) +{ + Details.alphaFade = fade; +} + +void +CFont::SetDropColor(CRGBA col) +{ + Details.dropColor = col; + if (Details.alphaFade < 255.0f) + Details.dropColor.a *= Details.alphaFade / 255.0f; +} + +void +CFont::SetDropShadowPosition(int16 pos) +{ + Details.dropShadowPosition = pos; +} + +wchar +CFont::character_code(uint8 c) +{ + if(c < 128) + return c; + return foreign_table[c-128]; +} \ No newline at end of file diff --git a/src/renderer/Font.h b/src/renderer/Font.h new file mode 100644 index 0000000..9316ed3 --- /dev/null +++ b/src/renderer/Font.h @@ -0,0 +1,182 @@ +#pragma once + +#include "Sprite2d.h" + +void AsciiToUnicode(const char *src, wchar *dst); +void UnicodeStrcpy(wchar *dst, const wchar *src); +void UnicodeStrcat(wchar *dst, wchar *append); +int UnicodeStrlen(const wchar *str); + +struct CFontDetails +{ + CRGBA color; + float scaleX; + float scaleY; + float slant; + float slantRefX; + float slantRefY; + bool8 justify; + bool8 centre; + bool8 rightJustify; + bool8 background; + bool8 backgroundOnlyText; + bool8 proportional; + float alphaFade; + CRGBA backgroundColor; + float wrapX; + float centreSize; + float rightJustifyWrap; + int16 style; + int32 bank; + int16 dropShadowPosition; + CRGBA dropColor; +}; + +class CSprite2d; + +enum { + FONT_BANK, + FONT_PAGER, + FONT_HEADING, +#ifdef MORE_LANGUAGES + FONT_JAPANESE, +#endif + MAX_FONTS +}; + +enum { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT, +}; + +#ifdef MORE_LANGUAGES +enum +{ + FONT_LANGSET_EFIGS, + FONT_LANGSET_RUSSIAN, + FONT_LANGSET_POLISH, + FONT_LANGSET_JAPANESE, + LANGSET_MAX +}; + +#define FONT_LOCALE(style) (CFont::IsJapanese() ? FONT_JAPANESE : style) +#else +#define FONT_LOCALE(style) (style) +#endif + +#ifdef BUTTON_ICONS +enum +{ + BUTTON_NONE = -1, +#if 0 // unused + BUTTON_UP, + BUTTON_DOWN, + BUTTON_LEFT, + BUTTON_RIGHT, +#endif + BUTTON_CROSS, + BUTTON_CIRCLE, + BUTTON_SQUARE, + BUTTON_TRIANGLE, + BUTTON_L1, + BUTTON_L2, + BUTTON_L3, + BUTTON_R1, + BUTTON_R2, + BUTTON_R3, + MAX_BUTTON_ICONS +}; +#endif // BUTTON_ICONS + + +class CFont +{ +#ifdef MORE_LANGUAGES + static int16 Size[LANGSET_MAX][MAX_FONTS][193]; + static uint8 LanguageSet; + static int32 Slot; +#else + static int16 Size[MAX_FONTS][193]; +#endif + static bool16 NewLine; +public: + static CSprite2d Sprite[MAX_FONTS]; + static CFontDetails Details; + +#ifdef BUTTON_ICONS + static int32 ButtonsSlot; + static CSprite2d ButtonSprite[MAX_BUTTON_ICONS]; + static int PS2Symbol; + + static void LoadButtons(const char *txdPath); + static void DrawButton(float x, float y); +#endif // BUTTON_ICONS + + + static void Initialise(void); + static void Shutdown(void); + static void InitPerFrame(void); + static void PrintChar(float x, float y, wchar c); + static void PrintString(float x, float y, wchar *s); + static void PrintStringFromBottom(float x, float y, wchar *str); +#ifdef XBOX_SUBTITLES + static void PrintOutlinedString(float x, float y, wchar *str, float outlineStrength, bool fromBottom, CRGBA outlineColor); +#endif + static int GetNumberLines(float xstart, float ystart, wchar *s); + static void GetTextRect(CRect *rect, float xstart, float ystart, wchar *s); +#ifdef MORE_LANGUAGES + static bool PrintString(float x, float y, wchar *start, wchar* &end, float spwidth, float japX); +#else + static void PrintString(float x, float y, wchar *start, wchar *end, float spwidth); +#endif + static float GetCharacterWidth(wchar c); + static float GetCharacterSize(wchar c); + static float GetStringWidth(wchar *s, bool spaces = false); +#ifdef MORE_LANGUAGES + static float GetStringWidth_Jap(wchar* s); +#endif + static uint16 *GetNextSpace(wchar *s); +#ifdef MORE_LANGUAGES + static uint16 *ParseToken(wchar *s, wchar*, bool japShit = false); +#else + static uint16 *ParseToken(wchar *s, wchar*); +#endif + static void DrawFonts(void); + static uint16 character_code(uint8 c); + + static void SetScale(float x, float y); + static void SetSlantRefPoint(float x, float y); + static void SetSlant(float s); + static void SetJustifyOn(void); + static void SetJustifyOff(void); + static void SetRightJustifyOn(void); + static void SetRightJustifyOff(void); + static void SetCentreOn(void); + static void SetCentreOff(void); + static void SetWrapx(float x); + static void SetCentreSize(float s); + static void SetBackgroundOn(void); + static void SetBackgroundOff(void); + static void SetBackGroundOnlyTextOn(void); + static void SetBackGroundOnlyTextOff(void); + static void SetPropOn(void); + static void SetPropOff(void); + static void SetFontStyle(int16 style); + static void SetRightJustifyWrap(float wrap); + static void SetAlphaFade(float fade); + static void SetDropShadowPosition(int16 pos); + static void SetBackgroundColor(CRGBA col); + static void SetColor(CRGBA col); + static void SetDropColor(CRGBA col); + +#ifdef MORE_LANGUAGES + static void ReloadFonts(uint8 set); + + // japanese stuff + static bool IsAnsiCharacter(wchar* s); + static bool IsJapanesePunctuation(wchar* str); + static bool IsJapanese() { return LanguageSet == FONT_LANGSET_JAPANESE; } + static bool IsJapaneseFont() { return IsJapanese() && (Details.style == FONT_JAPANESE || Details.style == FONT_PAGER); } +#endif +}; diff --git a/src/renderer/Glass.cpp b/src/renderer/Glass.cpp new file mode 100644 index 0000000..cc45648 --- /dev/null +++ b/src/renderer/Glass.cpp @@ -0,0 +1,719 @@ +#include "common.h" + +#include "Glass.h" +#include "Timer.h" +#include "Object.h" +#include "General.h" +#include "AudioScriptObject.h" +#include "World.h" +#include "Timecycle.h" +#include "Particle.h" +#include "Camera.h" +#include "RenderBuffer.h" +#include "Shadows.h" +#include "ModelIndices.h" +#include "main.h" +#include "soundlist.h" + + +uint32 CGlass::NumGlassEntities; +CEntity *CGlass::apEntitiesToBeRendered[NUM_GLASSENTITIES]; +CFallingGlassPane CGlass::aGlassPanes[NUM_GLASSPANES]; + + +CVector2D CentersWithTriangle[NUM_GLASSTRIANGLES]; +const CVector2D CoorsWithTriangle[NUM_GLASSTRIANGLES][3] = +{ + { + CVector2D(0.0f, 0.0f), + CVector2D(0.0f, 1.0f), + CVector2D(0.4f, 0.5f) + }, + + { + CVector2D(0.0f, 1.0f), + CVector2D(1.0f, 1.0f), + CVector2D(0.4f, 0.5f) + }, + + { + CVector2D(0.0f, 0.0f), + CVector2D(0.4f, 0.5f), + CVector2D(0.7f, 0.0f) + }, + + { + CVector2D(0.7f, 0.0f), + CVector2D(0.4f, 0.5f), + CVector2D(1.0f, 1.0f) + }, + + { + CVector2D(0.7f, 0.0f), + CVector2D(1.0f, 1.0f), + CVector2D(1.0f, 0.0f) + } +}; + +#define TEMPBUFFERVERTHILIGHTOFFSET 0 +#define TEMPBUFFERINDEXHILIGHTOFFSET 0 +#define TEMPBUFFERVERTHILIGHTSIZE 128 +#define TEMPBUFFERINDEXHILIGHTSIZE 512 + +#define TEMPBUFFERVERTSHATTEREDOFFSET TEMPBUFFERVERTHILIGHTSIZE +#define TEMPBUFFERINDEXSHATTEREDOFFSET TEMPBUFFERINDEXHILIGHTSIZE +#define TEMPBUFFERVERTSHATTEREDSIZE 192 +#define TEMPBUFFERINDEXSHATTEREDSIZE 768 + +#define TEMPBUFFERVERTREFLECTIONOFFSET TEMPBUFFERVERTSHATTEREDSIZE +#define TEMPBUFFERINDEXREFLECTIONOFFSET TEMPBUFFERINDEXSHATTEREDSIZE +#define TEMPBUFFERVERTREFLECTIONSIZE 256 +#define TEMPBUFFERINDEXREFLECTIONSIZE 1024 + +int32 TempBufferIndicesStoredHiLight = 0; +int32 TempBufferVerticesStoredHiLight = 0; +int32 TempBufferIndicesStoredShattered = 0; +int32 TempBufferVerticesStoredShattered = 0; +int32 TempBufferIndicesStoredReflection = 0; +int32 TempBufferVerticesStoredReflection = 0; + +void +CFallingGlassPane::Update(void) +{ + if ( CTimer::GetTimeInMilliseconds() >= m_nTimer ) + { + // Apply MoveSpeed + GetPosition() += m_vecMoveSpeed * CTimer::GetTimeStep(); + + // Apply Gravity + m_vecMoveSpeed.z -= 0.02f * CTimer::GetTimeStep(); + + // Apply TurnSpeed + GetRight() += CrossProduct(m_vecTurn, GetRight()); + GetForward() += CrossProduct(m_vecTurn, GetForward()); + GetUp() += CrossProduct(m_vecTurn, GetUp()); + + if ( GetPosition().z < m_fGroundZ ) + { + CVector pos; + CVector dir; + + m_bActive = false; + + pos = CVector(GetPosition().x, GetPosition().y, m_fGroundZ); + + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_LIGHT_BREAK, pos); + + RwRGBA color = { 255, 255, 255, 255 }; + + static int32 nFrameGen = 0; + + for ( int32 i = 0; i < 4; i++ ) + { + dir.x = CGeneral::GetRandomNumberInRange(-0.35f, 0.35f); + dir.y = CGeneral::GetRandomNumberInRange(-0.35f, 0.35f); + dir.z = CGeneral::GetRandomNumberInRange(0.05f, 0.20f); + + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, + pos, + dir, + nil, + CGeneral::GetRandomNumberInRange(0.02f, 0.2f), + color, + CGeneral::GetRandomNumberInRange(-40, 40), + 0, + ++nFrameGen & 3, + 500); + } + } + } +} + +void +CFallingGlassPane::Render(void) +{ + float distToCamera = (TheCamera.GetPosition() - GetPosition()).Magnitude(); + + CVector fwdNorm = GetForward(); + fwdNorm.Normalise(); + uint8 alpha = CGlass::CalcAlphaWithNormal(&fwdNorm); + +#ifdef FIX_BUGS + uint16 time = Clamp(CTimer::GetTimeInMilliseconds() > m_nTimer ? CTimer::GetTimeInMilliseconds() - m_nTimer : 0u, 0u, 500u); +#else + uint16 time = Clamp(CTimer::GetTimeInMilliseconds() - m_nTimer, 0, 500); +#endif + + uint8 color = int32( float(alpha) * (float(time) / 500) ); + + if ( TempBufferIndicesStoredHiLight >= TEMPBUFFERINDEXHILIGHTSIZE-7 || TempBufferVerticesStoredHiLight >= TEMPBUFFERVERTHILIGHTSIZE-4 ) + CGlass::RenderHiLightPolys(); + + // HiLight Polys + + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 0], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 1], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 2], color, color, color, color); + + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 0], 0.5f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 0], 0.5f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 1], 0.5f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 1], 0.6f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 2], 0.6f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 2], 0.6f); + + ASSERT(m_nTriIndex < NUM_GLASSTRIANGLES); + + CVector2D p0 = CoorsWithTriangle[m_nTriIndex][0] - CentersWithTriangle[m_nTriIndex]; + CVector2D p1 = CoorsWithTriangle[m_nTriIndex][1] - CentersWithTriangle[m_nTriIndex]; + CVector2D p2 = CoorsWithTriangle[m_nTriIndex][2] - CentersWithTriangle[m_nTriIndex]; + CVector v0 = *this * CVector(p0.x, 0.0f, p0.y); + CVector v1 = *this * CVector(p1.x, 0.0f, p1.y); + CVector v2 = *this * CVector(p2.x, 0.0f, p2.y); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 0], v0.x, v0.y, v0.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 1], v1.x, v1.y, v1.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 2], v2.x, v2.y, v2.z); + + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 0] = TempBufferVerticesStoredHiLight + 0; + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 1] = TempBufferVerticesStoredHiLight + 1; + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 2] = TempBufferVerticesStoredHiLight + 2; + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 3] = TempBufferVerticesStoredHiLight + 0; + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 4] = TempBufferVerticesStoredHiLight + 2; + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 5] = TempBufferVerticesStoredHiLight + 1; + + TempBufferVerticesStoredHiLight += 3; + TempBufferIndicesStoredHiLight += 6; + + if ( m_bShattered ) + { + if ( TempBufferIndicesStoredShattered >= TEMPBUFFERINDEXSHATTEREDSIZE-7 || TempBufferVerticesStoredShattered >= TEMPBUFFERVERTSHATTEREDSIZE-4 ) + CGlass::RenderShatteredPolys(); + + uint8 shatteredColor = 255; + if ( distToCamera > 30.0f ) + shatteredColor = int32((1.0f - (distToCamera - 30.0f) * 4.0f / 40.0f) * 255); + + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], shatteredColor, shatteredColor, shatteredColor, shatteredColor); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], shatteredColor, shatteredColor, shatteredColor, shatteredColor); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], shatteredColor, shatteredColor, shatteredColor, shatteredColor); + + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], 4.0f * CoorsWithTriangle[m_nTriIndex][0].x * m_fStep); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], 4.0f * CoorsWithTriangle[m_nTriIndex][0].y * m_fStep); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], 4.0f * CoorsWithTriangle[m_nTriIndex][1].x * m_fStep); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], 4.0f * CoorsWithTriangle[m_nTriIndex][1].y * m_fStep); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], 4.0f * CoorsWithTriangle[m_nTriIndex][2].x * m_fStep); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], 4.0f * CoorsWithTriangle[m_nTriIndex][2].y * m_fStep); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], v0.x, v0.y, v0.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], v1.x, v1.y, v1.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], v2.x, v2.y, v2.z); + + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 0] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 0; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 1] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 1; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 2] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 2; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 3] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 0; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 4] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 2; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 5] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 1; + + TempBufferIndicesStoredShattered += 6; + TempBufferVerticesStoredShattered += 3; + } +} + +void +CGlass::Init(void) +{ + for ( int32 i = 0; i < NUM_GLASSPANES; i++ ) + aGlassPanes[i].m_bActive = false; + + for ( int32 i = 0; i < NUM_GLASSTRIANGLES; i++ ) + CentersWithTriangle[i] = (CoorsWithTriangle[i][0] + CoorsWithTriangle[i][1] + CoorsWithTriangle[i][2]) / 3; +} + +void +CGlass::Update(void) +{ + for ( int32 i = 0; i < NUM_GLASSPANES; i++ ) + { + if ( aGlassPanes[i].m_bActive ) + aGlassPanes[i].Update(); + } +} + +void +CGlass::Render(void) +{ + TempBufferVerticesStoredHiLight = 0; + TempBufferIndicesStoredHiLight = 0; + + TempBufferVerticesStoredShattered = TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferIndicesStoredShattered = TEMPBUFFERINDEXSHATTEREDOFFSET; + + TempBufferVerticesStoredReflection = TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferIndicesStoredReflection = TEMPBUFFERINDEXREFLECTIONOFFSET; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGCOLOR, (void *)RWRGBALONG(CTimeCycle::GetFogRed(), CTimeCycle::GetFogGreen(), CTimeCycle::GetFogBlue(), 255)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + + PUSH_RENDERGROUP("CGlass::Render"); + + for ( int32 i = 0; i < NUM_GLASSPANES; i++ ) + { + if ( aGlassPanes[i].m_bActive ) + aGlassPanes[i].Render(); + } + + for ( uint32 i = 0; i < NumGlassEntities; i++ ) + RenderEntityInGlass(apEntitiesToBeRendered[i]); + + POP_RENDERGROUP(); + + NumGlassEntities = 0; + + RenderHiLightPolys(); + RenderShatteredPolys(); + RenderReflectionPolys(); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE); +} + +CFallingGlassPane * +CGlass::FindFreePane(void) +{ + for ( int32 i = 0; i < NUM_GLASSPANES; i++ ) + { + if ( !aGlassPanes[i].m_bActive ) + return &aGlassPanes[i]; + } + + return nil; +} + +void +CGlass::GeneratePanesForWindow(uint32 type, CVector pos, CVector up, CVector right, CVector speed, CVector point, + float moveSpeed, bool cracked, bool explosion) +{ + float upLen = up.Magnitude(); + float rightLen = right.Magnitude(); + + float upSteps = upLen + 0.75f; + if ( upSteps < 1.0f ) upSteps = 1.0f; + + float rightSteps = rightLen + 0.75f; + if ( rightSteps < 1.0f ) rightSteps = 1.0f; + + uint32 ysteps = (uint32)upSteps; + if ( ysteps > 3 ) ysteps = 3; + + uint32 xsteps = (uint32)rightSteps; + if ( xsteps > 3 ) xsteps = 3; + + if ( explosion ) + { + if ( ysteps > 1 ) ysteps = 1; + if ( xsteps > 1 ) xsteps = 1; + } + + float upScl = upLen / float(ysteps); + float rightScl = rightLen / float(xsteps); + + bool bZFound; + float groundZ = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &bZFound); + if ( !bZFound ) groundZ = pos.z - 2.0f; + + for ( uint32 y = 0; y < ysteps; y++ ) + { + for ( uint32 x = 0; x < xsteps; x++ ) + { + float stepy = float(y) * upLen / float(ysteps); + float stepx = float(x) * rightLen / float(xsteps); + + for ( int32 i = 0; i < NUM_GLASSTRIANGLES; i++ ) + { + CFallingGlassPane *pane = FindFreePane(); + if ( pane ) + { + pane->m_nTriIndex = i; + + pane->GetRight() = (right * rightScl) / rightLen; +#ifdef FIX_BUGS + pane->GetUp() = (up * upScl) / upLen; +#else + pane->GetUp() = (up * upScl) / rightLen; // copypaste bug +#endif + CVector fwd = CrossProduct(pane->GetRight(), pane->GetUp()); + fwd.Normalise(); + + pane->GetForward() = fwd; + + pane->GetPosition() = right / rightLen * (rightScl * CentersWithTriangle[i].x + stepx) + + up / upLen * (upScl * CentersWithTriangle[i].y + stepy) + + pos; + + pane->m_vecMoveSpeed.x = float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0015f + speed.x; + pane->m_vecMoveSpeed.y = float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0015f + speed.y; + pane->m_vecMoveSpeed.z = 0.0f + speed.z; + + if ( moveSpeed != 0.0f ) + { + CVector dist = pane->GetPosition() - point; + dist.Normalise(); + + pane->m_vecMoveSpeed += moveSpeed * dist; + } + + pane->m_vecTurn.x = float((CGeneral::GetRandomNumber() & 127) - 64) * 0.002f; + pane->m_vecTurn.y = float((CGeneral::GetRandomNumber() & 127) - 64) * 0.002f; + pane->m_vecTurn.z = float((CGeneral::GetRandomNumber() & 127) - 64) * 0.002f; + + switch ( type ) + { + case 0: + pane->m_nTimer = CTimer::GetTimeInMilliseconds(); + break; + case 1: + float dist = (pane->GetPosition() - point).Magnitude(); + pane->m_nTimer = uint32(dist*100 + CTimer::GetTimeInMilliseconds()); + break; + } + + pane->m_fGroundZ = groundZ; + pane->m_bShattered = cracked; + pane->m_fStep = upLen / float(ysteps); + pane->m_bActive = true; + } + } + } + } +} + +void +CGlass::AskForObjectToBeRenderedInGlass(CEntity *entity) +{ +#ifdef FIX_BUGS + if ( NumGlassEntities < NUM_GLASSENTITIES ) +#else + if ( NumGlassEntities < NUM_GLASSENTITIES-1 ) +#endif + { + apEntitiesToBeRendered[NumGlassEntities++] = entity; + } +} + +void +CGlass::RenderEntityInGlass(CEntity *entity) +{ + ASSERT(entity!=nil); + CObject *object = (CObject *)entity; + + if ( object->bGlassBroken ) + return; + + float distToCamera = (TheCamera.GetPosition() - object->GetPosition()).Magnitude(); + + if ( distToCamera > 40.0f ) + return; + + CVector fwdNorm = object->GetForward(); + fwdNorm.Normalise(); + uint8 alpha = CalcAlphaWithNormal(&fwdNorm); + + CColModel *col = object->GetColModel(); + ASSERT(col!=nil); + if ( col->numTriangles >= 2 ) + { + CVector a = object->GetMatrix() * col->vertices[0].Get(); + CVector b = object->GetMatrix() * col->vertices[1].Get(); + CVector c = object->GetMatrix() * col->vertices[2].Get(); + CVector d = object->GetMatrix() * col->vertices[3].Get(); + + if ( object->bGlassCracked ) + { + uint8 color = 255; + if ( distToCamera > 30.0f ) + color = int32((1.0f - (distToCamera - 30.0f) * 4.0f / 40.0f) * 255); + + if ( TempBufferIndicesStoredShattered >= TEMPBUFFERINDEXSHATTEREDSIZE-13 || TempBufferVerticesStoredShattered >= TEMPBUFFERVERTSHATTEREDSIZE-5 ) + RenderShatteredPolys(); + + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 3], color, color, color, color); + + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], 0.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], 0.0f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], 16.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], 0.0f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], 0.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], 16.0f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 3], 16.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 3], 16.0f); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], a.x, a.y, a.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], b.x, b.y, b.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], c.x, c.y, c.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 3], d.x, d.y, d.z); + + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 0] = col->triangles[0].a + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 1] = col->triangles[0].b + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 2] = col->triangles[0].c + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 3] = col->triangles[1].a + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 4] = col->triangles[1].b + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 5] = col->triangles[1].c + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 6] = col->triangles[0].a + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 7] = col->triangles[0].c + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 8] = col->triangles[0].b + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 9] = col->triangles[1].a + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 10] = col->triangles[1].c + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 11] = col->triangles[1].b + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + + TempBufferIndicesStoredShattered += 12; + TempBufferVerticesStoredShattered += 4; + } + + if ( TempBufferIndicesStoredReflection >= TEMPBUFFERINDEXREFLECTIONSIZE-13 || TempBufferVerticesStoredReflection >= TEMPBUFFERVERTREFLECTIONSIZE-5 ) + RenderReflectionPolys(); + + uint8 color = 100; + if ( distToCamera > 30.0f ) + color = int32((1.0f - (distToCamera - 30.0f) * 4.0f / 40.0f) * 100); + + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 0], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 1], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 2], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 3], color, color, color, color); + + float FwdAngle = CGeneral::GetATanOfXY(TheCamera.GetForward().x, TheCamera.GetForward().y); + float v = 2.0f * TheCamera.GetForward().z * 0.2f; + float u = float(object->m_randomSeed & 15) * 0.02f + (FwdAngle / TWOPI); + + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 0], u); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 0], v); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 1], u+0.2f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 1], v); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 2], u); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 2], v+0.2f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 3], u+0.2f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 3], v+0.2f); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 0], a.x, a.y, a.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 1], b.x, b.y, b.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 2], c.x, c.y, c.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 3], d.x, d.y, d.z); + + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 0] = col->triangles[0].a + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 1] = col->triangles[0].b + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 2] = col->triangles[0].c + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 3] = col->triangles[1].a + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 4] = col->triangles[1].b + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 5] = col->triangles[1].c + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 6] = col->triangles[0].a + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 7] = col->triangles[0].c + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 8] = col->triangles[0].b + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 9] = col->triangles[1].a + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 10] = col->triangles[1].c + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 11] = col->triangles[1].b + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + + TempBufferIndicesStoredReflection += 12; + TempBufferVerticesStoredReflection += 4; + } +} + +int32 +CGlass::CalcAlphaWithNormal(CVector *normal) +{ + ASSERT(normal!=nil); + + float fwdDir = 2.0f * DotProduct(*normal, TheCamera.GetForward()); + float fwdDot = DotProduct(TheCamera.GetForward()-fwdDir*(*normal), CVector(0.57f, 0.57f, -0.57f)); + return int32(lerp(fwdDot*fwdDot*fwdDot*fwdDot*fwdDot*fwdDot, 20.0f, 255.0f)); +} + +void +CGlass::RenderHiLightPolys(void) +{ + if ( TempBufferVerticesStoredHiLight != TEMPBUFFERVERTHILIGHTOFFSET ) + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)RwTextureGetRaster(gpShadowExplosionTex)); + + LittleTest(); + + if ( RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStoredHiLight, nil, rwIM3D_VERTEXUV) ) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStoredHiLight); + RwIm3DEnd(); + } + + TempBufferVerticesStoredHiLight = TEMPBUFFERVERTHILIGHTOFFSET; + TempBufferIndicesStoredHiLight = TEMPBUFFERINDEXHILIGHTOFFSET; + } +} + +void +CGlass::RenderShatteredPolys(void) +{ + if ( TempBufferVerticesStoredShattered != TEMPBUFFERVERTSHATTEREDOFFSET ) + { + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)RwTextureGetRaster(gpCrackedGlassTex)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + LittleTest(); + + if ( RwIm3DTransform(&TempBufferRenderVertices[TEMPBUFFERVERTSHATTEREDOFFSET], TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET, nil, rwIM3D_VERTEXUV) ) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, &TempBufferRenderIndexList[TEMPBUFFERINDEXSHATTEREDOFFSET], TempBufferIndicesStoredShattered - TEMPBUFFERINDEXSHATTEREDOFFSET); + RwIm3DEnd(); + } + + TempBufferIndicesStoredShattered = TEMPBUFFERINDEXSHATTEREDOFFSET; + TempBufferVerticesStoredShattered = TEMPBUFFERVERTSHATTEREDOFFSET; + } +} + +void +CGlass::RenderReflectionPolys(void) +{ + if ( TempBufferVerticesStoredReflection != TEMPBUFFERVERTREFLECTIONOFFSET ) + { + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)RwTextureGetRaster(gpShadowHeadLightsTex)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + LittleTest(); + + if ( RwIm3DTransform(&TempBufferRenderVertices[TEMPBUFFERVERTREFLECTIONOFFSET], TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET, nil, rwIM3D_VERTEXUV) ) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, &TempBufferRenderIndexList[TEMPBUFFERINDEXREFLECTIONOFFSET], TempBufferIndicesStoredReflection - TEMPBUFFERINDEXREFLECTIONOFFSET); + RwIm3DEnd(); + } + + TempBufferIndicesStoredReflection = TEMPBUFFERINDEXREFLECTIONOFFSET; + TempBufferVerticesStoredReflection = TEMPBUFFERVERTREFLECTIONOFFSET; + } +} + +void +CGlass::WindowRespondsToCollision(CEntity *entity, float amount, CVector speed, CVector point, bool explosion) +{ + ASSERT(entity!=nil); + + CObject *object = (CObject *)entity; + + if ( object->bGlassBroken ) + return; + + object->bGlassCracked = true; + + CColModel *col = object->GetColModel(); + ASSERT(col!=nil); + + CVector a = object->GetMatrix() * col->vertices[0].Get(); + CVector b = object->GetMatrix() * col->vertices[1].Get(); + CVector c = object->GetMatrix() * col->vertices[2].Get(); + CVector d = object->GetMatrix() * col->vertices[3].Get(); + + float minx = Min(Min(a.x, b.x), Min(c.x, d.x)); + float maxx = Max(Max(a.x, b.x), Max(c.x, d.x)); + float miny = Min(Min(a.y, b.y), Min(c.y, d.y)); + float maxy = Max(Max(a.y, b.y), Max(c.y, d.y)); + float minz = Min(Min(a.z, b.z), Min(c.z, d.z)); + float maxz = Max(Max(a.z, b.z), Max(c.z, d.z)); + + + if ( amount > 300.0f ) + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_BREAK_L, object->GetPosition()); + + GeneratePanesForWindow(0, + CVector(minx, miny, minz), + CVector(0.0f, 0.0f, maxz-minz), + CVector(maxx-minx, maxy-miny, 0.0f), + speed, point, 0.1f, !!object->bGlassCracked, explosion); + } + else + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_BREAK_S, object->GetPosition()); + + GeneratePanesForWindow(1, + CVector(minx, miny, minz), + CVector(0.0f, 0.0f, maxz-minz), + CVector(maxx-minx, maxy-miny, 0.0f), + speed, point, 0.1f, !!object->bGlassCracked, explosion); + } + + object->bGlassBroken = true; + object->GetMatrix().GetPosition().z = -100.0f; +} + +void +CGlass::WindowRespondsToSoftCollision(CEntity *entity, float amount) +{ + ASSERT(entity!=nil); + + CObject *object = (CObject *)entity; + + if ( amount > 50.0f && !object->bGlassCracked ) + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_CRACK, object->GetPosition()); + object->bGlassCracked = true; + } +} + +void +CGlass::WasGlassHitByBullet(CEntity *entity, CVector point) +{ + ASSERT(entity!=nil); + + CObject *object = (CObject *)entity; + + if ( IsGlass(object->GetModelIndex()) ) + { + if ( !object->bGlassCracked ) + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_CRACK, object->GetPosition()); + object->bGlassCracked = true; + } + else + { + if ( (CGeneral::GetRandomNumber() & 3) == 2 ) + WindowRespondsToCollision(object, 0.0f, CVector(0.0f, 0.0f, 0.0f), point, false); + } + } +} + +void +CGlass::WindowRespondsToExplosion(CEntity *entity, CVector point) +{ + ASSERT(entity!=nil); + + CObject *object = (CObject *)entity; + + CVector distToGlass = object->GetPosition() - point; + + float fDistToGlass = distToGlass.Magnitude(); + + if ( fDistToGlass < 10.0f ) + { + distToGlass *= (0.3f / fDistToGlass); // normalise + WindowRespondsToCollision(object, 10000.0f, distToGlass, object->GetPosition(), true); + } + else + { + if ( fDistToGlass < 30.0f ) + object->bGlassCracked = true; + } +} diff --git a/src/renderer/Glass.h b/src/renderer/Glass.h new file mode 100644 index 0000000..51c5aae --- /dev/null +++ b/src/renderer/Glass.h @@ -0,0 +1,52 @@ +#pragma once + +class CEntity; + +class CFallingGlassPane : public CMatrix +{ +public: + CVector m_vecMoveSpeed; + CVector m_vecTurn; + uint32 m_nTimer; + float m_fGroundZ; + float m_fStep; + uint8 m_nTriIndex; + bool m_bActive; + bool m_bShattered; + + CFallingGlassPane() { } + ~CFallingGlassPane() { } + + void Update(void); + void Render(void); +}; + +VALIDATE_SIZE(CFallingGlassPane, 0x70); + +enum +{ + NUM_GLASSTRIANGLES = 5, +}; + +class CGlass +{ + static uint32 NumGlassEntities; + static CEntity *apEntitiesToBeRendered[NUM_GLASSENTITIES]; + static CFallingGlassPane aGlassPanes[NUM_GLASSPANES]; +public: + static void Init(void); + static void Update(void); + static void Render(void); + static CFallingGlassPane *FindFreePane(void); + static void GeneratePanesForWindow(uint32 type, CVector pos, CVector up, CVector right, CVector speed, CVector point, float moveSpeed, bool cracked, bool explosion); + static void AskForObjectToBeRenderedInGlass(CEntity *entity); + static void RenderEntityInGlass(CEntity *entity); + static int32 CalcAlphaWithNormal(CVector *normal); + static void RenderHiLightPolys(void); + static void RenderShatteredPolys(void); + static void RenderReflectionPolys(void); + static void WindowRespondsToCollision(CEntity *entity, float amount, CVector speed, CVector point, bool explosion); + static void WindowRespondsToSoftCollision(CEntity *entity, float amount); + static void WasGlassHitByBullet(CEntity *entity, CVector point); + static void WindowRespondsToExplosion(CEntity *entity, CVector point); +}; \ No newline at end of file diff --git a/src/renderer/Hud.cpp b/src/renderer/Hud.cpp new file mode 100644 index 0000000..bba8c52 --- /dev/null +++ b/src/renderer/Hud.cpp @@ -0,0 +1,1713 @@ +#include "common.h" + +#include "Camera.h" +#include "DMAudio.h" +#include "Clock.h" +#include "Darkel.h" +#include "Hud.h" +#include "Messages.h" +#include "Frontend.h" +#include "Font.h" +#include "Pad.h" +#include "Radar.h" +#include "Replay.h" +#include "Wanted.h" +#include "Sprite.h" +#include "Sprite2d.h" +#include "Text.h" +#include "Timer.h" +#include "Script.h" +#include "TxdStore.h" +#include "User.h" +#include "World.h" + +#ifdef PS2_HUD +#define MONEY_X 100.0f +#define WEAPON_X 91.0f +#define AMMO_X 59.0f +#define HEALTH_X 100.0f +#define STARS_X 49.0f +#define ZONE_Y 61.0f +#define VEHICLE_Y 81.0f +#define CLOCK_X 101.0f +#define SUBS_Y 83.0f +#define WASTEDBUSTED_Y 122.0f +#define BIGMESSAGE_Y 80.0f +#else +#define MONEY_X 110.0f +#define WEAPON_X 99.0f +#define AMMO_X 66.0f +#define HEALTH_X 110.0f +#define STARS_X 60.0f +#define ZONE_Y 30.0f +#define VEHICLE_Y 55.0f +#define CLOCK_X 111.0f +#define SUBS_Y 68.0f +#define WASTEDBUSTED_Y 82.0f +#define BIGMESSAGE_Y 84.0f +#endif + +#ifdef FIX_BUGS +#define TIMER_RIGHT_OFFSET 34.0f // Taken from VC frenzy timer +#define BIGMESSAGE_Y_OFFSET 18.0f +#else +#define TIMER_RIGHT_OFFSET 27.0f +#define BIGMESSAGE_Y_OFFSET 20.0f +#endif + +#if defined(PS2_HUD) && !defined(FIX_BUGS) + #define SCREEN_SCALE_X_PC(a) (a) + #define SCREEN_SCALE_Y_PC(a) (a) + #define SCALE_AND_CENTER_X_PC(a) (a) +#else + #define SCREEN_SCALE_X_PC(a) SCREEN_SCALE_X(a) + #define SCREEN_SCALE_Y_PC(a) SCREEN_SCALE_Y(a) + #define SCALE_AND_CENTER_X_PC(a) SCALE_AND_CENTER_X(a) +#endif + +#if defined(FIX_BUGS) + #define SCREEN_SCALE_X_FIX(a) SCREEN_SCALE_X(a) + #define SCREEN_SCALE_Y_FIX(a) SCREEN_SCALE_Y(a) + #define SCALE_AND_CENTER_X_FIX(a) SCALE_AND_CENTER_X(a) +#else + #define SCREEN_SCALE_X_FIX(a) (a) + #define SCREEN_SCALE_Y_FIX(a) (a) + #define SCALE_AND_CENTER_X_FIX(a) (a) +#endif + +#ifdef FIX_BUGS +#define FRAMECOUNTER CTimer::GetLogicalFrameCounter() +#else +#define FRAMECOUNTER CTimer::GetFrameCounter() +#endif + +// Game has colors inlined in code. +// For easier modification we collect them here: +CRGBA MONEY_COLOR(89, 115, 150, 255); +CRGBA AMMO_COLOR(0, 0, 0, 255); +CRGBA HEALTH_COLOR(186, 101, 50, 255); +CRGBA ARMOUR_COLOR(124, 140, 95, 255); +CRGBA WANTED_COLOR(193, 164, 120, 255); +CRGBA ZONE_COLOR(152, 154, 82, 255); +CRGBA VEHICLE_COLOR(194, 165, 120, 255); +CRGBA CLOCK_COLOR(194, 165, 120, 255); +CRGBA TIMER_COLOR(186, 101, 50, 255); +CRGBA COUNTER_COLOR(0, 106, 164, 255); +CRGBA PAGER_COLOR(32, 162, 66, 205); +CRGBA RADARDISC_COLOR(0, 0, 0, 255); +CRGBA BIGMESSAGE_COLOR(85, 119, 133, 255); +CRGBA WASTEDBUSTED_COLOR(170, 123, 87, 255); +CRGBA ODDJOB_COLOR(89, 115, 150, 255); +CRGBA ODDJOB2_COLOR(156, 91, 40, 255); +CRGBA MISSIONTITLE_COLOR(220, 172, 2, 255); + + +int16 CHud::m_ItemToFlash; +CSprite2d CHud::Sprites[NUM_HUD_SPRITES]; +wchar *CHud::m_pZoneName; +wchar *CHud::m_pLastZoneName; +wchar *CHud::m_ZoneToPrint; +wchar CHud::m_Message[256]; +wchar CHud::m_BigMessage[6][128]; +wchar LastBigMessage[6][128]; +wchar CHud::m_PagerMessage[256]; +uint32 CHud::m_ZoneNameTimer; +int32 CHud::m_ZoneFadeTimer; +uint32 CHud::m_ZoneState; +wchar CHud::m_HelpMessage[HELP_MSG_LENGTH]; +wchar CHud::m_LastHelpMessage[HELP_MSG_LENGTH]; +wchar CHud::m_HelpMessageToPrint[HELP_MSG_LENGTH]; +uint32 CHud::m_HelpMessageTimer; +int32 CHud::m_HelpMessageFadeTimer; +uint32 CHud::m_HelpMessageState; +bool CHud::m_HelpMessageQuick; +float CHud::m_HelpMessageDisplayTime; +int32 CHud::SpriteBrightness; +bool CHud::m_Wants_To_Draw_Hud; +bool CHud::m_Wants_To_Draw_3dMarkers; +wchar *CHud::m_pVehicleName; +wchar *CHud::m_pLastVehicleName; +uint32 CHud::m_VehicleNameTimer; +int32 CHud::m_VehicleFadeTimer; +uint32 CHud::m_VehicleState; +wchar *CHud::m_pVehicleNameToPrint; + +// These aren't really in CHud +float BigMessageInUse[6]; +float BigMessageX[6]; +float BigMessageAlpha[6]; +int16 PagerOn; +int16 PagerTimer; +float PagerXOffset; +int16 PagerSoundPlayed; +int16 OddJob2On; +uint16 OddJob2Timer; +float OddJob2XOffset; +float OddJob2OffTimer; +bool CounterOnLastFrame; +uint16 CounterFlashTimer; +bool TimerOnLastFrame; +uint16 TimerFlashTimer; + +RwTexture *gpSniperSightTex; +RwTexture *gpRocketSightTex; + +struct +{ + const char *name; + const char *mask; +} WeaponFilenames[] = { + {"fist", "fistm"}, + {"bat", "batm"}, + {"pistol", "pistolm" }, + {"uzi", "uzim"}, + {"shotgun", "shotgunm"}, + {"ak47", "ak47m"}, + {"m16", "m16m"}, + {"sniper", "sniperm"}, + {"rocket", "rocketm"}, + {"flame", "flamem"}, + {"molotov", "molotovm"}, + {"grenade", "grenadem"}, + {"detonator", "detonator_mask"}, + {"", ""}, + {"", ""}, + {"radardisc", "radardisc"}, + {"pager", "pagerm"}, + {"", ""}, + {"", ""}, + {"bleeder", ""}, + {"sitesniper", "sitesniperm"}, + {"siteM16", "siteM16m"}, + {"siterocket", "siterocket"} +}; + +void CHud::Initialise() +{ + m_Wants_To_Draw_Hud = true; + m_Wants_To_Draw_3dMarkers = true; + + int HudTXD = CTxdStore::AddTxdSlot("hud"); + CTxdStore::LoadTxd(HudTXD, "MODELS/HUD.TXD"); + CTxdStore::AddRef(HudTXD); + CTxdStore::PopCurrentTxd(); + CTxdStore::SetCurrentTxd(HudTXD); + + for (int i = 0; i < NUM_HUD_SPRITES; i++) { + Sprites[i].SetTexture(WeaponFilenames[i].name, WeaponFilenames[i].mask); + } + + GetRidOfAllHudMessages(); + + if (gpSniperSightTex == nil) + gpSniperSightTex = RwTextureRead("sitesniper", nil); + if (gpRocketSightTex == nil) + gpRocketSightTex = RwTextureRead("siterocket", nil); + + CounterOnLastFrame = false; + m_ItemToFlash = ITEM_NONE; + OddJob2Timer = 0; + OddJob2OffTimer = 0.0f; + OddJob2On = 0; + OddJob2XOffset = 0.0f; + CounterFlashTimer = 0; + TimerOnLastFrame = false; + TimerFlashTimer = 0; + SpriteBrightness = 0; + PagerOn = 0; + PagerTimer = 0; + PagerSoundPlayed = 0; + PagerXOffset = 150.0f; + + CTxdStore::PopCurrentTxd(); +} + +void CHud::Shutdown() +{ + for (int i = 0; i < NUM_HUD_SPRITES; ++i) { + Sprites[i].Delete(); + } + + RwTextureDestroy(gpSniperSightTex); + gpSniperSightTex = nil; + + RwTextureDestroy(gpRocketSightTex); + gpRocketSightTex = nil; + + int HudTXD = CTxdStore::FindTxdSlot("hud"); + CTxdStore::RemoveTxdSlot(HudTXD); +} + +void CHud::ReInitialise() { + m_Wants_To_Draw_Hud = true; + m_Wants_To_Draw_3dMarkers = true; + + GetRidOfAllHudMessages(); + + CounterOnLastFrame = false; + m_ItemToFlash = ITEM_NONE; + OddJob2Timer = 0; + OddJob2OffTimer = 0.0f; + OddJob2On = 0; + OddJob2XOffset = 0.0f; + CounterFlashTimer = 0; + TimerOnLastFrame = false; + TimerFlashTimer = 0; + SpriteBrightness = 0; + PagerOn = 0; + PagerTimer = 0; + PagerSoundPlayed = 0; + PagerXOffset = 150.0f; +} + +void CHud::GetRidOfAllHudMessages() +{ + m_ZoneState = 0; + m_pLastZoneName = nil; + m_ZoneNameTimer = 0; + m_pZoneName = nil; + + for (int i = 0; i < HELP_MSG_LENGTH; i++) { + m_HelpMessage[i] = 0; + m_LastHelpMessage[i] = 0; + m_HelpMessageToPrint[i] = 0; + } + + m_HelpMessageTimer = 0; + m_HelpMessageFadeTimer = 0; + m_HelpMessageState = 0; + m_HelpMessageQuick = 0; + m_HelpMessageDisplayTime = 1.0f; + m_pVehicleName = nil; + m_pLastVehicleName = nil; + m_pVehicleNameToPrint = nil; + m_VehicleNameTimer = 0; + m_VehicleFadeTimer = 0; + m_VehicleState = 0; + + for (int i = 0; i < ARRAY_SIZE(m_Message); i++) + m_Message[i] = 0; + + for (int i = 0; i < 6; i++) { + BigMessageInUse[i] = 0.0f; + + for (int j = 0; j < 128; j++) + m_BigMessage[i][j] = 0; + } +} + +void CHud::SetZoneName(wchar *name) +{ + m_pZoneName = name; +} + +void CHud::SetHelpMessage(wchar *message, bool quick) +{ + if (!CReplay::IsPlayingBack()) { + CMessages::WideStringCopy(m_HelpMessage, message, HELP_MSG_LENGTH); + CMessages::InsertPlayerControlKeysInString(m_HelpMessage); + + for (int i = 0; i < HELP_MSG_LENGTH; i++) { + m_LastHelpMessage[i] = 0; + } + + m_HelpMessageState = 0; + m_HelpMessageQuick = quick; + } +} + +void CHud::SetVehicleName(wchar *name) +{ + m_pVehicleName = name; +} + +void CHud::Draw() +{ + // disable hud via second controller + if (CPad::GetPad(1)->GetStartJustDown()) + m_Wants_To_Draw_Hud = !m_Wants_To_Draw_Hud; + +#ifdef GTA_PC + if (CReplay::IsPlayingBack()) + return; +#endif + + if (m_Wants_To_Draw_Hud && !TheCamera.m_WideScreenOn) { + bool DrawCrossHair = false; +#ifdef GTA_PC + bool DrawCrossHairPC = false; +#endif + + int32 WeaponType = FindPlayerPed()->m_weapons[FindPlayerPed()->m_currentWeapon].m_eWeaponType; + int32 Mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + + if (Mode == CCam::MODE_SNIPER || Mode == CCam::MODE_ROCKETLAUNCHER || Mode == CCam::MODE_M16_1STPERSON +#ifdef GTA_PC + || Mode == CCam::MODE_HELICANNON_1STPERSON +#endif + ) + { + DrawCrossHair = true; + } + +#ifdef GTA_PC + if (Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || Mode == CCam::MODE_SNIPER_RUNABOUT) + DrawCrossHairPC = true; + + /* + Draw Crosshairs + */ + if (TheCamera.Cams[TheCamera.ActiveCam].Using3rdPersonMouseCam() && + (!CPad::GetPad(0)->GetLookBehindForPed() || TheCamera.m_bPlayerIsInGarage) || Mode == CCam::MODE_1STPERSON_RUNABOUT) { + if (FindPlayerPed() && !FindPlayerPed()->EnteringCar()) { + if ((WeaponType >= WEAPONTYPE_COLT45 && WeaponType <= WEAPONTYPE_M16) || WeaponType == WEAPONTYPE_FLAMETHROWER) + DrawCrossHairPC = true; + } + } +#endif + + if ( DrawCrossHair +#ifdef GTA_PC + || DrawCrossHairPC +#endif + ) + { + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *)rwFILTERLINEAR); + + SpriteBrightness = Min(SpriteBrightness+1, 30); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + + float fStep = Sin((CTimer::GetTimeInMilliseconds() & 1023)/1024.0f * 6.28f); + float fMultBright = SpriteBrightness / 30.0f * (0.25f * fStep + 0.75f); + CRect rect; +#ifdef GTA_PC + if (DrawCrossHairPC && TheCamera.Cams[TheCamera.ActiveCam].Using3rdPersonMouseCam()) { + float f3rdX = SCREEN_WIDTH * TheCamera.m_f3rdPersonCHairMultX; + float f3rdY = SCREEN_HEIGHT * TheCamera.m_f3rdPersonCHairMultY; +#ifdef ASPECT_RATIO_SCALE + f3rdY -= SCREEN_SCALE_Y(2.0f); +#endif + if (FindPlayerPed() && WeaponType == WEAPONTYPE_M16) { + rect.left = f3rdX - SCREEN_SCALE_X(32.0f * 0.6f); + rect.top = f3rdY - SCREEN_SCALE_Y(32.0f * 0.6f); + rect.right = f3rdX + SCREEN_SCALE_X(32.0f * 0.6f); + rect.bottom = f3rdY + SCREEN_SCALE_Y(32.0f * 0.6f); + + Sprites[HUD_SITEM16].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f); + } + else { + rect.left = f3rdX - SCREEN_SCALE_X(32.0f * 0.4f); + rect.top = f3rdY - SCREEN_SCALE_Y(32.0f * 0.4f); + rect.right = f3rdX + SCREEN_SCALE_X(32.0f * 0.4f); + rect.bottom = f3rdY + SCREEN_SCALE_Y(32.0f * 0.4f); + + Sprites[HUD_SITEM16].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f); + } + } + else +#endif + { + if (Mode == CCam::MODE_M16_1STPERSON +#ifdef GTA_PC + || Mode == CCam::MODE_M16_1STPERSON_RUNABOUT + || Mode == CCam::MODE_HELICANNON_1STPERSON +#endif + ) + { + rect.left = (SCREEN_WIDTH / 2) - SCREEN_SCALE_X(32.0f); + rect.top = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(32.0f); + rect.right = (SCREEN_WIDTH / 2) + SCREEN_SCALE_X(32.0f); + rect.bottom = (SCREEN_HEIGHT / 2) + SCREEN_SCALE_Y(32.0f); + Sprites[HUD_SITEM16].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f); + } +#ifdef GTA_PC + else if (Mode == CCam::MODE_1STPERSON_RUNABOUT) { + rect.left = (SCREEN_WIDTH / 2) - SCREEN_SCALE_X(32.0f * 0.7f); + rect.top = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(32.0f * 0.7f); + rect.right = (SCREEN_WIDTH / 2) + SCREEN_SCALE_X(32.0f * 0.7f); + rect.bottom = (SCREEN_HEIGHT / 2) + SCREEN_SCALE_Y(32.0f * 0.7f); + + Sprites[HUD_SITEM16].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f); + } +#endif + else if (Mode == CCam::MODE_ROCKETLAUNCHER +#ifdef GTA_PC + || Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT +#endif + ) + { + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpRocketSightTex)); + CSprite::RenderOneXLUSprite(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 1.0f, SCREEN_SCALE_X_PC(40.0f), SCREEN_SCALE_Y_PC(40.0f), (100.0f * fMultBright), (200.0f * fMultBright), (100.0f * fMultBright), 255, 1.0f, 255); + } + else { + // Sniper + rect.left = SCREEN_WIDTH/2 - SCREEN_SCALE_X(210.0f); + rect.top = SCREEN_HEIGHT/2 - SCREEN_SCALE_Y(210.0f); + rect.right = SCREEN_WIDTH/2; + rect.bottom = SCREEN_HEIGHT/2; + Sprites[HUD_SITESNIPER].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.01f, 0.01f, 1.0f, 0.0f, 0.01f, 1.0f, 1.0f, 1.0f); + + rect.left = SCREEN_WIDTH/2; + rect.top = SCREEN_HEIGHT/2 - SCREEN_SCALE_Y(210.0f); + rect.right = SCREEN_WIDTH/2 + SCREEN_SCALE_X(210.0f); + rect.bottom = SCREEN_HEIGHT/2; + Sprites[HUD_SITESNIPER].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.99f, 0.0f, 0.01f, 0.01f, 0.99f, 1.0f, 0.01f, 1.0f); + + rect.left = SCREEN_WIDTH/2 - SCREEN_SCALE_X(210.0f); + rect.top = SCREEN_HEIGHT/2; + rect.right = SCREEN_WIDTH/2; + rect.bottom = SCREEN_HEIGHT/2 + SCREEN_SCALE_Y(210.0f); + Sprites[HUD_SITESNIPER].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.01f, 0.99f, 1.0f, 0.99f, 0.01f, 0.01f, 1.0f, 0.01f); + + rect.left = SCREEN_WIDTH/2; + rect.top = SCREEN_HEIGHT/2; + rect.right = SCREEN_WIDTH/2 + SCREEN_SCALE_X(210.0f); + rect.bottom = SCREEN_HEIGHT/2 + SCREEN_SCALE_Y(210.0f); + Sprites[HUD_SITESNIPER].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.99f, 0.99f, 0.01f, 0.99f, 0.99f, 0.01f, 0.01f, 0.01f); + } + } + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + } + else { + SpriteBrightness = 0; + } + + /* + DrawMoneyCounter + */ + wchar sPrint[16]; + wchar sPrintIcon[16]; + char sTemp[16]; + + sprintf(sTemp, "$%08d", CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney); + AsciiToUnicode(sTemp, sPrint); + + CFont::SetPropOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetPropOff(); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(MONEY_X) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(43.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrint); + CFont::SetColor(MONEY_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(MONEY_X), SCREEN_SCALE_Y(43.0f), sPrint); + + /* + DrawAmmo + */ + int32 AmmoAmount = CWeaponInfo::GetWeaponInfo(FindPlayerPed()->GetWeapon()->m_eWeaponType)->m_nAmountofAmmunition; + int32 AmmoInClip = FindPlayerPed()->m_weapons[FindPlayerPed()->m_currentWeapon].m_nAmmoInClip; + int32 TotalAmmo = FindPlayerPed()->m_weapons[FindPlayerPed()->m_currentWeapon].m_nAmmoTotal; + int32 Ammo, Clip; + + if (AmmoAmount <= 1 || AmmoAmount >= 1000) + sprintf(sTemp, "%d", TotalAmmo); + else { + if (WeaponType == WEAPONTYPE_FLAMETHROWER) { + Clip = AmmoInClip / 10; + + Ammo = Min((TotalAmmo - AmmoInClip) / 10, 9999); + } + else { + Clip = AmmoInClip; + + Ammo = Min(TotalAmmo - AmmoInClip, 9999); + } + + sprintf(sTemp, "%d-%d", Ammo, Clip); + } + + AsciiToUnicode(sTemp, sPrint); + + /* + DrawWeaponIcon + */ + Sprites[WeaponType].Draw( + CRect( + SCREEN_SCALE_FROM_RIGHT(WEAPON_X), + SCREEN_SCALE_Y(27.0f), + SCREEN_SCALE_FROM_RIGHT(WEAPON_X)+SCREEN_SCALE_X(64.0f), + SCREEN_SCALE_Y(27.0f)+SCREEN_SCALE_Y(64.0f)), + CRGBA(255, 255, 255, 255), + 0.015f, + 0.015f, + 1.0f, + 0.0f, + 0.015f, + 1.0f, + 1.0f, + 1.0f); + + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.4f), SCREEN_SCALE_Y(0.6f)); + CFont::SetJustifyOff(); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_WIDTH); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_BANK); + + if (!CDarkel::FrenzyOnGoing() && WeaponType != WEAPONTYPE_UNARMED && WeaponType != WEAPONTYPE_BASEBALLBAT) { + CFont::SetColor(AMMO_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(AMMO_X), SCREEN_SCALE_Y(73.0f), sPrint); + } + + /* + DrawHealth + */ + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetJustifyOff(); + CFont::SetCentreOff(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetRightJustifyOn(); + CFont::SetPropOff(); + CFont::SetFontStyle(FONT_HEADING); + + if (m_ItemToFlash == ITEM_HEALTH && FRAMECOUNTER & 8 + || m_ItemToFlash != ITEM_HEALTH + || FindPlayerPed()->m_fHealth < 10 + && FRAMECOUNTER & 8) { + if (FindPlayerPed()->m_fHealth >= 10 + || FindPlayerPed()->m_fHealth < 10 && FRAMECOUNTER & 8) { + + AsciiToUnicode("{", sPrintIcon); +#ifdef FIX_BUGS + sprintf(sTemp, "%03d", int32(FindPlayerPed()->m_fHealth + 0.5f)); +#else + sprintf(sTemp, "%03d", (int32)FindPlayerPed()->m_fHealth); +#endif + AsciiToUnicode(sTemp, sPrint); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(HEALTH_X) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(65.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrint); + + if (!CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss || CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss + 2000 || FRAMECOUNTER & 4) + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(HEALTH_X) + SCREEN_SCALE_X_FIX(2.0f) - SCREEN_SCALE_X(56.0f) + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(65.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrintIcon); + + CFont::SetColor(HEALTH_COLOR); + + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(HEALTH_X), SCREEN_SCALE_Y(65.0f), sPrint); + + if (!CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss || CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss + 2000 || FRAMECOUNTER & 4) + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(HEALTH_X) + SCREEN_SCALE_X_FIX(2.0f) - SCREEN_SCALE_X(56.0f), SCREEN_SCALE_Y(65.0f), sPrintIcon); + } + } + + /* + DrawArmour + */ + if (m_ItemToFlash == ITEM_ARMOUR && FRAMECOUNTER & 8 || m_ItemToFlash != ITEM_ARMOUR) { + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + if (FindPlayerPed()->m_fArmour > 1.0f) { + AsciiToUnicode("[", sPrintIcon); +#ifdef FIX_BUGS + sprintf(sTemp, "%03d", int32(FindPlayerPed()->m_fArmour + 0.5f)); +#else + sprintf(sTemp, "%03d", (int32)FindPlayerPed()->m_fArmour); +#endif + AsciiToUnicode(sTemp, sPrint); + + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(182.0f) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(65.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrint); + + if (!CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastArmourLoss || CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastArmourLoss + 2000 || FRAMECOUNTER & 4) + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(182.0f) + SCREEN_SCALE_X_FIX(2.0f) - SCREEN_SCALE_X(54.0f) + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(65.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrintIcon); + + CFont::SetColor(ARMOUR_COLOR); + + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(182.0f), SCREEN_SCALE_Y(65.0f), sPrint); + + if (!CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastArmourLoss || CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastArmourLoss + 2000 || FRAMECOUNTER & 1) { + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(182.0f) - SCREEN_SCALE_X(54.0f) + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(65.0f), sPrintIcon); + } + } + } + + /* + DrawWantedLevel + */ + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetJustifyOff(); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_HEADING); + + AsciiToUnicode("]", sPrintIcon); + + float fStarsX = SCREEN_SCALE_FROM_RIGHT(STARS_X); + + for (int i = 0; i < 6; i++) { + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(fStarsX + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(87.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrintIcon); + + if (FindPlayerPed()->m_pWanted->GetWantedLevel() > i + && (CTimer::GetTimeInMilliseconds() > FindPlayerPed()->m_pWanted->m_nLastWantedLevelChange + + 2000 || FRAMECOUNTER & 4)) { + + CFont::SetColor(WANTED_COLOR); + CFont::PrintString(fStarsX, SCREEN_SCALE_Y(87.0f), sPrintIcon); + } + + fStarsX -= SCREEN_SCALE_X(23.0f); + } + + /* + DrawZoneName + */ + if (m_pZoneName) { + float fZoneAlpha = 255.0f; + + if (m_pZoneName != m_pLastZoneName) { + switch (m_ZoneState) { + case 0: + m_ZoneState = 2; + m_ZoneToPrint = m_pZoneName; + m_ZoneNameTimer = 0; + m_ZoneFadeTimer = 0; + break; + case 1: + case 2: + case 3: + case 4: + m_ZoneNameTimer = 5; + m_ZoneState = 4; + break; + default: + break; + } + m_pLastZoneName = m_pZoneName; + } + + if (m_ZoneState) { + switch (m_ZoneState) { + case 1: + m_ZoneFadeTimer = 1000; + if (m_ZoneNameTimer > 10000) { + m_ZoneFadeTimer = 1000; + m_ZoneState = 3; + } + fZoneAlpha = 255.0f; + break; + case 2: + m_ZoneFadeTimer += CTimer::GetTimeStepInMilliseconds(); + if (m_ZoneFadeTimer > 1000) { + m_ZoneState = 1; + m_ZoneFadeTimer = 1000; + } + fZoneAlpha = m_ZoneFadeTimer / 1000.0f * 255.0f; + break; + case 3: + m_ZoneFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_ZoneFadeTimer < 0) { + m_ZoneState = 0; + m_ZoneFadeTimer = 0; + } + fZoneAlpha = m_ZoneFadeTimer / 1000.0f * 255.0f; + break; + case 4: + m_ZoneFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_ZoneFadeTimer < 0) { + m_ZoneFadeTimer = 0; + m_ZoneToPrint = m_pLastZoneName; + m_ZoneState = 2; + } + fZoneAlpha = m_ZoneFadeTimer / 1000.0f * 255.0f; + break; + default: + break; + + } + +#ifndef HUD_ENHANCEMENTS + if (!m_Message[0]) +#else + if (!m_Message[0] && !m_BigMessage[2][0]) // Hide zone name if wasted/busted text is displaying +#endif + { + m_ZoneNameTimer += CTimer::GetTimeStepInMilliseconds(); + CFont::SetJustifyOff(); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + CFont::SetScale(SCREEN_SCALE_X(1.2f * 0.8f), SCREEN_SCALE_Y(1.2f)); + else + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.2f)); + + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetColor(CRGBA(0, 0, 0, fZoneAlpha)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(32.0f) + SCREEN_SCALE_X_FIX(1.0f), SCREEN_SCALE_FROM_BOTTOM(ZONE_Y) + SCREEN_SCALE_Y_FIX(1.0f), m_ZoneToPrint); + CFont::SetColor(CRGBA(ZONE_COLOR.r, ZONE_COLOR.g, ZONE_COLOR.b, fZoneAlpha)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(32.0f), SCREEN_SCALE_FROM_BOTTOM(ZONE_Y), m_ZoneToPrint); + } + } + } + + /* + DrawVehicleName + */ + if (m_pVehicleName) { + float fVehicleAlpha = 0.0f; + + if (m_pVehicleName != m_pLastVehicleName) { + switch (m_VehicleState) { + case 0: + m_VehicleState = 2; + m_pVehicleNameToPrint = m_pVehicleName; + m_VehicleNameTimer = 0; + m_VehicleFadeTimer = 0; + break; + case 1: + case 2: + case 3: + case 4: + m_VehicleNameTimer = 0; + m_VehicleState = 4; + break; + default: + break; + } + m_pLastVehicleName = m_pVehicleName; + } + + if (m_VehicleState) { + switch (m_VehicleState) { + case 1: + if (m_VehicleNameTimer > 10000) { + m_VehicleFadeTimer = 1000; + m_VehicleState = 3; + } + fVehicleAlpha = 255.0f; + break; + case 2: + m_VehicleFadeTimer += CTimer::GetTimeStepInMilliseconds(); + if (m_VehicleFadeTimer > 1000) { + m_VehicleState = 1; + m_VehicleFadeTimer = 1000; + } + fVehicleAlpha = m_VehicleFadeTimer / 1000.0f * 255.0f; + break; + case 3: + m_VehicleFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_VehicleFadeTimer < 0) { + m_VehicleState = 0; + m_VehicleFadeTimer = 0; + } + fVehicleAlpha = m_VehicleFadeTimer / 1000.0f * 255.0f; + break; + case 4: + m_VehicleFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_VehicleFadeTimer < 0) { + m_VehicleFadeTimer = 0; + m_pVehicleNameToPrint = m_pLastVehicleName; + m_VehicleNameTimer = 0; + m_VehicleState = 2; + } + fVehicleAlpha = m_VehicleFadeTimer / 1000.0f * 255.0f; + break; + default: + break; + } + +#ifndef HUD_ENHANCEMENTS + if (!m_Message[0]) +#else + if (!m_Message[0] && !m_BigMessage[2][0]) // Hide vehicle name if wasted/busted text is displaying +#endif + { + m_VehicleNameTimer += CTimer::GetTimeStepInMilliseconds(); + CFont::SetJustifyOff(); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + + if (FrontEndMenuManager.m_PrefsLanguage != CMenuManager::LANGUAGE_ITALIAN && FrontEndMenuManager.m_PrefsLanguage != CMenuManager::LANGUAGE_SPANISH) + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.2f)); + else + CFont::SetScale(SCREEN_SCALE_X(1.2f * 0.85f), SCREEN_SCALE_Y(1.2f)); + + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetColor(CRGBA(0, 0, 0, fVehicleAlpha)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(32.0f) + SCREEN_SCALE_X_FIX(1.0f), SCREEN_SCALE_FROM_BOTTOM(VEHICLE_Y) + SCREEN_SCALE_Y_FIX(1.0f), m_pVehicleNameToPrint); + CFont::SetColor(CRGBA(VEHICLE_COLOR.r, VEHICLE_COLOR.g, VEHICLE_COLOR.b, fVehicleAlpha)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(32.0f), SCREEN_SCALE_FROM_BOTTOM(VEHICLE_Y), m_pVehicleNameToPrint); + } + } + } + else { + m_pLastVehicleName = nil; + m_VehicleState = 0; + m_VehicleFadeTimer = 0; + m_VehicleNameTimer = 0; + } + + /* + DrawClock + */ + CFont::SetJustifyOff(); + CFont::SetCentreOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetPropOff(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + + sprintf(sTemp, "%02d:%02d", CClock::GetHours(), CClock::GetMinutes()); + AsciiToUnicode(sTemp, sPrint); + + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(CLOCK_X) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(22.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrint); + CFont::SetColor(CLOCK_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(CLOCK_X), SCREEN_SCALE_Y(22.0f), sPrint); + + /* + DrawOnScreenTimer + */ + wchar sTimer[16]; + + if (!CUserDisplay::OnscnTimer.m_sEntries[0].m_bTimerProcessed) + TimerOnLastFrame = false; + if (!CUserDisplay::OnscnTimer.m_sEntries[0].m_bCounterProcessed) + CounterOnLastFrame = false; + + if (CUserDisplay::OnscnTimer.m_bProcessed) { + if (CUserDisplay::OnscnTimer.m_sEntries[0].m_bTimerProcessed) { + if (!TimerOnLastFrame) + TimerFlashTimer = 1; + + TimerOnLastFrame = true; + + if (TimerFlashTimer) { + if (++TimerFlashTimer > 50) + TimerFlashTimer = 0; + } + + if (FRAMECOUNTER & 4 || !TimerFlashTimer) { + AsciiToUnicode(CUserDisplay::OnscnTimer.m_sEntries[0].m_bTimerBuffer, sTimer); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetPropOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(110.0f) + SCREEN_SCALE_Y_FIX(2.0f), sTimer); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetColor(TIMER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET), SCREEN_SCALE_Y(110.0f), sTimer); + + if (CUserDisplay::OnscnTimer.m_sEntries[0].m_aTimerText[0]) { + CFont::SetPropOn(); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::SetScale(SCREEN_SCALE_X(0.8f * 0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(80.0f) + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(110.0f) + SCREEN_SCALE_Y_FIX(2.0f), TheText.Get(CUserDisplay::OnscnTimer.m_sEntries[0].m_aTimerText)); + CFont::SetColor(TIMER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(80.0f), SCREEN_SCALE_Y(110.0f), TheText.Get(CUserDisplay::OnscnTimer.m_sEntries[0].m_aTimerText)); + } + } + } + if (CUserDisplay::OnscnTimer.m_sEntries[0].m_bCounterProcessed) { + if (!CounterOnLastFrame) + CounterFlashTimer = 1; + + CounterOnLastFrame = true; + + if (CounterFlashTimer) { + if (++CounterFlashTimer > 50) + CounterFlashTimer = 0; + } + + if (FRAMECOUNTER & 4 || !CounterFlashTimer) { + if (CUserDisplay::OnscnTimer.m_sEntries[0].m_nType == COUNTER_DISPLAY_NUMBER) { + AsciiToUnicode(CUserDisplay::OnscnTimer.m_sEntries[0].m_bCounterBuffer, sTimer); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetColor(CRGBA(244, 20, 20, 255)); + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetPropOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y_FIX(2.0f), sTimer); + CFont::SetColor(COUNTER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET), SCREEN_SCALE_Y(132.0f), sTimer); + } else { + int counter = atoi(CUserDisplay::OnscnTimer.m_sEntries[0].m_bCounterBuffer); +#ifdef FIX_BUGS + counter = Min(counter, 100); +#endif + CSprite2d::DrawRect + ( + CRect + ( + SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(100.0f) / 2 + SCREEN_SCALE_X_FIX(4.0f), + SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y(8.0f), + SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) + SCREEN_SCALE_X_FIX(4.0f), + SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y_PC(11.0f) + SCREEN_SCALE_Y(8.0f) + ), + CRGBA(0, 106, 164, 80) + ); + + CSprite2d::DrawRect + ( + CRect + ( + SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(100.0f) / 2 + SCREEN_SCALE_X_FIX(4.0f), + SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y(8.0f), + SCREEN_SCALE_X_PC((float)counter) / 2.0f + SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(100.0f) / 2.0f + SCREEN_SCALE_X_FIX(4.0f), + SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y_PC(11.0f) + SCREEN_SCALE_Y(8.0f) + ), + CRGBA(0, 106, 164, 255) + ); + } + + if (CUserDisplay::OnscnTimer.m_sEntries[0].m_aCounterText[0]) { + CFont::SetPropOn(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(61.0f) + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y_FIX(2.0f), TheText.Get(CUserDisplay::OnscnTimer.m_sEntries[0].m_aCounterText)); + CFont::SetColor(COUNTER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(61.0f), SCREEN_SCALE_Y(132.0f), TheText.Get(CUserDisplay::OnscnTimer.m_sEntries[0].m_aCounterText)); + } + } + } + } + + ///////////////////////////////// + /* + DrawPager + */ + if (!m_PagerMessage[0] && PagerOn == 1) { + PagerSoundPlayed = false; + PagerOn = 2; + } + if (m_PagerMessage[0] || PagerOn == 2) { + if (!PagerOn) { + PagerOn = 1; + PagerXOffset = 150.0f; + } + if (PagerOn == 1) { + if (PagerXOffset > 0.0f) { + float fStep = PagerXOffset * 0.1f; + if (fStep > 10.0f) + fStep = 10.0f; + PagerXOffset -= fStep * CTimer::GetTimeStep(); + } + if (!PagerSoundPlayed) { + DMAudio.PlayFrontEndSound(SOUND_PAGER, 0); + PagerSoundPlayed = 1; + } + } + else if (PagerOn == 2) { + float fStep = PagerXOffset * 0.1f; + if (fStep < 2.0f) + fStep = 2.0f; + PagerXOffset += fStep; + if (PagerXOffset > 150.0f) { + PagerXOffset = 150.0f; + PagerOn = 0; + } + } + Sprites[HUD_PAGER].Draw(CRect(SCREEN_SCALE_X(26.0f) - SCREEN_SCALE_X_FIX(PagerXOffset), SCREEN_SCALE_Y(27.0f), SCREEN_SCALE_X(160.0f) + SCREEN_SCALE_X(26.0f) - SCREEN_SCALE_X_FIX(PagerXOffset), SCREEN_SCALE_Y(80.0f) + SCREEN_SCALE_Y(27.0f)), CRGBA(255, 255, 255, 255)); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.84f), SCREEN_SCALE_Y(1.0f)); + CFont::SetColor(PAGER_COLOR); + CFont::SetRightJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetCentreOff(); + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetJustifyOff(); + CFont::SetPropOff(); + CFont::SetFontStyle(FONT_PAGER); + CFont::PrintString(SCREEN_SCALE_X(52.0f) - SCREEN_SCALE_X_FIX(PagerXOffset), SCREEN_SCALE_Y(54.0f), m_PagerMessage); + } + + /* + DrawRadar + */ + if (m_ItemToFlash == ITEM_RADAR && FRAMECOUNTER & 8 || m_ItemToFlash != ITEM_RADAR) { + CRadar::DrawMap(); + CRect rect(0.0f, 0.0f, SCREEN_SCALE_X(RADAR_WIDTH), SCREEN_SCALE_Y(RADAR_HEIGHT)); + rect.Translate(SCREEN_SCALE_X_FIX(RADAR_LEFT), SCREEN_SCALE_FROM_BOTTOM(RADAR_BOTTOM + RADAR_HEIGHT)); + +#ifdef PS2_HUD + #ifdef FIX_BUGS + rect.Grow(SCREEN_SCALE_X(2.0f), SCREEN_SCALE_X(4.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(4.0f)); + #else + rect.Grow(2.0f, 4.0f); + #endif +#else + #ifdef FIX_BUGS + rect.Grow(SCREEN_SCALE_X(4.0f), SCREEN_SCALE_X(4.0f), SCREEN_SCALE_Y(4.0f), SCREEN_SCALE_Y(4.0f)); + #else + rect.Grow(4.0f); + #endif +#endif + Sprites[HUD_RADARDISC].Draw(rect, RADARDISC_COLOR); + CRadar::DrawBlips(); + } + } + + /* + Draw3dMarkers + */ + if (m_Wants_To_Draw_3dMarkers && !TheCamera.m_WideScreenOn && !m_BigMessage[0][0] && !m_BigMessage[2][0]) { + CRadar::Draw3dMarkers(); + } + + /* + DrawScriptText + */ + if (!CTimer::GetIsUserPaused()) { + for (int i = 0; i < ARRAY_SIZE(CTheScripts::IntroTextLines); i++) { + if (CTheScripts::IntroTextLines[i].m_Text[0] && CTheScripts::IntroTextLines[i].m_bTextBeforeFade) { + CFont::SetScale(SCREEN_SCALE_X_PC(CTheScripts::IntroTextLines[i].m_fScaleX), SCREEN_SCALE_Y_PC(CTheScripts::IntroTextLines[i].m_fScaleY) +#if !defined(PS2_HUD) || defined(FIX_BUGS) + * 0.5f +#endif + ); + + CFont::SetColor(CTheScripts::IntroTextLines[i].m_sColor); + + if (CTheScripts::IntroTextLines[i].m_bJustify) + CFont::SetJustifyOn(); + else + CFont::SetJustifyOff(); + + if (CTheScripts::IntroTextLines[i].m_bRightJustify) + CFont::SetRightJustifyOn(); + else + CFont::SetRightJustifyOff(); + + if (CTheScripts::IntroTextLines[i].m_bCentered) + CFont::SetCentreOn(); + else + CFont::SetCentreOff(); + + CFont::SetWrapx(SCALE_AND_CENTER_X_PC(CTheScripts::IntroTextLines[i].m_fWrapX)); + + CFont::SetCentreSize(SCREEN_SCALE_X_PC(CTheScripts::IntroTextLines[i].m_fCenterSize)); + + if (CTheScripts::IntroTextLines[i].m_bBackground) + CFont::SetBackgroundOn(); + else + CFont::SetBackgroundOff(); + + CFont::SetBackgroundColor(CTheScripts::IntroTextLines[i].m_sBackgroundColor); + + if (CTheScripts::IntroTextLines[i].m_bBackgroundOnly) + CFont::SetBackGroundOnlyTextOn(); + else + CFont::SetBackGroundOnlyTextOff(); + + if (CTheScripts::IntroTextLines[i].m_bTextProportional) + CFont::SetPropOn(); + else + CFont::SetPropOff(); + + CFont::SetFontStyle(FONT_LOCALE(CTheScripts::IntroTextLines[i].m_nFont)); + +#if defined(PS2_HUD) && !defined(FIX_BUGS) + CFont::PrintString(CTheScripts::IntroTextLines[i].m_fAtX, CTheScripts::IntroTextLines[i].m_fAtY, CTheScripts::IntroTextLines[i].m_Text); +#else + CFont::PrintString(SCALE_AND_CENTER_X(DEFAULT_SCREEN_WIDTH - CTheScripts::IntroTextLines[i].m_fAtX), SCREEN_SCALE_Y(DEFAULT_SCREEN_HEIGHT - CTheScripts::IntroTextLines[i].m_fAtY), CTheScripts::IntroTextLines[i].m_Text); +#endif + } + } + for (int i = 0; i < ARRAY_SIZE(CTheScripts::IntroRectangles); i++) { + intro_script_rectangle &IntroRect = CTheScripts::IntroRectangles[i]; + + // Yeah, top and bottom changed place. R* vision + if (IntroRect.m_bIsUsed && IntroRect.m_bBeforeFade) { + if (IntroRect.m_nTextureId >= 0) { + CRect rect ( + IntroRect.m_sRect.left, + IntroRect.m_sRect.bottom, + IntroRect.m_sRect.right, + IntroRect.m_sRect.top ); + + CTheScripts::ScriptSprites[IntroRect.m_nTextureId].Draw(rect, IntroRect.m_sColor); + } + else { + CRect rect ( + IntroRect.m_sRect.left, + IntroRect.m_sRect.bottom, + IntroRect.m_sRect.right, + IntroRect.m_sRect.top ); + + CSprite2d::DrawRect(rect, IntroRect.m_sColor); + } + } + } + + /* + DrawSubtitles + */ + if (m_Message[0] && !m_BigMessage[2][0] && (FrontEndMenuManager.m_PrefsShowSubtitles == 1 || !TheCamera.m_WideScreenOn)) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetBackgroundColor(CRGBA(0, 0, 0, 128)); + CFont::SetScale(SCREEN_SCALE_X_PC(0.48f), SCREEN_SCALE_Y_PC(1.12f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + +#ifdef XBOX_SUBTITLES + float radarBulge = SCREEN_SCALE_X(45.0f) + SCREEN_SCALE_X(16.0f); + float rectWidth = SCREEN_WIDTH - SCREEN_SCALE_X(45.0f) - SCREEN_SCALE_X(16.0f) - radarBulge; + CFont::SetCentreSize(rectWidth); + CFont::SetColor(CRGBA(180, 180, 180, 255)); + + CFont::PrintOutlinedString(rectWidth / 2.0f + radarBulge, SCREEN_SCALE_Y(4.0f) + SCREEN_SCALE_FROM_BOTTOM(48.0f) - SCREEN_SCALE_Y(1), m_Message, + 2.0f, true, CRGBA(0, 0, 0, 255)); +#else + float radarBulge = SCREEN_SCALE_X(40.0f) + SCREEN_SCALE_X(8.0f); + float rectWidth = SCREEN_SCALE_FROM_RIGHT(50.0f) - SCREEN_SCALE_X(8.0f) - radarBulge; + + CFont::SetCentreSize(rectWidth); + + const int16 shadow = 1; + CFont::SetDropShadowPosition(shadow); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetColor(CRGBA(235, 235, 235, 255)); + + // I'm not sure shadow substaction was intentional here, might be a leftover if CFont::PrintString was used for a shadow draw call + CFont::PrintString(rectWidth / 2.0f + radarBulge - SCREEN_SCALE_X_FIX(shadow), SCREEN_SCALE_Y_PC(4.0f) + SCREEN_SCALE_FROM_BOTTOM(SUBS_Y) - SCREEN_SCALE_Y_FIX(shadow), m_Message); + CFont::SetDropShadowPosition(0); +#endif // #ifdef XBOX_SUBTITLES + } + + /* + DrawBigMessage + */ + // MissionCompleteFailedText + if (m_BigMessage[0][0]) { + if (BigMessageInUse[0] != 0.0f) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetBackGroundOnlyTextOff(); + + if (CGame::frenchGame || CGame::germanGame) + CFont::SetScale(SCREEN_SCALE_X_PC(1.8f), SCREEN_SCALE_Y_PC(1.8f)); + else + CFont::SetScale(SCREEN_SCALE_X_PC(1.8f), SCREEN_SCALE_Y_PC(1.8f)); + + CFont::SetPropOn(); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 25)); + CFont::SetColor(CRGBA(255, 255, 0, 255)); + CFont::SetFontStyle(FONT_HEADING); + + // Appearently sliding text in here was abandoned very early, since this text is centered now. +#ifdef FIX_BUGS + if (BigMessageX[0] >= SCALE_AND_CENTER_X(DEFAULT_SCREEN_WIDTH-20)) +#else + if (BigMessageX[0] >= SCREEN_WIDTH-20) +#endif + { + BigMessageInUse[0] += CTimer::GetTimeStep(); + + if (BigMessageInUse[0] >= 120.0f) { + BigMessageInUse[0] = 120.0f; + BigMessageAlpha[0] -= (CTimer::GetTimeStepInMilliseconds() * 0.3f); + } + + if (BigMessageAlpha[0] <= 0.0f) { + m_BigMessage[0][0] = 0; + BigMessageAlpha[0] = 0.0f; + } + } + else { + BigMessageX[0] += SCREEN_SCALE_X_FIX(CTimer::GetTimeStepInMilliseconds() * 0.3f); + BigMessageAlpha[0] += (CTimer::GetTimeStepInMilliseconds() * 0.3f); + + if (BigMessageAlpha[0] > 255.0f) + BigMessageAlpha[0] = 255.0f; + } + CFont::SetColor(CRGBA(0, 0, 0, BigMessageAlpha[0])); + +#if defined(PS2_HUD) && !defined(FIX_BUGS) // yeah, that's right. ps2 uses y=ScaleX(a) + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X_FIX(2.0f), (SCREEN_WIDTH / 2) - SCREEN_SCALE_X(120.0f) + SCREEN_SCALE_Y_FIX(2.0f), m_BigMessage[0]); +#else + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X_FIX(2.0f), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(BIGMESSAGE_Y_OFFSET) + SCREEN_SCALE_Y_FIX(2.0f), m_BigMessage[0]); +#endif + CFont::SetColor(CRGBA(BIGMESSAGE_COLOR.r, BIGMESSAGE_COLOR.g, BIGMESSAGE_COLOR.b, BigMessageAlpha[0])); +#if defined(PS2_HUD) && !defined(FIX_BUGS) // same + CFont::PrintString(SCREEN_WIDTH / 2, (SCREEN_WIDTH / 2) - SCREEN_SCALE_X(120.0f), m_BigMessage[0]); +#else + CFont::PrintString(SCREEN_WIDTH / 2, (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(18.0f), m_BigMessage[0]); +#endif + } + else { + BigMessageAlpha[0] = 0.0f; + BigMessageX[0] = SCALE_AND_CENTER_X_FIX(-60.0f); + BigMessageInUse[0] = 1.0f; + } + } + else { + BigMessageInUse[0] = 0.0f; + } + + // WastedBustedText + if (m_BigMessage[2][0]) { + if (BigMessageInUse[2] != 0.0f) { + BigMessageAlpha[2] += (CTimer::GetTimeStepInMilliseconds() * 0.4f); + + if (BigMessageAlpha[2] > 255.0f) + BigMessageAlpha[2] = 255.0f; + + CFont::SetBackgroundOff(); + + if (CGame::frenchGame || CGame::germanGame) + CFont::SetScale(SCREEN_SCALE_X_PC(1.4f), SCREEN_SCALE_Y_PC(1.4f)); + else + CFont::SetScale(SCREEN_SCALE_X_PC(2.0f), SCREEN_SCALE_Y_PC(2.0f)); + + CFont::SetPropOn(); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + + CFont::SetColor(CRGBA(0, 0, 0, BigMessageAlpha[2]*0.75f)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f) + SCREEN_SCALE_X_FIX(4.0f), SCREEN_SCALE_FROM_BOTTOM(WASTEDBUSTED_Y) + SCREEN_SCALE_Y(4.0f), m_BigMessage[2]); + CFont::SetColor(CRGBA(WASTEDBUSTED_COLOR.r, WASTEDBUSTED_COLOR.g, WASTEDBUSTED_COLOR.b, BigMessageAlpha[2])); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(WASTEDBUSTED_Y), m_BigMessage[2]); + } + else { + BigMessageAlpha[2] = 0.0f; + BigMessageInUse[2] = 1.0f; + } + } + else { + BigMessageInUse[2] = 0.0f; + } + } +} + +void CHud::DrawAfterFade() +{ + if (CTimer::GetIsUserPaused() || CReplay::IsPlayingBack()) + return; + + if (m_HelpMessage[0]) { + if (!CMessages::WideStringCompare(m_HelpMessage, m_LastHelpMessage, HELP_MSG_LENGTH)) { + switch (m_HelpMessageState) { + case 0: + m_HelpMessageFadeTimer = 0; + m_HelpMessageState = 2; + m_HelpMessageTimer = 0; + CMessages::WideStringCopy(m_HelpMessageToPrint, m_HelpMessage, HELP_MSG_LENGTH); + m_HelpMessageDisplayTime = CMessages::GetWideStringLength(m_HelpMessage) / 20.0f + 3.0f; + + if (TheCamera.m_ScreenReductionPercentage == 0.0f) + DMAudio.PlayFrontEndSound(SOUND_HUD, 0); + break; + case 1: + case 2: + case 3: + case 4: + m_HelpMessageTimer = 5; + m_HelpMessageState = 4; + break; + default: + break; + } + CMessages::WideStringCopy(m_LastHelpMessage, m_HelpMessage, HELP_MSG_LENGTH); + } + + float fAlpha = 225.0f; + + if (m_HelpMessageState != 0) { + switch (m_HelpMessageState) { + case 1: + fAlpha = 225.0f; + m_HelpMessageFadeTimer = 600; + if (m_HelpMessageTimer > m_HelpMessageDisplayTime * 1000.0f || m_HelpMessageQuick && m_HelpMessageTimer > 1500.0f) { + m_HelpMessageFadeTimer = 600; + m_HelpMessageState = 3; + } + break; + case 2: + m_HelpMessageFadeTimer += 2 * CTimer::GetTimeStepInMilliseconds(); + if (m_HelpMessageFadeTimer > 0) { + m_HelpMessageState = 1; + m_HelpMessageFadeTimer = 0; + } + fAlpha = m_HelpMessageFadeTimer / 1000.0f * 225.0f; + break; + case 3: + m_HelpMessageFadeTimer -= 2 * CTimer::GetTimeStepInMilliseconds(); + if (m_HelpMessageFadeTimer < 0) { + m_HelpMessageState = 0; + m_HelpMessageFadeTimer = 0; + } + fAlpha = m_HelpMessageFadeTimer / 1000.0f * 225.0f; + break; + case 4: + m_HelpMessageFadeTimer -= 2 * CTimer::GetTimeStepInMilliseconds(); + if (m_HelpMessageFadeTimer < 0) { + m_HelpMessageState = 2; + m_HelpMessageFadeTimer = 0; + CMessages::WideStringCopy(m_HelpMessageToPrint, m_LastHelpMessage, HELP_MSG_LENGTH); + } + fAlpha = m_HelpMessageFadeTimer / 1000.0f * 225.0f; + break; + default: + break; + } + + m_HelpMessageTimer += CTimer::GetTimeStepInMilliseconds(); + + CFont::SetAlphaFade(fAlpha); + CFont::SetCentreOff(); + CFont::SetPropOn(); + + if (CGame::germanGame) + CFont::SetScale(SCREEN_SCALE_X(0.52f * 0.85f), SCREEN_SCALE_Y(1.1f * 0.85f)); +#ifdef MORE_LANGUAGES + else if (CFont::IsJapanese()) + CFont::SetScale(SCREEN_SCALE_X(0.52f) * 1.35f, SCREEN_SCALE_Y(1.1f) * 1.25f); +#endif + else + CFont::SetScale(SCREEN_SCALE_X(0.52f), SCREEN_SCALE_Y(1.1f)); + + CFont::SetColor(CRGBA(175, 175, 175, 255)); + CFont::SetJustifyOff(); +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + CFont::SetWrapx(SCREEN_SCALE_X(229.0f) + SCREEN_SCALE_X(26.0f) - SCREEN_SCALE_X_FIX(4.0f)); + else +#endif + CFont::SetWrapx(SCREEN_SCALE_X(200.0f) + SCREEN_SCALE_X(26.0f) - SCREEN_SCALE_X_FIX(4.0f)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetBackgroundOn(); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetBackgroundColor(CRGBA(0, 0, 0, fAlpha * 0.9f)); + CFont::SetColor(CRGBA(175, 175, 175, 255)); + CFont::PrintString(SCREEN_SCALE_X(26.0f), SCREEN_SCALE_Y(28.0f) + SCREEN_SCALE_Y_FIX((150.0f - PagerXOffset) * 0.6f), m_HelpMessageToPrint); + CFont::SetAlphaFade(255.0f); + } + } + + for (int i = 0; i < ARRAY_SIZE(CTheScripts::IntroTextLines); i++) { + intro_text_line &line = CTheScripts::IntroTextLines[i]; + if (line.m_Text[0] != '\0' && !line.m_bTextBeforeFade) { + + CFont::SetScale(SCREEN_SCALE_X_PC(line.m_fScaleX), SCREEN_SCALE_Y_PC(line.m_fScaleY) +#if !defined(PS2_HUD) || defined(FIX_BUGS) + / 2 +#endif + ); + CFont::SetColor(line.m_sColor); + if (line.m_bJustify) + CFont::SetJustifyOn(); + else + CFont::SetJustifyOff(); + + if (line.m_bRightJustify) + CFont::SetRightJustifyOn(); + else + CFont::SetRightJustifyOff(); + + if (line.m_bCentered) + CFont::SetCentreOn(); + else + CFont::SetCentreOff(); + + CFont::SetWrapx(SCALE_AND_CENTER_X_PC(line.m_fWrapX)); + CFont::SetCentreSize(SCREEN_SCALE_X_PC(line.m_fCenterSize)); + + if (line.m_bBackground) + CFont::SetBackgroundOn(); + else + CFont::SetBackgroundOff(); + + CFont::SetBackgroundColor(line.m_sBackgroundColor); + if (line.m_bBackgroundOnly) + CFont::SetBackGroundOnlyTextOn(); + else + CFont::SetBackGroundOnlyTextOff(); + + if (line.m_bTextProportional) + CFont::SetPropOn(); + else + CFont::SetPropOff(); + + CFont::SetFontStyle(line.m_nFont); +#if defined(PS2_HUD) && !defined(FIX_BUGS) + CFont::PrintString(line.m_fAtX, line.m_fAtY, line.m_Text); +#else + CFont::PrintString(SCALE_AND_CENTER_X(DEFAULT_SCREEN_WIDTH - line.m_fAtX), SCREEN_SCALE_Y(DEFAULT_SCREEN_HEIGHT - line.m_fAtY), line.m_Text); +#endif + } + } + for (int i = 0; i < ARRAY_SIZE(CTheScripts::IntroRectangles); i++) { + intro_script_rectangle &rectangle = CTheScripts::IntroRectangles[i]; + if (rectangle.m_bIsUsed && !rectangle.m_bBeforeFade) { + + // Yeah, top and bottom changed place. R* vision + if (rectangle.m_nTextureId >= 0) { + CTheScripts::ScriptSprites[rectangle.m_nTextureId].Draw(CRect(rectangle.m_sRect.left, rectangle.m_sRect.bottom, + rectangle.m_sRect.right, rectangle.m_sRect.top), rectangle.m_sColor); + } else { + CSprite2d::DrawRect(CRect(rectangle.m_sRect.left, rectangle.m_sRect.bottom, + rectangle.m_sRect.right, rectangle.m_sRect.top), rectangle.m_sColor); + } + } + } + + /* + DrawBigMessage2 + */ + // Oddjob + if (m_BigMessage[3][0]) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X_PC(1.2f), SCREEN_SCALE_Y_PC(1.5f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 40)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString((SCREEN_WIDTH / 2) + SCREEN_SCALE_X_FIX(2.0f), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(BIGMESSAGE_Y) + SCREEN_SCALE_Y_FIX(2.0f), m_BigMessage[3]); + CFont::SetColor(ODDJOB_COLOR); + CFont::PrintString((SCREEN_WIDTH / 2), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(BIGMESSAGE_Y), m_BigMessage[3]); + } + + if (!m_BigMessage[1][0] && m_BigMessage[4][0]) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X_PC(1.2f), SCREEN_SCALE_Y_PC(1.5f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::PrintString((SCREEN_WIDTH / 2) - SCREEN_SCALE_X_FIX(2.0f), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(BIGMESSAGE_Y) - SCREEN_SCALE_Y_FIX(2.0f), m_BigMessage[4]); + CFont::SetColor(ODDJOB_COLOR); + CFont::PrintString((SCREEN_WIDTH / 2), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(BIGMESSAGE_Y), m_BigMessage[4]); + } + + // Oddjob result + if (OddJob2OffTimer > 0) + OddJob2OffTimer -= CTimer::GetTimeStepInMilliseconds(); + + float fStep; + if (m_BigMessage[5][0] && OddJob2OffTimer <= 0.0f) { + switch (OddJob2On) { + case 0: + OddJob2On = 1; + OddJob2XOffset = 380.0f; + break; + case 1: + if (OddJob2XOffset <= 2.0f) { + OddJob2Timer = 0; + OddJob2On = 2; + } + else { + fStep = Min(40.0f, OddJob2XOffset / 6.0f); + OddJob2XOffset = OddJob2XOffset - fStep; + } + break; + case 2: + OddJob2Timer += CTimer::GetTimeStepInMilliseconds(); + if (OddJob2Timer > 1500) { + OddJob2On = 3; + } + break; + case 3: + fStep = Max(30.0f, OddJob2XOffset / 5.0f); + + OddJob2XOffset = OddJob2XOffset - fStep; + + if (OddJob2XOffset < -380.0f) { + OddJob2OffTimer = 5000.0f; + OddJob2On = 0; + } + break; + default: + break; + } + + if (!m_BigMessage[1][0]) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(1.0f), SCREEN_SCALE_Y(1.2f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + // Not bug, we just want these kind of texts to be wrapped at the center. +#ifdef ASPECT_RATIO_SCALE + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20.0f)); +#else + CFont::SetCentreSize(SCREEN_SCALE_FROM_RIGHT(20.0f)); +#endif + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + +#ifdef BETA_SLIDING_TEXT + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X_PC(2.0f) - SCREEN_SCALE_X(OddJob2XOffset), SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(20.0f) + SCREEN_SCALE_Y_PC(2.0f), m_BigMessage[5]); + CFont::SetColor(ODDJOB2_COLOR); + CFont::PrintString(SCREEN_WIDTH / 2 - SCREEN_SCALE_X(OddJob2XOffset), SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(20.0f), m_BigMessage[5]); +#else + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X_PC(2.0f), SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(20.0f) + SCREEN_SCALE_Y_PC(2.0f), m_BigMessage[5]); + CFont::SetColor(ODDJOB2_COLOR); + CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(20.0f), m_BigMessage[5]); +#endif + } + } + + /* + DrawMissionTitle + */ + if (m_BigMessage[1][0]) { + if (BigMessageInUse[1] != 0.0f) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + + if (CGame::frenchGame || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + CFont::SetScale(SCREEN_SCALE_X_PC(0.884f), SCREEN_SCALE_Y_PC(1.36f)); + else + CFont::SetScale(SCREEN_SCALE_X_PC(1.04f), SCREEN_SCALE_Y_PC(1.6f)); + + CFont::SetPropOn(); +#ifdef FIX_BUGS + CFont::SetRightJustifyWrap(SCREEN_SCALE_FROM_RIGHT(DEFAULT_SCREEN_WIDTH + 500.0f)); +#else + CFont::SetRightJustifyWrap(-500.0f); +#endif + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + + if (BigMessageX[1] >= SCREEN_WIDTH - SCREEN_SCALE_X_FIX(20.0f)) + { + BigMessageInUse[1] += CTimer::GetTimeStep(); + + if (BigMessageInUse[1] >= 120.0f) { + BigMessageInUse[1] = 120.0f; + BigMessageAlpha[1] -= (CTimer::GetTimeStepInMilliseconds() * 0.3f); + } + if (BigMessageAlpha[1] <= 0) { + m_BigMessage[1][0] = 0; + BigMessageAlpha[1] = 0.0f; + } + } else { + BigMessageX[1] += SCREEN_SCALE_X_FIX(CTimer::GetTimeStepInMilliseconds() * 0.3f); + BigMessageAlpha[1] += (CTimer::GetTimeStepInMilliseconds() * 0.3f); + + if (BigMessageAlpha[1] > 255.0f) + BigMessageAlpha[1] = 255.0f; + } + + CFont::SetColor(CRGBA(40, 40, 40, BigMessageAlpha[1])); +#ifdef BETA_SLIDING_TEXT + CFont::PrintString(SCREEN_SCALE_X(2.0f) + BigMessageX[1], SCREEN_SCALE_FROM_BOTTOM(120.0f) + SCREEN_SCALE_Y_PC(2.0f), m_BigMessage[1]); + CFont::SetColor(CRGBA(MISSIONTITLE_COLOR.r, MISSIONTITLE_COLOR.g, MISSIONTITLE_COLOR.b, BigMessageAlpha[1])); + CFont::PrintString(BigMessageX[1], SCREEN_SCALE_FROM_BOTTOM(120.0f), m_BigMessage[1]); +#else + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_FROM_BOTTOM(120.0f) + SCREEN_SCALE_Y_PC(2.0f), m_BigMessage[1]); + CFont::SetColor(CRGBA(MISSIONTITLE_COLOR.r, MISSIONTITLE_COLOR.g, MISSIONTITLE_COLOR.b, BigMessageAlpha[1])); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(120.0f), m_BigMessage[1]); +#endif + } + else { + BigMessageAlpha[1] = 0.0f; +#ifdef FIX_BUGS + BigMessageX[1] = SCREEN_SCALE_FROM_RIGHT(DEFAULT_SCREEN_WIDTH + 60.0f); +#else + BigMessageX[1] = -60.0f; +#endif + BigMessageInUse[1] = 1.0f; + } + } + else { + BigMessageInUse[1] = 0.0f; + } +} + +void CHud::SetMessage(wchar *message) +{ + int i = 0; + for (i = 0; i < ARRAY_SIZE(m_Message); i++) { + if (message[i] == 0) + break; + + m_Message[i] = message[i]; + } + m_Message[i] = 0; +} + +void CHud::SetBigMessage(wchar *message, uint16 style) +{ + int i = 0; + + if (style == 5) { + for (i = 0; i < 128; i++) { + if (message[i] == 0) + break; + + if (message[i] != LastBigMessage[5][i]) { + OddJob2On = 0; + OddJob2OffTimer = 0.0f; + } + + m_BigMessage[5][i] = message[i]; + LastBigMessage[5][i] = message[i]; + } + } else { + for (i = 0; i < 128; i++) { + if (message[i] == 0) + break; + m_BigMessage[style][i] = message[i]; + } + } + LastBigMessage[style][i] = 0; + m_BigMessage[style][i] = 0; +#ifndef FIX_BUGS + m_BigMessage[style][i] = 0; +#endif +} + +void CHud::SetPagerMessage(wchar *message) +{ + int i = 0; + for (i = 0; i < ARRAY_SIZE(m_PagerMessage); i++) { + if (message[i] == 0) + break; + + m_PagerMessage[i] = message[i]; + } + m_PagerMessage[i] = 0; +} \ No newline at end of file diff --git a/src/renderer/Hud.h b/src/renderer/Hud.h new file mode 100644 index 0000000..adfdf1f --- /dev/null +++ b/src/renderer/Hud.h @@ -0,0 +1,81 @@ +#pragma once +#include "Sprite2d.h" + +#define HELP_MSG_LENGTH 256 + +enum eItems +{ + ITEM_NONE = -1, + ITEM_ARMOUR = 3, + ITEM_HEALTH = 4, + ITEM_RADAR = 8 +}; + +enum eSprites +{ + HUD_FIST, + HUD_BAT, + HUD_PISTOL, + HUD_UZI, + HUD_SHOTGUN, + HUD_AK47, + HUD_M16, + HUD_SNIPER, + HUD_ROCKET, + HUD_FLAME, + HUD_MOLOTOV, + HUD_GRENADE, + HUD_DETONATOR, + HUD_RADARDISC = 15, + HUD_PAGER = 16, + HUD_SITESNIPER = 20, + HUD_SITEM16, + HUD_SITEROCKET, + NUM_HUD_SPRITES, +}; + +class CHud +{ +public: + static int16 m_ItemToFlash; + static CSprite2d Sprites[NUM_HUD_SPRITES]; + static wchar *m_pZoneName; + static wchar *m_pLastZoneName; + static wchar *m_ZoneToPrint; + static wchar m_Message[256]; + static wchar m_BigMessage[6][128]; + static wchar m_PagerMessage[256]; + static uint32 m_ZoneNameTimer; + static int32 m_ZoneFadeTimer; + static uint32 m_ZoneState; + static wchar m_HelpMessage[HELP_MSG_LENGTH]; + static wchar m_LastHelpMessage[HELP_MSG_LENGTH]; + static wchar m_HelpMessageToPrint[HELP_MSG_LENGTH]; + static uint32 m_HelpMessageTimer; + static int32 m_HelpMessageFadeTimer; + static uint32 m_HelpMessageState; + static bool m_HelpMessageQuick; + static float m_HelpMessageDisplayTime; + static int32 SpriteBrightness; + static bool m_Wants_To_Draw_Hud; + static bool m_Wants_To_Draw_3dMarkers; + static wchar *m_pVehicleName; + static wchar *m_pLastVehicleName; + static uint32 m_VehicleNameTimer; + static int32 m_VehicleFadeTimer; + static uint32 m_VehicleState; + static wchar *m_pVehicleNameToPrint; +public: + static void Initialise(); + static void Shutdown(); + static void ReInitialise(); + static void GetRidOfAllHudMessages(); + static void SetZoneName(wchar *name); + static void SetHelpMessage(wchar *message, bool quick); + static void SetVehicleName(wchar *name); + static void Draw(); + static void DrawAfterFade(); + static void SetMessage(wchar *message); + static void SetBigMessage(wchar *message, uint16 style); + static void SetPagerMessage(wchar *message); +}; diff --git a/src/renderer/Instance.cpp b/src/renderer/Instance.cpp new file mode 100644 index 0000000..be6d73d --- /dev/null +++ b/src/renderer/Instance.cpp @@ -0,0 +1,9 @@ +#include "common.h" + +#include "Instance.h" + +void +CInstance::Shutdown() +{ + GetMatrix().Detach(); +} diff --git a/src/renderer/Instance.h b/src/renderer/Instance.h new file mode 100644 index 0000000..693cfdf --- /dev/null +++ b/src/renderer/Instance.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Placeable.h" + +// unused + +class CInstance : public CPlaceable +{ +public: + int m_modelIndex; +public: + ~CInstance() { } + void Shutdown(); +}; diff --git a/src/renderer/Lines.cpp b/src/renderer/Lines.cpp new file mode 100644 index 0000000..b5c8514 --- /dev/null +++ b/src/renderer/Lines.cpp @@ -0,0 +1,74 @@ +#include "common.h" + +#include "main.h" +#include "Lines.h" + +// This is super inefficient, why split the line into segments at all? +void +CLines::RenderLineWithClipping(float x1, float y1, float z1, float x2, float y2, float z2, uint32 c1, uint32 c2) +{ + static RwIm3DVertex v[2]; +#ifdef THIS_IS_STUPID + int i; + float f1, f2; + float len = sqrt(sq(x1-x2) + sq(y1-y2) + sq(z1-z2)); + int numsegs = len/1.5f + 1.0f; + + RwRGBA col1; + col1.red = c1>>24; + col1.green = c1>>16; + col1.blue = c1>>8; + col1.alpha = c1; + RwRGBA col2; + col2.red = c2>>24; + col2.green = c2>>16; + col2.blue = c2>>8; + col2.alpha = c2; + + float dx = x2 - x1; + float dy = y2 - y1; + float dz = z2 - z1; + for(i = 0; i < numsegs; i++){ + f1 = (float)i/numsegs; + f2 = (float)(i+1)/numsegs; + + RwIm3DVertexSetRGBA(&v[0], (int)(col1.red + (col2.red-col1.red)*f1), + (int)(col1.green + (col2.green-col1.green)*f1), + (int)(col1.blue + (col2.blue-col1.blue)*f1), + (int)(col1.alpha + (col2.alpha-col1.alpha)*f1)); + RwIm3DVertexSetRGBA(&v[1], (int)(col1.red + (col2.red-col1.red)*f2), + (int)(col1.green + (col2.green-col1.green)*f2), + (int)(col1.blue + (col2.blue-col1.blue)*f2), + (int)(col1.alpha + (col2.alpha-col1.alpha)*f2)); + RwIm3DVertexSetPos(&v[0], x1 + dx*f1, y1 + dy*f1, z1 + dz*f1); + RwIm3DVertexSetPos(&v[1], x1 + dx*f2, y1 + dy*f2, z1 + dz*f2); + + LittleTest(); + if(RwIm3DTransform(v, 2, nil, 0)){ + RwIm3DRenderLine(0, 1); + RwIm3DEnd(); + } + } +#else + RwRGBA col1; + col1.red = c1>>24; + col1.green = c1>>16; + col1.blue = c1>>8; + col1.alpha = c1; + RwRGBA col2; + col2.red = c2>>24; + col2.green = c2>>16; + col2.blue = c2>>8; + col2.alpha = c2; + + RwIm3DVertexSetRGBA(&v[0], col1.red, col1.green, col1.blue, col1.alpha); + RwIm3DVertexSetRGBA(&v[1], col2.red, col2.green, col2.blue, col2.alpha); + RwIm3DVertexSetPos(&v[0], x1, y1, z1); + RwIm3DVertexSetPos(&v[1], x2, y2, z2); + LittleTest(); + if(RwIm3DTransform(v, 2, nil, 0)){ + RwIm3DRenderLine(0, 1); + RwIm3DEnd(); + } +#endif +} diff --git a/src/renderer/Lines.h b/src/renderer/Lines.h new file mode 100644 index 0000000..f2694fc --- /dev/null +++ b/src/renderer/Lines.h @@ -0,0 +1,7 @@ +#pragma once + +class CLines +{ +public: + static void RenderLineWithClipping(float x1, float y1, float z1, float x2, float y2, float z2, uint32 c1, uint32 c2); +}; diff --git a/src/renderer/MBlur.cpp b/src/renderer/MBlur.cpp new file mode 100644 index 0000000..8e5fba2 --- /dev/null +++ b/src/renderer/MBlur.cpp @@ -0,0 +1,325 @@ +#ifndef LIBRW +#define WITHD3D +#endif +#include "common.h" +#ifndef LIBRW +#include +#endif + +#include "main.h" +#include "RwHelper.h" +#include "Camera.h" +#include "MBlur.h" +#include "postfx.h" + +// Originally taken from RW example 'mblur' + +RwRaster *CMBlur::pFrontBuffer; +bool CMBlur::ms_bJustInitialised; +bool CMBlur::ms_bScaledBlur; +bool CMBlur::BlurOn; + +static RwIm2DVertex Vertex[4]; +static RwImVertexIndex Index[6] = { 0, 1, 2, 0, 2, 3 }; + +#ifndef LIBRW +extern "C" D3DCAPS8 _RwD3D8DeviceCaps; +#endif +RwBool +CMBlur::MotionBlurOpen(RwCamera *cam) +{ +#ifdef EXTENDED_COLOURFILTER + CPostFX::Open(cam); + return TRUE; +#else +#ifdef GTA_PS2 + RwRect rect = {0, 0, 0, 0}; + + if (pFrontBuffer) + return TRUE; + + BlurOn = true; + + rect.w = RwRasterGetWidth(RwCameraGetRaster(cam)); + rect.h = RwRasterGetHeight(RwCameraGetRaster(cam)); + + pFrontBuffer = RwRasterCreate(0, 0, 0, rwRASTERDONTALLOCATE|rwRASTERTYPECAMERATEXTURE); + if (!pFrontBuffer) + { + printf("Error creating raster\n"); + return FALSE; + } + + RwRaster *raster = RwRasterSubRaster(pFrontBuffer, RwCameraGetRaster(cam), &rect); + if (!raster) + { + RwRasterDestroy(pFrontBuffer); + pFrontBuffer = NULL; + printf("Error subrastering\n"); + return FALSE; + } + + CreateImmediateModeData(cam, &rect); +#else + RwRect rect = { 0, 0, 0, 0 }; + + if(pFrontBuffer) + MotionBlurClose(); + +#ifndef LIBRW + extern void _GetVideoMemInfo(LPDWORD total, LPDWORD avaible); + DWORD total, avaible; + + _GetVideoMemInfo(&total, &avaible); + debug("Available video memory %d\n", avaible); +#endif + + if(BlurOn) + { + uint32 width = Pow(2.0f, int32(log2(RwRasterGetWidth (RwCameraGetRaster(cam))))+1); + uint32 height = Pow(2.0f, int32(log2(RwRasterGetHeight(RwCameraGetRaster(cam))))+1); + uint32 depth = RwRasterGetDepth(RwCameraGetRaster(cam)); + +#ifndef LIBRW + extern DWORD _dwMemTotalVideo; + if ( _RwD3D8DeviceCaps.MaxTextureWidth >= width && _RwD3D8DeviceCaps.MaxTextureHeight >= height ) + { + total = _dwMemTotalVideo - 3 * + ( RwRasterGetDepth(RwCameraGetRaster(cam)) + * RwRasterGetHeight(RwCameraGetRaster(cam)) + * RwRasterGetWidth(RwCameraGetRaster(cam)) / 8 ); + BlurOn = total >= height*width*(depth/8) + (12*1024*1024) /*12 MB*/; + } + else + BlurOn = false; +#endif + + if ( BlurOn ) + { + ms_bScaledBlur = false; + rect.w = width; + rect.h = height; + + pFrontBuffer = RwRasterCreate(rect.w, rect.h, depth, rwRASTERTYPECAMERATEXTURE); + if ( !pFrontBuffer ) + { + debug("MBlurOpen can't create raster."); + BlurOn = false; + rect.w = RwRasterGetWidth(RwCameraGetRaster(cam)); + rect.h = RwRasterGetHeight(RwCameraGetRaster(cam)); + } + else + ms_bJustInitialised = true; + } + else + { + rect.w = RwRasterGetWidth(RwCameraGetRaster(cam)); + rect.h = RwRasterGetHeight(RwCameraGetRaster(cam)); + } + +#ifndef LIBRW + _GetVideoMemInfo(&total, &avaible); + debug("Available video memory %d\n", avaible); +#endif + CreateImmediateModeData(cam, &rect); + } + else + { + rect.w = RwRasterGetWidth(RwCameraGetRaster(cam)); + rect.h = RwRasterGetHeight(RwCameraGetRaster(cam)); + CreateImmediateModeData(cam, &rect); + } + + return TRUE; +#endif +#endif +} + +RwBool +CMBlur::MotionBlurClose(void) +{ +#ifdef EXTENDED_COLOURFILTER + CPostFX::Close(); +#else + if(pFrontBuffer){ + RwRasterDestroy(pFrontBuffer); + pFrontBuffer = nil; + + return TRUE; + } +#endif + return FALSE; +} + +void +CMBlur::CreateImmediateModeData(RwCamera *cam, RwRect *rect) +{ + float zero, xmax, ymax; + + if(RwRasterGetDepth(RwCameraGetRaster(cam)) == 16){ + zero = HALFPX; + xmax = rect->w + HALFPX; + ymax = rect->h + HALFPX; + }else{ + zero = -HALFPX; + xmax = rect->w - HALFPX; + ymax = rect->h - HALFPX; + } + + RwIm2DVertexSetScreenX(&Vertex[0], zero); + RwIm2DVertexSetScreenY(&Vertex[0], zero); + RwIm2DVertexSetScreenZ(&Vertex[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[0], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[1], zero); + RwIm2DVertexSetScreenY(&Vertex[1], ymax); + RwIm2DVertexSetScreenZ(&Vertex[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[1], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[1], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[1], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[2], xmax); + RwIm2DVertexSetScreenY(&Vertex[2], ymax); + RwIm2DVertexSetScreenZ(&Vertex[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[2], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[3], xmax); + RwIm2DVertexSetScreenY(&Vertex[3], zero); + RwIm2DVertexSetScreenZ(&Vertex[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[3], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[3], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[3], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, 255); +} + +void +CMBlur::MotionBlurRender(RwCamera *cam, uint32 red, uint32 green, uint32 blue, uint32 blur, int32 type, uint32 bluralpha) +{ +#ifdef EXTENDED_COLOURFILTER + CPostFX::Render(cam, red, green, blue, blur, type, bluralpha); +#else + PUSH_RENDERGROUP("CMBlur::MotionBlurRender"); + RwRGBA color = { (RwUInt8)red, (RwUInt8)green, (RwUInt8)blue, (RwUInt8)blur }; +#ifdef GTA_PS2 + if( pFrontBuffer ) + OverlayRender(cam, pFrontBuffer, color, type, bluralpha); +#else + if(BlurOn){ + if(pFrontBuffer){ + if(ms_bJustInitialised) + ms_bJustInitialised = false; + else + OverlayRender(cam, pFrontBuffer, color, type, bluralpha); + } + RwRasterPushContext(pFrontBuffer); + RwRasterRenderFast(RwCameraGetRaster(cam), 0, 0); + RwRasterPopContext(); + }else{ + OverlayRender(cam, nil, color, type, bluralpha); + } +#endif + POP_RENDERGROUP(); +#endif +} + +void +CMBlur::OverlayRender(RwCamera *cam, RwRaster *raster, RwRGBA color, int32 type, int32 bluralpha) +{ + int r, g, b, a; + + r = color.red; + g = color.green; + b = color.blue; + a = color.alpha; + + DefinedState(); + + switch(type) + { + case MOTION_BLUR_SECURITY_CAM: + r = 0; + g = 255; + b = 0; + a = 128; + break; + case MOTION_BLUR_INTRO: + r = 100; + g = 220; + b = 230; + a = 158; + break; + case MOTION_BLUR_INTRO2: + r = 80; + g = 255; + b = 230; + a = 138; + break; + case MOTION_BLUR_INTRO3: + r = 255; + g = 60; + b = 60; + a = 200; + break; + case MOTION_BLUR_INTRO4: + r = 255; + g = 180; + b = 180; + a = 128; + break; + } + + if(!BlurOn){ + r = Min(r*0.6f, 255.0f); + g = Min(g*0.6f, 255.0f); + b = Min(b*0.6f, 255.0f); + if(type != MOTION_BLUR_SNIPER) + a = Min(a*0.6f, 255.0f); + // game clamps to 255 here, but why? + } + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[3], r, g, b, a); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, BlurOn ? raster : nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); + + a = bluralpha/2; + if(a < 30) + a = 30; + + if(BlurOn && a != 0){ // the second condition should always be true + RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, a); + RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, a); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); + } + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); +} diff --git a/src/renderer/MBlur.h b/src/renderer/MBlur.h new file mode 100644 index 0000000..e2e5d38 --- /dev/null +++ b/src/renderer/MBlur.h @@ -0,0 +1,17 @@ +#pragma once + +class CMBlur +{ +public: + static RwRaster *pFrontBuffer; + static bool ms_bJustInitialised; + static bool ms_bScaledBlur; + static bool BlurOn; + +public: + static RwBool MotionBlurOpen(RwCamera *cam); + static RwBool MotionBlurClose(void); + static void CreateImmediateModeData(RwCamera *cam, RwRect *rect); + static void MotionBlurRender(RwCamera *cam, uint32 red, uint32 green, uint32 blue, uint32 blur, int32 type, uint32 bluralpha); + static void OverlayRender(RwCamera *cam, RwRaster *raster, RwRGBA color, int32 type, int32 bluralpha); +}; diff --git a/src/renderer/Particle.cpp b/src/renderer/Particle.cpp new file mode 100644 index 0000000..76ddde5 --- /dev/null +++ b/src/renderer/Particle.cpp @@ -0,0 +1,1902 @@ +#include "common.h" + +#include "main.h" +#include "General.h" +#include "Timer.h" +#include "TxdStore.h" +#include "Entity.h" +#include "Sprite.h" +#include "Camera.h" +#include "Collision.h" +#include "World.h" +#include "Shadows.h" +#include "AudioScriptObject.h" +#include "ParticleObject.h" +#include "Particle.h" +#include "soundlist.h" +#include "debugmenu.h" + + +#define MAX_PARTICLES_ON_SCREEN (1000) + + +//(5) +#define MAX_SMOKE_FILES ARRAY_SIZE(SmokeFiles) + +//(5) +#define MAX_SMOKE2_FILES ARRAY_SIZE(Smoke2Files) +//(5) +#define MAX_RUBBER_FILES ARRAY_SIZE(RubberFiles) +//(5) +#define MAX_RAINSPLASH_FILES ARRAY_SIZE(RainSplashFiles) +//(3) +#define MAX_WATERSPRAY_FILES ARRAY_SIZE(WatersprayFiles) +//(6) +#define MAX_EXPLOSIONMEDIUM_FILES ARRAY_SIZE(ExplosionMediumFiles) +//(4) +#define MAX_GUNFLASH_FILES ARRAY_SIZE(GunFlashFiles) +//(2) +#define MAX_RAINSPLASHUP_FILES ARRAY_SIZE(RainSplashupFiles) +//(4) +#define MAX_BIRDFRONT_FILES ARRAY_SIZE(BirdfrontFiles) +//(4) +#define MAX_CARDEBRIS_FILES ARRAY_SIZE(CardebrisFiles) +//(4) +#define MAX_CARSPLASH_FILES ARRAY_SIZE(CarsplashFiles) + +//(4) +#define MAX_RAINDROP_FILES ARRAY_SIZE(RaindropFiles) + + + +const char SmokeFiles[][6+1] = +{ + "smoke1", + "smoke2", + "smoke3", + "smoke4", + "smoke5" +}; + + +const char Smoke2Files[][9+1] = +{ + "smokeII_1", + "smokeII_2", + "smokeII_3", + "smokeII_4", + "smokeII_5" +}; + +const char RubberFiles[][7+1] = +{ + "rubber1", + "rubber2", + "rubber3", + "rubber4", + "rubber5" +}; + +const char RainSplashFiles[][7+1] = +{ + "splash1", + "splash2", + "splash3", + "splash4", + "splash5" +}; + +const char WatersprayFiles[][11+1] = +{ + "waterspray1", + "waterspray2", + "waterspray3" +}; + +const char ExplosionMediumFiles[][7+1] = +{ + "explo01", + "explo02", + "explo03", + "explo04", + "explo05", + "explo06" +}; + +const char GunFlashFiles[][9+1] = +{ + "gunflash1", + "gunflash2", + "gunflash3", + "gunflash4" +}; + +const char RaindropFiles[][9+1] = +{ + "raindrop1", + "raindrop2", + "raindrop3", + "raindrop4" +}; + +const char RainSplashupFiles[][10+1] = +{ + "splash_up1", + "splash_up2" +}; + +const char BirdfrontFiles[][8+1] = +{ + "birdf_01", + "birdf_02", + "birdf_03", + "birdf_04" +}; + +const char CardebrisFiles[][12+1] = +{ + "cardebris_01", + "cardebris_02", + "cardebris_03", + "cardebris_04" +}; + +const char CarsplashFiles[][12+1] = +{ + "carsplash_01", + "carsplash_02", + "carsplash_03", + "carsplash_04" +}; + +CParticle gParticleArray[MAX_PARTICLES_ON_SCREEN]; + +RwTexture *gpSmokeTex[MAX_SMOKE_FILES]; +RwTexture *gpSmoke2Tex[MAX_SMOKE2_FILES]; +RwTexture *gpRubberTex[MAX_RUBBER_FILES]; +RwTexture *gpRainSplashTex[MAX_RAINSPLASH_FILES]; +RwTexture *gpWatersprayTex[MAX_WATERSPRAY_FILES]; +RwTexture *gpExplosionMediumTex[MAX_EXPLOSIONMEDIUM_FILES]; +RwTexture *gpGunFlashTex[MAX_GUNFLASH_FILES]; +RwTexture *gpRainSplashupTex[MAX_RAINSPLASHUP_FILES]; +RwTexture *gpBirdfrontTex[MAX_BIRDFRONT_FILES]; +RwTexture *gpCarDebrisTex[MAX_CARDEBRIS_FILES]; +RwTexture *gpCarSplashTex[MAX_CARSPLASH_FILES]; + +RwTexture *gpFlame1Tex; +RwTexture *gpFlame5Tex; +RwTexture *gpRainDropSmallTex; +RwTexture *gpBloodTex; +RwTexture *gpLeafTex; +RwTexture *gpCloudTex1; // unused +RwTexture *gpCloudTex4; +RwTexture *gpBloodSmallTex; +RwTexture *gpGungeTex; +RwTexture *gpCollisionSmokeTex; +RwTexture *gpBulletHitTex; +RwTexture *gpGunShellTex; +RwTexture *gpWakeOldTex; +RwTexture *gpPointlightTex; + +RwRaster *gpSmokeRaster[MAX_SMOKE_FILES]; +RwRaster *gpSmoke2Raster[MAX_SMOKE2_FILES]; +RwRaster *gpRubberRaster[MAX_RUBBER_FILES]; +RwRaster *gpRainSplashRaster[MAX_RAINSPLASH_FILES]; +RwRaster *gpWatersprayRaster[MAX_WATERSPRAY_FILES]; +RwRaster *gpExplosionMediumRaster[MAX_EXPLOSIONMEDIUM_FILES]; +RwRaster *gpGunFlashRaster[MAX_GUNFLASH_FILES]; +RwRaster *gpRainSplashupRaster[MAX_RAINSPLASHUP_FILES]; +RwRaster *gpBirdfrontRaster[MAX_BIRDFRONT_FILES]; +RwRaster *gpCarDebrisRaster[MAX_CARDEBRIS_FILES]; +RwRaster *gpCarSplashRaster[MAX_CARSPLASH_FILES]; + +RwRaster *gpFlame1Raster; +RwRaster *gpFlame5Raster; +RwRaster *gpRainDropSmallRaster; +RwRaster *gpBloodRaster; +RwRaster *gpLeafRaster; +RwRaster *gpCloudRaster1; // unused +RwRaster *gpCloudRaster4; +RwRaster *gpBloodSmallRaster; +RwRaster *gpGungeRaster; +RwRaster *gpCollisionSmokeRaster; +RwRaster *gpBulletHitRaster; +RwRaster *gpGunShellRaster; +RwRaster *gpWakeOldRaster; + + +RwRaster *gpPointlightRaster; // CPointLights::RenderFogEffect + +RwTexture *gpRainDropTex[MAX_RAINDROP_FILES]; // CWeather::RenderRainStreaks + + +RwRaster *gpRainDropRaster[MAX_RAINDROP_FILES]; + +float CParticle::ms_afRandTable[CParticle::RAND_TABLE_SIZE]; + + +CParticle *CParticle::m_pUnusedListHead; + + +float CParticle::m_SinTable[CParticle::SIN_COS_TABLE_SIZE]; +float CParticle::m_CosTable[CParticle::SIN_COS_TABLE_SIZE]; + +int32 Randomizer; + +int32 nParticleCreationInterval = 1; +float fParticleScaleLimit = 0.5f; + +#ifdef DEBUGMENU +SETTWEAKPATH("Particle"); +TWEAKINT32(nParticleCreationInterval, 0, 5, 1); +TWEAKFLOAT(fParticleScaleLimit, 0.0f, 1.0f, 0.1f); +TWEAKFUNC(CParticle::ReloadConfig); +#endif + +void CParticle::ReloadConfig() +{ + debug("Initialising CParticleMgr..."); + + mod_ParticleSystemManager.Initialise(); + + debug("Initialising CParticle..."); + + m_pUnusedListHead = gParticleArray; + + for ( int32 i = 0; i < MAX_PARTICLES_ON_SCREEN; i++ ) + { + if ( i == MAX_PARTICLES_ON_SCREEN - 1 ) + gParticleArray[i].m_pNext = nil; + else + gParticleArray[i].m_pNext = &gParticleArray[i + 1]; + + gParticleArray[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f); + + gParticleArray[i].m_vecVelocity = CVector(0.0f, 0.0f, 0.0f); + + gParticleArray[i].m_nTimeWhenWillBeDestroyed = 0; + + gParticleArray[i].m_nTimeWhenColorWillBeChanged = 0; + + gParticleArray[i].m_fSize = 0.2f; + + gParticleArray[i].m_fExpansionRate = 0.0f; + + gParticleArray[i].m_nColorIntensity = 255; + + gParticleArray[i].m_nFadeToBlackTimer = 0; + + gParticleArray[i].m_nAlpha = 255; + + gParticleArray[i].m_nFadeAlphaTimer = 0; + + gParticleArray[i].m_nCurrentZRotation = 0; + + gParticleArray[i].m_nZRotationTimer = 0; + + gParticleArray[i].m_fCurrentZRadius = 0.0f; + + gParticleArray[i].m_nZRadiusTimer = 0; + + gParticleArray[i].m_nCurrentFrame = 0; + + gParticleArray[i].m_nAnimationSpeedTimer = 0; + + gParticleArray[i].m_nRotation = 0; + + gParticleArray[i].m_nRotationStep = 0; + } +} + +void CParticle::Initialise() +{ + ReloadConfig(); + + CParticleObject::Initialise(); + + float randVal = -1.0f; + for ( int32 i = 0; i < RAND_TABLE_SIZE; i++ ) + { + ms_afRandTable[i] = randVal; + randVal += 0.1f; + } + + for ( int32 i = 0; i < SIN_COS_TABLE_SIZE; i++ ) + { + float angle = DEGTORAD(float(i) * float(360.0f / SIN_COS_TABLE_SIZE)); + + m_SinTable[i] = ::Sin(angle); + m_CosTable[i] = ::Cos(angle); + } + + int32 slot = CTxdStore::FindTxdSlot("particle"); + + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(slot); + + for ( int32 i = 0; i < MAX_SMOKE_FILES; i++ ) + { + gpSmokeTex[i] = RwTextureRead(SmokeFiles[i], nil); + gpSmokeRaster[i] = RwTextureGetRaster(gpSmokeTex[i]); + } + + for ( int32 i = 0; i < MAX_SMOKE2_FILES; i++ ) + { + gpSmoke2Tex[i] = RwTextureRead(Smoke2Files[i], nil); + gpSmoke2Raster[i] = RwTextureGetRaster(gpSmoke2Tex[i]); + } + + for ( int32 i = 0; i < MAX_RUBBER_FILES; i++ ) + { + gpRubberTex[i] = RwTextureRead(RubberFiles[i], nil); + gpRubberRaster[i] = RwTextureGetRaster(gpRubberTex[i]); + } + + for ( int32 i = 0; i < MAX_RAINSPLASH_FILES; i++ ) + { + gpRainSplashTex[i] = RwTextureRead(RainSplashFiles[i], nil); + gpRainSplashRaster[i] = RwTextureGetRaster(gpRainSplashTex[i]); + } + + for ( int32 i = 0; i < MAX_WATERSPRAY_FILES; i++ ) + { + gpWatersprayTex[i] = RwTextureRead(WatersprayFiles[i], nil); + gpWatersprayRaster[i] = RwTextureGetRaster(gpWatersprayTex[i]); + } + + for ( int32 i = 0; i < MAX_EXPLOSIONMEDIUM_FILES; i++ ) + { + gpExplosionMediumTex[i] = RwTextureRead(ExplosionMediumFiles[i], nil); + gpExplosionMediumRaster[i] = RwTextureGetRaster(gpExplosionMediumTex[i]); + } + + for ( int32 i = 0; i < MAX_GUNFLASH_FILES; i++ ) + { + gpGunFlashTex[i] = RwTextureRead(GunFlashFiles[i], NULL); + gpGunFlashRaster[i] = RwTextureGetRaster(gpGunFlashTex[i]); + } + + for ( int32 i = 0; i < MAX_RAINDROP_FILES; i++ ) + { + gpRainDropTex[i] = RwTextureRead(RaindropFiles[i], nil); + gpRainDropRaster[i] = RwTextureGetRaster(gpRainDropTex[i]); + } + + for ( int32 i = 0; i < MAX_RAINSPLASHUP_FILES; i++ ) + { + gpRainSplashupTex[i] = RwTextureRead(RainSplashupFiles[i], nil); + gpRainSplashupRaster[i] = RwTextureGetRaster(gpRainSplashupTex[i]); + } + + for ( int32 i = 0; i < MAX_BIRDFRONT_FILES; i++ ) + { + gpBirdfrontTex[i] = RwTextureRead(BirdfrontFiles[i], NULL); + gpBirdfrontRaster[i] = RwTextureGetRaster(gpBirdfrontTex[i]); + } + + for ( int32 i = 0; i < MAX_CARDEBRIS_FILES; i++ ) + { + gpCarDebrisTex[i] = RwTextureRead(CardebrisFiles[i], nil); + gpCarDebrisRaster[i] = RwTextureGetRaster(gpCarDebrisTex[i]); + } + + for ( int32 i = 0; i < MAX_CARSPLASH_FILES; i++ ) + { + gpCarSplashTex[i] = RwTextureRead(CarsplashFiles[i], nil); + gpCarSplashRaster[i] = RwTextureGetRaster(gpCarSplashTex[i]); + } + + gpFlame1Tex = RwTextureRead("flame1", NULL); + gpFlame1Raster = RwTextureGetRaster(gpFlame1Tex); + + gpFlame5Tex = RwTextureRead("flame5", nil); + +//#ifdef FIX_BUGS +#if 0 + gpFlame5Raster = RwTextureGetRaster(gpFlame5Tex); +#else + // this seems to have become more of a design choice + gpFlame5Raster = RwTextureGetRaster(gpFlame1Tex); // copy-paste bug ? +#endif + + gpRainDropSmallTex = RwTextureRead("rainsmall", nil); + gpRainDropSmallRaster = RwTextureGetRaster(gpRainDropSmallTex); + + gpBloodTex = RwTextureRead("blood", nil); + gpBloodRaster = RwTextureGetRaster(gpBloodTex); + + gpLeafTex = RwTextureRead("gameleaf01_64", nil); + gpLeafRaster = RwTextureGetRaster(gpLeafTex); + + gpCloudTex1 = RwTextureRead("cloud3", nil); + gpCloudRaster1 = RwTextureGetRaster(gpCloudTex1); + + gpCloudTex4 = RwTextureRead("cloudmasked", nil); + gpCloudRaster4 = RwTextureGetRaster(gpCloudTex4); + + gpBloodSmallTex = RwTextureRead("bloodsplat2", nil); + gpBloodSmallRaster = RwTextureGetRaster(gpBloodSmallTex); + + gpGungeTex = RwTextureRead("gunge", nil); + gpGungeRaster = RwTextureGetRaster(gpGungeTex); + + gpCollisionSmokeTex = RwTextureRead("collisionsmoke", nil); + gpCollisionSmokeRaster = RwTextureGetRaster(gpCollisionSmokeTex); + + gpBulletHitTex = RwTextureRead("bullethitsmoke", nil); + gpBulletHitRaster = RwTextureGetRaster(gpBulletHitTex); + + gpGunShellTex = RwTextureRead("gunshell", nil); + gpGunShellRaster = RwTextureGetRaster(gpGunShellTex); + + gpWakeOldTex = RwTextureRead("wake_old", nil); + gpWakeOldRaster = RwTextureGetRaster(gpWakeOldTex); + + gpPointlightTex = RwTextureRead("pointlight", nil); + gpPointlightRaster = RwTextureGetRaster(gpPointlightTex); + + CTxdStore::PopCurrentTxd(); + + for ( int32 i = 0; i < MAX_PARTICLES; i++ ) + { + tParticleSystemData *entry = &mod_ParticleSystemManager.m_aParticles[i]; + + switch ( i ) + { + case PARTICLE_BLOOD: + entry->m_ppRaster = &gpBloodRaster; + break; + + case PARTICLE_BLOOD_SMALL: + case PARTICLE_BLOOD_SPURT: + entry->m_ppRaster = &gpBloodSmallRaster; + break; + + case PARTICLE_DEBRIS2: + entry->m_ppRaster = &gpGungeRaster; + break; + + case PARTICLE_GUNFLASH: + case PARTICLE_GUNFLASH_NOANIM: + entry->m_ppRaster = gpGunFlashRaster; + break; + + case PARTICLE_GUNSMOKE: + case PARTICLE_SPLASH: + entry->m_ppRaster = nil; + break; + + case PARTICLE_FLAME: + case PARTICLE_CARFLAME: + entry->m_ppRaster = &gpFlame1Raster; + break; + + case PARTICLE_FIREBALL: + entry->m_ppRaster = &gpFlame5Raster; + break; + + case PARTICLE_RAIN_SPLASH: + case PARTICLE_RAIN_SPLASH_BIGGROW: + entry->m_ppRaster = gpRainSplashRaster; + break; + + case PARTICLE_RAIN_SPLASHUP: + entry->m_ppRaster = gpRainSplashupRaster; + break; + + case PARTICLE_WATERSPRAY: + entry->m_ppRaster = gpWatersprayRaster; + break; + + case PARTICLE_SHARD: + case PARTICLE_RAINDROP: + case PARTICLE_RAINDROP_2D: + entry->m_ppRaster = gpRainDropRaster; + break; + + case PARTICLE_EXPLOSION_MEDIUM: + case PARTICLE_EXPLOSION_LARGE: + case PARTICLE_EXPLOSION_MFAST: + case PARTICLE_EXPLOSION_LFAST: + entry->m_ppRaster = gpExplosionMediumRaster; + break; + + case PARTICLE_BOAT_WAKE: + entry->m_ppRaster = &gpWakeOldRaster; + break; + + case PARTICLE_CAR_SPLASH: + case PARTICLE_WATER_HYDRANT: + case PARTICLE_PED_SPLASH: + entry->m_ppRaster = gpCarSplashRaster; + break; + + case PARTICLE_SPARK: + case PARTICLE_SPARK_SMALL: + case PARTICLE_RAINDROP_SMALL: + case PARTICLE_HELI_ATTACK: + entry->m_ppRaster = &gpRainDropSmallRaster; + break; + + case PARTICLE_DEBRIS: + case PARTICLE_TREE_LEAVES: + entry->m_ppRaster = &gpLeafRaster; + break; + + case PARTICLE_CAR_DEBRIS: + case PARTICLE_HELI_DEBRIS: + entry->m_ppRaster = gpCarDebrisRaster; + break; + + case PARTICLE_WHEEL_DIRT: + case PARTICLE_STEAM2: + case PARTICLE_STEAM_NY: + case PARTICLE_STEAM_NY_SLOWMOTION: + case PARTICLE_ENGINE_STEAM: + case PARTICLE_BOAT_THRUSTJET: + case PARTICLE_PEDFOOT_DUST: + case PARTICLE_EXHAUST_FUMES: + entry->m_ppRaster = gpSmoke2Raster; + break; + + case PARTICLE_GUNSMOKE2: + case PARTICLE_RUBBER_SMOKE: + entry->m_ppRaster = gpRubberRaster; + break; + + case PARTICLE_CARCOLLISION_DUST: + case PARTICLE_BURNINGRUBBER_SMOKE: + entry->m_ppRaster = &gpCollisionSmokeRaster; + break; + + case PARTICLE_WHEEL_WATER: + case PARTICLE_WATER: + case PARTICLE_SMOKE: + case PARTICLE_SMOKE_SLOWMOTION: + case PARTICLE_GARAGEPAINT_SPRAY: + case PARTICLE_STEAM: + case PARTICLE_BOAT_SPLASH: + case PARTICLE_WATER_CANNON: + case PARTICLE_EXTINGUISH_STEAM: + case PARTICLE_HELI_DUST: + case PARTICLE_PAINT_SMOKE: + case PARTICLE_BULLETHIT_SMOKE: + entry->m_ppRaster = gpSmokeRaster; + break; + + case PARTICLE_GUNSHELL_FIRST: + case PARTICLE_GUNSHELL: + case PARTICLE_GUNSHELL_BUMP1: + case PARTICLE_GUNSHELL_BUMP2: + entry->m_ppRaster = &gpGunShellRaster; + break; + + case PARTICLE_ENGINE_SMOKE: + case PARTICLE_ENGINE_SMOKE2: + case PARTICLE_CARFLAME_SMOKE: + case PARTICLE_FIREBALL_SMOKE: + case PARTICLE_TEST: + entry->m_ppRaster = &gpCloudRaster4; + break; + + case PARTICLE_BIRD_FRONT: + entry->m_ppRaster = gpBirdfrontRaster; + break; + } + } + + debug("CParticle ready"); +} + +void +CEntity::AddSteamsFromGround(CVector *unused) +{ + int i, n; + C2dEffect *effect; + CVector pos; + + n = CModelInfo::GetModelInfo(GetModelIndex())->GetNum2dEffects(); + for(i = 0; i < n; i++){ + effect = CModelInfo::GetModelInfo(GetModelIndex())->Get2dEffect(i); + if(effect->type != EFFECT_PARTICLE) + continue; + + pos = GetMatrix() * effect->pos; + switch(effect->particle.particleType){ + case 0: + CParticleObject::AddObject(POBJECT_PAVEMENT_STEAM, pos, effect->particle.dir, effect->particle.scale, false); + break; + case 1: + CParticleObject::AddObject(POBJECT_WALL_STEAM, pos, effect->particle.dir, effect->particle.scale, false); + break; + case 2: + CParticleObject::AddObject(POBJECT_DRY_ICE, pos, effect->particle.scale, false); + break; + case 3: + CParticleObject::AddObject(POBJECT_SMALL_FIRE, pos, effect->particle.dir, effect->particle.scale, false); + break; + case 4: + CParticleObject::AddObject(POBJECT_DARK_SMOKE, pos, effect->particle.dir, effect->particle.scale, false); + break; + } + } +} + +void CParticle::Shutdown() +{ + debug("Shutting down CParticle..."); + + for ( int32 i = 0; i < MAX_SMOKE_FILES; i++ ) + { + RwTextureDestroy(gpSmokeTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpSmokeTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_SMOKE2_FILES; i++ ) + { + RwTextureDestroy(gpSmoke2Tex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpSmoke2Tex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_RUBBER_FILES; i++ ) + { + RwTextureDestroy(gpRubberTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpRubberTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_RAINSPLASH_FILES; i++ ) + { + RwTextureDestroy(gpRainSplashTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpRainSplashTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_WATERSPRAY_FILES; i++ ) + { + RwTextureDestroy(gpWatersprayTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpWatersprayTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_EXPLOSIONMEDIUM_FILES; i++ ) + { + RwTextureDestroy(gpExplosionMediumTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpExplosionMediumTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_GUNFLASH_FILES; i++ ) + { + RwTextureDestroy(gpGunFlashTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpGunFlashTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_RAINDROP_FILES; i++ ) + { + RwTextureDestroy(gpRainDropTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpRainDropTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_RAINSPLASHUP_FILES; i++ ) + { + RwTextureDestroy(gpRainSplashupTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpRainSplashupTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_BIRDFRONT_FILES; i++ ) + { + RwTextureDestroy(gpBirdfrontTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpBirdfrontTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_CARDEBRIS_FILES; i++ ) + { + RwTextureDestroy(gpCarDebrisTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpCarDebrisTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_CARSPLASH_FILES; i++ ) + { + RwTextureDestroy(gpCarSplashTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpCarSplashTex[i] = nil; +#endif + } + + RwTextureDestroy(gpFlame1Tex); +#if GTA_VERSION >= GTA3_PC_11 + gpFlame1Tex = nil; +#endif + + RwTextureDestroy(gpFlame5Tex); +#if GTA_VERSION >= GTA3_PC_11 + gpFlame5Tex = nil; +#endif + + RwTextureDestroy(gpRainDropSmallTex); +#if GTA_VERSION >= GTA3_PC_11 + gpRainDropSmallTex = nil; +#endif + + RwTextureDestroy(gpBloodTex); +#if GTA_VERSION >= GTA3_PC_11 + gpBloodTex = nil; +#endif + + RwTextureDestroy(gpLeafTex); +#if GTA_VERSION >= GTA3_PC_11 + gpLeafTex = nil; +#endif + + RwTextureDestroy(gpCloudTex1); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex1 = nil; +#endif + + RwTextureDestroy(gpCloudTex4); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex4 = nil; +#endif + + RwTextureDestroy(gpBloodSmallTex); +#if GTA_VERSION >= GTA3_PC_11 + gpBloodSmallTex = nil; +#endif + + RwTextureDestroy(gpGungeTex); +#if GTA_VERSION >= GTA3_PC_11 + gpGungeTex = nil; +#endif + + RwTextureDestroy(gpCollisionSmokeTex); +#if GTA_VERSION >= GTA3_PC_11 + gpCollisionSmokeTex = nil; +#endif + + RwTextureDestroy(gpBulletHitTex); +#if GTA_VERSION >= GTA3_PC_11 + gpBulletHitTex = nil; +#endif + + RwTextureDestroy(gpGunShellTex); +#if GTA_VERSION >= GTA3_PC_11 + gpGunShellTex = nil; +#endif + + RwTextureDestroy(gpWakeOldTex); +#if GTA_VERSION >= GTA3_PC_11 + gpWakeOldTex = nil; +#endif + + RwTextureDestroy(gpPointlightTex); +#if GTA_VERSION >= GTA3_PC_11 + gpPointlightTex = nil; +#endif + + int32 slot; + + slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::RemoveTxdSlot(slot); + + debug("CParticle shut down"); +} + +CParticle *CParticle::AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity, float fSize, int32 nRotationSpeed, int32 nRotation, int32 nCurFrame, int32 nLifeSpan) +{ + CRGBA color(0, 0, 0, 0); + return AddParticle(type, vecPos, vecDir, pEntity, fSize, color, nRotationSpeed, nRotation, nCurFrame, nLifeSpan); +} + +CParticle *CParticle::AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity, float fSize, RwRGBA const &color, int32 nRotationSpeed, int32 nRotation, int32 nCurFrame, int32 nLifeSpan) +{ + if ( CTimer::GetIsPaused() ) + return NULL; + +#ifdef PC_PARTICLE + if ( ( type == PARTICLE_ENGINE_SMOKE + || type == PARTICLE_ENGINE_SMOKE2 + || type == PARTICLE_ENGINE_STEAM + || type == PARTICLE_CARFLAME_SMOKE + || type == PARTICLE_RUBBER_SMOKE + || type == PARTICLE_BURNINGRUBBER_SMOKE + || type == PARTICLE_EXHAUST_FUMES + || type == PARTICLE_CARCOLLISION_DUST ) + && nParticleCreationInterval & CTimer::GetFrameCounter() ) + { + return nil; + } +#endif + + CParticle *pParticle = m_pUnusedListHead; + + if ( pParticle == nil ) + return nil; + + tParticleSystemData *psystem = &mod_ParticleSystemManager.m_aParticles[type]; + + if ( psystem->m_fCreateRange != 0.0f && psystem->m_fCreateRange < ( TheCamera.GetPosition() - vecPos ).MagnitudeSqr() ) + return nil; + + + pParticle->m_fSize = psystem->m_fDefaultInitialRadius; + pParticle->m_fExpansionRate = psystem->m_fExpansionRate; + + if ( nLifeSpan != 0 ) + pParticle->m_nTimeWhenWillBeDestroyed = CTimer::GetTimeInMilliseconds() + nLifeSpan; + else + pParticle->m_nTimeWhenWillBeDestroyed = CTimer::GetTimeInMilliseconds() + psystem->m_nLifeSpan; + + pParticle->m_nColorIntensity = psystem->m_nFadeToBlackInitialIntensity; + pParticle->m_nAlpha = psystem->m_nFadeAlphaInitialIntensity; + pParticle->m_nCurrentZRotation = psystem->m_nZRotationInitialAngle; + pParticle->m_fCurrentZRadius = psystem->m_fInitialZRadius; + + if ( nCurFrame != 0 ) + pParticle->m_nCurrentFrame = nCurFrame; + else + pParticle->m_nCurrentFrame = psystem->m_nStartAnimationFrame; + + pParticle->m_nFadeToBlackTimer = 0; + pParticle->m_nFadeAlphaTimer = 0; + pParticle->m_nZRotationTimer = 0; + pParticle->m_nZRadiusTimer = 0; + pParticle->m_nAnimationSpeedTimer = 0; + pParticle->m_fZGround = 0.0f; + pParticle->m_vecPosition = vecPos; + pParticle->m_vecVelocity = vecDir; + pParticle->m_vecParticleMovementOffset = CVector(0.0f, 0.0f, 0.0f); + pParticle->m_nTimeWhenColorWillBeChanged = 0; + + if ( color.alpha != 0 ) + RwRGBAAssign(&pParticle->m_Color, &color); + else + { + RwRGBAAssign(&pParticle->m_Color, &psystem->m_RenderColouring); + + if ( psystem->m_ColorFadeTime != 0 ) + pParticle->m_nTimeWhenColorWillBeChanged = CTimer::GetTimeInMilliseconds() + psystem->m_ColorFadeTime; + + if ( psystem->m_InitialColorVariation != 0 ) + { + int32 ColorVariation = CGeneral::GetRandomNumberInRange(-psystem->m_InitialColorVariation, psystem->m_InitialColorVariation); + //Float ColorVariation = CGeneral::GetRandomNumberInRange((float)-psystem->m_InitialColorVariation, (float)psystem->m_InitialColorVariation); + + pParticle->m_Color.red = Clamp(pParticle->m_Color.red + + PERCENT(pParticle->m_Color.red, ColorVariation), + 0, 255); + + pParticle->m_Color.green = Clamp(pParticle->m_Color.green + + PERCENT(pParticle->m_Color.green, ColorVariation), + 0, 255); + + pParticle->m_Color.blue = Clamp(pParticle->m_Color.blue + + PERCENT(pParticle->m_Color.blue, ColorVariation), + 0, 255); + } + } + + pParticle->m_nRotation = nRotation; + +// PC only + if ( pParticle->m_nRotation >= 360 ) + pParticle->m_nRotation -= 360; + else if ( pParticle->m_nRotation < 0 ) + pParticle->m_nRotation += 360; + + if ( nRotationSpeed != 0 ) + pParticle->m_nRotationStep = nRotationSpeed; + else + pParticle->m_nRotationStep = psystem->m_nRotationSpeed; + + if ( CGeneral::GetRandomNumber() & 1 ) + pParticle->m_nRotationStep = -pParticle->m_nRotationStep; + + pParticle->m_vecScreenPosition.x = 0.0f; // bug ? + + if ( psystem->m_fPositionRandomError != 0.0f ) + { + pParticle->m_vecPosition.x += psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + pParticle->m_vecPosition.y += psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + + if ( psystem->Flags & RAND_VERT_V ) + pParticle->m_vecPosition.z += psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + } + + if ( psystem->m_fVelocityRandomError != 0.0f ) + { + pParticle->m_vecVelocity.x += psystem->m_fVelocityRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + pParticle->m_vecVelocity.y += psystem->m_fVelocityRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + + if ( psystem->Flags & RAND_VERT_V ) + pParticle->m_vecVelocity.z += psystem->m_fVelocityRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + } + + if ( psystem->m_fExpansionRateError != 0.0f ) + pParticle->m_fExpansionRate += psystem->m_fExpansionRateError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE] + psystem->m_fExpansionRateError; + + if ( psystem->m_nRotationRateError != 0 ) + pParticle->m_nRotationStep += CGeneral::GetRandomNumberInRange(-psystem->m_nRotationRateError, psystem->m_nRotationRateError); + + if ( psystem->m_nLifeSpanErrorShape != 0 ) + { + float randVal = ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + if ( randVal > 0.0f ) + pParticle->m_nTimeWhenWillBeDestroyed += int32(float(psystem->m_nLifeSpan) * randVal * float(psystem->m_nLifeSpanErrorShape)); + else + pParticle->m_nTimeWhenWillBeDestroyed += int32(float(psystem->m_nLifeSpan) * randVal / float(psystem->m_nLifeSpanErrorShape)); + } + + if ( psystem->Flags & ZCHECK_FIRST ) + { + static bool bValidGroundFound = false; + static CVector LastTestCoors; + static float LastTestGroundZ; + + if ( bValidGroundFound + && vecPos.x == LastTestCoors.x + && vecPos.y == LastTestCoors.y + && vecPos.z == LastTestCoors.z ) + { + pParticle->m_fZGround = LastTestGroundZ; + } + else + { + bValidGroundFound = false; + + CColPoint point; + CEntity *entity; + + if ( !CWorld::ProcessVerticalLine( + pParticle->m_vecPosition + CVector(0.0f, 0.0f, 0.5f), + -100.0f, point, entity, true, true, false, false, true, false, nil) ) + { + return nil; + } + + if ( point.point.z >= pParticle->m_vecPosition.z ) + return nil; + + pParticle->m_fZGround = point.point.z; + bValidGroundFound = true; + LastTestCoors = vecPos; + LastTestGroundZ = point.point.z; + } + } + + if ( psystem->Flags & ZCHECK_BUMP ) + { + static float Z_Ground = 0.0f; + + if ( psystem->Flags & ZCHECK_BUMP_FIRST ) + { + bool bZFound = false; + + Z_Ground = CWorld::FindGroundZFor3DCoord(vecPos.x, vecPos.y, vecPos.z, (bool *)&bZFound); + + if ( bZFound == false ) + return nil; + + pParticle->m_fZGround = Z_Ground; + } + + pParticle->m_fZGround = Z_Ground; + } + + switch ( type ) + { + case PARTICLE_DEBRIS: + pParticle->m_vecVelocity.z *= CGeneral::GetRandomNumberInRange(0.5f, 3.0f); + break; + + case PARTICLE_EXPLOSION_MEDIUM: + pParticle->m_nColorIntensity -= 30 * (CGeneral::GetRandomNumber() & 1); // mb "+= -30 * rand" here ? + pParticle->m_nAnimationSpeedTimer = CGeneral::GetRandomNumber() & 7; + pParticle->m_fSize = CGeneral::GetRandomNumberInRange(0.3f, 0.8f); + pParticle->m_vecPosition.z -= CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + break; + + case PARTICLE_EXPLOSION_LARGE: + pParticle->m_nColorIntensity -= 30 * (CGeneral::GetRandomNumber() & 1); // mb "+= -30 * rand" here ? + pParticle->m_nAnimationSpeedTimer = CGeneral::GetRandomNumber() & 7; + pParticle->m_fSize = CGeneral::GetRandomNumberInRange(0.8f, 1.4f); + pParticle->m_vecPosition.z -= CGeneral::GetRandomNumberInRange(-0.3f, 0.3f); + break; + + case PARTICLE_WATER_HYDRANT: + pParticle->m_vecPosition.z += 20.0f * psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + break; + default: break; + } + + if ( fSize != 0.0f ) + pParticle->m_fSize = fSize; + + m_pUnusedListHead = pParticle->m_pNext; + + pParticle->m_pNext = psystem->m_pParticles; + + psystem->m_pParticles = pParticle; + + return pParticle; +} + +void CParticle::Update() +{ + if ( CTimer::GetIsPaused() ) + return; + + CRGBA color(0, 0, 0, 0); + + float fFricDeccel50 = pow(0.50f, CTimer::GetTimeStep()); + float fFricDeccel80 = pow(0.80f, CTimer::GetTimeStep()); + float fFricDeccel90 = pow(0.90f, CTimer::GetTimeStep()); + float fFricDeccel95 = pow(0.95f, CTimer::GetTimeStep()); + float fFricDeccel96 = pow(0.96f, CTimer::GetTimeStep()); + float fFricDeccel99 = pow(0.99f, CTimer::GetTimeStep()); + + CParticleObject::UpdateAll(); + + for ( int32 i = 0; i < MAX_PARTICLES; i++ ) + { + tParticleSystemData *psystem = &mod_ParticleSystemManager.m_aParticles[i]; + CParticle *particle = psystem->m_pParticles; + CParticle *prevParticle = nil; + bool bRemoveParticle; + + if ( particle == nil ) + continue; + + for ( ; particle != nil; _Next(particle, prevParticle, psystem, bRemoveParticle) ) + { + bRemoveParticle = false; + + CVector moveStep = particle->m_vecPosition + ( particle->m_vecVelocity * CTimer::GetTimeStep() ); + + if ( CTimer::GetTimeInMilliseconds() > particle->m_nTimeWhenWillBeDestroyed || particle->m_nAlpha == 0 ) + { + bRemoveParticle = true; + continue; + } + + if ( particle->m_nTimeWhenColorWillBeChanged != 0 ) + { + if ( particle->m_nTimeWhenColorWillBeChanged > CTimer::GetTimeInMilliseconds() ) + { + float colorMul = 1.0f - float(particle->m_nTimeWhenColorWillBeChanged - CTimer::GetTimeInMilliseconds()) / float(psystem->m_ColorFadeTime); + + particle->m_Color.red = Clamp( + psystem->m_RenderColouring.red + int32(float(psystem->m_FadeDestinationColor.red - psystem->m_RenderColouring.red) * colorMul), + 0, 255); + + particle->m_Color.green = Clamp( + psystem->m_RenderColouring.green + int32(float(psystem->m_FadeDestinationColor.green - psystem->m_RenderColouring.green) * colorMul), + 0, 255); + + particle->m_Color.blue = Clamp( + psystem->m_RenderColouring.blue + int32(float(psystem->m_FadeDestinationColor.blue - psystem->m_RenderColouring.blue) * colorMul), + 0, 255); + } + else + RwRGBAAssign(&particle->m_Color, &psystem->m_FadeDestinationColor); + } + + if ( psystem->Flags & CLIPOUT2D ) + { + if ( particle->m_vecPosition.x < -10.0f || particle->m_vecPosition.x > SCREEN_WIDTH + 10.0f + || particle->m_vecPosition.y < -10.0f || particle->m_vecPosition.y > SCREEN_HEIGHT + 10.0f ) + { + bRemoveParticle = true; + continue; + } + } + + float size = particle->m_fSize + particle->m_fExpansionRate; + + if ( size < 0.0f ) + { + bRemoveParticle = true; + continue; + } + + particle->m_fSize = size; + + switch ( psystem->m_nFrictionDecceleration ) + { + case 50: + particle->m_vecVelocity *= fFricDeccel50; + break; + + case 80: + particle->m_vecVelocity *= fFricDeccel80; + break; + + case 90: + particle->m_vecVelocity *= fFricDeccel90; + break; + + case 95: + particle->m_vecVelocity *= fFricDeccel95; + break; + + case 96: + particle->m_vecVelocity *= fFricDeccel96; + break; + + case 99: + particle->m_vecVelocity *= fFricDeccel99; + break; + } + + if ( psystem->m_fGravitationalAcceleration > 0.0f ) + { + if ( -50.0f * psystem->m_fGravitationalAcceleration < particle->m_vecVelocity.z ) + particle->m_vecVelocity.z -= psystem->m_fGravitationalAcceleration * CTimer::GetTimeStep(); + + if ( psystem->Flags & ZCHECK_FIRST ) + { + if ( particle->m_vecPosition.z < particle->m_fZGround ) + { + switch ( psystem->m_Type ) + { + case PARTICLE_RAINDROP: + case PARTICLE_RAINDROP_SMALL: + { + bRemoveParticle = true; + + if ( CGeneral::GetRandomNumber() & 1 ) + { + AddParticle(PARTICLE_RAIN_SPLASH, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0); + } + else + { + AddParticle(PARTICLE_RAIN_SPLASHUP, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0); + } + + continue; + } + break; + + case PARTICLE_WHEEL_WATER: + { + bRemoveParticle = true; + + int32 randVal = CGeneral::GetRandomNumber(); + + if ( randVal & 1 ) + { + if ( (randVal % 5) == 0 ) + { + AddParticle(PARTICLE_RAIN_SPLASH, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0); + } + else + { + AddParticle(PARTICLE_RAIN_SPLASHUP, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0); + } + + } + continue; + } + break; + + case PARTICLE_BLOOD: + case PARTICLE_BLOOD_SMALL: + { + bRemoveParticle = true; + + CVector vecPosn = particle->m_vecPosition; + vecPosn.z += 1.0f; + + Randomizer++; + int32 randVal = int32(Randomizer & 7); + + if ( randVal == 5 ) + { + CShadows::AddPermanentShadow(1, gpBloodPoolTex, &vecPosn, + 0.1f, 0.0f, 0.0f, -0.1f, + 255, + 255, 0, 0, + 4.0f, (CGeneral::GetRandomNumber() & 4095) + 2000, 1.0f); + } + else if ( randVal == 2 ) + { + CShadows::AddPermanentShadow(1, gpBloodPoolTex, &vecPosn, + 0.2f, 0.0f, 0.0f, -0.2f, + 255, + 255, 0, 0, + 4.0f, (CGeneral::GetRandomNumber() & 4095) + 8000, 1.0f); + } + continue; + } + break; + default: break; + } + } + } + else if ( psystem->Flags & ZCHECK_STEP ) + { + CColPoint point; + CEntity *entity; + + if ( CWorld::ProcessVerticalLine(particle->m_vecPosition, moveStep.z, point, entity, + true, true, false, false, true, false, nil) ) + { + if ( moveStep.z <= point.point.z ) + { + moveStep.z = point.point.z; + if ( psystem->m_Type == PARTICLE_DEBRIS2 ) + { + particle->m_vecVelocity.x *= 0.8f; + particle->m_vecVelocity.y *= 0.8f; + particle->m_vecVelocity.z *= -0.4f; + if ( particle->m_vecVelocity.z < 0.005f ) + particle->m_vecVelocity.z = 0.0f; + } + } + } + } + else if ( psystem->Flags & ZCHECK_BUMP ) + { + if ( particle->m_vecPosition.z < particle->m_fZGround ) + { + switch ( psystem->m_Type ) + { + case PARTICLE_GUNSHELL_FIRST: + case PARTICLE_GUNSHELL: + { + bRemoveParticle = true; + + AddParticle(PARTICLE_GUNSHELL_BUMP1, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector + ( + CGeneral::GetRandomNumberInRange(-0.02f, 0.02f), + CGeneral::GetRandomNumberInRange(-0.02f, 0.02f), + CGeneral::GetRandomNumberInRange(0.05f, 0.1f) + ), + nil, + particle->m_fSize, color, particle->m_nRotationStep, 0, 0, 0); + + PlayOneShotScriptObject(SCRIPT_SOUND_GUNSHELL_DROP, particle->m_vecPosition); + } + break; + + case PARTICLE_GUNSHELL_BUMP1: + { + bRemoveParticle = true; + + AddParticle(PARTICLE_GUNSHELL_BUMP2, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.03f, 0.06f)), + nil, + particle->m_fSize, color, 0, 0, 0, 0); + + PlayOneShotScriptObject(SCRIPT_SOUND_GUNSHELL_DROP_SOFT, particle->m_vecPosition); + } + break; + + case PARTICLE_GUNSHELL_BUMP2: + { + bRemoveParticle = true; + continue; + } + break; + default: break; + } + } + } + } + else + { + if ( psystem->m_fGravitationalAcceleration < 0.0f ) + { + if ( -5.0f * psystem->m_fGravitationalAcceleration > particle->m_vecVelocity.z ) + particle->m_vecVelocity.z -= psystem->m_fGravitationalAcceleration * CTimer::GetTimeStep(); + } + else + { + if ( psystem->Flags & ZCHECK_STEP ) + { + CColPoint point; + CEntity *entity; + + if ( CWorld::ProcessVerticalLine(particle->m_vecPosition, moveStep.z, point, entity, + true, false, false, false, true, false, nil) ) + { + if ( moveStep.z <= point.point.z ) + { + moveStep.z = point.point.z; + if ( psystem->m_Type == PARTICLE_HELI_ATTACK ) + { + bRemoveParticle = true; + AddParticle(PARTICLE_STEAM, moveStep, CVector(0.0f, 0.0f, 0.05f), nil, 0.2f, 0, 0, 0, 0); + continue; + } + } + } + } + } + } + + if ( psystem->m_nFadeToBlackAmount != 0 ) + { + if ( particle->m_nFadeToBlackTimer >= psystem->m_nFadeToBlackTime ) + { + particle->m_nFadeToBlackTimer = 0; + + particle->m_nColorIntensity = Clamp(particle->m_nColorIntensity - psystem->m_nFadeToBlackAmount, + 0, 255); + } + else + ++particle->m_nFadeToBlackTimer; + } + + if ( psystem->m_nFadeAlphaAmount != 0 ) + { + if ( particle->m_nFadeAlphaTimer >= psystem->m_nFadeAlphaTime ) + { + particle->m_nFadeAlphaTimer = 0; + + particle->m_nAlpha = Clamp(particle->m_nAlpha - psystem->m_nFadeAlphaAmount, + 0, 255); +#ifdef PC_PARTICLE + if ( particle->m_nAlpha == 0 ) + { + bRemoveParticle = true; + continue; + } +#endif + } + else + ++particle->m_nFadeAlphaTimer; + } + + if ( psystem->m_nZRotationAngleChangeAmount != 0 ) + { + if ( particle->m_nZRotationTimer >= psystem->m_nZRotationChangeTime ) + { + particle->m_nZRotationTimer = 0; + particle->m_nCurrentZRotation += psystem->m_nZRotationAngleChangeAmount; + } + else + ++particle->m_nZRotationTimer; + } + + if ( psystem->m_fZRadiusChangeAmount != 0.0f ) + { + if ( particle->m_nZRadiusTimer >= psystem->m_nZRadiusChangeTime ) + { + particle->m_nZRadiusTimer = 0; + particle->m_fCurrentZRadius += psystem->m_fZRadiusChangeAmount; + } + else + ++particle->m_nZRadiusTimer; + } + + if ( psystem->m_nAnimationSpeed != 0 ) + { + if ( particle->m_nAnimationSpeedTimer > psystem->m_nAnimationSpeed ) + { + particle->m_nAnimationSpeedTimer = 0; + + if ( ++particle->m_nCurrentFrame > psystem->m_nFinalAnimationFrame ) + { + if ( psystem->Flags & CYCLE_ANIM ) + particle->m_nCurrentFrame = psystem->m_nStartAnimationFrame; + else + --particle->m_nCurrentFrame; + } + } + else + ++particle->m_nAnimationSpeedTimer; + } + + if ( particle->m_nRotationStep != 0 ) + { + particle->m_nRotation += particle->m_nRotationStep; + + if ( particle->m_nRotation >= 360 ) + particle->m_nRotation -= 360; + else if ( particle->m_nRotation < 0 ) + particle->m_nRotation += 360; + } + + if ( particle->m_fCurrentZRadius != 0.0f ) + { + int32 nRot = particle->m_nCurrentZRotation % (SIN_COS_TABLE_SIZE - 1); + + float fX = (Cos(nRot) - Sin(nRot)) * particle->m_fCurrentZRadius; + + float fY = (Sin(nRot) + Cos(nRot)) * particle->m_fCurrentZRadius; + + moveStep -= particle->m_vecParticleMovementOffset; + + moveStep += CVector(fX, fY, 0.0f); + + particle->m_vecParticleMovementOffset = CVector(fX, fY, 0.0f); + } + + particle->m_vecPosition = moveStep; + } + } +} + +void CParticle::Render() +{ + PUSH_RENDERGROUP("CParticle::Render"); + + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void *)rwTEXTUREADDRESSWRAP); + RwRenderStateSet(rwRENDERSTATETEXTUREPERSPECTIVE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + CSprite::InitSpriteBuffer2D(); + + uint32 flags = DRAW_OPAQUE; + + RwRaster *prevFrame = nil; + + for ( int32 i = 0; i < MAX_PARTICLES; i++ ) + { + tParticleSystemData *psystem = &mod_ParticleSystemManager.m_aParticles[i]; +#ifdef PC_PARTICLE + bool particleBanned = false; +#endif + CParticle *particle = psystem->m_pParticles; + + RwRaster **frames = psystem->m_ppRaster; +#ifdef PC_PARTICLE + tParticleType type = psystem->m_Type; + + if ( type == PARTICLE_ENGINE_SMOKE + || type == PARTICLE_ENGINE_SMOKE2 + || type == PARTICLE_ENGINE_STEAM + || type == PARTICLE_CARFLAME_SMOKE + || type == PARTICLE_RUBBER_SMOKE + || type == PARTICLE_BURNINGRUBBER_SMOKE + || type == PARTICLE_EXHAUST_FUMES + || type == PARTICLE_CARCOLLISION_DUST ) + { + particleBanned = true; + } +#endif + + if ( particle ) + { + if ( (flags & DRAW_OPAQUE) != (psystem->Flags & DRAW_OPAQUE) + || (flags & DRAW_DARK) != (psystem->Flags & DRAW_DARK) ) + { + CSprite::FlushSpriteBuffer(); + + if ( psystem->Flags & DRAW_OPAQUE ) + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + } + else + { + if ( psystem->Flags & DRAW_DARK ) + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + else + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + } + + flags = psystem->Flags; + } + + if ( frames != nil ) + { + RwRaster *curFrame = *frames; + if ( curFrame != prevFrame ) + { + CSprite::FlushSpriteBuffer(); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)curFrame); + prevFrame = curFrame; + } + } + } + + while ( particle != nil ) + { + bool canDraw = true; +#ifdef PC_PARTICLE + + if ( particle->m_nAlpha == 0 ) + canDraw = false; +#endif + if ( canDraw && psystem->m_nFinalAnimationFrame != 0 && frames != nil ) + { + RwRaster *curFrame = frames[particle->m_nCurrentFrame]; + if ( prevFrame != curFrame ) + { + CSprite::FlushSpriteBuffer(); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)curFrame); + prevFrame = curFrame; + } + } + + if ( canDraw && psystem->Flags & DRAWTOP2D ) + { + if ( particle->m_nRotation != 0 ) + { + CSprite::RenderBufferedOneXLUSprite2D_Rotate_Dimension( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + particle->m_fSize * 63.0f, + particle->m_fSize * 63.0f, + particle->m_Color, + particle->m_nColorIntensity, + (float)particle->m_nRotation, //DEGTORAD((float)particle->m_nRotation) ps2 + particle->m_nAlpha); + } + else + { + CSprite::RenderBufferedOneXLUSprite2D( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + particle->m_fSize * 63.0f, + particle->m_fSize * 63.0f, + particle->m_Color, + particle->m_nColorIntensity, + particle->m_nAlpha); + } + + canDraw = false; + } + + if ( canDraw ) + { + CVector coors; + float w; + float h; + + if ( CSprite::CalcScreenCoors(particle->m_vecPosition, &coors, &w, &h, true) ) + { +#ifdef PC_PARTICLE + if ( (!particleBanned || SCREEN_WIDTH * fParticleScaleLimit >= w) + && SCREEN_HEIGHT * fParticleScaleLimit >= h ) +#endif + { + if ( particle->m_nRotation != 0 ) + { + CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(coors.x, coors.y, coors.z, + particle->m_fSize * w, particle->m_fSize * h, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + float(particle->m_nRotation), // DEGTORAD((float)particle->m_nRotation) ps2 + particle->m_nAlpha); + } + else if ( psystem->Flags & SCREEN_TRAIL ) + { + float fRotation; + float fTrailLength; + + if ( particle->m_vecScreenPosition.x == 0.0f ) + { + fTrailLength = 0.0f; + fRotation = 0.0f; + } + else + { + CVector2D vecDist + ( + coors.x - particle->m_vecScreenPosition.x, + coors.y - particle->m_vecScreenPosition.y + ); + + float fDist = vecDist.Magnitude(); + + fTrailLength = fDist; + + float fRot = Asin(vecDist.x / fDist); + + fRotation = fRot; + + if ( vecDist.y < 0.0f ) + fRotation = -1.0f * fRot + DEGTORAD(180.0f); + + fRotation = RADTODEG(fRotation); + + if ( fRotation < 0.0f ) + fRotation += 360.0f; + + float fSpeed = particle->m_vecVelocity.Magnitude(); + + float fNewTrailLength = fSpeed * CTimer::GetTimeStep() * w * 2.0f; + + if ( fDist > fNewTrailLength ) + fTrailLength = fNewTrailLength; + } + + CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(coors.x, coors.y, coors.z, + particle->m_fSize * w, + particle->m_fSize * h + fTrailLength * psystem->m_fTrailLengthMultiplier, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + fRotation, + particle->m_nAlpha); + + particle->m_vecScreenPosition = coors; + } + else if ( psystem->Flags & SPEED_TRAIL ) + { + CVector vecPrevPos = particle->m_vecPosition - particle->m_vecVelocity; + float fRotation; + float fTrailLength; + + if ( CSprite::CalcScreenCoors(vecPrevPos, &particle->m_vecScreenPosition, &fTrailLength, &fRotation, true) ) + { + CVector2D vecDist + ( + coors.x - particle->m_vecScreenPosition.x, + coors.y - particle->m_vecScreenPosition.y + ); + + float fDist = vecDist.Magnitude(); + + fTrailLength = fDist; + + float fRot = Asin(vecDist.x / fDist); + + fRotation = fRot; + + if ( vecDist.y < 0.0f ) + fRotation = -1.0f * fRot + DEGTORAD(180.0f); + + fRotation = RADTODEG(fRotation); + + if ( fRotation < 0.0f ) + fRotation += 360.0f; + } + else + { + fRotation = 0.0f; + fTrailLength = 0.0f; + } + + CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(coors.x, coors.y, coors.z, + particle->m_fSize * w, + particle->m_fSize * h + fTrailLength * psystem->m_fTrailLengthMultiplier, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + fRotation, + particle->m_nAlpha); + } + else if ( psystem->Flags & VERT_TRAIL ) + { + float fTrailLength = fabsf(particle->m_vecVelocity.z * 10.0f); + + CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z, + particle->m_fSize * w, + (particle->m_fSize + fTrailLength * psystem->m_fTrailLengthMultiplier) * h, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + particle->m_nAlpha); + } + else if ( i == PARTICLE_RAINDROP_SMALL ) + { + CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z, + particle->m_fSize * w * 0.05f, + particle->m_fSize * h, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + particle->m_nAlpha); + } + else if ( i == PARTICLE_BOAT_WAKE ) + { + CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z, + particle->m_fSize * w, + psystem->m_fDefaultInitialRadius * h, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + particle->m_nAlpha); + } + else + { + CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z, + particle->m_fSize * w, + particle->m_fSize * h, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + particle->m_nAlpha); + } + } + } + } + + particle = particle->m_pNext; + } + + CSprite::FlushSpriteBuffer(); + + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + POP_RENDERGROUP(); +} + +void CParticle::RemovePSystem(tParticleType type) +{ + tParticleSystemData *psystemdata = &mod_ParticleSystemManager.m_aParticles[type]; + + for ( CParticle *particle = psystemdata->m_pParticles; particle; particle = psystemdata->m_pParticles ) + RemoveParticle(particle, nil, psystemdata); +} + +void CParticle::RemoveParticle(CParticle *pParticle, CParticle *pPrevParticle, tParticleSystemData *pPSystemData) +{ + if ( pPrevParticle ) + pPrevParticle->m_pNext = pParticle->m_pNext; + else + pPSystemData->m_pParticles = pParticle->m_pNext; + + pParticle->m_pNext = m_pUnusedListHead; + m_pUnusedListHead = pParticle; +} + +void CParticle::AddJetExplosion(CVector const &vecPos, float fPower, float fSize) +{ + CRGBA color(240, 240, 240, 255); + + if ( fPower < 1.0f ) + fPower = 1.0f; + + CVector vecRandOffset + ( + CGeneral::GetRandomNumberInRange(-0.4f, 0.4f), + CGeneral::GetRandomNumberInRange(-0.4f, 0.4f), + CGeneral::GetRandomNumberInRange(0.1f, 0.3f) + ); + + vecRandOffset *= 2.0f; + + CVector vecStepPos = vecPos; + + for ( int32 i = 0; i < int32(fPower * 4.0f); i++ ) + { + AddParticle(PARTICLE_EXPLOSION_MFAST, + vecStepPos, + CVector + ( + CGeneral::GetRandomNumberInRange(-0.02f, 0.02f), + CGeneral::GetRandomNumberInRange(-0.02f, 0.02f), + CGeneral::GetRandomNumberInRange(-0.02f, 0.0f) + ), + nil, + fSize, color, 0, 0, 0, 0); + + AddParticle(PARTICLE_EXPLOSION_MFAST, + vecStepPos, + CVector + ( + CGeneral::GetRandomNumberInRange(-0.04f, 0.04f), + CGeneral::GetRandomNumberInRange(-0.04f, 0.04f), + CGeneral::GetRandomNumberInRange(0.0f, 0.07f) + ), + nil, + fSize, color, 0, 0, 0, 0); + + AddParticle(PARTICLE_EXPLOSION_MFAST, + vecStepPos, + CVector + ( + CGeneral::GetRandomNumberInRange(-0.04f, 0.04f), + CGeneral::GetRandomNumberInRange(-0.04f, 0.04f), + CGeneral::GetRandomNumberInRange(0.0f, 0.07f) + ), + nil, + fSize, color, 0, 0, 0, 0); + + vecStepPos += vecRandOffset; + } +} + +void CParticle::AddYardieDoorSmoke(CVector const &vecPos, CMatrix const &matMatrix) +{ + CRGBA color(0, 0, 0, 0); + + CMatrix invMat(Invert(matMatrix)); + + CVector vecBasePos = matMatrix * (invMat * vecPos + CVector(0.0f, -1.0f, 0.5f)); + + for ( int32 i = 0; i < 5; i++ ) + { + CVector pos = vecBasePos; + + pos.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f); + pos.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f); + + AddParticle(PARTICLE_CARCOLLISION_DUST, + pos, + CVector(0.0f, 0.0f, 0.0f), + nil, + 0.3f, color, 0, 0, 0, 0); + } +} diff --git a/src/renderer/Particle.h b/src/renderer/Particle.h new file mode 100644 index 0000000..7f02e31 --- /dev/null +++ b/src/renderer/Particle.h @@ -0,0 +1,94 @@ +#pragma once +#include "ParticleMgr.h" + + +class CEntity; + +class CParticle +{ +public: + enum + { + RAND_TABLE_SIZE = 20, + SIN_COS_TABLE_SIZE = 1024 + }; + + CVector m_vecPosition; + CVector m_vecVelocity; + CVector m_vecScreenPosition; + uint32 m_nTimeWhenWillBeDestroyed; + uint32 m_nTimeWhenColorWillBeChanged; + float m_fZGround; + CVector m_vecParticleMovementOffset; + int16 m_nCurrentZRotation; + uint16 m_nZRotationTimer; + float m_fCurrentZRadius; + uint16 m_nZRadiusTimer; + float m_fSize; + float m_fExpansionRate; + uint16 m_nFadeToBlackTimer; + uint16 m_nFadeAlphaTimer; + uint8 m_nColorIntensity; + uint8 m_nAlpha; + uint16 m_nCurrentFrame; + int16 m_nAnimationSpeedTimer; + int16 m_nRotationStep; + int16 m_nRotation; + RwRGBA m_Color; + CParticle *m_pNext; + + CParticle() + { + ; + } + + ~CParticle() + { + ; + } + + static float ms_afRandTable[RAND_TABLE_SIZE]; + static CParticle *m_pUnusedListHead; + + static float m_SinTable[SIN_COS_TABLE_SIZE]; + static float m_CosTable[SIN_COS_TABLE_SIZE]; + + static float Sin(int32 value) { return m_SinTable[value]; } + static float Cos(int32 value) { return m_CosTable[value]; } + + static void ReloadConfig(); + static void Initialise(); + static void Shutdown(); + + static CParticle *AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity = nil, float fSize = 0.0f, int32 nRotationSpeed = 0, int32 nRotation = 0, int32 nCurFrame = 0, int32 nLifeSpan = 0); + static CParticle *AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity, float fSize, RwRGBA const &color, int32 nRotationSpeed = 0, int32 nRotation = 0, int32 nCurFrame = 0, int32 nLifeSpan = 0); + + static void Update(); + static void Render(); + + static void RemovePSystem(tParticleType type); + static void RemoveParticle(CParticle *pParticle, CParticle *pPrevParticle, tParticleSystemData *pPSystemData); + + static void _Next(CParticle *&pParticle, CParticle *&pPrevParticle, tParticleSystemData *pPSystemData, bool bRemoveParticle) + { + if ( bRemoveParticle ) + { + RemoveParticle(pParticle, pPrevParticle, pPSystemData); + + if ( pPrevParticle ) + pParticle = pPrevParticle->m_pNext; + else + pParticle = pPSystemData->m_pParticles; + } + else + { + pPrevParticle = pParticle; + pParticle = pParticle->m_pNext; + } + } + + static void AddJetExplosion(CVector const &vecPos, float fPower, float fSize); + static void AddYardieDoorSmoke(CVector const &vecPos, CMatrix const &matMatrix); +}; + +VALIDATE_SIZE(CParticle, 0x68); diff --git a/src/renderer/ParticleMgr.cpp b/src/renderer/ParticleMgr.cpp new file mode 100644 index 0000000..3387d47 --- /dev/null +++ b/src/renderer/ParticleMgr.cpp @@ -0,0 +1,243 @@ +#include "common.h" + +#include "main.h" +#include "FileMgr.h" +#include "ParticleMgr.h" + +cParticleSystemMgr mod_ParticleSystemManager; + +const char *ParticleFilename = "PARTICLE.CFG"; + +cParticleSystemMgr::cParticleSystemMgr() +{ + memset(this, 0, sizeof(*this)); +} + +void cParticleSystemMgr::Initialise() +{ + LoadParticleData(); + + for ( int32 i = 0; i < MAX_PARTICLES; i++ ) + m_aParticles[i].m_pParticles = nil; +} + +void cParticleSystemMgr::LoadParticleData() +{ + CFileMgr::SetDir("DATA"); + CFileMgr::LoadFile(ParticleFilename, work_buff, ARRAY_SIZE(work_buff), "r"); + CFileMgr::SetDir(""); + + tParticleSystemData *entry = nil; + int32 type = PARTICLE_FIRST; + + char *lineStart = (char *)work_buff; + char *lineEnd = lineStart + 1; + + char line[500]; + char delims[4]; + + while ( true ) + { + ASSERT(lineStart != nil); + ASSERT(lineEnd != nil); + + while ( *lineEnd != '\n' ) + ++lineEnd; + + int32 lineLength = lineEnd - lineStart; + + ASSERT(lineLength < 500); + + strncpy(line, lineStart, lineLength); + + line[lineLength] = '\0'; + + if ( !strcmp(line, ";the end") ) + break; + + if ( *line != ';' ) + { + int32 param = CFG_PARAM_FIRST; + + strcpy(delims, " \t"); + + char *value = strtok(line, delims); + + ASSERT(value != nil); + + do + { + switch ( param ) + { + case CFG_PARAM_PARTICLE_TYPE_NAME: + ASSERT(type < MAX_PARTICLES); + entry = &m_aParticles[type]; + ASSERT(entry != nil); + entry->m_Type = (tParticleType)type++; + strcpy(entry->m_aName, value); + break; + + case CFG_PARAM_RENDER_COLOURING_R: + entry->m_RenderColouring.red = atoi(value); + break; + + case CFG_PARAM_RENDER_COLOURING_G: + entry->m_RenderColouring.green = atoi(value); + break; + + case CFG_PARAM_RENDER_COLOURING_B: + entry->m_RenderColouring.blue = atoi(value); + break; + + case CFG_PARAM_INITIAL_COLOR_VARIATION: + entry->m_InitialColorVariation = Min(atoi(value), 100); + break; + + case CFG_PARAM_FADE_DESTINATION_COLOR_R: + entry->m_FadeDestinationColor.red = atoi(value); + break; + + case CFG_PARAM_FADE_DESTINATION_COLOR_G: + entry->m_FadeDestinationColor.green = atoi(value); + break; + + case CFG_PARAM_FADE_DESTINATION_COLOR_B: + entry->m_FadeDestinationColor.blue = atoi(value); + break; + + case CFG_PARAM_COLOR_FADE_TIME: + entry->m_ColorFadeTime = atoi(value); + break; + + case CFG_PARAM_DEFAULT_INITIAL_RADIUS: + entry->m_fDefaultInitialRadius = atof(value); + break; + + case CFG_PARAM_EXPANSION_RATE: + entry->m_fExpansionRate = atof(value); + break; + + case CFG_PARAM_INITIAL_INTENSITY: + entry->m_nFadeToBlackInitialIntensity = atoi(value); + break; + + case CFG_PARAM_FADE_TIME: + entry->m_nFadeToBlackTime = atoi(value); + break; + + case CFG_PARAM_FADE_AMOUNT: + entry->m_nFadeToBlackAmount = atoi(value); + break; + + case CFG_PARAM_INITIAL_ALPHA_INTENSITY: + entry->m_nFadeAlphaInitialIntensity = atoi(value); + break; + + case CFG_PARAM_FADE_ALPHA_TIME: + entry->m_nFadeAlphaTime = atoi(value); + break; + + case CFG_PARAM_FADE_ALPHA_AMOUNT: + entry->m_nFadeAlphaAmount = atoi(value); + break; + + case CFG_PARAM_INITIAL_ANGLE: + entry->m_nZRotationInitialAngle = atoi(value); + break; + + case CFG_PARAM_CHANGE_TIME: + entry->m_nZRotationChangeTime = atoi(value); + break; + + case CFG_PARAM_ANGLE_CHANGE_AMOUNT: + entry->m_nZRotationAngleChangeAmount = atoi(value); + break; + + case CFG_PARAM_INITIAL_Z_RADIUS: + entry->m_fInitialZRadius = atof(value); + break; + + case CFG_PARAM_Z_RADIUS_CHANGE_TIME: + entry->m_nZRadiusChangeTime = atoi(value); + break; + + case CFG_PARAM_Z_RADIUS_CHANGE_AMOUNT: + entry->m_fZRadiusChangeAmount = atof(value); + break; + + case CFG_PARAM_ANIMATION_SPEED: + entry->m_nAnimationSpeed = atoi(value); + break; + + case CFG_PARAM_START_ANIMATION_FRAME: + entry->m_nStartAnimationFrame = atoi(value); + break; + + case CFG_PARAM_FINAL_ANIMATION_FRAME: + entry->m_nFinalAnimationFrame = atoi(value); + break; + + case CFG_PARAM_ROTATION_SPEED: + entry->m_nRotationSpeed = atoi(value); + break; + + case CFG_PARAM_GRAVITATIONAL_ACCELERATION: + entry->m_fGravitationalAcceleration = atof(value); + break; + + case CFG_PARAM_FRICTION_DECCELERATION: + entry->m_nFrictionDecceleration = atoi(value); + break; + + case CFG_PARAM_LIFE_SPAN: + entry->m_nLifeSpan = atoi(value); + break; + + case CFG_PARAM_POSITION_RANDOM_ERROR: + entry->m_fPositionRandomError = atof(value); + break; + + case CFG_PARAM_VELOCITY_RANDOM_ERROR: + entry->m_fVelocityRandomError = atof(value); + break; + + case CFG_PARAM_EXPANSION_RATE_ERROR: + entry->m_fExpansionRateError = atof(value); + break; + + case CFG_PARAM_ROTATION_RATE_ERROR: + entry->m_nRotationRateError = atoi(value); + break; + + case CFG_PARAM_LIFE_SPAN_ERROR_SHAPE: + entry->m_nLifeSpanErrorShape = atoi(value); + break; + + case CFG_PARAM_TRAIL_LENGTH_MULTIPLIER: + entry->m_fTrailLengthMultiplier = atof(value); + break; + + case CFG_PARAM_PARTICLE_CREATE_RANGE: + entry->m_fCreateRange = SQR(atof(value)); + break; + + case CFG_PARAM_FLAGS: + entry->Flags = atoi(value); + break; + } + + value = strtok(nil, delims); + + param++; + + if ( param > CFG_PARAM_LAST ) + param = CFG_PARAM_FIRST; + + } while ( value != nil ); + } + + lineEnd++; + lineStart = lineEnd; + lineEnd++; + } +} diff --git a/src/renderer/ParticleMgr.h b/src/renderer/ParticleMgr.h new file mode 100644 index 0000000..0100bb6 --- /dev/null +++ b/src/renderer/ParticleMgr.h @@ -0,0 +1,130 @@ +#pragma once + +#include "ParticleType.h" + +class CParticle; + +enum +{ + ZCHECK_FIRST = BIT(0), + ZCHECK_STEP = BIT(1), + DRAW_OPAQUE = BIT(2), + SCREEN_TRAIL = BIT(3), + SPEED_TRAIL = BIT(4), + RAND_VERT_V = BIT(5), + CYCLE_ANIM = BIT(6), + DRAW_DARK = BIT(7), + VERT_TRAIL = BIT(8), + _FLAG9 = BIT(9), // unused + DRAWTOP2D = BIT(10), + CLIPOUT2D = BIT(11), + ZCHECK_BUMP = BIT(12), + ZCHECK_BUMP_FIRST = BIT(13) +}; + + +struct tParticleSystemData +{ + tParticleType m_Type; + char m_aName[20]; + float m_fCreateRange; + float m_fDefaultInitialRadius; + float m_fExpansionRate; + uint16 m_nZRotationInitialAngle; + int16 m_nZRotationAngleChangeAmount; + uint16 m_nZRotationChangeTime; + uint16 m_nZRadiusChangeTime; + float m_fInitialZRadius; + float m_fZRadiusChangeAmount; + uint16 m_nFadeToBlackTime; + int16 m_nFadeToBlackAmount; + uint8 m_nFadeToBlackInitialIntensity; + uint8 m_nFadeAlphaInitialIntensity; + uint16 m_nFadeAlphaTime; + int16 m_nFadeAlphaAmount; + uint16 m_nStartAnimationFrame; + uint16 m_nFinalAnimationFrame; + uint16 m_nAnimationSpeed; + uint16 m_nRotationSpeed; + float m_fGravitationalAcceleration; + int32 m_nFrictionDecceleration; + int32 m_nLifeSpan; + float m_fPositionRandomError; + float m_fVelocityRandomError; + float m_fExpansionRateError; + int32 m_nRotationRateError; + uint32 m_nLifeSpanErrorShape; + float m_fTrailLengthMultiplier; + uint32 Flags; + RwRGBA m_RenderColouring; + uint8 m_InitialColorVariation; + RwRGBA m_FadeDestinationColor; + uint32 m_ColorFadeTime; + + RwRaster **m_ppRaster; + CParticle *m_pParticles; +}; + +VALIDATE_SIZE(tParticleSystemData, 0x88); + +class cParticleSystemMgr +{ + enum + { + CFG_PARAM_PARTICLE_TYPE_NAME = 0, + CFG_PARAM_RENDER_COLOURING_R, + CFG_PARAM_RENDER_COLOURING_G, + CFG_PARAM_RENDER_COLOURING_B, + CFG_PARAM_INITIAL_COLOR_VARIATION, + CFG_PARAM_FADE_DESTINATION_COLOR_R, + CFG_PARAM_FADE_DESTINATION_COLOR_G, + CFG_PARAM_FADE_DESTINATION_COLOR_B, + CFG_PARAM_COLOR_FADE_TIME, + CFG_PARAM_DEFAULT_INITIAL_RADIUS, + CFG_PARAM_EXPANSION_RATE, + CFG_PARAM_INITIAL_INTENSITY, + CFG_PARAM_FADE_TIME, + CFG_PARAM_FADE_AMOUNT, + CFG_PARAM_INITIAL_ALPHA_INTENSITY, + CFG_PARAM_FADE_ALPHA_TIME, + CFG_PARAM_FADE_ALPHA_AMOUNT, + CFG_PARAM_INITIAL_ANGLE, + CFG_PARAM_CHANGE_TIME, + CFG_PARAM_ANGLE_CHANGE_AMOUNT, + CFG_PARAM_INITIAL_Z_RADIUS, + CFG_PARAM_Z_RADIUS_CHANGE_TIME, + CFG_PARAM_Z_RADIUS_CHANGE_AMOUNT, + CFG_PARAM_ANIMATION_SPEED, + CFG_PARAM_START_ANIMATION_FRAME, + CFG_PARAM_FINAL_ANIMATION_FRAME, + CFG_PARAM_ROTATION_SPEED, + CFG_PARAM_GRAVITATIONAL_ACCELERATION, + CFG_PARAM_FRICTION_DECCELERATION, + CFG_PARAM_LIFE_SPAN, + CFG_PARAM_POSITION_RANDOM_ERROR, + CFG_PARAM_VELOCITY_RANDOM_ERROR, + CFG_PARAM_EXPANSION_RATE_ERROR, + CFG_PARAM_ROTATION_RATE_ERROR, + CFG_PARAM_LIFE_SPAN_ERROR_SHAPE, + CFG_PARAM_TRAIL_LENGTH_MULTIPLIER, + CFG_PARAM_PARTICLE_CREATE_RANGE, + CFG_PARAM_FLAGS, + + MAX_CFG_PARAMS, + CFG_PARAM_FIRST = CFG_PARAM_PARTICLE_TYPE_NAME, + CFG_PARAM_LAST = CFG_PARAM_FLAGS + }; + +public: + tParticleSystemData m_aParticles[MAX_PARTICLES]; + + cParticleSystemMgr(); + + void Initialise(); + void LoadParticleData(); + void RangeCheck(tParticleSystemData *pData) { } +}; + +VALIDATE_SIZE(cParticleSystemMgr, 0x2420); + +extern cParticleSystemMgr mod_ParticleSystemManager; diff --git a/src/renderer/ParticleType.h b/src/renderer/ParticleType.h new file mode 100644 index 0000000..8d352c4 --- /dev/null +++ b/src/renderer/ParticleType.h @@ -0,0 +1,77 @@ +#pragma once + +enum tParticleType +{ + PARTICLE_SPARK = 0, + PARTICLE_SPARK_SMALL, + PARTICLE_WHEEL_DIRT, + PARTICLE_WHEEL_WATER, + PARTICLE_BLOOD, + PARTICLE_BLOOD_SMALL, + PARTICLE_BLOOD_SPURT, + PARTICLE_DEBRIS, + PARTICLE_DEBRIS2, + PARTICLE_WATER, + PARTICLE_FLAME, + PARTICLE_FIREBALL, + PARTICLE_GUNFLASH, + PARTICLE_GUNFLASH_NOANIM, + PARTICLE_GUNSMOKE, + PARTICLE_GUNSMOKE2, + PARTICLE_SMOKE, + PARTICLE_SMOKE_SLOWMOTION, + PARTICLE_GARAGEPAINT_SPRAY, + PARTICLE_SHARD, + PARTICLE_SPLASH, + PARTICLE_CARFLAME, + PARTICLE_STEAM, + PARTICLE_STEAM2, + PARTICLE_STEAM_NY, + PARTICLE_STEAM_NY_SLOWMOTION, + PARTICLE_ENGINE_STEAM, + PARTICLE_RAINDROP, + PARTICLE_RAINDROP_SMALL, + PARTICLE_RAIN_SPLASH, + PARTICLE_RAIN_SPLASH_BIGGROW, + PARTICLE_RAIN_SPLASHUP, + PARTICLE_WATERSPRAY, + PARTICLE_EXPLOSION_MEDIUM, + PARTICLE_EXPLOSION_LARGE, + PARTICLE_EXPLOSION_MFAST, + PARTICLE_EXPLOSION_LFAST, + PARTICLE_CAR_SPLASH, + PARTICLE_BOAT_SPLASH, + PARTICLE_BOAT_THRUSTJET, + PARTICLE_BOAT_WAKE, + PARTICLE_WATER_HYDRANT, + PARTICLE_WATER_CANNON, + PARTICLE_EXTINGUISH_STEAM, + PARTICLE_PED_SPLASH, + PARTICLE_PEDFOOT_DUST, + PARTICLE_HELI_DUST, + PARTICLE_HELI_ATTACK, + PARTICLE_ENGINE_SMOKE, + PARTICLE_ENGINE_SMOKE2, + PARTICLE_CARFLAME_SMOKE, + PARTICLE_FIREBALL_SMOKE, + PARTICLE_PAINT_SMOKE, + PARTICLE_TREE_LEAVES, + PARTICLE_CARCOLLISION_DUST, + PARTICLE_CAR_DEBRIS, + PARTICLE_HELI_DEBRIS, + PARTICLE_EXHAUST_FUMES, + PARTICLE_RUBBER_SMOKE, + PARTICLE_BURNINGRUBBER_SMOKE, + PARTICLE_BULLETHIT_SMOKE, + PARTICLE_GUNSHELL_FIRST, + PARTICLE_GUNSHELL, + PARTICLE_GUNSHELL_BUMP1, + PARTICLE_GUNSHELL_BUMP2, + PARTICLE_TEST, + PARTICLE_BIRD_FRONT, + PARTICLE_RAINDROP_2D, + + MAX_PARTICLES, + PARTICLE_FIRST = PARTICLE_SPARK, + PARTICLE_LAST = PARTICLE_RAINDROP_2D +}; \ No newline at end of file diff --git a/src/renderer/PlayerSkin.cpp b/src/renderer/PlayerSkin.cpp new file mode 100644 index 0000000..f0fae45 --- /dev/null +++ b/src/renderer/PlayerSkin.cpp @@ -0,0 +1,166 @@ +#include "common.h" + +#include "main.h" +#include "PlayerSkin.h" +#include "TxdStore.h" +#include "rtbmp.h" +#include "ClumpModelInfo.h" +#include "VisibilityPlugins.h" +#include "World.h" +#include "PlayerInfo.h" +#include "CdStream.h" +#include "FileMgr.h" +#include "Directory.h" +#include "RwHelper.h" +#include "Timer.h" +#include "Lights.h" +#include "MemoryMgr.h" + +RpClump *gpPlayerClump; +float gOldFov; + +int CPlayerSkin::m_txdSlot; + +void +FindPlayerDff(uint32 &offset, uint32 &size) +{ + int file; + CDirectory::DirectoryInfo info; + + file = CFileMgr::OpenFile("models\\gta3.dir", "rb"); + + do { + if (!CFileMgr::Read(file, (char*)&info, sizeof(CDirectory::DirectoryInfo))) + return; + } while (strcasecmp("player.dff", info.name) != 0); + + offset = info.offset; + size = info.size; +} + +void +LoadPlayerDff(void) +{ + RwStream *stream; + RwMemory mem; + uint32 offset, size; + uint8 *buffer; + bool streamWasAdded = false; + + if (CdStreamGetNumImages() == 0) { + CdStreamAddImage("models\\gta3.img"); + streamWasAdded = true; + } + + FindPlayerDff(offset, size); + buffer = (uint8*)RwMallocAlign(size << 11, 2048); + CdStreamRead(0, buffer, offset, size); + CdStreamSync(0); + + mem.start = buffer; + mem.length = size << 11; + stream = RwStreamOpen(rwSTREAMMEMORY, rwSTREAMREAD, &mem); + + if (RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)) + gpPlayerClump = RpClumpStreamRead(stream); + + RwStreamClose(stream, &mem); + RwFreeAlign(buffer); + + if (streamWasAdded) + CdStreamRemoveImages(); +} + +void +CPlayerSkin::Initialise(void) +{ + m_txdSlot = CTxdStore::AddTxdSlot("skin"); + CTxdStore::Create(m_txdSlot); + CTxdStore::AddRef(m_txdSlot); +} + +void +CPlayerSkin::Shutdown(void) +{ + CTxdStore::RemoveTxdSlot(m_txdSlot); +} + +RwTexture * +CPlayerSkin::GetSkinTexture(const char *texName) +{ + RwTexture *tex; + RwRaster *raster; + int32 width, height, depth, format; + + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(m_txdSlot); + tex = RwTextureRead(texName, NULL); + CTxdStore::PopCurrentTxd(); + if (tex != nil) return tex; + + if (strcmp(DEFAULT_SKIN_NAME, texName) == 0) + sprintf(gString, "models\\generic\\player.bmp"); + else + sprintf(gString, "skins\\%s.bmp", texName); + + if (RwImage *image = RtBMPImageRead(gString)) { + RwImageFindRasterFormat(image, rwRASTERTYPETEXTURE, &width, &height, &depth, &format); + raster = RwRasterCreate(width, height, depth, format); + RwRasterSetFromImage(raster, image); + + tex = RwTextureCreate(raster); + RwTextureSetName(tex, texName); +#ifdef FIX_BUGS + RwTextureSetFilterMode(tex, rwFILTERLINEAR); // filtering bugfix from VC +#endif + RwTexDictionaryAddTexture(CTxdStore::GetSlot(m_txdSlot)->texDict, tex); + + RwImageDestroy(image); + } + return tex; +} + +void +CPlayerSkin::BeginFrontendSkinEdit(void) +{ + LoadPlayerDff(); + RpClumpForAllAtomics(gpPlayerClump, CClumpModelInfo::SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); + CWorld::Players[0].LoadPlayerSkin(); + gOldFov = CDraw::GetFOV(); + CDraw::SetFOV(30.0f); +} + +void +CPlayerSkin::EndFrontendSkinEdit(void) +{ + RpClumpDestroy(gpPlayerClump); + gpPlayerClump = NULL; + CDraw::SetFOV(gOldFov); +} + +void +CPlayerSkin::RenderFrontendSkinEdit(void) +{ + static float rotation = 0.0f; + RwRGBAReal AmbientColor = { 0.65f, 0.65f, 0.65f, 1.0f }; + const RwV3d pos = { 1.35f, 0.35f, 7.725f }; + const RwV3d axis1 = { 1.0f, 0.0f, 0.0f }; + const RwV3d axis2 = { 0.0f, 0.0f, 1.0f }; + static uint32 LastFlash = 0; + + RwFrame *frame = RpClumpGetFrame(gpPlayerClump); + + if (CTimer::GetTimeInMillisecondsPauseMode() - LastFlash > 7) { + rotation += 2.0f; + if (rotation > 360.0f) + rotation -= 360.0f; + LastFlash = CTimer::GetTimeInMillisecondsPauseMode(); + } + RwFrameTransform(frame, RwFrameGetMatrix(RwCameraGetFrame(Scene.camera)), rwCOMBINEREPLACE); + RwFrameTranslate(frame, &pos, rwCOMBINEPRECONCAT); + RwFrameRotate(frame, &axis1, -90.0f, rwCOMBINEPRECONCAT); + RwFrameRotate(frame, &axis2, rotation, rwCOMBINEPRECONCAT); + RwFrameUpdateObjects(frame); + SetAmbientColours(&AmbientColor); + RpClumpRender(gpPlayerClump); +} diff --git a/src/renderer/PlayerSkin.h b/src/renderer/PlayerSkin.h new file mode 100644 index 0000000..e0214ce --- /dev/null +++ b/src/renderer/PlayerSkin.h @@ -0,0 +1,15 @@ +#pragma once + +#define DEFAULT_SKIN_NAME "$$\"\"" + +class CPlayerSkin +{ + static int m_txdSlot; +public: + static void Initialise(); + static void Shutdown(); + static RwTexture *GetSkinTexture(const char *texName); + static void BeginFrontendSkinEdit(); + static void EndFrontendSkinEdit(); + static void RenderFrontendSkinEdit(); +}; \ No newline at end of file diff --git a/src/renderer/PointLights.cpp b/src/renderer/PointLights.cpp new file mode 100644 index 0000000..84ac4ab --- /dev/null +++ b/src/renderer/PointLights.cpp @@ -0,0 +1,289 @@ +#include "common.h" + +#include "main.h" +#include "Lights.h" +#include "Camera.h" +#include "Weather.h" +#include "World.h" +#include "Collision.h" +#include "Sprite.h" +#include "Timer.h" +#include "PointLights.h" + +int16 CPointLights::NumLights; +CRegisteredPointLight CPointLights::aLights[NUMPOINTLIGHTS]; + +void +CPointLights::InitPerFrame(void) +{ + NumLights = 0; +} + +#define MAX_DIST 22.0f + +void +CPointLights::AddLight(uint8 type, CVector coors, CVector dir, float radius, float red, float green, float blue, uint8 fogType, bool castExtraShadows) +{ + CVector dist; + float distance; + + // The check is done in some weird way in the game + // we're doing it a bit better here + if(NumLights >= NUMPOINTLIGHTS) + return; + + dist = coors - TheCamera.GetPosition(); + if(Abs(dist.x) < MAX_DIST && Abs(dist.y) < MAX_DIST){ + distance = dist.Magnitude(); + if(distance < MAX_DIST){ + aLights[NumLights].type = type; + aLights[NumLights].fogType = fogType; + aLights[NumLights].coors = coors; + aLights[NumLights].dir = dir; + aLights[NumLights].radius = radius; + aLights[NumLights].castExtraShadows = castExtraShadows; + if(distance < MAX_DIST*0.75f){ + aLights[NumLights].red = red; + aLights[NumLights].green = green; + aLights[NumLights].blue = blue; + }else{ + float fade = 1.0f - (distance/MAX_DIST - 0.75f)*4.0f; + aLights[NumLights].red = red * fade; + aLights[NumLights].green = green * fade; + aLights[NumLights].blue = blue * fade; + } + NumLights++; + } + } +} + +float +CPointLights::GenerateLightsAffectingObject(Const CVector *objCoors) +{ + int i; + float ret; + CVector dist; + float radius, distance; + + ret = 1.0f; + for(i = 0; i < NumLights; i++){ + if(aLights[i].type == LIGHT_FOGONLY || aLights[i].type == LIGHT_FOGONLY_ALWAYS) + continue; + + // same weird distance calculation. simplified here + dist = aLights[i].coors - *objCoors; + radius = aLights[i].radius; + if(Abs(dist.x) < radius && + Abs(dist.y) < radius && + Abs(dist.z) < radius){ + + distance = dist.Magnitude(); + if(distance < radius){ + + float distNorm = distance/radius; + if(aLights[i].type == LIGHT_DARKEN){ + // darken the object the closer it is + ret *= distNorm; + }else{ + float intensity; + // distance fade + if(distNorm < 0.5f) + intensity = 1.0f; + else + intensity = 1.0f - (distNorm - 0.5f)/(1.0f - 0.5f); + + if(distance != 0.0f){ + CVector dir = dist / distance; + + if(aLights[i].type == LIGHT_DIRECTIONAL){ + float dot = -DotProduct(dir, aLights[i].dir); + intensity *= Max((dot-0.5f)*2.0f, 0.0f); + } + + if(intensity > 0.0f) + AddAnExtraDirectionalLight(Scene.world, + dir.x, dir.y, dir.z, + aLights[i].red*intensity, aLights[i].green*intensity, aLights[i].blue*intensity); + } + } + } + } + } + + return ret; +} + +extern RwRaster *gpPointlightRaster; + +void +CPointLights::RemoveLightsAffectingObject(void) +{ + RemoveExtraDirectionalLights(Scene.world); +} + +// for directional fog +#define FOG_AREA_LENGTH 12.0f +#define FOG_AREA_WIDTH 5.0f +// for pointlight fog +#define FOG_AREA_RADIUS 9.0f + +float FogSizes[8] = { 1.3f, 2.0f, 1.7f, 2.0f, 1.4f, 2.1f, 1.5f, 2.3f }; + +void +CPointLights::RenderFogEffect(void) +{ + int i; + float fogginess; + CColPoint point; + CEntity *entity; + float xmin, ymin; + float xmax, ymax; + int16 xi, yi; + CVector spriteCoors; + float spritew, spriteh; + + PUSH_RENDERGROUP("CPointLights::RenderFogEffect"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpPointlightRaster); + + for(i = 0; i < NumLights; i++){ + if(aLights[i].fogType != FOG_NORMAL && aLights[i].fogType != FOG_ALWAYS) + continue; + + fogginess = aLights[i].fogType == FOG_NORMAL ? CWeather::Foggyness : 1.0f; + if(fogginess == 0.0f) + continue; + + if(aLights[i].type == LIGHT_DIRECTIONAL){ + + // TODO: test this. haven't found directional fog so far + + float coors2X = aLights[i].coors.x + FOG_AREA_LENGTH*aLights[i].dir.x; + float coors2Y = aLights[i].coors.y + FOG_AREA_LENGTH*aLights[i].dir.y; + + if(coors2X < aLights[i].coors.x){ + xmin = coors2X; + xmax = aLights[i].coors.x; + }else{ + xmax = coors2X; + xmin = aLights[i].coors.x; + } + if(coors2Y < aLights[i].coors.y){ + ymin = coors2Y; + ymax = aLights[i].coors.y; + }else{ + ymax = coors2Y; + ymin = aLights[i].coors.y; + } + + xmin -= 5.0f; + ymin -= 5.0f; + xmax += 5.0f; + ymax += 5.0f; + + for(xi = (int16)xmin - (int16)xmin % 4; xi <= (int16)xmax + 4; xi += 4){ + for(yi = (int16)ymin - (int16)ymin % 4; yi <= (int16)ymax + 4; yi += 4){ + // Some kind of pseudo random number? + int r = (xi ^ yi)>>2 & 0xF; + if((r & 1) == 0) + continue; + + // Check if fog effect is close enough to directional line in x and y + float dx = xi - aLights[i].coors.x; + float dy = yi - aLights[i].coors.y; + float dot = dx*aLights[i].dir.x + dy*aLights[i].dir.y; + float distsq = sq(dx) + sq(dy); + float linedistsq = distsq - sq(dot); + if(dot > 0.0f && dot < FOG_AREA_LENGTH && linedistsq < sq(FOG_AREA_WIDTH)){ + CVector fogcoors(xi, yi, aLights[i].coors.z + 10.0f); + if(CWorld::ProcessVerticalLine(fogcoors, fogcoors.z - 20.0f, + point, entity, true, false, false, false, true, false, nil)){ + // Now same check again in xyz + fogcoors.z = point.point.z + 1.3f; + // actually we don't have to recalculate x and y, but the game does it that way + dx = xi - aLights[i].coors.x; + dy = yi - aLights[i].coors.y; + float dz = fogcoors.z - aLights[i].coors.z; + dot = dx*aLights[i].dir.x + dy*aLights[i].dir.y + dz*aLights[i].dir.z; + distsq = sq(dx) + sq(dy) + sq(dz); + linedistsq = distsq - sq(dot); + if(dot > 0.0f && dot < FOG_AREA_LENGTH && linedistsq < sq(FOG_AREA_WIDTH)){ + float intensity = 158.0f * fogginess; + // more intensity the smaller the angle + intensity *= dot/Sqrt(distsq); + // more intensity the closer to light source + intensity *= 1.0f - sq(dot/FOG_AREA_LENGTH); + // more intensity the closer to line + intensity *= 1.0f - sq(Sqrt(linedistsq) / FOG_AREA_WIDTH); + + if(CSprite::CalcScreenCoors(fogcoors, &spriteCoors, &spritew, &spriteh, true)){ + float rotation = (CTimer::GetTimeInMilliseconds()&0x1FFF) * 2*3.14f / 0x2000; + float size = FogSizes[r>>1]; + CSprite::RenderOneXLUSprite_Rotate_Aspect(spriteCoors.x, spriteCoors.y, spriteCoors.z, + spritew * size, spriteh * size, + aLights[i].red * intensity, aLights[i].green * intensity, aLights[i].blue * intensity, + intensity, 1/spriteCoors.z, rotation, 255); + } + } + } + } + } + } + + }else if(aLights[i].type == LIGHT_POINT || aLights[i].type == LIGHT_FOGONLY || aLights[i].type == LIGHT_FOGONLY_ALWAYS){ + if(CWorld::ProcessVerticalLine(aLights[i].coors, aLights[i].coors.z - 20.0f, + point, entity, true, false, false, false, true, false, nil)){ + + xmin = aLights[i].coors.x - FOG_AREA_RADIUS; + ymin = aLights[i].coors.y - FOG_AREA_RADIUS; + xmax = aLights[i].coors.x + FOG_AREA_RADIUS; + ymax = aLights[i].coors.y + FOG_AREA_RADIUS; + + for(xi = (int16)xmin - (int16)xmin % 2; xi <= (int16)xmax + 2; xi += 2){ + for(yi = (int16)ymin - (int16)ymin % 2; yi <= (int16)ymax + 2; yi += 2){ + // Some kind of pseudo random number? + int r = (xi ^ yi)>>1 & 0xF; + if((r & 1) == 0) + continue; + + float dx = xi - aLights[i].coors.x; + float dy = yi - aLights[i].coors.y; + float lightdist = Sqrt(sq(dx) + sq(dy)); + if(lightdist < FOG_AREA_RADIUS){ + dx = xi - TheCamera.GetPosition().x; + dy = yi - TheCamera.GetPosition().y; + float camdist = Sqrt(sq(dx) + sq(dy)); + if(camdist < MAX_DIST){ + float intensity; + // distance fade + if(camdist < MAX_DIST/2) + intensity = 1.0f; + else + intensity = 1.0f - (camdist - MAX_DIST/2) / (MAX_DIST/2); + intensity *= 132.0f * fogginess; + // more intensity the closer to light source + intensity *= 1.0f - sq(lightdist / FOG_AREA_RADIUS); + + CVector fogcoors(xi, yi, point.point.z + 1.6f); + if(CSprite::CalcScreenCoors(fogcoors, &spriteCoors, &spritew, &spriteh, true)){ + float rotation = (CTimer::GetTimeInMilliseconds()&0x3FFF) * 2*3.14f / 0x4000; + float size = FogSizes[r>>1]; + CSprite::RenderOneXLUSprite_Rotate_Aspect(spriteCoors.x, spriteCoors.y, spriteCoors.z, + spritew * size, spriteh * size, + aLights[i].red * intensity, aLights[i].green * intensity, aLights[i].blue * intensity, + intensity, 1/spriteCoors.z, rotation, 255); + } + } + } + } + } + } + } + } + + POP_RENDERGROUP(); +} diff --git a/src/renderer/PointLights.h b/src/renderer/PointLights.h new file mode 100644 index 0000000..9e94328 --- /dev/null +++ b/src/renderer/PointLights.h @@ -0,0 +1,45 @@ +#pragma once + +class CRegisteredPointLight +{ +public: + CVector coors; + CVector dir; + float radius; + float red; + float green; + float blue; + int8 type; + int8 fogType; + bool castExtraShadows; +}; +VALIDATE_SIZE(CRegisteredPointLight, 0x2C); + +class CPointLights +{ +public: + static int16 NumLights; + static CRegisteredPointLight aLights[NUMPOINTLIGHTS]; + + enum { + LIGHT_POINT, + LIGHT_DIRECTIONAL, + LIGHT_DARKEN, // no effects at all + // these have only fog, otherwise no difference? + // only used by CEntity::ProcessLightsForEntity it seems + // and there used together with fog type + LIGHT_FOGONLY_ALWAYS, + LIGHT_FOGONLY, + }; + enum { + FOG_NONE, + FOG_NORMAL, // taken from Foggyness + FOG_ALWAYS + }; + + static void InitPerFrame(void); + static void AddLight(uint8 type, CVector coors, CVector dir, float radius, float red, float green, float blue, uint8 fogType, bool castExtraShadows); + static float GenerateLightsAffectingObject(Const CVector *objCoors); + static void RemoveLightsAffectingObject(void); + static void RenderFogEffect(void); +}; diff --git a/src/renderer/RenderBuffer.cpp b/src/renderer/RenderBuffer.cpp new file mode 100644 index 0000000..6120dfe --- /dev/null +++ b/src/renderer/RenderBuffer.cpp @@ -0,0 +1,52 @@ +#include "common.h" + +#include "RenderBuffer.h" + +int32 TempBufferVerticesStored; +int32 TempBufferIndicesStored; + +RwIm3DVertex TempBufferRenderVertices[TEMPBUFFERVERTSIZE]; +RwImVertexIndex TempBufferRenderIndexList[TEMPBUFFERINDEXSIZE]; + +int RenderBuffer::VerticesToBeStored; +int RenderBuffer::IndicesToBeStored; + +void +RenderBuffer::ClearRenderBuffer(void) +{ + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; +} + +void +RenderBuffer::StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart) +{ + if(TempBufferIndicesStored + numIndices >= TEMPBUFFERINDEXSIZE) + RenderStuffInBuffer(); + if(TempBufferVerticesStored + numVertices >= TEMPBUFFERVERTSIZE) + RenderStuffInBuffer(); + *indexStart = &TempBufferRenderIndexList[TempBufferIndicesStored]; + *vertexStart = &TempBufferRenderVertices[TempBufferVerticesStored]; + IndicesToBeStored = numIndices; + VerticesToBeStored = numVertices; +} + +void +RenderBuffer::StopStoring(void) +{ + int i; + for(i = TempBufferIndicesStored; i < TempBufferIndicesStored+IndicesToBeStored; i++) + TempBufferRenderIndexList[i] += TempBufferVerticesStored; + TempBufferIndicesStored += IndicesToBeStored; + TempBufferVerticesStored += VerticesToBeStored; +} + +void +RenderBuffer::RenderStuffInBuffer(void) +{ + if(TempBufferVerticesStored && RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + ClearRenderBuffer(); +} diff --git a/src/renderer/RenderBuffer.h b/src/renderer/RenderBuffer.h new file mode 100644 index 0000000..485d24e --- /dev/null +++ b/src/renderer/RenderBuffer.h @@ -0,0 +1,18 @@ +class RenderBuffer +{ +public: + static int VerticesToBeStored; + static int IndicesToBeStored; + static void ClearRenderBuffer(void); + static void StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart); + static void StopStoring(void); + static void RenderStuffInBuffer(void); +}; + +#define TEMPBUFFERVERTSIZE 256 +#define TEMPBUFFERINDEXSIZE 1024 + +extern int32 TempBufferVerticesStored; +extern int32 TempBufferIndicesStored; +extern RwIm3DVertex TempBufferRenderVertices[TEMPBUFFERVERTSIZE]; +extern RwImVertexIndex TempBufferRenderIndexList[TEMPBUFFERINDEXSIZE]; \ No newline at end of file diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp new file mode 100644 index 0000000..334f395 --- /dev/null +++ b/src/renderer/Renderer.cpp @@ -0,0 +1,1841 @@ +#define WITHD3D +#include "common.h" + +#include "main.h" +#include "Lights.h" +#include "ModelInfo.h" +#include "Treadable.h" +#include "Ped.h" +#include "Vehicle.h" +#include "Boat.h" +#include "Heli.h" +#include "Object.h" +#include "PathFind.h" +#include "Collision.h" +#include "VisibilityPlugins.h" +#include "Clock.h" +#include "World.h" +#include "Camera.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "Shadows.h" +#include "PointLights.h" +#include "Renderer.h" +#include "Frontend.h" +#include "custompipes.h" +#include "Debug.h" + +bool gbShowPedRoadGroups; +bool gbShowCarRoadGroups; +bool gbShowCollisionPolys; +bool gbShowCollisionLines; +bool gbShowCullZoneDebugStuff; +bool gbDisableZoneCull; // not original +bool gbBigWhiteDebugLightSwitchedOn; + +bool gbDontRenderBuildings; +bool gbDontRenderBigBuildings; +bool gbDontRenderPeds; +bool gbDontRenderObjects; +bool gbDontRenderVehicles; + +int32 EntitiesRendered; +int32 EntitiesNotRendered; +int32 RenderedBigBuildings; +int32 RenderedBuildings; +int32 RenderedCars; +int32 RenderedPeds; +int32 RenderedObjects; +int32 RenderedDummies; +int32 TestedBigBuildings; +int32 TestedBuildings; +int32 TestedCars; +int32 TestedPeds; +int32 TestedObjects; +int32 TestedDummies; + +// unused +int16 TestCloseThings; +int16 TestBigThings; + +struct EntityInfo +{ + CEntity *ent; + float sort; +}; + +CLinkList gSortedVehiclesAndPeds; + +int32 CRenderer::ms_nNoOfVisibleEntities; +CEntity *CRenderer::ms_aVisibleEntityPtrs[NUMVISIBLEENTITIES]; +CEntity *CRenderer::ms_aInVisibleEntityPtrs[NUMINVISIBLEENTITIES]; +int32 CRenderer::ms_nNoOfInVisibleEntities; +#ifdef NEW_RENDERER +int32 CRenderer::ms_nNoOfVisibleVehicles; +CEntity *CRenderer::ms_aVisibleVehiclePtrs[NUMVISIBLEENTITIES]; +int32 CRenderer::ms_nNoOfVisibleBuildings; +CEntity *CRenderer::ms_aVisibleBuildingPtrs[NUMVISIBLEENTITIES]; + +CLinkList gSortedBuildings; +#endif + +CVector CRenderer::ms_vecCameraPosition; +CVehicle *CRenderer::m_pFirstPersonVehicle; +bool CRenderer::m_loadingPriority; +float CRenderer::ms_lodDistScale = 1.2f; + +// unused +BlockedRange CRenderer::aBlockedRanges[16]; +BlockedRange *CRenderer::pFullBlockedRanges; +BlockedRange *CRenderer::pEmptyBlockedRanges; + +void +CRenderer::Init(void) +{ + gSortedVehiclesAndPeds.Init(40); + SortBIGBuildings(); +#ifdef NEW_RENDERER + gSortedBuildings.Init(NUMVISIBLEENTITIES); +#endif +} + +void +CRenderer::Shutdown(void) +{ + gSortedVehiclesAndPeds.Shutdown(); +#ifdef NEW_RENDERER + gSortedBuildings.Shutdown(); +#endif +} + +void +CRenderer::PreRender(void) +{ + int i; + CLink *node; + + for(i = 0; i < ms_nNoOfVisibleEntities; i++) + ms_aVisibleEntityPtrs[i]->PreRender(); + +#ifdef NEW_RENDERER + if(gbNewRenderer){ + for(i = 0; i < ms_nNoOfVisibleVehicles; i++) + ms_aVisibleVehiclePtrs[i]->PreRender(); + // How is this done with cWorldStream? + //for(i = 0; i < ms_nNoOfVisibleBuildings; i++) + // ms_aVisibleBuildingPtrs[i]->PreRender(); + for(CLink *node = gSortedBuildings.head.next; + node != &gSortedBuildings.tail; + node = node->next) + ((CEntity*)node->item.ent)->PreRender(); + for(node = CVisibilityPlugins::m_alphaBuildingList.head.next; + node != &CVisibilityPlugins::m_alphaBuildingList.tail; + node = node->next) + ((CEntity*)node->item.entity)->PreRender(); + } +#endif + + for (i = 0; i < ms_nNoOfInVisibleEntities; i++) { +#ifdef SQUEEZE_PERFORMANCE + if (ms_aInVisibleEntityPtrs[i]->IsVehicle() && ((CVehicle*)ms_aInVisibleEntityPtrs[i])->IsHeli()) +#endif + ms_aInVisibleEntityPtrs[i]->PreRender(); + } + + for(node = CVisibilityPlugins::m_alphaEntityList.head.next; + node != &CVisibilityPlugins::m_alphaEntityList.tail; + node = node->next) + ((CEntity*)node->item.entity)->PreRender(); + + CHeli::SpecialHeliPreRender(); + CShadows::RenderExtraPlayerShadows(); +} + +void +CRenderer::RenderOneRoad(CEntity *e) +{ +#ifndef MASTER + if(gbDontRenderBuildings) + return; + if(gbShowCollisionPolys) + CCollision::DrawColModel_Coloured(e->GetMatrix(), *CModelInfo::GetColModel(e->GetModelIndex()), e->GetModelIndex()); + else +#endif + { +#ifdef EXTENDED_PIPELINES + CustomPipes::AttachGlossPipe(e->GetAtomic()); +#endif + PUSH_RENDERGROUP(CModelInfo::GetModelInfo(e->GetModelIndex())->GetModelName()); + +#ifdef EXTRA_MODEL_FLAGS + if(!e->IsBuilding() || CModelInfo::GetModelInfo(e->GetModelIndex())->RenderDoubleSided()){ + BACKFACE_CULLING_OFF; + e->Render(); + BACKFACE_CULLING_ON; + }else +#endif + e->Render(); + + POP_RENDERGROUP(); + } +} + +void +CRenderer::RenderOneNonRoad(CEntity *e) +{ + CPed *ped; + CVehicle *veh; + int i; + bool resetLights; + +#ifndef MASTER + if(gbShowCollisionPolys){ + if(!e->IsVehicle()){ + CCollision::DrawColModel_Coloured(e->GetMatrix(), *CModelInfo::GetColModel(e->GetModelIndex()), e->GetModelIndex()); + return; + } + }else if(e->IsBuilding()){ + if(e->bIsBIGBuilding){ + if(gbDontRenderBigBuildings) + return; + }else{ + if(gbDontRenderBuildings) + return; + } + }else +#endif + if(e->IsPed()){ +#ifndef MASTER + if(gbDontRenderPeds) + return; +#endif + ped = (CPed*)e; + if(ped->m_nPedState == PED_DRIVING) + return; + } +#ifndef MASTER + else if(e->IsObject() || e->IsDummy()){ + if(gbDontRenderObjects) + return; + }else if(e->IsVehicle()){ + // re3 addition + if(gbDontRenderVehicles) + return; + } +#endif + + PUSH_RENDERGROUP(CModelInfo::GetModelInfo(e->GetModelIndex())->GetModelName()); + + resetLights = e->SetupLighting(); + + if(e->IsVehicle()) + CVisibilityPlugins::InitAlphaAtomicList(); + + // Render Peds in vehicle before vehicle itself + if(e->IsVehicle()){ + veh = (CVehicle*)e; + if(veh->pDriver && veh->pDriver->m_nPedState == PED_DRIVING) + veh->pDriver->Render(); + for(i = 0; i < 8; i++) + if(veh->pPassengers[i] && veh->pPassengers[i]->m_nPedState == PED_DRIVING) + veh->pPassengers[i]->Render(); + BACKFACE_CULLING_OFF; + } +#ifdef EXTRA_MODEL_FLAGS + if(!e->IsBuilding() || CModelInfo::GetModelInfo(e->GetModelIndex())->RenderDoubleSided()){ + BACKFACE_CULLING_OFF; + e->Render(); + BACKFACE_CULLING_ON; + }else +#endif + e->Render(); + + if(e->IsVehicle()){ + BACKFACE_CULLING_OFF; + e->bImBeingRendered = true; + CVisibilityPlugins::RenderAlphaAtomics(); + e->bImBeingRendered = false; + BACKFACE_CULLING_ON; + } + + e->RemoveLighting(resetLights); + + POP_RENDERGROUP(); +} + +void +CRenderer::RenderFirstPersonVehicle(void) +{ + if(m_pFirstPersonVehicle == nil) + return; + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RenderOneNonRoad(m_pFirstPersonVehicle); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); +} + +inline bool IsRoad(CEntity *e) { return e->IsBuilding() && ((CBuilding*)e)->GetIsATreadable(); } + +void +CRenderer::RenderRoads(void) +{ + int i; + CTreadable *t; + + PUSH_RENDERGROUP("CRenderer::RenderRoads"); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + BACKFACE_CULLING_ON; + DeActivateDirectional(); + SetAmbientColours(); + + for(i = 0; i < ms_nNoOfVisibleEntities; i++){ + t = (CTreadable*)ms_aVisibleEntityPtrs[i]; + if(IsRoad(t)){ +#ifndef MASTER + if(gbShowCarRoadGroups || gbShowPedRoadGroups){ + int ind = 0; + if(gbShowCarRoadGroups) + ind += ThePaths.m_pathNodes[t->m_nodeIndices[PATH_CAR][0]].group; + if(gbShowPedRoadGroups) + ind += ThePaths.m_pathNodes[t->m_nodeIndices[PATH_PED][0]].group; + SetAmbientColoursToIndicateRoadGroup(ind); + } +#endif + RenderOneRoad(t); +#ifndef MASTER + if(gbShowCarRoadGroups || gbShowPedRoadGroups) + ReSetAmbientAndDirectionalColours(); +#endif + } + } + POP_RENDERGROUP(); +} + +void +CRenderer::RenderEverythingBarRoads(void) +{ + int i; + CEntity *e; + CVector dist; + EntityInfo ei; + + PUSH_RENDERGROUP("CRenderer::RenderEverythingBarRoads"); + BACKFACE_CULLING_ON; + gSortedVehiclesAndPeds.Clear(); + + for(i = 0; i < ms_nNoOfVisibleEntities; i++){ + e = ms_aVisibleEntityPtrs[i]; + + if(IsRoad(e)) + continue; + +#ifdef EXTENDED_PIPELINES + if(CustomPipes::bRenderingEnvMap && (e->IsPed() || e->IsVehicle())) + continue; +#endif + + if(e->IsVehicle() || + e->IsPed() && CVisibilityPlugins::GetClumpAlpha((RpClump*)e->m_rwObject) != 255){ + if(e->IsVehicle() && ((CVehicle*)e)->IsBoat()){ + ei.ent = e; + dist = ms_vecCameraPosition - e->GetPosition(); + ei.sort = dist.MagnitudeSqr(); + gSortedVehiclesAndPeds.InsertSorted(ei); + }else{ + dist = ms_vecCameraPosition - e->GetPosition(); + if(!CVisibilityPlugins::InsertEntityIntoSortedList(e, dist.Magnitude())){ + printf("Ran out of space in alpha entity list"); + RenderOneNonRoad(e); + } + } + }else + RenderOneNonRoad(e); + } + POP_RENDERGROUP(); +} + +void +CRenderer::RenderVehiclesButNotBoats(void) +{ + // This function doesn't do anything + // because only boats are inserted into the list + CLink *node; + + for(node = gSortedVehiclesAndPeds.tail.prev; + node != &gSortedVehiclesAndPeds.head; + node = node->prev){ + // only boats in this list + CVehicle *v = (CVehicle*)node->item.ent; + if(!v->IsBoat()) + RenderOneNonRoad(v); + } +} + +void +CRenderer::RenderBoats(void) +{ + CLink *node; + + PUSH_RENDERGROUP("CRenderer::RenderBoats"); + BACKFACE_CULLING_ON; + + for(node = gSortedVehiclesAndPeds.tail.prev; + node != &gSortedVehiclesAndPeds.head; + node = node->prev){ + // only boats in this list + CVehicle *v = (CVehicle*)node->item.ent; + if(v->IsBoat()) + RenderOneNonRoad(v); + } + POP_RENDERGROUP(); +} + +#ifdef NEW_RENDERER +#ifndef LIBRW +#error "Need librw for EXTENDED_PIPELINES" +#endif +#include "WaterLevel.h" + +enum { + // blend passes + PASS_NOZ, // no z-write + PASS_ADD, // additive + PASS_BLEND // normal blend +}; + +static RwRGBAReal black; + +static void +SetStencilState(int state) +{ + switch(state){ + // disable stencil + case 0: + rw::SetRenderState(rw::STENCILENABLE, FALSE); + break; + // test against stencil + case 1: + rw::SetRenderState(rw::STENCILENABLE, TRUE); + rw::SetRenderState(rw::STENCILFUNCTION, rw::STENCILNOTEQUAL); + rw::SetRenderState(rw::STENCILPASS, rw::STENCILKEEP); + rw::SetRenderState(rw::STENCILFAIL, rw::STENCILKEEP); + rw::SetRenderState(rw::STENCILZFAIL, rw::STENCILKEEP); + rw::SetRenderState(rw::STENCILFUNCTIONMASK, 0xFF); + rw::SetRenderState(rw::STENCILFUNCTIONREF, 0xFF); + break; + // write to stencil + case 2: + rw::SetRenderState(rw::STENCILENABLE, TRUE); + rw::SetRenderState(rw::STENCILFUNCTION, rw::STENCILALWAYS); + rw::SetRenderState(rw::STENCILPASS, rw::STENCILREPLACE); + rw::SetRenderState(rw::STENCILFUNCTIONREF, 0xFF); + break; + } +} + +void +CRenderer::RenderOneBuilding(CEntity *ent, float camdist) +{ + if(ent->m_rwObject == nil) + return; + + ent->bImBeingRendered = true; // TODO: this seems wrong, but do we even need it? + + assert(RwObjectGetType(ent->m_rwObject) == rpATOMIC); + RpAtomic *atomic = (RpAtomic*)ent->m_rwObject; + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->GetModelIndex()); + +#ifdef EXTRA_MODEL_FLAGS + bool resetCull = false; + if(!ent->IsBuilding() || mi->RenderDoubleSided()){ + resetCull = true; + BACKFACE_CULLING_OFF; + } +#endif + + int pass = PASS_BLEND; + if(mi->m_additive) // very questionable + pass = PASS_ADD; + if(mi->m_noZwrite) + pass = PASS_NOZ; + + if(ent->bDistanceFade){ + RpAtomic *lodatm; + float fadefactor; + uint32 alpha; + + lodatm = mi->GetAtomicFromDistance(camdist - FADE_DISTANCE); + fadefactor = (mi->GetLargestLodDistance() - (camdist - FADE_DISTANCE))/FADE_DISTANCE; + if(fadefactor > 1.0f) + fadefactor = 1.0f; + alpha = mi->m_alpha * fadefactor; + + if(alpha == 255) + WorldRender::AtomicFirstPass(atomic, pass); + else{ + // not quite sure what this is about, do we have to do that? + RpGeometry *geo = RpAtomicGetGeometry(lodatm); + if(geo != RpAtomicGetGeometry(atomic)) + RpAtomicSetGeometry(atomic, geo, rpATOMICSAMEBOUNDINGSPHERE); + WorldRender::AtomicFullyTransparent(atomic, pass, alpha); + } + }else + WorldRender::AtomicFirstPass(atomic, pass); + +#ifdef EXTRA_MODEL_FLAGS + if(resetCull) + BACKFACE_CULLING_ON; +#endif + + ent->bImBeingRendered = false; // TODO: this seems wrong, but do we even need it? +} + +void +CRenderer::RenderWorld(int pass) +{ + int i; + CEntity *e; + CLink *node; + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + BACKFACE_CULLING_ON; + DeActivateDirectional(); + SetAmbientColours(); + + // Temporary...have to figure out sorting better + switch(pass){ + case 0: + // Roads + PUSH_RENDERGROUP("CRenderer::RenderWorld - Roads"); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); +/* + for(i = 0; i < ms_nNoOfVisibleBuildings; i++){ + e = ms_aVisibleBuildingPtrs[i]; + if(e->bIsBIGBuilding || IsRoad(e)) + RenderOneBuilding(e); + } +*/ + for(CLink *node = gSortedBuildings.tail.prev; + node != &gSortedBuildings.head; + node = node->prev){ + e = node->item.ent; + if(e->bIsBIGBuilding || IsRoad(e)) + RenderOneBuilding(e); + } + for(node = CVisibilityPlugins::m_alphaBuildingList.tail.prev; + node != &CVisibilityPlugins::m_alphaBuildingList.head; + node = node->prev){ + e = node->item.entity; + if(e->bIsBIGBuilding || IsRoad(e)) + RenderOneBuilding(e, node->item.sort); + } + + // KLUDGE for road puddles which have to be rendered at road-time + // only very temporary, there are more rendering issues + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + WorldRender::RenderBlendPass(PASS_BLEND); + WorldRender::numBlendInsts[PASS_BLEND] = 0; + POP_RENDERGROUP(); + break; + case 1: + // Opaque + PUSH_RENDERGROUP("CRenderer::RenderWorld - Opaque"); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); +/* + for(i = 0; i < ms_nNoOfVisibleBuildings; i++){ + e = ms_aVisibleBuildingPtrs[i]; + if(!(e->bIsBIGBuilding || IsRoad(e))) + RenderOneBuilding(e); + } +*/ + for(CLink *node = gSortedBuildings.tail.prev; + node != &gSortedBuildings.head; + node = node->prev){ + e = node->item.ent; + if(!(e->bIsBIGBuilding || IsRoad(e))) + RenderOneBuilding(e); + } + for(node = CVisibilityPlugins::m_alphaBuildingList.tail.prev; + node != &CVisibilityPlugins::m_alphaBuildingList.head; + node = node->prev){ + e = node->item.entity; + if(!(e->bIsBIGBuilding || IsRoad(e))) + RenderOneBuilding(e, node->item.sort); + } + // Now we have iterated through all visible buildings (unsorted and sorted) + // and the transparency list is done. + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); + WorldRender::RenderBlendPass(PASS_NOZ); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + POP_RENDERGROUP(); + break; + case 2: + // Transparent + PUSH_RENDERGROUP("CRenderer::RenderWorld - Transparent"); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + WorldRender::RenderBlendPass(PASS_ADD); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + WorldRender::RenderBlendPass(PASS_BLEND); + POP_RENDERGROUP(); + break; + } +} + +void +CRenderer::RenderPeds(void) +{ + int i; + CEntity *e; + + PUSH_RENDERGROUP("CRenderer::RenderPeds"); + for(i = 0; i < ms_nNoOfVisibleVehicles; i++){ + e = ms_aVisibleVehiclePtrs[i]; + if(e->IsPed()) + RenderOneNonRoad(e); + } + POP_RENDERGROUP(); +} + +void +CRenderer::RenderVehicles(void) +{ + int i; + CEntity *e; + EntityInfo ei; + CLink *node; + + PUSH_RENDERGROUP("CRenderer::RenderVehicles"); + // not the real thing + for(i = 0; i < ms_nNoOfVisibleVehicles; i++){ + e = ms_aVisibleVehiclePtrs[i]; + if(!e->IsVehicle()) + continue; +// if(PutIntoSortedVehicleList((CVehicle*)e)) +// continue; // boats handled elsewhere + ei.ent = e; + ei.sort = (ms_vecCameraPosition - e->GetPosition()).MagnitudeSqr(); + gSortedVehiclesAndPeds.InsertSorted(ei); + } + + for(node = gSortedVehiclesAndPeds.tail.prev; + node != &gSortedVehiclesAndPeds.head; + node = node->prev) + RenderOneNonRoad(node->item.ent); + POP_RENDERGROUP(); +} + +void +CRenderer::RenderWater(void) +{ + int i; + CEntity *e; + + PUSH_RENDERGROUP("CRenderer::RenderWater"); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + SetStencilState(2); + + for(i = 0; i < ms_nNoOfVisibleVehicles; i++){ + e = ms_aVisibleVehiclePtrs[i]; + if(e->IsVehicle() && ((CVehicle*)e)->IsBoat()) + ((CBoat*)e)->RenderWaterOutPolys(); + } + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + SetStencilState(1); + + CWaterLevel::RenderWater(); + + SetStencilState(0); + POP_RENDERGROUP(); +} + +void +CRenderer::ClearForFrame(void) +{ + ms_nNoOfVisibleEntities = 0; + ms_nNoOfVisibleVehicles = 0; + ms_nNoOfVisibleBuildings = 0; + ms_nNoOfInVisibleEntities = 0; + gSortedVehiclesAndPeds.Clear(); + gSortedBuildings.Clear(); + + WorldRender::numBlendInsts[PASS_NOZ] = 0; + WorldRender::numBlendInsts[PASS_ADD] = 0; + WorldRender::numBlendInsts[PASS_BLEND] = 0; +} +#endif + +void +CRenderer::RenderFadingInEntities(void) +{ + PUSH_RENDERGROUP("CRenderer::RenderFadingInEntities"); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + BACKFACE_CULLING_ON; + DeActivateDirectional(); + SetAmbientColours(); + CVisibilityPlugins::RenderFadingEntities(); + POP_RENDERGROUP(); +} + +void +CRenderer::RenderCollisionLines(void) +{ + int i; + + // game doesn't draw fading in entities + // this should probably be fixed + for(i = 0; i < ms_nNoOfVisibleEntities; i++){ + CEntity *e = ms_aVisibleEntityPtrs[i]; + if(Abs(e->GetPosition().x - ms_vecCameraPosition.x) < 100.0f && + Abs(e->GetPosition().y - ms_vecCameraPosition.y) < 100.0f) + CCollision::DrawColModel(e->GetMatrix(), *e->GetColModel()); + } +} + +// unused +void +CRenderer::RenderBlockBuildingLines(void) +{ + for(BlockedRange *br = pFullBlockedRanges; br; br = br->next) + printf("Blocked: %f %f\n", br->a, br->b); +} + +enum Visbility +{ + VIS_INVISIBLE, + VIS_VISIBLE, + VIS_OFFSCREEN, + VIS_STREAMME +}; + +// Time Objects can be time culled if +// other == -1 || CModelInfo::GetModelInfo(other)->GetRwObject() +// i.e. we have to draw even at the wrong time if +// other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject() == nil + +#define OTHERUNAVAILABLE (other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject() == nil) +#define CANTIMECULL (!OTHERUNAVAILABLE) + +int32 +CRenderer::SetupEntityVisibility(CEntity *ent) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->m_modelIndex); + CTimeModelInfo *ti; + int32 other; + float dist; + + bool request = true; + if (mi->GetModelType() == MITYPE_TIME) { + ti = (CTimeModelInfo*)mi; + other = ti->GetOtherTimeModel(); + if(CClock::GetIsTimeInRange(ti->GetTimeOn(), ti->GetTimeOff())){ + // don't fade in, or between time objects + if(CANTIMECULL) + ti->m_alpha = 255; + }else{ + // Hide if possible + if(CANTIMECULL) + return VIS_INVISIBLE; + // can't cull, so we'll try to draw this one, but don't request + // it since what we really want is the other one. + request = false; + } + }else{ + if (mi->GetModelType() != MITYPE_SIMPLE) { + if(FindPlayerVehicle() == ent && + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON){ + // Player's vehicle in first person mode + if(TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking == LOOKING_FORWARD || + ent->GetModelIndex() == MI_RHINO || + ent->GetModelIndex() == MI_COACH || + TheCamera.m_bInATunnelAndABigVehicle){ + ent->bNoBrightHeadLights = true; + }else{ + m_pFirstPersonVehicle = (CVehicle*)ent; + ent->bNoBrightHeadLights = false; + } + return VIS_OFFSCREEN; + } + // All sorts of Clumps + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + if(!ent->GetIsOnScreen()) + return VIS_OFFSCREEN; + if(ent->bDrawLast){ + dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude(); + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = false; + return VIS_INVISIBLE; + } + return VIS_VISIBLE; + } + if(ent->IsObject() && + ((CObject*)ent)->ObjectCreatedBy == TEMP_OBJECT){ + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + return ent->GetIsOnScreen() ? VIS_VISIBLE : VIS_OFFSCREEN; + } + } + + // Simple ModelInfo + + dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude(); + + // This can only happen with multi-atomic models (e.g. railtracks) + // but why do we bump up the distance? can only be fading... + if(LOD_DISTANCE + STREAM_DISTANCE < dist && dist < mi->GetLargestLodDistance()) + dist = mi->GetLargestLodDistance(); + + if(ent->IsObject() && ent->bRenderDamaged) + mi->m_isDamaged = true; + + RpAtomic *a = mi->GetAtomicFromDistance(dist); + if(a){ + mi->m_isDamaged = false; + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + // Make sure our atomic uses the right geometry and not + // that of an atomic for another draw distance. + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?) + mi->IncreaseAlpha(); + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + + if(!ent->GetIsOnScreen()){ + mi->m_alpha = 255; + return VIS_OFFSCREEN; + } + + if(mi->m_alpha != 255){ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = true; + return VIS_INVISIBLE; + } + + if(mi->m_drawLast || ent->bDrawLast){ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = false; + return VIS_INVISIBLE; + } + return VIS_VISIBLE; + } + + // Object is not loaded, figure out what to do + + if(mi->m_noFade){ + mi->m_isDamaged = false; + // request model + if(dist - STREAM_DISTANCE < mi->GetLargestLodDistance() && request) + return VIS_STREAMME; + return VIS_INVISIBLE; + } + + // We might be fading + + a = mi->GetAtomicFromDistance(dist - FADE_DISTANCE); + mi->m_isDamaged = false; + if(a == nil){ + // request model + if(dist - FADE_DISTANCE - STREAM_DISTANCE < mi->GetLargestLodDistance() && request) + return VIS_STREAMME; + return VIS_INVISIBLE; + } + + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?) + mi->IncreaseAlpha(); + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + + if(!ent->GetIsOnScreen()){ + mi->m_alpha = 255; + return VIS_OFFSCREEN; + }else{ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = true; + return VIS_OFFSCREEN; // Why this? + } +} + +int32 +CRenderer::SetupBigBuildingVisibility(CEntity *ent) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo *)CModelInfo::GetModelInfo(ent->GetModelIndex()); + CTimeModelInfo *ti; + int32 other; + + if (mi->GetModelType() == MITYPE_TIME) { + ti = (CTimeModelInfo*)mi; + other = ti->GetOtherTimeModel(); + // Hide objects not in time range if possible + if(CANTIMECULL) + if(!CClock::GetIsTimeInRange(ti->GetTimeOn(), ti->GetTimeOff())) + return VIS_INVISIBLE; + // Draw like normal + } else if (mi->GetModelType() == MITYPE_VEHICLE) + return ent->IsVisible() ? VIS_VISIBLE : VIS_INVISIBLE; + + float dist = (ms_vecCameraPosition-ent->GetPosition()).Magnitude(); + CSimpleModelInfo *nonLOD = mi->GetRelatedModel(); + + // Find out whether to draw below near distance. + // This is only the case if there is a non-LOD which is either not + // loaded or not completely faded in yet. + if(dist < mi->GetNearDistance() && dist < LOD_DISTANCE + STREAM_DISTANCE){ + // No non-LOD or non-LOD is completely visible. + if(nonLOD == nil || + nonLOD->GetRwObject() && nonLOD->m_alpha == 255) + return VIS_INVISIBLE; + + // But if it is a time object, we'd rather draw the wrong + // non-LOD than the right LOD. + if (nonLOD->GetModelType() == MITYPE_TIME) { + ti = (CTimeModelInfo*)nonLOD; + other = ti->GetOtherTimeModel(); + if(other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject()) + return VIS_INVISIBLE; + } + } + + RpAtomic *a = mi->GetAtomicFromDistance(dist); + if(a){ + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + + // Make sure our atomic uses the right geometry and not + // that of an atomic for another draw distance. + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?) + if (!ent->IsVisible() || !ent->GetIsOnScreenComplex()) + return VIS_INVISIBLE; + if(mi->m_drawLast){ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = false; + return VIS_INVISIBLE; + } + return VIS_VISIBLE; + } + + if(mi->m_noFade){ + ent->DeleteRwObject(); + return VIS_INVISIBLE; + } + + + // get faded atomic + a = mi->GetAtomicFromDistance(dist - FADE_DISTANCE); + if(a == nil){ + ent->DeleteRwObject(); + return VIS_INVISIBLE; + } + + // Fade... + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?) + if (ent->IsVisible() && ent->GetIsOnScreenComplex()) + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + return VIS_INVISIBLE; +} + +void +CRenderer::ConstructRenderList(void) +{ +#ifdef NEW_RENDERER + if(!gbNewRenderer) +#endif +{ + ms_nNoOfVisibleEntities = 0; + ms_nNoOfInVisibleEntities = 0; +} + ms_vecCameraPosition = TheCamera.GetPosition(); + + // unused + pFullBlockedRanges = nil; + pEmptyBlockedRanges = aBlockedRanges; + for(int i = 0; i < 16; i++){ + aBlockedRanges[i].prev = &aBlockedRanges[i-1]; + aBlockedRanges[i].next = &aBlockedRanges[i+1]; + } + aBlockedRanges[0].prev = nil; + aBlockedRanges[15].next = nil; + + // unused + TestCloseThings = 0; + TestBigThings = 0; + + ScanWorld(); +} + +void +LimitFrustumVector(CVector &vec1, const CVector &vec2, float l) +{ + float f; + f = (l - vec2.z) / (vec1.z - vec2.z); + vec1.x = f*(vec1.x - vec2.x) + vec2.x; + vec1.y = f*(vec1.y - vec2.y) + vec2.y; + vec1.z = f*(vec1.z - vec2.z) + vec2.z; +} + +enum Corners +{ + CORNER_CAM = 0, + CORNER_FAR_TOPLEFT, + CORNER_FAR_TOPRIGHT, + CORNER_FAR_BOTRIGHT, + CORNER_FAR_BOTLEFT, + CORNER_LOD_LEFT, + CORNER_LOD_RIGHT, + CORNER_PRIO_LEFT, + CORNER_PRIO_RIGHT, +}; + +void +CRenderer::ScanWorld(void) +{ + float f = RwCameraGetFarClipPlane(TheCamera.m_pRwCamera); + RwV2d vw = *RwCameraGetViewWindow(TheCamera.m_pRwCamera); + CVector vectors[9]; + RwMatrix *cammatrix; + RwV2d poly[3]; + +#ifndef MASTER + // missing in game but has to be done somewhere + EntitiesRendered = 0; + EntitiesNotRendered = 0; + RenderedBigBuildings = 0; + RenderedBuildings = 0; + RenderedCars = 0; + RenderedPeds = 0; + RenderedObjects = 0; + RenderedDummies = 0; + TestedBigBuildings = 0; + TestedBuildings = 0; + TestedCars = 0; + TestedPeds = 0; + TestedObjects = 0; + TestedDummies = 0; +#endif + + memset(vectors, 0, sizeof(vectors)); + vectors[CORNER_FAR_TOPLEFT].x = -vw.x * f; + vectors[CORNER_FAR_TOPLEFT].y = vw.y * f; + vectors[CORNER_FAR_TOPLEFT].z = f; + vectors[CORNER_FAR_TOPRIGHT].x = vw.x * f; + vectors[CORNER_FAR_TOPRIGHT].y = vw.y * f; + vectors[CORNER_FAR_TOPRIGHT].z = f; + vectors[CORNER_FAR_BOTRIGHT].x = vw.x * f; + vectors[CORNER_FAR_BOTRIGHT].y = -vw.y * f; + vectors[CORNER_FAR_BOTRIGHT].z = f; + vectors[CORNER_FAR_BOTLEFT].x = -vw.x * f; + vectors[CORNER_FAR_BOTLEFT].y = -vw.y * f; + vectors[CORNER_FAR_BOTLEFT].z = f; + + cammatrix = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + + m_pFirstPersonVehicle = nil; + CVisibilityPlugins::InitAlphaEntityList(); + CWorld::AdvanceCurrentScanCode(); + + if(cammatrix->at.z > 0.0f){ + // looking up, bottom corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_BOTLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_BOTRIGHT] * LOD_DISTANCE/f; + }else{ + // looking down, top corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_TOPLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_TOPRIGHT] * LOD_DISTANCE/f; + } + vectors[CORNER_PRIO_LEFT].x = vectors[CORNER_LOD_LEFT].x * 0.2f; + vectors[CORNER_PRIO_LEFT].y = vectors[CORNER_LOD_LEFT].y * 0.2f; + vectors[CORNER_PRIO_LEFT].z = vectors[CORNER_LOD_LEFT].z; + vectors[CORNER_PRIO_RIGHT].x = vectors[CORNER_LOD_RIGHT].x * 0.2f; + vectors[CORNER_PRIO_RIGHT].y = vectors[CORNER_LOD_RIGHT].y * 0.2f; + vectors[CORNER_PRIO_RIGHT].z = vectors[CORNER_LOD_RIGHT].z; + RwV3dTransformPoints(vectors, vectors, 9, cammatrix); + + m_loadingPriority = false; + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || +#ifdef FIX_BUGS + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_GTACLASSIC || +#endif + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + CRect rect; + int x1, x2, y1, y2; + LimitFrustumVector(vectors[CORNER_FAR_TOPLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPLEFT]); + LimitFrustumVector(vectors[CORNER_FAR_TOPRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTLEFT]); + x1 = CWorld::GetSectorIndexX(rect.left); + if(x1 < 0) x1 = 0; + x2 = CWorld::GetSectorIndexX(rect.right); + if(x2 >= NUMSECTORS_X-1) x2 = NUMSECTORS_X-1; + y1 = CWorld::GetSectorIndexY(rect.top); + if(y1 < 0) y1 = 0; + y2 = CWorld::GetSectorIndexY(rect.bottom); + if(y2 >= NUMSECTORS_Y-1) y2 = NUMSECTORS_Y-1; + for(; x1 <= x2; x1++) + for(int y = y1; y <= y2; y++) + ScanSectorList(CWorld::GetSector(x1, y)->m_lists); + }else{ + CVehicle *train = FindPlayerTrain(); + if(train && train->GetPosition().z < 0.0f){ + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList_Subway); + }else{ + if(f > LOD_DISTANCE){ + // priority + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_PRIO_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_PRIO_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_PRIO_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_PRIO_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList_Priority); + + // below LOD + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList); + }else{ + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_FAR_TOPLEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_FAR_TOPLEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_FAR_TOPRIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_FAR_TOPRIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList); + } +#ifdef NO_ISLAND_LOADING + if (CMenuManager::m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_HIGH) { + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_INDUSTRIAL)); + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_COMMERCIAL)); + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_SUBURBAN)); + } else +#endif + { + #ifdef FIX_BUGS + if (CCollision::ms_collisionInMemory != LEVEL_GENERIC) + #endif + ScanBigBuildingList(CWorld::GetBigBuildingList(CCollision::ms_collisionInMemory)); + } + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_GENERIC)); + } + } + +#ifndef MASTER + if(gbShowCullZoneDebugStuff){ + sprintf(gString, "Rejected: %d/%d.", EntitiesNotRendered, EntitiesNotRendered + EntitiesRendered); + CDebug::PrintAt(gString, 10, 10); + sprintf(gString, "Tested:BBuild:%d Build:%d Peds:%d Cars:%d Obj:%d Dummies:%d", + TestedBigBuildings, TestedBuildings, TestedPeds, TestedCars, TestedObjects, TestedDummies); + CDebug::PrintAt(gString, 10, 11); + sprintf(gString, "Rendered:BBuild:%d Build:%d Peds:%d Cars:%d Obj:%d Dummies:%d", + RenderedBigBuildings, RenderedBuildings, RenderedPeds, RenderedCars, RenderedObjects, RenderedDummies); + CDebug::PrintAt(gString, 10, 12); + } +#endif +} + +void +CRenderer::RequestObjectsInFrustum(void) +{ + float f = RwCameraGetFarClipPlane(TheCamera.m_pRwCamera); + RwV2d vw = *RwCameraGetViewWindow(TheCamera.m_pRwCamera); + CVector vectors[9]; + RwMatrix *cammatrix; + RwV2d poly[3]; + + memset(vectors, 0, sizeof(vectors)); + vectors[CORNER_FAR_TOPLEFT].x = -vw.x * f; + vectors[CORNER_FAR_TOPLEFT].y = vw.y * f; + vectors[CORNER_FAR_TOPLEFT].z = f; + vectors[CORNER_FAR_TOPRIGHT].x = vw.x * f; + vectors[CORNER_FAR_TOPRIGHT].y = vw.y * f; + vectors[CORNER_FAR_TOPRIGHT].z = f; + vectors[CORNER_FAR_BOTRIGHT].x = vw.x * f; + vectors[CORNER_FAR_BOTRIGHT].y = -vw.y * f; + vectors[CORNER_FAR_BOTRIGHT].z = f; + vectors[CORNER_FAR_BOTLEFT].x = -vw.x * f; + vectors[CORNER_FAR_BOTLEFT].y = -vw.y * f; + vectors[CORNER_FAR_BOTLEFT].z = f; + + cammatrix = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + + CWorld::AdvanceCurrentScanCode(); + + if(cammatrix->at.z > 0.0f){ + // looking up, bottom corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_BOTLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_BOTRIGHT] * LOD_DISTANCE/f; + }else{ + // looking down, top corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_TOPLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_TOPRIGHT] * LOD_DISTANCE/f; + } + vectors[CORNER_PRIO_LEFT].x = vectors[CORNER_LOD_LEFT].x * 0.2f; + vectors[CORNER_PRIO_LEFT].y = vectors[CORNER_LOD_LEFT].y * 0.2f; + vectors[CORNER_PRIO_LEFT].z = vectors[CORNER_LOD_LEFT].z; + vectors[CORNER_PRIO_RIGHT].x = vectors[CORNER_LOD_RIGHT].x * 0.2f; + vectors[CORNER_PRIO_RIGHT].y = vectors[CORNER_LOD_RIGHT].y * 0.2f; + vectors[CORNER_PRIO_RIGHT].z = vectors[CORNER_LOD_RIGHT].z; + RwV3dTransformPoints(vectors, vectors, 9, cammatrix); + + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || +#ifdef FIX_BUGS + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_GTACLASSIC || +#endif + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + CRect rect; + int x1, x2, y1, y2; + LimitFrustumVector(vectors[CORNER_FAR_TOPLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPLEFT]); + LimitFrustumVector(vectors[CORNER_FAR_TOPRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTLEFT]); + x1 = CWorld::GetSectorIndexX(rect.left); + if(x1 < 0) x1 = 0; + x2 = CWorld::GetSectorIndexX(rect.right); + if(x2 >= NUMSECTORS_X-1) x2 = NUMSECTORS_X-1; + y1 = CWorld::GetSectorIndexY(rect.top); + if(y1 < 0) y1 = 0; + y2 = CWorld::GetSectorIndexY(rect.bottom); + if(y2 >= NUMSECTORS_Y-1) y2 = NUMSECTORS_Y-1; + for(; x1 <= x2; x1++) + for(int y = y1; y <= y2; y++) + ScanSectorList_RequestModels(CWorld::GetSector(x1, y)->m_lists); + }else{ + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList_RequestModels); + } +} + +bool +CEntity::SetupLighting(void) +{ + DeActivateDirectional(); + SetAmbientColours(); + return false; +} + +void +CEntity::RemoveLighting(bool) +{ +} + +bool +CPed::SetupLighting(void) +{ + ActivateDirectional(); + SetAmbientColoursForPedsCarsAndObjects(); + +#ifndef MASTER + // Originally this was being called through iteration of Sectors, but putting it here is better. + if (GetDebugDisplay() != 0 && !IsPlayer()) + DebugRenderOnePedText(); +#endif + + if (bRenderScorched) { + WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f); + } else { + // Note that this lightMult is only affected by LIGHT_DARKEN. If there's no LIGHT_DARKEN, it will be 1.0. + float lightMult = CPointLights::GenerateLightsAffectingObject(&GetPosition()); + if (!bHasBlip && lightMult != 1.0f) { + SetAmbientAndDirectionalColours(lightMult); + return true; + } + } + return false; +} + +void +CPed::RemoveLighting(bool reset) +{ + CRenderer::RemoveVehiclePedLights(this, reset); +} + +float +CalcNewDelta(RwV2d *a, RwV2d *b) +{ + return (b->x - a->x) / (b->y - a->y); +} + +#ifdef FIX_BUGS +#define TOINT(x) ((int)Floor(x)) +#else +#define TOINT(x) ((int)(x)) +#endif + +void +CRenderer::ScanSectorPoly(RwV2d *poly, int32 numVertices, void (*scanfunc)(CPtrList *)) +{ + float miny, maxy; + int y, yend; + int x, xstart, xend; + int i; + int a1, a2, b1, b2; + float deltaA, deltaB; + float xA, xB; + + miny = poly[0].y; + maxy = poly[0].y; + a2 = 0; + xstart = 9999; + xend = -9999; + + for(i = 1; i < numVertices; i++){ + if(poly[i].y > maxy) + maxy = poly[i].y; + if(poly[i].y < miny){ + miny = poly[i].y; + a2 = i; + } + } + y = TOINT(miny); + yend = TOINT(maxy); + + // Go left in poly to find first edge b + b2 = a2; + for(i = 0; i < numVertices; i++){ + b1 = b2--; + if(b2 < 0) b2 = numVertices-1; + if(poly[b1].x < xstart) + xstart = TOINT(poly[b1].x); + if(TOINT(poly[b1].y) != TOINT(poly[b2].y)) + break; + } + // Go right to find first edge a + for(i = 0; i < numVertices; i++){ + a1 = a2++; + if(a2 == numVertices) a2 = 0; + if(poly[a1].x > xend) + xend = TOINT(poly[a1].x); + if(TOINT(poly[a1].y) != TOINT(poly[a2].y)) + break; + } + + // prestep x1 and x2 to next integer y + deltaA = CalcNewDelta(&poly[a1], &poly[a2]); + xA = deltaA * (Ceil(poly[a1].y) - poly[a1].y) + poly[a1].x; + deltaB = CalcNewDelta(&poly[b1], &poly[b2]); + xB = deltaB * (Ceil(poly[b1].y) - poly[b1].y) + poly[b1].x; + + if(y != yend){ + if(deltaB < 0.0f && TOINT(xB) < xstart) + xstart = TOINT(xB); + if(deltaA >= 0.0f && TOINT(xA) > xend) + xend = TOINT(xA); + } + + while(y <= yend && y < NUMSECTORS_Y){ + // scan one x-line + if(y >= 0 && xstart < NUMSECTORS_X) + for(x = xstart; x <= xend && x != NUMSECTORS_X; x++) + if(x >= 0) + scanfunc(CWorld::GetSector(x, y)->m_lists); + + // advance one scan line + y++; + xA += deltaA; + xB += deltaB; + + // update left side + if(y == TOINT(poly[b2].y)){ + // reached end of edge + if(y == yend){ + if(deltaB < 0.0f){ + do{ + xstart = TOINT(poly[b2--].x); + if(b2 < 0) b2 = numVertices-1; + }while(xstart > TOINT(poly[b2].x)); + }else + xstart = TOINT(xB - deltaB); + }else{ + // switch edges + if(deltaB < 0.0f) + xstart = TOINT(poly[b2].x); + else + xstart = TOINT(xB - deltaB); + do{ + b1 = b2--; + if(b2 < 0) b2 = numVertices-1; + if(TOINT(poly[b1].x) < xstart) + xstart = TOINT(poly[b1].x); + }while(y == TOINT(poly[b2].y)); + deltaB = CalcNewDelta(&poly[b1], &poly[b2]); + xB = deltaB * (Ceil(poly[b1].y) - poly[b1].y) + poly[b1].x; + if(deltaB < 0.0f && TOINT(xB) < xstart) + xstart = TOINT(xB); + } + }else{ + if(deltaB < 0.0f) + xstart = TOINT(xB); + else + xstart = TOINT(xB - deltaB); + } + + // update right side + if(y == TOINT(poly[a2].y)){ + // reached end of edge + if(y == yend){ + if(deltaA < 0.0f) + xend = TOINT(xA - deltaA); + else{ + do{ + xend = TOINT(poly[a2++].x); + if(a2 == numVertices) a2 = 0; + }while(xend < TOINT(poly[a2].x)); + } + }else{ + // switch edges + if(deltaA < 0.0f) + xend = TOINT(xA - deltaA); + else + xend = TOINT(poly[a2].x); + do{ + a1 = a2++; + if(a2 == numVertices) a2 = 0; + if(TOINT(poly[a1].x) > xend) + xend = TOINT(poly[a1].x); + }while(y == TOINT(poly[a2].y)); + deltaA = CalcNewDelta(&poly[a1], &poly[a2]); + xA = deltaA * (Ceil(poly[a1].y) - poly[a1].y) + poly[a1].x; + if(deltaA >= 0.0f && TOINT(xA) > xend) + xend = TOINT(xA); + } + }else{ + if(deltaA < 0.0f) + xend = TOINT(xA - deltaA); + else + xend = TOINT(xA); + } + } +} + +void +CRenderer::InsertEntityIntoList(CEntity *ent) +{ +#ifdef FIX_BUGS + if (!ent->m_rwObject) return; +#endif + +#ifdef NEW_RENDERER + // TODO: there are more flags being checked here + if(gbNewRenderer && (ent->IsVehicle() || ent->IsPed())) + ms_aVisibleVehiclePtrs[ms_nNoOfVisibleVehicles++] = ent; + else if(gbNewRenderer && ent->IsBuilding()){ + EntityInfo info; + info.ent = ent; + info.sort = -(ent->GetPosition() - ms_vecCameraPosition).MagnitudeSqr(); + gSortedBuildings.InsertSorted(info); +// ms_aVisibleBuildingPtrs[ms_nNoOfVisibleBuildings++] = ent; + }else +#endif + ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent; +} + +void +CRenderer::ScanBigBuildingList(CPtrList &list) +{ + CPtrNode *node; + CEntity *ent; + + for(node = list.first; node; node = node->next){ + ent = (CEntity*)node->item; +#ifndef MASTER + // all missing from game actually + TestedBigBuildings++; +#endif + if(!ent->bZoneCulled || gbDisableZoneCull){ + if(SetupBigBuildingVisibility(ent) == VIS_VISIBLE) + InsertEntityIntoList(ent); +#ifndef MASTER + EntitiesRendered++; + RenderedBigBuildings++; + }else{ + EntitiesNotRendered++; +#endif + } + } +} + +void +CRenderer::ScanSectorList(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + float dx, dy; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + + if(IsEntityCullZoneVisible(ent)){ + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + InsertEntityIntoList(ent); + break; + case VIS_INVISIBLE: + if(!IsGlass(ent->GetModelIndex())) + break; + // fall through + case VIS_OFFSCREEN: + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -65.0f && dx < 65.0f && + dy > -65.0f && dy < 65.0f && + ms_nNoOfInVisibleEntities < NUMINVISIBLEENTITIES - 1) + ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent; + break; + case VIS_STREAMME: + if(!CStreaming::ms_disableStreaming) + if(!m_loadingPriority || CStreaming::ms_numModelsRequested < 10) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + break; + } +#ifndef MASTER + EntitiesRendered++; + switch(ent->GetType()){ + case ENTITY_TYPE_BUILDING: + if(ent->bIsBIGBuilding) + RenderedBigBuildings++; + else + RenderedBuildings++; + break; + case ENTITY_TYPE_VEHICLE: + RenderedCars++; + break; + case ENTITY_TYPE_PED: + RenderedPeds++; + break; + case ENTITY_TYPE_OBJECT: + RenderedObjects++; + break; + case ENTITY_TYPE_DUMMY: + RenderedDummies++; + break; + } +#endif + }else if(IsRoad(ent) && !CStreaming::ms_disableStreaming){ + if(SetupEntityVisibility(ent) == VIS_STREAMME) + if(!m_loadingPriority || CStreaming::ms_numModelsRequested < 10) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + }else{ +#ifndef MASTER + EntitiesNotRendered++; +#endif + } + } + } +} + +void +CRenderer::ScanSectorList_Priority(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + float dx, dy; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + + if(IsEntityCullZoneVisible(ent)){ + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + InsertEntityIntoList(ent); + break; + case VIS_INVISIBLE: + if(!IsGlass(ent->GetModelIndex())) + break; + // fall through + case VIS_OFFSCREEN: + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -65.0f && dx < 65.0f && + dy > -65.0f && dy < 65.0f && + ms_nNoOfInVisibleEntities < NUMINVISIBLEENTITIES - 1) + ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent; + break; + case VIS_STREAMME: + if(!CStreaming::ms_disableStreaming){ + CStreaming::RequestModel(ent->GetModelIndex(), 0); + if(CStreaming::ms_aInfoForModel[ent->GetModelIndex()].m_loadState != STREAMSTATE_LOADED) + m_loadingPriority = true; + } + break; + } +#ifndef MASTER + // actually missing in game + EntitiesRendered++; + switch(ent->GetType()){ + case ENTITY_TYPE_BUILDING: + if(ent->bIsBIGBuilding) + RenderedBigBuildings++; + else + RenderedBuildings++; + break; + case ENTITY_TYPE_VEHICLE: + RenderedCars++; + break; + case ENTITY_TYPE_PED: + RenderedPeds++; + break; + case ENTITY_TYPE_OBJECT: + RenderedObjects++; + break; + case ENTITY_TYPE_DUMMY: + RenderedDummies++; + break; + } +#endif + }else if(IsRoad(ent) && !CStreaming::ms_disableStreaming){ + if(SetupEntityVisibility(ent) == VIS_STREAMME) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + }else{ +#ifndef MASTER + // actually missing in game + EntitiesNotRendered++; +#endif + } + } + } +} + +void +CRenderer::ScanSectorList_Subway(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + float dx, dy; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + InsertEntityIntoList(ent); + break; + case VIS_OFFSCREEN: + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -65.0f && dx < 65.0f && + dy > -65.0f && dy < 65.0f && + ms_nNoOfInVisibleEntities < NUMINVISIBLEENTITIES - 1) + ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent; + break; + } + } + } +} + +void +CRenderer::ScanSectorList_RequestModels(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + if(IsEntityCullZoneVisible(ent)) + if(ShouldModelBeStreamed(ent)) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + } + } +} + +// Put big buildings in front +// This seems pointless because the sector lists shouldn't have big buildings in the first place +void +CRenderer::SortBIGBuildings(void) +{ + int x, y; + for(y = 0; y < NUMSECTORS_Y; y++) + for(x = 0; x < NUMSECTORS_X; x++){ + SortBIGBuildingsForSectorList(&CWorld::GetSector(x, y)->m_lists[ENTITYLIST_BUILDINGS]); + SortBIGBuildingsForSectorList(&CWorld::GetSector(x, y)->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + } +} + +void +CRenderer::SortBIGBuildingsForSectorList(CPtrList *list) +{ + CPtrNode *node; + CEntity *ent; + + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->bIsBIGBuilding){ + list->RemoveNode(node); + list->InsertNode(node); + } + } +} + +bool +CRenderer::ShouldModelBeStreamed(CEntity *ent) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo *)CModelInfo::GetModelInfo(ent->GetModelIndex()); + float dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude(); + if(mi->m_noFade) + return dist - STREAM_DISTANCE < mi->GetLargestLodDistance(); + else + return dist - FADE_DISTANCE - STREAM_DISTANCE < mi->GetLargestLodDistance(); +} + +bool +CRenderer::IsEntityCullZoneVisible(CEntity *ent) +{ + CPed *ped; + CObject *obj; + + if(gbDisableZoneCull) return true; + +#ifndef MASTER + switch(ent->GetType()){ + case ENTITY_TYPE_BUILDING: + if(ent->bIsBIGBuilding) + TestedBigBuildings++; + else + TestedBuildings++; + break; + case ENTITY_TYPE_VEHICLE: + TestedCars++; + break; + case ENTITY_TYPE_PED: + TestedPeds++; + break; + case ENTITY_TYPE_OBJECT: + TestedObjects++; + break; + case ENTITY_TYPE_DUMMY: + TestedDummies++; + break; + } +#endif + if(ent->bZoneCulled) + return false; + + + switch(ent->GetType()){ + case ENTITY_TYPE_VEHICLE: + return IsVehicleCullZoneVisible(ent); + case ENTITY_TYPE_PED: + ped = (CPed*)ent; + if (ped->bInVehicle) { + if (ped->m_pMyVehicle) + return IsVehicleCullZoneVisible(ped->m_pMyVehicle); + else + return true; + } + return !(ped->m_pCurSurface && ped->m_pCurSurface->bZoneCulled2); + case ENTITY_TYPE_OBJECT: + obj = (CObject*)ent; + if(!obj->GetIsStatic()) + return true; + return !(obj->m_pCurSurface && obj->m_pCurSurface->bZoneCulled2); + default: break; + } + return true; +} + +bool +CRenderer::IsVehicleCullZoneVisible(CEntity *ent) +{ + CVehicle *v = (CVehicle*)ent; + switch(v->GetStatus()) { + case STATUS_SIMPLE: + case STATUS_PHYSICS: + case STATUS_ABANDONED: + case STATUS_WRECKED: + return !(v->m_pCurGroundEntity && v->m_pCurGroundEntity->bZoneCulled2); + default: break; + } + return true; +} + +void +CRenderer::RemoveVehiclePedLights(CEntity *ent, bool reset) +{ + if(ent->bRenderScorched){ + WorldReplaceScorchedLightsWithNormal(Scene.world); + return; + } + CPointLights::RemoveLightsAffectingObject(); + if(reset) + ReSetAmbientAndDirectionalColours(); +} diff --git a/src/renderer/Renderer.h b/src/renderer/Renderer.h new file mode 100644 index 0000000..0322939 --- /dev/null +++ b/src/renderer/Renderer.h @@ -0,0 +1,119 @@ +#pragma once + +class CEntity; + +#ifdef FIX_BUGS +#define LOD_DISTANCE (300.0f*TheCamera.LODDistMultiplier) +#else +#define LOD_DISTANCE 300.0f +#endif +#define FADE_DISTANCE 20.0f +#define STREAM_DISTANCE 30.0f + +#ifdef EXTRA_MODEL_FLAGS +#define BACKFACE_CULLING_ON SetCullMode(rwCULLMODECULLBACK) +#define BACKFACE_CULLING_OFF SetCullMode(rwCULLMODECULLNONE) +#else +#define BACKFACE_CULLING_ON +#define BACKFACE_CULLING_OFF +#endif + +extern bool gbShowPedRoadGroups; +extern bool gbShowCarRoadGroups; +extern bool gbShowCollisionPolys; +extern bool gbShowCollisionLines; +extern bool gbShowCullZoneDebugStuff; +extern bool gbDisableZoneCull; // not original +extern bool gbBigWhiteDebugLightSwitchedOn; + +extern bool gbDontRenderBuildings; +extern bool gbDontRenderBigBuildings; +extern bool gbDontRenderPeds; +extern bool gbDontRenderObjects; +extern bool gbDontRenderVehicles; + +class CVehicle; +class CPtrList; + +// unused +struct BlockedRange +{ + float a, b; // unknown + BlockedRange *prev, *next; +}; + +class CRenderer +{ + static int32 ms_nNoOfVisibleEntities; + static CEntity *ms_aVisibleEntityPtrs[NUMVISIBLEENTITIES]; + static int32 ms_nNoOfInVisibleEntities; + static CEntity *ms_aInVisibleEntityPtrs[NUMINVISIBLEENTITIES]; +#ifdef NEW_RENDERER + static int32 ms_nNoOfVisibleVehicles; + static CEntity *ms_aVisibleVehiclePtrs[NUMVISIBLEENTITIES]; + // for cWorldStream emulation + static int32 ms_nNoOfVisibleBuildings; + static CEntity *ms_aVisibleBuildingPtrs[NUMVISIBLEENTITIES]; +#endif + + static CVector ms_vecCameraPosition; + static CVehicle *m_pFirstPersonVehicle; + + // unused + static BlockedRange aBlockedRanges[16]; + static BlockedRange *pFullBlockedRanges; + static BlockedRange *pEmptyBlockedRanges; +public: + static float ms_lodDistScale; + static bool m_loadingPriority; + + static void Init(void); + static void Shutdown(void); + static void PreRender(void); + + static void RenderRoads(void); + static void RenderFadingInEntities(void); + static void RenderEverythingBarRoads(void); + static void RenderVehiclesButNotBoats(void); + static void RenderBoats(void); + static void RenderOneRoad(CEntity *); + static void RenderOneNonRoad(CEntity *); + static void RenderFirstPersonVehicle(void); + + static void RenderCollisionLines(void); + // unused + static void RenderBlockBuildingLines(void); + + static int32 SetupEntityVisibility(CEntity *ent); + static int32 SetupBigBuildingVisibility(CEntity *ent); + + static void ConstructRenderList(void); + static void ScanWorld(void); + static void RequestObjectsInFrustum(void); + static void ScanSectorPoly(RwV2d *poly, int32 numVertices, void (*scanfunc)(CPtrList *)); + static void ScanBigBuildingList(CPtrList &list); + static void ScanSectorList(CPtrList *lists); + static void ScanSectorList_Priority(CPtrList *lists); + static void ScanSectorList_Subway(CPtrList *lists); + static void ScanSectorList_RequestModels(CPtrList *lists); + + static void SortBIGBuildings(void); + static void SortBIGBuildingsForSectorList(CPtrList *list); + + static bool ShouldModelBeStreamed(CEntity *ent); + static bool IsEntityCullZoneVisible(CEntity *ent); + static bool IsVehicleCullZoneVisible(CEntity *ent); + + static void RemoveVehiclePedLights(CEntity *ent, bool reset); + + +#ifdef NEW_RENDERER + static void ClearForFrame(void); + static void RenderPeds(void); + static void RenderVehicles(void); // also renders peds in LCS + static void RenderOneBuilding(CEntity *ent, float camdist = 0.0f); + static void RenderWorld(int pass); // like cWorldStream::Render(int) + static void RenderWater(void); // keep-out polys and water +#endif + static void InsertEntityIntoList(CEntity *ent); +}; diff --git a/src/renderer/Rubbish.cpp b/src/renderer/Rubbish.cpp new file mode 100644 index 0000000..8da6b02 --- /dev/null +++ b/src/renderer/Rubbish.cpp @@ -0,0 +1,436 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "Timer.h" +#include "Weather.h" +#include "Camera.h" +#include "World.h" +#include "Vehicle.h" +#include "ZoneCull.h" +#include "TxdStore.h" +#include "RenderBuffer.h" +#include "Rubbish.h" + +#define RUBBISH_MAX_DIST (18.0f) +#define RUBBISH_FADE_DIST (16.5f) + +RwTexture *gpRubbishTexture[4]; +RwImVertexIndex RubbishIndexList[6]; +RwImVertexIndex RubbishIndexList2[6]; // unused +RwIm3DVertex RubbishVertices[4]; +bool CRubbish::bRubbishInvisible; +int CRubbish::RubbishVisibility; +COneSheet CRubbish::aSheets[NUM_RUBBISH_SHEETS]; +COneSheet CRubbish::StartEmptyList; +COneSheet CRubbish::EndEmptyList; +COneSheet CRubbish::StartStaticsList; +COneSheet CRubbish::EndStaticsList; +COneSheet CRubbish::StartMoversList; +COneSheet CRubbish::EndMoversList; + + +void +COneSheet::AddToList(COneSheet *list) +{ + this->m_next = list->m_next; + this->m_prev = list; + list->m_next = this; + this->m_next->m_prev = this; +} + +void +COneSheet::RemoveFromList(void) +{ + m_next->m_prev = m_prev; + m_prev->m_next = m_next; +} + + +void +CRubbish::Render(void) +{ + int type; + + PUSH_RENDERGROUP("CRubbish::Render"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + + for(type = 0; type < 4; type++){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpRubbishTexture[type])); + + TempBufferIndicesStored = 0; + TempBufferVerticesStored = 0; + + COneSheet *sheet; + for(sheet = &aSheets[type*NUM_RUBBISH_SHEETS / 4]; + sheet < &aSheets[(type+1)*NUM_RUBBISH_SHEETS / 4]; + sheet++){ + if(sheet->m_state == 0) + continue; + + uint32 alpha = 128; + CVector pos; + if(sheet->m_state == 1){ + pos = sheet->m_basePos; + if(!sheet->m_isVisible) + alpha = 0; + }else{ + pos = sheet->m_animatedPos; + // Not fully visible during animation, calculate current alpha + if(!sheet->m_isVisible || !sheet->m_targetIsVisible){ + float t = (float)(CTimer::GetTimeInMilliseconds() - sheet->m_moveStart)/sheet->m_moveDuration; + float f1 = sheet->m_isVisible ? 1.0f-t : 0.0f; + float f2 = sheet->m_targetIsVisible ? t : 0.0f; + alpha = 128 * (f1+f2); + } + } + + float camDist = (pos - TheCamera.GetPosition()).Magnitude2D(); + if(camDist < RUBBISH_MAX_DIST){ + if(camDist >= RUBBISH_FADE_DIST) + alpha -= alpha*(camDist-RUBBISH_FADE_DIST)/(RUBBISH_MAX_DIST-RUBBISH_FADE_DIST); + alpha = (RubbishVisibility*alpha)/256; + + float vx = Sin(sheet->m_angle) * 0.4f; + float vy = Cos(sheet->m_angle) * 0.4f; + + int v = TempBufferVerticesStored; + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+0], pos.x + vx, pos.y + vy, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+0], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+1], pos.x - vy, pos.y + vx, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+1], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+2], pos.x + vy, pos.y - vx, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+2], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+3], pos.x - vx, pos.y - vy, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+3], 255, 255, 255, alpha); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+0], 0.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+0], 0.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+1], 1.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+1], 0.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+2], 0.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+2], 1.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+3], 1.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+3], 1.0f); + + int i = TempBufferIndicesStored; + TempBufferRenderIndexList[i+0] = RubbishIndexList[0] + TempBufferVerticesStored; + TempBufferRenderIndexList[i+1] = RubbishIndexList[1] + TempBufferVerticesStored; + TempBufferRenderIndexList[i+2] = RubbishIndexList[2] + TempBufferVerticesStored; + TempBufferRenderIndexList[i+3] = RubbishIndexList[3] + TempBufferVerticesStored; + TempBufferRenderIndexList[i+4] = RubbishIndexList[4] + TempBufferVerticesStored; + TempBufferRenderIndexList[i+5] = RubbishIndexList[5] + TempBufferVerticesStored; + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; + } + } + + if(TempBufferIndicesStored != 0){ + LittleTest(); + if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + } + } + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + POP_RENDERGROUP(); +} + +void +CRubbish::StirUp(CVehicle *veh) +{ + if((CTimer::GetFrameCounter() ^ (veh->m_randomSeed&3)) == 0) + return; + + if(Abs(veh->GetPosition().x - TheCamera.GetPosition().x) < 20.0f && + Abs(veh->GetPosition().y - TheCamera.GetPosition().y) < 20.0f) + if(Abs(veh->GetMoveSpeed().x) > 0.05f || Abs(veh->GetMoveSpeed().y) > 0.05f){ + float speed = veh->GetMoveSpeed().Magnitude2D(); + if(speed > 0.05f){ + bool movingForward = DotProduct2D(veh->GetMoveSpeed(), veh->GetForward()) > 0.0f; + COneSheet *sheet = StartStaticsList.m_next; + CVector2D size = veh->GetColModel()->boundingBox.max; + + // Check all static sheets + while(sheet != &EndStaticsList){ + COneSheet *next = sheet->m_next; + CVector2D carToSheet = sheet->m_basePos - veh->GetPosition(); + float distFwd = DotProduct2D(carToSheet, veh->GetForward()); + + // sheet has to be a bit behind car + if(movingForward && distFwd < -0.5f*size.y && distFwd > -1.5f*size.y || + !movingForward && distFwd > 0.5f*size.y && distFwd < 1.5f*size.y){ + float distSide = Abs(DotProduct2D(carToSheet, veh->GetRight())); + if(distSide < 1.5*size.x){ + // Check with higher speed for sheet directly behind car + float speedToCheck = distSide < size.x ? speed : speed*0.5f; + if(speedToCheck > 0.05f){ + sheet->m_state = 2; + if(speedToCheck > 0.15f) + sheet->m_animationType = 2; + else + sheet->m_animationType = 1; + sheet->m_moveDuration = 2000; + sheet->m_xDist = veh->GetMoveSpeed().x; + sheet->m_yDist = veh->GetMoveSpeed().y; + float dist = Sqrt(SQR(sheet->m_xDist)+SQR(sheet->m_yDist)); + sheet->m_xDist *= 25.0f*speed/dist; + sheet->m_yDist *= 25.0f*speed/dist; + sheet->m_animHeight = 3.0f*speed; + sheet->m_moveStart = CTimer::GetTimeInMilliseconds(); + float tx = sheet->m_basePos.x + sheet->m_xDist; + float ty = sheet->m_basePos.y + sheet->m_yDist; + float tz = sheet->m_basePos.z + 3.0f; + sheet->m_targetZ = CWorld::FindGroundZFor3DCoord(tx, ty, tz, nil) + 0.1f; + sheet->RemoveFromList(); + sheet->AddToList(&StartMoversList); + } + } + } + + sheet = next; + } + } + } +} + +static float aAnimations[3][34] = { + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, + + // Normal move + { 0.0f, 0.05f, 0.12f, 0.25f, 0.42f, 0.57f, 0.68f, 0.8f, 0.86f, 0.9f, 0.93f, 0.95f, 0.96f, 0.97f, 0.98f, 0.99f, 1.0f, // XY movemnt + 0.15f, 0.35f, 0.6f, 0.9f, 1.2f, 1.25f, 1.3f, 1.2f, 1.1f, 0.95f, 0.8f, 0.6f, 0.45f, 0.3f, 0.2f, 0.1f, 0 }, // Z movement + + // Stirred up by fast vehicle + { 0.0f, 0.05f, 0.12f, 0.25f, 0.42f, 0.57f, 0.68f, 0.8f, 0.95f, 1.1f, 1.15f, 1.18f, 1.15f, 1.1f, 1.05f, 1.03f, 1.0f, + 0.15f, 0.35f, 0.6f, 0.9f, 1.2f, 1.25f, 1.3f, 1.2f, 1.1f, 0.95f, 0.8f, 0.6f, 0.45f, 0.3f, 0.2f, 0.1f, 0 } +}; + +void +CRubbish::Update(void) +{ + bool foundGround; + + // FRAMETIME + if(bRubbishInvisible) + RubbishVisibility = Max(RubbishVisibility-5, 0); + else + RubbishVisibility = Min(RubbishVisibility+5, 255); + + // Spawn a new sheet + COneSheet *sheet = StartEmptyList.m_next; + if(sheet != &EndEmptyList){ + float spawnDist; + float spawnAngle; + + spawnDist = (CGeneral::GetRandomNumber()&0xFF)/256.0f + RUBBISH_MAX_DIST; + uint8 r = CGeneral::GetRandomNumber(); + if(r&1) + spawnAngle = (CGeneral::GetRandomNumber()&0xFF)/256.0f * 6.28f; + else + spawnAngle = (r-128)/160.0f + TheCamera.Orientation; + sheet->m_basePos.x = TheCamera.GetPosition().x + spawnDist*Sin(spawnAngle); + sheet->m_basePos.y = TheCamera.GetPosition().y + spawnDist*Cos(spawnAngle); + sheet->m_basePos.z = CWorld::FindGroundZFor3DCoord(sheet->m_basePos.x, sheet->m_basePos.y, TheCamera.GetPosition().z, &foundGround) + 0.1f; + if(foundGround){ + // Found ground, so add to statics list + sheet->m_angle = (CGeneral::GetRandomNumber()&0xFF)/256.0f * 6.28f; + sheet->m_state = 1; + if(CCullZones::FindAttributesForCoors(sheet->m_basePos, nil) & ATTRZONE_NORAIN) + sheet->m_isVisible = false; + else + sheet->m_isVisible = true; + sheet->RemoveFromList(); + sheet->AddToList(&StartStaticsList); + } + } + + // Process animation + sheet = StartMoversList.m_next; + while(sheet != &EndMoversList){ + uint32 currentTime = CTimer::GetTimeInMilliseconds() - sheet->m_moveStart; + if(currentTime < sheet->m_moveDuration){ + // Animation + int step = 16 * currentTime / sheet->m_moveDuration; // 16 steps in animation + int stepTime = sheet->m_moveDuration/16; // time in each step + float s = (float)(currentTime - stepTime*step) / stepTime; // position on step + float t = (float)currentTime / sheet->m_moveDuration; // position on total animation + // factors for xy and z-movment + float fxy = aAnimations[sheet->m_animationType][step]*(1.0f-s) + aAnimations[sheet->m_animationType][step+1]*s; + float fz = aAnimations[sheet->m_animationType][step+17]*(1.0f-s) + aAnimations[sheet->m_animationType][step+1+17]*s; + sheet->m_animatedPos.x = sheet->m_basePos.x + fxy*sheet->m_xDist; + sheet->m_animatedPos.y = sheet->m_basePos.y + fxy*sheet->m_yDist; + sheet->m_animatedPos.z = (1.0f-t)*sheet->m_basePos.z + t*sheet->m_targetZ + fz*sheet->m_animHeight; + sheet->m_angle += CTimer::GetTimeStep()*0.04f; + if(sheet->m_angle > 6.28f) + sheet->m_angle -= 6.28f; + sheet = sheet->m_next; + }else{ + // End of animation, back into statics list + sheet->m_basePos.x += sheet->m_xDist; + sheet->m_basePos.y += sheet->m_yDist; + sheet->m_basePos.z = sheet->m_targetZ; + sheet->m_state = 1; + sheet->m_isVisible = sheet->m_targetIsVisible; + + COneSheet *next = sheet->m_next; + sheet->RemoveFromList(); + sheet->AddToList(&StartStaticsList); + sheet = next; + } + } + + // Stir up a sheet by wind + // FRAMETIME + int freq; + if(CWeather::Wind < 0.1f) + freq = 31; + else if(CWeather::Wind < 0.4f) + freq = 7; + else if(CWeather::Wind < 0.7f) + freq = 1; + else + freq = 0; + if((CTimer::GetFrameCounter() & freq) == 0){ + // Pick a random sheet and set animation state if static + int i = CGeneral::GetRandomNumber() % NUM_RUBBISH_SHEETS; + if(aSheets[i].m_state == 1){ + aSheets[i].m_moveStart = CTimer::GetTimeInMilliseconds(); + aSheets[i].m_moveDuration = CWeather::Wind*1500.0f + 1000.0f; + aSheets[i].m_animHeight = 0.2f; + aSheets[i].m_xDist = 3.0f*CWeather::Wind; + aSheets[i].m_yDist = 3.0f*CWeather::Wind; + // Check if target position is ok + float tx = aSheets[i].m_basePos.x + aSheets[i].m_xDist; + float ty = aSheets[i].m_basePos.y + aSheets[i].m_yDist; + float tz = aSheets[i].m_basePos.z + 3.0f; + aSheets[i].m_targetZ = CWorld::FindGroundZFor3DCoord(tx, ty, tz, &foundGround) + 0.1f; + if(CCullZones::FindAttributesForCoors(CVector(tx, ty, aSheets[i].m_targetZ), nil) & ATTRZONE_NORAIN) + aSheets[i].m_targetIsVisible = false; + else + aSheets[i].m_targetIsVisible = true; + if(foundGround){ + // start animation + aSheets[i].m_state = 2; + aSheets[i].m_animationType = 1; + aSheets[i].RemoveFromList(); + aSheets[i].AddToList(&StartMoversList); + } + } + } + + // Remove sheets that are too far away + int i = (CTimer::GetFrameCounter()%(NUM_RUBBISH_SHEETS/4))*4; + int last = ((CTimer::GetFrameCounter()%(NUM_RUBBISH_SHEETS/4)) + 1)*4; + for(; i < last; i++){ + if(aSheets[i].m_state == 1 && + (aSheets[i].m_basePos - TheCamera.GetPosition()).MagnitudeSqr2D() > SQR(RUBBISH_MAX_DIST+1.0f)){ + aSheets[i].m_state = 0; + aSheets[i].RemoveFromList(); + aSheets[i].AddToList(&StartEmptyList); + } + } +} + +void +CRubbish::SetVisibility(bool visible) +{ + bRubbishInvisible = !visible; +} + +void +CRubbish::Init(void) +{ + int i; + for(i = 0; i < NUM_RUBBISH_SHEETS; i++){ + aSheets[i].m_state = 0; + if(i < NUM_RUBBISH_SHEETS-1) + aSheets[i].m_next = &aSheets[i+1]; + else + aSheets[i].m_next = &EndEmptyList; + if(i > 0) + aSheets[i].m_prev = &aSheets[i-1]; + else + aSheets[i].m_prev = &StartEmptyList; + } + + StartEmptyList.m_next = &aSheets[0]; + StartEmptyList.m_prev = nil; + EndEmptyList.m_next = nil; + EndEmptyList.m_prev = &aSheets[NUM_RUBBISH_SHEETS-1]; + + StartStaticsList.m_next = &EndStaticsList; + StartStaticsList.m_prev = nil; + EndStaticsList.m_next = nil; + EndStaticsList.m_prev = &StartStaticsList; + + StartMoversList.m_next = &EndMoversList; + StartMoversList.m_prev = nil; + EndMoversList.m_next = nil; + EndMoversList.m_prev = &StartMoversList; + + // unused + RwIm3DVertexSetU(&RubbishVertices[0], 0.0f); + RwIm3DVertexSetV(&RubbishVertices[0], 0.0f); + RwIm3DVertexSetU(&RubbishVertices[1], 1.0f); + RwIm3DVertexSetV(&RubbishVertices[1], 0.0f); + RwIm3DVertexSetU(&RubbishVertices[2], 0.0f); + RwIm3DVertexSetV(&RubbishVertices[2], 1.0f); + RwIm3DVertexSetU(&RubbishVertices[3], 1.0f); + RwIm3DVertexSetV(&RubbishVertices[3], 1.0f); + + // unused + RubbishIndexList2[0] = 0; + RubbishIndexList2[1] = 2; + RubbishIndexList2[2] = 1; + RubbishIndexList2[3] = 1; + RubbishIndexList2[4] = 2; + RubbishIndexList2[5] = 3; + + RubbishIndexList[0] = 0; + RubbishIndexList[1] = 1; + RubbishIndexList[2] = 2; + RubbishIndexList[3] = 1; + RubbishIndexList[4] = 3; + RubbishIndexList[5] = 2; + + CTxdStore::PushCurrentTxd(); + int slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + gpRubbishTexture[0] = RwTextureRead("gameleaf01_64", nil); + gpRubbishTexture[1] = RwTextureRead("gameleaf02_64", nil); + gpRubbishTexture[2] = RwTextureRead("newspaper01_64", nil); + gpRubbishTexture[3] = RwTextureRead("newspaper02_64", nil); + CTxdStore::PopCurrentTxd(); + RubbishVisibility = 255; + bRubbishInvisible = false; +} + +void +CRubbish::Shutdown(void) +{ + RwTextureDestroy(gpRubbishTexture[0]); +#if GTA_VERSION >= GTA3_PC_11 + gpRubbishTexture[0] = nil; +#endif + RwTextureDestroy(gpRubbishTexture[1]); +#if GTA_VERSION >= GTA3_PC_11 + gpRubbishTexture[1] = nil; +#endif + RwTextureDestroy(gpRubbishTexture[2]); +#if GTA_VERSION >= GTA3_PC_11 + gpRubbishTexture[2] = nil; +#endif + RwTextureDestroy(gpRubbishTexture[3]); +#if GTA_VERSION >= GTA3_PC_11 + gpRubbishTexture[3] = nil; +#endif +} diff --git a/src/renderer/Rubbish.h b/src/renderer/Rubbish.h new file mode 100644 index 0000000..37f895f --- /dev/null +++ b/src/renderer/Rubbish.h @@ -0,0 +1,55 @@ +#pragma once + +class CVehicle; + +enum { + // NB: not all values are allowed, check the code +#ifdef SQUEEZE_PERFORMANCE + NUM_RUBBISH_SHEETS = 32 +#else + NUM_RUBBISH_SHEETS = 64 +#endif +}; + +class COneSheet +{ +public: + CVector m_basePos; + CVector m_animatedPos; + float m_targetZ; + int8 m_state; + int8 m_animationType; + uint32 m_moveStart; + uint32 m_moveDuration; + float m_animHeight; + float m_xDist; + float m_yDist; + float m_angle; + bool m_isVisible; + bool m_targetIsVisible; + COneSheet *m_next; + COneSheet *m_prev; + + void AddToList(COneSheet *list); + void RemoveFromList(void); +}; + +class CRubbish +{ + static bool bRubbishInvisible; + static int RubbishVisibility; + static COneSheet aSheets[NUM_RUBBISH_SHEETS]; + static COneSheet StartEmptyList; + static COneSheet EndEmptyList; + static COneSheet StartStaticsList; + static COneSheet EndStaticsList; + static COneSheet StartMoversList; + static COneSheet EndMoversList; +public: + static void Render(void); + static void StirUp(CVehicle *veh); // CAutomobile on PS2 + static void Update(void); + static void SetVisibility(bool visible); + static void Init(void); + static void Shutdown(void); +}; diff --git a/src/renderer/Shadows.cpp b/src/renderer/Shadows.cpp new file mode 100644 index 0000000..3884d3b --- /dev/null +++ b/src/renderer/Shadows.cpp @@ -0,0 +1,1785 @@ +#include "common.h" + +#include "main.h" +#include "TxdStore.h" +#include "Timer.h" +#include "Camera.h" +#include "Timecycle.h" +#include "CutsceneMgr.h" +#include "Automobile.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "World.h" +#include "Weather.h" +#include "ModelIndices.h" +#include "RenderBuffer.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif +#include "PointLights.h" +#include "SpecialFX.h" +#include "Shadows.h" + +#ifdef DEBUGMENU +//SETTWEAKPATH("Shadows"); +//TWEAKBOOL(gbPrintShite); +#endif + +RwImVertexIndex ShadowIndexList[24]; + +RwTexture *gpShadowCarTex; +RwTexture *gpShadowPedTex; +RwTexture *gpShadowHeliTex; +RwTexture *gpShadowExplosionTex; +RwTexture *gpShadowHeadLightsTex; +RwTexture *gpOutline1Tex; +RwTexture *gpOutline2Tex; +RwTexture *gpOutline3Tex; +RwTexture *gpBloodPoolTex; +RwTexture *gpReflectionTex; +RwTexture *gpGoalMarkerTex; +RwTexture *gpWalkDontTex; +RwTexture *gpCrackedGlassTex; +RwTexture *gpPostShadowTex; +RwTexture *gpGoalTex; + +int16 CShadows::ShadowsStoredToBeRendered; +CStoredShadow CShadows::asShadowsStored [MAX_STOREDSHADOWS]; +CPolyBunch CShadows::aPolyBunches [MAX_POLYBUNCHES]; +CStaticShadow CShadows::aStaticShadows [MAX_STATICSHADOWS]; +CPolyBunch *CShadows::pEmptyBunchList; +CPermanentShadow CShadows::aPermanentShadows[MAX_PERMAMENTSHADOWS]; + + +void +CShadows::Init(void) +{ + CTxdStore::PushCurrentTxd(); + + int32 slut = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slut); + + gpShadowCarTex = RwTextureRead("shad_car", NULL); + gpShadowPedTex = RwTextureRead("shad_ped", NULL); + gpShadowHeliTex = RwTextureRead("shad_heli", NULL); + gpShadowExplosionTex = RwTextureRead("shad_exp", NULL); + gpShadowHeadLightsTex = RwTextureRead("headlight", NULL); + gpOutline1Tex = RwTextureRead("outline_64", NULL); + gpOutline2Tex = RwTextureRead("outline2_64", NULL); + gpOutline3Tex = RwTextureRead("outline3_64", NULL); + gpBloodPoolTex = RwTextureRead("bloodpool_64", NULL); + gpReflectionTex = RwTextureRead("reflection01", NULL); + gpGoalMarkerTex = RwTextureRead("goal", NULL); + gpWalkDontTex = RwTextureRead("walk_dont", NULL); + gpCrackedGlassTex = RwTextureRead("wincrack_32", NULL); + gpPostShadowTex = RwTextureRead("lamp_shad_64", NULL); + + CTxdStore::PopCurrentTxd(); + + ASSERT(gpShadowCarTex != NULL); + ASSERT(gpShadowPedTex != NULL); + ASSERT(gpShadowHeliTex != NULL); + ASSERT(gpShadowExplosionTex != NULL); + ASSERT(gpShadowHeadLightsTex != NULL); + ASSERT(gpOutline1Tex != NULL); + ASSERT(gpOutline2Tex != NULL); + ASSERT(gpOutline3Tex != NULL); + ASSERT(gpBloodPoolTex != NULL); + ASSERT(gpReflectionTex != NULL); + ASSERT(gpGoalMarkerTex != NULL); + ASSERT(gpWalkDontTex != NULL); + ASSERT(gpCrackedGlassTex != NULL); + ASSERT(gpPostShadowTex != NULL); + + + ShadowIndexList[0] = 0; + ShadowIndexList[1] = 2; + ShadowIndexList[2] = 1; + + ShadowIndexList[3] = 0; + ShadowIndexList[4] = 3; + ShadowIndexList[5] = 2; + + ShadowIndexList[6] = 0; + ShadowIndexList[7] = 4; + ShadowIndexList[8] = 3; + + ShadowIndexList[9] = 0; + ShadowIndexList[10] = 5; + ShadowIndexList[11] = 4; + + ShadowIndexList[12] = 0; + ShadowIndexList[13] = 6; + ShadowIndexList[14] = 5; + + ShadowIndexList[15] = 0; + ShadowIndexList[16] = 7; + ShadowIndexList[17] = 6; + + ShadowIndexList[18] = 0; + ShadowIndexList[19] = 8; + ShadowIndexList[20] = 7; + + ShadowIndexList[21] = 0; + ShadowIndexList[22] = 9; + ShadowIndexList[23] = 8; + + + for ( int32 i = 0; i < MAX_STATICSHADOWS; i++ ) + { + aStaticShadows[i].m_nId = 0; + aStaticShadows[i].m_pPolyBunch = NULL; + } + + pEmptyBunchList = &aPolyBunches[0]; + + for ( int32 i = 0; i < MAX_POLYBUNCHES; i++ ) + { + if ( i == MAX_POLYBUNCHES - 1 ) + aPolyBunches[i].m_pNext = NULL; + else + aPolyBunches[i].m_pNext = &aPolyBunches[i + 1]; + } + + for ( int32 i = 0; i < MAX_PERMAMENTSHADOWS; i++ ) + { + aPermanentShadows[i].m_nType = SHADOWTYPE_NONE; + } +} + +void +CShadows::Shutdown(void) +{ + ASSERT(gpShadowCarTex != NULL); + ASSERT(gpShadowPedTex != NULL); + ASSERT(gpShadowHeliTex != NULL); + ASSERT(gpShadowExplosionTex != NULL); + ASSERT(gpShadowHeadLightsTex != NULL); + ASSERT(gpOutline1Tex != NULL); + ASSERT(gpOutline2Tex != NULL); + ASSERT(gpOutline3Tex != NULL); + ASSERT(gpBloodPoolTex != NULL); + ASSERT(gpReflectionTex != NULL); + ASSERT(gpGoalMarkerTex != NULL); + ASSERT(gpWalkDontTex != NULL); + ASSERT(gpCrackedGlassTex != NULL); + ASSERT(gpPostShadowTex != NULL); + + RwTextureDestroy(gpShadowCarTex); + RwTextureDestroy(gpShadowPedTex); + RwTextureDestroy(gpShadowHeliTex); + RwTextureDestroy(gpShadowExplosionTex); + RwTextureDestroy(gpShadowHeadLightsTex); + RwTextureDestroy(gpOutline1Tex); + RwTextureDestroy(gpOutline2Tex); + RwTextureDestroy(gpOutline3Tex); + RwTextureDestroy(gpBloodPoolTex); + RwTextureDestroy(gpReflectionTex); + RwTextureDestroy(gpGoalMarkerTex); + RwTextureDestroy(gpWalkDontTex); + RwTextureDestroy(gpCrackedGlassTex); + RwTextureDestroy(gpPostShadowTex); +} + +void +CShadows::AddPermanentShadow(uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, uint32 nTime, float fScale) +{ + ASSERT(pTexture != NULL); + ASSERT(pPosn != NULL); + + + // find free slot + int32 nSlot = 0; + while ( nSlot < MAX_PERMAMENTSHADOWS && aPermanentShadows[nSlot].m_nType != SHADOWTYPE_NONE ) + nSlot++; + + if ( nSlot < MAX_PERMAMENTSHADOWS ) + { + aPermanentShadows[nSlot].m_nType = ShadowType; + aPermanentShadows[nSlot].m_pTexture = pTexture; + aPermanentShadows[nSlot].m_vecPos = *pPosn; + aPermanentShadows[nSlot].m_vecFront.x = fFrontX; + aPermanentShadows[nSlot].m_vecFront.y = fFrontY; + aPermanentShadows[nSlot].m_vecSide.x = fSideX; + aPermanentShadows[nSlot].m_vecSide.y = fSideY; + aPermanentShadows[nSlot].m_nIntensity = nIntensity; + aPermanentShadows[nSlot].m_nRed = nRed; + aPermanentShadows[nSlot].m_nGreen = nGreen; + aPermanentShadows[nSlot].m_nBlue = nBlue; + aPermanentShadows[nSlot].m_fZDistance = fZDistance; + aPermanentShadows[nSlot].m_nLifeTime = nTime; + aPermanentShadows[nSlot].m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + } +} + +void +CShadows::StoreStaticShadow(uint32 nID, uint8 ShadowType, RwTexture *pTexture, Const CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, float fScale, float fDrawDistance, bool bTempShadow, float fUpDistance) +{ + ASSERT(pPosn != NULL); + + float fDistToCamSqr = (*pPosn - TheCamera.GetPosition()).MagnitudeSqr2D(); + + if ( SQR(fDrawDistance) > fDistToCamSqr) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + if ( fDistToCam >= (fDrawDistance*(1.0f-(1.0f/4.0f))) ) + { + //fDistToCam == 0 -> 4 + //fDistToCam == fDrawDistance -> 0 + float fMult = 1.0f - (4.0f / fDrawDistance) * (fDistToCam - (fDrawDistance*(1.0f-(1.0f/4.0f)))); + + nIntensity = (int32)(nIntensity * fMult); + nRed = (int32)(nRed * fMult); + nGreen = (int32)(nGreen * fMult); + nBlue = (int32)(nBlue * fMult); + } + + int32 nSlot; + + nSlot = 0; + while ( nSlot < MAX_STATICSHADOWS && !(nID == aStaticShadows[nSlot].m_nId && aStaticShadows[nSlot].m_pPolyBunch != NULL) ) + nSlot++; + + if ( nSlot < MAX_STATICSHADOWS ) + { + if ( Abs(pPosn->x - aStaticShadows[nSlot].m_vecPosn.x) < fUpDistance + && Abs(pPosn->y - aStaticShadows[nSlot].m_vecPosn.y) < fUpDistance ) + { + aStaticShadows[nSlot].m_bJustCreated = true; + aStaticShadows[nSlot].m_nType = ShadowType; + aStaticShadows[nSlot].m_pTexture = pTexture; + aStaticShadows[nSlot].m_nIntensity = nIntensity; + aStaticShadows[nSlot].m_nRed = nRed; + aStaticShadows[nSlot].m_nGreen = nGreen; + aStaticShadows[nSlot].m_nBlue = nBlue; + aStaticShadows[nSlot].m_fZDistance = fZDistance; + aStaticShadows[nSlot].m_fScale = fScale; + aStaticShadows[nSlot].m_bTemp = bTempShadow; + aStaticShadows[nSlot].m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + } + else if ( Abs(pPosn->x - aStaticShadows[nSlot].m_vecPosn.x) < 0.05f + && Abs(pPosn->y - aStaticShadows[nSlot].m_vecPosn.y) < 0.05f + && Abs(pPosn->z - aStaticShadows[nSlot].m_vecPosn.z) < 2.0f + + && fFrontX == aStaticShadows[nSlot].m_vecFront.x + && fFrontY == aStaticShadows[nSlot].m_vecFront.y + && fSideX == aStaticShadows[nSlot].m_vecSide.x + && fSideY == aStaticShadows[nSlot].m_vecSide.y ) + { + aStaticShadows[nSlot].m_bJustCreated = true; + aStaticShadows[nSlot].m_nType = ShadowType; + aStaticShadows[nSlot].m_pTexture = pTexture; + aStaticShadows[nSlot].m_nIntensity = nIntensity; + aStaticShadows[nSlot].m_nRed = nRed; + aStaticShadows[nSlot].m_nGreen = nGreen; + aStaticShadows[nSlot].m_nBlue = nBlue; + aStaticShadows[nSlot].m_fZDistance = fZDistance; + aStaticShadows[nSlot].m_fScale = fScale; + aStaticShadows[nSlot].m_bTemp = bTempShadow; + aStaticShadows[nSlot].m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + } + else + { + aStaticShadows[nSlot].Free(); + + aStaticShadows[nSlot].m_nId = nID; + aStaticShadows[nSlot].m_nType = ShadowType; + aStaticShadows[nSlot].m_pTexture = pTexture; + aStaticShadows[nSlot].m_nIntensity = nIntensity; + aStaticShadows[nSlot].m_nRed = nRed; + aStaticShadows[nSlot].m_nGreen = nGreen; + aStaticShadows[nSlot].m_nBlue = nBlue; + aStaticShadows[nSlot].m_fZDistance = fZDistance; + aStaticShadows[nSlot].m_fScale = fScale; + aStaticShadows[nSlot].m_vecPosn = *pPosn; + aStaticShadows[nSlot].m_vecFront.x = fFrontX; + aStaticShadows[nSlot].m_vecFront.y = fFrontY; + aStaticShadows[nSlot].m_vecSide.x = fSideX; + aStaticShadows[nSlot].m_vecSide.y = fSideY; + aStaticShadows[nSlot].m_bJustCreated = true; + aStaticShadows[nSlot].m_bTemp = bTempShadow; + aStaticShadows[nSlot].m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + + GeneratePolysForStaticShadow(nSlot); + } + } + else + { + nSlot = 0; + while ( nSlot < MAX_STATICSHADOWS && aStaticShadows[nSlot].m_pPolyBunch != NULL ) + nSlot++; + + if ( nSlot != MAX_STATICSHADOWS ) + { + aStaticShadows[nSlot].m_nId = nID; + aStaticShadows[nSlot].m_nType = ShadowType; + aStaticShadows[nSlot].m_pTexture = pTexture; + aStaticShadows[nSlot].m_nIntensity = nIntensity; + aStaticShadows[nSlot].m_nRed = nRed; + aStaticShadows[nSlot].m_nGreen = nGreen; + aStaticShadows[nSlot].m_nBlue = nBlue; + aStaticShadows[nSlot].m_fZDistance = fZDistance; + aStaticShadows[nSlot].m_fScale = fScale; + aStaticShadows[nSlot].m_vecPosn = *pPosn; + aStaticShadows[nSlot].m_vecFront.x = fFrontX; + aStaticShadows[nSlot].m_vecFront.y = fFrontY; + aStaticShadows[nSlot].m_vecSide.x = fSideX; + aStaticShadows[nSlot].m_vecSide.y = fSideY; + aStaticShadows[nSlot].m_bJustCreated = true; + aStaticShadows[nSlot].m_bTemp = bTempShadow; + aStaticShadows[nSlot].m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + + GeneratePolysForStaticShadow(nSlot); + } + } + } +} + +void +CShadows::StoreShadowToBeRendered(uint8 ShadowTexture, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue) +{ + ASSERT(pPosn != NULL); + + switch ( ShadowTexture ) + { + case SHADOWTEX_NONE: + { + break; + } + + case SHADOWTEX_CAR: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowCarTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + + case SHADOWTEX_PED: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowPedTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + + case SHADOWTEX_EXPLOSION: + { + StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + + case SHADOWTEX_HELI: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowHeliTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + + case SHADOWTEX_HEADLIGHTS: + { + StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowHeadLightsTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + + case SHADOWTEX_BLOOD: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpBloodPoolTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + } + + //ASSERT(false); +} + +void +CShadows::StoreShadowToBeRendered(uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, bool bDrawOnWater, float fScale) +{ + ASSERT(pTexture != NULL); + ASSERT(pPosn != NULL); + + if ( ShadowsStoredToBeRendered < MAX_STOREDSHADOWS ) + { + asShadowsStored[ShadowsStoredToBeRendered].m_ShadowType = ShadowType; + asShadowsStored[ShadowsStoredToBeRendered].m_pTexture = pTexture; + asShadowsStored[ShadowsStoredToBeRendered].m_vecPos = *pPosn; + asShadowsStored[ShadowsStoredToBeRendered].m_vecFront.x = fFrontX; + asShadowsStored[ShadowsStoredToBeRendered].m_vecFront.y = fFrontY; + asShadowsStored[ShadowsStoredToBeRendered].m_vecSide.x = fSideX; + asShadowsStored[ShadowsStoredToBeRendered].m_vecSide.y = fSideY; + asShadowsStored[ShadowsStoredToBeRendered].m_nIntensity = nIntensity; + asShadowsStored[ShadowsStoredToBeRendered].m_nRed = nRed; + asShadowsStored[ShadowsStoredToBeRendered].m_nGreen = nGreen; + asShadowsStored[ShadowsStoredToBeRendered].m_nBlue = nBlue; + asShadowsStored[ShadowsStoredToBeRendered].m_fZDistance = fZDistance; + asShadowsStored[ShadowsStoredToBeRendered].m_nFlags.bDrawOnWater = bDrawOnWater; + asShadowsStored[ShadowsStoredToBeRendered].m_fScale = fScale; + + ShadowsStoredToBeRendered++; + } +} + +void +CShadows::StoreShadowForCar(CAutomobile *pCar) +{ + ASSERT(pCar != NULL); + + if ( CTimeCycle::GetShadowStrength() != 0 ) + { + CVector CarPos = pCar->GetPosition(); + float fDistToCamSqr = (CarPos - TheCamera.GetPosition()).MagnitudeSqr2D(); + + if ( CCutsceneMgr::IsRunning() ) + fDistToCamSqr /= SQR(TheCamera.LODDistMultiplier) * 4.0f; + + float fDrawDistance = 18.0f; + + if ( fDistToCamSqr < SQR(fDrawDistance) ) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + //fDistToCam == 0 -> 4 + //fDistToCam == fDrawDistance -> 0 + float fMult = 1.0f - (4.0f / fDrawDistance) * (fDistToCam - (fDrawDistance*(1.0f-(1.0f/4.0f))) ); + + int32 nColorStrength; + + if ( fDistToCam >= (fDrawDistance*(1.0f-(1.0f/4.0f))) ) + nColorStrength = (int32)(CTimeCycle::GetShadowStrength() * fMult); + else + nColorStrength = CTimeCycle::GetShadowStrength(); + + float fVehicleHeight = pCar->GetColModel()->boundingBox.GetSize().y; + float fVehicleWidth = pCar->GetColModel()->boundingBox.GetSize().x; + + if ( pCar->GetModelIndex() == MI_DODO ) + { + fVehicleHeight *= 0.9f; + fVehicleWidth *= 0.4f; + } + + CarPos.x -= pCar->GetForward().x * ((fVehicleHeight / 2) - pCar->GetColModel()->boundingBox.max.y); + CarPos.y -= pCar->GetForward().y * ((fVehicleHeight / 2) - pCar->GetColModel()->boundingBox.max.y); + + if ( pCar->GetUp().z > 0.0f ) + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowCarTex, &CarPos, + pCar->GetForward().x * (fVehicleHeight / 2), + pCar->GetForward().y * (fVehicleHeight / 2), + pCar->GetRight().x * (fVehicleWidth / 2), + pCar->GetRight().y * (fVehicleWidth / 2), + nColorStrength, nColorStrength, nColorStrength, nColorStrength, + 4.5f, false, 1.0f); + } + else + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowCarTex, &CarPos, + pCar->GetForward().x * (fVehicleHeight / 2), + pCar->GetForward().y * (fVehicleHeight / 2), + -pCar->GetRight().x * (fVehicleWidth / 2), + -pCar->GetRight().y * (fVehicleWidth / 2), + nColorStrength, nColorStrength, nColorStrength, nColorStrength, + 4.5f, false, 1.0f); + } + } + } +} + +void +CShadows::StoreCarLightShadow(CAutomobile *pCar, int32 nID, RwTexture *pTexture, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fMaxViewAngle) +{ + ASSERT(pCar != NULL); + ASSERT(pPosn != NULL); + + float fDistToCamSqr = (*pPosn - TheCamera.GetPosition()).MagnitudeSqr2D(); + + bool bSpecialCam = TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN + || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED + || CCutsceneMgr::IsRunning(); + + float fDrawDistance = 27.0f; + + if ( fDistToCamSqr < SQR(fDrawDistance) || bSpecialCam ) + { + if ( bSpecialCam || DotProduct2D(CVector2D(TheCamera.CamFrontXNorm, TheCamera.CamFrontYNorm), + *pPosn - TheCamera.GetPosition() ) > -fMaxViewAngle ) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + if ( fDistToCam >= (fDrawDistance*(1.0f-(1.0f/4.0f))) && !bSpecialCam ) // BUG? must be 3.0? + { + //fDistToCam == 0 -> 3 + //fDistToCam == fDrawDistance -> 0 + float fMult = 1.0f - (3.0f / fDrawDistance) * (fDistToCam - (fDrawDistance*(1.0f-(1.0f/3.0f))) ); + + nRed = (int32)(nRed * fMult); + nGreen = (int32)(nGreen * fMult); + nBlue = (int32)(nBlue * fMult); + } + + StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, pTexture, pPosn, + fFrontX, fFrontY, + fSideX, fSideY, + 128, nRed, nGreen, nBlue, + 6.0f, false, 1.0f); + + } + } +} + +void +CShadows::StoreShadowForPed(CPed *pPed, float fDisplacementX, float fDisplacementY, + float fFrontX, float fFrontY, float fSideX, float fSideY) +{ + ASSERT(pPed != NULL); + + if ( pPed->bIsVisible ) + { + if ( !(pPed->bInVehicle && pPed->m_nPedState != PED_DRAG_FROM_CAR && pPed->m_nPedState != PED_EXIT_CAR) ) + { + if ( CTimeCycle::GetShadowStrength() != 0 ) + StoreShadowForPedObject(pPed, + fDisplacementX, fDisplacementY, + fFrontX, fFrontY, + fSideX, fSideY); + } + } +} + +void +CShadows::StoreShadowForPedObject(CEntity *pPedObject, float fDisplacementX, float fDisplacementY, + float fFrontX, float fFrontY, float fSideX, float fSideY) +{ + ASSERT(pPedObject != NULL); + + CVector PedPos = pPedObject->GetPosition(); + + float fDistToCamSqr = (PedPos - TheCamera.GetPosition()).MagnitudeSqr2D(); + + float fDrawDistance = 26.0f; + + if ( fDistToCamSqr < SQR(fDrawDistance*0.5f)/*?*/ ) + { + if ( pPedObject == FindPlayerPed() || TheCamera.IsSphereVisible(PedPos, 2.0f) != false ) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + //fDistToCam == 0 -> 2 + //fDistToCam == fDrawDistance -> -2 + float fMult = 1.0f - (4.0f / fDrawDistance) * (fDistToCam - (fDrawDistance*(1.0f/4.0f))); // BUG ? negative + int32 nColorStrength; + + if ( fDistToCam >= (fDrawDistance*(1.0f/4.0f)) ) // BUG ? negative + nColorStrength = (int32)(CTimeCycle::GetShadowStrength() * fMult); + else + nColorStrength = CTimeCycle::GetShadowStrength(); + + PedPos.x += fDisplacementX; + PedPos.y += fDisplacementY; + + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowPedTex, &PedPos, + fFrontX, fFrontY, + fSideX, fSideY, + nColorStrength, nColorStrength, nColorStrength, nColorStrength, + 4.0f, false, 1.0f); + } + } +} + +void +CShadows::StoreShadowForTree(CEntity *pTree) +{ + ASSERT(pTree != NULL); +} + +void +CShadows::StoreShadowForPole(CEntity *pPole, float fOffsetX, float fOffsetY, float fOffsetZ, + float fPoleHeight, float fPoleWidth, uint32 nID) +{ + ASSERT(pPole != NULL); + + if ( CTimeCycle::GetShadowStrength() != 0 ) + { + if ( pPole->GetUp().z < 0.5f ) + return; + + CVector PolePos = pPole->GetPosition(); + + PolePos.x += fOffsetX * pPole->GetRight().x + fOffsetY * pPole->GetForward().x; + PolePos.y += fOffsetX * pPole->GetRight().y + fOffsetY * pPole->GetForward().y; + PolePos.z += fOffsetZ; + + PolePos.x += -CTimeCycle::GetSunDirection().x * (fPoleHeight / 2); + PolePos.y += -CTimeCycle::GetSunDirection().y * (fPoleHeight / 2); + + StoreStaticShadow((uintptr)pPole + nID + _TODOCONST(51), SHADOWTYPE_DARK, gpPostShadowTex, &PolePos, + -CTimeCycle::GetSunDirection().x * (fPoleHeight / 2), + -CTimeCycle::GetSunDirection().y * (fPoleHeight / 2), + CTimeCycle::GetShadowSideX() * fPoleWidth, + CTimeCycle::GetShadowSideY() * fPoleWidth, + 2 * (int32)((pPole->GetUp().z - 0.5f) * CTimeCycle::GetShadowStrength() * 2.0f) / 3, + 0, 0, 0, + 15.0f, 1.0f, 40.0f, false, 0.0f); + } +} + +void +CShadows::SetRenderModeForShadowType(uint8 ShadowType) +{ + switch ( ShadowType ) + { + case SHADOWTYPE_DARK: + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + break; + } + + case SHADOWTYPE_ADDITIVE: + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + break; + } + + case SHADOWTYPE_INVCOLOR: + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCCOLOR); + break; + } + } +} + +void +CShadows::RenderStoredShadows(void) +{ + PUSH_RENDERGROUP("CShadows::RenderStoredShadows"); + + RenderBuffer::ClearRenderBuffer(); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + + for ( int32 i = 0; i < ShadowsStoredToBeRendered; i++ ) + asShadowsStored[i].m_nFlags.bRendered = false; + + for ( int32 i = 0; i < ShadowsStoredToBeRendered; i++ ) + { + if ( !asShadowsStored[i].m_nFlags.bRendered ) + { + SetRenderModeForShadowType(asShadowsStored[i].m_ShadowType); + + ASSERT(asShadowsStored[i].m_pTexture != NULL); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(asShadowsStored[i].m_pTexture)); + + for ( int32 j = i; j < ShadowsStoredToBeRendered; j++ ) + { + if ( asShadowsStored[i].m_ShadowType == asShadowsStored[j].m_ShadowType + && asShadowsStored[i].m_pTexture == asShadowsStored[j].m_pTexture ) + { + float fWidth = Abs(asShadowsStored[j].m_vecFront.x) + Abs(asShadowsStored[j].m_vecSide.x); + float fHeight = Abs(asShadowsStored[j].m_vecFront.y) + Abs(asShadowsStored[j].m_vecSide.y); + + CVector shadowPos = asShadowsStored[j].m_vecPos; + + float fStartX = shadowPos.x - fWidth; + float fEndX = shadowPos.x + fWidth; + float fStartY = shadowPos.y - fHeight; + float fEndY = shadowPos.y + fHeight; + + int32 nStartX = Max(CWorld::GetSectorIndexX(fStartX), 0); + int32 nStartY = Max(CWorld::GetSectorIndexY(fStartY), 0); + int32 nEndX = Min(CWorld::GetSectorIndexX(fEndX), NUMSECTORS_X-1); + int32 nEndY = Min(CWorld::GetSectorIndexY(fEndY), NUMSECTORS_Y-1); + + CWorld::AdvanceCurrentScanCode(); + + for ( int32 y = nStartY; y <= nEndY; y++ ) + { + for ( int32 x = nStartX; x <= nEndX; x++ ) + { + CSector *pCurSector = CWorld::GetSector(x, y); + + ASSERT(pCurSector != NULL); + + CastShadowSectorList(pCurSector->m_lists[ENTITYLIST_BUILDINGS], + fStartX, fStartY, + fEndX, fEndY, + &shadowPos, + asShadowsStored[j].m_vecFront.x, + asShadowsStored[j].m_vecFront.y, + asShadowsStored[j].m_vecSide.x, + asShadowsStored[j].m_vecSide.y, + asShadowsStored[j].m_nIntensity, + asShadowsStored[j].m_nRed, + asShadowsStored[j].m_nGreen, + asShadowsStored[j].m_nBlue, + asShadowsStored[j].m_fZDistance, + asShadowsStored[j].m_fScale, + NULL); + + CastShadowSectorList(pCurSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], + fStartX, fStartY, + fEndX, fEndY, + &shadowPos, + asShadowsStored[j].m_vecFront.x, + asShadowsStored[j].m_vecFront.y, + asShadowsStored[j].m_vecSide.x, + asShadowsStored[j].m_vecSide.y, + asShadowsStored[j].m_nIntensity, + asShadowsStored[j].m_nRed, + asShadowsStored[j].m_nGreen, + asShadowsStored[j].m_nBlue, + asShadowsStored[j].m_fZDistance, + asShadowsStored[j].m_fScale, + NULL); + } + } + + asShadowsStored[j].m_nFlags.bRendered = true; + } + } + + RenderBuffer::RenderStuffInBuffer(); + } + + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + + ShadowsStoredToBeRendered = 0; + + POP_RENDERGROUP(); +} + +void +CShadows::RenderStaticShadows(void) +{ + PUSH_RENDERGROUP("CShadows::RenderStaticShadows"); + + RenderBuffer::ClearRenderBuffer(); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE); + + SetAlphaTest(0); + + for ( int32 i = 0; i < MAX_STATICSHADOWS; i++ ) + aStaticShadows[i].m_bRendered = false; + + for ( int32 i = 0; i < MAX_STATICSHADOWS; i++ ) + { + if ( aStaticShadows[i].m_pPolyBunch && !aStaticShadows[i].m_bRendered ) + { + SetRenderModeForShadowType(aStaticShadows[i].m_nType); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(aStaticShadows[i].m_pTexture)); + + // optimization trick, render all shadows with same renderstate and texture + for ( int32 j = i; j < MAX_STATICSHADOWS; j++ ) + { + if ( aStaticShadows[j].m_pPolyBunch != NULL + && aStaticShadows[i].m_nType == aStaticShadows[j].m_nType + && aStaticShadows[i].m_pTexture == aStaticShadows[j].m_pTexture ) + { + for ( CPolyBunch *bunch = aStaticShadows[j].m_pPolyBunch; bunch != NULL; bunch = bunch->m_pNext ) + { + RwImVertexIndex *pIndexes; + RwIm3DVertex *pVerts; + + RenderBuffer::StartStoring(3 * (bunch->m_nNumVerts - 2), bunch->m_nNumVerts, &pIndexes, &pVerts); + + ASSERT(pIndexes != NULL); + ASSERT(pVerts != NULL); + + for ( int32 k = 0; k < bunch->m_nNumVerts; k++ ) + { + RwIm3DVertexSetRGBA(&pVerts[k], + aStaticShadows[j].m_nRed, + aStaticShadows[j].m_nGreen, + aStaticShadows[j].m_nBlue, + (int32)(aStaticShadows[j].m_nIntensity * (1.0f - CWeather::Foggyness * 0.5f))); + + RwIm3DVertexSetU (&pVerts[k], bunch->m_aU[k] / 200.0f); + RwIm3DVertexSetV (&pVerts[k], bunch->m_aV[k] / 200.0f); + RwIm3DVertexSetPos(&pVerts[k], bunch->m_aVerts[k].x, bunch->m_aVerts[k].y, bunch->m_aVerts[k].z + 0.03f); + } + + for ( int32 k = 0; k < 3 * (bunch->m_nNumVerts - 2); k++ ) + pIndexes[k] = ShadowIndexList[k]; + + RenderBuffer::StopStoring(); + } + + aStaticShadows[j].m_bRendered = true; + } + } + + RenderBuffer::RenderStuffInBuffer(); + } + } + RestoreAlphaTest(); + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + + POP_RENDERGROUP(); +} + +void +CShadows::GeneratePolysForStaticShadow(int16 nStaticShadowID) +{ + float fWidth = Abs(aStaticShadows[nStaticShadowID].m_vecFront.x) + Abs(aStaticShadows[nStaticShadowID].m_vecSide.x); + float fHeight = Abs(aStaticShadows[nStaticShadowID].m_vecFront.y) + Abs(aStaticShadows[nStaticShadowID].m_vecSide.y); + + CVector shadowPos = aStaticShadows[nStaticShadowID].m_vecPosn; + + float fStartX = shadowPos.x - fWidth; + float fEndX = shadowPos.x + fWidth; + float fStartY = shadowPos.y - fHeight; + float fEndY = shadowPos.y + fHeight; + + int32 nStartX = Max(CWorld::GetSectorIndexX(fStartX), 0); + int32 nStartY = Max(CWorld::GetSectorIndexY(fStartY), 0); + int32 nEndX = Min(CWorld::GetSectorIndexX(fEndX), NUMSECTORS_X-1); + int32 nEndY = Min(CWorld::GetSectorIndexY(fEndY), NUMSECTORS_Y-1); + + CWorld::AdvanceCurrentScanCode(); + + for ( int32 y = nStartY; y <= nEndY; y++ ) + { + for ( int32 x = nStartX; x <= nEndX; x++ ) + { + CSector *pCurSector = CWorld::GetSector(x, y); + + ASSERT(pCurSector != NULL); + + CastShadowSectorList(pCurSector->m_lists[ENTITYLIST_BUILDINGS], + fStartX, fStartY, + fEndX, fEndY, + &shadowPos, + aStaticShadows[nStaticShadowID].m_vecFront.x, + aStaticShadows[nStaticShadowID].m_vecFront.y, + aStaticShadows[nStaticShadowID].m_vecSide.x, + aStaticShadows[nStaticShadowID].m_vecSide.y, + 0, 0, 0, 0, + aStaticShadows[nStaticShadowID].m_fZDistance, + aStaticShadows[nStaticShadowID].m_fScale, + &aStaticShadows[nStaticShadowID].m_pPolyBunch); + + CastShadowSectorList(pCurSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], + fStartX, fStartY, + fEndX, fEndY, + &shadowPos, + aStaticShadows[nStaticShadowID].m_vecFront.x, + aStaticShadows[nStaticShadowID].m_vecFront.y, + aStaticShadows[nStaticShadowID].m_vecSide.x, + aStaticShadows[nStaticShadowID].m_vecSide.y, + 0, 0, 0, 0, + aStaticShadows[nStaticShadowID].m_fZDistance, + aStaticShadows[nStaticShadowID].m_fScale, + &aStaticShadows[nStaticShadowID].m_pPolyBunch); + } + } +} + +void +CShadows::CastShadowSectorList(CPtrList &PtrList, float fStartX, float fStartY, float fEndX, float fEndY, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, float fScale, CPolyBunch **ppPolyBunch) +{ + ASSERT(pPosn != NULL); + + CPtrNode *pNode = PtrList.first; + + CRect Bound; + + while ( pNode != NULL ) + { + CEntity *pEntity = (CEntity *)pNode->item; + uint16 nScanCode = pEntity->m_scanCode; + pNode = pNode->next; + + ASSERT( pEntity != NULL ); + + if ( nScanCode != CWorld::GetCurrentScanCode() ) + { + if ( pEntity->bUsesCollision && pEntity->IsBuilding() ) + { + pEntity->m_scanCode = CWorld::GetCurrentScanCode(); + + Bound = pEntity->GetBoundRect(); + + if ( fStartX < Bound.right + && fEndX > Bound.left + && fStartY < Bound.bottom + && fEndY > Bound.top ) + { + if ( pPosn->z - fZDistance < pEntity->GetPosition().z + pEntity->GetColModel()->boundingBox.max.z + && pEntity->GetPosition().z + pEntity->GetColModel()->boundingBox.min.z < pPosn->z ) + { + CastShadowEntity(pEntity, + fStartX, fStartY, + fEndX, fEndY, + pPosn, + fFrontX, fFrontY, + fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + fZDistance, fScale, ppPolyBunch); + } + } + } + } + } +} + +void +CShadows::CastShadowEntity(CEntity *pEntity, float fStartX, float fStartY, float fEndX, float fEndY, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, float fScale, CPolyBunch **ppPolyBunch) +{ + ASSERT(pEntity != NULL); + ASSERT(pPosn != NULL); + + static CVector List [20]; + static CVector Texture[20]; + static CVector Points [4]; + + CColModel *pCol = pEntity->GetColModel(); + ASSERT(pCol != NULL); + +#ifndef MASTER + if ( gbPrintShite ) + printf("MI:%d Triangles:%d Coors:%f %f BBoxXY:%f %f\n", + pEntity->GetModelIndex(), + pCol->numTriangles, + pEntity->GetPosition().x, + pEntity->GetPosition().y, + pCol->boundingBox.GetSize().x, + pCol->boundingBox.GetSize().y); +#endif + + CCollision::CalculateTrianglePlanes(pCol); + + float fFrontRight = DotProduct2D(CVector2D(fFrontX, fFrontY), pEntity->GetRight()); + float fFrontForward = DotProduct2D(CVector2D(fFrontX, fFrontY), pEntity->GetForward()); + float fSideRight = DotProduct2D(CVector2D(fSideX, fSideY), pEntity->GetRight()); + float fSideForward = DotProduct2D(CVector2D(fSideX, fSideY), pEntity->GetForward()); + float fLengthRight = DotProduct2D(*pPosn - pEntity->GetPosition(), pEntity->GetRight()); + float fLengthForward = DotProduct2D(*pPosn - pEntity->GetPosition(), pEntity->GetForward()); + + Points[0].x = (fLengthRight + fFrontRight ) - fSideRight; + Points[0].y = (fLengthForward + fFrontForward) - fSideForward; + + Points[1].x = fSideRight + (fLengthRight + fFrontRight); + Points[1].y = fSideForward + (fLengthForward + fFrontForward); + + Points[2].x = fSideRight + (fLengthRight - fFrontRight); + Points[2].y = fSideForward + (fLengthForward - fFrontForward); + + Points[3].x = (fLengthRight - fFrontRight) - fSideRight; + Points[3].y = (fLengthForward - fFrontForward) - fSideForward; + + float MinX = Min(Min(Points[0].x, Points[1].x), Min(Points[2].x, Points[3].x)); + float MaxX = Max(Max(Points[0].x, Points[1].x), Max(Points[2].x, Points[3].x)); + + float MinY = Min(Min(Points[0].y, Points[1].y), Min(Points[2].y, Points[3].y)); + float MaxY = Max(Max(Points[0].y, Points[1].y), Max(Points[2].y, Points[3].y)); + + float MaxZ = pPosn->z - pEntity->GetPosition().z; + float MinZ = MaxZ - fZDistance; + + for ( int32 i = 0; i < pCol->numTriangles; i++ ) + { + CColTrianglePlane *pColTriPlanes = pCol->trianglePlanes; + ASSERT(pColTriPlanes != NULL); + + CVector normal; + pColTriPlanes[i].GetNormal(normal); + if ( Abs(normal.z) > 0.1f ) + { + CColTriangle *pColTri = pCol->triangles; + ASSERT(pColTri != NULL); + + CVector PointA, PointB, PointC; + + pCol->GetTrianglePoint(PointA, pColTri[i].a); + pCol->GetTrianglePoint(PointB, pColTri[i].b); + pCol->GetTrianglePoint(PointC, pColTri[i].c); + + if ( (PointA.x > MinX || PointB.x > MinX || PointC.x > MinX) + && (PointA.x < MaxX || PointB.x < MaxX || PointC.x < MaxX) + && (PointA.y > MinY || PointB.y > MinY || PointC.y > MinY) + && (PointA.y < MaxY || PointB.y < MaxY || PointC.y < MaxY) + && (PointA.z < MaxZ || PointB.z < MaxZ || PointC.z < MaxZ) + && (PointA.z > MinZ || PointB.z > MinZ || PointC.z > MinZ) ) + + { + List[0].x = Points[0].x; + List[0].y = Points[0].y; + + List[1].x = Points[1].x; + List[1].y = Points[1].y; + + List[2].x = Points[2].x; + List[2].y = Points[2].y; + + List[3].x = Points[3].x; + List[3].y = Points[3].y; + + Texture[0].x = 0.0f; + Texture[0].y = 0.0f; + + Texture[1].x = 1.0f; + Texture[1].y = 0.0f; + + Texture[2].x = 1.0f; + Texture[2].y = 1.0f; + + Texture[3].x = 0.0f; + Texture[3].y = 1.0f; + + + CVector2D start; + CVector2D dist; + + int32 numVerts1 = 0; + int16 vertType1 = 0; + { + for ( int32 j = 0; j < 4; j++ ) + { + start = PointA; + dist = PointB - PointA; + + int32 in = j; + + float cp = CrossProduct2D(CVector2D(List[in]) - start, dist); + + if ( cp > 0.0f ) + { + switch ( vertType1 ) + { + case 0: + { + int32 out = numVerts1++ + 10; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 1: + { + int32 out = numVerts1++ + 10; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 2: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out1 = numVerts1++ + 10; + int32 out2 = numVerts1++ + 10; + + Texture[out1].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out1].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out1].x = Compl*List[in-1].x + Scale*List[in].x; + List[out1].y = Compl*List[in-1].y + Scale*List[in].y; + + Texture[out2].x = Texture[in].x; + Texture[out2].y = Texture[in].y; + List[out2].x = List[in].x; + List[out2].y = List[in].y; + + break; + } + } + + vertType1 = 1; + } + else + { + switch ( vertType1 ) + { + case 1: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out = numVerts1++ + 10; + + Texture[out].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out].x = Compl*List[in-1].x + Scale*List[in].x; + List[out].y = Compl*List[in-1].y + Scale*List[in].y; + + break; + } + } + + vertType1 = 2; + } + } + + float cp1 = CrossProduct2D(CVector2D(List[0]) - start, dist); + if ( cp1 > 0.0f && vertType1 == 2 || cp1 <= 0.0f && vertType1 == 1 ) + { + float cp2 = CrossProduct2D(CVector2D(List[3]) - start, dist); + + float Scale = Abs(cp2) / (Abs(cp2) + Abs(cp1)); + float Compl = 1.0f - Scale; + + int32 out = numVerts1++ + 10; + + Texture[out].x = Compl*Texture[3].x + Scale*Texture[0].x; + Texture[out].y = Compl*Texture[3].y + Scale*Texture[0].y; + List[out].x = Compl*List[3].x + Scale*List[0].x; + List[out].y = Compl*List[3].y + Scale*List[0].y; + } + } + + int32 numVerts2 = 0; + int16 vertType2 = 0; + { + for ( int32 j = 0; j < numVerts1; j++ ) + { + start = PointB; + dist = PointC - PointB; + + int32 in = j + 10; + float cp = CrossProduct2D(CVector2D(List[in]) - start, dist); + + if ( cp > 0.0f ) + { + switch ( vertType2 ) + { + case 0: + { + int32 out = numVerts2++; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 1: + { + int32 out = numVerts2++; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 2: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out1 = numVerts2++; + int32 out2 = numVerts2++; + + Texture[out1].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out1].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out1].x = Compl*List[in-1].x + Scale*List[in].x; + List[out1].y = Compl*List[in-1].y + Scale*List[in].y; + + Texture[out2].x = Texture[in].x; + Texture[out2].y = Texture[in].y; + List[out2].x = List[in].x; + List[out2].y = List[in].y; + + break; + } + } + + vertType2 = 1; + } + else + { + switch ( vertType2 ) + { + case 1: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out = numVerts2++; + + Texture[out].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out].x = Compl*List[in-1].x + Scale*List[in].x; + List[out].y = Compl*List[in-1].y + Scale*List[in].y; + + break; + } + } + + vertType2 = 2; + } + } + + float cp1 = CrossProduct2D(CVector2D(List[10]) - start, dist); + if ( cp1 > 0.0f && vertType2 == 2 || cp1 <= 0.0f && vertType2 == 1 ) + { + int32 in = numVerts1 + 10; + + float cp2 = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(cp2) / (Abs(cp2) + Abs(cp1)); + float Compl = 1.0f - Scale; + + int32 out = numVerts2++; + + Texture[out].x = Compl*Texture[in-1].x + Scale*Texture[10].x; + Texture[out].y = Compl*Texture[in-1].y + Scale*Texture[10].y; + List[out].x = Compl*List[in-1].x + Scale*List[10].x; + List[out].y = Compl*List[in-1].y + Scale*List[10].y; + } + } + + int32 numVerts3 = 0; + int16 vertType3 = 0; + { + for ( int32 j = 0; j < numVerts2; j++ ) + { + start = PointC; + dist = PointA - PointC; + + int32 in = j; + float cp = CrossProduct2D(CVector2D(List[in]) - start, dist); + + if ( cp > 0.0f ) + { + switch ( vertType3 ) + { + case 0: + { + int32 out = numVerts3++ + 10; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 1: + { + int32 out = numVerts3++ + 10; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 2: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out1 = numVerts3++ + 10; + int32 out2 = numVerts3++ + 10; + + Texture[out1].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out1].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out1].x = Compl*List[in-1].x + Scale*List[in].x; + List[out1].y = Compl*List[in-1].y + Scale*List[in].y; + + Texture[out2].x = Texture[in].x; + Texture[out2].y = Texture[in].y; + List[out2].x = List[in].x; + List[out2].y = List[in].y; + + break; + } + } + + vertType3 = 1; + } + else + { + switch ( vertType3 ) + { + case 1: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out = numVerts3++ + 10; + + Texture[out].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out].x = Compl*List[in-1].x + Scale*List[in].x; + List[out].y = Compl*List[in-1].y + Scale*List[in].y; + + break; + } + } + + vertType3 = 2; + } + } + + float cp1 = CrossProduct2D(CVector2D(List[0]) - start, dist); + if ( cp1 > 0.0f && vertType3 == 2 || cp1 <= 0.0f && vertType3 == 1 ) + { + int32 in = numVerts2; + + float cp2 = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(cp2) / (Abs(cp2) + Abs(cp1)); + float Compl = 1.0f - Scale; + + int32 out = numVerts3++ + 10; + + Texture[out].x = Compl*Texture[in-1].x + Scale*Texture[0].x; + Texture[out].y = Compl*Texture[in-1].y + Scale*Texture[0].y; + List[out].x = Compl*List[in-1].x + Scale*List[0].x; + List[out].y = Compl*List[in-1].y + Scale*List[0].y; + } + } + + if ( numVerts3 >= 3 ) + { + CVector norm; + + pColTriPlanes[i].GetNormal(norm); + + float dot = DotProduct(norm, PointA); + + for ( int32 j = 0; j < numVerts3; j++ ) + { + int32 idx = j + 10; + + List[idx].z = -(DotProduct2D(norm, List[idx]) - dot) / norm.z; + } + + for ( int32 j = 0; j < numVerts3; j++ ) + { + int32 idx = j + 10; + + CVector p = List[idx]; + + List[idx].x = p.y * pEntity->GetForward().x + p.x * pEntity->GetRight().x + pEntity->GetPosition().x; + List[idx].y = p.y * pEntity->GetForward().y + p.x * pEntity->GetRight().y + pEntity->GetPosition().y; + List[idx].z = p.z + pEntity->GetPosition().z; + } + + + if ( ppPolyBunch != NULL ) + { + if ( pEmptyBunchList != NULL ) + { + CPolyBunch *pBunch = pEmptyBunchList; + ASSERT(pBunch != NULL); + pEmptyBunchList = pEmptyBunchList->m_pNext; + pBunch->m_pNext = *ppPolyBunch; + *ppPolyBunch = pBunch; + + pBunch->m_nNumVerts = numVerts3; + + for ( int32 j = 0; j < numVerts3; j++ ) + { + int32 in = j + 10; + + pBunch->m_aVerts[j] = List[in]; + + pBunch->m_aU[j] = (int32)(Texture[in].x * 200.0f); + pBunch->m_aV[j] = (int32)(Texture[in].y * 200.0f); + } + } + } + else + { + RwImVertexIndex *pIndexes; + RwIm3DVertex *pVerts; + + RenderBuffer::StartStoring(3 * (numVerts3 - 2), numVerts3, &pIndexes, &pVerts); + + ASSERT(pIndexes != NULL); + ASSERT(pVerts != NULL); + + + for ( int32 j = 0; j < numVerts3; j++ ) + { + int32 in = j + 10; + + RwIm3DVertexSetRGBA(&pVerts[j], nRed, nGreen, nBlue, nIntensity); + RwIm3DVertexSetU (&pVerts[j], Texture[in].x*fScale); + RwIm3DVertexSetV (&pVerts[j], Texture[in].y*fScale); + RwIm3DVertexSetPos (&pVerts[j], List[in].x, List[in].y, List[in].z + 0.03f); + } + + for ( int32 j = 0; j < 3*(numVerts3 - 2); j++ ) + pIndexes[j] = ShadowIndexList[j]; + + RenderBuffer::StopStoring(); + } + } + } + } + } +} + +void +CShadows::UpdateStaticShadows(void) +{ + for ( int32 i = 0; i < MAX_STATICSHADOWS; i++ ) + { + if ( aStaticShadows[i].m_pPolyBunch != NULL && !aStaticShadows[i].m_bJustCreated + && (!aStaticShadows[i].m_bTemp || CTimer::GetTimeInMilliseconds() > aStaticShadows[i].m_nTimeCreated + 5000) ) + { + aStaticShadows[i].Free(); + } + + aStaticShadows[i].m_bJustCreated = false; + } +} + +void +CShadows::UpdatePermanentShadows(void) +{ + for ( int32 i = 0; i < MAX_PERMAMENTSHADOWS; i++ ) + { + if ( aPermanentShadows[i].m_nType != SHADOWTYPE_NONE ) + { + uint32 timePassed = CTimer::GetTimeInMilliseconds() - aPermanentShadows[i].m_nTimeCreated; + + if ( timePassed >= aPermanentShadows[i].m_nLifeTime ) + aPermanentShadows[i].m_nType = SHADOWTYPE_NONE; + else + { + if ( timePassed >= (aPermanentShadows[i].m_nLifeTime * 3 / 4) ) + { + // timePassed == 0 -> 4 + // timePassed == aPermanentShadows[i].m_nLifeTime -> 0 + float fMult = 1.0f - float(timePassed - (aPermanentShadows[i].m_nLifeTime * 3 / 4)) / (aPermanentShadows[i].m_nLifeTime / 4); + + StoreStaticShadow((uintptr)&aPermanentShadows[i], + aPermanentShadows[i].m_nType, + aPermanentShadows[i].m_pTexture, + &aPermanentShadows[i].m_vecPos, + aPermanentShadows[i].m_vecFront.x, + aPermanentShadows[i].m_vecFront.y, + aPermanentShadows[i].m_vecSide.x, + aPermanentShadows[i].m_vecSide.y, + (int32)(aPermanentShadows[i].m_nIntensity * fMult), + (int32)(aPermanentShadows[i].m_nRed * fMult), + (int32)(aPermanentShadows[i].m_nGreen * fMult), + (int32)(aPermanentShadows[i].m_nBlue * fMult), + aPermanentShadows[i].m_fZDistance, + 1.0f, 40.0f, false, 0.0f); + } + else + { + StoreStaticShadow((uintptr)&aPermanentShadows[i], + aPermanentShadows[i].m_nType, + aPermanentShadows[i].m_pTexture, + &aPermanentShadows[i].m_vecPos, + aPermanentShadows[i].m_vecFront.x, + aPermanentShadows[i].m_vecFront.y, + aPermanentShadows[i].m_vecSide.x, + aPermanentShadows[i].m_vecSide.y, + aPermanentShadows[i].m_nIntensity, + aPermanentShadows[i].m_nRed, + aPermanentShadows[i].m_nGreen, + aPermanentShadows[i].m_nBlue, + aPermanentShadows[i].m_fZDistance, + 1.0f, 40.0f, false, 0.0f); + } + } + } + } +} + +void +CStaticShadow::Free(void) +{ + if ( m_pPolyBunch != NULL ) + { + CPolyBunch *pFree = CShadows::pEmptyBunchList; + CShadows::pEmptyBunchList = m_pPolyBunch; + + CPolyBunch *pUsed = m_pPolyBunch; + while (pUsed->m_pNext != NULL) + pUsed = pUsed->m_pNext; + + pUsed->m_pNext = pFree; + } + + m_pPolyBunch = NULL; + + m_nId = 0; +} + +void +CShadows::CalcPedShadowValues(CVector vecLightDir, + float *pfFrontX, float *pfFrontY, + float *pfSideX, float *pfSideY, + float *pfDisplacementX, float *pfDisplacementY) +{ + ASSERT(pfFrontX != nil); + ASSERT(pfFrontY != nil); + ASSERT(pfSideX != nil); + ASSERT(pfSideY != nil); + ASSERT(pfDisplacementX != nil); + ASSERT(pfDisplacementY != nil); + + *pfFrontX = -vecLightDir.x; + *pfFrontY = -vecLightDir.y; + + float fDist = Sqrt(*pfFrontY * *pfFrontY + *pfFrontX * *pfFrontX); + float fMult = (fDist + 1.0f) / fDist; + + *pfFrontX *= fMult; + *pfFrontY *= fMult; + + *pfSideX = -vecLightDir.y / fDist; + *pfSideY = vecLightDir.x / fDist; + + *pfDisplacementX = -vecLightDir.x; + *pfDisplacementY = -vecLightDir.y; + + *pfFrontX /= 2; + *pfFrontY /= 2; + + *pfSideX /= 2; + *pfSideY /= 2; + + *pfDisplacementX /= 2; + *pfDisplacementY /= 2; + +} + +void +CShadows::RenderExtraPlayerShadows(void) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + if ( CTimeCycle::GetLightShadowStrength() != 0 ) + { + CVehicle *pCar = FindPlayerVehicle(); + + if ( pCar == NULL ) + { + for ( int32 i = 0; i < CPointLights::NumLights; i++ ) + { + if ( 0.0f != CPointLights::aLights[i].red + || 0.0f != CPointLights::aLights[i].green + || 0.0f != CPointLights::aLights[i].blue ) + { + if ( CPointLights::aLights[i].castExtraShadows ) + { + CVector vecLight = CPointLights::aLights[i].coors - FindPlayerCoors(); + float fLightDist = vecLight.Magnitude(); + float fRadius = CPointLights::aLights[i].radius; + + if ( fLightDist < fRadius ) + { + // fLightDist == fRadius -> 2.0f + // fLightDist == 0 -> 0.0f + float fMult = (1.0f - (2.0f * fLightDist - fRadius) / fRadius); + + int32 nColorStrength; + if ( fLightDist < fRadius*0.5f ) + nColorStrength = (5*CTimeCycle::GetLightShadowStrength()/8); + else + nColorStrength = int32((5*CTimeCycle::GetLightShadowStrength()/8) * fMult); + + float fInv = 1.0f / fLightDist; + vecLight.x *= fInv; + vecLight.y *= fInv; + vecLight.z *= fInv; + + float fFrontX, fFrontY, fSideX, fSideY, fDisplacementX, fDisplacementY; + + CalcPedShadowValues(vecLight, + &fFrontX, &fFrontY, + &fSideX, &fSideY, + &fDisplacementX, &fDisplacementY); + + CVector shadowPos = FindPlayerCoors(); + + shadowPos.x += fDisplacementX; + shadowPos.y += fDisplacementY; + + + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowPedTex, &shadowPos, + fFrontX, fFrontY, + fSideX, fSideY, + nColorStrength, 0, 0, 0, + 4.0f, false, 1.0f); + } + } + } + } + } + else + { + if ( pCar->GetModelIndex() != MI_RCBANDIT ) + { + for ( int32 i = 0; i < CPointLights::NumLights; i++ ) + { + if ( CPointLights::aLights[i].type == CPointLights::LIGHT_POINT + && CPointLights::aLights[i].castExtraShadows + &&(0.0f != CPointLights::aLights[i].red + || 0.0f != CPointLights::aLights[i].green + || 0.0f != CPointLights::aLights[i].blue) ) + { + CVector vecLight = CPointLights::aLights[i].coors - FindPlayerCoors(); + float fLightDist = vecLight.Magnitude(); + float fRadius = CPointLights::aLights[i].radius; + + if ( fLightDist < fRadius ) + { + // fLightDist == 0 -> 2.0f + // fLightDist == fRadius -> 0.0f + float fMult = (1.0f - (2.0f * fLightDist - fRadius) / fRadius); + + int32 nColorStrength; + if ( fLightDist < fRadius*0.5f ) + nColorStrength = (5*CTimeCycle::GetLightShadowStrength()/8); + else + nColorStrength = int32((5*CTimeCycle::GetLightShadowStrength()/8) * fMult); + + float fInv = 1.0f / fLightDist; + vecLight.x *= fInv; + vecLight.y *= fInv; + vecLight.z *= fInv; + + CVector shadowPos = pCar->GetPosition(); + + shadowPos.x -= vecLight.x * 1.2f; + shadowPos.y -= vecLight.y * 1.2f; + + float fVehicleWidth = pCar->GetColModel()->boundingBox.GetSize().x; + float fVehicleHeight = pCar->GetColModel()->boundingBox.GetSize().y; + + shadowPos.x -= ((fVehicleHeight/2) - pCar->GetColModel()->boundingBox.max.y) + * pCar->GetForward().x; + + shadowPos.y -= ((fVehicleHeight/2) - pCar->GetColModel()->boundingBox.max.y) + * pCar->GetForward().y; + + if ( pCar->GetUp().z > 0.0f ) + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowCarTex, &shadowPos, + pCar->GetForward().x * (fVehicleHeight/2), + pCar->GetForward().y * (fVehicleHeight/2), + pCar->GetRight().x * (fVehicleWidth/3), + pCar->GetRight().y * (fVehicleWidth/3), + nColorStrength, 0, 0, 0, + 4.5f, false, 1.0f); + } + else + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowCarTex, &shadowPos, + pCar->GetForward().x * (fVehicleHeight/2), + pCar->GetForward().y * (fVehicleHeight/2), + -pCar->GetRight().x * (fVehicleWidth/2), + -pCar->GetRight().y * (fVehicleWidth/2), + nColorStrength, 0, 0, 0, + 4.5f, false, 1.0f); + } + } + } + } + } + } + } +} + +void +CShadows::TidyUpShadows(void) +{ + for ( int32 i = 0; i < MAX_PERMAMENTSHADOWS; i++ ) + aPermanentShadows[i].m_nType = SHADOWTYPE_NONE; +} + +void +CShadows::RenderIndicatorShadow(uint32 nID, uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity) +{ + ASSERT(pPosn != NULL); + + C3dMarkers::PlaceMarkerSet(nID, MARKERTYPE_CYLINDER, *pPosn, Max(fFrontX, -fSideY), + 0, 128, 255, 128, + 2048, 0.2f, 0); +} diff --git a/src/renderer/Shadows.h b/src/renderer/Shadows.h new file mode 100644 index 0000000..8c909df --- /dev/null +++ b/src/renderer/Shadows.h @@ -0,0 +1,180 @@ +#pragma once + +#define MAX_STOREDSHADOWS 48 +#define MAX_POLYBUNCHES 300 +#define MAX_STATICSHADOWS 64 +#define MAX_PERMAMENTSHADOWS 48 + + +class CEntity; + +enum eShadowType +{ + SHADOWTYPE_NONE = 0, + SHADOWTYPE_DARK, + SHADOWTYPE_ADDITIVE, + SHADOWTYPE_INVCOLOR +}; + +enum eShadowTextureType +{ + SHADOWTEX_NONE = 0, + SHADOWTEX_CAR, + SHADOWTEX_PED, + SHADOWTEX_EXPLOSION, + SHADOWTEX_HELI, + SHADOWTEX_HEADLIGHTS, + SHADOWTEX_BLOOD +}; + +class CStoredShadow +{ +public: + CVector m_vecPos; + CVector2D m_vecFront; + CVector2D m_vecSide; + float m_fZDistance; + float m_fScale; + int16 m_nIntensity; + uint8 m_ShadowType; + uint8 m_nRed; + uint8 m_nGreen; + uint8 m_nBlue; + struct + { + uint8 bDrawOnWater : 1; + uint8 bRendered : 1; + //uint8 bDrawOnBuildings : 1; + } m_nFlags; + RwTexture *m_pTexture; + + CStoredShadow() + { } +}; + +VALIDATE_SIZE(CStoredShadow, 0x30); + +class CPolyBunch +{ +public: + int16 m_nNumVerts; + CVector m_aVerts[7]; + uint8 m_aU[7]; + uint8 m_aV[7]; + CPolyBunch *m_pNext; + + CPolyBunch() + { } +}; + +VALIDATE_SIZE(CPolyBunch, 0x6C); + +class CStaticShadow +{ +public: + uint32 m_nId; + CPolyBunch *m_pPolyBunch; + uint32 m_nTimeCreated; + CVector m_vecPosn; + CVector2D m_vecFront; + CVector2D m_vecSide; + float m_fZDistance; + float m_fScale; + uint8 m_nType; + int16 m_nIntensity; // unsigned ? + uint8 m_nRed; + uint8 m_nGreen; + uint8 m_nBlue; + bool m_bJustCreated; + bool m_bRendered; + bool m_bTemp; + RwTexture *m_pTexture; + + CStaticShadow() + { } + + void Free(); +}; + +VALIDATE_SIZE(CStaticShadow, 0x40); + +class CPermanentShadow +{ +public: + CVector m_vecPos; + CVector2D m_vecFront; + CVector2D m_vecSide; + float m_fZDistance; + float m_fScale; + int16 m_nIntensity; + uint8 m_nType; // eShadowType + uint8 m_nRed; + uint8 m_nGreen; + uint8 m_nBlue; + uint32 m_nTimeCreated; + uint32 m_nLifeTime; + RwTexture *m_pTexture; + + CPermanentShadow() + { } +}; + +VALIDATE_SIZE(CPermanentShadow, 0x38); + +class CPtrList; +class CAutomobile; +class CPed; + +class CShadows +{ +public: + static int16 ShadowsStoredToBeRendered; + static CStoredShadow asShadowsStored [MAX_STOREDSHADOWS]; + static CPolyBunch aPolyBunches [MAX_POLYBUNCHES]; + static CStaticShadow aStaticShadows [MAX_STATICSHADOWS]; + static CPolyBunch *pEmptyBunchList; + static CPermanentShadow aPermanentShadows[MAX_PERMAMENTSHADOWS]; + + static void Init (void); + static void Shutdown (void); + static void AddPermanentShadow ( uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, uint32 nTime, float fScale); + static void StoreStaticShadow (uint32 nID, uint8 ShadowType, RwTexture *pTexture, Const CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, float fScale, float fDrawDistance, bool bTempShadow, float fUpDistance); + static void StoreShadowToBeRendered ( uint8 ShadowType, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue); + static void StoreShadowToBeRendered ( uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, bool bDrawOnWater, float fScale); + static void StoreShadowForCar (CAutomobile *pCar); + static void StoreCarLightShadow (CAutomobile *pCar, int32 nID, RwTexture *pTexture, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, uint8 nRed, uint8 nGreen, uint8 nBlue, float fMaxViewAngle); + static void StoreShadowForPed (CPed *pPed, float fDisplacementX, float fDisplacementY, float fFrontX, float fFrontY, float fSideX, float fSideY); + static void StoreShadowForPedObject (CEntity *pPedObject, float fDisplacementX, float fDisplacementY, float fFrontX, float fFrontY, float fSideX, float fSideY); + static void StoreShadowForTree (CEntity *pTree); + static void StoreShadowForPole (CEntity *pPole, float fOffsetX, float fOffsetY, float fOffsetZ, float fPoleHeight, float fPoleWidth, uint32 nID); + static void SetRenderModeForShadowType (uint8 ShadowType); + static void RenderStoredShadows (void); + static void RenderStaticShadows (void); + static void GeneratePolysForStaticShadow (int16 nStaticShadowID); + static void CastShadowSectorList (CPtrList &PtrList, float fStartX, float fStartY, float fEndX, float fEndY, + CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, float fScale, CPolyBunch **ppPolyBunch); + static void CastShadowEntity (CEntity *pEntity, float fStartX, float fStartY, float fEndX, float fEndY, + CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, float fScale, CPolyBunch **ppPolyBunch); + static void UpdateStaticShadows (void); + static void UpdatePermanentShadows (void); + static void CalcPedShadowValues (CVector vecLightDir, float *pfFrontX, float *pfFrontY, float *pfSideX, float *pfSideY, float *pfDisplacementX, float *pfDisplacementY); + static void RenderExtraPlayerShadows (void); + static void TidyUpShadows (void); + static void RenderIndicatorShadow (uint32 nID, uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity); +}; + +extern RwTexture *gpShadowCarTex; +extern RwTexture *gpShadowPedTex; +extern RwTexture *gpShadowHeliTex; +extern RwTexture *gpShadowExplosionTex; +extern RwTexture *gpShadowHeadLightsTex; +extern RwTexture *gpOutline1Tex; +extern RwTexture *gpOutline2Tex; +extern RwTexture *gpOutline3Tex; +extern RwTexture *gpBloodPoolTex; +extern RwTexture *gpReflectionTex; +extern RwTexture *gpGoalMarkerTex; +extern RwTexture *gpWalkDontTex; +extern RwTexture *gpCrackedGlassTex; +extern RwTexture *gpPostShadowTex; +extern RwTexture *gpGoalTex; diff --git a/src/renderer/Skidmarks.cpp b/src/renderer/Skidmarks.cpp new file mode 100644 index 0000000..4c662a7 --- /dev/null +++ b/src/renderer/Skidmarks.cpp @@ -0,0 +1,262 @@ +#include "common.h" + +#include "main.h" +#include "TxdStore.h" +#include "Timer.h" +#include "Replay.h" +#include "Skidmarks.h" + +CSkidmark CSkidmarks::aSkidmarks[NUMSKIDMARKS]; + +RwImVertexIndex SkidmarkIndexList[SKIDMARK_LENGTH * 6]; +RwIm3DVertex SkidmarkVertices[SKIDMARK_LENGTH * 2]; +RwTexture *gpSkidTex; +RwTexture *gpSkidBloodTex; +RwTexture *gpSkidMudTex; + +void +CSkidmarks::Init(void) +{ + int i, ix, slot; + CTxdStore::PushCurrentTxd(); + slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + gpSkidTex = RwTextureRead("particleskid", nil); + gpSkidBloodTex = RwTextureRead("particleskidblood", nil); + gpSkidMudTex = RwTextureRead("particleskidmud", nil); + CTxdStore::PopCurrentTxd(); + + for(i = 0; i < NUMSKIDMARKS; i++){ + aSkidmarks[i].m_state = 0; + aSkidmarks[i].m_wasUpdated = false; + } + + ix = 0; + for(i = 0; i < SKIDMARK_LENGTH; i++){ + SkidmarkIndexList[i*6+0] = ix+0; + SkidmarkIndexList[i*6+1] = ix+2; + SkidmarkIndexList[i*6+2] = ix+1; + SkidmarkIndexList[i*6+3] = ix+1; + SkidmarkIndexList[i*6+4] = ix+2; + SkidmarkIndexList[i*6+5] = ix+3; + ix += 2; + } + + for(i = 0; i < SKIDMARK_LENGTH; i++){ + RwIm3DVertexSetU(&SkidmarkVertices[i*2 + 0], 0.0f); + RwIm3DVertexSetV(&SkidmarkVertices[i*2 + 0], i*5.01f); + RwIm3DVertexSetU(&SkidmarkVertices[i*2 + 1], 1.0f); + RwIm3DVertexSetV(&SkidmarkVertices[i*2 + 1], i*5.01f); + } +} + +void +CSkidmarks::Shutdown(void) +{ + RwTextureDestroy(gpSkidTex); +#if GTA_VERSION >= GTA3_PC_11 + gpSkidTex = nil; +#endif + RwTextureDestroy(gpSkidBloodTex); +#if GTA_VERSION >= GTA3_PC_11 + gpSkidBloodTex = nil; +#endif + RwTextureDestroy(gpSkidMudTex); +#if GTA_VERSION >= GTA3_PC_11 + gpSkidMudTex = nil; +#endif +} + +void +CSkidmarks::Clear(void) +{ + int i; + for(i = 0; i < NUMSKIDMARKS; i++){ + aSkidmarks[i].m_state = 0; + aSkidmarks[i].m_wasUpdated = false; + } +} + +void +CSkidmarks::Update(void) +{ + int i; + uint32 t1 = CTimer::GetTimeInMilliseconds() + 2500; + uint32 t2 = CTimer::GetTimeInMilliseconds() + 5000; + uint32 t3 = CTimer::GetTimeInMilliseconds() + 10000; + uint32 t4 = CTimer::GetTimeInMilliseconds() + 20000; + for(i = 0; i < NUMSKIDMARKS; i++){ + switch(aSkidmarks[i].m_state){ + case 1: + if(!aSkidmarks[i].m_wasUpdated){ + // Didn't continue this one last time, so finish it and set fade times + aSkidmarks[i].m_state = 2; + if(aSkidmarks[i].m_last < 4){ + aSkidmarks[i].m_fadeStart = t1; + aSkidmarks[i].m_fadeEnd = t2; + }else if(aSkidmarks[i].m_last < 9){ + aSkidmarks[i].m_fadeStart = t2; + aSkidmarks[i].m_fadeEnd = t3; + }else{ + aSkidmarks[i].m_fadeStart = t3; + aSkidmarks[i].m_fadeEnd = t4; + } + } + break; + case 2: + if(CTimer::GetTimeInMilliseconds() > aSkidmarks[i].m_fadeEnd) + aSkidmarks[i].m_state = 0; + break; + } + aSkidmarks[i].m_wasUpdated = false; + } +} + +void +CSkidmarks::Render(void) +{ + int i, j; + RwTexture *lastTex = nil; + + PUSH_RENDERGROUP("CSkidmarks::Render"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + for(i = 0; i < NUMSKIDMARKS; i++){ + if(aSkidmarks[i].m_state == 0 || aSkidmarks[i].m_last < 1) + continue; + + if(aSkidmarks[i].m_isBloody){ + if(lastTex != gpSkidBloodTex){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpSkidBloodTex)); + lastTex = gpSkidBloodTex; + } + }else if(aSkidmarks[i].m_isMuddy){ + if(lastTex != gpSkidMudTex){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpSkidMudTex)); + lastTex = gpSkidMudTex; + } + }else{ + if(lastTex != gpSkidTex){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpSkidTex)); + lastTex = gpSkidTex; + } + } + + uint32 fade, alpha; + if(aSkidmarks[i].m_state == 1 || CTimer::GetTimeInMilliseconds() < aSkidmarks[i].m_fadeStart) + fade = 255; + else + fade = 255*(aSkidmarks[i].m_fadeEnd - CTimer::GetTimeInMilliseconds()) / (aSkidmarks[i].m_fadeEnd - aSkidmarks[i].m_fadeStart); + + for(j = 0; j <= aSkidmarks[i].m_last; j++){ + alpha = 128; + if(j == 0 || j == aSkidmarks[i].m_last && aSkidmarks[i].m_state == 2) + alpha = 0; + alpha = alpha*fade/256; + + CVector p1 = aSkidmarks[i].m_pos[j] + aSkidmarks[i].m_side[j]; + CVector p2 = aSkidmarks[i].m_pos[j] - aSkidmarks[i].m_side[j]; + RwIm3DVertexSetRGBA(&SkidmarkVertices[j*2+0], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&SkidmarkVertices[j*2+0], p1.x, p1.y, p1.z+0.1f); + RwIm3DVertexSetRGBA(&SkidmarkVertices[j*2+1], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&SkidmarkVertices[j*2+1], p2.x, p2.y, p2.z+0.1f); + } + + LittleTest(); + if(RwIm3DTransform(SkidmarkVertices, 2*(aSkidmarks[i].m_last+1), nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, SkidmarkIndexList, 6*aSkidmarks[i].m_last); + RwIm3DEnd(); + } + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + + POP_RENDERGROUP(); +} + +void +CSkidmarks::RegisterOne(uintptr id, CVector pos, float fwdX, float fwdY, bool *isMuddy, bool *isBloody) +{ + int i; + CVector2D fwd(fwdX, fwdY); + + if(CReplay::IsPlayingBack()) + return; + + // Find a skidmark to continue + for(i = 0; i < NUMSKIDMARKS; i++) + if(aSkidmarks[i].m_state == 1 && aSkidmarks[i].m_id == id) + break; + + if(i < NUMSKIDMARKS){ + // Continue this one + + if(aSkidmarks[i].m_isBloody != *isBloody){ + // Blood-status changed, end this one + aSkidmarks[i].m_state = 2; + aSkidmarks[i].m_fadeStart = CTimer::GetTimeInMilliseconds() + 10000; + aSkidmarks[i].m_fadeEnd = CTimer::GetTimeInMilliseconds() + 20000; + return; + } + + aSkidmarks[i].m_wasUpdated = true; + + if(CTimer::GetTimeInMilliseconds() - aSkidmarks[i].m_lastUpdate <= 100){ + // Last update was recently, just change last coords + aSkidmarks[i].m_pos[aSkidmarks[i].m_last] = pos; + return; + } + aSkidmarks[i].m_lastUpdate = CTimer::GetTimeInMilliseconds(); + + if(aSkidmarks[i].m_last >= SKIDMARK_LENGTH-1){ + // No space to continue, end it + aSkidmarks[i].m_state = 2; + aSkidmarks[i].m_fadeStart = CTimer::GetTimeInMilliseconds() + 10000; + aSkidmarks[i].m_fadeEnd = CTimer::GetTimeInMilliseconds() + 20000; + *isBloody = false; // stpo blood marks at end + return; + } + aSkidmarks[i].m_last++; + + aSkidmarks[i].m_pos[aSkidmarks[i].m_last] = pos; + + CVector2D right(aSkidmarks[i].m_pos[aSkidmarks[i].m_last].y - aSkidmarks[i].m_pos[aSkidmarks[i].m_last - 1].y, + aSkidmarks[i].m_pos[aSkidmarks[i].m_last - 1].x - aSkidmarks[i].m_pos[aSkidmarks[i].m_last].x); + + right.NormaliseSafe(); + fwd.NormaliseSafe(); + float turn = DotProduct2D(fwd, right); + turn = Abs(turn) + 1.0f; + aSkidmarks[i].m_side[aSkidmarks[i].m_last] = CVector(right.x, right.y, 0.0f) * turn * 0.125f; + if(aSkidmarks[i].m_last == 1) + aSkidmarks[i].m_side[0] = aSkidmarks[i].m_side[1]; + + if(aSkidmarks[i].m_last > 8) + *isBloody = false; // stop blood marks after 8 + return; + } + + // Start a new one + for(i = 0; i < NUMSKIDMARKS; i++) + if(aSkidmarks[i].m_state == 0) + break; + if(i < NUMSKIDMARKS){ + // Found a free slot + aSkidmarks[i].m_state = 1; + aSkidmarks[i].m_id = id; + aSkidmarks[i].m_pos[0] = pos; + aSkidmarks[i].m_side[0] = CVector(0.0f, 0.0f, 0.0f); + aSkidmarks[i].m_wasUpdated = true; + aSkidmarks[i].m_last = 0; + aSkidmarks[i].m_lastUpdate = CTimer::GetTimeInMilliseconds() - 1000; + aSkidmarks[i].m_isBloody = *isBloody; + aSkidmarks[i].m_isMuddy = *isMuddy; + }else + *isBloody = false; // stop blood marks if no space +} diff --git a/src/renderer/Skidmarks.h b/src/renderer/Skidmarks.h new file mode 100644 index 0000000..c061782 --- /dev/null +++ b/src/renderer/Skidmarks.h @@ -0,0 +1,32 @@ +#pragma once + +enum { SKIDMARK_LENGTH = 16 }; + +class CSkidmark +{ +public: + uint8 m_state; + bool m_wasUpdated; + bool m_isBloody; + bool m_isMuddy; + uintptr m_id; + int16 m_last; + uint32 m_lastUpdate; + uint32 m_fadeStart; + uint32 m_fadeEnd; + CVector m_pos[SKIDMARK_LENGTH]; + CVector m_side[SKIDMARK_LENGTH]; +}; + +class CSkidmarks +{ + static CSkidmark aSkidmarks[NUMSKIDMARKS]; +public: + + static void Init(void); + static void Shutdown(void); + static void Clear(void); + static void Update(void); + static void Render(void); + static void RegisterOne(uintptr id, CVector pos, float fwdX, float fwdY, bool *isMuddy, bool *isBloody); +}; diff --git a/src/renderer/SpecialFX.cpp b/src/renderer/SpecialFX.cpp new file mode 100644 index 0000000..6d96d21 --- /dev/null +++ b/src/renderer/SpecialFX.cpp @@ -0,0 +1,1194 @@ +#include "common.h" + +#include "SpecialFX.h" +#include "RenderBuffer.h" +#include "Timer.h" +#include "Sprite.h" +#include "Font.h" +#include "Text.h" +#include "TxdStore.h" +#include "FileMgr.h" +#include "FileLoader.h" +#include "Timecycle.h" +#include "Lights.h" +#include "ModelIndices.h" +#include "VisibilityPlugins.h" +#include "World.h" +#include "PlayerPed.h" +#include "Particle.h" +#include "Shadows.h" +#include "General.h" +#include "Camera.h" +#include "Shadows.h" +#include "main.h" + +RwIm3DVertex StreakVertices[4]; +RwImVertexIndex StreakIndexList[12]; + +RwIm3DVertex TraceVertices[6]; +RwImVertexIndex TraceIndexList[12]; + + +void +CSpecialFX::Init(void) +{ + CBulletTraces::Init(); + + RwIm3DVertexSetU(&StreakVertices[0], 0.0f); + RwIm3DVertexSetV(&StreakVertices[0], 0.0f); + RwIm3DVertexSetU(&StreakVertices[1], 1.0f); + RwIm3DVertexSetV(&StreakVertices[1], 0.0f); + RwIm3DVertexSetU(&StreakVertices[2], 0.0f); + RwIm3DVertexSetV(&StreakVertices[2], 0.0f); + RwIm3DVertexSetU(&StreakVertices[3], 1.0f); + RwIm3DVertexSetV(&StreakVertices[3], 0.0f); + + StreakIndexList[0] = 0; + StreakIndexList[1] = 1; + StreakIndexList[2] = 2; + StreakIndexList[3] = 1; + StreakIndexList[4] = 3; + StreakIndexList[5] = 2; + StreakIndexList[6] = 0; + StreakIndexList[7] = 2; + StreakIndexList[8] = 1; + StreakIndexList[9] = 1; + StreakIndexList[10] = 2; + StreakIndexList[11] = 3; + + RwIm3DVertexSetRGBA(&TraceVertices[0], 20, 20, 20, 255); + RwIm3DVertexSetRGBA(&TraceVertices[1], 20, 20, 20, 255); + RwIm3DVertexSetRGBA(&TraceVertices[2], 70, 70, 70, 255); + RwIm3DVertexSetRGBA(&TraceVertices[3], 70, 70, 70, 255); + RwIm3DVertexSetRGBA(&TraceVertices[4], 10, 10, 10, 255); + RwIm3DVertexSetRGBA(&TraceVertices[5], 10, 10, 10, 255); + RwIm3DVertexSetU(&TraceVertices[0], 0.0); + RwIm3DVertexSetV(&TraceVertices[0], 0.0); + RwIm3DVertexSetU(&TraceVertices[1], 1.0); + RwIm3DVertexSetV(&TraceVertices[1], 0.0); + RwIm3DVertexSetU(&TraceVertices[2], 0.0); + RwIm3DVertexSetV(&TraceVertices[2], 0.5); + RwIm3DVertexSetU(&TraceVertices[3], 1.0); + RwIm3DVertexSetV(&TraceVertices[3], 0.5); + RwIm3DVertexSetU(&TraceVertices[4], 0.0); + RwIm3DVertexSetV(&TraceVertices[4], 1.0); + RwIm3DVertexSetU(&TraceVertices[5], 1.0); + RwIm3DVertexSetV(&TraceVertices[5], 1.0); + + TraceIndexList[0] = 0; + TraceIndexList[1] = 2; + TraceIndexList[2] = 1; + TraceIndexList[3] = 1; + TraceIndexList[4] = 2; + TraceIndexList[5] = 3; + TraceIndexList[6] = 2; + TraceIndexList[7] = 4; + TraceIndexList[8] = 3; + TraceIndexList[9] = 3; + TraceIndexList[10] = 4; + TraceIndexList[11] = 5; + + CMotionBlurStreaks::Init(); + CBrightLights::Init(); + CShinyTexts::Init(); + CMoneyMessages::Init(); + C3dMarkers::Init(); +} + +RwObject* +LookForBatCB(RwObject *object, void *data) +{ + static CMatrix MatLTM; + + if(CVisibilityPlugins::GetAtomicModelInfo((RpAtomic*)object) == (CSimpleModelInfo*)data){ + MatLTM = CMatrix(RwFrameGetLTM(RpAtomicGetFrame((RpAtomic*)object))); + CVector p1 = MatLTM * CVector(0.02f, 0.05f, 0.07f); + CVector p2 = MatLTM * CVector(0.246f, 0.0325f, 0.796f); + CMotionBlurStreaks::RegisterStreak((uintptr)object, 100, 100, 100, p1, p2); + } + return nil; +} + +void +CSpecialFX::Update(void) +{ + CMotionBlurStreaks::Update(); + CBulletTraces::Update(); + + if(FindPlayerPed() && + FindPlayerPed()->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT && + FindPlayerPed()->GetWeapon()->m_eWeaponState == WEAPONSTATE_FIRING){ +#ifdef PED_SKIN + if(IsClumpSkinned(FindPlayerPed()->GetClump())){ + LookForBatCB((RwObject*)FindPlayerPed()->m_pWeaponModel, CModelInfo::GetModelInfo(MI_BASEBALL_BAT)); + }else +#endif + RwFrameForAllObjects(FindPlayerPed()->m_pFrames[PED_HANDR]->frame, LookForBatCB, CModelInfo::GetModelInfo(MI_BASEBALL_BAT)); + } +} + +void +CSpecialFX::Shutdown(void) +{ + C3dMarkers::Shutdown(); +} + +void +CSpecialFX::Render(void) +{ + PUSH_RENDERGROUP("CSpecialFX::Render"); + CMotionBlurStreaks::Render(); + CBulletTraces::Render(); + CBrightLights::Render(); + CShinyTexts::Render(); + CMoneyMessages::Render(); +#ifdef NEW_RENDERER + if(!(gbNewRenderer && FredIsInFirstPersonCam())) +#endif + C3dMarkers::Render(); + POP_RENDERGROUP(); +} + +CRegisteredMotionBlurStreak CMotionBlurStreaks::aStreaks[NUMMBLURSTREAKS]; + +void +CRegisteredMotionBlurStreak::Update(void) +{ + int i; + bool wasUpdated; + bool lastWasUpdated = false; + for(i = 2; i > 0; i--){ + m_pos1[i] = m_pos1[i-1]; + m_pos2[i] = m_pos2[i-1]; + m_isValid[i] = m_isValid[i-1]; + wasUpdated = true; + if(!lastWasUpdated && !m_isValid[i]) + wasUpdated = false; + lastWasUpdated = wasUpdated; + } + m_isValid[0] = false; + if(!wasUpdated) + m_id = 0; +} + +void +CRegisteredMotionBlurStreak::Render(void) +{ + int i; + int a1, a2; + for(i = 0; i < 2; i++) + if(m_isValid[i] && m_isValid[i+1]){ + a1 = (255/3)*(3-i)/3; + RwIm3DVertexSetRGBA(&StreakVertices[0], m_red, m_green, m_blue, a1); + RwIm3DVertexSetRGBA(&StreakVertices[1], m_red, m_green, m_blue, a1); + a2 = (255/3)*(3-(i+1))/3; + RwIm3DVertexSetRGBA(&StreakVertices[2], m_red, m_green, m_blue, a2); + RwIm3DVertexSetRGBA(&StreakVertices[3], m_red, m_green, m_blue, a2); + RwIm3DVertexSetPos(&StreakVertices[0], m_pos1[i].x, m_pos1[i].y, m_pos1[i].z); + RwIm3DVertexSetPos(&StreakVertices[1], m_pos2[i].x, m_pos2[i].y, m_pos2[i].z); + RwIm3DVertexSetPos(&StreakVertices[2], m_pos1[i+1].x, m_pos1[i+1].y, m_pos1[i+1].z); + RwIm3DVertexSetPos(&StreakVertices[3], m_pos2[i+1].x, m_pos2[i+1].y, m_pos2[i+1].z); + LittleTest(); + if(RwIm3DTransform(StreakVertices, 4, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, StreakIndexList, 12); + RwIm3DEnd(); + } + } +} + +void +CMotionBlurStreaks::Init(void) +{ + int i; + for(i = 0; i < NUMMBLURSTREAKS; i++) + aStreaks[i].m_id = 0; +} + +void +CMotionBlurStreaks::Update(void) +{ + int i; + for(i = 0; i < NUMMBLURSTREAKS; i++) + if(aStreaks[i].m_id != 0) + aStreaks[i].Update(); +} + +void +CMotionBlurStreaks::RegisterStreak(uintptr id, uint8 r, uint8 g, uint8 b, CVector p1, CVector p2) +{ + int i; + for(i = 0; i < NUMMBLURSTREAKS; i++){ + if(aStreaks[i].m_id == id){ + // Found a streak from last frame, update + aStreaks[i].m_red = r; + aStreaks[i].m_green = g; + aStreaks[i].m_blue = b; + aStreaks[i].m_pos1[0] = p1; + aStreaks[i].m_pos2[0] = p2; + aStreaks[i].m_isValid[0] = true; + return; + } + } + // Find free slot + for(i = 0; aStreaks[i].m_id != 0; i++) + if(i == NUMMBLURSTREAKS-1) + return; + // Create a new streak + aStreaks[i].m_id = id; + aStreaks[i].m_red = r; + aStreaks[i].m_green = g; + aStreaks[i].m_blue = b; + aStreaks[i].m_pos1[0] = p1; + aStreaks[i].m_pos2[0] = p2; + aStreaks[i].m_isValid[0] = true; + aStreaks[i].m_isValid[1] = false; + aStreaks[i].m_isValid[2] = false; +} + +void +CMotionBlurStreaks::Render(void) +{ + bool setRenderStates = false; + int i; + for(i = 0; i < NUMMBLURSTREAKS; i++) + if(aStreaks[i].m_id != 0){ + if(!setRenderStates){ + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGCOLOR, + (void*)RWRGBALONG(CTimeCycle::GetFogRed(), CTimeCycle::GetFogGreen(), CTimeCycle::GetFogBlue(), 255)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void*)FALSE); + setRenderStates = true; + } + aStreaks[i].Render(); + } + if(setRenderStates){ + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE); + } +} + + +CBulletTrace CBulletTraces::aTraces[NUMBULLETTRACES]; + +void CBulletTraces::Init(void) +{ + for (int i = 0; i < NUMBULLETTRACES; i++) + aTraces[i].m_bInUse = false; +} + +void CBulletTraces::AddTrace(CVector* vecStart, CVector* vecTarget) +{ + int index; + for (index = 0; index < NUMBULLETTRACES; index++) { + if (!aTraces[index].m_bInUse) + break; + } + if (index == NUMBULLETTRACES) + return; + aTraces[index].m_vecCurrentPos = *vecStart; + aTraces[index].m_vecTargetPos = *vecTarget; + aTraces[index].m_bInUse = true; + aTraces[index].m_framesInUse = 0; + aTraces[index].m_lifeTime = 25 + CGeneral::GetRandomNumber() % 32; +} + +void CBulletTraces::Render(void) +{ + for (int i = 0; i < NUMBULLETTRACES; i++) { + if (!aTraces[i].m_bInUse) + continue; + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); +#ifdef FIX_BUGS + // Raster has no transparent pixels so it relies on the raster format having alpha + // to turn on blending. librw image conversion might get rid of it right now so let's + // just force it on. + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); +#endif + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpShadowExplosionTex)); + CVector inf = aTraces[i].m_vecCurrentPos; + CVector sup = aTraces[i].m_vecTargetPos; + CVector center = (inf + sup) / 2; + CVector width = CrossProduct(TheCamera.GetForward(), (sup - inf)); + width.Normalise(); + width /= 20; + uint8 intensity = aTraces[i].m_lifeTime; + for (int i = 0; i < ARRAY_SIZE(TraceVertices); i++) + RwIm3DVertexSetRGBA(&TraceVertices[i], intensity, intensity, intensity, 0xFF); + RwIm3DVertexSetPos(&TraceVertices[0], inf.x + width.x, inf.y + width.y, inf.z + width.z); + RwIm3DVertexSetPos(&TraceVertices[1], inf.x - width.x, inf.y - width.y, inf.z - width.z); + RwIm3DVertexSetPos(&TraceVertices[2], center.x + width.x, center.y + width.y, center.z + width.z); + RwIm3DVertexSetPos(&TraceVertices[3], center.x - width.x, center.y - width.y, center.z - width.z); + RwIm3DVertexSetPos(&TraceVertices[4], sup.x + width.x, sup.y + width.y, sup.z + width.z); + RwIm3DVertexSetPos(&TraceVertices[5], sup.x - width.x, sup.y - width.y, sup.z - width.z); + LittleTest(); + if (RwIm3DTransform(TraceVertices, ARRAY_SIZE(TraceVertices), nil, rwIM3D_VERTEXUV)) { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TraceIndexList, ARRAY_SIZE(TraceIndexList)); + RwIm3DEnd(); + } + } + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); +} + +void CBulletTraces::Update(void) +{ + for (int i = 0; i < NUMBULLETTRACES; i++) { + if (aTraces[i].m_bInUse) + aTraces[i].Update(); + } +} + +void CBulletTrace::Update(void) +{ + if (m_framesInUse == 0) { + m_framesInUse++; + return; + } + if (m_framesInUse > 60) { + m_bInUse = false; + return; + } + CVector diff = m_vecCurrentPos - m_vecTargetPos; + float remaining = diff.Magnitude(); + if (remaining > 0.8f) + m_vecCurrentPos = m_vecTargetPos + (remaining - 0.8f) / remaining * diff; + else + m_bInUse = false; + if (--m_lifeTime == 0) + m_bInUse = false; + m_framesInUse++; +} + +RpAtomic * +MarkerAtomicCB(RpAtomic *atomic, void *data) +{ + *(RpAtomic**)data = atomic; + return atomic; +} + +bool +C3dMarker::AddMarker(uint32 identifier, uint16 type, float fSize, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate) +{ + m_nIdentifier = identifier; + + m_Matrix.SetUnity(); + + RpAtomic *origAtomic; + origAtomic = nil; + RpClumpForAllAtomics(C3dMarkers::m_pRpClumpArray[type], MarkerAtomicCB, &origAtomic); + + RpAtomic *atomic = RpAtomicClone(origAtomic); + RwFrame *frame = RwFrameCreate(); + RpAtomicSetFrame(atomic, frame); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + + RpGeometry *geometry = RpAtomicGetGeometry(atomic); + RpGeometrySetFlags(geometry, RpGeometryGetFlags(geometry) | rpGEOMETRYMODULATEMATERIALCOLOR); + + m_pAtomic = atomic; + m_Matrix.Attach(RwFrameGetMatrix(RpAtomicGetFrame(m_pAtomic))); + m_pMaterial = RpGeometryGetMaterial(geometry, 0); + m_fSize = fSize; + m_fStdSize = m_fSize; + m_Color.red = r; + m_Color.green = g; + m_Color.blue = b; + m_Color.alpha = a; + m_nPulsePeriod = pulsePeriod; + m_fPulseFraction = pulseFraction; + m_nRotateRate = rotateRate; + m_nStartTime = CTimer::GetTimeInMilliseconds(); + m_nType = type; + return m_pAtomic != nil; +} + +void +C3dMarker::DeleteMarkerObject() +{ + RwFrame *frame; + + m_nIdentifier = 0; + m_nStartTime = 0; + m_bIsUsed = false; + m_nType = MARKERTYPE_INVALID; + + frame = RpAtomicGetFrame(m_pAtomic); + RpAtomicDestroy(m_pAtomic); + RwFrameDestroy(frame); + m_pAtomic = nil; +} + +void +C3dMarker::Render() +{ + if (m_pAtomic == nil) return; + + RpMaterialSetColor(m_pMaterial, &m_Color); + + m_Matrix.UpdateRW(); + + CMatrix matrix; + matrix.Attach(m_Matrix.m_attachment); + matrix.Scale(m_fSize); + matrix.UpdateRW(); + + RwFrameUpdateObjects(RpAtomicGetFrame(m_pAtomic)); + SetBrightMarkerColours(m_fBrightness); + if (m_nType != MARKERTYPE_ARROW) + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RpAtomicRender(m_pAtomic); + if (m_nType != MARKERTYPE_ARROW) + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + ReSetAmbientAndDirectionalColours(); +} + +C3dMarker C3dMarkers::m_aMarkerArray[NUM3DMARKERS]; +int32 C3dMarkers::NumActiveMarkers; +RpClump* C3dMarkers::m_pRpClumpArray[NUMMARKERTYPES]; + +void +C3dMarkers::Init() +{ + for (int i = 0; i < NUM3DMARKERS; i++) { + m_aMarkerArray[i].m_pAtomic = nil; + m_aMarkerArray[i].m_nType = MARKERTYPE_INVALID; + m_aMarkerArray[i].m_bIsUsed = false; + m_aMarkerArray[i].m_nIdentifier = 0; + m_aMarkerArray[i].m_Color.red = 255; + m_aMarkerArray[i].m_Color.green = 255; + m_aMarkerArray[i].m_Color.blue = 255; + m_aMarkerArray[i].m_Color.alpha = 255; + m_aMarkerArray[i].m_nPulsePeriod = 1024; + m_aMarkerArray[i].m_nRotateRate = 5; + m_aMarkerArray[i].m_nStartTime = 0; + m_aMarkerArray[i].m_fPulseFraction = 0.25f; + m_aMarkerArray[i].m_fStdSize = 1.0f; + m_aMarkerArray[i].m_fSize = 1.0f; + m_aMarkerArray[i].m_fBrightness = 1.0f; + m_aMarkerArray[i].m_fCameraRange = 0.0f; + } + NumActiveMarkers = 0; + int txdSlot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(txdSlot); + CFileMgr::ChangeDir("\\"); + m_pRpClumpArray[MARKERTYPE_ARROW] = CFileLoader::LoadAtomicFile2Return("models/generic/arrow.dff"); + m_pRpClumpArray[MARKERTYPE_CYLINDER] = CFileLoader::LoadAtomicFile2Return("models/generic/zonecylb.dff"); + CTxdStore::PopCurrentTxd(); +} + +void +C3dMarkers::Shutdown() +{ + for (int i = 0; i < NUM3DMARKERS; i++) { + if (m_aMarkerArray[i].m_pAtomic != nil) + m_aMarkerArray[i].DeleteMarkerObject(); + } + + for (int i = 0; i < NUMMARKERTYPES; i++) { + if (m_pRpClumpArray[i] != nil) + RpClumpDestroy(m_pRpClumpArray[i]); + } +} + +void +C3dMarkers::Render() +{ + NumActiveMarkers = 0; + ActivateDirectional(); + for (int i = 0; i < NUM3DMARKERS; i++) { + if (m_aMarkerArray[i].m_bIsUsed) { + if (m_aMarkerArray[i].m_fCameraRange < 120.0f) + m_aMarkerArray[i].Render(); + NumActiveMarkers++; + m_aMarkerArray[i].m_bIsUsed = false; + } else if (m_aMarkerArray[i].m_pAtomic != nil) { + m_aMarkerArray[i].DeleteMarkerObject(); + } + } +} + +C3dMarker * +C3dMarkers::PlaceMarker(uint32 identifier, uint16 type, CVector &pos, float size, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate) +{ + C3dMarker *pMarker; + + pMarker = nil; + float dist = Sqrt((pos.x - FindPlayerCentreOfWorld(0).x) * (pos.x - FindPlayerCentreOfWorld(0).x) + (pos.y - FindPlayerCentreOfWorld(0).y) * (pos.y - FindPlayerCentreOfWorld(0).y)); + + if (type != MARKERTYPE_ARROW && type != MARKERTYPE_CYLINDER) return nil; + + for (int i = 0; i < NUM3DMARKERS; i++) { + if (!m_aMarkerArray[i].m_bIsUsed && m_aMarkerArray[i].m_nIdentifier == identifier) { + pMarker = &m_aMarkerArray[i]; + break; + } + } + + if (pMarker == nil) { + for (int i = 0; i < NUM3DMARKERS; i++) { + if (m_aMarkerArray[i].m_nType == MARKERTYPE_INVALID) { + pMarker = &m_aMarkerArray[i]; + break; + } + } + } + + if (pMarker == nil && type == MARKERTYPE_ARROW) { + for (int i = 0; i < NUM3DMARKERS; i++) { + if (dist < m_aMarkerArray[i].m_fCameraRange && m_aMarkerArray[i].m_nType == MARKERTYPE_ARROW && (pMarker == nil || m_aMarkerArray[i].m_fCameraRange > pMarker->m_fCameraRange)) { + pMarker = &m_aMarkerArray[i]; + break; + } + } + + if (pMarker != nil) + pMarker->m_nType = MARKERTYPE_INVALID; + } + + if (pMarker == nil) return pMarker; + + pMarker->m_fCameraRange = dist; + if (pMarker->m_nIdentifier == identifier && pMarker->m_nType == type) { + if (type == MARKERTYPE_ARROW) { + if (dist < 25.0f) { + if (dist > 5.0f) + pMarker->m_fStdSize = size - (25.0f - dist) * (0.3f * size) / 20.0f; + else + pMarker->m_fStdSize = size - 0.3f * size; + } else { + pMarker->m_fStdSize = size; + } + } else if (type == MARKERTYPE_CYLINDER) { + if (dist < size + 12.0f) { + if (dist > size + 1.0f) + pMarker->m_Color.alpha = (1.0f - (size + 12.0f - dist) * 0.7f / 11.0f) * (float)a; + else + pMarker->m_Color.alpha = (float)a * 0.3f; + } else { + pMarker->m_Color.alpha = a; + } + } + float someSin = Sin(TWOPI * (float)((pMarker->m_nPulsePeriod - 1) & (CTimer::GetTimeInMilliseconds() - pMarker->m_nStartTime)) / (float)pMarker->m_nPulsePeriod); + pMarker->m_fSize = pMarker->m_fStdSize - pulseFraction * pMarker->m_fStdSize * someSin; + + if (type == MARKERTYPE_ARROW) { + pos.z += 0.25f * pMarker->m_fStdSize * someSin; + } else if (type == MARKERTYPE_0) { + if (someSin > 0.0f) + pMarker->m_Color.alpha = (float)a * 0.7f * someSin + a; + else + pMarker->m_Color.alpha = (float)a * 0.4f * someSin + a; + } + if (pMarker->m_nRotateRate) { + CVector pos = pMarker->m_Matrix.GetPosition(); + pMarker->m_Matrix.RotateZ(DEGTORAD(pMarker->m_nRotateRate * CTimer::GetTimeStep())); + pMarker->m_Matrix.GetPosition() = pos; + } + if (type == MARKERTYPE_ARROW) + pMarker->m_Matrix.GetPosition() = pos; + pMarker->m_bIsUsed = true; + return pMarker; + } + + if (pMarker->m_nIdentifier != 0) + pMarker->DeleteMarkerObject(); + + pMarker->AddMarker(identifier, type, size, r, g, b, a, pulsePeriod, pulseFraction, rotateRate); + if (type == MARKERTYPE_CYLINDER || type == MARKERTYPE_0 || type == MARKERTYPE_2) { + float z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 1.0f, nil); + if (z != 0.0f) + pos.z = z - 0.05f * size; + } + pMarker->m_Matrix.SetTranslate(pos.x, pos.y, pos.z); + if (type == MARKERTYPE_2) { + pMarker->m_Matrix.RotateX(PI); + pMarker->m_Matrix.GetPosition() = pos; + } + pMarker->m_Matrix.UpdateRW(); + if (type == MARKERTYPE_ARROW) { + if (dist < 25.0f) { + if (dist > 5.0f) + pMarker->m_fStdSize = size - (25.0f - dist) * (0.3f * size) / 20.0f; + else + pMarker->m_fStdSize = size - 0.3f * size; + } else { + pMarker->m_fStdSize = size; + } + } else if (type == MARKERTYPE_CYLINDER) { + if (dist < size + 12.0f) { + if (dist > size + 1.0f) + pMarker->m_Color.alpha = (1.0f - (size + 12.0f - dist) * 0.7f / 11.0f) * (float)a; + else + pMarker->m_Color.alpha = (float)a * 0.3f; + } else { + pMarker->m_Color.alpha = a; + } + } + pMarker->m_bIsUsed = true; + return pMarker; +} + +void +C3dMarkers::PlaceMarkerSet(uint32 id, uint16 type, CVector &pos, float size, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate) +{ + PlaceMarker(id, type, pos, size, r, g, b, a, pulsePeriod, pulseFraction, 1); + PlaceMarker(id, type, pos, size * 0.93f, r, g, b, a, pulsePeriod, pulseFraction, 2); + PlaceMarker(id, type, pos, size * 0.86f, r, g, b, a, pulsePeriod, pulseFraction, -1); +} + + +void +C3dMarkers::Update() +{ +} + + +#define BRIGHTLIGHTS_MAX_DIST (60.0f) // invisible beyond this +#define BRIGHTLIGHTS_FADE_DIST (45.0f) // strongest between these two +#define CARLIGHTS_MAX_DIST (30.0f) +#define CARLIGHTS_FADE_DIST (15.0f) // 31 for close lights + +int CBrightLights::NumBrightLights; +CBrightLight CBrightLights::aBrightLights[NUMBRIGHTLIGHTS]; + +void +CBrightLights::Init(void) +{ + NumBrightLights = 0; +} + +void +CBrightLights::RegisterOne(CVector pos, CVector up, CVector side, CVector front, + uint8 type, uint8 red, uint8 green, uint8 blue) +{ + if(NumBrightLights >= NUMBRIGHTLIGHTS) + return; + + aBrightLights[NumBrightLights].m_camDist = (pos - TheCamera.GetPosition()).Magnitude(); + if(aBrightLights[NumBrightLights].m_camDist > BRIGHTLIGHTS_MAX_DIST) + return; + + aBrightLights[NumBrightLights].m_pos = pos; + aBrightLights[NumBrightLights].m_up = up; + aBrightLights[NumBrightLights].m_side = side; + aBrightLights[NumBrightLights].m_front = front; + aBrightLights[NumBrightLights].m_type = type; + aBrightLights[NumBrightLights].m_red = red; + aBrightLights[NumBrightLights].m_green = green; + aBrightLights[NumBrightLights].m_blue = blue; + + NumBrightLights++; +} + +static float TrafficLightsSide[6] = { -0.09f, 0.09f, 0.162f, 0.09f, -0.09f, -0.162f }; +static float TrafficLightsUp[6] = { 0.162f, 0.162f, 0.0f, -0.162f, -0.162f, 0.0f }; +static float LongCarHeadLightsSide[8] = { -0.2f, 0.2f, -0.2f, 0.2f, -0.2f, 0.2f, -0.2f, 0.2f }; +static float LongCarHeadLightsFront[8] = { 0.1f, 0.1f, -0.1f, -0.1f, 0.1f, 0.1f, -0.1f, -0.1f }; +static float LongCarHeadLightsUp[8] = { 0.1f, 0.1f, 0.1f, 0.1f, -0.1f, -0.1f, -0.1f, -0.1f }; +static float SmallCarHeadLightsSide[8] = { -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f }; +static float SmallCarHeadLightsFront[8] = { 0.08f, 0.08f, -0.08f, -0.08f, 0.08f, 0.08f, -0.08f, -0.08f }; +static float SmallCarHeadLightsUp[8] = { 0.08f, 0.08f, 0.08f, 0.08f, -0.08f, -0.08f, -0.08f, -0.08f }; +static float BigCarHeadLightsSide[8] = { -0.15f, 0.15f, -0.15f, 0.15f, -0.15f, 0.15f, -0.15f, 0.15f }; +static float BigCarHeadLightsFront[8] = { 0.15f, 0.15f, -0.15f, -0.15f, 0.15f, 0.15f, -0.15f, -0.15f }; +static float BigCarHeadLightsUp[8] = { 0.15f, 0.15f, 0.15f, 0.15f, -0.15f, -0.15f, -0.15f, -0.15f }; +static float TallCarHeadLightsSide[8] = { -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f }; +static float TallCarHeadLightsFront[8] = { 0.08f, 0.08f, -0.08f, -0.08f, 0.08f, 0.08f, -0.08f, -0.08f }; +static float TallCarHeadLightsUp[8] = { 0.2f, 0.2f, 0.2f, 0.2f, -0.2f, -0.2f, -0.2f, -0.2f }; +static float SirenLightsSide[6] = { -0.04f, 0.04f, 0.06f, 0.04f, -0.04f, -0.06f }; +static float SirenLightsUp[6] = { 0.06f, 0.06f, 0.0f, -0.06f, -0.06f, 0.0f }; +static RwImVertexIndex TrafficLightIndices[4*3] = { 0, 1, 5, 1, 2, 3, 1, 3, 4, 1, 4, 5 }; +static RwImVertexIndex CubeIndices[12*3] = { + 0, 2, 1, 1, 2, 3, 3, 5, 1, 3, 7, 5, + 2, 7, 3, 2, 6, 7, 4, 0, 1, 4, 1, 5, + 6, 0, 4, 6, 2, 0, 6, 5, 7, 6, 4, 5 +}; + +void +CBrightLights::Render(void) +{ + int i, j; + CVector pos; + + if(NumBrightLights == 0) + return; + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + + for(i = 0; i < NumBrightLights; i++){ + if(TempBufferIndicesStored > TEMPBUFFERINDEXSIZE-40 || TempBufferVerticesStored > TEMPBUFFERVERTSIZE-40) + RenderOutGeometryBuffer(); + + int r, g, b, a; + float flicker = (CGeneral::GetRandomNumber()&0xFF) * 0.2f; + switch(aBrightLights[i].m_type){ + case BRIGHTLIGHT_TRAFFIC_GREEN: + r = flicker; g = 255; b = flicker; + break; + case BRIGHTLIGHT_TRAFFIC_YELLOW: + r = 255; g = 128; b = flicker; + break; + case BRIGHTLIGHT_TRAFFIC_RED: + r = 255; g = flicker; b = flicker; + break; + + case BRIGHTLIGHT_FRONT_LONG: + case BRIGHTLIGHT_FRONT_SMALL: + case BRIGHTLIGHT_FRONT_BIG: + case BRIGHTLIGHT_FRONT_TALL: + r = 255; g = 255; b = 255; + break; + + case BRIGHTLIGHT_REAR_LONG: + case BRIGHTLIGHT_REAR_SMALL: + case BRIGHTLIGHT_REAR_BIG: + case BRIGHTLIGHT_REAR_TALL: + r = 255; g = flicker; b = flicker; + break; + + case BRIGHTLIGHT_SIREN: + r = aBrightLights[i].m_red; + g = aBrightLights[i].m_green; + b = aBrightLights[i].m_blue; + break; + } + + if(aBrightLights[i].m_camDist < BRIGHTLIGHTS_FADE_DIST) + a = 255; + else + a = 255*(1.0f - (aBrightLights[i].m_camDist-BRIGHTLIGHTS_FADE_DIST)/(BRIGHTLIGHTS_MAX_DIST-BRIGHTLIGHTS_FADE_DIST)); + // fade car lights down to 31 as they come near + if(aBrightLights[i].m_type >= BRIGHTLIGHT_FRONT_LONG && aBrightLights[i].m_type <= BRIGHTLIGHT_REAR_TALL){ + if(aBrightLights[i].m_camDist < CARLIGHTS_FADE_DIST) + a = 31; + else if(aBrightLights[i].m_camDist < CARLIGHTS_MAX_DIST) + a = 31 + (255-31)*((aBrightLights[i].m_camDist-CARLIGHTS_FADE_DIST)/(CARLIGHTS_MAX_DIST-CARLIGHTS_FADE_DIST)); + } + + switch(aBrightLights[i].m_type){ + case BRIGHTLIGHT_TRAFFIC_GREEN: + case BRIGHTLIGHT_TRAFFIC_YELLOW: + case BRIGHTLIGHT_TRAFFIC_RED: + for(j = 0; j < 6; j++){ + pos = TrafficLightsSide[j]*aBrightLights[i].m_side + + TrafficLightsUp[j]*aBrightLights[i].m_up + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z); + } + for(j = 0; j < 4*3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored+j] = TrafficLightIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 6; + TempBufferIndicesStored += 4*3; + break; + + case BRIGHTLIGHT_FRONT_LONG: + case BRIGHTLIGHT_REAR_LONG: + for(j = 0; j < 8; j++){ + pos = LongCarHeadLightsSide[j]*aBrightLights[i].m_side + + LongCarHeadLightsUp[j]*aBrightLights[i].m_up + + LongCarHeadLightsFront[j]*aBrightLights[i].m_front + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z); + } + for(j = 0; j < 12*3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored+j] = CubeIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 8; + TempBufferIndicesStored += 12*3; + break; + + case BRIGHTLIGHT_FRONT_SMALL: + case BRIGHTLIGHT_REAR_SMALL: + for(j = 0; j < 8; j++){ + pos = SmallCarHeadLightsSide[j]*aBrightLights[i].m_side + + SmallCarHeadLightsUp[j]*aBrightLights[i].m_up + + SmallCarHeadLightsFront[j]*aBrightLights[i].m_front + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z); + } + for(j = 0; j < 12*3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored+j] = CubeIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 8; + TempBufferIndicesStored += 12*3; + break; + + case BRIGHTLIGHT_FRONT_BIG: + case BRIGHTLIGHT_REAR_BIG: + for (j = 0; j < 8; j++) { + pos = BigCarHeadLightsSide[j] * aBrightLights[i].m_side + + BigCarHeadLightsUp[j] * aBrightLights[i].m_up + + BigCarHeadLightsFront[j] * aBrightLights[i].m_front + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + j], pos.x, pos.y, pos.z); + } + for (j = 0; j < 12 * 3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored + j] = CubeIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 8; + TempBufferIndicesStored += 12 * 3; + break; + + case BRIGHTLIGHT_FRONT_TALL: + case BRIGHTLIGHT_REAR_TALL: + for(j = 0; j < 8; j++){ + pos = TallCarHeadLightsSide[j]*aBrightLights[i].m_side + + TallCarHeadLightsUp[j]*aBrightLights[i].m_up + + TallCarHeadLightsFront[j]*aBrightLights[i].m_front + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z); + } + for(j = 0; j < 12*3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored+j] = CubeIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 8; + TempBufferIndicesStored += 12*3; + break; + + case BRIGHTLIGHT_SIREN: + for(j = 0; j < 6; j++){ + pos = SirenLightsSide[j] * TheCamera.GetRight() + + SirenLightsUp[j] * TheCamera.GetUp() + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z); + } + for(j = 0; j < 4*3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored+j] = TrafficLightIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 6; + TempBufferIndicesStored += 4*3; + break; + + } + } + + RenderOutGeometryBuffer(); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + NumBrightLights = 0; +} + +void +CBrightLights::RenderOutGeometryBuffer(void) +{ + if(TempBufferIndicesStored != 0){ + LittleTest(); + if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + } +} + +int CShinyTexts::NumShinyTexts; +CShinyText CShinyTexts::aShinyTexts[NUMSHINYTEXTS]; + +void +CShinyTexts::Init(void) +{ + NumShinyTexts = 0; +} + +void +CShinyTexts::RegisterOne(CVector p0, CVector p1, CVector p2, CVector p3, + float u0, float v0, float u1, float v1, float u2, float v2, float u3, float v3, + uint8 type, uint8 red, uint8 green, uint8 blue, float maxDist) +{ + if(NumShinyTexts >= NUMSHINYTEXTS) + return; + + aShinyTexts[NumShinyTexts].m_camDist = (p0 - TheCamera.GetPosition()).Magnitude(); + if(aShinyTexts[NumShinyTexts].m_camDist > maxDist) + return; + aShinyTexts[NumShinyTexts].m_verts[0] = p0; + aShinyTexts[NumShinyTexts].m_verts[1] = p1; + aShinyTexts[NumShinyTexts].m_verts[2] = p2; + aShinyTexts[NumShinyTexts].m_verts[3] = p3; + aShinyTexts[NumShinyTexts].m_texCoords[0].x = u0; + aShinyTexts[NumShinyTexts].m_texCoords[0].y = v0; + aShinyTexts[NumShinyTexts].m_texCoords[1].x = u1; + aShinyTexts[NumShinyTexts].m_texCoords[1].y = v1; + aShinyTexts[NumShinyTexts].m_texCoords[2].x = u2; + aShinyTexts[NumShinyTexts].m_texCoords[2].y = v2; + aShinyTexts[NumShinyTexts].m_texCoords[3].x = u3; + aShinyTexts[NumShinyTexts].m_texCoords[3].y = v3; + aShinyTexts[NumShinyTexts].m_type = type; + aShinyTexts[NumShinyTexts].m_red = red; + aShinyTexts[NumShinyTexts].m_green = green; + aShinyTexts[NumShinyTexts].m_blue = blue; + // Fade out at half the max dist + float halfDist = maxDist*0.5f; + if(aShinyTexts[NumShinyTexts].m_camDist > halfDist){ + float f = 1.0f - (aShinyTexts[NumShinyTexts].m_camDist - halfDist)/halfDist; + aShinyTexts[NumShinyTexts].m_red *= f; + aShinyTexts[NumShinyTexts].m_green *= f; + aShinyTexts[NumShinyTexts].m_blue *= f; + } + + NumShinyTexts++; +} + +void +CShinyTexts::Render(void) +{ + int i, ix, v; + RwTexture *lastTex = nil; + + if(NumShinyTexts == 0) + return; + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + + for(i = 0; i < NumShinyTexts; i++){ + if(TempBufferIndicesStored > TEMPBUFFERINDEXSIZE-64 || TempBufferVerticesStored > TEMPBUFFERVERTSIZE-62) + RenderOutGeometryBuffer(); + + uint8 r = aShinyTexts[i].m_red; + uint8 g = aShinyTexts[i].m_green; + uint8 b = aShinyTexts[i].m_blue; + + switch(aShinyTexts[i].m_type){ + case SHINYTEXT_WALK: + if(lastTex != gpWalkDontTex){ + RenderOutGeometryBuffer(); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpWalkDontTex)); + lastTex = gpWalkDontTex; + } + quad: + v = TempBufferVerticesStored; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+0], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+0], aShinyTexts[i].m_verts[0].x, aShinyTexts[i].m_verts[0].y, aShinyTexts[i].m_verts[0].z); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+0], aShinyTexts[i].m_texCoords[0].x); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+0], aShinyTexts[i].m_texCoords[0].y); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+1], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+1], aShinyTexts[i].m_verts[1].x, aShinyTexts[i].m_verts[1].y, aShinyTexts[i].m_verts[1].z); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+1], aShinyTexts[i].m_texCoords[1].x); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+1], aShinyTexts[i].m_texCoords[1].y); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+2], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+2], aShinyTexts[i].m_verts[2].x, aShinyTexts[i].m_verts[2].y, aShinyTexts[i].m_verts[2].z); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+2], aShinyTexts[i].m_texCoords[2].x); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+2], aShinyTexts[i].m_texCoords[2].y); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+3], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+3], aShinyTexts[i].m_verts[3].x, aShinyTexts[i].m_verts[3].y, aShinyTexts[i].m_verts[3].z); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+3], aShinyTexts[i].m_texCoords[3].x); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+3], aShinyTexts[i].m_texCoords[3].y); + ix = TempBufferIndicesStored; + TempBufferRenderIndexList[ix+0] = 0 + TempBufferVerticesStored; + TempBufferRenderIndexList[ix+1] = 1 + TempBufferVerticesStored; + TempBufferRenderIndexList[ix+2] = 2 + TempBufferVerticesStored; + TempBufferRenderIndexList[ix+3] = 2 + TempBufferVerticesStored; + TempBufferRenderIndexList[ix+4] = 1 + TempBufferVerticesStored; + TempBufferRenderIndexList[ix+5] = 3 + TempBufferVerticesStored; + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; + break; + + case SHINYTEXT_FLAT: + if(lastTex != nil){ + RenderOutGeometryBuffer(); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + lastTex = nil; + } + goto quad; + } + } + + RenderOutGeometryBuffer(); + NumShinyTexts = 0; + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); +} + +void +CShinyTexts::RenderOutGeometryBuffer(void) +{ + if(TempBufferIndicesStored != 0){ + LittleTest(); + if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + } +} + + + +#define MONEY_MESSAGE_LIFETIME_MS 2000 + +CMoneyMessage CMoneyMessages::aMoneyMessages[NUMMONEYMESSAGES]; + +void +CMoneyMessage::Render() +{ + const float MAX_SCALE = 4.0f; + uint32 nLifeTime = CTimer::GetTimeInMilliseconds() - m_nTimeRegistered; + if (nLifeTime >= MONEY_MESSAGE_LIFETIME_MS) m_nTimeRegistered = 0; + else { + float fLifeTime = (float)nLifeTime / MONEY_MESSAGE_LIFETIME_MS; + RwV3d vecOut; + float fDistX, fDistY; + if (CSprite::CalcScreenCoors(m_vecPosition + CVector(0.0f, 0.0f, fLifeTime), &vecOut, &fDistX, &fDistY, true)) { + fDistX *= (0.7f * fLifeTime + 2.0f) * m_fSize; + fDistY *= (0.7f * fLifeTime + 2.0f) * m_fSize; + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + + float fScaleY = Min(fDistY / 100.0f, MAX_SCALE); + float fScaleX = Min(fDistX / 100.0f, MAX_SCALE); + +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(fScaleX), SCREEN_SCALE_Y(fScaleY)); +#else + CFont::SetScale(fScaleX, fScaleY); +#endif + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_WIDTH); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(m_Colour.r, m_Colour.g, m_Colour.b, (255.0f - 255.0f * fLifeTime) * m_fOpacity)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::PrintString(vecOut.x, vecOut.y, m_aText); + } + } +} + +void +CMoneyMessages::Init() +{ + for (int32 i = 0; i < NUMMONEYMESSAGES; i++) + aMoneyMessages[i].m_nTimeRegistered = 0; +} + +void +CMoneyMessages::Render() +{ + for (int32 i = 0; i < NUMMONEYMESSAGES; i++) { + if (aMoneyMessages[i].m_nTimeRegistered != 0) + aMoneyMessages[i].Render(); + } +} + +void +CMoneyMessages::RegisterOne(CVector vecPos, const char *pText, uint8 bRed, uint8 bGreen, uint8 bBlue, float fSize, float fOpacity) +{ + uint32 i; +#ifdef FIX_BUGS + for(i = 0; i < NUMMONEYMESSAGES && aMoneyMessages[i].m_nTimeRegistered != 0; i++); +#else + for(i = 0; aMoneyMessages[i].m_nTimeRegistered != 0 && i < NUMMONEYMESSAGES; i++); +#endif + + if(i < NUMMONEYMESSAGES) { + // Add data of this money message to the array + AsciiToUnicode(pText, aMoneyMessages[i].m_aText); + + aMoneyMessages[i].m_nTimeRegistered = CTimer::GetTimeInMilliseconds(); + aMoneyMessages[i].m_vecPosition = vecPos; + aMoneyMessages[i].m_Colour.red = bRed; + aMoneyMessages[i].m_Colour.green = bGreen; + aMoneyMessages[i].m_Colour.blue = bBlue; + aMoneyMessages[i].m_fSize = fSize; + aMoneyMessages[i].m_fOpacity = fOpacity; + } +} + +CRGBA FoamColour(255, 255, 255, 255); +uint32 CSpecialParticleStuff::BoatFromStart; + +void +CSpecialParticleStuff::CreateFoamAroundObject(CMatrix* pMatrix, float innerFw, float innerRg, float innerUp, int32 particles) +{ + float outerFw = innerFw + 5.0f; + float outerRg = innerRg + 5.0f; + float outerUp = innerUp + 5.0f; + for (int attempts = 0; particles > 0 && attempts < 1000; attempts++) { + CVector pos; + int rnd = CGeneral::GetRandomNumber(); + pos.x = (int8)(rnd - 128) * innerFw / 110.0f; + pos.y = (int8)((rnd >> 8) - 128) * innerFw / 110.0f; + pos.z = 0.0f; + if (DotProduct2D(pos, TheCamera.GetForward()) >= 0) + continue; + // was there any point in adding it here? + pos += pMatrix->GetPosition(); + pos.z = 2.0f; + float fw = Abs(DotProduct(pMatrix->GetForward(), pos - pMatrix->GetPosition())); + if (fw >= outerFw) + continue; + float rg = Abs(DotProduct(pMatrix->GetRight(), pos - pMatrix->GetPosition())); + if (rg >= outerRg) + continue; + float up = Abs(DotProduct(pMatrix->GetUp(), pos - pMatrix->GetPosition())); + if (up >= outerUp) + continue; + if (fw > innerFw || rg > innerRg || up > innerUp) { + CParticle::AddParticle(PARTICLE_STEAM2, pos, CVector(0.0f, 0.0f, 0.0f), nil, 4.0f, FoamColour, 1, 0, 0, 0); + particles--; + } + } +} + +void +CSpecialParticleStuff::StartBoatFoamAnimation() +{ + BoatFromStart = CTimer::GetTimeInMilliseconds(); +} + +void +CSpecialParticleStuff::UpdateBoatFoamAnimation(CMatrix* pMatrix) +{ + static int32 FrameInAnimation = 0; + static float X, Y, Z, dX, dY, dZ; + CreateFoamAroundObject(pMatrix, 107.0f, 24.1f, 30.5f, 2); + uint32 prev = CTimer::GetPreviousTimeInMilliseconds(); + uint32 cur = CTimer::GetTimeInMilliseconds(); + if (FrameInAnimation != 0) { + X += dX; + Y += dY; + Z += dZ; + CVector pos = *pMatrix * CVector(X, Y, Z); + CParticle::AddParticle(PARTICLE_STEAM_NY, pos, CVector(0.0f, 0.0f, 0.0f), + nil, FrameInAnimation * 0.5f + 2.0f, FoamColour, 1, 0, 0, 0); + if (++FrameInAnimation > 15) + FrameInAnimation = 0; + } + if ((cur & 0x3FF) < (prev & 0x3FF)) { + FrameInAnimation = 1; + int rnd = CGeneral::GetRandomNumber(); + X = (int8)(rnd - 128) * 0.2f; + Y = (int8)((rnd >> 8) - 128) * 0.2f; + Z = 10.0f; + rnd = CGeneral::GetRandomNumber(); + dX = (int8)(rnd - 128) * 0.02f; + dY = (int8)((rnd >> 8) - 128) * 0.02f; + dZ = 2.0f; + } +} diff --git a/src/renderer/SpecialFX.h b/src/renderer/SpecialFX.h new file mode 100644 index 0000000..2d9f18b --- /dev/null +++ b/src/renderer/SpecialFX.h @@ -0,0 +1,224 @@ +#pragma once + +class CSpecialFX +{ +public: + static void Render(void); + static void Update(void); + static void Init(void); + static void Shutdown(void); +}; + +class CRegisteredMotionBlurStreak +{ +public: + uintptr m_id; + uint8 m_red; + uint8 m_green; + uint8 m_blue; + CVector m_pos1[3]; + CVector m_pos2[3]; + bool m_isValid[3]; + + void Update(void); + void Render(void); +}; + +class CMotionBlurStreaks +{ + static CRegisteredMotionBlurStreak aStreaks[NUMMBLURSTREAKS]; +public: + static void Init(void); + static void Update(void); + static void RegisterStreak(uintptr id, uint8 r, uint8 g, uint8 b, CVector p1, CVector p2); + static void Render(void); +}; + +struct CBulletTrace +{ + CVector m_vecCurrentPos; + CVector m_vecTargetPos; + bool m_bInUse; + uint8 m_framesInUse; + uint8 m_lifeTime; + + void Update(void); +}; + +class CBulletTraces +{ +public: + static CBulletTrace aTraces[NUMBULLETTRACES]; + + static void Init(void); + static void AddTrace(CVector*, CVector*); + static void Render(void); + static void Update(void); +}; + +enum +{ + MARKERTYPE_0 = 0, + MARKERTYPE_ARROW, + MARKERTYPE_2, + MARKERTYPE_3, + MARKERTYPE_CYLINDER, + NUMMARKERTYPES, + + MARKERTYPE_INVALID = 0x101 +}; + + +class C3dMarker +{ +public: + CMatrix m_Matrix; + RpAtomic *m_pAtomic; + RpMaterial *m_pMaterial; + uint16 m_nType; + bool m_bIsUsed; + uint32 m_nIdentifier; + RwRGBA m_Color; + uint16 m_nPulsePeriod; + int16 m_nRotateRate; + uint32 m_nStartTime; + float m_fPulseFraction; + float m_fStdSize; + float m_fSize; + float m_fBrightness; + float m_fCameraRange; + + bool AddMarker(uint32 identifier, uint16 type, float fSize, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate); + void DeleteMarkerObject(); + void Render(); +}; + +class C3dMarkers +{ +public: + static void Init(); + static void Shutdown(); + static C3dMarker *PlaceMarker(uint32 id, uint16 type, CVector &pos, float size, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate); + static void PlaceMarkerSet(uint32 id, uint16 type, CVector &pos, float size, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate); + static void Render(); + static void Update(); + + static C3dMarker m_aMarkerArray[NUM3DMARKERS]; + static int32 NumActiveMarkers; + static RpClump* m_pRpClumpArray[NUMMARKERTYPES]; +}; + +enum +{ + BRIGHTLIGHT_INVALID, + BRIGHTLIGHT_TRAFFIC_GREEN, + BRIGHTLIGHT_TRAFFIC_YELLOW, + BRIGHTLIGHT_TRAFFIC_RED, + + // white + BRIGHTLIGHT_FRONT_LONG, + BRIGHTLIGHT_FRONT_SMALL, + BRIGHTLIGHT_FRONT_BIG, + BRIGHTLIGHT_FRONT_TALL, + + // red + BRIGHTLIGHT_REAR_LONG, + BRIGHTLIGHT_REAR_SMALL, + BRIGHTLIGHT_REAR_BIG, + BRIGHTLIGHT_REAR_TALL, + + BRIGHTLIGHT_SIREN, // unused + + BRIGHTLIGHT_FRONT = BRIGHTLIGHT_FRONT_LONG, + BRIGHTLIGHT_REAR = BRIGHTLIGHT_REAR_LONG, +}; + +class CBrightLight +{ +public: + CVector m_pos; + CVector m_up; + CVector m_side; + CVector m_front; + float m_camDist; + uint8 m_type; + uint8 m_red; + uint8 m_green; + uint8 m_blue; +}; + +class CBrightLights +{ + static int NumBrightLights; + static CBrightLight aBrightLights[NUMBRIGHTLIGHTS]; +public: + static void Init(void); + static void RegisterOne(CVector pos, CVector up, CVector side, CVector front, + uint8 type, uint8 red = 0, uint8 green = 0, uint8 blue = 0); + static void Render(void); + static void RenderOutGeometryBuffer(void); +}; + + +enum +{ + SHINYTEXT_WALK = 1, + SHINYTEXT_FLAT +}; + +class CShinyText +{ +public: + CVector m_verts[4]; + CVector2D m_texCoords[4]; + float m_camDist; + uint8 m_type; + uint8 m_red; + uint8 m_green; + uint8 m_blue; +}; + +class CShinyTexts +{ + static int NumShinyTexts; + static CShinyText aShinyTexts[NUMSHINYTEXTS]; +public: + static void Init(void); + static void RegisterOne(CVector p0, CVector p1, CVector p2, CVector p3, + float u0, float v0, float u1, float v1, float u2, float v2, float u3, float v3, + uint8 type, uint8 red, uint8 green, uint8 blue, float maxDist); + static void Render(void); + static void RenderOutGeometryBuffer(void); +}; + +class CMoneyMessage +{ + friend class CMoneyMessages; + + uint32 m_nTimeRegistered; + CVector m_vecPosition; + wchar m_aText[16]; + CRGBA m_Colour; + float m_fSize; + float m_fOpacity; +public: + void Render(); +}; + +class CMoneyMessages +{ + static CMoneyMessage aMoneyMessages[NUMMONEYMESSAGES]; +public: + static void Init(); + static void Render(); + static void RegisterOne(CVector vecPos, const char *pText, uint8 bRed, uint8 bGreen, uint8 bBlue, float fSize, float fOpacity); +}; + +class CSpecialParticleStuff +{ + static uint32 BoatFromStart; +public: + static void CreateFoamAroundObject(CMatrix*, float, float, float, int32); + static void StartBoatFoamAnimation(); + static void UpdateBoatFoamAnimation(CMatrix*); +}; diff --git a/src/renderer/Sprite.cpp b/src/renderer/Sprite.cpp new file mode 100644 index 0000000..3fef073 --- /dev/null +++ b/src/renderer/Sprite.cpp @@ -0,0 +1,603 @@ +#include "common.h" + +#include "main.h" +#include "Draw.h" +#include "Camera.h" +#include "Sprite.h" + +#ifdef ASPECT_RATIO_SCALE +#include "Frontend.h" +#endif + +float CSprite::m_f2DNearScreenZ; +float CSprite::m_f2DFarScreenZ; +float CSprite::m_fRecipNearClipPlane; +int32 CSprite::m_bFlushSpriteBufferSwitchZTest; + +float +CSprite::CalcHorizonCoors(void) +{ + CVector p = TheCamera.GetPosition() + CVector(TheCamera.CamFrontXNorm, TheCamera.CamFrontYNorm, 0.0f)*3000.0f; + p.z = 0.0f; + p = TheCamera.m_viewMatrix * p; + return p.y * SCREEN_HEIGHT / p.z; +} + +bool +CSprite::CalcScreenCoors(const RwV3d &in, RwV3d *out, float *outw, float *outh, bool farclip) +{ + CVector viewvec = TheCamera.m_viewMatrix * in; + *out = viewvec; + if(out->z <= CDraw::GetNearClipZ() + 1.0f) return false; + if(out->z >= CDraw::GetFarClipZ() && farclip) return false; + float recip = 1.0f/out->z; + out->x *= SCREEN_WIDTH * recip; + out->y *= SCREEN_HEIGHT * recip; + const float fov = DefaultFOV; + // this is used to scale correctly if you zoom in with sniper rifle + float fovScale = fov / CDraw::GetFOV(); + +#ifdef FIX_SPRITES + *outw = CDraw::ms_bFixSprites ? (fovScale * recip * SCREEN_HEIGHT) : (fovScale * SCREEN_SCALE_AR(recip) * SCREEN_WIDTH); +#else + *outw = fovScale * SCREEN_SCALE_AR(recip) * SCREEN_WIDTH; +#endif + *outh = fovScale * recip * SCREEN_HEIGHT; + + return true; +} + +#define SPRITEBUFFERSIZE 64 +static int32 nSpriteBufferIndex; +static RwIm2DVertex SpriteBufferVerts[SPRITEBUFFERSIZE*6]; +static RwIm2DVertex verts[4]; + +void +CSprite::InitSpriteBuffer(void) +{ + m_f2DNearScreenZ = RwIm2DGetNearScreenZ(); + m_f2DFarScreenZ = RwIm2DGetFarScreenZ(); +} + +void +CSprite::InitSpriteBuffer2D(void) +{ + m_fRecipNearClipPlane = 1.0f / RwCameraGetNearClipPlane(Scene.camera); + InitSpriteBuffer(); +} + +void +CSprite::FlushSpriteBuffer(void) +{ + if(nSpriteBufferIndex > 0){ + if(m_bFlushSpriteBufferSwitchZTest){ + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwIm2DRenderPrimitive(rwPRIMTYPETRILIST, SpriteBufferVerts, nSpriteBufferIndex*6); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + }else + RwIm2DRenderPrimitive(rwPRIMTYPETRILIST, SpriteBufferVerts, nSpriteBufferIndex*6); + nSpriteBufferIndex = 0; + } +} + +void +CSprite::RenderOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a) +{ + static short indices[] = { 0, 1, 2, 3 }; + // 0---3 + // | | + // 1---2 + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x-w; us[0] = 0.0f; + xs[1] = x-w; us[1] = 0.0f; + xs[2] = x+w; us[2] = 1.0f; + xs[3] = x+w; us[3] = 1.0f; + + ys[0] = y-h; vs[0] = 0.0f; + ys[1] = y+h; vs[1] = 1.0f; + ys[2] = y+h; vs[2] = 1.0f; + ys[3] = y-h; vs[3] = 0.0f; + + // clip + for(i = 0; i < 4; i++){ + if(xs[i] < 0.0f){ + us[i] = -xs[i] / (2.0f*w); + xs[i] = 0.0f; + } + if(xs[i] > SCREEN_WIDTH){ + us[i] = 1.0f - (xs[i]-SCREEN_WIDTH) / (2.0f*w); + xs[i] = SCREEN_WIDTH; + } + if(ys[i] < 0.0f){ + vs[i] = -ys[i] / (2.0f*h); + ys[i] = 0.0f; + } + if(ys[i] > SCREEN_HEIGHT){ + vs[i] = 1.0f - (ys[i]-SCREEN_HEIGHT) / (2.0f*h); + ys[i] = SCREEN_HEIGHT; + } + } + + // (DrawZ - DrawNear)/(DrawFar - DrawNear) = (SpriteZ-SpriteNear)/(SpriteFar-SpriteNear) + // So to calculate SpriteZ: + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + for(i = 0; i < 4; i++){ + RwIm2DVertexSetScreenX(&verts[i], xs[i]); + RwIm2DVertexSetScreenY(&verts[i], ys[i]); + RwIm2DVertexSetScreenZ(&verts[i], screenz); + RwIm2DVertexSetCameraZ(&verts[i], z); + RwIm2DVertexSetRecipCameraZ(&verts[i], recipz); + RwIm2DVertexSetIntRGBA(&verts[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&verts[i], us[i], recipz); + RwIm2DVertexSetV(&verts[i], vs[i], recipz); + } + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, verts, 4); +} + +void +CSprite::RenderOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float rotation, uint8 a) +{ + float c = Cos(rotation); + float s = Sin(rotation); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + // Fade out when too near + // why not in buffered version? + if(z < 3.0f){ + if(z < 1.5f) + return; + int f = (z - 1.5f)/1.5f * 255; + r = f*r >> 8; + g = f*g >> 8; + b = f*b >> 8; + intens = f*intens >> 8; + } + + xs[0] = x + w*(-c-s); us[0] = 0.0f; + xs[1] = x + w*(-c+s); us[1] = 0.0f; + xs[2] = x + w*(+c+s); us[2] = 1.0f; + xs[3] = x + w*(+c-s); us[3] = 1.0f; + + ys[0] = y + h*(-c+s); vs[0] = 0.0f; + ys[1] = y + h*(+c+s); vs[1] = 1.0f; + ys[2] = y + h*(+c-s); vs[2] = 1.0f; + ys[3] = y + h*(-c-s); vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > SCREEN_WIDTH && xs[1] > SCREEN_WIDTH && + xs[2] > SCREEN_WIDTH && xs[3] > SCREEN_WIDTH) return; + if(ys[0] > SCREEN_HEIGHT && ys[1] > SCREEN_HEIGHT && + ys[2] > SCREEN_HEIGHT && ys[3] > SCREEN_HEIGHT) return; + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + for(i = 0; i < 4; i++){ + RwIm2DVertexSetScreenX(&verts[i], xs[i]); + RwIm2DVertexSetScreenY(&verts[i], ys[i]); + RwIm2DVertexSetScreenZ(&verts[i], screenz); + RwIm2DVertexSetCameraZ(&verts[i], z); + RwIm2DVertexSetRecipCameraZ(&verts[i], recipz); + RwIm2DVertexSetIntRGBA(&verts[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&verts[i], us[i], recipz); + RwIm2DVertexSetV(&verts[i], vs[i], recipz); + } + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, verts, 4); +} + +void +CSprite::RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + + // 0---3 + // | | + // 1---2 + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x-w; us[0] = 0.0f; + xs[1] = x-w; us[1] = 0.0f; + xs[2] = x+w; us[2] = 1.0f; + xs[3] = x+w; us[3] = 1.0f; + + ys[0] = y-h; vs[0] = 0.0f; + ys[1] = y+h; vs[1] = 1.0f; + ys[2] = y+h; vs[2] = 1.0f; + ys[3] = y-h; vs[3] = 0.0f; + + // clip + for(i = 0; i < 4; i++){ + if(xs[i] < 0.0f){ + us[i] = -xs[i] / (2.0f*w); + xs[i] = 0.0f; + } + if(xs[i] > SCREEN_WIDTH){ + us[i] = 1.0f - (xs[i]-SCREEN_WIDTH) / (2.0f*w); + xs[i] = SCREEN_WIDTH; + } + if(ys[i] < 0.0f){ + vs[i] = -ys[i] / (2.0f*h); + ys[i] = 0.0f; + } + if(ys[i] > SCREEN_HEIGHT){ + vs[i] = 1.0f - (ys[i]-SCREEN_HEIGHT) / (2.0f*h); + ys[i] = SCREEN_HEIGHT; + } + } + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float rotation, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + // TODO: replace with lookup + float c = Cos(DEGTORAD(rotation)); + float s = Sin(DEGTORAD(rotation)); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x - c*w - s*h; us[0] = 0.0f; + xs[1] = x - c*w + s*h; us[1] = 0.0f; + xs[2] = x + c*w + s*h; us[2] = 1.0f; + xs[3] = x + c*w - s*h; us[3] = 1.0f; + + ys[0] = y - c*h + s*w; vs[0] = 0.0f; + ys[1] = y + c*h + s*w; vs[1] = 1.0f; + ys[2] = y + c*h - s*w; vs[2] = 1.0f; + ys[3] = y - c*h - s*w; vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > SCREEN_WIDTH && xs[1] > SCREEN_WIDTH && + xs[2] > SCREEN_WIDTH && xs[3] > SCREEN_WIDTH) return; + if(ys[0] > SCREEN_HEIGHT && ys[1] > SCREEN_HEIGHT && + ys[2] > SCREEN_HEIGHT && ys[3] > SCREEN_HEIGHT) return; + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float rotation, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + float c = Cos(rotation); + float s = Sin(rotation); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x + w*(-c-s); us[0] = 0.0f; + xs[1] = x + w*(-c+s); us[1] = 0.0f; + xs[2] = x + w*(+c+s); us[2] = 1.0f; + xs[3] = x + w*(+c-s); us[3] = 1.0f; + + ys[0] = y + h*(-c+s); vs[0] = 0.0f; + ys[1] = y + h*(+c+s); vs[1] = 1.0f; + ys[2] = y + h*(+c-s); vs[2] = 1.0f; + ys[3] = y + h*(-c-s); vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > SCREEN_WIDTH && xs[1] > SCREEN_WIDTH && + xs[2] > SCREEN_WIDTH && xs[3] > SCREEN_WIDTH) return; + if(ys[0] > SCREEN_HEIGHT && ys[1] > SCREEN_HEIGHT && + ys[2] > SCREEN_HEIGHT && ys[3] > SCREEN_HEIGHT) return; + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours(float x, float y, float z, float w, float h, uint8 r1, uint8 g1, uint8 b1, uint8 r2, uint8 g2, uint8 b2, float cx, float cy, float recipz, float rotation, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + float c = Cos(rotation); + float s = Sin(rotation); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + float cf[4]; + int i; + + xs[0] = x + w*(-c-s); us[0] = 0.0f; + xs[1] = x + w*(-c+s); us[1] = 0.0f; + xs[2] = x + w*(+c+s); us[2] = 1.0f; + xs[3] = x + w*(+c-s); us[3] = 1.0f; + + ys[0] = y + h*(-c+s); vs[0] = 0.0f; + ys[1] = y + h*(+c+s); vs[1] = 1.0f; + ys[2] = y + h*(+c-s); vs[2] = 1.0f; + ys[3] = y + h*(-c-s); vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > SCREEN_WIDTH && xs[1] > SCREEN_WIDTH && + xs[2] > SCREEN_WIDTH && xs[3] > SCREEN_WIDTH) return; + if(ys[0] > SCREEN_HEIGHT && ys[1] > SCREEN_HEIGHT && + ys[2] > SCREEN_HEIGHT && ys[3] > SCREEN_HEIGHT) return; + + // Colour factors, cx/y is the direction in which colours change from rgb1 to rgb2 + cf[0] = (cx*(-c-s) + cy*(-c+s))*0.5f + 0.5f; + cf[0] = Clamp(cf[0], 0.0f, 1.0f); + cf[1] = (cx*(-c+s) + cy*( c+s))*0.5f + 0.5f; + cf[1] = Clamp(cf[1], 0.0f, 1.0f); + cf[2] = (cx*( c+s) + cy*( c-s))*0.5f + 0.5f; + cf[2] = Clamp(cf[2], 0.0f, 1.0f); + cf[3] = (cx*( c-s) + cy*(-c-s))*0.5f + 0.5f; + cf[3] = Clamp(cf[3], 0.0f, 1.0f); + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], + r1*cf[indices[i]] + r2*(1.0f - cf[indices[i]]), + g1*cf[indices[i]] + g2*(1.0f - cf[indices[i]]), + b1*cf[indices[i]] + b2*(1.0f - cf[indices[i]]), + a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::Set6Vertices2D(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + float screenz, recipz; + float z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + screenz = m_f2DNearScreenZ; + recipz = m_fRecipNearClipPlane; + + RwIm2DVertexSetScreenX(&verts[0], r.left); + RwIm2DVertexSetScreenY(&verts[0], r.top); + RwIm2DVertexSetScreenZ(&verts[0], screenz); + RwIm2DVertexSetCameraZ(&verts[0], z); + RwIm2DVertexSetRecipCameraZ(&verts[0], recipz); + RwIm2DVertexSetIntRGBA(&verts[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[0], 0.0f, recipz); + RwIm2DVertexSetV(&verts[0], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[1], r.right); + RwIm2DVertexSetScreenY(&verts[1], r.top); + RwIm2DVertexSetScreenZ(&verts[1], screenz); + RwIm2DVertexSetCameraZ(&verts[1], z); + RwIm2DVertexSetRecipCameraZ(&verts[1], recipz); + RwIm2DVertexSetIntRGBA(&verts[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&verts[1], 1.0f, recipz); + RwIm2DVertexSetV(&verts[1], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[2], r.right); + RwIm2DVertexSetScreenY(&verts[2], r.bottom); + RwIm2DVertexSetScreenZ(&verts[2], screenz); + RwIm2DVertexSetCameraZ(&verts[2], z); + RwIm2DVertexSetRecipCameraZ(&verts[2], recipz); + RwIm2DVertexSetIntRGBA(&verts[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[2], 1.0f, recipz); + RwIm2DVertexSetV(&verts[2], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[3], r.left); + RwIm2DVertexSetScreenY(&verts[3], r.bottom); + RwIm2DVertexSetScreenZ(&verts[3], screenz); + RwIm2DVertexSetCameraZ(&verts[3], z); + RwIm2DVertexSetRecipCameraZ(&verts[3], recipz); + RwIm2DVertexSetIntRGBA(&verts[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&verts[3], 0.0f, recipz); + RwIm2DVertexSetV(&verts[3], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[4], r.left); + RwIm2DVertexSetScreenY(&verts[4], r.top); + RwIm2DVertexSetScreenZ(&verts[4], screenz); + RwIm2DVertexSetCameraZ(&verts[4], z); + RwIm2DVertexSetRecipCameraZ(&verts[4], recipz); + RwIm2DVertexSetIntRGBA(&verts[4], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[4], 0.0f, recipz); + RwIm2DVertexSetV(&verts[4], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[5], r.right); + RwIm2DVertexSetScreenY(&verts[5], r.bottom); + RwIm2DVertexSetScreenZ(&verts[5], screenz); + RwIm2DVertexSetCameraZ(&verts[5], z); + RwIm2DVertexSetRecipCameraZ(&verts[5], recipz); + RwIm2DVertexSetIntRGBA(&verts[5], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[5], 1.0f, recipz); + RwIm2DVertexSetV(&verts[5], 1.0f, recipz); +} + +void +CSprite::Set6Vertices2D(RwIm2DVertex *verts, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + float screenz, recipz; + float z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + screenz = m_f2DNearScreenZ; + recipz = m_fRecipNearClipPlane; + + RwIm2DVertexSetScreenX(&verts[0], x3); + RwIm2DVertexSetScreenY(&verts[0], y3); + RwIm2DVertexSetScreenZ(&verts[0], screenz); + RwIm2DVertexSetCameraZ(&verts[0], z); + RwIm2DVertexSetRecipCameraZ(&verts[0], recipz); + RwIm2DVertexSetIntRGBA(&verts[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[0], 0.0f, recipz); + RwIm2DVertexSetV(&verts[0], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[1], x4); + RwIm2DVertexSetScreenY(&verts[1], y4); + RwIm2DVertexSetScreenZ(&verts[1], screenz); + RwIm2DVertexSetCameraZ(&verts[1], z); + RwIm2DVertexSetRecipCameraZ(&verts[1], recipz); + RwIm2DVertexSetIntRGBA(&verts[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&verts[1], 1.0f, recipz); + RwIm2DVertexSetV(&verts[1], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[2], x2); + RwIm2DVertexSetScreenY(&verts[2], y2); + RwIm2DVertexSetScreenZ(&verts[2], screenz); + RwIm2DVertexSetCameraZ(&verts[2], z); + RwIm2DVertexSetRecipCameraZ(&verts[2], recipz); + RwIm2DVertexSetIntRGBA(&verts[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[2], 1.0f, recipz); + RwIm2DVertexSetV(&verts[2], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[3], x1); + RwIm2DVertexSetScreenY(&verts[3], y1); + RwIm2DVertexSetScreenZ(&verts[3], screenz); + RwIm2DVertexSetCameraZ(&verts[3], z); + RwIm2DVertexSetRecipCameraZ(&verts[3], recipz); + RwIm2DVertexSetIntRGBA(&verts[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&verts[3], 0.0f, recipz); + RwIm2DVertexSetV(&verts[3], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[4], x3); + RwIm2DVertexSetScreenY(&verts[4], y3); + RwIm2DVertexSetScreenZ(&verts[4], screenz); + RwIm2DVertexSetCameraZ(&verts[4], z); + RwIm2DVertexSetRecipCameraZ(&verts[4], recipz); + RwIm2DVertexSetIntRGBA(&verts[4], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[4], 0.0f, recipz); + RwIm2DVertexSetV(&verts[4], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[5], x2); + RwIm2DVertexSetScreenY(&verts[5], y2); + RwIm2DVertexSetScreenZ(&verts[5], screenz); + RwIm2DVertexSetCameraZ(&verts[5], z); + RwIm2DVertexSetRecipCameraZ(&verts[5], recipz); + RwIm2DVertexSetIntRGBA(&verts[5], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[5], 1.0f, recipz); + RwIm2DVertexSetV(&verts[5], 1.0f, recipz); +} + +void +CSprite::RenderBufferedOneXLUSprite2D(float x, float y, float w, float h, const RwRGBA &colour, int16 intens, uint8 alpha) +{ + m_bFlushSpriteBufferSwitchZTest = 1; + CRGBA col(intens * colour.red >> 8, intens * colour.green >> 8, intens * colour.blue >> 8, alpha); + CRect rect(x - w, y - h, x + h, y + h); + Set6Vertices2D(&SpriteBufferVerts[6 * nSpriteBufferIndex], rect, col, col, col, col); + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite2D_Rotate_Dimension(float x, float y, float w, float h, const RwRGBA &colour, int16 intens, float rotation, uint8 alpha) +{ + m_bFlushSpriteBufferSwitchZTest = 1; + CRGBA col(intens * colour.red >> 8, intens * colour.green >> 8, intens * colour.blue >> 8, alpha); + float c = Cos(DEGTORAD(rotation)); + float s = Sin(DEGTORAD(rotation)); + + Set6Vertices2D(&SpriteBufferVerts[6 * nSpriteBufferIndex], + x + c*w - s*h, + y - c*h - s*w, + x + c*w + s*h, + y + c*h - s*w, + x - c*w - s*h, + y - c*h + s*w, + x - c*w + s*h, + y + c*h + s*w, + col, col, col, col); + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} diff --git a/src/renderer/Sprite.h b/src/renderer/Sprite.h new file mode 100644 index 0000000..ec4c1d1 --- /dev/null +++ b/src/renderer/Sprite.h @@ -0,0 +1,28 @@ +#pragma once + +class CSprite +{ + static float m_f2DNearScreenZ; + static float m_f2DFarScreenZ; + static float m_fRecipNearClipPlane; + static int32 m_bFlushSpriteBufferSwitchZTest; +public: + static float CalcHorizonCoors(void); + static bool CalcScreenCoors(const RwV3d &in, RwV3d *out, float *outw, float *outh, bool farclip); + static void InitSpriteBuffer(void); + static void InitSpriteBuffer2D(void); + static void FlushSpriteBuffer(void); + static void RenderOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a); + static void RenderOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float roll, uint8 a); + static void RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a); + static void RenderBufferedOneXLUSprite_Rotate_Dimension(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float roll, uint8 a); + static void RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float roll, uint8 a); + // cx/y is the direction in which the colour changes + static void RenderBufferedOneXLUSprite_Rotate_2Colours(float x, float y, float z, float w, float h, uint8 r1, uint8 g1, uint8 b1, uint8 r2, uint8 g2, uint8 b2, float cx, float cy, float recipz, float rotation, uint8 a); + static void Set6Vertices2D(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void Set6Vertices2D(RwIm2DVertex *verts, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void RenderBufferedOneXLUSprite2D(float x, float y, float w, float h, const RwRGBA &colour, int16 intens, uint8 alpha); + static void RenderBufferedOneXLUSprite2D_Rotate_Dimension(float x, float y, float w, float h, const RwRGBA &colour, int16 intens, float rotation, uint8 alpha); + +}; diff --git a/src/renderer/Sprite2d.cpp b/src/renderer/Sprite2d.cpp new file mode 100644 index 0000000..5962251 --- /dev/null +++ b/src/renderer/Sprite2d.cpp @@ -0,0 +1,490 @@ +#include "common.h" + +#include "main.h" +#include "Draw.h" +#include "Camera.h" +#include "Sprite2d.h" +#include "Font.h" + +RwIm2DVertex CSprite2d::maVertices[8]; +float CSprite2d::RecipNearClip; +int32 CSprite2d::mCurrentBank; +RwTexture *CSprite2d::mpBankTextures[10]; +int32 CSprite2d::mCurrentSprite[10]; +int32 CSprite2d::mBankStart[10]; +RwIm2DVertex CSprite2d::maBankVertices[500]; + +void +CSprite2d::SetRecipNearClip(void) +{ + RecipNearClip = 1.0f / RwCameraGetNearClipPlane(Scene.camera); +} + +void +CSprite2d::InitPerFrame(void) +{ + int i; + + mCurrentBank = 0; + for(i = 0; i < 10; i++) + mCurrentSprite[i] = 0; +#ifndef SQUEEZE_PERFORMANCE + for(i = 0; i < 10; i++) + mpBankTextures[i] = nil; +#endif +} + +int32 +CSprite2d::GetBank(int32 n, RwTexture *tex) +{ +#ifndef SQUEEZE_PERFORMANCE + mpBankTextures[mCurrentBank] = tex; +#endif + mCurrentSprite[mCurrentBank] = 0; + mBankStart[mCurrentBank+1] = mBankStart[mCurrentBank] + n; + return mCurrentBank++; +} + +void +CSprite2d::AddSpriteToBank(int32 bank, const CRect &rect, const CRGBA &col, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2) +{ + SetVertices(&maBankVertices[6 * (mCurrentSprite[bank] + mBankStart[bank])], + rect, col, col, col, col, + u0, v0, u1, v1, u2, v2, u3, v3); + mCurrentSprite[bank]++; + if(mCurrentSprite[bank] + mBankStart[bank] >= mBankStart[bank+1]){ + DrawBank(bank); + mCurrentSprite[bank] = 0; + } +} + +void +CSprite2d::DrawBank(int32 bank) +{ + if(mCurrentSprite[bank] == 0) + return; +#ifndef SQUEEZE_PERFORMANCE + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, + mpBankTextures[bank] ? RwTextureGetRaster(mpBankTextures[bank]) : nil); +#else + CFont::Sprite[bank].SetRenderState(); +#endif + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwIm2DRenderPrimitive(rwPRIMTYPETRILIST, &maBankVertices[6*mBankStart[bank]], 6*mCurrentSprite[bank]); + mCurrentSprite[bank] = 0; + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); +} + + +void +CSprite2d::Delete(void) +{ + if(m_pTexture){ + RwTextureDestroy(m_pTexture); + m_pTexture = nil; + } +} + +void +CSprite2d::SetTexture(const char *name) +{ + Delete(); + if(name) + m_pTexture = RwTextureRead(name, nil); +} + +void +CSprite2d::SetTexture(const char *name, const char *mask) +{ + Delete(); + if(name) + m_pTexture = RwTextureRead(name, mask); +} + +void +CSprite2d::SetAddressing(RwTextureAddressMode addr) +{ + if(m_pTexture) + RwTextureSetAddressing(m_pTexture, addr); +} + +void +CSprite2d::SetRenderState(void) +{ + if(m_pTexture) + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(m_pTexture)); + else + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); +} + +void +CSprite2d::Draw(float x, float y, float w, float h, const CRGBA &col) +{ + SetVertices(CRect(x, y, x + w, y + h), col, col, col, col, 0); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + +void +CSprite2d::Draw(const CRect &rect, const CRGBA &col) +{ + SetVertices(rect, col, col, col, col, 0); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + +void +CSprite2d::Draw(const CRect &rect, const CRGBA &col, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2) +{ + SetVertices(rect, col, col, col, col, u0, v0, u1, v1, u3, v3, u2, v2); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + +void +CSprite2d::Draw(const CRect &rect, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + SetVertices(rect, c0, c1, c2, c3, 0); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + +void +CSprite2d::Draw(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, const CRGBA &col) +{ + SetVertices(x1, y1, x2, y2, x3, y3, x4, y4, col, col, col, col); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + + +// Arguments: +// 2---3 +// | | +// 0---1 +void +CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, uint32 far) +{ + float screenz, z, recipz; + + if(far){ + screenz = RwIm2DGetFarScreenZ(); + z = RwCameraGetFarClipPlane(Scene.camera); + }else{ + screenz = RwIm2DGetNearScreenZ(); + z = 1.0f/RecipNearClip; + } + recipz = 1.0f/z; + float offset = 1.0f/1024.0f; + + // This is what we draw: + // 0---1 + // | / | + // 3---2 + RwIm2DVertexSetScreenX(&maVertices[0], r.left); + RwIm2DVertexSetScreenY(&maVertices[0], r.top); + RwIm2DVertexSetScreenZ(&maVertices[0], screenz); + RwIm2DVertexSetCameraZ(&maVertices[0], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], 0.0f+offset, recipz); + RwIm2DVertexSetV(&maVertices[0], 0.0f+offset, recipz); + + RwIm2DVertexSetScreenX(&maVertices[1], r.right); + RwIm2DVertexSetScreenY(&maVertices[1], r.top); + RwIm2DVertexSetScreenZ(&maVertices[1], screenz); + RwIm2DVertexSetCameraZ(&maVertices[1], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], 1.0f+offset, recipz); + RwIm2DVertexSetV(&maVertices[1], 0.0f+offset, recipz); + + RwIm2DVertexSetScreenX(&maVertices[2], r.right); + RwIm2DVertexSetScreenY(&maVertices[2], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[2], screenz); + RwIm2DVertexSetCameraZ(&maVertices[2], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], 1.0f+offset, recipz); + RwIm2DVertexSetV(&maVertices[2], 1.0f+offset, recipz); + + RwIm2DVertexSetScreenX(&maVertices[3], r.left); + RwIm2DVertexSetScreenY(&maVertices[3], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[3], screenz); + RwIm2DVertexSetCameraZ(&maVertices[3], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], 0.0f+offset, recipz); + RwIm2DVertexSetV(&maVertices[3], 1.0f+offset, recipz); +} + +void +CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2) +{ + float screenz, z, recipz; + + screenz = RwIm2DGetNearScreenZ(); + z = 1.0f/RecipNearClip; + recipz = 1.0f/z; + + // This is what we draw: + // 0---1 + // | / | + // 3---2 + RwIm2DVertexSetScreenX(&maVertices[0], r.left); + RwIm2DVertexSetScreenY(&maVertices[0], r.top); + RwIm2DVertexSetScreenZ(&maVertices[0], screenz); + RwIm2DVertexSetCameraZ(&maVertices[0], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], u0, recipz); + RwIm2DVertexSetV(&maVertices[0], v0, recipz); + + RwIm2DVertexSetScreenX(&maVertices[1], r.right); + RwIm2DVertexSetScreenY(&maVertices[1], r.top); + RwIm2DVertexSetScreenZ(&maVertices[1], screenz); + RwIm2DVertexSetCameraZ(&maVertices[1], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], u1, recipz); + RwIm2DVertexSetV(&maVertices[1], v1, recipz); + + RwIm2DVertexSetScreenX(&maVertices[2], r.right); + RwIm2DVertexSetScreenY(&maVertices[2], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[2], screenz); + RwIm2DVertexSetCameraZ(&maVertices[2], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], u2, recipz); + RwIm2DVertexSetV(&maVertices[2], v2, recipz); + + RwIm2DVertexSetScreenX(&maVertices[3], r.left); + RwIm2DVertexSetScreenY(&maVertices[3], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[3], screenz); + RwIm2DVertexSetCameraZ(&maVertices[3], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], u3, recipz); + RwIm2DVertexSetV(&maVertices[3], v3, recipz); +} + +void +CSprite2d::SetVertices(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + float screenz, recipz; + float z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + screenz = RwIm2DGetNearScreenZ(); + recipz = RecipNearClip; + + RwIm2DVertexSetScreenX(&maVertices[0], x3); + RwIm2DVertexSetScreenY(&maVertices[0], y3); + RwIm2DVertexSetScreenZ(&maVertices[0], screenz); + RwIm2DVertexSetCameraZ(&maVertices[0], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], 0.0f, recipz); + RwIm2DVertexSetV(&maVertices[0], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&maVertices[1], x4); + RwIm2DVertexSetScreenY(&maVertices[1], y4); + RwIm2DVertexSetScreenZ(&maVertices[1], screenz); + RwIm2DVertexSetCameraZ(&maVertices[1], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], 1.0f, recipz); + RwIm2DVertexSetV(&maVertices[1], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&maVertices[2], x2); + RwIm2DVertexSetScreenY(&maVertices[2], y2); + RwIm2DVertexSetScreenZ(&maVertices[2], screenz); + RwIm2DVertexSetCameraZ(&maVertices[2], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], 1.0f, recipz); + RwIm2DVertexSetV(&maVertices[2], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&maVertices[3], x1); + RwIm2DVertexSetScreenY(&maVertices[3], y1); + RwIm2DVertexSetScreenZ(&maVertices[3], screenz); + RwIm2DVertexSetCameraZ(&maVertices[3], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], 0.0f, recipz); + RwIm2DVertexSetV(&maVertices[3], 1.0f, recipz); +} + +void +CSprite2d::SetVertices(int n, float *positions, float *uvs, const CRGBA &col) +{ + int i; + float screenz, recipz, z; + + screenz = RwIm2DGetNearScreenZ(); + recipz = RecipNearClip; + z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + + for(i = 0; i < n; i++){ + RwIm2DVertexSetScreenX(&maVertices[i], positions[i*2 + 0]); + RwIm2DVertexSetScreenY(&maVertices[i], positions[i*2 + 1]); + RwIm2DVertexSetScreenZ(&maVertices[i], screenz + 0.0001f); + RwIm2DVertexSetCameraZ(&maVertices[i], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[i], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[i], col.r, col.g, col.b, col.a); + RwIm2DVertexSetU(&maVertices[i], uvs[i*2 + 0], recipz); + RwIm2DVertexSetV(&maVertices[i], uvs[i*2 + 1], recipz); + } +} + +void +CSprite2d::SetMaskVertices(int n, float *positions) +{ + int i; + float screenz, recipz, z; + + screenz = RwIm2DGetNearScreenZ(); + recipz = RecipNearClip; + z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + for(i = 0; i < n; i++){ + RwIm2DVertexSetScreenX(&maVertices[i], positions[i*2 + 0]); + RwIm2DVertexSetScreenY(&maVertices[i], positions[i*2 + 1]); + RwIm2DVertexSetScreenZ(&maVertices[i], screenz); + RwIm2DVertexSetCameraZ(&maVertices[i], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[i], recipz); +#if !defined(GTA_PS2_STUFF) && defined(RWLIBS) + RwIm2DVertexSetIntRGBA(&maVertices[i], 0, 0, 0, 0); +#else + RwIm2DVertexSetIntRGBA(&maVertices[i], 255, 255, 255, 255); +#endif + } +} + +void +CSprite2d::SetVertices(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2) +{ + float screenz, recipz, z; + + screenz = RwIm2DGetNearScreenZ(); + recipz = RecipNearClip; + z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + RwIm2DVertexSetScreenX(&verts[0], r.left); + RwIm2DVertexSetScreenY(&verts[0], r.top); + RwIm2DVertexSetScreenZ(&verts[0], screenz); + RwIm2DVertexSetCameraZ(&verts[0], z); + RwIm2DVertexSetRecipCameraZ(&verts[0], recipz); + RwIm2DVertexSetIntRGBA(&verts[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[0], u0, recipz); + RwIm2DVertexSetV(&verts[0], v0, recipz); + + RwIm2DVertexSetScreenX(&verts[1], r.left); + RwIm2DVertexSetScreenY(&verts[1], r.bottom); + RwIm2DVertexSetScreenZ(&verts[1], screenz); + RwIm2DVertexSetCameraZ(&verts[1], z); + RwIm2DVertexSetRecipCameraZ(&verts[1], recipz); + RwIm2DVertexSetIntRGBA(&verts[1], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&verts[1], u2, recipz); + RwIm2DVertexSetV(&verts[1], v2, recipz); + + RwIm2DVertexSetScreenX(&verts[2], r.right); + RwIm2DVertexSetScreenY(&verts[2], r.bottom); + RwIm2DVertexSetScreenZ(&verts[2], screenz); + RwIm2DVertexSetCameraZ(&verts[2], z); + RwIm2DVertexSetRecipCameraZ(&verts[2], recipz); + RwIm2DVertexSetIntRGBA(&verts[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[2], u3, recipz); + RwIm2DVertexSetV(&verts[2], v3, recipz); + + RwIm2DVertexSetScreenX(&verts[3], r.left); + RwIm2DVertexSetScreenY(&verts[3], r.top); + RwIm2DVertexSetScreenZ(&verts[3], screenz); + RwIm2DVertexSetCameraZ(&verts[3], z); + RwIm2DVertexSetRecipCameraZ(&verts[3], recipz); + RwIm2DVertexSetIntRGBA(&verts[3], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[3], u0, recipz); + RwIm2DVertexSetV(&verts[3], v0, recipz); + + RwIm2DVertexSetScreenX(&verts[4], r.right); + RwIm2DVertexSetScreenY(&verts[4], r.bottom); + RwIm2DVertexSetScreenZ(&verts[4], screenz); + RwIm2DVertexSetCameraZ(&verts[4], z); + RwIm2DVertexSetRecipCameraZ(&verts[4], recipz); + RwIm2DVertexSetIntRGBA(&verts[4], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[4], u3, recipz); + RwIm2DVertexSetV(&verts[4], v3, recipz); + + RwIm2DVertexSetScreenX(&verts[5], r.right); + RwIm2DVertexSetScreenY(&verts[5], r.top); + RwIm2DVertexSetScreenZ(&verts[5], screenz); + RwIm2DVertexSetCameraZ(&verts[5], z); + RwIm2DVertexSetRecipCameraZ(&verts[5], recipz); + RwIm2DVertexSetIntRGBA(&verts[5], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&verts[5], u1, recipz); + RwIm2DVertexSetV(&verts[5], v1, recipz); + +} + +void +CSprite2d::DrawRect(const CRect &r, const CRGBA &col) +{ + SetVertices(r, col, col, col, col, false); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(col.a != 255)); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD); +} + +void +CSprite2d::DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + SetVertices(r, c0, c1, c2, c3, false); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); +} + +void +CSprite2d::DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + SetVertices(r, c0, c1, c2, c3, false); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); +} + +void CSprite2d::Draw2DPolygon(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, const CRGBA &color) +{ + SetVertices(x1, y1, x2, y2, x3, y3, x4, y4, color, color, color, color); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, 0); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(color.a != 255)); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD); +} diff --git a/src/renderer/Sprite2d.h b/src/renderer/Sprite2d.h new file mode 100644 index 0000000..0e12d44 --- /dev/null +++ b/src/renderer/Sprite2d.h @@ -0,0 +1,53 @@ +#pragma once + +class CSprite2d +{ + static float RecipNearClip; + static int32 mCurrentBank; + static RwTexture *mpBankTextures[10]; + static int32 mCurrentSprite[10]; + static int32 mBankStart[10]; + static RwIm2DVertex maBankVertices[500]; + static RwIm2DVertex maVertices[8]; +public: + RwTexture *m_pTexture; + + static void SetRecipNearClip(void); + static void InitPerFrame(void); + static int32 GetBank(int32 n, RwTexture *tex); + static void AddSpriteToBank(int32 bank, const CRect &rect, const CRGBA &col, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2); + static void DrawBank(int32 bank); + + CSprite2d(void) : m_pTexture(nil) {}; + ~CSprite2d(void) { Delete(); }; + void Delete(void); + void SetRenderState(void); + void SetTexture(const char *name); + void SetTexture(const char *name, const char *mask); + void SetAddressing(RwTextureAddressMode addr); + void Draw(float x, float y, float w, float h, const CRGBA &col); + void Draw(const CRect &rect, const CRGBA &col); + void Draw(const CRect &rect, const CRGBA &col, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2); + void Draw(const CRect &rect, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + void Draw(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, const CRGBA &col); + + static void SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, uint32 far); + static void SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2); + static void SetVertices(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void SetVertices(int n, float *positions, float *uvs, const CRGBA &col); + static void SetMaskVertices(int n, float *positions); + static void SetVertices(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2); + + static void DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void DrawRect(const CRect &r, const CRGBA &col); + static void DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + + static void Draw2DPolygon(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, const CRGBA &color); + + static RwIm2DVertex* GetVertices() { return maVertices; }; +}; diff --git a/src/renderer/TexList.cpp b/src/renderer/TexList.cpp new file mode 100644 index 0000000..1689837 --- /dev/null +++ b/src/renderer/TexList.cpp @@ -0,0 +1,41 @@ +#include "common.h" +#include "TexList.h" +#include "rtbmp.h" +#include "FileMgr.h" + +bool CTexList::ms_nTexUsed[MAX_TEXUSED]; + +void +CTexList::Initialise() +{} + +void +CTexList::Shutdown() +{} + +RwTexture * +CTexList::SetTexture(int32 slot, char *name) +{ + return nil; +} + +int32 +CTexList::GetFirstFreeTexture() +{ + for (int32 i = 0; i < MAX_TEXUSED; i++) + if (!ms_nTexUsed[i]) + return i; + return -1; +} + +RwTexture * +CTexList::LoadFileNameTexture(char *name) +{ + return SetTexture(GetFirstFreeTexture(), name); +} + +void +CTexList::LoadGlobalTextureList() +{ + CFileMgr::SetDir("TEXTURES"); +} \ No newline at end of file diff --git a/src/renderer/TexList.h b/src/renderer/TexList.h new file mode 100644 index 0000000..7e04221 --- /dev/null +++ b/src/renderer/TexList.h @@ -0,0 +1,14 @@ +#pragma once + +class CTexList +{ + enum { MAX_TEXUSED = 400, }; + static bool ms_nTexUsed[MAX_TEXUSED]; +public: + static void Initialise(); + static void Shutdown(); + static RwTexture *SetTexture(int32 slot, char *name); + static int32 GetFirstFreeTexture(); + static RwTexture *LoadFileNameTexture(char *name); + static void LoadGlobalTextureList(); +}; \ No newline at end of file diff --git a/src/renderer/Timecycle.cpp b/src/renderer/Timecycle.cpp new file mode 100644 index 0000000..0d94dbd --- /dev/null +++ b/src/renderer/Timecycle.cpp @@ -0,0 +1,317 @@ +#include "common.h" + +#include "main.h" +#include "Clock.h" +#include "Weather.h" +#include "Camera.h" +#include "Shadows.h" +#include "ZoneCull.h" +#include "CutsceneMgr.h" +#include "FileMgr.h" +#include "Timecycle.h" + +int32 CTimeCycle::m_nAmbientRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nAmbientGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nAmbientBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nDirectionalRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nDirectionalGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nDirectionalBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyTopRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyTopGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyTopBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyBottomRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyBottomGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyBottomBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoreRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoreGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoreBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoronaRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoronaGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoronaBlue[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fSunSize[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fSpriteSize[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fSpriteBrightness[NUMHOURS][NUMWEATHERS]; +int16 CTimeCycle::m_nShadowStrength[NUMHOURS][NUMWEATHERS]; +int16 CTimeCycle::m_nLightShadowStrength[NUMHOURS][NUMWEATHERS]; +int16 CTimeCycle::m_nTreeShadowStrength[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fFogStart[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fFarClip[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fLightsOnGroundBrightness[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nLowCloudsRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nLowCloudsGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nLowCloudsBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsTopRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsTopGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsTopBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsBottomRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsBottomGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsBottomBlue[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fBlurRed[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fBlurGreen[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fBlurBlue[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fBlurAlpha[NUMHOURS][NUMWEATHERS]; + +float CTimeCycle::m_fCurrentAmbientRed; +float CTimeCycle::m_fCurrentAmbientGreen; +float CTimeCycle::m_fCurrentAmbientBlue; +float CTimeCycle::m_fCurrentDirectionalRed; +float CTimeCycle::m_fCurrentDirectionalGreen; +float CTimeCycle::m_fCurrentDirectionalBlue; +int32 CTimeCycle::m_nCurrentSkyTopRed; +int32 CTimeCycle::m_nCurrentSkyTopGreen; +int32 CTimeCycle::m_nCurrentSkyTopBlue; +int32 CTimeCycle::m_nCurrentSkyBottomRed; +int32 CTimeCycle::m_nCurrentSkyBottomGreen; +int32 CTimeCycle::m_nCurrentSkyBottomBlue; +int32 CTimeCycle::m_nCurrentSunCoreRed; +int32 CTimeCycle::m_nCurrentSunCoreGreen; +int32 CTimeCycle::m_nCurrentSunCoreBlue; +int32 CTimeCycle::m_nCurrentSunCoronaRed; +int32 CTimeCycle::m_nCurrentSunCoronaGreen; +int32 CTimeCycle::m_nCurrentSunCoronaBlue; +float CTimeCycle::m_fCurrentSunSize; +float CTimeCycle::m_fCurrentSpriteSize; +float CTimeCycle::m_fCurrentSpriteBrightness; +int32 CTimeCycle::m_nCurrentShadowStrength; +int32 CTimeCycle::m_nCurrentLightShadowStrength; +int32 CTimeCycle::m_nCurrentTreeShadowStrength; +float CTimeCycle::m_fCurrentFogStart; +float CTimeCycle::m_fCurrentFarClip; +float CTimeCycle::m_fCurrentLightsOnGroundBrightness; +int32 CTimeCycle::m_nCurrentLowCloudsRed; +int32 CTimeCycle::m_nCurrentLowCloudsGreen; +int32 CTimeCycle::m_nCurrentLowCloudsBlue; +int32 CTimeCycle::m_nCurrentFluffyCloudsTopRed; +int32 CTimeCycle::m_nCurrentFluffyCloudsTopGreen; +int32 CTimeCycle::m_nCurrentFluffyCloudsTopBlue; +int32 CTimeCycle::m_nCurrentFluffyCloudsBottomRed; +int32 CTimeCycle::m_nCurrentFluffyCloudsBottomGreen; +int32 CTimeCycle::m_nCurrentFluffyCloudsBottomBlue; +float CTimeCycle::m_fCurrentBlurRed; +float CTimeCycle::m_fCurrentBlurGreen; +float CTimeCycle::m_fCurrentBlurBlue; +float CTimeCycle::m_fCurrentBlurAlpha; +int32 CTimeCycle::m_nCurrentFogColourRed; +int32 CTimeCycle::m_nCurrentFogColourGreen; +int32 CTimeCycle::m_nCurrentFogColourBlue; + +int32 CTimeCycle::m_FogReduction; + +int32 CTimeCycle::m_CurrentStoredValue; +CVector CTimeCycle::m_VectorToSun[16]; +float CTimeCycle::m_fShadowFrontX[16]; +float CTimeCycle::m_fShadowFrontY[16]; +float CTimeCycle::m_fShadowSideX[16]; +float CTimeCycle::m_fShadowSideY[16]; +float CTimeCycle::m_fShadowDisplacementX[16]; +float CTimeCycle::m_fShadowDisplacementY[16]; + + +void +CTimeCycle::Initialise(void) +{ + int w, h; + int li, bi; + char line[1040]; + + int ambR, ambG, ambB; + int dirR, dirG, dirB; + int skyTopR, skyTopG, skyTopB; + int skyBotR, skyBotG, skyBotB; + int sunCoreR, sunCoreG, sunCoreB; + int sunCoronaR, sunCoronaG, sunCoronaB; + float sunSz, sprSz, sprBght; + int shad, lightShad, treeShad; + float farClp, fogSt, lightGnd; + int cloudR, cloudG, cloudB; + int fluffyTopR, fluffyTopG, fluffyTopB; + int fluffyBotR, fluffyBotG, fluffyBotB; + float blurR, blurG, blurB, blurA; + + debug("Intialising CTimeCycle...\n"); + + CFileMgr::SetDir("DATA"); + CFileMgr::LoadFile("TIMECYC.DAT", work_buff, sizeof(work_buff), "rb"); + CFileMgr::SetDir(""); + + line[0] = '\0'; + bi = 0; + for(w = 0; w < NUMWEATHERS; w++) + for(h = 0; h < NUMHOURS; h++){ + li = 0; + while(work_buff[bi] == '/'){ + while(work_buff[bi] != '\n') + bi++; + bi++; + } + while(work_buff[bi] != '\n') + line[li++] = work_buff[bi++]; + line[li] = '\0'; + bi++; + + sscanf(line, "%d %d %d %d %d %d %d %d %d %d %d %d " + "%d %d %d %d %d %d %f %f %f %d %d %d %f %f %f " + "%d %d %d %d %d %d %d %d %d %f %f %f %f", + &ambR, &ambG, &ambB, + &dirR, &dirG, &dirB, + &skyTopR, &skyTopG, &skyTopB, + &skyBotR, &skyBotG, &skyBotB, + &sunCoreR, &sunCoreG, &sunCoreB, + &sunCoronaR, &sunCoronaG, &sunCoronaB, + &sunSz, &sprSz, &sprBght, + &shad, &lightShad, &treeShad, + &farClp, &fogSt, &lightGnd, + &cloudR, &cloudG, &cloudB, + &fluffyTopR, &fluffyTopG, &fluffyTopB, + &fluffyBotR, &fluffyBotG, &fluffyBotB, + &blurR, &blurG, &blurB, &blurA); + + m_nAmbientRed[h][w] = ambR; + m_nAmbientGreen[h][w] = ambG; + m_nAmbientBlue[h][w] = ambB; + m_nDirectionalRed[h][w] = dirR; + m_nDirectionalGreen[h][w] = dirG; + m_nDirectionalBlue[h][w] = dirB; + m_nSkyTopRed[h][w] = skyTopR; + m_nSkyTopGreen[h][w] = skyTopG; + m_nSkyTopBlue[h][w] = skyTopB; + m_nSkyBottomRed[h][w] = skyBotR; + m_nSkyBottomGreen[h][w] = skyBotG; + m_nSkyBottomBlue[h][w] = skyBotB; + m_nSunCoreRed[h][w] = sunCoreR; + m_nSunCoreGreen[h][w] = sunCoreG; + m_nSunCoreBlue[h][w] = sunCoreB; + m_nSunCoronaRed[h][w] = sunCoronaR; + m_nSunCoronaGreen[h][w] = sunCoronaG; + m_nSunCoronaBlue[h][w] = sunCoronaB; + m_fSunSize[h][w] = sunSz; + m_fSpriteSize[h][w] = sprSz; + m_fSpriteBrightness[h][w] = sprBght; + m_nShadowStrength[h][w] = shad; + m_nLightShadowStrength[h][w] = lightShad; + m_nTreeShadowStrength[h][w] = treeShad; + m_fFarClip[h][w] = farClp; + m_fFogStart[h][w] = fogSt; + m_fLightsOnGroundBrightness[h][w] = lightGnd; + m_nLowCloudsRed[h][w] = cloudR; + m_nLowCloudsGreen[h][w] = cloudG; + m_nLowCloudsBlue[h][w] = cloudB; + m_nFluffyCloudsTopRed[h][w] = fluffyTopR; + m_nFluffyCloudsTopGreen[h][w] = fluffyTopG; + m_nFluffyCloudsTopBlue[h][w] = fluffyTopB; + m_nFluffyCloudsBottomRed[h][w] = fluffyBotR; + m_nFluffyCloudsBottomGreen[h][w] = fluffyBotG; + m_nFluffyCloudsBottomBlue[h][w] = fluffyBotB; + m_fBlurRed[h][w] = blurR; + m_fBlurGreen[h][w] = blurG; + m_fBlurBlue[h][w] = blurB; + m_fBlurAlpha[h][w] = blurA; + } + + m_FogReduction = 0; + + debug("CTimeCycle ready\n"); +} + +void +CTimeCycle::Update(void) +{ + int h1 = CClock::GetHours(); + int h2 = (h1+1)%24; + int w1 = CWeather::OldWeatherType; + int w2 = CWeather::NewWeatherType; + float timeInterp = CClock::GetMinutes()/60.0f; + // coefficients for a bilinear interpolation + float c0 = (1.0f-timeInterp) * (1.0f-CWeather::InterpolationValue); + float c1 = timeInterp * (1.0f-CWeather::InterpolationValue); + float c2 = (1.0f-timeInterp) * CWeather::InterpolationValue; + float c3 = timeInterp * CWeather::InterpolationValue; + +#define INTERP(v) v[h1][w1]*c0 + v[h2][w1]*c1 + v[h1][w2]*c2 + v[h2][w2]*c3 + + m_nCurrentSkyTopRed = INTERP(m_nSkyTopRed); + m_nCurrentSkyTopGreen = INTERP(m_nSkyTopGreen); + m_nCurrentSkyTopBlue = INTERP(m_nSkyTopBlue); + + m_nCurrentSkyBottomRed = INTERP(m_nSkyBottomRed); + m_nCurrentSkyBottomGreen = INTERP(m_nSkyBottomGreen); + m_nCurrentSkyBottomBlue = INTERP(m_nSkyBottomBlue); + + m_fCurrentAmbientRed = INTERP(m_nAmbientRed); + m_fCurrentAmbientGreen = INTERP(m_nAmbientGreen); + m_fCurrentAmbientBlue = INTERP(m_nAmbientBlue); + m_fCurrentAmbientRed /= 255.0f; + m_fCurrentAmbientGreen /= 255.0f; + m_fCurrentAmbientBlue /= 255.0f; + + m_fCurrentDirectionalRed = INTERP(m_nDirectionalRed); + m_fCurrentDirectionalGreen = INTERP(m_nDirectionalGreen); + m_fCurrentDirectionalBlue = INTERP(m_nDirectionalBlue); + m_fCurrentDirectionalRed /= 255.0f; + m_fCurrentDirectionalGreen /= 255.0f; + m_fCurrentDirectionalBlue /= 255.0f; + + m_nCurrentSunCoreRed = INTERP(m_nSunCoreRed); + m_nCurrentSunCoreGreen = INTERP(m_nSunCoreGreen); + m_nCurrentSunCoreBlue = INTERP(m_nSunCoreBlue); + + m_nCurrentSunCoronaRed = INTERP(m_nSunCoronaRed); + m_nCurrentSunCoronaGreen = INTERP(m_nSunCoronaGreen); + m_nCurrentSunCoronaBlue = INTERP(m_nSunCoronaBlue); + + m_fCurrentSunSize = INTERP(m_fSunSize); + m_fCurrentSpriteSize = INTERP(m_fSpriteSize); + m_fCurrentSpriteBrightness = INTERP(m_fSpriteBrightness); + m_nCurrentShadowStrength = INTERP(m_nShadowStrength); + m_nCurrentLightShadowStrength = INTERP(m_nLightShadowStrength); + m_nCurrentTreeShadowStrength = INTERP(m_nTreeShadowStrength); + m_fCurrentFarClip = INTERP(m_fFarClip); + m_fCurrentFogStart = INTERP(m_fFogStart); + m_fCurrentLightsOnGroundBrightness = INTERP(m_fLightsOnGroundBrightness); + + m_nCurrentLowCloudsRed = INTERP(m_nLowCloudsRed); + m_nCurrentLowCloudsGreen = INTERP(m_nLowCloudsGreen); + m_nCurrentLowCloudsBlue = INTERP(m_nLowCloudsBlue); + + m_nCurrentFluffyCloudsTopRed = INTERP(m_nFluffyCloudsTopRed); + m_nCurrentFluffyCloudsTopGreen = INTERP(m_nFluffyCloudsTopGreen); + m_nCurrentFluffyCloudsTopBlue = INTERP(m_nFluffyCloudsTopBlue); + + m_nCurrentFluffyCloudsBottomRed = INTERP(m_nFluffyCloudsBottomRed); + m_nCurrentFluffyCloudsBottomGreen = INTERP(m_nFluffyCloudsBottomGreen); + m_nCurrentFluffyCloudsBottomBlue = INTERP(m_nFluffyCloudsBottomBlue); + + m_fCurrentBlurRed = INTERP(m_fBlurRed); + m_fCurrentBlurGreen = INTERP(m_fBlurGreen); + m_fCurrentBlurBlue = INTERP(m_fBlurBlue); + m_fCurrentBlurAlpha = INTERP(m_fBlurAlpha); + + if(TheCamera.m_BlurType == MOTION_BLUR_NONE || TheCamera.m_BlurType == MOTION_BLUR_LIGHT_SCENE) + TheCamera.SetMotionBlur(m_fCurrentBlurRed, m_fCurrentBlurGreen, m_fCurrentBlurBlue, m_fCurrentBlurAlpha, MOTION_BLUR_LIGHT_SCENE); + + if(m_FogReduction != 0) + m_fCurrentFarClip = Max(m_fCurrentFarClip, m_FogReduction/64.0f * 650.0f); + m_nCurrentFogColourRed = (m_nCurrentSkyTopRed + 2*m_nCurrentSkyBottomRed) / 3; + m_nCurrentFogColourGreen = (m_nCurrentSkyTopGreen + 2*m_nCurrentSkyBottomGreen) / 3; + m_nCurrentFogColourBlue = (m_nCurrentSkyTopBlue + 2*m_nCurrentSkyBottomBlue) / 3; + + m_CurrentStoredValue = (m_CurrentStoredValue+1)&0xF; + + float sunAngle = 2*PI*(CClock::GetMinutes() + CClock::GetHours()*60)/(24*60); + CVector &sunPos = GetSunDirection(); + sunPos.x = Sin(sunAngle); + sunPos.y = 1.0f; + sunPos.z = 0.2f - Cos(sunAngle); + sunPos.Normalise(); + + CShadows::CalcPedShadowValues(sunPos, + &m_fShadowFrontX[m_CurrentStoredValue], &m_fShadowFrontY[m_CurrentStoredValue], + &m_fShadowSideX[m_CurrentStoredValue], &m_fShadowSideY[m_CurrentStoredValue], + &m_fShadowDisplacementX[m_CurrentStoredValue], &m_fShadowDisplacementY[m_CurrentStoredValue]); + + if(TheCamera.GetForward().z < -0.9f || + !CWeather::bScriptsForceRain && (CCullZones::PlayerNoRain() || CCullZones::CamNoRain() || CCutsceneMgr::IsRunning())) + m_FogReduction = Min(m_FogReduction+1, 64); + else + m_FogReduction = Max(m_FogReduction-1, 0); +} diff --git a/src/renderer/Timecycle.h b/src/renderer/Timecycle.h new file mode 100644 index 0000000..d5d7b67 --- /dev/null +++ b/src/renderer/Timecycle.h @@ -0,0 +1,152 @@ +#pragma once + +class CTimeCycle +{ + static int32 m_nAmbientRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nAmbientGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nAmbientBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nDirectionalRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nDirectionalGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nDirectionalBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyTopRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyTopGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyTopBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyBottomRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyBottomGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyBottomBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoreRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoreGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoreBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoronaRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoronaGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoronaBlue[NUMHOURS][NUMWEATHERS]; + static float m_fSunSize[NUMHOURS][NUMWEATHERS]; + static float m_fSpriteSize[NUMHOURS][NUMWEATHERS]; + static float m_fSpriteBrightness[NUMHOURS][NUMWEATHERS]; + static int16 m_nShadowStrength[NUMHOURS][NUMWEATHERS]; + static int16 m_nLightShadowStrength[NUMHOURS][NUMWEATHERS]; + static int16 m_nTreeShadowStrength[NUMHOURS][NUMWEATHERS]; + static float m_fFogStart[NUMHOURS][NUMWEATHERS]; + static float m_fFarClip[NUMHOURS][NUMWEATHERS]; + static float m_fLightsOnGroundBrightness[NUMHOURS][NUMWEATHERS]; + static int32 m_nLowCloudsRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nLowCloudsGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nLowCloudsBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsTopRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsTopGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsTopBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsBottomRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsBottomGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsBottomBlue[NUMHOURS][NUMWEATHERS]; + static float m_fBlurRed[NUMHOURS][NUMWEATHERS]; + static float m_fBlurGreen[NUMHOURS][NUMWEATHERS]; + static float m_fBlurBlue[NUMHOURS][NUMWEATHERS]; + static float m_fBlurAlpha[NUMHOURS][NUMWEATHERS]; + + static float m_fCurrentAmbientRed; + static float m_fCurrentAmbientGreen; + static float m_fCurrentAmbientBlue; + static float m_fCurrentDirectionalRed; + static float m_fCurrentDirectionalGreen; + static float m_fCurrentDirectionalBlue; + static int32 m_nCurrentSkyTopRed; + static int32 m_nCurrentSkyTopGreen; + static int32 m_nCurrentSkyTopBlue; + static int32 m_nCurrentSkyBottomRed; + static int32 m_nCurrentSkyBottomGreen; + static int32 m_nCurrentSkyBottomBlue; + static int32 m_nCurrentSunCoreRed; + static int32 m_nCurrentSunCoreGreen; + static int32 m_nCurrentSunCoreBlue; + static int32 m_nCurrentSunCoronaRed; + static int32 m_nCurrentSunCoronaGreen; + static int32 m_nCurrentSunCoronaBlue; + static float m_fCurrentSunSize; + static float m_fCurrentSpriteSize; + static float m_fCurrentSpriteBrightness; + static int32 m_nCurrentShadowStrength; + static int32 m_nCurrentLightShadowStrength; + static int32 m_nCurrentTreeShadowStrength; + static float m_fCurrentFogStart; + static float m_fCurrentFarClip; + static float m_fCurrentLightsOnGroundBrightness; + static int32 m_nCurrentLowCloudsRed; + static int32 m_nCurrentLowCloudsGreen; + static int32 m_nCurrentLowCloudsBlue; + static int32 m_nCurrentFluffyCloudsTopRed; + static int32 m_nCurrentFluffyCloudsTopGreen; + static int32 m_nCurrentFluffyCloudsTopBlue; + static int32 m_nCurrentFluffyCloudsBottomRed; + static int32 m_nCurrentFluffyCloudsBottomGreen; + static int32 m_nCurrentFluffyCloudsBottomBlue; + static float m_fCurrentBlurRed; + static float m_fCurrentBlurGreen; + static float m_fCurrentBlurBlue; + static float m_fCurrentBlurAlpha; + static int32 m_nCurrentFogColourRed; + static int32 m_nCurrentFogColourGreen; + static int32 m_nCurrentFogColourBlue; + + static int32 m_FogReduction; + +public: + static int32 m_CurrentStoredValue; + static CVector m_VectorToSun[16]; + static float m_fShadowFrontX[16]; + static float m_fShadowFrontY[16]; + static float m_fShadowSideX[16]; + static float m_fShadowSideY[16]; + static float m_fShadowDisplacementX[16]; + static float m_fShadowDisplacementY[16]; + + static float GetAmbientRed(void) { return m_fCurrentAmbientRed; } + static float GetAmbientGreen(void) { return m_fCurrentAmbientGreen; } + static float GetAmbientBlue(void) { return m_fCurrentAmbientBlue; } + static float GetDirectionalRed(void) { return m_fCurrentDirectionalRed; } + static float GetDirectionalGreen(void) { return m_fCurrentDirectionalGreen; } + static float GetDirectionalBlue(void) { return m_fCurrentDirectionalBlue; } + static int32 GetSkyTopRed(void) { return m_nCurrentSkyTopRed; } + static int32 GetSkyTopGreen(void) { return m_nCurrentSkyTopGreen; } + static int32 GetSkyTopBlue(void) { return m_nCurrentSkyTopBlue; } + static int32 GetSkyBottomRed(void) { return m_nCurrentSkyBottomRed; } + static int32 GetSkyBottomGreen(void) { return m_nCurrentSkyBottomGreen; } + static int32 GetSkyBottomBlue(void) { return m_nCurrentSkyBottomBlue; } + static int32 GetSunCoreRed(void) { return m_nCurrentSunCoreRed; } + static int32 GetSunCoreGreen(void) { return m_nCurrentSunCoreGreen; } + static int32 GetSunCoreBlue(void) { return m_nCurrentSunCoreBlue; } + static int32 GetSunCoronaRed(void) { return m_nCurrentSunCoronaRed; } + static int32 GetSunCoronaGreen(void) { return m_nCurrentSunCoronaGreen; } + static int32 GetSunCoronaBlue(void) { return m_nCurrentSunCoronaBlue; } + static float GetSunSize(void) { return m_fCurrentSunSize; } + static float GetSpriteBrightness(void) { return m_fCurrentSpriteBrightness; } + static float GetSpriteSize(void) { return m_fCurrentSpriteSize; } + static int32 GetShadowStrength(void) { return m_nCurrentShadowStrength; } + static int32 GetLightShadowStrength(void) { return m_nCurrentLightShadowStrength; } + static float GetLightOnGroundBrightness(void) { return m_fCurrentLightsOnGroundBrightness; } + static float GetFarClip(void) { return m_fCurrentFarClip; } + static float GetFogStart(void) { return m_fCurrentFogStart; } + + static int32 GetLowCloudsRed(void) { return m_nCurrentLowCloudsRed; } + static int32 GetLowCloudsGreen(void) { return m_nCurrentLowCloudsGreen; } + static int32 GetLowCloudsBlue(void) { return m_nCurrentLowCloudsBlue; } + static int32 GetFluffyCloudsTopRed(void) { return m_nCurrentFluffyCloudsTopRed; } + static int32 GetFluffyCloudsTopGreen(void) { return m_nCurrentFluffyCloudsTopGreen; } + static int32 GetFluffyCloudsTopBlue(void) { return m_nCurrentFluffyCloudsTopBlue; } + static int32 GetFluffyCloudsBottomRed(void) { return m_nCurrentFluffyCloudsBottomRed; } + static int32 GetFluffyCloudsBottomGreen(void) { return m_nCurrentFluffyCloudsBottomGreen; } + static int32 GetFluffyCloudsBottomBlue(void) { return m_nCurrentFluffyCloudsBottomBlue; } + static int32 GetFogRed(void) { return m_nCurrentFogColourRed; } + static int32 GetFogGreen(void) { return m_nCurrentFogColourGreen; } + static int32 GetFogBlue(void) { return m_nCurrentFogColourBlue; } + static int32 GetFogReduction(void) { return m_FogReduction; } + + static void Initialise(void); + static void Update(void); + static CVector &GetSunDirection(void) { return m_VectorToSun[m_CurrentStoredValue]; } + static float GetShadowFrontX(void) { return m_fShadowFrontX[m_CurrentStoredValue]; } + static float GetShadowFrontY(void) { return m_fShadowFrontY[m_CurrentStoredValue]; } + static float GetShadowSideX(void) { return m_fShadowSideX[m_CurrentStoredValue]; } + static float GetShadowSideY(void) { return m_fShadowSideY[m_CurrentStoredValue]; } + static float GetShadowDisplacementX(void) { return m_fShadowDisplacementX[m_CurrentStoredValue]; } + static float GetShadowDisplacementY(void) { return m_fShadowDisplacementY[m_CurrentStoredValue]; } +}; diff --git a/src/renderer/WaterCannon.cpp b/src/renderer/WaterCannon.cpp new file mode 100644 index 0000000..08898be --- /dev/null +++ b/src/renderer/WaterCannon.cpp @@ -0,0 +1,307 @@ +#include "common.h" + +#include "WaterCannon.h" +#include "Vector.h" +#include "General.h" +#include "main.h" +#include "Timer.h" +#include "Pools.h" +#include "Ped.h" +#include "AnimManager.h" +#include "Fire.h" +#include "WaterLevel.h" +#include "Camera.h" + +#define WATERCANNONVERTS 4 +#define WATERCANNONINDEXES 12 + +RwIm3DVertex WaterCannonVertices[WATERCANNONVERTS]; +RwImVertexIndex WaterCannonIndexList[WATERCANNONINDEXES]; + +CWaterCannon CWaterCannons::aCannons[NUM_WATERCANNONS]; + +void CWaterCannon::Init(void) +{ + m_nId = 0; + m_nCur = 0; + m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + + for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ ) + m_abUsed[i] = false; + + RwIm3DVertexSetU(&WaterCannonVertices[0], 0.0f); + RwIm3DVertexSetV(&WaterCannonVertices[0], 0.0f); + + RwIm3DVertexSetU(&WaterCannonVertices[1], 1.0f); + RwIm3DVertexSetV(&WaterCannonVertices[1], 0.0f); + + RwIm3DVertexSetU(&WaterCannonVertices[2], 0.0f); + RwIm3DVertexSetV(&WaterCannonVertices[2], 0.0f); + + RwIm3DVertexSetU(&WaterCannonVertices[3], 1.0f); + RwIm3DVertexSetV(&WaterCannonVertices[3], 0.0f); + + WaterCannonIndexList[0] = 0; + WaterCannonIndexList[1] = 1; + WaterCannonIndexList[2] = 2; + + WaterCannonIndexList[3] = 1; + WaterCannonIndexList[4] = 3; + WaterCannonIndexList[5] = 2; + + WaterCannonIndexList[6] = 0; + WaterCannonIndexList[7] = 2; + WaterCannonIndexList[8] = 1; + + WaterCannonIndexList[9] = 1; + WaterCannonIndexList[10] = 2; + WaterCannonIndexList[11] = 3; +} + +void CWaterCannon::Update_OncePerFrame(int16 index) +{ + ASSERT(index < NUM_WATERCANNONS); + + if (CTimer::GetTimeInMilliseconds() > m_nTimeCreated + WATERCANNON_LIFETIME ) + { + m_nCur = (m_nCur + 1) % NUM_SEGMENTPOINTS; + m_abUsed[m_nCur] = false; + } + + for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ ) + { + if ( m_abUsed[i] ) + { + m_avecVelocity[i].z += -WATERCANNON_GRAVITY * CTimer::GetTimeStep(); + m_avecPos[i] += m_avecVelocity[i] * CTimer::GetTimeStep(); + } + } + + int32 extinguishingPoint = CGeneral::GetRandomNumber() & (NUM_SEGMENTPOINTS - 1); + if ( m_abUsed[extinguishingPoint] ) + gFireManager.ExtinguishPoint(m_avecPos[extinguishingPoint], 3.0f); + + if ( ((index + CTimer::GetFrameCounter()) & 3) == 0 ) + PushPeds(); + + // free if unused + + int32 i = 0; + while ( 1 ) + { + if ( m_abUsed[i] ) + break; + + if ( ++i >= NUM_SEGMENTPOINTS ) + { + m_nId = 0; + return; + } + } +} + +void CWaterCannon::Update_NewInput(CVector *pos, CVector *dir) +{ + ASSERT(pos != NULL); + ASSERT(dir != NULL); + + m_avecPos[m_nCur] = *pos; + m_avecVelocity[m_nCur] = *dir; + m_abUsed[m_nCur] = true; +} + +void CWaterCannon::Render(void) +{ + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpWaterRaster); + + float v = float(CGeneral::GetRandomNumber() & 255) / 256; + + RwIm3DVertexSetV(&WaterCannonVertices[0], v); + RwIm3DVertexSetV(&WaterCannonVertices[1], v); + RwIm3DVertexSetV(&WaterCannonVertices[2], v); + RwIm3DVertexSetV(&WaterCannonVertices[3], v); + + int16 pointA = m_nCur % NUM_SEGMENTPOINTS; + + int16 pointB = pointA - 1; + if ( pointB < 0 ) + pointB += NUM_SEGMENTPOINTS; + + bool bInit = false; + CVector norm; + + for ( int32 i = 0; i < NUM_SEGMENTPOINTS - 1; i++ ) + { + if ( m_abUsed[pointA] && m_abUsed[pointB] ) + { + if ( !bInit ) + { + CVector cp = CrossProduct(m_avecPos[pointB] - m_avecPos[pointA], TheCamera.GetForward()); + norm = cp * (0.05f / cp.Magnitude()); + bInit = true; + } + + float dist = float(i*i*i) / 300.0f + 1.0f; + float brightness = float(i) / NUM_SEGMENTPOINTS; + + int32 color = (int32)((1.0f - brightness*brightness) * 255.0f); + CVector offset = dist * norm; + + RwIm3DVertexSetRGBA(&WaterCannonVertices[0], color, color, color, color); + RwIm3DVertexSetPos (&WaterCannonVertices[0], m_avecPos[pointA].x - offset.x, m_avecPos[pointA].y - offset.y, m_avecPos[pointA].z - offset.z); + + RwIm3DVertexSetRGBA(&WaterCannonVertices[1], color, color, color, color); + RwIm3DVertexSetPos (&WaterCannonVertices[1], m_avecPos[pointA].x + offset.x, m_avecPos[pointA].y + offset.y, m_avecPos[pointA].z + offset.z); + + RwIm3DVertexSetRGBA(&WaterCannonVertices[2], color, color, color, color); + RwIm3DVertexSetPos (&WaterCannonVertices[2], m_avecPos[pointB].x - offset.x, m_avecPos[pointB].y - offset.y, m_avecPos[pointB].z - offset.z); + + RwIm3DVertexSetRGBA(&WaterCannonVertices[3], color, color, color, color); + RwIm3DVertexSetPos (&WaterCannonVertices[3], m_avecPos[pointB].x + offset.x, m_avecPos[pointB].y + offset.y, m_avecPos[pointB].z + offset.z); + + LittleTest(); + + if ( RwIm3DTransform(WaterCannonVertices, WATERCANNONVERTS, NULL, rwIM3D_VERTEXUV) ) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, WaterCannonIndexList, WATERCANNONINDEXES); + RwIm3DEnd(); + } + } + + pointA = pointB--; + if ( pointB < 0 ) + pointB += NUM_SEGMENTPOINTS; + } + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE); +} + +void CWaterCannon::PushPeds(void) +{ + float minx = 10000.0f; + float maxx = -10000.0f; + float miny = 10000.0f; + float maxy = -10000.0f; + float minz = 10000.0f; + float maxz = -10000.0f; + + for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ ) + { + if ( m_abUsed[i] ) + { + minx = Min(minx, m_avecPos[i].x); + maxx = Max(maxx, m_avecPos[i].x); + + miny = Min(miny, m_avecPos[i].y); + maxy = Max(maxy, m_avecPos[i].y); + + minz = Min(minz, m_avecPos[i].z); + maxz = Max(maxz, m_avecPos[i].z); + } + } + + for ( int32 i = CPools::GetPedPool()->GetSize() - 1; i >= 0; i--) + { + CPed *ped = CPools::GetPedPool()->GetSlot(i); + if ( ped ) + { + if ( ped->GetPosition().x > minx && ped->GetPosition().x < maxx + && ped->GetPosition().y > miny && ped->GetPosition().y < maxy + && ped->GetPosition().z > minz && ped->GetPosition().z < maxz ) + { + for ( int32 j = 0; j < NUM_SEGMENTPOINTS; j++ ) + { + if ( m_abUsed[j] ) + { + CVector dist = m_avecPos[j] - ped->GetPosition(); + + if ( dist.MagnitudeSqr() < 5.0f ) + { + int32 localDir = ped->GetLocalDirection(CVector2D(1.0f, 0.0f)); + + ped->bIsStanding = false; + + ped->ApplyMoveForce(0.0f, 0.0f, 2.0f * CTimer::GetTimeStep()); + + ped->m_vecMoveSpeed.x = (0.6f * m_avecVelocity[j].x + ped->m_vecMoveSpeed.x) * 0.5f; + ped->m_vecMoveSpeed.y = (0.6f * m_avecVelocity[j].y + ped->m_vecMoveSpeed.y) * 0.5f; + + ped->SetFall(2000, AnimationId(ANIM_STD_HIGHIMPACT_FRONT + localDir), 0); + + CFire *fire = ped->m_pFire; + if ( fire ) + fire->Extinguish(); + + j = NUM_SEGMENTPOINTS; + } + } + } + } + } + } +} + +void CWaterCannons::Init(void) +{ + for ( int32 i = 0; i < NUM_WATERCANNONS; i++ ) + aCannons[i].Init(); +} + +void CWaterCannons::UpdateOne(uint32 id, CVector *pos, CVector *dir) +{ + ASSERT(pos != NULL); + ASSERT(dir != NULL); + + // find the one by id + { + int32 n = 0; + while ( n < NUM_WATERCANNONS && id != aCannons[n].m_nId ) + n++; + + if ( n < NUM_WATERCANNONS ) + { + aCannons[n].Update_NewInput(pos, dir); + return; + } + } + + // if no luck then find a free one + { + int32 n = 0; + while ( n < NUM_WATERCANNONS && 0 != aCannons[n].m_nId ) + n++; + + if ( n < NUM_WATERCANNONS ) + { + aCannons[n].Init(); + aCannons[n].m_nId = id; + aCannons[n].Update_NewInput(pos, dir); + return; + } + } +} + +void CWaterCannons::Update(void) +{ + for ( int32 i = 0; i < NUM_WATERCANNONS; i++ ) + { + if ( aCannons[i].m_nId != 0 ) + aCannons[i].Update_OncePerFrame(i); + } +} + +void CWaterCannons::Render(void) +{ + PUSH_RENDERGROUP("CWaterCannons::Render"); + for ( int32 i = 0; i < NUM_WATERCANNONS; i++ ) + { + if ( aCannons[i].m_nId != 0 ) + aCannons[i].Render(); + } + POP_RENDERGROUP(); +} diff --git a/src/renderer/WaterCannon.h b/src/renderer/WaterCannon.h new file mode 100644 index 0000000..a37bdd1 --- /dev/null +++ b/src/renderer/WaterCannon.h @@ -0,0 +1,39 @@ +#pragma once + +#define WATERCANNON_GRAVITY (0.009f) +#define WATERCANNON_LIFETIME (150) + +class CWaterCannon +{ +public: + enum + { + NUM_SEGMENTPOINTS = 16, + }; + + int32 m_nId; + int16 m_nCur; + uint32 m_nTimeCreated; + CVector m_avecPos[NUM_SEGMENTPOINTS]; + CVector m_avecVelocity[NUM_SEGMENTPOINTS]; + bool m_abUsed[NUM_SEGMENTPOINTS]; + + void Init(void); + void Update_OncePerFrame(int16 index); + void Update_NewInput(CVector *pos, CVector *dir); + void Render(void); + void PushPeds(void); +}; + +VALIDATE_SIZE(CWaterCannon, 412); + +class CWaterCannons +{ +public: + static CWaterCannon aCannons[NUM_WATERCANNONS]; + + static void Init(void); + static void UpdateOne(uint32 id, CVector *pos, CVector *dir); + static void Update(); + static void Render(void); +}; \ No newline at end of file diff --git a/src/renderer/WaterLevel.cpp b/src/renderer/WaterLevel.cpp new file mode 100644 index 0000000..7001c0c --- /dev/null +++ b/src/renderer/WaterLevel.cpp @@ -0,0 +1,1554 @@ +#include "common.h" +#include "main.h" +#include "FileMgr.h" +#include "FileLoader.h" +#include "TxdStore.h" +#include "Timer.h" +#include "Weather.h" +#include "Camera.h" +#include "Vehicle.h" +#include "Boat.h" +#include "World.h" +#include "General.h" +#include "Timecycle.h" +#include "ZoneCull.h" +#include "Clock.h" +#include "Particle.h" +#include "ParticleMgr.h" +#include "RwHelper.h" +#include "Streaming.h" +#include "CdStream.h" +#include "Pad.h" +#include "RenderBuffer.h" +#include +#include "WaterLevel.h" +#include "MemoryHeap.h" + + +float TEXTURE_ADDU; +float TEXTURE_ADDV; + +int32 CWaterLevel::ms_nNoOfWaterLevels; +float CWaterLevel::ms_aWaterZs[48]; +CRect CWaterLevel::ms_aWaterRects[48]; +int8 CWaterLevel::aWaterBlockList[MAX_LARGE_SECTORS][MAX_LARGE_SECTORS]; +int8 CWaterLevel::aWaterFineBlockList[MAX_SMALL_SECTORS][MAX_SMALL_SECTORS]; +bool CWaterLevel::WavesCalculatedThisFrame; +RpAtomic *CWaterLevel::ms_pWavyAtomic; +RpGeometry *CWaterLevel::apGeomArray[8]; +int16 CWaterLevel::nGeomUsed; +//"Custom" Don't Render Water Toggle +bool gbDontRenderWater; + +//RwTexture *gpWaterTex; +//RwRaster *gpWaterRaster; + +RwTexture *gpWaterTex; +RwRaster *gpWaterRaster; + + +const float fAdd1 = 180.0f; +const float fAdd2 = 80.0f; +const float fRedMult = 0.6f; +const float fGreenMult = 1.0f; +const float fBlueMult = 1.4f; + + +void +CWaterLevel::Initialise(Const char *pWaterDat) +{ + ms_nNoOfWaterLevels = 0; + +#ifdef MASTER + int32 hFile = -1; + + do + { + hFile = CFileMgr::OpenFile("DATA\\waterpro.dat", "rb"); + } + while ( hFile < 0 ); +#else + int32 hFile = CFileMgr::OpenFile("DATA\\waterpro.dat", "rb"); +#endif + + if (hFile > 0) + { + CFileMgr::Read(hFile, (char *)&ms_nNoOfWaterLevels, sizeof(ms_nNoOfWaterLevels)); + CFileMgr::Read(hFile, (char *)ms_aWaterZs, sizeof(ms_aWaterZs)); + CFileMgr::Read(hFile, (char *)ms_aWaterRects, sizeof(ms_aWaterRects)); + CFileMgr::Read(hFile, (char *)aWaterBlockList, sizeof(aWaterBlockList)); + CFileMgr::Read(hFile, (char *)aWaterFineBlockList, sizeof(aWaterFineBlockList)); + + CFileMgr::CloseFile(hFile); + } +#ifndef MASTER + else + { + printf("Init waterlevels\n"); + + CFileMgr::SetDir(""); + hFile = CFileMgr::OpenFile(pWaterDat, "r"); + + char *line; + + while ((line = CFileLoader::LoadLine(hFile))) + { +#ifdef FIX_BUGS + if (*line && *line != ';' && !strstr(line, "* ;end of file")) +#else + if (*line && *line != ';') +#endif + { + float z, l, b, r, t; + sscanf(line, "%f %f %f %f %f", &z, &l, &b, &r, &t); + AddWaterLevel(l, b, r, t, z); + } + } + + CFileMgr::CloseFile(hFile); + + for (int32 x = 0; x < MAX_SMALL_SECTORS; x++) + { + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + aWaterFineBlockList[x][y] = NO_WATER; + } + } + + // rasterize water rects read from file + for (int32 i = 0; i < ms_nNoOfWaterLevels; i++) + { + int32 l = WATER_HUGE_X(ms_aWaterRects[i].left); + int32 r = WATER_HUGE_X(ms_aWaterRects[i].right) + 1.0f; + int32 t = WATER_HUGE_Y(ms_aWaterRects[i].top); + int32 b = WATER_HUGE_Y(ms_aWaterRects[i].bottom) + 1.0f; + +#ifdef FIX_BUGS + // water.dat has rects that go out of bounds + // which causes memory corruption + l = Clamp(l, 0, MAX_SMALL_SECTORS - 1); + r = Clamp(r, 0, MAX_SMALL_SECTORS - 1); + t = Clamp(t, 0, MAX_SMALL_SECTORS - 1); + b = Clamp(b, 0, MAX_SMALL_SECTORS - 1); +#endif + + for (int32 x = l; x <= r; x++) + { + for (int32 y = t; y <= b; y++) + { + aWaterFineBlockList[x][y] = i; + } + } + } + + // remove tiles that are obscured by land + for (int32 x = 0; x < MAX_SMALL_SECTORS; x++) + { + float worldX = WATER_START_X + x * SMALL_SECTOR_SIZE; + + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + if (aWaterFineBlockList[x][y] >= 0) + { + float worldY = WATER_START_Y + y * SMALL_SECTOR_SIZE; + + int32 i; + for (i = 0; i <= 8; i++) + { + for (int32 j = 0; j <= 8; j++) + { + CVector worldPos = CVector(worldX + i * (SMALL_SECTOR_SIZE / 8), worldY + j * (SMALL_SECTOR_SIZE / 8), ms_aWaterZs[aWaterFineBlockList[x][y]]); + + if ((worldPos.x > WORLD_MIN_X && worldPos.x < WORLD_MAX_X) && (worldPos.y > WORLD_MIN_Y && worldPos.y < WORLD_MAX_Y) && + (!WaterLevelAccordingToRectangles(worldPos.x, worldPos.y) || TestVisibilityForFineWaterBlocks(worldPos))) + continue; + + // at least one point in the tile wasn't blocked, so don't remove water + i = 1000; + break; + } + } + + if (i < 1000) + aWaterFineBlockList[x][y] = NO_WATER; + } + } + } + + RemoveIsolatedWater(); + + // calculate coarse tiles from fine tiles + for (int32 x = 0; x < MAX_LARGE_SECTORS; x++) + { + for (int32 y = 0; y < MAX_LARGE_SECTORS; y++) + { + if (aWaterFineBlockList[x * 2][y * 2] >= 0) + { + aWaterBlockList[x][y] = aWaterFineBlockList[x * 2][y * 2]; + } + else if (aWaterFineBlockList[x * 2 + 1][y * 2] >= 0) + { + aWaterBlockList[x][y] = aWaterFineBlockList[x * 2 + 1][y * 2]; + } + else if (aWaterFineBlockList[x * 2][y * 2 + 1] >= 0) + { + aWaterBlockList[x][y] = aWaterFineBlockList[x * 2][y * 2 + 1]; + } + else if (aWaterFineBlockList[x * 2 + 1][y * 2 + 1] >= 0) + { + aWaterBlockList[x][y] = aWaterFineBlockList[x * 2 + 1][y * 2 + 1]; + } + else + { + aWaterBlockList[x][y] = NO_WATER; + } + } + } + + hFile = CFileMgr::OpenFileForWriting("data\\waterpro.dat"); + + if (hFile > 0) + { + CFileMgr::Write(hFile, (char *)&ms_nNoOfWaterLevels, sizeof(ms_nNoOfWaterLevels)); + CFileMgr::Write(hFile, (char *)ms_aWaterZs, sizeof(ms_aWaterZs)); + CFileMgr::Write(hFile, (char *)ms_aWaterRects, sizeof(ms_aWaterRects)); + CFileMgr::Write(hFile, (char *)aWaterBlockList, sizeof(aWaterBlockList)); + CFileMgr::Write(hFile, (char *)aWaterFineBlockList, sizeof(aWaterFineBlockList)); + + CFileMgr::CloseFile(hFile); + } + } +#endif + + CTxdStore::PushCurrentTxd(); + + int32 slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + + if ( gpWaterTex == nil ) + gpWaterTex = RwTextureRead("water_old", nil); + gpWaterRaster = RwTextureGetRaster(gpWaterTex); + + CTxdStore::PopCurrentTxd(); + + CreateWavyAtomic(); + FreeBoatWakeArray(); + + printf("Done Initing waterlevels\n"); +} + +void +CWaterLevel::Shutdown() +{ + FreeBoatWakeArray(); + DestroyWavyAtomic(); + + if ( gpWaterTex != nil ) + { + RwTextureDestroy(gpWaterTex); + gpWaterTex = nil; + } +} + +void +CWaterLevel::CreateWavyAtomic() +{ + RpGeometry *wavyGeometry; + RpMaterial *wavyMaterial; + RpTriangle *wavyTriangles; + RpMorphTarget *wavyMorphTarget; + RwSphere boundingSphere; + RwV3d *wavyVert; + + RwFrame *wavyFrame; + + { + wavyGeometry = RpGeometryCreate(9*9, 8*8*2, rpGEOMETRYTRISTRIP + |rpGEOMETRYTEXTURED + |rpGEOMETRYPRELIT + |rpGEOMETRYMODULATEMATERIALCOLOR); + + ASSERT(wavyGeometry != nil); + + } + + { + wavyMaterial = RpMaterialCreate(); + + ASSERT(wavyMaterial != nil); + ASSERT(gpWaterTex != nil); + + RpMaterialSetTexture(wavyMaterial, gpWaterTex); + } + + { + wavyTriangles = RpGeometryGetTriangles(wavyGeometry); + + ASSERT(wavyTriangles != nil); + /* + [B] [C] + *********** + * * * + * * * + * * * + * * * + *********** + [A] [D] + */ + + for ( int32 i = 0; i < 8; i++ ) + { + for ( int32 j = 0; j < 8; j++ ) + { + RpGeometryTriangleSetVertexIndices(wavyGeometry, + &wavyTriangles[2 * 8*i + 2*j + 0], /*A*/9*i+j+0, /*B*/9*i+j+1, /*C*/9*i+j+9+1); + + RpGeometryTriangleSetVertexIndices(wavyGeometry, + &wavyTriangles[2 * 8*i + 2*j + 1], /*A*/9*i+j+0, /*C*/9*i+j+9+1, /*D*/9*i+j+9 ); + + RpGeometryTriangleSetMaterial(wavyGeometry, &wavyTriangles[2 * 8*i + 2*j + 0], wavyMaterial); + RpGeometryTriangleSetMaterial(wavyGeometry, &wavyTriangles[2 * 8*i + 2*j + 1], wavyMaterial); + } + } + } + + + { + wavyMorphTarget = RpGeometryGetMorphTarget(wavyGeometry, 0); + ASSERT(wavyMorphTarget != nil); + wavyVert = RpMorphTargetGetVertices(wavyMorphTarget); + ASSERT(wavyVert != nil); + + for ( int32 i = 0; i < 9; i++ ) + { + for ( int32 j = 0; j < 9; j++ ) + { + wavyVert[9*i+j].x = (float)i * 4.0f; + wavyVert[9*i+j].y = (float)j * 4.0f; + wavyVert[9*i+j].z = 0.0f; + } + } + + RpMorphTargetCalcBoundingSphere(wavyMorphTarget, &boundingSphere); + RpMorphTargetSetBoundingSphere(wavyMorphTarget, &boundingSphere); + RpGeometryUnlock(wavyGeometry); + } + + + { + wavyFrame = RwFrameCreate(); + ASSERT( wavyFrame != nil ); + + ms_pWavyAtomic = RpAtomicCreate(); + ASSERT( ms_pWavyAtomic != nil ); + + RpAtomicSetGeometry(ms_pWavyAtomic, wavyGeometry, 0); + RpAtomicSetFrame(ms_pWavyAtomic, wavyFrame); + RpMaterialDestroy(wavyMaterial); + RpGeometryDestroy(wavyGeometry); + } +} + +void +CWaterLevel::DestroyWavyAtomic() +{ + RwFrame *frame; + + frame = RpAtomicGetFrame(ms_pWavyAtomic); + + RpAtomicDestroy(ms_pWavyAtomic); + + RwFrameDestroy(frame); +} + +#ifndef MASTER +void +CWaterLevel::AddWaterLevel(float fXLeft, float fYBottom, float fXRight, float fYTop, float fLevel) +{ + ms_aWaterRects[ms_nNoOfWaterLevels] = CRect(fXLeft, fYBottom, fXRight, fYTop); + ms_aWaterZs[ms_nNoOfWaterLevels] = fLevel; + ms_nNoOfWaterLevels++; +} + +bool +CWaterLevel::WaterLevelAccordingToRectangles(float fX, float fY, float *pfOutLevel) +{ + if (ms_nNoOfWaterLevels <= 0) return false; + + for (int32 i = 0; i < ms_nNoOfWaterLevels; i++) + { + if (fX >= ms_aWaterRects[i].left && fX <= ms_aWaterRects[i].right + && fY >= ms_aWaterRects[i].top && fY <= ms_aWaterRects[i].bottom) + { + if (pfOutLevel) *pfOutLevel = ms_aWaterZs[i]; + + return true; + } + } + + return false; +} + +bool +CWaterLevel::TestVisibilityForFineWaterBlocks(const CVector &worldPos) +{ + static CVector2D tab[] = + { + { 50.0f, 50.0f }, + { -50.0f, 50.0f }, + { -50.0f, -50.0f }, + { 50.0f, -50.0f }, + { 50.0f, 0.0f }, + { -50.0f, 0.0f }, + { 0.0f, -50.0f }, + { 0.0f, 50.0f }, + }; + + CEntity *entity; + CColPoint col; + CVector lineStart, lineEnd; + + lineStart = worldPos; + + if (!CWorld::ProcessVerticalLine(lineStart, lineStart.z + 100.0f, col, entity, true, false, false, false, true, false, nil)) + { + lineStart.x += 0.4f; + lineStart.y += 0.4f; + + if (!CWorld::ProcessVerticalLine(lineStart, lineStart.z + 100.0f, col, entity, true, false, false, false, true, false, nil)) + { + return false; + } + } + + for (int32 i = 0; i < ARRAY_SIZE(tab); i++) + { + lineStart = worldPos; + lineEnd = worldPos; + + lineEnd.x += tab[i].x; + lineEnd.y += tab[i].y; + lineEnd.z += 100.0f; + + if ((lineEnd.x > WORLD_MIN_X && lineEnd.x < WORLD_MAX_X) && (lineEnd.y > WORLD_MIN_Y && lineEnd.y < WORLD_MAX_Y)) + { + if (!CWorld::ProcessLineOfSight(lineStart, lineEnd, col, entity, true, false, false, false, true, false)) + { + lineStart.x += 0.4f; + lineStart.y += 0.4f; + lineEnd.x += 0.4f; + lineEnd.y += 0.4f; + + if (!CWorld::ProcessLineOfSight(lineStart, lineEnd, col, entity, true, false, false, false, true, false)) + { + return false; + } + } + } + } + + return true; +} + +void +CWaterLevel::RemoveIsolatedWater() +{ + bool (*isConnected)[MAX_SMALL_SECTORS] = new bool[MAX_SMALL_SECTORS][MAX_SMALL_SECTORS]; + + for (int32 x = 0; x < MAX_SMALL_SECTORS; x++) + { + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + isConnected[x][y] = false; + } + } + + isConnected[0][0] = true; + bool keepGoing; + + do + { + keepGoing = false; + + for (int32 x = 0; x < MAX_SMALL_SECTORS; x++) + { + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + if (aWaterFineBlockList[x][y] < 0 || isConnected[x][y]) + continue; + + if (x > 0 && isConnected[x - 1][y]) + { + isConnected[x][y] = true; + keepGoing = true; + } + + if (y > 0 && isConnected[x][y - 1]) + { + isConnected[x][y] = true; + keepGoing = true; + } + + if (x + 1 < MAX_SMALL_SECTORS && isConnected[x + 1][y]) + { + isConnected[x][y] = true; + keepGoing = true; + } + + if (y + 1 < MAX_SMALL_SECTORS && isConnected[x][y + 1]) + { + isConnected[x][y] = true; + keepGoing = true; + } + } + } + } + while (keepGoing); + + int32 numRemoved = 0; + + for (int32 x = 0; x < MAX_SMALL_SECTORS; x++) + { + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + if (aWaterFineBlockList[x][y] >= 0 && !isConnected[x][y] && ms_aWaterZs[aWaterFineBlockList[x][y]] == 0.0f) + { + numRemoved++; + aWaterFineBlockList[x][y] = NO_WATER; + } + } + } + + printf("Removed %d isolated patches of water\n", numRemoved); + + delete[] isConnected; +} +#endif + +bool +CWaterLevel::GetWaterLevel(float fX, float fY, float fZ, float *pfOutLevel, bool bDontCheckZ) +{ + int32 x = WATER_HUGE_X(fX); + int32 y = WATER_HUGE_Y(fY); + + ASSERT( x >= 0 && x < HUGE_SECTOR_SIZE ); + ASSERT( y >= 0 && y < HUGE_SECTOR_SIZE ); + + int8 nBlock = aWaterFineBlockList[x][y]; + + if ( nBlock == NO_WATER ) + return false; + + ASSERT( pfOutLevel != nil ); + *pfOutLevel = ms_aWaterZs[nBlock]; + + float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f); + + float fWave = Sin + ( + /*( WATER_UNSIGN_Y(fY) - float(y) * MAX_HUGE_SECTORS + WATER_UNSIGN_X(fX) - float(x) * MAX_HUGE_SECTORS )*/ // VC + (float)( ((int32)fX & (MAX_HUGE_SECTORS-1)) + ((int32)fY & (MAX_HUGE_SECTORS-1)) ) + * (TWOPI / MAX_HUGE_SECTORS ) + fAngle + ); + + float fWindFactor = CWeather::Wind * 0.7f + 0.3f; + + *pfOutLevel += fWave * fWindFactor; + + if ( bDontCheckZ == false && (*pfOutLevel - fZ) > 3.0f ) + { + *pfOutLevel = 0.0f; + return false; + } + + return true; +} + +bool +CWaterLevel::GetWaterLevelNoWaves(float fX, float fY, float fZ, float *pfOutLevel) +{ + int32 x = WATER_HUGE_X(fX); + int32 y = WATER_HUGE_Y(fY); + + ASSERT( x >= 0 && x < HUGE_SECTOR_SIZE ); + ASSERT( y >= 0 && y < HUGE_SECTOR_SIZE ); + + int8 nBlock = aWaterFineBlockList[x][y]; + + if ( nBlock == NO_WATER ) + return false; + + ASSERT( pfOutLevel != nil ); + *pfOutLevel = ms_aWaterZs[nBlock]; + + return true; +} + +inline float +_GetWaterDrawDist() +{ + // if z less then 15.0f return 1200.0f + if ( TheCamera.GetPosition().z < 15.0f ) + return 1200.0f; + + // if z greater then 60.0f return 2000.0f; + if ( TheCamera.GetPosition().z > 60.0f ) + return 2000.0f; + + return (TheCamera.GetPosition().z + -15.0f) * 800.0f / 45.0f + 1200.0f; +} + +inline float +_GetWavyDrawDist() +{ + if ( FindPlayerVehicle() && FindPlayerVehicle()->IsBoat() ) + return 120.0f; + else + return 70.0f; +} + +inline void +_GetCamBounds(bool *bUseCamStartY, bool *bUseCamEndY, bool *bUseCamStartX, bool *bUseCamEndX) +{ + if ( TheCamera.GetForward().z > -0.8f ) + { + if ( Abs(TheCamera.GetForward().x) > Abs(TheCamera.GetForward().y) ) + { + if ( TheCamera.GetForward().x > 0.0f ) + *bUseCamStartX = true; + else + *bUseCamEndX = true; + } + else + { + if ( TheCamera.GetForward().y > 0.0f ) + *bUseCamStartY = true; + else + *bUseCamEndY = true; + } + } +} + +inline float +SectorRadius(float fSize) +{ + return Sqrt(Pow(fSize, 2) + Pow(fSize, 2)); +} + +void +CWaterLevel::RenderWater() +{ +//"Custom" Don't Render Water Toggle +#ifndef MASTER + if (gbDontRenderWater) + return; +#endif + PUSH_RENDERGROUP("CWaterLevel::RenderWater"); + bool bUseCamEndX = false; + bool bUseCamStartY = false; + + bool bUseCamStartX = false; + bool bUseCamEndY = false; + + float fWavySectorMaxRenderDist = _GetWavyDrawDist(); + float fWavySectorMaxRenderDistSqr = SQR(fWavySectorMaxRenderDist); + + _GetCamBounds(&bUseCamStartY, &bUseCamEndY, &bUseCamStartX, &bUseCamEndX); + + float fHugeSectorMaxRenderDist = _GetWaterDrawDist(); + float fHugeSectorMaxRenderDistSqr = SQR(fHugeSectorMaxRenderDist); + + float windAddUV = CWeather::Wind * 0.0015f + 0.0005f; + + + if ( !CTimer::GetIsPaused() ) + { +#ifdef FIX_BUGS + TEXTURE_ADDU += (CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV) * CTimer::GetTimeStepFix(); + TEXTURE_ADDV += (CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV) * CTimer::GetTimeStepFix(); +#else + TEXTURE_ADDU += CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV; + TEXTURE_ADDV += CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV; +#endif + } + + if ( TEXTURE_ADDU >= 1.0f ) + TEXTURE_ADDU = 0.0f; + if ( TEXTURE_ADDV >= 1.0f ) + TEXTURE_ADDV = 0.0f; + + WavesCalculatedThisFrame = false; + + RwRGBA color = { 0, 0, 0, 255 }; + + color.red = uint32((CTimeCycle::GetDirectionalRed() * 0.5f + CTimeCycle::GetAmbientRed() ) * 255.0f); + color.green = uint32((CTimeCycle::GetDirectionalGreen() * 0.5f + CTimeCycle::GetAmbientGreen()) * 255.0f); + color.blue = uint32((CTimeCycle::GetDirectionalBlue() * 0.5f + CTimeCycle::GetAmbientBlue() ) * 255.0f); + + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpWaterRaster); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDZERO); + + CVector2D camPos + ( + TheCamera.GetPosition().x, + TheCamera.GetPosition().y + ); + + int32 nStartX = WATER_TO_HUGE_SECTOR_X(camPos.x - fHugeSectorMaxRenderDist); + int32 nEndX = WATER_TO_HUGE_SECTOR_X(camPos.x + fHugeSectorMaxRenderDist) + 1; + int32 nStartY = WATER_TO_HUGE_SECTOR_Y(camPos.y - fHugeSectorMaxRenderDist); + int32 nEndY = WATER_TO_HUGE_SECTOR_Y(camPos.y + fHugeSectorMaxRenderDist) + 1; + + if ( bUseCamStartX ) + nStartX = WATER_TO_HUGE_SECTOR_X(camPos.x); + if ( bUseCamEndX ) + nEndX = WATER_TO_HUGE_SECTOR_X(camPos.x); + if ( bUseCamStartY ) + nStartY = WATER_TO_HUGE_SECTOR_Y(camPos.y); + if ( bUseCamEndY ) + nEndY = WATER_TO_HUGE_SECTOR_Y(camPos.y); + + nStartX = Clamp(nStartX, 0, MAX_HUGE_SECTORS - 1); + nEndX = Clamp(nEndX, 0, MAX_HUGE_SECTORS - 1); + nStartY = Clamp(nStartY, 0, MAX_HUGE_SECTORS - 1); + nEndY = Clamp(nEndY, 0, MAX_HUGE_SECTORS - 1); + + for ( int32 x = nStartX; x <= nEndX; x++ ) + { + for ( int32 y = nStartY; y <= nEndY; y++ ) + { + if ( aWaterBlockList[2*x+0][2*y+0] >= 0 + || aWaterBlockList[2*x+1][2*y+0] >= 0 + || aWaterBlockList[2*x+0][2*y+1] >= 0 + || aWaterBlockList[2*x+1][2*y+1] >= 0 ) + { + float fX = WATER_FROM_HUGE_SECTOR_X(x); + float fY = WATER_FROM_HUGE_SECTOR_Y(y); + + CVector2D vecHugeSectorCentre + ( + fX + HUGE_SECTOR_SIZE/2, + fY + HUGE_SECTOR_SIZE/2 + ); + + float fHugeSectorDistToCamSqr = (camPos - vecHugeSectorCentre).MagnitudeSqr(); + + if ( fHugeSectorMaxRenderDistSqr > fHugeSectorDistToCamSqr ) + { + if ( TheCamera.IsSphereVisible(CVector(vecHugeSectorCentre.x, vecHugeSectorCentre.y, 0.0f), SectorRadius(HUGE_SECTOR_SIZE)) ) + { + if ( fHugeSectorDistToCamSqr >= SQR(500.0f) /*fHugeSectorNearDist*/ ) + { + float fZ; + + if ( aWaterBlockList[2*x+0][2*y+0] >= 0 ) + fZ = ms_aWaterZs[ aWaterBlockList[2*x+0][2*y+0] ]; + + if ( aWaterBlockList[2*x+1][2*y+0] >= 0 ) + fZ = ms_aWaterZs[ aWaterBlockList[2*x+1][2*y+0] ]; + + if ( aWaterBlockList[2*x+0][2*y+1] >= 0 ) + fZ = ms_aWaterZs[ aWaterBlockList[2*x+0][2*y+1] ]; + + if ( aWaterBlockList[2*x+1][2*y+1] >= 0 ) + fZ = ms_aWaterZs[ aWaterBlockList[2*x+1][2*y+1] ]; + + RenderOneFlatHugeWaterPoly(fX, fY, fZ, color); + } + else + { + for ( int32 x2 = 2*x; x2 <= 2*x+1; x2++ ) + { + for ( int32 y2 = 2*y; y2 <= 2*y+1; y2++ ) + { + if ( aWaterBlockList[x2][y2] >= 0 ) + { + float fLargeX = WATER_FROM_LARGE_SECTOR_X(x2); + float fLargeY = WATER_FROM_LARGE_SECTOR_Y(y2); + + CVector2D vecLargeSectorCentre + ( + fLargeX + LARGE_SECTOR_SIZE/2, + fLargeY + LARGE_SECTOR_SIZE/2 + ); + + float fLargeSectorDistToCamSqr = (camPos - vecLargeSectorCentre).MagnitudeSqr(); + + if ( fLargeSectorDistToCamSqr < fHugeSectorMaxRenderDistSqr ) + { + if ( TheCamera.IsSphereVisible(CVector(vecLargeSectorCentre.x, vecLargeSectorCentre.y, 0.0f), SectorRadius(LARGE_SECTOR_SIZE)) ) //90.879997f, + { + // Render four small(32x32) sectors, or one large(64x64). + + // + // [N] + // --------- + // |0x1|1x1| + // [W] --------- [E] + // |0x0|1x0| + // --------- + // [S] + // + + if ( fLargeSectorDistToCamSqr < SQR(176.0f) ) + { + float fZ; + + // WS + if ( aWaterFineBlockList[2*x2+0][2*y2+0] >= 0 ) + { + float fSmallX = fLargeX; + float fSmallY = fLargeY; + + CVector2D vecSmallSectorCentre + ( + fSmallX + SMALL_SECTOR_SIZE/2, + fSmallY + SMALL_SECTOR_SIZE/2 + ); + + float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr(); + fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+0][2*y2+0] ]; + + if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr ) + RenderOneWavySector(fSmallX, fSmallY, fZ, color); + else + RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color); + } + + // SE + if ( aWaterFineBlockList[2*x2+1][2*y2+0] >= 0 ) + { + float fSmallX = fLargeX + (LARGE_SECTOR_SIZE/2); + float fSmallY = fLargeY; + + CVector2D vecSmallSectorCentre + ( + fSmallX + SMALL_SECTOR_SIZE/2, + fSmallY + SMALL_SECTOR_SIZE/2 + ); + + float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr(); + fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+1][2*y2+0] ]; + + if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr ) + RenderOneWavySector(fSmallX, fSmallY, fZ, color); + else + RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color); + } + + // WN + if ( aWaterFineBlockList[2*x2+0][2*y2+1] >= 0 ) + { + float fSmallX = fLargeX; + float fSmallY = fLargeY + (LARGE_SECTOR_SIZE/2); + + CVector2D vecSmallSectorCentre + ( + fSmallX + SMALL_SECTOR_SIZE/2, + fSmallY + SMALL_SECTOR_SIZE/2 + ); + + float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr(); + fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+0][2*y2+1] ]; + + if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr ) + RenderOneWavySector(fSmallX, fSmallY, fZ, color); + else + RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color); + } + + //NE + if ( aWaterFineBlockList[2*x2+1][2*y2+1] >= 0 ) + { + float fSmallX = fLargeX + (LARGE_SECTOR_SIZE/2); + float fSmallY = fLargeY + (LARGE_SECTOR_SIZE/2); + + CVector2D vecSmallSectorCentre + ( + fSmallX + SMALL_SECTOR_SIZE/2, + fSmallY + SMALL_SECTOR_SIZE/2 + ); + + float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr(); + fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+1][2*y2+1] ]; + + if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr ) + RenderOneWavySector(fSmallX, fSmallY, fZ, color); + else + RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color); + } + } + else + { + float fZ; + + fZ = ms_aWaterZs[ aWaterBlockList[x2][y2] ]; + + RenderOneFlatLargeWaterPoly(fLargeX, fLargeY, fZ, color); + } + } // if ( TheCamera.IsSphereVisible + } // if ( fLargeSectorDistToCamSqr < fHugeSectorMaxRenderDistSqr ) + } // if ( aWaterBlockList[x2][y2] >= 0 ) + } // for ( int32 y2 = 2*y; y2 <= 2*y+1; y2++ ) + } // for ( int32 x2 = 2*x; x2 <= 2*x+1; x2++ ) + // + + } + } + } + } + } + } + + /* + ----------- ---------------------- ---------------------- + | [N] | | [ EndY ] | | [ top ] | + | | | | | | + |[W] [0] [E]| |[StartX] [] [ EndX ]| |[ left ] [] [ right]| + | | | | | | + | [S] | | [StartY] | | [bottom] | + ----------- ---------------------- ---------------------- + + + [S] [StartY] [bottom] + [N] [EndY] [top] + [W] [StartX] [left] + [E] [EndX] [right] + + [S] -> [N] && [W] -> [E] + bottom -> top && left -> right + */ + + if ( !bUseCamStartY ) + { + for ( int32 x = 0; x < 26; x++ ) + { + for ( int32 y = 0; y < 5; y++ ) + { + float fX = WATER_SIGN_X(float(x) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + float fY = WATER_SIGN_Y(float(y) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + + CVector2D vecExtraHugeSectorCentre + ( + fX + EXTRAHUGE_SECTOR_SIZE/2, + fY + EXTRAHUGE_SECTOR_SIZE/2 + ); + + float fCamDistToSector = (vecExtraHugeSectorCentre - camPos).Magnitude(); + + if ( fCamDistToSector < fHugeSectorMaxRenderDistSqr ) + { + if ( TheCamera.IsSphereVisible(CVector(vecExtraHugeSectorCentre.x, vecExtraHugeSectorCentre.y, 0.0f), SectorRadius(EXTRAHUGE_SECTOR_SIZE)) ) + { + RenderOneFlatExtraHugeWaterPoly( + vecExtraHugeSectorCentre.x - EXTRAHUGE_SECTOR_SIZE/2, + vecExtraHugeSectorCentre.y - EXTRAHUGE_SECTOR_SIZE/2, + 0.0f, + color); + } + } + } + } + } + + for ( int32 y = 5; y < 21; y++ ) + { + for ( int32 x = 0; x < 5; x++ ) + { + float fX = WATER_SIGN_X(float(x) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + float fX2 = WATER_SIGN_X(float(x) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + float fY = WATER_SIGN_Y(float(y) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + + if ( !bUseCamStartX ) + { + CVector2D vecExtraHugeSectorCentre + ( + fX + EXTRAHUGE_SECTOR_SIZE/2, + fY + EXTRAHUGE_SECTOR_SIZE/2 + ); + + float fCamDistToSector = (vecExtraHugeSectorCentre - camPos).Magnitude(); + + if ( fCamDistToSector < fHugeSectorMaxRenderDistSqr ) + { + if ( TheCamera.IsSphereVisible(CVector(vecExtraHugeSectorCentre.x, vecExtraHugeSectorCentre.y, 0.0f), SectorRadius(EXTRAHUGE_SECTOR_SIZE)) ) + { + RenderOneFlatExtraHugeWaterPoly( + vecExtraHugeSectorCentre.x - EXTRAHUGE_SECTOR_SIZE/2, + vecExtraHugeSectorCentre.y - EXTRAHUGE_SECTOR_SIZE/2, + 0.0f, + color); + } + } + } + + if ( !bUseCamEndX ) + { + CVector2D vecExtraHugeSectorCentre + ( + -(fX2 + EXTRAHUGE_SECTOR_SIZE/2), + fY + EXTRAHUGE_SECTOR_SIZE/2 + ); + + float fCamDistToSector = (vecExtraHugeSectorCentre - camPos).Magnitude(); + + if ( fCamDistToSector < fHugeSectorMaxRenderDistSqr ) + { + if ( TheCamera.IsSphereVisible(CVector(vecExtraHugeSectorCentre.x, vecExtraHugeSectorCentre.y, 0.0f), SectorRadius(EXTRAHUGE_SECTOR_SIZE)) ) + { + RenderOneFlatExtraHugeWaterPoly( + vecExtraHugeSectorCentre.x - EXTRAHUGE_SECTOR_SIZE/2, + vecExtraHugeSectorCentre.y - EXTRAHUGE_SECTOR_SIZE/2, + 0.0f, + color); + } + } + } + } + } + + RenderAndEmptyRenderBuffer(); + + CVector cur_pos = TheCamera.GetPosition(); + + if ( !CCullZones::CamNoRain() + && !CCullZones::PlayerNoRain() + && CWeather::NewWeatherType == WEATHER_SUNNY + && CClock::GetHours() > 6 && CClock::GetHours() < 20 + && WavesCalculatedThisFrame) + { + static CVector prev_pos(0.0f, 0.0f, 0.0f); + static CVector prev_front(0.0f, 0.0f, 0.0f); + static int32 timecounter; + + if ( Abs(prev_pos.x - cur_pos.x) + Abs(prev_pos.y - cur_pos.y) + Abs(prev_pos.z - cur_pos.z) > 1.5f ) + { + prev_pos = cur_pos; + timecounter = CTimer::GetTimeInMilliseconds(); + } + else if ( CTimer::GetTimeInMilliseconds() - timecounter > 5000 ) + { + static int32 birdgenTime = 0; + + if ( CTimer::GetTimeInMilliseconds() - birdgenTime > 1000 ) + { + birdgenTime = CTimer::GetTimeInMilliseconds(); + + CVector vecPos = cur_pos; + + float fAngle = CGeneral::GetRandomNumberInRange(90.0f, 150.0f); + + int32 nRot = CGeneral::GetRandomNumber() % CParticle::SIN_COS_TABLE_SIZE-1; + + float fCos = CParticle::Cos(nRot); + float fSin = CParticle::Sin(nRot); + + vecPos.x += (fCos - fSin) * fAngle; + vecPos.y += (fSin + fCos) * fAngle; + vecPos.z += CGeneral::GetRandomNumberInRange(10.0f, 30.0f); + + CVector vecDir(CGeneral::GetRandomNumberInRange(-1.0f, 1.0f), + CGeneral::GetRandomNumberInRange(-1.0f, 1.0f), + 0.0f); + + CParticle::AddParticle(PARTICLE_BIRD_FRONT, vecPos, vecDir); + } + } + } + + DefinedState(); + + POP_RENDERGROUP(); +} + +void +CWaterLevel::RenderOneFlatSmallWaterPoly(float fX, float fY, float fZ, RwRGBA const &color) +{ + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + int32 vidx = TempBufferVerticesStored; + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + SMALL_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 1.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + SMALL_SECTOR_SIZE, fY + SMALL_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 1.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 1.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + SMALL_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 1.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, color.alpha); + + + int32 iidx = TempBufferIndicesStored; + + TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2; + + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; +} + +void +CWaterLevel::RenderOneFlatLargeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color) +{ + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + int32 vidx = TempBufferVerticesStored; + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + LARGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 2.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + LARGE_SECTOR_SIZE, fY + LARGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 2.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 2.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + LARGE_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 2.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, color.alpha); + + + int32 iidx = TempBufferIndicesStored; + + TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2; + + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; +} + +void +CWaterLevel::RenderOneFlatHugeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color) +{ + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + int32 vidx = TempBufferVerticesStored; + + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + HUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 4.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + HUGE_SECTOR_SIZE, fY + HUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 4.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 4.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + HUGE_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 4.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, 255); + + + int32 iidx = TempBufferIndicesStored; + + TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2; + + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; +} + +void +CWaterLevel::RenderOneFlatExtraHugeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color) +{ + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + int32 vidx = TempBufferVerticesStored; + + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + EXTRAHUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 8.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + EXTRAHUGE_SECTOR_SIZE, fY + EXTRAHUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 8.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 8.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + EXTRAHUGE_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 8.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, 255); + + + int32 iidx = TempBufferIndicesStored; + + TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2; + + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; +} + +void +CWaterLevel::RenderOneWavySector(float fX, float fY, float fZ, RwRGBA const &color, bool bUnk) +{ + float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f); + + if ( !WavesCalculatedThisFrame ) + { + nGeomUsed = 0; + + WavesCalculatedThisFrame = true; + + CBoat::FillBoatList(); + + ASSERT( ms_pWavyAtomic != nil ); + + RpGeometry *geometry = RpAtomicGetGeometry(ms_pWavyAtomic); + + ASSERT( geometry != nil ); + + RwRGBA *wavyPreLights = RpGeometryGetPreLightColors(geometry); + RwTexCoords *wavyTexCoords = RpGeometryGetVertexTexCoords(geometry, rwTEXTURECOORDINATEINDEX0); + RwV3d *wavyVertices = RpMorphTargetGetVertices(RpGeometryGetMorphTarget(geometry, 0)); + + ASSERT( wavyPreLights != nil ); + ASSERT( wavyTexCoords != nil ); + ASSERT( wavyVertices != nil ); + + RpGeometryLock(geometry, rpGEOMETRYLOCKVERTICES + | rpGEOMETRYLOCKPRELIGHT + | rpGEOMETRYLOCKTEXCOORDS); + + for ( int32 i = 0; i < 9; i++ ) + { + for ( int32 j = 0; j < 9; j++ ) + { + wavyTexCoords[9*i+j].u = float(i) / 8 + TEXTURE_ADDV; + wavyTexCoords[9*i+j].v = float(j) / 8 + TEXTURE_ADDU; + RwRGBAAssign(&wavyPreLights[9*i+j], &color); + + wavyVertices[9*i+j].z = ( CWeather::Wind * 0.7f + 0.3f ) + * ( Sin(float(i + j) * DEGTORAD(45.0f) + fAngle) ) + + ( CWeather::Wind * 0.2f * Sin(float(j - i) * PI + (2.0f * fAngle)) ); + } + } + + RpGeometryUnlock(geometry); + } + + static CBoat *apBoatList[4] = { nil }; + + if ( apGeomArray[0] + && nGeomUsed < MAX_BOAT_WAKES + && CBoat::IsSectorAffectedByWake( + CVector2D(fX + (SMALL_SECTOR_SIZE / 2), fY + (SMALL_SECTOR_SIZE / 2)), + SMALL_SECTOR_SIZE / 2, + apBoatList) ) + { + float fWakeColor = fAdd1 - Max(255.0f - float(color.blue + color.red + color.green) / 3, fAdd2); + + RpGeometry *wavyGeometry = RpAtomicGetGeometry(ms_pWavyAtomic); + RpGeometry *geom = apGeomArray[nGeomUsed++]; + + ASSERT( wavyGeometry != nil ); + ASSERT( geom != nil ); + + RpAtomic *atomic = RpAtomicCreate(); + ASSERT( atomic != nil ); + + RpAtomicSetGeometry(atomic, geom, 0); + + RwFrame *frame = RwFrameCreate(); + ASSERT( frame != nil ); + + RwMatrixCopy(RwFrameGetMatrix(frame), RwFrameGetMatrix(RpAtomicGetFrame(ms_pWavyAtomic))); + RpAtomicSetFrame(atomic, frame); + + RwTexCoords *geomTexCoords = RpGeometryGetVertexTexCoords(geom, rwTEXTURECOORDINATEINDEX0); + RwTexCoords *wavyTexCoord = RpGeometryGetVertexTexCoords(wavyGeometry, rwTEXTURECOORDINATEINDEX0); + RwRGBA *geomPreLights = RpGeometryGetPreLightColors(geom); + RwV3d *geomVertices = RpMorphTargetGetVertices(RpGeometryGetMorphTarget(geom, 0)); + RwV3d *wavyVertices = RpMorphTargetGetVertices(RpGeometryGetMorphTarget(wavyGeometry, 0)); + + ASSERT( geomTexCoords != nil ); + ASSERT( wavyTexCoord != nil ); + ASSERT( geomPreLights != nil ); + ASSERT( geomVertices != nil ); + ASSERT( wavyVertices != nil ); + + RpGeometryLock(geom, rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKPRELIGHT | rpGEOMETRYLOCKTEXCOORDS); + + for ( int32 i = 0; i < 9; i++ ) + { + for ( int32 j = 0; j < 9; j++ ) + { + geomTexCoords[9*i+j] = wavyTexCoord[9*i+j]; + + float fVertexX = (float)i * 4.0f + fX; + float fVertexY = (float)j * 4.0f + fY; + + float fDistMult = 0.0f; + + for ( int32 k = 0; k < 4; k++ ) + { + if ( apBoatList[k] != nil ) + fDistMult += CBoat::IsVertexAffectedByWake(CVector(fVertexX, fVertexY, 0.0f), apBoatList[k]); + } + + if ( fDistMult > 0.0f ) + { + RwRGBA wakeColor; + + RwRGBAAssign(&wakeColor, &color); + + wakeColor.red = Min(color.red + int32(fWakeColor * fRedMult * fDistMult), 255); + wakeColor.green = Min(color.green + int32(fWakeColor * fGreenMult * fDistMult), 255); + wakeColor.blue = Min(color.blue + int32(fWakeColor * fBlueMult * fDistMult), 255); + + RwRGBAAssign(&geomPreLights[9*i+j], &wakeColor); + + } + else + RwRGBAAssign(&geomPreLights[9*i+j], &color); + + + geomVertices[9*i+j].z = wavyVertices[9*i+j].z; + } + } + + RpGeometryUnlock(geom); + + + RwV3d pos = {0.0f, 0.0f, 0.0f}; + + pos.x = fX; + pos.z = fZ; + pos.y = fY; + + RwFrameTranslate(RpAtomicGetFrame(atomic), &pos, rwCOMBINEREPLACE); + + RpAtomicRender(atomic); + + RpAtomicDestroy(atomic); + RwFrameDestroy(frame); + } + else + { + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + + pos.x = fX; + pos.y = fY; + pos.z = fZ; + + ASSERT( ms_pWavyAtomic != nil ); + + RwFrameTranslate(RpAtomicGetFrame(ms_pWavyAtomic), &pos, rwCOMBINEREPLACE); + + RpAtomicRender(ms_pWavyAtomic); + } +} + +float +CWaterLevel::CalcDistanceToWater(float fX, float fY) +{ + const float fSectorMaxRenderDist = 75.0f; + + int32 nStartX = WATER_TO_SMALL_SECTOR_X(fX - fSectorMaxRenderDist) - 1; + int32 nEndX = WATER_TO_SMALL_SECTOR_X(fX + fSectorMaxRenderDist) + 1; + int32 nStartY = WATER_TO_SMALL_SECTOR_Y(fY - fSectorMaxRenderDist) - 1; + int32 nEndY = WATER_TO_SMALL_SECTOR_Y(fY + fSectorMaxRenderDist) + 1; + + nStartX = Clamp(nStartX, 0, MAX_SMALL_SECTORS - 1); + nEndX = Clamp(nEndX, 0, MAX_SMALL_SECTORS - 1); + nStartY = Clamp(nStartY, 0, MAX_SMALL_SECTORS - 1); + nEndY = Clamp(nEndY, 0, MAX_SMALL_SECTORS - 1); + + float fDistSqr = 1.0e10f; + + for ( int32 x = nStartX; x <= nEndX; x++ ) + { + for ( int32 y = nStartY; y <= nEndY; y++ ) + { + if ( aWaterFineBlockList[x][y] >= 0 ) + { + float fSectorX = WATER_FROM_SMALL_SECTOR_X(x); + float fSectorY = WATER_FROM_SMALL_SECTOR_Y(y); + + CVector2D vecDist + ( + fSectorX + SMALL_SECTOR_SIZE - fX, + fSectorY + SMALL_SECTOR_SIZE - fY + ); + + fDistSqr = Min(vecDist.MagnitudeSqr(), fDistSqr); + } + } + } + + return Clamp(Sqrt(fDistSqr) - 23.0f, 0.0f, fSectorMaxRenderDist); +} + +void +CWaterLevel::RenderAndEmptyRenderBuffer() +{ + if ( TempBufferVerticesStored ) + { + LittleTest(); + + if ( RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV) ) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + } + + TempBufferIndicesStored = 0; + TempBufferVerticesStored = 0; +} + +void +CWaterLevel::AllocateBoatWakeArray() +{ + CStreaming::MakeSpaceFor(14 * CDSTREAM_SECTOR_SIZE); + + PUSH_MEMID(MEMID_STREAM); + + ASSERT(ms_pWavyAtomic != nil ); + + RpGeometry *wavyGeometry = RpAtomicGetGeometry(ms_pWavyAtomic); + ASSERT(wavyGeometry != nil ); + RpMorphTarget *wavyMorphTarget = RpGeometryGetMorphTarget(wavyGeometry, 0); + RpMaterial *wavyMaterial = RpGeometryGetMaterial(wavyGeometry, 0); + + ASSERT(wavyMorphTarget != nil ); + ASSERT(wavyMaterial != nil ); + + for ( int32 geom = 0; geom < MAX_BOAT_WAKES; geom++ ) + { + if ( apGeomArray[geom] == nil ) + { + apGeomArray[geom] = RpGeometryCreate(9*9, 8*8*2, rpGEOMETRYTRISTRIP + | rpGEOMETRYPRELIT + | rpGEOMETRYMODULATEMATERIALCOLOR + | rpGEOMETRYTEXTURED); + ASSERT(apGeomArray[geom] != nil); + + RpTriangle *geomTriangles = RpGeometryGetTriangles(apGeomArray[geom]); + + ASSERT( geomTriangles != nil ); + + for ( int32 i = 0; i < 8; i++ ) + { + for ( int32 j = 0; j < 8; j++ ) + { + + /* + [B] [C] + *********** + * * * + * * * + * * * + * * * + *********** + [A] [D] + */ + + + RpGeometryTriangleSetVertexIndices(apGeomArray[geom], + &geomTriangles[2 * 8*i + 2*j + 0], /*A*/i*9+j+0, /*B*/i*9+j+1, /*C*/i*9+j+9+1); + + RpGeometryTriangleSetVertexIndices(apGeomArray[geom], + &geomTriangles[2 * 8*i + 2*j + 1], /*A*/i*9+j+0, /*C*/i*9+j+9+1, /*D*/i*9+j+9 ); + + RpGeometryTriangleSetMaterial(apGeomArray[geom], &geomTriangles[2 * 8*i + 2*j + 0], wavyMaterial); + + RpGeometryTriangleSetMaterial(apGeomArray[geom], &geomTriangles[2 * 8*i + 2*j + 1], wavyMaterial); + } + } + + RpMorphTarget *geomMorphTarget = RpGeometryGetMorphTarget(apGeomArray[geom], 0); + RwV3d *geomVertices = RpMorphTargetGetVertices(geomMorphTarget); + + ASSERT( geomMorphTarget != nil ); + ASSERT( geomVertices != nil ); + + for ( int32 i = 0; i < 9; i++ ) + { + for ( int32 j = 0; j < 9; j++ ) + { + geomVertices[9*i+j].x = (float)i * 4.0f; + geomVertices[9*i+j].y = (float)j * 4.0f; + geomVertices[9*i+j].z = 0.0f; + } + } + + RpMorphTargetSetBoundingSphere(geomMorphTarget, RpMorphTargetGetBoundingSphere(wavyMorphTarget)); + RpGeometryUnlock(apGeomArray[geom]); + } + } + + POP_MEMID(); +} + +void +CWaterLevel::FreeBoatWakeArray() +{ + for ( int32 i = 0; i < MAX_BOAT_WAKES; i++ ) + { + if ( apGeomArray[i] != nil ) + { + RpGeometryDestroy(apGeomArray[i]); + apGeomArray[i] = nil; + } + } + + nGeomUsed = 0; +} diff --git a/src/renderer/WaterLevel.h b/src/renderer/WaterLevel.h new file mode 100644 index 0000000..b797f25 --- /dev/null +++ b/src/renderer/WaterLevel.h @@ -0,0 +1,103 @@ +#pragma once + +#define WATER_Z_OFFSET (1.5f) + +#define NO_WATER -128 + +#define MAX_SMALL_SECTORS 128 +#define MAX_LARGE_SECTORS 64 +#define MAX_HUGE_SECTORS 32 +#define MAX_EXTRAHUGE_SECTORS 16 + +#define SMALL_SECTOR_SIZE 32 +#define LARGE_SECTOR_SIZE 64 +#define HUGE_SECTOR_SIZE 128 +#define EXTRAHUGE_SECTOR_SIZE 256 + +#define WATER_START_X -2048.0f +#define WATER_END_X 2048.0f + +#define WATER_START_Y -2048.0f +#define WATER_END_Y 2048.0f + +#define WATER_WIDTH ((WATER_END_X - WATER_START_X)) +#define WATER_HEIGHT ((WATER_END_Y - WATER_START_Y)) + +#define WATER_UNSIGN_X(x) ( (x) + (WATER_WIDTH /2) ) +#define WATER_UNSIGN_Y(y) ( (y) + (WATER_HEIGHT/2) ) +#define WATER_SIGN_X(x) ( (x) - (WATER_WIDTH /2) ) +#define WATER_SIGN_Y(y) ( (y) - (WATER_HEIGHT/2) ) + +// 32 +#define WATER_SMALL_X(x) ( WATER_UNSIGN_X(x) / MAX_SMALL_SECTORS ) +#define WATER_SMALL_Y(y) ( WATER_UNSIGN_Y(y) / MAX_SMALL_SECTORS ) +#define WATER_FROM_SMALL_SECTOR_X(x) ( ((x) - (MAX_SMALL_SECTORS/2) ) * SMALL_SECTOR_SIZE ) +#define WATER_FROM_SMALL_SECTOR_Y(y) ( ((y) - (MAX_SMALL_SECTORS/2) ) * SMALL_SECTOR_SIZE ) +#define WATER_TO_SMALL_SECTOR_X(x) ( WATER_UNSIGN_X(x) / SMALL_SECTOR_SIZE ) +#define WATER_TO_SMALL_SECTOR_Y(y) ( WATER_UNSIGN_Y(y) / SMALL_SECTOR_SIZE ) + +// 64 +#define WATER_LARGE_X(x) ( WATER_UNSIGN_X(x) / MAX_LARGE_SECTORS ) +#define WATER_LARGE_Y(y) ( WATER_UNSIGN_Y(y) / MAX_LARGE_SECTORS ) +#define WATER_FROM_LARGE_SECTOR_X(x) ( ((x) - (MAX_LARGE_SECTORS/2) ) * LARGE_SECTOR_SIZE ) +#define WATER_FROM_LARGE_SECTOR_Y(y) ( ((y) - (MAX_LARGE_SECTORS/2) ) * LARGE_SECTOR_SIZE ) +#define WATER_TO_LARGE_SECTOR_X(x) ( WATER_UNSIGN_X(x) / LARGE_SECTOR_SIZE ) +#define WATER_TO_LARGE_SECTOR_Y(y) ( WATER_UNSIGN_Y(y) / LARGE_SECTOR_SIZE ) + +// 128 +#define WATER_HUGE_X(x) ( WATER_UNSIGN_X(x) / MAX_HUGE_SECTORS ) +#define WATER_HUGE_Y(y) ( WATER_UNSIGN_Y(y) / MAX_HUGE_SECTORS ) +#define WATER_FROM_HUGE_SECTOR_X(x) ( ((x) - (MAX_HUGE_SECTORS/2) ) * HUGE_SECTOR_SIZE ) +#define WATER_FROM_HUGE_SECTOR_Y(y) ( ((y) - (MAX_HUGE_SECTORS/2) ) * HUGE_SECTOR_SIZE ) +#define WATER_TO_HUGE_SECTOR_X(x) ( WATER_UNSIGN_X(x) / HUGE_SECTOR_SIZE ) +#define WATER_TO_HUGE_SECTOR_Y(y) ( WATER_UNSIGN_Y(y) / HUGE_SECTOR_SIZE ) + +// 256 +#define WATER_EXTRAHUGE_X(x) ( WATER_UNSIGN_X(x) / MAX_EXTRAHUGE_SECTORS ) +#define WATER_EXTRAHUGE_Y(y) ( WATER_UNSIGN_Y(y) / MAX_EXTRAHUGE_SECTORS ) +#define WATER_FROM_EXTRAHUGE_SECTOR_X(x) ( ((x) - (MAX_EXTRAHUGE_SECTORS/2)) * EXTRAHUGE_SECTOR_SIZE ) +#define WATER_FROM_EXTRAHUGE_SECTOR_Y(y) ( ((y) - (MAX_EXTRAHUGE_SECTORS/2)) * EXTRAHUGE_SECTOR_SIZE ) +#define WATER_TO_EXTRAHUGE_SECTOR_X(x) ( WATER_UNSIGN_X(x) / EXTRAHUGE_SECTOR_SIZE ) +#define WATER_TO_EXTRAHUGE_SECTOR_Y(y) ( WATER_UNSIGN_Y(y) / EXTRAHUGE_SECTOR_SIZE ) + + +#define MAX_BOAT_WAKES 8 + +extern RwRaster* gpWaterRaster; +extern bool gbDontRenderWater; + +class CWaterLevel +{ + static int32 ms_nNoOfWaterLevels; + static float ms_aWaterZs[48]; + static CRect ms_aWaterRects[48]; + static int8 aWaterBlockList[MAX_LARGE_SECTORS][MAX_LARGE_SECTORS]; + static int8 aWaterFineBlockList[MAX_SMALL_SECTORS][MAX_SMALL_SECTORS]; + static bool WavesCalculatedThisFrame; + static RpAtomic *ms_pWavyAtomic; + static RpGeometry *apGeomArray[MAX_BOAT_WAKES]; + static int16 nGeomUsed; + +public: + static void Initialise(Const char *pWaterDat); // out of class in III PC and later because of SecuROM + static void Shutdown(); + static void CreateWavyAtomic(); + static void DestroyWavyAtomic(); + static void AddWaterLevel(float fXLeft, float fYBottom, float fXRight, float fYTop, float fLevel); + static bool WaterLevelAccordingToRectangles(float fX, float fY, float *pfOutLevel = nil); + static bool TestVisibilityForFineWaterBlocks(const CVector &worldPos); + static void RemoveIsolatedWater(); + static bool GetWaterLevel(float fX, float fY, float fZ, float *pfOutLevel, bool bDontCheckZ); + static bool GetWaterLevel(CVector coors, float *pfOutLevel, bool bDontCheckZ) { return GetWaterLevel(coors.x, coors.y, coors.z, pfOutLevel, bDontCheckZ); } + static bool GetWaterLevelNoWaves(float fX, float fY, float fZ, float *pfOutLevel); + static void RenderWater(); + static void RenderOneFlatSmallWaterPoly (float fX, float fY, float fZ, RwRGBA const &color); + static void RenderOneFlatLargeWaterPoly (float fX, float fY, float fZ, RwRGBA const &color); + static void RenderOneFlatHugeWaterPoly (float fX, float fY, float fZ, RwRGBA const &color); + static void RenderOneFlatExtraHugeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color); + static void RenderOneWavySector (float fX, float fY, float fZ, RwRGBA const &color, bool bUnk = false); + static float CalcDistanceToWater(float fX, float fY); + static void RenderAndEmptyRenderBuffer(); + static void AllocateBoatWakeArray(); + static void FreeBoatWakeArray(); +}; diff --git a/src/renderer/Weather.cpp b/src/renderer/Weather.cpp new file mode 100644 index 0000000..e57d57d --- /dev/null +++ b/src/renderer/Weather.cpp @@ -0,0 +1,552 @@ +#include "common.h" + +#include "Weather.h" + +#include "Camera.h" +#include "Clock.h" +#include "CutsceneMgr.h" +#include "DMAudio.h" +#include "General.h" +#include "Pad.h" +#include "Particle.h" +#include "RenderBuffer.h" +#include "Stats.h" +#include "Shadows.h" +#include "Timecycle.h" +#include "Timer.h" +#include "Vehicle.h" +#include "World.h" +#include "ZoneCull.h" + +int32 CWeather::SoundHandle = -1; + +int32 CWeather::WeatherTypeInList; +int16 CWeather::OldWeatherType; +int16 CWeather::NewWeatherType; +int16 CWeather::ForcedWeatherType; + +bool CWeather::LightningFlash; +bool CWeather::LightningBurst; +uint32 CWeather::LightningStart; +uint32 CWeather::LightningFlashLastChange; +uint32 CWeather::WhenToPlayLightningSound; +uint32 CWeather::LightningDuration; + +float CWeather::Foggyness; +float CWeather::CloudCoverage; +float CWeather::Wind; +float CWeather::Rain; +float CWeather::InterpolationValue; +float CWeather::WetRoads; +float CWeather::Rainbow; + +bool CWeather::bScriptsForceRain; +bool CWeather::Stored_StateStored; + +float CWeather::Stored_InterpolationValue; +int16 CWeather::Stored_OldWeatherType; +int16 CWeather::Stored_NewWeatherType; +float CWeather::Stored_Rain; + +tRainStreak Streaks[NUM_RAIN_STREAKS]; + +const int16 WeatherTypesList[] = { + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_CLOUDY, WEATHER_CLOUDY, WEATHER_RAINY, WEATHER_RAINY, + WEATHER_CLOUDY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_CLOUDY, WEATHER_FOGGY, WEATHER_FOGGY, WEATHER_CLOUDY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_CLOUDY, WEATHER_CLOUDY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_CLOUDY, WEATHER_CLOUDY, WEATHER_RAINY, WEATHER_RAINY, + WEATHER_CLOUDY, WEATHER_RAINY, WEATHER_CLOUDY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_FOGGY, WEATHER_FOGGY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_RAINY, WEATHER_CLOUDY, +}; + +const float Windyness[] = { + 0.0f, // WEATHER_SUNNY + 0.7f, // WEATHER_CLOUDY + 1.0f, // WEATHER_RAINY + 0.5f // WEATHER_FOGGY +}; + +#define MIN_TIME_BETWEEN_LIGHTNING_FLASH_CHANGES (50) + +#define RAIN_CHANGE_SPEED (0.003f) + +#define DROPLETS_LEFT_OFFSET (10.0f) +#define DROPLETS_RIGHT_OFFSET (10.0f) +#define DROPLETS_TOP_OFFSET (10.0f) +#define DROPLETS_BOTTOM_OFFSET (10.0f) + +#define STREAK_U (10.0f) +#define STREAK_V (18.0f) +#define LARGE_STREAK_COEFFICIENT (1.23f) +#define STREAK_MIN_DISTANCE (8.0f) +#define STREAK_MAX_DISTANCE (16.0f) + +#define SPLASH_CHECK_RADIUS (7.0f) +#define SPLASH_OFFSET_RADIUS (2.0f) + +#define STREAK_LIFETIME (4.0f) +#define STREAK_INTEROLATION_TIME (0.3f) + +#define RAIN_COLOUR_R (200) +#define RAIN_COLOUR_G (200) +#define RAIN_COLOUR_B (256) +#define RAIN_ALPHA (255) + +void CWeather::Init(void) +{ + NewWeatherType = WEATHER_SUNNY; + bScriptsForceRain = false; + OldWeatherType = WEATHER_CLOUDY; + Stored_StateStored = false; + InterpolationValue = 0.0f; + WhenToPlayLightningSound = 0; + WeatherTypeInList = 0; + ForcedWeatherType = WEATHER_RANDOM; + SoundHandle = DMAudio.CreateEntity(AUDIOTYPE_WEATHER, (void*)1); + if (SoundHandle >= 0) + DMAudio.SetEntityStatus(SoundHandle, TRUE); +} + +void CWeather::Update(void) +{ + float fNewInterpolation = CClock::GetMinutes() * 1.0f / 60; + if (fNewInterpolation < InterpolationValue) { + // new hour + OldWeatherType = NewWeatherType; + if (ForcedWeatherType >= 0) + NewWeatherType = ForcedWeatherType; + else { + WeatherTypeInList = (WeatherTypeInList + 1) % ARRAY_SIZE(WeatherTypesList); + NewWeatherType = WeatherTypesList[WeatherTypeInList]; +#ifdef FIX_BUGS + } + if (NewWeatherType == WEATHER_RAINY) + CStats::mmRain += CGeneral::GetRandomNumber() & 7; +#else + if (NewWeatherType == WEATHER_RAINY) + CStats::mmRain += CGeneral::GetRandomNumber() & 7; + } +#endif + } + InterpolationValue = fNewInterpolation; + if (CPad::GetPad(1)->GetRightShockJustDown()) { + NewWeatherType = (NewWeatherType + 1) % WEATHER_TOTAL; + OldWeatherType = NewWeatherType; + } + + // Lightning + if (NewWeatherType != WEATHER_RAINY || OldWeatherType != WEATHER_RAINY) { + LightningFlash = false; + LightningBurst = false; + } + else{ + if (LightningBurst) { + if ((CGeneral::GetRandomNumber() & 255) >= 32) { + // 0.875 probability + if (CTimer::GetTimeInMilliseconds() - LightningFlashLastChange > MIN_TIME_BETWEEN_LIGHTNING_FLASH_CHANGES) { + bool bOldLightningFlash = LightningFlash; + LightningFlash = CGeneral::GetRandomTrueFalse(); + if (LightningFlash != bOldLightningFlash) + LightningFlashLastChange = CTimer::GetTimeInMilliseconds(); + } + } + else { + // 0.125 probability + LightningBurst = false; + LightningDuration = Min(CTimer::GetFrameCounter() - LightningStart, 20); + LightningFlash = false; + WhenToPlayLightningSound = CTimer::GetTimeInMilliseconds() + 150 * (20 - LightningDuration); + } + } + else { + if (CGeneral::GetRandomNumber() >= 200) { + // lower probability on PC due to randomness bug + LightningFlash = false; + } + else { + LightningBurst = true; + LightningStart = CTimer::GetFrameCounter(); + LightningFlashLastChange = CTimer::GetTimeInMilliseconds(); + LightningFlash = true; + } + } + } + if (WhenToPlayLightningSound && CTimer::GetTimeInMilliseconds() > WhenToPlayLightningSound) { + DMAudio.PlayOneShot(SoundHandle, SOUND_LIGHTNING, LightningDuration); + CPad::GetPad(0)->StartShake(40 * LightningDuration + 100, 2 * LightningDuration + 80); + WhenToPlayLightningSound = 0; + } + + // Wet roads + if (OldWeatherType == WEATHER_RAINY) { + if (NewWeatherType == WEATHER_RAINY) + WetRoads = 1.0f; + else + WetRoads = 1.0f - InterpolationValue; + } + else { + if (NewWeatherType == WEATHER_RAINY) + WetRoads = InterpolationValue; + else + WetRoads = 0.0f; + } + + // Rain +#ifndef VC_RAIN_NERF + float fNewRain; + if (NewWeatherType == WEATHER_RAINY) { + // if raining for >1 hour, values: 0, 0.33, 0.66, 0.99, switching every ~16.5s + fNewRain = ((uint16)CTimer::GetTimeInMilliseconds() >> 14) * 0.33f; + if (OldWeatherType != WEATHER_RAINY) { + if (InterpolationValue < 0.4f) + // if rain has just started (<24 minutes), always 0.5 + fNewRain = 0.5f; + else + // if rain is ongoing for >24 minutes, values: 0.25, 0.5, 0.75, 1.0, switching every ~16.5s + fNewRain = 0.25f + ((uint16)CTimer::GetTimeInMilliseconds() >> 14) * 0.25f; + } + } + else + fNewRain = 0.0f; + if (Rain != fNewRain) { // ok to use comparasion + if (Rain < fNewRain) + Rain = Min(fNewRain, Rain + RAIN_CHANGE_SPEED * CTimer::GetTimeStep()); + else + Rain = Max(fNewRain, Rain - RAIN_CHANGE_SPEED * CTimer::GetTimeStep()); + } +#else + float fNewRain; + if (NewWeatherType == WEATHER_RAINY) { + // if raining for >1 hour, values: 0, 0.33, switching every ~16.5s + fNewRain = (((uint16)CTimer::GetTimeInMilliseconds() >> 14) & 1) * 0.33f; + if (OldWeatherType != WEATHER_RAINY) { + if (InterpolationValue < 0.4f) + // if rain has just started (<24 minutes), always 0.5 + fNewRain = 0.5f; + else + // if rain is ongoing for >24 minutes, values: 0.25, 0.5, switching every ~16.5s + fNewRain = 0.25f + (((uint16)CTimer::GetTimeInMilliseconds() >> 14) & 1) * 0.25f; + } + fNewRain = Max(fNewRain, 0.5f); + } + else + fNewRain = 0.0f; + Rain = fNewRain; +#endif + + // Clouds + if (OldWeatherType != WEATHER_SUNNY) + CloudCoverage = 1.0f - InterpolationValue; + else + CloudCoverage = 0.0f; + if (NewWeatherType != WEATHER_SUNNY) + CloudCoverage += InterpolationValue; + + // Fog + if (OldWeatherType == WEATHER_FOGGY) + Foggyness = 1.0f - InterpolationValue; + else + Foggyness = 0.0f; + if (NewWeatherType == WEATHER_FOGGY) + Foggyness += InterpolationValue; + if (OldWeatherType == WEATHER_RAINY && NewWeatherType == WEATHER_SUNNY && InterpolationValue < 0.5f && CClock::GetHours() > 6 && CClock::GetHours() < 21) + Rainbow = 1.0f - 4.0f * Abs(InterpolationValue - 0.25f) / 4.0f; + else + Rainbow = 0.0f; + Wind = InterpolationValue * Windyness[NewWeatherType] + (1.0f - InterpolationValue) * Windyness[OldWeatherType]; + AddRain(); +} + +void CWeather::ForceWeather(int16 weather) +{ + ForcedWeatherType = weather; +} + +void CWeather::ForceWeatherNow(int16 weather) +{ + OldWeatherType = weather; + NewWeatherType = weather; + ForcedWeatherType = weather; +} + +void CWeather::ReleaseWeather() +{ + ForcedWeatherType = -1; +} + +void CWeather::AddRain() +{ + if (CCullZones::CamNoRain() || CCullZones::PlayerNoRain()) + return; + if (TheCamera.GetLookingLRBFirstPerson()) { + CVehicle* pVehicle = FindPlayerVehicle(); + if (pVehicle && pVehicle->CarHasRoof()) { + CParticle::RemovePSystem(PARTICLE_RAINDROP_2D); + return; + } + } + if (Rain <= 0.1f) + return; + static RwRGBA colour; + float screen_width = SCREEN_WIDTH; + float screen_height = SCREEN_HEIGHT; + int cur_frame = (int)(3 * Rain) & 3; + int num_drops = (int)(2 * Rain) + 2; + static int STATIC_RAIN_ANGLE = -45; + static int count = 1500; + static int add_angle = 1; + if (--count == 0) { + count = 1; + if (add_angle) { + STATIC_RAIN_ANGLE += 12; + if (STATIC_RAIN_ANGLE > 45) { + count = 1500; + add_angle = !add_angle; + } + } + else { + STATIC_RAIN_ANGLE -= 12; + if (STATIC_RAIN_ANGLE < -45) { + count = 1500; + add_angle = !add_angle; + } + } + } + float rain_angle = DEGTORAD(STATIC_RAIN_ANGLE + ((STATIC_RAIN_ANGLE < 0) ? 360 : 0)); + float sin_angle = Sin(rain_angle); + float cos_angle = Cos(rain_angle); + float base_x = 0.0f * cos_angle - 1.0f * sin_angle; + float base_y = 1.0f * cos_angle + 0.0f * sin_angle; + CVector xpos(0.0f, 0.0f, 0.0f); + for (int i = 0; i < 2 * num_drops; i++) { + CVector dir; + dir.x = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_x) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.y = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_y) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.z = 0; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, xpos, dir, nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f), + colour, 0, rain_angle + CGeneral::GetRandomNumberInRange(-10, 10), cur_frame); + xpos.x += screen_width / (2 * num_drops); + xpos.x += CGeneral::GetRandomNumberInRange(-25.0f, 25.0f); + } + CVector ypos(0.0f, 0.0f, 0.0f); + for (int i = 0; i < num_drops; i++) { + CVector dir; + dir.x = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_x) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.y = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_y) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.z = 0; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, ypos, dir, nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f), + colour, 0, rain_angle + CGeneral::GetRandomNumberInRange(-10, 10), cur_frame); + ypos.y += screen_width / num_drops; + ypos.y += CGeneral::GetRandomNumberInRange(-25.0f, 25.0f); + } + CVector ypos2(0.0f, 0.0f, 0.0f); + for (int i = 0; i < num_drops; i++) { + CVector dir; + dir.x = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_x) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.y = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_y) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.z = 0; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, ypos2, dir, nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f), + colour, 0, rain_angle + CGeneral::GetRandomNumberInRange(-10, 10), cur_frame); + ypos2.y += screen_width / num_drops; + ypos2.y += CGeneral::GetRandomNumberInRange(-25.0f, 25.0f); + } + for (int i = 0; i < num_drops; i++) { + CVector pos; + pos.x = CGeneral::GetRandomNumberInRange(DROPLETS_LEFT_OFFSET, screen_width - DROPLETS_RIGHT_OFFSET); + pos.y = CGeneral::GetRandomNumberInRange(DROPLETS_TOP_OFFSET, screen_height - DROPLETS_TOP_OFFSET); + pos.z = 0.0f; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, pos, CVector(0.0f, 0.0f, 0.0f), nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f), + colour, CGeneral::GetRandomNumberInRange(-10, 10), 360 - rain_angle + CGeneral::GetRandomNumberInRange(-30, 30), cur_frame, 50); + } + int num_splash_attempts = (int)(3 * Rain) + 1; + int num_splashes = (int)(3 * Rain) + 4; + CVector splash_points[4]; + splash_points[0] = CVector(-RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) * + RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude(); + splash_points[1] = CVector(RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) * + RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude(); + splash_points[2] = 4.0f * CVector(-RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) * + RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude(); + splash_points[3] = 4.0f * CVector(RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) * + RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude(); + RwV3dTransformPoints(splash_points, splash_points, 4, RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera))); + CVector fp = (splash_points[0] + splash_points[1] + splash_points[2] + splash_points[3]) / 4; + for (int i = 0; i < num_splash_attempts; i++) { + CColPoint point; + CEntity* entity; + CVector np = fp + CVector(CGeneral::GetRandomNumberInRange(-SPLASH_CHECK_RADIUS, SPLASH_CHECK_RADIUS), CGeneral::GetRandomNumberInRange(-SPLASH_CHECK_RADIUS, SPLASH_CHECK_RADIUS), 0.0f); + if (CWorld::ProcessVerticalLine(np + CVector(0.0f, 0.0f, 40.0f), -40.0f, point, entity, true, false, false, false, true, false, nil)) { + for (int j = 0; j < num_splashes; j++) + CParticle::AddParticle((CGeneral::GetRandomTrueFalse() ? PARTICLE_RAIN_SPLASH : PARTICLE_RAIN_SPLASHUP), + CVector( + np.x + CGeneral::GetRandomNumberInRange(-SPLASH_OFFSET_RADIUS, SPLASH_OFFSET_RADIUS), + np.y + CGeneral::GetRandomNumberInRange(-SPLASH_OFFSET_RADIUS, SPLASH_OFFSET_RADIUS), + point.point.z + 0.1f), + CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, colour); + } + } +} + +void RenderOneRainStreak(CVector pos, CVector unused, int intensity, bool scale, float distance) +{ + static float RandomTex; + static float RandomTexX; + static float RandomTexY; + TempBufferRenderIndexList[TempBufferIndicesStored + 0] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[TempBufferIndicesStored + 1] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[TempBufferIndicesStored + 2] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[TempBufferIndicesStored + 3] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[TempBufferIndicesStored + 4] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[TempBufferIndicesStored + 5] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[TempBufferIndicesStored + 6] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[TempBufferIndicesStored + 7] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[TempBufferIndicesStored + 8] = TempBufferVerticesStored + 4; + TempBufferRenderIndexList[TempBufferIndicesStored + 9] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[TempBufferIndicesStored + 10] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[TempBufferIndicesStored + 11] = TempBufferVerticesStored + 4; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 0], 0, 0, 0, 0); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 0], pos.x + 11.0f * TheCamera.GetUp().x, pos.y + 11.0f * TheCamera.GetUp().y, pos.z + 11.0f * TheCamera.GetUp().z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 1], 0, 0, 0, 0); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 1], pos.x - 9.0f * TheCamera.GetRight().x, pos.y - 9.0f * TheCamera.GetRight().y, pos.z - 9.0f * TheCamera.GetRight().z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 2], RAIN_COLOUR_R * intensity / 256, RAIN_COLOUR_G * intensity / 256, RAIN_COLOUR_B * intensity / 256, RAIN_ALPHA); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 2], pos.x, pos.y, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 3], 0, 0, 0, 0); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 3], pos.x + 9.0f * TheCamera.GetRight().x, pos.y + 9.0f * TheCamera.GetRight().y, pos.z + 9.0f * TheCamera.GetRight().z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 4], 0, 0, 0, 0); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 4], pos.x - 11.0f * TheCamera.GetUp().x, pos.y - 11.0f * TheCamera.GetUp().y, pos.z - 11.0f * TheCamera.GetUp().z); + float u = STREAK_U; + float v = STREAK_V; + if (scale) { + u *= LARGE_STREAK_COEFFICIENT; + v *= LARGE_STREAK_COEFFICIENT; + } + float distance_coefficient; + if (distance < STREAK_MIN_DISTANCE) + distance_coefficient = 1.0f; + else if (distance > STREAK_MAX_DISTANCE) + distance_coefficient = 0.5f; + else + distance_coefficient = 1.0f - 0.5f * (distance - STREAK_MIN_DISTANCE) / (STREAK_MAX_DISTANCE - STREAK_MIN_DISTANCE); + u *= distance_coefficient; + v *= distance_coefficient; + if (!CTimer::GetIsPaused()) { + RandomTex = ((CGeneral::GetRandomNumber() & 255) - 128) * 0.01f; + RandomTexX = (CGeneral::GetRandomNumber() & 127) * 0.01f; + RandomTexY = (CGeneral::GetRandomNumber() & 127) * 0.01f; + } + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 0], 0.5f * u - RandomTex + RandomTexX); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 0], -v * 0.5f + RandomTexY); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 1], RandomTexX); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 1], RandomTexY); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 2], 0.5f * u + RandomTexX); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 2], RandomTexY); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 3], u + RandomTexX); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 3], RandomTexY); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 4], 0.5f * u + RandomTex + RandomTexX); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 5], 0.5f * v + RandomTexY); + TempBufferIndicesStored += 12; + TempBufferVerticesStored += 5; +} + +void CWeather::RenderRainStreaks(void) +{ + if (CTimer::GetIsCodePaused()) + return; + int base_intensity = (64.0f - CTimeCycle::GetFogReduction()) / 64.0f * int(255 * Rain); + if (base_intensity == 0) + return; + TempBufferIndicesStored = 0; + TempBufferVerticesStored = 0; + for (int i = 0; i < NUM_RAIN_STREAKS; i++) { + if (Streaks[i].timer) { + float secondsElapsed = (CTimer::GetTimeInMilliseconds() - Streaks[i].timer) / 1024.0f; + if (secondsElapsed > STREAK_LIFETIME) + Streaks[i].timer = 0; + else{ + int intensity; + if (secondsElapsed < STREAK_INTEROLATION_TIME) + intensity = base_intensity * 0.5f * secondsElapsed / STREAK_INTEROLATION_TIME; + else if (secondsElapsed > (STREAK_LIFETIME - STREAK_INTEROLATION_TIME)) + intensity = (STREAK_LIFETIME - secondsElapsed) * 0.5f * base_intensity / STREAK_INTEROLATION_TIME; + else + intensity = base_intensity * 0.5f; + CVector dir = Streaks[i].direction; + dir.Normalise(); + CVector pos = Streaks[i].position + secondsElapsed * Streaks[i].direction; + RenderOneRainStreak(pos, dir, intensity, false, (pos - TheCamera.GetPosition()).Magnitude()); +#ifndef FIX_BUGS // remove useless code + if (secondsElapsed > 1.0f && secondsElapsed < STREAK_LIFETIME - 1.0f) { + CGeneral::GetRandomNumber(), CGeneral::GetRandomNumber(); + } +#endif + } + } + else if ((CGeneral::GetRandomNumber() & 0xF00) == 0){ + // 1/16 probability + Streaks[i].direction = CVector(4.0f, 4.0f, -4.0f); + Streaks[i].position = 6.0f * TheCamera.GetForward() + TheCamera.GetPosition() + CVector(-1.8f * Streaks[i].direction.x, -1.8f * Streaks[i].direction.y, 8.0f); + if (!CCutsceneMgr::IsRunning()) { + Streaks[i].position.x += 2.0f * FindPlayerSpeed().x * 60.0f; + Streaks[i].position.y += 2.0f * FindPlayerSpeed().y * 60.0f; + } + else + Streaks[i].position += (TheCamera.GetPosition() - TheCamera.m_RealPreviousCameraPosition) * 20.0f; + Streaks[i].position.x += ((CGeneral::GetRandomNumber() & 255) - 128) * 0.08f; + Streaks[i].position.y += ((CGeneral::GetRandomNumber() & 255) - 128) * 0.08f; + Streaks[i].timer = CTimer::GetTimeInMilliseconds(); + } + } + if (TempBufferIndicesStored){ + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEFOGTYPE, (void*)rwFOGTYPELINEAR); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpRainDropTex[3])); + if (RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, 1)) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + } + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; +} + +void CWeather::StoreWeatherState() +{ + Stored_StateStored = true; + Stored_InterpolationValue = InterpolationValue; + Stored_Rain = Rain; + Stored_NewWeatherType = NewWeatherType; + Stored_OldWeatherType = OldWeatherType; +} + +void CWeather::RestoreWeatherState() +{ +#ifdef FIX_BUGS // it's not used anyway though + Stored_StateStored = false; +#endif + InterpolationValue = Stored_InterpolationValue; + Rain = Stored_Rain; + NewWeatherType = Stored_NewWeatherType; + OldWeatherType = Stored_OldWeatherType; +} diff --git a/src/renderer/Weather.h b/src/renderer/Weather.h new file mode 100644 index 0000000..9c67031 --- /dev/null +++ b/src/renderer/Weather.h @@ -0,0 +1,71 @@ +enum { + WEATHER_SUNNY, + WEATHER_CLOUDY, + WEATHER_RAINY, + WEATHER_FOGGY +}; + +class CWeather +{ +public: + enum { + WEATHER_RANDOM = -1, + WEATHER_SUNNY = 0, + WEATHER_CLOUDY = 1, + WEATHER_RAINY = 2, + WEATHER_FOGGY = 3, + WEATHER_TOTAL = 4 + }; + static int32 SoundHandle; + + static int32 WeatherTypeInList; + static int16 OldWeatherType; + static int16 NewWeatherType; + static int16 ForcedWeatherType; + + static bool LightningFlash; + static bool LightningBurst; + static uint32 LightningStart; + static uint32 LightningFlashLastChange; + static uint32 WhenToPlayLightningSound; + static uint32 LightningDuration; + + static float Foggyness; + static float CloudCoverage; + static float Wind; + static float Rain; + static float InterpolationValue; + static float WetRoads; + static float Rainbow; + + static bool bScriptsForceRain; + static bool Stored_StateStored; + static float Stored_InterpolationValue; + static int16 Stored_OldWeatherType; + static int16 Stored_NewWeatherType; + static float Stored_Rain; + + static void RenderRainStreaks(void); + static void Update(void); + static void Init(void); + + static void ReleaseWeather(); + static void ForceWeather(int16); + static void ForceWeatherNow(int16); + static void StoreWeatherState(); + static void RestoreWeatherState(); + static void AddRain(); +}; + +enum { + NUM_RAIN_STREAKS = 35 +}; + +struct tRainStreak +{ + CVector position; + CVector direction; + uint32 timer; +}; + +extern RwTexture* gpRainDropTex[4]; \ No newline at end of file diff --git a/src/rw/ClumpRead.cpp b/src/rw/ClumpRead.cpp new file mode 100644 index 0000000..5f50f52 --- /dev/null +++ b/src/rw/ClumpRead.cpp @@ -0,0 +1,224 @@ +#include "common.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 = STREAMPOS(stream); + return true; +} + +RpClump* +RpClumpGtaStreamRead2(RwStream *stream) +{ + int i; + RwUInt32 version; + RpAtomic *atomic; + RpClump *clump; + + clump = RpClumpCreate(); + if(clump == nil) + return nil; + + RwStreamSkip(stream, streamPosition - STREAMPOS(stream)); + + 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; +} diff --git a/src/rw/Lights.cpp b/src/rw/Lights.cpp new file mode 100644 index 0000000..b3cef6d --- /dev/null +++ b/src/rw/Lights.cpp @@ -0,0 +1,355 @@ +#include "common.h" +#include +#include + +#include "Lights.h" +#include "Timer.h" +#include "Timecycle.h" +#include "Coronas.h" +#include "Weather.h" +#include "ZoneCull.h" +#include "Frontend.h" + +RpLight *pAmbient; +RpLight *pDirect; +RpLight *pExtraDirectionals[4] = { nil }; +int LightStrengths[4]; +int NumExtraDirLightsInWorld; + +RwRGBAReal AmbientLightColourForFrame; +RwRGBAReal AmbientLightColourForFrame_PedsCarsAndObjects; +RwRGBAReal DirectionalLightColourForFrame; + +RwRGBAReal AmbientLightColour; +RwRGBAReal DirectionalLightColour; + +void +SetLightsWithTimeOfDayColour(RpWorld *) +{ + CVector vec1, vec2, vecsun; + RwMatrix mat; + + if(pAmbient){ + AmbientLightColourForFrame.red = CTimeCycle::GetAmbientRed() * CCoronas::LightsMult; + AmbientLightColourForFrame.green = CTimeCycle::GetAmbientGreen() * CCoronas::LightsMult; + AmbientLightColourForFrame.blue = CTimeCycle::GetAmbientBlue() * CCoronas::LightsMult; + if(CWeather::LightningFlash && !CCullZones::CamNoRain()){ + AmbientLightColourForFrame.red = 1.0f; + AmbientLightColourForFrame.green = 1.0f; + AmbientLightColourForFrame.blue = 1.0f; + } + AmbientLightColourForFrame_PedsCarsAndObjects.red = Min(1.0f, AmbientLightColourForFrame.red*1.3f); + AmbientLightColourForFrame_PedsCarsAndObjects.green = Min(1.0f, AmbientLightColourForFrame.green*1.3f); + AmbientLightColourForFrame_PedsCarsAndObjects.blue = Min(1.0f, AmbientLightColourForFrame.blue*1.3f); + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); + } + + if(pDirect){ + DirectionalLightColourForFrame.red = CTimeCycle::GetDirectionalRed() * CCoronas::LightsMult; + DirectionalLightColourForFrame.green = CTimeCycle::GetDirectionalGreen() * CCoronas::LightsMult; + DirectionalLightColourForFrame.blue = CTimeCycle::GetDirectionalBlue() * CCoronas::LightsMult; + RpLightSetColor(pDirect, &DirectionalLightColourForFrame); + + vecsun = CTimeCycle::m_VectorToSun[CTimeCycle::m_CurrentStoredValue]; + vec1 = CVector(0.0f, 0.0f, 1.0f); + vec2 = CrossProduct(vec1, vecsun); + vec2.Normalise(); + vec1 = CrossProduct(vec2, vecsun); + mat.at.x = -vecsun.x; + mat.at.y = -vecsun.y; + mat.at.z = -vecsun.z; + mat.right.x = vec1.x; + mat.right.y = vec1.y; + mat.right.z = vec1.z; + mat.up.x = vec2.x; + mat.up.y = vec2.y; + mat.up.z = vec2.z; + RwFrameTransform(RpLightGetFrame(pDirect), &mat, rwCOMBINEREPLACE); + } + + if(CMenuManager::m_PrefsBrightness > 256){ + float f1 = 2.0f * (CMenuManager::m_PrefsBrightness/256.0f - 1.0f) * 0.6f + 1.0f; + float f2 = 3.0f * (CMenuManager::m_PrefsBrightness/256.0f - 1.0f) * 0.6f + 1.0f; + + AmbientLightColourForFrame.red = Min(1.0f, AmbientLightColourForFrame.red * f2); + AmbientLightColourForFrame.green = Min(1.0f, AmbientLightColourForFrame.green * f2); + AmbientLightColourForFrame.blue = Min(1.0f, AmbientLightColourForFrame.blue * f2); + AmbientLightColourForFrame_PedsCarsAndObjects.red = Min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.red * f1); + AmbientLightColourForFrame_PedsCarsAndObjects.green = Min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.green * f1); + AmbientLightColourForFrame_PedsCarsAndObjects.blue = Min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.blue * f1); +#ifdef FIX_BUGS + DirectionalLightColourForFrame.red = Min(1.0f, DirectionalLightColourForFrame.red * f1); + DirectionalLightColourForFrame.green = Min(1.0f, DirectionalLightColourForFrame.green * f1); + DirectionalLightColourForFrame.blue = Min(1.0f, DirectionalLightColourForFrame.blue * f1); +#else + DirectionalLightColourForFrame.red = Min(1.0f, AmbientLightColourForFrame.red * f1); + DirectionalLightColourForFrame.green = Min(1.0f, AmbientLightColourForFrame.green * f1); + DirectionalLightColourForFrame.blue = Min(1.0f, AmbientLightColourForFrame.blue * f1); +#endif + } +} + +RpWorld* +LightsCreate(RpWorld *world) +{ + int i; + RwRGBAReal color; + RwFrame *frame; + + if(world == nil) + return nil; + + pAmbient = RpLightCreate(rpLIGHTAMBIENT); + RpLightSetFlags(pAmbient, rpLIGHTLIGHTATOMICS); + color.red = 0.25f; + color.green = 0.25f; + color.blue = 0.2f; + RpLightSetColor(pAmbient, &color); + + pDirect = RpLightCreate(rpLIGHTDIRECTIONAL); + RpLightSetFlags(pDirect, rpLIGHTLIGHTATOMICS); + color.red = 1.0f; + color.green = 0.85f; + color.blue = 0.45f; + RpLightSetColor(pDirect, &color); + RpLightSetRadius(pDirect, 2.0f); + frame = RwFrameCreate(); + RpLightSetFrame(pDirect, frame); + RwV3d axis = { 1.0f, 1.0f, 0.0f }; + RwFrameRotate(frame, &axis, 160.0f, rwCOMBINEPRECONCAT); + + RpWorldAddLight(world, pAmbient); + RpWorldAddLight(world, pDirect); + + for(i = 0; i < NUMEXTRADIRECTIONALS; i++){ + pExtraDirectionals[i] = RpLightCreate(rpLIGHTDIRECTIONAL); + RpLightSetFlags(pExtraDirectionals[i], 0); + color.red = 1.0f; + color.green = 0.5f; + color.blue = 0.0f; + RpLightSetColor(pExtraDirectionals[i], &color); + RpLightSetRadius(pExtraDirectionals[i], 2.0f); + frame = RwFrameCreate(); + RpLightSetFrame(pExtraDirectionals[i], frame); + RpWorldAddLight(world, pExtraDirectionals[i]); + } + + return world; +} + +void +LightsDestroy(RpWorld *world) +{ + int i; + + if(world == nil) + return; + + if(pAmbient){ + RpWorldRemoveLight(world, pAmbient); + RpLightDestroy(pAmbient); + pAmbient = nil; + } + + if(pDirect){ + RpWorldRemoveLight(world, pDirect); + RwFrameDestroy(RpLightGetFrame(pDirect)); + RpLightDestroy(pDirect); + pDirect = nil; + } + + for(i = 0; i < NUMEXTRADIRECTIONALS; i++) + if(pExtraDirectionals[i]){ + RpWorldRemoveLight(world, pExtraDirectionals[i]); + RwFrameDestroy(RpLightGetFrame(pExtraDirectionals[i])); + RpLightDestroy(pExtraDirectionals[i]); + pExtraDirectionals[i] = nil; + } +} + +void +WorldReplaceNormalLightsWithScorched(RpWorld *world, float l) +{ + RwRGBAReal color; + color.red = l; + color.green = l; + color.blue = l; + RpLightSetColor(pAmbient, &color); + RpLightSetFlags(pDirect, 0); +} + +void +WorldReplaceScorchedLightsWithNormal(RpWorld *world) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); + RpLightSetFlags(pDirect, rpLIGHTLIGHTATOMICS); +} + +void +AddAnExtraDirectionalLight(RpWorld *world, float dirx, float diry, float dirz, float red, float green, float blue) +{ + float strength; + int weakest; + int i, n; + RwRGBAReal color; + RwV3d *dir; + + strength = Max(Max(red, green), blue); + n = -1; + if(NumExtraDirLightsInWorld < NUMEXTRADIRECTIONALS) + n = NumExtraDirLightsInWorld; + else{ + weakest = strength; + for(i = 0; i < NUMEXTRADIRECTIONALS; i++) + if(LightStrengths[i] < weakest){ + weakest = LightStrengths[i]; + n = i; + } + } + + if(n < 0) + return; + + color.red = red; + color.green = green; + color.blue = blue; + RpLightSetColor(pExtraDirectionals[n], &color); + dir = RwMatrixGetAt(RwFrameGetMatrix(RpLightGetFrame(pExtraDirectionals[n]))); + dir->x = -dirx; + dir->y = -diry; + dir->z = -dirz; + RwMatrixUpdate(RwFrameGetMatrix(RpLightGetFrame(pExtraDirectionals[n]))); + RwFrameUpdateObjects(RpLightGetFrame(pExtraDirectionals[n])); + RpLightSetFlags(pExtraDirectionals[n], rpLIGHTLIGHTATOMICS); + LightStrengths[n] = strength; + NumExtraDirLightsInWorld = Min(NumExtraDirLightsInWorld+1, NUMEXTRADIRECTIONALS); +} + +void +RemoveExtraDirectionalLights(RpWorld *world) +{ + int i; + for(i = 0; i < NumExtraDirLightsInWorld; i++) + RpLightSetFlags(pExtraDirectionals[i], 0); + NumExtraDirLightsInWorld = 0; +} + +void +SetAmbientAndDirectionalColours(float f) +{ + AmbientLightColour.red = AmbientLightColourForFrame.red * f; + AmbientLightColour.green = AmbientLightColourForFrame.green * f; + AmbientLightColour.blue = AmbientLightColourForFrame.blue * f; + + DirectionalLightColour.red = DirectionalLightColourForFrame.red * f; + DirectionalLightColour.green = DirectionalLightColourForFrame.green * f; + DirectionalLightColour.blue = DirectionalLightColourForFrame.blue * f; + + RpLightSetColor(pAmbient, &AmbientLightColour); + RpLightSetColor(pDirect, &DirectionalLightColour); +} + +// unused +void +SetFlashyColours(float f) +{ + if(CTimer::GetTimeInMilliseconds() & 0x100){ + AmbientLightColour.red = 1.0f; + AmbientLightColour.green = 1.0f; + AmbientLightColour.blue = 1.0f; + + DirectionalLightColour.red = DirectionalLightColourForFrame.red; + DirectionalLightColour.green = DirectionalLightColourForFrame.green; + DirectionalLightColour.blue = DirectionalLightColourForFrame.blue; + + RpLightSetColor(pAmbient, &AmbientLightColour); + RpLightSetColor(pDirect, &DirectionalLightColour); + }else{ + SetAmbientAndDirectionalColours(f * 0.75f); + } +} + +// unused +void +SetFlashyColours_Mild(float f) +{ + if(CTimer::GetTimeInMilliseconds() & 0x100){ + AmbientLightColour.red = 0.65f; + AmbientLightColour.green = 0.65f; + AmbientLightColour.blue = 0.65f; + + DirectionalLightColour.red = DirectionalLightColourForFrame.red; + DirectionalLightColour.green = DirectionalLightColourForFrame.green; + DirectionalLightColour.blue = DirectionalLightColourForFrame.blue; + + RpLightSetColor(pAmbient, &AmbientLightColour); + RpLightSetColor(pDirect, &DirectionalLightColour); + }else{ + SetAmbientAndDirectionalColours(f * 0.9f); + } +} + +void +SetBrightMarkerColours(float f) +{ + AmbientLightColour.red = 0.6f; + AmbientLightColour.green = 0.6f; + AmbientLightColour.blue = 0.6f; + + DirectionalLightColour.red = (1.0f - DirectionalLightColourForFrame.red) * 0.4f + DirectionalLightColourForFrame.red; + DirectionalLightColour.green = (1.0f - DirectionalLightColourForFrame.green) * 0.4f + DirectionalLightColourForFrame.green; + DirectionalLightColour.blue = (1.0f - DirectionalLightColourForFrame.blue) * 0.4f + DirectionalLightColourForFrame.blue; + + RpLightSetColor(pAmbient, &AmbientLightColour); + RpLightSetColor(pDirect, &DirectionalLightColour); +} + +void +ReSetAmbientAndDirectionalColours(void) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); + RpLightSetColor(pDirect, &DirectionalLightColourForFrame); +} + +void +DeActivateDirectional(void) +{ + RpLightSetFlags(pDirect, 0); +} + +void +ActivateDirectional(void) +{ + RpLightSetFlags(pDirect, rpLIGHTLIGHTATOMICS); +} + +void +SetAmbientColours(void) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); +} + +void +SetAmbientColoursForPedsCarsAndObjects(void) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame_PedsCarsAndObjects); +} + +uint8 IndicateR[] = { 0, 255, 0, 0, 255, 255, 0 }; +uint8 IndicateG[] = { 0, 0, 255, 0, 255, 0, 255 }; +uint8 IndicateB[] = { 0, 0, 0, 255, 0, 255, 255 }; + +void +SetAmbientColoursToIndicateRoadGroup(int i) +{ + AmbientLightColour.red = IndicateR[i%7]/255.0f; + AmbientLightColour.green = IndicateG[i%7]/255.0f; + AmbientLightColour.blue = IndicateB[i%7]/255.0f; + RpLightSetColor(pAmbient, &AmbientLightColour); +} + +void +SetAmbientColours(RwRGBAReal *color) +{ + RpLightSetColor(pAmbient, color); +} diff --git a/src/rw/Lights.h b/src/rw/Lights.h new file mode 100644 index 0000000..5057f1d --- /dev/null +++ b/src/rw/Lights.h @@ -0,0 +1,26 @@ +#pragma once + +extern RpLight *pAmbient; +extern RpLight *pDirect; +extern RpLight *pExtraDirectionals[4]; +extern int LightStrengths[4]; +extern int NumExtraDirLightsInWorld; + +void SetLightsWithTimeOfDayColour(RpWorld *); +RpWorld *LightsCreate(RpWorld *world); +void LightsDestroy(RpWorld *world); +void WorldReplaceNormalLightsWithScorched(RpWorld *world, float l); +void WorldReplaceScorchedLightsWithNormal(RpWorld *world); +void AddAnExtraDirectionalLight(RpWorld *world, float dirx, float diry, float dirz, float red, float green, float blue); +void RemoveExtraDirectionalLights(RpWorld *world); +void SetAmbientAndDirectionalColours(float f); +void SetFlashyColours(float f); +void SetFlashyColours_Mild(float f); +void SetBrightMarkerColours(float f); +void ReSetAmbientAndDirectionalColours(void); +void DeActivateDirectional(void); +void ActivateDirectional(void); +void SetAmbientColours(void); +void SetAmbientColoursForPedsCarsAndObjects(void); +void SetAmbientColoursToIndicateRoadGroup(int i); +void SetAmbientColours(RwRGBAReal *color); diff --git a/src/rw/MemoryHeap.cpp b/src/rw/MemoryHeap.cpp new file mode 100644 index 0000000..469262d --- /dev/null +++ b/src/rw/MemoryHeap.cpp @@ -0,0 +1,497 @@ +#include "common.h" +#include "main.h" +#include "FileMgr.h" +#include "Timer.h" +#include "ModelInfo.h" +#include "Streaming.h" +#include "FileLoader.h" +#include "MemoryHeap.h" + +#ifdef USE_CUSTOM_ALLOCATOR + +//#define MEMORYHEAP_ASSERT(cond) { if (!(cond)) { printf("ASSERT File:%s Line:%d\n", __FILE__, __LINE__); exit(1); } } +//#define MEMORYHEAP_ASSERT_MESSAGE(cond, message) { if (!(cond)) { printf("ASSERT File:%s Line:%d:\n\t%s\n", __FILE__, __LINE__, message); exit(1); } } + +#define MEMORYHEAP_ASSERT(cond) assert(cond) +#define MEMORYHEAP_ASSERT_MESSAGE(cond, message) assert(cond) + +// registered pointers that we keep track of +void **gPtrList[4000]; +int32 numPtrs; +int32 gPosnInList; +// indices into the ptr list in here are free +CStack m_ptrListIndexStack; +// how much memory we've moved +uint32 memMoved; + +CMemoryHeap gMainHeap; + +void +CMemoryHeap::Init(uint32 total) +{ + MEMORYHEAP_ASSERT((total != 0xF) != 0); + + m_totalMemUsed = 0; + m_memUsed = nil; + m_currentMemID = MEMID_FREE; + m_blocksUsed = nil; + m_totalBlocksUsed = 0; + m_unkMemId = -1; + + uint8 *mem = (uint8*)malloc(total); + assert(((uintptr)mem & 0xF) == 0); + m_start = (HeapBlockDesc*)mem; + m_end = (HeapBlockDesc*)(mem + total - sizeof(HeapBlockDesc)); + m_start->m_memId = MEMID_FREE; + m_start->m_size = total - 2*sizeof(HeapBlockDesc); + m_end->m_memId = MEMID_GAME; + m_end->m_size = 0; + + m_freeList.m_last.m_size = INT_MAX; + m_freeList.Init(); + m_freeList.Insert(m_start); + + // TODO: figure out what these are and use sizeof + m_fixedSize[0].Init(0x10); + m_fixedSize[1].Init(0x20); + m_fixedSize[2].Init(0xE0); + m_fixedSize[3].Init(0x60); + m_fixedSize[4].Init(0x1C0); + m_fixedSize[5].Init(0x50); + + m_currentMemID = MEMID_FREE; // disable registration + m_memUsed = (uint32*)Malloc(NUM_MEMIDS * sizeof(uint32)); + m_blocksUsed = (uint32*)Malloc(NUM_MEMIDS * sizeof(uint32)); + RegisterMalloc(GetDescFromHeapPointer(m_memUsed)); + RegisterMalloc(GetDescFromHeapPointer(m_blocksUsed)); + + m_currentMemID = MEMID_GAME; + for(int i = 0; i < NUM_MEMIDS; i++){ + m_memUsed[i] = 0; + m_blocksUsed[i] = 0; + } +} + +void +CMemoryHeap::RegisterMalloc(HeapBlockDesc *block) +{ + block->m_memId = m_currentMemID; + if(m_currentMemID == MEMID_FREE) + return; + m_totalMemUsed += block->m_size + sizeof(HeapBlockDesc); + m_memUsed[m_currentMemID] += block->m_size + sizeof(HeapBlockDesc); + m_blocksUsed[m_currentMemID]++; + m_totalBlocksUsed++; +} + +void +CMemoryHeap::RegisterFree(HeapBlockDesc *block) +{ + if(block->m_memId == MEMID_FREE) + return; + m_totalMemUsed -= block->m_size + sizeof(HeapBlockDesc); + m_memUsed[block->m_memId] -= block->m_size + sizeof(HeapBlockDesc); + m_blocksUsed[block->m_memId]--; + m_totalBlocksUsed--; +} + +void* +CMemoryHeap::Malloc(uint32 size) +{ + static int recursion = 0; + + // weird way to round up + if((size & 0xF) != 0) + size = (size&~0xF) + 0x10; + + recursion++; + + // See if we can allocate from one of the fixed-size lists + for(int i = 0; i < NUM_FIXED_MEMBLOCKS; i++){ + CommonSize *list = &m_fixedSize[i]; + if(m_fixedSize[i].m_size == size){ + HeapBlockDesc *block = list->Malloc(); + if(block){ + RegisterMalloc(block); + recursion--; + return block->GetDataPointer(); + } + break; + } + } + + // now try the normal free list + HeapBlockDesc *next; + for(HeapBlockDesc *block = m_freeList.m_first.m_next; + block != &m_freeList.m_last; + block = next){ + MEMORYHEAP_ASSERT(block->m_memId == MEMID_FREE); + MEMORYHEAP_ASSERT_MESSAGE(block >= m_start && block <= m_end, "Block outside of memory"); + + // make sure block has maximum size + uint32 initialsize = block->m_size; + uint32 blocksize = CombineFreeBlocks(block); +#ifdef FIX_BUGS + // has to be done here because block can be moved + next = block->m_next; +#endif + if(initialsize != blocksize){ + block->RemoveHeapFreeBlock(); + HeapBlockDesc *pos = block->m_prev->FindSmallestFreeBlock(block->m_size); + block->InsertHeapFreeBlock(pos->m_prev); + } + if(block->m_size >= size){ + // got space to allocate from! + block->RemoveHeapFreeBlock(); + FillInBlockData(block, block->GetNextConsecutive(), size); + recursion--; + return block->GetDataPointer(); + } +#ifndef FIX_BUGS + next = block->m_next; +#endif + } + + // oh no, we're losing, try to free some stuff + static bool removeCollision = false; + static bool removeIslands = false; + static bool removeBigBuildings = false; + size_t initialMemoryUsed = CStreaming::ms_memoryUsed; + CStreaming::MakeSpaceFor(0xCFE800 - CStreaming::ms_memoryUsed); + if (recursion > 10) + CGame::TidyUpMemory(true, false); + else if (recursion > 6) + CGame::TidyUpMemory(false, true); + if (initialMemoryUsed == CStreaming::ms_memoryUsed && recursion > 11) { + if (!removeCollision && !CGame::playingIntro) { + CModelInfo::RemoveColModelsFromOtherLevels(LEVEL_GENERIC); + removeCollision = true; + } + else if (!removeIslands && !CGame::playingIntro) { + CStreaming::RemoveIslandsNotUsed(LEVEL_INDUSTRIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_COMMERCIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN); + removeIslands = true; + } + else if (!removeBigBuildings) { + CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL); + CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL); + CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN); + } + else { + LoadingScreen("NO MORE MEMORY", nil, nil); + LoadingScreen("NO MORE MEMORY", nil, nil); + } + CGame::TidyUpMemory(true, false); + } + void *mem = Malloc(size); + if (removeCollision) { + CTimer::Stop(); + // TODO: different on PS2 + CFileLoader::LoadCollisionFromDatFile(CCollision::ms_collisionInMemory); + removeCollision = false; + CTimer::Update(); + } + if (removeBigBuildings || removeIslands) { + CTimer::Stop(); + if (!CGame::playingIntro) + CStreaming::RequestBigBuildings(CGame::currLevel); + CStreaming::LoadAllRequestedModels(true); + removeBigBuildings = false; + removeIslands = false; + CTimer::Update(); + } + recursion--; + return mem; +} + +void* +CMemoryHeap::Realloc(void *ptr, uint32 size) +{ + if(ptr == nil) + return Malloc(size); + + // weird way to round up + if((size & 0xF) != 0) + size = (size&~0xF) + 0x10; + + HeapBlockDesc *block = GetDescFromHeapPointer(ptr); + +#ifdef FIX_BUGS + // better handling of size < block->m_size + if(size == 0){ + Free(ptr); + return nil; + } + if(block->m_size >= size){ + // shrink allocated block + RegisterFree(block); + PushMemId(block->m_memId); + FillInBlockData(block, block->GetNextConsecutive(), size); + PopMemId(); + return ptr; + } +#else + // not growing. just returning here is a bit cheap though + if(block->m_size >= size) + return ptr; +#endif + + // have to grow allocated block + HeapBlockDesc *next = block->GetNextConsecutive(); + MEMORYHEAP_ASSERT_MESSAGE(next >= m_start && next <= m_end, "Block outside of memory"); + if(next->m_memId == MEMID_FREE){ + // try to grow the current block + // make sure the next free block has maximum size + uint32 freespace = CombineFreeBlocks(next); + HeapBlockDesc *end = next->GetNextConsecutive(); + MEMORYHEAP_ASSERT_MESSAGE(end >= m_start && end <= m_end, "Block outside of memory"); + // why the sizeof here? + if(block->m_size + next->m_size + sizeof(HeapBlockDesc) >= size){ + // enough space to grow + next->RemoveHeapFreeBlock(); + RegisterFree(block); + PushMemId(block->m_memId); + FillInBlockData(block, next->GetNextConsecutive(), size); + PopMemId(); + return ptr; + } + } + + // can't grow the existing block, have to get a new one and copy + PushMemId(block->m_memId); + void *dst = Malloc(size); + PopMemId(); + memcpy(dst, ptr, block->m_size); + Free(ptr); + return dst; +} + +void +CMemoryHeap::Free(void *ptr) +{ + HeapBlockDesc *block = GetDescFromHeapPointer(ptr); + MEMORYHEAP_ASSERT_MESSAGE(block->m_memId != MEMID_FREE, "MemoryHeap corrupt"); + MEMORYHEAP_ASSERT(m_unkMemId == -1 || m_unkMemId == block->m_memId); + + RegisterFree(block); + block->m_memId = MEMID_FREE; + CombineFreeBlocks(block); + FreeBlock(block); + if(block->m_ptrListIndex != -1){ + int32 idx = block->m_ptrListIndex; + gPtrList[idx] = nil; + m_ptrListIndexStack.push(idx); + } + block->m_ptrListIndex = -1; +} + +// allocate 'size' bytes from 'block' +void +CMemoryHeap::FillInBlockData(HeapBlockDesc *block, HeapBlockDesc *end, uint32 size) +{ + block->m_size = size; + block->m_ptrListIndex = -1; + HeapBlockDesc *remainder = block->GetNextConsecutive(); + MEMORYHEAP_ASSERT(remainder <= end); + + if(remainder < end-1){ + RegisterMalloc(block); + + // can fit another block in the remaining space + remainder->m_size = GetSizeBetweenBlocks(remainder, end); + remainder->m_memId = MEMID_FREE; + MEMORYHEAP_ASSERT(remainder->m_size != 0); + FreeBlock(remainder); + }else{ + // fully allocate this one + if(remainder < end) + // no gaps allowed + block->m_size = GetSizeBetweenBlocks(block, end); + RegisterMalloc(block); + } +} + +// Make sure free block has no other free blocks after it +uint32 +CMemoryHeap::CombineFreeBlocks(HeapBlockDesc *block) +{ + HeapBlockDesc *next = block->GetNextConsecutive(); + if(next->m_memId != MEMID_FREE) + return block->m_size; + // get rid of free blocks after this one and adjust size + for(; next->m_memId == MEMID_FREE; next = next->GetNextConsecutive()) + next->RemoveHeapFreeBlock(); + block->m_size = GetSizeBetweenBlocks(block, next); + return block->m_size; +} + +// Try to move all registered memory blocks into more optimal location +void +CMemoryHeap::TidyHeap(void) +{ + for(int i = 0; i < numPtrs; i++){ + if(gPtrList[i] == nil || *gPtrList[i] == nil) + continue; + HeapBlockDesc *newblock = WhereShouldMemoryMove(*gPtrList[i]); + if(newblock) + *gPtrList[i] = MoveHeapBlock(newblock, GetDescFromHeapPointer(*gPtrList[i])); + } +} + +// +void +CMemoryHeap::RegisterMemPointer(void *ptr) +{ + HeapBlockDesc *block = GetDescFromHeapPointer(*(void**)ptr); + + if(block->m_ptrListIndex != -1) + return; // already registered + + int index; + if(m_ptrListIndexStack.sp > 0){ + // re-use a previously free'd index + index = m_ptrListIndexStack.pop(); + }else{ + // have to find a new index + index = gPosnInList; + + void **pp = gPtrList[index]; + // we're replacing an old pointer here?? + if(pp && *pp && *pp != (void*)0xDDDDDDDD) + GetDescFromHeapPointer(*pp)->m_ptrListIndex = -1; + + gPosnInList++; + if(gPosnInList == 4000) + gPosnInList = 0; + if(numPtrs < 4000) + numPtrs++; + } + gPtrList[index] = (void**)ptr; + block->m_ptrListIndex = index; +} + +void* +CMemoryHeap::MoveMemory(void *ptr) +{ + HeapBlockDesc *newblock = WhereShouldMemoryMove(ptr); + if(newblock) + return MoveHeapBlock(newblock, GetDescFromHeapPointer(ptr)); + else + return ptr; +} + +HeapBlockDesc* +CMemoryHeap::WhereShouldMemoryMove(void *ptr) +{ + HeapBlockDesc *block = GetDescFromHeapPointer(ptr); + MEMORYHEAP_ASSERT(block->m_memId != MEMID_FREE); + + HeapBlockDesc *next = block->GetNextConsecutive(); + if(next->m_memId != MEMID_FREE) + return nil; + + // we want to move the block into another block + // such that the free space between this and the next block can be minimized + HeapBlockDesc *newblock = m_freeList.m_first.FindSmallestFreeBlock(block->m_size); + // size of free space wouldn't decrease, so return + if(newblock->m_size >= block->m_size + next->m_size) + return nil; + // size of free space wouldn't decrease enough + if(newblock->m_size >= 16 + 1.125f*block->m_size) // what are 16 and 1.125 here? sizeof(HeapBlockDesc)? + return nil; + return newblock; +} + +void* +CMemoryHeap::MoveHeapBlock(HeapBlockDesc *dst, HeapBlockDesc *src) +{ + PushMemId(src->m_memId); + dst->RemoveHeapFreeBlock(); + FillInBlockData(dst, dst->GetNextConsecutive(), src->m_size); + PopMemId(); + memcpy(dst->GetDataPointer(), src->GetDataPointer(), src->m_size); + memMoved += src->m_size; + dst->m_ptrListIndex = src->m_ptrListIndex; + src->m_ptrListIndex = -1; + Free(src->GetDataPointer()); + return dst->GetDataPointer(); +} + +uint32 +CMemoryHeap::GetMemoryUsed(int32 id) +{ + return m_memUsed[id]; +} + +uint32 +CMemoryHeap::GetBlocksUsed(int32 id) +{ + return m_blocksUsed[id]; +} + +void +CMemoryHeap::PopMemId(void) +{ + assert(m_idStack.sp > 0); + m_currentMemID = m_idStack.pop(); + assert(m_currentMemID != MEMID_FREE); +} + +void +CMemoryHeap::PushMemId(int32 id) +{ + MEMORYHEAP_ASSERT(id != MEMID_FREE); + assert(m_idStack.sp < 16); + m_idStack.push(m_currentMemID); + m_currentMemID = id; +} + +void +CMemoryHeap::ParseHeap(void) +{ + char tmp[16]; + int fd = CFileMgr::OpenFileForWriting("heap.txt"); + CTimer::Stop(); + + // CMemoryHeap::IntegrityCheck(); + + uint32 addrQW = 0; + for(HeapBlockDesc *block = m_start; block < m_end; block = block->GetNextConsecutive()){ + char chr = '*'; // free + if(block->m_memId != MEMID_FREE) + chr = block->m_memId-1 + 'A'; + int numQW = block->m_size>>4; + + if((addrQW & 0x3F) == 0){ + sprintf(tmp, "\n%5dK:", addrQW>>6); + CFileMgr::Write(fd, tmp, 8); + } + CFileMgr::Write(fd, "#", 1); // the descriptor, has to be 16 bytes!!!! + addrQW++; + + while(numQW--){ + if((addrQW & 0x3F) == 0){ + sprintf(tmp, "\n%5dK:", addrQW>>6); + CFileMgr::Write(fd, tmp, 8); + } + CFileMgr::Write(fd, &chr, 1); + addrQW++; + } + } + + CTimer::Update(); + CFileMgr::CloseFile(fd); +} + + +void +CommonSize::Init(uint32 size) +{ + m_freeList.Init(); + m_size = size; + m_failed = 0; + m_remaining = 0; +} + +#endif diff --git a/src/rw/MemoryHeap.h b/src/rw/MemoryHeap.h new file mode 100644 index 0000000..cd8cf22 --- /dev/null +++ b/src/rw/MemoryHeap.h @@ -0,0 +1,202 @@ +#pragma once + +// some windows shit +#ifdef MoveMemory +#undef MoveMemory +#endif + +#ifdef USE_CUSTOM_ALLOCATOR +#define PUSH_MEMID(id) gMainHeap.PushMemId(id) +#define POP_MEMID() gMainHeap.PopMemId() +#define REGISTER_MEMPTR(ptr) gMainHeap.RegisterMemPointer(ptr) +#else +#define PUSH_MEMID(id) +#define POP_MEMID() +#define REGISTER_MEMPTR(ptr) +#endif + +enum { + MEMID_FREE, + MEMID_GAME = 1, // "Game" + MEMID_WORLD = 2, // "World" + MEMID_ANIMATION = 3, // "Animation" + MEMID_POOLS = 4, // "Pools" + MEMID_DEF_MODELS = 5, // "Default Models" + MEMID_STREAM = 6, // "Streaming" + MEMID_STREAM_MODELS = 7, // "Streamed Models" (instance) + MEMID_STREAM_TEXUTRES = 8, // "Streamed Textures" + MEMID_TEXTURES = 9, // "Textures" + MEMID_COLLISION = 10, // "Collision" + MEMID_RENDERLIST = 11, // ? + MEMID_GAME_PROCESS = 12, // "Game Process" + MEMID_SCRIPT = 13, // "Script" + MEMID_CARS = 14, // "Cars" + MEMID_RENDER = 15, // "Render" + MEMID_FRONTEND = 17, // ? + + NUM_MEMIDS, + + NUM_FIXED_MEMBLOCKS = 6 +}; + +template +class CStack +{ +public: + T values[N]; + uint32 sp; + + CStack() : sp(0) {} + void push(const T& val) { values[sp++] = val; } + T& pop() { return values[--sp]; } +}; + + +struct HeapBlockDesc +{ + uint32 m_size; + int16 m_memId; + int16 m_ptrListIndex; + HeapBlockDesc *m_next; + HeapBlockDesc *m_prev; + + HeapBlockDesc *GetNextConsecutive(void) + { + return (HeapBlockDesc*)((uintptr)this + sizeof(HeapBlockDesc) + m_size); + } + + void *GetDataPointer(void) + { + return (void*)((uintptr)this + sizeof(HeapBlockDesc)); + } + + void RemoveHeapFreeBlock(void) + { + m_next->m_prev = m_prev; + m_prev->m_next = m_next; + } + + // after node + void InsertHeapFreeBlock(HeapBlockDesc *node) + { + m_next = node->m_next; + node->m_next->m_prev = this; + m_prev = node; + node->m_next = this; + } + + HeapBlockDesc *FindSmallestFreeBlock(uint32 size) + { + HeapBlockDesc *b; + for(b = m_next; b->m_size < size; b = b->m_next); + return b; + } +}; + +#ifdef USE_CUSTOM_ALLOCATOR +// TODO: figure something out for 64 bit pointers +static_assert(sizeof(HeapBlockDesc) == 0x10, "HeapBlockDesc must have 0x10 size otherwise most of assumptions don't make sense"); +#endif + +struct HeapBlockList +{ + HeapBlockDesc m_first; + HeapBlockDesc m_last; + + void Init(void) + { + m_first.m_next = &m_last; + m_last.m_prev = &m_first; + } + + void Insert(HeapBlockDesc *node) + { + node->InsertHeapFreeBlock(&m_first); + } +}; + +struct CommonSize +{ + HeapBlockList m_freeList; + uint32 m_size; + uint32 m_failed; + uint32 m_remaining; + + void Init(uint32 size); + void Free(HeapBlockDesc *node) + { + m_freeList.Insert(node); + m_remaining++; + } + HeapBlockDesc *Malloc(void) + { + if(m_freeList.m_first.m_next == &m_freeList.m_last){ + m_failed++; + return nil; + } + HeapBlockDesc *block = m_freeList.m_first.m_next; + m_remaining--; + block->RemoveHeapFreeBlock(); + block->m_ptrListIndex = -1; + return block; + } +}; + +class CMemoryHeap +{ +public: + HeapBlockDesc *m_start; + HeapBlockDesc *m_end; + HeapBlockList m_freeList; + CommonSize m_fixedSize[NUM_FIXED_MEMBLOCKS]; + uint32 m_totalMemUsed; + CStack m_idStack; + uint32 m_currentMemID; + uint32 *m_memUsed; + uint32 m_totalBlocksUsed; + uint32 *m_blocksUsed; + uint32 m_unkMemId; + + CMemoryHeap(void) : m_start(nil) {} + void Init(uint32 total); + void RegisterMalloc(HeapBlockDesc *block); + void RegisterFree(HeapBlockDesc *block); + void *Malloc(uint32 size); + void *Realloc(void *ptr, uint32 size); + void Free(void *ptr); + void FillInBlockData(HeapBlockDesc *block, HeapBlockDesc *end, uint32 size); + uint32 CombineFreeBlocks(HeapBlockDesc *block); + void *MoveMemory(void *ptr); + HeapBlockDesc *WhereShouldMemoryMove(void *ptr); + void *MoveHeapBlock(HeapBlockDesc *dst, HeapBlockDesc *src); + void PopMemId(void); + void PushMemId(int32 id); + void RegisterMemPointer(void *ptr); + void TidyHeap(void); + uint32 GetMemoryUsed(int32 id); + uint32 GetBlocksUsed(int32 id); + int32 GetLargestFreeBlock(void) { return m_freeList.m_last.m_prev->m_size; } + + void ParseHeap(void); + + HeapBlockDesc *GetDescFromHeapPointer(void *block) + { + return (HeapBlockDesc*)((uintptr)block - sizeof(HeapBlockDesc)); + } + uint32 GetSizeBetweenBlocks(HeapBlockDesc *first, HeapBlockDesc *second) + { + return (uintptr)second - (uintptr)first - sizeof(HeapBlockDesc); + } + void FreeBlock(HeapBlockDesc *block){ + for(int i = 0; i < NUM_FIXED_MEMBLOCKS; i++){ + if(m_fixedSize[i].m_size == block->m_size){ + m_fixedSize[i].Free(block); + return; + } + } + HeapBlockDesc *b = m_freeList.m_first.FindSmallestFreeBlock(block->m_size); + block->InsertHeapFreeBlock(b->m_prev); + } +}; + +extern CMemoryHeap gMainHeap; diff --git a/src/rw/MemoryMgr.cpp b/src/rw/MemoryMgr.cpp new file mode 100644 index 0000000..b9cff04 --- /dev/null +++ b/src/rw/MemoryMgr.cpp @@ -0,0 +1,130 @@ +#include "common.h" +#include "MemoryHeap.h" +#include "MemoryMgr.h" + + +uint8 *pMemoryTop; + +void +InitMemoryMgr(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR +#ifdef GTA_PS2 +#error "finish this" +#else + // randomly allocate 128mb + gMainHeap.Init(128*1024*1024); +#endif +#endif +} + + +RwMemoryFunctions memFuncs = { + MemoryMgrMalloc, + MemoryMgrFree, + MemoryMgrRealloc, + MemoryMgrCalloc +}; + +#ifdef USE_CUSTOM_ALLOCATOR +// game seems to be using heap directly here, but this is nicer +void *operator new(size_t sz) throw() { return MemoryMgrMalloc(sz); } +void *operator new[](size_t sz) throw() { return MemoryMgrMalloc(sz); } +void operator delete(void *ptr) throw() { MemoryMgrFree(ptr); } +void operator delete[](void *ptr) throw() { MemoryMgrFree(ptr); } +#endif + +void* +MemoryMgrMalloc(size_t size) +{ +#ifdef USE_CUSTOM_ALLOCATOR + void *mem = gMainHeap.Malloc(size); +#else + void *mem = malloc(size); +#endif + if((uint8*)mem + size > pMemoryTop) + pMemoryTop = (uint8*)mem + size ; + return mem; +} + +void* +MemoryMgrRealloc(void *ptr, size_t size) +{ +#ifdef USE_CUSTOM_ALLOCATOR + void *mem = gMainHeap.Realloc(ptr, size); +#else + void *mem = realloc(ptr, size); +#endif + if((uint8*)mem + size > pMemoryTop) + pMemoryTop = (uint8*)mem + size ; + return mem; +} + +void* +MemoryMgrCalloc(size_t num, size_t size) +{ +#ifdef USE_CUSTOM_ALLOCATOR + void *mem = gMainHeap.Malloc(num*size); +#else + void *mem = calloc(num, size); +#endif + if((uint8*)mem + size > pMemoryTop) + pMemoryTop = (uint8*)mem + size ; +#ifdef FIX_BUGS + memset(mem, 0, num*size); +#endif + return mem; +} + +void +MemoryMgrFree(void *ptr) +{ +#ifdef USE_CUSTOM_ALLOCATOR +#ifdef FIX_BUGS + // i don't suppose this is handled by RW? + if(ptr == nil) return; +#endif + gMainHeap.Free(ptr); +#else + free(ptr); +#endif +} + +void * +RwMallocAlign(RwUInt32 size, RwUInt32 align) +{ +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) + uintptr ptralign = align-1; + void *mem = (void *)MemoryMgrMalloc(size + sizeof(uintptr) + ptralign); + + ASSERT(mem != nil); + + void *addr = (void *)((((uintptr)mem) + sizeof(uintptr) + ptralign) & ~ptralign); + + ASSERT(addr != nil); +#else + void *mem = (void *)MemoryMgrMalloc(size + align); + + ASSERT(mem != nil); + + void *addr = (void *)((((uintptr)mem) + align) & ~(align - 1)); + + ASSERT(addr != nil); +#endif + + *(((void **)addr) - 1) = mem; + + return addr; +} + +void +RwFreeAlign(void *mem) +{ + ASSERT(mem != nil); + + void *addr = *(((void **)mem) - 1); + + ASSERT(addr != nil); + + MemoryMgrFree(addr); +} diff --git a/src/rw/MemoryMgr.h b/src/rw/MemoryMgr.h new file mode 100644 index 0000000..e296280 --- /dev/null +++ b/src/rw/MemoryMgr.h @@ -0,0 +1,12 @@ +#pragma once + +extern RwMemoryFunctions memFuncs; +void InitMemoryMgr(void); + +void *MemoryMgrMalloc(size_t size); +void *MemoryMgrRealloc(void *ptr, size_t size); +void *MemoryMgrCalloc(size_t num, size_t size); +void MemoryMgrFree(void *ptr); + +void *RwMallocAlign(RwUInt32 size, RwUInt32 align); +void RwFreeAlign(void *mem); diff --git a/src/rw/NodeName.cpp b/src/rw/NodeName.cpp new file mode 100644 index 0000000..a7185e4 --- /dev/null +++ b/src/rw/NodeName.cpp @@ -0,0 +1,77 @@ +#include "common.h" + +#include "NodeName.h" + +static int32 gPluginOffset; + +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) +{ + char *name = NODENAMEEXT(object); // can't be nil + return name ? (RwInt32)rwstrlen(name) : 0; +} + +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/rw/NodeName.h b/src/rw/NodeName.h new file mode 100644 index 0000000..1a3e057 --- /dev/null +++ b/src/rw/NodeName.h @@ -0,0 +1,4 @@ +#pragma once + +bool NodeNamePluginAttach(void); +char *GetFrameNodeName(RwFrame *frame); diff --git a/src/rw/RwHelper.cpp b/src/rw/RwHelper.cpp new file mode 100644 index 0000000..3559372 --- /dev/null +++ b/src/rw/RwHelper.cpp @@ -0,0 +1,735 @@ +#define WITHD3D +#include "common.h" +#include + +#include "RwHelper.h" +#include "Timecycle.h" +#include "skeleton.h" +#include "Debug.h" +#include "MBlur.h" +#if !defined(FINAL) || defined(DEBUGMENU) +#include "rtcharse.h" +#endif +#ifndef FINAL +RtCharset *debugCharset; +bool bDebugRenderGroups; +#endif + +#ifdef PS2_ALPHA_TEST +bool gPS2alphaTest = true; +#else +bool gPS2alphaTest = false; +#endif +bool gBackfaceCulling = true; + +#if !defined(FINAL) || defined(DEBUGMENU) +static bool charsetOpen; +void OpenCharsetSafe() +{ + if(!charsetOpen) + RtCharsetOpen(); + charsetOpen = true; +} +#endif + +void CreateDebugFont() +{ +#ifndef FINAL + RwRGBA color = { 255, 255, 128, 255 }; + RwRGBA colorbg = { 0, 0, 0, 0 }; + OpenCharsetSafe(); + debugCharset = RtCharsetCreate(&color, &colorbg); +#endif +} + +void DestroyDebugFont() +{ +#ifndef FINAL + RtCharsetDestroy(debugCharset); + RtCharsetClose(); + charsetOpen = false; +#endif +} + +void ObrsPrintfString(const char *str, short x, short y) +{ +#ifndef FINAL + RtCharsetPrintBuffered(debugCharset, str, x*8, y*16, true); +#endif +} + +void FlushObrsPrintfs() +{ +#ifndef FINAL + RtCharsetBufferFlush(); +#endif +} + +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); + +#ifdef LIBRW + rw::SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAGREATEREQUAL); + + rw::SetRenderState(rw::GSALPHATEST, gPS2alphaTest); +#else + // D3D stuff + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER); +#endif + SetAlphaRef(2); +} + +void +SetAlphaRef(int ref) +{ +#ifdef LIBRW + rw::SetRenderState(rw::ALPHATESTREF, ref+1); +#else + RwD3D8SetRenderState(D3DRS_ALPHAREF, ref); +#endif +} + +void +SetCullMode(uint32 mode) +{ + if(gBackfaceCulling) + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)mode); + else + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); +} + +#ifndef FINAL +void +PushRendergroup(const char *name) +{ + if(!bDebugRenderGroups) + return; +#if defined(RW_OPENGL) + if(GLAD_GL_KHR_debug) + glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, name); +#elif defined(RW_D3D9) + static WCHAR tmp[256]; + MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, name, -1, tmp, sizeof(tmp)); + D3DPERF_BeginEvent(0xFFFFFFFF, tmp); +#endif +} + +void +PopRendergroup(void) +{ + if(!bDebugRenderGroups) + return; +#if defined(RW_OPENGL) + if(GLAD_GL_KHR_debug) + glPopDebugGroup(); +#elif defined(RW_D3D9) + D3DPERF_EndEvent(); +#endif +} +#endif + +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; +} + +#ifdef PED_SKIN +static RpAtomic* +isSkinnedCb(RpAtomic *atomic, void *data) +{ + RpAtomic **pAtomic = (RpAtomic**)data; + if(*pAtomic) + return nil; // already found one + if(RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic))) + *pAtomic = atomic; // we could just return nil here directly... + return atomic; +} + +RpAtomic* +IsClumpSkinned(RpClump *clump) +{ + RpAtomic *atomic = nil; + RpClumpForAllAtomics(clump, isSkinnedCb, &atomic); + return atomic; +} + +static RpAtomic* +GetAnimHierarchyCallback(RpAtomic *atomic, void *data) +{ + *(RpHAnimHierarchy**)data = RpSkinAtomicGetHAnimHierarchy(atomic); + return nil; +} + +RpHAnimHierarchy* +GetAnimHierarchyFromSkinClump(RpClump *clump) +{ + RpHAnimHierarchy *hier = nil; + RpClumpForAllAtomics(clump, GetAnimHierarchyCallback, &hier); + return hier; +} + +static RwFrame* +GetAnimHierarchyFromClumpCB(RwFrame *frame, void *data) +{ + RpHAnimHierarchy *hier = RpHAnimFrameGetHierarchy(frame); + if(hier){ + *(RpHAnimHierarchy**)data = hier; + return nil; + } + RwFrameForAllChildren(frame, GetAnimHierarchyFromClumpCB, data); + return frame; +} + +RpHAnimHierarchy* +GetAnimHierarchyFromClump(RpClump *clump) +{ + RpHAnimHierarchy *hier = nil; + RwFrameForAllChildren(RpClumpGetFrame(clump), GetAnimHierarchyFromClumpCB, &hier); + return hier; +} + +RwFrame* +GetHierarchyFromChildNodesCB(RwFrame *frame, void *data) +{ + RpHAnimHierarchy **pHier = (RpHAnimHierarchy**)data; + RpHAnimHierarchy *hier = RpHAnimFrameGetHierarchy(frame); + if(hier == nil) + RwFrameForAllChildren(frame, GetHierarchyFromChildNodesCB, &hier); + *pHier = hier; + return nil; +} + +void +SkinGetBonePositionsToTable(RpClump *clump, RwV3d *boneTable) +{ + int i, parent; + RpAtomic *atomic; + RpSkin *skin; + RpHAnimHierarchy *hier; + int numBones; + RwMatrix m, invmat; + int stack[32]; + int sp; + + if(boneTable == nil) + return; + +// atomic = GetFirstAtomic(clump); // mobile, also VC + atomic = IsClumpSkinned(clump); // xbox, seems safer + assert(atomic); + skin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic)); + assert(skin); + hier = GetAnimHierarchyFromSkinClump(clump); + assert(hier); + boneTable[0].x = 0.0f; + boneTable[0].y = 0.0f; + boneTable[0].z = 0.0f; + numBones = RpSkinGetNumBones(skin); + parent = 0; + sp = 0; +#ifdef FIX_BUGS + stack[0] = 0; // i think this is ok +#endif + for(i = 1; i < numBones; i++){ + RwMatrixCopy(&m, &RpSkinGetSkinToBoneMatrices(skin)[i]); + RwMatrixInvert(&invmat, &m); + const RwMatrix *x = RpSkinGetSkinToBoneMatrices(skin); + RwV3dTransformPoints(&boneTable[i], &invmat.pos, 1, &x[parent]); + if(HIERNODEINFO(hier)[i].flags & rpHANIMPUSHPARENTMATRIX) + stack[++sp] = parent; + if(HIERNODEINFO(hier)[i].flags & rpHANIMPOPPARENTMATRIX) + parent = stack[sp--]; + else + parent = i; + + //assert(parent >= 0 && parent < numBones); + } +} + +RpHAnimAnimation* +HAnimAnimationCreateForHierarchy(RpHAnimHierarchy *hier) +{ + int i; +#if defined FIX_BUGS || defined LIBRW + int numNodes = hier->numNodes*2; // you're supposed to have at least two KFs per node +#else + int numNodes = hier->numNodes; +#endif + RpHAnimAnimation *anim = RpHAnimAnimationCreate(rpHANIMSTDKEYFRAMETYPEID, numNodes, 0, 0.0f); + if(anim == nil) + return nil; + RpHAnimStdKeyFrame *frame; + for(i = 0; i < numNodes; i++){ + frame = (RpHAnimStdKeyFrame*)HANIMFRAME(anim, i); // games uses struct size here, not safe + frame->q.real = 1.0f; + frame->q.imag.x = frame->q.imag.y = frame->q.imag.z = 0.0f; + frame->t.x = frame->t.y = frame->t.z = 0.0f; +#if defined FIX_BUGS || defined LIBRW + // times are subtracted and divided giving NaNs + // so they can't both be 0 + frame->time = i/hier->numNodes; +#else + frame->time = 0.0f; +#endif + frame->prevFrame = nil; + } + return anim; +} + +void +RenderSkeleton(RpHAnimHierarchy *hier) +{ + int i; + int sp; + int stack[32]; + int par; + CVector p1, p2; + int numNodes = hier->numNodes; + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + p1 = mats[0].pos; + + par = 0; + sp = 0; + stack[sp++] = par; + for(i = 1; i < numNodes; i++){ + p1 = mats[par].pos; + p2 = mats[i].pos; + CDebug::AddLine(p1, p2, 0xFFFFFFFF, 0xFFFFFFFF); + if(HIERNODEINFO(hier)[i].flags & rpHANIMPUSHPARENTMATRIX) + stack[sp++] = par; + par = i; + if(HIERNODEINFO(hier)[i].flags & rpHANIMPOPPARENTMATRIX) + par = stack[--sp]; + } +} +#endif + +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; + + // BUG: game just changes camera raster's sizes, but this is a hack +#if defined FIX_BUGS || defined LIBRW + /* + * Destroy rasters... + */ + + raster = RwCameraGetRaster(camera); + if( raster ) + { + RwRasterDestroy(raster); + camera->frameBuffer = nil; + } + + zRaster = RwCameraGetZRaster(camera); + if( zRaster ) + { + RwRasterDestroy(zRaster); + camera->zBuffer = nil; + } + + /* + * 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); + } +#else + raster = RwCameraGetRaster(camera); + zRaster = RwCameraGetZRaster(camera); + + raster->width = zRaster->width = rect->w; + raster->height = zRaster->height = rect->h; +#endif +#ifdef FIX_BUGS + if(CMBlur::BlurOn){ + CMBlur::MotionBlurClose(); + CMBlur::MotionBlurOpen(camera); + } +#endif + } + + /* 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); +} + +#ifdef LIBRW +#include +#include "VehicleModelInfo.h" + +int32 +findPlatform(rw::Atomic *a) +{ + rw::Geometry *g = a->geometry; + if(g->instData) + return g->instData->platform; + return 0; +} + +// in CVehicleModelInfo in VC +static RpMaterial* +GetMatFXEffectMaterialCB(RpMaterial *material, void *data) +{ + if(RpMatFXMaterialGetEffects(material) == rpMATFXEFFECTNULL) + return material; + *(int*)data = RpMatFXMaterialGetEffects(material); + return nil; +} + +// Game doesn't read atomic extensions so we never get any other than the default pipe, +// but we need it for uninstancing +void +attachPipe(rw::Atomic *atomic) +{ + if(RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic))) + atomic->pipeline = rw::skinGlobals.pipelines[rw::platform]; + else{ + int fx = rpMATFXEFFECTNULL; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), GetMatFXEffectMaterialCB, &fx); + if(fx != rpMATFXEFFECTNULL) + RpMatFXAtomicEnableEffects(atomic); + } +} + +// Attach pipes for the platform we have native data for so we can uninstance +void +switchPipes(rw::Atomic *a, int32 platform) +{ + if(a->pipeline && a->pipeline->platform != platform){ + uint32 plgid = a->pipeline->pluginID; + switch(plgid){ + // assume default pipe won't be attached explicitly + case rw::ID_SKIN: + a->pipeline = rw::skinGlobals.pipelines[platform]; + break; + case rw::ID_MATFX: + a->pipeline = rw::matFXGlobals.pipelines[platform]; + break; + } + } +} + +RpAtomic* +ConvertPlatformAtomic(RpAtomic *atomic, void *data) +{ + int32 driver = rw::platform; + int32 platform = findPlatform(atomic); + if(platform != 0 && platform != driver){ + attachPipe(atomic); // kludge + rw::ObjPipeline *origPipe = atomic->pipeline; + rw::platform = platform; + switchPipes(atomic, rw::platform); + if(atomic->geometry->flags & rw::Geometry::NATIVE) + atomic->uninstance(); + // no ADC in this game + //rw::ps2::unconvertADC(atomic->geometry); + rw::platform = driver; + atomic->pipeline = origPipe; + } + return atomic; +} +#endif + +#if defined(FIX_BUGS) && defined(GTA_PC) +RwUInt32 saved_alphafunc, saved_alpharef; + +void +SetAlphaTest(RwUInt32 alpharef) +{ +#ifdef LIBRW + saved_alphafunc = rw::GetRenderState(rw::ALPHATESTFUNC); + saved_alpharef = rw::GetRenderState(rw::ALPHATESTREF); + + rw::SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAGREATEREQUAL); + rw::SetRenderState(rw::ALPHATESTREF, 0); +#else + RwD3D8GetRenderState(D3DRS_ALPHAFUNC, &saved_alphafunc); + RwD3D8GetRenderState(D3DRS_ALPHAREF, &saved_alpharef); + + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL); + RwD3D8SetRenderState(D3DRS_ALPHAREF, alpharef); +#endif +} + +void +RestoreAlphaTest() +{ +#ifdef LIBRW + rw::SetRenderState(rw::ALPHATESTFUNC, saved_alphafunc); + rw::SetRenderState(rw::ALPHATESTREF, saved_alpharef); +#else + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, saved_alphafunc); + RwD3D8SetRenderState(D3DRS_ALPHAREF, saved_alpharef); +#endif +} +#endif diff --git a/src/rw/RwHelper.h b/src/rw/RwHelper.h new file mode 100644 index 0000000..0e04aec --- /dev/null +++ b/src/rw/RwHelper.h @@ -0,0 +1,63 @@ +#pragma once + +extern bool bDebugRenderGroups; +extern bool gPS2alphaTest; + +void OpenCharsetSafe(); +void CreateDebugFont(); +void DestroyDebugFont(); +void ObrsPrintfString(const char *str, short x, short y); +void FlushObrsPrintfs(); +void DefinedState(void); +void SetAlphaRef(int ref); +void SetCullMode(uint32 mode); +RwFrame *GetFirstChild(RwFrame *frame); +RwObject *GetFirstObject(RwFrame *frame); +RpAtomic *GetFirstAtomic(RpClump *clump); +RwTexture *GetFirstTexture(RwTexDictionary *txd); + +#ifdef PED_SKIN +RpAtomic *IsClumpSkinned(RpClump *clump); +RpHAnimHierarchy *GetAnimHierarchyFromSkinClump(RpClump *clump); // get from atomic +RpHAnimHierarchy *GetAnimHierarchyFromClump(RpClump *clump); // get from frame +RwFrame *GetHierarchyFromChildNodesCB(RwFrame *frame, void *data); +void SkinGetBonePositionsToTable(RpClump *clump, RwV3d *boneTable); +RpHAnimAnimation *HAnimAnimationCreateForHierarchy(RpHAnimHierarchy *hier); +RpAtomic *AtomicRemoveAnimFromSkinCB(RpAtomic *atomic, void *data); +void RenderSkeleton(RpHAnimHierarchy *hier); +#endif + +RwTexDictionary *RwTexDictionaryGtaStreamRead(RwStream *stream); +RwTexDictionary *RwTexDictionaryGtaStreamRead1(RwStream *stream); +RwTexDictionary *RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict); +void ReadVideoCardCapsFile(uint32&, uint32&, uint32&, uint32&); +bool CheckVideoCardCaps(void); +void WriteVideoCardCapsFile(void); +void ConvertingTexturesScreen(uint32, uint32, const char*); +void DealWithTxdWriteError(uint32, uint32, const char*); +bool CreateTxdImageForVideoCard(); + +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); + + + +RpAtomic *ConvertPlatformAtomic(RpAtomic *atomic, void *data); + +#if defined(FIX_BUGS) && defined (GTA_PC) +void SetAlphaTest(RwUInt32 alpharef); +void RestoreAlphaTest(); +#else +#define SetAlphaTest(a) (0) +#define RestoreAlphaTest() (0) +#endif \ No newline at end of file diff --git a/src/rw/RwMatFX.cpp b/src/rw/RwMatFX.cpp new file mode 100644 index 0000000..c8384b0 --- /dev/null +++ b/src/rw/RwMatFX.cpp @@ -0,0 +1,312 @@ +#ifndef LIBRW + +#define WITHD3D +#include "common.h" +#include "rpmatfx.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; +}; + +extern "C" { + extern int MatFXMaterialDataOffset; + extern int MatFXAtomicDataOffset; + + void _rpMatFXD3D8AtomicMatFXEnvRender(RxD3D8InstanceData* inst, int flags, int sel, RwTexture* texture, RwTexture* envMap); + void _rpMatFXD3D8AtomicMatFXRenderBlack(RxD3D8InstanceData *inst); + void _rpMatFXD3D8AtomicMatFXBumpMapRender(RxD3D8InstanceData *inst, int flags, RwTexture *texture, RwTexture *bumpMap, RwTexture *envMap); + void _rpMatFXD3D8AtomicMatFXDualPassRender(RxD3D8InstanceData *inst, int flags, RwTexture *texture, RwTexture *dualTexture); +} + + +#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); + RwD3D8SetPixelShader(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); +} + +void +_rwD3D8EnableClippingIfNeeded(void *object, RwUInt8 type) +{ + int clip; + if (type == rpATOMIC) + clip = !RwD3D8CameraIsSphereFullyInsideFrustum(RwCameraGetCurrentCameraMacro(), RpAtomicGetWorldBoundingSphere((RpAtomic *)object)); + else + clip = !RwD3D8CameraIsBBoxFullyInsideFrustum(RwCameraGetCurrentCameraMacro(), &((RpWorldSector *)object)->tightBoundingBox); + RwD3D8SetRenderState(D3DRS_CLIPPING, clip); +} + +void +_rwD3D8AtomicMatFXRenderCallback(RwResEntry *repEntry, void *object, RwUInt8 type, RwUInt32 flags) +{ + RwBool lighting; + RwBool forceBlack; + RxD3D8ResEntryHeader *header; + RxD3D8InstanceData *inst; + RwInt32 i; + + if (flags & rpGEOMETRYPRELIT) { + RwD3D8SetRenderState(D3DRS_COLORVERTEX, 1); + RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1); + } else { + RwD3D8SetRenderState(D3DRS_COLORVERTEX, 0); + RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); + } + + _rwD3D8EnableClippingIfNeeded(object, type); + + RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting); + if (lighting || flags & rpGEOMETRYPRELIT) { + forceBlack = FALSE; + } else { + forceBlack = TRUE; + RwD3D8SetTexture(nil, 0); + RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, D3DCOLOR_RGBA(0, 0, 0, 255)); + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); + } + + header = (RxD3D8ResEntryHeader *)(repEntry + 1); + inst = (RxD3D8InstanceData *)(header + 1); + for (i = 0; i < header->numMeshes; i++) { + if (forceBlack) + _rpMatFXD3D8AtomicMatFXRenderBlack(inst); + else { + if (lighting) + RwD3D8SetSurfaceProperties(&inst->material->color, &inst->material->surfaceProps, flags & rpGEOMETRYMODULATEMATERIALCOLOR); + MatFX *matfx = *RWPLUGINOFFSET(MatFX *, inst->material, MatFXMaterialDataOffset); + int effect = matfx ? matfx->effects : rpMATFXEFFECTNULL; + switch (effect) { + case rpMATFXEFFECTNULL: + default: + _rpMatFXD3D8AtomicMatFXDefaultRender(inst, flags, inst->material->texture); + break; + case rpMATFXEFFECTBUMPMAP: + _rpMatFXD3D8AtomicMatFXBumpMapRender(inst, flags, inst->material->texture, matfx->fx[0].b.bumpedTex, nil); + break; + case rpMATFXEFFECTENVMAP: + { + // TODO: matfx switch in the settings + //_rpMatFXD3D8AtomicMatFXEnvRender(inst, flags, 0, inst->material->texture, matfx->fx[0].e.envTex); + _rpMatFXD3D8AtomicMatFXEnvRender_ps2(inst, flags, 0, inst->material->texture, matfx->fx[0].e.envTex); + break; + } + case rpMATFXEFFECTBUMPENVMAP: + _rpMatFXD3D8AtomicMatFXBumpMapRender(inst, flags, inst->material->texture, matfx->fx[0].b.bumpedTex, matfx->fx[1].e.envTex); + break; + case rpMATFXEFFECTDUAL: + _rpMatFXD3D8AtomicMatFXDualPassRender(inst, flags, inst->material->texture, matfx->fx[0].d.dualTex); + break; + } + } + inst++; + } + + if (forceBlack) { + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + } +} + +void +ReplaceMatFxCallback() +{ + RxD3D8AllInOneSetRenderCallBack( + RxPipelineFindNodeByName(RpMatFXGetD3D8Pipeline(rpMATFXD3D8ATOMICPIPELINE), RxNodeDefinitionGetD3D8AtomicAllInOne()->name, nil, nil), + _rwD3D8AtomicMatFXRenderCallback); + +} +#endif // PS2_MATFX + +#endif // !LIBRW diff --git a/src/rw/RwPS2AlphaTest.cpp b/src/rw/RwPS2AlphaTest.cpp new file mode 100644 index 0000000..c0d6835 --- /dev/null +++ b/src/rw/RwPS2AlphaTest.cpp @@ -0,0 +1,247 @@ +#ifndef LIBRW + +#define WITHD3D +#include "common.h" +#ifdef PS2_ALPHA_TEST +#include "rwcore.h" + +extern "C" { +RwBool _rwD3D8RenderStateIsVertexAlphaEnable(void); +RwBool _rwD3D8RenderStateVertexAlphaEnable(RwBool enable); +RwRaster *_rwD3D8RWGetRasterStage(RwUInt32 stage); +} + +extern bool gPS2alphaTest; + +void +_rxD3D8DualPassRenderCallback(RwResEntry *repEntry, void *object, RwUInt8 type, RwUInt32 flags) +{ + RxD3D8ResEntryHeader *resEntryHeader; + RxD3D8InstanceData *instancedData; + RwInt32 numMeshes; + RwBool lighting; + RwBool vertexAlphaBlend; + RwBool forceBlack; + RwUInt32 ditherEnable; + RwUInt32 shadeMode; + void *lastVertexBuffer; + + /* Get lighting state */ + RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting); + + forceBlack = FALSE; + + if (lighting) { + if (flags & rxGEOMETRY_PRELIT) { + /* Emmisive color from the vertex colors */ + RwD3D8SetRenderState(D3DRS_COLORVERTEX, TRUE); + RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1); + } else { + /* Emmisive color from material, set to black in the submit node */ + RwD3D8SetRenderState(D3DRS_COLORVERTEX, FALSE); + RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); + } + } else { + if ((flags & rxGEOMETRY_PRELIT) == 0) { + forceBlack = TRUE; + + RwD3D8GetRenderState(D3DRS_DITHERENABLE, &ditherEnable); + RwD3D8GetRenderState(D3DRS_SHADEMODE, &shadeMode); + + RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, 0xff000000); + RwD3D8SetRenderState(D3DRS_DITHERENABLE, FALSE); + RwD3D8SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT); + } + } + + /* Enable clipping */ + if (type == rpATOMIC) { + RpAtomic *atomic; + RwCamera *cam; + + atomic = (RpAtomic *)object; + + cam = RwCameraGetCurrentCamera(); + // RWASSERT(cam); + + if (RwD3D8CameraIsSphereFullyInsideFrustum(cam, RpAtomicGetWorldBoundingSphere(atomic))) { + RwD3D8SetRenderState(D3DRS_CLIPPING, FALSE); + } else { + RwD3D8SetRenderState(D3DRS_CLIPPING, TRUE); + } + } else { + RpWorldSector *worldSector; + RwCamera *cam; + + worldSector = (RpWorldSector *)object; + + cam = RwCameraGetCurrentCamera(); + // RWASSERT(cam); + + if (RwD3D8CameraIsBBoxFullyInsideFrustum(cam, RpWorldSectorGetTightBBox(worldSector))) { + RwD3D8SetRenderState(D3DRS_CLIPPING, FALSE); + } else { + RwD3D8SetRenderState(D3DRS_CLIPPING, TRUE); + } + } + + /* Set texture to NULL if hasn't any texture flags */ + if ((flags & (rxGEOMETRY_TEXTURED | rpGEOMETRYTEXTURED2)) == 0) { + RwD3D8SetTexture(NULL, 0); + + if (forceBlack) { + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); + + RwD3D8SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + } + } + + /* Get vertex alpha Blend state */ + vertexAlphaBlend = _rwD3D8RenderStateIsVertexAlphaEnable(); + + /* Set Last vertex buffer to force the call */ + lastVertexBuffer = (void *)0xffffffff; + + /* Get the instanced data */ + resEntryHeader = (RxD3D8ResEntryHeader *)(repEntry + 1); + instancedData = (RxD3D8InstanceData *)(resEntryHeader + 1); + + /* + * Data shared between meshes + */ + + /* + * Set the Default Pixel shader + */ + RwD3D8SetPixelShader(0); + + /* + * Vertex shader + */ + RwD3D8SetVertexShader(instancedData->vertexShader); + + /* Get the number of meshes */ + numMeshes = resEntryHeader->numMeshes; + while (numMeshes--) { + // RWASSERT(instancedData->material != NULL); + + if ((flags & (rxGEOMETRY_TEXTURED | rpGEOMETRYTEXTURED2))) { + RwD3D8SetTexture(instancedData->material->texture, 0); + + if (forceBlack) { + /* Only change the colorop, we need to use the texture alpha channel */ + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); + } + } + + if (instancedData->vertexAlpha || (0xFF != instancedData->material->color.alpha)) { + if (!vertexAlphaBlend) { + vertexAlphaBlend = TRUE; + + _rwD3D8RenderStateVertexAlphaEnable(TRUE); + } + } else { + if (vertexAlphaBlend) { + vertexAlphaBlend = FALSE; + + _rwD3D8RenderStateVertexAlphaEnable(FALSE); + } + } + + if (lighting) { + if (instancedData->vertexAlpha) { + RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1); + } else { + RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL); + } + + RwD3D8SetSurfaceProperties(&instancedData->material->color, &instancedData->material->surfaceProps, (flags & rxGEOMETRY_MODULATE)); + } + + /* + * Render + */ + + /* Set the stream source */ + if (lastVertexBuffer != instancedData->vertexBuffer) { + RwD3D8SetStreamSource(0, instancedData->vertexBuffer, instancedData->stride); + + lastVertexBuffer = instancedData->vertexBuffer; + } + if (!gPS2alphaTest) { + /* Set the Index buffer */ + if (instancedData->indexBuffer != NULL) { + RwD3D8SetIndices(instancedData->indexBuffer, instancedData->baseIndex); + + /* Draw the indexed primitive */ + RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType, 0, instancedData->numVertices, 0, instancedData->numIndices); + } else { + RwD3D8DrawPrimitive((D3DPRIMITIVETYPE)instancedData->primType, instancedData->baseIndex, instancedData->numVertices); + } + } else { + RwD3D8SetIndices(instancedData->indexBuffer, instancedData->baseIndex); + + int hasAlpha, alphafunc, alpharef, zwrite; + RwD3D8GetRenderState(D3DRS_ALPHABLENDENABLE, &hasAlpha); + RwD3D8GetRenderState(D3DRS_ZWRITEENABLE, &zwrite); + if (hasAlpha && zwrite) { + RwD3D8GetRenderState(D3DRS_ALPHAFUNC, &alphafunc); + RwD3D8GetRenderState(D3DRS_ALPHAREF, &alpharef); + + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL); + RwD3D8SetRenderState(D3DRS_ALPHAREF, 128); + + if (instancedData->indexBuffer) + RwD3D8DrawIndexedPrimitive(instancedData->primType, 0, instancedData->numVertices, 0, instancedData->numIndices); + else + RwD3D8DrawPrimitive(instancedData->primType, instancedData->baseIndex, instancedData->numVertices); + + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_LESS); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); + + if (instancedData->indexBuffer) + RwD3D8DrawIndexedPrimitive(instancedData->primType, 0, instancedData->numVertices, 0, instancedData->numIndices); + else + RwD3D8DrawPrimitive(instancedData->primType, instancedData->baseIndex, instancedData->numVertices); + + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, alphafunc); + RwD3D8SetRenderState(D3DRS_ALPHAREF, alpharef); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + } else { + if (instancedData->indexBuffer) + RwD3D8DrawIndexedPrimitive(instancedData->primType, 0, instancedData->numVertices, 0, instancedData->numIndices); + else + RwD3D8DrawPrimitive(instancedData->primType, instancedData->baseIndex, instancedData->numVertices); + } + } + + /* Move onto the next instancedData */ + instancedData++; + } + + if (forceBlack) { + RwD3D8SetRenderState(D3DRS_DITHERENABLE, ditherEnable); + RwD3D8SetRenderState(D3DRS_SHADEMODE, shadeMode); + + if (_rwD3D8RWGetRasterStage(0)) { + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + } else { + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + } + } +} + +void +ReplaceAtomicPipeCallback() +{ + RxD3D8AllInOneSetRenderCallBack(RxPipelineFindNodeByName(RXPIPELINEGLOBAL(platformAtomicPipeline), RxNodeDefinitionGetD3D8AtomicAllInOne()->name, nil, nil), + _rxD3D8DualPassRenderCallback); +} + +#endif // PS2_ALPHA_TEST + +#endif // !LIBRW \ No newline at end of file diff --git a/src/rw/TexRead.cpp b/src/rw/TexRead.cpp new file mode 100644 index 0000000..98e7d18 --- /dev/null +++ b/src/rw/TexRead.cpp @@ -0,0 +1,459 @@ +#pragma warning( push ) +#pragma warning( disable : 4005) +#pragma warning( pop ) +#define FORCE_PC_SCALING +#include "common.h" +#ifdef ANISOTROPIC_FILTERING +#include "rpanisot.h" +#endif +#include "crossplatform.h" +#include "platform.h" + +#include "Timer.h" +#ifdef GTA_PC +#include "FileMgr.h" +#include "Pad.h" +#include "main.h" +#include "Directory.h" +#include "Streaming.h" +#include "TxdStore.h" +#include "CdStream.h" +#include "Font.h" +#include "Sprite2d.h" +#include "Text.h" +#include "RwHelper.h" +#include "Frontend.h" +#endif //GTA_PC + +float texLoadTime; +int32 texNumLoaded; + +#ifdef LIBRW +#define READNATIVE(stream, tex, size) rwNativeTextureHackRead(stream, tex, size) +#else +#define READNATIVE(stream, tex, size) RWSRCGLOBAL(stdFunc[rwSTANDARDNATIVETEXTUREREAD](stream, tex, size)) +#endif + +RwTexture* +RwTextureGtaStreamRead(RwStream *stream) +{ + RwUInt32 size, version; + RwTexture *tex; + + if(!RwStreamFindChunk(stream, rwID_TEXTURENATIVE, &size, &version)) + return nil; + + float preloadTime = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); + + if(!READNATIVE(stream, &tex, size)) + return nil; + + if (gGameState == GS_INIT_PLAYING_GAME) { + texLoadTime = (texNumLoaded * texLoadTime + (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond() - preloadTime) / (float)(texNumLoaded+1); + texNumLoaded++; + } + +#ifdef ANISOTROPIC_FILTERING + if(tex && RpAnisotGetMaxSupportedMaxAnisotropy() > 1) // BUG? this was RpAnisotTextureGetMaxAnisotropy, but that doesn't make much sense + RpAnisotTextureSetMaxAnisotropy(tex, RpAnisotGetMaxSupportedMaxAnisotropy()); +#endif + + 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; + 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 = STREAMPOS(stream); + + return texDict; +} + +RwTexDictionary* +RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict) +{ + RwTexture *tex; + + RwStreamSkip(stream, streamPosition - STREAMPOS(stream)); + + while(numberTextures--){ + tex = RwTextureGtaStreamRead(stream); + if(tex == nil){ + RwTexDictionaryForAllTextures(texDict, destroyTexture, nil); + RwTexDictionaryDestroy(texDict); + return nil; + } + RwTexDictionaryAddTexture(texDict, tex); + } + + return texDict; +} + +#ifdef GTA_PC + +#ifdef LIBRW + +#define CAPSVERSION 0 + +struct GPUcaps +{ + uint32 version; // so we can force regeneration easily + uint32 platform; + uint32 subplatform; + uint32 dxtSupport; +}; + +static void +GetGPUcaps(GPUcaps *caps) +{ + caps->version = CAPSVERSION; + caps->platform = rw::platform; + caps->subplatform = 0; + caps->dxtSupport = 0; + // TODO: more later +#ifdef RW_GL3 + caps->subplatform = rw::gl3::gl3Caps.gles; + caps->dxtSupport = rw::gl3::gl3Caps.dxtSupported; +#endif +#ifdef RW_D3D9 + caps->dxtSupport = 1; // TODO, probably +#endif +} + +void +ReadVideoCardCapsFile(GPUcaps *caps) +{ + memset(caps, 0, sizeof(GPUcaps)); + + int32 file = CFileMgr::OpenFile("DATA\\CAPS.DAT", "rb"); + if (file != 0) { + CFileMgr::Read(file, (char*)&caps->version, 4); + CFileMgr::Read(file, (char*)&caps->platform, 4); + CFileMgr::Read(file, (char*)&caps->subplatform, 4); + CFileMgr::Read(file, (char*)&caps->dxtSupport, 4); + CFileMgr::CloseFile(file); + } +} + +bool +CheckVideoCardCaps(void) +{ + GPUcaps caps, fcaps; + GetGPUcaps(&caps); + ReadVideoCardCapsFile(&fcaps); + return caps.version != fcaps.version || + caps.platform != fcaps.platform || + caps.subplatform != fcaps.subplatform || + caps.dxtSupport != fcaps.dxtSupport; +} + +void +WriteVideoCardCapsFile(void) +{ + GPUcaps caps; + GetGPUcaps(&caps); + int32 file = CFileMgr::OpenFile("DATA\\CAPS.DAT", "wb"); + if (file != 0) { + CFileMgr::Write(file, (char*)&caps.version, 4); + CFileMgr::Write(file, (char*)&caps.platform, 4); + CFileMgr::Write(file, (char*)&caps.subplatform, 4); + CFileMgr::Write(file, (char*)&caps.dxtSupport, 4); + CFileMgr::CloseFile(file); + } +} + +#else +extern "C" RwInt32 _rwD3D8FindCorrectRasterFormat(RwRasterType type, RwInt32 flags); +void +ReadVideoCardCapsFile(uint32 &cap32, uint32 &cap24, uint32 &cap16, uint32 &cap8) +{ + cap32 = UINT32_MAX; + cap24 = UINT32_MAX; + cap16 = UINT32_MAX; + cap8 = UINT32_MAX; + + int32 file = CFileMgr::OpenFile("DATA\\CAPS.DAT", "rb"); + if (file != 0) { + CFileMgr::Read(file, (char*)&cap32, 4); + CFileMgr::Read(file, (char*)&cap24, 4); + CFileMgr::Read(file, (char*)&cap16, 4); + CFileMgr::Read(file, (char*)&cap8, 4); + CFileMgr::CloseFile(file); + } +} + +bool +CheckVideoCardCaps(void) +{ + uint32 cap32 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT8888); + uint32 cap24 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT888); + uint32 cap16 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT1555); + uint32 cap8 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMATPAL8 | rwRASTERFORMAT8888); + uint32 fcap32, fcap24, fcap16, fcap8; + ReadVideoCardCapsFile(fcap32, fcap24, fcap16, fcap8); + return cap32 != fcap32 || cap24 != fcap24 || cap16 != fcap16 || cap8 != fcap8; +} + +void +WriteVideoCardCapsFile(void) +{ + uint32 cap32 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT8888); + uint32 cap24 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT888); + uint32 cap16 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT1555); + uint32 cap8 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMATPAL8 | rwRASTERFORMAT8888); + int32 file = CFileMgr::OpenFile("DATA\\CAPS.DAT", "wb"); + if (file != 0) { + CFileMgr::Write(file, (char*)&cap32, 4); + CFileMgr::Write(file, (char*)&cap24, 4); + CFileMgr::Write(file, (char*)&cap16, 4); + CFileMgr::Write(file, (char*)&cap8, 4); + CFileMgr::CloseFile(file); + } +} +#endif + +void +ConvertingTexturesScreen(uint32 num, uint32 count, const char *text) +{ + HandleExit(); + + CSprite2d *splash = LoadSplash(nil); + if (!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) + return; + + 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)); + + CSprite2d::DrawRect(CRect(SCREEN_SCALE_X(200.0f), SCREEN_SCALE_Y(240.0f), SCREEN_SCALE_FROM_RIGHT(200.0f), SCREEN_SCALE_Y(248.0f)), CRGBA(64, 64, 64, 255)); + CSprite2d::DrawRect(CRect(SCREEN_SCALE_X(200.0f), SCREEN_SCALE_Y(240.0f), (SCREEN_SCALE_FROM_RIGHT(200.0f) - SCREEN_SCALE_X(200.0f)) * ((float)num / (float)count) + SCREEN_SCALE_X(200.0f), SCREEN_SCALE_Y(248.0f)), CRGBA(255, 217, 106, 255)); + CSprite2d::DrawRect(CRect(SCREEN_SCALE_X(120.0f), SCREEN_SCALE_Y(150.0f), SCREEN_SCALE_FROM_RIGHT(120.0f), SCREEN_HEIGHT - SCREEN_SCALE_Y(220.0f)), CRGBA(50, 50, 50, 210)); + + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetScale(SCREEN_SCALE_X(0.45f), SCREEN_SCALE_Y(0.7f)); + CFont::SetCentreOff(); + CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(170.0f)); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 217, 106, 255)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::PrintString(SCREEN_SCALE_X(170.0f), SCREEN_SCALE_Y(160.0f), TheText.Get(text)); + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +void +DealWithTxdWriteError(uint32 num, uint32 count, const char *text) +{ + while (!RsGlobal.quit) { + ConvertingTexturesScreen(num, count, text); + CPad::UpdatePads(); + if (CPad::GetPad(0)->GetEscapeJustDown()) + break; + } + RsGlobal.quit = false; + LoadingScreen(nil, nil, nil); + RsGlobal.quit = true; +} + +#ifdef LIBRW +#define STREAMTELL(str) str->tell() +#else +#define STREAMTELL(str) filesys->rwftell((str)->Type.file.fpFile) +#endif + +bool +CreateTxdImageForVideoCard() +{ + uint8 *buf = new uint8[CDSTREAM_SECTOR_SIZE]; + CDirectory *pDir = new CDirectory(TXDSTORESIZE); + CDirectory::DirectoryInfo dirInfo; + + CStreaming::FlushRequestList(); + +#ifndef LIBRW + RwFileFunctions *filesys = RwOsGetFileInterface(); +#endif + + RwStream *img = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, "models\\txd.img"); + if (img == nil) { + // original code does otherwise and it leaks + delete []buf; + delete pDir; + + if (_dwOperatingSystemVersion == OS_WINNT || _dwOperatingSystemVersion == OS_WIN2000 || _dwOperatingSystemVersion == OS_WINXP) + DealWithTxdWriteError(0, TXDSTORESIZE, "CVT_CRT"); + + return false; + } + +#ifdef RW_GL3 + // so we can read back DXT with GLES + // only works for textures that are not yet loaded + // so let's hope that is the case for all + rw::gl3::needToReadBackTextures = true; +#endif + +#ifdef DISABLE_VSYNC_ON_TEXTURE_CONVERSION + // let's disable vsync and frame limiter to speed up texture conversion + // (actually we probably don't need to disable frame limiter in here, but let's do it just in case =P) + int8 vsyncState = CMenuManager::m_PrefsVsync; + int8 frameLimiterState = CMenuManager::m_PrefsFrameLimiter; + CMenuManager::m_PrefsVsync = 0; + CMenuManager::m_PrefsFrameLimiter = 0; +#endif + + int32 i; + for (i = 0; i < TXDSTORESIZE; i++) { + ConvertingTexturesScreen(i, TXDSTORESIZE, "CVT_MSG"); + + if (CTxdStore::GetSlot(i) != nil && CStreaming::IsObjectInCdImage(i + STREAM_OFFSET_TXD)) { +#ifdef FIX_BUGS + if(strcmp(CTxdStore::GetTxdName(i), "generic") == 0) + continue; +#endif + + CStreaming::RequestTxd(i, STREAMFLAGS_KEEP_IN_MEMORY); + CStreaming::RequestModelStream(0); + CStreaming::FlushChannels(); + + char filename[64]; + sprintf(filename, "%s.txd", CTxdStore::GetTxdName(i)); + + if (CTxdStore::GetSlot(i)->texDict) { + + int32 pos = STREAMTELL(img); + + if (RwTexDictionaryStreamWrite(CTxdStore::GetSlot(i)->texDict, img) == nil) { + DealWithTxdWriteError(i, TXDSTORESIZE, "CVT_ERR"); + RwStreamClose(img, nil); + delete []buf; + delete pDir; + CStreaming::RemoveTxd(i); +#ifdef RW_GL3 + rw::gl3::needToReadBackTextures = false; +#endif + return false; + } + + int32 size = STREAMTELL(img) - pos; + int32 num = size % CDSTREAM_SECTOR_SIZE; + + size /= CDSTREAM_SECTOR_SIZE; + if (num != 0) { + size++; + num = CDSTREAM_SECTOR_SIZE - num; + RwStreamWrite(img, buf, num); + } + + dirInfo.offset = pos / CDSTREAM_SECTOR_SIZE; + dirInfo.size = size; + strncpy(dirInfo.name, filename, sizeof(dirInfo.name)); + pDir->AddItem(dirInfo); + CStreaming::RemoveTxd(i); + } + CStreaming::FlushRequestList(); + } + } + +#ifdef DISABLE_VSYNC_ON_TEXTURE_CONVERSION + // restore vsync and frame limiter states + CMenuManager::m_PrefsVsync = vsyncState; + CMenuManager::m_PrefsFrameLimiter = frameLimiterState; +#endif + + RwStreamClose(img, nil); + delete []buf; + +#ifdef RW_GL3 + rw::gl3::needToReadBackTextures = false; +#endif + + if (!pDir->WriteDirFile("models\\txd.dir")) { + DealWithTxdWriteError(i, TXDSTORESIZE, "CVT_ERR"); + delete pDir; + return false; + } + + delete pDir; + + WriteVideoCardCapsFile(); + return true; +} +#endif // GTA_PC diff --git a/src/rw/TexturePools.cpp b/src/rw/TexturePools.cpp new file mode 100644 index 0000000..c2ba6cf --- /dev/null +++ b/src/rw/TexturePools.cpp @@ -0,0 +1,221 @@ +#ifndef LIBRW + +#include +#define WITHD3D +#include "common.h" +#include "TexturePools.h" + +// TODO: this needs to be integrated into RW + +extern "C" LPDIRECT3DDEVICE8 _RwD3DDevice; + +CTexturePool aTexturePools[12]; +CPaletteList PaletteList; +int numTexturePools; +int MaxPaletteIndex; +bool bUsePaletteIndex = true; + + +void +CTexturePool::Create(D3DFORMAT _Format, int _size, uint32 mipmapLevels, int32 numTextures) +{ + Format = _Format; + size = _size; + levels = mipmapLevels; + pTextures = new IDirect3DTexture8 *[numTextures]; + texturesMax = numTextures; + texturesNum = 0; + texturesUsed = 0; +} + +void +CTexturePool::Release() +{ + int i = 0; + while (i < texturesNum) { + pTextures[i]->Release(); + i++; + } + + delete[] pTextures; + + pTextures = nil; + texturesNum = 0; + texturesUsed = 0; +} + +IDirect3DTexture8 * +CTexturePool::FindTexture() +{ + if (texturesNum == 0) + return nil; + texturesUsed--; + return pTextures[--texturesNum]; +} + +bool +CTexturePool::AddTexture(IDirect3DTexture8 *texture) +{ + ++texturesUsed; + if (texturesNum >= texturesMax) + return false; + pTextures[texturesNum] = texture; + ++texturesNum; + return true; +} + +void +CTexturePool::Resize(int numTextures) +{ + if (numTextures == texturesMax) + return; + + IDirect3DTexture8 **newTextures = new IDirect3DTexture8 *[numTextures]; + + for (int i = 0; i < texturesNum && i < numTextures; i++) + newTextures[i] = pTextures[i]; + + if (numTextures < texturesNum) { + for (int i = numTextures; i < texturesNum; i++) + pTextures[i]->Release(); + } + delete[] pTextures; + pTextures = newTextures; + texturesMax = numTextures; +} + +void +CPaletteList::Alloc(int max) +{ + Data = new int[max]; + Max = max; + Num = 0; +} + +void +CPaletteList::Free() +{ + delete[] Data; + Data = nil; + Num = 0; +} + +int +CPaletteList::Find() +{ + if (Num == 0) + return -1; + return Data[--Num]; +} + +void +CPaletteList::Add(int item) +{ + if (Num < Max) + Data[Num++] = item; + else { + Resize(2 * Max); + Add(item); + } +} + +void +CPaletteList::Resize(int max) +{ + if (max == Max) + return; + + int *newData = new int[4 * max]; + for (int i = 0; i < Num && i < max; i++) + newData[i] = Data[i]; + delete[] Data; + Data = newData; + Max = max; +} + +HRESULT +CreateTexture(int width, int height, int levels, D3DFORMAT Format, IDirect3DTexture8 **texture) +{ + if (width == height) { + for (int i = 0; i < numTexturePools; i++) { + if (width != aTexturePools[i].GetSize() && levels == aTexturePools[i].levels && Format == aTexturePools[i].Format) + *texture = aTexturePools[i].FindTexture(); + } + } + if (*texture) + return D3D_OK; + else + return _RwD3DDevice->CreateTexture(width, height, levels, 0, Format, D3DPOOL_MANAGED, texture); +} + +void +ReleaseTexture(IDirect3DTexture8 *texture) +{ + int levels = 1; + if (texture->GetLevelCount() > 1) + levels = 0; + + D3DSURFACE_DESC SURFACE_DESC; + + texture->GetLevelDesc(0, &SURFACE_DESC); + + if (SURFACE_DESC.Width == SURFACE_DESC.Height) { + for (int i = 0; i < numTexturePools; i++) { + if (SURFACE_DESC.Width == aTexturePools[i].GetSize() && SURFACE_DESC.Format == aTexturePools[i].Format && levels == aTexturePools[i].levels) { + if (!aTexturePools[i].AddTexture(texture)) { + if (aTexturePools[i].texturesUsed > 3 * aTexturePools[i].texturesMax / 2) { + aTexturePools[i].Resize(2 * aTexturePools[i].texturesMax); + aTexturePools[i].texturesUsed--; + aTexturePools[i].AddTexture(texture); + } else { + texture->Release(); + } + } + return; + } + } + } + if (numTexturePools < 12 && bUsePaletteIndex && levels != 0 && SURFACE_DESC.Width == SURFACE_DESC.Height && + (SURFACE_DESC.Width == 64 || SURFACE_DESC.Width == 128 || SURFACE_DESC.Width == 256)) { + aTexturePools[numTexturePools].Create(SURFACE_DESC.Format, SURFACE_DESC.Width, 1, 16); + aTexturePools[numTexturePools].AddTexture(texture); + numTexturePools++; + } else + texture->Release(); +} + +int +FindAvailablePaletteIndex() +{ + int index = PaletteList.Find(); + if (index == -1) + index = MaxPaletteIndex++; + return index; +} + +void +AddAvailablePaletteIndex(int index) +{ + if (bUsePaletteIndex) + PaletteList.Add(index); +} + +void +_TexturePoolsInitialise() +{ + PaletteList.Alloc(100); + MaxPaletteIndex = 0; +} + +void +_TexturePoolsShutdown() +{ + for (int i = 0; i < numTexturePools; i++) + aTexturePools[i].Release(); + + numTexturePools = 0; + bUsePaletteIndex = false; + PaletteList.Free(); +} + +#endif // !LIBRW \ No newline at end of file diff --git a/src/rw/TexturePools.h b/src/rw/TexturePools.h new file mode 100644 index 0000000..7518743 --- /dev/null +++ b/src/rw/TexturePools.h @@ -0,0 +1,42 @@ +#pragma once + +class CTexturePool +{ +public: + D3DFORMAT Format; + int size; + uint32 levels; + int32 texturesMax; + int32 texturesUsed; + int32 texturesNum; + IDirect3DTexture8 **pTextures; + +public: + CTexturePool() {} + void Create(D3DFORMAT _Format, int size, uint32 mipmapLevels, int32 numTextures); + void Release(); + IDirect3DTexture8 *FindTexture(); + bool AddTexture(IDirect3DTexture8 *texture); + void Resize(int numTextures); +#ifdef FIX_BUGS + int GetSize() { return size; } +#else + float GetSize() { return size; } +#endif +}; + +class CPaletteList +{ + int Max; + int Num; + int *Data; +public: + void Alloc(int max); + void Free(); + int Find(); + void Add(int item); + void Resize(int max); +}; + +void _TexturePoolsInitialise(); +void _TexturePoolsShutdown(); \ No newline at end of file diff --git a/src/rw/TxdStore.cpp b/src/rw/TxdStore.cpp new file mode 100644 index 0000000..a9e2972 --- /dev/null +++ b/src/rw/TxdStore.cpp @@ -0,0 +1,183 @@ +#include "common.h" + +#include "templates.h" +#include "General.h" +#include "Streaming.h" +#include "RwHelper.h" +#include "TxdStore.h" + +CPool *CTxdStore::ms_pTxdPool; +RwTexDictionary *CTxdStore::ms_pStoredTxd; + +void +CTxdStore::Initialise(void) +{ + if(ms_pTxdPool == nil) + ms_pTxdPool = new CPool(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) +{ + int size = ms_pTxdPool->GetSize(); + for(int i = 0; i < size; i++){ + TxdDef *def = GetSlot(i); + if(def && !CGeneral::faststricmp(def->name, name)) + return i; + } + return -1; +} + +char* +CTxdStore::GetTxdName(int slot) +{ + return GetSlot(slot)->name; +} + +void +CTxdStore::PushCurrentTxd(void) +{ + ms_pStoredTxd = RwTexDictionaryGetCurrent(); +} + +void +CTxdStore::PopCurrentTxd(void) +{ + RwTexDictionarySetCurrent(ms_pStoredTxd); + ms_pStoredTxd = nil; +} + +void +CTxdStore::SetCurrentTxd(int slot) +{ + RwTexDictionarySetCurrent(GetSlot(slot)->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::RemoveTxd(slot); +} + +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; +} diff --git a/src/rw/TxdStore.h b/src/rw/TxdStore.h new file mode 100644 index 0000000..937fd1b --- /dev/null +++ b/src/rw/TxdStore.h @@ -0,0 +1,44 @@ +#pragma once + +#include "templates.h" + +struct TxdDef { + RwTexDictionary *texDict; + int refCount; + char name[20]; +}; + +class CTxdStore +{ + static CPool *ms_pTxdPool; + static RwTexDictionary *ms_pStoredTxd; +public: + static void Initialise(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/rw/VisibilityPlugins.cpp b/src/rw/VisibilityPlugins.cpp new file mode 100644 index 0000000..e6d4641 --- /dev/null +++ b/src/rw/VisibilityPlugins.cpp @@ -0,0 +1,1059 @@ +#include "common.h" + +#include "RwHelper.h" +#include "templates.h" +#include "main.h" +#include "Entity.h" +#include "ModelInfo.h" +#include "Lights.h" +#include "Renderer.h" +#include "Camera.h" +#include "VisibilityPlugins.h" +#include "World.h" +#include "custompipes.h" +#include "MemoryHeap.h" + +CLinkList CVisibilityPlugins::m_alphaList; +CLinkList CVisibilityPlugins::m_alphaEntityList; +#ifdef NEW_RENDERER +CLinkList CVisibilityPlugins::m_alphaBuildingList; +#endif + +int32 CVisibilityPlugins::ms_atomicPluginOffset = -1; +int32 CVisibilityPlugins::ms_framePluginOffset = -1; +int32 CVisibilityPlugins::ms_clumpPluginOffset = -1; + +RwCamera *CVisibilityPlugins::ms_pCamera; +RwV3d *CVisibilityPlugins::ms_pCameraPosn; +float CVisibilityPlugins::ms_cullCompsDist; +float CVisibilityPlugins::ms_vehicleLod0Dist; +float CVisibilityPlugins::ms_vehicleLod1Dist; +float CVisibilityPlugins::ms_vehicleFadeDist; +float CVisibilityPlugins::ms_bigVehicleLod0Dist; +float CVisibilityPlugins::ms_bigVehicleLod1Dist; +float CVisibilityPlugins::ms_pedLod0Dist; +float CVisibilityPlugins::ms_pedLod1Dist; +float CVisibilityPlugins::ms_pedFadeDist; + +#ifdef GTA_PS2 // maybe something else? +// if wanted, delete the original geometry data after rendering +// and only keep the instanced data +bool +rpDefaultGeometryInstance(RpGeometry *geo, void *atomic, int del) +{ +#if THIS_IS_COMPATIBLE_WITH_GTA3_RW31 + if(RpGeometryGetNumMorphTargets(geo) != 1) + return false; + + // this needs R*'s modification that geometry data is + // allocated separately from the geometry itself + geo->instanceFlags = rpGEOMETRYINSTANCE; + AtomicDefaultRenderCallBack((RpAtomic*)atomic); + + if(!del) + return true; + + // New mesh without indices + RpMeshHeader *newheader = _rpMeshHeaderCreate(sizeof(RpMesh)*geo->mesh->numMeshes + sizeof(RpMeshHeader)); + newheader->numMeshes = geo->mesh->numMeshes; + newheader->serialNum = 1; + newheader->totalIndicesInMesh = 0; + newheader->firstMeshOffset = 0; + RpMesh *oldmesh = (RpMesh*)(geo->mesh+1); + RpMesh *newmesh = (RpMesh*)(newheader+1); + for(int i = 0; i < geo->mesh->numMeshes; i++){ + newmesh[i].indices = nil; + newmesh[i].numIndices = 0; + newmesh[i].material = oldmesh[i].material; + } + + geo->refCount++; + RpGeometryLock(geo, rpGEOMETRYLOCKPOLYGONS | rpGEOMETRYLOCKVERTICES | + rpGEOMETRYLOCKNORMALS | rpGEOMETRYLOCKPRELIGHT | + rpGEOMETRYLOCKTEXCOORDS1 | rpGEOMETRYLOCKTEXCOORDS2); + + // vertices and normals + RpMorphTarget *mt = RpGeometryGetMorphTarget(geo, 0); + if(mt->verts){ + RwFree(mt->verts); + mt->verts = nil; + mt->normals = nil; + } + geo->numVertices = 0; + + // triangles + for(int i = 0; i < RpGeometryGetNumTriangles(geo); i++){ + if(RpGeometryGetTriangles(geo)->matIndex == -1) + continue; + RpMaterialDestroy(_rpMaterialListGetMaterial(&geo->matList, RpGeometryGetTriangles(geo)->matIndex)); + } + if(RpGeometryGetTriangles(geo)){ + RwFree(RpGeometryGetTriangles(geo)); + geo->triangles = nil; + geo->numTriangles = 0; + } + + // tex coords + if(RpGeometryGetVertexTexCoords(geo, 1)){ + RwFree(RpGeometryGetVertexTexCoords(geo, 1)); + geo->texCoords[1] = nil; + } + if(RpGeometryGetVertexTexCoords(geo, 0)){ + RwFree(RpGeometryGetVertexTexCoords(geo, 0)); + geo->texCoords[0] = nil; + } + + // vertex colors + if(RpGeometryGetPreLightColors(geo)){ + RwFree(RpGeometryGetPreLightColors(geo)); + geo->preLitLum = nil; + } + + RpGeometryUnlock(geo); + + geo->instanceFlags = rpGEOMETRYPERSISTENT; + // BUG? don't we have to free the old mesh? + geo->mesh = newheader; + geo->refCount--; +#else + // We can do something for librw here actually, maybe later + AtomicDefaultRenderCallBack((RpAtomic*)atomic); +#endif + + return true; +} + +RpAtomic* +PreInstanceRenderCB(RpAtomic *atomic) +{ + RpGeometry *geo = RpAtomicGetGeometry(atomic); + if(RpGeometryGetTriangles(geo)){ + PUSH_MEMID(MEMID_STREAM_MODELS); + rpDefaultGeometryInstance(geo, atomic, 1); + POP_MEMID(); + }else + AtomicDefaultRenderCallBack(atomic); + return atomic; +} +#define RENDERCALLBACK PreInstanceRenderCB +#else +RpAtomic* +DefaultRenderCB_pushid(RpAtomic *atomic) +{ + PUSH_MEMID(MEMID_STREAM_MODELS); + AtomicDefaultRenderCallBack(atomic); + POP_MEMID(); + return atomic; +} +#define RENDERCALLBACK DefaultRenderCB_pushid +#endif + +void +CVisibilityPlugins::Initialise(void) +{ + m_alphaList.Init(NUMALPHALIST); + m_alphaList.head.item.sort = 0.0f; + m_alphaList.tail.item.sort = 100000000.0f; +#ifdef ASPECT_RATIO_SCALE + // default 150 is not enough for bigger FOVs + m_alphaEntityList.Init(NUMALPHAENTITYLIST * 3); +#else + m_alphaEntityList.Init(NUMALPHAENTITYLIST); +#endif // ASPECT_RATIO_SCALE + m_alphaEntityList.head.item.sort = 0.0f; + m_alphaEntityList.tail.item.sort = 100000000.0f; + +#ifdef NEW_RENDERER + m_alphaBuildingList.Init(NUMALPHAENTITYLIST); + m_alphaBuildingList.head.item.sort = 0.0f; + m_alphaBuildingList.tail.item.sort = 100000000.0f; +#endif +} + +void +CVisibilityPlugins::Shutdown(void) +{ + m_alphaList.Shutdown(); + m_alphaEntityList.Shutdown(); +#ifdef NEW_RENDERER + m_alphaBuildingList.Shutdown(); +#endif +} + +void +CVisibilityPlugins::InitAlphaEntityList(void) +{ + m_alphaEntityList.Clear(); +#ifdef NEW_RENDERER + m_alphaBuildingList.Clear(); +#endif +} + +bool +CVisibilityPlugins::InsertEntityIntoSortedList(CEntity *e, float dist) +{ +#ifdef FIX_BUGS + if (!e->m_rwObject) return true; +#endif + + AlphaObjectInfo item; + item.entity = e; + item.sort = dist; +#ifdef NEW_RENDERER + if(gbNewRenderer && e->IsBuilding()) + return !!m_alphaBuildingList.InsertSorted(item); +#endif + bool ret = !!m_alphaEntityList.InsertSorted(item); +// if(!ret) +// printf("list full %d\n", m_alphaEntityList.Count()); + return ret; +} + +void +CVisibilityPlugins::InitAlphaAtomicList(void) +{ + m_alphaList.Clear(); +} + +bool +CVisibilityPlugins::InsertAtomicIntoSortedList(RpAtomic *a, float dist) +{ + AlphaObjectInfo item; + item.atomic = a; + item.sort = dist; + bool ret = !!m_alphaList.InsertSorted(item); +// if(!ret) +// printf("list full %d\n", m_alphaList.Count()); + return ret; +} + +// can't increase this yet unfortunately... +// probably have to fix fading for this so material alpha isn't overwritten +#define VEHICLE_LODDIST_MULTIPLIER (TheCamera.GenerationDistMultiplier) + +void +CVisibilityPlugins::SetRenderWareCamera(RwCamera *camera) +{ + ms_pCamera = camera; + ms_pCameraPosn = RwMatrixGetPos(RwFrameGetMatrix(RwCameraGetFrame(camera))); + + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED) + ms_cullCompsDist = 1000000.0f; + else + ms_cullCompsDist = sq(TheCamera.LODDistMultiplier * 20.0f); + + ms_vehicleLod0Dist = sq(70.0f * VEHICLE_LODDIST_MULTIPLIER); + ms_vehicleLod1Dist = sq(90.0f * VEHICLE_LODDIST_MULTIPLIER); + ms_vehicleFadeDist = sq(100.0f * VEHICLE_LODDIST_MULTIPLIER); + ms_bigVehicleLod0Dist = sq(60.0f * VEHICLE_LODDIST_MULTIPLIER); + ms_bigVehicleLod1Dist = sq(150.0f * VEHICLE_LODDIST_MULTIPLIER); + ms_pedLod0Dist = sq(25.0f * TheCamera.LODDistMultiplier); + ms_pedLod1Dist = sq(60.0f * TheCamera.LODDistMultiplier); + ms_pedFadeDist = sq(70.0f * TheCamera.LODDistMultiplier); +} + +RpMaterial* +SetAlphaCB(RpMaterial *material, void *data) +{ + ((RwRGBA*)RpMaterialGetColor(material))->alpha = (uint8)(uintptr)data; + return material; +} + +RpMaterial* +SetTextureCB(RpMaterial *material, void *data) +{ + RpMaterialSetTexture(material, (RwTexture*)data); + return material; +} + +void +CVisibilityPlugins::RenderAlphaAtomics(void) +{ + CLink *node; + for(node = m_alphaList.tail.prev; + node != &m_alphaList.head; + node = node->prev) + RENDERCALLBACK(node->item.atomic); +} + +void +CVisibilityPlugins::RenderFadingEntities(void) +{ + CLink *node; + CSimpleModelInfo *mi; + for(node = m_alphaEntityList.tail.prev; + node != &m_alphaEntityList.head; + node = node->prev){ + CEntity *e = node->item.entity; + if(e->m_rwObject == nil) + continue; +#ifdef EXTENDED_PIPELINES + if(CustomPipes::bRenderingEnvMap && (e->IsPed() || e->IsVehicle())) + continue; +#endif + mi = (CSimpleModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex()); + +#ifdef FIX_BUGS + if(mi->GetModelType() == MITYPE_SIMPLE && mi->m_noZwrite) +#else + if(mi->m_noZwrite) +#endif + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); +#ifdef EXTRA_MODEL_FLAGS + else if(mi->m_bIsTree) + SetAlphaRef(128); + if(!e->IsBuilding() || mi->RenderDoubleSided()) + BACKFACE_CULLING_OFF; +#endif + + if(e->bDistanceFade){ + DeActivateDirectional(); + SetAmbientColours(); + e->bImBeingRendered = true; + PUSH_RENDERGROUP(mi->GetModelName()); + RenderFadingAtomic((RpAtomic*)e->m_rwObject, node->item.sort); + POP_RENDERGROUP(); + e->bImBeingRendered = false; + }else + CRenderer::RenderOneNonRoad(e); + +#ifdef EXTRA_MODEL_FLAGS + if(mi->m_bIsTree) + SetAlphaRef(2); + BACKFACE_CULLING_ON; +#endif +#ifdef FIX_BUGS + if(mi->GetModelType() == MITYPE_SIMPLE && mi->m_noZwrite) +#else + if(mi->m_noZwrite) +#endif + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + } +} + +RpAtomic* +CVisibilityPlugins::RenderWheelAtomicCB(RpAtomic *atomic) +{ + RpAtomic *lodatm; + RwMatrix *m; + RwV3d view; + float len; + CSimpleModelInfo *mi; + + mi = GetAtomicModelInfo(atomic); + m = RwFrameGetLTM(RpAtomicGetFrame(atomic)); + RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn); + len = RwV3dLength(&view); +#ifdef FIX_BUGS + // from VC + lodatm = mi->GetAtomicFromDistance(len * TheCamera.LODDistMultiplier / VEHICLE_LODDIST_MULTIPLIER); +#else + lodatm = mi->GetAtomicFromDistance(len); +#endif + if(lodatm){ + if(RpAtomicGetGeometry(lodatm) != RpAtomicGetGeometry(atomic)) + RpAtomicSetGeometry(atomic, RpAtomicGetGeometry(lodatm), rpATOMICSAMEBOUNDINGSPHERE); + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderObjNormalAtomic(RpAtomic *atomic) +{ + RwMatrix *m; + RwV3d view; + float len; + + m = RwFrameGetLTM(RpAtomicGetFrame(atomic)); + RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn); + len = RwV3dLength(&view); + if(RwV3dDotProduct(&view, RwMatrixGetUp(m)) < -0.3f*len && len > 8.0f) + return atomic; + RENDERCALLBACK(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderAlphaAtomic(RpAtomic *atomic, int alpha) +{ + RpGeometry *geo; + uint32 flags; + + geo = RpAtomicGetGeometry(atomic); + flags = RpGeometryGetFlags(geo); + RpGeometrySetFlags(geo, flags | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)alpha); + RENDERCALLBACK(atomic); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)255); + RpGeometrySetFlags(geo, flags); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderFadingAtomic(RpAtomic *atomic, float camdist) +{ + RpAtomic *lodatm; + float fadefactor; + uint32 alpha; + CSimpleModelInfo *mi; + + mi = GetAtomicModelInfo(atomic); + lodatm = mi->GetAtomicFromDistance(camdist - FADE_DISTANCE); + if(mi->m_additive){ + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RENDERCALLBACK(atomic); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + }else{ + fadefactor = (mi->GetLargestLodDistance() - (camdist - FADE_DISTANCE))/FADE_DISTANCE; + if(fadefactor > 1.0f) + fadefactor = 1.0f; + alpha = mi->m_alpha * fadefactor; + if(alpha == 255) + RENDERCALLBACK(atomic); + else{ + RpGeometry *geo = RpAtomicGetGeometry(lodatm); + uint32 flags = RpGeometryGetFlags(geo); + RpGeometrySetFlags(geo, flags | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)alpha); + if(geo != RpAtomicGetGeometry(atomic)) + RpAtomicSetGeometry(atomic, geo, rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?) + RENDERCALLBACK(atomic); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)255); + RpGeometrySetFlags(geo, flags); + } + } + return atomic; +} + + + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_vehicleLod0Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + } + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailAlphaCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_vehicleLod0Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + + if(flags & ATOMIC_FLAG_DRAWLAST){ + // sort before clump + if(!InsertAtomicIntoSortedList(atomic, distsq - 0.0001f)) + RENDERCALLBACK(atomic); + }else{ + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + RENDERCALLBACK(atomic); + } + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod0Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + return atomic; + } + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod0Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + return atomic; + + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB_Boat(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod1Dist) + RENDERCALLBACK(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq >= ms_bigVehicleLod0Dist && + distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + return atomic; + } + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq >= ms_bigVehicleLod0Dist && + distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + return atomic; + + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleReallyLowDetailCB(RpAtomic *atomic) +{ + RpClump *clump; + float dist; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump)); + if(dist >= ms_vehicleLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; + +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq >= ms_bigVehicleLod1Dist) + RENDERCALLBACK(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderTrainHiDetailCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + } + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderTrainHiDetailAlphaCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + + if(flags & ATOMIC_FLAG_DRAWLAST){ + if(!InsertAtomicIntoSortedList(atomic, distsq)) + RENDERCALLBACK(atomic); + }else{ + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + RENDERCALLBACK(atomic); + } + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderPlayerCB(RpAtomic *atomic) +{ + if(CWorld::Players[0].m_pSkinTexture) + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), SetTextureCB, CWorld::Players[0].m_pSkinTexture); + RENDERCALLBACK(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderPedLowDetailCB(RpAtomic *atomic) +{ + RpClump *clump; + float dist; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump)); + if(dist >= ms_pedLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderPedHiDetailCB(RpAtomic *atomic) +{ + RpClump *clump; + float dist; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump)); + if(dist < ms_pedLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; +} + +// This is needed for peds with only one clump, i.e. skinned models +// strangely even the xbox version has no such thing +RpAtomic* +CVisibilityPlugins::RenderPedCB(RpAtomic *atomic) +{ + int32 alpha; + RwV3d cam2atm; + + RwV3dSub(&cam2atm, &RwFrameGetLTM(RpAtomicGetFrame(atomic))->pos, ms_pCameraPosn); + if(RwV3dDotProduct(&cam2atm, &cam2atm) < ms_pedLod1Dist){ + alpha = GetClumpAlpha(RpAtomicGetClump(atomic)); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; +} + +float +CVisibilityPlugins::GetDistanceSquaredFromCamera(RwFrame *frame) +{ + RwMatrix *m; + RwV3d dist; + m = RwFrameGetLTM(frame); + RwV3dSub(&dist, RwMatrixGetPos(m), ms_pCameraPosn); + return RwV3dDotProduct(&dist, &dist); +} + +float +CVisibilityPlugins::GetDotProductWithCameraVector(RwMatrix *atomicMat, RwMatrix *clumpMat, uint32 flags) +{ + RwV3d dist; + float dot, dotdoor; + + // Vehicle forward is the y axis (RwMatrix.up) + // Vehicle right is the x axis (RwMatrix.right) + + RwV3dSub(&dist, RwMatrixGetPos(atomicMat), ms_pCameraPosn); + // forward/backward facing + if(flags & (ATOMIC_FLAG_FRONT | ATOMIC_FLAG_REAR)) + dot = RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat)); + // left/right facing + else if(flags & (ATOMIC_FLAG_LEFT | ATOMIC_FLAG_RIGHT)) + dot = RwV3dDotProduct(&dist, RwMatrixGetRight(clumpMat)); + else + dot = 0.0f; + if(flags & (ATOMIC_FLAG_LEFT | ATOMIC_FLAG_REAR)) + dot = -dot; + + if(flags & (ATOMIC_FLAG_REARDOOR | ATOMIC_FLAG_FRONTDOOR)){ + if(flags & ATOMIC_FLAG_REARDOOR) + dotdoor = -RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat)); + else if(flags & ATOMIC_FLAG_FRONTDOOR) + dotdoor = RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat)); + else + dotdoor = 0.0f; + + if(dot < 0.0f && dotdoor < 0.0f) + dot += dotdoor; + if(dot > 0.0f && dotdoor > 0.0f) + dot += dotdoor; + } + + return dot; +} + +/* These are all unused */ + +bool +CVisibilityPlugins::DefaultVisibilityCB(RpClump *clump) +{ + return true; +} + +bool +CVisibilityPlugins::FrustumSphereCB(RpClump *clump) +{ + RwSphere sphere; + RwFrame *frame = RpClumpGetFrame(clump); + + CClumpModelInfo *modelInfo = (CClumpModelInfo*)GetFrameHierarchyId(frame); + sphere.radius = modelInfo->GetColModel()->boundingSphere.radius; + sphere.center.x = modelInfo->GetColModel()->boundingSphere.center.x; + sphere.center.y = modelInfo->GetColModel()->boundingSphere.center.y; + sphere.center.z = modelInfo->GetColModel()->boundingSphere.center.z; + RwV3dTransformPoints(&sphere.center, &sphere.center, 1, RwFrameGetLTM(frame)); + return RwCameraFrustumTestSphere(ms_pCamera, &sphere) != rwSPHEREOUTSIDE; +} + +bool +CVisibilityPlugins::MloVisibilityCB(RpClump *clump) +{ + RwFrame *frame = RpClumpGetFrame(clump); + CMloModelInfo *modelInfo = (CMloModelInfo*)GetFrameHierarchyId(frame); + if (SQR(modelInfo->drawDist) < GetDistanceSquaredFromCamera(frame)) + return false; + return CVisibilityPlugins::FrustumSphereCB(clump); +} + +bool +CVisibilityPlugins::VehicleVisibilityCB(RpClump *clump) +{ + RwFrame *frame = RpClumpGetFrame(clump); + if (ms_vehicleLod1Dist < GetDistanceSquaredFromCamera(frame)) + return false; + return FrustumSphereCB(clump); +} + +bool +CVisibilityPlugins::VehicleVisibilityCB_BigVehicle(RpClump *clump) +{ + return FrustumSphereCB(clump); +} + + + + +// +// RW Plugins +// + +enum +{ + ID_VISIBILITYATOMIC = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x00), + ID_VISIBILITYCLUMP = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x01), + ID_VISIBILITYFRAME = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x02), +}; + +bool +CVisibilityPlugins::PluginAttach(void) +{ + ms_atomicPluginOffset = RpAtomicRegisterPlugin(sizeof(AtomicExt), + ID_VISIBILITYATOMIC, + AtomicConstructor, AtomicDestructor, AtomicCopyConstructor); + + ms_framePluginOffset = RwFrameRegisterPlugin(sizeof(FrameExt), + ID_VISIBILITYFRAME, + FrameConstructor, FrameDestructor, FrameCopyConstructor); + + ms_clumpPluginOffset = RpClumpRegisterPlugin(sizeof(ClumpExt), + ID_VISIBILITYCLUMP, + ClumpConstructor, ClumpDestructor, ClumpCopyConstructor); + +#if GTA_VERSION <= GTA3_PS2_160 + Initialise(); +#endif + + return ms_atomicPluginOffset != -1 && ms_clumpPluginOffset != -1; +} + +#define ATOMICEXT(o) (RWPLUGINOFFSET(AtomicExt, o, ms_atomicPluginOffset)) +#define FRAMEEXT(o) (RWPLUGINOFFSET(FrameExt, o, ms_framePluginOffset)) +#define CLUMPEXT(o) (RWPLUGINOFFSET(ClumpExt, o, ms_clumpPluginOffset)) + +// +// Atomic +// + +void* +CVisibilityPlugins::AtomicConstructor(void *object, int32, int32) +{ + ATOMICEXT(object)->modelInfo = nil; + return object; +} + +void* +CVisibilityPlugins::AtomicDestructor(void *object, int32, int32) +{ + return object; +} + +void* +CVisibilityPlugins::AtomicCopyConstructor(void *dst, const void *src, int32, int32) +{ + *ATOMICEXT(dst) = *ATOMICEXT(src); + return dst; +} + +void +CVisibilityPlugins::SetAtomicModelInfo(RpAtomic *atomic, + CSimpleModelInfo *modelInfo) +{ + AtomicExt *ext = ATOMICEXT(atomic); + ext->modelInfo = modelInfo; + switch (modelInfo->GetModelType()) { + case MITYPE_SIMPLE: + case MITYPE_TIME: + if(modelInfo->m_normalCull) + SetAtomicRenderCallback(atomic, RenderObjNormalAtomic); + default: break; + } +} + +CSimpleModelInfo* +CVisibilityPlugins::GetAtomicModelInfo(RpAtomic *atomic) +{ + return ATOMICEXT(atomic)->modelInfo; +} + +void +CVisibilityPlugins::SetAtomicFlag(RpAtomic *atomic, int f) +{ + ATOMICEXT(atomic)->flags |= f; +} + +void +CVisibilityPlugins::ClearAtomicFlag(RpAtomic *atomic, int f) +{ + ATOMICEXT(atomic)->flags &= ~f; +} + +void +CVisibilityPlugins::SetAtomicId(RpAtomic *atomic, int id) +{ + ATOMICEXT(atomic)->flags = id; +} + +int +CVisibilityPlugins::GetAtomicId(RpAtomic *atomic) +{ + return ATOMICEXT(atomic)->flags; +} + +void +CVisibilityPlugins::SetAtomicRenderCallback(RpAtomic *atomic, RpAtomicCallBackRender cb) +{ + if(cb == nil) + cb = RENDERCALLBACK; + RpAtomicSetRenderCallBack(atomic, cb); +} + +// +// Frame +// + +void* +CVisibilityPlugins::FrameConstructor(void *object, int32, int32) +{ + FRAMEEXT(object)->id = 0; + return object; +} + +void* +CVisibilityPlugins::FrameDestructor(void *object, int32, int32) +{ + return object; +} + +void* +CVisibilityPlugins::FrameCopyConstructor(void *dst, const void *src, int32, int32) +{ + *FRAMEEXT(dst) = *FRAMEEXT(src); + return dst; +} + +void +CVisibilityPlugins::SetFrameHierarchyId(RwFrame *frame, intptr id) +{ + FRAMEEXT(frame)->id = id; +} + +intptr +CVisibilityPlugins::GetFrameHierarchyId(RwFrame *frame) +{ + return FRAMEEXT(frame)->id; +} + + +// +// Clump +// + +void* +CVisibilityPlugins::ClumpConstructor(void *object, int32, int32) +{ + ClumpExt *ext = CLUMPEXT(object); + ext->visibilityCB = DefaultVisibilityCB; + ext->alpha = 0xFF; + return object; +} + +void* +CVisibilityPlugins::ClumpDestructor(void *object, int32, int32) +{ + return object; +} + +void* +CVisibilityPlugins::ClumpCopyConstructor(void *dst, const void *src, int32, int32) +{ + CLUMPEXT(dst)->visibilityCB = CLUMPEXT(src)->visibilityCB; + return dst; +} + +void +CVisibilityPlugins::SetClumpModelInfo(RpClump *clump, CClumpModelInfo *modelInfo) +{ + CVehicleModelInfo *vmi; + SetFrameHierarchyId(RpClumpGetFrame(clump), (intptr)modelInfo); + + // Unused + switch (modelInfo->GetModelType()) { + case MITYPE_MLO: + CLUMPEXT(clump)->visibilityCB = MloVisibilityCB; + break; + case MITYPE_VEHICLE: + vmi = (CVehicleModelInfo*)modelInfo; + if(vmi->m_vehicleType == VEHICLE_TYPE_TRAIN || + vmi->m_vehicleType == VEHICLE_TYPE_HELI || + vmi->m_vehicleType == VEHICLE_TYPE_PLANE) + CLUMPEXT(clump)->visibilityCB = VehicleVisibilityCB_BigVehicle; + else + CLUMPEXT(clump)->visibilityCB = VehicleVisibilityCB; + break; + default: break; + } +} + +CClumpModelInfo* +CVisibilityPlugins::GetClumpModelInfo(RpClump *clump) +{ + return (CClumpModelInfo*)GetFrameHierarchyId(RpClumpGetFrame(clump)); +} + +void +CVisibilityPlugins::SetClumpAlpha(RpClump *clump, int alpha) +{ + CLUMPEXT(clump)->alpha = alpha; +} + +int +CVisibilityPlugins::GetClumpAlpha(RpClump *clump) +{ + return CLUMPEXT(clump)->alpha; +} + +bool +CVisibilityPlugins::IsClumpVisible(RpClump *clump) +{ + return CLUMPEXT(clump)->visibilityCB(clump); +} diff --git a/src/rw/VisibilityPlugins.h b/src/rw/VisibilityPlugins.h new file mode 100644 index 0000000..f97fd58 --- /dev/null +++ b/src/rw/VisibilityPlugins.h @@ -0,0 +1,141 @@ +#pragma once + +#include "templates.h" + +class CEntity; +class CSimpleModelInfo; +class CClumpModelInfo; + +typedef bool (*ClumpVisibilityCB)(RpClump*); + +class CVisibilityPlugins +{ +public: + struct AlphaObjectInfo + { + union { + CEntity *entity; + RpAtomic *atomic; + }; + float sort; + }; + + static CLinkList m_alphaList; + static CLinkList m_alphaEntityList; +#ifdef NEW_RENDERER + static CLinkList m_alphaBuildingList; +#endif + static RwCamera *ms_pCamera; + static RwV3d *ms_pCameraPosn; + static float ms_cullCompsDist; + static float ms_vehicleLod0Dist; + static float ms_vehicleLod1Dist; + static float ms_vehicleFadeDist; + static float ms_bigVehicleLod0Dist; + static float ms_bigVehicleLod1Dist; + static float ms_pedLod0Dist; + static float ms_pedLod1Dist; + static float ms_pedFadeDist; + + static void Initialise(void); + static void Shutdown(void); + static void InitAlphaEntityList(void); + static bool InsertEntityIntoSortedList(CEntity *e, float dist); + static void InitAlphaAtomicList(void); + static bool InsertAtomicIntoSortedList(RpAtomic *a, float dist); + + static void SetRenderWareCamera(RwCamera *camera); + + static RpAtomic *RenderWheelAtomicCB(RpAtomic *atomic); + static RpAtomic *RenderObjNormalAtomic(RpAtomic *atomic); + static RpAtomic *RenderAlphaAtomic(RpAtomic *atomic, int alpha); + static RpAtomic *RenderFadingAtomic(RpAtomic *atm, float dist); + + static RpAtomic *RenderVehicleHiDetailCB(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailAlphaCB(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailAlphaCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailCB_Boat(RpAtomic *atomic); + static RpAtomic *RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleLowDetailAlphaCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleReallyLowDetailCB(RpAtomic *atomic); + static RpAtomic *RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderTrainHiDetailCB(RpAtomic *atomic); + static RpAtomic *RenderTrainHiDetailAlphaCB(RpAtomic *atomic); + + static RpAtomic *RenderPlayerCB(RpAtomic *atomic); + static RpAtomic *RenderPedLowDetailCB(RpAtomic *atomic); + static RpAtomic *RenderPedHiDetailCB(RpAtomic *atomic); + static RpAtomic *RenderPedCB(RpAtomic *atomic); // for skinned models with only one clump + + static void RenderAlphaAtomics(void); + static void RenderFadingEntities(void); + + // All actually unused + static bool DefaultVisibilityCB(RpClump *clump); + static bool FrustumSphereCB(RpClump *clump); + static bool MloVisibilityCB(RpClump *clump); + static bool VehicleVisibilityCB(RpClump *clump); + static bool VehicleVisibilityCB_BigVehicle(RpClump *clump); + + static float GetDistanceSquaredFromCamera(RwFrame *frame); + static float GetDotProductWithCameraVector(RwMatrix *atomicMat, RwMatrix *clumpMat, uint32 flags); + + // + // RW Plugins + // + + union AtomicExt + { + CSimpleModelInfo *modelInfo; // used by SimpleModelInfo + int flags; // used by ClumpModelInfo + }; + static void SetAtomicModelInfo(RpAtomic*, CSimpleModelInfo*); + static CSimpleModelInfo *GetAtomicModelInfo(RpAtomic *atomic); + static void SetAtomicFlag(RpAtomic*, int); + static void ClearAtomicFlag(RpAtomic*, int); + static void SetAtomicId(RpAtomic *atomic, int); + static int GetAtomicId(RpAtomic *atomic); + static void SetAtomicRenderCallback(RpAtomic*, RpAtomicCallBackRender); + + static void *AtomicConstructor(void *object, int32 offset, int32 len); + static void *AtomicDestructor(void *object, int32 offset, int32 len); + static void *AtomicCopyConstructor(void *dst, const void *src, + int32 offset, int32 len); + static int32 ms_atomicPluginOffset; + + struct FrameExt + { + // BUG: this is abused to hold a pointer by SetClumpModelInfo + intptr id; + }; + static void SetFrameHierarchyId(RwFrame *frame, intptr id); + static intptr GetFrameHierarchyId(RwFrame *frame); + + static void *FrameConstructor(void *object, int32 offset, int32 len); + static void *FrameDestructor(void *object, int32 offset, int32 len); + static void *FrameCopyConstructor(void *dst, const void *src, + int32 offset, int32 len); + static int32 ms_framePluginOffset; + + struct ClumpExt + { + ClumpVisibilityCB visibilityCB; + int alpha; + }; + static void SetClumpModelInfo(RpClump*, CClumpModelInfo*); + static CClumpModelInfo *GetClumpModelInfo(RpClump*); + static void SetClumpAlpha(RpClump*, int); + static int GetClumpAlpha(RpClump*); + static bool IsClumpVisible(RpClump*); + + static void *ClumpConstructor(void *object, int32 offset, int32 len); + static void *ClumpDestructor(void *object, int32 offset, int32 len); + static void *ClumpCopyConstructor(void *dst, const void *src, + int32 offset, int32 len); + static int32 ms_clumpPluginOffset; + + static bool PluginAttach(void); +}; + +RpMaterial *SetAlphaCB(RpMaterial *material, void *data); diff --git a/src/save/Date.cpp b/src/save/Date.cpp new file mode 100644 index 0000000..ca75bb5 --- /dev/null +++ b/src/save/Date.cpp @@ -0,0 +1,91 @@ +#include "common.h" +#include "Date.h" + +CDate::CDate() +{ + m_nYear = 0; + m_nSecond = 0; + m_nMinute = 0; + m_nHour = 0; + m_nDay = 0; + m_nMonth = 0; +} + +bool +CDate::operator>(const CDate &right) +{ + if (m_nYear > right.m_nYear) + return true; + if (m_nYear != right.m_nYear) + return false; + + if (m_nMonth > right.m_nMonth) + return true; + if (m_nMonth != right.m_nMonth) + return false; + + if (m_nDay > right.m_nDay) + return true; + if (m_nDay != right.m_nDay) + return false; + + if (m_nHour > right.m_nHour) + return true; + if (m_nHour != right.m_nHour) + return false; + + if (m_nMinute > right.m_nMinute) + return true; + if (m_nMinute != right.m_nMinute) + return false; + return m_nSecond > right.m_nSecond; +} + +bool +CDate::operator<(const CDate &right) +{ + if (m_nYear < right.m_nYear) + return true; + if (m_nYear != right.m_nYear) + return false; + + if (m_nMonth < right.m_nMonth) + return true; + if (m_nMonth != right.m_nMonth) + return false; + + if (m_nDay < right.m_nDay) + return true; + if (m_nDay != right.m_nDay) + return false; + + if (m_nHour < right.m_nHour) + return true; + if (m_nHour != right.m_nHour) + return false; + + if (m_nMinute < right.m_nMinute) + return true; + if (m_nMinute != right.m_nMinute) + return false; + return m_nSecond < right.m_nSecond; +} + +bool +CDate::operator==(const CDate &right) +{ + if (m_nYear != right.m_nYear || m_nMonth != right.m_nMonth || m_nDay != right.m_nDay || m_nHour != right.m_nHour || m_nMinute != right.m_nMinute) + return false; + return m_nSecond == right.m_nSecond; +} + +void +CDate::PopulateDateFields(int8 &second, int8 &minute, int8 &hour, int8 &day, int8 &month, int16 year) +{ + m_nSecond = second; + m_nMinute = minute; + m_nHour = hour; + m_nDay = day; + m_nMonth = month; + m_nYear = year; +} \ No newline at end of file diff --git a/src/save/Date.h b/src/save/Date.h new file mode 100644 index 0000000..15646c2 --- /dev/null +++ b/src/save/Date.h @@ -0,0 +1,18 @@ +#pragma once + +class CDate +{ +public: + int m_nSecond; + int m_nMinute; + int m_nHour; + int m_nDay; + int m_nMonth; + int m_nYear; + + CDate(); + bool operator>(const CDate &right); + bool operator<(const CDate &right); + bool operator==(const CDate &right); + void PopulateDateFields(int8 &second, int8 &minute, int8 &hour, int8 &day, int8 &month, int16 year); +}; \ No newline at end of file diff --git a/src/save/GenericGameStorage.cpp b/src/save/GenericGameStorage.cpp new file mode 100644 index 0000000..e4ee454 --- /dev/null +++ b/src/save/GenericGameStorage.cpp @@ -0,0 +1,1174 @@ +#define WITHWINDOWS +#include "common.h" +#include "crossplatform.h" +#include "main.h" + +#include "DMAudio.h" +#include "AudioScriptObject.h" +#include "Camera.h" +#include "CarGen.h" +#include "Cranes.h" +#include "Clock.h" +#include "Date.h" +#include "FileMgr.h" +#include "Font.h" +#include "Frontend.h" +#include "GameLogic.h" +#include "Gangs.h" +#include "Garages.h" +#include "GenericGameStorage.h" +#include "Pad.h" +#include "Particle.h" +#include "ParticleObject.h" +#include "PathFind.h" +#include "PCSave.h" +#include "Phones.h" +#include "Pickups.h" +#include "PlayerPed.h" +#include "ProjectileInfo.h" +#include "Pools.h" +#include "Radar.h" +#include "Restart.h" +#include "Script.h" +#include "Stats.h" +#include "Streaming.h" +#include "Timer.h" +#include "TimeStep.h" +#include "Weather.h" +#include "World.h" +#include "Zones.h" + +#define BLOCK_COUNT 20 +#define SIZE_OF_SIMPLEVARS 0xBC + +const uint32 SIZE_OF_ONE_GAME_IN_BYTES = 201729; + +#ifdef MISSION_REPLAY +int8 IsQuickSave; +const int PAUSE_SAVE_SLOT = SLOT_COUNT; +#endif + +char DefaultPCSaveFileName[260]; +char ValidSaveName[260]; +char LoadFileName[256]; +wchar SlotFileName[SLOT_COUNT][260]; +wchar SlotSaveDate[SLOT_COUNT][70]; +int CheckSum; +eLevelName m_LevelToLoad; +char SaveFileNameJustSaved[260]; +int Slots[SLOT_COUNT+1]; +CDate CompileDateAndTime; + +bool b_FoundRecentSavedGameWantToLoad; +bool JustLoadedDontFadeInYet; +bool StillToFadeOut; +uint32 TimeStartedCountingForFade; +uint32 TimeToStayFadedBeforeFadeOut = 1750; + +#define ReadDataFromBufferPointer(buf, to) memcpy(&to, buf, sizeof(to)); buf += align4bytes(sizeof(to)); +#define WriteDataToBufferPointer(buf, from) memcpy(buf, &from, sizeof(from)); buf += align4bytes(sizeof(from)); + +#define LoadSaveDataBlock()\ +do {\ + if (!ReadDataFromFile(file, (uint8 *) &size, 4))\ + return false;\ + size = align4bytes(size);\ + if (!ReadDataFromFile(file, work_buff, size))\ + return false;\ + buf = work_buff;\ +} while (0) + +#define ReadDataFromBlock(msg,load_func)\ +do {\ + debug(msg);\ + ReadDataFromBufferPointer(buf, size);\ + load_func(buf, size);\ + size = align4bytes(size);\ + buf += size;\ +} while (0) + +#define WriteSaveDataBlock(save_func)\ +do {\ + buf = work_buff;\ + reserved = 0;\ + MakeSpaceForSizeInBufferPointer(presize, buf, postsize);\ + save_func(buf, &size);\ + CopySizeAndPreparePointer(presize, buf, postsize, reserved, size);\ + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff))\ + return false;\ + totalSize += buf - work_buff;\ +} while (0) + +bool +GenericSave(int file) +{ + uint8 *buf, *presize, *postsize; + uint32 size; + uint32 reserved; + + uint32 totalSize; + + wchar *lastMissionPassed; + wchar suffix[6]; + wchar saveName[24]; + SYSTEMTIME saveTime; + CPad *currPad; + + CheckSum = 0; + buf = work_buff; + reserved = 0; + + // Save simple vars + lastMissionPassed = TheText.Get(CStats::LastMissionPassedName); + if (lastMissionPassed[0] != '\0') { + AsciiToUnicode("...'", suffix); +#ifdef FIX_BUGS + // fix buffer overflow + int len = UnicodeStrlen(lastMissionPassed); + if (len > ARRAY_SIZE(saveName)-1) + len = ARRAY_SIZE(saveName)-1; + memcpy(saveName, lastMissionPassed, sizeof(wchar) * len); +#else + TextCopy(saveName, lastMissionPassed); + int len = UnicodeStrlen(saveName); +#endif + saveName[len] = '\0'; + if (len > ARRAY_SIZE(saveName)-2) + TextCopy(&saveName[ARRAY_SIZE(saveName)-ARRAY_SIZE(suffix)], suffix); + saveName[ARRAY_SIZE(saveName)-1] = '\0'; + } + WriteDataToBufferPointer(buf, saveName); + GetLocalTime(&saveTime); + WriteDataToBufferPointer(buf, saveTime); +#ifdef MISSION_REPLAY + int32 data = IsQuickSave << 24 | SIZE_OF_ONE_GAME_IN_BYTES; + WriteDataToBufferPointer(buf, data); +#else + WriteDataToBufferPointer(buf, SIZE_OF_ONE_GAME_IN_BYTES); +#endif + WriteDataToBufferPointer(buf, CGame::currLevel); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().x); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().y); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().z); + WriteDataToBufferPointer(buf, CClock::ms_nMillisecondsPerGameMinute); + WriteDataToBufferPointer(buf, CClock::ms_nLastClockTick); + WriteDataToBufferPointer(buf, CClock::ms_nGameClockHours); + WriteDataToBufferPointer(buf, CClock::ms_nGameClockMinutes); + currPad = CPad::GetPad(0); + WriteDataToBufferPointer(buf, currPad->Mode); + WriteDataToBufferPointer(buf, CTimer::m_snTimeInMilliseconds); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeScale); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeStep); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeStepNonClipped); + WriteDataToBufferPointer(buf, CTimer::m_FrameCounter); + WriteDataToBufferPointer(buf, CTimeStep::ms_fTimeStep); + WriteDataToBufferPointer(buf, CTimeStep::ms_fFramesPerUpdate); + WriteDataToBufferPointer(buf, CTimeStep::ms_fTimeScale); + WriteDataToBufferPointer(buf, CWeather::OldWeatherType); + WriteDataToBufferPointer(buf, CWeather::NewWeatherType); + WriteDataToBufferPointer(buf, CWeather::ForcedWeatherType); + WriteDataToBufferPointer(buf, CWeather::InterpolationValue); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nSecond); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nMinute); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nHour); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nDay); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nMonth); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nYear); + WriteDataToBufferPointer(buf, CWeather::WeatherTypeInList); +#ifdef COMPATIBLE_SAVES + // converted to float for compatibility with original format + // TODO: maybe remove this? not really gonna break anything vital + float f = TheCamera.CarZoomIndicator; + WriteDataToBufferPointer(buf, f); + f = TheCamera.PedZoomIndicator; + WriteDataToBufferPointer(buf, f); +#else + WriteDataToBufferPointer(buf, TheCamera.CarZoomIndicator); + WriteDataToBufferPointer(buf, TheCamera.PedZoomIndicator); +#endif + assert(buf - work_buff == SIZE_OF_SIMPLEVARS); + + // Save scripts, block is nested within the same block as simple vars for some reason + presize = buf; + buf += 4; + postsize = buf; + CTheScripts::SaveAllScripts(buf, &size); + CopySizeAndPreparePointer(presize, buf, postsize, reserved, size); + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff)) + return false; + + totalSize = buf - work_buff; + + // Save the rest + WriteSaveDataBlock(CPools::SavePedPool); + WriteSaveDataBlock(CGarages::Save); + WriteSaveDataBlock(CPools::SaveVehiclePool); + WriteSaveDataBlock(CPools::SaveObjectPool); + WriteSaveDataBlock(ThePaths.Save); + WriteSaveDataBlock(CCranes::Save); + WriteSaveDataBlock(CPickups::Save); + WriteSaveDataBlock(gPhoneInfo.Save); + WriteSaveDataBlock(CRestart::SaveAllRestartPoints); + WriteSaveDataBlock(CRadar::SaveAllRadarBlips); + WriteSaveDataBlock(CTheZones::SaveAllZones); + WriteSaveDataBlock(CGangs::SaveAllGangData); + WriteSaveDataBlock(CTheCarGenerators::SaveAllCarGenerators); + WriteSaveDataBlock(CParticleObject::SaveParticle); + WriteSaveDataBlock(cAudioScriptObject::SaveAllAudioScriptObjects); + WriteSaveDataBlock(CWorld::Players[CWorld::PlayerInFocus].SavePlayerInfo); + WriteSaveDataBlock(CStats::SaveStats); + WriteSaveDataBlock(CStreaming::MemoryCardSave); + WriteSaveDataBlock(CPedType::Save); + + // sure just write garbage data repeatedly ... +#ifndef THIS_IS_STUPID + memset(work_buff, 0, sizeof(work_buff)); +#endif + + // Write padding + for (int i = 0; i < 4; i++) { + size = align4bytes(SIZE_OF_ONE_GAME_IN_BYTES - totalSize - 4); + if (size > sizeof(work_buff)) + size = sizeof(work_buff); + if (size > 4) { + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, size)) + return false; + totalSize += size; + } + } + + // Write checksum and close + CFileMgr::Write(file, (const char *) &CheckSum, sizeof(CheckSum)); + if (CFileMgr::GetErrorReadWrite(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; + + return false; + } + + return true; +} + +bool +GenericLoad() +{ + uint8 *buf; + int32 file; + uint32 size; +#ifdef MISSION_REPLAY + int8 qs; +#endif + + int32 saveSize; + CPad *currPad; + + // Load SimpleVars and Scripts + CheckSum = 0; + CDate dummy; // unused + CPad::ResetCheats(); + if (!ReadInSizeofSaveFileBuffer(file, size)) + return false; + size = align4bytes(size); + ReadDataFromFile(file, work_buff, size); + buf = (work_buff + 0x40); + ReadDataFromBufferPointer(buf, saveSize); +#ifdef MISSION_REPLAY // a hack to keep compatibility but get new data from save + qs = saveSize >> 24; +#endif + ReadDataFromBufferPointer(buf, CGame::currLevel); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().x); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().y); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().z); + ReadDataFromBufferPointer(buf, CClock::ms_nMillisecondsPerGameMinute); + ReadDataFromBufferPointer(buf, CClock::ms_nLastClockTick); + ReadDataFromBufferPointer(buf, CClock::ms_nGameClockHours); + ReadDataFromBufferPointer(buf, CClock::ms_nGameClockMinutes); + currPad = CPad::GetPad(0); + ReadDataFromBufferPointer(buf, currPad->Mode); + ReadDataFromBufferPointer(buf, CTimer::m_snTimeInMilliseconds); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeScale); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeStep); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeStepNonClipped); + ReadDataFromBufferPointer(buf, CTimer::m_FrameCounter); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fTimeStep); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fFramesPerUpdate); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fTimeScale); + ReadDataFromBufferPointer(buf, CWeather::OldWeatherType); + ReadDataFromBufferPointer(buf, CWeather::NewWeatherType); + ReadDataFromBufferPointer(buf, CWeather::ForcedWeatherType); + ReadDataFromBufferPointer(buf, CWeather::InterpolationValue); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nSecond); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nMinute); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nHour); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nDay); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nMonth); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nYear); + ReadDataFromBufferPointer(buf, CWeather::WeatherTypeInList); +#ifdef COMPATIBLE_SAVES + // converted to float for compatibility with original format + // TODO: maybe remove this? not really gonna break anything vital + float f; + ReadDataFromBufferPointer(buf, f); + TheCamera.CarZoomIndicator = f; + ReadDataFromBufferPointer(buf, f); + TheCamera.PedZoomIndicator = f; +#else + ReadDataFromBufferPointer(buf, TheCamera.CarZoomIndicator); + ReadDataFromBufferPointer(buf, TheCamera.PedZoomIndicator); +#endif + assert(buf - work_buff == SIZE_OF_SIMPLEVARS); +#ifdef MISSION_REPLAY + WaitForSave = 0; + if (FrontEndMenuManager.m_nCurrSaveSlot == PAUSE_SAVE_SLOT && qs == 3) + WaitForMissionActivate = CTimer::GetTimeInMilliseconds() + 2000; +#endif + ReadDataFromBlock("Loading Scripts \n", CTheScripts::LoadAllScripts); + + // Load the rest + LoadSaveDataBlock(); + ReadDataFromBlock("Loading PedPool \n", CPools::LoadPedPool); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Garages \n", CGarages::Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Vehicles \n", CPools::LoadVehiclePool); + LoadSaveDataBlock(); + CProjectileInfo::RemoveAllProjectiles(); + CObject::DeleteAllTempObjects(); + ReadDataFromBlock("Loading Objects \n", CPools::LoadObjectPool); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Paths \n", ThePaths.Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Cranes \n", CCranes::Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Pickups \n", CPickups::Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Phoneinfo \n", gPhoneInfo.Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Restart \n", CRestart::LoadAllRestartPoints); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Radar Blips \n", CRadar::LoadAllRadarBlips); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Zones \n", CTheZones::LoadAllZones); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Gang Data \n", CGangs::LoadAllGangData); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Car Generators \n", CTheCarGenerators::LoadAllCarGenerators); + CParticle::ReloadConfig(); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Particles \n", CParticleObject::LoadParticle); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading AudioScript Objects \n", cAudioScriptObject::LoadAllAudioScriptObjects); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Player Info \n", CWorld::Players[CWorld::PlayerInFocus].LoadPlayerInfo); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Stats \n", CStats::LoadStats); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Streaming Stuff \n", CStreaming::MemoryCardLoad); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading PedType Stuff \n", CPedType::Load); + + DMAudio.SetMusicMasterVolume(CMenuManager::m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(CMenuManager::m_PrefsSfxVolume); + if (!CloseFile(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + return false; + } + + DoGameSpecificStuffAfterSucessLoad(); + debug("Game successfully loaded \n"); + return true; +} + +bool +ReadInSizeofSaveFileBuffer(int32 &file, uint32 &size) +{ + file = CFileMgr::OpenFile(LoadFileName, "rb"); + if (file == 0) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN; + return false; + } + CFileMgr::Read(file, (const char*)&size, sizeof(size)); + if (CFileMgr::GetErrorReadWrite(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + return false; + } + return true; +} + +bool +ReadDataFromFile(int32 file, uint8 *buf, uint32 size) +{ + if (file == 0) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN; + return false; + } + size_t read_size = CFileMgr::Read(file, (const char*)buf, size); + if (CFileMgr::GetErrorReadWrite(file) || read_size != size) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + return false; + } + return true; +} + +bool +CloseFile(int32 file) +{ + return CFileMgr::CloseFile(file) == 0; +} + +void +DoGameSpecificStuffAfterSucessLoad() +{ + StillToFadeOut = true; + JustLoadedDontFadeInYet = true; + CTheScripts::Process(); +} + +bool +CheckSlotDataValid(int32 slot) +{ + PcSaveHelper.nErrorCode = SAVESTATUS_SUCCESSFUL; + if (CheckDataNotCorrupt(slot, LoadFileName)) { + CStreaming::DeleteAllRwObjects(); + return true; + } + + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_DATA_INVALID; + return false; +} + +void +MakeSpaceForSizeInBufferPointer(uint8 *&presize, uint8 *&buf, uint8 *&postsize) +{ + presize = buf; + buf += sizeof(uint32); + postsize = buf; +} + +void +CopySizeAndPreparePointer(uint8 *&buf, uint8 *&postbuf, uint8 *&postbuf2, uint32 &unused, uint32 &size) +{ + memcpy(buf, &size, sizeof(size)); + size = align4bytes(size); + postbuf2 += size; + postbuf = postbuf2; +} + +void +DoGameSpecificStuffBeforeSave() +{ + CGameLogic::PassTime(360); + CPlayerPed *ped = FindPlayerPed(); + ped->m_fCurrentStamina = ped->m_fMaxStamina; + CGame::TidyUpMemory(true, false); +} + + +void +MakeValidSaveName(int32 slot) +{ + ValidSaveName[0] = '\0'; + sprintf(ValidSaveName, "%s%i", DefaultPCSaveFileName, slot + 1); + strncat(ValidSaveName, ".b", 5); +} + +wchar * +GetSavedGameDateAndTime(int32 slot) +{ + return SlotSaveDate[slot]; +} + +wchar * +GetNameOfSavedGame(int32 slot) +{ + return SlotFileName[slot]; +} + +bool +CheckDataNotCorrupt(int32 slot, char *name) +{ +#ifdef FIX_BUGS + char filename[MAX_PATH]; +#else + char filename[100]; +#endif + + int32 blocknum = 0; + eLevelName level = LEVEL_GENERIC; + CheckSum = 0; + uint32 bytes_processed = 0; + sprintf(filename, "%s%i%s", DefaultPCSaveFileName, slot + 1, ".b"); + int file = CFileMgr::OpenFile(filename, "rb"); + if (file == 0) + return false; + strcpy(name, filename); + while (SIZE_OF_ONE_GAME_IN_BYTES - sizeof(uint32) > bytes_processed && blocknum < 40) { + int32 blocksize; + if (!ReadDataFromFile(file, (uint8*)&blocksize, sizeof(blocksize))) { + CloseFile(file); + return false; + } + if (blocksize > align4bytes(sizeof(work_buff))) + blocksize = sizeof(work_buff) - sizeof(uint32); + if (!ReadDataFromFile(file, work_buff, align4bytes(blocksize))) { + CloseFile(file); + return false; + } + + CheckSum += ((uint8*)&blocksize)[0]; + CheckSum += ((uint8*)&blocksize)[1]; + CheckSum += ((uint8*)&blocksize)[2]; + CheckSum += ((uint8*)&blocksize)[3]; + uint8 *_work_buf = work_buff; + for (int i = 0; i < align4bytes(blocksize); i++) { + CheckSum += *_work_buf++; + bytes_processed++; + } + + if (blocknum == 0) + memcpy(&level, work_buff+4, sizeof(level)); + blocknum++; + } + int32 _checkSum; + if (ReadDataFromFile(file, (uint8*)&_checkSum, sizeof(_checkSum))) { + if (CloseFile(file)) { + if (CheckSum == _checkSum) { + m_LevelToLoad = level; + return true; + } + return false; + } + return false; + } + + CloseFile(file); + return false; +} + +bool +RestoreForStartLoad() +{ + uint8 buf[999]; + + int file = CFileMgr::OpenFile(LoadFileName, "rb"); + if (file == 0) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN; + return false; + } + ReadDataFromFile(file, buf, sizeof(buf)); + if (CFileMgr::GetErrorReadWrite(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + return false; + } else { + uint8 *_buf = buf + sizeof(int32) + sizeof(wchar[24]) + sizeof(SYSTEMTIME) + sizeof(SIZE_OF_ONE_GAME_IN_BYTES); + ReadDataFromBufferPointer(_buf, CGame::currLevel); + ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().x); + ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().y); + ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().z); + ISLAND_LOADING_IS(LOW) + { + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + } + CCollision::SortOutCollisionAfterLoad(); + ISLAND_LOADING_IS(LOW) + { + CStreaming::RequestBigBuildings(CGame::currLevel); + CStreaming::LoadAllRequestedModels(false); + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); + CGame::TidyUpMemory(true, false); + } + if (CloseFile(file)) { + return true; + } else { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + return false; + } + } +} + +int +align4bytes(int32 size) +{ + return (size + 3) & 0xFFFFFFFC; +} + +#ifdef FIX_INCOMPATIBLE_SAVES +#define LoadSaveDataBlockNoCheck(buf, file, size) \ +do { \ + CFileMgr::Read(file, (const char *)&size, sizeof(size)); \ + size = align4bytes(size); \ + CFileMgr::Read(file, (const char *)work_buff, size); \ + buf = work_buff; \ +} while(0) + +#define WriteSavaDataBlockNoFunc(buf, file, size) \ +do { \ + if (!PcSaveHelper.PcClassSaveRoutine(file, buf, size)) \ + goto fail; \ + totalSize += size; \ +} while(0) + +#define FixSaveDataBlock(fix_func, file, size) \ +do { \ + ReadDataFromBufferPointer(buf, size); \ + memset(work_buff2, 0, sizeof(work_buff2)); \ + buf2 = work_buff2; \ + reserved = 0; \ + MakeSpaceForSizeInBufferPointer(presize, buf2, postsize); \ + fix_func(save_type, buf, buf2, &size); \ + CopySizeAndPreparePointer(presize, buf2, postsize, reserved, size); \ + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff2, buf2 - work_buff2)) \ + goto fail; \ + totalSize += buf2 - work_buff2; \ +} while(0) + +#define ReadDataFromBufferPointerWithSize(buf, to, size) memcpy(&to, buf, size); buf += align4bytes(size) + +#define ReadBuf(buf, to) memcpy(&to, buf, sizeof(to)); buf += sizeof(to) +#define WriteBuf(buf, from) memcpy(buf, &from, sizeof(from)); buf += sizeof(from) +#define CopyBuf(from, to, size) memcpy(to, from, size); to += (size); from += (size) +#define CopyPtr(from, to) memcpy(to, from, 4); to += 4; from += 8 +#define SkipBuf(buf, size) buf += (size) +#define SkipBoth(from, to, size) to += (size); from += (size) +#define SkipPtr(from, to) to += 4; from += 8 + +// unfortunately we need a 2nd buffer of the same size to store the fixed output ... +static uint8 work_buff2[sizeof(work_buff)]; + +enum +{ + SAVE_TYPE_NONE = 0, + SAVE_TYPE_32_BIT = 1, + SAVE_TYPE_64_BIT = 2, + SAVE_TYPE_MSVC = 4, + SAVE_TYPE_GCC = 8, +}; + +uint8 +GetSaveType(char *savename) +{ + uint8 save_type = SAVE_TYPE_NONE; + int file = CFileMgr::OpenFile(savename, "rb"); + + uint32 size; + CFileMgr::Read(file, (const char *)&size, sizeof(size)); + + uint8 *buf = work_buff; + CFileMgr::Read(file, (const char *)work_buff, size); // simple vars + scripts + + LoadSaveDataBlockNoCheck(buf, file, size); // ped pool + + LoadSaveDataBlockNoCheck(buf, file, size); // garages + ReadDataFromBufferPointer(buf, size); + + // store for later after we know how much data we need to skip + ReadDataFromBufferPointerWithSize(buf, work_buff2, size); + + LoadSaveDataBlockNoCheck(buf, file, size); // vehicle pool + LoadSaveDataBlockNoCheck(buf, file, size); // object pool + LoadSaveDataBlockNoCheck(buf, file, size); // paths + + LoadSaveDataBlockNoCheck(buf, file, size); // cranes + + CFileMgr::CloseFile(file); + + ReadDataFromBufferPointer(buf, size); + + if (size == 1032) + save_type |= SAVE_TYPE_32_BIT; + else if (size == 1160) + save_type |= SAVE_TYPE_64_BIT; + else + assert(0); // this should never happen + + buf = work_buff2; + + buf += 760; // skip everything before the first garage + buf += save_type & SAVE_TYPE_32_BIT ? 28 : 40; // skip first garage up to m_fX1 + + // now the values we want to verify + float fX1, fX2, fY1, fY2, fZ1, fZ2; + + ReadBuf(buf, fX1); + ReadBuf(buf, fX2); + ReadBuf(buf, fY1); + ReadBuf(buf, fY2); + ReadBuf(buf, fZ1); + ReadBuf(buf, fZ2); + + if (fX1 == CRUSHER_GARAGE_X1 && fX2 == CRUSHER_GARAGE_X2 && + fY1 == CRUSHER_GARAGE_Y1 && fY2 == CRUSHER_GARAGE_Y2 && + fZ1 == CRUSHER_GARAGE_Z1 && fZ2 == CRUSHER_GARAGE_Z2) + save_type |= SAVE_TYPE_MSVC; + else + save_type |= SAVE_TYPE_GCC; + + return save_type; +} + +static void +FixGarages(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + // hardcoded: 5484 + // x86 msvc: 5240 + // x86 gcc: 5040 + // amd64 msvc: 5880 + // amd64 gcc: 5808 + + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read; + uint32 written = 5240; + + if (save_type & SAVE_TYPE_32_BIT && save_type & SAVE_TYPE_GCC) + read = 5040; + else if (save_type & SAVE_TYPE_64_BIT && save_type & SAVE_TYPE_GCC) + read = 5808; + else + read = 5880; + + uint32 ptrsize = save_type & SAVE_TYPE_32_BIT ? 4 : 8; + + CopyBuf(buf, buf2, 4 * 6); + CopyBuf(buf, buf2, 4 * TOTAL_COLLECTCARS_GARAGES); + CopyBuf(buf, buf2, 4); + + if (save_type & SAVE_TYPE_GCC) + { + for (int32 i = 0; i < NUM_GARAGE_STORED_CARS; i++) + { +#define FixStoredCar(buf, buf2) \ +do { \ + CopyBuf(buf, buf2, 4 + sizeof(CVector) + sizeof(CVector)); \ + uint8 nFlags8; \ + ReadBuf(buf, nFlags8); \ + int32 nFlags32 = nFlags8; \ + WriteBuf(buf2, nFlags32); \ + CopyBuf(buf, buf2, 1 * 6); \ + SkipBuf(buf, 1); \ + SkipBuf(buf2, 2); \ +} while(0) + + FixStoredCar(buf, buf2); + FixStoredCar(buf, buf2); + FixStoredCar(buf, buf2); + +#undef FixStoredCar + } + } + else + { + CopyBuf(buf, buf2, sizeof(CStoredCar) * NUM_GARAGE_STORED_CARS); + CopyBuf(buf, buf2, sizeof(CStoredCar) * NUM_GARAGE_STORED_CARS); + CopyBuf(buf, buf2, sizeof(CStoredCar) * NUM_GARAGE_STORED_CARS); + } + + for (int32 i = 0; i < NUM_GARAGES; i++) + { + // skip the last 5 garages in 64bit builds without FIX_GARAGE_SIZE since they weren't actually saved and are unused + if (save_type & SAVE_TYPE_64_BIT && *size == 5484 && i >= NUM_GARAGES - 5) + { + SkipBuf(buf, 160); // sizeof(CGarage) on x64 + SkipBuf(buf2, 140); // sizeof(CGarage) on x86 + } + else + { + CopyBuf(buf, buf2, 1 * 6); + SkipBoth(buf, buf2, 2); + CopyBuf(buf, buf2, 4); + SkipBuf(buf, ptrsize - 4); // write 4 bytes padding if 8 byte pointer, if not, write 0 + SkipBuf(buf, ptrsize * 2); + SkipBuf(buf2, 4 * 2); + CopyBuf(buf, buf2, 1 * 7); + SkipBoth(buf, buf2, 1); + CopyBuf(buf, buf2, 4 * 15 + 1); + SkipBoth(buf, buf2, 3); + SkipBuf(buf, ptrsize * 2); + SkipBuf(buf2, 4 * 2); + + if (save_type & SAVE_TYPE_GCC) + SkipBuf(buf, save_type & SAVE_TYPE_64_BIT ? 36 + 4 : 36); // sizeof(CStoredCar) on gcc 64/32 before fix + else + SkipBuf(buf, sizeof(CStoredCar)); + + SkipBuf(buf2, sizeof(CStoredCar)); + } + } + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + +#ifdef FIX_GARAGE_SIZE + *size = (6 * sizeof(uint32) + TOTAL_COLLECTCARS_GARAGES * sizeof(*CGarages::CarTypesCollected) + sizeof(uint32) + 3 * NUM_GARAGE_STORED_CARS * sizeof(CStoredCar) + NUM_GARAGES * sizeof(CGarage)); +#else + *size = 5484; +#endif +} + +static void +FixCranes(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read = 2 * sizeof(uint32) + 0x480; // sizeof(aCranes) + uint32 written = 2 * sizeof(uint32) + 0x400; // see CRANES_SAVE_SIZE + + CopyBuf(buf, buf2, 4 + 4); + + for (int32 i = 0; i < NUM_CRANES; i++) + { + CopyPtr(buf, buf2); + CopyPtr(buf, buf2); + CopyBuf(buf, buf2, 15 * 4 + sizeof(CVector) * 3 + sizeof(CVector2D)); + CopyPtr(buf, buf2); + CopyBuf(buf, buf2, 4 + 7 * 1); + SkipBuf(buf, 5); + SkipBuf(buf2, 1); + } + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +static void +FixPickups(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read = 0x3480 + sizeof(uint16) + sizeof(uint16) + sizeof(int32) * NUMCOLLECTEDPICKUPS; // sizeof(aPickUps) + uint32 written = 0x24C0 + sizeof(uint16) + sizeof(uint16) + sizeof(int32) * NUMCOLLECTEDPICKUPS; // see PICKUPS_SAVE_SIZE + + for (int32 i = 0; i < NUMPICKUPS; i++) + { + CopyBuf(buf, buf2, 1 + 1 + 2); + SkipBuf(buf, 4); + CopyPtr(buf, buf2); + CopyBuf(buf, buf2, 4 + 2 + 2 + sizeof(CVector)); + SkipBuf(buf, 4); + } + + CopyBuf(buf, buf2, 2); + SkipBoth(buf, buf2, 2); + + CopyBuf(buf, buf2, NUMCOLLECTEDPICKUPS * 4); + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +static void +FixPhoneInfo(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read = 0x1138; // sizeof(CPhoneInfo) + uint32 written = 0xA30; // see PHONEINFO_SAVE_SIZE + + CopyBuf(buf, buf2, 4 + 4); + + for (int32 i = 0; i < NUMPHONES; i++) + { + CopyBuf(buf, buf2, sizeof(CVector)); + SkipBuf(buf, 4); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + CopyBuf(buf, buf2, 4); + SkipBuf(buf, 4); + CopyPtr(buf, buf2); + CopyBuf(buf, buf2, 4 + 1); + SkipBoth(buf, buf2, 3); + } + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +static void +FixZones(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read = 11300; // see SaveAllZones + uint32 written = 10100; // see SaveAllZones + + CopyBuf(buf, buf2, 1 * 4); + + SkipBuf(buf, 4); + uint32 hdr_size = 10100 - (1 * 4 + 4); // see SaveAllZones + WriteBuf(buf2, hdr_size); + + CopyBuf(buf, buf2, 4 * 2 + 2); + SkipBoth(buf, buf2, 2); + +#define FixOneZone(buf, buf2) \ +do { \ + CopyBuf(buf, buf2, 8 + 8 * 4 + 2 * 2); \ + SkipBuf(buf, 4); \ + CopyPtr(buf, buf2); \ + CopyPtr(buf, buf2); \ + CopyPtr(buf, buf2); \ +} while(0) + + for (int32 i = 0; i < NUMZONES; i++) + FixOneZone(buf, buf2); + + CopyBuf(buf, buf2, sizeof(CZoneInfo) * NUMZONES * 2); + CopyBuf(buf, buf2, 2 + 2); + + for (int32 i = 0; i < NUMMAPZONES; i++) + FixOneZone(buf, buf2); + + CopyBuf(buf, buf2, 2 * NUMAUDIOZONES); + CopyBuf(buf, buf2, 2 + 2); + +#undef FixOneZone + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +static void +FixParticles(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + + int32 numObjects; + ReadBuf(buf, numObjects); + WriteBuf(buf2, numObjects); + + uint32 read = 0xA0 * (numObjects + 1) + 4; // sizeof(CParticleObject) + uint32 written = 0x88 * (numObjects + 1) + 4; // see PARTICLE_OBJECT_SIZEOF + + for (int32 i = 0; i < numObjects; i++) + { + // CPlaceable + SkipPtr(buf, buf2); + CopyBuf(buf, buf2, 4 * 4 * 4); + SkipPtr(buf, buf2); + CopyBuf(buf, buf2, 1); + SkipBuf(buf, 7); + SkipBuf(buf2, 3); + + // CParticleObject + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + CopyBuf(buf, buf2, 4 * 3 + 2 * 1 + 2 * 2); + SkipBoth(buf, buf2, 2); + CopyBuf(buf, buf2, sizeof(CVector) + 2 * 4 + sizeof(CRGBA) + 2 * 1); + SkipBoth(buf, buf2, 2); + } + + SkipBuf(buf, 0xA0); // sizeof(CParticleObject) + SkipBuf(buf2, 0x88); // see PARTICLE_OBJECT_SIZEOF + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +bool +FixSave(int32 slot, uint8 save_type) +{ + if (save_type & SAVE_TYPE_32_BIT && save_type & SAVE_TYPE_MSVC) + return true; + + bool success = false; + + uint8 *buf, *presize, *postsize, *buf2; + uint32 size; + uint32 reserved; + + uint32 totalSize; + + char savename[MAX_PATH]; + char savename_bak[MAX_PATH]; + + sprintf(savename, "%s%i%s", DefaultPCSaveFileName, slot + 1, ".b"); + sprintf(savename_bak, "%s%i%s.%lld.bak", DefaultPCSaveFileName, slot + 1, ".b", time(nil)); + + assert(caserename(savename, savename_bak) == 0); + + int file_in = CFileMgr::OpenFile(savename_bak, "rb"); + int file_out = CFileMgr::OpenFileForWriting(savename); + + CheckSum = 0; + totalSize = 0; + + CFileMgr::Read(file_in, (const char *)&size, sizeof(size)); + size = align4bytes(size); + + buf = work_buff; + CFileMgr::Read(file_in, (const char *)work_buff, size); // simple vars + scripts + + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // ped pool + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // garages + FixSaveDataBlock(FixGarages, file_out, size); // garages need to be fixed in either case + + LoadSaveDataBlockNoCheck(buf, file_in, size); // vehicle pool + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // object pool + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // paths + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // cranes + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixCranes, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // pickups + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixPickups, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // phoneinfo + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixPhoneInfo, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // restart + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // radar blips + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // zones + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixZones, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // gang data + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // car generators + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // particles + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixParticles, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // audio script objects + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // player info + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // stats + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // streaming + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // ped type + WriteSavaDataBlockNoFunc(buf, file_out, size); + + memset(work_buff, 0, sizeof(work_buff)); + + for (int i = 0; i < 4; i++) { + size = align4bytes(SIZE_OF_ONE_GAME_IN_BYTES - totalSize - 4); + if (size > sizeof(work_buff)) + size = sizeof(work_buff); + if (size > 4) { + if (!PcSaveHelper.PcClassSaveRoutine(file_out, work_buff, size)) + goto fail; + totalSize += size; + } + } + + if (!CFileMgr::Write(file_out, (const char *)&CheckSum, sizeof(CheckSum))) + goto fail; + + success = true; + +fail:; + CFileMgr::CloseFile(file_in); + CFileMgr::CloseFile(file_out); + + return success; +} + +#undef LoadSaveDataBlockNoCheck +#undef WriteSavaDataBlockNoFunc +#undef FixSaveDataBlock +#undef ReadDataFromBufferPointerWithSize +#undef ReadBuf +#undef WriteBuf +#undef CopyBuf +#undef CopyPtr +#undef SkipBuf +#undef SkipBoth +#undef SkipPtr +#endif + +#ifdef MISSION_REPLAY + +void DisplaySaveResult(int unk, char* name) +{} + +bool SaveGameForPause(int type) +{ + if (AllowMissionReplay != MISSION_RETRY_STAGE_NORMAL) + return false; + if (type != SAVE_TYPE_QUICKSAVE_FOR_MISSION_REPLAY && WaitForSave > CTimer::GetTimeInMilliseconds()) + return false; + WaitForSave = 0; + if (gGameState != GS_PLAYING_GAME || CTheScripts::IsPlayerOnAMission() || CStats::LastMissionPassedName[0] == '\0') { + DisplaySaveResult(3, CStats::LastMissionPassedName); + return false; + } + IsQuickSave = type; + MissionStartTime = 0; + int res = PcSaveHelper.SaveSlot(PAUSE_SAVE_SLOT); + PcSaveHelper.PopulateSlotInfo(); + IsQuickSave = 0; + DisplaySaveResult(res, CStats::LastMissionPassedName); + return true; +} +#endif diff --git a/src/save/GenericGameStorage.h b/src/save/GenericGameStorage.h new file mode 100644 index 0000000..6a5b04f --- /dev/null +++ b/src/save/GenericGameStorage.h @@ -0,0 +1,63 @@ +#pragma once + +#include "Game.h" +#include "PCSave.h" + +#define SLOT_COUNT (8) + +bool GenericSave(int file); +bool GenericLoad(); +bool ReadInSizeofSaveFileBuffer(int32 &file, uint32 &size); +bool ReadDataFromFile(int32 file, uint8 *buf, uint32 size); +bool CloseFile(int32 file); +void DoGameSpecificStuffAfterSucessLoad(); +bool CheckSlotDataValid(int32 slot); +void MakeSpaceForSizeInBufferPointer(uint8 *&presize, uint8 *&buf, uint8 *&postsize); +void CopySizeAndPreparePointer(uint8 *&buf, uint8 *&postbuf, uint8 *&postbuf2, uint32 &unused, uint32 &size); +void DoGameSpecificStuffBeforeSave(); +void MakeValidSaveName(int32 slot); +wchar *GetSavedGameDateAndTime(int32 slot); +wchar *GetNameOfSavedGame(int32 slot); +bool CheckDataNotCorrupt(int32 slot, char *name); +bool RestoreForStartLoad(); +int align4bytes(int32 size); + +#ifdef FIX_INCOMPATIBLE_SAVES +uint8 GetSaveType(char *savename); +bool FixSave(int32 slot, uint8 save_type); +#endif + +extern class CDate CompileDateAndTime; + +extern char DefaultPCSaveFileName[260]; +extern char ValidSaveName[260]; +extern char LoadFileName[256]; +extern wchar SlotFileName[SLOT_COUNT][260]; +extern wchar SlotSaveDate[SLOT_COUNT][70]; +extern int CheckSum; +extern enum eLevelName m_LevelToLoad; +extern int Slots[SLOT_COUNT+1]; + +extern bool b_FoundRecentSavedGameWantToLoad; +extern bool JustLoadedDontFadeInYet; +extern bool StillToFadeOut; +extern uint32 TimeStartedCountingForFade; +extern uint32 TimeToStayFadedBeforeFadeOut; + +extern char SaveFileNameJustSaved[260]; // 8F2570 + +const char TopLineEmptyFile[] = "THIS FILE IS NOT VALID YET"; + +#ifdef MISSION_REPLAY +extern int8 IsQuickSave; // originally int + +bool SaveGameForPause(int); + +enum { + SAVE_TYPE_NORMAL, + SAVE_TYPE_QUICKSAVE, + SAVE_TYPE_2, + SAVE_TYPE_QUICKSAVE_FOR_MISSION_REPLAY +}; + +#endif diff --git a/src/save/MemoryCard.cpp b/src/save/MemoryCard.cpp new file mode 100644 index 0000000..d6e95d3 --- /dev/null +++ b/src/save/MemoryCard.cpp @@ -0,0 +1,3084 @@ +#define WITHWINDOWS +#include "common.h" +#ifdef PS2_MENU +#include "crossplatform.h" +#include "MemoryCard.h" +#include "main.h" +#include "DMAudio.h" +#include "AudioScriptObject.h" +#include "Camera.h" +#include "CarGen.h" +#include "Cranes.h" +#include "Clock.h" +#include "MBlur.h" +#include "Date.h" +#include "Font.h" +#include "FileMgr.h" +#include "Game.h" +#include "GameLogic.h" +#include "Gangs.h" +#include "Garages.h" +#include "GenericGameStorage.h" +#include "Pad.h" +#include "Particle.h" +#include "ParticleObject.h" +#include "PathFind.h" +#include "PCSave.h" +#include "Phones.h" +#include "Pickups.h" +#include "PlayerPed.h" +#include "ProjectileInfo.h" +#include "Pools.h" +#include "Radar.h" +#include "Restart.h" +#include "Script.h" +#include "Stats.h" +#include "Streaming.h" +#include "Sprite2d.h" +#include "Timer.h" +#include "TimeStep.h" +#include "Weather.h" +#include "World.h" +#include "Zones.h" +#include "Frontend_PS2.h" + +CMemoryCard TheMemoryCard; + +char icon_one[16] = "slime1.ico"; +char icon_two[16] = "slime2.ico"; +char icon_three[16] = "slime3.ico"; +char HostFileLocationOfIcons[64] = "icons\\"; +char TheGameRootDirectory[64] = "/BESLES-50330GTA30000"; + +#define ReadDataFromBufferPointer(buf, to) memcpy(&to, buf, sizeof(to)); buf += align4bytes(sizeof(to)); +#define WriteDataToBufferPointer(buf, from) memcpy(buf, &from, sizeof(from)); buf += align4bytes(sizeof(from)); + +static int +align4bytes(int32 size) +{ + return (size + 3) & 0xFFFFFFFC; +} + +unsigned short ascii_table[3][2] = +{ + { 0x824F, 0x30 }, /* 0-9 */ + { 0x8260, 0x41 }, /* A-Z */ + { 0x8281, 0x61 }, /* a-z */ +}; + +unsigned short ascii_special[33][2] = +{ + {0x8140, 0x20}, /* " " */ + {0x8149, 0x21}, /* "!" */ + {0x8168, 0x22}, /* """ */ + {0x8194, 0x23}, /* "#" */ + {0x8190, 0x24}, /* "$" */ + {0x8193, 0x25}, /* "%" */ + {0x8195, 0x26}, /* "&" */ + {0x8166, 0x27}, /* "'" */ + {0x8169, 0x28}, /* "(" */ + {0x816A, 0x29}, /* ")" */ + {0x8196, 0x2A}, /* "*" */ + {0x817B, 0x2B}, /* "+" */ + {0x8143, 0x2C}, /* "," */ + {0x817C, 0x2D}, /* "-" */ + {0x8144, 0x2E}, /* "." */ + {0x815E, 0x2F}, /* "/" */ + {0x8146, 0x3A}, /* ":" */ + {0x8147, 0x3B}, /* ";" */ + {0x8171, 0x3C}, /* "<" */ + {0x8181, 0x3D}, /* "=" */ + {0x8172, 0x3E}, /* ">" */ + {0x8148, 0x3F}, /* "?" */ + {0x8197, 0x40}, /* "@" */ + {0x816D, 0x5B}, /* "[" */ + {0x818F, 0x5C}, /* "\" */ + {0x816E, 0x5D}, /* "]" */ + {0x814F, 0x5E}, /* "^" */ + {0x8151, 0x5F}, /* "_" */ + {0x8165, 0x60}, /* "`" */ + {0x816F, 0x7B}, /* "{" */ + {0x8162, 0x7C}, /* "|" */ + {0x8170, 0x7D}, /* "}" */ + {0x8150, 0x7E}, /* "~" */ +}; + +unsigned short +Ascii2Sjis(unsigned char ascii_code) +{ + unsigned short sjis_code = 0; + unsigned char stmp; + unsigned char stmp2 = 0; + + if ((ascii_code >= 0x20) && (ascii_code <= 0x2f)) + stmp2 = 1; + else + if ((ascii_code >= 0x30) && (ascii_code <= 0x39)) + stmp = 0; + else + if ((ascii_code >= 0x3a) && (ascii_code <= 0x40)) + stmp2 = 11; + else + if ((ascii_code >= 0x41) && (ascii_code <= 0x5a)) + stmp = 1; + else + if ((ascii_code >= 0x5b) && (ascii_code <= 0x60)) + stmp2 = 37; + else + if ((ascii_code >= 0x61) && (ascii_code <= 0x7a)) + stmp = 2; + else + if ((ascii_code >= 0x7b) && (ascii_code <= 0x7e)) + stmp2 = 63; + else { + printf("bad ASCII code 0x%x\n", ascii_code); + return(0); + } + + if (stmp2) + sjis_code = ascii_special[ascii_code - 0x20 - (stmp2 - 1)][0]; + else + sjis_code = ascii_table[stmp][0] + ascii_code - ascii_table[stmp][1]; + + return(sjis_code); +} + +#if defined(GTA_PC) + +extern "C" +{ + extern void HandleExit(); +} + +char CardCurDir[MAX_CARDS][260] = { "", "" }; +char PCCardsPath[260]; +char PCCardDir[MAX_CARDS][12] = { "memcard1", "memcard2" }; + +const char* _psGetUserFilesFolder(); +void _psCreateFolder(LPCSTR path); + +void +PCMCInit() +{ + sprintf(PCCardsPath, "%s", _psGetUserFilesFolder()); + + char path[512]; + + sprintf(path, "%s\\%s", PCCardsPath, PCCardDir[CARD_ONE]); + _psCreateFolder(path); + + sprintf(path, "%s\\%s", PCCardsPath, PCCardDir[CARD_TWO]); + _psCreateFolder(path); +} +#endif + +CMemoryCardInfo::CMemoryCardInfo(void) +{ + type = 0; + free = 0; + format = 0; + + for ( int32 i = 0; i < sizeof(dir); i++ ) + dir[i] = '\0'; + + strncpy(dir, TheGameRootDirectory, sizeof(dir) - 1); +} + +int32 +CMemoryCard::Init(void) +{ +#if defined(PS2) + if ( sceMcInit() == sceMcIniSucceed ) + { + printf("Memory card initialsed\n"); + return RES_SUCCESS; + } + + printf("Memory Card not being initialised\n"); + return RES_FAILED; +#else + PCMCInit(); + printf("Memory card initialsed\n"); + return RES_SUCCESS; +#endif +} + +CMemoryCard::CMemoryCard(void) +{ + _unk0 = 0; + CurrentCard = CARD_ONE; + Cards[CARD_ONE].port = 0; + Cards[CARD_TWO].port = 1; + + for ( int32 i = 0; i < sizeof(_unkName3); i++ ) + _unkName3[i] = '\0'; + + m_bWantToLoad = false; + _bunk2 = false; + _bunk7 = false; + JustLoadedDontFadeInYet = false; + StillToFadeOut = false; + TimeStartedCountingForFade = 0; + TimeToStayFadedBeforeFadeOut = 1750; + b_FoundRecentSavedGameWantToLoad = false; + + char date[64]; + char time[64]; + char day[8]; + char month[8]; + char year[8]; + char hour[8]; + char minute[8]; + char second[8]; + + strncpy(date, "Oct 7 2001", 62); + strncpy(time, "15:48:32", 62); + + strncpy(month, date, 3); + month[3] = '\0'; + + strncpy(day, &date[4], 2); + day[2] = '\0'; + + strncpy(year, &date[7], 4); + year[4] = '\0'; + + strncpy(hour, time, 2); + hour[2] = '\0'; + + strncpy(minute, &time[3], 2); + minute[2] = '\0'; + + strncpy(second, &time[6], 2); + second[2] = '\0'; + + + #define _CMP(m) strncmp(month, m, sizeof(m)-1) + + if ( !_CMP("Jan") ) CompileDateAndTime.m_nMonth = 1; + else + if ( !_CMP("Feb") ) CompileDateAndTime.m_nMonth = 2; + else + if ( !_CMP("Mar") ) CompileDateAndTime.m_nMonth = 3; + else + if ( !_CMP("Apr") ) CompileDateAndTime.m_nMonth = 4; + else + if ( !_CMP("May") ) CompileDateAndTime.m_nMonth = 5; + else + if ( !_CMP("Jun") ) CompileDateAndTime.m_nMonth = 6; + else + if ( !_CMP("Jul") ) CompileDateAndTime.m_nMonth = 7; + else + if ( !_CMP("Aug") ) CompileDateAndTime.m_nMonth = 8; + else + if ( !_CMP("Oct") ) CompileDateAndTime.m_nMonth = 9; // BUG: oct and sep is swapped here + else + if ( !_CMP("Sep") ) CompileDateAndTime.m_nMonth = 10; + else + if ( !_CMP("Nov") ) CompileDateAndTime.m_nMonth = 11; + else + if ( !_CMP("Dec") ) CompileDateAndTime.m_nMonth = 12; + + #undef _CMP + + CompileDateAndTime.m_nDay = atoi(day); + CompileDateAndTime.m_nYear = atoi(year); + CompileDateAndTime.m_nHour = atoi(hour); + CompileDateAndTime.m_nMinute = atoi(minute); + CompileDateAndTime.m_nSecond = atoi(second); +} + +int32 +CMemoryCard::RestoreForStartLoad(void) +{ + uint8 buf[30]; + + int32 file = OpenMemCardFileForReading(CurrentCard, LoadFileName); + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + ReadFromMemCard(file, buf, sizeof(buf) - 1); + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + uint8 *pBuf = buf + sizeof(uint32) + sizeof(uint32); + ReadDataFromBufferPointer(pBuf, CGame::currLevel); + ReadDataFromBufferPointer(pBuf, TheCamera.GetMatrix().GetPosition().x); + ReadDataFromBufferPointer(pBuf, TheCamera.GetMatrix().GetPosition().y); + ReadDataFromBufferPointer(pBuf, TheCamera.GetMatrix().GetPosition().z); + + if ( CGame::currLevel != LEVEL_INDUSTRIAL ) + CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL); + + if ( CGame::currLevel != LEVEL_COMMERCIAL ) + CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL); + + if ( CGame::currLevel != LEVEL_SUBURBAN ) + CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN); + + CStreaming::RemoveIslandsNotUsed(CGame::currLevel); + CCollision::SortOutCollisionAfterLoad(); + CStreaming::RequestBigBuildings(CGame::currLevel); + CStreaming::LoadAllRequestedModels(false); + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); + CGame::TidyUpMemory(true, false); + + CloseMemCardFile(file); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + return RES_SUCCESS; +} + +int32 +CMemoryCard::LoadSavedGame(void) +{ + CheckSum = 0; + CDate date; + + int32 saveSize = 0; + uint32 size = 0; + + int32 oldLang = CMenuManager::m_PrefsLanguage; + + CPad::ResetCheats(); + + ChangeDirectory(CurrentCard, Cards[CurrentCard].dir); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + int32 file = OpenMemCardFileForReading(CurrentCard, LoadFileName); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + + #define LoadSaveDataBlock()\ + do {\ + ReadFromMemCard(file, &size, sizeof(size)); \ + if ( nError != NO_ERR_SUCCESS ) return RES_FAILED; \ + size = align4bytes(size); \ + ReadFromMemCard(file, work_buff, size); \ + if ( nError != NO_ERR_SUCCESS ) return RES_FAILED; \ + buf = work_buff; \ + } while (0) + + uint8 *buf; + + LoadSaveDataBlock(); + + ReadDataFromBufferPointer(buf, saveSize); + ReadDataFromBufferPointer(buf, CGame::currLevel); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().x); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().y); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().z); + ReadDataFromBufferPointer(buf, CClock::ms_nMillisecondsPerGameMinute); + ReadDataFromBufferPointer(buf, CClock::ms_nLastClockTick); + ReadDataFromBufferPointer(buf, CClock::ms_nGameClockHours); + ReadDataFromBufferPointer(buf, CClock::ms_nGameClockMinutes); + ReadDataFromBufferPointer(buf, CPad::GetPad(0)->Mode); + ReadDataFromBufferPointer(buf, CTimer::m_snTimeInMilliseconds); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeScale); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeStep); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeStepNonClipped); + ReadDataFromBufferPointer(buf, CTimer::m_FrameCounter); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fTimeStep); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fFramesPerUpdate); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fTimeScale); + ReadDataFromBufferPointer(buf, CWeather::OldWeatherType); + ReadDataFromBufferPointer(buf, CWeather::NewWeatherType); + ReadDataFromBufferPointer(buf, CWeather::ForcedWeatherType); + ReadDataFromBufferPointer(buf, CWeather::InterpolationValue); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsMusicVolume); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsSfxVolume); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsControllerConfig); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsUseVibration); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsStereoMono); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsRadioStation); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsBrightness); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsShowTrails); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsShowSubtitles); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsLanguage); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsUseWideScreen); + ReadDataFromBufferPointer(buf, CPad::GetPad(0)->Mode); +#ifdef PS2 + ReadDataFromBufferPointer(buf, BlurOn); +#else + ReadDataFromBufferPointer(buf, CMBlur::BlurOn); +#endif + ReadDataFromBufferPointer(buf, date.m_nSecond); + ReadDataFromBufferPointer(buf, date.m_nMinute); + ReadDataFromBufferPointer(buf, date.m_nHour); + ReadDataFromBufferPointer(buf, date.m_nDay); + ReadDataFromBufferPointer(buf, date.m_nMonth); + ReadDataFromBufferPointer(buf, date.m_nYear); + ReadDataFromBufferPointer(buf, CWeather::WeatherTypeInList); + ReadDataFromBufferPointer(buf, TheCamera.CarZoomIndicator); + ReadDataFromBufferPointer(buf, TheCamera.PedZoomIndicator); + + if ( date > CompileDateAndTime ) + ; + else + if ( date < CompileDateAndTime ) + ; + + #define ReadDataFromBlock(load_func)\ + do {\ + ReadDataFromBufferPointer(buf, size);\ + load_func(buf, size);\ + size = align4bytes(size);\ + buf += size;\ + } while (0) + + + + printf("Loading Scripts \n"); + ReadDataFromBlock(CTheScripts::LoadAllScripts); + + printf("Loading PedPool \n"); + ReadDataFromBlock(CPools::LoadPedPool); + + printf("Loading Garages \n"); + ReadDataFromBlock(CGarages::Load); + + printf("Loading Vehicles \n"); + ReadDataFromBlock(CPools::LoadVehiclePool); + + LoadSaveDataBlock(); + + CProjectileInfo::RemoveAllProjectiles(); + CObject::DeleteAllTempObjects(); + + printf("Loading Objects \n"); + ReadDataFromBlock(CPools::LoadObjectPool); + + printf("Loading Paths \n"); + ReadDataFromBlock(ThePaths.Load); + + printf("Loading Cranes \n"); + ReadDataFromBlock(CCranes::Load); + + LoadSaveDataBlock(); + + printf("Loading Pickups \n"); + ReadDataFromBlock(CPickups::Load); + + printf("Loading Phoneinfo \n"); + ReadDataFromBlock(gPhoneInfo.Load); + + printf("Loading Restart \n"); + ReadDataFromBlock(CRestart::LoadAllRestartPoints); + + printf("Loading Radar Blips \n"); + ReadDataFromBlock(CRadar::LoadAllRadarBlips); + + printf("Loading Zones \n"); + ReadDataFromBlock(CTheZones::LoadAllZones); + + printf("Loading Gang Data \n"); + ReadDataFromBlock(CGangs::LoadAllGangData); + + printf("Loading Car Generators \n"); + ReadDataFromBlock(CTheCarGenerators::LoadAllCarGenerators); + + printf("Loading Particles \n"); + ReadDataFromBlock(CParticleObject::LoadParticle); + + printf("Loading AudioScript Objects \n"); + ReadDataFromBlock(cAudioScriptObject::LoadAllAudioScriptObjects); + + printf("Loading Player Info \n"); + ReadDataFromBlock(CWorld::Players[CWorld::PlayerInFocus].LoadPlayerInfo); + + printf("Loading Stats \n"); + ReadDataFromBlock(CStats::LoadStats); + + printf("Loading Streaming Stuff \n"); + ReadDataFromBlock(CStreaming::MemoryCardLoad); + + printf("Loading PedType Stuff \n"); + ReadDataFromBlock(CPedType::Load); + + #undef LoadSaveDataBlock + #undef ReadDataFromBlock + + FrontEndMenuManager.SetSoundLevelsForMusicMenu(); + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); + + CloseMemCardFile(file); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + if ( oldLang != CMenuManager::m_PrefsLanguage ) + { + TheText.Unload(); + TheText.Load(); + } + + JustLoadedDontFadeInYet = true; + StillToFadeOut = true; + + CTheScripts::Process(); + + printf("Game sucessfully loaded \n"); + + return RES_SUCCESS; +} + +int32 +CMemoryCard::CheckCardInserted(int32 cardID) +{ +#if defined(PS2) + int cmd = sceMcFuncNoCardInfo; + int type = sceMcTypeNoCard; + + CTimer::Stop(); + + while ( sceMcGetInfo(Cards[cardID].port, 0, &type, 0, 0) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( type == sceMcTypePS2 ) + { + if ( result == sceMcResChangedCard || result == sceMcResSucceed ) + { + nError = NO_ERR_SUCCESS; + return nError; + } + else if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + } + + printf("Memory card %i not present\n", cardID); + + nError = ERR_NONE; + return nError; +#else + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::PopulateCardFlags(int32 cardID, bool bSlotFlag, bool bTypeFlag, bool bFreeFlag, bool bFormatFlag) +{ +#if defined(PS2) + int cmd = sceMcFuncNoCardInfo; + int type = sceMcTypeNoCard; + int free = 0; + int format = 0; + + CTimer::Stop(); + + Cards[cardID].type = 0; + Cards[cardID].free = 0; + Cards[cardID].format = 0; + + while ( sceMcGetInfo(Cards[cardID].port, 0, &type, &free, &format) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( type == sceMcTypePS2 ) + { + if ( result == sceMcResChangedCard || result == sceMcResSucceed ) + { + if ( bSlotFlag ) + Cards[cardID].slot = 0; + + //if ( bTypeFlag ) + Cards[cardID].type = type; + + if ( bFreeFlag ) + Cards[cardID].free = free; + + if ( bFormatFlag ) + Cards[cardID].format = format; + + printf("Memory card %i present\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; + } + else if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + } + + printf("Memory card %i not present\n", cardID); + + nError = ERR_NONE; + return nError; +#else + CTimer::Stop(); + + Cards[cardID].type = 0; + Cards[cardID].free = 0; + Cards[cardID].format = 0; + + if ( bSlotFlag ) + Cards[cardID].slot = 0; + + //if ( bTypeFlag ) + Cards[cardID].type = 0; + + if ( bFreeFlag ) + Cards[cardID].free = 1024 * 1024 * 4; + + if ( bFormatFlag ) + Cards[cardID].format = 0; + + printf("Memory card %i present\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::FormatCard(int32 cardID) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoFormat; + + int32 r = CheckCardInserted(cardID); + if ( r == NO_ERR_SUCCESS ) + { + while ( sceMcFormat(Cards[cardID].port, 0) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result < sceMcResSucceed ) + { + printf("Memory card %i could not be formatted\n", cardID); + + nError = ERR_FORMATFAILED; + return nError; + } + + printf("Memory card %i present and formatted\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; + } + + return r; +#else + printf("Memory card %i present and formatted\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::PopulateFileTable(int32 cardID) +{ + CTimer::Stop(); + +#if defined (PS2) + int cmd = sceMcFuncNoGetDir; + + ClearFileTableBuffer(cardID); + + while ( sceMcGetDir(Cards[cardID].port, 0, "*", 0, ARRAY_SIZE(Cards[cardID].table), Cards[cardID].table) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + printf("Memory card %i present PopulateFileTables function successfull \n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory card %i PopulateFileTables function successfull. MemoryCard not Formatted \n", cardID); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory card %i PopulateFileTables function unsuccessfull. Path does not exist \n", cardID); + + nError = ERR_FILETABLENOENTRY; + return nError; + } + + printf("Memory card %i not Present\n", cardID); + + nError = ERR_NONE; + return nError; +#else + ClearFileTableBuffer(cardID); + + char path[512]; + sprintf(path, "%s\\%s\\%s\\*", PCCardsPath, PCCardDir[cardID], CardCurDir[cardID]); + + memset(Cards[cardID].table, 0, sizeof(Cards[cardID].table)); + WIN32_FIND_DATA fd; HANDLE hFind; int32 num = 0; + if ( (hFind = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE ) + { + printf("Memory card %i not Present\n", cardID); + nError = ERR_NONE; + return nError; + } + do + { + SYSTEMTIME st; + FileTimeToSystemTime(&fd.ftCreationTime, &st); + Cards[cardID].table[num]._Create.Sec = st.wSecond;Cards[cardID].table[num]._Create.Min = st.wMinute;Cards[cardID].table[num]._Create.Hour = st.wHour;Cards[cardID].table[num]._Create.Day = st.wDay;Cards[cardID].table[num]._Create.Month = st.wMonth;Cards[cardID].table[num]._Create.Year = st.wYear; + FileTimeToSystemTime(&fd.ftLastWriteTime, &st); + Cards[cardID].table[num]._Modify.Sec = st.wSecond;Cards[cardID].table[num]._Modify.Min = st.wMinute;Cards[cardID].table[num]._Modify.Hour = st.wHour;Cards[cardID].table[num]._Modify.Day = st.wDay;Cards[cardID].table[num]._Modify.Month = st.wMonth;Cards[cardID].table[num]._Modify.Year = st.wYear; + Cards[cardID].table[num].FileSizeByte = fd.nFileSizeLow; + strncpy((char *)Cards[cardID].table[num].EntryName, fd.cFileName, sizeof(Cards[cardID].table[num].EntryName) - 1); + num++; + } while( FindNextFile(hFind, &fd) && num < ARRAY_SIZE(Cards[cardID].table) ); + FindClose(hFind); + + //todo errors + + printf("Memory card %i present PopulateFileTables function successfull \n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::CreateRootDirectory(int32 cardID) +{ + CTimer::Stop(); +#if defined(PS2) + int cmd = sceMcFuncNoMkdir; + + while ( sceMcMkdir(Cards[cardID].port, 0, Cards[cardID].dir) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result == sceMcResSucceed ) + { + printf("Memory card %i present. RootDirectory Created\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory card %i RootDirectory not created card unformatted\n", cardID); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResFullDevice ) + { + printf("Memory card %i RootDirectory not created due to insufficient memory card capacity\n", cardID); + + nError = ERR_DIRFULLDEVICE; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory card %i RootDirectory not created due to problem with pathname\n", cardID); + + nError = ERR_DIRBADENTRY; + return nError; + } + + printf("Memory card %i not present so RootDirectory not created \n", cardID); + + nError = ERR_NONE; + return nError; +#else + char path[512]; + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], Cards[cardID].dir); + _psCreateFolder(path); + + printf("Memory card %i present. RootDirectory Created\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::ChangeDirectory(int32 cardID, char *dir) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoChDir; + + while ( sceMcChdir(Cards[cardID].port, 0, dir, 0) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result == sceMcResSucceed ) + { + printf("Memory Card %i. Changed to the directory %s \n", cardID, dir); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory card %i. Couldn't change to the directory %s. MemoryCard not Formatted \n", cardID, dir); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory card %i Couldn't change to the directory %s. Path does not exist \n", cardID, dir); + + nError = ERR_DIRNOENTRY; + return nError; + } + + printf("Memory card %i not Present. So could not change to directory %s.\n", cardID, dir); + + nError = ERR_NONE; + return nError; +#else + + if ( !strcmp(dir, "/" ) ) + { + strncpy(CardCurDir[cardID], dir, sizeof(CardCurDir[cardID]) - 1); + printf("Memory Card %i. Changed to the directory %s \n", cardID, dir); + nError = NO_ERR_SUCCESS; + return nError; + } + + char path[512]; + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], dir); + + WIN32_FIND_DATA fd; HANDLE hFind; + if ( (hFind = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE ) + { + printf("Memory card %i Couldn't change to the directory %s. Path does not exist \n", cardID, dir); + + nError = ERR_DIRNOENTRY; + return nError; + } + + FindClose(hFind); + + strncpy(CardCurDir[cardID], dir, sizeof(CardCurDir[cardID]) - 1); + printf("Memory Card %i. Changed to the directory %s \n", cardID, dir); + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::CreateIconFiles(int32 cardID, char *icon_one, char *icon_two, char *icon_three) +{ +#if defined(PS2) + sceMcIconSys icon; + static sceVu0IVECTOR bgcolor[4] = { + { 0x80, 0, 0, 0 }, + { 0, 0x80, 0, 0 }, + { 0, 0, 0x80, 0 }, + { 0x80, 0x80, 0x80, 0 }, + }; + static sceVu0FVECTOR lightdir[3] = { + { 0.5, 0.5, 0.5, 0.0 }, + { 0.0,-0.4,-0.1, 0.0 }, + {-0.5,-0.5, 0.5, 0.0 }, + }; + static sceVu0FVECTOR lightcol[3] = { + { 0.48, 0.48, 0.03, 0.00 }, + { 0.50, 0.33, 0.20, 0.00 }, + { 0.14, 0.14, 0.38, 0.00 }, + }; + static sceVu0FVECTOR ambient = { 0.50, 0.50, 0.50, 0.00 }; + char head[8] = "PS2D"; + char title[8] = "GTA3"; + + memset(&icon, 0, sizeof(icon)); + + memcpy(icon.BgColor, bgcolor, sizeof(bgcolor)); + memcpy(icon.LightDir, lightdir, sizeof(lightdir)); + memcpy(icon.LightColor, lightcol, sizeof(lightcol)); + memcpy(icon.Ambient, ambient, sizeof(ambient)); + + icon.OffsLF = 24; + icon.TransRate = 0x60; + + unsigned short *titleName = (unsigned short *)icon.TitleName; + + uint32 titlec = 0; + while ( titlec < strlen(title) ) + { + unsigned short sjis = Ascii2Sjis(title[titlec]); + titleName[titlec] = (sjis << 8) | (sjis >> 8); + titlec++; + } + + titleName[titlec] = L'\0'; + + char icon1[80]; + char icon2[80]; + char icon3[80]; + + strncpy(icon1, icon_one, sizeof(icon1) - 1); + strncpy(icon2, icon_two, sizeof(icon2) - 1); + strncpy(icon3, icon_three, sizeof(icon3) - 1); + + strncpy((char *)icon.FnameView, icon1, sizeof(icon.FnameView) - 1); + strncpy((char *)icon.FnameCopy, icon2, sizeof(icon.FnameCopy) - 1); + strncpy((char *)icon.FnameDel, icon3, sizeof(icon.FnameDel) - 1); + strncpy((char *)icon.Head, head, sizeof(icon.Head)); + + int32 iconFile = CreateMemCardFileReadWrite(Cards[cardID].port, "icon.sys"); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + WritetoMemCard(iconFile, &icon, sizeof(icon)); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + CloseMemCardFile(iconFile); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + if ( LoadIconFiles(Cards[cardID].port, icon_one, icon_two, icon_three) == RES_SUCCESS ) + { + printf("All Icon files Created and loaded. \n"); + return RES_SUCCESS; + } + + printf("Could not load all the icon files \n"); + + return RES_FAILED; +#else + return RES_SUCCESS; +#endif +} + +int32 +CMemoryCard::LoadIconFiles(int32 cardID, char *icon_one, char *icon_two, char *icon_three) +{ +#if defined(PS2) + const uint32 size = 50968; + uint8 *data = new uint8[size]; + + char icon1_path[80]; + char icon2_path[80]; + char icon3_path[80]; + char icon1[32]; + char icon2[32]; + char icon3[32]; + + strncpy(icon1, icon_one, sizeof(icon1) - 1); + strncpy(icon2, icon_two, sizeof(icon2) - 1); + strncpy(icon3, icon_three, sizeof(icon3) - 1); + + int hostlen = strlen(HostFileLocationOfIcons); + + strncpy(icon1_path, HostFileLocationOfIcons, sizeof(icon1_path) - 1); + strncpy(icon2_path, HostFileLocationOfIcons, sizeof(icon2_path) - 1); + strncpy(icon3_path, HostFileLocationOfIcons, sizeof(icon3_path) - 1); + + strncpy(icon1_path+hostlen, icon_one, sizeof(icon1_path) - 1 - hostlen); + strncpy(icon2_path+hostlen, icon_two, sizeof(icon2_path) - 1 - hostlen); + strncpy(icon3_path+hostlen, icon_three, sizeof(icon3_path) - 1 - hostlen); + + // ico1 copy + int32 ico1file = CFileMgr::OpenFile(icon1_path); + CFileMgr::Read(ico1file, (char *)data, size); + CFileMgr::CloseFile(ico1file); + + int32 ico1mc = CreateMemCardFileReadWrite(Cards[cardID].port, icon1); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + WritetoMemCard(ico1mc, data, size); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + CloseMemCardFile(ico1mc); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + // ico2 copy + int32 ico2file = CFileMgr::OpenFile(icon2_path); + CFileMgr::Read(ico2file, (char *)data, size); + CFileMgr::CloseFile(ico2file); + + int32 ico2mc = CreateMemCardFileReadWrite(Cards[cardID].port, icon2); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + WritetoMemCard(ico2mc, data, size); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + CloseMemCardFile(ico2mc); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + // ico3 copy + int32 ico3file = CFileMgr::OpenFile(icon3_path); + CFileMgr::Read(ico3file, (char *)data, size); + CFileMgr::CloseFile(ico3file); + + int32 ico3mc = CreateMemCardFileReadWrite(Cards[cardID].port, icon3); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + WritetoMemCard(ico3mc, data, size); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + CloseMemCardFile(ico3mc); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + delete [] data; + + return RES_SUCCESS; +#else + return RES_SUCCESS; +#endif +} + +int32 +CMemoryCard::CloseMemCardFile(int32 file) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoClose; + + while ( sceMcClose(file) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result == sceMcResSucceed ) + { + printf("File %i closed\n", file); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory Card is Unformatted"); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory Card File Handle %i has not been opened", file); + + nError = ERR_OPENNOENTRY; + return nError; + } + + nError = ERR_NONE; + return nError; +#else + CFileMgr::CloseFile(file); + printf("File %i closed\n", file); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::CreateMemCardFileReadWrite(int32 cardID, char *filename) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoOpen; + + char buff[255]; + + strncpy(buff, filename, sizeof(buff)); + + while ( sceMcOpen(Cards[cardID].port, 0, buff, SCE_RDWR|SCE_CREAT) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + printf("%s File Created for MemoryCard. Its File handle is %i. \n", buff, result); + + nError = NO_ERR_SUCCESS; + return result; + } + + if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResFullDevice ) + { + nError = ERR_FILEFULLDEVICE; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + nError = ERR_FILENOPATHENTRY; + return nError; + } + + if ( result == sceMcResDeniedPermit ) + { + nError = ERR_FILEDENIED; + return nError; + } + + if ( result == sceMcResUpLimitHandle ) + { + nError = ERR_FILEUPLIMIT; + return nError; + } + + printf("File %s not created on memory card.\n", buff); + + return ERR_NONE; +#else + char path[512]; + sprintf(path, "%s\\%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], CardCurDir[cardID], filename); + int32 file = CFileMgr::OpenFile(path, "wb+"); + if (file == 0) + { + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], filename); + file = CFileMgr::OpenFile(path, "wb+"); + } + + if ( file ) + { + printf("%s File Created for MemoryCard. Its File handle is %i. \n", filename, file); + nError = NO_ERR_SUCCESS; + return file; + } + + printf("File %s not created on memory card.\n", path); + + nError = ERR_NONE; + return 0; +#endif +} + +int32 +CMemoryCard::OpenMemCardFileForReading(int32 cardID, char *filename) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoOpen; + + char buff[255]; + + strncpy(buff, filename, sizeof(buff)); + + while ( sceMcOpen(Cards[cardID].port, 0, buff, SCE_RDONLY) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + printf("%s File Created for MemoryCard. Its File handle is %i. \n", buff, result); + + nError = NO_ERR_SUCCESS; + return result; + } + + if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResFullDevice ) + { + nError = ERR_FILEFULLDEVICE; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + nError = ERR_FILENOPATHENTRY; + return nError; + } + + if ( result == sceMcResDeniedPermit ) + { + nError = ERR_FILEDENIED; + return nError; + } + + if ( result == sceMcResUpLimitHandle ) + { + nError = ERR_FILEUPLIMIT; + return nError; + } + + printf("File %s not created on memory card.\n", buff); + nError = ERR_NONE; + return nError; +#else + char path[512]; + sprintf(path, "%s\\%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], CardCurDir[cardID], filename); + int32 file = CFileMgr::OpenFile(path, "rb"); + if (file == 0) + { + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], filename); + file = CFileMgr::OpenFile(path, "rb"); + } + + if ( file ) + { + printf("%s File Created for MemoryCard. Its File handle is %i. \n", filename, file); + nError = NO_ERR_SUCCESS; + return file; + } + + printf("File %s not created on memory card.\n", path); + nError = ERR_NONE; + return 0; +#endif +} + +int32 +CMemoryCard::ReadFromMemCard(int32 file, void *buff, int32 size) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoRead; + + while ( sceMcRead(file, buff, size) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + if ( size >= result ) + { + printf("%i Bytes Read for Filehandle %i \n", result, file); + + nError = NO_ERR_SUCCESS; + return result; + } + } + + if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + nError = ERR_READNOENTRY; + return nError; + } + + if ( result == sceMcResDeniedPermit ) + { + nError = ERR_READDENIED; + return nError; + } + + printf("No Bytes Read for Filehandle %i \n", file); + + nError = ERR_NONE; + return result; +#else + int32 s = CFileMgr::Read(file, (const char *)buff, size); + if ( s == size ) + { + printf("%i Bytes Read for Filehandle %i \n", s, file); + + nError = NO_ERR_SUCCESS; + return s; + } + + printf("No Bytes Read for Filehandle %i \n", file); + + nError = ERR_NONE; + return s; +#endif +} + +int32 +CMemoryCard::DeleteMemoryCardFile(int32 cardID, char *filename) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoDelete; + + while ( sceMcDelete(Cards[cardID].port, 0, filename) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result == sceMcResSucceed ) + { + printf("Memory Card %i, %s NO_ERR_SUCCESSfully deleted", cardID, filename); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory Card %i, %s not deleted as memory Card is unformatted", cardID, filename); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory Card %i, %s attempt made to delete non existing file", cardID, filename); + + nError = ERR_DELETENOENTRY; + return nError; + } + + if ( result == sceMcResDeniedPermit ) + { + printf("Memory Card %i, %s not deleted as file is in use or write protected", cardID, filename); + + nError = ERR_DELETEDENIED; + return nError; + } + + if ( result == sceMcResNotEmpty ) + { + printf("Memory Card %i, %s not deleted. Entries remain in subdirectory", cardID, filename); + + nError = ERR_DELETEFAILED; + return nError; + } + + nError = ERR_NONE; + return nError; +#else + char path[512]; + sprintf(path, "%s\\%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], CardCurDir[cardID], filename); + int32 file = CFileMgr::OpenFile(path, "rb"); + if (file == 0) + { + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], filename); + file = CFileMgr::OpenFile(path, "rb"); + } + + if ( file ) + { + CFileMgr::CloseFile(file); + + DeleteFile(path); + + printf("Memory Card %i, %s NO_ERR_SUCCESSfully deleted", cardID, filename); + + nError = NO_ERR_SUCCESS; + return nError; + } + + printf("Memory Card %i, %s attempt made to delete non existing file", cardID, filename); + nError = ERR_DELETENOENTRY; + + //nError = ERR_NONE; + return nError; + +#endif +} + +void +CMemoryCard::PopulateErrorMessage() +{ + switch ( nError ) + { + case ERR_WRITEFULLDEVICE: + case ERR_DIRFULLDEVICE: + pErrorMsg = TheText.Get("SLONDR"); break; // Insufficient space to save. Please insert a Memory Card (PS2) with at least 500KB of free space available into MEMORY CARD slot 1. + case ERR_FORMATFAILED: + pErrorMsg = TheText.Get("SLONFM"); break; // Error formatting Memory Card (PS2) in MEMORY CARD slot 1. + case ERR_SAVEFAILED: + pErrorMsg = TheText.Get("SLNSP"); break; // Insufficient space to save. Please insert a Memory Card (PS2) with at least 200KB of free space available into MEMORY CARD slot 1. + case ERR_DELETEDENIED: + case ERR_NOFORMAT: + pErrorMsg = TheText.Get("SLONNF"); break; // Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. + case ERR_NONE: + pErrorMsg = TheText.Get("SLONNO"); break; // No Memory Card (PS2) in MEMORY CARD slot 1. + } +} + +int32 +CMemoryCard::WritetoMemCard(int32 file, void *buff, int32 size) +{ +#if defined(PS2) + int cmd = sceMcFuncNoWrite; + int result = sceMcResSucceed; + int result1 = sceMcResSucceed; + + CTimer::Stop(); + + while ( sceMcWrite(file, buff, size) != sceMcResSucceed ) + ; + + sceMcSync(0, &cmd, &result); + + if ( result == sceMcResNoFormat ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResFullDevice ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_WRITEFULLDEVICE; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_WRITENOENTRY; + return nError; + } + + if ( result == sceMcResDeniedPermit ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_WRITEDENIED; + return nError; + } + + if ( result == sceMcResFailReplace ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_WRITEFAILED; + return nError; + } + + if ( result <= -10 ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_NONE; + return nError; + } + + cmd = sceMcFuncNoFlush; + + while ( sceMcFlush(file) != sceMcResSucceed ) + ; + + sceMcSync(0, &cmd, &result1); + + if ( result1 == sceMcResNoFormat ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result1 == sceMcResNoEntry ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_FLUSHNOENTRY; + return nError; + } + + if ( result1 <= -10 ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_NONE; + return nError; + } + + if ( result > 0 && result1 == sceMcResSucceed ) + { + printf("%i Bytes written for Filehandle %i \n", result, file); + } + else if ( result == sceMcResSucceed && result1 == sceMcResSucceed ) + { + printf("Filehandle %i was flushed\n", file); + } + + nError = NO_ERR_SUCCESS; + return nError; +#else + CTimer::Stop(); + + int32 s = CFileMgr::Write(file, (const char *)buff, size); + if ( s == size ) + { + printf("%i Bytes written for Filehandle %i \n", s, file); + nError = NO_ERR_SUCCESS; + return s; + } + + nError = ERR_NONE; + return s; +#endif +} + +static inline void +MakeSpaceForSizeInBufferPointer(uint8 *&presize, uint8 *&buf, uint8 *&postsize) +{ + presize = buf; + buf += sizeof(uint32); + postsize = buf; +} + +static inline void +CopySizeAndPreparePointer(uint8 *&buf, uint8 *&postbuf, uint8 *&postbuf2, uint32 &unused, uint32 &size) +{ + memcpy(buf, &size, sizeof(size)); + size = align4bytes(size); + postbuf2 += size; + postbuf = postbuf2; +} + +bool +CMemoryCard::SaveGame(void) +{ + uint32 saveSize = 0; + uint32 totalSize = 0; + + CurrentCard = CARD_ONE; + + CheckSum = 0; + + CGameLogic::PassTime(360); + CPlayerPed *ped = FindPlayerPed(); + ped->m_fCurrentStamina = ped->m_fMaxStamina; + CGame::TidyUpMemory(true, false); + + saveSize = SAVE_FILE_SIZE; + int32 minfree = 198; + + PopulateCardFlags(CurrentCard, false, false, true, false); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + minfree += GetClusterAmountForFileCreation(CurrentCard); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( Cards[CurrentCard].free < 200 ) + { + CTimer::Update(); + uint32 startTime = CTimer::GetTimeInMillisecondsPauseMode(); + + while ( CTimer::GetTimeInMillisecondsPauseMode()-startTime < 1250 ) + { + for ( int32 i = 0; i < 1000; i++ ) + powf(3.33f, 3.444f); + + CTimer::Update(); + } + + nError = ERR_SAVEFAILED; + return false; + } + + if ( Cards[CurrentCard].free < minfree ) + { + CTimer::Update(); + uint32 startTime = CTimer::GetTimeInMillisecondsPauseMode(); + + while ( CTimer::GetTimeInMillisecondsPauseMode()-startTime < 1250 ) + { + for ( int32 i = 0; i < 1000; i++ ) + powf(3.33f, 3.444f); + + CTimer::Update(); + } + + nError = ERR_SAVEFAILED; + return false; + } + + uint32 size; + uint8 *buf = work_buff; + uint32 reserved = 0; + + int32 file = CreateMemCardFileReadWrite(CurrentCard, ValidSaveName); + + if ( nError != NO_ERR_SUCCESS ) + { + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(SaveFileNameJustSaved) - 1); + return false; + } + + WriteDataToBufferPointer(buf, saveSize); + WriteDataToBufferPointer(buf, CGame::currLevel); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().x); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().y); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().z); + WriteDataToBufferPointer(buf, CClock::ms_nMillisecondsPerGameMinute); + WriteDataToBufferPointer(buf, CClock::ms_nLastClockTick); + WriteDataToBufferPointer(buf, CClock::ms_nGameClockHours); + WriteDataToBufferPointer(buf, CClock::ms_nGameClockMinutes); + WriteDataToBufferPointer(buf, CPad::GetPad(0)->Mode); + WriteDataToBufferPointer(buf, CTimer::m_snTimeInMilliseconds); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeScale); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeStep); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeStepNonClipped); + WriteDataToBufferPointer(buf, CTimer::m_FrameCounter); + WriteDataToBufferPointer(buf, CTimeStep::ms_fTimeStep); + WriteDataToBufferPointer(buf, CTimeStep::ms_fFramesPerUpdate); + WriteDataToBufferPointer(buf, CTimeStep::ms_fTimeScale); + WriteDataToBufferPointer(buf, CWeather::OldWeatherType); + WriteDataToBufferPointer(buf, CWeather::NewWeatherType); + WriteDataToBufferPointer(buf, CWeather::ForcedWeatherType); + WriteDataToBufferPointer(buf, CWeather::InterpolationValue); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsMusicVolume); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsSfxVolume); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsControllerConfig); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsUseVibration); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsStereoMono); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsRadioStation); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsBrightness); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsShowTrails); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsShowSubtitles); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsLanguage); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsUseWideScreen); + WriteDataToBufferPointer(buf, CPad::GetPad(0)->Mode); +#ifdef PS2 + WriteDataToBufferPointer(buf, BlurOn); +#else + WriteDataToBufferPointer(buf, CMBlur::BlurOn); +#endif + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nSecond); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nMinute); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nHour); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nDay); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nMonth); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nYear); + WriteDataToBufferPointer(buf, CWeather::WeatherTypeInList); + WriteDataToBufferPointer(buf, TheCamera.CarZoomIndicator); + WriteDataToBufferPointer(buf, TheCamera.PedZoomIndicator); + + if ( nError != NO_ERR_SUCCESS ) + { + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(SaveFileNameJustSaved) - 1); + return false; + } + + uint8 *presize; + uint8 *postsize; + + #define WriteSaveDataBlock(save_func)\ + do {\ + MakeSpaceForSizeInBufferPointer(presize, buf, postsize);\ + save_func(buf, &size);\ + CopySizeAndPreparePointer(presize, buf, postsize, reserved, size);\ + } while (0) + + WriteSaveDataBlock(CTheScripts::SaveAllScripts); + printf("Script Save Size %d, \n", size); + + WriteSaveDataBlock(CPools::SavePedPool); + printf("PedPool Save Size %d, \n", size); + + WriteSaveDataBlock(CGarages::Save); + printf("Garage Save Size %d, \n", size); + + WriteSaveDataBlock(CPools::SaveVehiclePool); + printf("Vehicle Save Size %d, \n", size); + + DoClassSaveRoutine(file, work_buff, buf - work_buff); + totalSize += buf - work_buff; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + buf = work_buff; + reserved = 0; + + WriteSaveDataBlock(CPools::SaveObjectPool); + printf("Object Save Size %d, \n", size); + + WriteSaveDataBlock(ThePaths.Save); + printf("The Paths Save Size %d, \n", size); + + WriteSaveDataBlock(CCranes::Save); + printf("Cranes Save Size %d, \n", size); + + DoClassSaveRoutine(file, work_buff, buf - work_buff); + totalSize += buf - work_buff; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + buf = work_buff; + reserved = 0; + + WriteSaveDataBlock(CPickups::Save); + printf("Pick Ups Save Size %d, \n", size); + + WriteSaveDataBlock(gPhoneInfo.Save); + printf("Phones Save Size %d, \n", size); + + WriteSaveDataBlock(CRestart::SaveAllRestartPoints); + printf("RestartPoints Save Size %d, \n", size); + + WriteSaveDataBlock(CRadar::SaveAllRadarBlips); + printf("Radar Save Size %d, \n", size); + + WriteSaveDataBlock(CTheZones::SaveAllZones); + printf("Save Size %d, \n", size); + + WriteSaveDataBlock(CGangs::SaveAllGangData); + printf("Gangs Save Size %d, \n", size); + + WriteSaveDataBlock(CTheCarGenerators::SaveAllCarGenerators); + printf("Car Gens Save Size %d, \n", size); + + WriteSaveDataBlock(CParticleObject::SaveParticle); + printf("Particles Save Size %d, \n", size); + + WriteSaveDataBlock(cAudioScriptObject::SaveAllAudioScriptObjects); + printf("Audio Script Save Size %d, \n", size); + + WriteSaveDataBlock(CWorld::Players[CWorld::PlayerInFocus].SavePlayerInfo); + printf("Player Info Save Size %d, \n", size); + + WriteSaveDataBlock(CStats::SaveStats); + printf("Stats Save Size %d, \n", size); + + WriteSaveDataBlock(CStreaming::MemoryCardSave); + printf("Streaming Save Size %d, \n", size); + + WriteSaveDataBlock(CPedType::Save); + printf("PedType Save Size %d, \n", size); + + DoClassSaveRoutine(file, work_buff, buf - work_buff); + totalSize += buf - work_buff; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + buf = work_buff; + reserved = 0; + + for (int32 i = 0; i < 3; i++) + { + size = align4bytes(saveSize - totalSize - 4); + if (size > sizeof(work_buff)) + size = sizeof(work_buff); + if (size > 4) { + DoClassSaveRoutine(file, work_buff, size); + totalSize += size; + } + } + + WritetoMemCard(file, &CheckSum, sizeof(CheckSum)); + + CloseMemCardFile(file); + + #undef WriteSaveDataBlock + + if ( nError != NO_ERR_SUCCESS ) + { + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(SaveFileNameJustSaved) - 1); + DoHackRoundSTUPIDSonyDateTimeStuff(CARD_ONE, ValidSaveName); + return false; + } + + DoHackRoundSTUPIDSonyDateTimeStuff(CARD_ONE, ValidSaveName); + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(SaveFileNameJustSaved) - 1); + return true; +} + +bool +CMemoryCard::DoHackRoundSTUPIDSonyDateTimeStuff(int32 port, char *filename) +{ +#if defined(PS2) + int cmd = sceMcFuncNoFileInfo; + int result = sceMcResSucceed; + + sceCdCLOCK rtc; + sceCdReadClock(&rtc); + + sceScfGetLocalTimefromRTC(&rtc); + + #define ROUNDHACK(a) ( ((a) & 15) + ( ( ( ((a) >> 4) << 2 ) + ((a) >> 4) ) << 1 ) ) + + sceMcTblGetDir info; + + info._Create.Sec = ROUNDHACK(rtc.second); + info._Create.Min = ROUNDHACK(rtc.minute); + info._Create.Hour = ROUNDHACK(rtc.hour); + info._Create.Day = ROUNDHACK(rtc.day); + info._Create.Month = ROUNDHACK(rtc.month); + info._Create.Year = ROUNDHACK(rtc.year) + 2000; + + #undef ROUNDHACK + + while ( sceMcSetFileInfo(port, 0, filename, (char *)&info, sceMcFileInfoCreate) != sceMcResSucceed ) + ; + + sceMcSync(0, &cmd, &result); + + return sceMcResSucceed >= result; +#else + return true; +#endif +} + +int32 +CMemoryCard::LookForRootDirectory(int32 cardID) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoGetDir; + + while ( sceMcGetDir(Cards[cardID].port, 0, Cards[cardID].dir, 0, ARRAY_SIZE(Cards[cardID].table), Cards[cardID].table) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result == 0 ) + { + nError = NO_ERR_SUCCESS; + return ERR_NOROOTDIR; + } + + if ( result > sceMcResSucceed ) + { + printf("Memory card %i present PopulateFileTables function NO_ERR_SUCCESSfull \n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory card %i PopulateFileTables function unNO_ERR_SUCCESSfull. MemoryCard not Formatted \n", cardID); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory card %i PopulateFileTables function unNO_ERR_SUCCESSfull. Path does not exist \n", cardID); + + nError = ERR_FILETABLENOENTRY; + return nError; + } + + printf("Memory card %i not Present\n", cardID); + + nError = ERR_NONE; + return nError; +#else + char path[512]; + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], Cards[cardID].dir); + + memset(Cards[cardID].table, 0, sizeof(Cards[cardID].table)); + WIN32_FIND_DATA fd; HANDLE hFind; int32 num = 0; + if ( (hFind = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE ) + { + nError = NO_ERR_SUCCESS; + return ERR_NOROOTDIR; + } + do + { + SYSTEMTIME st; + FileTimeToSystemTime(&fd.ftCreationTime, &st); + Cards[cardID].table[num]._Create.Sec = st.wSecond;Cards[cardID].table[num]._Create.Min = st.wMinute;Cards[cardID].table[num]._Create.Hour = st.wHour;Cards[cardID].table[num]._Create.Day = st.wDay;Cards[cardID].table[num]._Create.Month = st.wMonth;Cards[cardID].table[num]._Create.Year = st.wYear; + FileTimeToSystemTime(&fd.ftLastWriteTime, &st); + Cards[cardID].table[num]._Modify.Sec = st.wSecond;Cards[cardID].table[num]._Modify.Min = st.wMinute;Cards[cardID].table[num]._Modify.Hour = st.wHour;Cards[cardID].table[num]._Modify.Day = st.wDay;Cards[cardID].table[num]._Modify.Month = st.wMonth;Cards[cardID].table[num]._Modify.Year = st.wYear; + Cards[cardID].table[num].FileSizeByte = fd.nFileSizeLow; + strncpy((char *)Cards[cardID].table[num].EntryName, fd.cFileName, sizeof(Cards[cardID].table[num].EntryName) - 1); + num++; + } while( FindNextFile(hFind, &fd) && num < ARRAY_SIZE(Cards[cardID].table) ); + FindClose(hFind); + + if ( num == 0 ) + { + nError = NO_ERR_SUCCESS; + return ERR_NOROOTDIR; + } + + //todo errors + + printf("Memory card %i present PopulateFileTables function NO_ERR_SUCCESSfull \n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::FillFirstFileWithGuff(int32 cardID) +{ + CTimer::Stop(); + + char buff[80]; + strncpy(buff, Cards[cardID].dir+1, sizeof(buff) - 1); + + int32 file = CreateMemCardFileReadWrite(Cards[cardID].port, buff); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + const int32 kBlockSize = GUFF_FILE_SIZE / 3; + + work_buff[kBlockSize-1] = 5; + WritetoMemCard(file, work_buff, kBlockSize); + WritetoMemCard(file, work_buff, kBlockSize); + WritetoMemCard(file, work_buff, kBlockSize); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + CloseMemCardFile(file); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + return RES_SUCCESS; +} + +bool +CMemoryCard::FindMostRecentFileName(int32 cardID, char *filename) +{ + CDate date1, date2; + + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoGetDir; + + ClearFileTableBuffer(cardID); + + while ( sceMcGetDir(Cards[cardID].port, 0, "*", 0, ARRAY_SIZE(Cards[cardID].table), Cards[cardID].table) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + printf("Memory card %i present PopulateFileTables function NO_ERR_SUCCESSfull \n", cardID); + nError = NO_ERR_SUCCESS; + + for ( int32 entry = 7; entry < ARRAY_SIZE(Cards[CARD_ONE].table); entry++ ) + { + bool found = false; + + if ( Cards[CARD_ONE].table[entry]._Modify.Sec != 0 + || Cards[CARD_ONE].table[entry]._Modify.Min != 0 + || Cards[CARD_ONE].table[entry]._Modify.Hour != 0 + || Cards[CARD_ONE].table[entry]._Modify.Day != 0 + || Cards[CARD_ONE].table[entry]._Modify.Month != 0 + || Cards[CARD_ONE].table[entry]._Modify.Year != 0 ) + { + date1.m_nSecond = Cards[CARD_ONE].table[entry]._Modify.Sec; + date1.m_nMinute = Cards[CARD_ONE].table[entry]._Modify.Min; + date1.m_nHour = Cards[CARD_ONE].table[entry]._Modify.Hour; + date1.m_nDay = Cards[CARD_ONE].table[entry]._Modify.Day; + date1.m_nMonth = Cards[CARD_ONE].table[entry]._Modify.Month; + date1.m_nYear = Cards[CARD_ONE].table[entry]._Modify.Year; + + if ( Cards[CARD_ONE].table[entry].FileSizeByte != 0 + && Cards[CARD_ONE].table[entry].AttrFile & sceMcFileAttrClosed + && Cards[CARD_ONE].table[entry].FileSizeByte >= SAVE_FILE_SIZE ) + { + found = true; + } + } + else + if ( Cards[CARD_ONE].table[entry]._Create.Sec != 0 + || Cards[CARD_ONE].table[entry]._Create.Min != 0 + || Cards[CARD_ONE].table[entry]._Create.Hour != 0 + || Cards[CARD_ONE].table[entry]._Create.Day != 0 + || Cards[CARD_ONE].table[entry]._Create.Month != 0 + || Cards[CARD_ONE].table[entry]._Create.Year != 0 ) + { + date1.m_nSecond = Cards[CARD_ONE].table[entry]._Create.Sec; + date1.m_nMinute = Cards[CARD_ONE].table[entry]._Create.Min; + date1.m_nHour = Cards[CARD_ONE].table[entry]._Create.Hour; + date1.m_nDay = Cards[CARD_ONE].table[entry]._Create.Day; + date1.m_nMonth = Cards[CARD_ONE].table[entry]._Create.Month; + date1.m_nYear = Cards[CARD_ONE].table[entry]._Create.Year; + + if ( Cards[CARD_ONE].table[entry].FileSizeByte != 0 + && Cards[CARD_ONE].table[entry].AttrFile & sceMcFileAttrClosed + && Cards[CARD_ONE].table[entry].FileSizeByte >= SAVE_FILE_SIZE ) + { + found = true; + } + } + + if ( found ) + { + int32 d; + if ( date1 > date2 ) d = 1; + else if ( date1 < date2 ) d = 2; + else d = 0; + + if ( d == 1 ) + { + char *entryname = (char *)Cards[CARD_ONE].table[entry].EntryName; + + date2 = date1; + strncpy(filename, entryname, 28); + } + else + { + int32 d; + if ( date1 > date2 ) d = 1; + else if ( date1 < date2 ) d = 2; + else d = 0; + + if ( d == 0 ) + { + char *entryname = (char *)Cards[CARD_ONE].table[entry].EntryName; + date2 = date1; + strncpy(filename, entryname, 28); + } + } + } + } + + if ( date2.m_nSecond != 0 + || date2.m_nMinute != 0 + || date2.m_nHour != 0 + || date2.m_nDay != 0 + || date2.m_nMonth != 0 + || date2.m_nYear != 0 ) + { + return true; + } + + return false; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory card %i PopulateFileTables function unNO_ERR_SUCCESSfull. MemoryCard not Formatted \n", cardID); + nError = ERR_NOFORMAT; + return false; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory card %i PopulateFileTables function unNO_ERR_SUCCESSfull. Path does not exist \n", cardID); + nError = ERR_FILETABLENOENTRY; + return false; + } + + printf("Memory card %i not Present\n", cardID); + nError = ERR_NONE; + return false; +#else + ClearFileTableBuffer(cardID); + + char path[512]; + sprintf(path, "%s\\%s\\%s\\*", PCCardsPath, PCCardDir[cardID], CardCurDir[cardID]); + + memset(Cards[cardID].table, 0, sizeof(Cards[cardID].table)); + WIN32_FIND_DATA fd; HANDLE hFind; int32 num = 0; + if ( (hFind = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE ) + { + printf("Memory card %i not Present\n", cardID); + nError = ERR_NONE; + return nError; + } + do + { + SYSTEMTIME st; + FileTimeToSystemTime(&fd.ftCreationTime, &st); + Cards[cardID].table[num]._Create.Sec = st.wSecond;Cards[cardID].table[num]._Create.Min = st.wMinute;Cards[cardID].table[num]._Create.Hour = st.wHour;Cards[cardID].table[num]._Create.Day = st.wDay;Cards[cardID].table[num]._Create.Month = st.wMonth;Cards[cardID].table[num]._Create.Year = st.wYear; + FileTimeToSystemTime(&fd.ftLastWriteTime, &st); + Cards[cardID].table[num]._Modify.Sec = st.wSecond;Cards[cardID].table[num]._Modify.Min = st.wMinute;Cards[cardID].table[num]._Modify.Hour = st.wHour;Cards[cardID].table[num]._Modify.Day = st.wDay;Cards[cardID].table[num]._Modify.Month = st.wMonth;Cards[cardID].table[num]._Modify.Year = st.wYear; + Cards[cardID].table[num].FileSizeByte = fd.nFileSizeLow; + strncpy((char *)Cards[cardID].table[num].EntryName, fd.cFileName, sizeof(Cards[cardID].table[num].EntryName) - 1); + num++; + } while( FindNextFile(hFind, &fd) && num < ARRAY_SIZE(Cards[cardID].table) ); + FindClose(hFind); + + if ( num > 0 ) + { + printf("Memory card %i present PopulateFileTables function NO_ERR_SUCCESSfull \n", cardID); + nError = NO_ERR_SUCCESS; + + for ( int32 entry = 0; entry < ARRAY_SIZE(Cards[CARD_ONE].table); entry++ ) + { + bool found = false; + + if ( Cards[CARD_ONE].table[entry]._Modify.Sec != 0 + || Cards[CARD_ONE].table[entry]._Modify.Min != 0 + || Cards[CARD_ONE].table[entry]._Modify.Hour != 0 + || Cards[CARD_ONE].table[entry]._Modify.Day != 0 + || Cards[CARD_ONE].table[entry]._Modify.Month != 0 + || Cards[CARD_ONE].table[entry]._Modify.Year != 0 ) + { + date1.m_nSecond = Cards[CARD_ONE].table[entry]._Modify.Sec; + date1.m_nMinute = Cards[CARD_ONE].table[entry]._Modify.Min; + date1.m_nHour = Cards[CARD_ONE].table[entry]._Modify.Hour; + date1.m_nDay = Cards[CARD_ONE].table[entry]._Modify.Day; + date1.m_nMonth = Cards[CARD_ONE].table[entry]._Modify.Month; + date1.m_nYear = Cards[CARD_ONE].table[entry]._Modify.Year; + + if ( Cards[CARD_ONE].table[entry].FileSizeByte != 0 + && Cards[CARD_ONE].table[entry].FileSizeByte >= SAVE_FILE_SIZE ) + { + found = true; + } + } + else + if ( Cards[CARD_ONE].table[entry]._Create.Sec != 0 + || Cards[CARD_ONE].table[entry]._Create.Min != 0 + || Cards[CARD_ONE].table[entry]._Create.Hour != 0 + || Cards[CARD_ONE].table[entry]._Create.Day != 0 + || Cards[CARD_ONE].table[entry]._Create.Month != 0 + || Cards[CARD_ONE].table[entry]._Create.Year != 0 ) + { + date1.m_nSecond = Cards[CARD_ONE].table[entry]._Create.Sec; + date1.m_nMinute = Cards[CARD_ONE].table[entry]._Create.Min; + date1.m_nHour = Cards[CARD_ONE].table[entry]._Create.Hour; + date1.m_nDay = Cards[CARD_ONE].table[entry]._Create.Day; + date1.m_nMonth = Cards[CARD_ONE].table[entry]._Create.Month; + date1.m_nYear = Cards[CARD_ONE].table[entry]._Create.Year; + + if ( Cards[CARD_ONE].table[entry].FileSizeByte != 0 + && Cards[CARD_ONE].table[entry].FileSizeByte >= SAVE_FILE_SIZE ) + { + found = true; + } + } + + if ( found ) + { + int32 d; + if ( date1 > date2 ) d = 1; + else if ( date1 < date2 ) d = 2; + else d = 0; + + if ( d == 1 ) + { + char *entryname = (char *)Cards[CARD_ONE].table[entry].EntryName; + + date2 = date1; + strncpy(filename, entryname, 28); + } + else + { + int32 d; + if ( date1 > date2 ) d = 1; + else if ( date1 < date2 ) d = 2; + else d = 0; + + if ( d == 0 ) + { + char *entryname = (char *)Cards[CARD_ONE].table[entry].EntryName; + date2 = date1; + strncpy(filename, entryname, 28); + } + } + } + } + + if ( date2.m_nSecond != 0 + || date2.m_nMinute != 0 + || date2.m_nHour != 0 + || date2.m_nDay != 0 + || date2.m_nMonth != 0 + || date2.m_nYear != 0 ) + { + return true; + } + + return false; + } + + //todo errors + + nError = ERR_NONE; + return false; +#endif +} + +void +CMemoryCard::ClearFileTableBuffer(int32 cardID) +{ + for ( int32 i = 0; i < ARRAY_SIZE(Cards[cardID].table); i++ ) + { + Cards[cardID].table[i].FileSizeByte = 0; + strncpy((char *)Cards[cardID].table[i].EntryName, " ", sizeof(Cards[cardID].table[i].EntryName) - 1); + } +} + +int32 +CMemoryCard::GetClusterAmountForFileCreation(int32 port) +{ +#if defined(PS2) + int cmd = sceMcFuncNoEntSpace; + int result = 0; + + CTimer::Stop(); + + while ( sceMcGetEntSpace(port, 0, TheGameRootDirectory) != sceMcResSucceed ) + ; + + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + nError = NO_ERR_SUCCESS; + return result; + } + + if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + + nError = ERR_NONE; + return nError; +#else + CTimer::Stop(); + nError = NO_ERR_SUCCESS; + return 0; +#endif +} + +bool +CMemoryCard::DeleteEverythingInGameRoot(int32 cardID) +{ + CTimer::Stop(); + + ChangeDirectory(CurrentCard, Cards[CurrentCard].dir); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + PopulateFileTable(cardID); + + for ( int32 i = ARRAY_SIZE(Cards[cardID].table) - 1; i >= 0; i--) + DeleteMemoryCardFile(cardID, (char *)Cards[cardID].table[i].EntryName); + + ChangeDirectory(CurrentCard, "/"); + + DeleteMemoryCardFile(cardID, Cards[CurrentCard].dir); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + return true; +} + +int32 +CMemoryCard::CheckDataNotCorrupt(char *filename) +{ + CheckSum = 0; + + int32 lang = 0; + int32 level = 0; + + LastBlockSize = 0; + + char buf[100*4]; + + for ( int32 i = 0; i < sizeof(buf); i++ ) + buf[i] = '\0'; + + strncpy(buf, Cards[CurrentCard].dir, sizeof(buf) - 1); + strncat(buf, "/", sizeof(buf) - 1); + strcat (buf, filename); + + ChangeDirectory(CurrentCard, Cards[CurrentCard].dir); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + int32 file = OpenMemCardFileForReading(CurrentCard, buf); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + int32 bytes_processed = 0; + int32 blocknum = 0; + int32 lastblocksize; + + while ( SAVE_FILE_SIZE - sizeof(int32) > bytes_processed && blocknum < 8 ) + { + int32 size; + + ReadFromMemCard(file, &size, sizeof(size)); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + lastblocksize = ReadFromMemCard(file, work_buff, align4bytes(size)); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + uint8 sizebuff[4]; + memcpy(sizebuff, &size, sizeof(size)); + + for ( int32 i = 0; i < ARRAY_SIZE(sizebuff); i++ ) + CheckSum += sizebuff[i]; + + uint8 *pWork_buf = work_buff; + for ( int32 i = 0; i < lastblocksize; i++ ) + { + CheckSum += *pWork_buf++; + bytes_processed++; + } + + if ( blocknum == 0 ) + { + uint8 *pBuf = work_buff + sizeof(uint32); + ReadDataFromBufferPointer(pBuf, level); + pBuf += sizeof(uint32) * 29; + ReadDataFromBufferPointer(pBuf, lang); + } + + blocknum++; + } + + int32 checkSum; + ReadFromMemCard(file, &checkSum, sizeof(checkSum)); + CloseMemCardFile(file); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + if ( CheckSum == checkSum ) + { + m_LevelToLoad = level; + m_LanguageToLoad = lang; + LastBlockSize = lastblocksize; + + return RES_SUCCESS; + } + + nError = ERR_DATACORRUPTED; + return RES_FAILED; +} + +int32 +CMemoryCard::GetLanguageToLoad(void) +{ + return m_LanguageToLoad; +} + +int32 +CMemoryCard::GetLevelToLoad(void) +{ + return m_LevelToLoad; +} + +bool +CMemoryCard::CreateGameDirectoryFromScratch(int32 cardID) +{ + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + + int32 err = RES_SUCCESS; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( Cards[CurrentCard].free < 500 ) + { + CTimer::Update(); + uint32 startTime = CTimer::GetTimeInMillisecondsPauseMode(); + + while ( CTimer::GetTimeInMillisecondsPauseMode()-startTime < 1250 ) + { + for ( int32 i = 0; i < 1000; i++ ) + powf(3.33f, 3.444f); + + CTimer::Update(); + } + + nError = ERR_DIRFULLDEVICE; + return false; + } + + TheMemoryCard.ChangeDirectory(CARD_ONE, "/"); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + int32 r = LookForRootDirectory(CARD_ONE); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( r == ERR_NOROOTDIR ) + { + CreateRootDirectory(CARD_ONE); + + if ( nError != NO_ERR_SUCCESS ) + DeleteEverythingInGameRoot(CARD_ONE); + } + + ChangeDirectory(CARD_ONE, Cards[CARD_ONE].dir); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + PopulateFileTable(CARD_ONE); + + if ( nError != NO_ERR_SUCCESS ) + return false; + +#if defined(PS2) + bool entryExist; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(CARD_ONE) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[CARD_ONE].table); i++ ) + { + if ( !strcmp("icon.sys", (char *)TheMemoryCard.Cards[CARD_ONE].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + err = RES_FAILED; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(CARD_ONE) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[CARD_ONE].table); i++ ) + { + if ( !strcmp(icon_one, (char *)TheMemoryCard.Cards[CARD_ONE].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + err = RES_FAILED; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(CARD_ONE) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[CARD_ONE].table); i++ ) + { + if ( !strcmp(icon_two, (char *)TheMemoryCard.Cards[CARD_ONE].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + err = RES_FAILED; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(CARD_ONE) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[CARD_ONE].table); i++ ) + { + if ( !strcmp(icon_three, (char *)TheMemoryCard.Cards[CARD_ONE].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + err = RES_FAILED; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( err != RES_SUCCESS ) + { + int32 icon = CreateIconFiles(CARD_ONE, icon_one, icon_two, icon_three); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( icon != RES_SUCCESS ) + DeleteEverythingInGameRoot(CARD_ONE); + } +#endif + + int32 guff = FillFirstFileWithGuff(CARD_ONE); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( guff == RES_SUCCESS ) + { + printf("Game Default directory present"); + return true; + } + + DeleteEverythingInGameRoot(CARD_ONE); + + return false; +} + +bool +CMemoryCard::CheckGameDirectoryThere(int32 cardID) +{ + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + + if ( TheMemoryCard.nError != NO_ERR_SUCCESS ) + return false; + + TheMemoryCard.ChangeDirectory(cardID, Cards[CARD_ONE].dir); + + if ( TheMemoryCard.nError != NO_ERR_SUCCESS ) + return false; + + PopulateFileTable(cardID); + + if ( TheMemoryCard.nError != NO_ERR_SUCCESS ) + return false; + + + bool entryExist; + +#if defined(PS2) + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(cardID) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[cardID].table); i++ ) + { + if ( !strcmp("icon.sys", (char *)TheMemoryCard.Cards[cardID].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(cardID) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[cardID].table); i++ ) + { + if ( !strcmp(icon_one, (char *)TheMemoryCard.Cards[cardID].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(cardID) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[cardID].table); i++ ) + { + if ( !strcmp(icon_two, (char *)TheMemoryCard.Cards[cardID].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(cardID) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[cardID].table); i++ ) + { + if ( !strcmp(icon_three, (char *)TheMemoryCard.Cards[cardID].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + return false; +#endif + + char buff[80]; + + strncpy(buff, Cards[cardID].dir+1, sizeof(buff) - 1); + + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(cardID) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[cardID].table); i++ ) + { + if ( !strcmp(buff, (char *)TheMemoryCard.Cards[cardID].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + return false; + + printf("Game directory present"); + + return true; +} + +void +CMemoryCard::PopulateSlotInfo(int32 cardID) +{ + CTimer::Stop(); + + for ( int32 i = 0; i < MAX_SLOTS; i++ ) + { + Slots[i] = SLOT_NOTPRESENT; + + for ( int32 j = 0; j < ARRAY_SIZE(SlotFileName[i]); j++ ) + SlotFileName[i][j] = L'\0'; + + for ( int32 j = 0; j < ARRAY_SIZE(SlotSaveDate[i]); j++ ) + SlotSaveDate[i][j] = L'\0'; + + UnicodeStrcpy(SlotSaveDate[i], TheText.Get("DEFDT")); + } + + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + + if ( nError != NO_ERR_SUCCESS ) + return; + + TheMemoryCard.ChangeDirectory(cardID, TheMemoryCard.Cards[CARD_ONE].dir); + + if ( nError != NO_ERR_SUCCESS && nError != ERR_DIRNOENTRY ) + return; + + PopulateFileTable(cardID); + + if ( nError != NO_ERR_SUCCESS && nError != ERR_FILETABLENOENTRY ) + return; + + for ( int32 slot = 0; slot < MAX_SLOTS; slot++ ) + { +#if defined(PS2) + for ( int32 entry = 7; entry < ARRAY_SIZE(Cards[cardID].table); entry++ ) +#else + for ( int32 entry = 0; entry < ARRAY_SIZE(Cards[cardID].table); entry++ ) +#endif + { + if ( TheMemoryCard.Cards[CARD_ONE].table[entry].FileSizeByte != 0 ) + { + char slotnum[30]; + char slotname[30]; + char slotdate[30]; + + if ( +#if defined(PS2) + TheMemoryCard.Cards[CARD_ONE].table[entry].AttrFile & sceMcFileAttrClosed && +#endif + TheMemoryCard.Cards[CARD_ONE].table[entry].FileSizeByte >= SAVE_FILE_SIZE ) + { + char *entryname = (char *)Cards[cardID].table[entry].EntryName; + + bool bFound = false; +#if defined(PS2) + for ( int32 i = 7; i < ARRAY_SIZE(Cards[cardID].table) && !bFound; i++ ) +#else + for ( int32 i = 0; i < ARRAY_SIZE(Cards[cardID].table) && !bFound; i++ ) +#endif + { + sprintf(slotnum, "%i ", slot+1); + + for ( int32 j = 0; j < sizeof(slotname); j++ ) + slotname[j] = '\0'; + + strncat(slotname, slotnum, sizeof(slotnum)-1); + + if ( !strncmp(slotname, entryname, 1) ) + { + bFound = true; + + Slots[slot] = SLOT_PRESENT; + AsciiToUnicode(entryname, SlotFileName[slot]); + + int32 sec = Cards[CARD_ONE].table[entry]._Create.Sec; + int32 month = Cards[CARD_ONE].table[entry]._Create.Month; + int32 year = Cards[CARD_ONE].table[entry]._Create.Year; + int32 min = Cards[CARD_ONE].table[entry]._Create.Min; + int32 hour = Cards[CARD_ONE].table[entry]._Create.Hour; + int32 day = Cards[CARD_ONE].table[entry]._Create.Day; + + for ( int32 j = 0; j < ARRAY_SIZE(SlotSaveDate[slot]); j++ ) + SlotSaveDate[slot][j] = L'\0'; + + for ( int32 j = 0; j < ARRAY_SIZE(slotdate); j++ ) + slotdate[j] = '\0'; + + char *monthstr; + switch ( month ) + { + case 1: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("JAN")); break; + case 2: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("FEB")); break; + case 3: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("MAR")); break; + case 4: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("APR")); break; + case 5: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("MAY")); break; + case 6: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("JUN")); break; + case 7: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("JUL")); break; + case 8: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("AUG")); break; + case 9: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("SEP")); break; + case 10: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("OCT")); break; + case 11: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("NOV")); break; + case 12: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("DEC")); break; + } + + sprintf(slotdate, "%02d %s %04d %02d:%02d:%02d", day, monthstr, year, hour, min, sec); + AsciiToUnicode(slotdate, SlotSaveDate[slot]); + } + } + } + else + { + char *entryname = (char *)Cards[cardID].table[entry].EntryName; + + bool bFound = false; +#if defined(PS2) + for ( int32 i = 7; i < ARRAY_SIZE(Cards[cardID].table) && !bFound; i++ ) // again ... +#else + for ( int32 i = 0; i < ARRAY_SIZE(Cards[cardID].table) && !bFound; i++ ) // again ... +#endif + { + sprintf(slotnum, "%i ", slot+1); + + for ( int32 j = 0; j < sizeof(slotname); j++ ) + slotname[j] = '\0'; + + strncat(slotname, slotnum, sizeof(slotnum)-1); + + if ( !strncmp(slotname, entryname, 1) ) + { + bFound = true; + + Slots[slot] = SLOT_CORRUPTED; + AsciiToUnicode(entryname, SlotFileName[slot]); + } + } + } + } + } + } + + nError = NO_ERR_SUCCESS; + return; +} + +int32 +CMemoryCard::GetInfoOnSpecificSlot(int32 slotID) +{ + return Slots[slotID]; +} + +wchar * +CMemoryCard::GetDateAndTimeOfSavedGame(int32 slotID) +{ + return SlotSaveDate[slotID]; +} + +int32 +CMemoryCard::CheckCardStateAtGameStartUp(int32 cardID) +{ + CheckCardInserted(cardID); + if ( nError == ERR_NOFORMAT ) + return MCSTATE_OK; + if ( nError == ERR_NONE ) + return MCSTATE_NOCARD; + + if ( !CheckGameDirectoryThere(cardID) ) + { + if ( nError == ERR_NONE ) + return MCSTATE_NOCARD; + + DeleteEverythingInGameRoot(cardID); + if ( nError == ERR_NONE ) + return MCSTATE_NOCARD; + + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + if ( nError == ERR_NONE ) + return MCSTATE_NOCARD; + + if ( Cards[CurrentCard].free < 500 ) + return MCSTATE_NEED_500KB; + + return MCSTATE_OK; + } + + TheMemoryCard.CheckCardInserted(CARD_ONE); + + if ( nError == NO_ERR_SUCCESS ) + { + if ( TheMemoryCard.ChangeDirectory(CARD_ONE, Cards[CARD_ONE].dir) != ERR_NONE ) + { + if ( TheMemoryCard.FindMostRecentFileName(CARD_ONE, MostRecentFile) == true ) + { + if ( TheMemoryCard.CheckDataNotCorrupt(MostRecentFile) == RES_FAILED ) + { + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + if ( Cards[CurrentCard].free < 200 ) + return MCSTATE_NEED_200KB; + } + } + else + { + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + if ( Cards[CurrentCard].free < 200 ) + return MCSTATE_NEED_200KB; + } + } + } + + if ( TheMemoryCard.CheckCardInserted(CARD_ONE) != NO_ERR_SUCCESS ) + return MCSTATE_NOCARD; + + return MCSTATE_OK; +} + +void +CMemoryCard::SaveSlot(int32 slotID) +{ + bool bSave = true; + + for ( int32 j = 0; j < sizeof(ValidSaveName); j++ ) + ValidSaveName[j] = '\0'; + + char buff[100]; + + sprintf(buff, "%i ", slotID+1); + strncat(ValidSaveName, buff, sizeof(ValidSaveName) - 1); + + if ( CStats::LastMissionPassedName[0] != '\0' ) + { + char mission[100]; + + strcpy(mission, UnicodeToAsciiForMemoryCard(TheText.Get(CStats::LastMissionPassedName))); + +#ifdef FIX_BUGS + strncat(ValidSaveName, mission, sizeof(ValidSaveName)-1); +#else + strncat(ValidSaveName, mission, 21); + strncat(ValidSaveName, "...", strlen("...")); +#endif + } + + if ( !CheckGameDirectoryThere(CARD_ONE) ) + { + DeleteEverythingInGameRoot(CARD_ONE); + bSave = CreateGameDirectoryFromScratch(CARD_ONE); + } + + if ( bSave ) + { + if ( Slots[slotID] == SLOT_PRESENT ) + { + TheMemoryCard.ChangeDirectory(CARD_ONE, Cards[CurrentCard].dir); + if ( nError == NO_ERR_SUCCESS ) + TheMemoryCard.DeleteMemoryCardFile(CARD_ONE, UnicodeToAsciiForMemoryCard(SlotFileName[slotID])); + } + + SaveGame(); + } + + CTimer::Stop(); + CStreaming::FlushRequestList(); + CStreaming::DeleteRwObjectsAfterDeath(FindPlayerPed()->GetPosition()); + CStreaming::RemoveUnusedModelsInLoadedList(); + CGame::DrasticTidyUpMemory(false); + CTimer::Update(); +} + +void +CMemoryCard::DeleteSlot(int32 slotID) +{ + TheMemoryCard.ChangeDirectory(CARD_ONE, Cards[CurrentCard].dir); + + if ( nError == NO_ERR_SUCCESS ) + TheMemoryCard.DeleteMemoryCardFile(CARD_ONE, UnicodeToAsciiForMemoryCard(SlotFileName[slotID])); +} + +void +CMemoryCard::LoadSlotToBuffer(int32 slotID) +{ + CStreaming::DeleteAllRwObjects(); + + strcpy(LoadFileName, UnicodeToAsciiForMemoryCard(SlotFileName[slotID])); + + TheMemoryCard.ChangeDirectory(CARD_ONE, Cards[CurrentCard].dir); + + if ( nError == NO_ERR_SUCCESS ) + TheMemoryCard.CheckDataNotCorrupt(LoadFileName); +} + +wchar * +CMemoryCard::GetNameOfSavedGame(int32 slotID) +{ + return SlotFileName[slotID]; +} + +int32 +CMemoryCard::DoClassSaveRoutine(int32 file, uint8 *data, uint32 size) +{ + WritetoMemCard(file, &size, sizeof(size)); + + if ( nError != NO_ERR_SUCCESS ) + { + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return ERR_NONE; + } + + WritetoMemCard(file, data, align4bytes(size)); + + uint8 sizebuff[4]; + memcpy(sizebuff, &size, sizeof(size)); + + for ( int32 i = 0; i < ARRAY_SIZE(sizebuff); i++ ) + CheckSum += sizebuff[i]; + + for ( int32 i = 0; i < align4bytes(size); i++ ) + CheckSum += *data++; + + if ( nError != NO_ERR_SUCCESS ) + { + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return ERR_NONE; + } + + return nError; +} + +#endif diff --git a/src/save/MemoryCard.h b/src/save/MemoryCard.h new file mode 100644 index 0000000..bae605f --- /dev/null +++ b/src/save/MemoryCard.h @@ -0,0 +1,197 @@ +#pragma once +#include "common.h" +#ifdef PS2_MENU +#include "Date.h" + +#if defined(PS2) +#include +#include +#include +#endif + +enum +{ + CARD_ONE = 0, + CARD_TWO, + MAX_CARDS, +}; + +class CMemoryCardInfo +{ +public: + int port; + int slot; + int type; + int free; + int format; + char dir[40]; +#if defined(PS2) + sceMcTblGetDir table[15]; +#else + struct + { + typedef struct {unsigned char Sec,Min,Hour; unsigned char Day,Month; unsigned short Year;} _time; + _time _Create; + _time _Modify; + unsigned int FileSizeByte; + unsigned short AttrFile; + unsigned char EntryName[32]; + }table[15]; +#endif + CMemoryCardInfo(void); +}; + + +#define GUFF_FILE_SIZE 147096 +#define SAVE_FILE_SIZE 201729 + +class CMemoryCard +{ +public: + enum + { + MAX_SLOTS = 8, + }; + + enum MCSTATE + { + MCSTATE_OK = 0, + MCSTATE_NEED_500KB, + MCSTATE_NEED_200KB, + MCSTATE_NOCARD, + }; + + enum SLOTINFO + { + SLOT_PRESENT = 0, + SLOT_NOTPRESENT, + SLOT_CORRUPTED, + }; + + int _unk0; + int _unk1; + bool m_bWantToLoad; + bool JustLoadedDontFadeInYet; + bool StillToFadeOut; + bool b_FoundRecentSavedGameWantToLoad; + uint32 TimeStartedCountingForFade; + uint32 TimeToStayFadedBeforeFadeOut; + uint32 LastBlockSize; + bool _bunk2; + char ValidSaveName [30]; + char MostRecentFile [30]; + char _unkName3 [30]; + char SaveFileNameJustSaved[30]; + char _pad0[3]; + wchar *pErrorMsg; + char _unk4[32]; + bool _bunk5; + bool _bunk6; + bool _bunk7; + bool _bunk8; + int nError; + wchar _unk9[30]; + char LoadFileName[30]; + char _pad1[2]; + CDate CompileDateAndTime; + int m_LanguageToLoad; + int m_LevelToLoad; + int CurrentCard; + CMemoryCardInfo Cards [MAX_CARDS]; + int Slots [MAX_SLOTS]; + wchar SlotFileName[MAX_SLOTS][30]; + wchar SlotSaveDate[MAX_SLOTS][30]; + char _unk10[32]; + + enum + { + ERR_NONE = 0, + ERR_NOFORMAT = 1, + ERR_DIRNOENTRY = 2, + ERR_OPENNOENTRY = 3, + ERR_DELETENOENTRY = 4, + ERR_DELETEDENIED = 5, + ERR_DELETEFAILED = 6, + ERR_WRITEFULLDEVICE = 7, + ERR_WRITENOENTRY = 8, + ERR_WRITEDENIED = 9, + ERR_FLUSHNOENTRY, + ERR_WRITEFAILED, + ERR_FORMATFAILED = 12, + ERR_FILETABLENOENTRY = 13, + ERR_DIRFULLDEVICE = 14, + ERR_DIRBADENTRY = 15, + ERR_FILEFULLDEVICE = 16, + ERR_FILENOPATHENTRY = 17, + ERR_FILEDENIED = 18, + ERR_FILEUPLIMIT = 19, + ERR_READNOENTRY = 20, + ERR_READDENIED = 21, + ERR_LOADFAILED = 22, // unused + ERR_SAVEFAILED = 23, + ERR_DATACORRUPTED = 24, + ERR_NOROOTDIR = 25, + NO_ERR_SUCCESS = 26, + }; + + enum + { + RES_SUCCESS = 1, + RES_FAILED = -1, + }; + + int32 GetError() + { + return nError; + } + + wchar *GetErrorMessage() + { + return pErrorMsg; + } + + int32 Init(void); + CMemoryCard(void); + int32 RestoreForStartLoad(void); + int32 LoadSavedGame(void); + int32 CheckCardInserted(int32 cardID); + int32 PopulateCardFlags(int32 cardID, bool bSlotFlag, bool bTypeFlag, bool bFreeFlag, bool bFormatFlag); + int32 FormatCard(int32 cardID); + int32 PopulateFileTable(int32 cardID); + int32 CreateRootDirectory(int32 cardID); + int32 ChangeDirectory(int32 cardID, char *dir); + int32 CreateIconFiles(int32 cardID, char *icon_one, char *icon_two, char *icon_three); + int32 LoadIconFiles(int32 cardID, char *icon_one, char *icon_two, char *icon_three); + int32 CloseMemCardFile(int32 file); + int32 CreateMemCardFileReadWrite(int32 cardID, char *filename); + int32 OpenMemCardFileForReading(int32 cardID, char *filename); + int32 ReadFromMemCard(int32 file, void *buff, int32 size); + int32 DeleteMemoryCardFile(int32 cardID, char *filename); + void PopulateErrorMessage(); + int32 WritetoMemCard(int32 file, void *buff, int32 size); + bool SaveGame(void); + bool DoHackRoundSTUPIDSonyDateTimeStuff(int32 port, char *filename); + int32 LookForRootDirectory(int32 cardID); + int32 FillFirstFileWithGuff(int32 cardID); + bool FindMostRecentFileName(int32 cardID, char *filename); + void ClearFileTableBuffer(int32 cardID); + int32 GetClusterAmountForFileCreation(int32 port); + bool DeleteEverythingInGameRoot(int32 cardID); + int32 CheckDataNotCorrupt(char *filename); + int32 GetLanguageToLoad(void); + int32 GetLevelToLoad(void); + bool CreateGameDirectoryFromScratch(int32 cardID); + bool CheckGameDirectoryThere(int32 cardID); + void PopulateSlotInfo(int32 cardID); + int32 GetInfoOnSpecificSlot(int32 slotID); + wchar *GetDateAndTimeOfSavedGame(int32 slotID); + int32 CheckCardStateAtGameStartUp(int32 cardID); + void SaveSlot(int32 slotID); + void DeleteSlot(int32 slotID); + void LoadSlotToBuffer(int32 slotID); + wchar *GetNameOfSavedGame(int32 slotID); + int32 DoClassSaveRoutine(int32 file, uint8 *data, uint32 size); +}; + +extern CMemoryCard TheMemoryCard; +#endif \ No newline at end of file diff --git a/src/save/PCSave.cpp b/src/save/PCSave.cpp new file mode 100644 index 0000000..0c228a6 --- /dev/null +++ b/src/save/PCSave.cpp @@ -0,0 +1,166 @@ +#define WITHWINDOWS +#include "common.h" +#include "crossplatform.h" + +#include "FileMgr.h" +#include "Font.h" +#ifdef MORE_LANGUAGES +#include "Game.h" +#endif +#include "GenericGameStorage.h" +#include "Messages.h" +#include "PCSave.h" +#include "Text.h" + +const char* _psGetUserFilesFolder(); + +C_PcSave PcSaveHelper; + +void +C_PcSave::SetSaveDirectory(const char *path) +{ + sprintf(DefaultPCSaveFileName, "%s\\%s", path, "GTA3sf"); +} + +bool +C_PcSave::DeleteSlot(int32 slot) +{ +#ifdef FIX_BUGS + char FileName[MAX_PATH]; +#else + char FileName[200]; +#endif + + PcSaveHelper.nErrorCode = SAVESTATUS_SUCCESSFUL; + sprintf(FileName, "%s%i.b", DefaultPCSaveFileName, slot + 1); + DeleteFile(FileName); + SlotSaveDate[slot][0] = '\0'; + return true; +} + +bool +C_PcSave::SaveSlot(int32 slot) +{ + MakeValidSaveName(slot); + PcSaveHelper.nErrorCode = SAVESTATUS_SUCCESSFUL; + _psGetUserFilesFolder(); + int file = CFileMgr::OpenFile(ValidSaveName, "wb"); + if (file != 0) { +#ifdef MISSION_REPLAY + if (!IsQuickSave) +#endif + DoGameSpecificStuffBeforeSave(); + if (GenericSave(file)) { + if (!!CFileMgr::CloseFile(file)) + nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; + return true; + } + + return false; + } + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CREATE; + return false; +} + +bool +C_PcSave::PcClassSaveRoutine(int32 file, uint8 *data, uint32 size) +{ + CFileMgr::Write(file, (const char*)&size, sizeof(size)); + if (CFileMgr::GetErrorReadWrite(file)) { + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + + CFileMgr::Write(file, (const char*)data, align4bytes(size)); + CheckSum += (uint8) size; + CheckSum += (uint8) (size >> 8); + CheckSum += (uint8) (size >> 16); + CheckSum += (uint8) (size >> 24); + for (int i = 0; i < align4bytes(size); i++) { + CheckSum += *data++; + } + if (CFileMgr::GetErrorReadWrite(file)) { + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + + return true; +} + +void +C_PcSave::PopulateSlotInfo() +{ + for (int i = 0; i < SLOT_COUNT; i++) { + Slots[i + 1] = SLOT_EMPTY; + SlotFileName[i][0] = '\0'; + SlotSaveDate[i][0] = '\0'; + } + for (int i = 0; i < SLOT_COUNT; i++) { +#ifdef FIX_BUGS + char savename[MAX_PATH]; +#else + char savename[52]; +#endif + struct { + int size; + wchar FileName[24]; + SYSTEMTIME SaveDateTime; + } header; + sprintf(savename, "%s%i%s", DefaultPCSaveFileName, i + 1, ".b"); + int file = CFileMgr::OpenFile(savename, "rb"); + if (file != 0) { + CFileMgr::Read(file, (char*)&header, sizeof(header)); + if (strncmp((char*)&header, TopLineEmptyFile, sizeof(TopLineEmptyFile)-1) != 0) { + Slots[i + 1] = SLOT_OK; + memcpy(SlotFileName[i], &header.FileName, sizeof(header.FileName)); + + SlotFileName[i][24] = '\0'; + } + CFileMgr::CloseFile(file); + } + if (Slots[i + 1] == SLOT_OK) { + if (CheckDataNotCorrupt(i, savename)) { +#ifdef FIX_INCOMPATIBLE_SAVES + if (!FixSave(i, GetSaveType(savename))) { + CMessages::InsertNumberInString(TheText.Get("FEC_SLC"), i + 1, -1, -1, -1, -1, -1, SlotFileName[i]); + Slots[i + 1] = SLOT_CORRUPTED; + continue; + } +#endif + SYSTEMTIME st; + memcpy(&st, &header.SaveDateTime, sizeof(SYSTEMTIME)); + const char *month; + switch (st.wMonth) + { + case 1: month = "JAN"; break; + case 2: month = "FEB"; break; + case 3: month = "MAR"; break; + case 4: month = "APR"; break; + case 5: month = "MAY"; break; + case 6: month = "JUN"; break; + case 7: month = "JUL"; break; + case 8: month = "AUG"; break; + case 9: month = "SEP"; break; + case 10: month = "OCT"; break; + case 11: month = "NOV"; break; + case 12: month = "DEC"; break; + default: assert(0); + } + char date[70]; +#ifdef MORE_LANGUAGES + if (CGame::japaneseGame) + sprintf(date, "%02d %02d %04d %02d:%02d:%02d", st.wDay, st.wMonth, st.wYear, st.wHour, st.wMinute, st.wSecond); + else +#endif // MORE_LANGUAGES + sprintf(date, "%02d %s %04d %02d:%02d:%02d", st.wDay, UnicodeToAsciiForSaveLoad(TheText.Get(month)), st.wYear, st.wHour, st.wMinute, st.wSecond); + AsciiToUnicode(date, SlotSaveDate[i]); + + } else { + CMessages::InsertNumberInString(TheText.Get("FEC_SLC"), i + 1, -1, -1, -1, -1, -1, SlotFileName[i]); + Slots[i + 1] = SLOT_CORRUPTED; + } + } + } +} diff --git a/src/save/PCSave.h b/src/save/PCSave.h new file mode 100644 index 0000000..83471b5 --- /dev/null +++ b/src/save/PCSave.h @@ -0,0 +1,40 @@ +#pragma once + +enum eSaveStatus +{ + SAVESTATUS_SUCCESSFUL = 0, + SAVESTATUS_ERR_SAVE_CREATE, + SAVESTATUS_ERR_SAVE_WRITE, + SAVESTATUS_ERR_SAVE_CLOSE, + SAVESTATUS_ERR_LOAD_OPEN, + SAVESTATUS_ERR_LOAD_READ, + SAVESTATUS_ERR_LOAD_CLOSE, + SAVESTATUS_ERR_DATA_INVALID, + + // unused + SAVESTATUS_DELETEFAILED8, + SAVESTATUS_DELETEFAILED9, + SAVESTATUS_DELETEFAILED10, +}; + +enum +{ + SLOT_OK = 0, + SLOT_EMPTY, + SLOT_CORRUPTED +}; + +class C_PcSave +{ +public: + eSaveStatus nErrorCode; + + C_PcSave() : nErrorCode(SAVESTATUS_SUCCESSFUL) {} + void PopulateSlotInfo(); + bool DeleteSlot(int32 slot); + bool SaveSlot(int32 slot); + bool PcClassSaveRoutine(int32 file, uint8 *data, uint32 size); + static void SetSaveDirectory(const char *path); +}; + +extern C_PcSave PcSaveHelper; diff --git a/src/save/SaveBuf.h b/src/save/SaveBuf.h new file mode 100644 index 0000000..aad2e1a --- /dev/null +++ b/src/save/SaveBuf.h @@ -0,0 +1,73 @@ +#pragma once + +#ifdef VALIDATE_SAVE_SIZE +extern int32 _saveBufCount; +#define INITSAVEBUF _saveBufCount = 0; +#define VALIDATESAVEBUF(b) assert(_saveBufCount == b); +#else +#define INITSAVEBUF +#define VALIDATESAVEBUF(b) +#endif + +inline void +SkipSaveBuf(uint8 *&buf, int32 skip) +{ + buf += skip; +#ifdef VALIDATE_SAVE_SIZE + _saveBufCount += skip; +#endif +} + +template +inline void +ReadSaveBuf(T* out, uint8 *&buf) +{ + *out = *(T *)buf; + SkipSaveBuf(buf, sizeof(T)); +} + +template +inline T * +WriteSaveBuf(uint8 *&buf, const T &value) +{ + T *p = (T *)buf; + *p = value; + SkipSaveBuf(buf, sizeof(T)); + return p; +} + +#ifdef COMPATIBLE_SAVES +inline void +ZeroSaveBuf(uint8 *&buf, uint32 length) +{ + memset(buf, 0, length); + SkipSaveBuf(buf, length); +} +#endif + +#define SAVE_HEADER_SIZE (4 * sizeof(char) + sizeof(uint32)) + +#define WriteSaveHeader(buf, a, b, c, d, size) \ + WriteSaveBuf(buf, a); \ + WriteSaveBuf(buf, b); \ + WriteSaveBuf(buf, c); \ + WriteSaveBuf(buf, d); \ + WriteSaveBuf(buf, (uint32)(size)); + +#ifdef VALIDATE_SAVE_SIZE +#define CheckSaveHeader(buf, a, b, c, d, size) do { \ + char _c; uint32 _size;\ + ReadSaveBuf(&_c, buf);\ + assert(_c == a);\ + ReadSaveBuf(&_c, buf);\ + assert(_c == b);\ + ReadSaveBuf(&_c, buf);\ + assert(_c == c);\ + ReadSaveBuf(&_c, buf);\ + assert(_c == d);\ + ReadSaveBuf(&_size, buf);\ + assert(_size == size);\ + } while(0) +#else +#define CheckSaveHeader(buf, a, b, c, d, size) SkipSaveBuf(buf, 8); +#endif \ No newline at end of file diff --git a/src/skel/crossplatform.cpp b/src/skel/crossplatform.cpp new file mode 100644 index 0000000..e69c22e --- /dev/null +++ b/src/skel/crossplatform.cpp @@ -0,0 +1,425 @@ +#include "common.h" +#include "crossplatform.h" + +// Codes compatible with Windows and Linux +#ifndef _WIN32 + +// For internal use +// wMilliseconds is not needed +void tmToSystemTime(const tm *tm, SYSTEMTIME *out) { + out->wYear = tm->tm_year + 1900; + out->wMonth = tm->tm_mon + 1; + out->wDayOfWeek = tm->tm_wday; + out->wDay = tm->tm_mday; + out->wHour = tm->tm_hour; + out->wMinute = tm->tm_min; + out->wSecond = tm->tm_sec; +} + +void GetLocalTime_CP(SYSTEMTIME *out) { + time_t timestamp = time(nil); + tm *localTm = localtime(×tamp); + tmToSystemTime(localTm, out); +} +#endif + +// Compatible with Linux/POSIX and MinGW on Windows +#ifndef _WIN32 +HANDLE FindFirstFile(const char* pathname, WIN32_FIND_DATA* firstfile) { + char pathCopy[MAX_PATH]; + strcpy(pathCopy, pathname); + + char *folder = strtok(pathCopy, "*"); + char *extension = strtok(NULL, "*"); + + // because I remember like strtok might not return NULL for last delimiter + if (extension && extension - folder == strlen(pathname)) + extension = nil; + + // Case-sensitivity and backslashes... + // Will be freed at the bottom + char *realFolder = casepath(folder); + if (realFolder) { + folder = realFolder; + } + + strncpy(firstfile->folder, folder, sizeof(firstfile->folder)); + + if (extension) + strncpy(firstfile->extension, extension, sizeof(firstfile->extension)); + else + firstfile->extension[0] = '\0'; + + if (realFolder) + free(realFolder); + + HANDLE d; + if ((d = (HANDLE)opendir(firstfile->folder)) == NULL || !FindNextFile(d, firstfile)) + return NULL; + + return d; +} + +bool FindNextFile(HANDLE d, WIN32_FIND_DATA* finddata) { + dirent *file; + static struct stat fileStats; + static char path[PATH_MAX], relativepath[NAME_MAX + sizeof(finddata->folder) + 1]; + int extensionLen = strlen(finddata->extension); + while ((file = readdir((DIR*)d)) != NULL) { + + // We only want "DT_REG"ular Files, but reportedly some FS and OSes gives DT_UNKNOWN as type. + if ((file->d_type == DT_UNKNOWN || file->d_type == DT_REG || file->d_type == DT_LNK) && + (extensionLen == 0 || strncasecmp(&file->d_name[strlen(file->d_name) - extensionLen], finddata->extension, extensionLen) == 0)) { + + sprintf(relativepath, "%s/%s", finddata->folder, file->d_name); + realpath(relativepath, path); + stat(path, &fileStats); + strncpy(finddata->cFileName, file->d_name, sizeof(finddata->cFileName)); + finddata->ftLastWriteTime = fileStats.st_mtime; + return true; + } + } + return false; +} + +void GetDateFormat(int unused1, int unused2, SYSTEMTIME* in, int unused3, char* out, int size) { + tm linuxTime; + linuxTime.tm_year = in->wYear - 1900; + linuxTime.tm_mon = in->wMonth - 1; + linuxTime.tm_wday = in->wDayOfWeek; + linuxTime.tm_mday = in->wDay; + linuxTime.tm_hour = in->wHour; + linuxTime.tm_min = in->wMinute; + linuxTime.tm_sec = in->wSecond; + strftime(out, size, nl_langinfo(D_FMT), &linuxTime); +} + +void FileTimeToSystemTime(time_t* writeTime, SYSTEMTIME* out) { + tm *ptm = gmtime(writeTime); + tmToSystemTime(ptm, out); +} +#endif + +// Because wchar length differs between platforms. +wchar* +AllocUnicode(const char* src) +{ + wchar *dst = (wchar*)malloc(strlen(src)*2 + 2); + wchar *i = dst; + while((*i++ = (unsigned char)*src++) != '\0'); + return dst; +} + +// Funcs/features from Windows that we need on other platforms +#ifndef _WIN32 +char *strupr(char *s) { + char* tmp = s; + + for (;*tmp;++tmp) { + *tmp = toupper((unsigned char) *tmp); + } + + return s; +} +char *strlwr(char *s) { + char* tmp = s; + + for (;*tmp;++tmp) { + *tmp = tolower((unsigned char) *tmp); + } + + return s; +} + +char *trim(char *s) { + char *ptr; + if (!s) + return NULL; // handle NULL string + if (!*s) + return s; // handle empty string + for (ptr = s + strlen(s) - 1; (ptr >= s) && isspace(*ptr); --ptr); + ptr[1] = '\0'; + return s; +} + +FILE* _fcaseopen(char const* filename, char const* mode) +{ + FILE* result; + char* real = casepath(filename); + if (!real) + result = fopen(filename, mode); + else { + result = fopen(real, mode); + free(real); + } + return result; +} + +int _caserename(const char *old_filename, const char *new_filename) +{ + int result; + char *real_old = casepath(old_filename); + char *real_new = casepath(new_filename); + + // hack so we don't even try to rename it to new_filename if it already exists + if (!real_new) { + free(real_old); + return -1; + } + + if (!real_old) + result = rename(old_filename, real_new); + else + result = rename(real_old, real_new); + + free(real_old); + free(real_new); + + return result; +} + +// Case-insensitivity on linux (from https://github.com/OneSadCookie/fcaseopen) +// Returned string should freed manually (if exists) +char* casepath(char const* path, bool checkPathFirst) +{ + if (checkPathFirst && access(path, F_OK) != -1) { + // File path is correct + return nil; + } + + size_t l = strlen(path); + char* p = (char*)alloca(l + 1); + char* out = (char*)malloc(l + 3); // for extra ./ + strcpy(p, path); + + // my addon: linux doesn't handle filenames with spaces at the end nicely + p = trim(p); + + size_t rl = 0; + + DIR* d; + char* c; + + #if defined(__SWITCH__) || defined(PSP2) + if( (c = strstr(p, ":/")) != NULL) // scheme used by some environments, eg. switch, vita + { + size_t deviceNameOffset = c - p + 3; + char* deviceNamePath = (char*)alloca(deviceNameOffset + 1); + strlcpy(deviceNamePath, p, deviceNameOffset); + deviceNamePath[deviceNameOffset] = 0; + d = opendir(deviceNamePath); + p = c + 1; + } + else + #endif + if (p[0] == '/' || p[0] == '\\') + { + d = opendir("/"); + } + else + { + d = opendir("."); + out[0] = '.'; + out[1] = 0; + rl = 1; + } + + bool cantProceed = false; // just convert slashes in what's left in string, don't correct case of letters(because we can't) + bool mayBeTrailingSlash = false; + + while (c = strsep(&p, "/\\")) + { + // May be trailing slash(allow), slash at the start(avoid), or multiple slashes(avoid) + if (*c == '\0') + { + mayBeTrailingSlash = true; + continue; + } else { + mayBeTrailingSlash = false; + } + + out[rl] = '/'; + rl += 1; + out[rl] = 0; + + if (cantProceed) + { + strcpy(out + rl, c); + rl += strlen(c); + continue; + } + + struct dirent* e; + while (e = readdir(d)) + { + if (strcasecmp(c, e->d_name) == 0) + { + strcpy(out + rl, e->d_name); + int reportedLen = (int)strlen(e->d_name); + rl += reportedLen; + assert(reportedLen == strlen(c) && "casepath: This is not good at all"); + + closedir(d); + d = opendir(out); + + // Either it wasn't a folder, or permission error, I/O error etc. + if (!d) { + cantProceed = true; + } + + break; + } + } + + if (!e) + { + printf("casepath couldn't find dir/file \"%s\", full path was %s\n", c, path); + // No match, add original name and continue converting further slashes. + strcpy(out + rl, c); + rl += strlen(c); + cantProceed = true; + } + } + + if (d) closedir(d); + if (mayBeTrailingSlash) { + out[rl] = '/'; rl += 1; + out[rl] = '\0'; + } + + if (rl > l + 2) { + printf("\n\ncasepath: Corrected path length is longer then original+2:\n\tOriginal: %s (%zu chars)\n\tCorrected: %s (%zu chars)\n\n", path, l, out, rl); + } + return out; +} +#endif + +#ifdef __SWITCH__ +/* Taken from glibc */ +char *realpath(const char *name, char *resolved) +{ + char *rpath, *dest = NULL; + const char *start, *end, *rpath_limit; + long int path_max; + + /* As per Single Unix Specification V2 we must return an error if + either parameter is a null pointer. We extend this to allow + the RESOLVED parameter to be NULL in case the we are expected to + allocate the room for the return value. */ + if (!name) + return NULL; + + /* As per Single Unix Specification V2 we must return an error if + the name argument points to an empty string. */ + if (name[0] == '\0') + return NULL; + +#ifdef PATH_MAX + path_max = PATH_MAX; +#else + path_max = pathconf(name, _PC_PATH_MAX); + if (path_max <= 0) + path_max = 1024; +#endif + + if (!resolved) + { + rpath = (char*)malloc(path_max); + if (!rpath) + return NULL; + } + else + rpath = resolved; + rpath_limit = rpath + path_max; + + if (name[0] != '/') + { + if (!getcwd(rpath, path_max)) + { + rpath[0] = '\0'; + goto error; + } + dest = (char*)memchr(rpath, '\0', path_max); + } + else + { + rpath[0] = '/'; + dest = rpath + 1; + } + + for (start = end = name; *start; start = end) + { + /* Skip sequence of multiple path-separators. */ + while (*start == '/') + ++start; + + /* Find end of path component. */ + for (end = start; *end && *end != '/'; ++end) + /* Nothing. */; + + if (end - start == 0) + break; + else if (end - start == 1 && start[0] == '.') + /* nothing */; + else if (end - start == 2 && start[0] == '.' && start[1] == '.') + { + /* Back up to previous component, ignore if at root already. */ + if (dest > rpath + 1) + while ((--dest)[-1] != '/') + ; + } + else + { + size_t new_size; + + if (dest[-1] != '/') + *dest++ = '/'; + + if (dest + (end - start) >= rpath_limit) + { + ptrdiff_t dest_offset = dest - rpath; + char *new_rpath; + + if (resolved) + { + if (dest > rpath + 1) + dest--; + *dest = '\0'; + goto error; + } + new_size = rpath_limit - rpath; + if (end - start + 1 > path_max) + new_size += end - start + 1; + else + new_size += path_max; + new_rpath = (char *)realloc(rpath, new_size); + if (!new_rpath) + goto error; + rpath = new_rpath; + rpath_limit = rpath + new_size; + + dest = rpath + dest_offset; + } + + dest = (char*)memcpy(dest, start, end - start); + *dest = '\0'; + } + } + if (dest > rpath + 1 && dest[-1] == '/') + --dest; + *dest = '\0'; + + return rpath; + +error: + if (!resolved) + free(rpath); + return NULL; +} + +ssize_t readlink (const char * __path, char * __buf, size_t __buflen) +{ + errno = ENOSYS; + return -1; +} +#endif diff --git a/src/skel/crossplatform.h b/src/skel/crossplatform.h new file mode 100644 index 0000000..67bb428 --- /dev/null +++ b/src/skel/crossplatform.h @@ -0,0 +1,184 @@ +#include +#include + +// This is the common include for platform/renderer specific skeletons(glfw.cpp, win.cpp etc.) and using cross platform things (like Windows directories wrapper, platform specific global arrays etc.) +// Functions that's different on glfw and win but have same signature, should be located on platform.h. + +enum eWinVersion +{ + OS_WIN95 = 0, + OS_WIN98, + OS_WINNT, + OS_WIN2000, + OS_WINXP, +}; + +#ifdef _WIN32 + +// As long as WITHWINDOWS isn't defined / isn't included, we only need type definitions so let's include . +// NOTE: It's perfectly fine to include here, but it can increase build size and time in *some* conditions, and maybe substantially in future if we'll use crossplatform.h more. +#ifndef _INC_WINDOWS + #ifndef __MWERKS__ + #include + #else + #include + #endif +#endif +#if defined RW_D3D9 || defined RWLIBS +#include "win.h" +#endif +extern DWORD _dwOperatingSystemVersion; +#define fcaseopen fopen +#define caserename rename +#else +char *strupr(char *str); +char *strlwr(char *str); + +enum { + LANG_OTHER, + LANG_GERMAN, + LANG_FRENCH, + LANG_ENGLISH, + LANG_ITALIAN, + LANG_SPANISH, +}; + +enum { + SUBLANG_OTHER, + SUBLANG_ENGLISH_AUS +}; + +extern long _dwOperatingSystemVersion; +char *casepath(char const *path, bool checkPathFirst = true); +FILE *_fcaseopen(char const *filename, char const *mode); +#define fcaseopen _fcaseopen +int _caserename(const char *old_filename, const char *new_filename); +#define caserename _caserename +#endif + +#ifdef RW_GL3 +typedef struct +{ + GLFWwindow* window; + RwBool fullScreen; + RwV2d lastMousePos; + double mouseWheel; // glfw doesn't cache it + bool cursorIsInWindow; + RwInt8 joy1id; + RwInt8 joy2id; +} +psGlobalType; + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +void CapturePad(RwInt32 padID); +void joysChangeCB(int jid, int event); +#endif + +#ifdef DETECT_JOYSTICK_MENU +extern char gSelectedJoystickName[128]; +#endif + +enum eGameState +{ + GS_START_UP = 0, + GS_INIT_LOGO_MPEG, + GS_LOGO_MPEG, + GS_INIT_INTRO_MPEG, + GS_INTRO_MPEG, + GS_INIT_ONCE, + GS_INIT_FRONTEND, + GS_FRONTEND, + GS_INIT_PLAYING_GAME, + GS_PLAYING_GAME, +}; +extern RwUInt32 gGameState; + +RwBool IsForegroundApp(); + +#ifndef MAX_PATH + #if !defined _WIN32 || defined __MINGW32__ + #define MAX_PATH PATH_MAX + #else + #define MAX_PATH 260 + #endif +#endif + +// Codes compatible with Windows and Linux +#ifndef _WIN32 +#define DeleteFile unlink + +// Needed for save games +struct SYSTEMTIME { + RwUInt16 wYear; + RwUInt16 wMonth; + RwUInt16 wDayOfWeek; + RwUInt16 wDay; + RwUInt16 wHour; + RwUInt16 wMinute; + RwUInt16 wSecond; + RwUInt16 wMilliseconds; +}; + +void GetLocalTime_CP(SYSTEMTIME* out); +#define GetLocalTime GetLocalTime_CP +#define OutputDebugString(s) re3_debug("[DBG-2]: %s\n",s) +#endif + +// Compatible with Linux/POSIX and MinGW on Windows +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include + +typedef void* HANDLE; +#define INVALID_HANDLE_VALUE NULL +#define FindClose(h) \ + do { \ + if (h != nil) \ + closedir((DIR*)h); \ + } while(0) + +#define LOCALE_USER_DEFAULT 0 +#define DATE_SHORTDATE 0 + +struct WIN32_FIND_DATA { + char extension[32]; // for searching + char folder[MAX_PATH]; // for searching + char cFileName[256]; // because tSkinInfo has it 256 + time_t ftLastWriteTime; +}; + +HANDLE FindFirstFile(const char*, WIN32_FIND_DATA*); +bool FindNextFile(HANDLE, WIN32_FIND_DATA*); +void FileTimeToSystemTime(time_t*, SYSTEMTIME*); +void GetDateFormat(int, int, SYSTEMTIME*, int, char*, int); +#endif + +#ifdef __SWITCH__ + +// tweak glfw values for switch to match expected pc bindings +#ifdef GLFW_GAMEPAD_BUTTON_A + #undef GLFW_GAMEPAD_BUTTON_A +#endif +#define GLFW_GAMEPAD_BUTTON_A 1 + +#ifdef GLFW_GAMEPAD_BUTTON_B + #undef GLFW_GAMEPAD_BUTTON_B +#endif +#define GLFW_GAMEPAD_BUTTON_B 0 + +#ifdef GLFW_GAMEPAD_BUTTON_X + #undef GLFW_GAMEPAD_BUTTON_X +#endif +#define GLFW_GAMEPAD_BUTTON_X 3 + +#ifdef GLFW_GAMEPAD_BUTTON_Y + #undef GLFW_GAMEPAD_BUTTON_Y +#endif +#define GLFW_GAMEPAD_BUTTON_Y 2 + +#endif diff --git a/src/skel/events.cpp b/src/skel/events.cpp new file mode 100644 index 0000000..8744781 --- /dev/null +++ b/src/skel/events.cpp @@ -0,0 +1,831 @@ +#include "common.h" +#include "Pad.h" +#include "ControllerConfig.h" +#include "Frontend.h" +#include "Camera.h" + +#include "rwcore.h" +#include "skeleton.h" +#include "events.h" + + +/* + ***************************************************************************** + */ +static RsEventStatus +HandleKeyDown(RsKeyStatus *keyStatus) +{ + CPad *pad0 = CPad::GetPad(0); + CPad *pad1 = CPad::GetPad(1); + + RwInt32 c = keyStatus->keyCharCode; + + if ( c != rsNULL ) + { + switch (c) + { + case rsESC: + { + CPad::TempKeyState.ESC = 255; + break; + } + + case rsINS: + { + CPad::TempKeyState.INS = 255; + break; + } + + case rsDEL: + { + CPad::TempKeyState.DEL = 255; + break; + } + + case rsHOME: + { + CPad::TempKeyState.HOME = 255; + break; + } + + case rsEND: + { + CPad::TempKeyState.END = 255; + break; + } + + case rsPGUP: + { + CPad::TempKeyState.PGUP = 255; + break; + } + + case rsPGDN: + { + CPad::TempKeyState.PGDN = 255; + break; + } + + case rsUP: + { + CPad::TempKeyState.UP = 255; + break; + } + + case rsDOWN: + { + CPad::TempKeyState.DOWN = 255; + break; + } + + case rsLEFT: + { + CPad::TempKeyState.LEFT = 255; + break; + } + + case rsRIGHT: + { + CPad::TempKeyState.RIGHT = 255; + break; + } + + case rsNUMLOCK: + { + CPad::TempKeyState.NUMLOCK = 255; + break; + } + + case rsPADDEL: + { + CPad::TempKeyState.DECIMAL = 255; + break; + } + + case rsPADEND: + { + CPad::TempKeyState.NUM1 = 255; + break; + } + + case rsPADDOWN: + { + CPad::TempKeyState.NUM2 = 255; + break; + } + + case rsPADPGDN: + { + CPad::TempKeyState.NUM3 = 255; + break; + } + + case rsPADLEFT: + { + CPad::TempKeyState.NUM4 = 255; + break; + } + + case rsPAD5: + { + CPad::TempKeyState.NUM5 = 255; + break; + } + + case rsPADRIGHT: + { + CPad::TempKeyState.NUM6 = 255; + break; + } + + case rsPADHOME: + { + CPad::TempKeyState.NUM7 = 255; + break; + } + + case rsPADUP: + { + CPad::TempKeyState.NUM8 = 255; + break; + } + + case rsPADPGUP: + { + CPad::TempKeyState.NUM9 = 255; + break; + } + + case rsPADINS: + { + CPad::TempKeyState.NUM0 = 255; + break; + } + + case rsDIVIDE: + { + CPad::TempKeyState.DIV = 255; + break; + } + + case rsTIMES: + { + CPad::TempKeyState.MUL = 255; + break; + } + + case rsMINUS: + { + CPad::TempKeyState.SUB = 255; + break; + } + + case rsPADENTER: + { + CPad::TempKeyState.ENTER = 255; + break; + } + + case rsPLUS: + { + CPad::TempKeyState.ADD = 255; + break; + } + + case rsENTER: + { + CPad::TempKeyState.EXTENTER = 255; + break; + } + + case rsSCROLL: + { + CPad::TempKeyState.SCROLLLOCK = 255; + break; + } + + case rsPAUSE: + { + CPad::TempKeyState.PAUSE = 255; + break; + } + + case rsBACKSP: + { + CPad::TempKeyState.BACKSP = 255; + break; + } + + case rsTAB: + { + CPad::TempKeyState.TAB = 255; + break; + } + + case rsCAPSLK: + { + CPad::TempKeyState.CAPSLOCK = 255; + break; + } + + case rsLSHIFT: + { + CPad::TempKeyState.LSHIFT = 255; + break; + } + + case rsSHIFT: + { + CPad::TempKeyState.SHIFT = 255; + break; + } + + case rsRSHIFT: + { + CPad::TempKeyState.RSHIFT = 255; + break; + } + + case rsLCTRL: + { + CPad::TempKeyState.LCTRL = 255; + break; + } + + case rsRCTRL: + { + CPad::TempKeyState.RCTRL = 255; + break; + } + + case rsLALT: + { + CPad::TempKeyState.LALT = 255; + break; + } + + case rsRALT: + { + CPad::TempKeyState.RALT = 255; + break; + } + + + case rsLWIN: + { + CPad::TempKeyState.LWIN = 255; + break; + } + + case rsRWIN: + { + CPad::TempKeyState.RWIN = 255; + break; + } + + case rsAPPS: + { + CPad::TempKeyState.APPS = 255; + break; + } + + case rsF1: + case rsF2: + case rsF3: + case rsF4: + case rsF5: + case rsF6: + case rsF7: + case rsF8: + case rsF9: + case rsF10: + case rsF11: + case rsF12: + { + CPad::TempKeyState.F[c - rsF1] = 255; + break; + } + + default: + { + if ( c < 255 ) + { + CPad::TempKeyState.VK_KEYS[c] = 255; + pad0->AddToPCCheatString(c); + } + break; + } + } + + if ( CPad::m_bMapPadOneToPadTwo ) + { + if ( c == 'D' ) pad1->PCTempKeyState.LeftStickX = 128; + if ( c == 'A' ) pad1->PCTempKeyState.LeftStickX = -128; + if ( c == 'W' ) pad1->PCTempKeyState.LeftStickY = 128; + if ( c == 'S' ) pad1->PCTempKeyState.LeftStickY = -128; + if ( c == 'J' ) pad1->PCTempKeyState.RightStickX = 128; + if ( c == 'G' ) pad1->PCTempKeyState.RightStickX = -128; + if ( c == 'Y' ) pad1->PCTempKeyState.RightStickY = 128; + if ( c == 'H' ) pad1->PCTempKeyState.RightStickY = -128; + if ( c == 'Z' ) pad1->PCTempKeyState.LeftShoulder1 = 255; + if ( c == 'X' ) pad1->PCTempKeyState.LeftShoulder2 = 255; + if ( c == 'C' ) pad1->PCTempKeyState.RightShoulder1 = 255; + if ( c == 'V' ) pad1->PCTempKeyState.RightShoulder2 = 255; + if ( c == 'O' ) pad1->PCTempKeyState.DPadUp = 255; + if ( c == 'L' ) pad1->PCTempKeyState.DPadDown = 255; + if ( c == 'K' ) pad1->PCTempKeyState.DPadLeft = 255; + if ( c == ';' ) pad1->PCTempKeyState.DPadRight = 255; + if ( c == 'B' ) pad1->PCTempKeyState.Start = 255; + if ( c == 'N' ) pad1->PCTempKeyState.Select = 255; + if ( c == 'M' ) pad1->PCTempKeyState.Square = 255; + if ( c == ',' ) pad1->PCTempKeyState.Triangle = 255; + if ( c == '.' ) pad1->PCTempKeyState.Cross = 255; + if ( c == '/' ) pad1->PCTempKeyState.Circle = 255; + if ( c == rsRSHIFT ) pad1->PCTempKeyState.LeftShock = 255; + if ( c == rsRCTRL ) pad1->PCTempKeyState.RightShock = 255; + } + } + + return rsEVENTPROCESSED; +} + + +static RsEventStatus +HandleKeyUp(RsKeyStatus *keyStatus) +{ + CPad *pad0 = CPad::GetPad(0); + CPad *pad1 = CPad::GetPad(1); + + RwInt32 c = keyStatus->keyCharCode; + + if ( c != rsNULL ) + { + switch (c) + { + case rsESC: + { + CPad::TempKeyState.ESC = 0; + break; + } + + case rsINS: + { + CPad::TempKeyState.INS = 0; + break; + } + + case rsDEL: + { + CPad::TempKeyState.DEL = 0; + break; + } + + case rsHOME: + { + CPad::TempKeyState.HOME = 0; + break; + } + + case rsEND: + { + CPad::TempKeyState.END = 0; + break; + } + + case rsPGUP: + { + CPad::TempKeyState.PGUP = 0; + break; + } + + case rsPGDN: + { + CPad::TempKeyState.PGDN = 0; + break; + } + + case rsUP: + { + CPad::TempKeyState.UP = 0; + break; + } + + case rsDOWN: + { + CPad::TempKeyState.DOWN = 0; + break; + } + + case rsLEFT: + { + CPad::TempKeyState.LEFT = 0; + break; + } + + case rsRIGHT: + { + CPad::TempKeyState.RIGHT = 0; + break; + } + + case rsNUMLOCK: + { + CPad::TempKeyState.NUMLOCK = 0; + break; + } + + case rsPADDEL: + { + CPad::TempKeyState.DECIMAL = 0; + break; + } + + case rsPADEND: + { + CPad::TempKeyState.NUM1 = 0; + break; + } + + case rsPADDOWN: + { + CPad::TempKeyState.NUM2 = 0; + break; + } + + case rsPADPGDN: + { + CPad::TempKeyState.NUM3 = 0; + break; + } + + case rsPADLEFT: + { + CPad::TempKeyState.NUM4 = 0; + break; + } + + case rsPAD5: + { + CPad::TempKeyState.NUM5 = 0; + break; + } + + case rsPADRIGHT: + { + CPad::TempKeyState.NUM6 = 0; + break; + } + + case rsPADHOME: + { + CPad::TempKeyState.NUM7 = 0; + break; + } + + case rsPADUP: + { + CPad::TempKeyState.NUM8 = 0; + break; + } + + case rsPADPGUP: + { + CPad::TempKeyState.NUM9 = 0; + break; + } + + case rsPADINS: + { + CPad::TempKeyState.NUM0 = 0; + break; + } + + case rsDIVIDE: + { + CPad::TempKeyState.DIV = 0; + break; + } + + case rsTIMES: + { + CPad::TempKeyState.MUL = 0; + break; + } + + case rsMINUS: + { + CPad::TempKeyState.SUB = 0; + break; + } + + case rsPADENTER: + { + CPad::TempKeyState.ENTER = 0; + break; + } + + case rsPLUS: + { + CPad::TempKeyState.ADD = 0; + break; + } + + case rsENTER: + { + CPad::TempKeyState.EXTENTER = 0; + break; + } + + case rsSCROLL: + { + CPad::TempKeyState.SCROLLLOCK = 0; + break; + } + + case rsPAUSE: + { + CPad::TempKeyState.PAUSE = 0; + break; + } + + case rsBACKSP: + { + CPad::TempKeyState.BACKSP = 0; + break; + } + + case rsTAB: + { + CPad::TempKeyState.TAB = 0; + break; + } + + case rsCAPSLK: + { + CPad::TempKeyState.CAPSLOCK = 0; + break; + } + + case rsLSHIFT: + { + CPad::TempKeyState.LSHIFT = 0; + break; + } + + case rsSHIFT: + { + CPad::TempKeyState.SHIFT = 0; + break; + } + + case rsRSHIFT: + { + CPad::TempKeyState.RSHIFT = 0; + break; + } + + case rsLCTRL: + { + CPad::TempKeyState.LCTRL = 0; + break; + } + + case rsRCTRL: + { + CPad::TempKeyState.RCTRL = 0; + break; + } + + case rsLALT: + { + CPad::TempKeyState.LALT = 0; + break; + } + + case rsRALT: + { + CPad::TempKeyState.RALT = 0; + break; + } + + + case rsLWIN: + { + CPad::TempKeyState.LWIN = 0; + break; + } + + case rsRWIN: + { + CPad::TempKeyState.RWIN = 0; + break; + } + + case rsAPPS: + { + CPad::TempKeyState.APPS = 0; + break; + } + + case rsF1: + case rsF2: + case rsF3: + case rsF4: + case rsF5: + case rsF6: + case rsF7: + case rsF8: + case rsF9: + case rsF10: + case rsF11: + case rsF12: + { + CPad::TempKeyState.F[c - rsF1] = 0; + break; + } + + default: + { + if ( c < 255 ) + { + CPad::TempKeyState.VK_KEYS[c] = 0; + } + break; + } + } + + if ( CPad::m_bMapPadOneToPadTwo ) + { + if ( c == 'D' ) pad1->PCTempKeyState.LeftStickX = 0; + if ( c == 'A' ) pad1->PCTempKeyState.LeftStickX = 0; + if ( c == 'W' ) pad1->PCTempKeyState.LeftStickY = 0; + if ( c == 'S' ) pad1->PCTempKeyState.LeftStickY = 0; + if ( c == 'J' ) pad1->PCTempKeyState.RightStickX = 0; + if ( c == 'G' ) pad1->PCTempKeyState.RightStickX = 0; + if ( c == 'Y' ) pad1->PCTempKeyState.RightStickY = 0; + if ( c == 'H' ) pad1->PCTempKeyState.RightStickY = 0; + if ( c == 'Z' ) pad1->PCTempKeyState.LeftShoulder1 = 0; + if ( c == 'X' ) pad1->PCTempKeyState.LeftShoulder2 = 0; + if ( c == 'C' ) pad1->PCTempKeyState.RightShoulder1 = 0; + if ( c == 'V' ) pad1->PCTempKeyState.RightShoulder2 = 0; + if ( c == 'O' ) pad1->PCTempKeyState.DPadUp = 0; + if ( c == 'L' ) pad1->PCTempKeyState.DPadDown = 0; + if ( c == 'K' ) pad1->PCTempKeyState.DPadLeft = 0; + if ( c == ';' ) pad1->PCTempKeyState.DPadRight = 0; + if ( c == 'B' ) pad1->PCTempKeyState.Start = 0; + if ( c == 'N' ) pad1->PCTempKeyState.Select = 0; + if ( c == 'M' ) pad1->PCTempKeyState.Square = 0; + if ( c == ',' ) pad1->PCTempKeyState.Triangle = 0; + if ( c == '.' ) pad1->PCTempKeyState.Cross = 0; + if ( c == '/' ) pad1->PCTempKeyState.Circle = 0; + if ( c == rsRSHIFT ) pad1->PCTempKeyState.LeftShock = 0; + if ( c == rsRCTRL ) pad1->PCTempKeyState.RightShock = 0; + } + } + + return rsEVENTPROCESSED; +} + + +/* + ***************************************************************************** + */ +static RsEventStatus +KeyboardHandler(RsEvent event, void *param) +{ + /* + * ...then the application events, if necessary... + */ + switch( event ) + { + case rsKEYDOWN: + { + return HandleKeyDown((RsKeyStatus *)param); + } + + case rsKEYUP: + { + return HandleKeyUp((RsKeyStatus *)param); + } + + default: + { + return rsEVENTNOTPROCESSED; + } + } +} + +/* + ***************************************************************************** + */ +static RsEventStatus +HandlePadButtonDown(RsPadButtonStatus *padButtonStatus) +{ + bool bPadTwo = false; + int32 padNumber = padButtonStatus->padID; + + CPad *pad = CPad::GetPad(padNumber); + + if ( CPad::m_bMapPadOneToPadTwo ) + padNumber = 1; + + if ( padNumber == 1 ) + bPadTwo = true; + + ControlsManager.UpdateJoyButtonState(padNumber); + + for ( int32 i = 0; i < _TODOCONST(16); i++ ) + { + RsPadButtons btn = rsPADNULL; + if ( ControlsManager.m_aButtonStates[i] == TRUE ) + btn = (RsPadButtons)(i + 1); + + if ( FrontEndMenuManager.m_bMenuActive || bPadTwo ) + ControlsManager.UpdateJoyInConfigMenus_ButtonDown(btn, padNumber); + else + ControlsManager.AffectControllerStateOn_ButtonDown(btn, JOYSTICK); + } + + return rsEVENTPROCESSED; +} + + +/* + ***************************************************************************** + */ +static RsEventStatus +HandlePadButtonUp(RsPadButtonStatus *padButtonStatus) +{ + bool bPadTwo = false; + int32 padNumber = padButtonStatus->padID; + + CPad *pad = CPad::GetPad(padNumber); + + if ( CPad::m_bMapPadOneToPadTwo ) + padNumber = 1; + + if ( padNumber == 1 ) + bPadTwo = true; + + bool bCam = false; + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if ( mode == CCam::MODE_FLYBY || mode == CCam::MODE_FIXED ) + bCam = true; + + ControlsManager.UpdateJoyButtonState(padNumber); + + for ( int32 i = 1; i < _TODOCONST(16); i++ ) + { + RsPadButtons btn = rsPADNULL; + if ( ControlsManager.m_aButtonStates[i] == FALSE ) + btn = (RsPadButtons)(i + 1); // bug ?, cycle begins from 1(not zero), 1+1==2==rsPADBUTTON2, so we skip rsPADBUTTON1, right ? + + if ( FrontEndMenuManager.m_bMenuActive || bPadTwo || bCam ) + ControlsManager.UpdateJoyInConfigMenus_ButtonUp(btn, padNumber); + else + ControlsManager.AffectControllerStateOn_ButtonUp(btn, JOYSTICK); + } + + return rsEVENTPROCESSED; +} + +/* + ***************************************************************************** + */ +static RsEventStatus +PadHandler(RsEvent event, void *param) +{ + switch( event ) + { + case rsPADBUTTONDOWN: + { + return HandlePadButtonDown((RsPadButtonStatus *)param); + } + + case rsPADBUTTONUP: + { + return HandlePadButtonUp((RsPadButtonStatus *)param); + } + + default: + { + return rsEVENTNOTPROCESSED; + } + } +} + + +/* + ***************************************************************************** + */ +RwBool +AttachInputDevices(void) +{ +#ifndef IGNORE_MOUSE_KEYBOARD + RsInputDeviceAttach(rsKEYBOARD, KeyboardHandler); +#endif + + RsInputDeviceAttach(rsPAD, PadHandler); + + return TRUE; +} diff --git a/src/skel/events.h b/src/skel/events.h new file mode 100644 index 0000000..ef812eb --- /dev/null +++ b/src/skel/events.h @@ -0,0 +1,7 @@ +#ifndef EVENTS_H +#define EVENTS_H + +#include +#include "skeleton.h" + +#endif /* EVENTS_H */ diff --git a/src/skel/glfw/glfw.cpp b/src/skel/glfw/glfw.cpp new file mode 100644 index 0000000..92a5a93 --- /dev/null +++ b/src/skel/glfw/glfw.cpp @@ -0,0 +1,2572 @@ +#if defined RW_GL3 && !defined LIBRW_SDL2 + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include + +DWORD _dwOperatingSystemVersion; +#include "resource.h" +#else +long _dwOperatingSystemVersion; +#ifndef __SWITCH__ +#ifndef __APPLE__ +#include +#else +#include +#include +#endif +#endif +#include +#include +#include +#include +#endif + +#include "common.h" +#if (defined(_MSC_VER)) +#include +#endif /* (defined(_MSC_VER)) */ +#include +#include "rwcore.h" +#include "skeleton.h" +#include "platform.h" +#include "crossplatform.h" + +#include "main.h" +#include "FileMgr.h" +#include "Text.h" +#include "Pad.h" +#include "Timer.h" +#include "DMAudio.h" +#include "ControllerConfig.h" +#include "Frontend.h" +#include "Game.h" +#include "PCSave.h" +#include "MemoryCard.h" +#include "Sprite2d.h" +#include "AnimViewer.h" +#include "Font.h" +#include "MemoryMgr.h" + +// This is defined on project-level, via premake5 or cmake +#ifdef GET_KEYBOARD_INPUT_FROM_X11 +#include +#include +#define GLFW_EXPOSE_NATIVE_X11 +#include +#endif + +#ifdef _WIN32 +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#endif + +#define MAX_SUBSYSTEMS (16) + +rw::EngineOpenParams openParams; + +static RwBool ForegroundApp = TRUE; +static RwBool WindowIconified = FALSE; +static RwBool WindowFocused = TRUE; + +static RwBool RwInitialised = FALSE; + +static RwSubSystemInfo GsubSysInfo[MAX_SUBSYSTEMS]; +static RwInt32 GnumSubSystems = 0; +static RwInt32 GcurSel = 0, GcurSelVM = 0; + +static RwBool useDefault; + +// What is that for anyway? +#ifndef IMPROVED_VIDEOMODE +static RwBool defaultFullscreenRes = TRUE; +#else +static RwBool defaultFullscreenRes = FALSE; +static RwInt32 bestWndMode = -1; +#endif + +static psGlobalType PsGlobal; + + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +size_t _dwMemAvailPhys; +RwUInt32 gGameState; + +#ifdef DETECT_JOYSTICK_MENU +char gSelectedJoystickName[128] = ""; +#endif + +/* + ***************************************************************************** + */ +void _psCreateFolder(const char *path) +{ +#ifdef _WIN32 + HANDLE hfle = CreateFile(path, GENERIC_READ, + FILE_SHARE_READ, + nil, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL, + nil); + + if ( hfle == INVALID_HANDLE_VALUE ) + CreateDirectory(path, nil); + else + CloseHandle(hfle); +#else + struct stat info; + char fullpath[PATH_MAX]; + realpath(path, fullpath); + + if (lstat(fullpath, &info) != 0) { + if (errno == ENOENT || (errno != EACCES && !S_ISDIR(info.st_mode))) { + mkdir(fullpath, 0755); + } + } +#endif +} + +/* + ***************************************************************************** + */ +const char *_psGetUserFilesFolder() +{ +#if defined USE_MY_DOCUMENTS && defined _WIN32 + HKEY hKey = NULL; + + static CHAR szUserFiles[256]; + + if ( RegOpenKeyEx(HKEY_CURRENT_USER, + REGSTR_PATH_SPECIAL_FOLDERS, + REG_OPTION_RESERVED, + KEY_READ, + &hKey) == ERROR_SUCCESS ) + { + DWORD KeyType; + DWORD KeycbData = sizeof(szUserFiles); + if ( RegQueryValueEx(hKey, + "Personal", + NULL, + &KeyType, + (LPBYTE)szUserFiles, + &KeycbData) == ERROR_SUCCESS ) + { + RegCloseKey(hKey); + strcat(szUserFiles, "\\GTA3 User Files"); + _psCreateFolder(szUserFiles); + return szUserFiles; + } + + RegCloseKey(hKey); + } + + strcpy(szUserFiles, "data"); + return szUserFiles; +#else + static char szUserFiles[256]; + strcpy(szUserFiles, "userfiles"); + _psCreateFolder(szUserFiles); + return szUserFiles; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psCameraBeginUpdate(RwCamera *camera) +{ + if ( !RwCameraBeginUpdate(Scene.camera) ) + { + ForegroundApp = FALSE; + RsEventHandler(rsACTIVATE, (void *)FALSE); + return FALSE; + } + + return TRUE; +} + +/* + ***************************************************************************** + */ +void +psCameraShowRaster(RwCamera *camera) +{ + if (CMenuManager::m_PrefsVsync) + RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPWAITVSYNC); + else + RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPDONTWAIT); + + return; +} + +/* + ***************************************************************************** + */ +RwImage * +psGrabScreen(RwCamera *pCamera) +{ +#ifndef LIBRW + RwRaster *pRaster = RwCameraGetRaster(pCamera); + if (RwImage *pImage = RwImageCreate(pRaster->width, pRaster->height, 32)) { + RwImageAllocatePixels(pImage); + RwImageSetFromRaster(pImage, pRaster); + return pImage; + } +#else + rw::Image *image = RwCameraGetRaster(pCamera)->toImage(); + image->removeMask(); + if(image) + return image; +#endif + return nil; +} + +/* + ***************************************************************************** + */ +#ifdef _WIN32 +#pragma comment( lib, "Winmm.lib" ) // Needed for time +RwUInt32 +psTimer(void) +{ + RwUInt32 time; + + TIMECAPS TimeCaps; + + timeGetDevCaps(&TimeCaps, sizeof(TIMECAPS)); + + timeBeginPeriod(TimeCaps.wPeriodMin); + + time = (RwUInt32) timeGetTime(); + + timeEndPeriod(TimeCaps.wPeriodMin); + + return time; +} +#else +double +psTimer(void) +{ + struct timespec start; +#if defined(CLOCK_MONOTONIC_RAW) + clock_gettime(CLOCK_MONOTONIC_RAW, &start); +#elif defined(CLOCK_MONOTONIC_FAST) + clock_gettime(CLOCK_MONOTONIC_FAST, &start); +#else + clock_gettime(CLOCK_MONOTONIC, &start); +#endif + return start.tv_sec * 1000.0 + start.tv_nsec/1000000.0; +} +#endif + + +/* + ***************************************************************************** + */ +void +psMouseSetPos(RwV2d *pos) +{ + glfwSetCursorPos(PSGLOBAL(window), pos->x, pos->y); + + PSGLOBAL(lastMousePos.x) = (RwInt32)pos->x; + + PSGLOBAL(lastMousePos.y) = (RwInt32)pos->y; + + return; +} + +/* + ***************************************************************************** + */ +RwMemoryFunctions* +psGetMemoryFunctions(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR + return &memFuncs; +#else + return nil; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psInstallFileSystem(void) +{ + return (TRUE); +} + + +/* + ***************************************************************************** + */ +RwBool +psNativeTextureSupport(void) +{ + return true; +} + +/* + ***************************************************************************** + */ +#ifdef UNDER_CE +#define CMDSTR LPWSTR +#else +#define CMDSTR LPSTR +#endif + +/* + ***************************************************************************** + */ + +#ifdef __SWITCH__ + +static HidVibrationValue SwitchVibrationValues[2]; +static HidVibrationDeviceHandle SwitchVibrationDeviceHandles[2][2]; +static HidVibrationDeviceHandle SwitchVibrationDeviceGC; + +static PadState SwitchPad; + +static Result HidInitializationResult[2]; +static Result HidInitializationGCResult; + +static void _psInitializeVibration() +{ + HidInitializationResult[0] = hidInitializeVibrationDevices(SwitchVibrationDeviceHandles[0], 2, HidNpadIdType_Handheld, HidNpadStyleTag_NpadHandheld); + if(R_FAILED(HidInitializationResult[0])) { + printf("Failed to initialize VibrationDevice for Handheld Mode\n"); + } + HidInitializationResult[1] = hidInitializeVibrationDevices(SwitchVibrationDeviceHandles[1], 2, HidNpadIdType_No1, HidNpadStyleSet_NpadFullCtrl); + if(R_FAILED(HidInitializationResult[1])) { + printf("Failed to initialize VibrationDevice for Detached Mode\n"); + } + HidInitializationGCResult = hidInitializeVibrationDevices(&SwitchVibrationDeviceGC, 1, HidNpadIdType_No1, HidNpadStyleTag_NpadGc); + if(R_FAILED(HidInitializationResult[1])) { + printf("Failed to initialize VibrationDevice for GC Mode\n"); + } + + SwitchVibrationValues[0].freq_low = 160.0f; + SwitchVibrationValues[0].freq_high = 320.0f; + + padConfigureInput(1, HidNpadStyleSet_NpadFullCtrl); + padInitializeDefault(&SwitchPad); +} + +static void _psHandleVibration() +{ + padUpdate(&SwitchPad); + + uint8 target_device = padIsHandheld(&SwitchPad) ? 0 : 1; + + if(R_SUCCEEDED(HidInitializationResult[target_device])) { + CPad* pad = CPad::GetPad(0); + + // value conversion based on SDL2 switch port + SwitchVibrationValues[0].amp_high = SwitchVibrationValues[0].amp_low = pad->ShakeFreq == 0 ? 0.0f : 320.0f; + SwitchVibrationValues[0].freq_low = pad->ShakeFreq == 0.0 ? 160.0f : (float)pad->ShakeFreq * 1.26f; + SwitchVibrationValues[0].freq_high = pad->ShakeFreq == 0.0 ? 320.0f : (float)pad->ShakeFreq * 1.26f; + + if (pad->ShakeDur < CTimer::GetTimeStepInMilliseconds()) + pad->ShakeDur = 0; + else + pad->ShakeDur -= CTimer::GetTimeStepInMilliseconds(); + if (pad->ShakeDur == 0) pad->ShakeFreq = 0; + + + if(target_device == 1 && R_SUCCEEDED(HidInitializationGCResult)) { + // gamecube rumble + hidSendVibrationGcErmCommand(SwitchVibrationDeviceGC, pad->ShakeFreq > 0 ? HidVibrationGcErmCommand_Start : HidVibrationGcErmCommand_Stop); + } + + memcpy(&SwitchVibrationValues[1], &SwitchVibrationValues[0], sizeof(HidVibrationValue)); + hidSendVibrationValues(SwitchVibrationDeviceHandles[target_device], SwitchVibrationValues, 2); + } +} +#else +static void _psInitializeVibration() {} +static void _psHandleVibration() {} +#endif + +/* + ***************************************************************************** + */ +RwBool +psInitialize(void) +{ + PsGlobal.lastMousePos.x = PsGlobal.lastMousePos.y = 0.0f; + + RsGlobal.ps = &PsGlobal; + + PsGlobal.fullScreen = FALSE; + PsGlobal.cursorIsInWindow = FALSE; + WindowFocused = TRUE; + WindowIconified = FALSE; + + PsGlobal.joy1id = -1; + PsGlobal.joy2id = -1; + + CFileMgr::Initialise(); + +#ifdef PS2_MENU + CPad::Initialise(); + CPad::GetPad(0)->Mode = 0; + + CGame::frenchGame = false; + CGame::germanGame = false; + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + +#ifndef _WIN32 + // Mandatory for Linux(Unix? Posix?) to set lang. to environment lang. + setlocale(LC_ALL, ""); + + char *systemLang, *keyboardLang; + + systemLang = setlocale (LC_ALL, NULL); + keyboardLang = setlocale (LC_CTYPE, NULL); + + short lang; + lang = !strncmp(systemLang, "fr_",3) ? LANG_FRENCH : + !strncmp(systemLang, "de_",3) ? LANG_GERMAN : + !strncmp(systemLang, "en_",3) ? LANG_ENGLISH : + !strncmp(systemLang, "it_",3) ? LANG_ITALIAN : + !strncmp(systemLang, "es_",3) ? LANG_SPANISH : + LANG_OTHER; +#else + WORD lang = PRIMARYLANGID(GetSystemDefaultLCID()); +#endif + + if ( lang == LANG_ITALIAN ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + else if ( lang == LANG_SPANISH ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + else if ( lang == LANG_GERMAN ) + { + CGame::germanGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + } + else if ( lang == LANG_FRENCH ) + { + CGame::frenchGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + } + else + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); + + TheMemoryCard.Init(); +#else + C_PcSave::SetSaveDirectory(_psGetUserFilesFolder()); + + InitialiseLanguage(); + +#if GTA_VERSION < GTA3_PC_11 + FrontEndMenuManager.LoadSettings(); +#endif + +#endif + + _psInitializeVibration(); + + gGameState = GS_START_UP; + TRACE("gGameState = GS_START_UP"); +#ifdef _WIN32 + OSVERSIONINFO verInfo; + verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + GetVersionEx(&verInfo); + + _dwOperatingSystemVersion = OS_WIN95; + + if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + if ( verInfo.dwMajorVersion == 4 ) + { + debug("Operating System is WinNT\n"); + _dwOperatingSystemVersion = OS_WINNT; + } + else if ( verInfo.dwMajorVersion == 5 ) + { + debug("Operating System is Win2000\n"); + _dwOperatingSystemVersion = OS_WIN2000; + } + else if ( verInfo.dwMajorVersion > 5 ) + { + debug("Operating System is WinXP or greater\n"); + _dwOperatingSystemVersion = OS_WINXP; + } + } + else if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) + { + if ( verInfo.dwMajorVersion > 4 || verInfo.dwMajorVersion == 4 && verInfo.dwMinorVersion != 0 ) + { + debug("Operating System is Win98\n"); + _dwOperatingSystemVersion = OS_WIN98; + } + else + { + debug("Operating System is Win95\n"); + _dwOperatingSystemVersion = OS_WIN95; + } + } +#else + _dwOperatingSystemVersion = OS_WINXP; // To fool other classes +#endif + + +#ifndef PS2_MENU + +#if GTA_VERSION >= GTA3_PC_11 + FrontEndMenuManager.LoadSettings(); +#endif + +#endif + + +#ifdef _WIN32 + MEMORYSTATUS memstats; + GlobalMemoryStatus(&memstats); + + _dwMemAvailPhys = memstats.dwAvailPhys; + + debug("Physical memory size %u\n", memstats.dwTotalPhys); + debug("Available physical memory %u\n", memstats.dwAvailPhys); +#elif defined (__APPLE__) + uint64_t size = 0; + uint64_t page_size = 0; + size_t uint64_len = sizeof(uint64_t); + size_t ull_len = sizeof(unsigned long long); + sysctl((int[]){CTL_HW, HW_PAGESIZE}, 2, &page_size, &ull_len, NULL, 0); + sysctl((int[]){CTL_HW, HW_MEMSIZE}, 2, &size, &uint64_len, NULL, 0); + vm_statistics_data_t vm_stat; + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vm_stat, &count); + _dwMemAvailPhys = (uint64_t)(vm_stat.free_count * page_size); + debug("Physical memory size %llu\n", _dwMemAvailPhys); + debug("Available physical memory %llu\n", size); +#elif defined (__SWITCH__) + svcGetInfo(&_dwMemAvailPhys, InfoType_UsedMemorySize, CUR_PROCESS_HANDLE, 0); + debug("Physical memory size %llu\n", _dwMemAvailPhys); +#else + struct sysinfo systemInfo; + sysinfo(&systemInfo); + _dwMemAvailPhys = systemInfo.freeram; + debug("Physical memory size %u\n", systemInfo.totalram); + debug("Available physical memory %u\n", systemInfo.freeram); +#endif + + TheText.Unload(); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +void +psTerminate(void) +{ + return; +} + +/* + ***************************************************************************** + */ +static RwChar **_VMList; + +RwInt32 _psGetNumVideModes() +{ + return RwEngineGetNumVideoModes(); +} + +/* + ***************************************************************************** + */ +RwBool _psFreeVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + numModes = _psGetNumVideModes(); + + if ( _VMList == nil ) + return TRUE; + + for ( i = 0; i < numModes; i++ ) + { + RwFree(_VMList[i]); + } + + RwFree(_VMList); + + _VMList = nil; + + return TRUE; +} + +/* + ***************************************************************************** + */ +RwChar **_psGetVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + if ( _VMList != nil ) + { + return _VMList; + } + + numModes = RwEngineGetNumVideoModes(); + + _VMList = (RwChar **)RwCalloc(numModes, sizeof(RwChar*)); + + for ( i = 0; i < numModes; i++ ) + { + RwVideoMode vm; + + RwEngineGetVideoModeInfo(&vm, i); + + if ( vm.flags & rwVIDEOMODEEXCLUSIVE ) + { + _VMList[i] = (RwChar*)RwCalloc(100, sizeof(RwChar)); + rwsprintf(_VMList[i],"%d X %d X %d", vm.width, vm.height, vm.depth); + } + else + _VMList[i] = nil; + } + + return _VMList; +} + +/* + ***************************************************************************** + */ +void _psSelectScreenVM(RwInt32 videoMode) +{ + RwTexDictionarySetCurrent( nil ); + + FrontEndMenuManager.UnloadTextures(); + + if (!_psSetVideoMode(RwEngineGetCurrentSubSystem(), videoMode)) + { + RsGlobal.quit = TRUE; + + printf("ERROR: Failed to select new screen resolution\n"); + } + else + FrontEndMenuManager.LoadAllTextures(); +} + +/* + ***************************************************************************** + */ + +RwBool IsForegroundApp() +{ + return !!ForegroundApp; +} +/* +UINT GetBestRefreshRate(UINT width, UINT height, UINT depth) +{ + LPDIRECT3D8 d3d = Direct3DCreate8(D3D_SDK_VERSION); + + ASSERT(d3d != nil); + + UINT refreshRate = INT_MAX; + D3DFORMAT format; + + if ( depth == 32 ) + format = D3DFMT_X8R8G8B8; + else if ( depth == 24 ) + format = D3DFMT_R8G8B8; + else + format = D3DFMT_R5G6B5; + + UINT modeCount = d3d->GetAdapterModeCount(GcurSel); + + for ( UINT i = 0; i < modeCount; i++ ) + { + D3DDISPLAYMODE mode; + + d3d->EnumAdapterModes(GcurSel, i, &mode); + + if ( mode.Width == width && mode.Height == height && mode.Format == format ) + { + if ( mode.RefreshRate == 0 ) + return 0; + + if ( mode.RefreshRate < refreshRate && mode.RefreshRate >= 60 ) + refreshRate = mode.RefreshRate; + } + } + +#ifdef FIX_BUGS + d3d->Release(); +#endif + + if ( refreshRate == -1 ) + return -1; + + return refreshRate; +} +*/ +/* + ***************************************************************************** + */ +RwBool +psSelectDevice() +{ + RwVideoMode vm; + RwInt32 subSysNum; + RwInt32 AutoRenderer = 0; + + + RwBool modeFound = FALSE; + + if ( !useDefault ) + { + GnumSubSystems = RwEngineGetNumSubSystems(); + if ( !GnumSubSystems ) + { + return FALSE; + } + + /* Just to be sure ... */ + GnumSubSystems = (GnumSubSystems > MAX_SUBSYSTEMS) ? MAX_SUBSYSTEMS : GnumSubSystems; + + /* Get the names of all the sub systems */ + for (subSysNum = 0; subSysNum < GnumSubSystems; subSysNum++) + { + RwEngineGetSubSystemInfo(&GsubSysInfo[subSysNum], subSysNum); + } + + /* Get the default selection */ + GcurSel = RwEngineGetCurrentSubSystem(); +#ifdef IMPROVED_VIDEOMODE + if(FrontEndMenuManager.m_nPrefsSubsystem < GnumSubSystems) + GcurSel = FrontEndMenuManager.m_nPrefsSubsystem; +#endif + } + + /* Set the driver to use the correct sub system */ + if (!RwEngineSetSubSystem(GcurSel)) + { + return FALSE; + } + +#ifdef IMPROVED_VIDEOMODE + FrontEndMenuManager.m_nPrefsSubsystem = GcurSel; +#endif + +#ifndef IMPROVED_VIDEOMODE + if ( !useDefault ) + { + if ( _psGetVideoModeList()[FrontEndMenuManager.m_nDisplayVideoMode] && FrontEndMenuManager.m_nDisplayVideoMode ) + { + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + GcurSelVM = FrontEndMenuManager.m_nDisplayVideoMode; + } + else + { +#ifdef DEFAULT_NATIVE_RESOLUTION + // get the native video mode + HDC hDevice = GetDC(NULL); + int w = GetDeviceCaps(hDevice, HORZRES); + int h = GetDeviceCaps(hDevice, VERTRES); + int d = GetDeviceCaps(hDevice, BITSPIXEL); +#else + const int w = 640; + const int h = 480; + const int d = 16; +#endif + while ( !modeFound && GcurSelVM < RwEngineGetNumVideoModes() ) + { + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + if ( defaultFullscreenRes && vm.width != w + || vm.height != h + || vm.depth != d + || !(vm.flags & rwVIDEOMODEEXCLUSIVE) ) + ++GcurSelVM; + else + modeFound = TRUE; + } + + if ( !modeFound ) + { +#ifdef DEFAULT_NATIVE_RESOLUTION + GcurSelVM = 1; +#else + printf("WARNING: Cannot find 640x480 video mode, selecting device cancelled\n"); + return FALSE; +#endif + } + } + } +#else + if ( !useDefault ) + { + if(FrontEndMenuManager.m_nPrefsWidth == 0 || + FrontEndMenuManager.m_nPrefsHeight == 0 || + FrontEndMenuManager.m_nPrefsDepth == 0){ + // Defaults if nothing specified + const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + FrontEndMenuManager.m_nPrefsWidth = mode->width; + FrontEndMenuManager.m_nPrefsHeight = mode->height; + FrontEndMenuManager.m_nPrefsDepth = 32; + FrontEndMenuManager.m_nPrefsWindowed = 0; + } + + // Find the videomode that best fits what we got from the settings file + RwInt32 bestFsMode = -1; + RwInt32 bestWidth = -1; + RwInt32 bestHeight = -1; + RwInt32 bestDepth = -1; + for(GcurSelVM = 0; GcurSelVM < RwEngineGetNumVideoModes(); GcurSelVM++){ + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + if (!(vm.flags & rwVIDEOMODEEXCLUSIVE)){ + bestWndMode = GcurSelVM; + } else { + // try the largest one that isn't larger than what we wanted + if(vm.width >= bestWidth && vm.width <= FrontEndMenuManager.m_nPrefsWidth && + vm.height >= bestHeight && vm.height <= FrontEndMenuManager.m_nPrefsHeight && + vm.depth >= bestDepth && vm.depth <= FrontEndMenuManager.m_nPrefsDepth){ + bestWidth = vm.width; + bestHeight = vm.height; + bestDepth = vm.depth; + bestFsMode = GcurSelVM; + } + } + } + + if(bestFsMode < 0){ + printf("WARNING: Cannot find desired video mode, selecting device cancelled\n"); + return FALSE; + } + GcurSelVM = bestFsMode; + + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + + FrontEndMenuManager.m_nSelectedScreenMode = FrontEndMenuManager.m_nPrefsWindowed; + } +#endif + + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + +#ifdef IMPROVED_VIDEOMODE + if (FrontEndMenuManager.m_nPrefsWindowed) + GcurSelVM = bestWndMode; + + // Now GcurSelVM is 0 but vm has sizes(and fullscreen flag) of the video mode we want, that's why we changed the rwVIDEOMODEEXCLUSIVE conditions below + FrontEndMenuManager.m_nPrefsWidth = vm.width; + FrontEndMenuManager.m_nPrefsHeight = vm.height; + FrontEndMenuManager.m_nPrefsDepth = vm.depth; +#endif + +#ifndef PS2_MENU + FrontEndMenuManager.m_nCurrOption = 0; +#endif + + /* Set up the video mode and set the apps window + * dimensions to match */ + if (!RwEngineSetVideoMode(GcurSelVM)) + { + return FALSE; + } + /* + TODO + if (vm.flags & rwVIDEOMODEEXCLUSIVE) + { + debug("%dx%dx%d", vm.width, vm.height, vm.depth); + + UINT refresh = GetBestRefreshRate(vm.width, vm.height, vm.depth); + + if ( refresh != (UINT)-1 ) + { + debug("refresh %d", refresh); + RwD3D8EngineSetRefreshRate((RwUInt32)refresh); + } + } + */ +#ifndef IMPROVED_VIDEOMODE + if (vm.flags & rwVIDEOMODEEXCLUSIVE) + { + RsGlobal.maximumWidth = vm.width; + RsGlobal.maximumHeight = vm.height; + RsGlobal.width = vm.width; + RsGlobal.height = vm.height; + + PSGLOBAL(fullScreen) = TRUE; + } +#else + RsGlobal.maximumWidth = FrontEndMenuManager.m_nPrefsWidth; + RsGlobal.maximumHeight = FrontEndMenuManager.m_nPrefsHeight; + RsGlobal.width = FrontEndMenuManager.m_nPrefsWidth; + RsGlobal.height = FrontEndMenuManager.m_nPrefsHeight; + + PSGLOBAL(fullScreen) = !FrontEndMenuManager.m_nPrefsWindowed; +#endif + +#ifdef MULTISAMPLING + RwD3D8EngineSetMultiSamplingLevels(1 << FrontEndMenuManager.m_nPrefsMSAALevel); +#endif + return TRUE; +} + +#ifndef GET_KEYBOARD_INPUT_FROM_X11 +void keypressCB(GLFWwindow* window, int key, int scancode, int action, int mods); +#endif +void resizeCB(GLFWwindow* window, int width, int height); +void scrollCB(GLFWwindow* window, double xoffset, double yoffset); +void cursorCB(GLFWwindow* window, double xpos, double ypos); +void cursorEnterCB(GLFWwindow* window, int entered); +void windowFocusCB(GLFWwindow* window, int focused); +void windowIconifyCB(GLFWwindow* window, int iconified); +void joysChangeCB(int jid, int event); + +bool IsThisJoystickBlacklisted(int i) +{ +#ifndef DETECT_JOYSTICK_MENU + return false; +#else + if (glfwJoystickIsGamepad(i)) + return false; + + const char* joyname = glfwGetJoystickName(i); + + if (gSelectedJoystickName[0] != '\0' && + strncmp(joyname, gSelectedJoystickName, strlen(gSelectedJoystickName)) == 0) + return false; + + return true; +#endif +} + +void _InputInitialiseJoys() +{ + PSGLOBAL(joy1id) = -1; + PSGLOBAL(joy2id) = -1; + + // Load our gamepad mappings. +#define SDL_GAMEPAD_DB_PATH "gamecontrollerdb.txt" + FILE *f = fopen(SDL_GAMEPAD_DB_PATH, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + size_t fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + char *db = (char*)malloc(fsize + 1); + if (fread(db, 1, fsize, f) == fsize) { + db[fsize] = '\0'; + + if (glfwUpdateGamepadMappings(db) == GLFW_FALSE) + Error("glfwUpdateGamepadMappings didn't succeed, check " SDL_GAMEPAD_DB_PATH ".\n"); + } else + Error("fread on " SDL_GAMEPAD_DB_PATH " wasn't successful.\n"); + + free(db); + fclose(f); + } else + printf("You don't seem to have copied " SDL_GAMEPAD_DB_PATH " file from re3/gamefiles to GTA3 directory. Some gamepads may not be recognized.\n"); + +#undef SDL_GAMEPAD_DB_PATH + + // But always overwrite it with the one in SDL_GAMECONTROLLERCONFIG. + char const* EnvControlConfig = getenv("SDL_GAMECONTROLLERCONFIG"); + if (EnvControlConfig != nil) { + glfwUpdateGamepadMappings(EnvControlConfig); + } + + for (int i = 0; i <= GLFW_JOYSTICK_LAST; i++) { + if (glfwJoystickPresent(i) && !IsThisJoystickBlacklisted(i)) { + if (PSGLOBAL(joy1id) == -1) + PSGLOBAL(joy1id) = i; + else if (PSGLOBAL(joy2id) == -1) + PSGLOBAL(joy2id) = i; + else + break; + } + } + + if (PSGLOBAL(joy1id) != -1) { + int count; + glfwGetJoystickButtons(PSGLOBAL(joy1id), &count); +#ifdef DETECT_JOYSTICK_MENU + strcpy(gSelectedJoystickName, glfwGetJoystickName(PSGLOBAL(joy1id))); +#endif + ControlsManager.InitDefaultControlConfigJoyPad(count); + } +} + +long _InputInitialiseMouse() +{ + glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + return 0; +} + +void psPostRWinit(void) +{ + RwVideoMode vm; + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + glfwSetFramebufferSizeCallback(PSGLOBAL(window), resizeCB); +#ifndef IGNORE_MOUSE_KEYBOARD +#ifndef GET_KEYBOARD_INPUT_FROM_X11 + glfwSetKeyCallback(PSGLOBAL(window), keypressCB); +#endif + glfwSetScrollCallback(PSGLOBAL(window), scrollCB); + glfwSetCursorPosCallback(PSGLOBAL(window), cursorCB); + glfwSetCursorEnterCallback(PSGLOBAL(window), cursorEnterCB); +#endif + glfwSetWindowIconifyCallback(PSGLOBAL(window), windowIconifyCB); + glfwSetWindowFocusCallback(PSGLOBAL(window), windowFocusCB); + glfwSetJoystickCallback(joysChangeCB); + + _InputInitialiseJoys(); + _InputInitialiseMouse(); + + if(!(vm.flags & rwVIDEOMODEEXCLUSIVE)) + glfwSetWindowSize(PSGLOBAL(window), RsGlobal.maximumWidth, RsGlobal.maximumHeight); + + // Make sure all keys are released + CPad::GetPad(0)->Clear(true); + CPad::GetPad(1)->Clear(true); +} + +/* + ***************************************************************************** + */ +RwBool _psSetVideoMode(RwInt32 subSystem, RwInt32 videoMode) +{ + RwInitialised = FALSE; + + RsEventHandler(rsRWTERMINATE, nil); + + GcurSel = subSystem; + GcurSelVM = videoMode; + + useDefault = TRUE; + + if ( RsEventHandler(rsRWINITIALIZE, &openParams) == rsEVENTERROR ) + return FALSE; + + RwInitialised = TRUE; + useDefault = FALSE; + + RwRect r; + + r.x = 0; + r.y = 0; + r.w = RsGlobal.maximumWidth; + r.h = RsGlobal.maximumHeight; + + RsEventHandler(rsCAMERASIZE, &r); + + psPostRWinit(); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +static RwChar ** +CommandLineToArgv(RwChar *cmdLine, RwInt32 *argCount) +{ + RwInt32 numArgs = 0; + RwBool inArg, inString; + RwInt32 i, len; + RwChar *res, *str, **aptr; + + len = strlen(cmdLine); + + /* + * Count the number of arguments... + */ + inString = FALSE; + inArg = FALSE; + + for(i=0; i<=len; i++) + { + if( cmdLine[i] == '"' ) + { + inString = !inString; + } + + if( (cmdLine[i] <= ' ' && !inString) || i == len ) + { + if( inArg ) + { + inArg = FALSE; + + numArgs++; + } + } + else if( !inArg ) + { + inArg = TRUE; + } + } + + /* + * Allocate memory for result... + */ + res = (RwChar *)malloc(sizeof(RwChar *) * numArgs + len + 1); + str = res + sizeof(RwChar *) * numArgs; + aptr = (RwChar **)res; + + strcpy(str, cmdLine); + + /* + * Walk through cmdLine again this time setting pointer to each arg... + */ + inArg = FALSE; + inString = FALSE; + + for(i=0; i<=len; i++) + { + if( cmdLine[i] == '"' ) + { + inString = !inString; + } + + if( (cmdLine[i] <= ' ' && !inString) || i == len ) + { + if( inArg ) + { + if( str[i-1] == '"' ) + { + str[i-1] = '\0'; + } + else + { + str[i] = '\0'; + } + + inArg = FALSE; + } + } + else if( !inArg && cmdLine[i] != '"' ) + { + inArg = TRUE; + + *aptr++ = &str[i]; + } + } + + *argCount = numArgs; + + return (RwChar **)res; +} + +/* + ***************************************************************************** + */ +void InitialiseLanguage() +{ +#ifndef _WIN32 + // Mandatory for Linux(Unix? Posix?) to set lang. to environment lang. + setlocale(LC_ALL, ""); + + char *systemLang, *keyboardLang; + + systemLang = setlocale (LC_ALL, NULL); + keyboardLang = setlocale (LC_CTYPE, NULL); + + short primUserLCID, primSystemLCID; + primUserLCID = primSystemLCID = !strncmp(systemLang, "fr_",3) ? LANG_FRENCH : + !strncmp(systemLang, "de_",3) ? LANG_GERMAN : + !strncmp(systemLang, "en_",3) ? LANG_ENGLISH : + !strncmp(systemLang, "it_",3) ? LANG_ITALIAN : + !strncmp(systemLang, "es_",3) ? LANG_SPANISH : + LANG_OTHER; + + short primLayout = !strncmp(keyboardLang, "fr_",3) ? LANG_FRENCH : (!strncmp(keyboardLang, "de_",3) ? LANG_GERMAN : LANG_ENGLISH); + + short subUserLCID, subSystemLCID; + subUserLCID = subSystemLCID = !strncmp(systemLang, "en_AU",5) ? SUBLANG_ENGLISH_AUS : SUBLANG_OTHER; + short subLayout = !strncmp(keyboardLang, "en_AU",5) ? SUBLANG_ENGLISH_AUS : SUBLANG_OTHER; + +#else + WORD primUserLCID = PRIMARYLANGID(GetSystemDefaultLCID()); + WORD primSystemLCID = PRIMARYLANGID(GetUserDefaultLCID()); + WORD primLayout = PRIMARYLANGID((DWORD)GetKeyboardLayout(0)); + + WORD subUserLCID = SUBLANGID(GetSystemDefaultLCID()); + WORD subSystemLCID = SUBLANGID(GetUserDefaultLCID()); + WORD subLayout = SUBLANGID((DWORD)GetKeyboardLayout(0)); +#endif + if ( primUserLCID == LANG_GERMAN + || primSystemLCID == LANG_GERMAN + || primLayout == LANG_GERMAN ) + { + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CGame::germanGame = true; + } + + if ( primUserLCID == LANG_FRENCH + || primSystemLCID == LANG_FRENCH + || primLayout == LANG_FRENCH ) + { + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CGame::frenchGame = true; + } + + if ( subUserLCID == SUBLANG_ENGLISH_AUS + || subSystemLCID == SUBLANG_ENGLISH_AUS + || subLayout == SUBLANG_ENGLISH_AUS ) + CGame::noProstitutes = true; + +#ifdef NASTY_GAME + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + CGame::noProstitutes = false; +#endif + + int32 lang; + + switch ( primSystemLCID ) + { + case LANG_GERMAN: + { + lang = LANG_GERMAN; + break; + } + case LANG_FRENCH: + { + lang = LANG_FRENCH; + break; + } + case LANG_SPANISH: + { + lang = LANG_SPANISH; + break; + } + case LANG_ITALIAN: + { + lang = LANG_ITALIAN; + break; + } + default: + { + lang = ( subSystemLCID == SUBLANG_ENGLISH_AUS ) ? -99 : LANG_ENGLISH; + break; + } + } + + CMenuManager::OS_Language = primUserLCID; + + switch ( lang ) + { + case LANG_GERMAN: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + break; + } + case LANG_SPANISH: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + break; + } + case LANG_FRENCH: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + break; + } + case LANG_ITALIAN: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + break; + } + default: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + break; + } + } + +#ifndef _WIN32 + // TODO this is needed for strcasecmp to work correctly across all languages, but can these cause other problems?? + setlocale(LC_CTYPE, "C"); + setlocale(LC_COLLATE, "C"); + setlocale(LC_NUMERIC, "C"); +#endif + + TheText.Unload(); + TheText.Load(); +} + +/* + ***************************************************************************** + */ + +void HandleExit() +{ +#ifdef _WIN32 + MSG message; + while ( PeekMessage(&message, nil, 0U, 0U, PM_REMOVE|PM_NOYIELD) ) + { + if( message.message == WM_QUIT ) + { + RsGlobal.quit = TRUE; + } + else + { + TranslateMessage(&message); + DispatchMessage(&message); + } + } +#else + // We now handle terminate message always, why handle on some cases? + return; +#endif +} + +#ifndef _WIN32 +void terminateHandler(int sig, siginfo_t *info, void *ucontext) { + RsGlobal.quit = TRUE; +} + +#ifdef FLUSHABLE_STREAMING +void dummyHandler(int sig){ + // Don't kill the app pls +} +#endif +#endif + +void resizeCB(GLFWwindow* window, int width, int height) { + /* + * Handle event to ensure window contents are displayed during re-size + * as this can be disabled by the user, then if there is not enough + * memory things don't work. + */ + /* redraw window */ + + if (RwInitialised && gGameState == GS_PLAYING_GAME) + { + RsEventHandler(rsIDLE, (void *)TRUE); + } + + if (RwInitialised && height > 0 && width > 0) { + RwRect r; + + // TODO fix artifacts of resizing with mouse + RsGlobal.maximumHeight = height; + RsGlobal.maximumWidth = width; + + r.x = 0; + r.y = 0; + r.w = width; + r.h = height; + + RsEventHandler(rsCAMERASIZE, &r); + } +// glfwSetWindowPos(window, 0, 0); +} + +void scrollCB(GLFWwindow* window, double xoffset, double yoffset) { + PSGLOBAL(mouseWheel) = yoffset; +} + +bool lshiftStatus = false; +bool rshiftStatus = false; + +#ifndef GET_KEYBOARD_INPUT_FROM_X11 +int keymap[GLFW_KEY_LAST + 1]; + +static void +initkeymap(void) +{ + int i; + for (i = 0; i < GLFW_KEY_LAST + 1; i++) + keymap[i] = rsNULL; + + keymap[GLFW_KEY_SPACE] = ' '; + keymap[GLFW_KEY_APOSTROPHE] = '\''; + keymap[GLFW_KEY_COMMA] = ','; + keymap[GLFW_KEY_MINUS] = '-'; + keymap[GLFW_KEY_PERIOD] = '.'; + keymap[GLFW_KEY_SLASH] = '/'; + keymap[GLFW_KEY_0] = '0'; + keymap[GLFW_KEY_1] = '1'; + keymap[GLFW_KEY_2] = '2'; + keymap[GLFW_KEY_3] = '3'; + keymap[GLFW_KEY_4] = '4'; + keymap[GLFW_KEY_5] = '5'; + keymap[GLFW_KEY_6] = '6'; + keymap[GLFW_KEY_7] = '7'; + keymap[GLFW_KEY_8] = '8'; + keymap[GLFW_KEY_9] = '9'; + keymap[GLFW_KEY_SEMICOLON] = ';'; + keymap[GLFW_KEY_EQUAL] = '='; + keymap[GLFW_KEY_A] = 'A'; + keymap[GLFW_KEY_B] = 'B'; + keymap[GLFW_KEY_C] = 'C'; + keymap[GLFW_KEY_D] = 'D'; + keymap[GLFW_KEY_E] = 'E'; + keymap[GLFW_KEY_F] = 'F'; + keymap[GLFW_KEY_G] = 'G'; + keymap[GLFW_KEY_H] = 'H'; + keymap[GLFW_KEY_I] = 'I'; + keymap[GLFW_KEY_J] = 'J'; + keymap[GLFW_KEY_K] = 'K'; + keymap[GLFW_KEY_L] = 'L'; + keymap[GLFW_KEY_M] = 'M'; + keymap[GLFW_KEY_N] = 'N'; + keymap[GLFW_KEY_O] = 'O'; + keymap[GLFW_KEY_P] = 'P'; + keymap[GLFW_KEY_Q] = 'Q'; + keymap[GLFW_KEY_R] = 'R'; + keymap[GLFW_KEY_S] = 'S'; + keymap[GLFW_KEY_T] = 'T'; + keymap[GLFW_KEY_U] = 'U'; + keymap[GLFW_KEY_V] = 'V'; + keymap[GLFW_KEY_W] = 'W'; + keymap[GLFW_KEY_X] = 'X'; + keymap[GLFW_KEY_Y] = 'Y'; + keymap[GLFW_KEY_Z] = 'Z'; + keymap[GLFW_KEY_LEFT_BRACKET] = '['; + keymap[GLFW_KEY_BACKSLASH] = '\\'; + keymap[GLFW_KEY_RIGHT_BRACKET] = ']'; + keymap[GLFW_KEY_GRAVE_ACCENT] = '`'; + keymap[GLFW_KEY_ESCAPE] = rsESC; + keymap[GLFW_KEY_ENTER] = rsENTER; + keymap[GLFW_KEY_TAB] = rsTAB; + keymap[GLFW_KEY_BACKSPACE] = rsBACKSP; + keymap[GLFW_KEY_INSERT] = rsINS; + keymap[GLFW_KEY_DELETE] = rsDEL; + keymap[GLFW_KEY_RIGHT] = rsRIGHT; + keymap[GLFW_KEY_LEFT] = rsLEFT; + keymap[GLFW_KEY_DOWN] = rsDOWN; + keymap[GLFW_KEY_UP] = rsUP; + keymap[GLFW_KEY_PAGE_UP] = rsPGUP; + keymap[GLFW_KEY_PAGE_DOWN] = rsPGDN; + keymap[GLFW_KEY_HOME] = rsHOME; + keymap[GLFW_KEY_END] = rsEND; + keymap[GLFW_KEY_CAPS_LOCK] = rsCAPSLK; + keymap[GLFW_KEY_SCROLL_LOCK] = rsSCROLL; + keymap[GLFW_KEY_NUM_LOCK] = rsNUMLOCK; + keymap[GLFW_KEY_PRINT_SCREEN] = rsNULL; + keymap[GLFW_KEY_PAUSE] = rsPAUSE; + + keymap[GLFW_KEY_F1] = rsF1; + keymap[GLFW_KEY_F2] = rsF2; + keymap[GLFW_KEY_F3] = rsF3; + keymap[GLFW_KEY_F4] = rsF4; + keymap[GLFW_KEY_F5] = rsF5; + keymap[GLFW_KEY_F6] = rsF6; + keymap[GLFW_KEY_F7] = rsF7; + keymap[GLFW_KEY_F8] = rsF8; + keymap[GLFW_KEY_F9] = rsF9; + keymap[GLFW_KEY_F10] = rsF10; + keymap[GLFW_KEY_F11] = rsF11; + keymap[GLFW_KEY_F12] = rsF12; + keymap[GLFW_KEY_F13] = rsNULL; + keymap[GLFW_KEY_F14] = rsNULL; + keymap[GLFW_KEY_F15] = rsNULL; + keymap[GLFW_KEY_F16] = rsNULL; + keymap[GLFW_KEY_F17] = rsNULL; + keymap[GLFW_KEY_F18] = rsNULL; + keymap[GLFW_KEY_F19] = rsNULL; + keymap[GLFW_KEY_F20] = rsNULL; + keymap[GLFW_KEY_F21] = rsNULL; + keymap[GLFW_KEY_F22] = rsNULL; + keymap[GLFW_KEY_F23] = rsNULL; + keymap[GLFW_KEY_F24] = rsNULL; + keymap[GLFW_KEY_F25] = rsNULL; + keymap[GLFW_KEY_KP_0] = rsPADINS; + keymap[GLFW_KEY_KP_1] = rsPADEND; + keymap[GLFW_KEY_KP_2] = rsPADDOWN; + keymap[GLFW_KEY_KP_3] = rsPADPGDN; + keymap[GLFW_KEY_KP_4] = rsPADLEFT; + keymap[GLFW_KEY_KP_5] = rsPAD5; + keymap[GLFW_KEY_KP_6] = rsPADRIGHT; + keymap[GLFW_KEY_KP_7] = rsPADHOME; + keymap[GLFW_KEY_KP_8] = rsPADUP; + keymap[GLFW_KEY_KP_9] = rsPADPGUP; + keymap[GLFW_KEY_KP_DECIMAL] = rsPADDEL; + keymap[GLFW_KEY_KP_DIVIDE] = rsDIVIDE; + keymap[GLFW_KEY_KP_MULTIPLY] = rsTIMES; + keymap[GLFW_KEY_KP_SUBTRACT] = rsMINUS; + keymap[GLFW_KEY_KP_ADD] = rsPLUS; + keymap[GLFW_KEY_KP_ENTER] = rsPADENTER; + keymap[GLFW_KEY_KP_EQUAL] = rsNULL; + keymap[GLFW_KEY_LEFT_SHIFT] = rsLSHIFT; + keymap[GLFW_KEY_LEFT_CONTROL] = rsLCTRL; + keymap[GLFW_KEY_LEFT_ALT] = rsLALT; + keymap[GLFW_KEY_LEFT_SUPER] = rsLWIN; + keymap[GLFW_KEY_RIGHT_SHIFT] = rsRSHIFT; + keymap[GLFW_KEY_RIGHT_CONTROL] = rsRCTRL; + keymap[GLFW_KEY_RIGHT_ALT] = rsRALT; + keymap[GLFW_KEY_RIGHT_SUPER] = rsRWIN; + keymap[GLFW_KEY_MENU] = rsNULL; +} + +void +keypressCB(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + if (key >= 0 && key <= GLFW_KEY_LAST && action != GLFW_REPEAT) { + RsKeyCodes ks = (RsKeyCodes)keymap[key]; + + if (key == GLFW_KEY_LEFT_SHIFT) + lshiftStatus = action != GLFW_RELEASE; + + if (key == GLFW_KEY_RIGHT_SHIFT) + rshiftStatus = action != GLFW_RELEASE; + + if (action == GLFW_RELEASE) RsKeyboardEventHandler(rsKEYUP, &ks); + else if (action == GLFW_PRESS) RsKeyboardEventHandler(rsKEYDOWN, &ks); + } +} + +#else + +uint32 keymap[512]; // 256 ascii + 256 KeySyms between 0xff00 - 0xffff +bool keyStates[512]; +uint32 keyCodeToKeymapIndex[256]; // cache for physical keys + +#define KEY_MAP_OFFSET (0xff00 - 256) +static void +initkeymap(void) +{ + Display *display = glfwGetX11Display(); + int i; + + for (i = 0; i < ARRAY_SIZE(keymap); i++) + keymap[i] = rsNULL; + + // You can add new ASCII mappings to here freely (but beware that if right hand side of assignment isn't supported on CFont, it'll be blank/won't work on binding screen) + // Right hand side of assigments should always be uppercase counterpart of character + keymap[XK_space] = ' '; + keymap[XK_apostrophe] = '\''; + keymap[XK_ampersand] = '&'; + keymap[XK_percent] = '%'; + keymap[XK_dollar] = '$'; + keymap[XK_comma] = ','; + keymap[XK_minus] = '-'; + keymap[XK_period] = '.'; + keymap[XK_slash] = '/'; + keymap[XK_question] = '?'; + keymap[XK_exclam] = '!'; + keymap[XK_quotedbl] = '"'; + keymap[XK_colon] = ':'; + keymap[XK_semicolon] = ';'; + keymap[XK_equal] = '='; + keymap[XK_bracketleft] = '['; + keymap[XK_backslash] = '\\'; + keymap[XK_bracketright] = ']'; + keymap[XK_grave] = '`'; + keymap[XK_0] = '0'; + keymap[XK_1] = '1'; + keymap[XK_2] = '2'; + keymap[XK_3] = '3'; + keymap[XK_4] = '4'; + keymap[XK_5] = '5'; + keymap[XK_6] = '6'; + keymap[XK_7] = '7'; + keymap[XK_8] = '8'; + keymap[XK_9] = '9'; + keymap[XK_a] = 'A'; + keymap[XK_b] = 'B'; + keymap[XK_c] = 'C'; + keymap[XK_d] = 'D'; + keymap[XK_e] = 'E'; + keymap[XK_f] = 'F'; + keymap[XK_g] = 'G'; + keymap[XK_h] = 'H'; + keymap[XK_i] = 'I'; + keymap[XK_I] = 'I'; // Turkish I problem + keymap[XK_j] = 'J'; + keymap[XK_k] = 'K'; + keymap[XK_l] = 'L'; + keymap[XK_m] = 'M'; + keymap[XK_n] = 'N'; + keymap[XK_o] = 'O'; + keymap[XK_p] = 'P'; + keymap[XK_q] = 'Q'; + keymap[XK_r] = 'R'; + keymap[XK_s] = 'S'; + keymap[XK_t] = 'T'; + keymap[XK_u] = 'U'; + keymap[XK_v] = 'V'; + keymap[XK_w] = 'W'; + keymap[XK_x] = 'X'; + keymap[XK_y] = 'Y'; + keymap[XK_z] = 'Z'; + + // Some of regional but ASCII characters that GTA supports + keymap[XK_agrave] = 0x00c0; + keymap[XK_aacute] = 0x00c1; + keymap[XK_acircumflex] = 0x00c2; + keymap[XK_adiaeresis] = 0x00c4; + + keymap[XK_ae] = 0x00c6; + + keymap[XK_egrave] = 0x00c8; + keymap[XK_eacute] = 0x00c9; + keymap[XK_ecircumflex] = 0x00ca; + keymap[XK_ediaeresis] = 0x00cb; + + keymap[XK_igrave] = 0x00cc; + keymap[XK_iacute] = 0x00cd; + keymap[XK_icircumflex] = 0x00ce; + keymap[XK_idiaeresis] = 0x00cf; + + keymap[XK_ccedilla] = 0x00c7; + keymap[XK_odiaeresis] = 0x00d6; + keymap[XK_udiaeresis] = 0x00dc; + + // These are 0xff00 - 0xffff range of KeySym's, and subtracting KEY_MAP_OFFSET is needed + keymap[XK_Escape - KEY_MAP_OFFSET] = rsESC; + keymap[XK_Return - KEY_MAP_OFFSET] = rsENTER; + keymap[XK_Tab - KEY_MAP_OFFSET] = rsTAB; + keymap[XK_BackSpace - KEY_MAP_OFFSET] = rsBACKSP; + keymap[XK_Insert - KEY_MAP_OFFSET] = rsINS; + keymap[XK_Delete - KEY_MAP_OFFSET] = rsDEL; + keymap[XK_Right - KEY_MAP_OFFSET] = rsRIGHT; + keymap[XK_Left - KEY_MAP_OFFSET] = rsLEFT; + keymap[XK_Down - KEY_MAP_OFFSET] = rsDOWN; + keymap[XK_Up - KEY_MAP_OFFSET] = rsUP; + keymap[XK_Page_Up - KEY_MAP_OFFSET] = rsPGUP; + keymap[XK_Page_Down - KEY_MAP_OFFSET] = rsPGDN; + keymap[XK_Home - KEY_MAP_OFFSET] = rsHOME; + keymap[XK_End - KEY_MAP_OFFSET] = rsEND; + keymap[XK_Caps_Lock - KEY_MAP_OFFSET] = rsCAPSLK; + keymap[XK_Scroll_Lock - KEY_MAP_OFFSET] = rsSCROLL; + keymap[XK_Num_Lock - KEY_MAP_OFFSET] = rsNUMLOCK; + keymap[XK_Pause - KEY_MAP_OFFSET] = rsPAUSE; + + keymap[XK_F1 - KEY_MAP_OFFSET] = rsF1; + keymap[XK_F2 - KEY_MAP_OFFSET] = rsF2; + keymap[XK_F3 - KEY_MAP_OFFSET] = rsF3; + keymap[XK_F4 - KEY_MAP_OFFSET] = rsF4; + keymap[XK_F5 - KEY_MAP_OFFSET] = rsF5; + keymap[XK_F6 - KEY_MAP_OFFSET] = rsF6; + keymap[XK_F7 - KEY_MAP_OFFSET] = rsF7; + keymap[XK_F8 - KEY_MAP_OFFSET] = rsF8; + keymap[XK_F9 - KEY_MAP_OFFSET] = rsF9; + keymap[XK_F10 - KEY_MAP_OFFSET] = rsF10; + keymap[XK_F11 - KEY_MAP_OFFSET] = rsF11; + keymap[XK_F12 - KEY_MAP_OFFSET] = rsF12; + keymap[XK_F13 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F14 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F15 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F16 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F17 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F18 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F19 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F20 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F21 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F22 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F23 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F24 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F25 - KEY_MAP_OFFSET] = rsNULL; + + keymap[XK_KP_0 - KEY_MAP_OFFSET] = rsPADINS; + keymap[XK_KP_1 - KEY_MAP_OFFSET] = rsPADEND; + keymap[XK_KP_2 - KEY_MAP_OFFSET] = rsPADDOWN; + keymap[XK_KP_3 - KEY_MAP_OFFSET] = rsPADPGDN; + keymap[XK_KP_4 - KEY_MAP_OFFSET] = rsPADLEFT; + keymap[XK_KP_5 - KEY_MAP_OFFSET] = rsPAD5; + keymap[XK_KP_6 - KEY_MAP_OFFSET] = rsPADRIGHT; + keymap[XK_KP_7 - KEY_MAP_OFFSET] = rsPADHOME; + keymap[XK_KP_8 - KEY_MAP_OFFSET] = rsPADUP; + keymap[XK_KP_9 - KEY_MAP_OFFSET] = rsPADPGUP; + keymap[XK_KP_Insert - KEY_MAP_OFFSET] = rsPADINS; + keymap[XK_KP_End - KEY_MAP_OFFSET] = rsPADEND; + keymap[XK_KP_Down - KEY_MAP_OFFSET] = rsPADDOWN; + keymap[XK_KP_Page_Down - KEY_MAP_OFFSET] = rsPADPGDN; + keymap[XK_KP_Left - KEY_MAP_OFFSET] = rsPADLEFT; + keymap[XK_KP_Begin - KEY_MAP_OFFSET] = rsPAD5; + keymap[XK_KP_Right - KEY_MAP_OFFSET] = rsPADRIGHT; + keymap[XK_KP_Home - KEY_MAP_OFFSET] = rsPADHOME; + keymap[XK_KP_Up - KEY_MAP_OFFSET] = rsPADUP; + keymap[XK_KP_Page_Up - KEY_MAP_OFFSET] = rsPADPGUP; + + keymap[XK_KP_Decimal - KEY_MAP_OFFSET] = rsPADDEL; + keymap[XK_KP_Divide - KEY_MAP_OFFSET] = rsDIVIDE; + keymap[XK_KP_Multiply - KEY_MAP_OFFSET] = rsTIMES; + keymap[XK_KP_Subtract - KEY_MAP_OFFSET] = rsMINUS; + keymap[XK_KP_Add - KEY_MAP_OFFSET] = rsPLUS; + keymap[XK_KP_Enter - KEY_MAP_OFFSET] = rsPADENTER; + keymap[XK_KP_Equal - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_Shift_L - KEY_MAP_OFFSET] = rsLSHIFT; + keymap[XK_Control_L - KEY_MAP_OFFSET] = rsLCTRL; + keymap[XK_Alt_L - KEY_MAP_OFFSET] = rsLALT; + keymap[XK_Super_L - KEY_MAP_OFFSET] = rsLWIN; + keymap[XK_Shift_R - KEY_MAP_OFFSET] = rsRSHIFT; + keymap[XK_Control_R - KEY_MAP_OFFSET] = rsRCTRL; + keymap[XK_Alt_R - KEY_MAP_OFFSET] = rsRALT; + keymap[XK_Super_R - KEY_MAP_OFFSET] = rsRWIN; + keymap[XK_Menu - KEY_MAP_OFFSET] = rsNULL; + + // Cache the key codes' key symbol equivelants, otherwise we will have to do it on each frame + // KeyCode is always in [0,255], and represents a physical key + + int min_keycode, max_keycode, keysyms_per_keycode; + KeySym *keymap, *origkeymap; + + char *keyboardLang = setlocale (LC_CTYPE, NULL); + setlocale(LC_CTYPE, ""); + + XDisplayKeycodes(display, &min_keycode, &max_keycode); + origkeymap = XGetKeyboardMapping(display, min_keycode, (max_keycode - min_keycode + 1), &keysyms_per_keycode); + keymap = origkeymap; + for (int i = min_keycode; i <= max_keycode; i++) { + int j, lastKeysym; + + lastKeysym = keysyms_per_keycode - 1; + while ((lastKeysym >= 0) && (keymap[lastKeysym] == NoSymbol)) + lastKeysym--; + + for (j = 0; j <= lastKeysym; j++) { + KeySym ks = keymap[j]; + + if (ks == NoSymbol) + continue; + + if (ks < 256) { + keyCodeToKeymapIndex[i] = ks; + break; + } else if (ks >= 0xff00 && ks < 0xffff) { + keyCodeToKeymapIndex[i] = ks - KEY_MAP_OFFSET; + break; + } + } + keymap += keysyms_per_keycode; + } + XFree(origkeymap); + + setlocale(LC_CTYPE, keyboardLang); +} +#undef KEY_MAP_OFFSET + +void checkKeyPresses() +{ + Display *display = glfwGetX11Display(); + char keys[32]; + XQueryKeymap(display, keys); + for (int i = 0; i < sizeof(keys); i++) { + for (int j = 0; j < 8; j++) { + KeyCode keycode = 8 * i + j; + uint32 keymapIndex = keyCodeToKeymapIndex[keycode]; + if (keymapIndex != 0) { + int rsCode = keymap[keymapIndex]; + if (rsCode == rsNULL) + continue; + + bool pressed = WindowFocused && !!(keys[i] & (1 << j)); + + // idk why R* does that + if (rsCode == rsLSHIFT) + lshiftStatus = pressed; + else if (rsCode == rsRSHIFT) + rshiftStatus = pressed; + + if (keyStates[keymapIndex] != pressed) { + if (pressed) { + RsKeyboardEventHandler(rsKEYDOWN, &rsCode); + } else { + RsKeyboardEventHandler(rsKEYUP, &rsCode); + } + } + + keyStates[keymapIndex] = pressed; + } + } + } + +} +#endif + +// R* calls that in ControllerConfig, idk why +void +_InputTranslateShiftKeyUpDown(RsKeyCodes *rs) { + RsKeyboardEventHandler(lshiftStatus ? rsKEYDOWN : rsKEYUP, &(*rs = rsLSHIFT)); + RsKeyboardEventHandler(rshiftStatus ? rsKEYDOWN : rsKEYUP, &(*rs = rsRSHIFT)); +} + +// TODO this only works in frontend(and luckily only frontend use this). Fun fact: if I get pos manually in game, glfw reports that it's > 32000 +void +cursorCB(GLFWwindow* window, double xpos, double ypos) { + if (!FrontEndMenuManager.m_bMenuActive) + return; + + int winw, winh; + glfwGetWindowSize(PSGLOBAL(window), &winw, &winh); + FrontEndMenuManager.m_nMouseTempPosX = xpos * (RsGlobal.maximumWidth / winw); + FrontEndMenuManager.m_nMouseTempPosY = ypos * (RsGlobal.maximumHeight / winh); +} + +void +cursorEnterCB(GLFWwindow* window, int entered) { + PSGLOBAL(cursorIsInWindow) = !!entered; +} + +void +windowFocusCB(GLFWwindow* window, int focused) { + WindowFocused = !!focused; +} + +void +windowIconifyCB(GLFWwindow* window, int iconified) { + WindowIconified = !!iconified; +} + +/* + ***************************************************************************** + */ +#ifdef _WIN32 +int PASCAL +WinMain(HINSTANCE instance, + HINSTANCE prevInstance __RWUNUSED__, + CMDSTR cmdLine, + int cmdShow) +{ + + RwInt32 argc; + RwChar** argv; + SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nil, SPIF_SENDCHANGE); + +#ifndef MASTER + if (strstr(cmdLine, "-console")) + { + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + } +#endif + +#else +int +main(int argc, char *argv[]) +{ +#endif + RwV2d pos; + RwInt32 i; + +#ifdef USE_CUSTOM_ALLOCATOR + InitMemoryMgr(); +#endif + +#if !defined(_WIN32) && !defined(__SWITCH__) + struct sigaction act; + act.sa_sigaction = terminateHandler; + act.sa_flags = SA_SIGINFO; + sigaction(SIGTERM, &act, NULL); +#ifdef FLUSHABLE_STREAMING + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_handler = dummyHandler; + sa.sa_flags = 0; + sigaction(SIGUSR1, &sa, NULL); +#endif +#endif + + /* + * Initialize the platform independent data. + * This will in turn initialize the platform specific data... + */ + if( RsEventHandler(rsINITIALIZE, nil) == rsEVENTERROR ) + { + return FALSE; + } + +#ifdef _WIN32 + /* + * Get proper command line params, cmdLine passed to us does not + * work properly under all circumstances... + */ + cmdLine = GetCommandLine(); + + /* + * Parse command line into standard (argv, argc) parameters... + */ + argv = CommandLineToArgv(cmdLine, &argc); + + + /* + * Parse command line parameters (except program name) one at + * a time BEFORE RenderWare initialization... + */ +#endif + for(i=1; iGetLeftMouseJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetEnterJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetCharJustDown(' ')) +// ++gGameState; +// else if (CPad::GetPad(0)->GetAltJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetTabJustDown()) +// ++gGameState; + + break; + } + + case GS_INIT_INTRO_MPEG: + { +//#ifndef NO_MOVIES +// CloseClip(); +// CoUninitialize(); +//#endif +// +// if (CMenuManager::OS_Language == LANG_FRENCH || CMenuManager::OS_Language == LANG_GERMAN) +// PlayMovieInWindow(cmdShow, "movies\\GTAtitlesGER.mpg"); +// else +// PlayMovieInWindow(cmdShow, "movies\\GTAtitles.mpg"); + + gGameState = GS_INTRO_MPEG; + TRACE("gGameState = GS_INTRO_MPEG;"); + break; + } + + case GS_INTRO_MPEG: + { +// CPad::UpdatePads(); +// +// if (startupDeactivate || ControlsManager.GetJoyButtonJustDown() != 0) + ++gGameState; +// else if (CPad::GetPad(0)->GetLeftMouseJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetEnterJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetCharJustDown(' ')) +// ++gGameState; +// else if (CPad::GetPad(0)->GetAltJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetTabJustDown()) +// ++gGameState; + + break; + } + + case GS_INIT_ONCE: + { + //CoUninitialize(); + +#ifdef PS2_MENU + extern char version_name[64]; + if ( CGame::frenchGame || CGame::germanGame ) + LoadingScreen(NULL, version_name, "loadsc24"); + else + LoadingScreen(NULL, version_name, "loadsc0"); + + printf("Into TheGame!!!\n"); +#else + LoadingScreen(nil, nil, "loadsc0"); +#endif + if ( !CGame::InitialiseOnceAfterRW() ) + RsGlobal.quit = TRUE; + +#ifdef PS2_MENU + gGameState = GS_INIT_PLAYING_GAME; +#else + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); +#endif + break; + } + +#ifndef PS2_MENU + case GS_INIT_FRONTEND: + { + LoadingScreen(nil, nil, "loadsc0"); + + FrontEndMenuManager.m_bGameNotLoaded = true; + + CMenuManager::m_bStartUpFrontEndRequested = true; + + if ( defaultFullscreenRes ) + { + defaultFullscreenRes = FALSE; + FrontEndMenuManager.m_nPrefsVideoMode = GcurSelVM; + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + } + + gGameState = GS_FRONTEND; + TRACE("gGameState = GS_FRONTEND;"); + break; + } + + case GS_FRONTEND: + { + if(!WindowIconified) + RsEventHandler(rsFRONTENDIDLE, nil); + +#ifdef PS2_MENU + if ( !FrontEndMenuManager.m_bMenuActive || TheMemoryCard.m_bWantToLoad ) +#else + if ( !FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bWantToLoad ) +#endif + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + +#ifdef PS2_MENU + if (TheMemoryCard.m_bWantToLoad ) +#else + if ( FrontEndMenuManager.m_bWantToLoad ) +#endif + { + InitialiseGame(); + FrontEndMenuManager.m_bGameNotLoaded = false; + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + } + break; + } +#endif + + case GS_INIT_PLAYING_GAME: + { +#ifdef PS2_MENU + CGame::Initialise("DATA\\GTA3.DAT"); + + //LoadingScreen("Starting Game", NULL, GetRandomSplashScreen()); + + if ( TheMemoryCard.CheckCardInserted(CARD_ONE) == CMemoryCard::NO_ERR_SUCCESS + && TheMemoryCard.ChangeDirectory(CARD_ONE, TheMemoryCard.Cards[CARD_ONE].dir) + && TheMemoryCard.FindMostRecentFileName(CARD_ONE, TheMemoryCard.MostRecentFile) == true + && TheMemoryCard.CheckDataNotCorrupt(TheMemoryCard.MostRecentFile)) + { + strcpy(TheMemoryCard.LoadFileName, TheMemoryCard.MostRecentFile); + TheMemoryCard.b_FoundRecentSavedGameWantToLoad = true; + + if (CMenuManager::m_PrefsLanguage != TheMemoryCard.GetLanguageToLoad()) + { + CMenuManager::m_PrefsLanguage = TheMemoryCard.GetLanguageToLoad(); + TheText.Unload(); + TheText.Load(); + } + + CGame::currLevel = (eLevelName)TheMemoryCard.GetLevelToLoad(); + } +#else + InitialiseGame(); + + FrontEndMenuManager.m_bGameNotLoaded = false; +#endif + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + break; + } + + case GS_PLAYING_GAME: + { + float ms = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); + if ( RwInitialised ) + { + if (!CMenuManager::m_PrefsFrameLimiter || (1000.0f / (float)RsGlobal.maxFPS) < ms) + RsEventHandler(rsIDLE, (void *)TRUE); + } + break; + } + } + } + else + { + if ( RwCameraBeginUpdate(Scene.camera) ) + { + RwCameraEndUpdate(Scene.camera); + ForegroundApp = TRUE; + RsEventHandler(rsACTIVATE, (void *)TRUE); + } + + } + } + + + /* + * About to shut down - block resize events again... + */ + RwInitialised = FALSE; + + FrontEndMenuManager.UnloadTextures(); +#ifdef PS2_MENU + if ( !(FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad)) + break; +#else + if ( !FrontEndMenuManager.m_bWantToRestart ) + break; +#endif + + CPad::ResetCheats(); + CPad::StopPadsShaking(); + + DMAudio.ChangeMusicMode(MUSICMODE_DISABLE); + +#ifdef PS2_MENU + CGame::ShutDownForRestart(); +#endif + + CTimer::Stop(); + +#ifdef PS2_MENU + if (FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + if (TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + FrontEndMenuManager.m_bWantToRestart = true; + TheMemoryCard.m_bWantToLoad = true; + } + + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + FrontEndMenuManager.m_bWantToRestart = false; + + continue; + } + + CGame::ShutDown(); + CTimer::Stop(); + + break; +#else + if ( FrontEndMenuManager.m_bWantToLoad ) + { + CGame::ShutDownForRestart(); + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + FrontEndMenuManager.m_bWantToLoad = false; + } + else + { +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + CTimer::Stop(); + + if ( FrontEndMenuManager.m_bFirstTime == true ) + { + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); + } + else + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + } + + FrontEndMenuManager.m_bFirstTime = false; + FrontEndMenuManager.m_bWantToRestart = false; +#endif + } + + +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + DMAudio.Terminate(); + + _psFreeVideoModeList(); + + + /* + * Tidy up the 3D (RenderWare) components of the application... + */ + RsEventHandler(rsRWTERMINATE, nil); + + /* + * Free the platform dependent data... + */ + RsEventHandler(rsTERMINATE, nil); + +#ifdef _WIN32 + /* + * Free the argv strings... + */ + free(argv); + + SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &SavedStickyKeys, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETPOWEROFFACTIVE, TRUE, nil, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETLOWPOWERACTIVE, TRUE, nil, SPIF_SENDCHANGE); + SetErrorMode(0); +#endif + + return 0; +} + +/* + ***************************************************************************** + */ + +RwV2d leftStickPos; +RwV2d rightStickPos; + +void CapturePad(RwInt32 padID) +{ + int8 glfwPad = -1; + + if( padID == 0 ) + glfwPad = PSGLOBAL(joy1id); + else if( padID == 1) + glfwPad = PSGLOBAL(joy2id); + else + assert("invalid padID"); + + if ( glfwPad == -1 ) + return; + + int numButtons, numAxes; + const uint8 *buttons = glfwGetJoystickButtons(glfwPad, &numButtons); + const float *axes = glfwGetJoystickAxes(glfwPad, &numAxes); + GLFWgamepadstate gamepadState; + + if (ControlsManager.m_bFirstCapture == false) { + memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(ControlsManager.m_NewState)); + } else { + // In case connected gamepad doesn't have L-R trigger axes. + ControlsManager.m_NewState.mappedButtons[15] = ControlsManager.m_NewState.mappedButtons[16] = 0; + } + + ControlsManager.m_NewState.buttons = (uint8*)buttons; + ControlsManager.m_NewState.numButtons = numButtons; + ControlsManager.m_NewState.id = glfwPad; + ControlsManager.m_NewState.isGamepad = glfwGetGamepadState(glfwPad, &gamepadState); + if (ControlsManager.m_NewState.isGamepad) { + memcpy(&ControlsManager.m_NewState.mappedButtons, gamepadState.buttons, sizeof(gamepadState.buttons)); + float lt = gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER], rt = gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER]; + + // glfw returns 0.0 for non-existent axises(which is bullocks) so we treat it as deadzone, and keep value of previous frame. + // otherwise if this axis is present, -1 = released, 1 = pressed + if (lt != 0.0f) + ControlsManager.m_NewState.mappedButtons[15] = lt > -0.8f; + + if (rt != 0.0f) + ControlsManager.m_NewState.mappedButtons[16] = rt > -0.8f; + } + // TODO? L2-R2 axes(not buttons-that's fine) on joysticks that don't have SDL gamepad mapping AREN'T handled, and I think it's impossible to do without mapping. + + if (ControlsManager.m_bFirstCapture == true) { + memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(ControlsManager.m_NewState)); + + ControlsManager.m_bFirstCapture = false; + } + + RsPadButtonStatus bs; + bs.padID = padID; + + RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + + // Gamepad axes are guaranteed to return 0.0f if that particular gamepad doesn't have that axis. + // And that's really good for sticks, because gamepads return 0.0 for them when sticks are in released state. + if ( glfwPad != -1 ) { + leftStickPos.x = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_X] : numAxes >= 1 ? axes[0] : 0.0f; + leftStickPos.y = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y] : numAxes >= 2 ? axes[1] : 0.0f; + + rightStickPos.x = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X] : numAxes >= 3 ? axes[2] : 0.0f; + rightStickPos.y = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y] : numAxes >= 4 ? axes[3] : 0.0f; + } + + { + if (CPad::m_bMapPadOneToPadTwo) + bs.padID = 1; + + RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + RsPadEventHandler(rsPADBUTTONDOWN, (void *)&bs); + } + + { + if (CPad::m_bMapPadOneToPadTwo) + bs.padID = 1; + + CPad *pad = CPad::GetPad(bs.padID); + + if ( Abs(leftStickPos.x) > 0.3f ) + pad->PCTempJoyState.LeftStickX = (int32)(leftStickPos.x * 128.0f); + + if ( Abs(leftStickPos.y) > 0.3f ) + pad->PCTempJoyState.LeftStickY = (int32)(leftStickPos.y * 128.0f); + + if ( Abs(rightStickPos.x) > 0.3f ) + pad->PCTempJoyState.RightStickX = (int32)(rightStickPos.x * 128.0f); + + if ( Abs(rightStickPos.y) > 0.3f ) + pad->PCTempJoyState.RightStickY = (int32)(rightStickPos.y * 128.0f); + } + + _psHandleVibration(); + + return; +} + +void joysChangeCB(int jid, int event) +{ + if (event == GLFW_CONNECTED && !IsThisJoystickBlacklisted(jid)) { + if (PSGLOBAL(joy1id) == -1) { + PSGLOBAL(joy1id) = jid; +#ifdef DETECT_JOYSTICK_MENU + strcpy(gSelectedJoystickName, glfwGetJoystickName(jid)); +#endif + // This is behind LOAD_INI_SETTINGS, because otherwise the Init call below will destroy/overwrite your bindings. +#ifdef LOAD_INI_SETTINGS + int count; + glfwGetJoystickButtons(PSGLOBAL(joy1id), &count); + ControlsManager.InitDefaultControlConfigJoyPad(count); +#endif + } else if (PSGLOBAL(joy2id) == -1) + PSGLOBAL(joy2id) = jid; + + } else if (event == GLFW_DISCONNECTED) { + if (PSGLOBAL(joy1id) == jid) { + PSGLOBAL(joy1id) = -1; + } else if (PSGLOBAL(joy2id) == jid) + PSGLOBAL(joy2id) = -1; + } +} + +#if (defined(_MSC_VER)) +int strcasecmp(const char* str1, const char* str2) +{ + return _strcmpi(str1, str2); +} +#endif +#endif diff --git a/src/skel/platform.h b/src/skel/platform.h new file mode 100644 index 0000000..c9a8a11 --- /dev/null +++ b/src/skel/platform.h @@ -0,0 +1,59 @@ +#ifndef PLATFORM_H +#define PLATFORM_H + +// Functions that's different on glfw/win etc. but have same signature (but if a function only used in win.cpp you can keep in win.h) + +#include "rwcore.h" +#include "skeleton.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#ifdef _WIN32 +extern RwUInt32 psTimer(void); +#else +extern double psTimer(void); +#endif + +extern RwBool psInitialize(void); +extern void psTerminate(void); + +extern void psCameraShowRaster(RwCamera *camera); +extern RwBool psCameraBeginUpdate(RwCamera *camera); +extern RwImage *psGrabScreen(RwCamera *camera); + +extern void psMouseSetPos(RwV2d *pos); + +extern RwBool psSelectDevice(); + +extern RwMemoryFunctions *psGetMemoryFunctions(void); + +/* install the platform specific file system */ +extern RwBool psInstallFileSystem(void); + + +/* Handle native texture support */ +extern RwBool psNativeTextureSupport(void); + +extern void _InputTranslateShiftKeyUpDown(RsKeyCodes* rs); +extern long _InputInitialiseMouse(); // returns HRESULT on Windows actually +extern void _InputInitialiseJoys(); + +extern void HandleExit(); + +extern void _psSelectScreenVM(RwInt32 videoMode); + +extern void InitialiseLanguage(); + +extern RwBool _psSetVideoMode(RwInt32 subSystem, RwInt32 videoMode); + +extern RwChar** _psGetVideoModeList(); + +extern RwInt32 _psGetNumVideModes(); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* PLATFORM_H */ diff --git a/src/skel/skeleton.cpp b/src/skel/skeleton.cpp new file mode 100644 index 0000000..7889056 --- /dev/null +++ b/src/skel/skeleton.cpp @@ -0,0 +1,432 @@ +#include "common.h" + + +#include +#include +#include +#include + +#include "rwcore.h" + +#include "skeleton.h" +#include "platform.h" +#include "main.h" +#include "MemoryHeap.h" + +static RwBool DefaultVideoMode = TRUE; + +RsGlobalType RsGlobal; + +#ifdef _WIN32 +RwUInt32 +#else +double +#endif +RsTimer(void) +{ + return psTimer(); +} + + +/* + ***************************************************************************** + */ +void +RsCameraShowRaster(RwCamera * camera) +{ + psCameraShowRaster(camera); + + return; +} + +/* + ***************************************************************************** + */ +RwBool +RsCameraBeginUpdate(RwCamera * camera) +{ + return psCameraBeginUpdate(camera); +} + +/* + ***************************************************************************** + */ +RwImage* +RsGrabScreen(RwCamera *camera) +{ + return psGrabScreen(camera); +} + +/* + ***************************************************************************** + */ +RwBool +RsRegisterImageLoader(void) +{ + return TRUE; +} + +/* + ***************************************************************************** + */ +static RwBool +RsSetDebug(void) +{ + return TRUE; +} + +/* + ***************************************************************************** + */ +void +RsMouseSetPos(RwV2d * pos) +{ + psMouseSetPos(pos); + + return; +} + +/* + ***************************************************************************** + */ +RwBool +RsSelectDevice(void) +{ + return psSelectDevice(); +} + +/* + ***************************************************************************** + */ +RwBool +RsInputDeviceAttach(RsInputDeviceType inputDevice, + RsInputEventHandler inputEventHandler) +{ + switch (inputDevice) + { + case rsKEYBOARD: + { + RsGlobal.keyboard.inputEventHandler = inputEventHandler; + RsGlobal.keyboard.used = TRUE; + break; + } + case rsMOUSE: + { + RsGlobal.mouse.inputEventHandler = inputEventHandler; + RsGlobal.mouse.used = TRUE; + break; + } + case rsPAD: + { + RsGlobal.pad.inputEventHandler = inputEventHandler; + RsGlobal.pad.used = TRUE; + break; + } + default: + { + return FALSE; + } + } + + return TRUE; +} + + +/* + ***************************************************************************** + */ +static RwBool +rsCommandLine(RwChar *arg) +{ + RsEventHandler(rsFILELOAD, arg); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +static RwBool +rsPreInitCommandLine(RwChar *arg) +{ + if( !strcmp(arg, RWSTRING("-vms")) ) + { + DefaultVideoMode = FALSE; + + return TRUE; + } +#ifndef MASTER + if (!strcmp(arg, RWSTRING("-animviewer"))) + { + gbModelViewer = TRUE; + + return TRUE; + } +#endif + return FALSE; +} + +/* + ***************************************************************************** + */ +RsEventStatus +RsKeyboardEventHandler(RsEvent event, void *param) +{ + if (RsGlobal.keyboard.used) + { + return RsGlobal.keyboard.inputEventHandler(event, param); + } + + return rsEVENTNOTPROCESSED; +} + +/* + ***************************************************************************** + */ +RsEventStatus +RsPadEventHandler(RsEvent event, void *param) +{ + if (RsGlobal.pad.used) + { + return RsGlobal.pad.inputEventHandler(event, param); + } + + return rsEVENTNOTPROCESSED; +} + +/* + ***************************************************************************** + */ +RsEventStatus +RsEventHandler(RsEvent event, void *param) +{ + RsEventStatus result; + RsEventStatus es; + + /* + * Give the application an opportunity to override any events... + */ + es = AppEventHandler(event, param); + + /* + * We never allow the app to replace the quit behaviour, + * only to intercept... + */ + if (event == rsQUITAPP) + { + /* + * Set the flag which causes the event loop to exit... + */ + RsGlobal.quit = TRUE; + } + + if (es == rsEVENTNOTPROCESSED) + { + switch (event) + { + case rsSELECTDEVICE: + result = + (RsSelectDevice()? rsEVENTPROCESSED : rsEVENTERROR); + break; + + case rsCOMMANDLINE: + result = (rsCommandLine((RwChar *) param) ? + rsEVENTPROCESSED : rsEVENTERROR); + break; + case rsPREINITCOMMANDLINE: + result = (rsPreInitCommandLine((RwChar *) param) ? + rsEVENTPROCESSED : rsEVENTERROR); + break; + case rsINITDEBUG: + result = + (RsSetDebug()? rsEVENTPROCESSED : rsEVENTERROR); + break; + + case rsREGISTERIMAGELOADER: + result = (RsRegisterImageLoader()? + rsEVENTPROCESSED : rsEVENTERROR); + break; + + case rsRWTERMINATE: + RsRwTerminate(); + result = (rsEVENTPROCESSED); + break; + + case rsRWINITIALIZE: + result = (RsRwInitialize(param) ? + rsEVENTPROCESSED : rsEVENTERROR); + break; + + case rsTERMINATE: + RsTerminate(); + result = (rsEVENTPROCESSED); + break; + + case rsINITIALIZE: + result = + (RsInitialize()? rsEVENTPROCESSED : rsEVENTERROR); + break; + + default: + result = (es); + break; + + } + } + else + { + result = (es); + } + + return result; +} + +/* + ***************************************************************************** + */ +void +RsRwTerminate(void) +{ + /* Close RenderWare */ + + RwEngineStop(); + RwEngineClose(); + RwEngineTerm(); + + return; +} + +/* + ***************************************************************************** + */ +RwBool +RsRwInitialize(void *displayID) +{ + RwEngineOpenParams openParams; + + PUSH_MEMID(MEMID_RENDER); // NB: not popped on failed return + + /* + * Start RenderWare... + */ + + if (!RwEngineInit(psGetMemoryFunctions(), 0, rsRESOURCESDEFAULTARENASIZE)) + { + return (FALSE); + } + + /* + * Install any platform specific file systems... + */ + psInstallFileSystem(); + + /* + * Initialize debug message handling... + */ + RsEventHandler(rsINITDEBUG, nil); + + /* + * Attach all plugins... + */ + if (RsEventHandler(rsPLUGINATTACH, nil) == rsEVENTERROR) + { + return (FALSE); + } + + /* + * Attach input devices... + */ + if (RsEventHandler(rsINPUTDEVICEATTACH, nil) == rsEVENTERROR) + { + return (FALSE); + } + + openParams.displayID = displayID; + + if (!RwEngineOpen(&openParams)) + { + RwEngineTerm(); + return (FALSE); + } + + if (RsEventHandler(rsSELECTDEVICE, displayID) == rsEVENTERROR) + { + RwEngineClose(); + RwEngineTerm(); + return (FALSE); + } + + if (!RwEngineStart()) + { + RwEngineClose(); + RwEngineTerm(); + return (FALSE); + } + + /* + * Register loaders for an image with a particular file extension... + */ + RsEventHandler(rsREGISTERIMAGELOADER, nil); + + psNativeTextureSupport(); + + RwTextureSetMipmapping(FALSE); + RwTextureSetAutoMipmapping(FALSE); + + POP_MEMID(); + + return TRUE; +} + +/* + ***************************************************************************** + */ +void +RsTerminate(void) +{ + psTerminate(); + + return; +} + +/* + ***************************************************************************** + */ +RwBool +RsInitialize(void) +{ + /* + * Initialize Platform independent data... + */ + RwBool result; + + RsGlobal.appName = RWSTRING("GTA3"); + RsGlobal.maximumWidth = DEFAULT_SCREEN_WIDTH; + RsGlobal.maximumHeight = DEFAULT_SCREEN_HEIGHT; + RsGlobal.width = DEFAULT_SCREEN_WIDTH; + RsGlobal.height = DEFAULT_SCREEN_HEIGHT; + + RsGlobal.maxFPS = 30; + + RsGlobal.quit = FALSE; + + /* setup the keyboard */ + RsGlobal.keyboard.inputDeviceType = rsKEYBOARD; + RsGlobal.keyboard.inputEventHandler = nil; + RsGlobal.keyboard.used = FALSE; + + /* setup the mouse */ + RsGlobal.mouse.inputDeviceType = rsMOUSE; + RsGlobal.mouse.inputEventHandler = nil; + RsGlobal.mouse.used = FALSE; + + /* setup the pad */ + RsGlobal.pad.inputDeviceType = rsPAD; + RsGlobal.pad.inputEventHandler = nil; + RsGlobal.pad.used = FALSE; + + result = psInitialize(); + + return result; +} diff --git a/src/skel/skeleton.h b/src/skel/skeleton.h new file mode 100644 index 0000000..380b6c0 --- /dev/null +++ b/src/skel/skeleton.h @@ -0,0 +1,290 @@ +#ifndef SKELETON_H +#define SKELETON_H + +#include "rwcore.h" + +/* Default arena size depending on platform. */ +#define rsRESOURCESDEFAULTARENASIZE (1 << 20) + +#if (!defined(RsSprintf)) +#define RsSprintf rwsprintf +#endif /* (!defined(RsSprintf)) */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#if (!defined(RSASSERT)) +#define RSASSERT(_condition) /* No-op */ +#endif /* (!defined(RSASSERT)) */ + +#define RSASSERTISTYPE(_f, _t) \ + RSASSERT( (!(_f)) || ((((const RwObject *)(_f))->type)==(_t)) ) + +enum RsInputDeviceType +{ + rsKEYBOARD, + rsMOUSE, + rsPAD +}; +typedef enum RsInputDeviceType RsInputDeviceType; + +enum RsEventStatus +{ + rsEVENTERROR, + rsEVENTPROCESSED, + rsEVENTNOTPROCESSED +}; +typedef enum RsEventStatus RsEventStatus; + +enum RsEvent +{ + rsCAMERASIZE, + rsCOMMANDLINE, + rsFILELOAD, + rsINITDEBUG, + rsINPUTDEVICEATTACH, + rsLEFTBUTTONDOWN, + rsLEFTBUTTONUP, + rsMOUSEMOVE, + rsMOUSEWHEELMOVE, + rsPLUGINATTACH, + rsREGISTERIMAGELOADER, + rsRIGHTBUTTONDOWN, + rsRIGHTBUTTONUP, + _rs_13, + _rs_14, + _rs_15, + _rs_16, + _rs_17, + _rs_18, + _rs_19, + _rs_20, + rsRWINITIALIZE, + rsRWTERMINATE, + rsSELECTDEVICE, + rsINITIALIZE, + rsTERMINATE, + rsIDLE, + rsFRONTENDIDLE, + rsKEYDOWN, + rsKEYUP, + rsQUITAPP, + rsPADBUTTONDOWN, + rsPADBUTTONUP, + rsPADANALOGUELEFT, + rsPADANALOGUELEFTRESET, + rsPADANALOGUERIGHT, + rsPADANALOGUERIGHTRESET, + rsPREINITCOMMANDLINE, + rsACTIVATE, +}; + +typedef enum RsEvent RsEvent; + +typedef RsEventStatus (*RsInputEventHandler)(RsEvent event, void *param); + +typedef struct RsInputDevice RsInputDevice; +struct RsInputDevice +{ + RsInputDeviceType inputDeviceType; + RwBool used; + RsInputEventHandler inputEventHandler; +}; + +typedef struct RsGlobalType RsGlobalType; +struct RsGlobalType +{ + const RwChar *appName; + RwInt32 width; + RwInt32 height; + RwInt32 maximumWidth; + RwInt32 maximumHeight; + RwInt32 maxFPS; + RwBool quit; + + void *ps; /* platform specific data */ + + RsInputDevice keyboard; + RsInputDevice mouse; + RsInputDevice pad; +}; + +enum RsKeyCodes +{ + rsESC = 1000, + + rsF1 = 1001, + rsF2 = 1002, + rsF3 = 1003, + rsF4 = 1004, + rsF5 = 1005, + rsF6 = 1006, + rsF7 = 1007, + rsF8 = 1008, + rsF9 = 1009, + rsF10 = 1010, + rsF11 = 1011, + rsF12 = 1012, + + rsINS = 1013, + rsDEL = 1014, + rsHOME = 1015, + rsEND = 1016, + rsPGUP = 1017, + rsPGDN = 1018, + + rsUP = 1019, + rsDOWN = 1020, + rsLEFT = 1021, + rsRIGHT = 1022, + + rsDIVIDE = 1023, + rsTIMES = 1024, + rsPLUS = 1025, + rsMINUS = 1026, + rsPADDEL = 1027, + rsPADEND = 1028, + rsPADDOWN = 1029, + rsPADPGDN = 1030, + rsPADLEFT = 1031, + rsPAD5 = 1032, + rsNUMLOCK = 1033, + rsPADRIGHT = 1034, + rsPADHOME = 1035, + rsPADUP = 1036, + rsPADPGUP = 1037, + rsPADINS = 1038, + rsPADENTER = 1039, + + rsSCROLL = 1040, + rsPAUSE = 1041, + + rsBACKSP = 1042, + rsTAB = 1043, + rsCAPSLK = 1044, + rsENTER = 1045, + rsLSHIFT = 1046, + rsRSHIFT = 1047, + rsSHIFT = 1048, + rsLCTRL = 1049, + rsRCTRL = 1050, + rsLALT = 1051, + rsRALT = 1052, + rsLWIN = 1053, + rsRWIN = 1054, + rsAPPS = 1055, + + rsNULL = 1056, + + rsMOUSELEFTBUTTON = 1, + rsMOUSMIDDLEBUTTON = 2, + rsMOUSERIGHTBUTTON = 3, + rsMOUSEWHEELUPBUTTON = 4, + rsMOUSEWHEELDOWNBUTTON = 5, + rsMOUSEX1BUTTON = 6, + rsMOUSEX2BUTTON = 7, +}; +typedef enum RsKeyCodes RsKeyCodes; + +typedef struct RsKeyStatus RsKeyStatus; +struct RsKeyStatus +{ + RwInt32 keyCharCode; +}; + +typedef struct RsPadButtonStatus RsPadButtonStatus; +struct RsPadButtonStatus +{ + RwInt32 padID; +}; + +enum RsPadButtons +{ + rsPADNULL = 0, + + rsPADBUTTON1 = 1, + rsPADBUTTON2 = 2, + rsPADBUTTON3 = 3, + rsPADBUTTON4 = 4, + + rsPADBUTTON5 = 5, + rsPADBUTTON6 = 6, + rsPADBUTTON7 = 7, + rsPADBUTTON8 = 8, + + rsPADSELECT = 9, + + rsPADBUTTONA1 = 10, + rsPADBUTTONA2 = 11, + + rsPADSTART = 12, + + rsPADDPADUP = 13, + rsPADDPADRIGHT = 14, + rsPADDPADDOWN = 15, + rsPADDPADLEFT = 16, +}; +typedef enum RsPadButtons RsPadButtons; + + +extern RsGlobalType RsGlobal; + +extern RsEventStatus AppEventHandler(RsEvent event, void *param); +extern RwBool AttachInputDevices(void); + +extern RsEventStatus RsEventHandler(RsEvent event, void *param); +extern RsEventStatus RsKeyboardEventHandler(RsEvent event, void *param); +extern RsEventStatus RsPadEventHandler(RsEvent event, void *param); + +extern RwBool +RsInitialize(void); + +extern RwBool +RsRegisterImageLoader(void); + +extern RwBool +RsRwInitialize(void *param); + +extern RwBool +RsSelectDevice(void); + +extern RwBool +RsInputDeviceAttach(RsInputDeviceType inputDevice, + RsInputEventHandler inputEventHandler); + +#ifdef _WIN32 +extern RwUInt32 +#else +extern double +#endif +RsTimer(void); + +extern void +RsCameraShowRaster(RwCamera *camera); + +extern RwBool +RsCameraBeginUpdate(RwCamera *camera); + +//TODO +//extern void +//RsMouseSetVisibility(RwBool visible); + +extern RwImage* +RsGrabScreen(RwCamera *camera); + +extern void +RsMouseSetPos(RwV2d *pos); + +extern void +RsRwTerminate(void); + +extern void +RsTerminate(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SKELETON_H */ diff --git a/src/skel/win/gta3.ico b/src/skel/win/gta3.ico new file mode 100644 index 0000000000000000000000000000000000000000..d0a4771306eeabea613587a299a94823fd448d70 GIT binary patch literal 161654 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 z

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 SG8(i10000 +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#pragma warning( push ) +#pragma warning( disable : 4005) + +#ifdef __MWERKS__ +#define MAPVK_VK_TO_CHAR (2) // this is missing from codewarrior win32 headers - but it gets used ... how? +#endif + +#include +#include +#pragma warning( pop ) + +#define WM_GRAPHNOTIFY WM_USER+13 + +#ifndef USE_D3D9 +#pragma comment( lib, "d3d8.lib" ) +#endif +#pragma comment( lib, "ddraw.lib" ) +#pragma comment( lib, "Winmm.lib" ) +#pragma comment( lib, "dxguid.lib" ) +#pragma comment( lib, "strmiids.lib" ) +#pragma comment( lib, "dinput8.lib" ) + +#define WITHD3D +#define WITHDINPUT +#include "common.h" +#if (defined(_MSC_VER)) +#include +#endif /* (defined(_MSC_VER)) */ +#include +#include "rwcore.h" +#include "resource.h" +#include "skeleton.h" +#include "platform.h" +#include "crossplatform.h" + +#define MAX_SUBSYSTEMS (16) + + +static RwBool ForegroundApp = TRUE; + +static RwBool RwInitialised = FALSE; + +static RwSubSystemInfo GsubSysInfo[MAX_SUBSYSTEMS]; +static RwInt32 GnumSubSystems = 0; +static RwInt32 GcurSel = 0, GcurSelVM = 0; + +static RwBool startupDeactivate; + +static RwBool useDefault; + +/* Class name for the MS Window's window class. */ + +static const RwChar *AppClassName = RWSTRING("Grand theft auto 3"); + +static psGlobalType PsGlobal; + + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +#undef MAKEPOINTS +#define MAKEPOINTS(l) (*((POINTS /*FAR*/ *)&(l))) + +#define SAFE_RELEASE(x) { if (x) x->Release(); x = NULL; } +#define JIF(x) if (FAILED(hr=(x))) \ + {debug(TEXT("FAILED(hr=0x%x) in ") TEXT(#x) TEXT("\n"), hr); return;} + +#include "main.h" +#include "FileMgr.h" +#include "Text.h" +#include "Pad.h" +#include "Timer.h" +#include "DMAudio.h" +#include "ControllerConfig.h" +#include "Frontend.h" +#include "Game.h" +#include "PCSave.h" +#include "AnimViewer.h" +#include "MemoryMgr.h" + +#ifdef PS2_MENU +#include "MemoryCard.h" +#include "Font.h" +#endif + +VALIDATE_SIZE(psGlobalType, 0x28); + +// DirectShow interfaces +IGraphBuilder *pGB = nil; +IMediaControl *pMC = nil; +IMediaEventEx *pME = nil; +IVideoWindow *pVW = nil; +IMediaSeeking *pMS = nil; + +DWORD dwDXVersion; +SIZE_T _dwMemTotalPhys; +size_t _dwMemAvailPhys; +SIZE_T _dwMemTotalVirtual; +SIZE_T _dwMemAvailVirtual; +DWORD _dwMemTotalVideo; +DWORD _dwMemAvailVideo; +DWORD _dwOperatingSystemVersion; + +RwUInt32 gGameState; +CJoySticks AllValidWinJoys; + +#ifdef DETECT_JOYSTICK_MENU +char gSelectedJoystickName[128] = ""; +#endif + +// What is that for anyway? +#ifndef IMPROVED_VIDEOMODE +static RwBool defaultFullscreenRes = TRUE; +#else +static RwBool defaultFullscreenRes = FALSE; +static RwInt32 bestWndMode = -1; +#endif + +CJoySticks::CJoySticks() +{ + for (int i = 0; i < MAX_JOYSTICKS; i++) + { + ClearJoyInfo(i); + } +} + +void CJoySticks::ClearJoyInfo(int joyID) +{ + m_aJoys[joyID].m_State = JOYPAD_UNUSED; + m_aJoys[joyID].m_bInitialised = false; + m_aJoys[joyID].m_bHasAxisZ = false; + m_aJoys[joyID].m_bHasAxisR = false; +} + + + +/* + ***************************************************************************** + */ +void _psCreateFolder(LPCSTR path) +{ + HANDLE hfle = CreateFile(path, GENERIC_READ, + FILE_SHARE_READ, + nil, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL, + nil); + + if ( hfle == INVALID_HANDLE_VALUE ) + CreateDirectory(path, nil); + else + CloseHandle(hfle); +} + +/* + ***************************************************************************** + */ +const char *_psGetUserFilesFolder() +{ +#ifdef USE_MY_DOCUMENTS + HKEY hKey = NULL; + + static CHAR szUserFiles[256]; + + if ( RegOpenKeyEx(HKEY_CURRENT_USER, + REGSTR_PATH_SPECIAL_FOLDERS, + REG_OPTION_RESERVED, + KEY_READ, + &hKey) == ERROR_SUCCESS ) + { + DWORD KeyType; + DWORD KeycbData = sizeof(szUserFiles); + if ( RegQueryValueEx(hKey, + "Personal", + NULL, + &KeyType, + (LPBYTE)szUserFiles, + &KeycbData) == ERROR_SUCCESS ) + { + RegCloseKey(hKey); + strcat(szUserFiles, "\\GTA3 User Files"); + _psCreateFolder(szUserFiles); + return szUserFiles; + } + + RegCloseKey(hKey); + } + + strcpy(szUserFiles, "data"); + return szUserFiles; +#else + static CHAR szUserFiles[256]; + strcpy(szUserFiles, "userfiles"); + _psCreateFolder(szUserFiles); + return szUserFiles; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psCameraBeginUpdate(RwCamera *camera) +{ + if ( !RwCameraBeginUpdate(Scene.camera) ) + { + ForegroundApp = FALSE; + RsEventHandler(rsACTIVATE, (void *)FALSE); + return FALSE; + } + + return TRUE; +} + +/* + ***************************************************************************** + */ +void +psCameraShowRaster(RwCamera *camera) +{ + if (CMenuManager::m_PrefsVsync) + RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPWAITVSYNC); + else + RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPDONTWAIT); + + return; +} + + +/* + ***************************************************************************** + */ +RwImage * +psGrabScreen(RwCamera *pCamera) +{ +#ifndef LIBRW + RwRaster *pRaster = RwCameraGetRaster(pCamera); + if (RwImage *pImage = RwImageCreate(pRaster->width, pRaster->height, 32)) { + RwImageAllocatePixels(pImage); + RwImageSetFromRaster(pImage, pRaster); + return pImage; + } +#else + rw::Image *image = RwCameraGetRaster(pCamera)->toImage(); + image->removeMask(); + if(image) + return image; +#endif + return nil; +} + +/* + ***************************************************************************** + */ +RwUInt32 +psTimer(void) +{ + RwUInt32 time; + + TIMECAPS TimeCaps; + + timeGetDevCaps(&TimeCaps, sizeof(TIMECAPS)); + + timeBeginPeriod(TimeCaps.wPeriodMin); + + time = (RwUInt32) timeGetTime(); + + timeEndPeriod(TimeCaps.wPeriodMin); + + return time; +} + +/* + ***************************************************************************** + */ +void +psMouseSetPos(RwV2d *pos) +{ + POINT point; + + point.x = (RwInt32) pos->x; + point.y = (RwInt32) pos->y; + + ClientToScreen(PSGLOBAL(window), &point); + + SetCursorPos(point.x, point.y); + + PSGLOBAL(lastMousePos.x) = (RwInt32)pos->x; + + PSGLOBAL(lastMousePos.y) = (RwInt32)pos->y; + + return; +} + +/* + ***************************************************************************** + */ +RwMemoryFunctions* +psGetMemoryFunctions(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR + return &memFuncs; +#else + return nil; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psInstallFileSystem(void) +{ + return (TRUE); +} + + +/* + ***************************************************************************** + */ +RwBool +psNativeTextureSupport(void) +{ + return RwD3D8DeviceSupportsDXTTexture(); +} + +/* + ***************************************************************************** + */ +static HWND +InitInstance(HANDLE instance) +{ + /* + * Perform any necessary initialization for this instance of the + * application. + * + * Create the MS Window's window instance for this application. The + * initial window size is given by the defined camera size. The window + * is not given a title as we set it during Init3D() with information + * about the version of RenderWare being used. + */ + + RECT rect; + + rect.left = rect.top = 0; + rect.right = RsGlobal.maximumWidth; + rect.bottom = RsGlobal.maximumHeight; + + AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); + + return CreateWindow(AppClassName, RsGlobal.appName, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + rect.right - rect.left, rect.bottom - rect.top, + (HWND)nil, (HMENU)nil, (HINSTANCE)instance, nil); +} + +void _GetVideoMemInfo(LPDWORD total, LPDWORD avaible) +{ + HRESULT hr; + LPDIRECTDRAW7 pDD7; + + hr = DirectDrawCreateEx(nil, (VOID**)&pDD7, IID_IDirectDraw7, nil); + + if ( FAILED(hr) ) + return; + + DDSCAPS2 caps; + + ZeroMemory(&caps, sizeof(DDSCAPS2)); + caps.dwCaps = DDSCAPS_VIDEOMEMORY; + + pDD7->GetAvailableVidMem(&caps, total, avaible); + + pDD7->Release(); +} + +/* + ***************************************************************************** + */ +typedef HRESULT(WINAPI * DIRECTDRAWCREATEEX)( GUID*, VOID**, REFIID, IUnknown* ); + + +//----------------------------------------------------------------------------- +// Name: GetDXVersion() +// Desc: This function returns the DirectX version number as follows: +// 0x0000 = No DirectX installed +// 0x0700 = At least DirectX 7 installed. +// 0x0800 = At least DirectX 8 installed. +// +// Please note that this code is intended as a general guideline. Your +// app will probably be able to simply query for functionality (via +// QueryInterface) for one or two components. +// +// Please also note: +// "if( dwDXVersion != 0x500 ) return FALSE;" is VERY BAD. +// "if( dwDXVersion < 0x500 ) return FALSE;" is MUCH BETTER. +// to ensure your app will run on future releases of DirectX. +//----------------------------------------------------------------------------- +DWORD GetDXVersion() +{ + DIRECTDRAWCREATEEX DirectDrawCreateEx = NULL; + HINSTANCE hDDrawDLL = nil; + HINSTANCE hD3D8DLL = nil; + HINSTANCE hDPNHPASTDLL = NULL; + DWORD dwDXVersion = 0; + //HRESULT hr; + + // First see if DDRAW.DLL even exists. + hDDrawDLL = LoadLibrary( "DDRAW.DLL" ); + if( hDDrawDLL == nil ) + { + dwDXVersion = 0; + OutputDebugString( "Couldn't LoadLibrary DDraw\r\n" ); + return dwDXVersion; + } + + + //------------------------------------------------------------------------- + // DirectX 7.0 Checks + //------------------------------------------------------------------------- + + // Check for DirectX 7 by creating a DDraw7 object + LPDIRECTDRAW7 pDD7; + DirectDrawCreateEx = (DIRECTDRAWCREATEEX)GetProcAddress( hDDrawDLL, + "DirectDrawCreateEx" ); + if( nil == DirectDrawCreateEx ) + { + FreeLibrary( hDDrawDLL ); + OutputDebugString( "Couldn't GetProcAddress DirectDrawCreateEx\r\n" ); + return dwDXVersion; + } + + if( FAILED( DirectDrawCreateEx( nil, (VOID**)&pDD7, IID_IDirectDraw7, + nil ) ) ) + { + FreeLibrary( hDDrawDLL ); + OutputDebugString( "Couldn't DirectDrawCreateEx\r\n" ); + return dwDXVersion; + } + + // DDraw7 was created successfully. We must be at least DX7.0 + dwDXVersion = 0x700; + pDD7->Release(); + +#ifdef USE_D3D9 + HINSTANCE hD3D9DLL = LoadLibrary("D3D9.DLL"); + if (hD3D9DLL != nil) { + FreeLibrary(hDDrawDLL); + FreeLibrary(hD3D9DLL); + + dwDXVersion = 0x900; + return dwDXVersion; + } +#endif + + //------------------------------------------------------------------------- + // DirectX 8.0 Checks + //------------------------------------------------------------------------- + + // Simply see if D3D8.dll exists. + hD3D8DLL = LoadLibrary( "D3D8.DLL" ); + if( hD3D8DLL == nil ) + { + FreeLibrary( hDDrawDLL ); + OutputDebugString( "Couldn't LoadLibrary D3D8.DLL\r\n" ); + return dwDXVersion; + } + + // D3D8.dll exists. We must be at least DX8.0 + dwDXVersion = 0x800; + + + //------------------------------------------------------------------------- + // DirectX 8.1 Checks + //------------------------------------------------------------------------- + + // Simply see if dpnhpast.dll exists. + hDPNHPASTDLL = LoadLibrary( "dpnhpast.dll" ); + if( hDPNHPASTDLL == nil ) + { + FreeLibrary( hDPNHPASTDLL ); + OutputDebugString( "Couldn't LoadLibrary dpnhpast.dll\r\n" ); + return dwDXVersion; + } + + // dpnhpast.dll exists. We must be at least DX8.1 + dwDXVersion = 0x801; + + + //------------------------------------------------------------------------- + // End of checking for versions of DirectX + //------------------------------------------------------------------------- + + // Close open libraries and return + FreeLibrary( hDDrawDLL ); + FreeLibrary( hD3D8DLL ); + + return dwDXVersion; +} + +/* + ***************************************************************************** + */ +#ifndef _WIN64 +static char cpuvendor[16] = "UnknownVendr"; +__declspec(naked) const char * _psGetCpuVendr() +{ + __asm + { + push ebx + xor eax, eax + cpuid + mov dword ptr [cpuvendor+0], ebx + mov dword ptr [cpuvendor+4], edx + mov dword ptr [cpuvendor+8], ecx + mov eax, offset cpuvendor + pop ebx + retn + } +} + +/* + ***************************************************************************** + */ +__declspec(naked) RwUInt32 _psGetCpuFeatures() +{ + __asm + { + mov eax, 1 + cpuid + mov eax, edx + retn + } +} + +/* + ***************************************************************************** + */ +__declspec(naked) RwUInt32 _psGetCpuFeaturesEx() +{ + __asm + { + mov eax, 80000000h + cpuid + + cmp eax, 80000000h + jbe short _NOEX + + mov eax, 80000001h + cpuid + + mov eax, edx + jmp short _RETEX + +_NOEX: + xor eax, eax + mov eax, eax + +_RETEX: + retn + } +} + +void _psPrintCpuInfo() +{ + RwUInt32 features = _psGetCpuFeatures(); + RwUInt32 FeaturesEx = _psGetCpuFeaturesEx(); + + debug("Running on a %s", _psGetCpuVendr()); + + if ( features & 0x800000 ) + debug("with MMX"); + if ( features & 0x2000000 ) + debug("with SSE"); + if ( FeaturesEx & 0x80000000 ) + debug("with 3DNow"); +} +#endif + +/* + ***************************************************************************** + */ +#ifdef UNDER_CE +#define CMDSTR LPWSTR +#else +#define CMDSTR LPSTR +#endif + +/* + ***************************************************************************** + */ +RwBool +psInitialize(void) +{ + PsGlobal.lastMousePos.x = PsGlobal.lastMousePos.y = 0.0f; + + RsGlobal.ps = &PsGlobal; + + PsGlobal.fullScreen = FALSE; + + PsGlobal.dinterface = nil; + PsGlobal.mouse = nil; + PsGlobal.joy1 = nil; + PsGlobal.joy2 = nil; + + CFileMgr::Initialise(); + +#ifdef PS2_MENU + CPad::Initialise(); + CPad::GetPad(0)->Mode = 0; + + CGame::frenchGame = false; + CGame::germanGame = false; + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + + WORD lang = PRIMARYLANGID(GetSystemDefaultLCID()); + if ( lang == LANG_ITALIAN ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + else if ( lang == LANG_SPANISH ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + else if ( lang == LANG_GERMAN ) + { + CGame::germanGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + } + else if ( lang == LANG_FRENCH ) + { + CGame::frenchGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + } + else + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); + + TheMemoryCard.Init(); +#else + C_PcSave::SetSaveDirectory(_psGetUserFilesFolder()); + + InitialiseLanguage(); +#if GTA_VERSION < GTA3_PC_11 + FrontEndMenuManager.LoadSettings(); +#endif + +#endif + + gGameState = GS_START_UP; + TRACE("gGameState = GS_START_UP"); +#ifndef _WIN64 + _psPrintCpuInfo(); +#endif + OSVERSIONINFO verInfo; + verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + GetVersionEx(&verInfo); + + _dwOperatingSystemVersion = OS_WIN95; + + if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + if ( verInfo.dwMajorVersion == 4 ) + { + debug("Operating System is WinNT\n"); + _dwOperatingSystemVersion = OS_WINNT; + } + else if ( verInfo.dwMajorVersion == 5 ) + { + debug("Operating System is Win2000\n"); + _dwOperatingSystemVersion = OS_WIN2000; + } + else if ( verInfo.dwMajorVersion > 5 ) + { + debug("Operating System is WinXP or greater\n"); + _dwOperatingSystemVersion = OS_WINXP; + } + } + else if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) + { + if ( verInfo.dwMajorVersion > 4 || verInfo.dwMajorVersion == 4 && verInfo.dwMinorVersion != 0 ) + { + debug("Operating System is Win98\n"); + _dwOperatingSystemVersion = OS_WIN98; + } + else + { + debug("Operating System is Win95\n"); + _dwOperatingSystemVersion = OS_WIN95; + } + } + +#ifndef PS2_MENU + +#if GTA_VERSION >= GTA3_PC_11 + FrontEndMenuManager.LoadSettings(); +#endif + +#endif + + dwDXVersion = GetDXVersion(); + debug("DirectX version 0x%x\n", dwDXVersion); + + if ( _dwOperatingSystemVersion == OS_WIN95 ) + { + MessageBoxW(nil, + (LPCWSTR)TheText.Get("WIN_95"), // Grand Theft Auto III cannot run on Windows 95 + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto III + MB_OK); + + return FALSE; + } + + if ( dwDXVersion < 0x801 ) + { + MessageBoxW(nil, + (LPCWSTR)TheText.Get("WIN_DX"), // Grand Theft Auto III requires at least DirectX version 8.1 + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto III + MB_OK); + + return FALSE; + } + + MEMORYSTATUS memstats; + GlobalMemoryStatus(&memstats); + + _dwMemTotalPhys = memstats.dwTotalPhys; + _dwMemAvailPhys = memstats.dwAvailPhys; + _dwMemTotalVirtual = memstats.dwTotalVirtual; + _dwMemAvailVirtual = memstats.dwAvailVirtual; + + _GetVideoMemInfo(&_dwMemTotalVideo, &_dwMemAvailVideo); +#ifdef FIX_BUGS + debug("Physical memory size %lu\n", _dwMemTotalPhys); + debug("Available physical memory %lu\n", _dwMemAvailPhys); + debug("Video memory size %lu\n", _dwMemTotalVideo); + debug("Available video memory %lu\n", _dwMemAvailVideo); +#else + debug("Physical memory size %d\n", _dwMemTotalPhys); + debug("Available physical memory %d\n", _dwMemAvailPhys); + debug("Video memory size %d\n", _dwMemTotalVideo); + debug("Available video memory %d\n", _dwMemAvailVideo); +#endif + + if ( _dwMemAvailVideo < (12 * 1024 * 1024) /*12 MB*/ ) + { + MessageBoxW(nil, + (LPCWSTR)TheText.Get("WIN_VDM"), // Grand Theft Auto III requires at least 12MB of available video memory + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto III + MB_OK); + + return FALSE; + } + + TheText.Unload(); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +void +psTerminate(void) +{ + return; +} + +/* + ***************************************************************************** + */ +static RwChar **_VMList; + +RwInt32 _psGetNumVideModes() +{ + return RwEngineGetNumVideoModes(); +} + +/* + ***************************************************************************** + */ +RwBool _psFreeVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + numModes = _psGetNumVideModes(); + + if ( _VMList == nil ) + return TRUE; + + for ( i = 0; i < numModes; i++ ) + { + RwFree(_VMList[i]); + } + + RwFree(_VMList); + + _VMList = nil; + + return TRUE; +} + +/* + ***************************************************************************** + */ +RwChar **_psGetVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + if ( _VMList != nil ) + { + return _VMList; + } + + numModes = RwEngineGetNumVideoModes(); + + _VMList = (RwChar **)RwCalloc(numModes, sizeof(RwChar*)); + + for ( i = 0; i < numModes; i++ ) + { + RwVideoMode vm; + + RwEngineGetVideoModeInfo(&vm, i); + + if ( vm.flags & rwVIDEOMODEEXCLUSIVE ) + { + if ( vm.width >= 640 + && vm.height >= 480 + && (vm.width == 640 + && vm.height == 480) + || !(vm.flags & rwVIDEOMODEEXCLUSIVE) + || (_dwMemTotalVideo - vm.depth * vm.height * vm.width / 8) > (12 * 1024 * 1024)/*12 MB*/ ) + { + _VMList[i] = (RwChar*)RwCalloc(100, sizeof(RwChar)); + rwsprintf(_VMList[i],"%lu X %lu X %lu", vm.width, vm.height, vm.depth); + } + else + _VMList[i] = nil; + } + else + _VMList[i] = nil; + } + + return _VMList; +} + +/* + ***************************************************************************** + */ +void _psSelectScreenVM(RwInt32 videoMode) +{ + RwTexDictionarySetCurrent( nil ); + + FrontEndMenuManager.UnloadTextures(); + + if ( !_psSetVideoMode(RwEngineGetCurrentSubSystem(), videoMode) ) + { + RsGlobal.quit = TRUE; + + ShowWindow(PSGLOBAL(window), SW_HIDE); + + MessageBoxW(nil, + (LPCWSTR)TheText.Get("WIN_RSZ"), // Failed to select new screen resolution + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto III + MB_OK); + } + else + FrontEndMenuManager.LoadAllTextures(); +} + +/* + ***************************************************************************** + */ +void WaitForState(FILTER_STATE State) +{ + HRESULT hr; + + ASSERT(pMC != nil); + + // Make sure we have switched to the required state + LONG lfs; + do + { + hr = pMC->GetState(10, &lfs); + } while (State != lfs); +} + +/* + ***************************************************************************** + */ +void HandleGraphEvent(void) +{ + LONG evCode; + LONG_PTR evParam1, evParam2; + HRESULT hr=S_OK; + + ASSERT(pME != nil); + + // Process all queued events + while (SUCCEEDED(pME->GetEvent(&evCode, &evParam1, &evParam2, 0))) + { + // Free memory associated with callback, since we're not using it + hr = pME->FreeEventParams(evCode, evParam1, evParam2); + + // If this is the end of the clip, reset to beginning + if (EC_COMPLETE == evCode) + { + switch (gGameState) + { + case GS_LOGO_MPEG: + { + gGameState = GS_INIT_INTRO_MPEG; + TRACE("gGameState = GS_INIT_INTRO_MPEG"); + break; + } + case GS_INTRO_MPEG: + { + gGameState = GS_INIT_ONCE; + TRACE("gGameState = GS_INIT_ONCE"); + break; + } + default: + { + break; + } + } + + pME->SetNotifyWindow((OAHWND)NULL, 0, 0); + } + } +} + +/* + ***************************************************************************** + */ + +LRESULT CALLBACK +MainWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) +{ + POINTS points; + static BOOL noMemory = FALSE; + + + switch( message ) + { + case WM_SETCURSOR: + { + ShowCursor(FALSE); + + SetCursor(nil); + + break; // is this correct ? + } + + case WM_SIZE: + { + RwRect r; + + r.x = 0; + r.y = 0; + r.w = LOWORD(lParam); + r.h = HIWORD(lParam); + + if (RwInitialised && r.h > 0 && r.w > 0) + { + RsEventHandler(rsCAMERASIZE, &r); + + if (r.w != LOWORD(lParam) && r.h != HIWORD(lParam)) + { + WINDOWPLACEMENT wp; + + /* failed to create window of required size */ + noMemory = TRUE; + + /* stop re-sizing */ + ReleaseCapture(); + + /* handle maximised window */ + GetWindowPlacement(window, &wp); + if (wp.showCmd == SW_SHOWMAXIMIZED) + { + SendMessage(window, WM_WINDOWPOSCHANGED, 0, 0); + } + } + else + { + noMemory = FALSE; + } + + } + + return 0L; + } + + case WM_SIZING: + { + /* + * Handle event to ensure window contents are displayed during re-size + * as this can be disabled by the user, then if there is not enough + * memory things don't work. + */ + RECT *newPos = (LPRECT) lParam; + RECT rect; + + /* redraw window */ + + if (RwInitialised && gGameState == GS_PLAYING_GAME) + { + RsEventHandler(rsIDLE, (void *)TRUE); + } + + /* Manually resize window */ + rect.left = rect.top = 0; + rect.bottom = newPos->bottom - newPos->top; + rect.right = newPos->right - newPos->left; + + SetWindowPos(window, HWND_TOP, rect.left, rect.top, + (rect.right - rect.left), + (rect.bottom - rect.top), SWP_NOMOVE); + + return 0L; + } + + case WM_LBUTTONDOWN: + { + SetCapture(window); + + return 0L; + } + + case WM_RBUTTONDOWN: + { + SetCapture(window); + + return 0L; + } + + case WM_MBUTTONDOWN: + { + SetCapture(window); + + return 0L; + } + + case WM_MOUSEWHEEL: + { + return 0L; + } + + case WM_MOUSEMOVE: + { + points = MAKEPOINTS(lParam); + + FrontEndMenuManager.m_nMouseTempPosX = points.x; + FrontEndMenuManager.m_nMouseTempPosY = points.y; + + return 0L; + } + + case WM_LBUTTONUP: + { + ReleaseCapture(); + + return 0L; + } + + case WM_RBUTTONUP: + { + ReleaseCapture(); + + return 0L; + } + + case WM_MBUTTONUP: + { + ReleaseCapture(); + + return 0L; + } + + case WM_KEYDOWN: + { + RsKeyCodes ks; + + if ( _InputTranslateKey(&ks, lParam, wParam) ) + RsKeyboardEventHandler(rsKEYDOWN, &ks); + + if ( wParam == VK_SHIFT ) + _InputTranslateShiftKeyUpDown(&ks); +#ifdef FIX_BUGS + break; +#else + return 0L; +#endif + } + + case WM_KEYUP: + { + RsKeyCodes ks; + + if ( _InputTranslateKey(&ks, lParam, wParam) ) + RsKeyboardEventHandler(rsKEYUP, &ks); + + if ( wParam == VK_SHIFT ) + _InputTranslateShiftKeyUpDown(&ks); + +#ifdef FIX_BUGS + break; +#else + return 0L; +#endif + } + + case WM_SYSKEYDOWN: + { + RsKeyCodes ks; + + if ( _InputTranslateKey(&ks, lParam, wParam) ) + RsKeyboardEventHandler(rsKEYDOWN, &ks); + + if ( wParam == VK_SHIFT ) + _InputTranslateShiftKeyUpDown(&ks); + +#ifdef FIX_BUGS + break; +#else + return 0L; +#endif + } + + case WM_SYSKEYUP: + { + RsKeyCodes ks; + + if ( _InputTranslateKey(&ks, lParam, wParam) ) + RsKeyboardEventHandler(rsKEYUP, &ks); + + if ( wParam == VK_SHIFT ) + _InputTranslateShiftKeyUpDown(&ks); + +#ifdef FIX_BUGS + break; +#else + return 0L; +#endif + } + + case WM_ACTIVATEAPP: + { + switch ( gGameState ) + { + case GS_LOGO_MPEG: + case GS_INTRO_MPEG: + { + ASSERT(pMC != nil); + + LONG state; + pMC->GetState(10, &state); + + if ( !(BOOL)wParam ) // losing activation + { + if ( state == State_Running && pMC != nil ) + { + HRESULT hr = pMC->Pause(); + + if (hr == S_FALSE) + OutputDebugString("Failed to pause the MPEG"); + else + WaitForState(State_Paused); + } + } + else + { + CenterVideo(); + + if ( state != State_Running && pMC != nil ) + { + HRESULT hr = pMC->Run(); + + if ( hr == S_FALSE ) + OutputDebugString("Failed to run the MPEG"); + else + { + WaitForState(State_Running); + SetFocus(PSGLOBAL(window)); + } + } + } + + break; + } + + case GS_START_UP: + { + if ( !(BOOL)wParam && PSGLOBAL(fullScreen) ) // losing activation + startupDeactivate = TRUE; + + break; + } + } + + CPad::GetPad(0)->Clear(false); + CPad::GetPad(1)->Clear(false); + + return 0L; + } + + case WM_TIMER: + { + return 0L; + } + + case WM_GRAPHNOTIFY: + { + if (gGameState == GS_INTRO_MPEG || gGameState == GS_LOGO_MPEG) + HandleGraphEvent(); + + break; + } + + case WM_CLOSE: + case WM_DESTROY: + { + /* + * Quit message handling. + */ + ClipCursor(nil); + + _InputShutdown(); + + PostQuitMessage(0); + + return 0L; + } + + case WM_DEVICECHANGE: + { + if( wParam == DBT_DEVICEREMOVECOMPLETE ) + { + PDEV_BROADCAST_HDR pDev = (PDEV_BROADCAST_HDR)lParam; + + if (pDev->dbch_devicetype != DBT_DEVTYP_VOLUME) + break; + + if ( DMAudio.IsAudioInitialised() ) + { + PDEV_BROADCAST_VOLUME pVol = (PDEV_BROADCAST_VOLUME)pDev; + if ( pVol->dbcv_flags & DBTF_MEDIA ) + { + char c = DMAudio.GetCDAudioDriveLetter(); + + if ( c >= 'A' && pVol->dbcv_unitmask & (1 << (c - 'A')) ) + { + OutputDebugString("About to check CD drive..."); + + while ( true ) + { + FrontEndMenuManager.WaitForUserCD(); + + if ( !FrontEndMenuManager.m_bQuitGameNoCD ) + { + if ( DMAudio.CheckForAnAudioFileOnCD() ) + { + OutputDebugString("GTA3 Audio CD has been inserted"); + break; + } + } + else + { + OutputDebugString("Exiting game as Audio CD was not inserted"); + break; + } + } + } + } + } + } + + break; + } + + } + + /* + * Let Windows handle all other messages. + */ + return DefWindowProc(window, message, wParam, lParam); +} + + +/* + ***************************************************************************** + */ +static BOOL +InitApplication(HANDLE instance) +{ + /* + * Perform any necessary MS Windows application initialization. Basically, + * this means registering the window class for this application. + */ + + WNDCLASS windowClass; + + windowClass.style = CS_BYTEALIGNWINDOW; + windowClass.lpfnWndProc = (WNDPROC)MainWndProc; + windowClass.cbClsExtra = 0; + windowClass.cbWndExtra = 0; + windowClass.hInstance = (HINSTANCE)instance; +#ifdef FIX_BUGS + windowClass.hIcon = LoadIcon((HINSTANCE)instance, MAKEINTRESOURCE(IDI_MAIN_ICON)); +#else + windowClass.hIcon = nil; +#endif + windowClass.hCursor = LoadCursor(nil, IDC_ARROW); + windowClass.hbrBackground = nil; + windowClass.lpszMenuName = NULL; + windowClass.lpszClassName = AppClassName; + + return RegisterClass(&windowClass); +} + + +/* + ***************************************************************************** + */ + +RwBool IsForegroundApp() +{ + return !!ForegroundApp; +} + +UINT GetBestRefreshRate(UINT width, UINT height, UINT depth) +{ +#ifdef USE_D3D9 + LPDIRECT3D9 d3d = Direct3DCreate9(D3D_SDK_VERSION); +#else + LPDIRECT3D8 d3d = Direct3DCreate8(D3D_SDK_VERSION); +#endif + ASSERT(d3d != nil); + + UINT refreshRate = INT_MAX; + D3DFORMAT format; + + if ( depth == 32 ) + format = D3DFMT_X8R8G8B8; + else if ( depth == 24 ) + format = D3DFMT_R8G8B8; + else + format = D3DFMT_R5G6B5; + +#ifdef USE_D3D9 + UINT modeCount = d3d->GetAdapterModeCount(GcurSel, format); +#else + UINT modeCount = d3d->GetAdapterModeCount(GcurSel); +#endif + + for ( UINT i = 0; i < modeCount; i++ ) + { + D3DDISPLAYMODE mode; + +#ifdef USE_D3D9 + d3d->EnumAdapterModes(GcurSel, format, i, &mode); +#else + d3d->EnumAdapterModes(GcurSel, i, &mode); +#endif + if ( mode.Width == width && mode.Height == height && mode.Format == format ) + { + if ( mode.RefreshRate == 0 ) { + // From VC +#ifdef FIX_BUGS + d3d->Release(); +#endif + return 0; + } + + if ( mode.RefreshRate < refreshRate && mode.RefreshRate >= 60 ) + refreshRate = mode.RefreshRate; + } + } + + // From VC +#ifdef FIX_BUGS + d3d->Release(); +#endif + + if ( refreshRate == -1 ) + return -1; + + return refreshRate; +} + +/* + ***************************************************************************** + */ +RwBool +psSelectDevice() +{ + RwVideoMode vm; + RwInt32 subSysNum; + RwInt32 AutoRenderer = 0; + + + RwBool modeFound = FALSE; + + if ( !useDefault ) + { + GnumSubSystems = RwEngineGetNumSubSystems(); + if ( !GnumSubSystems ) + { + return FALSE; + } + + /* Just to be sure ... */ + GnumSubSystems = (GnumSubSystems > MAX_SUBSYSTEMS) ? MAX_SUBSYSTEMS : GnumSubSystems; + + /* Get the names of all the sub systems */ + for (subSysNum = 0; subSysNum < GnumSubSystems; subSysNum++) + { + RwEngineGetSubSystemInfo(&GsubSysInfo[subSysNum], subSysNum); + } + + /* Get the default selection */ + GcurSel = RwEngineGetCurrentSubSystem(); +#ifdef IMPROVED_VIDEOMODE + if(FrontEndMenuManager.m_nPrefsSubsystem < GnumSubSystems) + GcurSel = FrontEndMenuManager.m_nPrefsSubsystem; +#endif + } + + /* Set the driver to use the correct sub system */ + if (!RwEngineSetSubSystem(GcurSel)) + { + return FALSE; + } + +#ifdef IMPROVED_VIDEOMODE + FrontEndMenuManager.m_nPrefsSubsystem = GcurSel; +#endif + +#ifndef IMPROVED_VIDEOMODE + if ( !useDefault ) + { + if ( _psGetVideoModeList()[FrontEndMenuManager.m_nDisplayVideoMode] && FrontEndMenuManager.m_nDisplayVideoMode ) + { + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + GcurSelVM = FrontEndMenuManager.m_nDisplayVideoMode; + } + else + { +#ifdef DEFAULT_NATIVE_RESOLUTION + // get the native video mode + HDC hDevice = GetDC(NULL); + int w = GetDeviceCaps(hDevice, HORZRES); + int h = GetDeviceCaps(hDevice, VERTRES); + int d = GetDeviceCaps(hDevice, BITSPIXEL); +#else + const int w = 640; + const int h = 480; + const int d = 16; +#endif + while ( !modeFound && GcurSelVM < RwEngineGetNumVideoModes() ) + { + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + if ( defaultFullscreenRes && vm.width != w + || vm.height != h + || vm.depth != d + || !(vm.flags & rwVIDEOMODEEXCLUSIVE) ) + ++GcurSelVM; + else + modeFound = TRUE; + } + + if ( !modeFound ) + { +#ifdef DEFAULT_NATIVE_RESOLUTION + GcurSelVM = 1; +#else + MessageBox(nil, "Cannot find 640x480 video mode", "GTA3", MB_OK); + return FALSE; +#endif + } + } + } +#else + if ( !useDefault ) + { + if(FrontEndMenuManager.m_nPrefsWidth == 0 || + FrontEndMenuManager.m_nPrefsHeight == 0 || + FrontEndMenuManager.m_nPrefsDepth == 0){ + // Defaults if nothing specified + FrontEndMenuManager.m_nPrefsWidth = GetSystemMetrics(SM_CXSCREEN); + FrontEndMenuManager.m_nPrefsHeight = GetSystemMetrics(SM_CYSCREEN); + FrontEndMenuManager.m_nPrefsDepth = 32; + FrontEndMenuManager.m_nPrefsWindowed = 0; + } + + // Find the videomode that best fits what we got from the settings file + RwInt32 bestFsMode = -1; + RwInt32 bestWidth = -1; + RwInt32 bestHeight = -1; + RwInt32 bestDepth = -1; + for (GcurSelVM = 0; GcurSelVM < RwEngineGetNumVideoModes(); GcurSelVM++) { + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + if (!(vm.flags & rwVIDEOMODEEXCLUSIVE)) { + bestWndMode = GcurSelVM; + } else { + // try the largest one that isn't larger than what we wanted + if (vm.width >= bestWidth && vm.width <= FrontEndMenuManager.m_nPrefsWidth && + vm.height >= bestHeight && vm.height <= FrontEndMenuManager.m_nPrefsHeight && + vm.depth >= bestDepth && vm.depth <= FrontEndMenuManager.m_nPrefsDepth){ + bestWidth = vm.width; + bestHeight = vm.height; + bestDepth = vm.depth; + bestFsMode = GcurSelVM; + } + } + } + + if(bestFsMode < 0){ + MessageBox(nil, "Cannot find desired video mode", "GTA3", MB_OK); + return FALSE; + } + GcurSelVM = bestFsMode; + + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + + FrontEndMenuManager.m_nSelectedScreenMode = FrontEndMenuManager.m_nPrefsWindowed; + } +#endif + + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + +#ifdef IMPROVED_VIDEOMODE + if (FrontEndMenuManager.m_nPrefsWindowed) + GcurSelVM = bestWndMode; + + // Now GcurSelVM is 0 but vm has sizes(and fullscreen flag) of the video mode we want, that's why we changed the rwVIDEOMODEEXCLUSIVE conditions below + FrontEndMenuManager.m_nPrefsWidth = vm.width; + FrontEndMenuManager.m_nPrefsHeight = vm.height; + FrontEndMenuManager.m_nPrefsDepth = vm.depth; +#endif + +#ifndef PS2_MENU + FrontEndMenuManager.m_nCurrOption = 0; +#endif + + /* Set up the video mode and set the apps window + * dimensions to match */ + if (!RwEngineSetVideoMode(GcurSelVM)) + { + return FALSE; + } + +#ifdef IMPROVED_VIDEOMODE + if (!FrontEndMenuManager.m_nPrefsWindowed) +#else + if (vm.flags & rwVIDEOMODEEXCLUSIVE) +#endif + { + debug("%dx%dx%d", vm.width, vm.height, vm.depth); + + UINT refresh = GetBestRefreshRate(vm.width, vm.height, vm.depth); + + if ( refresh != (UINT)-1 ) + { + debug("refresh %d", refresh); + RwD3D8EngineSetRefreshRate((RwUInt32)refresh); + } + } + +#ifdef IMPROVED_VIDEOMODE + if (!FrontEndMenuManager.m_nPrefsWindowed) +#else + if (vm.flags & rwVIDEOMODEEXCLUSIVE) +#endif + { + RsGlobal.maximumWidth = vm.width; + RsGlobal.maximumHeight = vm.height; + RsGlobal.width = vm.width; + RsGlobal.height = vm.height; + + PSGLOBAL(fullScreen) = TRUE; + +#ifdef IMPROVED_VIDEOMODE + SetWindowLong(PSGLOBAL(window), GWL_STYLE, WS_POPUP); + SetWindowPos(PSGLOBAL(window), nil, 0, 0, 0, 0, + SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER| + SWP_FRAMECHANGED); + }else{ + RECT rect; + rect.left = rect.top = 0; + rect.right = FrontEndMenuManager.m_nPrefsWidth; + rect.bottom = FrontEndMenuManager.m_nPrefsHeight; + AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); + + // center it + int spaceX = GetSystemMetrics(SM_CXSCREEN) - (rect.right-rect.left); + int spaceY = GetSystemMetrics(SM_CYSCREEN) - (rect.bottom-rect.top); + + SetWindowLong(PSGLOBAL(window), GWL_STYLE, WS_VISIBLE | WS_OVERLAPPEDWINDOW); + SetWindowPos(PSGLOBAL(window), HWND_NOTOPMOST, spaceX/2, spaceY/2, + (rect.right - rect.left), + (rect.bottom - rect.top), 0); + + // Have to get actual size because the window perhaps didn't fit + GetClientRect(PSGLOBAL(window), &rect); + RsGlobal.maximumWidth = rect.right; + RsGlobal.maximumHeight = rect.bottom; + RsGlobal.width = rect.right; + RsGlobal.height = rect.bottom; + + PSGLOBAL(fullScreen) = FALSE; +#endif + } +#ifdef MULTISAMPLING + RwD3D8EngineSetMultiSamplingLevels(1 << FrontEndMenuManager.m_nPrefsMSAALevel); +#endif + return TRUE; +} + +/* + ***************************************************************************** + */ +RwBool _psSetVideoMode(RwInt32 subSystem, RwInt32 videoMode) +{ + RwInitialised = FALSE; + + RsEventHandler(rsRWTERMINATE, nil); + + GcurSel = subSystem; + GcurSelVM = videoMode; + + useDefault = TRUE; + + if ( RsEventHandler(rsRWINITIALIZE, PSGLOBAL(window)) == rsEVENTERROR ) + return FALSE; + + RwInitialised = TRUE; + useDefault = FALSE; + + RwRect r; + + r.x = 0; + r.y = 0; + r.w = RsGlobal.maximumWidth; + r.h = RsGlobal.maximumHeight; + + RsEventHandler(rsCAMERASIZE, &r); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +static RwChar ** +CommandLineToArgv(RwChar *cmdLine, RwInt32 *argCount) +{ + RwInt32 numArgs = 0; + RwBool inArg, inString; + RwInt32 i, len; + RwChar *res, *str, **aptr; + + len = (int)strlen(cmdLine); + + /* + * Count the number of arguments... + */ + inString = FALSE; + inArg = FALSE; + + for(i=0; i<=len; i++) + { + if( cmdLine[i] == '"' ) + { + inString = !inString; + } + + if( (cmdLine[i] <= ' ' && !inString) || i == len ) + { + if( inArg ) + { + inArg = FALSE; + + numArgs++; + } + } + else if( !inArg ) + { + inArg = TRUE; + } + } + + /* + * Allocate memory for result... + */ + res = (RwChar *)malloc(sizeof(RwChar *) * numArgs + len + 1); + str = res + sizeof(RwChar *) * numArgs; + aptr = (RwChar **)res; + + strcpy(str, cmdLine); + + /* + * Walk through cmdLine again this time setting pointer to each arg... + */ + inArg = FALSE; + inString = FALSE; + + for(i=0; i<=len; i++) + { + if( cmdLine[i] == '"' ) + { + inString = !inString; + } + + if( (cmdLine[i] <= ' ' && !inString) || i == len ) + { + if( inArg ) + { + if( str[i-1] == '"' ) + { + str[i-1] = '\0'; + } + else + { + str[i] = '\0'; + } + + inArg = FALSE; + } + } + else if( !inArg && cmdLine[i] != '"' ) + { + inArg = TRUE; + + *aptr++ = &str[i]; + } + } + + *argCount = numArgs; + + return (RwChar **)res; +} + +/* + ***************************************************************************** + */ +void InitialiseLanguage() +{ + WORD primUserLCID = PRIMARYLANGID(GetSystemDefaultLCID()); + WORD primSystemLCID = PRIMARYLANGID(GetUserDefaultLCID()); + WORD primLayout = PRIMARYLANGID((DWORD_PTR)GetKeyboardLayout(0)); + + WORD subUserLCID = SUBLANGID(GetSystemDefaultLCID()); + WORD subSystemLCID = SUBLANGID(GetUserDefaultLCID()); + WORD subLayout = SUBLANGID((DWORD_PTR)GetKeyboardLayout(0)); + + if ( primUserLCID == LANG_GERMAN + || primSystemLCID == LANG_GERMAN + || primLayout == LANG_GERMAN ) + { + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CGame::germanGame = true; + } + + if ( primUserLCID == LANG_FRENCH + || primSystemLCID == LANG_FRENCH + || primLayout == LANG_FRENCH ) + { + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CGame::frenchGame = true; + } + + if ( subUserLCID == SUBLANG_ENGLISH_AUS + || subSystemLCID == SUBLANG_ENGLISH_AUS + || subLayout == SUBLANG_ENGLISH_AUS ) + CGame::noProstitutes = true; + +#ifdef NASTY_GAME + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + CGame::noProstitutes = false; +#endif + + int32 lang; + + switch ( primSystemLCID ) + { + case LANG_GERMAN: + { + lang = LANG_GERMAN; + break; + } + case LANG_FRENCH: + { + lang = LANG_FRENCH; + break; + } + case LANG_SPANISH: + { + lang = LANG_SPANISH; + break; + } + case LANG_ITALIAN: + { + lang = LANG_ITALIAN; + break; + } + default: + { + lang = ( subSystemLCID == SUBLANG_ENGLISH_AUS ) ? -99 : LANG_ENGLISH; + break; + } + } + + CMenuManager::OS_Language = primUserLCID; + + switch ( lang ) + { + case LANG_GERMAN: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + break; + } + case LANG_SPANISH: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + break; + } + case LANG_FRENCH: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + break; + } + case LANG_ITALIAN: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + break; + } + default: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + break; + } + } + + TheText.Unload(); + TheText.Load(); +} + +/* + ***************************************************************************** + */ +void CenterVideo(void) +{ + HRESULT hr = S_OK; + RECT rect; + + ASSERT(pVW != nil); + + GetClientRect(PSGLOBAL(window), &rect); + + JIF(pVW->SetWindowPosition(rect.left, rect.top, rect.right, rect.bottom)); + + JIF(pVW->put_MessageDrain((OAHWND) PSGLOBAL(window))); + + SetFocus(PSGLOBAL(window)); +} + +/* + ***************************************************************************** + */ +void PlayMovieInWindow(int cmdShow, const char* szFile) +{ + WCHAR wFileName[256]; + HRESULT hr; + + // Clear open dialog remnants before calling RenderFile() + UpdateWindow(PSGLOBAL(window)); + + // Convert filename to wide character string + MultiByteToWideChar(CP_ACP, 0, szFile, -1, wFileName, sizeof(wFileName) - 1); + + // Initialize COM +#ifdef FIX_BUGS // will also return S_FALSE if it has already been inited in the same thread + CoInitialize(nil); +#else + JIF(CoInitialize(nil)); +#endif + + // Get the interface for DirectShow's GraphBuilder + JIF(CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC, + IID_IGraphBuilder, (void **)&pGB)); + + if(SUCCEEDED(hr)) + { + // Have the graph builder construct its the appropriate graph automatically + JIF(pGB->RenderFile(&wFileName[0], nil)); + + // QueryInterface for DirectShow interfaces + JIF(pGB->QueryInterface(IID_IMediaControl, (void **)&pMC)); + JIF(pGB->QueryInterface(IID_IMediaEventEx, (void **)&pME)); + JIF(pGB->QueryInterface(IID_IMediaSeeking, (void **)&pMS)); + + // Query for video interfaces, which may not be relevant for audio files + JIF(pGB->QueryInterface(IID_IVideoWindow, (void **)&pVW)); + + JIF(pVW->put_Owner((OAHWND) PSGLOBAL(window))); + JIF(pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN)); + + // Have the graph signal event via window callbacks for performance + JIF(pME->SetNotifyWindow((OAHWND)PSGLOBAL(window), WM_GRAPHNOTIFY, 0)); + + CenterVideo(); + + // Run the graph to play the media file + JIF(pMC->Run()); + + SetFocus(PSGLOBAL(window)); + } + + ASSERT(pGB != nil); + ASSERT(pVW != nil); + ASSERT(pME != nil); + ASSERT(pMC != nil); + + if(FAILED(hr)) + CloseClip(); +} + +/* + ***************************************************************************** + */ +void CloseInterfaces(void) +{ + // Release and zero DirectShow interfaces + SAFE_RELEASE(pME); + SAFE_RELEASE(pMS); + SAFE_RELEASE(pMC); + SAFE_RELEASE(pVW); + SAFE_RELEASE(pGB); +} + +/* + ***************************************************************************** + */ +void CloseClip(void) +{ + HRESULT hr; + + // Stop playback + if(pMC) + hr = pMC->Stop(); + + // Free DirectShow interfaces + CloseInterfaces(); +} + +/* + ***************************************************************************** + */ +void HandleExit() +{ + MSG message; + while ( PeekMessage(&message, nil, 0U, 0U, PM_REMOVE|PM_NOYIELD) ) + { + if( message.message == WM_QUIT ) + { + RsGlobal.quit = TRUE; + } + else + { + TranslateMessage(&message); + DispatchMessage(&message); + } + } +} + +/* + ***************************************************************************** + */ +int PASCAL +WinMain(HINSTANCE instance, + HINSTANCE prevInstance __RWUNUSED__, + CMDSTR cmdLine, + int cmdShow) +{ + MSG message; + RwV2d pos; + RwInt32 argc, i; + RwChar **argv; + SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nil, SPIF_SENDCHANGE); + +#ifndef MASTER + if (strstr(cmdLine, "-console")) + { + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + } +#endif + +#ifdef USE_CUSTOM_ALLOCATOR + InitMemoryMgr(); +#endif + + /* + * Initialize the platform independent data. + * This will in turn initialize the platform specific data... + */ + if( RsEventHandler(rsINITIALIZE, nil) == rsEVENTERROR ) + { + return FALSE; + } + + /* + * Register the window class... + */ + if( !InitApplication(instance) ) + { + return FALSE; + } + + /* + * Get proper command line params, cmdLine passed to us does not + * work properly under all circumstances... + */ + cmdLine = GetCommandLine(); + + /* + * Parse command line into standard (argv, argc) parameters... + */ + argv = CommandLineToArgv(cmdLine, &argc); + + + /* + * Parse command line parameters (except program name) one at + * a time BEFORE RenderWare initialization... + */ + for(i=1; iGetLeftMouseJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetEnterJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetCharJustDown(' ') ) + ++gGameState; + else if ( CPad::GetPad(0)->GetAltJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetTabJustDown() ) + ++gGameState; + + break; + } + + case GS_INIT_INTRO_MPEG: + { +#ifdef NO_MOVIES + if (!gbNoMovies) +#endif + CloseClip(); +#ifndef FIX_BUGS + CoUninitialize(); +#endif + + if ( CMenuManager::OS_Language == LANG_FRENCH || CMenuManager::OS_Language == LANG_GERMAN ) + PlayMovieInWindow(cmdShow, "movies\\GTAtitlesGER.mpg"); + else + PlayMovieInWindow(cmdShow, "movies\\GTAtitles.mpg"); + + gGameState = GS_INTRO_MPEG; + TRACE("gGameState = GS_INTRO_MPEG;"); + break; + } + + case GS_INTRO_MPEG: + { + CPad::UpdatePads(); + + if ( startupDeactivate || ControlsManager.GetJoyButtonJustDown() != 0 ) + ++gGameState; + else if ( CPad::GetPad(0)->GetLeftMouseJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetEnterJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetCharJustDown(' ') ) + ++gGameState; + else if ( CPad::GetPad(0)->GetAltJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetTabJustDown() ) + ++gGameState; + + break; + } + + case GS_INIT_ONCE: + { +#ifdef NO_MOVIES + if (!gbNoMovies) +#endif + CloseClip(); +#ifndef FIX_BUGS + CoUninitialize(); +#endif + +#ifdef FIX_BUGS + // draw one frame because otherwise we'll end up looking at black screen for a while if vsync is on + RsCameraShowRaster(Scene.camera); +#endif + +#ifdef PS2_MENU + extern char version_name[64]; + if ( CGame::frenchGame || CGame::germanGame ) + LoadingScreen(NULL, version_name, "loadsc24"); + else + LoadingScreen(NULL, version_name, "loadsc0"); + + printf("Into TheGame!!!\n"); +#else + LoadingScreen(nil, nil, "loadsc0"); +#endif + if ( !CGame::InitialiseOnceAfterRW() ) + RsGlobal.quit = TRUE; + +#ifdef PS2_MENU + gGameState = GS_INIT_PLAYING_GAME; +#else + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); +#endif + break; + } + +#ifndef PS2_MENU + case GS_INIT_FRONTEND: + { + LoadingScreen(nil, nil, "loadsc0"); + + FrontEndMenuManager.m_bGameNotLoaded = true; + + CMenuManager::m_bStartUpFrontEndRequested = true; + + if ( defaultFullscreenRes ) + { + defaultFullscreenRes = FALSE; + FrontEndMenuManager.m_nPrefsVideoMode = GcurSelVM; + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + } + + gGameState = GS_FRONTEND; + TRACE("gGameState = GS_FRONTEND;"); + break; + } + + case GS_FRONTEND: + { + GetWindowPlacement(PSGLOBAL(window), &wp); + + if (wp.showCmd != SW_SHOWMINIMIZED) + RsEventHandler(rsFRONTENDIDLE, nil); + +#ifdef PS2_MENU + if ( !FrontEndMenuManager.m_bMenuActive || TheMemoryCard.m_bWantToLoad ) +#else + if ( !FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bWantToLoad ) +#endif + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + +#ifdef PS2_MENU + if (TheMemoryCard.m_bWantToLoad ) +#else + if ( FrontEndMenuManager.m_bWantToLoad ) +#endif + { + InitialiseGame(); + FrontEndMenuManager.m_bGameNotLoaded = false; + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + } + break; + } +#endif + + case GS_INIT_PLAYING_GAME: + { +#ifdef PS2_MENU + CGame::Initialise("DATA\\GTA3.DAT"); + + //LoadingScreen("Starting Game", NULL, GetRandomSplashScreen()); + + if ( TheMemoryCard.CheckCardInserted(CARD_ONE) == CMemoryCard::NO_ERR_SUCCESS + && TheMemoryCard.ChangeDirectory(CARD_ONE, TheMemoryCard.Cards[CARD_ONE].dir) + && TheMemoryCard.FindMostRecentFileName(CARD_ONE, TheMemoryCard.MostRecentFile) == true + && TheMemoryCard.CheckDataNotCorrupt(TheMemoryCard.MostRecentFile)) + { + strcpy(TheMemoryCard.LoadFileName, TheMemoryCard.MostRecentFile); + TheMemoryCard.b_FoundRecentSavedGameWantToLoad = true; + + if (CMenuManager::m_PrefsLanguage != TheMemoryCard.GetLanguageToLoad()) + { + CMenuManager::m_PrefsLanguage = TheMemoryCard.GetLanguageToLoad(); + TheText.Unload(); + TheText.Load(); + } + + CGame::currLevel = (eLevelName)TheMemoryCard.GetLevelToLoad(); + } +#else + InitialiseGame(); + + FrontEndMenuManager.m_bGameNotLoaded = false; +#endif + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + break; + } + + case GS_PLAYING_GAME: + { + float ms = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); + if ( RwInitialised ) + { + if (!CMenuManager::m_PrefsFrameLimiter || (1000.0f / (float)RsGlobal.maxFPS) < ms) + RsEventHandler(rsIDLE, (void *)TRUE); + } + break; + } + } + } + else + { + if ( RwCameraBeginUpdate(Scene.camera) ) + { + RwCameraEndUpdate(Scene.camera); + ForegroundApp = TRUE; + RsEventHandler(rsACTIVATE, (void *)TRUE); + } + + WaitMessage(); + } + } + + + /* + * About to shut down - block resize events again... + */ + RwInitialised = FALSE; + + FrontEndMenuManager.UnloadTextures(); +#ifdef PS2_MENU + if ( !(FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad)) + break; +#else + if ( !FrontEndMenuManager.m_bWantToRestart ) + break; +#endif + + CPad::ResetCheats(); + CPad::StopPadsShaking(); + + DMAudio.ChangeMusicMode(MUSICMODE_DISABLE); + +#ifdef PS2_MENU + CGame::ShutDownForRestart(); +#endif + CTimer::Stop(); + +#ifdef PS2_MENU + if (FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + if (TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + FrontEndMenuManager.m_bWantToRestart = true; + TheMemoryCard.m_bWantToLoad = true; + } + + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + FrontEndMenuManager.m_bWantToRestart = false; + + continue; + } + + CGame::ShutDown(); + CTimer::Stop(); + + break; +#else + if ( FrontEndMenuManager.m_bWantToLoad ) + { + CGame::ShutDownForRestart(); + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + FrontEndMenuManager.m_bWantToLoad = false; + } + else + { +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + CTimer::Stop(); + + if ( FrontEndMenuManager.m_bFirstTime == true ) + { + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); + } + else + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + } + + FrontEndMenuManager.m_bFirstTime = false; + FrontEndMenuManager.m_bWantToRestart = false; +#endif + } + + +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + DMAudio.Terminate(); + + _psFreeVideoModeList(); + + + /* + * Tidy up the 3D (RenderWare) components of the application... + */ + RsEventHandler(rsRWTERMINATE, nil); + + /* + * Kill the window... + */ + DestroyWindow(PSGLOBAL(window)); + + /* + * Free the platform dependent data... + */ + RsEventHandler(rsTERMINATE, nil); + + /* + * Free the argv strings... + */ + free(argv); + + ShowCursor(TRUE); + + SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &SavedStickyKeys, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETPOWEROFFACTIVE, TRUE, nil, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETLOWPOWERACTIVE, TRUE, nil, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, nil, SPIF_SENDCHANGE); + + SetErrorMode(0); + + return message.wParam; +} + +/* + ***************************************************************************** + */ + +#define DEVICE_AXIS_MIN -2000 +#define DEVICE_AXIS_MAX 2000 + + +HRESULT _InputInitialise() +{ + HRESULT hr; + + // Create a DInput object + if( FAILED( hr = DirectInput8Create( GetModuleHandle(nil), DIRECTINPUT_VERSION, + IID_IDirectInput8, (VOID**)&PSGLOBAL(dinterface), nil ) ) ) + return hr; + + return S_OK; +} + +HRESULT _InputInitialiseMouse() +{ + HRESULT hr; + + // Obtain an interface to the system mouse device. + if( FAILED( hr = PSGLOBAL(dinterface)->CreateDevice( GUID_SysMouse, &PSGLOBAL(mouse), nil ) ) ) + return hr; + + // Set the data format to "mouse format" - a predefined data format + // + // A data format specifies which controls on a device we + // are interested in, and how they should be reported. + // + // This tells DirectInput that we will be passing a + // DIMOUSESTATE2 structure to IDirectInputDevice::GetDeviceState. + if( FAILED( hr = PSGLOBAL(mouse)->SetDataFormat( &c_dfDIMouse2 ) ) ) + return hr; + + if( FAILED( hr = PSGLOBAL(mouse)->SetCooperativeLevel( PSGLOBAL(window), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND ) ) ) + return hr; + + // Acquire the newly created device + PSGLOBAL(mouse)->Acquire(); + + return S_OK; +} + +RwV2d leftStickPos; +RwV2d rightStickPos; + +HRESULT CapturePad(RwInt32 padID) +{ + HRESULT hr; + DIJOYSTATE2 js; + LPDIRECTINPUTDEVICE8 *pPad = nil; + + if( padID == 0 ) + pPad = &PSGLOBAL(joy1); + else if( padID == 1) + pPad = &PSGLOBAL(joy2); + else + assert("invalid padID"); + + if ( nil == (*pPad) ) + return S_OK; + + // Poll the device to read the current state + hr = (*pPad)->Poll(); + + if( FAILED(hr) ) + { + // DInput is telling us that the input stream has been + // interrupted. We aren't tracking any state between polls, so + // we don't have any special reset that needs to be done. We + // just re-acquire and try again. + hr = (*pPad)->Acquire(); + while( hr == DIERR_INPUTLOST ) + hr = (*pPad)->Acquire(); + + // hr may be DIERR_OTHERAPPHASPRIO or other errors. This + // may occur when the app is minimized or in the process of + // switching, so just try again later + + if( FAILED(hr) ) + return hr; + + hr = (*pPad)->Poll(); + if( FAILED(hr) ) + return hr; + } + + // Get the input's device state + if( FAILED( hr = (*pPad)->GetDeviceState( sizeof(DIJOYSTATE2), &js ) ) ) + return hr; // The device should have been acquired during the Poll() + + if ( ControlsManager.m_bFirstCapture == true ) + { + memcpy(&ControlsManager.m_OldState, &js, sizeof(DIJOYSTATE2)); + memcpy(&ControlsManager.m_NewState, &js, sizeof(DIJOYSTATE2)); + + ControlsManager.m_bFirstCapture = false; + } + else + { + memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(DIJOYSTATE2)); + memcpy(&ControlsManager.m_NewState, &js, sizeof(DIJOYSTATE2)); + } + + RsPadButtonStatus bs; + bs.padID = padID; + + RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + + bool deviceAvailable = (*pPad) != nil; + + if ( deviceAvailable ) + { + leftStickPos.x = (float)js.lX / (float)((DEVICE_AXIS_MAX - DEVICE_AXIS_MIN) / 2); + leftStickPos.y = (float)js.lY / (float)((DEVICE_AXIS_MAX - DEVICE_AXIS_MIN) / 2); + + if (LOWORD(js.rgdwPOV[0]) != 0xFFFF) + { + float angle = DEGTORAD((float)js.rgdwPOV[0] / 100.0f); + + leftStickPos.x = Sin(angle); + leftStickPos.y = -Cos(angle); + } + + if ( AllValidWinJoys.m_aJoys[bs.padID].m_bHasAxisR && AllValidWinJoys.m_aJoys[bs.padID].m_bHasAxisZ ) + { + rightStickPos.x = (float)js.lZ / (float)((DEVICE_AXIS_MAX - DEVICE_AXIS_MIN) / 2); + rightStickPos.y = (float)js.lRz / (float)((DEVICE_AXIS_MAX - DEVICE_AXIS_MIN) / 2); + } + } + + { + if (CPad::m_bMapPadOneToPadTwo) + bs.padID = 1; + + RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + RsPadEventHandler(rsPADBUTTONDOWN, (void *)&bs); + } + + { + if (CPad::m_bMapPadOneToPadTwo) + bs.padID = 1; + + CPad *pad = CPad::GetPad(bs.padID); + + if ( Abs(leftStickPos.x) > 0.3f ) + pad->PCTempJoyState.LeftStickX = (int32)(leftStickPos.x * 128.0f); + + if ( Abs(leftStickPos.y) > 0.3f ) + pad->PCTempJoyState.LeftStickY = (int32)(leftStickPos.y * 128.0f); + + if ( Abs(rightStickPos.x) > 0.3f ) + pad->PCTempJoyState.RightStickX = (int32)(rightStickPos.x * 128.0f); + + if ( Abs(rightStickPos.y) > 0.3f ) + pad->PCTempJoyState.RightStickY = (int32)(rightStickPos.y * 128.0f); + } + + return S_OK; +} + +void _InputInitialiseJoys() +{ + DIPROPDWORD prop; + DIDEVCAPS devCaps; + + for ( int32 i = 0; i < _TODOCONST(2); i++ ) + AllValidWinJoys.ClearJoyInfo(i); + + _InputAddJoys(); + + if ( PSGLOBAL(joy1) != nil ) + { + devCaps.dwSize = sizeof(DIDEVCAPS); + PSGLOBAL(joy1)->GetCapabilities(&devCaps); + + prop.diph.dwSize = sizeof(DIPROPDWORD); + prop.diph.dwHeaderSize = sizeof(DIPROPHEADER); + prop.diph.dwObj = 0; + prop.diph.dwHow = 0; + + PSGLOBAL(joy1)->GetProperty(DIPROP_VIDPID, (LPDIPROPHEADER)&prop); + AllValidWinJoys.m_aJoys[0].m_nVendorID = LOWORD(prop.dwData); + AllValidWinJoys.m_aJoys[0].m_nProductID = HIWORD(prop.dwData); + AllValidWinJoys.m_aJoys[0].m_bInitialised = true; + + ControlsManager.InitDefaultControlConfigJoyPad(devCaps.dwButtons); + } + + if ( PSGLOBAL(joy2) != nil ) + { + PSGLOBAL(joy2)->GetProperty(DIPROP_VIDPID, (LPDIPROPHEADER)&prop); + AllValidWinJoys.m_aJoys[1].m_nVendorID = LOWORD(prop.dwData); + AllValidWinJoys.m_aJoys[1].m_nProductID = HIWORD(prop.dwData); + AllValidWinJoys.m_aJoys[1].m_bInitialised = true; + } +} + +void _InputAddJoyStick(LPDIRECTINPUTDEVICE8 lpDevice, INT num) +{ + DIDEVICEOBJECTINSTANCE objInst; + + objInst.dwSize = sizeof( DIDEVICEOBJECTINSTANCE ); + + DIPROPRANGE range; + range.diph.dwSize = sizeof(DIPROPRANGE); + range.diph.dwHeaderSize = sizeof(DIPROPHEADER); + range.lMin = DEVICE_AXIS_MIN; + range.lMax = DEVICE_AXIS_MAX; + range.diph.dwHow = DIPH_BYOFFSET; + + // get the info about the object from the device + + range.diph.dwObj = DIJOFS_X; + if ( lpDevice != nil ) + { + if ( SUCCEEDED( lpDevice->GetObjectInfo( &objInst, DIJOFS_X, DIPH_BYOFFSET ) ) ) + { + if( FAILED( lpDevice->SetProperty( DIPROP_RANGE, (LPCDIPROPHEADER)&range ) ) ) + return; + else + ; + } + } + + range.diph.dwObj = DIJOFS_Y; + if ( lpDevice != nil ) + { + if ( SUCCEEDED( lpDevice->GetObjectInfo( &objInst, DIJOFS_Y, DIPH_BYOFFSET ) ) ) + { + if( FAILED( lpDevice->SetProperty( DIPROP_RANGE, (LPCDIPROPHEADER)&range ) ) ) + return; + else + ; + } + } + + range.diph.dwObj = DIJOFS_Z; + if ( lpDevice != nil ) + { + if ( SUCCEEDED( lpDevice->GetObjectInfo( &objInst, DIJOFS_Z, DIPH_BYOFFSET ) ) ) + { + if( FAILED( lpDevice->SetProperty( DIPROP_RANGE, (LPCDIPROPHEADER)&range ) ) ) + return; + else + AllValidWinJoys.m_aJoys[num].m_bHasAxisZ = true; // z rightStickPos.x + } + } + + range.diph.dwObj = DIJOFS_RZ; + if ( lpDevice != nil ) + { + if ( SUCCEEDED( lpDevice->GetObjectInfo( &objInst, DIJOFS_RZ, DIPH_BYOFFSET ) ) ) + { + if( FAILED( lpDevice->SetProperty( DIPROP_RANGE, (LPCDIPROPHEADER)&range ) ) ) + return; + else + AllValidWinJoys.m_aJoys[num].m_bHasAxisR = true; // r rightStickPos.y + } + } +} + +HRESULT _InputAddJoys() +{ + HRESULT hr; + + hr = PSGLOBAL(dinterface)->EnumDevices(DI8DEVCLASS_GAMECTRL, _InputEnumDevicesCallback, nil, DIEDFL_ATTACHEDONLY ); + + if( FAILED(hr) ) + return hr; + + if ( PSGLOBAL(joy1) == nil ) + return S_FALSE; + + _InputAddJoyStick(PSGLOBAL(joy1), 0); + + if ( PSGLOBAL(joy2) == nil ) + return S_OK; // we have one device already so return OK and ignore second + + _InputAddJoyStick(PSGLOBAL(joy2), 1); + + return S_OK; +} + +HRESULT _InputGetMouseState(DIMOUSESTATE2 *state) +{ + HRESULT hr; + + if ( PSGLOBAL(mouse) == nil ) + return S_FALSE; + + // Get the input's device state, and put the state in dims + ZeroMemory( state, sizeof(DIMOUSESTATE2) ); + + hr = PSGLOBAL(mouse)->GetDeviceState( sizeof(DIMOUSESTATE2), state ); + + if( FAILED(hr) ) + { + // DirectInput may be telling us that the input stream has been + // interrupted. We aren't tracking any state between polls, so + // we don't have any special reset that needs to be done. + // We just re-acquire and try again. + + // If input is lost then acquire and keep trying + hr = PSGLOBAL(mouse)->Acquire(); + while( hr == DIERR_INPUTLOST ) + hr = PSGLOBAL(mouse)->Acquire(); + + ZeroMemory( state, sizeof(DIMOUSESTATE2) ); + hr = PSGLOBAL(mouse)->GetDeviceState( sizeof(DIMOUSESTATE2), state ); + + return hr; + } + + return S_OK; +} + +void _InputShutdown() +{ + SAFE_RELEASE(PSGLOBAL(dinterface)); +} + +BOOL CALLBACK _InputEnumDevicesCallback( const DIDEVICEINSTANCE* pdidInstance, VOID* pContext ) +{ + HRESULT hr; + + static INT Count = 0; + + LPDIRECTINPUTDEVICE8 *pJoystick = nil; + + if ( Count == 0 ) + pJoystick = &PSGLOBAL(joy1); + else if ( Count == 1 ) + pJoystick = &PSGLOBAL(joy2); + else + assert("too many pads"); + + // Obtain an interface to the enumerated joystick. + hr = PSGLOBAL(dinterface)->CreateDevice( pdidInstance->guidInstance, pJoystick, nil ); + + // If it failed, then we can't use this joystick. (Maybe the user unplugged + // it while we were in the middle of enumerating it.) + if( hr != S_OK ) + return DIENUM_CONTINUE; + + hr = (*pJoystick)->SetDataFormat( &c_dfDIJoystick2 ); + if( hr != S_OK ) + { + (*pJoystick)->Release(); + return DIENUM_CONTINUE; + } + + ++Count; + + hr = (*pJoystick)->SetCooperativeLevel( PSGLOBAL(window), DISCL_NONEXCLUSIVE|DISCL_FOREGROUND ); + if( hr != S_OK ) + { + (*pJoystick)->Release(); +#ifdef FIX_BUGS + // BUG: enum will be called with Count == 2, which will write to a null pointer + // So decrement count again since we're not using this pad + --Count; +#endif + return DIENUM_CONTINUE; + } + + // Stop enumeration. Note: we're just taking the first two joysticks we get. You + // could store all the enumerated joysticks and let the user pick. + if ( Count == 2 ) + return DIENUM_STOP; + + return DIENUM_CONTINUE; +} + +BOOL _InputTranslateKey(RsKeyCodes *rs, UINT flag, UINT key) +{ + *rs = rsNULL; + + switch ( key ) + { + case VK_SHIFT: + { + if ( _dwOperatingSystemVersion == OS_WIN98 ) + *rs = rsSHIFT; + break; + } + + case VK_RETURN: + { + if ( _InputIsExtended(flag) ) + *rs = rsPADENTER; + else + *rs = rsENTER; + break; + } + + case VK_CONTROL: + { + if ( _InputIsExtended(flag) ) + *rs = rsRCTRL; + else + *rs = rsLCTRL; + break; + } + + case VK_MENU: + { + if ( _InputIsExtended(flag) ) + *rs = rsRALT; + else + *rs = rsLALT; + break; + } + + case VK_APPS: + { + *rs = rsAPPS; + break; + } + + case VK_PAUSE: + { + *rs = rsPAUSE; + break; + } + + case VK_CAPITAL: + { + *rs = rsCAPSLK; + break; + } + + case VK_ESCAPE: + { + *rs = rsESC; + break; + } + + case VK_PRIOR: + { + if ( _InputIsExtended(flag) ) + *rs = rsPGUP; + else + *rs = rsPADPGUP; + break; + } + + case VK_NEXT: + { + if ( _InputIsExtended(flag) ) + *rs = rsPGDN; + else + *rs = rsPADPGDN; + break; + } + + case VK_END: + { + if ( _InputIsExtended(flag) ) + *rs = rsEND; + else + *rs = rsPADEND; + break; + } + + case VK_HOME: + { + if ( _InputIsExtended(flag) ) + *rs = rsHOME; + else + *rs = rsPADHOME; + break; + } + + case VK_LEFT: + { + if ( _InputIsExtended(flag) ) + *rs = rsLEFT; + else + *rs = rsPADLEFT; + break; + } + + case VK_UP: + { + if ( _InputIsExtended(flag) ) + *rs = rsUP; + else + *rs = rsPADUP; + break; + } + + case VK_RIGHT: + { + if ( _InputIsExtended(flag) ) + *rs = rsRIGHT; + else + *rs = rsPADRIGHT; + break; + } + + case VK_DOWN: + { + if ( _InputIsExtended(flag) ) + *rs = rsDOWN; + else + *rs = rsPADDOWN; + break; + } + + case VK_INSERT: + { + if ( _InputIsExtended(flag) ) + *rs = rsINS; + else + *rs = rsPADINS; + break; + } + + case VK_DELETE: + { + if ( _InputIsExtended(flag) ) + *rs = rsDEL; + else + *rs = rsPADDEL; + break; + } + + case VK_LWIN: + { + *rs = rsLWIN; + break; + } + + case VK_RWIN: + { + *rs = rsRWIN; + break; + } + + case VK_NUMPAD0: + { + *rs = rsPADINS; + break; + } + + case VK_NUMPAD1: + { + *rs = rsPADEND; + break; + } + + case VK_NUMPAD2: + { + *rs = rsPADDOWN; + break; + } + + case VK_NUMPAD3: + { + *rs = rsPADPGDN; + break; + } + + case VK_NUMPAD4: + { + *rs = rsPADLEFT; + break; + } + + case VK_NUMPAD5: + { + *rs = rsPAD5; + break; + } + + case VK_NUMPAD6: + { + *rs = rsPADRIGHT; + break; + } + + case VK_NUMPAD7: + { + *rs = rsPADHOME; + break; + } + + case VK_NUMPAD8: + { + *rs = rsPADUP; + break; + } + + case VK_NUMPAD9: + { + *rs = rsPADPGUP; + break; + } + + case VK_MULTIPLY: + { + *rs = rsTIMES; + break; + } + + case VK_DIVIDE: + { + *rs = rsDIVIDE; + break; + } + + case VK_ADD: + { + *rs = rsPLUS; + break; + } + + case VK_SUBTRACT: + { + *rs = rsMINUS; + break; + } + + case VK_DECIMAL: + { + *rs = rsPADDEL; + break; + } + + case VK_F1: + { + *rs = rsF1; + break; + } + + case VK_F2: + { + *rs = rsF2; + break; + } + + case VK_F3: + { + *rs = rsF3; + break; + } + + case VK_F4: + { + *rs = rsF4; + break; + } + + case VK_F5: + { + *rs = rsF5; + break; + } + + case VK_F6: + { + *rs = rsF6; + break; + } + + case VK_F7: + { + *rs = rsF7; + break; + } + + case VK_F8: + { + *rs = rsF8; + break; + } + + case VK_F9: + { + *rs = rsF9; + break; + } + + case VK_F10: + { + *rs = rsF10; + break; + } + + case VK_F11: + { + *rs = rsF11; + break; + } + + case VK_F12: + { + *rs = rsF12; + break; + } + + case VK_NUMLOCK: + { + *rs = rsNUMLOCK; + break; + } + + case VK_SCROLL: + { + *rs = rsSCROLL; + break; + } + + case VK_BACK: + { + *rs = rsBACKSP; + break; + } + + case VK_TAB: + { + *rs = rsTAB; + break; + } + + default: + { + UINT vkey = MapVirtualKey(key, MAPVK_VK_TO_CHAR) & 0xFFFF; + if ( vkey < 255 ) + *rs = (RsKeyCodes)vkey; + break; + } + } + + return *rs != rsNULL; +} + +void _InputTranslateShiftKeyUpDown(RsKeyCodes *rs) +{ + if ( _dwOperatingSystemVersion != OS_WIN98 ) + { + if ( _InputTranslateShiftKey(rs, VK_LSHIFT, TRUE) ) + RsKeyboardEventHandler(rsKEYDOWN, rs); + if ( _InputTranslateShiftKey(rs, VK_RSHIFT, TRUE) ) + RsKeyboardEventHandler(rsKEYDOWN, rs); + if ( _InputTranslateShiftKey(rs, VK_LSHIFT, FALSE) ) + RsKeyboardEventHandler(rsKEYUP, rs); + if ( _InputTranslateShiftKey(rs, VK_RSHIFT, FALSE) ) + RsKeyboardEventHandler(rsKEYUP, rs); + } +} + +BOOL _InputTranslateShiftKey(RsKeyCodes *rs, UINT key, BOOLEAN bDown) +{ + *rs = rsNULL; + switch ( key ) + { + case VK_LSHIFT: + { + if ( bDown == (GetKeyState(VK_LSHIFT) & 0x8000) >> 15 ) + *rs = rsLSHIFT; + break; + } + + case VK_RSHIFT: + { + if ( bDown == (GetKeyState(VK_RSHIFT) & 0x8000) >> 15 ) + *rs = rsRSHIFT; + break; + } + + default: + { + return *rs != rsNULL; + } + } + + return TRUE; +} + +BOOL _InputIsExtended(INT flag) +{ + return (flag & 0x1000000) != 0; +} + +#if (defined(_MSC_VER)) +int strcasecmp(const char *str1, const char *str2) +{ + return _strcmpi(str1, str2); +} +#endif +#endif diff --git a/src/skel/win/win.h b/src/skel/win/win.h new file mode 100644 index 0000000..be84089 --- /dev/null +++ b/src/skel/win/win.h @@ -0,0 +1,91 @@ + +// DON'T include directly. crossplatform.h includes this if you're using D3D9 backend(win.cpp). + +#if (!defined(_PLATFORM_WIN_H)) +#define _PLATFORM_WIN_H + +#if (!defined(RSREGSETBREAKALLOC)) +#define RSREGSETBREAKALLOC(_name) /* No op */ +#endif /* (!defined(RSREGSETBREAKALLOC)) */ + +#ifdef __DINPUT_INCLUDED__ +/* platform specfic global data */ +typedef struct +{ + HWND window; + HINSTANCE instance; + RwBool fullScreen; + RwV2d lastMousePos; + + DWORD field_14; + + LPDIRECTINPUT8 dinterface; + LPDIRECTINPUTDEVICE8 mouse; + LPDIRECTINPUTDEVICE8 joy1; + LPDIRECTINPUTDEVICE8 joy2; +} +psGlobalType; + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +enum eJoypads +{ + JOYSTICK1 = 0, + JOYSTICK2, + MAX_JOYSTICKS +}; + +enum eJoypadState +{ + JOYPAD_UNUSED, + JOYPAD_ATTACHED, +}; + +struct tJoy +{ + eJoypadState m_State; + bool m_bInitialised; + bool m_bHasAxisZ; + bool m_bHasAxisR; + int m_nVendorID; + int m_nProductID; +}; + +class CJoySticks +{ +public: + tJoy m_aJoys[MAX_JOYSTICKS]; + + CJoySticks(); + void ClearJoyInfo(int joyID); +}; + +extern CJoySticks AllValidWinJoys; +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#ifdef __DINPUT_INCLUDED__ +HRESULT _InputInitialise(); +HRESULT CapturePad(RwInt32 padID); +void _InputAddJoyStick(LPDIRECTINPUTDEVICE8 lpDevice, INT num); +HRESULT _InputAddJoys(); +HRESULT _InputGetMouseState(DIMOUSESTATE2 *state); +void _InputShutdown(); +BOOL CALLBACK _InputEnumDevicesCallback( const DIDEVICEINSTANCE* pdidInstance, VOID* pContext ); +BOOL _InputTranslateKey(RsKeyCodes *rs, UINT flag, UINT key); +BOOL _InputTranslateShiftKey(RsKeyCodes *rs, UINT key, BOOLEAN bDown); +BOOL _InputIsExtended(INT flag); +#endif + +void CenterVideo(void); +void CloseClip(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* (!defined(_PLATFORM_WIN_H)) */ diff --git a/src/skel/win/win.rc b/src/skel/win/win.rc new file mode 100644 index 0000000..379c473 --- /dev/null +++ b/src/skel/win/win.rc @@ -0,0 +1,47 @@ +#include "resource.h" + +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +//#if !defined(__GNU_C__) +//#include "afxres.h" +//#else +#include "winresrc.h" +//#endif /* !defined(__GNU_C__) */ + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 186, 90 +STYLE DS_MODALFRAME | DS_CENTER | DS_CENTERMOUSE | WS_POPUP | WS_CAPTION | + WS_SYSMENU +CAPTION "Device Selection" +FONT 8, "MS Sans Serif" +BEGIN + COMBOBOX IDC_DEVICESEL,7,25,172,33,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + COMBOBOX IDC_VIDMODE,7,46,172,74,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + DEFPUSHBUTTON "EXIT",IDEXIT,103,69,52,14 + DEFPUSHBUTTON "OK",IDOK,28,69,50,14 + LTEXT "Please select the Device To Use:",IDC_SELECTDEVICE,7,7, + 137,8 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_MAIN_ICON ICON DISCARDABLE "gta3.ico" + +///////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/src/text/Messages.cpp b/src/text/Messages.cpp new file mode 100644 index 0000000..b68f918 --- /dev/null +++ b/src/text/Messages.cpp @@ -0,0 +1,815 @@ +#include "common.h" + +#include "Messages.h" +#include "RwHelper.h" +#include "Hud.h" +#include "User.h" +#include "Timer.h" +#include "Text.h" + +#include "ControllerConfig.h" + +#include "Font.h" + +tMessage CMessages::BriefMessages[NUMBRIEFMESSAGES]; +tPreviousBrief CMessages::PreviousBriefs[NUMPREVIOUSBRIEFS]; +tBigMessage CMessages::BIGMessages[NUMBIGMESSAGES]; +char CMessages::PreviousMissionTitle[16]; // unused + +void +CMessages::Init() +{ + ClearMessages(); + + for (int32 i = 0; i < NUMPREVIOUSBRIEFS; i++) { + PreviousBriefs[i].m_pText = nil; + PreviousBriefs[i].m_pString = nil; + } +} + +uint16 +CMessages::GetWideStringLength(wchar *src) +{ + uint16 length = 0; + while (*(src++)) length++; + return length; +} + +void +CMessages::WideStringCopy(wchar *dst, wchar *src, uint16 size) +{ + int32 i = 0; + if (src) { + while (i < size - 1) { + if (!src[i]) break; + dst[i] = src[i]; + i++; + } + } else { + while (i < size - 1) + dst[i++] = '\0'; + } + dst[i] = '\0'; +} + +wchar FixupChar(wchar c) +{ +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + return c & 0x7fff; +#endif + return c; +} + +bool +CMessages::WideStringCompare(wchar *str1, wchar *str2, uint16 size) +{ + uint16 len1 = GetWideStringLength(str1); + uint16 len2 = GetWideStringLength(str2); + if (len1 != len2 && (len1 < size || len2 < size)) + return false; + + for (int32 i = 0; i < size && FixupChar(str1[i]) != '\0'; i++) { + if (FixupChar(str1[i]) != FixupChar(str2[i])) + return false; + } + return true; +} + +void +CMessages::Process() +{ + for (int32 style = 0; style < 6; style++) { + if (BIGMessages[style].m_Stack[0].m_pText != nil && CTimer::GetTimeInMilliseconds() > BIGMessages[style].m_Stack[0].m_nTime + BIGMessages[style].m_Stack[0].m_nStartTime) { + BIGMessages[style].m_Stack[0].m_pText = nil; + + int32 i = 0; + while (i < 3) { + if (BIGMessages[style].m_Stack[i + 1].m_pText == nil) break; + BIGMessages[style].m_Stack[i] = BIGMessages[style].m_Stack[i + 1]; + i++; + } + + BIGMessages[style].m_Stack[i].m_pText = nil; + BIGMessages[style].m_Stack[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + } + } + + if (BriefMessages[0].m_pText != nil && CTimer::GetTimeInMilliseconds() > BriefMessages[0].m_nTime + BriefMessages[0].m_nStartTime) { + BriefMessages[0].m_pText = nil; + int32 i; + for (i = 0; i < NUMBRIEFMESSAGES-1 && BriefMessages[i + 1].m_pText != nil; i++) { + BriefMessages[i] = BriefMessages[i + 1]; + } + CMessages::BriefMessages[i].m_pText = nil; + CMessages::BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + if (BriefMessages[0].m_pText != nil) + AddToPreviousBriefArray( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + BriefMessages[0].m_pString); + } +} + +void +CMessages::Display() +{ + wchar outstr[256]; + + DefinedState(); + + for (int32 i = 0; i < NUMBIGMESSAGES; i++) { + InsertNumberInString( + BIGMessages[i].m_Stack[0].m_pText, + BIGMessages[i].m_Stack[0].m_nNumber[0], + BIGMessages[i].m_Stack[0].m_nNumber[1], + BIGMessages[i].m_Stack[0].m_nNumber[2], + BIGMessages[i].m_Stack[0].m_nNumber[3], + BIGMessages[i].m_Stack[0].m_nNumber[4], + BIGMessages[i].m_Stack[0].m_nNumber[5], + outstr); + InsertStringInString(outstr, BIGMessages[i].m_Stack[0].m_pString); + InsertPlayerControlKeysInString(outstr); + CHud::SetBigMessage(outstr, i); + } + + InsertNumberInString( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + outstr); + InsertStringInString(outstr, BriefMessages[0].m_pString); + InsertPlayerControlKeysInString(outstr); + CHud::SetMessage(outstr); +} + +void +CMessages::AddMessage(wchar *msg, uint32 time, uint16 flag) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, msg, 256); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + int32 i = 0; + while (i < NUMBRIEFMESSAGES && BriefMessages[i].m_pText != nil) + i++; + if (i >= NUMBRIEFMESSAGES) return; + + BriefMessages[i].m_pText = msg; + BriefMessages[i].m_nFlag = flag; + BriefMessages[i].m_nTime = time; + BriefMessages[i].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[i].m_nNumber[0] = -1; + BriefMessages[i].m_nNumber[1] = -1; + BriefMessages[i].m_nNumber[2] = -1; + BriefMessages[i].m_nNumber[3] = -1; + BriefMessages[i].m_nNumber[4] = -1; + BriefMessages[i].m_nNumber[5] = -1; + BriefMessages[i].m_pString = nil; + if (i == 0) + AddToPreviousBriefArray( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + BriefMessages[0].m_pString); +} + +void +CMessages::AddMessageJumpQ(wchar *msg, uint32 time, uint16 flag) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, msg, 256); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + BriefMessages[0].m_pText = msg; + BriefMessages[0].m_nFlag = flag; + BriefMessages[0].m_nTime = time; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[0].m_nNumber[0] = -1; + BriefMessages[0].m_nNumber[1] = -1; + BriefMessages[0].m_nNumber[2] = -1; + BriefMessages[0].m_nNumber[3] = -1; + BriefMessages[0].m_nNumber[4] = -1; + BriefMessages[0].m_nNumber[5] = -1; + BriefMessages[0].m_pString = nil; + AddToPreviousBriefArray(msg, -1, -1, -1, -1, -1, -1, 0); +} + +void +CMessages::AddMessageSoon(wchar *msg, uint32 time, uint16 flag) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, msg, 256); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + if (BriefMessages[0].m_pText != nil) { + for (int i = NUMBRIEFMESSAGES-1; i > 1; i--) + BriefMessages[i] = BriefMessages[i-1]; + + BriefMessages[1].m_pText = msg; + BriefMessages[1].m_nFlag = flag; + BriefMessages[1].m_nTime = time; + BriefMessages[1].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[1].m_nNumber[0] = -1; + BriefMessages[1].m_nNumber[1] = -1; + BriefMessages[1].m_nNumber[2] = -1; + BriefMessages[1].m_nNumber[3] = -1; + BriefMessages[1].m_nNumber[4] = -1; + BriefMessages[1].m_nNumber[5] = -1; + BriefMessages[1].m_pString = nil; + }else{ + BriefMessages[0].m_pText = msg; + BriefMessages[0].m_nFlag = flag; + BriefMessages[0].m_nTime = time; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[0].m_nNumber[0] = -1; + BriefMessages[0].m_nNumber[1] = -1; + BriefMessages[0].m_nNumber[2] = -1; + BriefMessages[0].m_nNumber[3] = -1; + BriefMessages[0].m_nNumber[4] = -1; + BriefMessages[0].m_nNumber[5] = -1; + BriefMessages[0].m_pString = nil; + AddToPreviousBriefArray(msg, -1, -1, -1, -1, -1, -1, nil); + } +} + +void +CMessages::ClearMessages() +{ + for (int32 i = 0; i < NUMBIGMESSAGES; i++) { + for (int32 j = 0; j < 4; j++) { + BIGMessages[i].m_Stack[j].m_pText = nil; + BIGMessages[i].m_Stack[j].m_pString = nil; + } + } + ClearSmallMessagesOnly(); +} + +void +CMessages::ClearSmallMessagesOnly() +{ + for (int32 i = 0; i < NUMBRIEFMESSAGES; i++) { + BriefMessages[i].m_pText = nil; + BriefMessages[i].m_pString = nil; + } +} + +void +CMessages::AddBigMessage(wchar *msg, uint32 time, uint16 style) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, msg, 256); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + BIGMessages[style].m_Stack[0].m_pText = msg; + BIGMessages[style].m_Stack[0].m_nFlag = 0; + BIGMessages[style].m_Stack[0].m_nTime = time; + BIGMessages[style].m_Stack[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BIGMessages[style].m_Stack[0].m_nNumber[0] = -1; + BIGMessages[style].m_Stack[0].m_nNumber[1] = -1; + BIGMessages[style].m_Stack[0].m_nNumber[2] = -1; + BIGMessages[style].m_Stack[0].m_nNumber[3] = -1; + BIGMessages[style].m_Stack[0].m_nNumber[4] = -1; + BIGMessages[style].m_Stack[0].m_nNumber[5] = -1; + BIGMessages[style].m_Stack[0].m_pString = nil; +} +void +CMessages::AddBigMessageQ(wchar *msg, uint32 time, uint16 style) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, msg, 256); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + int32 i = 0; + while (i < 4 && BIGMessages[style].m_Stack[i].m_pText != nil) + i++; + + if (i >= 4) return; + + BIGMessages[style].m_Stack[i].m_pText = msg; + BIGMessages[style].m_Stack[i].m_nFlag = 0; + BIGMessages[style].m_Stack[i].m_nTime = time; + BIGMessages[style].m_Stack[i].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BIGMessages[style].m_Stack[i].m_nNumber[0] = -1; + BIGMessages[style].m_Stack[i].m_nNumber[1] = -1; + BIGMessages[style].m_Stack[i].m_nNumber[2] = -1; + BIGMessages[style].m_Stack[i].m_nNumber[3] = -1; + BIGMessages[style].m_Stack[i].m_nNumber[4] = -1; + BIGMessages[style].m_Stack[i].m_nNumber[5] = -1; + BIGMessages[style].m_Stack[i].m_pString = nil; +} + +void +CMessages::AddToPreviousBriefArray(wchar *text, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, wchar *string) +{ + int32 i = 0; + for (i = 0; i < NUMPREVIOUSBRIEFS && PreviousBriefs[i].m_pText != nil; i++) { + if (PreviousBriefs[i].m_nNumber[0] == n1 + && PreviousBriefs[i].m_nNumber[1] == n2 + && PreviousBriefs[i].m_nNumber[2] == n3 + && PreviousBriefs[i].m_nNumber[3] == n4 + && PreviousBriefs[i].m_nNumber[4] == n5 + && PreviousBriefs[i].m_nNumber[5] == n6 + && PreviousBriefs[i].m_pText == text + && PreviousBriefs[i].m_pString == string) + return; + } + + if (i != 0) { + if (i == NUMPREVIOUSBRIEFS) i -= 2; + else i--; + + while (i >= 0) { + PreviousBriefs[i + 1] = PreviousBriefs[i]; + i--; + } + } + PreviousBriefs[0].m_pText = text; + PreviousBriefs[0].m_nNumber[0] = n1; + PreviousBriefs[0].m_nNumber[1] = n2; + PreviousBriefs[0].m_nNumber[2] = n3; + PreviousBriefs[0].m_nNumber[3] = n4; + PreviousBriefs[0].m_nNumber[4] = n5; + PreviousBriefs[0].m_nNumber[5] = n6; + PreviousBriefs[0].m_pString = string; +} + +void +CMessages::InsertNumberInString(wchar *str, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, wchar *outstr) +{ + char numStr[10]; + wchar wNumStr[10]; + + if (str == nil) { + *outstr = '\0'; + return; + } + + sprintf(numStr, "%d", n1); + size_t outLen = strlen(numStr); + AsciiToUnicode(numStr, wNumStr); + if (str[0] == 0) { + *outstr = '\0'; + return; + } + + int32 size = GetWideStringLength(str); + + int32 i = 0; + + for (int32 c = 0; c < size;) { +#ifdef MORE_LANGUAGES + if ((CFont::IsJapanese() && str[c] == (0x8000 | '~') && str[c + 1] == (0x8000 | '1') && str[c + 2] == (0x8000 | '~')) || + (!CFont::IsJapanese() && str[c] == '~' && str[c + 1] == '1' && str[c + 2] == '~')) { +#else + if (str[c] == '~' && str[c + 1] == '1' && str[c + 2] == '~') { +#endif + c += 3; + for (int j = 0; j < outLen; ) + *(outstr++) = wNumStr[j++]; + + i++; + switch (i) { + case 1: sprintf(numStr, "%d", n2); break; + case 2: sprintf(numStr, "%d", n3); break; + case 3: sprintf(numStr, "%d", n4); break; + case 4: sprintf(numStr, "%d", n5); break; + case 5: sprintf(numStr, "%d", n6); break; + } + outLen = strlen(numStr); + AsciiToUnicode(numStr, wNumStr); + } else { + *(outstr++) = str[c++]; + } + } + *outstr = '\0'; +} + +void +CMessages::InsertStringInString(wchar *str1, wchar *str2) +{ + wchar tempstr[256]; + + if (!str1 || !str2) return; + + int32 str1_size = GetWideStringLength(str1); + int32 str2_size = GetWideStringLength(str2); + int32 total_size = str1_size + str2_size; + + wchar *_str1 = str1; + uint16 i; + for (i = 0; i < total_size; ) { +#ifdef MORE_LANGUAGES + if ((CFont::IsJapanese() && *_str1 == (0x8000 | '~') && *(_str1 + 1) == (0x8000 | 'a') && *(_str1 + 2) == (0x8000 | '~')) + || (*_str1 == '~' && *(_str1 + 1) == 'a' && *(_str1 + 2) == '~')) + { +#else + if (*_str1 == '~' && *(_str1 + 1) == 'a' && *(_str1 + 2) == '~') { +#endif + _str1 += 3; + for (int j = 0; j < str2_size; j++) { + tempstr[i++] = str2[j]; + } + } else { + tempstr[i++] = *(_str1++); + } + } + tempstr[i] = '\0'; + + for (i = 0; i < total_size; i++) + str1[i] = tempstr[i]; + + while (i < 256) + str1[i++] = '\0'; +} + +void +CMessages::InsertPlayerControlKeysInString(wchar *str) +{ + uint16 i; + wchar outstr[256]; + wchar keybuf[256]; + + if (!str) return; + uint16 strSize = GetWideStringLength(str); + memset(keybuf, 0, 256*sizeof(wchar)); + + wchar *_outstr = outstr; + for (i = 0; i < strSize;) { +#ifdef MORE_LANGUAGES + if ((CFont::IsJapanese() && str[i] == (0x8000 | '~') && str[i + 1] == (0x8000 | 'k') && str[i + 2] == (0x8000 | '~')) || + (!CFont::IsJapanese() && str[i] == '~' && str[i + 1] == 'k' && str[i + 2] == '~')) { +#else + if (str[i] == '~' && str[i + 1] == 'k' && str[i + 2] == '~') { +#endif + i += 4; + bool done = false; + for (int32 cont = 0; cont < MAX_CONTROLLERACTIONS && !done; cont++) { + uint16 contSize = GetWideStringLength(ControlsManager.m_aActionNames[cont]); + if (contSize != 0) { + if (WideStringCompare(&str[i], ControlsManager.m_aActionNames[cont], contSize)) { + done = true; + ControlsManager.GetWideStringOfCommandKeys(cont, keybuf, 256); + uint16 keybuf_size = GetWideStringLength(keybuf); + for (uint16 j = 0; j < keybuf_size; j++) { + *(_outstr++) = keybuf[j]; + keybuf[j] = '\0'; + } + i += contSize + 1; + } + } + } + } else { + *(_outstr++) = str[i++]; + } + } + *_outstr = '\0'; + + for (i = 0; i < GetWideStringLength(outstr); i++) + str[i] = outstr[i]; + + while (i < 256) + str[i++] = '\0'; +} + +void +CMessages::AddMessageWithNumber(wchar *str, uint32 time, uint16 flag, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + wchar outstr[512]; // unused + InsertNumberInString(str, n1, n2, n3, n4, n5, n6, outstr); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + uint16 i = 0; + while (i < NUMBRIEFMESSAGES && BriefMessages[i].m_pText != nil) + i++; + + if (i >= NUMBRIEFMESSAGES) return; + + BriefMessages[i].m_pText = str; + BriefMessages[i].m_nFlag = flag; + BriefMessages[i].m_nTime = time; + BriefMessages[i].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[i].m_nNumber[0] = n1; + BriefMessages[i].m_nNumber[1] = n2; + BriefMessages[i].m_nNumber[2] = n3; + BriefMessages[i].m_nNumber[3] = n4; + BriefMessages[i].m_nNumber[4] = n5; + BriefMessages[i].m_nNumber[5] = n6; + BriefMessages[i].m_pString = nil; + if (i == 0) + AddToPreviousBriefArray( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + BriefMessages[0].m_pString); +} + +void +CMessages::AddMessageJumpQWithNumber(wchar *str, uint32 time, uint16 flag, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + wchar outstr[512]; // unused + InsertNumberInString(str, n1, n2, n3, n4, n5, n6, outstr); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + BriefMessages[0].m_pText = str; + BriefMessages[0].m_nFlag = flag; + BriefMessages[0].m_nTime = time; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[0].m_nNumber[0] = n1; + BriefMessages[0].m_nNumber[1] = n2; + BriefMessages[0].m_nNumber[2] = n3; + BriefMessages[0].m_nNumber[3] = n4; + BriefMessages[0].m_nNumber[4] = n5; + BriefMessages[0].m_nNumber[5] = n6; + BriefMessages[0].m_pString = nil; + AddToPreviousBriefArray(str, n1, n2, n3, n4, n5, n6, nil); +} + +void +CMessages::AddMessageSoonWithNumber(wchar *str, uint32 time, uint16 flag, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + wchar outstr[512]; // unused + InsertNumberInString(str, n1, n2, n3, n4, n5, n6, outstr); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + if (BriefMessages[0].m_pText != nil) { + for (int32 i = NUMBRIEFMESSAGES-1; i > 1; i--) + BriefMessages[i] = BriefMessages[i-1]; + + BriefMessages[1].m_pText = str; + BriefMessages[1].m_nFlag = flag; + BriefMessages[1].m_nTime = time; + BriefMessages[1].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[1].m_nNumber[0] = n1; + BriefMessages[1].m_nNumber[1] = n2; + BriefMessages[1].m_nNumber[2] = n3; + BriefMessages[1].m_nNumber[3] = n4; + BriefMessages[1].m_nNumber[4] = n5; + BriefMessages[1].m_nNumber[5] = n6; + BriefMessages[1].m_pString = nil; + } else { + BriefMessages[0].m_pText = str; + BriefMessages[0].m_nFlag = flag; + BriefMessages[0].m_nTime = time; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[0].m_nNumber[0] = n1; + BriefMessages[0].m_nNumber[1] = n2; + BriefMessages[0].m_nNumber[2] = n3; + BriefMessages[0].m_nNumber[3] = n4; + BriefMessages[0].m_nNumber[4] = n5; + BriefMessages[0].m_nNumber[5] = n6; + BriefMessages[0].m_pString = nil; + AddToPreviousBriefArray(str, n1, n2, n3, n4, n5, n6, nil); + } +} + +void +CMessages::AddBigMessageWithNumber(wchar *str, uint32 time, uint16 style, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + wchar outstr[512]; // unused + InsertNumberInString(str, n1, n2, n3, n4, n5, n6, outstr); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + BIGMessages[style].m_Stack[0].m_pText = str; + BIGMessages[style].m_Stack[0].m_nFlag = 0; + BIGMessages[style].m_Stack[0].m_nTime = time; + BIGMessages[style].m_Stack[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BIGMessages[style].m_Stack[0].m_nNumber[0] = n1; + BIGMessages[style].m_Stack[0].m_nNumber[1] = n2; + BIGMessages[style].m_Stack[0].m_nNumber[2] = n3; + BIGMessages[style].m_Stack[0].m_nNumber[3] = n4; + BIGMessages[style].m_Stack[0].m_nNumber[4] = n5; + BIGMessages[style].m_Stack[0].m_nNumber[5] = n6; + BIGMessages[style].m_Stack[0].m_pString = nil; +} + +void +CMessages::AddBigMessageWithNumberQ(wchar *str, uint32 time, uint16 style, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + wchar outstr[512]; // unused + InsertNumberInString(str, n1, n2, n3, n4, n5, n6, outstr); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + int32 i = 0; + + while (i < 4 && BIGMessages[style].m_Stack[i].m_pText != nil) + i++; + + if (i >= 4) return; + + BIGMessages[style].m_Stack[i].m_pText = str; + BIGMessages[style].m_Stack[i].m_nFlag = 0; + BIGMessages[style].m_Stack[i].m_nTime = time; + BIGMessages[style].m_Stack[i].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BIGMessages[style].m_Stack[i].m_nNumber[0] = n1; + BIGMessages[style].m_Stack[i].m_nNumber[1] = n2; + BIGMessages[style].m_Stack[i].m_nNumber[2] = n3; + BIGMessages[style].m_Stack[i].m_nNumber[3] = n4; + BIGMessages[style].m_Stack[i].m_nNumber[4] = n5; + BIGMessages[style].m_Stack[i].m_nNumber[5] = n6; + BIGMessages[style].m_Stack[i].m_pString = nil; +} + +void +CMessages::AddMessageWithString(wchar *text, uint32 time, uint16 flag, wchar *str) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, text, 256); + InsertStringInString(outstr, str); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + int32 i = 0; + while (i < NUMBRIEFMESSAGES && BriefMessages[i].m_pText != nil) + i++; + + if (i >= NUMBRIEFMESSAGES) return; + + BriefMessages[i].m_pText = text; + BriefMessages[i].m_nFlag = flag; + BriefMessages[i].m_nTime = time; + BriefMessages[i].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[i].m_nNumber[0] = -1; + BriefMessages[i].m_nNumber[1] = -1; + BriefMessages[i].m_nNumber[2] = -1; + BriefMessages[i].m_nNumber[3] = -1; + BriefMessages[i].m_nNumber[4] = -1; + BriefMessages[i].m_nNumber[5] = -1; + BriefMessages[i].m_pString = str; + if (i == 0) + AddToPreviousBriefArray( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + BriefMessages[0].m_pString); +} + +void +CMessages::AddMessageJumpQWithString(wchar *text, uint32 time, uint16 flag, wchar *str) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, text, 256); + InsertStringInString(outstr, str); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + BriefMessages[0].m_pText = text; + BriefMessages[0].m_nFlag = flag; + BriefMessages[0].m_nTime = time; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[0].m_nNumber[0] = -1; + BriefMessages[0].m_nNumber[1] = -1; + BriefMessages[0].m_nNumber[2] = -1; + BriefMessages[0].m_nNumber[3] = -1; + BriefMessages[0].m_nNumber[4] = -1; + BriefMessages[0].m_nNumber[5] = -1; + BriefMessages[0].m_pString = str; + AddToPreviousBriefArray(text, -1, -1, -1, -1, -1, -1, str); +} + +inline bool +FastWideStringComparison(wchar *str1, wchar *str2) +{ + while (*str1 == *str2) { + ++str1; + ++str2; + if (!*str1 && !*str2) return true; + } + return false; +} + +void +CMessages::ClearThisPrint(wchar *str) +{ + bool equal; + + do { + equal = false; + uint16 i; + for (i = 0; i < NUMBRIEFMESSAGES && BriefMessages[i].m_pText != nil; i++) { + equal = FastWideStringComparison(str, BriefMessages[i].m_pText); + + if (equal) break; + } + + if (equal) { + if (i != 0) { + BriefMessages[i].m_pText = nil; + for (; i < NUMBRIEFMESSAGES-1 && BriefMessages[i+1].m_pText != nil; i++) { + BriefMessages[i] = BriefMessages[i + 1]; + } + BriefMessages[i].m_pText = nil; + } else { + BriefMessages[0].m_pText = nil; + for (; i < NUMBRIEFMESSAGES-1 && BriefMessages[i+1].m_pText != nil; i++) { + BriefMessages[i] = BriefMessages[i + 1]; + } + BriefMessages[i].m_pText = nil; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + if (BriefMessages[0].m_pText != nil) + AddToPreviousBriefArray( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + BriefMessages[0].m_pString); + } + } + } while (equal); +} + +void +CMessages::ClearThisBigPrint(wchar *str) +{ + bool equal; + + do { + uint16 i = 0; + equal = false; + uint16 style = 0; + while (style < NUMBIGMESSAGES) + { + if (i >= 4) + break; + + if (CMessages::BIGMessages[style].m_Stack[i].m_pText == nil || equal) + break; + + equal = FastWideStringComparison(str, BIGMessages[style].m_Stack[i].m_pText); + + if (!equal && ++i == 4) { + i = 0; + style++; + } + } + if (equal) { + if (i != 0) { + BIGMessages[style].m_Stack[i].m_pText = nil; + while (i < 3) { + if (BIGMessages[style].m_Stack[i + 1].m_pText == nil) + break; + BIGMessages[style].m_Stack[i] = BIGMessages[style].m_Stack[i + 1]; + i++; + } + BIGMessages[style].m_Stack[i].m_pText = nil; + } else { + BIGMessages[style].m_Stack[0].m_pText = nil; + i = 0; + while (i < 3) { + if (BIGMessages[style].m_Stack[i + 1].m_pText == nil) + break; + BIGMessages[style].m_Stack[i] = BIGMessages[style].m_Stack[i + 1]; + i++; + } + BIGMessages[style].m_Stack[i].m_pText = nil; + BIGMessages[style].m_Stack[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + } + } + } while (equal); +} + +void +CMessages::ClearAllMessagesDisplayedByGame() +{ + ClearMessages(); + for (int32 i = 0; i < NUMPREVIOUSBRIEFS; i++) { + PreviousBriefs[i].m_pText = nil; + PreviousBriefs[i].m_pString = nil; + } + CHud::GetRidOfAllHudMessages(); + CUserDisplay::Pager.ClearMessages(); +} diff --git a/src/text/Messages.h b/src/text/Messages.h new file mode 100644 index 0000000..e8ba1bf --- /dev/null +++ b/src/text/Messages.h @@ -0,0 +1,69 @@ +#pragma once + +struct tMessage +{ + wchar *m_pText; + uint16 m_nFlag; + uint32 m_nTime; + uint32 m_nStartTime; + int32 m_nNumber[6]; + wchar *m_pString; +}; + +struct tBigMessage +{ + tMessage m_Stack[4]; +}; + +struct tPreviousBrief +{ + wchar *m_pText; + int32 m_nNumber[6]; + wchar *m_pString; +}; + +#define NUMBRIEFMESSAGES 8 +#define NUMBIGMESSAGES 6 +#define NUMPREVIOUSBRIEFS 5 + +class CMessages +{ +public: + static tMessage BriefMessages[NUMBRIEFMESSAGES]; + static tBigMessage BIGMessages[NUMBIGMESSAGES]; + static tPreviousBrief PreviousBriefs[NUMPREVIOUSBRIEFS]; + static char PreviousMissionTitle[16]; // unused +public: + static void Init(void); + static uint16 GetWideStringLength(wchar *src); + static void WideStringCopy(wchar *dst, wchar *src, uint16 size); + static bool WideStringCompare(wchar *str1, wchar *str2, uint16 size); + static void Process(void); + static void Display(void); + static void AddMessage(wchar *key, uint32 time, uint16 pos); + static void AddMessageJumpQ(wchar *key, uint32 time, uint16 pos); + static void AddMessageSoon(wchar *key, uint32 time, uint16 pos); + static void ClearMessages(void); + static void ClearSmallMessagesOnly(void); + static void AddBigMessage(wchar *key, uint32 time, uint16 pos); + static void AddBigMessageQ(wchar *key, uint32 time, uint16 pos); + static void AddToPreviousBriefArray(wchar *text, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, wchar *string); + static void InsertNumberInString(wchar *src, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, wchar *dst); + static void InsertStringInString(wchar *str1, wchar *str2); + static void InsertPlayerControlKeysInString(wchar *src); + static void AddMessageWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); + static void AddMessageJumpQWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); + static void AddMessageSoonWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); + static void AddBigMessageWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); + static void AddBigMessageWithNumberQ(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); + static void AddMessageWithString(wchar *text, uint32 time, uint16 flag, wchar *str); + static void AddMessageJumpQWithString(wchar *text, uint32 time, uint16 flag, wchar *str); + static void ClearThisPrint(wchar *str); + static void ClearThisBigPrint(wchar *str); + static void ClearAllMessagesDisplayedByGame(void); + + // unused or cut + //static void AddMessageSoonWithString(wchar*, uint32, uint16, wchar*); + //static void CutString(int16, char*, char**); + //static void PrintString(char*, int16, int16, int16); +}; diff --git a/src/text/Pager.cpp b/src/text/Pager.cpp new file mode 100644 index 0000000..609c686 --- /dev/null +++ b/src/text/Pager.cpp @@ -0,0 +1,184 @@ +#include "common.h" + +#include "Pager.h" +#include "Timer.h" +#include "Messages.h" +#include "Hud.h" +#include "Camera.h" + +void +CPager::Init() +{ + ClearMessages(); + m_nNumDisplayLetters = 8; +} + +void +CPager::Process() +{ + if (m_messages[0].m_pText != nil && m_messages[0].m_nCurrentPosition >= (int32)m_messages[0].m_nStringLength) { + m_messages[0].m_pText = nil; + uint16 i = 0; + while (i < NUMPAGERMESSAGES-1) { + if (m_messages[i + 1].m_pText == nil) break; + m_messages[i] = m_messages[i + 1]; + i++; + } + m_messages[i].m_pText = nil; + if (m_messages[0].m_pText != nil) + CMessages::AddToPreviousBriefArray( + m_messages[0].m_pText, + m_messages[0].m_nNumber[0], + m_messages[0].m_nNumber[1], + m_messages[0].m_nNumber[2], + m_messages[0].m_nNumber[3], + m_messages[0].m_nNumber[4], + m_messages[0].m_nNumber[5], + 0); + } + Display(); + if (m_messages[0].m_pText != nil) { + if (TheCamera.m_WideScreenOn || !CHud::m_Wants_To_Draw_Hud || CHud::m_BigMessage[0][0] || CHud::m_BigMessage[2][0]) { + RestartCurrentMessage(); + } else { + if (CTimer::GetTimeInMilliseconds() > m_messages[0].m_nTimeToChangePosition) { + m_messages[0].m_nCurrentPosition++; + m_messages[0].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + m_messages[0].m_nSpeedMs; + } + } + } +} + +void +CPager::Display() +{ + wchar outstr1[256]; + wchar outstr2[260]; + + wchar *pText = m_messages[0].m_pText; + uint16 i = 0; + if (pText != nil) { + CMessages::InsertNumberInString( + pText, + m_messages[0].m_nNumber[0], + m_messages[0].m_nNumber[1], + m_messages[0].m_nNumber[2], + m_messages[0].m_nNumber[3], + m_messages[0].m_nNumber[4], + m_messages[0].m_nNumber[5], + outstr1); + for (; i < m_nNumDisplayLetters; i++) { + int pos = m_messages[0].m_nCurrentPosition + i; + if (pos >= 0) { + if (!outstr1[pos]) break; + + outstr2[i] = outstr1[pos]; + } else { + outstr2[i] = ' '; + } + } + } + outstr2[i] = '\0'; + CHud::SetPagerMessage(outstr2); +} + +void +CPager::AddMessage(wchar *str, uint16 speed, uint16 priority, uint16 a5) +{ + uint16 size = CMessages::GetWideStringLength(str); + for (int32 i = 0; i < NUMPAGERMESSAGES; i++) { + if (m_messages[i].m_pText) { + if (m_messages[i].m_nPriority >= priority) + continue; + + for (int j = NUMPAGERMESSAGES-1; j > i; j--) + m_messages[j] = m_messages[j-1]; + + } + m_messages[i].m_pText = str; + m_messages[i].m_nSpeedMs = speed; + m_messages[i].m_nPriority = priority; + m_messages[i].unused = a5; + m_messages[i].m_nCurrentPosition = -(m_nNumDisplayLetters + 10); + m_messages[i].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + speed; + m_messages[i].m_nStringLength = size; + m_messages[i].m_nNumber[0] = -1; + m_messages[i].m_nNumber[1] = -1; + m_messages[i].m_nNumber[2] = -1; + m_messages[i].m_nNumber[3] = -1; + m_messages[i].m_nNumber[4] = -1; + m_messages[i].m_nNumber[5] = -1; + + if (i == 0) + CMessages::AddToPreviousBriefArray( + m_messages[0].m_pText, + m_messages[0].m_nNumber[0], + m_messages[0].m_nNumber[1], + m_messages[0].m_nNumber[2], + m_messages[0].m_nNumber[3], + m_messages[0].m_nNumber[4], + m_messages[0].m_nNumber[5], + nil); + return; + } +} + +void +CPager::AddMessageWithNumber(wchar *str, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, uint16 speed, uint16 priority, uint16 a11) +{ + wchar nstr[520]; + + CMessages::InsertNumberInString(str, n1, n2, n3, n4, n5, n6, nstr); + uint16 size = CMessages::GetWideStringLength(nstr); + for (int32 i = 0; i < NUMPAGERMESSAGES; i++) { + if (m_messages[i].m_pText) { + if (m_messages[i].m_nPriority >= priority) + continue; + + for (int j = NUMPAGERMESSAGES-1; j > i; j--) + m_messages[j] = m_messages[j - 1]; + + } + m_messages[i].m_pText = str; + m_messages[i].m_nSpeedMs = speed; + m_messages[i].m_nPriority = priority; + m_messages[i].unused = a11; + m_messages[i].m_nCurrentPosition = -(m_nNumDisplayLetters + 10); + m_messages[i].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + speed; + m_messages[i].m_nStringLength = size; + m_messages[i].m_nNumber[0] = n1; + m_messages[i].m_nNumber[1] = n2; + m_messages[i].m_nNumber[2] = n3; + m_messages[i].m_nNumber[3] = n4; + m_messages[i].m_nNumber[4] = n5; + m_messages[i].m_nNumber[5] = n6; + + if (i == 0) + CMessages::AddToPreviousBriefArray( + m_messages[0].m_pText, + m_messages[0].m_nNumber[0], + m_messages[0].m_nNumber[1], + m_messages[0].m_nNumber[2], + m_messages[0].m_nNumber[3], + m_messages[0].m_nNumber[4], + m_messages[0].m_nNumber[5], + nil); + return; + } +} + +void +CPager::ClearMessages() +{ + for (int32 i = 0; i < NUMPAGERMESSAGES; i++) + m_messages[i].m_pText = nil; +} + +void +CPager::RestartCurrentMessage() +{ + if (m_messages[0].m_pText != nil) { + m_messages[0].m_nCurrentPosition = -(m_nNumDisplayLetters + 10); + m_messages[0].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + m_messages[0].m_nSpeedMs; + } +} \ No newline at end of file diff --git a/src/text/Pager.h b/src/text/Pager.h new file mode 100644 index 0000000..1630797 --- /dev/null +++ b/src/text/Pager.h @@ -0,0 +1,28 @@ +#pragma once + +struct PagerMessage { + wchar *m_pText; + uint16 m_nSpeedMs; + int16 m_nCurrentPosition; + uint16 m_nStringLength; + uint16 m_nPriority; + uint32 m_nTimeToChangePosition; + int16 unused; // but still set in SCM. importance? ringtone? + int32 m_nNumber[6]; +}; + +#define NUMPAGERMESSAGES 8 + +class CPager +{ + int16 m_nNumDisplayLetters; + PagerMessage m_messages[NUMPAGERMESSAGES]; +public: + void Init(); + void Process(); + void Display(); + void AddMessage(wchar*, uint16, uint16, uint16); + void AddMessageWithNumber(wchar *str, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, uint16 speed, uint16 priority, uint16 a11); + void ClearMessages(); + void RestartCurrentMessage(); +}; \ No newline at end of file diff --git a/src/text/Text.cpp b/src/text/Text.cpp new file mode 100644 index 0000000..08ab0e1 --- /dev/null +++ b/src/text/Text.cpp @@ -0,0 +1,331 @@ +#include "common.h" + +#include "FileMgr.h" +#ifdef MORE_LANGUAGES +#include "Game.h" +#endif +#include "Frontend.h" +#include "Messages.h" +#include "Text.h" + +wchar WideErrorString[25]; + +CText TheText; + +CText::CText(void) +{ + encoding = 'e'; + memset(WideErrorString, 0, sizeof(WideErrorString)); +} + +void +CText::Load(void) +{ + uint8 *filedata; + char filename[32], type[4]; + ssize_t offset, length; + size_t sectlen; + + Unload(); + filedata = new uint8[0x40000]; + + CFileMgr::SetDir("TEXT"); + switch(CMenuManager::m_PrefsLanguage){ + case CMenuManager::LANGUAGE_AMERICAN: + sprintf(filename, "AMERICAN.GXT"); + break; + case CMenuManager::LANGUAGE_FRENCH: + sprintf(filename, "FRENCH.GXT"); + break; + case CMenuManager::LANGUAGE_GERMAN: + sprintf(filename, "GERMAN.GXT"); + break; + case CMenuManager::LANGUAGE_ITALIAN: + sprintf(filename, "ITALIAN.GXT"); + break; + case CMenuManager::LANGUAGE_SPANISH: + sprintf(filename, "SPANISH.GXT"); + break; +#ifdef MORE_LANGUAGES + case CMenuManager::LANGUAGE_POLISH: + sprintf(filename, "POLISH.GXT"); + break; + case CMenuManager::LANGUAGE_RUSSIAN: + sprintf(filename, "RUSSIAN.GXT"); + break; + case CMenuManager::LANGUAGE_JAPANESE: + sprintf(filename, "JAPANESE.GXT"); + break; +#endif + } + + 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) +{ +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) + return keyArray.Search(key, data.chars); +#else + return keyArray.Search(key); +#endif +} + +wchar UpperCaseTable[128] = { + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, + 149, 173, 173, 175, 176, 177, 178, 179, 180, 181, 182, + 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, + 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, + 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, + 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, + 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, + 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, + 249, 250, 251, 252, 253, 254, 255 +}; + +wchar FrenchUpperCaseTable[128] = { + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 65, 65, 65, 65, 132, 133, 69, 69, 69, 69, 73, 73, + 73, 73, 79, 79, 79, 79, 85, 85, 85, 85, 173, 173, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, + 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, + 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, + 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, + 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, + 253, 254, 255 +}; + +wchar +CText::GetUpperCase(wchar c) +{ + switch (encoding) + { + case 'e': + if (c >= 'a' && c <= 'z') + return c - 32; + break; + case 'f': + if (c >= 'a' && c <= 'z') + return c - 32; + + if (c >= 128 && c <= 255) + return FrenchUpperCaseTable[c-128]; + break; + case 'g': + case 'i': + case 's': + if (c >= 'a' && c <= 'z') + return c - 32; + + if (c >= 128 && c <= 255) + return UpperCaseTable[c-128]; + break; + default: + break; + } + return c; +} + +void +CText::UpperCase(wchar *s) +{ + while(*s){ + *s = GetUpperCase(*s); + s++; + } +} + + +void +CKeyArray::Load(size_t length, uint8 *data, ssize_t *offset) +{ + size_t i; + uint8 *rawbytes; + + // You can make numEntries size_t if you want to exceed 32-bit boundaries, everything else should be ready. + numEntries = (int)(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) +{ +#if !defined(FIX_BUGS) && !defined(FIX_BUGS_64) + int i; + for(i = 0; i < numEntries; i++) + entries[i].value = (wchar*)((uint8*)chars + (uintptr)entries[i].value); +#endif +} + +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* +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) +CKeyArray::Search(const char *key, wchar *data) +#else +CKeyArray::Search(const char *key) +#endif +{ + CKeyEntry *found; + char errstr[25]; + int i; + +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) + found = BinarySearch(key, entries, 0, numEntries-1); + if(found) + return (wchar*)((uint8*)data + found->valueOffset); +#else + found = BinarySearch(key, entries, 0, numEntries-1); + if(found) + return found->value; +#endif + sprintf(errstr, "%s missing", key); + for(i = 0; i < 25; i++) + WideErrorString[i] = errstr[i]; + return WideErrorString; +} + + +void +CData::Load(size_t length, uint8 *data, ssize_t *offset) +{ + size_t i; + uint8 *rawbytes; + + // You can make numChars size_t if you want to exceed 32-bit boundaries, everything else should be ready. + numChars = (int)(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; +} + +char* +UnicodeToAscii(wchar *src) +{ + static char aStr[256]; + int len; + for(len = 0; *src != '\0' && len < 256-1; len++, src++) +#ifdef MORE_LANGUAGES + if(*src < 128 || ((CGame::russianGame || CGame::japaneseGame) && *src < 256)) +#else + if(*src < 128) +#endif + aStr[len] = *src; + else + aStr[len] = '#'; + aStr[len] = '\0'; + return aStr; +} + +char* +UnicodeToAsciiForSaveLoad(wchar *src) +{ + static char aStr[256]; + int len; + for(len = 0; *src != '\0' && len < 256; len++, src++) + if(*src < 256) + aStr[len] = *src; + else + aStr[len] = '#'; + aStr[len] = '\0'; + return aStr; +} + +char* +UnicodeToAsciiForMemoryCard(wchar *src) +{ + static char aStr[256]; + int len; + for(len = 0; *src != '\0' && len < 256; len++, src++) + if(*src < 256) + aStr[len] = *src; + else + aStr[len] = '#'; + aStr[len] = '\0'; + return aStr; +} + +void +TextCopy(wchar *dst, const wchar *src) +{ + while((*dst++ = *src++) != '\0'); +} diff --git a/src/text/Text.h b/src/text/Text.h new file mode 100644 index 0000000..ab6d180 --- /dev/null +++ b/src/text/Text.h @@ -0,0 +1,66 @@ +#pragma once + +char *UnicodeToAscii(wchar *src); +char *UnicodeToAsciiForSaveLoad(wchar *src); +char *UnicodeToAsciiForMemoryCard(wchar *src); +void TextCopy(wchar *dst, const wchar *src); + +struct CKeyEntry +{ +#if defined(FIX_BUGS) || defined(FIX_BUGS_64) + uint32 valueOffset; +#else + wchar *value; +#endif + char key[8]; +}; + +// If this fails, CKeyArray::Load will have to be fixed +VALIDATE_SIZE(CKeyEntry, 12); + +class CKeyArray +{ +public: + CKeyEntry *entries; + int numEntries; // You can make this size_t if you want to exceed 32-bit boundaries, everything else should be ready. + + CKeyArray(void) : entries(nil), numEntries(0) {} + ~CKeyArray(void) { Unload(); } + void Load(size_t length, uint8 *data, ssize_t *offset); + void Unload(void); + void Update(wchar *chars); + CKeyEntry *BinarySearch(const char *key, CKeyEntry *entries, int16 low, int16 high); +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) + wchar *Search(const char *key, wchar *data); +#else + wchar *Search(const char *key); +#endif +}; + +class CData +{ +public: + wchar *chars; + int numChars; // You can make this size_t if you want to exceed 32-bit boundaries, everything else should be ready. + + CData(void) : chars(nil), numChars(0) {} + ~CData(void) { Unload(); } + void Load(size_t length, uint8 *data, ssize_t *offset); + void Unload(void); +}; + +class CText +{ + CKeyArray keyArray; + CData data; + char encoding; +public: + 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/vehicles/Automobile.cpp b/src/vehicles/Automobile.cpp new file mode 100644 index 0000000..417dd14 --- /dev/null +++ b/src/vehicles/Automobile.cpp @@ -0,0 +1,4735 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "RwHelper.h" +#include "Pad.h" +#include "ModelIndices.h" +#include "VisibilityPlugins.h" +#include "DMAudio.h" +#include "Clock.h" +#include "Timecycle.h" +#include "ZoneCull.h" +#include "Camera.h" +#include "Darkel.h" +#include "Rubbish.h" +#include "Fire.h" +#include "Explosion.h" +#include "Particle.h" +#include "ParticleObject.h" +#include "Antennas.h" +#include "Skidmarks.h" +#include "Shadows.h" +#include "PointLights.h" +#include "Coronas.h" +#include "SpecialFX.h" +#include "WaterCannon.h" +#include "WaterLevel.h" +#include "Floater.h" +#include "World.h" +#include "SurfaceTable.h" +#include "Weather.h" +#include "HandlingMgr.h" +#include "Record.h" +#include "Remote.h" +#include "Population.h" +#include "CarCtrl.h" +#include "CarAI.h" +#include "Garages.h" +#include "PathFind.h" +#include "AnimManager.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Object.h" +#include "Automobile.h" +#include "Wanted.h" +#include "SaveBuf.h" + +bool bAllCarCheat; // unused + +RwObject *GetCurrentAtomicObjectCB(RwObject *object, void *data); + +bool CAutomobile::m_sAllTaxiLights; + +const uint32 CAutomobile::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 1448; +#else + sizeof(CAutomobile); +#endif + +CAutomobile::CAutomobile(int32 id, uint8 CreatedBy) + : CVehicle(CreatedBy) +{ + int i; + + m_vehType = VEHICLE_TYPE_CAR; + + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + m_fFireBlowUpTimer = 0.0f; + m_auto_unk1 = 0; + bTaxiLight = m_sAllTaxiLights; + bFixedColour = false; + bBigWheels = false; + bWaterTight = false; + + SetModelIndex(id); + + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId); + + m_auto_unused1 = 20.0f; + m_auto_unused2 = 0; + + mi->ChooseVehicleColour(m_currentColour1, m_currentColour2); + + bIsVan = !!(pHandling->Flags & HANDLING_IS_VAN); + bIsBig = !!(pHandling->Flags & HANDLING_IS_BIG); + bIsBus = !!(pHandling->Flags & HANDLING_IS_BUS); + bLowVehicle = !!(pHandling->Flags & HANDLING_IS_LOW); + + // Doors + if(bIsBus){ + Doors[DOOR_FRONT_LEFT].Init(-HALFPI, 0.0f, 0, 2); + Doors[DOOR_FRONT_RIGHT].Init(0.0f, HALFPI, 1, 2); + }else{ + Doors[DOOR_FRONT_LEFT].Init(-PI*0.4f, 0.0f, 0, 2); + Doors[DOOR_FRONT_RIGHT].Init(0.0f, PI*0.4f, 1, 2); + } + if(bIsVan){ + Doors[DOOR_REAR_LEFT].Init(-HALFPI, 0.0f, 1, 2); + Doors[DOOR_REAR_RIGHT].Init(0.0f, HALFPI, 0, 2); + }else{ + Doors[DOOR_REAR_LEFT].Init(-PI*0.4f, 0.0f, 0, 2); + Doors[DOOR_REAR_RIGHT].Init(0.0f, PI*0.4f, 1, 2); + } + if(pHandling->Flags & HANDLING_REV_BONNET) + Doors[DOOR_BONNET].Init(-PI*0.3f, 0.0f, 1, 0); + else + Doors[DOOR_BONNET].Init(0.0f, PI*0.3f, 1, 0); + if(pHandling->Flags & HANDLING_HANGING_BOOT) + Doors[DOOR_BOOT].Init(-PI*0.4f, 0.0f, 0, 0); + else if(pHandling->Flags & HANDLING_TAILGATE_BOOT) + Doors[DOOR_BOOT].Init(0.0, HALFPI, 1, 0); + else + Doors[DOOR_BOOT].Init(-PI*0.3f, 0.0f, 1, 0); + if(pHandling->Flags & HANDLING_NO_DOORS){ + Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_REAR_LEFT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_REAR_RIGHT, DOOR_STATUS_MISSING); + } + + for(i = 0; i < 6; i++) + m_randomValues[i] = CGeneral::GetRandomNumberInRange(-0.15f, 0.15f); + + m_fMass = pHandling->fMass; + m_fTurnMass = pHandling->fTurnMass; + m_vecCentreOfMass = pHandling->CentreOfMass; + m_fAirResistance = pHandling->Dimension.x*pHandling->Dimension.z/m_fMass; + m_fElasticity = 0.05f; + m_fBuoyancy = pHandling->fBuoyancy; + + m_nBusDoorTimerEnd = 0; + m_nBusDoorTimerStart = 0; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + m_fBrakePedal = 0.0f; + m_pSetOnFireEntity = nil; + m_fGasPedalAudio = 0.0f; + bNotDamagedUpsideDown = false; + bMoreResistantToDamage = false; + m_fVelocityChangeForAudio = 0.0f; + m_hydraulicState = 0; + + for(i = 0; i < 4; i++){ + m_aGroundPhysical[i] = nil; + m_aGroundOffset[i] = CVector(0.0f, 0.0f, 0.0f); + m_aSuspensionSpringRatioPrev[i] = m_aSuspensionSpringRatio[i] = 1.0f; + m_aWheelTimer[i] = 0.0f; + m_aWheelRotation[i] = 0.0f; + m_aWheelSpeed[i] = 0.0f; + m_aWheelState[i] = WHEEL_STATE_NORMAL; + m_aWheelSkidmarkMuddy[i] = false; + m_aWheelSkidmarkBloody[i] = false; + } + + m_nWheelsOnGround = 0; + m_nDriveWheelsOnGround = 0; + m_nDriveWheelsOnGroundPrev = 0; + m_fHeightAboveRoad = 0.0f; + m_fTraction = 1.0f; + + CColModel *colModel = mi->GetColModel(); + if(colModel->lines == nil){ + colModel->lines = (CColLine*)RwMalloc(4*sizeof(CColLine)); + colModel->numLines = 4; + } + + SetupSuspensionLines(); + + SetStatus(STATUS_SIMPLE); + bUseCollisionRecords = true; + + m_nNumPassengers = 0; + + m_bombType = CARBOMB_NONE; + bDriverLastFrame = false; + m_pBombRigger = nil; + + if(m_nDoorLock == CARLOCK_UNLOCKED && + (id == MI_POLICE || id == MI_ENFORCER || id == MI_RHINO)) + m_nDoorLock = CARLOCK_LOCKED_INITIALLY; + + m_fCarGunLR = 0.0f; + m_fCarGunUD = 0.05f; + m_fPropellerRotation = 0.0f; + m_weaponDoorTimerLeft = 0.0f; + m_weaponDoorTimerRight = m_weaponDoorTimerLeft; + + if(GetModelIndex() == MI_DODO){ + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0); + CMatrix mat1; + mat1.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + CMatrix mat2(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + mat1.GetPosition() += CVector(mat2.GetPosition().x + 0.1f, 0.0f, mat2.GetPosition().z); + mat1.UpdateRW(); + }else if(GetModelIndex() == MI_MIAMI_SPARROW || GetModelIndex() == MI_MIAMI_RCRAIDER){ + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]), 0); + }else if(GetModelIndex() == MI_RHINO){ + bExplosionProof = true; + bBulletProof = true; + } +} + + +void +CAutomobile::SetModelIndex(uint32 id) +{ + CVehicle::SetModelIndex(id); + SetupModelNodes(); +} + +CVector vecDAMAGE_ENGINE_POS_SMALL(-0.1f, -0.1f, 0.0f); +CVector vecDAMAGE_ENGINE_POS_BIG(-0.5f, -0.3f, 0.0f); + +#pragma optimize("", off) // that's what R* did + +void +CAutomobile::ProcessControl(void) +{ + int i; + float wheelRot; + CColModel *colModel; + float brake = 0.0f; + + if(bUsingSpecialColModel) + colModel = &CWorld::Players[CWorld::PlayerInFocus].m_ColModel; + else + colModel = GetColModel(); + bWarnedPeds = false; + + // skip if the collision isn't for the current level + if(colModel->level > LEVEL_GENERIC && colModel->level != CCollision::ms_collisionInMemory) + return; + + // Improve grip of vehicles in certain cases + bool strongGrip1 = false; + bool strongGrip2 = false; + if(FindPlayerVehicle() && this != FindPlayerVehicle() && FindPlayerPed()->m_pWanted->GetWantedLevel() > 3 && + (AutoPilot.m_nCarMission == MISSION_RAMPLAYER_FARAWAY || AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE || + AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_FARAWAY || AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_CLOSE) && + FindPlayerSpeed().Magnitude() > 0.3f){ + + strongGrip1 = true; + if(FindPlayerSpeed().Magnitude() > 0.4f && + m_vecMoveSpeed.Magnitude() < 0.3f) + strongGrip2 = true; + else if((GetPosition() - FindPlayerCoors()).Magnitude() > 50.0f) + strongGrip2 = true; + } + + if(bIsBus) + ProcessAutoBusDoors(); + + ProcessCarAlarm(); + + // Scan if this car sees the player committing any crimes + if(GetStatus() != STATUS_ABANDONED && GetStatus() != STATUS_WRECKED && + GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && GetStatus() != STATUS_PLAYER_DISABLED){ + switch(GetModelIndex()) + case MI_FBICAR: + case MI_POLICE: + case MI_ENFORCER: + case MI_SECURICA: + case MI_RHINO: + case MI_BARRACKS: + ScanForCrimes(); + } + + // Process driver + if(pDriver){ + if(!bDriverLastFrame && m_bombType == CARBOMB_ONIGNITIONACTIVE){ + // If someone enters the car and there is a bomb, detonate + m_nBombTimer = 1000; + m_pBlowUpEntity = m_pBombRigger; + if(m_pBlowUpEntity) + m_pBlowUpEntity->RegisterReference((CEntity**)&m_pBlowUpEntity); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_BOMB_TICK, 1.0f); + } + bDriverLastFrame = true; + + if(IsUpsideDown() && CanPedEnterCar()){ + if(!pDriver->IsPlayer() && + !(pDriver->m_leader && pDriver->m_leader->bInVehicle) && + pDriver->CharCreatedBy != MISSION_CHAR) + pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, this); + } + }else + bDriverLastFrame = false; + + // Process passengers + if(m_nNumPassengers != 0 && IsUpsideDown() && CanPedEnterCar()){ + for(i = 0; i < m_nNumMaxPassengers; i++) + if(pPassengers[i]) + if(!pPassengers[i]->IsPlayer() && + !(pPassengers[i]->m_leader && pPassengers[i]->m_leader->bInVehicle) && + pPassengers[i]->CharCreatedBy != MISSION_CHAR) + pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, this); + } + + CRubbish::StirUp(this); + + // blend in clump + int clumpAlpha = CVisibilityPlugins::GetClumpAlpha((RpClump*)m_rwObject); + if(bFadeOut){ + clumpAlpha -= 8; + if(clumpAlpha < 0) + clumpAlpha = 0; + }else if(clumpAlpha < 255){ + clumpAlpha += 16; + if(clumpAlpha > 255) + clumpAlpha = 255; + } + CVisibilityPlugins::SetClumpAlpha((RpClump*)m_rwObject, clumpAlpha); + + AutoPilot.m_bSlowedDownBecauseOfCars = false; + AutoPilot.m_bSlowedDownBecauseOfPeds = false; + + // Set Center of Mass to make car more stable + if(strongGrip1 || bCheat3) + m_vecCentreOfMass.z = 0.3f*m_aSuspensionSpringLength[0] + -1.0f*m_fHeightAboveRoad; + else if(pHandling->Flags & HANDLING_NONPLAYER_STABILISER && GetStatus() == STATUS_PHYSICS) + m_vecCentreOfMass.z = pHandling->CentreOfMass.z - 0.2f*pHandling->Dimension.z; + else + m_vecCentreOfMass.z = pHandling->CentreOfMass.z; + + // Process depending on status + + bool playerRemote = false; + switch(GetStatus()){ + case STATUS_PLAYER_REMOTE: +#ifdef FIX_BUGS + if (CPad::GetPad(0)->CarGunJustDown()) { +#else + if (CPad::GetPad(0)->WeaponJustDown()) { +#endif + BlowUpCar(FindPlayerPed()); + CRemote::TakeRemoteControlledCarFromPlayer(); + } + + if(GetModelIndex() == MI_RCBANDIT){ + CVector pos = GetPosition(); + // FindPlayerCoors unused + if(RcbanditCheckHitWheels() || bIsInWater || CPopulation::IsPointInSafeZone(&pos)){ + if(CPopulation::IsPointInSafeZone(&pos)) + CGarages::TriggerMessage("HM2_5", -1, 5000, -1); + CRemote::TakeRemoteControlledCarFromPlayer(); + BlowUpCar(FindPlayerPed()); + } + } + + if(CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle == this) + playerRemote = true; + // fall through + case STATUS_PLAYER: + if(playerRemote || + pDriver && pDriver->GetPedState() != PED_EXIT_CAR && pDriver->GetPedState() != PED_DRAG_FROM_CAR){ + // process control input if controlled by player + if(playerRemote || pDriver->m_nPedType == PEDTYPE_PLAYER1) + ProcessControlInputs(0); + + PruneReferences(); + + if(GetStatus() == STATUS_PLAYER && !CRecordDataForChase::IsRecording()) + DoDriveByShootings(); + } + break; + + case STATUS_SIMPLE: + CCarAI::UpdateCarAI(this); + CPhysical::ProcessControl(); + CCarCtrl::UpdateCarOnRails(this); + + m_nWheelsOnGround = 4; + m_nDriveWheelsOnGroundPrev = m_nDriveWheelsOnGround; + m_nDriveWheelsOnGround = 4; + + pHandling->Transmission.CalculateGearForSimpleCar(AutoPilot.m_fMaxTrafficSpeed/50.0f, m_nCurrentGear); + + wheelRot = ProcessWheelRotation(WHEEL_STATE_NORMAL, GetForward(), m_vecMoveSpeed, 0.35f); + for(i = 0; i < 4; i++) + m_aWheelRotation[i] += wheelRot; + + PlayHornIfNecessary(); + ReduceHornCounter(); + bVehicleColProcessed = false; + // that's all we do for simple vehicles + return; + + case STATUS_PHYSICS: + CCarAI::UpdateCarAI(this); + CCarCtrl::SteerAICarWithPhysics(this); + PlayHornIfNecessary(); + break; + + case STATUS_ABANDONED: + m_fBrakePedal = 0.2f; + bIsHandbrakeOn = false; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + m_nCarHornTimer = 0; + break; + + case STATUS_WRECKED: + m_fBrakePedal = 0.05f; + bIsHandbrakeOn = true; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + m_nCarHornTimer = 0; + break; + + case STATUS_PLAYER_DISABLED: + m_fBrakePedal = 1.0f; + bIsHandbrakeOn = true; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + m_nCarHornTimer = 0; + break; + default: break; + } + + // what's going on here? + if(GetPosition().z < -0.6f && + Abs(m_vecMoveSpeed.x) < 0.05f && + Abs(m_vecMoveSpeed.y) < 0.05f) + m_vecTurnSpeed *= Pow(0.95f, CTimer::GetTimeStep()); + + // Skip physics if object is found to have been static recently + bool skipPhysics = false; + if(!bIsStuck && (GetStatus() == STATUS_ABANDONED || GetStatus() == STATUS_WRECKED)){ + bool makeStatic = false; + float moveSpeedLimit, turnSpeedLimit, distanceLimit; + + if(!bVehicleColProcessed && + m_vecMoveSpeed.IsZero() && + // BUG? m_aSuspensionSpringRatioPrev[3] is checked twice in the game. also, why 3? + m_aSuspensionSpringRatioPrev[3] != 1.0f) + makeStatic = true; + + if(GetStatus() == STATUS_WRECKED){ + moveSpeedLimit = 0.006f; + turnSpeedLimit = 0.0015f; + distanceLimit = 0.015f; + }else{ + moveSpeedLimit = 0.003f; + turnSpeedLimit = 0.0009f; + distanceLimit = 0.005f; + } + + m_vecMoveSpeedAvg = (m_vecMoveSpeedAvg + m_vecMoveSpeed)/2.0f; + m_vecTurnSpeedAvg = (m_vecTurnSpeedAvg + m_vecTurnSpeed)/2.0f; + + if(m_vecMoveSpeedAvg.MagnitudeSqr() <= sq(moveSpeedLimit*CTimer::GetTimeStep()) && + m_vecTurnSpeedAvg.MagnitudeSqr() <= sq(turnSpeedLimit*CTimer::GetTimeStep()) && + m_fDistanceTravelled < distanceLimit || + makeStatic){ + m_nStaticFrames++; + + if(m_nStaticFrames > 10 || makeStatic) + if(!CCarCtrl::MapCouldMoveInThisArea(GetPosition().x, GetPosition().y)){ + if(!makeStatic || m_nStaticFrames > 10) + m_nStaticFrames = 10; + + skipPhysics = true; + + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + } + }else + m_nStaticFrames = 0; + } + + // Postpone + for(i = 0; i < 4; i++) + if(m_aGroundPhysical[i] && !CWorld::bForceProcessControl && m_aGroundPhysical[i]->bIsInSafePosition){ + bWasPostponed = true; + return; + } + + VehicleDamage(0.0f, 0); + + // special control + switch(GetModelIndex()){ + case MI_FIRETRUCK: + FireTruckControl(); + break; + case MI_RHINO: + TankControl(); + BlowUpCarsInPath(); + break; + case MI_YARDIE: + // beta also had esperanto here it seems + HydraulicControl(); + break; + default: + if(CVehicle::bCheat3){ + // Make vehicle jump when horn is sounded + if(GetStatus() == STATUS_PLAYER && m_vecMoveSpeed.MagnitudeSqr() > sq(0.2f) && + // BUG: game checks [0] four times, instead of all wheels + m_aSuspensionSpringRatio[0] < 1.0f && + CPad::GetPad(0)->HornJustDown()){ + + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP, 1.0f); + + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, + m_aWheelColPoints[0].point + 0.5f*GetUp(), + 1.3f*m_vecMoveSpeed, nil, 2.5f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, + m_aWheelColPoints[0].point + 0.5f*GetUp(), + 1.2f*m_vecMoveSpeed, nil, 2.0f); + + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, + m_aWheelColPoints[2].point + 0.5f*GetUp(), + 1.3f*m_vecMoveSpeed, nil, 2.5f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, + m_aWheelColPoints[2].point + 0.5f*GetUp(), + 1.2f*m_vecMoveSpeed, nil, 2.0f); + + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, + m_aWheelColPoints[0].point + 0.5f*GetUp() - GetForward(), + 1.3f*m_vecMoveSpeed, nil, 2.5f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, + m_aWheelColPoints[0].point + 0.5f*GetUp() - GetForward(), + 1.2f*m_vecMoveSpeed, nil, 2.0f); + + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, + m_aWheelColPoints[2].point + 0.5f*GetUp() - GetForward(), + 1.3f*m_vecMoveSpeed, nil, 2.5f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, + m_aWheelColPoints[2].point + 0.5f*GetUp() - GetForward(), + 1.2f*m_vecMoveSpeed, nil, 2.0f); + + ApplyMoveForce(CVector(0.0f, 0.0f, 1.0f)*m_fMass*0.4f); + ApplyTurnForce(GetUp()*m_fMass*0.035f, GetForward()*1.0f); + } + } + break; + } + + if(skipPhysics){ + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + m_nCollisionRecords = 0; + bHasCollided = false; + bVehicleColProcessed = false; + m_nDamagePieceType = 0; + m_fDamageImpulse = 0.0f; + m_pDamageEntity = nil; + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + }else{ + + // This has to be done if ProcessEntityCollision wasn't called + if(!bVehicleColProcessed){ + CMatrix mat(GetMatrix()); + bIsStuck = false; + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + m_fDistanceTravelled = 0.0f; + m_bIsVehicleBeingShifted = false; + bSkipLineCol = false; + ApplyMoveSpeed(); + ApplyTurnSpeed(); + for(i = 0; CheckCollision() && i < 5; i++){ + GetMatrix() = mat; + ApplyMoveSpeed(); + ApplyTurnSpeed(); + } + bIsInSafePosition = true; + bIsStuck = false; + } + + CPhysical::ProcessControl(); + + ProcessBuoyancy(); + + // Rescale spring ratios, i.e. subtract wheel radius + for(i = 0; i < 4; i++){ + // wheel radius in relation to suspension line + float wheelRadius = 1.0f - m_aSuspensionSpringLength[i]/m_aSuspensionLineLength[i]; + // rescale such that 0.0 is fully compressed and 1.0 is fully extended + m_aSuspensionSpringRatio[i] = (m_aSuspensionSpringRatio[i]-wheelRadius)/(1.0f-wheelRadius); + } + + float fwdSpeed = Abs(DotProduct(m_vecMoveSpeed, GetForward())); + CVector contactPoints[4]; // relative to model + CVector contactSpeeds[4]; // speed at contact points + CVector springDirections[4]; // normalized, in world space + + for(i = 0; i < 4; i++){ + // Set spring under certain circumstances + if(Damage.GetWheelStatus(i) == WHEEL_STATUS_MISSING) + m_aSuspensionSpringRatio[i] = 1.0f; + else if(Damage.GetWheelStatus(i) == WHEEL_STATUS_BURST){ + // wheel more bumpy the faster we are + if(CGeneral::GetRandomNumberInRange(0, (uint16)(40*fwdSpeed) + 98) < 100){ + m_aSuspensionSpringRatio[i] += 0.3f*(m_aSuspensionLineLength[i]-m_aSuspensionSpringLength[i])/m_aSuspensionSpringLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + } + + // get points and directions if spring is compressed + if(m_aSuspensionSpringRatio[i] < 1.0f){ + contactPoints[i] = m_aWheelColPoints[i].point - GetPosition(); + springDirections[i] = Multiply3x3(GetMatrix(), colModel->lines[i].p1 - colModel->lines[i].p0); + springDirections[i].Normalise(); + } + } + + // Make springs push up vehicle + for(i = 0; i < 4; i++){ + if(m_aSuspensionSpringRatio[i] < 1.0f){ + float bias = pHandling->fSuspensionBias; + if(i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT) + bias = 1.0f - bias; + + ApplySpringCollision(pHandling->fSuspensionForceLevel, + springDirections[i], contactPoints[i], + m_aSuspensionSpringRatio[i], bias); + m_aWheelSkidmarkMuddy[i] = + m_aWheelColPoints[i].surfaceB == SURFACE_GRASS || + m_aWheelColPoints[i].surfaceB == SURFACE_MUD_DRY || + m_aWheelColPoints[i].surfaceB == SURFACE_SAND; + }else{ + contactPoints[i] = Multiply3x3(GetMatrix(), colModel->lines[i].p1); + } + } + + // Get speed at contact points + for(i = 0; i < 4; i++){ + contactSpeeds[i] = GetSpeed(contactPoints[i]); + if(m_aGroundPhysical[i]){ + // subtract movement of physical we're standing on + contactSpeeds[i] -= m_aGroundPhysical[i]->GetSpeed(m_aGroundOffset[i]); +#ifndef FIX_BUGS + // this shouldn't be reset because we still need it below + m_aGroundPhysical[i] = nil; +#endif + } + } + + // dampen springs + for(i = 0; i < 4; i++) + if(m_aSuspensionSpringRatio[i] < 1.0f) + ApplySpringDampening(pHandling->fSuspensionDampingLevel, + springDirections[i], contactPoints[i], contactSpeeds[i]); + + // Get speed at contact points again + for(i = 0; i < 4; i++){ + contactSpeeds[i] = GetSpeed(contactPoints[i]); + if(m_aGroundPhysical[i]){ + // subtract movement of physical we're standing on + contactSpeeds[i] -= m_aGroundPhysical[i]->GetSpeed(m_aGroundOffset[i]); + m_aGroundPhysical[i] = nil; + } + } + + + bool gripCheat = true; + fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward()); + if(!strongGrip1 && !CVehicle::bCheat3) + gripCheat = false; + float acceleration = pHandling->Transmission.CalculateDriveAcceleration(m_fGasPedal, m_nCurrentGear, m_fChangeGearTime, fwdSpeed, gripCheat); + acceleration /= m_fForceMultiplier; + + // unused + if(GetModelIndex() == MI_MIAMI_RCBARON || + GetModelIndex() == MI_MIAMI_RCRAIDER || + GetModelIndex() == MI_MIAMI_SPARROW) + acceleration = 0.0f; + + brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetTimeStep(); + bool neutralHandling = !!(pHandling->Flags & HANDLING_NEUTRALHANDLING); + float brakeBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fBrakeBias; + float brakeBiasRear = neutralHandling ? 1.0f : 2.0f*(1.0f-pHandling->fBrakeBias); + float tractionBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fTractionBias; + float tractionBiasRear = neutralHandling ? 1.0f : 2.0f-tractionBiasFront; + + // Count how many wheels are touching the ground + + m_nWheelsOnGround = 0; + m_nDriveWheelsOnGroundPrev = m_nDriveWheelsOnGround; + m_nDriveWheelsOnGround = 0; + + for(i = 0; i < 4; i++){ + if(m_aSuspensionSpringRatio[i] < 1.0f) + m_aWheelTimer[i] = 4.0f; + else + m_aWheelTimer[i] = Max(m_aWheelTimer[i]-CTimer::GetTimeStep(), 0.0f); + + if(m_aWheelTimer[i] > 0.0f){ + m_nWheelsOnGround++; + switch(pHandling->Transmission.nDriveType){ + case '4': + m_nDriveWheelsOnGround++; + break; + case 'F': + if(i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT) + m_nDriveWheelsOnGround++; + break; + case 'R': + if(i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT) + m_nDriveWheelsOnGround++; + break; + } + } + } + + float traction; + if(GetStatus() == STATUS_PHYSICS) + traction = 0.004f * m_fTraction; + else + traction = 0.004f; + traction *= pHandling->fTractionMultiplier / 4.0f; + traction /= m_fForceMultiplier; + if(CVehicle::bCheat3) + traction *= 4.0f; + + if(FindPlayerVehicle() && FindPlayerVehicle() == this){ + if(CPad::GetPad(0)->CarGunJustDown()){ + if(m_bombType == CARBOMB_TIMED){ + m_bombType = CARBOMB_TIMEDACTIVE; + m_nBombTimer = 7000; + m_pBlowUpEntity = FindPlayerPed(); + CGarages::TriggerMessage("GA_12", -1, 3000, -1); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_BOMB_TIMED_ACTIVATED, 1.0f); + }else if(m_bombType == CARBOMB_ONIGNITION){ + m_bombType = CARBOMB_ONIGNITIONACTIVE; + CGarages::TriggerMessage("GA_12", -1, 3000, -1); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_BOMB_ONIGNITION_ACTIVATED, 1.0f); + } + } + }else if(strongGrip1 || CVehicle::bCheat3){ + traction *= 1.2f; + acceleration *= 1.4f; + if(strongGrip2 || CVehicle::bCheat3){ + traction *= 1.3f; + acceleration *= 1.4f; + } + } + + static float fThrust; + static tWheelState WheelState[4]; + + // Process front wheels on ground + + if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] > 0.0f || m_aWheelTimer[CARWHEEL_FRONT_RIGHT] > 0.0f){ + float s = Sin(m_fSteerAngle); + float c = Cos(m_fSteerAngle); + CVector wheelFwd = Multiply3x3(GetMatrix(), CVector(-s, c, 0.0f)); + CVector wheelRight = Multiply3x3(GetMatrix(), CVector(c, s, 0.0f)); + + if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] > 0.0f){ + if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_FRONT_LEFT])*traction; + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceB); + WheelState[CARWHEEL_FRONT_LEFT] = m_aWheelState[CARWHEEL_FRONT_LEFT]; + + if(Damage.GetWheelStatus(CARWHEEL_FRONT_LEFT) == WHEEL_STATUS_BURST) + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_FRONT_LEFT], contactPoints[CARWHEEL_FRONT_LEFT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasFront, + adhesion*tractionBiasFront*Damage.m_fWheelDamageEffect, + CARWHEEL_FRONT_LEFT, + &m_aWheelSpeed[CARWHEEL_FRONT_LEFT], + &WheelState[CARWHEEL_FRONT_LEFT], + WHEEL_STATUS_BURST); + else + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_FRONT_LEFT], contactPoints[CARWHEEL_FRONT_LEFT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasFront, + adhesion*tractionBiasFront, + CARWHEEL_FRONT_LEFT, + &m_aWheelSpeed[CARWHEEL_FRONT_LEFT], + &WheelState[CARWHEEL_FRONT_LEFT], + WHEEL_STATUS_OK); + } + + if(m_aWheelTimer[CARWHEEL_FRONT_RIGHT] > 0.0f){ + if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_FRONT_RIGHT])*traction; + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceB); + WheelState[CARWHEEL_FRONT_RIGHT] = m_aWheelState[CARWHEEL_FRONT_RIGHT]; + + if(Damage.GetWheelStatus(CARWHEEL_FRONT_RIGHT) == WHEEL_STATUS_BURST) + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_FRONT_RIGHT], contactPoints[CARWHEEL_FRONT_RIGHT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasFront, + adhesion*tractionBiasFront*Damage.m_fWheelDamageEffect, + CARWHEEL_FRONT_RIGHT, + &m_aWheelSpeed[CARWHEEL_FRONT_RIGHT], + &WheelState[CARWHEEL_FRONT_RIGHT], + WHEEL_STATUS_BURST); + else + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_FRONT_RIGHT], contactPoints[CARWHEEL_FRONT_RIGHT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasFront, + adhesion*tractionBiasFront, + CARWHEEL_FRONT_RIGHT, + &m_aWheelSpeed[CARWHEEL_FRONT_RIGHT], + &WheelState[CARWHEEL_FRONT_RIGHT], + WHEEL_STATUS_OK); + } + } + + // Process front wheels off ground + + if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] <= 0.0f){ + if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){ + if(acceleration > 0.0f){ + if(m_aWheelSpeed[CARWHEEL_FRONT_LEFT] < 2.0f) + m_aWheelSpeed[CARWHEEL_FRONT_LEFT] -= 0.2f; + }else{ + if(m_aWheelSpeed[CARWHEEL_FRONT_LEFT] > -2.0f) + m_aWheelSpeed[CARWHEEL_FRONT_LEFT] += 0.1f; + } + }else{ + m_aWheelSpeed[CARWHEEL_FRONT_LEFT] *= 0.95f; + } + m_aWheelRotation[CARWHEEL_FRONT_LEFT] += m_aWheelSpeed[CARWHEEL_FRONT_LEFT]; + } + if(m_aWheelTimer[CARWHEEL_FRONT_RIGHT] <= 0.0f){ + if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){ + if(acceleration > 0.0f){ + if(m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] < 2.0f) + m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] -= 0.2f; + }else{ + if(m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] > -2.0f) + m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] += 0.1f; + } + }else{ + m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] *= 0.95f; + } + m_aWheelRotation[CARWHEEL_FRONT_RIGHT] += m_aWheelSpeed[CARWHEEL_FRONT_RIGHT]; + } + + // Process rear wheels + + if(m_aWheelTimer[CARWHEEL_REAR_LEFT] > 0.0f || m_aWheelTimer[CARWHEEL_REAR_RIGHT] > 0.0f){ + CVector wheelFwd = GetForward(); + CVector wheelRight = GetRight(); + +#ifdef FIX_BUGS + // Not sure if this is needed, but brake usually has timestep as a factor + if(bIsHandbrakeOn) + brake = 20000.0f * CTimer::GetTimeStepFix(); +#else + if(bIsHandbrakeOn) + brake = 20000.0f; +#endif + + if(m_aWheelTimer[CARWHEEL_REAR_LEFT] > 0.0f){ + if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_REAR_LEFT])*traction; + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceB); + WheelState[CARWHEEL_REAR_LEFT] = m_aWheelState[CARWHEEL_REAR_LEFT]; + + if(Damage.GetWheelStatus(CARWHEEL_REAR_LEFT) == WHEEL_STATUS_BURST) + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_REAR_LEFT], contactPoints[CARWHEEL_REAR_LEFT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasRear, + adhesion*tractionBiasRear*Damage.m_fWheelDamageEffect, + CARWHEEL_REAR_LEFT, + &m_aWheelSpeed[CARWHEEL_REAR_LEFT], + &WheelState[CARWHEEL_REAR_LEFT], + WHEEL_STATUS_BURST); + else + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_REAR_LEFT], contactPoints[CARWHEEL_REAR_LEFT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasRear, + adhesion*tractionBiasRear, + CARWHEEL_REAR_LEFT, + &m_aWheelSpeed[CARWHEEL_REAR_LEFT], + &WheelState[CARWHEEL_REAR_LEFT], + WHEEL_STATUS_OK); + } + + if(m_aWheelTimer[CARWHEEL_REAR_RIGHT] > 0.0f){ + if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_REAR_RIGHT])*traction; + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceB); + WheelState[CARWHEEL_REAR_RIGHT] = m_aWheelState[CARWHEEL_REAR_RIGHT]; + + if(Damage.GetWheelStatus(CARWHEEL_REAR_RIGHT) == WHEEL_STATUS_BURST) + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_REAR_RIGHT], contactPoints[CARWHEEL_REAR_RIGHT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasRear, + adhesion*tractionBiasRear*Damage.m_fWheelDamageEffect, + CARWHEEL_REAR_RIGHT, + &m_aWheelSpeed[CARWHEEL_REAR_RIGHT], + &WheelState[CARWHEEL_REAR_RIGHT], + WHEEL_STATUS_BURST); + else + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_REAR_RIGHT], contactPoints[CARWHEEL_REAR_RIGHT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasRear, + adhesion*tractionBiasRear, + CARWHEEL_REAR_RIGHT, + &m_aWheelSpeed[CARWHEEL_REAR_RIGHT], + &WheelState[CARWHEEL_REAR_RIGHT], + WHEEL_STATUS_OK); + } + } + + // Process rear wheels off ground + + if(m_aWheelTimer[CARWHEEL_REAR_LEFT] <= 0.0f){ + if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){ + if(acceleration > 0.0f){ + if(m_aWheelSpeed[CARWHEEL_REAR_LEFT] < 2.0f) + m_aWheelSpeed[CARWHEEL_REAR_LEFT] -= 0.2f; + }else{ + if(m_aWheelSpeed[CARWHEEL_REAR_LEFT] > -2.0f) + m_aWheelSpeed[CARWHEEL_REAR_LEFT] += 0.1f; + } + }else{ + m_aWheelSpeed[CARWHEEL_REAR_LEFT] *= 0.95f; + } + m_aWheelRotation[CARWHEEL_REAR_LEFT] += m_aWheelSpeed[CARWHEEL_REAR_LEFT]; + } + if(m_aWheelTimer[CARWHEEL_REAR_RIGHT] <= 0.0f){ + if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){ + if(acceleration > 0.0f){ + if(m_aWheelSpeed[CARWHEEL_REAR_RIGHT] < 2.0f) + m_aWheelSpeed[CARWHEEL_REAR_RIGHT] -= 0.2f; + }else{ + if(m_aWheelSpeed[CARWHEEL_REAR_RIGHT] > -2.0f) + m_aWheelSpeed[CARWHEEL_REAR_RIGHT] += 0.1f; + } + }else{ + m_aWheelSpeed[CARWHEEL_REAR_RIGHT] *= 0.95f; + } + m_aWheelRotation[CARWHEEL_REAR_RIGHT] += m_aWheelSpeed[CARWHEEL_REAR_RIGHT]; + } + + for(i = 0; i < 4; i++){ + float wheelPos = colModel->lines[i].p0.z; + if(m_aSuspensionSpringRatio[i] > 0.0f) + wheelPos -= m_aSuspensionSpringRatio[i]*m_aSuspensionSpringLength[i]; + m_aWheelPosition[i] += (wheelPos - m_aWheelPosition[i])*0.75f; + } + for(i = 0; i < 4; i++) + m_aWheelState[i] = WheelState[i]; + + // Process horn + + if(GetStatus() != STATUS_PLAYER){ + ReduceHornCounter(); + }else{ + if(GetModelIndex() == MI_MRWHOOP){ + if(Pads[0].bHornHistory[Pads[0].iCurrHornHistory] && + !Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+CPad::HORNHISTORY_SIZE-1) % CPad::HORNHISTORY_SIZE]){ + m_bSirenOrAlarm = !m_bSirenOrAlarm; + printf("m_bSirenOrAlarm toggled to %d\n", m_bSirenOrAlarm); + } + }else if(UsesSiren(GetModelIndex())){ + if(Pads[0].bHornHistory[Pads[0].iCurrHornHistory]){ + if(Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+CPad::HORNHISTORY_SIZE-1) % CPad::HORNHISTORY_SIZE] && + Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+CPad::HORNHISTORY_SIZE-2) % CPad::HORNHISTORY_SIZE]) + m_nCarHornTimer = 1; + else + m_nCarHornTimer = 0; + }else if(Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+CPad::HORNHISTORY_SIZE-1) % CPad::HORNHISTORY_SIZE] && + !Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+1) % CPad::HORNHISTORY_SIZE]){ + m_nCarHornTimer = 0; + m_bSirenOrAlarm = !m_bSirenOrAlarm; + }else + m_nCarHornTimer = 0; + }else if(GetModelIndex() != MI_YARDIE && !CVehicle::bCheat3){ + if(Pads[0].GetHorn()) + m_nCarHornTimer = 1; + else + m_nCarHornTimer = 0; + } + } + + // Flying + + if(GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && GetStatus() != STATUS_PHYSICS){ + if(GetModelIndex() == MI_MIAMI_RCRAIDER || GetModelIndex() == MI_MIAMI_SPARROW) + m_aWheelSpeed[0] = Max(m_aWheelSpeed[0]-0.0005f, 0.0f); + }else if((GetModelIndex() == MI_DODO || CVehicle::bAllDodosCheat) && + m_vecMoveSpeed.Magnitude() > 0.0f && CTimer::GetTimeStep() > 0.0f){ +#ifdef ALT_DODO_CHEAT + if (bAltDodoCheat) + FlyingControl(FLIGHT_MODEL_SEAPLANE); + else +#endif + FlyingControl(FLIGHT_MODEL_DODO); + }else if(GetModelIndex() == MI_MIAMI_RCBARON){ + FlyingControl(FLIGHT_MODEL_RCPLANE); + }else if(GetModelIndex() == MI_MIAMI_RCRAIDER || GetModelIndex() == MI_MIAMI_SPARROW || bAllCarCheat){ +#ifdef ALLCARSHELI_CHEAT + if (bAllCarCheat) + FlyingControl(FLIGHT_MODEL_HELI); + else +#endif + { + if (CPad::GetPad(0)->GetCircleJustDown()) + m_aWheelSpeed[0] = Max(m_aWheelSpeed[0] - 0.03f, 0.0f); + if (m_aWheelSpeed[0] < 0.22f) + m_aWheelSpeed[0] += 0.0001f; + if (m_aWheelSpeed[0] > 0.15f) + FlyingControl(FLIGHT_MODEL_HELI); + } + } + } + + + + // Process car on fire + // A similar calculation of damagePos is done elsewhere for smoke + + uint8 engineStatus = Damage.GetEngineStatus(); + CVector damagePos = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->m_positions[CAR_POS_HEADLIGHTS]; + + switch(Damage.GetDoorStatus(DOOR_BONNET)){ + case DOOR_STATUS_OK: + case DOOR_STATUS_SMASHED: + // Bonnet is still there, smoke comes out at the edge + damagePos += vecDAMAGE_ENGINE_POS_SMALL; + break; + case DOOR_STATUS_SWINGING: + case DOOR_STATUS_MISSING: + // Bonnet is gone, smoke comes out at the engine + damagePos += vecDAMAGE_ENGINE_POS_BIG; + break; + } + + // move fire forward if in first person + if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson()) + if(m_fHealth < 250.0f && GetStatus() != STATUS_WRECKED){ + if(GetModelIndex() == MI_FIRETRUCK) + damagePos += CVector(0.0f, 3.0f, -0.2f); + else + damagePos += CVector(0.0f, 1.2f, -0.8f); + } + + damagePos = GetMatrix()*damagePos; + damagePos.z += 0.15f; + + if(m_fHealth < 250.0f && GetStatus() != STATUS_WRECKED){ + // Car is on fire + + CParticle::AddParticle(PARTICLE_CARFLAME, damagePos, + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.01125f, 0.09f)), + nil, 0.9f); + + CVector coors = damagePos; + coors.x += CGeneral::GetRandomNumberInRange(-0.5625f, 0.5625f), + coors.y += CGeneral::GetRandomNumberInRange(-0.5625f, 0.5625f), + coors.z += CGeneral::GetRandomNumberInRange(0.5625f, 2.25f); + CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, coors, CVector(0.0f, 0.0f, 0.0f)); + + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, CVector(0.0f, 0.0f, 0.0f), nil, 0.5f); + + // Blow up car after 5 seconds + m_fFireBlowUpTimer += CTimer::GetTimeStepInMilliseconds(); + if(m_fFireBlowUpTimer > 5000.0f){ + CWorld::Players[CWorld::PlayerInFocus].AwardMoneyForExplosion(this); + BlowUpCar(m_pSetOnFireEntity); + } + }else + m_fFireBlowUpTimer = 0.0f; + + // Decrease car health if engine is damaged badly + if(engineStatus > ENGINE_STATUS_ON_FIRE && m_fHealth > 250.0f) + m_fHealth -= 2.0f; + + ProcessDelayedExplosion(); + + + if(m_bSirenOrAlarm && (CTimer::GetFrameCounter()&7) == 5 && + UsesSiren(GetModelIndex()) && GetModelIndex() != MI_MRWHOOP) + CCarAI::MakeWayForCarWithSiren(this); + + + // Find out how much to shake the pad depending on suspension and ground surface + + float suspShake = 0.0f; + float surfShake = 0.0f; + for(i = 0; i < 4; i++){ + float suspChange = m_aSuspensionSpringRatioPrev[i] - m_aSuspensionSpringRatio[i]; + if(suspChange > 0.3f){ + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP, suspChange); + if(suspChange > suspShake) + suspShake = suspChange; + } + + uint8 surf = m_aWheelColPoints[i].surfaceB; + if(surf == SURFACE_GRAVEL || surf == SURFACE_WATER || surf == SURFACE_HEDGE){ + if(surfShake < 0.2f) + surfShake = 0.3f; + }else if(surf == SURFACE_MUD_DRY || surf == SURFACE_SAND){ + if(surfShake < 0.1f) + surfShake = 0.2f; + }else if(surf == SURFACE_GRASS){ + if(surfShake < 0.05f) + surfShake = 0.1f; + } + + m_aSuspensionSpringRatioPrev[i] = m_aSuspensionSpringRatio[i]; + m_aSuspensionSpringRatio[i] = 1.0f; + } + + // Shake pad + + if((suspShake > 0.0f || surfShake > 0.0f) && GetStatus() == STATUS_PLAYER){ + float speed = m_vecMoveSpeed.MagnitudeSqr(); + if(speed > sq(0.1f)){ + speed = Sqrt(speed); + if(suspShake > 0.0f){ + uint8 freq = Min(200.0f*suspShake*speed*2000.0f/m_fMass + 100.0f, 250.0f); + CPad::GetPad(0)->StartShake(20000.0f*CTimer::GetTimeStep()/freq, freq); + }else{ + uint8 freq = Min(200.0f*surfShake*speed*2000.0f/m_fMass + 40.0f, 150.0f); + CPad::GetPad(0)->StartShake(5000.0f*CTimer::GetTimeStep()/freq, freq); + } + } + } + + bVehicleColProcessed = false; + + if(!bWarnedPeds) + CCarCtrl::ScanForPedDanger(this); + + + // Turn around at the edge of the world + // TODO: make the numbers defines + + float heading; + if(GetPosition().x > 1900.0f){ + if(m_vecMoveSpeed.x > 0.0f) + m_vecMoveSpeed.x *= -1.0f; + heading = GetForward().Heading(); + if(heading > 0.0f) // going west + SetHeading(-heading); + }else if(GetPosition().x < -1900.0f){ + if(m_vecMoveSpeed.x < 0.0f) + m_vecMoveSpeed.x *= -1.0f; + heading = GetForward().Heading(); + if(heading < 0.0f) // going east + SetHeading(-heading); + } + if(GetPosition().y > 1900.0f){ + if(m_vecMoveSpeed.y > 0.0f) + m_vecMoveSpeed.y *= -1.0f; + heading = GetForward().Heading(); + if(heading < HALFPI && heading > 0.0f) + SetHeading(PI-heading); + else if(heading > -HALFPI && heading < 0.0f) + SetHeading(-PI-heading); + }else if(GetPosition().y < -1900.0f){ + if(m_vecMoveSpeed.y < 0.0f) + m_vecMoveSpeed.y *= -1.0f; + heading = GetForward().Heading(); + if(heading > HALFPI) + SetHeading(PI-heading); + else if(heading < -HALFPI) + SetHeading(-PI-heading); + } + + if(bInfiniteMass){ + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + }else if(!skipPhysics && + (m_fGasPedal == 0.0f && brake == 0.0f || GetStatus() == STATUS_WRECKED)){ + if(Abs(m_vecMoveSpeed.x) < 0.005f && + Abs(m_vecMoveSpeed.y) < 0.005f && + Abs(m_vecMoveSpeed.z) < 0.005f){ + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed.z = 0.0f; + } + } +} + +#pragma optimize("", on) + +void +CAutomobile::Teleport(CVector pos) +{ + CWorld::Remove(this); + + SetPosition(pos); + SetOrientation(0.0f, 0.0f, 0.0f); + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + + ResetSuspension(); + + CWorld::Add(this); +} + +void +CAutomobile::PreRender(void) +{ + int i, j, n; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + if(GetModelIndex() == MI_RCBANDIT){ + CVector pos = GetMatrix() * CVector(0.218f, -0.444f, 0.391f); + CAntennas::RegisterOne((uintptr)this, GetUp(), pos, 1.0f); + } + + float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward())*180.0f; + + + // Wheel particles + + if(GetModelIndex() == MI_DODO){ + ; // nothing + }else if(GetModelIndex() == MI_RCBANDIT){ + for(i = 0; i < 4; i++){ + // Game has same code three times here + switch(m_aWheelState[i]){ + case WHEEL_STATE_SPINNING: + case WHEEL_STATE_SKIDDING: + case WHEEL_STATE_FIXED: + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.05f), + CVector(0.0f, 0.0f, 0.0f), nil, 0.1f); + break; + default: break; + } + } + }else{ + if(GetStatus() == STATUS_SIMPLE){ + CMatrix mat; + CVector pos; + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB])); + pos = mat.GetPosition(); + pos.z = 1.5f*m_aWheelPosition[CARWHEEL_REAR_RIGHT]; + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point = GetMatrix() * pos; + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceB = SURFACE_DEFAULT; + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB])); + pos = mat.GetPosition(); + pos.z = 1.5f*m_aWheelPosition[CARWHEEL_REAR_LEFT]; + m_aWheelColPoints[CARWHEEL_REAR_LEFT].point = GetMatrix() * pos; + m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceB = SURFACE_DEFAULT; + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + pos = mat.GetPosition(); + pos.z = 1.5f*m_aWheelPosition[CARWHEEL_FRONT_RIGHT]; + m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].point = GetMatrix() * pos; + m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceB = SURFACE_DEFAULT; + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + pos = mat.GetPosition(); + pos.z = 1.5f*m_aWheelPosition[CARWHEEL_FRONT_LEFT]; + m_aWheelColPoints[CARWHEEL_FRONT_LEFT].point = GetMatrix() * pos; + m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceB = SURFACE_DEFAULT; + } + + int drawParticles = Abs(fwdSpeed) < 90.0f; + if(GetStatus() == STATUS_SIMPLE || GetStatus() == STATUS_PHYSICS || + GetStatus() == STATUS_PLAYER || GetStatus() == STATUS_PLAYER_PLAYBACKFROMBUFFER){ + bool rearSkidding = false; + if(m_aWheelState[CARWHEEL_REAR_LEFT] == WHEEL_STATE_SKIDDING || + m_aWheelState[CARWHEEL_REAR_RIGHT] == WHEEL_STATE_SKIDDING) + rearSkidding = true; + + for(i = 0; i < 4; i++){ + switch(m_aWheelState[i]){ + case WHEEL_STATE_SPINNING: + if(AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles)){ + CParticle::AddParticle(PARTICLE_BURNINGRUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.0f)); + + CParticle::AddParticle(PARTICLE_BURNINGRUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.05f)); + } + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.0f)); + + if(m_aWheelTimer[i] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[i], &m_aWheelSkidmarkBloody[i]); + break; + + case WHEEL_STATE_SKIDDING: + if(i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT || rearSkidding){ + // same as below + + AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.0f)); + + if(m_aWheelTimer[i] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[i], &m_aWheelSkidmarkBloody[i]); + } + break; + + case WHEEL_STATE_FIXED: + AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.0f)); + + if(m_aWheelTimer[i] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[i], &m_aWheelSkidmarkBloody[i]); + break; + + default: + if(Abs(fwdSpeed) > 5.0f) + AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles); + if(m_aWheelSkidmarkBloody[i] && m_aWheelTimer[i] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[i], &m_aWheelSkidmarkBloody[i]); + } + } + } + } + + if(m_aCarNodes[CAR_WHEEL_RM]){ + // assume middle wheels are two units before rear ones + CVector offset = GetForward()*2.0f; + + switch(m_aWheelState[CARWHEEL_REAR_LEFT]){ + // Game has same code three times here + case WHEEL_STATE_SPINNING: + case WHEEL_STATE_SKIDDING: + case WHEEL_STATE_FIXED: + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[CARWHEEL_REAR_LEFT].point + CVector(0.0f, 0.0f, 0.25f) + offset, + CVector(0.0f, 0.0f, 0.0f)); + + if(m_aWheelTimer[CARWHEEL_REAR_LEFT] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + 5, + m_aWheelColPoints[CARWHEEL_REAR_LEFT].point + offset, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[CARWHEEL_REAR_LEFT], &m_aWheelSkidmarkBloody[CARWHEEL_REAR_LEFT]); + break; + default: break; + } + + switch(m_aWheelState[CARWHEEL_REAR_RIGHT]){ + // Game has same code three times here + case WHEEL_STATE_SPINNING: + case WHEEL_STATE_SKIDDING: + case WHEEL_STATE_FIXED: + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point + CVector(0.0f, 0.0f, 0.25f) + offset, + CVector(0.0f, 0.0f, 0.0f)); + + if(m_aWheelTimer[CARWHEEL_REAR_RIGHT] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + 6, + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point + offset, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[CARWHEEL_REAR_RIGHT], &m_aWheelSkidmarkBloody[CARWHEEL_REAR_RIGHT]); + break; + default: break; + } + } + + + // Rain on roof + if(!CCullZones::CamNoRain() && !CCullZones::PlayerNoRain() && + Abs(fwdSpeed) < 20.0f && CWeather::Rain > 0.02f){ + CColModel *colModel = GetColModel(); + + for(i = 0; i < colModel->numTriangles; i++){ + CVector p1, p2, p3, c; + + colModel->GetTrianglePoint(p1, colModel->triangles[i].a); + p1 = GetMatrix() * p1; + colModel->GetTrianglePoint(p2, colModel->triangles[i].b); + p2 = GetMatrix() * p2; + colModel->GetTrianglePoint(p3, colModel->triangles[i].c); + p3 = GetMatrix() * p3; + c = (p1 + p2 + p3)/3.0f; + + n = 6.0f*CWeather::Rain; + for(j = 0; j <= n; j++) + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, + c + CVector(CGeneral::GetRandomNumberInRange(-0.4f, 0.4f), CGeneral::GetRandomNumberInRange(-0.4f, 0.4f), 0.0f), + CVector(0.0f, 0.0f, 0.0f), + nil, 0.0f, 0, 0, CGeneral::GetRandomNumber() & 1); + } + } + + AddDamagedVehicleParticles(); + + // Exhaust smoke + if(bEngineOn && fwdSpeed < 90.0f){ + CVector exhaustPos = mi->m_positions[CAR_POS_EXHAUST]; + CVector pos1, pos2, dir; + + if(exhaustPos != CVector(0.0f, 0.0f, 0.0f)){ + dir.z = 0.0f; + if(fwdSpeed < 10.0f){ + CVector steerFwd(-Sin(m_fSteerAngle), Cos(m_fSteerAngle), 0.0f); + steerFwd = Multiply3x3(GetMatrix(), steerFwd); + float r = CGeneral::GetRandomNumberInRange(-0.06f, -0.03f); + dir.x = steerFwd.x * r; + dir.y = steerFwd.y * r; + }else{ + dir.x = m_vecMoveSpeed.x; + dir.y = m_vecMoveSpeed.y; + } + + bool dblExhaust = false; + pos1 = GetMatrix() * exhaustPos; + if(pHandling->Flags & HANDLING_DBL_EXHAUST){ + dblExhaust = true; + pos2 = exhaustPos; + pos2.x = -pos2.x; + pos2 = GetMatrix() * pos2; + } + + n = 4.0f*m_fGasPedal; + if(dblExhaust) + for(i = 0; i <= n; i++){ + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos1, dir); + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos2, dir); + } + else + for(i = 0; i <= n; i++) + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos1, dir); + } + } + + + // Siren and taxi lights + switch(GetModelIndex()){ + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_POLICE: + case MI_ENFORCER: + if(m_bSirenOrAlarm){ + CVector pos1, pos2; + uint8 r1, g1, b1; + uint8 r2, g2, b2; + uint8 r, g, b; + + switch(GetModelIndex()){ + case MI_FIRETRUCK: + pos1 = CVector(1.1f, 1.7f, 2.0f); + pos2 = CVector(-1.1f, 1.7f, 2.0f); + r1 = 255; g1 = 0; b1 = 0; + r2 = 255; g2 = 255; b2 = 0; + break; + case MI_AMBULAN: + pos1 = CVector(1.1f, 0.9f, 1.6f); + pos2 = CVector(-1.1f, 0.9f, 1.6f); + r1 = 255; g1 = 0; b1 = 0; + r2 = 255; g2 = 255; b2 = 255; + break; + case MI_POLICE: + pos1 = CVector(0.7f, -0.4f, 1.0f); + pos2 = CVector(-0.7f, -0.4f, 1.0f); + r1 = 255; g1 = 0; b1 = 0; + r2 = 0; g2 = 0; b2 = 255; + break; + case MI_ENFORCER: + pos1 = CVector(1.1f, 0.8f, 1.2f); + pos2 = CVector(-1.1f, 0.8f, 1.2f); + r1 = 255; g1 = 0; b1 = 0; + r2 = 0; g2 = 0; b2 = 255; + break; + } + + uint32 t = CTimer::GetTimeInMilliseconds() & 0x3FF; // 1023 + if(t < 512){ + r = r1/6; + g = g1/6; + b = b1/6; + }else{ + r = r2/6; + g = g2/6; + b = b2/6; + } + + t = CTimer::GetTimeInMilliseconds() & 0x1FF; // 511 + if(t < 100){ + float f = t/100.0f; + r *= f; + g *= f; + b *= f; + }else if(t > (512-100)){ + float f = (512-t)/100.0f; + r *= f; + g *= f; + b *= f; + } + + CVector pos = GetPosition(); + float angle = (CTimer::GetTimeInMilliseconds() & 0x3FF)*TWOPI/0x3FF; + float s = 8.0f*Sin(angle); + float c = 8.0f*Cos(angle); + CShadows::StoreCarLightShadow(this, (uintptr)this + 21, gpShadowHeadLightsTex, + &pos, c, s, s, -c, r, g, b, 8.0f); + + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos + GetUp()*2.0f, CVector(0.0f, 0.0f, 0.0f), 12.0f, + r*0.02f, g*0.02f, b*0.02f, CPointLights::FOG_NONE, true); + + pos1 = GetMatrix() * pos1; + pos2 = GetMatrix() * pos2; + + for(i = 0; i < 4; i++){ + uint8 sirenTimer = ((CTimer::GetTimeInMilliseconds() + (i<<6))>>8) & 3; + pos = (pos1*i + pos2*(3.0f-i))/3.0f; + + switch(sirenTimer){ + case 0: + CCoronas::RegisterCorona((uintptr)this + 21 + i, + r1, g1, b1, 255, + pos, 0.4f, 50.0f, + CCoronas::TYPE_STAR, + i == 1 ? CCoronas::FLARE_HEADLIGHTS : CCoronas::FLARE_NONE, + CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + break; + case 2: + CCoronas::RegisterCorona((uintptr)this + 21 + i, + r2, g2, b2, 255, + pos, 0.4f, 50.0f, + CCoronas::TYPE_STAR, + CCoronas::FLARE_NONE, + CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + break; + default: + CCoronas::UpdateCoronaCoors((uintptr)this + 21 + i, pos, 50.0f, 0.0f); + break; + } + } + } + break; + + case MI_FBICAR: + if(m_bSirenOrAlarm){ + CVector pos = GetMatrix() * CVector(0.4f, 0.6f, 0.3f); + if(CTimer::GetTimeInMilliseconds() & 0x100 && + DotProduct(GetForward(), GetPosition() - TheCamera.GetPosition()) < 0.0f) + CCoronas::RegisterCorona((uintptr)this + 21, + 0, 0, 255, 255, + pos, 0.4f, 50.0f, + CCoronas::TYPE_STAR, + CCoronas::FLARE_NONE, + CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::UpdateCoronaCoors((uintptr)this + 21, pos, 50.0f, 0.0f); + } + break; + + case MI_TAXI: + case MI_CABBIE: + case MI_BORGNINE: + if(bTaxiLight){ + CVector pos = GetPosition() + GetUp()*0.95f; + CCoronas::RegisterCorona((uintptr)this + 21, + 128, 128, 0, 255, + pos, 0.8f, 50.0f, + CCoronas::TYPE_NORMAL, + CCoronas::FLARE_NONE, + CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos, CVector(0.0f, 0.0f, 0.0f), 10.0f, + 1.0f, 1.0f, 0.5f, CPointLights::FOG_NONE, true); + } + break; + } + + if(GetModelIndex() != MI_RCBANDIT && GetModelIndex() != MI_DODO && + GetModelIndex() != MI_RHINO) { + // Process lights + + // Turn lights on/off + bool shouldLightsBeOn = + CClock::GetHours() > 20 || + CClock::GetHours() > 19 && CClock::GetMinutes() > (m_randomSeed & 0x3F) || + CClock::GetHours() < 7 || + CClock::GetHours() < 8 && CClock::GetMinutes() < (m_randomSeed & 0x3F) || + m_randomSeed/50000.0f < CWeather::Foggyness || + m_randomSeed/50000.0f < CWeather::WetRoads; + if(shouldLightsBeOn != bLightsOn && GetStatus() != STATUS_WRECKED){ + if(GetStatus() == STATUS_ABANDONED){ + // Turn off lights on abandoned vehicles only when we they're far away + if(bLightsOn && + Abs(TheCamera.GetPosition().x - GetPosition().x) + Abs(TheCamera.GetPosition().y - GetPosition().y) > 100.0f) + bLightsOn = false; + }else + bLightsOn = shouldLightsBeOn; + } + + // Actually render the lights + bool alarmOn = false; + bool alarmOff = false; + if(IsAlarmOn()){ + if(CTimer::GetTimeInMilliseconds() & 0x100) + alarmOn = true; + else + alarmOff = true; + } + if(bEngineOn && bLightsOn || alarmOn || alarmOff){ + CVector lookVector = GetPosition() - TheCamera.GetPosition(); + float camDist = lookVector.Magnitude(); + if(camDist != 0.0f) + lookVector *= 1.0f/camDist; + else + lookVector = CVector(1.0f, 0.0f, 0.0f); + + // 1.0 if directly behind car, -1.0 if in front + // BUG on PC: Abs of DotProduct is taken + float behindness = DotProduct(lookVector, GetForward()); + behindness = Clamp(behindness, -1.0f, 1.0f); // shouldn't be necessary + // 0.0 if behind car, PI if in front + // Abs not necessary + float angle = Abs(Acos(behindness)); + + // Headlights + + CVector headLightPos = mi->m_positions[CAR_POS_HEADLIGHTS]; + CVector lightR = GetMatrix() * headLightPos; + CVector lightL = lightR; + lightL -= GetRight()*2.0f*headLightPos.x; + + // Headlight coronas + if(behindness < 0.0f){ + // In front of car + float intensity = -0.5f*behindness + 0.3f; + float size = 1.0f - behindness; + + if(behindness < -0.97f && camDist < 30.0f){ + // Directly in front and not too far away + if(pHandling->Flags & HANDLING_HALOGEN_LIGHTS){ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 6, 150, 150, 195, 255, + lightL, 1.2f, 45.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 7, 150, 150, 195, 255, + lightR, 1.2f, 45.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle); + }else{ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 6, 160, 160, 140, 255, + lightL, 1.2f, 45.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 7, 160, 160, 140, 255, + lightR, 1.2f, 45.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle); + } + } + + if(alarmOff){ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this, 0, 0, 0, 0, + lightL, size, 0.0f, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 1, 0, 0, 0, 0, + lightR, size, 0.0f, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + }else{ + if(pHandling->Flags & HANDLING_HALOGEN_LIGHTS){ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this, 190*intensity, 190*intensity, 255*intensity, 255, + lightL, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 1, 190*intensity, 190*intensity, 255*intensity, 255, + lightR, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + }else{ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this, 210*intensity, 210*intensity, 195*intensity, 255, + lightL, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 1, 210*intensity, 210*intensity, 195*intensity, 255, + lightR, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + } + } + }else{ + // Behind car + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this, lightL, 50.0f*TheCamera.LODDistMultiplier, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 1, lightR, 50.0f*TheCamera.LODDistMultiplier, angle); + } + + // bright lights + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK && !bNoBrightHeadLights) + CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + BRIGHTLIGHT_FRONT); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK && !bNoBrightHeadLights) + CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + BRIGHTLIGHT_FRONT); + + // Taillights + + CVector tailLightPos = mi->m_positions[CAR_POS_TAILLIGHTS]; + lightR = GetMatrix() * tailLightPos; + lightL = lightR; + lightL -= GetRight()*2.0f*tailLightPos.x; + + // Taillight coronas + if(behindness > 0.0f){ + // Behind car + float intensity = (behindness + 1.0f)*0.4f; + float size = (behindness + 1.0f)*0.5f; + + if(m_fGasPedal < 0.0f){ + // reversing + intensity += 0.4f; + size += 0.3f; + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 14, 128*intensity, 128*intensity, 128*intensity, 255, + lightL, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 15, 128*intensity, 128*intensity, 128*intensity, 255, + lightR, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + }else{ + if(m_fBrakePedal > 0.0f){ + intensity += 0.4f; + size += 0.3f; + } + + if(alarmOff){ + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 14, 0, 0, 0, 0, + lightL, size, 0.0f, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 15, 0, 0, 0, 0, + lightR, size, 0.0f, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + }else{ + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 14, 128*intensity, 0, 0, 255, + lightL, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 15, 128*intensity, 0, 0, 255, + lightR, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + } + } + }else{ + // In front of car + // missing LODDistMultiplier probably a BUG + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 14, lightL, 50.0f, angle); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 15, lightR, 50.0f, angle); + } + + // bright lights + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR); + + // Light shadows + if(!alarmOff){ + CVector pos = GetPosition(); + CVector2D fwd(GetForward()); + fwd.Normalise(); + float f = headLightPos.y + 6.0f; + pos += CVector(f*fwd.x, f*fwd.y, 2.0f); + + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK || + Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CShadows::StoreCarLightShadow(this, (uintptr)this + 22, gpShadowHeadLightsTex, &pos, + 7.0f*fwd.x, 7.0f*fwd.y, 7.0f*fwd.y, -7.0f*fwd.x, 45, 45, 45, 7.0f); + + f = (tailLightPos.y - 2.5f) - (headLightPos.y + 6.0f); + pos += CVector(f*fwd.x, f*fwd.y, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK || + Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CShadows::StoreCarLightShadow(this, (uintptr)this + 25, gpShadowExplosionTex, &pos, + 3.0f, 0.0f, 0.0f, -3.0f, 35, 0, 0, 4.0f); + } + + if(this == FindPlayerVehicle() && !alarmOff){ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK || + Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CPointLights::AddLight(CPointLights::LIGHT_DIRECTIONAL, GetPosition(), GetForward(), + 20.0f, 1.0f, 1.0f, 1.0f, + FindPlayerVehicle()->m_vecMoveSpeed.MagnitudeSqr2D() < sq(0.45f) ? CPointLights::FOG_NORMAL : CPointLights::FOG_NONE, + false); + CVector pos = GetPosition() - 4.0f*GetForward(); + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK || + Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) { + if(m_fBrakePedal > 0.0f) + CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f), + 10.0f, 1.0f, 0.0f, 0.0f, + CPointLights::FOG_NONE, false); + else + CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f), + 7.0f, 0.6f, 0.0f, 0.0f, + CPointLights::FOG_NONE, false); + } + } + }else if(GetStatus() != STATUS_ABANDONED && GetStatus() != STATUS_WRECKED){ + // Lights off + + CVector lightPos = mi->m_positions[CAR_POS_TAILLIGHTS]; + CVector lightR = GetMatrix() * lightPos; + CVector lightL = lightR; + lightL -= GetRight()*2.0f*lightPos.x; + + if(m_fBrakePedal > 0.0f || m_fGasPedal < 0.0f){ + CVector lookVector = GetPosition() - TheCamera.GetPosition(); + lookVector.Normalise(); + float behindness = DotProduct(lookVector, GetForward()); + if(behindness > 0.0f){ + if(m_fGasPedal < 0.0f){ + // reversing + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 14, 120, 120, 120, 255, + lightL, 1.2f, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 15, 120, 120, 120, 255, + lightR, 1.2f, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_FRONT); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_FRONT); + }else{ + // braking + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 14, 120, 0, 0, 255, + lightL, 1.2f, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 15, 120, 0, 0, 255, + lightR, 1.2f, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR); + } + }else{ + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 14, lightL, 50.0f*TheCamera.LODDistMultiplier, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 15, lightR, 50.0f*TheCamera.LODDistMultiplier, 0.0f); + } + }else{ + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 14, lightL, 50.0f*TheCamera.LODDistMultiplier, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 15, lightR, 50.0f*TheCamera.LODDistMultiplier, 0.0f); + } + } + // end of lights + } + + CShadows::StoreShadowForCar(this); +} + +void +CAutomobile::Render(void) +{ + int i; + CMatrix mat; + CVector pos; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + if(GetModelIndex() == MI_RHINO && m_aCarNodes[CAR_BONNET]){ + // Rotate Rhino turret + CMatrix m; + CVector p; + m.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BONNET])); + p = m.GetPosition(); + m.SetRotateZ(m_fCarGunLR); + m.Translate(p); + m.UpdateRW(); + } + + CVector contactPoints[4]; // relative to model + CVector contactSpeeds[4]; // speed at contact points + CVector frontWheelFwd = Multiply3x3(GetMatrix(), CVector(-Sin(m_fSteerAngle), Cos(m_fSteerAngle), 0.0f)); + CVector rearWheelFwd = GetForward(); + for(i = 0; i < 4; i++){ + if (m_aWheelTimer[i] > 0.0f) { + contactPoints[i] = m_aWheelColPoints[i].point - GetPosition(); + contactSpeeds[i] = GetSpeed(contactPoints[i]); + if (i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT) + m_aWheelSpeed[i] = ProcessWheelRotation(m_aWheelState[i], frontWheelFwd, contactSpeeds[i], 0.5f*mi->m_wheelScale); + else + m_aWheelSpeed[i] = ProcessWheelRotation(m_aWheelState[i], rearWheelFwd, contactSpeeds[i], 0.5f*mi->m_wheelScale); + m_aWheelRotation[i] += m_aWheelSpeed[i]; + } + } + + // Rear right wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_REAR_RIGHT]; + if(Damage.GetWheelStatus(CARWHEEL_REAR_RIGHT) == WHEEL_STATUS_BURST) + mat.SetRotate(m_aWheelRotation[CARWHEEL_REAR_RIGHT], 0.0f, 0.3f*Sin(m_aWheelRotation[CARWHEEL_REAR_RIGHT])); + else + mat.SetRotateX(m_aWheelRotation[CARWHEEL_REAR_RIGHT]); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB])); + + // Rear left wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_REAR_LEFT]; + if(Damage.GetWheelStatus(CARWHEEL_REAR_LEFT) == WHEEL_STATUS_BURST) + mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI+0.3f*Sin(-m_aWheelRotation[CARWHEEL_REAR_LEFT])); + else + mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB])); + + // Mid right wheel + if(m_aCarNodes[CAR_WHEEL_RM]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RM])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_REAR_RIGHT]; + if(Damage.GetWheelStatus(CARWHEEL_REAR_RIGHT) == WHEEL_STATUS_BURST) + mat.SetRotate(m_aWheelRotation[CARWHEEL_REAR_RIGHT], 0.0f, 0.3f*Sin(m_aWheelRotation[CARWHEEL_REAR_RIGHT])); + else + mat.SetRotateX(m_aWheelRotation[CARWHEEL_REAR_RIGHT]); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RM])); + } + + // Mid left wheel + if(m_aCarNodes[CAR_WHEEL_LM]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LM])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_REAR_LEFT]; + if(Damage.GetWheelStatus(CARWHEEL_REAR_LEFT) == WHEEL_STATUS_BURST) + mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI+0.3f*Sin(-m_aWheelRotation[CARWHEEL_REAR_LEFT])); + else + mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LM])); + } + + if(GetModelIndex() == MI_DODO){ + // Front wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_FRONT_RIGHT]; + if(Damage.GetWheelStatus(CARWHEEL_FRONT_RIGHT) == WHEEL_STATUS_BURST) + mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle+0.3f*Sin(m_aWheelRotation[CARWHEEL_FRONT_RIGHT])); + else + mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF])); + + // Rotate propeller + if(m_aCarNodes[CAR_WINDSCREEN]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WINDSCREEN])); + pos = mat.GetPosition(); + mat.SetRotateY(m_fPropellerRotation); + mat.Translate(pos); + mat.UpdateRW(); + + m_fPropellerRotation += m_fGasPedal != 0.0f ? TWOPI/13.0f : TWOPI/26.0f; + if(m_fPropellerRotation > TWOPI) + m_fPropellerRotation -= TWOPI; + } + + // Rudder + if(Damage.GetDoorStatus(DOOR_BOOT) != DOOR_STATUS_MISSING && m_aCarNodes[CAR_BOOT]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BOOT])); + pos = mat.GetPosition(); + mat.SetRotate(0.0f, 0.0f, -m_fSteerAngle); + mat.Rotate(0.0f, Sin(m_fSteerAngle)*DEGTORAD(22.0f), 0.0f); + mat.Translate(pos); + mat.UpdateRW(); + } + + ProcessSwingingDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT); + ProcessSwingingDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + }else if(GetModelIndex() == MI_RHINO){ + // Front right wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_FRONT_RIGHT]; + // no damaged wheels or steering + mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, 0.0f); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF])); + + // Front left wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_FRONT_LEFT]; + // no damaged wheels or steering + mat.SetRotate(-m_aWheelRotation[CARWHEEL_FRONT_LEFT], 0.0f, PI); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF])); + }else{ + // Front right wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_FRONT_RIGHT]; + if(Damage.GetWheelStatus(CARWHEEL_FRONT_RIGHT) == WHEEL_STATUS_BURST) + mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle+0.3f*Sin(m_aWheelRotation[CARWHEEL_FRONT_RIGHT])); + else + mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF])); + + // Front left wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_FRONT_LEFT]; + if(Damage.GetWheelStatus(CARWHEEL_FRONT_LEFT) == WHEEL_STATUS_BURST) + mat.SetRotate(-m_aWheelRotation[CARWHEEL_FRONT_LEFT], 0.0f, PI+m_fSteerAngle+0.3f*Sin(-m_aWheelRotation[CARWHEEL_FRONT_LEFT])); + else + mat.SetRotate(-m_aWheelRotation[CARWHEEL_FRONT_LEFT], 0.0f, PI+m_fSteerAngle); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF])); + + ProcessSwingingDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT); + ProcessSwingingDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + ProcessSwingingDoor(CAR_DOOR_LR, DOOR_REAR_LEFT); + ProcessSwingingDoor(CAR_DOOR_RR, DOOR_REAR_RIGHT); + ProcessSwingingDoor(CAR_BONNET, DOOR_BONNET); + ProcessSwingingDoor(CAR_BOOT, DOOR_BOOT); + + mi->SetVehicleColour(m_currentColour1, m_currentColour2); + } + + if(!CVehicle::bWheelsOnlyCheat) + CEntity::Render(); +} + +int32 +CAutomobile::ProcessEntityCollision(CEntity *ent, CColPoint *colpoints) +{ + int i; + CColModel *colModel; + + if(GetStatus() != STATUS_SIMPLE) + bVehicleColProcessed = true; + + if(bUsingSpecialColModel) + colModel = &CWorld::Players[CWorld::PlayerInFocus].m_ColModel; + else + colModel = GetColModel(); + + int numWheelCollisions = 0; + float prevRatios[4] = { 0.0f, 0.0f, 0.0f, 0.0f}; + for(i = 0; i < 4; i++) + prevRatios[i] = m_aSuspensionSpringRatio[i]; + + int numCollisions = CCollision::ProcessColModels(GetMatrix(), *colModel, + ent->GetMatrix(), *ent->GetColModel(), + colpoints, + m_aWheelColPoints, m_aSuspensionSpringRatio); + + // m_aSuspensionSpringRatio are now set to the point where the tyre touches ground. + // In ProcessControl these will be re-normalized to ignore the tyre radius. + + if(m_bIsVehicleBeingShifted || bSkipLineCol || + GetModelIndex() == MI_DODO && (ent->IsPed() || ent->IsVehicle())){ + // don't do line collision + for(i = 0; i < 4; i++) + m_aSuspensionSpringRatio[i] = prevRatios[i]; + }else{ + for(i = 0; i < 4; i++) + if(m_aSuspensionSpringRatio[i] < 1.0f && m_aSuspensionSpringRatio[i] < prevRatios[i]){ + numWheelCollisions++; + + // wheel is touching a physical + if(ent->IsVehicle() || ent->IsObject()){ + CPhysical *phys = (CPhysical*)ent; + + m_aGroundPhysical[i] = phys; + phys->RegisterReference((CEntity**)&m_aGroundPhysical[i]); + m_aGroundOffset[i] = m_aWheelColPoints[i].point - phys->GetPosition(); + + if(phys->GetModelIndex() == MI_BODYCAST && GetStatus() == STATUS_PLAYER){ + // damage body cast + float speed = m_vecMoveSpeed.MagnitudeSqr(); + if(speed > 0.1f){ + CObject::nBodyCastHealth -= 0.1f*m_fMass*speed; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_PED_BODYCAST_HIT, 0.0f); + } + + // move body cast + if(phys->GetIsStatic()){ + phys->SetIsStatic(false); + phys->m_nStaticFrames = 0; + phys->ApplyMoveForce(m_vecMoveSpeed / Sqrt(speed)); + phys->AddToMovingList(); + } + } + } + + m_nSurfaceTouched = m_aWheelColPoints[i].surfaceB; + if(ent->IsBuilding()) + m_pCurGroundEntity = ent; + } + } + + if(numCollisions > 0 || numWheelCollisions > 0){ + AddCollisionRecord(ent); + if(!ent->IsBuilding()) + ((CPhysical*)ent)->AddCollisionRecord(this); + + if(numCollisions > 0) + if(ent->IsBuilding() || + ent->IsObject() && ((CPhysical*)ent)->bInfiniteMass) + bHasHitWall = true; + } + + return numCollisions; +} + +static int16 nLastControlInput; +static float fMouseCentreRange = 0.35f; +static float fMouseSteerSens = -0.0035f; +static float fMouseCentreMult = 0.975f; + +void +CAutomobile::ProcessControlInputs(uint8 pad) +{ + float speed = DotProduct(m_vecMoveSpeed, GetForward()); + + if(CPad::GetPad(pad)->GetExitVehicle()) + bIsHandbrakeOn = true; + else + bIsHandbrakeOn = !!CPad::GetPad(pad)->GetHandBrake(); + + // Steer left/right + if(CCamera::m_bUseMouse3rdPerson && !CVehicle::m_bDisableMouseSteering){ + if(CPad::GetPad(pad)->GetMouseX() != 0.0f){ + m_fSteerInput += fMouseSteerSens*CPad::GetPad(pad)->GetMouseX(); + nLastControlInput = 2; + if(Abs(m_fSteerInput) < fMouseCentreRange) + m_fSteerInput *= Pow(fMouseCentreMult, CTimer::GetTimeStep()); + }else if(CPad::GetPad(pad)->GetSteeringLeftRight() || nLastControlInput != 2){ + // mouse hasn't move, steer with pad like below + m_fSteerInput += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteerInput)* + 0.2f*CTimer::GetTimeStep(); + nLastControlInput = 0; + } + }else{ + m_fSteerInput += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteerInput)* + 0.2f*CTimer::GetTimeStep(); + nLastControlInput = 0; + } + m_fSteerInput = Clamp(m_fSteerInput, -1.0f, 1.0f); + + // Accelerate/Brake + float acceleration = (CPad::GetPad(pad)->GetAccelerate() - CPad::GetPad(pad)->GetBrake())/255.0f; + if(GetModelIndex() == MI_DODO && acceleration < 0.0f) + acceleration *= 0.3f; + if(Abs(speed) < 0.01f){ + // standing still, go into direction we want + m_fGasPedal = acceleration; + m_fBrakePedal = 0.0f; + }else{ +#if 1 + // simpler than the code below + if(speed * acceleration < 0.0f){ + // if opposite directions, have to brake first + m_fGasPedal = 0.0f; + m_fBrakePedal = Abs(acceleration); + }else{ + // accelerating in same direction we were already going + m_fGasPedal = acceleration; + m_fBrakePedal = 0.0f; + } +#else + if(speed < 0.0f){ + // moving backwards currently + if(acceleration < 0.0f){ + // still go backwards + m_fGasPedal = acceleration; + m_fBrakePedal = 0.0f; + }else{ + // want to go forwards, so brake + m_fGasPedal = 0.0f; + m_fBrakePedal = acceleration; + } + }else{ + // moving forwards currently + if(acceleration < 0.0f){ + // want to go backwards, so brake + m_fGasPedal = 0.0f; + m_fBrakePedal = -acceleration; + }else{ + // still go forwards + m_fGasPedal = acceleration; + m_fBrakePedal = 0.0f; + } + } +#endif + } + + // Actually turn wheels + static float fValue; // why static? + if(m_fSteerInput < 0.0f) + fValue = -sq(m_fSteerInput); + else + fValue = sq(m_fSteerInput); + m_fSteerAngle = DEGTORAD(pHandling->fSteeringLock) * fValue; + + if(bComedyControls){ +#if 0 // old comedy controls from PS2 - same as bike's + if(((CTimer::GetTimeInMilliseconds() >> 10) & 0xF) < 12) + m_fGasPedal = 1.0f; + if((((CTimer::GetTimeInMilliseconds() >> 10)+6) & 0xF) < 12) + m_fBrakePedal = 0.0f; + bIsHandbrakeOn = false; + if(CTimer::GetTimeInMilliseconds() & 0x800) + m_fSteerAngle += 0.08f; + else + m_fSteerAngle -= 0.03f; +#else + int rnd = CGeneral::GetRandomNumber() % 10; + switch(m_comedyControlState){ + case 0: + if(rnd < 2) + m_comedyControlState = 1; + else if(rnd < 4) + m_comedyControlState = 2; + break; + case 1: + m_fSteerAngle += 0.05f; + if(rnd < 2) + m_comedyControlState = 0; + break; + case 2: + m_fSteerAngle -= 0.05f; + if(rnd < 2) + m_comedyControlState = 0; + break; + } + }else{ + m_comedyControlState = 0; +#endif + } + + // Brake if player isn't in control + // BUG: game always uses pad 0 here + if(CPad::GetPad(pad)->ArePlayerControlsDisabled()){ + m_fBrakePedal = 1.0f; + bIsHandbrakeOn = true; + m_fGasPedal = 0.0f; + + FindPlayerPed()->KeepAreaAroundPlayerClear(); + + // slow down car immediately + speed = m_vecMoveSpeed.Magnitude(); + if(speed > 0.28f) + m_vecMoveSpeed *= 0.28f/speed; + } +} + +void +CAutomobile::FireTruckControl(void) +{ + if(this == FindPlayerVehicle()){ +#ifdef FIX_BUGS + if (!CPad::GetPad(0)->GetCarGunFired()) +#else + if (!CPad::GetPad(0)->GetWeapon()) +#endif // FIX_BUGS + return; +#ifdef FREE_CAM + if (!CCamera::bFreeCam) +#endif + { + m_fCarGunLR += CPad::GetPad(0)->GetCarGunLeftRight() * 0.00025f * CTimer::GetTimeStep(); + m_fCarGunUD += CPad::GetPad(0)->GetCarGunUpDown() * 0.0001f * CTimer::GetTimeStep(); + } + m_fCarGunUD = Clamp(m_fCarGunUD, 0.05f, 0.3f); + + + CVector cannonPos(0.0f, 1.5f, 1.9f); + cannonPos = GetMatrix() * cannonPos; + CVector cannonDir( + Sin(m_fCarGunLR) * Cos(m_fCarGunUD), + Cos(m_fCarGunLR) * Cos(m_fCarGunUD), + Sin(m_fCarGunUD)); + cannonDir = Multiply3x3(GetMatrix(), cannonDir); + cannonDir.z += (CGeneral::GetRandomNumber()&0xF)/1000.0f; + CWaterCannons::UpdateOne((uintptr)this, &cannonPos, &cannonDir); + }else if(GetStatus() == STATUS_PHYSICS){ + CFire *fire = gFireManager.FindFurthestFire_NeverMindFireMen(GetPosition(), 10.0f, 35.0f); + if(fire == nil) + return; + + // Target cannon onto fire + float targetAngle = CGeneral::GetATanOfXY(fire->m_vecPos.x-GetPosition().x, fire->m_vecPos.y-GetPosition().y); + float fwdAngle = CGeneral::GetATanOfXY(GetForward().x, GetForward().y); + float targetCannonAngle = fwdAngle - targetAngle; + float angleDelta = CTimer::GetTimeStep()*0.01f; + float cannonDelta = targetCannonAngle - m_fCarGunLR; + while(cannonDelta < PI) cannonDelta += TWOPI; + while(cannonDelta > PI) cannonDelta -= TWOPI; + if(Abs(cannonDelta) < angleDelta) + m_fCarGunLR = targetCannonAngle; + else if(cannonDelta > 0.0f) + m_fCarGunLR += angleDelta; + else + m_fCarGunLR -= angleDelta; + + // Go up and down a bit + float upDown = Sin((float)(CTimer::GetTimeInMilliseconds() & 0xFFF)/0x1000 * TWOPI); + m_fCarGunUD = 0.2f + 0.2f*upDown; + + // Spray water every once in a while + if((CTimer::GetTimeInMilliseconds()>>10) & 3){ + CVector cannonPos(0.0f, 0.0f, 2.2f); // different position than player's firetruck! + cannonPos = GetMatrix() * cannonPos; + CVector cannonDir( + Sin(m_fCarGunLR) * Cos(m_fCarGunUD), + Cos(m_fCarGunLR) * Cos(m_fCarGunUD), + Sin(m_fCarGunUD)); + cannonDir = Multiply3x3(GetMatrix(), cannonDir); + cannonDir.z += (CGeneral::GetRandomNumber()&0xF)/1000.0f; + CWaterCannons::UpdateOne((uintptr)this, &cannonPos, &cannonDir); + } + } +} + +void +CAutomobile::TankControl(void) +{ + int i; + + // These coords are 1 unit higher then they should be relative to model center + CVector turrentBase(0.0f, -1.394f, 2.296f); + CVector gunEnd(0.0f, 1.813f, 2.979f); + CVector baseToEnd = gunEnd - turrentBase; + + if(this != FindPlayerVehicle()) + return; + if(CWorld::Players[CWorld::PlayerInFocus].m_WBState != WBSTATE_PLAYING) + return; + + // Rotate turret + float prevAngle = m_fCarGunLR; +#ifdef FREE_CAM + if(!CCamera::bFreeCam) +#endif + m_fCarGunLR -= CPad::GetPad(0)->GetCarGunLeftRight() * 0.00015f * CTimer::GetTimeStep(); + + if(m_fCarGunLR < 0.0f) + m_fCarGunLR += TWOPI; + if(m_fCarGunLR > TWOPI) + m_fCarGunLR -= TWOPI; + if(m_fCarGunLR != prevAngle) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_TANK_TURRET_ROTATE, Abs(m_fCarGunLR - prevAngle)); + + // Shoot + if(CPad::GetPad(0)->CarGunJustDown() && + CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeTankShotGun + 800){ + CWorld::Players[CWorld::PlayerInFocus].m_nTimeTankShotGun = CTimer::GetTimeInMilliseconds(); + + // more like -sin(angle), cos(angle), i.e. rotated (0,1,0) + CVector turretDir = CVector(Sin(-m_fCarGunLR), Cos(-m_fCarGunLR), 0.0f); + turretDir = Multiply3x3(GetMatrix(), turretDir); + + float c = Cos(m_fCarGunLR); + float s = Sin(m_fCarGunLR); + CVector rotatedEnd( + c*baseToEnd.x - s*baseToEnd.y, + s*baseToEnd.x + c*baseToEnd.y, + baseToEnd.z - 1.0f); // correct offset here + rotatedEnd += turrentBase; + + CVector point1 = GetMatrix() * rotatedEnd; + CVector point2 = point1 + 60.0f*turretDir; + m_vecMoveSpeed -= 0.06f*turretDir; + m_vecMoveSpeed.z += 0.05f; + + CWeapon::DoTankDoomAiming(FindPlayerVehicle(), FindPlayerPed(), &point1, &point2); + CColPoint colpoint; + CEntity *entity = nil; + CWorld::ProcessLineOfSight(point1, point2, colpoint, entity, true, true, true, true, true, true, false); + if(entity) + point2 = colpoint.point - 0.04f*(colpoint.point - point1); + + CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_TANK_GRENADE, point2, 0); + + // Add particles on the way to the explosion; + float shotDist = (point2 - point1).Magnitude(); + int n = shotDist/4.0f; + RwRGBA black = { 0, 0, 0, 0 }; + for(i = 0; i < n; i++){ + float f = (float)i/n; + CParticle::AddParticle(PARTICLE_HELI_DUST, + point1 + f*(point2 - point1), + CVector(0.0f, 0.0f, 0.0f), + nil, 0.1f, black); + } + + // More particles + CVector shotDir = point2 - point1; + shotDir.Normalise(); + for(i = 0; i < 15; i++){ + float f = i/15.0f; + CParticle::AddParticle(PARTICLE_GUNSMOKE2, point1, + shotDir*CGeneral::GetRandomNumberInRange(0.3f, 1.0f)*f, + nil, CGeneral::GetRandomNumberInRange(0.5f, 1.5f)*f, black); + } + + // And some gun flashes near the gun + CVector flashPos = point1; + CVector nullDir(0.0f, 0.0f, 0.0f); + int lifeSpan = 250; + if(m_vecMoveSpeed.Magnitude() > 0.08f){ + lifeSpan = 125; + flashPos.x += 5.0f*m_vecMoveSpeed.x; + flashPos.y += 5.0f*m_vecMoveSpeed.y; + } + CParticle::AddParticle(PARTICLE_GUNFLASH, flashPos, nullDir, nil, 0.4f, black, 0, 0, 0, lifeSpan); + flashPos += 0.3f*shotDir; + CParticle::AddParticle(PARTICLE_GUNFLASH, flashPos, nullDir, nil, 0.2f, black, 0, 0, 0, lifeSpan); + flashPos += 0.1f*shotDir; + CParticle::AddParticle(PARTICLE_GUNFLASH, flashPos, nullDir, nil, 0.15f, black, 0, 0, 0, lifeSpan); + } + + // Actually update turret node + if(m_aCarNodes[CAR_WINDSCREEN]){ + CMatrix mat; + CVector pos; + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WINDSCREEN])); + pos = mat.GetPosition(); + mat.SetRotateZ(m_fCarGunLR); + mat.Translate(pos); + mat.UpdateRW(); + } +} + +#define HYDRAULIC_UPPER_EXT (-0.12f) +#define HYDRAULIC_LOWER_EXT (0.14f) + +void +CAutomobile::HydraulicControl(void) +{ + int i; + float wheelPositions[4]; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + CColModel *normalColModel = mi->GetColModel(); + float wheelRadius = 0.5f*mi->m_wheelScale; + CPlayerInfo *playerInfo = &CWorld::Players[CWorld::PlayerInFocus]; + CColModel *specialColModel = &playerInfo->m_ColModel; + + if(GetStatus() != STATUS_PLAYER){ + // reset hydraulics for non-player cars + + if(!bUsingSpecialColModel) + return; + if(specialColModel != nil) // this is always true + for(i = 0; i < 4; i++) + wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + for(i = 0; i < 4; i++){ + m_aSuspensionSpringLength[i] = pHandling->fSuspensionUpperLimit - pHandling->fSuspensionLowerLimit; + m_aSuspensionLineLength[i] = normalColModel->lines[i].p0.z - normalColModel->lines[i].p1.z; + m_aSuspensionSpringRatio[i] = (normalColModel->lines[i].p0.z - wheelPositions[i]) / m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + + if(m_hydraulicState == 0) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f); + else if(m_hydraulicState >= 100) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + + if(playerInfo->m_pVehicleEx == this) + playerInfo->m_pVehicleEx = nil; + bUsingSpecialColModel = false; + m_hydraulicState = 0; + return; + } + + // Player car + + float normalUpperLimit = pHandling->fSuspensionUpperLimit; + float normalLowerLimit = pHandling->fSuspensionLowerLimit; + float normalSpringLength = normalUpperLimit - normalLowerLimit; + float extendedUpperLimit = normalUpperLimit - 0.2f; + float extendedLowerLimit = normalLowerLimit - 0.2f; + float extendedSpringLength = extendedUpperLimit - extendedLowerLimit; + + if(!bUsingSpecialColModel){ + // Init special col model + + if(playerInfo->m_pVehicleEx && playerInfo->m_pVehicleEx == this) + playerInfo->m_pVehicleEx->bUsingSpecialColModel = false; + playerInfo->m_pVehicleEx = this; + playerInfo->m_ColModel = *normalColModel; + bUsingSpecialColModel = true; + specialColModel = &playerInfo->m_ColModel; + + if(m_fVelocityChangeForAudio > 0.1f) + m_hydraulicState = 20; + else{ + m_hydraulicState = 0; + normalUpperLimit += HYDRAULIC_UPPER_EXT; + normalSpringLength = normalUpperLimit - (normalLowerLimit+HYDRAULIC_LOWER_EXT); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + } + + // Setup suspension + float normalLineLength = normalSpringLength + wheelRadius; + CVector pos; + for(i = 0; i < 4; i++){ + wheelPositions[i] = normalColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + mi->GetWheelPosn(i, pos); + pos.z += normalUpperLimit; + specialColModel->lines[i].p0 = pos; + pos.z -= normalLineLength; + specialColModel->lines[i].p1 = pos; + m_aSuspensionSpringLength[i] = normalSpringLength; + m_aSuspensionLineLength[i] = normalLineLength; + + if(m_aSuspensionSpringRatio[i] < 1.0f){ + m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + } + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + + // Adjust col model + mi->GetWheelPosn(0, pos); + float minz = pos.z + extendedLowerLimit - wheelRadius; + if(minz < specialColModel->boundingBox.min.z) + specialColModel->boundingBox.min.z = minz; + float radius = Max(specialColModel->boundingBox.min.Magnitude(), specialColModel->boundingBox.max.Magnitude()); + if(specialColModel->boundingSphere.radius < radius) + specialColModel->boundingSphere.radius = radius; + return; + } + + if(playerInfo->m_WBState != WBSTATE_PLAYING) + return; + + bool setPrevRatio = false; + if(m_hydraulicState < 20 && m_fVelocityChangeForAudio > 0.2f){ + if(m_hydraulicState == 0){ + m_hydraulicState = 20; + for(i = 0; i < 4; i++) + m_aWheelPosition[i] -= 0.06f; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f); + setPrevRatio = true; + }else{ + m_hydraulicState++; + } + }else if(m_hydraulicState != 0){ // must always be true + if(m_hydraulicState < 21 && m_fVelocityChangeForAudio < 0.1f){ + m_hydraulicState--; + if(m_hydraulicState == 0) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + } + } + + if(CPad::GetPad(0)->HornJustDown()){ + // Switch between normal and extended + + if(m_hydraulicState < 100) + m_hydraulicState = 100; + else{ + if(m_fVelocityChangeForAudio > 0.1f) + m_hydraulicState = 20; + else + m_hydraulicState = 0; + } + + if(m_hydraulicState < 100){ + if(m_hydraulicState == 0){ + normalUpperLimit += HYDRAULIC_UPPER_EXT; + normalLowerLimit += HYDRAULIC_LOWER_EXT; + normalSpringLength = normalUpperLimit - normalLowerLimit; + } + + // Reset suspension to normal + float normalLineLength = normalSpringLength + wheelRadius; + CVector pos; + for(i = 0; i < 4; i++){ + wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + mi->GetWheelPosn(i, pos); + pos.z += normalUpperLimit; + specialColModel->lines[i].p0 = pos; + pos.z -= normalLineLength; + specialColModel->lines[i].p1 = pos; + m_aSuspensionSpringLength[i] = normalSpringLength; + m_aSuspensionLineLength[i] = normalLineLength; + + if(m_aSuspensionSpringRatio[i] < 1.0f){ + m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + } + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + }else{ + // Reset suspension to extended + float extendedLineLength = extendedSpringLength + wheelRadius; + CVector pos; + for(i = 0; i < 4; i++){ + wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + mi->GetWheelPosn(i, pos); + pos.z += extendedUpperLimit; + specialColModel->lines[i].p0 = pos; + pos.z -= extendedLineLength; + specialColModel->lines[i].p1 = pos; + m_aSuspensionSpringLength[i] = extendedSpringLength; + m_aSuspensionLineLength[i] = extendedLineLength; + + if(m_aSuspensionSpringRatio[i] < 1.0f){ + m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + + setPrevRatio = true; + m_aWheelPosition[i] -= 0.05f; + } + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + } + }else{ + float suspChange[4]; + float maxDelta = 0.0f; + float rear = CPad::GetPad(0)->GetCarGunUpDown()/128.0f; + float front = -rear; + float right = CPad::GetPad(0)->GetCarGunLeftRight()/128.0f; + float left = -right; + suspChange[CARWHEEL_FRONT_LEFT] = Max(front+left, 0.0f); + suspChange[CARWHEEL_REAR_LEFT] = Max(rear+left, 0.0f); + suspChange[CARWHEEL_FRONT_RIGHT] = Max(front+right, 0.0f); + suspChange[CARWHEEL_REAR_RIGHT] = Max(rear+right, 0.0f); + + if(m_hydraulicState < 100){ + // Lowered, move wheels up + + if(m_hydraulicState == 0){ + normalUpperLimit += HYDRAULIC_UPPER_EXT; + normalLowerLimit += HYDRAULIC_LOWER_EXT; + normalSpringLength = normalUpperLimit - normalLowerLimit; + } + + // Set suspension + CVector pos; + for(i = 0; i < 4; i++){ + if(suspChange[i] > 1.0f) + suspChange[i] = 1.0f; + + float oldZ = specialColModel->lines[i].p1.z; + float upperLimit = suspChange[i]*(extendedUpperLimit-normalUpperLimit) + normalUpperLimit; + float springLength = suspChange[i]*(extendedSpringLength-normalSpringLength) + normalSpringLength; + float lineLength = springLength + wheelRadius; + + wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + mi->GetWheelPosn(i, pos); + pos.z += upperLimit; + specialColModel->lines[i].p0 = pos; + pos.z -= lineLength; + if(Abs(pos.z - specialColModel->lines[i].p1.z) > Abs(maxDelta)) + maxDelta = pos.z - specialColModel->lines[i].p1.z; + specialColModel->lines[i].p1 = pos; + m_aSuspensionSpringLength[i] = springLength; + m_aSuspensionLineLength[i] = lineLength; + + if(m_aSuspensionSpringRatio[i] < 1.0f){ + m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + m_aWheelPosition[i] -= (oldZ - specialColModel->lines[i].p1.z)*0.3f; + } + } + }else{ + if(m_hydraulicState < 104){ + m_hydraulicState++; + for(i = 0; i < 4; i++) + m_aWheelPosition[i] -= 0.1f; + } + + if(m_fVelocityChangeForAudio < 0.1f){ + normalUpperLimit += HYDRAULIC_UPPER_EXT; + normalLowerLimit += HYDRAULIC_LOWER_EXT; + normalSpringLength = normalUpperLimit - normalLowerLimit; + } + + // Set suspension + CVector pos; + for(i = 0; i < 4; i++){ + if(suspChange[i] > 1.0f) + suspChange[i] = 1.0f; + + float upperLimit = suspChange[i]*(normalUpperLimit-extendedUpperLimit) + extendedUpperLimit; + float springLength = suspChange[i]*(normalSpringLength-extendedSpringLength) + extendedSpringLength; + float lineLength = springLength + wheelRadius; + + wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + mi->GetWheelPosn(i, pos); + pos.z += upperLimit; + specialColModel->lines[i].p0 = pos; + pos.z -= lineLength; + if(Abs(pos.z - specialColModel->lines[i].p1.z) > Abs(maxDelta)) + maxDelta = pos.z - specialColModel->lines[i].p1.z; + specialColModel->lines[i].p1 = pos; + m_aSuspensionSpringLength[i] = springLength; + m_aSuspensionLineLength[i] = lineLength; + + if(m_aSuspensionSpringRatio[i] < 1.0f){ + m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + } + } + + float limitDiff = extendedLowerLimit - normalLowerLimit; + if(limitDiff != 0.0f && Abs(maxDelta/limitDiff) > 0.01f){ + float f = (maxDelta + limitDiff)/2.0f/limitDiff; + f = Clamp(f, 0.0f, 1.0f); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_3, f); + if(f < 0.4f || f > 0.6f) + setPrevRatio = true; + if(f < 0.25f) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + else if(f > 0.75f) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f); + } + } + + if(setPrevRatio) + for(i = 0; i < 4; i++){ + // wheel radius in relation to suspension line + float wheelRadius = 1.0f - m_aSuspensionSpringLength[i]/m_aSuspensionLineLength[i]; + m_aSuspensionSpringRatioPrev[i] = (m_aSuspensionSpringRatio[i]-wheelRadius)/(1.0f-wheelRadius); + } +} + +void +CAutomobile::ProcessBuoyancy(void) +{ + int i; + CVector impulse, point; + + if(mod_Buoyancy.ProcessBuoyancy(this, m_fBuoyancy, &point, &impulse)){ + bTouchingWater = true; + ApplyMoveForce(impulse); + ApplyTurnForce(impulse, point); + + CVector initialSpeed = m_vecMoveSpeed; + float timeStep = Max(CTimer::GetTimeStep(), 0.01f); + float impulseRatio = impulse.z / (GRAVITY * m_fMass * timeStep); + float waterResistance = Pow(1.0f - 0.05f*impulseRatio, CTimer::GetTimeStep()); + m_vecMoveSpeed *= waterResistance; + m_vecTurnSpeed *= waterResistance; + + if(impulseRatio > 0.5f){ + bIsInWater = true; + if(m_vecMoveSpeed.z < -0.1f) + m_vecMoveSpeed.z = -0.1f; + + if(pDriver){ + pDriver->bIsInWater = true; + if(pDriver->IsPlayer() || !bWaterTight) + pDriver->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + } + for(i = 0; i < m_nNumMaxPassengers; i++) + if(pPassengers[i]){ + pPassengers[i]->bIsInWater = true; + if(pPassengers[i]->IsPlayer() || !bWaterTight) + pPassengers[i]->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + } + }else + bIsInWater = false; + + static uint32 nGenerateRaindrops = 0; + static uint32 nGenerateWaterCircles = 0; + + if(initialSpeed.z < -0.3f && impulse.z > 0.3f){ +#if defined(PC_PARTICLE) || defined (PS2_ALTERNATIVE_CARSPLASH) + RwRGBA color; + color.red = (0.5f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed())*0.45f*255; + color.green = (0.5f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen())*0.45f*255; + color.blue = (0.5f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue())*0.45f*255; + color.alpha = CGeneral::GetRandomNumberInRange(0, 32) + 128; + CParticleObject::AddObject(POBJECT_CAR_WATER_SPLASH, GetPosition(), + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.15f, 0.3f)), + 0.0f, 75, color, true); +#else + CVector pos = (initialSpeed * 2.0f) + (GetPosition() + point); + + for ( int32 i = 0; i < 360; i += 4 ) + { + float fSin = Sin(float(i)); + float fCos = Cos(float(i)); + + CVector dir(fSin*0.01f, fCos*0.01f, CGeneral::GetRandomNumberInRange(0.25f, 0.45f)); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, + pos + CVector(fSin*4.5f, fCos*4.5f, 0.0f), + dir, NULL, 0.0f, CRGBA(225, 225, 255, 180)); + + for ( int32 j = 0; j < 3; j++ ) + { + float fMul = 1.5f * float(j + 1); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, + pos + CVector(fSin * fMul, fCos * fMul, 0.0f), + dir, NULL, 0.0f, CRGBA(225, 225, 255, 180)); + } + } +#endif + + nGenerateRaindrops = CTimer::GetTimeInMilliseconds() + 300; + nGenerateWaterCircles = CTimer::GetTimeInMilliseconds() + 60; + if(m_vecMoveSpeed.z < -0.2f) + m_vecMoveSpeed.z = -0.2f; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WATER_FALL, 0.0f); + } + + if(nGenerateWaterCircles > 0 && nGenerateWaterCircles <= CTimer::GetTimeInMilliseconds()){ + CVector pos = GetPosition(); + float waterLevel = 0.0f; + if(CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &waterLevel, false)) + pos.z = waterLevel; + static RwRGBA black; + if(pos.z != 0.0f){ + nGenerateWaterCircles = 0; + pos.z += 1.0f; + for(i = 0; i < 4; i++){ + CVector p = pos; + p.x += CGeneral::GetRandomNumberInRange(-2.5f, 2.5f); + p.y += CGeneral::GetRandomNumberInRange(-2.5f, 2.5f); + CParticle::AddParticle(PARTICLE_RAIN_SPLASH_BIGGROW, + p, CVector(0.0f, 0.0f, 0.0f), + nil, 0.0f, black, 0, 0, 0, 0); + } + } + } + + if(nGenerateRaindrops > 0 && nGenerateRaindrops <= CTimer::GetTimeInMilliseconds()){ + CVector pos = GetPosition(); + float waterLevel = 0.0f; + if(CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &waterLevel, false)) + pos.z = waterLevel; + static RwRGBA black; + if(pos.z >= 0.0f){ + nGenerateRaindrops = 0; + pos.z += 0.5f; + CParticleObject::AddObject(POBJECT_SPLASHES_AROUND, + pos, CVector(0.0f, 0.0f, 0.0f), 6.5f, 2500, black, true); + } + } + }else{ + bIsInWater = false; + bTouchingWater = false; + + static RwRGBA splashCol = {155, 155, 185, 196}; + static RwRGBA smokeCol = {255, 255, 255, 255}; + + for(i = 0; i < 4; i++){ + if(m_aSuspensionSpringRatio[i] < 1.0f && m_aWheelColPoints[i].surfaceB == SURFACE_WATER){ + CVector pos = m_aWheelColPoints[i].point + 0.3f*GetUp() - GetPosition(); + CVector vSpeed = GetSpeed(pos); + vSpeed.z = 0.0f; +#ifdef GTA_PS2_STUFF + // ps2 puddle physics + CVector moveForce = CTimer::GetTimeStep() * (m_fMass * (vSpeed * -0.003f)); + ApplyMoveForce(moveForce.x, moveForce.y, moveForce.z); +#endif + float fSpeed = vSpeed.MagnitudeSqr(); +#ifdef PC_PARTICLE + if(fSpeed > sq(0.05f)){ + fSpeed = Sqrt(fSpeed); + + float size = Min((fSpeed < 0.15f ? 0.25f : 0.75f)*fSpeed, 0.6f); + CVector right = 0.2f*fSpeed*GetRight() + 0.2f*vSpeed; + + CParticle::AddParticle(PARTICLE_PED_SPLASH, + pos + GetPosition(), -0.5f*right, + nil, size, splashCol, + CGeneral::GetRandomNumberInRange(0.0f, 10.0f), + CGeneral::GetRandomNumberInRange(0.0f, 90.0f), 1, 0); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + pos + GetPosition(), -0.6f*right, + nil, size, smokeCol, 0, 0, 0, 0); + + if((CTimer::GetFrameCounter() & 0xF) == 0) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, 2000.0f*fSpeed); + } +#else + if ( ( (CTimer::GetFrameCounter() + i) & 3 ) == 0 ) + { + if(fSpeed > sq(0.05f)) + { + fSpeed = Sqrt(fSpeed); + CRGBA color(155, 185, 155, 255); + float boxY = GetColModel()->boundingBox.max.y; + CVector right = 0.5f * GetRight(); + + if ( i == 2 ) + { + CParticle::AddParticle(PARTICLE_PED_SPLASH, + GetPosition() + (boxY * GetForward()) + right, + 0.75f*m_vecMoveSpeed, NULL, 0.0f, color); + + } + else if ( i == 0 ) + { + CParticle::AddParticle(PARTICLE_PED_SPLASH, + GetPosition() + (boxY * GetForward()) - right, + 0.75f*m_vecMoveSpeed, NULL, 0.0f, color); + } + + if((CTimer::GetFrameCounter() & 0xF) == 0) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, 2000.0f*fSpeed); + } + } +#endif + } + } + } +} + +void +CAutomobile::DoDriveByShootings(void) +{ + CAnimBlendAssociation *anim; + CWeapon *weapon = pDriver->GetWeapon(); + if(weapon->m_eWeaponType != WEAPONTYPE_UZI) + return; + + weapon->Update(pDriver->m_audioEntityId); + + bool lookingLeft = false; + bool lookingRight = false; + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN){ + if(CPad::GetPad(0)->GetLookLeft()) + lookingLeft = true; + if(CPad::GetPad(0)->GetLookRight()) + lookingRight = true; + }else{ + if(TheCamera.Cams[TheCamera.ActiveCam].LookingLeft) + lookingLeft = true; + if(TheCamera.Cams[TheCamera.ActiveCam].LookingRight) + lookingRight = true; + } + + if(lookingLeft || lookingRight){ + if(lookingLeft){ + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + if(anim == nil || anim->blendDelta < 0.0f) + CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVEBY_LEFT); + else + anim->SetRun(); + }else if(pDriver->m_pMyVehicle->pPassengers[0] == nil || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON){ + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + if(anim == nil || anim->blendDelta < 0.0f) + CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVEBY_RIGHT); + else + anim->SetRun(); + } + + if(CPad::GetPad(0)->GetCarGunFired() && CTimer::GetTimeInMilliseconds() > weapon->m_nTimer){ + weapon->FireFromCar(this, lookingLeft); + weapon->m_nTimer = CTimer::GetTimeInMilliseconds() + 70; + } + }else{ + weapon->Reload(); + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + if(anim) + anim->blendDelta = -1000.0f; + } + + // TODO: what is this? + if(!lookingLeft && m_weaponDoorTimerLeft > 0.0f){ + m_weaponDoorTimerLeft = Max(m_weaponDoorTimerLeft - CTimer::GetTimeStep()*0.1f, 0.0f); + ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_NUM, m_weaponDoorTimerLeft); + } + if(!lookingRight && m_weaponDoorTimerRight > 0.0f){ + m_weaponDoorTimerRight = Max(m_weaponDoorTimerRight - CTimer::GetTimeStep()*0.1f, 0.0f); + ProcessOpenDoor(CAR_DOOR_RF, ANIM_STD_NUM, m_weaponDoorTimerRight); + } +} + +int32 +CAutomobile::RcbanditCheckHitWheels(void) +{ + int x, xmin, xmax; + int y, ymin, ymax; + + xmin = CWorld::GetSectorIndexX(GetPosition().x - 2.0f); + if(xmin < 0) xmin = 0; + xmax = CWorld::GetSectorIndexX(GetPosition().x + 2.0f); + if(xmax > NUMSECTORS_X-1) xmax = NUMSECTORS_X-1; + ymin = CWorld::GetSectorIndexY(GetPosition().y - 2.0f); + if(ymin < 0) ymin = 0; + ymax = CWorld::GetSectorIndexY(GetPosition().y + 2.0f); + if(ymax > NUMSECTORS_Y-1) ymax = NUMSECTORS_X-1; + + CWorld::AdvanceCurrentScanCode(); + + for(y = ymin; y <= ymax; y++) + for(x = xmin; x <= xmax; x++){ + CSector *s = CWorld::GetSector(x, y); + if(RcbanditCheck1CarWheels(s->m_lists[ENTITYLIST_VEHICLES]) || + RcbanditCheck1CarWheels(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP])) + return 1; + } + return 0; +} + +int32 +CAutomobile::RcbanditCheck1CarWheels(CPtrList &list) +{ + static CMatrix matW2B; + int i; + CPtrNode *node; + CAutomobile *car; + CColModel *colModel = GetColModel(); + CVehicleModelInfo *mi; + + for(node = list.first; node; node = node->next){ + car = (CAutomobile*)node->item; + if(this != car && car->IsCar() && car->m_scanCode != CWorld::GetCurrentScanCode()){ + car->m_scanCode = CWorld::GetCurrentScanCode(); + + if(Abs(this->GetPosition().x - car->GetPosition().x) < 10.0f && + Abs(this->GetPosition().y - car->GetPosition().y) < 10.0f){ + mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(car->GetModelIndex()); + + for(i = 0; i < 4; i++){ + if(car->m_aSuspensionSpringRatioPrev[i] < 1.0f || car->GetStatus() == STATUS_SIMPLE){ + CVector wheelPos; + CColSphere sph; + mi->GetWheelPosn(i, wheelPos); + matW2B = Invert(GetMatrix()); + sph.center = matW2B * (car->GetMatrix() * wheelPos); + sph.radius = mi->m_wheelScale*0.25f; + if(CCollision::TestSphereBox(sph, colModel->boundingBox)) + return 1; + } + } + } + } + } + return 0; +} + +void +CAutomobile::PlaceOnRoadProperly(void) +{ + CColPoint point; + CEntity *entity; + CColModel *colModel = GetColModel(); + float lenFwd, lenBack; + float frontZ, rearZ; + + lenFwd = colModel->boundingBox.max.y; + lenBack = -colModel->boundingBox.min.y; + + CVector front(GetPosition().x + GetForward().x*lenFwd, + GetPosition().y + GetForward().y*lenFwd, + GetPosition().z + 5.0f); + if(CWorld::ProcessVerticalLine(front, GetPosition().z - 5.0f, point, entity, + true, false, false, false, false, false, nil)){ + frontZ = point.point.z; + m_pCurGroundEntity = entity; + }else{ + frontZ = m_fMapObjectHeightAhead; + } + + CVector rear(GetPosition().x - GetForward().x*lenBack, + GetPosition().y - GetForward().y*lenBack, + GetPosition().z + 5.0f); + if(CWorld::ProcessVerticalLine(rear, GetPosition().z - 5.0f, point, entity, + true, false, false, false, false, false, nil)){ + rearZ = point.point.z; + m_pCurGroundEntity = entity; + }else{ + rearZ = m_fMapObjectHeightBehind; + } + + float len = lenFwd + lenBack; + float angle = Atan((frontZ - rearZ)/len); + float c = Cos(angle); + float s = Sin(angle); + + GetMatrix().GetRight() = CVector((front.y - rear.y) / len, -(front.x - rear.x) / len, 0.0f); + GetMatrix().GetForward() = CVector(-c * GetRight().y, c * GetRight().x, s); + GetMatrix().GetUp() = CrossProduct(GetRight(), GetForward()); + GetMatrix().GetPosition() = CVector((front.x + rear.x) / 2.0f, (front.y + rear.y) / 2.0f, (frontZ + rearZ) / 2.0f + GetHeightAboveRoad()); +} + +void +CAutomobile::VehicleDamage(float impulse, uint16 damagedPiece) +{ + int i; + float damageMultiplier = 0.2f; + bool doubleMoney = false; + + if(impulse == 0.0f){ + impulse = m_fDamageImpulse; + damagedPiece = m_nDamagePieceType; + damageMultiplier = 1.0f; + } + + CVector pos(0.0f, 0.0f, 0.0f); + + if(!bCanBeDamaged) + return; + + // damage flipped over car + if(GetUp().z < 0.0f && this != FindPlayerVehicle()){ + if(bNotDamagedUpsideDown || GetStatus() == STATUS_PLAYER_REMOTE || bIsInWater) + return; + m_fHealth -= 4.0f*CTimer::GetTimeStep(); + } + + if(impulse > 25.0f && GetStatus() != STATUS_WRECKED){ + if(bIsLawEnforcer && + FindPlayerVehicle() && FindPlayerVehicle() == m_pDamageEntity && + GetStatus() != STATUS_ABANDONED && + FindPlayerVehicle()->m_vecMoveSpeed.Magnitude() >= m_vecMoveSpeed.Magnitude() && + FindPlayerVehicle()->m_vecMoveSpeed.Magnitude() > 0.1f) + FindPlayerPed()->SetWantedLevelNoDrop(1); + + if(GetStatus() == STATUS_PLAYER && impulse > 50.0f){ + uint8 freq = Min(0.4f*impulse*2000.0f/m_fMass + 100.0f, 250.0f); + CPad::GetPad(0)->StartShake(40000/freq, freq); + } + + if(bOnlyDamagedByPlayer){ + if(m_pDamageEntity != FindPlayerPed() && + m_pDamageEntity != FindPlayerVehicle()) + return; + } + + if(bCollisionProof) + return; + + if(m_pDamageEntity){ + if(m_pDamageEntity->IsBuilding() && + DotProduct(m_vecDamageNormal, GetUp()) > 0.6f) + return; + } + + int oldLightStatus[4]; + for(i = 0; i < 4; i++) + oldLightStatus[i] = Damage.GetLightStatus((eLights)i); + + if(GetUp().z > 0.0f || m_vecMoveSpeed.MagnitudeSqr() > 0.1f){ + float impulseMult = bMoreResistantToDamage ? 0.5f : 4.0f; + + switch(damagedPiece){ + case CAR_PIECE_BUMP_FRONT: + GetComponentWorldPosition(CAR_BUMP_FRONT, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_BUMPER_FRONT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT); + doubleMoney = true; + } + if(m_aCarNodes[CAR_BONNET] && Damage.GetPanelStatus(VEHBUMPER_FRONT) == PANEL_STATUS_MISSING){ + case CAR_PIECE_BONNET: + GetComponentWorldPosition(CAR_BONNET, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(GetModelIndex() != MI_DODO) + if(Damage.ApplyDamage(COMPONENT_DOOR_BONNET, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_BONNET, DOOR_BONNET); + doubleMoney = true; + } + } + break; + + case CAR_PIECE_BUMP_REAR: + GetComponentWorldPosition(CAR_BUMP_REAR, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_BUMPER_REAR, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR); + doubleMoney = true; + } + if(m_aCarNodes[CAR_BOOT] && Damage.GetPanelStatus(VEHBUMPER_REAR) == PANEL_STATUS_MISSING){ + case CAR_PIECE_BOOT: + GetComponentWorldPosition(CAR_BOOT, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_DOOR_BOOT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_BOOT, DOOR_BOOT); + doubleMoney = true; + } + } + break; + + case CAR_PIECE_DOOR_LF: + GetComponentWorldPosition(CAR_DOOR_LF, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if((m_nDoorLock == CARLOCK_NOT_USED || m_nDoorLock == CARLOCK_UNLOCKED) && + Damage.ApplyDamage(COMPONENT_DOOR_FRONT_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT); + doubleMoney = true; + } + break; + case CAR_PIECE_DOOR_RF: + GetComponentWorldPosition(CAR_DOOR_RF, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if((m_nDoorLock == CARLOCK_NOT_USED || m_nDoorLock == CARLOCK_UNLOCKED) && + Damage.ApplyDamage(COMPONENT_DOOR_FRONT_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + doubleMoney = true; + } + break; + case CAR_PIECE_DOOR_LR: + GetComponentWorldPosition(CAR_DOOR_LR, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if((m_nDoorLock == CARLOCK_NOT_USED || m_nDoorLock == CARLOCK_UNLOCKED) && + Damage.ApplyDamage(COMPONENT_DOOR_REAR_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT); + doubleMoney = true; + } + break; + case CAR_PIECE_DOOR_RR: + GetComponentWorldPosition(CAR_DOOR_RR, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if((m_nDoorLock == CARLOCK_NOT_USED || m_nDoorLock == CARLOCK_UNLOCKED) && + Damage.ApplyDamage(COMPONENT_DOOR_REAR_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT); + doubleMoney = true; + } + break; + + case CAR_PIECE_WING_LF: + GetComponentWorldPosition(CAR_WING_LF, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_PANEL_FRONT_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetPanelDamage(CAR_WING_LF, VEHPANEL_FRONT_LEFT); + doubleMoney = true; + } + break; + case CAR_PIECE_WING_RF: + GetComponentWorldPosition(CAR_WING_RF, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_PANEL_FRONT_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetPanelDamage(CAR_WING_RF, VEHPANEL_FRONT_RIGHT); + doubleMoney = true; + } + break; + case CAR_PIECE_WING_LR: + GetComponentWorldPosition(CAR_WING_LR, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_PANEL_REAR_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetPanelDamage(CAR_WING_LR, VEHPANEL_REAR_LEFT); + doubleMoney = true; + } + break; + case CAR_PIECE_WING_RR: + GetComponentWorldPosition(CAR_WING_RR, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_PANEL_REAR_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetPanelDamage(CAR_WING_RR, VEHPANEL_REAR_RIGHT); + doubleMoney = true; + } + break; + + case CAR_PIECE_WHEEL_LF: + case CAR_PIECE_WHEEL_LR: + case CAR_PIECE_WHEEL_RF: + case CAR_PIECE_WHEEL_RR: + break; + + case CAR_PIECE_WINDSCREEN: + if(Damage.ApplyDamage(COMPONENT_PANEL_WINDSCREEN, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + uint8 oldStatus = Damage.GetPanelStatus(VEHPANEL_WINDSCREEN); + SetPanelDamage(CAR_WINDSCREEN, VEHPANEL_WINDSCREEN); + if(oldStatus != Damage.GetPanelStatus(VEHPANEL_WINDSCREEN)){ + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_WINDSHIELD_CRACK, 0.0f); + doubleMoney = true; + } + } + break; + } + + if(m_pDamageEntity && m_pDamageEntity == FindPlayerVehicle() && impulse > 10.0f){ + int money = (doubleMoney ? 2 : 1) * impulse*pHandling->nMonetaryValue/1000000.0f; + money = Min(money, 40); + if(money > 2){ + sprintf(gString, "$%d", money); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += money; + } + } + } + + float damage = (impulse-25.0f)*pHandling->fCollisionDamageMultiplier*0.6f*damageMultiplier; + + if(GetModelIndex() == MI_SECURICA && m_pDamageEntity && m_pDamageEntity->GetStatus() == STATUS_PLAYER) + damage *= 7.0f; + + if(damage > 0.0f){ + int oldHealth = m_fHealth; + if(this == FindPlayerVehicle()){ + m_fHealth -= bTakeLessDamage ? damage/6.0f : damage/2.0f; + }else{ + if(damage > 35.0f && pDriver) + pDriver->Say(SOUND_PED_ANNOYED_DRIVER); + m_fHealth -= bTakeLessDamage ? damage/12.0f : damage/4.0f; + } + if(m_fHealth <= 0.0f && oldHealth > 0) + m_fHealth = 1.0f; + } + + // play sound if a light broke + for(i = 0; i < 4; i++) + if(oldLightStatus[i] != 1 && Damage.GetLightStatus((eLights)i) == 1){ + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_LIGHT_BREAK, i); // BUG? i? + break; + } + } + + if(m_fHealth < 250.0f){ + // Car is on fire + if(Damage.GetEngineStatus() < ENGINE_STATUS_ON_FIRE){ + // Set engine on fire and remember who did this + Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE); + m_fFireBlowUpTimer = 0.0f; + m_pSetOnFireEntity = m_pDamageEntity; + if(m_pSetOnFireEntity) + m_pSetOnFireEntity->RegisterReference(&m_pSetOnFireEntity); + } + }else{ + if(GetModelIndex() == MI_BFINJECT){ + if(m_fHealth < 400.0f) + Damage.SetEngineStatus(200); + else if(m_fHealth < 600.0f) + Damage.SetEngineStatus(100); + } + } +} + +void +CAutomobile::dmgDrawCarCollidingParticles(const CVector &pos, float amount) +{ + int i, n; + + if(!GetIsOnScreen()) + return; + + // FindPlayerSpeed() unused + + n = (int)amount/20; + + for(i = 0; i < ((n+4)&0x1F); i++) + CParticle::AddParticle(PARTICLE_SPARK_SMALL, pos, + CVector(CGeneral::GetRandomNumberInRange(-0.1f, 0.1f), + CGeneral::GetRandomNumberInRange(-0.1f, 0.1f), + 0.006f)); + + for(i = 0; i < n+2; i++) + CParticle::AddParticle(PARTICLE_CARCOLLISION_DUST, + CVector(CGeneral::GetRandomNumberInRange(-1.2f, 1.2f) + pos.x, + CGeneral::GetRandomNumberInRange(-1.2f, 1.2f) + pos.y, + pos.z), + CVector(0.0f, 0.0f, 0.0f), nil, 0.5f); + + n = (int)amount/50 + 1; + for(i = 0; i < n; i++) + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, pos, + CVector(CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), + CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), + CGeneral::GetRandomNumberInRange(0.1f, 0.25f)), + nil, + CGeneral::GetRandomNumberInRange(0.02f, 0.08f), + CVehicleModelInfo::ms_vehicleColourTable[m_currentColour1], + CGeneral::GetRandomNumberInRange(-40, 40), + 0, + CGeneral::GetRandomNumberInRange(0, 4)); +} + +void +CAutomobile::AddDamagedVehicleParticles(void) +{ + if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson()) + return; + + uint8 engineStatus = Damage.GetEngineStatus(); + if(engineStatus < ENGINE_STATUS_STEAM1) + return; + + float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward()) * 180.0f; + CVector direction = 0.5f*m_vecMoveSpeed; + CVector damagePos = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->m_positions[CAR_POS_HEADLIGHTS]; + + switch(Damage.GetDoorStatus(DOOR_BONNET)){ + case DOOR_STATUS_OK: + case DOOR_STATUS_SMASHED: + // Bonnet is still there, smoke comes out at the edge + damagePos += vecDAMAGE_ENGINE_POS_SMALL; + break; + case DOOR_STATUS_SWINGING: + case DOOR_STATUS_MISSING: + // Bonnet is gone, smoke comes out at the engine + damagePos += vecDAMAGE_ENGINE_POS_BIG; + break; + } + + if(GetModelIndex() == MI_BFINJECT) + damagePos = CVector(0.3f, -1.5f, -0.1f); + + damagePos = GetMatrix()*damagePos; + damagePos.z += 0.15f; + + if(engineStatus < ENGINE_STATUS_STEAM2){ + if(fwdSpeed < 90.0f){ + direction.z += 0.05f; + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction, nil, 0.1f); + } + }else if(engineStatus < ENGINE_STATUS_SMOKE){ + if(fwdSpeed < 90.0f) + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction, nil, 0.0f); + }else if(engineStatus < ENGINE_STATUS_ON_FIRE){ + if(fwdSpeed < 90.0f){ + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction, nil, 0.0f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, damagePos, 0.3f*direction, nil, 0.0f); + } + }else if(m_fHealth > 250.0f){ + if(fwdSpeed < 90.0f) + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, 0.2f*direction, nil, 0.0f); + } +} + +int32 +CAutomobile::AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed) +{ + int i; + CVector dir; + static RwRGBA grassCol = { 8, 24, 8, 255 }; + static RwRGBA gravelCol = { 64, 64, 64, 255 }; + static RwRGBA mudCol = { 64, 32, 16, 255 }; + static RwRGBA waterCol = { 48, 48, 64, 0 }; + + if(!belowEffectSpeed) + return 0; + + switch(colpoint->surfaceB){ + case SURFACE_GRASS: + dir.x = -0.05f*m_vecMoveSpeed.x; + dir.y = -0.05f*m_vecMoveSpeed.y; + for(i = 0; i < 4; i++){ + dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil, + CGeneral::GetRandomNumberInRange(0.02f, 0.1f), grassCol); + } + return 0; + case SURFACE_GRAVEL: + dir.x = -0.05f*m_vecMoveSpeed.x; + dir.y = -0.05f*m_vecMoveSpeed.y; + for(i = 0; i < 4; i++){ + dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil, + CGeneral::GetRandomNumberInRange(0.02f, 0.06f), gravelCol); + } + return 1; + case SURFACE_MUD_DRY: + dir.x = -0.05f*m_vecMoveSpeed.x; + dir.y = -0.05f*m_vecMoveSpeed.y; + for(i = 0; i < 4; i++){ + dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil, + CGeneral::GetRandomNumberInRange(0.02f, 0.06f), mudCol); + } + return 0; + default: + if ( CWeather::WetRoads > 0.01f +#ifdef PC_PARTICLE + && CTimer::GetFrameCounter() & 1 +#endif + ) + { + CParticle::AddParticle( +#if defined(FIX_BUGS) && !defined(PC_PARTICLE) // looks wrong on PC particles + PARTICLE_WHEEL_WATER, +#else + PARTICLE_WATERSPRAY, +#endif + colpoint->point + CVector(0.0f, 0.0f, 0.25f+0.25f), +#ifdef PC_PARTICLE + CVector(0.0f, 0.0f, 1.0f), +#else + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.005f, 0.04f)), +#endif + nil, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), waterCol); + return 0; + } + + return 1; + } +} + +void +CAutomobile::GetComponentWorldPosition(int32 component, CVector &pos) +{ + if(m_aCarNodes[component] == nil){ + printf("CarNode missing: %d %d\n", GetModelIndex(), component); + return; + } + RwMatrix *ltm = RwFrameGetLTM(m_aCarNodes[component]); + pos = *RwMatrixGetPos(ltm); +} + +bool +CAutomobile::IsComponentPresent(int32 comp) +{ + return m_aCarNodes[comp] != nil; +} + +void +CAutomobile::SetComponentRotation(int32 component, CVector rotation) +{ + CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component])); + CVector pos = mat.GetPosition(); + // BUG: all these set the whole matrix + mat.SetRotateX(DEGTORAD(rotation.x)); + mat.SetRotateY(DEGTORAD(rotation.y)); + mat.SetRotateZ(DEGTORAD(rotation.z)); + mat.Translate(pos); + mat.UpdateRW(); +} + +void +CAutomobile::OpenDoor(int32 component, eDoors door, float openRatio) +{ + CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component])); + CVector pos = mat.GetPosition(); + float axes[3] = { 0.0f, 0.0f, 0.0f }; + float wasClosed = false; + + if(Doors[door].IsClosed()){ + // enable angle cull for closed doors + RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::ClearAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL); + wasClosed = true; + } + + Doors[door].Open(openRatio); + + if(wasClosed && Doors[door].RetAngleWhenClosed() != Doors[door].m_fAngle){ + // door opened + HideAllComps(); + // turn off angle cull for swinging door + RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::SetAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_DOOR_OPEN_BONNET + door, 0.0f); + } + + if(!wasClosed && openRatio == 0.0f){ + // door closed + if(Damage.GetDoorStatus(door) == DOOR_STATUS_SWINGING) + Damage.SetDoorStatus(door, DOOR_STATUS_OK); // huh? + ShowAllComps(); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_DOOR_CLOSE_BONNET + door, 0.0f); + } + + axes[Doors[door].m_nAxis] = Doors[door].m_fAngle; + mat.SetRotate(axes[0], axes[1], axes[2]); + mat.Translate(pos); + mat.UpdateRW(); +} + +inline void ProcessDoorOpenAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float end) +{ + if(time > start && time < end){ + float ratio = (time - start)/(end - start); + if(car->Doors[door].GetAngleOpenRatio() < ratio) + car->OpenDoor(component, door, ratio); + }else if(time > end){ + car->OpenDoor(component, door, 1.0f); + } +} + +inline void ProcessDoorCloseAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float end) +{ + if(time > start && time < end){ + float ratio = 1.0f - (time - start)/(end - start); + if(car->Doors[door].GetAngleOpenRatio() > ratio) + car->OpenDoor(component, door, ratio); + }else if(time > end){ + car->OpenDoor(component, door, 0.0f); + } +} + +inline void ProcessDoorOpenCloseAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float mid, float end) +{ + if(time > start && time < mid){ + // open + float ratio = (time - start)/(mid - start); + if(car->Doors[door].GetAngleOpenRatio() < ratio) + car->OpenDoor(component, door, ratio); + }else if(time > mid && time < end){ + // close + float ratio = 1.0f - (time - mid)/(end - mid); + if(car->Doors[door].GetAngleOpenRatio() > ratio) + car->OpenDoor(component, door, ratio); + }else if(time > end){ + car->OpenDoor(component, door, 0.0f); + } +} +void +CAutomobile::ProcessOpenDoor(uint32 component, uint32 anim, float time) +{ + eDoors door; + + switch(component){ + case CAR_DOOR_RF: door = DOOR_FRONT_RIGHT; break; + case CAR_DOOR_RR: door = DOOR_REAR_RIGHT; break; + case CAR_DOOR_LF: door = DOOR_FRONT_LEFT; break; + case CAR_DOOR_LR: door = DOOR_REAR_LEFT; break; + default: assert(0); + } + + if(IsDoorMissing(door)) + return; + + switch(anim){ + case ANIM_STD_QUICKJACK: + case ANIM_STD_CAR_OPEN_DOOR_LHS: + case ANIM_STD_CAR_OPEN_DOOR_RHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.66f, 0.8f); + break; + case ANIM_STD_CAR_CLOSE_DOOR_LHS: + case ANIM_STD_CAR_CLOSE_DOOR_LO_LHS: + case ANIM_STD_CAR_CLOSE_DOOR_RHS: + case ANIM_STD_CAR_CLOSE_DOOR_LO_RHS: + ProcessDoorCloseAnimation(this, component, door, time, 0.2f, 0.63f); + break; + case ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS: + case ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS: + ProcessDoorOpenCloseAnimation(this, component, door, time, 0.1f, 0.6f, 0.95f); + break; + case ANIM_STD_GETOUT_LHS: + case ANIM_STD_GETOUT_LO_LHS: + case ANIM_STD_GETOUT_RHS: + case ANIM_STD_GETOUT_LO_RHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.06f, 0.43f); + break; + case ANIM_STD_CAR_CLOSE_LHS: + case ANIM_STD_CAR_CLOSE_RHS: + ProcessDoorCloseAnimation(this, component, door, time, 0.1f, 0.23f); + break; + case ANIM_STD_CAR_PULL_OUT_PED_RHS: + case ANIM_STD_CAR_PULL_OUT_PED_LO_RHS: + OpenDoor(component, door, 1.0f); + break; + case ANIM_STD_COACH_OPEN_LHS: + case ANIM_STD_COACH_OPEN_RHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.66f, 0.8f); + break; + case ANIM_STD_COACH_GET_OUT_LHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.0f, 0.3f); + break; + case ANIM_STD_VAN_OPEN_DOOR_REAR_LHS: + case ANIM_STD_VAN_OPEN_DOOR_REAR_RHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.37f, 0.55f); + break; + case ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS: + case ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS: + ProcessDoorCloseAnimation(this, component, door, time, 0.5f, 0.8f); + break; + case ANIM_STD_VAN_GET_OUT_REAR_LHS: + case ANIM_STD_VAN_GET_OUT_REAR_RHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.5f, 0.6f); + break; + case ANIM_STD_NUM: + OpenDoor(component, door, time); + break; + } +} + +bool +CAutomobile::IsDoorReady(eDoors door) +{ + if(Doors[door].IsClosed() || IsDoorMissing(door)) + return true; + int doorflag = 0; + switch(door){ + case DOOR_FRONT_LEFT: doorflag = CAR_DOOR_FLAG_LF; break; + case DOOR_FRONT_RIGHT: doorflag = CAR_DOOR_FLAG_RF; break; + case DOOR_REAR_LEFT: doorflag = CAR_DOOR_FLAG_LR; break; + case DOOR_REAR_RIGHT: doorflag = CAR_DOOR_FLAG_RR; break; + default: break; + } + return (doorflag & m_nGettingInFlags) == 0; +} + +bool +CAutomobile::IsDoorFullyOpen(eDoors door) +{ + return Doors[door].IsFullyOpen() || IsDoorMissing(door); +} + +bool +CAutomobile::IsDoorClosed(eDoors door) +{ + return !!Doors[door].IsClosed(); +} + +bool +CAutomobile::IsDoorMissing(eDoors door) +{ + return Damage.GetDoorStatus(door) == DOOR_STATUS_MISSING; +} + +void +CAutomobile::RemoveRefsToVehicle(CEntity *ent) +{ + int i; + for(i = 0; i < 4; i++) + if(m_aGroundPhysical[i] == ent) + m_aGroundPhysical[i] = nil; +} + +void +CAutomobile::BlowUpCar(CEntity *culprit) +{ + int i; + RpAtomic *atomic; + + if(!bCanBeDamaged) + return; + + // explosion pushes vehicle up + m_vecMoveSpeed.z += 0.13f; + SetStatus(STATUS_WRECKED); + bRenderScorched = true; + m_nTimeOfDeath = CTimer::GetTimeInMilliseconds(); + Damage.FuckCarCompletely(); + + if(GetModelIndex() != MI_RCBANDIT){ + SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT); + SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR); + SetDoorDamage(CAR_BONNET, DOOR_BONNET); + SetDoorDamage(CAR_BOOT, DOOR_BOOT); + SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT); + SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT); + SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT); + SpawnFlyingComponent(CAR_WHEEL_LF, COMPGROUP_WHEEL); + atomic = nil; + RwFrameForAllObjects(m_aCarNodes[CAR_WHEEL_LF], GetCurrentAtomicObjectCB, &atomic); + if(atomic) + RpAtomicSetFlags(atomic, 0); + } + + m_fHealth = 0.0f; + m_nBombTimer = 0; + m_bombType = CARBOMB_NONE; + + TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z); + + // kill driver and passengers + if(pDriver){ + CDarkel::RegisterKillByPlayer(pDriver, WEAPONTYPE_EXPLOSION); + if(pDriver->GetPedState() == PED_DRIVING){ + pDriver->SetDead(); + if(!pDriver->IsPlayer()) + pDriver->FlagToDestroyWhenNextProcessed(); + }else + pDriver->SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } + for(i = 0; i < m_nNumMaxPassengers; i++){ + if(pPassengers[i]){ + CDarkel::RegisterKillByPlayer(pPassengers[i], WEAPONTYPE_EXPLOSION); + if(pPassengers[i]->GetPedState() == PED_DRIVING){ + pPassengers[i]->SetDead(); + if(!pPassengers[i]->IsPlayer()) + pPassengers[i]->FlagToDestroyWhenNextProcessed(); + }else + pPassengers[i]->SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } + } + + bEngineOn = false; + bLightsOn = false; + m_bSirenOrAlarm = false; + bTaxiLight = false; + if(bIsAmbulanceOnDuty){ + bIsAmbulanceOnDuty = false; + CCarCtrl::NumAmbulancesOnDuty--; + } + if(bIsFireTruckOnDuty){ + bIsFireTruckOnDuty = false; + CCarCtrl::NumFiretrucksOnDuty--; + } + ChangeLawEnforcerState(false); + + gFireManager.StartFire(this, culprit, 0.8f, true); + CDarkel::RegisterCarBlownUpByPlayer(this); + if(GetModelIndex() == MI_RCBANDIT) + CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR_QUICK, GetPosition(), 0); + else + CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR, GetPosition(), 0); +} + +bool +CAutomobile::SetUpWheelColModel(CColModel *colModel) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + CColModel *vehColModel = mi->GetColModel(); + + colModel->boundingSphere = vehColModel->boundingSphere; + colModel->boundingBox = vehColModel->boundingBox; + + CMatrix mat; + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + colModel->spheres[0].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LF); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB])); + colModel->spheres[1].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LR); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + colModel->spheres[2].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RF); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB])); + colModel->spheres[3].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RR); + + if(m_aCarNodes[CAR_WHEEL_LM] != nil && m_aCarNodes[CAR_WHEEL_RM] != nil){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LM])); + colModel->spheres[4].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LR); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RM])); + colModel->spheres[5].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RR); + colModel->numSpheres = 6; + }else + colModel->numSpheres = 4; + + return true; +} + +float fBurstForceMult = 0.03f; + +// this isn't used in III yet +void +CAutomobile::BurstTyre(uint8 wheel) +{ + switch(wheel){ + case CAR_PIECE_WHEEL_LF: wheel = CARWHEEL_FRONT_LEFT; break; + case CAR_PIECE_WHEEL_RF: wheel = CARWHEEL_FRONT_RIGHT; break; + case CAR_PIECE_WHEEL_LR: wheel = CARWHEEL_REAR_LEFT; break; + case CAR_PIECE_WHEEL_RR: wheel = CARWHEEL_REAR_RIGHT; break; + } + + int status = Damage.GetWheelStatus(wheel); + if(status == WHEEL_STATUS_OK){ + Damage.SetWheelStatus(wheel, WHEEL_STATUS_BURST); + + if(GetStatus() == STATUS_SIMPLE){ + SetStatus(STATUS_PHYSICS); + CCarCtrl::SwitchVehicleToRealPhysics(this); + } + + ApplyMoveForce(GetRight() * m_fMass * CGeneral::GetRandomNumberInRange(-fBurstForceMult, fBurstForceMult)); + ApplyTurnForce(GetRight() * m_fTurnMass * CGeneral::GetRandomNumberInRange(-fBurstForceMult, fBurstForceMult), GetForward()); + } +} + +bool +CAutomobile::IsRoomForPedToLeaveCar(uint32 component, CVector *doorOffset) +{ + CColPoint colpoint; + CEntity *ent; + colpoint.point = CVector(0.0f, 0.0f, 0.0f); + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + CVector seatPos; + switch(component){ + case CAR_DOOR_RF: + seatPos = mi->GetFrontSeatPosn(); + break; + case CAR_DOOR_LF: + seatPos = mi->GetFrontSeatPosn(); + seatPos.x = -seatPos.x; + break; + case CAR_DOOR_RR: + seatPos = mi->m_positions[CAR_POS_BACKSEAT]; + break; + case CAR_DOOR_LR: + seatPos = mi->m_positions[CAR_POS_BACKSEAT]; + seatPos.x = -seatPos.x; + break; + } + seatPos = GetMatrix() * seatPos; + + CVector doorPos = CPed::GetPositionToOpenCarDoor(this, component); + if(doorOffset){ + CVector off = *doorOffset; + if(component == CAR_DOOR_RF || component == CAR_DOOR_RR) + off.x = -off.x; + doorPos += Multiply3x3(GetMatrix(), off); + } + + if(GetUp().z < 0.0f){ + seatPos.z += 0.5f; + doorPos.z += 0.5f; + } + + CVector dist = doorPos - seatPos; + + // Removing that makes this func. return false for van doors. + doorPos.z += 0.5f; + float length = dist.Magnitude(); + CVector pedPos = seatPos + dist*((length+0.6f)/length); + + if(!CWorld::GetIsLineOfSightClear(seatPos, pedPos, true, false, false, true, false, false)) + return false; + if(CWorld::TestSphereAgainstWorld(doorPos, 0.6f, this, true, true, false, true, false, false)) + return false; + if(CWorld::ProcessVerticalLine(doorPos, 1000.0f, colpoint, ent, true, false, false, true, false, false, nil)) + if(colpoint.point.z > doorPos.z && colpoint.point.z < doorPos.z + 0.6f) + return false; + float upperZ = colpoint.point.z; + if(!CWorld::ProcessVerticalLine(doorPos, -1000.0f, colpoint, ent, true, false, false, true, false, false, nil)) + return false; + if(upperZ != 0.0f && upperZ < colpoint.point.z) + return false; + return true; +} + +float +CAutomobile::GetHeightAboveRoad(void) +{ + return m_fHeightAboveRoad; +} + +void +CAutomobile::PlayCarHorn(void) +{ + uint32 r; + + if(m_nCarHornTimer != 0) + return; + + r = CGeneral::GetRandomNumber() & 7; + if(r < 2){ + m_nCarHornTimer = 45; + }else if(r < 4){ + if(pDriver) + pDriver->Say(SOUND_PED_ANNOYED_DRIVER); + m_nCarHornTimer = 45; + }else{ + if(pDriver) + pDriver->Say(SOUND_PED_ANNOYED_DRIVER); + } +} + +void +CAutomobile::PlayHornIfNecessary(void) +{ + if(AutoPilot.m_bSlowedDownBecauseOfPeds || + AutoPilot.m_bSlowedDownBecauseOfCars) + if(!HasCarStoppedBecauseOfLight()) + PlayCarHorn(); +} + + +void +CAutomobile::ResetSuspension(void) +{ + int i; + for(i = 0; i < 4; i++){ + m_aSuspensionSpringRatio[i] = 1.0f; + m_aWheelTimer[i] = 0.0f; + m_aWheelRotation[i] = 0.0f; + m_aWheelState[i] = WHEEL_STATE_NORMAL; + } +} + +void +CAutomobile::SetupSuspensionLines(void) +{ + int i; + CVector posn; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + CColModel *colModel = mi->GetColModel(); + + // Each suspension line starts at the uppermost wheel position + // and extends down to the lowermost point on the tyre + for(i = 0; i < 4; i++){ + mi->GetWheelPosn(i, posn); + m_aWheelPosition[i] = posn.z; + + // uppermost wheel position + posn.z += pHandling->fSuspensionUpperLimit; + colModel->lines[i].p0 = posn; + + // lowermost wheel position + posn.z += pHandling->fSuspensionLowerLimit - pHandling->fSuspensionUpperLimit; + // lowest point on tyre + posn.z -= mi->m_wheelScale*0.5f; + colModel->lines[i].p1 = posn; + + // this is length of the spring at rest + m_aSuspensionSpringLength[i] = pHandling->fSuspensionUpperLimit - pHandling->fSuspensionLowerLimit; + m_aSuspensionLineLength[i] = colModel->lines[i].p0.z - colModel->lines[i].p1.z; + } + + // Compress spring somewhat to get normal height on road + m_fHeightAboveRoad = -(colModel->lines[0].p0.z + (colModel->lines[0].p1.z - colModel->lines[0].p0.z)* + (1.0f - 1.0f/(8.0f*pHandling->fSuspensionForceLevel))); + for(i = 0; i < 4; i++) + m_aWheelPosition[i] = mi->m_wheelScale*0.5f - m_fHeightAboveRoad; + + // adjust col model to include suspension lines + if(colModel->boundingBox.min.z > colModel->lines[0].p1.z) + colModel->boundingBox.min.z = colModel->lines[0].p1.z; + float radius = Max(colModel->boundingBox.min.Magnitude(), colModel->boundingBox.max.Magnitude()); + if(colModel->boundingSphere.radius < radius) + colModel->boundingSphere.radius = radius; + + if(GetModelIndex() == MI_RCBANDIT){ + colModel->boundingSphere.radius = 2.0f; + for(i = 0; i < colModel->numSpheres; i++) + colModel->spheres[i].radius = 0.3f; + } +} + +// called on police cars +void +CAutomobile::ScanForCrimes(void) +{ + if(FindPlayerVehicle() && FindPlayerVehicle()->IsCar()) + if(FindPlayerVehicle()->IsAlarmOn()) + // if player's alarm is on, increase wanted level + if((FindPlayerVehicle()->GetPosition() - GetPosition()).MagnitudeSqr() < sq(20.0f)) + CWorld::Players[CWorld::PlayerInFocus].m_pPed->SetWantedLevelNoDrop(1); +} + +void +CAutomobile::BlowUpCarsInPath(void) +{ + int i; + + if(m_vecMoveSpeed.Magnitude() > 0.1f) + for(i = 0; i < m_nCollisionRecords; i++) + if(m_aCollisionRecords[i] && + m_aCollisionRecords[i]->IsVehicle() && + m_aCollisionRecords[i]->GetModelIndex() != MI_RHINO && + !m_aCollisionRecords[i]->bRenderScorched) + ((CVehicle*)m_aCollisionRecords[i])->BlowUpCar(this); +} + +bool +CAutomobile::HasCarStoppedBecauseOfLight(void) +{ + int i; + + if(GetStatus() != STATUS_SIMPLE && GetStatus() != STATUS_PHYSICS) + return false; + + if(AutoPilot.m_nCurrentRouteNode && AutoPilot.m_nNextRouteNode){ + CPathNode *curnode = &ThePaths.m_pathNodes[AutoPilot.m_nCurrentRouteNode]; + for(i = 0; i < curnode->numLinks; i++) + if(ThePaths.ConnectedNode(curnode->firstLink + i) == AutoPilot.m_nNextRouteNode) + break; + if(i < curnode->numLinks && + ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3) + return true; + } + + if(AutoPilot.m_nCurrentRouteNode && AutoPilot.m_nPrevRouteNode){ + CPathNode *curnode = &ThePaths.m_pathNodes[AutoPilot.m_nCurrentRouteNode]; + for(i = 0; i < curnode->numLinks; i++) + if(ThePaths.ConnectedNode(curnode->firstLink + i) == AutoPilot.m_nPrevRouteNode) + break; + if(i < curnode->numLinks && + ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3) + return true; + } + + return false; +} + +void +CPed::DeadPedMakesTyresBloody(void) +{ + int minX = CWorld::GetSectorIndexX(GetPosition().x - 2.0f); + if (minX < 0) minX = 0; + int minY = CWorld::GetSectorIndexY(GetPosition().y - 2.0f); + if (minY < 0) minY = 0; + int maxX = CWorld::GetSectorIndexX(GetPosition().x + 2.0f); + if (maxX > NUMSECTORS_X-1) maxX = NUMSECTORS_X-1; + int maxY = CWorld::GetSectorIndexY(GetPosition().y + 2.0f); + if (maxY > NUMSECTORS_Y-1) maxY = NUMSECTORS_Y-1; + + CWorld::AdvanceCurrentScanCode(); + + for (int curY = minY; curY <= maxY; curY++) { + for (int curX = minX; curX <= maxX; curX++) { + CSector *sector = CWorld::GetSector(curX, curY); + MakeTyresMuddySectorList(sector->m_lists[ENTITYLIST_VEHICLES]); + MakeTyresMuddySectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP]); + } + } +} + +void +CPed::MakeTyresMuddySectorList(CPtrList &list) +{ + for (CPtrNode *node = list.first; node; node = node->next) { + CVehicle *veh = (CVehicle*)node->item; + if (veh->IsCar() && veh->m_scanCode != CWorld::GetCurrentScanCode()) { + veh->m_scanCode = CWorld::GetCurrentScanCode(); + + if (Abs(GetPosition().x - veh->GetPosition().x) < 10.0f) { + + if (Abs(GetPosition().y - veh->GetPosition().y) < 10.0f + && veh->m_vecMoveSpeed.MagnitudeSqr2D() > 0.05f) { + + for(int wheel = 0; wheel < 4; wheel++) { + + if (!((CAutomobile*)veh)->m_aWheelSkidmarkBloody[wheel] + && ((CAutomobile*)veh)->m_aSuspensionSpringRatio[wheel] < 1.0f) { + + CColModel *vehCol = veh->GetModelInfo()->GetColModel(); + CVector approxWheelOffset; + switch (wheel) { + case 0: + approxWheelOffset = CVector(-vehCol->boundingBox.max.x, vehCol->boundingBox.max.y, 0.0f); + break; + case 1: + approxWheelOffset = CVector(-vehCol->boundingBox.max.x, vehCol->boundingBox.min.y, 0.0f); + break; + case 2: + approxWheelOffset = CVector(vehCol->boundingBox.max.x, vehCol->boundingBox.max.y, 0.0f); + break; + case 3: + approxWheelOffset = CVector(vehCol->boundingBox.max.x, vehCol->boundingBox.min.y, 0.0f); + break; + default: + break; + } + + // I hope so + CVector wheelPos = veh->GetMatrix() * approxWheelOffset; + if (Abs(wheelPos.z - GetPosition().z) < 2.0f) { + + if ((wheelPos - GetPosition()).MagnitudeSqr2D() < 1.0f) { + if (CGame::nastyGame) { + ((CAutomobile*)veh)->m_aWheelSkidmarkBloody[wheel] = true; + DMAudio.PlayOneShot(veh->m_audioEntityId, SOUND_SPLATTER, 0.0f); + } + veh->ApplyMoveForce(CVector(0.0f, 0.0f, 50.0f)); + + CVector vehAndWheelDist = wheelPos - veh->GetPosition(); + veh->ApplyTurnForce(CVector(0.0f, 0.0f, 50.0f), vehAndWheelDist); + + if (veh == FindPlayerVehicle()) { + CPad::GetPad(0)->StartShake(300, 70); + } + } + } + } + } + } + } + } + } +} + +void +CAutomobile::SetBusDoorTimer(uint32 timer, uint8 type) +{ + if(timer < 1000) + timer = 1000; + if(type == 0) + // open and close + m_nBusDoorTimerStart = CTimer::GetTimeInMilliseconds(); + else + // only close + m_nBusDoorTimerStart = CTimer::GetTimeInMilliseconds() - 500; + m_nBusDoorTimerEnd = m_nBusDoorTimerStart + timer; +} + +void +CAutomobile::ProcessAutoBusDoors(void) +{ + if(CTimer::GetTimeInMilliseconds() < m_nBusDoorTimerEnd){ + if(m_nBusDoorTimerEnd != 0 && CTimer::GetTimeInMilliseconds() > m_nBusDoorTimerEnd-500){ + // close door + if(!IsDoorMissing(DOOR_FRONT_LEFT) && (m_nGettingInFlags & CAR_DOOR_FLAG_LF) == 0){ + if(IsDoorClosed(DOOR_FRONT_LEFT)){ + m_nBusDoorTimerEnd = CTimer::GetTimeInMilliseconds(); + OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f); + }else{ + OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, + 1.0f - (CTimer::GetTimeInMilliseconds() - (m_nBusDoorTimerEnd-500))/500.0f); + } + } + + if(!IsDoorMissing(DOOR_FRONT_RIGHT) && (m_nGettingInFlags & CAR_DOOR_FLAG_RF) == 0){ + if(IsDoorClosed(DOOR_FRONT_RIGHT)){ + m_nBusDoorTimerEnd = CTimer::GetTimeInMilliseconds(); + OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, 0.0f); + }else{ + OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, + 1.0f - (CTimer::GetTimeInMilliseconds() - (m_nBusDoorTimerEnd-500))/500.0f); + } + } + } + }else{ + // ended + if(m_nBusDoorTimerStart){ + if(!IsDoorMissing(DOOR_FRONT_LEFT) && (m_nGettingInFlags & CAR_DOOR_FLAG_LF) == 0) + OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f); + if(!IsDoorMissing(DOOR_FRONT_RIGHT) && (m_nGettingInFlags & CAR_DOOR_FLAG_RF) == 0) + OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, 0.0f); + m_nBusDoorTimerStart = 0; + m_nBusDoorTimerEnd = 0; + } + } +} + +void +CAutomobile::ProcessSwingingDoor(int32 component, eDoors door) +{ + if(Damage.GetDoorStatus(door) != DOOR_STATUS_SWINGING) + return; + + CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component])); + CVector pos = mat.GetPosition(); + float axes[3] = { 0.0f, 0.0f, 0.0f }; + + Doors[door].Process(this); + axes[Doors[door].m_nAxis] = Doors[door].m_fAngle; + mat.SetRotate(axes[0], axes[1], axes[2]); + mat.Translate(pos); + mat.UpdateRW(); +} + +void +CAutomobile::Fix(void) +{ + int component; + + Damage.ResetDamageStatus(); + + if(pHandling->Flags & HANDLING_NO_DOORS){ + Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_REAR_LEFT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_REAR_RIGHT, DOOR_STATUS_MISSING); + } + + bIsDamaged = false; + RpClumpForAllAtomics((RpClump*)m_rwObject, CVehicleModelInfo::HideAllComponentsAtomicCB, (void*)ATOMIC_FLAG_DAM); + + for(component = CAR_BUMP_FRONT; component < NUM_CAR_NODES; component++){ + if(m_aCarNodes[component]){ + CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component])); + mat.SetTranslate(mat.GetPosition()); + mat.UpdateRW(); + } + } +} + +void +CAutomobile::SetupDamageAfterLoad(void) +{ + if(m_aCarNodes[CAR_BUMP_FRONT]) + SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT); + if(m_aCarNodes[CAR_BONNET]) + SetDoorDamage(CAR_BONNET, DOOR_BONNET); + if(m_aCarNodes[CAR_BUMP_REAR]) + SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR); + if(m_aCarNodes[CAR_BOOT]) + SetDoorDamage(CAR_BOOT, DOOR_BOOT); + if(m_aCarNodes[CAR_DOOR_LF]) + SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT); + if(m_aCarNodes[CAR_DOOR_RF]) + SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + if(m_aCarNodes[CAR_DOOR_LR]) + SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT); + if(m_aCarNodes[CAR_DOOR_RR]) + SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT); + if(m_aCarNodes[CAR_WING_LF]) + SetPanelDamage(CAR_WING_LF, VEHPANEL_FRONT_LEFT); + if(m_aCarNodes[CAR_WING_RF]) + SetPanelDamage(CAR_WING_RF, VEHPANEL_FRONT_RIGHT); + if(m_aCarNodes[CAR_WING_LR]) + SetPanelDamage(CAR_WING_LR, VEHPANEL_REAR_LEFT); + if(m_aCarNodes[CAR_WING_RR]) + SetPanelDamage(CAR_WING_RR, VEHPANEL_REAR_RIGHT); +} + +RwObject* +GetCurrentAtomicObjectCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + assert(RwObjectGetType(object) == rpATOMIC); + if(RpAtomicGetFlags(atomic) & rpATOMICRENDER) + *(RpAtomic**)data = atomic; + return object; +} + +static CColPoint aTempPedColPts[MAX_COLLISION_POINTS]; + +CObject* +CAutomobile::SpawnFlyingComponent(int32 component, uint32 type) +{ + RpAtomic *atomic; + RwFrame *frame; + RwMatrix *matrix; + CObject *obj; + + if(CObject::nNoTempObjects >= NUMTEMPOBJECTS) + return nil; + + atomic = nil; + RwFrameForAllObjects(m_aCarNodes[component], GetCurrentAtomicObjectCB, &atomic); + if(atomic == nil) + return nil; + + obj = new CObject(); + if(obj == nil) + return nil; + + if(component == CAR_WINDSCREEN){ + obj->SetModelIndexNoCreate(MI_CAR_BONNET); + }else switch(type){ + case COMPGROUP_BUMPER: + obj->SetModelIndexNoCreate(MI_CAR_BUMPER); + break; + case COMPGROUP_WHEEL: + obj->SetModelIndexNoCreate(MI_CAR_WHEEL); + break; + case COMPGROUP_DOOR: + obj->SetModelIndexNoCreate(MI_CAR_DOOR); + obj->SetCenterOfMass(0.0f, -0.5f, 0.0f); + break; + case COMPGROUP_BONNET: + obj->SetModelIndexNoCreate(MI_CAR_BONNET); + obj->SetCenterOfMass(0.0f, 0.4f, 0.0f); + break; + case COMPGROUP_BOOT: + obj->SetModelIndexNoCreate(MI_CAR_BOOT); + obj->SetCenterOfMass(0.0f, -0.3f, 0.0f); + break; + case COMPGROUP_PANEL: + default: + obj->SetModelIndexNoCreate(MI_CAR_PANEL); + break; + } + + // object needs base model + obj->RefModelInfo(GetModelIndex()); + + // create new atomic + matrix = RwFrameGetLTM(m_aCarNodes[component]); + frame = RwFrameCreate(); + atomic = RpAtomicClone(atomic); + *RwFrameGetMatrix(frame) = *matrix; + RpAtomicSetFrame(atomic, frame); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + obj->AttachToRwObject((RwObject*)atomic); + + // init object + obj->m_fMass = 10.0f; + obj->m_fTurnMass = 25.0f; + obj->m_fAirResistance = 0.97f; + obj->m_fElasticity = 0.1f; + obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f; + obj->ObjectCreatedBy = TEMP_OBJECT; + obj->SetIsStatic(false); + obj->bIsPickup = false; + obj->bUseVehicleColours = true; + obj->m_colour1 = m_currentColour1; + obj->m_colour2 = m_currentColour2; + + // life time - the more objects the are, the shorter this one will live + CObject::nNoTempObjects++; + if(CObject::nNoTempObjects > 20) + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000/5.0f; + else if(CObject::nNoTempObjects > 10) + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000/2.0f; + else + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000; + + obj->m_vecMoveSpeed = m_vecMoveSpeed; + if(obj->m_vecMoveSpeed.z > 0.0f){ + obj->m_vecMoveSpeed.z *= 1.5f; + }else if(GetUp().z > 0.0f && + (component == COMPGROUP_BONNET || component == COMPGROUP_BOOT || component == CAR_WINDSCREEN)){ + obj->m_vecMoveSpeed.z *= -1.5f; + obj->m_vecMoveSpeed.z += 0.04f; + }else{ + obj->m_vecMoveSpeed.z *= 0.25f; + } + obj->m_vecMoveSpeed.x *= 0.75f; + obj->m_vecMoveSpeed.y *= 0.75f; + + obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f; + + // push component away from car + CVector dist = obj->GetPosition() - GetPosition(); + dist.Normalise(); + if(component == COMPGROUP_BONNET || component == COMPGROUP_BOOT || component == CAR_WINDSCREEN){ + // push these up some + dist += GetUp(); + if(GetUp().z > 0.0f){ + // simulate fast upward movement if going fast + float speed = CVector2D(m_vecMoveSpeed).Magnitude(); + obj->GetMatrix().Translate(GetUp()*speed); + } + } + obj->ApplyMoveForce(dist); + + if(type == COMPGROUP_WHEEL){ + obj->m_fTurnMass = 5.0f; + obj->m_vecTurnSpeed.x = 0.5f; + obj->m_fAirResistance = 0.99f; + } + + if(CCollision::ProcessColModels(obj->GetMatrix(), *obj->GetColModel(), + this->GetMatrix(), *this->GetColModel(), + aTempPedColPts, nil, nil) > 0) + obj->m_pCollidingEntity = this; + + if(bRenderScorched) + obj->bRenderScorched = true; + + CWorld::Add(obj); + + return obj; +} + +CObject* +CAutomobile::RemoveBonnetInPedCollision(void) +{ + CObject *obj; + + if(Damage.GetDoorStatus(DOOR_BONNET) == DOOR_STATUS_SWINGING && + Doors[DOOR_BONNET].RetAngleWhenOpen()*0.4f < Doors[DOOR_BONNET].m_fAngle){ +#ifdef FIX_BUGS + obj = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_BONNET); +#else + obj = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_DOOR); +#endif + // make both doors invisible on car + SetComponentVisibility(m_aCarNodes[CAR_BONNET], ATOMIC_FLAG_NONE); + Damage.SetDoorStatus(DOOR_BONNET, DOOR_STATUS_MISSING); + return obj; + } + return nil; +} + +void +CAutomobile::SetPanelDamage(int32 component, ePanels panel, bool noFlyingComponents) +{ + int status = Damage.GetPanelStatus(panel); + if(m_aCarNodes[component] == nil) + return; + if(status == PANEL_STATUS_SMASHED1){ + // show damaged part + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM); + }else if(status == PANEL_STATUS_MISSING){ + if(!noFlyingComponents) + SpawnFlyingComponent(component, COMPGROUP_PANEL); + // hide both + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_NONE); + } +} + +void +CAutomobile::SetBumperDamage(int32 component, ePanels panel, bool noFlyingComponents) +{ + int status = Damage.GetPanelStatus(panel); + if(m_aCarNodes[component] == nil){ + printf("Trying to damage component %d of %s\n", + component, CModelInfo::GetModelInfo(GetModelIndex())->GetModelName()); + return; + } + if(status == PANEL_STATUS_SMASHED1){ + // show damaged part + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM); + }else if(status == PANEL_STATUS_MISSING){ + if(!noFlyingComponents) + SpawnFlyingComponent(component, COMPGROUP_BUMPER); + // hide both + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_NONE); + } +} + +void +CAutomobile::SetDoorDamage(int32 component, eDoors door, bool noFlyingComponents) +{ + int status = Damage.GetDoorStatus(door); + if(m_aCarNodes[component] == nil){ + printf("Trying to damage component %d of %s\n", + component, CModelInfo::GetModelInfo(GetModelIndex())->GetModelName()); + return; + } + + if(door == DOOR_BOOT && status == DOOR_STATUS_SWINGING && pHandling->Flags & HANDLING_NOSWING_BOOT){ + Damage.SetDoorStatus(DOOR_BOOT, DOOR_STATUS_MISSING); + status = DOOR_STATUS_MISSING; + } + + switch(status){ + case DOOR_STATUS_SMASHED: + // show damaged part + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM); + break; + case DOOR_STATUS_SWINGING: + // turn off angle cull for swinging doors + RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::SetAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL); + break; + case DOOR_STATUS_MISSING: + if(!noFlyingComponents){ + if(door == DOOR_BONNET) + SpawnFlyingComponent(component, COMPGROUP_BONNET); + else if(door == DOOR_BOOT) + SpawnFlyingComponent(component, COMPGROUP_BOOT); + else + SpawnFlyingComponent(component, COMPGROUP_DOOR); + } + // hide both + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_NONE); + break; + } +} + + +static RwObject* +SetVehicleAtomicVisibilityCB(RwObject *object, void *data) +{ + uint32 flags = (uint32)(uintptr)data; + RpAtomic *atomic = (RpAtomic*)object; + if((CVisibilityPlugins::GetAtomicId(atomic) & (ATOMIC_FLAG_OK|ATOMIC_FLAG_DAM)) == flags) + RpAtomicSetFlags(atomic, rpATOMICRENDER); + else + RpAtomicSetFlags(atomic, 0); + return object; +} + +void +CAutomobile::SetComponentVisibility(RwFrame *frame, uint32 flags) +{ + HideAllComps(); + bIsDamaged = true; + RwFrameForAllObjects(frame, SetVehicleAtomicVisibilityCB, (void*)flags); +} + +void +CAutomobile::SetupModelNodes(void) +{ + int i; + for(i = 0; i < NUM_CAR_NODES; i++) + m_aCarNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), m_aCarNodes); +} + +void +CAutomobile::SetTaxiLight(bool light) +{ + bTaxiLight = light; +} + +bool +CAutomobile::GetAllWheelsOffGround(void) +{ + return m_nDriveWheelsOnGround == 0; +} + +void +CAutomobile::HideAllComps(void) +{ + // empty +} + +void +CAutomobile::ShowAllComps(void) +{ + // empty +} + +void +CAutomobile::ReduceHornCounter(void) +{ + if(m_nCarHornTimer != 0) + m_nCarHornTimer--; +} + +void +CAutomobile::SetAllTaxiLights(bool set) +{ + m_sAllTaxiLights = set; +} + +#ifdef COMPATIBLE_SAVES +void +CAutomobile::Save(uint8*& buf) +{ + CVehicle::Save(buf); + WriteSaveBuf(buf, Damage); + ZeroSaveBuf(buf, 800 - sizeof(CDamageManager)); +} + +void +CAutomobile::Load(uint8*& buf) +{ + CVehicle::Load(buf); + ReadSaveBuf(&Damage, buf); + SkipSaveBuf(buf, 800 - sizeof(CDamageManager)); + SetupDamageAfterLoad(); +} +#endif diff --git a/src/vehicles/Automobile.h b/src/vehicles/Automobile.h new file mode 100644 index 0000000..a5bee22 --- /dev/null +++ b/src/vehicles/Automobile.h @@ -0,0 +1,204 @@ +#pragma once + +#include "Vehicle.h" +#include "DamageManager.h" +#include "Door.h" + +class CObject; + +enum eCarNodes +{ + CAR_WHEEL_RF = 1, + CAR_WHEEL_RM, + CAR_WHEEL_RB, + CAR_WHEEL_LF, + CAR_WHEEL_LM, + CAR_WHEEL_LB, + CAR_BUMP_FRONT, + CAR_BUMP_REAR, + CAR_WING_RF, + CAR_WING_RR, + CAR_DOOR_RF, + CAR_DOOR_RR, + CAR_WING_LF, + CAR_WING_LR, + CAR_DOOR_LF, + CAR_DOOR_LR, + CAR_BONNET, + CAR_BOOT, + CAR_WINDSCREEN, + NUM_CAR_NODES, +}; + +enum { + CARWHEEL_FRONT_LEFT, + CARWHEEL_REAR_LEFT, + CARWHEEL_FRONT_RIGHT, + CARWHEEL_REAR_RIGHT +}; + +enum eBombType +{ + CARBOMB_NONE, + CARBOMB_TIMED, + CARBOMB_ONIGNITION, + CARBOMB_REMOTE, + CARBOMB_TIMEDACTIVE, + CARBOMB_ONIGNITIONACTIVE, +}; + +enum { + CAR_DOOR_FLAG_UNKNOWN = 0x0, + CAR_DOOR_FLAG_LF = 0x1, + CAR_DOOR_FLAG_LR = 0x2, + CAR_DOOR_FLAG_RF = 0x4, + CAR_DOOR_FLAG_RR = 0x8 +}; + +class CAutomobile : public CVehicle +{ +public: + // 0x288 + CDamageManager Damage; + CDoor Doors[6]; + RwFrame *m_aCarNodes[NUM_CAR_NODES]; + CColPoint m_aWheelColPoints[4]; + float m_aSuspensionSpringRatio[4]; + float m_aSuspensionSpringRatioPrev[4]; + float m_aWheelTimer[4]; // set to 4.0 when wheel is touching ground, then decremented + float m_auto_unused1; + bool m_aWheelSkidmarkMuddy[4]; + bool m_aWheelSkidmarkBloody[4]; + float m_aWheelRotation[4]; + float m_aWheelPosition[4]; + float m_aWheelSpeed[4]; + uint8 m_auto_unused2; + uint8 m_bombType : 3; + uint8 bTaxiLight : 1; + uint8 bDriverLastFrame : 1; // for bombs + uint8 bFixedColour : 1; + uint8 bBigWheels : 1; + uint8 bWaterTight : 1; // no damage for non-player peds + uint8 bNotDamagedUpsideDown : 1; + uint8 bMoreResistantToDamage : 1; + CEntity *m_pBombRigger; + int16 m_auto_unk1; + uint16 m_hydraulicState; + uint32 m_nBusDoorTimerEnd; + uint32 m_nBusDoorTimerStart; + float m_aSuspensionSpringLength[4]; + float m_aSuspensionLineLength[4]; + float m_fHeightAboveRoad; + float m_fTraction; + float m_fVelocityChangeForAudio; + float m_randomValues[6]; // used for what? + float m_fFireBlowUpTimer; + CPhysical *m_aGroundPhysical[4]; // physicals touching wheels + CVector m_aGroundOffset[4]; // from ground object to colpoint + CEntity *m_pSetOnFireEntity; + float m_weaponDoorTimerLeft; // still don't know what exactly this is + float m_weaponDoorTimerRight; + float m_fCarGunLR; + float m_fCarGunUD; + float m_fPropellerRotation; + uint8 stuff4[4]; + uint8 m_nWheelsOnGround; + uint8 m_nDriveWheelsOnGround; + uint8 m_nDriveWheelsOnGroundPrev; + float m_fGasPedalAudio; + tWheelState m_aWheelState[4]; + + static bool m_sAllTaxiLights; + + CAutomobile(int32 id, uint8 CreatedBy); + + // from CEntity + void SetModelIndex(uint32 id); + void ProcessControl(void); + void Teleport(CVector v); + void PreRender(void); + void Render(void); + + // from CPhysical + int32 ProcessEntityCollision(CEntity *ent, CColPoint *colpoints); + + // from CVehicle + void ProcessControlInputs(uint8); + void GetComponentWorldPosition(int32 component, CVector &pos); + bool IsComponentPresent(int32 component); + void SetComponentRotation(int32 component, CVector rotation); + void OpenDoor(int32 component, eDoors door, float openRatio); + void ProcessOpenDoor(uint32, uint32, float); + bool IsDoorReady(eDoors door); + bool IsDoorFullyOpen(eDoors door); + bool IsDoorClosed(eDoors door); + bool IsDoorMissing(eDoors door); + void RemoveRefsToVehicle(CEntity *ent); + void BlowUpCar(CEntity *ent); + bool SetUpWheelColModel(CColModel *colModel); + void BurstTyre(uint8 tyre); + bool IsRoomForPedToLeaveCar(uint32 component, CVector *doorOffset); + float GetHeightAboveRoad(void); + void PlayCarHorn(void); + + void FireTruckControl(void); + void TankControl(void); + void HydraulicControl(void); + void VehicleDamage(float impulse, uint16 damagedPiece); + void ProcessBuoyancy(void); + void DoDriveByShootings(void); + int32 RcbanditCheckHitWheels(void); + int32 RcbanditCheck1CarWheels(CPtrList &list); + void PlaceOnRoadProperly(void); + void dmgDrawCarCollidingParticles(const CVector &pos, float amount); + void AddDamagedVehicleParticles(void); + int32 AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed); + void PlayHornIfNecessary(void); + void ResetSuspension(void); + void SetupSuspensionLines(void); + void ScanForCrimes(void); + void BlowUpCarsInPath(void); + bool HasCarStoppedBecauseOfLight(void); + void SetBusDoorTimer(uint32 timer, uint8 type); + void ProcessAutoBusDoors(void); + void ProcessSwingingDoor(int32 component, eDoors door); + void SetupDamageAfterLoad(void); + CObject *SpawnFlyingComponent(int32 component, uint32 type); + CObject *RemoveBonnetInPedCollision(void); + void SetPanelDamage(int32 component, ePanels panel, bool noFlyingComponents = false); + void SetBumperDamage(int32 component, ePanels panel, bool noFlyingComponents = false); + void SetDoorDamage(int32 component, eDoors door, bool noFlyingComponents = false); + + void Fix(void); + void SetComponentVisibility(RwFrame *frame, uint32 flags); + void SetupModelNodes(void); + void SetTaxiLight(bool light); + bool GetAllWheelsOffGround(void); + void HideAllComps(void); + void ShowAllComps(void); + void ReduceHornCounter(void); +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + static const uint32 nSaveStructSize; + + static void SetAllTaxiLights(bool set); +}; + +VALIDATE_SIZE(CAutomobile, 0x5A8); + +inline uint8 GetCarDoorFlag(int32 carnode) { + switch (carnode) { + case CAR_DOOR_LF: + return CAR_DOOR_FLAG_LF; + case CAR_DOOR_LR: + return CAR_DOOR_FLAG_LR; + case CAR_DOOR_RF: + return CAR_DOOR_FLAG_RF; + case CAR_DOOR_RR: + return CAR_DOOR_FLAG_RR; + default: + return CAR_DOOR_FLAG_UNKNOWN; + } +} diff --git a/src/vehicles/Bike.h b/src/vehicles/Bike.h new file mode 100644 index 0000000..85ff211 --- /dev/null +++ b/src/vehicles/Bike.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Vehicle.h" + +// some miami bike leftovers + +enum eBikeNodes { + BIKE_NODE_NONE, + BIKE_CHASSIS, + BIKE_FORKS_FRONT, + BIKE_FORKS_REAR, + BIKE_WHEEL_FRONT, + BIKE_WHEEL_REAR, + BIKE_MUDGUARD, + BIKE_HANDLEBARS, + BIKE_NUM_NODES +}; + +class CBike : public CVehicle +{ +public: + RwFrame *m_aBikeNodes[BIKE_NUM_NODES]; // assuming + uint8 unk1[96]; + AnimationId m_bikeSitAnimation; + uint8 unk2[180]; + float m_aSuspensionSpringRatio[4]; + + /* copied from VC, one of the floats here is gone, assuming m_bike_unused1 */ + float m_aSuspensionSpringRatioPrev[4]; + float m_aWheelTimer[4]; + //float m_bike_unused1; + int m_aWheelSkidmarkType[2]; + bool m_aWheelSkidmarkBloody[2]; + bool m_aWheelSkidmarkUnk[2]; + float m_aWheelRotation[2]; + float m_aWheelSpeed[2]; + float m_aWheelPosition[2]; + float m_aWheelBasePosition[2]; + float m_aSuspensionSpringLength[4]; + float m_aSuspensionLineLength[4]; + float m_fHeightAboveRoad; + /**/ + + float m_fTraction; +}; \ No newline at end of file diff --git a/src/vehicles/Boat.cpp b/src/vehicles/Boat.cpp new file mode 100644 index 0000000..65cdd8c --- /dev/null +++ b/src/vehicles/Boat.cpp @@ -0,0 +1,952 @@ +#include "common.h" + +#include "main.h" +#include "General.h" +#include "Timecycle.h" +#include "HandlingMgr.h" +#include "CarCtrl.h" +#include "RwHelper.h" +#include "ModelIndices.h" +#include "VisibilityPlugins.h" +#include "DMAudio.h" +#include "Camera.h" +#include "Darkel.h" +#include "Explosion.h" +#include "Particle.h" +#include "WaterLevel.h" +#include "Floater.h" +#include "World.h" +#include "Pools.h" +#include "Pad.h" +#include "Boat.h" +#include "SaveBuf.h" + +#define INVALID_ORIENTATION (-9999.99f) + +float MAX_WAKE_LENGTH = 50.0f; +float MIN_WAKE_INTERVAL = 1.0f; +float WAKE_LIFETIME = 400.0f; + +float fShapeLength = 0.4f; +float fShapeTime = 0.05f; +float fRangeMult = 0.75f; +float fTimeMult = 1.0f/WAKE_LIFETIME; + +CBoat *CBoat::apFrameWakeGeneratingBoats[4]; + +const uint32 CBoat::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 1156; +#else + sizeof(CBoat); +#endif + +CBoat::CBoat(int mi, uint8 owner) : CVehicle(owner) +{ + CVehicleModelInfo *minfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(mi); + m_vehType = VEHICLE_TYPE_BOAT; + m_fAccelerate = 0.0f; + m_fBrake = 0.0f; + m_fSteeringLeftRight = 0.0f; + m_nPadID = 0; + m_fMovingRotation = 0.0f; + SetModelIndex(mi); + + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)minfo->m_handlingId); + minfo->ChooseVehicleColour(m_currentColour1, m_currentColour2); + + m_fMass = pHandling->fMass; + m_fTurnMass = pHandling->fTurnMass / 2.0f; + m_vecCentreOfMass = pHandling->CentreOfMass; + m_fAirResistance = pHandling->Dimension.x * pHandling->Dimension.z / m_fMass; + m_fElasticity = 0.1f; + m_fBuoyancy = pHandling->fBuoyancy; + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + m_fBrakePedal = 0.0f; + + m_fThrustZ = 0.25f; + m_fThrustY = 0.35f; + m_vecMoveRes = CVector(0.7f, 0.998f, 0.999f); + m_vecTurnRes = CVector(0.85f, 0.96f, 0.96f); + m_boat_unused3 = false; + + m_fVolumeUnderWater = 7.0f; + m_fPrevVolumeUnderWater = 7.0f; + m_vecBuoyancePoint = CVector(0.0f, 0.0f, 0.0f); + + m_nDeltaVolumeUnderWater = 0; + bBoatInWater = true; + bPropellerInWater = true; + + bIsInWater = true; + + m_boat_unused2 = 0; + m_bIsAnchored = true; + m_fOrientation = INVALID_ORIENTATION; + bTouchingWater = true; + m_fDamage = 0.0f; + m_pSetOnFireEntity = nil; + m_nNumWakePoints = 0; + + for (int16 i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++) + m_afWakePointLifeTime[i] = 0.0f; + + m_nAmmoInClip = 20; +} + +void +CBoat::SetModelIndex(uint32 id) +{ + CEntity::SetModelIndex(id); + SetupModelNodes(); +} + +void +CBoat::GetComponentWorldPosition(int32 component, CVector &pos) +{ + pos = *RwMatrixGetPos(RwFrameGetLTM(m_aBoatNodes[component])); +} + +void +CBoat::ProcessControl(void) +{ + if(m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory) + return; + + bool onLand = m_fDamageImpulse > 0.0f && m_vecDamageNormal.z > 0.1f; + + PruneWakeTrail(); + + int r, g, b; + RwRGBA splashColor, jetColor; + r = 114.75f*(CTimeCycle::GetAmbientRed() + 0.5f*CTimeCycle::GetDirectionalRed()); + g = 114.75f*(CTimeCycle::GetAmbientGreen() + 0.5f*CTimeCycle::GetDirectionalGreen()); + b = 114.75f*(CTimeCycle::GetAmbientBlue() + 0.5f*CTimeCycle::GetDirectionalBlue()); + r = Clamp(r, 0, 255); + g = Clamp(g, 0, 255); + b = Clamp(b, 0, 255); + splashColor.red = r; + splashColor.green = g; + splashColor.blue = b; + splashColor.alpha = CGeneral::GetRandomNumberInRange(128, 150); + + r = 242.25f*(CTimeCycle::GetAmbientRed() + 0.5f*CTimeCycle::GetDirectionalRed()); + g = 242.25f*(CTimeCycle::GetAmbientGreen() + 0.5f*CTimeCycle::GetDirectionalGreen()); + b = 242.25f*(CTimeCycle::GetAmbientBlue() + 0.5f*CTimeCycle::GetDirectionalBlue()); + r = Clamp(r, 0, 255); + g = Clamp(g, 0, 255); + b = Clamp(b, 0, 255); + jetColor.red = r; + jetColor.green = g; + jetColor.blue = b; + jetColor.alpha = CGeneral::GetRandomNumberInRange(96, 128); + + CGeneral::GetRandomNumber(); // unused + + ProcessCarAlarm(); + + switch(GetStatus()){ + case STATUS_PLAYER: + m_bIsAnchored = false; + m_fOrientation = INVALID_ORIENTATION; + ProcessControlInputs(0); + if(GetModelIndex() == MI_PREDATOR) + DoFixedMachineGuns(); + break; + case STATUS_SIMPLE: + m_bIsAnchored = false; + m_fOrientation = INVALID_ORIENTATION; + CPhysical::ProcessControl(); + bBoatInWater = true; + bPropellerInWater = true; + bIsInWater = true; + return; + case STATUS_PHYSICS: + m_bIsAnchored = false; + m_fOrientation = INVALID_ORIENTATION; + CCarCtrl::SteerAIBoatWithPhysics(this); + break; + case STATUS_ABANDONED: + case STATUS_WRECKED: + bBoatInWater = true; + bPropellerInWater = true; + bIsInWater = true; + m_fSteerAngle = 0.0; + bIsHandbrakeOn = false; + m_fBrakePedal = 0.5f; + m_fGasPedal = 0.0f; + if((GetPosition() - CWorld::Players[CWorld::PlayerInFocus].GetPos()).Magnitude() > 150.0f){ + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + return; + } + break; + default: break; + } + + float collisionDamage = pHandling->fCollisionDamageMultiplier * m_fDamageImpulse; +#ifdef FIX_BUGS + if (collisionDamage > 25.0f && GetStatus() != STATUS_WRECKED && m_fHealth >= 150.0f && !bCollisionProof) { +#else + if(collisionDamage > 25.0f && GetStatus() != STATUS_WRECKED && m_fHealth >= 150.0f){ +#endif + float prevHealth = m_fHealth; + if(this == FindPlayerVehicle()){ + if(bTakeLessDamage) + m_fHealth -= (collisionDamage-25.0f)/6.0f; + else + m_fHealth -= (collisionDamage-25.0f)/2.0f; + }else{ + if(collisionDamage > 60.0f && pDriver) + pDriver->Say(SOUND_PED_ANNOYED_DRIVER); + if(bTakeLessDamage) + m_fHealth -= (collisionDamage-25.0f)/12.0f; + else + m_fHealth -= (collisionDamage-25.0f)/4.0f; + } + + if(m_fHealth <= 0.0f && prevHealth > 0.0f){ + m_fHealth = 1.0f; + m_pSetOnFireEntity = m_pDamageEntity; + } + } + + // Damage particles + if(m_fHealth <= 600.0f && GetStatus() != STATUS_WRECKED && + Abs(GetPosition().x - TheCamera.GetPosition().x) < 200.0f && + Abs(GetPosition().y - TheCamera.GetPosition().y) < 200.0f){ + float speedSq = m_vecMoveSpeed.MagnitudeSqr(); + CVector smokeDir = 0.8f*m_vecMoveSpeed; + CVector smokePos; + switch(GetModelIndex()){ + case MI_SPEEDER: + smokePos = CVector(0.4f, -2.4f, 0.8f); + smokeDir += 0.05f*GetRight(); + smokeDir.z += 0.2f*m_vecMoveSpeed.z; + break; + case MI_REEFER: + smokePos = CVector(2.0f, -1.0f, 0.5f); + smokeDir += 0.07f*GetRight(); + break; + case MI_PREDATOR: + default: + smokePos = CVector(-1.5f, -0.5f, 1.2f); + smokeDir += -0.08f*GetRight(); + break; + } + + smokePos = GetMatrix() * smokePos; + + // On fire + if(m_fHealth < 150.0f){ + CParticle::AddParticle(PARTICLE_CARFLAME, smokePos, + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(2.25f/200.0f, 0.09f)), + nil, 0.9f); + CVector smokePos2 = smokePos; + smokePos2.x += CGeneral::GetRandomNumberInRange(-2.25f/4.0f, 2.25f/4.0f); + smokePos2.y += CGeneral::GetRandomNumberInRange(-2.25f/4.0f, 2.25f/4.0f); + smokePos2.z += CGeneral::GetRandomNumberInRange(2.25f/4.0f, 2.25f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, smokePos2, CVector(0.0f, 0.0f, 0.0f)); + + m_fDamage += CTimer::GetTimeStepInMilliseconds(); + if(m_fDamage > 5000.0f) + BlowUpCar(m_pSetOnFireEntity); + } + + if(speedSq < 0.25f && (CTimer::GetFrameCounter() + m_randomSeed) & 1) + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, smokePos, smokeDir); + if(speedSq < 0.25f && m_fHealth <= 350.0f) + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, smokePos, 1.25f*smokeDir); + } + + CPhysical::ProcessControl(); + + CVector buoyanceImpulse(0.0f, 0.0f, 0.0f); + CVector buoyancePoint(0.0f, 0.0f, 0.0f); + if(mod_Buoyancy.ProcessBuoyancy(this, pHandling->fBuoyancy, &buoyancePoint, &buoyanceImpulse)){ + // Process boat in water + if(0.1f * m_fMass * GRAVITY*CTimer::GetTimeStep() < buoyanceImpulse.z){ + bBoatInWater = true; + bIsInWater = true; + }else{ + bBoatInWater = false; + bIsInWater = false; + } + + m_fVolumeUnderWater = mod_Buoyancy.m_volumeUnderWater; + m_vecBuoyancePoint = buoyancePoint; + ApplyMoveForce(buoyanceImpulse); + if(!onLand) + ApplyTurnForce(buoyanceImpulse, buoyancePoint); + + if(!onLand && bBoatInWater && GetUp().z > 0.0f){ + float impulse; + if(m_fGasPedal > 0.05f) + impulse = m_vecMoveSpeed.MagnitudeSqr()*pHandling->fSuspensionForceLevel*buoyanceImpulse.z*CTimer::GetTimeStep()*0.5f*m_fGasPedal; + else + impulse = 0.0f; + impulse = Min(impulse, GRAVITY*pHandling->fSuspensionDampingLevel*m_fMass*CTimer::GetTimeStep()); + ApplyMoveForce(impulse*GetUp()); + ApplyTurnForce(impulse*GetUp(), buoyancePoint - pHandling->fSuspensionBias*GetForward()); + } + + // Handle boat moving forward + if(Abs(m_fGasPedal) > 0.05f || m_vecMoveSpeed.Magnitude2D() > 0.01f){ + if(bBoatInWater) + AddWakePoint(GetPosition()); + + float steerFactor = 1.0f - DotProduct(m_vecMoveSpeed, GetForward()); + if (GetModelIndex() == MI_GHOST) + steerFactor = 1.0f - DotProduct(m_vecMoveSpeed, GetForward())*0.3f; + if(steerFactor < 0.0f) steerFactor = 0.0f; + + CVector propeller(0.0f, -pHandling->Dimension.y*m_fThrustY, -pHandling->Dimension.z*m_fThrustZ); + propeller = Multiply3x3(GetMatrix(), propeller); + CVector propellerWorld = GetPosition() + propeller; + + float steerSin = Sin(-m_fSteerAngle * steerFactor); + float steerCos = Cos(-m_fSteerAngle * steerFactor); + float waterLevel; + CWaterLevel::GetWaterLevel(propellerWorld, &waterLevel, true); + if(propellerWorld.z-0.5f < waterLevel){ + float propellerDepth = waterLevel - (propellerWorld.z - 0.5f); + if(propellerDepth > 1.0f) + propellerDepth = 1.0f; + else + propellerDepth = SQR(propellerDepth); + bPropellerInWater = true; + + if(Abs(m_fGasPedal) > 0.05f){ + CVector forceDir = Multiply3x3(GetMatrix(), CVector(-steerSin, steerCos, -Abs(m_fSteerAngle))); + CVector force = propellerDepth * m_fGasPedal * 40.0f * pHandling->Transmission.fEngineAcceleration * pHandling->fMass * forceDir; + if(force.z > 0.2f) + force.z = SQR(1.2f - force.z) + 0.2f; + if(onLand){ + if(m_fGasPedal < 0.0f){ + force.x *= 5.0f; + force.y *= 5.0f; + } + if(force.z < 0.0f) + force.z = 0.0f; + ApplyMoveForce(force * CTimer::GetTimeStep()); + }else{ + ApplyMoveForce(force * CTimer::GetTimeStep()); + ApplyTurnForce(force * CTimer::GetTimeStep(), propeller - pHandling->fTractionBias*GetUp()); + float rightForce = DotProduct(GetRight(), force); + ApplyTurnForce(-rightForce*GetRight() * CTimer::GetTimeStep(), GetUp()); + } + + // Spray some particles + CVector jetDir = -0.04f * force; + if(m_fGasPedal > 0.0f){ + if(GetStatus() == STATUS_PLAYER){ + bool cameraHack = TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || + TheCamera.WhoIsInControlOfTheCamera == CAMCONTROL_OBBE; + CVector sternPos = GetColModel()->boundingBox.min; + sternPos.x = 0.0f; + sternPos.z = 0.0f; + sternPos = Multiply3x3(GetMatrix(), sternPos); + + CVector jetPos = GetPosition() + sternPos; + if(cameraHack) + jetPos.z = 1.0f; + else + jetPos.z = 0.0f; + +#ifdef PC_PARTICLE + CVector wakePos = GetPosition() + sternPos; + wakePos.z -= 0.65f; +#else + CVector wakePos = GetPosition() + sternPos; + wakePos.z = -0.3f; +#endif + + CVector wakeDir = 0.75f * jetDir; + + CParticle::AddParticle(PARTICLE_BOAT_THRUSTJET, jetPos, jetDir, nil, 0.0f, jetColor); +#ifdef PC_PARTICLE + CParticle::AddParticle(PARTICLE_CAR_SPLASH, jetPos, 0.25f * jetDir, nil, 1.0f, splashColor, + CGeneral::GetRandomNumberInRange(0, 30), + CGeneral::GetRandomNumberInRange(0, 90), 3); +#endif + if(!cameraHack) + CParticle::AddParticle(PARTICLE_BOAT_WAKE, wakePos, wakeDir, nil, 0.0f, jetColor); + }else if((CTimer::GetFrameCounter() + m_randomSeed) & 1){ +#ifdef PC_PARTICLE + jetDir.z = 0.018f; + jetDir.x *= 0.01f; + jetDir.y *= 0.01f; + propellerWorld.z += 1.5f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, propellerWorld, jetDir, nil, 1.5f, jetColor); +#else + jetDir.z = 0.018f; + jetDir.x *= 0.03f; + jetDir.y *= 0.03f; + propellerWorld.z += 1.0f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, propellerWorld, jetDir, nil, 0.0f, jetColor); +#endif + +#ifdef PC_PARTICLE + CParticle::AddParticle(PARTICLE_CAR_SPLASH, propellerWorld, 0.1f * jetDir, nil, 0.5f, splashColor, + CGeneral::GetRandomNumberInRange(0, 30), + CGeneral::GetRandomNumberInRange(0, 90), 3); +#endif + } + } + }else if(!onLand){ + float force = 50.0f*DotProduct(m_vecMoveSpeed, GetForward()); + force = Min(force, 10.0f); + CVector propellerForce = propellerDepth * Multiply3x3(GetMatrix(), force*CVector(-steerSin, 0.0f, 0.0f)); + ApplyMoveForce(propellerForce * CTimer::GetTimeStep()*0.5f); + ApplyTurnForce(propellerForce * CTimer::GetTimeStep()*0.5f, propeller); + } + }else + bPropellerInWater = false; + } + + // Slow down or push down boat as it approaches the world limits + m_vecMoveSpeed.x = Min(m_vecMoveSpeed.x, -(GetPosition().x - 1900.0f)*0.01f); // east + m_vecMoveSpeed.x = Max(m_vecMoveSpeed.x, -(GetPosition().x - -1515.0f)*0.01f); // west + m_vecMoveSpeed.y = Min(m_vecMoveSpeed.y, -(GetPosition().y - 600.0f)*0.01f); // north + m_vecMoveSpeed.y = Max(m_vecMoveSpeed.y, -(GetPosition().y - -1900.0f)*0.01f); // south + + if(!onLand && bBoatInWater) + ApplyWaterResistance(); + + // No idea what exactly is going on here besides drag in YZ + float fx = Pow(m_vecTurnRes.x, CTimer::GetTimeStep()); + float fy = Pow(m_vecTurnRes.y, CTimer::GetTimeStep()); + float fz = Pow(m_vecTurnRes.z, CTimer::GetTimeStep()); + m_vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); // invert - to local space + // TODO: figure this out + float magic = 1.0f/(1000.0f * SQR(m_vecTurnSpeed.x) + 1.0f) * fx; + m_vecTurnSpeed.y *= fy; + m_vecTurnSpeed.z *= fz; + float forceUp = (magic - 1.0f) * m_vecTurnSpeed.x * m_fTurnMass; + m_vecTurnSpeed = Multiply3x3(GetMatrix(), m_vecTurnSpeed); // back to world + CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + ApplyTurnForce(CVector(0.0f, 0.0f, forceUp), com + GetForward()); + + m_nDeltaVolumeUnderWater = (m_fVolumeUnderWater-m_fPrevVolumeUnderWater)*10000; + + // Falling into water + if(!onLand && bBoatInWater && GetUp().z > 0.0f && m_nDeltaVolumeUnderWater > 200){ + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, m_nDeltaVolumeUnderWater); + + float speedUp = m_vecMoveSpeed.MagnitudeSqr() * m_nDeltaVolumeUnderWater * 0.0004f; + if(speedUp + m_vecMoveSpeed.z > pHandling->fBrakeDeceleration) + speedUp = pHandling->fBrakeDeceleration - m_vecMoveSpeed.z; + if(speedUp < 0.0f) speedUp = 0.0f; + float speedFwd = DotProduct(m_vecMoveSpeed, GetForward()); + speedFwd *= -m_nDeltaVolumeUnderWater * 0.01f * pHandling->fTractionLoss; + CVector speed = speedFwd*GetForward() + CVector(0.0f, 0.0f, speedUp); + CVector splashImpulse = speed * m_fMass; + ApplyMoveForce(splashImpulse); + ApplyTurnForce(splashImpulse, buoyancePoint); + } + + // Spray particles on sides of boat +#ifdef PC_PARTICLE + if(m_nDeltaVolumeUnderWater > 75) +#else + if(m_nDeltaVolumeUnderWater > 120) +#endif + { + float speed = m_vecMoveSpeed.Magnitude(); + float splash1Size = speed; + float splash2Size = float(m_nDeltaVolumeUnderWater) * 0.005f * 0.2f; + float front = 0.9f * GetColModel()->boundingBox.max.y; + if(splash1Size > 0.75f) splash1Size = 0.75f; + + CVector dir, pos; + + // right +#ifdef PC_PARTICLE + dir = -0.5f*m_vecMoveSpeed; + dir.z += 0.1f*speed; + dir += 0.5f*GetRight()*speed; + pos = front*GetForward() + 0.5f*GetRight() + GetPosition() + m_vecBuoyancePoint; + CWaterLevel::GetWaterLevel(pos, &pos.z, true); +#else + dir = 0.3f*m_vecMoveSpeed; + dir.z += 0.05f*speed; + dir += 0.5f*GetRight()*speed; + pos = (GetPosition() + m_vecBuoyancePoint) + (1.5f*GetRight()); +#endif + +#ifdef PC_PARTICLE + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, 0.75f * dir, nil, splash1Size, splashColor, + CGeneral::GetRandomNumberInRange(0, 30), + CGeneral::GetRandomNumberInRange(0, 90), 1); + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size, jetColor); +#else + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size); +#endif + + + // left +#ifdef PC_PARTICLE + dir = -0.5f*m_vecMoveSpeed; + dir.z += 0.1f*speed; + dir -= 0.5f*GetRight()*speed; + pos = front*GetForward() - 0.5f*GetRight() + GetPosition() + m_vecBuoyancePoint; + CWaterLevel::GetWaterLevel(pos, &pos.z, true); +#else + dir = 0.3f*m_vecMoveSpeed; + dir.z += 0.05f*speed; + dir -= 0.5f*GetRight()*speed; + pos = (GetPosition() + m_vecBuoyancePoint) - (1.5f*GetRight()); +#endif + +#ifdef PC_PARTICLE + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, 0.75f * dir, nil, splash1Size, splashColor, + CGeneral::GetRandomNumberInRange(0, 30), + CGeneral::GetRandomNumberInRange(0, 90), 1); + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size, jetColor); +#else + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size); +#endif + } + + m_fPrevVolumeUnderWater = m_fVolumeUnderWater; + }else{ + bBoatInWater = false; + bIsInWater = false; + } + + if(m_bIsAnchored){ + m_vecMoveSpeed.x = 0.0f; + m_vecMoveSpeed.y = 0.0f; + + if(m_fOrientation == INVALID_ORIENTATION){ + m_fOrientation = GetForward().Heading(); + }else{ + // is this some inlined CPlaceable method? + CVector pos = GetPosition(); + GetMatrix().RotateZ(m_fOrientation - GetForward().Heading()); + GetMatrix().SetTranslateOnly(pos); + } + } + + ProcessDelayedExplosion(); +} + +void +CBoat::ProcessControlInputs(uint8 pad) +{ + m_nPadID = pad; + if(m_nPadID > 3) + m_nPadID = 3; + + m_fBrake += (CPad::GetPad(pad)->GetBrake()/255.0f - m_fBrake)*0.1f; + m_fBrake = Clamp(m_fBrake, 0.0f, 1.0f); + + if(m_fBrake < 0.05f){ + m_fBrake = 0.0f; + m_fAccelerate += (CPad::GetPad(pad)->GetAccelerate()/255.0f - m_fAccelerate)*0.1f; + m_fAccelerate = Clamp(m_fAccelerate, 0.0f, 1.0f); + }else + m_fAccelerate = -m_fBrake*0.2f; + + m_fSteeringLeftRight += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteeringLeftRight)*0.2f; + m_fSteeringLeftRight = Clamp(m_fSteeringLeftRight, -1.0f, 1.0f); + + float steeringSq = m_fSteeringLeftRight < 0.0f ? -SQR(m_fSteeringLeftRight) : SQR(m_fSteeringLeftRight); + m_fSteerAngle = pHandling->fSteeringLock * DEGTORAD(steeringSq); + m_fGasPedal = m_fAccelerate; +} + +void +CBoat::ApplyWaterResistance(void) +{ + float fwdSpeed = DotProduct(GetMoveSpeed(), GetForward()); + // TODO: figure out how this works + float resistance = 0.001f * SQR(m_fVolumeUnderWater) * m_fMass; + float magic = (SQR(fwdSpeed) + 0.05f) * resistance + 1.0f; + magic = Abs(magic); + float fx = Pow(m_vecMoveRes.x/magic, 0.5f*CTimer::GetTimeStep()); + float fy = Pow(m_vecMoveRes.y/magic, 0.5f*CTimer::GetTimeStep()); + float fz = Pow(m_vecMoveRes.z/magic, 0.5f*CTimer::GetTimeStep()); + + m_vecMoveSpeed = Multiply3x3(m_vecMoveSpeed, GetMatrix()); // invert - to local space + m_vecMoveSpeed.x *= fx; + m_vecMoveSpeed.y *= fy; + m_vecMoveSpeed.z *= fz; + float force = (fy - 1.0f) * m_vecMoveSpeed.y * m_fMass; + m_vecMoveSpeed = Multiply3x3(GetMatrix(), m_vecMoveSpeed); // back to world + + ApplyTurnForce(force*GetForward(), -GetUp()); + + if(m_vecMoveSpeed.z > 0.0f) + m_vecMoveSpeed.z *= fz; + else + m_vecMoveSpeed.z *= (1.0f - fz)*0.5f + fz; +} + +RwObject* +GetBoatAtomicObjectCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + assert(RwObjectGetType(object) == rpATOMIC); + if(RpAtomicGetFlags(atomic) & rpATOMICRENDER) + *(RpAtomic**)data = atomic; + return object; + + +} + +void +CBoat::BlowUpCar(CEntity *culprit) +{ + RpAtomic *atomic; + RwFrame *frame; + RwMatrix *matrix; + CObject *obj; + + if(!bCanBeDamaged) + return; + + // explosion pushes vehicle up + m_vecMoveSpeed.z += 0.13f; + SetStatus(STATUS_WRECKED); + bRenderScorched = true; + + m_fHealth = 0.0; + m_nBombTimer = 0; + TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z); + + if(this == FindPlayerVehicle()) + FindPlayerPed()->m_fHealth = 0.0f; // kill player + if(pDriver){ + CDarkel::RegisterKillByPlayer(pDriver, WEAPONTYPE_EXPLOSION); + pDriver->SetDead(); + pDriver->FlagToDestroyWhenNextProcessed(); + } + + bEngineOn = false; + bLightsOn = false; + ChangeLawEnforcerState(false); + + CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR, GetPosition(), 0); + if(m_aBoatNodes[BOAT_MOVING] == nil) + return; + + // much like CAutomobile::SpawnFlyingComponent from here on + + atomic = nil; + RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic); + if(atomic == nil) + return; + + obj = new CObject(); + if(obj == nil) + return; + + obj->SetModelIndexNoCreate(MI_CAR_WHEEL); + + // object needs base model + obj->RefModelInfo(GetModelIndex()); + + // create new atomic + matrix = RwFrameGetLTM(m_aBoatNodes[BOAT_MOVING]); + frame = RwFrameCreate(); + atomic = RpAtomicClone(atomic); + *RwFrameGetMatrix(frame) = *matrix; + RpAtomicSetFrame(atomic, frame); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + obj->AttachToRwObject((RwObject*)atomic); + + // init object + obj->m_fMass = 10.0f; + obj->m_fTurnMass = 25.0f; + obj->m_fAirResistance = 0.99f; + obj->m_fElasticity = 0.1f; + obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f; + obj->ObjectCreatedBy = TEMP_OBJECT; + obj->SetIsStatic(false); + obj->bIsPickup = false; + + // life time + CObject::nNoTempObjects++; + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000; + + obj->m_vecMoveSpeed = m_vecMoveSpeed; + if(GetUp().z > 0.0f) + obj->m_vecMoveSpeed.z = 0.3f; + else + obj->m_vecMoveSpeed.z = 0.0f; + + obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f; + obj->m_vecTurnSpeed.x = 0.5f; + + // push component away from boat + CVector dist = obj->GetPosition() - GetPosition(); + dist.Normalise(); + if(GetUp().z > 0.0f) + dist += GetUp(); + obj->GetMatrix().GetPosition() += dist; + + CWorld::Add(obj); + + atomic = nil; + RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic); + if(atomic) + RpAtomicSetFlags(atomic, 0); +} + +RwIm3DVertex KeepWaterOutVertices[4]; +RwImVertexIndex KeepWaterOutIndices[6]; + +void +CBoat::Render() +{ + CMatrix matrix; + + if (m_aBoatNodes[BOAT_MOVING] != nil) { + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_MOVING])); + + CVector pos = matrix.GetPosition(); + matrix.SetRotateZ(m_fMovingRotation); + matrix.Translate(pos); + + matrix.UpdateRW(); + if (CVehicle::bWheelsOnlyCheat) { + RpAtomicRender((RpAtomic*)GetFirstObject(m_aBoatNodes[BOAT_MOVING])); + } + } + m_fMovingRotation += 0.05f; + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->SetVehicleColour(m_currentColour1, m_currentColour2); + if (!CVehicle::bWheelsOnlyCheat) + CEntity::Render(); +#ifdef NEW_RENDERER + if(!gbNewRenderer) +#endif + RenderWaterOutPolys(); // not separate function in III +} + +void +CBoat::RenderWaterOutPolys(void) +{ + KeepWaterOutIndices[0] = 0; + KeepWaterOutIndices[1] = 2; + KeepWaterOutIndices[2] = 1; + KeepWaterOutIndices[3] = 1; + KeepWaterOutIndices[4] = 2; + KeepWaterOutIndices[5] = 3; + RwIm3DVertexSetRGBA(&KeepWaterOutVertices[0], 255, 255, 255, 255); + RwIm3DVertexSetRGBA(&KeepWaterOutVertices[1], 255, 255, 255, 255); + RwIm3DVertexSetRGBA(&KeepWaterOutVertices[2], 255, 255, 255, 255); + RwIm3DVertexSetRGBA(&KeepWaterOutVertices[3], 255, 255, 255, 255); + switch (GetModelIndex()) { + case MI_SPEEDER: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.15f, 3.61f, 1.03f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.15f, 3.61f, 1.03f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.15f, 0.06f, 1.03f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.15f, 0.06f, 1.03f); + break; + case MI_REEFER: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.9f, 2.83f, 1.0f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.9f, 2.83f, 1.0f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.66f, -4.48f, 0.83f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.66f, -4.48f, 0.83f); + break; + case MI_PREDATOR: + default: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.45f, 1.9f, 0.96f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.45f, 1.9f, 0.96f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.45f, -3.75f, 0.96f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.45f, -3.75f, 0.96f); + break; + } + KeepWaterOutVertices[0].u = 0.0f; + KeepWaterOutVertices[0].v = 0.0f; + KeepWaterOutVertices[1].u = 1.0f; + KeepWaterOutVertices[1].v = 0.0f; + KeepWaterOutVertices[2].u = 0.0f; + KeepWaterOutVertices[2].v = 1.0f; + KeepWaterOutVertices[3].u = 1.0f; + KeepWaterOutVertices[3].v = 1.0f; +#ifdef NEW_RENDERER + if(!gbNewRenderer) +#endif +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpWaterRaster); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); +} + if (!CVehicle::bWheelsOnlyCheat && RwIm3DTransform(KeepWaterOutVertices, 4, GetMatrix().m_attachment, rwIM3D_VERTEXUV)) { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, KeepWaterOutIndices, 6); + RwIm3DEnd(); + } +#ifdef NEW_RENDERER + if(!gbNewRenderer) +#endif +{ + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); +} +} + +void +CBoat::Teleport(CVector v) +{ + CWorld::Remove(this); + SetPosition(v); + SetOrientation(0.0f, 0.0f, 0.0f); + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + CWorld::Add(this); +} + +bool +CBoat::IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats) +{ + uint8 numVerts = 0; + + if ( apFrameWakeGeneratingBoats[0] == NULL ) + return false; + + for ( int32 i = 0; i < 4; i++ ) + { + CBoat *pBoat = apFrameWakeGeneratingBoats[i]; + if ( !pBoat ) + break; + + for ( int j = 0; j < pBoat->m_nNumWakePoints; j++ ) + { + float fDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[j]) * fShapeTime + float(j) * fShapeLength + fSize; + + if ( Abs(pBoat->m_avec2dWakePoints[j].x - sector.x) < fDist + && Abs(pBoat->m_avec2dWakePoints[i].y - sector.y) < fDist ) + { + apBoats[numVerts] = pBoat; + numVerts = 1; // += ? + break; + } + } + } + + return numVerts != 0; +} + +float +CBoat::IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat) +{ + for ( int i = 0; i < pBoat->m_nNumWakePoints; i++ ) + { + float fMaxDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fShapeTime + float(i) * fShapeLength; + + CVector2D vecDist = pBoat->m_avec2dWakePoints[i] - CVector2D(vecVertex); + + float fDist = vecDist.MagnitudeSqr(); + + if ( fDist < SQR(fMaxDist) ) + return 1.0f - Min(fRangeMult * Sqrt(fDist / SQR(fMaxDist)) + (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fTimeMult, 1.0f); + } + + return 0.0f; +} + +void +CBoat::SetupModelNodes() +{ + int i; + for(i = 0; i < ARRAY_SIZE(m_aBoatNodes); i++) + m_aBoatNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), m_aBoatNodes); +} + +void +CBoat::FillBoatList() +{ + int16 frameId = 0; + + apFrameWakeGeneratingBoats[0] = nil; + apFrameWakeGeneratingBoats[1] = nil; + apFrameWakeGeneratingBoats[2] = nil; + apFrameWakeGeneratingBoats[3] = nil; + + for (int i = CPools::GetVehiclePool()->GetSize() - 1; i >= 0; i--) { + CBoat *boat = (CBoat *)(CPools::GetVehiclePool()->GetSlot(i)); + if (boat && boat->m_vehType == VEHICLE_TYPE_BOAT) { + int16 nNumWakePoints = boat->m_nNumWakePoints; + if (nNumWakePoints != 0) { + if (frameId >= ARRAY_SIZE(apFrameWakeGeneratingBoats)) { + int16 frameId2 = -1; + for (int16 j = 0; j < ARRAY_SIZE(apFrameWakeGeneratingBoats); j++) { + if (apFrameWakeGeneratingBoats[j]->m_nNumWakePoints < nNumWakePoints) { + frameId2 = j; + nNumWakePoints = apFrameWakeGeneratingBoats[j]->m_nNumWakePoints; + } + } + + if (frameId2 != -1) + apFrameWakeGeneratingBoats[frameId2] = boat; + } else { + apFrameWakeGeneratingBoats[frameId++] = boat; + } + } + } + } +} + +void +CBoat::PruneWakeTrail(void) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++){ + if(m_afWakePointLifeTime[i] <= 0.0f) + break; + if(m_afWakePointLifeTime[i] <= CTimer::GetTimeStep()){ + m_afWakePointLifeTime[i] = 0.0f; + break; + } + m_afWakePointLifeTime[i] -= CTimer::GetTimeStep(); + } + m_nNumWakePoints = i; +} + +void +CBoat::AddWakePoint(CVector point) +{ + int i; + if(m_afWakePointLifeTime[0] > 0.0f){ + if((CVector2D(GetPosition()) - m_avec2dWakePoints[0]).MagnitudeSqr() < SQR(1.0f)){ + for(i = Min(m_nNumWakePoints, ARRAY_SIZE(m_afWakePointLifeTime)-1); i != 0; i--){ + m_avec2dWakePoints[i] = m_avec2dWakePoints[i-1]; + m_afWakePointLifeTime[i] = m_afWakePointLifeTime[i-1]; + } + m_avec2dWakePoints[0] = point; + m_afWakePointLifeTime[0] = 400.0f; + if(m_nNumWakePoints < ARRAY_SIZE(m_afWakePointLifeTime)) + m_nNumWakePoints++; + } + }else{ + m_avec2dWakePoints[0] = point; + m_afWakePointLifeTime[0] = 400.0f; + m_nNumWakePoints = 1; + } +} + +#ifdef COMPATIBLE_SAVES +void +CBoat::Save(uint8*& buf) +{ + CVehicle::Save(buf); + ZeroSaveBuf(buf, 1156 - 648); +} + +void +CBoat::Load(uint8*& buf) +{ + CVehicle::Load(buf); + SkipSaveBuf(buf, 1156 - 648); +} +#endif diff --git a/src/vehicles/Boat.h b/src/vehicles/Boat.h new file mode 100644 index 0000000..157b485 --- /dev/null +++ b/src/vehicles/Boat.h @@ -0,0 +1,81 @@ +#pragma once + +#include "Vehicle.h" + +enum eBoatNodes +{ + BOAT_MOVING = 1, + BOAT_RUDDER, + BOAT_WINDSCREEN, + NUM_BOAT_NODES +}; + +class CBoat : public CVehicle +{ +public: + // 0x288 + float m_fThrustZ; + float m_fThrustY; + CVector m_vecMoveRes; + CVector m_vecTurnRes; + float m_fMovingRotation; + int32 m_boat_unused1; + RwFrame *m_aBoatNodes[NUM_BOAT_NODES]; + uint8 bBoatInWater : 1; + uint8 bPropellerInWater : 1; + bool m_bIsAnchored; + float m_fOrientation; + int32 m_boat_unused2; + float m_fDamage; + CEntity *m_pSetOnFireEntity; + bool m_boat_unused3; + float m_fAccelerate; + float m_fBrake; + float m_fSteeringLeftRight; + uint8 m_nPadID; + int32 m_boat_unused4; + float m_fVolumeUnderWater; + CVector m_vecBuoyancePoint; + float m_fPrevVolumeUnderWater; + int16 m_nDeltaVolumeUnderWater; + uint16 m_nNumWakePoints; + CVector2D m_avec2dWakePoints[32]; + float m_afWakePointLifeTime[32]; + + CBoat(int, uint8); + + virtual void SetModelIndex(uint32 id); + virtual void ProcessControl(); + virtual void Teleport(CVector v); + virtual void PreRender(void) {}; + virtual void Render(void); + virtual void ProcessControlInputs(uint8); + virtual void GetComponentWorldPosition(int32 component, CVector &pos); + virtual bool IsComponentPresent(int32 component) { return true; } + virtual void BlowUpCar(CEntity *ent); + + void RenderWaterOutPolys(void); + void ApplyWaterResistance(void); + void SetupModelNodes(); + void PruneWakeTrail(void); + void AddWakePoint(CVector point); + + static CBoat *apFrameWakeGeneratingBoats[4]; + + static bool IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats); + static float IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat); + static void FillBoatList(void); + +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + static const uint32 nSaveStructSize; + +}; + +VALIDATE_SIZE(CBoat, 0x484); + +extern float MAX_WAKE_LENGTH; +extern float MIN_WAKE_INTERVAL; +extern float WAKE_LIFETIME; \ No newline at end of file diff --git a/src/vehicles/CarGen.cpp b/src/vehicles/CarGen.cpp new file mode 100644 index 0000000..ebb767d --- /dev/null +++ b/src/vehicles/CarGen.cpp @@ -0,0 +1,271 @@ +#include "common.h" + +#include "CarGen.h" + +#include "Automobile.h" +#include "Boat.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "CutsceneMgr.h" +#include "General.h" +#include "Pools.h" +#include "Streaming.h" +#include "Timer.h" +#include "Vehicle.h" +#include "World.h" +#include "SaveBuf.h" + +uint8 CTheCarGenerators::ProcessCounter; +uint32 CTheCarGenerators::NumOfCarGenerators; +CCarGenerator CTheCarGenerators::CarGeneratorArray[NUM_CARGENS]; +uint8 CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter; +uint32 CTheCarGenerators::CurrentActiveCount; + +void CCarGenerator::SwitchOff() +{ +#ifdef FIX_BUGS + if (m_nUsesRemaining != 0) +#endif + { + m_nUsesRemaining = 0; + --CTheCarGenerators::CurrentActiveCount; + } +} + +void CCarGenerator::SwitchOn() +{ + m_nUsesRemaining = UINT16_MAX; + m_nTimer = CalcNextGen(); + ++CTheCarGenerators::CurrentActiveCount; +} + +uint32 CCarGenerator::CalcNextGen() +{ + return CTimer::GetTimeInMilliseconds() + 4; +} + +void CCarGenerator::DoInternalProcessing() +{ + if (CheckForBlockage()) { + m_nTimer += 4; + if (m_nUsesRemaining == 0) + --CTheCarGenerators::CurrentActiveCount; + return; + } + if (CCarCtrl::NumParkedCars >= 10) + return; + CStreaming::RequestModel(m_nModelIndex, STREAMFLAGS_DEPENDENCY); + if (!CStreaming::HasModelLoaded(m_nModelIndex)) + return; + if (CModelInfo::IsBoatModel(m_nModelIndex)){ + CBoat* pBoat = new CBoat(m_nModelIndex, PARKED_VEHICLE); + pBoat->SetIsStatic(false); + pBoat->bEngineOn = false; + CVector pos = m_vecPos; + if (pos.z <= -100.0f) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += pBoat->GetDistanceFromCentreOfMassToBaseOfModel(); + pBoat->SetPosition(pos); + pBoat->SetOrientation(0.0f, 0.0f, DEGTORAD(m_fAngle)); + pBoat->SetStatus(STATUS_ABANDONED); + pBoat->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(pBoat); + if (CGeneral::GetRandomNumberInRange(0, 100) < m_nAlarm) + pBoat->m_nAlarmState = -1; + if (CGeneral::GetRandomNumberInRange(0, 100) < m_nDoorlock) + pBoat->m_nDoorLock = CARLOCK_LOCKED; + if (m_nColor1 != -1 && m_nColor2){ + pBoat->m_currentColour1 = m_nColor1; + pBoat->m_currentColour2 = m_nColor2; + } + m_nVehicleHandle = CPools::GetVehiclePool()->GetIndex(pBoat); + }else{ + bool groundFound; + CVector pos = m_vecPos; + if (pos.z > -100.0f){ + pos.z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &groundFound); + }else{ + groundFound = false; + CColPoint cp; + CEntity* pEntity; + groundFound = CWorld::ProcessVerticalLine(CVector(pos.x, pos.y, 1000.0f), -1000.0f, + cp, pEntity, true, false, false, false, false, false, nil); + if (groundFound) + pos.z = cp.point.z; + } + if (!groundFound) { + debug("CCarGenerator::DoInternalProcessing - can't find ground z for new car x = %f y = %f \n", m_vecPos.x, m_vecPos.y); + }else{ + CAutomobile* pCar; + + // So game crashes if it's bike :D + if (((CVehicleModelInfo*)CModelInfo::GetModelInfo(m_nModelIndex))->m_vehicleType != VEHICLE_TYPE_BIKE) + pCar = new CAutomobile(m_nModelIndex, PARKED_VEHICLE); + + pCar->SetIsStatic(false); + pCar->bEngineOn = false; + pos.z += pCar->GetDistanceFromCentreOfMassToBaseOfModel(); + pCar->SetPosition(pos); + pCar->SetOrientation(0.0f, 0.0f, DEGTORAD(m_fAngle)); + pCar->SetStatus(STATUS_ABANDONED); + pCar->bLightsOn = false; + pCar->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(pCar); + if (CGeneral::GetRandomNumberInRange(0, 100) < m_nAlarm) + pCar->m_nAlarmState = -1; + if (CGeneral::GetRandomNumberInRange(0, 100) < m_nDoorlock) + pCar->m_nDoorLock = CARLOCK_LOCKED; + if (m_nColor1 != -1 && m_nColor2) { + pCar->m_currentColour1 = m_nColor1; + pCar->m_currentColour2 = m_nColor2; + } + m_nVehicleHandle = CPools::GetVehiclePool()->GetIndex(pCar); + } + } +#ifdef FIX_BUGS + if (m_nUsesRemaining < UINT16_MAX) + --m_nUsesRemaining; +#else + if (m_nUsesRemaining < ~0) + --m_nUsesRemaining; +#endif + m_nTimer = CalcNextGen(); + if (m_nUsesRemaining == 0) + --CTheCarGenerators::CurrentActiveCount; +} + +void CCarGenerator::Process() +{ + if (m_nVehicleHandle == -1 && + (CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter || CTimer::GetTimeInMilliseconds() >= m_nTimer) && + m_nUsesRemaining != 0 && CheckIfWithinRangeOfAnyPlayer()) + DoInternalProcessing(); + if (m_nVehicleHandle == -1) + return; + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(m_nVehicleHandle); + if (!pVehicle){ + m_nVehicleHandle = -1; + return; + } + if (pVehicle->GetStatus() != STATUS_PLAYER) + return; + m_nTimer += 60000; + m_nVehicleHandle = -1; + m_bIsBlocking = true; + pVehicle->bExtendedRange = false; +} + +void CCarGenerator::Setup(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay) +{ + CMatrix m1, m2, m3; /* Unused but present on stack, so I'll leave them. */ + m_vecPos = CVector(x, y, z); + m_fAngle = angle; + m_nModelIndex = mi; + m_nColor1 = color1; + m_nColor2 = color2; + m_bForceSpawn = force; + m_nAlarm = alarm; + m_nDoorlock = lock; + m_nMinDelay = min_delay; + m_nMaxDelay = max_delay; + m_nVehicleHandle = -1; + m_nTimer = CTimer::GetTimeInMilliseconds() + 1; + m_nUsesRemaining = 0; + m_bIsBlocking = false; + m_vecInf = CModelInfo::GetColModel(m_nModelIndex)->boundingBox.min; + m_vecSup = CModelInfo::GetColModel(m_nModelIndex)->boundingBox.max; + m_fSize = Max(m_vecInf.Magnitude(), m_vecSup.Magnitude()); +} + +bool CCarGenerator::CheckForBlockage() +{ + int16 entities; + CWorld::FindObjectsKindaColliding(CVector(m_vecPos), m_fSize, 1, &entities, 2, nil, false, true, true, false, false); + return entities > 0; +} + +bool CCarGenerator::CheckIfWithinRangeOfAnyPlayer() +{ + CVector2D direction = FindPlayerCentreOfWorld(CWorld::PlayerInFocus) - m_vecPos; + float distance = direction.Magnitude(); + float farclip = 120.0f * TheCamera.GenerationDistMultiplier; + float nearclip = farclip - 20.0f; + if (distance >= farclip){ + if (m_bIsBlocking) + m_bIsBlocking = false; + return false; + } + if (CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter) + return true; + if (m_bIsBlocking) + return false; + if (distance < nearclip) + return false; + return DotProduct2D(direction, FindPlayerSpeed()) <= 0; +} + +void CTheCarGenerators::Process() +{ + if (FindPlayerTrain() || CCutsceneMgr::IsCutsceneProcessing()) + return; + if (++CTheCarGenerators::ProcessCounter == 4) + CTheCarGenerators::ProcessCounter = 0; + for (uint32 i = ProcessCounter; i < NumOfCarGenerators; i += 4) + CTheCarGenerators::CarGeneratorArray[i].Process(); + if (GenerateEvenIfPlayerIsCloseCounter) + GenerateEvenIfPlayerIsCloseCounter--; +} + +int32 CTheCarGenerators::CreateCarGenerator(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay) +{ + CarGeneratorArray[NumOfCarGenerators].Setup(x, y, z, angle, mi, color1, color2, force, alarm, lock, min_delay, max_delay); + return NumOfCarGenerators++; +} + +void CTheCarGenerators::Init() +{ + GenerateEvenIfPlayerIsCloseCounter = 0; + NumOfCarGenerators = 0; + ProcessCounter = 0; + CurrentActiveCount = 0; +} + +void CTheCarGenerators::SaveAllCarGenerators(uint8 *buffer, uint32 *size) +{ + const uint32 nGeneralDataSize = sizeof(NumOfCarGenerators) + sizeof(CurrentActiveCount) + sizeof(ProcessCounter) + sizeof(GenerateEvenIfPlayerIsCloseCounter) + sizeof(int16); + *size = sizeof(int) + nGeneralDataSize + sizeof(uint32) + sizeof(CarGeneratorArray) + SAVE_HEADER_SIZE; +INITSAVEBUF + WriteSaveHeader(buffer, 'C','G','N','\0', *size - SAVE_HEADER_SIZE); + + WriteSaveBuf(buffer, nGeneralDataSize); + WriteSaveBuf(buffer, NumOfCarGenerators); + WriteSaveBuf(buffer, CurrentActiveCount); + WriteSaveBuf(buffer, ProcessCounter); + WriteSaveBuf(buffer, GenerateEvenIfPlayerIsCloseCounter); + WriteSaveBuf(buffer, (int16)0); // alignment + WriteSaveBuf(buffer, (uint32)sizeof(CarGeneratorArray)); + for (int i = 0; i < NUM_CARGENS; i++) + WriteSaveBuf(buffer, CarGeneratorArray[i]); +VALIDATESAVEBUF(*size) +} + +void CTheCarGenerators::LoadAllCarGenerators(uint8* buffer, uint32 size) +{ + const int32 nGeneralDataSize = sizeof(NumOfCarGenerators) + sizeof(CurrentActiveCount) + sizeof(ProcessCounter) + sizeof(GenerateEvenIfPlayerIsCloseCounter) + sizeof(int16); + Init(); +INITSAVEBUF + CheckSaveHeader(buffer, 'C','G','N','\0', size - SAVE_HEADER_SIZE); + uint32 tmp; + ReadSaveBuf(&tmp, buffer); + assert(tmp == nGeneralDataSize); + ReadSaveBuf(&NumOfCarGenerators, buffer); + ReadSaveBuf(&CurrentActiveCount, buffer); + ReadSaveBuf(&ProcessCounter, buffer); + ReadSaveBuf(&GenerateEvenIfPlayerIsCloseCounter, buffer); + SkipSaveBuf(buffer, 2); + ReadSaveBuf(&tmp, buffer); + assert(tmp == sizeof(CarGeneratorArray)); + for (int i = 0; i < NUM_CARGENS; i++) + ReadSaveBuf(&CarGeneratorArray[i], buffer); +VALIDATESAVEBUF(size) +} diff --git a/src/vehicles/CarGen.h b/src/vehicles/CarGen.h new file mode 100644 index 0000000..9d64531 --- /dev/null +++ b/src/vehicles/CarGen.h @@ -0,0 +1,54 @@ +#pragma once +#include "common.h" +#include "config.h" + +enum { + CARGEN_MAXACTUALLIMIT = 100 +}; + +class CCarGenerator +{ + int32 m_nModelIndex; + CVector m_vecPos; + float m_fAngle; + int16 m_nColor1; + int16 m_nColor2; + uint8 m_bForceSpawn; + uint8 m_nAlarm; + uint8 m_nDoorlock; + int16 m_nMinDelay; + int16 m_nMaxDelay; + uint32 m_nTimer; + int32 m_nVehicleHandle; + uint16 m_nUsesRemaining; + bool m_bIsBlocking; + CVector m_vecInf; + CVector m_vecSup; + float m_fSize; +public: + void SwitchOff(); + void SwitchOn(); + uint32 CalcNextGen(); + void DoInternalProcessing(); + void Process(); + void Setup(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay); + bool CheckForBlockage(); + bool CheckIfWithinRangeOfAnyPlayer(); + void SetUsesRemaining(uint16 uses) { m_nUsesRemaining = uses; } +}; + +class CTheCarGenerators +{ +public: + static uint8 ProcessCounter; + static uint32 NumOfCarGenerators; + static CCarGenerator CarGeneratorArray[NUM_CARGENS]; + static uint8 GenerateEvenIfPlayerIsCloseCounter; + static uint32 CurrentActiveCount; + + static void Process(); + static int32 CreateCarGenerator(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay); + static void Init(); + static void SaveAllCarGenerators(uint8 *, uint32 *); + static void LoadAllCarGenerators(uint8 *, uint32); +}; diff --git a/src/vehicles/Cranes.cpp b/src/vehicles/Cranes.cpp new file mode 100644 index 0000000..db9d2e0 --- /dev/null +++ b/src/vehicles/Cranes.cpp @@ -0,0 +1,759 @@ +#include "common.h" + +#include "Cranes.h" + +#include "Camera.h" +#include "DMAudio.h" +#include "Garages.h" +#include "General.h" +#include "Entity.h" +#include "ModelIndices.h" +#include "Replay.h" +#include "Object.h" +#include "World.h" +#include "SaveBuf.h" + +#define MAX_DISTANCE_TO_FIND_CRANE (10.0f) +#define CRANE_UPDATE_RADIUS (300.0f) +#define CRANE_MOVEMENT_PROCESSING_RADIUS (150.0f) +#define CRUSHER_Z (-0.951f) +#define MILITARY_Z (10.7862f) +#define DISTANCE_FROM_PLAYER_TO_REMOVE_VEHICLE (5.0f) +#define DISTANCE_FROM_HOOK_TO_VEHICLE_TO_COLLECT (0.5f) +#define CAR_REWARD_MILITARY_CRANE (1500) +#define CAR_MOVING_SPEED_THRESHOLD (0.01f) +#define CRANE_SLOWDOWN_MULTIPLIER (0.3f) + +#define OSCILLATION_SPEED (0.002f) +#define CAR_ROTATION_SPEED (0.0035f) +#define CRANE_MOVEMENT_SPEED (0.001f) +#define HOOK_ANGLE_MOVEMENT_SPEED (0.004f) +#define HOOK_OFFSET_MOVEMENT_SPEED (0.1f) +#define HOOK_HEIGHT_MOVEMENT_SPEED (0.06f) + +#define MESSAGE_SHOW_DURATION (4000) + +#define MAX_DISTANCE (99999.9f) +#define MIN_VALID_POSITION (-10000.0f) +#define DEFAULT_OFFSET (20.0f) + +#ifdef COMPATIBLE_SAVES +#define CRANES_SAVE_SIZE 0x400 +#else +#define CRANES_SAVE_SIZE sizeof(aCranes) +#endif + +uint32 TimerForCamInterpolation; + +uint32 CCranes::CarsCollectedMilitaryCrane; +int32 CCranes::NumCranes; +CCrane CCranes::aCranes[NUM_CRANES]; + +void CCranes::InitCranes(void) +{ + CarsCollectedMilitaryCrane = 0; + NumCranes = 0; + for (int i = 0; i < NUMSECTORS_X; i++) { + for (int j = 0; j < NUMSECTORS_Y; j++) { + for (CPtrNode* pNode = CWorld::GetSector(i, j)->m_lists[ENTITYLIST_BUILDINGS].first; pNode; pNode = pNode->next) { + CEntity* pEntity = (CEntity*)pNode->item; + if (MODELID_CRANE_1 == pEntity->GetModelIndex() || + MODELID_CRANE_2 == pEntity->GetModelIndex() || + MODELID_CRANE_3 == pEntity->GetModelIndex()) + AddThisOneCrane(pEntity); + } + } + } + for (CPtrNode* pNode = CWorld::GetBigBuildingList(LEVEL_INDUSTRIAL).first; pNode; pNode = pNode->next) { + CEntity* pEntity = (CEntity*)pNode->item; + if (MODELID_CRANE_1 == pEntity->GetModelIndex() || + MODELID_CRANE_2 == pEntity->GetModelIndex() || + MODELID_CRANE_3 == pEntity->GetModelIndex()) + AddThisOneCrane(pEntity); + } +} + +void CCranes::AddThisOneCrane(CEntity* pEntity) +{ + pEntity->GetMatrix().ResetOrientation(); + if (NumCranes >= NUM_CRANES) + return; + CCrane* pCrane = &aCranes[NumCranes]; + pCrane->Init(); + pCrane->m_pCraneEntity = (CBuilding*)pEntity; + pCrane->m_nCraneStatus = CCrane::NONE; + pCrane->m_fHookAngle = NumCranes; // lol wtf + while (pCrane->m_fHookAngle > TWOPI) + pCrane->m_fHookAngle -= TWOPI; + pCrane->m_fHookOffset = DEFAULT_OFFSET; + pCrane->m_fHookHeight = DEFAULT_OFFSET; + pCrane->m_nTimeForNextCheck = 0; + pCrane->m_nCraneState = CCrane::IDLE; + pCrane->m_bWasMilitaryCrane = false; + pCrane->m_nAudioEntity = DMAudio.CreateEntity(AUDIOTYPE_CRANE, &aCranes[NumCranes]); + if (pCrane->m_nAudioEntity >= 0) + DMAudio.SetEntityStatus(pCrane->m_nAudioEntity, TRUE); + pCrane->m_bIsTop = (MODELID_CRANE_1 != pEntity->GetModelIndex()); + // Is this used to avoid military crane? + if (pCrane->m_bIsTop || pEntity->GetPosition().y > 0.0f) { + CObject* pHook = new CObject(MI_MAGNET, false); + pHook->ObjectCreatedBy = MISSION_OBJECT; + pHook->bUsesCollision = false; + pHook->bExplosionProof = true; + pHook->bAffectedByGravity = false; + pCrane->m_pHook = pHook; + pCrane->CalcHookCoordinates(&pCrane->m_vecHookCurPos.x, &pCrane->m_vecHookCurPos.y, &pCrane->m_vecHookCurPos.z); + pCrane->SetHookMatrix(); + } + else + pCrane->m_pHook = nil; + NumCranes++; +} + +void CCranes::ActivateCrane(float fInfX, float fSupX, float fInfY, float fSupY, float fDropOffX, float fDropOffY, float fDropOffZ, float fHeading, bool bIsCrusher, bool bIsMilitary, float fPosX, float fPosY) +{ + float fMinDistance = MAX_DISTANCE; + float X = fPosX, Y = fPosY; + if (X <= MIN_VALID_POSITION || Y <= MIN_VALID_POSITION) { + X = fDropOffX; + Y = fDropOffY; + } + int index = 0; + for (int i = 0; i < NumCranes; i++) { + float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude(); + if (distance < fMinDistance && distance < MAX_DISTANCE_TO_FIND_CRANE) { + fMinDistance = distance; + index = i; + } + } +#ifdef FIX_BUGS // classic + if (fMinDistance == MAX_DISTANCE) + return; +#endif + CCrane* pCrane = &aCranes[index]; + pCrane->m_fPickupX1 = fInfX; + pCrane->m_fPickupX2 = fSupX; + pCrane->m_fPickupY1 = fInfY; + pCrane->m_fPickupY2 = fSupY; + pCrane->m_vecDropoffTarget.x = fDropOffX; + pCrane->m_vecDropoffTarget.y = fDropOffY; + pCrane->m_vecDropoffTarget.z = fDropOffZ; + pCrane->m_nCraneStatus = CCrane::ACTIVATED; + pCrane->m_pVehiclePickedUp = nil; + pCrane->m_nVehiclesCollected = 0; + pCrane->m_fDropoffHeading = fHeading; + pCrane->m_bIsCrusher = bIsCrusher; + pCrane->m_bIsMilitaryCrane = bIsMilitary; + bool military = true; + if (!bIsMilitary && !pCrane->m_bWasMilitaryCrane) + military = false; + pCrane->m_bWasMilitaryCrane = military; + pCrane->m_nTimeForNextCheck = 0; + pCrane->m_nCraneState = CCrane::IDLE; + float Z; + if (bIsCrusher) + Z = CRUSHER_Z; + else if (bIsMilitary) + Z = MILITARY_Z; + else + Z = CWorld::FindGroundZForCoord((fInfX + fSupX) / 2, (fInfY + fSupY) / 2); + pCrane->FindParametersForTarget((fInfX + fSupX) / 2, (fInfY + fSupY) / 2, Z, &pCrane->m_fPickupAngle, &pCrane->m_fPickupDistance, &pCrane->m_fPickupHeight); + pCrane->FindParametersForTarget(fDropOffX, fDropOffY, fDropOffZ, &pCrane->m_fDropoffAngle, &pCrane->m_fDropoffDistance, &pCrane->m_fDropoffHeight); +} + +void CCranes::DeActivateCrane(float X, float Y) +{ + float fMinDistance = MAX_DISTANCE; + int index = 0; + for (int i = 0; i < NumCranes; i++) { + float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude(); + if (distance < fMinDistance && distance < MAX_DISTANCE_TO_FIND_CRANE) { + fMinDistance = distance; + index = i; + } + } +#ifdef FIX_BUGS // classic + if (fMinDistance == MAX_DISTANCE) + return; +#endif + aCranes[index].m_nCraneStatus = CCrane::DEACTIVATED; + aCranes[index].m_nCraneState = CCrane::IDLE; +} + +bool CCranes::IsThisCarPickedUp(float X, float Y, CVehicle* pVehicle) +{ + int index = 0; + bool result = false; + for (int i = 0; i < NumCranes; i++) { + float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude(); + if (distance < MAX_DISTANCE_TO_FIND_CRANE && aCranes[i].m_pVehiclePickedUp == pVehicle) { + if (aCranes[i].m_nCraneState == CCrane::LIFTING_TARGET || aCranes[i].m_nCraneState == CCrane::ROTATING_TARGET) + result = true; + } + } + return result; +} + +void CCranes::UpdateCranes(void) +{ + for (int i = 0; i < NumCranes; i++) { + if (aCranes[i].m_bIsTop || aCranes[i].m_bIsCrusher || + (TheCamera.GetPosition().x + CRANE_UPDATE_RADIUS > aCranes[i].m_pCraneEntity->GetPosition().x && + TheCamera.GetPosition().x - CRANE_UPDATE_RADIUS < aCranes[i].m_pCraneEntity->GetPosition().x && + TheCamera.GetPosition().y + CRANE_UPDATE_RADIUS > aCranes[i].m_pCraneEntity->GetPosition().y && + TheCamera.GetPosition().y + CRANE_UPDATE_RADIUS < aCranes[i].m_pCraneEntity->GetPosition().y)) + aCranes[i].Update(); + } +} + +void CCrane::Update(void) +{ + if (CReplay::IsPlayingBack()) + return; + if (((m_nCraneStatus == ACTIVATED || m_nCraneStatus == DEACTIVATED) && + Abs(TheCamera.GetGameCamPosition().x - m_pCraneEntity->GetPosition().x) < CRANE_MOVEMENT_PROCESSING_RADIUS && + Abs(TheCamera.GetGameCamPosition().y - m_pCraneEntity->GetPosition().y) < CRANE_MOVEMENT_PROCESSING_RADIUS) || + m_nCraneState != IDLE) { + switch (m_nCraneState) { + case IDLE: + if (GoTowardsTarget(m_fPickupAngle, m_fPickupDistance, GetHeightToPickup()) && + CTimer::GetTimeInMilliseconds() > m_nTimeForNextCheck) { + CWorld::AdvanceCurrentScanCode(); +#ifdef FIX_BUGS + int xstart = Max(0, CWorld::GetSectorIndexX(m_fPickupX1)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(m_fPickupX2)); + int ystart = Max(0, CWorld::GetSectorIndexY(m_fPickupY1)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(m_fPickupY2)); +#else + int xstart = CWorld::GetSectorIndexX(m_fPickupX1); + int xend = CWorld::GetSectorIndexX(m_fPickupX2); + int ystart = CWorld::GetSectorIndexY(m_fPickupY1); + int yend = CWorld::GetSectorIndexY(m_fPickupY1); +#endif + assert(xstart <= xend); + assert(ystart <= yend); + for (int i = xstart; i <= xend; i++) { + for (int j = ystart; j <= yend; j++) { + FindCarInSectorList(&CWorld::GetSector(i, j)->m_lists[ENTITYLIST_VEHICLES]); + FindCarInSectorList(&CWorld::GetSector(i, j)->m_lists[ENTITYLIST_VEHICLES_OVERLAP]); + } + } + } + break; + case GOING_TOWARDS_TARGET: + if (m_pVehiclePickedUp){ + if (m_pVehiclePickedUp->GetPosition().x < m_fPickupX1 || + m_pVehiclePickedUp->GetPosition().x > m_fPickupX2 || + m_pVehiclePickedUp->GetPosition().y < m_fPickupY1 || + m_pVehiclePickedUp->GetPosition().y > m_fPickupY2 || + m_pVehiclePickedUp->pDriver || + Abs(m_pVehiclePickedUp->GetMoveSpeed().x) > CAR_MOVING_SPEED_THRESHOLD || + Abs(m_pVehiclePickedUp->GetMoveSpeed().y) > CAR_MOVING_SPEED_THRESHOLD || + Abs(m_pVehiclePickedUp->GetMoveSpeed().z) > CAR_MOVING_SPEED_THRESHOLD || + (FindPlayerPed()->GetPedState() == PED_ENTER_CAR +#ifdef FIX_BUGS + || FindPlayerPed()->GetPedState() == PED_CARJACK +#endif + ) && FindPlayerPed()->m_pSeekTarget == m_pVehiclePickedUp) { + m_pVehiclePickedUp = nil; + m_nCraneState = IDLE; + } + else { + float fAngle, fOffset, fHeight; + FindParametersForTarget( + m_pVehiclePickedUp->GetPosition().x, + m_pVehiclePickedUp->GetPosition().y, + m_pVehiclePickedUp->GetPosition().z + m_pVehiclePickedUp->GetColModel()->boundingBox.max.z, + &fAngle, &fOffset, &fHeight); + if (GoTowardsTarget(fAngle, fOffset, fHeight)) { + CVector distance = m_pVehiclePickedUp->GetPosition() - m_vecHookCurPos; + distance.z += m_pVehiclePickedUp->GetColModel()->boundingBox.max.z; + if (distance.MagnitudeSqr() < SQR(DISTANCE_FROM_HOOK_TO_VEHICLE_TO_COLLECT)) { + m_nCraneState = GOING_TOWARDS_TARGET_ONLY_HEIGHT; + m_vecHookVelocity *= 0.4f; + m_pVehiclePickedUp->bLightsOn = false; + m_pVehiclePickedUp->bUsesCollision = false; + if (m_bIsCrusher) + m_pVehiclePickedUp->bCollisionProof = true; + DMAudio.PlayOneShot(m_nAudioEntity, SOUND_CRANE_PICKUP, 0.0f); + } + } + } + } + else + m_nCraneState = IDLE; + break; + case LIFTING_TARGET: + RotateCarriedCarProperly(); + if (GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, GetHeightToDropoff(), CRANE_SLOWDOWN_MULTIPLIER)) + m_nCraneState = ROTATING_TARGET; + if (!m_pVehiclePickedUp || m_pVehiclePickedUp->pDriver) { + m_pVehiclePickedUp = nil; + m_nCraneState = IDLE; + } + break; + case GOING_TOWARDS_TARGET_ONLY_HEIGHT: + RotateCarriedCarProperly(); + if (GoTowardsHeightTarget(GetHeightToPickupHeight(), CRANE_SLOWDOWN_MULTIPLIER)) + m_nCraneState = LIFTING_TARGET; + TimerForCamInterpolation = CTimer::GetTimeInMilliseconds(); + if (!m_pVehiclePickedUp || m_pVehiclePickedUp->pDriver) { + m_pVehiclePickedUp = nil; + m_nCraneState = IDLE; + } + break; + case ROTATING_TARGET: + { + bool bRotateFinished = RotateCarriedCarProperly(); + bool bMovementFinished = GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, m_fDropoffHeight, 0.3f); + if (bMovementFinished && bRotateFinished) { + float fDistanceFromPlayer = m_pVehiclePickedUp ? ((CVector2D)FindPlayerCoors() - (CVector2D)m_pVehiclePickedUp->GetPosition()).Magnitude() : 0.0f; + if (fDistanceFromPlayer > DISTANCE_FROM_PLAYER_TO_REMOVE_VEHICLE || !m_bWasMilitaryCrane) { + m_nCraneState = DROPPING_TARGET; + if (m_pVehiclePickedUp) { + m_pVehiclePickedUp->bUsesCollision = true; + m_pVehiclePickedUp->m_nStaticFrames = 0; + ++m_nVehiclesCollected; + if (m_bIsMilitaryCrane) { + CCranes::RegisterCarForMilitaryCrane(m_pVehiclePickedUp->GetModelIndex()); + if (!CCranes::HaveAllCarsBeenCollectedByMilitaryCrane()) { + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += CAR_REWARD_MILITARY_CRANE; + CGarages::TriggerMessage("GA_10", CAR_REWARD_MILITARY_CRANE, MESSAGE_SHOW_DURATION, -1); + } + CWorld::Remove(m_pVehiclePickedUp); + delete m_pVehiclePickedUp; + } + } + m_pVehiclePickedUp = nil; + } + } + break; + } + case DROPPING_TARGET: + if (GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, GetHeightToDropoffHeight(), CRANE_SLOWDOWN_MULTIPLIER)) { + m_nCraneState = IDLE; + m_nTimeForNextCheck = CTimer::GetTimeInMilliseconds() + 10000; + } + break; + default: + break; + } + CVector vecHook; + CalcHookCoordinates(&vecHook.x, &vecHook.y, &vecHook.z); + m_vecHookVelocity += ((CVector2D)vecHook - (CVector2D)m_vecHookCurPos) * CTimer::GetTimeStep() * CRANE_MOVEMENT_SPEED; + m_vecHookVelocity *= Pow(0.98f, CTimer::GetTimeStep()); + m_vecHookCurPos.x += m_vecHookVelocity.x * CTimer::GetTimeStep(); + m_vecHookCurPos.y += m_vecHookVelocity.y * CTimer::GetTimeStep(); + m_vecHookCurPos.z = vecHook.z; + switch (m_nCraneState) { + case LIFTING_TARGET: + case GOING_TOWARDS_TARGET_ONLY_HEIGHT: + case ROTATING_TARGET: + if (m_pVehiclePickedUp) { + m_pVehiclePickedUp->SetPosition(m_vecHookCurPos.x, m_vecHookCurPos.y, m_vecHookCurPos.z - m_pVehiclePickedUp->GetColModel()->boundingBox.max.z); + m_pVehiclePickedUp->SetMoveSpeed(0.0f, 0.0f, 0.0f); + CVector up(vecHook.x - m_vecHookCurPos.x, vecHook.y - m_vecHookCurPos.y, 20.0f); + up.Normalise(); + m_pVehiclePickedUp->GetRight() = CrossProduct(m_pVehiclePickedUp->GetForward(), up); + m_pVehiclePickedUp->GetForward() = CrossProduct(up, m_pVehiclePickedUp->GetRight()); + m_pVehiclePickedUp->GetUp() = up; + } + break; + default: + break; + } + } + else { + int16 rnd = (m_pCraneEntity->m_randomSeed + (CTimer::GetTimeInMilliseconds() >> 11)) & 0xF; + // 16 options, lasting 2048 ms each + // a bit awkward: why there are 4 periods for -= and 6 for +=? is it a bug? + if (rnd < 4) { + m_fHookAngle -= OSCILLATION_SPEED * CTimer::GetTimeStep(); + if (m_fHookAngle < 0.0f) + m_fHookAngle += TWOPI; + } + else if (rnd > 5 && rnd < 12) { + m_fHookAngle += OSCILLATION_SPEED * CTimer::GetTimeStep(); + if (m_fHookAngle > TWOPI) + m_fHookAngle -= TWOPI; + } + CalcHookCoordinates(&m_vecHookCurPos.x, &m_vecHookCurPos.y, &m_vecHookCurPos.z); + m_vecHookVelocity.x = m_vecHookVelocity.y = 0.0f; + } + float fCos = Cos(m_fHookAngle); + float fSin = Sin(m_fHookAngle); + m_pCraneEntity->GetRight().x = fCos; + m_pCraneEntity->GetForward().y = fCos; + m_pCraneEntity->GetRight().y = fSin; + m_pCraneEntity->GetForward().x = -fSin; + m_pCraneEntity->GetMatrix().UpdateRW(); + m_pCraneEntity->UpdateRwFrame(); + SetHookMatrix(); +} + +bool CCrane::RotateCarriedCarProperly() +{ + if (m_fDropoffHeading <= 0.0f) + return true; + if (!m_pVehiclePickedUp) + return true; + float fAngleDelta = m_fDropoffHeading - CGeneral::GetATanOfXY(m_pVehiclePickedUp->GetForward().x, m_pVehiclePickedUp->GetForward().y); + while (fAngleDelta < -HALFPI) + fAngleDelta += PI; + while (fAngleDelta > HALFPI) + fAngleDelta -= PI; + float fDeltaThisFrame = CAR_ROTATION_SPEED * CTimer::GetTimeStep(); + if (Abs(fAngleDelta) <= fDeltaThisFrame) // no rotation is actually applied? + return true; + m_pVehiclePickedUp->GetMatrix().RotateZ(fAngleDelta < 0 ? -fDeltaThisFrame : fDeltaThisFrame); + return false; +} + +void CCrane::FindCarInSectorList(CPtrList* pList) +{ + CPtrNode* node; + for (node = pList->first; node; node = node->next) { + CVehicle* pVehicle = (CVehicle*)node->item; + if (pVehicle->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + pVehicle->m_scanCode = CWorld::GetCurrentScanCode(); + if (pVehicle->GetPosition().x < m_fPickupX1 || pVehicle->GetPosition().x > m_fPickupX2 || + pVehicle->GetPosition().y < m_fPickupY1 || pVehicle->GetPosition().y > m_fPickupY2) + continue; + if (pVehicle->pDriver) + continue; + if (Abs(pVehicle->GetMoveSpeed().x) >= CAR_MOVING_SPEED_THRESHOLD || + Abs(pVehicle->GetMoveSpeed().y) >= CAR_MOVING_SPEED_THRESHOLD || + Abs(pVehicle->GetMoveSpeed().z) >= CAR_MOVING_SPEED_THRESHOLD) + continue; + if (!pVehicle->IsCar() || pVehicle->GetStatus() == STATUS_WRECKED || pVehicle->m_fHealth < 250.0f) + continue; + if (!DoesCranePickUpThisCarType(pVehicle->GetModelIndex()) || + m_bIsMilitaryCrane && CCranes::DoesMilitaryCraneHaveThisOneAlready(pVehicle->GetModelIndex())) { + if (!pVehicle->bCraneMessageDone) { + pVehicle->bCraneMessageDone = true; + if (!m_bIsMilitaryCrane) + CGarages::TriggerMessage("CR_1", -1, MESSAGE_SHOW_DURATION, -1); // Crane cannot lift this vehicle. + else if (DoesCranePickUpThisCarType(pVehicle->GetModelIndex())) + CGarages::TriggerMessage("GA_20", -1, MESSAGE_SHOW_DURATION, -1); // We got more of these than we can shift. Sorry man, no deal. + else + CGarages::TriggerMessage("GA_19", -1, MESSAGE_SHOW_DURATION, -1); // We're not interested in that model. + } + } + else { + m_pVehiclePickedUp = pVehicle; + pVehicle->RegisterReference((CEntity**)&m_pVehiclePickedUp); + m_nCraneState = GOING_TOWARDS_TARGET; + } + } +} + +bool CCrane::DoesCranePickUpThisCarType(uint32 mi) +{ + if (m_bIsCrusher) { + return mi != MI_FIRETRUCK && + mi != MI_TRASH && +#ifdef FIX_BUGS + mi != MI_COACH && +#else + mi != MI_BLISTA && +#endif + mi != MI_SECURICA && + mi != MI_BUS && + mi != MI_DODO && + mi != MI_RHINO; + } + if (m_bIsMilitaryCrane) { + return mi == MI_FIRETRUCK || + mi == MI_AMBULAN || + mi == MI_ENFORCER || + mi == MI_FBICAR || + mi == MI_RHINO || + mi == MI_BARRACKS || + mi == MI_POLICE; + } + return true; +} + +bool CCranes::DoesMilitaryCraneHaveThisOneAlready(uint32 mi) +{ + switch (mi) { + case MI_FIRETRUCK: return (CarsCollectedMilitaryCrane & 1); + case MI_AMBULAN: return (CarsCollectedMilitaryCrane & 2); + case MI_ENFORCER: return (CarsCollectedMilitaryCrane & 4); + case MI_FBICAR: return (CarsCollectedMilitaryCrane & 8); + case MI_RHINO: return (CarsCollectedMilitaryCrane & 0x10); + case MI_BARRACKS: return (CarsCollectedMilitaryCrane & 0x20); + case MI_POLICE: return (CarsCollectedMilitaryCrane & 0x40); + default: break; + } + return false; +} + +void CCranes::RegisterCarForMilitaryCrane(uint32 mi) +{ + switch (mi) { + case MI_FIRETRUCK: CarsCollectedMilitaryCrane |= 1; break; + case MI_AMBULAN: CarsCollectedMilitaryCrane |= 2; break; + case MI_ENFORCER: CarsCollectedMilitaryCrane |= 4; break; + case MI_FBICAR: CarsCollectedMilitaryCrane |= 8; break; + case MI_RHINO: CarsCollectedMilitaryCrane |= 0x10; break; + case MI_BARRACKS: CarsCollectedMilitaryCrane |= 0x20; break; + case MI_POLICE: CarsCollectedMilitaryCrane |= 0x40; break; + default: break; + } +} + +bool CCranes::HaveAllCarsBeenCollectedByMilitaryCrane() +{ + return (CarsCollectedMilitaryCrane & 0x7F) == 0x7F; +} + +bool CCrane::GoTowardsTarget(float fAngleToTarget, float fDistanceToTarget, float fTargetHeight, float fSpeedMultiplier) +{ + bool bAngleMovementFinished, bOffsetMovementFinished, bHeightMovementFinished; + float fHookAngleDelta = fAngleToTarget - m_fHookAngle; + while (fHookAngleDelta > PI) + fHookAngleDelta -= TWOPI; + while (fHookAngleDelta < -PI) + fHookAngleDelta += TWOPI; + float fHookAngleChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_ANGLE_MOVEMENT_SPEED; + if (Abs(fHookAngleDelta) < fHookAngleChangeThisFrame) { + m_fHookAngle = fAngleToTarget; + bAngleMovementFinished = true; + } else { + if (fHookAngleDelta < 0.0f) { + m_fHookAngle -= fHookAngleChangeThisFrame; + if (m_fHookAngle < 0.0f) + m_fHookAngle += TWOPI; + } + else { + m_fHookAngle += fHookAngleChangeThisFrame; + if (m_fHookAngle > TWOPI) + m_fHookAngle -= TWOPI; + } + bAngleMovementFinished = false; + } + float fHookOffsetDelta = fDistanceToTarget - m_fHookOffset; + float fHookOffsetChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_OFFSET_MOVEMENT_SPEED; + if (Abs(fHookOffsetDelta) < fHookOffsetChangeThisFrame) { + m_fHookOffset = fDistanceToTarget; + bOffsetMovementFinished = true; + } else { + if (fHookOffsetDelta < 0.0f) + m_fHookOffset -= fHookOffsetChangeThisFrame; + else + m_fHookOffset += fHookOffsetChangeThisFrame; + bOffsetMovementFinished = false; + } + float fHookHeightDelta = fTargetHeight - m_fHookHeight; + float fHookHeightChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_HEIGHT_MOVEMENT_SPEED; + if (Abs(fHookHeightDelta) < fHookHeightChangeThisFrame) { + m_fHookHeight = fTargetHeight; + bHeightMovementFinished = true; + } else { + if (fHookHeightDelta < 0.0f) + m_fHookHeight -= fHookHeightChangeThisFrame; + else + m_fHookHeight += fHookHeightChangeThisFrame; + bHeightMovementFinished = false; + } + return bAngleMovementFinished && bOffsetMovementFinished && bHeightMovementFinished; +} + +bool CCrane::GoTowardsHeightTarget(float fTargetHeight, float fSpeedMultiplier) +{ + bool bHeightMovementFinished; + float fHookHeightDelta = fTargetHeight - m_fHookHeight; + float fHookHeightChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_HEIGHT_MOVEMENT_SPEED; + if (Abs(fHookHeightDelta) < fHookHeightChangeThisFrame) { + m_fHookHeight = fTargetHeight; + bHeightMovementFinished = true; + } else { + if (fHookHeightDelta < 0.0f) + m_fHookHeight -= fHookHeightChangeThisFrame; + else + m_fHookHeight += fHookHeightChangeThisFrame; + bHeightMovementFinished = false; + } + return bHeightMovementFinished; +} + +void CCrane::FindParametersForTarget(float X, float Y, float Z, float* pAngle, float* pDistance, float* pHeight) +{ + *pAngle = CGeneral::GetATanOfXY(X - m_pCraneEntity->GetPosition().x, Y - m_pCraneEntity->GetPosition().y); + *pDistance = ((CVector2D(X, Y) - (CVector2D)m_pCraneEntity->GetPosition())).Magnitude(); + *pHeight = Z; +} + +void CCrane::CalcHookCoordinates(float* pX, float* pY, float* pZ) +{ + *pX = Cos(m_fHookAngle) * m_fHookOffset + m_pCraneEntity->GetPosition().x; + *pY = Sin(m_fHookAngle) * m_fHookOffset + m_pCraneEntity->GetPosition().y; + *pZ = m_fHookHeight; +} + +void CCrane::SetHookMatrix() +{ + if (m_pHook == nil) + return; + m_pHook->SetPosition(m_vecHookCurPos); + CVector up(m_vecHookInitPos.x - m_vecHookCurPos.x, m_vecHookInitPos.y - m_vecHookCurPos.y, 20.0f); + up.Normalise(); + m_pHook->GetRight() = CrossProduct(CVector(0.0f, 1.0f, 0.0f), up); + m_pHook->GetForward() = CrossProduct(up, m_pHook->GetRight()); + m_pHook->GetUp() = up; + m_pHook->SetOrientation(0.0f, 0.0f, -HALFPI); + m_pHook->GetMatrix().UpdateRW(); + m_pHook->UpdateRwFrame(); + CWorld::Remove(m_pHook); + CWorld::Add(m_pHook); +} + +bool CCranes::IsThisCarBeingCarriedByAnyCrane(CVehicle* pVehicle) +{ + for (int i = 0; i < NumCranes; i++) { + if (pVehicle == aCranes[i].m_pVehiclePickedUp) { + switch (aCranes[i].m_nCraneState) { + case CCrane::GOING_TOWARDS_TARGET_ONLY_HEIGHT: + case CCrane::LIFTING_TARGET: + case CCrane::ROTATING_TARGET: + return true; + default: + break; + } + } + } + return false; +} + +bool CCranes::IsThisCarBeingTargettedByAnyCrane(CVehicle* pVehicle) +{ + for (int i = 0; i < NumCranes; i++) { + if (pVehicle == aCranes[i].m_pVehiclePickedUp) + return true; + } + return false; +} + +void CCranes::Save(uint8* buf, uint32* size) +{ + INITSAVEBUF + + *size = 2 * sizeof(uint32) + CRANES_SAVE_SIZE; + WriteSaveBuf(buf, NumCranes); + WriteSaveBuf(buf, CarsCollectedMilitaryCrane); + for (int i = 0; i < NUM_CRANES; i++) { +#ifdef COMPATIBLE_SAVES + int32 tmp = aCranes[i].m_pCraneEntity != nil ? CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert(aCranes[i].m_pCraneEntity) + 1 : 0; + WriteSaveBuf(buf, tmp); + tmp = aCranes[i].m_pHook != nil ? CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(aCranes[i].m_pHook) + 1 : 0; + WriteSaveBuf(buf, tmp); + WriteSaveBuf(buf, aCranes[i].m_nAudioEntity); + WriteSaveBuf(buf, aCranes[i].m_fPickupX1); + WriteSaveBuf(buf, aCranes[i].m_fPickupX2); + WriteSaveBuf(buf, aCranes[i].m_fPickupY1); + WriteSaveBuf(buf, aCranes[i].m_fPickupY2); + WriteSaveBuf(buf, aCranes[i].m_vecDropoffTarget); + WriteSaveBuf(buf, aCranes[i].m_fDropoffHeading); + WriteSaveBuf(buf, aCranes[i].m_fPickupAngle); + WriteSaveBuf(buf, aCranes[i].m_fDropoffAngle); + WriteSaveBuf(buf, aCranes[i].m_fPickupDistance); + WriteSaveBuf(buf, aCranes[i].m_fDropoffDistance); + WriteSaveBuf(buf, aCranes[i].m_fPickupHeight); + WriteSaveBuf(buf, aCranes[i].m_fDropoffHeight); + WriteSaveBuf(buf, aCranes[i].m_fHookAngle); + WriteSaveBuf(buf, aCranes[i].m_fHookOffset); + WriteSaveBuf(buf, aCranes[i].m_fHookHeight); + WriteSaveBuf(buf, aCranes[i].m_vecHookInitPos); + WriteSaveBuf(buf, aCranes[i].m_vecHookCurPos); + WriteSaveBuf(buf, aCranes[i].m_vecHookVelocity); + tmp = aCranes[i].m_pVehiclePickedUp != nil ? CPools::GetVehiclePool()->GetJustIndex_NoFreeAssert(aCranes[i].m_pVehiclePickedUp) + 1 : 0; + WriteSaveBuf(buf, tmp); + WriteSaveBuf(buf, aCranes[i].m_nTimeForNextCheck); + WriteSaveBuf(buf, aCranes[i].m_nCraneStatus); + WriteSaveBuf(buf, aCranes[i].m_nCraneState); + WriteSaveBuf(buf, aCranes[i].m_nVehiclesCollected); + WriteSaveBuf(buf, aCranes[i].m_bIsCrusher); + WriteSaveBuf(buf, aCranes[i].m_bIsMilitaryCrane); + WriteSaveBuf(buf, aCranes[i].m_bWasMilitaryCrane); + WriteSaveBuf(buf, aCranes[i].m_bIsTop); + ZeroSaveBuf(buf, 1); +#else + CCrane *pCrane = WriteSaveBuf(buf, aCranes[i]); + if (pCrane->m_pCraneEntity != nil) + pCrane->m_pCraneEntity = (CBuilding*)(CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert(pCrane->m_pCraneEntity) + 1); + if (pCrane->m_pHook != nil) + pCrane->m_pHook = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(pCrane->m_pHook) + 1); + if (pCrane->m_pVehiclePickedUp != nil) + pCrane->m_pVehiclePickedUp = (CVehicle*)(CPools::GetVehiclePool()->GetJustIndex_NoFreeAssert(pCrane->m_pVehiclePickedUp) + 1); +#endif + } + + VALIDATESAVEBUF(*size); +} + +void CCranes::Load(uint8* buf, uint32 size) +{ + INITSAVEBUF + + ReadSaveBuf(&NumCranes, buf); + ReadSaveBuf(&CarsCollectedMilitaryCrane, buf); + for (int i = 0; i < NUM_CRANES; i++) { +#ifdef COMPATIBLE_SAVES + int32 tmp; + ReadSaveBuf(&tmp, buf); + aCranes[i].m_pCraneEntity = tmp != 0 ? CPools::GetBuildingPool()->GetSlot(tmp - 1) : nil; + ReadSaveBuf(&tmp, buf); + aCranes[i].m_pHook = tmp != 0 ? CPools::GetObjectPool()->GetSlot(tmp - 1) : nil; + ReadSaveBuf(&aCranes[i].m_nAudioEntity, buf); + ReadSaveBuf(&aCranes[i].m_fPickupX1, buf); + ReadSaveBuf(&aCranes[i].m_fPickupX2, buf); + ReadSaveBuf(&aCranes[i].m_fPickupY1, buf); + ReadSaveBuf(&aCranes[i].m_fPickupY2, buf); + ReadSaveBuf(&aCranes[i].m_vecDropoffTarget, buf); + ReadSaveBuf(&aCranes[i].m_fDropoffHeading, buf); + ReadSaveBuf(&aCranes[i].m_fPickupAngle, buf); + ReadSaveBuf(&aCranes[i].m_fDropoffAngle, buf); + ReadSaveBuf(&aCranes[i].m_fPickupDistance, buf); + ReadSaveBuf(&aCranes[i].m_fDropoffDistance, buf); + ReadSaveBuf(&aCranes[i].m_fPickupHeight, buf); + ReadSaveBuf(&aCranes[i].m_fDropoffHeight, buf); + ReadSaveBuf(&aCranes[i].m_fHookAngle, buf); + ReadSaveBuf(&aCranes[i].m_fHookOffset, buf); + ReadSaveBuf(&aCranes[i].m_fHookHeight, buf); + ReadSaveBuf(&aCranes[i].m_vecHookInitPos, buf); + ReadSaveBuf(&aCranes[i].m_vecHookCurPos, buf); + ReadSaveBuf(&aCranes[i].m_vecHookVelocity, buf); + ReadSaveBuf(&tmp, buf); + aCranes[i].m_pVehiclePickedUp = tmp != 0 ? CPools::GetVehiclePool()->GetSlot(tmp - 1) : nil; + ReadSaveBuf(&aCranes[i].m_nTimeForNextCheck, buf); + ReadSaveBuf(&aCranes[i].m_nCraneStatus, buf); + ReadSaveBuf(&aCranes[i].m_nCraneState, buf); + ReadSaveBuf(&aCranes[i].m_nVehiclesCollected, buf); + ReadSaveBuf(&aCranes[i].m_bIsCrusher, buf); + ReadSaveBuf(&aCranes[i].m_bIsMilitaryCrane, buf); + ReadSaveBuf(&aCranes[i].m_bWasMilitaryCrane, buf); + ReadSaveBuf(&aCranes[i].m_bIsTop, buf); + SkipSaveBuf(buf, 1); +#else + ReadSaveBuf(&aCranes[i], buf); + } + for (int i = 0; i < NUM_CRANES; i++) { + CCrane *pCrane = &aCranes[i]; + if (pCrane->m_pCraneEntity != nil) + pCrane->m_pCraneEntity = CPools::GetBuildingPool()->GetSlot((uintptr)pCrane->m_pCraneEntity - 1); + if (pCrane->m_pHook != nil) + pCrane->m_pHook = CPools::GetObjectPool()->GetSlot((uintptr)pCrane->m_pHook - 1); + if (pCrane->m_pVehiclePickedUp != nil) + pCrane->m_pVehiclePickedUp = CPools::GetVehiclePool()->GetSlot((uintptr)pCrane->m_pVehiclePickedUp - 1); +#endif + } + for (int i = 0; i < NUM_CRANES; i++) { + aCranes[i].m_nAudioEntity = DMAudio.CreateEntity(AUDIOTYPE_CRANE, &aCranes[i]); + if (aCranes[i].m_nAudioEntity != 0) + DMAudio.SetEntityStatus(aCranes[i].m_nAudioEntity, TRUE); + } + + VALIDATESAVEBUF(size); +} diff --git a/src/vehicles/Cranes.h b/src/vehicles/Cranes.h new file mode 100644 index 0000000..e917810 --- /dev/null +++ b/src/vehicles/Cranes.h @@ -0,0 +1,97 @@ +#pragma once +#include "common.h" + +#include "World.h" + +class CVehicle; +class CEntity; +class CObject; +class CBuilding; + +class CCrane +{ +public: + enum CraneState { + IDLE = 0, + GOING_TOWARDS_TARGET = 1, + LIFTING_TARGET = 2, + GOING_TOWARDS_TARGET_ONLY_HEIGHT = 3, + ROTATING_TARGET = 4, + DROPPING_TARGET = 5 + }; + enum CraneStatus { + NONE = 0, + ACTIVATED = 1, + DEACTIVATED = 2 + }; + CBuilding *m_pCraneEntity; + CObject *m_pHook; + int32 m_nAudioEntity; + float m_fPickupX1; + float m_fPickupX2; + float m_fPickupY1; + float m_fPickupY2; + CVector m_vecDropoffTarget; + float m_fDropoffHeading; + float m_fPickupAngle; + float m_fDropoffAngle; + float m_fPickupDistance; + float m_fDropoffDistance; + float m_fPickupHeight; + float m_fDropoffHeight; + float m_fHookAngle; + float m_fHookOffset; + float m_fHookHeight; + CVector m_vecHookInitPos; + CVector m_vecHookCurPos; + CVector2D m_vecHookVelocity; + CVehicle *m_pVehiclePickedUp; + uint32 m_nTimeForNextCheck; + uint8 m_nCraneStatus; + uint8 m_nCraneState; + uint8 m_nVehiclesCollected; + bool m_bIsCrusher; + bool m_bIsMilitaryCrane; + bool m_bWasMilitaryCrane; + bool m_bIsTop; + + void Init(void) { memset(this, 0, sizeof(*this)); } + void Update(void); + bool RotateCarriedCarProperly(void); + void FindCarInSectorList(CPtrList* pList); + bool DoesCranePickUpThisCarType(uint32 mi); + bool GoTowardsTarget(float fAngleToTarget, float fDistanceToTarget, float fTargetHeight, float fSpeedMultiplier = 1.0f); + bool GoTowardsHeightTarget(float fTargetHeight, float fSpeedMultiplier = 1.0f); + void FindParametersForTarget(float X, float Y, float Z, float* pAngle, float* pDistance, float* pHeight); + void CalcHookCoordinates(float* pX, float* pY, float* pZ); + void SetHookMatrix(void); + + float GetHeightToPickup() { return 4.0f + m_fPickupHeight + (m_bIsCrusher ? 4.5f : 0.0f); }; + float GetHeightToDropoff() { return m_bIsCrusher ? (2.0f + m_fDropoffHeight + 3.0f) : (2.0f + m_fDropoffHeight); } + float GetHeightToPickupHeight() { return m_fPickupHeight + (m_bIsCrusher ? 7.0f : 4.0f); } + float GetHeightToDropoffHeight() { return m_fDropoffHeight + (m_bIsCrusher ? 7.0f : 2.0f); } +}; + +VALIDATE_SIZE(CCrane, 128); + +class CCranes +{ +public: + static void InitCranes(void); + static void AddThisOneCrane(CEntity* pCraneEntity); + static void ActivateCrane(float fInfX, float fSupX, float fInfY, float fSupY, float fDropOffX, float fDropOffY, float fDropOffZ, float fHeading, bool bIsCrusher, bool bIsMilitary, float fPosX, float fPosY); + static void DeActivateCrane(float fX, float fY); + static bool IsThisCarPickedUp(float fX, float fY, CVehicle* pVehicle); + static void UpdateCranes(void); + static bool DoesMilitaryCraneHaveThisOneAlready(uint32 mi); + static void RegisterCarForMilitaryCrane(uint32 mi); + static bool HaveAllCarsBeenCollectedByMilitaryCrane(void); + static bool IsThisCarBeingCarriedByAnyCrane(CVehicle* pVehicle); + static bool IsThisCarBeingTargettedByAnyCrane(CVehicle* pVehicle); + static void Save(uint8* buf, uint32* size); + static void Load(uint8* buf, uint32 size); // out of class in III PC and later because of SecuROM + + static uint32 CarsCollectedMilitaryCrane; + static int32 NumCranes; + static CCrane aCranes[NUM_CRANES]; +}; diff --git a/src/vehicles/DamageManager.cpp b/src/vehicles/DamageManager.cpp new file mode 100644 index 0000000..56034de --- /dev/null +++ b/src/vehicles/DamageManager.cpp @@ -0,0 +1,229 @@ +#include "common.h" + +#include "General.h" +#include "Vehicle.h" +#include "DamageManager.h" + + +float G_aComponentDamage[] = { 2.5f, 1.25f, 3.2f, 1.4f, 2.5f, 2.8f, 0.5f }; + +CDamageManager::CDamageManager(void) +{ + ResetDamageStatus(); + m_fWheelDamageEffect = 0.75f; + field_18 = 1; +} + +void +CDamageManager::ResetDamageStatus(void) +{ + memset(this, 0, sizeof(*this)); +} + +void +CDamageManager::FuckCarCompletely(void) +{ + int i; + + m_wheelStatus[0] = WHEEL_STATUS_MISSING; + // wheels 1-3 not reset? + + for(i = 0; i < ARRAY_SIZE(m_doorStatus); i++) + m_doorStatus[i] = DOOR_STATUS_MISSING; + + for(i = 0; i < 3; i++){ +#ifdef FIX_BUGS + ProgressPanelDamage(VEHBUMPER_FRONT); + ProgressPanelDamage(VEHBUMPER_REAR); +#else + // this can't be right + ProgressPanelDamage(COMPONENT_BUMPER_FRONT); + ProgressPanelDamage(COMPONENT_BUMPER_REAR); +#endif + } + // Why set to no damage? +#ifndef FIX_BUGS + m_lightStatus = 0; + m_panelStatus = 0; +#endif + SetEngineStatus(250); +} + +bool +CDamageManager::ApplyDamage(tComponent component, float damage, float unused) +{ + tComponentGroup group; + uint8 subComp; + + GetComponentGroup(component, &group, &subComp); + damage *= G_aComponentDamage[group]; + if(damage > 150.0f){ + switch(group){ + case COMPGROUP_WHEEL: + ProgressWheelDamage(subComp); + break; + case COMPGROUP_DOOR: + case COMPGROUP_BOOT: + ProgressDoorDamage(subComp); + break; + case COMPGROUP_BONNET: + if(damage > 220.0f) + ProgressEngineDamage(); + ProgressDoorDamage(subComp); + break; + case COMPGROUP_PANEL: + // so windscreen is a light? + SetLightStatus((eLights)subComp, 1); + // fall through + case COMPGROUP_BUMPER: + if(damage > 220.0f && + (component == COMPONENT_PANEL_FRONT_LEFT || + component == COMPONENT_PANEL_FRONT_RIGHT || + component == COMPONENT_PANEL_WINDSCREEN)) + ProgressEngineDamage(); + ProgressPanelDamage(subComp); + break; + default: break; + } + return true; + } + return false; +} + +bool +CDamageManager::GetComponentGroup(tComponent component, tComponentGroup *componentGroup, uint8 *subComp) +{ + *subComp = -2; // ?? + + // This is done very strangely in the game, maybe an optimized switch? + if(component >= COMPONENT_PANEL_FRONT_LEFT){ + if(component >= COMPONENT_BUMPER_FRONT) + *componentGroup = COMPGROUP_BUMPER; + else + *componentGroup = COMPGROUP_PANEL; + *subComp = component - COMPONENT_PANEL_FRONT_LEFT; + return true; + }else if(component >= COMPONENT_DOOR_BONNET){ + if(component == COMPONENT_DOOR_BONNET) + *componentGroup = COMPGROUP_BONNET; + else if(component == COMPONENT_DOOR_BOOT) + *componentGroup = COMPGROUP_BOOT; + else + *componentGroup = COMPGROUP_DOOR; + *subComp = component - COMPONENT_DOOR_BONNET; + return true; + }else if(component >= COMPONENT_WHEEL_FRONT_LEFT){ + *componentGroup = COMPGROUP_WHEEL; + *subComp = component - COMPONENT_WHEEL_FRONT_LEFT; + return true; + }else if(component >= COMPONENT_DEFAULT){ + *componentGroup = COMPGROUP_DEFAULT; + *subComp = COMPONENT_DEFAULT; + return true; + }else + return false; +} + +void +CDamageManager::SetDoorStatus(int32 door, uint32 status) +{ + m_doorStatus[door] = status; +} + +int32 +CDamageManager::GetDoorStatus(int32 door) +{ + return m_doorStatus[door]; +} + +bool +CDamageManager::ProgressDoorDamage(uint8 door) +{ + int status = GetDoorStatus(door); + if(status == PANEL_STATUS_MISSING) + return false; + SetDoorStatus(door, status+1); + return true; +} + +void +CDamageManager::SetPanelStatus(int32 panel, uint32 status) +{ + m_panelStatus = dpb(status, panel*4, 4, m_panelStatus); +} + +int32 +CDamageManager::GetPanelStatus(int32 panel) +{ + return ldb(panel*4, 4, m_panelStatus); +} + +bool +CDamageManager::ProgressPanelDamage(uint8 panel) +{ + int status = GetPanelStatus(panel); + if(status == DOOR_STATUS_MISSING) + return false; + SetPanelStatus(panel, status+1); + return true; +} + +void +CDamageManager::SetLightStatus(eLights light, uint32 status) +{ + m_lightStatus = dpb(status, light*2, 2, m_lightStatus); +} + +int32 +CDamageManager::GetLightStatus(eLights light) +{ + return ldb(light*2, 2, m_lightStatus); +} + +void +CDamageManager::SetWheelStatus(int32 wheel, uint32 status) +{ + m_wheelStatus[wheel] = status; +} + +int32 +CDamageManager::GetWheelStatus(int32 wheel) +{ + return m_wheelStatus[wheel]; +} + +bool +CDamageManager::ProgressWheelDamage(uint8 wheel) +{ + int status = GetWheelStatus(wheel); + if(status == WHEEL_STATUS_MISSING) + return false; + SetWheelStatus(wheel, status+1); + return true; +} + +void +CDamageManager::SetEngineStatus(uint32 status) +{ + if(status > 250) + m_engineStatus = 250; + else + m_engineStatus = status; +} + +int32 +CDamageManager::GetEngineStatus(void) +{ + return m_engineStatus; +} + +bool +CDamageManager::ProgressEngineDamage(void) +{ + int status = GetEngineStatus(); + int newstatus = status + 32 + (CGeneral::GetRandomNumber() & 0x1F); + if(status < ENGINE_STATUS_ON_FIRE && newstatus > ENGINE_STATUS_ON_FIRE-1) + newstatus = ENGINE_STATUS_ON_FIRE-1; + SetEngineStatus(newstatus); + return true; +} diff --git a/src/vehicles/DamageManager.h b/src/vehicles/DamageManager.h new file mode 100644 index 0000000..312006e --- /dev/null +++ b/src/vehicles/DamageManager.h @@ -0,0 +1,115 @@ +#pragma once + +#include "common.h" + +// TODO: move some of this into Vehicle.h + +enum eEngineStatus +{ + ENGINE_STATUS_STEAM1 = 100, + ENGINE_STATUS_STEAM2 = 150, + ENGINE_STATUS_SMOKE = 200, + ENGINE_STATUS_ON_FIRE = 225 +}; + +enum eDoorStatus +{ + DOOR_STATUS_OK, + DOOR_STATUS_SMASHED, + DOOR_STATUS_SWINGING, + DOOR_STATUS_MISSING +}; + +enum ePanelStatus +{ + PANEL_STATUS_OK, + PANEL_STATUS_SMASHED1, + PANEL_STATUS_SMASHED2, + PANEL_STATUS_MISSING, +}; + +enum eWheelStatus +{ + WHEEL_STATUS_OK, + WHEEL_STATUS_BURST, + WHEEL_STATUS_MISSING +}; + +enum eLightStatus +{ + LIGHT_STATUS_OK, + LIGHT_STATUS_BROKEN +}; + +enum tComponent +{ + COMPONENT_DEFAULT, + COMPONENT_WHEEL_FRONT_LEFT, + COMPONENT_WHEEL_FRONT_RIGHT, + COMPONENT_WHEEL_REAR_LEFT, + COMPONENT_WHEEL_REAR_RIGHT, + COMPONENT_DOOR_BONNET, + COMPONENT_DOOR_BOOT, + COMPONENT_DOOR_FRONT_LEFT, + COMPONENT_DOOR_FRONT_RIGHT, + COMPONENT_DOOR_REAR_LEFT, + COMPONENT_DOOR_REAR_RIGHT, + COMPONENT_PANEL_FRONT_LEFT, + COMPONENT_PANEL_FRONT_RIGHT, + COMPONENT_PANEL_REAR_LEFT, + COMPONENT_PANEL_REAR_RIGHT, + COMPONENT_PANEL_WINDSCREEN, + COMPONENT_BUMPER_FRONT, + COMPONENT_BUMPER_REAR, +}; + +enum tComponentGroup +{ + COMPGROUP_BUMPER, + COMPGROUP_WHEEL, + COMPGROUP_DOOR, + COMPGROUP_BONNET, + COMPGROUP_BOOT, + COMPGROUP_PANEL, + COMPGROUP_DEFAULT, +}; + +enum eLights; + +class CDamageManager +{ +public: + + float m_fWheelDamageEffect; + uint8 m_engineStatus; + uint8 m_wheelStatus[4]; + uint8 m_doorStatus[6]; + uint32 m_lightStatus; + uint32 m_panelStatus; + uint8 field_18; + + CDamageManager(void); + + void ResetDamageStatus(void); + void FuckCarCompletely(void); + bool ApplyDamage(tComponent component, float damage, float unused); + bool GetComponentGroup(tComponent component, tComponentGroup *componentGroup, uint8 *foo); + + void SetDoorStatus(int32 door, uint32 status); + int32 GetDoorStatus(int32 door); + bool ProgressDoorDamage(uint8 door); + void SetPanelStatus(int32 panel, uint32 status); + int32 GetPanelStatus(int32 panel); + bool ProgressPanelDamage(uint8 panel); + // needed for CReplay + static int32 GetPanelStatus(uint32 panelstatus, int32 panel) { return ldb(panel*4, 4, panelstatus); } + void SetLightStatus(eLights light, uint32 status); + int32 GetLightStatus(eLights light); + void SetWheelStatus(int32 wheel, uint32 status); + int32 GetWheelStatus(int32 wheel); + bool ProgressWheelDamage(uint8 wheel); + void SetEngineStatus(uint32 status); + int32 GetEngineStatus(void); + bool ProgressEngineDamage(void); +}; +VALIDATE_SIZE(CDamageManager, 0x1C); diff --git a/src/vehicles/Door.cpp b/src/vehicles/Door.cpp new file mode 100644 index 0000000..1b3f9e8 --- /dev/null +++ b/src/vehicles/Door.cpp @@ -0,0 +1,170 @@ +#include "common.h" + +#include "Vehicle.h" +#include "Door.h" + +CDoor::CDoor(void) +{ + memset(this, 0, sizeof(*this)); +} + +void +CDoor::Open(float ratio) +{ + float open; + + m_fPrevAngle = m_fAngle; + open = RetAngleWhenOpen(); + if(ratio < 1.0f){ + m_fAngle = open*ratio; + if(m_fAngle == 0.0f) + m_fAngVel = 0.0f; + }else{ + m_nDoorState = DOORST_OPEN; + m_fAngle = open; + } +} + +void +CDoor::Process(CVehicle *vehicle) +{ + static CVector vecOffset(1.0f, 0.0f, 0.0f); + CVector speed = vehicle->GetSpeed(vecOffset); + CVector vecSpeedDiff = speed - m_vecSpeed; + vecSpeedDiff = Multiply3x3(vecSpeedDiff, vehicle->GetMatrix()); + + // air resistance + float fSpeedDiff = 0.0f; // uninitialized in game + switch(m_nAxis){ + case 0: // x-axis + if(m_nDirn) + fSpeedDiff = vecSpeedDiff.y + vecSpeedDiff.z; + else + fSpeedDiff = -(vecSpeedDiff.y + vecSpeedDiff.z); + break; + + // we don't support y axis apparently? + + case 2: // z-axis + if(m_nDirn) + fSpeedDiff = -(vecSpeedDiff.y + vecSpeedDiff.x); + else + fSpeedDiff = vecSpeedDiff.y - vecSpeedDiff.x; + break; + } + fSpeedDiff = Clamp(fSpeedDiff, -0.2f, 0.2f); + if(Abs(fSpeedDiff) > 0.002f) + m_fAngVel += fSpeedDiff; + m_fAngVel *= 0.945f; + m_fAngVel = Clamp(m_fAngVel, -0.3f, 0.3f); + + m_fAngle += m_fAngVel; + m_nDoorState = DOORST_SWINGING; + if(m_fAngle > m_fMaxAngle){ + m_fAngle = m_fMaxAngle; + m_fAngVel *= -0.8f; + m_nDoorState = DOORST_OPEN; + } + if(m_fAngle < m_fMinAngle){ + m_fAngle = m_fMinAngle; + m_fAngVel *= -0.8f; + m_nDoorState = DOORST_CLOSED; + } + m_vecSpeed = speed; +} + +float +CDoor::RetAngleWhenClosed(void) +{ + if(Abs(m_fMaxAngle) < Abs(m_fMinAngle)) + return m_fMaxAngle; + else + return m_fMinAngle; +} + +float +CDoor::RetAngleWhenOpen(void) +{ + if(Abs(m_fMaxAngle) < Abs(m_fMinAngle)) + return m_fMinAngle; + else + return m_fMaxAngle; +} + +float +CDoor::GetAngleOpenRatio(void) +{ + float open = RetAngleWhenOpen(); + if(open == 0.0f) + return 0.0f; + return m_fAngle/open; +} + +bool +CDoor::IsFullyOpen(void) +{ + // why -0.5? that's around 28 deg less than fully open + if(Abs(m_fAngle) < Abs(RetAngleWhenOpen()) - 0.5f) + return false; + return true; +} + +bool +CDoor::IsClosed(void) +{ + return m_fAngle == RetAngleWhenClosed(); +} + + +CTrainDoor::CTrainDoor(void) +{ + memset(this, 0, sizeof(*this)); +} + +void +CTrainDoor::Open(float ratio) +{ + float open; + + m_fPrevPosn = m_fPosn; + open = RetTranslationWhenOpen(); + if(ratio < 1.0f){ + m_fPosn = open*ratio; + }else{ + m_nDoorState = DOORST_OPEN; + m_fPosn = open; + } +} + +float +CTrainDoor::RetTranslationWhenClosed(void) +{ + if(Abs(m_fClosedPosn) < Abs(m_fOpenPosn)) + return m_fClosedPosn; + else + return m_fOpenPosn; +} + +float +CTrainDoor::RetTranslationWhenOpen(void) +{ + if(Abs(m_fClosedPosn) < Abs(m_fOpenPosn)) + return m_fOpenPosn; + else + return m_fClosedPosn; +} + +bool +CTrainDoor::IsFullyOpen(void) +{ + // 0.5f again... + if(Abs(m_fPosn) < Abs(RetTranslationWhenOpen()) - 0.5f) + return false; + return true; +} + +bool +CTrainDoor::IsClosed(void) +{ + return m_fPosn == RetTranslationWhenClosed(); +} diff --git a/src/vehicles/Door.h b/src/vehicles/Door.h new file mode 100644 index 0000000..567d326 --- /dev/null +++ b/src/vehicles/Door.h @@ -0,0 +1,69 @@ +#pragma once + +class CVehicle; + +enum eDoorState +{ + DOORST_SWINGING, + // actually wrong though, + // OPEN is really MAX_ANGLE and CLOSED is MIN_ANGLE + DOORST_OPEN, + DOORST_CLOSED +}; + +class CDoor +{ +public: + float m_fMaxAngle; + float m_fMinAngle; + // direction of rotation for air resistance + int8 m_nDirn; + // axis in which this door rotates + int8 m_nAxis; + int8 m_nDoorState; + float m_fAngle; + float m_fPrevAngle; + float m_fAngVel; + CVector m_vecSpeed; + + CDoor(void); + void Init(float minAngle, float maxAngle, int8 dir, int8 axis) { + m_fMinAngle = minAngle; + m_fMaxAngle = maxAngle; + m_nDirn = dir; + m_nAxis = axis; + } + void Open(float ratio); + void Process(CVehicle *veh); + float RetAngleWhenClosed(void); // dead + float RetAngleWhenOpen(void); + float GetAngleOpenRatio(void); + bool IsFullyOpen(void); + bool IsClosed(void); // dead +}; + +class CTrainDoor +{ +public: + float m_fClosedPosn; + float m_fOpenPosn; + int8 m_nDirn; + int8 m_nDoorState; // same enum as above? + int8 m_nAxis; + float m_fPosn; + float m_fPrevPosn; + int field_14; // unused? + + CTrainDoor(void); + void Init(float open, float closed, int8 dir, int8 axis) { + m_fOpenPosn = open; + m_fClosedPosn = closed; + m_nDirn = dir; + m_nAxis = axis; + } + bool IsClosed(void); + bool IsFullyOpen(void); + float RetTranslationWhenClosed(void); + float RetTranslationWhenOpen(void); + void Open(float ratio); +}; diff --git a/src/vehicles/Floater.cpp b/src/vehicles/Floater.cpp new file mode 100644 index 0000000..4331090 --- /dev/null +++ b/src/vehicles/Floater.cpp @@ -0,0 +1,185 @@ +#include "common.h" + +#include "Timer.h" +#include "WaterLevel.h" +#include "ModelIndices.h" +#include "Physical.h" +#include "Vehicle.h" +#include "Floater.h" + +cBuoyancy mod_Buoyancy; + +float fVolMultiplier = 1.0f; +// amount of boat volume in bounding box +// 1.0-volume is the empty space in the bbox +float fBoatVolumeDistribution[9] = { + // rear + 0.75f, 0.9f, 0.75f, + 0.95f, 1.0f, 0.95f, + 0.3f, 0.7f, 0.3f + // bow +}; + +bool +cBuoyancy::ProcessBuoyancy(CPhysical *phys, float buoyancy, CVector *point, CVector *impulse) +{ + m_numSteps = 2.0f; + + if(!CWaterLevel::GetWaterLevel(phys->GetPosition(), &m_waterlevel, phys->bTouchingWater)) + return false; + m_matrix = phys->GetMatrix(); + + PreCalcSetup(phys, buoyancy); + SimpleCalcBuoyancy(); + float f = CalcBuoyancyForce(phys, point, impulse); + if(m_isBoat) + return true; + return f != 0.0f; +} + +void +cBuoyancy::PreCalcSetup(CPhysical *phys, float buoyancy) +{ + CColModel *colModel; + + m_isBoat = phys->IsVehicle() && ((CVehicle*)phys)->IsBoat(); + colModel = phys->GetColModel(); + m_dimMin = colModel->boundingBox.min; + m_dimMax = colModel->boundingBox.max; + + if(m_isBoat){ + if(phys->GetModelIndex() == MI_PREDATOR){ + m_dimMax.y *= 0.9f; + m_dimMin.y *= 0.9f; + }else if(phys->GetModelIndex() == MI_SPEEDER){ + m_dimMax.y *= 1.1f; + m_dimMin.y *= 0.9f; + }else if(phys->GetModelIndex() == MI_REEFER){ + m_dimMin.y *= 0.9f; + }else{ + m_dimMax.y *= 0.9f; + m_dimMin.y *= 0.9f; + } + } + + m_step = (m_dimMax - m_dimMin)/m_numSteps; + + if(m_step.z > m_step.x && m_step.z > m_step.y){ + m_stepRatio.x = m_step.x/m_step.z; + m_stepRatio.y = m_step.y/m_step.z; + m_stepRatio.z = 1.0f; + }else if(m_step.y > m_step.x && m_step.y > m_step.z){ + m_stepRatio.x = m_step.x/m_step.y; + m_stepRatio.y = 1.0f; + m_stepRatio.z = m_step.z/m_step.y; + }else{ + m_stepRatio.x = 1.0f; + m_stepRatio.y = m_step.y/m_step.x; + m_stepRatio.z = m_step.z/m_step.x; + } + + m_haveVolume = false; + m_numPartialVolumes = 1.0f; + m_volumeUnderWater = 0.0f; + m_impulsePoint = CVector(0.0f, 0.0f, 0.0f); + m_position = phys->GetPosition(); + m_positionZ = CVector(0.0f, 0.0f, m_position.z); + m_buoyancy = buoyancy; + m_waterlevel += m_waterLevelInc; +} + +void +cBuoyancy::SimpleCalcBuoyancy(void) +{ + float x, y; + int ix, i; + tWaterLevel waterPosition; + + // Floater is divided into 3x3 parts. Process and sum each of them + ix = 0; + for(x = m_dimMin.x; x <= m_dimMax.x; x += m_step.x){ + i = ix; + for(y = m_dimMin.y; y <= m_dimMax.y; y += m_step.y){ + CVector waterLevel(x, y, 0.0f); + FindWaterLevel(m_positionZ, &waterLevel, &waterPosition); + fVolMultiplier = m_isBoat ? fBoatVolumeDistribution[i] : 1.0f; + if(waterPosition != FLOATER_ABOVE_WATER) + SimpleSumBuoyancyData(waterLevel, waterPosition); + i += 3; + } + ix++; + } + + m_volumeUnderWater /= (m_dimMax.z - m_dimMin.z)*sq(m_numSteps+1.0f); +} + +float +cBuoyancy::SimpleSumBuoyancyData(CVector &waterLevel, tWaterLevel waterPosition) +{ + static float fThisVolume; + static CVector AverageOfWaterLevel; + static float fFraction; + static float fRemainingSlice; + + float submerged = Abs(waterLevel.z - m_dimMin.z); + // subtract empty space from submerged volume + fThisVolume = submerged - (1.0f - fVolMultiplier); + if(fThisVolume < 0.0f) + return 0.0f; + + if(m_isBoat){ + fThisVolume *= fVolMultiplier; + if(fThisVolume < 0.5f) + fThisVolume = 2.0f*sq(fThisVolume); + if(fThisVolume < 1.0f) + fThisVolume = sq(fThisVolume); + fThisVolume = sq(fThisVolume); + } + + m_volumeUnderWater += fThisVolume; + + AverageOfWaterLevel.x = waterLevel.x * m_stepRatio.x; + AverageOfWaterLevel.y = waterLevel.y * m_stepRatio.y; + AverageOfWaterLevel.z = (waterLevel.z+m_dimMin.z)/2.0f * m_stepRatio.z; + + if(m_flipAverage) + AverageOfWaterLevel = -AverageOfWaterLevel; + + fFraction = 1.0f/m_numPartialVolumes; + fRemainingSlice = 1.0f - fFraction; + m_impulsePoint = m_impulsePoint*fRemainingSlice + AverageOfWaterLevel*fThisVolume*fFraction; + m_numPartialVolumes += 1.0f; + m_haveVolume = true; + return fThisVolume; +} + +void +cBuoyancy::FindWaterLevel(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition) +{ + *waterPosition = FLOATER_IN_WATER; + // waterLevel is a local x,y point + // m_position is the global position of our floater + // zpos is the global z coordinate of our floater + CVector xWaterLevel = Multiply3x3(m_matrix, *waterLevel); + CWaterLevel::GetWaterLevel(xWaterLevel.x + m_position.x, xWaterLevel.y + m_position.y, m_position.z, + &waterLevel->z, true); + waterLevel->z -= xWaterLevel.z + zpos.z; // make local + if(waterLevel->z > m_dimMax.z){ + waterLevel->z = m_dimMax.z; + *waterPosition = FLOATER_UNDER_WATER; + }else if(waterLevel->z < m_dimMin.z){ + waterLevel->z = m_dimMin.z; + *waterPosition = FLOATER_ABOVE_WATER; + } +} + +bool +cBuoyancy::CalcBuoyancyForce(CPhysical *phys, CVector *point, CVector *impulse) +{ + if(!m_haveVolume) + return false; + + *point = Multiply3x3(m_matrix, m_impulsePoint); + *impulse = CVector(0.0f, 0.0f, m_volumeUnderWater*m_buoyancy*CTimer::GetTimeStep()); + return true; +} diff --git a/src/vehicles/Floater.h b/src/vehicles/Floater.h new file mode 100644 index 0000000..1cfb46f --- /dev/null +++ b/src/vehicles/Floater.h @@ -0,0 +1,45 @@ +#pragma once + +class CPhysical; + +enum tWaterLevel +{ + FLOATER_ABOVE_WATER, + FLOATER_IN_WATER, + FLOATER_UNDER_WATER, +}; + +class cBuoyancy +{ +public: + CVector m_position; + CMatrix m_matrix; + int m_field_54; + CVector m_positionZ; + float m_waterlevel; + float m_waterLevelInc; + float m_buoyancy; + CVector m_dimMax; + CVector m_dimMin; + float m_numPartialVolumes; + int m_field_8C; + int m_field_90; + int m_field_94; + bool m_haveVolume; + CVector m_step; + CVector m_stepRatio; + float m_numSteps; + bool m_flipAverage; + char m_field_B9; + bool m_isBoat; + float m_volumeUnderWater; + CVector m_impulsePoint; + + bool ProcessBuoyancy(CPhysical *phys, float buoyancy, CVector *point, CVector *impulse); + void PreCalcSetup(CPhysical *phys, float buoyancy); + void SimpleCalcBuoyancy(void); + float SimpleSumBuoyancyData(CVector &waterLevel, tWaterLevel waterPosition); + void FindWaterLevel(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition); + bool CalcBuoyancyForce(CPhysical *phys, CVector *impulse, CVector *point); +}; +extern cBuoyancy mod_Buoyancy; diff --git a/src/vehicles/HandlingMgr.cpp b/src/vehicles/HandlingMgr.cpp new file mode 100644 index 0000000..00aaa68 --- /dev/null +++ b/src/vehicles/HandlingMgr.cpp @@ -0,0 +1,270 @@ +#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", + "BLISTA", + "PONY", + "MULE", + "CHEETAH", + "AMBULAN", + "FBICAR", + "MOONBEAM", + "ESPERANT", + "TAXI", + "KURUMA", + "BOBCAT", + "MRWHOOP", + "BFINJECT", + "POLICE", + "ENFORCER", + "SECURICA", + "BANSHEE", + "PREDATOR", + "BUS", + "RHINO", + "BARRACKS", + "TRAIN", + "HELI", + "DODO", + "COACH", + "CABBIE", + "STALLION", + "RUMPO", + "RCBANDIT", + "BELLYUP", + "MRWONGS", + "MAFIA", + "YARDIE", + "YAKUZA", + "DIABLOS", + "COLUMB", + "HOODS", + "AIRTRAIN", + "DEADDODO", + "SPEEDER", + "REEFER", + "PANLANT", + "FLATBED", + "YANKEE", + "BORGNINE" +}; + +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; + + CFileMgr::SetDir("DATA"); + CFileMgr::LoadFile(HandlingFilename, work_buff, sizeof(work_buff), "r"); + CFileMgr::SetDir(""); + + start = (char*)work_buff; + end = start+1; + handling = 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] != ';'){ + 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 = strtod(word, nil); break; + case 2: handling->Dimension.x = strtod(word, nil); break; + case 3: handling->Dimension.y = strtod(word, nil); break; + case 4: handling->Dimension.z = strtod(word, nil); break; + case 5: handling->CentreOfMass.x = strtod(word, nil); break; + case 6: handling->CentreOfMass.y = strtod(word, nil); break; + case 7: handling->CentreOfMass.z = strtod(word, nil); break; + case 8: handling->nPercentSubmerged = atoi(word); break; + case 9: handling->fTractionMultiplier = strtod(word, nil); break; + case 10: handling->fTractionLoss = strtod(word, nil); break; + case 11: handling->fTractionBias = strtod(word, nil); break; + case 12: handling->Transmission.nNumberOfGears = atoi(word); break; + case 13: handling->Transmission.fMaxVelocity = strtod(word, nil); break; + case 14: handling->Transmission.fEngineAcceleration = strtod(word, nil) * 0.4; break; + case 15: handling->Transmission.nDriveType = word[0]; break; + case 16: handling->Transmission.nEngineType = word[0]; break; + case 17: handling->fBrakeDeceleration = strtod(word, nil); break; + case 18: handling->fBrakeBias = strtod(word, nil); break; + case 19: handling->bABS = !!atoi(word); break; + case 20: handling->fSteeringLock = strtod(word, nil); break; + case 21: handling->fSuspensionForceLevel = strtod(word, nil); break; + case 22: handling->fSuspensionDampingLevel = strtod(word, nil); break; + case 23: handling->fSeatOffsetDistance = strtod(word, nil); break; + case 24: handling->fCollisionDamageMultiplier = strtod(word, nil); break; + case 25: handling->nMonetaryValue = atoi(word); break; + case 26: handling->fSuspensionUpperLimit = strtod(word, nil); break; + case 27: handling->fSuspensionLowerLimit = strtod(word, nil); break; + case 28: handling->fSuspensionBias = strtod(word, nil); break; + case 29: + sscanf(word, "%x", &handling->Flags); + handling->Transmission.Flags = handling->Flags; + break; + case 30: handling->FrontLights = atoi(word); break; + case 31: 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) +{ + // acceleration is in ms^-2, but we need mf^-2 where f is one frame time (50fps) + 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->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; + }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(); +} + +int32 +cHandlingDataMgr::GetHandlingId(const char *name) +{ + int i; + for(i = 0; i < NUMHANDLINGS; i++) + if(strncmp(VehicleNames[i], name, 14) == 0) + break; + return i; +} + +void +cHandlingDataMgr::ConvertDataToWorldUnits(tHandlingData *handling) +{ + // TODO: mobile code +} + +void +cHandlingDataMgr::RangeCheck(tHandlingData *handling) +{ + // TODO: mobile code +} + +void +cHandlingDataMgr::ModifyHandlingValue(CVehicle *, const tVehicleType &, const tField &, const bool &) +{ + // TODO: mobile code +} + +void +cHandlingDataMgr::DisplayHandlingData(CVehicle *, tHandlingData *, uint8, bool) +{ + // TODO: mobile code +} \ No newline at end of file diff --git a/src/vehicles/HandlingMgr.h b/src/vehicles/HandlingMgr.h new file mode 100644 index 0000000..9848bb7 --- /dev/null +++ b/src/vehicles/HandlingMgr.h @@ -0,0 +1,156 @@ +#pragma once + +#include "Transmission.h" + +enum tVehicleType +{ + HANDLING_LANDSTAL, + HANDLING_IDAHO, + HANDLING_STINGER, + HANDLING_LINERUN, + HANDLING_PEREN, + HANDLING_SENTINEL, + HANDLING_PATRIOT, + HANDLING_FIRETRUK, + HANDLING_TRASH, + HANDLING_STRETCH, + HANDLING_MANANA, + HANDLING_INFERNUS, + HANDLING_BLISTA, + HANDLING_PONY, + HANDLING_MULE, + HANDLING_CHEETAH, + HANDLING_AMBULAN, + HANDLING_FBICAR, + HANDLING_MOONBEAM, + HANDLING_ESPERANT, + HANDLING_TAXI, + HANDLING_KURUMA, + HANDLING_BOBCAT, + HANDLING_MRWHOOP, + HANDLING_BFINJECT, + HANDLING_POLICE, + HANDLING_ENFORCER, + HANDLING_SECURICA, + HANDLING_BANSHEE, + HANDLING_PREDATOR, + HANDLING_BUS, + HANDLING_RHINO, + HANDLING_BARRACKS, + HANDLING_TRAIN, + HANDLING_HELI, + HANDLING_DODO, + HANDLING_COACH, + HANDLING_CABBIE, + HANDLING_STALLION, + HANDLING_RUMPO, + HANDLING_RCBANDIT, + HANDLING_BELLYUP, + HANDLING_MRWONGS, + HANDLING_MAFIA, + HANDLING_YARDIE, + HANDLING_YAKUZA, + HANDLING_DIABLOS, + HANDLING_COLUMB, + HANDLING_HOODS, + HANDLING_AIRTRAIN, + HANDLING_DEADDODO, + HANDLING_SPEEDER, + HANDLING_REEFER, + HANDLING_PANLANT, + HANDLING_FLATBED, + HANDLING_YANKEE, + HANDLING_BORGNINE, + + NUMHANDLINGS +}; + +enum tField // most likely a handling field enum, never used so :shrug: +{ + +}; + +enum +{ + HANDLING_1G_BOOST = 1, + HANDLING_2G_BOOST = 2, + HANDLING_REV_BONNET = 4, + HANDLING_HANGING_BOOT = 8, + HANDLING_NO_DOORS = 0x10, + HANDLING_IS_VAN = 0x20, + HANDLING_IS_BUS = 0x40, + HANDLING_IS_LOW = 0x80, + HANDLING_DBL_EXHAUST = 0x100, + HANDLING_TAILGATE_BOOT = 0x200, + HANDLING_NOSWING_BOOT = 0x400, + HANDLING_NONPLAYER_STABILISER = 0x800, + HANDLING_NEUTRALHANDLING = 0x1000, + HANDLING_HAS_NO_ROOF = 0x2000, + HANDLING_IS_BIG = 0x4000, + HANDLING_HALOGEN_LIGHTS = 0x8000, +}; + +struct tHandlingData +{ + tVehicleType nIdentifier; + float fMass; + float fInvMass; + float fTurnMass; + CVector Dimension; + CVector CentreOfMass; + int8 nPercentSubmerged; + float fBuoyancy; + float fTractionMultiplier; + cTransmission Transmission; + float fBrakeDeceleration; + float fBrakeBias; + int8 bABS; + float fSteeringLock; + float fTractionLoss; + float fTractionBias; + float fUnused; + float fSuspensionForceLevel; + float fSuspensionDampingLevel; + float fSuspensionUpperLimit; + float fSuspensionLowerLimit; + float fSuspensionBias; + float fCollisionDamageMultiplier; + uint32 Flags; + float fSeatOffsetDistance; + int32 nMonetaryValue; + int8 FrontLights; + int8 RearLights; +}; +VALIDATE_SIZE(tHandlingData, 0xD8); + +class CVehicle; + +class cHandlingDataMgr +{ + float field_0; // unused it seems +public: + float fWheelFriction; // wheel related +private: + float field_8; // + float field_C; // unused it seems + float field_10; // + tHandlingData HandlingData[NUMHANDLINGS]; + uint32 field_302C; // unused it seems + +public: + cHandlingDataMgr(void); + void Initialise(void); + void LoadHandlingData(void); + int FindExactWord(const char *word, const char *words, int wordLen, int numWords); + void ConvertDataToWorldUnits(tHandlingData *handling); + void ConvertDataToGameUnits(tHandlingData *handling); + void RangeCheck(tHandlingData *handling); + void ModifyHandlingValue(CVehicle *, const tVehicleType &, const tField &, const bool &); + void DisplayHandlingData(CVehicle *, tHandlingData *, uint8, bool); + int32 GetHandlingId(const char *name); + tHandlingData *GetHandlingData(tVehicleType id) { return &HandlingData[id]; } + bool HasRearWheelDrive(tVehicleType id) { return HandlingData[id].Transmission.nDriveType != 'F'; } + bool HasFrontWheelDrive(tVehicleType id) { return HandlingData[id].Transmission.nDriveType != 'R'; } +}; +VALIDATE_SIZE(cHandlingDataMgr, 0x3030); +extern cHandlingDataMgr mod_HandlingManager; diff --git a/src/vehicles/Heli.cpp b/src/vehicles/Heli.cpp new file mode 100644 index 0000000..6e302e0 --- /dev/null +++ b/src/vehicles/Heli.cpp @@ -0,0 +1,1070 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "Darkel.h" +#include "Stats.h" +#include "SurfaceTable.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "Camera.h" +#include "VisibilityPlugins.h" +#include "ZoneCull.h" +#include "Particle.h" +#include "Shadows.h" +#include "Coronas.h" +#include "Explosion.h" +#include "Timecycle.h" +#include "TempColModels.h" +#include "World.h" +#include "WaterLevel.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "Object.h" +#include "HandlingMgr.h" +#include "Heli.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif + +enum +{ + HELI_STATUS_HOVER, + HELI_STATUS_CHASE_PLAYER, + HELI_STATUS_FLY_AWAY, + HELI_STATUS_SHOT_DOWN, + HELI_STATUS_HOVER2, +}; + +CHeli *CHeli::pHelis[NUM_HELIS]; +int16 CHeli::NumRandomHelis; +uint32 CHeli::TestForNewRandomHelisTimer; +int16 CHeli::NumScriptHelis; // unused +bool CHeli::CatalinaHeliOn; +bool CHeli::CatalinaHasBeenShotDown; +bool CHeli::ScriptHeliOn; + +CHeli::CHeli(int32 id, uint8 CreatedBy) + : CVehicle(CreatedBy) +{ + int i; + + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + m_vehType = VEHICLE_TYPE_HELI; + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId); + SetModelIndex(id); + m_heliStatus = HELI_STATUS_HOVER; + m_pathState = 0; + + m_fMass = 100000000.0f; + m_fTurnMass = 100000000.0f; + m_fAirResistance = 0.9994f; + m_fElasticity = 0.05f; + + m_nHeliId = 0; + m_fRotorRotation = 0.0f; + m_nBulletDamage = 0; + m_fAngularSpeed = 0.0f; + m_fRotation = 0.0f; + m_nSearchLightTimer = CTimer::GetTimeInMilliseconds(); + for(i = 0; i < 6; i++){ + m_aSearchLightHistoryX[i] = 0.0f; + m_aSearchLightHistoryY[i] = 0.0f; + } + + for(i = 0; i < 8; i++) + m_fHeliDustZ[i] = -50.0f; + + m_nPoliceShoutTimer = CTimer::GetTimeInMilliseconds(); + SetStatus(STATUS_HELI); + m_bTestRight = true; + m_fTargetOffset = 0.0f; + m_fSearchLightX = m_fSearchLightY = 0.0f; + + // BUG: not in game but gets initialized to CDCDCDCD in debug + m_nLastShotTime = 0; +} + +void +CHeli::SetModelIndex(uint32 id) +{ + int i; + + CVehicle::SetModelIndex(id); + for(i = 0; i < NUM_HELI_NODES; i++) + m_aHeliNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), m_aHeliNodes); +} + +static float CatalinaTargetX[7] = { -478.0, -677.0, -907.0, -1095.0, -1152.0, -1161.0, -1161.0 }; +static float CatalinaTargetY[7] = { 227.0, 206.0, 210.0, 242.0, 278.0, 341.0, 341.0 }; +static float CatalinaTargetZ[7] = { 77.0, 66.0, 60.0, 53.0, 51.0, 46.0, 30.0 }; +static float DamPathX[6] = { -1191.0, -1176.0, -1128.0, -1072.0, -1007.0, -971.0 }; +static float DamPathY[6] = { 350.0, 388.0, 429.0, 447.0, 449.0, 416.0 }; +static float DamPathZ[6] = { 42.0, 37.0, 28.0, 28.0, 31.0, 33.0 }; +static float ShortPathX[4] = { -974.0, -1036.0, -1112.0, -1173.0 }; +static float ShortPathY[4] = { 340.0, 312.0, 317.0, 294.0 }; +static float ShortPathZ[4] = { 41.0, 38.0, 32.0, 39.0 }; +static float LongPathX[7] = { -934.0, -905.0, -906.0, -1063.0, -1204.0, -1233.0, -1207.0 }; +static float LongPathY[7] = { 371.0, 362.0, 488.0, 548.0, 451.0, 346.0, 308.0 }; +static float LongPathZ[7] = { 57.0, 90.0, 105.0, 100.0, 81.0, 79.0, 70.0 }; + +static int PathPoint; + +void +CHeli::ProcessControl(void) +{ + int i; + + if(gbModelViewer) + return; + + // Find target + CVector target(0.0f, 0.0f, 0.0f); + CVector2D vTargetDist; + if(m_heliType == HELI_TYPE_CATALINA && m_heliStatus != HELI_STATUS_SHOT_DOWN){ + switch(m_pathState){ + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + target.x = CatalinaTargetX[m_pathState]; + target.y = CatalinaTargetY[m_pathState]; + target.z = CatalinaTargetZ[m_pathState]; + if((target - GetPosition()).Magnitude() < 9.0f) + m_pathState++; + break; + case 6: + target.x = CatalinaTargetX[m_pathState]; + target.y = CatalinaTargetY[m_pathState]; + target.z = CatalinaTargetZ[m_pathState]; + if(GetPosition().z > 31.55f) + break; + m_pathState = 7; + GetMatrix().GetPosition().z = 31.55f; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + break; + case 7: + GetMatrix().GetPosition().z = 31.55f; + target = GetPosition(); + break; + + + // Take off + case 8: + target.x = GetPosition().x; + target.y = GetPosition().y; + target.z = 74.0f; + if(GetPosition().z < 40.0f) + break; + PathPoint = 2; + m_pathState = 9; + break; + // Circle around dam + case 9: + target.x = DamPathX[PathPoint]; + target.y = DamPathY[PathPoint]; + target.z = DamPathZ[PathPoint]; + if((target - GetPosition()).Magnitude() < 9.0f){ + PathPoint++; + if(PathPoint >= 6){ + m_pathState = 10; + PathPoint = 0; + } + } + break; + case 10: + target.x = ShortPathX[PathPoint]; + target.y = ShortPathY[PathPoint]; + target.z = ShortPathZ[PathPoint]; + if((target - GetPosition()).Magnitude() < 9.0f){ + PathPoint++; + if(PathPoint >= 3){ + m_pathState = 9; + PathPoint = 1; + } + } + break; + // how do we get here? + case 11: + target.x = LongPathX[PathPoint]; + target.y = LongPathY[PathPoint]; + target.z = LongPathZ[PathPoint]; + if((target - GetPosition()).Magnitude() < 9.0f){ + PathPoint++; + if(PathPoint >= 7){ + m_pathState = 9; + PathPoint = 0; + } + } + break; + + + // Fly away + case 12: + target.x = GetPosition().x; + target.y = GetPosition().y; + target.z = 200.0f; + break; + } + + vTargetDist = target - GetPosition(); + m_fTargetZ = target.z; + if(m_pathState == 6){ + GetMatrix().GetPosition().x = GetMatrix().GetPosition().x*0.99f + target.x*0.01f; + GetMatrix().GetPosition().y = GetMatrix().GetPosition().y*0.99f + target.y*0.01f; + } + }else{ + vTargetDist = FindPlayerCoors() - GetPosition(); + m_fTargetZ = FindPlayerCoors().z; + + // Heli flies away to (0, 0) + if(m_heliStatus == HELI_STATUS_FLY_AWAY && GetPosition().z > 20.0f){ + vTargetDist.x = 0.0f - GetPosition().x; + vTargetDist.y = 0.0f - GetPosition().y; + } + + float groundZ; + switch(m_heliStatus){ + case HELI_STATUS_HOVER: + groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil); + m_fTargetZ = Max(groundZ, m_fTargetZ) + 8.0f; + break; + case HELI_STATUS_SHOT_DOWN: + groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil); + m_fTargetZ = Max(groundZ, m_fTargetZ) + 8.0f + m_fTargetOffset; + break; + case HELI_STATUS_HOVER2: + groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil); + m_fTargetZ = Max(groundZ, m_fTargetZ) + 8.0f + m_fTargetOffset; + break; + default: + groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil); + m_fTargetZ = Max(groundZ, m_fTargetZ) + 12.0f; + break; + } + + // Move up if too low + if(GetPosition().z - 2.0f < groundZ && m_heliStatus != HELI_STATUS_SHOT_DOWN) + m_vecMoveSpeed.z += CTimer::GetTimeStep()*0.01f; + m_vecMoveSpeed.z = Clamp(m_vecMoveSpeed.z, -0.3f, 0.3f); + } + + float fTargetDist = vTargetDist.Magnitude(); + + switch(m_heliStatus){ + case HELI_STATUS_HOVER: + case HELI_STATUS_HOVER2:{ + float targetHeight; + if(m_heliType == HELI_TYPE_CATALINA) + targetHeight = 8.0f; + else + targetHeight = 40.0f - m_nHeliId*10.0f; + if(fTargetDist > targetHeight) + m_heliStatus = HELI_STATUS_CHASE_PLAYER; + } +#ifdef FIX_BUGS + break; +#endif + case HELI_STATUS_CHASE_PLAYER:{ + float targetHeight; + if(m_heliType == HELI_TYPE_CATALINA) + targetHeight = 4.0f; + else + targetHeight = 30.0f - m_nHeliId*7.5f; + if(fTargetDist < 1.0f || + fTargetDist < targetHeight && CWorld::GetIsLineOfSightClear(GetPosition(), FindPlayerCoors(), true, false, false, false, false, false)) + m_heliStatus = HELI_STATUS_HOVER; + } + } + + // Find xy speed + float speed; + if(fTargetDist > 100.0f) + speed = 1.0f; + else if(fTargetDist > 75.0f) + speed = 0.7f; + else + speed = 0.4f; + if(m_heliStatus == HELI_STATUS_HOVER || m_heliStatus == HELI_STATUS_HOVER2 || m_heliStatus == HELI_STATUS_SHOT_DOWN) + speed = 0.0f; + + if(fTargetDist != 0.0f) + vTargetDist /= fTargetDist; + else + vTargetDist.x = 1.0f; + CVector2D targetSpeed = vTargetDist * speed; + + if(m_heliStatus == HELI_STATUS_HOVER2 || m_heliStatus == HELI_STATUS_SHOT_DOWN){ + bool force = !!((CTimer::GetFrameCounter() + m_randomSeed) & 8); + if(m_bTestRight){ + if(force || CWorld::TestSphereAgainstWorld(GetPosition() + 4.0f*GetRight(), 2.0f, this, true, false, false, false, false, false) == nil){ + if(m_heliStatus == HELI_STATUS_SHOT_DOWN){ + m_fTargetOffset -= CTimer::GetTimeStep()*0.05f; + targetSpeed.x -= -vTargetDist.x*0.15f; + targetSpeed.y -= vTargetDist.y*0.15f; + }else{ + targetSpeed.x -= -vTargetDist.x*0.05f; + targetSpeed.y -= vTargetDist.y*0.05f; + } + }else{ + m_bTestRight = false; + if(m_heliStatus == HELI_STATUS_HOVER2) + m_fTargetOffset += 5.0f; + else + m_fTargetOffset -= 5.0f; + } + }else{ + if(force || CWorld::TestSphereAgainstWorld(GetPosition() - 4.0f*GetRight(), 2.0f, this, true, false, false, false, false, false) == nil){ + if(m_heliStatus == HELI_STATUS_SHOT_DOWN){ + m_fTargetOffset -= CTimer::GetTimeStep()*0.05f; + targetSpeed.x += -vTargetDist.x*0.15f; + targetSpeed.y += vTargetDist.y*0.15f; + }else{ + targetSpeed.x += -vTargetDist.x*0.05f; + targetSpeed.y += vTargetDist.y*0.05f; + } + }else{ + m_bTestRight = true; + if(m_heliStatus == HELI_STATUS_HOVER2) + m_fTargetOffset += 5.0f; + else + m_fTargetOffset -= 5.0f; + } + } + + if(m_fTargetOffset > 30.0f) + m_fTargetOffset = 30.0f; + + if(m_heliStatus == HELI_STATUS_SHOT_DOWN && force){ + if(CWorld::TestSphereAgainstWorld(GetPosition() + 1.5f*GetForward(), 2.0f, this, true, false, false, false, false, false) || + CWorld::TestSphereAgainstWorld(GetPosition() - 1.5f*GetForward(), 2.0f, this, true, false, false, false, false, false)) + m_nExplosionTimer = CTimer::GetPreviousTimeInMilliseconds(); + } + }else + if(m_fTargetOffset >= 2.0f) + m_fTargetOffset -= 2.0f; + + if(m_heliType == HELI_TYPE_CATALINA) + if(m_pathState == 9 || m_pathState == 11 || m_pathState == 10){ + float f = Pow(0.997f, CTimer::GetTimeStep()); + m_vecMoveSpeed.x *= f; + m_vecMoveSpeed.y *= f; + } + + CVector2D speedDir = targetSpeed - m_vecMoveSpeed; + float speedDiff = speedDir.Magnitude(); + if(speedDiff != 0.0f) + speedDir /= speedDiff; + else + speedDir.x = 1.0f; + float speedInc = CTimer::GetTimeStep()*0.002f; + if(speedDiff < speedInc){ + m_vecMoveSpeed.x = targetSpeed.x; + m_vecMoveSpeed.y = targetSpeed.y; + }else{ + m_vecMoveSpeed.x += speedDir.x*speedInc; + m_vecMoveSpeed.y += speedDir.y*speedInc; + } + GetMatrix().GetPosition().x += m_vecMoveSpeed.x*CTimer::GetTimeStep(); + GetMatrix().GetPosition().y += m_vecMoveSpeed.y*CTimer::GetTimeStep(); + + // Find z target + if(m_heliStatus == HELI_STATUS_FLY_AWAY) + m_fTargetZ = 1000.0f; + if((CTimer::GetTimeInMilliseconds() + 800*m_nHeliId) & 0x800) + m_fTargetZ += 2.0f; + m_fTargetZ += m_nHeliId*5.0f; + + // Find z speed + float targetSpeedZ = (m_fTargetZ - GetPosition().z)*0.01f; + float speedDiffZ = targetSpeedZ - m_vecMoveSpeed.z; + float speedIncZ = CTimer::GetTimeStep()*0.001f; + if(m_heliStatus == HELI_STATUS_FLY_AWAY) + speedIncZ *= 1.5f; + if(Abs(speedDiffZ) < speedIncZ) + m_vecMoveSpeed.z = targetSpeedZ; + else if(speedDiffZ < 0.0f) + m_vecMoveSpeed.z -= speedIncZ; + else + m_vecMoveSpeed.z += speedIncZ*1.5f; + GetMatrix().GetPosition().z += m_vecMoveSpeed.z*CTimer::GetTimeStep(); + + // Find angular speed + float targetAngularSpeed; + m_fAngularSpeed *= Pow(0.995f, CTimer::GetTimeStep()); + if(fTargetDist < 8.0f) + targetAngularSpeed = 0.0f; + else{ + float rotationDiff = CGeneral::GetATanOfXY(vTargetDist.x, vTargetDist.y) - m_fRotation; + while(rotationDiff < -3.14f) rotationDiff += 6.28f; + while(rotationDiff > 3.14f) rotationDiff -= 6.28f; + if(Abs(rotationDiff) > 0.4f){ + if(rotationDiff < 0.0f) + targetAngularSpeed = -0.2f; + else + targetAngularSpeed = 0.2f; + }else + targetAngularSpeed = 0.0f; + } + float angularSpeedDiff = targetAngularSpeed - m_fAngularSpeed; + float angularSpeedInc = CTimer::GetTimeStep()*0.0001f; + if(Abs(angularSpeedDiff) < angularSpeedInc) + m_fAngularSpeed = targetAngularSpeed; + else if(angularSpeedDiff < 0.0f) + m_fAngularSpeed -= angularSpeedInc; + else + m_fAngularSpeed += angularSpeedInc; + m_fRotation += m_fAngularSpeed * CTimer::GetTimeStep(); + + // Set matrix + CVector up(3.0f*m_vecMoveSpeed.x, 3.0f*m_vecMoveSpeed.y, 1.0f); + up.Normalise(); + CVector fwd(-Cos(m_fRotation), -Sin(m_fRotation), 0.0f); // not really forward + CVector right = CrossProduct(up, fwd); + fwd = CrossProduct(up, right); + GetRight() = right; + GetForward() = fwd; + GetUp() = up; + + // Search light and shooting + if(m_heliStatus == HELI_STATUS_FLY_AWAY || m_heliType == HELI_TYPE_CATALINA || CCullZones::PlayerNoRain()) + m_fSearchLightIntensity = 0.0f; + else { + // Update search light history once every 1000ms + int timeDiff = CTimer::GetTimeInMilliseconds() - m_nSearchLightTimer; + while (timeDiff > 1000) { + for (i = 5; i > 0; i--) { + m_aSearchLightHistoryX[i] = m_aSearchLightHistoryX[i - 1]; + m_aSearchLightHistoryY[i] = m_aSearchLightHistoryY[i - 1]; + } + m_aSearchLightHistoryX[0] = FindPlayerCoors().x + FindPlayerSpeed().x * 50.0f * (m_nHeliId + 2); + m_aSearchLightHistoryY[0] = FindPlayerCoors().y + FindPlayerSpeed().y * 50.0f * (m_nHeliId + 2); + + timeDiff -= 1000; + m_nSearchLightTimer += 1000; + } + assert(timeDiff <= 1000); + float f1 = timeDiff / 1000.0f; + float f2 = 1.0f - f1; + m_fSearchLightX = m_aSearchLightHistoryX[m_nHeliId + 2] * f2 + m_aSearchLightHistoryX[m_nHeliId + 2 - 1] * f1; + m_fSearchLightY = m_aSearchLightHistoryY[m_nHeliId + 2] * f2 + m_aSearchLightHistoryY[m_nHeliId + 2 - 1] * f1; + + float searchLightDist = (CVector2D(m_fSearchLightX, m_fSearchLightY) - GetPosition()).Magnitude(); + if (searchLightDist > 60.0f) + m_fSearchLightIntensity = 0.0f; + else if (searchLightDist < 40.0f) + m_fSearchLightIntensity = 1.0f; + else + m_fSearchLightIntensity = 1.0f - (40.0f - searchLightDist) / (60.0f-40.0f); + + if (m_fSearchLightIntensity < 0.9f || sq(FindPlayerCoors().x - m_fSearchLightX) + sq(FindPlayerCoors().y - m_fSearchLightY) > sq(7.0f)) + m_nShootTimer = CTimer::GetTimeInMilliseconds(); + else if (CTimer::GetTimeInMilliseconds() > m_nPoliceShoutTimer) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_PED_HELI_PLAYER_FOUND, 0.0f); + m_nPoliceShoutTimer = CTimer::GetTimeInMilliseconds() + 4500 + (CGeneral::GetRandomNumber() & 0xFFF); + } +#ifdef FIX_BUGS + if (!CReplay::IsPlayingBack()) +#endif + { + // Shoot + int shootTimeout; + if (m_heliType == HELI_TYPE_RANDOM) { + switch (FindPlayerPed()->m_pWanted->GetWantedLevel()) { + case 0: + case 1: + case 2: shootTimeout = 999999; break; + case 3: shootTimeout = 10000; break; + case 4: shootTimeout = 5000; break; + case 5: shootTimeout = 3500; break; + case 6: shootTimeout = 2000; break; + } + if (CCullZones::NoPolice()) + shootTimeout /= 2; + } + else + shootTimeout = 1500; + + if (FindPlayerPed()->m_pWanted->IsIgnored()) + m_nShootTimer = CTimer::GetTimeInMilliseconds(); + else { + // Check if line of sight is clear + if (CTimer::GetTimeInMilliseconds() > m_nShootTimer + shootTimeout && + CTimer::GetPreviousTimeInMilliseconds() <= m_nShootTimer + shootTimeout) { + if (CWorld::GetIsLineOfSightClear(GetPosition(), FindPlayerCoors(), true, false, false, false, false, false)) { + if (m_heliStatus == HELI_STATUS_HOVER2) + m_heliStatus = HELI_STATUS_HOVER; + } + else { + m_nShootTimer = CTimer::GetTimeInMilliseconds(); + if (m_heliStatus == HELI_STATUS_HOVER) + m_heliStatus = HELI_STATUS_HOVER2; + } + } + + // Shoot! + if (CTimer::GetTimeInMilliseconds() > m_nShootTimer + shootTimeout && + CTimer::GetTimeInMilliseconds() > m_nLastShotTime + 200) { + CVector shotTarget = FindPlayerCoors(); + // some inaccuracy + shotTarget.x += ((CGeneral::GetRandomNumber() & 0xFF) - 128) / 50.0f; + shotTarget.y += ((CGeneral::GetRandomNumber() & 0xFF) - 128) / 50.0f; + CVector direction = FindPlayerCoors() - GetPosition(); + direction.Normalise(); + shotTarget += 3.0f * direction; + CVector shotSource = GetPosition(); + shotSource += 3.0f * direction; + FireOneInstantHitRound(&shotSource, &shotTarget, 20); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + m_nLastShotTime = CTimer::GetTimeInMilliseconds(); + } + } + } + } + + // Drop Catalina's bombs + if(m_heliType == HELI_TYPE_CATALINA && m_pathState > 8 && (CTimer::GetTimeInMilliseconds()>>9) != (CTimer::GetPreviousTimeInMilliseconds()>>9)){ + CVector bombPos = GetPosition() - 60.0f*m_vecMoveSpeed; + if(sq(FindPlayerCoors().x-bombPos.x) + sq(FindPlayerCoors().y-bombPos.y) < sq(35.0f)){ + bool found; + float groundZ = CWorld::FindGroundZFor3DCoord(bombPos.x, bombPos.y, bombPos.z, &found); + float waterZ; + if(!CWaterLevel::GetWaterLevelNoWaves(bombPos.x, bombPos.y, bombPos.z, &waterZ)) + waterZ = 0.0f; + if(groundZ > waterZ){ + bombPos.z = groundZ + 2.0f; + CExplosion::AddExplosion(nil, this, EXPLOSION_HELI_BOMB, bombPos, 0); + }else{ + bombPos.z = waterZ; + CVector dir; + for(i = 0; i < 16; i++){ + dir.x = ((CGeneral::GetRandomNumber()&0xFF)-127)*0.001f; + dir.y = ((CGeneral::GetRandomNumber()&0xFF)-127)*0.001f; + dir.z = 0.5f; + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, bombPos, dir, nil, 0.2f); + } + } + } + } + + RemoveAndAdd(); + bIsInSafePosition = true; + GetMatrix().UpdateRW(); + UpdateRwFrame(); +} + +void +CHeli::PreRender(void) +{ + float angle; + uint8 i; + CColPoint point; + CEntity *entity; + uint8 r, g, b; + float testLowZ = FindPlayerCoors().z - 10.0f; + float radius = (GetPosition().z - FindPlayerCoors().z - 10.0f - 1.0f) * 0.3f + 10.0f; + int frm = CTimer::GetFrameCounter() & 7; + + i = 0; + for(angle = 0.0f; angle < TWOPI; angle += TWOPI/32){ + CVector pos(radius*Cos(angle), radius*Sin(angle), 0.0f); + CVector dir = CVector(pos.x, pos.y, 1.0f)*0.01f; + pos += GetPosition(); + + if(CWorld::ProcessVerticalLine(pos, testLowZ, point, entity, true, false, false, false, true, false, nil)) + m_fHeliDustZ[frm] = point.point.z; + else + m_fHeliDustZ[frm] = -101.0f; + + switch(point.surfaceB){ + default: + case SURFACE_TARMAC: + r = 10; + g = 10; + b = 10; + break; + case SURFACE_GRASS: + r = 10; + g = 6; + b = 3; + break; + case SURFACE_GRAVEL: + r = 10; + g = 8; + b = 7; + break; + case SURFACE_MUD_DRY: + r = 10; + g = 6; + b = 3; + break; + } + RwRGBA col = { r, g, b, 32 }; +#ifdef FIX_BUGS + pos.z = m_fHeliDustZ[frm]; +#else + // What the hell is the point of this? + pos.z = m_fHeliDustZ[(i - (i&3))/4]; // advance every 4 iterations, why not just /4? +#endif + if(pos.z > -200.0f && GetPosition().z - pos.z < 20.0f) + CParticle::AddParticle(PARTICLE_HELI_DUST, pos, dir, nil, 0.0f, col); + i++; + } +} + +void +CHeli::Render(void) +{ + CMatrix mat; + CVector pos; + + mat.Attach(RwFrameGetMatrix(m_aHeliNodes[HELI_TOPROTOR])); + pos = mat.GetPosition(); + mat.SetRotateZ(m_fRotorRotation); + mat.Translate(pos); + mat.UpdateRW(); + + m_fRotorRotation += 3.14f/6.5f; + if(m_fRotorRotation > 6.28f) + m_fRotorRotation -= 6.28f; + + mat.Attach(RwFrameGetMatrix(m_aHeliNodes[HELI_BACKROTOR])); + pos = mat.GetPosition(); + mat.SetRotateX(m_fRotorRotation); + mat.Translate(pos); + mat.UpdateRW(); + + CEntity::Render(); +} + +void +CHeli::PreRenderAlways(void) +{ + CVector shadowPos(m_fSearchLightX, m_fSearchLightY, GetPosition().z); + if(m_fSearchLightIntensity > 0.0f){ + CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &shadowPos, + 6.0f, 0.0f, 0.0f, -6.0f, + 80*m_fSearchLightIntensity, 80*m_fSearchLightIntensity, 80*m_fSearchLightIntensity, 80*m_fSearchLightIntensity, + 50.0f, true, 1.0f); + + CVector front = GetMatrix() * CVector(0.0f, 7.0f, 0.0f); + CVector toPlayer = FindPlayerCoors() - front; + toPlayer.Normalise(); + float intensity = m_fSearchLightIntensity*sq(CTimeCycle::GetSpriteBrightness()); + if(DotProduct(toPlayer, TheCamera.GetForward()) < -0.8f) + CCoronas::RegisterCorona((uintptr)this, 255*intensity, 255*intensity, 255*intensity, 255, + front, 10.0f, 60.0f, CCoronas::TYPE_STAR, + CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::RegisterCorona((uintptr)this, 200*intensity, 200*intensity, 200*intensity, 255, + front, 8.0f, 60.0f, CCoronas::TYPE_STAR, + CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } + + CVector back = GetMatrix() * CVector(0.0f, -9.0f, 0.0f); + if(CTimer::GetTimeInMilliseconds() & 0x100) + CCoronas::RegisterCorona((uintptr)this + 2, 255, 0, 0, 255, + back, 1.0f, 60.0f, CCoronas::TYPE_STAR, + CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::RegisterCorona((uintptr)this + 2, 0, 0, 0, 255, + back, 1.0f, 60.0f, CCoronas::TYPE_STAR, + CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); +} + +RwObject* +GetHeliAtomicObjectCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + assert(RwObjectGetType(object) == rpATOMIC); + if(RpAtomicGetFlags(atomic) & rpATOMICRENDER) + *(RpAtomic**)data = atomic; + return object; +} + +CObject* +CHeli::SpawnFlyingComponent(int32 component) +{ + RpAtomic *atomic; + RwFrame *frame; + RwMatrix *matrix; + CObject *obj; + + if(m_aHeliNodes[component] == nil) + return nil; + + atomic = nil; + RwFrameForAllObjects(m_aHeliNodes[component], GetHeliAtomicObjectCB, &atomic); + if(atomic == nil) + return nil; + + obj = new CObject; + if(obj == nil) + return nil; + + obj->SetModelIndexNoCreate(MI_CAR_WHEEL); + // object needs base model + obj->RefModelInfo(GetModelIndex()); + + // create new atomic + matrix = RwFrameGetLTM(m_aHeliNodes[component]); + frame = RwFrameCreate(); + atomic = RpAtomicClone(atomic); + *RwFrameGetMatrix(frame) = *matrix; + RpAtomicSetFrame(atomic, frame); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + obj->AttachToRwObject((RwObject*)atomic); + + // init object + obj->m_fMass = 10.0f; + obj->m_fTurnMass = 25.0f; + obj->m_fAirResistance = 0.99f; + obj->m_fElasticity = 0.1f; + obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f; + obj->ObjectCreatedBy = TEMP_OBJECT; + obj->SetIsStatic(false); + obj->bIsPickup = false; + + // life time + CObject::nNoTempObjects++; + if(component == HELI_TOPROTOR) + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 1000; + else + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 3000; + + obj->m_vecMoveSpeed = m_vecMoveSpeed; + if(obj->m_vecMoveSpeed.z > 0.0f) + obj->m_vecMoveSpeed.z = 0.3f; + else + obj->m_vecMoveSpeed.z = 0.0f; + + obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f; + + if(component == HELI_BACKROTOR) + obj->m_vecTurnSpeed.x = 0.5f; + else if(component == HELI_TOPROTOR || component == HELI_TOPKNOT) + obj->m_vecTurnSpeed.z = 0.5f; + else + obj->m_vecTurnSpeed.y = 0.5f; + + obj->bRenderScorched = true; + + CWorld::Add(obj); + + atomic = nil; + RwFrameForAllObjects(m_aHeliNodes[component], GetHeliAtomicObjectCB, &atomic); + if(atomic) + RpAtomicSetFlags(atomic, 0); + + return obj; +} + + + +void +CHeli::InitHelis(void) +{ + int i; + + NumRandomHelis = 0; + TestForNewRandomHelisTimer = 0; + NumScriptHelis = 0; + CatalinaHeliOn = false; + ScriptHeliOn = false; + for(i = 0; i < NUM_HELIS; i++) + pHelis[i] = nil; + +#if GTA_VERSION >= GTA3_PS2_160 + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_ESCAPE))->SetColModel(&CTempColModels::ms_colModelPed1); + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_CHOPPER))->SetColModel(&CTempColModels::ms_colModelPed1); +#endif +} + +CHeli* +CHeli::GenerateHeli(bool catalina) +{ + CHeli *heli; + CVector heliPos; + int i; + +#if GTA_VERSION < GTA3_PS2_160 + if(catalina) + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_ESCAPE))->SetColModel(&CTempColModels::ms_colModelPed1); + else + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_CHOPPER))->SetColModel(&CTempColModels::ms_colModelPed1); +#endif + + if(catalina) + heli = new CHeli(MI_ESCAPE, PERMANENT_VEHICLE); + else + heli = new CHeli(MI_CHOPPER, PERMANENT_VEHICLE); + + if(catalina) + heliPos = CVector(-224.0f, 201.0f, 83.0f); + else{ + heliPos = FindPlayerCoors(); + float angle = (float)(CGeneral::GetRandomNumber() & 0xFF)/0x100 * 6.28f; + heliPos.x += 250.0f*Sin(angle); + heliPos.y += 250.0f*Cos(angle); + if(heliPos.x < -2000.0f || heliPos.x > 2000.0f || heliPos.y < -2000.0f || heliPos.y > 2000.0f){ + heliPos = FindPlayerCoors(); + heliPos.x -= 250.0f*Sin(angle); + heliPos.y -= 250.0f*Cos(angle); + } + heliPos.z += 50.0f; + } + heli->GetMatrix().SetTranslate(heliPos); + if(catalina) + heli->GetMatrix().SetRotateZOnly(DEGTORAD(270.0f)); // game actually uses 3.14 here + + heli->SetStatus(STATUS_ABANDONED); + heli->bIsLocked = true; + + int id = -1; + bool found = false; + while(!found){ + id++; + found = true; + for(i = 0; i < 4; i++) + if(pHelis[i] && pHelis[i]->m_nHeliId == id) + found = false; + } + heli->m_nHeliId = id; + + CWorld::Add(heli); + + return heli; +} + +void +CHeli::UpdateHelis(void) +{ + int i, j; + + // Spawn new police helis + int numHelisRequired = +#ifdef FIX_BUGS + CReplay::IsPlayingBack() ? 0 : +#endif + FindPlayerPed()->m_pWanted->NumOfHelisRequired(); + if(CStreaming::HasModelLoaded(MI_CHOPPER) && CTimer::GetTimeInMilliseconds() > TestForNewRandomHelisTimer){ + // Spawn a police heli + TestForNewRandomHelisTimer = CTimer::GetTimeInMilliseconds() + 15000; + if(NumRandomHelis < numHelisRequired){ + NumRandomHelis++; + CHeli *heli = GenerateHeli(false); + heli->m_heliType = HELI_TYPE_RANDOM; + if(pHelis[HELI_RANDOM0] == nil) + pHelis[HELI_RANDOM0] = heli; + else if(pHelis[HELI_RANDOM1] == nil) + pHelis[HELI_RANDOM1] = heli; + else + assert(0 && "too many helis"); + } + } + + // Handle script heli + if(ScriptHeliOn){ + if(CStreaming::HasModelLoaded(MI_CHOPPER) && pHelis[HELI_SCRIPT] == nil){ + pHelis[HELI_SCRIPT] = GenerateHeli(false); + pHelis[HELI_SCRIPT]->m_heliType = HELI_TYPE_SCRIPT; + }else + CStreaming::RequestModel(MI_CHOPPER, 0); + }else{ + if(pHelis[HELI_SCRIPT]) + pHelis[HELI_SCRIPT]->m_heliStatus = HELI_STATUS_FLY_AWAY; + } + + // Handle Catalina's heli + if(CatalinaHeliOn){ + if(CStreaming::HasModelLoaded(MI_ESCAPE) && pHelis[HELI_CATALINA] == nil){ + pHelis[HELI_CATALINA] = GenerateHeli(true); + pHelis[HELI_CATALINA]->m_heliType = HELI_TYPE_CATALINA; + }else + CStreaming::RequestModel(MI_ESCAPE, STREAMFLAGS_DONT_REMOVE); + }else{ + if(pHelis[HELI_CATALINA]) + pHelis[HELI_CATALINA]->m_heliStatus = HELI_STATUS_FLY_AWAY; + } + + // Delete helis that we no longer need + for(i = 0; i < NUM_HELIS; i++) + if(pHelis[i] && pHelis[i]->m_heliStatus == HELI_STATUS_FLY_AWAY && pHelis[i]->GetPosition().z > 150.0f){ + CWorld::Remove(pHelis[i]); + delete pHelis[i]; + pHelis[i] = nil; + if(i != HELI_SCRIPT && i != HELI_CATALINA) + NumRandomHelis--; + } + + // Handle explosions + for(i = 0; i < NUM_HELIS; i++){ + if(pHelis[i] && pHelis[i]->m_heliStatus == HELI_STATUS_SHOT_DOWN && CTimer::GetTimeInMilliseconds() > pHelis[i]->m_nExplosionTimer){ + // Second part of explosion + static int nFrameGen; + CRGBA colors[8]; + + TheCamera.CamShake(0.7f, pHelis[i]->GetPosition().x, pHelis[i]->GetPosition().y, pHelis[i]->GetPosition().z); + + colors[0] = CRGBA(0, 0, 0, 255); + colors[1] = CRGBA(224, 230, 238, 255); + colors[2] = CRGBA(0, 0, 0, 255); + colors[3] = CRGBA(0, 0, 0, 255); + colors[4] = CRGBA(66, 162, 252, 255); + colors[5] = CRGBA(0, 0, 0, 255); + colors[6] = CRGBA(0, 0, 0, 255); + colors[7] = CRGBA(0, 0, 0, 255); + + CVector pos = pHelis[i]->GetPosition(); + CVector dir; + for(j = 0; j < 40; j++){ + dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f); + int rotSpeed = CGeneral::GetRandomNumberInRange(10, 30); + if(CGeneral::GetRandomNumber() & 1) + rotSpeed = -rotSpeed; + int f = ++nFrameGen & 3; + CParticle::AddParticle(PARTICLE_HELI_DEBRIS, pos, dir, + nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f), + colors[nFrameGen], rotSpeed, 0, f, 0); + } + + CExplosion::AddExplosion(nil, nil, EXPLOSION_HELI, pos, 0); + + pHelis[i]->SpawnFlyingComponent(HELI_SKID_LEFT); + pHelis[i]->SpawnFlyingComponent(HELI_SKID_RIGHT); + pHelis[i]->SpawnFlyingComponent(HELI_TOPROTOR); + + CDarkel::RegisterCarBlownUpByPlayer(pHelis[i]); + CWorld::Remove(pHelis[i]); + delete pHelis[i]; + pHelis[i] = nil; + if(i != HELI_SCRIPT && i != HELI_CATALINA) + NumRandomHelis--; + if(i == HELI_CATALINA) + CatalinaHasBeenShotDown = true; + + CStats::HelisDestroyed++; + CStats::PeopleKilledByPlayer += 2; + CStats::PedsKilledOfThisType[PEDTYPE_COP] += 2; + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 250; + pos = CWorld::Players[CWorld::PlayerInFocus].m_pPed->GetPosition(); + CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->RegisterCrime_Immediately(CRIME_SHOOT_HELI, + pos, i + 19843, false); + + TestForNewRandomHelisTimer = CTimer::GetTimeInMilliseconds() + 50000; + }else if(pHelis[i] && pHelis[i]->m_heliStatus == HELI_STATUS_SHOT_DOWN && CTimer::GetTimeInMilliseconds()+7000 > pHelis[i]->m_nExplosionTimer){ + // First part of explosion + if(CTimer::GetPreviousTimeInMilliseconds()+7000 < pHelis[i]->m_nExplosionTimer){ + pHelis[i]->SpawnFlyingComponent(HELI_BACKROTOR); + pHelis[i]->SpawnFlyingComponent(HELI_TAIL); + pHelis[i]->m_fAngularSpeed *= -2.5f; + pHelis[i]->bRenderScorched = true; + + TheCamera.CamShake(0.4f, pHelis[i]->GetPosition().x, pHelis[i]->GetPosition().y, pHelis[i]->GetPosition().z); + + CVector pos = pHelis[i]->GetPosition() - 2.5f*pHelis[i]->GetForward(); + CExplosion::AddExplosion(nil, nil, EXPLOSION_HELI, pos, 0); + }else + pHelis[i]->m_fAngularSpeed *= 1.03f; + } + } + + // Find police helis to remove + for(i = 0; i < 2; i++) + if(pHelis[i] && pHelis[i]->m_heliStatus != HELI_STATUS_FLY_AWAY){ + if(numHelisRequired > 0) + numHelisRequired--; + else + pHelis[i]->m_heliStatus = HELI_STATUS_FLY_AWAY; + } + + // Remove all helis if in a tunnel or under water + if(FindPlayerCoors().z < - 2.0f) + for(i = 0; i < NUM_HELIS; i++) + if(pHelis[i] && pHelis[i]->m_heliStatus != HELI_STATUS_SHOT_DOWN) + pHelis[i]->m_heliStatus = HELI_STATUS_FLY_AWAY; +} + +void +CHeli::SpecialHeliPreRender(void) +{ + int i; + for(i = 0; i < NUM_HELIS; i++) + if(pHelis[i]) + pHelis[i]->PreRenderAlways(); +} + +bool +CHeli::TestRocketCollision(CVector *rocketPos) +{ + int i; + bool hit = false; + + for(i = 0; i < NUM_HELIS; i++){ + if(pHelis[i] && !pHelis[i]->bExplosionProof && (*rocketPos - pHelis[i]->GetPosition()).MagnitudeSqr() < sq(8.0f)){ + pHelis[i]->m_fAngularSpeed = CGeneral::GetRandomTrueFalse() ? 0.05f : -0.05f; + pHelis[i]->m_heliStatus = HELI_STATUS_SHOT_DOWN; + pHelis[i]->m_nExplosionTimer = CTimer::GetTimeInMilliseconds() + 10000; + hit = true; + } + } + return hit; +} + +bool +CHeli::TestBulletCollision(CVector *line0, CVector *line1, CVector *bulletPos, int32 damage) +{ + int i; + bool hit = false; + + for(i = 0; i < NUM_HELIS; i++) + if(pHelis[i] && !pHelis[i]->bBulletProof && CCollision::DistToLine(line0, line1, &pHelis[i]->GetPosition()) < 5.0f){ + // Find bullet position + float distToHeli = (pHelis[i]->GetPosition() - *line0).Magnitude(); + CVector line = (*line1 - *line0); + float lineLength = line.Magnitude(); + *bulletPos = *line0 + line*Max(1.0f, distToHeli-5.0f)/lineLength; + + pHelis[i]->m_nBulletDamage += damage; + + if(pHelis[i]->m_heliType == HELI_CATALINA && pHelis[i]->m_nBulletDamage > 400 || + pHelis[i]->m_heliType != HELI_CATALINA && pHelis[i]->m_nBulletDamage > 700){ + pHelis[i]->m_fAngularSpeed = CGeneral::GetRandomTrueFalse() ? 0.05f : -0.05f; + pHelis[i]->m_heliStatus = HELI_STATUS_SHOT_DOWN; + pHelis[i]->m_nExplosionTimer = CTimer::GetTimeInMilliseconds() + 10000; + } + + hit = true; + } + return hit; +} + +void CHeli::StartCatalinaFlyBy(void) +{ + CatalinaHeliOn = true; + CatalinaHasBeenShotDown = false; +} + +void +CHeli::RemoveCatalinaHeli(void) +{ + CatalinaHeliOn = false; + if(pHelis[HELI_CATALINA]){ + CWorld::Remove(pHelis[HELI_CATALINA]); + delete pHelis[HELI_CATALINA]; + pHelis[HELI_CATALINA] = nil; + } +} + +CHeli *CHeli::FindPointerToCatalinasHeli(void) { return pHelis[HELI_CATALINA]; } +void CHeli::CatalinaTakeOff(void) { pHelis[HELI_CATALINA]->m_pathState = 8; } +void CHeli::MakeCatalinaHeliFlyAway(void) { pHelis[HELI_CATALINA]->m_pathState = 12; } +bool CHeli::HasCatalinaBeenShotDown(void) { return CatalinaHasBeenShotDown; } + +void CHeli::ActivateHeli(bool activate) { ScriptHeliOn = activate; } diff --git a/src/vehicles/Heli.h b/src/vehicles/Heli.h new file mode 100644 index 0000000..5fef799 --- /dev/null +++ b/src/vehicles/Heli.h @@ -0,0 +1,101 @@ +#pragma once + +#include "Vehicle.h" + +class CObject; + +enum eHeliNodes +{ + HELI_CHASSIS = 1, + HELI_TOPROTOR, + HELI_BACKROTOR, + HELI_TAIL, + HELI_TOPKNOT, + HELI_SKID_LEFT, + HELI_SKID_RIGHT, + NUM_HELI_NODES +}; + +enum +{ + HELI_RANDOM0, + HELI_RANDOM1, + HELI_SCRIPT, + HELI_CATALINA, + NUM_HELIS +}; + +enum +{ + HELI_TYPE_RANDOM, + HELI_TYPE_SCRIPT, + HELI_TYPE_CATALINA, +}; + + +class CHeli : public CVehicle +{ +public: + // 0x288 + RwFrame *m_aHeliNodes[NUM_HELI_NODES]; + int8 m_heliStatus; + float m_fSearchLightX; + float m_fSearchLightY; + uint32 m_nExplosionTimer; + float m_fRotation; + float m_fAngularSpeed; + float m_fTargetZ; + float m_fSearchLightIntensity; + int8 m_nHeliId; + int8 m_heliType; + int8 m_pathState; + float m_aSearchLightHistoryX[6]; + float m_aSearchLightHistoryY[6]; + uint32 m_nSearchLightTimer; + uint32 m_nShootTimer; + uint32 m_nLastShotTime; + uint32 m_nBulletDamage; + float m_fRotorRotation; + float m_fHeliDustZ[8]; + uint32 m_nPoliceShoutTimer; + float m_fTargetOffset; + bool m_bTestRight; + + static CHeli *pHelis[NUM_HELIS]; + static int16 NumRandomHelis; + static uint32 TestForNewRandomHelisTimer; + static int16 NumScriptHelis; // unused + static bool CatalinaHeliOn; + static bool CatalinaHasBeenShotDown; + static bool ScriptHeliOn; + + CHeli(int32 id, uint8 CreatedBy); + + // from CEntity + void SetModelIndex(uint32 id); + void ProcessControl(void); + void PreRender(void); + void Render(void); + + void PreRenderAlways(void); + CObject *SpawnFlyingComponent(int32 component); + + static void InitHelis(void); + static CHeli *GenerateHeli(bool catalina); // out of class in III PC and later because of SecuROM + static void UpdateHelis(void); + static void SpecialHeliPreRender(void); + static bool TestRocketCollision(CVector *coors); + static bool TestBulletCollision(CVector *line0, CVector *line1, CVector *bulletPos, int32 damage); + + static void StartCatalinaFlyBy(void); // out of class in III PC and later because of SecuROM + static void RemoveCatalinaHeli(void); + static CHeli *FindPointerToCatalinasHeli(void); + static void CatalinaTakeOff(void); + static void MakeCatalinaHeliFlyAway(void); + static bool HasCatalinaBeenShotDown(void); + + static void ActivateHeli(bool activate); +}; + +VALIDATE_SIZE(CHeli, 0x33C); + diff --git a/src/vehicles/Plane.cpp b/src/vehicles/Plane.cpp new file mode 100644 index 0000000..8ea03bf --- /dev/null +++ b/src/vehicles/Plane.cpp @@ -0,0 +1,975 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "ModelIndices.h" +#include "FileMgr.h" +#include "Streaming.h" +#include "Replay.h" +#include "Camera.h" +#include "DMAudio.h" +#include "Wanted.h" +#include "Coronas.h" +#include "Particle.h" +#include "Explosion.h" +#include "World.h" +#include "HandlingMgr.h" +#include "Plane.h" +#include "MemoryHeap.h" + +CPlaneNode *pPathNodes; +CPlaneNode *pPath2Nodes; +CPlaneNode *pPath3Nodes; +CPlaneNode *pPath4Nodes; +int32 NumPathNodes; +int32 NumPath2Nodes; +int32 NumPath3Nodes; +int32 NumPath4Nodes; +float TotalLengthOfFlightPath; +float TotalLengthOfFlightPath2; +float TotalLengthOfFlightPath3; +float TotalLengthOfFlightPath4; +float TotalDurationOfFlightPath; +float TotalDurationOfFlightPath2; +float TotalDurationOfFlightPath3; +float TotalDurationOfFlightPath4; +float LandingPoint; +float TakeOffPoint; +CPlaneInterpolationLine aPlaneLineBits[6]; + +float PlanePathPosition[3]; +float OldPlanePathPosition[3]; +float PlanePathSpeed[3]; +float PlanePath2Position[3]; +float PlanePath3Position; +float PlanePath4Position; +float PlanePath2Speed[3]; +float PlanePath3Speed; +float PlanePath4Speed; + + +enum +{ + CESNA_STATUS_NONE, // doesn't even exist + CESNA_STATUS_FLYING, + CESNA_STATUS_DESTROYED, + CESNA_STATUS_LANDED, +}; + +int32 CesnaMissionStatus; +int32 CesnaMissionStartTime; +CPlane *pDrugRunCesna; +int32 DropOffCesnaMissionStatus; +int32 DropOffCesnaMissionStartTime; +CPlane *pDropOffCesna; + + +CPlane::CPlane(int32 id, uint8 CreatedBy) + : CVehicle(CreatedBy) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + m_vehType = VEHICLE_TYPE_PLANE; + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId); + SetModelIndex(id); + + m_fMass = 100000000.0f; + m_fTurnMass = 100000000.0f; + m_fAirResistance = 0.9994f; + m_fElasticity = 0.05f; + + bUsesCollision = false; + m_bHasBeenHit = false; + m_bIsDrugRunCesna = false; + m_bIsDropOffCesna = false; + + SetStatus(STATUS_PLANE); + bIsBIGBuilding = true; + m_level = LEVEL_GENERIC; + +#ifdef FIX_BUGS + m_isFarAway = false; +#endif +} + +CPlane::~CPlane() +{ + DeleteRwObject(); +} + +void +CPlane::SetModelIndex(uint32 id) +{ + CVehicle::SetModelIndex(id); +} + +void +CPlane::DeleteRwObject(void) +{ + if(m_rwObject && RwObjectGetType(m_rwObject) == rpATOMIC){ + GetMatrix().Detach(); + if(RwObjectGetType(m_rwObject) == rpATOMIC){ // useless check + RwFrame *f = RpAtomicGetFrame((RpAtomic*)m_rwObject); + RpAtomicDestroy((RpAtomic*)m_rwObject); + RwFrameDestroy(f); + } + m_rwObject = nil; + } + CEntity::DeleteRwObject(); +} + +// There's a LOT of copy and paste in here. Maybe this could be refactored somehow +void +CPlane::ProcessControl(void) +{ + int i; + CVector pos; + + // Explosion + if(m_bHasBeenHit){ + // BUG: since this is all based on frames, you can skip the explosion processing when you go into the menu + if(GetModelIndex() == MI_AIRTRAIN){ + int frm = CTimer::GetFrameCounter() - m_nFrameWhenHit; + if(frm == 20){ + static int nFrameGen; + CRGBA colors[8]; + + CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), 0); + + colors[0] = CRGBA(0, 0, 0, 255); + colors[1] = CRGBA(224, 230, 238, 255); + colors[2] = CRGBA(224, 230, 238, 255); + colors[3] = CRGBA(0, 0, 0, 255); + colors[4] = CRGBA(224, 230, 238, 255); + colors[5] = CRGBA(0, 0, 0, 255); + colors[6] = CRGBA(0, 0, 0, 255); + colors[7] = CRGBA(224, 230, 238, 255); + + CVector dir; + for(i = 0; i < 40; i++){ + dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f); + int rotSpeed = CGeneral::GetRandomNumberInRange(10, 30); + if(CGeneral::GetRandomNumber() & 1) + rotSpeed = -rotSpeed; + int f = ++nFrameGen & 3; + CParticle::AddParticle(PARTICLE_HELI_DEBRIS, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), dir, + nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f), + colors[nFrameGen&7], rotSpeed, 0, f, 0); + } + } + if(frm >= 40 && frm <= 80 && frm & 1){ + if(frm & 1){ + pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f; + pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f; + pos.y = frm - 40; + pos = GetMatrix() * pos; + }else{ + pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f; + pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f; + pos.y = 40 - frm; + pos = GetMatrix() * pos; + } + CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, pos, 0); + } + if(frm == 60) + bRenderScorched = true; + if(frm == 82){ + TheCamera.SetFadeColour(255, 255, 255); + TheCamera.Fade(0.0f, FADE_OUT); + TheCamera.ProcessFade(); + TheCamera.Fade(1.0f, FADE_IN); + FlagToDestroyWhenNextProcessed(); + } + }else{ + int frm = CTimer::GetFrameCounter() - m_nFrameWhenHit; + if(frm == 20){ + static int nFrameGen; + CRGBA colors[8]; + + CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), 0); + + colors[0] = CRGBA(0, 0, 0, 255); + colors[1] = CRGBA(224, 230, 238, 255); + colors[2] = CRGBA(224, 230, 238, 255); + colors[3] = CRGBA(0, 0, 0, 255); + colors[4] = CRGBA(252, 66, 66, 255); + colors[5] = CRGBA(0, 0, 0, 255); + colors[6] = CRGBA(0, 0, 0, 255); + colors[7] = CRGBA(252, 66, 66, 255); + + CVector dir; + for(i = 0; i < 40; i++){ + dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f); + int rotSpeed = CGeneral::GetRandomNumberInRange(30.0f, 20.0f); + if(CGeneral::GetRandomNumber() & 1) + rotSpeed = -rotSpeed; + int f = ++nFrameGen & 3; + CParticle::AddParticle(PARTICLE_HELI_DEBRIS, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), dir, + nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f), + colors[nFrameGen&7], rotSpeed, 0, f, 0); + } + } + if(frm >= 40 && frm <= 60 && frm & 1){ + if(frm & 1){ + pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f; + pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f; + pos.y = (frm - 40)*0.3f; + pos = GetMatrix() * pos; + }else{ + pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f; + pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f; + pos.y = (40 - frm)*0.3f; + pos = GetMatrix() * pos; + } + CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, pos, 0); + } + if(frm == 30) + bRenderScorched = true; + if(frm == 62){ + TheCamera.SetFadeColour(200, 200, 200); + TheCamera.Fade(0.0f, FADE_OUT); + TheCamera.ProcessFade(); + TheCamera.Fade(1.0f, FADE_IN); + if(m_bIsDrugRunCesna){ + CesnaMissionStatus = CESNA_STATUS_DESTROYED; + pDrugRunCesna = nil; + } + if(m_bIsDropOffCesna){ + DropOffCesnaMissionStatus = CESNA_STATUS_DESTROYED; + pDropOffCesna = nil; + } + FlagToDestroyWhenNextProcessed(); + } + } + } + + // Update plane position and speed + if(GetModelIndex() == MI_AIRTRAIN || !m_isFarAway || ((CTimer::GetFrameCounter() + m_randomSeed) & 7) == 0){ + if(GetModelIndex() == MI_AIRTRAIN){ + float pathPositionRear = PlanePathPosition[m_nPlaneId] - 30.0f; + if(pathPositionRear < 0.0f) + pathPositionRear += TotalLengthOfFlightPath; + float pathPosition = pathPositionRear + 30.0f; + + float pitch = 0.0f; + float distSinceTakeOff = pathPosition - TakeOffPoint; + if(distSinceTakeOff <= 0.0f && distSinceTakeOff > -70.0f){ + // shortly before take off + pitch = 1.0f - distSinceTakeOff/-70.0f; + }else if(distSinceTakeOff >= 0.0f && distSinceTakeOff < 100.0f){ + // shortly after take off + pitch = 1.0f - distSinceTakeOff/100.0f; + } + + float distSinceLanding = pathPosition - LandingPoint; + if(distSinceLanding <= 0.0f && distSinceLanding > -200.0f){ + // shortly before landing + pitch = 1.0f - distSinceLanding/-200.0f; + }else if(distSinceLanding >= 0.0f && distSinceLanding < 70.0f){ + // shortly after landing + pitch = 1.0f - distSinceLanding/70.0f; + } + + + // Advance current node to appropriate position + float pos1, pos2; + int nextTrackNode = m_nCurPathNode + 1; + pos1 = pPathNodes[m_nCurPathNode].t; + if(nextTrackNode < NumPathNodes) + pos2 = pPathNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = TotalLengthOfFlightPath; + } + while(pathPositionRear < pos1 || pathPositionRear > pos2){ + m_nCurPathNode = (m_nCurPathNode+1) % NumPathNodes; + nextTrackNode = m_nCurPathNode + 1; + pos1 = pPathNodes[m_nCurPathNode].t; + if(nextTrackNode < NumPathNodes) + pos2 = pPathNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = TotalLengthOfFlightPath; + } + } + bool bothOnGround = pPathNodes[m_nCurPathNode].bOnGround && pPathNodes[nextTrackNode].bOnGround; + if(PlanePathPosition[m_nPlaneId] >= LandingPoint && OldPlanePathPosition[m_nPlaneId] < LandingPoint) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_PLANE_ON_GROUND, 0.0f); + float dist = pPathNodes[nextTrackNode].t - pPathNodes[m_nCurPathNode].t; + if(dist < 0.0f) + dist += TotalLengthOfFlightPath; + float f = (pathPositionRear - pPathNodes[m_nCurPathNode].t)/dist; + CVector posRear = (1.0f - f)*pPathNodes[m_nCurPathNode].p + f*pPathNodes[nextTrackNode].p; + + // Same for the front + float pathPositionFront = pathPositionRear + 60.0f; + if(pathPositionFront > TotalLengthOfFlightPath) + pathPositionFront -= TotalLengthOfFlightPath; + int curPathNodeFront = m_nCurPathNode; + int nextPathNodeFront = curPathNodeFront + 1; + pos1 = pPathNodes[curPathNodeFront].t; + if(nextPathNodeFront < NumPathNodes) + pos2 = pPathNodes[nextPathNodeFront].t; + else{ + nextPathNodeFront = 0; + pos2 = TotalLengthOfFlightPath; + } + while(pathPositionFront < pos1 || pathPositionFront > pos2){ + curPathNodeFront = (curPathNodeFront+1) % NumPathNodes; + nextPathNodeFront = curPathNodeFront + 1; + pos1 = pPathNodes[curPathNodeFront].t; + if(nextPathNodeFront < NumPathNodes) + pos2 = pPathNodes[nextPathNodeFront].t; + else{ + nextPathNodeFront = 0; + pos2 = TotalLengthOfFlightPath; + } + } + dist = pPathNodes[nextPathNodeFront].t - pPathNodes[curPathNodeFront].t; + if(dist < 0.0f) + dist += TotalLengthOfFlightPath; + f = (pathPositionFront - pPathNodes[curPathNodeFront].t)/dist; + CVector posFront = (1.0f - f)*pPathNodes[curPathNodeFront].p + f*pPathNodes[nextPathNodeFront].p; + + // And for another point 60 units in front of the plane, used to calculate roll + float pathPositionFront2 = pathPositionFront + 60.0f; + if(pathPositionFront2 > TotalLengthOfFlightPath) + pathPositionFront2 -= TotalLengthOfFlightPath; + int curPathNodeFront2 = m_nCurPathNode; + int nextPathNodeFront2 = curPathNodeFront2 + 1; + pos1 = pPathNodes[curPathNodeFront2].t; + if(nextPathNodeFront2 < NumPathNodes) + pos2 = pPathNodes[nextPathNodeFront2].t; + else{ + nextPathNodeFront2 = 0; + pos2 = TotalLengthOfFlightPath; + } + while(pathPositionFront2 < pos1 || pathPositionFront2 > pos2){ + curPathNodeFront2 = (curPathNodeFront2+1) % NumPathNodes; + nextPathNodeFront2 = curPathNodeFront2 + 1; + pos1 = pPathNodes[curPathNodeFront2].t; + if(nextPathNodeFront2 < NumPathNodes) + pos2 = pPathNodes[nextPathNodeFront2].t; + else{ + nextPathNodeFront2 = 0; + pos2 = TotalLengthOfFlightPath; + } + } + dist = pPathNodes[nextPathNodeFront2].t - pPathNodes[curPathNodeFront2].t; + if(dist < 0.0f) + dist += TotalLengthOfFlightPath; + f = (pathPositionFront2 - pPathNodes[curPathNodeFront2].t)/dist; + CVector posFront2 = (1.0f - f)*pPathNodes[curPathNodeFront2].p + f*pPathNodes[nextPathNodeFront2].p; + + // Now set matrix + GetMatrix().SetTranslateOnly((posRear + posFront) / 2.0f); + GetMatrix().GetPosition().z += 4.3f; + CVector fwd = posFront - posRear; + fwd.Normalise(); + if(pitch != 0.0f){ + fwd.z += 0.4f*pitch; + fwd.Normalise(); + } + CVector fwd2 = posFront2 - posRear; + fwd2.Normalise(); + CVector roll = CrossProduct(fwd, fwd2); + CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f)); + if(!bothOnGround) + right.z += 3.0f*roll.z; + right.Normalise(); + CVector up = CrossProduct(right, fwd); + GetMatrix().GetRight() = right; + GetMatrix().GetUp() = up; + GetMatrix().GetForward() = fwd; + + // Set speed + m_vecMoveSpeed = fwd*PlanePathSpeed[m_nPlaneId]/60.0f; + m_fSpeed = PlanePathSpeed[m_nPlaneId]/60.0f; + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + + m_isFarAway = !((posFront - TheCamera.GetPosition()).MagnitudeSqr2D() < sq(300.0f)); + }else{ + float planePathPosition; + float totalLengthOfFlightPath; + CPlaneNode *pathNodes; + float planePathSpeed; + int numPathNodes; + + if(m_bIsDrugRunCesna){ + planePathPosition = PlanePath3Position; + totalLengthOfFlightPath = TotalLengthOfFlightPath3; + pathNodes = pPath3Nodes; + planePathSpeed = PlanePath3Speed; + numPathNodes = NumPath3Nodes; + if(CesnaMissionStatus == CESNA_STATUS_LANDED){ + pDrugRunCesna = nil; + FlagToDestroyWhenNextProcessed(); + } + }else if(m_bIsDropOffCesna){ + planePathPosition = PlanePath4Position; + totalLengthOfFlightPath = TotalLengthOfFlightPath4; + pathNodes = pPath4Nodes; + planePathSpeed = PlanePath4Speed; + numPathNodes = NumPath4Nodes; + if(DropOffCesnaMissionStatus == CESNA_STATUS_LANDED){ + pDropOffCesna = nil; + FlagToDestroyWhenNextProcessed(); + } + }else{ + planePathPosition = PlanePath2Position[m_nPlaneId]; + totalLengthOfFlightPath = TotalLengthOfFlightPath2; + pathNodes = pPath2Nodes; + planePathSpeed = PlanePath2Speed[m_nPlaneId]; + numPathNodes = NumPath2Nodes; + } + + // Advance current node to appropriate position + float pathPositionRear = planePathPosition - 10.0f; + if(pathPositionRear < 0.0f) + pathPositionRear += totalLengthOfFlightPath; + float pos1, pos2; + int nextTrackNode = m_nCurPathNode + 1; + pos1 = pathNodes[m_nCurPathNode].t; + if(nextTrackNode < numPathNodes) + pos2 = pathNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = totalLengthOfFlightPath; + } + while(pathPositionRear < pos1 || pathPositionRear > pos2){ + m_nCurPathNode = (m_nCurPathNode+1) % numPathNodes; + nextTrackNode = m_nCurPathNode + 1; + pos1 = pathNodes[m_nCurPathNode].t; + if(nextTrackNode < numPathNodes) + pos2 = pathNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = totalLengthOfFlightPath; + } + } + float dist = pathNodes[nextTrackNode].t - pathNodes[m_nCurPathNode].t; + if(dist < 0.0f) + dist += totalLengthOfFlightPath; + float f = (pathPositionRear - pathNodes[m_nCurPathNode].t)/dist; + CVector posRear = (1.0f - f)*pathNodes[m_nCurPathNode].p + f*pathNodes[nextTrackNode].p; + + // Same for the front + float pathPositionFront = pathPositionRear + 20.0f; + if(pathPositionFront > totalLengthOfFlightPath) + pathPositionFront -= totalLengthOfFlightPath; + int curPathNodeFront = m_nCurPathNode; + int nextPathNodeFront = curPathNodeFront + 1; + pos1 = pathNodes[curPathNodeFront].t; + if(nextPathNodeFront < numPathNodes) + pos2 = pathNodes[nextPathNodeFront].t; + else{ + nextPathNodeFront = 0; + pos2 = totalLengthOfFlightPath; + } + while(pathPositionFront < pos1 || pathPositionFront > pos2){ + curPathNodeFront = (curPathNodeFront+1) % numPathNodes; + nextPathNodeFront = curPathNodeFront + 1; + pos1 = pathNodes[curPathNodeFront].t; + if(nextPathNodeFront < numPathNodes) + pos2 = pathNodes[nextPathNodeFront].t; + else{ + nextPathNodeFront = 0; + pos2 = totalLengthOfFlightPath; + } + } + dist = pathNodes[nextPathNodeFront].t - pathNodes[curPathNodeFront].t; + if(dist < 0.0f) + dist += totalLengthOfFlightPath; + f = (pathPositionFront - pathNodes[curPathNodeFront].t)/dist; + CVector posFront = (1.0f - f)*pathNodes[curPathNodeFront].p + f*pathNodes[nextPathNodeFront].p; + + // And for another point 30 units in front of the plane, used to calculate roll + float pathPositionFront2 = pathPositionFront + 30.0f; + if(pathPositionFront2 > totalLengthOfFlightPath) + pathPositionFront2 -= totalLengthOfFlightPath; + int curPathNodeFront2 = m_nCurPathNode; + int nextPathNodeFront2 = curPathNodeFront2 + 1; + pos1 = pathNodes[curPathNodeFront2].t; + if(nextPathNodeFront2 < numPathNodes) + pos2 = pathNodes[nextPathNodeFront2].t; + else{ + nextPathNodeFront2 = 0; + pos2 = totalLengthOfFlightPath; + } + while(pathPositionFront2 < pos1 || pathPositionFront2 > pos2){ + curPathNodeFront2 = (curPathNodeFront2+1) % numPathNodes; + nextPathNodeFront2 = curPathNodeFront2 + 1; + pos1 = pathNodes[curPathNodeFront2].t; + if(nextPathNodeFront2 < numPathNodes) + pos2 = pathNodes[nextPathNodeFront2].t; + else{ + nextPathNodeFront2 = 0; + pos2 = totalLengthOfFlightPath; + } + } + dist = pathNodes[nextPathNodeFront2].t - pathNodes[curPathNodeFront2].t; + if(dist < 0.0f) + dist += totalLengthOfFlightPath; + f = (pathPositionFront2 - pathNodes[curPathNodeFront2].t)/dist; + CVector posFront2 = (1.0f - f)*pathNodes[curPathNodeFront2].p + f*pathNodes[nextPathNodeFront2].p; + + // Now set matrix + GetMatrix().SetTranslateOnly((posRear + posFront) / 2.0f); + GetMatrix().GetPosition().z += 1.0f; + CVector fwd = posFront - posRear; + fwd.Normalise(); + CVector fwd2 = posFront2 - posRear; + fwd2.Normalise(); + CVector roll = CrossProduct(fwd, fwd2); + CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f)); + right.z += 3.0f*roll.z; + right.Normalise(); + CVector up = CrossProduct(right, fwd); + GetMatrix().GetRight() = right; + GetMatrix().GetUp() = up; + GetMatrix().GetForward() = fwd; + + // Set speed + m_vecMoveSpeed = fwd*planePathSpeed/60.0f; + m_fSpeed = planePathSpeed/60.0f; + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + + m_isFarAway = !((posFront - TheCamera.GetPosition()).MagnitudeSqr2D() < sq(300.0f)); + } + } + + bIsInSafePosition = true; + GetMatrix().UpdateRW(); + UpdateRwFrame(); + + // Handle streaming and such + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + if(m_isFarAway){ + // Switch to LOD model + if(m_rwObject && RwObjectGetType(m_rwObject) == rpCLUMP){ + DeleteRwObject(); + if(mi->m_planeLodId != -1){ + PUSH_MEMID(MEMID_WORLD); + m_rwObject = CModelInfo::GetModelInfo(mi->m_planeLodId)->CreateInstance(); + POP_MEMID(); + if(m_rwObject) + GetMatrix().AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic*)m_rwObject))); + } + } + }else if(CStreaming::HasModelLoaded(GetModelIndex())){ + if(m_rwObject && RwObjectGetType(m_rwObject) == rpATOMIC){ + // Get rid of LOD model + GetMatrix().Detach(); + if(m_rwObject){ // useless check + if(RwObjectGetType(m_rwObject) == rpATOMIC){ // useless check + RwFrame *f = RpAtomicGetFrame((RpAtomic*)m_rwObject); + RpAtomicDestroy((RpAtomic*)m_rwObject); + RwFrameDestroy(f); + } + m_rwObject = nil; + } + } + // Set high detail model + if(m_rwObject == nil){ + int id = GetModelIndex(); + m_modelIndex = -1; + SetModelIndex(id); + } + }else{ + CStreaming::RequestModel(GetModelIndex(), STREAMFLAGS_DEPENDENCY); + } +} + +void +CPlane::PreRender(void) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + CVector lookVector = GetPosition() - TheCamera.GetPosition(); + float camDist = lookVector.Magnitude(); + if(camDist != 0.0f) + lookVector *= 1.0f/camDist; + else + lookVector = CVector(1.0f, 0.0f, 0.0f); + float behindness = DotProduct(lookVector, GetForward()); + + // Wing lights + if(behindness < 0.0f){ + // in front of plane + CVector lightPos = mi->m_positions[PLANE_POS_LIGHT_RIGHT]; + CVector lightR = GetMatrix() * lightPos; + CVector lightL = lightR; + lightL -= GetRight()*2.0f*lightPos.x; + + float intensity = -0.6f*behindness + 0.4f; + float size = 1.0f - behindness; + + if(behindness < -0.9f && camDist < 50.0f){ + // directly in front + CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255, + lightL, size, 240.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255, + lightR, size, 240.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + }else{ + CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255, + lightL, size, 240.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255, + lightR, size, 240.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + } + } + + // Tail light + if(CTimer::GetTimeInMilliseconds() & 0x200){ + CVector pos = GetMatrix() * mi->m_positions[PLANE_POS_LIGHT_TAIL]; + + CCoronas::RegisterCorona((uintptr)this + 12, 255, 0, 0, 255, + pos, 1.0f, 120.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + } +} + +void +CPlane::Render(void) +{ + CEntity::Render(); +} + +#define CRUISE_SPEED (50.0f) +#define TAXI_SPEED (5.0f) + +void +CPlane::InitPlanes(void) +{ + int i; + + CesnaMissionStatus = CESNA_STATUS_NONE; + + // Jumbo + if(pPathNodes == nil){ + pPathNodes = LoadPath("data\\paths\\flight.dat", NumPathNodes, TotalLengthOfFlightPath, true); + + // Figure out which nodes are on ground + CColPoint colpoint; + CEntity *entity; + for(i = 0; i < NumPathNodes; i++){ + if(CWorld::ProcessVerticalLine(pPathNodes[i].p, 1000.0f, colpoint, entity, true, false, false, false, true, false, nil)){ + pPathNodes[i].p.z = colpoint.point.z; + pPathNodes[i].bOnGround = true; + }else + pPathNodes[i].bOnGround = false; + } + + // Find lading and takeoff points + LandingPoint = -1.0f; + TakeOffPoint = -1.0f; + bool lastOnGround = pPathNodes[NumPathNodes-1].bOnGround; + for(i = 0; i < NumPathNodes; i++){ + if(pPathNodes[i].bOnGround && !lastOnGround) + LandingPoint = pPathNodes[i].t; + else if(!pPathNodes[i].bOnGround && lastOnGround) + TakeOffPoint = pPathNodes[i].t; + lastOnGround = pPathNodes[i].bOnGround; + } + + // Animation + float time = 0.0f; + float position = 0.0f; + // Start on ground with slow speed + aPlaneLineBits[0].type = 1; + aPlaneLineBits[0].time = time; + aPlaneLineBits[0].position = position; + aPlaneLineBits[0].speed = TAXI_SPEED; + aPlaneLineBits[0].acceleration = 0.0f; + float dist = (TakeOffPoint-600.0f) - position; + time += dist/TAXI_SPEED; + position += dist; + + // Accelerate to take off + aPlaneLineBits[1].type = 2; + aPlaneLineBits[1].time = time; + aPlaneLineBits[1].position = position; + aPlaneLineBits[1].speed = TAXI_SPEED; + aPlaneLineBits[1].acceleration = 618.75f/600.0f; + time += 600.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f); + position += 600.0f; + + // Fly at cruise speed + aPlaneLineBits[2].type = 1; + aPlaneLineBits[2].time = time; + aPlaneLineBits[2].position = position; + aPlaneLineBits[2].speed = CRUISE_SPEED; + aPlaneLineBits[2].acceleration = 0.0f; + dist = LandingPoint - TakeOffPoint; + time += dist/CRUISE_SPEED; + position += dist; + + // Brake after landing + aPlaneLineBits[3].type = 2; + aPlaneLineBits[3].time = time; + aPlaneLineBits[3].position = position; + aPlaneLineBits[3].speed = CRUISE_SPEED; + aPlaneLineBits[3].acceleration = -618.75f/600.0f; + time += 600.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f); + position += 600.0f; + + // Taxi + aPlaneLineBits[4].type = 1; + aPlaneLineBits[4].time = time; + aPlaneLineBits[4].position = position; + aPlaneLineBits[4].speed = TAXI_SPEED; + aPlaneLineBits[4].acceleration = 0.0f; + time += (TotalLengthOfFlightPath - position)/TAXI_SPEED; + + // end + aPlaneLineBits[5].time = time; + TotalDurationOfFlightPath = time; + } + + // Dodo + if(pPath2Nodes == nil){ + pPath2Nodes = LoadPath("data\\paths\\flight2.dat", NumPath2Nodes, TotalLengthOfFlightPath2, true); + TotalDurationOfFlightPath2 = TotalLengthOfFlightPath2/CRUISE_SPEED; + } + + // Mission Cesna + if(pPath3Nodes == nil){ + pPath3Nodes = LoadPath("data\\paths\\flight3.dat", NumPath3Nodes, TotalLengthOfFlightPath3, false); + TotalDurationOfFlightPath3 = TotalLengthOfFlightPath3/CRUISE_SPEED; + } + + // Mission Cesna + if(pPath4Nodes == nil){ + pPath4Nodes = LoadPath("data\\paths\\flight4.dat", NumPath4Nodes, TotalLengthOfFlightPath4, false); + TotalDurationOfFlightPath4 = TotalLengthOfFlightPath4/CRUISE_SPEED; + } + + CStreaming::LoadAllRequestedModels(false); + CStreaming::RequestModel(MI_AIRTRAIN, 0); + CStreaming::LoadAllRequestedModels(false); + + for(i = 0; i < 3; i++){ + CPlane *plane = new CPlane(MI_AIRTRAIN, PERMANENT_VEHICLE); + plane->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + plane->SetStatus(STATUS_ABANDONED); + plane->bIsLocked = true; + plane->m_nPlaneId = i; + plane->m_nCurPathNode = 0; + CWorld::Add(plane); + } + + + CStreaming::RequestModel(MI_DEADDODO, 0); + CStreaming::LoadAllRequestedModels(false); + + for(i = 0; i < 3; i++){ + CPlane *plane = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE); + plane->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + plane->SetStatus(STATUS_ABANDONED); + plane->bIsLocked = true; + plane->m_nPlaneId = i; + plane->m_nCurPathNode = 0; + CWorld::Add(plane); + } +} + +void +CPlane::Shutdown(void) +{ + delete[] pPathNodes; + delete[] pPath2Nodes; + delete[] pPath3Nodes; + delete[] pPath4Nodes; + pPathNodes = nil; + pPath2Nodes = nil; + pPath3Nodes = nil; + pPath4Nodes = nil; +} + +CPlaneNode* +CPlane::LoadPath(char const *filename, int32 &numNodes, float &totalLength, bool loop) +{ + int bp, lp; + int i; + + CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r"); + *gString = '\0'; + for(bp = 0, lp = 0; work_buff[bp] != '\n'; bp++, lp++) + gString[lp] = work_buff[bp]; + bp++; + gString[lp] = '\0'; + sscanf(gString, "%d", &numNodes); + CPlaneNode *nodes = new CPlaneNode[numNodes]; + + for(i = 0; i < numNodes; i++){ + *gString = '\0'; + for(lp = 0; work_buff[bp] != '\n'; bp++, lp++) + gString[lp] = work_buff[bp]; + bp++; + // BUG: game doesn't terminate string + gString[lp] = '\0'; + sscanf(gString, "%f %f %f", &nodes[i].p.x, &nodes[i].p.y, &nodes[i].p.z); + } + + // Calculate length of segments and path + totalLength = 0.0f; + for(i = 0; i < numNodes; i++){ + nodes[i].t = totalLength; + float l = (nodes[(i+1) % numNodes].p - nodes[i].p).Magnitude2D(); + if(!loop && i == numNodes-1) + l = 0.0f; + totalLength += l; + } + + return nodes; +} + +void +CPlane::UpdatePlanes(void) +{ + int i, j; + uint32 time; + float t, deltaT; + + if(CReplay::IsPlayingBack()) + return; + + // Jumbo jets + time = CTimer::GetTimeInMilliseconds(); + for(i = 0; i < 3; i++){ + t = TotalDurationOfFlightPath * (float)(time & 0x7FFFF)/0x80000; + // find current frame + for(j = 0; t > aPlaneLineBits[j+1].time; j++); + + OldPlanePathPosition[i] = PlanePathPosition[i]; + deltaT = t - aPlaneLineBits[j].time; + switch(aPlaneLineBits[j].type){ + case 0: // standing still + PlanePathPosition[i] = aPlaneLineBits[j].position; + PlanePathSpeed[i] = 0.0f; + break; + case 1: // moving with constant speed + PlanePathPosition[i] = aPlaneLineBits[j].position + aPlaneLineBits[j].speed*deltaT; + PlanePathSpeed[i] = (TotalDurationOfFlightPath*1000.0f/0x80000) * aPlaneLineBits[j].speed; + break; + case 2: // accelerating/braking + PlanePathPosition[i] = aPlaneLineBits[j].position + (aPlaneLineBits[j].speed + aPlaneLineBits[j].acceleration*deltaT)*deltaT; + PlanePathSpeed[i] = (TotalDurationOfFlightPath*1000.0f/0x80000)*aPlaneLineBits[j].speed + 2.0f*aPlaneLineBits[j].acceleration*deltaT; + break; + } + + // time offset for each plane + time += 0x80000/3; + } + + time = CTimer::GetTimeInMilliseconds(); + + t = TotalDurationOfFlightPath2/0x80000; + PlanePath2Position[0] = CRUISE_SPEED * (time & 0x7FFFF)*t; + PlanePath2Position[1] = CRUISE_SPEED * ((time + 0x80000/3) & 0x7FFFF)*t; + PlanePath2Position[2] = CRUISE_SPEED * ((time + 0x80000/3*2) & 0x7FFFF)*t; + PlanePath2Speed[0] = CRUISE_SPEED*t; + PlanePath2Speed[1] = CRUISE_SPEED*t; + PlanePath2Speed[2] = CRUISE_SPEED*t; + + if(CesnaMissionStatus == CESNA_STATUS_FLYING){ + PlanePath3Speed = CRUISE_SPEED*TotalDurationOfFlightPath3/0x20000; + PlanePath3Position = PlanePath3Speed * ((time - CesnaMissionStartTime) & 0x1FFFF); + if(time - CesnaMissionStartTime >= 128072) + CesnaMissionStatus = CESNA_STATUS_LANDED; + } + + if(DropOffCesnaMissionStatus == CESNA_STATUS_FLYING){ + PlanePath4Speed = CRUISE_SPEED*TotalDurationOfFlightPath4/0x80000; + PlanePath4Position = PlanePath4Speed * ((time - DropOffCesnaMissionStartTime) & 0x7FFFF); + if(time - DropOffCesnaMissionStartTime >= 521288) + DropOffCesnaMissionStatus = CESNA_STATUS_LANDED; + } +} + +bool +CPlane::TestRocketCollision(CVector *rocketPos) +{ + int i; + + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + CPlane *plane = (CPlane*)CPools::GetVehiclePool()->GetSlot(i); + if(plane && +#ifdef EXPLODING_AIRTRAIN + (plane->GetModelIndex() == MI_AIRTRAIN || plane->GetModelIndex() == MI_DEADDODO) && +#else + plane->GetModelIndex() != MI_AIRTRAIN && plane->GetModelIndex() == MI_DEADDODO && // strange check +#endif + !plane->m_bHasBeenHit && (*rocketPos - plane->GetPosition()).Magnitude() < 25.0f){ + plane->m_nFrameWhenHit = CTimer::GetFrameCounter(); + plane->m_bHasBeenHit = true; + CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->RegisterCrime_Immediately(CRIME_DESTROYED_CESSNA, + plane->GetPosition(), i+1983, false); + return true; + } + } + return false; +} + +// BUG: not in CPlane in the game +void +CPlane::CreateIncomingCesna(void) +{ + if(CesnaMissionStatus == CESNA_STATUS_FLYING){ + CWorld::Remove(pDrugRunCesna); + delete pDrugRunCesna; + pDrugRunCesna = nil; + } + pDrugRunCesna = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE); + pDrugRunCesna->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + pDrugRunCesna->SetStatus(STATUS_ABANDONED); + pDrugRunCesna->bIsLocked = true; + pDrugRunCesna->m_nPlaneId = 0; + pDrugRunCesna->m_nCurPathNode = 0; + pDrugRunCesna->m_bIsDrugRunCesna = true; + CWorld::Add(pDrugRunCesna); + + CesnaMissionStatus = CESNA_STATUS_FLYING; + CesnaMissionStartTime = CTimer::GetTimeInMilliseconds(); + printf("CPlane::CreateIncomingCesna(void)\n"); +} + +void +CPlane::CreateDropOffCesna(void) +{ + if(DropOffCesnaMissionStatus == CESNA_STATUS_FLYING){ + CWorld::Remove(pDropOffCesna); + delete pDropOffCesna; + pDropOffCesna = nil; + } + pDropOffCesna = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE); + pDropOffCesna->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + pDropOffCesna->SetStatus(STATUS_ABANDONED); + pDropOffCesna->bIsLocked = true; + pDropOffCesna->m_nPlaneId = 0; + pDropOffCesna->m_nCurPathNode = 0; + pDropOffCesna->m_bIsDropOffCesna = true; + CWorld::Add(pDropOffCesna); + + DropOffCesnaMissionStatus = CESNA_STATUS_FLYING; + DropOffCesnaMissionStartTime = CTimer::GetTimeInMilliseconds(); + printf("CPlane::CreateDropOffCesna(void)\n"); +} + +const CVector CPlane::FindDrugPlaneCoordinates(void) { return pDrugRunCesna->GetPosition(); } +const CVector CPlane::FindDropOffCesnaCoordinates(void) { return pDropOffCesna->GetPosition(); } +bool CPlane::HasCesnaLanded(void) { return CesnaMissionStatus == CESNA_STATUS_LANDED; } +bool CPlane::HasCesnaBeenDestroyed(void) { return CesnaMissionStatus == CESNA_STATUS_DESTROYED; } +bool CPlane::HasDropOffCesnaBeenShotDown(void) { return DropOffCesnaMissionStatus == CESNA_STATUS_DESTROYED; } diff --git a/src/vehicles/Plane.h b/src/vehicles/Plane.h new file mode 100644 index 0000000..783c53b --- /dev/null +++ b/src/vehicles/Plane.h @@ -0,0 +1,71 @@ +#pragma once + +#include "Vehicle.h" + +enum ePlaneNodes +{ + PLANE_WHEEL_FRONT = 2, + PLANE_WHEEL_READ, + NUM_PLANE_NODES +}; + +struct CPlaneNode +{ + CVector p; // position + float t; // xy-distance from start on path + bool bOnGround; // i.e. not flying +}; + +struct CPlaneInterpolationLine +{ + uint8 type; + float time; // when does this keyframe start + // initial values at start of frame + float position; + float speed; + float acceleration; +}; + +class CPlane : public CVehicle +{ +public: + // 0x288 + int16 m_nPlaneId; + int16 m_isFarAway; + int16 m_nCurPathNode; + float m_fSpeed; + uint32 m_nFrameWhenHit; + bool m_bHasBeenHit; + bool m_bIsDrugRunCesna; + bool m_bIsDropOffCesna; + + CPlane(int32 id, uint8 CreatedBy); + ~CPlane(void); + + // from CEntity + void SetModelIndex(uint32 id); + void DeleteRwObject(void); + void ProcessControl(void); + void PreRender(void); + void Render(void); + void FlagToDestroyWhenNextProcessed() { bRemoveFromWorld = true; } + + static void InitPlanes(void); + static void Shutdown(void); + static CPlaneNode *LoadPath(char const *filename, int32 &numNodes, float &totalLength, bool loop); + static void UpdatePlanes(void); + static bool TestRocketCollision(CVector *rocketPos); + static void CreateIncomingCesna(void); + static void CreateDropOffCesna(void); + static const CVector FindDrugPlaneCoordinates(void); + static const CVector FindDropOffCesnaCoordinates(void); + static bool HasCesnaLanded(void); + static bool HasCesnaBeenDestroyed(void); + static bool HasDropOffCesnaBeenShotDown(void); +}; + +VALIDATE_SIZE(CPlane, 0x29C); + +extern float LandingPoint; +extern float TakeOffPoint; +extern float PlanePathPosition[3]; diff --git a/src/vehicles/Train.cpp b/src/vehicles/Train.cpp new file mode 100644 index 0000000..be546c7 --- /dev/null +++ b/src/vehicles/Train.cpp @@ -0,0 +1,738 @@ +#include "common.h" +#include "main.h" + +#include "Timer.h" +#include "ModelIndices.h" +#include "FileMgr.h" +#include "Streaming.h" +#include "Pad.h" +#include "Camera.h" +#include "Coronas.h" +#include "World.h" +#include "Ped.h" +#include "DMAudio.h" +#include "HandlingMgr.h" +#include "Train.h" +#include "AudioScriptObject.h" + +static CTrainNode* pTrackNodes; +static int16 NumTrackNodes; +static float StationDist[3] = { 873.0f, 1522.0f, 2481.0f }; +static float TotalLengthOfTrack; +static float TotalDurationOfTrack; +static CTrainInterpolationLine aLineBits[17]; +static float EngineTrackPosition[2]; +static float EngineTrackSpeed[2]; + +static CTrainNode* pTrackNodes_S; +static int16 NumTrackNodes_S; +static float StationDist_S[4] = { 55.0f, 1388.0f, 2337.0f, 3989.0f }; +static float TotalLengthOfTrack_S; +static float TotalDurationOfTrack_S; +static CTrainInterpolationLine aLineBits_S[18]; +static float EngineTrackPosition_S[4]; +static float EngineTrackSpeed_S[4]; + +CVector CTrain::aStationCoors[3]; +CVector CTrain::aStationCoors_S[4]; + +static bool bTrainArrivalAnnounced[3] = {false, false, false}; + +CTrain::CTrain(int32 id, uint8 CreatedBy) + : CVehicle(CreatedBy) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + m_vehType = VEHICLE_TYPE_TRAIN; + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId); + SetModelIndex(id); + + Doors[0].Init(0.8f, 0.0f, 1, 0); + Doors[1].Init(-0.8f, 0.0f, 0, 0); + + m_fMass = 100000000.0f; + m_fTurnMass = 100000000.0f; + m_fAirResistance = 0.9994f; + m_fElasticity = 0.05f; + + m_bProcessDoor = true; + m_bTrainStopping = false; + m_nTrackId = TRACK_ELTRAIN; + m_nNumMaxPassengers = 5; + m_nDoorTimer = CTimer::GetTimeInMilliseconds(); + m_nDoorState = TRAIN_DOOR_CLOSED; + + bUsesCollision = true; + SetStatus(STATUS_TRAIN_MOVING); + +#ifdef FIX_BUGS + m_isFarAway = true; +#endif +} + +void +CTrain::SetModelIndex(uint32 id) +{ + int i; + + CVehicle::SetModelIndex(id); + for(i = 0; i < NUM_TRAIN_NODES; i++) + m_aTrainNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), m_aTrainNodes); +} + +void +CTrain::ProcessControl(void) +{ + if(gbModelViewer || m_isFarAway && (CTimer::GetFrameCounter() + m_nWagonId) & 0xF) + return; + + CTrainNode *trackNodes; + int16 numTrackNodes; + float totalLengthOfTrack; + float *engineTrackPosition; + float *engineTrackSpeed; + + if(m_nTrackId == TRACK_SUBWAY){ + trackNodes = pTrackNodes_S; + numTrackNodes = NumTrackNodes_S; + totalLengthOfTrack = TotalLengthOfTrack_S; + engineTrackPosition = EngineTrackPosition_S; + engineTrackSpeed = EngineTrackSpeed_S; + }else{ + trackNodes = pTrackNodes; + numTrackNodes = NumTrackNodes; + totalLengthOfTrack = TotalLengthOfTrack; + engineTrackPosition = EngineTrackPosition; + engineTrackSpeed = EngineTrackSpeed; + } + + float trackPositionRear = engineTrackPosition[m_nWagonGroup] - m_fWagonPosition; + if(trackPositionRear < 0.0f) + trackPositionRear += totalLengthOfTrack; + + // Advance current node to appropriate position + float pos1, pos2; + int nextTrackNode = m_nCurTrackNode + 1; + pos1 = trackNodes[m_nCurTrackNode].t; + if(nextTrackNode < numTrackNodes) + pos2 = trackNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = totalLengthOfTrack; + } + while(trackPositionRear < pos1 || trackPositionRear > pos2){ + m_nCurTrackNode = (m_nCurTrackNode+1) % numTrackNodes; + nextTrackNode = m_nCurTrackNode + 1; + pos1 = trackNodes[m_nCurTrackNode].t; + if(nextTrackNode < numTrackNodes) + pos2 = trackNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = totalLengthOfTrack; + } + } + float dist = trackNodes[nextTrackNode].t - trackNodes[m_nCurTrackNode].t; + if(dist < 0.0f) + dist += totalLengthOfTrack; + float f = (trackPositionRear - trackNodes[m_nCurTrackNode].t)/dist; + CVector posRear = (1.0f - f)*trackNodes[m_nCurTrackNode].p + f*trackNodes[nextTrackNode].p; + + // Now same again for the front + float trackPositionFront = trackPositionRear + 20.0f; + if(trackPositionFront > totalLengthOfTrack) + trackPositionFront -= totalLengthOfTrack; + int curTrackNodeFront = m_nCurTrackNode; + int nextTrackNodeFront = curTrackNodeFront + 1; + pos1 = trackNodes[curTrackNodeFront].t; + if(nextTrackNodeFront < numTrackNodes) + pos2 = trackNodes[nextTrackNodeFront].t; + else{ + nextTrackNodeFront = 0; + pos2 = totalLengthOfTrack; + } + while(trackPositionFront < pos1 || trackPositionFront > pos2){ + curTrackNodeFront = (curTrackNodeFront+1) % numTrackNodes; + nextTrackNodeFront = curTrackNodeFront + 1; + pos1 = trackNodes[curTrackNodeFront].t; + if(nextTrackNodeFront < numTrackNodes) + pos2 = trackNodes[nextTrackNodeFront].t; + else{ + nextTrackNodeFront = 0; + pos2 = totalLengthOfTrack; + } + } + dist = trackNodes[nextTrackNodeFront].t - trackNodes[curTrackNodeFront].t; + if(dist < 0.0f) + dist += totalLengthOfTrack; + f = (trackPositionFront - trackNodes[curTrackNodeFront].t)/dist; + CVector posFront = (1.0f - f)*trackNodes[curTrackNodeFront].p + f*trackNodes[nextTrackNodeFront].p; + + // Now set matrix + SetPosition((posRear + posFront)/2.0f); + CVector fwd = posFront - posRear; + fwd.Normalise(); + CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f)); + right.Normalise(); + CVector up = CrossProduct(right, fwd); + GetRight() = right; + GetUp() = up; + GetForward() = fwd; + + // Set speed + m_vecMoveSpeed = fwd*engineTrackSpeed[m_nWagonGroup]/60.0f; + m_fSpeed = engineTrackSpeed[m_nWagonGroup]/60.0f; + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + + if(engineTrackSpeed[m_nWagonGroup] > 0.001f){ + SetStatus(STATUS_TRAIN_MOVING); + m_bTrainStopping = false; + m_bProcessDoor = true; + }else{ + SetStatus(STATUS_TRAIN_NOT_MOVING); + m_bTrainStopping = true; + } + + m_isFarAway = !((posFront - TheCamera.GetPosition()).Magnitude2D() < sq(250.0f)); + + if(m_fWagonPosition == 20.0f && m_fSpeed > 0.0001f) + if(Abs(TheCamera.GetPosition().z - GetPosition().z) < 15.0f) + CPad::GetPad(0)->StartShake_Train(GetPosition().x, GetPosition().y); + + if(m_bProcessDoor) + switch(m_nDoorState){ + case TRAIN_DOOR_CLOSED: + if(m_bTrainStopping){ + m_nDoorTimer = CTimer::GetTimeInMilliseconds() + 1000; + m_nDoorState = TRAIN_DOOR_OPENING; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_TRAIN_DOOR_CLOSE, 0.0f); + } + break; + + case TRAIN_DOOR_OPENING: + if(CTimer::GetTimeInMilliseconds() < m_nDoorTimer){ + OpenTrainDoor(1.0f - (m_nDoorTimer - CTimer::GetTimeInMilliseconds())/1000.0f); + }else{ + OpenTrainDoor(1.0f); + m_nDoorState = TRAIN_DOOR_OPEN; + } + break; + + case TRAIN_DOOR_OPEN: + if(!m_bTrainStopping){ + m_nDoorTimer = CTimer::GetTimeInMilliseconds() + 1000; + m_nDoorState = TRAIN_DOOR_CLOSING; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_TRAIN_DOOR_OPEN, 0.0f); + } + break; + + case TRAIN_DOOR_CLOSING: + if(CTimer::GetTimeInMilliseconds() < m_nDoorTimer){ + OpenTrainDoor((m_nDoorTimer - CTimer::GetTimeInMilliseconds())/1000.0f); + }else{ + OpenTrainDoor(0.0f); + m_nDoorState = TRAIN_DOOR_CLOSED; + m_bProcessDoor = false; + } + break; + } + + GetMatrix().UpdateRW(); + UpdateRwFrame(); + RemoveAndAdd(); + + bIsStuck = false; + bIsInSafePosition = true; + bWasPostponed = false; + + // request/remove model + if(m_isFarAway){ + if(m_rwObject) + DeleteRwObject(); + }else if(CStreaming::HasModelLoaded(MI_TRAIN)){ + if(m_rwObject == nil){ + m_modelIndex = -1; + SetModelIndex(MI_TRAIN); + } + }else{ + if(FindPlayerCoors().z * GetPosition().z >= 0.0f) + CStreaming::RequestModel(MI_TRAIN, STREAMFLAGS_DEPENDENCY); + } + + // Hit stuff + if(m_bIsFirstWagon && GetStatus()== STATUS_TRAIN_MOVING){ + CVector front = GetPosition() + GetForward()*GetColModel()->boundingBox.max.y + m_vecMoveSpeed*CTimer::GetTimeStep(); + + int x, xmin, xmax; + int y, ymin, ymax; + + xmin = CWorld::GetSectorIndexX(front.x - 3.0f); + if(xmin < 0) xmin = 0; + xmax = CWorld::GetSectorIndexX(front.x + 3.0f); + if(xmax > NUMSECTORS_X-1) xmax = NUMSECTORS_X-1; + ymin = CWorld::GetSectorIndexY(front.y - 3.0f); + if(ymin < 0) ymin = 0; + ymax = CWorld::GetSectorIndexY(front.y + 3.0f); + if(ymax > NUMSECTORS_Y-1) ymax = NUMSECTORS_X-1; + + CWorld::AdvanceCurrentScanCode(); + + for(y = ymin; y <= ymax; y++) + for(x = xmin; x <= xmax; x++){ + CSector *s = CWorld::GetSector(x, y); + TrainHitStuff(s->m_lists[ENTITYLIST_VEHICLES]); + TrainHitStuff(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]); + TrainHitStuff(s->m_lists[ENTITYLIST_PEDS]); + TrainHitStuff(s->m_lists[ENTITYLIST_PEDS_OVERLAP]); + } + } +} + +void +CTrain::PreRender(void) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + if(m_bIsFirstWagon){ + CVector lookVector = GetPosition() - TheCamera.GetPosition(); + float camDist = lookVector.Magnitude(); + if(camDist != 0.0f) + lookVector *= 1.0f/camDist; + else + lookVector = CVector(1.0f, 0.0f, 0.0f); + float behindness = DotProduct(lookVector, GetForward()); + + if(behindness < 0.0f){ + // In front of train + CVector lightPos = mi->m_positions[TRAIN_POS_LIGHT_FRONT]; + CVector lightR = GetMatrix() * lightPos; + CVector lightL = lightR; + lightL -= GetRight()*2.0f*lightPos.x; + + float intensity = -0.4f*behindness + 0.2f; + float size = 1.0f - behindness; + + if(behindness < -0.9f && camDist < 35.0f){ + // directly in front + CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255, + lightL, size, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255, + lightR, size, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + }else{ + CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255, + lightL, size, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255, + lightR, size, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + } + } + } + + if(m_bIsLastWagon){ + CVector lightPos = mi->m_positions[TRAIN_POS_LIGHT_REAR]; + CVector lightR = GetMatrix() * lightPos; + CVector lightL = lightR; + lightL -= GetRight()*2.0f*lightPos.x; + + CCoronas::RegisterCorona((uintptr)this + 12, 255, 0, 0, 255, + lightL, 1.0f, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CCoronas::RegisterCorona((uintptr)this + 13, 255, 0, 0, 255, + lightR, 1.0f, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + } +} + +void +CTrain::Render(void) +{ + CEntity::Render(); +} + +void +CTrain::TrainHitStuff(CPtrList &list) +{ + CPtrNode *node; + CPhysical *phys; + + for(node = list.first; node; node = node->next){ + phys = (CPhysical*)node->item; + if(phys != this && Abs(this->GetPosition().z - phys->GetPosition().z) < 1.5f) + phys->bHitByTrain = true; + } +} + +void +CTrain::AddPassenger(CPed *ped) +{ + int i = ped->m_vehDoor; + if((i == TRAIN_POS_LEFT_ENTRY || i == TRAIN_POS_MID_ENTRY || i == TRAIN_POS_RIGHT_ENTRY) && pPassengers[i] == nil){ + pPassengers[i] = ped; + m_nNumPassengers++; + }else{ + for(i = 0; i < 6; i++) + if(pPassengers[i] == nil){ + pPassengers[i] = ped; + m_nNumPassengers++; + return; + } + } +} + +void +CTrain::OpenTrainDoor(float ratio) +{ + if(m_rwObject == nil) + return; + + CMatrix doorL(RwFrameGetMatrix(m_aTrainNodes[TRAIN_DOOR_LHS])); + CMatrix doorR(RwFrameGetMatrix(m_aTrainNodes[TRAIN_DOOR_RHS])); + CVector posL = doorL.GetPosition(); + CVector posR = doorR.GetPosition(); + + bool isClosed = Doors[0].IsClosed(); // useless + + Doors[0].Open(ratio); + Doors[1].Open(ratio); + + if(isClosed) + Doors[0].RetTranslationWhenClosed(); // useless + + posL.y = Doors[0].m_fPosn; + posR.y = Doors[1].m_fPosn; + + doorL.SetTranslate(posL); + doorR.SetTranslate(posR); + + doorL.UpdateRW(); + doorR.UpdateRW(); +} + + + +void +CTrain::InitTrains(void) +{ + int i, j; + CTrain *train; + + // El train + if(pTrackNodes == nil) + ReadAndInterpretTrackFile("data\\paths\\tracks.dat", &pTrackNodes, &NumTrackNodes, 3, StationDist, + &TotalLengthOfTrack, &TotalDurationOfTrack, aLineBits, false); + // Subway + if(pTrackNodes_S == nil) + ReadAndInterpretTrackFile("data\\paths\\tracks2.dat", &pTrackNodes_S, &NumTrackNodes_S, 4, StationDist_S, + &TotalLengthOfTrack_S, &TotalDurationOfTrack_S, aLineBits_S, true); + + int trainId; + CStreaming::LoadAllRequestedModels(false); + if(CModelInfo::GetModelInfo("train", &trainId)) + CStreaming::RequestModel(trainId, 0); + CStreaming::LoadAllRequestedModels(false); + + // El-Train wagons + float wagonPositions[] = { 0.0f, 20.0f, 40.0f, 0.0f, 20.0f }; + int8 firstWagon[] = { 1, 0, 0, 1, 0 }; + int8 lastWagon[] = { 0, 0, 1, 0, 1 }; + int16 wagonGroup[] = { 0, 0, 0, 1, 1 }; + for(i = 0; i < 5; i++){ + train = new CTrain(MI_TRAIN, PERMANENT_VEHICLE); + train->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + train->SetStatus(STATUS_ABANDONED); + train->bIsLocked = true; + train->m_fWagonPosition = wagonPositions[i]; + train->m_bIsFirstWagon = firstWagon[i]; + train->m_bIsLastWagon = lastWagon[i]; + train->m_nWagonGroup = wagonGroup[i]; + train->m_nWagonId = i; + train->m_nCurTrackNode = 0; + CWorld::Add(train); + } + + // Subway wagons + float wagonPositions_S[] = { 0.0f, 20.0f, 0.0f, 20.0f, 0.0f, 20.0f, 0.0f, 20.0f }; + int8 firstWagon_S[] = { 1, 0, 1, 0, 1, 0, 1, 0 }; + int8 lastWagon_S[] = { 0, 1, 0, 1, 0, 1, 0, 1 }; + int16 wagonGroup_S[] = { 0, 0, 1, 1, 2, 2, 3, 3 }; + for(i = 0; i < 8; i++){ + train = new CTrain(MI_TRAIN, PERMANENT_VEHICLE); + train->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + train->SetStatus(STATUS_ABANDONED); + train->bIsLocked = true; + train->m_fWagonPosition = wagonPositions_S[i]; + train->m_bIsFirstWagon = firstWagon_S[i]; + train->m_bIsLastWagon = lastWagon_S[i]; + train->m_nWagonGroup = wagonGroup_S[i]; + train->m_nWagonId = i; + train->m_nCurTrackNode = 0; + train->m_nTrackId = TRACK_SUBWAY; + CWorld::Add(train); + } + + // This code is actually useless, it seems it was used for announcements once + for(i = 0; i < 3; i++){ + for(j = 0; pTrackNodes[j].t < StationDist[i]; j++); + aStationCoors[i] = pTrackNodes[j].p; + } + for(i = 0; i < 4; i++){ + for(j = 0; pTrackNodes_S[j].t < StationDist_S[i]; j++); + aStationCoors_S[i] = pTrackNodes_S[j].p; + } +} + +void +CTrain::Shutdown(void) +{ + delete[] pTrackNodes; + delete[] pTrackNodes_S; + pTrackNodes = nil; + pTrackNodes_S = nil; +} + +void +CTrain::ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int16 *numNodes, int32 numStations, float *stationDists, + float *totalLength, float *totalDuration, CTrainInterpolationLine *interpLines, bool rightRail) +{ + bool readingFile = false; + int bp, lp; + int i, tmp; + + if(*nodes == nil){ + readingFile = true; + + CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r"); + *gString = '\0'; + for(bp = 0, lp = 0; work_buff[bp] != '\n'; bp++, lp++) + gString[lp] = work_buff[bp]; + bp++; + // BUG: game doesn't terminate string and uses numNodes in sscanf directly + gString[lp] = '\0'; + sscanf(gString, "%d", &tmp); + *numNodes = tmp; + *nodes = new CTrainNode[*numNodes]; + + for(i = 0; i < *numNodes; i++){ + *gString = '\0'; + for(lp = 0; work_buff[bp] != '\n'; bp++, lp++) + gString[lp] = work_buff[bp]; + bp++; + // BUG: game doesn't terminate string + gString[lp] = '\0'; + sscanf(gString, "%f %f %f", &(*nodes)[i].p.x, &(*nodes)[i].p.y, &(*nodes)[i].p.z); + } + + // Coordinates are of one of the rails, but we want the center + float toCenter = rightRail ? 0.9f : -0.9f; + CVector fwd; + for(i = 0; i < *numNodes; i++){ + if(i == *numNodes-1) + fwd = (*nodes)[0].p - (*nodes)[i].p; + else + fwd = (*nodes)[i+1].p - (*nodes)[i].p; + CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f)); + right.Normalise(); + (*nodes)[i].p -= right*toCenter; + } + } + + // Calculate length of segments and track + float t = 0.0f; + for(i = 0; i < *numNodes; i++){ + (*nodes)[i].t = t; + t += ((*nodes)[(i+1) % (*numNodes)].p - (*nodes)[i].p).Magnitude2D(); + } + *totalLength = t; + + // Find correct z values + if(readingFile){ + CColPoint colpoint; + CEntity *entity; + for(i = 0; i < *numNodes; i++){ + CVector p = (*nodes)[i].p; + p.z += 1.0f; + if(CWorld::ProcessVerticalLine(p, p.z-0.5f, colpoint, entity, true, false, false, false, true, false, nil)) + (*nodes)[i].p.z = colpoint.point.z; + (*nodes)[i].p.z += 0.2f; + } + } + + // Create animation for stopping at stations + // TODO: figure out magic numbers? + float position = 0.0f; + float time = 0.0f; + int j = 0; + for(i = 0; i < numStations; i++){ + // Start at full speed + interpLines[j].type = 1; + interpLines[j].time = time; + interpLines[j].position = position; + interpLines[j].speed = 15.0f; + interpLines[j].acceleration = 0.0f; + j++; + // distance to next keyframe + float dist = (stationDists[i]-40.0f) - position; + time += dist/15.0f; + position += dist; + + // Now slow down 40 units before stop + interpLines[j].type = 2; + interpLines[j].time = time; + interpLines[j].position = position; + interpLines[j].speed = 15.0f; + interpLines[j].acceleration = -45.0f/32.0f; + j++; + time += 80.0f/15.0f; + position += 40.0f; // at station + + // stopping + interpLines[j].type = 0; + interpLines[j].time = time; + interpLines[j].position = position; + interpLines[j].speed = 0.0f; + interpLines[j].acceleration = 0.0f; + j++; + time += 25.0f; + + // accelerate again + interpLines[j].type = 2; + interpLines[j].time = time; + interpLines[j].position = position; + interpLines[j].speed = 0.0f; + interpLines[j].acceleration = 45.0f/32.0f; + j++; + time += 80.0f/15.0f; + position += 40.0f; // after station + } + // last keyframe + interpLines[j].type = 1; + interpLines[j].time = time; + interpLines[j].position = position; + interpLines[j].speed = 15.0f; + interpLines[j].acceleration = 0.0f; + j++; + *totalDuration = time + (*totalLength - position)/15.0f; + + // end + interpLines[j].time = *totalDuration; +} + +void +PlayAnnouncement(uint8 sound, uint8 station) +{ + // this was gone in a PC version but inlined on PS2 + cAudioScriptObject *obj = new cAudioScriptObject; + obj->AudioId = sound; + obj->Posn = CTrain::aStationCoors[station]; + obj->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(obj); +} + +void +ProcessTrainAnnouncements(void) +{ + for (int i = 0; i < ARRAY_SIZE(StationDist); i++) { + for (int j = 0; j < ARRAY_SIZE(EngineTrackPosition); j++) { + if (!bTrainArrivalAnnounced[i]) { + float preDist = StationDist[i] - 100.0f; + if (preDist < 0.0f) + preDist += TotalLengthOfTrack; + if (EngineTrackPosition[j] > preDist && EngineTrackPosition[j] < StationDist[i]) { + bTrainArrivalAnnounced[i] = true; + PlayAnnouncement(SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_1, i); + break; + } + } else { + float postDist = StationDist[i] + 10.0f; +#ifdef FIX_BUGS + if (postDist > TotalLengthOfTrack) + postDist -= TotalLengthOfTrack; +#else + if (postDist < 0.0f) // does this even make sense here? + postDist += TotalLengthOfTrack; +#endif + if (EngineTrackPosition[j] > StationDist[i] && EngineTrackPosition[j] < postDist) { + bTrainArrivalAnnounced[i] = false; + PlayAnnouncement(SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_2, i); + break; + } + } + } + } +} + +void +CTrain::UpdateTrains(void) +{ + int i, j; + uint32 time; + float t, deltaT; + + if(TheCamera.GetPosition().x > 200.0f && TheCamera.GetPosition().x < 1600.0f && + TheCamera.GetPosition().y > -1000.0f && TheCamera.GetPosition().y < 500.0f){ + // Update El-Train + + time = CTimer::GetTimeInMilliseconds(); + for(i = 0; i < 2; i++){ + t = TotalDurationOfTrack * (float)(time & 0x1FFFF)/0x20000; + // find current frame + for(j = 0; t > aLineBits[j+1].time; j++); + + deltaT = t - aLineBits[j].time; + switch(aLineBits[j].type){ + case 0: // standing still + EngineTrackPosition[i] = aLineBits[j].position; + EngineTrackSpeed[i] = 0.0f; + break; + case 1: // moving with constant speed + EngineTrackPosition[i] = aLineBits[j].position + aLineBits[j].speed*deltaT; + EngineTrackSpeed[i] = (TotalDurationOfTrack*1000.0f/0x20000) * aLineBits[j].speed; + break; + case 2: // accelerating/braking + EngineTrackPosition[i] = aLineBits[j].position + (aLineBits[j].speed + aLineBits[j].acceleration*deltaT)*deltaT; + EngineTrackSpeed[i] = (TotalDurationOfTrack*1000.0f/0x20000)*aLineBits[j].speed + 2.0f*aLineBits[j].acceleration*deltaT; + break; + } + + // time offset for each train + time += 0x20000/2; + } + + ProcessTrainAnnouncements(); + } + + // Update Subway + time = CTimer::GetTimeInMilliseconds(); + for(i = 0; i < 4; i++){ + t = TotalDurationOfTrack_S * (float)(time & 0x3FFFF)/0x40000; + // find current frame + for(j = 0; t > aLineBits_S[j+1].time; j++); + + deltaT = t - aLineBits_S[j].time; + switch(aLineBits_S[j].type){ + case 0: // standing still + EngineTrackPosition_S[i] = aLineBits_S[j].position; + EngineTrackSpeed_S[i] = 0.0f; + break; + case 1: // moving with constant speed + EngineTrackPosition_S[i] = aLineBits_S[j].position + aLineBits_S[j].speed*deltaT; + EngineTrackSpeed_S[i] = (TotalDurationOfTrack*1000.0f/0x40000) * aLineBits_S[j].speed; + break; + case 2: // accelerating/braking + EngineTrackPosition_S[i] = aLineBits_S[j].position + (aLineBits_S[j].speed + aLineBits_S[j].acceleration*deltaT)*deltaT; + EngineTrackSpeed_S[i] = (TotalDurationOfTrack*1000.0f/0x40000)*aLineBits_S[j].speed + 2.0f*aLineBits_S[j].acceleration*deltaT; + break; + } + + // time offset for each train + time += 0x40000/4; + } +} diff --git a/src/vehicles/Train.h b/src/vehicles/Train.h new file mode 100644 index 0000000..3446eeb --- /dev/null +++ b/src/vehicles/Train.h @@ -0,0 +1,86 @@ +#pragma once + +#include "Vehicle.h" +#include "Door.h" + +enum +{ + TRACK_ELTRAIN, + TRACK_SUBWAY +}; + +enum +{ + TRAIN_DOOR_CLOSED, + TRAIN_DOOR_OPENING, + TRAIN_DOOR_OPEN, + TRAIN_DOOR_CLOSING +}; + +enum eTrainNodes +{ + TRAIN_DOOR_LHS = 1, + TRAIN_DOOR_RHS, + NUM_TRAIN_NODES +}; + +struct CTrainNode +{ + CVector p; // position + float t; // xy-distance from start on track +}; + +struct CTrainInterpolationLine +{ + uint8 type; + float time; // when does this keyframe start + // initial values at start of frame + float position; + float speed; + float acceleration; +}; + +class CTrain : public CVehicle +{ +public: + // 0x288 + float m_fWagonPosition; + int16 m_nWagonId; + int16 m_isFarAway; // don't update so often? + int16 m_nCurTrackNode; + int16 m_nWagonGroup; + float m_fSpeed; + bool m_bProcessDoor; + bool m_bTrainStopping; + bool m_bIsFirstWagon; + bool m_bIsLastWagon; + uint8 m_nTrackId; // or m_bUsesSubwayTracks? + uint32 m_nDoorTimer; + int16 m_nDoorState; + CTrainDoor Doors[2]; + RwFrame *m_aTrainNodes[NUM_TRAIN_NODES]; + + // unused + static CVector aStationCoors[3]; + static CVector aStationCoors_S[4]; + + CTrain(int32 id, uint8 CreatedBy); + + // from CEntity + void SetModelIndex(uint32 id); + void ProcessControl(void); + void PreRender(void); + void Render(void); + + void AddPassenger(CPed *ped); + void OpenTrainDoor(float ratio); + void TrainHitStuff(CPtrList &list); + + static void InitTrains(void); + static void Shutdown(void); + static void ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int16 *numNodes, int32 numStations, float *stationDists, + float *totalLength, float *totalDuration, CTrainInterpolationLine *interpLines, bool rightRail); + static void UpdateTrains(void); +}; + +VALIDATE_SIZE(CTrain, 0x2E4); diff --git a/src/vehicles/Transmission.cpp b/src/vehicles/Transmission.cpp new file mode 100644 index 0000000..109847a --- /dev/null +++ b/src/vehicles/Transmission.cpp @@ -0,0 +1,138 @@ +#include "common.h" + +#include "Timer.h" +#include "HandlingMgr.h" +#include "Transmission.h" + +void +cTransmission::InitGearRatios(void) +{ + static tGear *pGearRatio0 = nil; + static tGear *pGearRatio1 = nil; + int i; + float velocityDiff; + + memset(Gears, 0, sizeof(Gears)); + + for(i = 1; i <= nNumberOfGears; i++){ + pGearRatio0 = &Gears[i-1]; + pGearRatio1 = &Gears[i]; + + pGearRatio1->fMaxVelocity = (float)i / nNumberOfGears * fMaxVelocity; + + velocityDiff = pGearRatio1->fMaxVelocity - pGearRatio0->fMaxVelocity; + + if(i >= nNumberOfGears){ + pGearRatio1->fShiftUpVelocity = fMaxVelocity; + }else{ + Gears[i+1].fShiftDownVelocity = velocityDiff*0.42f + pGearRatio0->fMaxVelocity; + pGearRatio1->fShiftUpVelocity = velocityDiff*0.6667f + pGearRatio0->fMaxVelocity; + } + } + + // Reverse gear + Gears[0].fMaxVelocity = fMaxReverseVelocity; + Gears[0].fShiftUpVelocity = -0.01f; + Gears[0].fShiftDownVelocity = fMaxReverseVelocity; + + Gears[1].fShiftDownVelocity = -0.01f; +} + +void +cTransmission::CalculateGearForSimpleCar(float speed, uint8 &gear) +{ + static tGear *pGearRatio; + + pGearRatio = &Gears[gear]; + fCurVelocity = speed; + if(speed > pGearRatio->fShiftUpVelocity) + gear++; + else if(speed < pGearRatio->fShiftDownVelocity){ + if(gear - 1 < 0) + gear = 0; + else + gear--; + } +} + +float +cTransmission::CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, float &time, const float &velocity, bool cheat) +{ + static float fAcceleration = 0.0f; + static float fVelocity; + static float fCheat; + static tGear *pGearRatio; + + fVelocity = velocity; + if(fVelocity < fMaxReverseVelocity){ + fVelocity = fMaxReverseVelocity; + return 0.0f; + } + if(fVelocity > fMaxVelocity){ + fVelocity = fMaxVelocity; + return 0.0f; + } + fCurVelocity = fVelocity; + + assert(gear <= nNumberOfGears); + + pGearRatio = &Gears[gear]; + if(fVelocity > pGearRatio->fShiftUpVelocity){ + if(gear != 0 || gasPedal > 0.0f){ + gear++; + time = 0.0f; + return CalculateDriveAcceleration(gasPedal, gear, time, fVelocity, false); + } + }else if(fVelocity < pGearRatio->fShiftDownVelocity && gear != 0){ + if(gear != 1 || gasPedal < 0.0f){ + gear--; + time = 0.0f; + return CalculateDriveAcceleration(gasPedal, gear, time, fVelocity, false); + } + } + + if(time > 0.0f){ + // changing gears currently, can't accelerate + fAcceleration = 0.0f; + time -= CTimer::GetTimeStepInSeconds(); + }else{ + float speedMul, accelMul; + + if(gear < 1){ + // going reverse + accelMul = (Flags & HANDLING_2G_BOOST) ? 2.0f : 1.0f; + speedMul = -1.0f; + }else if(nNumberOfGears == 1){ + accelMul = 1.0f; + speedMul = 1.0f; + }else{ + // BUG or not? this is 1.0 normally but 0.0 in the highest gear + float f = 1.0f - (gear-1)/(nNumberOfGears-1); + speedMul = 3.0f*sq(f) + 1.0f; + // This is pretty ugly, could be written more clearly + if(Flags & HANDLING_2G_BOOST){ + if(gear == 1) + accelMul = (Flags & HANDLING_1G_BOOST) ? 3.0f : 2.0f; + else if(gear == 2) + accelMul = 1.3f; + else + accelMul = 1.0f; + }else if(Flags & HANDLING_1G_BOOST && gear == 1){ + accelMul = 3.0f; + }else + accelMul = 1.0f; + } + + if(cheat) + fCheat = 1.2f; + else + fCheat = 1.0f; + float targetVelocity = Gears[gear].fMaxVelocity*speedMul*fCheat; + float accel = (targetVelocity - fVelocity) * (fEngineAcceleration*accelMul) / Abs(targetVelocity); + if(Abs(fVelocity) < Abs(Gears[gear].fMaxVelocity*fCheat)) + fAcceleration = gasPedal * accel * CTimer::GetTimeStep(); + else + fAcceleration = 0.0f; + } + return fAcceleration; +} diff --git a/src/vehicles/Transmission.h b/src/vehicles/Transmission.h new file mode 100644 index 0000000..a3d1551 --- /dev/null +++ b/src/vehicles/Transmission.h @@ -0,0 +1,28 @@ +#pragma once + +struct tGear +{ + float fMaxVelocity; + float fShiftUpVelocity; + float fShiftDownVelocity; +}; + +class cTransmission +{ +public: + // Gear 0 is reverse, 1-5 are forward + tGear Gears[6]; + char nDriveType; + char nEngineType; + int8 nNumberOfGears; + uint8 Flags; + float fEngineAcceleration; + float fMaxVelocity; + float fMaxCruiseVelocity; + float fMaxReverseVelocity; + float fCurVelocity; + + void InitGearRatios(void); + void CalculateGearForSimpleCar(float speed, uint8 &gear); + float CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, float &time, const float &velocity, bool cheat); +}; diff --git a/src/vehicles/Vehicle.cpp b/src/vehicles/Vehicle.cpp new file mode 100644 index 0000000..451f3a3 --- /dev/null +++ b/src/vehicles/Vehicle.cpp @@ -0,0 +1,1385 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "Timer.h" +#include "Pad.h" +#include "Vehicle.h" +#include "Pools.h" +#include "HandlingMgr.h" +#include "CarCtrl.h" +#include "Population.h" +#include "ModelIndices.h" +#include "World.h" +#include "Lights.h" +#include "PointLights.h" +#include "Renderer.h" +#include "DMAudio.h" +#include "Radar.h" +#include "Fire.h" +#include "Darkel.h" +#include "SaveBuf.h" + +bool CVehicle::bWheelsOnlyCheat; +bool CVehicle::bAllDodosCheat; +bool CVehicle::bCheat3; +bool CVehicle::bCheat4; +bool CVehicle::bCheat5; +#ifdef ALT_DODO_CHEAT +bool CVehicle::bAltDodoCheat; +#endif +bool CVehicle::m_bDisableMouseSteering = true; + +void *CVehicle::operator new(size_t sz) throw() { return CPools::GetVehiclePool()->New(); } +void *CVehicle::operator new(size_t sz, int handle) throw() { return CPools::GetVehiclePool()->New(handle); } +void CVehicle::operator delete(void *p, size_t sz) throw() { CPools::GetVehiclePool()->Delete((CVehicle*)p); } +void CVehicle::operator delete(void *p, int handle) throw() { CPools::GetVehiclePool()->Delete((CVehicle*)p); } + +#ifdef FIX_BUGS +// I think they meant that +#define DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE (MYRAND_MAX * 35 / 100) +#define DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE (MYRAND_MAX * 70 / 100) +#else +#define DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE (35000) +#define DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE (70000) +#endif +#define DAMAGE_HEALTH_TO_FLEE_ALWAYS (200.0f) +#define DAMAGE_HEALTH_TO_CATCH_FIRE (250.0f) + + +CVehicle::CVehicle(uint8 CreatedBy) +{ + int i; + + m_nCurrentGear = 1; + m_fChangeGearTime = 0.0f; + m_fSteerInput = 0.0f; + m_type = ENTITY_TYPE_VEHICLE; + VehicleCreatedBy = CreatedBy; + bIsLocked = false; + bIsLawEnforcer = false; + bIsAmbulanceOnDuty = false; + bIsFireTruckOnDuty = false; +#ifdef FIX_BUGS + bIsHandbrakeOn = false; +#endif + CCarCtrl::UpdateCarCount(this, false); + m_fHealth = 1000.0f; + bEngineOn = true; + bFreebies = true; + pDriver = nil; + m_nNumPassengers = 0; + m_nNumGettingIn = 0; + m_nGettingInFlags = 0; + m_nGettingOutFlags = 0; + m_nNumMaxPassengers = ARRAY_SIZE(pPassengers); + for(i = 0; i < m_nNumMaxPassengers; i++) + pPassengers[i] = nil; + m_nBombTimer = 0; + m_pBlowUpEntity = nil; + m_nPacManPickupsCarried = 0; + bComedyControls = false; + bCraneMessageDone = false; + bExtendedRange = false; + bTakeLessDamage = false; + bIsDamaged = false; + bFadeOut = false; + bIsBeingCarJacked = false; + m_nTimeOfDeath = 0; + m_pCarFire = nil; + bHasBeenOwnedByPlayer = false; + bCreateRoadBlockPeds = false; + bCanBeDamaged = true; + bUsingSpecialColModel = false; + bOccupantsHaveBeenGenerated = false; + bGunSwitchedOff = false; + m_nGunFiringTime = 0; + m_nTimeBlocked = 0; + bLightsOn = false; + bVehicleColProcessed = false; + m_numPedsUseItAsCover = 0; + bIsCarParkVehicle = false; + bHasAlreadyBeenRecorded = false; + m_bSirenOrAlarm = false; + m_nCarHornTimer = 0; + m_nCarHornPattern = 0; + m_nAlarmState = 0; + m_nDoorLock = CARLOCK_UNLOCKED; + m_nLastWeaponDamage = -1; + m_fMapObjectHeightAhead = m_fMapObjectHeightBehind = 0.0f; + m_audioEntityId = DMAudio.CreateEntity(AUDIOTYPE_PHYSICAL, this); + if(m_audioEntityId >= 0) + DMAudio.SetEntityStatus(m_audioEntityId, TRUE); + m_nRadioStation = CGeneral::GetRandomNumber() % USERTRACK; + m_pCurGroundEntity = nil; + m_bRainAudioCounter = 0; + m_bRainSamplesCounter = 0; + m_comedyControlState = 0; + m_aCollPolys[0].valid = false; + m_aCollPolys[1].valid = false; + AutoPilot.m_nCarMission = MISSION_NONE; + AutoPilot.m_nTempAction = TEMPACT_NONE; + AutoPilot.m_nTimeToStartMission = CTimer::GetTimeInMilliseconds(); + AutoPilot.m_bStayInCurrentLevel = false; + AutoPilot.m_bIgnorePathfinding = false; +} + +CVehicle::~CVehicle() +{ + m_nAlarmState = 0; + if (m_audioEntityId >= 0){ + DMAudio.DestroyEntity(m_audioEntityId); + m_audioEntityId = -5; + } + CRadar::ClearBlipForEntity(BLIP_CAR, CPools::GetVehiclePool()->GetIndex(this)); + if (pDriver) + pDriver->FlagToDestroyWhenNextProcessed(); + for (int i = 0; i < m_nNumMaxPassengers; i++){ + if (pPassengers[i]) + pPassengers[i]->FlagToDestroyWhenNextProcessed(); + } + if (m_pCarFire) + m_pCarFire->Extinguish(); + CCarCtrl::UpdateCarCount(this, true); + if (bIsAmbulanceOnDuty){ + CCarCtrl::NumAmbulancesOnDuty--; + bIsAmbulanceOnDuty = false; + } + if (bIsFireTruckOnDuty){ + CCarCtrl::NumFiretrucksOnDuty--; + bIsFireTruckOnDuty = false; + } +} + +void +CVehicle::SetModelIndex(uint32 id) +{ + CEntity::SetModelIndex(id); + m_aExtras[0] = CVehicleModelInfo::ms_compsUsed[0]; + m_aExtras[1] = CVehicleModelInfo::ms_compsUsed[1]; + m_nNumMaxPassengers = CVehicleModelInfo::GetMaximumNumberOfPassengersFromNumberOfDoors(id); +} + +bool +CVehicle::SetupLighting(void) +{ + ActivateDirectional(); + SetAmbientColoursForPedsCarsAndObjects(); + + if(bRenderScorched){ + WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f); + }else{ + CVector coors = GetPosition(); + float lighting = CPointLights::GenerateLightsAffectingObject(&coors); + if(!bHasBlip && lighting != 1.0f){ + SetAmbientAndDirectionalColours(lighting); + return true; + } + } + + return false; +} + +void +CVehicle::RemoveLighting(bool reset) +{ + CRenderer::RemoveVehiclePedLights(this, reset); +} + +float +CVehicle::GetHeightAboveRoad(void) +{ + return -1.0f * GetColModel()->boundingBox.min.z; +} + +const float fRCPropFallOff = 3.0f; +const float fRCAeroThrust = 0.003f; +const float fRCSideSlipMult = 0.1f; +const float fRCRudderMult = 0.2f; +const float fRCYawMult = -0.01f; +const float fRCRollMult = 0.02f; +const float fRCRollStabilise = -0.08f; +const float fRCPitchMult = 0.005f; +const float fRCTailMult = 0.3f; +const float fRCFormLiftMult = 0.02f; +const float fRCAttackLiftMult = 0.25f; +const CVector vecRCAeroResistance(0.998f, 0.998f, 0.9f); + +const float fSeaPropFallOff = 2.3f; +const float fSeaThrust = 0.002f; +const float fSeaSideSlipMult = 0.1f; +const float fSeaRudderMult = 0.01f; +const float fSeaYawMult = -0.0003f; +const float fSeaRollMult = 0.0015f; +const float fSeaRollStabilise = -0.01f; +const float fSeaPitchMult = 0.0002f; +const float fSeaTailMult = 0.01f; +const float fSeaFormLiftMult = 0.012f; +const float fSeaAttackLiftMult = 0.1f; +const CVector vecSeaAeroResistance(0.995f, 0.995f, 0.85f); + +const float fSpeedResistanceY = 500.0f; +const float fSpeedResistanceZ = 500.0f; + +const CVector vecHeliMoveRes(0.995f, 0.995f, 0.99f); +const CVector vecRCHeliMoveRes(0.99f, 0.99f, 0.99f); +const float fThrustVar = 0.3f; +const float fRotorFallOff = 0.75f; +const float fStabiliseVar = 0.015f; +const float fPitchBrake = 10.0f; +const float fPitchVar = 0.006f; +const float fRollVar = 0.006f; +const float fYawVar = -0.001f; +const CVector vecHeliResistance(0.81f, 0.85f, 0.99f); +const CVector vecRCHeliResistance(0.92f, 0.92f, 0.998f); +const float fSpinSpeedRes = 20.0f; + +void +CVehicle::FlyingControl(eFlightModel flightModel) +{ + switch(flightModel){ + case FLIGHT_MODEL_DODO: + { + // This seems pretty magic + + // Move Left/Right + float moveSpeed = m_vecMoveSpeed.Magnitude(); + float sideSpeed = DotProduct(m_vecMoveSpeed, GetRight()); + float sideImpulse = -1.0f * sideSpeed / moveSpeed; + float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward()); + float magic = m_vecMoveSpeed.MagnitudeSqr() * sq(fwdSpeed); + float turnImpulse = (sideImpulse*0.003f + m_fSteerAngle*0.001f) * + magic*m_fTurnMass*CTimer::GetTimeStep(); + ApplyTurnForce(turnImpulse*GetRight(), -4.0f*GetForward()); + + float impulse = sideImpulse*0.2f * + magic*m_fMass*CTimer::GetTimeStep(); + ApplyMoveForce(impulse*GetRight()); + ApplyTurnForce(impulse*GetRight(), 2.0f*GetUp()); + + + // Move Up/Down + moveSpeed = m_vecMoveSpeed.Magnitude(); + float upSpeed = DotProduct(m_vecMoveSpeed, GetUp()); + float upImpulse = -1.0f * upSpeed / moveSpeed; + turnImpulse = (upImpulse*0.002f + -CPad::GetPad(0)->GetSteeringUpDown()/128.0f*0.001f) * + magic*m_fTurnMass*CTimer::GetTimeStep(); + ApplyTurnForce(turnImpulse*GetUp(), -4.0f*GetForward()); + + impulse = (upImpulse*3.5f + 0.5f)*0.05f * + magic*m_fMass*CTimer::GetTimeStep(); + if(GRAVITY*m_fMass*CTimer::GetTimeStep() < impulse && + GetPosition().z > 100.0f) + impulse = 0.9f*GRAVITY*m_fMass*CTimer::GetTimeStep(); + CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + ApplyMoveForce(impulse*GetUp()); + ApplyTurnForce(impulse*GetUp(), 2.0f*GetUp() + com); + + + m_vecTurnSpeed.y *= Pow(0.9f, CTimer::GetTimeStep()); + moveSpeed = m_vecMoveSpeed.MagnitudeSqr(); + if(moveSpeed > SQR(1.5f)) + m_vecMoveSpeed *= 1.5f/Sqrt(moveSpeed); + + float turnSpeed = m_vecTurnSpeed.MagnitudeSqr(); + if(turnSpeed > SQR(0.2f)) + m_vecTurnSpeed *= 0.2f/Sqrt(turnSpeed); + break; + } + + case FLIGHT_MODEL_RCPLANE: + case FLIGHT_MODEL_SEAPLANE: + { + // thrust + float fThrust = flightModel == FLIGHT_MODEL_RCPLANE ? fRCAeroThrust : fSeaThrust; + float fThrustFallOff = flightModel == FLIGHT_MODEL_RCPLANE ? fRCPropFallOff : fSeaPropFallOff; + + float fForwSpeed = DotProduct(GetMoveSpeed(), GetForward()); + CVector vecTail = GetColModel()->boundingBox.min.y * GetForward(); + float fPedalState = (CPad::GetPad(0)->GetAccelerate() - CPad::GetPad(0)->GetBrake()) / 255.0f; + if (fForwSpeed > 0.1f || (flightModel == FLIGHT_MODEL_RCPLANE && fForwSpeed > 0.02f)) + fPedalState += 1.0f; + else if (fForwSpeed > 0.0f && fPedalState < 0.0f) + fPedalState = 0.0f; + float fThrustAccel = (fPedalState - fThrustFallOff * fForwSpeed) * fThrust; + + ApplyMoveForce(fThrustAccel * GetForward() * m_fMass * CTimer::GetTimeStep()); + + // left/right + float fSideSpeed = -DotProduct(GetMoveSpeed(), GetRight()); + float fSteerLR = CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f; + float fSideSlipAccel; + if (flightModel == FLIGHT_MODEL_RCPLANE) + fSideSlipAccel = Abs(fSideSpeed) * fSideSpeed * fRCSideSlipMult; + else + fSideSlipAccel = Abs(fSideSpeed) * fSideSpeed * fSeaSideSlipMult; + ApplyMoveForce(m_fMass * GetRight() * fSideSlipAccel * CTimer::GetTimeStep()); + + float fYaw = -DotProduct(GetSpeed(vecTail), GetRight()); + float fYawAccel; + if (flightModel == FLIGHT_MODEL_RCPLANE) + fYawAccel = fRCRudderMult * fYaw * Abs(fYaw) + fRCYawMult * fSteerLR * fForwSpeed; + else + fYawAccel = fSeaRudderMult * fYaw * Abs(fYaw) + fSeaYawMult * fSteerLR * fForwSpeed; + ApplyTurnForce(fYawAccel * GetRight() * m_fTurnMass * CTimer::GetTimeStep(), vecTail); + + float fRollAccel; + if (flightModel == FLIGHT_MODEL_RCPLANE) { + float fDirectionMultiplier = CPad::GetPad(0)->GetLookRight(); + if (CPad::GetPad(0)->GetLookLeft()) + fDirectionMultiplier = -1; + fRollAccel = (0.5f * fDirectionMultiplier + fSteerLR) * fRCRollMult; + } + else + fRollAccel = fSteerLR * fSeaRollMult; + ApplyTurnForce(GetRight() * fRollAccel * fForwSpeed * m_fTurnMass * CTimer::GetTimeStep(), GetUp()); + + CVector vecFRight = CrossProduct(GetForward(), CVector(0.0f, 0.0f, 1.0f)); + CVector vecStabilise = (GetUp().z > 0.0f) ? vecFRight : -vecFRight; + float fStabiliseDirection = (GetRight().z > 0.0f) ? -1.0f : 1.0f; + float fStabiliseSpeed; + if (flightModel == FLIGHT_MODEL_RCPLANE) + fStabiliseSpeed = fRCRollStabilise * fStabiliseDirection * (1.0f - DotProduct(GetRight(), vecStabilise)) * (1.0f - Abs(GetForward().z)); + else + fStabiliseSpeed = fSeaRollStabilise * fStabiliseDirection * (1.0f - DotProduct(GetRight(), vecStabilise)) * (1.0f - Abs(GetForward().z)); + ApplyTurnForce(fStabiliseSpeed * m_fTurnMass * GetRight(), GetUp()); // no CTimer::GetTimeStep(), is it right? VC doesn't have it too + + // up/down + float fTail = -DotProduct(GetSpeed(vecTail), GetUp()); + float fSteerUD = -CPad::GetPad(0)->GetSteeringUpDown() / 128.0f; + float fPitchAccel; + if (flightModel == FLIGHT_MODEL_RCPLANE) + fPitchAccel = fRCTailMult * fTail * Abs(fTail) + fRCPitchMult * fSteerUD * fForwSpeed; + else + fPitchAccel = fSeaTailMult * fTail * Abs(fTail) + fSeaPitchMult * fSteerUD * fForwSpeed; + ApplyTurnForce(fPitchAccel * m_fTurnMass * GetUp() * CTimer::GetTimeStep(), vecTail); + + float fLift = DotProduct(GetMoveSpeed(), GetUp()) / Max(0.01f, GetMoveSpeed().Magnitude()); //accel*angle + float fLiftAccel; + if (flightModel == FLIGHT_MODEL_RCPLANE) + fLiftAccel = (fRCFormLiftMult - fRCAttackLiftMult * fLift) * SQR(fForwSpeed); + else + fLiftAccel = (fSeaFormLiftMult - fSeaAttackLiftMult * fLift) * SQR(fForwSpeed); + float fLiftImpulse = fLiftAccel * m_fMass * CTimer::GetTimeStep(); + if (GRAVITY * CTimer::GetTimeStep() * m_fMass < fLiftImpulse) { + if (flightModel == FLIGHT_MODEL_RCPLANE && GetPosition().z > 50.0f) + fLiftImpulse = CTimer::GetTimeStep() * 0.9f*GRAVITY * m_fMass; + else if (flightModel == FLIGHT_MODEL_SEAPLANE && GetPosition().z > 80.0f) + fLiftImpulse = CTimer::GetTimeStep() * 0.9f*GRAVITY * m_fMass; + } + ApplyMoveForce(fLiftImpulse * GetUp()); + + CVector vecResistance; + if (flightModel == FLIGHT_MODEL_RCPLANE) + vecResistance = vecRCAeroResistance; + else + vecResistance = vecSeaAeroResistance; + float rX = Pow(vecResistance.x, CTimer::GetTimeStep()); + float rY = Pow(vecResistance.y, CTimer::GetTimeStep()); + float rZ = Pow(vecResistance.z, CTimer::GetTimeStep()); + CVector vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); + vecTurnSpeed.x *= rX; + float fResistance = vecTurnSpeed.y * (1.0f / (fSpeedResistanceY * SQR(vecTurnSpeed.y) + 1.0f)) * rY - vecTurnSpeed.y; + vecTurnSpeed.z *= rZ; + m_vecTurnSpeed = Multiply3x3(GetMatrix(), vecTurnSpeed); + ApplyTurnForce(-GetUp() * fResistance * m_fTurnMass, GetRight() + Multiply3x3(GetMatrix(), m_vecCentreOfMass)); + break; + } + case FLIGHT_MODEL_HELI: + { + CVector vecMoveResistance; + if (GetModelIndex() == MI_MIAMI_SPARROW) + vecMoveResistance = vecHeliMoveRes; + else + vecMoveResistance = vecRCHeliMoveRes; + float rmX = Pow(vecMoveResistance.x, CTimer::GetTimeStep()); + float rmY = Pow(vecMoveResistance.y, CTimer::GetTimeStep()); + float rmZ = Pow(vecMoveResistance.z, CTimer::GetTimeStep()); + m_vecMoveSpeed.x *= rmX; + m_vecMoveSpeed.y *= rmY; + m_vecMoveSpeed.z *= rmZ; + if (GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE) + return; + float fThrust; + if (bCheat5) + fThrust = CPad::GetPad(0)->GetSteeringUpDown() * fThrustVar / 128.0f + 0.95f; + else + fThrust = fThrustVar * (CPad::GetPad(0)->GetAccelerate() - 2 * CPad::GetPad(0)->GetBrake()) / 255.0f + 0.95f; + fThrust -= fRotorFallOff * DotProduct(m_vecMoveSpeed, GetUp()); +#if GTA_VERSION >= GTA3_PC_11 + if (fThrust > 0.9f && GetPosition().z > 80.0f) + fThrust = 0.9f; +#endif + ApplyMoveForce(GRAVITY * GetUp() * fThrust * m_fMass * CTimer::GetTimeStep()); + + if (GetUp().z > 0.0f) + ApplyTurnForce(-CVector(GetUp().x, GetUp().y, 0.0f) * fStabiliseVar * m_fTurnMass * CTimer::GetTimeStep(), GetUp()); + + float fRoll, fPitch, fYaw; + if (bCheat5) { + fPitch = CPad::GetPad(0)->GetCarGunUpDown() / 128.0f; + fRoll = -CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f; + fYaw = CPad::GetPad(0)->GetCarGunLeftRight() / 128.0f; + } + else { + fPitch = CPad::GetPad(0)->GetSteeringUpDown() / 128.0f; + fRoll = CPad::GetPad(0)->GetLookLeft(); + if (CPad::GetPad(0)->GetLookRight()) + fRoll = -1.0f; + fYaw = CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f; + } + if (CPad::GetPad(0)->GetHorn()) { + fYaw = 0.0f; + fPitch = Clamp(10.0f * DotProduct(m_vecMoveSpeed, GetForward()), -200.0f, 1.3f); + fRoll = Clamp(10.0f * DotProduct(m_vecMoveSpeed, GetRight()), -200.0f, 1.3f); + } + ApplyTurnForce(fPitch * GetUp() * fPitchVar * m_fTurnMass * CTimer::GetTimeStep(), GetForward()); + ApplyTurnForce(fRoll * GetUp() * fRollVar * m_fTurnMass * CTimer::GetTimeStep(), GetRight()); + ApplyTurnForce(fYaw * GetForward() * fYawVar * m_fTurnMass * CTimer::GetTimeStep(), GetRight()); + + CVector vecResistance; + if (GetModelIndex() == MI_MIAMI_SPARROW) + vecResistance = vecHeliResistance; + else + vecResistance = vecRCHeliResistance; + float rX = Pow(vecResistance.x, CTimer::GetTimeStep()); + float rY = Pow(vecResistance.y, CTimer::GetTimeStep()); + float rZ = Pow(vecResistance.z, CTimer::GetTimeStep()); + CVector vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); + float fResistanceMultiplier = Pow(1.0f / (fSpinSpeedRes * SQR(vecTurnSpeed.z) + 1.0f) * rZ, CTimer::GetTimeStep()); + float fResistance = vecTurnSpeed.z * fResistanceMultiplier - vecTurnSpeed.z; + vecTurnSpeed.x *= rX; + vecTurnSpeed.y *= rY; + vecTurnSpeed.z *= fResistanceMultiplier; + m_vecTurnSpeed = Multiply3x3(GetMatrix(), vecTurnSpeed); + ApplyTurnForce(-GetRight() * fResistance * m_fTurnMass, GetForward() + Multiply3x3(GetMatrix(), m_vecCentreOfMass)); + break; + } + } +} + +float fBurstSpeedMax = 0.3f; +float fBurstTyreMod = 0.1f; + +void +CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, + int32 wheelsOnGround, float thrust, float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, uint16 wheelStatus) +{ + // BUG: using statics here is probably a bad idea + static bool bAlreadySkidding = false; // this is never reset + static bool bBraking; + static bool bDriving; + +#ifdef FIX_SIGNIFICANT_BUGS + bAlreadySkidding = false; +#endif + + // how much force we want to apply in these axes + float fwd = 0.0f; + float right = 0.0f; + + bBraking = brake != 0.0f; + if(bBraking) + thrust = 0.0f; + bDriving = thrust != 0.0f; + + float contactSpeedFwd = DotProduct(wheelContactSpeed, wheelFwd); + float contactSpeedRight = DotProduct(wheelContactSpeed, wheelRight); + + if(*wheelState != WHEEL_STATE_NORMAL) + bAlreadySkidding = true; + *wheelState = WHEEL_STATE_NORMAL; + + adhesion *= CTimer::GetTimeStep(); + if(bAlreadySkidding) + adhesion *= pHandling->fTractionLoss; + + // moving sideways + if(contactSpeedRight != 0.0f){ + // exert opposing force + right = -contactSpeedRight/wheelsOnGround; + // BUG? + // contactSpeedRight is independent of framerate but right has timestep as a factor + // so we probably have to fix this + // fixing this causes jittery cars at 15fps, and causes the car to move backwards slowly at 18fps + // at 19fps, the effects are gone ... + //right *= CTimer::GetTimeStepFix(); + + if(wheelStatus == WHEEL_STATUS_BURST){ + float fwdspeed = Min(contactSpeedFwd, fBurstSpeedMax); + right += fwdspeed * CGeneral::GetRandomNumberInRange(-fBurstTyreMod, fBurstTyreMod); + } + } + + if(bDriving){ + fwd = thrust; + + // limit sideways force (why?) + if(right > 0.0f){ + if(right > adhesion) + right = adhesion; + }else{ + if(right < -adhesion) + right = -adhesion; + } + }else if(contactSpeedFwd != 0.0f){ + fwd = -contactSpeedFwd/wheelsOnGround; +#ifdef FIX_BUGS + // contactSpeedFwd is independent of framerate but fwd has timestep as a factor + // so we probably have to fix this + // better get rid of it here too + //fwd *= CTimer::GetTimeStepFix(); +#endif + + if(!bBraking){ + if(m_fGasPedal < 0.01f){ + if(GetModelIndex() == MI_RCBANDIT) + brake = 0.2f * mod_HandlingManager.fWheelFriction / pHandling->fMass; + else + brake = mod_HandlingManager.fWheelFriction / pHandling->fMass; +#ifdef FIX_BUGS + brake *= CTimer::GetTimeStepFix(); +#endif + } + } + + if(brake > adhesion){ + if(Abs(contactSpeedFwd) > 0.005f) + *wheelState = WHEEL_STATE_FIXED; + }else { + if(fwd > 0.0f){ + if(fwd > brake) + fwd = brake; + }else{ + if(fwd < -brake) + fwd = -brake; + } + } + } + + float speedSq = sq(right) + sq(fwd); + if(sq(adhesion) < speedSq){ + if(*wheelState != WHEEL_STATE_FIXED){ + if(bDriving && contactSpeedFwd < 0.2f) + *wheelState = WHEEL_STATE_SPINNING; + else + *wheelState = WHEEL_STATE_SKIDDING; + } + + float l = Sqrt(speedSq); + float tractionLoss = bAlreadySkidding ? 1.0f : pHandling->fTractionLoss; + right *= adhesion * tractionLoss / l; + fwd *= adhesion * tractionLoss / l; + } + + if(fwd != 0.0f || right != 0.0f){ + CVector direction = fwd*wheelFwd + right*wheelRight; + float speed = direction.Magnitude(); + direction.Normalise(); + + float impulse = speed*m_fMass; + float turnImpulse = speed*GetMass(wheelContactPoint, direction); + + ApplyMoveForce(impulse * direction); + ApplyTurnForce(turnImpulse * direction, wheelContactPoint); + } +} + +void +CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, int32 wheelsOnGround, float thrust, + float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, eBikeWheelSpecial special, uint16 wheelStatus) +{ + // TODO: mobile code +} + +float +CVehicle::ProcessWheelRotation(tWheelState state, const CVector &fwd, const CVector &speed, float radius) +{ + float angularVelocity; + switch(state){ + case WHEEL_STATE_SPINNING: + angularVelocity = -1.1f; // constant speed forward + break; + case WHEEL_STATE_FIXED: + angularVelocity = 0.0f; // not moving + break; + default: + angularVelocity = -DotProduct(fwd, speed) / radius; // forward speed + break; + } + return angularVelocity * CTimer::GetTimeStep(); +} + +void +CVehicle::InflictDamage(CEntity* damagedBy, eWeaponType weaponType, float damage) +{ + if (!bCanBeDamaged) + return; + if (bOnlyDamagedByPlayer && (damagedBy != FindPlayerPed() && damagedBy != FindPlayerVehicle())) + return; + bool bFrightensDriver = false; + switch (weaponType) { + case WEAPONTYPE_UNARMED: + case WEAPONTYPE_BASEBALLBAT: + if (bMeleeProof) + return; + break; + case WEAPONTYPE_COLT45: + case WEAPONTYPE_UZI: + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_AK47: + case WEAPONTYPE_M16: + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_TOTAL_INVENTORY_WEAPONS: + case WEAPONTYPE_UZI_DRIVEBY: + if (bBulletProof) + return; + bFrightensDriver = true; + break; + case WEAPONTYPE_ROCKETLAUNCHER: + case WEAPONTYPE_MOLOTOV: + case WEAPONTYPE_GRENADE: + case WEAPONTYPE_EXPLOSION: + if (bExplosionProof) + return; + bFrightensDriver = true; + break; + case WEAPONTYPE_FLAMETHROWER: + if (bFireProof) + return; + break; + case WEAPONTYPE_RAMMEDBYCAR: + if (bCollisionProof) + return; + break; + default: + break; + } + if (m_fHealth > 0.0f) { + if (VehicleCreatedBy == RANDOM_VEHICLE && pDriver && + (GetStatus() == STATUS_SIMPLE || GetStatus() == STATUS_PHYSICS) && + AutoPilot.m_nCarMission == MISSION_CRUISE) { + if (m_randomSeed < DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE) { + CCarCtrl::SwitchVehicleToRealPhysics(this); + AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + AutoPilot.m_nCruiseSpeed = GAME_SPEED_TO_CARAI_SPEED * pHandling->Transmission.fMaxCruiseVelocity; + SetStatus(STATUS_PHYSICS); + } + } + m_nLastWeaponDamage = weaponType; + float oldHealth = m_fHealth; + if (m_fHealth > damage) { + m_fHealth -= damage; + if (VehicleCreatedBy == RANDOM_VEHICLE && + (m_fHealth < DAMAGE_HEALTH_TO_FLEE_ALWAYS || + bFrightensDriver && m_randomSeed > DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE)) { + switch (GetStatus()) { + case STATUS_SIMPLE: + case STATUS_PHYSICS: + if (pDriver) { + SetStatus(STATUS_ABANDONED); + pDriver->bFleeAfterExitingCar = true; + pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, this); + } + for (int i = 0; i < m_nNumMaxPassengers; i++) { + if (pPassengers[i]) { + pPassengers[i]->bFleeAfterExitingCar = true; + pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, this); + } + } + break; + default: + break; + } + } + if (oldHealth >= DAMAGE_HEALTH_TO_CATCH_FIRE && m_fHealth < DAMAGE_HEALTH_TO_CATCH_FIRE) { + if (IsCar()) { + CAutomobile* pThisCar = (CAutomobile*)this; + pThisCar->Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE); + pThisCar->m_pSetOnFireEntity = damagedBy; + if (damagedBy) + damagedBy->RegisterReference((CEntity**)&pThisCar->m_pSetOnFireEntity); + } + } + } + else { + m_fHealth = 0.0f; + if (weaponType == WEAPONTYPE_EXPLOSION) { + // between 1000 and 3047. Also not very nice: can't be saved by respray or cheat + m_nBombTimer = 1000 + CGeneral::GetRandomNumber() & 0x7FF; + m_pBlowUpEntity = damagedBy; + if (damagedBy) + damagedBy->RegisterReference((CEntity**)&m_pBlowUpEntity); + } + else + BlowUpCar(damagedBy); + } + } +#ifdef FIX_BUGS // removing dumb case when shooting police car in player's own garage gives wanted level + if (GetModelIndex() == MI_POLICE && damagedBy == FindPlayerPed() && damagedBy != nil && !bHasBeenOwnedByPlayer) +#else + if (GetModelIndex() == MI_POLICE && damagedBy == FindPlayerPed()) +#endif + FindPlayerPed()->SetWantedLevelNoDrop(1); +} + +void +CVehicle::DoFixedMachineGuns(void) +{ + if(CPad::GetPad(0)->GetCarGunFired() && !bGunSwitchedOff){ + if(CTimer::GetTimeInMilliseconds() > m_nGunFiringTime + 150){ + CVector source, target; + float dx, dy, len; + + dx = GetForward().x; + dy = GetForward().y; + len = Sqrt(SQR(dx) + SQR(dy)); + if(len < 0.1f) len = 0.1f; + dx /= len; + dy /= len; + + m_nGunFiringTime = CTimer::GetTimeInMilliseconds(); + + source = GetMatrix() * CVector(2.0f, 2.5f, 1.0f); + target = source + CVector(dx, dy, 0.0f)*60.0f; + target += CVector( + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f, + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f, + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.02f); + CWeapon::DoTankDoomAiming(this, pDriver, &source, &target); + FireOneInstantHitRound(&source, &target, 15); + + source = GetMatrix() * CVector(-2.0f, 2.5f, 1.0f); + target = source + CVector(dx, dy, 0.0f)*60.0f; + target += CVector( + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f, + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f, + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.02f); + CWeapon::DoTankDoomAiming(this, pDriver, &source, &target); + FireOneInstantHitRound(&source, &target, 15); + + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + + m_nAmmoInClip--; + if(m_nAmmoInClip == 0){ + m_nAmmoInClip = 20; + m_nGunFiringTime = CTimer::GetTimeInMilliseconds() + 1400; + } + } + }else{ + if(CTimer::GetTimeInMilliseconds() > m_nGunFiringTime + 1400) + m_nAmmoInClip = 20; + } +} + +void +CVehicle::ExtinguishCarFire(void) +{ + m_fHealth = Max(m_fHealth, 300.0f); + if(m_pCarFire) + m_pCarFire->Extinguish(); + if(IsCar()){ + CAutomobile *car = (CAutomobile*)this; + if(car->Damage.GetEngineStatus() >= ENGINE_STATUS_ON_FIRE) + car->Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE-10); + car->m_fFireBlowUpTimer = 0.0f; + } +} + +bool +CVehicle::ShufflePassengersToMakeSpace(void) +{ + if (m_nNumPassengers >= m_nNumMaxPassengers) + return false; + if (pPassengers[1] && + !(m_nGettingInFlags & CAR_DOOR_FLAG_LR) && + IsRoomForPedToLeaveCar(CAR_DOOR_LR, nil)) { + if (!pPassengers[2] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RR)) { + pPassengers[2] = pPassengers[1]; + pPassengers[1] = nil; + pPassengers[2]->m_vehDoor = CAR_DOOR_RR; + return true; + } + if (!pPassengers[0] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RF)) { + pPassengers[0] = pPassengers[1]; + pPassengers[1] = nil; + pPassengers[0]->m_vehDoor = CAR_DOOR_RF; + return true; + } + return false; + } + if (pPassengers[2] && + !(m_nGettingInFlags & CAR_DOOR_FLAG_RR) && + IsRoomForPedToLeaveCar(CAR_DOOR_RR, nil)) { + if (!pPassengers[1] && !(m_nGettingInFlags & CAR_DOOR_FLAG_LR)) { + pPassengers[1] = pPassengers[2]; + pPassengers[2] = nil; + pPassengers[1]->m_vehDoor = CAR_DOOR_LR; + return true; + } + if (!pPassengers[0] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RF)) { + pPassengers[0] = pPassengers[2]; + pPassengers[2] = nil; + pPassengers[0]->m_vehDoor = CAR_DOOR_RF; + return true; + } + return false; + } + if (pPassengers[0] && + !(m_nGettingInFlags & CAR_DOOR_FLAG_RF) && + IsRoomForPedToLeaveCar(CAR_DOOR_RF, nil)) { + if (!pPassengers[1] && !(m_nGettingInFlags & CAR_DOOR_FLAG_LR)) { + pPassengers[1] = pPassengers[0]; + pPassengers[0] = nil; + pPassengers[1]->m_vehDoor = CAR_DOOR_LR; + return true; + } + if (!pPassengers[2] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RR)) { + pPassengers[2] = pPassengers[0]; + pPassengers[0] = nil; + pPassengers[2]->m_vehDoor = CAR_DOOR_RR; + return true; + } + return false; + } + return false; +} + +void +CVehicle::ProcessDelayedExplosion(void) +{ + if(m_nBombTimer == 0) + return; + + int tick = CTimer::GetTimeStep()/60.0f*1000.0f; + int16 prev = m_nBombTimer; + if(tick > m_nBombTimer) + m_nBombTimer = 0; + else + m_nBombTimer -= tick; + + if(IsCar() && ((CAutomobile*)this)->m_bombType == CARBOMB_TIMEDACTIVE && (m_nBombTimer & 0xFE00) != (prev & 0xFE00)) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_BOMB_TICK, 0.0f); + + if (m_nBombTimer == 0){ + if(FindPlayerVehicle() != this && m_pBlowUpEntity == FindPlayerPed()) + CWorld::Players[CWorld::PlayerInFocus].AwardMoneyForExplosion(this); + BlowUpCar(m_pBlowUpEntity); + } +} + +bool +CVehicle::IsLawEnforcementVehicle(void) +{ + switch(GetModelIndex()){ + case MI_FBICAR: + case MI_POLICE: + case MI_ENFORCER: + case MI_PREDATOR: + case MI_RHINO: + case MI_BARRACKS: + return true; + default: + return false; + } +} + +bool +CVehicle::UsesSiren(uint32 id) +{ + switch(id){ + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_FBICAR: + case MI_MRWHOOP: + case MI_POLICE: + case MI_ENFORCER: + case MI_PREDATOR: + return true; + default: + return false; + } +} + +bool +CVehicle::IsVehicleNormal(void) +{ + if (!pDriver || m_nNumPassengers != 0 || GetStatus() == STATUS_WRECKED) + return false; + switch (GetModelIndex()){ + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_TAXI: + case MI_POLICE: + case MI_ENFORCER: + case MI_BUS: + case MI_RHINO: + case MI_BARRACKS: + case MI_DODO: + case MI_COACH: + case MI_CABBIE: + case MI_RCBANDIT: + case MI_BORGNINE: + return false; + default: + return true; + } +} + +bool +CVehicle::CarHasRoof(void) +{ + if((pHandling->Flags & HANDLING_HAS_NO_ROOF) == 0) + return true; + if(m_aExtras[0] && m_aExtras[1]) + return false; + return true; +} + +bool +CVehicle::IsUpsideDown(void) +{ + if(GetUp().z > -0.9f) + return false; + return true; +} + +bool +CVehicle::IsOnItsSide(void) +{ + if(GetRight().z < 0.8f && GetRight().z > -0.8f) + return false; + return true; +} + +bool +CVehicle::CanBeDeleted(void) +{ + int i; + + if(m_nNumGettingIn || m_nGettingOutFlags) + return false; + + if(pDriver){ + // This looks like it was inlined + if(pDriver->CharCreatedBy == MISSION_CHAR) + return false; + if(pDriver->GetPedState() != PED_DRIVING && + pDriver->GetPedState() != PED_DEAD) + return false; + } + + for(i = 0; i < ARRAY_SIZE(pPassengers); i++){ + // Same check as above + if(pPassengers[i]){ + if(pPassengers[i]->CharCreatedBy == MISSION_CHAR) + return false; + if(pPassengers[i]->GetPedState() != PED_DRIVING && + pPassengers[i]->GetPedState() != PED_DEAD) + return false; + } + // and then again... probably because something was inlined + if(pPassengers[i]){ + if(pPassengers[i]->GetPedState() != PED_DRIVING && + pPassengers[i]->GetPedState() != PED_DEAD) + return false; + } + } + + switch(VehicleCreatedBy){ + case RANDOM_VEHICLE: return true; + case MISSION_VEHICLE: return false; + case PARKED_VEHICLE: return true; + case PERMANENT_VEHICLE: return false; + } + return true; +} + +bool +CVehicle::CanPedOpenLocks(CPed *ped) +{ + if(m_nDoorLock == CARLOCK_LOCKED || + m_nDoorLock == CARLOCK_LOCKED_INITIALLY || + m_nDoorLock == CARLOCK_LOCKED_PLAYER_INSIDE) + return false; + if(ped->IsPlayer() && m_nDoorLock == CARLOCK_LOCKOUT_PLAYER_ONLY) + return false; + return true; +} + +bool +CVehicle::CanPedEnterCar(void) +{ + // can't enter when car is on side + if(GetUp().z > 0.1f || GetUp().z < -0.1f){ + // also when car is moving too fast + if(m_vecMoveSpeed.MagnitudeSqr() > sq(0.2f)) + return false; + if(m_vecTurnSpeed.MagnitudeSqr() > sq(0.2f)) + return false; + return true; + } + return false; +} + +bool +CVehicle::CanPedExitCar(void) +{ + CVector up = GetUp(); + if(up.z > 0.1f || up.z < -0.1f){ +#ifdef VC_PED_PORTS + if (IsBoat()) + return true; +#endif + // can't exit when car is moving too fast + if(m_vecMoveSpeed.MagnitudeSqr() > 0.005f) + return false; + // if car is slow enough, check turn speed + if(Abs(m_vecTurnSpeed.x) > 0.01f || + Abs(m_vecTurnSpeed.y) > 0.01f || + Abs(m_vecTurnSpeed.z) > 0.01f) + return false; + return true; + }else{ + // What is this? just > replaced by >= ?? + + // can't exit when car is moving too fast + if(m_vecMoveSpeed.MagnitudeSqr() >= 0.005f) + return false; + // if car is slow enough, check turn speed + if(Abs(m_vecTurnSpeed.x) >= 0.01f || + Abs(m_vecTurnSpeed.y) >= 0.01f || + Abs(m_vecTurnSpeed.z) >= 0.01f) + return false; + return true; + } +} + +void +CVehicle::ChangeLawEnforcerState(uint8 enable) +{ + if (enable) { + if (!bIsLawEnforcer) { + bIsLawEnforcer = true; + CCarCtrl::NumLawEnforcerCars++; + } + } else { + if (bIsLawEnforcer) { + bIsLawEnforcer = false; + CCarCtrl::NumLawEnforcerCars--; + } + } +} + +CPed* +CVehicle::SetUpDriver(void) +{ + if(pDriver) + return pDriver; + if(VehicleCreatedBy != RANDOM_VEHICLE) + return nil; + + pDriver = CPopulation::AddPedInCar(this); + pDriver->m_pMyVehicle = this; + pDriver->m_pMyVehicle->RegisterReference((CEntity**)&pDriver->m_pMyVehicle); + pDriver->bInVehicle = true; + pDriver->SetPedState(PED_DRIVING); + if(bIsBus) + pDriver->bRenderPedInCar = false; + return pDriver; +} + +CPed* +CVehicle::SetupPassenger(int n) +{ + if(pPassengers[n]) + return pPassengers[n]; + + pPassengers[n] = CPopulation::AddPedInCar(this); + pPassengers[n]->m_pMyVehicle = this; + pPassengers[n]->m_pMyVehicle->RegisterReference((CEntity**)&pPassengers[n]->m_pMyVehicle); + pPassengers[n]->bInVehicle = true; + pPassengers[n]->SetPedState(PED_DRIVING); + if(bIsBus) + pPassengers[n]->bRenderPedInCar = false; + ++m_nNumPassengers; + return pPassengers[n]; +} + +void +CVehicle::SetDriver(CPed *driver) +{ + pDriver = driver; + pDriver->RegisterReference((CEntity**)&pDriver); + + if(bFreebies && driver == FindPlayerPed()){ + if(GetModelIndex() == MI_AMBULAN) + FindPlayerPed()->m_fHealth = Min(FindPlayerPed()->m_fHealth + 20.0f, 100.0f); + else if(GetModelIndex() == MI_TAXI) + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 25; + else if(GetModelIndex() == MI_POLICE) + driver->GiveWeapon(WEAPONTYPE_SHOTGUN, 5); + else if(GetModelIndex() == MI_ENFORCER) + driver->m_fArmour = Max(driver->m_fArmour, 100.0f); + else if(GetModelIndex() == MI_CABBIE || GetModelIndex() == MI_BORGNINE) + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 25; + bFreebies = false; + } + + ApplyTurnForce(0.0f, 0.0f, -0.2f*driver->m_fMass, + driver->GetPosition().x - GetPosition().x, + driver->GetPosition().y - GetPosition().y, + 0.0f); +} + +bool +CVehicle::AddPassenger(CPed *passenger) +{ + int i; + + ApplyTurnForce(0.0f, 0.0f, -0.2f*passenger->m_fMass, + passenger->GetPosition().x - GetPosition().x, + passenger->GetPosition().y - GetPosition().y, + 0.0f); + + for(i = 0; i < m_nNumMaxPassengers; i++) + if(pPassengers[i] == nil){ + pPassengers[i] = passenger; + m_nNumPassengers++; + return true; + } + return false; +} + +bool +CVehicle::AddPassenger(CPed *passenger, uint8 n) +{ + if(bIsBus) + return AddPassenger(passenger); + + ApplyTurnForce(0.0f, 0.0f, -0.2f*passenger->m_fMass, + passenger->GetPosition().x - GetPosition().x, + passenger->GetPosition().y - GetPosition().y, + 0.0f); + + if(n < m_nNumMaxPassengers && pPassengers[n] == nil){ + pPassengers[n] = passenger; + m_nNumPassengers++; + return true; + } + return false; +} + +void +CVehicle::RemoveDriver(void) +{ +#ifdef FIX_BUGS + if (GetStatus() != STATUS_WRECKED) +#endif + SetStatus(STATUS_ABANDONED); + pDriver = nil; +} + +void +CVehicle::RemovePassenger(CPed *p) +{ + if (IsTrain()){ + for (int i = 0; i < ARRAY_SIZE(pPassengers); i++){ + if (pPassengers[i] == p) { + pPassengers[i] = nil; + m_nNumPassengers--; + return; + } + } + return; + } + for (int i = 0; i < m_nNumMaxPassengers; i++){ + if (pPassengers[i] == p){ + pPassengers[i] = nil; + m_nNumPassengers--; + return; + } + } +} + +void +CVehicle::ProcessCarAlarm(void) +{ + uint32 step; + + if(m_nAlarmState == 0 || m_nAlarmState == -1) + return; + + step = CTimer::GetTimeStepInMilliseconds(); + if((uint16)m_nAlarmState < step) + m_nAlarmState = 0; + else + m_nAlarmState -= step; +} + +bool +CVehicle::IsSphereTouchingVehicle(float sx, float sy, float sz, float radius) +{ + float x, y, z; + // sphere relative to vehicle + CVector sph = CVector(sx, sy, sz) - GetPosition(); + CColModel *colmodel = GetColModel(); + + x = DotProduct(sph, GetRight()); + if(colmodel->boundingBox.min.x - radius > x || + colmodel->boundingBox.max.x + radius < x) + return false; + y = DotProduct(sph, GetForward()); + if(colmodel->boundingBox.min.y - radius > y || + colmodel->boundingBox.max.y + radius < y) + return false; + z = DotProduct(sph, GetUp()); + if(colmodel->boundingBox.min.z - radius > z || + colmodel->boundingBox.max.z + radius < z) + return false; + + return true; +} + +void +DestroyVehicleAndDriverAndPassengers(CVehicle* pVehicle) +{ + if (pVehicle->pDriver) { + CDarkel::RegisterKillByPlayer(pVehicle->pDriver, WEAPONTYPE_UNIDENTIFIED); + pVehicle->pDriver->FlagToDestroyWhenNextProcessed(); + } + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) { + if (pVehicle->pPassengers[i]) { + CDarkel::RegisterKillByPlayer(pVehicle->pPassengers[i], WEAPONTYPE_UNIDENTIFIED); + pVehicle->pPassengers[i]->FlagToDestroyWhenNextProcessed(); + } + } + CWorld::Remove(pVehicle); + delete pVehicle; +} + +#ifdef COMPATIBLE_SAVES +void +CVehicle::Save(uint8*& buf) +{ + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, GetRight().x); + WriteSaveBuf(buf, GetRight().y); + WriteSaveBuf(buf, GetRight().z); + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, GetForward().x); + WriteSaveBuf(buf, GetForward().y); + WriteSaveBuf(buf, GetForward().z); + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, GetUp().x); + WriteSaveBuf(buf, GetUp().y); + WriteSaveBuf(buf, GetUp().z); + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, GetPosition().x); + WriteSaveBuf(buf, GetPosition().y); + WriteSaveBuf(buf, GetPosition().z); + ZeroSaveBuf(buf, 16); + SaveEntityFlags(buf); + ZeroSaveBuf(buf, 212); + AutoPilot.Save(buf); + WriteSaveBuf(buf, m_currentColour1); + WriteSaveBuf(buf, m_currentColour2); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, m_nAlarmState); + ZeroSaveBuf(buf, 43); + WriteSaveBuf(buf, m_nNumMaxPassengers); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, field_1D0[0]); + WriteSaveBuf(buf, field_1D0[1]); + WriteSaveBuf(buf, field_1D0[2]); + WriteSaveBuf(buf, field_1D0[3]); + ZeroSaveBuf(buf, 8); + WriteSaveBuf(buf, m_fSteerAngle); + WriteSaveBuf(buf, m_fGasPedal); + WriteSaveBuf(buf, m_fBrakePedal); + WriteSaveBuf(buf, VehicleCreatedBy); + uint8 flags = 0; + if (bIsLawEnforcer) flags |= BIT(0); + if (bIsLocked) flags |= BIT(3); + if (bEngineOn) flags |= BIT(4); + if (bIsHandbrakeOn) flags |= BIT(5); + if (bLightsOn) flags |= BIT(6); + if (bFreebies) flags |= BIT(7); + WriteSaveBuf(buf, flags); + ZeroSaveBuf(buf, 10); + WriteSaveBuf(buf, m_fHealth); + WriteSaveBuf(buf, m_nCurrentGear); + ZeroSaveBuf(buf, 3); + WriteSaveBuf(buf, m_fChangeGearTime); + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, m_nTimeOfDeath); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, m_nBombTimer); + ZeroSaveBuf(buf, 12); + WriteSaveBuf(buf, m_nDoorLock); + ZeroSaveBuf(buf, 96); +} + +void +CVehicle::Load(uint8*& buf) +{ + CMatrix tmp; + SkipSaveBuf(buf, 4); + ReadSaveBuf(&tmp.GetRight().x, buf); + ReadSaveBuf(&tmp.GetRight().y, buf); + ReadSaveBuf(&tmp.GetRight().z, buf); + SkipSaveBuf(buf, 4); + ReadSaveBuf(&tmp.GetForward().x, buf); + ReadSaveBuf(&tmp.GetForward().y, buf); + ReadSaveBuf(&tmp.GetForward().z, buf); + SkipSaveBuf(buf, 4); + ReadSaveBuf(&tmp.GetUp().x, buf); + ReadSaveBuf(&tmp.GetUp().y, buf); + ReadSaveBuf(&tmp.GetUp().z, buf); + SkipSaveBuf(buf, 4); + ReadSaveBuf(&tmp.GetPosition().x, buf); + ReadSaveBuf(&tmp.GetPosition().y, buf); + ReadSaveBuf(&tmp.GetPosition().z, buf); + m_matrix = tmp; + SkipSaveBuf(buf, 16); + LoadEntityFlags(buf); + SkipSaveBuf(buf, 212); + AutoPilot.Load(buf); + ReadSaveBuf(&m_currentColour1, buf); + ReadSaveBuf(&m_currentColour2, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&m_nAlarmState, buf); + SkipSaveBuf(buf, 43); + ReadSaveBuf(&m_nNumMaxPassengers, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&field_1D0[0], buf); + ReadSaveBuf(&field_1D0[1], buf); + ReadSaveBuf(&field_1D0[2], buf); + ReadSaveBuf(&field_1D0[3], buf); + SkipSaveBuf(buf, 8); + ReadSaveBuf(&m_fSteerAngle, buf); + ReadSaveBuf(&m_fGasPedal, buf); + ReadSaveBuf(&m_fBrakePedal, buf); + ReadSaveBuf(&VehicleCreatedBy, buf); + uint8 flags; + ReadSaveBuf(&flags, buf); + bIsLawEnforcer = !!(flags & BIT(0)); + bIsLocked = !!(flags & BIT(3)); + bEngineOn = !!(flags & BIT(4)); + bIsHandbrakeOn = !!(flags & BIT(5)); + bLightsOn = !!(flags & BIT(6)); + bFreebies = !!(flags & BIT(7)); + SkipSaveBuf(buf, 10); + ReadSaveBuf(&m_fHealth, buf); + ReadSaveBuf(&m_nCurrentGear, buf); + SkipSaveBuf(buf, 3); + ReadSaveBuf(&m_fChangeGearTime, buf); + SkipSaveBuf(buf, 4); + ReadSaveBuf(&m_nTimeOfDeath, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&m_nBombTimer, buf); + SkipSaveBuf(buf, 12); + ReadSaveBuf(&m_nDoorLock, buf); + SkipSaveBuf(buf, 96); +} +#endif diff --git a/src/vehicles/Vehicle.h b/src/vehicles/Vehicle.h new file mode 100644 index 0000000..ab60ee2 --- /dev/null +++ b/src/vehicles/Vehicle.h @@ -0,0 +1,293 @@ +#pragma once + +#include "Physical.h" +#include "AutoPilot.h" +#include "ModelIndices.h" +#include "AnimationId.h" +#include "WeaponType.h" +#include "Collision.h" + +class CPed; +class CFire; +struct tHandlingData; + +enum { + RANDOM_VEHICLE = 1, + MISSION_VEHICLE = 2, + PARKED_VEHICLE = 3, + PERMANENT_VEHICLE = 4, +}; + +enum eCarLock { + CARLOCK_NOT_USED, + CARLOCK_UNLOCKED, + CARLOCK_LOCKED, + CARLOCK_LOCKOUT_PLAYER_ONLY, + CARLOCK_LOCKED_PLAYER_INSIDE, + CARLOCK_LOCKED_INITIALLY, + CARLOCK_FORCE_SHUT_DOORS +}; + +enum eDoors +{ + DOOR_BONNET = 0, + DOOR_BOOT, + DOOR_FRONT_LEFT, + DOOR_FRONT_RIGHT, + DOOR_REAR_LEFT, + DOOR_REAR_RIGHT +}; + +enum ePanels +{ + VEHPANEL_FRONT_LEFT, + VEHPANEL_FRONT_RIGHT, + VEHPANEL_REAR_LEFT, + VEHPANEL_REAR_RIGHT, + VEHPANEL_WINDSCREEN, + VEHBUMPER_FRONT, + VEHBUMPER_REAR, +}; + +enum eLights +{ + VEHLIGHT_FRONT_LEFT, + VEHLIGHT_FRONT_RIGHT, + VEHLIGHT_REAR_LEFT, + VEHLIGHT_REAR_RIGHT, +}; + +enum +{ + CAR_PIECE_BONNET = 1, + CAR_PIECE_BOOT, + CAR_PIECE_BUMP_FRONT, + CAR_PIECE_BUMP_REAR, + CAR_PIECE_DOOR_LF, + CAR_PIECE_DOOR_RF, + CAR_PIECE_DOOR_LR, + CAR_PIECE_DOOR_RR, + CAR_PIECE_WING_LF, + CAR_PIECE_WING_RF, + CAR_PIECE_WING_LR, + CAR_PIECE_WING_RR, + CAR_PIECE_WHEEL_LF, + CAR_PIECE_WHEEL_RF, + CAR_PIECE_WHEEL_LR, + CAR_PIECE_WHEEL_RR, + CAR_PIECE_WINDSCREEN, +}; + +enum tWheelState +{ + WHEEL_STATE_NORMAL, // standing still or rolling normally + WHEEL_STATE_SPINNING, // rotating but not moving + WHEEL_STATE_SKIDDING, + WHEEL_STATE_FIXED, // not rotating +}; + +enum eFlightModel +{ + FLIGHT_MODEL_DODO, + // not used in III + FLIGHT_MODEL_RCPLANE, + FLIGHT_MODEL_HELI, + FLIGHT_MODEL_SEAPLANE +}; + +// TODO: what is this even? +enum eBikeWheelSpecial { + BIKE_WHEELSPEC_0, // both wheels on ground + BIKE_WHEELSPEC_1, // rear wheel on ground + BIKE_WHEELSPEC_2, // only front wheel on ground + BIKE_WHEELSPEC_3, // can't happen +}; + + +class CVehicle : public CPhysical +{ +public: + // 0x128 + tHandlingData *pHandling; + CAutoPilot AutoPilot; + uint8 m_currentColour1; + uint8 m_currentColour2; + int8 m_aExtras[2]; + int16 m_nAlarmState; + int16 m_nMissionValue; + CPed *pDriver; + CPed *pPassengers[8]; + uint8 m_nNumPassengers; + int8 m_nNumGettingIn; + int8 m_nGettingInFlags; + int8 m_nGettingOutFlags; + uint8 m_nNumMaxPassengers; + float field_1D0[4]; + CEntity *m_pCurGroundEntity; + CFire *m_pCarFire; + float m_fSteerAngle; + float m_fGasPedal; + float m_fBrakePedal; + uint8 VehicleCreatedBy; + + // cf. https://github.com/DK22Pac/plugin-sdk/blob/master/plugin_sa/game_sa/CVehicle.h from R* + uint8 bIsLawEnforcer: 1; // Is this guy chasing the player at the moment + uint8 bIsAmbulanceOnDuty: 1; // Ambulance trying to get to an accident + uint8 bIsFireTruckOnDuty: 1; // Firetruck trying to get to a fire + uint8 bIsLocked: 1; // Is this guy locked by the script (cannot be removed) + uint8 bEngineOn: 1; // For sound purposes. Parked cars have their engines switched off (so do destroyed cars) + uint8 bIsHandbrakeOn: 1; // How's the handbrake doing ? + uint8 bLightsOn: 1; // Are the lights switched on ? + uint8 bFreebies: 1; // Any freebies left in this vehicle ? + + uint8 bIsVan: 1; // Is this vehicle a van (doors at back of vehicle) + uint8 bIsBus: 1; // Is this vehicle a bus + uint8 bIsBig: 1; // Is this vehicle a bus + uint8 bLowVehicle: 1; // Need this for sporty type cars to use low getting-in/out anims + uint8 bComedyControls : 1; // Will make the car hard to control (hopefully in a funny way) + uint8 bWarnedPeds : 1; // Has scan and warn peds of danger been processed? + uint8 bCraneMessageDone : 1; // A crane message has been printed for this car allready + uint8 bExtendedRange : 1; // This vehicle needs to be a bit further away to get deleted + + uint8 bTakeLessDamage : 1; // This vehicle is stronger (takes about 1/4 of damage) + uint8 bIsDamaged : 1; // This vehicle has been damaged and is displaying all its components + uint8 bHasBeenOwnedByPlayer : 1;// To work out whether stealing it is a crime + uint8 bFadeOut : 1; // Fade vehicle out + uint8 bIsBeingCarJacked : 1; // Fade vehicle out + uint8 bCreateRoadBlockPeds : 1; // If this vehicle gets close enough we will create peds (coppers or gang members) round it + uint8 bCanBeDamaged : 1; // Set to FALSE during cut scenes to avoid explosions + uint8 bUsingSpecialColModel : 1;// Is player vehicle using special collision model, stored in player strucure + + uint8 bOccupantsHaveBeenGenerated : 1; // Is true if the occupants have already been generated. (Shouldn't happen again) + uint8 bGunSwitchedOff : 1; // Level designers can use this to switch off guns on boats + uint8 bVehicleColProcessed : 1;// Has ProcessEntityCollision been processed for this car? + uint8 bIsCarParkVehicle : 1; // Car has been created using the special CAR_PARK script command + uint8 bHasAlreadyBeenRecorded : 1; // Used for replays + + int8 m_numPedsUseItAsCover; + uint8 m_nAmmoInClip; // Used to make the guns on boat do a reload (20 by default) + int8 m_nPacManPickupsCarried; + uint8 m_nRoadblockType; + int16 m_nRoadblockNode; + float m_fHealth; // 1000.0f = full health. 250.0f = fire. 0 -> explode + uint8 m_nCurrentGear; + float m_fChangeGearTime; + uint32 m_nGunFiringTime; // last time when gun on vehicle was fired (used on boats) + uint32 m_nTimeOfDeath; + uint16 m_nTimeBlocked; + int16 m_nBombTimer; // goes down with each frame + CEntity *m_pBlowUpEntity; + float m_fMapObjectHeightAhead; // front Z? + float m_fMapObjectHeightBehind; // rear Z? + eCarLock m_nDoorLock; + int8 m_nLastWeaponDamage; // see eWeaponType, -1 if no damage + uint8 m_nRadioStation; + uint8 m_bRainAudioCounter; + uint8 m_bRainSamplesCounter; + uint8 m_nCarHornTimer; + uint8 m_nCarHornPattern; // last horn? + bool m_bSirenOrAlarm; + int8 m_comedyControlState; + CStoredCollPoly m_aCollPolys[2]; // poly which is under front/rear part of car + float m_fSteerInput; + eVehicleType m_vehType; + + static void *operator new(size_t) throw(); + static void *operator new(size_t sz, int slot) throw(); + static void operator delete(void*, size_t) throw(); + static void operator delete(void*, int) throw(); + + CVehicle(void) {} // FAKE + CVehicle(uint8 CreatedBy); + ~CVehicle(void); + // from CEntity + void SetModelIndex(uint32 id); + bool SetupLighting(void); + void RemoveLighting(bool); + void FlagToDestroyWhenNextProcessed(void) {} + + virtual void ProcessControlInputs(uint8) {} + virtual void GetComponentWorldPosition(int32 component, CVector &pos) {} + virtual bool IsComponentPresent(int32 component) { return false; } + virtual void SetComponentRotation(int32 component, CVector rotation) {} + virtual void OpenDoor(int32, eDoors door, float) {} + virtual void ProcessOpenDoor(uint32, uint32, float) {} + virtual bool IsDoorReady(eDoors door) { return false; } + virtual bool IsDoorFullyOpen(eDoors door) { return false; } + virtual bool IsDoorClosed(eDoors door) { return false; } + virtual bool IsDoorMissing(eDoors door) { return false; } + virtual void RemoveRefsToVehicle(CEntity *ent) {} + virtual void BlowUpCar(CEntity *ent) {} + virtual bool SetUpWheelColModel(CColModel *colModel) { return false; } + virtual void BurstTyre(uint8 tyre) {} + virtual bool IsRoomForPedToLeaveCar(uint32 component, CVector *forcedDoorPos) { return false;} + virtual float GetHeightAboveRoad(void); + virtual void PlayCarHorn(void) {} +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + + bool IsCar(void) { return m_vehType == VEHICLE_TYPE_CAR; } + bool IsBoat(void) { return m_vehType == VEHICLE_TYPE_BOAT; } + bool IsTrain(void) { return m_vehType == VEHICLE_TYPE_TRAIN; } + bool IsHeli(void) { return m_vehType == VEHICLE_TYPE_HELI; } + bool IsPlane(void) { return m_vehType == VEHICLE_TYPE_PLANE; } + bool IsBike(void) { return m_vehType == VEHICLE_TYPE_BIKE; } + + void FlyingControl(eFlightModel flightModel); + void ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, + int32 wheelsOnGround, float thrust, float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, uint16 wheelStatus); + void ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, int32 wheelsOnGround, float thrust, + float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, eBikeWheelSpecial special, uint16 wheelStatus); + void ExtinguishCarFire(void); + void ProcessDelayedExplosion(void); + float ProcessWheelRotation(tWheelState state, const CVector &fwd, const CVector &speed, float radius); + bool IsLawEnforcementVehicle(void); + void ChangeLawEnforcerState(uint8 enable); + bool UsesSiren(uint32 id); + bool IsVehicleNormal(void); + bool CarHasRoof(void); + bool IsUpsideDown(void); + bool IsOnItsSide(void); + bool CanBeDeleted(void); + bool CanPedOpenLocks(CPed *ped); + bool CanPedEnterCar(void); + bool CanPedExitCar(void); + // do these two actually return something? + CPed *SetUpDriver(void); + CPed *SetupPassenger(int n); + void SetDriver(CPed *driver); + bool AddPassenger(CPed *passenger); + bool AddPassenger(CPed *passenger, uint8 n); + void RemovePassenger(CPed *passenger); + void RemoveDriver(void); + void ProcessCarAlarm(void); + bool IsSphereTouchingVehicle(float sx, float sy, float sz, float radius); + bool ShufflePassengersToMakeSpace(void); + void InflictDamage(CEntity *damagedBy, eWeaponType weaponType, float damage); + void DoFixedMachineGuns(void); + +#ifdef FIX_BUGS + bool IsAlarmOn(void) { return m_nAlarmState != 0 && m_nAlarmState != -1 && GetStatus() != STATUS_WRECKED; } +#else + bool IsAlarmOn(void) { return m_nAlarmState != 0 && m_nAlarmState != -1; } +#endif + CVehicleModelInfo* GetModelInfo() { return (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); } + bool IsTaxi(void) { return GetModelIndex() == MI_TAXI || GetModelIndex() == MI_CABBIE || GetModelIndex() == MI_BORGNINE; } + AnimationId GetDriverAnim(void) { return IsCar() && bLowVehicle ? ANIM_STD_CAR_SIT_LO : (IsBoat() && GetModelIndex() != MI_SPEEDER ? ANIM_STD_BOAT_DRIVE : ANIM_STD_CAR_SIT); } + + static bool bWheelsOnlyCheat; + static bool bAllDodosCheat; + static bool bCheat3; + static bool bCheat4; + static bool bCheat5; +#ifdef ALT_DODO_CHEAT + static bool bAltDodoCheat; +#endif + static bool m_bDisableMouseSteering; +}; + +VALIDATE_SIZE(CVehicle, 0x288); + +void DestroyVehicleAndDriverAndPassengers(CVehicle* pVehicle); diff --git a/src/weapons/BulletInfo.cpp b/src/weapons/BulletInfo.cpp new file mode 100644 index 0000000..bfe27e1 --- /dev/null +++ b/src/weapons/BulletInfo.cpp @@ -0,0 +1,306 @@ +#include "common.h" + +#include "BulletInfo.h" + +#include "AnimBlendAssociation.h" +#include "DMAudio.h" +#include "AudioScriptObject.h" +#ifdef FIX_BUGS +#include "Collision.h" +#endif +#include "RpAnimBlend.h" +#include "Entity.h" +#include "EventList.h" +#include "Fire.h" +#include "Glass.h" +#include "Particle.h" +#include "Ped.h" +#include "Object.h" +#include "Stats.h" +#include "Timer.h" +#include "Vehicle.h" +#include "Weapon.h" +#include "WeaponInfo.h" +#include "World.h" +#include "SurfaceTable.h" + +#ifdef SQUEEZE_PERFORMANCE +uint32 bulletInfoInUse; +#endif + +#define BULLET_LIFETIME (1000) +#define NUM_PED_BLOOD_PARTICLES (8) +#define BLOOD_PARTICLE_OFFSET (CVector(0.0f, 0.0f, 0.0f)) +#define NUM_VEHICLE_SPARKS (16) +#define NUM_OTHER_SPARKS (8) +#define BULLET_HIT_FORCE (7.5f) +#define MAP_BORDER (1960.0f) + +CBulletInfo gaBulletInfo[CBulletInfo::NUM_BULLETS]; +bool bPlayerSniperBullet; +CVector PlayerSniperBulletStart; +CVector PlayerSniperBulletEnd; + +void CBulletInfo::Initialise(void) +{ + debug("Initialising CBulletInfo...\n"); + for (int i = 0; i < NUM_BULLETS; i++) { + gaBulletInfo[i].m_bInUse = false; + gaBulletInfo[i].m_eWeaponType = WEAPONTYPE_COLT45; + gaBulletInfo[i].m_fTimer = 0.0f; + gaBulletInfo[i].m_pSource = nil; + } + debug("CBulletInfo ready\n"); +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse = 0; +#endif +} + +void CBulletInfo::Shutdown(void) +{ + debug("Shutting down CBulletInfo...\n"); + debug("CBulletInfo shut down\n"); +} + +bool CBulletInfo::AddBullet(CEntity* pSource, eWeaponType type, CVector vecPosition, CVector vecSpeed) +{ + int i; + for (i = 0; i < NUM_BULLETS; i++) { + if (!gaBulletInfo[i].m_bInUse) + break; + } + if (i == NUM_BULLETS) + return false; + gaBulletInfo[i].m_pSource = pSource; + gaBulletInfo[i].m_eWeaponType = type; + gaBulletInfo[i].m_nDamage = CWeaponInfo::GetWeaponInfo(type)->m_nDamage; + gaBulletInfo[i].m_vecPosition = vecPosition; + gaBulletInfo[i].m_vecSpeed = vecSpeed; + gaBulletInfo[i].m_fTimer = CTimer::GetTimeInMilliseconds() + BULLET_LIFETIME; + gaBulletInfo[i].m_bInUse = true; + +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse++; +#endif + return true; +} + +void CBulletInfo::Update(void) +{ +#ifdef SQUEEZE_PERFORMANCE + if (bulletInfoInUse == 0) + return; +#endif + bool bAddSound = true; + bPlayerSniperBullet = false; + for (int i = 0; i < NUM_BULLETS; i++) { + CBulletInfo* pBullet = &gaBulletInfo[i]; + if (pBullet->m_pSource && pBullet->m_pSource->IsPed() && !((CPed*)pBullet->m_pSource)->IsPointerValid()) + pBullet->m_pSource = nil; + if (!pBullet->m_bInUse) + continue; + if (CTimer::GetTimeInMilliseconds() > pBullet->m_fTimer) { + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + } + CVector vecOldPos = pBullet->m_vecPosition; + CVector vecNewPos = pBullet->m_vecPosition + pBullet->m_vecSpeed * CTimer::GetTimeStep() * 0.5f; + CWorld::bIncludeCarTyres = true; + CWorld::bIncludeDeadPeds = true; + CWorld::pIgnoreEntity = pBullet->m_pSource; + CColPoint point; + CEntity* pHitEntity; + if (CWorld::ProcessLineOfSight(vecOldPos, vecNewPos, point, pHitEntity, true, true, true, true, true, true)) { + if (pBullet->m_pSource && (pHitEntity->IsPed() || pHitEntity->IsVehicle())) + CStats::InstantHitsHitByPlayer++; + if (pHitEntity->IsPed()) { + CPed* pPed = (CPed*)pHitEntity; + if (!pPed->DyingOrDead() && pPed != pBullet->m_pSource) { + if (pPed->DoesLOSBulletHitPed(point)) { + if (pPed->IsPedInControl() && !pPed->bIsDucking) { + pPed->ClearAttackByRemovingAnim(); + CAnimBlendAssociation* pAnim = CAnimManager::AddAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HITBYGUN_FRONT); + pAnim->SetBlend(0.0f, 8.0f); + } + pPed->InflictDamage(pBullet->m_pSource, pBullet->m_eWeaponType, pBullet->m_nDamage, (ePedPieceTypes)point.pieceB, pPed->GetLocalDirection(pPed->GetPosition() - point.point)); + CEventList::RegisterEvent(pPed->m_nPedType == PEDTYPE_COP ? EVENT_SHOOT_COP : EVENT_SHOOT_PED, EVENT_ENTITY_PED, pPed, (CPed*)pBullet->m_pSource, 1000); + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + vecNewPos = point.point; + } + else { + bAddSound = false; + } + } + if (CGame::nastyGame) { + CVector vecParticleDirection = (point.point - pPed->GetPosition()) * 0.01f; + vecParticleDirection.z = 0.01f; + if (pPed->GetIsOnScreen()) { + for (int j = 0; j < NUM_PED_BLOOD_PARTICLES; j++) + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, point.point + BLOOD_PARTICLE_OFFSET, vecParticleDirection); + } + if (pPed->GetPedState() == PED_DEAD) { + CAnimBlendAssociation* pAnim; + if (RpAnimBlendClumpGetFirstAssociation(pPed->GetClump(), ASSOC_FRONTAL)) + pAnim = CAnimManager::BlendAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR_FRONT, 8.0f); + else + pAnim = CAnimManager::BlendAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR, 8.0f); + if (pAnim) { + pAnim->SetCurrentTime(0.0f); + pAnim->flags |= ASSOC_RUNNING; + pAnim->flags &= ~ASSOC_FADEOUTWHENDONE; + } + } + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + vecNewPos = point.point; + } + } + else if (pHitEntity->IsVehicle()) { + CVehicle* pVehicle = (CVehicle*)pHitEntity; + pVehicle->InflictDamage(pBullet->m_pSource, pBullet->m_eWeaponType, pBullet->m_nDamage); + if (pBullet->m_eWeaponType == WEAPONTYPE_FLAMETHROWER) // huh? + gFireManager.StartFire(pVehicle, pBullet->m_pSource, 0.8f, true); + else { + for (int j = 0; j < NUM_VEHICLE_SPARKS; j++) + CParticle::AddParticle(PARTICLE_SPARK, point.point, point.normal / 20); + } +#ifdef FIX_BUGS + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + vecNewPos = point.point; +#endif + } + else { + for (int j = 0; j < NUM_OTHER_SPARKS; j++) + CParticle::AddParticle(PARTICLE_SPARK, point.point, point.normal / 20); + if (pHitEntity->IsObject()) { + CObject* pObject = (CObject*)pHitEntity; + if (!pObject->bInfiniteMass) { + if (pObject->GetIsStatic() && pObject->m_fUprootLimit <= 0.0f) { + pObject->SetIsStatic(false); + pObject->AddToMovingList(); + } + if (!pObject->GetIsStatic()) + pObject->ApplyMoveForce(-BULLET_HIT_FORCE * point.normal); + } + } +#ifdef FIX_BUGS + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + vecNewPos = point.point; +#endif + } + if (pBullet->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE && bAddSound) { + cAudioScriptObject* pAudio; + switch (pHitEntity->GetType()) { + case ENTITY_TYPE_BUILDING: + pAudio = new cAudioScriptObject(); + pAudio->Posn = pHitEntity->GetPosition(); + pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_1; + pAudio->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(pAudio); + break; + case ENTITY_TYPE_OBJECT: + pAudio = new cAudioScriptObject(); + pAudio->Posn = pHitEntity->GetPosition(); + pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_2; + pAudio->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(pAudio); + break; + case ENTITY_TYPE_DUMMY: + pAudio = new cAudioScriptObject(); + pAudio->Posn = pHitEntity->GetPosition(); + pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_3; + pAudio->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(pAudio); + break; + case ENTITY_TYPE_PED: + DMAudio.PlayOneShot(((CPed*)pHitEntity)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)pHitEntity)->Say(SOUND_PED_BULLET_HIT); + break; + case ENTITY_TYPE_VEHICLE: + DMAudio.PlayOneShot(((CVehicle*)pHitEntity)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + default: break; + } + } + CGlass::WasGlassHitByBullet(pHitEntity, point.point); + CWeapon::BlowUpExplosiveThings(pHitEntity); + } + CWorld::pIgnoreEntity = nil; + CWorld::bIncludeDeadPeds = false; + CWorld::bIncludeCarTyres = false; + if (pBullet->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE) { + bPlayerSniperBullet = true; + PlayerSniperBulletStart = pBullet->m_vecPosition; + PlayerSniperBulletEnd = vecNewPos; + } + pBullet->m_vecPosition = vecNewPos; + if (pBullet->m_vecPosition.x < -MAP_BORDER || pBullet->m_vecPosition.x > MAP_BORDER || + pBullet->m_vecPosition.y < -MAP_BORDER || pBullet->m_vecPosition.y > MAP_BORDER) { + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + } + } +} + +bool CBulletInfo::TestForSniperBullet(float x1, float x2, float y1, float y2, float z1, float z2) +{ + if (!bPlayerSniperBullet) + return false; +#ifdef FIX_BUGS // original code is not going work anyway... + CColLine line(PlayerSniperBulletStart, PlayerSniperBulletEnd); + CColBox box; + box.Set(CVector(x1, y1, z1), CVector(x2, y2, z2), SURFACE_DEFAULT, 0); + return CCollision::TestLineBox(line, box); +#else + float minP = 0.0f; + float maxP = 1.0f; + float minX = Min(PlayerSniperBulletStart.x, PlayerSniperBulletEnd.x); + float maxX = Max(PlayerSniperBulletStart.x, PlayerSniperBulletEnd.x); + if (minX < x2 || maxX > x1) { + if (minX < x1) + minP = Min(minP, (x1 - minX) / (maxX - minX)); + if (maxX > x2) + maxP = Max(maxP, (maxX - x2) / (maxX - minX)); + } + else + return false; + float minY = Min(PlayerSniperBulletStart.y, PlayerSniperBulletEnd.y); + float maxY = Max(PlayerSniperBulletStart.y, PlayerSniperBulletEnd.y); + if (minY < y2 || maxY > y1) { + if (minY < y1) + minP = Min(minP, (y1 - minY) / (maxY - minY)); + if (maxY > y2) + maxP = Max(maxP, (maxY - y2) / (maxY - minY)); + } +#ifdef FIX_BUGS + else + return false; +#endif + float minZ = Min(PlayerSniperBulletStart.z, PlayerSniperBulletEnd.z); + float maxZ = Max(PlayerSniperBulletStart.z, PlayerSniperBulletEnd.z); + if (minZ < z2 || maxZ > z1) { + if (minZ < z1) + minP = Min(minP, (z1 - minZ) / (maxZ - minZ)); + if (maxZ > z2) + maxP = Max(maxP, (maxZ - z2) / (maxZ - minZ)); + } + else + return false; + return minP <= maxP; +#endif +} diff --git a/src/weapons/BulletInfo.h b/src/weapons/BulletInfo.h new file mode 100644 index 0000000..cf1dd27 --- /dev/null +++ b/src/weapons/BulletInfo.h @@ -0,0 +1,25 @@ +#pragma once + +#include "WeaponType.h" + +class CEntity; + +class CBulletInfo +{ + eWeaponType m_eWeaponType; + CEntity* m_pSource; + float m_fTimer; // big mistake + bool m_bInUse; + CVector m_vecPosition; + CVector m_vecSpeed; + int16 m_nDamage; +public: + enum { + NUM_BULLETS = 100 + }; + static void Initialise(void); + static void Shutdown(void); + static bool AddBullet(CEntity* pSource, eWeaponType type, CVector vecPosition, CVector vecSpeed); + static void Update(void); + static bool TestForSniperBullet(float x1, float x2, float y1, float y2, float z1, float z2); +}; \ No newline at end of file diff --git a/src/weapons/Explosion.cpp b/src/weapons/Explosion.cpp new file mode 100644 index 0000000..f79c027 --- /dev/null +++ b/src/weapons/Explosion.cpp @@ -0,0 +1,465 @@ +#include "common.h" + +#include "Automobile.h" +#include "Bike.h" +#include "Camera.h" +#include "Coronas.h" +#include "DMAudio.h" +#include "Entity.h" +#include "EventList.h" +#include "Explosion.h" +#include "General.h" +#include "Fire.h" +#include "Pad.h" +#include "Particle.h" +#include "PointLights.h" +#include "Shadows.h" +#include "Timer.h" +#include "Vehicle.h" +#include "WaterLevel.h" +#include "World.h" + +CExplosion gaExplosion[NUM_EXPLOSIONS]; + +// these two were not initialised in original code, I'm really not sure what were they meant to be +RwRGBA colMedExpl = { 0, 0, 0, 0 }; +RwRGBA colUpdate = { 0, 0, 0, 0 }; + +int AudioHandle = AEHANDLE_NONE; + +void +CExplosion::Initialise() +{ + debug("Initialising CExplosion...\n"); + for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) { + gaExplosion[i].m_ExplosionType = EXPLOSION_GRENADE; + gaExplosion[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f); + gaExplosion[i].m_fRadius = 1.0f; + gaExplosion[i].m_fPropagationRate = 0.0f; + gaExplosion[i].m_fZshift = 0.0f; + gaExplosion[i].m_pCreatorEntity = nil; + gaExplosion[i].m_pVictimEntity = nil; + gaExplosion[i].m_fStopTime = 0.0f; + gaExplosion[i].m_nIteration = 0; + gaExplosion[i].m_fStartTime = 0.0f; + gaExplosion[i].m_bIsBoat = false; + } + AudioHandle = DMAudio.CreateEntity(AUDIOTYPE_EXPLOSION, (void*)1); + if (AudioHandle >= 0) + DMAudio.SetEntityStatus(AudioHandle, TRUE); + debug("CExplosion ready\n"); +} + +void +CExplosion::Shutdown() +{ + debug("Shutting down CExplosion...\n"); + if (AudioHandle >= 0) { + DMAudio.DestroyEntity(AudioHandle); + AudioHandle = AEHANDLE_NONE; + } + debug("CExplosion shut down\n"); +} + +int8 +CExplosion::GetExplosionActiveCounter(uint8 id) +{ + return gaExplosion[id].m_nActiveCounter; +} + +void +CExplosion::ResetExplosionActiveCounter(uint8 id) +{ + gaExplosion[id].m_nActiveCounter = 0; +} + +uint8 +CExplosion::GetExplosionType(uint8 id) +{ + return gaExplosion[id].m_ExplosionType; +} + +CVector * +CExplosion::GetExplosionPosition(uint8 id) +{ + return &gaExplosion[id].m_vecPosition; +} + +bool +CExplosion::AddExplosion(CEntity *explodingEntity, CEntity *culprit, eExplosionType type, const CVector &pos, uint32 lifetime) +{ + CVector pPosn; + CVector posGround; + + RwRGBA colorMedium = colMedExpl; + bool bDontExplode = false; + const RwRGBA color = { 160, 160, 160, 255 }; + pPosn = pos; + pPosn.z += 5.0f; +#ifdef FIX_BUGS + CShadows::AddPermanentShadow(SHADOWTEX_CAR, gpShadowHeliTex, &pPosn, 8.0f, 0.0f, 0.0f, -8.0f, 200, 0, 0, 0, 10.0f, 30000, 1.0f); +#else + // last two arguments are swapped resulting in no shadow + CShadows::AddPermanentShadow(SHADOWTEX_CAR, gpShadowHeliTex, &pPosn, 8.0f, 0.0f, 0.0f, -8.0f, 200, 0, 0, 0, 10.0f, 1, 30000.0f); +#endif + + int n = 0; +#ifdef FIX_BUGS + while (n < ARRAY_SIZE(gaExplosion) && gaExplosion[n].m_nIteration != 0) +#else + // array overrun is UB + while (gaExplosion[n].m_nIteration != 0 && n < ARRAY_SIZE(gaExplosion)) +#endif + n++; + if (n == ARRAY_SIZE(gaExplosion)) + return false; + + CExplosion &explosion = gaExplosion[n]; + explosion.m_ExplosionType = type; + explosion.m_vecPosition = pos; + explosion.m_fRadius = 1.0f; + explosion.m_fZshift = 0.0f; + explosion.m_pCreatorEntity = culprit; + if (culprit != nil) + culprit->RegisterReference(&explosion.m_pCreatorEntity); + explosion.m_pVictimEntity = explodingEntity; + if (explodingEntity != nil) + explodingEntity->RegisterReference(&explosion.m_pVictimEntity); + explosion.m_nIteration = 1; + explosion.m_nActiveCounter = 1; + explosion.m_bIsBoat = false; + explosion.m_nParticlesExpireTime = lifetime != 0 ? CTimer::GetTimeInMilliseconds() + lifetime : 0; + switch (type) + { + case EXPLOSION_GRENADE: + explosion.m_fRadius = 9.0f; + explosion.m_fPower = 300.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + posGround = pos; + posGround.z = CWorld::FindGroundZFor3DCoord(posGround.x, posGround.y, posGround.z + 3.0f, nil); + CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250); + if (Distance(explosion.m_vecPosition, TheCamera.GetPosition()) < 40.0f) + CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color); + break; + case EXPLOSION_MOLOTOV: + { + explosion.m_fRadius = 6.0f; + explosion.m_fPower = 0.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 3000; + explosion.m_fPropagationRate = 0.5f; + posGround = pos; + bool found; + posGround.z = CWorld::FindGroundZFor3DCoord(posGround.x, posGround.y, posGround.z + 3.0f, &found); + if (found) { + float waterLevel; + if (CWaterLevel::GetWaterLevelNoWaves(posGround.x, posGround.y, posGround.z, &waterLevel) + && posGround.z < waterLevel + && waterLevel - 6.0f < posGround.z) // some subway/tunnels check? + bDontExplode = true; + else + gFireManager.StartFire(posGround, 1.8f, false); + } + else + bDontExplode = true; + break; + } + case EXPLOSION_ROCKET: + explosion.m_fRadius = 10.0f; + explosion.m_fPower = 300.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + CEventList::RegisterEvent(EVENT_EXPLOSION, pos, 250); + if (Distance(explosion.m_vecPosition, TheCamera.GetPosition()) < 40.0f) + CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color); + break; + case EXPLOSION_CAR: + case EXPLOSION_CAR_QUICK: + explosion.m_fRadius = 9.0f; + explosion.m_fPower = 300.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 4250; + explosion.m_fPropagationRate = 0.5f; + explosion.m_fStartTime = CTimer::GetTimeInMilliseconds(); + if (explosion.m_pVictimEntity != nil) { + if (explosion.m_pVictimEntity->IsVehicle() && ((CVehicle*)explosion.m_pVictimEntity)->IsBoat()) + explosion.m_bIsBoat = true; + CEventList::RegisterEvent(EVENT_EXPLOSION, EVENT_ENTITY_VEHICLE, explosion.m_pVictimEntity, nil, 1000); + } else + CEventList::RegisterEvent(EVENT_EXPLOSION, pos, 1000); + + if (explosion.m_pVictimEntity != nil && !explosion.m_bIsBoat) { + int rn = (CGeneral::GetRandomNumber() & 1) + 2; + for (int i = 0; i < rn; i++) { + CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, explosion.m_pVictimEntity->GetPosition(), CVector(0.0f, 0.0f, 0.0f), nil, 3.5f, colMedExpl); + CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, explosion.m_pVictimEntity->GetPosition(), CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color); + } + CVehicle *veh = (CVehicle*)explosion.m_pVictimEntity; + int32 component = CAR_WING_LR; + + // miami leftover + if (veh->IsBike()) + component = BIKE_FORKS_REAR; + + if (veh->IsComponentPresent(component)) { + CVector componentPos; + veh->GetComponentWorldPosition(component, componentPos); + rn = (CGeneral::GetRandomNumber() & 1) + 1; + for (int i = 0; i < rn; i++) + CParticle::AddJetExplosion(componentPos, 1.4f, 0.0f); + } + } + break; + case EXPLOSION_HELI: + explosion.m_fRadius = 6.0f; + explosion.m_fPower = 300.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + explosion.m_fStartTime = CTimer::GetTimeInMilliseconds(); + for (int i = 0; i < 10; i++) { + CVector randpos; + uint8 x, y, z; + + x = CGeneral::GetRandomNumber(); + y = CGeneral::GetRandomNumber(); + z = CGeneral::GetRandomNumber(); + randpos = pos + CVector(x - 128, y - 128, z - 128) / 20.0f; + + CParticle::AddParticle(PARTICLE_EXPLOSION_MFAST, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 2.5f, color); + + x = CGeneral::GetRandomNumber(); + y = CGeneral::GetRandomNumber(); + z = CGeneral::GetRandomNumber(); + randpos = pos + CVector(x - 128, y - 128, z - 128) / 20.0f; + + CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 5.0f, color); + + x = CGeneral::GetRandomNumber(); + y = CGeneral::GetRandomNumber(); + z = CGeneral::GetRandomNumber(); + randpos = pos + CVector(x - 128, y - 128, z - 128) / 20.0f; + + CParticle::AddJetExplosion(randpos, 1.4f, 3.0f); + } + CEventList::RegisterEvent(EVENT_EXPLOSION, pos, 1000); + break; + case EXPLOSION_MINE: + explosion.m_fRadius = 10.0f; + explosion.m_fPower = 150.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + posGround = pos; + //posGround.z = + CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused + CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250); + break; + case EXPLOSION_BARREL: + explosion.m_fRadius = 7.0f; + explosion.m_fPower = 150.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + for (int i = 0; i < 6; i++) { + CVector randpos; + uint8 x, y, z; + + x = CGeneral::GetRandomNumber(); + y = CGeneral::GetRandomNumber(); + z = CGeneral::GetRandomNumber(); + randpos = CVector(x - 128, y - 128, z - 128); + + randpos.x /= 50.0f; + randpos.y /= 50.0f; + randpos.z /= 25.0f; + randpos += pos; + CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, colorMedium); + } + posGround = pos; + //posGround.z = + CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused + CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250); + break; + case EXPLOSION_TANK_GRENADE: + explosion.m_fRadius = 10.0f; + explosion.m_fPower = 150.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + posGround = pos; + //posGround.z = + CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused + CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250); + break; + case EXPLOSION_HELI_BOMB: + explosion.m_fRadius = 8.0f; + explosion.m_fPower = 50.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + posGround = pos; + //posGround.z = + CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused + CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250); + break; + } + if (bDontExplode) { + explosion.m_nIteration = 0; + return false; + } + + if (explosion.m_fPower != 0.0f && explosion.m_nParticlesExpireTime == 0) + CWorld::TriggerExplosion(pos, explosion.m_fRadius, explosion.m_fPower, culprit, (type == EXPLOSION_ROCKET || type == EXPLOSION_CAR_QUICK || type == EXPLOSION_MINE || type == EXPLOSION_BARREL || type == EXPLOSION_TANK_GRENADE || type == EXPLOSION_HELI_BOMB)); + + TheCamera.CamShake(0.6f, pos.x, pos.y, pos.z); + CPad::GetPad(0)->StartShake_Distance(300, 128, pos.x, pos.y, pos.z); + return true; +} + +void +CExplosion::Update() +{ + RwRGBA color = colUpdate; + for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) { + CExplosion &explosion = gaExplosion[i]; + if (explosion.m_nIteration == 0) continue; + + if (explosion.m_nParticlesExpireTime != 0) { + if (CTimer::GetTimeInMilliseconds() > explosion.m_nParticlesExpireTime) { + explosion.m_nParticlesExpireTime = 0; + if (explosion.m_fPower != 0.0f) + CWorld::TriggerExplosion(explosion.m_vecPosition, explosion.m_fRadius, explosion.m_fPower, explosion.m_pCreatorEntity, (explosion.m_ExplosionType == EXPLOSION_ROCKET || explosion.m_ExplosionType == EXPLOSION_CAR_QUICK || explosion.m_ExplosionType == EXPLOSION_MINE || explosion.m_ExplosionType == EXPLOSION_BARREL || explosion.m_ExplosionType == EXPLOSION_TANK_GRENADE || explosion.m_ExplosionType == EXPLOSION_HELI_BOMB)); + } + } else { + explosion.m_fRadius += explosion.m_fPropagationRate * CTimer::GetTimeStep(); + int32 someTime = explosion.m_fStopTime - CTimer::GetTimeInMilliseconds(); + switch (explosion.m_ExplosionType) + { + case EXPLOSION_GRENADE: + case EXPLOSION_ROCKET: + case EXPLOSION_HELI: + case EXPLOSION_MINE: + case EXPLOSION_BARREL: + if (CTimer::GetFrameCounter() & 1) { + CPointLights::AddLight(CPointLights::LIGHT_POINT, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), 20.0f, 1.0f, 1.0f, 0.5f, CPointLights::FOG_NONE, true); + CCoronas::RegisterCorona((uintptr)&explosion, 255, 255, 200, 255, explosion.m_vecPosition, 8.0f, 120.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } else + CCoronas::RegisterCorona((uintptr)&explosion, 128, 128, 100, 255, explosion.m_vecPosition, 8.0f, 120.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + CCoronas::RegisterCorona((uintptr)&explosion + 1, 30, 30, 25, 255, explosion.m_vecPosition, explosion.m_fRadius, 120.0f, gpCoronaTexture[7], CCoronas::TYPE_STAR, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + break; + case EXPLOSION_MOLOTOV: + CWorld::SetPedsOnFire(explosion.m_vecPosition.x, explosion.m_vecPosition.y, explosion.m_vecPosition.z, 6.0f, explosion.m_pCreatorEntity); + CWorld::SetCarsOnFire(explosion.m_vecPosition.x, explosion.m_vecPosition.y, explosion.m_vecPosition.z, 6.0f, explosion.m_pCreatorEntity); + if (explosion.m_nIteration < 10) { + if (explosion.m_nIteration == 1) { + CVector point1 = explosion.m_vecPosition; + point1.z += 5.0f; + CColPoint colPoint; + CEntity *pEntity; + CWorld::ProcessVerticalLine(point1, -1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil); + explosion.m_fZshift = colPoint.point.z; + } + float ff = ((float)explosion.m_nIteration * 0.55f); + for (int i = 0; i < 5 * ff; i++) { + float angle = CGeneral::GetRandomNumber() / 256.0f * 6.28f; + + CVector pos = explosion.m_vecPosition; + pos.x += ff * Sin(angle); + pos.y += ff * Cos(angle); + pos.z += 5.0f; // what is the point of this? + + pos.z = explosion.m_fZshift + 0.5f; + CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, color, CGeneral::GetRandomNumberInRange(-3.0f, 3.0f), CGeneral::GetRandomNumberInRange(-180.0f, 180.0f)); + } + } + break; + case EXPLOSION_CAR: + case EXPLOSION_CAR_QUICK: + if (someTime >= 3500) { + if (explosion.m_pVictimEntity != nil && !explosion.m_bIsBoat) { + if ((CGeneral::GetRandomNumber() & 0xF) == 0) { + CVehicle *veh = (CVehicle*)explosion.m_pVictimEntity; + uint8 component = CAR_WING_LR; + + // miami leftover + if (veh->IsBike()) + component = BIKE_FORKS_REAR; + + if (veh->IsComponentPresent(component)) { + CVector componentPos; + veh->GetComponentWorldPosition(component, componentPos); + CParticle::AddJetExplosion(componentPos, 1.5f, 0.0f); + } + } + if (CTimer::GetTimeInMilliseconds() > explosion.m_fStartTime) { + explosion.m_fStartTime = CTimer::GetTimeInMilliseconds() + 125 + (CGeneral::GetRandomNumber() & 0x7F); + CVector pos = explosion.m_pVictimEntity->GetPosition(); + for (int i = 0; i < (CGeneral::GetRandomNumber() & 1) + 1; i++) { + CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, CVector(0.0f, 0.0f, 0.0f), nil, 3.5f, color); + CParticle::AddParticle(PARTICLE_EXPLOSION_LARGE, pos, CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color); + } + } + } + if (CTimer::GetFrameCounter() & 1) { + CPointLights::AddLight(CPointLights::LIGHT_POINT, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), 15.0f, 1.0f, 0.0f, 0.0f, CPointLights::FOG_NONE, true); + CCoronas::RegisterCorona((uintptr)&explosion, 200, 100, 0, 255, explosion.m_vecPosition, 6.0f, 80.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } else + CCoronas::RegisterCorona((uintptr)&explosion, 128, 0, 0, 255, explosion.m_vecPosition, 8.0f, 80.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + + CCoronas::RegisterCorona((uintptr)&explosion + 1, 30, 15, 0, 255, explosion.m_vecPosition, explosion.m_fRadius, 80.0f, gpCoronaTexture[7], CCoronas::TYPE_STAR, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } else if (explosion.m_nIteration & 1) { + if (explosion.m_pVictimEntity != nil) + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, explosion.m_pVictimEntity->GetPosition(), CVector(0.0f, 0.0f, 0.0f), nil, CGeneral::GetRandomNumberInRange(0.5f, 0.8f), color); + CVector pos = explosion.m_vecPosition; + pos.z += 1.0f; + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, pos, CVector(0.0f, 0.0f, 0.11f), nil, CGeneral::GetRandomNumberInRange(0.5f, 2.0f), color); + } + break; + case EXPLOSION_TANK_GRENADE: + case EXPLOSION_HELI_BOMB: + if (explosion.m_nIteration < 5) { + float ff = ((float)explosion.m_nIteration * 0.65f); + for (int i = 0; i < 10 * ff; i++) { + uint8 x = CGeneral::GetRandomNumber(), y = CGeneral::GetRandomNumber(), z = CGeneral::GetRandomNumber(); + CVector pos(x - 128, y - 128, (z % 128) + 1); + + pos.Normalise(); + pos *= ff / 5.0f; + pos += explosion.m_vecPosition; + pos.z += 0.5f; + CParticle::AddParticle(PARTICLE_EXPLOSION_LARGE, pos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, color, CGeneral::GetRandomNumberInRange(-3.0f, 3.0f), CGeneral::GetRandomNumberInRange(-180.0f, 180.0f)); + } + } + break; + } + if (someTime > 0) + explosion.m_nIteration++; + else + explosion.m_nIteration = 0; + } + } +} + +bool +CExplosion::TestForExplosionInArea(eExplosionType type, float x1, float x2, float y1, float y2, float z1, float z2) +{ + for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) { + if (gaExplosion[i].m_nIteration != 0) { + if (type == gaExplosion[i].m_ExplosionType) { + if (gaExplosion[i].m_vecPosition.x >= x1 && gaExplosion[i].m_vecPosition.x <= x2) { + if (gaExplosion[i].m_vecPosition.y >= y1 && gaExplosion[i].m_vecPosition.y <= y2) { + if (gaExplosion[i].m_vecPosition.z >= z1 && gaExplosion[i].m_vecPosition.z <= z2) + return true; + } + } + } + } + } + return false; +} + +void +CExplosion::RemoveAllExplosionsInArea(CVector pos, float radius) +{ + for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) { + if (gaExplosion[i].m_nIteration != 0) { + if ((pos - gaExplosion[i].m_vecPosition).MagnitudeSqr() < SQR(radius)) + gaExplosion[i].m_nIteration = 0; + } + } +} \ No newline at end of file diff --git a/src/weapons/Explosion.h b/src/weapons/Explosion.h new file mode 100644 index 0000000..bf54328 --- /dev/null +++ b/src/weapons/Explosion.h @@ -0,0 +1,49 @@ +#pragma once + +class CEntity; +class CVector; + +enum eExplosionType +{ + EXPLOSION_GRENADE, + EXPLOSION_MOLOTOV, + EXPLOSION_ROCKET, + EXPLOSION_CAR, + EXPLOSION_CAR_QUICK, + EXPLOSION_HELI, + EXPLOSION_MINE, + EXPLOSION_BARREL, + EXPLOSION_TANK_GRENADE, + EXPLOSION_HELI_BOMB +}; + +class CExplosion +{ + eExplosionType m_ExplosionType; + CVector m_vecPosition; + float m_fRadius; + float m_fPropagationRate; + CEntity *m_pCreatorEntity; + CEntity *m_pVictimEntity; + float m_fStopTime; + uint8 m_nIteration; + uint8 m_nActiveCounter; + float m_fStartTime; + uint32 m_nParticlesExpireTime; + float m_fPower; + bool m_bIsBoat; + float m_fZshift; +public: + static void Initialise(); + static void Shutdown(); + static int8 GetExplosionActiveCounter(uint8 id); + static void ResetExplosionActiveCounter(uint8 id); + static uint8 GetExplosionType(uint8 id); + static CVector *GetExplosionPosition(uint8 id); + static bool AddExplosion(CEntity *explodingEntity, CEntity *culprit, eExplosionType type, const CVector &pos, uint32 lifetime); + static void Update(); + static bool TestForExplosionInArea(eExplosionType type, float x1, float x2, float y1, float y2, float z1, float z2); + static void RemoveAllExplosionsInArea(CVector pos, float radius); +}; + +extern CExplosion gaExplosion[NUM_EXPLOSIONS]; \ No newline at end of file diff --git a/src/weapons/ProjectileInfo.cpp b/src/weapons/ProjectileInfo.cpp new file mode 100644 index 0000000..da00b87 --- /dev/null +++ b/src/weapons/ProjectileInfo.cpp @@ -0,0 +1,342 @@ +#include "common.h" + +#include "Camera.h" +#include "General.h" +#include "Heli.h" +#include "ModelIndices.h" +#include "Particle.h" +#include "Ped.h" +#include "Plane.h" +#include "ProjectileInfo.h" +#include "Projectile.h" +#include "Explosion.h" +#include "Weapon.h" +#include "World.h" + +#ifdef SQUEEZE_PERFORMANCE +uint32 projectileInUse; +#endif + +CProjectileInfo gaProjectileInfo[NUM_PROJECTILES]; +CProjectile *CProjectileInfo::ms_apProjectile[NUM_PROJECTILES]; + +void +CProjectileInfo::Initialise() +{ + debug("Initialising CProjectileInfo...\n"); + + for (int i = 0; i < ARRAY_SIZE(ms_apProjectile); i++) { + ms_apProjectile[i] = nil; + gaProjectileInfo[i].m_eWeaponType = WEAPONTYPE_GRENADE; + gaProjectileInfo[i].m_pSource = nil; + gaProjectileInfo[i].m_nExplosionTime = 0; + gaProjectileInfo[i].m_bInUse = false; + } + + debug("CProjectileInfo ready\n"); + +#ifdef SQUEEZE_PERFORMANCE + projectileInUse = 0; +#endif +} + +void +CProjectileInfo::Shutdown() +{ + debug("Shutting down CProjectileInfo...\n"); + debug("CProjectileInfo shut down\n"); +} + +CProjectileInfo* +CProjectileInfo::GetProjectileInfo(int32 id) +{ + return &gaProjectileInfo[id]; +} + +bool +CProjectileInfo::AddProjectile(CEntity *entity, eWeaponType weapon, CVector pos, float speed) +{ + int8 SpecialCollisionResponseCase = COLLRESPONSE_NONE; + bool gravity = true; + CMatrix matrix; + float elasticity = 0.75f; + CPed* ped = (CPed*)entity; + int time; + CVector velocity; + + switch (weapon) + { + case WEAPONTYPE_ROCKETLAUNCHER: + { + float vy = 1.25f; + time = CTimer::GetTimeInMilliseconds() + 1400; + if (ped->IsPlayer()) { + matrix.GetForward() = TheCamera.Cams[TheCamera.ActiveCam].Front; + matrix.GetUp() = TheCamera.Cams[TheCamera.ActiveCam].Up; + matrix.GetRight() = CrossProduct(TheCamera.Cams[TheCamera.ActiveCam].Up, TheCamera.Cams[TheCamera.ActiveCam].Front); + matrix.GetPosition() = pos; + } else if (ped->m_pSeekTarget != nil) { + float ry = CGeneral::GetRadianAngleBetweenPoints(1.0f, ped->m_pSeekTarget->GetPosition().z, 1.0f, pos.z); + float rz = Atan2(-ped->GetForward().x, ped->GetForward().y); + vy = 0.35f * speed + 0.15f; + matrix.SetTranslate(0.0f, 1.0f, 1.0f); + matrix.Rotate(0.0f, ry, rz); + matrix.GetPosition() += pos; + } else { + matrix = ped->GetMatrix(); + } + velocity = Multiply3x3(matrix, CVector(0.0f, vy, 0.0f)); + gravity = false; + break; + } + case WEAPONTYPE_FLAMETHROWER: + Error("Undefined projectile type, AddProjectile, ProjectileInfo.cpp"); + break; + case WEAPONTYPE_MOLOTOV: + { + time = CTimer::GetTimeInMilliseconds() + 2000; + float scale = 0.22f * speed + 0.15f; + if (scale < 0.2f) + scale = 0.2f; + float angle = Atan2(-ped->GetForward().x, ped->GetForward().y); + matrix.SetTranslate(0.0f, 0.0f, 0.0f); + matrix.RotateZ(angle); + matrix.GetPosition() += pos; + velocity.x = -1.0f * scale * Sin(angle); + velocity.y = scale * Cos(angle); + velocity.z = (0.2f * speed + 0.4f) * scale; + break; + } + case WEAPONTYPE_GRENADE: + { + time = CTimer::GetTimeInMilliseconds() + 2000; + float scale = 0.0f; + if (speed != 0.0f) + scale = 0.22f * speed + 0.15f; + float angle = Atan2(-ped->GetForward().x, ped->GetForward().y); + matrix.SetTranslate(0.0f, 0.0f, 0.0f); + matrix.RotateZ(angle); + matrix.GetPosition() += pos; + SpecialCollisionResponseCase = COLLRESPONSE_UNKNOWN5; + velocity.x = -1.0f * scale * Sin(angle); + velocity.y = scale * Cos(angle); + velocity.z = (0.4f * speed + 0.4f) * scale; + elasticity = 0.5f; + break; + } + default: break; + } + + int i = 0; +#ifdef FIX_BUGS + while (i < ARRAY_SIZE(gaProjectileInfo) && gaProjectileInfo[i].m_bInUse) i++; +#else + // array overrun is UB + while (gaProjectileInfo[i].m_bInUse && i < ARRAY_SIZE(gaProjectileInfo)) i++; +#endif + if (i == ARRAY_SIZE(gaProjectileInfo)) + return false; + + switch (weapon) + { + case WEAPONTYPE_ROCKETLAUNCHER: + ms_apProjectile[i] = new CProjectile(MI_MISSILE); + break; + case WEAPONTYPE_FLAMETHROWER: + break; + case WEAPONTYPE_MOLOTOV: + ms_apProjectile[i] = new CProjectile(MI_MOLOTOV); + break; + case WEAPONTYPE_GRENADE: + ms_apProjectile[i] = new CProjectile(MI_GRENADE); + break; + default: break; + } + + if (ms_apProjectile[i] == nil) + return false; + + gaProjectileInfo[i].m_eWeaponType = weapon; + gaProjectileInfo[i].m_pSource = ped; + ms_apProjectile[i]->GetMatrix() = matrix; + ms_apProjectile[i]->SetMoveSpeed(velocity); + ms_apProjectile[i]->bAffectedByGravity = gravity; + + gaProjectileInfo[i].m_nExplosionTime = time; + ms_apProjectile[i]->m_fElasticity = elasticity; + ms_apProjectile[i]->m_nSpecialCollisionResponseCases = SpecialCollisionResponseCase; + +#ifdef SQUEEZE_PERFORMANCE + projectileInUse++; +#endif + + gaProjectileInfo[i].m_bInUse = true; + CWorld::Add(ms_apProjectile[i]); + + gaProjectileInfo[i].m_vecPos = ms_apProjectile[i]->GetPosition(); + return true; +} + +void +CProjectileInfo::RemoveProjectile(CProjectileInfo *info, CProjectile *projectile) +{ + RemoveNotAdd(info->m_pSource, info->m_eWeaponType, projectile->GetPosition()); +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + info->m_bInUse = false; + CWorld::Remove(projectile); + delete projectile; +} + +void +CProjectileInfo::RemoveNotAdd(CEntity *entity, eWeaponType weaponType, CVector pos) +{ + switch (weaponType) + { + case WEAPONTYPE_GRENADE: + CExplosion::AddExplosion(nil, entity, EXPLOSION_GRENADE, pos, 0); + break; + case WEAPONTYPE_MOLOTOV: + CExplosion::AddExplosion(nil, entity, EXPLOSION_MOLOTOV, pos, 0); + break; + case WEAPONTYPE_ROCKETLAUNCHER: + CExplosion::AddExplosion(nil, entity, EXPLOSION_ROCKET, pos, 0); + break; + default: break; + } +} + +void +CProjectileInfo::Update() +{ +#ifdef SQUEEZE_PERFORMANCE + if (projectileInUse == 0) + return; +#endif + + for (int i = 0; i < ARRAY_SIZE(gaProjectileInfo); i++) { + if (!gaProjectileInfo[i].m_bInUse) continue; + + CPed *ped = (CPed*)gaProjectileInfo[i].m_pSource; + if (ped != nil && ped->IsPed() && !ped->IsPointerValid()) + gaProjectileInfo[i].m_pSource = nil; + + if (ms_apProjectile[i] == nil) { +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + gaProjectileInfo[i].m_bInUse = false; + continue; + } + + if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) { + CParticle::AddParticle(PARTICLE_SMOKE, ms_apProjectile[i]->GetPosition(), CVector(0.0f, 0.0f, 0.0f)); + } + + if (CTimer::GetTimeInMilliseconds() <= gaProjectileInfo[i].m_nExplosionTime) { + if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) { + CVector pos = ms_apProjectile[i]->GetPosition(); + CWorld::pIgnoreEntity = ms_apProjectile[i]; + if (ms_apProjectile[i]->bHasCollided + || !CWorld::GetIsLineOfSightClear(gaProjectileInfo[i].m_vecPos, pos, true, true, true, true, false, false) + || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER && (CHeli::TestRocketCollision(&pos) || CPlane::TestRocketCollision(&pos))) { + RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]); + } + CWorld::pIgnoreEntity = nil; + } else if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_MOLOTOV) { + CVector pos = ms_apProjectile[i]->GetPosition(); + CWorld::pIgnoreEntity = ms_apProjectile[i]; + + if (gaProjectileInfo[i].m_pSource == nil + || ((gaProjectileInfo[i].m_vecPos - gaProjectileInfo[i].m_pSource->GetPosition()).MagnitudeSqr() >= 2.0f)) + { + if (ms_apProjectile[i]->bHasCollided + || !CWorld::GetIsLineOfSightClear(gaProjectileInfo[i].m_vecPos, pos, true, true, true, true, false, false) + || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER && (CHeli::TestRocketCollision(&pos) || CPlane::TestRocketCollision(&pos))) { + RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]); + } + } + CWorld::pIgnoreEntity = nil; + } + } else { + RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]); + } + + gaProjectileInfo[i].m_vecPos = ms_apProjectile[i]->GetPosition(); + } +} + +bool +CProjectileInfo::IsProjectileInRange(float x1, float x2, float y1, float y2, float z1, float z2, bool remove) +{ + bool result = false; + for (int i = 0; i < ARRAY_SIZE(ms_apProjectile); i++) { + if (gaProjectileInfo[i].m_bInUse) { + if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_MOLOTOV || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_GRENADE) { + const CVector &pos = ms_apProjectile[i]->GetPosition(); + if (pos.x >= x1 && pos.x <= x2 && pos.y >= y1 && pos.y <= y2 && pos.z >= z1 && pos.z <= z2) { + result = true; + if (remove) { +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + gaProjectileInfo[i].m_bInUse = false; + CWorld::Remove(ms_apProjectile[i]); + delete ms_apProjectile[i]; + } + } + } + } + } + return result; +} + +void +CProjectileInfo::RemoveAllProjectiles() +{ +#ifdef SQUEEZE_PERFORMANCE + if (projectileInUse == 0) + return; +#endif + + for (int i = 0; i < ARRAY_SIZE(ms_apProjectile); i++) { + if (gaProjectileInfo[i].m_bInUse) { +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + gaProjectileInfo[i].m_bInUse = false; + CWorld::Remove(ms_apProjectile[i]); + delete ms_apProjectile[i]; + } + } +} + +bool +CProjectileInfo::RemoveIfThisIsAProjectile(CObject *object) +{ +#ifdef SQUEEZE_PERFORMANCE + if (projectileInUse == 0) + return false; +#endif + + int i = 0; + while (ms_apProjectile[i++] != object) { + if (i >= ARRAY_SIZE(ms_apProjectile)) + return false; + } + +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + gaProjectileInfo[i].m_bInUse = false; + CWorld::Remove(ms_apProjectile[i]); + delete ms_apProjectile[i]; + ms_apProjectile[i] = nil; + return true; +} diff --git a/src/weapons/ProjectileInfo.h b/src/weapons/ProjectileInfo.h new file mode 100644 index 0000000..3d8074c --- /dev/null +++ b/src/weapons/ProjectileInfo.h @@ -0,0 +1,33 @@ +#pragma once + +#include "WeaponType.h" + +class CEntity; +class CObject; +class CProjectile; + +class CProjectileInfo +{ +public: + eWeaponType m_eWeaponType; + CEntity *m_pSource; + uint32 m_nExplosionTime; + bool m_bInUse; + CVector m_vecPos; + +public: + static CProjectileInfo *GetProjectileInfo(int32 id); + static CProjectile *ms_apProjectile[NUM_PROJECTILES]; + + static void Initialise(); + static void Shutdown(); + static bool AddProjectile(CEntity *ped, eWeaponType weapon, CVector pos, float speed); + static void RemoveProjectile(CProjectileInfo *info, CProjectile *projectile); + static void RemoveNotAdd(CEntity *entity, eWeaponType weaponType, CVector pos); + static bool RemoveIfThisIsAProjectile(CObject *pObject); + static void RemoveAllProjectiles(); + static void Update(); + static bool IsProjectileInRange(float x1, float x2, float y1, float y2, float z1, float z2, bool remove); +}; + +extern CProjectileInfo gaProjectileInfo[NUM_PROJECTILES]; \ No newline at end of file diff --git a/src/weapons/ShotInfo.cpp b/src/weapons/ShotInfo.cpp new file mode 100644 index 0000000..e604093 --- /dev/null +++ b/src/weapons/ShotInfo.cpp @@ -0,0 +1,149 @@ +#include "common.h" + +#include "ShotInfo.h" +#include "Entity.h" +#include "Weapon.h" +#include "World.h" +#include "WeaponInfo.h" +#include "General.h" +#include "Timer.h" +#include "Ped.h" +#include "Fire.h" + +CShotInfo gaShotInfo[NUMSHOTINFOS]; +float CShotInfo::ms_afRandTable[20]; + +#ifdef SQUEEZE_PERFORMANCE +uint32 shotInfoInUse; +#endif + +/* + Used for flamethrower. I don't know why it's name is CShotInfo. + Has no relation with any visual, just calculates the area fire affects + (including spreading and slowing of fire) and make entities burn/flee. +*/ + +void +CShotInfo::Initialise() +{ + debug("Initialising CShotInfo...\n"); + for(int i=0; im_fRadius; + + if (weaponInfo->m_fSpread != 0.0f) { + gaShotInfo[slot].m_areaAffected.x += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] * weaponInfo->m_fSpread; + gaShotInfo[slot].m_areaAffected.y += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] * weaponInfo->m_fSpread; + gaShotInfo[slot].m_areaAffected.z += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)]; + } + gaShotInfo[slot].m_areaAffected.Normalise(); + if (weaponInfo->IsFlagSet(WEAPONFLAG_RAND_SPEED)) + gaShotInfo[slot].m_areaAffected *= CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] + weaponInfo->m_fSpeed; + else + gaShotInfo[slot].m_areaAffected *= weaponInfo->m_fSpeed; + + gaShotInfo[slot].m_sourceEntity = sourceEntity; + gaShotInfo[slot].m_timeout = CTimer::GetTimeInMilliseconds() + weaponInfo->m_fLifespan; + + return true; +} + +void +CShotInfo::Shutdown() +{ + debug("Shutting down CShotInfo...\n"); + debug("CShotInfo shut down\n"); +} + +void +CShotInfo::Update() +{ +#ifdef SQUEEZE_PERFORMANCE + if (shotInfoInUse == 0) + return; +#endif + for (int slot = 0; slot < ARRAY_SIZE(gaShotInfo); slot++) { + CShotInfo &shot = gaShotInfo[slot]; + if (shot.m_sourceEntity && shot.m_sourceEntity->IsPed() && !((CPed*)shot.m_sourceEntity)->IsPointerValid()) + shot.m_sourceEntity = nil; + + if (!shot.m_inUse) + continue; + + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(shot.m_weapon); + if (CTimer::GetTimeInMilliseconds() > shot.m_timeout) { +#ifdef SQUEEZE_PERFORMANCE + shotInfoInUse--; +#endif + shot.m_inUse = false; + } + + if (weaponInfo->IsFlagSet(WEAPONFLAG_SLOWS_DOWN)) + shot.m_areaAffected *= pow(0.96, CTimer::GetTimeStep()); // FRAMERATE + + if (weaponInfo->IsFlagSet(WEAPONFLAG_EXPANDS)) + shot.m_radius += 0.075f * CTimer::GetTimeStep(); + + shot.m_startPos += CTimer::GetTimeStep() * shot.m_areaAffected; + if (shot.m_sourceEntity) { + assert(shot.m_sourceEntity->IsPed()); + CPed *ped = (CPed*) shot.m_sourceEntity; + float radius = Max(1.0f, shot.m_radius); + + for (int i = 0; i < ped->m_numNearPeds; ++i) { + CPed *nearPed = ped->m_nearPeds[i]; + if (nearPed->IsPointerValid()) { + if (nearPed->IsPedInControl() && (nearPed->GetPosition() - shot.m_startPos).MagnitudeSqr() < radius && !nearPed->bFireProof) { + + if (!nearPed->IsPlayer()) { + nearPed->SetFindPathAndFlee(shot.m_sourceEntity, 10000); + nearPed->SetMoveState(PEDMOVE_SPRINT); + } + gFireManager.StartFire(nearPed, shot.m_sourceEntity, 0.8f, true); + } + } + } + } + if (!((CTimer::GetFrameCounter() + slot) & 3)) + CWorld::SetCarsOnFire(shot.m_startPos.x, shot.m_startPos.y, shot.m_startPos.z, 4.0f, shot.m_sourceEntity); + } +} \ No newline at end of file diff --git a/src/weapons/ShotInfo.h b/src/weapons/ShotInfo.h new file mode 100644 index 0000000..db6158c --- /dev/null +++ b/src/weapons/ShotInfo.h @@ -0,0 +1,24 @@ +#pragma once + +#include "WeaponType.h" + +class CEntity; + +class CShotInfo +{ +public: + eWeaponType m_weapon; + CVector m_startPos; + CVector m_areaAffected; + float m_radius; + CEntity *m_sourceEntity; + float m_timeout; + bool m_inUse; + + static float ms_afRandTable[20]; + + static void Initialise(void); + static bool AddShot(CEntity*, eWeaponType, CVector, CVector); + static void Shutdown(void); + static void Update(void); +}; diff --git a/src/weapons/Weapon.cpp b/src/weapons/Weapon.cpp new file mode 100644 index 0000000..d6af820 --- /dev/null +++ b/src/weapons/Weapon.cpp @@ -0,0 +1,2415 @@ +#include "common.h" + +#include "Weapon.h" +#include "AnimBlendAssociation.h" +#include "AudioManager.h" +#include "BulletInfo.h" +#include "Camera.h" +#include "Coronas.h" +#include "DMAudio.h" +#include "Explosion.h" +#include "General.h" +#include "Glass.h" +#include "Heli.h" +#include "ModelIndices.h" +#include "Object.h" +#include "Pad.h" +#include "Particle.h" +#include "Ped.h" +#include "PointLights.h" +#include "Pools.h" +#include "ProjectileInfo.h" +#include "RpAnimBlend.h" +#include "ShotInfo.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "TempColModels.h" +#include "Timer.h" +#include "Automobile.h" +#include "Boat.h" +#include "WaterLevel.h" +#include "WeaponInfo.h" +#include "World.h" +#include "SaveBuf.h" + +uint16 gReloadSampleTime[WEAPONTYPE_LAST_WEAPONTYPE] = +{ + 0, // UNARMED + 0, // BASEBALLBAT + 250, // COLT45 + 400, // UZI + 650, // SHOTGUN + 300, // AK47 + 300, // M16 + 423, // SNIPERRIFLE + 400, // ROCKETLAUNCHER + 0, // FLAMETHROWER + 0, // MOLOTOV + 0, // GRENADE + 0, // DETONATOR + 0 // HELICANNON +}; + +#ifdef FREE_CAM +static bool +Find3rdPersonCamTargetVectorFromCachedVectors(float dist, CVector pos, CVector& source, CVector& target, CVector camSource, CVector camFront, CVector camUp) +{ + if (CPad::GetPad(0)->GetLookBehindForPed()) { + source = pos; + target = dist * FindPlayerPed()->GetForward() + source; + return false; + } else { + float angleX = DEGTORAD((TheCamera.m_f3rdPersonCHairMultX - 0.5f) * 1.8f * 0.5f * TheCamera.Cams[TheCamera.ActiveCam].FOV * CDraw::GetAspectRatio()); + float angleY = DEGTORAD((0.5f - TheCamera.m_f3rdPersonCHairMultY) * 1.8f * 0.5f * TheCamera.Cams[TheCamera.ActiveCam].FOV); + source = camSource; + target = camFront; + target += camUp * Tan(angleY); + target += CrossProduct(camFront, camUp) * Tan(angleX); + target.Normalise(); + source += DotProduct(pos - source, target) * target; + target = dist * target + source; + return true; + } +} +#endif + +CWeaponInfo * +CWeapon::GetInfo() +{ + CWeaponInfo *info = CWeaponInfo::GetWeaponInfo(m_eWeaponType); + ASSERT(info!=nil); + return info; +} + +void +CWeapon::InitialiseWeapons(void) +{ + CWeaponInfo::Initialise(); + CShotInfo::Initialise(); + CExplosion::Initialise(); + CProjectileInfo::Initialise(); + CBulletInfo::Initialise(); +} + +void +CWeapon::ShutdownWeapons(void) +{ + CWeaponInfo::Shutdown(); + CShotInfo::Shutdown(); + CExplosion::Shutdown(); + CProjectileInfo::Shutdown(); + CBulletInfo::Shutdown(); +} + +void +CWeapon::UpdateWeapons(void) +{ + CShotInfo::Update(); + CExplosion::Update(); + CProjectileInfo::Update(); + CBulletInfo::Update(); +} + +void +CWeapon::Initialise(eWeaponType type, int32 ammo) +{ + m_eWeaponType = type; + m_eWeaponState = WEAPONSTATE_READY; + if (ammo > 99999) + m_nAmmoTotal = 99999; + else + m_nAmmoTotal = ammo; + m_nAmmoInClip = 0; + Reload(); + m_nTimer = 0; +} + +bool +CWeapon::Fire(CEntity *shooter, CVector *fireSource) +{ + ASSERT(shooter!=nil); + + CVector fireOffset(0.0f, 0.0f, 0.6f); + CVector *source = fireSource; + + if (!fireSource) + { + fireOffset = shooter->GetMatrix() * fireOffset; +#ifdef FIX_BUGS + static CVector tmp; + tmp = fireOffset; + source = &tmp; +#else + source = &fireOffset; +#endif + + } + if ( m_bAddRotOffset ) + { + float heading = RADTODEG(shooter->GetForward().Heading()); + float angle = DEGTORAD(heading); + (*source).x += -Sin(angle) * 0.15f; + (*source).y += Cos(angle) * 0.15f; + } + + if ( m_eWeaponState != WEAPONSTATE_READY && m_eWeaponState != WEAPONSTATE_FIRING ) + return false; + + bool fired; + + if ( GetInfo()->m_eWeaponFire != WEAPON_FIRE_MELEE ) + { + if ( m_nAmmoInClip <= 0 ) + return false; + + switch ( m_eWeaponType ) + { + case WEAPONTYPE_SHOTGUN: + { + fired = FireShotgun(shooter, source); + + break; + } + + case WEAPONTYPE_COLT45: + case WEAPONTYPE_UZI: + case WEAPONTYPE_AK47: + { + fired = FireInstantHit(shooter, source); + + break; + } + + case WEAPONTYPE_SNIPERRIFLE: + { + fired = FireSniper(shooter); + + break; + } + + case WEAPONTYPE_M16: + { + if ( TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON && shooter == FindPlayerPed() ) + fired = FireM16_1stPerson(shooter); + else + fired = FireInstantHit(shooter, source); + + break; + } + + case WEAPONTYPE_ROCKETLAUNCHER: + { + if ( shooter->IsPed() && ((CPed*)shooter)->m_pSeekTarget != nil ) + { + float distToTarget = (shooter->GetPosition() - ((CPed*)shooter)->m_pSeekTarget->GetPosition()).Magnitude(); + + if ( distToTarget > 8.0f || ((CPed*)shooter)->IsPlayer() ) + fired = FireProjectile(shooter, source, 0.0f); + else + fired = false; + } + else + fired = FireProjectile(shooter, source, 0.0f); + + break; + } + + case WEAPONTYPE_MOLOTOV: + case WEAPONTYPE_GRENADE: + { + if ( shooter == FindPlayerPed() ) + { + fired = FireProjectile(shooter, source, ((CPlayerPed*)shooter)->m_fAttackButtonCounter*0.0375f); + if ( m_eWeaponType == WEAPONTYPE_GRENADE ) + CStats::KgsOfExplosivesUsed++; + } + else if ( shooter->IsPed() && ((CPed*)shooter)->m_pSeekTarget != nil ) + { + float distToTarget = (shooter->GetPosition() - ((CPed*)shooter)->m_pSeekTarget->GetPosition()).Magnitude(); + float power = Clamp((distToTarget-10.0f)*0.02f, 0.2f, 1.0f); + + fired = FireProjectile(shooter, source, power); + } + else + fired = FireProjectile(shooter, source, 0.3f); + + break; + } + + case WEAPONTYPE_FLAMETHROWER: + { + fired = FireAreaEffect(shooter, source); + + break; + } + + case WEAPONTYPE_DETONATOR: + { + CWorld::UseDetonator(shooter); + m_nAmmoTotal = 1; + m_nAmmoInClip = m_nAmmoTotal; + fired = true; + + break; + } + + case WEAPONTYPE_HELICANNON: + { + if ( (TheCamera.PlayerWeaponMode.Mode == CCam::MODE_HELICANNON_1STPERSON || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON ) + && shooter == FindPlayerPed() ) + { + fired = FireM16_1stPerson(shooter); + } + else + fired = FireInstantHit(shooter, source); + + break; + } + + default: + { + debug("Unknown weapon type, Weapon.cpp"); + break; + } + } + + if (fired) + { + bool isPlayer = false; + + if (shooter->IsPed()) + { + CPed* shooterPed = (CPed*)shooter; + + shooterPed->bIsShooting = true; + + if (shooterPed->IsPlayer()) + isPlayer = true; + + DMAudio.PlayOneShot(shooterPed->m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + } + + if (m_nAmmoInClip > 0) m_nAmmoInClip--; + if (m_nAmmoTotal > 0 && (m_nAmmoTotal < 25000 || isPlayer)) m_nAmmoTotal--; + + if (m_eWeaponState == WEAPONSTATE_READY && m_eWeaponType == WEAPONTYPE_FLAMETHROWER) + DMAudio.PlayOneShot(((CPhysical*)shooter)->m_audioEntityId, SOUND_WEAPON_FLAMETHROWER_FIRE, 0.0f); + + m_eWeaponState = WEAPONSTATE_FIRING; + + if (m_nAmmoInClip == 0) + { + if (m_nAmmoTotal == 0) + return true; + + m_eWeaponState = WEAPONSTATE_RELOADING; + m_nTimer = CTimer::GetTimeInMilliseconds() + GetInfo()->m_nReload; + + if (shooter == FindPlayerPed()) + { + if (CWorld::Players[CWorld::PlayerInFocus].m_bFastReload) + m_nTimer = CTimer::GetTimeInMilliseconds() + GetInfo()->m_nReload / 4; + } + + return true; + } + + m_nTimer = CTimer::GetTimeInMilliseconds() + 1000; + if (shooter == FindPlayerPed()) + CStats::RoundsFiredByPlayer++; + } + } + else + { + if ( m_eWeaponState != WEAPONSTATE_FIRING ) + { + m_nTimer = CTimer::GetTimeInMilliseconds() + GetInfo()->m_nReload; + m_eWeaponState = WEAPONSTATE_FIRING; + } + + FireMelee(shooter, *source); + } + + if ( m_eWeaponType == WEAPONTYPE_UNARMED || m_eWeaponType == WEAPONTYPE_BASEBALLBAT ) + return true; + else + return fired; +} + +bool +CWeapon::FireFromCar(CAutomobile *shooter, bool left) +{ + ASSERT(shooter!=nil); + + if ( m_eWeaponState != WEAPONSTATE_READY && m_eWeaponState != WEAPONSTATE_FIRING ) + return false; + + if ( m_nAmmoInClip <= 0 ) + return false; + + if ( FireInstantHitFromCar(shooter, left) ) + { + DMAudio.PlayOneShot(shooter->m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + + if ( m_nAmmoInClip > 0 ) m_nAmmoInClip--; + if ( m_nAmmoTotal < 25000 && m_nAmmoTotal > 0 ) m_nAmmoTotal--; + + m_eWeaponState = WEAPONSTATE_FIRING; + + if ( m_nAmmoInClip == 0 ) + { + if ( m_nAmmoTotal == 0 ) + return true; + + m_eWeaponState = WEAPONSTATE_RELOADING; + m_nTimer = CTimer::GetTimeInMilliseconds() + GetInfo()->m_nReload; + + return true; + } + + m_nTimer = CTimer::GetTimeInMilliseconds() + 1000; + if ( shooter == FindPlayerVehicle() ) + CStats::RoundsFiredByPlayer++; + } + + return true; +} + +bool +CWeapon::FireMelee(CEntity *shooter, CVector &fireSource) +{ + ASSERT(shooter!=nil); + + CWeaponInfo *info = GetInfo(); + + bool anim2Playing = false; + if ( RpAnimBlendClumpGetAssociation(shooter->GetClump(), info->m_Anim2ToPlay) ) + anim2Playing = true; + + ASSERT(shooter->IsPed()); + + CPed *shooterPed = (CPed*)shooter; + + for ( int32 i = 0; i < shooterPed->m_numNearPeds; i++ ) + { + CPed *victimPed = shooterPed->m_nearPeds[i]; + ASSERT(victimPed!=nil); + + if ( (victimPed->m_nPedType != shooterPed->m_nPedType || victimPed == shooterPed->m_pSeekTarget) + && victimPed != shooterPed->m_leader || !(CGeneral::GetRandomNumber() & 31) ) + { + bool collided = false; + + CColModel *victimPedCol = &CTempColModels::ms_colModelPed1; + if ( victimPed->OnGround() || !victimPed->IsPedHeadAbovePos(-0.3f) ) + victimPedCol = &CTempColModels::ms_colModelPedGroundHit; + + + float victimPedRadius = victimPed->GetBoundRadius() + info->m_fRadius; + if ( victimPed->bUsesCollision || victimPed->Dead() || victimPed->Driving() ) + { + CVector victimPedPos = victimPed->GetPosition(); + if ( SQR(victimPedRadius) > (victimPedPos-fireSource).MagnitudeSqr() ) + { + CVector collisionDist; + + int32 s = 0; + while ( s < victimPedCol->numSpheres ) + { + CColSphere *sphere = &victimPedCol->spheres[s]; + collisionDist = victimPedPos+sphere->center-fireSource; + + if ( SQR(sphere->radius + info->m_fRadius) > collisionDist.MagnitudeSqr() ) + { + collided = true; + break; + } + s++; + } + + if ( !(victimPed->IsPlayer() && victimPed->GetPedState() == PED_GETUP) ) + { + if ( collided ) + { + float victimPedHealth = victimPed->m_fHealth; + CVector bloodPos = fireSource + (collisionDist*0.7f); + + CVector2D posOffset(shooterPed->GetPosition().x-victimPedPos.x, shooterPed->GetPosition().y-victimPedPos.y); + + int32 localDir = victimPed->GetLocalDirection(posOffset); + + bool isBat = m_eWeaponType == WEAPONTYPE_BASEBALLBAT; + + if ( !victimPed->DyingOrDead() ) + victimPed->ReactToAttack(shooterPed); + + uint8 hitLevel = HITLEVEL_HIGH; + if ( isBat && victimPed->OnGround() ) + hitLevel = HITLEVEL_GROUND; + + victimPed->StartFightDefend(localDir, hitLevel, 10); + + if ( !victimPed->DyingOrDead() ) + { + if ( shooterPed->IsPlayer() && isBat && anim2Playing ) + victimPed->InflictDamage(shooterPed, m_eWeaponType, 100.0f, PEDPIECE_TORSO, localDir); + else if ( shooterPed->IsPlayer() && ((CPlayerPed*)shooterPed)->m_bAdrenalineActive ) + victimPed->InflictDamage(shooterPed, m_eWeaponType, 3.5f*info->m_nDamage, PEDPIECE_TORSO, localDir); + else + { + if ( victimPed->IsPlayer() && isBat ) // wtf, it's not fair + victimPed->InflictDamage(shooterPed, m_eWeaponType, 2.0f*info->m_nDamage, PEDPIECE_TORSO, localDir); + else + victimPed->InflictDamage(shooterPed, m_eWeaponType, info->m_nDamage, PEDPIECE_TORSO, localDir); + } + } + + if ( CGame::nastyGame ) + { + if ( victimPed->GetIsOnScreen() ) + { + CVector dir = collisionDist * RecipSqrt(1.0f, 10.0f*collisionDist.MagnitudeSqr()); + + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir); + + if ( isBat ) + { + dir.x += CGeneral::GetRandomNumberInRange(-0.05f, 0.05f); + dir.y += CGeneral::GetRandomNumberInRange(-0.05f, 0.05f); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir); + + dir.x += CGeneral::GetRandomNumberInRange(-0.05f, 0.05f); + dir.y += CGeneral::GetRandomNumberInRange(-0.05f, 0.05f); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir); + } + } + } + + if ( !victimPed->OnGround() ) + { + if ( victimPed->m_fHealth > 0.0f + && (victimPed->m_fHealth < 20.0f && victimPedHealth > 20.0f || isBat && !victimPed->IsPlayer()) ) + { + posOffset.Normalise(); + victimPed->bIsStanding = false; + victimPed->ApplyMoveForce(posOffset.x*-5.0f, posOffset.y*-5.0f, 3.0f); + + if ( isBat && victimPed->IsPlayer() ) + victimPed->SetFall(3000, AnimationId(ANIM_STD_HIGHIMPACT_FRONT + localDir), false); + else + victimPed->SetFall(1500, AnimationId(ANIM_STD_HIGHIMPACT_FRONT + localDir), false); + + shooterPed->m_pSeekTarget = victimPed; + shooterPed->m_pSeekTarget->RegisterReference(&shooterPed->m_pSeekTarget); + } + } + else if (victimPed->Dying() && !anim2Playing) + { + posOffset.Normalise(); + victimPed->bIsStanding = false; + victimPed->ApplyMoveForce(posOffset.x*-5.0f, posOffset.y*-5.0f, 3.0f); + } + + m_eWeaponState = WEAPONSTATE_MELEE_MADECONTACT; + + if ( victimPed->m_nPedType == PEDTYPE_COP ) + CEventList::RegisterEvent(EVENT_ASSAULT_POLICE, EVENT_ENTITY_PED, victimPed, shooterPed, 2000); + else + CEventList::RegisterEvent(EVENT_ASSAULT, EVENT_ENTITY_PED, victimPed, shooterPed, 2000); + } + } + } + } + } + } + + return true; +} + +bool +CWeapon::FireInstantHit(CEntity *shooter, CVector *fireSource) +{ + ASSERT(shooter!=nil); + ASSERT(fireSource!=nil); + + CWeaponInfo *info = GetInfo(); + + CVector source, target; + CColPoint point; + CEntity *victim = nil; + + float heading = RADTODEG(shooter->GetForward().Heading()); + float angle = DEGTORAD(heading); + + CVector2D ahead(-Sin(angle), Cos(angle)); + ahead.Normalise(); + + CVector vel = ((CPed *)shooter)->m_vecMoveSpeed; + int32 shooterMoving = false; + if ( Abs(vel.x) > 0.0f && Abs(vel.y) > 0.0f ) + shooterMoving = true; + + if ( shooter == FindPlayerPed() ) + { + static float prev_heading = 0.0f; + prev_heading = ((CPed*)shooter)->m_fRotationCur; + } + + if ( shooter->IsPed() && ((CPed *)shooter)->m_pPointGunAt ) + { + CPed *shooterPed = (CPed *)shooter; + if ( shooterPed->m_pedIK.m_flags & CPedIK::GUN_POINTED_SUCCESSFULLY ) + { + int32 accuracy = shooterPed->m_wepAccuracy; + int32 inaccuracy = 100-accuracy; + + if ( accuracy != 100 ) + FindPlayerPed(); //what ? + + CPed *threatAttack = (CPed*)shooterPed->m_pPointGunAt; + if ( threatAttack->IsPed() ) + { + threatAttack->m_pedIK.GetComponentPosition(target, PED_MID); + threatAttack->ReactToPointGun(shooter); + } + else + target = threatAttack->GetPosition(); + + target -= *fireSource; + target *= info->m_fRange / target.Magnitude(); + target += *fireSource; + + if ( inaccuracy != 0 ) + { + target.x += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f) * inaccuracy; + target.y += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f) * inaccuracy; + target.z += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * inaccuracy; + } + + CWorld::bIncludeDeadPeds = true; + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + CWorld::bIncludeDeadPeds = false; + } + else + { + target.x = info->m_fRange; + target.y = 0.0f; + target.z = 0.0f; + + shooterPed->TransformToNode(target, PED_HANDR); + + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + } +#ifdef FIX_BUGS + // fix muzzleflash rotation + heading = CGeneral::GetAngleBetweenPoints(fireSource->x, fireSource->y, target.x, target.y); + angle = DEGTORAD(heading); + + ahead = CVector2D(-Sin(angle), Cos(angle)); + ahead.Normalise(); +#endif + } + else if ( shooter == FindPlayerPed() && TheCamera.Cams[0].Using3rdPersonMouseCam() ) + { + CVector src, trgt; +#ifdef FREE_CAM + if (CCamera::bFreeCam) { + CPlayerPed *shooterPed = (CPlayerPed*)shooter; + Find3rdPersonCamTargetVectorFromCachedVectors(info->m_fRange, *fireSource, src, trgt, shooterPed->m_cachedCamSource, shooterPed->m_cachedCamFront, shooterPed->m_cachedCamUp); + if ((shooterPed->m_pedIK.m_flags & CPedIK::GUN_POINTED_SUCCESSFULLY) == 0) { + trgt.x = info->m_fRange; + trgt.y = 0.0f; + trgt.z = 0.0f; + + shooterPed->TransformToNode(trgt, PED_HANDR); + } + } else +#endif + { + TheCamera.Find3rdPersonCamTargetVector(info->m_fRange, *fireSource, src, trgt); + } + +#ifdef FIX_BUGS + // fix muzzleflash rotation + heading = CGeneral::GetAngleBetweenPoints(src.x, src.y, trgt.x, trgt.y); + angle = DEGTORAD(heading); + + ahead = CVector2D(-Sin(angle), Cos(angle)); + ahead.Normalise(); +#endif + + CWorld::bIncludeDeadPeds = true; + ProcessLineOfSight(src, trgt,point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + CWorld::bIncludeDeadPeds = false; + + int32 rotSpeed = 1; + if ( m_eWeaponType == WEAPONTYPE_M16 ) + rotSpeed = 4; + + CVector bulletPos; + if ( CHeli::TestBulletCollision(&src, &trgt, &bulletPos, 4) ) + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, bulletPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, rotSpeed); + } + } + else + { + float shooterHeading = RADTODEG(shooter->GetForward().Heading()); + float shooterAngle = DEGTORAD(shooterHeading); + + CVector2D rotOffset(-Sin(shooterAngle), Cos(shooterAngle)); + rotOffset.Normalise(); + + target = *fireSource; + target.x += rotOffset.x * info->m_fRange; + target.y += rotOffset.y * info->m_fRange; + + if ( shooter->IsPed() ) + DoDoomAiming(shooter, fireSource, &target); + + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + + int32 rotSpeed = 1; + if ( m_eWeaponType == WEAPONTYPE_M16 ) + rotSpeed = 4; + + CVector bulletPos; + if ( CHeli::TestBulletCollision(fireSource, &target, &bulletPos, 4) ) + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, bulletPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, rotSpeed); + } + } + + if ( victim && shooter->IsPed() && victim == ((CPed*)shooter)->m_leader ) + return false; + + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, shooter, (CPed *)shooter, 1000); + + if ( shooter == FindPlayerPed() ) + { + CStats::InstantHitsFiredByPlayer++; + if ( !(CTimer::GetFrameCounter() & 3) ) + MakePedsJumpAtShot((CPhysical*)shooter, fireSource, &target); + } + + switch ( m_eWeaponType ) + { + case WEAPONTYPE_AK47: + { + static uint8 counter = 0; + + if ( !(++counter & 1) ) + { + CPointLights::AddLight(CPointLights::LIGHT_POINT, + *fireSource, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CVector gunflashPos = *fireSource; + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.10f); + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.08f); + gunflashPos += CVector(0.05f*ahead.x, 0.05f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.06f); + gunflashPos += CVector(0.04f*ahead.x, 0.04f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + + CVector gunsmokePos = *fireSource; + float rnd = CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*rnd, ahead.y*rnd, 0.0f)); + + CVector gunshellPos = *fireSource; + gunshellPos -= CVector(0.5f*ahead.x, 0.5f*ahead.y, 0.0f); + CVector dir = CrossProduct(CVector(ahead.x, ahead.y, 0.0f), CVector(0.0f, 0.0f, 5.0f)); + dir.Normalise2D(); + AddGunshell(shooter, gunshellPos, CVector2D(dir.x, dir.y), 0.018f); + } + + break; + } + + case WEAPONTYPE_M16: + { + static uint8 counter = 0; + + if ( !(++counter & 1) ) + { + CPointLights::AddLight(CPointLights::LIGHT_POINT, + *fireSource, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CVector gunflashPos = *fireSource; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.08f); + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.06f); + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.06f); + + gunflashPos = *fireSource; + gunflashPos += CVector(-0.1f*ahead.x, -0.1f*ahead.y, 0.0f); + gunflashPos.z += 0.04f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos.z += 0.04f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + gunflashPos.z += 0.03f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + + gunflashPos = *fireSource; + gunflashPos += CVector(-0.1f*ahead.x, -0.1f*ahead.y, 0.0f); + gunflashPos.z -= 0.04f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos.z -= 0.04f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + gunflashPos.z -= 0.03f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + + CVector offset = CrossProduct(CVector(ahead.x, ahead.y, 0.0f), CVector(0.0f, 0.0f, 5.0f)); + offset.Normalise2D(); + + gunflashPos = *fireSource; + gunflashPos += CVector(-0.1f*ahead.x, -0.1f*ahead.y, 0.0f); + gunflashPos += CVector(0.06f*offset.x, 0.06f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos += CVector(0.04f*offset.x, 0.04f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.03f); + gunflashPos += CVector(0.03f*offset.x, 0.03f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + + gunflashPos = *fireSource; + gunflashPos += CVector(-0.1f*ahead.x, -0.1f*ahead.y, 0.0f); + gunflashPos -= CVector(0.06f*offset.x, 0.06f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos -= CVector(0.04f*offset.x, 0.04f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.03f); + gunflashPos -= CVector(0.03f*offset.x, 0.03f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + + CVector gunsmokePos = *fireSource; + float rnd = CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*rnd, ahead.y*rnd, 0.0f)); + + CVector gunshellPos = *fireSource; + gunshellPos -= CVector(0.65f*ahead.x, 0.65f*ahead.y, 0.0f); + CVector dir = CrossProduct(CVector(ahead.x, ahead.y, 0.0f), CVector(0.0f, 0.0f, 5.0f)); + dir.Normalise2D(); + AddGunshell(shooter, gunshellPos, CVector2D(dir.x, dir.y), 0.02f); + } + + break; + } + + case WEAPONTYPE_UZI: + { + CPointLights::AddLight(CPointLights::LIGHT_POINT, + *fireSource, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CVector gunflashPos = *fireSource; + + if ( shooterMoving ) + gunflashPos += CVector(1.5f*vel.x, 1.5f*vel.y, 0.0f); + + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.07f); + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.05f); + gunflashPos += CVector(0.04f*ahead.x, 0.04f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos += CVector(0.04f*ahead.x, 0.04f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.03f); + gunflashPos += CVector(0.03f*ahead.x, 0.03f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.03f); + gunflashPos += CVector(0.03f*ahead.x, 0.03f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + gunflashPos += CVector(0.02f*ahead.x, 0.02f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.01f); + + CVector gunsmokePos = *fireSource; + float rnd = CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*rnd, ahead.y*rnd, 0.0f)); + + CVector gunshellPos = *fireSource; + gunshellPos -= CVector(0.2f*ahead.x, 0.2f*ahead.y, 0.0f); + CVector dir = CrossProduct(CVector(ahead.x, ahead.y, 0.0f), CVector(0.0f, 0.0f, 5.0f)); + dir.Normalise2D(); + AddGunshell(shooter, gunshellPos, CVector2D(dir.x, dir.y), 0.015f); + + break; + } + + case WEAPONTYPE_COLT45: + { + CPointLights::AddLight(CPointLights::LIGHT_POINT, + *fireSource, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CVector gunflashPos = *fireSource; + + if ( shooterMoving ) + gunflashPos += CVector(1.5f*vel.x, 1.5f*vel.y, 0.0f); + + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.06f); + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos += CVector(0.04f*ahead.x, 0.04f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + + CVector gunsmokePos = *fireSource; + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*0.10f, ahead.y*0.10f, 0.0f), nil, 0.005f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*0.15f, ahead.y*0.15f, 0.0f), nil, 0.015f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*0.20f, ahead.y*0.20f, 0.0f), nil, 0.025f); + + CVector gunshellPos = *fireSource; + gunshellPos -= CVector(0.2f*ahead.x, 0.2f*ahead.y, 0.0f); + CVector dir = CrossProduct(CVector(ahead.x, ahead.y, 0.0f), CVector(0.0f, 0.0f, 5.0f)); + dir.Normalise2D(); + AddGunshell(shooter, gunshellPos, CVector2D(dir.x, dir.y), 0.015f); + + break; + } + default: break; + } + + DoBulletImpact(shooter, victim, fireSource, &target, &point, ahead); + + return true; +} + +void +CWeapon::AddGunshell(CEntity *shooter, CVector const &source, CVector2D const &direction, float size) +{ + ASSERT(shooter!=nil); + + if ( shooter == nil) + return; + + CVector dir(direction.x*0.05f, direction.y*0.05f, CGeneral::GetRandomNumberInRange(0.02f, 0.08f)); + + static CVector prevEntityPosition(0.0f, 0.0f, 0.0f); + CVector entityPosition = shooter->GetPosition(); + + CVector diff = entityPosition - prevEntityPosition; + + if ( Abs(diff.x)+Abs(diff.y)+Abs(diff.z) > 1.5f ) + { + prevEntityPosition = entityPosition; + + CParticle::AddParticle(PARTICLE_GUNSHELL_FIRST, + source, dir, nil, size, CGeneral::GetRandomNumberInRange(-20.0f, 20.0f)); + } + else + { + CParticle::AddParticle(PARTICLE_GUNSHELL, + source, dir, nil, size, CGeneral::GetRandomNumberInRange(-20.0f, 20.0f)); + } +} + +void +CWeapon::DoBulletImpact(CEntity *shooter, CEntity *victim, + CVector *source, CVector *target, CColPoint *point, CVector2D ahead) +{ + ASSERT(shooter!=nil); + ASSERT(source!=nil); + ASSERT(target!=nil); + ASSERT(point!=nil); + + CWeaponInfo *info = GetInfo(); + + if ( victim ) + { + CGlass::WasGlassHitByBullet(victim, point->point); + + CVector traceTarget = point->point; + CBulletTraces::AddTrace(source, &traceTarget); + + if ( shooter != nil ) + { + if ( shooter == FindPlayerPed() ) + { + if ( victim->IsPed() || victim->IsVehicle() ) + CStats::InstantHitsHitByPlayer++; + } + } + + if ( victim->IsPed() && ((CPed*)shooter)->m_nPedType != ((CPed*)victim)->m_nPedType || ((CPed*)shooter)->m_nPedType == PEDTYPE_PLAYER2 ) + { + CPed *victimPed = (CPed *)victim; + if ( !victimPed->DyingOrDead() && victim != shooter ) + { + if ( victimPed->DoesLOSBulletHitPed(*point) ) + { + CVector pos = victimPed->GetPosition(); + + CVector2D posOffset(source->x-pos.x, source->y-pos.y); + int32 localDir = victimPed->GetLocalDirection(posOffset); + + victimPed->ReactToAttack(shooter); + + if ( !victimPed->IsPedInControl() || victimPed->bIsDucking ) + { + victimPed->InflictDamage(shooter, m_eWeaponType, info->m_nDamage, (ePedPieceTypes)point->pieceB, localDir); + } + else + { + if ( m_eWeaponType == WEAPONTYPE_SHOTGUN || m_eWeaponType == WEAPONTYPE_HELICANNON ) + { + posOffset.Normalise(); + victimPed->bIsStanding = false; + + victimPed->ApplyMoveForce(posOffset.x*-5.0f, posOffset.y*-5.0f, 5.0f); + victimPed->SetFall(1500, AnimationId(ANIM_STD_HIGHIMPACT_FRONT + localDir), false); + + victimPed->InflictDamage(shooter, m_eWeaponType, info->m_nDamage, (ePedPieceTypes)point->pieceB, localDir); + } + else + { + if ( victimPed->IsPlayer() ) + { + CPlayerPed *victimPlayer = (CPlayerPed *)victimPed; + if ( victimPlayer->m_nHitAnimDelayTimer < CTimer::GetTimeInMilliseconds() ) + { + victimPed->ClearAttackByRemovingAnim(); + + CAnimBlendAssociation *asoc = CAnimManager::AddAnimation(victimPed->GetClump(), ASSOCGRP_STD, AnimationId(ANIM_STD_HITBYGUN_FRONT + localDir)); + ASSERT(asoc!=nil); + + asoc->blendAmount = 0.0f; + asoc->blendDelta = 8.0f; + + if ( m_eWeaponType == WEAPONTYPE_AK47 || m_eWeaponType == WEAPONTYPE_M16 ) + victimPlayer->m_nHitAnimDelayTimer = CTimer::GetTimeInMilliseconds() + 2500; + else + victimPlayer->m_nHitAnimDelayTimer = CTimer::GetTimeInMilliseconds() + 1000; + } + } + else + { + victimPed->ClearAttackByRemovingAnim(); + + CAnimBlendAssociation *asoc = CAnimManager::AddAnimation(victimPed->GetClump(), ASSOCGRP_STD, AnimationId(ANIM_STD_HITBYGUN_FRONT + localDir)); + ASSERT(asoc!=nil); + + asoc->blendAmount = 0.0f; + asoc->blendDelta = 8.0f; + } + + victimPed->InflictDamage(shooter, m_eWeaponType, info->m_nDamage, (ePedPieceTypes)point->pieceB, localDir); + } + } + + if ( victimPed->m_nPedType == PEDTYPE_COP ) + CEventList::RegisterEvent(EVENT_SHOOT_COP, EVENT_ENTITY_PED, victim, (CPed*)shooter, 10000); + else + CEventList::RegisterEvent(EVENT_SHOOT_PED, EVENT_ENTITY_PED, victim, (CPed*)shooter, 10000); + + if ( CGame::nastyGame ) + { + uint8 bloodAmount = 8; + if ( m_eWeaponType == WEAPONTYPE_SHOTGUN || m_eWeaponType == WEAPONTYPE_HELICANNON ) + bloodAmount = 32; + + CVector dir = (point->point - victim->GetPosition()) * 0.01f; + dir.z = 0.01f; + + if ( victimPed->GetIsOnScreen() ) + { + for ( uint8 i = 0; i < bloodAmount; i++ ) + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, point->point, dir); + } + } + } + } + else + { + if ( CGame::nastyGame ) + { + CVector dir = (point->point - victim->GetPosition()) * 0.01f; + dir.z = 0.01f; + + if ( victim->GetIsOnScreen() ) + { + for ( int32 i = 0; i < 8; i++ ) + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, point->point + CVector(0.0f, 0.0f, 0.15f), dir); + } + + if ( victimPed->Dead() ) + { + CAnimBlendAssociation *asoc; + if ( RpAnimBlendClumpGetFirstAssociation(victimPed->GetClump(), ASSOC_FRONTAL) ) + asoc = CAnimManager::BlendAnimation(victimPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR_FRONT, 8.0f); + else + asoc = CAnimManager::BlendAnimation(victimPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR, 8.0f); + + if ( asoc ) + { + asoc->SetCurrentTime(0.0f); + asoc->flags |= ASSOC_RUNNING; + asoc->flags &= ~ASSOC_FADEOUTWHENDONE; + } + } + } + } + } + else + { + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, point->point, point->normal*0.05f); + +#ifndef FIX_BUGS + CVector dist = point->point - (*source); + CVector offset = dist - Max(0.2f * dist.Magnitude(), 2.0f) * CVector(ahead.x, ahead.y, 0.0f); + CVector smokePos = *source + offset; +#else + CVector smokePos = point->point; +#endif // !FIX_BUGS + + smokePos.x += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + smokePos.y += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + smokePos.z += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, smokePos, CVector(0.0f, 0.0f, 0.0f)); + + break; + } + case ENTITY_TYPE_VEHICLE: + { + ((CVehicle *)victim)->InflictDamage(shooter, m_eWeaponType, info->m_nDamage); + + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, point->point, point->normal*0.05f); + +#ifndef FIX_BUGS + CVector dist = point->point - (*source); + CVector offset = dist - Max(0.2f*dist.Magnitude(), 0.5f) * CVector(ahead.x, ahead.y, 0.0f); + CVector smokePos = *source + offset; +#else + CVector smokePos = point->point; +#endif // !FIX_BUGS + + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, smokePos, CVector(0.0f, 0.0f, 0.0f)); + + if ( shooter->IsPed() ) + { + CPed *shooterPed = (CPed *)shooter; + + if ( shooterPed->bNotAllowedToDuck ) + { + if ( shooterPed->bKindaStayInSamePlace && victim != shooterPed->m_pPointGunAt ) + { + shooterPed->bKindaStayInSamePlace = false; + shooterPed->m_duckAndCoverTimer = CTimer::GetTimeInMilliseconds() + 15000; + } + } + } + + break; + } + case ENTITY_TYPE_OBJECT: + { + for ( int32 i = 0; i < 8; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, point->point, point->normal*0.05f); + + CObject *victimObject = (CObject *)victim; + + if ( !victimObject->bInfiniteMass ) + { + if ( victimObject->GetIsStatic() && victimObject->m_fUprootLimit <= 0.0f ) + { + victimObject->SetIsStatic(false); + victimObject->AddToMovingList(); + } + + if ( !victimObject->GetIsStatic()) + { + CVector moveForce = point->normal*-4.0f; + victimObject->ApplyMoveForce(moveForce.x, moveForce.y, moveForce.z); + } + } + + break; + } + default: break; + } + } + + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_1, point->point); + break; + } + case ENTITY_TYPE_VEHICLE: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_2, point->point); + break; + } + case ENTITY_TYPE_DUMMY: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_3, point->point); + break; + } + default: break; + } + } + else + CBulletTraces::AddTrace(source, target); + + if ( shooter == FindPlayerPed() ) + CPad::GetPad(0)->StartShake_Distance(240, 128, FindPlayerPed()->GetPosition().x, FindPlayerPed()->GetPosition().y, FindPlayerPed()->GetPosition().z); + + BlowUpExplosiveThings(victim); +} + +bool +CWeapon::FireShotgun(CEntity *shooter, CVector *fireSource) +{ + ASSERT(shooter!=nil); + ASSERT(fireSource!=nil); + + CWeaponInfo *info = GetInfo(); + + float heading = RADTODEG(shooter->GetForward().Heading()); + float angle = DEGTORAD(heading); + + CVector2D rotOffset(-Sin(angle), Cos(angle)); + rotOffset.Normalise(); + + CVector gunflashPos = *fireSource; + gunflashPos += CVector(rotOffset.x*0.1f, rotOffset.y*0.1f, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f); + gunflashPos += CVector(rotOffset.x*0.1f, rotOffset.y*0.1f, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.15f); + gunflashPos += CVector(rotOffset.x*0.1f, rotOffset.y*0.1f, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.2f); + CParticle::AddParticle(PARTICLE_GUNFLASH, *fireSource, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f); + + CVector gunsmokePos = *fireSource; + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(rotOffset.x*0.10f, rotOffset.y*0.10f, 0.0f), nil, 0.1f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(rotOffset.x*0.15f, rotOffset.y*0.15f, 0.0f), nil, 0.1f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(rotOffset.x*0.20f, rotOffset.y*0.20f, 0.0f), nil, 0.1f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(rotOffset.x*0.25f, rotOffset.y*0.25f, 0.0f), nil, 0.1f); + + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, shooter, (CPed*)shooter, 1000); + + CPointLights::AddLight(CPointLights::LIGHT_POINT, *fireSource, CVector(0.0, 0.0, 0.0), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + float shooterAngle; + + if ( shooter->IsPed() && ((CPed*)shooter)->m_pPointGunAt != nil ) + { + CEntity *threatAttack = ((CPed*)shooter)->m_pPointGunAt; + shooterAngle = CGeneral::GetAngleBetweenPoints(threatAttack->GetPosition().x, threatAttack->GetPosition().y, + (*fireSource).x, (*fireSource).y); + } + else + shooterAngle = RADTODEG(shooter->GetForward().Heading()); + + + for ( int32 i = 0; i < 5; i++ ) // five shoots at once + { + float shootAngle = DEGTORAD(7.5f*i + shooterAngle - 15.0f); + CVector2D shootRot(-Sin(shootAngle), Cos(shootAngle)); + shootRot.Normalise(); + + CVector source, target; + CColPoint point; + CEntity *victim; + + if ( shooter == FindPlayerPed() && TheCamera.Cams[0].Using3rdPersonMouseCam() ) + { + CVector Left; +#ifdef FREE_CAM + if (CCamera::bFreeCam) { + CPlayerPed* shooterPed = (CPlayerPed*)shooter; + Find3rdPersonCamTargetVectorFromCachedVectors(1.0f, *fireSource, source, target, shooterPed->m_cachedCamSource, shooterPed->m_cachedCamFront, shooterPed->m_cachedCamUp); + Left = CrossProduct(shooterPed->m_cachedCamFront, shooterPed->m_cachedCamUp); + } + else +#endif + { + TheCamera.Find3rdPersonCamTargetVector(1.0f, *fireSource, source, target); + Left = CrossProduct(TheCamera.Cams[TheCamera.ActiveCam].Front, TheCamera.Cams[TheCamera.ActiveCam].Up); + } + + float f = float(i - 2) * (DEGTORAD(7.5f) / 2); + target = f * Left + target - source; + target *= info->m_fRange; + target += source; + + ProcessLineOfSight(source, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + } + else + { + target = *fireSource; + target.x += shootRot.x * info->m_fRange; + target.y += shootRot.y * info->m_fRange; + + if ( shooter->IsPed() ) + { + CPed *shooterPed = (CPed *)shooter; + + if ( shooterPed->m_pPointGunAt == nil ) + DoDoomAiming(shooter, fireSource, &target); + else + { + float distToTarget = (shooterPed->m_pPointGunAt->GetPosition() - (*fireSource)).Magnitude2D(); + target.z += info->m_fRange / distToTarget * (shooterPed->m_pPointGunAt->GetPosition().z - target.z); + } + } + + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + } + + if ( victim ) + { + CGlass::WasGlassHitByBullet(victim, point.point); + + CBulletTraces::AddTrace(fireSource, &point.point); + + if ( victim->IsPed() ) + { + CPed *victimPed = (CPed *)victim; + if ( !victimPed->OnGround() && victim != shooter && victimPed->DoesLOSBulletHitPed(point) ) + { + bool cantStandup = true; + + CVector pos = victimPed->GetPosition(); + + CVector2D posOffset((*fireSource).x-pos.x, (*fireSource).y-pos.y); + int32 localDir = victimPed->GetLocalDirection(posOffset); + + victimPed->ReactToAttack(FindPlayerPed()); + + posOffset.Normalise(); + + if ( victimPed->m_getUpTimer > (CTimer::GetTimeInMilliseconds() - 3000) ) + cantStandup = false; + + if ( victimPed->bIsStanding && cantStandup ) + { + victimPed->bIsStanding = false; + + victimPed->ApplyMoveForce(posOffset.x*-6.0f, posOffset.y*-6.0f, 5.0f); + } + else + victimPed->ApplyMoveForce(posOffset.x*-2.0f, posOffset.y*-2.0f, 0.0f); + + if ( cantStandup ) + victimPed->SetFall(1500, AnimationId(ANIM_STD_HIGHIMPACT_FRONT + localDir), false); + + victimPed->InflictDamage(shooter, m_eWeaponType, info->m_nDamage, (ePedPieceTypes)point.pieceB, localDir); + + if ( victimPed->m_nPedType == PEDTYPE_COP ) + CEventList::RegisterEvent(EVENT_SHOOT_COP, EVENT_ENTITY_PED, victim, (CPed*)shooter, 10000); + else + CEventList::RegisterEvent(EVENT_SHOOT_PED, EVENT_ENTITY_PED, victim, (CPed*)shooter, 10000); + + if ( CGame::nastyGame ) + { + uint8 bloodAmount = 8; + if ( m_eWeaponType == WEAPONTYPE_SHOTGUN ) + bloodAmount = 32; + + CVector dir = (point.point - victim->GetPosition()) * 0.01f; + dir.z = 0.01f; + + if ( victimPed->GetIsOnScreen() ) + { + for ( uint8 i = 0; i < bloodAmount; i++ ) + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, point.point, dir); + } + } + } + } + else + { + switch ( victim->GetType() ) + { + case ENTITY_TYPE_VEHICLE: + { + ((CVehicle *)victim)->InflictDamage(shooter, m_eWeaponType, info->m_nDamage); + + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, point.point, point.normal*0.05f); + +#ifndef FIX_BUGS + CVector dist = point.point - (*fireSource); + CVector offset = dist - Max(0.2f*dist.Magnitude(), 2.0f) * CVector(shootRot.x, shootRot.y, 0.0f); + CVector smokePos = *fireSource + offset; +#else + CVector smokePos = point.point; +#endif + + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, smokePos, CVector(0.0f, 0.0f, 0.0f)); + + break; + } + + case ENTITY_TYPE_BUILDING: + case ENTITY_TYPE_OBJECT: + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, point.point, point.normal*0.05f); + +#ifndef FIX_BUGS + CVector dist = point.point - (*fireSource); + CVector offset = dist - Max(0.2f*dist.Magnitude(), 2.0f) * CVector(shootRot.x, shootRot.y, 0.0f); + CVector smokePos = *fireSource + offset; +#else + CVector smokePos = point.point; +#endif + + smokePos.x += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + smokePos.y += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + smokePos.z += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, smokePos, CVector(0.0f, 0.0f, 0.0f)); + + if ( victim->IsObject() ) + { + CObject *victimObject = (CObject *)victim; + + if ( !victimObject->bInfiniteMass ) + { + if ( victimObject->GetIsStatic() && victimObject->m_fUprootLimit <= 0.0f ) + { + victimObject->SetIsStatic(false); + victimObject->AddToMovingList(); + } + + if ( !victimObject->GetIsStatic()) + { + CVector moveForce = point.normal*-5.0f; + victimObject->ApplyMoveForce(moveForce.x, moveForce.y, moveForce.z); + } + } + } + + break; + } + default: break; + } + } + + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_1, point.point); + break; + } + case ENTITY_TYPE_VEHICLE: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_2, point.point); + break; + } + case ENTITY_TYPE_DUMMY: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_3, point.point); + break; + } + default: break; + } + } + else + { + CVector traceTarget = *fireSource; + traceTarget += (target - (*fireSource)) * Min(info->m_fRange, 30.0f) / info->m_fRange; + CBulletTraces::AddTrace(fireSource, &traceTarget); + } + } + + if ( shooter == FindPlayerPed() ) + CPad::GetPad(0)->StartShake_Distance(240, 128, FindPlayerPed()->GetPosition().x, FindPlayerPed()->GetPosition().y, FindPlayerPed()->GetPosition().z); + + return true; +} + +bool +CWeapon::FireProjectile(CEntity *shooter, CVector *fireSource, float power) +{ + ASSERT(shooter!=nil); + ASSERT(fireSource!=nil); + + CVector source, target; + + if ( m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER ) + { + source = *fireSource; + + if ( shooter->IsPed() && ((CPed*)shooter)->IsPlayer() ) + { + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if (!( mode == CCam::MODE_M16_1STPERSON + || mode == CCam::MODE_SNIPER + || mode == CCam::MODE_ROCKETLAUNCHER + || mode == CCam::MODE_M16_1STPERSON_RUNABOUT + || mode == CCam::MODE_SNIPER_RUNABOUT + || mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT) ) + { + return false; + } + + *fireSource += TheCamera.Cams[TheCamera.ActiveCam].Front; + } + else + *fireSource += shooter->GetForward(); + + target = *fireSource; + } + else + { + float dot = DotProduct(*fireSource-shooter->GetPosition(), shooter->GetForward()); + + if ( dot < 0.3f ) + *fireSource += (0.3f-dot) * shooter->GetForward(); + + target = *fireSource; + + if ( target.z - shooter->GetPosition().z > 0.0f ) + target += 0.6f*shooter->GetForward(); + + source = *fireSource - shooter->GetPosition(); + + source = *fireSource - DotProduct(source, shooter->GetForward()) * shooter->GetForward(); + } + + if ( !CWorld::GetIsLineOfSightClear(source, target, true, true, false, true, false, false, false) ) + { + if ( m_eWeaponType != WEAPONTYPE_GRENADE ) + CProjectileInfo::RemoveNotAdd(shooter, m_eWeaponType, *fireSource); + else + { + if ( shooter->IsPed() ) + { + source = shooter->GetPosition() - shooter->GetForward(); + source.z -= 0.4f; + + if ( !CWorld::TestSphereAgainstWorld(source, 0.5f, nil, false, false, true, false, false, false) ) + CProjectileInfo::AddProjectile(shooter, m_eWeaponType, source, 0.0f); + else + CProjectileInfo::RemoveNotAdd(shooter, m_eWeaponType, *fireSource); + } + } + } + else + CProjectileInfo::AddProjectile(shooter, m_eWeaponType, *fireSource, power); + + return true; +} + +void +CWeapon::GenerateFlameThrowerParticles(CVector pos, CVector dir) +{ + dir *= 0.7f; + CParticle::AddParticle(PARTICLE_FIREBALL, pos, dir); + + dir *= 0.7f; + CParticle::AddParticle(PARTICLE_FIREBALL, pos, dir); + + dir *= 0.7f; + CParticle::AddParticle(PARTICLE_FIREBALL, pos, dir); + + dir *= 0.7f; + CParticle::AddParticle(PARTICLE_FIREBALL, pos, dir); + + dir *= 0.7f; + CParticle::AddParticle(PARTICLE_FIREBALL, pos, dir); +} + +bool +CWeapon::FireAreaEffect(CEntity *shooter, CVector *fireSource) +{ + ASSERT(shooter!=nil); + ASSERT(fireSource!=nil); + + CWeaponInfo *info = GetInfo(); + + float heading = RADTODEG(shooter->GetForward().Heading()); + + CVector source; + CVector target; + CVector dir; + + if ( shooter == FindPlayerPed() && TheCamera.Cams[0].Using3rdPersonMouseCam() ) + { +#ifdef FREE_CAM + if (CCamera::bFreeCam) { + CPlayerPed *shooterPed = (CPlayerPed*)shooter; + Find3rdPersonCamTargetVectorFromCachedVectors(info->m_fRange, *fireSource, source, target, shooterPed->m_cachedCamSource, shooterPed->m_cachedCamFront, shooterPed->m_cachedCamUp); + } + else +#endif + { + TheCamera.Find3rdPersonCamTargetVector(info->m_fRange, *fireSource, source, target); + } + float norm = (1.0f / info->m_fRange); + dir = (target - source) * norm; + } + else + { + float angle = DEGTORAD(heading); + dir = CVector(-Sin(angle)*0.5f, Cos(angle)*0.5f, 0.0f); + target = *fireSource + dir; + } + + CShotInfo::AddShot(shooter, m_eWeaponType, *fireSource, target); + CWeapon::GenerateFlameThrowerParticles(*fireSource, dir); + + return true; +} + +bool +CWeapon::FireSniper(CEntity *shooter) +{ + ASSERT(shooter!=nil); + + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if (!( mode == CCam::MODE_M16_1STPERSON + || mode == CCam::MODE_SNIPER + || mode == CCam::MODE_ROCKETLAUNCHER + || mode == CCam::MODE_M16_1STPERSON_RUNABOUT + || mode == CCam::MODE_SNIPER_RUNABOUT + || mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT) ) + { + return false; + } + +#ifndef FIX_BUGS + CWeaponInfo *info = GetInfo(); //unused +#endif + + CCam *cam = &TheCamera.Cams[TheCamera.ActiveCam]; + ASSERT(cam!=nil); + + CVector source = cam->Source; + CVector dir = cam->Front; + + if ( DotProduct(dir, CVector(0.0f, -0.9894f, 0.145f)) > 0.997f ) + CCoronas::bSmallMoon = !CCoronas::bSmallMoon; + + dir.Normalise(); + dir *= 16.0f; + + CBulletInfo::AddBullet(shooter, m_eWeaponType, source, dir); + + if ( shooter == FindPlayerPed() ) + CStats::InstantHitsFiredByPlayer++; + + if ( shooter == FindPlayerPed() ) + { + CPad::GetPad(0)->StartShake_Distance(240, 128, + FindPlayerPed()->GetPosition().x, + FindPlayerPed()->GetPosition().y, + FindPlayerPed()->GetPosition().z); + + CamShakeNoPos(&TheCamera, 0.2f); + } + + return true; +} + +bool +CWeapon::FireM16_1stPerson(CEntity *shooter) +{ + ASSERT(shooter!=nil); + + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + + if (!( mode == CCam::MODE_M16_1STPERSON + || mode == CCam::MODE_SNIPER + || mode == CCam::MODE_ROCKETLAUNCHER + || mode == CCam::MODE_M16_1STPERSON_RUNABOUT + || mode == CCam::MODE_SNIPER_RUNABOUT + || mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT + || mode == CCam::MODE_HELICANNON_1STPERSON) ) + { + return false; + } + + CWeaponInfo *info = GetInfo(); + + CColPoint point; + CEntity *victim; + + CWorld::bIncludeCarTyres = true; + CWorld::pIgnoreEntity = shooter; + CWorld::bIncludeDeadPeds = true; + + CCam *cam = &TheCamera.Cams[TheCamera.ActiveCam]; + ASSERT(cam!=nil); + + CVector source = cam->Source; + CVector target = cam->Front*info->m_fRange + source; + + ProcessLineOfSight(source, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + CWorld::bIncludeDeadPeds = false; + CWorld::pIgnoreEntity = nil; + CWorld::bIncludeCarTyres = false; + + CVector2D front(cam->Front.x, cam->Front.y); + front.Normalise(); + + DoBulletImpact(shooter, victim, &source, &target, &point, front); + + CVector bulletPos; + if ( CHeli::TestBulletCollision(&source, &target, &bulletPos, 4) ) + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, bulletPos, CVector(0.0f, 0.0f, 0.0f)); + } + + if ( shooter == FindPlayerPed() ) + { +#ifdef FIX_BUGS + CStats::InstantHitsFiredByPlayer++; +#endif + CPad::GetPad(0)->StartShake_Distance(240, 128, FindPlayerPed()->GetPosition().x, FindPlayerPed()->GetPosition().y, FindPlayerPed()->GetPosition().z); + + if ( m_eWeaponType == WEAPONTYPE_M16 ) + { + TheCamera.Cams[TheCamera.ActiveCam].Beta += float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0003f; + TheCamera.Cams[TheCamera.ActiveCam].Alpha += float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0003f; + } + else if ( m_eWeaponType == WEAPONTYPE_HELICANNON ) + { + TheCamera.Cams[TheCamera.ActiveCam].Beta += float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0001f; + TheCamera.Cams[TheCamera.ActiveCam].Alpha += float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0001f; + } + } + + return true; +} + +bool +CWeapon::FireInstantHitFromCar(CAutomobile *shooter, bool left) +{ + CWeaponInfo *info = GetInfo(); + + CVehicleModelInfo *modelInfo = shooter->GetModelInfo(); + + CVector source, target; + if ( left ) + { + source = shooter->GetMatrix() * CVector(-shooter->GetColModel()->boundingBox.max.x + -0.2f, + float(CGeneral::GetRandomNumber() & 255) * 0.001f + modelInfo->GetFrontSeatPosn().y, + modelInfo->GetFrontSeatPosn().z + 0.5f); + source += CTimer::GetTimeStep() * shooter->m_vecMoveSpeed; + + + target = shooter->GetMatrix() * CVector(-info->m_fRange, + modelInfo->GetFrontSeatPosn().y, + modelInfo->GetFrontSeatPosn().z + 0.5f); + } + else + { + source = shooter->GetMatrix() * CVector(shooter->GetColModel()->boundingBox.max.x + 0.2f, + float(CGeneral::GetRandomNumber() & 255) * 0.001f + modelInfo->GetFrontSeatPosn().y, + modelInfo->GetFrontSeatPosn().z + 0.5f); + source += CTimer::GetTimeStep() * shooter->m_vecMoveSpeed; + + target = shooter->GetMatrix() * CVector(info->m_fRange, + modelInfo->GetFrontSeatPosn().y, + modelInfo->GetFrontSeatPosn().z + 0.5f); + } + #undef FRONTSEATPOS + + if ( TheCamera.GetLookingLRBFirstPerson() && !left ) + { + source -= 0.3f * shooter->GetForward(); + target -= 0.3f * shooter->GetForward(); + } + + target += CVector(float(CGeneral::GetRandomNumber()&255)*0.01f-1.28f, + float(CGeneral::GetRandomNumber()&255)*0.01f-1.28f, + float(CGeneral::GetRandomNumber()&255)*0.01f-1.28f); + + DoDriveByAutoAiming(FindPlayerPed(), &source, &target); + + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, FindPlayerPed(), FindPlayerPed(), 1000); + + if ( !TheCamera.GetLookingLRBFirstPerson() ) + CParticle::AddParticle(PARTICLE_GUNFLASH, source, CVector(0.0f, 0.0f, 0.0f)); + else + CamShakeNoPos(&TheCamera, 0.01f); + + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_VEHICLE, shooter, FindPlayerPed(), 1000); + + CPointLights::AddLight(CPointLights::LIGHT_POINT, source, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CColPoint point; + CEntity *victim; + ProcessLineOfSight(source, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + + if ( !(CTimer::GetFrameCounter() & 3) ) + MakePedsJumpAtShot(shooter, &source, &target); + + if ( victim ) + { + CVector traceTarget = point.point; + CBulletTraces::AddTrace(&source, &traceTarget); + + if ( victim->IsPed() ) + { + CPed *victimPed = (CPed*)victim; + + if ( !victimPed->DyingOrDead() && victim != (CEntity *)shooter ) + { + CVector pos = victimPed->GetPosition(); + + CVector2D posOffset(source.x-pos.x, source.y-pos.y); + int32 localDir = victimPed->GetLocalDirection(posOffset); + + victimPed->ReactToAttack(FindPlayerPed()); + victimPed->ClearAttackByRemovingAnim(); + + CAnimBlendAssociation *asoc = CAnimManager::AddAnimation(victimPed->GetClump(), ASSOCGRP_STD, AnimationId(ANIM_STD_HITBYGUN_FRONT + localDir)); + ASSERT(asoc!=nil); + asoc->blendAmount = 0.0f; + asoc->blendDelta = 8.0f; + + victimPed->InflictDamage(shooter, WEAPONTYPE_UZI_DRIVEBY, 3*info->m_nDamage, (ePedPieceTypes)point.pieceB, localDir); + + pos.z += 0.8f; + + if ( victimPed->GetIsOnScreen() ) + { + if ( CGame::nastyGame ) + { + for ( int32 i = 0; i < 4; i++ ) + { + CVector dir; + dir.x = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + dir.y = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + dir.z = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + + CParticle::AddParticle(PARTICLE_BLOOD, pos, dir); + } + } + } + + if ( victimPed->m_nPedType == PEDTYPE_COP ) + CEventList::RegisterEvent(EVENT_SHOOT_COP, EVENT_ENTITY_PED, victimPed, FindPlayerPed(), 10000); + else + CEventList::RegisterEvent(EVENT_SHOOT_PED, EVENT_ENTITY_PED, victimPed, FindPlayerPed(), 10000); + } + } + else if ( victim->IsVehicle() ) + ((CVehicle *)victim)->InflictDamage(FindPlayerPed(), WEAPONTYPE_UZI_DRIVEBY, info->m_nDamage); + else + CGlass::WasGlassHitByBullet(victim, point.point); + + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_1, point.point); + break; + } + case ENTITY_TYPE_VEHICLE: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_2, point.point); + break; + } + case ENTITY_TYPE_DUMMY: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_3, point.point); + break; + } + default: break; + } + } + else + { + float norm = 30.0f/info->m_fRange; + CVector traceTarget = (target-source)*norm + source; + CBulletTraces::AddTrace(&source, &traceTarget); + } + + if ( shooter == FindPlayerVehicle() ) + CPad::GetPad(0)->StartShake_Distance(240, 128, FindPlayerVehicle()->GetPosition().x, FindPlayerVehicle()->GetPosition().y, FindPlayerVehicle()->GetPosition().z); + + return true; +} + +void +CWeapon::DoDoomAiming(CEntity *shooter, CVector *source, CVector *target) +{ + ASSERT(shooter!=nil); + ASSERT(source!=nil); + ASSERT(target !=nil); + +#ifndef FIX_BUGS + CEntity entity; // unused +#endif + + CPed *shooterPed = (CPed*)shooter; + if ( shooterPed->IsPed() && shooterPed->bCrouchWhenShooting ) + return; + + int16 lastEntity; + CEntity *entities[16]; + CWorld::FindObjectsInRange(*source, (*target-*source).Magnitude(), true, &lastEntity, 15, entities, false, true, true, false, false); + + float closestEntityDist = 10000.0f; + int16 closestEntity; + + for ( int32 i = 0; i < lastEntity; i++ ) + { + CEntity *victim = entities[i]; + ASSERT(victim!=nil); + + if ( (CEntity*)shooterPed != victim && shooterPed->CanSeeEntity(victim, DEGTORAD(22.5f)) ) + { + if ( !(victim->GetStatus() == STATUS_TRAIN_MOVING + || victim->GetStatus() == STATUS_TRAIN_NOT_MOVING + || victim->GetStatus() == STATUS_HELI + || victim->GetStatus() == STATUS_PLANE) ) + { + float distToVictim = (shooterPed->GetPosition()-victim->GetPosition()).Magnitude2D(); + float distToVictimZ = Abs(shooterPed->GetPosition().z-victim->GetPosition().z); + + if ( 1.5f*distToVictimZ < distToVictim ) + { + float entityDist = Sqrt(SQR(distToVictim) + SQR(distToVictimZ)); + + if ( entityDist < closestEntityDist ) + { + closestEntityDist = entityDist; + closestEntity = i; + } + } + } + } + } + + if ( closestEntityDist < DOOMAUTOAIMING_MAXDIST ) + { + CEntity *victim = entities[closestEntity]; + ASSERT(victim !=nil); + + float distToTarget = (*target - *source).Magnitude2D(); + float distToSource = (victim->GetPosition() - *source).Magnitude2D(); + + float victimZ = victim->GetPosition().z + 0.3f; + if ( victim->IsPed() ) + { + if ( ((CPed*)victim)->bIsDucking ) + victimZ -= 0.8f; + } + + (*target).z = (distToTarget / distToSource) * (victimZ - (*source).z) + (*source).z; + } +} + +void +CWeapon::DoTankDoomAiming(CEntity *shooter, CEntity *driver, CVector *source, CVector *target) +{ + ASSERT(shooter!=nil); + ASSERT(driver!=nil); + ASSERT(source!=nil); + ASSERT(target!=nil); + +#ifndef FIX_BUGS + CEntity entity; // unused +#endif + + int16 lastEntity; + CEntity *entities[16]; + CWorld::FindObjectsInRange(*source, (*target-*source).Magnitude(), true, &lastEntity, 15, entities, false, true, false, false, false); + + float closestEntityDist = 10000.0f; + int16 closestEntity; + + float normZ = (target->z - source->z) / (*target-*source).Magnitude(); + + for ( int32 i = 0; i < lastEntity; i++ ) + { + CEntity *victim = entities[i]; + + ASSERT(victim!=nil); + + if ( shooter != victim && driver != victim ) + { + if ( !(victim->GetStatus() == STATUS_TRAIN_MOVING + || victim->GetStatus() == STATUS_TRAIN_NOT_MOVING + || victim->GetStatus() == STATUS_HELI + || victim->GetStatus() == STATUS_PLANE) ) + { + if ( !(victim->IsVehicle() && victim->bRenderScorched) ) + { + float distToVictim = (shooter->GetPosition()-victim->GetPosition()).Magnitude2D(); + float distToVictimZ = Abs(shooter->GetPosition().z - (distToVictim*normZ + victim->GetPosition().z)); + + if ( 3.0f*distToVictimZ < distToVictim ) + { + CVector tmp = CVector(victim->GetPosition().x, victim->GetPosition().y, 0.0f); + if ( CCollision::DistToLine(source, target, + &tmp) < victim->GetBoundRadius()*3.0f ) + { + float vehicleDist = Sqrt(SQR(distToVictim) + SQR(distToVictimZ)); + if ( vehicleDist < closestEntityDist ) + { + closestEntityDist = vehicleDist; + closestEntity = i; + } + } + } + } + } + } + } + + if ( closestEntityDist < DOOMAUTOAIMING_MAXDIST ) + { + CEntity *victim = entities[closestEntity]; + ASSERT(victim!=nil); + + float distToTarget = (*target - *source).Magnitude2D(); + float distToSource = (victim->GetPosition() - *source).Magnitude2D(); + + (*target).z = (distToTarget / distToSource) * (0.3f + victim->GetPosition().z - (*source).z) + (*source).z; + } +} + +void +CWeapon::DoDriveByAutoAiming(CEntity *shooter, CVector *source, CVector *target) +{ + ASSERT(shooter!=nil); + ASSERT(source!=nil); + ASSERT(target!=nil); + +#ifndef FIX_BUGS + CEntity entity; // unused +#endif + + CPed *shooterPed = (CPed*)shooter; + if ( shooterPed->IsPed() && shooterPed->bCrouchWhenShooting ) + return; + + int16 lastEntity; + CEntity *entities[16]; + CWorld::FindObjectsInRange(*source, (*target-*source).Magnitude(), true, &lastEntity, 15, entities, false, false, true, false, false); + + float closestEntityDist = 10000.0f; + int16 closestEntity; + + for ( int32 i = 0; i < lastEntity; i++ ) + { + CEntity *victim = entities[i]; + ASSERT(victim!=nil); + + if ( shooter != victim ) + { + float lineDist = CCollision::DistToLine(source, target, &victim->GetPosition()); + float distToVictim = (victim->GetPosition() - shooter->GetPosition()).Magnitude(); + float pedDist = 0.15f*distToVictim + lineDist; + + if ( DotProduct((*target-*source), victim->GetPosition()-*source) > 0.0f && pedDist < closestEntityDist) + { + closestEntity = i; + closestEntityDist = pedDist; + } + } + } + + if ( closestEntityDist < DRIVEBYAUTOAIMING_MAXDIST ) + { + CEntity *victim = entities[closestEntity]; + ASSERT(victim!=nil); + + float distToTarget = (*source - *target).Magnitude(); + float distToSource = (*source - victim->GetPosition()).Magnitude(); + *target = (distToTarget / distToSource) * (victim->GetPosition() - *source) + *source; + } +} + +void +CWeapon::Reload(void) +{ + if (m_nAmmoTotal == 0) + return; + + CWeaponInfo *info = GetInfo(); + + if (m_nAmmoTotal >= info->m_nAmountofAmmunition) + m_nAmmoInClip = info->m_nAmountofAmmunition; + else + m_nAmmoInClip = m_nAmmoTotal; +} + +void +CWeapon::Update(int32 audioEntity) +{ + switch ( m_eWeaponState ) + { + case WEAPONSTATE_MELEE_MADECONTACT: + { + m_eWeaponState = WEAPONSTATE_READY; + break; + } + + case WEAPONSTATE_FIRING: + { + if ( m_eWeaponType == WEAPONTYPE_SHOTGUN && AEHANDLE_IS_OK(audioEntity) ) + { + uint32 timePassed = m_nTimer - gReloadSampleTime[WEAPONTYPE_SHOTGUN]; + if ( CTimer::GetPreviousTimeInMilliseconds() < timePassed && CTimer::GetTimeInMilliseconds() >= timePassed ) + DMAudio.PlayOneShot(audioEntity, SOUND_WEAPON_RELOAD, 0.0f); + } + + if ( CTimer::GetTimeInMilliseconds() > m_nTimer ) + { + if ( GetInfo()->m_eWeaponFire != WEAPON_FIRE_MELEE && m_nAmmoTotal == 0 ) + m_eWeaponState = WEAPONSTATE_OUT_OF_AMMO; + else + m_eWeaponState = WEAPONSTATE_READY; + } + + break; + } + + case WEAPONSTATE_RELOADING: + { + if ( AEHANDLE_IS_OK(audioEntity) && m_eWeaponType < WEAPONTYPE_LAST_WEAPONTYPE ) + { + uint32 timePassed = m_nTimer - gReloadSampleTime[m_eWeaponType]; + if ( CTimer::GetPreviousTimeInMilliseconds() < timePassed && CTimer::GetTimeInMilliseconds() >= timePassed ) + DMAudio.PlayOneShot(audioEntity, SOUND_WEAPON_RELOAD, 0.0f); + } + + if ( CTimer::GetTimeInMilliseconds() > m_nTimer ) + { + Reload(); + m_eWeaponState = WEAPONSTATE_READY; + } + + break; + } + default: break; + } +} + +void +FireOneInstantHitRound(CVector *source, CVector *target, int32 damage) +{ + ASSERT(source!=nil); + ASSERT(target!=nil); + + CParticle::AddParticle(PARTICLE_GUNFLASH, *source, CVector(0.0f, 0.0f, 0.0f)); + + CPointLights::AddLight(CPointLights::LIGHT_POINT, + *source, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CColPoint point; + CEntity *victim; + CWorld::ProcessLineOfSight(*source, *target, point, victim, true, true, true, true, true, true, false); + + CParticle::AddParticle(PARTICLE_HELI_ATTACK, *source, ((*target) - (*source)) * 0.15f); + + if ( victim ) + { + if ( victim->IsPed() ) + { + CPed *victimPed = (CPed *)victim; + if ( !victimPed->DyingOrDead() ) + { + CVector pos = victimPed->GetPosition(); + + CVector2D posOffset((*source).x-pos.x, (*source).y-pos.y); + int32 localDir = victimPed->GetLocalDirection(posOffset); + + victimPed->ClearAttackByRemovingAnim(); + + CAnimBlendAssociation *asoc = CAnimManager::AddAnimation(victimPed->GetClump(), ASSOCGRP_STD, AnimationId(ANIM_STD_HITBYGUN_FRONT + localDir)); + ASSERT(asoc!=nil); + asoc->blendAmount = 0.0f; + asoc->blendDelta = 8.0f; + + victimPed->InflictDamage(nil, WEAPONTYPE_UZI, damage, (ePedPieceTypes)point.pieceB, localDir); + + pos.z += 0.8f; + + if ( victimPed->GetIsOnScreen() ) + { + if ( CGame::nastyGame ) + { + for ( int32 i = 0; i < 4; i++ ) + { + CVector dir; + dir.x = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + dir.y = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + dir.z = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + + CParticle::AddParticle(PARTICLE_BLOOD, pos, dir); + } + } + } + } + } + else if ( victim->IsVehicle() ) + ((CVehicle *)victim)->InflictDamage(nil, WEAPONTYPE_UZI, damage); + //BUG ? no CGlass::WasGlassHitByBullet + + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_1, point.point); + CParticle::AddParticle(PARTICLE_SMOKE, point.point, CVector(0.0f, 0.0f, 0.01f)); + break; + } + case ENTITY_TYPE_VEHICLE: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_2, point.point); + break; + } + case ENTITY_TYPE_DUMMY: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_3, point.point); + break; + } + default: break; + } + } + else + { + float waterLevel; + if ( CWaterLevel::GetWaterLevel((*target).x, (*target).y, (*target).z + 10.0f, &waterLevel, false) ) + { + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, CVector((*target).x, (*target).y, waterLevel), CVector(0.0f, 0.0f, 0.01f)); + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_WATER, point.point); // no sound(empty) + } + } +} + +bool +CWeapon::IsTypeMelee(void) +{ + return m_eWeaponType == WEAPONTYPE_UNARMED || m_eWeaponType == WEAPONTYPE_BASEBALLBAT; +} + +bool +CWeapon::IsType2Handed(void) +{ + return m_eWeaponType >= WEAPONTYPE_SHOTGUN && m_eWeaponType <= WEAPONTYPE_FLAMETHROWER && m_eWeaponType != WEAPONTYPE_ROCKETLAUNCHER; +} + +void +CWeapon::MakePedsJumpAtShot(CPhysical *shooter, CVector *source, CVector *target) +{ + ASSERT(shooter!=nil); + ASSERT(source!=nil); + ASSERT(target!=nil); + + float minx = Min(source->x, target->x) - 2.0f; + float maxx = Max(source->x, target->x) + 2.0f; + float miny = Min(source->y, target->y) - 2.0f; + float maxy = Max(source->y, target->y) + 2.0f; + float minz = Min(source->z, target->z) - 2.0f; + float maxz = Max(source->z, target->z) + 2.0f; + + for ( int32 i = CPools::GetPedPool()->GetSize() - 1; i >= 0; i--) + { + CPed *ped = CPools::GetPedPool()->GetSlot(i); + + if ( ped ) + { + if ( ped->GetPosition().x > minx && ped->GetPosition().x < maxx + && ped->GetPosition().y > miny && ped->GetPosition().y < maxy + && ped->GetPosition().z > minz && ped->GetPosition().z < maxz ) + { + if ( ped != FindPlayerPed() && !((uint8)(ped->m_randomSeed ^ CGeneral::GetRandomNumber()) & 31) ) + ped->SetEvasiveDive(shooter, 1); + } + } + } +} + +bool +CWeapon::HitsGround(CEntity *holder, CVector *fireSource, CEntity *aimingTo) +{ + ASSERT(holder!=nil); + ASSERT(aimingTo!=nil); + + if (!holder->IsPed() || !((CPed*)holder)->m_pSeekTarget) + return false; + + CWeaponInfo *info = GetInfo(); + + CVector adjustedOffset = info->m_vecFireOffset; + adjustedOffset.z += 0.6f; + + CVector source, target; + CEntity *foundEnt = nil; + CColPoint foundCol; + + if (fireSource) + source = *fireSource; + else + source = holder->GetMatrix() * adjustedOffset; + + CEntity *aimEntity = aimingTo ? aimingTo : ((CPed*)holder)->m_pSeekTarget; + ASSERT(aimEntity!=nil); + + target = aimEntity->GetPosition(); + target.z += 0.6f; + + CWorld::ProcessLineOfSight(source, target, foundCol, foundEnt, true, false, false, false, false, false, false); + if (foundEnt && foundEnt->IsBuilding()) { + // That was supposed to be Magnitude, according to leftover code in assembly + float diff = (foundCol.point.z - source.z); + if (diff < 0.0f && diff > -3.0f) + return true; + } + + return false; +} + +void +CWeapon::BlowUpExplosiveThings(CEntity *thing) +{ +#ifdef FIX_BUGS + if ( thing && thing->IsObject() ) +#else + if ( thing ) +#endif + { + CObject *object = (CObject*)thing; + int32 mi = object->GetModelIndex(); + if ( IsExplosiveThingModel(mi) && !object->bHasBeenDamaged ) + { + object->bHasBeenDamaged = true; + + CExplosion::AddExplosion(object, FindPlayerPed(), EXPLOSION_BARREL, object->GetPosition()+CVector(0.0f,0.0f,0.5f), 100); + + if ( MI_EXPLODINGBARREL == mi ) + object->m_vecMoveSpeed.z += 0.75f; + else + object->m_vecMoveSpeed.z += 0.45f; + + object->m_vecMoveSpeed.x += float((CGeneral::GetRandomNumber()&255) - 128) * 0.0002f; + object->m_vecMoveSpeed.y += float((CGeneral::GetRandomNumber()&255) - 128) * 0.0002f; + + if ( object->GetIsStatic()) + { + object->SetIsStatic(false); + object->AddToMovingList(); + } + } + } +} + +bool +CWeapon::HasWeaponAmmoToBeUsed(void) +{ + switch (m_eWeaponType) { + case WEAPONTYPE_UNARMED: + case WEAPONTYPE_BASEBALLBAT: + return true; + default: + return m_nAmmoTotal != 0; + } +} + +bool +CPed::IsPedDoingDriveByShooting(void) +{ + if (FindPlayerPed() == this && GetWeapon()->m_eWeaponType == WEAPONTYPE_UZI) { + if (TheCamera.Cams[TheCamera.ActiveCam].LookingLeft || TheCamera.Cams[TheCamera.ActiveCam].LookingRight) + return true; + } + return false; +} + +bool +CWeapon::ProcessLineOfSight(CVector const &point1, CVector const &point2, CColPoint &point, CEntity *&entity, eWeaponType type, CEntity *shooter, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + return CWorld::ProcessLineOfSight(point1, point2, point, entity, checkBuildings, checkVehicles, checkPeds, checkObjects, checkDummies, ignoreSeeThrough, ignoreSomeObjects); +} + +#ifdef COMPATIBLE_SAVES +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +void +CWeapon::Save(uint8*& buf) +{ + CopyToBuf(buf, m_eWeaponType); + CopyToBuf(buf, m_eWeaponState); + CopyToBuf(buf, m_nAmmoInClip); + CopyToBuf(buf, m_nAmmoTotal); + CopyToBuf(buf, m_nTimer); + CopyToBuf(buf, m_bAddRotOffset); + ZeroSaveBuf(buf, 3); +} + +void +CWeapon::Load(uint8*& buf) +{ + CopyFromBuf(buf, m_eWeaponType); + CopyFromBuf(buf, m_eWeaponState); + CopyFromBuf(buf, m_nAmmoInClip); + CopyFromBuf(buf, m_nAmmoTotal); + CopyFromBuf(buf, m_nTimer); + CopyFromBuf(buf, m_bAddRotOffset); + SkipSaveBuf(buf, 3); +} + +#undef CopyFromBuf +#undef CopyToBuf +#endif diff --git a/src/weapons/Weapon.h b/src/weapons/Weapon.h new file mode 100644 index 0000000..c7685e0 --- /dev/null +++ b/src/weapons/Weapon.h @@ -0,0 +1,78 @@ +#pragma once + +#include "WeaponType.h" + +#define DRIVEBYAUTOAIMING_MAXDIST (2.5f) +#define DOOMAUTOAIMING_MAXDIST (9000.0f) + +class CEntity; +class CPhysical; +class CAutomobile; +struct CColPoint; +class CWeaponInfo; + +class CWeapon +{ +public: + eWeaponType m_eWeaponType; + eWeaponState m_eWeaponState; + int32 m_nAmmoInClip; + int32 m_nAmmoTotal; + uint32 m_nTimer; + bool m_bAddRotOffset; + + CWeapon() { + m_bAddRotOffset = false; + } + + CWeaponInfo *GetInfo(); + + static void InitialiseWeapons(void); + static void ShutdownWeapons (void); + static void UpdateWeapons (void); + + void Initialise(eWeaponType type, int32 ammo); + + bool Fire (CEntity *shooter, CVector *fireSource); + bool FireFromCar (CAutomobile *shooter, bool left); + bool FireMelee (CEntity *shooter, CVector &fireSource); + bool FireInstantHit(CEntity *shooter, CVector *fireSource); + + void AddGunshell (CEntity *shooter, CVector const &source, CVector2D const &direction, float size); + void DoBulletImpact(CEntity *shooter, CEntity *victim, CVector *source, CVector *target, CColPoint *point, CVector2D ahead); + + bool FireShotgun (CEntity *shooter, CVector *fireSource); + bool FireProjectile(CEntity *shooter, CVector *fireSource, float power); + + static void GenerateFlameThrowerParticles(CVector pos, CVector dir); + + bool FireAreaEffect (CEntity *shooter, CVector *fireSource); + bool FireSniper (CEntity *shooter); + bool FireM16_1stPerson (CEntity *shooter); + bool FireInstantHitFromCar(CAutomobile *shooter, bool left); + + static void DoDoomAiming (CEntity *shooter, CVector *source, CVector *target); + static void DoTankDoomAiming (CEntity *shooter, CEntity *driver, CVector *source, CVector *target); + static void DoDriveByAutoAiming(CEntity *shooter, CVector *source, CVector *target); + + void Reload(void); + void Update(int32 audioEntity); + bool IsTypeMelee (void); + bool IsType2Handed(void); + + static void MakePedsJumpAtShot(CPhysical *shooter, CVector *source, CVector *target); + + bool HitsGround(CEntity *holder, CVector *fireSource, CEntity *aimingTo); + static void BlowUpExplosiveThings(CEntity *thing); + bool HasWeaponAmmoToBeUsed(void); + + static bool ProcessLineOfSight(CVector const &point1, CVector const &point2, CColPoint &point, CEntity *&entity, eWeaponType type, CEntity *shooter, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects); + +#ifdef COMPATIBLE_SAVES + void Save(uint8*& buf); + void Load(uint8*& buf); +#endif +}; +VALIDATE_SIZE(CWeapon, 0x18); + +void FireOneInstantHitRound(CVector *source, CVector *target, int32 damage); \ No newline at end of file diff --git a/src/weapons/WeaponEffects.cpp b/src/weapons/WeaponEffects.cpp new file mode 100644 index 0000000..32e55fb --- /dev/null +++ b/src/weapons/WeaponEffects.cpp @@ -0,0 +1,104 @@ +#include "common.h" + +#include "main.h" +#include "WeaponEffects.h" +#include "TxdStore.h" +#include "Sprite.h" + +RwTexture *gpCrossHairTex; +RwRaster *gpCrossHairRaster; + +CWeaponEffects gCrossHair; + +CWeaponEffects::CWeaponEffects() +{ + +} + +CWeaponEffects::~CWeaponEffects() +{ + +} + +void +CWeaponEffects::Init(void) +{ + gCrossHair.m_bActive = false; + gCrossHair.m_vecPos = CVector(0.0f, 0.0f, 0.0f); + gCrossHair.m_nRed = 0; + gCrossHair.m_nGreen = 0; + gCrossHair.m_nBlue = 0; + gCrossHair.m_nAlpha = 255; + gCrossHair.m_fSize = 1.0f; + gCrossHair.m_fRotation = 0.0f; + + + CTxdStore::PushCurrentTxd(); + int32 slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + + gpCrossHairTex = RwTextureRead("crosshair", nil); + gpCrossHairRaster = RwTextureGetRaster(gpCrossHairTex); + + CTxdStore::PopCurrentTxd(); +} + +void +CWeaponEffects::Shutdown(void) +{ + RwTextureDestroy(gpCrossHairTex); +#if GTA_VERSION >= GTA3_PC_11 + gpCrossHairTex = nil; +#endif +} + +void +CWeaponEffects::MarkTarget(CVector pos, uint8 red, uint8 green, uint8 blue, uint8 alpha, float size) +{ + gCrossHair.m_bActive = true; + gCrossHair.m_vecPos = pos; + gCrossHair.m_nRed = red; + gCrossHair.m_nGreen = green; + gCrossHair.m_nBlue = blue; + gCrossHair.m_nAlpha = alpha; + gCrossHair.m_fSize = size; +} + +void +CWeaponEffects::ClearCrossHair(void) +{ + gCrossHair.m_bActive = false; +} + +void +CWeaponEffects::Render(void) +{ + if ( gCrossHair.m_bActive ) + { + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpCrossHairRaster); + + RwV3d pos; + float w, h; + if ( CSprite::CalcScreenCoors(gCrossHair.m_vecPos, &pos, &w, &h, true) ) + { + PUSH_RENDERGROUP("CWeaponEffects::Render"); + + float recipz = 1.0f / pos.z; + CSprite::RenderOneXLUSprite(pos.x, pos.y, pos.z, + gCrossHair.m_fSize * w, gCrossHair.m_fSize * h, + gCrossHair.m_nRed, gCrossHair.m_nGreen, gCrossHair.m_nBlue, 255, + recipz, 255); + + POP_RENDERGROUP(); + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + } +} \ No newline at end of file diff --git a/src/weapons/WeaponEffects.h b/src/weapons/WeaponEffects.h new file mode 100644 index 0000000..f6592b3 --- /dev/null +++ b/src/weapons/WeaponEffects.h @@ -0,0 +1,26 @@ +#pragma once + +class CWeaponEffects +{ +public: + bool m_bActive; + CVector m_vecPos; + uint8 m_nRed; + uint8 m_nGreen; + uint8 m_nBlue; + uint8 m_nAlpha; + float m_fSize; + float m_fRotation; + +public: + CWeaponEffects(); + ~CWeaponEffects(); + + static void Init(void); + static void Shutdown(void); + static void MarkTarget(CVector pos, uint8 red, uint8 green, uint8 blue, uint8 alpha, float size); + static void ClearCrossHair(void); + static void Render(void); +}; + +VALIDATE_SIZE(CWeaponEffects, 0x1C); \ No newline at end of file diff --git a/src/weapons/WeaponInfo.cpp b/src/weapons/WeaponInfo.cpp new file mode 100644 index 0000000..ba87245 --- /dev/null +++ b/src/weapons/WeaponInfo.cpp @@ -0,0 +1,189 @@ +#include "common.h" + +#include "main.h" +#include "FileMgr.h" +#include "WeaponInfo.h" +#include "AnimManager.h" +#include "AnimBlendAssociation.h" +#include "Weapon.h" + +static CWeaponInfo aWeaponInfo[WEAPONTYPE_TOTALWEAPONS]; + +static char ms_aWeaponNames[][32] = { + "Unarmed", + "BaseballBat", + "Colt45", + "Uzi", + "Shotgun", + "AK47", + "M16", + "SniperRifle", + "RocketLauncher", + "FlameThrower", + "Molotov", + "Grenade", + "Detonator", + "HeliCannon" +}; + +CWeaponInfo* +CWeaponInfo::GetWeaponInfo(eWeaponType weaponType) { + return &aWeaponInfo[weaponType]; +} + +void +CWeaponInfo::Initialise(void) +{ + debug("Initialising CWeaponInfo...\n"); + for (int i = 0; i < WEAPONTYPE_TOTALWEAPONS; i++) { + aWeaponInfo[i].m_eWeaponFire = WEAPON_FIRE_INSTANT_HIT; + aWeaponInfo[i].m_AnimToPlay = ANIM_STD_PUNCH; + aWeaponInfo[i].m_Anim2ToPlay = ANIM_STD_NUM; + aWeaponInfo[i].m_Flags = WEAPONFLAG_USE_GRAVITY | WEAPONFLAG_SLOWS_DOWN | WEAPONFLAG_RAND_SPEED | WEAPONFLAG_EXPANDS | WEAPONFLAG_EXPLODES; + } + debug("Loading weapon data...\n"); + LoadWeaponData(); + debug("CWeaponInfo ready\n"); +} + +void +CWeaponInfo::LoadWeaponData(void) +{ + float spread, speed, lifeSpan, radius; + float range, fireOffsetX, fireOffsetY, fireOffsetZ; + float delayBetweenAnimAndFire, delayBetweenAnim2AndFire, animLoopStart, animLoopEnd; + int flags, ammoAmount, damage, reload, weaponType; + int firingRate, modelId; + char line[256], weaponName[32], fireType[32]; + char animToPlay[32], anim2ToPlay[32]; + + CAnimBlendAssociation *animAssoc; + AnimationId animId; + + size_t bp, buflen; + int lp, linelen; + + CFileMgr::SetDir("DATA"); + buflen = CFileMgr::LoadFile("WEAPON.DAT", work_buff, sizeof(work_buff), "r"); + CFileMgr::SetDir(""); + + for (bp = 0; bp < buflen; ) { + // read file line by line + for (linelen = 0; work_buff[bp] != '\n' && bp < buflen; bp++) { + line[linelen++] = work_buff[bp]; + } + bp++; + line[linelen] = '\0'; + + // skip white space + for (lp = 0; line[lp] <= ' ' && line[lp] != '\0'; lp++); + + if (line[lp] == '\0' || line[lp] == '#') + continue; + + spread = 0.0f; + flags = 0; + speed = 0.0f; + ammoAmount = 0; + lifeSpan = 0.0f; + radius = 0.0f; + range = 0.0f; + damage = 0; + reload = 0; + firingRate = 0; + fireOffsetX = 0.0f; + weaponName[0] = '\0'; + fireType[0] = '\0'; + fireOffsetY = 0.0f; + fireOffsetZ = 0.0f; + animId = ANIM_STD_WALK; + sscanf( + &line[lp], + "%s %s %f %d %d %d %d %f %f %f %f %f %f %f %s %s %f %f %f %f %d %d", + weaponName, + fireType, + &range, + &firingRate, + &reload, + &ammoAmount, + &damage, + &speed, + &radius, + &lifeSpan, + &spread, + &fireOffsetX, + &fireOffsetY, + &fireOffsetZ, + animToPlay, + anim2ToPlay, + &animLoopStart, + &animLoopEnd, + &delayBetweenAnimAndFire, + &delayBetweenAnim2AndFire, + &modelId, + &flags); + + if (strncmp(weaponName, "ENDWEAPONDATA", 13) == 0) + return; + + weaponType = FindWeaponType(weaponName); + + animAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, animToPlay); + animId = static_cast(animAssoc->animId); + + if (strcmp(anim2ToPlay, "null") != 0) { + animAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, anim2ToPlay); + aWeaponInfo[weaponType].m_Anim2ToPlay = (AnimationId) animAssoc->animId; + } + + CVector vecFireOffset(fireOffsetX, fireOffsetY, fireOffsetZ); + + aWeaponInfo[weaponType].m_eWeaponFire = FindWeaponFireType(fireType); + aWeaponInfo[weaponType].m_fRange = range; + aWeaponInfo[weaponType].m_nFiringRate = firingRate; + aWeaponInfo[weaponType].m_nReload = reload; + aWeaponInfo[weaponType].m_nAmountofAmmunition = ammoAmount; + aWeaponInfo[weaponType].m_nDamage = damage; + aWeaponInfo[weaponType].m_fSpeed = speed; + aWeaponInfo[weaponType].m_fRadius = radius; + aWeaponInfo[weaponType].m_fLifespan = lifeSpan; + aWeaponInfo[weaponType].m_fSpread = spread; + aWeaponInfo[weaponType].m_vecFireOffset = vecFireOffset; + aWeaponInfo[weaponType].m_AnimToPlay = animId; + aWeaponInfo[weaponType].m_fAnimLoopStart = animLoopStart / 30.0f; + aWeaponInfo[weaponType].m_fAnimLoopEnd = animLoopEnd / 30.0f; + aWeaponInfo[weaponType].m_fAnimFrameFire = delayBetweenAnimAndFire / 30.0f; + aWeaponInfo[weaponType].m_fAnim2FrameFire = delayBetweenAnim2AndFire / 30.0f; + aWeaponInfo[weaponType].m_nModelId = modelId; + aWeaponInfo[weaponType].m_Flags = flags; + } +} + +eWeaponType +CWeaponInfo::FindWeaponType(char *name) +{ + for (int i = 0; i < WEAPONTYPE_TOTALWEAPONS; i++) { + if (strcmp(ms_aWeaponNames[i], name) == 0) { + return static_cast(i); + } + } + return WEAPONTYPE_UNARMED; +} + +eWeaponFire +CWeaponInfo::FindWeaponFireType(char *name) +{ + if (strcmp(name, "MELEE") == 0) return WEAPON_FIRE_MELEE; + if (strcmp(name, "INSTANT_HIT") == 0) return WEAPON_FIRE_INSTANT_HIT; + if (strcmp(name, "PROJECTILE") == 0) return WEAPON_FIRE_PROJECTILE; + if (strcmp(name, "AREA_EFFECT") == 0) return WEAPON_FIRE_AREA_EFFECT; + Error("Unknown weapon fire type, WeaponInfo.cpp"); + return WEAPON_FIRE_INSTANT_HIT; +} + +void +CWeaponInfo::Shutdown(void) +{ + debug("Shutting down CWeaponInfo...\n"); + debug("CWeaponInfo shut down\n"); +} diff --git a/src/weapons/WeaponInfo.h b/src/weapons/WeaponInfo.h new file mode 100644 index 0000000..96e2ecf --- /dev/null +++ b/src/weapons/WeaponInfo.h @@ -0,0 +1,52 @@ +#pragma once + +#include "AnimationId.h" +#include "WeaponType.h" + +enum +{ + WEAPONFLAG_USE_GRAVITY = 1, + WEAPONFLAG_SLOWS_DOWN = 1 << 1, + WEAPONFLAG_DISSIPATES = 1 << 2, + WEAPONFLAG_RAND_SPEED = 1 << 3, + WEAPONFLAG_EXPANDS = 1 << 4, + WEAPONFLAG_EXPLODES = 1 << 5, + WEAPONFLAG_CANAIM = 1 << 6, + WEAPONFLAG_CANAIM_WITHARM = 1 << 7, + WEAPONFLAG_1ST_PERSON = 1 << 8, + WEAPONFLAG_HEAVY = 1 << 9, + WEAPONFLAG_THROW = 1 << 10, +}; + +class CWeaponInfo { +public: + eWeaponFire m_eWeaponFire; + float m_fRange; + uint32 m_nFiringRate; + uint32 m_nReload; + int32 m_nAmountofAmmunition; + uint32 m_nDamage; + float m_fSpeed; + float m_fRadius; + float m_fLifespan; + float m_fSpread; + CVector m_vecFireOffset; + AnimationId m_AnimToPlay; + AnimationId m_Anim2ToPlay; + float m_fAnimLoopStart; + float m_fAnimLoopEnd; + float m_fAnimFrameFire; + float m_fAnim2FrameFire; + int32 m_nModelId; + uint32 m_Flags; + + static void Initialise(void); + static void LoadWeaponData(void); + static CWeaponInfo *GetWeaponInfo(eWeaponType weaponType); + static eWeaponFire FindWeaponFireType(char *name); + static eWeaponType FindWeaponType(char *name); + static void Shutdown(void); + bool IsFlagSet(uint32 flag) const { return (m_Flags & flag) != 0; } +}; + +VALIDATE_SIZE(CWeaponInfo, 0x54); \ No newline at end of file diff --git a/src/weapons/WeaponType.h b/src/weapons/WeaponType.h new file mode 100644 index 0000000..b45740b --- /dev/null +++ b/src/weapons/WeaponType.h @@ -0,0 +1,49 @@ +#pragma once + +enum eWeaponType +{ + WEAPONTYPE_UNARMED, + WEAPONTYPE_BASEBALLBAT, + WEAPONTYPE_COLT45, + WEAPONTYPE_UZI, + WEAPONTYPE_SHOTGUN, + WEAPONTYPE_AK47, + WEAPONTYPE_M16, + WEAPONTYPE_SNIPERRIFLE, + WEAPONTYPE_ROCKETLAUNCHER, + WEAPONTYPE_FLAMETHROWER, + WEAPONTYPE_MOLOTOV, + WEAPONTYPE_GRENADE, + WEAPONTYPE_DETONATOR, + WEAPONTYPE_HELICANNON, + WEAPONTYPE_LAST_WEAPONTYPE, + WEAPONTYPE_ARMOUR, + WEAPONTYPE_RAMMEDBYCAR, + WEAPONTYPE_RUNOVERBYCAR, + WEAPONTYPE_EXPLOSION, + WEAPONTYPE_UZI_DRIVEBY, + WEAPONTYPE_DROWNING, + WEAPONTYPE_FALL, + WEAPONTYPE_UNIDENTIFIED, + + WEAPONTYPE_TOTALWEAPONS = WEAPONTYPE_LAST_WEAPONTYPE, + WEAPONTYPE_TOTAL_INVENTORY_WEAPONS = 13, +}; + +enum eWeaponFire { + WEAPON_FIRE_MELEE, + WEAPON_FIRE_INSTANT_HIT, + WEAPON_FIRE_PROJECTILE, + WEAPON_FIRE_AREA_EFFECT, + WEAPON_FIRE_USE +}; + +// Taken from MTA SA, seems it's unchanged +enum eWeaponState +{ + WEAPONSTATE_READY, + WEAPONSTATE_FIRING, + WEAPONSTATE_RELOADING, + WEAPONSTATE_OUT_OF_AMMO, + WEAPONSTATE_MELEE_MADECONTACT +}; \ No newline at end of file diff --git a/utils/gxt/american.txt b/utils/gxt/american.txt new file mode 100644 index 0000000..57ec4d7 --- /dev/null +++ b/utils/gxt/american.txt @@ -0,0 +1,8110 @@ +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~Hey! Get back in the vehicle! + +[IN_VEH2] +~g~You need some wheels for this job! + +[IN_BOAT] +~g~You need a boat for this job! + +[HEY] +~g~Don't go solo, keep your posse together! + +[HEY2] +~g~Don't split up, keep the group together! + +[HEY3] +~g~You've dropped your main man, go back and get 8-Ball! + +[HEY4] +~g~Lose Misty and Luigi will lose your face! Go and get her! + +[HEY5] +~g~One of the girls is AWOL, Go back and round her up! + +[HEY6] +~g~You left your honor with the Yakuza Kanbu. You must protect him! + +[HEY7] +~g~An extra gun could be useful. Go back and pick up your contact! + +[HEY8] +~g~Protection means just that -Protect the Old Oriental Gentleman! + +[HEY9] +~g~You want the word on the street? Go see the contact! + +[HELP2_A] +Press the ~h~/ button~w~ when running to ~h~sprint. + +[HELP3] +You can only sprint for short periods before becoming tired. + +[HELP4_A] +Press the~h~ ~k~~VEHICLE_ACCELERATE~ button~w~ to ~h~accelerate. + +[HELP4_D] +Push the~h~ right analog stick~w~ up to ~h~accelerate. + +[HELP5_A] +Press the~h~ ~k~~VEHICLE_BRAKE~ button~w~ to ~h~brake~w~, or to ~h~reverse~w~ if the vehicle has stopped. + +[HELP5_D] +Pull the ~h~right analog stick~w~ back to ~h~brake~w~, or to ~h~reverse~w~ if the vehicle has stopped. + +[HELP6_A] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP6_C] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP6_D] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP7_A] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ button ~w~to ~h~target~w~ with the sniper rifle. + +[HELP7_D] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ button ~w~to ~h~target ~w~with the sniper rifle. + +[HELP8_A] +Press the~h~ ~k~~PED_SNIPER_ZOOM_IN~ button ~w~to ~h~zoom in ~w~with the rifle and the~h~ ~k~~PED_SNIPER_ZOOM_OUT~ button ~w~to ~h~zoom out ~w~again. + +[HELP9_A] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HELP10] +This badge indicates you have a police wanted level. + +[HELP11] +The more badges the higher your wanted level. + +[HELP13] +Sometimes you may need to use pathways not shown on the radar. + +[TIMER] +This is a timed mission, you must complete it before the timer counts down to zero. + +[MISTY1] +~r~Misty is morgue-meat! + +[OUT_VEH] +~g~Get out of the vehicle! + +[GARAGE] +Drive the vehicle into the garage, then walk outside. + +[WANTED1] +~g~Shake the cops and lose your wanted level! + +[NODOORS] +~g~They ain't sardines! Get some wheels with enough seats. + +[TRASH] +~g~You've junked your wheels real bad! Get your vehicle repaired! + +[WRECKED] +~r~The vehicle is wrecked! + +[HORN] +~g~Sound the horn. + +[NOMONEY] +~g~You need more cash! + +[OUTTIME] +~r~Too slow, man, too slow! + +[SPOTTED] +~r~They're on to you! + +[REWARD] +REWARD $~1~ + +[GAMEOVR] +GAME OVER + +[Z] +Z-axis value: ~1~ + +[M_FAIL] +MISSION FAILED! + +[M_PASS] +MISSION PASSED! $~1~ + +[O_PASS] +ODD JOB PASSED! + +[O_FAIL] +ODD JOB FAILED! + +[DEAD] +WASTED! + +[BUSTED] +BUSTED! + +[S_PROMP] +When not on a mission you can ~h~save your game here~w~, this will advance time six hours. + +[NUMBER] +~1~ + +[SCORE] +$~1~ + +[LOADCAR] +LOADING VEHICLE... (PRESS L1 TO CANCEL) + +[CARSOFF] +Cars turned off. + +[CARS_ON] +Cars turned on. + +[TEXTXYZ] +Writing coordinates to file... + +[CHEATON] +Cheat mode ON + +[CHEATOF] +Cheat mode OFF + +[UZI_IN] +The Uzi is now in stock at Ammunation! + +[IMPORT1] +Go outside and wait for your vehicle. + +[PAGEB1] +Pistol delivered to hideout + +[PAGEB2] +Uzi delivered to hideout + +[PAGEB3] +Body armor delivered to hideout + +[PAGEB4] +Shotgun delivered to hideout + +[PAGEB5] +grenades delivered to hideout + +[PAGEB6] +molotovs delivered to hideout + +[PAGEB7] +AK47 delivered to hideout + +[PAGEB8] +Sniper rifle delivered to hideout + +[PAGEB9] +M16 delivered to hideout + +[PAGEB10] +Rocket Launcher delivered to hideout + +[PAGEB11] +Flamethrower delivered to hideout + +[WANT_A] +You will only be arrested if you have a ~h~wanted level. + +[WANT_B] +Your ~h~wanted level~w~ is represented by the row of stars in the top right of the screen. + +[WANT_C] +You now have a ~h~wanted level~w~ of one... + +[WANT_D] +two... + +[WANT_E] +three... + +[WANT_F] +As your ~h~wanted level~w~ increases you will attract more powerful forms of law enforcement. + +[WANT_G] +When you are ~h~'busted'~w~ you are returned to the nearest police station. + +[WANT_H] +The cops will take all your weapons and some of your cash as a bribe. + +[WANT_I] +Any mission you were on will be failed. + +[WANT_J] +You will find ways of reducing your wanted level the more you play. + +[WANT_K] +If you are in a car, ~h~SPRAY SHOPS~w~ will ~h~clear your wanted level. + +[HEAL_B] +When you are ~h~'wasted'~w~ you are returned to the nearest hospital. + +[HEAL_C] +You will lose your weapons and the doctors will take some cash for patching you up. + +[HEAL_E] +You will find ways of healing or protecting yourself the more you play the game. + +[DAM] +DAMAGE: + +[KILLS] +KILLS: + +[FARES] +FARES: + +[BULL] +BULLION: + +[EVID] +EVIDENCE: + +[HEALTH] +CAR HEALTH: + +[COLLECT] +COLLECTED: + +[BOMB] +Drive your vehicle into the bomb shop to attach a ~h~bomb~w~. Cost - ~h~$1000. + +[SAVE1] +Walk through the doorway to ~h~Save the game~w~. You cannot save during a mission. + +[SAVE2] +Any vehicle left in this garage will be stored when the game is saved. + +[AMMU] +Go inside Ammu-Nation to buy a weapon. + +[BRIDGE1] +When the Callahan Bridge is repaired you will be able to drive to Staunton Island. + +[TUNNEL] +When the Porter Tunnel is opened you will be able to drive to Staunton Island. + +[LUIGI] +LUIGI MISSIONS + +[TONI] +TONI MISSIONS + +[JOEY] +JOEY MISSIONS + +[FRANK] +SALVATORE MISSIONS + +[DIABLO] +DIABLO MISSIONS + +[ASUKA] +ASUKA MISSIONS + +[B_SITE] +ASUKA SUBURBAN MISSIONS + +[KENJI] +KENJI MISSIONS + +[RAY] +RAY MISSIONS + +[LOVE] +LOVE MISSIONS + +[YARDIE] +YARDIE MISSIONS + +[HOOD] +HOOD MISSIONS + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Atlantic Quays + +[PORT_E] +Portland Harbor + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Portland Beach + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Red Light District + +[TOWERS] +Hepburn Heights + +[HARWOOD] +Harwood + +[ROADBR1] +Callahan Bridge + +[ROADBR2] +Callahan Bridge + +[TUNNELP] +Porter Tunnel + +[BOMB1] +8-Ball's Garage + +[COM_ZON] +Staunton Island + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Liberty Campus + +[CONSTRU] +Fort Staunton + +[PARK] +Belleville Park + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Francis Intl. Airport + +[PROJECT] +Wichita Gardens + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Cochrane Dam + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Ambulance + +[CAR_2] +Firetruck + +[CAR_3] +Police + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Rhino + +[CAR_7] +FBIcar + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_10] +Coach + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Cabbie + +[CAR_39] +Buggy + +[LUIGIS] +Luigi's Place + +[GOAWAY] +~g~You are already on a mission! + +[LUIGGO] +~g~Luigi's interviewing some new girls -Come back later! + +[JOEYGO] +~g~Joey's out on the town with Misty -Drop by later! + +[TONIGO] +~g~Toni's taken his Momma to the opera -Call in some other time! + +[KEMUGO] +~g~Maria and Kemuri are all tied up at the moment -Drop by later! + +[KENJGO] +~g~Kenji is attending a Yakuza meeting -Call by some other time! + +[RAYGO] +~g~Ray has other toilets to hang around -Try again later! + +[LOVEGO] +~g~Donald Love has other business to attend to -Make an appointment later! + +[KENSGO] +~g~Kenji is busy! -Call by later! + +[HOODGO] +~g~The Hoods are not available at this time! + +[WRONGT1] +~g~Come back between 05:00 and 21:00 for a job + +[WRONGT2] +~g~Come back between 06:00 and 14:00 for a job + +[WRONGT3] +~g~Come back between 15:00 and 00:00 for a job + +[GUN_1A] +Use the ~h~~k~~PED_CYCLE_WEAPON_RIGHT~ button ~w~and the ~h~~k~~PED_CYCLE_WEAPON_LEFT~ button ~w~to cycle through your weapons. + +[GUN_2A] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_2C] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_2D] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_3A] +While holding the ~h~~k~~PED_LOCK_TARGET~ button,~w~ press the ~h~~k~~PED_CYCLE_TARGET_LEFT~ button~w~ or the ~h~~k~~PED_CYCLE_TARGET_RIGHT~ button to switch target. + +[GUN_3B] +While holding the ~h~~k~~PED_LOCK_TARGET~ button,~w~ press the ~h~~k~~PED_CYCLE_TARGET_LEFT~ button~w~ or the ~h~~k~~PED_CYCLE_TARGET_RIGHT~ button to switch target. + +[GUN_4A] +While holding the ~h~~k~~PED_LOCK_TARGET~ button~w~ you can walk or run while remaining locked onto a target. + +[GUN_4B] +While holding the ~h~~k~~PED_LOCK_TARGET~ button~w~ you can walk or run while remaining locked onto a target. + +[GUN_5] +You can practice targeting and shooting on these paper targets. When you are finished resume the mission. + +[TAXI1] +~g~Look for a fare. + +[FARE1] +~g~Destination ~w~'Meeouch Sex Kitten Club' ~g~in Redlight. + +[FARE2] +~g~Destination ~w~'Supa Save' ~g~in Portland View. + +[FARE3] +~g~Destination ~w~'old school hall' ~g~in Chinatown. + +[FARE4] +~g~Destination ~w~'Greasy Joe's Cafe' ~g~in Callahan Point. + +[FARE5] +~g~Destination ~w~'AmmuNation' ~g~in Redlight. + +[FARE6] +~g~Destination ~w~'Easy Credit Autos' ~g~in Saint Mark's. + +[FARE7] +~g~Destination ~w~'Woody's topless bar' ~g~in Redlight. + +[FARE8] +~g~Destination ~w~'Marcos Bistro' ~g~in Saint Mark's. + +[FARE9] +~g~Destination ~w~'import export garage' ~g~in Portland Harbor. + +[FARE10] +~g~Destination ~w~'Punk Noodles' ~g~in Chinatown. + +[FARE12] +~g~Destination ~w~'Football Stadium' ~g~in Aspatria. + +[FARE13] +~g~Destination ~w~'The Church' ~g~in Bedford Point + +[FARE14] +~g~Destination ~w~'The Casino' ~g~in Torrington + +[FARE15] +~g~Destination ~w~'Liberty University' ~g~in Liberty Campus + +[FARE16] +~g~Destination ~w~'Shopping Mall' ~g~in Belleville Park Area + +[FARE17] +~g~Destination ~w~'Museum' ~g~in Newport + +[FARE18] +~g~Destination ~w~'AmCo Building' ~g~in Torrington + +[FARE19] +~g~Destination ~w~'Bolt Burgers' ~g~in Bedford Point + +[FARE20] +~g~Destination ~w~'The Park' ~g~in Belleville + +[FARE21] +~g~Destination ~w~'Francis intl. Airport' + +[FARE22] +~g~Destination ~w~'Cochrane Dam' + +[FARE24] +~g~Destination ~w~'The hospital' ~g~in Pike Creek + +[FARE25] +~g~Destination ~w~'The Park' ~g~in Shoreside Vale + +[FARE26] +~g~Destination ~w~'North West Towers' ~g~in Wichita Gardens + +[NEW_TAX] +BIGGER! FASTER! HARDER! new Borgnine taxis open for business in Harwood. Call 555-BORGNINE today! + +[TSCORE2] +$~1~ + +[IN_ROW] +~1~ IN A ROW bonus! $~1~ + +[TTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle taxi missions on or off. + +[TTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle taxi missions on or off. + +[A_TIME] ++~1~ seconds + +[A_FULL] +~r~Ambulance full!! + +[A_RANGE] +~g~The ambulance radio is out of range, get closer to a hospital! + +[FTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle fire truck missions on or off. + +[FTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle fire truck missions on or off. + +[F_PASS1] +Fire extinguished! + +[F_RANGE] +~g~The fire truck radio is out of range, get closer to a fire station! + +[C_BREIF] +~g~Suspect last seen in the ~a~ area. + +[C_RANGE] +~g~The police radio is out of range, get closer to a police station! + +[DODO_FT] +You flew for ~1~ seconds! + +[EBAL_A] +I know a place on the edge of the Red Light District where we can lay low, + +[EBAL_A1] +but my hands are all messed up so you better drive, brother. + +[EBAL_1] +Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a vehicle. + +[EBAL_1B] +Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a vehicle. + +[EBAL_2] +~g~Get back into the car! + +[EBAL_3] +This is the ~h~radar~w~. Use it to navigate the city, follow the ~h~blip~w~ on the ~h~radar~w~ to find the hideout! + +[EBAL_D] +I know a guy, he's connected, his name's Luigi. + +[EBAL_D1] +Me an' him go back so I could probably get you some work. C'mon lets head over there. + +[EBAL_E] +C'mon, lets drop by and I'll introduce you. + +[EBAL_I] +The boss will be out to see you shortly... + +[EBAL_J] +8-Ball's got some business up stairs. + +[EBAL_K] +Maybe you can do me a favor. + +[EBAL_L] +One of my girls needs a ride so grab a car and pick up Misty from the clinic. Then bring her back here. + +[EBAL_N] +So keep your hands on the wheel! + +[EBAL_4] +~r~8-Ball's dead! + +[EBAL_5] +~g~Get a vehicle! + +[EBAL_6] +~g~Pick up Misty! + +[LM1] +'LUIGI'S GIRLS' + +[LM2] +'DON'T SPANK MA BITCH UP' + +[LM3] +'DRIVE MISTY FOR ME' + +[LM5] +'THE FUZZ BALL' + +[LM1_2] +~g~Take Misty to Luigi's Club. + +[LM1_3] +~g~Press the horn to get the girl into the car. + +[LM1_6] +~g~Get back into the car! + +[LM1_7] +Stop the vehicle next to Misty and allow her to enter it. + +[LM1_8] +You can go and see Luigi for more work or check out Liberty City. + +[LM2_A] +There's a new high on the street goes by the name of SPANK. + +[LM2_E] +Some wiseguy's been introducing this trash to my girls down Portland Harbor. + +[LM2_B] +Go and introduce a bat to his face! + +[LM2_G] +I want compensation for this insult! + +[LM2_1] +~g~Take his car and get it resprayed. + +[LM2_2A] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat! + +[LM2_2C] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat~w~! + +[LM2_2D] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat~w~! + +[LM2_3] +~g~Stash the car in Luigi's lockup! + +[LM2_4] +~g~Respray the car! + +[LM3_A] +Hey I've gotta talk to you... All right Mick I'll talk to yah later. + +[LM3_B] +How yah doing kid? + +[LM3_C] +The Don's son, Joey Leone, he wants some action from his regular girl Misty. + +[LM3_D] +Go pick her up at Hepburn Heights... + +[LM3_E] +but watch yourself that's Diablo turf. + +[LM3_F] +Then run her over to his garage in Trenton and make it quick, + +[LM3_H] +so keep your eyes on the road and off Misty! + +[LM3_2] +~g~Take Misty to Joey's. + +[LM3_4] +~g~Go pick up Misty! + +[LM3_5] +You working regular for Luigi now huh? It's about time he got a driver we can trust! + +[LM3_7] +I'll be with you in a minute spark plug. + +[LM3_10] +~g~Get a vehicle! + +[LM4_B] +Go and take care of things for me. + +[LM4_C] +If you need a piece go around the back of AmmuNation opposite the subway. + +[LM5_A] +The Policeman's Ball is being held at the old school hall near the Callahan Bridge + +[LM5_B] +and they'll be looking for some 'old school' action. + +[LM5_C] +Now I got girls all over town walking the streets. + +[LM5_D] +Get'em to the ball they'll make a bundle. + +[LM5_1] +~g~You pack these ladies too tight, they gonna bruise! ~g~Drop these girls off first, then come back for more. + +[LM5_2] +~r~One of Luigi's girls is bodybag meat! + +[LM5_3] +~g~You need a car! + +[LM5_4] +~g~Pick up the girls working St. Marks. + +[LM5_5] +~g~Take the girls to the Fuzz Ball! + +[LM5_8] +~g~Girls working the Ball: ~1~ + +[JM2] +'FAREWELL 'CHUNKY' LEE CHONG' + +[JM4] +'CIPRIANI'S CHAUFFEUR' + +[JM5] +'DEAD SKUNK IN THE TRUNK' + +[JM1_1] +~g~Take Forelli's car to 8-Ball's garage North of here, behind 'Easy Credit Autos'. + +[JM1_2] +~g~Park the car back at Marco's Bistro. + +[JM1_3] +~g~Activate the car bomb then get out of there! + +[JM1_4] +~g~You're trashing the vehicle! Get it repaired! + +[JM1_5] +~g~The car bomb's not set! + +[JM1_6] +~g~Put the car back in the correct position. + +[JM1_8A] +~y~Hey, it's my main man! + +[JM1_8B] +~y~The bomb shop's automated. Just drive in, stop your car and the shop will do the rest. + +[JM1_8C] +~y~Here, your first can be free, but after that it'll cost. + +[JM2_A] +Chunky Lee Chong is pushing spank for some new gang from Colombia... or Colorado... or something.... + +[JM2_B] +I'm not really sure. Who needs details anyway. + +[JM2_D] +That rat has sold his last stir fry. + +[JM2_E] +I want you to take him out! + +[JM2_G] +Sort yourself with a nine, you know where it is, right? + +[JM2_H] +Well remember, just watch your back in China Town, it's Triad territory. + +[JM3_A] +Alright, we're gonna hit the pay role van. + +[JM3_B] +It leaves the edge of China Town everyday. + +[JM3_C] +Bullets won't even dent the van's armor, so get a car and ram it off the road. + +[JM3_D] +Now hit it hard and the punk ass security guards should bail. + +[JM3_E] +Then take it to the warehouse at the docks and my guys are gonna take over from there. + +[JM3_F] +Now it won't be doin' it's rounds all day, so don't hang around. + +[JM3_1] +~g~Take the van to the lock up. + +[JM3_2] +~g~Ram the van until its damage is below 70 percent. + +[JM4_B] +Oh! Here's the guy I was telling you about! + +[JM4_C] +Alright Listen. This guy ain't Italian and he's no mechanic but he can get things fixed. + +[JM4_D] +This is Pops Capo, Toni Cipriani. + +[JM4_E] +Yeah, I'm Toni Cipriani + +[JM4_F] +Take him to Momma's restaurant at St Marks, alright. + +[JM4_G] +Now listen to me, I'm planning a job that needs a good driver so drop by sometime later Ok? + +[JM4_2] +Wait here! Keep the engine running. This ain't a social call. + +[JM4_3] +It's a Triad ambush! Get us out of here kid! + +[JM4_4] +The Triads think they can mess with me, the triads, with ME! + +[JM4_6] +Hey watch the car! I said no fancy crap. + +[JM4_7] +~g~Take Toni to his momma's restaurant. + +[JM4_8] +~r~Toni's been wasted! + +[JM5_A] +Beautiful! Just beautiful. + +[JM5_B] +Alright, Just the guy I need to talk to! + +[JM5_D] +One of the Forellis thought he was a wise guy, so he got what he had coming to him. + +[JM5_E] +Take the corpse to the crusher in Harwood, alright? + +[JM5_1] +~g~Take it to the crusher! + +[JM5_2] +~g~It's the Forelli brothers! + +[JM6_A] +What a ride she's gonna be, huh? + +[JM6_B] +Alright, listen. Get some wheels to the safehouse at St. Marks and pick up a few friends of mine. + +[JM6_C] +They're hittin' a bank and they need a driver. + +[JM6_D] +I gave my word that you were the man, so don't screw this up. + +[JM6_E] +Get them to the bank before five o'clock, not a minute after. + +[JM6_2] +Keep the engine running we'll be in and out in no time. + +[JM6_3] +Get us out of here!! + +[JM6_4] +Shake the cops and get us to the safehouse!! + +[JM6_6] +~g~Go and get a vehicle less conspicuous! + +[JM6_7] +~g~You need all 3 to rob the bank! + +[TM1] +'TAKING OUT THE LAUNDRY' + +[TM2] +'THE PICK-UP' + +[TM3] +'SALVATORE'S CALLED A MEETING' + +[TM4] +'TRIADS AND TRIBULATIONS' + +[TM5] +'BLOW FISH' + +[TONI_P] +I've got some urgent work for you! -Toni + +[TM1_A] +~w~Take a seat kid, take a god damned seat. + +[TM1_B] +~w~So the laundry won't pay any protection eh? + +[TM1_C] +~w~The Triads think they can mess with me? + +[TM1_D] +~w~Let's teach these would be tough guys what it means to be a tough guy. + +[TM1_E] +~w~Yeah, teach 'em some respect. No son of mine gets it from some Triads. + +[TM1_F] +~w~Your father, god rest his soul, took no crap from no Triads back in Sicily. + +[TM1_G] +~w~Sorry Ma. Yes Ma. + +[TM1_H] +~w~I want you to destroy their laundry vans + +[TM1_I] +~w~and mangle any triad gimp that gets in your way. + +[TM1_J] +~w~8-Ball can supply you with what you're gonna need. + +[TM2_A] +~w~TONI's off making people bleed or trying to. + +[TM2_AA] +~w~He'll never be as tough as his Pop, but he left you a note on the table. + +[TM2_B] +~w~The laundry has agreed to pay - you did real good kid! + +[TM2_C] +~w~Go collect the cash and bring it back here. Watch out for the Triads. + +[TM2_D] +~w~They may be shoving a firecracker up your ass, but don't take no crap. + +[TM2_E] +~w~Nobody I mean nobody, messes with TONI CIPRIANI! + +[TM2_1] +~g~Get the cash back to Toni's!! + +[TM2_2] +~g~You iced them all! + +[TM3_MA] +~w~I don't know where he is! + +[TM3_MB] +~w~I swear that boy of mine don't know himself sometimes. + +[TM3_MC] +~w~Now his father, he was different. Always on top, in charge, manful... + +[TM3_A] +~w~Don Salvatore has called a meeting. + +[TM3_B] +~w~I need you to collect the limo and his boy, Joey, from the garage. + +[TM3_C] +~w~Then get Luigi from his club, come back here and pick me up, + +[TM3_D] +~w~then we'll all drive over to the boss's place together. + +[TM3_E] +~w~Those Triads, they don't know when to stop. + +[TM3_F] +~w~They want a war. They got a war. + +[TM3_G] +~w~Now get going. + +[TM3_1] +~g~Pick up the Stretch from Joey's. + +[TM3_2] +~g~Now go pick up Luigi. + +[TM3_3] +~g~Now go pick up Toni. + +[TM3_4] +~g~Drive the goodfellas to Salvatore's place. + +[TM3_5] +~y~It's a triad ambush!! + +[TM4_B] +~w~We're at WAR! The Triads have a fish factory as a front. + +[TM4_C] +~w~Most of their business goes down at the fish market in Chinatown. + +[TM4_D] +~w~That laundry still owes us protection. + +[TM4_E] +~w~They reckon the Triads are protecting them now, so I say we exact a fitting punishment. + +[TM4_F] +~w~Take these boys over and whack the Triad Warlords! + +[TM4_G] +~w~Hell, if you get a chance, pop some of their soldiers too. + +[TM4_GAT] +~g~You need a 'Triad fish van' to enter. + +[TM5_B] +~w~OK, I've had enough of this shit. + +[TM5_C] +~w~We're gonna finish the Triads in Liberty once and for all! + +[TM5_D] +8-Ball's rigged a dustcart with a bomb. + +[TM5_E] +~w~It's on a timer so if you mess up there'll be no evidence. Go and pick up the dustcart. + +[TM5_F] +~w~Careful, 8-Ball says it's real sensitive and the slightest bump could set that thing off! + +[TM5_G] +~w~Their fish factory will open its gates for a dustcart, so you can drive right in. + +[TM5_H] +~w~Park up between the gas canisters and get the hell out of there! + +[TM5_I] +~w~I want it to rain mackerel. + +[TM5_J] +~w~We're talking real biblical here, nothing low budget. + +[FM2] +'CUTTING THE GRASS' + +[FM4] +'LAST REQUESTS' + +[FM1_A] +~w~Me an' the fellas need to talk business + +[FM1_B] +~w~so you're gonna look after my girl for the evening. + +[FM1_C] +~w~HEY MARIA! MOVE YOUR BUTT! + +[FM1_D] +~w~Dumb broad does this every time. + +[FM1_E] +~w~And here she is, the one and only Queen of Sheba! + +[FM1_F] +~w~What were you doing up there? + +[FM1_G] +~w~Whatever it was, I bet it cost me money. + +[FM1_H] +~w~Well, you don't think I hang around for the conversation, do you? + +[FM1_I] +~w~Get in that car and keep your big mouth shut. + +[FM1_J] +~w~Take the limo but bring it back in one piece, y'hear me? + +[FM1_K] +~w~And watch her, she can be trouble. + +[FM1_L] +~w~Yeah, yeah, yeah! I'm sure your new lap dog has everything covered, + +[FM1_M] +~w~and isn't he big and strong? + +[FM1_N] +~w~Hey Fido, Let's go visit Chico and get some party treats! + +[FM1_P] +~g~That's Chico over there, pull up next to him. + +[FM1_S] +~w~Here you go lady. + +[FM1_TT] +~w~IT'S A POLICE RAID! + +[FM1_1] +~g~Get back into the Stretch! + +[FM1_2] +~g~Get into the Stretch! + +[FM1_3] +~r~Leave Maria and Salvatore will have you whacked, go back and pick her up. + +[FM1_4] +~g~You've dumped the Don's woman! Get back to the warehouse and wait for Maria! + +[FM1_5] +~g~Get Maria safely back to Salvatore's! + +[FM1_6] +~g~Chico won't be there forever, get Maria to the waterfront! + +[FM1_7] +~r~Maria's dead! Salvatore won't be too pleased... + +[FM1_8] +~r~You wasted Maria's supplier! + +[FM2_J] +Leave us alone for a minute. + +[FM2_A] +The Colombian Cartel is making SPANK somewhere in Liberty. + +[FM2_K] +but we don't know where, and they seem to know everything we're doin' before we do. + +[FM2_L] +There is a guy named Curly Bob works the bar at Luigi's. + +[FM2_M] +He's been throwing more money around than he's earning. + +[FM2_N] +He usually gets a taxi home after work. So follow him. + +[FM2_O] +And if he's rattin' us out... kill him. + +[FM2_F] +Here comes our little friend. Mr big mouth himself. + +[FM2_G] +Were you followed? You know what goes on here is our little secret. + +[FM2_H] +No..no, I wasn't followed. You got my stuff? + +[FM2_I] +Here's your SPANK, squealer, now talk. + +[FM2_P] +OK, so the Leone's are fighting wars on two fronts. + +[FM2_Q] +They're in a turf war with the Triads with no sign of either side giving up. + +[FM2_R] +Meanwhile Joey Leone has stirred up some bad blood with the Forellis. + +[FM2_S] +Every day they're losing men and influence in the city. + +[FM2_T] +Salvatore is becoming dangerous and paranoid. He suspects everybody and everything. + +[FM2_U] +With loyalty like yours, what has he possibly got to worry about. + +[FM2_1] +~g~There's Curly Bob! + +[FM2_2] +~g~Curly's left the club, tail him! + +[FM2_5] +~g~Take him to Portland Harbor. + +[FM2_6] +~r~Curly won't get into a smashed-up taxi! + +[FM2_7] +~r~Curly's spooked! The meeting's off! + +[FM2_8] +~g~Whack Curly Bob! + +[FM2_9] +~r~Curly Bob's dead! + +[FM2_10] +~r~Curly got away! + +[FM2_11] +~g~Park out the front of Luigi's Club, Curly Bob will be leaving shortly. + +[FM2_12] +~r~He gave you the slip! + +[FM3_A] +~w~We should take these Colombian bastards out, + +[FM3_B] +~w~but while we're at war with the Triads we ain't strong enough. + +[FM3_C] +~w~The Cartel has got bottomless funds from pushing that SPANK crap. + +[FM3_D] +~w~If we make an open attack on them, they'll wipe the floor with us. + +[FM3_E] +~w~They must be making SPANK on that big boat that Curly lead you to. + +[FM3_F] +~w~So we gotta use our heads, or rather one head. Your head. + +[FM3_G] +~w~I'm asking you to destroy that SPANK factory as a personal favor to me, Salvatore Leone. + +[FM3_H] +~w~If you do this for me, you will be a made man, anything you want. + +[FM3_I] +~w~Go and see 8-Ball, you'll need his expertise to blow-up that boat. + +[FM3_8A] +~w~Yo my man! Salvatore phoned ahead, + +[FM3_8B] +~w~but a job like this is gonna need a lot of fireworks. + +[FM3_8D] +~w~but you know with me you get a lot of bang for your buck. + +[FM3_8E] +~w~Okay, let's do this thing! + +[FM3_8F] +~w~I can set this baby to detonate, but I still can't use a piece with these hands. + +[FM3_8G] +~w~Here, this rifle should help you pop some heads! + +[FM3_4] +~g~Stop the vehicle and let 8-Ball out! + +[FM3_7] +~r~8-Ball's been iced! + +[FM3_8] +~r~The guards have been alerted! + +[FM4_A] +~w~It's my favorite cleaner. + +[FM4_B] +~w~I'm proud of you my boy, you kicked the shit out of those grease balls. + +[FM4_C] +~w~I've got just one little job for you before we can all celebrate. + +[FM4_D] +~w~There's a car around the block from Luigi's club. + +[FM4_E] +~w~The inside is covered in brains. + +[FM4_F] +~w~We had to help some guy make up his mind and it proved a little messy. + +[FM4_H] +~w~Take it to the crusher before the cops find it. + +[AM3] +'PAPARAZZI PURGE' + +[AM4] +'PAYDAY FOR RAY' + +[AM5] +'TWO-FACED TANNER' + +[AM1_A] +We have certain issues to clear up before we can continue any form of relationship, + +[AM1_B] +business or otherwise. Lets lay our cards on the table. + +[AM1_C] +I am Yakuza and I know you worked for Salvatore Leone's family. + +[AM1_D] +I can give you work with our organization, + +[AM1_E] +But first you must prove to me that your ties with the Mafia are truly broken. + +[AM1_G] +Make sure he doesn't reach his club alive. + +[AM1_H] +Meanwhile Maria and I will catch up on old times. + +[AM1_I] +Oh..Asuka, you've got a massager. + +[AM1_J] +That's not a massager. + +[AM1_1] +~g~Salvatore is now leaving Luigi's! + +[AM1_2] +~r~You have been spotted! + +[AM1_3] +~r~You've missed Salvatore! + +[AM1_4] +~r~Nice going, you scared off the target! Call yourself a hitman? + +[AM1_5] +~g~Get to the Red Light District and wait for Salvatore to leave the club. + +[AM1_7] +~r~Salvatore's home, safe and sipping a cocktail. Ain't no one gonna call you the 'Jackal'! + +[AM1_8] +~g~Salvatore will be leaving Luigi's at about ~1~:~1~ + +[AM2_4] +~g~They seen you coming like a dayglow elephant! + +[AM3_A] +A reporter has been nosing around. + +[AM3_B] +Maria and I have taken a little holiday together until you can get rid of this perverted voyeur. + +[AM4_A] +It's my handsome handyman! + +[AM4_B] +Maria's all tied up at the moment but I'll tell her you called. + +[AM4_C] +Who's that? Asuka? I know I've been a naughty girl but I really need to pee! OK? + +[AM4_D] +It's time you met our man inside the LPD. + +[AM4_E] +Here's his payment for the last little job he did for us. + +[AM4_F] +He is understandably cautious. + +[AM4_G] +Get to the pay phone in Torrington as quick as you can and await his instructions. + +[AM5_A] +Maria and I have gone shopping. + +[AM5_B] +Our source in the police has informed us that one of our drivers is a strangely animated undercover cop! + +[AM5_C] +He's more or less useless out of his car, so we've tagged it with a tracer. + +[AM5_D] +Make him bleed! + +[AM5_1] +Tanner's on to you! + +[AS1] +'BAIT' + +[AS2] +'ESPRESSO-2-GO!' + +[AS4] +'RANSOM' + +[AS1_A] +~w~Miguel seems to think I'm mistreating him. + +[AS1_B] +~w~Still, he's revealed the extent to which Catalina fears your quest for revenge. + +[AS2_A] +~w~We underestimated Catalina's plans for SPANK. + +[AS2_B] +~w~It reaches far beyond the Yardies selling it on the street corners. + +[AS2_D] +~w~They've been selling SPANK through the street stalls. + +[AS2_1] +~g~All espresso stalls in Portland wrecked!! + +[AS2_2] +~g~All espresso stalls in Staunton Island wrecked!! + +[AS2_3] +~g~All espresso stalls in Shoreside Vale wrecked!! + +[AS2_4] +~r~The Cartel have warned their pushers!! + +[AS2_5] +~g~There are still espresso stalls in Shoreside Vale and on Staunton Island! + +[AS2_6] +~g~There are still espresso stalls in Shoreside Vale! + +[AS2_7] +~g~There are still espresso stalls on Staunton Island! + +[AS2_8] +~g~There are still espresso stalls in Portland! + +[AS2_9] +~g~There are still espresso stalls in Portland and Shoreside Vale! + +[AS2_10] +~g~There are still espresso stalls in Portland and on Staunton Island + +[AS2_12] +~g~Cruise Liberty's districts to find ~b~Espresso-2-Go stalls! + +[AS3_A] +~W~Do we tighten it some more now, or just wait for it to turn black and fall off? + +[AS3_B] +~w~Give it a quick prod... + +[AS3_D] +~w~My Handyman! + +[AS3_E] +~w~I was bored so I came over to keep Asuka company. + +[AS3_1] +~g~Find the ~r~boat~g~ and get to the ~b~marker buoy! + +[AS3_3] +~g~Wait for the ~y~plane~g~ to start its approach! + +[AS3_5] +~g~Collect the cargo! + +[AS3_4] +~g~Use a rocket launcher to shoot the ~y~plane~g~ down!! + +[AS3_2] +~b~Get to the runway marker buoys! ~y~The plane is on its final approach!! + +[AS3_6] +~g~~1~ OF 8 + +[KM1] +'KANBU BUST-OUT' + +[KM3] +'DEAL STEAL' + +[KM4] +'SHIMA' + +[KM5] +'SMACK DOWN' + +[KM1_A] +My sister speaks highly of you, + +[KM1_E] +though I am yet to be convinced that a gaijin can offer anything but disappointment. + +[KM1_B] +Perhaps you could help deal with a situation that has me at a disadvantage. + +[KM1_F] +Of course failure has its own disgrace. + +[KM1_C] +A Yakuza Kanbu is in custody awaiting transfer for trial. + +[KM1_G] +He is a valued member of the family. + +[KM1_H] +Break him out of custody and get him to the dojo at Bedford Point. + +[KM1_D] +We thankyou for your selfless actions. If you ever need help the dojo will be honoured to provide two men who will stand at your side. + +[KM1_1] +~g~Steal a cop car! + +[KM1_2] +~g~Rig the car with a bomb! + +[KM1_3] +~g~Now get him to the Yakuza dojo. + +[KM1_5] +~g~Okay now go to the police station. + +[KM1_6] +~g~Fit the car with a bomb! + +[KM1_7] +~g~Authorised police vehicles only! + +[KM1_9] +~r~You did not use a car bomb to destroy the wall + +[KM1_10] +~r~The Yakuza Kanbu is dead -along with your honor! + +[KM1_11] +~r~You brought the heat down on yourself! + +[KM2_A] +It is impossible to over-estimate the importance of etiquette in this line of work. + +[KM2_B] +To my eternal shame, a man once did me a favor and I have never had the opportunity to repay his kindness. + +[KM2_C] +The man's weakness is motor cars and he has requested that we acquire him certain models for his collection. + +[KM2_F] +My honor demands it. + +[KM2_2] +~g~Car delivered. + +[KM3_A] +When trouble looms, the fool turns his back, while the wise man faces it down. + +[KM3_B] +The Colombian Cartel have ignored repeated requests to leave our interests in Liberty well alone. + +[KM3_C] +Now they are negotiating terms with the Jamaicans in order to humiliate us further. + +[KM3_D] +They are finalizing a deal across town. + +[KM3_F] +Take one of my men, steal a Yardie car, and go and pay your respects to the Colombians. + +[KM3_E] +Our Honor demands that you leave no one alive. + +[KM3_2] +~g~Go and pick up your contact. + +[KM3_3] +~g~The meeting is being held in the hospital parking lot in Rockford! + +[KM3_4] +~r~They got away! + +[KM3_6] +~g~Kill them, kill them all! + +[KM3_8] +~g~You need a Yardie car to get on with the job! + +[KM3_9] +~r~One of the Colombians is dead, the deal's off. + +[KM3_10] +~r~The contact is dead! + +[KM4_A] +To be truly strong, it is important that you never show weakness. + +[KM4_C] +Go and collect the money immediately, so we can enter it into the casino accounts. + +[KM4_1] +I can't pay you and I wouldn't pay you if I could! + +[KM4_9] +Some young gang just jacked out the place! They took everything! + +[KM4_2] +You guys are useless. + +[KM4_10] +What kind of Yakuza are YOU anyway...? + +[KM4_3] +This ain't what I pay you goons for. If I wanted this kind of protection I'd have used the god damn police service + +[KM4_4] +~g~Punish the gang responsible and retrieve the ~b~protection money~g~! + +[KM4_7] +~r~The shopkeeper's breathed his last! + +[KM4_5] +Donald Love wishes you to drop by his tea garden so you and he can talk. + +[KM4_6] +There's the money its all there! + +[KM4_8] +~g~Briefcase collected! + +[KM5_A] +YOU! How fitting you should choose this moment to show your worthless face! + +[KM5_B] +It would appear your attempts to dissuade the Jamaicans + +[KM5_B1] +from becoming bed fellows with the Cartel were wholly inadequate! + +[KM5_C] +Yardie pushers line Liberty's streets selling packets of SPANK like they were selling hotdogs! + +[KM5_D] +Those Cartel pigs are laughing at us, at me! + +[KM5_E] +I will give you one last chance to prove my sister's faith in you to be well founded! + +[KM5_F] +Run these scumbags into the ground and wash your shame in rivers of our enemies' blood!!! + +[KM5_3] +~r~You failed to kill at least ~1~ yardies. + +[KM5_4] +~g~Congratulations you killed ~1~ Yardies. + +[KM5_5] +~g~Congratulations you killed ~1~ Yardies. BONUS $~1~ + +[RM1] +'SILENCE THE SNEAK' + +[RM3] +'EVIDENCE DASH' + +[RM4] +'GONE FISHING' + +[RM5] +'PLASTER BLASTER' + +[RM1_D] +He's under armed protection in WitSec property down in Newport, some apartment behind the car park. + +[RM1_E] +Torch that place, that should flush 'em out, and you'll hunt 'em down, make sure he never talks to nobody. + +[RM1_1] +~g~Check out the witness protection house. + +[RM1_2] +~g~Take out McAffrey! + +[RM2_A1] +Hey kid over here! + +[RM2_A] +An old army buddy of mine runs a business in Rockford. + +[RM2_D] +He's gonna need some back-up and in return he'll give you knock-down rates on any hardware you buy. + +[RM2_E] +Ray phoned ahead....but I thought there'd be more of you. + +[RM2_F] +Well, three arms are better than one, so grab whatever you need. + +[RM2_G] +~g~Go and check on Phil! + +[RM2_H] +~r~Phil has been killed!! + +[RM2_L] +Heh-hey! If I'd teamed up with you in Nicaragua maybe I'd still have my arm! + +[RM2_N] +Leave the cash behind. Now get out of here, I'll handle the cops. + +[RM3_D] +The evidence is being driven across town. + +[RM3_E] +You are going to have to ram that car and collect each little bit of evidence as it falls out. + +[RM3_F] +When you've got it all, leave it in the car and torch it. + +[RM3_G] +We're both gonna do pretty well outta this kid. + +[RM3_1] +~g~Leave the evidence in a car then torch the car. + +[RM3_4] +~g~The Prosecution has dropped the evidence! + +[RM3_6] +~r~The photos will be washed up all over Liberty! + +[RM3_7] +~g~Now torch the car! + +[RM4_A] +I think my partner's a rat. + +[RM4_C] +He goes fishing out of his boat near the lighthouse on Portland Rock most nights. + +[RM4_D] +Steal a police boat and make sure his back stabbing plans are sunk! + +[RM4_1] +~g~Go and steal a police boat. + +[RM4_2] +~g~Get to the lighthouse and 'rub out' Ray's partner! + +[RM5_A] +You useless bastard! + +[RM5_A1] +You totally messed up! My ass is on the line and you can't even kill a god damned fly. + +[RM5_B] +I paid you good money to kill that witness and he ain't dead! + +[RM5_B1] +And today he's gonna make a Federal Deposition! + +[RM5_C] +He's being moved any second now from the Carson General Hospital up in Rockford. + +[RM5_D] +If he squeals, I squeal.... + +[RM5_E] +so go do the job you were paid for! + +[RM5_1] +~g~Intercept the ambulance. + +[RM5_2] +~g~You've been spotted!! + +[RM5_3] +~g~It was a decoy! + +[RM5_4] +~g~Bullets won't get through that armored bodycast!! + +[RM5_5] +~g~That armored bodycast is flame retardant!! + +[RM5_7] +~r~Witness has been delivered!! + +[RM5_8] +~g~Witness has drowned!! + +[LOVE2] +'WAKA-GASHIRA WIPEOUT!' + +[LOVE3] +'A DROP IN THE OCEAN' + +[LOVE1_A] +First of all, let me thank you for dealing with that personal matter. + +[LOVE1_F] +People will read something into anything these days. + +[LOVE1_D] +They're trying to extort additional funds from me but I don't believe in re-negotiation. + +[LOVE1_E] +A deal is a deal, so they'll not see a penny from me. + +[LOVE1_G] +Go and rescue my friend, do whatever it takes. + +[LOVE1_2] +~g~Rescue the Old Oriental Gentleman. + +[LOVE1_3] +~g~Take the Old Oriental Gentleman back to Donald Love's building. + +[LOVE1_4] +~g~The Old Oriental Gentleman must be in one of the garages.... + +[LOVE1_6] +~r~The Old Oriental Gentleman's guts are all over the street! + +[LOVE1_7] +~g~The gate will only open for a Colombian Gang-car. + +[LOVE2_A] +Nothing drives down real estate prices like a good old fashioned gang war, + +[LOVE2_B] +apart from an outbreak of plague......but that might be going too far in this case. + +[LOVE2_C] +I've noticed the Yakuza and the Colombians are far from friends. + +[LOVE2_D] +Let's capitalise on this business opportunity. + +[LOVE2_E] +I want you to kill the Yakuza Waka-gashira, Kenji Kasen. + +[LOVE2_F] +Kenji is attending a meeting at the top of the multi-story carpark in Newport. + +[LOVE2_G] +Get a Cartel gangcar and eliminate him! + +[LOVE2_H] +The Yakuza must blame the Cartel for this declaration of war. + +[LOVE2_1] +~g~Go to Fort Staunton and steal a Colombian gangcar! + +[LOVE2_2] +~g~Now get to the ~p~multi-storey in Newport~g~ and whack Kenji! + +[LOVE2_3] +~r~If you proceed without a Cartel car you will be identified!! + +[LOVE2_4] +~r~The Yakuza have identified you!! + +[LOVE2_6] +~r~You've killed all the witnesses!! + +[LOVE3_A] +In these days of moral hypocrisy certain valuable commodities can be hard to import. + +[LOVE3_C] +It will drop several packages into the water. + +[LOVE3_D] +Make sure you pick them up before anyone else does. + +[LOVE3_1] +~g~Get a ~r~boat~g~ and follow the ~y~plane~g~! + +[LOVE4] +'GRAND THEFT AERO' + +[LOVE5] +'ESCORT SERVICE' + +[LOVE4_A] +Thank you for retrieving those packages, but they were only a decoy. + +[LOVE4_B] +Sorry about that, but that's sometimes the way in business. + +[LOVE4_C] +My real objective was hidden on the plane all along. + +[LOVE4_F] +I've paid off the officials. + +[LOVE4_1] +~r~The Colombian Cartel is here!! + +[LOVE4_2] +~g~The package is gone! Track down the Colombians and retrieve it. + +[LOVE4_3] +~g~Panlantic Construction...? + +[LOVE4_5] +~g~The package should be in the plane.... + +[LOVE4_6] +~g~Take the lift up the tower! + +[LOVE5_B] +My Oriental friend will need an escort while he takes my latest acquisition to be authenticated. + +[LOVE5_1] +~g~Lets go! + +[LOVE5_2] +~g~You'll need a car! + +[LOVE5_3] +~g~Go ahead and scout the exit of the tunnel! + +[LOVE5_4] +~r~Protect the truck! + +[RM6] +'MARKED MAN' + +[RM6_A] +You weren't followed? Good. + +[RM6_B] +This is it, I'm way over my head and I'm starting to drown here! + +[RM6_D] +I'm a marked man, so I'm getting out of here. + +[RM6_E] +Get me to my flight at the airport and I'll make it worth your while! + +[RM6_666] +Take care of my bullet proof Patriot. See you in Miami, Ray + +[CAT1] +'RANSOM' + +[CAT2] +'THE EXCHANGE' + +[CAT1_A] +I've got your precious Maria. If you don't want her face to look like she fell out with the butcher. + +[CAT2_F] +I broke a nail, and my hair's ruined. Can you believe it? This one cost me fifty dollars! + +[CAT2_G] +I was so scared, but then I thought to myself, you're a big girl now. + +[CAT2_H] +Oh we're going to have such fun, cause, you know, my sister said she wanted to come to stay with her two kids, + +[CAT2_I] +because her husband's playing around again and.. + +[CAT1_E] +XXXX + +[CAT1_F] +Get to Catalina before the time runs out! + +[CAT_MON] +~g~You don't have enough money yet. You need $500,000. + +[BITCH_D] +~g~Maria's dead! + +[WEATHER] +FORCE WEATHER + +[WEATHE2] +WEATHER NORMAL + +[8001] +You failed miserably!! + +[1000] +YOU ARE DEAD + +[1001] +YOU ARE DEAD + +[1002] +YOU ARE DEAD + +[1003] +YOU ARE DEAD + +[1004] +YOU ARE DEAD + +[1005] +BUSTED + +[1006] +BUSTED + +[1007] +BUSTED + +[1008] +BUSTED + +[1009] +BUSTED + +[GA_4] +Car bombs are $1000 each + +[GA_5] +Your car is already fitted with a bomb. + +[GA_6] { re3 change } +Park it, prime it by pressing the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ and LEG IT! + +[GA_7] { re3 change } +Arm with ~h~~k~~VEHICLE_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + +[GA_8] +Use the detonator to activate the bomb. + +[GA_9] +You collected ~1~ out of 10 special cars + +[GA_10] +Nice one. Here's your $~1~ + +[GA_11] +We got these wheels already. It's worthless to us! + +[GA_12] +Bomb armed + +[GA_13] +Delivered like a pro. Complete the list and there'll be a bonus for you. + +[GA_14] +All the cars. NICE! Here's a little something. + +[GA_15] +Hope you like the new color. + +[GA_16] +Respray is complementary. + +[GA_19] +We're not interested in that model. + +[GA_20] +We got more of these than we can shift. Sorry man, no deal. + +[CR_1] +Crane cannot lift this vehicle. + +[PU_MONY] +You don't have enough cash. + +[CO_ALL] +You got all of them. Here's a little something... + +[PAUSED] +GAME PAUSED + +[HEALTH1] +Get outta here! You're perfectly healthy. + +[HEALTH2] +Healthcare costs. + +[HEALTH3] +I'll just fix you up. + +[HEALTH4] +That will be $250. + +[FEB_STA] +Stats + +[FEB_BRI] +Briefs + +[FEB_CON] +Controls + +[FEB_AUD] +Audio + +[FEB_DIS] +Display + +[FEB_LAN] +Language + +[FEP_STA] +STATS + +[FEP_BRI] +BRIEFS + +[FEP_CON] +CONTROLS + +[FEP_AUD] +AUDIO + +[FEP_DIS] +DISPLAY + +[FEP_LAN] +LANGUAGE + +[FEF_ST1] +Who's the bad man? + +[FEF_ST2] +How much havoc have you caused + +[FEF_BR1] +Lost the plot? + +[FEF_CO1] +Need more control, freak? + +[FEF_CO2] +Choose the best contoller set-up for your playing style + +[FEF_SA1] +Keep your place in the pile! + +[FEF_SA2] +Save and load your games + +[FEF_AU1] +Pump up the volume! + +[FEF_AU2] +Select a radio station and sound effect + +[FEF_DI1] +Change the game! + +[FEF_DI2] +Customize the game for your TV + +[FEF_LA1] +What you talking about willis? + +[FEF_LA2] +Choose your preferred parlance + +[FEB_PMB] +Previous Mission Briefs: + +[FEC_NA] +NA + +[FEC_CWL] +Cycle Weapon left + +[FEC_CWR] +Cycle Weapon right + +[FEC_LOF] +Look forward + +[FEC_TAR] +Target + +[FEC_MOV] +Movement + +[FEC_CAM] +Camera modes + +[FEC_PAU] +Pause + +[FEC_ENV] +Enter vehicle + +[FEC_JUM] +Jump + +[FEC_ATT] +Attack or Fire weapon + +[FEC_RUN] +Run + +[FEC_FPC] +First person camera + +[FEC_LL] +Look left + +[FEC_LB1] +Look + +[FEC_LB2] +behind + +[FEC_LB] +Look behind + +[FEC_LR] +Look right + +[FEC_HOR] +Horn + +[FEC_VES] +Vehicle control + +[FEC_RSC] +Radio station cycle + +[FEC_BRA] +Brake or Reverse + +[FEC_HAB] +Hand brake + +[FEC_CAW] +Car weapon + +[FEC_ACC] +Accelerate + +[FEC_SMT] +Special mission trigger + +[FEA_OUT] +Output: + +[FEA_ST] +Stereo + +[FEA_MNO] +Mono + +[FEA_NON] +None + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX 109 + +[FED_DBG] +Menu Debug + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_SPR] +Show Ped Road Groups + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_DSR] +Debug Streaming Requests + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Memory Card Menu + +[FEM_RMC] +Register MemCard One + +[FEM_TFM] +Test Format MemCard One + +[FEM_TUM] +Test UnFormat MemCard One + +[FEM_CRD] +Create Root Dir + +[FEM_CLI] +Create And Load Icons + +[FEM_FFF] +Fill First File with Guff + +[FEM_SOG] +Save Only The Game + +[FEM_CES] +Check Every 0kB4 Save + +[FEM_STG] +Save The Game + +[FEM_STS] +Save The Game under GTA3 name + +[FEM_CPD] +Create copy protected mag directory + +[FEM_MC2] +Memory Card Menu 2 + +[FEM_TS] +Test Save: + +[FEM_TL] +Test Load: + +[FEM_TD] +Test Delete: + +[PL_STAT] +Player stats + +[PE_WAST] +People you've wasted + +[PE_WSOT] +People wasted by others + +[CAR_EXP] +Cars exploded + +[TM_BUST] +Times busted + +[M_WASTE] +Civilian males wasted + +[F_WASTE] +Civilian females wasted + +[PIG_WST] +Cops wasted + +[GNG_WST] +Gang members wasted + +[MED_WST] +Medics wasted + +[FIRE_WS] +Firemen wasted + +[DED_CRI] +Criminals wasted + +[DED_DED] +Deadbeats wasted + +[DED_HOK] +Hookers wasted + +[HEL_DST] +Helicopters destroyed + +[PER_COM] +Percentage completed + +[KGS_EXP] +Kgs of explosives used + +[ACCURA] +Accuracy + +[ELBURRO] +Best Turismo time in secs + +[CAR_CRU] +Cars crushed + +[HED_EX] +Heads exploded + +[TM_DED] +Hospital visits + +[DAYSPS] +Days passed in game + +[MMRAIN] +Mm rain fallen + +[MXCARD] +Max. INSANE Jump dist. (ft) + +[MXCARJ] +Max. INSANE Jump height (ft) + +[MXCARDM] +Max. INSANE Jump dist. (m) + +[MXCARJM] +Max. INSANE Jump height (m) + +[MXFLIP] +Max. INSANE Jump flips + +[MXJUMP] +Max. INSANE Jump rotation + +[BSTSTU] +Best INSANE stunt so far: + +[INSTUN] +Insane stunt + +[PRINST] +Perfect insane stunt + +[DBINST] +Double insane stunt + +[DBPINS] +Perfect double insane stunt + +[TRINST] +Triple insane stunt + +[PRTRST] +Perfect triple insane stunt + +[QUINST] +Quadruple insane stunt + +[PQUINS] +Perfect quadruple insane stunt + +[NOSTUC] +No INSANE stunts completed + +[NOUNIF] +Unique Jumps completed + +[NOUNGM] +Total Unique Jumps + +[NMISON] +Mission attempts + +[NMMISP] +Missions passed + +[PASDRO] +Passengers dropped off + +[MONTAX] +Cash made in taxi + +[DAYPLC] +Daily police spending + +[CRIMRA] +Criminal rating: + +[GMSTOR] +Game Store + +[PREBRF] +Previous Briefs + +[CNTLS] +Controls + +[MUSMEN] +Music/SFX + +[GAMSET] +Game Settings + +[LANGUA] +Language + +[DSPLAY] +Display + +[DEBUGM] +Debug Menu + +[QUITOP] +Quit Options + +[CONTRL] +Control Configuration + +[SET1EN] +SetUp 1. Enabled + +[SET1] +SetUp 1. + +[SET2EN] +SetUp 2. Enabled + +[SET2] +SetUp 2 + +[SET3EN] +SetUp 3. Enabled + +[SET3] +SetUp 3 + +[SET4EN] +SetUp 4. Enabled + +[SET4] +SetUp 4 + +[GOBACK] +GoBack + +[SOUND] +SOUND + +[MUSVOL] +Music Volume + +[SFXVOL] +SFX Volume + +[SCROPT] +SCREEN OPTIONS + +[CTRSCR] +Center Screen + +[SCRFOR] +Screen format + +[GMSVLQ] +GAME SAVE-LOAD-QUIT + +[GMREST] +Restart Game + +[NOGMSV] +Can save only at your hideout. + +[DLFILE] +Delete Grand Theft Auto III Files + +[CHFILE] +CHOOSE FILE TO LOAD + +[CHFIDL] +CHOOSE FILE TO DELETE + +[SVCONF] +SAVE CONFIRMATION + +[LANGSL] +LANGUAGE SELECTION + +[ENGLIS] +English + +[GERMAN] +German + +[ITALIA] +Italian + +[FRENCH] +French + +[SPAIN] +Spanish + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Parse Heap + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[CRRGON] +ShowCarRoadGroups On + +[CRGOFF] +ShowCarRoadGroups Off + +[CLZOON] +Show Cull Zones On + +[CLZOOF] +Show Cull Zones Off + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (teststuff) + +[UNFRM1] +UnFormatMemCard 1 (teststuff) + +[GORLEV] +Gore Level + +[SICASS] +Sick Fuck + +[SICSIC] +Sick Fucker + +[SCASSL] +Sick Fuck Selected + +[SCSCSL] +Sick Fucker Selected + +[PRVMEN] +Previous Mission Briefs + +[FORMEN] +Format Menu + +[MEMTST] +MemoryCardTest screen + +[REGCAR] +Register MemoryCard One + +[TEFONE] +Test Format MemCard One + +[TEUFON] +Test UnFormat MemCard One + +[CRROOT] +CreateRootDir + +[CRLDIC] +Create and Load Icons + +[FLFSGF] +Fill First File With Guff + +[PUSAVE] +Save Only the game + +[CHEVOK] +CheckEveryOkB4Save + +[SVGMON] +SaveTheGame + +[CNTSAV] +Can't save the game. On a mission. + +[CNCSAV] +Can't save the game. You're in a car + +[CRMGSV] +Create copy protected magazine directory + +[MGSVCN] +MagazineDirectory Created + +[MGSVNC] +MagazineDirectory Not Created + +[YES] +Yes + +[NO] +No + +[X] +x + +[LAST] +Last message. + +[FEDS_XB] +Select + +[FEDS_ST] +START button - RESUME + +[FEST_OO] +out of + +[FEC_TUC] +Turret control + +[FEC_SM3] +Special mission trigger (R3 button) + +[FEC_RS3] +Radio station cycle (L3 button) + +[FEC_HO3] +Horn (L3 button) + +[DIAB1] +'TURISMO' + +[DIAB2] +'I SCREAM, YOU SCREAM' + +[DIAB3] +'TRIAL BY FIRE' + +[DIAB4] +'BIG'N'VEINY' + +[DIAB1_A] +El Burro wants to offer you an opportunity. Get to the payphone in Hepburn Heights if you want more info. + +[DIAB1_C] +You drive a mean race. Drop by the payphone again and 'El Burro' may have some work for you. + +[DIAB1_1] +~g~3..2..1.. GO GO GO! + +[DIAB1_4] +~g~Get a fast car and get to the starting grid. + +[DIAB1_3] +~r~You couldn't win a raffle, LOSER! + +[DIAB1_2] +~g~Congratulations you won, with an incredible time of ~1~ seconds. + +[FIRST] +~g~1st + +[SECOND] +~g~2nd + +[THIRD] +~g~3rd + +[FOURTH] +~g~4th + +[DIAB2_1] +~g~Pick up the briefcase in Harwood. + +[DIAB2_2] +~g~Find an icecream van. + +[DIAB2_3] +~g~Park the icecream van down at Atlantic Quays. + +[DIAB2_4] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_6] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_7] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_5] +~g~Exit the van then use the remote to detonate the Icecream van. + +[YD1] +'BLING-BLING SCRAMBLE' + +[YD2] +'UZI RIDER' + +[YD3] +'GANGCAR ROUND-UP' + +[YD4] +'KINGDOM COME' + +[YD_P] +King Courtney would like a word. Get to the payphone in Aspatria!! + +[YD1_A] +~w~This is King Courtney. + +[YD1_A1] +~w~My Yardie posse could do with a driver and you've got a reputation for hot moves. + +[YD1_B] +~w~Get to the waste ground opposite the stadium in a car and wait for the other hopefuls. + +[YD1_C] +~w~I've got men watching checkpoints all over Staunton. + +[YD1_D] +~w~First driver to a checkpoint gets a Grand, then it's on to the next stop. + +[YD1_D1] +~w~If you get more checkpoints than any other driver, I could have some work for you. + +[YD1_E] +~g~Prepare to race! + +[YD1_F] +~g~You jumped the start -I like your style!! + +[YD1_G] +~r~This is a CAR RACE. You need a CAR fool! + +[YD1GO] +~g~GO!! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +$1000!! + +[Y1_1ST] +~g~You came first with ~1~ successful checkpoints! + +[Y1_2ND] +~y~2nd with ~1~ successful checkpoints. ~y~Close, but you just ain't the best! + +[Y1_3RD] +~r~3rd with ~1~ successful checkpoints. ~r~I thought you said you were good! + +[Y1_LAST] +~r~You were last! ~r~You wasted my time FOOL! + +[Y1_J1ST] +~y~Joint 1st with ~1~ successful checkpoints. ~y~Good, but you gotta be the best to drive for Queen Lizzy! + +[Y1_J2ND] +~r~Joint 2nd with ~1~ successful checkpoints. You drive like a crazed monkey! + +[Y1JLAST] +~r~Joint last! You talk like a driver, but you drive like a talker! + +[Y1_TEST] +CAR IN WATER!! + +[YD2_A] +~w~I need to see if you're capable of doing my dirty work. + +[YD2_A1] +~w~See if you can be trusted. + +[YD2_B] +~w~Two of my boys will be there any second to take you for a ride, + +[YD2_B1] +~w~see if you are who you say you are. + +[YD2_C] +~w~We're going for a little ride into Hepburn Heights, whack us some filthy Diablos been dissing Queen Lizzy. + +[YD2_CC] +~w~Here, you'll need a 'piece'. + +[YD2_D] +~w~You do the driving and shooting. We'll make sure you don't get cold feet. + +[YD2_E] +~w~Let's drive!! + +[YD2_F] +~r~He's bailed out on us, cap his yellow ass!!! + +[YD2_G1] +~w~Hepburn Heights..Let's kill me some filthy Diablos... + +[YD2_G2] +~w~But remember, ~r~You don't leave this car!! + +[YD2_H] +~w~OK, Get us back to Yardie turf! GO GO GO!! + +[YD2_L] +~w~You did good, Reaperman! + +[YD2_M] +~r~He's wrecked my car! Waste him! + +[YD2_N] +~w~Get your ass back in this car! + +[YD3_A] +I want you to boost some gang cars + +[YD3_A1] +so we can do hits on our enemies' turf. + +[YD3_B] +I need a Mafia Sentinel, + +[YD3_B1] +a Yakuza Stinger and a + +[YD3_B2] +Diablo Stallion so we can hit any gang in Liberty. + +[YD3_C] +Drop them off at the garage in Newport and remember, + +[YD3_C1] +they're no use to us wrecked!! + +[YD3_D] +Spare text label + +[YD3_E] +~r~You've already boosted a diablo gangcar! + +[YD3_F] +~r~You've already boosted a Mafia gangcar! + +[YD3_G] +~r~You've already boosted a Yakuza gangcar! + +[YD3_H] +~g~Diablo gangcar boosted! + +[YD3_I] +~g~Mafia gangcar boosted! + +[YD3_J] +~g~Yakuza gangcar boosted! + +[YD3_K] +~r~The car's nearly wrecked! Get it repaired! + +[YD3_L] +~g~Take it to the ~p~garage! + +[YD3_M] +~r~You've flipped it! Get another one! + +[YD4_A] +Listen up! + +[YD4_A1] +Get over to Bedford Point. + +[YD4_A2] +There's a stash in an old car I need pronto! + +[YD4_B] +LETTER: I hear you've been a busy boy. Well I've been a busy girl. + +[YD4_C] +I think it's time you witnessed the real power of 'SPANK'! Besos y fuderes, Catalina, xxx. + +[YD4_D] +PS: DIE PEEG DOG, DIE!! + +[YD4_1] +~g~SPANKED-up madmen! + +[YD4_2] +~g~Destroy the madmens' vans!! + +[HM_1] +'UZI MONEY' + +[HM_2] +'TOYMINATOR' + +[HM_3] +'RIGGED TO BLOW' + +[HM_5] +'RUMBLE' + +[HOOD1_A] +Get to the payphone in Wichita Gardens and we'll talk business. + +[HM1_A] +Yo! This is D-Ice of the Red Jacks! + +[HM1_C] +These young punks, they come onto the streets and they got nothing but guns and SPANK on their minds. + +[HM1_3] +~g~The 'Nines' walk their turf in Wichita Gardens. + +[HM2_3] +If you hit a vehicle's wheels the RC buggy will detonate! + +[HM2_4] +If it goes out of range the RC buggy will detonate! + +[HM2_5] +~r~Out of range! + +[HM3_1] +~g~Get to the garage but watch out if the car takes too much damage it will blow! + +[HM3_2] +~g~Take the car back it has to be in perfect condition - no damage! + +[HM3_3] +~g~Get the car repaired! + +[HM4_D] +~g~Get a vehicle! + +[HM4_E] +TEXT NO LONGER REQUIRED + +[HM4_1] +~g~Head to the location where the cargo is scattered, you need to collect 30 pieces of bullion. + +[HM4_2] +~g~Remember when the vehicle becomes too heavy and slow goto the garage and drop off the cargo. + +[HM5_3] +~r~You were told to use a baseball bat only! + +[HM5_4] +~r~Your contact's dead! + +[MEA1] +'THE CROOK' + +[MEA2] +'THE THIEVES' + +[MEA3] +'THE WIFE' + +[MEA4] +'HER LOVER' + +[MEAT1_A] +A friend said you could fix some problems I got. Get to the payphone in Trenton if you think you can help. + +[MEA1_B3] +~g~Go and meet the Bank Manager. + +[MEA1_B6] +~g~Take the car to the crusher to get rid of evidence, get out of the car and the crane will pick it up. + +[MEA1_1] +~r~The Bank Manager's dead! + +[MEA1_2] +~r~You were told to crush the vehicle! + +[MEA1_3] +~g~Get out of the car! + +[MEA1_4] +~r~You have left the Bank Manager behind! + +[MEA2_B3] +~g~Go and meet the thieves. + +[MEA2_B4] +~g~Take them to the Bitch'n' Dog Food Factory + +[MEA2_B6] +~g~Get the car resprayed to get rid of any evidence. + +[MEA2_1] +~r~You were told to crush the vehicle! + +[MEA2_2] +~r~A thief's dead! + +[MEA2_4] +~r~You have left a thief behind! + +[MEA3_B3] +~g~Go and collect Mrs. Chonks + +[MEA3_B6] +~g~Take the car and dump it into the sea, this will get rid of any evidence. + +[MEA3_1] +~r~The wife's dead! + +[MEA3_2] +~r~You were supposed to dump the vehicle in the water! + +[MEA3_3] +~r~You have left his wife behind! + +[MEA4_B3] +~g~Pick up his wife's lover + +[MEA4_B6] +It's far too late for that Marty. You had your chance, but now I'm taking over the business... + +[MEA4_1] +~r~Carlos is dead! + +[MEA4_3] +~r~You have left Carlos the loan shark behind! + +[LOOK_A] +Press and hold the ~h~~k~~VEHICLE_LOOKLEFT~ button ~w~or the ~h~~k~~VEHICLE_LOOKRIGHT~ button~w~ to look ~h~left~w~ or ~h~right~w~ while in a vehicle. Press both to look ~h~behind~w~. + +[LOVE6_1] +~g~Now lead the cops away from the warehouse! + +[LOVE6_2] +~r~You failed to lead the police far enough away! + +[RM4_3] +~r~Ray's partner has escaped! + +[RM6_C] +The CIA seem to have a vested interest in SPANK + +[RM6_C1] +and they don't like us screwing with the Cartel. + +[C_PASS] +THREAT ELIMINATED! + +[CTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Vigilante missions on or off. + +[CTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Vigilante missions on or off. + +[COPCART] +~g~You have ~1~ seconds to return to a police vehicle before the mission ends. + +[C_FAIL] +Vigilante mission ended! + +[C_CANC] +~r~Vigilante mission cancelled! + +[C_ESCP] +~r~The suspect has escaped! + +[C_TIME] +~r~Your time as a law enforcer is over! + +[C_VIGIL] +VIGILANTE BONUS!! + +[A_FAIL2] +~r~Your lack of urgency has been fatal to the patient! + +[A_FAIL3] +~r~The patient is dead!! + +[A_PASS] +Rescued! + +[F_FAIL2] +~r~You're too late! + +[A_COMP2] +You will never get tired! + +[RM2_M] +If you need any firepower just drop by and take what you need from the lockers. + +[HEAL_A] +Your ~h~health~w~ is displayed in orange in the top right of the screen. + +[YD1_CNT] +~1~ of 15! + +[FM1_9] +~g~Thats the party up ahead, drop Maria off out front. + +[FM1_Y] +~w~You know I enjoyed myself for the first time in a long while, and you treated me really good. With respect and everything. + +[FM1_AA] +~w~Oh, I'd better go. I'll see you around I hope. + +[NOCONTE] +Please re-insert the analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2) in controller port 1 to continue + +[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). + +[WRCONTE] +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). + +[WRONGCD] +Incorrect disc. Please insert correct disc. + +[NOCD] +The disc tray is empty. Please insert disc. + +[OPENCD] +The disc tray is open. Please close the disc tray. + +[CDERROR] +Error reading the Grand Theft Auto III DVD + +[RESTART] +Starting new game + +[GA_3] +No more freebies. $1000 to respray! + +[GA_1] +Whoa! I don't touch nothing THAT hot! + +[GA_1A] +Come back when you're not so busy... + +[S_PROM2] +The garage next door can store one vehicle when you save your game. + +[STOCK] +out of stock + +[FM1_O] +~w~He's at the rail station at the Chinatown waterfront I think. + +[EBAL_B] +This is the place right here, let's get off the street and find a change of clothes! + +[EBAL_G] +This is Luigi's club. Let's go round the back and use the service door. + +[AM4_3] +You must be Asuka's new errand boy! + +[AM4_4] +You got the money? Is it all here? + +[AM4_5] +I know what you're thinking, another bent cop. + +[AM4_6] +Well, it's a bent world. + +[AM4_7] +Just 'cause I lost a few partners, those suckers from internal affairs have started sniffing around. + +[AM4_8] +Reckon they can smell me. + +[AM4_9] +Well, this city is just one big open sewer. + +[AM4_10] +But I'm gonna need some non-union help. + +[AM4_11] +And if you're interested you'll know where to find me. + +[CAM_A] +Press the ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~ button~w~ to change ~h~camera ~w~modes when on foot or in a vehicle. + +[CAM_B] +Press the ~h~directional button up~w~ and ~h~down~w~ to change ~h~camera ~w~modes when on foot or in a vehicle. + +[KM2_1] +~g~Repair the car, it's gotta be mint. + +[LM3_6] +Joey... + +[LM3_6A] +Am I goin' to play with your big end again? + +[LM3_9A] +there might be some work for you. + +[LM3_9B] +Alright? + +[AWAY2] +~r~They got away. + +[AWAY] +~r~He's clean out of here! + +[JM6_1] +Get to the bank on the main drag. + +[GA_6B] { re3 change } +Park it, prime it by pressing the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ and LEG IT! + +[GA_7B] { re3 change } +Arm with ~h~~k~~VEHICLE_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + +[BAT1] +~g~Pick up the bat! + +[EBAL_O] +If you don't mess this up, maybe there be more work for you. Now get outta here! + +[HELP9_B] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HELP9_C] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[JM6_8] +~r~You've lost all the robbers! + +[COLT_IN] +The Pistol is now in stock at Ammunation! + +[TAXI2] +~r~You're out of time! + +[TAXI3] +~r~Your passenger fled in terror! + +[TAXI7] +~r~Your car is trashed, get it repaired. + +[TAXI4] +Fare complete! + +[TAXI5] +SPEED BONUS!! + +[TAXI6] +Taxi mission over + +[FRANGO] +~g~Salvatore wants you to help Toni deal with the Triads first! + +[PAGEB12] +Police Bribe delivered to hideout + +[PAGEB13] +Health delivered to hideout + +[PAGEB14] +Adrenaline delivered to hideout + +[KM1_4] +~g~You need a cop car to do the job! + +[CAT1_B] +bring $500,000 to the Villa at Cedar Grove. + +[JM2_C] +He's got a noodle stand down in China Town. + +[RM6_1] +Here's a key to a lock-up. + +[RM6_2] +You'll find some cash and some 'supplies' I'd stashed in case things got tight. + +[RM6_3] +See y'around. + +[FE_INIP] +Initialising and loading Pause Menu... Please wait. + +[FESZ_CA] +Cancel + +[FESZ_QU] +Quit + +[FESZ_L1] +Game saved successfully! + +[FESZ_L2] +Your saved filename is: + +[FESZ_OK] +OK + +[FES_LGA] +Load Game + +[FES_NGA] +New Game + +[FES_CAN] +Cancel + +[FESZ_QL] +All unsaved progress in your current game will be lost. Proceed with loading? + +[FESZ_QD] +Proceed with deleting this saved game? + +[FESZ_QO] +Proceed with overwriting this saved game? + +[FESZ_QR] +Are you sure you want to start a new game? All progress since the last save game will be lost. Proceed? + +[FESZ_QS] +PROCEED WITH SAVE ? + +[T4X4_1] +'PATRIOT PLAYGROUND' + +[T4X4_2] +'A RIDE IN THE PARK' + +[T4X4_3] +'GRIPPED!' + +[MM_1] +'MULTISTOREY MAYHEM' + +[T4X4_1A] +~g~You have ~y~5 minutes~g~ to collect ~y~15~g~ checkpoints. ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_1B] +~1~ of 15! + +[T4X4_1C] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~20 SECONDS~g~. + +[T4X4_2A] +~g~You have ~y~2 minutes~g~ to collect ~y~12~g~ checkpoints!! ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_2B] +~1~ of 12! + +[T4X4_2C] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~10 SECONDS~g~. + +[T4X4_3A] +~g~You have ~y~5 minutes~g~ to collect ~y~20~g~ checkpoints. ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_3B] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~15 SECONDS~g~. + +[T4X4_3C] +~1~ of 20! + +[T4X4_F] +~r~You bailed! Too tough for you?! + +[MM_1_A] +~g~You have ~y~2 minutes~g~ to collect ~y~20 checkpoints~g~ in the multistorey! ~g~You may collect them in ~y~ANY ORDER. + +[MM_1_B] +~1~ of 20! + +[MM_1_C] +~g~That's 20 seconds, plus ~y~5 SECONDS~g~ for each checkpoint. ~g~The timer will start ~y~IMMEDIATELY. + +[FM2_14] +~r~You got too close and spooked Curly! + +[FM2_15] +~g~Don't get too close or Curly will suspect something! + +[UPSIDE] +~r~You flipped your wheels! + +[FM2_16] +SPOOKOMETER: + +[LM3_11] +~g~Misty won't ride in a bus get another vehicle! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Firetruck + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulance + +[FBICAR] +Fbi Car + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Police + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[TRAIN] +Train + +[HELI] +Helicopter + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +RC Bandit + +[BELLYUP] +Triad Fish Van + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Mafia Sentinel + +[YARDICR] +Yardie Lobo + +[YAKUZCR] +Yakuza Stinger + +[DIABLCR] +Diablo Stallion + +[COLOMCR] +Cartel Cruiser + +[HOODSCR] +Hoods Rumpo XL + +[AEROPL] +Aeroplane + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Dist. travelled on foot (miles) + +[FEST_DC] +Dist. travelled by car (miles) + +[FESTDFM] +Distance travelled on foot (m) + +[FESTDCM] +Distance travelled by car (m) + +[FEST_R1] +Patriot Playground in secs + +[FEST_R2] +A Ride In The Park in secs + +[FEST_R3] +Gripped! in secs + +[FEST_RM] +Multistorey Mayhem in secs + +[FEST_LS] +People saved in an Ambulance + +[FEST_CC] +Criminals killed on Vigilante Mission + +[FEST_FE] +Total fires extinguished + +[FEST_LF] +Longest flight in Dodo + +[FEST_BD] +Best time for bomb defusal + +[FEST_RP] +Rampages passed + +[FEST_MP] +Missions passed + +[FEST_BB] +Bling-bling Scramble: + +[FEST_H0] +Most checkpoints + +[FEST_GC] +Gang Cars Totalled: + +[FEST_H1] +Diablo destruction + +[FEST_H2] +Mafia Massacre + +[FEST_H3] +Casino Calamity + +[FEST_H4] +Rumpo Wrecker + +[USJI1] +TEXT NO LONGER REQUIRED + +[USJI2] +TEXT NO LONGER REQUIRED + +[USJI3] +TEXT NO LONGER REQUIRED + +[USJ] +UNIQUE STUNT BONUS! + +[SPRAY] +Drive your vehicle into the spray shop to lose your ~h~wanted level~w~, ~h~repair ~w~and ~h~respray ~w~your vehicle. Cost - ~h~$1000. + +[HM1_1] +~g~Ice 20 Purple Nines in 2 minutes 30 seconds. + +[KM1_8A] { re3 change } +Press the~h~ ~k~~VEHICLE_FIREWEAPON~ button~w~ to ~h~activate the bomb,~w~ remember to get out of the way. + +[KM1_8D] { re3 change } +Press the~h~ ~k~~VEHICLE_FIREWEAPON~ button~w~ to ~h~activate the bomb,~w~ remember to get out of the way. + +[KM1_12] +~g~Get him to the dojo but get rid of the cops first! + +[RATNG1] +Pickpocket + +[RATNG2] +Bully + +[RATNG3] +Thug + +[RATNG4] +Hustler + +[RATNG5] +Goon + +[RATNG6] +Wheelman + +[RATNG7] +Hired muscle + +[RATNG8] +Fixer + +[RATNG9] +Associate + +[RATNG10] +Cleaner + +[RATNG11] +Assassin + +[RATNG12] +Right-hand man + +[RATNG13] +Executioner + +[RATNG14] +Capo + +[RATNG15] +Boss + +[1010] +~r~Your vehicle is upside down + +[1011] +~r~Your vehicle is upside down + +[1012] +~r~Your vehicle is upside down + +[1013] +~r~Your vehicle is upside down + +[1014] +~r~Your vehicle is upside down + +[JM4_10] +OK, Kid. Drive me to the laundry in Chinatown first, I got a bit of business to take care of. + +[JM4_11] +Those washer women aint been payin' their protection money. + +[JM4_12] +And watch the car, Joey just fixed this junk heap. + +[JM4_13] +So no fancy crap, OK? + +[KM4_11] +~g~Take the money back to the casino! + +[FEF_BR2] +Find it again by reading any mission briefs collected to date. + +[TRAIN_1] +Kurowski Station + +[TRAIN_2] +Rothwell Station + +[TRAIN_3] +Baillie Station + +[SUBWAY1] +Portland Station + +[SUBWAY2] +Rockford Station + +[SUBWAY3] +Staunton South Station + +[SUBWAY4] +Shoreside Terminal + +[MEA4_2] +~r~Marty Chonks is dead! + +[SPRAY1] +Drive your vehicle into the spray shop to lose your ~h~wanted level~w~, ~h~repair ~w~and ~h~respray ~w~your vehicle. Cost - ~h~$1000~w~. This time it's free. + +[JM4_A] +Yeah, I know Toni, I've tuned her real sweet. She purrs, you know what I mean? + +[JM4_5] +Drop by later and we'll give them something to launder, their own blood stained clothes! + +[AMMU_A] +Luigi said you'd need a piece... + +[AMMU_B] +Joey told me to tool you up... + +[AMMU_C] +So go around back of the shop. I left you a nine in the yard. + +[AMMU_D] +I got all your home defence needs. + +[AMMU_E] +You want a license too? + +[AMMU_F] +I don't need to see any I.D. you look trustworthy. + +[DETON] +DETONATION: + +[DRIVE_A] { re3 change } +Have an Uzi selected when entering a vehicle then look left or right and press the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ to fire. + +[DRIVE_B] { re3 change } +Have an Uzi selected when entering a vehicle then look left or right and press the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ to fire. + +[RECORD] +~g~NEW RECORD!! + +[NRECORD] +~r~NO NEW RECORD! + +[RCHELP] { re3 change } +Press ~k~~VEHICLE_FIREWEAPON~, or drive the RC car into a vehicle's wheels to detonate. + +[RCHELPA] { re3 change } +Press the ~k~~VEHICLE_FIREWEAPON~ button, or drive the RC car into a vehicle's wheels to detonate. + +[RC_1] +You have 2 minutes to blow up as many Diablo Gang Cars as possible! + +[RC_2] +You have 2 minutes to blow up as many Mafia Gang Cars as possible! + +[RC_3] +You have 2 minutes to blow up as many Yakuza Gang Cars as possible! + +[RC_4] +You have 2 minutes to blow up as many Yardie Gang Cars as possible! + +[RC_5] +You have 2 minutes to blow up as many Hood Gang Cars as possible! + +[RC_6] +You have 2 minutes to blow up as many Cartel Gang Cars as possible! + +[RAMPAGE] +RAMPAGE!! + +[RAMP_P] +RAMPAGE COMPLETE! + +[RAMP_F] +RAMPAGE FAILED + +[PAGE_00] +. + +[PAGE_01] +Murder ~1~ Diablos in 120 seconds! + +[PAGE_02] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_03] +Kill ~1~ Mafia in 120 seconds! + +[PAGE_04] +Kill ~1~ Triads in 120 seconds! + +[PAGE_05] +Kill ~1~ Triads in 120 seconds! + +[PAGE_06] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_07] +Pop ~1~ Yardie heads in 120 seconds! + +[PAGE_08] +Burn ~1~ Yakuza in 120 seconds! + +[PAGE_09] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_10] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_11] +Annihialate ~1~ Yardies in 120 seconds! + +[PAGE_12] +Torch ~1~ Yakuza in 120 seconds! + +[PAGE_13] +Explode ~1~ Yardies in 120 seconds! + +[PAGE_14] +Fry ~1~ Colombians in 120 seconds! + +[PAGE_15] +Splatter ~1~ Hoods in 120 seconds! + +[PAGE_16] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_17] +Splatter ~1~ Colombians with a car in 120 seconds! + +[PAGE_18] +Driveby and Destroy ~1~ vehicles in 120 seconds! + +[PAGE_19] +Remove ~1~ Colombian heads in 120 seconds! + +[PAGE_20] +Behead ~1~ Hoods in 120 seconds! + +[JM1_A] +Hey, I'm bored when you gonna drill me? + +[JM1_B] +In a moment sweet heart, I got a little business to take care of. + +[JM1_C] +I got a little job for you pal. + +[JM1_D] +The Forelli brothers have owed me money for too long + +[JM1_E] +and they need to be taught some respect. + +[JM1_F] +Lips Forelli is stuffing his fat face in St Marks Bistro, + +[JM1_G] +so steal his car and take it to 8-Ball's bomb shop up in Harwood. + +[JM1_H] +You know 8-Ball right? + +[JM1_I] +Once he's fitted it with a bomb, go park the car where you found it. + +[JM1_J] +Then sit back and watch the whole show. + +[JM1_K] +But hurry up, he won't be eating forever. + +[CAT2_A1] +Come on you dumb bitch! + +[CAT2_A] +The real question is, did you turn up to rescue Maria or to get me back? + +[CAT2_B] +Well I got news for you, + +[CAT2_B2] +shooting you will be a pleasure but dating you was only business. + +[CAT2_C] +You are muy peccinno amigo! + +[CAT2_D] +Throw over the cash. + +[CAT2_E] +You have been a busy boy! + +[CAT2_E2] +But you haven't learned, I'm not to be trusted. + +[CAT2_E3] +Kill the idiot. + +[CAT2_J] +Get this thing airborne!! + +[HM5_1] +Yo, Ice said was comin'. There rules. Bats only. No guns, no cars. + +[HM5_5] +This is a battle for respect, you cool? + +[HELP14] +To collect weapons walk through them. These cannot be collected while in a vehicle. + +[CRUSH] +Park in the marked area and exit your vehicle. The vehicle will then be crushed. + +[DIAB2_B] +A gang of no-goods has threatened to remove my starring member if I don't pay them a cut. + +[DIAB2_C] +They threaten the wrong man, amigo. + +[DIAB2_D] +They have a weakness for the icecream. + +[DIAB2_E] +Pick up the bomb I've hidden in Harwood, + +[DIAB2_F] +hijack the regular icecream van on its rounds. + +[DIAB2_G] +and lure these fools to their doom with the jeengle-jeengle. + +[DIAB2_H] +They hide in a warehouse on Atlantic Quay. + +[DIAB3_A] +Some insolent Triads stole my beautiful car last night, + +[DIAB3_B] +wrecked it and left it burning. + +[DIAB3_C] +Some of my most precious donkey memorabilia was in the trunk - + +[DIAB3_D] +real collectibles that are irreplaceable my friend. + +[DIAB3_E] +I've hidden a throbbing weapon on the edge of Chinatown. + +[DIAB3_F] +Take it and teach these Triad vandals to fear El Burro's well-endowed wrath. + +[DIAB3_1] +KILL 25 TRIADS + +[DIAB4_A] +A thieving opportunist has stolen a van of my latest publication hot off the press! + +[DIAB4_B] +But that SPANKED-up idiot has left the rear doors open + +[DIAB4_C] +and now my beautifully produced, + +[DIAB4_D] +tastefully photographed adult literature is being dropped all over Liberty! + +[DIAB4_E] +Take the van and follow that trail of Donkey Does Dallas volumes 1, 2 and 3 + +[DIAB4_F] +collecting it as you go. + +[DIAB4_G] +When you've followed the trail to that thieving SPANK-head, waste him! + +[DIAB4_H] +Then deliver my donkey derby to XXX Mags in the Red Light District. + +[DIAB4_1] +~g~Take the van to the back of XXX Magazines. + +[HM1_E] +I want you to show these punk ass bitches how a real drive-by works. + +[HM1_H] +Take these nines off of here!! + +[HM2_A] +Those Nines are pressing me. + +[HM2_B] +These Bitches got armored cars and now they're running SPANK... + +[HM2_C] +and slinging it to brothers with no fear. + +[HM2_D] +There's a car parked up the way. + +[HM2_E] +There's some stuff in there to put these sissys on blass... + +[HM3_A] +Some effa has wired my wheels to blow. + +[HM3_B] +If I lose those wheels, my rep on the street will be dead. + +[HM3_C] +Pick up my car and take it over to the garage on St. Marks, a'right yo. + +[HM3_D] +Let them diffuse that, let them take care of that bomb. + +[HM3_E] +The clocks ticking and the wiring is messed up. + +[HM3_F] +One pot hole too many and that thing could blow. + +[HM3_G] +Now move it! + +[HM4_A] +Yo, a Federal Reserve flight just smashed down at Francis International. + +[HM4_B] +There's platinum all over the strip. + +[HM4_C] +Get a car and snatch up as much as you can. + +[HM4_F] +You can drop the bling off at one of my garages. + +[HM4_G] +This platinum is mad heavy and it will slow your wheels down some. + +[HM4_H] +So make regular drop off's at the garage. + +[HM5_A] +Them Nines are down to a few scabby herds... + +[HM5_B] +but they still wanna bring it. + +[HM5_C] +They agreed to go toe to toe. + +[HM5_D] +A gang of them against two of us, or rather... + +[HM5_E] +two of yaw + +[HM5_F] +I'd join you but... + +[HM5_G] +I ain't due my parole hearing for another three months now, + +[HM5_H] +y'know what I mean? + +[HM5_I] +Go and meet my baby brother, + +[HM5_J] +He'll show you where they are fighting a'right son. + +[MEA1_B] +The name's Chonks, Marty Chonks. + +[MEA1_C] +I run the Bitchin' Dog Food factory around the corner. + +[MEA1_D] +I got money troubles, but hey, who doesn't right? + +[MEA1_E] +I'm meeting my bank manager later. + +[MEA1_F] +He's a crooked bastard that keeps bumping up the loan repayments so he can cut a slice. + +[MEA1_G] +Take my car, pick him up and bring him back here. + +[MEA1_H] +I've got a little surprise for that blood sucking leech!! + +[MEA2_A] +I hired some thieves to break into my apartment... + +[MEA2_C] +The thieving bastards are threatening to tell the insurance company, + +[MEA2_D] +if I don't give them a cut. + +[MEA2_E] +Can you believe it? + +[MEA2_F] +I've left a car inside the factory gates. + +[MEA2_G] +Use it to go and pick them up from their turf in the Red Light district. + +[MEA2_H] +Then bring 'em back to the factory so I can make 'em see Marty's point of view. + +[MEA3_A] +The business is going to go under unless I get hold of some serious cash soon. + +[MEA3_B] +My wife has an insurance policy and all she's ever been to me is a hole in my pocket. + +[MEA3_C] +I've left a car in the usual place. + +[MEA3_D] +Go and pick up my wife from Classic Nails and bring her back to the factory. + +[MEA4_A] +Damn, I'm in trouble! + +[MEA4_B] +Turns out my wife was seeing some guy I owe money to. + +[MEA4_C] +He's got real angry and he's looking for payback! + +[MEA4_E] +he thinks I'm gonna pay him off... + +[MEA4_F] +but my guess is... + +[MEA4_G] +Liberty's dogs are gonna get yet another flavor this month! + +[WELCOME] +WELCOME TO + +[HM1_2] +~g~Get a vehicle, remember only Uzi drive by kills count! + +[HELP8_B] +Press the~h~ ~k~~PED_SNIPER_ZOOM_IN~ button ~w~to ~h~zoom in ~w~with the rifle and the~h~ ~k~~PED_SNIPER_ZOOM_OUT~ button ~w~to ~h~zoom out ~w~again. + +[LRQC_1] +Asuka and I are gonna have to talk, uh, + +[LRQC_2] +Why don't you go cruise around? + +[LRQC_3] +You'll need a place to lie low. + +[LRQC_4] +There's a warehouse at the edge of Belleville that should suit your needs. + +[LRQC_5] +Come back here to my Condo when you are ready, + +[LRQC_6] +and we can have a little chat. + +[JM6_5] +~g~You need a getaway vehicle, Idiot! + +[JM2_F] +If you need a piece go around back of AmmuNation opposite the subway. + +[LOVE4_7] +~g~There's a construction yard in Staunton Island, maybe they took the package there. + +[LOVE4_8] +~g~You'll need a car to open the garage. + +[TSCORE] +EARNINGS: $~1~ + +[AM1_9] +~r~Salvatore has escaped back into Luigi's Club! + +[AM1_6] +~g~If you hang around Luigi's club, the Mafia will spot you! + +[TM2_3] +~g~It's a trap! Waste them all!! + +[FM4_1] +This is Maria. The car's a trap! Meet me at the slip south of Callahan Bridge. + +[JM1_7] +~g~Close the car door! He'll notice! + +[KM5_1] +~g~DEALER MINCED!!. + +[KM5_6] +~g~You must murder at least 8 Yardie dealers. + +[KM5_7] +~g~Kill them quickly! Once they've pushed their SPANK they're off the streets. + +[RM3_8] +~r~That car is a decoy!! + +[LM3_8] +Hey, I'm Joey. + +[LM3_9] +Luigi said you were reliable so come back later, + +[KM3_5] +~g~Press the horn to get the deal going. + +[LOVE7] +LOVE'S DISAPPEARANCE + +[LOVE2_5] +~g~Kenji is fender meat! Get out of Newport and dump the car! + +[AS2_11] +~g~~1~ OF 9! + +[GARAGE1] +~g~Get out of the vehicle and walk outside. + +[KM3_11] +~g~The Cartel have been attacked and the briefcase has not been recovered. + +[KM3_12] +~g~Kill all of the Colombians, destory the vehicles and recover the briefcase. + +[KM3_13] +~g~Take the briefcase back to the casino. + +[RM5_6] +~g~He's bailed out!! Smash his bodycast with a vehicle or an explosion!! + +[PBOAT_1] { re3 change } +Press the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ to fire the boat cannons. + +[PBOAT_2] { re3 change } +Press the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ to fire the boat cannons. + +[DIAB1_B] +This is El Burro of the Diablos. + +[DIAB1_D] +You're new in Liberty, but already you are gaining a reputation on the streets. + +[DIAB1_E] +There's a street race starting by the old school hall near the Callahan Bridge. + +[DIAB1_F] +Get yourself some wheels and first through all the checkpoints wins the prize. + +[HM2_1] { re3 change } +Use the RC buggies to destroy the armored cars. Press the ~h~~k~~VEHICLE_FIREWEAPON~ button ~w~to detonate. + +[HM2_1A] { re3 change } +Use the RC buggies to destroy the armored cars. Press the ~h~~k~~VEHICLE_FIREWEAPON~ button ~w~to detonate. + +[HM2_2] +~r~You failed to destroy all the armored cars! + +[HM2_6] +~g~Armored Car destroyed! + +[RM3_A] +I know a real important man in town, a soft touch, + +[RM3_H] +with shall we say, exotic tastes and the money to indulge them. + +[RM3_B] +He's involved in a legal matter and the prosecution has some rather embarrassing photos of him + +[RM3_C] +at a morgue party or something. + +[LOVE6_A] +A lesson in business, my friend. + +[LOVE6_E] +If you have a unique commodity, the world and his wife will try to wrestle it from your grasp... + +[LOVE6_C] +SWAT teams have cordoned off the area around my associate and the package. + +[LOVE6_D] +Get over there, pick up the van and act as a decoy. + +[LOVE6_F] +Keep them busy and he should make good his escape. + +[AM3_C] +He's probably out in the bay as you read this! Steal a police boat, and sink his career! + +[FESZ_UC] +CANCEL + +[FEDS_SM] +L1,R1-CHANGE MENU + +[FEDS_AS] +;=-CHANGE SELECTION + +[FEDSAS2] +<>-CHANGE SELECTION + +[FEDS_SS] +L1,R1-CHANGE SELECTION + +[FEDSSC1] +;-FASTER SCROLLING + +[FEDSSC2] +=-STOP SCROLLING + +[MEA2_3] +~g~Bring the car back to the factory. + +[RM1_3] +~r~McAffrey escaped! + +[RM1_4] +~g~You have used all the grenades! Get some more from ammunation! + +[RM1_5] +~g~Go back and torch the safehouse! + +[RM6_4] +~g~Go over to the lockup and collect Ray's stash. + +[RM6_5] +~g~The CIA have the bridge under surveillance, find another route across. + +[HM2_F] +and wreck all their armored stuff. + +[HM_4] +'BULLION RUN' + +[MEA2_B5] +TEXT NO LONGER NEEDED + +[MEA1_B5] +TEXT NO LONGER NEEDED + +[MEA3_B5] +TEXT NO LONGER NEEDED + +[MEA4_B7] +but if you just step into my office... + +[MEA3_B4] +Marty wants to see me? Well it better be quick because I have to get my hair done. + +[KM3_7] +It's a Yakuza trap man! + +[FES_LOF] +Load Failed. + +[FES_SLO] +SAVE FILE + +[FES_ISC] +IS CORRUPTED + +[FESZ_TI] +SAVE Z1 + +[FESZ_SA] +Save game + +[MC_LDFL] +Load Failed! + +[MC_NWRE] +Now Restarting Game. + +[LOVE6_3] +~g~You have ~1~ seconds to return to the Securicar before you fail the mission. + +[LOVE6_4] +~r~You ditched the Decoy Securicar! + +[HELP1] +Stop in the center of the blue marker. + +[HELP12] +Walk into the center of the blue marker to trigger a mission. + +[HJSTAT] +Distance: ~1~.~1~m Height: ~1~.~1~m Flips: ~1~ Rotation: ~1~_ + +[HJSTATW] +Distance: ~1~.~1~m Height: ~1~.~1~m Flips: ~1~ Rotation: ~1~_ And what a great landing! + +[DIAB1_5] +RACE TIME: + +[LOVE3_4] +~r~You destroyed the plane!! + +[F_FAIL1] +Fire Truck mission ended. + +[F_CANC] +~r~Fire Fighter mission cancelled! + +[F_EXTIN] +FIRES: + +[A_COMP1] +Paramedic missions complete! + +[A_CANC] +~r~Paramedic mission cancelled! + +[A_COMP3] +Paramedic missions complete! You will never get tired when running! + +[ATUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Paramedic missions on or off. + +[ATUTOR3] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Paramedic missions on or off. + +[ALEVEL] +Paramedic Mission Level ~1~ + +[A_FAIL1] +Paramedic mission ended. + +[FEST_HA] +Highest Paramedic Mission level + +[A_SAVES] +PEOPLE SAVED: ~1~ + +[C_KILLS] +CRIMINALS KILLED: ~1~ + +[HM1_B] +I got a problem they tryin' to play me. + +[AM2_A] +Salvatore's death comes as pleasurable news, + +[AM2_A2] +you are an efficient killer. I like that in a man. + +[AM2_B] +This is my brother Kenji. + +[AM2_C] +Asuka has a little job for you, but when you're done, drop by my casino and we can talk. + +[AM2_D] +Just like Kenji, always trying to play with my toys. + +[AM2_E] +My police source indicates that the Mafia are watching our interests around the city + +[AM2_E2] +in a bid to track you down. + +[AM2_F] +We cannot continue our operations until they are dealt with. + +[AM2_G] +Take out these spying fools and end this vendetta once and for all. + +[F_START] +~g~Burning vehicle reported in the ~a~ area. Go and extinguish the fire. + +[AM4_1A] +Get to the Phone in West Belleville Park. + +[AM4_1B] +Get to the Phone on Liberty Campus. + +[AM4_1C] +Get to the Phone in South Belleville Park. + +[AM4_1D] +Meet me in the toilet block in the park. + +[HJSTATF] +Distance: ~1~ft Height: ~1~ft Flips: ~1~ Rotation: ~1~_ + +[HJSTAWF] +Distance: ~1~ft Height: ~1~ft Flips: ~1~ Rotation: ~1~_ And what a great landing! + +[HM1_F] +Watch your back though, there'll be Jacks on the street who'll think you're trying to blast them too! + +[HM1_D] +'Nines' is their tag and purple is their flag and each day they rock their colors... + +[HM1_G] +is another day the 'Jacks' look soft. + +[MEA2_B] +and steal some stuff so I could claim on the insurance as you do. + +[TM3_H] +~w~You did good back there kid, real good. + +[TM3_I] +~w~Come on, let's introduce you to the Don. + +[TM3_J] +~w~Heeyyy! Luigi! + +[TM3_K] +~w~Oh my girls have been missing you so long Salvatore, you been away too long. + +[TM3_L] +~w~You tell them that once this unfortunate business is taken care of, + +[TM3_M] +~w~we'll all go down to the club and celebrate, ok? + +[TM3_N] +~w~Here's my boy. + +[TM3_N2] +~w~How you doin' pop? + +[TM3_O] +~w~You got yourself a good woman yet? + +[TM3_P] +~w~Hey, your mother, god bless her soul, would be turning over in her grave + +[TM3_Q] +~w~to see you without a wife. + +[TM3_R] +~w~I know Pop, I'm working on it. + +[TM3_S] +~w~TONI! How's your Momma? + +[TM3_T] +~w~She's a great woman you know. Strong. Firenze. + +[TM3_U] +~w~She's good...fine. + +[TM3_V] +~w~Terrific, Terrific. Now listen you guys, you go inside while I talk to our new friend here. + +[TM3_W] +~w~I see nothing but good things for you my boy... + +[RM1_A] +That scumbag McAffrey, he took more bribes than anyone. + +[RM1_B] +He thinks he's gonna get an honorable discharge if he turns states evidence. + +[RM1_C] +He just squealed! + +[RM4_B] +We gotta shut him up, permanently. + +[RM4_E] +I want him sleeping with the fishes, not eating them. + +[LOVE3_B] +On its approach to the airport tonight, a light aircraft will pass over the bay. + +[LOVE4_D] +Unfortunately the port authorities seized the plane and were stripping it down + +[LOVE4_H] +until I intervened at great personal expense. + +[LOVE4_E] +Cross the bridge to Shoreside Vale and go to Francis International Airport. + +[GTAB_A] +Hey, let's get this out of here. God knows what it is + +[GTAB_B] +but he seems to want it badly enough so it must be worth something. + +[GTAB_C] +Who the Heck! + +[GTAB_D] +YOU! + +[GTAB_E] +Hey take it easy amigo! De nada! De nada! + +[GTAB_F] +I left you pouring your heart out into the gutter! + +[GTAB_G] +Don't shoot amigo. No problem. We all friends. Here, take this. + +[GTAB_H] +Don't be such a pussy! + +[GTAB_I] +We got no choice baby! + +[GTAB_J] +We always got a choice you dumb bastard! + +[GTAB_K] +I'm sorry about that crazy bitch man, they all the same...por favor?? + +[GTAB_L] +So the whore got away. + +[GTAB_M] +But you've done me a favor, + +[GTAB_N] +you're not the only one that has a score to settle with the Cartel, + +[GTAB_O] +this worm killed my brother! + +[GTAB_P] +I never killed no Yakuza! + +[GTAB_Q] +LIAR! We all saw the Cartel assassin. + +[GTAB_R] +We are going to hunt down and kill all you Colombian dogs! + +[GTAB_S] +I'll be operating on our friend here to extract information and a little pleasure. + +[GTAB_T] +You, drop by later, I'm sure I'll require your services. + +[GTAB_U] +Please amigo, don't leave me with her, she psycho chica! Amigo? Hey AMEEEGO!!!...Aiiieeeeaaargghh! + +[LOVE5_A] +You are proving to be a safe investment, a rare thing in these days of false hood. + +[KM3_1] +~g~The Cartel are expecting a Yardie Posse go and steal a Yardie car! Head north you'll find one in Newport. + +[LOVE1_1] +~g~Go 'jack a Colombian gang car, so you can infiltrate the hideout, head north you'll find one in Fort Staunton. + +[FM1_Q1] +~w~You looking for some fun? A little...hmm? Some SPANK? + +[FM1_R] +~w~Hi Chico. Nah, just the usual. + +[FM1_T] +~w~Thanks Chico. See you around. + +[FM1_W] +~w~Alright Fido, you wait here and look after the car while I go and shake my butt alright. + +[FM1_X] +~w~OK Fido, let's get out of here. Wooooh! + +[FM1_Q] +~w~Hey Maria! It's my favorite lady! + +[FM1_S1] +~w~Hey, maybe you should check out the warehouse party at the east end of Atlantic Quays. + +[FM1_U] +~w~Gracias and enjoy. That's good stuff. + +[FM1_V] +~w~C'mon Fido, let's go and check out this party! + +[FM1_SS] +~r~SCANNER: ~g~Four-five to all units: Assist narcotics raid Atlantic Quays... + +[LOVE6_B] +even if they have little understanding as to its true value. + +[TM3_A1] +~r~Joey's Fried! + +[TM3_A2] +~r~Joey and Luigi have been cremated! + +[TM3_A3] +~r~Joey, Luigi and Toni are Toast! + +[FM4_2] +Listen, Salvatore thinks that we're going behind his back, + +[FM4_3] +so he was offering you to the Cartel in order to make a deal. + +[FM4_4] +I couldn't let him do that, I mean the worst thing is, + +[FM4_4B] +it's all my fault... because I told him, we were an item. + +[FM4_5] +Don't ask me why. I don't know. + +[FM4_6] +Look you're a marked man on Mafia turf and I've got to get out of here too. + +[FM4_6B] +I've seen too much killing. Too much blood! + +[FM4_7] +This is a friend of mine ok, she's an old friend.. it's Asuka, she's someone we can trust. + +[FM4_8] +C'mon, Enough of the speeches. + +[FM4_9] +We better get out of here before we get more hysterical Italians wanting less friendly reunions. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCER + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTION + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DESIGN + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +WRITTEN BY + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +CHARACTERS + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMATION & DIRECTION + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +AUTO DESIGN + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODERS + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +SCORE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +SOUND DESIGN & MASTERING + +[CRED042] +ALLAN WALKER + +[CRED043] +AUDIO PROGRAMMING + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD TESTERS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTERS + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +TECHNICAL PRODUCER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VP OF MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +TECHNICAL COORDINATOR + +[CRED088] +BRANDON ROSE + +[CRED089] +QA MANAGER + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +LIBERTY TREE EDITORIAL + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT-SCENES + +[CRED108] +SCRIPT BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER + +[CRED110] +AUDIO PRODUCED BY RENAUD SEBBANE + +[CRED111] +CAST + +[CRED112] +FRANK VINCENT AS SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO AS LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN AS TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT AS JOEY LEONE + +[CRED116] +DEBBI MAZAR AS MARIA + +[CRED117] +KYLE MACLACHLAN AS DONALD LOVE + +[CRED118] +ROBERT LOGGIA AS RAY MACHOWSKI + +[CRED119] +GURU AS 8-BALL + +[CRED120] +SONDRA JAMES AS MOMMA + +[CRED121] +LIANA PAI AS ASUKA + +[CRED122] +LES MAU AS KENJI + +[CRED123] +CYNTHIA FARRELL AS CATALINA + +[CRED124] +AL ESPINOSA AS MIGUEL + +[CRED125] +CHRIS PHILLIPS AS EL BURRO + +[CRED126] +HUNTER PLATIN AS CHICO + +[CRED127] +WALTER MUDU AS D-ICE + +[CRED128] +CURTIS MCCLARIN AS CURTLY + +[CRED129] +BILL FIORE AS DARKEL + +[CRED130] +CHRIS PHILLIPS AS MARTY CHONKS + +[CRED131] +HUNTER PLATIN AS CURLY BOB + +[CRED132] +WALTER MUDU AS KING COURTNEY + +[CRED133] +HUNTER PLATIN AS ONE-ARMED PHIL + +[CRED134] +KIM GURNEY AS MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRECTED BY + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUCED BY + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +RECORDED AT MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORS + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRED142] +WRITTEN BY DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRECTED BY CRAIG CONNER, DAN HOUSER AND LAZLOW + +[CRED144] +PRODUCED BY RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +RADIO STATIONS AND MUSIC + +[CRED218] +PRODUCERS FOR ROCKSTAR UK + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDITED BY + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING WRITTEN BY + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CRED270] +MIKE HONG + +[CINCAM] +Cinematic Camera + +[KM1_13] +Drive the vehicle into the garage! + +[KM3_14] +~r~You have been spotted the deal is off! + +[EBAL_H] +Wait here man while I go in and talk to Luigi. + +[EBAL_M] +Remember no one messes with my girls! + +[LM2_F] +Then take his car, respray it. + +[LM2_D] +here, here take it. + +[LM1_9] +Hi I'm Misty. + +[LM4_A] +Some Diablo scumbag has been pimping his skuzzy bitches in my backyard. + +[FM2_B] +We got us a rat! + +[FM2_C] +He ain't pimpin' or pushin' so he must be talking. + +[FM3_CC] +~w~Come back brother when you have the money. + +[FEDS_AM] +<>-CHANGE MENU + +[LOVE5_5] +~r~You failed to protect the truck! + +[RM6_6] +~r~Ray is dead! + +[RM6_7] +~r~Ray has missed his flight. + +[RM6_8] +~g~You have left Ray behind, go back and get him. + +[FM1_10] +~g~You have left Maria behind, go back and pick her up. + +[LOVE4_9] +~r~The plane has been destroyed! + +[LOV4_10] +~r~The only lead to where the package has gone has been destroyed! + +[KM2_D] +Needless to say, we must give him the cars as a gift, to repay the debt that I owe him. + +[KM4_B] +The business's fortunate enough to have our protection settle their accounts today. + +[KM2_E] +You must obtain the cars on this list and deliver them to a garage behind the car park in Newport. + +[FM3_8I] +~w~Get a good vantage point then I'll head in when you fire the first shot. + +[LOVE1_B] +Experience has taught me that a man like you can be very loyal for the right price, + +[LOVE1_H] +but groups of men get greedy. + +[LOVE1_C] +A valued resource, an old oriental gentleman I know, + +[LOVE1_I] +has been kept hostage by some South Americans in Aspatria. + +[MEA4_D] +I've agreed to see him... + +[MEA4_B4] +Marty sent you huh? OK, I'm gonna show that creep the meaning of the word business. + +[MEA4_B5] +Carl, hi! i eerr, I need more time to get your money. + +[MEA1_B4] +Ah, Mr Chonks sent you did he. Let's go and pay the fellow a visit. + +[HM5_6] +Let's go crack some skulls... + +[LOVE1_5] +~g~Stop hanging around, get a Colombian Gang car and rescue Love's associate. + +[AS1_D] +~w~Act as the bait, and get the death squads to follow you to Pike Creek + +[AS1_E] +~w~where some of my men will be waiting for them. + +[AS2_C] +~w~The Cartel have a front company, The Kappa Coffee House. + +[AS2_E] +~w~We have no choice but to put these drug stands out of operation. + +[AS2_F] +~w~Smash them to splinters!! + +[AS2_A1] +~w~Miguel certainly has some of that famous Latin stamina. + +[AS2_A2] +~w~I'm quite exhausted. + +[SIREN_3] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[SIREN_4] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[AS3_C] +~w~Eeeeeeyoooo! What IS that gooey yellow stuff? + +[AS3_C1] +~w~Oh hi Babe. + +[AS3_F] +~w~She's got the makings of a natural this girl. + +[AS3_F1] +~w~She's managed to extract this little gem from our guest. + +[AS3_G] +~w~There is a plane coming into Francis International in 2 hours time. + +[AS3_G1] +~w~It is full of Catalina's poison. + +[AS3_H] +~w~You can avoid airport security by getting a boat out to the runway-light buoys + +[AS3_H1] +and shooting the plane down on its approach. + +[AS3_I] +~w~Collect the cargo from the debris and stash it! + +[AS3_J] +~w~Oh you be careful now, OK baby? + +[AS3_K] +~w~Now try the chilli oil..... + +[RM2_F1] +Those Colombians'll be here any minute! + +[RM2_K] +Goddam they're here!! LOCK'N'LOAD!! + +[LOVE2_7] +~g~Now dump the car! + +[LOVE2_8] +~g~Now get out of Newport! + +[AM1_F] +Salvatore Leone will be leaving Luigi's in about three hours time. (~1~:~1~) + +[LOVE5_C] +I want you to follow him, and make sure both he and my package get to Pike Creek unharmed. + +[FESZ_SR] +Save Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FESZ_FO] +Would you like to format the Memory Card (PS2) in MEMORY CARD slot 1? + +[FELZ_FO] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. + +[FES_NOC] +No Memory Card (PS2) in MEMORY CARD slot 1. + +[FES_LOE] +Load Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FES_DEE] +Deleting Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[SLONFM] +Error formatting Memory Card (PS2) in MEMORY CARD slot 1. + +[SLONDR] +Insufficient space to save. Please insert a Memory Card (PS2) with at least 500KB of free space available into MEMORY CARD slot 1. + +[SLNSP] +Insufficient space to save. Please insert a Memory Card (PS2) with at least 200KB of free space available into MEMORY CARD slot 1. + +[FEFD_WR] +Formatting Memory Card (PS2) in MEMORY CARD slot 1. Please do not remove the Memory Card (PS2), reset or switch off the console. + +[FES_ISF] +NOT PRESENT + +[FES_SAG] +PRESENT + +[SLONNO] +No Memory Card (PS2) in MEMORY CARD slot 1. + +[SLONNF] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. + +[FESZ_FM] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. Would you like to format Memory Card (PS2) in MEMORY CARD slot 1? + +[FESZ_FF] +Format Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[MCDNSP] +There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 500KB is needed to save this application data. Do you wish to start? (YES or NO) + +[MCGNSP] +There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 200KB is needed to save this application data. Do you wish to start? (YES or NO) + +[FESZ_WR] +Saving data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FESZ_OW] +Overwriting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FELD_WR] +Loading data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FEDL_WR] +Deleting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[LM2_C] +Luigi said to, to give you this so... + +[LM3_G] +Joey ain't the kind you keep waiting, remember, this is your foot in the door... + +[LM5_E] +Get as many of them as you can before the cops drink away their green. + +[JM5_C] +Alright, there's a car stuffed with a stiff at the cafe near Callahan Point. + +[RM2_B] +We saw action in Nicaragua, back when the country knew what it was doing. + +[RM2_C] +Some Cartel scum roughed him up yesterday, said they'd be back for some of his stock today. + +[RM2_D1] +I'd go myself but the old sciatica's playing up again -cough cough- so, eerr-hhrrmmm, good luck. + +[CATINF1] +~g~Get Catalina! + +[CATINF2] +~g~Follow the chopper to find Catalina. + +[BOATIN1] +Jump into a boat and press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~to get in. + +[BOATIN2] +You can press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~if you are near a boat to get in it. + +[BOATIN3] +Jump into a boat and press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~to get in. + +[BOATIN4] +You can press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~if you are near a boat to get in it. + +[JM6] +'THE GETAWAY' + +[FM1] +'CHAPERONE' + +[JM1] +'MIKE LIPS LAST LUNCH' + +[FM21] +'BOMB DA BASE: ACT I' + +[FM3] +'BOMB DA BASE: ACT II' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'UNDER SURVEILLANCE' + +[KM2] +'GRAND THEFT AUTO' + +[AS3] +'S.A.M.' + +[RM2] +'ARMS SHORTAGE' + +[LOVE6] +'DECOY' + +[LOVE1] +'LIBERATOR' + +[RC1] +'DIABLO DESTRUCTION' + +[RC2] +'MAFIA MASSACRE' + +[RC3] +'CASINO CALAMITY' + +[RC4] +'RUMPO RAMPAGE' + +[RM2_E1] +I can't believe those yellow-bellied bastards left me without proper cover again! + +[GREN_1] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[GREN_2] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[GREN_3] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[LOVE4_G] +My property will be waiting for you at the customs hanger in the aircraft's fuselage. + +[KABOOM] +KABOOOM! + +[SPLAT] +SPLAT! + +[PANCAK] +PANCAKED! + +[SOAKED] +SOAKED! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Chatterbox FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +When the subway opens you will be able to catch a train to Staunton Island. + +[TUBE2] +When Shoreside Vale opens you will be able to Exit Shoreside Terminal to Francis International Airport. + +[TUBE_2] +To board a subway train, press the ~h~'enter vehicle' button~w~. + +[LEGAL] +~g~Eliminate the criminal threat! + +[GA_2] +New engine and paint job. The cops won't recognize you! + +[LM1_8A] +To earn some extra cash, why not 'borrow' a taxi... + +[TAXIH1] +Stop near a highlighted pedestrian to pick them up then drive them to their destination before the time runs out. + +[LM5_7] +~g~Less than four girls working the ~p~Fuzz Ball~g~ and Luigi won't be happy! + +[KM2_3] +~g~Remember the ~r~cars~g~ have to be in mint condition to be accepted by the ~p~garage~g~. + +[KM5_2] +~g~A Yardie is off the streets. + +[BETRA_A] +Sorry, babe. + +[BETRA_B] +I'm an ambitious girl and you, + +[BETRA_C] +you're just small time. + +[JAILB_C] +* + +[JAILB_D] +* + +[JAILB_E] +* + +[JAILB_F] +* + +[JAILB_G] +* + +[JAILB_H] +* + +[JAILB_I] +* + +[JAILB_J] +* + +[JAILB_P] +* + +[JAILB_Q] +Come on! + +[JAILB_R] +Senor dickhead! + +[JAILB_S] +It's no problem to kill you. + +[JAILB_T] +You gonna be sorry. + +[JAILB_U] +A'right, a'right. Get lost. + +[HELP15] +When on foot press the ~h~~k~~PED_LOOKBEHIND~ button~w~ to ~h~look behind~w~. + +[FEC_LB3] +Look behind + +[FEC_R3] +(R3 button) + +[FES_AFO] +This Memory Card (PS2) is already formatted. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- CHANGE SELECTION + +[FEDSAS4] +;=<> - CHANGE SELECTION + +[SPRAY_4] { re3 change } +Use the ~h~~k~~VEHICLE_FIREWEAPON~ button ~w~to fire the water cannon. + +[SPRAY_1] { re3 change } +Use the ~h~~k~~VEHICLE_FIREWEAPON~ button ~w~to fire the water cannon. + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[AM1_10] +~g~Salvatore will be leaving Luigi's at about 0~1~:~1~ + +[JAILB_V] +* + +[JAILB_A] +* + +[JAILB_B] +* + +[JAILB_W] +* + +[JAILB_K] +* + +[JAILB_L] +* + +[JAILB_M] +* + +[JAILB_N] +* + +[JAILB_O] +* + +[JAILB_X] +* + +[FEDS_SE] +/ button - SELECT + +[FEDS_SB] +/ button - SELECT " button - BACK + +[TM4_A] +~w~Oh it's you. TONI ain't here. + +[TM4_A2] +~w~But he left one of his sugary love letters for you. + +[DIAB2_A] +I started my exotic entertainment business with nothing but the sizeable contents of my leather pants! + +[LM5_9] +GIRLS: + +[PERPIC] +Hidden Packages found + +[CO_ONE] +Hidden Package ~1~ of ~1~ + +[LOVE3_3] +~g~The plane has dropped ~1~ of 6 packages. + +[FARE11] +~g~Destination ~w~'Construction site' ~g~in Fort staunton. + +[GA_21] +You cannot store any more cars in this garage. + +[CHEAT1] +Cheat activated + +[CHEAT2] +Weapon cheat + +[CHEAT3] +Health cheat + +[CHEAT4] +Armor cheat + +[CHEAT5] +Wanted level cheat + +[CHEAT6] +Money cheat + +[CHEAT7] +Weather cheat + +[AS1_H] +~r~You failed to lead the Deathsquad into the Yakuza trap!! + +[FEDS_BA] +" button - BACK + +[RAMP_A] +ALL RAMPAGES COMPLETED! + +[USJ_ALL] +ALL UNIQUE STUNTS COMPLETED! + +[FARE23] +~g~Destination ~w~'import export garage' ~g~in Cochrane Dam district + +[L_TRN_1] +You can ride the L-train around Portland. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[L_TRN_2] +You can ride the L-train around Portland. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[S_TRN_1] +You can take the subway trains across Liberty. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[S_TRN_2] +You can take the subway trains across Liberty. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[AS1_C] +~w~She has three death squads dotted around Liberty, whose sole job is to hunt you down. + +[AS1_G] +~r~All the Yakuza are dead!! + +[JAN] +Jan + +[FEB] +Feb + +[MAR] +Mar + +[APR] +Apr + +[MAY] +May + +[JUN] +Jun + +[JUL] +Jul + +[AUG] +Aug + +[SEP] +Sept + +[OCT] +Oct + +[NOV] +Nov + +[DEC] +Dec + +[DEFDT] +--:---:---- --:--:-- + +[BUGGY] +BUGGIES LEFT: + +[BONUS] +~g~BONUS $~1~ + +[HORN1] +Press the ~h~L3 button ~w~to activate the ~h~horn. + +[HORN2] +Press the ~h~L1 button ~w~to activate the ~h~horn + +[HORN3] +Press the ~h~R1 button ~w~to activate the ~h~horn + +[LM3_1A] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[LM3_1B] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[LM3_1C] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[RADIO_A] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_B] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_C] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_D] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[FEC_EXV] +Enter and exit vehicle + +[TAXI_M] +'TAXI DRIVER' + +[COP_M] +'VIGILANTE' + +[FIRE_M] +'FIREFIGHTER' + +[AMBUL_M] +'PARAMEDIC' + +[HJ_IS] +INSANE STUNT BONUS: $~1~ + +[HJ_PIS] +PERFECT INSANE STUNT BONUS: $~1~ + +[HJ_DIS] +DOUBLE INSANE STUNT BONUS: $~1~ + +[HJ_PDIS] +PERFECT DOUBLE INSANE STUNT BONUS: $~1~ + +[HJ_TIS] +TRIPLE INSANE STUNT BONUS: $~1~ + +[HJ_PTIS] +PERFECT TRIPLE INSANE STUNT BONUS: $~1~ + +[HJ_QIS] +QUADRUPLE INSANE STUNT BONUS: $~1~ + +[HJ_PQIS] +PERFECT QUADRUPLE INSANE STUNT BONUS: $~1~ + +[AM1_K] +Salvatore Leone will be leaving Luigi's in about three hours time. (0~1~:~1~) + +[IMPEXPP] +Import/Export garage, Portland Harbor. We have orders for various vehicles. Check our notice board for our requirements. + +[VANHSTP] +Any more Securicars you want cracked? Bring them to our garage in the Portland Harbor. + +[EMVHPUP] +Great rates paid for new and used Emergency Vehicles. Bring them to the crane in the north east of Portland Harbor. + +[STANDS] +STALLS WRECKED: + +[STASH] +~g~Stash the SPANK back at the ~p~construction site! + +[MCSTNS] +There is no Memory Card (PS2) in MEMORY CARD slot 1. Do you wish to start? (YES or NO) + +[LOVE3_5] +~g~The plane is now in range. + +[LOVE3_6] +~r~The Police got to the packages first! + +[SIREN_1] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[SIREN_2] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[FM3_8C] +~w~I'll need $100,000 to cover expenses, + +[MCLOAD] +Loading Data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FES_GME] +Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + +[FESZ_QF] +Are you sure you wish to format the Memory Card (PS2) in MEMORY CARD slot 1? + +[FESZ_LS] +Load Successful. + +[RM3_5] +~g~You have ~1~ of 6 evidence packages. + +[LOVE3_2] +~g~You have all the packages! Take them back to Donald Love. + +[LOVE4_4] +~g~Take the package back to Donald Love! + +[FEB_SAV] +Load + +[FEP_SAV] +LOAD GAME + +[AS2_12A] +~g~After you trash the first stall, you will have 8 minutes before the Cartel warn their pushers! + +[AS3_1A] +~g~Now get to the ~b~marker buoy! + +[NOCONT] +Please reconnect an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). to controller port 1 to continue + +[BET_JB] +BETRAYED BY HIS LOVER CATALINA AND LEFT FOR DEAD. CONVICTED AND SENTENCED, HE BEGINS HIS JOURNEY TO LIBERTY CITY PENITENTIARY. BUT ONLY ONE THOUGHT BURNS IN HIS CRIMINAL MIND......REVENGE! + +[END_A] +Residents in Cedar Grove have been coming to terms + +[END_B] +with the emotional aftermath of a full blown war + +[END_C] +that hit the area yesterday. + +[END_D] +Local resident, Clive Denver described to police + +[END_E] +a single gunman that he saw fleeing the scene, with a dark haired woman. + +[END_F] +Oh, you know, we're gonna have such fun, 'cos you know, you know, + +[END_G] +I love you, I, I, I, I really do, 'cos you're such a big strong man + +[END_H] +and that's just what I need. + +[END_I] +Anyway, what was I saying? + +[END_J] +Oh, you know, I forget. But you know what it's like, don't you? + +[END_K] +The sound of explosions shook nearby homes as people ran for cover. + +[END_L] +Several citizens were injured in the panic as ground fire was exchanged + +[END_M] +between ground forces and a helicopter circling the dam. + +[END_N] +Yeah, we got a good view from down here in the gardens. + +[END_O] +When the 'copter finally got taken out, + +[END_P] +better than the fireworks on the 4th of July. + +[END_Q] +With the death toll already over twenty, + +[END_R] +police are still finding bodies. + +[END_S] +There have been no official denials concerning rumours + +[END_T] +that the dead were members of the Colombian Cartel, + +[END_U] +and still no leads as to the cause of the massacre. + +[END_V] +I broke a nail and my hair is ruined, I mean can you believe it? + +[END_W] +This one cost me fifty dollars... + +[PAPER1] +* + +[PAPER2] +* + +[PAPER3] +* + +[FEB_CPC] +Control Configuration + +[FEC_PED] +Controls On Foot + +[FEC_VEH] +Controls In Vehicle + +[FEC_FPR] +Controls For First Person + +[FEC_CMM] +Common Controls + +[FEC_PWL] +GO Left + +[FEC_PWR] +Go Right + +[FEC_PWF] +Walk Forward + +[FEC_PWT] +Walk towards camera + +[FEC_PLB] +Look Behind. + +[FEC_PFR] +Fire Weapon + +[FEC_CLE] +Cycle Weapon Left + +[FEC_CRI] +Cycle Weapon Right + +[FEC_LKT] +Lock Target + +[FEC_PJP] +Ped Jump + +[FEC_PSP] +Ped Sprint + +[FEC_PSH] +Ped Shoot + +[FEC_TLF] +Next Target To Left + +[FEC_TRG] +Next Target to Right + +[FEC_CCM] +Center Camera Behind player. + +[FEC_SZI] +Sniper Rifle Zoom In + +[FEC_SZO] +Sniper Rifle Zoom Out + +[FEC_LKL] +First Person Look Left + +[FEC_LRT] +First Person Look Right + +[FEC_LUP] +1st Person Look Up + +[FEC_LDN] +1st Person Look Down + +[FEC_LBH] +Look Behind Vehicle + +[FEC_LLF] +Look Left of Vehicle + +[FEC_LRG] +Look Right of Vehicle + +[FEC_HRN] +Horn + +[FEC_HBR] +Vehicle Handbrake + +[FEC_ACL] +Vehicle Accelerate + +[FEC_BRK] +Vehicle Brake + +[FEC_TSM] +Toggle SubMissions + +[FEC_CRD] +Change Radio Station + +[FEC_ENT] +Enter/Exit Vehicle + +[FEC_WPN] +Fire Weapon + +[FEC_PAS] +Pause + +[FEC_FPO] +1st Person Weapons + +[FEC_SMS] +Show mouse pointer + +[FEC_CMS] +Change camera mode all situations. + +[FEC_TSS] +Take Screen Shot + +[FEN_NET] +Network + +[FEN_CON] +Connection + +[FEN_GAM] +Find game + +[FEN_TYP] +Game type + +[FEN_TY0] +Deathmatch + +[FEN_TY1] +Deathmatch stealth + +[FEN_TY2] +Team Deathmatch + +[FEN_TY3] +Team Deathmatch stealth + +[FEN_TY4] +Stash the Cash + +[FEN_TY5] +Capture the Flag + +[FEN_TY6] +Rat Race + +[FEN_TY7] +Domination + +[FEN_NAM] +Name: + +[FEN_GNA] +Game name: + +[FEM_MAP] +Select map + +[FEN_PLS] +Player settings + +[FEN_PLC] +Player color + +[FEM_MA0] +Liberty City + +[FEM_MA1] +RedLight + +[FEM_MA2] +Chinatown + +[FEM_MA3] +The Tower + +[FEM_MA4] +The Sewer + +[FEM_MA5] +Industrial Park + +[FEM_MA6] +The Docks + +[FEM_MA7] +Staunton + +[FEC_EMS] +Unique Keyboard Keys only please. + +[FEC_DBG] +DEBUG MENU + +[FEC_TGD] +Toggle Pad Game/Debug + +[FEC_TDO] +Turn Debug Camera Off + +[FEC_IVH] +Invert Mouse Horizontally: + +[FEC_MSL] +LMB + +[FEC_MSM] +MMB + +[FEC_MSR] +RMB + +[FEC_QUE] +??? + +[FEC_TWO] +Only Two Keyboard Keys Allowed + +[FEC_UMS] +Unique Mouse Keys only please. + +[FEC_OMS] +Only One Mouse Keys Allowed + +[FEC_UJS] +Unique Joystick buttons only please. + +[FEC_OJS] +Only One Joystick Buttons per action allowed + +[FEC_PTL] +Use LockTarget with Weapon Switch Left. + +[FEC_PTR] +Use LockTarget with Weapon Switch Right. + +[FEC_LBC] +Use Look Left With Look Right. + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +Cannot pause in multiplayer. Press twice to quit! + +[FEM_SL1] +Slot 1 is free + +[FEM_SL2] +Slot 2 is free + +[FEM_SL3] +Slot 3 is free + +[FEM_SL4] +Slot 4 is free + +[FEM_SL5] +Slot 5 is free + +[FEM_SL6] +Slot 6 is free + +[FEM_SL7] +Slot 7 is free + +[FEM_SL8] +Slot 8 is free + +[FEM_MM] +MAIN MENU + +[FEQ_SRE] +Are you sure you want to quit? All progress since the last save game will be lost. Proceed? + +[FEQ_SRW] +Are you sure you want to quit the game? + +[FEG_SRV] +SERVER + +[FEG_MAP] +MAP + +[FEG_PLY] +PLAYERS + +[FEG_TYP] +TYPE + +[FEG_PNG] +PING + +[FET_FG] +FIND GAME + +[FET_SP] +SINGLE PLAYER + +[FET_MP] +MULTIPLAYER + +[FET_HG] +HOST GAME + +[FET_PS] +PLAYER SETUP + +[FET_CON] +CONNECTION + +[FET_AUD] +AUDIO SETUP + +[FET_DIS] +DISPLAY SETUP + +[FET_LAN] +LANGUAGE SETUP + +[FET_LG] +LOAD GAME + +[FET_DG] +DELETE GAME + +[FET_NG] +NEW GAME + +[FET_SG] +SAVE GAME + +[FET_MAP] +SELECT MAP + +[FET_GT] +GAME TYPE + +[FET_CTL] +CONTROLLER SETUP + +[FET_OPT] +OPTIONS + +[FET_QG] +QUIT GAME + +[FET_STA] +STATISTICS + +[FET_BRE] +BRIEFS + +[FEC_WAR] +Warning + +[FEC_OKK] +O.K. + +[FED_CON] +Delete File Confirmation + +[FES_SSC] +Game successfully saved. + +[DEL_FNM] +File Successfully Deleted. + +[PCLOAD] +Loading File Data + +[PCRESRT] +Restarting Grand Theft Auto III + +[FEC_DLF] +Delete Failed. + +[FEC_SVU] +Save Unsuccessful. + +[FEC_LUN] +Load Unsuccessful. File Corrupted, Please delete. + +[FEN_PLA] +Number of players: + +[FET_NON] +NO GAMES AVAILABLE + +[FET_SFG] +SEARCHING FOR GAMES... + +[FET_SRT] +SORTING GAMES... + +[FEF_LAN] +LAN + +[FEF_INT] +INTERNET + +[FET_REF] +Refresh + +[FET_FIL] +Filter + +[FET_JG] +Join + +[FEC_NTW] +Talk To Network + +[FEC_ESR] +Escape key is Restricted + +[FEC_GSL] +Show head bob: + +[FIL_FLT] +FILTER GAMES LIST + +[FET_SAN] +START NEW GAME + +[FIL_MAP] +Map: + +[FIL_SRV] +Server: + +[FIL_TYP] +Game type: + +[FIL_SPC] +Games with Space available? + +[FIL_PNG] +Ping: + +[FEN_UKH] +Unknown host + +[FEN_UKM] +Map not found + +[FEN_UKT] +Game type not found + +[FEN_NCI] +NOT CONNECTED TO THE INTERNET + +[FET_PAU] +PAUSE MENU + +[FET_SGA] +START GAME + +[FEC_SGJ] +Set Game Joystick + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FEC_WHL] +Steering wheel + +[FEC_CNT] +Controller type: + +[FES_CSA] +Select a skin from the list below: + +[FES_SKN] +SKIN NAME + +[FES_DAT] +DATE + +[FES_NON] +NO SKINS AVAILABLE + +[FEA_FM9] +MP3 PLAYER + +[FESZ_QZ] +Are you sure you want to save this game? + +[FES_CGA] +Current game slots available: + +[FES_SCG] +Save the current game? + +[FES_LCG] +Load the game and continue playing? + +[FEC_FIR] +Fire + +[FEC_NWE] +Next weapon + +[FEC_PWE] +Previous weapon + +[FEC_FOR] +Forward + +[FEC_BAC] +Backwards + +[FEC_LEF] +Left + +[FEC_RIG] +Right + +[FEC_ZIN] +Zoom in + +[FEC_ZOT] +Zoom out + +[FEC_EEX] +Enter /exit + +[FEC_RAD] +Radio + +[FEC_SUB] +Sub-mission + +[FEC_CMR] +Change camera + +[FEC_JMP] +Jump + +[FEC_SPN] +Sprint + +[FEC_HND] +Handbrake + +[FEC_TUL] +Turret left + +[FEC_TUR] +Turret right + +[FEC_LOL] +Look left + +[FEC_LOR] +Look right + +[FEC_NTR] +Next target + +[FEC_PTT] +Previous target + +[FEC_LBA] +Look behind + +[FEC_CEN] +Center camera + +[FEC_UND] +(NO) + +[FET_CFT] +ON FOOT + +[FET_CCR] +IN CAR + +[CVT_MSG] +Converting textures to optimal format for your video card + +[FET_CAC] +ACTION + +[FEC_IBT] +- + +[FEC_SPC] +SPC + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +UNBOUND + +[FET_CME] +CONTROL METHOD + +[FET_RDK] +REDEFINE CONTROLS + +[FET_AMS] +MOUSE SETTINGS + +[FET_STI] +STANDARD CONTROL CONFIGURATION + +[FET_CTI] +CLASSIC CONTROL CONFIGURATION + +[FET_MTI] +MOUSE CONTROL CONFIGURATION + +[FET_DAM] +DYNAMIC ACOUSTIC MODELING + +[FEC_TFL] +Turret Left + +[FEC_TFR] +Turret Right + +[FEC_MWF] +WHEEL UP + +[FEC_MWB] +WHEEL DN + +[FEC_ORR] +or + +[FEC_NUS] +NOT USED + +[FEC_LUD] +Look Up + +[FEC_LDU] +Look Down + +[FEC_CMP] +COMBO: LOOK L+R + +[FEC_NTT] +No Text Yet For This Key + +[FEC_FNC] +F~1~ + +[FEC_IRT] +INS + +[FEC_DLL] +DEL + +[FEC_HME] +HOME + +[FEC_END] +END + +[FEC_PGU] +PGUP + +[FEC_PGD] +PGDN + +[FEC_UPA] +UP + +[FEC_DWA] +DOWN + +[FEC_LFA] +LEFT + +[FEC_RFA] +RIGHT + +[FEC_NUM] +NUM + +[FEC_NMN] +NUM~1~ + +[FEC_FWS] +NUM / + +[FEC_PLS] +NUM + + +[FEC_MIN] +NUM - + +[FEC_DOT] +NUM . + +[FEC_NLK] +NUMLOCK + +[FEC_ETR] +ENT + +[FEC_SLK] +SCROLL LOCK + +[FEC_PSB] +BREAK + +[FEC_BSP] +BSPACE + +[FEC_TAB] +TAB + +[FEC_CLK] +CAPSLOCK + +[FEC_RTN] +RET + +[FEC_LSF] +LSHIFT + +[FEC_RSF] +RSHIFT + +[FEC_LCT] +LCTRL + +[FEC_RCT] +RCTRL + +[FEC_LAL] +LALT + +[FEC_RAL] +RALT + +[FEC_LWD] +LWIN + +[FEC_RWD] +RWIN + +[FEC_WRC] +WINCLICK + +[WIN_TTL] +Grand Theft Auto III + +[WIN_95] +Grand Theft Auto III cannot run on Windows 95 + +[WIN_DX] +Grand Theft Auto III requires at least DirectX version 8.1 + +[WIN_VDM] +Grand Theft Auto III requires at least 12MB of available video memory + +[DIAB3_G] +Arriba! + +[FEM_RES] +RESUME GAME + +[FES_SNG] +START NEW GAME + +[FEM_SP] +SINGLE PLAYER + +[FEM_MP] +MULTIPLAYER + +[FEM_QT] +QUIT GAME + +[FES_SG] +START NEW GAME + +[FES_LG] +LOAD GAME + +[FEM_HST] +HOST GAME + +[FEM_OPT] +OPTIONS + +[FEM_DBG] +DEBUG + +[FET_PSU] +PLAYER SETUP + +[FET_DEF] +RESTORE DEFAULTS + +[FED_BRI] +BRIGHTNESS + +[FED_TRA] +TRAILS + +[FEM_LOD] +DRAW DISTANCE + +[FEM_VSC] +FRAME SYNC + +[FEM_FRM] +FRAME LIMITER + +[FED_RES] +SCREEN RESOLUTION + +[FED_WIS] +WIDE SCREEN + +[FEDS_TB] +BACK + +[FEA_MUS] +MUSIC VOLUME + +[FEA_SFX] +SFX VOLUME + +[FEA_RSS] +RADIO STATION + +[FEL_ENG] +ENGLISH + +[FEL_FRE] +FRENCH + +[FEL_GER] +GERMAN + +[FEL_ITA] +ITALIAN + +[FEL_SPA] +SPANISH + +[FEA_3DH] +AUDIO HARDWARE + +[FEA_SPK] +SPEAKERS CONFIGURATION + +[FEA_2SP] +2 SPEAKERS + +[FEA_4SP] +MORE THAN 2 SPEAKERS + +[FEA_EAR] +HEADPHONES + +[FEA_NAH] +NO AUDIO HARDWARE + +[FET_SNG] +START NEW GAME + +[FEN_STA] +START GAME + +[GMLOAD] +LOAD GAME + +[GMSAVE] +SAVE GAME + +[FES_DGA] +DELETE GAME + +[FEM_NON] +NONE + +[FEC_IVV] +INVERT MOUSE VERTICALLY + +[FEC_MSH] +MOUSE SENSITIVITY + +[FET_CCN] +CONTROLS: CLASSIC + +[FET_SCN] +CONTROLS: STANDARD + +[FES_SET] +USE SKIN + +[GHOST] +Ghost + +[WIN_RSZ] +Failed to select new screen resolution + +[FEC_TFU] +Turret /Dodo up + +[FEC_TFD] +Turret /Dodo down + +[FET_APL] +APPLY + +[FET_APP] +LMB,RETURN TO APPLY THE NEW SETTING + +[FET_HRD] +DEFAULT SETTINGS RESTORED + +[FET_MST] +MOUSE CONTROLLED STEERING + +[FEC_STR] +NUM STAR + +[FET_MIG] +LEFT,RIGHT,MOUSEWHEEL TO ADJUST + +[FET_CIG] +BACKSPACE TO CLEAR - LMB,RETURN TO CHANGE + +[FET_RIG] +SELECT A NEW CONTROL FOR THIS ACTION OR ESC TO CANCEL + +[FET_EIG] +CANNOT SET A CONTROL FOR THIS ACTION + +[NO_PCCD] +Please insert Grand Theft Auto III - disc 2 into the drive or press ESC to cancel + +[CVT_ERR] +You have run out of disk space. Please make some space on your harddisk before continuing. Press ESC to cancel. + +[FED_SUB] +SUBTITLES + +[FET_DSN] +Default Player Skin.bmp + +[JM3] +'VAN HEIST' + +[ATUTOR2] +~g~Drive the patients to Hospital CAREFULLY. Each bump reduces their chances of survival. + +[EBAL] +'GIVE ME LIBERTY' + +[LM4] +'PUMP-ACTION PIMP' + +[REPLAY] +REPLAY + +[FEC_SFT] +SHIFT + +[CRED254] +STUDIO MANAGER + +[CVT_CRT] +Cannot convert textures for your video card. You must login to an Administrator account to do this. Press ESC to quit. + +[FEM_ON] +ON + +[FEM_OFF] +OFF + +[FEM_YES] +YES + +[FEM_NO] +NO + +[FES_WAR] +Saving, please wait... + +[FED_DLW] +Deleting, please wait... + +[FED_LDW] +Loading, please wait... + +[FEC_SLC] +Slot is corrupted + +[FED_LFL] +Loading save game has failed. The game will restart now. + +[FET_RSO] +ORIGINAL SETTING RESTORED + +[FET_RSC] +HARDWARE NOT AVAILABLE - ORIGINAL SETTING RESTORED + +{ re3 updates } +{ new languages } +[FEL_JAP] +JAPANESE + +[FEL_POL] +POLISH + +[FEL_RUS] +RUSSIAN + +{ new display menus } +[FET_GFX] +GRAPHICS SETUP + +[FED_MIP] +MIP MAPPING + +[FED_AAS] +ANTI ALIASING + +[FED_FIL] +TEXTURE FILTERING + +[FED_BIL] +BILINEAR + +[FED_TRL] +TRILINEAR + +[FED_WND] +WINDOWED + +[FED_FLS] +FULLSCREEN + +[FEM_CSB] +CUTSCENE BORDERS + +[FEM_SCF] +SCREEN FORMAT + +[FEM_ISL] +MAP MEMORY USAGE + +[FEM_LOW] +LOW + +[FEM_MED] +MEDIUM + +[FEM_HIG] +HIGH + +[FEM_2PR] +PS2 ALPHA TEST + +[FEC_FRC] +FREE CAM + +{ Linux joy detection } +[FEC_JOD] +DETECT JOYSTICK + +[FEC_JPR] +Press any key on the joystick of your choice that you want to use on the game, and it will be selected. + +[FEC_JDE] +Detected joystick + +{ mission restart } +[FET_RMS] +REPLAY MISSION + +[FESZ_RM] +RETRY? + +{ more graphics } +[FED_VPL] +VEHICLE PIPELINE + +[FED_PRM] +PED RIM LIGHT + +[FED_RGL] +ROAD GLOSS + +[FED_CLF] +COLOUR FILTER + +[FED_WLM] +WORLD LIGHTMAPS + +[FED_MBL] +MOTION BLUR + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MOBILE + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +AUTO + +{ controls } +[FEC_IVP] +INVERT PAD VERTICALLY + +{ map } +[FEM_TWP] +Toggle Waypoint + +[FEA_FMN] +RADIO OFF + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +XBOX 360 CONTROLLER + +[FEC_ONE] +XBOX ONE CONTROLLER + +[FEC_NSW] +NINTENDO SWITCH CONTROLLER + +[FEC_TYP] +GAMEPAD TYPE + +[FEC_CCF] +CONFIGURATION + +[FEC_CF1] +SETUP 1 + +[FEC_CF2] +SETUP 2 + +[FEC_CF3] +SETUP 3 + +[FEC_CF4] +SETUP 4 + +[FEC_CDP] +CONTROLLER DISPLAY + +[FEC_ONF] +ON FOOT + +[FEC_INC] +IN CAR + +[FEC_VIB] +VIBRATION + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +{ end of file } + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/build.bat b/utils/gxt/build.bat new file mode 100644 index 0000000..a674850 --- /dev/null +++ b/utils/gxt/build.bat @@ -0,0 +1,8 @@ +gxt -g III -i "american.txt" -o "../../gamefiles/TEXT/american.gxt" +gxt -g III -i "english.txt" -o "../../gamefiles/TEXT/english.gxt" +gxt -g III -i "french.txt" -o "../../gamefiles/TEXT/french.gxt" +gxt -g III -i "german.txt" -o "../../gamefiles/TEXT/german.gxt" +gxt -g III -i "italian.txt" -o "../../gamefiles/TEXT/italian.gxt" +gxt -g III -i "spanish.txt" -o "../../gamefiles/TEXT/spanish.gxt" +gxt -g III -r -i "russian.txt" -o "../../gamefiles/TEXT/russian.gxt" +gxt -g III -p -i "polish.txt" -o "../../gamefiles/TEXT/polish.gxt" diff --git a/utils/gxt/english.txt b/utils/gxt/english.txt new file mode 100644 index 0000000..adc6378 --- /dev/null +++ b/utils/gxt/english.txt @@ -0,0 +1,7026 @@ +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~Hey! Get back in the vehicle! + +[IN_VEH2] +~g~You need some wheels for this job! + +[IN_BOAT] +~g~You need a boat for this job! + +[HEY] +~g~Don't go solo, keep your posse together! + +[HEY2] +~g~Don't split up, keep the group together! + +[HEY3] +~g~You've dropped your main man, go back and get 8-Ball! + +[HEY4] +~g~Lose Misty and Luigi will lose your face! Go and get her! + +[HEY5] +~g~One of the girls is AWOL, Go back and round her up! + +[HEY6] +~g~You left your honor with the Yakuza Kanbu. You must protect him! + +[HEY7] +~g~An extra gun could be useful. Go back and pick up your contact! + +[HEY8] +~g~Protection means just that -Protect the Old Oriental Gentleman! + +[HEY9] +~g~You want the word on the street? Go see the contact! + +[HELP2_A] +Press the ~h~/ button~w~ when running to ~h~sprint. + +[HELP3] +You can only sprint for short periods before becoming tired. + +[HELP4_A] +Press the~h~ ~k~~VEHICLE_ACCELERATE~ button~w~ to ~h~accelerate. + +[HELP4_D] +Push the~h~ right analog stick~w~ up to ~h~accelerate. + +[HELP5_A] +Press the~h~ ~k~~VEHICLE_BRAKE~ button~w~ to ~h~brake~w~, or to ~h~reverse~w~ if the vehicle has stopped. + +[HELP5_D] +Pull the ~h~right analog stick~w~ back to ~h~brake~w~, or to ~h~reverse~w~ if the vehicle has stopped. + +[HELP6_A] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP6_C] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP6_D] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP7_A] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ button ~w~to ~h~target~w~ with the sniper rifle. + +[HELP7_D] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ button ~w~to ~h~target ~w~with the sniper rifle. + +[HELP8_A] +Press the~h~ ~k~~PED_SNIPER_ZOOM_IN~ button ~w~to ~h~zoom in ~w~with the rifle and the~h~ ~k~~PED_SNIPER_ZOOM_OUT~ button ~w~to ~h~zoom out ~w~again. + +[HELP9_A] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HELP10] +This badge indicates you have a police wanted level. + +[HELP11] +The more badges the higher your wanted level. + +[HELP13] +Sometimes you may need to use pathways not shown on the radar. + +[TIMER] +This is a timed mission, you must complete it before the timer counts down to zero. + +[MISTY1] +~r~Misty is morgue-meat! + +[OUT_VEH] +~g~Get out of the vehicle! + +[GARAGE] +Drive the vehicle into the garage, then walk outside. + +[WANTED1] +~g~Shake the cops and lose your wanted level! + +[NODOORS] +~g~They ain't sardines! Get some wheels with enough seats. + +[TRASH] +~g~You've junked your wheels real bad! Get your vehicle repaired! + +[WRECKED] +~r~The vehicle is wrecked! + +[HORN] +~g~Sound the horn. + +[NOMONEY] +~g~You need more cash! + +[OUTTIME] +~r~Too slow, man, too slow! + +[SPOTTED] +~r~They're on to you! + +[REWARD] +REWARD $~1~ + +[GAMEOVR] +GAME OVER + +[Z] +Z-axis value: ~1~ + +[M_FAIL] +MISSION FAILED! + +[M_PASS] +MISSION PASSED! $~1~ + +[O_PASS] +ODD JOB PASSED! + +[O_FAIL] +ODD JOB FAILED! + +[DEAD] +WASTED! + +[BUSTED] +BUSTED! + +[S_PROMP] +When not on a mission you can ~h~save your game here~w~, this will advance time six hours. + +[NUMBER] +~1~ + +[SCORE] +$~1~ + +[LOADCAR] +LOADING VEHICLE... (PRESS L1 TO CANCEL) + +[CARSOFF] +Cars turned off. + +[CARS_ON] +Cars turned on. + +[TEXTXYZ] +Writing coordinates to file... + +[CHEATON] +Cheat mode ON + +[CHEATOF] +Cheat mode OFF + +[UZI_IN] +The Uzi is now in stock at Ammunation! + +[IMPORT1] +Go outside and wait for your vehicle. + +[PAGEB1] +Pistol delivered to hideout + +[PAGEB2] +Uzi delivered to hideout + +[PAGEB3] +Body armor delivered to hideout + +[PAGEB4] +Shotgun delivered to hideout + +[PAGEB5] +grenades delivered to hideout + +[PAGEB6] +molotovs delivered to hideout + +[PAGEB7] +AK47 delivered to hideout + +[PAGEB8] +Sniper rifle delivered to hideout + +[PAGEB9] +M16 delivered to hideout + +[PAGEB10] +Rocket Launcher delivered to hideout + +[PAGEB11] +Flamethrower delivered to hideout + +[WANT_A] +You will only be arrested if you have a ~h~wanted level. + +[WANT_B] +Your ~h~wanted level~w~ is represented by the row of stars in the top right of the screen. + +[WANT_C] +You now have a ~h~wanted level~w~ of one... + +[WANT_D] +two... + +[WANT_E] +three... + +[WANT_F] +As your ~h~wanted level~w~ increases you will attract more powerful forms of law enforcement. + +[WANT_G] +When you are ~h~'busted'~w~ you are returned to the nearest police station. + +[WANT_H] +The cops will take all your weapons and some of your cash as a bribe. + +[WANT_I] +Any mission you were on will be failed. + +[WANT_J] +You will find ways of reducing your wanted level the more you play. + +[WANT_K] +If you are in a car, ~h~SPRAY SHOPS~w~ will ~h~clear your wanted level. + +[HEAL_B] +When you are ~h~'wasted'~w~ you are returned to the nearest hospital. + +[HEAL_C] +You will lose your weapons and the doctors will take some cash for patching you up. + +[HEAL_E] +You will find ways of healing or protecting yourself the more you play the game. + +[DAM] +DAMAGE: + +[KILLS] +KILLS: + +[FARES] +FARES: + +[BULL] +BULLION: + +[EVID] +EVIDENCE: + +[HEALTH] +CAR HEALTH: + +[COLLECT] +COLLECTED: + +[BOMB] +Drive your vehicle into the bomb shop to attach a ~h~bomb~w~. Cost - ~h~$1000. + +[SAVE1] +Walk through the doorway to ~h~Save the game~w~. You cannot save during a mission. + +[SAVE2] +Any vehicle left in this garage will be stored when the game is saved. + +[AMMU] +Go inside Ammu-Nation to buy a weapon. + +[BRIDGE1] +When the Callahan Bridge is repaired you will be able to drive to Staunton Island. + +[TUNNEL] +When the Porter Tunnel is opened you will be able to drive to Staunton Island. + +[LUIGI] +LUIGI MISSIONS + +[TONI] +TONI MISSIONS + +[JOEY] +JOEY MISSIONS + +[FRANK] +SALVATORE MISSIONS + +[DIABLO] +DIABLO MISSIONS + +[ASUKA] +ASUKA MISSIONS + +[B_SITE] +ASUKA SUBURBAN MISSIONS + +[KENJI] +KENJI MISSIONS + +[RAY] +RAY MISSIONS + +[LOVE] +LOVE MISSIONS + +[YARDIE] +YARDIE MISSIONS + +[HOOD] +HOOD MISSIONS + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Atlantic Quays + +[PORT_E] +Portland Harbor + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Portland Beach + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Red Light District + +[TOWERS] +Hepburn Heights + +[HARWOOD] +Harwood + +[ROADBR1] +Callahan Bridge + +[ROADBR2] +Callahan Bridge + +[TUNNELP] +Porter Tunnel + +[BOMB1] +8-Ball's Garage + +[COM_ZON] +Staunton Island + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Liberty Campus + +[CONSTRU] +Fort Staunton + +[PARK] +Belleville Park + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Francis Intl. Airport + +[PROJECT] +Wichita Gardens + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Cochrane Dam + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Ambulance + +[CAR_2] +Firetruck + +[CAR_3] +Police + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Rhino + +[CAR_7] +FBIcar + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_10] +Coach + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Cabbie + +[CAR_39] +Buggy + +[LUIGIS] +Luigi's Place + +[GOAWAY] +~g~You are already on a mission! + +[LUIGGO] +~g~Luigi's interviewing some new girls -Come back later! + +[JOEYGO] +~g~Joey's out on the town with Misty -Drop by later! + +[TONIGO] +~g~Toni's taken his Momma to the opera -Call in some other time! + +[KEMUGO] +~g~Maria and Kemuri are all tied up at the moment -Drop by later! + +[KENJGO] +~g~Kenji is attending a Yakuza meeting -Call by some other time! + +[RAYGO] +~g~Ray has other toilets to hang around -Try again later! + +[LOVEGO] +~g~Donald Love has other business to attend to -Make an appointment later! + +[KENSGO] +~g~Kenji is busy! -Call by later! + +[HOODGO] +~g~The Hoods are not available at this time! + +[WRONGT1] +~g~Come back between 05:00 and 21:00 for a job + +[WRONGT2] +~g~Come back between 06:00 and 14:00 for a job + +[WRONGT3] +~g~Come back between 15:00 and 00:00 for a job + +[GUN_1A] +Use the ~h~~k~~PED_CYCLE_WEAPON_RIGHT~ button ~w~and the ~h~~k~~PED_CYCLE_WEAPON_LEFT~ button ~w~to cycle through your weapons. + +[GUN_2A] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_2C] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_2D] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_3A] +While holding the ~h~~k~~PED_LOCK_TARGET~ button,~w~ press the ~h~~k~~PED_CYCLE_TARGET_LEFT~ button~w~ or the ~h~~k~~PED_CYCLE_TARGET_RIGHT~ button to switch target. + +[GUN_3B] +While holding the ~h~~k~~PED_LOCK_TARGET~ button,~w~ press the ~h~~k~~PED_CYCLE_TARGET_LEFT~ button~w~ or the ~h~~k~~PED_CYCLE_TARGET_RIGHT~ button to switch target. + +[GUN_4A] +While holding the ~h~~k~~PED_LOCK_TARGET~ button~w~ you can walk or run while remaining locked onto a target. + +[GUN_4B] +While holding the ~h~~k~~PED_LOCK_TARGET~ button~w~ you can walk or run while remaining locked onto a target. + +[GUN_5] +You can practice targeting and shooting on these paper targets. When you are finished resume the mission. + +[TAXI1] +~g~Look for a fare. + +[FARE1] +~g~Destination ~w~'Meeouch Sex Kitten Club' ~g~in Redlight. + +[FARE2] +~g~Destination ~w~'Supa Save' ~g~in Portland View. + +[FARE3] +~g~Destination ~w~'old school hall' ~g~in Chinatown. + +[FARE4] +~g~Destination ~w~'Greasy Joe's Cafe' ~g~in Callahan Point. + +[FARE5] +~g~Destination ~w~'AmmuNation' ~g~in Redlight. + +[FARE6] +~g~Destination ~w~'Easy Credit Autos' ~g~in Saint Mark's. + +[FARE7] +~g~Destination ~w~'Woody's topless bar' ~g~in Redlight. + +[FARE8] +~g~Destination ~w~'Marcos Bistro' ~g~in Saint Mark's. + +[FARE9] +~g~Destination ~w~'import export garage' ~g~in Portland Harbor. + +[FARE10] +~g~Destination ~w~'Punk Noodles' ~g~in Chinatown. + +[FARE12] +~g~Destination ~w~'Football Stadium' ~g~in Aspatria. + +[FARE13] +~g~Destination ~w~'The Church' ~g~in Bedford Point + +[FARE14] +~g~Destination ~w~'The Casino' ~g~in Torrington + +[FARE15] +~g~Destination ~w~'Liberty University' ~g~in Liberty Campus + +[FARE16] +~g~Destination ~w~'Shopping Mall' ~g~in Belleville Park Area + +[FARE17] +~g~Destination ~w~'Museum' ~g~in Newport + +[FARE18] +~g~Destination ~w~'AmCo Building' ~g~in Torrington + +[FARE19] +~g~Destination ~w~'Bolt Burgers' ~g~in Bedford Point + +[FARE20] +~g~Destination ~w~'The Park' ~g~in Belleville + +[FARE21] +~g~Destination ~w~'Francis intl. Airport' + +[FARE22] +~g~Destination ~w~'Cochrane Dam' + +[FARE24] +~g~Destination ~w~'The hospital' ~g~in Pike Creek + +[FARE25] +~g~Destination ~w~'The Park' ~g~in Shoreside Vale + +[FARE26] +~g~Destination ~w~'North West Towers' ~g~in Wichita Gardens + +[NEW_TAX] +BIGGER! FASTER! HARDER! new Borgnine taxis open for business in Harwood. Call 555-BORGNINE today! + +[TSCORE2] +$~1~ + +[IN_ROW] +~1~ IN A ROW bonus! $~1~ + +[TTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle taxi missions on or off. + +[TTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle taxi missions on or off. + +[ATUTOR2] +~g~Drive the patients to Hospital CAREFULLY. Each bump reduces their chances of survival. + +[A_TIME] ++~1~ seconds + +[A_FULL] +~r~Ambulance full!! + +[A_RANGE] +~g~The ambulance radio is out of range, get closer to a hospital! + +[FTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle fire truck missions on or off. + +[FTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle fire truck missions on or off. + +[F_PASS1] +Fire extinguished! + +[F_RANGE] +~g~The fire truck radio is out of range, get closer to a fire station! + +[C_BREIF] +~g~Suspect last seen in the ~a~ area. + +[C_RANGE] +~g~The police radio is out of range, get closer to a police station! + +[DODO_FT] +You flew for ~1~ seconds! + +[EBAL] +'GIVE ME LIBERTY' + +[EBAL_A] +I know a place on the edge of the Red Light District where we can lay low, + +[EBAL_A1] +but my hands are all messed up so you better drive, brother. + +[EBAL_1] +Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a vehicle. + +[EBAL_1B] +Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a vehicle. + +[EBAL_2] +~g~Get back into the car! + +[EBAL_3] +This is the ~h~radar~w~. Use it to navigate the city, follow the ~h~blip~w~ on the ~h~radar~w~ to find the hideout! + +[EBAL_D] +I know a guy, he's connected, his name's Luigi. + +[EBAL_D1] +Me an' him go back so I could probably get you some work. C'mon lets head over there. + +[EBAL_E] +C'mon, lets drop by and I'll introduce you. + +[EBAL_I] +The boss will be out to see you shortly... + +[EBAL_J] +8-Ball's got some business up stairs. + +[EBAL_K] +Maybe you can do me a favor. + +[EBAL_L] +One of my girls needs a ride so grab a car and pick up Misty from the clinic. Then bring her back here. + +[EBAL_N] +So keep your hands on the wheel! + +[EBAL_4] +~r~8-Ball's dead! + +[EBAL_5] +~g~Get a vehicle! + +[EBAL_6] +~g~Pick up Misty! + +[LM1] +'LUIGI'S GIRLS' + +[LM2] +'DON'T SPANK MA BITCH UP' + +[LM3] +'DRIVE MISTY FOR ME' + +[LM4] +'PUMP-ACTION PIMP' + +[LM5] +'THE FUZZ BALL' + +[LM1_2] +~g~Take Misty to Luigi's Club. + +[LM1_3] +~g~Press the horn to get the girl into the car. + +[LM1_6] +~g~Get back into the car! + +[LM1_7] +Stop the vehicle next to Misty and allow her to enter it. + +[LM1_8] +You can go and see Luigi for more work or check out Liberty City. + +[LM2_A] +There's a new high on the street goes by the name of SPANK. + +[LM2_E] +Some wiseguy's been introducing this trash to my girls down Portland Harbor. + +[LM2_B] +Go and introduce a bat to his face! + +[LM2_G] +I want compensation for this insult! + +[LM2_1] +~g~Take his car and get it resprayed. + +[LM2_2A] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat! + +[LM2_2C] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat~w~! + +[LM2_2D] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat~w~! + +[LM2_3] +~g~Stash the car in Luigi's lockup! + +[LM2_4] +~g~Respray the car! + +[LM3_A] +Hey I've gotta talk to you... All right Mick I'll talk to yah later. + +[LM3_B] +How yah doing kid? + +[LM3_C] +The Don's son, Joey Leone, he wants some action from his regular girl Misty. + +[LM3_D] +Go pick her up at Hepburn Heights... + +[LM3_E] +but watch yourself that's Diablo turf. + +[LM3_F] +Then run her over to his garage in Trenton and make it quick, + +[LM3_H] +so keep your eyes on the road and off Misty! + +[LM3_2] +~g~Take Misty to Joey's. + +[LM3_4] +~g~Go pick up Misty! + +[LM3_5] +You working regular for Luigi now huh? It's about time he got a driver we can trust! + +[LM3_7] +I'll be with you in a minute spark plug. + +[LM3_10] +~g~Get a vehicle! + +[LM4_B] +Go and take care of things for me. + +[LM4_C] +If you need a piece go around the back of AmmuNation opposite the subway. + +[LM5_A] +The Policeman's Ball is being held at the old school hall near the Callahan Bridge + +[LM5_B] +and they'll be looking for some 'old school' action. + +[LM5_C] +Now I got girls all over town walking the streets. + +[LM5_D] +Get'em to the ball they'll make a bundle. + +[LM5_1] +~g~You pack these ladies too tight, they gonna bruise! ~g~Drop these girls off first, then come back for more. + +[LM5_2] +~r~One of Luigi's girls is bodybag meat! + +[LM5_3] +~g~You need a car! + +[LM5_4] +~g~Pick up the girls working St. Marks. + +[LM5_5] +~g~Take the girls to the Fuzz Ball! + +[LM5_8] +~g~Girls working the Ball: ~1~ + +[JM2] +'FAREWELL 'CHUNKY' LEE CHONG' + +[JM3] +'VAN HEIST' + +[JM4] +'CIPRIANI'S CHAUFFEUR' + +[JM5] +'DEAD SKUNK IN THE TRUNK' + +[JM1_1] +~g~Take Forelli's car to 8-Ball's garage North of here, behind 'Easy Credit Autos'. + +[JM1_2] +~g~Park the car back at Marco's Bistro. + +[JM1_3] +~g~Activate the car bomb then get out of there! + +[JM1_4] +~g~You're trashing the vehicle! Get it repaired! + +[JM1_5] +~g~The car bomb's not set! + +[JM1_6] +~g~Put the car back in the correct position. + +[JM1_8A] +~y~Hey, it's my main man! + +[JM1_8B] +~y~The bomb shop's automated. Just drive in, stop your car and the shop will do the rest. + +[JM1_8C] +~y~Here, your first can be free, but after that it'll cost. + +[JM2_A] +Chunky Lee Chong is pushing spank for some new gang from Colombia... or Colorado... or something.... + +[JM2_B] +I'm not really sure. Who needs details anyway. + +[JM2_D] +That rat has sold his last stir fry. + +[JM2_E] +I want you to take him out! + +[JM2_G] +Sort yourself with a nine, you know where it is, right? + +[JM2_H] +Well remember, just watch your back in China Town, it's Triad territory. + +[JM3_A] +Alright, we're gonna hit the pay role van. + +[JM3_B] +It leaves the edge of China Town everyday. + +[JM3_C] +Bullets won't even dent the van's armor, so get a car and ram it off the road. + +[JM3_D] +Now hit it hard and the punk ass security guards should bail. + +[JM3_E] +Then take it to the warehouse at the docks and my guys are gonna take over from there. + +[JM3_F] +Now it won't be doin' it's rounds all day, so don't hang around. + +[JM3_1] +~g~Take the van to the lock up. + +[JM3_2] +~g~Ram the van until its damage is below 70 percent. + +[JM4_B] +Oh! Here's the guy I was telling you about! + +[JM4_C] +Alright Listen. This guy ain't Italian and he's no mechanic but he can get things fixed. + +[JM4_D] +This is Pops Capo, Toni Cipriani. + +[JM4_E] +Yeah, I'm Toni Cipriani + +[JM4_F] +Take him to Momma's restaurant at St Marks, alright. + +[JM4_G] +Now listen to me, I'm planning a job that needs a good driver so drop by sometime later Ok? + +[JM4_2] +Wait here! Keep the engine running. This ain't a social call. + +[JM4_3] +It's a Triad ambush! Get us out of here kid! + +[JM4_4] +The Triads think they can mess with me, the triads, with ME! + +[JM4_6] +Hey watch the car! I said no fancy crap. + +[JM4_7] +~g~Take Toni to his momma's restaurant. + +[JM4_8] +~r~Toni's been wasted! + +[JM5_A] +Beautiful! Just beautiful. + +[JM5_B] +Alright, Just the guy I need to talk to! + +[JM5_D] +One of the Forellis thought he was a wise guy, so he got what he had coming to him. + +[JM5_E] +Take the corpse to the crusher in Harwood, alright? + +[JM5_1] +~g~Take it to the crusher! + +[JM5_2] +~g~It's the Forelli brothers! + +[JM6_A] +What a ride she's gonna be, huh? + +[JM6_B] +Alright, listen. Get some wheels to the safehouse at St. Marks and pick up a few friends of mine. + +[JM6_C] +They're hittin' a bank and they need a driver. + +[JM6_D] +I gave my word that you were the man, so don't screw this up. + +[JM6_E] +Get them to the bank before five o'clock, not a minute after. + +[JM6_2] +Keep the engine running we'll be in and out in no time. + +[JM6_3] +Get us out of here!! + +[JM6_4] +Shake the cops and get us to the safehouse!! + +[JM6_6] +~g~Go and get a vehicle less conspicuous! + +[JM6_7] +~g~You need all 3 to rob the bank! + +[TM1] +'TAKING OUT THE LAUNDRY' + +[TM2] +'THE PICK-UP' + +[TM3] +'SALVATORE'S CALLED A MEETING' + +[TM4] +'TRIADS AND TRIBULATIONS' + +[TM5] +'BLOW FISH' + +[TONI_P] +I've got some urgent work for you! -Toni + +[TM1_A] +~w~Take a seat kid, take a god damned seat. + +[TM1_B] +~w~So the laundry won't pay any protection eh? + +[TM1_C] +~w~The Triads think they can mess with me? + +[TM1_D] +~w~Let's teach these would be tough guys what it means to be a tough guy. + +[TM1_E] +~w~Yeah, teach 'em some respect. No son of mine gets it from some Triads. + +[TM1_F] +~w~Your father, god rest his soul, took no crap from no Triads back in Sicily. + +[TM1_G] +~w~Sorry Ma. Yes Ma. + +[TM1_H] +~w~I want you to destroy their laundry vans + +[TM1_I] +~w~and mangle any triad gimp that gets in your way. + +[TM1_J] +~w~8-Ball can supply you with what you're gonna need. + +[TM2_A] +~w~TONI's off making people bleed or trying to. + +[TM2_AA] +~w~He'll never be as tough as his Pop, but he left you a note on the table. + +[TM2_B] +~w~The laundry has agreed to pay - you did real good kid! + +[TM2_C] +~w~Go collect the cash and bring it back here. Watch out for the Triads. + +[TM2_D] +~w~They may be shoving a firecracker up your ass, but don't take no crap. + +[TM2_E] +~w~Nobody I mean nobody, messes with TONI CIPRIANI! + +[TM2_1] +~g~Get the cash back to Toni's!! + +[TM2_2] +~g~You iced them all! + +[TM3_MA] +~w~I don't know where he is! + +[TM3_MB] +~w~I swear that boy of mine don't know himself sometimes. + +[TM3_MC] +~w~Now his father, he was different. Always on top, in charge, manful... + +[TM3_A] +~w~Don Salvatore has called a meeting. + +[TM3_B] +~w~I need you to collect the limo and his boy, Joey, from the garage. + +[TM3_C] +~w~Then get Luigi from his club, come back here and pick me up, + +[TM3_D] +~w~then we'll all drive over to the boss's place together. + +[TM3_E] +~w~Those Triads, they don't know when to stop. + +[TM3_F] +~w~They want a war. They got a war. + +[TM3_G] +~w~Now get going. + +[TM3_1] +~g~Pick up the Stretch from Joey's. + +[TM3_2] +~g~Now go pick up Luigi. + +[TM3_3] +~g~Now go pick up Toni. + +[TM3_4] +~g~Drive the goodfellas to Salvatore's place. + +[TM3_5] +~y~It's a triad ambush!! + +[TM4_B] +~w~We're at WAR! The Triads have a fish factory as a front. + +[TM4_C] +~w~Most of their business goes down at the fish market in Chinatown. + +[TM4_D] +~w~That laundry still owes us protection. + +[TM4_E] +~w~They reckon the Triads are protecting them now, so I say we exact a fitting punishment. + +[TM4_F] +~w~Take these boys over and whack the Triad Warlords! + +[TM4_G] +~w~Hell, if you get a chance, pop some of their soldiers too. + +[TM4_GAT] +~g~You need a 'Triad fish van' to enter. + +[TM5_B] +~w~OK, I've had enough of this shit. + +[TM5_C] +~w~We're gonna finish the Triads in Liberty once and for all! + +[TM5_D] +8-Ball's rigged a dustcart with a bomb. + +[TM5_E] +~w~It's on a timer so if you mess up there'll be no evidence. Go and pick up the dustcart. + +[TM5_F] +~w~Careful, 8-Ball says it's real sensitive and the slightest bump could set that thing off! + +[TM5_G] +~w~Their fish factory will open its gates for a dustcart, so you can drive right in. + +[TM5_H] +~w~Park up between the gas canisters and get the hell out of there! + +[TM5_I] +~w~I want it to rain mackerel. + +[TM5_J] +~w~We're talking real biblical here, nothing low budget. + +[FM2] +'CUTTING THE GRASS' + +[FM4] +'LAST REQUESTS' + +[FM1_A] +~w~Me an' the fellas need to talk business + +[FM1_B] +~w~so you're gonna look after my girl for the evening. + +[FM1_C] +~w~HEY MARIA! MOVE YOUR BUTT! + +[FM1_D] +~w~Dumb broad does this every time. + +[FM1_E] +~w~And here she is, the one and only Queen of Sheba! + +[FM1_F] +~w~What were you doing up there? + +[FM1_G] +~w~Whatever it was, I bet it cost me money. + +[FM1_H] +~w~Well, you don't think I hang around for the conversation, do you? + +[FM1_I] +~w~Get in that car and keep your big mouth shut. + +[FM1_J] +~w~Take the limo but bring it back in one piece, y'hear me? + +[FM1_K] +~w~And watch her, she can be trouble. + +[FM1_L] +~w~Yeah, yeah, yeah! I'm sure your new lap dog has everything covered, + +[FM1_M] +~w~and isn't he big and strong? + +[FM1_N] +~w~Hey Fido, Let's go visit Chico and get some party treats! + +[FM1_P] +~g~That's Chico over there, pull up next to him. + +[FM1_S] +~w~Here you go lady. + +[FM1_TT] +~w~IT'S A POLICE RAID! + +[FM1_1] +~g~Get back into the Stretch! + +[FM1_2] +~g~Get into the Stretch! + +[FM1_3] +~r~Leave Maria and Salvatore will have you whacked, go back and pick her up. + +[FM1_4] +~g~You've dumped the Don's woman! Get back to the warehouse and wait for Maria! + +[FM1_5] +~g~Get Maria safely back to Salvatore's! + +[FM1_6] +~g~Chico won't be there forever, get Maria to the waterfront! + +[FM1_7] +~r~Maria's dead! Salvatore won't be too pleased... + +[FM1_8] +~r~You wasted Maria's supplier! + +[FM2_J] +Leave us alone for a minute. + +[FM2_A] +The Colombian Cartel is making SPANK somewhere in Liberty. + +[FM2_K] +but we don't know where, and they seem to know everything we're doin' before we do. + +[FM2_L] +There is a guy named Curly Bob works the bar at Luigi's. + +[FM2_M] +He's been throwing more money around than he's earning. + +[FM2_N] +He usually gets a taxi home after work. So follow him. + +[FM2_O] +And if he's rattin' us out... kill him. + +[FM2_F] +Here comes our little friend. Mr big mouth himself. + +[FM2_G] +Were you followed? You know what goes on here is our little secret. + +[FM2_H] +No..no, I wasn't followed. You got my stuff? + +[FM2_I] +Here's your SPANK, squealer, now talk. + +[FM2_P] +OK, so the Leone's are fighting wars on two fronts. + +[FM2_Q] +They're in a turf war with the Triads with no sign of either side giving up. + +[FM2_R] +Meanwhile Joey Leone has stirred up some bad blood with the Forellis. + +[FM2_S] +Every day they're losing men and influence in the city. + +[FM2_T] +Salvatore is becoming dangerous and paranoid. He suspects everybody and everything. + +[FM2_U] +With loyalty like yours, what has he possibly got to worry about. + +[FM2_1] +~g~There's Curly Bob! + +[FM2_2] +~g~Curly's left the club, tail him! + +[FM2_5] +~g~Take him to Portland Harbor. + +[FM2_6] +~r~Curly won't get into a smashed-up taxi! + +[FM2_7] +~r~Curly's spooked! The meeting's off! + +[FM2_8] +~g~Whack Curly Bob! + +[FM2_9] +~r~Curly Bob's dead! + +[FM2_10] +~r~Curly got away! + +[FM2_11] +~g~Park out the front of Luigi's Club, Curly Bob will be leaving shortly. + +[FM2_12] +~r~He gave you the slip! + +[FM3_A] +~w~We should take these Colombian bastards out, + +[FM3_B] +~w~but while we're at war with the Triads we ain't strong enough. + +[FM3_C] +~w~The Cartel has got bottomless funds from pushing that SPANK crap. + +[FM3_D] +~w~If we make an open attack on them, they'll wipe the floor with us. + +[FM3_E] +~w~They must be making SPANK on that big boat that Curly lead you to. + +[FM3_F] +~w~So we gotta use our heads, or rather one head. Your head. + +[FM3_G] +~w~I'm asking you to destroy that SPANK factory as a personal favor to me, Salvatore Leone. + +[FM3_H] +~w~If you do this for me, you will be a made man, anything you want. + +[FM3_I] +~w~Go and see 8-Ball, you'll need his expertise to blow-up that boat. + +[FM3_8A] +~w~Yo my man! Salvatore phoned ahead, + +[FM3_8B] +~w~but a job like this is gonna need a lot of fireworks. + +[FM3_8D] +~w~but you know with me you get a lot of bang for your buck. + +[FM3_8E] +~w~Okay, let's do this thing! + +[FM3_8F] +~w~I can set this baby to detonate, but I still can't use a piece with these hands. + +[FM3_8G] +~w~Here, this rifle should help you pop some heads! + +[FM3_4] +~g~Stop the vehicle and let 8-Ball out! + +[FM3_7] +~r~8-Ball's been iced! + +[FM3_8] +~r~The guards have been alerted! + +[FM4_A] +~w~It's my favorite cleaner. + +[FM4_B] +~w~I'm proud of you my boy, you kicked the shit out of those grease balls. + +[FM4_C] +~w~I've got just one little job for you before we can all celebrate. + +[FM4_D] +~w~There's a car around the block from Luigi's club. + +[FM4_E] +~w~The inside is covered in brains. + +[FM4_F] +~w~We had to help some guy make up his mind and it proved a little messy. + +[FM4_H] +~w~Take it to the crusher before the cops find it. + +[AM3] +'PAPARAZZI PURGE' + +[AM4] +'PAYDAY FOR RAY' + +[AM5] +'TWO-FACED TANNER' + +[AM1_A] +We have certain issues to clear up before we can continue any form of relationship, + +[AM1_B] +business or otherwise. Lets lay our cards on the table. + +[AM1_C] +I am Yakuza and I know you worked for Salvatore Leone's family. + +[AM1_D] +I can give you work with our organization, + +[AM1_E] +But first you must prove to me that your ties with the Mafia are truly broken. + +[AM1_G] +Make sure he doesn't reach his club alive. + +[AM1_H] +Meanwhile Maria and I will catch up on old times. + +[AM1_I] +Oh..Asuka, you've got a massager. + +[AM1_J] +That's not a massager. + +[AM1_1] +~g~Salvatore is now leaving Luigi's! + +[AM1_2] +~r~You have been spotted! + +[AM1_3] +~r~You've missed Salvatore! + +[AM1_4] +~r~Nice going, you scared off the target! Call yourself a hitman? + +[AM1_5] +~g~Get to the Red Light District and wait for Salvatore to leave the club. + +[AM1_7] +~r~Salvatore's home, safe and sipping a cocktail. Ain't no one gonna call you the 'Jackal'! + +[AM1_8] +~g~Salvatore will be leaving Luigi's at about ~1~:~1~ + +[AM2_4] +~g~They seen you coming like a dayglow elephant! + +[AM3_A] +A reporter has been nosing around. + +[AM3_B] +Maria and I have taken a little holiday together until you can get rid of this perverted voyeur. + +[AM4_A] +It's my handsome handyman! + +[AM4_B] +Maria's all tied up at the moment but I'll tell her you called. + +[AM4_C] +Who's that? Asuka? I know I've been a naughty girl but I really need to pee! OK? + +[AM4_D] +It's time you met our man inside the LPD. + +[AM4_E] +Here's his payment for the last little job he did for us. + +[AM4_F] +He is understandably cautious. + +[AM4_G] +Get to the pay phone in Torrington as quick as you can and await his instructions. + +[AM5_A] +Maria and I have gone shopping. + +[AM5_B] +Our source in the police has informed us that one of our drivers is a strangely animated undercover cop! + +[AM5_C] +He's more or less useless out of his car, so we've tagged it with a tracer. + +[AM5_D] +Make him bleed! + +[AM5_1] +Tanner's on to you! + +[AS1] +'BAIT' + +[AS2] +'ESPRESSO-2-GO!' + +[AS4] +'RANSOM' + +[AS1_A] +~w~Miguel seems to think I'm mistreating him. + +[AS1_B] +~w~Still, he's revealed the extent to which Catalina fears your quest for revenge. + +[AS2_A] +~w~We underestimated Catalina's plans for SPANK. + +[AS2_B] +~w~It reaches far beyond the Yardies selling it on the street corners. + +[AS2_D] +~w~They've been selling SPANK through the street stalls. + +[AS2_1] +~g~All espresso stalls in Portland wrecked!! + +[AS2_2] +~g~All espresso stalls in Staunton Island wrecked!! + +[AS2_3] +~g~All espresso stalls in Shoreside Vale wrecked!! + +[AS2_4] +~r~The Cartel have warned their pushers!! + +[AS2_5] +~g~There are still espresso stalls in Shoreside Vale and on Staunton Island! + +[AS2_6] +~g~There are still espresso stalls in Shoreside Vale! + +[AS2_7] +~g~There are still espresso stalls on Staunton Island! + +[AS2_8] +~g~There are still espresso stalls in Portland! + +[AS2_9] +~g~There are still espresso stalls in Portland and Shoreside Vale! + +[AS2_10] +~g~There are still espresso stalls in Portland and on Staunton Island + +[AS2_12] +~g~Cruise Liberty's districts to find ~b~Espresso-2-Go stalls! + +[AS3_A] +~W~Do we tighten it some more now, or just wait for it to turn black and fall off? + +[AS3_B] +~w~Give it a quick prod... + +[AS3_D] +~w~My Handyman! + +[AS3_E] +~w~I was bored so I came over to keep Asuka company. + +[AS3_1] +~g~Find the ~r~boat~g~ and get to the ~b~marker buoy! + +[AS3_3] +~g~Wait for the ~y~plane~g~ to start its approach! + +[AS3_5] +~g~Collect the cargo! + +[AS3_4] +~g~Use a rocket launcher to shoot the ~y~plane~g~ down!! + +[AS3_2] +~b~Get to the runway marker buoys! ~y~The plane is on its final approach!! + +[AS3_6] +~g~~1~ OF 8 + +[KM1] +'KANBU BUST-OUT' + +[KM3] +'DEAL STEAL' + +[KM4] +'SHIMA' + +[KM5] +'SMACK DOWN' + +[KM1_A] +My sister speaks highly of you, + +[KM1_E] +though I am yet to be convinced that a gaijin can offer anything but disappointment. + +[KM1_B] +Perhaps you could help deal with a situation that has me at a disadvantage. + +[KM1_F] +Of course failure has its own disgrace. + +[KM1_C] +A Yakuza Kanbu is in custody awaiting transfer for trial. + +[KM1_G] +He is a valued member of the family. + +[KM1_H] +Break him out of custody and get him to the dojo at Bedford Point. + +[KM1_D] +We thankyou for your selfless actions. If you ever need help the dojo will be honoured to provide two men who will stand at your side. + +[KM1_1] +~g~Steal a cop car! + +[KM1_2] +~g~Rig the car with a bomb! + +[KM1_3] +~g~Now get him to the Yakuza dojo. + +[KM1_5] +~g~Okay now go to the police station. + +[KM1_6] +~g~Fit the car with a bomb! + +[KM1_7] +~g~Authorised police vehicles only! + +[KM1_9] +~r~You did not use a car bomb to destroy the wall + +[KM1_10] +~r~The Yakuza Kanbu is dead -along with your honor! + +[KM1_11] +~r~You brought the heat down on yourself! + +[KM2_A] +It is impossible to over-estimate the importance of etiquette in this line of work. + +[KM2_B] +To my eternal shame, a man once did me a favor and I have never had the opportunity to repay his kindness. + +[KM2_C] +The man's weakness is motor cars and he has requested that we acquire him certain models for his collection. + +[KM2_F] +My honor demands it. + +[KM2_2] +~g~Car delivered. + +[KM3_A] +When trouble looms, the fool turns his back, while the wise man faces it down. + +[KM3_B] +The Colombian Cartel have ignored repeated requests to leave our interests in Liberty well alone. + +[KM3_C] +Now they are negotiating terms with the Jamaicans in order to humiliate us further. + +[KM3_D] +They are finalizing a deal across town. + +[KM3_F] +Take one of my men, steal a Yardie car, and go and pay your respects to the Colombians. + +[KM3_E] +Our Honor demands that you leave no one alive. + +[KM3_2] +~g~Go and pick up your contact. + +[KM3_3] +~g~The meeting is being held in the hospital parking lot in Rockford! + +[KM3_4] +~r~They got away! + +[KM3_6] +~g~Kill them, kill them all! + +[KM3_8] +~g~You need a Yardie car to get on with the job! + +[KM3_9] +~r~One of the Colombians is dead, the deal's off. + +[KM3_10] +~r~The contact is dead! + +[KM4_A] +To be truly strong, it is important that you never show weakness. + +[KM4_C] +Go and collect the money immediately, so we can enter it into the casino accounts. + +[KM4_1] +I can't pay you and I wouldn't pay you if I could! + +[KM4_9] +Some young gang just jacked out the place! They took everything! + +[KM4_2] +You guys are useless. + +[KM4_10] +What kind of Yakuza are YOU anyway...? + +[KM4_3] +This ain't what I pay you goons for. If I wanted this kind of protection I'd have used the god damn police service + +[KM4_4] +~g~Punish the gang responsible and retrieve the ~b~protection money~g~! + +[KM4_7] +~r~The shopkeeper's breathed his last! + +[KM4_5] +Donald Love wishes you to drop by his tea garden so you and he can talk. + +[KM4_6] +There's the money its all there! + +[KM4_8] +~g~Briefcase collected! + +[KM5_A] +YOU! How fitting you should choose this moment to show your worthless face! + +[KM5_B] +It would appear your attempts to dissuade the Jamaicans + +[KM5_B1] +from becoming bed fellows with the Cartel were wholly inadequate! + +[KM5_C] +Yardie pushers line Liberty's streets selling packets of SPANK like they were selling hotdogs! + +[KM5_D] +Those Cartel pigs are laughing at us, at me! + +[KM5_E] +I will give you one last chance to prove my sister's faith in you to be well founded! + +[KM5_F] +Run these scumbags into the ground and wash your shame in rivers of our enemies' blood!!! + +[KM5_3] +~r~You failed to kill at least ~1~ yardies. + +[KM5_4] +~g~Congratulations you killed ~1~ Yardies. + +[KM5_5] +~g~Congratulations you killed ~1~ Yardies. BONUS $~1~ + +[RM1] +'SILENCE THE SNEAK' + +[RM3] +'EVIDENCE DASH' + +[RM4] +'GONE FISHING' + +[RM5] +'PLASTER BLASTER' + +[RM1_D] +He's under armed protection in WitSec property down in Newport, some apartment behind the car park. + +[RM1_E] +Torch that place, that should flush 'em out, and you'll hunt 'em down, make sure he never talks to nobody. + +[RM1_1] +~g~Check out the witness protection house. + +[RM1_2] +~g~Take out McAffrey! + +[RM2_A1] +Hey kid over here! + +[RM2_A] +An old army buddy of mine runs a business in Rockford. + +[RM2_D] +He's gonna need some back-up and in return he'll give you knock-down rates on any hardware you buy. + +[RM2_E] +Ray phoned ahead....but I thought there'd be more of you. + +[RM2_F] +Well, three arms are better than one, so grab whatever you need. + +[RM2_G] +~g~Go and check on Phil! + +[RM2_H] +~r~Phil has been killed!! + +[RM2_L] +Heh-hey! If I'd teamed up with you in Nicaragua maybe I'd still have my arm! + +[RM2_N] +Leave the cash behind. Now get out of here, I'll handle the cops. + +[RM3_D] +The evidence is being driven across town. + +[RM3_E] +You are going to have to ram that car and collect each little bit of evidence as it falls out. + +[RM3_F] +When you've got it all, leave it in the car and torch it. + +[RM3_G] +We're both gonna do pretty well outta this kid. + +[RM3_1] +~g~Leave the evidence in a car then torch the car. + +[RM3_4] +~g~The Prosecution has dropped the evidence! + +[RM3_6] +~r~The photos will be washed up all over Liberty! + +[RM3_7] +~g~Now torch the car! + +[RM4_A] +I think my partner's a rat. + +[RM4_C] +He goes fishing out of his boat near the lighthouse on Portland Rock most nights. + +[RM4_D] +Steal a police boat and make sure his back stabbing plans are sunk! + +[RM4_1] +~g~Go and steal a police boat. + +[RM4_2] +~g~Get to the lighthouse and 'rub out' Ray's partner! + +[RM5_A] +You useless bastard! + +[RM5_A1] +You totally messed up! My ass is on the line and you can't even kill a god damned fly. + +[RM5_B] +I paid you good money to kill that witness and he ain't dead! + +[RM5_B1] +And today he's gonna make a Federal Deposition! + +[RM5_C] +He's being moved any second now from the Carson General Hospital up in Rockford. + +[RM5_D] +If he squeals, I squeal.... + +[RM5_E] +so go do the job you were paid for! + +[RM5_1] +~g~Intercept the ambulance. + +[RM5_2] +~g~You've been spotted!! + +[RM5_3] +~g~It was a decoy! + +[RM5_4] +~g~Bullets won't get through that armored bodycast!! + +[RM5_5] +~g~That armored bodycast is flame retardant!! + +[RM5_7] +~r~Witness has been delivered!! + +[RM5_8] +~g~Witness has drowned!! + +[LOVE2] +'WAKA-GASHIRA WIPEOUT!' + +[LOVE3] +'A DROP IN THE OCEAN' + +[LOVE1_A] +First of all, let me thank you for dealing with that personal matter. + +[LOVE1_F] +People will read something into anything these days. + +[LOVE1_D] +They're trying to extort additional funds from me but I don't believe in re-negotiation. + +[LOVE1_E] +A deal is a deal, so they'll not see a penny from me. + +[LOVE1_G] +Go and rescue my friend, do whatever it takes. + +[LOVE1_2] +~g~Rescue the Old Oriental Gentleman. + +[LOVE1_3] +~g~Take the Old Oriental Gentleman back to Donald Love's building. + +[LOVE1_4] +~g~The Old Oriental Gentleman must be in one of the garages.... + +[LOVE1_6] +~r~The Old Oriental Gentleman's guts are all over the street! + +[LOVE1_7] +~g~The gate will only open for a Colombian Gang-car. + +[LOVE2_A] +Nothing drives down real estate prices like a good old fashioned gang war, + +[LOVE2_B] +apart from an outbreak of plague......but that might be going too far in this case. + +[LOVE2_C] +I've noticed the Yakuza and the Colombians are far from friends. + +[LOVE2_D] +Let's capitalise on this business opportunity. + +[LOVE2_E] +I want you to kill the Yakuza Waka-gashira, Kenji Kasen. + +[LOVE2_F] +Kenji is attending a meeting at the top of the multi-story carpark in Newport. + +[LOVE2_G] +Get a Cartel gangcar and eliminate him! + +[LOVE2_H] +The Yakuza must blame the Cartel for this declaration of war. + +[LOVE2_1] +~g~Go to Fort Staunton and steal a Colombian gangcar! + +[LOVE2_2] +~g~Now get to the ~p~multi-storey in Newport~g~ and whack Kenji! + +[LOVE2_3] +~r~If you proceed without a Cartel car you will be identified!! + +[LOVE2_4] +~r~The Yakuza have identified you!! + +[LOVE2_6] +~r~You've killed all the witnesses!! + +[LOVE3_A] +In these days of moral hypocrisy certain valuable commodities can be hard to import. + +[LOVE3_C] +It will drop several packages into the water. + +[LOVE3_D] +Make sure you pick them up before anyone else does. + +[LOVE3_1] +~g~Get a ~r~boat~g~ and follow the ~y~plane~g~! + +[LOVE4] +'GRAND THEFT AERO' + +[LOVE5] +'ESCORT SERVICE' + +[LOVE4_A] +Thank you for retrieving those packages, but they were only a decoy. + +[LOVE4_B] +Sorry about that, but that's sometimes the way in business. + +[LOVE4_C] +My real objective was hidden on the plane all along. + +[LOVE4_F] +I've paid off the officials. + +[LOVE4_1] +~r~The Colombian Cartel is here!! + +[LOVE4_2] +~g~The package is gone! Track down the Colombians and retrieve it. + +[LOVE4_3] +~g~Panlantic Construction...? + +[LOVE4_5] +~g~The package should be in the plane.... + +[LOVE4_6] +~g~Take the lift up the tower! + +[LOVE5_B] +My Oriental friend will need an escort while he takes my latest acquisition to be authenticated. + +[LOVE5_1] +~g~Lets go! + +[LOVE5_2] +~g~You'll need a car! + +[LOVE5_3] +~g~Go ahead and scout the exit of the tunnel! + +[LOVE5_4] +~r~Protect the truck! + +[RM6] +'MARKED MAN' + +[RM6_A] +You weren't followed? Good. + +[RM6_B] +This is it, I'm way over my head and I'm starting to drown here! + +[RM6_D] +I'm a marked man, so I'm getting out of here. + +[RM6_E] +Get me to my flight at the airport and I'll make it worth your while! + +[RM6_666] +Take care of my bullet proof Patriot. See you in Miami, Ray + +[CAT1] +'RANSOM' + +[CAT2] +'THE EXCHANGE' + +[CAT1_A] +I've got your precious Maria. If you don't want her face to look like she fell out with the butcher. + +[CAT2_F] +I broke a nail, and my hair's ruined. Can you believe it? This one cost me fifty dollars! + +[CAT2_G] +I was so scared, but then I thought to myself, you're a big girl now. + +[CAT2_H] +Oh we're going to have such fun, cause, you know, my sister said she wanted to come to stay with her two kids, + +[CAT2_I] +because her husband's playing around again and.. + +[CAT1_E] +XXXX + +[CAT1_F] +Get to Catalina before the time runs out! + +[CAT_MON] +~g~You don't have enough money yet. You need $500,000. + +[BITCH_D] +~g~Maria's dead! + +[WEATHER] +FORCE WEATHER + +[WEATHE2] +WEATHER NORMAL + +[8001] +You failed miserably!! + +[1000] +YOU ARE DEAD + +[1001] +YOU ARE DEAD + +[1002] +YOU ARE DEAD + +[1003] +YOU ARE DEAD + +[1004] +YOU ARE DEAD + +[1005] +BUSTED + +[1006] +BUSTED + +[1007] +BUSTED + +[1008] +BUSTED + +[1009] +BUSTED + +[GA_4] +Car bombs are $1000 each + +[GA_5] +Your car is already fitted with a bomb. + +[GA_6] +Park it, prime it by pressing the ~h~~k~~PED_FIREWEAPON~ button~w~ and LEG IT! + +[GA_7] +Arm with ~h~~k~~PED_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + +[GA_8] +Use the detonator to activate the bomb. + +[GA_9] +You collected ~1~ out of 10 special cars + +[GA_10] +Nice one. Here's your $~1~ + +[GA_11] +We got these wheels already. It's worthless to us! + +[GA_12] +Bomb armed + +[GA_13] +Delivered like a pro. Complete the list and there'll be a bonus for you. + +[GA_14] +All the cars. NICE! Here's a little something. + +[GA_15] +Hope you like the new color. + +[GA_16] +Respray is complementary. + +[GA_19] +We're not interested in that model. + +[GA_20] +We got more of these than we can shift. Sorry man, no deal. + +[CR_1] +Crane cannot lift this vehicle. + +[PU_MONY] +You don't have enough cash. + +[CO_ALL] +You got all of them. Here's a little something... + +[PAUSED] +GAME PAUSED + +[HEALTH1] +Get outta here! You're perfectly healthy. + +[HEALTH2] +Healthcare costs. + +[HEALTH3] +I'll just fix you up. + +[HEALTH4] +That will be $250. + +[FEB_STA] +Stats + +[FEB_BRI] +Briefs + +[FEB_CON] +Controls + +[FEB_AUD] +Audio + +[FEB_DIS] +Display + +[FEB_LAN] +Language + +[FEP_STA] +STATS + +[FEP_BRI] +BRIEFS + +[FEP_CON] +CONTROLS + +[FEP_AUD] +AUDIO + +[FEP_DIS] +DISPLAY + +[FEP_LAN] +LANGUAGE + +[FEF_ST1] +Who's the bad man? + +[FEF_ST2] +How much havoc have you caused + +[FEF_BR1] +Lost the plot? + +[FEF_CO1] +Need more control, freak? + +[FEF_CO2] +Choose the best contoller set-up for your playing style + +[FEF_SA1] +Keep your place in the pile! + +[FEF_SA2] +Save and load your games + +[FEF_AU1] +Pump up the volume! + +[FEF_AU2] +Select a radio station and sound effect + +[FEF_DI1] +Change the game! + +[FEF_DI2] +Customize the game for your TV + +[FEF_LA1] +What you talking about willis? + +[FEF_LA2] +Choose your preferred parlance + +[FEM_ON] +On + +[FEM_OFF] +Off + +[FEM_YES] +Yes + +[FEM_NO] +No + +[FEM_NON] +None + +[FEB_PMB] +Previous Mission Briefs: + +[FEC_NA] +NA + +[FEC_CWL] +Cycle Weapon left + +[FEC_CWR] +Cycle Weapon right + +[FEC_LOF] +Look forward + +[FEC_TAR] +Target + +[FEC_MOV] +Movement + +[FEC_CAM] +Camera modes + +[FEC_PAU] +Pause + +[FEC_ENV] +Enter vehicle + +[FEC_JUM] +Jump + +[FEC_ATT] +Attack or Fire weapon + +[FEC_RUN] +Run + +[FEC_FPC] +First person camera + +[FEC_LL] +Look left + +[FEC_LB1] +Look + +[FEC_LB2] +behind + +[FEC_LB] +Look behind + +[FEC_LR] +Look right + +[FEC_HOR] +Horn + +[FEC_VES] +Vehicle control + +[FEC_RSC] +Radio station cycle + +[FEC_BRA] +Brake or Reverse + +[FEC_HAB] +Hand brake + +[FEC_CAW] +Car weapon + +[FEC_ACC] +Accelerate + +[FEC_SMT] +Special mission trigger + +[FEC_CCF] +Configuration: + +[FEC_CF1] +Setup1 + +[FEC_CF2] +Setup2 + +[FEC_CF3] +Setup3 + +[FEC_CF4] +Setup4 + +[FEC_CDP] +Controller Display: + +[FEC_ONF] +On Foot + +[FEC_INC] +In Car + +[FEC_VIB] +Vibration: + +[FEA_MUS] +Music Volume + +[FEA_SFX] +SFX Volume + +[FEA_OUT] +Output: + +[FEA_ST] +Stereo + +[FEA_MNO] +Mono + +[FEA_RSS] +Radio station select: + +[FEA_NON] +None + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX 109 + +[FED_BRI] +Brightness + +[FED_TRA] +Trails: + +[FEL_ENG] +English + +[FEL_FRE] +French + +[FEL_GER] +German + +[FEL_ITA] +Italian + +[FEL_SPA] +Spanish + +[FED_DBG] +Menu Debug + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_SPR] +Show Ped Road Groups + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_DSR] +Debug Streaming Requests + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Memory Card Menu + +[FEM_RMC] +Register MemCard One + +[FEM_TFM] +Test Format MemCard One + +[FEM_TUM] +Test UnFormat MemCard One + +[FEM_CRD] +Create Root Dir + +[FEM_CLI] +Create And Load Icons + +[FEM_FFF] +Fill First File with Guff + +[FEM_SOG] +Save Only The Game + +[FEM_CES] +Check Every 0kB4 Save + +[FEM_STG] +Save The Game + +[FEM_STS] +Save The Game under GTA3 name + +[FEM_CPD] +Create copy protected mag directory + +[FEM_MC2] +Memory Card Menu 2 + +[FEM_TS] +Test Save: + +[FEM_TL] +Test Load: + +[FEM_TD] +Test Delete: + +[FEM_SL0] +Slot0 + +[FEM_SL1] +Slot1 + +[FEM_SL2] +Slot2 + +[FEM_SL3] +Slot3 + +[FEM_SL4] +Slot4 + +[FEM_SL5] +Slot5 + +[FEM_SL6] +Slot6 + +[FEM_SL7] +Slot7 + +[PL_STAT] +Player stats + +[PE_WAST] +People you've wasted + +[PE_WSOT] +People wasted by others + +[CAR_EXP] +Cars exploded + +[TM_BUST] +Times busted + +[M_WASTE] +Civilian males wasted + +[F_WASTE] +Civilian females wasted + +[PIG_WST] +Cops wasted + +[GNG_WST] +Gang members wasted + +[MED_WST] +Medics wasted + +[FIRE_WS] +Firemen wasted + +[DED_CRI] +Criminals wasted + +[DED_DED] +Deadbeats wasted + +[DED_HOK] +Hookers wasted + +[HEL_DST] +Helicopters destroyed + +[PER_COM] +Percentage completed + +[KGS_EXP] +Kgs of explosives used + +[ACCURA] +Accuracy + +[ELBURRO] +Best Turismo time in secs + +[CAR_CRU] +Cars crushed + +[HED_EX] +Heads exploded + +[TM_DED] +Hospital visits + +[DAYSPS] +Days passed in game + +[MMRAIN] +Mm rain fallen + +[MXCARD] +Max. INSANE Jump dist. (ft) + +[MXCARJ] +Max. INSANE Jump height (ft) + +[MXCARDM] +Max. INSANE Jump dist. (m) + +[MXCARJM] +Max. INSANE Jump height (m) + +[MXFLIP] +Max. INSANE Jump flips + +[MXJUMP] +Max. INSANE Jump rotation + +[BSTSTU] +Best INSANE stunt so far: + +[INSTUN] +Insane stunt + +[PRINST] +Perfect insane stunt + +[DBINST] +Double insane stunt + +[DBPINS] +Perfect double insane stunt + +[TRINST] +Triple insane stunt + +[PRTRST] +Perfect triple insane stunt + +[QUINST] +Quadruple insane stunt + +[PQUINS] +Perfect quadruple insane stunt + +[NOSTUC] +No INSANE stunts completed + +[NOUNIF] +Unique Jumps completed + +[NOUNGM] +Total Unique Jumps + +[NMISON] +Mission attempts + +[NMMISP] +Missions passed + +[PASDRO] +Passengers dropped off + +[MONTAX] +Cash made in taxi + +[DAYPLC] +Daily police spending + +[CRIMRA] +Criminal rating + +[GMSTOR] +Game Store + +[PREBRF] +Previous Briefs + +[CNTLS] +Controls + +[MUSMEN] +Music/SFX + +[GAMSET] +Game Settings + +[LANGUA] +Language + +[DSPLAY] +Display + +[DEBUGM] +Debug Menu + +[QUITOP] +Quit Options + +[CONTRL] +Control Configuration + +[SET1EN] +SetUp 1. Enabled + +[SET1] +SetUp 1. + +[SET2EN] +SetUp 2. Enabled + +[SET2] +SetUp 2 + +[SET3EN] +SetUp 3. Enabled + +[SET3] +SetUp 3 + +[SET4EN] +SetUp 4. Enabled + +[SET4] +SetUp 4 + +[GOBACK] +GoBack + +[SOUND] +SOUND + +[MUSVOL] +Music Volume + +[SFXVOL] +SFX Volume + +[SCROPT] +SCREEN OPTIONS + +[CTRSCR] +Center Screen + +[SCRFOR] +Screen format + +[GMSVLQ] +GAME SAVE-LOAD-QUIT + +[GMREST] +Restart Game + +[GMLOAD] +Load Game + +[GMSAVE] +Save Game + +[NOGMSV] +Can save only at your hideout. + +[DLFILE] +Delete Grand Theft Auto 3 Files + +[CHFILE] +CHOOSE FILE TO LOAD + +[CHFIDL] +CHOOSE FILE TO DELETE + +[SVCONF] +SAVE CONFIRMATION + +[SVFNAM] +Your saved file name is + +[LANGSL] +LANGUAGE SELECTION + +[ENGLIS] +English + +[GERMAN] +German + +[ITALIA] +Italian + +[FRENCH] +French + +[SPAIN] +Spanish + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Parse Heap + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[CRRGON] +ShowCarRoadGroups On + +[CRGOFF] +ShowCarRoadGroups Off + +[CLZOON] +Show Cull Zones On + +[CLZOOF] +Show Cull Zones Off + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (teststuff) + +[UNFRM1] +UnFormatMemCard 1 (teststuff) + +[GORLEV] +Gore Level + +[SICASS] +Sick Fuck + +[SICSIC] +Sick Fucker + +[SCASSL] +Sick Fuck Selected + +[SCSCSL] +Sick Fucker Selected + +[PRVMEN] +Previous Mission Briefs + +[FORMEN] +Format Menu + +[MEMTST] +MemoryCardTest screen + +[REGCAR] +Register MemoryCard One + +[TEFONE] +Test Format MemCard One + +[TEUFON] +Test UnFormat MemCard One + +[CRROOT] +CreateRootDir + +[CRLDIC] +Create and Load Icons + +[FLFSGF] +Fill First File With Guff + +[PUSAVE] +Save Only the game + +[CHEVOK] +CheckEveryOkB4Save + +[SVGMON] +SaveTheGame + +[CNTSAV] +Can't save the game. On a mission. + +[CNCSAV] +Can't save the game. You're in a car + +[CRMGSV] +Create copy protected magazine directory + +[MGSVCN] +MagazineDirectory Created + +[MGSVNC] +MagazineDirectory Not Created + +[YES] +Yes + +[NO] +No + +[X] +x + +[LAST] +Last message. + +[FEDS_XB] +Select + +[FEDS_TB] +Back + +[FEDS_ST] +START button - RESUME + +[FEST_OO] +out of + +[FED_SUB] +Subtitles: + +[FEC_TUC] +Turret control + +[FEC_SM3] +Special mission trigger (R3 button) + +[FEC_RS3] +Radio station cycle (L3 button) + +[FEC_HO3] +Horn (L3 button) + +[DIAB1] +'TURISMO' + +[DIAB2] +'I SCREAM, YOU SCREAM' + +[DIAB3] +'TRIAL BY FIRE' + +[DIAB4] +'BIG'N'VEINY' + +[DIAB1_A] +El Burro wants to offer you an opportunity. Get to the payphone in Hepburn Heights if you want more info. + +[DIAB1_C] +You drive a mean race. Drop by the payphone again and 'El Burro' may have some work for you. + +[DIAB1_1] +~g~3..2..1.. GO GO GO! + +[DIAB1_4] +~g~Get a fast car and get to the starting grid. + +[DIAB1_3] +~r~You couldn't win a raffle, LOSER! + +[DIAB1_2] +~g~Congratulations you won, with an incredible time of ~1~ seconds. + +[FIRST] +~g~1st + +[SECOND] +~g~2nd + +[THIRD] +~g~3rd + +[FOURTH] +~g~4th + +[DIAB2_1] +~g~Pick up the briefcase in Harwood. + +[DIAB2_2] +~g~Find an icecream van. + +[DIAB2_3] +~g~Park the icecream van down at Atlantic Quays. + +[DIAB2_4] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_6] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_7] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_5] +~g~Exit the van then use the remote to detonate the Icecream van. + +[YD1] +'BLING-BLING SCRAMBLE' + +[YD2] +'UZI RIDER' + +[YD3] +'GANGCAR ROUND-UP' + +[YD4] +'KINGDOM COME' + +[YD_P] +King Courtney would like a word. Get to the payphone in Aspatria!! + +[YD1_A] +~w~This is King Courtney. + +[YD1_A1] +~w~My Yardie posse could do with a driver and you've got a reputation for hot moves. + +[YD1_B] +~w~Get to the waste ground opposite the stadium in a car and wait for the other hopefuls. + +[YD1_C] +~w~I've got men watching checkpoints all over Staunton. + +[YD1_D] +~w~First driver to a checkpoint gets a Grand, then it's on to the next stop. + +[YD1_D1] +~w~If you get more checkpoints than any other driver, I could have some work for you. + +[YD1_E] +~g~Prepare to race! + +[YD1_F] +~g~You jumped the start -I like your style!! + +[YD1_G] +~r~This is a CAR RACE. You need a CAR fool! + +[YD1GO] +~g~GO!! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +$1000!! + +[Y1_1ST] +~g~You came first with ~1~ successful checkpoints! + +[Y1_2ND] +~y~2nd with ~1~ successful checkpoints. ~y~Close, but you just ain't the best! + +[Y1_3RD] +~r~3rd with ~1~ successful checkpoints. ~r~I thought you said you were good! + +[Y1_LAST] +~r~You were last! ~r~You wasted my time FOOL! + +[Y1_J1ST] +~y~Joint 1st with ~1~ successful checkpoints. ~y~Good, but you gotta be the best to drive for Queen Lizzy! + +[Y1_J2ND] +~r~Joint 2nd with ~1~ successful checkpoints. You drive like a crazed monkey! + +[Y1JLAST] +~r~Joint last! You talk like a driver, but you drive like a talker! + +[Y1_TEST] +CAR IN WATER!! + +[YD2_A] +~w~I need to see if you're capable of doing my dirty work. + +[YD2_A1] +~w~See if you can be trusted. + +[YD2_B] +~w~Two of my boys will be there any second to take you for a ride, + +[YD2_B1] +~w~see if you are who you say you are. + +[YD2_C] +~w~We're going for a little ride into Hepburn Heights, whack us some filthy Diablos been dissing Queen Lizzy. + +[YD2_CC] +~w~Here, you'll need a 'piece'. + +[YD2_D] +~w~You do the driving and shooting. We'll make sure you don't get cold feet. + +[YD2_E] +~w~Let's drive!! + +[YD2_F] +~r~He's bailed out on us, cap his yellow ass!!! + +[YD2_G1] +~w~Hepburn Heights..Let's kill me some filthy Diablos... + +[YD2_G2] +~w~But remember, ~r~You don't leave this car!! + +[YD2_H] +~w~OK, Get us back to Yardie turf! GO GO GO!! + +[YD2_L] +~w~You did good, Reaperman! + +[YD2_M] +~r~He's wrecked my car! Waste him! + +[YD2_N] +~w~Get your ass back in this car! + +[YD3_A] +I want you to boost some gang cars + +[YD3_A1] +so we can do hits on our enemies' turf. + +[YD3_B] +I need a Mafia Sentinel, + +[YD3_B1] +a Yakuza Stinger and a + +[YD3_B2] +Diablo Stallion so we can hit any gang in Liberty. + +[YD3_C] +Drop them off at the garage in Newport and remember, + +[YD3_C1] +they're no use to us wrecked!! + +[YD3_D] +Spare text label + +[YD3_E] +~r~You've already boosted a diablo gangcar! + +[YD3_F] +~r~You've already boosted a Mafia gangcar! + +[YD3_G] +~r~You've already boosted a Yakuza gangcar! + +[YD3_H] +~g~Diablo gangcar boosted! + +[YD3_I] +~g~Mafia gangcar boosted! + +[YD3_J] +~g~Yakuza gangcar boosted! + +[YD3_K] +~r~The car's nearly wrecked! Get it repaired! + +[YD3_L] +~g~Take it to the ~p~garage! + +[YD3_M] +~r~You've flipped it! Get another one! + +[YD4_A] +Listen up! + +[YD4_A1] +Get over to Bedford Point. + +[YD4_A2] +There's a stash in an old car I need pronto! + +[YD4_B] +LETTER: I hear you've been a busy boy. Well I've been a busy girl. + +[YD4_C] +I think it's time you witnessed the real power of 'SPANK'! Besos y fuderes, Catalina, xxx. + +[YD4_D] +PS: DIE PEEG DOG, DIE!! + +[YD4_1] +~g~SPANKED-up madmen!! + +[YD4_2] +~g~Destroy the madmens' vans!! + +[HM_1] +'UZI MONEY' + +[HM_2] +'TOYMINATOR' + +[HM_3] +'RIGGED TO BLOW' + +[HM_5] +'RUMBLE' + +[HOOD1_A] +Get to the payphone in Wichita Gardens and we'll talk business. + +[HM1_A] +Yo! This is D-Ice of the Red Jacks! + +[HM1_C] +These young punks, they come onto the streets and they got nothing but guns and SPANK on their minds. + +[HM1_3] +~g~The 'Nines' walk their turf in Wichita Gardens. + +[HM2_3] +If you hit a vehicle's wheels the RC buggy will detonate! + +[HM2_4] +If it goes out of range the RC buggy will detonate! + +[HM2_5] +~r~Out of range! + +[HM3_1] +~g~Get to the garage but watch out if the car takes too much damage it will blow! + +[HM3_2] +~g~Take the car back it has to be in perfect condition - no damage! + +[HM3_3] +~g~Get the car repaired! + +[HM4_D] +~g~Get a vehicle! + +[HM4_E] +TEXT NO LONGER REQUIRED + +[HM4_1] +~g~Head to the location where the cargo is scattered, you need to collect 30 pieces of bullion. + +[HM4_2] +~g~Remember when the vehicle becomes too heavy and slow goto the garage and drop off the cargo. + +[HM5_3] +~r~You were told to use a baseball bat only! + +[HM5_4] +~r~Your contact's dead! + +[MEA1] +'THE CROOK' + +[MEA2] +'THE THIEVES' + +[MEA3] +'THE WIFE' + +[MEA4] +'HER LOVER' + +[MEAT1_A] +A friend said you could fix some problems I got. Get to the payphone in Trenton if you think you can help. + +[MEA1_B3] +~g~Go and meet the Bank Manager. + +[MEA1_B6] +~g~Take the car to the crusher to get rid of evidence, get out of the car and the crane will pick it up. + +[MEA1_1] +~r~The Bank Manager's dead! + +[MEA1_2] +~r~You were told to crush the vehicle! + +[MEA1_3] +~g~Get out of the car! + +[MEA1_4] +~r~You have left the Bank Manager behind! + +[MEA2_B3] +~g~Go and meet the thieves. + +[MEA2_B4] +~g~Take them to the Bitch'n' Dog Food Factory + +[MEA2_B6] +~g~Get the car resprayed to get rid of any evidence. + +[MEA2_1] +~r~You were told to crush the vehicle! + +[MEA2_2] +~r~A thief's dead! + +[MEA2_4] +~r~You have left a thief behind! + +[MEA3_B3] +~g~Go and collect Mrs. Chonks + +[MEA3_B6] +~g~Take the car and dump it into the sea, this will get rid of any evidence. + +[MEA3_1] +~r~The wife's dead! + +[MEA3_2] +~r~You were supposed to dump the vehicle in the water! + +[MEA3_3] +~r~You have left his wife behind! + +[MEA4_B3] +~g~Pick up his wife's lover + +[MEA4_B6] +It's far too late for that Marty. You had your chance, but now I'm taking over the business... + +[MEA4_1] +~r~Carlos is dead! + +[MEA4_3] +~r~You have left Carlos the loan shark behind! + +[LOOK_A] +Press and hold the ~h~~k~~VEHICLE_LOOKLEFT~ button ~w~or the ~h~~k~~VEHICLE_LOOKRIGHT~ button~w~ to look ~h~left~w~ or ~h~right~w~ while in a vehicle. Press both to look ~h~behind~w~. + +[LOVE6_1] +~g~Now lead the cops away from the warehouse! + +[LOVE6_2] +~r~You failed to lead the police far enough away! + +[RM4_3] +~r~Ray's partner has escaped! + +[RM6_C] +The CIA seem to have a vested interest in SPANK + +[RM6_C1] +and they don't like us screwing with the Cartel. + +[C_PASS] +THREAT ELIMINATED! + +[CTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Vigilante missions on or off. + +[CTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Vigilante missions on or off. + +[COPCART] +~g~You have ~1~ seconds to return to a police vehicle before the mission ends. + +[C_FAIL] +Vigilante mission ended! + +[C_CANC] +~r~Vigilante mission cancelled! + +[C_ESCP] +~r~The suspect has escaped! + +[C_TIME] +~r~Your time as a law enforcer is over! + +[C_VIGIL] +VIGILANTE BONUS!! + +[A_FAIL2] +~r~Your lack of urgency has been fatal to the patient! + +[A_FAIL3] +~r~The patient is dead!! + +[A_PASS] +Rescued! + +[F_FAIL2] +~r~You're too late! + +[A_COMP2] +You will never get tired! + +[RM2_M] +If you need any firepower just drop by and take what you need from the lockers. + +[HEAL_A] +Your ~h~health~w~ is displayed in orange in the top right of the screen. + +[YD1_CNT] +~1~ of 15! + +[FM1_9] +~g~Thats the party up ahead, drop Maria off out front. + +[FM1_Y] +~w~You know I enjoyed myself for the first time in a long while, and you treated me really good. With respect and everything. + +[FM1_AA] +~w~Oh, I'd better go. I'll see you around I hope. + +[NOCONTE] +Please re-insert an analog controller (DUALSHOCK#) or analog controller (DUALSHOCK#2) in controller port 1 to continue. + +[WRCONT] +The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto 3 requires an analog controller (DUALSHOCK#) or analog controller (DUALSHOCK#2). + +[WRCONTE] +The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto 3 requires an analog controller (DUALSHOCK#) or analog controller (DUALSHOCK#2). + +[WRONGCD] +Incorrect disc. Please insert correct disc. + +[NOCD] +The disc tray is empty. Please insert disc. + +[OPENCD] +The disc tray is open. Please close the disc tray. + +[CDERROR] +Error reading the Grand Theft Auto 3 DVD + +[RESTART] +Starting new game + +[GA_3] +No more freebies. $1000 to respray! + +[GA_1] +Whoa! I don't touch nothing THAT hot! + +[GA_1A] +Come back when you're not so busy... + +[S_PROM2] +The garage next door can store one vehicle when you save your game. + +[STOCK] +out of stock + +[FM1_O] +~w~He's at the rail station at the Chinatown waterfront I think. + +[EBAL_B] +This is the place right here, let's get off the street and find a change of clothes! + +[EBAL_G] +This is Luigi's club. Let's go round the back and use the service door. + +[AM4_3] +You must be Asuka's new errand boy! + +[AM4_4] +You got the money? Is it all here? + +[AM4_5] +I know what you're thinking, another bent cop. + +[AM4_6] +Well, it's a bent world. + +[AM4_7] +Just 'cause I lost a few partners, those suckers from internal affairs have started sniffing around. + +[AM4_8] +Reckon they can smell me. + +[AM4_9] +Well, this city is just one big open sewer. + +[AM4_10] +But I'm gonna need some non-union help. + +[AM4_11] +And if you're interested you'll know where to find me. + +[CAM_A] +Press the ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~ button~w~ to change ~h~camera ~w~modes when on foot or in a vehicle. + +[CAM_B] +Press the ~h~directional button up~w~ and ~h~down~w~ to change ~h~camera ~w~modes when on foot or in a vehicle. + +[KM2_1] +~g~Repair the car, it's gotta be mint. + +[LM3_6] +Joey... + +[LM3_6A] +Am I goin' to play with your big end again? + +[LM3_9A] +there might be some work for you. + +[LM3_9B] +Alright? + +[AWAY2] +~r~They got away. + +[AWAY] +~r~He's clean out of here! + +[JM6_1] +Get to the bank on the main drag. + +[GA_6B] +Park it, prime it by pressing the ~h~~k~~PED_FIREWEAPON~ button~w~ and LEG IT! + +[GA_7B] +Arm with ~h~~k~~PED_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + +[BAT1] +~g~Pick up the bat! + +[EBAL_O] +If you don't mess this up, maybe there be more work for you. Now get outta here! + +[HELP9_B] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HELP9_C] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[JM6_8] +~r~You've lost all the robbers! + +[COLT_IN] +The Pistol is now in stock at Ammunation! + +[TAXI2] +~r~You're out of time! + +[TAXI3] +~r~Your passenger fled in terror! + +[TAXI7] +~r~Your car is trashed, get it repaired. + +[TAXI4] +Fare complete! + +[TAXI5] +SPEED BONUS!! + +[TAXI6] +Taxi mission over + +[FRANGO] +~g~Salvatore wants you to help Toni deal with the Triads first! + +[PAGEB12] +Police Bribe delivered to hideout + +[PAGEB13] +Health delivered to hideout + +[PAGEB14] +Adrenaline delivered to hideout + +[KM1_4] +~g~You need a cop car to do the job! + +[CAT1_B] +bring $500,000 to the Villa at Cedar Grove. + +[JM2_C] +He's got a noodle stand down in China Town. + +[RM6_1] +Here's a key to a lock-up. + +[RM6_2] +You'll find some cash and some 'supplies' I'd stashed in case things got tight. + +[RM6_3] +See y'around. + +[FE_INIP] +Initialising and loading Pause Menu... Please wait. + +[FESZ_CA] +Cancel + +[FESZ_QU] +Quit + +[FESZ_L1] +Game saved successfully! + +[FESZ_L2] +Your saved filename is: + +[FESZ_OK] +OK + +[FES_LGA] +Load Game + +[FES_DGA] +Delete Game + +[FES_NGA] +New Game + +[FES_CAN] +Cancel + +[FESZ_QL] +All unsaved progress in your current game will be lost. Proceed with loading? + +[FESZ_QD] +Proceed with deleting this saved game? + +[FESZ_QO] +Proceed with overwriting this saved game? + +[FESZ_QR] +Are you sure you want to start a new game? All progress since the last save game will be lost. Proceed? + +[FESZ_QS] +PROCEED WITH SAVE ? + +[T4X4_1] +'PATRIOT PLAYGROUND' + +[T4X4_2] +'A RIDE IN THE PARK' + +[T4X4_3] +'GRIPPED!' + +[MM_1] +'MULTISTOREY MAYHEM' + +[T4X4_1A] +~g~You have ~y~5 minutes~g~ to collect ~y~15~g~ checkpoints. ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_1B] +~1~ of 15! + +[T4X4_1C] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~20 SECONDS~g~. + +[T4X4_2A] +~g~You have ~y~2 minutes~g~ to collect ~y~12~g~ checkpoints!! ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_2B] +~1~ of 12! + +[T4X4_2C] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~10 SECONDS~g~. + +[T4X4_3A] +~g~You have ~y~5 minutes~g~ to collect ~y~20~g~ checkpoints. ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_3B] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~15 SECONDS~g~. + +[T4X4_3C] +~1~ of 20! + +[T4X4_F] +~r~You bailed! Too tough for you?! + +[MM_1_A] +~g~You have ~y~2 minutes~g~ to collect ~y~20 checkpoints~g~ in the multistorey! ~g~You may collect them in ~y~ANY ORDER. + +[MM_1_B] +~1~ of 20! + +[MM_1_C] +~g~That's 20 seconds, plus ~y~5 SECONDS~g~ for each checkpoint. ~g~The timer will start ~y~IMMEDIATELY. + +[FM2_14] +~r~You got too close and spooked Curly! + +[FM2_15] +~g~Don't get too close or Curly will suspect something! + +[UPSIDE] +~r~You flipped your wheels! + +[FM2_16] +SPOOKOMETER: + +[LM3_11] +~g~Misty won't ride in a bus get another vehicle! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Firetruck + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulance + +[FBICAR] +Fbi Car + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Police + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[TRAIN] +Train + +[HELI] +Helicopter + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +RC Bandit + +[BELLYUP] +Triad Fish Van + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Mafia Sentinel + +[YARDICR] +Yardie Lobo + +[YAKUZCR] +Yakuza Stinger + +[DIABLCR] +Diablo Stallion + +[COLOMCR] +Cartel Cruiser + +[HOODSCR] +Hoods Rumpo XL + +[AEROPL] +Aeroplane + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Dist. travelled on foot (miles) + +[FEST_DC] +Dist. travelled by car (miles) + +[FESTDFM] +Distance travelled on foot (m) + +[FESTDCM] +Distance travelled by car (m) + +[FEST_R1] +Patriot Playground in secs + +[FEST_R2] +A Ride In The Park in secs + +[FEST_R3] +Gripped! in secs + +[FEST_RM] +Multistorey Mayhem in secs + +[FEST_LS] +People saved in an Ambulance + +[FEST_CC] +Criminals killed on Vigilante Mission + +[FEST_FE] +Total fires extinguished + +[FEST_LF] +Longest flight in Dodo + +[FEST_BD] +Best time for bomb defusal + +[FEST_RP] +Rampages passed + +[FEST_MP] +Missions passed + +[FEST_BB] +Bling-bling Scramble: + +[FEST_H0] +Most checkpoints + +[FEST_GC] +Gang Cars Totalled: + +[FEST_H1] +Diablo destruction + +[FEST_H2] +Mafia Massacre + +[FEST_H3] +Casino Calamity + +[FEST_H4] +Rumpo Wrecker + +[USJI1] +TEXT NO LONGER REQUIRED + +[USJI2] +TEXT NO LONGER REQUIRED + +[USJI3] +TEXT NO LONGER REQUIRED + +[USJ] +UNIQUE STUNT BONUS! + +[SPRAY] +Drive your vehicle into the spray shop to lose your ~h~wanted level~w~, ~h~repair ~w~and ~h~respray ~w~your vehicle. Cost - ~h~$1000. + +[HM1_1] +~g~Ice 20 Purple Nines in 2 minutes 30 seconds. + +[KM1_8A] +Press the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~activate the bomb,~w~ remember to get out of the way. + +[KM1_8D] +Press the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~activate the bomb,~w~ remember to get out of the way. + +[KM1_12] +~g~Get him to the dojo but get rid of the cops first! + +[RATNG1] +Pickpocket + +[RATNG2] +Bully + +[RATNG3] +Thug + +[RATNG4] +Hustler + +[RATNG5] +Goon + +[RATNG6] +Wheelman + +[RATNG7] +Hired muscle + +[RATNG8] +Fixer + +[RATNG9] +Associate + +[RATNG10] +Cleaner + +[RATNG11] +Assassin + +[RATNG12] +Right-hand man + +[RATNG13] +Executioner + +[RATNG14] +Capo + +[RATNG15] +Boss + +[1010] +~r~Your vehicle is upside down + +[1011] +~r~Your vehicle is upside down + +[1012] +~r~Your vehicle is upside down + +[1013] +~r~Your vehicle is upside down + +[1014] +~r~Your vehicle is upside down + +[JM4_10] +OK, Kid. Drive me to the laundry in Chinatown first, I got a bit of business to take care of. + +[JM4_11] +Those washer women aint been payin' their protection money. + +[JM4_12] +And watch the car, Joey just fixed this junk heap. + +[JM4_13] +So no fancy crap, OK? + +[KM4_11] +~g~Take the money back to the casino! + +[FEF_BR2] +Find it again by reading any mission briefs collected to date. + +[TRAIN_1] +Kurowski Station + +[TRAIN_2] +Rothwell Station + +[TRAIN_3] +Baillie Station + +[SUBWAY1] +Portland Station + +[SUBWAY2] +Rockford Station + +[SUBWAY3] +Staunton South Station + +[SUBWAY4] +Shoreside Terminal + +[MEA4_2] +~r~Marty Chonks is dead! + +[SPRAY1] +Drive your vehicle into the spray shop to lose your ~h~wanted level~w~, ~h~repair ~w~and ~h~respray ~w~your vehicle. Cost - ~h~$1000~w~. This time it's free. + +[JM4_A] +Yeah, I know Toni, I've tuned her real sweet. She purrs, you know what I mean? + +[JM4_5] +Drop by later and we'll give them something to launder, their own blood stained clothes! + +[AMMU_A] +Luigi said you'd need a piece... + +[AMMU_B] +Joey told me to tool you up... + +[AMMU_C] +So go around back of the shop. I left you a nine in the yard. + +[AMMU_D] +I got all your home defence needs. + +[AMMU_E] +You want a license too? + +[AMMU_F] +I don't need to see any I.D. you look trustworthy. + +[DETON] +DETONATION: + +[DRIVE_A] +Have an Uzi selected when entering a vehicle then look left or right and press the ~h~~k~~PED_FIREWEAPON~ button~w~ to fire. + +[DRIVE_B] +Have an Uzi selected when entering a vehicle then look left or right and press the ~h~~k~~PED_FIREWEAPON~ button~w~ to fire. + +[RECORD] +~g~NEW RECORD!! + +[NRECORD] +~r~NO NEW RECORD! + +[RCHELP] +Press ~k~~PED_FIREWEAPON~, or drive the RC car into a vehicle's wheels to detonate. + +[RCHELPA] +Press the ~k~~PED_FIREWEAPON~ button, or drive the RC car into a vehicle's wheels to detonate. + +[RC_1] +You have 2 minutes to blow up as many Diablo Gang Cars as possible! + +[RC_2] +You have 2 minutes to blow up as many Mafia Gang Cars as possible! + +[RC_3] +You have 2 minutes to blow up as many Yakuza Gang Cars as possible! + +[RC_4] +You have 2 minutes to blow up as many Yardie Gang Cars as possible! + +[RC_5] +You have 2 minutes to blow up as many Hood Gang Cars as possible! + +[RC_6] +You have 2 minutes to blow up as many Cartel Gang Cars as possible! + +[RAMPAGE] +RAMPAGE!! + +[RAMP_P] +RAMPAGE COMPLETE! + +[RAMP_F] +RAMPAGE FAILED + +[PAGE_00] +. + +[PAGE_01] +Murder ~1~ Diablos in 120 seconds! + +[PAGE_02] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_03] +Kill ~1~ Mafia in 120 seconds! + +[PAGE_04] +Kill ~1~ Triads in 120 seconds! + +[PAGE_05] +Kill ~1~ Triads in 120 seconds! + +[PAGE_06] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_07] +Pop ~1~ Yardie heads in 120 seconds! + +[PAGE_08] +Burn ~1~ Yakuza in 120 seconds! + +[PAGE_09] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_10] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_11] +Annihialate ~1~ Yardies in 120 seconds! + +[PAGE_12] +Torch ~1~ Yakuza in 120 seconds! + +[PAGE_13] +Explode ~1~ Yardies in 120 seconds! + +[PAGE_14] +Fry ~1~ Colombians in 120 seconds! + +[PAGE_15] +Splatter ~1~ Hoods in 120 seconds! + +[PAGE_16] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_17] +Splatter ~1~ Colombians with a car in 120 seconds! + +[PAGE_18] +Driveby and Destroy ~1~ vehicles in 120 seconds! + +[PAGE_19] +Remove ~1~ Colombian heads in 120 seconds! + +[PAGE_20] +Behead ~1~ Hoods in 120 seconds! + +[JM1_A] +Hey, I'm bored when you gonna drill me? + +[JM1_B] +In a moment sweet heart, I got a little business to take care of. + +[JM1_C] +I got a little job for you pal. + +[JM1_D] +The Forelli brothers have owed me money for too long + +[JM1_E] +and they need to be taught some respect. + +[JM1_F] +Lips Forelli is stuffing his fat face in St Marks Bistro, + +[JM1_G] +so steal his car and take it to 8-Ball's bomb shop up in Harwood. + +[JM1_H] +You know 8-Ball right? + +[JM1_I] +Once he's fitted it with a bomb, go park the car where you found it. + +[JM1_J] +Then sit back and watch the whole show. + +[JM1_K] +But hurry up, he won't be eating forever. + +[CAT2_A1] +Come on you dumb bitch! + +[CAT2_A] +The real question is, did you turn up to rescue Maria or to get me back? + +[CAT2_B] +Well I got news for you, + +[CAT2_B2] +shooting you will be a pleasure but dating you was only business. + +[CAT2_C] +You are muy peccinno amigo! + +[CAT2_D] +Throw over the cash. + +[CAT2_E] +You have been a busy boy! + +[CAT2_E2] +But you haven't learned, I'm not to be trusted. + +[CAT2_E3] +Kill the idiot. + +[CAT2_J] +Get this thing airborne!! + +[HM5_1] +Yo, Ice said was comin'. There rules. Bats only. No guns, no cars. + +[HM5_5] +This is a battle for respect, you cool? + +[HELP14] +To collect weapons walk through them. These cannot be collected while in a vehicle. + +[CRUSH] +Park in the marked area and exit your vehicle. The vehicle will then be crushed. + +[DIAB2_B] +A gang of no-goods has threatened to remove my starring member if I don't pay them a cut. + +[DIAB2_C] +They threaten the wrong man, amigo. + +[DIAB2_D] +They have a weakness for the icecream. + +[DIAB2_E] +Pick up the bomb I've hidden in Harwood, + +[DIAB2_F] +hijack the regular icecream van on its rounds. + +[DIAB2_G] +and lure these fools to their doom with the jeengle-jeengle. + +[DIAB2_H] +They hide in a warehouse on Atlantic Quay. + +[DIAB3_A] +Some insolent Triads stole my beautiful car last night, + +[DIAB3_B] +wrecked it and left it burning. + +[DIAB3_C] +Some of my most precious donkey memorabilia was in the trunk - + +[DIAB3_D] +real collectibles that are irreplaceable my friend. + +[DIAB3_E] +I've hidden a throbbing weapon on the edge of Chinatown. + +[DIAB3_F] +Take it and teach these Triad vandals to fear El Burro's well-endowed wrath. + +[DIAB3_G] +Ariba! + +[DIAB3_1] +KILL 25 TRIADS + +[DIAB4_A] +A thieving opportunist has stolen a van of my latest publication hot off the press! + +[DIAB4_B] +But that SPANKED-up idiot has left the rear doors open + +[DIAB4_C] +and now my beautifully produced, + +[DIAB4_D] +tastefully photographed adult literature is being dropped all over Liberty! + +[DIAB4_E] +Take the van and follow that trail of Donkey Does Dallas volumes 1, 2 and 3 + +[DIAB4_F] +collecting it as you go. + +[DIAB4_G] +When you've followed the trail to that thieving SPANK-head, waste him! + +[DIAB4_H] +Then deliver my donkey derby to XXX Mags in the Red Light District. + +[DIAB4_1] +~g~Take the van to the back of XXX Magazines. + +[HM1_E] +I want you to show these punk ass bitches how a real drive-by works. + +[HM1_H] +Take these nines off of here!! + +[HM2_A] +Those Nines are pressing me. + +[HM2_B] +These Bitches got armored cars and now they're running SPANK... + +[HM2_C] +and slinging it to brothers with no fear. + +[HM2_D] +There's a car parked up the way. + +[HM2_E] +There's some stuff in there to put these sissys on blass... + +[HM3_A] +Some effa has wired my wheels to blow. + +[HM3_B] +If I lose those wheels, my rep on the street will be dead. + +[HM3_C] +Pick up my car and take it over to the garage on St. Marks, a'right yo. + +[HM3_D] +Let them diffuse that, let them take care of that bomb. + +[HM3_E] +The clocks ticking and the wiring is messed up. + +[HM3_F] +One pot hole too many and that thing could blow. + +[HM3_G] +Now move it! + +[HM4_A] +Yo, a Federal Reserve flight just smashed down at Francis International. + +[HM4_B] +There's platinum all over the strip. + +[HM4_C] +Get a car and snatch up as much as you can. + +[HM4_F] +You can drop the bling off at one of my garages. + +[HM4_G] +This platinum is mad heavy and it will slow your wheels down some. + +[HM4_H] +So make regular drop off's at the garage. + +[HM5_A] +Them Nines are down to a few scabby herds... + +[HM5_B] +but they still wanna bring it. + +[HM5_C] +They agreed to go toe to toe. + +[HM5_D] +A gang of them against two of us, or rather... + +[HM5_E] +two of yaw + +[HM5_F] +I'd join you but... + +[HM5_G] +I ain't due my parole hearing for another three months now, + +[HM5_H] +y'know what I mean? + +[HM5_I] +Go and meet my baby brother, + +[HM5_J] +He'll show you where they are fighting a'right son. + +[MEA1_B] +The name's Chonks, Marty Chonks. + +[MEA1_C] +I run the Bitchin' Dog Food factory around the corner. + +[MEA1_D] +I got money troubles, but hey, who doesn't right? + +[MEA1_E] +I'm meeting my bank manager later. + +[MEA1_F] +He's a crooked bastard that keeps bumping up the loan repayments so he can cut a slice. + +[MEA1_G] +Take my car, pick him up and bring him back here. + +[MEA1_H] +I've got a little surprise for that blood sucking leech!! + +[MEA2_A] +I hired some thieves to break into my apartment... + +[MEA2_C] +The thieving bastards are threatening to tell the insurance company, + +[MEA2_D] +if I don't give them a cut. + +[MEA2_E] +Can you believe it? + +[MEA2_F] +I've left a car inside the factory gates. + +[MEA2_G] +Use it to go and pick them up from their turf in the Red Light district. + +[MEA2_H] +Then bring 'em back to the factory so I can make 'em see Marty's point of view. + +[MEA3_A] +The business is going to go under unless I get hold of some serious cash soon. + +[MEA3_B] +My wife has an insurance policy and all she's ever been to me is a hole in my pocket. + +[MEA3_C] +I've left a car in the usual place. + +[MEA3_D] +Go and pick up my wife from Classic Nails and bring her back to the factory. + +[MEA4_A] +Damn, I'm in trouble! + +[MEA4_B] +Turns out my wife was seeing some guy I owe money to. + +[MEA4_C] +He's got real angry and he's looking for payback! + +[MEA4_E] +he thinks I'm gonna pay him off... + +[MEA4_F] +but my guess is... + +[MEA4_G] +Liberty's dogs are gonna get yet another flavor this month! + +[WELCOME] +WELCOME TO + +[HM1_2] +~g~Get a vehicle, remember only Uzi drive by kills count! + +[HELP8_B] +Press the~h~ ~k~~PED_SNIPER_ZOOM_IN~ button ~w~to ~h~zoom in ~w~with the rifle and the~h~ ~k~~PED_SNIPER_ZOOM_OUT~ button ~w~to ~h~zoom out ~w~again. + +[LRQC_1] +Asuka and I are gonna have to talk, uh, + +[LRQC_2] +Why don't you go cruise around? + +[LRQC_3] +You'll need a place to lie low. + +[LRQC_4] +There's a warehouse at the edge of Belleville that should suit your needs. + +[LRQC_5] +Come back here to my Condo when you are ready, + +[LRQC_6] +and we can have a little chat. + +[JM6_5] +~g~You need a getaway vehicle, Idiot! + +[JM2_F] +If you need a piece go around back of AmmuNation opposite the subway. + +[LOVE4_7] +~g~There's a construction yard in Staunton Island, maybe they took the package there. + +[LOVE4_8] +~g~You'll need a car to open the garage. + +[TSCORE] +EARNINGS: $~1~ + +[AM1_9] +~r~Salvatore has escaped back into Luigi's Club! + +[AM1_6] +~g~If you hang around Luigi's club, the Mafia will spot you! + +[TM2_3] +~g~It's a trap! Waste them all!! + +[FM4_1] +This is Maria. The car's a trap! Meet me at the slip south of Callahan Bridge. + +[JM1_7] +~g~Close the car door! He'll notice! + +[KM5_1] +~g~DEALER MINCED!!. + +[KM5_6] +~g~You must murder at least 8 Yardie dealers. + +[KM5_7] +~g~Kill them quickly! Once they've pushed their SPANK they're off the streets. + +[RM3_8] +~r~That car is a decoy!! + +[LM3_8] +Hey, I'm Joey. + +[LM3_9] +Luigi said you were reliable so come back later, + +[KM3_5] +~g~Press the horn to get the deal going. + +[LOVE7] +LOVE'S DISAPPEARANCE + +[LOVE2_5] +~g~Kenji is fender meat! Get out of Newport and dump the car! + +[AS2_11] +~g~~1~ OF 9! + +[GARAGE1] +~g~Get out of the vehicle and walk outside. + +[KM3_11] +~g~The Cartel have been attacked and the briefcase has not been recovered. + +[KM3_12] +~g~Kill all of the Colombians, destory the vehicles and recover the briefcase. + +[KM3_13] +~g~Take the briefcase back to the casino. + +[RM5_6] +~g~He's bailed out!! Smash his bodycast with a vehicle or an explosion!! + +[PBOAT_1] +Press the ~h~~k~~PED_FIREWEAPON~ button~w~ to fire the boat cannons. + +[PBOAT_2] +Press the ~h~~k~~PED_FIREWEAPON~ button~w~ to fire the boat cannons. + +[DIAB1_B] +This is El Burro of the Diablos. + +[DIAB1_D] +You're new in Liberty, but already you are gaining a reputation on the streets. + +[DIAB1_E] +There's a street race starting by the old school hall near the Callahan Bridge. + +[DIAB1_F] +Get yourself some wheels and first through all the checkpoints wins the prize. + +[HM2_1] +Use the RC buggies to destroy the armored cars. Press the ~h~~k~~PED_FIREWEAPON~ button ~w~to detonate. + +[HM2_1A] +Use the RC buggies to destroy the armored cars. Press the ~h~~k~~PED_FIREWEAPON~ button ~w~to detonate. + +[HM2_2] +~r~You failed to destroy all the armored cars! + +[HM2_6] +~g~Armored Car destroyed! + +[RM3_A] +I know a real important man in town, a soft touch, + +[RM3_H] +with shall we say, exotic tastes and the money to indulge them. + +[RM3_B] +He's involved in a legal matter and the prosecution has some rather embarrassing photos of him + +[RM3_C] +at a morgue party or something. + +[LOVE6_A] +A lesson in business, my friend. + +[LOVE6_E] +If you have a unique commodity, the world and his wife will try to wrestle it from your grasp... + +[LOVE6_C] +SWAT teams have cordoned off the area around my associate and the package. + +[LOVE6_D] +Get over there, pick up the van and act as a decoy. + +[LOVE6_F] +Keep them busy and he should make good his escape. + +[AM3_C] +He's probably out in the bay as you read this! Steal a police boat, and sink his career! + +[FESZ_UC] +CANCEL + +[FEDS_SM] +L1,R1-CHANGE MENU + +[FEDS_AS] +;=-CHANGE SELECTION + +[FEDSAS2] +<>-CHANGE SELECTION + +[FEDS_SS] +L1,R1-CHANGE SELECTION + +[FEDSSC1] +;-FASTER SCROLLING + +[FEDSSC2] +=-STOP SCROLLING + +[MEA2_3] +~g~Bring the car back to the factory. + +[RM1_3] +~r~McAffrey escaped! + +[RM1_4] +~g~You have used all the grenades! Get some more from ammunation! + +[RM1_5] +~g~Go back and torch the safehouse! + +[RM6_4] +~g~Go over to the lockup and collect Ray's stash. + +[RM6_5] +~g~The CIA have the bridge under surveillance, find another route across. + +[HM2_F] +and wreck all their armored stuff. + +[HM_4] +'BULLION RUN' + +[MEA2_B5] +TEXT NO LONGER NEEDED + +[MEA1_B5] +TEXT NO LONGER NEEDED + +[MEA3_B5] +TEXT NO LONGER NEEDED + +[MEA4_B7] +but if you just step into my office... + +[MEA3_B4] +Marty wants to see me? Well it better be quick because I have to get my hair done. + +[KM3_7] +It's a Yakuza trap man! + +[FES_LOF] +Load Failed. + +[FES_SLO] +SAVE FILE + +[FES_ISC] +IS CORRUPTED + +[FESZ_TI] +SAVE Z1 + +[FESZ_SA] +Save game + +[MC_LDFL] +Load Failed! + +[MC_NWRE] +Now Restarting Game. + +[LOVE6_3] +~g~You have ~1~ seconds to return to the Securicar before you fail the mission. + +[LOVE6_4] +~r~You ditched the Decoy Securicar! + +[HELP1] +Stop in the center of the blue marker. + +[HELP12] +Walk into the center of the blue marker to trigger a mission. + +[HJSTAT] +Distance: ~1~.~1~m Height: ~1~.~1~m Flips: ~1~ Rotation: ~1~_ + +[HJSTATW] +Distance: ~1~.~1~m Height: ~1~.~1~m Flips: ~1~ Rotation: ~1~_ And what a great landing! + +[DIAB1_5] +RACE TIME: + +[LOVE3_4] +~r~You destroyed the plane!! + +[F_FAIL1] +Fire Truck mission ended. + +[F_CANC] +~r~Fire Fighter mission cancelled! + +[F_EXTIN] +FIRES: + +[A_COMP1] +Paramedic missions complete! + +[A_CANC] +~r~Paramedic mission cancelled! + +[A_COMP3] +Paramedic missions complete! You will never get tired when running! + +[ATUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Paramedic missions on or off. + +[ATUTOR3] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Paramedic missions on or off. + +[ALEVEL] +Paramedic Mission Level ~1~ + +[A_FAIL1] +Paramedic mission ended. + +[FEST_HA] +Highest Paramedic Mission level + +[A_SAVES] +PEOPLE SAVED: ~1~ + +[C_KILLS] +CRIMINALS KILLED: ~1~ + +[HM1_B] +I got a problem they tryin' to play me. + +[AM2_A] +Salvatore's death comes as pleasurable news, + +[AM2_A2] +you are an efficient killer. I like that in a man. + +[AM2_B] +This is my brother Kenji. + +[AM2_C] +Asuka has a little job for you, but when you're done, drop by my casino and we can talk. + +[AM2_D] +Just like Kenji, always trying to play with my toys. + +[AM2_E] +My police source indicates that the Mafia are watching our interests around the city + +[AM2_E2] +in a bid to track you down. + +[AM2_F] +We cannot continue our operations until they are dealt with. + +[AM2_G] +Take out these spying fools and end this vendetta once and for all. + +[F_START] +~g~Burning vehicle reported in the ~a~ area. Go and extinguish the fire. + +[AM4_1A] +Get to the Phone in West Belleville Park. + +[AM4_1B] +Get to the Phone on Liberty Campus. + +[AM4_1C] +Get to the Phone in South Belleville Park. + +[AM4_1D] +Meet me in the toilet block in the park. + +[HJSTATF] +Distance: ~1~ft Height: ~1~ft Flips: ~1~ Rotation: ~1~_ + +[HJSTAWF] +Distance: ~1~ft Height: ~1~ft Flips: ~1~ Rotation: ~1~_ And what a great landing! + +[HM1_F] +Watch your back though, there'll be Jacks on the street who'll think you're trying to blast them too! + +[HM1_D] +'Nines' is their tag and purple is their flag and each day they rock their colors... + +[HM1_G] +is another day the 'Jacks' look soft. + +[MEA2_B] +and steal some stuff so I could claim on the insurance as you do. + +[TM3_H] +~w~You did good back there kid, real good. + +[TM3_I] +~w~Come on, let's introduce you to the Don. + +[TM3_J] +~w~Heeyyy! Luigi! + +[TM3_K] +~w~Oh my girls have been missing you so long Salvatore, you been away too long. + +[TM3_L] +~w~You tell them that once this unfortunate business is taken care of, + +[TM3_M] +~w~we'll all go down to the club and celebrate, ok? + +[TM3_N] +~w~Here's my boy. + +[TM3_N2] +~w~How you doin' pop? + +[TM3_O] +~w~You got yourself a good woman yet? + +[TM3_P] +~w~Hey, your mother, god bless her soul, would be turning over in her grave + +[TM3_Q] +~w~to see you without a wife. + +[TM3_R] +~w~I know Pop, I'm working on it. + +[TM3_S] +~w~TONI! How's your Momma? + +[TM3_T] +~w~She's a great woman you know. Strong. Firenze. + +[TM3_U] +~w~She's good...fine. + +[TM3_V] +~w~Terrific, Terrific. Now listen you guys, you go inside while I talk to our new friend here. + +[TM3_W] +~w~I see nothing but good things for you my boy... + +[RM1_A] +That scumbag McAffrey, he took more bribes than anyone. + +[RM1_B] +He thinks he's gonna get an honorable discharge if he turns states evidence. + +[RM1_C] +He just squealed! + +[RM4_B] +We gotta shut him up, permanently. + +[RM4_E] +I want him sleeping with the fishes, not eating them. + +[LOVE3_B] +On its approach to the airport tonight, a light aircraft will pass over the bay. + +[LOVE4_D] +Unfortunately the port authorities seized the plane and were stripping it down + +[LOVE4_H] +until I intervened at great personal expense. + +[LOVE4_E] +Cross the bridge to Shoreside Vale and go to Francis International Airport. + +[GTAB_A] +Hey, let's get this out of here. God knows what it is + +[GTAB_B] +but he seems to want it badly enough so it must be worth something. + +[GTAB_C] +Who the Heck! + +[GTAB_D] +YOU! + +[GTAB_E] +Hey take it easy amigo! De nada! De nada! + +[GTAB_F] +I left you pouring your heart out into the gutter! + +[GTAB_G] +Don't shoot amigo. No problem. We all friends. Here, take this. + +[GTAB_H] +Don't be such a pussy! + +[GTAB_I] +We got no choice baby! + +[GTAB_J] +We always got a choice you dumb bastard! + +[GTAB_K] +I'm sorry about that crazy bitch man, they all the same...por favor?? + +[GTAB_L] +So the whore got away. + +[GTAB_M] +But you've done me a favor, + +[GTAB_N] +you're not the only one that has a score to settle with the Cartel, + +[GTAB_O] +this worm killed my brother! + +[GTAB_P] +I never killed no Yakuza! + +[GTAB_Q] +LIAR! We all saw the Cartel assassin. + +[GTAB_R] +We are going to hunt down and kill all you Colombian dogs! + +[GTAB_S] +I'll be operating on our friend here to extract information and a little pleasure. + +[GTAB_T] +You, drop by later, I'm sure I'll require your services. + +[GTAB_U] +Please amigo, don't leave me with her, she psycho chica! Amigo? Hey AMEEEGO!!!...Aiiieeeeaaargghh! + +[LOVE5_A] +You are proving to be a safe investment, a rare thing in these days of false hood. + +[KM3_1] +~g~The Cartel are expecting a Yardie Posse go and steal a Yardie car! Head north you'll find one in Newport. + +[LOVE1_1] +~g~Go 'jack a Colombian gang car, so you can infiltrate the hideout, head north you'll find one in Fort Staunton. + +[FM1_Q1] +~w~You looking for some fun? A little...hmm? Some SPANK? + +[FM1_R] +~w~Hi Chico. Nah, just the usual. + +[FM1_T] +~w~Thanks Chico. See you around. + +[FM1_W] +~w~Alright Fido, you wait here and look after the car while I go and shake my butt alright. + +[FM1_X] +~w~OK Fido, let's get out of here. Wooooh! + +[FM1_Q] +~w~Hey Maria! It's my favorite lady! + +[FM1_S1] +~w~Hey, maybe you should check out the warehouse party at the east end of Atlantic Quays. + +[FM1_U] +~w~Gracias and enjoy. That's good stuff. + +[FM1_V] +~w~C'mon Fido, let's go and check out this party! + +[FM1_SS] +~r~SCANNER: ~g~Four-five to all units: Assist narcotics raid Atlantic Quays... + +[LOVE6_B] +even if they have little understanding as to its true value. + +[TM3_A1] +~r~Joey's Fried! + +[TM3_A2] +~r~Joey and Luigi have been cremated! + +[TM3_A3] +~r~Joey, Luigi and Toni are Toast! + +[FM4_2] +Listen, Salvatore thinks that we're going behind his back, + +[FM4_3] +so he was offering you to the Cartel in order to make a deal. + +[FM4_4] +I couldn't let him do that, I mean the worst thing is, + +[FM4_4B] +it's all my fault... because I told him, we were an item. + +[FM4_5] +Don't ask me why. I don't know. + +[FM4_6] +Look you're a marked man on Mafia turf and I've got to get out of here too. + +[FM4_6B] +I've seen too much killing. Too much blood! + +[FM4_7] +This is a friend of mine ok, she's an old friend.. it's Asuka, she's someone we can trust. + +[FM4_8] +C'mon, Enough of the speeches. + +[FM4_9] +We better get out of here before we get more hysterical Italians wanting less friendly reunions. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCER + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTION + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DESIGN + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +WRITTEN BY + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +CHARACTERS + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMATION & DIRECTION + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +AUTO DESIGN + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODERS + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +SCORE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +SOUND DESIGN & MASTERING + +[CRED042] +ALLAN WALKER + +[CRED043] +AUDIO PROGRAMMING + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD TESTERS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTERS + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +TECHNICAL PRODUCER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VP OF MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +TECHNICAL COORDINATOR + +[CRED088] +BRANDON ROSE + +[CRED089] +QA MANAGER + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +LIBERTY TREE EDITORIAL + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT-SCENES + +[CRED108] +SCRIPT BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER + +[CRED110] +AUDIO PRODUCED BY RENAUD SEBBANE + +[CRED111] +CAST + +[CRED112] +FRANK VINCENT AS SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO AS LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN AS TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT AS JOEY LEONE + +[CRED116] +DEBBI MAZAR AS MARIA + +[CRED117] +KYLE MACLACHLAN AS DONALD LOVE + +[CRED118] +ROBERT LOGGIA AS RAY MACHOWSKI + +[CRED119] +GURU AS 8-BALL + +[CRED120] +SONDRA JAMES AS MOMMA + +[CRED121] +LIANA PAI AS ASUKA + +[CRED122] +LES MAU AS KENJI + +[CRED123] +CYNTHIA FARRELL AS CATALINA + +[CRED124] +AL ESPINOSA AS MIGUEL + +[CRED125] +CHRIS PHILLIPS AS EL BURRO + +[CRED126] +HUNTER PLATIN AS CHICO + +[CRED127] +WALTER MUDU AS D-ICE + +[CRED128] +CURTIS MCCLARIN AS CURTLY + +[CRED129] +BILL FIORE AS DARKEL + +[CRED130] +CHRIS PHILLIPS AS MARTY CHONKS + +[CRED131] +HUNTER PLATIN AS CURLY BOB + +[CRED132] +WALTER MUDU AS KING COURTNEY + +[CRED133] +HUNTER PLATIN AS ONE-ARMED PHIL + +[CRED134] +KIM GURNEY AS MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRECTED BY + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUCED BY + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +RECORDED AT MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORS + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRED142] +WRITTEN BY DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRECTED BY CRAIG CONNER, DAN HOUSER AND LAZLOW + +[CRED144] +PRODUCED BY RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +RADIO STATIONS AND MUSIC + +[CRED218] +PRODUCERS FOR DMA DESIGN + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDITED BY + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING WRITTEN BY + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED254] +STUDIO MANAGER + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CINCAM] +Cinematic Camera + +[KM1_13] +Drive the vehicle into the garage! + +[KM3_14] +~r~You have been spotted the deal is off! + +[EBAL_H] +Wait here man while I go in and talk to Luigi. + +[EBAL_M] +Remember no one messes with my girls! + +[LM2_F] +Then take his car, respray it. + +[LM2_D] +here, here take it. + +[LM1_9] +Hi I'm Misty. + +[LM4_A] +Some Diablo scumbag has been pimping his skuzzy bitches in my backyard. + +[FM2_B] +We got us a rat! + +[FM2_C] +He ain't pimpin' or pushin' so he must be talking. + +[FM3_CC] +~w~Come back brother when you have the money. + +[FEDS_AM] +<>-CHANGE MENU + +[LOVE5_5] +~r~You failed to protect the truck! + +[RM6_6] +~r~Ray is dead! + +[RM6_7] +~r~Ray has missed his flight. + +[RM6_8] +~g~You have left Ray behind, go back and get him. + +[FM1_10] +~g~You have left Maria behind, go back and pick her up. + +[LOVE4_9] +~r~The plane has been destroyed! + +[LOV4_10] +~r~The only lead to where the package has gone has been destroyed! + +[KM2_D] +Needless to say, we must give him the cars as a gift, to repay the debt that I owe him. + +[KM4_B] +The business's fortunate enough to have our protection settle their accounts today. + +[KM2_E] +You must obtain the cars on this list and deliver them to a garage behind the car park in Newport. + +[FM3_8I] +~w~Get a good vantage point then I'll head in when you fire the first shot. + +[LOVE1_B] +Experience has taught me that a man like you can be very loyal for the right price, + +[LOVE1_H] +but groups of men get greedy. + +[LOVE1_C] +A valued resource, an old oriental gentleman I know, + +[LOVE1_I] +has been kept hostage by some South Americans in Aspatria. + +[MEA4_D] +I've agreed to see him... + +[MEA4_B4] +Marty sent you huh? OK, I'm gonna show that creep the meaning of the word business. + +[MEA4_B5] +Carl, hi! i eerr, I need more time to get your money. + +[MEA1_B4] +Ah, Mr Chonks sent you did he. Let's go and pay the fellow a visit. + +[HM5_6] +Let's go crack some skulls... + +[LOVE1_5] +~g~Stop hanging around, get a Colombian Gang car and rescue Love's associate. + +[AS1_D] +~w~Act as the bait, and get the death squads to follow you to Pike Creek + +[AS1_E] +~w~where some of my men will be waiting for them. + +[AS2_C] +~w~The Cartel have a front company, The Kappa Coffee House. + +[AS2_E] +~w~We have no choice but to put these drug stands out of operation. + +[AS2_F] +~w~Smash them to splinters!! + +[AS2_A1] +~w~Miguel certainly has some of that famous Latin stamina. + +[AS2_A2] +~w~I'm quite exhausted. + +[SIREN_3] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[SIREN_4] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[AS3_C] +~w~Eeeeeeyoooo! What IS that gooey yellow stuff? + +[AS3_C1] +~w~Oh hi Babe. + +[AS3_F] +~w~She's got the makings of a natural this girl. + +[AS3_F1] +~w~She's managed to extract this little gem from our guest. + +[AS3_G] +~w~There is a plane coming into Francis International in 2 hours time. + +[AS3_G1] +~w~It is full of Catalina's poison. + +[AS3_H] +~w~You can avoid airport security by getting a boat out to the runway-light buoys + +[AS3_H1] +and shooting the plane down on its approach. + +[AS3_I] +~w~Collect the cargo from the debris and stash it! + +[AS3_J] +~w~Oh you be careful now, OK baby? + +[AS3_K] +~w~Now try the chilli oil..... + +[RM2_F1] +Those Colombians'll be here any minute! + +[RM2_K] +Goddam they're here!! LOCK'N'LOAD!! + +[LOVE2_7] +~g~Now dump the car! + +[LOVE2_8] +~g~Now get out of Newport! + +[AM1_F] +Salvatore Leone will be leaving Luigi's in about three hours time. (~1~:~1~) + +[LOVE5_C] +I want you to follow him, and make sure both he and my package get to Pike Creek unharmed. + +[FESZ_SR] +Save Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again. + +[FESZ_FO] +Would you like to format the memory card (PS2) in MEMORY CARD slot 1? + +[FELZ_FO] +Memory card (PS2) in MEMORY CARD slot 1 is unformatted. + +[FES_NOC] +No memory card (PS2) in MEMORY CARD slot 1. + +[FES_LOE] +Load Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again. + +[FES_DEE] +Deleting Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again. + +[SLONFM] +Error formatting memory card (PS2) in MEMORY CARD slot 1. + +[SLONDR] +Insufficient space to save. Please insert a memory card (PS2) with at least 500KB of free space available into MEMORY CARD slot 1. + +[SLNSP] +Insufficient space to save. Please insert a memory card (PS2) with at least 200KB of free space available into MEMORY CARD slot 1. + +[FEFD_WR] +Formatting memory card (PS2) in MEMORY CARD slot 1. Please do not remove the memory card (PS2), reset or switch off the console. + +[FES_ISF] +NOT PRESENT + +[FES_SAG] +PRESENT + +[SLONNO] +No memory card (PS2) in MEMORY CARD slot 1. + +[SLONNF] +Memory card (PS2) in MEMORY CARD slot 1 is unformatted. + +[FESZ_FM] +Memory card (PS2) in MEMORY CARD slot 1 is unformatted. Would you like to format memory card (PS2) in MEMORY CARD slot 1? + +[FESZ_FF] +Format Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again. + +[MCDNSP] +There is insufficient space on the memory card (PS2) in MEMORY CARD slot 1. At least 500KB is needed to save this application data. Do you wish to start? (YES or NO) + +[MCGNSP] +There is insufficient space on the memory card (PS2) in MEMORY CARD slot 1. At least 200KB is needed to save this application data. Do you wish to start? (YES or NO) + +[FESZ_WR] +Saving data. Please do not remove the memory card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FESZ_OW] +Overwriting data. Please do not remove the memory card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FELD_WR] +Loading data. Please do not remove the memory card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FEDL_WR] +Deleting data. Please do not remove the memory card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[LM2_C] +Luigi said to, to give you this so... + +[LM3_G] +Joey ain't the kind you keep waiting, remember, this is your foot in the door... + +[LM5_E] +Get as many of them as you can before the cops drink away their green. + +[JM5_C] +Alright, there's a car stuffed with a stiff at the cafe near Callahan Point. + +[RM2_B] +We saw action in Nicaragua, back when the country knew what it was doing. + +[RM2_C] +Some Cartel scum roughed him up yesterday, said they'd be back for some of his stock today. + +[RM2_D1] +I'd go myself but the old sciatica's playing up again -cough cough- so, eerr-hhrrmmm, good luck. + +[CATINF1] +~g~Get Catalina! + +[CATINF2] +~g~Follow the chopper to find Catalina. + +[BOATIN1] +Jump into a boat and press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~to get in. + +[BOATIN2] +You can press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~if you are near a boat to get in it. + +[BOATIN3] +Jump into a boat and press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~to get in. + +[BOATIN4] +You can press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~if you are near a boat to get in it. + +[JM6] +'THE GETAWAY' + +[FM1] +'CHAPERONE' + +[JM1] +'MIKE LIPS LAST LUNCH' + +[FM21] +'BOMB DA BASE: ACT I' + +[FM3] +'BOMB DA BASE: ACT II' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'UNDER SURVEILLANCE' + +[KM2] +'GRAND THEFT AUTO' + +[AS3] +'S.A.M.' + +[RM2] +'ARMS SHORTAGE' + +[LOVE6] +'DECOY' + +[LOVE1] +'LIBERATOR' + +[RC1] +'DIABLO DESTRUCTION' + +[RC2] +'MAFIA MASSACRE' + +[RC3] +'CASINO CALAMITY' + +[RC4] +'RUMPO RAMPAGE' + +[RM2_E1] +I can't believe those yellow-bellied bastards left me without proper cover again! + +[GREN_1] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[GREN_2] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[GREN_3] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[LOVE4_G] +My property will be waiting for you at the customs hanger in the aircraft's fuselage. + +[KABOOM] +KABOOOM! + +[SPLAT] +SPLAT! + +[PANCAK] +PANCAKED! + +[SOAKED] +SOAKED! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Chatterbox FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +When the subway opens you will be able to catch a train to Staunton Island. + +[TUBE2] +When Shoreside Vale opens you will be able to Exit Shoreside Terminal to Francis International Airport. + +[TUBE_2] +To board a subway train, press the ~h~'enter vehicle' button~w~. + +[LEGAL] +~g~Eliminate the criminal threat! + +[GA_2] +New engine and paint job. The cops won't recognize you! + +[LM1_8A] +To earn some extra cash, why not 'borrow' a taxi... + +[TAXIH1] +Stop near a highlighted pedestrian to pick them up then drive them to their destination before the time runs out. + +[LM5_7] +~g~Less than four girls working the ~p~Fuzz Ball~g~ and Luigi won't be happy! + +[KM2_3] +~g~Remember the ~r~cars~g~ have to be in mint condition to be accepted by the ~p~garage~g~. + +[KM5_2] +~g~A Yardie is off the streets. + +[BETRA_A] +Sorry, babe. + +[BETRA_B] +I'm an ambitious girl and you, + +[BETRA_C] +you're just small time. + +[JAILB_Q] +Come on! + +[JAILB_R] +Senor dickhead! + +[JAILB_S] +It's no problem to kill you. + +[JAILB_T] +You gonna be sorry. + +[JAILB_U] +A'right, a'right. Get lost. + +[HELP15] +When on foot press the ~h~~k~~PED_LOOKBEHIND~ button~w~ to ~h~look behind~w~. + +[FEC_LB3] +Look behind + +[FEC_R3] +(R3 button) + +[FES_AFO] +This memory card (PS2) is already formatted. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- CHANGE SELECTION + +[FEDSAS4] +;=<> - CHANGE SELECTION + +[SPRAY_4] +Use the ~h~~k~~PED_FIREWEAPON~ button ~w~to fire the water cannon. + +[SPRAY_1] +Use the ~h~~k~~PED_FIREWEAPON~ button ~w~to fire the water cannon. + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[AM1_10] +~g~Salvatore will be leaving Luigi's at about 0~1~:~1~ + +[FEDS_SE] +/ button - SELECT + +[FEDS_SB] +/ button - SELECT " button - BACK + +[TM4_A] +~w~Oh it's you. TONI ain't here. + +[TM4_A2] +~w~But he left one of his sugary love letters for you. + +[DIAB2_A] +I started my exotic entertainment business with nothing but the sizeable contents of my leather pants! + +[LM5_9] +GIRLS: + +[PERPIC] +Hidden Packages found + +[CO_ONE] +Hidden Package ~1~ of ~1~ + +[LOVE3_3] +~g~The plane has dropped ~1~ of 6 packages. + +[FARE11] +~g~Destination ~w~'Construction site' ~g~in Fort staunton. + +[GA_21] +You cannot store any more cars in this garage. + +[CHEAT1] +Cheat activated + +[CHEAT2] +Weapon cheat + +[CHEAT3] +Health cheat + +[CHEAT4] +Armor cheat + +[CHEAT5] +Wanted level cheat + +[CHEAT6] +Money cheat + +[CHEAT7] +Weather cheat + +[AS1_H] +~r~You failed to lead the Deathsquad into the Yakuza trap!! + +[FEDS_BA] +" button - BACK + +[FED_WIS] +Wide Screen: + +[RAMP_A] +ALL RAMPAGES COMPLETED! + +[USJ_ALL] +ALL UNIQUE STUNTS COMPLETED! + +[FARE23] +~g~Destination ~w~'import export garage' ~g~in Cochrane Dam district + +[L_TRN_1] +You can ride the L-train around Portland. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[L_TRN_2] +You can ride the L-train around Portland. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[S_TRN_1] +You can take the subway trains across Liberty. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[S_TRN_2] +You can take the subway trains across Liberty. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[AS1_C] +~w~She has three death squads dotted around Liberty, whose sole job is to hunt you down. + +[AS1_G] +~r~All the Yakuza are dead!! + +[JAN] +Jan + +[FEB] +Feb + +[MAR] +Mar + +[APR] +Apr + +[MAY] +May + +[JUN] +Jun + +[JUL] +Jul + +[AUG] +Aug + +[SEP] +Sept + +[OCT] +Oct + +[NOV] +Nov + +[DEC] +Dec + +[DEFDT] +--:---:---- --:--:-- + +[BUGGY] +BUGGIES LEFT: + +[BONUS] +~g~BONUS $~1~ + +[HORN1] +Press the ~h~L3 button ~w~to activate the ~h~horn. + +[HORN2] +Press the ~h~L1 button ~w~to activate the ~h~horn + +[HORN3] +Press the ~h~R1 button ~w~to activate the ~h~horn + +[LM3_1A] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[LM3_1B] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[LM3_1C] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[RADIO_A] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_B] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_C] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_D] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[FEC_EXV] +Enter and exit vehicle + +[TAXI_M] +'TAXI DRIVER' + +[COP_M] +'VIGILANTE' + +[FIRE_M] +'FIREFIGHTER' + +[AMBUL_M] +'PARAMEDIC' + +[HJ_IS] +INSANE STUNT BONUS: $~1~ + +[HJ_PIS] +PERFECT INSANE STUNT BONUS: $~1~ + +[HJ_DIS] +DOUBLE INSANE STUNT BONUS: $~1~ + +[HJ_PDIS] +PERFECT DOUBLE INSANE STUNT BONUS: $~1~ + +[HJ_TIS] +TRIPLE INSANE STUNT BONUS: $~1~ + +[HJ_PTIS] +PERFECT TRIPLE INSANE STUNT BONUS: $~1~ + +[HJ_QIS] +QUADRUPLE INSANE STUNT BONUS: $~1~ + +[HJ_PQIS] +PERFECT QUADRUPLE INSANE STUNT BONUS: $~1~ + +[AM1_K] +Salvatore Leone will be leaving Luigi's in about three hours time. (0~1~:~1~) + +[IMPEXPP] +Import/Export garage, Portland Harbor. We have orders for various vehicles. Check our notice board for our requirements. + +[VANHSTP] +Any more Securicars you want cracked? Bring them to our garage in the Portland Harbor. + +[EMVHPUP] +Great rates paid for new and used Emergency Vehicles. Bring them to the crane in the north east of Portland Harbor. + +[STANDS] +STALLS WRECKED: + +[STASH] +~g~Stash the SPANK back at the ~p~construction site! + +[MCSTNS] +There is no memory card (PS2) in MEMORY CARD slot 1. Do you wish to start? (YES or NO) + +[LOVE3_5] +~g~The plane is now in range. + +[LOVE3_6] +~r~The Police got to the packages first! + +[SIREN_1] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[SIREN_2] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[FM3_8C] +~w~I'll need $100,000 to cover expenses, + +[MCLOAD] +Loading Data. Please do not remove the memory card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FES_GME] +Error Reading memory card (PS2) in MEMORY CARD slot 1 please check and try again. + +[FESZ_QF] +Are you sure you wish to format the memory card (PS2) in MEMORY CARD slot 1? + +[FESZ_LS] +Load Successful. + +[RM3_5] +~g~You have ~1~ of 6 evidence packages. + +[LOVE3_2] +~g~You have all the packages! Take them back to Donald Love. + +[LOVE4_4] +~g~Take the package back to Donald Love! + +[FEB_SAV] +Load + +[FEP_SAV] +LOAD GAME + +[AS2_12A] +~g~After you trash the first stall, you will have 8 minutes before the Cartel warn their pushers! + +[AS3_1A] +~g~Now get to the ~b~marker buoy! + +[NOCONT] +Please reconnect analog controller (DUALSHOCK#) or analog controller (DUALSHOCK#2) to controller port 1 to continue + +[BET_JB] +BETRAYED BY HIS LOVER CATALINA AND LEFT FOR DEAD. CONVICTED AND SENTENCED, HE BEGINS HIS JOURNEY TO LIBERTY CITY PENITENTIARY. BUT ONLY ONE THOUGHT BURNS IN HIS CRIMINAL MIND......REVENGE! + +[END_A] +Residents in Cedar Grove have been coming to terms + +[END_B] +with the emotional aftermath of a full blown war + +[END_C] +that hit the area yesterday. + +[END_D] +Local resident, Clive Denver described to police + +[END_E] +a single gunman that he saw fleeing the scene, with a dark haired woman. + +[END_F] +Oh, you know, we're gonna have such fun, 'cos you know, you know, + +[END_G] +I love you, I, I, I, I really do, 'cos you're such a big strong man + +[END_H] +and that's just what I need. + +[END_I] +Anyway, what was I saying? + +[END_J] +Oh, you know, I forget. But you know what it's like, don't you? + +[END_K] +The sound of explosions shook nearby homes as people ran for cover. + +[END_L] +Several citizens were injured in the panic as ground fire was exchanged + +[END_M] +between ground forces and a helicopter circling the dam. + +[END_N] +Yeah, we got a good view from down here in the gardens. + +[END_O] +When the 'copter finally got taken out, + +[END_P] +better than the fireworks on the 4th of July. + +[END_Q] +With the death toll already over twenty, + +[END_R] +police are still finding bodies. + +[END_S] +There have been no official denials concerning rumours + +[END_T] +that the dead were members of the Colombian Cartel, + +[END_U] +and still no leads as to the cause of the massacre. + +[END_V] +I broke a nail and my hair is ruined, I mean can you believe it? + +[END_W] +This one cost me fifty dollars... + +[PAPER1] +* + +[PAPER2] +* + +[PAPER3] +* + +[JAILB_V] +* + +[JAILB_A] +* + +[JAILB_B] +* + +[JAILB_C] +* + +[JAILB_E] +* + +[JAILB_F] +* + +[JAILB_G] +* + +[JAILB_I] +* + +[JAILB_W] +* + +[JAILB_K] +* + +[JAILB_L] +* + +[JAILB_X] +* + +[JAILB_M] +* + +[JAILB_N] +* + +[JAILB_O] +* + +[JAILB_P] +* + +[JAILB_D] +* + +[JAILB_H] +* + +[JAILB_J] +* + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/french.txt b/utils/gxt/french.txt new file mode 100644 index 0000000..bdb12c7 --- /dev/null +++ b/utils/gxt/french.txt @@ -0,0 +1,8378 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. +} + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~Hé! Retourne dans ta caisse! + +[IN_VEH2] +~g~T'as besoin d'une caisse pour cette mission! + +[IN_BOAT] +~g~T'as besoin d'un bateau pour cette mission! + +[HEY] +~g~Te la joue pas perso, pense à tes potes! + +[HEY2] +~g~Restez groupés! + +[HEY3] +~g~T'as perdu ton meilleur homme, retournes-y et ramène-le! + +[HEY4] +~g~Perds Misty, et Luigi te fera sauter la tête ! Alors, retourne la chercher! + +[HEY5] +~g~L'une des filles manque à l'appel! Retourne la chercher! + +[HEY6] +~g~Ton honneur est lié à celui du Yakuza Kanbu. Tu dois le protéger! + +[HEY7] +~g~Un flingue de plus ferait pas de mal! Retourne en arrière et embarque ton contact! + +[HEY8] +~g~Dans protection, y'a protection, compris ? Alors protège le vieux bridé! + +[HEY9] +~g~Tu veux savoir ce qui se passe dans la rue ? Ben va voir ton contact! + +[HELP2_A] +Appuie sur la ~h~touche /~w~ quand tu cours pour piquer un ~h~sprint. + +[HELP3] +Tu ne peux sprinter que pendant une courte durée, avant d'être claqué! + +[HELP4_A] +Appuie sur la ~h~touche ~k~~VEHICLE_ACCELERATE~~w~ pour ~h~accélérer. + +[HELP4_D] +Pousse le ~h~stick analogique de droit~w~ vers le haut pour accélérer. + +[HELP5_A] +Appuie sur la~h~ touche ~k~~VEHICLE_BRAKE~~w~ pour ~h~freiner~w~ ou pour ~h~passer la marche arrière~w~ si ta caisse est à l'arrêt. + +[HELP5_D] +Pousse le ~h~stick analogique de droit~w~ vers le bas pour ~h~freiner~w~ ou pour ~h~passer la marche arrière~w~ si ta caisse est à l'arrêt. + +[HELP6_A] +Appuie sur la ~h~touche ~k~~VEHICLE_HANDBRAKE~~w~ pour utiliser le ~h~frein à main du véhicule. + +[HELP6_C] +Appuie sur la ~h~touche ~k~~VEHICLE_HANDBRAKE~~w~ pour utiliser le ~h~frein à main du véhicule. + +[HELP6_D] +Appuie sur la ~h~touche ~k~~VEHICLE_HANDBRAKE~~w~ pour utiliser le ~h~frein à main du caisse. + +[HELP7_A] +Maintiens la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée pour ~h~viser~w~ avec le fusil à lunette. + +[HELP7_D] +Maintiens la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée pour ~h~viser~w~ avec le fusil à lunette. + +[HELP8_A] +Appuie sur la ~h~touche ~k~~PED_SNIPER_ZOOM_IN~~w~ pour faire un ~h~zoom avant~w~ avec le fusil et sur la ~h~touche ~k~~PED_SNIPER_ZOOM_OUT~~w~ pour faire un ~h~zoom arrière~w~. + +[HELP9_A] +Appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~tirer~w~ au fusil à lunette. + +[HELP10] +Ce badge t'indique que la police te recherche. + +[HELP11] +Plus il y a de badges, plus il y a de flics à tes trousses. + +[HELP13] +Tu as parfois intérêt à utiliser des chemins qui n'apparaissent pas sur le radar. + +[TIMER] +C'est une mission en temps limité, alors il faut la finir avant que le compte à rebours arrive à zéro! + +[MISTY1] +~r~Misty bouffe les pissenlits par la racine! + +[OUT_VEH] +~g~Sors du véhicule! + +[GARAGE] +Conduis ta caisse dans le garage et repars à pied. + +[WANTED1] +~g~Largue les flics pour en avoir moins à tes basques! + +[NODOORS] +~g~Hé, c'est pas des sardines! Trouve une caisse avec assez de sièges! + +[TRASH] +~g~T'as vachement bousillé ta bagnole ! Fais-la réparer ! + +[WRECKED] +~r~Le caisse est fortue! + +[HORN] +~g~Klaxonne! + +[HORN4] +Appuie sur la ~h~touche L3~w~ pour ~h~klaxonner. + +[NOMONEY] +~g~T'as besoin de thune! + +[OUTTIME] +~r~T'es lent, mec, t'es trop lent! + +[SPOTTED] +~r~Ils en ont après ta peau! + +[REWARD] +~1~$ de récompense + +[GAMEOVR] +FIN DE PARTIE + +[Z] +Valeur axe Z : ~1~ + +[M_FAIL] +ECHEC DE LA MISSION! + +[M_PASS] +MISSION REUSSIE! ~1~$ + +[O_PASS] +PETIT BOULOT REUSSI! + +[O_FAIL] +PETIT BOULOT RATE! + +[DEAD] +T'ES MORT! + +[BUSTED] +TU T'ES FAIT COFFRER! + +[S_PROMP] +Si tu n'es pas en mission, tu peux ~h~sauvegarder le jeu ici~w~, ça fera avancer la montre de six heures. + +[NUMBER] +~1~ + +[SCORE] +~1~$ + +[LOADCAR] +CHARGEMENT DU VEHICULE... + +[CARSOFF] +Trafic désactivé + +[CARS_ON] +Trafic activé + +[TEXTXYZ] +Ecriture des coordonnées sur le fichier... + +[CHEATON] +Mode Triche activé + +[CHEATOF] +Fonction tricher OFF + +[UZI_IN] +L'Uzi est disponible maintenant à Ammu-Nation! + +[IMPORT1] +Va dehors et attends ton véhicule. + +[PAGEB1] +Pistolet livré à la planque. + +[PAGEB2] +Uzi livré à la planque. + +[PAGEB3] +Armure livrée à la planque. + +[PAGEB4] +Fusil à pompe livré à la planque. + +[PAGEB5] +Grenades livrées à la planque. + +[PAGEB6] +Cokctails molotov livrés à la planque. + +[PAGEB7] +AK47 livré à la planque. + +[PAGEB8] +Fusil à lunette livré à la planque. + +[PAGEB9] +M16 livré à la planque. + +[PAGEB10] +Lance-roquettes livré à la planque. + +[PAGEB11] +Lance-flammes livré à la planque. + +[WANT_A] +Tu ne seras arrêté que si tu possèdes un ~h~indice de recherche. + +[WANT_B] +Ton ~h~indice de recherche~w~ est symbolisé par les étoiles dans le coin supérieur droit de l'écran. + +[WANT_C] +Ton ~h~indice de recherche~w~ est de 1... + +[WANT_D] +2... + +[WANT_E] +3... + +[WANT_F] +Plus ton ~h~indice de recherche~w~ augmente, plus les forces de l'ordre t'en veulent. + +[WANT_G] +Quand tu te fais ~h~choper~w~, tu es amené au poste de police le plus proche. + +[WANT_H] +Les flics se laisseront corrompre contre tes armes et une partie de ton oseille. + +[WANT_I] +Toute mission en cours sera automatiquement un échec. + +[WANT_J] +Plus tu joueras, plus tu trouveras de moyens de diminuer ton indice de recherche. + +[WANT_K] +En voiture, les ~h~ateliers de peinture~w~ te permettront d'~h~enrayer ton indice de recherche. + +[HEAL_B] +Quand tu es ~h~H.S.~w~, t'es amené à l'hosto le plus proche. + +[HEAL_C] +On te confisque alors toute ton artillerie et les toubibs te pompent ton flouze pour recoller les bouts. + +[HEAL_E] +En jouant, tu trouveras d'autres moyens de te soigner ou de te protéger. + +[DAM] +DEGATS : + +[KILLS] +VICTIMES : + +[FARES] +FRAIS : + +[BULL] +FRIC : + +[EVID] +PREUVES : + +[HEALTH] +ETAT DU VEHICULE : + +[COLLECT] +RECUPERE : + +[BOMB] +Conduis ton véhicule chez l'artificier et piège-le avec une ~h~bombe~w~. Coût - ~h~1.000 dollars. + +[SAVE1] +Franchis la porte pour ~h~sauvegarder la partie~w~. Tu ne peux pas sauvegarder en cours de mission. + +[SAVE2] +Tout véhicule laissé dans ce garage sera enregistré lors de la sauvegarde. + +[AMMU] +Va chez Ma-Gnum pour acheter une arme. + +[BRIDGE1] +Quand le pont Callahan sera réparé, tu pourras te rendre à l'île Staunton . + +[TUNNEL] +Quand le tunnel Porter aura réouvert, tu pourras te rendre à l'île Staunton . + +[LUIGI] +MISSIONS DE LUIGI + +[TONI] +MISSIONS DE TONI + +[JOEY] +MISSIONS DE JOEY + +[FRANK] +MISSIONS DE SALVATORE + +[DIABLO] +MISSIONS DE DIABLO + +[ASUKA] +MISSIONS D'ASUKA + +[B_SITE] +MISSIONS DE BANLIEUE D'ASUKA + +[KENJI] +MISSIONS DE KENJI + +[RAY] +MISSIONS DE RAY + +[LOVE] +MISSIONS DE LOVE + +[YARDIE] +MISSIONS DE YARDIE + +[HOOD] +MISSIONS DE HOOD + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Point Callahan + +[PORT_S] +Atlantic Quays + +[PORT_E] +Port de Portland + +[PORT_I] +Trenton + +[S_VIEW] +Vue de Portland + +[CHINA] +Chinatown + +[EASTBAY] +Plage de Portland + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Le Quartier Rouge + +[TOWERS] +Hauteurs de Hepburn + +[HARWOOD] +Harwood + +[ROADBR1] +Pont Callahan + +[ROADBR2] +Pont Callahan + +[TUNNELP] +Tunnel Porter + +[BOMB1] +Garage de 8-Ball + +[COM_ZON] +Ile de Staunton + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Campus Liberty + +[CONSTRU] +Fort Staunton + +[PARK] +Parc Belleville + +[COM_EAS] +Newport + +[SHOPING] +Point Bedford + +[YAKUSA] +Torrington + +[SUB_ZON] +Vallée Shoreside + +[AIRPORT] +Aéroport intl. Francis + +[PROJECT] +Jardins Wichita + +[SUB_IND] +Crique de Pike + +[SWANKS] +Bosquet Cedar + +[BIG_DAM] +Ecluse Cochrane + +[SUB_ZO2] +Vallée Shoreside + +[SUB_ZO3] +Vallée Shoreside + +[CAR_1] +Ambulance + +[CAR_2] +Camion de pompier + +[CAR_3] +Voiture de police + +[CAR_4] +Enforcer + +[CAR_5] +Barraquements + +[CAR_6] +Rhino + +[CAR_7] +Voiture du FBI + +[CAR_8] +Sécuricar + +[CAR_9] +Moonbeam + +[CAR_10] +Autobus + +[CAR_11] +Camion + +[CAR_12] +Linerunner + +[CAR_13] +Camion-poubelle + +[CAR_14] +Patriot + +[CAR_15] +M. Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinelle + +[CAR_25] +Guépard + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Tout-terrain + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Etalon + +[CAR_37] +Taxi + +[CAR_38] +Tacot + +[CAR_39] +Buggy + +[LUIGIS] +Chez Luigi + +[GOAWAY] +~g~T'es déjà en mission, imbécile! + +[LUIGGO] +~g~Luigi fait passer un entretien à de nouvelles filles. Reviens plus tard! + +[JOEYGO] +~g~Joey est en ville avec Misty. Repasse plus tard! + +[TONIGO] +~g~Toni a emmené sa mère à l'opéra. Rappelle plus tard! + +[KEMUGO] +~g~Maria et Kenuri sont occupés. Reviens un peu plus tard! + +[KENJGO] +~g~Kenji est à un congrès de Yakuzas. Repasse plus tard. + +[RAYGO] +Ray a autre chose à faire que de voir ta tronche. Va faire un tour! + +[LOVEGO] +~g~Donald Love a d'autres chats à fouetter. Prends rendez-vous la prochaine fois! + +[KENSGO] +~g~Kenji est occupé. Repasse à un autre moment. + +[ASUSGO] +~g~Asuka n'est pas dispo pour l'instant. + +[HOODGO] +~g~Les Hoods ne sont pas là pour le moment. + +[WRONGT1] +~g~Repasse entre 05:00 et 21:00 pour du boulot. + +[WRONGT2] +~g~Repasse entre 06:00 et 14:00 pour du taf. + +[WRONGT3] +~g~Ramène ta fraise entre 15:00 et 00:00 pour bosser. + +[GUN_1A] +Sers-toi de la ~h~touche ~k~~PED_CYCLE_WEAPON_RIGHT~~w~ et de la ~h~touche ~k~~PED_CYCLE_WEAPON_LEFT~~w~ pour faire défiler tes armes. + +[GUN_2A] +Maintiens la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée pour ~h~viser automatiquement~w~ tes ennemis et appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour tirer. Entraîne-toi sur les cibles... + +[GUN_2C] +Maintiens la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée pour ~h~viser automatiquement~w~ tes ennemis et appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour tirer. Entraîne-toi sur les cibles... + +[GUN_2D] +Maintiens la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée pour ~h~viser automatiquement~w~ tes ennemis et appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour tirer. Entraîne-toi sur les cibles... + +[GUN_3A] +Tout en appuyant sur la ~h~touche ~k~~PED_LOCK_TARGET~~w~, appuie sur la ~h~touche ~k~~PED_CYCLE_TARGET_LEFT~~w~ ou la ~h~touche ~k~~PED_CYCLE_TARGET_RIGHT~~w~ pour changer de cible. + +[GUN_3B] +Tout en appuyant sur la ~h~touche ~k~~PED_LOCK_TARGET~~w~, appuie sur la ~h~touche ~k~~PED_CYCLE_TARGET_LEFT~~w~ ou la ~h~touche ~k~~PED_CYCLE_TARGET_RIGHT~~w~ pour changer de cible. + +[GUN_4A] +Tu peux marcher ou courir tout en gardant la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée afin de verrouiller une cible. + +[GUN_4B] +Tu peux marcher ou courir tout en gardant la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée afin de verrouiller une cible. + +[GUN_5] +Tu peux t'entraîner à cibler et tirer sur ces cibles en papier. Quand tu as finis, reprends ta mission. + +[TAXI1] +~g~Cherche une course. + +[FARE1] +~g~Destination : ~w~Le 'Club Sex Meeouch'~g~ dans Le Quartier Rouge. + +[FARE2] +~g~Destination : ~w~'Pribas'~g~ au Belvédère de Portland. + +[FARE3] +~g~Destination : ~w~la 'Vieille Ecole'~g~ à Chinatown. + +[FARE4] +~g~Destination : ~w~le 'Cafe greasy Joes' ~g~au Point Callahan. + +[FARE5] +~g~Destination : ~w~'AmmuNation'~g~ dans Le Quartier Rouge. + +[FARE6] +~g~Destination : ~w~'Caisses à crédit'~g~ à Saint Mark's. + +[FARE7] +~g~Destination : ~w~le 'Woody's topless bar' ~g~ dans le Quartier Rouge. + +[FARE8] +~g~Destination : ~w~le 'Bistro de Marco'~g~ à Saint Mark's. + +[FARE9] +~g~Destination : ~w~le 'Garage import export' ~g~au Port de Portland. + +[FARE10] +~g~Destination : ~w~'Têtes de Punk' ~g~ à Chinatown. + +[FARE12] +~g~Destination : ~w~le 'Stade de Football'~g~ à Aspatria. + +[FARE13] +~g~Destination : ~w~'L'église'~g~ au Point Bedford. + +[FARE14] +~g~Destination : ~w~'Le Casino'~g~ à Torrington. + +[FARE15] +~g~Destination : ~w~'Université de Liberty'~g~ au Campus Liberty. + +[FARE16] +~g~Destination : ~w~le 'Centre commercial~g~ dans le coin du Parc Belleville. + +[FARE17] +~g~Destination : ~w~le 'Musée'~g~ de Newport + +[FARE18] +~g~Destination : ~w~le 'Bâtiment Am'~g~ de Torrington. + +[FARE19] +~g~Destination : ~w~'Burgers bourgeois'~g~ à Point Bedford. + +[FARE20] +~g~Destination : ~w~'Le parc'~g~ à Belleville. + +[FARE21] +~g~Destination : ~w~'Aéroport internationnal Francis'~g~. + +[FARE22] +~g~Destination : ~w~'l'écluse de Cochrane'~g~. + +[FARE24] +~g~Destination : ~w~'L'hôpital'~g~ de la Crique Pike. + +[FARE25] +~g~Destination : ~w~le 'Parc'~g~ à la vallée Shoreside. + +[FARE26] +~g~Destination : ~w~les 'Tours North West'~g~ des jardins Wichita. + +[NEW_TAX] +PLUS GROS! PLUS RAPIDES! PLUS SOLIDES! Les taxis Borgnine s'installent à Harwood. Contactez-les dès aujourd'hui au 555-Borgnine! + +[TSCORE2] +~1~$ + +[IN_ROW] +~1~ DE SUITE! Bonus : ~1~$ + +[TTUTOR] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver l'affichage des missions taxi. + +[TTUTOR2] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver l'affichage des missions taxi. + +[ATUTOR2] +~g~Conduis les patients à l'hôpital. DOUCEMENT. Chaque secousse réduit leurs chances de survie. + +[A_TIME] ++~1~ secondes. + +[A_FULL] +~r~Ambulance pleine! + +[A_RANGE] +~g~La radio de l'ambulance ne capte plus rien. Rapproche-toi d'un hôpital! + +[FTUTOR] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions camion de pompier. + +[FTUTOR2] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions camion de pompier. + +[F_PASS1] +Feu éteint! + +[F_RANGE] +~g~La radio du camion de pompier ne capte plus rien. Rapproche-toi d'une caserne de pompiers! + +[C_BREIF] +~g~Suspect aperçu pour la dernière fois dans le secteur : ~a~. + +[C_RANGE] +~g~La radio de la voiture de police ne capte plus rien. Rapproche-toi d'un poste de police! + +[DODO_FT] +Tu as 'volé' pendant ~1~ secondes! + +[EBAL_A] +Je connais un coin dans Le Quartier Rouge où on pourra se planquer, + +[EBAL_A1] +faut que tu prennes le volant, mes mains tremblent trop. + +[EBAL_1] +Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~sortir~w~ d'un véhicule. + +[EBAL_1B] +Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~sortir~w~ d'un véhicule. + +[EBAL_2] +~g~Remonte dans la bagnole! + +[EBAL_3] +C'est le ~h~radar~w~. Utilise-le pour te repérer dans la ville, et suis le ~h~symbole~w~ sur le ~h~radar~w~ pour trouver la planque! + +[EBAL_D] +Je connais un mec qu'a des relations. Il s'appelle Luigi. + +[EBAL_D1] +On est pote, alors il pourra certainement te trouver du boulot. Viens, on y va. + +[EBAL_E] +Allez, viens, on va y faire un tour, histoire de te présenter. + +[EBAL_I] +Le patron viendra bientôt vous voir... + +[EBAL_J] +8-Ball a des trucs à faire en haut. + +[EBAL_K] +Tu peux peut-être me rendre un service. + +[EBAL_L] +Une de mes filles a besoin d'un taxi. Tire une bagnole et va chercher Misty à la clinique. Ensuite, ramène-la ici. + +[EBAL_N] +Et garde bien tes mains sur le volant, compris ? + +[EBAL_4] +~r~8-Ball est mort! + +[EBAL_5] +~g~Prenez une caisse! + +[EBAL_6] +~g~Va chercher Misty! + +[LM1] +'LES FILLES DE LUIGI' + +[LM2] +'PAS DE SPANK POUR LA PEPEE' + +[LM3] +'LA MYSTERIEUSE MISTY' + +[LM5] +'LE BAL A BALLES' + +[LM1_2] +~g~Emmène Misty au club de Luigi. + +[LM1_3] +~g~Klaxonne pour faire monter la fille. + +[LM1_6] +~g~Rentre dans la caisse! + +[LM1_7] +Arrête la bagnole près de Misty et laisse-la monter à bord. + +[LM1_8] +Tu peux retourner voir Luigi pour le boulot, ou visiter Liberty City. + +[LM2_A] +Y'a une nouvelle saloperie sur le marché. Ca s'appelle la SPANK. + +[LM2_E] +Un petit malin fourgue cette merde à mes filles du Port de Portland. + +[LM2_B] +Va lui foutre une raclée! + +[LM2_G] +Je me vengerai! + +[LM2_1] +~g~Pique sa caisse et fais-la repeindre. + +[LM2_2A] +Sers-toi de la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~donner un coup de poing~w~, ~h~un coup de pied~w~ ou un ~h~coup de batte~w~. + +[LM2_2C] +Sers-toi de la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~donner un coup de poing~w~, ~h~un coup de pied~w~ ou un ~h~coup de batte~w~. + +[LM2_2D] +Sers-toi de la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~donner un coup de poing~w~, ~h~un coup de pied~w~ ou un ~h~coup de batte~w~. + +[LM2_3] +~g~Planque la bagnole dans le garage de Luigi! + +[LM2_4] +~g~Repeins la bagnole! + +[LM3_A] +Hé, faut que je te cause... OK, Mick, je te parle plus tard. + +[LM3_B] +Comment ça va, gamin ? + +[LM3_C] +Le fils du Don, Joey Leone, il veut voir sa régulière, Misty. + +[LM3_D] +Va la chercher à Hauteurs de Hepburn... + +[LM3_E] +Mais fais gaffe, c'est le territoire de Diablo. + +[LM3_F] +Amène-la ensuite au garage de joey, à Trenton et vite! + +[LM3_H] +Donc tes yeux, ils regardent la route et pas Misty, ok ? + +[LM3_1D] +Appuie sur la ~h~touche L3~w~ pour ~h~klaxonner~w~ et indiquer à Misty que tu es là. + +[LM3_2] +~g~Emmène Misty chez Joey! + +[LM3_4] +~g~Va chercher Misty! + +[LM3_5] +Tu bosses pour Luigi, hein ? Il était temps qu'il se trouve un chauffeur digne de confiance. + +[LM3_7] +Je suis à toi dans une minute, poupée. + +[LM3_10] +~g~Trouve une caisse! + +[LM4_B] +Va t'occuper de cette affaire pour moi. + +[LM4_C] +Si t'as besoin d'un calibre, va derrière Ammu-Nation en face du métro. + +[LM5_A] +Le Bal de la police a lieu dans la vieille école près du Pont de Callahan. + +[LM5_B] +Alors les flics auront besoin d'action à l'ancienne! + +[LM5_C] +J'ai des filles partout dans les rues. + +[LM5_D] +Emmène-les au bal, histoire de faire d'une pierre deux coups : profit et détente! + +[LM5_1] +~g~Si tu les serres trop, elles vont avoir des bleus! ~g~Livre d'abord celles-la et reviens en chercher d'autres. + +[LM5_2] +~r~L'une des filles de Luigi est bonne pour la morgue! + +[LM5_3] +~g~T'as besoin d'un véhicule! + +[LM5_4] +~g~Va ramasser les filles qui tapinent à St. Mark's. + +[LM5_5] +~g~Emmène les filles au bal! + +[LM5_8] +~g~Filles au bal : ~1~ + +[JM2] +'AU REVOIR LEE' + +[JM4] +'LE CHAUFFEUR DE MONSIEUR' + +[JM5] +'TRANSPORT DE MARCHANDISES AVARIEES' + +[JM1_1] +~g~Amène la voiture de Forelli au garage de 8-Ball, au nord, derrière 'Caisses à crédit'. + +[JM1_2] +~g~Ramène la bagnole au Bistro de Marco. + +[JM1_3] +~g~Arme la bombe et CASSE-TOI EN VITESSE! + +[JM1_4] +~g~T'as bousillé la caisse! Fais-la réparer! + +[JM1_5] +~g~T'as pas armé la bombe! + +[JM1_6] +~g~Gare la bagnole correctement! + +[JM1_8A] +~y~Hé, c'est mon gars préféré! + +[JM1_8B] +~y~Chez l'artifier, tout est automatique. T'amènes la voiture, tu la gares, et le reste se fait tout seul! + +[JM1_8C] +~y~Viens, le premier est gratuit, mais juste le premier, compris ? + +[JM2_A] +Chunky Lee Chong fout la merde avec la Spank pour un nouveau gang de Colombie... ou du Colorado... un coin comme ça... + +[JM2_B] +J'en sais rien. On s'en fout, après tout. + +[JM2_D] +Il a dépassé les bornes! + +[JM2_E] +Il faut lui régler son compte! + +[JM2_G] +Débrouille-toi avec un 9 mm, tu sais où il se trouve, non ? + +[JM2_H] +Et souviens-toi : fais gaffe à Chinatown, c'est le territoire de la Triade. + +[JM3_A] +Bon, on va s'attaquer à un fourgon blindé. + +[JM3_B] +Il part de Chinatown tous les jours. + +[JM3_C] +Les balles traverseront pas son blindage, alors vole une caisse et rentre-lui dedans. + +[JM3_D] +Cogne bien fort et les larbins de sécurité devraient pas demander leur reste! + +[JM3_E] +Ensuite, t'emmènes le fourgon à l'entrepôt des docks et mes gars prennent le relais. + +[JM3_F] +Mais bon, ils vont pas faire leur ronde toute la journée, alors traîne pas en route! + +[JM3_1] +~g~Emmène le fourgon à la planque. + +[JM3_2] +~g~Fonce dans le fourgon jusqu'à ce que ses dégâts soient inférieurs à 70%. + +[JM4_B] +Hé! C'est le gars dont je te parlais! + +[JM4_C] +Ok. Ce gars, c'est pas un Italien, c'est pas un mécano, mais il peut réparer les choses. + +[JM4_D] +C'est Pops Capo, Toni Cipriani. + +[JM4_E] +Ouais, je suis Toni Cipriani. + +[JM4_F] +Emmène-le au resto de la Mamma, à St Mark's, ok ? + +[JM4_G] +Maintenant, écoute. Je prépare un truc et j'ai besoin d'un bon conducteur. Alors, passe me voir, ok ? + +[JM4_2] +Attends-moi ici. Et laisse tourner le moulin. C'est pas une visite de courtoisie. + +[JM4_3] +Une embuscade de la Triade! Sors-nous d'ici, gamin! + +[JM4_4] +La Triade pense qu'elle peut s'attaquer à moi! Hum! Tu te rends compte, A MOI! + +[JM4_6] +Hé, fais gaffe à la bagnole! J'ai dit pas de lézards! + +[JM4_7] +~g~Emmène Toni au restaurant de la Mamma. + +[JM4_8] +~r~Toni sert d'engrais aux chrisantèmes! + +[JM5_A] +Magnifique! Réellement magnifique! + +[JM5_B] +Très bien, c'est l'homme qu'il me fallait! + +[JM5_D] +Un des Forelli s'est cru un peu trop malin et il en a pris pour son grade! + +[JM5_E] +Emmène le corps au broyeur de Harwood, ok ? + +[JM5_1] +~g~Emmène-le au broyeur! + +[JM5_2] +~g~C'est les frères Forelli! + +[JM6_A] +Joli morceau, hein? + +[JM6_B] +Ok, écoute : choisis une caisse dans l'entrepôt de Saint Mark's et va chercher quelques potes à moi. + +[JM6_C] +Il font un retrait à la banque et ils ont besoin d'un taxi. + +[JM6_D] +J'ai dit que tu ferais parfaitement l'affaire, alors me déçois pas! + +[JM6_E] +Amène-les à la banque avant 5 heures, et sois pas en retard! + +[JM6_2] +Laisse le moulin tourner, y'en a pas pour longtemps! + +[JM6_3] +Sors-nous de là! + +[JM6_4] +Débarrasse-toi des poulets et amène-nous à l'entrepôt! + +[JM6_6] +~g~Va voler une caisse moins voyante! + +[JM6_7] +~g~Faut que tu prennes les 3 pour voler la banque! + +[TM1] +'LINGE SALE' + +[TM2] +'LA LIVRAISON' + +[TM3] +'LA RENCONTRE' + +[TM4] +'LE TRIANGLE DES TRIADES' + +[TM5] +'LA HUITIEME PLAIE' + +[TONI_P] +J'ai un boulot urgent pour toi! + +[TM1_A] +~w~Assieds-toi, gamin. Prends une de ces putains de chaises. + +[TM1_B] +~w~Alors, la laverie veut pas payer pour sa protection, hein ? + +[TM1_C] +~w~La Triade pense qu'elle peut se mêler de mes affaires ? + +[TM1_D] +~w~On va apprendre à ces faux durs ce que c'est que des vrais hommes! + +[TM1_E] +~w~Ouais, on va leur apprendre à nous respecter! Aucun de mes gars ne se laisse intimider par une Triade minable! + +[TM1_F] +~w~Ton père, qu'il repose en paix, se laissait pas faire par les Triades, à l'époque, en Sicile! + +[TM1_G] +~w~Pardon Ma. Oui Ma. + +[TM1_H] +~w~Je veux que tu détruises les camionnettes de la laverie. + +[TM1_I] +~w~Et roule sur tous les gars de la Triade que tu croiseras. + +[TM1_J] +~w~8-Ball te donnera ce dont tu as besoin. + +[TM2_A] +~w~Toni est parti en faire saigner plus d'un, ou du moins, il essaie. + +[TM2_AA] +Il ne sera jamais aussi fort que son papa. Il t'a laissé un mot sur la table. + +[TM2_B] +~w~La laverie a accepté de payer. C'est du bon boulot! + +[TM2_C] +~w~Va chercher la thune et ramène-la ici. Et fais gaffe à la Triade. + +[TM2_D] +~w~C'est comme les roquets : ça aboie, mais ça mord pas! + +[TM2_E] +~w~Personne, je dis bien PERSONNE, ne se mêle des affaires de TONY CIPRIANI! + +[TM2_1] +~g~Apporte le flouze à Toni! + +[TM2_2] +~g~Tu les as tous refroidis! + +[TM3_MA] +~w~Je ne sais pas où il est! + +[TM3_MB] +~w~Ce gamin, des fois, il sait même pas où il se trouve! + +[TM3_MC] +~w~Son père, c'est sûr, c'était différent. Toujours au top, sûr de lui, viril... + +[TM3_A] +~w~Don Salvatore a convoqué une assemblée. + +[TM3_B] +~w~J'ai besoin de toi pour aller chercher la Stretch et Joey, son fils, au garage. + +[TM3_C] +~w~Alors va chercher Luigi à son club et reviens ensuite me chercher. + +[TM3_D] +~w~On ira tous ensemble chez le don. + +[TM3_E] +~w~Ces Triades, elles savent jamais quand s'arrêter. + +[TM3_F] +~w~Elles veulent la guerre, elles auront la guerre. + +[TM3_G] +~w~Maintenant, faut y aller. + +[TM3_1] +~g~Prends la Stretch chez Joey. + +[TM3_2] +~g~Va chercher Luigi. + +[TM3_3] +~g~Va chercher Toni. + +[TM3_4] +~g~Conduis tout le monde chez Salvatore. + +[TM3_5] +~y~Une embuscade de la Triade! + +[TM4_B] +~w~Nous sommes en GUERRE! La Triade se sert d'une conserverie de poisson comme façade! + +[TM4_C] +~w~Ils règlent la majeure partie de leurs affaires au marché aux poissons de Chinatown. + +[TM4_D] +~w~Cette laverie doit toujours payer son assurance... + +[TM4_E] +~w~Ils pensent que la Triade les protège, donc il faut leur montrer que ce n'est pas le cas. + +[TM4_F] +~w~Prends les garçons avec toi pour éliminer les chefs de la Triade! + +[TM4_G] +~w~Et puis, si t'en as l'occasion, descends quelques-uns de leurs porte-flingues. + +[TM4_GAT] +~g~Tu as besoin d'un 'Camion de poisson de la Triade' pour entrer. + +[TM5_A] +TEXT NO LONGER REQUIRED + +[TM5_B] +~w~Ok, j'en ai marre de toutes ces conneries. + +[TM5_C] +~w~On va en finir une bonne fois pour toute avec la Triade de Liberty!. + +[TM5_D] +8-Ball a installé une bombe sur une benne à ordures. + +[TM5_E] +~w~Il y a un minuteur donc, si tu te chies dessus, y'aura pas de preuves. Va chercher la benne. + +[TM5_F] +~w~Fais gaffe, 8-Ball dit que c'est super-sensible et que la moindre secousse peut tout faire sauter. + +[TM5_G] +~w~La conserverie de poisson laissera entrer la benne, après ce sera à toi de jouer. + +[TM5_H] +~w~Gare-toi entre les bonbonnes de gaz et casse-toi en vitesse! + +[TM5_I] +~w~Je veux qu'il pleuve des maquereaux! + +[TM5_J] +~w~C'est du Cecil B. DeMille que je veux, pas de la série Z! + +[FM2] +'LA FILLE DU MOGHOL' + +[FM4] +'DERNIERES VOLONTES' + +[FM1_A] +~w~Moi et les gars, on a besoin de causer affaires. + +[FM1_B] +~w~Alors tu vas veiller sur ma fille pendant la soirée. + +[FM1_C] +~w~MARIA! RAMENE TES FESSES PAR ICI! + +[FM1_D] +~w~Cette petite conne fait toujours ça. + +[FM1_E] +~w~Et la voilà, la seule et unique reine de Saba! + +[FM1_F] +~w~Qu'est-ce que tu faisais là-bas ? + +[FM1_G] +~w~Enfin, peu importe, je parie que ça m'a coûté de l'argent! + +[FM1_H] +~w~Bon, tu crois quand même pas que je suis dans le coin pour te faire la conversation ? + +[FM1_I] +~w~Monte dans cette bagnole et ferme ta grande gueule! + +[FM1_J] +~w~Prends la Stretch, mais tu la ramènes en un seul morceau, compris? + +[FM1_K] +~w~Et surveille-la, elle peut foutre la merde! + +[FM1_L] +~w~Ouais, ouais, ouais, je suis sûr que le neurone de ton nouveau larbin a tout enregistré! + +[FM1_M] +~w~Et pis il est taillé comme une armoire normande, alors y'a pas de risque! + +[FM1_N] +~w~Allez, Fido. On va chez Chico s'éclater un peu! + +[FM1_P] +~g~C'est Chico, là! Arrête-moi à côté! + +[FM1_S] +~w~Bonsoir, jolie dame. + +[FM1_TT] +~w~22, V'LA LES FLICS! + +[FM1_1] +~g~Retourne dans la Stretch! + +[FM1_2] +~g~Monte dans la Stretch! + +[FM1_3] +~r~Si tu laisses Maria, Salvatore va l'avoir mauvaise, alors retourne la chercher! + +[FM1_4] +~g~Tu viens de larguer la femme du Don! Retourne à l'entrepôt et attends Maria! + +[FM1_5] +~g~Ramène Maria chez Salvatore! + +[FM1_6] +~g~Chico ne restera pas là tout le temps, alors emmène Maria voir la mer. + +[FM1_7] +~r~Maria est morte! Salvatore va être furax... + +[FM1_8] +~r~T'as trucidé le fournisseur de Maria! + +[FM2_J] +Laisse-nous seuls un moment. + +[FM2_A] +Le Cartel colombien fabrique de la SPANK quelque part à Liberty. + +[FM2_K] +Mais on sait pas où, et on dirait qu'ils sont au courant de nos moindres faits et gestes. + +[FM2_L] +Y'a un gars, Bob le frisé, qui bosse au bar chaz Luigi. + +[FM2_M] +Il dépense plus qu'il ne gagne... + +[FM2_N] +Il prend souvent un taxi pour rentrer chez lui après le boulot. Alors, suis-le. + +[FM2_O] +Et si c'est lui la balance, descends-le... + +[FM2_F] +Voilà notre copain. Monsieur grande gueule en personne. + +[FM2_G] +T'as été suivi ? Tu sais que ce qui se passe ici, c'est notre petit secret... + +[FM2_H] +Non, non, j'ai pas été suivi. T'as la came ? + +[FM2_I] +Voilà ta SPANK, alors crache le morceau. + +[FM2_P] +Ok, alors ce qui se passe, c'est que les Leone mènent la guerre sur deux fronts à la fois. + +[FM2_Q] +Y'a une guerre de territoire avec la Triade qu'est pas prête de finir + +[FM2_R] +et Joey Leone a foutu la merde avec les Forelli. + +[FM2_S] +Ils perdent du terrain et des hommes tous les jours. + +[FM2_T] +Salvatore devient de plus en plus irascible et parano chaque jour. Il soupçonne tout le monde. + +[FM2_U] +Ben, quand je te vois, je me dis qu'il a pas forcément tort... + +[FM2_1] +~g~Voilà Bob le frisé! + +[FM2_2] +~g~Bob a quitté le club, suis-le! + +[FM2_5] +~g~Amène-le au port de Portland. + +[FM2_6] +~r~Bob montera jamais dans un taxi bousillé! + +[FM2_7] +~r~Bob le frisé s'est dégonflé. Le rendez-vous est annulé. + +[FM2_8] +~g~Cogne Bob le frisé! + +[FM2_9] +~r~Bob le frisé est mort! + +[FM2_10] +~r~Bob le frisé s'est fait la malle! + +[FM2_11] +~g~Gare-toi devant le club de Luigi, Bob le frisé devrait pas tarder à sortir. + +[FM2_12] +~r~Il t'a faussé compagnie! + +[FM3_A] +~w~On devrait régler leur compte à ces bâtards de Colombiens. + +[FM3_B] +~w~Mais tant qu'on est en guerre avec les Triades, on n'est pas assez fort. + +[FM3_C] +~w~Les fonds du Cartel sont illimités, avec tout l'argent qu'ils se font sur la SPANK. + +[FM3_D] +~w~Si on les attaquait de front, ils nous lamineraient! + +[FM3_E] +~w~Ils doivent fabriquer la SPANK sur le bateau vers lequel Le frisé t'a conduit. + +[FM3_F] +~w~Va falloir utiliser nos cerveaux, ou plutôt un. Le tien! + +[FM3_G] +~w~Je te demande de détruire cette usine de SPANK comme une faveur, pour moi, Salvatore Leone. + +[FM3_H] +~w~Si tu y arrives, ton avenir est assuré, tu auras tout ce que tu veux! + +[FM3_I] +~w~Va voir 8-Ball, il te dira comment faire, c'est un expert en explosifs. + +[FM3_8A] +~w~Salut mon gars! Salvatore m'a téléphoné. + +[FM3_8B] +~w~Pour un boulot comme ça, il va te falloir un sacré paquet de feux d'artifices! + +[FM3_8D] +~w~Mais tu sais que tu en as toujours pour ton pognon avec moi! + +[FM3_8E] +~w~Ok, au boulot! + +[FM3_8F] +~w~Je peux régler le détonateur de ce bébé, mais je peux toujours pas utiliser un flingue avec ces mains. + +[FM3_8G] +~w~Tiens, cette pétoire devrait te servir à faire sauter quelques têtes. + +[FM3_4] +~g~Arrête la bagnole et laisse 8-Ball sortir. + +[FM3_7] +~r~8-Ball s'est fait refroidir! + +[FM3_8] +~r~Les gardes ont été prévenus! + +[FM4_A] +~w~Ah, voilà mon nettoyeur favori! + +[FM4_B] +~w~Je suis fier de toi, mon garçon, tu leur en as mis plein la gueule! + +[FM4_C] +~w~J'ai juste un autre petit travail à te confier avant de pouvoir sabrer le champagne. + +[FM4_D] +~w~Il y a une voiture devant le club de Luigi. + +[FM4_E] +~w~L'intérieur est tapissé de cervelle! + +[FM4_F] +~w~On a aidé un gars à se faire une idée, et y'a eu quelques salissures. + +[FM4_H] +~w~Emmène-le au broyeur avant que les flics ne le trouvent. + +[AM3] +'PURGE DE PAPARAZZI' + +[AM4] +'JOUR DE PAYE' + +[AM5] +'TANNER L'AGENT DOUBLE' + +[AM1_A] +Nous devons éclaircir certains points avant de poursuivre nos relations, + +[AM1_B] +affaires ou autres. Mettons cartes sur table. + +[AM1_C] +Moi, je suis Yakuza et je sais que tu travailles pour la famille de Salvatore Leone. + +[AM1_D] +Nous pouvons te donner du travail, + +[AM1_E] +mais il faut d'abord nous prouver que t'as plus aucun lien avec la mafia. + +[AM1_G] +Débrouille-toi pour qu'il n'arrive pas vivant à son club. + +[AM1_H] +Pendant ce temps-là, Maria et moi, nous nous remémorerons le bon vieux temps. + +[AM1_I] +Oh... Asuka, tu t'es trouvé un messager. + +[AM1_J] +Ce n'est pas un simple messager. + +[AM1_1] +~g~Salvatore part de chez Luigi! + +[AM1_2] +~r~Tu t'es fait repérer! + +[AM1_3] +~r~Tu as raté Salvatore! + +[AM1_4] +~r~Bravo, t'as effrayé la cible! Et t'es censé être un tireur d'élite, c'est ça ? + +[AM1_5] +~g~Va au Le Quartier Rouge et attends que Salvatore sorte du club. + +[AM1_7] +~r~Salvatore est chez lui, à siroter un whisky. T'es vraiment pas une flèche, toi! + +[AM1_8] +~g~Salvatore va sortir de chez Luigi à environ ~1~:~1~. + +[AM2_4] +~g~Ils t'ont vu venir aussi nettement que si t'étais un éléphant rose! + +[AM3_A] +Y'a un journaliste qui nous tourne autour. + +[AM3_B] +Maria et moi, on part en vacances, histoire de te laisser le temps de te débarrasser de ce voyeur pervers. + +[AM4_A] +Ah, mon meilleur homme! + +[AM4_B] +Maria est très occupée, mais je lui dirai que t'es passé. + +[AM4_C] +Qui est là ? Asuka ? Je sais que je n'ai pas été très sage, mais j'ai le droit d'aller toute seule au petit coin, ok ? + +[AM4_D] +Il est temps que tu rencontres notre homme au LPD. + +[AM4_E] +Voilà sa paye pour le dernier service qu'il nous a rendu. + +[AM4_F] +Il est terriblement prudent. + +[AM4_G] +Va à la cabine de téléphone de Torrington aussi vite que tu peux et attends ses instructions. + +[AM5_A] +Maria et moi, on est allé faire des courses. + +[AM5_B] +Notre informateur dans la police nous a dit qu'un de nos conducteurs est un flic sous couverture! + +[AM5_C] +Il ne vaut pas grand chose hors de sa voiture, alors on y a installé un capteur. + +[AM5_D] +Je veux qu'il souffre! + +[AM5_1] +Tanner est à tes trousses! + +[AS1] +'L'APPAT' + +[AS2] +'CAFE RAPIDO' + +[AS4] +'LA RANCON' + +[AS1_A] +~w~Miguel semble croire que je l'ai maltraité. + +[AS1_B] +Il nous a néanmoins révélé à quel point Catalina s'inquiète de ta soif de vengeance. + +[AS2_A] +~w~On a sous-estimé les ambitions de Catalina avec la SPANK. + +[AS2_B] +~w~Cela va plus loin qu'une vente à la sauvette dans les rues. + +[AS2_D] +~w~Il fourgue sa came sur des étals. + +[AS2_1] +~g~Tous les étals des vendeurs de café de Portland ont été renversés! + +[AS2_2] +~g~Tous les étals des vendeurs de café de l'île de Staunton ont été renversés! + +[AS2_3] +~g~Tous les étals des vendeurs de café la vallée Shoreside ont été renversés! + +[AS2_4] +~r~Le Cartel a mis en garde ses revendeurs! + +[AS2_5] +~g~Il y a toujours des stands de café à la vallée Shoreside et sur l'île Staunton! + +[AS2_6] +~g~Il y a toujours des stands de café à la vallée Shoreside! + +[AS2_7] +~g~Il y a toujours des stands de café sur l'île Staunton! + +[AS2_8] +~g~Il y a encore des étals de vendeurs de café à Portland! + +[AS2_9] +~g~Il y a toujours des stands de café dans Portland et la vallée Shoreside! + +[AS2_10] +~g~Il y a toujours des stands de café dans Portland et sur l'île Staunton! + +[AS2_12] +~g~Parcours les districts de Liberty pour trouver ~b~les étals de Café Rapido! + +[AS3_A] +~W~Est-ce qu'on le serre un peu plus maintenant ou on attend juste qu'il vire au noir et qu'il tombe tout seul? + +[AS3_B] +~w~Allez, encore un peu... + +[AS3_D] +~w~Mon homme à tout faire! + +[AS3_E] +~w~Je m'ennuyais alors je suis venue tenir compagnie à Asuka. + +[AS3_1] +~g~Trouve un ~r~bateau~g~ et rejoins la ~b~bouée repère! + +[AS3_3] +~g~Attends que l'~y~avion~g~ s'approche! + +[AS3_5] +~g~Récupère la marchandise! + +[AS3_4] +~g~Sers-toi d'un lance-roquettes pour descendre l'~y~avion~g~! + +[AS3_2] +~b~Va vers les bouées repères! ~y~L'avion est en phase d'approche! + +[AS3_6] +~g~~1~ SUR 8 + +[KM1] +'LA GRANDE EVASION' + +[KM3] +'NEGOCIATION' + +[KM4] +'SHIMA' + +[KM5] +'LES RIVIERES POURPRES' + +[KM1_A] +Ma soeur m'a parlé de toi en des termes élogieux, + +[KM1_E] +même si je suis toujours pas convaincu qu'un gaijin à autre chose à offrir que des déceptions. + +[KM1_B] +Quoique, tu pourrais peut-être m'aider à régler un petit problème. + +[KM1_F] +Bien sûr, un échec te serait fatal. + +[KM1_C] +Un kanbu Yakuza est en garde à vue, en attente d'un transfert au palais de justice. + +[KM1_G] +C'est un membre apprécié de notre famille. + +[KM1_H] +Libère-le et amène-le au dojo de Bedford Point. + +[KM1_D] +Nous te remercions pour ton aide désintéressée. Si toi aussi, tu as besoin d'aide un jour, le dojo te fournira avec plaisir deux hommes pour t'assister. + +[KM1_1] +~g~Vole une bagnole de flics! + +[KM1_2] +~g~Equipe-la d'une bombe! + +[KM1_3] +~g~Maintenant, emmène-la au dojo des Yakuzas! + +[KM1_5] +~g~Bien, maintenant, va au poste de police. + +[KM1_6] +~g~Equipe la voiture d'une bombe! + +[KM1_7] +~g~Réservé aux véhicules de police! + +[KM1_9] +~r~Tu n'as pas utilisé de voiture piégée pour détruire le mur. + +[KM1_10] +~r~Le Yakuza Kanbu est mort, tout comme ton honneur! + +[KM1_11] +~r~Tu t'es fouré tout seul dans le pétrin! + +[KM2_A] +Dans ce milieu, l'étiquette ne doit jamais être sous-estimée. Au contraire! + +[KM2_B] +J'ai honte mais un homme m'a autrefois rendu service et je n'ai jamais eu l'occasion de lui montrer ma reconnaissance. + +[KM2_C] +Cet homme est un passionné de voitures et il a demandé que nous lui fournissions certains modèles pour sa collection. + +[KM2_F] +Mon honneur m'interdit de refuser. + +[KM2_2] +~g~Voiture livrée. + +[KM3_A] +Face au danger, l'inconscient tourne le dos alors que le sage fait front. + +[KM3_B] +Nous avons prévenu à plusieurs reprises le Cartel colombien qu'il fallait laisser nos intérêts à Liberty tranquilles. + +[KM3_C] +En ce moment même, ils négocient avec les Jamaïcains pour se moquer encore plus de nous. + +[KM3_D] +Ils sont en train de passer un accord pour l'ensemble de la ville. + +[KM3_F] +Prends un de mes hommes, vole une bagnole de Yardie et va présenter nos respects aux Colombiens. + +[KM3_E] +Pour notre honneur, ils doivent tous mourir! + +[KM3_2] +~g~Va prendre ton contact. + +[KM3_3] +~g~La réunion a lieu sur le parking de l'hôpital de Rockford! + +[KM3_4] +~r~Ils ont filé! + +[KM3_6] +~g~Supprime-les, tue-les tous! + +[KM3_8] +~g~Il te faut une bagnole de Yardie pour ce boulot! + +[KM3_9] +~r~L'un des Colombiens est mort, l'accord est caduc. + +[KM3_10] +~r~Le contact est mort! + +[KM4_A] +La seule vraie force consiste à ne jamais montrer ses faiblesses. + +[KM4_C] +Va récupérer l'argent immédiatement pour qu'on puisse le placer sur les comptes du casino. + +[KM4_1] +Je peux pas vous payer et même si je pouvais, je le ferais pas! + +[KM4_9] +Un gang de jeunes vient de dévaliser cet endroit! Ils ont tout pris! + +[KM4_2] +Vous ne servez à rien! + +[KM4_10] +Quel genre de Yakuza êtes-vous? hein? + +[KM4_3] +C'est pas pour ça que je vous paye! Si j'avais voulu ce genre de protection, je me serais adressé directement aux flics! + +[KM4_4] +~g~Réglez son compte au gang et récupérez ~b~l'argent de la protection~g~! + +[KM4_7] +~r~Le commerçant est mort! + +[KM4_5] +Donald Love aimerait que vous passiez prendre le thé chez lui afin d'avoir une conversation avec vous. + +[KM4_6] +Tout l'argent est là! + +[KM4_8] +~g~Malette récupérée! + +[KM5_A] +TOI! Et c'est maintenant que tu te décides à te montrer! + +[KM5_B] +Il semblerait que ta tentative pour empêcher les Jamaïcains + +[KM5_B1] +de s'allier aux Colombiens ait complètement raté! + +[KM5_C] +Y a des dealers du gang des Caraïbes dans tout Liberty qui vendent des sachets de SPANK comme si c'était des friandises! + +[KM5_D] +Les membres du Cartel se foutent de nous et de moi surtout! + +[KM5_E] +Je te donne une dernière chance de me prouver que ma soeur avait raison de te faire confiance! + +[KM5_F] +Débarrasse-moi de tous ces rats et rachète-toi avec leur sang! + +[KM5_3] +~r~Tu n'as pas réussi à tuer au moins ~1~ Yardies. + +[KM5_4] +~g~Bravo, tu as tué ~1~ Yardies. + +[KM5_5] +~g~Bravo! Tu as tué ~1~ Yardies. Bonus : ~1~$ + +[RM1] +'EQUILIBRER LA BALANCE' + +[RM3] +'PREUVES A PROUVER' + +[RM4] +'PECHE AU GROS' + +[RM5] +'LE MARTEAU ET L'ENCLUME' + +[RM1_D] +Il est sous protection dans une propriété de WitSec à Newport, des appartements à l'arrière du parking. + +[RM1_E] +Enfume-moi cet endroit, ça devrait les faire sortir, et tu me les butes tous, fais en sorte que Mc Machin ne parle plus à personne. + +[RM1_1] +~g~Vérifie la maison du programme de protection des témoins. + +[RM1_2] +~g~Chope McAffrey! + +[RM2_A1] +Hé, gamin, par là! + +[RM2_A] +Un vieux pote à moi de l'armée a une affaire à Rockford. + +[RM2_D] +Il a besoin d'un coup de main et, en échange, il te fera des prix d'enfer sur son matos. + +[RM2_E] +Ray a téléphoné... mais je pensais que vous seriez plusieurs... + +[RM2_F] +Bon, trois bras valent mieux qu'un, alors prends ce qu'il te faut. + +[RM2_G] +~g~Va voir Phil! + +[RM2_H] +~r~Phil a été tué! + +[RM2_L] +Héhé! Si je t'avais eu comme partenaire au Nicaragua, j'aurais peut-être encore mes deux bras! + +[RM2_N] +Laisse le fric. Maintenant, dégage, je m'occupe des poulets. + +[RM3_D] +La pièce à conviction est en train de rouler dans la ville. + +[RM3_E] +Va falloir que tu tamponnes cette caisse et que tu récupères les preuves au fur et à mesure qu'elles tombent! + +[RM3_F] +Quand t'auras tout, laisse-les dans la voiture et brûle-la. + +[RM3_G] +C'est une bonne affaire pour tous les deux, tu sais, gamin. + +[RM3_1] +~g~Laisse les preuves dans la voiture et brûle-la. + +[RM3_4] +~g~L'accusation n'a pas tenu compte des pièces à conviction! + +[RM3_6] +~r~Ces photos seront dans tout Liberty! + +[RM3_7] +~g~Brûle la voiture! + +[RM4_A] +Mon partenaire est un rat. + +[RM4_C] +Il va souvent pêcher en bateau près du phare de Portland Rock, la nuit. + +[RM4_D] +Vole une vedette de la police et assure-toi que ses sales plans de traître tombent à l'eau! + +[RM4_1] +~g~Vole une vedette de la police. + +[RM4_2] +~g~Va jusqu'au phare et 'occupe-toi' du partenaire de Ray! + +[RM5_A] +Espèce de bon à rien! + +[RM5_A1] +Tu t'es chié dessus! Mon cul est en première ligne et t'es même pas foutu de buter une mouche! + +[RM5_B] +Je t'ai filé de la thune pour buter ce témoin et il est encore en vie! + +[RM5_B1] +Et il va faire une déposition devant les fédéraux aujourd'hui! + +[RM5_C] +Ils vont le transférer d'un moment à l'autre de l'Hôpital Général Carson à Rockford! + +[RM5_D] +S'il crache le morceau, je vais cracher mes dents, et plus encore... + +[RM5_E] +Alors fais ce putain de boulot pour lequel je t'ai payé! + +[RM5_1] +~g~Intercepte l'ambulance. + +[RM5_2] +~g~Tu t'es fait repérer! + +[RM5_3] +~g~C'était un piège à cons! + +[RM5_4] +~g~Les balles ne peuvent pas traverser cette armure! + +[RM5_5] +~g~Cette armure est recouverte d'amiante! + +[RM5_7] +~r~Le témoin est arrivé à bon port! + +[RM5_8] +~g~Le témoin s'est noyé! + +[LOVE2] +'HARA-KIRI WAKA-SHIRA' + +[LOVE3] +'RIEN QU'UNE GOUTTE DANS L'OCEAN' + +[LOVE1_A] +Tout d'abord, je tiens à te remercier pour m'avoir aidé à régler cette affaire privée. + +[LOVE1_F] +Les gens croient tout ce qu'on leur raconte de nos jours! + +[LOVE1_D] +Ils essaient de m'extorquer plus d'argent, mais j'aime pas renégocier mes accords! + +[LOVE1_E] +Un accord est un accord et il n'auront rien de plus de moi! + +[LOVE1_G] +Va aider mon ami, tu as carte blanche. + +[LOVE1_2] +~g~Sauve le vieux bridé. + +[LOVE1_3] +~g~Amène le vieux bridé à l'appartement de Donald Love. + +[LOVE1_4] +~g~Le vieux bridé doit se trouver dans un de ces garages... + +[LOVE1_6] +~r~Les tripes du vieux bridé sont étalées sur le trottoir. Attention, ça glisse! + +[LOVE1_7] +~g~Les portes ne s'ouvriront que pour laisser passer une voiture du gang des Colombiens. + +[LOVE2_A] +Rien de mieux qu'une bonne vieille guerre des gangs pour faire marcher l'immobilier, + +[LOVE2_B] +en dehors d'une bonne épidémie... mais ça serait peut-être un peu trop. + +[LOVE2_C] +J'ai remarqué que les Yakuzas et les Colombiens n'étaient pas très copains. + +[LOVE2_D] +C'est une opportunité que nous devons saisir! + +[LOVE2_E] +Je veux que tu tues le waka-gashira des Yakuzas, Kenji Kasen. + +[LOVE2_F] +Kenji est à une réunion au sommet du parking à plusieurs étages de Newport. + +[LOVE2_G] +Procure-toi une voiture du Cartel et tue-le! + +[LOVE2_H] +Les Yakuzas doivent accuser le Cartel de leur déclarer la guerre. + +[LOVE2_1] +~g~Va à Fort Staunton et vole une voiture du gang des Colombiens. + +[LOVE2_2] +~g~Maintenant, rends-toi au ~p~parking de Newport~g~ et élimine Kenji. + +[LOVE2_3] +~r~Si tu n'as pas de voiture du Cartel, tu seras démasqué! + +[LOVE2_4] +~r~Les Yakuzas t'ont reconnu! + +[LOVE2_6] +~r~Tu as tué tous les témoins! + +[LOVE3_A] +De nos jours, certaines marchandises de valeur sont difficiles à importer. + +[LOVE3_C] +Plusieurs paquets vont être jetés à l'eau. + +[LOVE3_D] +Récupère-les avant tout le monde. + +[LOVE3_1] +~g~Procure-toi un ~r~bateau~g~ et suis l'~y~avion~g~! + +[LOVE4] +'VOL AU VENT' + +[LOVE5] +'ACCORTE ESCORTE!' + +[LOVE4_A] +Merci beaucoup pour ces paquets, mais ils n'étaient qu'un leurre! + +[LOVE4_B] +Je suis désolé pour ça, mais c'est quelque fois comme ça dans les affaires. + +[LOVE4_C] +Mon réel objectif se trouvait depuis le début caché à bord de l'avion. + +[LOVE4_F] +J'avais graissé la patte aux autorités. + +[LOVE4_1] +~r~Les Colombiens sont là! + +[LOVE4_2] +~g~Le paquet est parti! Suis les Colombiens et récupère-le! + +[LOVE4_3] +~g~Panlantic construction ? + +[LOVE4_5] +~g~Le paquet devrait être à bord de l'avion... + +[LOVE4_6] +~g~Prends l'ascenseur pour accéder au sommet de la tour! + +[LOVE5_B] +Mon ami oriental a besoin d'une escorte pour l'accompagner pendant l'authentification de ma dernière acquisition. + +[LOVE5_1] +~g~Vas-y! + +[LOVE5_2] +~g~Tu vas avoir besoin d'une caisse! + +[LOVE5_3] +~g~Passe devant et contrôle la sortie du tunnel! + +[LOVE5_4] +~r~Protège le camion! + +[RM6] +'L'HOMME A ABATTRE!' + +[RM6_A] +T'as pas été suivi ? Parfait! + +[RM6_B] +Ca y est! Je suis cuit, déjà mort, en quelque sorte! + +[RM6_D] +J'ai un contrat sur ma tête, alors je me casse! + +[RM6_E] +Amène-moi à l'aéroport et t'auras un bon pourboire! + +[RM6_666] +Prends soin de ma Patriot blindée. On se verra à Miami, Ray! + +[CAT1] +'LA RANCON' + +[CAT2] +'L'ECHANGE' + +[CAT1_A] +C'est moi qui ai Maria. Je pense pas que t'aies l'envie que son visage ressemble à un foie de veau trop faisandé, hein ? + +[CAT2_F] +Je me suis cassé un ongle et ma mise en plis est foutue! Tu le crois, ça ? Ca m'avait coûté 50 dollars! + +[CAT2_G] +J'étais complètement flippée, mais, je me suis dit, Maria, t'es une grande fille maintenant. + +[CAT2_H] +Oh on va bien s'amuser, tu sais, parce que ma soeur, elle a dit qu'elle voulait venir s'installer avec nous, elle et ses deux gosses. + +[CAT2_I] +Son mec a recommencé à aller voir ailleurs et... + +[CAT1_F] +Rattrape Catalina avant la fin du temps imparti! + +[CAT_MON] +~g~T'as pas encore assez d'argent. T'as besoin de 500 000$. + +[BITCH_D] +~g~Maria est morte! + +[WEATHER] +METEO AGITEE + +[WEATHE2] +METEO NORMALE + +[8001] +Tu as échoué lamentablement! + +[1000] +TU ES MORT + +[1001] +TU ES MORT + +[1002] +TU ES MORT + +[1003] +TU ES MORT + +[1004] +TU ES MORT + +[1005] +CAPTURE! + +[1006] +CAPTURE! + +[1007] +CAPTURE! + +[1008] +CAPTURE! + +[1009] +CAPTURE! + +[GA_4] +Une voiture piégée coûte 1000$ pièce! + +[GA_5] +Ta voiture est déjà équipée d'une bombe. + +[GA_6] { re3 change } +Gare-la, arme la bombe avec la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ et CASSE-TOI! + +[GA_7] { re3 change } +Arme la bombe avec la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~. Elle explose au démarrage. + +[GA_8] +Utilise le détonateur pour armer la bombe. + +[GA_9] +Tu as récupéré ~1~ des 10 voitures spéciales. + +[GA_10] +Beau morceau. Voilà tes ~1~$. + +[GA_11] +On en a déjà des comme ça. Ca nous sert à rien! + +[GA_12] +Bombe armée. + +[GA_13] +Comme un pro! Finis la liste et t'auras un bonus! + +[GA_14] +Toutes les voitures! Parfait! Tiens, voilà un petit quelque chose! + +[GA_15] +J'espère que t'aimes la nouvelle couleur! + +[GA_16] +La peinture est en option! + +[GA_19] +Ce modèle, ça nous intéresse pas! + +[GA_20] +On en a trop des comme ça! Désolé, mec. + +[CR_1] +La grue ne peut pas soulever ce véhicule. + +[PU_MONY] +T'as pas assez d'argent. + +[CO_ALL] +Tu les as toutes eues. Tiens, voilà pour toi... + +[PAUSED] +PAUSE + +[HEALTH1] +Sors de là! T'es en pleine forme! + +[HEALTH2] +Coût des soins + +[HEALTH3] +Je vais vous remettre sur pied! + +[HEALTH4] +Ca fera 250$. + +[FEB_STA] +Statistiques + +[FEB_BRI] +Briefings + +[FEB_CON] +Commandes + +[FEB_AUD] +Son + +[FEB_DIS] +Affichage + +[FEB_LAN] +Langue + +[FEP_STA] +STATISTIQUES + +[FEP_BRI] +BRIEFINGS + +[FEP_CON] +COMMANDES + +[FEP_AUD] +SON + +[FEP_DIS] +AFFICHAGE + +[FEP_LAN] +LANGUE + +[FEF_ST1] +C'est qui, le méchant ? + +[FEF_ST2] +T'as foutu un sacré merdier! + +[FEF_BR1] +T'as perdu le fil de l'histoire ? + +[FEF_CO1] +Tu veux prendre les commandes, c'est ça ? + +[FEF_CO2] +Choisis la configuration qui correspond le mieux à ta façon de jouer. + +[FEF_SA1] +Fais la queue! + +[FEF_SA2] +Sauvegarde et charge tes parties + +[FEF_AU1] +Fais péter la sono! + +[FEF_AU2] +Sélectionne une station de radio et des effets sonores + +[FEF_DI1] +Change de partie! + +[FEF_DI2] +Configure le jeu pour ta télé pourrie! + +[FEF_LA1] +Mais qu'est-ce que tu racontes ? + +[FEF_LA2] +Do tu hablar Deutsh ? + +[FEB_PMB] +Briefings de mission précédents: + +[FEC_NA] +Oups! + +[FEC_CWL] +Montrer les armes à gauche + +[FEC_CWR] +Montrer les armes à droite + +[FEC_LOF] +Regarder devant + +[FEC_TAR] +Cible + +[FEC_MOV] +Déplacement + +[FEC_CAM] +Modes caméra + +[FEC_PAU] +Pause + +[FEC_ENV] +Monter dans un véhicule + +[FEC_JUM] +Sauter + +[FEC_ATT] +Attaquer ou tirer + +[FEC_RUN] +Courir + +[FEC_FPC] +Vue subjective + +[FEC_LL] +Regarder à gauche + +[FEC_LB1] +Regarder + +[FEC_LB2] +derrière + +[FEC_LB] +Regarder derrière + +[FEC_LR] +Regarder à droite + +[FEC_HOR] +Klaxon + +[FEC_VES] +Commandes du véhicule + +[FEC_RSC] +Chang. les stations de radio + +[FEC_BRA] +Frein ou marche arrière + +[FEC_HAB] +Frein à main + +[FEC_CAW] +Arme du véhicule + +[FEC_ACC] +Accélérateur + +[FEC_SMT] +Déclencheur de mission spéciale + +[FEA_OUT] +Sortie audio: + +[FEA_ST] +Stéréo + +[FEA_MNO] +Mono + +[FEA_NON] +Aucune + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX 109 + +[FED_DBG] +Menu Debug + +[FEM_MCM] +Menu memory card + +[FEM_RMC] +Enregistrer memory card 1 + +[FEM_TFM] +Test memory card 1 formatée + +[FEM_TUM] +Test memory card 1 non formatée + +[FEM_CRD] +Créer rép. racine + +[FEM_CLI] +Créer et charger des icones + +[FEM_FFF] +Remplir le premier fichier de conneries + +[FEM_SOG] +Sauvegarder uniquement la partie + +[FEM_CES] +Vérifier chaque sauvegarde de 0kb4 + +[FEM_STG] +Sauvegarder la partie + +[FEM_STS] +Sauvegarder la partie sous le nom GTA3 + +[FEM_CPD] +Créer un répertoire protégé contre les copies + +[FEM_MC2] +Menu memory card 2 + +[FEM_TS] +Test de sauvegarde : + +[FEM_TL] +Test de chargement : + +[FEM_TD] +Test de suppression : + +[PL_STAT] +Statistiques du joueur + +[PE_WAST] +Victimes + +[PE_WSOT] +Personnes tuées par d'autres + +[CAR_EXP] +Véhicules détruits + +[TM_BUST] +Nb de fois capturé + +[M_WASTE] +Hommes tués + +[F_WASTE] +Femmes tuées + +[PIG_WST] +Flics tués + +[GNG_WST] +Gangsters tués + +[MED_WST] +Toubibs tués + +[FIRE_WS] +Pompiers tués + +[DED_CRI] +Criminels tués + +[DED_DED] +Parasites tués + +[DED_HOK] +Putes tuées + +[HEL_DST] +Hélicoptères détruits + +[PER_COM] +Pourcentage accompli + +[KGS_EXP] +Kilos d'explosif utilisés + +[ACCURA] +Précision + +[ELBURRO] +Meilleur temps en sec. + +[CAR_CRU] +Véhicules défoncés + +[HED_EX] +Têtes explosées + +[TM_DED] +Visites à l'hôpital + +[DAYSPS] +Jours de jeu + +[MMRAIN] +Mm de pluie tombés + +[MXCARD] +Distance max. de sauts dangereux (pieds) + +[MXCARJ] +Hauteur max de sauts dangereux (pieds) + +[MXCARDM] +Distance max. de sauts dangereux (m) + +[MXCARJM] +Hauteur max de sauts dangereux (m) + +[MXFLIP] +Saltos max. de sauts dangereux + +[MXJUMP] +Rotation max. de sauts dangereux + +[BSTSTU] +Meilleures cascades jusqu'à maintenant: + +[INSTUN] +Cascade dangereuse + +[PRINST] +Cascade dangereuse parfaite + +[DBINST] +Double cascade dangereuse + +[DBPINS] +Double cascade dangereuse parfaite + +[TRINST] +Triple cascade dangereuse + +[PRTRST] +Triple cascade dangereuse parfaite + +[QUINST] +Quadruple cascade dangereuse + +[PQUINS] +Quadruple cascade dangereuse parfaite + +[NOSTUC] +Aucune cascade dangereuse accomplie + +[NOUNIF] +Sauts uniques accomplis + +[NOUNGM] +Total sauts uniques + +[NMISON] +Missions commencées + +[NMMISP] +Missions réussies + +[PASDRO] +Passagers perdus + +[MONTAX] +Total courses en taxi + +[DAYPLC] +Dépenses quotidiennes de police + +[CRIMRA] +Taux de criminalité + +[GMSTOR] +Magasin du jeu + +[PREBRF] +Briefings précédents + +[CNTLS] +Commandes + +[MUSMEN] +Musique/Effets + +[GAMSET] +Paramètres du jeu + +[LANGUA] +Langue + +[DSPLAY] +Affichage + +[DEBUGM] +Menu Debug + +[QUITOP] +Options de fin + +[CONTRL] +Configuration des manettes + +[SET1EN] +Config 1. activée + +[SET1] +Config 1. + +[SET2EN] +Config 2. activée + +[SET2] +Config 2. + +[SET3EN] +Config 3. activée + +[SET3] +Config 3. + +[SET4EN] +Config 4. activée + +[SET4] +Config 4. + +[GOBACK] +Retour + +[SOUND] +AUDIO + +[MUSVOL] +Volume de la musique + +[SFXVOL] +Volume des effets + +[SCROPT] +OPTIONS D'AFFICHAGE + +[CTRSCR] +Centrer l'écran + +[SCRFOR] +Format d'écran + +[GMSVLQ] +SAUVEGARDER-CHARGER-QUITTER + +[GMREST] +Recommencer + +[NOGMSV] +Tu ne peux sauvegarder que depuis ta planque. + +[DLFILE] +Supprimer les fichiers de GTA3 + +[CHFILE] +CHOISIR LE FICHIER A CHARGER + +[CHFIDL] +CHOISIR LE FICHIER A SUPPRIMER + +[SVCONF] +CONFIRMER SAUVEGARDE + +[SVFNAM] +Le nom du fichier de sauvegarde est + +[SAVEDN] +Erreur. Echec de la sauvegarde. + +[LANGSL] +CHOIX DE LA LANGUE + +[ENGLIS] +Anglais + +[GERMAN] +Allemand + +[ITALIA] +Italien + +[FRENCH] +Français + +[SPAIN] +Espagnol + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[PRVMEN] +Briefings de mission précédents + +[DOSVGM] +Tu veux sauvegarder la partie ? + +[FORMEN] +Menu formatage + +[MEMTST] +Ecran de test memory card + +[REGCAR] +Enregistrer memory card 1 + +[TEFONE] +Test memory card 1 formatée + +[TEUFON] +Test memory card 1 non formatée + +[CRROOT] +Créer rép. racine + +[CRLDIC] +Créer et charger des icones + +[FLFSGF] +Remplir le premier fichier de conneries + +[PUSAVE] +Sauvegarder uniquement la partie + +[CHEVOK] +Vérifier chaque sauvegarde de 0kb4 + +[SVGMON] +Sauvegarder + +[CNTSAV] +Sauvegarde impossible. Tu es en mission. + +[CNCSAV] +Sauvegarde impossible. Tu es en voiture. + +[CRMGSV] +Créer un répertoire protégé contre les copies + +[MGSVCN] +Répertoire créé + +[MGSVNC] +Répertoire non créé + +[YES] +Oui + +[NO] +Non + +[X] +x + +[LAST] +Dernier message. + +[FEDS_XB] +Choisir + +[FEDS_ST] +touche START - REPRENDRE + +[FEST_OO] +sur + +[FEC_TUC] +Commandes tourelle + +[FEC_SM3] +Déclencheur mission spéciale (touche R3) + +[FEC_RS3] +Chang. les stations de radio (touche L3) + +[FEC_HO3] +Klaxonner (touche L3) + +[DIAB1] +'TOURISME' + +[DIAB2] +'DE LA GLACE, T'ES REFROIDI' + +[DIAB3] +'JUGÉ PAR LE FEU' + +[DIAB4] +'STAR DU X' + +[DIAB1_A] +El Burro veut te donner une chance. Va à la cabine téléphonique de Hauteurs de Hepburn si tu veux plus d'infos. + +[DIAB1_C] +Tu t'es bien défendu. Retourne à la cabine et El Burro aura peut-être du boulot pour toi. + +[DIAB1_1] +~g~3..2..1..GO GO GO! + +[DIAB1_4] +~g~Trouve une voiture rapide et va sur la grille de départ. + +[DIAB1_3] +~r~Tu ne pourrais même pas gagner à une tombol, blaireau! + +[DIAB1_2] +~g~Félicitations tu as gagné, avec un temps incroyable de ~1~ secondes + +[FIRST] +~g~1er + +[SECOND] +~g~2e + +[THIRD] +~g~3e + +[FOURTH] +~g~4e + +[DIAB2_1] +~g~Va prendre la malette à Harwood. + +[DIAB2_2] +~g~Trouve une camionnette de vendeur de glaces. + +[DIAB2_3] +~g~Gare la camionette à Atlantic Quays. + +[DIAB2_4] +~g~Appuie sur la ~w~touche ~k~~VEHICLE_HORN~ ~g~pour actionner la clochette. + +[DIAB2_6] +~g~Appuie sur la ~w~touche ~k~~VEHICLE_HORN~ ~g~pour actionner la clochette. + +[DIAB2_7] +~g~Appuie sur la ~w~touche ~k~~VEHICLE_HORN~ ~g~pour actionner la clochette. + +[DIAB2_5] +~g~Sors de la camionette et sers-toi de la télécommande pour faire exploser la camionnette. + +[YD1] +'LES FOUS DU VOLANT' + +[YD2] +'TOUS A L'UZI' + +[YD3] +'LA VALSE DES VOITURES' + +[YD4] +'A NOUS LE ROYAUME' + +[YD_P] +King Courtney voudrait te dire un mot. Va à la cabine d'Aspatria! + +[YD1_A] +~w~Voila King Courtney. + +[YD1_A1] +~w~Mon gang de Yardies aurait besoin d'un bon chauffeur et t'as une réputation de rapide. + +[YD1_B] +~w~Va à la décharge en face du stade et attends les autres joueurs. + +[YD1_C] +~w~J'ai des gars qui surveillent tous les points de passage de Staunton. + +[YD1_D] +~w~Le premier pilote qui franchit un point de passage gagne 1000$ et ainsi de suite. + +[YD1_D1] +~w~Si tu passes plus de checkpoints que tous les autres pilotes, j'aurais peut-être du boulot pour toi. + +[YD1_E] +~g~Prépare-toi à partir! + +[YD1_F] +~g~T'as foncé dès le départ. J'adore ton style! + +[YD1_G] +~r~C'est une course de bagnoles. T'as besoin d'une bagnole, patate! + +[YD1GO] +~g~GO! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +1000$! + +[Y1_1ST] +~g~T'es arrivé premier en franchissant ~1~ points de passage! + +[Y1_2ND] +~y~2e avec ~1~ points de passage gagnants. ~y~Pas mal, mais t'es pas le meilleur. + +[Y1_3RD] +~r~3e avec ~1~ points de passage gagnants. ~r~Je croyais que t'étais bon! + +[Y1_LAST] +~r~Tu es le dernier! ~r~Tu m'as fait perdre mon temps, imbécile! + +[Y1_J1ST] +~y~1er ex aequo avec ~1~ points de passage gagnants. ~y~C'est bien, mais tu dois être le meilleur pour courir sur Queen lizzy! + +[Y1_J2ND] +~r~2e ex aequo avec ~1~ points de passage gagnants. Tu conduis comme un vieux singe dingo! + +[Y1JLAST] +~r~Dernier ex æquo! Tu parles comme un pilote, mais tu pilotes comme un beau-parleur! + +[Y1_TEST] +VOITURE A L'EAU! + +[YD2_A] +~w~J'ai besoin de voir si tu peux faire mon sale boulot. + +[YD2_A1] +~w~Faut voir si on peut te faire confiance. + +[YD2_B] +~w~Deux de mes gars seront là-bas dans peu de temps pour te faire faire un tour, + +[YD2_B1] +~w~histoire de voir si tu vaux quelque chose... + +[YD2_C] +~w~On va aller faire un tour à Hauteurs de Hepburn, flingue-nous quelques Diablos qui font des misères à Queen Lizzy. + +[YD2_CC] +~w~Tiens, t'auras besoin d'un calibre. + +[YD2_D] +~w~Tu conduis et tu tires. On s'arrangera pour que tu te fasses pas descendre. + +[YD2_E] +~w~Allons-y!! + +[YD2_F] +~r~Il nous a échappé, colle-lui au cul!!! + +[YD2_G1] +~w~Hauteurs de Hepburn... On va se farcir quelques Diablos de malheur... + +[YD2_G2] +~w~Mais n'oublie pas, ~r~tu restes dans la bagnole!! + +[YD2_H] +~w~OK, ramène-nous sur le territoire des Yardies! Allez, FONCE! + +[YD2_L] +~w~Tu t'es bien débrouillé, le bourreau. + +[YD2_M] +~r~Il a bousillé ma bagnole! Descends-le! + +[YD2_N] +~w~Ramène ta fraise dans cette bagnole! + +[YD3_A] +Je veux que tu piques quelques bagnoles de gangs + +[YD3_A1] +comme ça, on pourra faire un carton chez nos ennemis. + +[YD3_B] +Je veux une Sentinelle de la Mafia, + +[YD3_B1] +une Stinger des Yakuzas et une + +[YD3_B2] +Stallion des Diablos comme ça, on pourra frapper dans tous les gangs de Liberty. + +[YD3_C] +Tu les amènes au garage de Newport et souviens-toi, + +[YD3_C1] +elles ne nous sont utiles qu'en bon état! + +[YD3_D] +Spare text label + +[YD3_E] +~r~Tu as déjà piqué une voiture des Diablos! + +[YD3_F] +~r~Tu as déjà piqué une voiture de la Mafia! + +[YD3_G] +~r~Tu as déjà piqué une voiture des Yazukas! + +[YD3_H] +~g~Bagnole des Diablos chourrée! + +[YD3_I] +~g~Bagnole de la Mafia chourrée! + +[YD3_J] +~g~Bagnole des Yasukas chourrée! + +[YD3_K] +~r~La bagnole est presque une épave! Fais-la réparer! + +[YD3_L] +Emmène-la au garage! + +[YD3_M] +~r~T'as fait un tonneau avec! Va en chercher une autre! + +[YD4_A] +Ecoute-moi bien! + +[YD4_A1] +Tu vas aller à Bedford Point. + +[YD4_A2] +Il y a de la came dont j'ai besoin rapidos dans une vieille bagnole! + +[YD4_B] +LETTRE : je sais que tu es très occupé. Eh bien moi aussi, je suis très occupée. + +[YD4_C] +Je pense qu'il est temps que tu te rendes compte de ce que c'est, de la SPANK! Catalina, Bisous. + +[YD4_D] +PS : CREVE SALE CHIEN, CREVE! + +[YD4_1] +Des drogués défoncés ! + +[YD4_2] +Détruis les camionnettes de SPANK !! + +[HM_1] +'DESCENTE A L'UZI' + +[HM_2] +'BUGGY FAIT DES RAVAGES' + +[HM_3] +'VOITURE PIEGEE' + +[HM_5] +'LA RIXE' + +[HOOD1_A] +Va à la cabine dans Wichita Gardens et on parlera affaires. + +[HM1_A] +Salut! C'est D-Ice des Red Jacks! + +[HM1_C] +Ces jeunes cons, ils viennent dans la rue et ils ont qu'une idée en tête, la SPANK et les flingues. + +[HM1_3] +~g~Les 'Nines' font leur business dans Wichita Gardens. + +[HM2_3] +Si tu touches les roues d'une bagnole, le buggy télécommandé explosera! + +[HM2_4] +Si le buggy télécommandé est hors de portée, il explosera! + +[HM2_5] +~r~Hors de portée! + +[HM3_1] +~g~Va au garage mais fais gaffe, si tu brusques trop la bagnole, elle va exploser! + +[HM3_2] +~g~Ramène la voiture en parfait état, pas de grabuge! + +[HM3_3] +~g~Fais réparer la voiture! + +[HM4_D] +~g~Trouve une caisse! + +[HM4_E] +TEXT NO LONGER REQUIRED + +[HM4_1] +~g~Va à l'endroit où la cargaison est tombée, tu dois ramasser 30 lingots. + +[HM4_2] +~g~Souviens-toi que quand la bagnole est trop chargée, elle se traîne alors quand c'est le cas, fonce au garage pour livrer la marchandise. + +[HM5_3] +~r~On t'a dit de te servir d'une batte de baseball et c'est tout! + +[HM5_4] +~r~Ton contact est mort! + +[MEA1] +'L'ESCROC' + +[MEA2] +'LES BRAQUEURS' + +[MEA3] +'LA FEMME' + +[MEA4] +'SON AMANT' + +[MEAT1_A] +Un ami m'a dit que tu pouvais régler certains de mes problèmes. Si tu penses que tu peux m'aider, va à la cabine de Trenton. + +[MEA1_B3] +~g~Va voir le banquier. + +[MEA1_B6] +~g~Amène la voiture à la casse pour se débarrasser des preuves. Une fois sur place, sors de la voiture et laisse faire la grue! + +[MEA1_1] +~r~Le banquier est mort! + +[MEA1_2] +~r~On t'a dit d'écrabouiller la voiture! + +[MEA1_3] +~g~Sors de la bagnole! + +[MEA1_4] +~r~T'as laissé le banquier derrière toi! + +[MEA2_B3] +~g~Va voir les braqueurs. + +[MEA2_B4] +~g~Amène-les à l'usine de pâtée pour chiens. + +[MEA2_B6] +~g~Fais repeindre la voiture, comme ça y'aura pas de preuve. + +[MEA2_1] +~r~On t'a dit d'écrabouiller la voiture! + +[MEA2_2] +~r~Un braqueur est mort! + +[MEA2_4] +~r~T'as laissé un des braqueurs derrière toi! + +[MEA3_B3] +~g~Va chercher Mme Chonks. + +[MEA3_B6] +~g~Prends la voiture et fous-la à l'eau, comme ça y'aura pas de preuve. + +[MEA3_1] +~r~La femme est morte! + +[MEA3_2] +~r~Tu étais censé balancer la bagnole à la mer! + +[MEA3_3] +~r~T'as laissé sa femme derrière! + +[MEA4_B3] +~g~Va chercher l'amant de sa femme. + +[MEA4_B6] +C'est beaucoup trop tard, Marty. Je t'ai donné une chance, mais maintenant je reprends tout en charge! + +[MEA4_1] +~r~Carlos est mort! + +[MEA4_3] +~r~T'as laissé Carlos l'usurier derrière! + +[LOOK_A] +Appuie et maintiens enfoncée la ~h~touche ~k~~VEHICLE_LOOKLEFT~ ~w~ou ~h~touche ~k~~VEHICLE_LOOKRIGHT~ ~w~ pour regarder ~h~à gauche~w~ ou ~h~à droite~w~ quand tu es en voiture. Appuie sur ces deux touches pour regarder ~h~derrière~w~. + +[LOVE6_1] +~g~Maintenant, attire les flics loin de l'entrepôt! + +[LOVE6_2] +~r~T'as pas réussi à attirer les flics assez loin! + +[RM4_3] +~r~L'associé de Ray s'est échappé! + +[RM6_C] +La CIA semble s'intéresser de près à la SPANK. + +[RM6_C1] +Et elle n'aime pas qu'on cherche des noises au Cartel. + +[C_PASS] +MENACE ENRAYEE! + +[CTUTOR] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions de caisse. + +[CTUTOR2] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions de caisse. + +[COPCART] +~g~Tu as ~1~ secondes pour retourner à une voiture de police avant que la mission ne s'achève. + +[C_FAIL] +Mission de caisse terminée! + +[C_CANC] +~r~Mission de caisse annulée! + +[C_ESCP] +~r~Le suspect s'est échappé! + +[C_TIME] +~r~Ta carrière de flic est terminée! + +[C_VIGIL] +BONUS POLICE! + +[A_FAIL2] +~r~Ton manque de précipitation a été fatal pour le patient! + +[A_FAIL3] +~r~Le patient est mort! + +[A_PASS] +Sauvé! + +[F_FAIL2] +~r~T'arrives trop tard! + +[A_COMP2] +T'en as jamais assez ? + +[RM2_M] +Si tu as besoin d'artillerie, passe prendre ce dont t'as besoin dans les armoires. + +[HEAL_A] +Ton niveau de ~h~santé~w~ s'affiche en orange en haut à droite de l'écran. + +[YD1_CNT] +~1~ sur 15! + +[FM1_9] +~g~C'est la fête un peu plus haut. Dépose Maria devant. + +[FM1_Y] +~w~Ça faisait longtemps que je m'étais pas amusée comme ça et tu m'as traitée vraiment bien ... avec respect et tout. + +[FM1_AA] +~w~Oh, faut que j'y aille, à bientôt j'espère. + +[NOCONTE] +Reconnecte la manette analogique (DUALSHOCK#) ou manette analogique (DUALSHOCK#2) au port de manette 1 pour continuer. + +[WRCONT] +La manette connectée au port de manette 1 n'est pas une manette compatible. GTA3 nécessite une manette analogique (DUALSHOCK#) ou manette analogique (DUALSHOCK#2). + +[WRCONTE] +La manette connectée au port de manette 1 n'est pas une manette compatible. GTA3 nécessite une manette analogique (DUALSHOCK#) ou manette analogique (DUALSHOCK#2). + +[WRONGCD] +Disque invalide. Veuillez insérer le bon disque. + +[NOCD] +Le compartiment à disque est vide. Veuillez insérer le disque. + +[OPENCD] +Le compartiment à disque est ouvert. Veuillez refermer le compartiment à disque. + +[CDERROR] +Erreur de lecture du DVD de GTA3 + +[RESTART] +Démarrage d'une nouvelle partie + +[GA_3] +Plus de cadeaux. 1000$ pour repeindre! + +[GA_1] +Oula! Je ne touche à rien d'aussi chaud, moi! + +[GA_1A] +Reviens quand tu seras moins occupé... + +[S_PROM2] +Le garage d'à côté peut garder une voiture quand tu sauvegardes la partie. + +[STOCK] +Stock épuisé + +[FM1_O] +~w~Je pense qu'il est à la gare sur le bord de mer de Chinatown. + +[EBAL_B] +Tiens c'est là! Allez, on va se garer et on va trouver de nouvelles fringues! + +[EBAL_G] +Ça, c'est la boîte de Luigi. Viens, on va rentrer par la porte de derrière. + +[AM4_3] +T'es le nouveau coursier d'Azuka! + +[AM4_4] +T'as le fric ? Le compte y est ? + +[AM4_5] +Je sais ce que tu penses, encore un ripoux. + +[AM4_6] +Ben, le monde est pourri. + +[AM4_7] +Parce que j'ai perdu quelques équipiers, ces enfoirés de poulets commencent à renifler partout. + +[AM4_8] +J'imagine qu'ils peuvent me sentir. + +[AM4_9] +Et oui, cette ville est un vrai dépotoir. + +[AM4_10] +Mais je vais avoir besoin d'aide. + +[AM4_11] +Si ça t'intéresse, tu sais où me trouver. + +[CAM_A] +Appuie sur la ~h~touche ~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ pour changer les modes ~h~caméra ~w~quand tu es à pied ou en voiture. + +[CAM_B] +~w~Appuie sur la ~h~touche directionnelle haut~w~ ou ~h~bas~w~ pour changer les modes ~h~caméra~w~ quand tu es à pied ou en voiture. + +[KM2_1] +~g~Répare la voiture, elle doit être comme neuve. + +[LM3_6] +Joey... + +[LM3_6A] +Est-ce que je vais jouer avec ton gros canon ? + +[LM3_9A] +Y'a peut-être du travail pour toi. + +[LM3_9B] +D'accord ? + +[AWAY2] +~r~Ils se sont enfuis. + +[AWAY] +~r~Il s'est barré d'ici! + +[JM6_1] +Va à la banque par la rue principale. + +[GA_6B] { re3 change } +Tu la gares, tu l'amorces en appuyant sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ et tu te barres! + +[GA_7B] { re3 change } +Pour armer la bombe, appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~. Elle explosera au démarrage. + +[BAT1] +~g~Ramasse la batte! + +[EBAL_O] +Si t'es réglo, j'ai du travail pour toi. Et maintenant, casse-toi! + +[HELP9_B] +Appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~tirer~w~ avec le fusil à lunettes. + +[HELP9_C] +Appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~tirer~w~ avec le fusil à lunettes. + +[JM6_8] +~r~T'as semé tous les braqueurs! + +[COLT_IN] +Le pistolet est disponible au Ma-Gnum. + +[TAXI2] +~r~Tu n'as plus le temps! + +[TAXI3] +~r~Ton client est parti terrorisé! + +[TAXI7] +~r~Ta voiture est endommagée, fais-la réparer. + +[TAXI4] +La course est finie! + +[TAXI5] +BONUS DE VITESSE! + +[TAXI6] +La mission Taxi est finie. + +[FRANGO] +~g~Salvatore veux que tu aides d'abord Toni à régler son affaire avec la Triade! + +[PAGEB12] +Pot-de-vin des flics livré à la planque + +[PAGEB13] +Santé livrée à la planque + +[PAGEB14] +Adrénaline livrée à la planque + +[KM1_4] +~g~T'as besoin d'une bagnole de flic pour ce job! + +[CAT1_B] +Apporte 500 000$ à la Villa de Cedar Grove. + +[JM2_C] +Il a un stand de nouilles dans Chinatown. + +[RM6_1] +Voilà la clé d'un coffre. + +[RM6_2] +T'y trouveras du cash et des provisions. J'avais mis ça de coté au cas où les choses tourneraient mal. + +[RM6_3] +A plus. + +[FE_INIP] +Initialisation et chargement du menu pause... Veuillez patienter. + +[FESZ_CA] +Annuler + +[FESZ_QU] +Quitter + +[FESZ_L1] +Partie sauvegardée avec succès! + +[FESZ_L2] +Le nom du fichier sauvegardé est : + +[FESZ_OK] +OK + +[FES_NGA] +Nouvelle partie + +[FES_CAN] +Annuler + +[FESZ_QL] +Toutes les étapes non sauvegardées de cette partie seront perdues. Charger la partie? + +[FESZ_QD] +Effacer cette sauvegarde? + +[FESZ_QO] +Ecraser cette sauvegarde? + +[FESZ_QR] +Veux-tu vraiment commencer une autre partie? Toutes vos données depuis la dernière partie sauvegardée seront perdues. Continuer? + +[FESZ_QS] +PASSEZ EN SAUVEGARDE? + +[SLONFP] +Port 1 Fichier Protégé. + +[T4X4_1] +'TERRAIN DE JEU DU PATRIOT' + +[T4X4_2] +'UN TOUR DANS LE PARC' + +[T4X4_3] +'ADHÉRENCE!' + +[MM_1] +'GAZ À TOUS LES ÉTAGES' + +[T4X4_1A] +~g~T'as ~y~5 minutes~g~ pour franchir ~y~15~g~ points de passage. + +[T4X4_1B] +~1~ sur 15! + +[T4X4_1C] +~g~Chaque point de passage te rapportera ~y~20 secondes~g~. + +[T4X4_2A] +~g~Tu as ~y~2 minutes~g~ pour franchir ~y~12~g~ points de passage! ~g~Tu peux les franchir dans ~y~n'importe quel ordre. + +[T4X4_2B] +~1~ sur 12! + +[T4X4_2C] +~y~Passe le premier point de passage pour déclencher le chrono. ~y~ Chaque point de passage te rapportera ~y~10 secondes~g~. + +[T4X4_3A] +~g~Tu as ~y~5 minutes~g~ pour franchir ~y~20~g~ points de passage. ~g~Tu peux les franchir dans ~y~n'importe quel ordre. + +[T4X4_3B] +~y~Franchis~g~ le premier point de passage pour déclencher le chrono. ~g~Chaque point de passage te rapportera ~y~15 secondes~g~. + +[T4X4_3C] +~1~ sur 20! + +[T4X4_F] +~r~Tu t'es barré! Trop dur pour toi ?! + +[MM_1_A] +~g~Tu as ~y~2 minutes~g~ pour franchir les ~y~20 points de passage~g~ dans le parking à plusieurs étages! ~g~Tu peux les franchir dans n'importe quel ordre. + +[MM_1_B] +~1~ sur 20! + +[MM_1_C] +~g~Ca fait 20 secondes, plus ~y~5 secondes~g~ pour chaque point de passage. ~g~Le chrono commence ~y~immédiatement. + +[FM2_14] +~r~Tu t'es trop approché et Bill t'a vu! + +[FM2_15] +~g~Ne t'approche pas trop ou Bill va se douter de quelque chose! + +[UPSIDE] +~r~Tu t'es retourné! + +[FM2_16] +BALANÇOMETRE : + +[LM3_11] +~g~Misty ne montera pas dans un bus, trouve un autre véhicule! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinelle + +[PATRIOT] +Patriot + +[FIRETRK] +Camion de pompier + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulance + +[FBICAR] +F.B.I. + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +M. Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Police + +[ENFORCR] +Enforcer + +[SECURI] +Sécuricar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[TRAIN] +Train + +[HELI] +Hélicoptère + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +RC Bandit + +[BELLYUP] +Triad + +[MRWONGS] +M. Wongs + +[MAFIACR] +Mafia + +[YARDICR] +Yardie + +[YAKUZCR] +Yakuza + +[DIABLCR] +Diablo + +[COLOMCR] +Cartel + +[HOODSCR] +Hoods + +[AEROPL] +Avion + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Distance parcourue à pied (miles) + +[FEST_DC] +Distance parcourue en voiture (miles) + +[FESTDFM] +Distance parcourue à pied (m) + +[FESTDCM] +Distance parcourue en voiture (m) + +[FEST_R1] +Terrain de jeu du Patriot en secondes + +[FEST_R2] +Un tour dans le parc en secondes + +[FEST_R3] +Adhérence en secondes + +[FEST_RM] +Gaz à tous les étages en secondes + +[FEST_LS] +Nbre de personnes sauvées dans l'ambulance + +[FEST_CC] +Criminels tués dans la mission de police + +[FEST_FE] +Nombre total de feux éteints + +[FEST_LF] +Vol le plus long en Dodo + +[FEST_BD] +Meilleur temps pour désamorcer la bombe + +[FEST_RP] +Rodéos réussis + +[FEST_MP] +Missions réussies + +[FEST_BB] +Les fous du volants: + +[FEST_H0] +La plupart des points de passage + +[FEST_GC] +Nombre total de voitures de gang: + +[FEST_H1] +Destruction de Diablo + +[FEST_H2] +Massacre de la Mafia + +[FEST_H3] +Le désastre du casino + +[FEST_H4] +Le destructeur de Rumpo + +[USJI1] +TEXT NO LONGER REQUIRED + +[USJI2] +TEXT NO LONGER REQUIRED + +[USJI3] +TEXT NO LONGER REQUIRED + +[USJ] +BONUS POUR CASCADE UNIQUE! + +[SPRAY] +Amène ton véhicule à l'atelier de peinture pour annuler ton ~h~indice de recherche~w~, ~h~répare ~w~et ~h~repeins ~w~ton véhicule. Coût -~h~ 1000$. + +[HM1_1] +~G~Refroidis 20 Nines violets en 2 minutes et 30 secondes. + +[KM1_8A] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour ~h~activer la bombe~w~, n'oublie pas de t'éloigner. + +[KM1_8D] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour ~h~activer la bombe~w~, n'oublie pas de t'éloigner. + +[KM1_12] +~g~Amène-le au dojo mais débarrasse-toi des flics d'abord! + +[RATNG1] +Pickpocket + +[RATNG2] +Rascal + +[RATNG3] +Voyou + +[RATNG4] +Prostituée + +[RATNG5] +Idiot + +[RATNG6] +Pilote + +[RATNG7] +Gros Bras + +[RATNG8] +Réparateur + +[RATNG9] +Associé + +[RATNG10] +Nettoyeur + +[RATNG11] +Assassin + +[RATNG12] +Bras droit + +[RATNG13] +Exécutant + +[RATNG14] +Capo + +[RATNG15] +Patron + +[1010] +~r~Ta bagnole est sur le toit + +[1011] +~r~Ta bagnole est sur le toit + +[1012] +~r~Ta bagnole est sur le toit + +[1013] +~r~Ta bagnole est sur le toit + +[1014] +~r~Ta bagnole est sur le toit + +[JM4_10] +OK, Petit. Emmène-moi d'abord à la laverie de Chinatown, j'ai une affaire à régler. + +[JM4_11] +Cette petite laveuse ne paie pas pour sa protection. + +[JM4_12] +Et fais gaffe à la bagnole, Joey vient juste de la faire réparer. + +[JM4_13] +Alors pas de conneries, ok ? + +[KM4_11] +~g~Ramène la monnaie au casino! + +[FEF_BR2] +Retrouve-les en lisant les précédents briefings de missions. + +[TRAIN_1] +Station Kurowski + +[TRAIN_2] +Station Rothwell + +[TRAIN_3] +Station Baillie + +[SUBWAY1] +Station Portland + +[SUBWAY2] +Station Rockford + +[SUBWAY3] +Station Staunton South + +[SUBWAY4] +Terminal Shoreside + +[MEA4_2] +~r~Marty Chonks est mort! + +[SPRAY1] +Amène ton véhicule à l'atelier de peinture pour annuler ton ~h~indice de recherche~w~, ~h~répare ~w~et ~h~repeins ~w~ton véhicule. Coût -~h~ 1000$. Pour cette fois, c'est gratos. + +[JM4_A] +Ouais, je sais Tony, je l'ai défoncée en douceur. Elle miaule, tu vois ce que je veux dire ? + +[JM4_5] +Reviens plus tard et on leur donnera de la lessive à faire, leurs propres fringues avec leur sang dessus! + +[AMMU_A] +Luigi m'a dit que t'as besoin d'un calibre... + +[AMMU_B] +Joey m'a dit de t'équiper... + +[AMMU_C] +Alors tu vas aller derrière le magasin. Je t'ai laissé un 9mm dans le jardin. + +[AMMU_D] +J'ai tout ce qu'il faut en matière de sécurité. + +[AMMU_E] +Tu veux un permis aussi ? + +[AMMU_F] +J'ai pas besoin de tes papiers d'identité. Je pense qu'on peut te faire confiance. + +[DETON] +DETONATION : + +[DRIVE_A] { re3 change } +Selectionne un Uzi quand tu montes dans la voiture, regarde à gauche ou à droite et appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer. + +[DRIVE_B] { re3 change } +Selectionne un Uzi quand tu montes dans la voiture, regarde à gauche ou à droite et appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer. + +[RECORD] +~g~NOUVEAU RECORD! + +[NRECORD] +~r~PAS DE RECORD! + +[RCHELP] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ ou heurte une roue de voiture avec le véhicule télécommandé pour le faire exploser. + +[RCHELPA] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ ou heurte une roue de voiture avec le véhicule télécommandé pour le faire exploser. + +[RC_1] +Tu as 2 minutes pour faire péter autant de voitures de Diablo que tu peux! + +[RC_2] +Tu as 2 minutes pour faire péter autant de voitures de Mafia que tu peux! + +[RC_3] +Tu as 2 minutes pour faire péter autant de voitures de Yasuka que tu peux! + +[RC_4] +Tu as 2 minutes pour faire péter autant de voitures de Yardie que tu peux! + +[RC_5] +Tu as 2 minutes pour faire péter autant de voitures des Hoods que tu peux! + +[RC_6] +Tu as 2 minutes pour faire péter autant de voitures du Cartel que tu peux! + +[RAMPAGE] +RODEO! + +[RAMP_P] +RODEO REUSSI! + +[RAMP_F] +RODEO RATE + +[PAGE_00] +.. + +[PAGE_01] +Elimine ~1~ Diablos en 120 secondes! + +[PAGE_02] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_03] +Tue ~1~ Mafia en 120 secondes! + +[PAGE_04] +Tue ~1~ Triades en 120 secondes! + +[PAGE_05] +Tue ~1~ Triades en 120 secondes! + +[PAGE_06] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_07] +Dégomme ~1~ têtes de Yardies en 120 secondes! + +[PAGE_08] +Brûle ~1~ Yakuzas en 120 secondes! + +[PAGE_09] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_10] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_11] +Bute-moi ~1~ Yardies en 120 secondes! + +[PAGE_12] +Flambe ~1~ Yakuzas en 120 secondes! + +[PAGE_13] +Explose ~1~ Yardies en 120 secondes! + +[PAGE_14] +Grille-moi ~1~ Colombiens en 120 secondes! + +[PAGE_15] +Eclate ~1~ Hoods en 120 secondes! + +[PAGE_16] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_17] +Eclate ~1~ Colombiens avec une voiture en 120 secondes! + +[PAGE_18] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_19] +Fais sauter ~1~ têtes de Colombiens en 120 secondes! + +[PAGE_20] +Décapite ~1~ Hoods en 120 secondes! + +[JM1_A] +Hé, je m'fais chier, quand est-ce que tu me sautes ? + +[JM1_B] +Ça va pas tarder mon coeur, je dois juste m'occuper d'un truc. + +[JM1_C] +J'ai un petit boulot pour toi, mon gars. + +[JM1_D] +Les frères Forelli me doivent du fric depuis trop longtemps + +[JM1_E] +et ils ont besoin qu'on leur apprenne un peu ce que c'est que le respect. + +[JM1_F] +Forelli Grosses Babines est en train de s'empiffrer au Bistro de St. Marks, + +[JM1_G] +alors tu lui prends sa tire et tu l'amènes à l'atelier de 8-Ball, à Harwood. + +[JM1_H] +Tu connais 8-Ball, pas vrai ? + +[JM1_I] +Une fois qu'il l'a truffée avec une bombe, tu remets la voiture où tu l'as trouvée. + +[JM1_J] +Et puis tu t'assois et tu regardes le spectacle. + +[JM1_K] +Mais grouille, il va mettre trois plombes à bouffer. + +[CAT2_A1] +Viens là, petite pute! + +[CAT2_A] +La question est : tu es venu pour sauver Maria ou pour me récupérer ? + +[CAT2_B] +Eh bien, j'ai des infos pour toi, + +[CAT2_B2] +te tuer sera un vrai plaisir mais sortir avec toi faisait partie du business. + +[CAT2_C] +Tu estas muy peccino amigo! + +[CAT2_D] +Lance-moi l'argent + +[CAT2_E] +T'as été très occupé! + +[CAT2_E2] +Mais tu n'as rien appris, on ne peut pas me faire confiance. + +[CAT2_E3] +Descends cet idiot. + +[CAT2_J] +Fais-moi voler ça! + +[HM5_1] +Salut. D-Ice a dit que tu venais. Y'a des règles : juste des battes, pas de flingue, pas de bagnole. + +[HM5_5] +On se bat pour le respect, tu piges ? + +[HELP14] +Pour ramasser les armes, marche dessus. Tu ne peux pas en récupérer depuis un véhicule. + +[CRUSH] +Gare-toi sur l'emplacement indiqué et sors de la voiture. La voiture sera alors compressée. + +[DIAB2_B] +Un gang de bons à rien m'a menacé de me couper mon outil de travail si je ne leur paye pas un impôt. + +[DIAB2_C] +Ils ont menacé le mauvais gars, amigo. + +[DIAB2_D] +Je sais qu'ils ont un faible pour les glaces. + +[DIAB2_E] +Va chercher la bombe que j'ai planquée à Harwood + +[DIAB2_F] +et pique la camionnette du vendeur de glaces pendant sa tournée. + +[DIAB2_G] +Attire ces abrutis vers leur destinée fatale avec la clochette. + +[DIAB2_H] +Ils se planquent dans un entrepôt à Atlantic Quays. + +[DIAB3_A] +Des prétentieux de la Triade ont volé ma magnifique bagnole la nuit dernière, + +[DIAB3_B] +ils l'ont bousillée et l'ont brûlée. + +[DIAB3_C] +Quelques-uns de mes plus précieux objets étaient dans le coffre : + +[DIAB3_D] +des collectors irremplaçables! + +[DIAB3_E] +J'ai planqué une arme à la lisière de Chinatown. + +[DIAB3_F] +Prends-la et montre à ces vandales de la Triade qu'El Burro est en colère! + +[DIAB3_1] +TUE 25 TRIADES + +[DIAB4_A] +Un petit malin a piqué un camion plein de mes dernières publications fraîchement imprimées. + +[DIAB4_B] +Mais cet idiot camé à la Spank a laissé les portes de la camionette ouvertes + +[DIAB4_C] +et toutes les belles photos + +[DIAB4_D] +de ma dernière production pour adultes, sont semées à travers Liberty! + +[DIAB4_E] +Prends le camion et suis la piste des magazines Le Taureau se fait Suelen, volumes 1, 2 et 3. + +[DIAB4_F] +Dès que t'en trouves un, ramasse-le. + +[DIAB4_G] +Quand tu auras retrouvé ce voleur bourré à la Spank, descends-le! + +[DIAB4_H] +Après, tu iras livrer les magazines porno à XXX Mags dans Le Quartier Rouge. + +[DIAB4_1] +~g~Amène la camionnette à l'arrière de XXX Magazines. + +[HM1_E] +Je veux que tu montres à ces assoiffés de sang comment ça marche une vraie descente. + +[HM1_H] +Fais-moi déguerpir tous ces 'Nines'! + +[HM2_A] +Ces Nines me poussent à bout. + +[HM2_B] +Ces salauds ont des voitures blindées et maintenant ils dealent de la SPANK... + +[HM2_C] +et ils poussent à la conso chez nous sans être inquiétés. + +[HM2_D] +Y'a une bagnole garée plus haut. + +[HM2_E] +Y'a des trucs dedans pour mettre ces abrutis hors d'état de nuire... + +[HM3_A] +Y'en a qui ont piégé ma bagnole pour qu'elle saute. + +[HM3_B] +Si je perds ma bagnole, ma réputation est foutue. + +[HM3_C] +Va chercher ma caisse et amène-la au garage en haut de St. Marks, t'as pigé ? + +[HM3_D] +Laisse-les faire, laisse-les s'occuper de la bombe. + +[HM3_E] +L'horloge tourne et le cablage est un vrai bordel. + +[HM3_F] +Un nid de poule de trop et ça peut péter. + +[HM3_G] +Maintenant grouille! + +[HM4_A] +Un vol de la réserve fédérale s'est crashé à l'atterrissage, à l'aéroport Francis. + +[HM4_B] +Y'a du platine partout sur la piste. + +[HM4_C] +Prends une bagnole et va ramasser tout ce que tu peux. + +[HM4_F] +Tu peux balancer les lingots dans un de mes garages. + +[HM4_G] +Ce platine, ça pèse le poids d'un âne mort et ça va ralentir ta bagnole. + +[HM4_H] +Alors fais plusieurs voyages au garage. + +[HM5_A] +Les Nines se sont faits massacrer... + +[HM5_B] +mais ils veulent toujours la guerre. + +[HM5_C] +Ils sont d'accord pour un duel. + +[HM5_D] +Un de leurs gangs contre 2 des nôtres, ou plutôt + +[HM5_E] +deux des vôtres. + +[HM5_F] +J'irais bien mais... + +[HM5_G] +je suis en conditionnelle pour encore trois mois. + +[HM5_H] +Tu vois ce que je veux dire ? + +[HM5_I] +Va voir mon petit frère, + +[HM5_J] +il te montrera où il faut que t'ailles. + +[MEA1_B] +Mon nom est Chonks, Marty Chonks. + +[MEA1_C] +Je dirige l'usine de pâtée pour chiens juste à coté. + +[MEA1_D] +J'ai des problèmes de fric, mais qui n'en a pas, hein ? + +[MEA1_E] +Je dois rencontrer mon banquier plus tard. + +[MEA1_F] +C'est un sale voleur qui arrête pas d'augmenter les mensualités de mon emprunt pour s'en foutre une part dans la fouille. + +[MEA1_G] +Prends ma bagnole, va le chercher et ramène-le ici. + +[MEA1_H] +J'ai une petite surprise pour cette sangsue! + +[MEA2_A] +J'ai engagé des cambrioleurs pour braquer mon appart... + +[MEA2_C] +Ces bâtards menacent de tout dire à la compagnie d'assurance, + +[MEA2_D] +si je les arrose pas plus. + +[MEA2_E] +T'y crois toi ? + +[MEA2_F] +J'ai laissé une voiture dans l'usine. + +[MEA2_G] +Prends-la et va les chercher chez eux dans le Le Quartier Rouge. + +[MEA2_H] +Et puis tu les ramènes à l'usine, comme ça je vais pouvoir leur expliquer le point de vue de Marty. + +[MEA3_A] +si je trouve pas du cash tout de suite, mon affaire va couler. + +[MEA3_B] +Ma femme a une assurance-vie et tout ce qu'elle a jamais fait pour moi, c'est un trou dans mon budget. + +[MEA3_C] +J'ai laissé la voiture à sa place. + +[MEA3_D] +Va checher ma femme chez la manucure Classic Nails et ramène-la à l'usine. + +[MEA4_A] +Putain, je suis dans la merde! + +[MEA4_B] +Ben, ma femme voyait un mec à qui je dois de l'argent. + +[MEA4_C] +Il est est très fâché et lui, tout ce qu'il veut, c'est récupérer son pognon! + +[MEA4_E] +Il pense que je vais le rembourser... + +[MEA4_F] +mais moi je pense plutôt que... + +[MEA4_G] +les chiens de Liberty vont devoir s'habituer à une nouvelle pâtée ce mois-ci! + +[WELCOME] +BIENVENUE A + +[HM1_2] +~g~Trouve une bagnole et souviens-toi que seuls les meurtres à l'Uzi sont pris en considération! + +[HELP8_B] +Appuie sur la~h~ touche ~k~~PED_SNIPER_ZOOM_IN~ ~w~pour faire un ~h~zoom avant ~w~avec la lunette et sur la ~h~touche ~k~~PED_SNIPER_ZOOM_OUT~~w~ pour faire un ~h~zoom arrière~w~. + +[LRQC_1] +Asuka et moi, on doit parler, hein, + +[LRQC_2] +alors va faire un tour, ok ? + +[LRQC_3] +T'auras besoin d'un endroit pour te planquer. + +[LRQC_4] +Il y a un entrepôt au bord de Belleville qui te conviendra. + +[LRQC_5] +Reviens ici dans mon Condo quand tu es prêt + +[LRQC_6] +et on aura une petite conversation. + +[JM6_5] +~g~Il te faut une caisse pour t'enfuir, imbécile! + +[JM2_F] +Si t'as besoin d'un calibre, va derrière le AmmuNation, en face du métro. + +[LOVE4_7] +~g~Il y a un chantier à l'île Staunton . Ils ont peut-être amené la marchandise là-bas. + +[LOVE4_8] +~G~Tu auras besoin d'une voiture pour ouvrir le garage. + +[TSCORE] +GAINS : ~1~$ + +[AM1_9] +~r~Salvatore est retourné au club de Luigi! + +[AM1_6] +~g~Si tu restes autour du club de Luigi, la Mafia va te repérer! + +[TM2_3] +~g~C'est un piège! Descends-les tous! + +[FM4_1] +C'est Maria. La voiture est piégée! Rejoins-moi sur le quai au sud du pont de Callahan. + +[JM1_7] +~g~Ferme la portière! Il va nous repérer! + +[KM5_1] +~g~DEALER DESCENDU! + +[KM5_6] +~g~Tu dois tuer au moins 8 dealers de Yardie. + +[KM5_7] +~g~Ne perds pas de temps! Dès qu'ils auront dealer la SPANK, ils ne resteront pas dans les parages bien longtemps! + +[RM3_8] +~r~Cette bagnole est un leurre! + +[LM3_8] +Salut. Moi, c'est Joey. + +[LM3_9] +Luigi m'a dit que t'étais réglo alors reviens plus tard. + +[KM3_5] +~g~Klaxonne pour donner le signal. + +[LOVE7] +LA DISPARITION DE LOVE + +[LOVE2_5] +~g~Kenji est écrabouillé! Tire-toi de Newport et débarrasse-toi de la bagnole. + +[AS2_11] +~g~~1~ SUR 9! + +[GARAGE1] +~g~Sors de la bagnole et continue à pied. + +[KM3_11] +~g~Le Cartel a été attaqué et la malette n'a pas été retrouvée. + +[KM3_12] +~g~Tue tous les Colombiens, détruis les bagnoles et retrouve la malette. + +[KM3_13] +~g~Ramène la malette au casino. + +[RM5_6] +~g~Il s'est enfui! Bousille-lui son plâtre avec une bagnole ou une explosion! + +[PBOAT_1] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer avec les canons du bateau. + +[PBOAT_2] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer avec les canons du bateau. + +[DIAB1_B] +C'est El Burro des Diablos. + +[DIAB1_D] +T'es nouveau à Liberty mais tu t'es déjà fait une sacrée réputation dans la rue. + +[DIAB1_E] +Il y a une course de bagnoles qui va partir de la vieille école près du pont de Callahan. + +[DIAB1_F] +Trouve-toi une caisse et le premier qui franchit tous les points de passage, gagne le gros lot. + +[HM2_1] { re3 change } +Utilise les buggies télécommandés pour détruire les voitures blindées. Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour les faire exploser. + +[HM2_1A] { re3 change } +Utilise les buggies télécommandés pour détruire les voitures blindées. Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour les faire exploser. + +[HM2_2] +~r~T'as pas réussi à détruire toutes leurs voitures blindées! + +[HM2_6] +~g~Voiture blindée détruite! + +[RM3_A] +Je connais un mec important en ville, un coeur tendre, + +[RM3_H] +avec ce qu'on pourrait appeler des envies exotiques et l'argent pour les assouvir. + +[RM3_B] +Il est compromis dans une affaire et l'avocat général a des photos assez embarrassantes de lui, + +[RM3_C] +pendant une séance de tir ou quelque chose comme ça. + +[LOVE6_A] +Une leçon en affaires, mon ami. + +[LOVE6_E] +Si tu as quelque chose d'unique, le monde entier essaiera de te le subtiliser.. + +[LOVE6_C] +Les équipes spéciales de la police ont fermé le périmètre autour de mon associé et du paquet. + +[LOVE6_D] +Va là-bas, prends la camionnette et fais diversion. + +[LOVE6_F] +Occupe-les pour que nos hommes puissent s'échapper. + +[AM3_C] +Il est sûrement dans la baie à l'heure qu'il est! Vole un bateau de la police et fais-le couler! + +[FESZ_UC] +ANNULER + +[FEDS_SM] +L1,R1-CHANGER MENU + +[FEDS_AS] +;=-CHANGER SELECTION + +[FEDSAS2] +<>-CHANGER SELECTION + +[FEDS_SS] +L1,R1-CHANGER SELECTION + +[FEDSSC1] +;-DEFILEMENT RAPIDE + +[FEDSSC2] +=-ARRETER DEFILEMENT + +[MEA2_3] +~g~Ramène la voiture à l'usine. + +[RM1_3] +~r~McCaffrey s'est enfui! + +[RM1_4] +~g~T'as utilisé toutes les grenades! Va en chercher chez Ma-Gnum! + +[RM1_5] +~g~Retourne là-bas et brûle la barraque! + +[RM6_4] +~g~Va aux coffres et ramasse la came de Ray. + +[RM6_5] +~g~La CIA surveille le pont, trouve une autre route. + +[HM2_F] +Et bousille tous leurs fourgons blindés. + +[HM_4] +'LA COURSE AUX LINGOTS' + +[MEA2_B5] +TEXT NO LONGER NEEDED + +[MEA1_B5] +TEXT NO LONGER NEEDED + +[MEA3_B5] +TEXT NO LONGER NEEDED + +[MEA4_B7] +Mais si tu viens dans mon bureau... + +[MEA3_B4] +Marty veut me voir ? Ben, il a intérêt à se dépêcher parce que j'ai rendez-vous chez le coiffeur après. + +[KM3_7] +C'est un piège des Yakuzas, mec! + +[FES_LOF] +Echec du chargement. + +[P1INSA] +Port 1 carte mémoire insérée. ~1~Ko d'espace libre. ~1~Ko requis. + +[P1INSN] +Port 1 carte mémoire. Espace libre insuffisant. Veuillez effacer certains fichiers. + +[FES_SLO] +FICHIER + +[FES_ISC] +EST CORROMPUE + +[FESZ_TI] +SAUVEGARDER Z1 + +[FESZ_SA] +Sauvegarder la partie + +[P1NOIN] +Port 1. Carte mémoire non insérée. + +[P1INSE] +Port 1. Carte mémoire insérée. + +[MC_LDFL] +Echec du chargement. + +[MC_NWRE] +Redémarrage du jeu... + +[LOVE6_3] +~g~Tu as ~1~ secondes pour retourner au fourgon blindé avant de rater la mission. + +[LOVE6_4] +~r~Tu t'es débarrassé de la fausse Sécuricar! + +[HELP1] +Arrête-toi au centre du repère bleu. + +[HELP12] +Va au milieu du repère bleu pour lancer une mission. + +[HJSTAT] +Distance : ~1~.~1~m Hauteur : ~1~.~1~m Saltos :~1~ Rotation : ~1~' + +[HJSTATW] +Distance : ~1~.~1~m Hauteur : ~1~.~1~m Saltos : ~1~ Rotation : ~1~ Et quelle belle réception! + +[DIAB1_5] +TEMPS DE COURSE : + +[LOVE3_4] +~r~Tu as détruit l'avion! + +[F_FAIL1] +Mission Camion de Pompier terminée. + +[F_CANC] +~r~Mission Pompier annulée! + +[F_EXTIN] +FEUX : + +[A_COMP1] +Missions Ambulance réussies! + +[A_CANC] +~r~Mission Ambulance annulée! + +[A_COMP3] +Missions Ambulance réussies! Tu ne seras jamais fatigué en courant! + +[ATUTOR] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Ambulance. + +[ATUTOR3] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Ambulance. + +[ALEVEL] +Mission Ambulance Niveau ~1~ + +[A_FAIL1] +Mission Ambulance terminée. + +[FEST_HA] +Mission Ambulance Niveau Maximum + +[A_SAVES] +PERSONNES SAUVEES : ~1~ + +[C_KILLS] +CRIMINELS TUES : ~1~ + +[HM1_B] +J'ai un problème, ils essaient de me rouler. + +[AM2_A] +La mort de Salvatore est une bonne nouvelle, + +[AM2_A2] +tu es un tueur efficace, j'aime ça chez un homme. + +[AM2_B] +Voilà mon frère, Kenji. + +[AM2_C] +Asuka a un petit boulot pour toi, mais quand t'as fini, passe à mon casino et on va causer. + +[AM2_D] +C'est bien Kenji, faut toujours qu'il joue avec mes jouets. + +[AM2_E] +Mon indic à la police m'a dit que la Mafia surveille nos activités en ville + +[AM2_E2] +et qu'elle est à tes trousses. + +[AM2_F] +On ne peut pas reprendre nos activités, tant qu'ils sont dans les parages. + +[AM2_G] +Elimine ces fouille-merde et mets un terme à cette vendetta une bonne fois pour toutes. + +[F_START] +~g~Véhicule en feu signalé dans le secteur ~a~. Vas-y et éteins le feu. + +[AM4_1A] +Va à la cabine de parc de Belleville ouest. + +[AM4_1B] +Va à la cabine du campus de Liberty. + +[AM4_1C] +Va à la cabine du parc de Belleville sud. + +[AM4_1D] +Retrouve-moi dans les toilettes du parc. + +[HJSTATF] +Distance : ~1~ft Hauteur : ~1~ft Saltos :~1~ Rotation : ~1~- + +[HJSTAWF] +Distance : ~1~ft Hauteur : ~1~ft Saltos :~1~ Rotation : ~1~- Et quelle belle réception! + +[HM1_F] +Mais fais gaffe à toi, y'aura aussi des Jacks dans la rue qui vont croire que tu veux aussi les buter. + +[HM1_D] +Leur nom, c'est 'Nines' et leur drapeau est violet. Et chaque fois qu'ils font parader leurs couleurs... + +[HM1_G] +les 'Jacks' perdent la face. + +[MEA2_B] +et voler plein de trucs, comme ça l'assurance me remboursera. + +[TM3_H] +~w~T'as fait du bon boulot là-bas petit, c'est très bien. + +[TM3_I] +~w~Allez, on va te présenter au Don. + +[TM3_J] +~w~Hé! Luigi! + +[TM3_K] +~w~Oh tu as beaucoup manqué à mes filles, Salvatore, tu as été absent trop longtemps. + +[TM3_L] +Tu leur diras que, quand tous ces emmerdes seront terminés, + +[TM3_M] +on ira tous à la boîte pour fêter ça, ok ? + +[TM3_N] +~w~Voila mon petit. + +[TM3_N2] +~w~Comment ça va, Papa ? + +[TM3_O] +~w~Alors tu t'es enfin trouvé une femme ? + +[TM3_P] +~w~Ta mère, paix à son âme, se retournerait dans sa tombe + +[TM3_Q] +~w~de te voir sans femme. + +[TM3_R] +~w~Je sais 'Pa, j'y travaille. + +[TM3_S] +~w~Toni! Comment va ta mère ? + +[TM3_T] +~w~C'est une femme bien. Forte. De Florence. + +[TM3_U] +~w~Ça va... elle va bien. + +[TM3_V] +~w~Très bien. Bon les gars, entrez pendant que je parle à notre nouveau venu. + +[TM3_W] +~w~Je ne vois que des bonnes choses pour toi, mon petit... + +[RM1_A] +Cet enfoiré de McAffrey a accepté plus de pots-de-vin que n'importe qui. + +[RM1_B] +Il pense qu'il ne va pas être poursuivi s'il donne des preuves aux condés. + +[RM1_C] +Il a balancé! + +[RM4_B] +Faut qu'on le réduise au silence définitivement. + +[RM4_E] +Je le veux dormant avec les poissons, pas en train de les bouffer. + +[LOVE3_B] +Ce soir en approchant de l'aéroport de Liberty, un petit avion passera au dessus de la baie. + +[LOVE4_D] +Malheureusement, les autorités du port ont saisi l'avion et l'ont mis en pièces + +[LOVE4_H] +avant que je puisse intervenir et ça m'a coûté cher. + +[LOVE4_E] +Traverse le pont et va à l'aéroport International Francis. + +[GTAB_A] +Hé, débarrasse-toi de ça. On ne sait pas ce que c'est + +[GTAB_B] +mais il a l'air d'y tenir beaucoup, donc ça doit valoir quelque chose. + +[GTAB_C] +Qui est-ce qui... + +[GTAB_D] +TOI! + +[GTAB_E] +Hé calme-toi amigo! De nada! De nada! + +[GTAB_F] +Je t'ai laissé à moitié mort dans le caniveau! + +[GTAB_G] +Ne tire pas amigo. Pas de problèmes. On est tous amis. Tiens, prends ça. + +[GTAB_H] +Arrête de faire la tapette! + +[GTAB_I] +On n'a pas le choix, bébé! + +[GTAB_J] +On a toujours le choix, espèce d'abruti! + +[GTAB_K] +Je m'excuse pour cette pute hystérique mec, elles sont toutes les mêmes... por favor ?? + +[GTAB_L] +Alors la pute s'est barrée. + +[GTAB_M] +Mais tu m'as fait une fleur, + +[GTAB_N] +tu n'es pas le seul à avoir à régler des comptes avec le Cartel, + +[GTAB_O] +ce cafard a tué mon frère! + +[GTAB_P] +Je n'ai jamais tué de Yakuza! + +[GTAB_Q] +MENTEUR! On a tous vu l'assassin du Cartel. + +[GTAB_R] +On va tous vous retrouver et vous descendre, vous, les chiens de Colombiens! + +[GTAB_S] +Je vais travailler avec votre ami ici, pour obtenir des informations et un peu de plaisir. + +[GTAB_T] +Toi, tu reviens plus tard, je suis sûr que je vais avoir besoin de tes services. + +[GTAB_U] +S'il te plait amigo, ne me laisse pas avec elle, c'est une minette psychotique! Amigo ? Hé AMIIIGO!!!... Aïïïïïïïeeeeee! + +[LOVE5_A] +Tu as prouvé que tu étais un bon investissement, ce qui est rare en ces jours de récession. + +[KM3_1] +~g~Le Cartel s'attend à ce qu'un des Yardies vole une voiture de Yardie! Va vers le Nord, tu en trouveras une à Newport. + +[LOVE1_1] +~g~Va piquer une voiture du gang des Colombiens, comme ça tu pourras rentrer dans leur planque. Dirige-toi vers le Nord, tu en trouveras une à Fort Stanton. + +[FM1_Q1] +~w~Vous voulez vous amuser ? Un petit... hmm ? Du Spank ? + +[FM1_R] +~w~Salut Chico. Non, comme d'habitude. + +[FM1_T] +~w~Merci Chico. A plus tard. + +[FM1_W] +~w~Ok Fido, tu attends ici et tu surveilles la caisse pendant que je vais m'éclater. + +[FM1_X] +~w~Ok Fido, tirons-nous d'ici. Ouuulaa! + +[FM1_Q] +~w~Hé Maria! C'est ma plus belle jument! + +[FM1_S1] +~w~Tu devrais passer à la fête de l'entrepôt à l'est d'Atlantic Quays. + +[FM1_U] +~w~Gracias et éclate-toi. C'est de la bombe. + +[FM1_V] +~w~Allez Fido, on va aller faire un tour à cette fête! + +[FM1_SS] +~r~SCANNER : ~w~Quatre-Cinq à toutes les unités : rejoignez les stups à Atlantic Quays... + +[LOVE6_B] +Même s'ils n'ont pas la moindre idée de sa vraie valeur. + +[TM3_A1] +~r~Joey s'est fait avoir! + +[TM3_A2] +~r~Joey et Luigi se sont fait descendre! + +[TM3_A3] +~r~Joey, Luigi et Toni se sont fait buter! + +[FM4_2] +Salvatore pense qu'on le court-circuite, + +[FM4_3] +alors il voulait te donner au Cartel pour pouvoir faire un deal. + +[FM4_4] +Je pouvais pas le laisser faire, je veux dire, + +[FM4_4B] +tout ça, c'est de ma faute... parce que je lui ai dit qu'on était ensemble. + +[FM4_5] +Me demande pas pourquoi. J'en sais rien. + +[FM4_6] +T'es sur la liste rouge dans la Mafia et je veux me tirer d'ici, moi aussi. + +[FM4_6B] +J'ai vu trop de crimes. Trop de sang! + +[FM4_7] +C'est une amie à moi Ok, une vieille copine... Elle s'appelle Asuka, on peut lui faire confiance. + +[FM4_8] +Allez, on a assez causé. + +[FM4_9] +On ferait mieux de se barrer avant d'avoir tous ces Italiens hystériques aux fesses. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCER + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTION + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DESIGN + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +WRITTEN BY + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +CHARACTERS + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMATION & DIRECTION + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +AUTO DESIGN + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODERS + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +SCORE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +SOUND DESIGN & MASTERING + +[CRED042] +ALLAN WALKER + +[CRED043] +AUDIO PROGRAMMING + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD TESTERS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTERS + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +TECHNICAL PRODUCER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VP OF MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +TECHNICAL COORDINATOR + +[CRED088] +BRANDON ROSE + +[CRED089] +QA MANAGER + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +LIBERTY TREE EDITORIAL + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT-SCENES + +[CRED108] +SCRIPT BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER + +[CRED110] +AUDIO PRODUCED BY RENAUD SEBBANE + +[CRED111] +CAST + +[CRED112] +FRANK VINCENT AS SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO AS LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN AS TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT AS JOEY LEONE + +[CRED116] +DEBBI MAZAR AS MARIA + +[CRED117] +KYLE MACLACHLAN AS DONALD LOVE + +[CRED118] +ROBERT LOGGIA AS RAY MACHOWSKI + +[CRED119] +GURU AS 8-BALL + +[CRED120] +SONDRA JAMES AS MOMMA + +[CRED121] +LIANA PAI AS ASUKA + +[CRED122] +LES MAU AS KENJI + +[CRED123] +CYNTHIA FARRELL AS CATALINA + +[CRED124] +AL ESPINOSA AS MIGUEL + +[CRED125] +CHRIS PHILLIPS AS EL BURRO + +[CRED126] +HUNTER PLATIN AS CHICO + +[CRED127] +WALTER MUDU AS D-ICE + +[CRED128] +CURTIS MCCLARIN AS CURTLY + +[CRED129] +BILL FIORE AS DARKEL + +[CRED130] +CHRIS PHILLIPS AS MARTY CHONKS + +[CRED131] +HUNTER PLATIN AS CURLY BOB + +[CRED132] +WALTER MUDU AS KING COURTNEY + +[CRED133] +HUNTER PLATIN AS ONE-ARMED PHIL + +[CRED134] +KIM GURNEY AS MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRECTED BY + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUCED BY + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +RECORDED AT MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORS + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRED142] +WRITTEN BY DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRECTED BY CRAIG CONNER, DAN HOUSER AND LAZLOW + +[CRED144] +PRODUCED BY RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +RADIO STATIONS AND MUSIC + +[CRED218] +PRODUCERS FOR ROCKSTAR UK + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDITED BY + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING WRITTEN BY + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[J_EP] +PRODUCTEUR EXECUTIF + +[N_EP] +SAM HOUSER + +[J_PROD] +PRODUCTEUR + +[N_PROD] +LESLIE BENZIES + +[J_AD] +DIRECTEUR ARTISTIQUE + +[N_AD] +AARON GARBUT + +[J_TD] +DIRECTEURS TECHNIQUES + +[N_TD1] +OBBE VERMEIJ + +[N_TD2] +ADAM FOWLER + +[J_COD] +ENCODEURS + +[N_COD1] +ALEXANDER ROGER + +[N_COD2] +GRAEME WILLIAMSON + +[N_COD3] +MARK HANLON + +[N_COD4] +ALAN CAMPBELL + +[N_COD5] +RAYMOND USHER + +[N_COD6] +ANDRZEJ MADAJCZYK + +[J_ART] +INFOGRAPHISTES + +[N_ART1] +ADAM COCHRANE + +[N_ART2] +ALISDAIR WOOD + +[N_ART3] +GARY MCADAM + +[N_ART4] +ANDREW SOOSAY + +[N_ART5] +KEIRAN BAILLIE + +[J_AUTO] +CONCEPTION AUTOMOBILE + +[N_AUTO] +PAUL KUROWSKI + +[J_CHAR] +PERSONNAGES + +[N_CHAR] +IAN MCQUE + +[J_ANIM] +ANIMATION ET REALISATION + +[N_ANIM1] +ALEX HORTON + +[N_ANIM2] +NAVID KHONSARI + +[N_ANIM3] +LEE MONTGOMERY + +[J_SND] +CONCEPTION SON + +[N_SND1] +ALLAN WALKER + +[J_SCR] +MUSIQUE + +[N_SCR1] +CRAIG CONNER + +[N_SCR2] +STUART ROSS + +[J_DSGN] +CONCEPTION + +[N_DSGN1] +CRAIG FILSHIE + +[N_DSGN2] +WILLIAM MILLS + +[N_DSGN3] +CHRIS ROTHWELL + +[N_DSGN4] +JAMES WORRALL + +[J_WRT] +SCENARIO + +[N_WRT1] +JAMES WORRALL + +[N_WRT2] +DAN HOUSER + +[N_WRT3] +PAUL KUROWSKI + +[J_IT] +ASSISTANCE TECHNIQUE + +[N_IT1] +LORRAINE ROY + +[N_IT2] +CHRISTINE CHALMERS + +[J_IQA] +RESPONSABLE DES TESTS + +[N_IQA1] +CRAIG ARBUTHNOTT + +[LEAD_T] +TESTEURS PRINCIPAUX + +[N_IQA2] +JOHN HAIME + +[N_IQA3] +NEIL CORBETT + +[N_IQA4] +ANDY DUTHIE + +[TEST] +TESTEURS + +[N_IQA5] +GRAEME JENNINGS + +[N_IQA6] +DAVID MURDOCH + +[N_IQA7] +DAVID BEDDOES + +[N_IQA8] +EDWIN SMITH + +[N_IQA9] +MARK FLETT + +[N_IQ10] +MICHAEL SUTHERLAND + +[J_EQA] +ROCKSTAR NEW YORK + +[N_EQA1] +JEFF ROSA + +[LEAD_T2] +TESTEUR PRINCIPAL + +[N_EQA2] +ADAM DAVIDSON + +[N_EQA3] +JOE HOWELL + +[N_EQA4] +JOE GREEN + +[N_EQA5] +RICH HUIE + +[N_EQA6] +JEREMY POPE + +[N_EQA7] +KAHLEEM POOLE + +[N_EQA8] +HAKLIN NG + +[N_EQA9] +MIKE HONG + +[N_EQA10] +BRIAN PLANAR + +[N_EQA11] +JAMEEL VEGA + +[CINCAM] +Caméra Cinématique + +[KM1_13] +Amène cette bagnole au garage! + +[KM3_14] +~r~Tu t'es fait repérer, le marché est annulé! + +[EBAL_H] +Attends-moi ici pendant que je vais aller parler à luigi. + +[EBAL_M] +Rappelle-toi que personne n'embrouille mes filles! + +[LM2_F] +Tu lui prends sa caisse et tu la repeins. + +[LM2_D] +Ben voilà, prends-le. + +[LM1_9] +Salut, je m'appelle Misty. + +[LM4_A] +Y'a des connards de Diablos qui font tapiner leurs sales putes sur mon territoire. + +[FM2_B] +On a une balance! + +[FM2_C] +Il fait pas le maquereau,ni le dealer, donc il doit parler. + +[FM3_CC] +~w~Reviens quand t'auras le pognon, frangin. + +[FEDS_AM] +<>-CHANGER MENU + +[LOVE5_5] +~r~T'as pas su protéger le camion! + +[RM6_6] +~r~Ray est mort! + +[RM6_7] +. ~r~Ray a raté son vol. + +[RM6_8] +~g~Tu as laissé tomber Ray, retourne le chercher. + +[FM1_10] +~g~Tu as laissé tomber Maria, retourne la chercher. + +[LOVE4_9] +~r~L'avion a été détruit! + +[LOV4_10] +~r~La seule piste pour retrouver le paquet est partie en fumée! + +[KM2_D] +Cela va sans dire, on va lui en faire cadeau, pour éponger la dette que j'ai envers lui. + +[KM4_B] +Les mecs qui ont la chance de bénéficier de notre protection, font leurs comptes aujourd'hui. + +[KM2_E] +Tu dois trouver les voitures qui sont sur cette liste et les livrer au garage derrière le parking de Newport. + +[FM3_8I] +~w~Trouve une bonne place et je rentrerai quand tu tireras le premier coup. + +[LOVE1_B] +L'expérience m'a appris que quelqu'un comme toi peut être très loyal si on le paye bien, + +[LOVE1_H] +mais ca fait des jaloux. + +[LOVE1_C] +Un vieux bridé que je connais, un homme de confiance, + +[LOVE1_I] +est retenu en otage par des Sud-Américains à Aspatria. + +[MEA4_D] +J'ai accepté de le voir... + +[MEA4_B4] +C'est Marty qui t'envoie, hein ? D'accord, je vais lui apprendre, moi, le sens des affaires. + +[MEA4_B5] +Carl, salut! Euh... j'ai besoin de plus de temps pour ton pognon. + +[MEA1_B4] +C'est M. Chonks qui t'envoie, n'est-ce pas ? Allons lui rendre visite. + +[HM5_6] +Allons fracasser des crânes... + +[LOVE1_5] +~g~Arrête de tourner en rond, trouve une bagnole des Colombiens et sauve l'associé de Love. + +[AS1_D] +~w~T'as qu'à faire l'appât et attirer les escadrons de la mort dans la Crique de Pike. + +[AS1_E] +~w~Mes hommes les attendront là-bas. + +[AS2_C] +~w~Le Cartel a une couverture : l'usine de café Kappa. + +[AS2_E] +~w~On a pas d'autre choix que de neutraliser ces charettes à drogue. + +[AS2_F] +~w~Fais-en des allumettes!! + +[AS2_A1] +~w~Miguel a sûrement un peu de cette fameuse énergie latine. + +[AS2_A2] +~w~Je suis crevé. + +[SIREN_3] +Pour activer la sirène, appuie sur la ~h~touche ~k~~VEHICLE_HORN~~w~. + +[SIREN_4] +Pour activer la sirène, appuie sur la ~h~touche ~k~~VEHICLE_HORN~~w~. + +[AS3_C] +~w~Oulala! C'est quoi ce truc jaune ? + +[AS3_C1] +~w~Salut ma poule. + +[AS3_F] +~w~Elle se classe tout de suite dans les meilleures, cette nana. + +[AS3_F1] +~w~Elle s'est arrangée pour dérober ce joli petit bijou à notre invité. + +[AS3_G] +~w~Y'a un avion qui arrive à l'aéroport international Francis dans 2 heures. + +[AS3_G1] +~w~Il est rempli de poison de Catalina. + +[AS3_H] +~w~Tu peux éviter la sécurité de l'aéroport en prenant un bateau jusqu'aux bouées lumineuses d'approche. + +[AS3_H1] +~w~Et dès que l'avion descends, tu l'explose! + +[AS3_I] +~w~Récupère la marchandise au milieu des débris. + +[AS3_J] +~w~Maintenant, fais attention ma poule! + +[AS3_K] +~w~Essaie avec l'huile pimentée... + +[RM2_F1] +Ces Colombiens seront là d'une minute à l'autre! + +[RM2_K] +Merde ils sont là!! Feu à volonté!! + +[LOVE2_7] +~g~Maintenant, largue la bagnole! + +[LOVE2_8] +~g~Dégage de Newport! + +[AM1_F] +Salvatore Leone va partir de chez Luigi vers ~1~:~1~. + +[LOVE5_C] +Je veux que tu le suives et que tu fasses en sorte que lui et mon paquet arrivent à la Crique de Pike sains et saufs. + +[FESZ_SR] +Echec de la sauvegarde! Vérifie la memory card (PS2) dans la fente pour MEMORY CARD 1 et réessaie. + +[FESZ_FO] +Veux-tu formater la memory card (PS2) dans la fente pour MEMORY CARD 1? + +[FELZ_FO] +La memory card (PS2) dans la fente pour MEMORY CARD 1 n'est pas formatée. + +[FES_NOC] +Aucune memory card (PS2) dans la fente pour MEMORY CARD 1. + +[FES_LOE] +Echec du chargement! Vérifie la memory card (PS2) dans la fente pour MEMORY CARD 1 et réessaie. + +[FES_DEE] +Echec de la suppression! Vérifie la memory card (PS2) dans la fente pour MEMORY CARD 1 et réessaie. + +[FORSUC] +Formatage réussi de la memory card (PS2) dans la fente pour MEMORY CARD 1. + +[ERFOUN] +Echec du formatage de la memory card (PS2) dans la fente pour MEMORY CARD 1. + +[ERMCNP] +Aucune memory card (PS2) dans la fente pour MEMORY CARD 1. + +[SVMEM1] +Sauvegarder sur la memory card (PS2) dans la fente pour MEMORY CARD 1. + +[FORSLO] +Formater la memory card (PS2) dans la fente pour MEMORY CARD 1. + +[SLONFM] +Erreur de formatage de la Memort Card (PS2) dans la fente pour MEMORY CARD 1. + +[SLONDR] +Espace insuffisant pour sauvegarder. Insère une memory card (PS2) dotée d'au moins 500 Ko d'espace disponible, dans la fente pour MEMORY CARD 1. + +[SLNSP] +Espace insuffisant pour sauvegarder. Insère une memory card (PS2) dotée d'au moins 200 Ko d'espace disponible, dans la fente pour MEMORY CARD 1. + +[FEFD_WR] +Formatage de la memory card (PS2) dans la fente pour MEMORY CARD 1. Ne pas retirer la memory card (PS2), ni réinitialiser ou éteindre la console. + +[FES_ISF] +ABSENT + +[FES_SAG] +EXISTANT + +[SLONNO] +Aucune memory card (PS2) dans la fente pour MEMORY CARD 1. + +[SLONNF] +La memory card (PS2) dans la fente pour MEMORY CARD 1 pas formatée. + +[FESZ_FM] +La memory card (PS2) dans la fente pour MEMORY CARD 1 n'est pas formatée. Veux-tu la formater? + +[FESZ_FF] +Echec du formatage! Vérifie la memory card (PS2) dans la fente pour MEMORY CARD 1 et réessaie. + +[MCDNSP] +Espace insuffisant sur la memory card (PS2) dans la fente pour MEMORY CARD 1. 500 Ko minimum sont requis pour sauvegarder. Veux-tu commencer? (OUI ou NON) + +[MCGNSP] +Espace insuffisant sur la memory card (PS2) dans la fente pour MEMORY CARD 1. 200 Ko minimum sont requis pour sauvegarder. Veux-tu commencer ? (OUI ou NON) + +[FESZ_WR] +Sauvegarde en cours. Ne pas retirer la memory card (PS2) de la fente pour MEMORY CARD 1, ni réinitialiser ou éteindre la console. + +[FESZ_OW] +Ecrasement en cours. Ne pas retirer la memory card (PS2) de la fente pour MEMORY CARD 1, ni réinitialiser ou éteindre la console. + +[FELD_WR] +Chargement en cours. Ne pas retirer la memory card (PS2) de la fente pour MEMORY CARD 1, ni réinitialiser ou éteindre la console. + +[FEDL_WR] +Effacement en cours. Ne pas retirer la memory card (PS2) de la fente pour MEMORY CARD 1, ni réinitialiser ou éteindre la console. + +[LM2_C] +Luigi m'a dit de te donner ça, alors... + +[LM3_G] +Joey n'est pas du genre à être patient, rappelle-toi, c'est ton ticket d'entrée... + +[LM5_E] +Ramènes-en autant que tu peux avant que ces poulets aient cramé tout leur blé. + +[JM5_C] +Y'a une bagnole avec un macchabée devant le café à coté de Point Callahan. + +[RM2_B] +On a fait le Nicaragua ensemble, à l'époque où ce pays savait ce qu'il faisait. + +[RM2_C] +Un enfoiré du Cartel l'a bousculé hier et lui a dit qu'ils reviendraient aujourd'hui pour de la marchandise. + +[RM2_D1] +J'y serais bien allé moi-même mais ma vieille sciatique s'est réveillée - hhurrh hhurrh - alors, bonne chance. + +[CATINF1] +~g~Chope Catalina! + +[CATINF2] +~g~Suis l'hélico pour trouver Catalina. + +[BOATIN1] +Saute dans un bateau et appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour entrer dedans. + +[BOATIN2] +Tu peux appuyer sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~ ~w~si tu es près d'un bateau pour te glisser à l'intérieur. + +[BOATIN3] +Saute dans un bateau et appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~ ~w~pour entrer dedans. + +[BOATIN4] +Tu peux appuyer sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~ ~w~si tu es près d'un bateau pour te glisser à l'intérieur. + +[JM6] +'L'EVASION' + +[FM1] +'LE CHAPERON' + +[JM1] +'LE DERNIER REPAS DE MIKE 'BABINES' + +[FM21] +'LA BOMBE : ACTE 1' + +[FM3] +'LA BOMBE : ACTE 2' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'SOUS SURVEILLANCE' + +[KM2] +'GRAND THEFT AUTO' + +[AS3] +'S.A.M.' + +[RM2] +'PENURIE D'ARMES' + +[LOVE6] +'L'APPAT' + +[LOVE1] +'LIBERATEUR' + +[RC1] +'DESTRUCTION DE DIABLO' + +[RC2] +'MASSACRE DE LA MAFIA' + +[RC3] +'CALAMITÉ AU CASINO' + +[RC4] +'RODEO DE RUMPO' + +[RM2_E1] +Je peux pas croire que ce trouillard m'ait encore laissé sans protection! + +[GREN_1] +Plus tu maintiendras la ~h~touche ~k~~PED_FIREWEAPON~~w~ enfoncée, plus tu lanceras loin la grenade. + +[GREN_2] +Plus tu maintiendras la ~h~touche ~k~~PED_FIREWEAPON~~w~ enfoncée, plus tu lanceras loin la grenade. + +[GREN_3] +Plus tu maintiendras la ~h~touche ~k~~PED_FIREWEAPON~~w~ enfoncée, plus tu lanceras loin la grenade. + +[LOVE4_G] +Mon bien t'attendra dans le hangar des douanes à l'intérieur du fuselage de l'avion. + +[KABOOM] +KABOOOM! + +[SPLAT] +DESSOUDE! + +[PANCAK] +REFROIDI! + +[SOAKED] +FLINGUE! + +[HEAD] +Radio tête + +[DBL_CLF] +Radio double FM + +[FLASHB] +Nostalgia FM + +[RISE] +Lévitation FM + +[LIPS] +Love 106 + +[CHAT] +Causette FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Echec FM + +[MSX_FM] +MSX FM + +[TUBE1] +Quand le métro ouvrira, tu pourras prendre une rame pour aller à l'île Staunton . + +[TUBE2] +Quand Shoreside Vale sera ouvert, tu pourras sortir au Shoreside Terminal pour aller à l'aéroport international Francis. + +[TUBE_2] +Pour prendre le métro, appuie sur la ~h~touche 'monter véhicule'~w~. + +[LEGAL] +~g~Enraye toute menace criminelle! + +[GA_2] +Nouveau moteur et nouvelle peinture. Les flics ne te reconnaîtront jamais! + +[LM1_8A] +Pour te faire du fric en plus, tu pourrais peut-être 'emprunter' un taxi... + +[TAXIH1] +Arrête-toi près d'un passage piéton pour prendre des passagers et emmène-les à destination avant la fin du temps imparti. + +[LM5_7] +~g~Y'a moins de quatre filles qui bossent au ~p~Bal~g~, Luigi va pas être content! + +[KM2_3] +~g~Rappelle-toi que les ~r~voitures~g~ doivent être en parfait état pour être acceptées par le ~p~garage~g~. + +[KM5_2] +~g~Un homme du gang de Yardie est hors d'état de nuire. + +[BETRA_A] +Désolé, chérie. + +[BETRA_B] +Je suis une fille ambitieuse et toi, + +[BETRA_C] +t'es juste un passe-temps. + +[HELP15] +A pied, appuie sur la ~h~touche ~k~~PED_LOOKBEHIND~~w~ pour ~h~regarder derrière~w~. + +[FEC_LB3] +Regarder derrière + +[FEC_R3] +(touche R3) + +[FES_AFO] +Cette memory card (PS2) est déjà formatée. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- CHANGER SELECTION + +[FEDSAS4] +;=<> - CHANGER SELECTION + +[SPRAY_4] { re3 change } +Utilise la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer à l'aide du canon à eau. + +[SPRAY_1] { re3 change } +Utilise la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer à l'aide du canon à eau. + +[AM1_10] +~g~Salvatore Leone partira de chez Luigi vers 0~1~:~1~. + +[JAILB_V] +Liberty City est en état de choc! + +[JAILB_A] +La police et les services d'urgence s'occupent des conséquences + +[JAILB_B] +d'une attaque lancée contre un convoi de la police ce matin. + +[JAILB_C] +On n'a reçu aucune info sur les prisonniers qui étaient transférés ce matin. + +[JAILB_D] +Et aucun groupe, n'a revendiqué cette attaque. + +[JAILB_E] +Le convoi a quitté le Q.G. de la police tôt ce matin... + +[JAILB_F] +pour un transfert vers le pénitencier de Liberty. + +[JAILB_G] +L'attaque a eu lieu sur le pont de Callahan, + +[JAILB_H] +laissant peu de témoins et un pont gravement endommagé. + +[JAILB_I] +On suppose que certains prisonniers sont morts dans l'explosion... + +[JAILB_J] +qui a suivi la première attaque. + +[JAILB_W] +Le professionnalisme de cette attaque a pris de court la police, + +[JAILB_K] +lorsque l'identification des prisonniers évadés a été ralentie... + +[JAILB_L] +par la découverte d'un piratage informatique simultané des bases de données du Q.G. de la police. + +[JAILB_O] +Le chantier du tunnel Porter prenant de plus en plus de retard, + +[JAILB_P] +cette catastrophe laisse Portland isolé du reste de la ville. + +[JAILB_Q] +Allez, viens! + +[JAILB_R] +Monsieur tête de noeud + +[JAILB_S] +Ca me pose aucun problème de te tuer. + +[JAILB_T] +Tu vas le regretter. + +[JAILB_U] +D'accord, d'accord. Allez, dégage! + +[JAILB_M] +Le Maire O'Donovan a fait clairement comprendre que la police considérait + +[JAILB_N] +* + +[JAILB_X] +Dans une déclaration faite ce matin, + +[FEDS_SE] +Touche / - SELECTIONNER + +[FEDS_SB] +Touche / - SELECTIONNER Touche " - RETOUR + +[TM4_A] +~w~Oh, c'est toi. Toni n'est pas là. + +[TM4_A2] +~w~Mais il a laissé une de ses lettres d'amour pour toi. + +[DIAB2_A] +J'ai commencé dans les loisirs exotiques avec rien d'autre que le contenu, pas si négligeable que ça, de mon pantalon de cuir! + +[LM5_9] +FILLES : + +[PERPIC] +Paquets cachés trouvés + +[CO_ONE] +~1~ paquet caché sur ~1~ trouvé. + +[LOVE3_3] +~g~L'avion a largué ~1~ paquets sur 6. + +[FARE11] +~g~Destination : ~w~'Chantier'~g~ de Fort Staunton. + +[GA_21] +Impossible de garer plus de véhicules dans ce garage. + +[CHEAT1] +Codes activés + +[CHEAT2] +Code d'arme + +[CHEAT3] +Code de santé + +[CHEAT4] +Code d'armure + +[CHEAT5] +Code d'indice de recherche + +[CHEAT6] +Code d'argent + +[CHEAT7] +Code de météo + +[AS1_H] +~r~T'as pas réussi à amener l'escadron de la mort dans le piège des Yakuzas! + +[FEDS_BA] +Touche " - RETOUR + +[RAMP_A] +TOUS LES RODEOS ONT ETE ACCOMPLIS! + +[USJ_ALL] +TOUTES LES CASCADES ONT ETE ACCOMPLIES! + +[FARE23] +~g~Destination : ~w~'Transport-Export'~g~ au Barrage Cochrane. + +[L_TRN_1] +Tu peux prendre le train-L à Portland. Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~descendre~w~ du train. + +[L_TRN_2] +Tu peux prendre le train-L à Portland. Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~descendre~w~ du train. + +[S_TRN_1] +Tu peux prendre le métro à Liberty. Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~descendre~w~ du train. + +[S_TRN_2] +Tu peux prendre le métro à Liberty. Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~descendre~w~ du train. + +[AS1_C] +~w~Il y a trois escadrons de la mort autour de Liberty et tout ce qu'ils veulent, c'est te buter! + +[AS1_G] +~r~Tous les Yakuzas sont morts! + +[JAN] +Jan + +[FEB] +Fév + +[MAR] +Mar + +[APR] +Avr + +[MAY] +Mai + +[JUN] +Juin + +[JUL] +Juil + +[AUG] +Août + +[SEP] +Sep + +[OCT] +Oct + +[NOV] +Nov + +[DEC] +Déc + +[DEFDT] +Date de sauvegarde invalide + +[BUGGY] +BUGGIES RESTANTS : + +[BONUS] +~g~BONUS ~1~$ + +[HORN1] +Appuie sur la ~h~touche L3 ~w~pour ~h~klaxonner. + +[HORN2] +Appuie sur la ~h~touche L1 ~w~pour ~h~klaxonner. + +[HORN3] +Appuie sur la ~h~touche R1 ~w~pour ~h~klaxonner. + +[LM3_1A] +Appuie sur la ~h~touche ~k~~VEHICLE_HORN~ ~w~pour ~h~klaxonner~w~ et prévenir Misty de ton arrivée. + +[LM3_1B] +Appuie sur la ~h~touche ~k~~VEHICLE_HORN~ ~w~pour ~h~klaxonner~w~ et prévenir Misty de ton arrivée. + +[LM3_1C] +Appuie sur la ~h~touche ~k~~VEHICLE_HORN~ ~w~pour ~h~klaxonner~w~ et prévenir Misty de ton arrivée. + +[RADIO_A] +Appuie sur la ~h~touche ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ pour faire défiler les ~h~stations de radio. + +[RADIO_B] +Appuie sur la ~h~touche ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ pour faire défiler les ~h~stations de radio. + +[RADIO_C] +Appuie sur la ~h~touche ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ pour faire défiler les ~h~stations de radio. + +[RADIO_D] +Appuie sur la ~h~touche ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ pour faire défiler les ~h~stations de radio. + +[FEC_EXV] +Entrer\sortir d'un véhicule + +[TAXI_M] +'TAXI DRIVER' + +[COP_M] +'POLICE' + +[FIRE_M] +'POMPIER' + +[AMBUL_M] +'AMBULANCE' + +[HJ_IS] +BONUS DE CASCADE DANGEREUSE : ~1~$ + +[HJ_PIS] +BONUS DE CASCADE DANGEREUSE PARFAITE : ~1~$ + +[HJ_DIS] +BONUS DE DOUBLE CASCADE DANGEREUSE : ~1~$ + +[HJ_PDIS] +BONUS DE DOUBLE CASCADE DANGEREUSE PARFAITE : ~1~$ + +[HJ_TIS] +BONUS DE TRIPLE CASCADE DANGEREUSE : ~1~$ + +[HJ_PTIS] +BONUS DE TRIPLE CASCADE DANGEREUSE PARFAITE : ~1~$ + +[HJ_QIS] +BONUS DE QUADRUPLE CASCADE DANGEREUSE : ~1~$ + +[HJ_PQIS] +BONUS DE QUADRUPLE CASCADE DANGEREUSE PARFAITE : ~1~$ + +[AM1_K] +Salvatore Leone partira de chez Luigi dans environ trois heures. (0~1~:~1~) + +[IMPEXPP] +Transport-Export, port de Portland. On a commandé différents véhicules. Consulte notre panneau d'affichage pour avoir plus d'infos. + +[VANHSTP] +Si t'arrives à choper d'autres Sécuricars, amène-les à notre garage dans le port de Portland. + +[EMVHPUP] +Achat de véhicules de secours neufs et d'occasion à très bons prix. Apporte-les à la grue, au nord-est du port de Portland. + +[STANDS] +ETALS RENVERSES : + +[STASH] +~g~Planque la SPANK sur le ~p~chantier! + +[MCSTNS] +Aucune memory card (PS2) dans la fente pour MEMORY CARD 1. Veux-tu commencer? (OUI ou NON) + +[LOVE3_5] +~g~L'avion est à portée. + +[LOVE3_6] +~r~La police a réussi à récupérer les paquets! + +[SIREN_1] +Pour déclencher la sirène de ce véhicule, appuie brièvement sur la ~h~touche ~k~~VEHICLE_HORN~~w~. + +[SIREN_2] +Pour déclencher la sirène de ce véhicule, appuie brièvement sur la ~h~touche ~k~~VEHICLE_HORN~~w~. + +[FM3_8C] +~w~J'ai besoin de 100 000$ pour couvrir mes dépenses, + +[MCLOAD] +Chargement des données. Ne pas retirer la memory card (PS2) de la fente pour MEMORY CARD 1, ni réinitialiser ou éteindre la console. + +[FES_GME] +Erreur de lecture sur la memory card (PS2) de la fente pour MEMORY CARD 1. Vérifie-la et réessaie. + +[FESZ_QF] +Veux-tu vraiment formater la memory card (PS2) de la fente pour MEMORY CARD 1? + +[FESZ_LS] +Chargement réussi. + +[RM3_5] +~g~Tu as ~1~ des 6 paquets de preuves. + +[LOVE3_2] +~g~Tu as récupéré tous les paquets! Rapporte-les à Donald Love. + +[LOVE4_4] +~g~Rapporte le paquet à Donald Love! + +[CLZOON] +Show Cull Zones On + +[CLZOOF] +Show Cull Zones Off + +[CRRGON] +ShowCarRoadGroups On + +[CRGOFF] +ShowCarRoadGroups Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_DSR] +Debug Streaming Requests + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_SCP] +gbShowCollisionPolys + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_SPR] +Show Ped Road Groups + +[GORLEV] +Gore Level + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[PARSHP] +Parse Heap + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[SCASSL] +Sick Fuck Selected + +[SCSCSL] +Sick Fucker Selected + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[SICSIC] +Sick Fucker + +[SICASS] +Sick Fuck + +[FEB_SAV] +Charger + +[FEP_SAV] +CHARGER PARTIE + +[AS2_12A] +~g~Quand t'auras renversé le premier étal, t'auras plus que 8 minutes avant que le Cartel prévienne ses revendeurs! + +[AS3_1A] +~g~Et maintenant, rejoins la ~b~bouée repère! + +[NOCONT] +Reconnecte une manette analogique (DUALSHOCK#) ou manette analogique (DUALSHOCK#2) au port de manette 1 pour continuer. + +[BET_JB] +TRAHI PAR CATALINA, SA PETITE AMIE, ET LAISSE POUR MORT. JUGE COUPABLE ET CONDAMNE A UNE PEINE DE PRISON, IL COMMENCE SA PEINE AU PENITENCIER DE LIBERTY. MAIS UNE SEULE ET UNIQUE PENSEE LE HANTE...... LA VENGEANCE ! + +[END_A] +Les habitants de Cedar Grove subissent les conséquences + +[END_B] +psychologiques de l'attentat qui a frappé + +[END_C] +leur quartier, hier. + +[END_D] +Un riverain, Clive Denver, a donné à la police la description + +[END_E] +d'un homme armé qu'il a vu fuir du secteur, accompagné d'une femme aux cheveux noirs. + +[END_F] +Oh, tu sais, on va bien s'amuser, parce que, tu sais, oui, tu le sais, + +[END_G] +je t'aime, hein, je t'aime de tout mon coeur, tu es si beau, si fort, + +[END_H] +tu es l'homme que j'ai toujours rêvé d'avoir à mes côtés ! + +[END_I] +Peu importe, j'en étais où, moi ? + +[END_J] +Oh, je sais plus. Mais tu sais ce que c'est, hein ? + +[END_K] +Les explosions ont retenti près des habitations, semant Le trouble dans le quartier. Les gens couraient dans tous les sens à la recherche d'un abri. + +[END_L] +Plusieurs civils ont été blessés dans la panique alors que les forces de l'ordre + +[END_M] +échangeaient des coups de feu avec un hélicoptère qui survolait le barrage. + +[END_N] +Ouais, d'ici, on a une vue imprenable sur les jardins. + +[END_O] +Lorsque l'hélico a enfin été bousillé, + +[END_P] +c'était encore plus grandiose qu'un feu d'artifices ! + +[END_Q] +On recense déjà plus de vingt morts mais + +[END_R] +la police continue à découvrir des corps sous les décombres. + +[END_S] +Les rumeurs selon lesquelles les morts appartenaient au cartel des Colombiens + +[END_T] +n'ont pas été officiellement démenties. + +[END_U] +Il n'y a toujours aucune piste qui expliquerait la raison de ce massacre. + +[END_V] +Je me suis cassé un ongle et ma mise en plis est foutue ! Tu le crois, ça ? + +[END_W] +Ca m'avait coûté 50 dollars... + +[PAPER1] +UN CRIMINEL TRAHI PAR SA PETITE AMIE ET COMPLICE. LES JURES RECONNAISSENT, LE VOLEUR ARME, COUPABLE A L'UNANIMITE! + +[PAPER2] +LOVE, CONDAMNE A DIX ANS FERME ! + +[FEB_CPC] +Configuration des commandes + +[FEC_PED] +Commandes à pied + +[FEC_VEH] +Commandes des véhicules + +[FEC_FPR] +Commandes en vue subjective + +[FEC_CMM] +Commandes principales + +[FEC_PWL] +Aller à gauche + +[FEC_PWR] +Aller à droite + +[FEC_PWF] +Avancer + +[FEC_PWT] +Avancer vers caméra + +[FEC_PLB] +Vue arrière + +[FEC_PFR] +Tirer + +[FEC_CLE] +Défilement Gauche des armes + +[FEC_CRI] +Défilement Droite des armes + +[FEC_LKT] +Verrouiller cible + +[FEC_PJP] +Saut à pied + +[FEC_PSP] +Sprint à pied + +[FEC_PSH] +Tir à pied + +[FEC_TLF] +Cible suivante Gauche + +[FEC_TRG] +Cible suivante Droite + +[FEC_CCM] +Centrer caméra derrière joueur + +[FEC_SZI] +Fusil à lunette zoom avant + +[FEC_SZO] +Fusil à lunette zoom arrière + +[FEC_LKL] +Regarder à gauche en vue subjective + +[FEC_LRT] +Regarder à droite en vue subjective First Person Look Right + +[FEC_LUP] +Regarder en haut en vue subjective + +[FEC_LDN] +Regarder en bas en vue subjective + +[FEC_LBH] +Regarder derrière le véhicule + +[FEC_LLF] +Regarder à gauche du véhicule + +[FEC_LRG] +Regarder à droite du véhicule + +[FEC_HRN] +Klaxon + +[FEC_HBR] +Frein à main + +[FEC_ACL] +Accélérer + +[FEC_BRK] +Freiner + +[FEC_TSM] +Activer/Désactiver sous-missions + +[FEC_CRD] +Changer la station de radio + +[FEC_ENT] +Entrer/Sortir d'un véhicule + +[FEC_WPN] +Tirer + +[FEC_PAS] +Pause + +[FEC_FPO] +Changer d'arme en vue subjective + +[FEC_SMS] +Afficher/Masquer curseur + +[FEC_CMS] +Changer de mode de caméra + +[FEC_TSS] +Faire une capture d'écran + +[FEN_NET] +Réseau + +[FEN_CON] +Connexion + +[FEN_GAM] +Trouver partie + +[FEN_TYP] +Type de partie + +[FEN_TY0] +Deathmatch + +[FEN_TY1] +Furtif en Deathmatch + +[FEN_TY2] +Deathmatch par équipes + +[FEN_TY3] +Furtif en Deathmatch par équipes + +[FEN_TY4] +Planquer l'argent + +[FEN_TY5] +Capturer le drapeau + +[FEN_TY6] +Rat Race + +[FEN_TY7] +Domination + +[FEN_NAM] +Nom : + +[FEN_GNA] +Nom partie : + +[FEM_MAP] +Choisir carte + +[FEN_PLS] +Réglages joueur + +[FEN_PLC] +Couleur joueur + +[FEM_MA0] +Liberty City + +[FEM_MA1] +Le Quartier Rouge + +[FEM_MA2] +Chinatown + +[FEM_MA3] +La Tour + +[FEM_MA4] +Le Dépotoir + +[FEM_MA5] +Le Parc Industriel + +[FEM_MA6] +Les Docks + +[FEM_MA7] +Staunton + +[FEC_EMS] +Touches clavier uniquement + +[FEC_DBG] +Menu Debug + +[FEC_TGD] +Alterner manette jeu/debug + +[FEC_TDO] +Désactiver caméra debug + +[FEC_IVH] +Inverser souris horizontale + +[FEC_MSL] +BGS + +[FEC_MSM] +BMS + +[FEC_MSR] +BDS + +[FEC_QUE] +??? + +[FEC_TWO] +Deux touches clavier au maximum + +[FEC_UMS] +Boutons souris uniquement + +[FEC_OMS] +Un bouton souris au maximum + +[FEC_UJS] +Un bouton joystick au maximum + +[FEC_OJS] +Un bouton joystick maximum par action + +[FEC_PTL] +Utiliser verrouillage de cible avec commande de tir gauche + +[FEC_PTR] +Utiliser verrouillage de cible avec commande de tir droite + +[FEC_LBC] +Utiliser regarder gauche avec regarder droite + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +Impossible de mettre en pause en multijoueur. Appuyez deux fois pour quitter ! + +[FEM_SL1] +Emplacement 1 libre + +[FEM_SL2] +Emplacement 2 libre + +[FEM_SL3] +Emplacement 3 libre + +[FEM_SL4] +Emplacement 4 libre + +[FEM_SL5] +Emplacement 5 libre + +[FEM_SL6] +Emplacement 6 libre + +[FEM_SL7] +Emplacement 7 libre + +[FEM_SL8] +Emplacement 8 libre + +[FEM_MM] +MENU PRINCIPAL + +[FEM_SNG] +Nouvelle partie + +[FEM_QTW] +Quitter + +[FEQ_SRE] +Etes-vous sûr de vouloir quitter ? Votre progression depuis la dernière sauvegarde sera perdue. Continuer ? + +[FEQ_SRW] +Etes-vous sûr de vouloir quitter la partie ? + +[FEG_SRV] +SERVEUR + +[FEG_MAP] +CARTE + +[FEG_PLY] +JOUEURS + +[FEG_TYP] +TYPE + +[FEG_PNG] +PING + +[FET_FG] +TROUVER PARTIE + +[FET_SP] +SOLO + +[FET_MP] +MULTIJOUEUR + +[FET_HG] +CREER UNE PARTIE + +[FET_PS] +CONFIG. JOUEURS + +[FET_CON] +CONNEXION + +[FET_AUD] +CONFIG. AUDIO + +[FET_DIS] +CONFIG. AFFICHAGE + +[FET_LAN] +CHOIX LANGUE + +[FET_LG] +CHARGER PARTIE + +[FET_DG] +SUPPRIMER PARTIE + +[FET_NG] +NOUVELLE PARTIE + +[FET_SG] +SAUVEGARDER PARTIE + +[FET_MAP] +CHOISIR CARTE + +[FET_GT] +TYPE DE PARTIE + +[FET_CTL] +CONFIG. PERIPHERIQUE + +[FET_OPT] +OPTIONS + +[FET_QG] +QUITTER PARTIE + +[FET_STA] +STATISTIQUES + +[FET_BRE] +BRIEFINGS + +[FEC_WAR] +Avertissement + +[FEC_OKK] +O.K. + +[FED_CON] +Confirmation de suppression de fichier + +[FES_SSC] +Sauvegarde de la partie effectuée + +[DEL_FNM] +Suppression du fichier effectuée + +[PCLOAD] +Chargement des données du fichier + +[PCRESRT] +Redémarrage de Grand Theft Auto III + +[FEC_DLF] +Erreur lors de suppression + +[FEC_SVU] +Erreur lors de la sauvegarde + +[FEC_LUN] +Erreur lors du chargement. Fichier corrompu, veuillez le supprimer. + +[FEN_PLA] +Nombre de joueurs : + +[FET_NON] +AUCUNE PARTIE DISPONIBLE + +[FET_SFG] +RECHERCHE DE PARTIES... + +[FET_SRT] +TRI DES PARTIES... + +[FEF_LAN] +RESEAU + +[FEF_INT] +INTERNET + +[FET_REF] +Rafraîchir + +[FET_FIL] +Filtre + +[FET_JG] +Rejoindre + +[FEC_NTW] +Talk To Network + +[FEC_ESR] +Utilisation restreinte de la touche Echap + +[FEC_GSL] +Show head bob: + +[FIL_FLT] +FILTRER LISTE DES PARTIES + +[FET_SAN] +NOUVELLE PARTIE + +[FIL_MAP] +Carte : + +[FIL_SRV] +Serveur : + +[FIL_TYP] +Type de partie : + +[FIL_SPC] +Parties avec espace disponible ? + +[FIL_PNG] +Ping : + +[FEN_UKH] +Hôte inconnu + +[FEN_UKM] +Carte non trouvée + +[FEN_UKT] +Type de partie non trouvé + +[FEN_NCI] +VOUS N'ETES PAS CONNECTE A INTERNET + +[FET_PAU] +MENU PAUSE + +[FET_SGA] +COMMENCER PARTIE + +[FEC_SGJ] +Régler joystick + +[FEC_PAD] +Manette + +[FEC_JOY] +Joystick + +[FEC_WHL] +Volant + +[FEC_CNT] +Type de périphérique : + +[FET_APL] +APPLIQUER + +[FES_CSA] +Sélectionnez une apparence dans la liste suivante : + +[FES_SKN] +NOM DE L'APPARENCE + +[FES_DAT] +DATE + +[FES_NON] +AUCUNE APPARENCE DISPONIBLE + +[FEA_FM9] +LECTEUR MP3 + +[FESZ_QZ] +Etes-vous sûr de vouloir sauvegarder cette partie ? + +[FES_CGA] +Emplacements disponibles : + +[FES_SCG] +Sauvegarder la partie actuelle ? + +[FES_LCG] +Charger la partie et continuer à jouer ? + +[FEC_FIR] +Tirer + +[FEC_NWE] +Arme suivante + +[FEC_PWE] +Arme précédente + +[FEC_FOR] +Avant + +[FEC_BAC] +Arrière + +[FEC_LEF] +Gauche + +[FEC_RIG] +Droite + +[FEC_ZIN] +Zoom avant + +[FEC_ZOT] +Zoom arrière + +[FEC_EEX] +Entrer+sortir + +[FEC_RAD] +Radio + +[FEC_SUB] +Sous-mission + +[FEC_CMR] +Changer caméra + +[FEC_JMP] +Sauter + +[FEC_SPN] +Sprint + +[FEC_HND] +Frein à main + +[FEC_TUL] +Tourelle gauche + +[FEC_TUR] +Tourelle droite + +[FEC_LOL] +Regarder à gauche + +[FEC_LOR] +Regarder à droite + +[FEC_NTR] +Cible suivante + +[FEC_PTT] +Cible précédente + +[FEC_LBA] +Regarder en arrière + +[FEC_CEN] +Centrer caméra + +[FEC_UND] +(NON) + +[FET_CFT] +A PIED + +[FET_CCR] +EN VOITURE + +[CVT_MSG] +Conversion des textures vers un format optimal pour votre carte graphique + +[FET_CAC] +ACTION + +[FEC_IBT] +- + +[FEC_SPC] +ESP + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +NON UTILISE + +[FET_CME] +TYPE DE COMMANDES + +[FET_RDK] +REDEFINIR COMMANDES + +[FET_AMS] +PARAMETRES SOURIS + +[FET_STI] +CONFIG. COMMANDES STANDARD + +[FET_CTI] +CONFIG. COMMANDES NORMALES + +[FET_MTI] +CONFIG. SOURIS + +[FET_DAM] +MODELAGE ACCOUST. DYNAMIQUE + +[FEC_TFL] +Tourelle Gauche + +[FEC_TFR] +Tourelle Droite + +[FEC_TFU] +Tourelle /Dodo Haut + +[FEC_TFD] +Tourelle /Dodo Bas + +[FEC_MWF] +MOLETTE HAUT + +[FEC_MWB] +MOLETTE BAS + +[FEC_ORR] +ou + +[FEC_NUS] +NON UTILISE + +[FEC_LUD] +Regarder Haut + +[FEC_LDU] +Regarder Bas + +[FEC_CMP] +COMBO : REGARDER G+D + +[FEC_NTT] +No Text Yet For This Key + +[FEC_FNC] +F~1~ + +[FEC_IRT] +INSER + +[FEC_DLL] +SUPPR + +[FEC_HME] +ORIG + +[FEC_END] +FIN + +[FEC_PGU] +PAGE HAUT + +[FEC_PGD] +PAGE BAS + +[FEC_UPA] +HAUT + +[FEC_DWA] +BAS + +[FEC_LFA] +GAUCHE + +[FEC_RFA] +DROITE + +[FEC_NUM] +PAV.NUM + +[FEC_NMN] +PAV.NUM~1~ + +[FEC_FWS] +PAV.NUM / + +[FEC_PLS] +PAV.NUM + + +[FEC_MIN] +PAV.NUM - + +[FEC_DOT] +PAV.NUM . + +[FEC_NLK] +VERR NUM + +[FEC_ETR] +ENTR + +[FEC_SLK] +ARRET DEFIL + +[FEC_PSB] +PAUSE + +[FEC_BSP] +RET. ARR. + +[FEC_TAB] +TAB + +[FEC_CLK] +VERR MAJ + +[FEC_RTN] +RETOUR + +[FEC_LSF] +MAJ. G + +[FEC_RSF] +MAJ. D + +[FEC_LCT] +CTRL G + +[FEC_RCT] +CTRL D + +[FEC_LAL] +ALT G + +[FEC_RAL] +ALT D + +[FEC_LWD] +WIN G + +[FEC_RWD] +WIN D + +[FEC_WRC] +CLIC WIN + +[WIN_TTL] +Grand Theft Auto III + +[WIN_95] +Grand Theft Auto III n'est pas compatible WINDOWS 95 + +[WIN_DX] +Grand Theft Auto III requiert la version 8.1 de DirectX minimum. + +[WIN_VDM] +Grand Theft Auto III requiert au moins 12 Mo de mémoire vidéo libre. + +[DIAB3_G] +Arriba ! + +[FEM_RES] +REPRENDRE PARTIE + +[FES_SNG] +NOUVELLE PARTIE + +[FEM_SP] +MODE SOLO + +[FEM_MP] +MODE MULTIJOUEUR + +[FEM_QT] +QUITTER + +[FES_SG] +NOUVELLE PARTIE + +[FES_LG] +CHARGER PARTIE + +[FEM_HST] +HEBERGER PARTIE + +[FEM_OPT] +OPTIONS + +[FEM_DBG] +DEBUG + +[FET_PSU] +PARAMETRES JOUEUR + +[FET_DEF] +PAR DEFAUT + +[FED_BRI] +LUMINOSITE + +[FED_TRA] +TRAINEES + +[FEM_LOD] +DISTANCE MODELES + +[FEM_VSC] +SYNCHRO VIDEO + +[FEM_FRM] +RESTRICTION VIDEO + +[FED_RES] +RESOLUTION ECRAN + +[FED_WIS] +PLEIN ECRAN + +[FEDS_TB] +RETOUR + +[FEA_MUS] +MUSIQUE + +[FEA_SFX] +EFFETS SPECIAUX + +[FEA_RSS] +STATION RADIO + +[FEL_ENG] +ANGLAIS + +[FEL_FRE] +FRANCAIS + +[FEL_GER] +ALLEMAND + +[FEL_ITA] +ITALIEN + +[FEL_SPA] +ESPAGNOL + +[FEA_3DH] +CONFIG. CARTE-SON + +[FEA_SPK] +CONFIG. HAUT-PARLEURS + +[FEA_2SP] +2 HAUT-PARLEURS + +[FEA_4SP] +PLUS DE 2 HAUT-PARLEURS + +[FEA_EAR] +CASQUE + +[FEA_NAH] +PAS DE CARTE-SON + +[FET_SNG] +NOUVELLE PARTIE + +[FEN_STA] +COMMENCER PARTIE + +[GMLOAD] +CHARGER PARTIE + +[GMSAVE] +SAUVEGARDER PARTIE + +[FES_DGA] +EFFACER PARTIE + +[FEM_NON] +AUCUN + +[FEC_IVV] +INVERSER SOURIS VERTIC. + +[FEC_MSH] +SENSIBILITE SOURIS + +[FET_CCN] +COMMANDES : NORMALES + +[FET_SCN] +COMMANDES : STANDARD + +[FES_SET] +UTILISER MODELE + +[GHOST] +Fantôme + +[WIN_RSZ] +Impossible de choisir la nouvelle résolution. + +[FET_APP] +BGS, RETOUR POUR APPLIQUER LE NOUV. PARAMETRE + +[FET_HRD] +PARAMETRES PAR DEFAUT RETABLIS + +[FET_MST] +DIRECTION CONTROLEE PAR LA SOURIS + +[FEC_STR] +ETOILE PAV.NUM. + +[FET_MIG] +GAUCHE, DROITE, MOLETTE SOURIS POUR REGLER + +[FET_CIG] +RETOUR ARRIERE POUR EFFACER - BGS, RETOUR POUR CHANGER + +[FET_RIG] +SELECTIONNEZ NOUV. TOUCHE POUR CETTE ACTION OU ECHAP POUR ANNULER + +[FET_EIG] +IMPOSSIBLE DE PARAMETRER UNE TOUCHE POUR CETTE ACTION + +[NO_PCCD] +Insérez le disque 2 de Grand Theft Auto III dans le lecteur ou appuyez sur ECHAP pour annuler. + +[CVT_ERR] +Espace disque épuisé. Libérez de la mémoire sur votre disque dur pour continuer. Appuyez sur ECHAP pour annuler. + +[FED_SUB] +SOUS-TITRES + +[FET_DSN] +Skin joueur par défaut.bmp + +[JM3] +'HAUT LES MAINS' + +[EBAL] +'A MOI LIBERTY' + +[LM4] +'MAQUEREAU EN BOITE' + +[REPLAY] +RALENTI + +[FEC_SFT] +MAJ + +[CRED254] +RESPONSABLE STUDIO + +[CVT_CRT] +Pour convertir les textures pour votre carte graphique, connectez-vous à un compte Administrateur. Pour quitter, appuyez sur ECHAP. + +[FEM_ON] +AVEC + +[FEM_OFF] +SANS + +[FEM_YES] +OUI + +[FEM_NO] +NON + +[FES_WAR] +Sauvegarde en cours... + +[FED_DLW] +Suppression en cours... + +[FED_LDW] +Chargement en cours... + +[FEC_SLC] +Emplacement corrompu + +[FED_LFL] +Echec du chargement de la sauvegarde. La partie va être relancée. + +[FET_RSO] +PARAMETRE D'ORIGINE RETABLI + +[FET_RSC] +MATERIEL INDISPONIBLE - PARAMETRE D'ORIGINE RETABLI + +[CRED270] +MIKE HONG + +{ re3 updates } +{ new languages } +[FEL_JAP] +JAPONAIS + +[FEL_POL] +POLONAIS + +[FEL_RUS] +RUSSE + +{ new display menus } +[FET_GFX] { this probably needs to be retranslated } +CONFIG. EFFETS SPECIAUX + +[FED_MIP] +MIP MAPPING + +[FED_AAS] +ANTI ALIASING + +[FED_FIL] +TEXTURE FILTERING + +[FED_BIL] +BILINEAR + +[FED_TRL] +TRILINEAR + +[FED_WND] +WINDOWED + +[FED_FLS] +FULLSCREEN + +[FEM_CSB] +CUTSCENE BORDERS + +[FEM_SCF] +SCREEN FORMAT + +[FEM_ISL] +MAP MEMORY USAGE + +[FEM_LOW] +LOW + +[FEM_MED] +MEDIUM + +[FEM_HIG] +HIGH + +[FEM_2PR] +PS2 ALPHA TEST + +[FEC_FRC] +FREE CAM + +{ Linux joy detection } +[FEC_JOD] +DETECT JOYSTICK + +[FEC_JPR] +Press any key on the joystick of your choice that you want to use on the game, and it will be selected. + +[FEC_JDE] +Detected joystick + +{ mission restart } +[FET_RMS] +REJOUER MISSION + +[FESZ_RM] +REJOUER? + +{ more graphics } +[FED_VPL] +VEHICLE PIPELINE + +[FED_PRM] +PED RIM LIGHT + +[FED_RGL] +ROAD GLOSS + +[FED_CLF] +COLOUR FILTER + +[FED_WLM] +WORLD LIGHTMAPS + +[FED_MBL] +MOTION BLUR + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MOBILE + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +AUTO + +{ controls } +[FEC_IVP] +INVERT PAD VERTICALLY + +{ map } +[FEM_TWP] +Toggle Waypoint + +[FEA_FMN] +RADIO ETEINTE + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +XBOX 360 CONTROLLER + +[FEC_ONE] +XBOX ONE CONTROLLER + +[FEC_NSW] +NINTENDO SWITCH CONTROLLER + +[FEC_TYP] +GAMEPAD TYPE + +[FEC_CCF] +CONFIGURATION + +[FEC_CF1] +CONFIGURATION 1 + +[FEC_CF2] +CONFIGURATION 2 + +[FEC_CF3] +CONFIGURATION 3 + +[FEC_CF4] +CONFIGURATION 4 + +[FEC_CDP] +AFFICHAGE DE LA MANETTE + +[FEC_ONF] +A PIED + +[FEC_INC] +DANS UN VÉHICULE + +[FEC_VIB] +VIBRATIONS + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +{ end of file } + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/german.txt b/utils/gxt/german.txt new file mode 100644 index 0000000..b77cb17 --- /dev/null +++ b/utils/gxt/german.txt @@ -0,0 +1,8193 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. +} + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[ARSE] +ü ß ä + +[IN_VEH] +~g~Hey! Zurück ins Auto!! + +[IN_VEH2] +~g~Du brauchst einen Schlitten für diesen Job! + +[IN_BOAT] +~g~Du brauchst ein Boot für diesen Job! + +[HEY] +~g~Keine Alleingänge. Halt die Gang beisammen! + +[HEY2] +~g~Nicht aufteilen. Halt die Leute zusammen! + +[HEY3] +~g~Du hast deinen besten Mann verloren. Los, zurück! Hol 8-Ball! + +[HEY4] +~g~Wenn du Misty verlierst, kriegst du's mit Luigi zu tun. Los, hol sie. + +[HEY5] +~g~Eines der Girls fehlt. Los, zurück! Treib das Mädchen auf! + +[HEY6] +~g~Du stehst mit deiner Ehre für den Yakuza Kanbu ein. Du musst ihn beschützen! + +[HEY7] +~g~Ein Mann mehr kann nicht schaden. Los, zurück. Hol deinen Kontaktmann ab! + +[HEY8] +~g~Beschützen heißt so viel wie beschützen - Beschütze den alten Asiaten! + +[HEY9] +~g~Du willst wissen, was so geredet wird? Sprich mit deinem Kontaktmann! + +[HELP2_A] +Drücke die ~h~/-Taste~w~, um zu ~h~sprinten. + +[HELP3] +Du kannst nur kurze Zeit sprinten, ohne müde zu werden. + +[HELP4_A] +Drücke die~h~ ~k~~VEHICLE_ACCELERATE~-Taste~w~, um zu ~h~beschleunigen. + +[HELP4_D] +Drücke den~h~ Rechten Analog-Stick nach oben, um zu ~h~beschleunigen. + +[HELP5_A] +Drücke die~h~ ~k~~VEHICLE_BRAKE~-Taste~w~, um zu ~h~bremsen~w~, oder um ~h~zurückzusetzen~w~, wenn das Fahrzeug steht. + +[HELP5_D] +Zieh den ~h~Rechten Analog-Stick~w~ zurück, um zu ~h~bremsen~w~, oder um ~h~zurückzusetzen~w~, wenn das Fahrzeug steht. + +[HELP6_A] +Drücke die~h~ ~k~~VEHICLE_HANDBRAKE~-Taste~w~, um die ~h~Handbremse anzuziehen. + +[HELP6_C] +Drücke die~h~ ~k~~VEHICLE_HANDBRAKE~-Taste~w~, um die ~h~Handbremse anzuziehen. + +[HELP6_D] +Drücke die~h~ ~k~~VEHICLE_HANDBRAKE~-Taste~w~, um die ~h~Handbremse anzuziehen. + +[HELP7_A] +Halte die~h~ ~k~~PED_LOCK_TARGET~-Taste ~w~gedrückt, um mit dem Präzisionsgewehr zu zielen. + +[HELP7_D] +Halte die~h~ ~k~~PED_LOCK_TARGET~-Taste ~w~gedrückt, um mit dem Präzisionsgewehr zu zielen. + +[HELP8_A] +Drücke die~h~ ~k~~PED_SNIPER_ZOOM_IN~-Taste~w~, um ~h~an das Ziel heranzuzoomen ~w~und die~h~ ~k~~PED_SNIPER_ZOOM_OUT~-Taste~w~,um ~h~herauszuzoomen ~w~. + +[HELP9_A] +Drücke die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um das Präzisionsgewehr abzufeuern. + +[HELP10] +Dieser Stern zeigt an, dass du von der Polizei gesucht wirst. + +[HELP11] +Je mehr Sterne, desto dringender wirst du gesucht. + +[HELP13] +Manchmal musst du vielleicht Wege finden, die das Radar nicht zeigt. + +[TIMER] +Diese Mission hat ein Zeitlimit. Du musst sie beendet haben, bevor die Zeit um ist. + +[MISTY1] +~r~Misty ist hinüber! + +[OUT_VEH] +~g~Raus aus dem Fahrzeug! + +[GARAGE] +Fahr den Wagen in eine Garage und geh dann nach draußen. + +[WANTED1] +~g~Schüttle die Cops ab. Verringere deinen Fahndungslevel. + +[NODOORS] +~g~Das sind keine Sardinen! Besorg einen Wagen mit ausreichend Sitzplätzen. + +[TRASH] +~g~Du hast deine Karre ziemlich geschrottet! Repariere sie! + +[WRECKED] +~r~Das Fahrzeug ist Schrott! + +[HORN] +~g~Drück auf die Hupe. + +[HORN4] +Drück die ~h~L3-Taste~w~, um zu hupen. + +[NOMONEY] +~g~Du brauchst mehr Cash! + +[OUTTIME] +~r~Zu langsam, Mann, zu langsam! + +[SPOTTED] +~r~Sie sind dir auf den Fersen! + +[REWARD] +BELOHNUNG $~1~ + +[GAMEOVR] +GAME OVER + +[Z] +Z-Achse Wert: ~1~ + +[M_FAIL] +MISSION FEHLGESCHLAGEN! + +[M_PASS] +MISSION ERFÜLLT! $~1~ + +[O_PASS] +JOB ERLEDIGT! + +[O_FAIL] +JOB FEHLGESCHLAGEN! + +[DEAD] +AUSSER GEFECHT! + +[BUSTED] +VERHAFTET! + +[S_PROMP] +Außerhalb einer Mission kannst du dein ~h~Spiel hier speichern~w~. Dies rückt die Uhr um sechs Stunden vor. + +[NUMBER] +~1~ + +[SCORE] +$~1~ + +[LOADCAR] +LADE FAHRZEUG... (ABBRECHEN MIT L1) + +[CARSOFF] +Deaktivierte Fahrzeuge. + +[CARS_ON] +Aktivierte Fahrzeuge. + +[TEXTXYZ] +Schreibe Koordinaten in Datei... + +[CHEATON] +Cheat Modus AN + +[CHEATOF] +Cheat Modus AUS + +[UZI_IN] +Die Uzi ist jetzt im AmmuNation zu haben! + +[IMPORT1] +Geh nach draußen und warte auf dein Fahrzeug. + +[PAGEB1] +Pistole wurde im Versteck angeliefert. + +[PAGEB2] +Uzi wurde im Versteck angeliefert. + +[PAGEB3] +Kugelsichere Weste wurde im Versteck angeliefert. + +[PAGEB4] +Schrotflinte wurde im Versteck angeliefert. + +[PAGEB5] +Granaten wurden im Versteck angeliefert. + +[PAGEB6] +Molotowcocktails wurden im Versteck angeliefert. + +[PAGEB7] +AK47 wurde im Versteck angeliefert. + +[PAGEB8] +Präzisionsgewehr wurde im Versteck angeliefert. + +[PAGEB9] +M16 wurde im Versteck angeliefert. + +[PAGEB10] +Raketenwerfer wurde im Versteck angeliefert. + +[PAGEB11] +Flammenwerfer wurde im Versteck angeliefert. + +[WANT_A] +Verhaftet wirst du nur, wenn die Polizei nach dir ~h~fahndet. + +[WANT_B] +Dein ~h~Fahndungslevel~w~ wird durch die Reihe von Sternen oben rechts auf dem Bildschirm dargestellt. + +[WANT_C] +Du hast jetzt einen ~h~Fahndungslevel~w~ von eins... + +[WANT_D] +zwei... + +[WANT_E] +drei... + +[WANT_F] +Steigt dein ~h~Fahndungslevel~w~, wirst du von besser ausgebildeten Polizisten gejagt. + +[WANT_G] +Wirst du ~h~verhaftet~w~, wirst du zum nächsten Polizeirevier gebracht. + +[WANT_H] +Die Cops werden dir alle Waffen abnehmen und kassieren ein wenig Bestechungsgeld von dir. + +[WANT_I] +Wenn dir das auf einer Mission passiert, ist die Mission fehlgeschlagen. + +[WANT_J] +Im Verlauf des Spiels wirst du Möglichkeiten entdecken, deinen Fahndungslevel zu reduzieren. + +[WANT_K] +Wenn du in einem Wagen sitzt, werden ~h~LACKIEREREIEN~w~ den Fahndungslevel ~h~annullieren. + +[HEAL_B] +Wenn du ~h~'außer Gefecht'~w~ bist, wirst du zur nächsten Klinik gebracht. + +[HEAL_C] +Du verlierst alle Waffen, und die Ärzte knöpfen dir ein wenig Cash für die Behandlung ab. + +[HEAL_E] +Je länger du spielst, desto mehr Wege wirst du finden, dich selbst zu verarzten oder zu schützen. + +[DAM] +SCHADEN: + +[KILLS] +HITS: + +[FARES] +FAHRTEN + +[BULL] +GOLDBARREN + +[EVID] +BEWEISMITTEL + +[HEALTH] +ZUSTAND AUTO + +[COLLECT] +GESAMMELT: + +[BOMB] +Fahr deinen Wagen in die Bombenwerkstatt, um eine ~h~Bombe~w~ anzubringen. Kosten - ~h~$1000. + +[SAVE1] +Geh durch den Eingang. So kannst du dein ~h~Spiel speichern~w~. Während einer Mission kannst du nicht speichern. + +[SAVE2] +Jedes Fahrzeug, das in dieser Garage abgestellt wird, wird für dich aufbewahrt, wenn das Spiel gespeichert wird. + +[AMMU] +Betritt den AmmuNation-Laden, um eine Waffe zu kaufen. + +[BRIDGE1] +Wenn die Callahan Bridge repariert ist, kannst du nach Staunton Island rüber fahren. + +[TUNNEL] +Wenn der Porter Tunnel geöffnet ist, kannst du nach Staunton Island rüber fahren. + +[LUIGI] +LUIGI MISSIONEN + +[TONI] +TONI MISSIONEN + +[JOEY] +JOEY MISSIONEN + +[FRANK] +SALVATORE MISSIONEN + +[DIABLO] +DIABLO MISSIONEN + +[ASUKA] +ASUKA MISSIONEN + +[B_SITE] +ASUKA VORSTADT-MISSIONEN + +[KENJI] +KENJI MISSIONEN + +[RAY] +RAY MISSIONEN + +[LOVE] +LOVE MISSIONEN + +[YARDIE] +YARDIE MISSIONEN + +[HOOD] +HOOD MISSIONEN + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Atlantic Quays + +[PORT_E] +Portland Harbor + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Portland Beach + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Rotlichtbezirk + +[TOWERS] +Hepburn Heights + +[HARWOOD] +Harwood + +[ROADBR1] +Callahan Bridge + +[ROADBR2] +Callahan Bridge + +[TUNNELP] +Porter Tunnel + +[BOMB1] +8-Balls Werkstatt + +[COM_ZON] +Staunton Island + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Liberty Campus + +[CONSTRU] +Fort Staunton + +[PARK] +Belleville Park + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Francis Int. Airport + +[PROJECT] +Wichita Gardens + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Cochrane Dam + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Krankenwagen + +[CAR_2] +Feuerwehrwagen + +[CAR_3] +Polizei + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Rhino + +[CAR_7] +FBI-Wagen + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_10] +Kleinbus + +[CAR_11] +Lkw + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch Limo + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Cabbie + +[CAR_39] +Buggy + +[LUIGIS] +Luigis Club + +[GOAWAY] +~g~Du bist bereits auf einer Mission! + +[LUIGGO] +~g~Luigi checkt gerade ein paar neue Girls aus. Komm später wieder! + +[JOEYGO] +~g~Joey ist mit Misty in der Stadt unterwegs. Komm später wieder! + +[TONIGO] +~g~Toni ist mit seiner Mamma in der Oper. Probier's ein andermal! + +[KEMUGO] +~g~Maria und Kemuri sind gerade beschäftigt. Versuch's später nochmal! + +[KENJGO] +~g~Kenji ist bei einem Yakuza-Treffen. Schau ein andermal wieder vorbei. + +[RAYGO] +~g~Ray hängt gerade auf irgend einem anderen Klo rum. Komm später wieder! + +[LOVEGO] +~g~Donald Love hat anderes zu tun. Vielleicht hat er später Zeit! + +[KENSGO] +~g~Kenji hat zu tun! Komm später wieder! + +[ASUSGO] +~g~Asuka hat gerade überhaupt keine Zeit! + +[HOODGO] +~g~Die Hoods haben gerade keine Zeit! + +[WRONGT1] +~g~Komm zwischen 05:00 und 21:00 wieder. Dann gibt's einen Job. + +[WRONGT2] +~g~Komm zwischen 06:00 und 14:00 wieder. Dann gibt's einen Job. + +[WRONGT3] +~g~Komm zwischen 15:00 und 00:00 wieder. Dann gibt's einen Job. + +[GUN_1A] +Benutze die ~h~~k~~PED_CYCLE_WEAPON_RIGHT~-Taste ~w~und die ~h~~k~~PED_CYCLE_WEAPON_LEFT~-Taste~w~, um zwischen deinen Waffen zu wechseln. + +[GUN_2A] +Halte die ~h~~k~~PED_LOCK_TARGET~-Taste ~w~gedrückt, um automatisch zu zielen. Drücke die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu feuern! Versuch, die Ziele zu treffen... + +[GUN_2C] +Halte die ~h~~k~~PED_LOCK_TARGET~-Taste ~w~gedrückt, um automatisch zu zielen. Drücke die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu feuern! Versuch, die Ziele zu treffen... + +[GUN_2D] +Halte die ~h~~k~~PED_LOCK_TARGET~-Taste ~w~gedrückt, um automatisch zu zielen. Drücke die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu feuern! Versuch, die Ziele zu treffen... + +[GUN_3A] +Halte die ~h~~k~~PED_LOCK_TARGET~-Taste~w~ gedrückt und drücke die ~h~~k~~PED_CYCLE_TARGET_LEFT~-Taste~w~ oder die ~h~~k~~PED_CYCLE_TARGET_RIGHT~-Taste, um das Ziel zu wechseln. + +[GUN_3B] +Halte die ~h~~k~~PED_LOCK_TARGET~-Taste~w~ gedrückt und drücke die ~h~~k~~PED_CYCLE_TARGET_LEFT~-Taste~w~ oder die ~h~~k~~PED_CYCLE_TARGET_RIGHT~-Taste, um das Ziel zu wechseln. + +[GUN_4A] +Mit gedrückter ~h~~k~~PED_LOCK_TARGET~-Taste~w~ kannst du gehen oder laufen und behältst dein Ziel im Visier. + +[GUN_4B] +Mit gedrückter ~h~~k~~PED_LOCK_TARGET~-Taste~w~ kannst du gehen oder laufen und behältst dein Ziel im Visier. + +[GUN_5] +An diesen Pappkameraden kannst du zielen und schießen üben. Wenn du fertig bist, widme dich wieder deiner Mission. + +[TAXI1] +~g~Besorg dir einen Passagier. + +[FARE1] +~g~Fahrziel: ~w~'Meeouch Sex Kitten Club' ~g~im Rotlichtbezirk. + +[FARE2] +~g~Fahrtziel: ~w~'Supa Save' ~g~in Portland View. + +[FARE3] +~g~Fahrtziel: ~w~'Alte Schulhalle' ~g~in Chinatown. + +[FARE4] +~g~Fahrtziel: ~w~'Greasy Joe's Cafe' ~g~in Callahan Point. + +[FARE5] +~g~Fahrtziel: ~w~'AmmuNation' ~g~im Rotlichtbezirk. + +[FARE6] +~g~Fahrtziel: ~w~'Easy Credit Autos' ~g~in Saint Mark's. + +[FARE7] +~g~Fahrtziel: ~w~'Woody's Topless Bar' ~g~im Rotlichtbezirk. + +[FARE8] +~g~Fahrtziel: ~w~'Marcos Bistro' ~g~in Saint Mark's. + +[FARE9] +~g~Fahrtziel: ~w~'Import-Export Garage' ~g~in Portland Harbour. + +[FARE10] +~g~Fahrtziel: ~w~'Punk Noodles' ~g~in Chinatown. + +[FARE12] +~g~Fahrtziel: ~w~'Football Stadion' ~g~in Aspatria. + +[FARE13] +~g~Fahrtziel: ~w~'Die Kirche' ~g~in Bedford Point. + +[FARE14] +~g~Fahrtziel: ~w~'Das Casino' ~g~in Torrington. + +[FARE15] +~g~Fahrtziel: ~w~'Liberty University' ~g~in Liberty Campus. + +[FARE16] +~g~Fahrtziel: ~w~'Einkaufszentrum' ~g~in der Belleville Park Area. + +[FARE17] +~g~Fahrtziel: ~w~'Museum' ~g~in Newport. + +[FARE18] +~g~Fahrtziel: ~w~'AmCo Gebäude' ~g~in Torrington. + +[FARE19] +~g~Fahrtziel: ~w~'Bolt Burgers' ~g~in Bedford Point. + +[FARE20] +~g~Fahrtziel: ~w~'Der Park' ~g~in Belleville. + +[FARE21] +~g~Fahrtziel: ~w~'Francis Int. Airport'. + +[FARE22] +~g~Fahrtziel: ~w~'Cochrane Dam'. + +[FARE24] +~g~Fahrtziel: ~w~'Die Klinik' ~g~in Pike Creek. + +[FARE25] +~g~Fahrtziel: ~w~'Der Park' ~g~in Shoreside Vale. + +[FARE26] +~g~Fahrtziel: ~w~'North West Towers' ~g~in Wichita Gardens. + +[NEW_TAX] +GRÖSSER! SCHNELLER! HÄRTER! Neu! Borgnine Taxis jetzt in Harwood! Rufen Sie 555-BORGNINE! Heute noch! + +[TSCORE2] +$~1~ + +[IN_ROW] +~1~ SERIEN-Bonus! $~1~ + +[TTUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Taxi-Missionen an- oder abzuschalten. + +[TTUTOR2] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Taxi-Missionen an- oder abzuschalten. + +[ATUTOR2] +~g~Fahre die Patienten VORSICHTIG in die Klinik. + +[A_TIME] ++~1~ Sekunden + +[A_FULL] +~r~Krankenwagen voll!! + +[A_RANGE] +~g~Du bist außer Reichweite des Notarztfunks. Fahr näher an die Klinik heran! + +[FTUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Feuerwehr Missionen an- oder abzuschalten. + +[FTUTOR2] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Feuerwehr Missionen an- oder abzuschalten. + +[F_PASS1] +Feuer gelöscht! + +[F_RANGE] +~g~Du bist außer Reichweite des Feuerwehrfunks. Fahr näher an eine Feuerwache heran! + +[C_BREIF] +~g~Verdächtiger wurde zuletzt in der Gegend von ~a~ gesichtet. + +[C_RANGE] +~g~Du bist außer Reichweite des Polizeifunks. Fahr näher an ein Polizeirevier heran! + +[DODO_FT] +Du bist ~1~ Sekunden geflogen! + +[EBAL_A] +Ich kenn ein Plätzchen im Rotlichtbezirk, wo wir untertauchen können. + +[EBAL_A1] +Aber meine Hände sind im Eimer. Also, fahr du. + +[EBAL_1] +Drücke die~h~ ~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in ein Fahrzeug ~h~ein- oder auszusteigen~w~. + +[EBAL_1B] +Drücke die~h~ ~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in ein Fahrzeug ~h~ein- oder auszusteigen~w~. + +[EBAL_2] +~g~Steig wieder in den Wagen! + +[EBAL_3] +Dies ist das ~h~Radar~w~. Damit navigierst du durch die Stadt. Folge dem ~h~Leuchtpunkt~w~ auf dem ~h~Radar~w~, um das Versteck zu finden! + +[EBAL_D] +Ich kenn einen, der hat Beziehungen zur Mafia. Er heißt Luigi. + +[EBAL_D1] +Wir sind alte Bekannte. Vielleicht kann ich dir 'nen Job bei ihm verschaffen. Komm, hier rüber. + +[EBAL_E] +Komm, wir gehen zu ihm. Ich stell dich vor. + +[EBAL_I] +Der Boss kommt gleich zu dir raus... + +[EBAL_J] +8-Ball hat oben was zu erledigen. + +[EBAL_K] +Du könntest mir einen Gefallen tun. + +[EBAL_L] +Eines meiner Girls braucht 'nen Fahrer. Schnapp dir ein Auto, hol Misty von der Klinik ab und bring sie her. + +[EBAL_N] +Also lass die Hände am Lenkrad! + +[EBAL_4] +~r~8-Ball ist tot! + +[EBAL_5] +~g~Besorg dir ein Fahrzeug! + +[EBAL_6] +~g~Hol Misty ab! + +[LM1] +'LUIGIS GIRLS' + +[LM2] +'KEIN SPANK FÜR DIE LADIES' + +[LM3] +'MISTY UND DER MAFIOSO' + +[LM5] +'DER BULLEN-BALL' + +[LM1_2] +~g~Bring Misty zu Luigis Club. + +[LM1_3] +~g~Drück auf die Hupe, damit die Kleine einsteigt. + +[LM1_6] +~g~Steig wieder in den Wagen! + +[LM1_7] +Halte neben Misty an und lass sie einsteigen. + +[LM1_8] +Du kannst dir bei Luigi den nächsten Job abholen oder Liberty City erkunden. + +[LM2_A] +Da ist eine neue Droge in Umlauf, sie heißt SPANK. + +[LM2_E] +Irgendein Kerl hat diesen Müll meinen Girls in Portland Harbour verabreicht. + +[LM2_B] +Fahr hin und verabreich ihm ein paar mit 'nem Baseballschläger! + +[LM2_G] +Der Typ soll bezahlen für diese Beleidigung! + +[LM2_1] +~g~Nimm sein Auto und spritz es um. + +[LM2_2A] +Benutze die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu ~h~schlagen und zu treten~w~ oder um ~h~den Schläger zu schwingen~w~! + +[LM2_2C] +Benutze die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu ~h~schlagen und zu treten~w~ oder um ~h~den Schläger zu schwingen~w~! + +[LM2_2D] +Benutze die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu ~h~schlagen und zu treten~w~ oder um ~h~den Schläger zu schwingen~w~! + +[LM2_3] +~g~Stell das Auto in Luigis Garage ab! + +[LM2_4] +~g~Lackiere das Auto um! + +[LM3_A] +He, ich muss mit dir reden... Okay, Mick, wir reden später. + +[LM3_B] +Na? Alles klar, mein Junge? + +[LM3_C] +Der Sohn des Don, Joey Leone, will seine kleine Misty sehen. + +[LM3_D] +Hol sie in Hepburn Heights ab. + +[LM3_E] +Aber Vorsicht, das ist Diablo-Gebiet. + +[LM3_F] +Dann bringst du sie rüber zu seiner Werkstatt in Trenton. Aber dalli. + +[LM3_H] +Also, Augen auf die Straße und nicht auf Misty! + +[LM3_1D] +Drücke die~h~ L3-Taste~w~, um zu ~h~hupen~w~. So weiß Misty, dass du da bist. + +[LM3_2] +~g~Fahr Misty zu Joey. + +[LM3_4] +~g~Hol Misty ab! + +[LM3_5] +Du arbeitest jetzt fest für Luigi? War auch Zeit, dass er 'nen verlässlichen Fahrer anbringt. + +[LM3_7] +Ich bin gleich bei dir, Süße. + +[LM3_10] +~g~Besorg dir ein Auto! + +[LM4_B] +Fahr hin und regle das für mich. + +[LM4_C] +Wenn du 'ne Knarre brauchst, geh zum Hintereingang von AmmuNation, gegenüber der U-Bahn. + +[LM5_A] +Der Polizeiball findet in der alten Schulhalle nahe der Callahan Bridge statt, + +[LM5_B] +und bei solchen Bällen möchten auch Cops ein wenig 'Action' haben. + +[LM5_C] +Ich hab Girls in der ganzen Stadt stehen. + +[LM5_D] +Bring sie zu dem Ball. Das bringt 'nen Haufen Kohle. + +[LM5_1] +~g~Wenn du zu viele Ladies ins Auto stopfst, holen sie sich Schrammen! ~g~Liefere erst diese Mädchen ab und hol dann den Rest. + +[LM5_2] +~r~Eins von Luigis Girls ist hinüber! + +[LM5_3] +~g~Du brauchst ein Auto! + +[LM5_4] +~g~Hol die Girls, die in St. Mark's arbeiten. + +[LM5_5] +~g~Bring die Girls zum Polizeiball! + +[LM5_8] +~g~Girls auf dem Ball: ~1~ + +[JM2] +'ADIEU, 'CHUNKY' LEE CHONG' + +[JM3] +' DER GELDTRANSPORTER' + +[JM4] +'CIPRIANIS CHAUFFEUR' + +[JM5] +'DER TOTE PASSAGIER' + +[JM1_1] +~g~Bring Forellis Wagen zu 8-Balls Werkstatt nördlich von hier, hinter 'Easy Credit Autos'. + +[JM1_2] +~g~Park den Wagen wieder vor Marcos Bistro. + +[JM1_3] +~g~Aktiviere die Autobombe und dann nichts wie weg! + +[JM1_4] +~g~Du schrottest das Auto! Repariere es! + +[JM1_5] +~g~Die Autobombe ist nicht aktiviert! + +[JM1_6] +~g~Stell den Wagen wieder an den richtigen Platz. + +[JM1_8A] +~y~Hey, mein alter Freund! + +[JM1_8B] +~y~Die Bombenwerkstatt ist automatisiert. Einfach reinfahren und anhalten, der Rest passiert von selbst. + +[JM1_8C] +~y~Hier, die erste ist umsonst, jede weitere kostet aber. + +[JM2_A] +Chunky Lee Chong verhökert Spank für irgend so eine neue Gang aus Kolumbien oder Colorado oder so... + +[JM2_B] +Ich weiß nicht genau. Aber wen interessieren schon die Details? + +[JM2_D] +Diese Ratte hat seine letzte Frühlingsrolle verkauft. + +[JM2_E] +Ich möchte, dass du ihn erledigst. + +[JM2_G] +Besorg dir 'ne 9mm. Du weißt ja, wo du sie findest, oder? + +[JM2_H] +Und sei vorsichtig in Chinatown. Das ist Triaden-Gebiet. + +[JM3_A] +Also, wir überfallen den Transporter mit den Lohngeldern. + +[JM3_B] +Er startet jeden Tag an der Grenze zu Chinatown. + +[JM3_C] +Kugeln können der Karre nichts anhaben. Also besorg dir einen Wagen und ramm ihn von der Straße. + +[JM3_D] +Fahr ihm voll rein, dann dürften die Wachmänner schnell abhauen. + +[JM3_E] +Fahr den Transporter dann zum Lagerhaus bei den Docks, von da an übernehmen meine Leute. + +[JM3_F] +Der Transporter ist nicht ewig unterwegs, also beeil dich. + +[JM3_1] +~g~Fahr den Transporter zu der Garage. + +[JM3_2] +~g~Ramm den Wagen, bis der Schadenswert unter 70 Prozent liegt. + +[JM4_B] +Oh! Da ist der Typ, von dem ich dir erzählt habe! + +[JM4_C] +Okay, hör zu. Der Typ ist kein Italiener und kein Mechaniker, aber er kann alles 'richten'. + +[JM4_D] +Das ist Paps' Capo, Toni Cipriani. + +[JM4_E] +Ja, ich bin Toni Cipriani. + +[JM4_F] +Bring ihn zu Mammas Restaurant in St. Mark's. + +[JM4_G] +Hör zu, ich plane eine Sache, da brauche ich einen guten Fahrer. Also komm später wieder, okay? + +[JM4_2] +Warte hier. Lass den Motor laufen. Das ist kein Freundschaftsbesuch. + +[JM4_3] +Ein Hinterhalt der Triaden! Bring uns hier raus! + +[JM4_4] +Die Triaden denken wohl, sie können mich fertigmachen. Die! MICH! + +[JM4_6] +Hey, Vorsicht! Ich sagte, keine künstlerischen Einlagen! + +[JM4_7] +~g~Fahr Toni zu Mammas Restaurant. + +[JM4_8] +~r~Toni ist tot! + +[JM5_A] +Großartig! Einfach großartig! + +[JM5_B] +Na also. Genau der, mit dem ich jetzt reden muss! + +[JM5_D] +Einer der Forellis meinte, er weiß zu viel, also hat er gekriegt, was er verdiente. + +[JM5_E] +Schaff die Leiche zu der Schrottpresse in Harwood, okay? + +[JM5_1] +~g~Bring ihn zu der Schrottpresse! + +[JM5_2] +~g~Die Forelli Brüder! + +[JM6_A] +Nicht schlecht, das Ding, was? + +[JM6_B] +Hör zu. Fahr mit einem Wagen zu der sicheren Wohnung in St. Mark's und hol ein paar Freunde von mir ab. + +[JM6_C] +Die überfallen eine Bank und brauchen einen Fahrer. + +[JM6_D] +Ich hab ihnen gesagt, du bist der richtige. Also, vermassle es nicht. + +[JM6_E] +Bring sie vor 5 Uhr zu der Bank, keine Minute später. + +[JM6_2] +Lass den Motor laufen. Wir sind gleich wieder da. + +[JM6_3] +Bring uns hier weg!! + +[JM6_4] +Häng die Cops ab und bring uns in die sichere Wohnung! + +[JM6_6] +~g~Los, besorge ein weniger verdächtiges Fahrzeug! + +[JM6_7] +~g~Du brauchst alle 3 für den Überfall! + +[TM1] +'SCHMUTZIGE WÄSCHE' + +[TM2] +'DER GELDBOTE' + +[TM3] +'DAS TREFFEN BEI SALVATORE' + +[TM4] +'TRIADEN UND ANDERE KLEINE FISCHE' + +[TM5] +'EXPLODIERENDE FISCHE' + +[TONI_P] +Ich habe einen dringenden Job für dich! -Toni + +[TM1_A] +~w~Setz dich, Junge. Los, mach's dir bequem. + +[TM1_B] +~w~Die Wäscherei will also kein Schutzgeld zahlen, was? + +[TM1_C] +~w~Denken die Triaden, sie können mich verscheißern? + +[TM1_D] +~w~Diesen Möchtegern-Gangstern werden wir eine Lektion erteilen. + +[TM1_E] +~w~Ja, ich werde denen Respekt beibringen. Die rühren keinen meiner Söhne ungestraft an. + +[TM1_F] +~w~Dein Vater - Gott hab ihn selig - hat sich von den Triaden nie etwas gefallen lassen. + +[TM1_G] +~w~Sorry, Ma. Ja, Ma. + +[TM1_H] +~w~Ich will, dass du ihre Wäscherei-Transporter zerstörst + +[TM1_I] +~w~und jeden Triaden-Tölpel niedermachst, der dir in die Quere kommt. + +[TM1_J] +~w~8-Ball liefert dir alles, was du dazu brauchst. + +[TM2_A] +~w~TONI ist unterwegs, um jemanden zu erledigen - oder er versucht es jedenfalls. + +[TM2_AA] +Er wird nie so sein wie sein Papa. Auf dem Tisch hat er dir eine Nachricht hinterlassen. + +[TM2_B] +~w~Die Wäscherei will jetzt bezahlen. Gute Arbeit, mein Junge! + +[TM2_C] +~w~Hol das Geld ab und bring es hierher. Pass auf die Triaden auf. + +[TM2_D] +~w~Die wollen dich wahrscheinlich zu Chop Suey verarbeiten, aber lass dir nichts gefallen. + +[TM2_E] +~w~Niemand, wirklich niemand, macht TONI CIPRIANI fertig! + +[TM2_1] +~g~Bring das Geld zu Toni!! + +[TM2_2] +~g~Du hast sie alle erledigt! + +[TM3_MA] +~w~Ich weiß nicht, wo er ist! + +[TM3_MB] +~w~Ach, mein Sohn weiß manchmal selbst nicht, wer er ist. + +[TM3_MC] +~w~Ja, sein Vater, der war da ganz anders. Immer auf Draht, top, ein echter Mann... + +[TM3_A] +~w~Don Salvatore hat ein Treffen angesetzt. + +[TM3_B] +~w~Du musst erst die Limo und seinen Sohn Joey aus der Werkstatt abholen. + +[TM3_C] +~w~Dann holst du Luigi aus seinem Club ab und dann kommst du wieder her und holst mich ab. + +[TM3_D] +~w~Dann fahren wir alle gemeinsam zum Boss. + +[TM3_E] +~w~Diese Triaden wissen einfach nicht, wann Schluss ist. + +[TM3_F] +~w~Wenn sie Krieg wollen, sollen sie Krieg haben. + +[TM3_G] +~w~Also, los jetzt. + +[TM3_1] +~g~Hol die Limousine bei Joey ab. + +[TM3_2] +~g~Jetzt hol Luigi ab. + +[TM3_3] +~g~Jetzt hol Toni ab. + +[TM3_4] +~g~Jetzt fahr die Männer zu Salvatore. + +[TM3_5] +~y~Ein Hinterhalt der Triaden!! + +[TM4_B] +~w~Es herrscht KRIEG! Die Triaden betreiben zur Tarnung einen Fischmarkt in Chinatown. + +[TM4_C] +~w~Die meisten ihrer Geschäfte werden auf diesem Fischmarkt durchgezogen. + +[TM4_D] +~w~Diese Wäscherei schuldet uns immer noch Geld. + +[TM4_E] +~w~Die denken, die Triaden beschützen sie jetzt. Ich schlage vor, wir führen eine Strafaktion durch. + +[TM4_F] +~w~Nimm dir diese Jungs und knöpf dir die Köpfe der Triaden vor! + +[TM4_G] +~w~Und wenn es geht, macht auch ein paar von deren Soldaten fertig. + +[TM4_GAT] +~g~Du brauchst einen 'Triaden-Packwagon', um da reinzukommen. + +[TM5_A] +TEXT NO LONGER REQUIRED + +[TM5_B] +~w~Okay, jetzt hab ich aber die Schnauze voll. + +[TM5_C] +~w~Wir machen die Triaden ein für alle Mal fertig. + +[TM5_D] +8-Ball hat einen Müllkarren mit einer Bombe präpariert. + +[TM5_E] +~w~Sie hat einen Zeitzünder. Wenn du's vermasselst, hinterlassen wir keine Spuren. Hol den Müllkarren ab. + +[TM5_F] +~w~Fahr vorsichtig. 8-Ball sagt, die Bombe ist extrem empfindlich, das kleinste Schlagloch und sie geht hoch. + +[TM5_G] +~w~In ihrer Fischfabrik werden sie dich reinlassen mit dem Müllkarren. + +[TM5_H] +~w~Stell das Ding zwischen den Benzinkanistern ab und dann nichts wie weg. + +[TM5_I] +~w~Es soll rummsen, dass es Fische vom Himmel regnet. + +[TM5_J] +~w~Ne biblische Apokalypse will ich haben, nichts popeliges. + +[FM2] +'CURLYS GEHEIMKONTAKTE' + +[FM4] +'DER LETZTE WUNSCH' + +[FM1_A] +~w~Die Jungs und ich haben einiges zu besprechen, + +[FM1_B] +~w~du wirst dich heute abend um meine Kleine kümmern. + +[FM1_C] +~w~HEY, MARIA! WO BLEIBST DU? + +[FM1_D] +~w~Dämliche Ziege. Jedes Mal dasselbe. + +[FM1_E] +~w~Und hier ist sie, die Königin der Nacht höchstpersönlich! + +[FM1_F] +~w~Was hast du denn da oben getrieben? + +[FM1_G] +~w~Was es auch war, jede Wette, es hat mich Geld gekostet. + +[FM1_H] +~w~Du glaubst doch nicht, ich bin zum Palavern hier, oder? + +[FM1_I] +~w~Halt die Klappe und steig in den Wagen. + +[FM1_J] +~w~Nimm die Limo, aber bring sie mir heil wieder, hörst du? + +[FM1_K] +~w~Und pass auf sie auf, sie kann eine Menge Ärger machen. + +[FM1_L] +~w~Ja, ja, ja! Dein neues Schoßhündchen wird schon alles im Griff haben. + +[FM1_M] +~w~Er ist ja auch so groß und stark. + +[FM1_N] +~w~Hey, Fiffi, los, wir besuchen Chico und besorgen uns was zum 'Naschen'! + +[FM1_P] +~g~Da ist Chico. Halt neben ihm an. + +[FM1_S] +~w~Bitte sehr, die Dame. + +[FM1_TT] +~w~EINE POLIZEI-RAZZIA! + +[FM1_1] +~g~Zurück in die Limo! + +[FM1_2] +~g~Steig in die Limo! + +[FM1_3] +~r~Wenn du Maria im Stich lässt, bringt Salvatore dich um. Kehr um und hol sie! + +[FM1_4] +~g~Du hast die Frau des Don im Stich gelassen! Los, zurück zur Lagerhalle! Warte dort auf Maria! + +[FM1_5] +~g~Bring Maria wohlbehalten zu Salvatore zurück! + +[FM1_6] +~g~Chico ist nicht ewig dort. Bring Maria zu diesem Ufer! + +[FM1_7] +~r~Maria ist tot! Das wird Salvatore nicht gefallen... + +[FM1_8] +~r~Du hast Marias Lieferanten erledigt! + +[FM2_J] +Lasst uns eine Minute alleine. + +[FM2_A] +Das kolumbianische Kartell stellt irgendwo in Liberty SPANK her. + +[FM2_K] +Aber wir wissen nicht wo. Und die scheinen jeden unserer Schritte im Voraus zu kennen. + +[FM2_L] +Es gibt da einen Typ namens Curly Bob. Er arbeitet in Luigis Bar. + +[FM2_M] +Der verpulvert schon dauernd mehr Geld als er verdient. + +[FM2_N] +Normalerweise fährt er nach der Arbeit mit dem Taxi nach Hause. Folge ihm. + +[FM2_O] +Und wenn er der Verräter ist, mach ihn fertig. + +[FM2_F] +Da kommt ja unser kleiner, gesprächiger Freund. + +[FM2_G] +Ist man dir gefolgt? Du weißt, was hier läuft, muss unter uns bleiben. + +[FM2_H] +Nein, nein, niemand ist mir gefolgt. Hast du meinen Stoff? + +[FM2_I] +Hier ist dein SPANK, du Ratte, und jetzt rede. + +[FM2_P] +Okay. Die Leones führen einen Zwei-Fronten-Krieg. + +[FM2_Q] +Sie kämpfen mit den Triaden um ein Territorium, und keiner der beiden gibt nach. + +[FM2_R] +Gleichzeitig hat Joey Leone Streit mit den Forellis angefangen. + +[FM2_S] +Jeden Tag verlieren sie Leute und Einfluss in der Stadt. + +[FM2_T] +Salvatore wird gefährlich und paranoid. Er verdächtigt alles und jeden. + +[FM2_U] +Bei treuen Gefolgsleuten wie dir, wie kann er sich da nur Sorgen machen? + +[FM2_1] +~g~Da ist Curly Bob! + +[FM2_2] +~g~Curly hat den Club verlassen. Folge ihm! + +[FM2_5] +~g~Bring ihn nach Portland Harbour. + +[FM2_6] +~r~Curly steigt in kein geschrottetes Taxi! + +[FM2_7] +~r~Curly hat Angst! Das Treffen ist abgeblasen! + +[FM2_8] +~g~Knöpf dir Curly Bob vor! + +[FM2_9] +~r~Curly Bob ist tot! + +[FM2_10] +~r~Curly ist entwischt! + +[FM2_11] +~g~Parke vor Luigis Club, Curly Bob kommt gleich heraus. + +[FM2_12] +~r~Er ist dir entwischt! + +[FM3_A] +~w~Wir sollten diese kolumbianischen Mistkerle fertigmachen, + +[FM3_B] +~w~aber durch den Krieg mit den Triaden sind wir dazu zu geschwächt. + +[FM3_C] +~w~Das Kartell hat unendlich Geld aus dem Handel mit diesem Mistzeug SPANK. + +[FM3_D] +~w~Wenn wir sie offen angreifen, putzen sie uns weg. + +[FM3_E] +~w~Die müssen das SPANK auf diesem großen Schiff machen, zu dem dich Curly geführt hat. + +[FM3_F] +~w~Wir müssen also mit Köpfchen vorgehen. Genauer gesagt, mit DEINEM Köpfchen. + +[FM3_G] +~w~Ich bitte dich, mir, Salvatore Leone zuliebe, dieses SPANK Labor zu zerstören. + +[FM3_H] +~w~Wenn du das für mich tust, bist du ein gemachter Mann. Du kriegst alles, was du willst. + +[FM3_I] +~w~Geh zu 8-Ball. Du brauchst einen Fachmann, um dieses Schiff hochzujagen. + +[FM3_8A] +~w~Hi, Kumpel! Salvatore hat schon angerufen, + +[FM3_8B] +~w~aber für so einen Job brauchst du eine Menge Chinaböller. + +[FM3_8D] +~w~Aber du kennst mich. Dafür scheppert's dann auch gewaltig. + +[FM3_8E] +~w~Okay, dann wollen wir mal! + +[FM3_8F] +~w~Ich kann das Baby scharf machen, aber eine Knarre kann ich mit diesen Händen immer noch nicht halten. + +[FM3_8G] +~w~Hier, das Gewehr hier wirst du sicher brauchen. + +[FM3_4] +~g~Halt an und lass 8-Ball aussteigen! + +[FM3_7] +~r~8-Ball hat's erwischt! + +[FM3_8] +~r~Die Wachmänner wurden alarmiert! + +[FM4_A] +~w~Ah, sieh an! Mein bester Troubleshooter. + +[FM4_B] +~w~Ich bin stolz auf dich, meine Junge. Du hast es diesen Mistkerlen gezeigt. + +[FM4_C] +~w~Ich hab nur noch einen kleinen Job für dich, bevor wir alle feiern können. + +[FM4_D] +~w~Um die Ecke von Luigis Club steht ein Wagen. + +[FM4_E] +~w~Innen drin sieht's ziemlich aus. + +[FM4_F] +~w~Wir haben so einem Typ versehentlich ein Loch in den Kopf gemacht. + +[FM4_H] +~w~Bring den Wagen zur Schrottpresse, bevor die Cops ihn finden. + +[AM3] +'DER PAPARAZZO' + +[AM4] +'ZAHLTAG FÜR RAY' + +[AM5] +'V-MANN TANNER' + +[AM1_A] +Wir müssen ein paar Dinge klären, bevor wir unsere Beziehungen fortsetzen, + +[AM1_B] +geschäftlich oder sonstwie. Legen wir also die Karten auf den Tisch. + +[AM1_C] +Ich bin eine Yakuza und ich weiß, dass du für Salvatore Leones Familie gearbeitet hast. + +[AM1_D] +Ich kann dir einen Job in unserer Organisation verschaffen, + +[AM1_E] +aber zuerst musst du mir beweisen, dass du dich wirklich von der Mafia losgesagt hast. + +[AM1_G] +Sorge dafür, dass er seinen Club nicht lebend erreicht. + +[AM1_H] +Maria und ich reden inzwischen ein bisschen über die alten Zeiten. + +[AM1_I] +Oh, Asuka, du hast einen Massagestab. + +[AM1_J] +Das ist kein Massagestab. + +[AM1_1] +~g~Salvatore verlässt jetzt Luigis Club! + +[AM1_2] +~r~Man hat dich entdeckt! + +[AM1_3] +~r~Du hast Salvatore verpasst! + +[AM1_4] +~r~Na, prima! Du hast dein Opfer verscheucht. Und du willst ein Profi sein? + +[AM1_5] +~g~Begib dich in den Rotlichtbezirk und warte, bis Salvatore den Club verlässt. + +[AM1_7] +~r~Salvatore sitzt bequem zu Hause und schlürft einen Cocktail. 'Der Schakal' bist du nicht gerade! + +[AM1_8] +~g~Salvatore wird Luigis Club um zirka ~1~:~1~ verlassen. + +[AM2_4] +~g~Du bist für die so unsichtbar wie ein Hochhaus! + +[AM3_A] +Ein Reporter hat rumgeschnüffelt. + +[AM3_B] +Maria und ich sind ein bisschen ins Grüne gefahren, bis du diesen miesen Voyeur beseitigt hast. + +[AM4_A] +Ah, mein hübsches Helferlein! + +[AM4_B] +Maria ist gerade beschäftigt, aber ich richte ihr aus, dass du hier warst. + +[AM4_C] +Wer ist da? Asuka? Ich weiß, ich war ein böses Mädchen, aber ich muss dringend pinkeln! + +[AM4_D] +Wird Zeit dass du unseren Mann bei der Polizei kennenlernst. + +[AM4_E] +Das ist seine Bezahlung für den letzten Job, den er für uns erledigt hat. + +[AM4_F] +Verständlicherweise ist er vorsichtig. + +[AM4_G] +Begib dich so schnell wie möglich zu dem öffentlichen Fernsprecher in Torrington und warte auf seine Anweisungen. + +[AM5_A] +Maria und ich sind shoppen gegangen. + +[AM5_B] +Unser Spitzel hat uns informiert, dass einer unserer Fahrer ein übereifriger Undercover Cop ist! + +[AM5_C] +Ohne sein Auto ist er praktisch ein Nichts. Wir haben seinen Wagen mit einem Sender versehen. + +[AM5_D] +Knöpf ihn dir vor! + +[AM5_1] +Tanner hat dich bemerkt! + +[AS1] +'DER KÖDER' + +[AS2] +'ESPRESSO-2-GO!' + +[AS4] +'DAS LÖSEGELD' + +[AS1_A] +~w~Miguel findet anscheinend, dass ich ihn schlecht behandle. + +[AS1_B] +~w~Trotzdem hat er uns mitgeteilt, wie sehr Catalina deine Rache fürchtet. + +[AS2_A] +~w~Wir haben Catalinas Pläne mit dem SPANK unterschätzt. + +[AS2_B] +~w~Das beschränkt sich bei weitem nicht darauf, dass die Yardies es an der Straßenecke verkaufen. + +[AS2_D] +~w~Die verkaufen SPANK über Kaffeestände. + +[AS2_1] +~g~Alle Espressostände in Portland zerstört!! + +[AS2_2] +~g~Alle Espressostände auf Staunton Island zerstört!! + +[AS2_3] +~g~Alle Espressostände in Shoreside Vale zerstört!! + +[AS2_4] +~r~Das Kartell hat seine Dealer gewarnt!! + +[AS2_5] +~g~Da sind noch Espressostände in Shoreside Vale und auf Staunton Island! + +[AS2_6] +~g~Da sind noch Espressostände in Shoreside Vale! + +[AS2_7] +~g~Da sind noch Espressostände auf Staunton Island! + +[AS2_8] +~g~Da sind noch Espressostände in Portland! + +[AS2_9] +~g~Da sind noch Espressostände in Portland und Shoreside Vale! + +[AS2_10] +~g~Da sind noch Espressostände in Portland und auf Staunton Island! + +[AS2_12] +~g~Suche in den Sadtteilen von Liberty City nach ~b~Espresso-2-Go-Ständen! + +[AS3_A] +~W~Drücken wir noch fester zu oder warten wir, bis es von selbst abfällt? + +[AS3_B] +~w~Hau einfach drauf... + +[AS3_D] +~w~Mein Helferlein! + +[AS3_E] +~w~Mir war langweilig, da dachte ich mir, ich leiste Asuka Gesellschaft. + +[AS3_1] +~g~Such dir ein ~r~Boot~g~ und fahre zu der ~b~Markierungsboje! + +[AS3_3] +~g~Warte, bis die ~y~Maschine~g~ zur Landung ansetzt! + +[AS3_5] +~g~Sammle die Ladung ein! + +[AS3_4] +~g~Benutze einen Raketenwerfer, um das ~y~Flugzeug~g~ abzuschießen!! + +[AS3_2] +~b~Fahr zu der Markierungsboje! ~y~Das Flugzeug landet gleich!! + +[AS3_6] +~g~~1~ VON 8 + +[KM1] +'DIE BEFREIUNG DES KANBU' + +[KM3] +'DER JAMAICA DEAL' + +[KM4] +'DIE GANG' + +[KM5] +'DIE ABRECHNUNG' + +[KM1_A] +Meine Schwester hält große Stücke auf dich, + +[KM1_E] +aber ich bin noch nicht überzeugt, dass ein Gajin wie du was auf dem Kasten hat. + +[KM1_B] +Vielleicht kannst du mir bei einer etwas kniffligen Sache helfen. + +[KM1_F] +Ein Fehlschlag wäre natürlich unverzeihlich. + +[KM1_C] +Ein Yakuza Kanbu sitzt in Haft und wartet auf seine Überführung zum Prozess. + +[KM1_G] +Er ist ein geschätztes Mitglied der Familie. + +[KM1_H] +Befreie ihn aus der Haft und bring ihn in das Dojo beim Bedford Point. + +[KM1_D] +Wir danken dir für deinen selbstlosen Einsatz. Solltest du jemals Hilfe brauchen, wird das Dojo dir jederzeit zwei Mann zur Seite stellen. + +[KM1_1] +~g~Klau ein Polizeiauto! + +[KM1_2] +~g~Bau eine Bombe in den Wagen ein! + +[KM1_3] +~g~Jetzt bring ihn zu dem Yakuza Dojo. + +[KM1_5] +~g~Okay, jetzt fahr zum Polizeirevier. + +[KM1_6] +~g~Bau eine Bombe in das Auto ein! + +[KM1_7] +~g~Nur autorisierte Polizeifahrzeuge! + +[KM1_9] +~r~Du hast keine Autobombe benutzt, um die Wand zu sprengen. + +[KM1_10] +~r~Der Yakuza Kanbu ist tot - genau wie deine Ehre! + +[KM1_11] +~r~Du hast dich selbst in Schwierigkeiten gebracht! + +[KM2_A] +Gewisse Umgangsformen sind in diesem Beruf von nicht zu unterschätzender Wichtigkeit. + +[KM2_B] +Es ist eine Schande. Jemand hat mir einmal einen Gefallen getan, und ich konnte mich nie dafür erkenntlich zeigen. + +[KM2_C] +Der Mann ist ein Autonarr, und er hat gebeten, dass wir ihm bestimmte Modelle für seine Sammlung besorgen. + +[KM2_F] +Mein Ehrgefühl verlangt das von mir. + +[KM2_2] +~g~Auto abgeliefert. + +[KM3_A] +Wenn Ungemach droht, wendet der Narr sich ab, während der Weise sich ihm stellt. + +[KM3_B] +Das kolumbianische Kartell hat unsere wiederholten Bitten ignoriert, unsere Interessen in Liberty zu berücksichtigen. + +[KM3_C] +Jetzt verhandeln die mit den Jamaikanern, um uns weiter zu demütigen. + +[KM3_D] +Sie wollen den Deal am anderen Ende der Stadt besiegeln. + +[KM3_F] +Nimm einen meiner Männer, klau einen Yardie-Wagen und statte den Kolumbianern einen Besuch ab. + +[KM3_E] +Unser Ehrgefühl verlangt es, dass niemand am Leben bleibt. + +[KM3_2] +~g~Hole deinen Kontaktmann ab. + +[KM3_3] +~g~Das Treffen findet auf dem Krankenhaus-Parkplatz in Rockford statt! + +[KM3_4] +~r~Sie sind entkommen! + +[KM3_6] +~g~Knöpf sie dir vor! Mach sie fertig! + +[KM3_8] +~g~Du brauchst einen Yardie-Wagen für diesen Job! + +[KM3_9] +~r~Einer der Kolumbianer ist tot. Der Deal ist geplatzt. + +[KM3_10] +~r~Der Kontaktmann ist tot! + +[KM4_A] +Um wahrhaft stark zu sein, darfst du niemals Schwäche zeigen. + +[KM4_C] +Sammle die Gelder umgehend ein, damit wir sie in unsere Casinos stecken können. + +[KM4_1] +Ich kann euch nicht bezahlen, und selbst wenn ich könnte, würde ich es nicht tun. + +[KM4_9] +Eine Jugendbande hat mich gerade überfallen! Sie haben mir alles genommen! + +[KM4_2] +Ihr seid zu nichts nutze. + +[KM4_10] +Was sind SIE denn eigentlich für ein Yakuza..? + +[KM4_3] +Dafür bezahle ich euch Gangster nicht. Diese Art von Schutz kann ich auch von der verdammten Polizei bekommen! + +[KM4_4] +~g~Bestrafe die verantwortliche Gang und stelle das ~b~Schutzgeld~g~ sicher! + +[KM4_7] +~r~Der Ladenbesitzer hat sein Leben verröchelt. + +[KM4_5] +Donald Love möchte dich in seinem Teegarten sehen, um mit dir zu reden. + +[KM4_6] +Da ist das Geld. Es ist alles da! + +[KM4_8] +~g~Tasche aufgesammelt! + +[KM5_A] +DU! Wie passend, dass du ausgerechnet jetzt dein ehrloses Gesicht zeigst! + +[KM5_B] +Es scheint, deine Versuche, die Jamaikaner davon abzuhalten, + +[KM5_B1] +sich mit dem Kartell einzulassen, sind komplett fehlgeschlagen! + +[KM5_C] +Yardie-Pusher verdealen päckchenweise SPANK in den Straßen von Liberty, als würden sie Hotdogs verkaufen! + +[KM5_D] +Die Mistkerle vom Kartell lachen uns aus, lachen MICH aus! + +[KM5_E] +Ich gebe dir eine letzte Chance das Vertrauen zu rechtfertigen, das meine Schwester in dich setzt! + +[KM5_F] +Mach diese Dreckskerle fertig und wasch deine befleckte Ehre im Blut unserer Feinde rein!!! + +[KM5_3] +~r~Du hast mindestens ~1~ der Yardies nicht erwischt. + +[KM5_4] +~g~Du hast ~1~ der Yardies erwischt. + +[KM5_5] +~g~Du hast ~1~ der Yardies erwischt. BONUS $~1~ + +[RM1] +'DAS SCHWEIGEN DES VERRÄTERS' + +[RM3] +'BRENNENDE BEWEISE' + +[RM4] +'TÖDLICHE BOOTSFAHRT' + +[RM5] +'DER GEPANZERTE ZEUGE' + +[RM1_D] +Er steht unter Zeugenschutz und sitzt mit bewaffneten Leibwächtern in einer Wohnung in Newport, irgendwo hinter dem Parkplatz. + +[RM1_E] +Zünde die Bude an, und wenn sie rausgerannt kommen, kannst du sie dir vornehmen. Sorge dafür, dass er mit niemandem redet. + +[RM1_1] +~g~Check das Zeugenschutz-Haus aus. + +[RM1_2] +~g~Knöpf dir McAffrey vor! + +[RM2_A1] +Hey, Junge! Hier rüber! + +[RM2_A] +Ein alter Kumpel aus der Army macht Geschäfte in Rockford. + +[RM2_D] +Er braucht Hilfe. Zum Dank will er dir Superpreise machen für alles, was du bei ihm kaufst. + +[RM2_E] +Ray hat schon angerufen... Aber ich dachte, er schickt mehr Leute. + +[RM2_F] +Na ja, drei Arme sind besser als einer, also nimm dir, was du brauchst. + +[RM2_G] +~g~Sieh nach Phil! + +[RM2_H] +~r~Phil hat's erwischt!! + +[RM2_L] +Heh-hey! Wäre ich mit DIR in Nicaragua gewesen, hätte ich vielleicht meinen Arm noch! + +[RM2_N] +Lass das Geld da. Und jetzt verschwinde. Ich regle das mit den Cops. + +[RM3_D] +Das Beweismaterial wird gerade quer durch die Stadt transportiert. + +[RM3_E] +Du wirst diesen Wagen rammen und jedes kleine Beweisstück einsammeln, wenn es rausfällt. + +[RM3_F] +Wenn du alles hast, lass das Zeug im Wagen und zünde ihn an. + +[RM3_G] +Das bringt uns beiden eine Stange Geld ein, mein Junge. + +[RM3_1] +~g~Lass das Beweismaterial in einem Auto und zünde das Auto dann an. + +[RM3_4] +~g~Der Staatsanwalt hat die Beweisfotos verloren! + +[RM3_6] +~r~Die Fotos werden sich über ganz Liberty verteilen! + +[RM3_7] +~g~Steck jetzt das Auto in Brand! + +[RM4_A] +Ich glaube, mein Partner ist ein Verräter. + +[RM4_C] +Meistens fährt er abends mit seinem Boot fischen, nahe dem Leuchtturm auf dem Portland Rock. + +[RM4_D] +Klau ein Polizeiboot und mach seinen miesen Machenschaften ein Ende! + +[RM4_1] +~g~Klau ein Polizeiboot. + +[RM4_2] +~g~Fahr zum Leuchtturm und nimm dir Rays Partner vor! + +[RM5_A] +Du unfähiger Idiot! + +[RM5_A1] +Du hast alles vermasselt! Es geht um mein Leben, und du kannst nicht mal eine Fliege totschlagen! + +[RM5_B] +Ich hab dir viel Geld gezahlt, dafür, dass du diesen Zeugen beseitigst, aber er lebt! + +[RM5_B1] +Und heute wird er vor dem FBI aussagen! + +[RM5_C] +Er muss jeden Moment aus dem Carson General Hospital in Rockford rausgebracht werden. + +[RM5_D] +Wenn er singt, singe ich auch... + +[RM5_E] +Also los, erledige den Job, für den ich dich bezahlt habe! + +[RM5_1] +~g~Fang den Krankenwagen ab. + +[RM5_2] +~g~Man hat dich bemerkt!! + +[RM5_3] +~g~Das war ein Ablenkungsmanöver! + +[RM5_4] +~g~Kugel können den Panzer nicht durchschlagen!! + +[RM5_5] +~g~Der Panzer ist feuersicher!! + +[RM5_7] +~r~Zeuge wurde abgeliefert!! + +[RM5_8] +~g~Zeuge ist ertrunken!! + +[LOVE2] +'DAS KENJI-KOMPLOTT' + +[LOVE3] +'NÄCHTLICHER FISCHZUG' + +[LOVE1_A] +Zunächst möchte ich dir danken, dass du diese Sache für mich geregelt hast. + +[LOVE1_F] +Die Leute interpretieren heute in alles etwas hinein. + +[LOVE1_D] +Sie versuchen, mir immer noch mehr Geld abzupressen, aber ich halte nichts von Verhandlungen. + +[LOVE1_E] +Deal ist Deal, die sehen keinen Penny von mir. + +[LOVE1_G] +Befreie meinen Freund, egal wie. + +[LOVE1_2] +~g~Rette den alten asiatischen Gentleman. + +[LOVE1_3] +~g~Bring den alten Asiaten zu Love. + +[LOVE1_4] +~g~Der alte Asiate muss in einer der Garagen sein... + +[LOVE1_6] +~r~Der alte Asiate ist von uns gegangen! + +[LOVE1_7] +~g~Das Tor öffnet sich nur für Autos der kolumbianischen Gang. + +[LOVE2_A] +Nichts lässt die Grundstückspreise so tief purzeln wie ein guter alter Bandenkrieg, + +[LOVE2_B] +außer vielleicht eine Pestepidemie... Aber das ginge hier wohl zu weit. + +[LOVE2_C] +Wie ich bemerkt habe, sind die Yakuza und die Kolumbianer nicht eben Busenfreunde. + +[LOVE2_D] +Daraus sollte man Kapital schlagen. + +[LOVE2_E] +Ich möchte, dass du dir den Yakuza WAKA-Gashira Kenji Kasen vornimmst. + +[LOVE2_F] +Kenji ist bei einem Treffen auf dem Dach der Parkgarage in Newport. + +[LOVE2_G] +Besorg dir einen Wagen des Kartells und nimm ihn dir vor. + +[LOVE2_H] +Die Yakuza werden das als Kriegserklärung des Kartells auffassen. + +[LOVE2_1] +~g~Klau in Fort Staunton einen Wagen der kolumbianischen Gang! + +[LOVE2_2] +~g~Fahre jetzt zu dem ~p~Parkhaus in Newport~g~ und zieh Kenji aus dem Verkehr! + +[LOVE2_3] +~r~Wenn du nicht mit einem Auto des Kartells aufkreuzt, wird man dich erkennen! + +[LOVE2_4] +~r~Die Yakuza haben dich erkannt! + +[LOVE2_6] +~r~Du hast alle Zeugen aus dem Weg geräumt!! + +[LOVE3_A] +In scheinheiligen Zeiten wie diesen sind bestimmte wertvolle Waren schwer zu importieren. + +[LOVE3_C] +Es wird einige kleine Päckchen ins Wasser abwerfen. + +[LOVE3_D] +Sammle sie ein, bevor es ein anderer tut. + +[LOVE3_1] +~g~Besorg dir ein ~r~Boot~g~ und folge dem ~y~Flugzeug~g~! + +[LOVE4] +'DER FLUGHAFEN-COUP' + +[LOVE5] +'BODYGUARD ACTION' + +[LOVE4_A] +Danke, dass du die Päckchen geholt hast. Aber die sollten nur als Köder dienen. + +[LOVE4_B] +Sorry, aber so läuft das manchmal in dem Geschäft. + +[LOVE4_C] +Die Ware, um die es mir wirklich geht, ist noch in dem Flugzeug versteckt. + +[LOVE4_F] +Ich habe die Beamten bestochen. + +[LOVE4_1] +~r~Das kolumbianische Kartell ist da!! + +[LOVE4_2] +~g~Das Päckchen ist weg! Du musst die Kolumbianer finden und es ihnen abjagen. + +[LOVE4_3] +~g~Bauunternehmen Panlantic...? + +[LOVE4_5] +~g~Das Päckchen müsste im Flugzeug sein... + +[LOVE4_6] +~g~Nimm den Lift nach oben in den Tower! + +[LOVE5_B] +Mein asiatischer Freund braucht einen Bodyguard. Er lässt meine neue Lieferung auf Qualität überprüfen. + +[LOVE5_1] +~g~Los! + +[LOVE5_2] +~g~Du wirst ein Auto brauchen! + +[LOVE5_3] +~g~Los, sieh nach, ob am Tunnelende die Luft rein ist. + +[LOVE5_4] +~r~Dem Truck darf nichts passieren! + +[RM6] +'IM FADENKREUZ' + +[RM6_A] +Dir ist niemand gefolgt? Gut. + +[RM6_B] +Es wird langsam brenzlig, ich stecke bis zum Hals in der Scheiße! + +[RM6_D] +Die haben mich im Visier, ich muss mich absetzen. + +[RM6_E] +Wenn du mich rechtzeitig zum Flughafen bringst, lass ich ordentlich was springen. + +[RM6_666] +Pass gut auf meinen kugelsicheren Patriot auf, Ray. Wir sehen uns in Miami. + +[CAT1] +'LÖSEGELD' + +[CAT2] +'DIE ÜBERGABE' + +[CAT1_A] +Ich habe deine Maria. Wenn ihr Gesicht nicht aussehen soll, als wär's in einen Fleischwolf geraten, + +[CAT2_F] +Ich hab mir 'nen Fingernagel abgebrochen und meine Frisur ist hin! Fünfzig Dollar im Eimer! + +[CAT2_G] +Mann, hatte ich Angst. Aber dann dachte ich mir, du bist doch kein kleines Mädchen mehr. + +[CAT2_H] +Du, das wird lustig, weißt du, meine Schwester will nämlich mit ihren zwei Kindern eine zeitlang bei uns wohnen, + +[CAT2_I] +weil ihr Mann gerade mal wieder fremdgeht und... + +[CAT1_C] +XXXX + +[CAT1_D] +XXXX + +[CAT1_E] +XXXX + +[CAT1_F] +Du musst rechtzeitig bei Catalina sein! + +[CAT1_G] +XXXX + +[CAT1_H] +XXXX + +[CAT1_I] +XXXX + +[CAT1_J] +XXXX + +[CAT1_K] +XXXX + +[CAT1_L] +XXXX + +[AS4_1] +XXXX + +[CAT_MON] +~g~Du hast noch nicht genug Geld. Du brauchst $500.000. + +[BITCH_D] +~g~Maria ist tot! + +[WEATHER] +WETTER ÄNDERN + +[WEATHE2] +WETTER NORMAL + +[8001] +Du hast komplett versagt! + +[1000] +DU BIST TOT + +[1001] +DU BIST TOT + +[1002] +DU BIST TOT + +[1003] +DU BIST TOT + +[1004] +DU BIST TOT + +[1005] +VERHAFTET + +[1006] +VERHAFTET + +[1007] +VERHAFTET + +[1008] +VERHAFTET + +[1009] +VERHAFTET + +[GA_4] +Autobomben kosten $1000 pro Stück. + +[GA_5] +In deinem Wagen ist schon eine Autobombe. + +[GA_6] { re3 change } +Park die Karre, mach sie durch Drücken der ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~ scharf, und dann nichts wie weg! + +[GA_7] { re3 change } +Mach die Bombe mit der ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~ scharf. Dann geht sie hoch, wenn der Wagen angelassen wird. + +[GA_8] +Benutze den Zünder, um die Bombe hochgehen zu lassen. + +[GA_9] +Du hast ~1~ von zehn Spezialautos beschafft. + +[GA_10] +Hübsche Karre. Hier sind deine $~1~. + +[GA_11] +So eine Karre haben wir schon. Die können wir nicht gebrauchen. + +[GA_12] +Bombe ist scharf. + +[GA_13] +Auf dich ist Verlass. Wenn du alle, die auf der Liste stehen, abgeliefert hast, kriegst du einen Bonus. + +[GA_14] +Du hast alle georderten Karren geliefert. Sehr gut. Hier, für dich. + +[GA_15] +Hoffentlich gefällt dir die neue Farbe. + +[GA_16] +Das Umspritzen ist gratis. + +[GA_19] +An dem Modell haben wir kein Interesse. + +[GA_20] +Von der Sorte haben wir schon mehr als genug. Sorry, da kommen wir nicht ins Geschäft. + +[CR_1] +Kran kann dieses Fahrzeug nicht anheben. + +[PU_MONY] +Du hast nicht genug Geld. + +[CO_ALL] +Du hast sie alle geliefert. Hier, für dich. + +[PAUSED] +PAUSE + +[HEALTH1] +Stell dich nicht so an! Dir fehlt doch überhaupt nichts. + +[HEALTH2] +Medizinische Versorgung ist teuer. + +[HEALTH3] +Ich flick dich wieder zusammen. + +[HEALTH4] +Das macht $250. + +[FEB_STA] +Statistik + +[FEB_BRI] +Mission + +[FEB_CON] +Steuerung + +[FEB_AUD] +Audio + +[FEB_DIS] +Anzeige + +[FEB_LAN] +Sprache + +[FEP_STA] +STATISTIKEN + +[FEP_BRI] +MISSIONSINFOS + +[FEP_CON] +STEUERUNG + +[FEP_AUD] +AUDIO + +[FEP_DIS] +ANZEIGE + +[FEP_LAN] +SPRACHE + +[FEF_ST1] +Wer ist der Schurke? + +[FEF_ST2] +Wie viel Chaos hast du angerichtet? + +[FEF_BR1] +Du blickst bei der Story nicht mehr durch? + +[FEF_CO1] +Taste dich ran, Mann! + +[FEF_CO2] +Wähle das Controller-Setup, das zu deinem Spielstil am besten passt + +[FEF_SA1] +Bring deine Daten in Sicherheit! + +[FEF_SA2] +Spiele laden und speichern + +[FEF_AU1] +Volle Dröhnung gefällig? + +[FEF_AU2] +Radiosender und Soundeffekt auswählen + +[FEF_DI1] +Andere Optik? + +[FEF_DI2] +Spiel für deinen Fernseher optimieren + +[FEF_LA1] +Was für ein Gefasel! + +[FEF_LA2] +Sprache auswählen + +[FEB_PMB] +Vorherige Missionsinfos: + +[FEC_NA] +Nicht verfügbar + +[FEC_CWL] +Eine Waffe nach links + +[FEC_CWR] +Eine Waffe nach rechts + +[FEC_LOF] +Nach vorne schauen + +[FEC_TAR] +Zielen + +[FEC_MOV] +Bewegung + +[FEC_CAM] +Blickwinkel + +[FEC_PAU] +Pause + +[FEC_ENV] +In Fahrzeug einsteigen + +[FEC_JUM] +Springen + +[FEC_ATT] +Angreifen\Waffe abfeuern + +[FEC_RUN] +Rennen + +[FEC_FPC] +Subjektive Kamera + +[FEC_LB1] +Schau + +[FEC_LL] +Nach links schauen + +[FEC_LB2] +nach hinten + +[FEC_LB] +Nach hinten schauen + +[FEC_LR] +Nach rechts schauen + +[FEC_HOR] +Hupe + +[FEC_VES] +Fahrzeug steuern + +[FEC_RSC] +Radiosender auswählen + +[FEC_BRA] +Bremsen\rückwärts fahren + +[FEC_HAB] +Handbremse + +[FEC_CAW] +Fahrzeugwaffe + +[FEC_ACC] +Beschleunigen + +[FEC_SMT] +Spezialmission aktivieren + +[FEA_OUT] +Tonausgabe: + +[FEA_ST] +Stereo + +[FEA_MNO] +Mono + +[FEA_NON] +Keinen + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX 109 + +[FED_DBG] +Menu Debug + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_SPR] +Show Ped Road Groups + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_DSR] +Debug Streaming Requests + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Memory Card Menü + +[FEM_RMC] +Register MemCard One + +[FEM_TFM] +Test Format MemCard One + +[FEM_TUM] +Test UnFormat MemCard One + +[FEM_CRD] +Create Root Dir + +[FEM_CLI] +Create And Load Icons + +[FEM_FFF] +Fill First File with Guff + +[FEM_SOG] +Save Only The Game + +[FEM_CES] +Check Every 0kB4 Save + +[FEM_STG] +Save The Game + +[FEM_STS] +Save The Game under GTA3 name + +[FEM_CPD] +Create copy protected mag directory + +[FEM_MC2] +Memory Card Menu 2 + +[FEM_TS] +Test Save: + +[FEM_TL] +Test Load: + +[FEM_TD] +Test Delete: + +[PL_STAT] +Spielerstatistiken + +[PE_WAST] +Von dir abservierte Personen + +[PE_WSOT] +Von anderen abservierte Personen + +[CAR_EXP] +Explodierte Autos + +[TM_BUST] +Zahl deiner Verhaftungen + +[M_WASTE] +Männliche Passanten + +[F_WASTE] +Weibliche Passanten + +[PIG_WST] +Cops + +[GNG_WST] +Gang-Mitglieder + +[MED_WST] +Sanitäter + +[FIRE_WS] +Feuerwehrmänner + +[DED_CRI] +Kriminelle + +[DED_DED] +Schnorrer + +[DED_HOK] +Girls + +[HEL_DST] +Zerstörte Helikopter + +[PER_COM] +Absolviert (in Prozent) + +[KGS_EXP] +Sprengstoffverbrauch in kg + +[ACCURA] +Treffsicherheit + +[ELBURRO] +Beste Turismo-Zeit in Sekunden + +[CAR_CRU] +Geschrottete Autos + +[HED_EX] +Köpfe + +[TM_DED] +Krankenhausbesuche + +[DAYSPS] +Im Spiel verstrichene Tage + +[MMRAIN] +Regenfälle in mm + +[MXCARD] +Weitester IRRSINNS-Sprung (in Fuß) + +[MXCARJ] +Höchster IRRSINNS-Sprung (in Fuß) + +[MXCARDM] +Weitester IRRSINNS-Sprung (m) + +[MXCARJM] +Höchster IRRSINNS-Sprung (m) + +[MXFLIP] +Max. Anzahl Saltos + +[MXJUMP] +Max. Drehungen im Sprung + +[BSTSTU] +Bester IRRSINNS-Stunt bisher: + +[INSTUN] +Irrsinns-Stunt + +[PRINST] +Super Irrsinns-Stunt + +[DBINST] +Doppelter Irrsinns-Stunt + +[DBPINS] +Super-Doppel-Irrsinns-Stunt + +[TRINST] +Dreifacher Irrsinns-Stunt + +[PRTRST] +Super-Dreifach-Irrsinns-Stunt + +[QUINST] +Vierfacher Irrsinns-Stunt + +[PQUINS] +Super-Vierfach-Irrsinns-Stunt + +[NOSTUC] +Bisher keine Stunts geschafft + +[NOUNIF] +Monster-Stunts geschafft + +[NOUNGM] +Monster-Stunts insgesamt + +[NMISON] +Begonnene Missionen + +[NMMISP] +Erfüllte Missionen + +[PASDRO] +Beförderte Fahrgäste + +[MONTAX] +Mit Taxi verdientes Geld + +[DAYPLC] +Tagesetat für Polizei + +[CRIMRA] +Punktzahl: + +[GMSTOR] +Spielarchiv + +[PREBRF] +Vorherige Missionsinfos + +[CNTLS] +Steuerung + +[MUSMEN] +Musik SFX + +[GAMSET] +Spieleinstellungen + +[LANGUA] +Sprache + +[DSPLAY] +Anzeige + +[DEBUGM] +Debug Menu + +[QUITOP] +Optionen verlassen + +[CONTRL] +Konfiguration d. Steuerung + +[SET1EN] +Konfiguration 1 aktiviert + +[SET1] +Konfiguration 1 + +[SET2EN] +Konfiguration 2 aktiviert + +[SET2] +Konfiguration 2 + +[SET3EN] +Konfiguration 3 aktiviert + +[SET3] +Konfiguration 3 + +[SET4EN] +Konfiguration 4 aktiviert + +[SET4] +Konfiguration 4 + +[GOBACK] +Zurück + +[SOUND] +SOUND + +[MUSVOL] +Lautstärke Musik + +[SFXVOL] +Lautstärke SFX + +[SCROPT] +BILDSCHIRMOPTIONEN + +[CTRSCR] +Bildschirm zentrieren + +[SCRFOR] +Bildschirmformat + +[GMSVLQ] +SPEICHERN-LADEN-BEENDEN + +[GMREST] +Spiel neu starten + +[NOGMSV] +Du kannst nur in deinem Unterschlupf speichern. + +[DLFILE] +GTA3 Dateien löschen + +[CHFILE] +ZU LADENDE DATEI AUSWÄHLEN + +[CHCDLD] +Wähle Memory Card (PS2) von der geladen werden soll + +[CDUNFR] +Memory Card (PS2) ist nicht formatiert. + +[CHFIDL] +ZU LÖSCHENDE DATEI AUSWÄHLEN + +[SVCONF] +SPEICHERBESTÄTIGUNG + +[SVFNAM] +Dateiname des gespeicherten Spiels: + +[SAVEDN] +Fehler - Speicherung nicht vollständig. + +[LANGSL] +SPRACHAUSWAHL + +[ENGLIS] +Englisch + +[GERMAN] +Deutsch + +[ITALIA] +Italienisch + +[FRENCH] +Französisch + +[SPAIN] +Spanisch + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Parse Heap + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[CRRGON] +ShowCarRoadGroups On + +[CRGOFF] +ShowCarRoadGroups Off + +[CLZOON] +Show Cull Zones On + +[CLZOOF] +Show Cull Zones Off + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (teststuff) + +[UNFRM1] +UnFormatMemCard 1 (teststuff) + +[GORLEV] +Gewalt-Level + +[SICASS] +Mittel + +[SICSIC] +Hoch + +[SCASSL] +Gewalt-Level 'mittel' gewählt + +[SCSCSL] +Gewalt-Level 'hoch' gewählt + +[PRVMEN] +Vorherige Missionsinfos + +[DOSVGM] +Spiel speichern? + +[FORMEN] +Formatierungsmenü + +[MEMTST] +Memory Card-Testmenü + +[REGCAR] +Memory Card 1 registrieren + +[TEFONE] +Memory Card 1 testweise formatieren + +[TEUFON] +Memory Card 1 testweise de-formatieren + +[CRROOT] +CreateRootDir + +[CRLDIC] +Create and Load Icons + +[FLFSGF] +Fill First File With Guff + +[PUSAVE] +Save Only the game + +[CHEVOK] +CheckEveryOkB4Save + +[SVGMON] +SaveTheGame + +[CNTSAV] +Spiel kann nicht gespeichert werden. Mitten in Mission. + +[CNCSAV] +Spiel kann nicht gespeichert werden. Du bist im Auto. + +[CRMGSV] +Create copy protected magazine directory + +[MGSVCN] +MagazineDirectory Created + +[MGSVNC] +MagazineDirectory Not Created + +[YES] +Ja + +[NO] +Nein + +[X] +x + +[LAST] +Letzte Nachricht + +[FEDS_XB] +Auswählen + +[FEDS_ST] +START-Taste - WEITER + +[FEST_OO] +von + +[FEC_TUC] +Geschützsteuerung + +[FEC_SM3] +Spezialmission aktivieren (R3-Taste) + +[FEC_RS3] +Radiosender auswählen (L3-Taste) + +[FEC_HO3] +Hupe (L3-Taste) + +[DIAB1] +'GRAN TURISMO' + +[DIAB2] +'BRANDHEISSE EISCREME' + +[DIAB3] +'FEUERTAUFE' + +[DIAB4] +'JÄGER DES VERLORENEN SCHUNDES' + +[DIAB1_A] +El Burro bietet dir eine Chance. Über den öffentlichen Fernsprecher in Hepburn Heights erfährst du näheres. + +[DIAB1_C] +Du bist kein übler Fahrer. Komm wieder zu dem Telefon. Vielleicht hat El Burro noch mehr Jobs für dich. + +[DIAB1_1] +~g~3...2...1... LOS. LOS, LOS! + +[DIAB1_4] +~g~Schnapp dir einen schnellen Wagen und fahr zum Start. + +[DIAB1_3] +~r~Du würdest nicht mal beim Sackhüpfen gewinnen, du LOSER! + +[DIAB1_2] +~g~Gratulation! Du hast gesiegt. In der unglaublichen Zeit von ~1~ Sekunden. + +[FIRST] +~g~1. + +[SECOND] +~g~2. + +[THIRD] +~g~3. + +[FOURTH] +~g~4. + +[DIAB2_1] +~g~Hol die Aktentasche in Harwood ab. + +[DIAB2_2] +~g~Such dir einen Eis-Wagen. + +[DIAB2_3] +~g~Parke den Eis-Wagen unten bei den Atlantic Quays. + +[DIAB2_4] +~g~Drücke die ~w~~k~~VEHICLE_HORN~-Taste~g~, um den Eiscreme-Jingle abzuspielen. + +[DIAB2_6] +~g~Drücke die ~w~~k~~VEHICLE_HORN~-Taste~g~, um den Eiscreme-Jingle abzuspielen. + +[DIAB2_7] +~g~Drücke die ~w~~k~~VEHICLE_HORN~-Taste~g~, um den Eiscreme-Jingle abzuspielen. + +[DIAB2_5] +~g~Steig aus und sprenge den Eis-Wagen mit dem Fernzünder. + +[YD1] +'SCHNELLE AUTOS, SCHNELLES GELD' + +[YD2] +'UZI RIDER' + +[YD3] +'DER GROSSE AUTOKLAU' + +[YD4] +'TAG DER RACHE' + +[YD_P] +King Courtney will dich sprechen - am Telefon in Aspatria!! + +[YD1_A] +~w~Hier spricht King Courtney. + +[YD1_A1] +~w~Meine Yardies könnten einen Fahrer brauchen, und du hast keinen schlechten Ruf. + +[YD1_B] +~w~Fahr mit einem Wagen zu dem Gelände gegenüber dem Stadion und warte auf die anderen Mitbewerber. + +[YD1_C] +~w~Meine Männer beobachten Checkpoints überall in Staunton. + +[YD1_D] +~w~Wer einen Checkpoint als erster erreicht, kriegt $1000. Dann geht's weiter zur nächsten Station. + +[YD1_D1] +~w~Wenn du mehr Checkpoints als die anderen gewinnst, habe ich vielleicht Arbeit für dich. + +[YD1_E] +~g~Fertigmachen zum Start! + +[YD1_F] +~g~Du bist zu früh gestartet. Das gefällt mir!! + +[YD1_G] +~r~Dies ist ein AUTORENNEN. Du brauchst ein AUTO, Hirni! + +[YD1GO] +~g~LOS!! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +$1000!! + +[Y1_1ST] +~g~Du bist Erster mit ~1~ gewonnenen Checkpoints! + +[Y1_2ND] +~y~Zweiter mit ~1~ gewonnenen Checkpoints. ~y~Tja, knapp daneben ist auch versagt! + +[Y1_3RD] +~r~Dritter mit ~1~ gewonnenen Checkpoints. ~r~Findest du das okay? + +[Y1_LAST] +~r~Du bist Letzter! ~r~Du vergeudest meine Zeit, BLÖDMANN! + +[Y1_J1ST] +~y~Du teilst dir den 1. Platz. ~1~ gewonnene Checkpoints. ~y~Gut, aber du musst der Beste sein, um für Queen Lizzy zu fahren! + +[Y1_J2ND] +~r~Du teilst dir den 2. Platz. ~1~ gewonnene Checkpoints. Du Schläfer! + +[Y1JLAST] +~r~Du bist unter den Letzten! Wo hast du deinen Führerschein gemacht? + +[Y1_TEST] +AUTO IM WASSER!! + +[YD2_A] +~w~Ich will sehen, ob du die Drecksjobs für mich machen kannst. + +[YD2_A1] +Mal sehen, ob man dir trauen kann. + +[YD2_B] +Gleich kommen zwei meiner Jungs und holen dich ab. + +[YD2_B1] +Wollen sehen, ob du so gut bist, wie du sagst. + +[YD2_C] +~w~Wir fahren nach Hepburn Heights und nehmen uns ein paar Diablos vor, die Queen Lizzy angemacht haben. + +[YD2_CC] +~w~Hier, du wirst 'ne Knarre brauchen. + +[YD2_D] +~w~Du fährst UND ballerst. Wir achten drauf, dass du keine kalten Füße kriegst. + +[YD2_E] +~w~Los geht's!! + +[YD2_F] +~r~Er haut ab! Schnapp dir den Feigling!!! + +[YD2_G1] +~w~Hepburn Heights. Knöpf dir ein paar Diablos vor. + +[YD2_G2] +~w~Aber denk dran, ~r~du steigst nicht aus dem Wagen!! + +[YD2_H] +~w~Okay, fahr uns zurück auf Yardie-Gebiet! LOS, LOS, LOS!! + +[YD2_L] +~w~Gut gemacht, Sichler! + +[YD2_M] +~r~Er hat mein Auto geschrottet! Mach ihn fertig! + +[YD2_N] +~w~Steig sofort wieder in den Wagen! + +[YD3_A] +Besorg ein paar Bandenautos, + +[YD3_A1] +damit wir auf Feindgebiet operieren können. + +[YD3_B] +Ich brauche einen Mafia Sentinel, + +[YD3_B1] +einen Yakuza Stinger und einen + +[YD3_B2] +Diablo Stallion. So können wir jede Gang in Liberty angreifen. + +[YD3_C] +Stell sie in der Garage in Newport ab. Aber denk dran: , + +[YD3_C1] +Geschrottet nützen sie uns nichts!! + +[YD3_D] +Spare text label + +[YD3_E] +~r~Du hast bereits einen Diablo-Wagen! + +[YD3_F] +~r~Du hast bereits einen Mafia-Wagen! + +[YD3_G] +~r~Du hast bereits einen Yakuza-Wagen! + +[YD3_H] +~g~Diablo-Wagen besorgt! + +[YD3_I] +~g~Mafia-Wagen besorgt! + +[YD3_J] +~g~Yakuza-Wagen besorgt! + +[YD3_K] +~r~Das Auto ist praktisch Schrott! Repariere es! + +[YD3_L] +~g~Fahr das Auto zu der ~p~Garage! + +[YD3_M] +~r~Du hast dich überschlagen! Besorg ein anderes Auto! + +[YD4_A] +Hör zu! + +[YD4_A1] +Komm nach Bedford Point. + +[YD4_A2] +In einem alten Wagen ist etwas versteckt. Das brauche ich pronto! + +[YD4_B] +BRIEF: Man hört, du bist ein viel beschäftigter Mann. Nun, ich bin eine viel beschäftigte Frau. + +[YD4_C] +Es wird Zeit, dass du die wahre Macht von 'SPANK' kennen lernst! Besos y fuderes, Catalina, xxx. + +[YD4_D] +PS: FAHR ZUR HÖLLE!! + +[YD4_1] +Irre auf SPANK!! + +[YD4_2] +~g~Zerstöre die SPANK-Lieferwagen!! + +[HM_1] +'DRIVE-BY ACTION' + +[HM_2] +'TÖDLICHES SPIELZEUG' + +[HM_3] +'DAS HÖLLENFAHRTSKOMMANDO' + +[HM_5] +'SHOWDOWN' + +[HOOD1_A] +Komm zu dem Fernsprecher in Wichita Gardens. Es gibt Arbeit. + +[HM1_A] +Yo! Hier spricht D-Ice von den Red Jacks! + +[HM1_C] +Diese pubertären Idioten treiben sich hier rum und haben nichts als Knarren und SPANK im Sinn. + +[HM1_3] +~g~Die 'Nines' hängen in ihrem Gebiet in Wichita Gardens rum. + +[HM2_3] +Wenn du die Reifen eines Wagens triffst, explodiert der Buggy! + +[HM2_4] +Wenn er außer Reichweite kommt, explodiert der Buggy! + +[HM2_5] +~r~Außer Reichweite! + +[HM3_1] +~g~Fahr zur Garage. Aber Vorsicht: Wird der Wagen zu stark beschädigt, explodiert er! + +[HM3_2] +~g~Bring den Wagen zurück. Er muss in 1A Zustand sein. Keine Delle! + +[HM3_3] +~g~Repariere den Wagen! + +[HM4_D] +~g~Besorg dir ein Fahrzeug! + +[HM4_E] +TEXT NO LONGER REQUIRED + +[HM4_1] +~g~Begib dich an den Ort, wo die Ladung herumliegt. Du musst 30 Goldbarren einsammeln. + +[HM4_2] +~g~Denk dran: Wird der Wagen zu schwer und zu langsam, fahr in die Garage und lade das Zeug aus. + +[HM5_3] +~r~Du solltest nur einen Baseball-Schläger verwenden! + +[HM5_4] +~r~Deine Kontaktperson ist tot! + +[MEA1] +'DER ABKOCHER' + +[MEA2] +'DIE DIEBE' + +[MEA3] +'DIE FRAU' + +[MEA4] +'IHR LIEBHABER' + +[MEAT1_A] +Man sagt, du kannst mir bei einigen Problemen helfen. Wenn das so ist, komm zu dem Fernsprecher in Trenton. + +[MEA1_B3] +~g~Triff dich mit dem Bankier. + +[MEA1_B6] +~g~Fahr das Auto zur Schrottpresse, um Beweise zu beseitigen. Steig aus, der Kran hebt das Auto rein. + +[MEA1_1] +~r~Der Bankier ist tot! + +[MEA1_2] +~r~Du solltest den Wagen doch verschrotten! + +[MEA1_3] +~g~Steig aus dem Wagen! + +[MEA1_4] +~r~Du hast den Bankier nicht mitgenommen! + +[MEA2_B3] +~g~Triff dich mit den Dieben. + +[MEA2_B4] +~g~Bring sie zur Bitchin' Dog Food Fleischfabrik. + +[MEA2_B6] +~g~Spritz den Wagen um, um Beweise zu beseitigen. + +[MEA2_1] +~r~Du solltest den Wagen doch verschrotten! + +[MEA2_2] +~r~Ein Dieb ist tot! + +[MEA2_4] +~r~Du hast einen Dieb nicht mitgenommen! + +[MEA3_B3] +~g~Hole Mrs. Chonks ab. + +[MEA3_B6] +~g~Versenke den Wagen im Meer, um Beweismaterial zu beseitigen. + +[MEA3_1] +~r~Die Frau ist tot! + +[MEA3_2] +~r~Du solltest den Wagen doch im Meer versenken! + +[MEA3_3] +~r~Du hast die Frau nicht mitgenommen! + +[MEA4_B3] +~g~Hol den Liebhaber seiner Frau ab. + +[MEA4_B6] +Dazu ist es jetzt zu spät, Marty. Du hattest deine Chance. Jetzt übernehme ich den Laden hier. + +[MEA4_1] +~r~Carlos ist tot! + +[MEA4_3] +~r~Du hast Carlos den Kredithai nicht mitgenommen! + +[LOOK_A] +Halte die ~h~~k~~VEHICLE_LOOKLEFT~-Taste ~w~oder die ~h~~k~~VEHICLE_LOOKRIGHT~-Taste~w~ gedrückt, um im Wagen sitzend nach ~h~links~w~ oder ~h~rechts~w~ zu sehen. Drücke beide Tasten, um nach ~h~hinten~w~ zu sehen. + +[LOVE6_1] +~g~Locke jetzt die Cops vom Lagerhaus weg! + +[LOVE6_2] +~r~Du hast die Cops nicht weit genug weggelockt! + +[RM4_3] +~r~Rays Partner ist entkommen! + +[RM6_C] +Die CIA scheint sich sehr für SPANK zu interessieren. + +[RM6_C1] +Sie wollen, dass wir das Kartell in Ruhe lassen. + +[C_PASS] +BEDROHUNG AUSGERÄUMT! + +[CTUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Bürgerwehr Missionen zu aktivieren oder zu deaktivieren. + +[CTUTOR2] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Bürgerwehr Missionen zu aktivieren oder zu deaktivieren. + +[COPCART] +~g~Du hast ~1~ Sekunden, um zu einem Polizeiwagen zurückzukehren, bevor die Mission endet. + +[C_FAIL] +Mission beendet! + +[C_CANC] +~r~Bürgerwehr Mission abgebrochen! + +[C_ESCP] +~r~Der Verdächtige ist entwischt! + +[C_TIME] +~r~Deine Zeit als Gesetzeshüter ist vorbei! + +[C_VIGIL] +BÜRGERWEHR BONUS!! + +[A_FAIL2] +~r~Deine Bummelei war tödlich für den Patienten! + +[A_FAIL3] +~r~Der Patient ist tot!! + +[A_PASS] +Gerettet! + +[F_FAIL2] +~r~Du kommst zu spät! + +[A_COMP2] +Du ermüdest nie! + +[RM2_M] +Wenn du eine Waffe brauchst, komm vorbei und nimm dir aus den Kästen, was du brauchst. + +[HEAL_A] +Dein ~h~Gesundheit~w~ wird rechts oben auf dem Bildschirm in Orange angezeigt. + +[YD1_CNT] +~1~ von 15! + +[FM1_9] +~g~Da vorne ist die Party. Setz Maria vor dem Gebäude ab. + +[FM1_Y] +~w~Das war seit langem mal wieder ein guter Abend. Und du hast mich wirklich gut behandelt, mit Respekt und so. + +[FM1_AA] +~w~Oh, ich geh jetzt besser. Ich hoffe, wir sehen uns. + +[NOCONTE] +Bitte stecken Sie einen Analog Controller (DUALSHOCK#) oder einen Analog Controller (DUALSHOCK#2) in Controller-Anschluss 1, um fortzufahren. + +[WRCONT] +Der Controller an Controller-Anschluss 1 wird nicht unterstützt. GTA3 benötigt einen Analog Controller (DUALSHOCK#) oder einen Analog Controller (DUALSHOCK#2). + +[WRCONTE] +Der Controller an Controller-Anschluss 1 wird nicht unterstützt. GTA3 benötigt einen Analog Controller (DUALSHOCK#) oder einen Analog Controller (DUALSHOCK#2). + +[WRONGCD] +Falsche DVD. Bitte legen Sie die richtige DVD ein. + +[NOCD] +Die DVD-Lade ist leer. Bitte DVD einlegen. + +[OPENCD] +Die DVD-Lade ist offen. Bitte schließen. + +[CDERROR] +Fehler beim Lesen der GTA3 DVD. + +[RESTART] +Neues Spiel wird gestartet + +[GA_3] +Keine Gratisjobs mehr. Umspritzen kostet $1000! + +[GA_1] +So was heißes rühre ich nicht an! + +[GA_1A] +Komm wieder, wenn du nicht so viel zu tun hast... + +[S_PROM2] +In die Garage nebenan kann 1 Fahrzeug eingestellt werden, wenn du das Spiel speicherst. + +[STOCK] +Nicht vorrätig + +[FM1_O] +~w~Er ist beim Bahnhof am Chinatown-Ufer, glaube ich. + +[EBAL_B] +Hier ist es! Los, wir tauchen ab und besorgen uns neue Klamotten! + +[EBAL_G] +Hier ist Luigis Club. Wir gehen hinten rum und nehmen den Lieferanteneingang. + +[AM4_3] +Du musst Asukas neuer Botenjunge sein! + +[AM4_4] +Hast du das Geld? Die ganze Summe? + +[AM4_5] +Ich weiß, was du denkst. Wieder so ein korrupter Cop. + +[AM4_6] +Tja, die Welt ist schlecht. + +[AM4_7] +Nur weil ich ein paar Partner verloren habe, sitzen mir die Typen von der Dienstaufsicht im Genick. + +[AM4_8] +Die glauben, ich habe Dreck am Stecken. + +[AM4_9] +Tja, die ganze Stadt ist ein einziges Dreckloch. + +[AM4_10] +Aber ich brauch Hilfe von außerhalb der Polizei. + +[AM4_11] +Falls du interessiert bist... du weißt, wo du mich findest. + +[CAM_A] +Drücke die ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~-taste~w~, um den ~h~Blickwinkel ~w~zu verändern, wenn du zu Fuß oder in einem Fahrzeug unterwegs bist. + +[CAM_B] +Drücke die ~h~Richtungstaste Oben~w~ und die ~h~Richtungstaste Unten~w~, um den ~h~Blickwinkel ~w~zu verändern, wenn du zu Fuß oder in einem Fahrzeug unterwegs bist. + +[KM2_1] +~g~Repariere den Wagen. Er muss top aussehen. + +[LM3_6] +Joey... + +[LM3_6A] +Na, ein bisschen Nahkampf mit deiner Braut gefällig? + +[LM3_9A] +Vielleicht gibt's Arbeit für dich. + +[LM3_9B] +Okay? + +[AWAY2] +~r~Sie sind entkommen. + +[AWAY] +~r~Er ist verschwunden! + +[JM6_1] +Fahr zu der Bank auf dem Boulevard. + +[GA_6B] { re3 change } +Park die Karre, mach sie durch Drücken der ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~ scharf, und dann HAU AB! + +[GA_7B] { re3 change } +Mach die Bombe mit der ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~ scharf. Sie geht hoch, wenn der Wagen angelassen wird. + +[BAT1] +~g~Nimm dir den Schläger! + +[EBAL_O] +Wenn du deine Sache gut machst, gibt's vielleicht noch mehr Jobs für dich. Und jetzt verschwinde! + +[HELP9_B] +Drücke die ~h~~k~~PED_FIREWEAPON~-Taste~w~, um mit dem Präzisionsgewehr zu ~h~feuern~w~. + +[HELP9_C] +Drücke die ~h~~k~~PED_FIREWEAPON~-Taste~w~, um mit dem Präzisionsgewehr zu ~h~feuern~w~. + +[JM6_8] +~r~Du hast alle Räuber verloren! + +[COLT_IN] +Die Pistole ist jetzt im AmmuNation vorrätig! + +[TAXI2] +~r~Die Zeit ist um! + +[TAXI3] +~r~Dein Fahrgast ist entsetzt geflohen! + +[TAXI7] +~r~Dein Wagen ist Schrott. Repariere ihn. + +[TAXI4] +Fahrt abgeschlossen! + +[TAXI5] +SPEED BONUS!! + +[TAXI6] +Taxi-Mission beendet + +[FRANGO] +~g~Salvatore sagt, du sollst zuerst Toni mit den Triaden helfen. + +[PAGEB12] +Polizei-Schmiergelder wurde im Versteck angeliefert. + +[PAGEB13] +Gesundheits-Powerups wurde im Versteck angeliefert. + +[PAGEB14] +Adrenalin wurde im Versteck vorrätig. + +[KM1_4] +~g~Du brauchst einen Polizeiwagen für diesen Job! + +[CAT1_B] +dann bring $500 000 zu der Villa in Cedar Grove. + +[JM2_C] +Er hat eine Imbissbude in Chinatown. + +[RM6_1] +Hier ist ein Schlüssel für eine Garage. + +[RM6_2] +Darin findest du Bargeld und ein paar 'Requisiten' für alle Fälle. + +[RM6_3] +Bis dann. + +[FE_INIP] +Initalisiere und lade Pausenmenü. Bitte warten. + +[FESZ_CA] +Abbrechen + +[FESZ_QU] +Beenden + +[FESZ_L1] +Spiel erfolgreich gespeichert! + +[FESZ_L2] +Spiel gespeichert unter: + +[FESZ_OK] +OK + +[FES_LGA] +Spiel laden + +[FES_NGA] +Neues Spiel + +[FES_CAN] +Abbrechen + +[FESZ_QL] +Alle nicht gespeicherten Daten des aktuellen Spiels werden verlorengehen. Ladevorgang fortsetzen? + +[FESZ_QD] +Dieses gespeicherte Spiel wirklich löschen? + +[FESZ_QO] +Dieses gespeicherte Spiel wirklich überschreiben? + +[FESZ_QR] +Wirklich ein neues Spiel beginnen? Alle Daten seit dem letzten Speichern werden verlorengehen. Weiter? + +[FESZ_QS] +SPEICHERN FORTSETZEN? + +[SLONFP] +MEMORY CARD-Steckplatz 1: Datei geschützt. + +[T4X4_1] +'PATRIOT RALLYE' + +[T4X4_2] +'SPAZIERFAHRT IM PARK' + +[T4X4_3] +'CHECKPOINT FIEBER' + +[MM_1] +'VOLLGAS IM PARKHAUS' + +[T4X4_1A] +~g~Du hast ~y~5 Minuten~g~, um ~y~15~g~ Checkpoints abzufahren. ~g~Die ~y~REIHENFOLGE IST BELIEBIG. + +[T4X4_1B] +~1~ von 15! + +[T4X4_1C] +~y~PASSIERE~g~ den ersten Checkpoint, dann läuft die Zeit. ~g~Jeder Checkpoint bringt dir ~y~20 SEKUNDEN~g~. + +[T4X4_2A] +~g~Du hast ~y~2 Minuten~g~, um ~y~12~g~ Checkpoints abzufahren. ~g~Die ~y~REIHENFOLGE IST BELIEBIG. + +[T4X4_2B] +~1~ von 12! + +[T4X4_2C] +~y~PASSIERE~g~ den ersten Checkpoint, dann läuft die Zeit. ~g~Jeder Checkpoint bringt dir ~y~10 SEKUNDEN~g~. + +[T4X4_3A] +~g~Du hast ~y~5 Minuten~g~, um ~y~20~g~ Checkpoints abzufahren. ~g~Die ~y~REIHENFOLGE IST BELIEBIG. + +[T4X4_3B] +~y~PASSIERE~g~ den ersten Checkpoint, dann läuft die Zeit. ~g~Jeder Checkpoint bringt dir ~y~15 SEKUNDEN~g~. + +[T4X4_3C] +~1~ von 20! + +[T4X4_F] +~r~Du hast gekniffen! Schon überfordert? + +[MM_1_A] +~g~Du hast ~y~2 Minuten~g~, um ~y~20 Checkpoints~g~ in dem Parkhaus abzufahren! ~g~Die ~y~REIHENFOLGE IST BELIEBIG. + +[MM_1_B] +~1~ von 20! + +[MM_1_C] +~g~Das macht 20 Sekunden plus ~y~5 SEKUNDEN~g~ für jeden Checkpoint. ~g~Die Zeit läuft ~y~AB SOFORT. + +[FM2_14] +~r~Du warst zu nah dran und hast Curly aufgeschreckt! + +[FM2_15] +~g~Nicht zu nahe ran, sonst schöpft Curly Verdacht! + +[UPSIDE] +~r~Du hast dich überschlagen! + +[FM2_16] +SCHRECK-O-METER: + +[LM3_11] +~g~Misty steigt in keinen Bus. Besorg ein anderes Fahrzeug! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Feuerwehrwagen + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Krankenwagen + +[FBICAR] +F.B.I. + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Polizei + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[TRAIN] +Zug + +[HELI] +Helikopter + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +RC Bandit + +[BELLYUP] +Triad + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Mafia + +[YARDICR] +Yardie + +[YAKUZCR] +Yakuza + +[DIABLCR] +Diablo + +[COLOMCR] +Cartel + +[HOODSCR] +Hoods + +[AEROPL] +Flugzeug + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Zu Fuß zurückgel. Meilen + +[FEST_DC] +Mit Auto gefahrene Meilen + +[FESTDFM] +Zu Fuß zurückgel. Meter + +[FESTDCM] +Mit Auto gefahrene Meter + +[FEST_R1] +Patriot Rallye in Sek. + +[FEST_R2] +Spazierfahrt im Park in Sek. + +[FEST_R3] +Checkpoint-Fieber in Sek. + +[FEST_RM] +Vollgas durch die Parkgarage in Sek. + +[FEST_LS] +Mit Krankenwagen gerettete Menschen + +[FEST_CC] +Kriminelle bei Bürgerwehr Mission + +[FEST_FE] +Gelöschte Feuer gesamt + +[FEST_LF] +Längster Flug in Dodo + +[FEST_BD] +Bestzeit Bombenentschärfung + +[FEST_RP] +Bestandene Amokfahrten + +[FEST_MP] +Erledigte Missionen + +[FEST_BB] +Schnelle Autos, Schnelles Geld: + +[FEST_H0] +Meiste Checkpoints + +[FEST_GC] +Geschrottete Gang-Autos: + +[FEST_H1] +Im Dschungel der Diablos + +[FEST_H2] +Mafia-Massaker + +[FEST_H3] +Der Casino-Coup + +[FEST_H4] +Rock'n'Roll mit Rumpo + +[USJI1] +TEXT NO LONGER REQUIRED + +[USJI2] +TEXT NO LONGER REQUIRED + +[USJI3] +TEXT NO LONGER REQUIRED + +[USJ] +MONSTER-STUNT-BONUS! + +[SPRAY] +Fahre deinen Wagen in die Lackiererei, um deinen ~h~Fahndungslevel~w~ loszuwerden und das Auto zu ~h~reparieren~w~ und ~h~umzuspritzen~w~. Kosten - ~h~$1000. + +[HM1_1] +~g~Fertige 20 Purple Nines in 2 Min. 30 Sek. ab. + +[KM1_8A] { re3 change } +Drücke die~h~ ~k~~VEHICLE_FIREWEAPON~-Taste~w~ zum ~h~Zünden der Bombe~w~. Aber geh vorher in Deckung! + +[KM1_8D] { re3 change } +Drücke die~h~ ~k~~VEHICLE_FIREWEAPON~-Taste~w~ zum ~h~Zünden der Bombe~w~. Aber geh vorher in Deckung! + +[KM1_12] +~g~Bring ihm zum Dojo, aber häng vorher die Cops ab! + +[RATNG1] +Taschendieb + +[RATNG2] +Laufbursche + +[RATNG3] +Gauner + +[RATNG4] +Soldat + +[RATNG5] +Profi + +[RATNG6] +Fahrer + +[RATNG7] +Gangster + +[RATNG8] +Obergangster + +[RATNG9] +Partner + +[RATNG10] +Troubleshooter + +[RATNG11] +Vollstrecker + +[RATNG12] +Capo + +[RATNG13] +Rechte Hand + +[RATNG14] +Vize + +[RATNG15] +Boss + +[1010] +~r~Dein Fahrzeug liegt auf dem Kopf + +[1011] +~r~Dein Fahrzeug liegt auf dem Kopf + +[1012] +~r~Dein Fahrzeug liegt auf dem Kopf + +[1013] +~r~Dein Fahrzeug liegt auf dem Kopf + +[1014] +~r~Dein Fahrzeug liegt auf dem Kopf + +[JM4_10] +Okay, Junge. Fahr mich zuerst in die Wäscherei in Chinatown. Ich hab da was zu erledigen. + +[JM4_11] +Die Waschweiber da haben ihr Schutzgeld nicht bezahlt. + +[JM4_12] +Und pass auf den Wagen auf. Joey hat ihn gerade repariert. + +[JM4_13] +Also, keine Dummheiten, okay? + +[KM4_11] +~g~Bring das Geld ins Casino! + +[FEF_BR2] +Orientiere dich, indem du alle bisherigen Einsatzbesprechungen durchsiehst. + +[TRAIN_1] +Kurowski Station + +[TRAIN_2] +Rothwell Station + +[TRAIN_3] +Baillie Station + +[SUBWAY1] +Portland Station + +[SUBWAY2] +Rockford Station + +[SUBWAY3] +Staunton South Station + +[SUBWAY4] +Shoreside Terminal + +[MEA4_2] +~r~Marty Chonks ist tot! + +[SPRAY1] +Fahre deinen Wagen in die Lackiererei, um deinen ~h~Fahndungslevel~w~ loszuwerden und das Auto zu ~h~reparieren~w~ und ~h~umzuspritzen~w~. Kosten - ~h~$1000~w~. Diesmal kostet es nichts. + +[JM4_A] +Ja, ich weiß, Toni, ich hab sie gut erzogen. Sie schnurrt, falls du verstehst, was ich meine. + +[JM4_5] +Komm später wieder, dann zeigen wir den Kerlen, was Sache ist. + +[AMMU_A] +Luigi sagt, du brauchst 'ne Knarre... + +[AMMU_B] +Joey sagt, ich soll dich 'ausrüsten'... + +[AMMU_C] +Geh hinten um den Laden herum. Ich hab eine 9mm im Hof deponiert. + +[AMMU_D] +Ich hab alles, was man so zur Selbstverteidigung braucht. + +[AMMU_E] +Willst du auch 'nen Waffenschein? + +[AMMU_F] +Auf den Ausweis verzichte ich. Du siehst vertrauenswürdig aus. + +[DETON] +DETONATION: + +[DRIVE_A] { re3 change } +Halt eine Uzi im Anschlag, wenn du in ein Fahrzeug steigst. Schau dann nach links oder rechts und drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~, um zu feuern. + +[DRIVE_B] { re3 change } +Halt eine Uzi im Anschlag, wenn du in ein Fahrzeug steigst. Schau dann nach links oder rechts und drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~, um zu feuern. + +[RECORD] +~g~NEUER REKORD!! + +[NRECORD] +~r~KEIN NEUER REKORD! + +[RCHELP] { re3 change } +Drücke die ~k~~VEHICLE_FIREWEAPON~-Taste oder fahre das ferngesteuerte Auto in die Räder eines Fahrzeugs, um es zu sprengen. + +[RCHELPA] { re3 change } +Drücke die ~k~~VEHICLE_FIREWEAPON~-Taste oder fahre das ferngesteuerte Auto in die Räder eines Fahrzeugs, um es zu sprengen. + +[RC_1] +Du hast 2 Minuten, um so viele Diablo-Autos wie möglich zu sprengen! + +[RC_2] +Du hast 2 Minuten, um so viele Mafia-Autos wie möglich zu sprengen! + +[RC_3] +Du hast 2 Minuten, um so viele Yakuza-Autos wie möglich zu sprengen! + +[RC_4] +Du hast 2 Minuten, um so viele Yardie-Autos wie möglich zu sprengen! + +[RC_5] +Du hast 2 Minuten, um so viele Hood-Autos wie möglich zu sprengen! + +[RC_6] +Du hast 2 Minuten, um so viele Kartell-Autos wie möglich zu sprengen! + +[RAMPAGE] +AMOKLAUF!! + +[RAMP_P] +AMOKLAUF BEENDET! + +[RAMP_F] +AMOKLAUF FEHLGESCHLAGEN + +[PAGE_00] +. + +[PAGE_01] +Dein Job: ~1~ Diablos in 120 Sekunden! + +[PAGE_02] +Zerstöre ~1~ Fahrzeuge in 120 Sekunden! + +[PAGE_03] +Dein Job: ~1~ Mafiosi in 120 Sekunden! + +[PAGE_04] +Dein Job: ~1~ Triaden in 120 Sekunden! + +[PAGE_05] +Dein Job: ~1~ Triaden in 120 Sekunden! + +[PAGE_06] +Zerstöre ~1~ Fahrzeuge in 120 Sekunden! + +[PAGE_07] +Dein Job: ~1~ Yardies in 120 Sekunden! + +[PAGE_08] +Dein Job: ~1~Yakuza in 120 Sekunden! + +[PAGE_09] +Zerstöre ~1~ Fahrzeuge in 120 Sekunden! + +[PAGE_10] +Zerstöre ~1~ Fahrzeuge in 120 Sekunden! + +[PAGE_11] +Dein Job: ~1~ Yardies in 120 Sekunden! + +[PAGE_12] +Dein Job: ~1~Yakuza in 120 Sekunden! + +[PAGE_13] +Dein Job: ~1~ Yardies in 120 Sekunden! + +[PAGE_14] +Dein Job: ~1~ Kolumbianer in 120 Sekunden! + +[PAGE_15] +Dein Job: ~1~ Hoods in 120 Sekunden! + +[PAGE_16] +Zerstöre ~1~ Fahrzeuge in 120 Sekunden! + +[PAGE_17] +Nimm dir mit einem Auto ~1~ Kolumbianer in 120 Sekunden vor! + +[PAGE_18] +Zerstöre ~1~ Fahrzeuge mit einem Drive-by in 120 Sekunden! + +[PAGE_19] +Dein Job: ~1~ Kolumbianer in 120 Sekunden! + +[PAGE_20] +Dein Job: ~1~ Hoods in 120 Sekunden! + +[JM1_A] +Mir ist langweilig. Wann läuft hier endlich mal was ab? + +[JM1_B] +Gleich, Schätzchen. Muss nur schnell was erledigen. + +[JM1_C] +Ich hab 'nen kleinen Job für dich. + +[JM1_D] +Die Forelli Brüder zahlen ihre Schulden nicht. + +[JM1_E] +Ich muss ihnen ein bisschen Respekt einbläuen. + +[JM1_F] +Lips Forelli stopft sich gerade in Marcos Bistro seinen fetten Bauch voll. + +[JM1_G] +Klau ihm sein Auto und bring es in 8-Balls Bombenwerkstatt in Harwood. + +[JM1_H] +Du kennst doch 8-Ball, oder? + +[JM1_I] +Sobald er 'ne Bombe eingebaut hat, parkst du die Karre an ihrem alten Platz. + +[JM1_J] +Dann lehn dich zurück und sieh dir die Show an. + +[JM1_K] +Aber Beeilung. Der Typ isst nicht ewig. + +[CAT2_A1] +Komm schon, du dummes Luder! + +[CAT2_A] +Bist du eigentlich gekommen, um Maria zu retten, oder weil du heiß auf mich bist? + +[CAT2_B] +Falls du's noch nicht weißt: + +[CAT2_B2] +Die Dates mit dir waren Arbeit - dich fertig zu machen, wird ein Vergnügen. + +[CAT2_C] +Du bist ein armes Würstchen, Amigo. + +[CAT2_D] +Wirf die Kohle rüber! + +[CAT2_E] +Du bist an sich ein fähiger Bursche. + +[CAT2_E2] +Aber du kapierst nicht, dass man mir nicht trauen kann. + +[CAT2_E3] +Macht den Idioten fertig. + +[CAT2_J] +Bring das Ding in die Luft!! + +[HM5_1] +Yo, Ice hat dich angekündigt. Es gibt hier Regeln. Nur Schläger. Keine Knarren, keine Autos. + +[HM5_5] +Hier geht's um Respekt und Achtung, klar? + +[HELP14] +Um Waffen aufzunehmen, gehe darüber hinweg. Dies funktioniert nicht, wenn du in einem Fahrzeug sitzt. + +[CRUSH] +Parke in der markierten Zone und steig aus. Das Fahrzeug wird dann geschrottet. + +[DIAB2_B] +Eine Bande von Taugenichtsen will mir ans Leder, falls ich sie nicht an meinen Geschäften beteilige. + +[DIAB2_C] +Aber da sind sie an den Falschen geraten, Amigo. + +[DIAB2_D] +Die haben eine Schwäche für Eiscreme. + +[DIAB2_E] +Hol die Bombe ab, die ich in Harwood versteckt habe, + +[DIAB2_F] +schnapp dir einen normalen Eis-Wagen, der rumfährt + +[DIAB2_G] +und locke diese Idioten mit dem Eis-Jingle in ihr Verderben. + +[DIAB2_H] +Sie verstecken sich in einem Lagerhaus auf den Atlantic Quays. + +[DIAB3_A] +Irgendwelche frechen Triaden haben gestern Nacht meinen Traumwagen geklaut, + +[DIAB3_B] +geschrottet und ausbrennen lassen. + +[DIAB3_C] +Im Kofferraum waren einige sehr wertvolle Erinnerungsstücke, + +[DIAB3_D] +unersetzliche Sammlerstücke, mein Freund. + +[DIAB3_E] +Ich hab eine ziemlich heiße Waffe am Rand von Chinatown versteckt. + +[DIAB3_F] +Hol sie dir und lehre die Triaden, El Burros Zorn zu fürchten. + +[DIAB3_1] +NIMM DIR 25 TRIADEN VOR + +[DIAB4_A] +Ein mieser Dieb hat einen Transporter mit der gesamten neuen Ausgabe meines Magazins gestohlen! + +[DIAB4_B] +Aber dieser SPANK-Junkie hat die Hecktüren offen gelassen + +[DIAB4_C] +und jetzt verteilt sich mein wunderschönes, + +[DIAB4_D] +geschmackvoll fotografiertes Magazin gleichmäßig über ganz Liberty! + +[DIAB4_E] +Nimm den Transporter, folge der Spur von 'Donkey Does Dallas' Nr. 1, 2 und 3. + +[DIAB4_F] +Und sammle das Zeug wieder ein. + +[DIAB4_G] +Wenn du bei diesem diebischen Junkie angekommen bist, nimm ihn dir vor! + +[DIAB4_H] +Und dann lieferst du meine Magazine an das XXX Mags im Rotlichtviertel. + +[DIAB4_1] +~g~Fahr den Transporter zur Rückseite des XXX Mags. + +[HM1_E] +Ich möchte, dass du diesen Vollidioten zeigst, wie ein echter Driveby aussieht. + +[HM1_H] +Schaff mir diese Nines vom Hals!! + +[HM2_A] +Die Nines bringen mich in die Klemme. + +[HM2_B] +Die Kerle haben gepanzerte Autos, und jetzt verhökern sie SPANK + +[HM2_C] +an meine Brüder, als wäre das gar nichts. + +[HM2_D] +Auf dem Weg steht ein Wagen geparkt. + +[HM2_E] +Da ist was drin, was diesen Feiglingen Beine machen wird. + +[HM3_A] +Irgend so ein Mistkerl hat 'ne Bombe in meine Karre eingebaut. + +[HM3_B] +Wenn ich die Karre verliere, stehe ich als Trottel da. + +[HM3_C] +Hol meinen Wagen ab und bring ihn in die Werkstatt in St. Mark's. + +[HM3_D] +Die sollen die Bombe da entschärfen. + +[HM3_E] +Die Uhr tickt und die Drähte sind locker. + +[HM3_F] +Ein Schlagloch zuviel, und das Ding geht hoch. + +[HM3_G] +Also, Beeilung! + +[HM4_A] +Yo, ein Flugzeug der Staatsbank ist gerade beim Francis Int. Airport abgestürzt. + +[HM4_B] +Auf dem ganzen Rollfeld liegt Platin rum + +[HM4_C] +Schnapp dir ein Auto und sack so viel wie möglich davon ein. + +[HM4_F] +Du kannst das Zeug in einer meiner Garagen abladen. + +[HM4_G] +Platin ist verdammt schwer. Dein Auto wird etwas langsamer laufen. + +[HM4_H] +Also, lade regelmäßig etwas davon in einer Garage ab. + +[HM5_A] +Die Nines sind nur noch ein versprengter Haufen... + +[HM5_B] +aber sie machen immer noch Stress. + +[HM5_C] +Jetzt wollen sie's endgültig wissen. + +[HM5_D] +Eine Gang von denen gegen zwei von uns. Oder vielmehr... + +[HM5_E] +zwei von euch. + +[HM5_F] +Ich wäre ja dabei, aber... + +[HM5_G] +ich hab noch drei Monate Bewährung, + +[HM5_H] +verstehst du, was ich meine? + +[HM5_I] +Triff dich mit meinem kleinen Bruder. + +[HM5_J] +Er zeigt dir, wo der Fight stattfindet, okay? + +[MEA1_B] +Mein Name ist Chonks, Marty Chonks. + +[MEA1_C] +Mir gehört die Bitchin' Dog Food Fleischfabrik um die Ecke. + +[MEA1_D] +Ich hab Geldsorgen, aber wer hat die nicht, was? + +[MEA1_E] +Ich treff mich später mit meinem Banker. + +[MEA1_F] +Das ist ein linker Säger, der mir dauernd die Kreditraten raufsetzt, um sich 'ne Scheibe abzuschneiden. + +[MEA1_G] +Nimm mein Auto, hol ihn ab und bring ihn hierher. + +[MEA1_H] +Ich habe eine kleine Überraschung für diesen Blutsauger!! + +[MEA2_A] +Ich hab Diebe angeheuert, um in meine Wohnung einzubrechen. + +[MEA2_C] +Jetzt drohen diese Dreckskerle, sie erzählen alles der Versicherung, + +[MEA2_D] +wenn ich sie nicht beteilige. + +[MEA2_E] +Ist das zu fassen? + +[MEA2_F] +Ich hab einen Wagen innerhalb der Fabrik abgestellt. + +[MEA2_G] +Hol damit die Kerle in ihrem Revier im Rotlichtbezirk ab. + +[MEA2_H] +Bring sie in die Fabrik, damit ich ihnen meinen Standpunkt klarmachen kann. + +[MEA3_A] +Wenn nicht bald ein Haufen Geld ins Haus kommt, gehe ich pleite. + +[MEA3_B] +Meine Frau hat eine Versicherung, aber mir hat sie immer nur das Geld aus der Tasche gezogen. + +[MEA3_C] +Ich hab ein Auto am gewohnten Ort abgestellt. + +[MEA3_D] +Hol damit meine Frau vom Classic Nails Studio ab und bring sie in die Fabrik. + +[MEA4_A] +Mist, ich steck in der Klemme! + +[MEA4_B] +Meine Frau hatte was mit einem Typen, dem ich Geld schulde. + +[MEA4_C] +Der ist jetzt ziemlich sauer und will sich rächen! + +[MEA4_E] +Der denkt, er kann mich erpressen... + +[MEA4_F] +aber ich glaube eher, + +[MEA4_G] +dass auch dieser gute Mann als Hundefutter enden wird. + +[WELCOME] +WILLKOMMEN IN + +[HM1_2] +~g~Besorg dir ein Fahrzeug. Beachte: Hier zählt nur die Uzi! + +[HELP8_B] +Drücke die ~h~~k~~PED_SNIPER_ZOOM_IN~-Taste~w~ zum ~h~Heranzoomen~w~ mit dem Gewehr, und die ~h~~k~~PED_SNIPER_ZOOM_OUT~-Taste~w~ zum ~h~Wegzoomen~w~. + +[LRQC_1] +Asuka und ich haben was zu besprechen. + +[LRQC_2] +Fahr doch ein bisschen durch die Gegend. + +[LRQC_3] +Du wirst einen Unterschlupf brauchen. + +[LRQC_4] +Am Rand von Belleville ist ein Lagerhaus. Das könnte was für dich sein. + +[LRQC_5] +Komm wieder hierher, wenn du fertig bist. + +[LRQC_6] +Dann können wir uns ein bisschen unterhalten. + +[JM6_5] +~g~Du brauchst einen Fluchtwagen, du Idiot! + +[JM2_F] +Wenn du 'ne Knarre brauchst, geh zum Hintereingang vom AmmuNation gegenüber der U-Bahn. + +[LOVE4_7] +~g~Es gibt einen Bauhof auf Staunton Island. Vielleicht haben sie das Päckchen dorthin gebracht. + +[LOVE4_8] +~g~Du brauchst ein Auto, um die Garage zu öffnen. + +[TSCORE] +EINKÜNFTE: $~1~ + +[AM1_9] +~r~Salvatore hat sich in Luigis Club abgesetzt! + +[AM1_6] +~g~Wenn du vor Luigis Club rumhängst, bemerken dich die Mafiosi! + +[TM2_3] +~g~Das ist eine Falle! Nimm sie dir alle vor! + +[FM4_1] +Hier Maria. Das mit dem Wagen ist eine Falle! Komm zu dem Steg südlich der Callahan Bridge. + +[JM1_7] +~g~Mach die Wagentür zu, sonst merkt er was! + +[KM5_1] +~g~DEALER ERLEDIGT!! + +[KM5_6] +~g~Du musst mind. 8 Yardie Dealer außer Gefecht setzen. + +[KM5_7] +~g~Beeilung! Sobald sie ihr SPANK verhökert haben, verschwinden sie wieder. + +[RM3_8] +~r~Dieser Wagen dient nur zur Ablenkung!! + +[LM3_8] +Hi, ich bin Joey. + +[LM3_9] +Luigi sagt, du bist verlässlich. Also komm später wieder. + +[KM3_5] +~g~Drück die Hupe, damit der Deal in Gang kommt. + +[LOVE7] +LOVES VERSCHWINDEN + +[LOVE2_5] +~g~Auftrag erledigt. Verschwinde aus Newport und dann weg mit dem Wagen! + +[AS2_11] +~g~~1~ VON 9! + +[GARAGE1] +~g~Steig aus und geh nach draußen. + +[KM3_11] +~g~Das Kartell wurde angegriffen. Die Aktentasche wurde nicht sichergestellt. + +[KM3_12] +~g~Nimm dir sämtliche Kolumbianer vor, zerstöre die Fahrzeuge und stell die Aktentasche sicher. + +[KM3_13] +~g~Bring die Aktentasche ins Casino. + +[RM5_6] +~g~Er ist abgehauen! Zerstöre seine Panzerung mit einem Auto oder einer Explosion!! + +[PBOAT_1] { re3 change } +Drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~, um die Bordkanonen abzufeuern. + +[PBOAT_2] { re3 change } +Drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~, um die Bordkanonen abzufeuern. + +[DIAB1_B] +Hier El Burro, von den Diablos. + +[DIAB1_D] +Du bist neu in Liberty, aber du hast bereits eine guten Ruf auf der Straße. + +[DIAB1_E] +Bei der alten Schulhalle nahe der Callahan Bridge findet ein Rennen statt. + +[DIAB1_F] +Besorg dir 'nen fahrbaren Untersatz. Wer als erster alle Checkpoints abfährt, ist Sieger. + +[HM2_1] { re3 change } +Zerstöre die gepanzerten Fahrzeuge mit den Buggies. Zur Zündung drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~. + +[HM2_1A] { re3 change } +Zerstöre die gepanzerten Fahrzeuge mit den Buggies. Zur Zündung drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~. + +[HM2_2] +~r~Du hast nicht alle gepanzerten Fahrzeuge zerstört! + +[HM2_6] +~g~Gepanzertes Fahrzeug zerstört! + +[RM3_A] +Ich kenne einen wichtigen Mann in der Stadt, mit + +[RM3_H] +sagen wir mal, einem Sinn für's Exotische und dem nötigen Kleingeld dafür. + +[RM3_B] +Er steht unter Anklage und der Staatsanwalt hat einige ziemlich peinliche Fotos von ihm + +[RM3_C] +auf einer Friedhofsparty oder so. + +[LOVE6_A] +Kleine Lektion in Sachen Business, mein Freund: + +[LOVE6_E] +Hast du eine hochinteressante Ware, wird Gott und die Welt sie dir abjagen wollen... + +[LOVE6_C] +Spezialeinheiten haben die Gegend um meinen Geschäftsfreund und das Päckchen abgeriegelt. + +[LOVE6_D] +Mach, dass du hinkommst, nimm den Transporter und lenk sie ab. + +[LOVE6_F] +Beschäftige die Typen, damit er sich absetzen kann. + +[AM3_C] +Vermutlich ist er in der Bucht draußen, während du dies liest. Nimm mein Boot und mach seiner Karriere ein Ende! + +[FESZ_UC] +ABBRECHEN + +[FEDS_SM] +L1, R1-MENÜ WECHSELN + +[FEDS_AS] +;=-AUSWAHL ÄNDERN + +[FEDSAS2] +<>-AUSWAHL ÄNDERN + +[FEDS_SS] +L1, R1-AUSWAHL ÄNDERN + +[FEDSSC1] +;-SCHNELLER BILDLAUF + +[FEDSSC2] +=-BILDLAUF STOP + +[MEA2_3] +~g~Bring das Auto zurück zur Fabrik. + +[RM1_3] +~r~McAffrey ist entkommen! + +[RM1_4] +~g~Du hast alle Granaten verbraucht! Hol neue im AmmuNation! + +[RM1_5] +~g~Zurück! Zünde die sichere Wohnung an! + +[RM6_4] +~g~Du musst zu der Garage und Rays Auto mit den Waffen holen. + +[RM6_5] +~g~Die Brücke wird von der CIA überwacht. Such dir eine andere Route. + +[HM2_F] +Und zerstöre alle ihre gepanzerten Wagen. + +[HM_4] +'GOLDRAUSCH' + +[MEA2_B5] +TEXT NO LONGER NEEDED + +[MEA1_B5] +TEXT NO LONGER NEEDED + +[MEA3_B5] +TEXT NO LONGER NEEDED + +[MEA4_B7] +aber wenn du in mein Büro kommst, könnte ich... + +[MEA3_B4] +Marty will mich sehen? Das muss aber schnell gehen, ich hab nämlich einen Friseurtermin. + +[KM3_7] +Das ist eine Falle der Yakuza, Mann! + +[FES_LOF] +Laden fehlgeschlagen. + +[P1INSA] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1: ~1~K freier Speicherplatz. Erforderlich: ~1~K. + +[P1INSN] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1: Kein freier Speicherplatz. Bitte einige Dateien löschen. + +[FES_SLO] +DATEI + +[FES_ISC] +IST BESCHÄDIGT + +[FESZ_TI] +Z1 SPEICHERN + +[FESZ_SA] +Spiel speichern + +[P1NOIN] +Keine Memory Card (PS2) in MEMORY CARD-Steckplatz 1 + +[P1INSE] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 vorhanden + +[MC_LDFL] +Laden fehlgeschlagen! + +[MC_NWRE] +Spiel wird neu gestartet + +[LOVE6_3] +~g~Du hast ~1~ Sekunden, um zu dem Securicar zurückzukehren, bevor die Mission fehlschlägt. + +[LOVE6_4] +~r~Du hast den falschen Securicar abgehängt! + +[HELP1] +Halte in der Mitte der blauen Markierung. + +[HELP12] +Geh ins Zentrum der blauen Markierung, um eine Mission zu starten. + +[HJSTAT] +Distanz: ~1~.~1~m Höhe: ~1~.~1~m Saltos: ~1~ Drehungen: ~1~_ + +[HJSTATW] +Distanz: ~1~.~1~m Höhe: ~1~.~1~m Saltos: ~1~ Drehungen: ~1~_ Und was für eine Landung! + +[DIAB1_5] +ZEIT: + +[LOVE3_4] +~r~Du hast das Flugzeug zerstört! + +[F_FAIL1] +Feuerwehr -Mission beendet + +[F_CANC] +~r~Feuerwehr -Mission abgebrochen! + +[F_EXTIN] +FEUER: + +[A_COMP1] +Krankenwagen Missionen abgeschlossen! + +[A_CANC] +~r~Krankenwagen Mission abgebrochen! + +[A_COMP3] +Krankenwagen Missionen abgeschlossen! Du wirst beim Laufen nie ermüden! + +[ATUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Krankenwagen Missionen an- oder abzuschalten. + +[ATUTOR3] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Krankenwagen Missionen an- oder abzuschalten. + +[ALEVEL] +Krankenwagen Mission Level ~1~ + +[A_FAIL1] +Krankenwagen Mission beendet + +[FEST_HA] +Höchster Krankenwagen Missions Level + +[A_SAVES] +GERETTETE MENSCHEN: ~1~ + +[C_KILLS] +KRIMINELLE: ~1~ + +[HM1_B] +Ich hab 'n Problem. Die wollen mich verscheißern. + +[AM2_A] +Sehr erfreulich, dass Salvatore nicht mehr unter uns weilt. + +[AM2_A2] +Du bist ein effizienter Mann - das gefällt mir. + +[AM2_B] +Das ist mein Bruder Kenji. + +[AM2_C] +Asuka hat einen kleinen Job für dich, aber wenn du fertig bist, komm in mein Casino, dann reden wir. + +[AM2_D] +Typisch Kenji, immer will er mit meinen Spielsachen spielen. + +[AM2_E] +Mein Spitzel bei der Polizei hat mir gesteckt, dass die Mafia unsere Aktivitäten im Auge behält. + +[AM2_E2] +Sie wollen dich aufstöbern. + +[AM2_F] +Wir müssen unsere Operationen ruhen lassen, bis wir ihnen das Handwerk gelegt haben. + +[AM2_G] +Zieh die Kerle aus dem Verkehr und mach dieser Vendetta ein für alle mal ein Ende. + +[F_START] +~g~Brennendes Fahrzeug in der Gegend von ~a~ gemeldet. Lösche den Brand. + +[AM4_1A] +Komm zu dem Fernsprecher im West Belleville Park. + +[AM4_1B] +Komm zu dem Fernsprecher auf dem Liberty Campus. + +[AM4_1C] +Komm zu dem Fernsprecher im South Belleville Park. + +[AM4_1D] +Wir treffen uns im Toilettenhäuschen im Park. + +[HJSTATF] +Distanz: ~1~Fuß Höhe: ~1~Fuß Saltos: ~1~ Drehungen: ~1~_ + +[HJSTAWF] +Distanz: ~1~Fuß Höhe: ~1~Fuß Saltos: ~1~ Drehungen: ~1~_ Und was für eine Landung! + +[HM1_F] +Aber pass auf. Es werden Jacks da sein, und die werden denken, du willst auch ihnen ans Leder! + +[HM1_D] +Sie nennen sich 'Nines' und ihre Farbe ist 'Lila'. Und jeder Tag im Zeichen dieser Farbe... + +[HM1_G] +ist ein Tag, an dem die Jacks schlecht aussehen. + +[MEA2_B] +Und stiehl ein paar Sachen, damit ich die Versicherung kassieren kann. + +[TM3_H] +~w~Das hast du gut gemacht vorhin, wirklich gut, mein Junge. + +[TM3_I] +~w~Komm, wir stellen dich dem Don vor. + +[TM3_J] +~w~Heeyyy! Luigi! + +[TM3_K] +~w~Meine Girls fragen andauernd nach dir, Salvatore. Du warst so lange nicht mehr bei uns. + +[TM3_L] +~w~Sag ihnen, wenn diese lästige Geschichte hier vorbei ist, + +[TM3_M] +~w~gehen wir alle in den Club und feiern, okay? + +[TM3_N] +~w~Ah, mein Junge! + +[TM3_N2] +~w~Hallo, Paps. + +[TM3_O] +~w~Hast du endlich eine Frau gefunden? + +[TM3_P] +~w~Hey, Deine Mutter - Gott hab sie selig - würde sich im Grab umdrehen, + +[TM3_Q] +~w~wenn du keine abkriegen würdest. + +[TM3_R] +~w~Ich weiß, Paps, ich arbeite dran. + +[TM3_S] +~w~TONI! Wie geht's deiner Mamma? + +[TM3_T] +~w~Sie ist eine großartige Frau! Stark. Florentinerin eben. + +[TM3_U] +~w~Es geht ihr gut. Sehr gut. + +[TM3_V] +~w~Hervorragend. Okay, geht schon mal vor, ich hab was mit unserem neuen Freund hier zu bereden. + +[TM3_W] +~w~Ich sehe goldene Zeiten auf dich zukommen, mein Junge... + +[RM1_A] +Dieser McAffrey! Der hat mehr Bestechungsgeld kassiert als jeder andere. + +[RM1_B] +Jetzt denkt er, er wird ehrenhaft entlassen, wenn er als Kronzeuge aussagt. + +[RM1_C] +Er ist einfach ein Verräter. + +[RM4_B] +Wir müssen ihn zum Schweigen bringen - für immer. + +[RM4_E] +Er soll bei den Fischen schlafen, statt sie zu essen. + +[LOVE3_B] +Beim Landeanflug auf den Flughafen wird ein kleines Flugzeug die Bucht überfliegen. + +[LOVE4_D] +Leider hat sich die Hafenbehörde die Maschine geschnappt und wollte sie auseinandernehmen, + +[LOVE4_H] +bis ich das mit erheblichen Unkosten verhindert habe. + +[LOVE4_E] +Fahr über die Brücke nach Shoreside Vale und dann zum Francis Int. Airport. + +[GTAB_A] +Hey, schaffen wir das hier weg. Weiß der Teufel, was das ist. + +[GTAB_B] +Aber da er's unbedingt haben will, muss es wohl was wert sein. + +[GTAB_C] +Wer zum Teufel...? + +[GTAB_D] +DU! + +[GTAB_E] +Hey, ganz ruhig, Amigo! De nada! De nada! + +[GTAB_F] +Ich dachte, ich hätte dich erledigt! + +[GTAB_G] +Nicht schießen, Amigo! Kein Problem. Wir sind alle Freunde. Hier, nimm. + +[GTAB_H] +Nicht so memmenhaft! + +[GTAB_I] +Wir haben keine Wahl, Baby! + +[GTAB_J] +Wir haben immer eine Wahl, du dämlicher Idiot! + +[GTAB_K] +Sorry wegen dem abgedrehten Flittchen, Mann. Die sind doch alle gleich. Por favor? + +[GTAB_L] +Das Luder ist also entwischt. + +[GTAB_M] +Aber du hast mir einen Gefallen getan. + +[GTAB_N] +Du bist nicht der einzige, der eine Rechnung mit dem Kartell offen hat. + +[GTAB_O] +Dieser Wurm hat meinen Bruder auf dem Gewissen! + +[GTAB_P] +I hab nie einen Yakuza umgebracht! + +[GTAB_Q] +Lügner! Wir alle haben den Kartell-Attentäter gesehen. + +[GTAB_R] +Wir werden euch kolumbianische Hunde alle erledigen! + +[GTAB_S] +Ich befasse mich mit unserem Freund. Mal sehen, was ich aus ihm rauskriege. + +[GTAB_T] +Komm später wieder. Ich werde deine Dienste brauchen. + +[GTAB_U] +Bitte, Amigo, lass mich nicht hier bei ihr! Die ist 'n Psycho! Amigo? Hey, AMIIIGO!!!...Aiiieeeeaaargghh! + +[LOVE5_A] +Du bist eine sichere Bank. So etwas ist selten in diesen schlechten Zeiten. + +[KM3_1] +~g~Das Kartell erwartet eine Yardie Gang. Klau einen Yardie-Wagen! Fahr nach Norden. In Newport fndest du einen. + +[LOVE1_1] +~g~Mit einem Wagen der Kolumbianer kommst du in ihren Unterschlupf rein. Im Norden, in Fort Staunton, findest du einen. + +[FM1_Q1] +~w~Na, kleine Erfrischung gefällig? Ein bisschen SPANK? + +[FM1_R] +~w~Hi, Chico. Nein, nur das übliche. + +[FM1_T] +~w~Danke, Chico. Bis bald. + +[FM1_W] +~w~Okay, Fiffi, du passt hier auf den Wagen auf, ich geh ein bisschen abtanzen. + +[FM1_X] +~w~Okay, Fiffi, lass uns 'nen Abgang machen. Huuh! + +[FM1_Q] +~w~Hey, Maria! Meine Traumfrau! + +[FM1_S1] +~w~Vielleicht solltest du mal die Party in der Lagerhalle am Ostende von Atlantic Quays auschecken. + +[FM1_U] +~w~Gracias. Und viel Spaß. Ist gutes Zeug. + +[FM1_V] +~w~Na los, Fiffi, sehen wir uns mal die Party an! + +[FM1_SS] +~r~SCANNER: ~g~4-5 an alle Einheiten: Assistieren Sie bei der Rauschgift-Razzia in Atlantic Quays... + +[LOVE6_B] +Auch wenn kaum einer weiß, wie viel sie wirklich wert ist. + +[TM3_A1] +~r~Joey ist hinüber! + +[TM3_A2] +~r~Joey und Luigi sind hinüber! + +[TM3_A3] +~r~Joey, Luigi und Toni sind hinüber! + +[FM4_2] +Hör zu, Salvatore denkt, ich betrüge ihn mit dir, + +[FM4_3] +da hat er dem Kartell deinen Kopf angeboten, um einen Deal mit denen zu machen. + +[FM4_4] +Das kann ich nicht zulassen. Das ist alles meine Schuld. + +[FM4_4B] +Ich hab ihm erzählt, dass wir uns gut verstehen. + +[FM4_5] +Frag mich nicht wieso. Ich weiß es nicht. + +[FM4_6] +Auf Mafia-Gebiet bist du Freiwild, und ich muss auch abhauen. + +[FM4_6B] +Ich hab genug von all der Gewalt, all dem Blut. + +[FM4_7] +Das ist eine Freundin von mir, eine alte Freundin. Sie heißt Asuka. Ihr können wir trauen. + +[FM4_8] +Kommt. Genug geredet. + +[FM4_9] +Wir verschwinden lieber, bevor uns noch mehr hysterische Italiener unbedingt wiedersehen möchten. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCER + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTION + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DESIGN + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +WRITTEN BY + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +CHARACTERS + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMATION & DIRECTION + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +AUTO DESIGN + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODERS + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +SCORE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +SOUND DESIGN & MASTERING + +[CRED042] +ALLAN WALKER + +[CRED043] +AUDIO PROGRAMMING + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD TESTERS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTERS + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +TECHNICAL PRODUCER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VP OF MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +TECHNICAL COORDINATOR + +[CRED088] +BRANDON ROSE + +[CRED089] +QA MANAGER + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +LIBERTY TREE EDITORIAL + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT-SCENES + +[CRED108] +SCRIPT BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER + +[CRED110] +AUDIO PRODUCED BY RENAUD SEBBANE + +[CRED111] +CAST + +[CRED112] +FRANK VINCENT AS SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO AS LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN AS TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT AS JOEY LEONE + +[CRED116] +DEBBI MAZAR AS MARIA + +[CRED117] +KYLE MACLACHLAN AS DONALD LOVE + +[CRED118] +ROBERT LOGGIA AS RAY MACHOWSKI + +[CRED119] +GURU AS 8-BALL + +[CRED120] +SONDRA JAMES AS MOMMA + +[CRED121] +LIANA PAI AS ASUKA + +[CRED122] +LES MAU AS KENJI + +[CRED123] +CYNTHIA FARRELL AS CATALINA + +[CRED124] +AL ESPINOSA AS MIGUEL + +[CRED125] +CHRIS PHILLIPS AS EL BURRO + +[CRED126] +HUNTER PLATIN AS CHICO + +[CRED127] +WALTER MUDU AS D-ICE + +[CRED128] +CURTIS MCCLARIN AS CURTLY + +[CRED129] +BILL FIORE AS DARKEL + +[CRED130] +CHRIS PHILLIPS AS MARTY CHONKS + +[CRED131] +HUNTER PLATIN AS CURLY BOB + +[CRED132] +WALTER MUDU AS KING COURTNEY + +[CRED133] +HUNTER PLATIN AS ONE-ARMED PHIL + +[CRED134] +KIM GURNEY AS MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRECTED BY + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUCED BY + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +RECORDED AT MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORS + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRED142] +WRITTEN BY DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRECTED BY CRAIG CONNER, DAN HOUSER AND LAZLOW + +[CRED144] +PRODUCED BY RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +RADIO STATIONS AND MUSIC + +[CRED218] +PRODUCERS FOR ROCKSTAR UK + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDITED BY + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING WRITTEN BY + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CINCAM] +Cinematic-Kamera + +[KM1_13] +Fahr das Fahrzeug in die Garage! + +[KM3_14] +~r~Du bist gesehen worden. Der Deal fällt flach! + +[EBAL_H] +Warte hier. Ich gehe rein und rede mit Luigi. + +[EBAL_M] +Und merk dir: Finger weg von meinen Girls. + +[LM2_F] +Dann schnapp dir seine Karre und spritze sie um. + +[LM2_D] +Hier. Hier, nimm. + +[LM1_9] +Hi. Ich bin Misty. + +[LM4_A] +So ein Mistkerl von den Diablos hat in meinem Gebiet Mädchen laufen. + +[FM2_B] +Wir haben einen Verräter unter uns! + +[FM2_C] +Er verdient kein Geld mit Mädchen oder Dealen, also wird er Informationen verkaufen. + +[FM3_CC] +~w~Komm wieder, wenn du die Kohle hast, Bruder. + +[FEDS_AM] +<>-MENÜ WECHSELN + +[LOVE5_5] +~r~Du hast es nicht geschafft, den Truck zu beschützen! + +[RM6_6] +~r~Ray ist tot! + +[RM6_7] +~r~Ray hat seinen Flug verpasst. + +[RM6_8] +~g~Du hast Ray zurückgelassen. Kehr um und hol ihn. + +[FM1_10] +~g~Du hast Maria zurückgelassen. Kehr um und hol sie. + +[LOVE4_9] +~r~Das Flugzeug ist zerstört worden! + +[LOV4_10] +~r~Die einzige Spur auf den Verbleib des Päckchens ist vernichtet worden. + +[KM2_D] +Unnötig zu sagen, dass wir ihm die Autos schenken müssen, um meine Schuld bei ihm zu begleichen. + +[KM4_B] +Für Läden, die das Glück haben, unter unserem Schutz zu stehen, ist heute Zahltag. + +[KM2_E] +Du musst die Autos auf der Liste besorgen und zu einer Garage hinter dem Parkplatz in Newport bringen. + +[FM3_8I] +~w~Such dir eine günstige Position. Ich geh rein, wenn du den ersten Schuss abfeuerst. + +[LOVE1_B] +Ich weiß, dass einer wie du sehr loyal sein kann, wenn das Geld stimmt. + +[LOVE1_H] +Aber je mehr Leute, desto größer die Gier. + +[LOVE1_C] +Ein werter Geschäftsfreund, ein alter Asiate, + +[LOVE1_I] +wird von irgendwelchen Südamerikanern in Aspatria als Geisel festgehalten. + +[MEA4_D] +Ich habe ein Treffen mit ihm vereinbart... + +[MEA4_B4] +Marty schickt dich also? Dem Penner werde ich zeigen, was es heißt, mit mir Geschäfte zu machen. + +[MEA4_B5] +Carl, hi. Ich, äh, ich brauche noch ein bisschen Zeit, um dein Geld aufzutreiben. + +[MEA1_B4] +Mr Chonks schickt dich also. Wollen wir dem Burschen doch mal einen Besuch abstatten. + +[HM5_6] +Dann wollen wir doch mal ein paar Leute aufmischen... + +[LOVE1_5] +~g~Häng hier nicht rum, besorg dir ein Auto der Kolumbianer und rette Loves Geschäftsfreund. + +[AS1_D] +~w~Spiel den Köder und locke die Killerkommandos nach Pine Creek, + +[AS1_E] +~w~wo meine Leute sie erwarten werden. + +[AS2_C] +~w~Das Kartell betreibt eine Scheinfirma zur Tarnung - das Kappa Coffee House. + +[AS2_E] +~w~Wir haben keine Wahl. Wir müssen diese Drogenbuden zerstören. + +[AS2_F] +~w~Zerleg diese Dinger!! + +[AS2_A1] +~w~Miguel ist ein echter Latin Lover. Der hat ein Stehvermögen! + +[AS2_A2] +~w~Ich bin völlig erschöpft. + +[SIREN_3] +Um die Sirenen dieses Fahrzeugs einzuschalten, drücke die ~h~~k~~VEHICLE_HORN~-Taste~w~. + +[SIREN_4] +Um die Sirenen dieses Fahrzeugs einzuschalten, drücke die ~h~~k~~VEHICLE_HORN~-Taste~w~. + +[AS3_C] +~w~Iiiiiiiiiiih! Was ist denn das für ein gelbes Glibberzeug? + +[AS3_C1] +~w~Oh, hi, Baby. + +[AS3_F] +~w~Das Mädchen ist ein Naturtalent. + +[AS3_F1] +~w~Sie hat unserem Gast eine kleine Info entlockt. + +[AS3_G] +~w~In 2 Stunden landet ein Flugzeug auf dem Francis Int. Airport. + +[AS3_G1] +~w~Es ist voll mit Catalinas Giftzeug. + +[AS3_H] +~w~Du entgehst den Security Checks am Flughafen, wenn du dir ein Boot besorgst, zu den Leuchtbojen rausfährst + +[AS3_H1] +und die Maschine im Anflug abschießt. + +[AS3_I] +~w~Krame die Ladung aus den Trümmern! + +[AS3_J] +~w~Und sei vorsichtig, okay, Baby? + +[AS3_K] +~w~Versuch's mal mit Chili-Öl... + +[RM2_F1] +Die Kolumbianer müssen jeden Moment hier sein! + +[RM2_K] +Verdammt, da sind sie!! LOS, LADEN!! + +[LOVE2_7] +~g~Jetzt lass den Wagen stehen! + +[LOVE2_8] +~g~Jetzt verschwinde aus Newport! + +[AM1_F] +Salvatore Leone wird Luigis Club in zirka drei Stunden verlassen (~1~:~1~). + +[LOVE5_C] +Folge ihm und sorg dafür, dass er und mein Päckchen wohlbehalten in Pike Creek ankommen. + +[FESZ_SR] +Speicherung fehlgeschlagen! Bitte Memory Card (PS2) in MEMORY CARD-Steckplatz 1 überprüfen und noch einmal versuchen. + +[FESZ_FO] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 formatieren? + +[FELZ_FO] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 ist nicht formatiert. + +[FES_NOC] +Keine Memory Card (PS2) in MEMORY CARD-Steckplatz 1. + +[FES_LOE] +Laden fehlgeschlagen! Bitte Memory Card (PS2) in MEMORY CARD-Steckplatz 1 überprüfen und noch einmal versuchen. + +[FES_DEE] +Löschen fehlgeschlagen! Bitte Memory Card (PS2) in MEMORY CARD-Steckplatz 1 überprüfen und noch einmal versuchen. + +[FORSUC] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 wurde formatiert. + +[ERFOUN] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 konnte nicht formatiert werden. + +[ERMCNP] +Keine Memory Card (PS2) in MEMORY CARD-Steckplatz 1. + +[SVMEM1] +Speichern auf Memory Card (PS2) in MEMORY CARD-Steckplatz 1. + +[FORSLO] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 formatieren. + +[SLONFM] +Fehler beim Formatieren der Memory Card (PS2) in MEMORY CARD-Steckplatz 1. + +[SLONDR] +Nicht genug Speicherplatz. Bitte eine Memory Card (PS2) mit mindestens 500KB freiem Speicherplatz in MEMORY CARD-Steckplatz 1 einstecken. + +[SLNSP] +Nicht genug Speicherplatz. Bitte eine Memory Card (PS2) mit mindestens 200KB freiem Speicherplatz in MEMORY CARD-Steckplatz 1 einstecken. + +[FEFD_WR] +Formatiere Memory Card (PS2) in MEMORY CARD-Steckplatz 1. Bitte die Memory Card (PS2) nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[FES_ISF] +NICHT VORHANDEN + +[FES_SAG] +VORHANDEN + +[SLONNO] +Keine Memory Card (PS2) in MEMORY CARD-Steckplatz 1. + +[SLONNF] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 ist nicht formatiert. + +[FESZ_FM] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 ist nicht formatiert. Soll die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 formatiert werden? + +[FESZ_FF] +Formatierung fehlgeschlagen. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 überprüfen und noch einmal versuchen. + +[MCDNSP] +Nicht genug Platz auf Memory Card (PS2) in MEMORY CARD-Steckplatz 1. Es werden mind. 500KB Speicherplatz benötigt, um Spieldaten zu speichern. Trotzdem starten? (JA oder NEIN) + +[MCGNSP] +Nicht genug Platz auf Memory Card (PS2) in MEMORY CARD-Steckplatz 1. Es werden mind. 200KB Speicherplatz benötigt, um Spieldaten zu speichern. Trotzdem starten? (JA oder NEIN) + +[FESZ_WR] +Daten werden gespeichert. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[FESZ_OW] +Daten werden überschrieben. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[FELD_WR] +Daten werden geladen. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[FEDL_WR] +Daten werden gelöscht. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[LM2_C] +Luigi sagt, das soll ich dir geben... + +[LM3_G] +Joey ist einer, den man nicht warten lässt. Das ist deine Chance. + +[LM5_E] +Bring so viele wie möglich hin, bevor die Cops ihr ganzes Geld versaufen. + +[JM5_C] +Vor dem Cafe in der Nähe vom Callahan Point steht ein Auto mit einem Toten drin. + +[RM2_B] +Wir waren zusammen in Nicaragua, als dieses Land noch wusste, was es tut. + +[RM2_C] +Irgendwelche Dreckskerle vom Kartell haben ihn gestern verprügelt und kommen heute wieder, um ihm seine Ware abzunehmen. + +[RM2_D1] +Ich würde es selbst machen, aber meine Bronchien melden sich wieder. Tja, äh, viel Glück. + +[CATINF1] +~g~Schnapp dir Catalina! + +[CATINF2] +~g~Um Catalina zu finden, folge dem Hubschrauber. + +[BOATIN1] +Spring auf ein Boot und drücke die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um hinein zu gelangen. + +[BOATIN2] +Wenn du in der Nähe eines Bootes bist, kannst du die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste ~w~benutzen, um an Bord zu gelangen. + +[BOATIN3] +Spring auf ein Boot und drücke die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um hinein zu gelangen. + +[BOATIN4] +Wenn du in der Nähe eines Bootes bist, kannst du die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste ~w~benutzen, um an Bord zu gelangen. + +[JM6] +'DIE FLUCHT' + +[FM1] +'AUF PISTE MIT MARIA' + +[JM1] +'MIKE 'LIPS' LETZTE LASAGNE' + +[FM21] +'DER BOMBENANSCHLAG, TEIL 1' + +[FM3] +'DER BOMBENANSCHLAG, TEIL 2' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'UNTER ÜBERWACHUNG' + +[KM2] +'GTA' + +[AS3] +'DER ABFANGJÄGER' + +[RM2] +'DAS WAFFENARSENAL' + +[LOVE6] +'LOCKVOGEL' + +[LOVE1] +'DIE BEFREIUNGSAKTION' + +[RC1] +'IM DSCHUNGEL DER DIABLOS' + +[RC2] +'DAS MAFIA-MASSAKER' + +[RC3] +'DER CASINO-COUP' + +[RC4] +'ROCK'N ROLL MIT RUMPO' + +[RM2_E1] +Unglaublich, dass die feigen Säcke mich wieder ohne ausreichenden Schutz ins Gefecht schicken. + +[GREN_1] +Je länger du die ~h~~k~~PED_FIREWEAPON~-Taste~w~ gedrückt hältst, desto weiter kannst du die Granate werfen. + +[GREN_2] +Je länger du die ~h~~k~~PED_FIREWEAPON~-Taste~w~ gedrückt hältst, desto weiter kannst du die Granate werfen. + +[GREN_3] +Je länger du die ~h~~k~~PED_FIREWEAPON~-Taste~w~ gedrückt hältst, desto weiter kannst du die Granate werfen. + +[LOVE4_G] +Die Maschine mit meiner Ware steht im Zollhangar. + +[KABOOM] +KAWUMM! + +[SPLAT] +WUSCH! + +[PANCAK] +PLATT! + +[SOAKED] +AARGH! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Chatterbox FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +Wenn der U-Bahnhof öffnet, kannst du die U-Bahn nach Staunton Island nehmen. + +[TUBE2] +Wenn der U-Bahnhof in Shoreside Vale aufmacht, dann kannst du das Shoreside Terminal in Richtung Francis Int. Airport verlassen. + +[TUBE_2] +Um in eine U-Bahn einzusteigen, ~h~'Einsteigen'-Taste~w~ drücken. + +[LEGAL] +~g~Schalte die kriminelle Bedrohung aus! + +[GA_2] +Neuer Motor und neue Lackierung. Die Cops werden dich nicht identifizieren! + +[LM1_8A] +Warum nicht ein Taxi 'ausleihen', um dir was dazu zu verdienen...? + +[TAXIH1] +Halte neben einem gehighlighteten Fußgänger, um ihn einsteigen zu lassen, dann bringe ihn rechtzeitig an sein Fahrtziel. + +[LM5_7] +~g~Wenn weniger als vier Girls bei dem ~p~Polizeiball~g~ auftauchen, wird Luigi sauer! + +[KM2_3] +~g~Die ~r~Autos~g~ müssen in 1A-Zustand sein, damit die ~p~Garage~g~ sie annimmt. + +[KM5_2] +~g~Ein Yardie ist weg. + +[BETRA_A] +Sorry, Baby. + +[BETRA_B] +Ich bin ein anspruchsvolles Mädchen und du... + +[BETRA_C] +... du bist ein kleiner Fisch. + +[JAILB_C] +Noch gibt es keine näheren Informationen zu den Häftlingen, die mit dem Konvoi transportiert wurden, + +[JAILB_E] +Der Konvoi war am frühen Morgen vom Polizei-Hauptquartier aus + +[JAILB_F] +zu einem Gefangenentransport mit Ziel Haftanstalt Liberty aufgebrochen. + +[JAILB_G] +Der Anschlag erfolgte an der Callahan-Brücke. + +[JAILB_P] +ist Portland durch diese Katastrophe vom Rest der Stadt abgeschnitten. + +[JAILB_Q] +Los jetzt! + +[JAILB_R] +Vollidiot! + +[JAILB_S] +Kein Problem, dich alle zu machen. + +[JAILB_T] +Das wird dir noch leid tun. + +[JAILB_U] +Okay, okay. Hau ab. + +[HELP15] +Wenn zu Fuß unterwegs, drücke die ~h~~k~~PED_LOOKBEHIND~-Taste~w~, um ~h~nach hinten zu schauen~w~. + +[FEC_LB3] +Nach hinten schauen + +[FEC_R3] +(R3-Taste) + +[FES_AFO] +Diese Memory Card (PS2) ist bereits formatiert. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- AUSWAHL ÄNDERN + +[FEDSAS4] +;=<> - AUSWAHL ÄNDERN + +[SPRAY_4] { re3 change } +~h~~k~~VEHICLE_FIREWEAPON~-Taste ~w~benutzen, um die Wasserkanone abzufeuern. + +[SPRAY_1] { re3 change } +~h~~k~~VEHICLE_FIREWEAPON~-Taste ~w~benutzen, um die Wasserkanone abzufeuern. + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[AM1_10] +~g~Salvatore Leone wird Luigis Club um zirka 0~1~:~1~ verlassen. + +[JAILB_V] +Liberty City ist heute vom Schrecken gezeichnet. + +[JAILB_A] +Polizei und Noteinsatzkräfte arbeiten unter Hochdruck, nachdem heute morgen + +[JAILB_B] +ein verheerender Anschlag auf einen Polizeikonvoi verübt wurde. + +[JAILB_W] +Nach stundenlangen Ermittlungen wurde klar, dass der Anschlag das Werk von Profis war. + +[JAILB_K] +Die Identifizierung der noch vermissten Kriminellen wurde darüber hinaus + +[JAILB_L] +durch eine Hacker-Attacke auf den Zentral-Computer der Polizei erschwert. + +[JAILB_M] +stellte Bürgermeister O'Donovan klar, dass die Polizei + +[JAILB_N] +* + +[JAILB_O] +Da die Fertigstellung des Porter Tunnels sich verzögert, + +[JAILB_X] +In einer ersten Stellungnahme + +[FEDS_SE] +/-Taste - AUSWAHL + +[FEDS_SB] +/-Taste - AUSWAHL "-Taste - ZURÜCK + +[TM4_A] +~w~Ach, du bist es. Toni ist nicht da. + +[TM4_A2] +~w~Aber er hat wieder ein Liebesbriefchen für dich hinterlassen. + +[DIAB2_A] +Ich habe mein Entertainment-Business mit nichts als dem üppigen Inhalt meiner Lederhose gestartet. + +[LM5_9] +GIRLS: + +[PERPIC] +Versteckte Päckchen gefunden + +[CO_ONE] +Verstecktes Päckchen ~1~ von ~1~ + +[LOVE3_3] +~g~Das Flugzeug hat ~1~ von 6 Päckchen abgeworfen. + +[FARE11] +~g~Fahrziel: ~w~'Baustelle' ~g~in Fort Staunton. + +[GA_21] +In dieser Garage bringst du keine Autos mehr unter. + +[CHEAT1] +Cheat aktiviert + +[CHEAT2] +Waffen-Cheat + +[CHEAT3] +Health-Cheat + +[CHEAT4] +Panzerungs-Cheat + +[CHEAT5] +Fahndungslevel-Cheat + +[CHEAT6] +Geld-Cheat + +[CHEAT7] +Wetter-Cheat + +[AS1_H] +~r~Es ist dir nicht gelungen, das Killerkommando in die Yakuza-Falle zu locken! + +[FEDS_BA] +"-Taste - ZURÜCK + +[RAMP_A] +ALLE AMOKLÄUFE BEENDET! + +[USJ_ALL] +ALLE MONSTER-STUNTS ABSOLVIERT! + +[FARE23] +~g~Fahrtziel: ~w~'Import-Export Garage' ~g~im Cochrane Dam Distrikt. + +[L_TRN_1] +Mit der U-Bahnlinie L kannst du in Portland herumfahren. Drücke die~h~ ~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in eine U-Bahn ~h~ein- oder auszusteigen~w~. + +[L_TRN_2] +Mit der U-Bahnlinie L kannst du in Portland herumfahren. Drücke die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in eine U-Bahn ~h~ein- oder auszusteigen~w~. + +[S_TRN_1] +Mit der U-Bahn kannst du in Liberty herumfahren. Drücke die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in eine U-Bahn ~h~ein- oder auszusteigen~w~. + +[S_TRN_2] +Mit der U-Bahn kannst du in Liberty herumfahren. Drücke die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in eine U-Bahn ~h~ein- oder auszusteigen~w~. + +[AS1_C] +~w~Sie hat drei Killerkommandos in Liberty verteilt, die dich zur Strecke bringen sollen. + +[AS1_G] +~r~Alle Yakuza sind erledigt! + +[JAN] +Jan + +[FEB] +Feb + +[MAR] +Mär + +[APR] +Apr + +[MAY] +Mai + +[JUN] +Jun + +[JUL] +Jul + +[AUG] +Aug + +[SEP] +Sept + +[OCT] +Okt + +[NOV] +Nov + +[DEC] +Dez + +[DEFDT] +--:---:---- --:--:-- + +[BUGGY] +VERBLEIBENDE BUGGIES: + +[BONUS] +~g~BONUS $~1~ + +[HORN1] +Drück die ~h~L3-Taste~w~, um zu ~h~hupen. + +[HORN2] +Drück die ~h~L1-Taste~w~, um zu ~h~hupen. + +[HORN3] +Drück die ~h~R1-Taste~w~, um zu ~h~hupen. + +[LM3_1A] +Drück die ~h~~k~~VEHICLE_HORN~-Taste~w~, um zu ~h~hupen. So weiß Misty, dass du da bist. + +[LM3_1B] +Drück die~h~ ~k~~VEHICLE_HORN~-Taste~w~, um zu ~h~hupen. So weiß Misty, dass du da bist. + +[LM3_1C] +Drück die~h~ ~k~~VEHICLE_HORN~-Taste~w~, um zu ~h~hupen. So weiß Misty, dass du da bist. + +[RADIO_A] +Drück die ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~-Taste~w~, um die verschiedenen ~h~Radiosender zu hören. + +[RADIO_B] +Drück die ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~-taste~w~, um die verschiedenen ~h~Radiosender zu hören. + +[RADIO_C] +Drück die ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~-Taste~w~, um die verschiedenen ~h~Radiosender zu hören. + +[RADIO_D] +Drück die ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~-Taste~w~, um die verschiedenen ~h~Radiosender zu hören. + +[FEC_EXV] +In Fahrzeug ein-\aussteigen + +[TAXI_M] +'TAXI DRIVER' + +[COP_M] +'BÜRGERWEHR' + +[FIRE_M] +'FEUERWEHR' + +[AMBUL_M] +'KRANKENWAGEN' + +[HJ_IS] +IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_PIS] +SUPER IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_DIS] +DOPPELTER IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_PDIS] +SUPER-DOPPEL-IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_TIS] +DREIFACHER IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_PTIS] +SUPER-DREIFACH-IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_QIS] +VIERFACHER IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_PQIS] +SUPER-VIERFACH-IRRSINNS-STUNT-BONUS: $~1~ + +[AM1_K] +Salvatore Leone wird Luigis Club in zirka drei Stunden verlassen. (0~1~:~1~) + +[IMPEXPP] +Import-Export Garage, Portland Harbor. Wir haben Bestellungen für verschiedene Fahrzeuge. Näheres steht auf unserem Schwarzen Brett. + +[VANHSTP] +Willst du noch mehr Securicars aufgebrochen haben? Bring sie zu unserer Garage in Portland Harbor. + +[EMVHPUP] +Zahlen Bestpreise für Neu- und Gebrauchtwagen. Bring sie zum Kran im Nordosten von Portland Harbor. + +[STANDS] +ZERSTÖRTE ESPRESSOSTÄNDE: + +[STASH] +~g~Deponiere das SPANK auf der ~p~Baustelle! + +[MCSTNS] +Keine Memory Card (PS2) in MEMORY CARD-Steckplatz 1. Trotzdem starten? (JA oder NEIN) + +[LOVE3_5] +~g~Das Flugzeug ist jetzt in Reichweite. + +[LOVE3_6] +~r~Die Polizei war vor dir bei den Päckchen! + +[SIREN_1] +Um die Sirenen dieses Fahrzeugs einzuschalten, drücke die ~h~~k~~VEHICLE_HORN~-Taste~w~. + +[SIREN_2] +Um die Sirenen dieses Fahrzeugs einzuschalten, drücke die ~h~~k~~VEHICLE_HORN~-Taste~w~. + +[FM3_8C] +~w~Ich brauch $100 000 für Auslagen. + +[MCLOAD] +Daten werden geladen. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[FES_GME] +Fehler beim Lesen der Memory Card (PS2) in MEMORY CARD-Steckplatz 1! Bitte überprüfen und noch einmal versuchen. + +[FESZ_QF] +Soll die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 wirklich formatiert werden? + +[FESZ_LS] +Ladevorgang abgeschlossen. + +[RM3_5] +~g~Du hast ~1~ von 6 Päckchen mit Beweisfotos. + +[LOVE3_2] +~g~Du hast alle Päckchen. Bring sie zu Donald Love. + +[LOVE4_4] +~g~Bring das Päckchen zu Donald Love! + +[FEB_SAV] +Laden + +[FEP_SAV] +SPIEL LADEN + +[AS2_12A] +~g~Wenn du den ersten Espressostand zerstört hast, hast du 8 Minuten Zeit, bis das Kartell seine Dealer warnt! + +[AS3_1A] +~g~Jetzt fahr zu der ~b~Markierungsboje! + +[NOCONT] +Bitte stecken Sie einen Analog Controller (DUALSHOCK#) oder einen Analog Controller (DUALSHOCK#2) in Controller-Anschluss 1, um fortzufahren. + +[BET_JB] +VON CATALINA, SEINER GELIEBTEN, IM STICH GELASSEN, FÜR SCHULDIG BEFUNDEN UND VERURTEILT, BEFINDET ER SICH AUF DEM WEG INS GEFÄNGNIS VON LIBERTY CITY. DOCH IN SEINEM KOPF HÄMMERT EIN GEDANKE... MIT EUCH BIN ICH NOCH NICHT FERTIG! + +[END_A] +Die Bewohner von Cedar Grove erholen sich langsam + +[END_B] +von dem Schock der schrecklichen Ereignisse, + +[END_C] +die sich gestern hier abgespielt haben. + +[END_D] +Clive Denver, ein Augenzeuge, beschrieb der Polizei + +[END_E] +den Täter, den er zusammen mit einer dunkelhaarigen Frau flüchten sah. + +[END_F] +Hör mal, wir werden sehr viel Spaß miteinander haben. Weißt du, + +[END_G] +ich liebe dich nämlich. Wirklich, du bist so groß und stark, + +[END_H] +und genau so einen Mann brauche ich. + +[END_I] +Jedenfalls - was wollte ich gerade sagen? + +[END_J] +Weiß nicht mehr. Aber du verstehst doch, was ich meine, oder? + +[END_K] +Der Donner von Explosionen erschütterte umliegende Häuser. Menschen rannten in Deckung. + +[END_L] +Mehrere Anwohner wurden verletzt, als es in dem Chaos zu einem Schusswechsel + +[END_M] +zwischen Bodeneinheiten und einem Helikopter kam, der über dem Damm kreiste. + +[END_N] +Ja, von hier in den Gärten konnten wir alles genau beobachten. + +[END_O] +Wie sie den Helikopter dann abgeschossen haben, + +[END_P] +das war ein ziemliches Feuerwerk. + +[END_Q] +Die Zahl der Toten ist inzwischen auf über 20 angestiegen + +[END_R] +und die Polizei findet immer noch weitere Leichen. + +[END_S] +Die Gerüchte, dass es sich bei den Opfern um Angehörige des kolumbianischen Kartells + +[END_T] +handelt, wurden von offizieller Seite bisher nicht dementiert. + +[END_U] +Und es gibt nach wie vor keine Anhaltspunkte für das Motiv der Tat. + +[END_V] +Ich hab mir 'nen Fingernagel abgebrochen und meine Frisur ist hin! + +[END_W] +Fünfzig Dollar im Eimer! + +[PAPER1] +GANGSTER VON KOMPLIZIN, DIE ER LIEBT, VERRATEN. GERICHT BEFINDET RÄUBER EINSTIMMIG FÜR SCHULDIG. + +[PAPER2] +ZEHN JAHRE AUS LIEBE! + +[JAILB_D] +und es hat sich bisher auch keine kriminelle Vereinigung zu dem Attentat bekannt. + +[JAILB_H] +Die meisten Zeugen kamen ums Leben und die Brücke wurde schwer beschädigt. + +[JAILB_I] +Man geht davon aus, dass auch einige der Häftlinge bei der sich ereignenden Explosion umkamen. + +[JAILB_J] +* + +[FEB_CPC] +Konfiguration d. Steuerung + +[FEC_PED] +Steuerung zu Fuß + +[FEC_VEH] +Steuerung in Fahrzeug + +[FEC_FPR] +Steuerung für First-Person + +[FEC_CMM] +Allgemeine Steuerung + +[FEC_PWL] +Nach links + +[FEC_PWR] +Nach rechts + +[FEC_PWF] +Vorwärts gehen + +[FEC_PWT] +Auf Kamera zugehen + +[FEC_PLB] +Nach hinten schauen + +[FEC_PFR] +Waffe abfeuern + +[FEC_CLE] +Eine Waffe nach links + +[FEC_CRI] +Eine Waffe nach rechts + +[FEC_LKT] +Ziel fixieren + +[FEC_PJP] +Fußgänger springen + +[FEC_PSP] +Fußgänger sprinten + +[FEC_PSH] +Fußgänger schießen + +[FEC_TLF] +Ein Ziel nach links + +[FEC_TRG] +Ein Ziel nach rechts + +[FEC_CCM] +Kamera hinter Spieler zentrieren + +[FEC_SZI] +Mit Präzisionsgewehr heranzoomen + +[FEC_SZO] +Mit Präzisionsgewehr herauszoomen + +[FEC_LKL] +First-Person nach links schauen + +[FEC_LRT] +First-Person nach rechts schauen + +[FEC_LUP] +First-Person nach oben schauen + +[FEC_LDN] +First-Person nach unten schauen + +[FEC_LBH] +Aus Fahrzeug nach hinten schauen + +[FEC_LLF] +Aus Fahrzeug nach links schauen + +[FEC_LRG] +Aus Fahrzeug nach rechts schauen + +[FEC_HRN] +Hupe + +[FEC_HBR] +Handbremse + +[FEC_ACL] +Gas geben + +[FEC_BRK] +Bremsen + +[FEC_TSM] +Spezialmissionen An/Aus + +[FEC_CRD] +Radiosender wechseln + +[FEC_ENT] +In Fahrzeug Ein-/Aussteigen + +[FEC_WPN] +Waffe abfeuern + +[FEC_PAS] +Pause + +[FEC_FPO] +First Person Weapons Toggle. + +[FEC_SMS] +Mauszeiger An/Aus + +[FEC_CMS] +Blickwinkel wechseln. + +[FEC_TSS] +Screen Shot + +[FEN_NET] +Netzwerk + +[FEN_CON] +Verbindung + +[FEN_GAM] +Spiel suchen + +[FEN_TYP] +Spiel-Typ + +[FEN_TY0] +Deathmatch + +[FEN_TY1] +Deathmatch Stealth + +[FEN_TY2] +Team Deathmatch + +[FEN_TY3] +Team Deathmatch Stealth + +[FEN_TY4] +Stash the Cash + +[FEN_TY5] +Capture the Flag + +[FEN_TY6] +Rat Race + +[FEN_TY7] +Domination + +[FEN_NAM] +Name: + +[FEN_GNA] +Spiel-Name: + +[FEM_MAP] +Karte auswählen + +[FEN_PLS] +Spieler-Einstellungen + +[FEN_PLC] +Spieler-Farbe + +[FEM_MA0] +Liberty City + +[FEM_MA1] +Rotlichtbezirk + +[FEM_MA2] +Chinatown + +[FEM_MA3] +Der Tower + +[FEM_MA4] +Die Kanalisation + +[FEM_MA5] +Industriepark + +[FEM_MA6] +Docks + +[FEM_MA7] +Staunton + +[FEC_DBG] +Debug-Menü + +[FEC_TGD] +Mit Pad zwischen Spiel- u. Debug-Modus wechseln + +[FEC_TDO] +Debug-Kamera Aus + +[FEC_IVH] +Maus horizontal invertieren + +[FEC_MSL] +MAUSTASTE L + +[FEC_MSM] +MAUSTASTE M + +[FEC_MSR] +MAUSTASTE R + +[FEC_QUE] +??? + +[FEC_TWO] +Nur zwei Tastaturtasten erlaubt + +[FEC_OMS] +Nur eine Maustaste erlaubt + +[FEC_OJS] +Nur ein Joystick-Button pro Aktion erlaubt + +[FEC_PTL] +"Ziel fixieren" u. "Waffenauswahl links" gleichzeitig drücken. + +[FEC_PTR] +"Ziel fixieren" u. "Waffenauswahl rechts" gleichzeitig drücken. + +[FEC_LBC] +"Nach links schauen" u. "Nach rechts schauen" gleichzeitig drücken. + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +Pause in Multiplayer nicht möglich. Zum Beenden zwei Mal drücken! + +[FEM_SL1] +Speicherplatz 1 ist frei + +[FEM_SL2] +Speicherplatz 2 ist frei + +[FEM_SL3] +Speicherplatz 3 ist frei + +[FEM_SL4] +Speicherplatz 4 ist frei + +[FEM_SL5] +Speicherplatz 5 ist frei + +[FEM_SL6] +Speicherplatz 6 ist frei + +[FEM_SL7] +Speicherplatz 7 ist frei + +[FEM_SL8] +Speicherplatz 8 ist frei + +[FEM_MM] +HAUPTMENÜ + +[FEM_SNG] +Neues Spiel starten + +[FEM_QTW] +Beenden + +[FEQ_SRE] +Wirklich beenden? Alle Daten seit dem letzten Speichern werden verlorengehen. Weiter? + +[FEQ_SRW] +Spiel wirklich beenden? + +[FEG_SRV] +SERVER + +[FEG_MAP] +KARTE + +[FEG_PLY] +SPIELER + +[FEG_TYP] +TYP + +[FEG_PNG] +PING + +[FET_FG] +SPIEL SUCHEN + +[FET_SP] +SINGLEPLAYER + +[FET_MP] +MULTIPLAYER + +[FET_HG] +SPIEL HOSTEN + +[FET_PS] +SPIELER SETUP + +[FET_CON] +VERBINDUNG + +[FET_AUD] +AUDIO-SETUP + +[FET_DIS] +ANZEIGEN-SETUP + +[FET_LAN] +SPRACHEN-SETUP + +[FET_LG] +SPIEL LADEN + +[FET_DG] +SPIEL LÖSCHEN + +[FET_NG] +NEUES SPIEL + +[FET_SG] +SPIEL SPEICHERN + +[FET_MAP] +KARTE AUSWÄHLEN + +[FET_GT] +SPIEL-TYP + +[FET_CTL] +CONTROLLER-SETUP + +[FET_OPT] +OPTIONEN + +[FET_QG] +SPIEL BEENDEN + +[FET_STA] +STATISTIK + +[FET_BRE] +MISSIONSINFOS + +[FEC_WAR] +Achtung! + +[FEC_OKK] +OK + +[FED_CON] +Löschen bestätigen + +[FES_SSC] +Spiel wurde gespeichert. + +[DEL_FNM] +Datei wurde gelöscht. + +[PCLOAD] +Datei wird geladen + +[PCRESRT] +GTA 3 wird neu gestartet + +[FEC_DLF] +Löschen fehlgeschlagen. + +[FEC_SVU] +Speichern fehlgeschlagen. + +[FEC_LUN] +Laden fehlgeschlagen. Datei beschädigt. Bitte löschen. + +[FEN_PLA] +Anzahl der Spieler: + +[FET_NON] +KEINE SPIELE VERFÜGBAR + +[FET_SFG] +SPIELE WERDEN GESUCHT... + +[FET_SRT] +SPIELE WERDEN SORTIERT... + +[FEF_LAN] +LAN + +[FEF_INT] +INTERNET + +[FET_REF] +Aktualisieren + +[FET_FIL] +Filter + +[FET_JG] +Beitreten + +[FEC_NTW] +Talk To Network + +[FEC_ESR] +Esc-Taste nicht zugelassen + +[FEC_GSL] +Show head bob: + +[FIL_FLT] +SPIELE-LISTE FILTERN + +[FET_SAN] +NEUES SPIEL STARTEN + +[FIL_MAP] +Karte: + +[FIL_SRV] +Server: + +[FIL_TYP] +Spiel-Typ + +[FIL_SPC] +Offene Spiele? + +[FIL_PNG] +Ping: + +[FEN_UKH] +Unbekannter Host + +[FEN_UKM] +Map nicht gefunden + +[FEN_UKT] +Spiel-Typ nicht gefunden + +[FEN_NCI] +KEINE INTERNET-VERBINDUNG + +[FET_PAU] +PAUSENMENÜ + +[FET_SGA] +SPIEL STARTEN + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FEC_WHL] +Lenkrad + +[FEC_CNT] +Controller-Typ: + +[FET_APL] +ÜBERNEHMEN + +[FES_CSA] +Wählen Sie eine Skin aus der Liste aus: + +[FES_SKN] +SKIN-NAME + +[FES_DAT] +DATUM + +[FES_NON] +KEINE SKINS VERFÜGBAR + +[FEA_FM9] +SPIELER MP3 + +[FESZ_QZ] +Dieses Spiel wirklich speichern? + +[FES_CGA] +Momentan verfügbare Speicherplätze: + +[FES_SCG] +Laufendes Spiel speichern? + +[FES_LCG] +Spiel laden und weiterspielen? + +[FEC_FIR] +Feuern + +[FEC_NWE] +Nächste Waffe + +[FEC_PWE] +Vorherige Waffe + +[FEC_FOR] +Vorwärts + +[FEC_BAC] +Rückwärts + +[FEC_LEF] +Links + +[FEC_RIG] +Rechts + +[FEC_ZIN] +Heranzoomen + +[FEC_ZOT] +Herauszoomen + +[FEC_EEX] +Ein-/Aussteigen + +[FEC_RAD] +Radio + +[FEC_SUB] +Spezialmission + +[FEC_CMR] +Blickwinkel ändern + +[FEC_JMP] +Springen + +[FEC_SPN] +Sprinten + +[FEC_HND] +Handbremse + +[FEC_TUL] +Geschütz links + +[FEC_TUR] +Geschütz rechts + +[FEC_LOL] +Nach links schauen + +[FEC_LOR] +Nach rechts schauen + +[FEC_NTR] +Nächstes Ziel + +[FEC_PTT] +Vorheriges Ziel + +[FEC_LBA] +Nach hinten schauen + +[FEC_CEN] +Kamera zentrieren + +[FEC_UND] +(NEIN) + +[FET_CFT] +ZU FUSS + +[FET_CCR] +IN FAHRZEUG + +[CVT_MSG] +Texturen werden in optimales Format für Ihre Grafikkarte konvertiert + +[FET_CAC] +AKTION + +[FEC_IBT] +- + +[FEC_SPC] +LEERT. + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +NICHT BEL. + +[FET_CME] +STEUERUNGSART + +[FET_RDK] +STEUERUNG ÄNDERN + +[FET_AMS] +MAUS-EINSTELLG. + +[FET_STI] +STANDARD STEURUNGSKONFIG. + +[FET_CTI] +CLASSIC STEURUNGSKONFIG. + +[FET_MTI] +MAUS STEURUNGSKONFIG. + +[FET_DAM] +DYNAMISCHE AKUSTIK + +[FEC_TFL] +Geschütz Links + +[FEC_TFR] +Geschütz Rechts + +[FEC_TFU] +Geschütz /Dodo aufwärts + +[FEC_TFD] +Geschütz /Dodo abwärts + +[FEC_MWF] +RAD AUFW. + +[FEC_MWB] +RAD ABW. + +[FEC_ORR] +oder + +[FEC_NUS] +NICHT VERWENDET + +[FEC_LUD] +Aufw. Sehen + +[FEC_LDU] +Abw. Sehen + +[FEC_CMP] +COMBO: L+R SEHEN + +[FEC_NTT] +Noch kein Text für diese Taste + +[FEC_FNC] +F~1~ + +[FEC_IRT] +EINFG + +[FEC_DLL] +ENTF + +[FEC_HME] +POS1 + +[FEC_END] +ENDE + +[FEC_PGU] +BILD AUF + +[FEC_PGD] +BILD AB + +[FEC_UPA] +AUF + +[FEC_DWA] +AB + +[FEC_LFA] +LINKS + +[FEC_RFA] +RECHTS + +[FEC_NUM] +NUM + +[FEC_NMN] +NUM~1~ + +[FEC_FWS] +NUM / + +[FEC_PLS] +NUM + + +[FEC_MIN] +NUM - + +[FEC_DOT] +NUM , + +[FEC_NLK] +NUMLOCK + +[FEC_ETR] +ENT + +[FEC_SLK] +ROLLEN + +[FEC_PSB] +UNTBR + +[FEC_BSP] +RÜCKT. + +[FEC_TAB] +TAB + +[FEC_CLK] +CAPSLOCK + +[FEC_RTN] +RET + +[FEC_LSF] +LUMSCHALT + +[FEC_RSF] +RUMSCHALT + +[FEC_LCT] +LSTRG + +[FEC_RCT] +RSTRG + +[FEC_LAL] +LALT + +[FEC_RAL] +RALT + +[FEC_LWD] +LWIN + +[FEC_RWD] +RWIN + +[FEC_WRC] +WINKLICK + +[WIN_TTL] +GTA 3 + +[WIN_95] +GTA 3 läuft nicht unter Windows 95 + +[WIN_DX] +GTA 3 benötigt mind. DirectX Version 8.1 + +[WIN_VDM] +GTA 3 benötigt mind. 12MB freien Grafikspeicher + +[DIAB3_G] +Arriba! + +[FEM_RES] +SPIEL FORTSETZEN + +[FES_SNG] +NEUES SPIEL STARTEN + +[FEM_SP] +SINGLEPLAYER + +[FEM_MP] +MULTIPLAYER + +[FEM_QT] +SPIEL BEENDEN + +[FES_SG] +NEUES SPIEL STARTEN + +[FES_LG] +SPIEL LADEN + +[FEM_HST] +SPIEL HOSTEN + +[FEM_OPT] +OPTIONEN + +[FEM_DBG] +DEBUG + +[FET_PSU] +SPIELER SETUP + +[FET_DEF] +STANDARD WIEDERHERST. + +[FED_BRI] +HELLIGKEIT + +[FED_TRA] +UNSCHÄRFE-FX + +[FEM_LOD] +DISTANZ-DARSTELLG. + +[FEM_VSC] +FRAME SYNC + +[FEM_FRM] +FRAME LIMITER + +[FED_RES] +BILDSCHIRMAUFLSG. + +[FED_WIS] +BREITBILD + +[FEDS_TB] +ZURÜCK + +[FEA_MUS] +MUSIK VOLUME + +[FEA_SFX] +SFX VOLUME + +[FEA_RSS] +RADIOSENDER + +[FEL_ENG] +ENGLISCH + +[FEL_FRE] +FRANZ. + +[FEL_GER] +DEUTSCH + +[FEL_ITA] +ITALIEN. + +[FEL_SPA] +SPANISCH + +[FEA_3DH] +AUDIO HARDWARE + +[FEA_SPK] +BOXEN KONFIGURATION + +[FEA_2SP] +2 BOXEN + +[FEA_4SP] +MEHR ALS 2 BOXEN + +[FEA_EAR] +KOPFHÖRER + +[FEA_NAH] +KEINE AUDIO HARDWARE + +[FET_SNG] +NEUES SPIEL STARTEN + +[FEN_STA] +SPIEL STARTEN + +[GMLOAD] +SPIEL LADEN + +[GMSAVE] +SPIEL SPEICHERN + +[FES_DGA] +SPIEL LÖSCHEN + +[FEM_NON] +KEIN + +[FEC_IVV] +MAUS VERTIKAL INVERTIEREN + +[FEC_MSH] +MAUSEMPFINDLICHKEIT + +[FET_CCN] +STEUERUNG: CLASSIC + +[FET_SCN] +STEUERUNG: STANDARD + +[FES_SET] +SKIN VERWENDEN + +[GHOST] +Ghost + +[WIN_RSZ] +Neue Auflösung konnte nicht aktiviert werden + +[FET_APP] +LMT,RETURN,UM NEUE EINSTLLG. ZU SPEICHERN + +[FET_HRD] +STANDARDEINSTLLG. WIEDERHERGESTELLT + +[FET_MST] +MAUSSTEUERUNG + +[FEC_STR] +NUM STERN + +[FET_MIG] +LINKS,RECHTS,MAUSRAD ZUR EINSTLLG. + +[FET_CIG] +RÜCKT. ZUM LÖSCHEN - LMT,RETURN ZUM ÄNDERN + +[FET_RIG] +NEUE STEUERUNG FÜR DIESE AKTION WÄHLEN ODER ESC FÜR ABBRUCH + +[FET_EIG] +KANN DIESER AKTION KEINE STEUERUNG ZUWEISEN + +[NO_PCCD] +Bitte legen Sie die GTA 3 Disk 2 ein oder drücken Sie ESC zum Abbrechen + +[CVT_ERR] +Kein Platz mehr auf der Festplatte. Bitte schaffen Sie Speicherplatz, bevor Sie fortfahren. ESC zum Abbrechen. + +[FED_SUB] +UNTERTITEL + +[FET_DSN] +Standard-Player Skin.bmp + +[EBAL] +'GIB MIR LIBERTY' + +[LM4] +'PUMP-ACTION-THRILLER' + +[REPLAY] +WIEDERHOLUNG + +[FEC_SFT] +UMSCHALT + +[CRED254] +STUDIO MANAGER + +[CVT_CRT] +Texturen können nicht für Ihre Grafikkarte konvertiert werden. Sie müssen sich als Administrator einloggen, damit dies möglich ist. Verlassen mit ESC. + +[FEM_ON] +AN + +[FEM_OFF] +AUS + +[FEM_YES] +JA + +[FEM_NO] +NEIN + +[FES_WAR] +Speichere Daten, bitte warten... + +[FED_DLW] +Lösche Daten, bitte warten... + +[FED_LDW] +Lade Daten, bitte warten... + +[FEC_SLC] +Slot ist beschädigt + +[FED_LFL] +Spiel konnte nicht geladen werden. Das Spiel wird neu gestartet. + +[FET_RSO] +ORIGINAL-EINSTELLG. WIEDERHERGESTELLT + +[FET_RSC] +HARDWARE NICHT VERFÜGBAR - ORIGINAL-EINSTELLG. WIEDERHERGESTELLT + +[CRED270] +MIKE HONG + +{ re3 updates } +{ new languages } +[FEL_JAP] +JAPANISCH + +[FEL_POL] +POLNISCH + +[FEL_RUS] +RUSSISCH + +{ new display menus } +[FET_GFX] +GRAFIK-SETUP + +[FED_MIP] +MIP MAPPING + +[FED_AAS] +KANTENGLÄTTUNG + +[FED_FIL] +TEXTURFILTER + +[FED_BIL] +BILINEAR + +[FED_TRL] +TRILINEAR + +[FED_WND] +FENSTERMODUS + +[FED_FLS] +VOLLBILD + +[FEM_CSB] +CUTSCENE BALKEN + +[FEM_SCF] +BILDSCHIRMFORMAT + +[FEM_ISL] +KARTENSPEICHERNUTZUNG + +[FEM_LOW] +NIEDRIG + +[FEM_MED] +MITTEL + +[FEM_HIG] +HOCH + +[FEM_2PR] +PS2 ALPHA TEST + +[FEC_FRC] +FREIE KAMERA + +{ Linux joy detection } +[FEC_JOD] +JOYSTICK ERKENNEN + +[FEC_JPR] +Drücke eine beliebige Taste auf dem Joystick der für das Spiel verwendet werden soll, und er wird ausgewählt. + +[FEC_JDE] +Joystick erkannt + +{ mission restart } +[FET_RMS] +MISSION WIEDERHOLEN + +[FESZ_RM] +WIEDERHOLEN? + +{ more graphics } +[FED_VPL] +FAHRZEUG-PIPELINE + +[FED_PRM] +CHARAKTER KANTEN LICHT + +[FED_RGL] +GLÄNZENDE STRAßEN + +[FED_CLF] +FARBFILTER + +[FED_WLM] +WELT LIGHTMAPS + +[FED_MBL] +BEWEGUNGSUNSCHÄRFE + +[FEM_SIM] +SIMPEL + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MOBILE + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +AUTO + +{ controls } +[FEC_IVP] +PAD VERTIKAL INVERTIEREN + +{ map } +[FEM_TWP] +Wegpunkt umschalten + +[FEA_FMN] +RADIO AUS + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +XBOX 360 CONTROLLER + +[FEC_ONE] +XBOX ONE CONTROLLER + +[FEC_NSW] +NINTENDO SWITCH CONTROLLER + +[FEC_TYP] +GAMEPAD-TYP + +[FEC_CCF] +KONFIGURATION + +[FEC_CF1] +KONFIGURATION 1 + +[FEC_CF2] +KONFIGURATION 2 + +[FEC_CF3] +KONFIGURATION 3 + +[FEC_CF4] +KONFIGURATION 4 + +[FEC_CDP] +CONTROLLER-ANZEIGE + +[FEC_ONF] +Zu Fuß + +[FEC_INC] +Im Auto + +[FEC_VIB] +Vibration : + +[FET_AGS] +KONTROLLEREINSTELLUNGEN + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED diff --git a/utils/gxt/gxt.exe b/utils/gxt/gxt.exe new file mode 100644 index 0000000000000000000000000000000000000000..0f55b7605a8a83400f2b7a64d7527cfa2457c6b5 GIT binary patch literal 307200 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

wjK^`mnCeatS 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 G1pWsQQ@aHK literal 0 HcmV?d00001 diff --git a/utils/gxt/italian.txt b/utils/gxt/italian.txt new file mode 100644 index 0000000..9dfee85 --- /dev/null +++ b/utils/gxt/italian.txt @@ -0,0 +1,8205 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. +} + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBFàèéìòùÀÈÉÌÒÙ + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~Ehi! Torna nel veicolo! + +[IN_VEH2] +~g~Avrai bisogno di un veicolo per questo lavoro! + +[IN_BOAT] +~g~Hai bisogno di un'imbarcazione per questo lavoro! + +[HEY] +~g~Non procedere da solo, stai insieme ai tuoi compagni! + +[HEY2] +~g~Non dividetevi, tieni il gruppo compatto! + +[HEY3] +~g~Hai abbandonato il tuo uomo! Torna indietro e recupera 8-Ball! + +[HEY4] +~g~Se abbandoni Misty, Luigi ti fa a pezzi! Vai a recuperarla! + +[HEY5] +~g~Manca una ragazza alla lista! Vai a recuperarla! + +[HEY6] +~g~Hai perso l'onore insieme a Yakuza Kanbu. Devi proteggerlo! + +[HEY7] +~g~Un'arma extra può tornare utile. Torna indietro e recupera il tuo contatto! + +[HEY8] +~g~Forse non hai ben presente il concetto di protezione: non abbandonare il vecchietto orientale! + +[HEY9] +~g~Vuoi sapere cosa si dice in giro? Fa'una visita al tuo contatto! + +[HELP2_A] +Premi il ~h~tasto /~w~ mentre corri per effettuare uno ~h~scatto~w~. + +[HELP3] +Ricorda che puoi eseguire uno scatto solo per brevi tratti. + +[HELP4_A] +Premi il ~h~tasto ~k~~VEHICLE_ACCELERATE~~w~ per ~h~accelerare~w~. + +[HELP4_D] +Sposta la ~h~levetta analogica destra~w~ verso l'alto per ~h~accelerare~w~. + +[HELP5_A] +Premi il ~h~tasto ~k~~VEHICLE_BRAKE~~w~ per ~h~frenare~w~ o, se il veicolo è fermo, per inserire la ~h~retromarcia~w~. + +[HELP5_D] +Sposta la ~h~levetta analogica destra~w~ verso il basso per ~h~frenare~w~ o, se il veicolo è fermo, per inserire la ~h~retromarcia~w~. + +[HELP6_A] +Premi il ~h~tasto ~k~~VEHICLE_HANDBRAKE~~w~ per tirare il ~h~freno a mano~w~. + +[HELP6_C] +Premi il ~h~tasto ~k~~VEHICLE_HANDBRAKE~~w~ per tirare il ~h~freno a mano~w~. + +[HELP6_D] +Premi il ~h~tasto ~k~~VEHICLE_HANDBRAKE~~w~ per tirare il ~h~freno a mano~w~. + +[HELP7_A] +Tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~ per ~h~mirare~w~ con il fucile di precisione. + +[HELP7_D] +Tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~ per ~h~mirare~w~ con il fucile di precisione. + +[HELP8_A] +Premi il ~h~tasto ~k~~PED_SNIPER_ZOOM_IN~~w~ per ~h~zoomare~w~ col fucile e il ~h~tasto ~k~~PED_SNIPER_ZOOM_OUT~~w~ per ~h~allargare il campo~w~. + +[HELP9_A] +Premi il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ con il fucile di precisione. + +[HELP10] +Questo distintivo indica che hai un livello di sospetto. + +[HELP11] +Più distintivi hai, maggiore è il tuo livello di sospetto. + +[HELP13] +In alcuni casi potresti dover utilizzare passaggi non indicati sul radar. + +[TIMER] +Questa è una missione a tempo: devi completarla prima che il contatore raggiunga lo zero. + +[MISTY1] +~r~Mistry è pronta per l'obitorio! + +[OUT_VEH] +~g~Esci dal veicolo! + +[GARAGE] +Porta l'auto dentro il garage e poi esci a piedi all'esterno. + +[WANTED1] +~g~Semina i poliziotti per perdere il tuo livello di sospetto. + +[NODOORS] +~g~Non sono sardine! Trova un veicolo con sedili a sufficienza. + +[TRASH] +~g~Hai ridotto proprio male il tuo veicolo! Vedi di farlo riparare! + +[WRECKED] +~g~Il veicolo è a pezzi! + +[HORN] +~g~Suona il clacson. + +[HORN4] +Premi il ~h~tasto R3~w~ per attivare il ~h~clacson~w~. + +[NOMONEY] +~g~Ti servono più soldi! + +[OUTTIME] +~r~Troppo lento, troppo lento! + +[SPOTTED] +~r~Ti hanno visto! + +[REWARD] +RICOMPENSA ~1~$ + +[GAMEOVR] +GAME OVER + +[Z] +Valore asse Z: ~1~ + +[M_FAIL] +MISSIONE FALLITA! + +[M_PASS] +MISSIONE COMPIUTA! ~1~$ + +[O_PASS] +LAVORO OCCASIONALE ESEGUITO! + +[O_FAIL] +LAVORO OCCASIONALE FALLITO! + +[DEAD] +MASSACRATO! + +[BUSTED] +BECCATO! + +[S_PROMP] +Quando non stai eseguendo una missione, puoi ~h~salvare la partita qui~w~ in questo modo il tempo avanzerà di sei ore. + +[NUMBER] +~1~ + +[SCORE] +~1~$ + +[LOADCAR] +CARICAMENTO VEICOLO... (PREMI L1 PER ANNULLARE) + +[CARSOFF] +Veicoli disabilitati. + +[CARS_ON] +Veicoli attivati. + +[TEXTXYZ] +Scrittura delle coordinate sul file... + +[CHEATON] +Modalità trucchi attivata + +[CHEATOF] +Modalità trucchi disattivata + +[UZI_IN] +L'Uzi adesso e' disponibile da AmmuNation! + +[IMPORT1] +Esci fuori e aspetta il tuo veicolo. + +[PAGEB1] +Pistola depositata nel nascondiglio + +[PAGEB2] +Uzi depositato nel nascondiglio + +[PAGEB3] +Armatura depositata nel nascondiglio + +[PAGEB4] +Fucile a pompa depositato nel nascondiglio + +[PAGEB5] +Granate depositate nel nascondiglio + +[PAGEB6] +Molotov depositate nel nascondiglio + +[PAGEB7] +AK47 depositato nel nascondiglio + +[PAGEB8] +Fucile di precisione depositato nel nascondiglio + +[PAGEB9] +M16 depositato nel nascondiglio + +[PAGEB10] +Lanciamissili depositato nel nascondiglio + +[PAGEB11] +Lanciafiamme depositato nel nascondiglio + +[WANT_A] +Non puoi essere arrestato se il tuo ~h~livello di sospetto~w~ è nullo. + +[WANT_B] +Il tuo ~h~livello di sospetto~w~ è rappresentato da una riga di stelle nell'angolo superiore destro dello schermo. + +[WANT_C] +Adesso hai un ~h~livello di sospetto~w~ pari a uno... + +[WANT_D] +due... + +[WANT_E] +tre... + +[WANT_F] +Man mano che il tuo ~h~livello di sospetto~w~ aumenta, attirerai l'attenzione di forze dell'ordine sempre più potenti. + +[WANT_G] +Se vieni ~h~'beccato'~w~, verrai portato alla più vicina stazione di polizia. + +[WANT_H] +I poliziotti prenderanno tutte le tue armi e parte dei tuoi risparmi come bustarella. + +[WANT_I] +Qualsiasi missione stessi affrontando, sarà considerata fallita. + +[WANT_J] +Scoprirai alcuni modi per ridurre il tuo livello di sospetto procedendo nel gioco. + +[WANT_K] +Se sei in un veicolo, i ~h~carozzieri~w~ potranno ~h~azzerare il tuo livello di sospetto~w~. + +[HEAL_B] +Quando sei ~h~'massacrato'~w~, verrai trasportato al più vicino ospedale. + +[HEAL_C] +Perderai tutte le tue armi e i dottori prenderanno parte dei tuoi risparmi per rimetterti in sesto. + +[HEAL_E] +Scoprirai alcuni modi per curarti o per proteggerti dagli attacchi procedendo nel gioco. + +[DAM] +DANNO: + +[KILLS] +UCCISIONI: + +[FARES] +CLIENTI: + +[BULL] +LINGOTTI: + +[EVID] +PROVE: + +[HEALTH] +CONDIZIONI VEICOLO: + +[COLLECT] +RACCOLTO: + +[BOMB] +Guida il veicolo dentro un'armeria per installare una ~h~bomba~w~. Costo - ~h~1000$~w~. + +[SAVE1] +Passa attraverso la porta per ~h~salvare la partita~w~. Non puoi salvare durante una missione. + +[SAVE2] +Qualsiasi veicolo parcheggiato nel garage verrà salvato insieme alla partita. + +[AMMU] +Entra in AmmuNation per comprare un'arma. + +[BRIDGE1] +Quando verrà riparato il ponte Callahan, potrai raggiungere Staunton Island. + +[TUNNEL] +Quando il sottopassaggio Porter verrà inaugurato, potrai raggiungere Staunton Island. + +[LUIGI] +MISSIONI LUIGI + +[TONI] +MISSIONI TONI + +[JOEY] +MISSIONI JOEY + +[FRANK] +MISSIONI SALVATORE + +[DIABLO] +MISSIONI DIABLO + +[ASUKA] +MISSIONI ASUKA + +[B_SITE] +MISSIONI SUBURBANE ASUKA + +[KENJI] +MISSIONI KENJI + +[RAY] +MISSIONI RAY + +[LOVE] +MISSIONI LOVE + +[YARDIE] +MISSIONI YARDIE + +[HOOD] +MISSIONI HOOD + +[CITYZON] +Città di Liberty + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Molo atlantico + +[PORT_E] +Porto di Portland + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Spiaggia di Portland + +[LITTLEI] +Saint Mark + +[REDLIGH] +Distretto a luci rosse + +[TOWERS] +Hepburn Heights + +[HARWOOD] +Harwood + +[ROADBR1] +Ponte Callahan + +[ROADBR2] +Ponte Callahan + +[TUNNELP] +Sottopassaggio Porter + +[BOMB1] +Garage di 8-Ball + +[COM_ZON] +Staunton Island + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Campus Liberty + +[CONSTRU] +Fort Staunton + +[PARK] +Parco Belleville + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Aeroporto Francis + +[PROJECT] +Giardini Wichita + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Diga Cochrane + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Ambulanza + +[CAR_2] +Camion dei pompieri + +[CAR_3] +Polizia + +[CAR_4] +Cellulare + +[CAR_5] +Caserma + +[CAR_6] +Rhino + +[CAR_7] +Auto dell'FBI + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_10] +Coach + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Familiare + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Vecchio taxi + +[CAR_39] +Maggiolino + +[LUIGIS] +Club Luigi's + +[GOAWAY] +~g~Stai già svolgendo una missione! + +[LUIGGO] +~g~Luigi sta intervistando delle nuove ragazze. Torna più tari! + +[JOEYGO] +~g~Joey è fuori città con Misty. Passa più tardi! + +[TONIGO] +~g~Toni ha portato sua madre a teatro. Passa un'altra volta! + +[KEMUGO] +~g~Maria e Kemuri sono impegnati la momento. Passa più tardi! + +[KENJGO] +~g~Kenji è a una riunione della Yakuza. Passa un'altra volta! + +[RAYGO] +~g~Ray è andato a un centro estetico. Passa un'altra volta! + +[LOVEGO] +~g~Donald Love si sta occupando di affari urgenti. Prendi un appuntamento più tardi! + +[KENSGO] +~g~Kenji e' occupato! Torna più tardi! + +[ASUSGO] +~g~Asuka adesso non è disponibile! + +[HOODGO] +~g~Gli Hood non sono disponibili! + +[WRONGT1] +~g~Passa tra le 05:00 e le 21:00 per un lavoro + +[WRONGT2] +~g~Passa tra le 06:00 e le 14:00 per un lavoro + +[WRONGT3] +~g~Passa tra le 15:00 e le 00:00 per un lavoro + +[GUN_1A] +Usa il ~h~tasto ~k~~PED_CYCLE_WEAPON_RIGHT~~w~ e il ~h~tasto ~k~~PED_CYCLE_WEAPON_LEFT~~w~ per passare in rassegna le armi. + +[GUN_2A] +Tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~ per la ~h~mira automatica~w~ e premi il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per sparare! Allenati con i bersagli... + +[GUN_2C] +Tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~ per la ~h~mira automatica~w~ e premi il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per sparare! Allenati con i bersagli... + +[GUN_2D] +Tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~ per la ~h~mira automatica~w~ e premi il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per sparare! Allenati con i bersagli... + +[GUN_3A] +Mentre tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~, premi il ~h~tasto ~k~~PED_CYCLE_TARGET_LEFT~~w~ o il ~h~tasto ~k~~PED_CYCLE_TARGET_RIGHT~~w~ per cambiare bersaglio. + +[GUN_3B] +Mentre tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~, premi il ~h~tasto ~k~~PED_CYCLE_TARGET_LEFT~~w~ o il ~h~tasto ~k~~PED_CYCLE_TARGET_RIGHT~~w~ per cambiare bersaglio. + +[GUN_4A] +Mentre tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~, puoi camminare o correre tenendo sotto mira il bersaglio. + +[GUN_4B] +Mentre tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~, puoi camminare o correre tenendo sotto mira il bersaglio. + +[GUN_5] +Puoi far pratica sparando a questi bersagli di carta. Quando hai finito, riprendi la missione. + +[TAXI1] +~g~Trova un passeggero. + +[FARE1] +~g~Destinazione: ~w~'Meeouch Sex Kitten Club' ~g~nel distretto a luci rosse. + +[FARE2] +~g~Destinazione: ~w~'Supa Save' ~g~a Portland View. + +[FARE3] +~g~Destinazione: ~w~'l'auditorio della scuola' ~g~a Chinatown. + +[FARE4] +~g~Destinazione: ~w~'Greasy Joe's Cafe' ~g~a Callahan Point. + +[FARE5] +~g~Destinazione: ~w~'AmmuNation' ~g~nel distretto a luci rosse. + +[FARE6] +~g~Destinazione: ~w~'Easy Credit Autos' ~g~a Saint Mark. + +[FARE7] +~g~Destinazione: ~w~'Woody's topless bar' ~g~nel distretto a luci rosse. + +[FARE8] +~g~Destinazione: ~w~'Marcos Bistro' ~g~a Saint Mark. + +[FARE9] +~g~Destinazione: ~w~'Garage importazioni-esportazioni' ~g~al porto di Portland. + +[FARE10] +~g~Destinazione: ~w~'Punk Noodles' ~g~a Chinatown. + +[FARE12] +~g~Destinazione: ~w~'lo stadio di football' ~g~in Aspatria. + +[FARE13] +~g~Destinazione: ~w~'la chiesa' ~g~a Bedford Point. + +[FARE14] +~g~Destinazione: ~w~'il Casinò' ~g~in Torrington. + +[FARE15] +~g~Destinazione: ~w~'università di Liberty' ~g~al campus di Liberty. + +[FARE16] +~g~Destinazione: ~w~'il grande magazzino' ~g~nel parco Belleville. + +[FARE17] +~g~Destinazione: ~w~'il museo' ~g~a Newport. + +[FARE18] +~g~Destinazione: ~w~'AmCo Building' ~g~a Torrington. + +[FARE19] +~g~Destinazione: ~w~'Bolt Burgers' ~g~a Bedford Point. + +[FARE20] +~g~Destinazione: ~w~'il parco' ~g~a Belleville. + +[FARE21] +~g~Destinazione: ~w~'Aeroporto Francis'~g~. + +[FARE22] +~g~Destinazione: ~w~'la diga di Cochrane'~g~. + +[FARE24] +~g~Destinazione: ~w~'l'ospedale' ~g~a Pike Creek. + +[FARE25] +~g~Destinazione: ~w~'il parco' ~g~a Shoreside Vale. + +[FARE26] +~g~Destinazione: ~w~'North West Towers' ~g~ai giardini Wichita. + +[NEW_TAX] +PIÙ GRANDI! PIÙ VELOCI! PIÙ ROBUSTI! I nuovi taxi Borgnine disponibili ad Harwood. Chiamate subito il numero 555-BORGNINE! + +[TSCORE2] +~1~$ + +[IN_ROW] +Bonus ~1~ di seguito! ~1~$ + +[TTUTOR] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Taxi. + +[TTUTOR2] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Taxi. + +[A_TIME] ++~1~ secondi + +[A_FULL] +~r~Ambulanza piena! + +[A_RANGE] +~g~Sei fuori dal raggio d'azione della radio. Ritorna in prossimità dell'ospedale! + +[FTUTOR] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Camion dei pompieri. + +[FTUTOR2] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Camion dei pompieri. + +[F_PASS1] +Incendio spento! + +[F_RANGE] +~g~Sei fuori dal raggio d'azione della radio. Ritorna in prossimità della stazione dei pompieri! + +[C_BREIF] +~g~Sospetto visto in prossimità dell'area ~a~. + +[C_RANGE] +~g~Sei fuori dal raggio d'azione della radio. Ritorna in prossimità della stazione di polizia! + +[DODO_FT] +Hai volato per ~1~ secondi! + +[EBAL_A] +Conosco un posto nei dintorni del distretto a luci rosse dove si puo'parlare, + +[EBAL_A1] +ma le mie mani sono a pezzi, quindi è meglio se guidi te, fratello. + +[EBAL_1] +Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un veicolo. + +[EBAL_1B] +Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un veicolo. + +[EBAL_2] +~g~Rientra in macchina! + +[EBAL_3] +Questo è il ~h~radar~w~: usalo per orientarti nella città. Segui il ~h~puntino~w~ sul ~h~radar~w~ per trovare il nascondiglio! + +[EBAL_D] +Conosco un tipo, è nel giro. Si chiama Luigi. + +[EBAL_D1] +Forza, raggiungiamolo, magari riesco a procurarti qualche lavoretto. + +[EBAL_E] +Forza, facciamo un salto. Te lo voglio presentare. + +[EBAL_I] +Il boss sara' con voi al più presto... + +[EBAL_J] +8-Ball ha alcune faccende da sbrigare al piano di sopra. + +[EBAL_K] +Forse potresti farmi un favore. + +[EBAL_L] +Una delle mie ragazze ha bisogno di un passaggio, prendi una macchina, recupera Misty dalla clinica e portala qua. + +[EBAL_N] +E tieni le mani sul volante! + +[EBAL_4] +~r~8-Ball è morto! + +[EBAL_5] +~g~Trova un veicolo! + +[EBAL_6] +~g~Recupera Misty! + +[LM1] +'LE RAGAZZE DI LUIGI' + +[LM2] +'NIENTE SPANK PER LE RAGAZZE' + +[LM3] +'PORTA IN GIRO MISTY PER ME' + +[LM5] +'FESTA DEGLI SBIRRI' + +[LM1_2] +~g~Porta Misty al club Luigi's. + +[LM1_3] +~g~Premi il clacson per far entrare la ragazza in macchina. + +[LM1_6] +~g~Torna in macchina! + +[LM1_7] +~g~Ferma il veicolo in prossimità di Misty per permetterle di salire. + +[LM1_8] +Puoi tornare da Luigi per altri lavori o gironzolare per la città di Liberty. + +[LM2_A] +C'è una nuova droga in giro per la città chiamata SPANK. + +[LM2_E] +Qualche farabutto ne ha passata un po' alle mie ragazze giù al porto di Portland. + +[LM2_B] +Raggiungi lo spacciatore e gioca a baseball con la sua testa! + +[LM2_G] +Voglio soddisfazione per questo insulto! + +[LM2_1] +~g~Prendi la sua macchina e falla riverniciare. + +[LM2_2A] +Usa il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per tirare ~h~pugni~w~ e ~h~calci~w~ o per ~h~usare~w~ la mazza! + +[LM2_2C] +Usa il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per tirare ~h~pugni~w~ e ~h~calci~w~ o per ~h~usare~w~ la mazza! + +[LM2_2D] +Usa il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per tirare ~h~pugni~w~ e ~h~calci~w~ o per ~h~usare~w~ la mazza! + +[LM2_3] +~g~Parcheggia la macchina nel rifugio di Luigi! + +[LM2_4] +~g~Rivernicia la macchina! + +[LM3_A] +Ehi, devo parlarti... Va bene Mick, ne discutiamo più tardi. + +[LM3_B] +Come va ragazzo? + +[LM3_C] +Il figlio del Don, Joey Leone, ha bisogno delle doti di Misty, la sua ragazza. + +[LM3_D] +Valla a prendere a Hepburn Heights... + +[LM3_E] +ma fa attenzione: è il territorio dei Diablo. + +[LM3_F] +Poi portala in fretta fino al suo garage in Trenton. + +[LM3_H] +Mi raccomando, tieni gli occhi sulla strada e lontano da Misty! + +[LM3_1D] +Premi il ~h~tasto L3~w~ per suonare il ~h~clacson~w~ e avvertire Misty che sei arrivato. + +[LM3_2] +~g~Porta Misty da Joey. + +[LM3_4] +~g~Vai a prendere Misty! + +[LM3_5] +Adesso lavori regolarmente per Luigi, vero? Era ora che trovasse un autista di cui ci si può fidare! + +[LM3_7] +Sarò da te fra un minuto, stellina mia. + +[LM3_10] +~g~Recupera un veicolo! + +[LM4_B] +Occupati di ciò che ti ho chiesto. + +[LM4_C] +Se ti serve un'arma, vai sul retro di AmmuNation dal lato opposto della metropolitana. + +[LM5_A] +La festa degli sbirri viene tenuta in una vecchia scuola presso il ponte Callahan. + +[LM5_B] +A quanto sembra vogliono un po' di azione vecchio stile... + +[LM5_C] +Ora ho ragazze su tutti i marciapiedi della città. + +[LM5_D] +Portale alla festa il prima possibile. + +[LM5_1] +~g~Non sovraffollate il mezzo o mi sciuperai le ragazze! ~g~Scarica subito queste e poi vai a cercarne altre. + +[LM5_2] +~r~Una delle ragazze di Luigi è ridotta a pezzi! + +[LM5_3] +~g~Hai bisogno di un mezzo! + +[LM5_4] +~g~Raccogli le ragazze che lavorano presso St. Marks. + +[LM5_5] +~g~Porta le ragazze alla festa degli sbirri! + +[LM5_8] +~g~Ragazze alla festa: ~1~ + +[JM2] +'ADDIO 'CHUNKY' LEE CHONG' + +[JM4] +'L'AUTISTA DI CIPRIANI' + +[JM5] +'CADAVERE NEL BAGAGLIAIO' + +[JM1_1] +~g~Porta la macchina di Forelli al garage di 8-Ball a nord di qui, dietro a 'Easy Credit Autos'. + +[JM1_2] +~g~Parcheggia la macchina nuovamente di fronte al Marco's Bistro. + +[JM1_3] +~g~Attiva la bomba e vattene in fretta! + +[JM1_4] +~g~Hai rovinato il veicolo! Fallo riparare! + +[JM1_5] +~g~La bomba non è stata piazzata! + +[JM1_6] +~g~Parcheggia la macchina nel posto giusto. + +[JM1_8A] +~y~Ehi, amico mio! + +[JM1_8B] +~y~L'armeria è automatizzata: entra dentro, ferma la macchina e il resto dell'operazione verrà eseguito automaticamente. + +[JM1_8C] +~y~La prima volta è gratis, ma le altre volte dovrai pagare. + +[JM2_A] +Chunky Lee Chong sta spacciando SPANK per una nuova gang dalla Colombia... o dal Colorado...o qualcosa del genere... + +[JM2_B] +Non ricordo bene... ma del resto che importa? + +[JM2_D] +Quel verme ha venduto la sua roba per l'ultima volta. + +[JM2_E] +Voglio che tu lo elimini! + +[JM2_G] +Equipaggiati con una calibro nove: sai dove trovarla, vero? + +[JM2_H] +Guardati le spalle a Chinatown: è territorio della Triade. + +[JM3_A] +Bene, colpiremo il furgone portavalori. + +[JM3_B] +Esce da Chinatown tutti i giorni. + +[JM3_C] +I proiettili non scalfiscono neanche la sua blindatura, per cui prendi una macchina e fallo uscire fuori strada. + +[JM3_D] +Colpiscilo forte e le guardie dovrebbero arrendersi. + +[JM3_E] +Ora portalo fino al magazzino vicino agli impianti portuali e i miei ragazzi si occuperanno del resto. + +[JM3_F] +Ricorda che non starà in giro per delle ore, per cui non perdere tempo. + +[JM3_1] +~g~Porta il furgone a destinazione. + +[JM3_2] +~g~Sperona il furgone finché non è danneggiato oltre al 70 percento. + +[JM4_B] +Oh! Ecco il tipo di cui ti stavo parlando. + +[JM4_C] +Bene, ascolta: questo tipo non è italiano e non è un meccanico, ma sa come riparare le cose. + +[JM4_D] +Il suo nome è Toni Cipriani. + +[JM4_E] +Piacere, Toni Cipriani. + +[JM4_F] +Portalo al ristorante Momma's a St Marks, OK? + +[JM4_G] +Adesso ascolta, sto pianificando un lavoro che richiede un buon autista, per cui passa a trovarmi un giorno di questi, OK? + +[JM4_2] +Aspetta qua! Tieni il motore acceso, potrebbero esserci grane. + +[JM4_3] +È un'imboscata della Triade! Portaci via di qua, ragazzo! + +[JM4_4] +La Triade pensa di potersi prendere gioco di me, la Triade... di ME! + +[JM4_6] +Fai attenzione alla macchina! Ti ho detto di non fare stronzate. + +[JM4_7] +~g~Accompagna Toni al ristorante Momma's. + +[JM4_8] +~r~Toni è stato ucciso! + +[JM5_A] +Perfetto! Semplicemente perfetto. + +[JM5_B] +Sei proprio la persona di cui avevo bisogno! + +[JM5_D] +Uno dei Forelli ha voluto fare il furbo e si è beccato quello che si meritava. + +[JM5_E] +Porta il cadavere al rottamatore ad Hardwood, OK? + +[JM5_1] +~g~Porta il cadavere al rottamatore! + +[JM5_2] +~g~Sono i fratelli Forelli! + +[JM6_A] +Ci si prospetta una bella corsetta, vero? + +[JM6_B] +Bene, recupera un veicolo e raggiungi il rifugio a St. Marks e recupera alcuni miei amici. + +[JM6_C] +Stanno per fare un colpo a una banca e hanno bisogno dell'autista. + +[JM6_D] +Ho dato loro la mia parola che tu sei il migliore sul mercato. + +[JM6_E] +Portali in banca prima delle cinque, non un minuto più tardi! + +[JM6_2] +Tieni il motore acceso: torneremo in un attimo! + +[JM6_3] +Portaci via di qua!! + +[JM6_4] +Semina i poliziotti e portaci al rifugio! + +[JM6_6] +~g~Prendi un veicolo meno appariscente! + +[JM6_7] +~g~Hai bisogno di tutti e tre per rapinare la banca! + +[TM1] +'RIPULIRE LA LAVANDERIA' + +[TM2] +'LA RACCOLTA' + +[TM3] +'SALVATORE RICHIEDE UN INCONTRO' + +[TM4] +'TRIADE E TRIBOLAZIONE' + +[TM5] +'PESCE ESPLOSIVO' + +[TONI_P] +Ho un lavoro urgente per te! -Toni + +[TM1_A] +~w~Prendi una sedia, ragazzo, prendi una maledetta sedia. + +[TM1_B] +~w~Allora la lavanderia non intende pagare il pizzo, eh? + +[TM1_C] +~w~La Triade pensa di poter mettersi contro di me? + +[TM1_D] +~w~Insegniamo a questi presuntuosi cosa significa fare sul serio. + +[TM1_E] +~w~Sì, insegniamo loro un po' di rispetto. Nessun mio figliolo si fa fregare dalla Triade. + +[TM1_F] +Tuo padre, pace all'anima sua, non si è mai fatto fregare da quelli della Triade in Sicilia. + +[TM1_G] +~w~Scusa mamma. Sì mamma. + +[TM1_H] +~w~Voglio che tu distrugga i furgoni della lavanderia + +[TM1_I] +~w~e faccia a pezzi qualsiasi idiota della Triade che oserà mettersi in mezzo. + +[TM1_J] +~w~8-Ball ti fornirà qualsiasi cosa di cui tu possa aver bisogno. + +[TM2_A] +~w~Toni vuole fare il duro, + +[TM2_AA] +ma non riuscirà mai a eguagliare suo padre. Ha lasciato una nota per te sul tavolo. + +[TM2_B] +~w~La lavanderia ha accettato di pagare: bel lavoro, ragazzo! + +[TM2_C] +~w~Vai a recuperare i contanti e portali qua. Fai attenzione alla Triade. + +[TM2_D] +~w~Potrebbero volerti ficcare qualche petardo nel sedere, ma tu non farti impressionare. + +[TM2_E] +~w~Nessuno, e intendo nessuno, fa le scarpe a TONI CIPRIANI! + +[TM2_1] +~g~Riporta i contanti a Toni!!! + +[TM2_2] +~g~Li hai freddati tutti! + +[TM3_MA] +~w~Non so dove sia! + +[TM3_MB] +~w~Giuro che ogni tanto anche lui non si rende conto di cosa fa. + +[TM3_MC] +~w~Suo padre invece era diverso. Sempre in prima linea, sempre in carica, coraggioso... + +[TM3_A] +~w~Don Salvatore richiede un incontro. + +[TM3_B] +~w~Recupera la limousine da suo garage e il suo ragazzo, Joey. + +[TM3_C] +~w~Poi passa a prendere Luigi dal suo club e torna qua a prendere me. + +[TM3_D] +~w~Poi andremo tutti assieme al luogo dell'incontro. + +[TM3_E] +~w~Quelli della Triade non sanno quando è l'ora di fermarsi. + +[TM3_F] +~w~Se vogliono la guerra, avranno la guerra! + +[TM3_G] +~w~Adesso muoviamoci. + +[TM3_1] +~g~Prendi la limousine da Joey. + +[TM3_2] +~g~Ora vai a prendere Luigi. + +[TM3_3] +~g~Ora vai a prendere Toni. + +[TM3_4] +~g~Porta i tuoi passeggeri da Salvatore. + +[TM3_5] +~y~Un'imboscata della Triade!!! + +[TM4_B] +~w~Siamo in GUERRA! La Triade utilizza uno stabilimento per il pesce come facciata. + +[TM4_C] +~w~La maggior parte del loro lavoro si svolge nel mercato del pesce di Chinatown. + +[TM4_D] +~w~La lavanderia ha smesso nuovamente di pagarci il pizzo. + +[TM4_E] +~w~Pensano di essere sotto la protezione della Triade, per cui si meritano una punizione esemplare. + +[TM4_F] +~w~Prendi questi ragazzi e fai fuori i signori della Triade! + +[TM4_G] +~w~E se ne hai il tempo, elimina anche qualcuno dei loro scagnozzi. + +[TM4_GAT] +~w~Avrai bisogno di un 'furgone del pesce della Triade' per riuscire a entrare. + +[TM5_A] +TESTO NON PIÙ NECESSARIO + +[TM5_B] +~w~Basta, ne ho avuto abbastanza! + +[TM5_C] +~w~Elimineremo una volta per tutte la Triade da Liberty! + +[TM5_D] +8-Ball ha messo una bomba in un camion della nettezza urbana. + +[TM5_E] +~w~La bomba è collegata a un timer, per cui se fai errori non resteranno prove. Vai a prendere il camion. + +[TM5_F] +~w~Fai attenzione: 8-Ball ha detto che il sistema è molto sensibile e potrebbe esplodere se prendi un colpo. + +[TM5_G] +~w~Il loro stabilimento aprirà i cancelli al camion. + +[TM5_H] +~w~Parcheggia tra i due serbatoi del gas e allontanati in fretta! + +[TM5_I] +~w~Voglio che piovano sgombri. + +[TM5_J] +~w~Stiamo facendo le cose in grande, basta con gli scherzi. + +[FM2] +'TAGLIARE L'ERBA' + +[FM4] +'ULTIME RICHIESTE' + +[FM1_A] +~w~Io e i ragazzi dobbiamo parlare di lavoro, + +[FM1_B] +~w~per cui dovrai occuparti della mia ragazza questa sera. + +[FM1_C] +~w~EHI MARIA! MUOVI IL CULO! + +[FM1_D] +~w~Quella vacca fa sempre così. + +[FM1_E] +~w~Ed eccola qua, la vera e unica regina di Sheba! + +[FM1_F] +~w~Cosa stavi facendo di sopra? + +[FM1_G] +~w~Qualsiasi cosa fosse, sono certo che mi è costato molto. + +[FM1_H] +~w~Beh, credevo non mi volessi attorno quando parli di lavoro, vero? + +[FM1_I] +~w~Entra in macchina e tieni chiusa la boccaccia. + +[FM1_J] +~w~Prendi la limousine, ma riportala indietro come nuova, capito? + +[FM1_K] +~w~E fai attenzione alla ragazza, può causarti dei problemi. + +[FM1_L] +~w~Tranquillo, sono certa che il tuo nuovo cagnolino sa cosa deve fare: + +[FM1_M] +~w~non è grande e grosso a sufficienza? + +[FM1_N] +~w~Ehi, Fido: andiamo a far visita a Chico e prendiamo della roba per divertirci. + +[FM1_P] +~g~Ecco, quello è Chico. Fermati vicino a lui. + +[FM1_S] +~w~Buongiorno signora. + +[FM1_TT] +~w~È UN RETATA DELLA POLIZZIA! + +[FM1_1] +~g~Torna nella limousine! + +[FM1_2] +~g~Rientra nella limousine! + +[FM1_3] +~r~Se abbandoni Maria, Salvatore ti farà ammazzare! Torna indietro a prenderla. + +[FM1_4] +~g~Hai scaricato la donna del Don. Torna al magazzino e aspetta Maria! + +[FM1_5] +~g~Riporta Maria sana e salva a Salvatore! + +[FM1_6] +~g~Chico non aspetterà per sempre. Porta Maria al porto. + +[FM1_7] +~r~Maria è morta! Salvatore non ne sarà entusiasta... + +[FM1_8] +~r~Hai fatto fuori lo spacciatore di Maria! + +[FM2_J] +Lasciaci da soli per un minuto. + +[FM2_A] +Il Cartello Colombiano sta producendo SPANK da qualche parte a Liberty. + +[FM2_K] +Non sappiamo però dove, e loro sembrano conoscere ogni nostra mossa in anticipo. + +[FM2_L] +C'è un tipo chiamato Ricciolino Bob che lavora al bar di Luigi. + +[FM2_M] +Sta spendendo molti più soldi di quanti ne guadagna. + +[FM2_N] +Normalmente prende un taxi per tornare a casa. Voglio che tu lo segua. + +[FM2_O] +E se ci sta vendendo... fallo fuori! + +[FM2_F] +Ecco il nostro caro amico: mister bocca larga. + +[FM2_G] +Sei stato seguito? Lo sai che questo deve restare un nostro segreto. + +[FM2_H] +No, no... nessuno mi ha seguito. Hai la mia roba? + +[FM2_I] +Ecco il tuo SPANK, e adesso parla! + +[FM2_P] +OK, i Leone stanno combattendo su due fronti. + +[FM2_Q] +Sono ai ferri corti con la Triade e sembra che nessuno dei due sia intenzionato a cedere. + +[FM2_R] +Contemporaneamente, Joey Leone si è fatto dei nemici tra i Forelli. + +[FM2_S] +Ogni giorno perdono uomini e influenza sulla città. + +[FM2_T] +Salvatore sta diventando pericoloso e paranoico. Sospetta di tutto e di tutti. + +[FM2_U] +Con persone fedeli come te, non capisco di cosa si preoccupi. + +[FM2_1] +~g~Ecco Ricciolino Bob! + +[FM2_2] +~g~Ricciolino ha lasciato il club: seguilo! + +[FM2_5] +~g~Andiamo al porto di Portland. + +[FM2_6] +~r~Ricciolino non salirà su un taxi distrutto! + +[FM2_7] +~r~Ricciolino è spaventato! L'appuntamento è saltato! + +[FM2_8] +~g~Elimina Ricciolino Bob! + +[FM2_9] +~r~Ricciolino Bob è morto! + +[FM2_10] +~r~Ricciolino è scappato! + +[FM2_11] +~g~Parcheggia di fronte al club Luigi's: Ricciolino Bob uscirà fra poco. + +[FM2_12] +~r~Ti ha seminato! + +[FM3_A] +~w~Dovremmo eliminare i fottuti Colombiani, + +[FM3_B] +~w~ma siamo già in guerra con la Triade e non siamo abbastanza forti. + +[FM3_C] +~w~Il Cartello ha fondi infiniti grazie allo smercio dello SPANK. + +[FM3_D] +~w~Se li attacchiamo apertamente, ci spazzeranno via come foglie secche. + +[FM3_E] +~w~Probalbimente producono lo SPANK sull'imbarcazione dove si è diretto Ricciolino. + +[FM3_F] +~w~Per cui dovremo usare la testa... meglio ancora la tua testa. + +[FM3_G] +~w~Ti chiedo di distruggere la fabbrica di SPANK come favore personale a me, Salvatore Leone. + +[FM3_H] +~w~Se ci riuscirai, sarai una persona felice: potrai avere tutto ciò che vuoi. + +[FM3_I] +~w~Vai a trovare 8-Ball: avrai bisogno della sua esperienza per far saltare l'imbarcazione. + +[FM3_8A] +~w~Ehi amico, Salvatore ha appena chiamato: + +[FM3_8B] +~w~per un lavoro come questo avrai bisogno di una bella potenza di fuoco. + +[FM3_8D] +~w~ma tu sai che sono soldi spesi bene. + +[FM3_8E] +~w~OK, procediamo allora! + +[FM3_8F] +~w~Posso impostare questo bambino e farlo esplodere, ma non posso sparare con queste mani. + +[FM3_8G] +~w~Prendi questo fucile e fai saltare un po' di teste! + +[FM3_4] +~g~Ferma il veicolo e fai scendere 8-Ball! + +[FM3_7] +~r~8-Ball è stato freddato! + +[FM3_8] +~r~Le guardie sono state messe in guardia! + +[FM4_A] +~w~Sei l'uomo delle pulizie che preferisco. + +[FM4_B] +~w~Sono fiero di te, hai saputo prendere a calci quei maledetti! + +[FM4_C] +~w~Ho un ultimo lavoro per te prima di celebrare. + +[FM4_D] +~w~C'è una macchina vicino al club Luigi's. + +[FM4_E] +~w~All'interno è schizzato cervello dappertutto. + +[FM4_F] +~w~Abbiamo dovuto far ragionare un tipo e la cosa si è rilevata un po', ehm, sporca. + +[FM4_H] +~w~Portala al rottamatore prima che la trovino i poliziotti. + +[AM3] +'STRONCA I PAPARAZZI' + +[AM4] +'GIORNO DI PAGA PER RAY' + +[AM5] +'DOPPIA FACCIA' + +[AM1_A] +Ci sono alcuni punti da chiarire prima di procedere con la nostra relazione di lavoro. + +[AM1_B] +Direi che è il caso di mostrare le carte. + +[AM1_C] +Io sono della Yakuza e so che hai lavorato per la famiglia di Salvatore Leone. + +[AM1_D] +La mia organizzazione può darti lavoro, + +[AM1_E] +ma prima devi dimostrare che i tuoi legami con la Mafia sono definitivamente chiusi. + +[AM1_G] +Assicurati che non raggiunga vivo il suo club. + +[AM1_H] +Nel frattempo io e Maria discuteremo dei tempi passati. + +[AM1_I] +Oh... Asuka, hai un massaggiatore. + +[AM1_J] +Non è un massaggiatore. + +[AM1_1] +~g~Salvatore sta lasciando adesso il club Luigi's! + +[AM1_2] +~r~Sei stato avvistato! + +[AM1_3] +~r~Hai mancato Salvatore! + +[AM1_4] +~r~Ma bravo, hai spaventato il bersaglio! E tu dovresti essere un professionista? + +[AM1_5] +~g~Raggiungi il distretto a luci rosse e aspetta che Salvatore esca dal club. + +[AM1_7] +~r~Salvatore è arrivato a casa e si sta bevendo un cocktail. Ma non ti chiamavano lo sciacallo? + +[AM1_8] +~g~Salvatore uscirà dal club Luigi's attorno alle ~1~:~1~ + +[AM2_4] +~g~Sei più appariscente di un elefante fluorescente! + +[AM3_A] +Un reporter ci è ronzato troppo attorno. + +[AM3_B] +Io e Maria ci prenderemo un po' di vacanza fino a quando non ti sarai liberato di questo guardone. + +[AM4_A] +Il mio truffatore preferito! + +[AM4_B] +Maria è molto presa al momento: le dirò che sei passato. + +[AM4_C] +Chi è? Asuka? Lo so, sono stata una bambina cattiva, ma devo andare in bagno! OK? + +[AM4_D] +È giunta l'ora che incontri il nostro uomo nella polizia. + +[AM4_E] +Ecco la ricompensa per l'ultimo lavoro che ha svolto per noi. + +[AM4_F] +È un tipo molto cauto. + +[AM4_G] +Raggiungi in fretta il telefono pubblico a Torrington e aspetta le istruzioni. + +[AM5_A] +Maria e io siamo andati a fare spese. + +[AM5_B] +La nostra fonte alla polizia ci ha informato che uno dei nostri autisti è un maledetto infiltrato! + +[AM5_C] +Fuori dalla sua macchina è particolarmente vulnerabile: gli abbiamo posizionato addosso un tracciante. + +[AM5_D] +Fallo sanguinare! + +[AM5_1] +Ben fatto! + +[AS1] +'ESCA' + +[AS2] +'ESPRESSO E VIA!' + +[AS4] +'RISCATTO' + +[AS1_A] +~w~Miguel pensa non lo stia trattando correttamente. + +[AS1_B] +~w~Ciò nonostante, mi ha rilevato quanta paura ha Catalina di una tua possibile vendetta. + +[AS2_A] +~w~Abbiamo sottovalutato i piani di Catalina per lo SPANK. + +[AS2_B] +~w~È ben più avanti dei Yardie nelle vendite per strada. + +[AS2_D] +~w~Sembra spaccino lo SPANK attraverso i chioschi per strada. + +[AS2_1] +~g~Distrutti tutti i chioschi a Portland!!! + +[AS2_2] +~g~Distrutti tutti i chioschi a Staunton Island!!! + +[AS2_3] +~g~Distrutti tutti i chioschi a Shoreside Vale!!! + +[AS2_4] +~r~Il Cartello ha avvertito i loro spacciatori! + +[AS2_5] +~g~Ci sono ancora dei chioschi a Shoreside Vale e a Staunton Island! + +[AS2_6] +~g~Ci sono ancora dei chioschi a Shoreside Vale! + +[AS2_7] +~g~Ci sono ancora dei chioschi a Staunton Island! + +[AS2_8] +~g~Ci sono ancora dei chioschi a Portland! + +[AS2_9] +~g~Ci sono ancora dei chioschi a Portland e a Shoreside Vale! + +[AS2_10] +~g~Ci sono ancora dei chioschi a Portland e a Staunton Island! + +[AS2_12] +~g~Gira per Liberty alla ricerca dei ~b~chioschi~g~! + +[AS3_A] +~w~Dovremmo stringerlo ancora un po' o aspettare che diventi nero e cada? + +[AS3_B] +~w~Diamogli uno sprone... + +[AS3_D] +~w~Caro il mio truffatore! + +[AS3_E] +~w~Mi stavo annoiando, così sono venuto a fare compagnia a Asuka. + +[AS3_1] +~g~Trova una ~r~barca~g~ e raggiungi la ~b~boa segnalatrice~g~! + +[AS3_3] +~g~Attendi fino a quando l'~y~aeroplano~g~ non inizia l'atterraggio! + +[AS3_5] +~g~Raccogli il carico! + +[AS3_4] +~g~Usa un lanciamissili per abbattere l'~g~aeroplano~g~!!! + +[AS3_2] +~b~Raggiungi la boa di segnalazione della pista! ~y~L'aereo sta per atterrare! + +[AS3_6] +~g~~1~ SU 8 + +[KM1] +'LA FUGA DI KANBU' + +[KM3] +'STRONCA IL PATTO' + +[KM4] +'SHIMA' + +[KM5] +'VENDETTA' + +[KM1_A] +Mia sorella mi ha parlato bene di te, + +[KM1_E] +anche se credo ancora che voi gaijin siete tutti quanti deludenti. + +[KM1_B] +Forse mi potresti aiutare a risolvere una situazione che si sta rilevando svantaggiosa. + +[KM1_F] +Chiaramente il fallimento ha le sue conseguenze. + +[KM1_C] +Un Kambu della Yakuza è tenuto in custodia per essere processato. + +[KM1_G] +È un elemento importante della famiglia. + +[KM1_H] +Liberalo e portalo al dojo a Bedford Point. + +[KM1_D] +Grazie per la tua azione altruista. Se avrai mai bisogno di aiuto, il dojo sarà onorato di fornirti due uomini pronti ad affiancarti. + +[KM1_1] +~g~Ruba una macchina della polizia! + +[KM1_2] +Installa una bomba sulla macchina! + +[KM1_3] +~g~Raggiungi il dojo della Yakuza. + +[KM1_5] +~g~Adesso raggiungi la stazione di polizia. + +[KM1_6] +Installa una bomba sulla macchina! + +[KM1_7] +~g~Solo mezzi della polizia autorizzati! + +[KM1_9] +~r~Non hai utilizzato un'auto esplosiva per distruggere il muro! + +[KM1_10] +~r~Il Kandu della Yakuza è morto - insieme al tuo onore! + +[KM1_11] +~r~Hai attirato la sventura su di te! + +[KM2_A] +Non è consigliabile sottovalutare l'importanza dell'etichetta in questo ambiente. + +[KM2_B] +Un uomo mi ha fatto una volta un favore e io non ho avuto l'opportunità di ricambiare la sua gentilezza. + +[KM2_C] +La persona in questione ha la passione delle automobili e mi ha richiesto alcuni modelli per la sua collezione. + +[KM2_F] +Il mio onore lo richiede. + +[KM2_2] +~g~Macchine consegnate. + +[KM3_A] +Quando i problemi insorgono, lo stolto mostra la schiena mentre il saggio si prepara ad affrontarli. + +[KM3_B] +Il Cartello Colombiano ha ignorato ripetutamente le nostre richieste di non interferire con i nostri interessi a Liberty. + +[KM3_C] +Adesso stanno negoziando con i Giamaicani per nel tentativo di umiliarci ulteriormente. + +[KM3_D] +Stanno completando un accordo in città. + +[KM3_F] +Prendi uno dei miei uomini, ruba una macchina degli Yardie e vai a porgere i miei saluti ai Colombiani. + +[KM3_E] +Il nostro onore richiede che nessuno di loro sopravviva. + +[KM3_2] +~g~Vai a prendere il tuo contatto. + +[KM3_3] +~g~L'incontro si svolge nel parcheggio dell'ospedale a Rockford! + +[KM3_4] +~r~Sono fuggiti! + +[KM3_6] +~g~Uccidili, uccidili tutti quanti! + +[KM3_8] +~g~Hai bisogno di una macchia degli Yardie per completare questa missione! + +[KM3_9] +~r~Uno dei Colombiani è morto: l'accordo è saltato. + +[KM3_10] +~r~Il contatto è morto! + +[KM4_A] +Per essere veramente forte, non devi mai mostrare debolezze. + +[KM4_C] +Vai e raccogli immediatamente i soldi per farli riciclare dal Casinò. + +[KM4_1] +Non ti posso pagare e non lo fare neanche se potessi! + +[KM4_9] +Alcuni delinquenti da strapazzo mi hanno appena rapinato! Hanno preso tutto quanto! + +[KM4_2] +La vostra protezione è nulla. + +[KM4_10] +E tu saresti della Yakuza? Con quella faccia? + +[KM4_3] +Non è per questo che vi pago; se volevo essere protetto così bene mi affidavo alla polizia. + +[KM4_4] +~g~Punisci la gang responsabile del furto dei ~b~soldi~g~! + +[KM4_7] +~r~Il negoziante ha respirato per l'ultima volta! + +[KM4_5] +Donald Love vorrebbe che tu passassi a trovarlo nel suo giardino. + +[KM4_6] +Ci sono soldi dappertutto! + +[KM4_8] +~g~Valigetta raccolta! + +[KM5_A] +TU! Che piacevole coincidenza vedere il tuo sporco muso proprio adesso! + +[KM5_B] +Sembrerebbe che i tuoi vani tentativi di dissuadere i Giamaicani + +[KM5_B1] +a collaborare con il Cartello siano miseramente falliti! + +[KM5_C] +Gli spacciatori Yardie stanno vendendo lo SPANK per ogni strada come se si trattasse di hotdog! + +[KM5_D] +I maiali del Cartello stanno ridendoci dietro! + +[KM5_E] +Ti darò un'ultima possibilità per provarmi che la fiducia a te data da mia sorella non era del tutto infondata. + +[KM5_F] +Abbatti tutti i nostri avversari e lava il tuo onore nel loro sangue!!! + +[KM5_3] +~r~Hai fallito nell'eliminare almeno ~1~ Yardie. + +[KM5_4] +~g~Congratulazioni, hai eliminato ~1~ Yardie. + +[KM5_5] +~g~Congratulazioni, hai eliminato ~1~ Yardie. BONUS ~1~$ + +[RM1] +'FAI TACERE L'UCCELLINO' + +[RM3] +'ELIMINA LE PROVE' + +[RM4] +'FUORI A PESCARE' + +[RM5] +'STRONCA L'INFILTRATO' + +[RM1_D] +Si trova sotto protezione in un palazzo della sicurezza testimoni. Il suo appartamento è dietro al parcheggio. + +[RM1_E] +Dai fuoco al palazzo per farlo uscire allo scoperto e fai in modo che non parli mai più. + +[RM1_1] +~g~Controlla l'appartamento della sicurezza testimoni. + +[RM1_2] +~g~Elimina McAffrey! + +[RM2_A1] +Ehi ragazzo, da questa parte! + +[RM2_A] +Un mio vecchio amico ha uno spaccio d'armi a Rockford. + +[RM2_D] +Ha bisogno di una mano ed è pronto a farti uno scontro sui migliori armamenti in circolazione. + +[RM2_E] +Ray ha appena chiamato... non credevo però che saresti venuto da solo. + +[RM2_F] +Bene, tre pistole sono meglio di una: prendete ciò che vi serve. + +[RM2_G] +~g~Vai a parlare con Phil! + +[RM2_H] +~r~Phil è stato ucciso!!! + +[RM2_L] +Ehi! Se avessi avuto te come compagno in Nicaragua, probabilmente avrei ancora il braccio. + +[RM2_N] +Lascia qua il bottino e vattene: mi occuperò io dei poliziotti. + +[RM3_D] +Le prove sono trasportate da un veicolo attraverso la città. + +[RM3_E] +Dovrai speronare quella macchina e raccogliere ogni prova che lascerà cadere. + +[RM3_F] +Quando le avrai raccolte tutte, lasciale in macchina e dalle fuoco. + +[RM3_G] +Sono sicuro che non avrai problemi a gestire la faccenda. + +[RM3_1] +~g~Lascia le prove in macchina e dalle fuoco. + +[RM3_4] +~g~L'accusa ha fatto cadere delle prove! + +[RM3_6] +~r~Le foro saranno sparse per tutta Liberty! + +[RM3_7] +~g~Adesso dai fuco alla macchina! + +[RM4_A] +Inizio a pensare che il mio socio sia una talpa. + +[RM4_C] +Esce fuori a pescare sulla sua imbarcazione quasi tutte le notti vicino al faro di Portland. + +[RM4_D] +Ruba una lancia della polizia e manda a fondo i suoi piani doppiogiochisti! + +[RM4_1] +~g~Ruba una lancia della polizia. + +[RM4_2] +~g~Raggiungi il faro e affonda il socio di Ray! + +[RM5_A] +Fottuto inutile! + +[RM5_A1] +Hai combinato un vero casino! Sono con il culo scoperto perché non sei neanche capace di ammazzare una mosca. + +[RM5_B] +Ti ho pagato profumatamente per eliminare il testimone ed è ancora vivo! + +[RM5_B1] +E oggi deve fare una deposizione ai federali! + +[RM5_C] +Sta per essere trasferito dall'ospedale Carson fino a Rockford. + +[RM5_D] +Se canta sono fregato... + +[RM5_E] +per cui vai ed esegui il lavoro per cui sei stato pagato! + +[RM5_1] +~g~Intercetta l'ambulanza. + +[RM5_2] +~g~Sei stato individuato!!! + +[RM5_3] +~g~Era un diversivo! + +[RM5_4] +~g~I proiettili non scalfiranno la blindatura!!! + +[RM5_5] +~g~La blindatura è ignifuga!!! + +[RM5_7] +~r~Il testimone è arrivato a destinazione!!! + +[RM5_8] +~g~Il testimone è affogato!!! + +[LOVE2] +'SPAZZA VIA WAKA-GASHIRA' + +[LOVE3] +'UNA GOCCIA NELL'OCEANO' + +[LOVE1_A] +Prima di tutto grazie per aver risolto il problema personale... + +[LOVE1_F] +La gente crede a tutto in questi giorni. + +[LOVE1_D] +Stanno cercando di estorcermi altri fondi, ma a me non piace chi ritratta all'ultimo minuto. + +[LOVE1_E] +Un patto è un patto, per cui non gli sgancerò neppure un altro penny. + +[LOVE1_G] +Vai a salvare il mio amico: fai tutto ciò che si rivelerà necessario. + +[LOVE1_2] +~g~Salva il vecchietto orientale. + +[LOVE1_3] +~g~Riporta il vecchietto orientale all'edificio di Donald Love. + +[LOVE1_4] +~g~Il vecchietto orientale deve trovarsi in uno dei garage... + +[LOVE1_6] +~r~Le interiora del vecchietto orientale sono sparse per tutta la strada! + +[LOVE1_7] +~g~Il cancello verrà aperto solo per un'auto dei Colombiani. + +[LOVE2_A] +Niente abbassa i prezzi degli immobili quanto una bella guerra fra gang... + +[LOVE2_B] +tranne forse un'epidemia di peste... ma potrebbe non essere la soluzione migliore in questo caso. + +[LOVE2_C] +È evidente chi la Yakuza e i Colombiani non sono amici per la pelle. + +[LOVE2_D] +Perché non investire in questa opportunità? + +[LOVE2_E] +Voglio che tu uccida il Waka-Gashira della Yakuza, Kenji Kasen. + +[LOVE2_F] +Kenji partecipa a un incontro al parcheggio all'ultimo piano di un palazzo a Newport. + +[LOVE2_G] +Recupera una macchina del Cartello ed eliminalo! + +[LOVE2_H] +La Yakuza deve dare la colpa al Cartello per questa dichiarazione di guerra. + +[LOVE2_1] +~g~Raggiungi Fort Staunton e ruba una macchina dei Colombiani! + +[LOVE2_2] +~g~Raggiungi il ~p~palazzo a Newport~g~ ed elimina Kenji! + +[LOVE2_3] +~r~Se procedi con una macchina non del Cartello, verrai identificato!!! + +[LOVE2_4] +~r~Gli uomini della Yakuza ti hanno identificato!!! + +[LOVE2_6] +~r~Hai ucciso tutti i testimoni!!! + +[LOVE3_A] +In questi giorni di ipocrisia globale, alcune piacevoli comodità potrebbero essere difficili da importare. + +[LOVE3_C] +Farò depositare alcuni pacchi in mare. + +[LOVE3_D] +Assicurati di raccoglierli prima che qualcun altro lo faccia. + +[LOVE3_1] +~g~Recupera un'~r~imbarcazione~g~ e insegui l'~y~aeroplano~g~! + +[LOVE4] +'GRAND THEFT AERO' + +[LOVE5] +'SERVIZIO DI SCORTA' + +[LOVE4_A] +Grazie per aver recuperato i pacchi, ma si trattava solo di un diversivo. + +[LOVE4_B] +Mi dispiace, ma sono gli inconvenienti del mestiere. + +[LOVE4_C] +Il mio vero obiettivo era nascosto all'interno dell'aereo. + +[LOVE4_F] +Ho corrotto gli ufficiali. + +[LOVE4_1] +~r~Il Cartello Colombiano è in zona!!! + +[LOVE4_2] +~g~Il pacco è scomparso! Scova i Colombiani e recuperalo! + +[LOVE4_3] +~g~Panlantic Construction...? + +[LOVE4_5] +~g~Il pacco dovrebbe essere a bordo dell'aereo... + +[LOVE4_6] +~g~Prendi l'ascensore fino alla cima della torre! + +[LOVE5_B] +Il mio amico orientale ha bisogno di una scorta mentre porta la mia ultima acquisizione a far autenticare. + +[LOVE5_1] +~g~Procedi! + +[LOVE5_2] +~g~Avrai bisogno di una macchina! + +[LOVE5_3] +~g~Vai avanti a esplorare l'uscita dal sottopassaggio! + +[LOVE5_4] +~r~Proteggi il camion! + +[RM6] +'RICERCATO' + +[RM6_A] +Non sei stato seguito? Bene. + +[RM6_B] +Adesso basta, sono nei guai fino alla gola e sto iniziando a sprofondare! + +[RM6_D] +Sono ricercato, me la batto. + +[RM6_E] +Portami al mio volo all'aeroporto e non te ne pentirai! + +[RM6_666] +Prenditi cura della mia Patriot antiproiettile. Ci vediamo a Miami! Ray + +[CAT1] +'RISCATTO' + +[CAT2] +'LO SCAMBIO' + +[CAT1_A] +Ho la tua cara Maria. Non credo tu voglia che la sua faccia finisca come una polpetta per cani, vero? + +[CAT2_F] +Mi sono rotta un'unghia e i miei capelli sono un disastro. Ma ci puoi credere? Mi è costato 50 dollari! + +[CAT2_G] +Ero così spaventata, ma mi sono detta: adesso sei una signorina. + +[CAT2_H] +Oh, ci divertiremo un mondo! Mia sorella mi ha detto che verrà a stare con me insieme ai suoi due figli + +[CAT2_I] +poiché suo marito continua a tradirla a ogni occasione... + +[CAT1_C] +XXXX + +[CAT1_D] +XXXX + +[CAT1_E] +XXXX + +[CAT1_F] +Raggiungi Catalina prima dello scadere del tempo! + +[CAT1_G] +XXXX + +[CAT1_H] +XXXX + +[CAT1_I] +XXXX + +[CAT1_J] +XXXX + +[CAT1_K] +XXXX + +[CAT1_L] +XXXX + +[AS4_1] +XXXX + +[CAT_MON] +~g~Non hai abbastanza soldi. Ti servono 500.000$. + +[BITCH_D] +~g~Maria è morta! + +[WEATHER] +TEMPO AVVERSO + +[WEATHE2] +TEMPO NORMALE + +[8001] +Hai fallito miseramente!!! + +[1000] +SEI MORTO + +[1001] +SEI MORTO + +[1002] +SEI MORTO + +[1003] +SEI MORTO + +[1004] +SEI MORTO + +[1005] +SEI STATO CATTURATO + +[1006] +SEI STATO CATTURATO + +[1007] +SEI STATO CATTURATO + +[1008] +SEI STATO CATTURATO + +[1009] +SEI STATO CATTURATO + +[GA_4] +Le bombe per le macchine costano 1000$ + +[GA_5] +La tua macchina ha già una bomba installata. + +[GA_6] { re3 change } +Parcheggiala, attivala premendo il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ e DATTELA A GAMBE! + +[GA_7] { re3 change } +Arma la bomba con il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~: esploderà non appena qualcuno cercherà di avviarla. + +[GA_8] +Usa il detonatore per attivare la bomba. + +[GA_9] +Hai raccolto ~1~ su 10 macchine speciali + +[GA_10] +Non male. Eccoti ~1~$ + +[GA_11] +Ne abbiamo già una. A noi non serve! + +[GA_12] +Bomba innescata + +[GA_13] +Un furto da manuale. Completa la lista e ci sarà un bonus per te. + +[GA_14] +Hai portato tutte le macchine. BENE! Eccoti un riconoscimento... + +[GA_15] +Spero che ti piaccia il nuovo colore. + +[GA_16] +La riverniciatura non è necessaria. + +[GA_19] +Non siamo interessati a questo modello. + +[GA_20] +Di questo modello ne abbiamo in abbondanza. Mi dispiace, non siamo interessati. + +[CR_1] +La gru non può alzare questo veicolo. + +[PU_MONY] +Non hai abbastanza soldi. + +[CO_ALL] +Li hai presi tutti. Eccoti un piccolo extra... + +[PAUSED] +PAUSA + +[HEALTH1] +Via di qui! Sei in perfetta forma. + +[HEALTH2] +L'assistenza medica costa. + +[HEALTH3] +Ti rimetterò in sesto. + +[HEALTH4] +Fanno 250$. + +[FEB_STA] +Statistiche + +[FEB_BRI] +Briefing + +[FEB_CON] +Comandi + +[FEB_AUD] +Audio + +[FEB_DIS] +Video + +[FEB_LAN] +Lingua + +[FEP_STA] +STATISTICHE + +[FEP_BRI] +BRIEFING + +[FEP_CON] +COMANDI + +[FEP_AUD] +AUDIO + +[FEP_DIS] +VIDEO + +[FEP_LAN] +LINGUA + +[FEF_ST1] +Chi è il vero cattivo? + +[FEF_ST2] +Che casino hai scatenato? + +[FEF_BR1] +Hai perso il filo? + +[FEF_CO1] +Hai bisogno di altri controlli? + +[FEF_CO2] +Scegli la configurazione più adatta al tuo stile di gioco + +[FEF_SA1] +Pensa alla tua posizione, salvala! + +[FEF_SA2] +Salva e carica le partite + +[FEF_AU1] +Pompa il volume! + +[FEF_AU2] +Scegli la stazione radio e gli effetti + +[FEF_DI1] +Cambia il gioco! + +[FEF_DI2] +Personalizza il gioco per la tua TV + +[FEF_LA1] +What you talking about willis? Che cavolo stai dicendo Willis? + +[FEF_LA2] +Scegli il tuo gergo preferito + +[FEB_PMB] +Brifing di missione precedenti: + +[FEC_NA] +NA + +[FEC_CWL] +Scorri armi a sinistra + +[FEC_CWR] +Scorri armi a destra + +[FEC_LOF] +Guarda avanti + +[FEC_TAR] +Bersaglio + +[FEC_MOV] +Movimento + +[FEC_CAM] +Modalità telecamera + +[FEC_PAU] +Pausa + +[FEC_ENV] +Sali sul veicolo + +[FEC_JUM] +Salta + +[FEC_ATT] +Attacca o fuoco con arma + +[FEC_RUN] +Corri + +[FEC_FPC] +Visuale in prima persona + +[FEC_LL] +Guarda a sinistra + +[FEC_LB1] +Guarda + +[FEC_LB2] +indietro + +[FEC_LB] +Guarda indietro + +[FEC_LR] +Guarda a destra + +[FEC_HOR] +Clacson + +[FEC_VES] +Comandi veicolo + +[FEC_RSC] +Cambia stazione radio + +[FEC_BRA] +Freno o retromarcia + +[FEC_HAB] +Freno a mano + +[FEC_CAW] +Arma vettura + +[FEC_ACC] +Accelera + +[FEC_SMT] +Attivatore missione speciale + +[FEA_OUT] +Uscita: + +[FEA_ST] +Stereo + +[FEA_MNO] +Mono + +[FEA_NON] +Nessuna + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX 109 + +[FED_DBG] +Menu Debug + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_SPR] +Show Ped Road Groups + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_DSR] +Debug Streaming Requests + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Memory Card Menu + +[FEM_RMC] +Registra MEMORY CARD uno + +[FEM_TFM] +Test Format MemCard One + +[FEM_TUM] +Test UnFormat MemCard One + +[FEM_CRD] +Create Root Dir + +[FEM_CLI] +Crea e carica icone + +[FEM_FFF] +Riempi il primo file con fuffa + +[FEM_SOG] +Salva solo la partita + +[FEM_CES] +Verifica ogni salvataggio 0kB4 + +[FEM_STG] +Salva la partita + +[FEM_STS] +Salva la partita con il nome GTA3 + +[FEM_CPD] +Crea una cartella mag protetta + +[FEM_MC2] +Memory Card Menu 2 + +[FEM_TS] +Prova salvataggio: + +[FEM_TL] +Prova caricamento: + +[FEM_TD] +Prova eliminazione: + +[PL_STAT] +Statistiche giocatore + +[PE_WAST] +Persone massacrate + +[PE_WSOT] +Persone massacrate da altri + +[CAR_EXP] +Macchine esplose + +[TM_BUST] +Tempi battuti + +[M_WASTE] +Uomini civili massacrati + +[F_WASTE] +Donne civili massacrate + +[PIG_WST] +Poliziotti massacrati + +[GNG_WST] +Membri delle gang massacrati + +[MED_WST] +Medici massacrati + +[FIRE_WS] +Pompieri massacrati + +[DED_CRI] +Criminali massacrati + +[DED_DED] +Scrocconi massacrati + +[DED_HOK] +Prostitute massacrate + +[HEL_DST] +Elicotteri distrutti + +[PER_COM] +Percentuale completata + +[KGS_EXP] +Kg di esplosivi utilizzati + +[ACCURA] +Accuratezza + +[ELBURRO] +Miglior tempo di corsa in sec + +[CAR_CRU] +Macchine distrutte + +[HED_EX] +Teste esplose + +[TM_DED] +Visite in ospedale + +[DAYSPS] +Giorni trascorsi nel gioco + +[MMRAIN] +Pioggia caduta in mm + +[MXCARD] +Distanza max salto FOLLE (ft) + +[MXCARJ] +Altezza max salto FOLLE (ft) + +[MXCARDM] +Distanza max salto FOLLE (m) + +[MXCARJM] +Altezza max salto FOLLE (m) + +[MXFLIP] +Numero max ribaltamenti FOLLI in aria + +[MXJUMP] +Numero max rotazioni FOLLI in aria + +[BSTSTU] +Migliore acrobazia FOLLE: + +[INSTUN] +Acrobazia folle + +[PRINST] +Acrobazia folle perfetta + +[DBINST] +Doppia acrobazia folle + +[DBPINS] +Doppia acrobazia folle perfetta + +[TRINST] +Tripla acrobazia folle + +[PRTRST] +Tripla acrobazia folle perfetta + +[QUINST] +Quadrupla acrobazia folle + +[PQUINS] +Quadrupla acrobazia folle perfetta + +[NOSTUC] +Nessuna acrobazia FOLLE effettuata + +[NOUNIF] +Acrobazie uniche effettuate + +[NOUNGM] +Acrobazie uniche complessive + +[NMISON] +Missioni provate + +[NMMISP] +Missioni superate + +[PASDRO] +Passeggeri scaricati + +[MONTAX] +Soldi guadagnati in taxi + +[DAYPLC] +Spesa giornaliera polizia + +[CRIMRA] +Livello criminalità: + +[GMSTOR] +Negozio dei giochi + +[PREBRF] +Briefing precedenti + +[CNTLS] +Comandi + +[MUSMEN] +Musica-FX + +[GAMSET] +Impostazioni + +[LANGUA] +Lingua + +[DSPLAY] +Video + +[DEBUGM] +Menu di debug + +[QUITOP] +Esci dalle opzioni + +[CONTRL] +Configurazione comandi + +[SET1EN] +Configurazione 1. Abilitata. + +[SET1] +Configurazione 1 + +[SET2EN] +Configurazione 2. Abilitata. + +[SET2] +Configurazione 2 + +[SET3EN] +Configurazione 3. Abilitata. + +[SET3] +Configurazione 3 + +[SET4EN] +Configurazione 4. Abilitata. + +[SET4] +Configurazione 4 + +[GOBACK] +Indietro + +[SOUND] +SONORO + +[MUSVOL] +Volume musica + +[SFXVOL] +Volume FX + +[SCROPT] +OPZIONI SCHERMO + +[CTRSCR] +Centra schermo + +[SCRFOR] +Formato schermo + +[GMSVLQ] +SALVA-CARICA-ESCI + +[GMREST] +Ricomincia partita + +[NOGMSV] +Puoi salvare solo al tuo nascondiglio. + +[DLFILE] +Elimina i file di Grand Theft Auto III + +[CHFILE] +SCEGLI IL FILE DA CARICARE + +[CHCDLD] +Choose Card to Load From + +[CDUNFR] +Card is unformatted. + +[CHFIDL] +SCEGLI IL FILE DA CANCELLARE + +[SVCONF] +SALVA CONFERMA + +[SVFNAM] +Il nome del tuo salvataggio è + +[SAVEDN] +Errore. Salvataggio non completato. + +[LANGSL] +SCELTA LINGUA + +[ENGLIS] +Inglese + +[GERMAN] +Tedesco + +[ITALIA] +Italiano + +[FRENCH] +Francese + +[SPAIN] +Spagnolo + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Parse Heap + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[CRRGON] +ShowCarRoadGroups On + +[CRGOFF] +ShowCarRoadGroups Off + +[CLZOON] +Show Cull Zones On + +[CLZOOF] +Show Cull Zones Off + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (teststuff) + +[UNFRM1] +UnFormatMemCard 1 (teststuff) + +[GORLEV] +Livello violenza + +[SICASS] +Stupro + +[SICSIC] +Stupratore + +[SCASSL] +Stupro selezionato + +[SCSCSL] +Stupratore selezionato + +[PRVMEN] +Briefing di missione precedente + +[DOSVGM] +Vuoi salvare la partita? + +[FORMEN] +Format Menu + +[MEMTST] +MemoryCardTest screen + +[REGCAR] +Register MemoryCard One + +[TEFONE] +Test Format MemCard One + +[TEUFON] +Test UnFormat MemCard One + +[CRROOT] +CreaDirRoot + +[CRLDIC] +Crea e carica icone + +[FLFSGF] +Riempi il primo file con fuffa + +[PUSAVE] +Salva solo il gioco + +[CHEVOK] +VerificaOgniSalvataggioOKB4 + +[SVGMON] +SalvaIlGioco + +[CNTSAV] +Impossibile salvare il gioco, sei in missione. + +[CNCSAV] +Impossibile salvare il gioco, sei in macchina. + +[CRMGSV] +Crea cartella magazine protetta + +[MGSVCN] +Cartella Magazine creata + +[MGSVNC] +Cartella Magazine non creata + +[YES] +Sì + +[NO] +No + +[X] +x + +[LAST] +Ultimo messaggio. + +[FEDS_XB] +Selezione + +[FEDS_ST] +Tasto START - RIPRENDI + +[FEST_OO] +su + +[FEC_TUC] +Torretta di controllo + +[FEC_SM3] +Attivazione missione speciale (tasto R3) + +[FEC_RS3] +Stazioni radio (tasto L3) + +[FEC_HO3] +Clacson (tasto L3) + +[DIAB1] +'TURISMO' + +[DIAB2] +'IO GRIDO, TU GRIDI' + +[DIAB3] +'BATTESIMO DEL FUOCO' + +[DIAB4] +'GROSSO E ARTERIOSO' + +[DIAB1_A] +El Burro ti vuole offrire un'opportunità. Vai alla cabina telefonica di Hepburn Heights se vuoi saperne di più. + +[DIAB1_C] +Sei un pilota provetto. Ripassa per la cabina telefonica, 'El Burro' potrebbe avere altro lavoro per te. + +[DIAB1_1] +~g~3..2..1.. VIA VIA VIA! + +[DIAB1_4] +~g~Procurati un'auto veloce e vai alla griglia di partenza. + +[DIAB1_3] +~r~Non riusciresti a vincere neanche una lotteria, FALLITO! + +[DIAB1_2] +~g~Congratulazioni, hai vinto con l'incredibile tempo di ~1~ secondi. + +[FIRST] +~g~Primo + +[SECOND] +~g~Secondo + +[THIRD] +~g~Terzo + +[FOURTH] +~g~Quarto + +[DIAB2_1] +~g~Prendi la valigia ad Harwood. + +[DIAB2_2] +~g~Trova un furgone dei gelati. + +[DIAB2_3] +~g~Parcheggia il furgone dei gelati al molo atlantico. + +[DIAB2_4] +~g~Premi il ~w~tasto ~k~~VEHICLE_HORN~ ~g~per attivare il campanello. + +[DIAB2_6] +~g~Premi il ~w~tasto ~k~~VEHICLE_HORN~ ~g~per attivare il campanello. + +[DIAB2_7] +~g~Premi il ~w~tasto ~k~~VEHICLE_HORN~ ~g~per attivare il campanello. + +[DIAB2_5] +~g~Esci dal furgone dei gelati e usa il telecomando per farlo esplodere. + +[YD1] +'BLING-BLING SCRAMBLE' + +[YD2] +'UZI RIDER' + +[YD3] +'CORSA TRA GANGSTER' + +[YD4] +'VENGA IL TUO REGNO' + +[YD_P] +King Courtney vuole parlarti. Vai alla cabina telefonica ad Aspatria! + +[YD1_A] +~w~Sono King Courtney. + +[YD1_A1] +~w~Alla mia cricca Yardie servirebbe un buon autista e tu hai un'ottima reputazione. + +[YD1_B] +~w~Vai in macchina alla discarica dalla parte opposta dello stadio e aspetta gli altri aspiranti. + +[YD1_C] +~w~Ho uomini di guardia in postazioni sparse per tutta Staunton. + +[YD1_D] +~w~Il primo pilota che raggiunge una postazione prende $1000, e cosi via fino alla tappa successiva. + +[YD1_D1] +~w~Se raggiungi per primo più postazioni degli altri piloti, potrei avere del lavoro per te. + +[YD1_E] +~g~Preparati alla gara! + +[YD1_F] +~g~Hai anticipato la partenza; mi piace il tuo stile! + +[YD1_G] +~r~È una GARA AUTOMOBILISTICA. Ti serve un'AUTO, idiota! + +[YD1GO] +~g~VIA! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +$1000! + +[Y1_1ST] +~g~Sei arrivato primo con ~1~ postazioni! + +[Y1_2ND] +~y~Sei arrivato secondo con ~1~ postazioni. ~y~Non male, ma non sei il migliore! + +[Y1_3RD] +~r~Sei arrivato terzo con ~1~ postazioni. ~r~Credevo avessi detto di essere in gamba! + +[Y1_LAST] +~r~Sei arrivato ultimo! ~r~Mi hai fatto perdere tempo, IDIOTA! + +[Y1_J1ST] +~y~Primo a pari merito con ~1~ postazioni. ~y~Bravo, ma devi essere il migliore per guidare per Queen Lizzy! + +[Y1_J2ND] +~r~Secondo a pari merito con ~1~ postazioni. Guidi come una scimmia impazzita! + +[Y1JLAST] +~r~Ultimo a pari merito! Sei più veloce con la lingua che con l'auto! + +[Y1_TEST] +AUTO IN ACQUA! + +[YD2_A] +~w~Devo vedere se sei in grado di eseguire certi lavoretti per me. + +[YD2_A1] +~w~Vediamo se ci si può fidare di te. + +[YD2_B] +~w~Due dei miei ragazzi saranno lì a momenti per accompagnarti in un giro: + +[YD2_B1] +~w~vedremo se sei davvero chi dici di essere. + +[YD2_C] +~w~Andiamo a fare un giro a Hepburn Heights: levaci di torno alcuni di quei luridi Diablo che mancano di rispetto a Queen Lizzy. + +[YD2_CC] +~w~Prendi, ti serviranno 'i ferri del mestiere'. + +[YD2_D] +~w~Dovrai guidare e sparare. Noi faremo in modo che tu non finisca al creatore. + +[YD2_E] +~w~Comincia a guidare! + +[YD2_F] +~r~Ci è scappato! Facciamo secco quel bastardo muso giallo!!! + +[YD2_G1] +~w~Hepburn Heights... Facciamo fuori qualche lurido Diablo... + +[YD2_G2] +~w~Ma ricorda: ~r~non pensare di uscire da quest'auto! + +[YD2_H] +~w~OK, riportaci nel territorio di Yardie! VIA VIA VIA!!! + +[YD2_L] +~w~Bel lavoro, Cecchino! + +[YD2_M] +~r~Ha distrutto la mia auto! Fallo secco! + +[YD2_N] +~w~Rimetti il culo in macchina! + +[YD3_A] +Devi rubare alcune auto delle gang avversarie + +[YD3_A1] +per poter colpire nel loro territorio. + +[YD3_B] +Mi serve una Sentinel della Mafia, + +[YD3_B1] +una Stinger della Yakuza e uno + +[YD3_B2] +Stallion dei Diablo per poter attaccare qualunque gang di Liberty. + +[YD3_C] +Parcheggiale nel garage a Newport e ricorda, + +[YD3_C1] +se le distruggi sono inutilizzabili! + +[YD3_D] +Etichetta testo aggiuntiva + +[YD3_E] +~r~Hai già rubato un'auto dei Diablo! + +[YD3_F] +~r~Hai già rubato un'auto della Mafia! + +[YD3_G] +~r~Hai già rubato un'auto della Yakuza! + +[YD3_H] +~g~Auto dei Diablo rubata! + +[YD3_I] +~g~Auto della Mafia rubata! + +[YD3_J] +~g~Auto della Yakuza rubata! + +[YD3_K] +~r~L'auto è quasi un rottame! Falla riparare! + +[YD3_L] +~g~Portala al ~p~garage! + +[YD3_M] +~r~Hai cappottato l'auto! Procuratene un'altra! + +[YD4_A] +Stammi a sentire! + +[YD4_A1] +Arriva fino a Bedford Point. + +[YD4_A2] +C'è della roba nascosta in una vecchia automobile che mi serve subito! + +[YD4_B] +LETTERA: Ho saputo che ti sei dato da fare. Beh mi sono data da fare anch'io. + +[YD4_C] +Credo sia ora che tu provi il vero potere dello 'SPANK'! Baci e abbracci, Catalina, con amore. + +[YD4_D] +PS: MUORI CANE BASTARDO, MUORI! + +[YD4_1] +Pazzi strafatti di SPANK! + +[YD4_2] +Distruggi i camion di SPANK! + +[HM_1] +'SOLDI E UZI' + +[HM_2] +'STERMINATOR' + +[HM_3] +'PRONTO A ESPLODERE' + +[HM_5] +'RISSA' + +[HOOD1_A] +Raggiungi il telefono pubblico dei giardini Wichita se vuoi parlare d'affari. + +[HM1_A] +Ehi, sono D-Ice dei Red Jack! + +[HM1_C] +Questi stronzetti scorrazzano per le strade con nient'altro che armi e SPANK nella testa. + +[HM1_3] +~g~I 'Nine' controllano il territorio dei giardini Wichita. + +[HM2_3] +Se colpisci i pneumatici, il maggiolino radiocomandato esploderà! + +[HM2_4] +Se supera la portata massima, il maggiolino radiocomandato esploderà! + +[HM2_5] +~r~Fuori dalla portata massima! + +[HM3_1] +~g~Vai al garage, ma stai attento: se l'auto viene danneggiata troppo, salterà in aria! + +[HM3_2] +~g~Riporta l'auto in ottime condizioni: nessun danno! + +[HM3_3] +~g~Fai riparare l'auto! + +[HM4_D] +~g~Prendi un veicolo! + +[HM4_E] +TESTO NON PIÙ NECESSARIO + +[HM4_1] +~g~Dirigiti nel posto dove il carico s'è rovesciato: dovrai raccogliere almeno 30 lingotti. + +[HM4_2] +~g~Ricorda di raggiungere il garage e depositare il carico quando il veicolo diventa troppo pesante e lento. + +[HM5_3] +~r~Dovevi usare solo una mazza da baseball! + +[HM5_4] +~r~Il tuo intermediario è morto! + +[MEA1] +'L'IMBROGLIONE' + +[MEA2] +'I LADRI' + +[MEA3] +'LA MOGLIE' + +[MEA4] +'L'AMANTE' + +[MEAT1_A] +Un amico mi ha detto che puoi risolvermi dei problemi. Vai alla cabina telefonica a Trenton se credi di essere la persona giusta. + +[MEA1_B3] +~g~Devi incontrare il direttore della banca. + +[MEA1_B6] +~g~Porta l'auto dal rottamatore per distruggere le prove: esci dall'auto e la gru la raccoglierà. + +[MEA1_1] +~r~Il direttore della banca è morto! + +[MEA1_2] +~r~Ti era stato detto di rottamare il veicolo! + +[MEA1_3] +~g~Esci dalla macchina! + +[MEA1_4] +~r~Ti sei dimenticato del direttore della banca! + +[MEA2_B3] +~g~Vai a incontrare i ladri. + +[MEA2_B4] +~g~Accompagnali alla fabbrica di cibo per cani Bitch'n' Dog. + +[MEA2_B6] +~g~Fai riverniciare l'auto per eliminare ogni prova. + +[MEA2_1] +~r~Ti era stato detto di far rottamare il veicolo! + +[MEA2_2] +~r~Un ladro è morto! + +[MEA2_4] +~r~Ti sei dimenticato un ladro! + +[MEA3_B3] +~g~Vai a prelevare la signora Chonks. + +[MEA3_B6] +~g~Prendi l'auto e scaricala in mare: questo cancellerà ogni prova. + +[MEA3_1] +~r~La moglie è morta! + +[MEA3_2] +~r~Dovevi scaricare il veicolo in mare! + +[MEA3_3] +~r~Ti sei dimenticato di della moglie! + +[MEA4_B3] +~g~Vai a prendere l'amante della moglie. + +[MEA4_B6] +È davvero troppo tardi per farlo, Marty. Ti ho dato una chance, ma adesso prenderò io il controllo del tuo business... + +[MEA4_1] +~r~Carlos è morto! + +[MEA4_3] +~r~Ti sei dimenticato di Carlos lo strozzino! + +[LOOK_A] +Tieni premuto il ~h~tasto ~k~~VEHICLE_LOOKLEFT~ ~w~o il ~h~tasto ~k~~VEHICLE_LOOKRIGHT~~w~ per guardare a ~h~sinistra~w~ o a ~h~destra~w~ mentre sei nel veicolo. Premi entrambi i tasti per guardare ~h~indietro~w~. + +[LOVE6_1] +~g~Adesso attira gli sbirri lontano dai magazzini! + +[LOVE6_2] +~r~Non sei riuscito ad attirare gli sbirri abbastanza lontano! + +[RM4_3] +~r~Il socio di Ray è fuggito! + +[RM6_C] +La CIA sembra interessata allo SPANK + +[RM6_C1] +e non vogliono che interferiamo con il Cartello. + +[C_PASS] +MINACCIA ELIMINATA! + +[CTUTOR] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Vigilante. + +[CTUTOR2] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Vigilante. + +[COPCART] +~g~Hai ~1~ secondi per tornare a un mezzo della polizia prima che termini la missione. + +[C_FAIL] +Missione Vigilante terminata! + +[C_CANC] +~r~Missione Vigilante annullata! + +[C_ESCP] +~r~Il sospetto è fuggito! + +[C_TIME] +~r~È scaduto il tuo tempo come tutore della legge! + +[C_VIGIL] +BONUS VIGILANTE! + +[A_FAIL2] +~r~La tua scarsa sollecitudine è stata fatale al paziente! + +[A_FAIL3] +~r~Il paziente è morto! + +[A_PASS] +Salvato! + +[F_FAIL2] +~r~Sei arrivato tardi! + +[A_COMP2] +Non ti stancherai mai! + +[RM2_M] +Se ti servono armi da fuoco, passa di qui e prendi ciò che ti serve dagli armadietti. + +[HEAL_A] +La tua ~h~salute~w~ è indicata in color arancione in alto a destra sullo schermo. + +[YD1_CNT] +~1~ su 15! + +[FM1_9] +~g~Lì davanti c'è il party: fai scendere Maria all'ingresso. + +[FM1_Y] +~w~Sai, non mi divertivo così tanto da una vita, e mi hai trattato proprio bene. Con molto rispetto. + +[FM1_AA] +~w~Oh, devo andare. Spero di rivederti. + +[NOCONTE] +Ricollega il controller analogico (DUALSHOCK#) o controller analogico (DUALSHOCK#2) all'ingresso controller 1 per continuare. + +[WRCONT] +Il controller collegato all'ingresso controller 1 non è supportato. Grand Theft Auto III richiede un controller analogico (DUALSHOCK#) o (DUALSHOCK#2). + +[WRCONTE] +Il controller collegato all'ingresso controller 1 non è supportato. Grand Theft Auto III richiede un controller analogico (DUALSHOCK#) o controller analogico (DUALSHOCK#2). + +[WRONGCD] +Disco errato. Inserisci il disco corretto. + +[NOCD] +Il vano cassetto del disco è vuoto. Inserisci il disco. + +[OPENCD] +Il vano cassetto del disco è aperto. Chiudi il vano. + +[CDERROR] +Errore di lettura del DVD Grand Theft Auto III. + +[RESTART] +Avvio di una nuova partita + +[GA_3] +Niente più sconti. $1000 per la riverniciatura! + +[GA_1] +Wow! Non tocco niente di COSÌ caldo! + +[GA_1A] +Torna quando non sarai così occupato... + +[S_PROM2] +Nel garage qua accanto puoi parcheggiare un veicolo quando salvi la partita. + +[STOCK] +scorte esaurite + +[FM1_O] +~w~Credo si trovi alla stazione vicino al porto di Chinatown. + +[EBAL_B] +Siamo arrivati, togliamoci dalla strada e cerchiamo di cambiarci d'abito! + +[EBAL_G] +Questo è il club Luigi's. Passiamo dal retro attraverso la porta di servizio. + +[AM4_3] +Tu devi essere il nuovo tuttofare di Asuka! + +[AM4_4] +Hai i soldi? Tutto qui? + +[AM4_5] +So cosa pensi, un altro sbirro corrotto. + +[AM4_6] +Beh, il mondo è corrotto. + +[AM4_7] +Solo perché ho perso dei partner, quegli idioti degli Affari Interni hanno cominciato a curiosare. + +[AM4_8] +Immagino che sentano puzza di bruciato. + +[AM4_9] +Beh, questa città è una grande fogna. + +[AM4_10] +Mi servirà aiuto anche fuori dai sindacati. + +[AM4_11] +Se sei interessato, sai dove trovarmi. + +[CAM_A] +Premi il ~h~tasto ~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ per cambiare le ~h~modalità di visuale ~w~quando sei a piedi o in un veicolo. + +[CAM_B] +Premi il ~h~tasto direzionale in alto~w~ e ~h~in basso~w~ per cambiare le ~h~modalità di visuale ~w~quando sei a piedi o in un veicolo. + +[KM2_1] +~g~Ripara l'auto, sarà come nuova. + +[LM3_6] +Joey... + +[LM3_6A] +Mi farai giocare ancora col tuo pistone? + +[LM3_9A] +Potrebbe esserci del lavoro per te. + +[LM3_9B] +OK? + +[AWAY2] +~r~Sono scappati. + +[AWAY] +~r~È scomparso nel nulla! + +[JM6_1] +Raggiungi la banca sulla strada principale. + +[GA_6B] { re3 change } +Parcheggiala, innesca la bomba schiacciando il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ e BATTITELA! + +[GA_7B] { re3 change } +Innescala col ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~. La bomba esploderà quando si tenterà di avviare il motore. + +[BAT1] +~g~Prendi la mazza! + +[EBAL_O] +Se andrà tutto liscio, forse potrei darti altro lavoro. Adesso fuori di qui! + +[HELP9_B] +Premere il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile di precisione. + +[HELP9_C] +Premere il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile di precisione. + +[JM6_8] +~r~Hai perso tutti i rapinatori! + +[COLT_IN] +La pistola è adesso disponibile da AmmuNation! + +[TAXI2] +~r~Tempo scaduto! + +[TAXI3] +~r~Il passeggero è fuggito terrorizzato! + +[TAXI7] +~r~La tua auto è distrutta, falla riparare. + +[TAXI4] +Tariffa completa! + +[TAXI5] +BONUS VELOCITÀ! + +[TAXI6] +Missione taxi terminata + +[FRANGO] +~g~Salvatore vuole che tu aiuti prima Toni ad affrontare le Triadi! + +[PAGEB12] +Tangente per la Polizia consegnata al nascondiglio + +[PAGEB13] +Salute consegnata al nascondiglio + +[PAGEB14] +Adrenalina consegnata al nascondiglio + +[KM1_4] +~g~Ti serve un'auto della polizia per svolgere il lavoro! + +[CAT1_B] +Porta 500.000$ alla Villa a Cedar Grove. + +[JM2_C] +Ha un chiosco di spaghetti cinesi a China Town. + +[RM6_1] +Eccoti la chiave di un deposito. + +[RM6_2] +Troverai del denaro e alcune 'dotazioni' da utilizzare nei momenti difficili. + +[RM6_3] +Ci vediamo. + +[FE_INIP] +Inizializzazione e caricamento menu pausa... Un momento. + +[FESZ_CA] +Annulla + +[FESZ_QU] +Esci + +[FESZ_L1] +Partita salvata con successo! + +[FESZ_L2] +Il nome del file salvato è: + +[FESZ_OK] +OK + +[FES_LGA] +Carica partita + +[FES_NGA] +Nuova partita + +[FES_CAN] +Annulla + +[FESZ_QL] +Tutti i progressi della partita attuale non ancora salvati andranno perduti. Vuoi procedere con il caricamento? + +[FESZ_QD] +Vuoi eliminare questa partita salvata? + +[FESZ_QO] +Vuoi procedere con la sovrascrittura di questa partita salvata? + +[FESZ_QR] +Sei sicuro di voler iniziare una nuova partita? Tutti i progressi fatti fino all'ultimo salvataggio andranno perduti. Vuoi procedere? + +[FESZ_QS] +VUOI PROCEDERE CON IL SALVATAGGIO? + +[SLONFP] +Ingresso 1. File protetto. + +[T4X4_1] +'CAMPO DEL PATRIOTA' + +[T4X4_2] +'UN GIRO NEL PARCO' + +[T4X4_3] +'AFFERRATO' + +[MM_1] +'MASSACRO NEL PALAZZO' + +[T4X4_1A] +~g~Hai ~y~5 minuti~g~ per attraversare ~y~15~g~ punti di controllo. ~g~Puoi attraversarli in ~y~QUALSIASI ORDINE~g~. + +[T4X4_1B] +~1~ su 15! + +[T4X4_1C] +~y~ATTRAVERSA~g~ il primo punto di controllo per attivare il timer. ~g~Ogni punto di controllo ti fornirà ~y~20 SECONDI~g~ extra. + +[T4X4_2A] +~g~Hai ~y~2 minuti~g~ per attraversare ~y~12~g~ punti di controllo!!! ~g~Puoi attraversarli in ~y~QUALSIASI ORDINE~g~. + +[T4X4_2B] +~1~ su 12! + +[T4X4_2C] +~y~ATTRAVERSA~g~ il primo punto di controllo per attivare il timer. ~g~Ogni punto di controllo ti fornirà ~y~10 SECONDI~g~ extra. + +[T4X4_3A] +~g~Hai ~y~5 minuti~g~ per attraversare ~y~20~g~ punti di controllo!!! ~g~Puoi attraversarli in ~y~QUALSIASI ORDINE~g~. + +[T4X4_3B] +~y~ATTRAVERSA~g~ il primo punto di controllo per attivare il timer. ~g~Ogni punto di controllo ti fornirà ~y~10 SECONDI~g~ extra. + +[T4X4_3C] +~1~ su 20! + +[T4X4_F] +~r~Sei scappato! Troppo difficile per te? + +[MM_1_A] +~g~Hai ~y~2 minuti~g~ per attraversare ~y~20 punti di controllo~g~ nel palazzo! ~g~Puoi attraversarli in ~y~QUALSIASI ORDINE~g~. + +[MM_1_B] +~1~ su 20! + +[MM_1_C] +~g~Sono 20 secondi più ~y~5 secondi~g~ per ogni punto di controllo. ~g~Il timer partirà ~y~IMMEDIATAMENTE~g~. + +[FM2_14] +~r~Ti sei avvicinato troppo e hai spaventato Ricciolino! + +[FM2_15] +~g~Non ti avvicinare troppo o Ricciolino si insospettirà! + +[UPSIDE] +~r~Hai cappottato la macchina! + +[FM2_16] +PAURIMETRO: + +[LM3_11] +~g~Misty non sale su un autobus, prendi un altro veicolo! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Camion dei pompieri + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mulo + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulanza + +[FBICAR] +F.B.I. + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Polizia + +[ENFORCR] +Cellulare + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Caserma OL + +[TRAIN] +Treno + +[HELI] +Elicottero + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +Bandito RC + +[BELLYUP] +Triad + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Mafia + +[YARDICR] +Yardie + +[YAKUZCR] +Yakuza + +[DIABLCR] +Diablo + +[COLOMCR] +Cartel + +[HOODSCR] +Hoods + +[AEROPL] +Aeroplano + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Distanza percorsa a piedi (miglia) + +[FEST_DC] +Distanza percorsa in auto (miglia) + +[FESTDFM] +Distanza percorsa a piedi (m) + +[FESTDCM] +Distanza percorsa in auto (m) + +[FEST_R1] +Campo del Patriota (secondi) + +[FEST_R2] +un Giro nel Parco (secondi) + +[FEST_R3] +Afferrato! (secondi) + +[FEST_RM] +Massacro Multi-livello (secondi) + +[FEST_LS] +Persone salvate in ambulanza + +[FEST_CC] +Missione Vigilante - Criminali uccisi + +[FEST_FE] +Totale incendi spenti + +[FEST_LF] +Volo più lungo nel Dodo + +[FEST_BD] +Disinnesco bomba - Miglior tempo + +[FEST_RP] +Violenze eseguite + +[FEST_MP] +Missioni eseguite + +[FEST_BB] +Bling-bling Scramble: + +[FEST_H0] +Maggior numero di checkpoints + +[FEST_GC] +Auto delle gang distrutte: + +[FEST_H1] +Distruzione Diablo + +[FEST_H2] +Massacro Mafioso + +[FEST_H3] +Calamità al Casinò + +[FEST_H4] +Demolizioni Rumpo + +[USJI1] +TESTO NON PIÙ RICHIESTO + +[USJI2] +TESTO NON PIÙ RICHIESTO + +[USJI3] +TESTO NON PIÙ RICHIESTO + +[USJ] +BONUS PER ACROBAZIA UNICA! + +[SPRAY] +Porta l'auto dal carrozziere per perdere il ~h~livello di sospetto~w~, ~h~riparare~w~ e ~h~riverniciare~w~ il veicolo. Costo - ~h~1000$~w~. + +[HM1_1] +~g~Fredda 20 Purple Nine in 2 minuti e 30 secondi. + +[KM1_8A] { re3 change } +Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per ~h~attivare la bomba~w~: ricorda di toglierti di mezzo! + +[KM1_8D] { re3 change } +Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per ~h~attivare la bomba~w~: e ricorda di toglierti di mezzo! + +[KM1_12] +~g~Portalo nel dojo, ma prima sbarazzati degli sbirri! + +[RATNG1] +Borsaiolo + +[RATNG2] +Prepotente + +[RATNG3] +Teppista + +[RATNG4] +Attaccabrighe + +[RATNG5] +Gorilla + +[RATNG6] +Autista + +[RATNG7] +Aiutante + +[RATNG8] +Riparatore + +[RATNG9] +Socio + +[RATNG10] +Uomo delle pulizie + +[RATNG11] +Assassino + +[RATNG12] +Braccio destro + +[RATNG13] +Boia + +[RATNG14] +Capo + +[RATNG15] +Boss + +[1010] +~r~Il tuo veicolo si è ribaltato + +[1011] +~r~Il tuo veicolo si è ribaltato + +[1012] +~r~Il tuo veicolo si è ribaltato + +[1013] +~r~Il tuo veicolo si è ribaltato + +[1014] +~r~Il tuo veicolo si è ribaltato + +[JM4_10] +OK, ragazzo. Accompagnami prima alla lavanderia di Chinatown, devo occuparmi di alcuni affari. + +[JM4_11] +La donna della lavanderia non mi ha pagato i soldi del pizzo. + +[JM4_12] +E bada all'auto: Joey ha appena riparato questo rottame. + +[JM4_13] +Quindi, niente colpi di testa, OK? + +[KM4_11] +~g~Riporta i soldi al Casinò! + +[FEF_BR2] +Ritrovalo leggendo i sommari di missione che hai già ricevuto. + +[TRAIN_1] +Stazione Kurowski + +[TRAIN_2] +Stazione Rothwell + +[TRAIN_3] +Stazione Baillie + +[SUBWAY1] +Stazione Portland + +[SUBWAY2] +Stazione Rockford + +[SUBWAY3] +Stazione Sud Staunton + +[SUBWAY4] +Capolinea Shoreside + +[MEA4_2] +~r~Marty Chonks è morto! + +[SPRAY1] +Porta l'auto dal carrozziere per perdere il ~h~livello di sospetto~w~, ~h~riparare~w~ e ~h~rivernicirea~w~ il veicolo. Costo - ~h~1000$~w~. Per questa volta è gratis. + +[JM4_A] +Sì, lo so Toni, l'ho sistemata per bene. Il motore fa le fusa, non so se mi spiego. + +[JM4_5] +Ripassa più tardi e darò loro qualcosa da lavare... i panni macchiati del loro sangue! + +[AMMU_A] +Luigi dice che ti serve 'un ferro del mestiere'... + +[AMMU_B] +Joey mi ha detto di equipaggiarti... + +[AMMU_C] +Vai sul retro del negozio. Ti ho lasciato una calibro nove in cortile. + +[AMMU_D] +Ho tutto quello che ti serve per la tua auto-difesa. + +[AMMU_E] +Vuoi anche una licenza? + +[AMMU_F] +Non mi serve che mi mostri i documenti, hai una faccia fidata. + +[DETON] +DETONAZIONE: + +[DRIVE_A] { re3 change } +Seleziona un Uzi quando entri in un veicolo, poi guarda a destra o a sinistra e premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare. + +[DRIVE_B] { re3 change } +Seleziona un Uzi quando entri in un veicolo, poi guarda a destra o a sinistra e premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare. + +[RECORD] +~g~NUOVO RECORD!!! + +[NRECORD] +~r~NESSUN NUOVO RECORD! + +[RCHELP] { re3 change } +Premi il tasto ~k~~VEHICLE_FIREWEAPON~ o dirigi l'auto radiocomandata contro i pneumatici di un veicolo per provocarne la detonazione. + +[RCHELPA] { re3 change } +Premi il tasto ~k~~VEHICLE_FIREWEAPON~ o dirigi l'auto radiocomandata contro i pneumatici di un veicolo per provocarne la detonazione. + +[RC_1] +Hai 2 minuti per far esplodere quante più auto dei Diablo possibile! + +[RC_2] +Hai 2 minuti per far esplodere quante più auto Mafiose possibile! + +[RC_3] +Hai 2 minuti per far esplodere quante più auto della Yakuza possibile! + +[RC_4] +Hai 2 minuti per far esplodere quante più auto degli Yardie possibile! + +[RC_5] +Hai 2 minuti per far esplodere quante più auto degli Hood possibile! + +[RC_6] +Hai 2 minuti per far esplodere quante più auto del Cartello possibile! + +[RAMPAGE] +VIOLENZA!! + +[RAMP_P] +VIOLENZA COMPIUTA! + +[RAMP_F] +VIOLENZA FALLITA + +[PAGE_00] +. + +[PAGE_01] +Uccidi ~1~ Diablo in 120 secondi! + +[PAGE_02] +Distruggi ~1~ veicoli in 120 secondi! + +[PAGE_03] +Uccidi ~1~ Mafiosi in 120 secondi! + +[PAGE_04] +Uccidi ~1~ elementi della Triade in 120 secondi! + +[PAGE_05] +Uccidi ~1~ elementi della Triade in 120 secondi! + +[PAGE_06] +Distruggi ~1~ veicoli in 120 secondi! + +[PAGE_07] +Fai saltare la testa a ~1~ Yardie in 120 secondi! + +[PAGE_08] +Brucia ~1~ elementi della Yakuza in 120 secondi! + +[PAGE_09] +Distruggi ~1~ veicoli in 120 secondi! + +[PAGE_10] +Distruggi ~1~ veicoli in 120 secondi! + +[PAGE_11] +Annienta ~1~ Yardie in 120 secondi! + +[PAGE_12] +Carbonizza ~1~ elementi della Yakuza in 120 secondi! + +[PAGE_13] +Fai esplodere ~1~ Yardie in 120 secondi! + +[PAGE_14] +Fai fuori ~1~ ColombianI in 120 secondi! + +[PAGE_15] +Polverizza ~1~ Hood in 120 secondi! + +[PAGE_16] +Distruggi ~1~ veicoli in 120 secondi! + +[PAGE_17] +Investi in macchina ~1~ Colombiani in 120 secondi! + +[PAGE_18] +Distruggi guidando ~1~ veicoli in 120 secondi! + +[PAGE_19] +Stacca la testa a ~1~ Colombiani in 120 secondi! + +[PAGE_20] +Decapita ~1~ Hood in 120 secondi! + +[JOEY_1] +TESTO NON PIÙ RICHIESTO + +[JM1_A] +Ehi, sono stufa, quand'è che si passa ai fatti? + +[JM1_B] +Tra un minuto, cocca, ho una cosetta da sbrigare. + +[JM1_C] +Ho un lavoretto per te, amico. + +[JM1_D] +I fratelli Forelli mi devono soldi da troppo tempo + +[JM1_E] +e occorre insegnargli un po' di rispetto. + +[JM1_F] +'Labbra' Forelli si sta ingozzando come un porco al St Mark's Bistro, + +[JM1_G] +quindi rubagli l'auto e portala nell'armeria di 8-Ball ad Harwood. + +[JM1_H] +Conosci 8-Ball, vero? + +[JM1_I] +Dopo che sarà stata installata una bomba, parcheggia l'auto dove l'hai trovata. + +[JM1_J] +Poi rilassati e goditi lo spettacolo. + +[JM1_K] +Ma fai in fretta, non starà a pranzo una vita. + +[CAT2_A1] +Forza, brutta deficiente! + +[CAT2_A] +La domanda è: sei venuto a salvare Maria o a riportarmi indietro? + +[CAT2_B] +Beh, stammi a sentire, + +[CAT2_B2] +spararti sarà un piacere, ma uscire con te è stato solo lavoro. + +[CAT2_C] +Sei il mio peccinno amigo! + +[CAT2_D] +Butta qui la grana. + +[CAT2_E] +Ti sei dato molto da fare! + +[CAT2_E2] +Ma non hai imparato che non devi fidarti di me. + +[CAT2_E3] +Ammazzate quell'idiota. + +[CAT2_J] +Fai decollare questo affare! + +[HM5_1] +Ehi, Ice mi ha detto che saresti arrivato. Ecco le regole. Solo mazze. Niente pistole, niente auto. + +[HM5_5] +È una battaglia per il rispetto, chiaro?. + +[HELP14] +Per raccogliere le armi, passaci sopra a piedi. Non puoi raccoglierle mentre sei in un veicolo. + +[CRUSH] +Parcheggia nell'area contrassegnata ed esci dal veicolo. Il veicolo verrà rottamato. + +[DIAB2_B] +Un gruppo di gentaglia mi ha minacciato di asportare il mio attributo di tutto rispetto se non gli sgancio dei soldi. + +[DIAB2_C] +Hanno minacciato l'uomo sbagliato, amigo. + +[DIAB2_D] +Hanno un debole per il gelato. + +[DIAB2_E] +Prendi la bomba che ho nascosto ad Harwood, + +[DIAB2_F] +dirotta il furgone dei gelati durante i suoi spostamenti + +[DIAB2_G] +e attira quegli idioti verso il loro destino con il campanello. + +[DIAB2_H] +Si nascondono in un magazzino ad Atlantic Quay. + +[DIAB3_A] +Qualche insolente della Triade ha rubato la mia stupenda macchina ieri notte, + +[DIAB3_B] +l'ha distrutta e carbonizzata. + +[DIAB3_C] +Uno dei miei più cari escrementi asinini si trovava nel bagagliaio + +[DIAB3_D] +veri oggetti da collezione, insostituibili amico mio. + +[DIAB3_E] +Ho nascosto un'arma lancinante alla periferia di Chinatown. + +[DIAB3_F] +Prendila e insegna ai vandali della Triade a temere l'ira super-dotata di El Burro! + +[DIAB3_1] +UCCIDI 25 ELEMENTI DELLA TRIADE + +[DIAB4_A] +Un ladruncolo approfittatore ha rubato un furgone carico di copie delle mie ultime pubblicazioni! + +[DIAB4_B] +Ma quell'idiota fatto di SPANK ha lasciato lo sportello posteriore aperto, + +[DIAB4_C] +per cui tutto quello splendido capolavoro patinato + +[DIAB4_D] +con intriganti foto pornografiche è stato sparso per tutta Liberty! + +[DIAB4_E] +Prendi il furgone e segui la scia di copie di Un Asino si fa Dallas volume 1, 2 e 3 + +[DIAB4_F] +raccogliendole lungo la strada. + +[DIAB4_G] +Quando raggiungi quel ladruncolo imbottito di SPANK, fallo fuori! + +[DIAB4_H] +Poi consegna il mio materiale asinino a Riviste XXX nel quartiere a luci rosse. + +[DIAB4_1] +~g~Porta il furgone nel retro di Riviste XXX. + +[HM1_E] +Devi far vedere a queste depravate come si fa una vera sparatoria. + +[HM1_H] +Togli di torno questi Nine! + +[HM2_A] +Questi Nine mi stanno causando problemi. + +[HM2_B] +Queste stronze hanno auto blindate e adesso stanno spacciando SPANK... + +[HM2_C] +e lo smerciano senza paura ai miei fratelli. + +[HM2_D] +C'è un'auto parcheggiata in strada. + +[HM2_E] +Dentro c'è della roba che metterà le sorelline fuori combattimento... + +[HM3_A] +Qualche bastardo ha messo una bomba nella mia auto. + +[HM3_B] +Se perdo l'auto, la mia reputazione nel giro sarà finita. + +[HM3_C] +Prendi la mia macchina e portala al garage a St. Marks, chiaro? + +[HM3_D] +Fagli disinnescare la bomba, lascia che se ne occupino loro. + +[HM3_E] +Il tempo corre e l'ordigno è tutto incasinato. + +[HM3_F] +Se becchi una buca potrebbe saltare tutto in aria. + +[HM3_G] +Adesso muoviti! + +[HM4_A] +Ehi, un aereo federale portavalute si è appena schiantato all'aeroporto Francis. + +[HM4_B] +C'è del platino sparso per tutta la pista. + +[HM4_C] +Prendi un'auto e raccogline quanto ne puoi! + +[HM4_F] +Puoi scaricare il malloppo in uno dei miei garage. + +[HM4_G] +Il platino è molto pesante e rallenterà un po' l'automobile. + +[HM4_H] +Quindi fai diverse consegne nel garage. + +[HM5_A] +Quei Nine sono rimasti in una manciata di rognosi... + +[HM5_B] +ma insistono ancora. + +[HM5_C] +Ci siamo accordati per uno scontro. + +[HM5_D] +Un gruppo dei loro contro due di noi, anzi... + +[HM5_E] +due di te. + +[HM5_F] +Ti accompagnerei, ma... + +[HM5_G] +non sarò in libertà vigilata per altri tre mesi. + +[HM5_H] +Tu mi capisci, vero? + +[HM5_I] +Vai a trovare il mio fratellino, + +[HM5_J] +Ti accompagnerà sul luogo della rissa, amico. + +[MEA1_B] +Mi chiamo Chonks, Marty Chonks. + +[MEA1_C] +Gestisco la fabbrica di cibo per cani Bitch'n'Dog qui vicino. + +[MEA1_D] +Ho dei problemi finanziari, ma in fondo chi non ne ha? + +[MEA1_E] +Più tardi mi incontrerò col direttore della mia banca. + +[MEA1_F] +È un imbroglione schifoso che continua a far crescere gli interessi di un prestito per arricchirsi. + +[MEA1_G] +Usa la mia auto, vallo a prendere e portalo qui. + +[MEA1_H] +Ho una sorpresina per quella maledetta sanguisuga! + +[MEA2_A] +Ho assunto dei ladri per entrare nel mio appartamento... + +[MEA2_C] +Quei ladruncoli bastardi minacciano di dire tutto all'assicurazione, + +[MEA2_D] +se non do loro una percentuale. + +[MEA2_E] +Roba da non credere! + +[MEA2_F] +Ho parcheggiato un'auto dietro i cancelli della fabbrica. + +[MEA2_G] +Usala e valli a prelevare nel loro territorio nel quartiere a luci rosse. + +[MEA2_H] +Poi portali qui in fabbrica così che comprendano il mio punto di vista. + +[MEA3_A] +L'azienda farà bancarotta se non riesco a procurarmi in fretta parecchi contanti. + +[MEA3_B] +Mia moglie ha un'assicurazione e l'unica cosa che sa fare è sperperare i miei soldi. + +[MEA3_C] +Ho lasciato un'auto al solito posto. + +[MEA3_D] +Vai a prendere mia moglie al salone di bellezza Classic Nails e accompagnala alla fabbrica. + +[MEA4_A] +Maledizione, sono nei guai! + +[MEA4_B] +Sembra che mia moglie avesse una storia con un tipo a cui devo dei soldi. + +[MEA4_C] +È molto arrabiato e vuole vendetta! + +[MEA4_E] +Lui si aspetta che cercherò di patteggiare... + +[MEA4_F] +ma io credo che... + +[MEA4_G] +i cani di Liberty assaggeranno dei bocconcini extra questo mese! + +[WELCOME] +BENVENUTI A + +[HM1_2] +~g~Prendi un veicolo e ricorda: contano solo le uccisioni fatte con l'Uzi mentre guidi! + +[HELP8_B] +Premi il ~h~tasto ~k~~PED_SNIPER_ZOOM_IN~ ~w~per ~h~zoomare ~w~col fucile e il~h~ tasto ~k~~PED_SNIPER_ZOOM_OUT~~w~ per ~h~allargare il campo~w~. + +[LRQC_1] +Asuka e io dobbiamo parlare, OK? + +[LRQC_2] +Perché non ti fai un giro? + +[LRQC_3] +Ti servira un posto dove nasconderti. + +[LRQC_4] +C'è un magazzino al confine di Bellville che farà al caso tuo. + +[LRQC_5] +Ritorna qui nel mio appartamento quando sei pronto, + +[LRQC_6] +Così possiamo chiacchierare un po'. + +[JM6_5] +~g~Ti serve un veicolo per la fuga, idiota! + +[JM2_F] +Se ti serve un'arma, vai sul retro di AmmuNation dal lato opposto della metropolitana. + +[LOVE4_7] +~g~C'è un cantiere edile a Staunton Island, forse hanno portato lì il pacco. + +[LOVE4_8] +~g~Ti servirà un'auto per accedere al garage. + +[TSCORE] +GUADAGNI: ~1~$ + +[AM1_9] +~r~Salvatore è fuggito di nuovo dentro il club Luigi's! + +[AM1_6] +~g~Se ti aggiri attorno al club Luigi's, la Mafia ti avvisterà! + +[TM2_3] +~g~È una trappola! Falli fuori tutti!!! + +[FM4_1] +Sono Maria. L'auto è una trappola! Incontriamoci alla rampa a sud del ponte Callahan. + +[JM1_7] +~g~Chiudi la portiera! Lo noterà! + +[KM5_1] +~g~TRITATO DI SPACCIATORE! + +[KM5_6] +~g~Devi uccidere almeno 8 spacciatori Yardie. + +[KM5_7] +~g~Uccidili in fretta! Dopo aver venduto lo SPANK spariscono dalla circolazione. + +[RM3_8] +~r~Quell'auto è un tranello!!! + +[LM3_8] +Ehi, sono Joey. + +[LM3_9] +Luigi mi ha detto che sei affidabile, quindi torna più tardi, + +[KM3_5] +~g~Suona il clacson per avviare l'affare. + +[LOVE7] +LA SCOMPARSA DI LOVE + +[LOVE2_5] +~g~Kenji è un tritato di carne! Allontanati da Newport e abbandona l'auto! + +[AS2_11] +~g~~1~ SU 9! + +[GARAGE1] +~g~Esci dal veicolo ed esci fuori. + +[KM3_11] +~g~Il Cartello è stato attaccato e la valigia non è stata recuperata. + +[KM3_12] +~g~Uccidi tutti i Colombiani, distruggi i veicoli e recupera la valigia. + +[KM3_13] +~g~Riconsegna la valigia al Casinò. + +[RM5_6] +~g~Ci è scappato! Spaccagli la corazza con un veicolo o con una bella esplosione! + +[PBOAT_1] { re3 change } +Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare con i cannoni della barca. + +[PBOAT_2] { re3 change } +Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare con i cannoni della barca. + +[DIAB1_B] +Sono El Burro dei Diablo. + +[DIAB1_D] +Sei da poco a Liberty, ma ti sei già guadagnato una reputazione in città. + +[DIAB1_E] +C'e una corsa d'auto con inizio alla vecchia scuola vicino al ponte Callahan. + +[DIAB1_F] +Procurati un mezzo, il primo che attraversa tutti i posti di blocco vince il premio. + +[HM2_1] { re3 change } +Usa i maggiolini radiocomandati per distruggere le auto blindate. Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per farli esplodere. + +[HM2_1A] { re3 change } +Usa i maggiolini radiocomandati per distruggere le auto blindate. Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per farli esplodere. + +[HM2_2] +~r~Non sei riuscito a distruggere tutte le auto blindate! + +[HM2_6] +~g~Auto blindata distrutta! + +[RM3_A] +Conosco un uomo molto importante in città, di manica larga, + +[RM3_H] +con gusti, diciamo, esotici e i soldi per permetterseli. + +[RM3_B] +È coinvolto in un processo e l'accusa ha delle sue foto piuttosto imbarazzanti + +[RM3_C] +in cui appare in un party all'obitorio. + +[LOVE6_A] +Una lezione d'affari, amico. + +[LOVE6_E] +Se possiedi qualcosa di unico, il mondo intero cercherà di strappartelo dalle mani... + +[LOVE6_C] +Squadre speciali di polizia hanno circondato la zona attorno al mio socio e al pacco. + +[LOVE6_D] +Vai li, prendi il furgone e fuggi per attirarli dietro di te. + +[LOVE6_F] +Tienili occupati, così che lui possa sfuggire! + +[AM3_C] +Probabilmente quando lo leggerai questo sarà in alto mare! Ruba un'imbarcazione della polizia e mandalo a picco! + +[FESZ_UC] +ANNULLA + +[FEDS_SM] +L1,R1-CAMBIA MENU + +[FEDS_AS] +;=-CAMBIA SELEZIONE + +[FEDSAS2] +<>-CAMBIA SELEZIONE + +[FEDS_SS] +L1,R1-CAMBIA SELEZIONE + +[FEDSSC1] +;-SCORRIMENTO RAPIDO + +[FEDSSC2] +=-FERMA SCORRIMENTO + +[MEA2_3] +~g~Riporta l'auto alla fabbrica. + +[RM1_3] +~r~McAffrey è scappato! + +[RM1_4] +~g~Hai usato tutte le granate! Procuratene altre da AmmuNation! + +[RM1_5] +~g~Torna indietro e incendia il rifugio! + +[RM6_4] +~g~Vai al nascondiglio e preleva la roba di Ray. + +[RM6_5] +~g~La CIA ha messo il ponte sotto sorveglianza, trova un altro punto d'accesso. + +[HM2_F] +e distruggi tutte le loro attrezzature blindate. + +[HM_4] +'CORSA AL LINGOTTO' + +[MEA2_B5] +TESTO NON PIÙ NECESSARIO + +[MEA1_B5] +TESTO NON PIÙ NECESSARIO + +[MEA3_B5] +TESTO NON PIÙ NECESSARIO + +[MEA4_B7] +ma se entri un attimo nel mio ufficio... + +[MEA3_B4] +Marty vuole vedermi? È meglio che si sbrighi perché devo acconciarmi i capelli. + +[KM3_7] +È un esperto in trappole della Yakuza! + +[FES_LOF] +Caricamento fallito. + +[P1INSA] +Ingresso 1. memory card (PS2) inserita. ~1~ K di spazio disponibile per salvare. ~1~ K necessari. + +[P1INSN] +Ingresso 1. memory card (PS2). Spazio disponibile insufficiente. Elimina alcuni file. + +[FES_SLO] +SALVA FILE + +[FES_ISC] +CORROTTO + +[FESZ_TI] +SALVA Z1 + +[FESZ_SA] +Salva partita + +[P1NOIN] +Ingresso 1. memory card (PS2) non inserita + +[P1INSE] +Ingresso 1. memory card (PS2) inserita. + +[MC_LDFL] +Caricamento fallito! + +[MC_NWRE] +Partita in fase di riavvio. + +[LOVE6_3] +~g~Hai ~1~ secondi per tornare alla Securicar prima di fallire la missione. + +[LOVE6_4] +~r~Hai distrutto la Securicar da depistaggio! + +[HELP1] +Fermati nel centro del segnale blu. + +[HELP12] +Cammina nel centro del segnale blu per attivare una missione. + +[HJSTAT] +Distanza: ~1~.~1~m Altezza: ~1~.~1~m Ribaltamenti: ~1~ Rotazioni: ~1~_ + +[HJSTATW] +Distanza: ~1~.~1~m Altezza: ~1~.~1~m Ribaltamenti: ~1~ Rotazioni: ~1~_ E che grande atterraggio! + +[DIAB1_5] +TEMPO DI GARA: + +[LOVE3_4] +~r~Hai distrutto l'aereo!!! + +[F_FAIL1] +Missione Camion dei pompieri terminata. + +[F_CANC] +~r~Missione Pompieri annullata! + +[F_EXTIN] +INCENDI: + +[A_COMP1] +Missioni Infermieri completate! + +[A_CANC] +~r~Missione Infermieri annullata! + +[A_COMP3] +Missioni Infermieri completate! Ora non ti stancherai mai mentre corri! + +[ATUTOR] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Infermieri. + +[ATUTOR3] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Infermieri. + +[ALEVEL] +Missione Infermieri Livello ~1~ + +[A_FAIL1] +Missione Infermieri terminata. + +[FEST_HA] +Missione Infermieri - Livello Max. + +[A_SAVES] +PERSONE SALVATE: ~1~ + +[C_KILLS] +CRIMINALI UCCISI: ~1~ + +[HM1_B] +Ho un problema. Stanno cercando di farsi gioco di me. + +[AM2_A] +La morte di Salvatore è un'ottima notizia, + +[AM2_A2] +sei un killer efficiente. Mi piace come qualità in un uomo. + +[AM2_B] +Lui è mio fratello Kenji. + +[AM2_C] +Asuka ha un lavoretto per te: quando hai finito, passa al Casinò così parliamo. + +[AM2_D] +Proprio come Kenji, che cerca sempre di usare i miei giocattoli. + +[AM2_E] +La mia fonte alla polizia mi ha informato che la Mafia sta studiando le nostre operazioni nella città + +[AM2_E2] +nel tentativo di scovarti. + +[AM2_F] +Non possiamo continuare le nostre operazioni fino a quando il problema non verrà risolto. + +[AM2_G] +Elimina questi insulsi spioni e termina questa vendetta una volta per tutte. + +[F_START] +~g~Veicolo in fiamme avvistato nell'area ~a~. Vai a spegnere il fuoco. + +[AM4_1A] +Vai al telefono al parco Belleville ovest. + +[AM4_1B] +Vai al telefono nel campus Liberty. + +[AM4_1C] +Vai al telefono al parco Belleville sud. + +[AM4_1D] +Incontriamoci nei bagni pubblici nel parco. + +[HJSTATF] +Distanza: ~1~ piedi Altezza: ~1~ piedi Ribaltamenti: ~1~ Rotazioni: ~1~_ + +[HJSTAWF] +Distanza: ~1~ piedi Altezza: ~1~ piedi Ribaltamenti: ~1~ Rotazioni: ~1~_ E che grande atterraggio! + +[HM1_F] +Stai in guardia, però. Ci saranno dei Jack in giro che penseranno che vuoi far fuori anche loro! + +[HM1_D] +'Nine' è il loro nome e la loro bandiera è porpora e ogni giorno che portano i loro colori... + +[HM1_G] +è un giorno in cui i 'Jack' appaiono rammolliti. + +[MEA2_B] +e ruba qualcosa così che io possa incassare l'assicurazione come fai tu. + +[TM3_H] +~w~Hai fatto un bel lavoro laggiù, molto bravo. + +[TM3_I] +~w~Forza, adesso ti presento al Don. + +[TM3_J] +~w~Eehi!!! Luigi! + +[TM3_K] +~w~Oh, le mie ragazze hanno sentito la sua mancanza, Don Salvatore, è mancato per molto tempo. + +[TM3_L] +~w~Dì loro che una volta risolta questa situazione spiacevole, + +[TM3_M] +~w~andremo tutti al club a festeggiare, OK? + +[TM3_N] +~w~Ecco il mio ragazzo! + +[TM3_N2] +~w~Come va, papà? + +[TM3_O] +~w~Ti sei trovato una brava mogliettina? + +[TM3_P] +~w~Ehi, tua madre, pace all'anima sua, si rivolterebbe nella tomba + +[TM3_Q] +~w~vedendoti ancora senza moglie. + +[TM3_R] +~w~Lo so, papà, ci sto pensando su. + +[TM3_S] +~w~TONI! Come sta tua madre? + +[TM3_T] +~w~È una donna eccezionale. Forte. + +[TM3_U] +~w~Sta bene... tutto OK. + +[TM3_V] +~w~Bene, bene. Voi ragazzi entrate pure, mentre io chiacchiero col nostro nuovo amico. + +[TM3_W] +~w~Vedo un futuro roseo per te, figghiu miu... + +[RM1_A] +Quell'infame di McAffrey, si prende più bustarelle di chiunque. + +[RM1_B] +Pensa di venir assolto dignitosamente presentando delle prove inequivocabili. + +[RM1_C] +Ha appena cantato! + +[RM4_B] +Dobbiamo metterlo a tacere, per sempre. + +[RM4_E] +Voglio che dorma con i pesci, in senso letterale. + +[LOVE3_B] +Avvicinandosi all'aeroporto stasera, un piccolo aereo sorvolerà la baia. + +[LOVE4_D] +Sfortunatamente le autorità aeroportuali avevano già iniziato a smantellare l'aereo + +[LOVE4_H] +finché non sono intervenuto sborsando una fortuna. + +[LOVE4_E] +Attraversa il ponte per Shoreside Vale e vai all'aeroporto internazionale Francis. + +[GTAB_A] +Ehi, portiamo questo fuori di qui. Dio solo sa cos'è + +[GTAB_B] +ma sembra che lui lo voglia davvero, quindi dovrà pur valere qualcosa. + +[GTAB_C] +Chi diavolo è! + +[GTAB_D] +TU! + +[GTAB_E] +Ehi, in gamba amigo, stai calmo, amigo! Di niente, di niente! + +[GTAB_F] +Ti ho lasciato sfogare! + +[GTAB_G] +Non sparare amigo. Nessun problema. Siamo tutti amici. Ecco, prendi questo. + +[GTAB_H] +Non fare la femminuccia! + +[GTAB_I] +Non abbiamo scelta, baby! + +[GTAB_J] +C'è sempre una scelta, stupido bastardo! + +[GTAB_K] +Mi spiace per quella cretina, amigo, sono tutte uguali... por favor? + +[GTAB_L] +Allora la puttana è scappata. + +[GTAB_M] +Ma mi hai fatto un favore, + +[GTAB_N] +non sei il solo che ha un conto in sospeso col Cartello, + +[GTAB_O] +questo verme ha ucciso mio fratello! + +[GTAB_P] +Non ho mai ucciso nessun Yakuza! + +[GTAB_Q] +BUGIARDO! Abbiamo visto tutti l'assassino del Cartello. + +[GTAB_R] +Daremo la caccia ed elimineremo tutti voi cani Colombiani! + +[GTAB_S] +Farò un'operazione al nostro amichetto per strappargli qualche informazione e per divertirmi. + +[GTAB_T] +Tu, passa più tardi, sono certo che avrò bisogno del tuo aiuto. + +[GTAB_U] +Ti prego, amigo, non lasciarmi con lei, è tutta matta! Amigo? Ehi Amiiigooo!!! Aieeeeeeaaaaahh!!! + +[LOVE5_A] +Ti stai dimostrando un buon investimento, cosa rara in questi giorni. + +[KM3_1] +~g~Il Cartello aspetta una gang di Yardie. Vai a rubare una loro auto! Dirigiti a nord, ne troverai una a Newport. + +[LOVE1_1] +~g~Procurati un'auto dei gangster Colombiani per infiltrarti nel loro nascondiglio: dirigiti a nord, ne troverai una a Fort Staunton. + +[FM1_Q1] +~w~Ti va di divertirti? Un po' di... hmmm? Un po' di SPANK? + +[FM1_R] +~w~Ciao Chico. No, il solito. + +[FM1_T] +~w~Grazie Chico, a presto. + +[FM1_W] +~w~OK, Fido, aspetta qui e tieni d'occhio l'auto mentre io vado a sculettare un po'. + +[FM1_X] +~w~OK, Fido, andiamocene via di qui. Uuuhhh! + +[FM1_Q] +~w~Ehi, Maria! La mia ragazza preferita! + +[FM1_S1] +~w~Ehi, forse dovresti dare un'occhiata al party nei magazzini sulla costa est del molo atlantico. + +[FM1_U] +~w~Gracias e buon divertimento. È roba buona. + +[FM1_V] +~w~Forza, Fido, facciamo un salto a questo party! + +[FM1_SS] +~r~SCANNER: ~g~4-5 a tutte le unità: rinforzi per l'operazione antinarcotici al molo atlantico... + +[LOVE6_B] +Anche se hanno scarsa comprensione del suo autentico valore. + +[TM3_A1] +~r~Joey è spacciato! + +[TM3_A2] +~r~Joey e Luigi sono stati cremati! + +[TM3_A3] +~r~Joey, Luigi e Toni sono fritti! + +[FM4_2] +Ascolta, Salvatore pensa che vogliamo tradirlo, + +[FM4_3] +per cui ti stava per consegnare al Cartello per raggiungere un accordo. + +[FM4_4] +Non potevo lasciarglielo fare, e poi la cosa peggiore è che + +[FM4_4B] +è tutta colpa mia... perché gli ho detto che stavamo insieme. + +[FM4_5] +Non chiedermi il perché. Non lo so. + +[FM4_6] +Senti, sei un uomo spacciato in territorio mafioso, e anch'io devo svignarmela. + +[FM4_6B] +Ho visto troppi omicidi. Troppo sangue! + +[FM4_7] +Questa è una mia vecchia amica, OK, di vecchia data. È Asuka, ed è una tipa fidata. + +[FM4_8] +Forza, basta con i discorsi. + +[FM4_9] +Dovremmo andarcene prima che arrivino altri italiani isterici poco propensi a una discussione amichevole. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUTTORE + +[CRED003] +LESLIE BENZIES + +[CRED004] +DIREZIONE ARTISTICA + +[CRED005] +AARON GARBUT + +[CRED006] +DIREZIONE TECNICA + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DESIGN + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +SCRITTO DA + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +PERSONAGGI + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMAZIONE E REGIA + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +DESIGN AUTOMOBILI + +[CRED024] +PAUL KUROWSKI + +[CRED025] +GRAFICI + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODIFICATORI + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +MUSICHE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +DESIGN AUDIO E MASTERIZZAZIONE + +[CRED042] +ALLAN WALKER + +[CRED043] +PROGRAMMAZIONE AUDIO + +[CRED044] +RAYMOND USHER + +[CRED045] +DIRETTORE TESTING + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +TESTER PRINCIPALI + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTER + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +SUPPORTO TECNICO + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +PRODUTTORE ESECUTIVO + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUTTORE + +[CRED064] +DAN HOUSER + +[CRED065] +DIRETTORE DI SVILUPPO + +[CRED066] +JAMIE KING + +[CRED067] +PRODUTTORE TECNICO + +[CRED068] +GARY J. FOREMAN + +[CRED069] +PRODUTTORE ASSOCIATO + +[CRED070] +JEREMY POPE + +[CRED071] +SUPERVISORE MUSICHE + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VICEPRESIDENTE MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +COORDINATORE TECNICO + +[CRED088] +BRANDON ROSE + +[CRED089] +RESPONSABILE CONTROLLO QUALITÀ + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +ANALISTA DEL GIOCO + +[CRED094] +RICHARD HUIE + +[CRED095] +SQUADRA TESTING + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +PIANTA DI LIBERTY + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +SEQUENZE ANIMATE + +[CRED108] +COPIONE SCRITTO DA DAN HOUSER E JAMES WORRALL + +[CRED109] +REGISTRAZIONE AUDIO DIRETTA DA DAN HOUSER + +[CRED110] +AUDIO PRODOTTO DA RENAUD SEBBANE + +[CRED111] +CASTING + +[CRED112] +FRANK VINCENT NEL RUOLO DI SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO NEL RUOLO DI LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN NEL RUOLO DI TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT NEL RUOLO DI JOEY LEONE + +[CRED116] +DEBBI MAZAR NEL RUOLO DI MARIA + +[CRED117] +KYLE MACLACHLAN NEL RUOLO DI DONALD LOVE + +[CRED118] +ROBERT LOGGIA NEL RUOLO DI RAY MACHOWSKI + +[CRED119] +GURU NEL RUOLO DI 8-BALL + +[CRED120] +SONDRA JAMES NEL RUOLO DI MOMMA + +[CRED121] +LIANA PAI NEL RUOLO DI ASUKA + +[CRED122] +LES MAU NEL RUOLO DI KENJI + +[CRED123] +CYNTHIA FARRELL NEL RUOLO DI CATALINA + +[CRED124] +AL ESPINOSA NEL RUOLO DI MIGUEL + +[CRED125] +CHRIS PHILLIPS NEL RUOLO DI EL BURRO + +[CRED126] +HUNTER PLATIN NEL RUOLO DI CHICO + +[CRED127] +WALTER MUDU NEL RUOLO DI D-ICE + +[CRED128] +CURTIS MCCLARIN NEL RUOLO DI CURTLY + +[CRED129] +BILL FIORE NEL RUOLO DI DARKEL + +[CRED130] +CHRIS PHILLIPS NEL RUOLO DI MARTY CHONKS + +[CRED131] +HUNTER PLATIN NEL RUOLO DI RICCIOLINO BOB + +[CRED132] +WALTER MUDU NEL RUOLO DI KING COURTNEY + +[CRED133] +HUNTER PLATIN NEL RUOLO DI ONE-ARMED PHIL + +[CRED134] +KIM GURNEY NEL RUOLO DI MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATO DA + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRETTO DA + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODOTTO DA + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +REGISTRATO PRESSO GLI STUDI MODERN UPRISING, BROOKLYN + +[CRED140] +ATTORI + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS + +[CRED141] +DIALOGO DEI PEDONI + +[CRED142] +SCRITTO DA DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRETTO DA CRAIG CONNER, DAN HOUSER E LAZLOW + +[CRED144] +PRODOTTO DA RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +STAZIONI RADIO E MUSICA + +[CRED218] +PRODUTTORI PER ROCKSTAR UK + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +COORDINATORE COLONNA SONORA + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUTTORE PER ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +POST-PRODOTTA DA + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER E IMAGING SCRITTE DA + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +RINGRAZIAMENTI SPECIALI A + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CRED270] +MIKE HONG + +[CINCAM] +Camera mobile + +[KM1_13] +Entra in auto dentro il garage! + +[KM3_14] +~r~Sei stato scoperto, l'affare è saltato! + +[EBAL_H] +Aspetta qui mentre entro a parlare con Luigi. + +[EBAL_M] +Ricorda, niente casini con le mie ragazze! + +[LM2_F] +Poi prendi la sua macchina, riverniciala. + +[LM2_D] +tieni, prendi. + +[LM1_9] +Ciao, sono Misty. + +[LM4_A] +I papponi dei Diablo hanno messo a battere le loro mignotte nella mia zona. + +[FM2_B] +C'è di mezzo una spia. + +[FM2_C] +Non è ne un pusher ne un pappone, quindi dev'essere una spia. + +[FM3_CC] +~w~Fratello, torna quando avrai i soldi. + +[LOVE5_5] +~r~Non sei riuscito a proteggere il camion! + +[RM6_6] +~r~Ray è morto! + +[RM6_7] +~r~Ray ha perso il volo. + +[RM6_8] +~g~Ti sei dimenticato Ray, torna a prenderlo. + +[FM1_10] +~g~Ti sei dimenticato Maria, torna a prenderla. + +[LOVE4_9] +~r~L'aereo è stato distrutto! + +[LOV4_10] +~r~Il solo indizio che indicasse dov'è finito il pacco è stato distrutto! + +[KM2_D] +Non occorre dire che dobbiamo donargli le auto, per ripagare il debito che gli è dovuto. + +[KM4_B] +Le imprese che godono della nostra protezione oggi salderanno i conti. + +[KM2_E] +Devi procurarti le auto nella lista e consegnarle nel garage dietro il parcheggio a Newport. + +[FM3_8I] +~w~Scegli una postazione vantaggiosa, poi io entrerò dopo che hai sparato il primo colpo. + +[LOVE1_B] +L'esperienza mi ha insegnato che un uomo come te sa essere molto leale se ben pagato, + +[LOVE1_H] +ma gruppi di uomini diventano avidi. + +[LOVE1_C] +Un personaggio chiave, un vecchietto orientale che conosco, + +[LOVE1_I] +è ostaggio di alcuni sud-americani in Aspatria. + +[MEA4_D] +Ho preso accordi per vederlo... + +[MEA4_B4] +Ti manda Marty, eh? OK, mostrerò a quello schifoso il significato della parola affari. + +[MEA4_B5] +Carl, ciao! Mi, uh, mi serve più tempo per pagarti. + +[MEA1_B4] +Ah, ti manda il signor Chonk, vero? Andiamo a fargli un visita. + +[HM5_6] +Andiamo a spaccare qualche cranio... + +[LOVE1_5] +~g~Non perdere altro tempo, procurati un'auto dei Colombiani e salva il socio di Love. + +[AS1_D] +~w~Agisci da esca e fatti inseguire dai plotoni d'esecuzione fino a Pike Creek + +[AS1_E] +~w~dove i miei uomini li aspetteranno. + +[AS2_C] +~w~Il Cartello ha un negozio di copertura, la compagnia Kappa Coffee House. + +[AS2_E] +~w~Non abbiamo scelta, dobbiamo mettere questi spacciatori ambulanti fuori combattimento. + +[AS2_F] +~w~Riducili in poltiglia!! + +[AS2_A1] +~w~Miguel ha di certo un po' di quella famosa prestanza latina. + +[AS2_A2] +~w~Io non ce la faccio più. + +[SIREN_3] +Per attivare la sirena di questo veicolo premi il ~h~tasto ~k~~VEHICLE_HORN~~w~. + +[SIREN_4] +Per attivare la sirena di questo veicolo premi il ~h~tasto ~k~~VEHICLE_HORN~~w~. + +[AS3_C] +~w~Eeeeyooo! Cos'è quella roba gialla e collosa? + +[AS3_C1] +~w~Oh, ciao pupa. + +[AS3_F] +~w~Ha un talento naturale questa ragazza. + +[AS3_F1] +~w~È riuscita a strappare questa perla al nostro ospite. + +[AS3_G] +~w~C'è un aereo in arrivo all'aeroporto internazionale Francis in 2 ore. + +[AS3_G1] +~w~È pieno del veleno di Catalina. + +[AS3_H] +~w~Puoi evitare la sicurezza dell'aeroporto andando in barca vicino alle boe di segnalazione della pista di atterraggio + +[AS3_H1] +e abbattendo l'aereo quando si avvicina. + +[AS3_I] +~w~Raccogli il carico dalle macerie! + +[AS3_J] +~w~Oh, adesso fa attenzione, OK baby? + +[AS3_K] +~w~Adesso prova l'olio di chili... + +[RM2_F1] +Quei Colombiani dovrebbero essere qui a momenti! + +[RM2_K] +Maledizione, sono arrivati!!! CARICA LE ARMI!!! + +[LOVE2_7] +~g~Adesso abbandona l'auto! + +[LOVE2_8] +~g~Ora allontanati da Newport! + +[AM1_F] +Salvatore Leone lascerà Luigi's all'incirca alle ~1~:~1~. + +[LOVE5_C] +Voglio che tu lo segua che ti accerti che lui e il mio pacco giungano a Pine Creex illesi. + +[FESZ_SR] +Salvataggio fallito! Controlla la memory card (PS2) nell'ingresso MEMORY CARD 1 e riprova. + +[FESZ_FO] +Vuoi formattare la memory card (PS2) nell'ingresso MEMORY CARD 1? + +[FELZ_FO] +La memory card (PS2) nell'ingresso MEMORY CARD 1 non è formattata. + +[FES_NOC] +Nessuna memory card (PS2) nell'ingresso MEMORY CARD 1. + +[FES_LOE] +Caricamento fallito! Controlla la memory card (PS2) nell'ingresso MEMORY CARD 1 e riprova. + +[FES_DEE] +Eliminazione fallita! Controlla la memory card (PS2) nell'ingresso MEMORY CARD 1 e riprova. + +[FORSUC] +Formattazione della memory card (PS2) nell'ingresso MEMORY CARD 1 completata. + +[ERFOUN] +Formattazione della memory card (PS2) nell'ingresso MEMORY CARD 1 fallita. + +[ERMCNP] +Nessuna memory card (PS2) nell'ingresso MEMORY CARD 1. + +[SVMEM1] +Salva sulla memory card (PS2) nell'ingresso MEMORY CARD 1. + +[FORSLO] +Formatta la memory card (PS2) nell'ingresso MEMORY CARD 1. + +[SLONFM] +Errore di formattazione della memory card (PS2) nell'ingresso MEMORY CARD 1. + +[SLONDR] +Spazio insufficiente. Inserire una memory card (PS2) con almeno 500KB di spazio libero nell'ingresso MEMORY CARD 1. + +[SLNSP] +Spazio insufficiente. Inserire una memory card (PS2) con almeno 200KB di spazio libero nell'ingresso MEMORY CARD 1. + +[FEFD_WR] +Formattazione della memory card (PS2) nell'ingresso MEMORY CARD 1. Non rimuovere la memory card (PS2), riavviare o spegnere la console. + +[FES_ISF] +NON PRESENTE + +[FES_SAG] +PRESENTE + +[SLONNO] +Nessuna memory card (PS2) nell'ingresso MEMORY CARD 1. + +[SLONNF] +Memory card (PS2) nell'ingresso MEMORY CARD 1 non formattata. + +[FESZ_FM] +Memory card (PS2) nell'ingresso MEMORY CARD 1 non formattata. Vuoi formattare la memory card (PS2) nell'ingresso MEMORY CARD 1? + +[FESZ_FF] +Formattazione fallita! Controlla la memory card (PS2) nell'ingresso MEMORY CARD 1 e riprova. + +[MCDNSP] +Spazio insufficiente sulla memory card (PS2) nell'ingresso MEMORY CARD 1. Sono necessari almeno 500KB di spazio libero. Si desidera procedere? (SÌ o NO) + +[MCGNSP] +Spazio insufficiente sulla memory card (PS2) nell'ingresso MEMORY CARD 1. Sono necessari almeno 200KB di spazio libero. Si desidera procedere? (SÌ o NO) + +[FESZ_WR] +Salvataggio dati. Non rimuovere la memory card (PS2) dall'ingresso MEMORY CARD 1, riavviare o spegnere la console. + +[FESZ_OW] +Sovrascrittura dati. Non rimuovere la memory card (PS2) dall'ingresso MEMORY CARD 1, riavviare o spegnere la console. + +[FELD_WR] +Caricamento dati. Non rimuovere la memory card (PS2) dall'ingresso MEMORY CARD 1, riavviare o spegnere la console. + +[FEDL_WR] +Eliminazione dati. Non rimuovere la memory card (PS2) dall'ingresso MEMORY CARD 1, riavviare o spegnere la console. + +[LM2_C] +Luigi dice di, di darti questo, quindi... + +[LM3_G] +Joey non è tipo da aspettare: ricorda, questa è la tua grande occasione... + +[LM5_E] +Portane più che puoi prima che gli sbirri si bevano tutto lo stipendio. + +[JM5_C] +OK, c'è un'auto con ripieno di cadavere al bar vicino Callahan Point. + +[RM2_B] +Abbiamo visto i casini in Nicaragua, ai tempi in cui la nazione aveva ancora un senso. + +[RM2_C] +Qualche bastardo del Cartello l'ha maltrattato ieri e ha detto che ritornerà oggi per la sua merce. + +[RM2_D1] +Andrei io, ma la mia vecchia sciatica si fa sentire -cough cough- quindi, uhh, in bocca al lupo. + +[CATINF1] +~g~Prendi Catalina! + +[CATINF2] +~g~Segui l'elicottero per trovare Catalina. + +[BOATIN1] +Salta su una barca e premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per entrare. + +[BOATIN2] +Se sei vicino a una barca, puoi premere il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per tirarla verso di te. + +[BOATIN3] +Salta su una barca e premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per entrare. + +[BOATIN4] +Se sei vicino a una barca, puoi premere il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per tirarla verso di te. + +[JM6] +'LA PARTENZA' + +[FM1] +L'AUTISTA + +[JM1] +'L'ULTIMA CENA DI MIKE 'LABBRA'' + +[FM21] +'BOMBARDA LA BASE: ATTO PRIMO' + +[FM3] +'BOMBARDA LA BASE: ATTO SECONDO' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'SOTTO SORVEGLIANZA' + +[KM2] +'GRAND THEFT AUTO' + +[AS3] +'S.A.M.' + +[RM2] +'A CORTO D'ARMI' + +[LOVE6] +'TRANELLO' + +[LOVE1] +'LIBERATORE' + +[RC1] +'DISTRUZIONE DEI DIABLO' + +[RC2] +'MASSACRO MAFIOSO' + +[RC3] +'CALAMITÀ AL CASINÒ' + +[RC4] +'LA FURIA DI RUMPO' + +[RM2_E1] +Non posso credere che quei bastardi musi gialli se ne siano andati lasciandomi di nuovo senza copertura! + +[GREN_1] +Quanto più a lungo tieni premuto il ~h~tasto ~k~~PED_FIREWEAPON~~w~, tanto più lontano lancerai la granata. + +[GREN_2] +Quanto più a lungo tieni premuto il ~h~tasto ~k~~PED_FIREWEAPON~~w~, tanto più lontano lancerai la granata. + +[GREN_3] +Quanto più a lungo tieni premuto il ~h~tasto ~k~~PED_FIREWEAPON~~w~, tanto più lontano lancerai la granata. + +[LOVE4_G] +Le mie proprietà ti aspettano all'interno dell'aereo nell'hangar doganale. + +[KABOOM] +KABOOOM! + +[SPLAT] +SPIACCICATO! + +[PANCAK] +SCHIACCIATO! + +[SOAKED] +ANNEGATO! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Chatterbox FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +Quando aprirà la metropolitana potrai prendere un treno verso Staunton Island. + +[TUBE2] +Quando aprirà Shoreside Vale potrai uscire dallo Shoreside terminal verso l'aeroporto internazionale Francis. + +[TUBE_2] +Per salire sulla metropolitana, premi il ~h~tasto 'entra nel veicolo'~w~. + +[LEGAL] +~g~Elimina la minaccia criminale! + +[GA_2] +Motore nuovo e carrozzeria riverniciata. Gli sbirri non ti riconosceranno! + +[LM1_8A] +Per guadagnare qualche soldo extra, perché non 'prendere in prestito' un taxi... + +[TAXIH1] +Fermati vicino a un pedone evidenziato per farlo salire a bordo e portarlo a destinazione prima che scada il tempo. + +[LM5_7] +~g~Se ci sono meno di quattro ragazze alla ~p~festa degli sbirri~g~, Luigi sarà nero! + +[KM2_3] +~g~Ricorda, le ~r~macchine~g~ devono essere in condizioni eccellenti per essere accolte nel ~p~garage~g~. + +[KM5_2] +~g~Uno Yardie è in giro per strada. + +[BETRA_A] +Scusa baby. + +[BETRA_B] +Sono una ragazza ambiziosa e tu, + +[BETRA_C] +sei una mezza calzetta. + +[JAILB_C] +Nessun dettaglio è stato fornito riguardo i prigionieri che venivano trasferiti dalla scorta, + +[JAILB_E] +La scorta aveva lasciato la centrale di polizia questa mattina presto... + +[JAILB_F] +per un trasferimento ordinario verso il penitenziario di Liberty. + +[JAILB_G] +L'attacco è avvenuto sul ponte Callahan, + +[JAILB_I] +Alcuni dei detenuti sembrerebbero essere deceduti a seguito dell'esplosione... + +[JAILB_P] +questo disastro lascia Portland isolata dal resto della città. + +[JAILB_Q] +Forza! + +[JAILB_R] +Signor stronzo! + +[JAILB_S] +Non è un problema ucciderti. + +[JAILB_T] +Te ne pentirai. + +[JAILB_U] +Va bene, va bene. Sparisci. + +[HELP15] +Quando sei a piedi, premi il ~h~tasto ~k~~PED_LOOKBEHIND~~w~ per ~h~guardare indietro~w~. + +[FEC_LB3] +Guarda indietro + +[FEC_R3] +(tasto R3) + +[FES_AFO] +Questa memory card (PS2) è già formattata. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- CAMBIA SELEZIONE + +[FEDSAS4] +;=<> - CAMBIA SELEZIONE + +[SPRAY_4] { re3 change } +Usa il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare col cannone ad acqua. + +[SPRAY_1] { re3 change } +Usa il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare col cannone ad acqua. + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[AM1_10] +Salvatore uscirà da Luigi's attorno alle 0~1~:~1~. + +[JAILB_V] +La città di Liberty è sotto shock. + +[JAILB_A] +Mentre la polizia e i servizi di emergenza affrontano le conseguenze... + +[JAILB_B] +di un devastante attacco alla scorta avvenuto questa mattina. + +[JAILB_W] +Rivelazioni sulla professionalità dell'attacco hanno sconvolto le forze dell'ordine, + +[JAILB_K] +Inoltre, l'identificazione dei detenuti scomparsi è stata ulteriormente ostacolata... + +[JAILB_L] +da un attacco informatico al computer principale della centrale di Polizia. + +[JAILB_M] +Il Sindaco O'Donovan ha dichiarato che la polizia considera l'attentato... + +[JAILB_N] +* + +[JAILB_O] +Considerato il ritardo nella costruzione del sottopassaggio Porter, + +[JAILB_X] +In un'affermazione resa nota ieri, + +[FEDS_SE] +Tasto / - SELEZIONA + +[FEDS_SB] +Tasto / - SELEZIONA Tasto " - INDIETRO + +[TM4_A] +~w~Oh, sei te. TONI non è qua. + +[TM4_A2] +~w~Ma ha lasciato una delle sue dolci lettere per te. + +[DIAB2_A] +Ho iniziato a lavorare nel business dell'intrattenimento solo grazie al voluminoso contenuto dei miei pantaloni di pelle! + +[LM5_9] +RAGAZZE: + +[PERPIC] +Pacchetti speciali recuperati + +[CO_ONE] +Pacchetto speciale ~1~ su ~1~ + +[LOVE3_3] +~g~L'aeroplano ha fatto cadere ~1~ dei 6 pacchi. + +[FARE11] +~g~Destinazione: ~w~'il cantiere costruzioni'~g~ a Fort Staunton. + +[GA_21] +Non puoi parcheggiare altri veicoli in questo garage. + +[CHEAT1] +Trucco attivato + +[CHEAT2] +Trucco armi + +[CHEAT3] +Trucco salute + +[CHEAT4] +Trucco armatura + +[CHEAT5] +Trucco livello di sospetto + +[CHEAT6] +Trucco soldi + +[CHEAT7] +Trucco tempo atmosferico + +[AS1_H] +~r~Hai fallito nel far cadere la squadra della morte nella trappola della Yakuza!!! + +[FEDS_BA] +Tasto " - INDIETRO + +[RAMP_A] +TUTTE LE VIOLENZE COMPLETATE! + +[USJ_ALL] +TUTTE LE ACROBAZIE UNICHE COMPLETATE + +[FARE23] +~g~Destinazione: ~w~'Garage importazioni-esportazioni' ~g~nel distretto della diga Cochrane. + +[L_TRN_1] +Puoi prendere il treno sopraelevato a Portland. Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un vagone. + +[L_TRN_2] +Puoi prendere il treno sopraelevato a Portland. Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un vagone. + +[S_TRN_1] +Puoi prendere la metropolitana attraverso Liberty. Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un vagone. + +[S_TRN_2] +Puoi prendere la metropolitana attraverso Liberty. Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un vagone. + +[AS1_C] +~w~Ha tre squadre della morte in giro per Liberty il cui unico scopo è beccarti. + +[AS1_G] +~r~Tutti i membri della Yakuza sono morti!!! + +[JAN] +Gen + +[FEB] +Feb + +[MAR] +Mar + +[APR] +Apr + +[MAY] +Mag + +[JUN] +Giu + +[JUL] +Lug + +[AUG] +Ago + +[SEP] +Set + +[OCT] +Ott + +[NOV] +Nov + +[DEC] +Dic + +[DEFDT] +Nessun salvataggio valido + +[BUGGY] +MAGGIOLINI RIMASTI: + +[BONUS] +~g~BONUS $~1~$ + +[HORN1] +Premi il ~h~tasto L3~w~ per suonare il ~h~clacson~w~. + +[HORN2] +Premi il ~h~tasto L1~w~ per suonare il ~h~clacson~w~. + +[HORN3] +Premi il ~h~tasto R1~w~ per suonare il ~h~clacson~w~. + +[LM3_1A] +Premi il ~h~tasto ~k~~VEHICLE_HORN~~w~ per suonare il ~h~clacson~w~ e avvertire Misty che sei arrivato. + +[LM3_1B] +Premi il ~h~tasto ~k~~VEHICLE_HORN~~w~ per suonare il ~h~clacson~w~ e avvertire Misty che sei arrivato. + +[LM3_1C] +Premi il ~h~tasto ~k~~VEHICLE_HORN~~w~ per suonare il ~h~clacson~w~ e avvertire Misty che sei arrivato. + +[RADIO_A] +Premi il ~h~tasto ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ per passare in rassegna le ~h~stazioni radio~w~. + +[RADIO_B] +Premi il ~h~tasto ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ per passare in rassegna le ~h~stazioni radio~w~. + +[RADIO_C] +Premi il ~h~tasto ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ per passare in rassegna le ~h~stazioni radio~w~. + +[RADIO_D] +Premi il ~h~tasto ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ per passare in rassegna le ~h~stazioni radio~w~. + +[FEC_EXV] +Entra o esci dal veicolo + +[TAXI_M] +'TAXISTA' + +[COP_M] +'VIGILANTE' + +[FIRE_M] +'POMPIERE' + +[AMBUL_M] +'PARAMEDICO' + +[HJ_IS] +BONUS ACROBAZIA FOLLE: ~1~$ + +[HJ_PIS] +BONUS ACROBAZIA FOLLE PERFETTA: $~1~ + +[HJ_DIS] +BONUS DOPPIA ACROBAZIA FOLLE: $~1~ + +[HJ_PDIS] +BONUS DOPPIA ACROBAZIA FOLLE PERFETTA: $~1~ + +[HJ_TIS] +BONUS TRIPLA ACROBAZIA FOLLE: $~1~ + +[HJ_PTIS] +BONUS TRIPLA ACROBAZIA FOLLE PERFETTA: $~1~ + +[HJ_QIS] +BONUS QUADRUPLA ACROBAZIA FOLLE: $~1~ + +[HJ_PQIS] +BONUS QUADRUPLA ACROBAZIA FOLLE PERFETTA: $~1~ + +[AM1_K] +Salvatore Leone uscirà da Luigi's fra circa tre ore. (0~1~:~1~) + +[IMPEXPP] +Garage importazioni-esportazioni, porto di Portland. Abbiamo richieste per vari mezzi: consulta la bacheca per le saperne di più. + +[VANHSTP] +Hai scassinato altre Securicar? Portale al nostro garage al porto di Portland. + +[EMVHPUP] +Ottimi prezzi per veicoli di soccorso nuovi o usati. Portali vicino alla gru a nord est del porto di Portland. + +[STANDS] +CHIOSCHI DISTRUTTI: + +[STASH] +~g~Nascondi lo SPANK nel ~p~cantiere edile~g~! + +[MCSTNS] +Nessuna memory card (PS2) nell'ingresso MEMORY CARD 1. Vuoi procedere comunque? (SÌ o NO) + +[LOVE3_5] +~g~L'aeroplano è a tiro. + +[LOVE3_6] +~r~La polizia è arrivata ai pacchi per prima! + +[SIREN_1] +Per accendere le sirene di questo veicolo, premi il ~h~tasto ~k~~VEHICLE_HORN~~w~. + +[SIREN_2] +Per accendere le sirene di questo veicolo, premi il ~h~tasto ~k~~VEHICLE_HORN~~w~. + +[FM3_8C] +~w~Avrò bisogno di 100.000$ per coprire le spese, + +[MCLOAD] +Caricamento dati. Non rimuovere la memory card (PS2) dall'ingresso MEMORY CARD 1, riavviare o spegnere la console. + +[FES_GME] +Errore di lettura della memory card (PS2) nell'ingresso MEMORY CARD 1! Controlla la memory card (PS2) e riprova. + +[FESZ_QF] +Sei sicuro di voler formattare la memory card (PS2) nell'ingresso MEMORY CARD 1? + +[FESZ_LS] +Caricamento riuscito. + +[RM3_5] +~g~Hai raccolto ~1~ su 6 prove. + +[LOVE3_2] +~g~Hai tutti i pacchi! Riportali subito a Donald Love. + +[LOVE4_4] +~g~Riporta il pacco a Donald Love. + +[FEDS_AM] +<>-CAMBIA MENU + +[FEB_SAV] +Carica + +[FEP_SAV] +CARICA PARTITA + +[AS2_12A] +~g~Dopo aver distrutto il primo chiosco, avrai 8 minuti prima che il Cartello informi i propri spacciatori! + +[AS3_1A] +~g~Adesso raggiungi la ~b~boa segnalatrice~g~! + +[NOCONT] +Ricollega il controller analogico (DUALSHOCK#) o controller analogico (DUALSHOCK#2) all'ingresso controller 1 per continuare. + +[BET_JB] +TRADITO DALLA SUA AMANTE CATALANA E ABBANDONATO MEZZO MORTO. GIUDICATO COLPEVOLE, INIZIA LA SUA AVVENTURA NEL PENITENZIARIO DELLA CITTÀ DI LIBERTY. HA UN SOLO PENSIERO FISSO NELLA TESTA... LA VENDETTA! + +[END_A] +I residenti in Cedar Grove si sono finalmente ripresi + +[END_B] +dallo spavento per i violenti scontri + +[END_C] +avvenuti ieri nell'area. + +[END_D] +Un abitante della zona, Clive Denver, ha dichiarato alla polizia + +[END_E] +di aver visto un uomo armato che sia allontanava dalla scena del crimine con una donna dai capelli neri. + +[END_F] +Oh, lo sai, vedrai che ci divertiremo, perché lo sai, cioè, + +[END_G] +io ti amo, io... io ti amo davvero perché sei un vero uomo + +[END_H] +ed è ciò di cui ho bisogno. + +[END_I] +Comunque, cosa stavo dicendo? + +[END_J] +Oh, lo sai, me lo sono dimenticato. Sai com'è che succede, vero? + +[END_K] +Il suono delle esplosioni ha fatto tremare le case mentre la gente correva al riparo. + +[END_L] +Molti cittadini sono rimasti feriti per il panico mentre venivano sparati colpi + +[END_M] +tra le forze terrestri e l'elicottero che sorvolava la diga. + +[END_N] +Sì, c'è proprio una bella vista da qui in giardino. + +[END_O] +Quando hanno fatto saltare l'elicottero + +[END_P] +è stato meglio dei fuochi d'artificio del 4 luglio. + +[END_Q] +Il numero di cadaveri recuperati ha già superato la ventina, + +[END_R] +ma la polizia continua a trovare nuovi corpi. + +[END_S] +Non sono state smentite le voci secondo cui + +[END_T] +i morti appartenessero al Cartello Colombiano, + +[END_U] +ciò nonostante, non ci sono indizi sulle ragioni di questa strage. + +[END_V] +Mi sono rotta un'unghia e i miei capelli sono un disastro. Ma ci puoi credere? + +[END_W] +Mi è costato 50 dollari... + +[PAPER1] +CRIMINALE FERITO DALLA FIDANZATA COMPLICE. LA CORTE GIUDICA IL RAPINATORE COLPEVOLE CON VERDETTO UNANIME. + +[PAPER2] +DIECI ANNI PER L'AMORE! + +[JAILB_J] +che è seguita all'attacco iniziale. + +[JAILB_D] +E nessun gruppo ha rivendicato l'attentato. + +[JAILB_H] +lasciando pochi testimoni e il ponte stesso seriamente danneggiato. + +[FEB_CPC] +Configurazione comandi + +[FEC_PED] +Comandi a piedi + +[FEC_VEH] +Comandi in un veicolo + +[FEC_FPR] +Comandi in prima persona + +[FEC_CMM] +Comandi comuni + +[FEC_PWL] +Cammina a sinistra + +[FEC_PWR] +Cammina a destra + +[FEC_PWF] +Cammina in avanti + +[FEC_PWT] +Cammina verso la telecamera + +[FEC_PLB] +Guardati indietro + +[FEC_PFR] +Spara con l'arma + +[FEC_CLE] +Arma precedente + +[FEC_CRI] +Arma successiva + +[FEC_LKT] +Aggancia bersaglio + +[FEC_PJP] +Salto Ped + +[FEC_PSP] +Scatto Ped + +[FEC_PSH] +Sparo Ped + +[FEC_TLF] +Bersaglio successivo a sinistra + +[FEC_TRG] +Bersaglio successivo a destra + +[FEC_CCM] +Centra visuale dietro al giocatore + +[FEC_SZI] +Ingrandimento fucile di precisione + +[FEC_SZO] +Riduzione fucile di precisione + +[FEC_LKL] +Sguardo a sinistra + +[FEC_LRT] +Sguardo a destra + +[FEC_LUP] +Sguardo verso l'alto + +[FEC_LDN] +Sguardo verso il basso + +[FEC_LBH] +Guarda dietro al veicolo + +[FEC_LLF] +Guarda a sinistra del veicolo + +[FEC_LRG] +Guarda a destra del veicolo + +[FEC_HRN] +Clacson + +[FEC_HBR] +Freno a mano + +[FEC_ACL] +Acceleratore + +[FEC_BRK] +Freno + +[FEC_TSM] +Attiva/Disattiva sottomissioni + +[FEC_CRD] +Cambia stazione radio + +[FEC_ENT] +Entra/Esci da un veicolo + +[FEC_WPN] +Spara con l'arma + +[FEC_PAS] +Pausa + +[FEC_FPO] +Attiva/Disattiva visuale di puntamento + +[FEC_SMS] +Mostra/Nascondi puntatore del mouse + +[FEC_CMS] +Cambia la modalità di visuale in qualsiasi situazione + +[FEC_TSS] +Salva un'immagine di gioco + +[FEN_NET] +Rete + +[FEN_CON] +Connessione + +[FEN_GAM] +Trova partite + +[FEN_TYP] +Modalità + +[FEN_TY0] +Deathmatch + +[FEN_TY1] +Deathmatch stealth + +[FEN_TY2] +Deathmatch a squadre + +[FEN_TY3] +Deathmatch stealth a squadre + +[FEN_TY4] +Accumula denaro + +[FEN_TY5] +Cattura la bandiera + +[FEN_TY6] +Rat Race + +[FEN_TY7] +Dominazione + +[FEN_NAM] +Nome: + +[FEN_GNA] +Nome partita: + +[FEM_MAP] +Seleziona mappa + +[FEN_PLS] +Impostazioni giocatore + +[FEN_PLC] +Colore giocatore + +[FEM_MA0] +Città di Liberty + +[FEM_MA1] +Luci rosse + +[FEM_MA2] +Chinatown + +[FEM_MA3] +La torre + +[FEM_MA4] +Le fogne + +[FEM_MA5] +Area industriale + +[FEM_MA6] +Porto + +[FEM_MA7] +Staunton + +[FEC_EMS] +Un tasto può essere assegnato a un solo comando! + +[FEC_DBG] +Menu debug + +[FEC_TGD] +Pad game/Debug + +[FEC_TDO] +Disabilita visuale debug + +[FEC_IVH] +Inverti mouse orizzontalmente + +[FEC_MSL] +PULSANTE SX + +[FEC_MSM] +PULSANTE CEN + +[FEC_MSR] +PULSANTE DX + +[FEC_QUE] +??? + +[FEC_TWO] +Un massimo di due tasti per comando. + +[FEC_UMS] +Un pulsante del mouse può essere assegnato a un solo comando! + +[FEC_OMS] +Solo un pulsante del mouse. + +[FEC_UJS] +Un pulsante del joystick può essere usato per un solo comando! + +[FEC_OJS] +Solo un pulsante del joystick. + +[FEC_PTL] +Usa Aggancia bersaglio con Arma precedente + +[FEC_PTR] +Usa Aggancia bersaglio con Arma successiva + +[FEC_LBC] +Usa Guarda a sinistra con Guarda a destra + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +Pausa non disponibile in modalità multigiocatore. Premi ancora per uscire. + +[FEM_SL1] +Blocco 1 libero + +[FEM_SL2] +Blocco 2 libero + +[FEM_SL3] +Blocco 3 libero + +[FEM_SL4] +Blocco 4 libero + +[FEM_SL5] +Blocco 5 libero + +[FEM_SL6] +Blocco 6 libero + +[FEM_SL7] +Blocco 7 libero + +[FEM_SL8] +Blocco 8 libero + +[FEM_MM] +MENU PRINCIPALE + +[FEM_SNG] +Nuova partita + +[FEM_QTW] +Esci + +[FEQ_SRE] +Sei sicuro di voler uscire? Tutti i progressi dall'ultimo salvataggio verranno persi. Vuoi procedere? + +[FEQ_SRW] +Sei sicuro di voler uscire dal gioco? + +[FEG_SRV] +SERVER + +[FEG_MAP] +MAPPA + +[FEG_PLY] +GIOCATORI + +[FEG_TYP] +MODALITÀ + +[FEG_PNG] +PING + +[FET_FG] +TROVA PARTITA + +[FET_SP] +GIOCATORE SINGOLO + +[FET_MP] +MULTIGIOCATORE + +[FET_HG] +OSPITA PARTITA + +[FET_PS] +IMPOSTAZIONI GIOCATORE + +[FET_CON] +CONNESSIONE + +[FET_AUD] +IMPOSTAZIONI AUDIO + +[FET_DIS] +IMPOSTAZIONI VIDEO + +[FET_LAN] +IMPOSTAZIONI LINGUA + +[FET_LG] +CARICA PARTITA + +[FET_DG] +ELIMINA PARTITA + +[FET_NG] +NUOVA PARTITA + +[FET_SG] +SALVA PARTITA + +[FET_MAP] +SELEZIONA MAPPA + +[FET_GT] +MODALITÀ DI GIOCO + +[FET_CTL] +IMPOSTAZIONI DEI COMANDI + +[FET_OPT] +OPZIONI + +[FET_QG] +ESCI DAL GIOCO + +[FET_STA] +STATISTICHE + +[FET_BRE] +MISSIONI + +[FEC_WAR] +Attenzione + +[FEC_OKK] +OK + +[FED_CON] +Conferma eliminazione file + +[FES_SSC] +Partita salvata con successo + +[DEL_FNM] +File eliminato con successo. + +[PCLOAD] +Caricamento dati in corso + +[PCRESRT] +Riavvio di Grand Theft Auto III + +[FEC_DLF] +Eliminazione fallita. + +[FEC_SVU] +Salvataggio fallito. + +[FEC_LUN] +Caricamento fallito. File corrotto: è necessario eliminarlo. + +[FEN_PLA] +Numero di giocatori: + +[FET_NON] +NESSUNA PARTITA DISPONIBILE + +[FET_SFG] +RICERCA DI PARTITE... + +[FET_SRT] +CLASSIFICAZIONE PARTITE... + +[FEF_LAN] +LAN + +[FEF_INT] +INTERNET + +[FET_REF] +Aggiorna + +[FET_FIL] +Filtra + +[FET_JG] +Partecipa + +[FEC_NTW] +Talk To Network + +[FEC_ESR] +Il tasto ESC è riservato + +[FEC_GSL] +Show head bob: + +[FIL_FLT] +FILTRA ELENCO PARTITE + +[FET_SAN] +NUOVA PARTITA + +[FIL_MAP] +Mappa: + +[FIL_SRV] +Server: + +[FIL_TYP] +Modalità: + +[FIL_SPC] +Partite non piene? + +[FIL_PNG] +Ping: + +[FEN_UKH] +Host sconosciuto + +[FEN_UKM] +Mappa non trovata + +[FEN_UKT] +Modalità non trovata + +[FEN_NCI] +NON CONNESSO A INTERNET + +[FET_PAU] +MENU PAUSA + +[FET_SGA] +AVVIA PARTITA + +[FEC_SGJ] +Imposta joystick + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FEC_WHL] +Volante + +[FEC_CNT] +Periferica di controllo: + +[FET_APL] +APPLICA + +[FES_CSA] +Seleziona una skin dalla seguente lista: + +[FES_SKN] +NOME SKIN + +[FES_DAT] +DATA + +[FES_NON] +NESSUNA SKIN DISPONIBILE + +[FEA_FM9] +RIPRODUTTORE MP3 + +[FESZ_QZ] +Sei sicuro di voler salvare la partita? + +[FES_CGA] +Blocchi di gioco disponibili: + +[FES_SCG] +Vuoi salvare la partita attuale? + +[FES_LCG] +Vuoi caricare e continuare la partita? + +[FEC_FIR] +Fuoco + +[FEC_NWE] +Arma successiva + +[FEC_PWE] +Arma precedente + +[FEC_FOR] +Avanti + +[FEC_BAC] +Indietro + +[FEC_LEF] +Sinistra + +[FEC_RIG] +Destra + +[FEC_ZIN] +Ingrandimento + +[FEC_ZOT] +Riduzione + +[FEC_EEX] +Entra/Esci + +[FEC_RAD] +Radio + +[FEC_SUB] +Sottomissioni + +[FEC_CMR] +Cambia visuale + +[FEC_JMP] +Salto + +[FEC_SPN] +Scatto + +[FEC_HND] +Freno a mano + +[FEC_TUL] +Torretta a sx + +[FEC_TUR] +Torretta a dx + +[FEC_LOL] +Guarda a sinistra + +[FEC_LOR] +Guarda a destra + +[FEC_NTR] +Bersaglio successivo + +[FEC_PTT] +Bersaglio precedente + +[FEC_LBA] +Guarda indietro + +[FEC_CEN] +Centra visuale + +[FEC_UND] +(NO) + +[FET_CFT] +A PIEDI + +[FET_CCR] +IN MACCHINA + +[CVT_MSG] +Conversione texture per prestazioni ottimali con la tua scheda video + +[FET_CAC] +AZIONE + +[FEC_IBT] +- + +[FEC_SPC] +BARRA + +[FEC_MXO] +P1MX + +[FEC_MXT] +P2MX + +[FEC_UNB] +NESSUNO + +[FET_CME] +METODO DI CONTROLLO + +[FET_RDK] +RIDEFINISCI COMANDI + +[FET_AMS] +IMPOSTAZIONI MOUSE + +[FET_STI] +CONFIGURAZIONE PREDEFINITA + +[FET_CTI] +CONFIGURAZIONE CLASSICA + +[FET_MTI] +CONFIGURAZIONE COL MOUSE + +[FET_DAM] +MODELLAZIONE ACUSTICA DINAMICA + +[FEC_TFL] +Torretta a sinistra + +[FEC_TFR] +Torretta a destra + +[FEC_TFU] +Torretta /Aereo su + +[FEC_TFD] +Torretta /Aereo giù + +[FEC_MWF] +VOLANTE SU + +[FEC_MWB] +VOLANTE GIÙ + +[FEC_ORR] +o + +[FEC_NUS] +NON USATO + +[FEC_LUD] +Sguardo su + +[FEC_LDU] +Sguardo giù + +[FEC_CMP] +COMBO: SGUARDO S+D + +[FEC_NTT] +Nessun testo assegnato a questo tasto + +[FEC_FNC] +F~1~ + +[FEC_IRT] +INS + +[FEC_DLL] +CANC + +[FEC_HME] +HOME + +[FEC_END] +FINE + +[FEC_PGU] +PAG SU + +[FEC_PGD] +PAG GIÙ + +[FEC_UPA] +SU + +[FEC_DWA] +GIÙ + +[FEC_LFA] +SINISTRA + +[FEC_RFA] +DESTRA + +[FEC_NUM] +NUM + +[FEC_NMN] +NUM~1~ + +[FEC_FWS] +NUM / + +[FEC_PLS] +NUM + + +[FEC_MIN] +NUM - + +[FEC_DOT] +NUM . + +[FEC_NLK] +BLOC NUM + +[FEC_ETR] +INVIO + +[FEC_SLK] +BLOC SCORR + +[FEC_PSB] +PAUSA + +[FEC_BSP] +INDIETRO + +[FEC_TAB] +TAB + +[FEC_CLK] +BLOC MAIUSC + +[FEC_RTN] +RET + +[FEC_LSF] +MAIUSC SX + +[FEC_RSF] +MAIUSC DX + +[FEC_LCT] +CTRL SX + +[FEC_RCT] +CTRL DX + +[FEC_LAL] +ALT SX + +[FEC_RAL] +ALT DX + +[FEC_LWD] +WIN SX + +[FEC_RWD] +WIN DX + +[FEC_WRC] +CLIC WIN + +[WIN_TTL] +Grand Theft Auto III + +[WIN_95] +Grand Theft Auto III non è supportato da Windows 95 + +[WIN_DX] +Grand Theft Auto III richiede DirectX versione 8.1 o superiore + +[WIN_VDM] +Grand Theft Auto III richiede almeno 12 MB di memoria video disponibile + +[DIAB3_G] +Arriba! + +[FEM_RES] +RIPRENDI PARTITA + +[FES_SNG] +NUOVA PARTITA + +[FEM_SP] +GIOCATORE SINGOLO + +[FEM_MP] +MULTIGIOCATORE + +[FEM_QT] +ESCI DAL GIOCO + +[FES_SG] +NUOVA PARTITA + +[FES_LG] +CARICA PARTITA + +[FEM_HST] +OSPITA PARTITA + +[FEM_OPT] +OPZIONI + +[FEM_DBG] +DEBUG + +[FET_PSU] +IMPOSTAZIONI GIOCATORE + +[FET_DEF] +RIPRISTINA PREDEFINITI + +[FED_BRI] +LUMINOSITÀ + +[FED_TRA] +TRACCE + +[FEM_LOD] +DISTANZA VISUALE + +[FEM_VSC] +SINCRONIA FRAME + +[FEM_FRM] +LIMITATORE FRAME + +[FED_RES] +RISOLUZIONE + +[FED_WIS] +SCHERMO 16:9 + +[FEDS_TB] +INDIETRO + +[FEA_MUS] +VOLUME MUSICA + +[FEA_SFX] +VOLUME EFFETTI + +[FEA_RSS] +STAZIONE RADIO + +[FEL_ENG] +INGLESE + +[FEL_FRE] +FRANCESE + +[FEL_GER] +TEDESCO + +[FEL_ITA] +ITALIANO + +[FEL_SPA] +SPAGNOLO + +[FEA_3DH] +HARDWARE AUDIO + +[FEA_SPK] +CONFIGURAZIONE ALTOPARLANTI + +[FEA_2SP] +DUE CASSE + +[FEA_4SP] +PIÙ DI DUE CASSE + +[FEA_EAR] +CUFFIE + +[FEA_NAH] +NESSUN HARDWARE AUDIO + +[FET_SNG] +NUOVA PARTITA + +[FEN_STA] +AVVIA LA PARTITA + +[GMLOAD] +CARICA PARTITA + +[GMSAVE] +SALVA PARTITA + +[FES_DGA] +ELIMINA PARTITA + +[FEM_NON] +NESSUNO + +[FEC_IVV] +INVERTI MOUSE VERTICALMENTE + +[FEC_MSH] +SENSIBILITÀ MOUSE + +[FET_CCN] +COMANDI: CLASSICO + +[FET_SCN] +COMANDI: PREDEFINITO + +[FES_SET] +USA SKIN + +[GHOST] +Fantasma + +[WIN_RSZ] +Impossibile impostare la nuova risoluzione + +[FET_APP] +PULSANTE SX, INVIO PER APPLICARE LE NUOVE IMPOSTAZIONI + +[FET_HRD] +IMPOSTAZIONI PREDEFINITE RIPRISTINATE + +[FET_MST] +STERZO VIA MOUSE + +[FEC_STR] +NUM ASTERISCO + +[FET_MIG] +SINISTRA, DESTRA, ROTELLA PER MODIFICARE + +[FET_CIG] +INDIETRO PER AZZERARE - PULSANTE SX, INVIO PER MODIFICARE + +[FET_RIG] +SELEZIONA UN NUOVO COMANDO PER QUESTA AZIONE O PREMI ESC PER ANNULLARE + +[FET_EIG] +IMPOSSIBILE ASSEGNARE UN COMANDO A QUESTA AZIONE + +[NO_PCCD] +Inserisci il disco 2 di Grand Theft Auto III nel lettore o premi ESC per annullare + +[CVT_ERR] +Spazio su disco esaurito; è necessario liberare dello spazio prima di poter procedere. Premi ESC per annullare. + +[FED_SUB] +SOTTOTITOLI + +[FET_DSN] +Skin predefinita.bmp + +[JM3] +'RAPINA AL FURGONE' + +[EBAL] +'GIVE ME LIBERTY' + +[LM4] +'ARMI PRONTE ALL'AZIONE' + +[ATUTOR2] +~g~Porta i pazienti all'ospedale! Qualsiasi scossone ridurrà le probabilità che arrivino vivi. + +[REPLAY] +REPLAY + +[FEC_SFT] +MAIUSC + +[CRED254] +RESPONSABILE STUDIO + +[CVT_CRT] +Impossibile convertire le texture per la scheda video. Deve collegarti come Amministratore per poterlo fare. Premi ESC per uscire. + +[FEM_ON] +ON + +[FEM_OFF] +OFF + +[FEM_YES] +SÌ + +[FEM_NO] +NO + +[FES_WAR] +Salvataggio, attendere... + +[FED_DLW] +Eliminazione, attendere... + +[FED_LDW] +Caricamento, attendere... + +[FEC_SLC] +Slot non valido + +[FED_LFL] +Caricamento posizione salvata fallito. Il gioco verrà riavviato. + +[FET_RSO] +RIPRISTINATE IMPOSTAZIONI ORIGINALI + +[FET_RSC] +HARDWARE NON DISPONIBILE - RIPRISTINATE IMPOSTAZIONI ORIGINALI + +{ re3 updates } +{ new languages } +[FEL_JAP] +GIAPPONESE + +[FEL_POL] +POLACCO + +[FEL_RUS] +RUSSO + +{ new display menus } +[FET_GFX] +IMPOSTAZIONI GRAFICA + +[FED_MIP] +MIP MAPPING + +[FED_AAS] +ANTI ALIASING + +[FED_FIL] +TEXTURE FILTERING + +[FED_BIL] +BILINEAR + +[FED_TRL] +TRILINEAR + +[FED_WND] +WINDOWED + +[FED_FLS] +FULLSCREEN + +[FEM_CSB] +CUTSCENE BORDERS + +[FEM_SCF] +SCREEN FORMAT + +[FEM_ISL] +MAP MEMORY USAGE + +[FEM_LOW] +LOW + +[FEM_MED] +MEDIUM + +[FEM_HIG] +HIGH + +[FEM_2PR] +PS2 ALPHA TEST + +[FEC_FRC] +FREE CAM + +{ Linux joy detection } +[FEC_JOD] +DETECT JOYSTICK + +[FEC_JPR] +Press any key on the joystick of your choice that you want to use on the game, and it will be selected. + +[FEC_JDE] +Detected joystick + +{ mission restart } +[FET_RMS] +RIGIOCA MISSIONE + +[FESZ_RM] +RIGIOCA? + +{ more graphics } +[FED_VPL] +VEHICLE PIPELINE + +[FED_PRM] +PED RIM LIGHT + +[FED_RGL] +ROAD GLOSS + +[FED_CLF] +COLOUR FILTER + +[FED_WLM] +WORLD LIGHTMAPS + +[FED_MBL] +MOTION BLUR + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MOBILE + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +AUTO + +{ controls } +[FEC_IVP] +INVERT PAD VERTICALLY + +{ map } +[FEM_TWP] +Toggle Waypoint + +[FEA_FMN] +RADIO SPENTA + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +XBOX 360 CONTROLLER + +[FEC_ONE] +XBOX ONE CONTROLLER + +[FEC_NSW] +NINTENDO SWITCH CONTROLLER + +[FEC_TYP] +GAMEPAD TYPE + +[FEC_CCF] +CONFIGURAZIONE + +[FEC_CF1] +CONFIGURAZIONE 1 + +[FEC_CF2] +CONFIGURAZIONE 2 + +[FEC_CF3] +CONFIGURAZIONE 3 + +[FEC_CF4] +CONFIGURAZIONE 4 + +[FEC_CDP] +SCHERMATA CONTROLLER + +[FEC_ONF] +A PIEDI + +[FEC_INC] +IN MACCHINA + +[FEC_VIB] +VIBRAZIONE + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/polish.txt b/utils/gxt/polish.txt new file mode 100755 index 0000000..98d373e --- /dev/null +++ b/utils/gxt/polish.txt @@ -0,0 +1,8117 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. +} + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789'$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~Hej! Wracaj do samochodu! + +[IN_VEH2] +~g~Do tej roboty jest ci potrzebna jakaś gablota! + +[IN_BOAT] +~g~Do tej roboty jest ci potrzebna łódź! + +[HEY] +~g~Nie idź sam, trzymaj się całej paczki! + +[HEY2] +~g~Nie rozdzielajcie się, niech cała grupa porusza się razem! + +[HEY3] +~g~Straciłeś z oczu swojego podopiecznego - wracaj i odszukaj 8-Balla! + +[HEY4] +~g~Jeżeli stracisz Misty, to Luigi zadba, abyś stracił życie! Wracaj po nią! + +[HEY5] +~g~One of the girls is AWOL, Go back and round her up! + +[HEY6] +~g~Twój honor jest związany z osobą Kanbu z Yakuzy. Musisz go chronić! + +[HEY7] +~g~Przydałaby się dodatkowa spluwa. Wracaj i zabierz ze sobą swój kontakt! + +[HEY8] +~g~Ochrona oznacza właśnie to, co podejrzewasz - ochraniaj starego dżentelmena z dalekiego wschodu! + +[HEY9] +~g~Chcesz posłuchać, co się dzieje w mieście? Odszukaj swój kontakt! + +[HELP2_A] +Jeżeli w trakcie biegu chcesz ~h~przyspieszyć~h~, naciśnij klawisz ~h~/~w~. + +[HELP3] +Sprintem można pokonywać wyłącznie krótkie odcinki, dopóki postać ma zapas sił. + +[HELP4_A] +Aby ~h~przyspieszyć~w~, naciśnij klawisz ~h~ ~k~~VEHICLE_ACCELERATE~~w~. + +[HELP4_D] +Aby ~h~przyspieszyć~w~, popchnij ~h~prawy drążek analogowy~w~ do góry. + +[HELP5_A] +Naciśnij klawisz ~h~ ~k~~VEHICLE_BRAKE~~w~, aby ~h~zahamować~w~ lub włączyć ~h~wsteczny bieg~w~, jeżeli pojazd już stoi. + +[HELP5_D] +Pociągnij ~h~prawy drążek analogowy~w~ do tyłu, aby ~h~zahamować~w~ lub włączyć ~h~wsteczny bieg~w~, jeżeli pojazd już stoi. + +[HELP6_A] +Aby skorzystać z ~h~hamulca ręcznego~w~, naciśnij klawisz ~h~ ~k~~VEHICLE_HANDBRAKE~~w~. + +[HELP6_C] +Aby skorzystać z ~h~hamulca ręcznego~w~, naciśnij klawisz ~h~ ~k~~VEHICLE_HANDBRAKE~~w~. + +[HELP6_D] +Aby skorzystać z ~h~hamulca ręcznego~w~, naciśnij klawisz ~h~ ~k~~VEHICLE_HANDBRAKE~~w~. + +[HELP7_A] +Aby ~h~wycelować~w~ karabin snajperski, naciśnij i przytrzymaj klawisz~h~ ~k~~PED_LOCK_TARGET~~w~. + +[HELP7_D] +Aby ~h~wycelować~w~ karabin snajperski, naciśnij i przytrzymaj klawisz~h~ ~k~~PED_LOCK_TARGET~~w~. + +[HELP8_A] +Naciśnij klawisz~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, aby ~h~przybliżyć ~w~widok przez lunetkę karabinu oraz klawisz~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, aby ~h~oddalić~w~ widok. + +[HELP9_A] +Naciśnij klawisz~h~ ~k~~PED_FIREWEAPON~~w~, aby oddać ~h~strzał~w~ z karabinu snajperskiego. + +[HELP10] +Gwiazda szeryfa oznacza, że jesteś poszukiwany przez policję. + +[HELP11] +Im więcej gwiazdek, tym wyższy jest poziom twojej złej sławy. + +[HELP13] +Niekiedy trzeba będzie skorzystać z tras, które nie są zaznaczone na radarze. + +[TIMER] +Jest to misja na czas - musisz ją wykonać, zanim licznik czasu osiągnie zero. + +[MISTY1] +~r~Misty nadaje się już tylko na łóżko w kostnicy! + +[OUT_VEH] +~g~Wysiądź z samochodu! + +[GARAGE] +Wprowadź samochód do garażu, a potem wyjdź na zewnątrz. + +[WANTED1] +~g~Zgub gliniarzy i obniż swój poziom złej sławy! + +[NODOORS] +~g~To nie są sardynki! Załatw furę, która pomieści więcej osób. + +[TRASH] +~g~Doprowadziłeś gablotę do ruiny! Połataj ją trochę! + +[WRECKED] +~r~Samochód jest zniszczony! + +[HORN] +~g~Naciśnij klakson. + +[NOMONEY] +~g~Masz za mało forsy! + +[OUTTIME] +~r~Za wolno, koleś, za wolno! + +[SPOTTED] +~r~Gonią cię! + +[REWARD] +NAGRODA $~1~ + +[GAMEOVR] +KONIEC GRY + +[Z] +Wartość osi Z: ~1~ + +[M_FAIL] +MISJA NIEUDANA! + +[M_PASS] +MISJA ZALICZONA! $~1~ + +[O_PASS] +ROBOTA ZAKOŃCZONA + +[O_FAIL] +ROBOTA NIEUDANA + +[DEAD] +KONIEC Z TOBĄ! + +[BUSTED] +WPADKA! + +[S_PROMP] +Kiedy nie jesteś w trakcie misji, możesz w tym miejscu ~h~zapisać stan gry~w~. Wiąże się to z upływem sześciu godzin w grze. + +[NUMBER] +~1~ + +[SCORE] +$~1~ + +[LOADCAR] +ŁADOWANIE POJAZDU (NACIŚNIJ L1, ABY ANULOWAĆ) + +[CARSOFF] +Wyłączone samochody: + +[CARS_ON] +Uruchomione samochody: + +[TEXTXYZ] +Zapisywanie współrzędnych w pliku... + +[CHEATON] +Tryb ułatwień WŁĄCZONY + +[CHEATOF] +Tryb ułatwień WYŁĄCZONY + +[UZI_IN] +Amu-Nacja zaczyna sprzedawać uzi! + +[IMPORT1] +Wyjdź na zewnątrz i poczekaj na swój samochód. + +[PAGEB1] +Pistolet dostarczony do kryjówki + +[PAGEB2] +Uzi dostarczone do kryjówki + +[PAGEB3] +Pancerz dostarczony do kryjówki + +[PAGEB4] +Obrzyn dostarczony do kryjówki + +[PAGEB5] +Granaty dostarczone do kryjówki + +[PAGEB6] +Koktajle Mołotowa dostarczone do kryjówki + +[PAGEB7] +AK 47 dostarczony do kryjówki + +[PAGEB8] +Karabin snajperski dostarczony do kryjówki + +[PAGEB9] +M16 dostarczony do kryjówki + +[PAGEB10] +Wyrzutnia rakiet dostarczona do kryjówki + +[PAGEB11] +Miotacz ognia dostarczony do kryjówki + +[WANT_A] +Aresztowanie grozi ci dopiero wtedy, kiedy posiadasz ~h~złą sławę. + +[WANT_B] +Twój ~h~poziom złej sławy~w~ przedstawia rząd gwiazdek znajdujących się w prawym górnym rogu ekranu. + +[WANT_C] +W tym momencie twój ~h~poziom złej sławy~w~ wynosi jeden... + +[WANT_D] +dwa... + +[WANT_E] +trzy... + +[WANT_F] +W miarę wzrostu ~h~złej sławy~w~ ścigać cię będą coraz potężniejsze siły policyjne. + +[WANT_G] +Kiedy zaliczysz ~h~'wpadkę'~w~, zostajesz odwieziony na najbliższy posterunek policji. + +[WANT_H] +Gliniarze zarekwirują ci całą broń i będziesz musiał wypłacić im małą łapówkę. + +[WANT_I] +Misja, którą właśnie wykonywałeś, zostanie uznana za nieudaną. + +[WANT_J] +W miarę rozwoju gry odkryjesz sposoby zmniejszania swojego poziomu złej sławy. + +[WANT_K] +Jeżeli jesdziesz samochodem, ~h~WARSZTATY LAKIERNICZE~w~ umożliwiają ~h~usunięcie złej sławy. + +[HEAL_B] +Kiedy zostajesz ~h~'załatwiony'~w~, trafiasz do najbliższego szpitala. + +[HEAL_C] +Tracisz całą broń oraz musisz zapłacić lekarzom trochę forsy za ich wysiłki. + +[HEAL_E] +W trakcie gry poznasz rozmaite metody leczenia bądź chronienia głównego bohatera. + +[DAM] +USZKODZENIA: + +[KILLS] +OFIARY: + +[FARES] +PRZEJAZDY: + +[BULL] +SZTABKI: + +[EVID] +DOWODY: + +[HEALTH] +STAN POJAZDU: + +[COLLECT] +ZEBRANO: + +[BOMB] +Wprowadź samochód do garażu, aby przymocować do niego ~h~bombę~w~. Cena - ~h~$1000. + +[SAVE1] +Aby ~h~zapisać stan gry~w~, przejdź przez drzwi. Jeżeli jesteś w trakcie misji, nie można zapisać gry. + +[SAVE2] +Samochody pozostawione w tym garażu zostaną zachowane wraz z zapisem stanu gry. + +[AMMU] +Wejdź do Amu-nacji, aby zakupić broń. + +[BRIDGE1] +Kiedy naprawa Mostu Callahan zostanie ukończona, będziesz mógł przejechać nim na Wyspę Staunton. + +[TUNNEL] +Kiedy Tunel Porter zostanie otwarty, będziesz mógł przejechać na Wyspę Staunton. + +[LUIGI] +MISJE LUIGIEGO + +[TONI] +MISJE TONIEGO + +[JOEY] +MISJE JOEYA + +[FRANK] +MISJE SALVATORE + +[DIABLO] +MISJE DIABLO + +[ASUKA] +MISJE ASUKI + +[B_SITE] +MISJE PODMIEJSKIE ASUKI + +[KENJI] +MISJE KENJIEGO + +[RAY] +MISJE RAYA + +[LOVE] +MISJE LOVE'A + +[YARDIE] +MISJE DLA GANGU YARDIE + +[HOOD] +MISJE DLA GANGU HOOD + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Atlantic Quays + +[PORT_E] +Portland Harbor + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Plaza Portland + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Dz. Czerwonych Świateł + +[TOWERS] +Wzgórza Hepburn + +[HARWOOD] +Harwood + +[ROADBR1] +Most Callahan + +[ROADBR2] +Most Callahan + +[TUNNELP] +Tunel Porter + +[BOMB1] +Warsztat 8-Balla. + +[COM_ZON] +Wyspa Staunton + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Kampus Liberty + +[CONSTRU] +Fort Staunton + +[PARK] +Park Belleville + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Port Lotniczy Francis + +[PROJECT] +Wichita Gardens + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Tama Cochrane + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Karetka + +[CAR_2] +Straż pożarna + +[CAR_3] +Radiowóz + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Hipcio + +[CAR_7] +Samochód FBI + +[CAR_8] +Konwojowóz + +[CAR_9] +Moonbeam + +[CAR_10] +Autokar + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Śmieciożer + +[CAR_14] +Patriot + +[CAR_15] +Pan Smakołyk + +[CAR_16] +Muł + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Autobus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Demon + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Ogier + +[CAR_37] +Taksówka + +[CAR_38] +Taksówka + +[CAR_39] +Buggy + +[LUIGIS] +Lokal Luigiego + +[GOAWAY] +~g~Już podjąłeś się jednej misji! + +[LUIGGO] +~g~Luigi sprawdza nowe dziewczyny. Przyjdź później! + +[JOEYGO] +~g~Joey wyszedł do miasta z Misty. Przyjdź później. + +[TONIGO] +~g~Toni zabrał swoją Mamuśkę do opery - wpadnij kiedy indziej! + +[KEMUGO] +~g~Maria i Kemuri mają chwilowo inne obowiązki - wpadnij później! + +[KENJGO] +~g~Kenji jest na naradzie Yakuzy. Wpadnij innym razem! + +[RAYGO] +~g~Ray kręci się przy innych kiblach - wpadnij później! + +[LOVEGO] +~g~Donald Love chwilowo zajmuje się innymi sprawami. Umów się z nim na późniejszą godzinę! + +[KENSGO] +~g~Kenji jest zajęty! Wpadnij później! + +[HOODGO] +~g~Hoods są aktualnie niedostępni! + +[WRONGT1] +~g~Jeżeli szukasz zajęcia, wróć między 5:00 a 21:00. + +[WRONGT2] +~g~Jeżeli szukasz zajęcia, wróć między 6:00 a 14:00. + +[WRONGT3] +~g~Jeżeli szukasz zajęcia, wróć między 15:00 a 00:00. + +[GUN_1A] +Użyj klawiszy ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~ oraz ~h~~k~~PED_CYCLE_WEAPON_LEFT~ ~w~, aby przełączać się między kolejnymi rodzajami broni. + +[GUN_2A] +Przytrzymaj klawisz ~h~~k~~PED_LOCK_TARGET~ ~w~, aby ~h~wykonać autocelowanie~w~ i naciśnij klawisz ~h~ ~k~~PED_FIREWEAPON~ ~w~, aby ~h~otworzyć ogień! Spróbuj postrzelać do celu... + +[GUN_2C] +Przytrzymaj klawisz ~h~~k~~PED_LOCK_TARGET~ ~w~, aby ~h~wykonać autocelowanie~w~ i naciśnij klawisz ~h~ ~k~~PED_FIREWEAPON~ ~w~, aby ~h~otworzyć ogień! Spróbuj postrzelać do celu... + +[GUN_2D] +Przytrzymaj klawisz ~h~~k~~PED_LOCK_TARGET~ ~w~, aby ~h~wykonać autocelowanie~w~ i naciśnij klawisz ~h~ ~k~~PED_FIREWEAPON~ ~w~, aby ~h~otworzyć ogień! Spróbuj postrzelać do celu... + +[GUN_3A] +Trzymając wciśnięty klawisz ~h~~k~~PED_LOCK_TARGET~,~w~ naciśnij klawisz ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ lub klawisz ~h~~k~~PED_CYCLE_TARGET_RIGHT~ , aby przełączać się między celami. + +[GUN_3B] +Trzymając wciśnięty klawisz ~h~~k~~PED_LOCK_TARGET~,~w~ naciśnij klawisz ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ lub klawisz ~h~~k~~PED_CYCLE_TARGET_RIGHT~ , aby przełączać się między celami. + +[GUN_4A] +Trzymając wciśnięty klawisz ~h~~k~~PED_LOCK_TARGET~~w~, możesz chodzić lub biegać, a celownik cały czas pozostanie zablokowany na wybranym celu. + +[GUN_4B] +Trzymając wciśnięty klawisz ~h~~k~~PED_LOCK_TARGET~~w~, możesz chodzić lub biegać, a celownik cały czas pozostanie zablokowany na wybranym celu. + +[GUN_5] +Możesz przećwiczyć wybieranie celów i prowadzenie ognia na tych papierowych celach. Po treningu wróć do wykonywania misji. + +[TAXI1] +~g~Poszukaj pasażera. + +[FARE1] +~g~Cel podróży ~w~'Klub Seksownego Kociaka Miauu'~g~ w Dzielnicy Czerwonych Świateł. + +[FARE2] +~g~Cel podróży ~w~Super Przecena~g~ w Portland View. + +[FARE3] +~g~Cel podróży ~w~'stara szkoła'~g~ w Chinatown. + +[FARE4] +~g~Cel podróży ~w~'kawiarenka Tłustego Joe'~g~ w Callahan Point. + +[FARE5] +~g~Cel podróży ~w~'Amu-Nacja'~g~ w Dzielnicy Czerwonych Świateł. + +[FARE6] +~g~Cel podróży ~w~'Auta na Kredyt'~g~ w Saint Mark's. + +[FARE7] +~g~Cel podróży ~w~'bar topless 'U Woody'ego''~g~ w Dzielnicy Czerwonych Świateł. + +[FARE8] +~g~Cel podróży ~w~'Bistro Marcos'~g~ w Saint Mark's. + +[FARE9] +~g~Cel podróży ~w~'warsztat importowo-eksportowy'~g~ w Portland Harbor. + +[FARE10] +~g~Cel podróży ~w~'Smażony Makaron'~g~ w Chinatown. + +[FARE12] +~g~Cel podróży ~w~'stadion piłkarski'~g~ w Aspatrii. + +[FARE13] +~g~Cel podróży ~w~'kościół'~g~ w Bedford Point. + +[FARE14] +~g~Cel podróży ~w~'kasyno'~g~ w Torrington. + +[FARE15] +~g~Cel podróży ~w~biblioteka Liberty~g~ w Kampusie Liberty. + +[FARE16] +~g~Cel podróży ~w~galeria handlowa~g~ w okolicach Belville Park. + +[FARE17] +~g~Cel podróży ~w~muzeum~g~ w Newport. + +[FARE18] +~g~Cel podróży ~w~siedziba AmCo~g~ w Torrington. + +[FARE19] +~g~Cel podróży ~w~Bolt Burgers~g~ w Bedford Point. + +[FARE20] +~g~Cel podróży ~w~park~g~ w Belville. + +[FARE21] +~g~Cel podróży ~w~Port Lotniczy im. Francisa. + +[FARE22] +~g~Cel podróży ~w~'Tama Cochrane'. + +[FARE24] +~g~Cel podróży ~w~'szpital' ~g~w Pike Creek. + +[FARE25] +~g~Cel podróży ~w~'park'~g~ w Soherside Vale. + +[FARE26] +~g~Cel podróży ~w~'North West Towers'~g~ w Wichita Gardens. + +[NEW_TAX] +WIĘKSZE! SZYBSZE! MOCNIEJSZE! Nowe taksówki Borgnine rozpoczynają pracę w Harwood. Dzwoń już dziś: 555-BORGNINE. + +[TSCORE2] +$~1~ + +[IN_ROW] +~1~Premia za SEKWENCJĘ! $~1~ + +[TTUTOR] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyć misje w taksówce. + +[TTUTOR2] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyć misje taksówkarskie. + +[A_TIME] ++~1~ sekund + +[A_FULL] +~r~Karetka jest pełna! + +[A_RANGE] +~g~Radiostacja w karetce ma zbyt mały zasięg, zbliż się bardziej w stronę szpitala! + +[FTUTOR] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyc misje strażackie. + +[FTUTOR2] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyc misje strażackie. + +[F_PASS1] +Pożar ugaszony! + +[F_RANGE] +~g~Radiostacja w samochodzie strażackim ma zbyt mały zasięg - zbliż się bardziej w stronę remizy! + +[C_BREIF] +~g~Podejrzany ostatnio widziany był w rejonie ~a~. + +[C_RANGE] +~g~Radiostacja w samochodzie ma zbyt mały zasięg, zbliż się bardziej w stronę komisariatu! + +[DODO_FT] +Leciałeś przez ~1~ sekund! + +[EBAL_A] +Znam takie miejsce na skraju Dzielnicy Czerwonych Świateł, w którym możemy się zamelinować na jakiś czas, + +[EBAL_A1] +ale moje ręce są do niczego, więc lepiej ty prowadź, brachu. + +[EBAL_1] +Naciśnij klawisz ~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pojazdu. + +[EBAL_1B] +Naciśnij klawisz ~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pojazdu. + +[EBAL_2] +~g~Wracaj do samochodu! + +[EBAL_3] +To jest ~h~radar~w~. Korzystaj z niego podczas poruszania się po mieście. Jedź za ~h~kropką~w~ na ~h~radarze~w~, aby dotrzeć do kryjówki! + +[EBAL_D] +Jest tu taki jeden gość, który zna kogo trzeba. Nazywa się Luigi. + +[EBAL_D1] +Niejedno razem przeszliśmy. Dla ciebie też pewnie znajdzie się jakaś robota. Zajrzyjmy do niego! + +[EBAL_E] +Wpadniemy tam, a ja przedstawię cię komu trzeba. + +[EBAL_I] +Szef zaraz wyjdzie do ciebie... + +[EBAL_J] +8-Ball ma jakiś interes na górze. + +[EBAL_K] +Może zrobisz coś dla mnie? + +[EBAL_L] +Trzeba podwieźć jedną z moich dziewczynek. Załatw gablotę i odbierz Misty z kliniki. Przywieź ją tutaj. + +[EBAL_N] +I lepiej cały czas gap się tylko na drogę! + +[EBAL_4] +~r~8-Ball nie żyje~! + +[EBAL_5] +~g~Załatw samochód! + +[EBAL_6] +~g~Zabierz Misty! + +[LM1] +'DZIEWCZYNKI LUIGIEGO' + +[LM2] +'MOJE DZIWKI NIE ĆPAJĄ!' + +[LM3] +'WOŻĄC PANNĘ MISTY' + +[LM5] +'ACH, CÓŻ TO BYŁ ZA BAL...' + +[LM1_2] +~g~Zabierz Misty do klubu 'U Luigiego'. + +[LM1_3] +~g~Naciśnij klakson, aby zaprosić dziewczynę do samochodu. + +[LM1_6] +~g~Wracaj do samochodu! + +[LM1_7] +Zatrzymaj samochód obok Misty i poczekaj, aż wsiądzie. + +[LM1_8] +Możesz wrócić do Luigiego i zapytać go o pracę albo pozwiedzać Liberty City. + +[LM2_A] +Na ulicy pojawił się nowy towar, HEROINA. + +[LM2_E] +Jakiś mądrala wciska ten syf moim dziewczynkom na Portland Harbor. + +[LM2_B] +Jedź tam i zapoznaj jego twarz z bejsbolem! + +[LM2_G] +Należy mi się jakaś rekompensata za tę zniewagę! + +[LM2_1] +~g~Weź jego samochód i przemaluj go. + +[LM2_2A] +Użyj klawisza ~h~ ~k~~PED_FIREWEAPON~~w~, aby zadawać ciosy ~h~pięścią ~w~i ~h~nogą~w~ lub ~h~uderzyć kijem ~w~! + +[LM2_2C] +Użyj klawisza ~h~ ~k~~PED_FIREWEAPON~~w~, aby zadawać ciosy ~h~pięścią ~w~i ~h~nogą~w~ lub ~h~uderzyć kijem ~w~! + +[LM2_2D] +Użyj klawisza ~h~ ~k~~PED_FIREWEAPON~~w~, aby zadawać ciosy ~h~pięścią ~w~i ~h~nogą~w~ lub ~h~uderzyć kijem ~w~! + +[LM2_3] +~g~Schowaj samochód w kryjówce Luigiego! + +[LM2_4] +~g~Przemaluj samochód! + +[LM3_A] +Ej, muszę z tobą pogadać... Spoko, Mick, dokończymy później. + +[LM3_B] +Jak leci, młody? + +[LM3_C] +Syn Dona, Joey, chce się zabawić ze swoją ulubioną dziewczynką, Misty. + +[LM3_D] +Jedź po nią na Wzgórza Hepburn... + +[LM3_E] +ale uważaj, to terytorium gangu Diablo. + +[LM3_F] +Potem odwieź ją do warsztatu Joeya w Trenton, byle szybko. + +[LM3_H] +więc gap się na drogę, a nie na cycki Misty! + +[LM3_2] +~g~Zabierz Misty do Joeya. + +[LM3_4] +~g~Jedź po Misty! + +[LM3_5] +To ty pracujesz teraz jako szoferak dla Luigiego, co? Najwyższy czas, od dawna potrzebujemy zaufanego kierowcy! + +[LM3_7] +Zajmę się tobą za chwilkę, iskierko. + +[LM3_10] +~g~Zdobądź samochód! + +[LM4_B] +Jedź tam i załatw dla mnie ten problem. + +[LM4_C] +Jeżeli potrzebujesz spluwy, wpadnij na zaplecze sklepu Amu-Nacja, naprzeciwko stacji metra. + +[LM5_A] +Przy Moście Callahan, w budynku starej szkoły trwa Bal Policjanta. + +[LM5_B] +Skoro budynek jest stary, to i goście zapewne będą szukać rozrywek 'w starym stylu'. + +[LM5_C] +Moje dziewczyny pracują na ulicach w całym mieście. + +[LM5_D] +Zawieź je na bal, aby mogły solidnie popracować. + +[LM5_1] +~g~Jeżeli zabierzesz za dużo dziewczyn, to się poobijają w środku!~g~ Najpierw wysadź te, które już masz, potem wróć po następne. + +[LM5_2] +~r~Jedna z dziewczyn Luigiego jest już tylko kupą padliny! + +[LM5_3] +~g~Jest ci potrzebny samochód! + +[LM5_4] +~g~Zgarnij dziewczyny pracujące w St. Mark's. + +[LM5_5] +~g~Zawieź dziewczyny na Bal Policjanta! + +[LM5_8] +~g~Dziewczyny na balu: ~1~ + +[JM2] +'ŻEGNAJ 'OKRĄGŁY' LEE CHONG' + +[JM4] +'SZOFER CIPRIANIEGO' + +[JM5] +'TRUP W BAGAŻNIKU' + +[JM1_1] +~g~Zabierz samochód Forelliego do warsztatu 8-Balla na północ stąd, zaraz za salonem 'Auta na Kredyt'. + +[JM1_2] +~g~Potem zaparkuj wózek z powrotem na właściwym miejscu pod Bistro Marcos. + +[JM1_3] +~g~Uaktywnij bombę i spadaj stamtąd! + +[JM1_4] +~g~Niszczysz samochód! Napraw go! + +[JM1_5] +~g~Bomba w samochodzie nie została uzbrojona! + +[JM1_6] +~g~Zaparkuj samochód we właściwej pozycji. + +[JM1_8A] +~y~Hej, przecież to mój kumpel! + +[JM1_8B] +~y~Warsztat jest zautomatyzowany. Po prostu wjedź do środka, zatrzymaj samochód, a obsługa załatwi całą resztę. + +[JM1_8C] +~y~Pierwszy raz montujemy ładunek za darmo, ale za każdym następnym razem będziesz musiał zapłacić. + +[JM2_A] +'Okrągły' Lee Chong handluje prochami dla jakiegoś nowego gangu z Kolumbii... Czy z Kolorado... Nieważne... + +[JM2_B] +Nie pamiętam. Zresztą, kogo to obchodzi. + +[JM2_D] +Ten szczurek właśnie sprzedaje ostatnie sajgonki, rozumiesz? + +[JM2_E] +Masz go zdmuchnąć! + +[JM2_G] +Załatw sobie dziewiątkę. Znajdziesz ten sklep, nie? + +[JM2_H] +Tylko pamiętaj, w Chinatown lepiej dobrze pilnować własnego dupska. To terytorium Triady. + +[JM3_A] +W porządku, mam zamiar skubnąć ciężarówkę z wypłatami. + +[JM3_B] +Codziennie wyjeżdża na miasto z okolic Chinatown. + +[JM3_C] +Zwykłe kule nawet nie zarysują jej pancerza, więc musisz po prostu zdobyć ciężki samochód i zepchnąć ją z drogi. + +[JM3_D] +Jak mocno walniesz, to ci tchórze z ochrony zwieją, gdzie pieprz rośnie. + +[JM3_E] +Wtedy zabierz ciężarówkę do magazynu w dokach - moi ludzie już się nią tam zajmą. + +[JM3_F] +Ciężarówka nie będzie na ciebie czekać, dlatego lepiej się streszczaj. + +[JM3_1] +~g~Zabierz ciężarówkę do kryjówki. + +[JM3_2] +~g~Taranuj ciężarówkę dopóki poziom jej uszkodzeń nie przekroczy 70 procent. + +[JM4_B] +O, to jest ten gość, o którym ci mówiłem! + +[JM4_C] +Posłuchaj, on nie jest Włochem i żaden z niego mechanik, ale potrafi radzić sobie z problemami. + +[JM4_D] +To jest Pops Capo, a to Tony Cipriani. + +[JM4_E] +Tak, Tony Cipriani to ja. + +[JM4_F] +Podrzuć go do restauracji Mamuśki w St. Marks. + +[JM4_G] +Jeszcze jedno. Posłuchaj, planuję taką robótkę, do której potrzebny jest dobry kierowca. Wpadnij później, to pogadamy, OK? + +[JM4_2] +Zaczekaj tutaj. Nie gaś silnika - to nie jest wizyta towarzyska. + +[JM4_3] +To pułapka Triad! Zabierz nas stąd, młody! + +[JM4_4] +Triady myślą, że mogą ze mną zadzierać... ZE MNĄ! + +[JM4_6] +Ostrożnie z tym wozem! Mówiłem, bez numerów! + +[JM4_7] +~g~Zabierz Toniego do restauracji jego matki. + +[JM4_8] +~r~Toni został załatwiony! + +[JM5_A] +Pięknie! Po prostu pięknie. + +[JM5_B] +Oto i facet, którego mi potrzeba! + +[JM5_D] +Jeden z braci Forelli myślał, że jest bystrzejszy niż woda w kiblu, a więc doczekał się odpowiedniej kary. + +[JM5_E] +Zabierz samochód z ciałem do zgniatarki w Harwood, OK? + +[JM5_1] +~g~Zabierz samochód do zgniatarki! + +[JM5_2] +~g~To bracia Forelli! + +[JM6_A] +Ale z niej sztuka, co? + +[JM6_B] +W porząsiu, posłuchaj. Załatw sobie gablotę i jedź do kryjówki w St. Marks. Tam zabierzesz paru moich kumpli. + +[JM6_C] +Robią napad na bank i potrzebują kierowcy. + +[JM6_D] +Dałem im słowo, że znasz się na tym fachu, więc nie spieprz sprawy, dobra? + +[JM6_E] +Dowieź ich do banku przed godziną piątą i nie waż się spóźnić nawet minutę. + +[JM6_2] +Nie gaś silnika, zaraz wracamy. + +[JM6_3] +Zabierz nas stąd!! + +[JM6_4] +Zgub gliniarzy i dowież nas do kryjówki! + +[JM6_6] +~g~Skombinuj jakiś samochód, który będzie trochę mniej rzucał się w oczy. + +[JM6_7] +~g~Musisz zabrać wszystkich 3 bandytów, aby móc przeprowadzić skok! + +[TM1] +'ZABIERZ BRUDY DO PRALNI' + +[TM2] +'HARACZ' + +[TM3] +'SALVATORE ZWOŁUJE NARADĘ' + +[TM4] +'TRIADY I TROSKI' + +[TM5] +'SMAŻONE RYBY' + +[TONI_P] +Mam dla ciebie pilną pracę, Toni! + +[TM1_A] +~w~Siadaj, synu! Siadaj, zamknij jadaczkę i słuchaj. + +[TM1_B] +~w~Więc pralnie nie chcą płacić haraczu, co? + +[TM1_C] +~w~Triady myślą, że mogą mi podskakiwać? + +[TM1_D] +~w~Nauczymy tych dmuchanych twardzieli, co to znaczy być prawdziwym twardzielem. + +[TM1_E] +~w~Tak, damy im lekcję pokory. Mój syn nie będzie znosić upokorzeń od jakiejś Triady! + +[TM1_F] +~w~Twój ojciec, Panie świeć nad jego duszą, jeszcze na Sycylii trzymał ich krótko i nie pozwalał, żeby Triady wciskały mu kit.v + +[TM1_G] +~w~Przepraszam, Mamo. Tak jest, Mamo. + +[TM1_H] +~w~Chcę, żebyś zniszczył ciężarówki z pralni + +[TM1_I] +~w~i rozdeptał każdego sługusa Triad, który wejdzie ci w drogę. + +[TM1_J] +~w~8-Ball załatwi ci wszystko, co jest potrzebne do tej roboty. + +[TM2_A] +~w~TONI gdzieś polazł, znowu będzie straszyć ludzi albo przynajmniej usiłować kogoś przestraszyć. + +[TM2_AA] +~w~Nigdy nie będzie nawet w połowie takim twardzielem jak jego Papa. Zostawił dla ciebie kartkę na stole. + +[TM2_B] +~w~Pralnie zgodziły się zapłacić - spisałeś się dobrze, synu! + +[TM2_C] +~w~Odbierz forsę i przywieź ją tutaj. Uważaj na Triady. + +[TM2_D] +~w~Możliwe, że będą chcieli podłożyć nam świnię, ale nie daj sobie wciskać żadnego gówna. + +[TM2_E] +~w~Nikt, powtarzam, nikt nie zadziera z TONIM CIPRIANI! + +[TM2_1] +~g~Odwieź forsę Toniemu!!! + +[TM2_2] +~g~Zdmuchnąłeś ich wszystkich! + +[TM3_MA] +~w~Nie wiem, gdzie on się podziewa! + +[TM3_MB] +~w~Jak pragnę zdrowia, ten mój chłopak czasami jest taki głupi. + +[TM3_MC] +~w~Jego ojciec był zupełnie inny. Zawsze silny, męski, u steru wydarzeń... + +[TM3_A] +~w~Don Salvatore zwołał naradę. + +[TM3_B] +~w~Masz odebrać z warsztatu limuzynę i jego chłopaka, Joeya. + +[TM3_C] +~w~Potem zabierz Luigiego z klubu i wróć tutaj po mnie. + +[TM3_D] +~w~Razem pojedziemy do domu szefa. + +[TM3_E] +~w~Triady nie znają umiaru. + +[TM3_F] +~w~Chcą wojny, będą mieli wojnę. + +[TM3_G] +~w~Teraz zmykaj. + +[TM3_1] +~g~Odbierz limuzynę od Joeya. + +[TM3_2] +~g~Teraz jedź po Luigiego. + +[TM3_3] +~g~Teraz jedź po Toniego. + +[TM3_4] +~g~Zawieź ferajnę do rezydencji Salvatore. + +[TM3_5] +~y~Triady przygotowały zasadzkę! + +[TM4_B] +~w~A więc WOJNA! Triady używają jako przykrywki fabryki przetwórstwa ryb. + +[TM4_C] +~w~Większość ich interesów odbywa się na targu rybnym w chińskiej dzielnicy. + +[TM4_D] +~w~Pralnie nadal wiszą nam haracz za ochronę. + +[TM4_E] +~w~Żółtki myślą, że Triady ich obronią, więc czas wymierzyć im stosowną karę. + +[TM4_F] +~w~Weź tych dwóch chłopaków i załatwcie szefów Triady! + +[TM4_G] +~w~Tam, u diabła, jeżeli nadarzy się okazja, to załatwcie też paru żołnierzy Triady. + +[TM4_GAT] +~g~Do środka może wjechać tylko ciężarówka Triad. + +[TM5_B] +~w~Wystarczy już tego pitolenia. + +[TM5_C] +~w~Czas wykończyć Triady w Liberty raz na zawsze! + +[TM5_D] +8-Ball założył ładunek wybuchowy w śmieciarce. + +[TM5_E] +~w~To jest bomba z zegarem czasowym, więc jak się spóźnisz, nie pozostanie po tobie żaden ślad. Jedź po śmieciarkę. + +[TM5_F] +~w~Tylko ostrożnie, 8-Ball mówi, że mechanizm jest cholernie czuły i byle wstrząs może spowodować wybuch! + +[TM5_G] +~w~Zaparkuj między cysternami z benzyną i znikaj stamtąd! + +[TM5_H] +~w~Zaparkuj pomiędzy zbiornikami z gazem i spadaj gdzie pieprz rośnie! + +[TM5_I] +~w~Chcę, żeby na miasto spadł deszcz makreli. + +[TM5_J] +~w~Zobaczysz, tym razem to nie żadne wielkanocne bum-bum, tylko prawdziwa plaga biblijna! + +[FM2] +'RÓWNO Z TRAWĄ' + +[FM4] +'OSTATNIE PROŚBY' + +[FM1_A] +~w~Ja i moi ludzie musimy obgadać parę kwestii, + +[FM1_B] +~w~a więc ty zapewnisz mojej dziewczynie rozrywkę na wieczór. + +[FM1_C] +~w~HEJ, MARIA! RUSZ DUPĘ! + +[FM1_D] +~w~Głupia suka, zawsze się tak zachowuje. + +[FM1_E] +~w~A oto i ona, jedyna w swoim rodzaju królowa piękności! + +[FM1_F] +~w~Co tam robiłaś tyle czasu? + +[FM1_G] +~w~Cokolwiek to było, na pewno straciłem na tym jakieś pieniądze. + +[FM1_H] +~w~Chyba nie myślisz, że jestem tu, bo lubię z tobą gadać? + +[FM1_I] +~w~Wsiadaj do samochodu i zamknij swoją wielką gębę. + +[FM1_J] +~w~Możesz zabrać limuzynę, ale przyprowadź ją z powrotem w jednym kawałku, zrozumiano? + +[FM1_K] +~w~I uważaj na nią! Ona lubi pakować się w kłopoty! + +[FM1_L] +~w~Dość! Twój nowy piesek gończy na pewno zna już te historyjki, + +[FM1_M] +~w~a poza tym to przecież kawał chłopa! + +[FM1_N] +~w~Hej, piesku! Odwiedzimy Chico i załatwimy jakieś dopalacze na imprezę. + +[FM1_P] +~g~To właśnie Chico, podjedź do niego. + +[FM1_S] +~w~Jak sobie życzysz, moja pani. + +[FM1_TT] +~w~NALOT POLICYJNY! + +[FM1_1] +~g~Wracaj do limuzyny! + +[FM1_2] +~g~Wsiadaj do limuzyny! + +[FM1_3] +~r~Jeżeli zostawisz Marię, Salvatore każe cię obić jak psa! Zawróćj i zabierz ją ze sobą! + +[FM1_4] +~g~Zostawiłeś kobietę Dona! Wracaj do magazynu i zaczekaj na Marię! + +[FM1_5] +~g~Zawieź Marię do domu Salvatore tak, aby włos jej z głowy nie spadł! + +[FM1_6] +~g~Chico nie będzie czekał wiecznie - zabierz Marię na wybrzeże! + +[FM1_7] +~r~Maria nie żyje! Salvatore nie ucieszy się z tej wiadomości... + +[FM1_8] +~r~Załatwiłeś dostawcę Marii!!! + +[FM2_J] +Zostaw nas na chwilkę samych. + +[FM2_A] +Kartel Kolumbijski produkuje HEROINĘ gdzieś na terenie Liberty. + +[FM2_K] +ale nadal nie wiemy, gdzie, a oni działają tak, jakby z góry znali wszystkie nasze posunięcia. + +[FM2_L] +Za barem 'U Luigiego' pracuje pewien gość. Nazywa się Kudłaty Bob. + +[FM2_M] +Ostatnio wydaje więcej kasy niż mógłby zarobić. + +[FM2_N] +Zazwyczaj po pracy wraca do domu taksówką. Jedź za nim. + +[FM2_O] +A jeżeli okaże się, że to nasz kret... Zabij go! + +[FM2_F] +Oto nasz mały przyjaciel, pan wielka gęba we własnej osobie. + +[FM2_G] +Ktoś cię śledził? Wiesz, że to, co się tu dzieje, to nasz mały sekret. + +[FM2_H] +Nie, nikt mnie nie śledził. Masz mój towar? + +[FM2_I] +Masz swoje prochy, śmieciu, a teraz mów. + +[FM2_P] +A więc rodzina Leone prowadzi teraz wojnę na dwa fronty. + +[FM2_Q] +Z Triadami toczą wojnę o terytorium i wygląda na to, że żadna ze stron nie ma zamiaru się poddać. + +[FM2_R] +Z drugiej strony, Joey Leone wplątał się w krwawe porachunki z rodziną Forellich. + +[FM2_S] +Rodzina Leone z każdym dniem traci ludzi i wpływy w mieście. + +[FM2_T] +Salvatore zaczyna zachowywać się jak niebezpieczny dla otoczenia paranoik. Podejrzewa wszystkich i wszędzie wietrzy spisek. + +[FM2_U] +Ale jeżeli wszyscy są tak lojalni jak ty, to nie ma się o co martwić, prawda? + +[FM2_1] +~g~To Kudłaty Bob! + +[FM2_2] +~g~Kudłaty wyszedł z klubu - śledź go! + +[FM2_5] +~g~Zabierz go do Portland Harbor. + +[FM2_6] +~r~Kudłaty nie wsiądzie do porozbijanej taksówki! + +[FM2_7] +~r~Kudłaty zwiał! Spotkanie odwołane! + +[FM2_8] +~g~Walnij Kudłatego Boba! + +[FM2_9] +~r~Kudłaty Bob nie żyje! + +[FM2_10] +~r~Kudłaty ucieka! + +[FM2_11] +~g~Zaparkuj przed klubem 'U Luigiego'! Kudłaty Bob powinien niedługo wyjść. + +[FM2_12] +~r~Zgubiłeś go! + +[FM3_A] +~w~Powinniśmy sprzątnąć tych łajdaków z Kartelu, + +[FM3_B] +~w~ale dopóki mamy na głowie wojnę z Triadami, nie mamy na to dość sił. + +[FM3_C] +~w~Kartel zarabia ogromne pieniądze na sprzedaży tej syfiastej HEROINY. + +[FM3_D] +~w~Jeżeli przypuścimy otwarty atak, rozsmarują nas na miazgę. + +[FM3_E] +~w~Zapewne produkują prochy na tym wielkim statku, do którego doprowadził cię Kudłaty. + +[FM3_F] +~w~Dlatego właśnie musimy działać z głową. I to z twoją głową. + +[FM3_G] +~w~Chciałbym prosić cię, abyś zrobił mi osobistą przysługę i zniszczył tę fabrykę HERY, Salvatore. + +[FM3_H] +~w~Jeżeli to ci się uda, spędzisz resztę życia w dostatku. + +[FM3_I] +~w~Spotkaj się z 8-Ballem. On zna się na dynamicie jak nikt inny - będziesz potrzebował jego doświadczenia. + +[FM3_8A] +~w~Mój czarnuch! Salvatore już dzwonił, + +[FM3_8B] +~w~ale do takiej roboty potrzeba będzie sporo fajerwerków. + +[FM3_8D] +~w~ale chyba wiesz, że żaden dolar z tej sumy nie zostanie zmarnowany. + +[FM3_8E] +~w~W porządku, zróbmy to! + +[FM3_8F] +~w~Mogę nastawić to maleństwo na wybuch, ale rany na moich łapskach jeszcze się nie wygoiły i nie poradzę sobię z giwerą. + +[FM3_8G] +~w~Weź karabin i rozwal parę łbów. + +[FM3_4] +~g~Zatrzymaj samochód i wypuść 8-Balla! + +[FM3_7] +~r~Zgubiłeś 8-Balla! + +[FM3_8] +~r~Strażnicy podnieśli alarm! + +[FM4_A] +~w~To mój ulubiony czyściciel. + +[FM4_B] +~w~Jestem z ciebie dumny, chłopcze Wybiłeś tym tępakom to ich gówno z głów. + +[FM4_C] +~w~Zanim będziemy mogli uczcić twój sukces, mam dla ciebie jeszcze jedną małą robótkę. + +[FM4_D] +~w~Na następnej przecznicy stoi samochód z klubu Lugiego. + +[FM4_E] +~w~W środku jest cały upaćkany mózgiem. + +[FM4_F] +~w~Musieliśmy pomóc jednemu gościowi namyślić się nad paroma sprawami i wyszedł z tego niezły bałagan. + +[FM4_H] +~w~Zabierz ten samochód do zgniatacza, zanim wypatrzą go gliniarze. + +[AM3] +'PAPARAZZO NA DNIE' + +[AM4] +'WYPŁATA DLA RAYA' + +[AM5] +DWULICOWY TANNER + +[AM1_A] +Zanim przejdziemy do jakichkolwiek interesów, musimy wyjaśnić sobie + +[AM1_B] +pewne kwestie. Wyłóżmy nasze karty na stół. + +[AM1_C] +Należę do Yakuzy i wiem, że pracowałeś dla rodziny Salvatore Leone. + +[AM1_D] +Myślę, że znajdzie się dla ciebie miejsce w naszej organiacji. + +[AM1_E] +Ale najpierw musisz udowodnić, że naprawdę zerwałeś wszystkie związki z Mafią. + +[AM1_G] +Zadbaj, aby nie dotarł do celu żywy. + +[AM1_H] +W międzyczasie, ja i Maria pogawędzimy o starych dobrych czasach. + +[AM1_I] +Asuka, przyszedł twój masażysta. + +[AM1_J] +To nie żaden masażysta. + +[AM1_1] +~g~Salvatore wychodzi z knajpy 'U Luigiego'! + +[AM1_2] +~r~Zostałeś zauważony! + +[AM1_3] +~r~Spudłowałeś do Salvatore! + +[AM1_4] +~r~Znakomicie, wystraszyłeś cel! I ty uważasz się za zawodowca? + +[AM1_5] +~g~Jedź do Dzielnicy Czerwonych Świateł i poczekaj, aż Salvatore wyjdzie z klubu. + +[AM1_7] +~r~Salvatore siedzi sobie bezpiecznie w domu i sączy swój koktajl. Nie spodziewaj się, że ktoś nazwie cię 'Szakalem'! + +[AM1_8] +~g~Salvatore będzie wychodzić z klubu Luigiego około ~1~:~1~ + +[AM2_4] +~g~Skradałeś się z gracją słonia w składzie porcelany! + +[AM3_A] +Jakiś nieproszony reporter wtykał tu swój nos + +[AM3_B] +Maria i ja jedziemy razem na małe wakacje, a ty pozbądź się tego zboczonego podglądacza. + +[AM4_A] +A oto i mój ulubiony człowiek do każdej roboty! + +[AM4_B] +Maria chciałaby cię widzieć, ale... nie może podejść. Powiem jej, że pytałeś. + +[AM4_C] +Kto tam? Asuka? Wiem, że byłam niegrzeczną dziewczynką, ale naprawdę muszę do toalety! OK? + +[AM4_D] +Czas, abyś poznał naszą wtyczkę w tutejszej policji. + +[AM4_E] +Tu masz zapłatę za ostatnią robótkę, jaką dla nas wykonał. + +[AM4_F] +Facet jest bardzo ostrożny, to chyba jasne. + +[AM4_G] +Jak najszybciej odszukaj budkę telefoniczną w Torrington i czekaj na instrukcje. + +[AM5_A] +Maria i ja wyszliśmy na zakupy. + +[AM5_B] +Nasze źródło w policji donosi, że jeden z naszych kierowców to policyjny tajniak. + +[AM5_C] +Bez samochodu nie jest groźny. Przyczepiliśmy do jego gabloty nadajnik. + +[AM5_D] +Niech się dowie, czym grozi taka zabawa. + +[AM5_1] +Tanner chyba ma coś do ciebie! + +[AS1] +'PRZYNĘTA' + +[AS2] +'ESPRESSO NA WYNOS!' + +[AS4] +'OKUP' + +[AS1_A] +~w~Miguel chyba uważa, że źle go traktuję. + +[AS1_B] +~w~Mimo to zdradził mi, jak bardzo Catalina obawia się twojej zemsty. + +[AS2_A] +~w~Nie doceniliśmy planów, jakie Catalina wiąże ze swoją heroiną. + +[AS2_B] +~w~Marzy jej się coś więcej niż banda Yardies sprzedająca prochy na rogu. + +[AS2_D] +~w~Sprzedają herę w swojej sieci budek ulicznych. + +[AS2_1] +~g~Wszystkie budki z espresso w Portland zostałyzniszczone!! + +[AS2_2] +~g~Wszystkie budki z espresso na Wyspie Staunton zostały zniszczone!! + +[AS2_3] +~g~Wszystkie budki z espresso w Shoreside Vale zostały zniszczone!! + +[AS2_4] +~r~Kartel ostrzegł swoich dilerów!! + +[AS2_5] +~g~W Shoreside Vale i na Wyspie Staunton nadal stoi kilka budek z espresso! + +[AS2_6] +~g~W Shoreside Vale nadal stoi kilka budek z espresso! + +[AS2_7] +~g~Na Wyspie Staunton nadal stoi kilka budek z espresso! + +[AS2_8] +~g~W Portland nadal stoi kilka budek z espresso + +[AS2_9] +~g~W Portland i Shoreside Vale nadal stoi kilka budek z espresso + +[AS2_10] +~g~W Portland i na Wyspie Staunton nadal stoi kilka budek z espresso + +[AS2_12] +~g~Badaj dzielnice miasta i szukaj budek Espresso-2-Go! + +[AS3_A] +~W~Przyciskamy już teraz czy poczekamy, aż trochę osłabnie? + +[AS3_B] +~w~Popieść go trochę... + +[AS3_D] +~w~Mój Człowiek do Każdej Roboty! + +[AS3_E] +~w~Nudziłam się, więc wpadłam żeby dotrzymać Asuce towarzystwa. + +[AS3_1] +~g~Odszukaj ~r~łódź~g~ i dopłyń do ~b~boi! + +[AS3_3] +~g~Odczekaj, aż ~y~samolot~g~ rozpocznie podejście!! + +[AS3_5] +~g~Zbierz ładunek! + +[AS3_4] +~g~Użyj wyrzutni rakietowej aby zestrzelić ~y~samolot~g~!! + +[AS3_2] +~b~Płyń do boi wyznaczających lądowisko! ~y~Samolot wykonuje już ostatnie podejście!! + +[AS3_6] +~g~~1~ Z 8! + +[KM1] +'UCIECZKA KANBU' + +[KM3] +'ZDUSIĆ UKŁADY' + +[KM4] +'SHIMA' + +[KM5] +'UDERZENIE' + +[KM1_A] +Moja siostra ma o tobie dobre mniemanie, + +[KM1_E] +choć nie mogę sobie wyobrazić, żeby gaijin mógł dać nam coś innego niż rozczarowania. + +[KM1_B] +Być może mógłbyś pomóc w rozwiązaniu problemu, który leży mi na sercu. + +[KM1_F] +Rzecz jasna, porażka oznacza utratę honoru. + +[KM1_C] +Kanbu, członek Yakuzy, przebywa w areszcie, gdzie czeka na proces. + +[KM1_G] +To ceniony członek rodziny. + +[KM1_H] +Odbij go z aresztu i przywieź do dojo w Bedford Point. + +[KM1_D] +Dziękujemy ci za twoje bezinteresowne działania. Jeżeli kiedykolwiek będziesz potrzebował pomocy, dojo z radością przydzieli ci dwóch wojowników, którzy staną u twego boku. + +[KM1_1] +~g~Ukradnij radiowóz! + +[KM1_2] +~g~Załóż bombę w samochodzie! + +[KM1_3] +~g~Teraz zawieź go do dojo Yakuzy. + +[KM1_5] +~g~W porządku, teraz jazda na posterunek. + +[KM1_6] +~g~Zamontuj w samochodzie ładunek wybuchowy! + +[KM1_7] +~g~Tylko dla pojazdów policyjnych! + +[KM1_9] +~r~Nie użyłeś bomby samochodowej, aby zniszczyć mur. + +[KM1_10] +~r~Kanbu z Yakuzy jest trupem - tak samo jak twój honor! + +[KM1_11] +~r~Ściągnąłeś sobie na głowę kłopoty! + +[KM2_A] +Nie sposób przecenić znaczenia etykiety w tej branży. + +[KM2_B] +Na moją hańbę, pewien człowiek oddał mi kiedyś przysługę, a ja nigdy nie miałem okazji, aby mu się odwdzięczyć. + +[KM2_C] +Konikiem tego człowieka są samochody. Poprosił mnie, abym zgromadził pewne modele aut do jego kolekcji. + +[KM2_F] +Od tego zależy mój honor. + +[KM2_2] +~g~Samochód dostarczony. + +[KM3_A] +Kiedy nadciągają ciemne chmury, głupiec odwraca wzrok, a mędrzec stawia im czoła. + +[KM3_B] +Kartel Kolumbijski zignorował liczne prośby, aby nie naruszać naszych interesów w Liberty City. + +[KM3_C] +Teraz negocjują układ z Jamajczykami, aby jeszcze bardziej nas upokorzyć. + +[KM3_D] +Właśnie finalizują układ o podziale wpływów w mieście. + +[KM3_F] +Weź jednego z moich ludzi, ukradnij samochód gangu Yardie i jedź przekazać Kolumbijczykom nasze wyrazy szacunku. + +[KM3_E] +Nasz honor wymaga, aby wszyscy umarli. + +[KM3_2] +~g~Jedź po swój kontakt. + +[KM3_3] +~g~Spotkanie odbędzie się na parkingu szpitalnym w Rockford! + +[KM3_4] +~r~Uciekają! + +[KM3_6] +~g~Zabij ich, zabij ich wszystkich! + +[KM3_8] +~g~Aby wykonać zadanie, potrzebujesz samochodu gangu Yardie. + +[KM3_9] +~r~Jeden z Kolumbiczyków nie żyje, układ odwołany. + +[KM3_10] +~r~Twój kontakt nie żyje! + +[KM4_A] +Jeżeli chcesz być naprawdę silny, nie możesz nigdy okazywać słabości. + +[KM4_C] +Jak najszybciej odbierz pieniądze, abyśmy mogli wpłacić je na konto kasyna. + +[KM4_1] +Nie mam wam czym zapłacić, ale nawet gdybym miał, to i tak bym tego nie zrobił! + +[KM4_9] +Jakaś banda szczeniaków właśnie stąd uciekła! Zabrali wszystko! + +[KM4_2] +Nie ma z was żadnego pożytku. + +[KM4_10] +A czy ty w ogóle należysz do Yakuzy...? + +[KM4_3] +Nie za to wam płacę, obwiesie. Gdybym chciał takiej ochrony, to zaprosiłbym cholerną policję. + +[KM4_4] +~g~Wymierz karę gangowi odpowiedzialnemu za napad i odzyskaj ~b~opłatę za ochronę~g~! + +[KM4_7] +~r~Sklepikarz wydał ostatnie tchnienie! + +[KM4_5] +Donald Love zaprasza cię do swojego ogrodu herbacianego na rozmowę. + +[KM4_6] +Tam są pieniądze! + +[KM4_8] +~r~Teczka odebrana! + +[KM5_A] +TO TY! Wybrałeś najwłaściwszy moment, aby pokazać swoją bezwartościową postać! + +[KM5_B] +Zdaje się, że twoje marne próby, aby odwieść Jamajczyków + +[KM5_B1] +od skumplowania się z Kartelem były całkowicie chybione! + +[KM5_C] +Handlarze Yardie krążą po ulicach Liberty, sprzedając woreczki HERY tak, jakby sprzedawali hotdogi! + +[KM5_D] +Te wieprze z Kartelu śmieją się z nas, ze mnie! + +[KM5_E] +Dam ci ostatnią szansę, abyś mógł dowieść, że zaufanie, jakim obdarzyła cię moja siostra, nie było bezpodstawne. + +[KM5_F] +Rozjedź tych śmieci i spłucz swoją hańbę w potokach krwi naszych wrogów!!! + +[KM5_3] +~r~Nie udało ci się zabić co najmniej ~1~ członków gangu Yardie. + +[KM5_4] +~g~Gratulacje, zabiłeś ~1~ członków gangu Yardie. + +[KM5_5] +~g~Gratulacje, zabiłeś ~1~ członków gangu Yardie. PREMIA $~1~. + +[RM1] +'UCISZYĆ KAPUSIA' + +[RM3] +'GONIĄC DOWODY' + +[RM4] +'NA RYBY' + +[RM5] +'DOKOŃCZYĆ DZIEŁO' + +[RM1_D] +Siedzi pod ochroną policji w budynku WitSec w Newport, w którymś z mieszkań za parkingiem. + +[RM1_E] +Podpal tę budę, to powinno ich wypłoszyć. Wtedy na nich zapoluj - zadbaj, aby McAffrey już nigdy z nikim nie rozmawiał! + +[RM1_1] +~g~Znajdź miejsce, w którym przebywa chroniony świadek. + +[RM1_2] +~g~Wykończ McAffrey'a! + +[RM2_A1] +Hej, synu, chodź tutaj! + +[RM2_A] +Mój stary kumpel z wojska prowadzi sklepik w Rockford. + +[RM2_D] +Potrzeba mu wsparcia. W zamian możesz liczyć na spore obniżki cen na spluwy, które ma na składzie. + +[RM2_E] +Ray wspominał, że ktoś przyjdzie... ale nie myślałem, że przyśle takiego szczeniaka. + +[RM2_F] +No cóż, trzy ramiona to zawsze więcej niż jedno, więc bierz broń według życzenia. + +[RM2_G] +~g~Zasuwaj i pilnuj Phila! + +[RM2_H] +~r~Phil zginął! + +[RM2_L] +No, no! Gdybyś był z nami wtedy w Nikaragui, może jeszcze miałbym swoją rękę! + +[RM2_N] +Zostaw forsę. Teraz lepiej znikaj, sam zajmę się policją. + +[RM3_D] +Dowody będą przewożone przez miasto. + +[RM3_E] +Musisz staranować ten samochód i zebrać wszystkie dowody, co do jednego! + +[RM3_F] +Kiedy już je zbierzesz, zostaw je w samochodzie i podpal go. + +[RM3_G] +Obaj będziemy mieć sporo korzyści, chłopcze. + +[RM3_1] +~g~Zostaw dowody w samochodzie i podpal wóz. + +[RM3_4] +~g~Samochód prokuratury zgubił dowody! + +[RM3_6] +~r~Teraz te fotografie obejrzy całe miasto! + +[RM3_7] +~g~Podpal samochód! + +[RM4_A] +Podejrzewam, że mój wspólnik to kret. + +[RM4_C] +On każdego wieczoru wypływa na morze, w okolice latarni przy Portland Rock, aby łowić ryby. + +[RM4_D] +Podwędź policyjną łódź i dopilnuj, aby poszedł na dno razem ze swoimi zdradzieckimi planami! + +[RM4_1] +~g~Ukradnij łódź policyjną! + +[RM4_2] +~g~Płyń do latarni morskiej i załatw kolegę Raya! + +[RM5_A] +Ty nieudaczny łajdaku! + +[RM5_A1] +Schrzaniłeś robotę! Moja dupa już się zaczyna smażyć, a ty nie potrafisz zabić nawet cholernej muchy. + +[RM5_B] +Zapłaciłem ci kupę szmalu, żebyś sprzątnął świadka, a on dalej żyje! + +[RM5_B1] +Dzisiaj będzie składał pierwsze zeznania w Sądzie Federalnym! + +[RM5_C] +Lada chwila będzie wyjeżdżał ze Szpitala Ogólnego Carson w Rockford. + +[RM5_D] +Jeżeli on zacznie sypać, koniec ze mną... + +[RM5_E] +więc lepiej zrób to, za co ci zapłaciłem! + +[RM5_1] +~g~Przechwyć karetkę. + +[RM5_2] +~g~Zostałeś rozpoznany! + +[RM5_3] +~g~To była tylko przynęta! + +[RM5_4] +~g~Kule nie przebiją pancernego kadłuba! + +[RM5_5] +~g~Pancerna karoseria jest ognioodporna! + +[RM5_7] +~r~Świadek dotarł na miejsce! + +[RM5_8] +~g~Świadek poszedł na dno! + +[LOVE2] +'SPRZĄTNĄĆ WAKA-GASHIRĘ' + +[LOVE3] +'KROPLA W OCEANIE' + +[LOVE1_A] +Przede wszystkim pozwól mi podziękować, że zechciałeś zająć się tą sprawą o charakterze osobistym. + +[LOVE1_F] +W dzisiejszych czasach ludzie nie szanują żadnych porozumień. + +[LOVE1_D] +Usiłują wymusić na mnie dodatkowe pieniądze, ale ja nie wierzę w renegocjacje. + +[LOVE1_E] +Umowa to umowa, więc nie powinni spodziewać się ode mnie nawet grosza. + +[LOVE1_G] +Uratuj mojego przyjaciela, zrób wszystko, co będzie trzeba. + +[LOVE1_2] +~g~Uratuj starego pana z dalekiego wschodu. + +[LOVE1_3] +~g~Zabierz starego pana z dalekiego wschodu do budynku Donalda Love'a. + +[LOVE1_4] +~g~Stary pan z dalekiego wschodu musi się znajdować w jednym z tych garaży... + +[LOVE1_6] +~r~Flaki starego pana z dalekiego wschodu zostały rozsmarowane po całej ulicy! + +[LOVE1_7] +~g~Brama otworzy się wyłącznie przed samochodem gangu kolumbijskiego. + +[LOVE2_A] +Nic tak nie wpływa na spadek cen nieruchomości, jak stara dobra wojna gangów. + +[LOVE2_B] +No, może z wyjątkiem wybuchu epidemii... ale w tym wypadku nie trzeba się posuwać aż tak daleko. + +[LOVE2_C] +Zauważyłem, że Yakuza i Kolumbijczycy nie są do siebie przyjaźnie nastawieni. + +[LOVE2_D] +Skorzystajmy z tej szansy. + +[LOVE2_E] +Masz zabić Waka-gashirę gangu Yakuzy, Kenji'ego Kasena. + +[LOVE2_F] +Kenji właśnie jest na spotkaniu na szczycie parkingu piętrowego w Newport. + +[LOVE2_G] +Załatw sobie samochód Kartelu i rozsmaruj go na ścianie! + +[LOVE2_H] +Zrób to tak, aby Yakuza obciążyła Kartel za ten akt. + +[LOVE2_1] +~g~Jedź do Fort Staunton i zwędź samochód gangu kolumbijskiego! + +[LOVE2_2] +~g~Teraz jedź na ~p~parking wielopiętrowy w Newport~p~ i załatw Kenjiego! + +[LOVE2_3] +~r~Jeżeli pojedziesz tam bez samochodu Kartelu, zostaniesz rozpoznany! + +[LOVE2_4] +~r~Członkowie Yakuzy cię rozpoznali! + +[LOVE2_6] +~r~Zabiłeś wszystkich świadków!!! + +[LOVE3_A] +W czasach hipokryzji moralnej ciężko jest zdobyć niektóre cenne towary z zagranicy. + +[LOVE3_C] +Pilot zrzuci do wody kilka pakunków. + +[LOVE3_D] +Zbierz je, zanim wpadną w niepowołane ręce. + +[LOVE3_1] +~g~Załatw sobie ~r~łódź~g~ i płyń za ~y~samolotem~g~! + +[LOVE4] +'GRAND THEFT AERO' + +[LOVE5] +'KONWOJENT' + +[LOVE4_A] +Dziękuję za odzyskanie paczek. Przykro mi to mówić, ale była to jedynie przynęta. + +[LOVE4_B] +Nie chciałem cię urazić, po prostu czasami w interesach trzeba tak postąpić. + +[LOVE4_C] +Mój prawdziwy cel przez cały czas był ukryty w samolocie. + +[LOVE4_F] +Przekupiłem kogo trzeba. + +[LOVE4_1] +~r~Są tu ludzie z Kartelu Kolumbijskiego! + +[LOVE4_2] +~g~Pakunek zniknął! Wyśledź Kolumbijczyków i odzyskaj ładunek. + +[LOVE4_3] +~g~Firma budowlana Panlantic Construction? + +[LOVE4_5] +~g~Paczka powinna nadal być w samolocie... + +[LOVE4_6] +~g~Wjedź windą na wieżę! + +[LOVE5_B] +Mój orientalny przyjaciel potrzebuje eskorty, kiedy będze wiózł mój najnowszy nabytek do specjalistów. + +[LOVE5_1] +~g~Ruszamy! + +[LOVE5_2] +~g~Potrzebujesz samochodu! + +[LOVE5_3] +~g~Jedź przodem i zbadaj wylot tunelu! + +[LOVE5_4] +~r~Osłaniaj ciężarówkę! + +[RM6] +'NA WIDELCU' + +[RM6_A] +Nikt cię nie śledził? To dobrze. + +[RM6_B] +To już koniec. Jestem po uszy w gównie i nadal się zapadam. + +[RM6_D] +Jestem na widelcu, więc postanowiłem zniknąć. + +[RM6_E] +Zawieź mnie na mój samolot, a dobrze ci się odwdzięczę! + +[RM6_666] +Zatroszcz się o mojego kuloodpornego Patriota. Do zobaczenia w Miami, Ray. + +[CAT1] +'OKUP' + +[CAT2] +'WYMIANA' + +[CAT1_A] +Mam twoją słodziutką Marię. Jeżeli nie chcesz, żeby jej twarz wyglądała jak po randce z rzeźnikiem, + +[CAT2_F] +Złamałam paznokieć i cała jestem potargana. Nie do wiary! Ta fryzura kosztowała mnie pięćdziesiąt dolców! + +[CAT2_G] +Strasznie się bałam, ale w końcu powiedziałam sobie: jesteś już przecież dużą dziewczyną. + +[CAT2_H] +Och, będziemy się wspaniale bawić, bo moja siostra powiedziała, że chciałaby wpaść do nas ze swoimi dziećmi, + +[CAT2_I] +bo jej mąż znowu się gdzieś szwenda i... + +[CAT1_E] +XXXX + +[CAT1_F] +Dotrzyj do Cataliny sprzed upływem wyznaczonego czasu! + +[CAT_MON] +~g~Nie masz jeszcze tyle pieniędzy. Potrzebujesz $500.000 + +[BITCH_D] +~g~Maria nie żyje! + +[WEATHER] +POGODA WYMUSZONA + +[WEATHE2] +ZWYKŁA POGODA + +[8001] +Marnie kończysz!! + +[1000] +JESTEŚ MARTWY + +[1001] +JESTEŚ MARTWY + +[1002] +JESTEŚ MARTWY + +[1003] +JESTEŚ MARTWY + +[1004] +JESTEŚ MARTWY + +[1005] +WPADKA + +[1006] +WPADKA + +[1007] +WPADKA + +[1008] +WPADKA + +[1009] +WPADKA + +[GA_4] +Bomby samochodowe kosztują 1000 dolarów za sztukę. + +[GA_5] +W twoim samochodzie bomba już została zainstalowana. + +[GA_6] { re3 change } +Zaparkuj wóz, włącz mechanizm klawiszem ~h~~k~~VEHICLE_FIREWEAPON~~w~ i W NOGI! + +[GA_7] { re3 change } +Uaktywnij bombę za pomocą klawisza ~h~~k~~VEHICLE_FIREWEAPON~~w~. Bomba wybuchnie w momencie włączenia silnika. + +[GA_8] +Użyj detonatora, aby aktywować bombę. + +[GA_9] +Zgromadziłeś ~1~ z 10 samochodów specjalnych. + +[GA_10] +Ładne cacko. Oto twoje ~1~$. + +[GA_11] +Mamy już taki wózek. Dla nas jest on bez wartości. + +[GA_12] +Bomba uzbrojona + +[GA_13] +Robota zawodowca. Skombinuj dla mnie wszystkie wózki z listy, a czeka cię premia. + +[GA_14] +Wszystkie samochody? DOSKONALE! Oto niespodzianka dla ciebie! + +[GA_15] +Mam nadzieję, że podoba ci się nowy kolor. + +[GA_16] +Lakierowanie zakończone. + +[GA_19] +Nie interesuje nas ten model. + +[GA_20] +Mamy tego więcej, niż możemy zepchnąć. Sorry, facet, ale nie wchodzę w to. + +[CR_1] +Dźwig nie jest w stanie podnieść tego pojazdu. + +[PU_MONY] +Nie masz dość forsy. + +[CO_ALL] +Masz już wszystkie. Oto mała niespodzianka... + +[PAUSED] +GRA ZATRZYMANA + +[HEALTH1] +Spadaj stąd! Jesteś zdrów jak ryba. + +[HEALTH2] +Koszty opieki medycznej. + +[HEALTH3] +Trochę cię połatam. + +[HEALTH4] +To kosztuje 250 dolarów. + +[FEB_STA] +Statystyki + +[FEB_BRI] +Zadania + +[FEB_CON] +Sterowanie + +[FEB_AUD] +Audio + +[FEB_DIS] +Ekran + +[FEB_LAN] +Język + +[FEP_STA] +STATYSTYKI + +[FEP_BRI] +CELE + +[FEP_CON] +STEROWANIE + +[FEP_AUD] +DŹWIĘK + +[FEP_DIS] +EKRAN + +[FEP_LAN] +JĘZYK + +[FEF_ST1] +Kto tu jest złym facetem? + +[FEF_ST2] +Ile paniki dzisiaj wzbudziłeś? + +[FEF_BR1] +Straciłeś wątek? + +[FEF_CO1] +Potrzebujesz lepszej kontroli, perfekcjonisto? + +[FEF_CO2] +Określ taką konfigurację klawiszy sterujących, która najlepiej odpowiada preferowanemu stylowi gry. + +[FEF_SA1] +Trzymaj wszystko na kupie! + +[FEF_SA2] +Zapisuj i wczytuj swoje gry + +[FEF_AU1] +Więcej czadu! + +[FEF_AU2] +Wybierz stację radiową oraz efekty dźwiękowe + +[FEF_DI1] +Zmień grę! + +[FEF_DI2] +Dostosuj grę do odbiornika TV + +[FEF_LA1] +O czym gadasz? + +[FEF_LA2] +Wybierz preferowany język + +[FEB_PMB] +Cele poprzednich misji: + +[FEC_NA] +N.D. + +[FEC_CWL] +Przełącz rodzaj broni w lewo + +[FEC_CWR] +Przełącz rodzaj broni w prawo + +[FEC_LOF] +Spójrz do przodu + +[FEC_TAR] +Cel + +[FEC_MOV] +Ruch + +[FEC_CAM] +Tryby kamery + +[FEC_PAU] +Pauza + +[FEC_ENV] +Wsiadanie do pojazdu + +[FEC_JUM] +Skok + +[FEC_ATT] +Atak lub strzał z broni + +[FEC_RUN] +Bieg + +[FEC_FPC] +Kamera - widok z oczu postaci + +[FEC_LL] +Spójrz w lewo + +[FEC_LB1] +Spójrz + +[FEC_LB2] +do tyłu + +[FEC_LB] +Spójrz do tyłu + +[FEC_LR] +Spójrz w prawo + +[FEC_HOR] +Klakson + +[FEC_VES] +Sterowanie w pojeździe + +[FEC_RSC] +Przełącz stacje radiowe + +[FEC_BRA] +Hamulec lub wsteczny + +[FEC_HAB] +Hamulec ręczny + +[FEC_CAW] +Broń w samochodzie + +[FEC_ACC] +Przyspieszenie + +[FEC_SMT] +Włączenie misji specjalnych + +[FEA_OUT] +Wyjście: + +[FEA_ST] +stereo + +[FEA_MNO] +mono + +[FEA_NON] +Brak + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +GADUŁA 109 + +[FED_DBG] +Menu debugowania + +[FED_RID] +Ponowne wczytanie IDE + +[FED_RIP] +Ponowne wczytanie IPL + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_SPR] +Show Ped Road Groups + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_DSR] +Żądania przetworzenia w trybie debugowania + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Menu karty pamięci + +[FEM_RMC] +Register MemCard One + +[FEM_TFM] +Próbne formatowanie karty pamięci 1 + +[FEM_TUM] +Próbne odformatowanie karty pamięci 1 + +[FEM_CRD] +Utwórz katalog główny + +[FEM_CLI] +Twórz i wczytuj ikony + +[FEM_FFF] +Fill First File with Guff + +[FEM_SOG] +Zapisz tylko grę + +[FEM_CES] +Check Every 0kB4 Save + +[FEM_STG] +Zapisz grę + +[FEM_STS] +Zapisz grę pod nazwą GTA3 + +[FEM_CPD] +Utwórz chroniony katalog magazynowy + +[FEM_MC2] +Menu karty pamięci 2 + +[FEM_TS] +Próbne zapisywanie: + +[FEM_TL] +Próbne wczytywanie: + +[FEM_TD] +Próbne kasowanie: + +[PL_STAT] +Statystyki gracza + +[PE_WAST] +Ludzie załatwieni przez gracza + +[PE_WSOT] +Ludzie załatwieni przez innych + +[CAR_EXP] +Wysadzone samochody: + +[TM_BUST] +Liczba wpadek + +[M_WASTE] +Załatwieni mężczyźni-cywile + +[F_WASTE] +Załatwione kobiety-cywile: + +[PIG_WST] +Załatwieni gliniarze + +[GNG_WST] +Członkowie gangu załatwieni. + +[MED_WST] +Załatwieni lekarze + +[FIRE_WS] +Strażak załatwiony + +[DED_CRI] +Załatwieni przestępcy: + +[DED_DED] +Załatwione lumpy: + +[DED_HOK] +Załatwione dziwki: + +[HEL_DST] +Zniszczone helikoptery + +[PER_COM] +Procent ukończenia gry + +[KGS_EXP] +Użyte materiały wybuchowe (kg) + +[ACCURA] +Dokładność + +[ELBURRO] +Najlepsze czasy wyścigu w sekundach: + +[CAR_CRU] +Zmiażdżone samochody: + +[HED_EX] +Rozbite głowy + +[TM_DED] +Wizyty w szpitalu + +[DAYSPS] +Liczba dni, które upłynęły w grze: + +[MMRAIN] +mm deszczu + +[MXCARD] +Maks. odległość SZALONEGO skoku (w stopach) + +[MXCARJ] +Maks. wysokość SZALONEGO skoku (w stopach) + +[MXCARDM] +Maks. odległość SZALONEGO skoku (w metrach) + +[MXCARJM] +Maks. wysokość SZALONEGO skoku (w metrach) + +[MXFLIP] +Maks. liczba salt w SZALONYM skoku + +[MXJUMP] +Maks. liczba obrotów w SZALONYM skoku + +[BSTSTU] +Najlepszy SZALONY skok do tej pory: + +[INSTUN] +Szalony skok + +[PRINST] +Bezbłędny szalony skok + +[DBINST] +Podwójny szalony skok + +[DBPINS] +Bezbłędny podwójny szalony skok + +[TRINST] +Potrójny szalony skok + +[PRTRST] +Bezbłędny potrójny szalony skok + +[QUINST] +Poczwórny szalony skok + +[PQUINS] +Bezbłędny poczwórny szalony skok + +[NOSTUC] +Nie wykonano żadnych SZALONYCH skoków + +[NOUNIF] +Wyjątkowe skoki wykonane + +[NOUNGM] +Wyjątkowe skoki razem + +[NMISON] +Próby wykonania misji + +[NMMISP] +Wykonane misje + +[PASDRO] +Zgubieni pasażerowie + +[MONTAX] +Forsa zarobiona w taksówce + +[DAYPLC] +Dzienne wydatki policji: + +[CRIMRA] +Ranking zbrodni: + +[GMSTOR] +Zachowanie gry + +[PREBRF] +Poprzednie zapisy + +[CNTLS] +Sterowanie + +[MUSMEN] +Muzyka/dźwięki + +[GAMSET] +Ustawienia gry + +[LANGUA] +Język + +[DSPLAY] +Ekran + +[DEBUGM] +Menu funkcji debugowania + +[QUITOP] +Wyjście z menu opcji + +[CONTRL] +Konfiguracja sterowania + +[SET1EN] +SetUp 1. Enabled + +[SET1] +SetUp 1. + +[SET2EN] +SetUp 2. Enabled + +[SET2] +SetUp 2 + +[SET3EN] +SetUp 3. Enabled + +[SET3] +SetUp 3 + +[SET4EN] +SetUp 4. Enabled + +[SET4] +SetUp 4 + +[GOBACK] +Wróć + +[SOUND] +DŹWIĘK + +[MUSVOL] +Głośność muzyki + +[SFXVOL] +Głośność efektów dźwiękowych + +[SCROPT] +OPCJE EKRANU + +[CTRSCR] +Wyśrodkowanie Ekranu + +[SCRFOR] +Format ekranu + +[GMSVLQ] +WCZYTAJ-ZAPISZ-WYJDŹ Z GRY + +[GMREST] +Ponowne uruchomienie gry + +[NOGMSV] +Zapisywanie stanu gry jest możliwe tylko w kryjówce. + +[DLFILE] +Skasować pliki Grand Theft Auto III + +[CHFILE] +WYBIERZ PLIK, KTÓRY MA ZOSTAĆ WCZYTANY + +[CHFIDL] +WYBIERZ PLIK, KTÓRY MA ZOSTAĆ SKASOWANY + +[SVCONF] +POTWIERDZENIE ZAPISU + +[LANGSL] +WYBÓR JĘZYKA + +[ENGLIS] +Polski + +[GERMAN] +Niemiecki + +[ITALIA] +Włoski + +[FRENCH] +Francuski + +[SPAIN] +Hiszpański + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Parse Heap + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[BGWHON] +Big White Debug Light - włączony + +[BGWOFF] +Big White Debug Light - wyłączony + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[CRRGON] +ShowCarRoad Group Włączone + +[CRGOFF] +ShowCarRoadGroups Wyłączone + +[CLZOON] +Wyłączone pokazywanie stref zniszczeń + +[CLZOOF] +Włączone pokazywanie stref zniszczeń + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (element testowy) + +[UNFRM1] +UnFormatMemCard 1 (element próbny) + +[GORLEV] +Poziom 'Krwawy' + +[SICASS] +Sick Fuck + +[SICSIC] +Sick Fucker + +[SCASSL] +Sick Fuck wybrany + +[SCSCSL] +Sick Fucker wybrany + +[PRVMEN] +Cele poprzednich misji + +[FORMEN] +Menu formatu + +[MEMTST] +Ekran TestKartPamięci + +[REGCAR] +Rejestracja KartaPamięci Jeden + +[TEFONE] +Próbne formatowanie karty pamięci 1 + +[TEUFON] +Próbne odformatowanie karty pamięci 1 + +[CRROOT] +Utwórz Katalog Główny + +[CRLDIC] +Tworzenie i wczytywanie ikon + +[FLFSGF] +Fill First File With Guff + +[PUSAVE] +Zapisz tylko grę + +[CHEVOK] +CheckEveryOkB4Save + +[SVGMON] +Zapisz grę + +[CNTSAV] +Nie można zapisać stanu gry. Jesteś w trakcie misji. + +[CNCSAV] +Nie można zapisać stanu gry. Jesteś w samochodzie. + +[CRMGSV] +Utwórz chroniony katalog magazynowy + +[MGSVCN] +Katalog magazynowy utworzony + +[MGSVNC] +Katalog magazynowy nieutworzony + +[YES] +Tak + +[NO] +Nie + +[X] +x + +[LAST] +Ostatnia wiadomość + +[FEDS_XB] +Wybierz + +[FEDS_ST] +klawisz START - WZNÓW + +[FEST_OO] +z + +[FEC_TUC] +Sterowanie wieżyczką + +[FEC_SM3] +Włączenie misji specjalnych (klawisz R3) + +[FEC_RS3] +Przełącz stacje radiowe (klawisz L3) + +[FEC_HO3] +Klakson (klawisz lewy SHIFT) + +[DIAB1] +'WYŚCIG' + +[DIAB2] +'PRZEŁAMAĆ LODY' + +[DIAB3] +'PRÓBA OGNIA' + +[DIAB4] +'WIELKI I ŻYLASTY' + +[DIAB1_A] +El Burro ma dla ciebie propozycję. Jeżeli jesteś zainteresowany, odszukaj budkę telefoniczną w Hepburn Heights. + +[DIAB1_C] +Niezły z ciebie kierowca! Jedź do wskazanej budki telefonicznej, a może El Burro da ci jakieś zajęcie. + +[DIAB1_1] +~g~3... 2... 1... NAPRZÓD! NAPRZÓD! NAPRZÓD! + +[DIAB1_4] +~g~Załatw sobie szybki wóz i jedź na miejsce startu. + +[DIAB1_3] +~r~Nie wygrałbyś nawet z własną babcią, LESZCZU! + +[DIAB1_2] +~g~Gratulacje, wygrywasz, uzyskując niesamowity czas: ~1~sekund. + +[FIRST] +~g~pierwszy + +[SECOND] +~g~drugi + +[THIRD] +~g~trzeci + +[FOURTH] +~g~4 + +[DIAB2_1] +~g~Zabierz teczkę z Harwood. + +[DIAB2_2] +~g~Odszukaj półciężarówkę lodziarza. + +[DIAB2_3] +~g~Zaparkuj samochód lodziarza na Atlantic Quays. + +[DIAB2_4] +~g~Naciśnij klawisz ~w~~k~~VEHICLE_HORN~~g~, aby włączyć sygnał reklamujący lody. + +[DIAB2_6] +~g~Naciśnij klawisz ~w~~k~~VEHICLE_HORN~~g~, aby włączyć sygnał reklamujący lody. + +[DIAB2_7] +~g~Naciśnij klawisz ~w~~k~~VEHICLE_HORN~~g~, aby włączyć sygnał reklamujący lody. + +[DIAB2_5] +~g~Wysiądź z samochodu, a następnie zdetonuj go za pomocą nadajnika. + +[YD1] +'SZUKAJ PUNKTÓW!' + +[YD2] +'RUCHOMY CEL' + +[YD3] +'KOLEKCJONER WOZÓW' + +[YD4] +'KRÓLESTWO NIEBIESKIE' + +[YD_P] +King Courtney prosi cię na słówko. Znajdź budkę telefoniczną w Aspatrii!! + +[YD1_A] +~w~Z tej strony King Courtney. + +[YD1_A1] +~w~Moja paczka, Yardies, potrzebuje kierowcy, a ty masz reputację bystrego faceta. + +[YD1_B] +~w~Jedź na wysypisko naprzeciwko stadionu i poczekaj na innych zawodników. + +[YD1_C] +~w~Moi ludzie pilnują punktów kontrolnych w całym Staunton. + +[YD1_D] +~w~Kierowca, który pierwszy dotrze do takiego miejsca, otrzymuje jeden punkt. Potem ścigamy się do następnego przystanku. + +[YD1_D1] +~w~Jeżeli zaliczysz więcej punktów niż inni kierowcy, być może będę miał dla ciebie zadanie. + +[YD1_E] +~g~Gotowi do wyścigu! + +[YD1_F] +~g~Minąłeś punkt startu - podoba mi się twój styl!!! + +[YD1_G] +~r~To jest WYŚCIG SAMOCHODOWY. Masz jechać SAMOCHODEM, IDIOTO! + +[YD1GO] +~g~START! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +$1000!! + +[Y1_1ST] +~g~Kończysz na pierwszym miejscu i pomyślnie zaliczasz ~1~ punktów kontrolnych! + +[Y1_2ND] +~y~Jesteś drugi, pomyślnie zaliczyłeś ~1~ punktów kontrolnych. ~r~Było blisko, ale trochę ci jeszcze brakuje. + +[Y1_3RD] +~r~Jesteś trzeci, pomyślnie zaliczyłeś ~1~ punktów kontrolnych. ~r~A mówiłeś, że jesteś niezły! + +[Y1_LAST] +~r~Jesteś ostatni! ~r~Tylko marnujesz mój czas, IDIOTO! + +[Y1_J1ST] +~y~Pierwsze miejsce ex aequo, pomyślnie zaliczyłeś ~1~ punktów kontrolnych. ~y~Nieźle, ale musisz być najlepszy z najlepszych, aby móc jeździć dla Królowej Lizzy! + +[Y1_J2ND] +~r~Drugie miejsce ex aequo, pomyślnie zaliczyłeś ~1~ punktów kontrolnych. Jechałeś jak wściekły goryl! + +[Y1JLAST] +~r~Ostatnie miejsce ex aequo! Wymądrzałeś się jak stary kierowca, ale kierowałeś jak stary przemądrzalec! + +[Y1_TEST] +SAMOCHÓD W WODZIE!! + +[YD2_A] +~w~Muszę sprawdzić, czy dajesz sobie radę z mokrą robotą. + +[YD2_A1] +~w~Zobaczymy, czy można ci zaufać. + +[YD2_B] +~w~Dwóch moich chłopców zaraz po ciebie przyjedzie, żeby zabrać cię na przejażdżkę + +[YD2_B1] +~w~i sprawdzić, czy naprawdę umiesz tyle, ile twierdzisz. + +[YD2_C] +~w~Jedziemy na mały wypad na Wzgórza Hepburn, żeby sprzątnąc paru śmierdziuchów z gangu Diablo, którzy wkurzali Królową Lizzy. + +[YD2_CC] +~w~Będziesz potrzebował gnata, trzymaj. + +[YD2_D] +~w~Ty kierujesz i strzelasz. My zadbamy, żeby nie zabrakło ci odwagi. + +[YD2_E] +~w~Jazda! + +[YD2_F] +~w~Oszukał nas! Dorwać jego zdradliwą dupę! + +[YD2_G1] +~w~Wzgórza Hepburn. Zabijmy paru śmierdzących Diablo... + +[YD2_G2] +~w~Tylko pamiętaj, ~r~ masz nie wysiadać z samochodu!!! + +[YD2_H] +~w~W porządku, wracamy na terytorium Yardies! JAZDA, SZYBCIEJ!! + +[YD2_L] +~w~Dobrze się spisałeś, żniwiarzu! + +[YD2_M] +~r~Rozwalił mój samochód! Załatwić go! + +[YD2_N] +~w~Posadź tyłek z powrotem w samochodzie! + +[YD3_A] +Masz porwać dla mnie kilka samochodów gangów tak, + +[YD3_A1] +abyśmy mogli uderzyć we wrogów na ich własnym terytorium. + +[YD3_B] +Potrzebuję mafijnego Sentinela, + +[YD3_B1] +Stingera Yakuzy oraz + +[YD3_B2] +Ogiera gangu Diablo. Wtedy będzie można uderzyć na każdego w Liberty. + +[YD3_C] +Zostaw je przy garażu w Newport, ale pamiętaj, + +[YD3_C1] +potrzebujemy tylko fury w dobrym stanie!!! + +[YD3_D] +Wolne miejsce na tekst + +[YD3_E] +~r~Już zdobyłeś samochód gangu Diablo! + +[YD3_F] +~r~Już zdobyłeś samochód mafii! + +[YD3_G] +~r~Już zdobyłeś samochód Yakuzy! + +[YD3_H] +~r~Zdobyłeś samochód gangu Diablo! + +[YD3_I] +~r~Zdobyłeś samochód mafii! + +[YD3_J] +~r~Zdobyłeś samochód Yakuzy! + +[YD3_K] +~r~Ten samochód to ruina! Musisz go naprawić! + +[YD3_L] +~g~Zabierz samochód do garażu! + +[YD3_M] +~r~Straciłeś wóz! Musisz zdobyć jeszcze jeden! + +[YD4_A] +Posłuchaj! + +[YD4_A1] +Jedź do Bedford Point. + +[YD4_A2] +W starym samochodzie jest coś, czego potrzebuję, pronto! + +[YD4_B] +LIST: Słyszałam, że ostatnio byłeś pilnym uczniem. Cóż, ja byłam pilną uczennicą. + +[YD4_C] +Czas, abyś poznał prawdziwą siłę HEROINY! Besos y fuderes, Catalina, xxx. + +[YD4_D] +PS. ZDYCHAJ KUNDLU! + +[YD4_1] +~g~Naćpani szaleńcy! + +[YD4_2] +~g~Zniszcz ciężarówki wariatów! + +[HM_1] +'AGRESYWNA JAZDA' + +[HM_2] +'ZABAWKOWY ZABÓJCA' + +[HM_3] +'ZDĄŻYĆ PRZED WYBUCHEM'v + +[HM_5] +'ROZRÓBA' + +[HOOD1_A] +Znajdź budkę telefoniczną w Wichita Gardens, to pogadamy o interesach. + +[HM1_A] +Yo! Z tej strony D-Ice z gangu Red Jacks! + +[HM1_C] +Te szczeniaki wyłażą na ulice i myślą tylko o tym, kogo by tu zastrzelić i co zaćpać. + +[HM1_3] +~g~'Dziewiątki' mają swoje terytorium w Wichita Gardens. + +[HM2_3] +Jeżeli uderzysz zdalnie sterowanym samochodzikiem w koła pojazdu, ładunek wybuchnie! + +[HM2_4] +Jeżeli samochodzik wyjedzie poza zasięg nadajnika, ładunek wybuchnie! + +[HM2_5] +~r~Samochodzik poza zasięgiem! + +[HM3_1] +~g~Zabierz samochód do warsztatu, ale uważaj! Jeżeli samochód zostanie mocno uszkodzony, ładunek może wybuchnąć! + +[HM3_2] +~g~Zwróć samochód właścicielowi. Jedź ostrożnie, wóz musi być w doskonałym stanie! + +[HM3_3] +~g~Napraw samochód! + +[HM4_D] +~g~Zdobądź samochód! + +[HM4_E] +TEKST NIEPOTRZEBNY + +[HM4_1] +~g~Jedź do miejsca, w któym rozsypał się ładunek. Musisz zebrać 30 sztabek. + +[HM4_2] +~g~Pamiętaj, kiedy samochód zrobi się ciężki, poturlaj się do garażu i wysyp ładunek. + +[HM5_3] +~r~Miałeś używać wyłącznie kija bejsbolowego! + +[HM5_4] +~r~Twój kontakt nie żyje! + +[MEA1] +'CWANIAK' + +[MEA2] +'ZŁODZIEJE' + +[MEA3] +'ŻONA' + +[MEA4] +'KOCHANEK' + +[MEAT1_A] +Znajomy powiedział mi, że potrafisz rozwiązywać problemy. Jeżeli chcesz zająć się moimi kłopotami, znajdź budkę telefoniczną w Trenton. + +[MEA1_B3] +~g~Spotkaj się z kierownikiem banku. + +[MEA1_B6] +~g~Zabierz samochód do zgniatarki, aby pozbyć się dowodów. Wysiądź z samochodu, a dźwig już się wszystkim zajmie. + +[MEA1_1] +~r~Kierownik banku nie żyje! + +[MEA1_2] +~r~Miałeś zniszczyć ten pojazd! + +[MEA1_3] +~g~Wysiądź z samochodu! + +[MEA1_4] +~r~Zgubiłeś kierownika banku! + +[MEA2_B3] +~g~Jedź po złodziei. + +[MEA2_B4] +~g~Zabierz ich do fabryki Delikatesów Pod Psem. + +[MEA2_B6] +~g~Przemaluj samochód, aby zatrzeć ślady. + +[MEA2_1] +~r~Miałeś zniszczyć ten pojazd! + +[MEA2_2] +~r~Złodziej nie żyje! + +[MEA2_4] +~r~Zgubiłeś jednego ze złodziei! + +[MEA3_B3] +~g~Jedź po panią Chonks. + +[MEA3_B6] +~g~Zabierz samochód i wrzuć go do wody, aby pozbyć się dowodów. + +[MEA3_1] +~r~Żona nie żyje! + +[MEA3_2] +~r~Miałeś wrzucić samochód do wody! + +[MEA3_3] +~r~Zgubiłeś żonę Marty'ego! + +[MEA4_B3] +~g~Zabierz kochanka żony. + +[MEA4_B6] +Na to już za późno, Marty. Miałeś szansę, ale teraz przejmuję twoją budę... + +[MEA4_1] +~r~Carlos nie żyje! + +[MEA4_3] +~r~Zgubiłeś Carlosa lichwiarza! + +[LOOK_A] +Naciśnij i przytrzymaj klawisz ~h~~k~~VEHICLE_LOOKLEFT~ ~w~lub klawisz ~h~~k~~VEHICLE_LOOKRIGHT~ ~w~, aby spojrzeć ~h~w lewo~w~ lub ~h~w prawo~w~ przez szyby pojadu. Naciśnij oba klawisze naraz, aby spojrzeć ~h~do tyłu~w~. + +[LOVE6_1] +~g~Teraz odciągnij gliniarzy od magazynu! + +[LOVE6_2] +~r~Nie udało ci się odciągnąć glin na wystarczającą odległość! + +[RM4_3] +~r~Kumpel Raya zwiał! + +[RM6_C] +Zdaje się, że CIA ma jakiś swój interes w utrzymaniu handlu PROCHAMI + +[RM6_C1] +i nie spodobało im się, że zadarliśmy z Kartelem. + +[C_PASS] +ZAGROŻENIE ZLIKWIDOWANE + +[CTUTOR] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~, aby włączyć lub wyłączyć misje patrolowe. + +[CTUTOR2] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~, aby włączyć lub wyłączyć misje patrolowe. + +[COPCART] +~g~Masz ~1~ sekund na powrót do radiowozu albo misja zakończy się. + +[C_FAIL] +Misja patrolowa zakończona! + +[C_CANC] +~r~Misja patrolowa anulowana! + +[C_ESCP] +~r~Podejrzany uciekł! + +[C_TIME] +~r~Twój czas w roli stróża prawa minął! + +[C_VIGIL] +PREMIA PATROLOWA!! + +[A_FAIL2] +~r~Twoje ślamazarność kosztowała pacjenta życie! + +[A_FAIL3] +~r~Pacjent nie żyje! + +[A_PASS] +Uratowany! + +[F_FAIL2] +~r~Spóźniłeś się! + +[A_COMP2] +Ty chyba nigdy się nie męczysz! + +[RM2_M] +Jak będziesz potrzebował spluwy, wpadaj do mnie jak w dym i bierz z szafek, co ci się podoba. + +[HEAL_A] +Twój ~h~poziom życia~w~ jest wyświetlony na pomarańczowo w prawym górnym narożniku ekranu. + +[YD1_CNT] +~1~ z 15! + +[FM1_9] +~g~Przed nami miejsce imprezy - wysadź Marię przed budynkiem. + +[FM1_Y] +~w~Wiesz, dawno się tak dobrze nie bawiłam, a ty traktowałeś mnie naprawdę dobrze. Z szacunkiem i w ogóle.. + +[FM1_AA] +~w~Chyba już pójdę. W takim razie - do zobaczenia! + +[NOCONTE] +Aby kontynuować, proszę ponownie umieścić wtyczki kontrolera analogowego (DUALSHOCK@) lub kontrolera analogowego (DUALSHOCK@2) w porcie kontrolerów gry nr 1. + +[WRCONT] +Kontroler w porcie nr 1 nie jest rozpoznany. Gra Grand Theft Auto III wymaga kontrolera analogowego (DUALSHOCK@) lub kontrolera analogowego (DUALSHOCK@2). + +[WRCONTE] +Kontroler w porcie nr 2 nie jest rozpoznany. Gra Grand Theft Auto III wymaga kontrolera analogowego (DUALSHOCK@) lub kontrolera analogowego (DUALSHOCK@2). + +[WRONGCD] +Niewłaściwa płyta. Proszę włożyć właściwą płytę. + +[NOCD] +Nie znaleziono GTAIII CD w czytniku. + +[OPENCD] +Taca napędu jest wysunięta. Wsuń tacę napędu CD-ROM. + +[CDERROR] +Błąd w odczycie płyty Grand Theft Auto III. + +[RESTART] +Trwa rozpoczynanie nowej gry + +[GA_3] +Koniec z promocjami. 1000 dolców za malowanie! + +[GA_1] +Coś ty! Nawet nie dotknę takiego trefnego towaru! + +[GA_1A] +Wróć, kiedy będziesz miał chwilę wolnego czasu... + +[S_PROM2] +Garaż znajdujący się za sąsiednimi drzwiami służy do przechowywania pojazdów podczas zapisywania stanu gry. + +[STOCK] +brak towaru + +[FM1_O] +~w~Myślę, że znajdziemy go nad brzegiem morza, w okolicach Chinatown. + +[EBAL_B] +To właśnie tu. Zjedźmy z ulicy i poszukajmy jakichś ciuchów, żeby zmienić te więzienne łachy! + +[EBAL_G] +To jest właśnie klub 'U Luigiego'. Obejdziemy tę budę i skorzystamy z tylnych drzwi. + +[AM4_3] +A więc to ty jesteś nowym chłopcem na posyłki Asuki? + +[AM4_4] +Masz forsę? Mam nadzieję, że wszystko jest jak trzeba? + +[AM4_5] +Wiem, co sobie myślisz, następny sprzedajny gliniarz. + +[AM4_6] +Cóż, każdy orze jak może. + +[AM4_7] +Straciłem ostatnio paru partnerów i ci frajerzy z wydziału wewnetrznego zaczęli coś przewąchiwać. + +[AM4_8] +Żeby tylko nie wyniuchali moich śladów. + +[AM4_9] +To miasto to jeden wielki otwarty ściek. + +[AM4_10] +Przyda mi się pomoc kogoś niezrzeszonego. + +[AM4_11] +Jeżeli masz jakiś interes, wiesz gdzie mnie znaleźć. + +[CAM_A] +Wciskaj klawisz ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~, aby zmieniać tryby pracy ~h~kamery ~w~, zarówno w samochodzie jak i poza nim. + +[CAM_B] +Wciskaj klawisz ~h~strzałki w gorę~w~ oraz ~h~strzałki w dół~w~, aby zmieniać tryby pracy ~h~kamery ~w~, zarówno w pojeździe jak i poza nim. + +[KM2_1] +~g~Napraw samochód. Wóz musi być w idealnym stanie. + +[LM3_6] +Joey... + +[LM3_6A] +Znowu będę mogła się pobawić twoim drągiem? + +[LM3_9A] +może będę miał dla ciebie jakieś zajęcie. + +[LM3_9B] +W porządku? + +[AWAY2] +~r~Uciekli. + +[AWAY] +~r~Zwiał stąd, gdzie pieprz rośnie! + +[JM6_1] +Jedź do banku na głównej ulicy. + +[GA_6B] { re3 change } +Zaparkuj wóz, włącz mechanizm klawiszem ~h~~k~~VEHICLE_FIREWEAPON~~w~ i W NOGI! + +[GA_7B] { re3 change } +Uaktywnij bombę za pomocą klawisza ~h~~k~~VEHICLE_FIREWEAPON~~w~. Bomba wybuchnie w momencie włączenia silnika. + +[BAT1] +~g~Podnieś kij bejsbolowy! + +[EBAL_O] +Jeśli nic nie schrzanisz, może znajdzie się dla ciebie jakaś praca. A teraz zjeżdżaj! + +[HELP9_B] +Naciśnij klawisz~h~ ~k~~PED_FIREWEAPON~~w~, aby oddać ~h~strzał~w~ z karabinu snajperskiego. + +[HELP9_C] +Naciśnij klawisz~h~ ~k~~PED_FIREWEAPON~~w~, aby oddać ~h~strzał~w~ z karabinu snajperskiego. + +[JM6_8] +~r~Straciłeś wszystkich złodziei! + +[COLT_IN] +Ammu-nacja zaczyna sprzedaż pistoletów! + +[TAXI2] +~r~Koniec czasu! + +[TAXI3] +~r~Przerażony pasażer ucieka! + +[TAXI7] +~r~Twoja taksówka to ruina, połataj ją trochę. + +[TAXI4] +Kurs wykonany! + +[TAXI5] +PREMIA ZA SZYBKOŚĆ!!! + +[TAXI6] +Koniec misji w taksówce + +[FRANGO] +~g~Salvatore chce, abyś najpierw pomógł Toniemu załatwić porachunki z Triadami! + +[PAGEB12] +Łapówka policyjna dostarczona do kryjówki + +[PAGEB13] +Życie dostarczone do kryjówki + +[PAGEB14] +Adrenalina dostarczona do kryjówki + +[KM1_4] +~g~Do tej roboty przydałby się radiowóz! + +[CAT1_B] +przynieś 500.000 $ do Willi w Cedar Grove. + +[JM2_C] +Gość ma budę z makaronem w Chinatown. + +[RM6_1] +Tu masz klucz do dziupli. + +[RM6_2] +Znajdziesz tam trochę forsy i 'zapasów', które zbierałem na czarną godzinę. + +[RM6_3] +Trzymaj się. + +[FE_INIP] +Inicjalizacja i wczytywanie menu pauzy... Proszę czekać. + +[FESZ_CA] +Anuluj + +[FESZ_QU] +Wyjście + +[FESZ_L1] +Gra została pomyślnie zapisana. + +[FESZ_L2] +Gra została zapisana w pliku o nazwie: + +[FESZ_OK] +OK + +[FES_LGA] +Wczytaj grę + +[FES_NGA] +Nowa gra + +[FES_CAN] +Anuluj + +[FESZ_QL] +Wszelkie niezapisane osiągnięcia i zdobycze w trwającej grze zostaną utracone. Wczytać grę? + +[FESZ_QD] +Czy skasować ten zapis gry? + +[FESZ_QO] +Czy nadpisać tę grę na starszym pliku? + +[FESZ_QR] +Czy jesteś pewien, że chcesz rozpocząć nową grę? Wszelkie osiągnięcia i postępy poczynione od momentu ostatniego zapisu gry zostaną utracone. Kontynuować? + +[FESZ_QS] +KONTYNUOWAĆ ZAPIS? + +[T4X4_1] +'PLAC ZABAW PATRIOTÓW' + +[T4X4_2] +'PRZEJAŻDŻKA W PARKU' + +[T4X4_3] +'W POTRZASKU!' + +[MM_1] +'KOSZMAR WIELU PIĘTER' + +[T4X4_1A] +~g~Masz ~y~5 minut~g~ na zaliczenie ~y~15~g~ punktów kontrolnych. ~g~Możesz zaliczać je w ~y~DOWOLNEJ KOLEJNOŚCI. + +[T4X4_1B] +~1~ z 15! + +[T4X4_1C] +~y~PRZEJEDŹ PRZEZ~g~ pierwszy punkt kontrolny, aby uruchomić odliczanie czasu. ~g~Zaliczenie każdego punktu jest premiowane dodatkowymi ~y~20 SEKUNDAMI~g~ + +[T4X4_2A] +~g~Masz ~y~2 minuty~g~ na zaliczenie ~y~12~g~ punktów kontrolnych. ~g~Możesz zaliczać je w ~y~DOWOLNEJ KOLEJNOŚCI. + +[T4X4_2B] +~1~ z 12! + +[T4X4_2C] +~y~PRZEJEDŹ PRZEZ~g~ pierwszy punkt kontrolny, aby uruchomić odliczanie czasu. ~g~Zaliczenie każdego punktu jest premiowane dodatkowymi ~y~10 SEKUNDAMI~g~ + +[T4X4_3A] +~g~Masz ~y~5 minut~g~ na zaliczenie ~y~20~g~ punktów kontrolnych. ~g~Możesz zaliczać je w ~y~DOWOLNEJ KOLEJNOŚCI. + +[T4X4_3B] +~y~PRZEJEDŹ PRZEZ~g~ pierwszy punkt kontrolny, aby uruchomić odliczanie czasu. ~g~Zaliczenie każdego punktu jest premiowane dodatkowymi ~y~15 SEKUNDAMI~g~ + +[T4X4_3C] +~1~ z 20! + +[T4X4_F] +~r~Wymiękasz! Może lepiej sprawdzisz się w wyścigach na hulajnodze?! + +[MM_1_A] +~g~Masz ~y~2 minuty~g~ na zaliczenie ~y~20 punktów kontrolnych~g~ w całym obiekcie! ~g~Możesz zaliczać punkty w ~y~DOWOLNEJ KOLEJNOŚCI. + +[MM_1_B] +~1~ z 20! + +[MM_1_C] +~g~To oznacza 20 sekund plus ~y~5 SEKUND~g~ premii za każdy zaliczony punkt. ~g~Zegar zaczyna odliczanie ~y~NATYCHMIAST. + +[FM2_14] +~r~Zbliżyłeś się za bardzo i wystraszyłeś Kudłatego! + +[FM2_15] +~g~Nie zbliżaj się zbytnio, bo Kudłaty zacznie coś podejrzewać! + +[UPSIDE] +~r~Przewróciłeś samochód! + +[FM2_16] +STRACHOMETR: + +[LM3_11] +~g~Misty nie będzie jeździć autobusem, załatw inny pojazd! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Wóz strażacki + +[TRASHM] +Śmieciożer + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Muł + +[CHEETAH] +Cheetah + +[AMBULAN] +Karetka pogotowia + +[FBICAR] +Samochód FBI: + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taksówka + +[KURUMA] +KURUMA + +[BOBCAT] +Bobcat + +[WHOOPEE] +Pan Smakołyk + +[BFINJC] +Zastrzyk BF + +[POLICAR] +Policja + +[ENFORCR] +Enforcer + +[SECURI] +Konwojowóz + +[BANSHEE] +Demon + +[PREDATR] +Predator + +[BUS] +Autobus + +[RHINO] +Hipcio + +[BARRCKS] +Koszary OL + +[TRAIN] +Pociąg + +[HELI] +Helikopter + +[DODO] +Dodo + +[COACH] +Autokar + +[CABBIE] +Taksówka + +[STALION] +Ogier + +[RUMPO] +Rumpo + +[RCBANDT] +Bandziorek + +[BELLYUP] +Ciężarówka Triady + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Sentinel mafii + +[YARDICR] +Lobo gangu Yardie + +[YAKUZCR] +Stinger gangu Yakuza + +[DIABLCR] +Ogier gangu Diablo + +[COLOMCR] +Krążownik Kartelu + +[HOODSCR] +Rumpo XL gangu Hoods + +[AEROPL] +Samolot + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +ZABAWKI + +[FEST_DF] +Odległość przebyta pieszo (w milach) + +[FEST_DC] +Odległość przebyta samochodem (w milach) + +[FESTDFM] +Odległość przebyta pieszo (w metrach) + +[FESTDCM] +Odległość przebyta samochodem (w metrach) + +[FEST_R1] +Plac Zabaw Patriotów w sekundach + +[FEST_R2] +Przejażdżka w parku w sekundach + +[FEST_R3] +W Potrzasku! w sekundach + +[FEST_RM] +Koszmar Wielu Pięter w sekundach + +[FEST_LS] +Ludzie uratowani przez karetkę + +[FEST_CC] +Przestępcy zabici podczas misji patrolowych + +[FEST_FE] +Liczba ugaszonych pożarów + +[FEST_LF] +Najdłuższy lot dodo + +[FEST_BD] +Najlepszy czas rozbrojenia bomby + +[FEST_RP] +Wykonane rozwałki: + +[FEST_MP] +Wykonane misje + +[FEST_BB] +Szukaj Punktów + +[FEST_H0] +Najwięcej punktów kontrolnych + +[FEST_GC] +Łączna liczba pojazdów gangów: + +[FEST_H1] +Diabelska demolka + +[FEST_H2] +Mafijna masakra + +[FEST_H3] +Krwawe kasyno + +[FEST_H4] +Rumpo-rozróba + +[USJI1] +TEKST DŁUŻEJ NIEPOTRZEBNY + +[USJI2] +TEKST DŁUŻEJ NIEPOTRZEBNY + +[USJI3] +TEKST DŁUŻEJ NIEPOTRZEBNY + +[USJ] +PREMIA ZA NIETYPOWY SKOK! + +[SPRAY] +Wprowadź samochód do warsztatu lakierniczego, aby obniżyć swój ~h~poziom złek sławy~w~, ~h~naprawić~h~ oraz przemalować~w~ swój wóz. Koszt - ~h~$1000. + +[HM1_1] +~g~Załatw 20 Purpurowych Dziewiątek w 2 minuty 30 sekund. + +[KM1_8A] { re3 change } +Naciśnij klawisz ~h~ ~k~~VEHICLE_FIREWEAPON~ ~w~, aby ~h~aktywować bombę.~w~ Nie zapomnij oddalić się od miejsca eksplozji. + +[KM1_8D] { re3 change } +Naciśnij klawisz ~h~ ~k~~VEHICLE_FIREWEAPON~ ~w~, aby ~h~aktywować bombę.~w~ Nie zapomnij oddalić się od miejsca eksplozji. + +[KM1_12] +~g~Odwieź go do dojo, ale najpierw pozbądź się gliniarzy! + +[RATNG1] +Kieszonkowiec + +[RATNG2] +Mięśniak + +[RATNG3] +Łotr + +[RATNG4] +Hazardzista + +[RATNG5] +Zbir + +[RATNG6] +Kierowca + +[RATNG7] +Twardziel do wynajęcia + +[RATNG8] +Oszust + +[RATNG9] +Współpracownik + +[RATNG10] +Sprzątacz + +[RATNG11] +Zabójca + +[RATNG12] +Złota rączka + +[RATNG13] +Egzekutor + +[RATNG14] +Capo + +[RATNG15] +Szef + +[1010] +~r~Twój pojazd dachował + +[1011] +~r~Twój pojazd dachował + +[1012] +~r~Twój pojazd dachował + +[1013] +~r~Twój pojazd dachował + +[1014] +~r~Twój pojazd dachował + +[JM4_10] +Słuchaj, młody! Najpierw zawieź mnie do pralni w chińskiej dzielnicy. Mam małą sprawę do załatwienia. + +[JM4_11] +Praczki przestały płacić haracz za ochronę. + +[JM4_12] +Tylko uważaj na wóz, Joey dopiero poskładał ten szmelc. + +[JM4_13] +Więc bez żadnych numerów, OK? + +[KM4_11] +~g~Odwieź pieniądze do kasyna! + +[FEF_BR2] +Możesz przypomnieć sobie fabułę gry, czytając zebrane dotąd streszczenia celów misji. + +[TRAIN_1] +Stacja Kurowski + +[TRAIN_2] +Stacja Rothwell + +[TRAIN_3] +Stacja Baillie + +[SUBWAY1] +Portland Station + +[SUBWAY2] +Rockford Station + +[SUBWAY3] +Staunton South Station + +[SUBWAY4] +Shoreside Terminal + +[MEA4_2] +~r~Marty Chonks nie żyje! + +[SPRAY1] +Wprowadź samochód do warsztatu lakierniczego, aby obniżyć swój ~h~poziom złej sławy~w~, ~h~naprawić~h~ oraz przemalować~w~ swój wóz. Koszt - ~h~$1000~w~. Tym razem zrobimy to za darmo. + +[JM4_A] +Tak, wiem Toni, naprawdę nieźle ją sobie wychowałem. Aż mruczy z zadowolenia, kapujesz? + +[JM4_5] +Wpadnij później to damy im coś do prania - ich własne pokrwawione gacie! + +[AMMU_A] +Luigi mówił, że potrzebujesz gnata... + +[AMMU_B] +Joey wspominał, że potrzebna ci artyleria... + +[AMMU_C] +Idź na tył sklepu. Na podwórzu zostawiłem dla ciebie dziewiątkę. + +[AMMU_D] +Mam wszystko, co potrzeba do obrony własnego gospodarstwa domowego. + +[AMMU_E] +Chcesz jeszcze pozwolenie? + +[AMMU_F] +Nie musisz pokazywać dowodu, wyglądasz na wiarygodnego gościa. + +[DETON] +DETONACJA: + +[DRIVE_A] { re3 change } +Wybierz jako broń uzi i wsiądź do pojazdu. Następnie spójrz w lewo lub w prawo - aby otworzyć ogień, naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[DRIVE_B] { re3 change } +Wybierz jako broń uzi i wsiądź do pojazdu. Następnie spójrz w lewo lub w prawo - aby otworzyć ogień, naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[RECORD] +~g~NOWY REKORD! + +[NRECORD] +~r~NIE MA NOWEGO REKORDU! + +[RCHELP] { re3 change } +Naciśnij klawisz ~k~~VEHICLE_FIREWEAPON~ lub uderz zdalnie sterowanym samochodzikiem w koła pojazdu, aby spowodować eksplozję. + +[RCHELPA] { re3 change } +Naciśnij klawisz ~k~~VEHICLE_FIREWEAPON~ lub uderz zdalnie sterowanym samochodzikiem w koła pojazdu, aby spowodować eksplozję. + +[RC_1] +Masz 2 minuty, aby wysadzić tyle samochodów gangu Diablo, ile tylko się da! + +[RC_2] +Masz 2 minuty, aby wysadzić tyle samochodów mafii, ile tylko się da! + +[RC_3] +Masz 2 minuty, aby wysadzić tyle samochodów Yakuzy, ile tylko się da! + +[RC_4] +Masz 2 minuty, aby wysadzić tyle samochodów gangu Yardie, ile tylko się da! + +[RC_5] +Masz 2 minuty, aby wysadzić tyle samochodów gangu Hoods, ile tylko się da! + +[RC_6] +Masz 2 minuty, aby wysadzić tyle samochodów Kartelu, ile tylko się da! + +[RAMPAGE] +ROZWAŁKA!! + +[RAMP_P] +ROZWAŁKA WYKONANA! + +[RAMP_F] +ROZWAŁKA NIEUDANA! + +[PAGE_00] +. + +[PAGE_01] +Załatw ~1~ludzi z gangu Diablo w 120 sekund! + +[PAGE_02] +Zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_03] +Zabij ~1~ członków mafii w ciągu 120 sekund! + +[PAGE_04] +Zabij ~1~ członków Triady w ciągu 120 sekund! + +[PAGE_05] +Zabij ~1~ członków Triady w ciągu 120 sekund! + +[PAGE_06] +Zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_07] +Rozwal ~1~ łebków z gangu Yardie w ciągu 120 sekund! + +[PAGE_08] +Spal ~1~ członków Yakuzy w ciągu 120 sekund! + +[PAGE_09] +Zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_10] +Zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_11] +Skasuj ~1~ członków gangu Yardie w ciągu 120 sekund! + +[PAGE_12] +Podpal ~1~ członków Yakuzy w ciągu 120 sekund! + +[PAGE_13] +Wysadź w powietrze ~1~ członków gangu Yardie w ciągu 120 sekund! + +[PAGE_14] +Usmaż ~1~ Kolumbijczyków w ciągu 120 sekund. + +[PAGE_15] +Rozjedź ~1~ członków gangu Hoods w ciągu 120 sekund! + +[PAGE_16] +Zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_17] +Rozjedź samochodem ~1~ Kolumbijczyków w ciągu 120 sekund! + +[PAGE_18] +Rozjedź i zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_19] +Urwij ~1~ głów Kolumbijczyków w ciągu 120 sekund! + +[PAGE_20] +Obetnij głowy ~1~ członkom gangu Hoods w ciągu 120 sekund! + +[JM1_A] +Hej, umieram z nudów! Kiedy w końcu mnie przelecisz? + +[JM1_B] +Za chwileczkę, złotko! Muszę się zająć jedną drobną kwestią... + +[JM1_C] +Mam dla ciebie robótkę, kolego. + +[JM1_D] +Bracia Forelli od dawna wiszą mi kasę. Od zbyt dawna. + +[JM1_E] +Trzeba dać im lekcję szacunku. + +[JM1_F] +Buźka Forelli napycha właśnie swój bęben w Bistro w St. Marks, + +[JM1_G] +więc ukradnij jego samochód i zabierz go do warsztatu 8-Balla w Harwood. + +[JM1_H] +Znasz 8-Balla, nie? + +[JM1_I] +Kiedy 8-Ball założy w samochodzie ładunek, odprowadź furę na to samo miejsce, z którego ją wziąłeś. + +[JM1_J] +Potem usiądź w bezpiecznej odległości i podziwiaj fajerwerki. + +[JM1_K] +Tylko się pospiesz, grubas nie będzie przecież jadł cały dzień. + +[CAT2_A1] +Jazda, głupia dziwko! + +[CAT2_A] +Trzeba zadać sobie pytanie: czy przyjechałeś ratować Marię czy też żeby spotkać się ze mną? + +[CAT2_B] +Mam dla ciebie wiadomość: + +[CAT2_B2] +romans z tobą to był wyłącznie interes, za to zastrzelę cię dla przyjemności. + +[CAT2_C] +Jesteś muy peccino amigo! + +[CAT2_D] +Rzuć forsę. + +[CAT2_E] +Ostatnio byłeś bardzo pilnym uczniem! + +[CAT2_E2] +Ale nic się nie nauczyłeś. Mnie nie wolno ufać. + +[CAT2_E3] +Zabić tego idiotę. + +[CAT2_J] +Poderwij ten złom w powietrze! + +[HM5_1] +Yo, Ice mówił, że przyjdziesz. Teraz zasady: bierzemy tylko bejsbole - bez spluw i bez samochodów. + +[HM5_5] +To bitwa o honor, czaisz? + +[HELP14] +Aby podnieść broń, po prostu wejdź na nią. Nie możesz podnieść broni, jeżeli siedzisz w samochodzie. + +[CRUSH] +Zaparkuj w oznaczonym miejscu i wysiądź z pojazdu. Samochód zostanie zgnieciony. + +[DIAB2_B] +Gang brzydkich panów zagroził, że pozbawi mnie mojego gwiazdora, jeżeli nie odpalę im doli. + +[DIAB2_C] +Zatańczyli z niewłaściwym człowiekiem, amigo. + +[DIAB2_D] +Oni mają słabość do lodów. + +[DIAB2_E] +Odszukaj bombę, którą zostawiłem w Harwood, + +[DIAB2_F] +porwij jeden z samochodów sprzedających lody w całym mieście, + +[DIAB2_G] +a potem zwab tych idiotów reklamowym sygnałem lodziarza. + +[DIAB2_H] +Ukrywają się w magazynach przy Atlantic Quay. + +[DIAB3_A] +Jacyś niegrzeczni członkowie Triady ukradli wczoraj w nocy mój samochód, + +[DIAB3_B] +rozbili go i zostawili, aby się dopalił. + +[DIAB3_C] +W bagażniku miałem kilka wyjątkowo cennych pamiątek - + +[DIAB3_D] +prawdziwe rzadkie okazy, których nie da się niczym zastąpić, mój przyjacielu. + +[DIAB3_E] +Na granicy Chinatown ukryłem dla ciebie naprawdę potężną broń. + +[DIAB3_F] +Skorzystaj z niej i naucz wandali z Triady, co oznacza zasłużony gniew El Burro. + +[DIAB3_1] +ZABIJ 25 CZŁONKÓW TRIADY + +[DIAB4_A] +Jakiś marny złodziejaszek ukradł mi półciężarówkę z najnowszym wydaniem moich magazynów... Prosto z drukarni! + +[DIAB4_B] +Ale ten zaćpany idiota nie zamknął tylnych drzwi + +[DIAB4_C] +i teraz moja starannie opracowana literatura dla dorosłych, + +[DIAB4_D] +opatrzona wysmakowanymi zdjęciami, wala się po całym Liberty! + +[DIAB4_E] +Weź półciężarówkę i jedź śladem magazynów 'Donkey Daje Całemu Dallas' część 1, 2 i 3. + +[DIAB4_F] +Zbieraj wszystko, co znajdziesz. + +[DIAB4_G] +Kiedy dotrzesz po tropie do tego złodziejskiego ĆPUNA, załatw go! + +[DIAB4_H] +A potem zawieź moje książeczki z Donkey do Magazynów XXX w Dzielnicy Czerwonych Świateł. + +[DIAB4_1] +~g~Zabierz samochód na zaplecze Magazynów XXX. + +[HM1_E] +Pokaż tym zaćpanym siuśkom, na czym polega prawdziwa jazda samochodem. + +[HM1_H] +Usuń mi te 'Dziewiątki' z widoku! + +[HM2_A] +Te 'Dziewiątki' nadal nadeptują mi na odcisk. + +[HM2_B] +Szczeniaki załatwiły sobie samochody opancerzone i sprzedają PROCHY... + +[HM2_C] +...naszym niewinnym czarnym braciom. + +[HM2_D] +Zostawiłem dla ciebie samochód. + +[HM2_E] +W środku znajdziesz parę zabawek, które pomogą ci dać siuśkom nauczkę... + +[HM3_A] +Jakiś samobójca wsadził bombę do mojej gabloty. + +[HM3_B] +Jeżeli stracę tę furę, mogę pożegnać się z moją reputacją na ulicach. + +[HM3_C] +Weź mój wóz i zabierz go do warsztatu w St. Marks, brachu. + +[HM3_D] +Niech chłopaki się nim zajmą i rozbroją bombę. + +[HM3_E] +Zegar już odlicza czas, a bomba chyba jest uszkodzona. + +[HM3_F] +Wpadniesz w jedną dziurę za dużo i to cacko wyleci w powietrze. + +[HM3_G] +Na co jeszcze czekasz? + +[HM4_A] +Yo, na lotnisku im. Francisa właśnie roztrzaskał się samolot Banku Narodowego. + +[HM4_B] +Platyna wala się po całym pasie startowym. + +[HM4_C] +Załatw samochód i zgarnij tyle, ile tylko się da. + +[HM4_F] +Możesz wysypać platynę przy jednym z moich garaży. + +[HM4_G] +Platyna jest cholernie ciężka, więc nie zdziw się, kiedy przeciążysz gablotę i fura będzie się wlokła jak ślimak. + +[HM4_H] +Lepiej regularnie zrzucaj towar przy jakimś garażu. + +[HM5_A] +Z gangu 'Dziewiątek' zostały już tylko niedobitki... + +[HM5_B] +ale nadal chcą się pobawić. + +[HM5_C] +Zgodzili się na pojedynek twarzą w twarz. + +[HM5_D] +Ich banda przeciwko dwóm spośród nas, a raczej... + +[HM5_E] +przeciwko tobie i jeszcze komuś + +[HM5_F] +Poszedłbym z tobą, ale... + +[HM5_G] +jeszcze przez trzy miesiące mam wyrok w zawieszeniu i nie mogę rozrabiać, + +[HM5_H] +sam rozumiesz. + +[HM5_I] +Weźmiesz ze sobą mojego młodszego brata. + +[HM5_J] +On ci pokaże, gdzie jesteście umówieni. + +[MEA1_B] +Nazywam się Chonks, Marty Chonks. + +[MEA1_C] +Prowadzę Delikatesy Pod Psem, tuż za rogiem. + +[MEA1_D] +Mam kłopoty z kasą, ale kto ich dzisiaj nie ma? + +[MEA1_E] +Jestem umówiony z kierownikiem mojego banku. + +[MEA1_F] +Ten cwaniaczek cały czas podnosi odsetki mojego kredytu, żeby móc odkroić swoją działkę. + +[MEA1_G] +Weź mój samochód, jedź po niego i przywieź go tutaj. + +[MEA1_H] +Mam małą niespodziankę dla tego krwiopijcy!! + +[MEA2_A] +Wynająłem paru złodziejaszków, aby włamali się do mojego mieszkania + +[MEA2_C] +Te złodziejskie szumowiny grożą, że zakapują mnie w firmie ubezpieczeniowej, + +[MEA2_D] +jeżeli nie odpalę im doli. + +[MEA2_E] +To się po prostu w głowie nie mieści! + +[MEA2_F] +W fabryce zostawiłem samochód. + +[MEA2_G] +Skorzystaj z niego i zabierz złodziei z Dzielnicy Czerwonych Świateł. + +[MEA2_H] +Potem przywieź ich do fabryki. Tam wytłumaczę im mój punkt widzenia w tej sprawie. + +[MEA3_A] +Mój interes zbankrutuje, jeżeli szybko nie dostanę do rąk większej gotówki. + +[MEA3_B] +Moja żona ma sporą polisę ubezpieczeniową, a i tak przez całe życie tylko wyciągała ode mnie pieniądze. + +[MEA3_C] +Zostawiłem samochód w umówionym miejscu. + +[MEA3_D] +Jedź po moją żonę do pawilonu 'Klasyczny Manicure' i przywieź ją do fabryki. + +[MEA4_A] +Cholera, wpakowałem się w tarapaty! + +[MEA4_B] +Okazuje się, że moja żona romansowała z gościem, któremu wiszę pieniądze. + +[MEA4_C] +Jest mocno wkurzony i chce mi się zrewanżować! + +[MEA4_E] +on myśli, że chcę oddać mu kasę... + +[MEA4_F] +ale mnie się zdaje... + +[MEA4_G] +że do misek psów z Liberty jeszcze w tym miesiącu trafi kolejny rodzaj mięska! + +[WELCOME] +WITAMY W + +[HM1_2] +~g~Zdobądź samochód! Pamiętaj, że liczą się tylko kolesie rozjechani samochodem! + +[HELP8_B] +Naciśnij klawisz~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, aby ~h~przybliżyć ~w~widok przez lunetkę karabinu oraz klawisz~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, aby ~h~oddalić~w~ widok. + +[LRQC_1] +Muszę, hm, porozmawiać z Asuką. + +[LRQC_2] +Może wyskoczysz na spacer po mieście? + +[LRQC_3] +Musisz znaleźć sobie jakąś kryjówkę. + +[LRQC_4] +W Belville jest magazyn, który powinien ci odpowiadać. + +[LRQC_5] +Kiedy będziesz gotowy, wróć do mojego apartamentu, + +[LRQC_6] +to pogadamy, co robić dalej. + +[JM6_5] +~g~Musisz załatwić pojazd, którym uciekniemy, idioto! + +[JM2_F] +Jeżeli potrzebujesz giwery, to idź na zaplecze Amu-Nacji naprzeciwko stacji metra. + +[LOVE4_7] +~g~Na Wyspie Staunton jest jakiś plac budowy, może to właśnie tam zabrali pakunek. + +[LOVE4_8] +~g~Aby otworzyć ten garaż, musisz mieć samochód. + +[TSCORE] +ZAROBEK: $~1~ + +[AM1_9] +~r~Salvatore uciekł z powrotem do klubu 'U Luigiego'! + +[AM1_6] +~g~Jeżeli będziesz kręcił się wokół klubu Luigiego, to mafia z pewnością cię wypatrzy! + +[TM2_3] +~g~To pułapka! Załatw ich!!! + +[FM4_1] +Tu mówi Maria. Ten samochód to pułapka! Spotkaj się ze mną na południowym końcu Mostu Callahan. + +[JM1_7] +~g~Zamknij drzwi samochodu! Mike może coś zwąchać! + +[KM5_1] +~g~DILER ROZJECHANY!!! + +[KM5_6] +~g~Musisz zamordować co najmniej 8 dilerów z gangu Yardie. + +[KM5_7] +~g~Zabijaj jak najszybciej! Kiedy sprzedadzą cały towar, pochowają się w swoich norach! + +[RM3_8] +~r~Ten samochód to tylko przynęta!! + +[LM3_8] +Cześć, jestem Joey. + +[LM3_9] +Luigi mówił, że można ci ufać, więc wpadnij później, + +[KM3_5] +~g~Naciśnij klakson, aby zacząć rozmowy. + +[LOVE7] +ZNIKNIĘCIE LOVE'A + +[LOVE2_5] +~g~Z Kenji'ego została już tylko kupa mięsa na twoje masce! Uciekaj z Newport i pozbądź się samochodu! + +[AS2_11] +~g~~1~ Z 9! + +[GARAGE1] +~g~Wysiądź z samochodu i wyjdź na zewnątrz. + +[KM3_11] +~g~Kartel został zaatakowany, a teczka nie została odzyskana. + +[KM3_12] +~g~Zabij wszystkich Kolumbijczyków, zniszcz pojazdy i odzyskaj teczkę. + +[KM3_13] +~g~Odwieź teczkę do kasyna. + +[RM5_6] +~g~Prawie go masz! Staranuj jego wóz swoim pojazdem albo rozwal materiałami wybuchowymi! + +[PBOAT_1] { re3 change } +Naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby otworzyć ogień z działek na łodzi. + +[PBOAT_2] { re3 change } +Naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby otworzyć ogień z działek na łodzi. + +[DIAB1_B] +Mówi El Burro z gangu Diablo. + +[DIAB1_D] +Jesteś nowy w Liberty, ale na ulicach już zaczyna być o tobie głośno. + +[DIAB1_E] +Organizuję dla rozrywki mały wyścig. Punkt startu znajduje się przy starej szkole w okolicach Mostu Callahan. + +[DIAB1_F] +Skołuj sobie gablotę. Wygrywa ten, kto pierwszy zaliczy wszystkie punkty na trasie. + +[HM2_1] { re3 change } +Użyj zdalnie sterowanych samochodzików, aby zniszczyć samochody opancerzone. Naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby zdetonować ładunek. + +[HM2_1A] { re3 change } +Użyj zdalnie sterowanych samochodzików, aby zniszczyć samochody opancerzone. Naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby zdetonować ładunek. + +[HM2_2] +~r~Nie udało ci się zniszczyć wszystkich samochodów opancerzonych! + +[HM2_6] +~g~Samochód opancerzony został zniszczony! + +[RM3_A] +Znam w tym mieście jednego bardzo ważnego faceta, prawdziwą grubą rybę, + +[RM3_H] +który słynie ze swych, jak to ująć, nietypowych upodobań i wielkiej fortuny, jaką na nie wydaje. + +[RM3_B] +Uwikłał się w proces sądowy, a prokuratura zdobyła kompromitujące go fotografie. + +[RM3_C] +Zrobili je na imprezie w kostnicy czy coś takiego. + +[LOVE6_A] +Przyjacielu, przyjmij ode mnie lekcję prowadzenia interesów. + +[LOVE6_E] +Jeżeli posiadasz przedmiot jedyny w swoim rodzaju, to dokładnie wszyscy, nawet ze swoimi żonami, będą się ze wszystkich sił starać ci go odebrać, + +[LOVE6_C] +Oddziały antyterrorystyczne otoczyły obszar, na którym znajduje się mój współpracownik wraz z pakunkiem. + +[LOVE6_D] +Jedź tam i weź ciężarówkę. Posłużysz jako przynęta. + +[LOVE6_F] +Odciągnij ich uwagę tak, aby mój przyjaciel mógł spokojnie opuścić to miejsce. + +[AM3_C] +Teraz najprawdopodobniej czyha na zatoce! Ukradnij łódź policyjną i raz na zawsze zakończ jego karierę! + +[FESZ_UC] +ANULUJ + +[FEDS_SM] +L1, R1 - ZMIANA MENU + +[FEDS_AS] +;= - ZMIANA WYBORU + +[FEDSAS2] +<> - ZMIANA WYBORU + +[FEDS_SS] +L1, R1 - ZMIANA WYBORU + +[FEDSSC1] +; - SZYBSZE PRZEWIJANIE + +[FEDSSC2] +Err:509 + +[MEA2_3] +~g~Odwieź samochód do fabryki. + +[RM1_3] +~r~McAffrey zwiał! + +[RM1_4] +~g~Zużyłeś wszystkie granaty! Wróć po nowy zapas do Amu-Nacji! + +[RM1_5] +~g~Wracaj i podpal ten dom! + +[RM6_4] +~g~Jedź do dziupli i zabierz rzeczy Raya. + +[RM6_5] +~g~CIA nieustannie obserwuje most, znajdź inną trasę. + +[HM2_F] +i sprzątnąć ich pancerny złom. + +[HM_4] +'W POGONI ZA KASĄ' + +[MEA2_B5] +TEKST JUŻ NIEPOTRZEBNY + +[MEA1_B5] +TEKST JUŻ NIEPOTRZEBNY + +[MEA3_B5] +TEKST JUŻ NIEPOTRZEBNY + +[MEA4_B7] +ale jeżeli zechcesz wpaść do mojego biura... + +[MEA3_B4] +Marty chce się ze mną widzieć? Lepiej niech się streszcza, bo muszę jeszcze zrobić sobie dzisiaj nową fryzurę. + +[KM3_7] +Ludzie, to pułapka Yakuzy! + +[FES_LOF] +Wczytywanie nieudane. + +[FES_SLO] +ZAPISZ PLIK + +[FES_ISC] +USZKODZONY + +[FESZ_TI] +ZAPISZ Z1 + +[FESZ_SA] +Zapis gry + +[MC_LDFL] +Wczytywanie nieudane! + +[MC_NWRE] +Trwa ponowne uruchamianie gry. + +[LOVE6_3] +~g~Masz ~1~ sekund na powrót do konwojowozu albo misja zakończy się porażką. + +[LOVE6_4] +~r~Straciłeś fałszywy konwojowóz! + +[HELP1] +Zatrzymaj się wewnątrz niebieskiego pola. + +[HELP12] +Stań na niebieskim polu, aby rozpocząć misję. + +[HJSTAT] +Odległość: ~1~,~1~m Wysokość:~1~,~1~m Salta: ~1~ Obroty: ~1~_ + +[HJSTATW] +Odległość: ~1~.~1~m Wysokość: ~1~.~1~m Salta: ~1~ Obroty: ~1~_ Plus doskonałe lądowanie! + +[DIAB1_5] +CZAS WYŚCIGU: + +[LOVE3_4] +~r~Zniszczyłeś samolot! + +[F_FAIL1] +Misja strażacka zakończona. + +[F_CANC] +~r~Misja strażacka anulowana! + +[F_EXTIN] +POŻARY: + +[A_COMP1] +Misja ratunkowa wykonana! + +[A_CANC] +~R~Misja ratunkowa anulowana! + +[A_COMP3] +Misja ratunkowa wykonana! Ty chyba nigdy się nie męczysz! + +[ATUTOR] +Wciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyć misje ratunkowe. + +[ATUTOR3] +Wciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyć misje ratunkowe. + +[ALEVEL] +Misja Ratunkowa, Poziom ~1~ + +[A_FAIL1] +Misja ratunkowa zakończona. + +[FEST_HA] +Najwyższy poziom misji ratunkowej + +[A_SAVES] +URATOWANI LUDZIE:~1~ + +[C_KILLS] +ZABICI PRZESTĘPCY: ~1~ + +[HM1_B] +Mam problem z paroma frajerami. + +[AM2_A] +Śmierć Salvatore to radosna wiadomość, + +[AM2_A2] +widać, że jesteś dobrym zabójcą. Lubię tę cechę u ludzi. + +[AM2_B] +To mój brat Kenji. + +[AM2_C] +Asuka ma dla ciebie małą robótkę. Kiedy skończysz, wpadnij do mojego kasyna, to pogadamy. + +[AM2_D] +Zupełnie jak Kenji, on też zawsze chce bawić się moimi zabawkami. + +[AM2_E] +Moja wtyczka w policji donosi, że Mafia obserwuje nasze lokale w całym mieście. + +[AM2_E2] +Prawdopodobnie usiłują cię wytropić. + +[AM2_F] +Dopóki nie załatwimy tej sprawy, nie możemy prowadzić zwykłej działalności. + +[AM2_G] +Załatw tych głupawych szpiegów i raz na zawsze zakończ tę wendettę. + +[F_START] +~g~W okolicach ~a~ zauważono płonący pojazd. Udaj się tam i ugaś pożar. + +[AM4_1A] +Odszukaj telefon na Park West Belleville. + +[AM4_1B] +Odszukaj telefon na kampusie Liberty. + +[AM4_1C] +Odszukaj telefon na Park South Belleville. + +[AM4_1D] +Spotkajmy się w parku przy toaletach. + +[HJSTATF] +Odległość: ~1~ stóp Wysokość: ~1~ stóp Salta: ~1~ Obroty: ~1~_ + +[HJSTAWF] +Odległość: ~1~.~1~ stóp Wysokość: ~1~.~1~ stóp Salta: ~1~ Obroty: ~1~_ Plus doskonałe lądowanie! + +[HM1_F] +Lepiej uważaj - na ulicach będą też ludzie z Jacks, którzy mogą uznać, że polujesz również na nich! + +[HM1_D] +Nazywają się 'Dziewiątki' i ubierają się na purpurowo. Każdy dzień, kiedy te leszcze obnoszą się ze swoim barwami, + +[HM1_G] +to dzień wstydu dla mojego gangu. + +[MEA2_B] +i ukradli parę rzeczy, co pozwoli mi wyciągnąć kasę z odszkodowania. + +[TM3_H] +~w~Dobrze się spisałeś, młody, naprawdę dobrze. + +[TM3_I] +~w~Chodź, Don chce cię poznać. + +[TM3_J] +~w~Heeeej, Luigi! + +[TM3_K] +~w~Moje dziewczynki tęsknią za tobą, Salvatore, dawno cię u nas nie było. + +[TM3_L] +~w~Przekaż im ode mnie, że kiedy załatwimy całą tę nieszczęsną sprawę, + +[TM3_M] +~w~razem pojedziemy do klubu i uczcimy zwycięstwo + +[TM3_N] +~w~Oto i mój chłopak. + +[TM3_N2] +~w~Jak się masz, tato? + +[TM3_O] +~w~Znalazłeś już sobie przyzwoitą kobietę? + +[TM3_P] +~w~Ech, twoja matka, wieczny odpoczynek racz jej dać Panie, przewraca się w grobie, + +[TM3_Q] +~w~bo jej syn jeszcze nie ma żony. + +[TM3_R] +~w~Wiem, tato. Pracuję nad tym. + +[TM3_S] +~w~TONI! Jak twoja Mamuśka? + +[TM3_T] +~w~To wspaniała kobieta. Silna. Firenze. + +[TM3_U] +~w~Mama ma się dobrze... Znakomicie. + +[TM3_V] +~w~Doskonale, doskonale. Posłuchajcie, panowie, rozgośćcie się w środku, a ja porozmawiam z naszym nowym przyjacielem. + +[TM3_W] +~w~Widzę przed tobą wielką przyszłość, chłopcze... + +[RM1_A] +Ta szuja McAffrey wziął więcej łapówek niż ktokolwiek inny. + +[RM1_B] +Teraz myśli, że zasłuży na wygodną emeryturę, jeżeli zakapuje nas policji. + +[RM1_C] +Właśnie nas wsypał! + +[RM4_B] +Trzeba go uciszyć raz na zawsze. + +[RM4_E] +Od jutra ma spać z rybami, a nie jadać je na kolację. + +[LOVE3_B] +Dziś w nocy mały samolot przeleci nad zatoką, podchodząc do lądowania. + +[LOVE4_D] +Niestety, władze lotniska przejęły samolot i zaczęły rozbierać go na części, + +[LOVE4_H] +dopóki nie rzuciłem na szalę całego swojego autorytetu. + +[LOVE4_E] +Przejedź przez most do Shoreside Vale i jedź na lotnisko im. Francisa. + +[GTAB_A] +Lepiej stąd znikajmy. Cholera wie, co to jest, + +[GTAB_B] +ale zdaje się, że temu facetowi bardzo na tym zależy, więc to z pewnością musi mieć jakąś wartość. + +[GTAB_C] +Co, u diabła! + +[GTAB_D] +TO TY! + +[GTAB_E] +Hej, spokojnie, amigo! De nada! De nada! + +[GTAB_F] +Kiedy ostatni raz cię widziałem, twoje truchło spływało do ścieków! + +[GTAB_G] +Nie strzelaj, amigo. Nie ma problemu. My przyjaciele. Proszę, weź to sobie. + +[GTAB_H] +Nie bądź taką ciotą! + +[GTAB_I] +Nie mamy wyboru, kotku! + +[GTAB_J] +Zawsze mamy wybór, tępaku! + +[GTAB_K] +Przepraszam za tę głupią sukę, one wszystkie są jednakowe... por favor? + +[GTAB_L] +Więc ta dziwka zwiała? + +[GTAB_M] +Ale zrobiłeś mi przysługę, + +[GTAB_N] +nie jesteś jedyną osobą, która ma rachunki do wyrównania z Kartelem. + +[GTAB_O] +Ten gnidy zabiły mi brata! + +[GTAB_P] +Nigdy nie zabiłem żadnego członka Yakuzy! + +[GTAB_Q] +KŁAMIESZ! Wszyscy widzieliśmy zabójcę z Kartelu. + +[GTAB_R] +Wytropimy i wytłuczemy was wszystkich, wy kolumbijskie kundle! + +[GTAB_S] +Pobawię się z twoim przyjacielem, aby wydobyć z niego jakieś informacje i troszkę się rozerwać. + +[GTAB_T] +Ej, wpadnij później, będe cię jeszcze potrzebował. + +[GTAB_U] +Proszę, amigo, nie zostawiaj mnie z nią! Ta chica to wariatka! Amigo? Hej, AMIGOOO!!!... Aaaaaa! + +[LOVE5_A] +Raz po raz dowodzisz, że warto w ciebie inwestować, a to rzadkość w dzisiejszych czasach kłamstwa i obłudy. + +[KM3_1] +~g~Kartel spodziewa się kolesia z Yardies, więc idź i ukradnij samochód gangu Yardie! Powinieneś znaleźć ich w Newport, na północy. + +[LOVE1_1] +~g~Podwędź samochód gangu kolumbijskiego, abyś mógł swobodnie przeniknąć do ich kryjówki. Samochód znajdziesz na północ stąd, w Fort Staunton. + +[FM1_Q1] +~w~Szukasz mocnych wrażeń? Może odrobinę... Hm? Odrobinę HEROINY? + +[FM1_R] +~w~Czołem, Chico. Nie, daj mi to, co zawsze. + +[FM1_T] +~w~Dzięki, Chico. Na razie! + +[FM1_W] +~w~W porządku, piesku! Posiedź tu i popilnuj samochodu, a ja wyskoczę i poruszam trochę tyłkiem. + +[FM1_X] +~w~OK, piesku! Znikajmy stąd. Hau hau! + +[FM1_Q] +~g~Cześć, Mario! Moja ulubiona klientka! + +[FM1_S1] +~w~Hej, może wpadniesz na imprezę w tej pustej hali magazynowej na wschodnim krańcu Atlantic Quays? + +[FM1_U] +~w~Gracias i życzę przyjemnych wrażeń. To niezły towar... + +[FM1_V] +~w~Jazda, piesku! Jedziemy zajrzeć na tę imprezę! + +[FM1_SS] +~r~NASŁUCH RADIOWY: ~g~Cztery-pięć do wszystkich jednostek: Zapewnić wsparcie akcji antynarkotykowej w Atlantic Quays... + +[LOVE6_B] +nawet jeżeli mają tylko blade pojęcie na temat jego rzeczywistej wartości. + +[TM3_A1] +~r~Joey się usmażył! + +[TM3_A2] +~r~Joey i Lugi spiekli się na węgiel! + +[TM3_A3] +~r~Joey, Luigi i Toni usmażeni! + +[FM4_2] +Posłuchaj, Salvatore podejrzewa, że kombinujemy coś za jego plecami, + +[FM4_3] +dlatego postanowił sprzedać cię Kartelowi. + +[FM4_4] +Nie mogę do tego dopuścić. Najgorsze w tym wszystkim jest to, + +[FM4_4B] +że to moja wina... To ja mu powiedziałam, że między nami coś jest... + +[FM4_5] +Nie pytaj mnie, po co. Sama nie wiem. + +[FM4_6] +Posłuchaj, na terytorium mafii jesteś poszukiwany, ja też chciałabym się stąd wyrwać. + +[FM4_6B] +Widziałam już za dużo śmierci, zbyt wiele krwi! + +[FM4_7] +Mam starą dobrą przyjaciółkę... Nazywa się Asuka. Możemy jej zaufać. + +[FM4_8] +Dobra, wystarczy już tych przemówień. + +[FM4_9] +Zbierajmy się stąd, zanim pojawią się tu całe wycieczki rozhisteryzowanych Włochów, którzy będą chcieli rozstrzygać z nami rodzinne zatargi. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCENT + +[CRED003] +LESLIE BENZIES + +[CRED004] +KIEROWNIK ARTYSTYCZNY + +[CRED005] +AARON GARBUT + +[CRED006] +KIEROWNIK TECHNICZNY + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +PROJEKT + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +SCENARIUSZ + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +POSTACI + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMACJA & REŻYSERIA + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +PROJEKTY POJAZDÓW + +[CRED024] +PAUL KUROWSKI + +[CRED025] +GRAFICY + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +KODERZY + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +MUZYKA + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +KONCEPCJA I MASTERING DŹWIĘKU + +[CRED042] +ALLAN WALKER + +[CRED043] +PROGRAMOWANIE AUDIO + +[CRED044] +RAYMOND USHER + +[CRED045] +KIEROWNIK TESTÓW + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +GŁÓWNI TESTERZY + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTERZY + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +POMOC TECHNICZNA + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +PRODUCENT WYKONAWCZY + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCENT + +[CRED064] +DAN HOUSER + +[CRED065] +DYREKTOR DS. ROZWOJU + +[CRED066] +JAMIE KING + +[CRED067] +PRODUCENT TECHNICZNY + +[CRED068] +GARY J. FOREMAN + +[CRED069] +PRODUCENT POMOCNICZY + +[CRED070] +JEREMY POPE + +[CRED071] +KOORDYNACJA MUZYCZNA + +[CRED072] +TERRY DONOVAN + +[CRED073] +ZESPÓŁ PRODUKCYJNY ROCKSTAR + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +WICEPREZES DS. MARKETINGU + +[CRED086] +TERRY DONOVAN + +[CRED087] +KOORDYNACJA TECHNICZNA + +[CRED088] +BRANDON ROSE + +[CRED089] +KIEROWNIK DS. ZAPEWNIENIA JAKOŚCI + +[CRED090] +JEFF ROSA + +[CRED091] +GŁÓWNY ANALITYK + +[CRED092] +ADAM DAVIDSON + +[CRED093] +ANALITYK GRY + +[CRED094] +RICHARD HUIE + +[CRED095] +ZESPÓŁ TESTUJĄCY + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +REDAKCJA 'LIBERTY TREE' + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +SEKWENCJE FILMOWE + +[CRED108] +SCENARIUSZ: DAN HOUSER I JAMES WORALL + +[CRED109] +REŻYSERIA DŹWIĘKU: DAN HOUSER + +[CRED110] +PRODUKCJA DŹWIĘKU: RENAUD SEBBANE + +[CRED111] +OBSADA + +[CRED112] +FRANK VINCENT JAKO SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO JAKO LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN JAKO TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT JAKO JOEY LEONE + +[CRED116] +DEBBI MAZAR JAKO MARIA + +[CRED117] +KYLE MACLACHLAN JAKO DONALD LOVE + +[CRED118] +ROBERT LOGGIA JAKO RAY MACHOWSKI + +[CRED119] +GURU JAKO 8-BALL + +[CRED120] +SONDRA JAMES JAKO MAMUŚKA + +[CRED121] +LIANA PAI JAKO ASUKA + +[CRED122] +LES MAU JAKO KENJI + +[CRED123] +CYNTHIA FARRELL JAKO CATALINA + +[CRED124] +AL. ESPINOSA JAKO MIGUEL + +[CRED125] +CHRIS PHILLIPS JAKO EL BURRO + +[CRED126] +HUNTER PLATIN JAKO CHICO + +[CRED127] +WALTER MUDU JAKO D-ICE + +[CRED128] +CURTIS MCCLARIN JAKO CURTLY + +[CRED129] +BILL FIORE JAKO DARKEL + +[CRED130] +CHRIS PHILLIPS JAKO MARTY CHONKS + +[CRED131] +HUNTER PLATIN JAKO KUDŁATY BOB + +[CRED132] +WALTER MUDU JAKO KING COURTNEY + +[CRED133] +HUNTER PLATIN JAKO JEDNORĘKI PHIL + +[CRED134] +KIM GURNEY JAKO MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMACJA + +[CRD136A] +ALEX HORTON + +[CRED137] +REŻYSERIA + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUKCJA + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +NAGRANIA PRZEPROWADZONO W MODERN UPRISINGS STUDIOS, BROOKLYN + +[CRED140] +AKTORZY + +[CRD140A] +MARTINEZ + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRED141] +DIALOGI PRZECHODNIÓW + +[CRED142] +TEKST: DAN HOUSER, NAVID KHONSARI I JAMES WORALL + +[CRED143] +REŻYSERIA: CRAIG CONNER, DAN HOUSER I LAZLOW + +[CRED144] +PRODUKCJA: RENAUD SEBBANE + +[CRED145] +OBSADA + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +STACJE RADIOWE ORAZ MUZYKA + +[CRED218] +PRODUKCJA DLA ROCKSTAR UK + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +KOORDYNATOR ŚCIEŻKI DŹWIĘKOWEJ + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUKCJA DLA ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +REDAKCJA + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +TEKSTY I WIZERUNKI PREZENTERÓW: + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECJALNE PODZIĘKOWANIA DLA: + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTYŚCI + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CRED270] +MIKE HONG + +[CINCAM] +Kamera Filmowa + +[KM1_13] +Wprowadź samochód do garażu! + +[KM3_14] +~r~Zostałeś zauważony, układ odwołany! + +[EBAL_H] +Poczekaj tutaj, brachu, a ja pójdę do środka i pogadam z Luigim. + +[EBAL_M] +Tylko pamiętaj - nic nie kombinuj z moimi dziewczynami! + +[LM2_F] +Potem zabierz jego samochód i przemaluj go. + +[LM2_D] +proszę bardzo. + +[LM1_9] +Cześć, jestem Misty... + +[LM4_A] +Jakaś gnida z gangu Diablo nasyła swoje brudne dziwki na moje terytorium. + +[FM2_B] +Mamy kreta! + +[FM2_C] +Żaden z niego alfons czy diler, więc pewnie dorabia na boku sprzedając informacje. + +[FM3_CC] +~w~Bracie, wróć, kiedy będziesz miał pieniądze. + +[FEDS_AM] +<> - ZMIANA MENU + +[LOVE5_5] +~r~Nie udało cię się ochronić ciężarówki! + +[RM6_6] +~r~Ray nie żyje! + +[RM6_7] +~r~Ray spóźnił się na samolot. + +[RM6_8] +~r~Zgubiłeś Raya, wracaj po niego. + +[FM1_10] +~g~Zostawiłeś Marię - zawróć i ją zabierz. + +[LOVE4_9] +~r~Samolot został zniszczony! + +[LOV4_10] +~r~Jedyny ślad, który wskazywał, gdzie zniknęła paczka, został zniszczony! + +[KM2_D] +Nie muszę chyba dodawać, że przekażę mu te samochody w darze, aby spłacić dług, który u niego zaciągnąłem. + +[KM4_B] +Interes idzie na tyle dobrze, że dziś możemy odebrać należną nam opłatę za ochronę. + +[KM2_E] +Zdobądź samochody z tej listy i dostarcz je do warsztatu za parkingiem w Newport. + +[FM3_8I] +~w~Znajdź dobre stanowisko strzeleckie. Kiedy oddasz pierwszy strzał, ja zrobię to, co do mnie należy. + +[LOVE1_B] +Doświadczenie uczy mnie, że ludzie tacy jak ty potrafią być niezwykle lojalni za odpowiednią cenę. + +[LOVE1_H] +ale niektórzy ludzie robią się coraz bardziej chciwi. + +[LOVE1_C] +Znam pewnego starszego pana, pochodzącego z krajów Orientu, który jest dla mnie niezwykle cenny. + +[LOVE1_I] +Niestety został on porwany przez jakiś gang z Ameryki Południowej w okolicach Aspatrii. + +[MEA4_D] +Zgodziłem się z nim spotkać... + +[MEA4_B4] +Marty cię przysyła, co? W porząsiu, pokażę tej gliście, jak się robi interesy. + +[MEA4_B5] +Carl, cześć! Ee, potrzebuję jeszcze trochę czasu, żeby zebrać dla ciebie pieniądze. + +[MEA1_B4] +Ach, przysłał cię Chonks, prawda? Chodźmy odwiedzić naszego wspólnego przyjaciela. + +[HM5_6] +Trzeba rozłupać parę łbów... + +[LOVE1_5] +~g~Przestań się obijać, załatw samochód Kolumbijczyków i uratuj przyjaciela Love'a. + +[AS1_D] +~w~Posłuzysz jako przynęta i ściągniesz szwadrony śmierci za sobą do Pike Creek, + +[AS1_E] +~w~gdzie moi ludzie urzadzą im właściwe przyjęcie. + +[AS2_C] +~w~Kartel działa pod przykrywką firmy Dom Kawy Kappa. + +[AS2_E] +~w~Nie mamy wyjścia, trzeba wyłączyć z gry te punkty sprzedaży dragów. + +[AS2_F] +~w~Rozwal je w drzazgi!! + +[AS2_A1] +~w~Miguel to doskonały przykład słynnej latynoskiej odporności. + +[AS2_A2] +~w~Ręce opadają mi ze zmęczenia. + +[SIREN_3] +Aby włączyc syrenę pojazdu, naciśnij klawisz ~h~~k~~VEHICLE_HORN~ ~w~. + +[SIREN_4] +Aby włączyc syrenę pojazdu, naciśnij klawisz ~h~~k~~VEHICLE_HORN~ ~w~. + +[AS3_C] +~w~Eeej! Co to za lepkie żółte świństwo? + +[AS3_C1] +~w~O, cześć, kochanie. + +[AS3_F] +~w~Ta dziewczyna po prostu ma talent. + +[AS3_F1] +~w~Udało jej się wydobyć z naszego gościa ten oto klejnocik. + +[AS3_G] +~w~Za 2 godziny na Lotnisko Francisa przyleci pewien samolot. + +[AS3_G1] +~w~Jest on wyładowany trucizną Cataliny. + +[AS3_H] +~w~Możesz ominąć lotniskowe służby bezpieczeństwa, jeżeli popłyniesz łodzią + +[AS3_H1] +do boi wyznaczających lądowisko i zestrzelisz lądujący samolot. + +[AS3_I] +~w~Zabierz z wraku ładunek! + +[AS3_J] +~w~Tylko bądź ostrożny, dobrze, kochanie? + +[AS3_K] +~w~A teraz spróbujemy olejku chilli... + +[RM2_F1] +Kolumbijczycy będą tutaj lada chwila! + +[RM2_K] +Cholera, już tu są! OGNIA! + +[LOVE2_7] +~g~Teraz pozbądź się samochodu!v + +[LOVE2_8] +~g~Uciekaj z Newport! + +[AM1_F] +Za około trzy godziny (~1~:~1~), Salvatore Leone będzie wychodził z klubu 'U Luigiego'. + +[LOVE5_C] +Masz za nim jechać i pilnować, aby zarówno on, jak i mój pakunek dotarli do Pike Creek bez szwanku. + +[FESZ_SR] +Zapisywanie zakończone niepowodzeniem! Sprawdź kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 i ponów próbę. + +[FESZ_FO] +Czy chcesz sformatować kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1? + +[FELZ_FO] +Karta pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 nie jest sformatowana. + +[FES_NOC] +Brak karty pamięci (PS2) w gnieździe KART PAMIĘCI nr 1. + +[FES_LOE] +Wczytywanie zakończone niepowodzeniem! Sprawdź kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 i ponów próbę. + +[FES_DEE] +Kasowanie zakończone niepowodzeniem! Sprawdź kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 i ponów próbę. + +[SLONFM] +Błąd podczas formatowania karty pamięci (PS2) w gnieździe KART PAMIĘCI nr 1. + +[SLONDR] +Niewystarczająca ilość miejsca, aby zapisać stan gry. Proszę włożyć kartę pamięci (PS2) zawierającą co najmniej 500KB wolnego miejsca do gniazda KART PAMIĘCI nr 1. + +[SLNSP] +Niewystarczająca ilość miejsca, aby zapisać stan gry. Proszę włożyć kartę pamięci (PS2) zawierającą co najmniej 200KB wolnego miejsca do gniazda KART PAMIĘCI nr 1. + +[FEFD_WR] +Trwa formatowanie karty pamięci (PS2) w gnieździe KART PAMIĘCI nr 1. Proszę nie wyjmować karty pamięci (PS2), nie resetować ani nie wyłączać konsoli + +[FES_ISF] +NIEOBECNY + +[FES_SAG] +OBECNY + +[SLONNO] +No Memory Card (PS2) in MEMORY CARD slot 1. + +[SLONNF] +Brak karty pamięci (PS2) w gnieździe KART PAMIĘCI nr 1. + +[FESZ_FM] +Karta pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 nie jest sformatowana. Czy chcesz sformatować kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1? + +[FESZ_FF] +Formatowanie zakończone niepowodzeniem! Sprawdź kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 i ponów próbę. + +[MCDNSP] +Na karcie pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 jest zbyt mało wolnego miejsca. Aby zapisać dane aplikacji wymagane jest co najmniej 500KB. Czy chcesz kontynuować? (TAK lub NIE) + +[MCGNSP] +Na karcie pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 jest zbyt mało wolnego miejsca. Aby zapisać dane aplikacji wymagane jest co najmniej 200KB. Czy chcesz kontynuować? (TAK lub NIE) + +[FESZ_WR] +Trwa zapisywanie danych. Proszę nie wyjmować karty pamięci (PS2) z gniazda KART PAMIĘCI nr 1, nie resetować ani nie wyłączać konsoli. + +[FESZ_OW] +Trwa nadpisywanie danych. Proszę nie wyjmować karty pamięci (PS2) z gniazda KART PAMIĘCI nr 1, nie resetować ani nie wyłączać konsoli. + +[FELD_WR] +Trwa wczytywanie danych. Proszę nie wyjmować karty pamięci (PS2), nie resetować ani nie wyłączać konsoli. + +[FEDL_WR] +Trwa usuwanie danych. Proszę nie wyjmować karty pamięci (PS2) z gniazda KART PAMIĘCI nr 1, nie resetować ani nie wyłączać konsoli. + +[LM2_C] +Luigi kazał ci to przekazać, więc... + +[LM3_G] +Joey nie lubi czekać. Pamiętaj, to może być twoja szansa na karierę... + +[LM5_E] +Zawieź tam jak najwięcej dziewczyn, zanim gliniarze przepiją wszystkie pieniądze! + +[JM5_C] +Jest taka kwestia: przy kawiarni w Callahan Point stoi samochód z truposzem w środku. + +[RM2_B] +Powąchaliśmy razem prochu w Nikaragui, kiedy w tym kraju jeszcze rządzili ludzie, którzy wiedzieli, o co chodzi. + +[RM2_C] +Wczoraj groziły mu jakieś mendy z Kartelu. Powiedzieli, że wrócą i zabiorą mu towar. + +[RM2_D1] +Poszedłbym sam, ale moje korzonki znowu się odzywają - więc, ee... Powodzenia! + +[CATINF1] +~g~Dorwij Catalinę! + +[CATINF2] +~g~Śledź helikopter, aby odszukać Catalinę. + +[BOATIN1] +Wskakuj do łodzi i wciśnij klawisz ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~, aby zająć miejsce. + +[BOATIN2] +Jeżeli jesteś w pobliżu łodzi, możesz wcisnąć klawisz ~h~~k~~VEHICLE_ENTER_EXIT~~w~, aby zająć w niej miejsce. + +[BOATIN3] +Wskakuj do łodzi i wciśnij klawisz ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~, aby zająć miejsce. + +[BOATIN4] +Jeżeli jesteś w pobliżu łodzi, możesz wcisnąć klawisz ~h~~k~~VEHICLE_ENTER_EXIT~~w~, aby zająć w niej miejsce. + +[JM6] +'UCIECZKA' + +[FM1] +'NIAŃKA' + +[JM1] +'OSTATNI OBIAD MIKE'A BUŹKI' + +[FM21] +'CIOS W SERCE: AKT I' + +[FM3] +'CIOS W SERCE: AKT II' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'POD OBSERWACJĄ' + +[KM2] +'GRAND THEFT AUTO' + +[AS3] +'WYRZUTNIA ZIEMIA-POWIETRZE' + +[RM2] +'TOWARZYSZE BRONI' + +[LOVE6] +'ZMYŁKA' + +[LOVE1] +'WYZWOLICIEL' + +[RC1] +'DIABELSKA DEMOLKA' + +[RC2] +'MAFIJNA MASAKRA' + +[RC3] +'KRWAWE KASYNO' + +[RC4] +'RUMPO-ROZRÓBA' + +[RM2_E1] +Nie mogę uwierzyć, że te żółte pokraki znowu zostawiły mnie bez ochrony. + +[GREN_1] +Im dłużej przytrzymasz klawisz ~h~~k~~PED_FIREWEAPON~~w~, tym dalej rzucisz granatem. + +[GREN_2] +Im dłużej przytrzymasz klawisz ~h~~k~~PED_FIREWEAPON~~w~, tym dalej rzucisz granatem. + +[GREN_3] +Im dłużej przytrzymasz klawisz ~h~~k~~PED_FIREWEAPON~~w~, tym dalej rzucisz granatem. + +[LOVE4_G] +Rzecz, która do mnie należy, będzie czekać na ciebie w hangarze celnym przy kadłubie samolotu. + +[KABOOM] +BUM! + +[SPLAT] +PLASK! + +[PANCAK] +UPIECZONY! + +[SOAKED] +DO SUCHEJ NITKI! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Gaduła FM + +[K_JAH] +Radio K-Jah + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +Kiedy metro zostanie otwarte, będziesz mógł przejechać pociągiem na Wyspę Staunton. + +[TUBE2] +Kiedy Shoreside Vale zostanie otwarte, będziesz mógł dotrzeć do stacji końcowej Shoreside Terminal i na Lotnisko Międzynarodowe im. Francisa. + +[TUBE_2] +Aby wsiąść do metra, naciśnij klawisz ~h~'wsiadania do pojazdu'~w~. + +[LEGAL] +~g~Wyeliminuj element przestępczy! + +[GA_2] +Nowy silnik i lakier. Masz spokój z gliniarzami! + +[LM1_8A] +Jeżeli chcesz trochę sobie dorobić, możesz spróbować 'pożyczyć' taksówkę... + +[TAXIH1] +Zatrzymaj się w pobliżu podświetlonego przechodnia i pozwól mu wsiąść, a potem zawieź go pod wskazany adres przed upływem wyznaczonego czasu. + +[LM5_7] +~g~Jeżeli na ~p~Balu Policjanta~g~ pojawią się mniej niż cztery dziewczyny, Luigi będzie niezadowolony! + +[KM2_3] +~g~Pamiętaj, ~r~samochody~g~ muszą być w idealnym stanie albo nie zostaną przyjęte w ~p~warsztacie~g~. + +[KM5_2] +~g~Yardies zniknęli z ulic. + +[BETRA_A] +Przykro mi, kotku. + +[BETRA_B] +Jestem ambitną dziewczyną, + +[BETRA_C] +a ty jesteś tylko małą płotką. + +[JAILB_C] +* + +[JAILB_D] +* + +[JAILB_E] +* + +[JAILB_F] +* + +[JAILB_G] +* + +[JAILB_H] +* + +[JAILB_I] +* + +[JAILB_J] +* + +[JAILB_P] +* + +[JAILB_Q] +Jazda! + +[JAILB_R] +Panie frajerze! + +[JAILB_S] +Zabiję cię bez najmniejszego problemu. + +[JAILB_T] +Pożałujesz tego. + +[JAILB_U] +Dobrze, dobrze! Znikaj stąd. + +[HELP15] +Jeżeli jesteś poza pojazdem i chcesz ~h~spojrzeć za siebie~w~, naciśnij klawisz ~h~~k~~PED_LOOKBEHIND~~w~. + +[FEC_LB3] +Spójrz do tyłu + +[FEC_R3] +(klawisz R3) + +[FES_AFO] +Ta karta pamięci (PS2) jest już sformatowana. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- ZMIANA WYBORU + +[FEDSAS4] +;=<> - ZMIANA WYBORU + +[SPRAY_4] { re3 change } +Użyj klawisza ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby strzelać z armatki wodnej. + +[SPRAY_1] { re3 change } +Użyj klawisza ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby strzelać z armatki wodnej. + +[LITTLE] +MAŁY T + +[NICK] +NICK LOVE + +[AM1_10] +~g~Salvatore opuści knajpę 'U Luigiego' około 0~1~:~1~ + +[JAILB_V] +* + +[JAILB_A] +* + +[JAILB_B] +* + +[JAILB_W] +* + +[JAILB_K] +* + +[JAILB_L] +* + +[JAILB_M] +* + +[JAILB_N] +* + +[JAILB_O] +* + +[JAILB_X] +* + +[FEDS_SE] +klawisz / - WYBIERZ + +[FEDS_SB] +klawisz / - WYBIERZ klawisz ' - WSTECZ + +[TM4_A] +~w~Ach, to ty. TONIEGO nie ma. + +[TM4_A2] +~w~Ale zostawił na stole jeden ze swoich słodziutkich listów miłosnych do ciebie. + +[DIAB2_A] +Kiedy zaczynałem robić interesy w branży rozrywek egzotycznych, nie miałem nic oprócz pokaźnej zawartości mojego rozporka! + +[LM5_9] +DZIEWCZYNY: + +[PERPIC] +Znalezione ukryte paczki + +[CO_ONE] +Ukryta Paczka ~1~ z ~1~ + +[LOVE3_3] +~g~Samolot zrzucił ~1~ z 6 pakunków. + +[FARE11] +~g~Cel podróży ~w~'Teren budowy'~g~ w Fort Staunton. + +[GA_21] +W tym garażu nie możesz przechowywać więcej samochodów. + +[CHEAT1] +Ułatwienie uaktywnione + +[CHEAT2] +Ułatwienie - broń + +[CHEAT3] +Ułatwienie - życie + +[CHEAT4] +Ułatwienie - pancerz + +[CHEAT5] +Ułatwienie - poziom wanted + +[CHEAT6] +Ułatwienie - pieniądze + +[CHEAT7] +Ułatwienie - pogoda + +[AS1_H] +~w~Nie udało ci się wprowadzić Szwadronu Śmierci w pułapkę Yakuzy!! + +[FEDS_BA] +klawisz ' - WSTECZ + +[RAMP_A] +WSZYSTKIE ROZWAŁKI ZALICZONE! + +[USJ_ALL] +WSZYSTKIE NIETYPOWE SKOKI WYKONANE! + +[FARE23] +~g~Cel podróży ~w~'warsztat importowo-eksportowy'~g~ w okolicach Tamy Cochrane. + +[L_TRN_1] +Po Portland możesz się też poruszać kolejką. Naciśnij klawisz ~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pociągu. + +[L_TRN_2] +Po Portland możesz się też poruszać kolejką. Naciśnij klawisz ~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pociągu. + +[S_TRN_1] +Przez Liberty możesz także podróżować metrem. Naciśnij klawisz~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pociągu. + +[S_TRN_2] +Przez Liberty możesz także podróżować metrem. Naciśnij klawisz~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pociągu. + +[AS1_C] +~w~Rozmieściła w całym Liberty trzy szwadrony śmierci, których zadaniem jest wytropić twój tyłek. + +[AS1_G] +~w~Wszyscy członkowie Yakuzy nie żyją!! + +[JAN] +stycz. + +[FEB] +luty + +[MAR] +marzec + +[APR] +kwiec. + +[MAY] +maj + +[JUN] +czerw. + +[JUL] +lip. + +[AUG] +sierp. + +[SEP] +wrz. + +[OCT] +paźdz. + +[NOV] +listop. + +[DEC] +grudz. + +[DEFDT] +--:---:---- --:--:-- + +[BUGGY] +POZOSTAŁE GARBUSKI: + +[BONUS] +~g~PREMIA $~1~v + +[HORN1] +Naciśnij ~h~klawisz L3 ~w~, aby użyć ~h~klaksonu. + +[HORN2] +Naciśnij ~h~klawisz L1 ~w~, aby użyć ~h~klaksonu. + +[HORN3] +Naciśnij ~h~klawisz R1 ~w~, aby użyć ~h~klaksonu. + +[LM3_1A] +Naciśnij klawisz~h~ ~k~~VEHICLE_HORN~~w~, aby użyć ~h~klaksonu~w~ i zaprosić Misty do środka. + +[LM3_1B] +Naciśnij klawisz~h~ ~k~~VEHICLE_HORN~~w~, aby użyć ~h~klaksonu~w~ i zaprosić Misty do środka. + +[LM3_1C] +Naciśnij klawisz~h~ ~k~~VEHICLE_HORN~~w~, aby użyć ~h~klaksonu~w~ i zaprosić Misty do środka. + +[RADIO_A] +Naciśnij klawisz ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~, aby przełączać ~h~stacje radiowe. + +[RADIO_B] +Naciśnij klawisz ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~, aby przełączać ~h~stacje radiowe. + +[RADIO_C] +Naciśnij klawisz ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~, aby przełączać ~h~stacje radiowe.v + +[RADIO_D] +Naciśnij klawisz ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~, aby przełączać ~h~stacje radiowe. + +[FEC_EXV] +Wsiadanie i wysiadanie z pojazdu + +[TAXI_M] +'TAKSÓWKARZ' + +[COP_M] +'PATROL' + +[FIRE_M] +'STRAŻAK' + +[AMBUL_M] +'SANITARIUSZ' + +[HJ_IS] +PREMIA ZA SZALONY SKOK: $~1~ + +[HJ_PIS] +PREMIA ZA BEZBŁĘDNY SZALONY SKOK: $~1~ + +[HJ_DIS] +PREMIA ZA PODWÓJNY SZALONY SKOK: $~1~ + +[HJ_PDIS] +PREMIA ZA BEZBŁĘDNY PODWÓJNY SZALONY SKOK: $~1~ + +[HJ_TIS] +PREMIA ZA POTRÓJNY SZALONY SKOK: $~1~ + +[HJ_PTIS] +PREMIA ZA BEZBŁĘDNY POTRÓJNY SZALONY SKOK: $~1~ + +[HJ_QIS] +PREMIA ZA POCZWÓRNY SZALONY SKOK: $~1~ + +[HJ_PQIS] +PREMIA ZA BEZBŁĘDNY POCZWÓRNY SZALONY SKOK: $~1~ + +[AM1_K] +Za około trzy godziny (0~1~:~1~), Salvatore Leone będzie wychodził z klubu 'U Luigiego'. + +[IMPEXPP] +Warsztat import-eksport w Portland Harbor. Mamy zamówienia na różne pojazdy. Listę aktualnie skupowanych wozów znajdziesz na naszej tablicy ogłoszeniowej. + +[VANHSTP] +Chcesz rozbić konwojowóz? Przywieź go do naszego warsztatu w Portland Harbor! + +[EMVHPUP] +Doskonałe ceny za nowe i używane pojazdy służb miejskich! Skup odbywa się przy dźwigu na północny wschód od Portland Harbor. + +[STANDS] +ROZBITE BUDKI: + +[STASH] +~g~Odwieź PROCHY z powrotem na ~p~teren budowy! + +[MCSTNS] +W gnieździe KART PAMIĘCI nr 1 nie ma karty pamięci (PS2). Czy chcesz kontynuować? (TAK lub NIE) + +[LOVE3_5] +~g~Samolot jest w zasięgu. + +[LOVE3_6] +~r~Policja dotarła do pakunków przed tobą! + +[SIREN_1] +Aby włączyc syrenę pojazdu, naciśnij klawisz ~h~~k~~VEHICLE_HORN~ ~w~. + +[SIREN_2] +Aby włączyc syrenę pojazdu, naciśnij klawisz ~h~~k~~VEHICLE_HORN~ ~w~. + +[FM3_8C] +~w~Potrzebuję jakieś 100 000 dolarów na niezbędne wydatki, + +[MCLOAD] +Trwa wczytywanie danych. Proszę nie wyjmować karty pamięci (PS2) z gniazda KART PAMIĘCI nr 1, nie resetować ani nie wyłączać konsoli. + +[FES_GME] +Błąd odczytu danych z karty pamięci (PS2) w gnieździe KART PAMIĘCI nr 1. Sprawdź kartę i ponów próbę. + +[FESZ_QF] +Czy na pewno chcesz sformatować kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1? + +[FESZ_LS] +Pomyślne wczytywanie + +[RM3_5] +~g~Masz już ~1~ z 6 pakietów dowodów. + +[LOVE3_2] +~g~Masz już wszystkie pakunki! Zawieź je do Donalda Love'a. + +[LOVE4_4] +~g~Zabierz pakunek do Donalda Love'a! + +[FEB_SAV] +Wczytanie + +[FEP_SAV] +WCZYTAJ GRĘ + +[AS2_12A] +~g~Od momentu rozwalenia pierwszej budki będziesz miał 8 minut, zanim Kartel ostrzeże swoich dilerów! + +[AS3_1A] +~g~Teraz płyń do ~b~boi ! + +[NOCONT] +Aby kontynuować, proszę ponownie podłączyć kontroler analogowy (DUALSHOCK@) lub kontroler analogowy (DUALSHOCK@2) do portu kontrolerów gry nr 1. + +[BET_JB] +CATALINA, JEGO KOCHANKA, ZDRADZIŁA GO I POZOSTAWIŁA NA PEWNĄ ŚMIERĆ. TERAZ, OSĄDZONY I SKAZANY, ROZPOCZYNA PODRÓŻ DO ZAKŁADU KARNEGO W LIBERTY CITY. ALE W JEGO ZBRODNICZYM UMYŚLE PŁONIE TYLKO JEDNA MYŚL......ZEMSTA! + +[END_A] +Mieszkańcy Cedar Grove nadal dochodzą do siebie + +[END_B] +po gwałtownych wydarzeniach, jakie rozegrały się wczoraj + +[END_C] +w tej dzielnicy, a przypominały regularną wojnę. + +[END_D] +Pan Clive Denver, mieszkający w tej okolicy od lat, opisał wczoraj policji + +[END_E] +samotnego strzelca, który uciekał z miejsca przestępstwa z ciemnowłosą kobietą. + +[END_F] +Och, będziemy się razem świetnie bawić! Bo... no, wiesz... + +[END_G] +Ja cię kocham, naprawdę... Bo jesteś taki silny i w ogóle... + +[END_H] +A właśnie kogoś takiego szukałam. + +[END_I] +Ale... o czym to ja mówiłam? + +[END_J] +Wyleciało mi z głowy! Ale wiesz, o co mi chodzi, prawda? + +[END_K] +Odgłosy eksplozji wstrząsały okolicznymi budynkami, z których wybiegali przerażeni ludzie. + +[END_L] +Kilkunastu obywateli doznało obrażań w wyniku wymiany ognia + +[END_M] +między bandytami na ziemi a helikopterem krążącym nad tamą. + +[END_N] +Tak, tutaj - z ogrodu - miałem na wszystko doskonały widok. + +[END_O] +Kiedy helikopter w końcu został trafiony, + +[END_P] +wyglądało to lepiej niż fajerwerki na Wielkanoc! + +[END_Q] +Liczba zabitych przekroczyła już dwadzieścia osób, + +[END_R] +ale policja ciągle odnajduje nowe ciała. + +[END_S] +Jak dotąd nie wydano żadnego oficjalnego komunikatu, który zaprzeczyłby pogłoskom, + +[END_T] +że ofiary to członkowie Kartelu Kolumbijskiego. + +[END_U] +Nadal nie ma też żadnych informacji na temat przyczyn tej masakry. + +[END_V] +Złamałam paznokieć i zrujnowałam sobie całą fryzurę. To skandal! + +[END_W] +Kosztowała mnie pięćdziesiąt dolców... + +[PAPER1] +* + +[PAPER2] +* + +[PAPER3] +* + +[FEB_CPC] +Konfiguracja klawiszy sterujących + +[FEC_PED] +Sterowanie postacią poza pojazdem + +[FEC_VEH] +Klawisze sterujące pojazdem + +[FEC_FPR] +Sterowanie dla trybu widoku z oczu postaci + +[FEC_CMM] +Standardowe sterowanie + +[FEC_PWL] +IDŹ w lewo + +[FEC_PWR] +Idź w prawo + +[FEC_PWF] +Idź do przodu + +[FEC_PWT] +Idź w stronę kamery + +[FEC_PLB] +Spójrz za siebie + +[FEC_PFR] +Strzał z broni + +[FEC_CLE] +Przełącz rodzaj broni w lewo + +[FEC_CRI] +Przełącz rodzaj broni w prawo + +[FEC_LKT] +Zablokuj cel + +[FEC_PJP] +Skok + +[FEC_PSP] +Szybki bieg + +[FEC_PSH] +Strzał + +[FEC_TLF] +Następny cel w lewo + +[FEC_TRG] +Następny cel w prawo + +[FEC_CCM] +Wyśrodkuj kamerę za graczem + +[FEC_SZI] +Karabin snajperski - przybliżenie + +[FEC_SZO] +Karabin snajperski - oddalenie + +[FEC_LKL] +Spójrz w lewo w trybie widoku z oczu postaci + +[FEC_LRT] +Spójrz w prawo w trybie widoku z oczu postaci + +[FEC_LUP] +Spójrz w górę w trybie widoku z oczu postaci + +[FEC_LDN] +Spójrz w dół w trybie widoku z oczu postaci + +[FEC_LBH] +Spójrz przez tylną szybę + +[FEC_LLF] +Spójrz przez lewą szybę + +[FEC_LRG] +Spójrz przez prawą szybę + +[FEC_HRN] +Klakson + +[FEC_HBR] +Hamulec ręczny pojazdu + +[FEC_ACL] +Przyspieszenie pojazdu + +[FEC_BRK] +Hamulec pojazdu + +[FEC_TSM] +Włącz podmisje + +[FEC_CRD] +Zmiana stacji radiowej + +[FEC_ENT] +Wsiadanie do pojazdu/wysiadanie z pojazdu + +[FEC_WPN] +Strzał z broni + +[FEC_PAS] +Pauza + +[FEC_FPO] +Broń w trybie widoku z oczu postaci + +[FEC_SMS] +Pokazuj kursor myszy + +[FEC_CMS] +Zmiana trybu kamery dla wszystkich sytuacji. + +[FEC_TSS] +Zapisz wygląd ekranu + +[FEN_NET] +Sieć + +[FEN_CON] +Połączenie + +[FEN_GAM] +Szukaj sesji gry + +[FEN_TYP] +Rodzaj gry + +[FEN_TY0] +Tryb Deathmatch + +[FEN_TY1] +Tryb Niewidzialny Deathmatch + +[FEN_TY2] +Zespołowy tryb Deathmatch + +[FEN_TY3] +Zespołowy tryb Niewidzialny Deathmatch + +[FEN_TY4] +Gromadź szmal + +[FEN_TY5] +Zdobądź flagę + +[FEN_TY6] +Wyścig szczurów + +[FEN_TY7] +Dominacja + +[FEN_NAM] +Nazwa: + +[FEN_GNA] +Nazwa gry: + +[FEM_MAP] +Wybierz mapę + +[FEN_PLS] +Ustawienia gracza + +[FEN_PLC] +Kolor gracza + +[FEM_MA0] +Liberty City + +[FEM_MA1] +Dz. Czerwonych Świateł + +[FEM_MA2] +Chinatown + +[FEM_MA3] +Wieża + +[FEM_MA4] +Kanały + +[FEM_MA5] +Park Przemysłowy + +[FEM_MA6] +Doki + +[FEM_MA7] +Staunton + +[FEC_EMS] +Tylko klawisze z klawiatur niestandardowych + +[FEC_DBG] +MENU DEBUGOWANIA + +[FEC_TGD] +Przełącznik gra/debugowanie + +[FEC_TDO] +Wyłącz kamerę trybu debugowania + +[FEC_IVH] +Odwróć osie myszy w poziomie: + +[FEC_MSL] +Lewy przycisk myszy + +[FEC_MSM] +Śr. przycisk myszy + +[FEC_MSR] +Prawy przycisk myszy + +[FEC_QUE] +??? + +[FEC_TWO] +Dozwolone są tylko dwa klawisze z klawiatury + +[FEC_UMS] +Tylko przyciski myszy niestandardowych + +[FEC_OMS] +Dozwolony jest tylko jeden przycisk myszy + +[FEC_UJS] +Tylko przyciski joysticków niestandardowych + +[FEC_OJS] +Dozwolony jest tylko jeden przycisk joysticka na daną czynność . + +[FEC_PTL] +Użyj blokowania celu oraz przełączenia broni w lewo + +[FEC_PTR] +Użyj blokowania celu oraz przełączenia broni w prawo + +[FEC_LBC] +Użyj klawiszy 'spójrz w lewo' oraz 'spójrz w prawo' + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +Nie można zatrzymać gry w trybie wieloosobowym. Dwukrotnie naciśnij klawisz, aby wyjść z gry! + +[FEM_SL1] +Gniazdo 1 jest wolne + +[FEM_SL2] +Gniazdo 2 jest wolne + +[FEM_SL3] +Gniazdo 3 jest wolne + +[FEM_SL4] +Gniazdo 4 jest wolne + +[FEM_SL5] +Gniazdo 5 jest wolne + +[FEM_SL6] +Gniazdo 6 jest wolne + +[FEM_SL7] +Gniazdo 7 jest wolne + +[FEM_SL8] +Gniazdo 8 jest wolne + +[FEM_MM] +MENU GŁÓWNE + +[FEQ_SRE] +Czy na pewno chcesz wyjść z gry? Efekty wszystkich działań podjętych od czasu ostatniego zapisu gry zostaną utracone. Kontynuować? + +[FEQ_SRW] +Czy na pewno chcesz wyjść z gry? + +[FEG_SRV] +SERWER + +[FEG_MAP] +MAPA + +[FEG_PLY] +GRACZE + +[FEG_TYP] +RODZAJ + +[FEG_PNG] +PING + +[FET_FG] +ODSZUKAJ SESJĘ GRY + +[FET_SP] +TRYB DLA JEDNEGO GRACZA + +[FET_MP] +TRYB WIELOOSOBOWY + +[FET_HG] +HOSTUJ GRĘ + +[FET_PS] +KONFIGURACJA GRACZA + +[FET_CON] +POŁĄCZENIE + +[FET_AUD] +KONFIGURACJA DŻWIĘKU + +[FET_DIS] +KONFIGURACJA EKRANU + +[FET_LAN] +OKREŚL JĘZYK + +[FET_LG] +WCZYTAJ GRĘ + +[FET_DG] +USUŃ GRĘ + +[FET_NG] +NOWA GRA + +[FET_SG] +ZAPISZ GRĘ + +[FET_MAP] +WYBIERZ MAPĘ + +[FET_GT] +RODZAJ GRY + +[FET_CTL] +KONFIGURACJA STEROWANIA + +[FET_OPT] +OPCJE + +[FET_QG] +WYJDŹ Z GRY + +[FET_STA] +STATYSTYKI + +[FET_BRE] +CELE + +[FEC_WAR] +Ostrzeżenie + +[FEC_OKK] +OK + +[FED_CON] +Potwierdzenie usunięcia pliku + +[FES_SSC] +Gra została pomyślnie zapisana. + +[DEL_FNM] +Plik został pomyślnie usunięty. + +[PCLOAD] +Wczytywanie danych z pliku + +[PCRESRT] +Trwa uruchamianie Grand Theft Auto III + +[FEC_DLF] +Kasowanie nieudane. + +[FEC_SVU] +Zapis nieudany. + +[FEC_LUN] +Wczytywanie nieudane. Plik uszkodzony. Proszę go usunąć. + +[FEN_PLA] +Liczba graczy: + +[FET_NON] +BRAK DOSTĘPNYCH GIER + +[FET_SFG] +WYSZUKIWANIE SESJI GRY... + +[FET_SRT] +SORTOWANIE SESJI GRY... + +[FEF_LAN] +LAN + +[FEF_INT] +INTERNET + +[FET_REF] +Odśwież + +[FET_FIL] +Filtr + +[FET_JG] +Dołącz + +[FEC_NTW] +Rozmowa przez sieć + +[FEC_ESR] +Klawisz ESC jest zastrzeżony + +[FEC_GSL] +Pokazuj wstrząsy głowy: + +[FIL_FLT] +FILTR LISTY SESJI GRY + +[FET_SAN] +ROZPOCZNIJ NOWĄ GRĘ + +[FIL_MAP] +Mapa: + +[FIL_SRV] +Serwer: + +[FIL_TYP] +Rodzaj gry: + +[FIL_SPC] +Gry z wolnym miejscem? + +[FIL_PNG] +Ping: + +[FEN_UKH] +Nieznany host + +[FEN_UKM] +Nie odnaleziono mapy + +[FEN_UKT] +Nie odnaleziono danego rodzaju gry + +[FEN_NCI] +BRAK POŁĄCZENIA Z INTERNETEM + +[FET_PAU] +MENU PAUZY + +[FET_SGA] +ROZPOCZNIJ GRĘ + +[FEC_SGJ] +Ustaw joystick do gry + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FEC_WHL] +Kierownica + +[FEC_CNT] +Typ sterownika: + +[FES_CSA] +Wybierz 'skórę' z poniższej listy: + +[FES_SKN] +NAZWA SKÓRY + +[FES_DAT] +DATA + +[FES_NON] +BRAK DOSTĘPNYCH 'SKÓR' + +[FEA_FM9] +ODTWARZACZ MP3 + +[FESZ_QZ] +Czy jesteś pewien, że chcesz zapisać grę? + +[FES_CGA] +Dostępne gniazda gry: + +[FES_SCG] +Zapisać bieżącą grę? + +[FES_LCG] +Wczytać grę i kontynuować rozgrywkę? + +[FEC_FIR] +Strzał + +[FEC_NWE] +Następna broń + +[FEC_PWE] +Poprzednia broń + +[FEC_FOR] +Do przodu + +[FEC_BAC] +Do tyłu + +[FEC_LEF] +W lewo + +[FEC_RIG] +W prawo + +[FEC_ZIN] +Przybliżenie + +[FEC_ZOT] +Oddalenie + +[FEC_EEX] +Wejście/wyjście + +[FEC_RAD] +Radio + +[FEC_SUB] +Podmisja + +[FEC_CMR] +Zmiana kamery + +[FEC_JMP] +Skok + +[FEC_SPN] +Sprint + +[FEC_HND] +Hamulec ręczny + +[FEC_TUL] +Wieżyczka w lewo + +[FEC_TUR] +Wieżyczka w prawo + +[FEC_LOL] +Spójrz w lewo + +[FEC_LOR] +Spójrz w prawo + +[FEC_NTR] +Następny cel + +[FEC_PTT] +Poprzedni cel + +[FEC_LBA] +Spójrz do tyłu + +[FEC_CEN] +Wyśrodkuj kamerę + +[FEC_UND] +(NIE) + +[FET_CFT] +PIESZO + +[FET_CCR] +W AUCIE + +[CVT_MSG] +Trwa konwersja tekstur do formatu optymalnego dla zainstalowanej karty graficznej. + +[FET_CAC] +CZYNNOŚĆ + +[FEC_IBT] +- + +[FEC_SPC] +SPACJA + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +UNBOUND + +[FET_CME] +SPOSÓB STEROWANIA + +[FET_RDK] +ZMIANA KLAWISZY STERUJĄCYCH + +[FET_AMS] +USTAWIENIA MYSZY + +[FET_STI] +ZWYKŁA KONFIGURACJA STEROWANIA + +[FET_CTI] +STANDARDOWA KONFIGURACJA STEROWANIA + +[FET_MTI] +KONFIGURACJA STEROWANIA MYSZĄ + +[FET_DAM] +DYNAMICZNE MODELOWANIE AKUSTYCZNE + +[FEC_TFL] +Wieżyczka w lewo + +[FEC_TFR] +Wieżyczka w prawo + +[FEC_MWF] +KÓŁKO W GÓRĘ + +[FEC_MWB] +KÓŁKO W DÓŁ + +[FEC_ORR] +lub + +[FEC_NUS] +NIEUŻYWANY + +[FEC_LUD] +Spójrz w górę + +[FEC_LDU] +Spójrz w dół + +[FEC_CMP] +KOMBINACJA: SPÓJRZ W LEWO + SPÓJRZ W PRAWO + +[FEC_NTT] +Brak tekstu dla tego klawisza + +[FEC_FNC] +F~1~ + +[FEC_IRT] +INSERT + +[FEC_DLL] +DELETE + +[FEC_HME] +HOME + +[FEC_END] +END + +[FEC_PGU] +PAGE UP + +[FEC_PGD] +PAGE DOWN + +[FEC_UPA] +STRZAŁKA W GÓRĘ + +[FEC_DWA] +STRZAŁKA W DÓŁ + +[FEC_LFA] +W LEWO + +[FEC_RFA] +STRZAŁKA W PRAWO + +[FEC_NUM] +NUM + +[FEC_NMN] +NUM~1~ + +[FEC_FWS] +NUM / + +[FEC_PLS] +NUM + + +[FEC_MIN] +NUM - + +[FEC_DOT] +NUM . + +[FEC_NLK] +NUM LOCK + +[FEC_ETR] +ENTER + +[FEC_SLK] +SCROLL LOCK + +[FEC_PSB] +PAUSE BREAK + +[FEC_BSP] +BACKSPACE + +[FEC_TAB] +TAB + +[FEC_CLK] +CAPS LOCK + +[FEC_RTN] +RETURN + +[FEC_LSF] +LEWY SHIFT + +[FEC_RSF] +PRAWY SHIFT + +[FEC_LCT] +LEWY CTRL + +[FEC_RCT] +PRAWY CTRL + +[FEC_LAL] +LEWY ALT + +[FEC_RAL] +PRAWY ALT + +[FEC_LWD] +LEWY KLAWISZ WINDOWS + +[FEC_RWD] +PRAWY KLAWISZ WINDOWS + +[FEC_WRC] +WINCLICK + +[WIN_TTL] +Grand Theft Auto III + +[WIN_95] +Gra Grand Theft Auto III nie pracuje pod systemem Windows 95. + +[WIN_DX] +Gra Grand Theft Auto III wymaga bibliotek DirectX w wersji 8.1 lub nowszych. + +[WIN_VDM] +Gra Grand Theft Auto III wymaga karty graficznej z przynajmniej 12 MB RAM. + +[DIAB3_G] +Arriba! + +[FEM_RES] +WZNÓW GRĘ + +[FES_SNG] +ROZPOCZNIJ NOWĄ GRĘ + +[FEM_SP] +TRYB DLA JEDNEGO GRACZA + +[FEM_MP] +TRYB WIELOOSOBOWY + +[FEM_QT] +WYJDŹ Z GRY + +[FES_SG] +ROZPOCZNIJ NOWĄ GRĘ + +[FES_LG] +WCZYTAJ GRĘ + +[FEM_HST] +HOST GRY + +[FEM_OPT] +OPCJE + +[FEM_DBG] +DEBUGOWANIE + +[FET_PSU] +KONFIGURACJA GRACZA + +[FET_DEF] +PRZYWRÓĆ USTAWIENIA DOMYŚLNE + +[FED_BRI] +JASNOŚĆ + +[FED_TRA] +ŚLADY + +[FEM_LOD] +ODLEGŁOŚC RYSOWANIA + +[FEM_VSC] +SYNCHRONIZACJA KLATEK + +[FEM_FRM] +OGRANICZENIE KLATEK + +[FED_RES] +ROZDZIELCZOŚĆ EKRANU + +[FED_WIS] +SZEROKI EKRAN + +[FEDS_TB] +WSTECZ + +[FEA_MUS] +GŁOŚNOŚĆ MUZYKI + +[FEA_SFX] +GŁOŚNOŚĆ EFEKTÓW + +[FEA_RSS] +STACJA RADIOWA + +[FEL_ENG] +ANGIELSKI + +[FEL_FRE] +FRANCUSKI + +[FEL_GER] +NIEMIECKI + +[FEL_ITA] +WŁOSKI + +[FEL_SPA] +HISZPAŃSKI + +[FEA_3DH] +SPRZĘT AUDIO + +[FEA_SPK] +KONFIGURACJA GŁOŚNIKÓW + +[FEA_2SP] +DWA GŁOŚNIKI + +[FEA_4SP] +WIĘCEJ NIŻ DWA GŁOŚNIKI + +[FEA_EAR] +SŁUCHAWKI + +[FEA_NAH] +BRAK SPRZĘTU AUDIO + +[FET_SNG] +ROZPOCZNIJ NOWĄ GRĘ + +[FEN_STA] +ROZPOCZNIJ GRĘ + +[GMLOAD] +WCZYTAJ GRĘ + +[GMSAVE] +ZAPISZ GRĘ + +[FES_DGA] +USUŃ GRĘ + +[FEM_NON] +BRAK + +[FEC_IVV] +ODWRÓĆ OSIE MYSZY W PIONIE + +[FEC_MSH] +CZUŁOŚĆ MYSZY + +[FET_CCN] +STEROWANIE: STANDARDOWE + +[FET_SCN] +STEROWANIE: ZWYKŁE + +[FES_SET] +UŻYJ 'SKÓRY' + +[GHOST] +Ghost + +[WIN_RSZ] +Zmiana rozmiaru ekranu nieudana. + +[FEC_TFU] +Wież./dodo w górę + +[FEC_TFD] +Wież./dodo w dół + +[FET_APL] +ZASTOSUJ + +[FET_APP] +KLIKNIJ LPM LUB RETURN, ABY ZASTOSOWAĆ NOWE USTAWIENIA + +[FET_HRD] +PRZYWRÓCONO USTAWIENIA DOMYŚLNE + +[FET_MST] +STEROWANIE POJAZDAMI ZA POMOCĄ MYSZY + +[FEC_STR] +NUM * + +[FET_MIG] +STRZAŁKA W LEWO, W PRAWO, KÓŁKO MYSZY, ABY REGULOWAĆ + +[FET_CIG] +BACKSPACE: USUWANIE - LPM, RETURN - ZMIANA + +[FET_RIG] +WYBIERZ NOWY KLAWISZ LUB NACIŚNIJ ESC, ABY ANULOWAĆ + +[FET_EIG] +NIE MOŻNA PRZYPISAĆ KLAWISZA TEJ CZYNNOŚCI + +[NO_PCCD] +Włóż do napędu CD-ROM płytę z grą Grand Theft Auto III nr 2 albo naciśnij ESC, aby anulować. + +[CVT_ERR] +Na dysku twardym zabrakło wolnego miejsca. Przed dalszą pracą z programem zwolnij odpowiednią ilość pamięci. Aby wyjść, naciśnij ESC. + +[FED_SUB] +NAPISY + +[FET_DSN] +Skin gracza.bmp + +[JM3] +'SKOK NA KONWÓJ' + +[ATUTOR2] +~g~OSTROŻNIE przewoź pacjentów do Szpitala. Każde zderzenie zmniejsza ich szanse przeżycia. + +[EBAL] +'WOLNOŚĆ W LIBERTY' + +[LM4] +'ALFONS' + +[REPLAY] +POWTÓRKA + +[FEC_SFT] +SHIFT + +[CRED254] +KIEROWNIK STUDIA + +[CVT_CRT] +Nie można dokonać konwersji tekstur dla zainstalowanej karty graficznej. W tym celu należy zalogować się na konto Administratora. Aby wyjść, naciśnij ESC. + +[FEM_ON] +WŁ. + +[FEM_OFF] +WYŁ. + +[FEM_YES] +TAK + +[FEM_NO] +NIE + +[FES_WAR] +Trwa zapisywanie, proszę czekać... + +[FED_DLW] +Trwa kasowanie, proszę czekać... + +[FED_LDW] +Trwa wczytywanie, proszę czekać... + +[FEC_SLC] +Gniazdo jest uszkodzone + +[FED_LFL] +Nieudane wczytanie zapisanej gry. Za chwilę gra uruchomi się ponownie. + +[FET_RSO] +PRZYWRÓCONO PIERWOTNE USTAWIENIA + +[FET_RSC] +URZĄDZENIE NIEDOSTĘPNE - PRZYWRÓCONO PIERWOTNE USTAWIENIA + +{ re3 updates } +{ new languages } +[FEL_JAP] +JAPOŃSKI + +[FEL_POL] +POLSKI + +[FEL_RUS] +ROSYJSKI + +{ new display menus } +[FET_GFX] +USTAWIENIA GRAFIKI + +[FED_MIP] +MIPMAPPING + +[FED_AAS] +WYGŁADZANIE KRAWĘDZI + +[FED_FIL] +FILTROWANIE TEKSTUR + +[FED_BIL] +DWULINIOWE + +[FED_TRL] +TRÓJLINIOWE + +[FED_WND] +OKIENKOWY + +[FED_FLS] +PEŁNY EKRAN + +[FEM_CSB] +RAMKI CUTSCENEK + +[FEM_SCF] +FORMAT OBRAZU + +[FEM_ISL] +PRZYPISZ WYKORZYSTANIE PAMIĘCI + +[FEM_LOW] +NISKIE + +[FEM_MED] +ŚREDNIE + +[FEM_HIG] +WYSOKIE + +[FEM_2PR] +ALFA TEST PS2 + +[FEC_FRC] +SWOBODNA KAMERA + +{ Linux joy detection } +[FEC_JOD] +WYKRYJ PADA + +[FEC_JPR] +Naciśnij dowolny klawisz na padzie, którego chcesz użyć w grze. + +[FEC_JDE] +Wykryto pada + +{ mission restart } +[FET_RMS] +PONÓW MISJĘ + +[FESZ_RM] +PONOWIĆ? + +{ more graphics } +[FED_VPL] +POTOK POJAZDÓW + +[FED_PRM] +PODŚWIETLENIE PED + +[FED_RGL] +POŁYSK DROGI + +[FED_CLF] +FILTR KOLORU + +[FED_WLM] +LIGHTMAPY ŚWIATA + +[FED_MBL] +ROZMYCIE RUCHU + +[FEM_SIM] +PROSTE + +[FEM_NRM] +NORMALNY + +[FEM_MOB] +MOBILNY + +[FED_MFX] +MATFX + +[FED_NEO] +PODŚWIETLENIE + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +AUTO + +[FEC_IVP] +ODWRÓĆ OŚ PADA W PIONIE + +{ map } +[FEM_TWP] +Toggle Waypoint + +[FEA_FMN] +RADIO OFF + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +XBOX 360 CONTROLLER + +[FEC_ONE] +XBOX ONE CONTROLLER + +[FEC_NSW] +NINTENDO SWITCH CONTROLLER + +[FEC_TYP] +GAMEPAD TYPE + +[FEC_CCF] +KONFIGURACJA + +[FEC_CF1] +SETUP1 + +[FEC_CF2] +SETUP2 + +[FEC_CF3] +SETUP3 + +[FEC_CF4] +SETUP4 + +[FEC_CDP] +STEROWNIK EKRANU + +[FEC_ONF] +PIESZO + +[FEC_INC] +W AUCIE + +[FEC_VIB] +WIBRACJA + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +{ end of file } + +[DUMMY] + +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/russian.txt b/utils/gxt/russian.txt new file mode 100644 index 0000000..3b42bb2 --- /dev/null +++ b/utils/gxt/russian.txt @@ -0,0 +1,8118 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. +} + +[1000] +ТЫ ПОКОЙНИК + +[1001] +ТЫ ПОКОЙНИК + +[1002] +ТЫ ПОКОЙНИК + +[1003] +ТЫ ПОКОЙНИК + +[1004] +ТЫ ПОКОЙНИК + +[1005] +АРЕСТОВАН + +[1006] +АРЕСТОВАН + +[1007] +АРЕСТОВАН + +[1008] +АРЕСТОВАН + +[1009] +АРЕСТОВАН + +[1010] +~r~Твоя тачка перевернулась + +[1011] +~r~Твоя тачка перевернулась + +[1012] +~r~Твоя тачка перевернулась + +[1013] +~r~Твоя тачка перевернулась + +[1014] +~r~Твоя тачка перевернулась + +[8001] +Тебе крупно не повезло!! + +[ACCURA] +Меткость + +[AEROPL] +Самолет + +[AIRPORT] +Аэропорт Фрэнсис + +[ALEVEL] +Скорая помощь. Задание ~1~ + +[AM1] +'САЙОНАРА, САЛЬВАТОРЕ' + +[AM1_1] +~g~Сейчас Сальваторе выходит из клуба Луиджи! + +[AM1_10] +~g~Сальваторе уйдет от Луиджи в 0~1~:~1~ + +[AM1_2] +~r~Тебя засекли! + +[AM1_3] +~r~Ты упустил Сальваторе! + +[AM1_4] +~r~Кретин, ты его спугнул! Какой ты к черту киллер? + +[AM1_5] +~g~Отправляйся в квартал красных фонарей и жди, когда Сальваторе выйдет из клуба. + +[AM1_6] +~g~Если ты будешь болтаться около клуба Луиджи, то тебя увидит мафия! + +[AM1_7] +~r~Сальваторе дома в безопасности попивает коктейль. Все с тобой ясно, шакал! + +[AM1_8] +~g~Сальваторе уйдет от Луиджи примерно в ~1~:~1~ + +[AM1_9] +~r~Сальваторе скрылся в клубе Луиджи! + +[AM1_A] +Нам нужно кое что прояснить, прежде чем начнем налаживать какие-либо отношения, + +[AM1_B] +деловые или нет. Давай раскроем карты. + +[AM1_C] +Я из Якудзы и знаю, что ты работал на семью Сальваторе Леоне. + +[AM1_D] +Я могу дать тебе работу в нашей организации, + +[AM1_E] +но сначала тебе придется доказать, что ты на самом деле порвал с мафией. + +[AM1_F] +Сальваторе Леоне выйдет от Луиджи примерно через три часа. (~1~:~1~) + +[AM1_G] +Постарайся, чтобы он не добрался до дома живым. + +[AM1_H] +Ну а мы с Марией пока вспомним былые времена. + +[AM1_I] +О, Асука, у тебя новый массажист. + +[AM1_J] +Это не массажист. + +[AM1_K] +Сальваторе Леоне выйдет от Луиджи примерно в три часа. (0~1~:~1~) + +[AM2] +'ПОД НАДЗОРОМ' + +[AM2_4] +~g~Они тебя засекли, ты шел как слепой слон! + +[AM2_A] +Смерть Сальваторе - очень приятная новость, + +[AM2_A2] +ты отличный киллер. Мне нравятся такие мужики. + +[AM2_B] +Это мой брат Кенжи. + +[AM2_C] +У Асуки для тебя есть работа, но как только освободишься - загляни ко мне в казино. + +[AM2_D] +Кенжи как всегда тянет руки к моим игрушкам. + +[AM2_E] +Мои люди в полиции сообщили, что мафия следит за всеми нашими делами в городе, + +[AM2_E2] +так они пытаются выйти на тебя. + +[AM2_F] +Они мешают нам работать. Нужно устранить эту проблему. + +[AM2_G] +Разберись с этими шпионами-самоучками и прекрати вендетту раз и навсегда. + +[AM3] +'ПРОЩАЙ, ПАПАРАЦЦИ' + +[AM3_A] +Тут один журналист что-то вынюхивает. + +[AM3_B] +Мы с Марией немного отдохнем, пока ты не избавишься от этого назойливого писаки. + +[AM3_C] +Пока ты это читаешь, он, вероятно, в гавани! Угони полицейский катер и 'потопи' его карьеру! + +[AM4] +'ДЕНЬГИ ДЛЯ РЭЯ' + +[AM4_10] +Знаешь, я тоже иногда могу подкинуть работенку. + +[AM4_11] +Если что - ты знаешь, где меня найти. + +[AM4_1A] +Доберись до телефона к западу от парка Бельвиль. + +[AM4_1B] +Доберись до телефона в Учебном городке. + +[AM4_1C] +Доберись до телефона к югу от парка Бельвиль. + +[AM4_1D] +Встречаемся в туалете, расположенном в парке. + +[AM4_3] +Ты, должно быть, новый посыльный Асуки! + +[AM4_4] +Ты принес мне деньги? Это все? + +[AM4_5] +Я знаю, ты думаешь - еще один продажный коп. + +[AM4_6] +Да, мы живем в продажном мире. + +[AM4_7] +Мной занялась служба внутреннего расследования только из-за того, что я потерял двух напарников. + +[AM4_8] +Приходится быть начеку. + +[AM4_9] +Да, весь этот город - одна большая помойная яма. + +[AM4_A] +Да это же мой красавчик! + +[AM4_B] +Мария сейчас очень занята, но я ей передам, что ты заходил. + +[AM4_C] +Асука, кто это? Я бы и сама вышла, но я очень хочу писать, окей? + +[AM4_D] +Ты должен встретиться с моим знакомым копом. + +[AM4_E] +Это деньги за его последнюю работу, что он сделал для нас. + +[AM4_F] +Разумеется, он очень осторожен. + +[AM4_G] +Немедленно отправляйся к телефону-автомату в Торрингтоне и жди его инструкций. + +[AM5] +'ДВУЛИКИЙ ТАННЕР' + +[AM5_1] +Таннер у тебя на хвосте! + +[AM5_A] +Мы с Марией пошли по магазинам. + +[AM5_B] +Мой человек в полиции сообщил, что один из наших водил, на самом деле - полицейский под прикрытием! + +[AM5_C] +Он не вылезает из своей тачки, поэтому мы прикрепили к ней маячок. + +[AM5_D] +Пусти ему кровь! + +[AMBULAN] +Скорая + +[AMBUL_M] +'СКОРАЯ ПОМОЩЬ' + +[AMMU] +Чтобы купить оружие - зайди в магазин. + +[AMMU_A] +Луиджи сказал, тебе нужен ствол... + +[AMMU_B] +Джоуи кое что для тебя заказал... + +[AMMU_C] +Иди во двор магазина, я там тебе кое-что припас. + +[AMMU_D] +У меня есть все что тебе понадобится. + +[AMMU_E] +Тебе и лицензия нужна? + +[AMMU_F] +Документов можешь не показывать, я и так тебе верю. + +[APR] +Апр + +[AS1] +'НАЖИВКА' + +[AS1_A] +~w~Мигель считает, что я плохо с ним обращаюсь. + +[AS1_B] +~w~Но он все-таки рассказал, что Каталина жутко боится твоей мести. + +[AS1_C] +~w~Она послала в город три группы убийц, чтобы они выследили и прикончили тебя. + +[AS1_D] +~w~Тебе придется побыть наживкой и постараться заманить их в Пайк Крик, + +[AS1_E] +~w~там их уже будут поджидать мои люди. + +[AS1_G] +~r~Все якудза перебиты! + +[AS1_H] +~r~Тебе не удалось заманить убийц в ловушку Якудзы! + +[AS2] +'КОФЕ НА ВЫНОС!' + +[AS2_1] +~g~В Портланде уничтожены все кофейные ларьки! + +[AS2_10] +~g~Кофейные ларьки еще стоят в Портланде и на острове Стаунтон. + +[AS2_11] +~g~~1~ ИЗ 9! + +[AS2_12] +~g~Объедь все районы города и отыщи ~b~кофейные ларьки~g~! + +[AS2_12A] +~g~После того как ты уничтожишь первый ларек, у тебя будет 8 минут, прежде чем Картель забьет тревогу! + +[AS2_2] +~g~На острове Стаунтон уничтожены все кофейные ларьки! + +[AS2_3] +~g~В Шорсайд Вейл уничтожены все кофейные ларьки! + +[AS2_4] +~r~Картель предупредил продавцов наркоты! + +[AS2_5] +~g~Кофейные ларьки еще стоят в Шорсайд Вейл и на острове Стаунтон! + +[AS2_6] +~g~Кофейные ларьки еще стоят в Шорсайд Вейл! + +[AS2_7] +~g~Кофейные ларьки еще стоят на острове Стаунтон! + +[AS2_8] +~g~Кофейные ларьки еще стоят в Портланде! + +[AS2_9] +~g~Кофейные ларьки еще стоят в Портланде и в Шорсайд Вейл! + +[AS2_A] +~w~Мы недооценили план Каталины по реализации СПАНКа. + +[AS2_A1] +~w~Мигель вынослив, как настоящий латиноамериканец. + +[AS2_A2] +~w~Я уже выдохлась. + +[AS2_B] +~w~Ярди - торгующие СПАНКом по всему городу - это мелкие сошки. + +[AS2_C] +~w~Картель действует через свою компанию 'The Kappa Coffee House'. + +[AS2_D] +~w~Они продают СПАНК через сеть уличных ларьков. + +[AS2_E] +~w~Нам не остается ничего другого, кроме как прикрыть все эти лавочки. + +[AS2_F] +~w~Разнеси их в щепки! + +[AS3] +'БЫЛО ВАШЕ, СТАЛО НАШЕ' + +[AS3_1] +~g~Найди ~r~катер~g~ и встань около ~b~буя~g~! + +[AS3_1A] +~g~Теперь двигайся к ~b~бую~g~! + +[AS3_2] +~b~Двигайся к буям на посадочной полосе! ~y~Самолет уже на подлете! + +[AS3_3] +~g~Подожди, пока не покажется ~y~самолет~g~! + +[AS3_4] +~g~Сбей ~y~самолет~g~ из ракетницы! + +[AS3_5] +~g~Собирай товар! + +[AS3_6] +~g~~1~ ИЗ 8 + +[AS3_A] +~W~Ну что, затянем немного потуже, или просто подождем, пока почернеет и отвалится? + +[AS3_B] +~w~Ну ка, давай посмотрим... + +[AS3_C] +~w~Фууууууууу! что это за желтая липкая гадость? + +[AS3_C1] +~w~Привет малыш. + +[AS3_D] +~w~Мой работничек! + +[AS3_E] +~w~Мне было скучно, и я решила составить Асуке компанию. + +[AS3_F] +~w~Похоже она очень одаренная девочка. + +[AS3_F1] +~w~Ей удалось узнать кое что важное у нашего гостя. + +[AS3_G] +~w~В два часа в аэропорт Фрэнсис Интернешнл прибывает самолет. + +[AS3_G1] +~w~Он просто набит отравой Каталины. + +[AS3_H] +~w~Чтобы тебе не мешалась служба безопасности, возьми катер чтобы добраться до буев + +[AS3_H1] +и взорвать самолет, когда тот пойдет на посадку. + +[AS3_I] +~w~Найди среди обломков груз и забери его! + +[AS3_J] +~w~Малыш, будь поосторожней там, Окей? + +[AS3_K] +~w~Попробуй-ка соус чили... + +[AS4] +'ВЫКУП' + +[ASUKA] +ЗАДАНИЯ АСУКИ + +[ATUTOR] +Чтобы получить задание Скорой или отказаться от него нажми ~h~~k~~TOGGLE_SUBMISSIONS~~w~. + +[ATUTOR2] +~g~ОСТОРОЖНО отвози пациентов в больницу. Каждое столкновение ухудшает их состояние. + +[ATUTOR3] +Чтобы получить задание Скорой или отказаться от него нажми ~h~~k~~TOGGLE_SUBMISSIONS~~w~. + +[AUG] +Авг + +[AWAY] +~r~Он свалил отсюда! + +[AWAY2] +~r~Они свалили. + +[A_CANC] +~r~Задание скорой отменено! + +[A_COMP1] +Все задания скорой выполнены! + +[A_COMP2] +Ты никогда не будешь уставать! + +[A_COMP3] +Все задания скорой выполнены! Ты никогда не будешь уставать при быстром беге! + +[A_FAIL1] +Задание скорой прервано. + +[A_FAIL2] +~r~Из-за твоей медлительности скончался пациент! + +[A_FAIL3] +~r~Пациент скончался! + +[A_FULL] +~r~Скорая переполнена! + +[A_PASS] +Спасен! + +[A_RANGE] +~g~Рация в скорой не ловит сигнал, подъедь ближе к больнице! + +[A_SAVES] +ЛЮДЕЙ СПАСЕНО: ~1~ + +[A_TIME] ++~1~ секунд + +[BANSHEE] +Банши + +[BARRCKS] +Барракс OL + +[BAT1] +~g~Возьми биту! + +[BELLYUP] +Фургон Триад + +[BETRA_A] +Прости, детка. + +[BETRA_B] +У меня свои планы, а ты... + +[BETRA_C] +Ты мне больше не нужен. + +[BET_JB] +ПРЕДАННЫЙ СВОЕЙ ВОЗЛЮБЛЕННОЙ КАТАЛИНОЙ, БРОСИВШЕЙ ЕГО УМИРАТЬ. ОСУЖДЕННЫЙ И ПРИГОВОРЕННЫЙ, ОН УЖЕ НА ПУТИ В ТЮРЬМУ. ОН НЕ МОЖЕТ ДУМАТЬ НИ О ЧЕМ ИНОМ, КРОМЕ...... МЕСТИ! + +[BFINJC] +Иджэкшен BF + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[BIG_DAM] +Плотина Кокрейн + +[BITCH_D] +~g~Мария мертва! + +[BLISTA] +Блиста + +[BOATIN1] +Запрыгни в катер и нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~ чтобы сесть за руль. + +[BOATIN2] +Если ты рядом с катером, то нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~, чтобы в него сесть. + +[BOATIN3] +Запрыгни в катер и нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~ чтобы сесть за руль. + +[BOATIN4] +Если ты рядом с катером, то нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~, чтобы в него сесть. + +[BOBCAT] +Рысь + +[BOMB] +Чтобы установить ~h~бомбу~w~ - поставь тачку в гараж. Стоимость бомбы - ~h~$1000~w~. + +[BOMB1] +Гараж Лысого + +[BONUS] +~g~ПРИЗ $~1~ + +[BORGNIN] +Борнини + +[BRIDGE1] +Как только отремонтируют мост Каллахан, ты сможешь попасть на остров Стаунтон. + +[BSTSTU] +Лучший БЕЗУМНЫЙ трюк: + +[BUGGY] +ОСТАЛОСЬ: + +[BULL] +СЛИТКОВ: + +[BUS] +Автобус + +[BUSTED] +АРЕСТОВАН! + +[B_SITE] +ЗАДАНИЯ АСУКИ (ПРИГОРОД) + +[CABBIE] +Кэб + +[CAM_A] +Чтобы сменить расположение ~h~камеры~w~, нажми ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~. + +[CAM_B] +Чтобы сменить расположение ~h~камеры~w~, нажми ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~. + +[CARSOFF] +Машины выключены. + +[CARS_ON] +Машины включены. + +[CAR_1] +Ambulance + +[CAR_10] +Coach + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_2] +Firetruck + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_3] +Police + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Cabbie + +[CAR_39] +Buggy + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Rhino + +[CAR_7] +FBIcar + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_CRU] +Машин разбито + +[CAR_EXP] +Машин взорвано + +[CAT1] +'ВЫКУП' + +[CAT1_A] +Твоя драгоценная Мария у меня. Если не хочешь, чтобы ее личико было похоже на мясной фарш, + +[CAT1_B] +привези мне $500,000 на виллу в Кедровой роще. + +[CAT1_E] +XXXX + +[CAT1_F] +Езжай к Каталине, пока не вышло время! + +[CAT2] +'ОБМЕН' + +[CAT2_A] +Интересно, ты пришел ради того, чтобы спасти Марию или вернуться ко мне? + +[CAT2_A1] +Да пошла ты, глупая сучка! + +[CAT2_B] +Хочу сказать тебе кое-что, + +[CAT2_B2] +мне так и хочется тебя пристрелить, но бизнес прежде всего. + +[CAT2_C] +Ты просто находка для меня, амиго! + +[CAT2_D] +Давай бабки. + +[CAT2_E] +О, да ты без дела не шатался! + +[CAT2_E2] +Но ты так и не понял, что мне нельзя верить. + +[CAT2_E3] +Пристрелите кретина. + +[CAT2_F] +Я сломала ноготь, и моя прическа растрепалась. А она обошлась мне в 50 долларов, представляешь? + +[CAT2_G] +Я была так напугана, но потом я сказала себе - ты уже взрослая девочка. + +[CAT2_H] +О, у нас будет так весело, потому что, ты знаешь, моя сестра сказала, что она поживет у нас со своими детьми, + +[CAT2_I] +потому что у ее мужа снова интрижка и... + +[CAT2_J] +Быстрее, взлетаем! + +[CATINF1] +~g~Убей Каталину! + +[CATINF2] +~g~Чтобы найти Каталину - следуй за вертушкой. + +[CAT_MON] +~g~У тебя недостаточно зелени. Необходимо $500,000 баксов. + +[CDERROR] +Ошибка чтения диска с игрой Grand Theft Auto III + +[CHAT] +Chatterbox FM + +[CHEAT1] +Чит-код активирован + +[CHEAT2] +Чит-код: оружие + +[CHEAT3] +Чит-код: здоровье + +[CHEAT4] +Чит-код: броня + +[CHEAT5] +Чит-код: в розыске + +[CHEAT6] +Чит-код: деньги + +[CHEAT7] +Чит-код: погода + +[CHEATOF] +Режим чит-кодов Выкл. + +[CHEATON] +Режим чит-кодов Вкл. + +[CHEETAH] +Гепард + +[CHEVOK] +CheckEveryOkB4Save + +[CHFIDL] +ВЫБЕРИТЕ ЗАПИСЬ ДЛЯ УДАЛЕНИЯ + +[CHFILE] +ВЫБЕРИТЕ ИГРУ ДЛЯ ЗАГРУЗКИ + +[CHINA] +Чайнатаун + +[CINCAM] +Вид из телекамеры + +[CITYZON] +Либерти Сити + +[CLZOOF] +Show Cull Zones Off + +[CLZOON] +Show Cull Zones On + +[CNCSAV] +Нельзя сохранить игру, сидя в тачке. + +[CNTLS] +Управление + +[CNTSAV] +Во время задания запись невозможна. + +[COACH] +Автобус-люкс + +[COLLECT] +СОБРАНО: + +[COLOMCR] +Круизер картеля + +[COLT_IN] +В магазин завезли пистолеты! + +[COM_EAS] +Ньюпорт + +[COM_ZON] +Остров Стаунтон + +[CONSTRU] +Форт Стаунтон + +[CONTRL] +Задание управления + +[COPCART] +~g~У тебя есть ~1~ секунд чтобы вернуться в полицейскую машину, иначе задание будет отменено. + +[COP_M] +'ПОЛИЦЕЙСКИЙ' + +[CO_ALL] +Ты взял всех преступников. Вот тебе кое-что... + +[CO_ONE] +Особые пакеты: ~1~ из ~1~ + +[CRD050A] +ТЕСТЕРЫ + +[CRD136A] +ALEX HORTON + +[CRD137A] +NAVID KHONSARI + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCER + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTION + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +ДИЗАЙН + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +АВТОРЫ СЮЖЕТА + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +ГЕРОИ + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMATION & DIRECTION + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +AUTO DESIGN + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODERS + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +SCORE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +SOUND DESIGN & MASTERING + +[CRED042] +ALLAN WALKER + +[CRED043] +AUDIO PROGRAMMING + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD TESTERS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +TECHNICAL PRODUCER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VP OF MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +TECHNICAL COORDINATOR + +[CRED088] +BRANDON ROSE + +[CRED089] +QA MANAGER + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +LIBERTY TREE EDITORIAL + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT-SCENES + +[CRED108] +SCRIPT BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER + +[CRED110] +AUDIO PRODUCED BY RENAUD SEBBANE + +[CRED111] +CAST + +[CRED112] +FRANK VINCENT AS SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO AS LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN AS TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT AS JOEY LEONE + +[CRED116] +DEBBI MAZAR AS MARIA + +[CRED117] +KYLE MACLACHLAN AS DONALD LOVE + +[CRED118] +ROBERT LOGGIA AS RAY MACHOWSKI + +[CRED119] +GURU AS 8-BALL + +[CRED120] +SONDRA JAMES AS MOMMA + +[CRED121] +LIANA PAI AS ASUKA + +[CRED122] +LES MAU AS KENJI + +[CRED123] +CYNTHIA FARRELL AS CATALINA + +[CRED124] +AL ESPINOSA AS MIGUEL + +[CRED125] +CHRIS PHILLIPS AS EL BURRO + +[CRED126] +HUNTER PLATIN AS CHICO + +[CRED127] +WALTER MUDU AS D-ICE + +[CRED128] +CURTIS MCCLARIN AS CURTLY + +[CRED129] +BILL FIORE AS DARKEL + +[CRED130] +CHRIS PHILLIPS AS MARTY CHONKS + +[CRED131] +HUNTER PLATIN AS CURLY BOB + +[CRED132] +WALTER MUDU AS KING COURTNEY + +[CRED133] +HUNTER PLATIN AS ONE-ARMED PHIL + +[CRED134] +KIM GURNEY AS MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRED137] +DIRECTED BY + +[CRED138] +PRODUCED BY + +[CRED139] +RECORDED AT MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORS + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRED142] +WRITTEN BY DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRECTED BY CRAIG CONNER, DAN HOUSER AND LAZLOW + +[CRED144] +PRODUCED BY RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +RADIO STATIONS AND MUSIC + +[CRED218] +PRODUCERS FOR ROCKSTAR UK + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDITED BY + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING WRITTEN BY + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED254] +STUDIO MANAGER + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CRED270] +MIKE HONG + +[CRGOFF] +ShowCarRoadGroups Off + +[CRIMRA] +Ваш рейтинг: + +[CRLDIC] +Создание и загрузка иконок + +[CRMGSV] +Create copy protected magazine directory + +[CRRGON] +ShowCarRoadGroups On + +[CRROOT] +CreateRootDir + +[CRUSH] +Поставь машину в голубой круг и выйди из нее. После этого машина будет утилизирована. + +[CR_1] +Кран не может поднять эту машину. + +[CTRSCR] +Центровка экрана + +[CTUTOR] +Нажми ~h~~k~~TOGGLE_SUBMISSIONS~~w~, чтобы заняться работой полицейского или отказаться от нее. + +[CTUTOR2] +Нажми ~h~~k~~TOGGLE_SUBMISSIONS~~w~, чтобы заняться работой полицейского или отказаться от нее. + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[CVT_CRT] +Конвертация текстур не выполнена. Чтобы ее выполнить, вам нужно войти под администраторским паролем. Нажмите ESC для отмены. + +[CVT_ERR] +У вас закончилось место на жестком диске. Пожалуйста, освободите место перед запуском игры. Нажмите ESC для отмены. + +[CVT_MSG] +Конвертация текстур в формат оптимальный для вашей видеокарты. + +[C_BREIF] +~g~~a~ - там в последний раз видели подозреваемого. + +[C_CANC] +~r~Миссия полицейского прервана! + +[C_ESCP] +~r~Подозреваемый скрылся! + +[C_FAIL] +Миссия полицейского не выполнена! + +[C_KILLS] +ПРЕСТУПНИКОВ УБИТО: ~1~ + +[C_PASS] +СПРАВЕДЛИВОСТЬ ВОСТОРЖЕСТВОВАЛА! + +[C_RANGE] +~g~Полицейская рация не ловит сигнал, надо подъехать ближе к участку! + +[C_TIME] +~r~Время выполнения задания вышло! + +[C_VIGIL] +НАГРАДА ОТ ПОЛИЦИИ! + +[DAM] +ПОВРЕЖДЕНИЯ: + +[DAYPLC] +Ежедневные расходы полиции + +[DAYSPS] +Дней проведенных за игрой + +[DBFOFF] +CTheScripts::DbgFlag Off + +[DBGFON] +CTheScripts::DbgFlag On + +[DBINST] +Двойной безумный трюк + +[DBL_CLF] +Double Clef FM + +[DBPINS] +Идеальный безумный трюк + +[DEAD] +ВЫРУБИЛСЯ! + +[DEBUGM] +Отладочное меню + +[DEC] +Дек + +[DED_CRI] +Убито преступников + +[DED_DED] +Убито должников + +[DED_HOK] +Убито шлюх + +[DEFDT] +--:---:---- --:--:-- + +[DEFNAM] +Клод---------------------- + +[DEL_FNM] +Файл удален. + +[DETON] +ВЗРЫВ ЧЕРЕЗ: + +[DIAB1] +'ГОНКА' + +[DIAB1_1] +~g~3..2..1.. СТАРТ! + +[DIAB1_2] +~g~Поздравляем, ты победил с отличным результатом: ~1~ секунд. + +[DIAB1_3] +~r~Куда тебе с нами тягаться, НЕУДАЧНИК! + +[DIAB1_4] +~g~Найди быструю тачку, и приезжай на старт. + +[DIAB1_5] +ВРЕМЯ: + +[DIAB1_A] +Эль Бурро хочет предложить тебе одно дельце. Двигай к телефону в Хепберн Хейтс, если тебя это заинтересовало. + +[DIAB1_B] +Это Эль Бурро из банды Дьяволов. + +[DIAB1_C] +Не так уж ты и крут. Подойди к телефону, и возможно Эль Бурро предложит тебе работенку. + +[DIAB1_D] +Ты новичок в городе, но на улицах о тебе уже ходят слухи. + +[DIAB1_E] +Мы решили устроить гонку, которая начнется у старой школы, что у моста Каллахан. + +[DIAB1_F] +Достань себе тачку получше, и если выиграешь гонку, получишь приз. + +[DIAB2] +'СМЕРТЬ ОТ МОРОЖЕНОГО' + +[DIAB2_1] +~g~Возьми дипломат в Харвуде. + +[DIAB2_2] +~g~Найди фургон с мороженым. + +[DIAB2_3] +~g~Припаркуй фургон с мороженым на пристани Атлантик. + +[DIAB2_4] +~g~Понажимай ~w~~k~~VEHICLE_HORN~~g~ чтобы включить на фургоне колокольчик. + +[DIAB2_5] +~g~Выйди из фургона и затем с помощью дистанционника взорви его. + +[DIAB2_6] +~g~Понажимай ~w~~k~~VEHICLE_HORN~~g~ чтобы включить на фургоне колокольчик. + +[DIAB2_7] +~g~Понажимай ~w~~k~~VEHICLE_HORN~~g~ чтобы включить колокольчик на фургоне. + +[DIAB2_A] +Когда я начал свой экзотический бизнес, моим единственным активом было содержимое моих кожаных штанов! + +[DIAB2_B] +Одна банда стала угрожать, что лишит меня этой штуки, если я не буду отстегивать им процент. + +[DIAB2_C] +Они не с тем связались, амиго. + +[DIAB2_D] +Эти кретины западают на мороженое. + +[DIAB2_E] +Возьми бомбу, что я спрятал в Харвуде, + +[DIAB2_F] +и угони фургон с мороженым, что катается по городу. + +[DIAB2_G] +Эти глупцы побегут на верную смерть, услышав колокольчик фургона. + +[DIAB2_H] +Они прячутся на складе пристани Атлантик. + +[DIAB3] +'ИСПЫТАНИЕ ОГНЕМ' + +[DIAB3_1] +УБЕЙ 25 ТРИАДОВЦЕВ + +[DIAB3_A] +Какой-то наглый триадовец прошлой ночью спер мою тачку, + +[DIAB3_B] +разбил ее и бросил догорать. + +[DIAB3_C] +В багажнике было несколько моих любимых безделушек - + +[DIAB3_D] +настоящая гордость коллекционера, таких сейчас не достать. + +[DIAB3_E] +Неподалеку от Чайнатауна я припрятал огнемет. + +[DIAB3_F] +Возьми его и научи этих вандалов из Триады бояться грозного орудия Эль Бурро. + +[DIAB3_G] +Арриба! + +[DIAB4] +'ПОРНОКРАД' + +[DIAB4_1] +~g~Подгони фургон на задний двор магазина порнухи. + +[DIAB4_A] +Один слишком шустрый делец угнал фургон с последним тиражом моего издания! + +[DIAB4_B] +Но этот обкурившийся кретин не закрыл дверцы фургона, + +[DIAB4_C] +и теперь все мои прекрасные + +[DIAB4_D] +порножурналы с изумительными фотографиями разбросаны по всему городу! + +[DIAB4_E] +Садись в фургон и иди по следу 'Петушка из Портленда' выпусков 1, 2 и 3, + +[DIAB4_F] +ну и собирай их заодно по пути. + +[DIAB4_G] +Как только выследишь этого шустрого отморозка - разберись с ним! + +[DIAB4_H] +Затем отгони мой фургон с грузом к сексшопу в квартале красных фонарей. + +[DIABLCR] +Жеребец Дьяволов + +[DIABLO] +ЗАДАНИЯ ДЬЯВОЛОВ + +[DLFILE] +Удаление файлов игры GTA3 + +[DODO] +Додо + +[DODO_FT] +Вы были в воздухе ~1~ секунд! + +[DRIVE_A] { re3 change } +Садясь в машину, возьми в руки Узи. Посмотри вправо или влево и нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~ для выстрела. + +[DRIVE_B] { re3 change } +Садясь в машину, возьми в руки Узи. Посмотри вправо или влево и нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~ для выстрела. + +[DSPLAY] +Экран + +[DSTROFF] +Debug Streaming Requests Off + +[DSTRON] +Debug Streaming Requests On + +[EASTBAY] +Портланд Бич + +[EBAL] +'НА СВОБОДУ' + +[EBAL_1] +Нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~, чтобы ~h~сесть за руль ~w~или ~h~выйти~w~ из машины. + +[EBAL_1B] +Нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~, чтобы ~h~сесть за руль ~w~или ~h~выйти~w~ из машины. + +[EBAL_2] +~g~Возвращайся в машину! + +[EBAL_3] +Это ~h~радар~w~. Он очень полезен для навигации в городе. Сейчас ~h~цветная отметка~w~ на нем показывает расположение укрытия! + +[EBAL_4] +~r~Лысый погиб! + +[EBAL_5] +~g~Достань тачку! + +[EBAL_6] +~g~Подбери Мисти! + +[EBAL_A] +Я знаю место на углу квартала Красных фонарей, где мы сможем залечь, + +[EBAL_A1] +но я хреновый водитель, так что, браток, лучше садись ты за руль. + +[EBAL_B] +Вот и приехали. Давай зайдем внутрь и сменим эти чертовы шмотки! + +[EBAL_D] +Я знаю одного парня с большими связями, его зовут Луиджи. + +[EBAL_D1] +Я с ним работал раньше, так что, может, он и тебе работенку подкинет. Давай, съездим к нему. + +[EBAL_E] +Давай, пошли, я тебя представлю. + +[EBAL_G] +Это клуб Луиджи. Давай не будем светиться и пройдем через черный ход. + +[EBAL_H] +Погоди меня тут, пока я схожу и потолкую с Луиджи. + +[EBAL_I] +Босс решил выйти и взглянуть на тебя... + +[EBAL_J] +У Лысого какие-то дела там наверху. + +[EBAL_K] +Сделай мне одолжение, парень. + +[EBAL_L] +Одной из моих девиц нужно прокатиться, так что бери тачку, забирай Мисти из клиники и вези сюда. + +[EBAL_M] +Запомни, никто не лапает моих девок! + +[EBAL_N] +Так что держи свои руки на баранке! + +[EBAL_O] +Если не завалишь это дело, то может найтись и другая работенка. Все, проваливай! + +[ELBURRO] +Лучший результат в гонке (сек) + +[EMVHPUP] +Мы заплатим хорошие бабки за старые и новые машины скорой помощи. Подгони их к крану на северо-востоке порта Портланда. + +[END_A] +Жители Кедровой рощи едва успели оправиться + +[END_B] +от шока, вызванного настоящей криминальной войной, + +[END_C] +всполошившей вчера весь район. + +[END_D] +Местный житель Клив Денвер рассказал полиции, + +[END_E] +что видел, как с места происшествия убегал бандит и какая-то брюнетка. + +[END_F] +Ой, ты знаешь, нам будет так клево, потому что, ну... знаешь, + +[END_G] +я люблю тебя, я, я, я правда люблю, потому что ты такой крутой сильный мужик + +[END_H] +и ты именно тот, кто мне нужен. + +[END_I] +Ой, о чем это я? + +[END_J] +Ну вот, я забыла. Но ты понял, что я имела в виду, правда? + +[END_K] +Люди бросились в укрытия, когда взрывы стали сотрясать ближайшие дома. + +[END_L] +Несколько жителей пострадало во время ожесточенной перестрелки + +[END_M] +между бандитами и вертолетом, кружившим над плотиной. + +[END_N] +Да, из садов открывался отличный вид на это зрелище! + +[END_O] +Когда вертолет наконец сбили, + +[END_P] +был фейерверк получше, чем на Четвертое июля! + +[END_Q] +Хотя найдено уже более двадцати тел, + +[END_R] +полиция все еще находит останки. + +[END_S] +Мы не получили официального опровержения слухов, о том, + +[END_T] +что все тела принадлежат членам колумбийского Картеля. + +[END_U] +Истинные причины этой бойни до сих пор неизвестны. + +[END_V] +Представляешь, я сломала ноготь и испортила прическу! + +[END_W] +А она обошлась мне в пятьдесят баксов... + +[ENFORCR] +Энфорсер + +[ENGLIS] +English + +[ESPERAN] +Эсперанто + +[EVID] +УЛИКИ: + +[FARE1] +~g~Едем в ~w~'Клуб секс кошечек' ~g~в Квартале красных фонарей. + +[FARE10] +~g~Едем в ~w~'Лапшу по-китайски' ~g~в Чайнатауне. + +[FARE11] +~g~Едем на ~w~строительную площадку ~g~в форте Стаунтон. + +[FARE12] +~g~Едем на ~w~футбольный стадион ~g~на Аспатрии. + +[FARE13] +~g~Едем в ~w~церковь ~g~в Бедфорд Поинт. + +[FARE14] +~g~Едем в ~w~казино ~g~в Торрингтоне. + +[FARE15] +~g~Едем к ~w~зданию университета ~g~в учебном городке. + +[FARE16] +~g~Едем в ~w~торговый комплекс ~g~в в Районе парка Бельвиль. + +[FARE17] +~g~Едем в ~w~музей ~g~в Ньюпорте. + +[FARE18] +~g~Едем к ~w~зданию 'Амко' ~g~в Торрингтоне. + +[FARE19] +~g~Едем в ~w~'Болт Бургер' ~g~в Бедфорд Поинт. + +[FARE2] +~g~Едем в ~w~'Сюпа Сейв' ~g~на Портланд Вью. + +[FARE20] +~g~Едем в ~w~парк ~g~в Бельвиле. + +[FARE21] +~g~Едем в ~w~аэропорт Фрэнсис~g~. + +[FARE22] +~g~Едем к ~w~плотине Кокрейн~g~. + +[FARE23] +~g~Едем к ~w~автосалону ~g~ рядом с плотиной Кокрейн. + +[FARE24] +~g~Едем в ~w~больницу ~g~в Пайк Крик. + +[FARE25] +~g~Едем в ~w~парк ~g~в Шорсайд Вейл. + +[FARE26] +~g~Едем к ~w~Северо-западным башням ~g~в Садах Вичита. + +[FARE3] +~g~Едем к ~w~старой школе ~g~в Чайнатауне. + +[FARE4] +~g~Едем в ~w~'Кафе Грейзи Джо' ~g~на мысе Каллахан. + +[FARE5] +~g~Едем в ~w~оружейный магазин ~g~в квартале красных фонарей. + +[FARE6] +~g~Едем в ~w~'Автомобили в кредит' ~g~на Сент-Марк. + +[FARE7] +~g~Едем в ~w~'Топлесс-Бар Вуди' ~g~в квартале красных фонарей. + +[FARE8] +~g~Едем в ~w~бистро Марко ~g~на Сент-Марк. + +[FARE9] +~g~Едем в ~w~Автосалон ~g~в гавани Портланда. + +[FARES] +ОТВЕЗЕНО: + +[FBICAR] +Машина ФБР + +[FEA_2SP] +ДВЕ КОЛОНКИ + +[FEA_3DH] +СПОСОБ ВЫВОДА ЗВУКА + +[FEA_4SP] +БОЛЬШЕ ДВУХ КОЛОНОК + +[FEA_DO] += + +[FEA_EAR] +НАУШНИКИ + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +ПУСТОМЕЛЯ 109 + +[FEA_FM9] +ПРОИГРЫВАТЕЛЬ MP3 + +[FEA_LE] +< + +[FEA_MNO] +Моно + +[FEA_MUS] +ГРОМКОСТЬ МУЗЫКИ + +[FEA_NAH] +НЕТ ЗВУКОВОЙ КАРТЫ + +[FEA_NON] +Нет + +[FEA_OUT] +Выход: + +[FEA_RI] +> + +[FEA_RSS] +РАДИОСТАНЦИЯ + +[FEA_SFX] +ГРОМКОСТЬ ЗВУКОВ + +[FEA_SPK] +ТИП АКУСТИКИ + +[FEA_ST] +Стерео + +[FEA_UP] +; + +[FEB] +Фев + +[FEB_AUD] +Аудио + +[FEB_BRI] +Сообщения + +[FEB_CON] +Управление + +[FEB_CPC] +Настройка управления + +[FEB_DIS] +Экран + +[FEB_LAN] +Язык + +[FEB_PMB] +Задание предыдущей миссии: + +[FEB_SAV] +Загрузить + +[FEB_STA] +Статистика + +[FEC_ACC] +Газ + +[FEC_ACL] +Педаль газа + +[FEC_ATT] +Удар или выстрел из оружия + +[FEC_BAC] +Назад + +[FEC_BRA] +Тормоз / Задний ход + +[FEC_BRK] +Педаль тормоза + +[FEC_BSP] +ЗАБОЙ + +[FEC_CAM] +Расположение камеры + +[FEC_CAW] +Оружие в машине + +[FEC_CCM] +Поставить камеру сзади игрока. + +[FEC_CEN] +Центровка камеры + +[FEC_CLE] +Прокрутка оружия влево + +[FEC_CLK] +CAPSLOCK + +[FEC_CMM] +Общее управления + +[FEC_CMP] +ВМЕСТЕ: ВЗГЛЯД ВЛЕВО + ВЗГЛЯД ВПРАВО + +[FEC_CMR] +Выбор позиции камеры + +[FEC_CMS] +Изменение расположения камеры для всех вариантов. + +[FEC_CNT] +Тип управления: + +[FEC_CRD] +Перебор радиостанций + +[FEC_CRI] +Прокрутка оружия вправо + +[FEC_CWL] +Прокрутка оружия влево + +[FEC_CWR] +Прокрутка оружия вправо + +[FEC_DBG] +ОТЛАДОЧНОЕ МЕНЮ + +[FEC_DLF] +Удаление невозможно. + +[FEC_DLL] +DEL + +[FEC_DOT] +ДОП. + +[FEC_DWA] +СТРЕЛКА ВНИЗ + +[FEC_EEX] +В машину /Из машины + +[FEC_EMS] +Эту клавишу назначить нельзя. + +[FEC_END] +END + +[FEC_ENT] +В машину /Из машины + +[FEC_ENV] +Сесть в машину + +[FEC_ESR] +Клавишу ESC задавать нельзя + +[FEC_ETR] +ДОП ВВОД + +[FEC_EXV] +В машину /Из машины + +[FEC_FIR] +Выстрел + +[FEC_FNC] +F~1~ + +[FEC_FOR] +Вперед + +[FEC_FPC] +Вид от первого лица + +[FEC_FPO] +Оружие человека + +[FEC_FPR] +Управление от первого лица + +[FEC_FWS] +ДОП / + +[FEC_GSL] +Покачивание камеры при ходьбе: + +[FEC_HAB] +Ручной тормоз + +[FEC_HBR] +Ручной тормоз машины + +[FEC_HME] +HOME + +[FEC_HND] +Тормоз + +[FEC_HO3] +Сигнал (кнопка L3) + +[FEC_HOR] +Сигнал + +[FEC_HRN] +Сигнал + +[FEC_IBT] +- + +[FEC_IRT] +INS + +[FEC_IVH] +Инвертирование мыши по горизонтали: + +[FEC_IVV] +ИНВЕРТИРОВАНИЕ МЫШИ ПО ВЕРТИКАЛИ + +[FEC_JBO] +ДЖ. ~1~ + +[FEC_JMP] +Прыжок + +[FEC_JOY] +Джойстик + +[FEC_JUM] +Прыжок + +[FEC_LAL] +ЛЕВ.ALT + +[FEC_LB] +Оглянуться назад + +[FEC_LB1] +Взгляд + +[FEC_LB2] +назад + +[FEC_LB3] +Оглянуться назад + +[FEC_LBA] +Оглянуться назад + +[FEC_LBC] +Нажать взгляд Влево и Вправо. + +[FEC_LBH] +Посмотреть назад + +[FEC_LCT] +ЛЕВ.CTRL + +[FEC_LDN] +Посмотреть вниз + +[FEC_LDU] +Посмотреть вниз + +[FEC_LEF] +Влево + +[FEC_LFA] +СТРЕЛКА ВЛЕВО + +[FEC_LKL] +Посмотреть влево + +[FEC_LKT] +Зафиксировать цель + +[FEC_LL] +Посмотреть налево + +[FEC_LLF] +Взгляд налево из машины + +[FEC_LOF] +Посмотреть вперед + +[FEC_LOL] +Посмотреть налево + +[FEC_LOR] +Посмотреть направо + +[FEC_LR] +Посмотреть направо + +[FEC_LRG] +Взгляд вправо из машины + +[FEC_LRT] +Посмотреть вправо + +[FEC_LSF] +ЛЕВ.SHIFT + +[FEC_LUD] +Посмотреть вверх + +[FEC_LUN] +Загрузка невозможна. Файл поврежден, пожалуйста удалите эту запись. + +[FEC_LUP] +Посмотреть вверх + +[FEC_LWD] +ЛЕВ.WIN + +[FEC_MIN] +ДОП - + +[FEC_MOV] +Движение + +[FEC_MSH] +ЧУВСТВИТЕЛЬНОСТЬ МЫШИ + +[FEC_MSL] +ЛЕВ.КН.МЫШИ + +[FEC_MSM] +СРЕД.КН.МЫШИ + +[FEC_MSR] +ПРАВ.КН.МЫШИ + +[FEC_MWB] +КОЛЕСО МЫШИ ВНИЗ + +[FEC_MWF] +КОЛЕСО МЫШИ ВВЕРХ + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_NA] +НЕТ + +[FEC_NLK] +NUMLOCK + +[FEC_NMN] +ДОП ~1~ + +[FEC_NTR] +Следующая цель + +[FEC_NTT] +Неизвестная клавиша + +[FEC_NTW] +Разговор по сети + +[FEC_NUM] +ДОП + +[FEC_NUS] +НЕ ИСПОЛЬЗУЕТСЯ + +[FEC_NWE] +Следующее оружие + +[FEC_OJS] +Для каждого действия можно назначить лишь одну кнопку джойстика + +[FEC_OKK] +О.К. + +[FEC_OMS] +Для действия можно задать лишь одну кнопку мыши + + +[FEC_ORR] +или + +[FEC_PAD] +Геймпад + +[FEC_PAS] +Пауза + +[FEC_PAU] +Пауза + +[FEC_PED] +Управление героем + +[FEC_PFR] +Выстрел из оружия + +[FEC_PGD] +PGDN + +[FEC_PGU] +PGUP + +[FEC_PJP] +Прыжок + +[FEC_PLB] +Взгляд назад. + +[FEC_PLS] +ДОП + + +[FEC_PSB] +BREAK + +[FEC_PSH] +Выстрел + +[FEC_PSP] +Бежать + +[FEC_PTL] +Use LockTarget with Weapon Switch Left. + +[FEC_PTR] +Use LockTarget with Weapon Switch Right. + +[FEC_PTT] +Предыдущая цель + +[FEC_PWE] +Предыдущее оружие + +[FEC_PWF] +Идти вперед + +[FEC_PWL] +Идти влево + +[FEC_PWR] +Идти вправо + +[FEC_PWT] +Движение относительно камеры + +[FEC_QUE] +??? + +[FEC_R3] +(R3 button) + +[FEC_RAD] +Радио + +[FEC_RAL] +ПР.ALT + +[FEC_RCT] +ПР.CTRL + +[FEC_RFA] +СТРЕЛКА ВПРАВО + +[FEC_RIG] +Вправо + +[FEC_RS3] +Перебор радиостанций (L3 button) + +[FEC_RSC] +Перебор радиостанций + +[FEC_RSF] +ПР.SHIFT + +[FEC_RTN] +ВВОД + +[FEC_RUN] +Бежать + +[FEC_RWD] +ПР.WIN + +[FEC_SFT] +SHIFT + +[FEC_SGJ] +Настройка джойстика + +[FEC_SLC] +Slot is corrupted + +[FEC_SLK] +SCROLL LOCK + +[FEC_SM3] +Special mission trigger (R3 button) + +[FEC_SMS] +Показывать указатель мыши + +[FEC_SMT] +Вызов спецзадания + +[FEC_SPC] +ПРОБЕЛ + +[FEC_SPN] +Бежать + +[FEC_STR] +ДОП ЗВЕЗДОЧКА + +[FEC_SUB] +Задание доп. миссий + +[FEC_SVU] +Запись не удалась. + +[FEC_SZI] +Снайперский прицел + + +[FEC_SZO] +Снайперский прицел - + +[FEC_TAB] +TAB + +[FEC_TAR] +Цель + +[FEC_TDO] +Камера отладки ВЫКЛ. + +[FEC_TFD] +Башню /Додо вниз + +[FEC_TFL] +Башню влево + +[FEC_TFR] +Башню вправо + +[FEC_TFU] +Башню /Додо вверх + +[FEC_TGD] +Toggle Pad Game/Debug + +[FEC_TLF] +Следующая цель слева + +[FEC_TRG] +Следующая цель справа + +[FEC_TSM] +Задание доп. миссий + +[FEC_TSS] +Записать картинку с экрана + +[FEC_TUC] +Управление башней + +[FEC_TUL] +Башню влево + +[FEC_TUR] +Башню вправо + +[FEC_TWO] +Можно задать не более двух клавиш. + +[FEC_UJS] +Эту кнопку джойстика назначить нельзя. + +[FEC_UMS] +Эту кнопку мыши назначить нельзя. + +[FEC_UNB] +НЕ ЗАДАНО + +[FEC_UND] +(НЕТ) + +[FEC_UPA] +СТРЕЛКА ВВЕРХ + +[FEC_VEH] +Управление машиной + +[FEC_VES] +Управление машиной + +[FEC_WAR] +Внимание! + +[FEC_WHL] +Рулевое управление + +[FEC_WPN] +Выстрел из оружия + +[FEC_WRC] +WINCLICK + +[FEC_ZIN] +Приблизить + +[FEC_ZOT] +Отдалить + +[FEDL_WR] +Deleting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FEDSAS2] +<>-CHANGE SELECTION + +[FEDSAS3] +- CHANGE SELECTION + +[FEDSAS4] +;=<> - CHANGE SELECTION + +[FEDSSC1] +;-FASTER SCROLLING + +[FEDSSC2] +=-STOP SCROLLING + +[FEDS_AM] +<>-CHANGE MENU + +[FEDS_AS] +;=-CHANGE SELECTION + +[FEDS_BA] +" button - BACK + +[FEDS_SB] +/ button - SELECT " button - BACK + +[FEDS_SE] +/ button - SELECT + +[FEDS_SM] +L1,R1-CHANGE MENU + +[FEDS_SS] +L1,R1-CHANGE SELECTION + +[FEDS_ST] +START button - RESUME + +[FEDS_TB] +НАЗАД + +[FEDS_XB] +Выбор + +[FED_BRI] +ЯРКОСТЬ + +[FED_CON] +Подтвердите удаление записи + +[FED_DBG] +Отладочное меню + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Большая отладочная лампа переключена + +[FED_DLW] +Идет удаление. Пожалуйста, подождите... + +[FED_DSR] +Debug Streaming Requests + +[FED_LDW] +Идет загрузка. Пожалуйста, подождите... + +[FED_LFL] +Игру загрузить не удалось. Игра будет перезапущена. + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_RES] +РАЗРЕШЕНИЕ ЭКРАНА + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_SCP] +gbShowCollisionPolys + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_SPR] +Show Ped Road Groups + +[FED_SUB] +СУБТИТРЫ + +[FED_TRA] +ШЛЕЙФ ОТ ДВИЖУЩИХСЯ ОБЪЕКТОВ + +[FED_WIS] +ШИРОКИЙ ЭКРАН + +[FEFD_WR] +Formatting Memory Card (PS2) in MEMORY CARD slot 1. Please do not remove the Memory Card (PS2), reset or switch off the console. + +[FEF_AU1] +Прибавьте громкости! + +[FEF_AU2] +Выберите радиостанцию и звуковой эффект + +[FEF_BR1] +Не знаете, что делать? + +[FEF_BR2] +Прочтите тексты последних заданий, отсортированных по дате. + +[FEF_CO1] +Вас не устраивает такой способ управления? + +[FEF_CO2] +Выберите наиболее удобное для вас управление + +[FEF_DI1] +Игра изменена! + +[FEF_DI2] +Настройка игры под ваш телевизор + +[FEF_INT] +ИНТЕРНЕТ + +[FEF_LA1] +О чем ты, черт побери, говоришь? + +[FEF_LA2] +Выбери такую манеру выражаться, какая тебя устроит. + +[FEF_LAN] +ЛОКАЛЬНАЯ СЕТЬ + +[FEF_SA1] +Борись за свое место в списке! + +[FEF_SA2] +Загрузка и сохранение вашей игры + +[FEF_ST1] +Кто тут у нас плохой? + +[FEF_ST2] +Сколько ты вызвал аварий + +[FEG_MAP] +КАРТА + +[FEG_PLY] +ИГРОКИ + +[FEG_PNG] +ПИНГ + +[FEG_SRV] +СЕРВЕР + +[FEG_TYP] +ТИП + +[FELD_WR] +Loading data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FELZ_FO] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. + +[FEL_ENG] +АНГЛИЙСКИЙ + +[FEL_FRE] +ФРАНЦУЗСКИЙ + +[FEL_GER] +НЕМЕЦКИЙ + +[FEL_ITA] +ИТАЛЬЯНСКИЙ + +[FEL_SPA] +ИСПАНСКИЙ + +[FEM_CES] +Check Every 0kB4 Save + +[FEM_CLI] +Создание и загрузка иконок + +[FEM_CPD] +Create copy protected mag directory + +[FEM_CRD] +Create Root Dir + +[FEM_DBG] +ОТЛАДКА + +[FEM_FFF] +Fill First File with Guff + +[FEM_FRM] +ОГРАНИЧЕНИЕ КОЛИЧЕСТВА КАДРОВ + +[FEM_HST] +СОЗДАТЕЛЬ ИГРЫ + +[FEM_LOD] +ДАЛЬНОСТЬ ПРОРИСОВКИ + +[FEM_MA0] +Либерти Сити + +[FEM_MA1] +Кв. красных фонарей + +[FEM_MA2] +Чайнатаун + +[FEM_MA3] +Башня + +[FEM_MA4] +Канализация + +[FEM_MA5] +Промышленный парк + +[FEM_MA6] +Доки + +[FEM_MA7] +Стаунтон + +[FEM_MAP] +Выбор карты + +[FEM_MC2] +Memory Card Menu 2 + +[FEM_MCM] +Memory Card Menu + +[FEM_MM] +ГЛАВНОЕ МЕНЮ + +[FEM_MP] +ИГРА ПО СЕТИ + +[FEM_NO] +НЕТ + +[FEM_NON] +НЕТ + +[FEM_OFF] +ВЫКЛ + +[FEM_ON] +ВКЛ + +[FEM_OPT] +НАСТРОЙКИ + +[FEM_QT] +ВЫХОД + +[FEM_RES] +ВЕРНУТЬСЯ В ИГРУ + +[FEM_RMC] +Register MemCard One + +[FEM_SL1] +Ячейка 1 свободна + +[FEM_SL2] +Ячейка 2 свободна + +[FEM_SL3] +Ячейка 3 свободна + +[FEM_SL4] +Ячейка 4 свободна + +[FEM_SL5] +Ячейка 5 свободна + +[FEM_SL6] +Ячейка 6 свободна + +[FEM_SL7] +Ячейка 7 свободна + +[FEM_SL8] +Ячейка 8 свободна + +[FEM_SOG] +Save Only The Game + +[FEM_SP] +ОДИН ИГРОК + +[FEM_STG] +Запись игры + +[FEM_STS] +Save The Game under GTA3 name + +[FEM_TD] +Test Delete: + +[FEM_TFM] +Test Format MemCard One + +[FEM_TL] +Test Load: + +[FEM_TS] +Test Save: + +[FEM_TUM] +Test UnFormat MemCard One + +[FEM_VSC] +СИНХРОНИЗАЦИЯ КАДРОВ + +[FEM_YES] +ДА + +[FEN_CON] +Подключение + +[FEN_GAM] +Поиск игр + +[FEN_GNA] +Имя игры: + +[FEN_NAM] +Имя: + +[FEN_NCI] +НЕТ СВЯЗИ С ИНТЕРНЕТОМ + +[FEN_NET] +Сеть + +[FEN_PLA] +Число игроков: + +[FEN_PLC] +Цвет игрока + +[FEN_PLS] +Настройки игрока + +[FEN_STA] +НАЧАТЬ ИГРУ + +[FEN_TY0] +Сражение + +[FEN_TY1] +Партизанская война + +[FEN_TY2] +Командный бой + +[FEN_TY3] +Командная партизанская война + +[FEN_TY4] +Кража денег + +[FEN_TY5] +Захват флага + +[FEN_TY6] +Крысиные гонки + +[FEN_TY7] +Доминатор + +[FEN_TYP] +Тип игры + +[FEN_UKH] +Неизвестный сервер + +[FEN_UKM] +Карта не найдена + +[FEN_UKT] +Неизвестный тип игры + +[FEP_AUD] +ЗВУК + +[FEP_BRI] +СООБЩЕНИЯ + +[FEP_CON] +УПРАВЛЕНИЕ + +[FEP_DIS] +ЭКРАН + +[FEP_LAN] +ЯЗЫК + +[FEP_SAV] +ПРОДОЛЖИТЬ ИГРУ + +[FEP_STA] +СТАТИСТИКА + +[FEQ_SRE] +Вы точно решили выйти? Все что вы успели сделать после последнего сохранения будет потеряно. Выходим? + +[FEQ_SRW] +Вы точно решили выйти из игры? + +[FESTDCM] +Расстояние, пройденное на машине (м) + +[FESTDFM] +Расстояние, пройденное пешком (м) + +[FEST_BB] +Быстрые тачки - легкие деньги: + +[FEST_BD] +Лучшее время деактивации бомбы + +[FEST_CC] +Убито преступников во время работы полицейским + +[FEST_DC] +Расстояние, пройденное на машине (миль) + +[FEST_DF] +Расстояние, пройденное пешком (миль) + +[FEST_FE] +Потушено пожаров + +[FEST_GC] +Взорвано бандитских машин: + +[FEST_H0] +Пройдено контрольных точек + +[FEST_H1] +Драка с Дьяволами + +[FEST_H2] +Мордобой с мафией + +[FEST_H3] +Катастрофа в казино + +[FEST_H4] +Разборка с растаманами + +[FEST_HA] +Максимальный уровень заданий 'скорой' + +[FEST_LF] +Лучшее время полета на Додо + +[FEST_LS] +Спасено людей во время работы на 'скорой' + +[FEST_MP] +Заданий выполнено + +[FEST_OO] +из + +[FEST_R1] +'Пэтриот Ралли' (сек) + +[FEST_R2] +'Гонка в парке' (сек) + +[FEST_R3] +Догнал! за (сек) + +[FEST_RM] +'Гонка по гаражу' (сек) + +[FEST_RP] +Схваток завершено + +[FESZ_CA] +Отмена + +[FESZ_FF] +Format Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FESZ_FM] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. Would you like to format Memory Card (PS2) in MEMORY CARD slot 1? + +[FESZ_FO] +Would you like to format the Memory Card (PS2) in MEMORY CARD slot 1? + +[FESZ_L1] +Игра успешно сохранена! + +[FESZ_L2] +Игра сохранена в файле: + +[FESZ_LS] +Загрузка завершена. + +[FESZ_OK] +OK + +[FESZ_OW] +Overwriting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FESZ_QD] +Вы точно решили удалить эту запись игры? + +[FESZ_QF] +Are you sure you wish to format the Memory Card (PS2) in MEMORY CARD slot 1? + +[FESZ_QL] +Все что вы успели сделать после последнего сохранения будет потеряно. Вы точно решили загрузить сохраненную игру? + +[FESZ_QO] +Вы решили записать игру на место предыдущей записи? + +[FESZ_QR] +Вы точно решили начать новую игру? Все что вы успели сделать после последнего сохранения будет потеряно. Начнем? + +[FESZ_QS] +ЗАПИСАТЬ ИГРУ ? + +[FESZ_QU] +Выход + +[FESZ_QZ] +Вы уверены, что хотите сохранить игру? + +[FESZ_SA] +Запись игры + +[FESZ_SR] +Save Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FESZ_TI] +SAVE Z1 + +[FESZ_UC] +ОТМЕНА + +[FESZ_WR] +Saving data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FES_AFO] +This Memory Card (PS2) is already formatted. + +[FES_CAN] +Отмена + +[FES_CGA] +Доступные ячейки для сохранения: + +[FES_CSA] +Выберите одежду из списка: + +[FES_DAT] + ДАТА + +[FES_DEE] +Deleting Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FES_DGA] +УДАЛЕНИЕ ЗАПИСЕЙ + +[FES_GME] +Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + +[FES_ISC] +ПОВРЕЖДЕН + +[FES_ISF] +ОТСУТСТВУЕТ + +[FES_LCG] +Продолжить игру с того места, где вы записались? + +[FES_LG] +ЗАГРУЗКА ИГРЫ + +[FES_LGA] +Загрузить игру + +[FES_LOE] +Load Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FES_LOF] +Загрузка не удалась. + +[FES_NGA] +Новая игра + +[FES_NOC] +No Memory Card (PS2) in MEMORY CARD slot 1. + +[FES_NON] +ВАРИАНТЫ ОДЕЖДЫ ОТСУТСТВУЮТ + +[FES_SAG] +ИМЕЕТСЯ + +[FES_SCG] +Сохранить текущую игру? + +[FES_SET] +ПЕРЕОДЕТЬСЯ + +[FES_SG] +НАЧАТЬ НОВУЮ ИГРУ + +[FES_SKN] +НАЗВАНИЕ ОДЕЖДЫ + +[FES_SLO] +ФАЙЛ ЗАПИСИ + +[FES_SNG] +НАЧАТЬ НОВУЮ ИГРУ + +[FES_SSC] +Игра успешно сохранена. + +[FES_WAR] +Идет запись, подождите... + +[FET_AMS] +НАСТРОЙКА МЫШИ + +[FET_APL] + ОК? + +[FET_APP] +ЛЕВАЯ КНОПКА МЫШИ ИЛИ ВВОД ДЛЯ ПРИНЯТИЯ ИЗМЕНЕНИЙ + +[FET_AUD] +НАСТРОЙКА ЗВУКА + +[FET_BRE] +СООБЩЕНИЯ + +[FET_CAC] +ДЕЙСТВИЯ + +[FET_CCN] +УПРАВЛЕНИЕ: СТАНДАРТНОЕ + +[FET_CCR] +В МАШИНЕ + +[FET_CFT] +ПЕШКОМ + +[FET_CIG] +ЗАБОЙ - УБРАТЬ КЛАВИШИ, ЛЕВАЯ КНОПКА МЫШИ ИЛИ ВВОД - ЗАДАТЬ + +[FET_CME] +ТИП УПРАВЛЕНИЯ + +[FET_CON] +СОЕДИНЕНИЕ + +[FET_CTI] +СТАНДАРТНЫЙ ТИП УПРАВЛЕНИЯ + +[FET_CTL] +НАСТРОЙКА УПРАВЛЕНИЯ + +[FET_DAM] +ДИНАМИЧЕСКАЯ МОДЕЛЬ АКУСТИКИ + +[FET_DEF] +СТАНДАРТНЫЕ НАСТРОЙКИ + +[FET_DG] +УДАЛЕНИЕ ИГРЫ + +[FET_DIS] +НАСТРОЙКА ДИСПЛЕЯ + +[FET_DSN] +Обычная}одежда.bmp + +[FET_EIG] +ДЛЯ ЭТОГО ДЕЙСТВИЯ НЕЛЬЗЯ ЗАДАТЬ КНОПКУ + +[FET_FG] +НАЙТИ ИГРУ + +[FET_FIL] +Фильтр + +[FET_GT] +ТИП ИГРЫ + +[FET_HG] +СОЗДАТЬ ИГРУ + +[FET_HRD] +УСТАНОВЛЕНЫ СТАНДАРТНЫЕ НАСТРОЙКИ + +[FET_JG] +Подключиться + +[FET_LAN] +ВЫБОР ЯЗЫКА + +[FET_LG] +ЗАГРУЗКА ИГРЫ + +[FET_MAP] +ВЫБОР КАРТЫ + +[FET_MIG] +ВЫБОР ПАРАМЕТРА СТРЕЛКАМИ ВЛЕВО, ВПРАВО И КОЛЕСИКОМ МЫШИ + +[FET_MP] +ИГРА ПО СЕТИ + +[FET_MST] +РУЛИТЬ МЫШЬЮ + +[FET_MTI] +ЗАДАНИЕ ПАРАМЕТРОВ МЫШИ + +[FET_NG] +НОВАЯ ИГРА + +[FET_NON] +НЕТ ДОСТУПНЫХ ИГР + +[FET_OPT] +НАСТРОЙКИ + +[FET_PAU] +ПАУЗА В ИГРЕ + +[FET_PS] +ВЫБОР ОДЕЖДЫ + +[FET_PSU] +ВЫБОР ОДЕЖДЫ + +[FET_QG] +ВЫХОД + +[FET_RDK] +ЗАДАТЬ УПРАВЛЕНИЕ + +[FET_REF] +Частота обновления + +[FET_RIG] +ВЫБЕРИТЕ КНОПКУ ДЛЯ ЭТОГО ДЕЙСТВИЯ ИЛИ НАЖМИТЕ ESC ДЛЯ ОТМЕНЫ + +[FET_RSC] +НЕВОЗМОЖНО УСТАНОВИТЬ ЭТОТ ПАРАМЕТР + +[FET_RSO] +ВОССТАНОВЛЕНЫ СТАНДАРТНЫЕ НАСТРОЙКИ + +[FET_SAN] +НАЧАТЬ НОВУЮ ИГРУ + +[FET_SCN] +УПРАВЛЕНИЕ: ОБЫЧНОЕ + +[FET_SFG] +ПОИСК ИГР... + +[FET_SG] +СОХРАНЕНИЕ ИГРЫ + +[FET_SGA] +НАЧАТЬ ИГРУ + +[FET_SNG] +НАЧАТЬ НОВУЮ ИГРУ + +[FET_SP] +ОДИН ИГРОК + +[FET_SRT] +СОРТИРОВКА ИГР... + +[FET_STA] +СТАТИСТИКА + +[FET_STI] +ОБЫЧНЫЙ ТИП УПРАВЛЕНИЯ + +[FE_INIP] +Инициализация и загрузка меню... Подождите. + +[FIL_FLT] +ФИЛЬТР СПИСКА ИГР + +[FIL_MAP] +Карта: + +[FIL_PNG] +Пинг: + +[FIL_SPC] +Доступна игра одному? + +[FIL_SRV] +Создатель: + +[FIL_TYP] +Тип игры: + +[FIRETRK] +Пожарная + +[FIRE_M] +'ПОЖАРНЫЙ' + +[FIRE_WS] +Пожарник погиб + +[FIRST] +~g~первый + +[FLASHB] +Flashback FM + +[FLATBED] +Флетбед + +[FLFSGF] +Fill First File With Guff + +[FM1] +'ПРОГУЛКА' + +[FM1_1] +~g~Вернись назад в Стретч! + +[FM1_10] +~g~Ты проехал мимо Марии, возвращайся и посади ее в машину. + +[FM1_2] +~g~Залезай в Стретч! + +[FM1_3] +~r~Вернись за Марией, иначе Сальваторе с тобой рассчитается. + +[FM1_4] +~g~Ты бросил дочь дона! Возвращайся на склад и жди Марию! + +[FM1_5] +~g~Отвези Марию назад к Сальваторе! + +[FM1_6] +~g~Чико не будет ждать вечно! Отвези Марию на берег! + +[FM1_7] +~r~Мария мертва! Сальваторе это не понравится... + +[FM1_8] +~r~Ты замочил дилера Марии! + +[FM1_9] +~g~Там какая-то гулянка, высади Марию неподалеку. + +[FM1_A] +~w~Мне с приятелями нужно поговорить о деле, + +[FM1_AA] +~w~Я лучше пойду. Надеюсь, увидимся попозже. + +[FM1_B] +~w~Этим вечером тебе придется присматривать за моей девочкой. + +[FM1_C] +~w~ЭЙ МАРИЯ! ПОДНИМАЙ СВОЮ ЗАДНИЦУ! + +[FM1_D] +~w~Эту глупую девку постоянно приходится звать. + +[FM1_E] +~w~А вот и она, единственная и неповторимая королева красоты! + +[FM1_F] +~w~Чем ты там занималась? + +[FM1_G] +~w~Наверняка просаживала мои деньги. + +[FM1_H] +~w~Ну ты же не хочешь, чтобы я крутилась тут и мешала вашему разговору? + +[FM1_I] +~w~Заткнись и иди в машину. + +[FM1_J] +~w~Возьми мой лимузин, но верни его в целости и сохранности, понял? + +[FM1_K] +~w~И смотри чтобы она не влипла в историю. + +[FM1_L] +~w~Да, да, да! Я думаю, твой новый пес может обо мне позаботиться - + +[FM1_M] +~w~вон он какой здоровый. + +[FM1_N] +~w~Эй, Тузик, давай разживемся гостинцами у Чико! + +[FM1_O] +~w~Я думаю, он где-то на железнодорожной станции, у берега Чайнатауна. + +[FM1_P] +~g~А вот и Чико, давай рули к нему. + +[FM1_Q] +~w~Привет, Мария! Прелесть моя! + +[FM1_Q1] +~w~Хочешь поразвлечься? Ну немного... а? Может СПАНКа? + +[FM1_R] +~w~Привет, Чико. Нет, я как обычно. + +[FM1_S] +~w~Держи, золотце. + +[FM1_S1] +~w~Эй, может ты заглянешь ко мне на вечеринку на складе к востоку от пристаней Атлантик. + +[FM1_SS] +~r~РАЦИЯ: ~g~Четыре-пять всем подразделениям: Требуется помощь в облаве на наркоторговцев у пристаней Атлантик... + +[FM1_T] +~w~Спасибо Чико. До скорого. + +[FM1_TT] +~w~ЭТО ОБЛАВА! + +[FM1_U] +~w~Благодарствую, наслаждайтесь. Это хороший товар. + +[FM1_V] +~w~Давай, Тузик, скатаемся на его вечеринку! + +[FM1_W] +~w~Хорошо, Тузик, присматривай за тачкой и жди меня, а я пока пойду подвигаю попой. + +[FM1_X] +~w~Эй, Тузик, давай сматываться отсюда. Ихууу! + +[FM1_Y] +~w~Знаешь, я уже давно так не веселилась, и ты хорошо со мной обращался. С уважением и все такое. + +[FM2] +'СТРИЖКА ТРАВЫ' + +[FM21] +'БОМБА НА БАЗЕ: ЧАСТЬ I' + +[FM2_1] +~g~А вот и Кудрявый Боб! + +[FM2_10] +~r~Кудрявый слинял! + +[FM2_11] +~g~Встань у клуба Луиджи, Кудрявый Боб скоро выйдет. + +[FM2_12] +~r~Он улизнул от тебя! + +[FM2_14] +~r~Ты подошел слишком близко к Кудрявому и спугнул его! + +[FM2_15] +~g~Не приближайся к Кудрявому, а то он заподозрит неладное! + +[FM2_16] +ИСПУГ БОБА: + +[FM2_2] +~g~Кудрявый вышел из клуба, начинай следить! + +[FM2_5] +~g~Отвези его в гавань Портланда. + +[FM2_6] +~r~Кудрявый не сядет в разбитое такси! + +[FM2_7] +~r~Кудрявый испугался! Оне не пойдет на встречу! + +[FM2_8] +~g~Разделайся с Кудрявым Бобом! + +[FM2_9] +~r~Кудрявый Боб мертв! + +[FM2_A] +Колумбийский картель производит СПАНК где-то в городе, + +[FM2_B] +У нас завелась крыса! + +[FM2_C] +Телками или дурью он не торгует - значит, стучит. + +[FM2_F] +А вот и наш дружок - мистер Болтун. + +[FM2_G] +За тобой следили? Лучше, чтобы никто не знал про наш маленький секрет. + +[FM2_H] +Нет..нет, за мной нет хвоста. Дурь у тебя? + +[FM2_I] +Вот твой СПАНК, нытик, теперь выкладывай. + +[FM2_J] +Оставь нас одних, нам нужно поговорить. + +[FM2_K] +но мы не знаем где, а вот они, похоже, знают о каждом нашем шаге, еще до того как мы его сделаем. + +[FM2_L] +Есть один парень - Кудрявый Боб, он работает в баре у Луиджи. + +[FM2_M] +Он тратит больше бабок, чем зарабатывает. + +[FM2_N] +Домой он обычно ездит на такси. Выследи его, + +[FM2_O] +и, если он и есть та самая крыса... прикончи его. + +[FM2_P] +В общем, так: семья Леоне воюет на два фронта. + +[FM2_Q] +Они дерутся за сферы влияния с Триадой, но пока ни одна из сторон не уступает. + +[FM2_R] +А тем временем Джоуи Леоне пустил немного крови Форелли. + +[FM2_S] +Каждый день они теряют людей и свое влияние в городе. + +[FM2_T] +Сальваторе становится опасным параноиком. Он всюду видит одни заговоры. + +[FM2_U] +С такими преданными людьми, как ты, ему не о чем беспокоиться. + +[FM3] +'БОМБА НА БАЗЕ: ЧАСТЬ II' + +[FM3_4] +~g~Останови тачку и дай Лысому выйти! + +[FM3_7] +~r~Лысого замочили! + +[FM3_8] +~r~Охранники подняли тревогу! + +[FM3_8A] +~w~Здорово, друган! Сальваторе мне уже звонил. + +[FM3_8B] +~w~Для этой работенки потребуется много взрывчатки. + +[FM3_8C] +~w~Мне нужно $100,000, чтобы покрыть расходы, + +[FM3_8D] +~w~но ты же знаешь, что я отработаю каждый доллар. + +[FM3_8E] +~w~Окей, давай провернем это дельце! + +[FM3_8F] +~w~Я бы взорвал эту крошку, но пока еще не могу удержать оружие в руках. + +[FM3_8G] +~w~Вот, это ружьишко поможет тебе снести несколько голов! + +[FM3_8I] +~w~Займи удобную позицию, я пойду сразу, после твоего первого выстрела. + +[FM3_A] +~w~Мы должны убрать этих колумбийских ублюдков, + +[FM3_B] +~w~но эта война с Триадой сильно истощила наши ряды. + +[FM3_C] +~w~У Картеля очень много зелени, полученной с продажи этого СПАНКа. + +[FM3_CC] +~w~Браток, приходи, когда наскребешь бабок. + +[FM3_D] +~w~Если мы пойдем в открытую атаку, то они просто размажут нас по стенке. + +[FM3_E] +~w~Похоже, они производят СПАНК на том большом корабле, к которому тебя вывел Кудрявый. + +[FM3_F] +~w~Так что нам придется пораскинуть мозгами. Твоими мозгами. + +[FM3_G] +~w~Я, Сальваторе Леоне, лично прошу тебя сделать мне одолжение и уничтожить эту фабрику по производству СПАНКа. + +[FM3_H] +~w~Если ты провернешь для меня это дело - проси все что пожелаешь. + +[FM3_I] +~w~Иди к Лысому. Без его помощи тебе эту работу не сделать. + +[FM4] +'ПОСЛЕДНЯЯ ПРОСЬБА' + +[FM4_1] +Это Мария. Эта машина - ловушка! Давай встретимся южнее моста Каллахан. + +[FM4_2] +Послушай, Сальваторе думает что мы за его спиной крутим роман, + +[FM4_3] +поэтому он заключил с картелем сделку и заложил тебя. + +[FM4_4] +Я не могла этого допустить, ведь тебя бы прикончили, + +[FM4_4B] +и это была бы моя вина... потому что я сказала, что мы были вместе. + +[FM4_5] +Я сама не знаю, почему я это ему сказала. + +[FM4_6] +Похоже теперь за тобой будет охотиться мафия, да и за мной, видимо, тоже. + +[FM4_6B] +Я уже устала от убийств. От этих рек крови! + +[FM4_7] +Это моя хорошая знакомая, она старый друг... это Асука, она из тех, кому можно доверять. + +[FM4_8] +Пошли, хватит распинаться. + +[FM4_9] +Нам лучше убираться, пока здесь не появились наши старые друзья, которым уже нельзя доверять. + +[FM4_A] +~w~А, мой лучший чистильщик. + +[FM4_B] +~w~Я горжусь тобой, мальчик, ты выбил дерьмо из этих жирных свиней. + +[FM4_C] +~w~Но выполни еще одно маленькое задание, прежде чем мы отпразднуем победу. + +[FM4_D] +~w~Рядом с клубом Луиджи стоит одна машина. + +[FM4_E] +~w~Внутри все залито кровью. + +[FM4_F] +~w~Мы вправили одному парню мозги, но получилось не очень аккуратно. + +[FM4_H] +~w~Отгони машину в утилизатор, чтобы не смущать копов. + +[FORMEN] +Format Menu + +[FORMM1] +FormatMemCard 1 (teststuff) + +[FOURTH] +~g~четвертый + +[FRANGO] +~g~Сальваторе хочет, чтобы ты сперва помог Тони разобраться с Триадой! + +[FRANK] +ЗАДАНИЯ САЛЬВАТОРЕ + +[FRENCH] +Французский + +[FTUTOR] +Нажми кнопку ~h~~k~~TOGGLE_SUBMISSIONS~~w~, чтобы заняться работой пожарного, или отказаться от нее. + +[FTUTOR2] +Нажми кнопку ~h~~k~~TOGGLE_SUBMISSIONS~~w~, чтобы заняться работой пожарного, или отказаться от нее. + +[F_CANC] +~r~Задание пожарного прервано! + +[F_EXTIN] +ПОЖАРЫ: + +[F_FAIL1] +Задание не выполнено. + +[F_FAIL2] +~r~Ты опоздал! + +[F_PASS1] +Огонь потушен! + +[F_RANGE] +~g~Рация в машине не ловит сигнал, подъедь поближе к пожарной части! + +[F_START] +~g~Место возгорания машины: ~a~. Езжай и сбей огонь. + +[F_WASTE] +Женщин убито + +[GAMEOVR] +ИГРА ОКОНЧЕНА + +[GAMSET] +Настройки игры + +[GAM_FM] +Радио Game FM + +[GARAGE] +Поставь машину в гараж и выйди наружу. + +[GARAGE1] +~g~Вылазь из машины и выходи из гаража. + +[GA_1] +Ух, ты! Я не трогал ничего БОЛЕЕ горячего! + +[GA_10] +Отлично. Вот тебе ~1~ баксов. + +[GA_11] +У нас уже есть такая тачка. Больше нам уже не нужно! + +[GA_12] +Бомба активирована + +[GA_13] +Отличная работа! Пригонишь остальные - получишь вознаграждение. + +[GA_14] +Все тачки. КРУТО! Вот тебе за это. + +[GA_15] +Надеюсь, тебе понравится этот цвет. + +[GA_16] +Перекраска бесплатно. + +[GA_19] +Нам такая колымага не нужна. + +[GA_1A] +Возвращайся, когда будешь свободен... + +[GA_2] +Машина отремонтирована и перекрашена. Теперь копы тебя не узнают! + +[GA_20] +У нас этого хлама больше чем нужно. Прости, сделка не состоится. + +[GA_21] +Больше в этот гараж поставить машин нельзя. + +[GA_3] +Подарков больше не будет. Окраска стоит $1000! + +[GA_4] +Установка бомбы в машину стоит $1000 + +[GA_5] +Я уже поставил в бомбу в эту тачку. + +[GA_6] { re3 change } +Припаркуй тачку на место, нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~ и ДЕЛАЙ НОГИ! + +[GA_6B] { re3 change } +Припаркуй тачку на место, нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~ и ДЕЛАЙ НОГИ! + +[GA_7] { re3 change } +Включи бомбу, нажав на ~h~~k~~VEHICLE_FIREWEAPON~~w~. Бомба взорвется как только заведется мотор. + +[GA_7B] { re3 change } +Включи бомбу, нажав на ~h~~k~~VEHICLE_FIREWEAPON~~w~. Бомба взорвется как только заведется мотор. + +[GA_8] +Взорви бомбу с помощью детонатора. + +[GA_9] +Ты собрал ~1~ из 10 особых тачек + +[GERMAN] +German + +[GHOST] +Призрак + +[GMLOAD] +ПРОДОЛЖИТЬ ИГРУ + +[GMREST] +Перезапуск игры + +[GMSAVE] +СОХРАНЕНИЕ ИГРЫ + +[GMSTOR] +Game Store + +[GMSVLQ] +GAME SAVE-LOAD-QUIT + +[GNG_WST] +Убито членов банд + +[GOAWAY] +~g~Ты уже на задании! + +[GOBACK] +GoBack + +[GORLEV] +Уровень крови + +[GREN_1] +Чем дольше ты держишь ~h~~k~~PED_FIREWEAPON~~w~, тем дальше ты бросишь гранату. + +[GREN_2] +Чем дольше ты держишь ~h~~k~~PED_FIREWEAPON~~w~, тем дальше ты бросишь гранату. + +[GREN_3] +Чем дольше ты держишь ~h~~k~~PED_FIREWEAPON~~w~, тем дальше ты бросишь гранату. + +[GTAB_A] +Эй, давайте уберите это отсюда. Черт его знает, что это такое, + +[GTAB_B] +но раз он так хотел это заполучить, то это, наверное, что-то ценное. + +[GTAB_C] +Что за черт! + +[GTAB_D] +ТЫ! + +[GTAB_E] +Успокойся, амиго! Де нада! Де нада! + +[GTAB_F] +Я порежу тебя и брошу в канаву истекать кровью! + +[GTAB_G] +Не стреляй, амиго. Без базара. Мы все друзья. Вот, забирай. + +[GTAB_H] +Не будь таким засранцем! + +[GTAB_I] +У нас нет выбора, крошка! + +[GTAB_J] +Выбор есть всегда, ты чертов ублюдок! + +[GTAB_K] +Прости, это все эта бешеная сучка, это все она...пор фавор? + +[GTAB_L] +Итак, этой сучки больше нет. + +[GTAB_M] +Но ты оказал мне услугу, + +[GTAB_N] +ты не единственный, у кого есть счеты с Картелем, + +[GTAB_O] +эта мразь убила моего брата! + +[GTAB_P] +Я никогда не убивал якудза! + +[GTAB_Q] +ЛЖЕЦ. Мы все видели убийцу Картеля. + +[GTAB_R] +Мы выследим и убьем всех вас, колумбийских собак! + +[GTAB_S] +Я поиграю немного с твоим другом, чтобы получить информацию и доставить себе удовольствие. + +[GTAB_T] +Я жду тебя позже, я уверена, мне понадобятся твои услуги. + +[GTAB_U] +Пожалуйста, амиго, не оставляй меня с ней, она ненормальная! Друг? Эй, ДРУУУГ!!!... Аииииаааахххх! + +[GUN_1A] +Для смены оружия используй ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~ и ~h~~k~~PED_CYCLE_WEAPON_LEFT~. + +[GUN_2A] +Держи ~h~~k~~PED_LOCK_TARGET~~w~, чтобы ~h~прицелиться~w~, а затем нажимай ~h~~k~~PED_FIREWEAPON~~w~, чтобы выстрелить! Потренируйся на мишенях... + +[GUN_2C] +Держи ~h~~k~~PED_LOCK_TARGET~~w~, чтобы ~h~прицелиться~w~, а затем нажимай ~h~~k~~PED_FIREWEAPON~~w~, чтобы выстрелить! Потренируйся на мишенях... + +[GUN_2D] +Держи ~h~~k~~PED_LOCK_TARGET~~w~ чтобы ~h~прицелиться~w~, а затем нажимай ~h~~k~~PED_FIREWEAPON~~w~ чтобы выстрелить! Потренируйся на мишенях... + +[GUN_3A] +Чтобы сменить цель, удерживая ~h~~k~~PED_LOCK_TARGET~~w~, нажимай ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ или ~h~~k~~PED_CYCLE_TARGET_RIGHT~~w~. + +[GUN_3B] +Чтобы сменить цель, удерживая ~h~~k~~PED_LOCK_TARGET~~w~, нажимай ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ или ~h~~k~~PED_CYCLE_TARGET_RIGHT~~w~. + +[GUN_4A] +Когда ты держишь ~h~~k~~PED_LOCK_TARGET~~w~, то можешь ходить или бегать, не выпуская цель из перекрестия. + +[GUN_4B] +Когда ты держишь ~h~~k~~PED_LOCK_TARGET~~w~, то можешь ходить или бегать, не выпуская цель из перекрестия. + +[GUN_5] +Ты можешь попрактиковаться на этих мишенях в прицеливании и стрельбе. Как только закончишь - возвращайся к заданию. + +[HARWOOD] +Харвуд + +[HEAD] +Head Radio + +[HEALTH] +ЦЕЛОСТНОСТЬ: + +[HEALTH1] +Проваливай! Ты полностью здоров. + +[HEALTH2] +Лечение не бесплатное. + +[HEALTH3] +Сейчас я тебя подлатаю. + +[HEALTH4] +Это будет стоить тебе $250. + +[HEAL_A] +Твое ~h~здоровье~w~ отображается оранжевыми цифрами в верхнем правом углу экрана. + +[HEAL_B] +Когда ты ~h~вырубаешься,~w~ тебя отвозят в ближайший госпиталь + +[HEAL_C] +При этом ты теряешь все свое оружие и доктора берут с тебя немного денег за лечение. + +[HEAL_E] +Во время игры ты найдешь способы, как подлечиться или сберечь свое здоровье. + +[HED_EX] +Голов расколото + +[HELI] +Вертолет + +[HELP1] +Остановите машину в центре голубого круга. + +[HELP10] +Эти значки показывают, насколько сильно стремление копов арестовать тебя. + +[HELP11] +Чем больше этих значков, тем более усердно тебя преследует полиция. + +[HELP12] +Войди в голубой круг, чтобы получить новое задание. + +[HELP13] +Иногда тебе придется ехать по дорогам, которые не показаны на радаре. + +[HELP14] +Чтобы подобрать оружие, тебе нужно к нему подойти. Сидя в машине, ты оружие не возьмешь. + +[HELP15] +Когда ты без машины, то, нажав ~h~~k~~PED_LOOKBEHIND~~w~, можешь ~h~оглянуться назад~w~. + +[HELP2_A] +Чтобы ~h~разогнаться~w~, держи ~h~кнопку /~w~ во время бега. + +[HELP3] +Мчаться ты можешь лишь до тех пор, пока не устанешь. + +[HELP4_A] +Чтобы ~h~поехать вперед~w~, держи кнопку ~h~ ~k~~VEHICLE_ACCELERATE~~w~. + +[HELP4_D] +Push the~h~ right analog stick~w~ up to ~h~accelerate. + +[HELP5_A] +Держи кнопку~h~ ~k~~VEHICLE_BRAKE~~w~, чтобы ~h~затормозить~w~, или ~h~поехать назад~w~ после того, как машина остановится. + +[HELP5_D] +Pull the ~h~right analog stick~w~ back to ~h~brake~w~, or to ~h~reverse~w~ if the vehicle has stopped. + +[HELP6_A] +Держи кнопку~h~ ~k~~VEHICLE_HANDBRAKE~~w~, чтобы воспользоваться ~h~ручным тормозом. + +[HELP6_C] +Держи кнопку~h~ ~k~~VEHICLE_HANDBRAKE~~w~, чтобы воспользоваться ~h~ручным тормозом. + +[HELP6_D] +Держи кнопку~h~ ~k~~VEHICLE_HANDBRAKE~~w~, чтобы воспользоваться ~h~ручным тормозом. + +[HELP7_A] +Нажми и удерживай~h~ ~k~~PED_LOCK_TARGET~~w~ кнопку, чтобы ~h~прицелиться~w~ из снайперской винтовки. + +[HELP7_D] +Нажми и удерживай~h~ ~k~~PED_LOCK_TARGET~~w~ кнопку, чтобы ~h~прицелиться~w~ из снайперской винтовки. + +[HELP8_A] +Нажми ~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, чтобы ~h~увеличить ~w~ изображение или ~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, чтобы ~h~уменьшить~w~ его при прицеливании из снайперской винтовки. + +[HELP8_B] +Нажми ~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, чтобы ~h~увеличить ~w~ изображение или ~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, чтобы ~h~уменьшить~w~ его при прицеливании из снайперской винтовки. + +[HELP9_A] +Нажми ~h~ ~k~~PED_FIREWEAPON~~w~, чтобы ~h~выстрелить~w~ из снайперской винтовки. + +[HELP9_B] +Нажми ~h~ ~k~~PED_FIREWEAPON~~w~, чтобы ~h~выстрелить~w~ из снайперской винтовки. + +[HELP9_C] +Нажми ~h~ ~k~~PED_FIREWEAPON~~w~, чтобы ~h~выстрелить~w~ из снайперской винтовки. + +[HEL_DST] +Сбито вертолетов + +[HEY] +~g~Не ходи в одиночку, иди с компанией! + +[HEY2] +~g~Не разделяйтесь, идите вместе! + +[HEY3] +~g~Ты потерял своего напарника, вернись к Лысому! + +[HEY4] +~g~Потеряешь Мисти - к Луиджи лучше не возвращайся! Иди и найди ее! + +[HEY5] +~g~Для полного кайфа не хватает еще одной телки. Сгоняй за ней! + +[HEY6] +~g~Твоя честь зависит от якудза Канбу. Ты должен защищать его! + +[HEY7] +~g~Возможно стоит получше вооружиться. Иди и забери своего напарника! + +[HEY8] +~g~Защита - это значит 'защищать Старого Азиата'! + +[HEY9] +~g~Хочешь совет? Иди, встреться с напарником! + +[HJSTAT] +Дальность: ~1~.~1~м Высота: ~1~.~1~м Переворотов: ~1~ Поворот на: ~1~_ + +[HJSTATF] +Дальность: ~1~ft Высота: ~1~ft Переворотов: ~1~ Поворот на: ~1~_ + +[HJSTATW] +Дальность: ~1~.~1~м Высота: ~1~.~1~м Переворотов: ~1~ Поворот на: ~1~_ И отличное приземление! + +[HJSTAWF] +Дальность: ~1~ft Высота: ~1~ft Переворотов: ~1~ Поворот на: ~1~_ И отличное приземление! + +[HJ_DIS] +ПРИЗ ЗА ДВОЙНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_IS] +ПРИЗ ЗА БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_PDIS] +ПРИЗ ЗА ИДЕАЛЬНЫЙ ДВОЙНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_PIS] +ПРИЗ ЗА ИДЕАЛЬНЫЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_PQIS] +ПРИЗ ЗА ИДЕАЛЬНЫЙ ЧЕТВЕРНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_PTIS] +ПРИЗ ЗА ИДЕАЛЬНЫЙ ТРОЙНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_QIS] +ПРИЗ ЗА ЧЕТВЕРНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_TIS] +ПРИЗ ЗА ТРОЙНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HM1_1] +~g~Прикончи 20 Пурпурных Девяток за 2 минуты 30 секунд. + +[HM1_2] +~g~Возьми тачку, но не забывай, что из машины можно стрелять лишь из Узи! + +[HM1_3] +~g~'Девятки' любят шататься в Садах Вичита. + +[HM1_A] +Йо! Это ДиАйс из Красных Валетов! + +[HM1_B] +Тут кое кто решил со мной поиграть. + +[HM1_C] +У этих молодых панков, что заполонили весь город, головы забиты лишь СПАНком и оружием. + +[HM1_D] +'Девятка' - это их знак, они придумали себе пурпурный флаг и готовы трясти им целыми днями... + +[HM1_E] +Я хочу, чтобы ты показал этим малолетним ублюдкам, как работают профессионалы. + +[HM1_F] +Будь поосторожней. На улицах могут оказаться Валеты, которые решат, что ты и их решил заодно мочкануть! + +[HM1_G] +эти засранцы начинают теснить 'Валетов'. + +[HM1_H] +Сделай так, чтобы этих Девяток здесь не было! + +[HM2_1] { re3 change } +Используй радиоуправляемые машинки, чтобы подорвать броневики. Взрыв бомбы - ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[HM2_1A] { re3 change } +Используй радиоуправляемые машинки, чтобы подорвать броневики. Взрыв бомбы - ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[HM2_2] +~r~Ты так и не смог подорвать все броневики! + +[HM2_3] +Если машинка стукнется о колесо, то она взорвется! + +[HM2_4] +Если машинка окажется вне действия радиоуправления, то она взорвется! + +[HM2_5] +~r~Вне зоны приема сигнала! + +[HM2_6] +~g~Броневик уничтожен! + +[HM2_A] +Эти Девятки достали меня. + +[HM2_B] +Уроды раздобыли где-то броневики и развозят на них СПАНК... + +[HM2_C] +и без страха впаривают его желающим. + +[HM2_D] +Выше по улице припаркована тачка. + +[HM2_E] +В ней для тебя я кое-что припас, чтобы ты смог проучить этих тупиц... + +[HM2_F] +и раздолбать их бронированные игрушки. + +[HM3_1] +~g~Двигай к гаражу, но смотри, если сильно помнешь тачку - она взорвется! + +[HM3_2] +~g~Забирай тачку, теперь она в полном порядке! + +[HM3_3] +~g~Отремонтируй тачку! + +[HM3_A] +Какая-то сволочь установила мне бомбу в тачку. + +[HM3_B] +Если я потеряю колеса, то лишусь своей репутации в районе. + +[HM3_C] +Давай отгони мою колымагу в гараж на Сент-Марк, ты понял, йо. + +[HM3_D] +Как снять бомбу - это уже пусть они сами думают. + +[HM3_E] +Часы уже тикают, давай, гони. + +[HM3_F] +Но учти, одного столкновения достаточно, чтобы взлететь на воздух. + +[HM3_G] +Ну же, вперед! + +[HM4_1] +~g~Отправляйся к месту крушения самолета, тебе нужно собрать не менее 30 слитков. + +[HM4_2] +~g~Запомни, по мере заполнения тачка будет тяжелеть и хуже слушаться руля, съезди в гараж и вывали там груз. + +[HM4_A] +Йо, рейсовый самолет, перевозящий федеральные деньги разбился в аэропорту Фрэнсис. + +[HM4_B] +Слитки платины валяются по всей взлетной полосе. + +[HM4_C] +Бери тачку и сопри, сколько сможешь. + +[HM4_D] +~g~Возьми тачку! + +[HM4_E] +TEXT NO LONGER REQUIRED + +[HM4_F] +Ты можешь возить слитки в один из моих гаражей. + +[HM4_G] +Эти слитки чертовски тяжелые и будут замедлять движение. + +[HM4_H] +Так что не забывай почаще сваливать груз. + +[HM5_1] +Йо, Айс сказал ты придешь. Правила просты. Только биты. Никаких пушек, никаких машин. + +[HM5_3] +~r~Тебе же сказали, что можно пускать в ход лишь биту! + +[HM5_4] +~r~Твой напарник мертв! + +[HM5_5] +Эта драка для поднятия авторитета, ты въехал? + +[HM5_6] +Пошли, расколем несколько черепушек... + +[HM5_A] +Эти Девятки уже в полном дерьме... + +[HM5_B] +но им не терпится поквитаться. + +[HM5_C] +Они согласились прийти на разборку. + +[HM5_D] +Их банда против нас двоих, или точнее... + +[HM5_E] +вас двоих, + +[HM5_F] +я с тобой идти не могу... + +[HM5_G] +мне еще три месяца нельзя светиться, у меня условное, + +[HM5_H] +ты уловил, о чем я? + +[HM5_I] +Мой младшенький встретит тебя, + +[HM5_J] +он и покажет, где намечается устроить разборку, успехов, сынок. + +[HM_1] +'РАБОТА ДЛЯ УЗИ' + +[HM_2] +'ИГРУШЕЧНАЯ ВОЙНА' + +[HM_3] +'ТАЧКА С БОМБОЙ' + +[HM_4] +'МАННА НЕБЕСНАЯ' + +[HM_5] +'РАЗБОРКА' + +[HOOD] +ЗАДАНИЯ КАПЮШОНОВ + +[HOOD1_A] +Сними трубку телефона стоящего в Садах Вичита, и мы обсудим одно дело. + +[HOODGO] +~g~Сейчас у Капюшонов нет заданий! + +[HOODSCR] +Рампо XL Капюшонов + +[HORN] +~g~Посигналь. + +[HORN1] +Press the ~h~L3 button ~w~to activate the ~h~horn. + +[HORN2] +Press the ~h~L1 button ~w~to activate the ~h~horn + +[HORN3] +Press the ~h~R1 button ~w~to activate the ~h~horn + +[HOSPI_2] +Рокфорд + +[IDAHO] +Айдахо + +[IMPEXPP] +Автосалон в Портланде. Есть заказы на различные машины. Подробности - на нашей доске объявлений. + +[IMPORT1] +Выйди наружу и жди, когда тебе подадут тачку. + +[IND_ZON] +Портланд + +[INFERNS] +Инфернус + +[INSTUN] +Обычный безумный трюк + +[IN_BOAT] +~g~Без катера тебе это задание не выполнить! + +[IN_ROW] +Приз за ~1~ ПОДРЯД! $~1~ + +[IN_VEH] +~g~Эй! Ты куда? Давай садись за руль! + +[IN_VEH2] +~g~Без машины тебе это задание не выполнить! + +[ITALIA] +Italian + +[JAILB_A] +Утром полиция и служба спасения напряженно работали над устранением + +[JAILB_B] +чрезвычайной ситуации, вызванной нападением на полицейский конвой. + +[JAILB_C] +У нас пока нет никаких сведений ни о том, кого сегодня перевозили, + +[JAILB_D] +ни о том, какая преступная группировка ответственна за это дерзкое нападение. + +[JAILB_E] +Как обычно, ранним утром полицейский конвой покинул участок, + +[JAILB_F] +чтобы перевезти заключенных в городскую тюрьму. + +[JAILB_G] +Нападение произошло на мосту Каллахан. + +[JAILB_H] +Большинство свидетелей преступления погибло при взрыве, который сильно повредил мост. + +[JAILB_I] +Полиция полагает, что при взрыве также могли погибнуть + +[JAILB_J] +и некоторые заключенные. + +[JAILB_K] +Выяснение личностей сбежавших преступников осложнено + +[JAILB_L] +из-за атаки хакеров, испортивших базу данных полиции. + +[JAILB_M] +Мэр ОТДонован объяснил, что полиция + +[JAILB_N] +* + +[JAILB_O] +Из-за этой катастрофы район Портланда оказался изолирован от города, + +[JAILB_P] +так как прокладка туннеля Портер все еще не завершена. + +[JAILB_Q] +Ну же, открывайте! + +[JAILB_R] +А, мистер членоголовый! + +[JAILB_S] +Я бы мог спокойно тебя пристрелить. + +[JAILB_T] +Вы еще об этом пожалеете. + +[JAILB_U] +Эй, вы, давайте, смывайтесь. + +[JAILB_V] +Либерти Сити был потрясен сегодняшним преступлением. + +[JAILB_W] +Полиция считает, что это нападение - несомненно, дело рук профессионалов. + +[JAILB_X] +По предварительным данным + +[JAN] +Янв + +[JM1] +'ПОСЛЕДНИЙ УЖИН ГУБАСТОГО' + +[JM1_1] +~g~Отгони машину Форелли в гараж к Лысому, он на севере отсюда за 'Автомобилями в кредит'. + +[JM1_2] +~g~Припаркуй тачку на стоянке у бистро Марко. + +[JM1_3] +~g~Включи бомбу и быстрее сваливай! + +[JM1_4] +~g~Ты помял тачку! Ее нужно отремонтировать! + +[JM1_5] +~g~Ты не включил бомбу! + +[JM1_6] +~g~Поставь машину так, как она стояла раньше. + +[JM1_7] +~g~Закрой дверцу машины, а то он заподозрит неладное! + +[JM1_8A] +~y~Эй, это мой главный работник! + +[JM1_8B] +~y~Здесь все автоматизировано. Просто заезжай внутрь и жди, пока установят бомбу. + +[JM1_8C] +~y~Вот, первый раз даром, но потом плати бабки. + +[JM1_A] +Эй, мне скучно, может, потрахаемся? + +[JM1_B] +Погоди, дорогуша, мне тут нужно одно дельце провернуть. + +[JM1_C] +Для тебя, друг, есть одна работенка. + +[JM1_D] +Братья Форелли забыли мне вернуть старый должок. + +[JM1_E] +Надо бы преподать им хороший урок, чтоб все знали. + +[JM1_F] +Губастый Форелли сейчас набивает пузо в бистро на Сент-Марк, + +[JM1_G] +отгони его машину в гараж Лысого, тот что в Харвуде. + +[JM1_H] +Ты же знаком с Лысым? + +[JM1_I] +Как только он установит бомбу, верни машину туда, где взял. + +[JM1_J] +Ну а потом тебе останется только наблюдать. + +[JM1_K] +Но давай живее, толстяк не будет жрать вечно. + +[JM2] +'ПРОЩАЙ, ЧАНКИ ЛИ ЧОНГ' + +[JM2_A] +Чанки Ли Чонг толкает СПАНК новой банде из Колумбии... или Колорадо... или как там... + +[JM2_B] +Короче, я не знаю точно, да это и не важно. + +[JM2_C] +У него киоск с лапшой в центре Чайнатауна. + +[JM2_D] +Лучше бы этот крысеныш лишь свою жратву толкал. + +[JM2_E] +Я хочу, чтобы ты с ним разобрался! + +[JM2_F] +Если тебе нужен ствол, зайди во двор оружейного магазина, который рядом с метро. + +[JM2_G] +Знаешь, где этот магазинчик? Возьмешь там пистолет. + +[JM2_H] +Да, не забывай, что Чайнатаун - территория Триады, будь там поосторожней. + +[JM3] +'ОГРАБЛЕНИЕ ФУРГОНА' + +[JM3_1] +~g~Отгони фургон в гараж. + +[JM3_2] +~g~Тарань фургон до тех пор, пока не разобьешь его на 70%. + +[JM3_A] +Мы решили грабануть инкассаторский фургон. + +[JM3_B] +Он каждый день выезжает из Чайнатауна. + +[JM3_C] +Пули его броню не возьмут, так что тебе придется таранить его своей тачкой + +[JM3_D] +Хорошенько его помни, и сраный охранник сам его бросит. + +[JM3_E] +Потом отгони его на склад в доках, а мои парни сделают все остальное. + +[JM3_F] +Давай за работу. Фургон не будет вечно кататься по городу. + +[JM4] +'ШОФЕР СИПРИАНИ' + +[JM4_10] +Окей, парень. Сначала мы заглянем в прачечную в Чайнатауне, у меня там есть кое-какие дела. + +[JM4_11] +Эти прачки не желают платить крыше. + +[JM4_12] +И поаккуратней веди, Джоуи только что починил эту развалюху. + +[JM4_13] +Не превращай ее снова в груду металла, окей? + +[JM4_2] +Подожди здесь! Не глуши мотор, мало ли что может случиться. + +[JM4_3] +Это засада Триады! Парень, сваливаем отсюда! + +[JM4_4] +Триада думает, что может безнаказанно перейти мне дорогу, Триада, МНЕ! + +[JM4_5] +Зайди ко мне попозже, зададим им работенку - отстирывать с одежды собственную кровь! + +[JM4_6] +Эй, аккуратней! Я же тебе говорил. + +[JM4_7] +~g~Отвези Тони в мамочкин ресторан. + +[JM4_8] +~r~Тони загасили! + +[JM4_A] +Да, я знаю, Тони, я все отрегулировал. Она прямо мурлычет, послушай сам. + +[JM4_B] +О! А вот и тот парень, о котором я тебе говорил! + +[JM4_C] +Познакомься. Этот парень не итальянец, и не механик, но с работой справляется. + +[JM4_D] +Это Папс Капо, Тони Сиприани. + +[JM4_E] +Хай, я Тони Сиприани. + +[JM4_F] +Отвези его в мамочкин ресторан на Сент-Марк, хорошо? + +[JM4_G] +И еще, зайди ко мне попозже, я планирую одно дельце и мне понадобится хороший водила. + +[JM5] +'КАТАФАЛК СО СКУНСОМ' + +[JM5_1] +~g~Отвези его в утилизатор! + +[JM5_2] +~g~Это братья Форелли! + +[JM5_A] +Прекрасно! Просто великолепно. + +[JM5_B] +Отлично, с тобой-то я и хотел поговорить! + +[JM5_C] +В-общем, у кафе рядом с мысом Каллахан стоит катафалк со жмуриком. + +[JM5_D] +Один из братьев Форелли решил, что он слишком умный, ну и получил за это по заслугам. + +[JM5_E] +Отвези его тело к утилизатору в Харвуд, ладно? + +[JM6] +'БЕГСТВО' + +[JM6_1] +Поехали к банку по главной улице. + +[JM6_2] +Не глуши мотор, мы по-быстрому. Туда и обратно. + +[JM6_3] +Быстрее, сваливаем! + +[JM6_4] +Стряхни копов с хвоста и гони в укрытие! + +[JM6_5] +~g~Понадобится быстрая тачка, идиот! + +[JM6_6] +~g~Иди, найди менее заметную тачку! + +[JM6_7] +~g~Чтобы ограбить банк, тебе нужны все трое! + +[JM6_8] +~r~Ты потерял всех налетчиков! + +[JM6_A] +Классная будет тачка, да? + +[JM6_B] +Короче слушай. Достань тачку получше и езжай к убежищу на Сент-Марк, где подберешь моих друзей. + +[JM6_C] +Они решили взять банк, но им нужен водила. + +[JM6_D] +Я замолвил за тебя словечко, так что не напортачь. + +[JM6_E] +Отвези их к банку до пяти часов, и ни минутой позже. + +[JOEY] +ЗАДАНИЯ ЗОУИ + +[JOEYGO] +~g~Джоуи развлекается в городе вместе с Мисти. Приходи позже! + +[JUL] +Июл + +[JUN] +Июн + +[KABOOM] +БАБАХ! + +[KEMUGO] +~g~Мария и Кемури сейчас заняты. Загляни попозже! + +[KENJGO] +~g~Кенжи сейчас на собрании Якудзы. Заходи как-нибудь потом! + +[KENJI] +ЗАДАНИЯ КЕНДЖИ + +[KENSGO] +~g~Кенжи занят! Зайди попозже! + +[KGS_EXP] +Использовано взрывчатки (Кг.) + +[KILLS] +УБИТО: + +[KM1] +'ОСВОБОЖДЕНИЕ КАНБУ' + +[KM1_1] +~g~Укради полицейскую машину! + +[KM1_10] +~r~Якудза Канбу погиб - как и твоя честь! + +[KM1_11] +~r~Ты поджарил сам себя! + +[KM1_12] +~g~Отвези его к Доджо, но сперва разберись с полицейскими! + +[KM1_13] +Поставь машину в гараж! + +[KM1_2] +~g~Начини машину взрывчаткой! + +[KM1_3] +~g~Теперь вези его к Доджо Якудзы. + +[KM1_4] +~g~Для этого задания тебе понадобится полицейская машина! + +[KM1_5] +~g~Отлично, теперь вали к полицейскому участку. + +[KM1_6] +~g~Установи в машину бомбу! + +[KM1_7] +~g~Проезд только для полицейских! + +[KM1_8A] { re3 change } +Чтобы ~h~подорвать бомбу~w~, нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~, но не забудь отойти подальше от машины. + +[KM1_8D] { re3 change } +Чтобы ~h~подорвать бомбу~w~, нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~, но не забудь отойти подальше от машины. + +[KM1_9] +~r~Установленной в машине бомбой ты должен был взорвать стену! + +[KM1_A] +Моя сестра хорошо о тебе отзывается, + +[KM1_B] +Возможно, ты сможешь урегулировать одну ситуацию, которая ставит меня в неловкое положение. + +[KM1_C] +Якудза Канбу сейчас в участке и ждет отправки в суд. + +[KM1_D] +Мы благодарны тебе за такой самоотверженный поступок. Если тебе вдруг понадобится помощь, то Доджо даст тебе двух своих лучших людей. + +[KM1_E] +но я, пока что, не видел от вас, гайдзинов, ничего, кроме разочарований. + +[KM1_F] +Разумеется, в случае провала мы опозоримся. + +[KM1_G] +Он очень ценный член семьи. + +[KM1_H] +Вызволи его из заключения и отвези к Доджо на Бедфорд Поинт. + +[KM2] +'ВЕЛИКИЙ АВТОВОР' + +[KM2_1] +~g~Отремонтируй тачку, она должна сверкать. + +[KM2_2] +~g~Тачка пригнана. + +[KM2_3] +~g~Запомни, ~r~тачка~g~ должна быть в идеальном состоянии, иначе ее не примут в ~p~гараже~g~. + +[KM2_A] +Ты даже не представляешь, сколько в нашей работе внимания приходится уделять этикету. + +[KM2_B] +К моему великому стыду, один человек оказал мне услугу, а я так и не смог отплатить ему за его доброту. + +[KM2_C] +Его слабость - машины. Он просил, чтобы мы приобрели для его коллекции несколько редких экземпляров. + +[KM2_D] +Эти автомобили мы преподнесем ему как подарок, в счет погашения моего долга. + +[KM2_E] +Ты должен раздобыть эти машины и доставить в гараж за стоянкой в Ньюпорте. + +[KM2_F] +Для меня это дело чести. + +[KM3] +'ДОГОВОР' + +[KM3_1] +~g~Картель ожидает прибытия ямайцев, иди и укради тачку Ярди! Ты найдешь ее к северу отсюда, в Ньюпорте. + +[KM3_10] +~r~Напарник убит! + +[KM3_11] +~g~Картель атакован и дипломат забрать не удастся. + +[KM3_12] +~g~Убей всех колумбийцев, взорви их тачки и забери дипломат. + +[KM3_13] +~g~Отвези дипломат назад в казино. + +[KM3_14] +~r~Тебя опознали, ты напортачил! + +[KM3_2] +~g~Езжай и подбери своего напарника. + +[KM3_3] +~g~Они встречаются на автостоянке больницы в Рокфорде! + +[KM3_4] +~r~Они смылись! + +[KM3_5] +~g~Чтобы дела пошли - посигналь. + +[KM3_6] +~g~Убей их, убей их всех! + +[KM3_7] +Это подстава Якудзы! + +[KM3_8] +~g~Чтобы выполнить эту работу, тебе нужна тачка Ярди! + +[KM3_9] +~r~Один из Колумбийцев мертв, дело не выгорит. + +[KM3_A] +Дурак подставляет опасности спину, а умный смотрит ей в лицо. + +[KM3_B] +Колумбийский Картель проигнорировал наши многочисленные требования убраться из нашей сферы влияния. + +[KM3_C] +Сейчас они договариваются о сотрудничестве с Ямайцами, чтобы сообща выступить против нас. + +[KM3_D] +Сейчас в городе они ведут последнюю стадию переговоров. + +[KM3_E] +Это дело чести, чтобы ты их там всех порешил. + +[KM3_F] +Возьми моего человека, укради машину Ярди, иди и окажи уважение Колумбийцам. + +[KM4] +'ШИМА' + +[KM4_1] +Мне нечем платить, но я не стал бы платить, даже если бы мог! + +[KM4_10] +Да какие ВЫ к черту якудза...? + +[KM4_11] +~g~Верни деньги назад в казино! + +[KM4_2] +От вас никакого толка. + +[KM4_3] +Я не за этим вам плачу. Если бы мне нужна была такая защита, я обратился бы к этим сраным копам. + +[KM4_4] +~g~Разберись с этой бандой и верни ~b~дань~g~, принадлежащую Якудзе! + +[KM4_5] +Дональд Лав желает принять тебя в своем чайном саду, и обсудить кое-какие дела. + +[KM4_6] +Вот деньги, все как договаривались! + +[KM4_7] +~r~Хозяин магазина сдох! + +[KM4_8] +~g~Ты взял чемоданчик! + +[KM4_9] +Какие-то юные бандиты обчистили это место! Они забрали все что было! + +[KM4_A] +Чтобы быть действительно сильным, важно никогда не проявлять слабость. + +[KM4_B] +Дела идут довольно успешно, сегодня как раз нужно собрать дань. + +[KM4_C] +Иди и немедленно забери деньги, чтобы мы могли положить их на счет в казино. + +[KM5] +'РАЗЪЯСНЕНИЯ' + +[KM5_1] +~g~ПРОДАВЕЦ УБИТ! + +[KM5_2] +~g~Ярди слинял. + +[KM5_3] +~r~Ты не успел убить даже ~1~ Ярди. + +[KM5_4] +~g~Поздравляем, ты прикончил ~1~ Ярди. + +[KM5_5] +~g~Поздравляем, ты прикончил ~1~ Ярди. ПРИЗ $~1~ + +[KM5_6] +~g~Ты должен убить как минимум 8 торговцев-Ярди. + +[KM5_7] +~g~Убивай их побыстрее! Продав СПАНК, они смоются. + +[KM5_A] +ТЫ! Отличный момент чтобы прийти и показать свою тупую морду! + +[KM5_B] +Ты в курсе, что твоя прошлая попытка объяснить ямайцам, + +[KM5_B1] +что им не стоит водить дружбу с Картелем, не принесла никаких плодов? + +[KM5_C] +Ярди заполонили весь город, и торгуют пакетиками со СПАНКом, словно горячими пирожками! + +[KM5_D] +Эти Картельные свиньи смеются над нами, надо мной! + +[KM5_E] +Я даю тебе последний шанс доказать, что моя сестра в тебе не ошиблась! + +[KM5_F] +Иди же, прикончи этих подонков, и пусть твой позор смоют реки крови врагов!!! + +[KURUMA] +Курума + +[K_JAH] +Радио K-Jah + +[LANDSTK] +Лэндсталкер + +[LANGSL] +ВЫБОР ЯЗЫКА + +[LANGUA] +Язык + +[LAST] +Последнее сообщение. + +[LEGAL] +~g~Разберитесь с преступником! + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBFабвгдежзийклмнопрстуфхцчшщъыьэюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ + +[LINERUN] +Лайнранер + +[LIPS] +Губки 106 + +[LITTLE] +LITTLE T + +[LITTLEI] +Сент-Марк + +[LM1] +'ДЕВОЧКИ ЛУИДЖИ' + +[LM1_2] +~g~Отвези Мисти в клуб к Луиджи. + +[LM1_3] +~g~Посигналь, чтобы девица подошла к машине. + +[LM1_6] +~g~Вернись в машину! + +[LM1_7] +Остановись рядом с Мисти и подожди, пока она не сядет в тачку. + +[LM1_8] +Ты можешь пойти к Луиджи и получить у него задание, или покататься по городу. + +[LM1_8A] +Чтобы подзаработать немного деньжат, можно 'одолжить' такси... + +[LM1_9] +Привет, я Мисти. + +[LM2] +'ОТВАЛИ ОТ МОИХ ТЕЛОК' + +[LM2_1] +~g~Отгони его тачку в мастерскую для перекраски. + +[LM2_2A] +Нажав на ~h~ ~k~~PED_FIREWEAPON~~w~ ты ~h~ударишь~w~, ~h~пнешь~w~, или ~h~двинешь~w~ битой противника! + +[LM2_2C] +Нажав на ~h~ ~k~~PED_FIREWEAPON~~w~ ты ~h~ударишь~w~, ~h~пнешь~w~, или ~h~двинешь~w~ битой противника! + +[LM2_2D] +Нажав на ~h~ ~k~~PED_FIREWEAPON~~w~ ты ~h~ударишь~w~, ~h~пнешь~w~, или ~h~двинешь~w~ битой противника! + +[LM2_3] +~g~Отгони машину в гараж к Луиджи! + +[LM2_4] +~g~Перекрась машину! + +[LM2_A] +На улицах появился новый наркотик под названием СПАНК. + +[LM2_B] +Иди и впарь ему битой по морде! + +[LM2_C] +Луиджи велел передать тебе... + +[LM2_D] +вот это, держи. + +[LM2_E] +Какой-то умник впаривает эту дрянь моим девкам в Портландской гавани. + +[LM2_F] +Потом возьми его тачку и перекрась ее. + +[LM2_G] +Я требую возмещения морального ущерба! + +[LM3] +'ПРИВЕЗИ МНЕ МИСТИ' + +[LM3_10] +~g~Достань тачку! + +[LM3_11] +~g~Мисти в автобусе не ездит, достань нормальную тачку! + +[LM3_1A] +Нажми ~h~ ~k~~VEHICLE_HORN~~w~, чтобы ~h~посигналить~w~ и вызвать Мисти из дома. + +[LM3_1B] +Нажми ~h~ ~k~~VEHICLE_HORN~~w~, чтобы ~h~посигналить~w~ и вызвать Мисти из дома. + +[LM3_1C] +Нажми ~h~ ~k~~VEHICLE_HORN~~w~, чтобы ~h~посигналить~w~ и вызвать Мисти из дома. + +[LM3_2] +~g~Отвези Мисти к Джоуи. + +[LM3_4] +~g~Отправляйся за Мисти! + +[LM3_5] +Ты работаешь на Луиджи, да? Похоже, он наконец нашел водилу, которому можно доверять! + +[LM3_6] +Джоуи... + +[LM3_6A] +Твоя большая штука опять по мне соскучилась? + +[LM3_7] +Иди, я сейчас, заводная моя. + +[LM3_8] +Хай, я Джоуи. + +[LM3_9] +Луиджи сказал, что ты свой парень, так что заходи как-нибудь, + +[LM3_9A] +может, и у меня будет для тебя работенка. + +[LM3_9B] +Ладно. + +[LM3_A] +Эй, иди сюда, потолкуем... Мик, я с тобой попозже закончу. + +[LM3_B] +Как поживаешь, парень? + +[LM3_C] +Джоуи Леоне, сын дона, хочет поразвлечься со своей девчонкой Мисти. + +[LM3_D] +Сгоняй за ней в Хепберн Хейтс... + +[LM3_E] +но учти, этот район контролируют Дьяволы. + +[LM3_F] +Отвези ее к нему в гараж на Трентоне, да поживее, + +[LM3_G] +Джоуи не из тех, кто привык ждать, так что одна нога здесь... + +[LM3_H] +да, и советую смотреть на дорогу, а не на Мисти! + +[LM4] +'ОСАТАНЕЛЫЙ СУТЕНЕР' + +[LM4_A] +Какой-то урод из Дьяволов поставил своих потаскух работать на моей территории. + +[LM4_B] +Иди, разберись с этим козлом. + +[LM4_C] +Если нужен ствол - возьми его во дворе магазина, который напротив метро. + +[LM5] +'ВЕЧЕРИНКА У КОПОВ' + +[LM5_1] +~g~У тебя телок в тачке, как сельдей в бочке! ~g~Отвези их на вечеринку и приезжай за новыми. + +[LM5_2] +~r~Девка Луиджи теперь похожа на мешок с мясом! + +[LM5_3] +~g~Тебе нужна тачка! + +[LM5_4] +~g~Подбери телок работающих на бульваре Св.Марка. + +[LM5_5] +~g~Отвези девок на вечеринку к копам! + +[LM5_7] +~g~Луиджи хочет, чтобы на ~p~вечеринке у копов~g~ было не менее четырех девок! + +[LM5_8] +~g~Телок на вечеринке: ~1~ + +[LM5_9] +ДЕВОЧЕК: + +[LM5_A] +Копы решили устроить вечеринку в зале старой школы, которая рядом с мостом Каллахан, + +[LM5_B] +и их теперь потянуло на старые школьные забавы. + +[LM5_C] +Но сейчас все мои девки работают на панели. + +[LM5_D] +Привези им побольше моих телок, доставь всем удовольствие. + +[LM5_E] +Привези побольше, пока копы не пропили все свои бабки. + +[LOADCAR] +LOADING VEHICLE... (PRESS L1 TO CANCEL) + +[LOOK_A] +Удерживай кнопку ~h~~k~~VEHICLE_LOOKLEFT~~w~ или ~h~~k~~VEHICLE_LOOKRIGHT~~w~, чтобы посмотреть из машины ~h~налево~w~ или ~h~направо~w~. Если нажать обе, то посмотришь ~h~назад~w~. + +[LOV4_10] +~r~Ты уничтожил единственный способ выяснить, куда делся пакет! + +[LOVE] +ЗАДАНИЯ ДОНАЛЬДА + +[LOVE1] +'СПАСИТЕЛЬ' + +[LOVE1_1] +~g~Чтобы попасть в логово колумбийцев, тебе понадобится одна из их тачек. Возможно, ты ее найдешь в форте Стаунтона. + +[LOVE1_2] +~g~Спасай Старого Азиата. + +[LOVE1_3] +~g~Отвези старого азиата к Дональду Лаву. + +[LOVE1_4] +~g~Старый Азиат должен быть в одном из этих гаражей... + +[LOVE1_5] +~g~Хватит болтаться без дела, достань машину колумбийцев и спаси товарища Дональда. + +[LOVE1_6] +~r~Да... теперь кишки азиата размазаны по всей улице! + +[LOVE1_7] +~g~Через эти ворота пропускают лишь машины колумбийской банды. + +[LOVE1_A] +Во первых, я хочу поблагодарить вас за ту услугу, которую вы мне оказали. + +[LOVE1_B] +Но, жизненный опыт подсказывает мне, что преданность такого человека, как вы, можно купить, + +[LOVE1_C] +У меня есть один очень ценный знакомый - старый азиат. + +[LOVE1_D] +Они пытаются увеличить сумму назначенного ранее выкупа, но я не привык менять условия сделки. + +[LOVE1_E] +Сделка есть сделка, они не получат ни пенса больше. + +[LOVE1_F] +Люди всегда пытаются истолковать все по-своему. + +[LOVE1_G] +Вы должны спасти моего друга любой ценой. + +[LOVE1_H] +в отличие от преданности банды. + +[LOVE1_I] +Его держат в заложниках какие-то южноамериканцы в Аспатрии. + +[LOVE2] +'КОНЕЦ ВАКАГАСИРА!' + +[LOVE2_1] +~g~Отправляйся в форт Стаунтон и достань тачку колумбийцев! + +[LOVE2_2] +~g~Теперь заезжай в ~p~многоэтажный гараж в Ньюпорте~g~ и прихлопни Кенжи! + +[LOVE2_3] +~r~Если ты будешь без машины Картеля, тебя сразу узнают! + +[LOVE2_4] +~r~Якудза узнали тебя! + +[LOVE2_5] +~g~Кенжи теперь похож на бифштекс! Сваливай из Ньюпорта и уничтожь машину! + +[LOVE2_6] +~r~Ты замочил всех свидетелей! + +[LOVE2_7] +~g~Теперь уничтожь машину! + +[LOVE2_8] +~g~Быстрее сваливай из Ньюпорта! + +[LOVE2_A] +Ничто так не сбивает цены на недвижимость, как старая добрая гангстерская война, + +[LOVE2_B] +не считая, конечно, вспышки чумы......но она может доставить куда больше проблем. + +[LOVE2_C] +Я слышал о натянутых отношениях между Якудзой и колумбийцами. + +[LOVE2_D] +Нам стоит погреть руки на их дурацкой вражде. + +[LOVE2_E] +Я хочу чтобы вы прикончили вакагасира, Кенжи Касена. + +[LOVE2_F] +Сейчас у Кенжи деловая встреча на крыше многоэтажного гаража а Ньюпорте. + +[LOVE2_G] +Достаньте машину Картеля и разделайтесь с ним! + +[LOVE2_H] +Якудза решит, что это убийство - дело рук картеля, и объявит войну. + +[LOVE3] +'ГРУЗ В ОКЕАНЕ' + +[LOVE3_1] +~g~Возьми ~r~катер~g~ и следуй за ~y~самолетом~g~! + +[LOVE3_2] +~g~Ты собрал весь груз! Отвези его Дональду Лаву. + +[LOVE3_3] +~g~Самолет сбросил ~1~ из 6 упаковок. + +[LOVE3_4] +~r~Ты уничтожил самолет! + +[LOVE3_5] +~g~Самолет уже рядом. + +[LOVE3_6] +~r~Упаковка досталась полиции! + +[LOVE3_A] +Некоторые ценные товары очень сложно импортировать из-за лицемерия властей. + +[LOVE3_B] +Сегодня ночью один маленький самолетик на подлете к аэродрому откроет свой трюм. + +[LOVE3_C] +Он сбросит в воду несколько ценных упаковок. + +[LOVE3_D] +Постарайтесь опередить полицию и собрать груз первым. + +[LOVE4] +'ВЕЛИКИЙ АЭРОВОР' + +[LOVE4_1] +~r~Картель уже здесь! + +[LOVE4_2] +~g~Посылки нет! Выследи колумбийцев и забери у них груз. + +[LOVE4_3] +~g~Панлантик Констракшн...? + +[LOVE4_4] +~g~Доставь посылку к Дональду Лаву! + +[LOVE4_5] +~g~Посылка должна быть в самолете.... + +[LOVE4_6] +~g~Отправляйся на лифте в башню! + +[LOVE4_7] +~g~На острове Стаунтон есть одна стройка, возможно они повезли груз туда. + +[LOVE4_8] +~g~Без машины ты в гараж не попадешь. + +[LOVE4_9] +~r~Самолет уничтожен! + +[LOVE4_A] +Спасибо, что привезли мне этот груз, но он был всего лишь приманкой. + +[LOVE4_B] +Простите, но ради крупного дела на это приходится идти. + +[LOVE4_C] +По настоящему важный груз до сих пор все еще в самолете. + +[LOVE4_D] +К несчастью, власти аэропорта все же решили задержать и обыскать самолет, + +[LOVE4_E] +Поезжайте через мост в Шорсайд Вейл и затем в Международный аэропорт Фрэнсис. + +[LOVE4_F] +Деньги властям уже уплачены. + +[LOVE4_G] +Моя посылка ожидает вас в фюзеляже самолета, который стоит в ангаре на таможне. + +[LOVE4_H] +так что мне пришлось заплатить им за издержки. + +[LOVE5] +'ЭСКОРТ' + +[LOVE5_1] +~g~Поехали! + +[LOVE5_2] +~g~Тебе нужна тачка! + +[LOVE5_3] +~g~Езжай вперед и посмотри, что там в конце тоннеля! + +[LOVE5_4] +~r~Защищай грузовик! + +[LOVE5_5] +~r~Ты не сумел защитить грузовик! + +[LOVE5_A] +На вас можно положиться, друг мой. Что ни говори, а это сейчас большая редкость. + +[LOVE5_B] +Мой азиатский друг повезет доставленный вами товар по назначению, но ему потребуется эскорт. + +[LOVE5_C] +Я бы хотел, чтобы вы помогли ему в целости и сохранности доставить этот груз в Пайк Крик + +[LOVE6] +'ПРИМАНКА' + +[LOVE6_1] +~g~Теперь уводи копов подальше от склада! + +[LOVE6_2] +~r~Тебе не удалось увести полицию на достаточное расстояние! + +[LOVE6_3] +У тебя ~1~ секунд, чтобы вернуться к инкассаторской машине, иначе задание будет провалено. + +[LOVE6_4] +~r~Ты разбил приманку для копов! + +[LOVE6_A] +Урок бизнеса, мой друг. + +[LOVE6_B] +даже если она вообще не представляет его истинной ценности. + +[LOVE6_C] +Полиция оцепила весь район, в котором сейчас находится мой друг с грузом. + +[LOVE6_D] +Отправляйтесь туда и используйте фургон как приманку для копов. + +[LOVE6_E] +Если у вас есть особый товар, то каждая собака будет пытаться его у вас отнять... + +[LOVE6_F] +Пусть они за вами погоняются, а он тем временем уйдет. + +[LOVE7] +ИСЧЕЗНОВЕНИЕ ЛАВА + +[LOVEGO] +~g~У Дональда Лава возникли неотложные дела. Заходи попозже! + +[LRQC_1] +Нам с Асукой нужно кое-что обсудить, + +[LRQC_2] +а ты пока можешь тут осмотреться. + +[LRQC_3] +Тебе нужно место, где бы ты мог залечь. + +[LRQC_4] +На углу Бельвилля есть склад, в котором есть все, что тебе нужно. + +[LRQC_5] +Приходи ко мне сюда, когда будешь готов, + +[LRQC_6] +и я обсужу с тобой кое-какие дела. + +[LUIGGO] +~g~Луиджи осматривает новых девочек. Загляни попозже! + +[LUIGI] +ЗАДАНИЯ ЛУИДЖИ + +[LUIGIS] +Жилище Луиджи + +[L_TRN_1] +Ты можешь попасть в другой район Портланда, воспользовавшись метро. Нажми~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, чтобы ~h~сесть~w~ или ~h~выйти~w~ из поезда. + +[L_TRN_2] +Ты можешь попасть в другой район Портланда, воспользовавшись метро. Нажми~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, чтобы ~h~сесть~w~ или ~h~выйти~w~ из поезда. + +[MAFIACR] +Сентинел Мафии + +[MANANA] +Манана + +[MAR] +Мар + +[MAY] +Май + +[MCDNSP] +There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 500KB is needed to save this application data. Do you wish to start? (YES or NO) + +[MCGNSP] +There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 200KB is needed to save this application data. Do you wish to start? (YES or NO) + +[MCLOAD] +Loading Data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[MCSTNS] +There is no Memory Card (PS2) in MEMORY CARD slot 1. Do you wish to start? (YES or NO) + +[MC_LDFL] +Загрузка не прошла! + +[MC_NWRE] +Идет перезапуск игры. + +[MEA1] +'МОШЕННИК' + +[MEA1_1] +~r~Управляющий банка мертв! + +[MEA1_2] +~r~Тебе же сказали уничтожить машину! + +[MEA1_3] +~g~Выбирайся из машины! + +[MEA1_4] +~r~Ты проехал мимо управляющего банка! + +[MEA1_B] +Я Чонкс, Марти Чонкс. + +[MEA1_B3] +~g~Езжай и встреть управляющего банка. + +[MEA1_B4] +А, тебя послал мистер Чонкс. Ну поехали, нанесем ему визит. + +[MEA1_B5] +TEXT NO LONGER NEEDED + +[MEA1_B6] +~g~Отгони машину в утилизатор, чтобы избавиться от улик. Выйди из машины и ее подцепит кран. + +[MEA1_C] +Я тут на углу построил заводик по производству корма для кошек и собак. + +[MEA1_D] +У меня возникли проблемы с финансами, но у кого их не бывает? + +[MEA1_E] +Я собирался встретиться с управляющим из банка. + +[MEA1_F] +Этот мерзкий мошенник решил поживиться за мой счет, и поднял процент займа. + +[MEA1_G] +Возьми мою тачку, встреть его и привези сюда. + +[MEA1_H] +Я приготовил небольшой сюрприз для этого кровопийцы! + +[MEA2] +'КРАЖА' + +[MEA2_1] +~r~Тебе же сказали уничтожить тачку! + +[MEA2_2] +~r~Вор мертв! + +[MEA2_3] +~g~Отгони машину назад на фабрику. + +[MEA2_4] +~r~Ты проехал мимо вора! + +[MEA2_A] +Я нанял воров, чтобы они забрались в мой офис... + +[MEA2_B] +и кое-что вынесли. А я бы потом смог потребовать страховку. + +[MEA2_B3] +~g~Езжай, встреть воров. + +[MEA2_B4] +~g~Отвези их к фабрике по производству корма. + +[MEA2_B5] +TEXT NO LONGER NEEDED + +[MEA2_B6] +~g~Перекрась машину, чтобы избавиться от улик. + +[MEA2_C] +Но эти ублюдки теперь угрожают тем, что расскажут все страховой компании, + +[MEA2_D] +если я с ними не поделюсь. + +[MEA2_E] +Нет, ты представляешь? + +[MEA2_F] +За воротами фабрики я поставил машину. + +[MEA2_G] +Возьми ее и сгоняй за ними. Они тусуются где-то в квартале красных фонарей. + +[MEA2_H] +Привези их ко мне на фабрику и я покажу им кузькину мать. + +[MEA3] +'ЖЕНА' + +[MEA3_1] +~r~Жена мертва! + +[MEA3_2] +~r~Ты должен был утопить тачку! + +[MEA3_3] +~r~Ты проехал мимо его жены! + +[MEA3_A] +Чтобы продолжить свой бизнес, мне нужно как быстрее решить финансовые проблемы. + +[MEA3_B] +У моей жены хорошая страховка жизни, и к тому же она постоянно транжирит мои деньги. + +[MEA3_B3] +~g~Езжай и подбери миссис Чонкс. + +[MEA3_B4] +Марти хочет меня видеть? Давай быстрее, а то мне нужно еще успеть в парикмахерскую. + +[MEA3_B5] +TEXT NO LONGER NEEDED + +[MEA3_B6] +~g~Чтобы избавиться от улик, утопи тачку. + +[MEA3_C] +Я поставил машину в обычном месте. + +[MEA3_D] +Забери мою жену из маникюрного салона и привези ко мне на фабрику. + +[MEA4] +'ЛЮБОВНИК' + +[MEA4_1] +~r~Карлос мертв! + +[MEA4_2] +~r~Марти Чонкс мертв! + +[MEA4_3] +~r~Ты проехал мимо Карлоса! + +[MEA4_A] +Проклятье, у меня проблемы! + +[MEA4_B] +Оказалось, что моя жена спала с тем, кому я задолжал. + +[MEA4_B3] +~g~Подбери любовника жены Чонкса. + +[MEA4_B4] +Тебя послал Марти, да? Я объясню этой мерзкой крысе, что такое настоящий бизнес. + +[MEA4_B5] +Карл, привет! Мне эээ... нужно время, чтобы отдать долг. + +[MEA4_B6] +Нет, Марти, прошло и так много времени. У тебя был шанс, а теперь я забираю эту фабрику... + +[MEA4_B7] +Ну ты хоть ко мне в офис зайди... + +[MEA4_C] +Он очень зол и требует, чтобы я вернул ему деньги! + +[MEA4_D] +Мне нужно с ним поговорить... + +[MEA4_E] +он думает, что я отдам ему долг... + +[MEA4_F] +но я решил... + +[MEA4_G] +что в этом месяце городских дворняг ждет новое блюдо! + +[MEAT1_A] +Друзья посоветовали мне обратиться к тебе, чтобы решить все мои проблемы. Если ты согласен, подойди к телефону в Трентоне. + +[MED_WST] +Убито медиков + +[MEMTST] +MemoryCardTest screen + +[MGSVCN] +MagazineDirectory Created + +[MGSVNC] +MagazineDirectory Not Created + +[MISTY1] +~r~Мисти теперь лучше везти в морг! + +[MMRAIN] +Количество выпавших осадков (мм) + +[MM_1] +'ГОНКА ПО ГАРАЖУ' + +[MM_1_A] +~g~У тебя ~y~2 минуты~g~, чтобы проехать ~y~20 контрольных пунктов~g~ в высотном гараже! ~g~Ты можешь проезжать их ~y~В ЛЮБОМ ПОРЯДКЕ~g~. + +[MM_1_B] +~1~ из 20! + +[MM_1_C] +~g~У тебя 20 секунд, после прохождения каждого контрольного пункта тебе будут добавлять по ~y~5 секунд~g~. ~g~Время ~y~УЖЕ~g~ пошло. + +[MONTAX] +Денег заработано на такси + +[MOONBM] +Лунный луч + +[MRWONGS] +М-р.Вонг + +[MSX_FM] +MSX FM + +[MULE] +Тягач + +[MUSMEN] +Музыка/Звуки + +[MUSVOL] +Громкость музыки + +[MXCARD] +Длина лучшего БЕЗУМНОГО прыжка (ft) + +[MXCARDM] +Длина лучшего БЕЗУМНОГО прыжка (м) + +[MXCARJ] +Высота лучшего БЕЗУМНОГО прыжка (ft) + +[MXCARJM] +Высота лучшего БЕЗУМНОГО прыжка (м) + +[MXFLIP] +Переворотов в лучшем БЕЗУМНОМ прыжке + +[MXJUMP] +Разворот в лучшем БЕЗУМНОМ прыжке + +[M_FAIL] +ЗАДАНИЕ ПРОВАЛЕНО! + +[M_PASS] +ЗАДАНИЕ ВЫПОЛНЕНО! $~1~ + +[M_WASTE] +Мужчин убито + +[NEW_TAX] +БОЛЬШЕ! БЫСТРЕЕ! ЛУЧШЕ! В Харвуде открылся новый таксопарк Борнини. Звоните 555-БОРНИНИ! + +[NICK] +НИК ЛАВ + +[NMISON] +Заданий начато + +[NMMISP] +Заданий завершено + +[NO] +Нет + +[NOCD] +CD-привод пуст. Пожалуйста, вставьте диск. + +[NOCONT] +Please reconnect an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). to controller port 1 to continue + +[NOCONTE] +Please re-insert the analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2) in controller port 1 to continue + +[NODOORS] +~g~Они все туда не влезут! Достань тачку попросторней! + +[NOGMSV] +Записаться можно лишь в твоем гараже. + +[NOMONEY] +~g~У тебя не хватает денег! + +[NOSTUC] +Не выполнено ни одного БЕЗУМНОГО трюка + +[NOUNGM] +Всего уникальных прыжков + +[NOUNIF] +Выполнено уникальных прыжков + +[NOV] +Ноя + +[NO_PAUZ] +При игре по сети пауза не работает. Чтобы выйти нажми ESC дважды! + +[NO_PCCD] +Пожалуйста, вставьте второй диск игры GTA3 в CDROM привод, или нажмите ESC для выхода из игры. + +[NRECORD] +~r~РЕКОРД НЕ ПОБИТ! + +[NUMBER] +~1~ + +[OCT] +Окт + +[OPENCD] +CRDOM-привод открыт. Пожалуйста, закройте CDROM-привод. + +[OUTTIME] +~r~Очень медленно, друг, очень медленно! + +[OUT_VEH] +~g~Выйди из тачки! + +[O_FAIL] +ДОПОЛНИТЕЛЬНОЕ ЗАДАНИЕ ПРОВАЛЕНО! + +[O_PASS] +ДОПОЛНИТЕЛЬНОЕ ЗАДАНИЕ ВЫПОЛНЕНО! + +[PAGEB1] +В убежище доставлен пистолет + +[PAGEB10] +В убежище доставлена ракетница + +[PAGEB11] +В убежище доставлен огнемет + +[PAGEB12] +В убежище доставлена взятка для полиции + +[PAGEB13] +В убежище доставлено лекарство + +[PAGEB14] +В убежище доставлен адреналин + +[PAGEB2] +В убежище доставлен 'Узи' + +[PAGEB3] +В убежище доставлена броня + +[PAGEB4] +В убежище доставлен дробовик + +[PAGEB5] +В убежище доставлены гранаты + +[PAGEB6] +В убежище доставлены коктейли Молотова + +[PAGEB7] +В убежище доставлен AK-47 + +[PAGEB8] +В убежище доставлена снайперская винтовка + +[PAGEB9] +В убежище доставлена винтовка M16 + +[PAGE_00] +. + +[PAGE_01] +Убей ~1~ Дьяволов за 120 секунд! + +[PAGE_02] +Уничтожь ~1~ машин за 120 секунд! + +[PAGE_03] +Убей ~1~ мафиози за 120 секунд! + +[PAGE_04] +Убей ~1~ триадовцев за 120 секунд! + +[PAGE_05] +Убей ~1~ триадовцев за 120 секунд! + +[PAGE_06] +Уничтожь ~1~ машин за 120 секунд! + +[PAGE_07] +Расколи ~1~ голов Ярди за 120 секунд! + +[PAGE_08] +Сожги ~1~ членов Якудзы за 120 секунд! + +[PAGE_09] +Уничтож ~1~ машин за 120 секунд! + +[PAGE_10] +Уничтожь ~1~ машин за 120 секунд! + +[PAGE_11] +Истреби ~1~ Ярди за 120 секунд! + +[PAGE_12] +Подожги ~1~ членов Якудзы за 120 секунд! + +[PAGE_13] +Взорви ~1~ Ярди за 120 секунд! + +[PAGE_14] +Поджарь ~1~ колумбийцев за 120 секунд! + +[PAGE_15] +Задави ~1~ Капюшонов за 120 секунд! + +[PAGE_16] +Уничтожь ~1~ машин за 120 секунд! + +[PAGE_17] +Задави ~1~ колумбийцев за 120 секунд! + +[PAGE_18] +Уничтожь ~1~ машин за 120 секунд! + +[PAGE_19] +Лиши голов ~1~ колумбийцев за 120 секунд! + +[PAGE_20] +Обезглавь ~1~ Капюшонов за 120 секунд! + +[PANCAK] +РАСПЛЮЩИЛ! + +[PANLANT] +Панлантик + +[PAPER1] +* + +[PAPER2] +* + +[PAPER3] +* + +[PARK] +Парк Бельвиль + +[PARSHP] +Parse Heap + +[PASDRO] +Потеряно пассажиров + +[PATRIOT] +Пэтриот + +[PAUSED] +ИГРА ОСТАНОВЛЕНА + +[PBOAT_1] { re3 change } +Чтобы выстрелить из орудия катера нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[PBOAT_2] { re3 change } +Чтобы выстрелить из орудия катера нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[PCLOAD] +Loading File Data + +[PCRESRT] +Перезапуск Grand Theft Auto III + +[PDRGON] +ShowPedRoadGroups On + +[PEREN] +Многолетник + +[PERPIC] +Найдено особых пакетов + +[PER_COM] +Процент завершения игры + +[PE_WAST] +Убито вами людей + +[PE_WSOT] +Убито преступниками людей + +[PIG_WST] +Убито копов + +[PL_STAT] +ДАННЫЕ ОБ ИГРОКЕ + +[POLICAR] +Полицейская + +[PONY] +Пони + +[PORT_E] +Порт Портланда + +[PORT_I] +Трентон + +[PORT_S] +Пристань Атлантик + +[PORT_W] +Мыс Каллахан + +[PQUINS] +Идеальный четверной безумный трюк + +[PREBRF] +Предыдущие сообщения + +[PREDATR] +Хищник + +[PRGOFF] +ShowPedRoadGroups Off + +[PRINST] +Идеальный безумный трюк + +[PROJECT] +Сады Вичита + +[PRTRST] +Идеальный тройной безумный трюк + +[PRVMEN] +Задание предыдущей миссии + +[PUSAVE] +Save Only the game + +[PU_MONY] +Не хватает денег. + +[QUINST] +Четверной безумный трюк + +[QUITOP] +Quit Options + +[RADIO_A] +Нажимай ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ ~w~, чтобы менять ~h~ радиоволну. + +[RADIO_B] +Нажимай ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ ~w~, чтобы менять ~h~ радиоволну. + +[RADIO_C] +Нажимай ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ ~w~, чтобы менять ~h~ радиоволну. + +[RADIO_D] +Нажимай ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ ~w~, чтобы менять ~h~ радиоволну. + +[RAMPAGE] +СХВАТКА!! + +[RAMP_A] +ВСЕ СХВАТКИ ПРОЙДЕНЫ! + +[RAMP_F] +СХВАТКА ПРОИГРАНА + +[RAMP_P] +СХВАТКА ВЫИГРАНА! + +[RATNG1] +Воришка + +[RATNG10] +Чистильщик + +[RATNG11] +Профессионал + +[RATNG12] +Киллер + +[RATNG13] +Правая рука + +[RATNG14] +Главарь + +[RATNG15] +Босс + +[RATNG2] +Мошенник + +[RATNG3] +Хулиган + +[RATNG4] +Шестерка + +[RATNG5] +Грабитель + +[RATNG6] +Громила + +[RATNG7] +Бандит + +[RATNG8] +Головорез + +[RATNG9] +Братан + +[RAY] +ЗАДАНИЯ РЭЯ + +[RAYGO] +~g~Рэй пошел в другой туалет. Зайди сюда попозже! + +[RC1] +'ДРАКА С ДЬЯВОЛАМИ' + +[RC2] +'МОРДОБОЙ С МАФИЕЙ' + +[RC3] +'КАТАСТРОФА В КАЗИНО' + +[RC4] +'РАЗБОРКА С РАСТАМАНАМИ' + +[RCBANDT] +Багги Бандит + +[RCHELP] { re3 change } +Чтобы подорвать машинку, нажми ~k~~VEHICLE_FIREWEAPON~, или врежься в колесо жертвы. + +[RCHELPA] { re3 change } +Чтобы подорвать машинку, нажми ~k~~VEHICLE_FIREWEAPON~, или врежься в колесо жертвы. + +[RC_1] +У тебя 2 минуты, чтобы подорвать как можно больше машин Дьяволов! + +[RC_2] +У тебя 2 минуты, чтобы подорвать как можно больше машин мафии! + +[RC_3] +У тебя 2 минуты, чтобы подорвать как можно больше машин Якудзы! + +[RC_4] +У тебя 2 минуты, чтобы подорвать как можно больше машин Ярди! + +[RC_5] +У тебя 2 минуты, чтобы подорвать как можно больше машин Капюшонов! + +[RC_6] +У тебя 2 минуты, чтобы подорвать как можно больше машин Картеля! + +[RECORD] +~g~НОВЫЙ РЕКОРД!! + +[REDLIGH] +Квартал красных фонарей + +[REEFER] +Рифер + +[REGCAR] +Register MemoryCard One + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[REPLAY] +ПОВТОР + +[RESTART] +Новая игра + +[REWARD] +НАГРАДА $~1~ + +[RHINO] +Носорог + +[RISE] +Rise FM + +[RM1] +'КОНЕЦ СТУКАЧА' + +[RM1_1] +~g~Осмотри дом защиты свидетелей. + +[RM1_2] +~g~Прикончи МакАффри! + +[RM1_3] +~r~МакАффри скрылся! + +[RM1_4] +~g~Ты использовал все гранаты! Иди купи в магазине еще! + +[RM1_5] +~g~Вернись и подожги дом! + +[RM1_A] +Этот подонок МакАффри брал взяток больше, чем кто-либо. + +[RM1_B] +Он решил всех заложить, и таким образом оправдать все свои темные делишки. + +[RM1_C] +Хренов стукач! + +[RM1_D] +Он в центре Ньюпорта под вооруженной охраной в здании защиты свидетелей, которое позади стоянки. + +[RM1_E] +Чтобы выкурить его наружу - подожги этот дом, ну а потом, я думаю, ты сможешь заставить его замолчать. + +[RM2] +'РУКА ПОМОЩИ' + +[RM2_A] +Мой старый армейский друг организовал бизнес в Рокфорде. + +[RM2_A1] +Эй, парень, сюда! + +[RM2_B] +Мы с ним были в Никарагуа в те годы, когда правительство знало, что делает. Ну так вот. + +[RM2_C] +Его вчера избили какие-то сволочи из Картеля, и сказали что сегодня заберут кое-что из его товара. + +[RM2_D] +Ему нужно помочь. Ну, а он в долгу тоже не останется - даст тебе затовариться 'скобяными изделиями'. + +[RM2_D1] +Я бы, конечно, и сам помог ему, но старость не радость - кхе кхе - вот, в-общем, удачи тебе. + +[RM2_E] +Рэй мне звонил... но я думал, он пришлет побольше народу. + +[RM2_E1] +Я не могу поверить, что эти трусливые ублюдки снова оставили меня без прикрытия! + +[RM2_F] +Ну, три руки лучше чем одна, так что бери все, что тебе пригодится. + +[RM2_F1] +Колумбийцы прибудут с минуты на минуту! + +[RM2_G] +~g~Проверь, как дела у Фила! + +[RM2_H] +~r~Фила убили! + +[RM2_K] +Проклятье, вот они! ПОЛУЧАЙТЕ! + +[RM2_L] +Эй! Если бы ты тогда был в Никарагуа, глядишь, я и не потерял бы руку! + +[RM2_M] +Если тебе понадобится оружие, то не стесняйся, бери все что нужно из ящиков. + +[RM2_N] +Только не забудь заплатить. А сейчас сваливай, с копами я все улажу. + +[RM3] +'УНИЧТОЖЕНИЕ УЛИК' + +[RM3_1] +~g~Оставь улики в машине и подожги ее. + +[RM3_4] +~g~Обвинение лишилось улик! + +[RM3_5] +~g~У тебя ~1~ из 6 пакетов с уликами. + +[RM3_6] +~r~Фотографии разлетятся по всему городу! + +[RM3_7] +~g~Теперь поджигай тачку! + +[RM3_8] +~r~Эта тачка была приманкой! + +[RM3_A] +Я знаю одного очень важного человека в городе, + +[RM3_B] +Против него выдвинуто обвинение. Есть какие-то непристойные фотки, где его сняли + +[RM3_C] +на вечеринке в морге или еще где. + +[RM3_D] +Улики скоро собираются везти в суд. + +[RM3_E] +Тебе нужно протаранить машину с уликами и собрать все до одной выпавшие фотографии. + +[RM3_F] +Как только соберешь все улики - оставь их в машине и взорви ее. + +[RM3_G] +Если ты это провернешь, то все будут в плюсе. + +[RM3_H] +с довольно экзотическим вкусом и деньгами для его удовлетворения. + +[RM4] +'РЫБАЛКА' + +[RM4_1] +~g~Тебе нужно угнать полицейский катер. + +[RM4_2] +~g~Отправляйся к маяку и пусти напарника Рэя на корм рыбам! + +[RM4_3] +~r~Партнер Рэя скрылся! + +[RM4_A] +Думаю, мой напарник - стукач. + +[RM4_B] +Нужно побыстрее заткнуть ему пасть. + +[RM4_C] +На своем катере он вечерами ходит рыбачить к маяку на утесе Портланда. + +[RM4_D] +Возьми полицейский катер и разберись с этим чертовым стукачем! + +[RM4_E] +Хватит ему рыбу ловить. Пусть он ее теперь покормит. + +[RM5] +'ЖИВАЯ МУМИЯ' + +[RM5_1] +~g~Перехвати скорую. + +[RM5_2] +~g~Тебя засекли! + +[RM5_3] +~g~Это была приманка! + +[RM5_4] +~g~Пули эту загипсованную куклу не берут! + +[RM5_5] +~g~Этой загипсованной кукле огнемет не страшен! + +[RM5_6] +~g~Он вывалился! Раскатай его по асфальту, или взорви гранатами! + +[RM5_7] +~r~Свидетеля доставили на место! + +[RM5_8] +~g~Свидетеля больше нет! + +[RM5_A] +Ты никчемный ублюдок! + +[RM5_A1] +Ты все завалил! Я уже на крючке, а ты даже не смог поджарить этого хренова шпиона. + +[RM5_B] +Я же тебе хорошо заплатил за то, чтобы ты убрал этого стукача! + +[RM5_B1] +Сегодня этот урод собирается давать показания! + +[RM5_C] +Его с минуты на минуту собираются перевезти из госпиталя Карсон в участок Рокфорда. + +[RM5_D] +Если он заговорит, мне конец... + +[RM5_E] +так что иди, и доделывай свою работу! + +[RM6] +'НА МУШКЕ' + +[RM6_1] +Вот тебе ключ от тайника. + +[RM6_2] +В нем я на черный день припрятал наличные и кое-какие полезные вещички. + +[RM6_3] +Увидимся. + +[RM6_4] +~g~Отправляйся к тайнику и забери вещички Рэя. + +[RM6_5] +~g~Мост под наблюдением ЦРУ. Тебе придется найти другой путь. + +[RM6_6] +~r~Рэй мертв! + +[RM6_666] +Возьми мой пуленепробиваемый Пэтриот. Увидимся в Майами, Рэй + +[RM6_7] +~r~Рэй опоздал на самолет. + +[RM6_8] +~g~Ты проехал мимо Рэя, вернись и посади его. + +[RM6_A] +За тобой хвоста нет? Хорошо. + +[RM6_B] +Похоже, я прыгнул выше головы и перешел кое-кому дорогу! + +[RM6_C] +Я так понял, что ЦРУ имеет долю от продажи СПАНКА + +[RM6_C1] +и они убирают тех, кто мешает Картелю работать. + +[RM6_D] +Я у них на мушке, мне нужно быстрее сваливать. + +[RM6_E] +Мой самолет вот-вот отлетает, гони в аэропорт, я хорошо заплачу! + +[ROADBR1] +Мост Каллахан + +[ROADBR2] +Мост Каллахан + +[RUMPO] +Рампо + +[SAVE1] +Чтобы ~h~сохранить игру~w~ - пройди в дверь. Во время задания записаться нельзя. + +[SAVE2] +Если в гараже оставить машину, то она будет сохранена при записи игры. + +[SCASSL] +Sick Fuck Selected + +[SCORE] +$~1~ + +[SCRFOR] +Screen format + +[SCROPT] +НАСТРОЙКИ ЭКРАНА + +[SCSCSL] +Sick Fucker Selected + +[SECOND] +~g~второй + +[SECURI] +Инкассатор + +[SENTINL] +Сентинел + +[SEP] +Сен + +[SET1] +SetUp 1. + +[SET1EN] +SetUp 1. Enabled + +[SET2] +SetUp 2 + +[SET2EN] +SetUp 2. Enabled + +[SET3] +SetUp 3 + +[SET3EN] +SetUp 3. Enabled + +[SET4] +SetUp 4 + +[SET4EN] +SetUp 4. Enabled + +[SFXVOL] +Громкость звуков + +[SHOPING] +Бедфорд Поинт + +[SHPLOF] +gbShowCollisionPolys Off + +[SHPLON] +gbShowCollisionPolys On + +[SICASS] +Sick Fuck + +[SICSIC] +Sick Fucker + +[SIREN_1] +Чтобы включить сирену, понажимай кнопку ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_2] +Чтобы включить сирену, понажимай кнопку ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_3] +Чтобы включить сирену, понажимай кнопку ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_4] +Чтобы включить сирену, понажимай кнопку ~h~~k~~VEHICLE_HORN~~w~. + +[SLNSP] +Insufficient space to save. Please insert a Memory Card (PS2) with at least 200KB of free space available into MEMORY CARD slot 1. + +[SLONDR] +Insufficient space to save. Please insert a Memory Card (PS2) with at least 500KB of free space available into MEMORY CARD slot 1. + +[SLONFM] +Error formatting Memory Card (PS2) in MEMORY CARD slot 1. + +[SLONNF] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. + +[SLONNO] +No Memory Card (PS2) in MEMORY CARD slot 1. + +[SOAKED] +ЧЕРТ! + +[SOUND] +ЗВУК + +[SPAIN] +Spanish + +[SPEEDER] +Speeder + +[SPLAT] +ГОТОВ! + +[SPOTTED] +~r~Они у тебя на хвосте! + +[SPRAY] +В мастерской твою тачку ~h~отремонтируют~w~ и ~h~перекрасят~w~. Копы тебя больше ~h~не узнают~w~. Стоимость - ~h~$1000~w~. + +[SPRAY1] +В мастерской твою тачку ~h~отремонтируют~w~ и ~h~перекрасят~w~. Копы тебя больше ~h~не узнают~w~. Стоимость - ~h~$1000~w~. Первый раз это бесплатно. + +[SPRAY_1] { re3 change } +Чтобы включить брандспойт, нажми на ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[SPRAY_4] { re3 change } +Чтобы включить брандспойт, нажми на ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[STADIUM] +Аспатрия + +[STALION] +Жеребец + +[STANDS] +ЛАРЬКОВ РАЗБИТО: + +[STASH] +~g~Отвези СПАНК на ~p~стройку~g~! + +[STINGER] +Стингер + +[STOCK] +отсутствует + +[STRETCH] +Стретч + +[SUBWAY1] +Станция Портланд + +[SUBWAY2] +Станция Рокфорд + +[SUBWAY3] +Станция Южный Стаунтон + +[SUBWAY4] +Вокзал Шорсайд + +[SUB_IND] +Пайк Крик + +[SUB_ZO2] +Шорсайд Вейл + +[SUB_ZO3] +Шорсайд Вейл + +[SUB_ZON] +Шорсайд Вейл + +[SVCONF] +ПОДТВЕРДИТЕ ЗАПИСЬ + +[SVGMON] +SaveTheGame + +[SWANKS] +Кедровая роща + +[S_PROM2] +Если в этот гараж поставить машину, то она будет сохранена при записи игры. + +[S_PROMP] +Когда ты не на задании, то здесь можно ~h~сохранить игру~w~, но это добавит ко времени шесть часов. + +[S_TRN_1] +Воспользовавшись метро, ты можешь попасть в другой район города. Нажми~h~ ~k~~VEHICLE_ENTER_EXIT~~w~ чтобы ~h~сесть~w~ или ~h~выйти~w~ из поезда. + +[S_TRN_2] +Воспользовавшись метро, ты можешь попасть в другой район города. Нажми~h~ ~k~~VEHICLE_ENTER_EXIT~~w~ чтобы ~h~сесть~w~ или ~h~выйти~w~ из поезда. + +[S_VIEW] +Портланд Вью + +[T4X4_1] +'ПЭТРИОТ РАЛЛИ' + +[T4X4_1A] +~g~У тебя ~y~5 минут~g~ чтобы пройти ~y~15 контрольных пунктов~g~. Ты можешь их проходить в ~y~ЛЮБОМ~g~ порядке. + +[T4X4_1B] +~1~ из 15! + +[T4X4_1C] +~g~Отсчет времени пойдет, как только ты ~y~ПРОЕДЕШЬ~g~ через первый контрольный пункт. После прохождения каждой точки тебе будет добавляться ~y~20 секунд~g~. + +[T4X4_2] +'ГОНКА В ПАРКЕ' + +[T4X4_2A] +~g~У тебя ~y~5 минут~g~ чтобы пройти ~y~12 контрольных пунктов~g~. Ты можешь их проходить в ~y~ЛЮБОМ~g~ порядке. + +[T4X4_2B] +~1~ из 12! + +[T4X4_2C] +~g~Отсчет времени пойдет, как только ты ~y~ПРОЕДЕШЬ~g~ через первый контрольный пункт. После прохождения каждой точке тебе будет добавляться ~y~10 секунд~g~. + +[T4X4_3] +'ЖАРКАЯ ГОНКА' + +[T4X4_3A] +~g~У тебя ~y~5 минут~g~, чтобы пройти ~y~20 контрольных пунктов~g~. Ты можешь их проходить в ~y~ЛЮБОМ~g~ порядке. + +[T4X4_3B] +~g~Отсчет времени пойдет, как только ты ~y~ПРОЕДЕШЬ~g~ через первый контрольный пункт. После прохождения каждой точки тебе будет добавляться ~y~15 секунд~g~. + +[T4X4_3C] +~1~ из 20! + +[T4X4_F] +~r~Ты не успел! Слишком сложно для тебя?! + +[TAXI] +Такси + +[TAXI1] +~g~Ищи пассажира. + +[TAXI2] +~r~Ты не успел вовремя! + +[TAXI3] +~r~Твой пассажир бежал в ужасе! + +[TAXI4] +Пассажир доставлен! + +[TAXI5] +ПРИЗ ЗА СКОРОСТЬ! + +[TAXI6] +Задание такси окончено + +[TAXI7] +~r~Такси слишком помято, отремонтируй его. + +[TAXIH1] +Остановись рядом с указанным пешеходом, подожди, пока он не сядет в такси и за указанное время отвези его куда он хочет. + +[TAXI_M] +'ВОДИТЕЛЬ ТАКСИ' + +[TEFONE] +Test Format MemCard One + +[TEUFON] +Test UnFormat MemCard One + +[TEXTXYZ] +Writing coordinates to file... + +[THIRD] +~g~третий + +[TIMER] +Это задание на время, ты должен успеть его выполнить, пока тикает таймер. + +[TM1] +'БОЛЬШАЯ СТИРКА' + +[TM1_A] +~w~Присаживайся, малыш, давай присаживайся. + +[TM1_B] +~w~Итак, прачечная не хочет платить за защиту? + +[TM1_C] +~w~Триадовцы думают, что могут тягаться со мной? + +[TM1_D] +~w~Давай-ка покажем этим 'крутым парням', что значит быть действительно крутым. + +[TM1_E] +~w~Да, преподай им урок. Еще никто из нас не отступал перед Триадой. + +[TM1_F] +~w~Твой отец, упокой господь его душу, не цацкался с ними на Сицилии. + +[TM1_G] +~w~Извини Ма. Да, Ма. + +[TM1_H] +~w~Я хочу чтобы ты взорвал все фургоны прачечной + +[TM1_I] +~w~и прикончил Триадовцев, что встанут у тебя на пути. + +[TM1_J] +~w~Все, что для этого нужно, ты можешь взять у Лысого. + +[TM2] +'МЗДА' + +[TM2_1] +~g~Отвези деньги Тони! + +[TM2_2] +~g~Ты загасил их всех! + +[TM2_3] +~g~Это ловушка! Прикончи их! + +[TM2_A] +~w~Тони пошел на очередное дело. + +[TM2_AA] +~w~Он никогда не станет таким авторитетом, как отец. Вон тебе записка. + +[TM2_B] +~w~Прачечная согласна платить - ты все сделал как нужно! + +[TM2_C] +~w~Иди, забери деньги и принеси их мне. Но остерегайся триадовцев. + +[TM2_D] +~w~Они решили поджарить твою задницу, но не бери это в голову, сынок. + +[TM2_E] +~w~Никто, повторяю, никто не связывается с ТОНИ СИПРИАНИ! + +[TM3] +'СОБРАНИЕ СЕМЬИ САЛЬВАТОРЕ' + +[TM3_1] +~g~Возьми у Джоуи Стретч. + +[TM3_2] +~g~Теперь езжай за Луиджи. + +[TM3_3] +~g~Теперь забери Тони. + +[TM3_4] +~g~Отвези приятелей в особняк Сальваторе. + +[TM3_5] +~y~Это засада Триады! + +[TM3_A] +~w~Дон Сальваторе решил нас всех собрать + +[TM3_A1] +~r~Джоуи поджарился! + +[TM3_A2] +~r~Джоуи и Луиджи кремированы! + +[TM3_A3] +~r~Джоуи, Тони и Луиджи спеклись! + +[TM3_B] +~w~Нужно заехать к Джоуи, и забрать лимузин и его самого из гаража. + +[TM3_C] +~w~Затем отправишься в клуб за Луиджи, и потом заедешь за мной, + +[TM3_D] +~w~Сегодня нам всем босс назначил встречу у него в особняке. + +[TM3_E] +~w~Эти Триадовцы должны знать свое место. + +[TM3_F] +~w~Им нужна война, они ее получат. + +[TM3_G] +~w~Давай, отправляйся. + +[TM3_H] +~w~Ты молодец, парень, просто молодец. + +[TM3_I] +~w~Пошли, познакомлю тебя с доном. + +[TM3_J] +~w~Эээй! Луиджи! + +[TM3_K] +~w~О, Сальваторе, мои девочки по тебе уже соскучились, тебя так долго не было. + +[TM3_L] +~w~Передай им, что как только мы закончим это небольшое дельце, + +[TM3_M] +~w~то отправимся в клуб и отпразднуем, хорошо? + +[TM3_MA] +~w~Да не знаю я, где он! + +[TM3_MB] +~w~Я уверена, что он сам себя иногда не понимает. + +[TM3_MC] +~w~Они с отцом такие разные. Отец, тот всегда при деле, решительный... + +[TM3_N] +~w~Вот он, мой мальчик. + +[TM3_N2] +~w~Как у тебя дела, пап? + +[TM3_O] +~w~Ты еще не нашел себе хорошую девушку? + +[TM3_P] +~w~Эй, твоя мама, упокой господь ее душу, перевернулась бы в могиле, + +[TM3_Q] +~w~если бы увидела тебя без жены. + +[TM3_R] +~w~Я знаю, па, я над этим работаю. + +[TM3_S] +~w~Тони, как твоя мама? + +[TM3_T] +~w~Знаешь, она чудесная женщина. Сильная. Умная. + +[TM3_U] +~w~У нее все...хорошо. + +[TM3_V] +~w~Прекрасно, прекрасно. Слушайте, парни, проходите внутрь, а я пока поговорю с нашим новым другом. + +[TM3_W] +~w~Я смотрю ты многое для нас сделал, парень... + +[TM4] +'ПЕРЕПОЛОХ В ЧАЙНАТАУНЕ' + +[TM4_A] +~w~Ах, это ты. Тони сейчас нет. + +[TM4_A2] +~w~Но он оставил тебе одно из своих любовных посланий. + +[TM4_B] +~w~Мы в состоянии ВОЙНЫ! Триада использует рыбный завод в качестве прикрытия. + +[TM4_C] +~w~Они проворачивают свои темные делишки на рыбном рынке в Чайнатауне. + +[TM4_D] +~w~Прачечная опять задолжала нам деньги. + +[TM4_E] +~w~Они считают, что за защиту лучше платить Триаде, так что придется их снова наказать. + +[TM4_F] +~w~Накажи этих умников, и нанеси визит боссам Триады! + +[TM4_G] +~w~Черт, если представится шанс, убей заодно и несколько их солдат. + +[TM4_GAT] +~g~Чтобы тебя пропустили, возьми фургон Триады. + +[TM5] +'ВЗРЫВ НА ЗАВОДЕ' + +[TM5_B] +~w~OK, мне надоело это дерьмо. + +[TM5_C] +~w~Нужно закончить разборки с Триадой раз и навсегда! + +[TM5_D] +Лысый установил бомбу в мусоровоз. + +[TM5_E] +~w~Бомба на таймере, так что много времени на размышления у тебя не будет. Бери машину. + +[TM5_F] +~w~Будь осторожен на дороге, Лысый сказал. что бомба может рвануть от любого столкновения! + +[TM5_G] +~w~Они откроют ворота для мусоровоза, так что ты без проблем проедешь на завод. + +[TM5_H] +~w~Припаркуй мусорку между двумя резервуарами с бензином и быстрее уноси ноги! + +[TM5_I] +~w~Пусть пойдет дождь из скумбрии. + +[TM5_J] +~w~Нам нужен библейский апокалипсис, а не какая-то дешевка. + +[TM_BUST] +Число ваших арестов + +[TM_DED] +Число визитов в больницу + +[TONI] +ЗАДАНИЯ ТОНИ + +[TONIGO] +~g~Тони повез свою мамочку в оперу. Зайди позже! + +[TONI_P] +У меня есть для тебя одно срочное дело! - Тони + +[TOWERS] +Хепберн Хейтс + +[TOYZ] +Фургон игрушек + +[TRAIN] +Поезд + +[TRAIN_1] +Станция Куровски + +[TRAIN_2] +Станция Ротвелл + +[TRAIN_3] +Станция Бейли + +[TRASH] +~g~Ты сильно помял свою тачку! Езжай и отремонтируй ее! + +[TRASHM] +Мусоровоз + +[TRINST] +Тройной безумный трюк + +[TSCORE] +ЗАРАБОТАНО: $~1~ + +[TSCORE2] +$~1~ + +[TTUTOR] +Чтобы заняться работой таксиста или отказаться от нее, нажми кнопку ~h~~k~~TOGGLE_SUBMISSIONS~~w~. + +[TTUTOR2] +Чтобы заняться работой таксиста или отказаться от нее, нажми кнопку ~h~~k~~TOGGLE_SUBMISSIONS~~w~. + +[TUBE1] +Как только откроется метро, ты сможешь на нем проехать на остров Стаунтон. + +[TUBE2] +Как только откроется Шорсайд Вейл, ты сможешь выйти на вокзале Шорсайд, чтобы попасть в аэропорт Фрэнсис. + +[TUBE_2] +чтобы сесть на электричку в метро - нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~. + +[TUNNEL] +Как только откроется туннель Портер, ты сможешь проехать на остров Стаунтон. + +[TUNNELP] +Туннель Портер + +[UNFRM1] +UnFormatMemCard 1 (teststuff) + +[UNIVERS] +Учебный городок + +[UPSIDE] +~r~Ты перевернул тачку! + +[USJ] +ПРИЗ ЗА ОСОБЫЙ ТРЮК! + +[USJI1] +TEXT NO LONGER REQUIRED + +[USJI2] +TEXT NO LONGER REQUIRED + +[USJI3] +TEXT NO LONGER REQUIRED + +[USJ_ALL] +ТЫ ИСПОЛНИЛ ВСЕ ОСОБЫЕ ТРЮКИ! + +[UZI_IN] +В магазин оружия завезли 'Узи'! + +[VANHSTP] +Решил вскрыть еще несколько Инкассаторов? Пригони их в наш гараж в гавани Портланда. + +[WANTED1] +~g~Стряхни копов с хвоста и понизь их заинтересованность в тебе! + +[WANT_A] +Тебя арестовывают лишь в том случае, если ты ~h~объявлен в розыск~w~. + +[WANT_B] +Величина ~h~уровня розыска~w~ отображается строкой звездочек в верхнем правом углу экрана. + +[WANT_C] +Сейчас твой ~h~уровень розыска~w~ равен одному... + +[WANT_D] +двум... + +[WANT_E] +трем... + +[WANT_F] +Чем выше твой ~h~уровень розыска~w~, тем больше сил будет брошено на твой арест. + +[WANT_G] +После ~h~ареста~w~ тебя отвозят в ближайший полицейский участок + +[WANT_H] +Копы забирают у тебя все оружие и часть денег в качестве взятки. + +[WANT_I] +Твое текущее задание будет считаться проваленным. + +[WANT_J] +Во время игры ты найдешь разные способы понижать ~h~уровень розыска~w~. + +[WANT_K] +~h~Уровень розыска~w~ сбрасывается после того, как машина будет ~h~ПЕРЕКРАШЕНА~w~. + +[WEATHE2] +ОБЫЧНАЯ ПОГОДА + +[WEATHER] +НЕНАСТЬЕ + +[WELCOME] +ДОБРО ПОЖАЛОВАТЬ + +[WHOOPEE] +М-р.Вупи + +[WIN_95] +Grand Theft Auto III }u ~ytu‚ ~t Windows 95 + +[WIN_DX] +Grand Theft Auto III ‚Ђuqѓu‚ DirectX ruЂЃxx }u }xvu 8.1 + +[WIN_RSZ] +]u ѓtp{~ЃЊ ѓЃ‚p}~rx‚Њ }~r~u ЂpwЂu€u}xu ЌzЂp}p + +[WIN_TTL] +Grand Theft Auto III + +[WIN_VDM] +Grand Theft Auto III ‚Ђuqѓu‚ }p rxtu~zpЂ‚u }u |u}uu 12\q p|Џ‚x + +[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). + +[WRCONTE] +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). + +[WRECKED] +~r~Машина разбита! + +[WRONGCD] +Неверный диск. Пожалуйста, вставьте нужный диск. + +[WRONGT1] +~g~За работой приходи между 05:00 и 21:00 + +[WRONGT2] +~g~За работой приходи между 06:00 и 14:00 + +[WRONGT3] +~g~За работой приходи между 15:00 и 00:00 + +[X] +x + +[Y1JLAST] +~r~Пришел последним! Ты болтаешь как водила, а водишь как болтун! + +[Y1_1ST] +~g~Ты пришел первым, пройдя ~1~ контрольных пунктов! + +[Y1_2ND] +~y~Ты пришел вторым, пройдя ~1~ контрольных пунктов. ~y~Неплохо, но ты не победил! + +[Y1_3RD] +~r~Ты пришел третьим, пройдя ~1~ контрольных пунктов. ~r~Я был о тебе более высокого мнения! + +[Y1_J1ST] +~y~Ты пришел первым, пройдя ~1~ контрольных пунктов. ~y~Хорошо, но ты должен быть лучшим, чтобы получить Королеву Лиззи! + +[Y1_J2ND] +~r~Ты пришел вторым, пройдя ~1~ контрольных пунктов. Ты водишь как бешеная макака! + +[Y1_LAST] +~r~Ты пришел последним! ~r~Ты просто тратишь мое время, ДУРАК! + +[Y1_TEST] +МАШИНА В ВОДЕ! + +[YAKUSA] +Торрингтон + +[YAKUZCR] +Стингер Якудзы + +[YANKEE] +Янки + +[YARDICR] +Лобо Ярди + +[YARDIE] +ЗАДАНИЯ ЯРДИ + +[YD1] +'БЫСТРЫЕ ТАЧКИ - ЛЕГКИЕ ДЕНЬГИ' + +[YD1GO] +~g~СТАРТ! + +[YD1_1] +~r~ОДИН + +[YD1_2] +~r~ДВА + +[YD1_3] +~r~ТРИ + +[YD1_A] +~w~Это король Куртни. + +[YD1_A1] +~w~Мои Ярди хотят устроить хорошую гонку, а я слышал, что ты крутой водила. + +[YD1_B] +~w~Приезжай на своей тачке на пустырь за стадионом и жди там других участников заезда. + +[YD1_BON] +$1000!! + +[YD1_C] +~w~Мои люди будут наблюдать за всеми контрольными точками. + +[YD1_CNT] +~1~ из 15! + +[YD1_D] +~w~Тот кто пройдет контрольный пункт первым - получит приз, и так далее. + +[YD1_D1] +~w~Если ты проедешь больше контрольных пунктов, чем мои ребята, я дам тебе работу. + +[YD1_E] +~g~Готовься к гонке! + +[YD1_F] +~g~Мощный старт - мне нравится твой стиль! + +[YD1_G] +~r~Это гонка на МАШИНАХ. Тебе нужна МАШИНА, кретин! + +[YD2] +'РЕЙД С 'УЗИ' + +[YD2_A] +~w~Посмотрим, сможешь ли ты сделать за меня грязную работу. + +[YD2_A1] +~w~Узнаем, можно ли тебе доверять. + +[YD2_B] +~w~Сейчас подойдут двое моих парней, и ты пойдешь покатаешься с ними, + +[YD2_B1] +~w~посмотрим, чего ты стоишь на самом деле. + +[YD2_C] +~w~Давай, покатайся по Хепберн Хейтс и прикончи нескольких Дьяволов - они надоедают королеве Лиззи. + +[YD2_CC] +~w~Вот, это тебе пригодится. + +[YD2_D] +~w~Вести и стрелять будешь ты, ну а мы проследим чтобы ты не скопытился. + +[YD2_E] +~w~Поехали! + +[YD2_F] +~r~Он имел что-то против нас, пореши этого ублюдка! + +[YD2_G1] +~w~Хепберн Хейтс...Давай ка прикончи несколько тупых Дьяволов... + +[YD2_G2] +~w~Но запомни, ~r~Из машины тебе выходить нельзя! + +[YD2_H] +~w~Ок, возвращаемся на нашу территорию! ВПЕРЕД! + +[YD2_L] +~w~Отличная работа, Жнец! + +[YD2_M] +~r~Он разбил мою тачку! Прикончи его! + +[YD2_N] +~w~У тебя 5 секунд, чтобы вернуться в тачку! + +[YD3] +'МАШИНЫ БАНД' + +[YD3_A] +Я хотел бы получить тачки некоторых банд + +[YD3_A1] +чтобы без помех провернуть дела на их территории. + +[YD3_B] +Мне нужен Сентинел Мафии, + +[YD3_B1] +Стингер Якудзы и + +[YD3_B2] +Жеребец Дьяволов, для маскировки под их людей. + +[YD3_C] +Отгони их в мой гараж в Ньюпорте, но запомни, + +[YD3_C1] +мне не нужны помятые тачки! + +[YD3_D] +Перебей номера + +[YD3_E] +~r~Ты уже своровал тачку Дьяволов! + +[YD3_F] +~r~Ты уже своровал тачку Мафии! + +[YD3_G] +~r~Ты уже своровал тачку Якудзы! + +[YD3_H] +~g~Тачка Дьяволов сворована! + +[YD3_I] +~g~Тачка мафии сворована! + +[YD3_J] +~g~Тачка Якудзы сворована! + +[YD3_K] +~r~Машина сильно помята! Отремонтируй ее! + +[YD3_L] +~g~Отгони ее в ~p~гараж~g~! + +[YD3_M] +~r~Ты перевернулся! Ищи другую тачку! + +[YD4] +'ПОДСТАВА' + +[YD4_1] +~g~Обкурившиеся психи! + +[YD4_2] +~g~Взорви фургоны камикадзе! + +[YD4_A] +Короче, слушай! + +[YD4_A1] +Отправляйся в Бедфорд Поинт. + +[YD4_A2] +Там у меня тайник в старой тачке, сгоняй за ней! + +[YD4_B] +ПИСЬМО: Говорят, ты нашел себе работу. Я тоже не сидела сложа руки. + +[YD4_C] +Пришло тебе время почувствовать тебе силу СПАНКа! Besos y fuderes, Каталина, xxx. + +[YD4_D] +PS: УМРИ, ПАРШИВЫЙ ПЕС, УМРИ! + +[YD_P] +С тобой хочет побазарить король Куртни. Подойди к телефону на Аспатрии! + +[YES] +Да + +[Z] +Значение по оси Z: ~1~ + +{ re3 updates } +{ new languages } +[FEL_JAP] +ЯПОНСКИЙ + +[FEL_POL] +ПОЛЬСКИЙ + +[FEL_RUS] +РУССКИЙ + +{ new display menus } +[FET_GFX] +НАСТРОЙКА ГРАФИКИ + +[FED_MIP] +МИП-МАППИНГ + +[FED_AAS] +СГЛАЖИВАНИЕ + +[FED_FIL] +ТЕКСТУРНАЯ ФИЛЬТРАЦИЯ + +[FED_BIL] +БИЛИНЕЙНАЯ + +[FED_TRL] +ТРИЛИНЕЙНАЯ + +[FED_WND] +ОКОННЫЙ + +[FED_FLS] +ПОЛНОЭКРАННЫЙ + +[FEM_CSB] +CUTSCENE BORDERS + +[FEM_SCF] +ФОРМАТ ОКНА + +[FEM_ISL] +MAP MEMORY USAGE + +[FEM_LOW] +LOW + +[FEM_MED] +MEDIUM + +[FEM_HIG] +HIGH + +[FEM_2PR] +PS2 ALPHA TEST + +[FEC_FRC] +СВОБОДНАЯ КАМЕРА + +{ Linux joy detection } +[FEC_JOD] +DETECT JOYSTICK + +[FEC_JPR] +Press any key on the joystick of your choice that you want to use on the game, and it will be selected. + +[FEC_JDE] +Detected joystick + +{ mission restart } +[FET_RMS] +ПОВТОРИТЬ ЗАДАНИЕ + +[FESZ_RM] +НАЧАТЬ ЗАНОВО? + +{ more graphics } +[FED_VPL] +VEHICLE PIPELINE + +[FED_PRM] +PED RIM LIGHT + +[FED_RGL] +ROAD GLOSS + +[FED_CLF] +COLOUR FILTER + +[FED_WLM] +WORLD LIGHTMAPS + +[FED_MBL] +MOTION BLUR + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MOBILE + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +АВТО + +{ controls } +[FEC_IVP] +ИНВЕРТИРОВАТЬ ВЕРТИКАЛЬНУЮ ОСЬ + +{ map } +[FEM_TWP] +Поставить метку + +[FEA_FMN] +РАДИО ВЫКЛ + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +КОНТРОЛЛЕР XBOX 360 + +[FEC_ONE] +КОНТРОЛЛЕР XBOX ONE + +[FEC_NSW] +КОНТРОЛЛЕР NINTENDO SWITCH + +[FEC_TYP] +ГЕЙМПАД + +[FEC_CCF] +КОНФИГУРАЦИЯ + +[FEC_CF1] +СХЕМА 1 + +[FEC_CF2] +СХЕМА 2 + +[FEC_CF3] +СХЕМА 3 + +[FEC_CF4] +СХЕМА 4 + +[FEC_CDP] +ТИП УПРАВЛЕНИЯ + +[FEC_ONF] +ПЕШКОМ + +[FEC_INC] +В МАШИНЕ + +[FEC_VIB] +ВИБРАЦИЯ + +[FET_AGS] +НАСТРОЙКИ ГЕЙМПАДА + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +{ end of file } + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/spanish.txt b/utils/gxt/spanish.txt new file mode 100644 index 0000000..2775d34 --- /dev/null +++ b/utils/gxt/spanish.txt @@ -0,0 +1,8177 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. + + SPANISH NOTE: + This is European Spanish, do not mix it with Latin American Spanish. + If you want the Latin American Spanish translation you'd have to create + a separate txt for it. +} + +{ Grand Theft Auto III Spanish (Spain) Translation } +{ Contains some of the official fixes made by Rockstar for the iOS port } +{ Additional translation rewrites, corrections and fixes by IlDucci } + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~¡Oye! ¡Vuelve al vehículo! + +[IN_VEH2] +~g~¡Necesitas un coche para realizar este trabajo! + +[IN_BOAT] +~g~¡Necesitas una lancha para realizar este trabajo! + +[HEY] +~g~¡No te vayas por tu cuenta! ¡Mantén a tu grupo unido! + +[HEY2] +~g~¡No te separes del grupo! + +[HEY3] +~g~¡Has perdido a tu colega! ¡Vuelve a por 8-Ball! + +[HEY4] +~g~¡Si pierdes a Misty, Luigi te linchará! ¡Ve y recógela! + +[HEY5] +~g~Una de las chicas se ha escapado, ¡vuelve y recógela! + +[HEY6] +~g~Abandonaste tu honor con el kanbu de la yakuza. ¡Debes protegerlo! + +[HEY7] +~g~Te vendría bien una ayudita. ¡Vuelve y encuentra a tu contacto! + +[HEY8] +~g~Protección significa exactamente eso: ¡Protege al anciano oriental! + +[HEY9] +~g~¿Sabes lo que tienes que hacer? ¡Ver a tu contacto! + +[HELP2_A] +Pulsa el ~h~botón /~w~ mientras corres para ~h~esprintar~w~. + +[HELP3] +Sólo puedes esprintar durante cortos periodos de tiempo antes de cansarte. + +[HELP4_A] +Pulsa ~h~~k~~VEHICLE_ACCELERATE~~w~ para ~h~acelerar~w~. + +[HELP4_D] +Mueve el~h~ joystick analógico derecho~w~ hacia arriba para ~h~acelerar~w~. + +[HELP5_A] +Pulsa ~h~~k~~VEHICLE_BRAKE~~w~ para ~h~frenar~w~ o para ~h~dar marcha atrás~w~ si el vehículo está detenido. + +[HELP5_D] +Mueve el ~h~joystick analógico derecho~w~ hacia atrás para ~h~frenar~w~ o para ~h~dar marcha atrás~w~ si el vehículo está detenido. + +[HELP6_A] +Pulsa ~h~~k~~VEHICLE_HANDBRAKE~ ~w~para utilizar el ~h~freno de mano del vehículo~w~. + +[HELP6_C] +Pulsa ~h~~k~~VEHICLE_HANDBRAKE~ ~w~para utilizar el ~h~freno de mano del vehículo~w~. + +[HELP6_D] +Pulsa ~h~~k~~VEHICLE_HANDBRAKE~ ~w~para utilizar el ~h~freno de mano del vehículo~w~. + +[HELP7_A] +Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar~w~ con el fusil de francotirador. + +[HELP7_D] +Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar~w~ con el fusil de francotirador. + +[HELP8_A] +Pulsa ~h~~k~~PED_SNIPER_ZOOM_IN~ ~w~para ~h~aumentar el zoom ~w~de la mira del fusil y ~h~~k~~PED_SNIPER_ZOOM_OUT~~w~ para ~h~reducirlo~w~. + +[HELP9_A] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar ~w~el fusil de francotirador. + +[HELP10] +Esta insignia indica que la policía te está buscando. + +[HELP11] +Cuantas más insignias tengas, mayor será tu nivel de búsqueda. + +[HELP13] +En ocasiones necesitarás ir por caminos que no aparecen en el radar. + +[TIMER] +Ésta es una misión cronometrada, debes completarla antes de que el cronómetro llegue a cero. + +[MISTY1] +~r~¡Misty está para el arrastre! + +[OUT_VEH] +~g~¡Sal del vehículo! + +[GARAGE] +Mete el vehículo dentro del garaje y sal andando. + +[WANTED1] +~g~¡Despista a la poli y pierde tu nivel de búsqueda! + +[NODOORS] +~g~¡No son sardinas! Consigue un vehículo con suficientes asientos. + +[TRASH] +~g~¡Has dejado tu vehículo hecho unos zorros! ¡Haz que lo reparen! + +[WRECKED] +~r~¡El vehículo está destrozado! + +[HORN] +~g~Toca el claxon. + +[HORN4] +Pulsa el ~h~botón L3~w~ para tocar el ~h~claxon. + +[NOMONEY] +~g~¡Necesitas más pasta! + +[OUTTIME] +~r~¡Demasiado lento, tío, demasiado lento! + +[SPOTTED] +~r~¡Te siguen la pista! + +[REWARD] +RECOMPENSA: ~1~ $ + +[GAMEOVR] +FIN DE LA PARTIDA + +[Z] +Valor del eje Z: ~1~ + +[M_FAIL] +¡MISIÓN FALLIDA! + +[M_PASS] +¡MISIÓN SUPERADA! ~1~ $ + +[O_PASS] +¡TRABAJO ESPORÁDICO SUPERADO! + +[O_FAIL] +¡TRABAJO ESPORÁDICO FRACASADO! + +[DEAD] +¡ELIMINADO! + +[BUSTED] +¡TRINCADO! + +[S_PROMP] +Cuando no estés en una misión, podrás ~h~guardar tu partida aquí~w~. Esto hará avanzar el tiempo seis horas. + +[NUMBER] +~1~ + +[SCORE] +~1~ $ + +[LOADCAR] +CARGANDO VEHÍCULO... (PULSA EL BOTÓN L1 PARA CANCELAR) + +[CARSOFF] +Coches activados. + +[CARS_ON] +Coches desactivados. + +[TEXTXYZ] +Escribiendo coordenadas al archivo... + +[CHEATON] +Trucos ACTIVADOS + +[CHEATOF] +Trucos DESACTIVADOS + +[UZI_IN] +¡La Uzi ya está disponible en la tienda Ammu-Nation! + +[IMPORT1] +Sal y espera a tu vehículo. + +[PAGEB1] +Pistola entregada en tu guarida. + +[PAGEB2] +Uzi entregada en tu guarida. + +[PAGEB3] +Chaleco antibalas entregado en tu guarida. + +[PAGEB4] +Escopeta entregada en tu guarida. + +[PAGEB5] +Granadas entregadas en tu guarida. + +[PAGEB6] +Molotovs entregados en tu guarida. + +[PAGEB7] +AK47 entregado en tu guarida. + +[PAGEB8] +Fusil de francotirador entregado en tu guarida. + +[PAGEB9] +M16 entregado en tu guarida. + +[PAGEB10] +Lanzacohetes entregado en tu guarida. + +[PAGEB11] +Lanzallamas entregado en tu guarida. + +[WANT_A] +Sólo serás arrestado si tienes un ~h~nivel de búsqueda~w~. + +[WANT_B] +Tu ~h~nivel de búsqueda~w~ está representado por una hilera de estrellas en la parte superior derecha de la pantalla. + +[WANT_C] +Ahora tienes un ~h~nivel de búsqueda~w~ de una estrella... + +[WANT_D] +dos... + +[WANT_E] +tres... + +[WANT_F] +A medida que aumente tu ~h~nivel de búsqueda~w~, serás perseguido por fuerzas con una mayor potencia de fuego. + +[WANT_G] +Si te ~h~trinca~w~ la poli, te llevarán a la comisaría más cercana. + +[WANT_H] +Los agentes te requisarán todas tus armas y se quedarán con parte de tu dinero como soborno. + +[WANT_I] +Fracasarás cualquier misión que estuvieses llevando a cabo. + +[WANT_J] +A medida que avances en el juego, encontrarás formas de reducir tu nivel de búsqueda. + +[WANT_K] +Si estás en un coche, los ~h~TALLERES DE PINTURA~w~ te quitarán ~h~tu nivel de búsqueda~w~. + +[HEAL_B] +Cuando seas ~h~eliminado~w~, te llevarán al hospital más cercano. + +[HEAL_C] +Te quitarán tus armas y los médicos te cobrarán un dinero por curarte. + +[HEAL_E] +A medida que avances en el juego, encontrarás modos de curarte o de protegerte. + +[DAM] +DAÑO: + +[KILLS] +MUERTES: + +[FARES] +CARRERAS: + +[BULL] +LINGOTES: + +[EVID] +PRUEBAS: + +[HEALTH] +ESTADO DEL COCHE: + +[COLLECT] +RECOGIDOS: + +[BOMB] +Mete tu vehículo en la tienda de ~h~bombas~w~ para poner una. Coste: ~h~1.000 $~w~. + +[SAVE1] +Pasa por la puerta para ~h~guardar la partida~w~. No podrás guardar durante una misión. + +[SAVE2] +Cualquier vehículo que dejes en este garaje se conservará al guardar la partida. + +[AMMU] +Entra en la tienda Ammu-Nation para comprar armas. + +[BRIDGE1] +Podrás conducir hasta Staunton Island cuando el puente Callahan esté reparado. + +[TUNNEL] +Podrás conducir hasta Staunton Island cuando el túnel Porter esté abierto. + +[LUIGI] +MISIONES DE LUIGI + +[TONI] +MISIONES DE TONI + +[JOEY] +MISIONES DE JOEY + +[FRANK] +MISIONES DE SALVATORE + +[DIABLO] +MISIONES DE DIABLO + +[ASUKA] +MISIONES DE ASUKA + +[B_SITE] +MISIONES SUBURBANAS DE ASUKA + +[KENJI] +MISIONES DE KENJI + +[RAY] +MISIONES DE RAY + +[LOVE] +MISIONES DE LOVE + +[YARDIE] +MISIONES DE JAMAICANOS + +[HOOD] +MISIONES DE HOOD + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Muelle Atlantic + +[PORT_E] +Puerto de Portland + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Playa de Portland + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Red Light District + +[TOWERS] +Cerros de Hepburn + +[HARWOOD] +Harwood + +[ROADBR1] +Puente Callahan + +[ROADBR2] +Puente Callahan + +[TUNNELP] +Túnel Porter + +[BOMB1] +Taller de 8-Ball + +[COM_ZON] +Staunton Island + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Campus de Liberty + +[CONSTRU] +Fort Staunton + +[PARK] +Parque Belleville + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Aeropuerto Int. Francis + +[PROJECT] +Wichita Gardens + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Presa Cochrane + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Ambulancia + +[CAR_2] +Camión de bomberos + +[CAR_3] +Coche patrulla + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Rhino + +[CAR_7] +Coche del FBI + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_10] +Coach + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr. Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Autobús + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Mañana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Cabbie + +[CAR_39] +Buggy + +[LUIGIS] +Club de Luigi + +[GOAWAY] +~g~¡Ya estás en una misión! + +[LUIGGO] +~g~Luigi está entrevistando a unas chicas nuevas... ¡Vuelve más tarde! + +[JOEYGO] +~g~Joey ha salido con Misty. ¡Pásate más tarde! + +[TONIGO] +~g~Toni ha llevado a su mamá a la ópera... ¡Vente en otro momento! + +[KEMUGO] +~g~María y Kemuri están muy liadas... ¡Pásate más tarde! + +[KENJGO] +~g~Kenji está reunido con la yakuza, ¡ven en otro momento! + +[RAYGO] +~g~Ray tiene otros baños por los que rondar, ¡ven en otro momento! + +[LOVEGO] +~g~Donald Love tiene otros asuntos que atender. ¡Pide una cita más tarde! + +[KENSGO] +~g~¡Kenji está ocupado! ¡Llama más tarde! + +[ASUSGO] +~g~¡Asuka no está disponible en este momento! + +[HOODGO] +~g~¡Los Hoods no pueden atenderte en este momento! + +[WRONGT1] +~g~Vuelve entre las 05:00 y las 21:00 para buscar trabajo. + +[WRONGT2] +~g~Vuelve entre las 06:00 y las 14:00 para buscar trabajo. + +[WRONGT3] +~g~Vuelve entre las 15:00 y las 00:00 para buscar trabajo. + +[GUN_1A] +Pulsa ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~ y ~h~~k~~PED_CYCLE_WEAPON_LEFT~ ~w~para cambiar de arma. + +[GUN_2A] +¡Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar automáticamente~w~ y pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para ~h~disparar! Intenta darle a las dianas... + +[GUN_2C] +¡Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar automáticamente~w~ y pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para ~h~disparar! Intenta darle a las dianas... + +[GUN_2D] +¡Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar automáticamente~w~ y pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para ~h~disparar! Intenta darle a las dianas... + +[GUN_3A] +Mientras mantienes pulsado ~h~~k~~PED_LOCK_TARGET~,~w~ pulsa ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ o ~h~~k~~PED_CYCLE_TARGET_RIGHT~ para cambiar de objetivo. + +[GUN_3B] +Mientras mantienes pulsado ~h~~k~~PED_LOCK_TARGET~,~w~ pulsa ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ o ~h~~k~~PED_CYCLE_TARGET_RIGHT~ para cambiar de objetivo. + +[GUN_4A] +Mientras mantienes pulsado ~h~~k~~PED_LOCK_TARGET~~w~, puedes moverte mientras sigues apuntando a tu objetivo. + +[GUN_4B] +Mientras mantienes pulsado ~h~~k~~PED_LOCK_TARGET~~w~, puedes moverte mientras sigues apuntando a tu objetivo. + +[GUN_5] +Puedes practicar disparando a estas dianas. Vuelve a la misión cuando hayas terminado. + +[TAXI1] +~g~Busca un cliente. + +[FARE1] +~g~Ve al ~w~club Sex Kitten Meeouch ~g~en el Red Light District. + +[FARE2] +~g~Ve a ~w~Supa Save ~g~en Portland View. + +[FARE3] +~g~Ve a ~w~la sala Clásica ~g~en Chinatown. + +[FARE4] +~g~Ve a la ~w~cafetería Greasy Joe ~g~en Callahan Point. + +[FARE5] +~g~Ve a la ~w~tienda de armas Ammu-Nation ~g~en el Red Light District. + +[FARE6] +~g~Ve a ~w~Easy Credit Autos ~g~en Saint Mark's. + +[FARE7] +~g~Ve al ~w~bar de topless de Woody ~g~en el Red Light District. + +[FARE8] +~g~Ve al ~w~restaurante Marcos ~g~en Saint Mark's. + +[FARE9] +~g~Ve al ~w~taller de importación y exportación ~g~en el puerto de Portland. + +[FARE10] +~g~Ve a ~w~Punk Noodles ~g~en Chinatown. + +[FARE12] +~g~Ve al ~w~estadio de fútbol ~g~en Aspatria. + +[FARE13] +~g~Ve a la ~w~iglesia ~g~de Bedford Point. + +[FARE14] +~g~Ve al ~w~casino ~g~de Torrington. + +[FARE15] +~g~Ve a la ~w~universidad ~g~en el Campus de Liberty. + +[FARE16] +~g~Ve al ~w~centro comercial ~g~en la zona del parque Belleville. + +[FARE17] +~g~Ve al ~w~museo ~g~de Newport. + +[FARE18] +~g~Ve al ~w~edificio de AmCo ~g~en Torrington. + +[FARE19] +~g~Ve a ~w~Bolt Burgers ~g~en Bedford Point. + +[FARE20] +~g~Ve al ~w~parque ~g~de Belleville. + +[FARE21] +~g~Ve al ~w~Aeropuerto Internacional Francis. + +[FARE22] +~g~Ve a la ~w~presa Cochrane~g~. + +[FARE24] +~g~Ve al ~w~hospital ~g~de Pike Creek. + +[FARE25] +~g~Ve al ~w~parque ~g~de Shoreside Vale. + +[FARE26] +~g~Ve a las ~w~Torres del Noroeste ~g~de Wichita Gardens. + +[NEW_TAX] +¡MÁS GRANDES! ¡MÁS RÁPIDOS! ¡MÁS DUROS! Los nuevos taxis Borgnine abren sus puertas en Harwood. ¡Llame hoy al 555-BORGNINE! + +[TSCORE2] +~1~ $ + +[IN_ROW] +¡Racha de ~1~ clientes! Premio de ~1~ $. + +[TTUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de taxi. + +[TTUTOR2] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de taxi. + +[ATUTOR2] +~g~Lleva a los pacientes al hospital CON CUIDADO. Cada golpe reducirá sus posibilidades de supervivencia. + +[A_TIME] ++~1~ segundos + +[A_FULL] +~r~¡Ambulancia llena! + +[A_RANGE] +~g~La radio de la ambulancia está fuera de cobertura, ¡acércate a un hospital! + +[FTUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones del camión de bomberos. + +[FTUTOR2] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones del camión de bomberos. + +[F_PASS1] +¡Fuego apagado! + +[F_RANGE] +~g~La radio del camión de bomberos está fuera de cobertura, ¡acércate a una estación de bomberos! + +[C_BREIF] +~g~Sospechoso visto por última vez en: ~a~. + +[C_RANGE] +~g~La radio de la policía está fuera de cobertura, ¡acércate a una comisaría! + +[DODO_FT] +¡Has volado durante ~1~ segundos! + +[EBAL_A] +Conozco un lugar en las afueras del Red Light District donde podemos escondernos, + +[EBAL_A1] +pero mis manos están destrozadas, así que conduce tú, hermano. + +[EBAL_1] +Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un vehículo. + +[EBAL_1B] +Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un vehículo. + +[EBAL_2] +~g~¡Vuelve al coche! + +[EBAL_3] +Éste es el ~h~radar~w~. Utilízalo para guiarte por la ciudad. ¡Ve hasta la ~h~señal~w~ del ~h~radar~w~ para encontrar el escondite! + +[EBAL_D] +Conozco a un tipo con contactos, se llama Luigi. + +[EBAL_D1] +Somos viejos amigos, así que podremos buscarte curro. Vamos a verlo. + +[EBAL_E] +Venga, vamos a pasarnos por allí y te presentaré. + +{ UNUSED } + +[EBAL_I] +El jefe os recibirá enseguida... + +[EBAL_J] +8-Ball tiene cosas que hacer arriba. + +[EBAL_K] +Quizá puedas hacerme un favor. + +[EBAL_L] +Una de mis chicas necesita que la lleven, así que consigue un coche, recoge a Misty en la clínica y tráela aquí. + +[EBAL_N] +¡Así que no quites las manos del volante! + +[EBAL_4] +~r~¡8-Ball ha muerto! + +[EBAL_5] +~g~¡Consigue un vehículo! + +[EBAL_6] +~g~¡Recoge a Misty! + +[LM1] +'LAS CHICAS DE LUIGI' + +[LM2] +'NO DROGUES A MI ZORRA' + +[LM3] +'LLEVA A MISTY POR MÍ' + +[LM5] +'EL BAILE DE LA POLICÍA' + +[LM1_2] +~g~Lleva a Misty al club de Luigi. + +[LM1_3] +~g~Toca el claxon para que la chica entre en el coche. + +[LM1_6] +~g~¡Vuelve al coche! + +[LM1_7] +Para el vehículo junto a Misty y déjala que suba. + +[LM1_8] +Puedes ir a ver a Luigi para que te dé más trabajo o explorar Liberty City. + +[LM2_A] +Hay una nueva droga en la ciudad llamada SPANK. + +[LM2_E] +Algún listillo ha pasado esta basura a mis chicas en el puerto de Portland. + +[LM2_B] +¡Ve y repásale la cara con un bate de béisbol! + +[LM2_G] +¡Quiero una compensación por este insulto! + +[LM2_1] +~g~Llévate su coche y repíntalo. + +[LM2_2A] +¡Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~dar puñetazos~w~, ~h~patadas~w~ o ~h~pegar ~w~con el bate! + +[LM2_2C] +¡Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~dar puñetazos~w~, ~h~patadas~w~ o ~h~pegar ~w~con el bate! + +[LM2_2D] +¡Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~dar puñetazos~w~, ~h~patadas~w~ o ~h~pegar ~w~con el bate! + +[LM2_3] +~g~¡Mete el coche en el garaje de Luigi! + +[LM2_4] +~g~¡Vuelve a pintar el coche! + +[LM3_A] +Hola, tengo que hablar contigo... Bueno, Mick, ya hablaremos. + +[LM3_B] +¿Cómo te va, chico? + +[LM3_C] +El hijo del Don, Joey Leone, quiere fiesta con su chica de siempre, Misty. + +[LM3_D] +Recógela en los cerros de Hepburn, + +[LM3_E] +pero cuidado, ese es territorio de los Diablos. + +[LM3_F] +Luego llévala rapidito hasta su taller en Trenton, + +[LM3_H] +¡así que fíjate en el camino y no en Misty! + +[LM3_1D] +Pulsa ~h~~k~~VEHICLE_HORN~~w~ para tocar el ~h~claxon~w~ y avisar a Misty. + +[LM3_2] +~g~Lleva a Misty al local de Joey. + +[LM3_4] +~g~¡Recoge a Misty! + +[LM3_5] +Ahora trabajas para Luigi, ¿no? ¡Ya era hora de que tuviese un conductor de confianza! + +[LM3_7] +Estaré contigo en un momento, bujía mía. + +[LM3_10] +~g~¡Consigue un vehículo! + +[LM4_B] +Resuelve esto por mí. + +[LM4_C] +Si necesitas un arma, ve a la parte de atrás del Ammu-Nation que está enfrente del metro. + +[LM5_A] +El baile de la policía se está celebrando en la sala Clásica, cerca del puente, + +[LM5_B] +y parece que tienen ganas de una fiesta más ''clásica''. + +[LM5_C] +Tengo chicas haciendo la calle. + +[LM5_D] +Llévalas al baile, sacarán una buena tajada. + +[LM5_1] +~g~¡Tienes a las chicas tan embutidas que les van a salir moratones! Primero deja a las que llevas y luego ve a por más. + +[LM5_2] +~r~¡Una chica de Luigi la ha espichado! + +[LM5_3] +~g~¡Necesitas un coche! + +[LM5_4] +~g~Recoge a las chicas que trabajan en Saint Mark's. + +[LM5_5] +~g~¡Lleva a las chicas al baile de la policía! + +[LM5_8] +~g~Chicas trabajando en el baile: ~1~ + +[JM2] +'DESPEDIDA A LEE CHONG ''EL GORDO''' + +[JM4] +'EL CHÓFER DE CIPRIANI' + +[JM5] +'FIAMBRE EN EL MALETERO' + +[JM1_1] +~g~Lleva el coche de Forelli al taller que tiene 8-Ball al norte, tras Easy Credit Autos. + +[JM1_2] +~g~Vuelve a aparcar el coche frente al restaurante de Marco. + +[JM1_3] +~g~¡Activa la bomba del coche y luego sal de ahí! + +[JM1_4] +~g~¡Te estás cargando el coche! ¡Repáralo! + +[JM1_5] +~g~¡La bomba del coche no está activada! + +[JM1_6] +~g~Vuelve a aparcar el coche en el sitio correcto. + +[JM1_8A] +~y~¡Ey, pero si es mi colega! + +[JM1_8B] +~y~El taller de bombas está automatizado. Sólo tienes que meter el coche, pararlo y el taller hará el resto. + +[JM1_8C] +~y~Ten, tu primer coche es gratis, pero los siguientes te los cobraré. + +[JM2_A] +Lee Chong ''el Gordo'' está traficando SPANK para una nueva banda de Colombia, o Colorado... O como se llame. + +[JM2_B] +No me acuerdo. El caso es que no importa. + +[JM2_D] +Ese cabrito ha vendido su último fideo. + +[JM2_E] +¡Lo quiero muerto! + +[JM2_G] +Búscate un nueve, queda claro, ¿no? + +[JM2_H] +Y recuerda, ve con ojo en Chinatown: es territorio de las Tríadas. + +[JM3_A] +Vamos a robar un furgón blindado. + +[JM3_B] +Sale de Chinatown todos los días. + +[JM3_C] +Las balas ni siquiera abollarán el furgón, así que coge un coche y échalo de la carretera. + +[JM3_D] +Si lo zurras bien, los imbéciles de los guardias se achantarán. + +[JM3_E] +Luego llévalo al almacén de los muelles y mis chicos se encargarán del resto. + +[JM3_F] +El furgón no estará fuera todo el día, así que no pierdas el tiempo. + +[JM3_1] +~g~Lleva el furgón al almacén. + +[JM3_2] +~g~Carga contra el furgón hasta que su daño esté por debajo del 70 por ciento. + +[JM4_B] +¡Ah, aquí está el tío que te decía! + +[JM4_C] +Bueno, éste no es italiano ni tampoco mecánico, pero sabe arreglar cosas. + +[JM4_D] +Él es el capo de papá, Toni Cipriani. + +[JM4_E] +Sí, soy Toni Cipriani. + +[JM4_F] +Llévale al restaurante de Mamma en Saint Mark's, ¿va? + +[JM4_G] +Oye, estoy planeando un curro que necesita un buen conductor, pásate por aquí más tarde, ¿estamos? + +[JM4_2] +¡Espera aquí! Deja el motor en marcha. Ésta no es una visita social. + +[JM4_3] +¡Es una emboscada de las Tríadas! ¡Sácanos de aquí, chico! + +[JM4_4] +Las Tríadas creen que pueden meterse conmigo. ¡Las Tríadas, CONMIGO! + +[JM4_6] +¡Mi coche! Dije que fueras con cuidado. + +[JM4_7] +~g~Lleva a Toni al restaurante de Mamma. + +[JM4_8] +~r~¡Toni la ha palmado! + +[JM5_A] +Guapo, muy guapo... + +[JM5_B] +¡Justo te estaba buscando! + +[JM5_D] +Uno de los Forelli se las daba de mafioso y se llevó su merecido. + +[JM5_E] +Lleva el cuerpo a la trituradora en Harwood, ¿vale? + +[JM5_1] +~g~¡Llévalo a la trituradora! + +[JM5_2] +~g~¡Son los hermanos Forelli! + +[JM6_A] +Vaya cochazo, ¿eh? + +[JM6_B] +Bueno, escucha. Lleva un coche a la casa franca de Saint Mark's y recoge a unos colegas. + +[JM6_C] +Van a atracar un banco y necesitan un conductor. + +[JM6_D] +Di mi palabra de que eras su hombre, así que no la cagues. + +[JM6_E] +Llévalos al banco antes de las cinco en punto, ni un minuto después. + +[JM6_2] +Mantén el motor en marcha, entraremos y saldremos enseguida. + +[JM6_3] +¡Sácanos de aquí! + +[JM6_4] +¡Líbrate de la poli y llévanos a la casa franca! + +[JM6_6] +~g~¡Busca un vehículo menos llamativo! + +[JM6_7] +~g~¡Necesitas a los 3 para robar el banco! + +[TM1] +'SACANDO LA COLADA' + +[TM2] +'LA ENTREGA' + +[TM3] +'SALVATORE HA CONVOCADO UNA REUNIÓN' + +[TM4] +'TRÍADAS Y TRIBULACIONES' + +[TM5] +'LLUVIA DE PECES' + +[TONI_P] +¡Tengo un trabajo urgente para ti! -Toni + +[TM1_A] +Siéntate, chico, siéntate de una vez. + +[TM1_B] +La lavandería no quiere pagar la protección, ¿eh? + +[TM1_C] +¿Las Tríadas creen que pueden meterse conmigo? + +[TM1_D] +¡Se van a enterar esos aspirantes a tipos duros de lo que es ser un tipo duro! + +[TM1_E] +¡Eso, enséñales respeto! ¡Las Tríadas no se ríen de ninguno de mis hijos! + +[TM1_F] +Tu padre, Dios acoja su alma, no toleraba ni media a las Tríadas en Sicilia. + +[TM1_G] +Lo siento, mamá. Sí, mamá. + +[TM1_H] +Quiero que te cargues sus furgonetas de lavandería + +[TM1_I] +y a cualquiera de las Tríadas que se cruce en tu camino. + +[TM1_J] +8-Ball te dará lo que necesites. + +[TM2_A] +Toni ha salido a partir cabezas, o a intentarlo... + +[TM2_AA] +Nunca será tan duro como su padre. Te dejó una nota en la mesa. + +[TM2_B] +La lavandería ya quiere pagar. ¡Buen trabajo, chico! + +[TM2_C] +Ve a por el dinero y tráelo aquí. Cuidado con las Tríadas. + +[TM2_D] +Puede que intenten metértela doblada, pero no te dejes. + +[TM2_E] +¡Nadie, quiero decir nadie, se mete con Toni Cipriani! + +[TM2_1] +~g~¡Lleva el dinero a Toni! + +[TM2_2] +~g~¡Los dejaste tiesos a todos! + +[TM3_MA] +¡No sé dónde está! + +[TM3_MB] +Te juro que mi hijo a veces no sabe lo que hace. + +[TM3_MC] +Ahora bien, su padre era diferente. Siempre en la cumbre, pendiente de todo, valiente... + +[TM3_A] +Don Salvatore ha convocado una reunión. + +[TM3_B] +Necesito que vayas a por la limusina y su hijo, Joey, a su taller. + +[TM3_C] +Luego recoge a Luigi en su club, ven a buscarme, + +[TM3_D] +y nos iremos todos juntos a la casa del jefe. + +[TM3_E] +Los de las Tríadas no saben cuándo parar... + +[TM3_F] +Si quieren guerra, la tendrán. + +[TM3_G] +Ponte en marcha. + +[TM3_1] +~g~Ve al taller de Joey a por la limusina. + +[TM3_2] +~g~Recoge a Luigi. + +[TM3_3] +~g~Ahora recoge a Toni. + +[TM3_4] +~g~Lleva a los mafiosos a la casa de Salvatore. + +[TM3_5] +~y~¡Es una emboscada de las Tríadas! + +[TM4_B] +¡Esto es la guerra! Las Tríadas tienen una piscifactoría como tapadera. + +[TM4_C] +Casi todos sus negocios pasan por la lonja de Chinatown. + +[TM4_D] +La lavandería todavía no ha pagado la protección. + +[TM4_E] +Creen que está protegida por las Tríadas, así que creo que se merecen un castigo. + +[TM4_F] +¡Llévate a estos chicos y elimina a los líderes de las Tríadas! + +[TM4_G] +Qué narices, si ves la oportunidad, cárgate también a unos cuántos de sus soldados. + +[TM4_GAT] +~g~Necesitas un camión de pescado de las Tríadas para entrar. + +[TM5_B] +Vale, ya me he hartado de esta mierda. + +[TM5_C] +¡Vamos a acabar con las Tríadas en Liberty de una vez por todas! + +[TM5_D] +8-Ball ha instalado una bomba en un camión de la basura. + +[TM5_E] +Lleva un temporizador, así que si te despistas, no quedará nada de ti. Ve a por el camión. + +[TM5_F] +¡Cuidado, 8-Ball dice que es muy sensible y que cualquier golpe puede hacerlo estallar! + +[TM5_G] +Su piscifactoría abrirá sus puertas a uno de sus camiones, así que podrás entrar sin más. + +[TM5_H] +¡Aparca entre los tanques de gas y lárgate de ahí! + +[TM5_I] +Quiero que llueva pescado. + +[TM5_J] +En plan bíblico, nada de bajo presupuesto. + +[FM2] +'CORTANDO LA HIERBA' + +[FM4] +'ÚLTIMA VOLUNTAD' + +[FM1_A] +Mis chicos y yo queremos hablar de negocios, + +[FM1_B] +así que esta tarde vas a cuidar de mi chica. + +[FM1_C] +¡OYE, MARÍA! ¡MUEVE ESE CULO! + +[FM1_D] +La muy idiota siempre hace lo mismo. + +[FM1_E] +Y aquí está, ¡la primera y única reina de Saba! + +[FM1_F] +¿Qué hacías arriba? + +[FM1_G] +Fuera lo que fuera, seguro que me cuesta dinero. + +[FM1_H] +Bueno, no pensarás que estoy aquí para conversar, ¿o sí? + +[FM1_I] +Métete en el coche y cierra el pico. + +[FM1_J] +Llévate la limusina, pero devuélvela de una pieza, ¿me has oído? + +[FM1_K] +Y cuidado con ella, es una lianta. + +[FM1_L] +¡Sí, sí, sí! Seguro que tu nuevo perrito faldero está pendiente de todo. + +[FM1_M] +¿No es fuerte y grande? + +[FM1_N] +¡Oye, tú, vamos a ver a Chico para que nos dé unas pirulas! + +[FM1_P] +~g~Ése de ahí es Chico, párate cerca. + +[FM1_S] +Aquí tienes, señorita. + +[FM1_TT] +¡UNA REDADA! + +[FM1_1] +~g~¡Vuelve al Stretch! + +[FM1_2] +~g~¡Entra en el Stretch! + +[FM1_3] +~r~Si abandonas a María, Salvatore hará que te maten. Vuelve a por ella. + +[FM1_4] +~g~¡Has dejado a la chica del Don tirada! ¡Vuelve al almacén y espera a María! + +[FM1_5] +~g~¡Lleva a María sana y salva con Salvatore! + +[FM1_6] +~g~¡Chico no estará esperando todo el día, lleva a María al muelle! + +[FM1_7] +~r~¡María está muerta! A Salvatore le va a sentar muy mal... + +[FM1_8] +~r~¡Te cargaste al camello de María! + +[FM2_J] +Déjanos a solas un momento. + +[FM2_A] +El cártel colombiano fabrica SPANK en Liberty, + +[FM2_K] +pero no sabemos dónde y parece como si supieran qué vamos a hacer antes de que lo pensemos. + +[FM2_L] +Hay un tipo llamado Curly Bob que trabaja en el bar de Luigi. + +[FM2_M] +Ha estado gastando más dinero del que gana. + +[FM2_N] +Suele ir del trabajo a casa en taxi, así que síguelo. + +[FM2_O] +Y si nos está vendiendo..., mátalo. + +[FM2_F] +Aquí viene nuestro amiguito. El señor Bocón en persona. + +[FM2_G] +¿Te siguieron? Ya sabes que esto es nuestro secretito. + +[FM2_H] +No, no, no me han seguido. ¿Tienes lo mío? + +[FM2_I] +Toma tu SPANK, soplón, ahora habla. + +[FM2_P] +Vale, los Leone libran una guerra en dos frentes. + +[FM2_Q] +Tienen una guerra territorial con las Tríadas y no parece que ninguno se vaya a rendir. + +[FM2_R] +Mientras tanto, Joey Leone ha cabreado a los Forelli. + +[FM2_S] +Cada día pierden más hombres e influencia. + +[FM2_T] +Salvatore se está volviendo peligroso y paranoico. Sospecha de todos y de todo. + +[FM2_U] +Con lo leales que son algunos, ¿de qué tendría que preocuparse? + +[FM2_1] +~g~¡Ahí está Curly Bob! + +[FM2_2] +~g~¡Curly ha salido del club, síguelo! + +[FM2_5] +~g~Llévale al puerto de Portland. + +[FM2_6] +~r~¡Curly no se meterá en un taxi cascado! + +[FM2_7] +~r~¡Curly se ha asustado! ¡Han cancelado la reunión! + +[FM2_8] +~g~¡Elimina a Curly Bob! + +[FM2_9] +~r~¡Curly Bob la ha espichado! + +[FM2_10] +~r~¡Curly se ha largado! + +[FM2_11] +~g~Aparca enfrente del club de Luigi;Curly Bob saldrá pronto. + +[FM2_12] +~r~¡Te ha dado esquinazo! + +[FM3_A] +Tenemos que despachar a esos puñeteros colombianos, + +[FM3_B] +pero mientras estemos en guerra con las Tríadas, no tendremos la fuerza necesaria. + +[FM3_C] +El cártel tiene fondos ilimitados gracias a esa mierda del SPANK. + +[FM3_D] +Si les atacamos abiertamente, nos harán picadillo. + +[FM3_E] +Estarán fabricando el SPANK en ese barco grande al que te llevó Curly. + +[FM3_F] +Así que tenemos que actuar con cuidado, o mejor dicho, tú tienes que hacerlo. + +[FM3_G] +Te estoy pidiendo que destruyas esa fábrica de SPANK como un favor personal para mí, Salvatore Leone. + +[FM3_H] +Si lo haces, entrarás en la familia. Tendrás todo lo que quieras. + +[FM3_I] +Ve a ver a 8-Ball, necesitarás su ayuda para volar ese barco. + +[FM3_8A] +¡Hombre, colega! Salvatore me avisó, + +[FM3_8B] +pero este trabajo va a necesitar muchos fuegos artificiales. + +[FM3_8D] +pero ya sabes que conmigo valdrá la pena. + +[FM3_8E] +Venga, ¡vamos allá! + +[FM3_8F] +Puedo reventar el barco, pero aún no puedo usar una pipa con estas manos. + +[FM3_8G] +¡Toma, con este fusil podrás reventar unas cuantas cabezas! + +[FM3_4] +~g~¡Para el coche y deja que 8-Ball salga! + +[FM3_7] +~r~¡8-Ball ha pasado a mejor vida! + +[FM3_8] +~r~¡Los guardias han sido alertados! + +[FM4_A] +¡Mi socio favorito! + +[FM4_B] +Estoy orgulloso, hijo, pusiste finos a esos sudacas. + +[FM4_C] +Tengo un trabajito más para ti antes de que podamos celebrarlo. + +[FM4_D] +Hay un coche a la vuelta del edificio del club de Luigi. + +[FM4_E] +El interior está pringado de sesos. + +[FM4_F] +Tuvimos que ayudar a un tipo a que se decidiera y fue un poco... sucio. + +[FM4_H] +Llévalo a la trituradora antes de que lo encuentre la pasma. + +[AM3] +'PURGA DEL PAPARAZZI' + +[AM4] +'LA PAGA DE RAY' + +[AM5] +'TANNER EL TRAIDOR' + +[AM1_A] +Tenemos que aclarar ciertos temas antes de seguir con cualquier clase de relación, + +[AM1_B] +sea de negocios u otra. Pongamos las cartas sobre la mesa. + +[AM1_C] +Soy yakuza y sé que trabajaste para la familia de Salvatore Leone. + +[AM1_D] +Puedo darte trabajo en nuestra organización, + +[AM1_E] +pero primero debes demostrarme que has cortado todos tus lazos con la mafia. + +[AM1_G] +Asegúrate de que no salga con vida. + +[AM1_H] +Mientras tanto, María y yo nos pondremos al día. + +[AM1_I] +Uy, Asuka, tienes un masajeador... + +[AM1_J] +Eso no es un masajeador. + +[AM1_1] +~g~¡Salvatore está saliendo del club de Luigi! + +[AM1_2] +~r~¡Te han descubierto! + +[AM1_3] +~r~¡Has perdido a Salvatore! + +[AM1_4] +~r~Muy bonito, ¡has asustado al objetivo! ¿Y te consideras un asesino? + +[AM1_5] +~g~Ve al Red Light District y espera a que Salvatore salga del club. + +[AM1_7] +~r~Salvatore está en su casita, sano y salvo, tomándose un cóctel. ¡Nadie te va a llamar ''Chacal''! + +[AM1_8] +~g~Salvatore saldrá del club de Luigi sobre las ~1~:~1~. + +[AM2_4] +~g~¡Has irrumpido como un elefante en una cacharrería! + +[AM3_A] +Un periodista ha estado husmeando por aquí. + +[AM3_B] +María y yo nos hemos ido de vacaciones hasta que puedas librarte de ese mirón pervertido. + +[AM4_A] +¡Si es mi guapo manitas! + +[AM4_B] +María está un poquito liada, pero le diré que has venido. + +[AM4_C] +¿Quién es, Asuka? Sé que he sido muy traviesa, ¡pero necesito mear! ¿Vale? + +[AM4_D] +Es hora de que conozcas a nuestro espía en la policía. + +[AM4_E] +Aquí está su pago por el último trabajito que hizo para nosotros. + +[AM4_F] +Él es comprensiblemente cauteloso. + +[AM4_G] +Ve a la cabina en Torrington tan rápido como puedas y espera sus instrucciones. + +[AM5_A] +María y yo hemos ido de compras. + +[AM5_B] +¡Nuestro contacto en la policía nos ha informado de que uno de nuestros conductores es un poli infiltrado que se mueve de forma extraña! + +[AM5_C] +Sin un coche es prácticamente un inútil, así que le hemos puesto un localizador. + +[AM5_D] +¡Haz que sufra! + +[AM5_1] +¡Tanner te ha pillado! + +[AS1] +'CEBO' + +[AS2] +'CAFÉ PARA LLEVAR' + +[AS4] +'RESCATE' + +[AS1_A] +Parece que Miguel piensa que le maltrato. + +[AS1_B] +Pero ha revelado cuánto teme Catalina tu búsqueda de venganza. + +[AS2_A] +Subestimamos los planes que tiene Catalina para el SPANK. + +[AS2_B] +Va mucho más allá de hacer que los jamaicanos lo vendan en las esquinas. + +[AS2_D] +Vende SPANK en puestos callejeros. + +[AS2_1] +~g~¡Todos los puestos de café en Portland han sido destruidos! + +[AS2_2] +~g~¡Todos los puestos de café en Staunton Island han sido destruidos! + +[AS2_3] +~g~¡Todos los puestos de café en Shoreside Vale han sido destruidos! + +[AS2_4] +~r~¡El cártel ha avisado a sus camellos! + +[AS2_5] +~g~¡Todavía hay puestos de café en Shoreside Vale y en Staunton Island! + +[AS2_6] +~g~¡Todavía hay puestos de café en Shoreside Vale! + +[AS2_7] +~g~¡Todavía hay puestos de café en Staunton Island! + +[AS2_8] +~g~¡Todavía hay puestos de café en Portland! + +[AS2_9] +~g~¡Todavía hay puestos de café en Portland y en Shoreside Vale! + +[AS2_10] +~g~¡Todavía hay puestos de café en Portland y en Staunton Island! + +[AS2_12] +~g~¡Recorre los distritos de Liberty y busca puestos de café ~b~Espresso-2-Go~g~! + +[AS3_A] +¿Lo apretamos un poco más o esperamos a que se vuelva negro y se caiga? + +[AS3_B] +Dale un toque rápido... + +[AS3_D] +¡Mi manitas! + +[AS3_E] +Me aburría, así que vine a hacer compañía a Asuka. + +[AS3_1] +~g~¡Busca una ~r~lancha~g~ y ve a la ~b~boya señalada~g~! + +[AS3_3] +~g~¡Espera a que la ~y~avioneta~g~ comience a acercarse! + +[AS3_5] +~g~¡Recupera el cargamento! + +[AS3_4] +~g~¡Usa un lanzacohetes para derribar ~y~la avioneta~g~! + +[AS3_2] +~b~¡Ve a la boya del aeropuerto señalada! ~y~¡El avión se está acercando! + +[AS3_6] +~g~~1~ DE 8 + +[KM1] +'LA FUGA DEL KANBU' + +[KM3] +'UN MAL TRATO' + +[KM4] +'SHIMA' + +[KM5] +'CONTRINCANTES TRAFICANTES' + +[KM1_A] +Mi hermana te tiene en gran estima, + +[KM1_E] +pero yo no estoy convencido de que un gaijin pueda ofrecerme algo más que desilusiones. + +[KM1_B] +Tal vez puedas ayudar con una situación que me perjudica. + +[KM1_F] +Por supuesto, el fracaso conllevará una desgracia. + +[KM1_C] +Un kanbu, o jefe de la yakuza, está bajo custodia a la espera de juicio. + +[KM1_G] +Es un miembro importante de la familia. + +[KM1_H] +Libéralo y llévalo al dojo de Bedford Point. + +[KM1_D] +Agradecemos tus generosas acciones. Si alguna vez necesitas ayuda, el dojo tendrá el honor de proporcionarte dos hombres que te ayudarán. + +[KM1_1] +~g~¡Roba un coche de la policía! + +[KM1_2] +~g~¡Instala una bomba en el coche! + +[KM1_3] +~g~Ahora llévalo al dojo de la yakuza. + +[KM1_5] +~g~Ahora ve a la comisaría. + +[KM1_6] +~g~¡Pon una bomba en el coche! + +[KM1_7] +~g~¡Sólo pueden entrar vehículos de policía autorizados! + +[KM1_9] +~r~No destruiste la pared con un coche bomba. + +[KM1_10] +~r~El kanbu está muerto, ¡y tu honor también! + +[KM1_11] +~r~¡La policía te ha seguido! + +[KM2_A] +Es imposible sobreestimar la importancia del protocolo en este trabajo. + +[KM2_B] +Para mi vergüenza eterna, un hombre me hizo una vez un favor y nunca pude corresponder su amabilidad. + +[KM2_C] +Su debilidad son los coches y nos ha pedido que adquiramos ciertos modelos para su colección. + +[KM2_F] +Mi honor lo exige. + +[KM2_2] +~g~Coche entregado. + +[KM3_A] +Cuando los problemas acechan, el tonto les da la espalda, mientras que el sabio los enfrenta. + +[KM3_B] +El cártel colombiano ha ignorado nuestras repetidas peticiones de que dejen en paz nuestros intereses en Liberty. + +[KM3_C] +Ahora están negociando con los jamaicanos para humillarnos aún más. + +[KM3_D] +Están cerrando un trato en la ciudad. + +[KM3_F] +Llévate a uno de mis hombres, roba un coche de los jamaicanos y presenta tus respetos a los colombianos. + +[KM3_E] +¡Nuestro honor exige que no dejes a nadie vivo! + +[KM3_2] +~g~Recoge a tu contacto. + +[KM3_3] +~g~¡La reunión está teniendo lugar en el aparcamiento del hospital de Rockford! + +[KM3_4] +~r~¡Han escapado! + +[KM3_6] +~g~¡Mátalos, mátalos a todos! + +[KM3_8] +~g~¡Necesitas un coche de los jamaicanos para este trabajo! + +[KM3_9] +~r~Uno de los colombianos ha muerto, se ha cancelado el trato. + +[KM3_10] +~r~¡El contacto está muerto! + +[KM4_A] +Para ser verdaderamente fuerte, es importante no mostrar debilidad. + +[KM4_C] +Hazte con el dinero inmediatamente para que podamos ingresarlo en las cuentas del casino. + +[KM4_1] +¡Ni puedo pagarte, ni lo haría aunque pudiera! + +[KM4_9] +¡Una banda de chavales acaba de arrasar el local! ¡Se han llevado todo! + +[KM4_2] +¡Sois inútiles! + +[KM4_10] +Además, ¿qué clase de yakuza eres tú? + +{ Unused string? } + +[KM4_3] +No pago a matones como vosotros para esto. Si quisiera esta clase de protección, llamaría a la policía, demonios. + +[KM4_4] +~g~¡Castiga a la banda responsable y recupera el ~b~dinero de la protección~g~! + +[KM4_7] +~r~¡El tendero la acaba de palmar! + +[KM4_5] +Donald Love desea que te pases por su terraza para tener una charla. + +[KM4_6] +¡Aquí está todo el dinero! + +[KM4_8] +~g~¡Maletín conseguido! + +[KM5_A] +¡Tú! ¡Qué oportuno por tu parte que muestres ahora tu despreciable cara! + +[KM5_B] +¡Parece que tu ataque para disuadir a los jamaicanos + +[KM5_B1] +de convertirse en aliados del cártel ha sido completamente inútil! + +[KM5_C] +¡Los traficantes jamaicanos están vendiendo SPANK por las calles de Liberty como si fueran perritos calientes! + +[KM5_D] +Estos cerdos del cártel se están riendo de nosotros, ¡de mí! + +[KM5_E] +¡Te daré una última oportunidad para demostrar que la fe que tiene mi hermana en ti está justificada! + +[KM5_F] +¡Aplasta a estos canallas y lava tu vergüenza en el río de la sangre de nuestros enemigos! + +[KM5_3] +~r~No has matado a ~1~ jamaicanos. + +[KM5_4] +~g~Enhorabuena, has matado a ~1~ jamaicanos. + +[KM5_5] +~g~Enhorabuena, has matado a ~1~ jamaicanos. PREMIO DE ~1~ $ + +[RM1] +'SILENCIA AL SOPLÓN' + +[RM3] +'FALTA DE PRUEBAS' + +[RM4] +'DE PESCA' + +[RM5] +'DESESCAYOLADOR' + +[RM1_D] +Se encuentra bajo protección armada en una propiedad de WitSec en el centro de Newport, en algún piso que hay detrás del aparcamiento. + +[RM1_E] +Quema el lugar, así saldrán y podrás cazarlos. Asegúrate de que no hable con nadie. + +[RM1_1] +~g~Ve a la casa de protección de testigos. + +[RM1_2] +~g~¡Cárgate a McAffrey! + +[RM2_A1] +¡Chico, estoy aquí! + +[RM2_A] +Un viejo colega del ejército tiene un negocio en Rockford. + +[RM2_D] +Necesitará refuerzos y a cambio te venderá todo su material a precios de ganga. + +[RM2_E] +Ray me avisó... Pero pensé que vendría más gente. + +[RM2_F] +Bueno, tres brazos son mejor que uno, así que sírvete. + +[RM2_G] +~g~¡Ve a ver a Phil! + +[RM2_H] +~r~¡Phil ha muerto! + +[RM2_L] +¡Caray! ¡Si hubieras estado a mi lado en Nicaragua, a lo mejor conservaría el brazo! + +[RM2_N] +Deja el dinero en el suelo. Ahora vete, ya me ocupo yo de la poli. + +[RM3_D] +Están transportando las pruebas por la ciudad. + +[RM3_E] +Tendrás que embestir al coche y recoger hasta la última prueba que se le caiga. + +[RM3_F] +En cuanto tengas todas, déjalas en tu coche y préndele fuego. + +[RM3_G] +Cuando acabes, nos irá mucho mejor, chico. + +[RM3_1] +~g~Deja las pruebas en un coche y después préndele fuego. + +[RM3_4] +~g~¡Al fiscal se le ha caído una prueba! + +[RM3_6] +~r~¡Las fotos circularán por toda la ciudad! + +[RM3_7] +~g~¡Ahora préndele fuego al coche! + +[RM4_A] +Creo que mi socio es un soplón. + +[RM4_C] +Casi todas las noches sale a pescar con su lancha cerca del faro que hay en Portland Rock. + +[RM4_D] +¡Roba una lancha de la policía y húndele las ganas de dar puñaladas traperas! + +[RM4_1] +~g~Roba una lancha de la policía. + +[RM4_2] +~g~¡Ve al faro y despacha al socio de Ray! + +[RM5_A] +¡Inútil de mierda! + +[RM5_A1] +¡La has cagado! Me la estoy jugando y ni siquiera eres capaz de matar a una maldita mosca. + +[RM5_B] +¡Te pagué una pasta para matar al testigo y aún sigue vivo! + +[RM5_B1] +¡Y hoy va a declarar ante los federales! + +[RM5_C] +Está a punto de ser trasladado del hospital general Carson, en Rockford. + +[RM5_D] +¡Si él canta, yo canto! + +[RM5_E] +¡Así que termina el trabajo por el que te pagué! + +[RM5_1] +~g~Intercepta la ambulancia. + +[RM5_2] +~g~¡Te han descubierto! + +[RM5_3] +~g~¡Era un señuelo! + +[RM5_4] +~g~¡Las balas no atraviesan esa escayola de cuerpo entero! + +[RM5_5] +~g~¡Esa escayola de cuerpo entero es ignífugo! + +[RM5_7] +~r~¡El testigo está a salvo! + +[RM5_8] +~g~¡El testigo se ha ahogado! + +[LOVE2] +'¡EXTERMINA AL WAKA-GASHIRA!' + +[LOVE3] +'UNA GOTA EN EL OCÉANO' + +[LOVE1_A] +Antes que nada, permíteme agradecerte que te hayas ocupado de ese asunto personal. + +[LOVE1_F] +Últimamente la gente tiende a malinterpretar las cosas. + +[LOVE1_D] +Pretenden exigirme más de lo pactado, pero no me gusta renegociar. + +[LOVE1_E] +Un trato es un trato, así que no van a ver ni un dólar mío. + +[LOVE1_G] +Rescata a mi amigo cueste lo que cueste. + +[LOVE1_2] +~g~Rescata al anciano oriental. + +[LOVE1_3] +~g~Lleva al anciano oriental de vuelta al edificio de Donald Love. + +[LOVE1_4] +~g~El anciano oriental debe estar detrás de alguna de estas puertas... + +[LOVE1_6] +~r~¡Las tripas del anciano oriental están desparramadas por las calles! + +[LOVE1_7] +~g~La puerta sólo se abrirá ante un coche de los colombianos. + +[LOVE2_A] +No hay nada como una clásica guerra de bandas para hacer que bajen los precios de los bienes inmuebles, + +[LOVE2_B] +excepto una plaga... pero eso sería excesivo en este caso. + +[LOVE2_C] +He observado que la yakuza y los colombianos no son precisamente buenos amigos. + +[LOVE2_D] +Vamos a aprovechar esta oportunidad de negocio. + +[LOVE2_E] +Quiero que mates a Kenji Kasen, el waka-gashira (segundo al mando) de la yakuza. + +[LOVE2_F] +Kenji está asistiendo a una reunión en lo alto del aparcamiento de Newport. + +[LOVE2_G] +¡Hazte con un coche de la banda del cártel y elimínalo! + +[LOVE2_H] +La yakuza debe culpar al cártel de esta declaración de guerra. + +[LOVE2_1] +~g~¡Ve a Fort Staunton y roba un coche de la banda de los colombianos! + +[LOVE2_2] +~g~¡Ahora ve al ~p~aparcamiento de Newport~g~ y cárgate a Kenji! + +[LOVE2_3] +~r~¡Si no llevas un coche del cártel, te descubrirán! + +[LOVE2_4] +~r~¡La yakuza te ha reconocido! + +[LOVE2_6] +~r~¡Has matado a todos los testigos! + +[LOVE3_A] +En estos días de hipocresía moral, ciertos artículos pueden resultar difíciles de importar. + +[LOVE3_C] +Echará varios paquetes al agua. + +[LOVE3_D] +Asegúrate de los consigues antes que nadie. + +[LOVE3_1] +~g~¡Consigue una ~r~lancha~g~ y sigue a la ~y~avioneta~g~! + +[LOVE4] +'ROBO DE ALTOS VUELOS' + +[LOVE5] +'SERVICIO DE ESCOLTA' + +[LOVE4_A] +Gracias por conseguir esos paquetes, pero sólo eran un señuelo. + +[LOVE4_B] +Lo siento, pero a veces los negocios son así. + +[LOVE4_C] +Mi verdadero objetivo se ocultaba en la avioneta. + +[LOVE4_F] +He sobornado a los funcionarios. + +[LOVE4_1] +~r~¡El cártel colombiano está aquí! + +[LOVE4_2] +~g~¡El paquete ha desparecido! Síguele la pista a los colombianos y recupéralo. + +[LOVE4_3] +~g~¿''Panlantic Construction''...? + +[LOVE4_5] +~g~El paquete debería estar en la avioneta... + +[LOVE4_6] +~g~¡Usa el ascensor! + +[LOVE5_B] +Mi amigo oriental necesita que le escolten mientras comprueba la autenticidad de mi última adquisición. + +[LOVE5_1] +~g~¡En marcha! + +[LOVE5_2] +~g~¡Necesitas un coche! + +[LOVE5_3] +~g~¡Comprueba la salida del túnel! + +[LOVE5_4] +~r~¡Protege el camión! + +[RM6] +'HOMBRE MUERTO' + +[RM6_A] +¿No te han seguido? Bien. + +[RM6_B] +Se acabó, ¡esto se me va de las manos y estoy con la soga al cuello! + +[RM6_D] +Soy hombre muerto, así que tengo que largarme. + +[RM6_E] +¡Consigue que no pierda mi vuelo en el aeropuerto y haré que tu trabajo haya merecido la pena! + +[RM6_666] +Cuida mi Patriot a prueba de balas. Nos vemos en Miami, Ray + +[CAT1] +'RESCATE' + +[CAT2] +'EL INTERCAMBIO' + +[CAT1_A] +Tengo a tu linda María. Si no quieres que parezca que un carnicero se ensañó con ella, + +{ UNUSED DUPLICATE } + +[CAT2_F] +Me he roto una uña y se me ha estropeado el peinado. Es increíble. ¡Me había costado cincuenta dólares! + +{ UNUSED DUPLICATE } + +[CAT2_G] +Estaba muerta de miedo, pero luego me dije a mí misma: ''Ya eres mayorcita''. + +{ UNUSED DUPLICATE } + +[CAT2_H] +Oh, nos lo vamos a pasar a lo grande, porque, ¿sabes?, mi hermana dijo que quería venirse a vivir con sus dos hijos, + +{ UNUSED DUPLICATE } + +[CAT2_I] +porque su marido a vuelto a enredar por ahí y... + +[CAT1_F] +¡Ve a donde está Catalina antes de que se acabe el tiempo! + +[CAT_MON] +~g~Todavía te falta dinero. Necesitas 500.000 dólares. + +[BITCH_D] +~g~¡María está muerta! + +[WEATHER] +FORZAR EL CLIMA + +[WEATHE2] +CLIMA NORMAL + +[8001] +¡Has fallado miserablemente! + +[1000] +HAS MUERTO + +[1001] +HAS MUERTO + +[1002] +HAS MUERTO + +[1003] +HAS MUERTO + +[1004] +HAS MUERTO + +[1005] +TRINCADO + +[1006] +TRINCADO + +[1007] +TRINCADO + +[1008] +TRINCADO + +[1009] +TRINCADO + +[GA_4] +Una bomba de coche vale 1.000 dólares. + +[GA_5] +Tu coche ya tiene una bomba instalada. + +[GA_6] { re3 change } +¡Apárcalo, actívala pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~ y SAL PITANDO! + +[GA_7] { re3 change } +Activa la bomba pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~. Estallará cuando se arranque el motor. + +[GA_8] +Utiliza el detonador para activar la bomba. + +[GA_9] +Ha conseguido ~1~ de los 10 coches especiales. + +[GA_10] +Buen trabajo. Aquí tienes tus ~1~ dólares. + +[GA_11] +Ya tenemos este modelo. ¡No nos sirve para nada! + +[GA_12] +Bomba armada + +[GA_13] +Eres todo un profesional. Completa la lista y conseguirás una recompensa. + +[GA_14] +Ya están todos los coches. ¡Estupendo! Toma un detallito. + +[GA_15] +Espero que te guste el nuevo color. + +[GA_16] +Esta mano de pintura es gratuita. + +[GA_19] +No nos interesa ese modelo. + +[GA_20] +De ese modelo tenemos más de los que podemos colocar. Lo siento, tío, no hay trato. + +[CR_1] +La grúa no puede levantar este vehículo. + +[PU_MONY] +No tienes suficiente dinero. + +[CO_ALL] +Te has hecho con todos. Toma un detallito... + +[PAUSED] +PARTIDA EN PAUSA + +[HEALTH1] +¡Largo! Estás completamente sano. + +[HEALTH2] +La sanidad cuesta dinero. + +[HEALTH3] +Te pondré como nuevo. + +[HEALTH4] +Te va a costar 250 $. + +[FEB_STA] +Estadísticas + +[FEB_BRI] +Resumen + +[FEB_CON] +Controles + +[FEB_AUD] +Sonido + +[FEB_DIS] +Pantalla + +[FEB_LAN] +Idioma + +[FEP_STA] +ESTADÍSTICAS + +[FEP_BRI] +RESUMEN + +[FEP_CON] +CONTROLES + +[FEP_AUD] +SONIDO + +[FEP_DIS] +PANTALLA + +[FEP_LAN] +IDIOMA + +[FEF_ST1] +¿Quién es el malo? + +[FEF_ST2] +Cuánta destrucción has producido + +[FEF_BR1] +¿Has perdido el hilo? + +[FEF_CO1] +¿Necesitas más control? + +[FEF_CO2] +Elige la mejor configuración del mando para tu estilo de juego + +[FEF_SA1] +¡Mantén tu sitio en el bloque! + +[FEF_SA2] +Guarda y carga tus partidas + +[FEF_AU1] +¡Dale candela al volumen! + +[FEF_AU2] +Elige una emisora de radio y un efecto de sonido + +[FEF_DI1] +¡Cambia el juego! + +[FEF_DI2] +Personaliza el juego para tu televisión + +[FEF_LA1] +¿Qué dices de los Willis? + +[FEF_LA2] +Elige tu idioma preferido + +[FEB_PMB] +Resumen de la última misión: + +[FEC_NA] +No disponible + +[FEC_CWL] +Siguiente arma + +[FEC_CWR] +Arma anterior + +[FEC_LOF] +Mirar hacia delante + +{ !!!!!!!!!!!!!!!!!! ACHTUNG! THIS STRING IS USED FOR BOTH THE MAP'S CURRENT TARGET AS WELL AS FOR THE ACTION OF TARGETING WITH A GUN! THIS WILL SCREW AROUND TRANSLATIONS !!!!!!!!!!!!!!! } +[FEC_TAR] +Objetivo + +[FEC_MOV] +Movimiento + +[FEC_CAM] +Cambiar cámara + +[FEC_PAU] +Pausa + +[FEC_ENV] +Entrar en vehículo + +[FEC_JUM] +Saltar + +[FEC_ATT] +Atacar o disparar + +[FEC_RUN] +Correr + +[FEC_FPC] +Cámara en primera persona + +[FEC_LL] +Mirar a la izquierda + +[FEC_LB1] +Mirar + +[FEC_LB2] +atrás + +[FEC_LB] +Mirar atrás + +[FEC_LR] +Mirar a la derecha + +[FEC_HOR] +Claxon + +[FEC_VES] +Controlar vehículo + +[FEC_RSC] +Cambiar emisora de radio + +[FEC_BRA] +Freno o marcha atrás + +[FEC_HAB] +Freno de mano + +[FEC_CAW] +Arma del vehículo + +[FEC_ACC] +Acelerar + +[FEC_SMT] +Activar misión especial + +[FEA_OUT] +Salida: + +[FEA_ST] +Estéreo + +[FEA_MNO] +Mono + +[FEA_NON] +Ninguna + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEF FM + +[FEA_FM2] +K-JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX FM + +[FED_DBG] +Menú de depuración + +[FED_RID] +Recargar IDE + +[FED_RIP] +Recargar IPL + +[FED_PAH] +Analizar pila + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Cambiar luz blanca grande de depuración + +[FED_SPR] +Mostrar rutas de aceras + +[FED_SCR] +Mostrar rutas de carreteras + +[FED_SCZ] +Mostrar zonas de máscara selectiva + +[FED_DSR] +Solicitudes de depuración de streaming + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Menú de Memory Card + +[FEM_RMC] +Registrar MemCard uno + +[FEM_TFM] +Probar MemCard uno formateada + +[FEM_TUM] +Probar Memcard uno sin formato + +[FEM_CRD] +Crear directorio raíz + +[FEM_CLI] +Crear y cargar iconos + +[FEM_FFF] +Llenar primer archivo con basura + +[FEM_SOG] +Guardar sólo partida + +[FEM_CES] +Comprobar cada guardado de 0kB4 + +[FEM_STG] +Guardar partida + +[FEM_STS] +Guardar partida bajo el nombre GTA3 + +[FEM_CPD] +Crear copia protegida del directorio mag + +[FEM_MC2] +Menú de Memory Card 2 + +[FEM_TS] +Prueba de guardado: + +[FEM_TL] +Prueba de carga: + +[FEM_TD] +Prueba de borrado: + +[PL_STAT] +Estadísticas de jugador + +[PE_WAST] +Personas asesinadas + +[PE_WSOT] +Personas asesinadas por otros + +[CAR_EXP] +Coches reventados + +[TM_BUST] +Veces arrestado + +[M_WASTE] +Hombres civiles asesinados + +[F_WASTE] +Mujeres civiles asesinadas + +[PIG_WST] +Polis asesinados + +[GNG_WST] +Miembros de bandas asesinados + +[MED_WST] +Médicos asesinados + +[FIRE_WS] +Bomberos asesinados + +[DED_CRI] +Criminales asesinados + +[DED_DED] +Vagabundos asesinados + +[DED_HOK] +Prostitutas asesinadas + +[HEL_DST] +Helicópteros destruidos + +[PER_COM] +Porcentaje completado + +[KGS_EXP] +Kgs de explosivos empleados + +[ACCURA] +Precisión de tiro + +[ELBURRO] +Mejor tiempo de ''Turismo'' en segundos + +[CAR_CRU] +Coches destrozados + +[HED_EX] +Cabezas explotadas + +[TM_DED] +Visitas al hospital + +[DAYSPS] +Días de juego transcurridos + +[MMRAIN] +mm de lluvia caídos + +[MXCARD] +Dist. máx. de salto demencial (pies) + +[MXCARJ] +Altura máx. de salto demencial (pies) + +[MXCARDM] +Dist. máx. de salto demencial (metros) + +[MXCARJM] +Altura máx. de salto demencial (metros) + +[MXFLIP] +Vueltas máximas en salto demencial + +[MXJUMP] +Rotación máxima en salto demencial + +[BSTSTU] +Mayor acrobacia demencial: + +[INSTUN] +Acrobacia demencial + +[PRINST] +Acrobacia demencial perfecta + +[DBINST] +Acrobacia demencial doble + +[DBPINS] +Acrobacia demencial doble perfecta + +[TRINST] +Acrobacia demencial triple + +[PRTRST] +Acrobacia demencial triple perfecta + +[QUINST] +Acrobacia demencial cuádruple + +[PQUINS] +Acrobacia demencial cuádruple perfecta + +[NOSTUC] +No se han hecho acrobacias + +[NOUNIF] +Saltos únicos completados + +[NOUNGM] +Saltos únicos + +[NMISON] +Intentos de misión + +[NMMISP] +Misiones superadas + +[PASDRO] +Pasajeros transportados + +[MONTAX] +Dinero conseguido en taxi + +[DAYPLC] +Gastos diarios provocados a la policía + +[CRIMRA] +Rango de criminal: + +[GMSTOR] +Almacenar partida + +[PREBRF] +Resúmenes anteriores + +[CNTLS] +Controles + +[MUSMEN] +Música/Efectos + +[GAMSET] +Ajustes del juego + +[LANGUA] +Idioma + +[DSPLAY] +Pantalla + +[DEBUGM] +Menú de depuración + +[QUITOP] +Salir de las opciones + +[CONTRL] +Configuración de controles + +[SET1EN] +Ajuste 1. Activado + +[SET1] +Ajuste 1. + +[SET2EN] +Ajuste 2. Activado + +[SET2] +Ajuste 2 + +[SET3EN] +Ajuste 3. Activado + +[SET3] +Ajuste 3 + +[SET4EN] +Ajuste 4. Activado + +[SET4] +Ajuste 4 + +[GOBACK] +Volver + +[SOUND] +SONIDO + +[MUSVOL] +Volumen de la música + +[SFXVOL] +Volumen de los efectos + +[SCROPT] +OPCIONES DE PANTALLA + +[CTRSCR] +Centrar pantalla + +[SCRFOR] +Formato de imagen + +[GMSVLQ] +GUARDAR-CARGAR-SALIR + +[GMREST] +Reiniciar partida + +[GMLOAD] +CARGAR PARTIDA + +[NOGMSV] +Sólo puedes guardar desde tu escondite. + +[DLFILE] +Borrar archivos de Grand Theft Auto III + +[CHFILE] +ELEGIR ARCHIVO A CARGAR + +[CHCDLD] +ELEGIR MEMORY CARD A USAR + +[CDUNFR] +Memory Card sin formato. + +[CHFIDL] +ELEGIR ARCHIVO A BORRAR + +[SVCONF] +CONFIRMACIÓN + +[SVFNAM] +El nombre de tu archivo guardado es + +[SAVEDN] +Error. Fallo al guardar. + +[LANGSL] +SELECCIÓN DE IDIOMA + +[ENGLIS] +Inglés + +[GERMAN] +Alemán + +[ITALIA] +Italiano + +[FRENCH] +Francés + +[SPAIN] +Español + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Analizar pila + +[DBGFON] +CTheScripts::DbgFlag activado + +[DBFOFF] +CTheScripts::DbgFlag desactivado + +[BGWHON] +Luz blanca grande de depuración activada + +[BGWOFF] +Luz blanca grande de depuración desactivada + +[DSTRON] +Solicitudes de depuración de streaming activadas + +[DSTROFF] +Solicitudes de depuración de streaming desactivadas + +[PDRGON] +ShowPedRoadGroups activado + +[PRGOFF] +ShowPedRoadGroups desactivado + +[CRRGON] +ShowCarRoadGroups activado + +[CRGOFF] +ShowCarRoadGroups desactivado + +[CLZOON] +Zonas de máscara selectiva mostradas + +[CLZOOF] +Zonas de máscara selectiva ocultadas + +[SHPLON] +gbShowCollisionPolys activado + +[SHPLOF] +gbShowCollisionPolys desactivado + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (prueba) + +[UNFRM1] +UnFormatMemCard 1 (prueba) + +[GORLEV] +Nivel de violencia + +[SICASS] +Puta locura + +[SICSIC] +Puto manicomio + +[SCASSL] +Seleccionado Puta locura + +[SCSCSL] +Seleccionado Puto manicomio + +[PRVMEN] +Resúmenes de misiones anteriores + +[DOSVGM] +¿Deseas guardar la partida? + +[FORMEN] +Menú de formato + +[MEMTST] +Pantalla MemoryCardTest + +[REGCAR] +Registrar MemoryCard uno + +[TEFONE] +Probar MemCard uno con formato + +[TEUFON] +Probar MemCard uno sin formato + +[CRROOT] +CreateRootDir + +[CRLDIC] +Crear y cargar iconos + +[FLFSGF] +Llenar el primer archivo con basura + +[PUSAVE] +Guardar sólo la partida + +[CHEVOK] +CheckEveryOkB4Save + +[SVGMON] +SaveTheGame + +[CNTSAV] +No se puede guardar la partida en una misión. + +[CNCSAV] +No se puede guardar la partida dentro de un vehículo. + +[CRMGSV] +Crear directorio de cargador con protección de copia + +[MGSVNC] +MagazineDirectory no creado + +[MGSVCN] +MagazineDirectory creado + +[YES] +Sí + +[NO] +No + +[X] +x + +[LAST] +Último mensaje + +[FEDS_XB] +Seleccionar + +[FEDS_ST] +Botón START - CONTINUAR + +[FEST_OO] +de + +[FEC_TUC] +Controlar torreta + +[FEC_SM3] +Activar misión secundaria (botón R3) + +[FEC_RS3] +Cambiar emisora de radio (botón L3) + +[FEC_HO3] +Claxon (botón L3) + +[DIAB1] +'TURISMO' + +[DIAB2] +'IRA HELADA' + +[DIAB3] +'A LA HOGUERA' + +[DIAB4] +'GRANDE Y VENOSA' + +[DIAB1_A] +El Burro quiere ofrecerte una oportunidad. Ve al teléfono público de los cerros de Hepburn si te interesa. + +[DIAB1_C] +Se te da bien correr. Pásate por el teléfono público y puede que El Burro tenga trabajo para ti. + +[DIAB1_1] +~g~3... 2... 1... ¡ADELANTE! + +[DIAB1_4] +~g~Hazte con un coche rápido y ve a la parrilla de salida. + +[DIAB1_3] +~r~No sabes ganar ni una rifa, ¡FRACASADO! + +[DIAB1_2] +~g~Felicidades, has ganado con un tiempo increíble de ~1~ segundos. + +[FIRST] +~g~1 + +[SECOND] +~g~2 + +[THIRD] +~g~3 + +[FOURTH] +~g~4 + +[DIAB2_1] +~g~Recoge el maletín en Harwood. + +[DIAB2_2] +~g~Encuentra un camión de helados. + +[DIAB2_3] +~g~Aparca el camión de helados en el muelle Atlantic. + +[DIAB2_4] +~g~Pulsa ~w~~k~~VEHICLE_HORN~~g~ para activar la melodía del camión de helados. + +[DIAB2_6] +~g~Pulsa ~w~~k~~VEHICLE_HORN~~g~ para activar la melodía del camión de helados. + +[DIAB2_7] +~g~Pulsa ~w~~k~~VEHICLE_HORN~~g~ para activar la melodía del camión de helados. + +[DIAB2_5] +~g~Sal del camión y luego usa el mando a distancia para detonarlo. + +[YD1] +'CORRE A POR LA PASTA' + +[YD2] +'JINETE CON UZI' + +[YD3] +'COLECCIONANDO COCHES DE BANDAS' + +[YD4] +'POR LOS AIRES' + +[YD_P] +El Rey Courtney quiere hablarte. ¡Ve al teléfono público de Aspatria! + +[YD1_A] +Habla el rey Courtney. + +[YD1_A1] +Mi banda jamaicana necesita un conductor y tú tienes buena reputación. + +[YD1_B] +Ve en un coche al vertedero que hay detrás del estadio y espera a los otros candidatos. + +[YD1_C] +Tengo hombres vigilando puntos de control por toda Staunton. + +[YD1_D] +El primer conductor que llegue a un punto de control se lleva mil pavos, y así de punto a punto. + +[YD1_D1] +Si ganas más puntos de control que los demás, podría tener trabajo para ti. + +[YD1_E] +~g~¡Prepárate! + +[YD1_F] +~g~Te has saltado la salida. ¡Me gusta tu estilo! + +[YD1_G] +~r~Esto es una carrera DE COCHES. ¡Necesitas un COCHE, idiota! + +[YD1GO] +~g~¡VAMOS! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +¡1.000 $! + +[Y1_1ST] +~G~¡Has quedado primero con ~1~ puntos de control! + +[Y1_2ND] +~y~Eres el segundo con ~1~ puntos de control. ~y~¡Casi, pero no! + +[Y1_3RD] +~r~Eres el tercero con ~1~ puntos de control. ~r~¿No decías que eras bueno? + +[Y1_LAST] +~r~¡Has quedado el último! ~r~¡Me has hecho perder el tiempo, IDIOTA! + +[Y1_J1ST] +~y~Has empatado el primero con ~1~ puntos de control. ~y~Bien, ¡pero debes ser el mejor si quieres conducir para la Reina Lizzy! + +[Y1_J2ND] +~r~Has empatado el segundo con ~1~ puntos de control. ¡Conduces como un mono loco! + +[Y1JLAST] +~r~¡Has empatado el último! ¡Hablas como un conductor, pero conduces como un hablador! + +[Y1_TEST] +¡COCHE AL AGUA! + +[YD2_A] +Necesito ver si eres capaz de hacer mi trabajo sucio, + +[YD2_A1] +si se puede confiar en ti. + +[YD2_B] +Dos de mis chicos llegarán allí enseguida para llevarte de paseo, + +[YD2_B1] +a ver si eres quien dices que eres. + +[YD2_C] +Vamos a dar un paseíto por los cerros de Hepburn para cargarnos a los asquerosos Diablos que han estado metiéndose con la reina Lizzy. + +[YD2_CC] +Toma, necesitarás una pipa. + +[YD2_D] +Tú te ocupas de conducir y disparar. Nosotros nos aseguraremos de que no te eches atrás. + +[YD2_E] +¡Conduce! + +[YD2_F] +~r~Se quiere escapar, ¡cárgatelo! + +[YD2_G1] +Los cerros de Hepburn... Vamos a matar a algunos asquerosos Diablos... + +[YD2_G2] +Pero recuerda, ~r~¡no salgas de este coche! + +[YD2_H] +¡Vale, ya está! ¡Llévanos de vuelta a territorio jamaicano! ¡Vamos, vamos, vamos! + +[YD2_L] +¡Eres la Muerte personificada! + +[YD2_M] +~r~¡Ha destrozado mi coche! ¡Liquídalo! + +[YD2_N] +¡Vuelve al coche! + +[YD3_A] +Quiero que robes coches de otras bandas + +[YD3_A1] +para que podamos atacar sus territorios. + +[YD3_B] +Necesito un Mafia Sentinel, + +[YD3_B1] +un Yakuza Stinger y un + +[YD3_B2] +Diablo Stallion para que podamos ir a por cualquier banda de Liberty. + +[YD3_C] +Déjalos en el garaje de Newport y recuerda, + +[YD3_C1] +¡dañados no nos sirven de nada! + +[YD3_D] +UNUSED + +[YD3_E] +~r~¡Ya has entregado un coche de los Diablos! + +[YD3_F] +~r~¡Ya has entregado un coche de la mafia! + +[YD3_G] +~r~¡Ya has entregado un coche de la yakuza! + +[YD3_H] +~g~¡Coche de los Diablos entregado! + +[YD3_I] +~g~¡Coche de la mafia entregado! + +[YD3_J] +~g~¡Coche de la yakuza entregado! + +[YD3_K] +~r~¡El coche está casi destrozado! ¡Haz que lo reparen! + +[YD3_L] +~g~¡Llévalo al ~p~garaje~g~! + +[YD3_M] +~r~¡Has volcado el vehículo! ¡Consigue otro! + +[YD4_A] +¡Escucha! + +[YD4_A1] +Mueve el culo a Bedford Point. + +[YD4_A2] +¡Hay un cargamento en una tartana que necesito pronto! + +[YD4_B] +CARTA: Dicen que estuviste entretenido. Igual que yo. + +[YD4_C] +¡Es hora de que seas testigo del verdadero poder del SPANK! Con mucho amor, Catalina. + +[YD4_D] +P.D. ¡MUERE, BASURA, MUERE! + +[YD4_1] +~g~¡Son psicópatas adictos al SPANK! + +[YD4_2] +~g~¡Destruye las furgonetas de SPANK! + +[HM_1] +'PASADA AMETRALLADA' + +[HM_2] +'BOMBINATOR' + +[HM_3] +'PREPARADO PARA ESTALLAR' + +[HM_5] +'ENFRENTAMIENTO' + +[HOOD1_A] +Ve al teléfono público de Wichita Gardens y hablaremos de negocios. + +[HM1_A] +¡Ey! ¡Soy D-Ice, de los Red Jacks! + +[HM1_C] +Hay unos macarrillas en la calle que no piensan más que en pistolas y en SPANK. + +[HM1_3] +~g~Los Nines tienen su territorio en Wichita Gardens. + +[HM2_3] +¡Si golpeas las ruedas de un vehículo, el coche teledirigido estallará! + +[HM2_4] +¡Si el coche teledirigido sale fuera de cobertura, estallará solo! + +[HM2_5] +~r~¡Te has ido de cobertura! + +[HM3_1] +~g~¡Ve al garaje, pero pon atención, porque el coche explotará si se daña demasiado! + +[HM3_2] +~g~Lleva el coche de vuelta. ¡Tiene que estar inmaculado! + +[HM3_3] +~g~¡Haz que reparen el vehículo! + +[HM4_D] +~g~¡Hazte con un vehículo! + +[HM4_1] +~g~Dirígete al lugar donde está el cargamento. Necesitas conseguir 30 lingotes. + +[HM4_2] +~g~Recuerda: cuando el vehículo esté pesado y vaya más despacio, ve al garaje y deja el cargamento. + +[HM5_3] +~r~¡Te han dicho que uses sólo un bate de béisbol! + +[HM5_4] +~r~¡Tu contacto está muerto! + +[MEA1] +'EL CRIMINAL' + +[MEA2] +'LOS LADRONES' + +[MEA3] +'LA MUJER' + +[MEA4] +'SU AMANTE' + +[MEAT1_A] +Un amigo dijo que tú podías arreglar algunos problemas que tengo. Ve al teléfono público de Trenton si crees que puedes ayudar. + +[MEA1_B3] +~g~Reúnete con el director del banco. + +[MEA1_B6] +~g~Lleva el coche a la trituradora para eliminar las pruebas, allí sal del coche y la grúa lo recogerá. + +[MEA1_1] +~r~¡El director del banco está muerto! + +[MEA1_2] +~r~¡Te dijeron que trituraras el coche! + +[MEA1_3] +~g~¡Sal del coche! + +[MEA1_4] +~r~¡Has abandonado al director del banco! + +[MEA2_B3] +~g~Reúnete con los ladrones. + +[MEA2_B4] +~g~Llévalos a la fábrica de comestibles Bitch'n' Dog. + +[MEA2_B6] +~g~Haz que repinten el coche para borrar las pruebas. + +[MEA2_1] +~r~¡Te dijeron que trituraras el vehículo! + +[MEA2_2] +~r~¡Un ladrón ha muerto! + +[MEA2_4] +~r~¡Has abandonado a un ladrón! + +[MEA3_B3] +~g~Recoge a la Sra. Chonks. + +[MEA3_B6] +~g~Llévate el coche y tíralo al mar para eliminar las pruebas. + +[MEA3_1] +~r~¡La mujer ha muerto! + +[MEA3_2] +~r~¡Se suponía que debías tirar el coche al agua! + +[MEA3_3] +~r~¡Has abandonado a su mujer! + +[MEA4_B3] +~g~Recoge al amante de su mujer. + +[MEA4_B6] +Ya es muy tarde, Marty. Tuviste tu oportunidad, pero ahora yo me ocuparé de tu negocio... + +[MEA4_1] +~r~¡Carlos está muerto! + +[MEA4_3] +~r~¡Has abandonado a Carlos, el usurero! + +[LOOK_A] +Mantén pulsado ~h~~k~~VEHICLE_LOOKLEFT~~w~ o ~h~~k~~VEHICLE_LOOKRIGHT~~w~ para mirar~h~ a la izquierda~w~ o ~h~a la derecha ~w~estando dentro de un vehículo. Pulsa ambos para mirar~h~ atrás~w~. + +[LOVE6_1] +~g~¡Ahora aleja a los policías del almacén! + +[LOVE6_2] +~r~¡No has distraído a la policía! + +[RM4_3] +~r~¡El socio de Ray ha escapado! + +[RM6_C] +La CIA parece tener intereses con el SPANK + +[RM6_C1] +y no les gusta que incordiemos al cártel. + +[C_PASS] +¡AMENAZA ELIMINADA! + +[CTUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de justiciero. + +[CTUTOR2] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de justiciero. + +[COPCART] +~g~Tienes ~1~ segundos para volver a un vehículo de la policía antes de que termine la misión. + +[C_FAIL] +¡Misión de justiciero terminada! + +[C_CANC] +~r~¡Misión de justiciero cancelada! + +[C_ESCP] +~r~¡El sospechoso ha escapado! + +[C_TIME] +~r~¡Tu trabajo como defensor de la ley ha terminado! + +[C_VIGIL] +¡PREMIO POR JUSTICIERO! + +[A_FAIL2] +~r~¡Tu falta de rapidez ha sido fatal para el paciente! + +[A_FAIL3] +~r~¡Tu paciente ha muerto! + +[A_PASS] +¡Paciente a salvo! + +[F_FAIL2] +~r~¡Llegas demasiado tarde! + +[A_COMP2] +¡Nunca te cansarás! + +[RM2_M] +Si necesitas armas nuevas, pasa por aquí y coge lo que necesites de las taquillas. + +[HEAL_A] +Tu ~h~salud ~w~está indicada en naranja en la parte superior derecha de la pantalla. + +[YD1_CNT] +¡~1~ de 15! + +[FM1_9] +~g~La fiesta es allí. Deja a María enfrente. + +[FM1_Y] +Quiero que sepas que me lo he pasado muy bien por primera vez en mucho tiempo y que me has tratado muy bien, con respeto. + +[FM1_AA] +Bueno, será mejor que me vaya. Espero que nos veamos pronto. + +[NOCONTE] +Vuelve a conectar un mando analógico (DUALSHOCK#) o mando analógico (DUALSHOCK#2) en el puerto de mando 1 para continuar. + +[WRCONT] +El mando conectado al puerto de mando 1 es un mando no compatible. Grand Theft Auto III necesita un mando analógico (DUALSHOCK#) o mando analógico (DUALSHOCK#2). + +[WRCONTE] +El mando conectado al puerto de mando 1 es un mando no compatible. Grand Theft Auto III necesita un mando analógico (DUALSHOCK#) o mando analógico (DUALSHOCK#2). + +[WRONGCD] +Disco incorrecto. Introduce el disco correcto. + +[NOCD] +La bandeja del disco está vacía. Introduce un disco. + +[OPENCD] +La bandeja del disco está abierta. Cierra la bandeja de disco. + +[CDERROR] +Error al leer el DVD de Grand Theft Auto III + +[RESTART] +Empezando una nueva partida + +[GA_3] +¡Ya no es gratis! ¡La mano de pintura son 1.000 dólares! + +[GA_1] +¡Hala! ¡Demasiado peligroso para mi gusto! + +[GA_1A] +Vuelve cuando no estés tan ocupado... + +[S_PROM2] +Puedes alojar un vehículo en el garaje de al lado al guardar tu partida. + +[STOCK] +Mercancía agotada + +[FM1_O] +Creo que está en la estación de ferrocarril del muelle de Chinatown. + +[EBAL_B] +Es aquí, ¡entremos y cambiémonos de ropa! + +[EBAL_G] +Éste es el club de Luigi. Vamos a la parte de atrás, a la puerta de servicio. + +[AM4_3] +¡Tú debes de ser el nuevo recadero de Asuka! + +[AM4_4] +¿Tienes el dinero? ¿Está todo? + +[AM4_5] +Sé lo que estás pensando: ''otro poli corrupto''. + +[AM4_6] +Pues éste es un mundo corrupto. + +[AM4_7] +He perdido a un par de socios y esos papanatas de Asuntos Internos ya se han puesto a husmear. + +[AM4_8] +¡Y seguro que les llega mi olor! + +[AM4_9] +¡Pues esta ciudad es una gran alcantarilla! + +[AM4_10] +Pero voy a necesitar un ayudita ajena al cuerpo. + +[AM4_11] +Si estás interesado, ya sabes dónde encontrarme. + +[CAM_A] +Pulsa ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ para cambiar la ~h~cámara ~w~cuando vayas a pie o estés en un vehículo. + +[CAM_B] +Pulsa el ~h~botón de dirección hacia arriba ~w~y ~h~abajo~w~ para cambiar la ~h~cámara ~w~cuando vayas a pie o estés en un vehículo. + +[KM2_1] +~g~Repara el coche, tiene que estar en perfecto estado. + +[LM3_6] +¡Joey...! + +[LM3_6A] +¿Voy a jugar con tu paquetín otra vez? + +[LM3_9A] +que tendré trabajo para ti. + +[LM3_9B] +¿De acuerdo? + +[AWAY2] +~r~Se ha escapado. + +[AWAY] +~r~¡Se ha pirado! + +[JM6_1] +Llévanos al banco de la avenida principal. + +[GA_6B] { re3 change } +¡Apárcalo, actívala pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~ y SAL PITANDO! + +[GA_7B] { re3 change } +Activa la bomba pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~. Estallará cuando se arranque el motor. + +[BAT1] +~g~¡Coge el bate! + +[EBAL_O] +Si no la cagas, puede que haya más trabajo para ti. ¡Ahora largo! + +[HELP9_B] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar ~w~el fusil de francotirador. + +[HELP9_C] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar ~w~el fusil de francotirador. + +[JM6_8] +~r~¡Has perdido a todos los ladrones! + +[COLT_IN] +¡La pistola ya está disponible en la tienda Ammu-Nation! + +[TAXI2] +~r~¡Se te acabó el tiempo! + +[TAXI3] +~r~¡Tu cliente ha salido por patas! + +[TAXI7] +~r~Tu coche está destrozado, repáralo. + +[TAXI4] +¡Carrera terminada! + +[TAXI5] +¡VIAJE RÁPIDO! + +[TAXI6] +Misión de taxista cancelada + +[FRANGO] +~g~¡Salvatore quiere que ayudes primero a Toni a ocuparse de las Tríadas! + +[PAGEB12] +Soborno policial entregado en tu guarida. + +[PAGEB13] +Salud entregada en tu guarida. + +[PAGEB14] +Adrenalina entregada en tu guarida. + +[KM1_4] +~g~¡Necesitas un coche de policía para hacer el trabajo! + +[CAT1_B] +lleva 500.000 dólares a la mansión de Cedar Grove. + +[JM2_C] +Tiene un puesto de fideos en Chinatown. + +[RM6_1] +Esta llave es de un almacén. + +[RM6_2] +Encontrarás dinero en metálico y ''suministros'' que guardé por si las cosas se ponían feas. + +[RM6_3] +Hasta la vista. + +[FE_INIP] +Iniciando y cargando el menú de pausa... Espera, por favor. + +[FESZ_CA] +Cancelar + +[FESZ_QU] +Abandonar + +[FESZ_L1] +¡La partida se ha guardado! + +[FESZ_L2] +El nombre guardado es: + +[FESZ_OK] +ACEPTAR + +[FES_NGA] +Nueva partida + +[FES_CAN] +Cancelar + +[FESZ_QL] +Perderás todo el progreso en tu partida actual. ¿Quieres continuar con la carga? + +[FESZ_QD] +¿Quieres borrar esta partida guardada? + +[FESZ_QO] +¿Quieres sobreescribir esta partida guardada? + +[FESZ_QR] +¿Seguro que quieres empezar una nueva partida? Perderás todo el progreso desde la última partida guardada. + +[FESZ_QS] +¿QUIERES GUARDAR? + +[SLONFP] +Puerto 1. Archivo protegido. + +[T4X4_1] +'PATIO DE RECREO DEL PATRIOT' + +[T4X4_2] +'UN PASEO POR EL PARQUE' + +[T4X4_3] +'¡AGARRADO!' + +[MM_1] +'CAOS EN EL APARCAMIENTO' + +[T4X4_1A] +~g~Tienes ~y~5 minutos~g~ para pasar por ~y~15~g~ puntos de control. ~g~Puedes atravesarlos en ~y~CUALQUIER ORDEN. + +[T4X4_1B] +¡~1~ de 15! + +[T4X4_1C] +~y~ATRAVIESA~g~ el primer punto de control para poner en marcha el cronómetro. ~g~Cada punto de control te dará ~y~20 SEGUNDOS~g~ adicionales. + +[T4X4_2A] +~g~¡Tienes ~y~2 minutos~g~ para pasar por ~y~12~g~ puntos de control! ~g~Puedes atravesarlos en ~y~CUALQUIER ORDEN. + +[T4X4_2B] +¡~1~ de 12! + +[T4X4_2C] +~y~ATRAVIESA~g~ el primer punto de control para poner en marcha el cronómetro. ~g~Cada punto de control te dará ~y~10 SEGUNDOS~g~ adicionales. + +[T4X4_3A] +~g~Tienes ~y~5 minutos~g~ para pasar por ~y~20~g~ puntos de control. ~g~Puedes atravesarlos en ~y~CUALQUIER ORDEN. + +[T4X4_3B] +~y~ATRAVIESA~g~ el primer punto de control para poner en marcha el cronómetro. ~g~Cada punto de control te dará ~y~15 SEGUNDOS~g~ adicionales. + +[T4X4_3C] +¡~1~ de 20! + +[T4X4_F] +~r~¡Te has escapado! ¿Demasiado difícil para ti? + +[MM_1_A] +~g~¡Tienes ~y~2 minutos~g~ para pasar por ~y~20 puntos de control~g~ en el aparcamiento! ~g~Puedes atravesarlos en ~y~CUALQUIER ORDEN. + +[MM_1_B] +¡~1~ de 20! + +[MM_1_C] +~g~Tendrás 20 segundos, más ~y~5 SEGUNDOS~g~ por cada punto de control. ~g~El cronómetro comenzará ~y~INMEDIATAMENTE. + +[FM2_14] +~r~¡Te acercaste demasiado y asustaste a Curly! + +[FM2_15] +~g~No te acerques mucho, ¡o Curly sospechará! + +[UPSIDE] +~r~¡Has volcado! + +[FM2_16] +ASUSTÓMETRO: + +[LM3_11] +~g~Misty no irá en un autobús, ¡hazte con otro vehículo! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Camión de bomberos + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Mañana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulancia + +[FBICAR] +Coche del FBI + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr. Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Coche patrulla + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Autobús + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[TRAIN] +Tren + +[HELI] +Helicóptero + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +Bandit RC + +[BELLYUP] +Furgoneta de las Tríadas + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Sentinel de la mafia + +[YARDICR] +Lobo jamaicano + +[YAKUZCR] +Stinger de la yakuza + +[DIABLCR] +Stallion de los Diablos + +[COLOMCR] +Cruiser del cártel + +[HOODSCR] +Rumpo XL de los hood + +[AEROPL] +Avioneta + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Distancia recorrida a pie (millas) + +[FEST_DC] +Distancia recorrida en coche (millas) + +[FESTDFM] +Distancia recorrida a pie (metros) + +[FESTDCM] +Distancia recorrida en coche (metros) + +[FEST_R1] +''Patio de recreo del Patriot'' en segundos + +[FEST_R2] +''Un paseo por el parque'' en segundos + +[FEST_R3] +''¡Agarrado!'' en segundos + +[FEST_RM] +''Caos en el aparcamiento'' en segundos + +[FEST_LS] +Personas salvadas con una ambulancia + +[FEST_CC] +Criminales asesinados en misiones de justiciero + +[FEST_FE] +Incendios extinguidos + +[FEST_LF] +Vuelo más largo en Dodo + +[FEST_BD] +Mejor tiempo al desactivar la bomba + +[FEST_RP] +Masacres superadas + +[FEST_MP] +Misiones superadas + +[FEST_BB] +''Corre a por la pasta'': + +[FEST_H0] +Puntos de control alcanzados + +[FEST_GC] +Coches de bandas destruidos: + +[FEST_H1] +Destrucción de los Diablos + +[FEST_H2] +La masacre de la mafia + +[FEST_H3] +Calamidad en el casino + +[FEST_H4] +Exterminio de Rumpos + +[USJ] +¡PREMIO POR ACROBACIA ÚNICA! + +[SPRAY] +Mete tu vehículo en el taller de pintura para perder tu ~h~nivel de búsqueda~w~, ~h~reparar~w~ y~h~ repintar~w~ tu vehículo. Coste: ~h~1.000 $~w~. + +[HM1_1] +~g~Cepíllate a 20 Purple Nines en 2 minutos y 30 segundos. + +[KM1_8A] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para ~h~activar la bomba~w~. Acuérdate de alejarte de ella. + +[KM1_8D] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para ~h~activar la bomba~w~. Acuérdate de alejarte de ella. + +[KM1_12] +~g~¡Llévalo al dojo, pero deshazte primero de la policía! + +[RATNG1] +Carterista + +[RATNG2] +Abusón + +[RATNG3] +Chorizo + +[RATNG4] +Granuja + +[RATNG5] +Secuaz + +[RATNG6] +Conductor + +[RATNG7] +Guardaespaldas + +[RATNG8] +Negociador + +[RATNG9] +Socio + +[RATNG10] +Sicario + +[RATNG11] +Asesino + +[RATNG12] +Mano derecha + +[RATNG13] +Verdugo + +[RATNG14] +Capo + +[RATNG15] +Jefe + +[1010] +~r~Tu vehículo ha volcado + +[1011] +~r~Tu vehículo ha volcado + +[1012] +~r~Tu vehículo ha volcado + +[1013] +~r~Tu vehículo ha volcado + +[1014] +~r~Tu vehículo ha volcado + +[JM4_10] +Vale, chaval, llévame primero a la lavandería de Chinatown, tengo un asunto del que ocuparme. + +[JM4_11] +Las lavanderas no están pagando por su protección. + +[JM4_12] +Y ojo con el coche, Joey acaba de arreglar esta chatarra. + +[JM4_13] +Así que ve con cuidado, ¿vale? + +[KM4_11] +~g~¡Lleva el dinero de vuelta al casino! + +[FEF_BR2] +Encuéntralo de nuevo leyendo los resúmenes de las misiones jugadas hasta la fecha. + +[TRAIN_1] +Estación Kurowski + +[TRAIN_2] +Estación Rothwell + +[TRAIN_3] +Estación Baillie + +[SUBWAY1] +Estación Portland + +[SUBWAY2] +Estación Rockford + +[SUBWAY3] +Estación Staunton sur + +[SUBWAY4] +Terminal Shoreside + +[MEA4_2] +~r~¡Marty Chonks ha muerto! + +[SPRAY1] +Mete tu vehículo en el taller de pintura para perder tu ~h~nivel de búsqueda~w~, ~h~reparar~w~ y~h~ repintar~w~ tu vehículo. Coste: ~h~1.000 $~w~. Esta vez es gratis. + +[JM4_A] +Sí, Toni, ya está a punto. Ahora es una delicia, ¿sabes? + +[JM4_5] +Pasa más tarde y les daremos algo que lavar... ¡Su propia ropa manchada de sangre! + +[AMMU_A] +Luigi dijo que necesitabas una pipa... + +[AMMU_B] +Joey me pidió que te armara... + +[AMMU_C] +Ve a la parte de atrás. Te dejé un nueve en el patio. + +[AMMU_D] +Tengo todo lo necesario para defender tu casa. + +[AMMU_E] +¿También quieres una licencia? + +[AMMU_F] +No necesito tu documentación, pareces de fiar. + +[DETON] +DETONACIÓN: + +[DRIVE_A] { re3 change } +Ten una Uzi seleccionada cuando entres en un vehículo, luego mira a la izquierda o a la derecha y pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar. + +[DRIVE_B] { re3 change } +Ten una Uzi seleccionada cuando entres en un vehículo, luego mira a la izquierda o a la derecha y pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar. + +[RECORD] +~g~¡NUEVO RÉCORD! + +[NRECORD] +~r~¡NO HAY UN NUEVO RÉCORD! + +[RCHELP] { re3 change } +Pulsa ~k~~VEHICLE_FIREWEAPON~ o lleva el coche teledirigido hasta las ruedas de otro coche para detonarlo. + +[RCHELPA] { re3 change } +Pulsa ~k~~VEHICLE_FIREWEAPON~ o lleva el coche teledirigido hasta las ruedas de otro coche para detonarlo. + +[RC_1] +¡Tienes 2 minutos para destruir todos los coches de los Diablos que puedas! + +[RC_2] +¡Tienes 2 minutos para destruir todos los coches de la mafia que puedas! + +[RC_3] +¡Tienes 2 minutos para destruir todos los coches de la yakuza que puedas! + +[RC_4] +¡Tienes 2 minutos para destruir todos los coches de los jamaicanos que puedas! + +[RC_5] +¡Tienes 2 minutos para destruir todos los coches de los Hood que puedas! + +[RC_6] +¡Tienes 2 minutos para destruir todos los coches del cártel que puedas! + +[RAMPAGE] +¡MASACRE! + +[RAMP_P] +¡MASACRE COMPLETADA! + +[RAMP_F] +MASACRE FALLIDA + +[PAGE_00] +. + +[PAGE_01] +¡Liquida a ~1~ Diablos en 120 segundos! + +[PAGE_02] +¡Destruye ~1~ vehículos en 120 segundos! + +[PAGE_03] +¡Mata a ~1~ mafiosos en 120 segundos! + +[PAGE_04] +¡Mata a ~1~ Tríadas en 120 segundos! + +[PAGE_05] +¡Mata a ~1~ Tríadas en 120 segundos! + +[PAGE_06] +¡Destruye ~1~ vehículos en 120 segundos! + +[PAGE_07] +¡Revienta ~1~ cabezas jamaicanas en 120 segundos! + +[PAGE_08] +¡Quema a ~1~ yakuzas en 120 segundos! + +[PAGE_09] +¡Destruye ~1~ vehículos en 120 segundos! + +[PAGE_10] +¡Destruye ~1~ vehículos en 120 segundos! + +[PAGE_11] +¡Aniquila a ~1~ jamaicanos en 120 segundos! + +[PAGE_12] +¡Quema a ~1~ yakuzas en 120 segundos! + +[PAGE_13] +¡Vuela a ~1~ jamaicanos en 120 segundos! + +[PAGE_14] +¡Fríe a ~1~ colombianos en 120 segundos! + +[PAGE_15] +¡Machaca a ~1~ Hoods en 120 segundos! + +[PAGE_16] +¡Destruye ~1~ vehículos en 120 segundos! + +[PAGE_17] +¡Arrolla a ~1~ colombianos con un coche en 120 segundos! + +[PAGE_18] +¡Destruye ~1~ vehículos disparando desde otro en 120 segundos! + +[PAGE_19] +¡Revienta ~1~ cabezas colombianas en 120 segundos! + +[PAGE_20] +¡Decapita a ~1~ Hoods en 120 segundos! + +[JM1_A] +Jo, me aburro, ¿cuándo me la vas a meter? + +[JM1_B] +Dame un momento, cariño, que tengo un asuntillo que tratar. + +[JM1_C] +Tengo un trabajito para ti, colega. + +[JM1_D] +Los hermanos Forelli me deben dinero desde hace mucho tiempo + +[JM1_E] +y hay que enseñarles modales. + +[JM1_F] +''Labios'' Forelli está atiborrándose en el restaurante de Saint Mark's, + +[JM1_G] +así que róbale el coche y llévalo al taller de bombas de 8-Ball en Harwood. + +[JM1_H] +Conoces a 8-Ball, ¿verdad? + +[JM1_I] +En cuanto le ponga una bomba, aparca el coche donde lo encontraste. + +[JM1_J] +Luego salte y disfruta del espectáculo. + +[JM1_K] +Pero cuidado, no va a estar comiendo toda la vida. + +[CAT2_A1] +¡Vamos, perra! + +[CAT2_A] +La verdadera pregunta es: ¿vienes buscando a María o buscándome a mí? + +[CAT2_B] +Tengo una noticia para ti: + +[CAT2_B2] +matarte será un placer, pero salir contigo sólo fue un negocio. + +[CAT2_C] +¡Eres muy pequeñito, amigo! + +[CAT2_D] +Dame el dinero. + +[CAT2_E] +¡Has estado muy ocupado! + +[CAT2_E2] +Pero no has aprendido que yo no soy de fiar. + +[CAT2_E3] +¡Mata al idiota! + +[CAT2_J] +¡Despega de una vez! + +[HM5_1] +Hola, Ice dijo que vendrías. Las reglas son sólo bates. Cero armas, cero coches. + +[HM5_5] +Es una batalla por respeto, ¿vale? + +[HELP14] +Para conseguir un arma, pasa sobre ella. No podrás recogerlas estando dentro de un vehículo. + +[CRUSH] +Aparca en la zona señalada y sal de tu vehículo para que sea triturado. + +[DIAB2_B] +Una banda de roñosos amenazó con quitarme a mi protagonista si no les pago. + +[DIAB2_C] +Amenazaron al hombre equivocado, amigo. + +[DIAB2_D] +Su debilidad son los helados. + +[DIAB2_E] +Hazte con la bomba que dejé en Harwood, + +[DIAB2_F] +secuestra la camioneta del heladero mientras hace su ronda + +[DIAB2_G] +y engaña a esos idiotas con esa musiquita. + +[DIAB2_H] +Se esconden en un almacén del muelle Atlantic. + +[DIAB3_A] +¡Unas Tríadas insolentes robaron mi lindo carro anoche, + +[DIAB3_B] +lo destrozaron y lo quemaron! + +[DIAB3_C] +En el baúl había algunas de mis más preciadas posesiones sobre burros... + +[DIAB3_D] +Objetos de coleccionista irremplazables, amigo. + +[DIAB3_E] +Escondí un arma caliente en el borde de Chinatown. + +[DIAB3_F] +Tómala y enseña a esos vándalos de las Tríadas a tenerle miedo a la ira pijuda de El Burro. + +[DIAB3_1] +MATA A 25 TRÍADAS + +[DIAB4_A] +¡Un ladrón oportunista robó una camioneta con mi última publicación! + +[DIAB4_B] +Pero ese idiota drogado de SPANK dejó las puertas de atrás abiertas, + +[DIAB4_C] +¡y ahora mi literatura para adultos + +[DIAB4_D] +tan bellamente producida, tan gustosamente fotografiada, está siendo esparcida por toda Liberty! + +[DIAB4_E] +Monta en la camioneta y sigue el rastro de los volúmenes 1, 2 y 3 de Donkey Does Dallas, + +[DIAB4_F] +tomándolos por el camino. + +[DIAB4_G] +¡Cuando alcances a ese bandido espaciado con SPANK, chíngatelo! + +[DIAB4_H] +Luego reparte mis revistas XXX por el Red Light District. + +[DIAB4_1] +~g~Lleva la furgoneta a la parte de atrás de Revistas XXX. + +[HM1_E] +Quiero que esos mierdecillas sepan lo que es un verdadero tiroteo desde un vehículo. + +[HM1_H] +¡Haz que esos Nines se larguen! + +[HM2_A] +Los Nines me están presionando. + +[HM2_B] +Tienen coches blindados y ahora están pasando SPANK + +[HM2_C] +a los hermanos sin ningún pudor. + +[HM2_D] +Hay un coche aparcado en la calle. + +[HM2_E] +Dentro lleva algo que te servirá para poner a esos gallinas en su sitio + +[HM3_A] +Algún cazurro me ha puesto una bomba en el coche. + +[HM3_B] +Si lo pierdo, mi reputación en la calle se irá a la porra. + +[HM3_C] +Coge mi coche y llévalo al taller de Saint Mark's, ¿valiendo? + +[HM3_D] +Que se encarguen ellos de desarmar la bomba. + +[HM3_E] +El tiempo corre y ese trasto es un peligro. + +[HM3_F] +Un golpe más de la cuenta y podría saltar por los aires. + +[HM3_G] +¡Tira! + +[HM4_A] +Tío, un vuelo de la Reserva Federal se la acaba de pegar en el Aeropuerto Francis. + +[HM4_B] +Hay platino por toda la pista. + +[HM4_C] +Consigue un coche y agarra todo lo que puedas. + +[HM4_F] +Puedes dejar la mercancía en uno de mis garajes. + +[HM4_G] +El platino pesa un huevo y hará que tu buga vaya a paso de tortuga, + +[HM4_H] +así que ve dejándolo de vez en cuando en el garaje. + +[HM5_A] +Ya sólo quedan unos pocos Nines, + +[HM5_B] +pero todavía quieren guerra. + +[HM5_C] +Han aceptado un mano a mano. + +[HM5_D] +Un puñado de los suyos contra dos de los nuestros, + +[HM5_E] +o más bien, tú y uno más. + +[HM5_F] +Os ayudaría, pero... + +[HM5_G] +No quiero jugarme mi libertad condicional, + +[HM5_H] +¿me captas? + +[HM5_I] +Reúnete con mi hermano pequeño, + +[HM5_J] +él te enseñará dónde será la pelea. + +[MEA1_B] +Me llamo Chonks, Marty Chonks. + +[MEA1_C] +Soy el dueño de la fábrica de comestibles Bitch'n' Dog que está a la vuelta de la esquina. + +[MEA1_D] +Tengo problemas económicos, ¿pero y quién no? + +[MEA1_E] +He quedado con el director de mi banco. + +[MEA1_F] +Es un chorizo que no para de inflar los intereses del préstamo para sacarme una buena tajada. + +[MEA1_G] +Coge mi coche, búscale y tráelo aquí. + +[MEA1_H] +¡Tengo una sorpresita para ese parásito chupasangre! + +[MEA2_A] +Contraté a unos ladrones para que entraran en mi piso + +[MEA2_C] +Ahora los muy cabritos me han amenazado con delatarme + +[MEA2_D] +si no les doy una parte. + +[MEA2_E] +¿Te lo puedes creer? + +[MEA2_F] +He dejado un coche dentro de la fábrica. + +[MEA2_G] +Ve a recogerles con él en su territorio, en el Red Light District. + +[MEA2_H] +Luego tráelos a la fábrica para que conozcan la opinión de Marty. + +[MEA3_A] +El negocio se irá a pique si no consigo un pastizal muy pronto. + +[MEA3_B] +Mi esposa tiene un seguro de vida y ella no ha hecho más que vaciarme los bolsillos. + +[MEA3_C] +He dejado un coche donde siempre. + +[MEA3_D] +Ve a por a mi esposa a Classic Nails y tráela a la fábrica. + +[MEA4_A] +Mierda, ¡la he liado! + +[MEA4_B] +Resulta que mi esposa salía con uno al que debo dinero. + +[MEA4_C] +¡Se ha cabreado y ahora quiere vengarse! + +[MEA4_E] +y piensa que voy a devolverle el dinero. + +[MEA4_F] +Pero creo... + +[MEA4_G] +¡que los perros de Liberty van a disfrutar de un sabor nuevo este mes! + +[WELCOME] +BIENVENIDO A + +[HM1_2] +~g~Hazte con un vehículo y recuerda que sólo cuentan las muertes a tiros desde el coche. + +[HELP8_B] +Pulsa ~h~~k~~PED_SNIPER_ZOOM_IN~ ~w~para ~h~aumentar el zoom ~w~de la mira del fusil y ~h~~k~~PED_SNIPER_ZOOM_OUT~~w~ para ~h~reducirlo~w~. + +[LRQC_1] +Asuka y yo tenemos que hablar... + +[LRQC_2] +¿Por qué no te das una vuelta? + +[LRQC_3] +Necesitarás un escondite. + +[LRQC_4] +Hay un almacén en la orilla de Belleville que podría servirte. + +[LRQC_5] +Regresa a mi apartamento cuando estés listo + +[LRQC_6] +y podremos tener una charla. + +[JM6_5] +~g~Necesitas un vehículo de fuga, ¡idiota! + +[JM2_F] +Si necesitas una pipa, ve a la parte de atrás del Ammu-Nation que está enfrente del metro. + +[LOVE4_7] +~g~Hay un edificio en construcción en Staunton Island, puede que hayan llevado el paquete allí. + +[LOVE4_8] +~g~Necesitarás un coche para abrir el garaje. + +[TSCORE] +GANANCIAS: ~1~ $ + +[AM1_9] +~r~¡Salvatore ha vuelto a meterse en el club de Luigi! + +[AM1_6] +~g~Si te dejas ver por el club de Luigi, ¡la mafia te descubrirá! + +[TM2_3] +~g~¡Es una trampa! ¡Liquidadlos a todos! + +[FM4_1] +Soy María. ¡El coche es una trampa! Estoy en el lado sur del puente Callahan. + +[JM1_7] +~g~¡Cierra la puerta del coche o se dará cuenta! + +[KM5_1] +~g~¡TRAFICANTE LIQUIDADO! + +[KM5_6] +~g~Debes matar a al menos 8 traficantes jamaicanos. + +[KM5_7] +~g~¡Mátalos rápido! En cuanto vendan el SPANK se retirarán de las calles. + +[RM3_8] +~r~¡El coche es un señuelo! + +[LM3_8] +Hola, soy Joey. + +[LM3_9] +Luigi dijo que eras de fiar, así que vuelve más tarde, + +[KM3_5] +~g~Toca el claxon para empezar con el trato. + +[LOVE7] +'LA DESAPARICIÓN DE LOVE' + +[LOVE2_5] +~g~¡Kenji ha palmado! ¡Sal de Newport y deshazte del coche! + +[AS2_11] +~g~¡~1~ DE 9! + +[GARAGE1] +~g~Sal del vehículo y aléjate caminando. + +[KM3_11] +~g~El cártel ha sido atacado y el maletín no ha sido recuperado. + +[KM3_12] +~g~Mata a todos los colombianos, destruye los vehículos y recupera el maletín. + +[KM3_13] +~g~Lleva el maletín de vuelta al casino. + +[RM5_6] +~g~¡Ha salido de la ambulancia! ¡Cárgate su escayola con un vehículo o una explosión! + +[PBOAT_1] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar los cañones de la lancha. + +[PBOAT_2] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar los cañones de la lancha. + +[DIAB1_B] +Al habla El Burro, de los Diablos. + +[DIAB1_D] +Eres nuevo en Liberty, pero ya te estás ganando una reputación en las calles. + +[DIAB1_E] +Hay una carrera que empezará junto a la sala Clásica, cerca del puente Callahan. + +[DIAB1_F] +Consíguete un buen carro y el primero que pase por todos los puntos de control se llevará el premio. + +[HM2_1] { re3 change } +Usa los coches teledirigidos para destruir los furgones blindados. Pulsa ~h~~k~~VEHICLE_FIREWEAPON~ ~w~para detonarlos. + +[HM2_1A] { re3 change } +Usa los coches teledirigidos para destruir los furgones blindados. Pulsa ~h~~k~~VEHICLE_FIREWEAPON~ ~w~para detonarlos. + +[HM2_2] +~r~¡No has destruido todos los furgones blindados! + +[HM2_6] +~g~¡Te has cargado un furgón blindado! + +[RM3_A] +Conozco a un pez gordo de la ciudad, un tipo muy cándido, + +[RM3_H] +con, digamos... gustos exóticos y el dinero para pagárselos. + +[RM3_B] +Está involucrado en un asunto legal y la fiscalía tiene fotos de él muy comprometedoras + +[RM3_C] +en una fiesta en la morgue o algo así. + +[LOVE6_A] +Una lección sobre negocios, amigo mío: + +[LOVE6_E] +Si tienes una mercancía única, todo el mundo tratará de arrebatártela, + +[LOVE6_C] +Unos SWAT han acordonado la zona donde se encuentran mi socio y el paquete. + +[LOVE6_D] +Ve allí, recoge la furgoneta y haz de señuelo. + +[LOVE6_F] +Mantenlos ocupados para que él pueda escapar. + +[AM3_C] +Ahora mismo estará en la bahía. ¡Roba una lancha de la policía y hunde su carrera! + +[FESZ_UC] +CANCELAR + +[FEDS_SM] +L1, R1 - CAMBIAR MENÚ + +[FEDS_AS] +;= - CAMBIAR SELECCIÓN + +[FEDSAS2] +<> - CAMBIAR SELECCIÓN + +[FEDS_SS] +L1,R1 - CAMBIAR SELECCIÓN + +[FEDSSC1] +; - ACELERAR + +[FEDSSC2] += - RALENTIZAR + +[MEA2_3] +~g~Lleva el coche de vuelta a la fábrica. + +[RM1_3] +~r~¡McAffrey se ha escapado! + +[RM1_4] +~g~¡Has usado todas las granadas! ¡Consigue más en la tienda Ammu-Nation! + +[RM1_5] +~g~¡Vuelve y quema la casa franca! + +[RM6_4] +~g~Ve al almacén a por el cargamento de Ray. + +[RM6_5] +~g~La CIA vigila el puente, busca otro camino. + +[HM2_F] +y cargarte sus blindados. + +[HM_4] +'FIEBRE DEL PLATINO' + +[MEA4_B7] +Pero si pasas por mi oficina... + +[MEA3_B4] +¿Marty quiere verme? Bueno, pues que sea rápido, porque tengo que ir a la peluquería. + +[KM3_7] +¡Es una trampa de la yakuza, man! + +[FES_LOF] +Fallo al cargar. + +[P1INSA] +La Memory Card (PS2) insertada en la ranura para MEMORY CARD 1 tiene ~1~ KB de espacio disponible. Necesitas ~1~ KB para guardar. + +[P1INSN] +La Memory Card (PS2) insertada en la ranura para MEMORY CARD 1 no tiene espacio suficiente. Por favor, borra algunos archivos. + +[FES_SLO] +ARCHIVO + +[FES_ISC] +ESTÁ DAÑADO + +[FESZ_TI] +ARCHIVO Z1 + +[FESZ_SA] +Guardar partida + +[P1NOIN] +No hay una Memory Card (PS2) insertada en la ranura para MEMORY CARD 1. + +[P1INSE] +Memory Card (PS2) insertada en la ranura para MEMORY CARD 1. + +[MC_LDFL] +¡Fallo al cargar! + +[MC_NWRE] +Reiniciando partida. + +[LOVE6_3] +~g~Tienes ~1~ segundos para volver al Securicar antes de fracasar la misión. + +[LOVE6_4] +~r~¡Has abandonado el Securicar señuelo! + +[HELP1] +Detente en el centro del marcador azul. + +[HELP12] +Entra en el marcador azul para comenzar una misión. + +[HJSTAT] +Distancia: ~1~,~1~ m. Altura: ~1~,~1~ m. Vueltas: ~1~. Rotación: ~1~_. + +[HJSTATW] +Distancia: ~1~,~1~ m. Altura: ~1~,~1~ m. Vueltas: ~1~. Rotación: ~1~_. ¡Y qué buen aterrizaje! + +[DIAB1_5] +TIEMPO DE CARRERA: + +[LOVE3_4] +~r~¡Has destruido la avioneta! + +[F_FAIL1] +¡Misión del camión de bomberos terminada! + +[F_CANC] +~r~¡Misión del camión de bomberos cancelada! + +[F_EXTIN] +INCENDIOS: + +[A_COMP1] +¡Misiones de conductor de ambulancia completadas! + +[A_CANC] +~r~¡Misión de conductor de ambulancia cancelada! + +[A_COMP3] +¡Misiones de conductor de ambulancia completadas! ¡Ahora no te cansarás al esprintar! + +[ATUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de conductor de ambulancia. + +[ATUTOR3] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de conductor de ambulancia. + +[ALEVEL] +Nivel de misión de conductor de ambulancia: ~1~ + +[A_FAIL1] +Misión de conductor de ambulancia terminada. + +[FEST_HA] +Mayor nivel de misión de cond. de ambulancia + +[A_SAVES] +PERSONAS SALVADAS: ~1~ + +[C_KILLS] +CRIMINALES ABATIDOS: ~1~ + +[HM1_B] +Tengo un problema, me la están liando. + +[AM2_A] +La muerte de Salvatore es una placentera noticia, + +[AM2_A2] +eres un asesino eficaz. Eso me gusta en un hombre. + +[AM2_B] +Éste es mi hermano Kenji. + +[AM2_C] +Asuka tiene un trabajito para ti, pero cuando acabes, pásate por mi casino y podremos hablar. + +[AM2_D] +Típico de Kenji, siempre está detrás de mis juguetes. + +{ The voiced dialogue doesn't match the English subtitle for the next string } + +[AM2_E] +Mi contacto en la policía me dice que el FBI ha montado un operativo de vigilancia + +{ The voiced dialogue doesn't match the English subtitle for the next string } + +[AM2_E2] +en varios puntos de la ciudad. + +{ The voiced dialogue doesn't match the English subtitle for the next string } + +[AM2_F] +No tenemos tiempo para contactar con nadie y evitar que nos incriminen. + +{ The voiced dialogue doesn't match the English subtitle for the next string } + +[AM2_G] +Liquida a esos polis espías, pero cuidado: tendrán apoyo. + +[F_START] +~g~Se ha avistado un vehículo en llamas en: ~a~. Ve y extingue el fuego. + +[AM4_1A] +Ve a la cabina al oeste del parque Belleville. + +[AM4_1B] +Ve a la cabina del campus de Liberty. + +[AM4_1C] +Ve a la cabina al sur del parque Belleville. + +[AM4_1D] +Ven a verme a los baños públicos del parque. + +[HJSTATF] +Distancia: ~1~ pies. Altura: ~1~ pies. Vueltas: ~1~. Rotación: ~1~_. + +[HJSTAWF] +Distancia: ~1~ pies. Altura: ~1~ pies. Vueltas: ~1~. Rotación: ~1~_. ¡Y qué buen aterrizaje! + +[HM1_F] +Pero ojo, también habrá Jacks que se creerán que vas a por ellos. + +[HM1_D] +Se llaman ''Nines'', van de púrpura, y cada día en el que se hacen notar + +[HM1_G] +es otro día más que los Jacks parecemos blandos. + +[MEA2_B] +y robaran cuanto pillaran para que yo pudiera reclamar a la aseguradora. + +[TM3_H] +Buen trabajo, chaval, muy bueno. + +[TM3_I] +Vente, te presentaré al Don. + +[TM3_J] +¡Hola! ¡Luigi! + +[TM3_K] +Mis chicas te han echado de menos, Salvatore. Hace bastante que no te vemos. + +[TM3_L] +Diles que en cuanto resolvamos este desafortunado incidente + +[TM3_M] +iremos todos al club para celebrarlo, ¿vale? + +[TM3_N] +¡Mi niño! + +[TM3_N2] +¿Cómo estás, papá? + +[TM3_O] +¿Has encontrado ya una buena mujer? + +[TM3_P] +Tu madre, que en paz descanse, se retorcería en la tumba + +[TM3_Q] +si te viera sin una mujer. + +[TM3_R] +Lo sé, papá, estoy en ello. + +[TM3_S] +¡Toni! ¿Cómo está tu madre? + +[TM3_T] +Es una gran mujer, ¿sabes? Fuerte, ''firenze''. + +[TM3_U] +Está bien, estupendamente. + +[TM3_V] +Fantástico, fantástico. Bien, muchachos, entrad mientras yo hablo con nuestro nuevo amigo. + +[TM3_W] +Tienes un gran futuro por delante, hijo mío... + +[RM1_A] +¡Ese canalla de McAffrey...! ¡Aceptó más sobornos que nadie, + +[RM1_B] +se piensa que le darán la pensión completa si se convierte en un testigo de cargo! + +[RM1_C] +¡Y ha cantado! + +[RM4_B] +Tenemos que cerrarle la boca para siempre. + +[RM4_E] +¡Quiero que deje de comer peces y se vaya a dormir con ellos! + +[LOVE3_B] +Esta noche, durante su aproximación al aeropuerto, una avioneta sobrevolará la bahía. + +[LOVE4_D] +Desgraciadamente, las autoridades aduaneras incautaron la avioneta y la comenzaron a desmontar + +[LOVE4_H] +hasta que yo intervine a través de mi fortuna. + +[LOVE4_E] +Cruza el puente hacia Shoreside Vale y ve al Aeropuerto Internacional Francis. + +[GTAB_A] +Oye, saquemos esto de aquí. Dios sabrá lo que será, + +[GTAB_B] +pero él lo quiere a toda costa, así que tendrá algún valor. + +[GTAB_C] +¡¿Qué diablos?! + +[GTAB_D] +¡TÚ! + +[GTAB_E] +¡Tranquilo, amigo! ¡No es nada! ¡No es nada! + +[GTAB_F] +¡Te dejé desangrándote entre la basura! + +[GTAB_G] +No dispares, amigo. No hay problema. Somos amigos. Mira, coge esto. + +[GTAB_H] +¡No seas cagón! + +[GTAB_I] +¡No tenemos elección, nena! + +[GTAB_J] +¡Siempre la hay, imbécil! + +[GTAB_K] +¡Siento mucho lo de esa perra enloquecida, todas son iguales...! ¿Por favor? + +[GTAB_L] +Así que la zorra se fue. + +[GTAB_M] +Pero me has hecho un favor, + +[GTAB_N] +no eres el único que tiene una cuenta pendiente con el cártel... + +[GTAB_O] +¡Este gusano mató a mi hermano! + +[GTAB_P] +¡Nunca maté a ningún yakuza! + +[GTAB_Q] +¡Mientes! Todos vimos al asesino del cártel. + +[GTAB_R] +¡Vamos a cazaros y a mataros a todos, perros colombianos! + +[GTAB_S] +Me trabajaré a nuestro querido amigo para extraerle información y un poco de placer. + +[GTAB_T] +Tú, ven más tarde, estoy segura de que voy a necesitar tus servicios. + +[GTAB_U] +¡Por favor, amigo! ¡No me dejes con ella, esa chica está loca! ¿Amigo? ¡Oye, amigo! ¡Amigo...! + +[LOVE5_A] +Estás demostrando ser una inversión segura, algo muy raro en estos días. + +[KM3_1] +~g~El cártel espera a una banda jamaicana, ¡así que roba uno de sus coches! Dirígete al norte, encontrarás uno en Newport. + +[LOVE1_1] +~g~Roba un coche de la banda colombiana para que puedas entrar en su escondite. Ve al norte, encontrarás uno en Fort Staunton. + +[FM1_Q1] +¿Quieres un poco de diversión? ¿Un poco de... hmmm, de SPANK? + +[FM1_R] +Hola, Chico. No, sólo lo de siempre. + +[FM1_T] +Gracias, Chico, nos vemos. + +[FM1_W] +Oye, tú, espera aquí y quédate pendiente del coche mientras yo voy a menear el esqueleto, ¿vale? + +[FM1_X] +¡Vale, tú, salgamos de aquí! ¡Uaaah! + +[FM1_Q] +¡Ay, mira, es mi chica favorita! + +[FM1_S1] +Ey, deberías echar un vistazo a la fiesta del almacén en el lado este del muelle Atlantic. + +[FM1_U] +Gracias y disfruta. Es buen material. + +[FM1_V] +¡Venga, tú, vamos a ver esa fiesta! + +[FM1_SS] +~r~ESCÁNER: ~g~Cuatro-cinco a todas las unidades: asistan redada de narcóticos en el muelle Atlantic. + +[LOVE6_B] +aun sin conocer su verdadera valía. + +[TM3_A1] +~r~¡Joey está frito! + +[TM3_A2] +~r~¡Joey y Luigi han sido incinerados! + +[TM3_A3] +~r~¡Joey, Luigi y Toni están calcinados! + +[FM4_2] +Mira, Salvatore cree que se la estamos jugando, + +[FM4_3] +así que te vendió al cártel para llegar a un trato. + +[FM4_4] +No podía permitírselo, o sea, lo peor es... + +[FM4_4B] +que yo tengo la culpa, porque le dije que somos pareja. + +[FM4_5] +¡No me preguntes por qué, no lo sé! + +[FM4_6] +Mira, la mafia te quiere muerto y yo también tengo que salir de aquí. + +[FM4_6B] +¡He visto demasiados asesinatos, demasiada sangre! + +[FM4_7] +Es una amiga mía, ¿vale?, una vieja amiga... Es Asuka, es de fiar. + +[FM4_8] +Venga, dejémonos de discursos. + +[FM4_9] +Mejor nos vamos antes de que lleguen más italianos histéricos con intenciones menos amistosas. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCTOR + +[CRED003] +LESLIE BENZIES + +[CRED004] +DIRECTOR DE ARTE + +[CRED005] +AARON GARBUT + +[CRED006] +DIRECCIÓN TÉCNICA + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DISEÑO + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +GUIÓN + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +PERSONAJES + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMACIÓN Y DIRECCIÓN + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +DISEÑO DE VEHÍCULOS + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTAS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +PROGRAMADORES + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +MÚSICA + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +DISEÑO DE SONIDO Y MÁSTER + +[CRED042] +ALLAN WALKER + +[CRED043] +PROGRAM. DE AUDIO + +[CRED044] +RAYMOND USHER + +[CRED045] +DIRECTOR DE PRUEBAS + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +JEFES DE PRUEBAS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +PROBADORES + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +SOPORTE TÉCNICO + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +PRODUCTOR EJECUTIVO + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCTOR + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR DE DESARROLLO + +[CRED066] +JAMIE KING + +[CRED067] +PRODUCTOR TÉCNICO + +[CRED068] +GARY J. FOREMAN + +[CRED069] +PRODUCTOR ASOCIADO + +[CRED070] +JEREMY POPE + +[CRED071] +SUPERVISOR MUSICAL + +[CRED072] +TERRY DONOVAN + +[CRED073] +EQUIPO DE PRODUCCIÓN DE ROCKSTAR + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +V.P. DE MÁRKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +COORDINADOR TÉCNICO + +[CRED088] +BRANDON ROSE + +[CRED089] +DIRECTOR DE CONTROL DE CALIDAD + +[CRED090] +JEFF ROSA + +[CRED091] +JEFE DE ANÁLISIS + +[CRED092] +ADAM DAVIDSON + +[CRED093] +ANALISTA DEL JUEGO + +[CRED094] +RICHARD HUIE + +[CRED095] +EQUIPO DE PRUEBAS + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +EDITORIAL DEL LIBERTY TREE + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CINEMÁTICAS + +[CRED108] +GUIÓN DE DAN HOUSER Y JAMES WORRALL + +[CRED109] +AUDIO DIRIGIDO POR DAN HOUSER + +[CRED110] +AUDIO PRODUCIDO POR RENAUD SEBBANE + +[CRED111] +REPARTO + +[CRED112] +FRANK VINCENT - SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO - LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN - TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT - JOEY LEONE + +[CRED116] +DEBBI MAZAR - MARÍA + +[CRED117] +KYLE MACLACHAN - DONALD LOVE + +[CRED118] +ROBERT LOGGIA - RAY MACHOWSKI + +[CRED119] +GURU - 8-BALL + +[CRED120] +SONDRA JAMES - MAMMA + +[CRED121] +LIANA PAI - ASUKA + +[CRED122] +LES MAU - KENJI + +[CRED123] +CYNTHIA FARRELL - CATALINA + +[CRED124] +AL ESPINOSA - MIGUEL + +[CRED125] +CHRIS PHILLIPS - EL BURRO + +[CRED126] +HUNTER PLATIN - CHICO + +[CRED127] +WALTER MUDU - D-ICE + +[CRED128] +CURTIS MCCLARIN - CURTLY + +[CRED129] +BILL FIORE - DARKEL + +[CRED130] +CHRIS PHILLIPS - MARTY CHONKS + +[CRED131] +HUNTER PLATIN - CURLY BOB + +[CRED132] +WALTER MUDU - REY COURTNEY + +[CRED133] +HUNTER PLATIN - PHIL EL MANCO + +[CRED134] +KIM GURNEY - MISTY + +[CRED135] +CAPTURA DE MOVIM. + +[CRED136] +ANIMACIÓN + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRECCIÓN + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUCCIÓN + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +GRABADA EN MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORES + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS + +[CRED141] +DIÁLOGO DE PEATONES + +[CRED142] +ESCRITO POR DAN HOUSER, NAVID KHONSARI Y JAMES WORRALL + +[CRED143] +DIRIGIDO POR CRAIG CONNER, DAN HOUSER Y LAZLOW + +[CRED144] +PRODUCIDO POR RENAUD SEBBANE + +[CRED145] +REPARTO + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +EMISORAS DE RADIO Y MÚSICA + +[CRED218] +PRODUCTORES DE ROCKSTAR REINO UNIDO + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +COORDINADOR DE BANDA SONORA + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCTOR DE ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDICIÓN + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +GUIÓN DE LOCUTORES Y CUÑAS + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +AGRADECIMIENTOS + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRISTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTISTAS + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CINCAM] +Cámara cinematográfica + +[KM1_13] +¡Mete el vehículo en el garaje! + +[KM3_14] +~r~¡Te han descubierto, han cancelado el trato! + +[EBAL_H] +Espera, tío, que voy a hablar con Luigi. + +[EBAL_M] +¡Recuerda: nadie se mete con mis chicas! + +[LM2_F] +Luego coge su coche y repíntalo. + +[LM2_D] +Toma, toma, para ti. + +[LM1_9] +Hola, soy Misty. + +[LM4_A] +Algún Diablo de las narices ha estado vendiendo a sus putitas en mi territorio. + +[FM2_B] +¡Tenemos a un chivato! + +[FM2_C] +No está ni chuleando ni traficando, así que estará cantando. + +[FM3_CC] +Regresa cuando tengas el dinero. + +[FEDS_AM] +<> - CAMBIAR MENÚ + +[LOVE5_5] +~r~¡No has protegido al camión! + +[RM6_6] +~r~¡Ray ha muerto! + +[RM6_7] +~r~¡Ray ha perdido su avión! + +[RM6_8] +~g~Has abandonado a Ray, vuelve a por él. + +[FM1_10] +~g~Has abandonado a María, vuelve a por ella. + +[LOVE4_9] +~r~¡El avión ha sido destruido! + +[LOV4_10] +~r~¡La única pista sobre dónde se encuentra el paquete ha sido destruida! + +[KM2_D] +No hace falta decir que debemos darle los coches como un regalo para pagar mi deuda con él. + +[KM4_B] +El negocio es lo bastante afortunado como para permitir que nuestra protección salde sus cuentas hoy mismo. + +[KM2_E] +Debes obtener los coches de la lista y llevarlos a un garaje que hay tras el aparcamiento de Newport. + +[FM3_8I] +Encuentra una posición elevada. Entraré cuando dispares el primer tiro. + +[LOVE1_B] +La experiencia me ha enseñado que un hombre como tú puede ser muy leal por el precio correcto, + +[LOVE1_H] +pero los grupos de hombres se vuelven codiciosos. + +[LOVE1_C] +Un activo valioso, un anciano oriental que conozco, + +[LOVE1_I] +está siendo retenido por unos sudamericanos en Aspatria. + +[MEA4_D] +He quedado con él, + +[MEA4_B4] +¿Te envía Marty? Vale, le voy a enseñar a ese sinvergüenza el significado de la palabra negocio. + +[MEA4_B5] +¡Carl, hola! Ehhh... Necesito más tiempo para conseguir tu dinero. + +[MEA1_B4] +Ah, te envió el Sr. Chonks, ¿verdad? Vayamos a visitarlo. + +[HM5_6] +Vamos a partir cabezas... + +[LOVE1_5] +~g~Deja de perder el tiempo, hazte con un coche de los colombianos y rescata al socio de Love. + +[AS1_D] +Haz de cebo y consigue que los escuadrones te sigan hasta Pike Creek, + +[AS1_E] +donde estarán esperando algunos de mis hombres. + +[AS2_C] +El cártel tiene una tapadera, el Kappa Coffee House. + +[AS2_E] +No tenemos otra opción más que sabotear esos puntos de venta. + +[AS2_F] +¡Redúcelos a cenizas! + +[AS2_A1] +¡Está claro que Miguel tiene esa famosa resistencia latina! + +[AS2_A2] +Estoy agotada. + +[SIREN_3] +Para activar la sirena de este vehículo, pulsa ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_4] +Para activar la sirena de este vehículo, pulsa ~h~~k~~VEHICLE_HORN~~w~. + +[AS3_C] +¡Buaj! ¿Qué es esa cosa amarilla pegajosa? + +[AS3_C1] +¡Hola, guapo! + +[AS3_F] +Esta chica tiene un talento nato para la tortura. + +[AS3_F1] +Se las ha arreglado para extraerle esta joyita a nuestro invitado. + +[AS3_G] +Hay una avioneta que llegará al Aeropuerto Francis en dos horas. + +[AS3_G1] +Está llena del veneno de Catalina. + +[AS3_H] +Podrás evitar la seguridad del aeropuerto si conduces una lancha hasta las boyas luminosas, + +[AS3_H1] +así podrás disparar a la avioneta al aterrizar. + +[AS3_I] +¡Recoge el cargamento de entre los escombros y tráelo! + +[AS3_J] +Ten cuidado, guapo, ¿vale? + +[AS3_K] +Ahora prueba con el aceite picante... + +[RM2_F1] +¡Los colombianos llegarán en cualquier momento! + +[RM2_K] +Maldita sea, ¡están aquí! ¡FUEGO A DISCRECIÓN! + +[LOVE2_7] +~g~¡Ahora deshazte del coche! + +[LOVE2_8] +~g~¡Sal de Newport! + +[AM1_F] +Salvatore Leone saldrá del club de Luigi dentro de unas tres horas. (~1~:~1~) + +[LOVE5_C] +Quiero que le sigas y que te asegures de que tanto él como mi paquete llegan a Pike Creek sin daño alguno. + +[FESZ_SR] +¡Error al guardar! Comprueba la Memory Card (PS2) de la ranura de MEMORY CARD 1 e inténtalo de nuevo. + +[FESZ_FO] +¿Deseas formatear la Memory Card (PS2) en la ranura de MEMORY CARD 1? + +[FELZ_FO] +La Memory Card (PS2) de la ranura de MEMORY CARD 1 no tiene formato. + +[FES_NOC] +No hay una Memory Card (PS2) insertada en la ranura para MEMORY CARD 1. + +[FES_LOE] +¡Fallo al cargar! Comprueba la Memory Card (PS2) de la ranura de MEMORY CARD 1 e inténtalo de nuevo. + +[FES_DEE] +¡Fallo al borrar! Comprueba la Memory Card (PS2) de la ranura de MEMORY CARD 1 e inténtalo de nuevo. + +[FORSUC] +Memory Card (PS2) de la ranura de MEMORY CARD 1 formateada con éxito. + +[ERFOUN] +¡Error al formatear la Memory Card (PS2)! + +[ERMCNP] +No hay Memory Card (PS2) en la ranura de MEMORY CARD 1. + +[SVMEM1] +Guardando en la Memory Card (PS2) de la ranura de MEMORY CARD 1. + +[FORSLO] +Formateando Memory Card (PS2) de la ranura de MEMORY CARD 1. + +[SLONFM] +¡Error al formatear la Memory Card (PS2)! + +[SLONDR] +No hay espacio suficiente en la Memory Card (PS2). Inserta una Memory Card (PS2) con, al menos, 500KB de espacio libre en la ranura de MEMORY CARD 1. + +[SLNSP] +No hay espacio suficiente en la Memory Card (PS2). Inserta una Memory Card (PS2) con, al menos, 200KB de espacio libre en la ranura de MEMORY CARD 1. + +[FEFD_WR] +Formateando la Memory Card (PS2) de la ranura de MEMORY CARD 1. No extraigas la Memory Card (PS2), ni reinicies o apagues la consola. + +[FES_ISF] +NO PRESENTE + +[FES_SAG] +PRESENTE + +[SLONNO] +No hay una Memory Card (PS2) en la ranura de MEMORY CARD 1. + +[SLONNF] +La Memory Card (PS2) de la ranura de MEMORY CARD 1 no tiene formato. + +[FESZ_FM] +La Memory Card (PS2) de la ranura de MEMORY CARD 1 no tiene formato. ¿Deseas formatear la Memory Card (PS2)? + +[FESZ_FF] +¡Fallo al formatear! Comprueba la Memory Card (PS2) de la ranura de MEMORY CARD 1 e inténtalo de nuevo. + +[MCDNSP] +No hay espacio suficiente en la Memory Card (PS2) de la ranura de MEMORY CARD 1. Inserta una Memory Card (PS2) con, al menos, 500KB de espacio libre para guardar los datos de esta aplicación. ¿Deseas empezar? (SÍ o NO) + +[MCGNSP] +No hay espacio suficiente en la Memory Card (PS2) de la ranura de MEMORY CARD 1. Se necesitan al menos 200KB para guardar los datos de esta aplicación. ¿Deseas empezar? (SÍ o NO) + +[FESZ_WR] +Guardando datos. No extraigas la Memory Card (PS2) de la ranura de MEMORY CARD 1, ni reinicies o apagues la consola. + +[FESZ_OW] +Sobrescribiendo datos. No extraigas la Memory Card (PS2) de la ranura de MEMORY CARD 1, ni reinicies o apagues la consola. + +[FELD_WR] +Cargando datos. No extraigas la Memory Card (PS2) de la ranura de MEMORY CARD 1, ni reinicies o apagues la consola. + +[FEDL_WR] +Borrando datos. No extraigas la Memory Card (PS2) de la ranura de MEMORY CARD 1, ni reinicies o apagues la consola. + +[LM2_C] +Luigi dijo que... que te diera esto... + +[LM3_G] +que a Joey no le gusta esperar. Recuerda, tienes un pie dentro... + +[LM5_E] +Saca todo el dinero que puedas a los polis antes de que se lo beban. + +[JM5_C] +Vale, hay un coche cargado con un fiambre en el bar cercano a Callahan Point. + +[RM2_B] +Combatimos juntos en Nicaragua, cuando el país sabía lo que hacía. + +[RM2_C] +En fin, ayer unos cerdos del cártel lo zurraron y le dijeron que volverían hoy para quitarle parte de su género. + +[RM2_D1] +Iría yo mismo, pero la ciática ya está en sus trece... ¡Cof, cof! Así que... buena suerte. + +[CATINF1] +~g~¡Liquida a Catalina! + +[CATINF2] +~g~Sigue al helicóptero para dar con Catalina. + +[BOATIN1] +Sube a una lancha y pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ponerte a los mandos. + +[BOATIN2] +Puedes pulsar ~h~~k~~VEHICLE_ENTER_EXIT~~w~ si estás cerca de una lancha para abordarla. + +[BOATIN3] +Sube a una lancha y pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ponerte a los mandos. + +[BOATIN4] +Puedes pulsar ~h~~k~~VEHICLE_ENTER_EXIT~~w~ si estás cerca de una lancha para abordarla. + +[JM6] +'LA HUIDA' + +[FM1] +'CARABINA' + +[JM1] +'EL ÚLTIMO ALMUERZO DE MIKE ''LABIOS'' ' + +[FM21] +'BASE FUERA: ACTO I' + +[FM3] +'BASE FUERA: ACTO II' + +[AM1] +'SAYONARA, SALVATORE' + +[AM2] +'BAJO VIGILANCIA' + +[KM2] +'ROBO DE VEHÍCULOS' + +[AS3] +'TIERRA-AIRE' + +[RM2] +'ESCASEZ DE MANOS' + +[LOVE6] +'SEÑUELO' + +[LOVE1] +'EL LIBERADOR' + +[RC1] +'DESTRUCCIÓN DE LOS DIABLOS' + +[RC2] +'LA MASACRE DE LA MAFIA' + +[RC3] +'CALAMIDAD EN EL CASINO' + +[RC4] +'EXTERMINIO DE RUMPOS' + +[RM2_E1] +¡No puedo creer que esos mamones amarillos me hayan vuelto a dejar con el culo al aire! + +[GREN_1] +Cuanto más tiempo mantengas pulsado ~h~~k~~PED_FIREWEAPON~~w~, más lejos lanzarás la granada. + +[GREN_2] +Cuanto más tiempo mantengas pulsado ~h~~k~~PED_FIREWEAPON~~w~, más lejos lanzarás la granada. + +[GREN_3] +Cuanto más tiempo mantengas pulsado ~h~~k~~PED_FIREWEAPON~~w~, más lejos lanzarás la granada. + +[LOVE4_G] +Mis pertenencias estarán esperándote en el hangar de aduanas, dentro de la avioneta. + +[KABOOM] +¡BUUUM! + +[SPLAT] +¡PLOF! + +[PANCAK] +¡APLASTADO! + +[SOAKED] +¡AHOGADO! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Chatterbox FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +Cuando abra el metro, podrás usarlo para ir a Staunton Island. + +[TUBE2] +Cuando abra Shoreside Vale, podrás salir por la terminal Shoreside al Aeropuerto Internacional Francis. + +[TUBE_2] +Para subirte a un vagón, pulsa el ~h~botón Entrar al vehículo~w~. + +[LEGAL] +~g~¡Elimina la amenaza criminal! + +[GA_2] +He cambiado el motor y la mano de pintura. ¡La poli no te reconocerá! + +[LM1_8A] +Si quieres ganar un dinerillo extra, siempre puedes ''coger prestado'' un taxi... + +[TAXIH1] +Para cerca de un peatón señalado para recogerlo y llevarlo a su destino antes de que se acabe el tiempo. + +[LM5_7] +~g~¡Luigi no estará contento si hay menos de cuatro chicas trabajando en el ~p~baile de la policía~g~! + +[KM2_3] +~g~Recuerda que los ~r~coches ~g~tienen que estar en perfecto estado para ser aceptados en el ~p~garaje~g~. + +[KM5_2] +~g~Uno de los jamaicanos se ha ido. + +[BETRA_A] +Lo siento, cariño. + +[BETRA_B] +Soy una chica ambiciosa, ¿pero tú? + +[BETRA_C] +Eres un pez pequeñito. + +[JAILB_Q] +¡Ándele! + +[JAILB_R] +¡Señor malparido! + +[JAILB_S] +No nos importará matarte. + +[JAILB_T] +Os vais a arrepentir. + +[JAILB_U] +Bien, bien, piérdete. + +[HELP15] +Cuando vayas a pie, mantén pulsado ~h~~k~~PED_LOOKBEHIND~~w~ para~h~ mirar atrás~w~. + +[FEC_LB3] +Mirar atrás + +[FEC_R3] +(botón R3) + +[FES_AFO] +Esta Memory Card (PS2) ya está formateada. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- CAMBIAR SELECCIÓN + +[FEDSAS4] +;=<> - CAMBIAR SELECCIÓN + +[SPRAY_4] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar el cañón de agua. + +[SPRAY_1] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar el cañón de agua. + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[AM1_10] +~g~Salvatore saldrá del club de Luigi sobre las 0~1~:~1~. + +[JAILB_V] +Hoy, Liberty City se ha visto conmocionada. + +[JAILB_A] +La policía y los servicios de emergencia lidian con las consecuencias + +[JAILB_B] +de un devastador ataque a un convoy policial ocurrido esta mañana. + +[JAILB_C] +No se ha informado de quiénes eran los prisioneros trasladados en el convoy + +[JAILB_D] +y ningún grupo ha reconocido la autoría. + +[JAILB_E] +El convoy abandonó las oficinas de la policía esta mañana + +[JAILB_F] +para hacer una transferencia de reos a la penitenciaría de Liberty. + +[JAILB_G] +El ataque se produjo en el puente Callahan, + +[JAILB_H] +dejando pocos testigos y al puente seriamente dañado. + +[JAILB_I] +Se cree que algunos de los convictos han perecido en la explosión + +[JAILB_J] +posterior al ataque inicial. + +[JAILB_W] +Pasadas las horas se hizo evidente que el ataque era obra de profesionales, + +[JAILB_K] +ya que la identificación de los prófugos se complicó + +[JAILB_L] +cuando piratas informáticos atacaron las bases de datos de la policía. + +[JAILB_O] +Con el retraso del proyecto del túnel Porter, + +[JAILB_P] +este desastre deja a Portland aislada del resto de la ciudad. + +[JAILB_M] +* + +[JAILB_N] +* + +[JAILB_X] +UNUSED + +[FEDS_SE] +Botón / - ELEGIR + +[FEDS_SB] +Botón / - ELEGIR Botón " - VOLVER + +[TM4_A] +Ah, eres tú. Toni no está. + +[TM4_A2] +Pero te ha dejado una de sus cartitas de amor. + +[DIAB2_A] +¡Empecé mi negocio de entretenimiento exótico sin nada más que lo que cabía en mis pantalones de cuero! + +[LM5_9] +CHICAS: + +[PERPIC] +Paquetes ocultos encontrados + +[CO_ONE] +Paquete oculto ~1~ de ~1~ + +[LOVE3_3] +~g~La avioneta ha tirado ~1~ de los 6 paquetes. + +[FARE11] +~g~Ve al ~w~edificio en construcción ~g~de Fort Staunton. + +[GA_21] +No puedes guardar más coches en este garaje. + +[CHEAT1] +Trucos activados + +[CHEAT2] +Arma trucada + +[CHEAT3] +Salud trucada + +[CHEAT4] +Armadura trucada + +[CHEAT5] +Busca y captura trucado + +[CHEAT6] +Dinero trucado + +[CHEAT7] +Clima trucado + +[AS1_H] +~r~¡No has llevado al escuadrón de la muerte hasta la trampa de la yakuza! + +[FEDS_BA] +Botón " - VOLVER + +[RAMP_A] +¡TODAS LAS MASACRES COMPLETADAS! + +[USJ_ALL] +¡TODAS LAS ACROBACIAS COMPLETADAS! + +[FARE23] +~g~Ve al ~w~taller de importación/exportación~g~ en el distrito de la presa Cochrane. + +[L_TRN_1] +Puedes coger el tren para recorrer Portland. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un tren. + +[L_TRN_2] +Puedes coger el tren para recorrer Portland. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un tren. + +[S_TRN_1] +Puedes coger el metro para recorrer Liberty City. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un tren. + +[S_TRN_2] +Puedes coger el metro para recorrer Liberty City. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un tren. + +[AS1_C] +Ella cuenta con tres escuadrones de la muerte que patrullan por Liberty con el único fin de darte caza. + +[AS1_G] +~r~¡Todos los yakuza están muertos! + +[JAN] +Ene + +[FEB] +Feb + +[MAR] +Mar + +[APR] +Abr + +[MAY] +May + +[JUN] +Jun + +[JUL] +Jul + +[AUG] +Ago + +[SEP] +Sept + +[OCT] +Oct + +[NOV] +Nov + +[DEC] +Dic + +[DEFDT] +--:---:---- --:--:-- + +[BUGGY] +COCHES RESTANTES: + +[BONUS] +~g~PRIMA DE ~1~ $ + +[HORN1] +Pulsa el ~h~botón L3~w~ para tocar el ~h~claxon. + +[HORN2] +Pulsa el ~h~botón L1~w~ para tocar el ~h~claxon. + +[HORN3] +Pulsa el ~h~botón R1~w~ para tocar el ~h~claxon. + +[LM3_1A] +Pulsa ~h~~k~~VEHICLE_HORN~~w~ para tocar el ~h~claxon~w~ y avisar a Misty. + +[LM3_1B] +Pulsa ~h~~k~~VEHICLE_HORN~~w~ para tocar el ~h~claxon~w~ y avisar a Misty. + +[LM3_1C] +Pulsa ~h~~k~~VEHICLE_HORN~~w~ para tocar el ~h~claxon~w~ y avisar a Misty. + +[RADIO_A] +Pulsa ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ para cambiar de ~h~emisora de radio. + +[RADIO_B] +Pulsa ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ para cambiar de ~h~emisora de radio. + +[RADIO_C] +Pulsa ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ para cambiar de ~h~emisora de radio. + +[RADIO_D] +Pulsa ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ para cambiar de ~h~emisora de radio. + +[FEC_EXV] +Entrar/salir de vehículo + +[TAXI_M] +'TAXISTA' + +[COP_M] +'JUSTICIERO' + +[FIRE_M] +'BOMBERO' + +[AMBUL_M] +'CONDUCTOR DE AMBULANCIA' + +[HJ_IS] +PREMIO POR ACROBACIA DEMENCIAL: ~1~ $ + +[HJ_PIS] +PREMIO POR ACROBACIA DEMENCIAL PERFECTA: ~1~ $ + +[HJ_DIS] +PREMIO POR ACROBACIA DEMENCIAL DOBLE: ~1~ $ + +[HJ_PDIS] +PREMIO POR ACROBACIA DEMENCIAL DOBLE PERFECTA: ~1~ $ + +[HJ_TIS] +PREMIO POR ACROBACIA DEMENCIAL TRIPLE: ~1~ $ + +[HJ_PTIS] +PREMIO POR ACROBACIA DEMENCIAL TRIPLE PERFECTA: ~1~ $ + +[HJ_QIS] +PREMIO POR ACROBACIA DEMENCIAL CUÁDRUPLE: ~1~ $ + +[HJ_PQIS] +PREMIO POR ACROBACIA DEMENCIAL CUÁDRUPLE PERFECTA: ~1~ $ + +[AM1_K] +Salvatore Leone saldrá del club de Luigi dentro de unas tres horas. (0~1~:~1~) + +[IMPEXPP] +Garaje de importación y exportación, puerto de Portland. Requerimos varios vehículos, revisa nuestro tablón de notas para saber más. + +[VANHSTP] +¿Quieres abrir algún Securicar? Llévalo a nuestro garaje en el puerto de Portland. + +[EMVHPUP] +Se compran vehículos de emergencia nuevos y usados a buen precio. Llévalos a la grúa que hay al noroeste del puerto de Portland. + +[STANDS] +PUESTOS DESTROZADOS: + +[STASH] +~g~¡Lleva el SPANK de vuelta al ~p~edificio en construcción~g~! + +[MCSTNS] +No hay una Memory Card (PS2) insertada en la ranura para MEMORY CARD 1. ¿Quieres empezar? (SÍ o NO) + +[LOVE3_5] +~g~La avioneta ha llegado a la zona. + +[LOVE3_6] +~r~¡La bofia ha llegado a los paquetes antes que tú! + +[SIREN_1] +Para activar la sirena de este vehículo, pulsa ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_2] +Para activar la sirena de este vehículo, pulsa ~h~~k~~VEHICLE_HORN~~w~. + +[FM3_8C] +Necesitaré 100.000 dólares para cubrir gastos, + +[MCLOAD] +Cargando datos. No extraigas la Memory Card (PS2) de la ranura de MEMORY CARD 1, ni reinicies o apagues la consola. + +[FES_GME] +Fallo al leer la Memory Card (PS2) de la ranura de MEMORY CARD 1. Compruébala e inténtalo de nuevo. + +[FESZ_QF] +¿Seguro que quieres formatear la Memory Card (PS2) de la ranura de MEMORY CARD 1? + +[FESZ_LS] +Carga completada. + +[RM3_5] +~g~Tienes ~1~ de 6 paquetes de pruebas. + +[LOVE3_2] +~g~¡Tienes todos los paquetes! Llévaselos a Donald Love. + +[LOVE4_4] +~g~¡Lleva el paquete a Donald Love! + +[FEB_SAV] +Cargar + +[FEP_SAV] +CARGAR PARTIDA + +[AS2_12A] +~g~Cuando destruyas el primer puesto, ¡tendrás 8 mintutos antes que el cártel avise a sus camellos! + +[AS3_1A] +~g~¡Ahora ve a la ~b~boya señalada~g~! + +[NOCONT] +Vuelve a conectar un mando analógico (DUALSHOCK#) o mando analógico (DUALSHOCK#2) en el puerto de mando 1 para continuar. + +{ Possibly unused } + +[BET_JB] +TRAICIONADO POR SU AMANTE CATALINA Y DADO POR MUERTO. TRAS SER CONDENADO Y SENTENCIADO, INICIA SU VIAJE A LA PRISIÓN DE LIBERTY CITY. PERO SÓLO TIENE UN PENSAMIENTO... ¡VENGANZA! + +[END_A] +Los residentes de Cedar Grove todavía están asumiendo + +[END_B] +las consecuencias emocionales provocadas + +[END_C] +por la guerra que estalló ayer en la zona. + +[END_D] +Un residente local, Clive Denver, dijo a la policía + +[END_E] +que vio a un hombre armado huyendo de la escena, acompañado de una mujer de pelo negro. + +[END_F] +¿Sabes? Vamos a pasar un buen rato, porque, como sabes... + +[END_G] +¡Te amo! Yo, yo, yo realmente te amo, porque eres superfuerte, + +[END_H] +y eso es todo lo que necesito. + +[END_I] +Bueno, ¿qué estaba diciendo? + +[END_J] +Lo he olvidado. Pero me entiendes, ¿verdad? + +[END_K] +Las explosiones sacudieron las casas más próximas mientra sus residentes buscaban refugio. + +[END_L] +Varios ciudadanos resultaron heridos entre el caos debido al tiroteo + +[END_M] +entre las fuerzas terrestres y un helicóptero que rodeaba a la presa. + +[END_N] +Sí, en estos jardines tuvimos una vista excelente. + +[END_O] +El momento en el que derribaron el helicóptero + +[END_P] +fue mejor que los fuegos artificiales del 4 de julio. + +[END_Q] +El número de víctimas asciende a las veinte + +[END_R] +mientras la policía sigue encontrando cuerpos. + +[END_S] +No se han negado oficialmente los rumores + +[END_T] +de que las víctimas eran miembros del cártel colombiano, + +[END_U] +y aún no hay pistas que aclaren las causas de la masacre. + +[END_V] +Me he roto una uña y me pelo está hecho un asco, ¿puedes creerlo? + +[PAPER1] +CRIMINAL HERIDO DE BALA POR SU NOVIA Y CÓMPLICE; EL TRIBUNAL ENCUENTRA CULPABLE AL ATRACADOR CON UN VEREDICTO UNÁNIME. + +[PAPER2] +¡DIEZ AÑOS POR AMOR! + +[END_W] +Me costó cincuenta dólares... + +[FEB_CPC] +Configuración de controles + +[FEC_PED] +Controles a pie + +[FEC_VEH] +Controles en vehículos + +[FEC_FPR] +Controles en primera persona + +[FEC_CMM] +Controles comunes + +[FEC_PWL] +Ir a la izquierda + +[FEC_PWR] +Ir a la derecha + +[FEC_PWF] +Andar hacia delante + +[FEC_PWT] +Andar hacia la cámara + +[FEC_PLB] +Mirar hacia atrás + +[FEC_PFR] +Disparar arma + +[FEC_CLE] +Arma de la izquierda + +[FEC_CRI] +Arma de la derecha + +[FEC_LKT] +Fijar objetivo + +[FEC_PJP] +Saltar a pie + +[FEC_PSP] +Esprintar a pie + +[FEC_PSH] +Disparar a pie + +[FEC_TLF] +Objetivo a la izq. + +[FEC_TRG] +Objetivo a la dcha. + +[FEC_CCM] +Centrar cámara tras jugador + +[FEC_SZI] +Acercar zoom de fusil + +[FEC_SZO] +Alejar zoom de fusil + +[FEC_LKL] +Mirar izq. en primera persona + +[FEC_LRT] +Mirar dcha. en primera persona + +[FEC_LUP] +Mirar arr. en primera persona + +[FEC_LDN] +Mirar abj. en primera persona + +[FEC_LBH] +Mirar hacia atrás en vehículo + +[FEC_LLF] +Mirar a la izq. en vehículo + +[FEC_LRG] +Mirar a la dch. en vehículo + +[FEC_HRN] +Claxon + +[FEC_HBR] +Freno de mano del vehículo + +[FEC_ACL] +Acelerar vehículo + +[FEC_BRK] +Frenar vehículo + +[FEC_TSM] +Misiones secundarias + +[FEC_CRD] +Cambiar emisora de radio + +[FEC_ENT] +Entrar /salir de vehículo + +[FEC_WPN] +Disparar arma + +[FEC_PAS] +Pausa + +[FEC_FPO] +Armas en primera persona + +[FEC_SMS] +Mostrar puntero del ratón + +[FEC_CMS] +Cambiar cámara en todas las situaciones + +[FEC_TSS] +Capturar pantalla + +[FEN_STA] +INICIAR PARTIDA +[FEN_NET] +Red + +[FEN_CON] +Conexión + +[FEN_GAM] +Buscar partida + +[FEN_TYP] +Tipo de partida + +[FEN_TY0] +Duelo a muerte + +[FEN_TY1] +Duelo a muerte sigiloso + +[FEN_TY2] +Duelo a muerte en equipo + +[FEN_TY3] +Duelo a muerte sigiloso en equipo + +[FEN_TY4] +Busca la pasta + +[FEN_TY5] +Captura la bandera + +[FEN_TY6] +Carrera de locos + +[FEN_TY7] +Dominación + +[FEN_NAM] +Nombre: + +[FEN_GNA] +Nombre de partida: + +[FEM_MAP] +Elegir mapa + +[FEN_PLS] +Ajustes del jugador + +[FEN_PLC] +Color del jugador + +[FEM_MA0] +Liberty City + +[FEM_MA1] +Red Light District + +[FEM_MA2] +Chinatown + +[FEM_MA3] +La Torre + +[FEM_MA4] +Alcantarillas + +[FEM_MA5] +Zona industrial + +[FEM_MA6] +Muelles + +[FEM_MA7] +Staunton + +[FEC_EMS] +Sólo se permiten teclas del teclado. + +[FEC_DBG] +MENÚ DE DEPURACIÓN + +[FEC_TGD] +Cambiar mando de juego/depuración + +[FEC_TDO] +Desactivar cámara de depuración + +[FEC_IVH] +Invertir horizontalidad ratón + +[FEC_MSL] +BIR + +[FEC_MSM] +BCR + +[FEC_MSR] +BDR + +[FEC_QUE] +¿? + +[FEC_TWO] +Sólo se permiten dos teclas del teclado. + +[FEC_UMS] +Sólo se permiten botones del ratón. + +[FEC_OMS] +Sólo se permite un botón del ratón. + +[FEC_UJS] +Sólo se permiten botones del joystick. + +[FEC_OJS] +Sólo se permite un botón del joystick por acción. + +[FEC_PTL] +Usar Fijar objetivo con Siguiente arma. + +[FEC_PTR] +Usar Fijar objetivo con Arma anterior. + +[FEC_LBC] +Usar Mirar a izq. con Mirar a la dcha. + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +No se puede parar una partida multijugador. ¡Pulsa dos veces para salir! + +[FEM_SL1] +Espacio 1 libre + +[FEM_SL2] +Espacio 2 libre + +[FEM_SL3] +Espacio 3 libre + +[FEM_SL4] +Espacio 4 libre + +[FEM_SL5] +Espacio 5 libre + +[FEM_SL6] +Espacio 6 libre + +[FEM_SL7] +Espacio 7 libre + +[FEM_SL8] +Espacio 8 libre + +[FEM_MM] +MENÚ PRINCIPAL + +[FEM_SNG] +NUEVA PARTIDA + +[FEM_QTW] +Salir + +[FEQ_SRE] +¿Seguro que quieres salir del juego? Se perderán todos los progresos desde la última partida guardada. + +[FEQ_SRW] +¿Seguro que quieres salir del juego? + +[FEG_SRV] +SERVIDOR + +[FEG_MAP] +MAPA + +[FEG_PLY] +JUGADORES + +[FEG_TYP] +TIPO + +[FEG_PNG] +PING + +[FET_FG] +ENCONTRAR PARTIDA + +[FET_SP] +UN JUGADOR + +[FET_MP] +MULTIJUGADOR + +[FET_HG] +CREAR PARTIDA + +[FET_PS] +AJUSTES DE JUGADOR + +[FET_CON] +CONEXIÓN + +[FET_AUD] +AJUSTES DE AUDIO + +[FET_DIS] +AJUSTES DE PANTALLA + +[FET_LAN] +AJUSTES DE IDIOMA + +[FET_LG] +CARGAR PARTIDA + +[FET_DG] +BORRAR PARTIDA + +[FET_NG] +NUEVA PARTIDA + +[FET_SG] +GUARDAR PARTIDA + +[FET_MAP] +ELEGIR MAPA + +[FET_GT] +TIPO DE JUEGO + +[FET_CTL] +AJUSTES DE CONTROL + +[FET_OPT] +OPCIONES + +[FET_QG] +SALIR DEL JUEGO + +[FET_STA] +ESTADÍSTICAS + +[FET_BRE] +RESUMEN + +[FEC_WAR] +Aviso + +[FEC_OKK] +ACEPTAR + +[FED_CON] +Confirmación de borrado + +[FES_SSC] +Partida guardada. + +[DEL_FNM] +Partida eliminada. + +[PCLOAD] +Cargando datos de la partida + +[PCRESRT] +Reiniciando Grand Theft Auto III + +[FEC_DLF] +Fallo al borrar. + +[FEC_SVU] +Fallo al guardar. + +[FEC_LUN] +Fallo al cargar. Archivo dañado, por favor, bórralo. + +[FEN_PLA] +Número de jugadores: + +[FET_NON] +NO HAY PARTIDAS DISPONIBLES + +[FET_SFG] +BUSCANDO PARTIDAS... + +[FET_SRT] +ORDENANDO PARTIDAS... + +[FEF_LAN] +RED LOCAL + +[FEF_INT] +INTERNET + +[FET_REF] +Actualizar + +[FET_FIL] +Filtrar + +[FET_JG] +Unirse + +[FEC_NTW] +Conectar con red + +[FEC_ESR] +Tecla Esc limitada + +[FEC_GSL] +Balanceo de cabeza: + +[FIL_FLT] +FILTRAR LISTAS DE PARTIDAS + +[FET_SAN] +INICIAR NUEVA PARTIDA + +[FIL_MAP] +Mapa: + +[FIL_SRV] +Servidor: + +[FIL_TYP] +Tipo de juego: + +[FIL_SPC] +¿Partidas que no estén llenas? + +[FIL_PNG] +Latencia: + +[FEN_UKH] +Anfitrión desconocido + +[FEN_UKM] +Mapa no encontrado + +[FEN_UKT] +Tipo de juego no encontrado + +[FEN_NCI] +NO CONECTADO A INTERNET + +[FET_PAU] +MENÚ DE PAUSA + +[FET_SGA] +INICIAR PARTIDA + +[FEC_SGJ] +Establecer joystick de juego + +[FEC_PAD] +Mando + +[FEC_JOY] +Joystick + +[FEC_WHL] +Volante + +[FEC_CNT] +Tipo de mando: + +[FET_APL] +APLICAR + +[FES_CSA] +Selecciona una apariencia: + +[FES_SKN] +NOMBRE DE APARIENCIA + +[FES_DAT] +FECHA + +[FES_NON] +NO HAY APARIENCIAS DISPONIBLES + +[FEA_FM9] +REPRODUCTOR MP3 + +[FESZ_QZ] +¿Seguro que deseas guardar esta partida? + +[FES_CGA] +Espacios de partida disponibles: + +[FES_SCG] +¿Guardar la partida actual? + +[FES_LCG] +¿Cargar la partida y seguir jugando? + +[FEC_FIR] +Disparar + +[FEC_NWE] +Siguiente arma + +[FEC_PWE] +Arma anterior + +[FEC_FOR] +Avanzar + +[FEC_BAC] +Retroceder + +[FEC_LEF] +Izquierda + +[FEC_RIG] +Derecha + +[FEC_ZIN] +Acercar zoom + +[FEC_ZOT] +Alejar zoom + +[FEC_EEX] +Entrar /salir + +[FEC_RAD] +Radio + +[FEC_SUB] +Misión secundaria + +[FEC_CMR] +Cambiar cámara + +[FEC_JMP] +Saltar + +[FEC_SPN] +Esprintar + +[FEC_HND] +Freno de mano + +[FEC_TUL] +Torreta a izq. + +[FEC_TUR] +Torreta a dcha. + +[FEC_LOL] +Mirar a izq. + +[FEC_LOR] +Mirar a dcha. + +[FEC_NTR] +Siguiente objetivo + +[FEC_PTT] +Objetivo anterior + +[FEC_LBA] +Mirar atrás + +[FEC_CEN] +Centrar cámara + +[FEC_UND] +(NO) + +[FET_CFT] +A PIE + +[FET_CCR] +EN VEHÍCULO + +[CVT_MSG] +Convirtiendo texturas a un formato óptimo para tu tarjeta de vídeo + +[FET_CAC] +ACCIÓN + +[FEC_IBT] +- + +[FEC_SPC] +ESPACIO + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +SIN ASIGNAR + +[FET_CME] +MÉTODO DE CONTROL + +[FET_RDK] +REDEFINIR CONTROLES + +[FET_AMS] +AJUSTES DEL RATÓN + +[FET_STI] +CONFIGURACIÓN DE CONTROL ESTÁNDAR + +[FET_CTI] +CONFIGURACIÓN DE CONTROL CLÁSICA + +[FET_MTI] +CONFIGURACIÓN DE CONTROL CON RATÓN + +[FET_DAM] +MODELADO ACÚSTICO DINÁMICO + +[FEC_TFL] +Torreta a izq. + +[FEC_TFR] +Torreta a dcha. + +[FEC_TFU] +Torreta /Dodo arriba + +[FEC_TFD] +Torreta /Dodo abajo + +[FEC_MWF] +RUEDA ARR. + +[FEC_MWB] +RUEDA AB. + +[FEC_ORR] +o + +[FEC_NUS] +SIN USO + +[FEC_LUD] +Mirar arriba + +[FEC_LDU] +Mirar abajo + +[FEC_CMP] +COMBO: MIRAR I+D + +[FEC_NTT] +No hay texto para esta tecla + +[FEC_FNC] +F~1~ + +[FEC_IRT] +INSERT + +[FEC_DLL] +SUPR + +[FEC_HME] +INICIO + +[FEC_END] +FIN + +[FEC_PGU] +RE PÁG + +[FEC_PGD] +AV PÁG + +[FEC_UPA] +ARRIBA + +[FEC_DWA] +ABAJO + +[FEC_LFA] +IZDA + +[FEC_RFA] +DCHA + +[FEC_NUM] +NUM. + +[FEC_NMN] +NUM. ~1~ + +[FEC_FWS] +NUM. / + +[FEC_PLS] +NUM. + + +[FEC_MIN] +NUM. - + +[FEC_DOT] +NUM. . + +[FEC_NLK] +BLOQ NUM + +[FEC_ETR] +INTRO + +[FEC_SLK] +BLOQ DESPL + +[FEC_PSB] +PAUSA/INTER + +[FEC_BSP] +RETROCESO + +[FEC_TAB] +TAB + +[FEC_CLK] +BLOQ MAYÚS + +[FEC_RTN] +INTRO + +[FEC_LSF] +MAYÚS IZQ + +[FEC_RSF] +MAYÚS DCHA + +[FEC_LCT] +CTRL IZQ + +[FEC_RCT] +CTRL DCHO + +[FEC_LAL] +ALT IZQ + +[FEC_RAL] +ALT DCHO + +[FEC_LWD] +WIN IZQ + +[FEC_RWD] +WIN DCHO + +[FEC_WRC] +APMENÚ + +[WIN_TTL] +Grand Theft Auto III + +[WIN_95] +Grand Theft Auto III no se puede ejecutar bajo Windows 95 + +[WIN_DX] +Grand Theft Auto III necesita al menos la versión 8.1 de DirectX + +[WIN_VDM] +Grand Theft Auto III necesita al menos 12 MB de memoria de vídeo + +[DIAB3_G] +¡Arriba! + +[FEM_RES] +REANUDAR PARTIDA + +[FES_SNG] +INICIAR NUEVA PARTIDA + +[FEM_SP] +UN JUGADOR + +[FEM_MP] +MULTIJUGADOR + +[FEM_QT] +SALIR + +[FES_SG] +INICIAR NUEVA PARTIDA + +[FES_LG] +CARGAR PARTIDA + +[FEM_HST] +CREAR PARTIDA + +[FEM_OPT] +OPCIONES + +[FEM_DBG] +DEPURACIÓN + +[FET_PSU] +AJUSTES DE JUGADOR + +[FET_DEF] +RESTAURAR PREDETERMINADOS + +[FED_BRI] +BRILLO + +[FED_TRA] +ESTELAS + +[FEM_LOD] +DISTANCIA DE DIBUJADO + +[FEM_VSC] +SINCRONIZAR IMAGEN + +[FEM_FRM] +LIMITADOR DE FOTOGRAMAS + +[FED_RES] +RESOLUCIÓN + +[FED_WIS] +PANTALLA PANORÁMICA + +[FEDS_TB] +ATRÁS + +[FEA_MUS] +VOLUMEN DE MÚSICA + +[FEA_SFX] +VOLUMEN DE EFECTOS + +[FEA_RSS] +EMISORA DE RADIO + +[FEL_ENG] +INGLÉS + +[FEL_FRE] +FRANCÉS + +[FEL_GER] +ALEMÁN + +[FEL_ITA] +ITALIANO + +[FEL_SPA] +ESPAÑOL + +[FEA_3DH] +HARDWARE DE SONIDO + +[FEA_SPK] +CONFIGURACIÓN DE ALTAVOCES + +[FEA_2SP] +2 ALTAVOCES + +[FEA_4SP] +MÁS DE 2 ALTAVOCES + +[FEA_EAR] +AURICULARES + +[FEA_NAH] +NO HAY HARDWARE DE SONIDO + +[FET_SNG] +INICIAR NUEVA PARTIDA + +[GMSAVE] +GUARDAR PARTIDA + +[FES_DGA] +BORRAR PARTIDA + +{ STRING MISSING FROM THE OFFICIAL SPANISH TRANSLATION. } + +[FEM_NON] +NADA + +[FEC_IVV] +INVERTIR VERTICALIDAD RATÓN + +[FEC_MSH] +SENSIBILIDAD DEL RATÓN + +[FET_CCN] +CONTROL: CLÁSICO + +[FET_SCN] +CONTROL: ESTÁNDAR + +[FES_SET] +USAR APARIENCIA + +[GHOST] +Ghost + +[WIN_RSZ] +Error al seleccionar la nueva resolución de pantalla + +[FET_APP] +BIR, INTRO: APLICAR AJUSTES + +[FET_HRD] +AJUSTES PREDETERMINADOS RESTAURADOS + +[FET_MST] +CONDUCCIÓN CONTROLADA POR EL RATÓN + +[FEC_STR] +NUM. INICIO + +[FET_MIG] +IZQUIERDA, DERECHA, RUEDA DEL RATÓN: AJUSTAR + +[FET_CIG] +RETROCESO: QUITAR - BIR, INTRO: CAMBIAR + +[FET_RIG] +SELECCIONA UN NUEVO CONTROL O PULSA ESC PARA CANCELAR + +[FET_EIG] +NO SE PUEDE DEFINIR UN CONTROL PARA ESTA ACCIÓN + +[NO_PCCD] +Por favor, introduce el disco 2 de Grand Theft Auto III en la unidad o pulsa ESC para cancelar + +[CVT_ERR] +Te has quedado sin espacio en el disco duro. Por favor, libera espacio en tu disco duro antes de continuar. Pulsa ESC para cancelar. + +[FED_SUB] +SUBTÍTULOS + +[FET_DSN] +Apariencia predeterminada del jugador.bmp + +[JM3] +'EL ROBO DEL FURGÓN' + +[EBAL] +'DAME LIBERTAD' + +[LM4] +'UN CHULO Y SU ESCOPETA' + +[REPLAY] +REPETICIÓN + +[FEC_SFT] +MAYÚS + +[CRED254] +JEFE DEL ESTUDIO + +[CVT_CRT] +No se pueden convertir las texturas para tu tarjeta de vídeo. Necesitas privilegios de administrador. Pulsa ESC para salir. + +[FEM_ON] +SÍ + +[FEM_OFF] +NO + +[FEM_YES] +SÍ + +[FEM_NO] +NO + +[FES_WAR] +Guardando, espera... + +[FED_DLW] +Borrando, espera... + +[FED_LDW] +Cargando, espera... + +[FEC_SLC] +Ranura dañada + +[FED_LFL] +Error al cargar la partida. El juego se reiniciará. + +[FET_RSO] +SE HAN REINICIADO LOS AJUSTES + +[FET_RSC] +HARDWARE NO DISPONIBLE. RESTAURADO AJUSTE ORIGINAL + +[CRED270] +MIKE HONG + +{ re3 updates } +{ new languages } +[FEL_JAP] +JAPONÉS + +[FEL_POL] +POLACO + +[FEL_RUS] +RUSO + +{ new display menus } +[FET_GFX] +AJUSTES GRÁFICOS + +[FED_MIP] +MIPMAPPING + +[FED_AAS] +SUAVIZADO DE BORDES + +[FED_FIL] +FILTRO DE TEXTURAS + +[FED_BIL] +BILINEAL + +[FED_TRL] +TRILINEAL + +[FED_WND] +VENTANA + +[FED_FLS] +PANTALLA COMPLETA + +[FEM_CSB] +BORDES EN CINEMÁTICAS + +[FEM_SCF] +FORMATO DE IMAGEN + +[FEM_ISL] +USO DE MEMORIA DEL MAPA + +[FEM_LOW] +BAJO + +[FEM_MED] +MEDIO + +[FEM_HIG] +ALTO + +[FEM_2PR] +ALPHA TEST TIPO PS2 + +[FEC_FRC] +CÁMARA LIBRE + +{ Linux joy detection } +[FEC_JOD] +DETECTAR JOYSTICK + +[FEC_JPR] +Pulsa cualquier botón del joystick que quieras usar con el juego para seleccionarlo. + +[FEC_JDE] +Joystick detectado + +{ mission restart } +[FET_RMS] +REPETIR MISIÓN + +[FESZ_RM] +¿REINTENTAR? + +[FED_VPL] +RENDERIZADO DE VEHÍCULOS + +[FED_PRM] +ILUMINAR CONTORNOS DE PEATONES + +[FED_RGL] +BRILLO DE CARRETERAS + +[FED_CLF] +FILTRO DE COLOR + +[FED_WLM] +MAPAS DE LUZ DEL MUNDO + +[FED_MBL] +DESENFOQUE DE MOVIMIENTO + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MÓVIL + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] +AUTOM. + +[FEC_IVP] +INVERTIR VERTICALIDAD MANDO + +[FEM_TWP] +Poner o quitar punto de referencia + +[FEA_FMN] +RADIO APAGADA + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +MANDO DE XBOX 360 + +[FEC_ONE] +MANDO DE XBOX ONE + +[FEC_NSW] +MANDO DE NINTENDO SWITCH + +[FEC_TYP] +TIPO DE MANDO + +[FEC_CCF] +CONFIGURACIÓN + +[FEC_CF1] +AJUSTE 1 + +[FEC_CF2] +AJUSTE 2 + +[FEC_CF3] +AJUSTE 3 + +[FEC_CF4] +AJUSTE 4 + +[FEC_CDP] +CONTROLES A MOSTRAR + +[FEC_ONF] +A PIE + +[FEC_INC] +EN VEHÍCULO + +[FEC_VIB] +VIBRACIÓN + +[FET_AGS] +AJUSTES DE MANDO + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +{ end of file } + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/vendor/librw/.appveyor.yml b/vendor/librw/.appveyor.yml new file mode 100644 index 0000000..f74fc38 --- /dev/null +++ b/vendor/librw/.appveyor.yml @@ -0,0 +1,32 @@ +image: Visual Studio 2017 +configuration: Release +environment: + GLFW_BASE: glfw-3.3.4.bin.WIN64 + GLFW_URL: https://github.com/glfw/glfw/releases/download/3.3.4/%GLFW_BASE%.zip + SDL2_BASE: SDL2-devel-2.0.14-VC + SDL2_URL: https://www.libsdl.org/release/%SDL2_BASE%.zip + SDL2_DIRAME: SDL2-2.0.14 + PREMAKE5_URL: https://github.com/premake/premake-core/releases/download/v5.0.0-alpha16/premake-5.0.0-alpha16-windows.zip + matrix: + - PLATFORM: win-amd64-null + - PLATFORM: win-amd64-gl3 + PREMAKE5_EXTRA_ARGS: --gfxlib=glfw + - PLATFORM: win-amd64-gl3 + PREMAKE5_EXTRA_ARGS: --gfxlib=sdl2 + - PLATFORM: win-amd64-d3d9 + +install: + - appveyor DownloadFile %GLFW_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE%.zip" + - 7z x "%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE%.zip" + - appveyor DownloadFile %SDL2_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/%SDL2_BASE%.zip" + - 7z x "%APPVEYOR_BUILD_FOLDER%/%SDL2_BASE%.zip" + - appveyor DownloadFile %PREMAKE5_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/premake5.zip" + - mkdir "%APPVEYOR_BUILD_FOLDER%/bin" && cd "%APPVEYOR_BUILD_FOLDER%/bin" && 7z x "%APPVEYOR_BUILD_FOLDER%/premake5.zip" + - set PATH=%APPVEYOR_BUILD_FOLDER%/bin;%PATH% +before_build: + - mkdir "%APPVEYOR_BUILD_FOLDER%/build" + - cd "%APPVEYOR_BUILD_FOLDER%" + - premake5 vs2017 --glfwdir64=%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE% --sdl2dir=%APPVEYOR_BUILD_FOLDER%/%SDL2_DIRAME% %PREMAKE5_EXTRA_ARGS% +build: + project: c:\projects\librw\build\librw.sln + verbosity: minimal diff --git a/vendor/librw/.github/workflows/build-cmake-conan.yml b/vendor/librw/.github/workflows/build-cmake-conan.yml new file mode 100644 index 0000000..43f85a9 --- /dev/null +++ b/vendor/librw/.github/workflows/build-cmake-conan.yml @@ -0,0 +1,60 @@ +name: Conan +on: + pull_request: + push: + release: + types: published +jobs: + build-cmake: + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + platform: ['null', 'gl3', 'd3d9'] + gl3_gfxlib: ['glfw', 'sdl2'] + exclude: + - os: ubuntu-latest + platform: d3d9 + - os: macos-latest + platform: d3d9 + - platform: 'null' + gl3_gfxlib: sdl2 + - platform: d3d9 + gl3_gfxlib: sdl2 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: "Setup conan" + run: | + python -m pip install conan + conan config init + conan config set log.print_run_commands=True + conan config set general.revisions_enabled=1 + conan remote add bincrafters https://bincrafters.jfrog.io/artifactory/api/conan/public-conan + - name: "Create host profile" + shell: bash + run: | + cp ~/.conan/profiles/default host_profile + - name: "Download/build dependencies (conan install)" + run: | + conan install ${{ github.workspace }} librw/master@ -if build -o librw:platform=${{ matrix.platform }} -o librw:gl3_gfxlib=${{ matrix.gl3_gfxlib }} --build missing -pr:h ./host_profile -pr:b default + env: + CONAN_SYSREQUIRES_MODE: enabled + - name: "Build librw (conan build)" + run: | + conan build ${{ github.workspace }} -if build -bf build -pf package + - name: "Package librw (conan package)" + run: | + conan package ${{ github.workspace }} -if build -bf build -pf package + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "${{ matrix.os }}-${{ matrix.platform }}" + path: build/*.tar.xz + if-no-files-found: error diff --git a/vendor/librw/.github/workflows/build-ps2.yml b/vendor/librw/.github/workflows/build-ps2.yml new file mode 100644 index 0000000..bb0b0ec --- /dev/null +++ b/vendor/librw/.github/workflows/build-ps2.yml @@ -0,0 +1,29 @@ +name: Playstation 2 (by ps2dev) +on: + pull_request: + push: + release: + types: published +jobs: + build-playstation-2: + runs-on: ubuntu-latest + container: ps2dev/ps2dev + steps: + - uses: actions/checkout@v2 + - name: "Install dependencies" + run: | + apk add build-base cmake + - name: "Build files" + run: | + cmake -S. -Bbuild -DLIBRW_INSTALL=ON -DLIBRW_PLATFORM=PS2 -DCMAKE_TOOLCHAIN_FILE=cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake + cmake --build build --parallel + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "ps2" + path: build/*.tar.xz + if-no-files-found: error diff --git a/vendor/librw/.github/workflows/build-switch.yml b/vendor/librw/.github/workflows/build-switch.yml new file mode 100644 index 0000000..cfda93c --- /dev/null +++ b/vendor/librw/.github/workflows/build-switch.yml @@ -0,0 +1,26 @@ +name: Nintendo Switch (by devkitPro) +on: + pull_request: + push: + release: + types: published +jobs: + build-nintendo-switch: + runs-on: ubuntu-latest + container: devkitpro/devkita64:latest + steps: + - uses: actions/checkout@v2 + - name: "Build files" + run: | + /opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake -S. -Bbuild -DLIBRW_PLATFORM=GL3 -DLIBRW_GL3_GFXLIB=GLFW -DLIBRW_INSTALL=True + cmake --build build --parallel + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "switch-gl3" + path: build/*.tar.xz + if-no-files-found: error diff --git a/vendor/librw/.gitignore b/vendor/librw/.gitignore new file mode 100644 index 0000000..de870ed --- /dev/null +++ b/vendor/librw/.gitignore @@ -0,0 +1,10 @@ +/bin +/build +/obj +/lib +/output +/.vs +librw.vcxproj.user +librw.VC.db +librw.VC.VC.opendb +imgui.ini diff --git a/vendor/librw/.travis.yml b/vendor/librw/.travis.yml new file mode 100644 index 0000000..91411e6 --- /dev/null +++ b/vendor/librw/.travis.yml @@ -0,0 +1,46 @@ +language: cpp + +matrix: + include: + - os: linux + env: TARGET=release_linux-amd64-null + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 gmake && cd /librw/build && make config=$TARGET verbose=1" + - os: linux + env: TARGET=release_linux-amd64-gl3 GFXLIB=glfw + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 --gfxlib=$GFXLIB gmake && cd /librw/build && make config=$TARGET verbose=1" + - os: linux + env: TARGET=release_linux-amd64-gl3 GFXLIB=sdl2 + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 --gfxlib=$GFXLIB gmake && cd /librw/build && make config=$TARGET verbose=1" + - name: "ps2" + os: linux + env: TARGET=release_ps2 + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 gmake && cd /librw/build && make config=$TARGET verbose=1" + allow_failures: + - name: "ps2" diff --git a/vendor/librw/ARCHITECTURE.MD b/vendor/librw/ARCHITECTURE.MD new file mode 100644 index 0000000..dad04fa --- /dev/null +++ b/vendor/librw/ARCHITECTURE.MD @@ -0,0 +1,174 @@ +This document gives an overview of the architecture of librw. + +Disclaimer: Some of these design decision were taken over from original RW, +some are my own. I only take partial responsibility. + +Differently from original RW, librw has no neat separation into modules. +Some things could be made optional, but in particular RW's RpWorld +plugin is integrated with everything else. + +# Plugins + +To extend structs with custom data, +RW (and librw) provides a plugin mechanism +for certain structs. +This can be used to tack more data onto a struct +and register custom streaming functions. +Plugins must be registered before any instance +of that struct is allocated. + +# Pipelines + +RW's pipeline architecture was designed for very flexible data flow. +Unfortunately for RW most of the rendering pipeline moved into the GPU +causing RW's pipeline architecture to be severely overengineered. + +librw's pipeline architecture is therefore much simplified +and only implements what is actually needed, +but the name *pipeline* is retained. + +Three pipelines are implemented in librw itself: +Default, Skin, MatFX (only env map so far). +Others can be implemented by applications using librw. + +# RW Objects + +## Frame + +A Frame is an orientation in space, arranged in a hierarchy. +Camera, Lights and Atomics can be attached to it. +It has two matrices: a (so called) model matrix, +which is relative to its parent, +and a local transformation matrix (LTM) which is relative to the world. +The LTM is updated automatically as needed whenever the hierarchy gets dirty. + +## Camera + +A Camera is attached to a Frame to position it in space +and has a framebuffer and a z-buffer attached to render to. +Rendering is started by `beginUpdate` and ended by `endUpdate`. +This sets up things like framebuffers and matrices +so that the Camera's raster can be rendered to. + +## Light + +Lights are attached to a Frame to position it in space. +They are used to light Atomics for rendering. +Different types of light are possible. + +## Geometry + +A Geometry contains the raw geometry data that can be rendered. +It has a list of materials that are applied to its triangles. +The latter are sorted by materials into meshes for easier instancing. + +## Atomic + +An Atomic is attached to a Frame to position it in space +and references a Geometry. +Atomics are the objects that are rendered by pipelines. + +## Clump + +A Clump is a container of Atomics, Lights and Cameras. +Clumps can be read from and written to DFF files. +Rendering a Clump will be render all of its Atomics. + +# Engine + +Due to the versatility of librw, +there are three levels of code: +Platform indpendent code, +platform specific code, +and render device specific code. + +The second category does not exist in original RW, +but because librw is supposed to be able to +convert between all sorts of platform specific files, +the code for that has to be available no matter +the render platform used. +The common interface for all device-independent +platform code is the `Driver` struct. +The `Engine` struct has an array with one for each platform. + +The render device specific code +is used for actually rendering something to the screen. +The common interface for the device-dependent +code is the `Device` struct and the `Engine` +struct only has a single one, as there can only be one render device +(i.e. you cannot select D3D or OpenGL at runtime). + +Thus when implementing a new backend +you have to implement the `Driver` and `Device` interfaces. +But do note that the `Driver` can be extended with plugins! + +# Driver + +The driver is mostly concerned with conversion +between platform independent data to platform dependent one, and vice versa. +This concerns the following two cases. + +## Raster, Images + +Images contain platform independent uncompressed pixel data. +Rasters contain platform dependent (and possibly compressed) pixel data. +A driver has to be able to convert an image to a raster for the purposes of loading textures +from files or having them converted from foreign rasters. +Converting from rasters to images is not absolutely necessary but it's needed e.g. for taking screenshots. +librw has a set of default raster formats that the platform is +expected to implement for the most part, however not all have to be supported necessarily. +A driver is also free to implement its own formats; +this is done for texture compression. + +Rasters have different types, +`TEXTURE` and `CAMERATEXTURE` rasters can be used as textures, +`CAMERA` and `CAMERATEXTURE` can be used as render targets, +`ZBUFFER` is used as a depth-buffer. + +## Pipelines + +A librw ObjPipeline implements essentially +an instance stage which converts platform independent geometry +to a format that can efficiently be rendered, +and a render stage that actually renders it. +(There is also an uninstance function, +but this is only used to convert platform specific geometry back to the generic format +and hence is not necessary.) + +# Device + +The device implements everything that is actually needed for rendering. + +This includes starting, handling and closing the rendering device, +setting render states, +allocating and destroying buffers and textures, +im2d and im3d rendering, +and the render functions of the pipelines. + +## System + +The `system` function implements certain device requests +from the engine (why these aren't separate functions I don't know, RW design). + +The `Engine` is started in different stages, at various points of which +the render device gets different requests. +At the end the device is initialized and ready for rendering. +A similar but reverse sequence happens on shutdown. + +Subsystems (screens) and video modes are queried through +the `system` by the application before the device is started. + +## Immediate mode + +Im2d and im3d are immediate-mode style rendering interface. + +## Pipelines + +For instancing the typical job is to allocate and fill +a struct to hold some data about an atomic, +an array of structs for all the meshes, +and vertex and index buffers to hold geometry for rendering. + +The render function will render the previously instanced +data by doing a per-object setup and then iterating over +and rendering all the meshes. diff --git a/vendor/librw/CMakeLists.txt b/vendor/librw/CMakeLists.txt new file mode 100644 index 0000000..1f4d3ce --- /dev/null +++ b/vendor/librw/CMakeLists.txt @@ -0,0 +1,160 @@ +set(librw_MAINPROJECT ON) +if(DEFINED PROJECT_NAME) + set(librw_MAINPROJECT OFF) +endif() + +cmake_minimum_required(VERSION 3.8) +project(librw + VERSION 0.0.1 + LANGUAGES C CXX +) +set(librw_AUTHOR aap) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +if(WIN32) + set(LIBRW_PLATFORMS "NULL" "GL3" "D3D9") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL ON) +elseif(NINTENDO_SWITCH) + set(LIBRW_PLATFORMS "NULL" "GL3") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL OFF) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/nx") + include(NXFunctions) +elseif(PS2) + set(LIBRW_PLATFORMS "PS2") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL OFF) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/ps2") + include(PS2Functions) +else() + set(LIBRW_PLATFORMS "NULL" "GL3") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL ON) +endif() +list(GET LIBRW_PLATFORMS 0 LIBRW_PLATFORM_DEFAULT) +set(LIBRW_PLATFORM "${LIBRW_PLATFORM_DEFAULT}" CACHE STRING "Platform") +set_property(CACHE LIBRW_PLATFORM PROPERTY STRINGS ${LIBRW_PLATFORMS}) +message(STATUS "LIBRW_PLATFORM = ${LIBRW_PLATFORM} (choices=${LIBRW_PLATFORMS})") +set("LIBRW_PLATFORM_${LIBRW_PLATFORM}" ON) +if(NOT LIBRW_PLATFORM IN_LIST LIBRW_PLATFORMS) + message(FATAL_ERROR "Illegal LIBRW_PLATFORM=${LIBRW_PLATFORM}") +endif() + +set(LIBRW_GL3_GFXLIBS "GLFW" "SDL2") +set(LIBRW_GL3_GFXLIB "GLFW" CACHE STRING "gfxlib for gl3") +set_property(CACHE LIBRW_GL3_GFXLIB PROPERTY STRINGS ${LIBRW_GL3_GFXLIBS}) +if(LIBRW_PLATFORM_GL3) + message(STATUS "LIBRW_GL3_GFXLIB = ${LIBRW_GL3_GFXLIB} (choices=${LIBRW_GL3_GFXLIBS})") +endif() +if(NOT LIBRW_GL3_GFXLIB IN_LIST LIBRW_GL3_GFXLIBS) + message(FATAL_ERROR "Illegal LIBRW_GL3_GFXLIB=${LIBRW_GL3_GFXLIB}") +endif() + +if(LIBRW_PLATFORM_PS2) + enable_language(DSM) +endif() + +if(NOT COMMAND librw_platform_target) + function(librw_platform_target) + endfunction() +endif() + +include(CMakeDependentOption) + +option(LIBRW_TOOLS "Build librw tools" ${librw_MAINPROJECT}) +option(LIBRW_INSTALL "Install librw files" ${librw_MAINPROJECT}) +cmake_dependent_option(LIBRW_EXAMPLES "Build librw examples" ON "LIBRW_TOOLS;NOT LIBRW_PLATFORM_NULL" OFF) + +if(LIBRW_INSTALL) + include(GNUInstallDirs) + set(LIBRW_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}/librw") +endif() + +add_subdirectory(src) + +if(LIBRW_TOOLS AND NOT LIBRW_PLATFORM_PS2 AND NOT LIBRW_PLATFORM_NULL) + add_subdirectory(skeleton) +endif() + +add_subdirectory(tools) + +if(LIBRW_INSTALL) + include(CMakePackageConfigHelpers) + configure_package_config_file(cmake/librw-config.cmake.in librw-config.cmake + INSTALL_DESTINATION "${CMAKE_INSTALL_PREFIX}" + ) + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/librw-config.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + ) + install( + EXPORT librw-targets NAMESPACE librw:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + ) + + if(LIBRW_GL3_GFXLIB STREQUAL "SDL2") + install( + FILES "${CMAKE_CURRENT_LIST_DIR}/cmake/FindSDL2.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake" + ) + endif() + + string(REPLACE "." ";" cmake_c_compiler_version_list "${CMAKE_C_COMPILER_VERSION}") + list(GET cmake_c_compiler_version_list 0 cmake_c_compiler_version_major) + + string(TOLOWER "${LIBRW_PLATFORM}" librw_platform) + set(compiler) + set(os) + if(NOT LIBRW_PLATFORM STREQUAL "PS2") + if(MSVC) + set(compiler "-msvc${MSVC_VERSION}") + elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(compiler "-gcc${cmake_c_compiler_version_major}") + elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang") + set(compiler "-clang${cmake_c_compiler_version_major}") + elseif(CMAKE_C_COMPILER_ID STREQUAL "AppleClang") + set(compiler "-appleclang${cmake_c_compiler_version_major}") + else() + set(compiler "-UNK") + message(WARNING "Unknown compiler. Created cpack package will be wrong. (override using cpack -P)") + endif() + endif() + if(LIBRW_PLATFORM_NULL) + set(platform "-null") + elseif(LIBRW_PLATFORM_PS2) + set(platform "-ps2") + elseif(LIBRW_PLATFORM_GL3) + if(LIBRW_GL3_GFXLIB STREQUAL "GLFW") + set(platform "-gl3-glfw") + else() + set(platform "-gl3-sdl2") + endif() + elseif(LIBRW_PLATFORM_D3D9) + set(platform "-d3d9") + endif() + if(NOT LIBRW_PLATFORM_PS2) + if(WIN32) + set(os "-win") + elseif(NINTENDO_SWITCH) + set(os "-switch") + elseif(PS2) + set(os "-ps2") + elseif(APPLE) + set(os "-apple") + elseif(UNIX) + set(os "-linux") + else() + set(compiler "-UNK") + message(WARNING "Unknown os. Created cpack package will be wrong. (override using cpack -P)") + endif() + endif() + + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}${platform}${os}${compiler}") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A (partial) re-implementation of RenderWare Graphics") + set(CPACK_PACKAGE_VENDOR "aap") + set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") + set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_GENERATOR "TXZ") + include(CPack) +endif() diff --git a/vendor/librw/Dockerfile b/vendor/librw/Dockerfile new file mode 100644 index 0000000..a664173 --- /dev/null +++ b/vendor/librw/Dockerfile @@ -0,0 +1,60 @@ +FROM ubuntu:rolling + +ENV PS2DEV /ps2dev +ENV PS2SDK $PS2DEV/ps2sdk +ENV PATH $PATH:$PS2DEV/bin:$PS2DEV/ee/bin:$PS2DEV/iop/bin:$PS2DEV/dvp/bin:$PS2SDK/bin + +ENV DEBIAN_FRONTEND noninteractive + +ENV TOOLCHAIN_GIT_URL git://github.com/ps2dev/ps2toolchain.git +ENV TOOLCHAIN_GIT_BRANCH master + +ENV PREMAKE5_URL=https://github.com/premake/premake-core/releases/download/v5.0.0-alpha12/premake-5.0.0-alpha12-linux.tar.gz + +RUN mkdir -p "$PS2DEV" "$PS2SDK" \ + && apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y \ + build-essential \ + cmake \ + autoconf \ + bzip2 \ + gcc \ + git \ + libucl-dev \ + make \ + patch \ + vim \ + wget \ + zip \ + zlib1g-dev \ + libglfw3-dev \ + libsdl2-dev \ + && git clone -b $TOOLCHAIN_GIT_BRANCH $TOOLCHAIN_GIT_URL /toolchain \ + && cd /toolchain \ + && ./toolchain.sh \ + && git clone git://github.com/ps2dev/ps2eth.git /ps2dev/ps2eth \ + && make -C /ps2dev/ps2eth \ + && git clone git://github.com/ps2dev/ps2-packer.git /ps2-packer \ + && make install -C /ps2-packer \ + && rm -rf \ + /ps2-packer \ + /ps2dev/ps2eth/.git \ + /ps2dev/ps2sdk/test.tmp \ + /ps2dev/test.tmp \ + /toolchain \ + && rm -rf /var/lib/apt/lists/* \ + && wget "$PREMAKE5_URL" -O /tmp/premake5.tar.gz \ + && tar xf /tmp/premake5.tar.gz -C /usr/bin/ \ + && rm /tmp/premake5.tar.gz \ + && groupadd 1000 -g 1000 \ + && groupadd 1001 -g 1001 \ + && groupadd 2000 -g 2000 \ + && groupadd 999 -g 999 \ + && useradd -ms /bin/bash builder -g 1001 -G 1000,2000,999 \ + && printf "builder:builder" | chpasswd \ + && adduser builder sudo \ + && printf "builder ALL= NOPASSWD: ALL\\n" >> /etc/sudoers + +USER builder +WORKDIR /home/builder diff --git a/vendor/librw/LICENSE b/vendor/librw/LICENSE new file mode 100644 index 0000000..ef53cc2 --- /dev/null +++ b/vendor/librw/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 aap + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/librw/README.cmake b/vendor/librw/README.cmake new file mode 100644 index 0000000..59493b1 --- /dev/null +++ b/vendor/librw/README.cmake @@ -0,0 +1,9 @@ +Build with cmake +================ + +Linux + + mkdir build + cd build + cmake .. -DLIBRW_PLATFORM=GL3 -DLIBRW_GL3_GFXLIB=SDL2 + make diff --git a/vendor/librw/README.md b/vendor/librw/README.md new file mode 100644 index 0000000..77defd0 --- /dev/null +++ b/vendor/librw/README.md @@ -0,0 +1,26 @@ +librw +===== + +This library is supposed to be a re-implementation of RenderWare graphics, +or a good part of it anyway. + +It is intended to be cross-platform in two senses: +support rendering on different platforms similar to RW; +supporting all file formats for all platforms at all times and provide +way to convert to all other platforms. + +Supported file formats are DFF and TXD for PS2, D3D8, D3D9 and Xbox. +Not all pre-instanced PS2 DFFs are supported. +BSP is not supported at all. + +For rendering we have D3D9 and OpenGL (>=2.1, ES >= 2.0) backends. +Rendering some things on the PS2 is working as a test only. + +# Uses + +librw can be used for rendering [GTA](https://github.com/gtamodding/re3). + +# Building + +Get premake5. Generate a config, e.g. with ``premake5 gmake``, +and look in the build directory. diff --git a/vendor/librw/TODO b/vendor/librw/TODO new file mode 100644 index 0000000..5c2c220 --- /dev/null +++ b/vendor/librw/TODO @@ -0,0 +1,24 @@ +TODO: +- tristrips +- examples + skeleton + interface + camtex + matfx1 (more new shaders) + hanim1 + fog + imlight? + patch? +- morphing +- Pipelines (PDS, Xbox, PC) +- bsp + +driver +- metrics + +ps2 +- rendering! +- ADC conversion + + +BUGS: + - fseek with negative offset on ps2 over ps2link messes up the current position diff --git a/vendor/librw/args.h b/vendor/librw/args.h new file mode 100644 index 0000000..0e666c1 --- /dev/null +++ b/vendor/librw/args.h @@ -0,0 +1,24 @@ +extern char *argv0; +#define USED(x) ((void)x) +#define SET(x) ((x)=0) + +#define ARGBEGIN for((argv0||(argv0=*argv)),argv++,argc--;\ + argv[0] && argv[0][0]=='-' && argv[0][1];\ + argc--, argv++) {\ + char *_args, *_argt;\ + char _argc;\ + _args = &argv[0][1];\ + if(_args[0]=='-' && _args[1]==0){\ + argc--; argv++; break;\ + }\ + _argc = 0;\ + while(*_args && (_argc = *_args++))\ + switch(_argc) +#define ARGEND SET(_argt);USED(_argt);USED(_argc);USED(_args);}USED(argv);USED(argc); +#define ARGF() (_argt=_args, _args=(char*)"",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): 0)) +#define EARGF(x) (_argt=_args, _args=(char*)"",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): ((x), abort(), (char*)0))) + +#define ARGC() _argc + diff --git a/vendor/librw/cmake/FindSDL2.cmake b/vendor/librw/cmake/FindSDL2.cmake new file mode 100644 index 0000000..24288b4 --- /dev/null +++ b/vendor/librw/cmake/FindSDL2.cmake @@ -0,0 +1,38 @@ +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(SDL2 IMPORTED_TARGET "sdl2") + if(TARGET PkgConfig::SDL2 AND NOT TARGET SDL2::SDL2) + add_library(SDL2::SDL2 INTERFACE IMPORTED) + set_property(TARGET SDL2::SDL2 PROPERTY INTERFACE_LINK_LIBRARIES PkgConfig::SDL2) + endif() +endif() + +find_library(SDL2main_LIBRARY SDL2main) + +if(NOT SDL2_FOUND) + find_path(SDL2_INCLUDE_DIR sdl2.h) + find_library(SDL2_LIBRARY SDL2 SDL2d) + + find_library(SDL2main_LIBRARY SDL2main) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(libuv + REQUIRED_VARS SDL2_INCLUDE_DIR SDL2_LIBRARY + ) + + if(NOT TARGET SDL2::SDL2) + add_library(SDL2::SDL2 UNKNOWN IMPORTED) + set_target_properties(SDL2::SDL2 PROPERTIES + IMPORTED_LOCATION "${SDL2_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}" + ) + endif() +endif() + +if(SDL2main_LIBRARY AND NOT TARGET SDL2::SDL2main) + add_library(SDL2::SDL2main UNKNOWN IMPORTED) + set_target_properties(SDL2::SDL2main PROPERTIES + IMPORTED_LOCATION "${SDL2main_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}" + ) +endif() diff --git a/vendor/librw/cmake/librw-config.cmake.in b/vendor/librw/cmake/librw-config.cmake.in new file mode 100644 index 0000000..47b3487 --- /dev/null +++ b/vendor/librw/cmake/librw-config.cmake.in @@ -0,0 +1,22 @@ +include("${CMAKE_CURRENT_LIST_DIR}/librw-targets.cmake") + +set(LIBRW_PLATFORM "@LIBRW_PLATFORM@") +set(LIBRW_PLATFORMS "@LIBRW_PLATFORMS@") +set(LIBRW_PLATFORM_@LIBRW_PLATFORM@ ON) + +if(LIBRW_PLATFORM_GL3) + set(LIBRW_GL3_GFXLIB "@LIBRW_GL3_GFXLIB@") + set(LIBRW_GL3_GFXLIBS "@LIBRW_GL3_GFXLIBS@") + + set(OpenGL_GL_PREFERENCE GLVND) + find_package(OpenGL) + if(NOT TARGET OpenGL::OpenGL AND NOT TARGET OpenGL::EGL AND NOT TARGET OpenGL::GL) + message(FATAL_ERROR "find_package(OpenGL) failed: no target was created") + endif() + + if(LIBRW_GL3_GFXLIB STREQUAL "GLFW") + find_package(glfw3 REQUIRED) + elseif(LIBRW_GL3_GFXLIB STREQUAL "SDL2") + find_package(SDL2 REQUIRED) + endif() +endif() diff --git a/vendor/librw/cmake/nx/NXFunctions.cmake b/vendor/librw/cmake/nx/NXFunctions.cmake new file mode 100644 index 0000000..bab3360 --- /dev/null +++ b/vendor/librw/cmake/nx/NXFunctions.cmake @@ -0,0 +1,37 @@ +if(NOT COMMAND nx_generate_nacp) + message(FATAL_ERROR "The `nx_generate_nacp` cmake command is not available. Please use an appropriate Nintendo Switch toolchain.") +endif() + +if(NOT COMMAND nx_create_nro) + message(FATAL_ERROR "The `nx_create_nro` cmake command is not available. Please use an appropriate Nintendo Switch toolchain.") +endif() + +set(CMAKE_EXECUTABLE_SUFFIX ".elf") + +function(librw_platform_target TARGET) + cmake_parse_arguments(LPT "INSTALL" "" "" ${ARGN}) + + get_target_property(TARGET_TYPE "${TARGET}" TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + nx_generate_nacp(${TARGET}.nacp + NAME "${TARGET}" + AUTHOR "${librw_AUTHOR}" + VERSION "${librw_VERSION}" + ) + + nx_create_nro(${TARGET} + NACP ${TARGET}.nacp + ) + + if(LIBRW_INSTALL AND LPT_INSTALL) + get_target_property(TARGET_OUTPUT_NAME ${TARGET} OUTPUT_NAME) + if(NOT TARGET_OUTPUT_NAME) + set(TARGET_OUTPUT_NAME "${TARGET}") + endif() + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_OUTPUT_NAME}.nro" + DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + endif() + endif() +endfunction() diff --git a/vendor/librw/cmake/ps2/PS2Functions.cmake b/vendor/librw/cmake/ps2/PS2Functions.cmake new file mode 100644 index 0000000..5cd81dd --- /dev/null +++ b/vendor/librw/cmake/ps2/PS2Functions.cmake @@ -0,0 +1,18 @@ +if(NOT COMMAND add_erl_executable) + message(FATAL_ERROR "The `add_erl_executable` cmake command is not available. Please use an appropriate Playstation 2 toolchain.") +endif() + +function(librw_platform_target TARGET) + cmake_parse_arguments(LPT "INSTALL" "" "" ${ARGN}) + + get_target_property(TARGET_TYPE "${TARGET}" TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + add_erl_executable(${TARGET} OUTPUT_VAR ERL_FILE) + + if(LIBRW_INSTALL AND LPT_INSTALL) + install(FILES "${ERL_FILE}" + DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + endif() + endif() +endfunction() diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in new file mode 100644 index 0000000..9a55d4c --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in @@ -0,0 +1,16 @@ +set(CMAKE_DSM_COMPILER "@_CMAKE_DSM_COMPILER@") +set(CMAKE_DSM_COMPILER_ARG1 "@_CMAKE_DSM_COMPILER_ARG1@") +set(CMAKE_DSM_COMPILER_AR "@_CMAKE_DSM_COMPILER_AR@") +set(CMAKE_RANLIB "@CMAKE_RANLIB@") +set(CMAKE_DSM_COMPILER_RANLIB "@_CMAKE_DSM_COMPILER_RANLIB@") +set(CMAKE_LINKER "@CMAKE_LINKER@") +set(CMAKE_DSM_COMPILER_LOADED 1) +set(CMAKE_DSM_COMPILER_ID "@_CMAKE_DSM_COMPILER_ID@") +set(CMAKE_DSM_COMPILER_VERSION "@_CMAKE_DSM_COMPILER_VERSION@") +set(CMAKE_DSM_COMPILER_ENV_VAR "@_CMAKE_DSM_COMPILER_ENV_VAR@") +@_SET_CMAKE_DSM_COMPILER_ARCHITECTURE_ID@ + +set(CMAKE_DSM_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) +set(CMAKE_DSM_LINKER_PREFERENCE 0) + +@CMAKE_DSM_COMPILER_CUSTOM_CODE@ diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake new file mode 100644 index 0000000..27b56d6 --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake @@ -0,0 +1,79 @@ +if(UNIX) + set(CMAKE_DSM_OUTPUT_EXTENSION .o) +else() + set(CMAKE_DSM_OUTPUT_EXTENSION .obj) +endif() + +set(CMAKE_INCLUDE_FLAG_DSM "-I") + +set(CMAKE_DSM_FLAGS_INIT "$ENV{DSMFLAGS} ${CMAKE_DSM_FLAGS_INIT}") + +# replace for CMake >= 3.11 +foreach(c "" _DEBUG _RELEASE _MINSIZEREL _RELWITHDEBINFO) + string(STRIP "${CMAKE_DSM_FLAGS${c}_INIT}" CMAKE_DSM_FLAGS${c}_INIT) +endforeach() + +set (CMAKE_DSM_FLAGS "${CMAKE_DSM_FLAGS_INIT}" CACHE STRING + "Flags used by the assembler during all build types.") + +if(NOT CMAKE_NOT_USING_CONFIG_FLAGS) + get_property(_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + # default build type is none + if(NOT _GENERATOR_IS_MULTI_CONFIG AND NOT CMAKE_NO_BUILD_TYPE) + set (CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE_INIT} CACHE STRING + "Choose the type of build, options are: None, Debug Release RelWithDebInfo MinSizeRel.") + endif() + unset(_GENERATOR_IS_MULTI_CONFIG) + set (CMAKE_DSM_FLAGS_DEBUG "${CMAKE_DSM_FLAGS_DEBUG_INIT}" CACHE STRING + "Flags used by the assembler during debug builds.") + set (CMAKE_DSM_FLAGS_MINSIZEREL "${CMAKE_DSM_FLAGS_MINSIZEREL_INIT}" CACHE STRING + "Flags used by the assembler during release minsize builds.") + set (CMAKE_DSM_FLAGS_RELEASE "${CMAKE_DSM_FLAGS_RELEASE_INIT}" CACHE STRING + "Flags used by the assembler during release builds.") + set (CMAKE_DSM_FLAGS_RELWITHDEBINFO "${CMAKE_DSM_FLAGS_RELWITHDEBINFO_INIT}" CACHE STRING + "Flags used by the assembler during Release with Debug Info builds.") +endif() + +mark_as_advanced(CMAKE_DSM_FLAGS + CMAKE_DSM_FLAGS_DEBUG + CMAKE_DSM_FLAGS_MINSIZEREL + CMAKE_DSM_FLAGS_RELEASE + CMAKE_DSM_FLAGS_RELWITHDEBINFO + ) +# WITH: cmake_initialize_per_config_variable(CMAKE_DSM_FLAGS "Flags used by the DSM compiler") + +if(NOT CMAKE_DSM_COMPILE_OBJECT) + set(CMAKE_DSM_COMPILE_OBJECT " -o ") +endif() + +if(NOT CMAKE_DSM_CREATE_STATIC_LIBRARY) + set(CMAKE_DSM_CREATE_STATIC_LIBRARY + " cr " + " ") +endif() + +if(NOT CMAKE_DSM_LINK_EXECUTABLE) + set(CMAKE_DSM_LINK_EXECUTABLE + " -o ") +endif() + +if(NOT CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG) + set(CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG ${CMAKE_SHARED_LIBRARY_RUNTIME_DSM_FLAG}) +endif() + +if(NOT CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG_SEP) + set(CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG_SEP ${CMAKE_SHARED_LIBRARY_RUNTIME_DSM_FLAG_SEP}) +endif() + +if(NOT CMAKE_EXECUTABLE_RPATH_LINK_DSM_FLAG) + set(CMAKE_EXECUTABLE_RPATH_LINK_DSM_FLAG ${CMAKE_SHARED_LIBRARY_RPATH_LINK_DSM_FLAG}) +endif() + +# to be done +if(NOT CMAKE_DSM_CREATE_SHARED_LIBRARY) + set(CMAKE_DSM_CREATE_SHARED_LIBRARY) +endif() + +if(NOT CMAKE_DSM_CREATE_SHARED_MODULE) + set(CMAKE_DSM_CREATE_SHARED_MODULE) +endif() diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake new file mode 100644 index 0000000..d3bda8b --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake @@ -0,0 +1,87 @@ +include(${CMAKE_ROOT}/Modules/CMakeDetermineCompiler.cmake) + +if (NOT CMAKE_DSM_COMPILER) + message(FATAL_ERROR "Need CMAKE_DSM_COMPILER set") +endif() + +_cmake_find_compiler_path(DSM) +mark_as_advanced(CMAKE_DSM_COMPILER) + +if (NOT CMAKE_DSM_COMPILER_ID) + # Table of per-vendor compiler id flags with expected output. + list(APPEND CMAKE_DSM_COMPILER_ID_VENDORS GNU ) + set(CMAKE_DSM_COMPILER_ID_VENDOR_FLAGS_GNU "--version") + set(CMAKE_DSM_COMPILER_ID_VENDOR_REGEX_GNU "(GNU assembler)|(GCC)|(Free Software Foundation)") + + include(CMakeDetermineCompilerId) + cmake_determine_compiler_id_vendor(DSM "") + +endif() + +if (NOT _CMAKE_TOOLCHAIN_LOCATION) + get_filename_component(_CMAKE_TOOLCHAIN_LOCATION "${CMAKE_DSM_COMPILER}" PATH) +endif() + +if (CMAKE_DSM_COMPILER_ID) + if (CMAKE_DSM_COMPILER_VERSION) + set(_version " ${CMAKE_DSM_COMPILER_VERSION}") + else() + set(_version "") + endif() + message(STATUS "The DSM compiler identification is ${CMAKE_DSM_COMPILER_ID}${_version}") + unset(_version) +else() + message(STATUS "The DSM compiler identification is unknown") +endif() + +if (NOT _CMAKE_TOOLCHAIN_PREFIX) + get_filename_component(COMPILER_BASENAME "${CMAKE_DSM_COMPILER}" NAME) + if (COMPILER_BASENAME MATCHES "^(.+1)g?as(-[0-9]+\\.[0-9]+\\.[0-9]+)?(\\.exe)?$") + set(_CMAKE_TOOLCHAIN_PREFIX ${CMAKE_MATCH_1}) + endif() + +endif() + +set(_CMAKE_PROCESSING_LANGUAGE "DSM") +find_program(CMAKE_DSM_COMPILER_AR NAMES ${_CMAKE_TOOLCHAIN_PREFIX}ar HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_RANLIB NAMES ${_CMAKE_TOOLCHAIN_PREFIX}ranlib HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_STRIP NAMES ${_CMAKE_TOOLCHAIN_PREFIX}strip HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_NM NAMES ${_CMAKE_TOOLCHAIN_PREFIX}nm HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_OBJDUMP NAMES ${_CMAKE_TOOLCHAIN_PREFIX}objdump HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_OBJCOPY NAMES ${_CMAKE_TOOLCHAIN_PREFIX}objcopy HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) + +unset(_CMAKE_PROCESSING_LANGUAGE) + +set(CMAKE_DSM_COMPILER_ENV_VAR "DSM") + +if (CMAKE_DSM_COMPILER) + message(STATUS "Found DSM assembler: ${CMAKE_DSM_COMPILER}") +else() + message(STATUS "Didn't find assembler") +endif() + +foreach(_var + COMPILER + COMPILER_ID + COMPILER_ARG1 + COMPILER_ENV_VAR + COMPILER_AR + COMPILER_RANLIB + COMPILER_VERSION + ) + set(_CMAKE_DSM_${_var} "${CMAKE_DSM_${_var}}") +endforeach() + +configure_file("${CMAKE_CURRENT_LIST_DIR}/CMakeDSMCompiler.cmake.in" + "${CMAKE_PLATFORM_INFO_DIR}/CMakeDSMCompiler.cmake" @ONLY) + +foreach(_var + COMPILER + COMPILER_ID + COMPILER_ARG1 + COMPILER_ENV_VAR + COMPILER_AR + COMPILER_VERSION + ) + unset(_CMAKE_DSM_${_var}) +endforeach() diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake new file mode 100644 index 0000000..5c514ea --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake @@ -0,0 +1,7 @@ +set(_ASM_COMPILER_WORKS 0) + +if(CMAKE_DSM_COMPILER) + set(_DSM_COMPILER_WORKS) +endif() + +set(CMAKE_DSM_COMPILER_WORKS ${_DSM_COMPILER_WORKS} CACHE INTERNAL "") diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake new file mode 100644 index 0000000..bd2995e --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake @@ -0,0 +1 @@ +set(CMAKE_EXECUTABLE_SUFFIX ".elf") diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py b/vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py new file mode 100644 index 0000000..4cc9d3c --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py @@ -0,0 +1,24 @@ +from conans import ConanFile +import os +import shutil + + +class Ps2devCMakeToolchainConan(ConanFile): + name = "ps2dev-cmaketoolchain" + description = "CMake toolchain script for ps2dev" + topics = "ps2", "sdk", "library", "sony", "playstation", "ps2" + + def export_sources(self): + self.copy("*.cmake*", dst="cmake") + self.copy("Platform", dst="cmake") + + def package(self): + shutil.copytree(os.path.join(self.source_folder, "cmake"), + os.path.join(self.package_folder, "cmake")) + + def package_info(self): + self.user_info.cmake_dir = os.path.join(self.package_folder, "cmake").replace("\\", "/") + + cmake_toolchain_file = os.path.join(self.package_folder, "cmake", "toolchain_ps2_ee.cmake").replace("\\", "/") + self.user_info.cmake_toolchain_file = cmake_toolchain_file + self.cpp_info.CONAN_CMAKE_TOOLCHAIN_FILE = cmake_toolchain_file diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake new file mode 100644 index 0000000..da1efad --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake @@ -0,0 +1,92 @@ +cmake_minimum_required(VERSION 3.7) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + +set(CMAKE_SYSTEM_NAME "PlayStation2") +set(CMAKE_SYSTEM_PROCESSOR "mips64r5900el") +set(CMAKE_SYSTEM_VERSION 1) + +set(CMAKE_NO_SYSTEM_FROM_IMPORTED ON) + +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +if(NOT DEFINED ENV{PS2DEV}) + message(FATAL_ERROR "Need environment variable PS2DEV set") +endif() +if(NOT DEFINED ENV{PS2SDK}) + message(FATAL_ERROR "Need environment variable PS2SDK set") +endif() +if(NOT DEFINED ENV{GSKIT}) + message(FATAL_ERROR "Need environment variable PS2SDK set") +endif() + +set(PS2DEV "$ENV{PS2DEV}") +set(PS2SDK "$ENV{PS2SDK}") +set(GSKIT "$ENV{GSKIT}") + +if(NOT IS_DIRECTORY "${PS2DEV}") + message(FATAL_ERROR "PS2DEV must be a folder path (${PS2DEV})") +endif() + +if(NOT IS_DIRECTORY "${PS2SDK}") + message(FATAL_ERROR "PS2SDK must be a folder path (${PS2SDK})") +endif() + +if(NOT IS_DIRECTORY "${GSKIT}") + message(FATAL_ERROR "GSKIT must be a folder path (${GSKIT})") +endif() + +set(CMAKE_DSM_SOURCE_FILE_EXTENSIONS "dsm") + +set(CMAKE_C_COMPILER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-gcc" CACHE FILEPATH "C compiler") +set(CMAKE_CXX_COMPILER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-g++" CACHE FILEPATH "CXX compiler") +set(CMAKE_ASM_COMPILER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-g++" CACHE FILEPATH "ASM assembler") +set(CMAKE_DSM_COMPILER "${PS2DEV}/dvp/bin/dvp-as" CACHE FILEPATH "DSM assembler") +set(CMAKE_AR "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-ar" CACHE FILEPATH "archiver") +set(CMAKE_LINKER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-ld" CACHE FILEPATH "Linker") +set(CMAKE_RANLIB "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-ranlib" CACHE FILEPATH "ranlib") +set(CMAKE_STRIP "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-strip" CACHE FILEPATH "strip") + +set(CMAKE_ASM_FLAGS_INIT "-G0 -I\"${PS2SDK}/ee/include\" -I\"${PS2SDK}/common/include\" -D_EE") +set(CMAKE_C_FLAGS_INIT "-G0 -fno-common -I\"${PS2SDK}/ee/include\" -I\"${PS2SDK}/common/include\" -D_EE") +set(CMAKE_CXX_FLAGS_INIT "-G0 -fno-common -I\"${PS2SDK}/ee/include\" -I\"${PS2SDK}/common/include\" -D_EE") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-G0 -L\"${PS2SDK}/ee/lib\" -Wl,-r -Wl,-d") + +set(CMAKE_FIND_ROOT_PATH "${PS2DEV}/ee" "${PS2SDK}/ee" "${GSKIT}") +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +set(PS2 1) +set(EE 1) + +set(CMAKE_EXECUTABLE_SUFFIX ".elf") + +function(add_erl_executable TARGET) + cmake_parse_arguments("AEE" "" "OUTPUT_VAR" "" ${ARGN}) + + get_target_property(output_dir "${TARGET}" RUNTIME_OUTPUT_DIRECTORY) + if(NOT output_dir) + set(output_dir ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + get_target_property(output_name ${TARGET} OUTPUT_NAME) + if(NOT output_name) + set(output_name ${TARGET}) + endif() + set(outfile "${output_dir}/${output_name}.erl") + + add_custom_command(OUTPUT "${outfile}" + COMMAND "${CMAKE_COMMAND}" -E copy "$" "${outfile}" + COMMAND "${CMAKE_STRIP}" --strip-unneeded -R .mdebug.eabi64 -R .reginfo -R .comment "${outfile}" + DEPENDS ${TARGET} + ) + add_custom_target("${TARGET}_erl" ALL + DEPENDS "${outfile}" + ) + + if(AEE_OUTPUT_VAR) + set("${AEE_OUTPUT_VAR}" "${outfile}" PARENT_SCOPE) + endif() +endfunction() diff --git a/vendor/librw/conan/playstation2 b/vendor/librw/conan/playstation2 new file mode 100644 index 0000000..ddad52b --- /dev/null +++ b/vendor/librw/conan/playstation2 @@ -0,0 +1,12 @@ +[settings] +os=Playstation2 +arch=mips +compiler=gcc +compiler.version=3.2 +compiler.libcxx=libstdc++ +build_type=Release +[options] +librw:platform=ps2 +[build_requires] +ps2dev-ps2toolchain/unknown@madebr/testing +[env] diff --git a/vendor/librw/conanfile.py b/vendor/librw/conanfile.py new file mode 100644 index 0000000..4cefb49 --- /dev/null +++ b/vendor/librw/conanfile.py @@ -0,0 +1,136 @@ +from conans import ConanFile, CMake, tools +from conans.errors import ConanException, ConanInvalidConfiguration +import os +import shutil +import textwrap + + +class LibrwConan(ConanFile): + name = "librw" + version = "master" + license = "MIT" + settings = "os", "arch", "compiler", "build_type" + generators = "cmake", "cmake_find_package" + options = { + "platform": ["null", "gl3", "d3d9", "ps2"], + "gl3_gfxlib": ["glfw", "sdl2"], + } + default_options = { + "platform": "gl3", + "gl3_gfxlib": "glfw", + "openal:with_external_libs": False, + "sdl2:vulkan": False, + "sdl2:opengl": True, + "sdl2:sdl2main": True, + } + no_copy_source = True + + @property + def _os_is_playstation2(self): + try: + return self.settings.os == "Playstation2" + except ConanException: + return False + + def config_options(self): + if self._os_is_playstation2: + self.options.platform = "ps2" + if self.settings.os == "Windows": + self.options.platform = "d3d9" + self.options["sdl2"].directx = False + + def configure(self): + if self.options.platform != "gl3": + del self.options.gl3_gfxlib + + def validate(self): + if self.options.platform == "d3d9" and self.settings.os != "Windows": + raise ConanInvalidConfiguration("platform=d3d9 can only be built for os=Windows") + if self._os_is_playstation2: + if self.options.platform not in ("null", "ps2"): + raise ConanInvalidConfiguration("os=Playstation2 only supports platform=(null,ps2)") + + def requirements(self): + if self.options.platform == "gl3": + if self.options.gl3_gfxlib == "glfw": + self.requires("glfw/3.3.2") + elif self.options.gl3_gfxlib == "sdl2": + self.requires("sdl2/2.0.12@bincrafters/stable") + elif self.options.platform == "ps2": + self.requires("ps2dev-ps2sdk/unknown@madebr/testing") + if self._os_is_playstation2: + self.requires("ps2dev-cmaketoolchain/{}".format(self.version)) + + def export_sources(self): + for d in ("cmake", "skeleton", "src", "tools"): + shutil.copytree(src=d, dst=os.path.join(self.export_sources_folder, d)) + self.copy("args.h") + self.copy("rw.h") + self.copy("CMakeLists.txt") + self.copy("LICENSE") + + @property + def _librw_platform(self): + return { + "null": "NULL", + "gl3": "GL3", + "d3d9": "D3D9", + "ps2": "PS2", + }[str(self.options.platform)] + + def build(self): + if self.source_folder == self.build_folder: + raise Exception("cannot build with source_folder == build_folder") + if self.options.platform == "gl3" and self.options.gl3_gfxlib == "glfw": + tools.save("Findglfw3.cmake", + textwrap.dedent( + """ + if(NOT TARGET glfw) + message(STATUS "Creating glfw TARGET") + add_library(glfw INTERFACE IMPORTED) + set_target_properties(glfw PROPERTIES + INTERFACE_LINK_LIBRARIES CONAN_PKG::glfw) + endif() + """), append=True) + tools.save("CMakeLists.txt", + textwrap.dedent( + """ + cmake_minimum_required(VERSION 3.0) + project(cmake_wrapper) + + include("{}/conanbuildinfo.cmake") + conan_basic_setup(TARGETS) + + add_subdirectory("{}" librw) + """).format(self.install_folder.replace("\\", "/"), + self.source_folder.replace("\\", "/"))) + cmake = CMake(self) + env = {} + cmake.definitions["LIBRW_PLATFORM"] = self._librw_platform + cmake.definitions["LIBRW_INSTALL"] = True + cmake.definitions["LIBRW_TOOLS"] = True + if self.options.platform == "gl3": + cmake.definitions["LIBRW_GL3_GFXLIB"] = str(self.options.gl3_gfxlib).upper() + if self._os_is_playstation2: + env["PS2SDK"] = self.deps_cpp_info["ps2dev-ps2sdk"].rootpath + with tools.environment_append(env): + cmake.configure(source_folder=self.build_folder) + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.includedirs.append(os.path.join("include", "librw")) + self.cpp_info.libs = ["librw" if self.settings.compiler == "Visual Studio" else "rw"] + if self.options.platform == "null": + self.cpp_info.defines.append("RW_NULL") + elif self.options.platform == "gl3": + self.cpp_info.defines.append("RW_GL3") + if self.options.gl3_gfxlib == "sdl2": + self.cpp_info.defines.append("LIBRW_SDL2") + elif self.options.platform == "d3d9": + self.cpp_info.defines.append("RW_D3D9") + elif self.options.platform == "ps2": + self.cpp_info.defines.append("RW_PS2") diff --git a/vendor/librw/docker_rebuild_ps2.sh b/vendor/librw/docker_rebuild_ps2.sh new file mode 100755 index 0000000..cf8148f --- /dev/null +++ b/vendor/librw/docker_rebuild_ps2.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +TARGET=release_ps2 + +set -e + +LIBRWDIR=$(dirname "$(readlink -f "$0")") +echo "LIBRWDIR is $LIBRWDIR" + +cd "$LIBRWDIR" + +premake5 gmake + +docker rm librw_instance -f >/dev/null 2>&1 || true +docker stop librw_instance -t 0 >/dev/null 2>&1 || true + +docker pull librw/librw +docker run -v "$LIBRWDIR:/librw:rw,z" --name librw_instance -d librw/librw sleep infinity +docker exec -u builder librw_instance /bin/bash -c "cd /librw/build && make clean config=$TARGET && make config=$TARGET verbose=1" + +docker rm librw_instance -f >/dev/null 2>&1 || true +docker stop librw_instance -t 0 >/dev/null 2>&1 || true diff --git a/vendor/librw/premake-vs2019.cmd b/vendor/librw/premake-vs2019.cmd new file mode 100644 index 0000000..18298f5 --- /dev/null +++ b/vendor/librw/premake-vs2019.cmd @@ -0,0 +1 @@ +premake5 vs2019 \ No newline at end of file diff --git a/vendor/librw/premake5.exe b/vendor/librw/premake5.exe new file mode 100644 index 0000000000000000000000000000000000000000..9048d51e09da5bde02e6337dcfaa603a2cebf292 GIT binary patch literal 1362432 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 G{Qm=J=rvdX literal 0 HcmV?d00001 diff --git a/gamefiles/models/generic.txd b/gamefiles/models/generic.txd new file mode 100644 index 0000000000000000000000000000000000000000..8e0d60f38db92130627eb4893d8c5e9117780cbf GIT binary patch literal 1604112 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�)fLQ^ffNGwXLE|OxFx#}u)1NUup~sv`%yoSv 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